test_browser/util/storage_test.js

Summary

Maintainability
B
6 hrs
Test Coverage
'use strict';

/* eslint-env browser, commonjs, node, mocha */

var assert = require('assert');


describe('DOMStorage', function() {
    var DOMStorage = milo.util.storage;
    var domStorage;

    function getLocalStorage() {
        var length = localStorage.length
            , keys = [];

        for (var i = 0; i < length; i++)
            keys.push(localStorage.key(i));

        var data = _.mapToObject(keys, function(key) {
            return localStorage.getItem(key);
        });

        return data;
    }


    beforeEach(function() {
        window.localStorage.clear();
        DOMStorage._storedKeys[false] = {};
        domStorage = new DOMStorage('MiloTest');
    });


    describe('setItem and getItem instance methods', function() {
        it('should store and get strings', function() {
            domStorage.setItem('name', 'milo');
            assert.deepEqual(getLocalStorage(), {
                'MiloTest/name': 'milo',
                'MiloTest/name:___milo_data_type': 'string'
            });
            var itemValue = domStorage.getItem('name');
            assert(itemValue === 'milo');
        });


        it('should store and get numbers', function() {
            domStorage.setItem('year', 2014);
            assert.deepEqual(getLocalStorage(), {
                'MiloTest/year': '2014',
                'MiloTest/year:___milo_data_type': 'number'
            });
            var itemValue = domStorage.getItem('year');
            assert(itemValue === 2014);
        });


        it('should store and get booleans', function() {
            domStorage.setItem('trueValue', true);
            domStorage.setItem('falseValue', false);
            assert.deepEqual(getLocalStorage(), {
                'MiloTest/trueValue': 'true',
                'MiloTest/trueValue:___milo_data_type': 'boolean',
                'MiloTest/falseValue': 'false',
                'MiloTest/falseValue:___milo_data_type': 'boolean',
            });
            var itemValue = domStorage.getItem('trueValue');
            assert(itemValue === true);
            var itemValue = domStorage.getItem('falseValue');
            assert(itemValue === false);
        });


        it('should store and get Date', function() {
            var now = new Date;
            domStorage.setItem('time', now);
            assert.deepEqual(getLocalStorage(), {
                'MiloTest/time': now.toString(),
                'MiloTest/time:___milo_data_type': 'Date'
            });
            var itemValue = domStorage.getItem('time');
            assert(itemValue - now < 1000);
            assert.equal(itemValue.toString(), now.toString());
            assert(itemValue instanceof Date);
        });


        it('should store and get Objects', function() {
            domStorage.setItem('info', { name: 'milo', test: 1, list: ['item1', 2] });
            assert.deepEqual(getLocalStorage(), {
                'MiloTest/info': '{"name":"milo","test":1,"list":["item1",2]}',
                'MiloTest/info:___milo_data_type': 'Object'
            });
            var itemValue = domStorage.getItem('info');
            assert.deepEqual(itemValue, { name: 'milo', test: 1, list: ['item1', 2] });
            assert(itemValue instanceof Object);
        });


        it('should store and get Arrays', function() {
            domStorage.setItem('list', [ 'item1', 2, { item: 3 } ]);
            assert.deepEqual(getLocalStorage(), {
                'MiloTest/list': '["item1",2,{"item":3}]',
                'MiloTest/list:___milo_data_type': 'Array'
            });
            var itemValue = domStorage.getItem('list');
            assert.deepEqual(itemValue, [ 'item1', 2, { item: 3 } ]);
            assert(Array.isArray(itemValue));
        });


        it.skip('should store and get functions', function() {
            function myFunc() { return 1234; }
            domStorage.setItem('myFunc', myFunc);
            assert.deepEqual(getLocalStorage(), {
                'MiloTest/myFunc': 'function myFunc() { return 1234; }',
                'MiloTest/myFunc:___milo_data_type': 'function'
            });
            var itemValue = domStorage.getItem('myFunc');
            assert.equal(itemValue.toString(), myFunc.toString());
            assert.equal(typeof itemValue, 'function');
            assert.equal(itemValue(), 1234);
        });


        it('should store and get RegExp', function() {
            domStorage.setItem('pattern', /ab+c/i);
            assert.deepEqual(getLocalStorage(), {
                'MiloTest/pattern': '/ab+c/i',
                'MiloTest/pattern:___milo_data_type': 'RegExp'
            });
            var itemValue = domStorage.getItem('pattern');
            assert.equal(itemValue.toString(), /ab+c/i.toString());
            assert(itemValue instanceof RegExp);
            assert(itemValue.test('ABBC'));
        });


        it('should store and get Models and ModelPaths (with registration)', function() {
            var Model = milo.Model;
            Model.registerWithDOMStorage();
            var m = new Model({ info: { name: 'milo' } });
            domStorage.setItem('myModel', m);
            domStorage.setItem('mPath', m('.info'));

            assert.deepEqual(getLocalStorage(), {
                'MiloTest/myModel': '{"info":{"name":"milo"}}',
                'MiloTest/myModel:___milo_data_type': 'Model',
                'MiloTest/mPath': '{"name":"milo"}',
                'MiloTest/mPath:___milo_data_type': 'Model'
            });
            var itemValue = domStorage.getItem('myModel');
            assert.deepEqual(m.get(), { info: { name: 'milo' } });
            assert(itemValue instanceof Model);
            var itemValue = domStorage.getItem('mPath');
            assert.deepEqual(m('.info').get(), { name: 'milo' });
            assert(itemValue instanceof Model);
        });


        it('should store and get Component (sub)class instances', function() {
            var Component = milo.Component;
            var comp = Component.createOnElement(undefined, '<span>Test</span>');
            assert(/<div ml-bind="Component:[A-Za-z0-9_]+"><span>Test<\/span><\/div>/.test(comp.el.outerHTML));

            domStorage.setItem('comp', comp);
            assert.equal(localStorage.length, 2);
            var itemValue = domStorage.getItem('comp');
            assert.equal(comp.name, itemValue.name);
            assert.equal(comp.el.outerHTML, itemValue.el.outerHTML);
            assert(itemValue instanceof Component);
        });
    });


    describe('registerDataType class method', function() {
        it('should facilitate storing and getting of custom classes', function() {
            function MyClass(data) {
                this._data = data;
            }

            function myClassSerializer(value) {
                return JSON.stringify(value._data);
            }

            function myClassParser(valueStr) {
                var value = _.jsonParse(valueStr);
                return new MyClass(value);
            }

            DOMStorage.registerDataType('MyClass', myClassSerializer, myClassParser);

            var myClass = new MyClass({ name: 'milo' });
            domStorage.setItem('myClass', myClass);
            assert.deepEqual(getLocalStorage(), {
                'MiloTest/myClass': '{"name":"milo"}',
                'MiloTest/myClass:___milo_data_type': 'MyClass'
            });
            var itemValue = domStorage.getItem('myClass');
            assert.deepEqual(itemValue._data, { name: 'milo' });
            assert(itemValue instanceof MyClass);
        });
    });


    describe('removeItem method', function() {
        it('should remove items', function() {
            assert.equal(localStorage.length, 0);
            domStorage.setItem('name', 'milo');
            assert.equal(localStorage.length, 2);
            domStorage.removeItem('name');
            assert.equal(localStorage.length, 0);
        });
    });


    describe('set and get instance methods', function() {
        var itemsToStore = {
            name: 'milo',
            test: 1,
            list: [ 'item1', 2 ],
            info: { test: 3 }
        };

        var expectedLocalStorage = {
            'MiloTest/name': 'milo',
            'MiloTest/name:___milo_data_type': 'string',
            'MiloTest/test': '1',
            'MiloTest/test:___milo_data_type': 'number',
            'MiloTest/list': '["item1",2]',
            'MiloTest/list:___milo_data_type': 'Array',
            'MiloTest/info': '{"test":3}',
            'MiloTest/info:___milo_data_type': 'Object',
        };


        function testStoredValues() {
            assert.deepEqual(getLocalStorage(), expectedLocalStorage);
            var itemValues = domStorage.get(['name', 'test', 'list', 'info']);
            assert.deepEqual(itemValues, itemsToStore);
            var itemValues = domStorage.get('name', 'test', 'list', 'info');
            assert.deepEqual(itemValues, itemsToStore);
        }


        it('should store data passed as object to multiple keys', function() {
            domStorage.set(itemsToStore);
            testStoredValues();
        });


        it('should store data passed as list of keys and values in arguments', function() {
            domStorage.set(
                'name', 'milo',
                'test', 1,
                'list', [ 'item1', 2 ],
                'info', { test: 3 }
            );
            testStoredValues();
        });

        it.skip('should send message on QuotaExceededError', function(done) {
            milo.config({ domStorage: { quotaExceeded: {
                message: true,
                throwError: false
            } } });

            var recognizedData;
            function recognizeTrojan(msg, data) {
                recognizedData = _.deepClone(data);
                assert.deepEqual(recognizedData, trojanData);
                done();
            }

            milo.mail.onMessage('quotaexceedederror', recognizeTrojan);

            var trojanData = [];
            for(var i=0; i<1000*1000; i++) trojanData.push(i);
            domStorage.set('trojanData', trojanData);

            milo.config({ domStorage: { quotaExceeded: {
                message: false,
                throwError: true
            } } });
        });

    });


    describe('remove instance method', function() {
        var itemsToStore = {
            name: 'milo',
            test: 1,
            list: [ 'item1', 2 ],
            info: { test: 3 }
        };

        beforeEach(function() {
            assert.equal(localStorage.length, 0);
            domStorage.set(itemsToStore);
            assert.equal(localStorage.length, 8);
        });

        it('should remove multiple keys when passed as array', function() {
            domStorage.remove(['name', 'test', 'list', 'info']);
            assert.equal(localStorage.length, 0);
        });

        it('should remove multiple keys when passed as list', function() {
            domStorage.remove('name', 'test', 'list', 'info');
            assert.equal(localStorage.length, 0);
        });

        it('should remove multiple keys when passed as list/arrays combination', function() {
            domStorage.remove('name', ['test', ['list', 'info']]);
            assert.equal(localStorage.length, 0);
        });
    });


    describe('getAllKeys and getAllItems instance and class methods', function() {
        var itemsToStore = {
            name: 'milo',
            test: 1,
            list: [ 'item1', 2 ],
            info: { test: 3 }
        };

        beforeEach(function() {
            window.localStorage.clear();
            DOMStorage.local._keys[false] = {};
            domStorage.set(itemsToStore);
            assert.equal(localStorage.length, 8);
        });

        it('instance getAllKeys should return the list of stored keys', function() {
            var keys = domStorage.getAllKeys();
            assert.deepEqual(_.object(keys, true), {
                name: true,
                test: true,
                list: true,
                info: true
            });
        });

        it('instance getAllItems should return all stored values', function() {
            var items = domStorage.getAllItems();
            assert.deepEqual(items, {
                name: 'milo',
                test: 1,
                list: [ 'item1', 2 ],
                info: { test: 3 }
            });
        });

        it('class getAllKeys should return the list of stored keys', function() {
            var keys = DOMStorage.local.getAllKeys();
            assert.deepEqual(_.object(keys, true), {
                'MiloTest/name': true,
                'MiloTest/test': true,
                'MiloTest/list': true,
                'MiloTest/info': true
            });
        });

        it('getAllItems should return all stored values', function() {
            var items = DOMStorage.local.getAllItems();
            assert.deepEqual(items, {
                'MiloTest/name': 'milo',
                'MiloTest/test': 1,
                'MiloTest/list': [ 'item1', 2 ],
                'MiloTest/info': { test: 3 }
            });
        });
    });


    describe('messenger', function() {
        this.timeout(10000);

        var win = window.open('', 'test');

        var messageTimestamp = milo.config.domStorage.messageTimestamp;

        beforeEach(function() {
            window.localStorage.clear();
            domStorage.createMessenger();
        });


        it('should store data in localStorage when message is triggered', function(done) {
            domStorage.trigger('testmessage', { test: 1 });

            _.defer(function() {
                var items = DOMStorage.local.getAllItems();
                _.eachKey(items, function(item) {
                    delete item[messageTimestamp];
                });
                assert.deepEqual(items, { 'MiloTest/___milo_message/testmessage': { test: 1} });
                done();
            });
        });


        // TODO the test is always fixed because webdriver started to always block popups
        (isOldFirefox() ? it.skip : it.skip) ('should deliver messages when data is stored with correct key in storage', function(done) {
            _.delay(function() {
                var posted = [];
                domStorage.on('testmessage', function(msg, data) {
                    delete data[messageTimestamp];
                    posted.push({ message: msg, data: data });
                });

                var posted2 = [];
                domStorage.on('anothermessage', function(msg, data) {
                    delete data[messageTimestamp];
                    posted2.push({ message: msg, data: data });
                });

                win.localStorage.setItem('MiloTest/' + milo.config.domStorage.messageKey + 'testmessage', 'test: 2');
                win.localStorage.setItem('MiloTest/' + milo.config.domStorage.messageKey + 'anothermessage', 'test: 3');
                win.localStorage.setItem('anotherkey', 'test: 4');

                _.delay(function() {
                    assert.deepEqual(posted, [{ message: 'testmessage', data: 'test: 2' }]);
                    assert.deepEqual(posted2, [{ message: 'anothermessage', data: 'test: 3' }]);
                    done();
                }, 3000);
            }, 10000);
        });

        
        function isOldFirefox() {
            var matches = navigator.userAgent.match(/firefox\/([0-9]+)/i);
            if (!matches) return;
            var version = +matches[1];
            return version < 37;
        }
    });
});