blob: be81c2d86dc2c0effa7dbb9633e70d456d628ca5 [file] [log] [blame]
// Copyright 2017 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview Unit tests for lib.PreferenceManager.
*/
import {lib} from '../index.js';
/**
* If another window changes a preference to the default it will delete the
* localStorage entry. Here we mock the deleting of a localStorage entry so we
* can test the window correctly return the default value.
*/
it('local-delete-default', (done) => {
const storage = new lib.Storage.Local();
const preferenceManager = new lib.PreferenceManager(storage);
const defaultColor = 'red';
preferenceManager.definePreference('color', defaultColor, function(value) {
assert.strictEqual(value, defaultColor);
done();
});
// Fake current value is 'blue'.
preferenceManager.prefRecords_['color'].currentValue = 'blue';
/**
* Workaround bad extern in closure. cl/307771888
*
* @suppress {checkTypes}
* @return {!StorageEvent}
*/
function newEvent() {
return new StorageEvent('storage', {
storageArea: globalThis.localStorage,
key: '/color',
oldValue: JSON.stringify('blue'),
newValue: null,
});
}
// Simpulate deleting the key on another browser.
const event = newEvent();
globalThis.dispatchEvent(event);
});
/**
* Verify export prefs works.
*/
it('export-json', () => {
const storage = new lib.Storage.Memory();
const manager = new lib.PreferenceManager(storage);
// Declare prefs on the top object. Change some, but keep some as defaults.
manager.definePreferences([
['color', 'red'],
['lines', null],
]);
manager.set('color', 'blue');
// Declare children prefs since we will have to recurse into them.
manager.defineChildren('profiles', function(parent, id) {
const childManager = new lib.PreferenceManager(
parent.storage, `/profiles/${id}`);
childManager.definePreferences([
['host', 'localhost'],
['port', 22],
]);
return childManager;
});
// Add a child with changed prefs.
const child = manager.createChild('profiles', undefined, '1234');
child.set('host', '::1');
child.set('port', 443);
// Add a child that uses the defaults.
manager.createChild('profiles', undefined, '2222');
// Export the prefs and make sure it works.
const prefs = manager.exportAsJson();
assert.deepStrictEqual(prefs, {
color: 'blue',
profiles: [
{
id: '1234',
json: {
host: '::1',
port: 443,
},
},
{
id: '2222',
json: {},
},
],
});
});
/**
* Verify import prefs works.
*/
it('import-json', async () => {
const storage = new lib.Storage.Memory();
const manager = new lib.PreferenceManager(storage);
// Declare prefs on the top object. Change their values from what we import.
manager.definePreferences([
['color', 'red'],
['lines', null],
]);
manager.set('color', 'green');
manager.set('lines', 10);
// Declare children prefs since we will have to recurse into them.
manager.defineChildren('profiles', function(parent, id) {
const childManager = new lib.PreferenceManager(
parent.storage, `/profiles/${id}`);
childManager.definePreferences([
['host', 'localhost'],
['port', 22],
]);
return childManager;
});
// Add some children that won't match.
const child = manager.createChild('profiles', undefined, '6666');
child.set('host', '::1');
child.set('port', 443);
// Add a child that uses the defaults.
manager.createChild('profiles', undefined, '7777');
// Add a child that will be overwritten.
manager.createChild('profiles', undefined, '1234');
child.set('host', 'remote');
// Import a completely diff set of prefs.
const newPrefs = {
color: 'blue',
profiles: [
{
id: '1234',
json: {
host: '::1',
port: 443,
},
},
{
id: '2222',
json: {},
},
],
};
await manager.importFromJson(newPrefs);
// Export the prefs and make sure it matches.
const prefs = manager.exportAsJson();
assert.deepStrictEqual(prefs, newPrefs);
});
/**
* Verify changing prefix works.
*/
it('change-prefix', async () => {
const storage = new lib.Storage.Memory();
// a: p1 and p2 have custom value.
// b: p1 has custom value.
// c: p2 has custom value.
// d: no custom values.
storage.setItems({
'/p1/a': 'p1a',
'/p1/b': 'p1b',
'/p2/a': 'p2a',
'/p2/c': 'p2c',
});
const manager = new lib.PreferenceManager(storage, '/p1');
manager.definePreferences([
['a', 'a'],
['b', 'b'],
['c', 'c'],
['d', 'd'],
]);
await manager.readStorage();
const observed = {};
let observerCount = 0;
for (const c of 'abcd') {
manager.addObserver(c, (value, key) => {
observed[key] = value;
observerCount++;
});
}
// Observe all prefs for '/p1'.
manager.notifyAll();
assert.deepStrictEqual({a: 'p1a', b: 'p1b', c: 'c', d: 'd'}, observed);
assert.equal(4, observerCount);
assert.equal('p1a', manager.get('a'));
assert.equal('p1b', manager.get('b'));
assert.equal('c', manager.get('c'));
assert.equal('d', manager.get('d'));
// Change prefix to '/p2', and validate.
let prefixObserved;
manager.onPrefixChange.addListener((value) => {
prefixObserved = value;
// This should be called before prefs are notified.
assert.equal(4, observerCount);
});
await manager.setPrefix('/p2');
assert.equal('/p2/', prefixObserved);
assert.deepStrictEqual({a: 'p2a', b: 'b', c: 'p2c', d: 'd'}, observed);
assert.equal(8, observerCount);
assert.equal('p2a', manager.get('a'));
assert.equal('b', manager.get('b'));
assert.equal('p2c', manager.get('c'));
assert.equal('d', manager.get('d'));
});
/**
* Verify we always return a copy of array or objects.
*/
it('get-array-or-object-returns-copy', async () => {
const storage = new lib.Storage.Memory();
const manager = new lib.PreferenceManager(storage);
const defaultObject = {default: true};
const defaultArray = ['default', 'array'];
manager.definePreferences([
['object', defaultObject],
['array', defaultArray],
]);
await manager.readStorage();
// Check when value is default.
let o = /** @type {!Object} */(manager.get('object'));
assert.notEqual(o, defaultObject);
assert.deepEqual(o, defaultObject);
o['changed'] = true;
assert.isUndefined(defaultObject['changed']);
let a = /** @type {!Array} */(manager.get('array'));
assert.notEqual(a, defaultArray);
assert.deepEqual(a, defaultArray);
a.push('changed');
assert.equal(defaultArray.length, 2);
// Check when value is not default
const notDefaultObject = {notDefault: true};
const notDefaultArray = ['notdefault', 'array'];
manager.set('object', notDefaultObject);
manager.set('array', notDefaultArray);
o = /** @type {!Object} */(manager.get('object'));
assert.notEqual(o, notDefaultObject);
assert.deepEqual(o, notDefaultObject);
o['changed'] = true;
assert.isUndefined(notDefaultObject['changed']);
a = /** @type {!Array} */(manager.get('array'));
assert.notEqual(a, notDefaultArray);
assert.deepEqual(a, notDefaultArray);
a.push('changed');
assert.equal(notDefaultArray.length, 2);
});
/**
* Verify we clear storage when we set an object back to its default value.
*/
it('set-array-or-object-detects-default', async () => {
const storage = new lib.Storage.Memory();
const manager = new lib.PreferenceManager(storage);
const defaultObject = {default: true};
const defaultArray = ['default', 'array'];
manager.definePreferences([
['object', defaultObject],
['array', defaultArray],
]);
await manager.readStorage();
assert.isUndefined(await storage.getItem('/object'));
assert.isUndefined(await storage.getItem('/array'));
manager.set('object', {notdefault: true});
manager.set('array', ['notdefault', 'array']);
assert.isDefined(await storage.getItem('/object'));
assert.isDefined(await storage.getItem('/array'));
manager.set('object', {default: true});
manager.set('array', ['default', 'array']);
assert.isUndefined(await storage.getItem('/object'));
assert.isUndefined(await storage.getItem('/array'));
});