OLD | NEW |
1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 /** @fileoverview Suite of tests for cr-settings-prefs. */ | 5 /** @fileoverview Suite of tests for cr-settings-prefs. */ |
6 cr.define('cr_settings_prefs', function() { | 6 cr.define('cr_settings_prefs', function() { |
7 /** | 7 /** |
| 8 * Creates a deep copy of the object. |
| 9 * @param {!Object} obj |
| 10 * @return {!Object} |
| 11 */ |
| 12 function deepCopy(obj) { |
| 13 return JSON.parse(JSON.stringify(obj)); |
| 14 } |
| 15 |
| 16 /** |
8 * Mock of chrome.settingsPrivate API. | 17 * Mock of chrome.settingsPrivate API. |
9 * @constructor | 18 * @constructor |
10 * @extends {chrome.settingsPrivate} | 19 * @extends {chrome.settingsPrivate} |
11 */ | 20 */ |
12 function MockSettingsApi() { | 21 function MockSettingsApi() { |
13 this.prefs = {}; | 22 this.prefs = {}; |
14 this.listener_ = null; | 23 this.listener_ = null; |
| 24 this.rand_ = getPseudoRand(); |
15 | 25 |
16 // Hack alert: bind this instance's onPrefsChanged members to this. | 26 // Hack alert: bind this instance's onPrefsChanged members to this. |
17 this.onPrefsChanged = { | 27 this.onPrefsChanged = { |
18 addListener: this.onPrefsChanged.addListener.bind(this), | 28 addListener: this.onPrefsChanged.addListener.bind(this), |
19 removeListener: this.onPrefsChanged.removeListener.bind(this), | 29 removeListener: this.onPrefsChanged.removeListener.bind(this), |
20 }; | 30 }; |
21 | 31 |
22 this.addBooleanPref_('top_level_pref', true); | 32 this.addBooleanPref_('top_level_pref', true); |
23 this.addBooleanPref_('browser.enable_flash', false); | 33 this.addBooleanPref_('browser.enable_flash', false); |
24 this.addBooleanPref_('browser.enable_html5', true); | 34 this.addBooleanPref_('browser.enable_html5', true); |
25 this.addNumberPref_('device.overclock', .6); | 35 this.addNumberPref_('device.overclock', .6); |
26 this.addStringPref_('homepage', 'example.com'); | 36 this.addStringPref_('homepage', 'example.com'); |
| 37 this.addListPref_('languages', generateList(10, String, this.rand_)); |
| 38 this.addListPref_('content.sites', generateList(2, Object, this.rand_)); |
27 } | 39 } |
28 | 40 |
29 MockSettingsApi.prototype = { | 41 MockSettingsApi.prototype = { |
30 // chrome.settingsPrivate overrides. | 42 // chrome.settingsPrivate overrides. |
31 onPrefsChanged: { | 43 onPrefsChanged: { |
32 addListener: function(listener) { | 44 addListener: function(listener) { |
33 this.listener_ = listener; | 45 this.listener_ = listener; |
34 }, | 46 }, |
35 | 47 |
36 removeListener: function(listener) { | 48 removeListener: function(listener) { |
37 expectNotEquals(null, this.listener_); | 49 expectNotEquals(null, this.listener_); |
38 this.listener_ = null; | 50 this.listener_ = null; |
39 }, | 51 }, |
40 }, | 52 }, |
41 | 53 |
42 getAllPrefs: function(callback) { | 54 getAllPrefs: function(callback) { |
43 // Send a copy of prefs to keep our internal state private. | 55 // Send a copy of prefs to keep our internal state private. |
44 var prefs = []; | 56 var prefs = []; |
45 for (var key in this.prefs) | 57 for (var key in this.prefs) |
46 prefs.push(this.createCopy_(this.prefs[key])); | 58 prefs.push(deepCopy(this.prefs[key])); |
47 | 59 |
48 setTimeout(callback.bind(null, prefs)); | 60 setTimeout(callback.bind(null, prefs)); |
49 }, | 61 }, |
50 | 62 |
51 setPref: function(key, value, pageId, callback) { | 63 setPref: function(key, value, pageId, callback) { |
52 var pref = this.prefs[key]; | 64 var pref = this.prefs[key]; |
53 assertNotEquals(undefined, pref); | 65 assertNotEquals(undefined, pref); |
54 assertEquals(typeof value, typeof pref.value); | 66 assertEquals(typeof value, typeof pref.value); |
55 assertEquals(Array.isArray(value), Array.isArray(pref.value)); | 67 assertEquals(Array.isArray(value), Array.isArray(pref.value)); |
56 | 68 |
57 if (this.failNextSetPref_) { | 69 if (this.failNextSetPref_) { |
58 setTimeout(callback.bind(null, false)); | 70 setTimeout(callback.bind(null, false)); |
59 this.failNextSetPref_ = false; | 71 this.failNextSetPref_ = false; |
60 return; | 72 return; |
61 } | 73 } |
| 74 assertNotEquals(true, this.disallowSetPref_); |
62 | 75 |
63 // TODO(michaelpg): support list and dict prefs. | 76 var changed = JSON.stringify(pref.value) != JSON.stringify(value); |
64 var changed = this.prefs[key].value != value; | 77 pref.value = JSON.parse(JSON.stringify(value)); |
65 this.prefs[key].value = value; | 78 setTimeout(callback.bind(null,true)); |
66 setTimeout(function() { | 79 if (changed) { |
67 callback(true); | 80 setTimeout(function() { |
68 // Like chrome.settingsPrivate, send a notification when prefs change. | 81 // Like chrome.settingsPrivate, send a notification when prefs change. |
69 if (changed) | 82 this.sendPrefChanges([key], [deepCopy(value)]); |
70 this.sendPrefChanges([key], [value]); | 83 }.bind(this)); |
71 }.bind(this)); | 84 } |
72 }, | 85 }, |
73 | 86 |
74 getPref: function(key, callback) { | 87 getPref: function(key, callback) { |
75 var pref = this.prefs[key]; | 88 var pref = this.prefs[key]; |
76 assertNotEquals(undefined, pref); | 89 assertNotEquals(undefined, pref); |
77 | 90 |
78 setTimeout(callback.bind(null, this.createCopy_(pref))); | 91 var copy = deepCopy(pref); |
| 92 setTimeout(callback.bind(null, copy)); |
79 }, | 93 }, |
80 | 94 |
81 // Functions used by tests. | 95 // Functions used by tests. |
82 | 96 |
83 /** Instructs the API to fail when setPref is next called. */ | 97 /** Instructs the API to return a failure when setPref is next called. */ |
84 failNextSetPref: function() { | 98 failNextSetPref: function() { |
85 this.failNextSetPref_ = true; | 99 this.failNextSetPref_ = true; |
86 }, | 100 }, |
87 | 101 |
| 102 /** Instructs the API to assert (fail the test) if setPref is called. */ |
| 103 disallowSetPref: function() { |
| 104 this.disallowSetPref_ = true; |
| 105 }, |
| 106 |
| 107 allowSetPref: function() { |
| 108 this.disallowSetPref_ = false; |
| 109 }, |
| 110 |
88 /** | 111 /** |
89 * Notifies the listener of pref changes. | 112 * Notifies the listener of pref changes. |
90 * @param {!Array<string>} keys | 113 * @param {!Array<string>} keys |
91 * @param {!Array<*>} values | 114 * @param {!Array<*>} values |
92 */ | 115 */ |
93 sendPrefChanges: function(keys, values) { | 116 sendPrefChanges: function(keys, values) { |
94 assertNotEquals(null, this.listener_); | 117 assertNotEquals(null, this.listener_); |
95 | 118 |
96 var prefs = []; | 119 var prefs = []; |
97 for (var i = 0; i < keys.length; i++) { | 120 for (var i = 0; i < keys.length; i++) { |
98 var key = keys[i]; | 121 var key = keys[i]; |
99 var pref = this.prefs[key]; | 122 var pref = this.prefs[key]; |
| 123 assertNotEquals(undefined, pref); |
100 pref.value = values[i]; | 124 pref.value = values[i]; |
101 prefs.push(this.createCopy_(pref)); | 125 prefs.push(deepCopy(pref)); |
102 } | 126 } |
103 | 127 |
104 setTimeout(this.listener_.bind(null, prefs)); | 128 this.listener_(prefs); |
| 129 // setTimeout(this.listener_.bind(null, prefs)); |
105 }, | 130 }, |
106 | 131 |
107 // Private methods for use by the mock API. | 132 // Private methods for use by the mock API. |
108 | 133 |
109 /** | 134 /** |
110 * @param {!chrome.settingsPrivate.PrefType} type | 135 * @param {!chrome.settingsPrivate.PrefType} type |
111 * @param {string} key | 136 * @param {string} key |
112 * @param {*} value | 137 * @param {*} value |
113 */ | 138 */ |
114 addPref_: function(type, key, value) { | 139 addPref_: function(type, key, value) { |
(...skipping 22 matching lines...) Expand all Loading... |
137 | 162 |
138 /** | 163 /** |
139 * @param {string} key | 164 * @param {string} key |
140 * @param {string} value | 165 * @param {string} value |
141 */ | 166 */ |
142 addStringPref_: function(key, value) { | 167 addStringPref_: function(key, value) { |
143 this.addPref_('STRING', key, value); | 168 this.addPref_('STRING', key, value); |
144 }, | 169 }, |
145 | 170 |
146 /** | 171 /** |
147 * Creates a deep copy of the preference. | 172 * @param {string} key |
148 * @param {chrome.settingsPrivate.PrefObject} pref | 173 * @param {!Array} value |
149 * @return {chrome.settingsPrivate.PrefObject} | |
150 */ | 174 */ |
151 createCopy_: function(pref) { | 175 addListPref_: function(key, value) { |
152 return JSON.parse(JSON.stringify(pref)); | 176 this.addPref_(chrome.settingsPrivate.PrefType.LIST, key, value); |
153 }, | 177 }, |
154 }; | 178 }; |
155 | 179 |
156 /** | 180 /** |
157 * Helper function to deal with asynchronicity by repeatedly queueing | 181 * Helper function to deal with asynchronicity by repeatedly queueing |
158 * tasks to check if the callback returns true. | 182 * tasks to check if the callback returns true. |
159 * @param {function(): boolean} callback | 183 * @param {function(): boolean} callback |
160 * @return {Promise} | 184 * @return {Promise} |
161 */ | 185 */ |
162 function waitUntilTrue(callback) { | 186 function waitUntilTrue(callback) { |
163 return new Promise(function(resolve, reject) { | 187 return new Promise(function(resolve, reject) { |
164 var interval = setInterval(function() { | 188 var interval = setInterval(function() { |
165 if (callback.call()) { | 189 if (callback.call()) { |
166 clearInterval(interval); | 190 clearInterval(interval); |
167 resolve(); | 191 resolve(); |
168 } | 192 } |
169 }); | 193 }); |
170 }); | 194 }); |
171 } | 195 } |
172 | 196 |
| 197 // Pseudo-random number generator using Fibonacci LFSR. |
| 198 function getPseudoRand() { |
| 199 // Start with any integer in [1, 65535]. |
| 200 var lfsr = 3465; // Chromium's melting point (F). |
| 201 var bit; |
| 202 return function() { |
| 203 bit = ((lfsr >> 0) ^ (lfsr >> 2) ^ (lfsr >> 3) ^ (lfsr >> 5) ) & 1; |
| 204 lfsr = (lfsr >> 1) | (bit << 15); |
| 205 return lfsr; |
| 206 }; |
| 207 } |
| 208 |
| 209 function generateList(length, valueType, rand) { |
| 210 var list = []; |
| 211 for (var i = 0; i < length; i++) { |
| 212 if (valueType == Boolean) { |
| 213 list.push(Boolean(rand() % 2)); |
| 214 } else if (valueType == Number) { |
| 215 list.push(rand()); |
| 216 } else if (valueType == String) { |
| 217 // Allow empty strings. |
| 218 list.push(generateString(rand() % 20, rand)); |
| 219 } else if (valueType == Object) { |
| 220 // Don't allow empty objects. |
| 221 list.push(generateObject(rand() % 2 + 1, rand)); |
| 222 } |
| 223 } |
| 224 |
| 225 return list; |
| 226 }; |
| 227 |
| 228 function generateString(length, rand) { |
| 229 var s = ''; |
| 230 for (var i = 0; i < length; i++) |
| 231 s += String.fromCharCode(65 + rand() % 26); |
| 232 return s; |
| 233 } |
| 234 |
| 235 function generateObject(numProps, rand) { |
| 236 var obj = {}; |
| 237 var types = [Boolean, Number, String, Array]; |
| 238 var typesIndex = 0; |
| 239 for (var i = 0; i < numProps; i++) { |
| 240 var prop; |
| 241 do { |
| 242 prop = generateString(rand() % 20 + 1, rand); |
| 243 } while (obj[prop] != undefined); |
| 244 switch(types[typesIndex++ % types.length]) { |
| 245 case Boolean: |
| 246 obj[prop] = Boolean(rand() % 2); |
| 247 break; |
| 248 case Number: |
| 249 obj[prop] = rand(); |
| 250 break; |
| 251 case String: |
| 252 obj[prop] = generateString(rand() % 20, rand); |
| 253 break; |
| 254 case Array: |
| 255 // Generate a list of booleans, numbers or strings. |
| 256 obj[prop] = generateList(rand() % 10, types[rand() % 3], rand); |
| 257 break; |
| 258 } |
| 259 } |
| 260 return obj; |
| 261 } |
| 262 /* |
| 263 var pref = { |
| 264 type: 'LIST', |
| 265 key: key, |
| 266 value: list |
| 267 }; |
| 268 */ |
| 269 |
173 function registerTests() { | 270 function registerTests() { |
174 suite('CrSettingsPrefs', function() { | 271 suite('CrSettingsPrefs', function() { |
175 /** | 272 /** |
176 * Prefs instance created before each test. | 273 * Prefs instance created before each test. |
177 * @type {CrSettingsPrefs} | 274 * @type {CrSettingsPrefs} |
178 */ | 275 */ |
179 var prefs; | 276 var prefs; |
180 | 277 |
181 /** @type {MockSettingsApi} */ | 278 /** @type {MockSettingsApi} */ |
182 var mockApi = null; | 279 var mockApi = null; |
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
222 // We've already registered an error, so skip the pref. | 319 // We've already registered an error, so skip the pref. |
223 skipPref = true; | 320 skipPref = true; |
224 break; | 321 break; |
225 } | 322 } |
226 } | 323 } |
227 if (skipPref) | 324 if (skipPref) |
228 continue; | 325 continue; |
229 | 326 |
230 expectEquals(expectedPref.key, actualPref.key); | 327 expectEquals(expectedPref.key, actualPref.key); |
231 expectEquals(expectedPref.type, actualPref.type); | 328 expectEquals(expectedPref.type, actualPref.type); |
232 if (expectedPref.type != chrome.settingsPrivate.LIST) { | 329 if (expectedPref.type != chrome.settingsPrivate.PrefType.LIST) { |
233 expectEquals(expectedPref.value, actualPref.value); | 330 expectEquals(expectedPref.value, actualPref.value); |
234 } else { | 331 } else { |
235 expectEquals(JSON.stringify(expectedPref.value), | 332 expectEquals(JSON.stringify(expectedPref.value), |
236 JSON.stringify(actualPref.value)); | 333 JSON.stringify(actualPref.value)); |
237 } | 334 } |
238 } | 335 } |
239 }); | 336 }); |
240 | 337 |
241 test('forwards pref changes to API', function() { | 338 test('forwards pref changes to API', function(done) { |
| 339 var rand = getPseudoRand(); |
| 340 rand();rand(); |
| 341 |
242 // Test that cr-settings-prefs uses the setPref API. | 342 // Test that cr-settings-prefs uses the setPref API. |
243 prefs.set('prefs.browser.enable_flash.value', true); | 343 prefs.set('prefs.browser.enable_flash.value', true); |
244 expectTrue(mockApi.prefs['browser.enable_flash'].value); | 344 expectTrue(mockApi.prefs['browser.enable_flash'].value); |
245 prefs.set('prefs.device.overclock.value', 1.2); | 345 prefs.set('prefs.device.overclock.value', 1.2); |
246 expectEquals(1.2, mockApi.prefs['device.overclock'].value); | 346 expectEquals(1.2, mockApi.prefs['device.overclock'].value); |
247 prefs.set('prefs.homepage.value', 'chromium.org'); | 347 prefs.set('prefs.homepage.value', 'chromium.org'); |
248 expectEquals('chromium.org', mockApi.prefs['homepage'].value); | 348 expectEquals('chromium.org', mockApi.prefs['homepage'].value); |
| 349 var list = generateList(10, String, rand); |
| 350 prefs.set('prefs.languages.value', deepCopy(list)); |
| 351 expectEquals(JSON.stringify(list), |
| 352 JSON.stringify(mockApi.prefs['languages'].value)); |
| 353 var newList = deepCopy(mockApi.prefs['content.sites'].value); |
| 354 var dict = newList[1]; |
| 355 var prop = Object.keys(dict)[0]; |
| 356 // Set a property (and append _ to ensure the value is different) |
| 357 dict[prop] = generateString(10, rand) + '_'; |
| 358 // var listOfDicts = generateList(3, Object, rand); |
| 359 prefs.set('prefs.content.sites.value', deepCopy(newList)); |
| 360 expectEquals(JSON.stringify(newList), |
| 361 JSON.stringify(mockApi.prefs['content.sites'].value)); |
| 362 PolymerTest.async(function() { |
| 363 mockApi.disallowSetPref(); |
| 364 prefs.set('prefs.content.sites.value', deepCopy(newList)); |
| 365 mockApi.allowSetPref(); |
249 | 366 |
250 // Test that when setPref fails, the pref is reverted locally. | 367 // Test that when setPref fails, the pref is reverted locally. |
251 mockApi.failNextSetPref(); | 368 mockApi.failNextSetPref(); |
252 prefs.set('prefs.browser.enable_flash.value', false); | 369 prefs.set('prefs.browser.enable_flash.value', false); |
253 mockApi.failNextSetPref(); | 370 mockApi.failNextSetPref(); |
254 prefs.set('prefs.device.overclock.value', 2.0); | 371 prefs.set('prefs.device.overclock.value', 2.0); |
255 mockApi.failNextSetPref(); | 372 mockApi.failNextSetPref(); |
256 prefs.set('prefs.homepage.value', 'invalid-url'); | 373 prefs.set('prefs.homepage.value', 'invalid-url'); |
| 374 mockApi.failNextSetPref(); |
| 375 prefs.set('prefs.languages.value', generateList(10, String, rand)); |
| 376 mockApi.failNextSetPref(); |
| 377 prefs.set('prefs.content.sites.value', generateList(3, Object, rand)); |
| 378 }).async(function() { |
257 | 379 |
258 // Multiple asynchronous round trips need to happen. | 380 // Multiple asynchronous round trips need to happen. |
259 return Promise.all([ | 381 Promise.all([ |
260 waitUntilTrue(function() { | 382 waitUntilTrue(function() { |
261 return prefs.prefs.browser.enable_flash.value; | 383 return prefs.prefs.browser.enable_flash.value; |
262 }), | 384 }), |
263 waitUntilTrue(function() { | 385 waitUntilTrue(function() { |
264 return prefs.prefs.device.overclock.value == 1.2; | 386 return prefs.prefs.device.overclock.value == 1.2; |
265 }), | 387 }), |
266 waitUntilTrue(function() { | 388 waitUntilTrue(function() { |
267 return prefs.prefs.homepage.value == 'chromium.org'; | 389 return prefs.prefs.homepage.value == 'chromium.org'; |
268 }), | 390 }), |
269 ]); | 391 waitUntilTrue(function() { |
| 392 return JSON.stringify(prefs.prefs.languages.value) == |
| 393 JSON.stringify(list); |
| 394 }), |
| 395 waitUntilTrue(function() { |
| 396 return JSON.stringify(prefs.prefs.content.sites.value) == |
| 397 JSON.stringify(newList); |
| 398 }), |
| 399 ]).then(function() { |
| 400 done(); |
| 401 }); |
| 402 }); |
270 }); | 403 }); |
271 | 404 |
272 test('responds to API changes', function(done) { | 405 test('responds to API changes', function(done) { |
273 mockApi.sendPrefChanges(['top_level_pref'], [false]); | 406 var rand = getPseudoRand(); |
| 407 |
| 408 var topLevelPref = false; |
| 409 var contentSites = generateList(2, Object, rand); |
| 410 contentSites = [{E: 'e', F: 'ff'}, {G: true, H: false}]; |
| 411 |
| 412 mockApi.disallowSetPref(); |
274 PolymerTest.async(function() { | 413 PolymerTest.async(function() { |
275 expectEquals(false, prefs.prefs.top_level_pref.value); | 414 mockApi.sendPrefChanges(['top_level_pref', 'content.sites'], |
| 415 [topLevelPref, deepCopy(contentSites)]); |
| 416 }).async(function() { |
| 417 expectEquals(topLevelPref, prefs.prefs.top_level_pref.value); |
| 418 expectEquals(JSON.stringify(contentSites), |
| 419 JSON.stringify(prefs.prefs.content.sites.value)); |
276 | 420 |
277 mockApi.sendPrefChanges(['top_level_pref'], [true]); | 421 topLevelPref = true; |
| 422 var oldContentSites = contentSites; |
| 423 contentSites = deepCopy(contentSites); |
| 424 var dict = contentSites[1]; |
| 425 var prop = Object.keys(contentSites[1])[0]; |
| 426 // Set a property (and append _ to ensure the value is different) |
| 427 dict[prop] = generateString(10, rand) + '_'; |
| 428 mockApi.sendPrefChanges(['top_level_pref', 'content.sites'], |
| 429 [topLevelPref, deepCopy(contentSites)]); |
278 }).async(function() { | 430 }).async(function() { |
279 expectEquals(true, prefs.prefs.top_level_pref.value); | 431 expectEquals(topLevelPref, prefs.prefs.top_level_pref.value); |
280 done(); | 432 expectEquals(JSON.stringify(contentSites), |
281 }); | 433 JSON.stringify(prefs.prefs.content.sites.value)); |
| 434 }).async(function() { |
| 435 // Shouldn't respond to non-changes. |
| 436 mockApi.disallowSetPref(); |
| 437 mockApi.sendPrefChanges(['top_level_pref', 'content.sites'], |
| 438 [topLevelPref, deepCopy(contentSites)]); |
| 439 }).async(function() {} |
| 440 ).async(done); |
| 441 }); |
| 442 }); |
| 443 |
| 444 suite('pseudo-random number generator', function() { |
| 445 test('generates pseudo-random numbers', function() { |
| 446 var numbers = {}; |
| 447 var rand1 = getPseudoRand(); |
| 448 var rand2 = getPseudoRand(); |
| 449 for (var i = 0; i < 1000; i++) { |
| 450 var num1 = rand1(); |
| 451 var num2 = rand2(); |
| 452 // Generators should have the same seed and algorithm. |
| 453 expectEquals(num1, num2); |
| 454 // Generator shouldn't cycle in the first few numbers. |
| 455 expectEquals(undefined, numbers[num1]); |
| 456 numbers[num1] = true; |
| 457 } |
| 458 }); |
| 459 }); |
| 460 |
| 461 suite('list generator', function() { |
| 462 var rand; |
| 463 |
| 464 function checkList(length, type) { |
| 465 var list = generateList(length, type, rand); |
| 466 expectEquals(length, list.length); |
| 467 for (var i = 0; i < length; i++) |
| 468 expectEquals(typeof list[i], type.name.toLowerCase()); |
| 469 return list; |
| 470 } |
| 471 |
| 472 setup(function() { |
| 473 rand = getPseudoRand(); |
| 474 }); |
| 475 |
| 476 test('flat lists', function() { |
| 477 checkList(10, Boolean); |
| 478 checkList(20, Number); |
| 479 checkList(5, String); |
| 480 }); |
| 481 |
| 482 test('object lists', function() { |
| 483 var list = checkList(40, Object); |
| 484 for (var i = 0; i < 40; i++) |
| 485 expectGT(Object.keys(list[i]).length, 0); |
282 }); | 486 }); |
283 }); | 487 }); |
284 } | 488 } |
285 | 489 |
286 return { | 490 return { |
287 registerTests: registerTests, | 491 registerTests: registerTests, |
288 }; | 492 }; |
289 }); | 493 }); |
OLD | NEW |