| OLD | NEW |
| 1 /* | 1 /* |
| 2 * This file is part of Adblock Plus <https://adblockplus.org/>, | 2 * This file is part of Adblock Plus <https://adblockplus.org/>, |
| 3 * Copyright (C) 2006-present eyeo GmbH | 3 * Copyright (C) 2006-present eyeo GmbH |
| 4 * | 4 * |
| 5 * Adblock Plus is free software: you can redistribute it and/or modify | 5 * Adblock Plus is free software: you can redistribute it and/or modify |
| 6 * it under the terms of the GNU General Public License version 3 as | 6 * it under the terms of the GNU General Public License version 3 as |
| 7 * published by the Free Software Foundation. | 7 * published by the Free Software Foundation. |
| 8 * | 8 * |
| 9 * Adblock Plus is distributed in the hope that it will be useful, | 9 * Adblock Plus is distributed in the hope that it will be useful, |
| 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of | 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 12 * GNU General Public License for more details. | 12 * GNU General Public License for more details. |
| 13 * | 13 * |
| 14 * You should have received a copy of the GNU General Public License | 14 * You should have received a copy of the GNU General Public License |
| 15 * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. | 15 * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. |
| 16 */ | 16 */ |
| 17 | 17 |
| 18 "use strict"; | 18 "use strict"; |
| 19 | 19 |
| 20 const assert = require("assert"); |
| 21 |
| 20 let { | 22 let { |
| 21 createSandbox, setupTimerAndFetch, setupRandomResult, unexpectedError | 23 createSandbox, setupTimerAndFetch, setupRandomResult |
| 22 } = require("./_common"); | 24 } = require("./_common"); |
| 23 | 25 |
| 24 let Prefs = null; | 26 let Prefs = null; |
| 25 let Utils = null; | 27 let Utils = null; |
| 26 let Notification = null; | 28 let Notification = null; |
| 27 | 29 |
| 28 // Only starting NodeJS 10 that URL is in the global space. | 30 // Only starting NodeJS 10 that URL is in the global space. |
| 29 const {URL} = require("url"); | 31 const {URL} = require("url"); |
| 30 | 32 |
| 31 exports.setUp = function(callback) | 33 describe("Notifications", () => |
| 32 { | 34 { |
| 33 // Inject our Array and JSON to make sure that instanceof checks on arrays | 35 let runner = {}; |
| 34 // within the sandbox succeed even with data passed in from outside. | 36 |
| 35 let globals = Object.assign({Array, JSON}, | 37 beforeEach(() => |
| 36 setupTimerAndFetch.call(this), setupRandomResult.call(this)); | 38 { |
| 37 | 39 runner = {}; |
| 38 let sandboxedRequire = createSandbox({globals}); | 40 // Inject our Array and JSON to make sure that instanceof checks on arrays |
| 39 ( | 41 // within the sandbox succeed even with data passed in from outside. |
| 40 {Prefs} = sandboxedRequire("./stub-modules/prefs"), | 42 let globals = Object.assign({Array, JSON}, |
| 41 {Utils} = sandboxedRequire("./stub-modules/utils"), | 43 setupTimerAndFetch.call(runner), setupRandomResult.call(runner)); |
| 42 {Notification} = sandboxedRequire("../lib/notification") | 44 |
| 43 ); | 45 let sandboxedRequire = createSandbox({globals}); |
| 44 | 46 ( |
| 45 callback(); | 47 {Prefs} = sandboxedRequire("./stub-modules/prefs"), |
| 46 }; | 48 {Utils} = sandboxedRequire("./stub-modules/utils"), |
| 47 | 49 {Notification} = sandboxedRequire("../lib/notification") |
| 48 function showNotifications(location) | 50 ); |
| 49 { | 51 }); |
| 50 let shownNotifications = []; | 52 |
| 51 function showListener(notification) | 53 function showNotifications(location) |
| 52 { | 54 { |
| 53 shownNotifications.push(notification); | 55 let shownNotifications = []; |
| 54 Notification.markAsShown(notification.id); | 56 function showListener(notification) |
| 57 { |
| 58 shownNotifications.push(notification); |
| 59 Notification.markAsShown(notification.id); |
| 60 } |
| 61 Notification.addShowListener(showListener); |
| 62 Notification.showNext(location && new URL(location)); |
| 63 Notification.removeShowListener(showListener); |
| 64 return shownNotifications; |
| 55 } | 65 } |
| 56 Notification.addShowListener(showListener); | 66 |
| 57 Notification.showNext(location && new URL(location)); | 67 function* pairs(array) |
| 58 Notification.removeShowListener(showListener); | 68 { |
| 59 return shownNotifications; | 69 for (let element1 of array) |
| 60 } | 70 { |
| 61 | 71 for (let element2 of array) |
| 62 function* pairs(array) | 72 { |
| 63 { | 73 if (element1 != element2) |
| 64 for (let element1 of array) | 74 yield [element1, element2]; |
| 65 { | 75 } |
| 66 for (let element2 of array) | |
| 67 { | |
| 68 if (element1 != element2) | |
| 69 yield [element1, element2]; | |
| 70 } | 76 } |
| 71 } | 77 } |
| 72 } | 78 |
| 73 | 79 function registerHandler(notifications, checkCallback) |
| 74 function registerHandler(notifications, checkCallback) | 80 { |
| 75 { | 81 runner.registerHandler("/notification.json", metadata => |
| 76 this.registerHandler("/notification.json", metadata => | 82 { |
| 77 { | 83 if (checkCallback) |
| 78 if (checkCallback) | 84 checkCallback(metadata); |
| 79 checkCallback(metadata); | 85 |
| 80 | 86 let notification = { |
| 81 let notification = { | 87 version: 55, |
| 82 version: 55, | 88 notifications |
| 83 notifications | 89 }; |
| 84 }; | 90 |
| 85 | 91 return [200, JSON.stringify(notification)]; |
| 86 return [200, JSON.stringify(notification)]; | 92 }); |
| 87 }); | 93 } |
| 88 } | 94 |
| 89 | 95 it("No Data", () => |
| 90 exports.testNoData = function(test) | 96 { |
| 91 { | 97 assert.deepEqual(showNotifications(), [], "No notifications should be return
ed if there is no data"); |
| 92 test.deepEqual(showNotifications(), [], "No notifications should be returned i
f there is no data"); | 98 }); |
| 93 test.done(); | 99 |
| 94 }; | 100 it("Single Notificaion", () => |
| 95 | 101 { |
| 96 exports.testSingleNotification = function(test) | |
| 97 { | |
| 98 let information = { | |
| 99 id: 1, | |
| 100 type: "information", | |
| 101 message: {"en-US": "Information"} | |
| 102 }; | |
| 103 | |
| 104 registerHandler.call(this, [information]); | |
| 105 this.runScheduledTasks(1).then(() => | |
| 106 { | |
| 107 test.deepEqual(showNotifications(), [information], "The notification is show
n"); | |
| 108 test.deepEqual(showNotifications(), [], "Informational notifications aren't
shown more than once"); | |
| 109 }).catch(unexpectedError.bind(test)).then(() => test.done()); | |
| 110 }; | |
| 111 | |
| 112 exports.testInformationAndCritical = function(test) | |
| 113 { | |
| 114 let information = { | |
| 115 id: 1, | |
| 116 type: "information", | |
| 117 message: {"en-US": "Information"} | |
| 118 }; | |
| 119 let critical = { | |
| 120 id: 2, | |
| 121 type: "critical", | |
| 122 message: {"en-US": "Critical"} | |
| 123 }; | |
| 124 | |
| 125 registerHandler.call(this, [information, critical]); | |
| 126 this.runScheduledTasks(1).then(() => | |
| 127 { | |
| 128 test.deepEqual(showNotifications(), [critical], "The critical notification i
s given priority"); | |
| 129 test.deepEqual(showNotifications(), [critical], "Critical notifications can
be shown multiple times"); | |
| 130 }).catch(unexpectedError.bind(test)).then(() => test.done()); | |
| 131 }; | |
| 132 | |
| 133 exports.testNoType = function(test) | |
| 134 { | |
| 135 let information = { | |
| 136 id: 1, | |
| 137 message: {"en-US": "Information"} | |
| 138 }; | |
| 139 | |
| 140 registerHandler.call(this, [information]); | |
| 141 this.runScheduledTasks(1).then(() => | |
| 142 { | |
| 143 test.deepEqual(showNotifications(), [information], "The notification is show
n"); | |
| 144 test.deepEqual(showNotifications(), [], "Notification is treated as type inf
ormation"); | |
| 145 }).catch(unexpectedError.bind(test)).then(() => test.done()); | |
| 146 }; | |
| 147 | |
| 148 function testTargetSelectionFunc(propName, value, result) | |
| 149 { | |
| 150 return function(test) | |
| 151 { | |
| 152 let targetInfo = {}; | |
| 153 targetInfo[propName] = value; | |
| 154 | |
| 155 let information = { | 102 let information = { |
| 156 id: 1, | 103 id: 1, |
| 157 type: "information", | 104 type: "information", |
| 158 message: {"en-US": "Information"}, | 105 message: {"en-US": "Information"} |
| 159 targets: [targetInfo] | 106 }; |
| 160 }; | 107 |
| 161 | 108 registerHandler.call(runner, [information]); |
| 162 registerHandler.call(this, [information]); | 109 return runner.runScheduledTasks(1).then(() => |
| 163 this.runScheduledTasks(1).then(() => | 110 { |
| 164 { | 111 assert.deepEqual(showNotifications(), [information], "The notification is
shown"); |
| 165 let expected = (result ? [information] : []); | 112 assert.deepEqual(showNotifications(), [], "Informational notifications are
n't shown more than once"); |
| 166 test.deepEqual(showNotifications(), expected, "Selected notification for "
+ JSON.stringify(information.targets)); | 113 }); |
| 167 test.deepEqual(showNotifications(), [], "No notification on second call"); | 114 }); |
| 168 }).catch(unexpectedError.bind(test)).then(() => test.done()); | 115 |
| 169 }; | 116 it("Information and Critical", () => |
| 170 } | 117 { |
| 171 | |
| 172 exports.testTargetSelection = {}; | |
| 173 | |
| 174 for (let [propName, value, result] of [ | |
| 175 ["extension", "adblockpluschrome", true], | |
| 176 ["extension", "adblockplus", false], | |
| 177 ["extension", "adblockpluschrome2", false], | |
| 178 ["extensionMinVersion", "1.4", true], | |
| 179 ["extensionMinVersion", "1.4.1", true], | |
| 180 ["extensionMinVersion", "1.5", false], | |
| 181 ["extensionMaxVersion", "1.5", true], | |
| 182 ["extensionMaxVersion", "1.4.1", true], | |
| 183 ["extensionMaxVersion", "1.4.*", true], | |
| 184 ["extensionMaxVersion", "1.4", false], | |
| 185 ["application", "chrome", true], | |
| 186 ["application", "firefox", false], | |
| 187 ["applicationMinVersion", "27.0", true], | |
| 188 ["applicationMinVersion", "27", true], | |
| 189 ["applicationMinVersion", "26", true], | |
| 190 ["applicationMinVersion", "28", false], | |
| 191 ["applicationMinVersion", "27.1", false], | |
| 192 ["applicationMinVersion", "27.0b1", true], | |
| 193 ["applicationMaxVersion", "27.0", true], | |
| 194 ["applicationMaxVersion", "27", true], | |
| 195 ["applicationMaxVersion", "28", true], | |
| 196 ["applicationMaxVersion", "26", false], | |
| 197 ["applicationMaxVersion", "27.0b1", false], | |
| 198 ["platform", "chromium", true], | |
| 199 ["platform", "gecko", false], | |
| 200 ["platformMinVersion", "12.0", true], | |
| 201 ["platformMinVersion", "12", true], | |
| 202 ["platformMinVersion", "11", true], | |
| 203 ["platformMinVersion", "13", false], | |
| 204 ["platformMinVersion", "12.1", false], | |
| 205 ["platformMinVersion", "12.0b1", true], | |
| 206 ["platformMaxVersion", "12.0", true], | |
| 207 ["platformMaxVersion", "12", true], | |
| 208 ["platformMaxVersion", "13", true], | |
| 209 ["platformMaxVersion", "11", false], | |
| 210 ["platformMaxVersion", "12.0b1", false], | |
| 211 ["blockedTotalMin", "11", false], | |
| 212 ["blockedTotalMin", "10", true], | |
| 213 ["blockedTotalMax", "10", true], | |
| 214 ["blockedTotalMax", "1", false], | |
| 215 ["locales", ["en-US"], true], | |
| 216 ["locales", ["en-US", "de-DE"], true], | |
| 217 ["locales", ["de-DE"], false], | |
| 218 ["locales", ["en-GB", "de-DE"], false] | |
| 219 ]) | |
| 220 { | |
| 221 exports.testTargetSelection[`${propName}=${value}`] = testTargetSelectionFunc(
propName, value, result); | |
| 222 } | |
| 223 | |
| 224 exports.testTargetSelectionNoShowStats = { | |
| 225 | |
| 226 setUp(callback) | |
| 227 { | |
| 228 this.show_statsinpopup_orig = Prefs.show_statsinpopup; | |
| 229 Prefs.show_statsinpopup = false; | |
| 230 callback(); | |
| 231 }, | |
| 232 tearDown(callback) | |
| 233 { | |
| 234 Prefs.show_statsinpopup = this.show_statsinpopup_orig; | |
| 235 callback(); | |
| 236 } | |
| 237 }; | |
| 238 for (let [propName, value, result] of [ | |
| 239 ["blockedTotalMin", "10", false], | |
| 240 ["blockedTotalMax", "10", false] | |
| 241 ]) | |
| 242 { | |
| 243 exports.testTargetSelectionNoShowStats[`${propName}=${value}`] = testTargetSel
ectionFunc(propName, value, result); | |
| 244 } | |
| 245 | |
| 246 exports.testMultipleTargets = {}; | |
| 247 | |
| 248 for (let [[propName1, value1, result1], [propName2, value2, result2]] of pairs([ | |
| 249 ["extension", "adblockpluschrome", true], | |
| 250 ["extension", "adblockplus", false], | |
| 251 ["extensionMinVersion", "1.4", true], | |
| 252 ["extensionMinVersion", "1.5", false], | |
| 253 ["application", "chrome", true], | |
| 254 ["application", "firefox", false], | |
| 255 ["applicationMinVersion", "27", true], | |
| 256 ["applicationMinVersion", "28", false], | |
| 257 ["platform", "chromium", true], | |
| 258 ["platform", "gecko", false], | |
| 259 ["platformMinVersion", "12", true], | |
| 260 ["platformMinVersion", "13", false], | |
| 261 ["unknown", "unknown", false] | |
| 262 ])) | |
| 263 { | |
| 264 exports.testMultipleTargets[`${propName1}=${value1},${propName2}=${value2}`] =
function(test) | |
| 265 { | |
| 266 let targetInfo1 = {}; | |
| 267 targetInfo1[propName1] = value1; | |
| 268 let targetInfo2 = {}; | |
| 269 targetInfo2[propName2] = value2; | |
| 270 | |
| 271 let information = { | 118 let information = { |
| 272 id: 1, | 119 id: 1, |
| 273 type: "information", | 120 type: "information", |
| 274 message: {"en-US": "Information"}, | 121 message: {"en-US": "Information"} |
| 275 targets: [targetInfo1, targetInfo2] | 122 }; |
| 123 let critical = { |
| 124 id: 2, |
| 125 type: "critical", |
| 126 message: {"en-US": "Critical"} |
| 127 }; |
| 128 |
| 129 registerHandler.call(this, [information, critical]); |
| 130 return runner.runScheduledTasks(1).then(() => |
| 131 { |
| 132 assert.deepEqual(showNotifications(), [critical], "The critical notificati
on is given priority"); |
| 133 assert.deepEqual(showNotifications(), [critical], "Critical notifications
can be shown multiple times"); |
| 134 }); |
| 135 }); |
| 136 |
| 137 it("No Type", () => |
| 138 { |
| 139 let information = { |
| 140 id: 1, |
| 141 message: {"en-US": "Information"} |
| 276 }; | 142 }; |
| 277 | 143 |
| 278 registerHandler.call(this, [information]); | 144 registerHandler.call(this, [information]); |
| 279 this.runScheduledTasks(1).then(() => | 145 return runner.runScheduledTasks(1).then(() => |
| 280 { | 146 { |
| 281 let expected = (result1 || result2 ? [information] : []); | 147 assert.deepEqual(showNotifications(), [information], "The notification is
shown"); |
| 282 test.deepEqual(showNotifications(), expected, "Selected notification for "
+ JSON.stringify(information.targets)); | 148 assert.deepEqual(showNotifications(), [], "Notification is treated as type
information"); |
| 283 }).catch(unexpectedError.bind(test)).then(() => test.done()); | 149 }); |
| 284 }; | 150 }); |
| 285 } | 151 |
| 286 | 152 function testTargetSelectionFunc(propName, value, result) |
| 287 exports.testParametersSent = function(test) | 153 { |
| 288 { | 154 return function() |
| 289 Prefs.notificationdata = { | 155 { |
| 290 data: { | 156 let targetInfo = {}; |
| 291 version: "3" | 157 targetInfo[propName] = value; |
| 292 } | 158 |
| 293 }; | 159 let information = { |
| 294 | 160 id: 1, |
| 295 let parameters = null; | 161 type: "information", |
| 296 registerHandler.call(this, [], metadata => | 162 message: {"en-US": "Information"}, |
| 297 { | 163 targets: [targetInfo] |
| 298 parameters = decodeURI(metadata.queryString); | 164 }; |
| 299 }); | 165 |
| 300 this.runScheduledTasks(1).then(() => | 166 registerHandler.call(this, [information]); |
| 301 { | 167 return runner.runScheduledTasks(1).then(() => |
| 302 test.equal(parameters, | 168 { |
| 303 "addonName=adblockpluschrome&addonVersion=1.4.1&application=chrome&app
licationVersion=27.0&platform=chromium&platformVersion=12.0&lastVersion=3&downlo
adCount=0", | 169 let expected = (result ? [information] : []); |
| 304 "The correct parameters are sent to the server"); | 170 assert.deepEqual(showNotifications(), expected, "Selected notification f
or " + JSON.stringify(information.targets)); |
| 305 }).catch(unexpectedError.bind(test)).then(() => test.done()); | 171 assert.deepEqual(showNotifications(), [], "No notification on second cal
l"); |
| 306 }; | 172 }); |
| 307 | 173 }; |
| 308 exports.testExpirationInterval = {}; | |
| 309 | |
| 310 let initialDelay = 1 / 60; | |
| 311 for (let currentTest of [ | |
| 312 { | |
| 313 randomResult: 0.5, | |
| 314 requests: [initialDelay, initialDelay + 24, initialDelay + 48] | |
| 315 }, | |
| 316 { | |
| 317 randomResult: 0, // Changes interval by factor 0.8 (19.2 hours) | |
| 318 requests: [initialDelay, initialDelay + 20, initialDelay + 40] | |
| 319 }, | |
| 320 { | |
| 321 randomResult: 1, // Changes interval by factor 1.2 (28.8 hours) | |
| 322 requests: [initialDelay, initialDelay + 29, initialDelay + 58] | |
| 323 }, | |
| 324 { | |
| 325 randomResult: 0.25, // Changes interval by factor 0.9 (21.6 hours) | |
| 326 requests: [initialDelay, initialDelay + 22, initialDelay + 44] | |
| 327 }, | |
| 328 { | |
| 329 randomResult: 0.5, | |
| 330 skipAfter: initialDelay + 5, | |
| 331 skip: 10, // Short break should not increase soft expiration | |
| 332 requests: [initialDelay, initialDelay + 24] | |
| 333 }, | |
| 334 { | |
| 335 randomResult: 0.5, | |
| 336 skipAfter: initialDelay + 5, | |
| 337 skip: 30, // Long break should increase soft expiration, hitti
ng hard expiration | |
| 338 requests: [initialDelay, initialDelay + 48] | |
| 339 } | 174 } |
| 340 ]) | 175 |
| 341 { | 176 describe("Target selection", () => |
| 342 let testId = "Math.random() returning " + currentTest.randomResult; | 177 { |
| 343 if (typeof currentTest.skip != "number") | 178 for (let [propName, value, result] of [ |
| 344 testId += " skipping " + currentTest.skip + " hours after " + currentTest.sk
ipAfter + " hours"; | 179 ["extension", "adblockpluschrome", true], |
| 345 exports.testExpirationInterval[testId] = function(test) | 180 ["extension", "adblockplus", false], |
| 346 { | 181 ["extension", "adblockpluschrome2", false], |
| 347 let requests = []; | 182 ["extensionMinVersion", "1.4", true], |
| 348 registerHandler.call(this, [], metadata => requests.push(this.getTimeOffset(
))); | 183 ["extensionMinVersion", "1.4.1", true], |
| 349 | 184 ["extensionMinVersion", "1.5", false], |
| 350 this.randomResult = currentTest.randomResult; | 185 ["extensionMaxVersion", "1.5", true], |
| 351 | 186 ["extensionMaxVersion", "1.4.1", true], |
| 352 let maxHours = Math.round(Math.max.apply(null, currentTest.requests)) + 1; | 187 ["extensionMaxVersion", "1.4.*", true], |
| 353 this.runScheduledTasks(maxHours, currentTest.skipAfter, currentTest.skip).th
en(() => | 188 ["extensionMaxVersion", "1.4", false], |
| 354 { | 189 ["application", "chrome", true], |
| 355 test.deepEqual(requests, currentTest.requests, "Requests"); | 190 ["application", "firefox", false], |
| 356 }).catch(unexpectedError.bind(test)).then(() => test.done()); | 191 ["applicationMinVersion", "27.0", true], |
| 357 }; | 192 ["applicationMinVersion", "27", true], |
| 358 } | 193 ["applicationMinVersion", "26", true], |
| 359 | 194 ["applicationMinVersion", "28", false], |
| 360 exports.testUsingSeverityInsteadOfType = function(test) | 195 ["applicationMinVersion", "27.1", false], |
| 361 { | 196 ["applicationMinVersion", "27.0b1", true], |
| 362 let severityNotification = { | 197 ["applicationMaxVersion", "27.0", true], |
| 363 id: 1, | 198 ["applicationMaxVersion", "27", true], |
| 364 severity: "information", | 199 ["applicationMaxVersion", "28", true], |
| 365 message: {"en-US": "Information"} | 200 ["applicationMaxVersion", "26", false], |
| 366 }; | 201 ["applicationMaxVersion", "27.0b1", false], |
| 367 | 202 ["platform", "chromium", true], |
| 368 function listener(name) | 203 ["platform", "gecko", false], |
| 369 { | 204 ["platformMinVersion", "12.0", true], |
| 370 if (name !== "notificationdata") | 205 ["platformMinVersion", "12", true], |
| 371 return; | 206 ["platformMinVersion", "11", true], |
| 372 | 207 ["platformMinVersion", "13", false], |
| 373 Prefs.removeListener(listener); | 208 ["platformMinVersion", "12.1", false], |
| 374 let notification = Prefs.notificationdata.data.notifications[0]; | 209 ["platformMinVersion", "12.0b1", true], |
| 375 test.ok(!("severity" in notification), "Severity property was removed"); | 210 ["platformMaxVersion", "12.0", true], |
| 376 test.ok("type" in notification, "Type property was added"); | 211 ["platformMaxVersion", "12", true], |
| 377 test.equal(notification.type, severityNotification.severity, "Type property
has correct value"); | 212 ["platformMaxVersion", "13", true], |
| 378 test.done(); | 213 ["platformMaxVersion", "11", false], |
| 379 } | 214 ["platformMaxVersion", "12.0b1", false], |
| 380 Prefs.addListener(listener); | 215 ["blockedTotalMin", "11", false], |
| 381 | 216 ["blockedTotalMin", "10", true], |
| 382 let responseText = JSON.stringify({ | 217 ["blockedTotalMax", "10", true], |
| 383 notifications: [severityNotification] | 218 ["blockedTotalMax", "1", false], |
| 384 }); | 219 ["locales", ["en-US"], true], |
| 385 Notification._onDownloadSuccess({}, responseText, () => {}, () => {}); | 220 ["locales", ["en-US", "de-DE"], true], |
| 386 }; | 221 ["locales", ["de-DE"], false], |
| 387 | 222 ["locales", ["en-GB", "de-DE"], false] |
| 388 exports.testURLSpecificNotification = function(test) | 223 ]) |
| 389 { | 224 { |
| 390 let withURLFilterFoo = { | 225 it(`Target ${propName}=${value}`, testTargetSelectionFunc(propName, value,
result)); |
| 391 id: 1, | 226 } |
| 392 urlFilters: ["foo.com$document"] | 227 }); |
| 393 }; | 228 |
| 394 let withoutURLFilter = { | 229 describe("No show stats", () => |
| 395 id: 2 | 230 { |
| 396 }; | 231 beforeEach(() => |
| 397 let withURLFilterBar = { | 232 { |
| 398 id: 3, | 233 runner.show_statsinpopup_orig = Prefs.show_statsinpopup; |
| 399 urlFilters: ["bar.com$document"] | 234 Prefs.show_statsinpopup = false; |
| 400 }; | 235 }); |
| 401 let subdomainURLFilter = { | 236 |
| 402 id: 4, | 237 afterEach(() => |
| 403 urlFilters: ["||example.com$document"] | 238 { |
| 404 }; | 239 Prefs.show_statsinpopup = runner.show_statsinpopup_orig; |
| 405 | 240 }); |
| 406 registerHandler.call(this, [ | 241 |
| 407 withURLFilterFoo, | 242 for (let [propName, value, result] of [ |
| 408 withoutURLFilter, | 243 ["blockedTotalMin", "10", false], |
| 409 withURLFilterBar, | 244 ["blockedTotalMax", "10", false] |
| 410 subdomainURLFilter | 245 ]) |
| 411 ]); | 246 { |
| 412 this.runScheduledTasks(1).then(() => | 247 it(`Target ${propName}=${value}`, testTargetSelectionFunc(propName, value,
result)); |
| 413 { | 248 } |
| 414 test.deepEqual(showNotifications(), [withoutURLFilter], "URL-specific notifi
cations are skipped"); | 249 }); |
| 415 test.deepEqual(showNotifications("http://foo.com"), [withURLFilterFoo], "URL
-specific notification is retrieved"); | 250 |
| 416 test.deepEqual(showNotifications("http://foo.com"), [], "URL-specific notifi
cation is not retrieved"); | 251 describe("Multiple Targets", () => |
| 417 test.deepEqual(showNotifications("http://www.example.com"), [subdomainURLFil
ter], "URL-specific notification matches subdomain"); | 252 { |
| 418 }).catch(unexpectedError.bind(test)).then(() => test.done()); | 253 for (let [[propName1, value1, result1], [propName2, value2, result2]] of pai
rs([ |
| 419 }; | 254 ["extension", "adblockpluschrome", true], |
| 420 | 255 ["extension", "adblockplus", false], |
| 421 exports.testInterval = function(test) | 256 ["extensionMinVersion", "1.4", true], |
| 422 { | 257 ["extensionMinVersion", "1.5", false], |
| 423 let relentless = { | 258 ["application", "chrome", true], |
| 424 id: 3, | 259 ["application", "firefox", false], |
| 425 type: "relentless", | 260 ["applicationMinVersion", "27", true], |
| 426 interval: 100 | 261 ["applicationMinVersion", "28", false], |
| 427 }; | 262 ["platform", "chromium", true], |
| 428 | 263 ["platform", "gecko", false], |
| 429 registerHandler.call(this, [relentless]); | 264 ["platformMinVersion", "12", true], |
| 430 this.runScheduledTasks(1).then(() => | 265 ["platformMinVersion", "13", false], |
| 431 { | 266 ["unknown", "unknown", false] |
| 432 test.deepEqual(showNotifications(), [relentless], "Relentless notifications
are shown initially"); | 267 ])) |
| 433 }).then(() => | 268 { |
| 434 { | 269 it(`Targets ${propName1}=${value1},${propName2}=${value2}`, () => |
| 435 test.deepEqual(showNotifications(), [], "Relentless notifications are not sh
own before the interval"); | 270 { |
| 436 }).then(() => | 271 let targetInfo1 = {}; |
| 437 { | 272 targetInfo1[propName1] = value1; |
| 438 // Date always returns a fixed time (see setupTimerAndFetch) so we | 273 let targetInfo2 = {}; |
| 439 // manipulate the shown data manually. | 274 targetInfo2[propName2] = value2; |
| 440 Prefs.notificationdata.shown[relentless.id] -= relentless.interval; | 275 |
| 441 test.deepEqual(showNotifications(), [relentless], "Relentless notifications
are shown after the interval"); | 276 let information = { |
| 442 }).catch(unexpectedError.bind(test)).then(() => test.done()); | 277 id: 1, |
| 443 }; | 278 type: "information", |
| 444 | 279 message: {"en-US": "Information"}, |
| 445 exports.testRelentlessNotification = function(test) | 280 targets: [targetInfo1, targetInfo2] |
| 446 { | 281 }; |
| 447 let relentless = { | 282 |
| 448 id: 3, | 283 registerHandler.call(this, [information]); |
| 449 type: "relentless", | 284 return runner.runScheduledTasks(1).then(() => |
| 450 interval: 100, | 285 { |
| 451 urlFilters: ["foo.com$document", "bar.foo$document"] | 286 let expected = (result1 || result2 ? [information] : []); |
| 452 }; | 287 assert.deepEqual(showNotifications(), expected, "Selected notification
for " + JSON.stringify(information.targets)); |
| 453 | 288 }); |
| 454 registerHandler.call(this, [relentless]); | 289 }); |
| 455 this.runScheduledTasks(1).then(() => | 290 } |
| 456 { | 291 }); |
| 457 test.deepEqual(showNotifications(), [], "Relentless notification is not show
n without URL"); | 292 |
| 458 test.deepEqual(showNotifications("http://bar.com"), [], "Relentless notifica
tion is not shown for a non-matching URL"); | 293 it("Parameters Sent", () => |
| 459 test.deepEqual(showNotifications("http://foo.com"), [relentless], "Relentles
s notification is shown for a matching URL"); | 294 { |
| 460 }).then(() => | 295 Prefs.notificationdata = { |
| 461 { | 296 data: { |
| 462 test.deepEqual(showNotifications("http://foo.com"), [], "Relentless notifica
tions are not shown before the interval"); | 297 version: "3" |
| 463 }).then(() => | 298 } |
| 464 { | 299 }; |
| 465 // Date always returns a fixed time (see setupTimerAndFetch) so we | 300 |
| 466 // manipulate the shown data manually. | 301 let parameters = null; |
| 467 Prefs.notificationdata.shown[relentless.id] -= relentless.interval; | 302 registerHandler.call(this, [], metadata => |
| 468 test.deepEqual(showNotifications(), [], "Relentless notifications are not sh
own after the interval without URL"); | 303 { |
| 469 test.deepEqual(showNotifications("http://bar.com"), [], "Relentless notifica
tions are not shown after the interval for a non-matching URL"); | 304 parameters = decodeURI(metadata.queryString); |
| 470 test.deepEqual(showNotifications("http://bar.foo.com"), [relentless], "Relen
tless notifications are shown after the interval for a matching URL"); | 305 }); |
| 471 }).catch(unexpectedError.bind(test)).then(() => test.done()); | 306 return runner.runScheduledTasks(1).then(() => |
| 472 }; | 307 { |
| 473 | 308 assert.equal(parameters, |
| 474 exports.testGlobalOptOut = function(test) | 309 "addonName=adblockpluschrome&addonVersion=1.4.1&application=chr
ome&applicationVersion=27.0&platform=chromium&platformVersion=12.0&lastVersion=3
&downloadCount=0", |
| 475 { | 310 "The correct parameters are sent to the server"); |
| 476 Notification.toggleIgnoreCategory("*", true); | 311 }); |
| 477 test.ok(Prefs.notifications_ignoredcategories.indexOf("*") != -1, "Force enabl
e global opt-out"); | 312 }); |
| 478 Notification.toggleIgnoreCategory("*", true); | 313 |
| 479 test.ok(Prefs.notifications_ignoredcategories.indexOf("*") != -1, "Force enabl
e global opt-out (again)"); | 314 describe("Expiration Interval", () => |
| 480 Notification.toggleIgnoreCategory("*", false); | 315 { |
| 481 test.ok(Prefs.notifications_ignoredcategories.indexOf("*") == -1, "Force disab
le global opt-out"); | 316 let initialDelay = 1 / 60; |
| 482 Notification.toggleIgnoreCategory("*", false); | 317 for (let currentTest of [ |
| 483 test.ok(Prefs.notifications_ignoredcategories.indexOf("*") == -1, "Force disab
le global opt-out (again)"); | 318 { |
| 484 Notification.toggleIgnoreCategory("*"); | 319 randomResult: 0.5, |
| 485 test.ok(Prefs.notifications_ignoredcategories.indexOf("*") != -1, "Toggle enab
le global opt-out"); | 320 requests: [initialDelay, initialDelay + 24, initialDelay + 48] |
| 486 Notification.toggleIgnoreCategory("*"); | 321 }, |
| 487 test.ok(Prefs.notifications_ignoredcategories.indexOf("*") == -1, "Toggle disa
ble global opt-out"); | 322 { |
| 488 | 323 randomResult: 0, // Changes interval by factor 0.8 (19.2 hours) |
| 489 Prefs.notifications_showui = false; | 324 requests: [initialDelay, initialDelay + 20, initialDelay + 40] |
| 490 Notification.toggleIgnoreCategory("*", false); | 325 }, |
| 491 test.ok(!Prefs.notifications_showui, "Opt-out UI will not be shown if global o
pt-out hasn't been enabled yet"); | 326 { |
| 492 Notification.toggleIgnoreCategory("*", true); | 327 randomResult: 1, // Changes interval by factor 1.2 (28.8 hours) |
| 493 test.ok(Prefs.notifications_showui, "Opt-out UI will be shown after enabling g
lobal opt-out"); | 328 requests: [initialDelay, initialDelay + 29, initialDelay + 58] |
| 494 Notification.toggleIgnoreCategory("*", false); | 329 }, |
| 495 test.ok(Prefs.notifications_showui, "Opt-out UI will be shown after enabling g
lobal opt-out even if it got disabled again"); | 330 { |
| 496 | 331 randomResult: 0.25, // Changes interval by factor 0.9 (21.6 hours) |
| 497 let information = { | 332 requests: [initialDelay, initialDelay + 22, initialDelay + 44] |
| 498 id: 1, | 333 }, |
| 499 type: "information" | 334 { |
| 500 }; | 335 randomResult: 0.5, |
| 501 let critical = { | 336 skipAfter: initialDelay + 5, |
| 502 id: 2, | 337 skip: 10, // Short break should not increase soft expirati
on |
| 503 type: "critical" | 338 requests: [initialDelay, initialDelay + 24] |
| 504 }; | 339 }, |
| 505 let relentless = { | 340 { |
| 506 id: 3, | 341 randomResult: 0.5, |
| 507 type: "relentless" | 342 skipAfter: initialDelay + 5, |
| 508 }; | 343 skip: 30, // Long break should increase soft expiration, h
itting hard expiration |
| 509 | 344 requests: [initialDelay, initialDelay + 48] |
| 510 Notification.toggleIgnoreCategory("*", true); | 345 } |
| 511 registerHandler.call(this, [information]); | 346 ]) |
| 512 this.runScheduledTasks(1).then(() => | 347 { |
| 513 { | 348 let testId = `Math.random() returning ${currentTest.randomResult}`; |
| 514 test.deepEqual(showNotifications(), [], "Information notifications are ignor
ed after enabling global opt-out"); | 349 if (typeof currentTest.skip == "number") |
| 350 testId += ` skipping ${currentTest.skip} hours after ${currentTest.skipA
fter} hours`; |
| 351 |
| 352 it(testId, () => |
| 353 { |
| 354 let requests = []; |
| 355 registerHandler.call(this, [], metadata => requests.push(runner.getTimeO
ffset())); |
| 356 |
| 357 runner.randomResult = currentTest.randomResult; |
| 358 |
| 359 let maxHours = Math.round(Math.max.apply(null, currentTest.requests)) +
1; |
| 360 return runner.runScheduledTasks(maxHours, currentTest.skipAfter, current
Test.skip).then(() => |
| 361 { |
| 362 assert.deepEqual(requests, currentTest.requests, "Requests"); |
| 363 }); |
| 364 }); |
| 365 } |
| 366 }); |
| 367 |
| 368 it("Using severity instead of type", done => |
| 369 { |
| 370 let severityNotification = { |
| 371 id: 1, |
| 372 severity: "information", |
| 373 message: {"en-US": "Information"} |
| 374 }; |
| 375 |
| 376 function listener(name) |
| 377 { |
| 378 if (name !== "notificationdata") |
| 379 return; |
| 380 |
| 381 Prefs.removeListener(listener); |
| 382 let notification = Prefs.notificationdata.data.notifications[0]; |
| 383 assert.ok(!("severity" in notification), "Severity property was removed"); |
| 384 assert.ok("type" in notification, "Type property was added"); |
| 385 assert.equal(notification.type, severityNotification.severity, "Type prope
rty has correct value"); |
| 386 done(); |
| 387 } |
| 388 Prefs.addListener(listener); |
| 389 |
| 390 let responseText = JSON.stringify({ |
| 391 notifications: [severityNotification] |
| 392 }); |
| 393 Notification._onDownloadSuccess({}, responseText, () => {}, () => {}); |
| 394 }); |
| 395 |
| 396 it("URL Specific Notification", () => |
| 397 { |
| 398 let withURLFilterFoo = { |
| 399 id: 1, |
| 400 urlFilters: ["foo.com$document"] |
| 401 }; |
| 402 let withoutURLFilter = { |
| 403 id: 2 |
| 404 }; |
| 405 let withURLFilterBar = { |
| 406 id: 3, |
| 407 urlFilters: ["bar.com$document"] |
| 408 }; |
| 409 let subdomainURLFilter = { |
| 410 id: 4, |
| 411 urlFilters: ["||example.com$document"] |
| 412 }; |
| 413 |
| 414 registerHandler.call(this, [ |
| 415 withURLFilterFoo, |
| 416 withoutURLFilter, |
| 417 withURLFilterBar, |
| 418 subdomainURLFilter |
| 419 ]); |
| 420 return runner.runScheduledTasks(1).then(() => |
| 421 { |
| 422 assert.deepEqual(showNotifications(), [withoutURLFilter], "URL-specific no
tifications are skipped"); |
| 423 assert.deepEqual(showNotifications("http://foo.com"), [withURLFilterFoo],
"URL-specific notification is retrieved"); |
| 424 assert.deepEqual(showNotifications("http://foo.com"), [], "URL-specific no
tification is not retrieved"); |
| 425 assert.deepEqual(showNotifications("http://www.example.com"), [subdomainUR
LFilter], "URL-specific notification matches subdomain"); |
| 426 }); |
| 427 }); |
| 428 |
| 429 it("Interval", () => |
| 430 { |
| 431 let relentless = { |
| 432 id: 3, |
| 433 type: "relentless", |
| 434 interval: 100 |
| 435 }; |
| 436 |
| 437 registerHandler.call(this, [relentless]); |
| 438 return runner.runScheduledTasks(1).then(() => |
| 439 { |
| 440 assert.deepEqual(showNotifications(), [relentless], "Relentless notificati
ons are shown initially"); |
| 441 }).then(() => |
| 442 { |
| 443 assert.deepEqual(showNotifications(), [], "Relentless notifications are no
t shown before the interval"); |
| 444 }).then(() => |
| 445 { |
| 446 // Date always returns a fixed time (see setupTimerAndFetch) so we |
| 447 // manipulate the shown data manually. |
| 448 Prefs.notificationdata.shown[relentless.id] -= relentless.interval; |
| 449 assert.deepEqual(showNotifications(), [relentless], "Relentless notificati
ons are shown after the interval"); |
| 450 }); |
| 451 }); |
| 452 |
| 453 it("Relentless notification", () => |
| 454 { |
| 455 let relentless = { |
| 456 id: 3, |
| 457 type: "relentless", |
| 458 interval: 100, |
| 459 urlFilters: ["foo.com$document", "bar.foo$document"] |
| 460 }; |
| 461 |
| 462 registerHandler.call(this, [relentless]); |
| 463 return runner.runScheduledTasks(1).then(() => |
| 464 { |
| 465 assert.deepEqual(showNotifications(), [], "Relentless notification is not
shown without URL"); |
| 466 assert.deepEqual(showNotifications("http://bar.com"), [], "Relentless noti
fication is not shown for a non-matching URL"); |
| 467 assert.deepEqual(showNotifications("http://foo.com"), [relentless], "Relen
tless notification is shown for a matching URL"); |
| 468 }).then(() => |
| 469 { |
| 470 assert.deepEqual(showNotifications("http://foo.com"), [], "Relentless noti
fications are not shown before the interval"); |
| 471 }).then(() => |
| 472 { |
| 473 // Date always returns a fixed time (see setupTimerAndFetch) so we |
| 474 // manipulate the shown data manually. |
| 475 Prefs.notificationdata.shown[relentless.id] -= relentless.interval; |
| 476 assert.deepEqual(showNotifications(), [], "Relentless notifications are no
t shown after the interval without URL"); |
| 477 assert.deepEqual(showNotifications("http://bar.com"), [], "Relentless noti
fications are not shown after the interval for a non-matching URL"); |
| 478 assert.deepEqual(showNotifications("http://bar.foo.com"), [relentless], "R
elentless notifications are shown after the interval for a matching URL"); |
| 479 }); |
| 480 }); |
| 481 |
| 482 it("Global opt-out", () => |
| 483 { |
| 484 Notification.toggleIgnoreCategory("*", true); |
| 485 assert.ok(Prefs.notifications_ignoredcategories.indexOf("*") != -1, "Force e
nable global opt-out"); |
| 486 Notification.toggleIgnoreCategory("*", true); |
| 487 assert.ok(Prefs.notifications_ignoredcategories.indexOf("*") != -1, "Force e
nable global opt-out (again)"); |
| 515 Notification.toggleIgnoreCategory("*", false); | 488 Notification.toggleIgnoreCategory("*", false); |
| 516 test.deepEqual(showNotifications(), [information], "Information notification
s are shown after disabling global opt-out"); | 489 assert.ok(Prefs.notifications_ignoredcategories.indexOf("*") == -1, "Force d
isable global opt-out"); |
| 517 | 490 Notification.toggleIgnoreCategory("*", false); |
| 491 assert.ok(Prefs.notifications_ignoredcategories.indexOf("*") == -1, "Force d
isable global opt-out (again)"); |
| 492 Notification.toggleIgnoreCategory("*"); |
| 493 assert.ok(Prefs.notifications_ignoredcategories.indexOf("*") != -1, "Toggle
enable global opt-out"); |
| 494 Notification.toggleIgnoreCategory("*"); |
| 495 assert.ok(Prefs.notifications_ignoredcategories.indexOf("*") == -1, "Toggle
disable global opt-out"); |
| 496 |
| 497 Prefs.notifications_showui = false; |
| 498 Notification.toggleIgnoreCategory("*", false); |
| 499 assert.ok(!Prefs.notifications_showui, "Opt-out UI will not be shown if glob
al opt-out hasn't been enabled yet"); |
| 518 Notification.toggleIgnoreCategory("*", true); | 500 Notification.toggleIgnoreCategory("*", true); |
| 519 Prefs.notificationdata = {}; | 501 assert.ok(Prefs.notifications_showui, "Opt-out UI will be shown after enabli
ng global opt-out"); |
| 520 registerHandler.call(this, [critical]); | 502 Notification.toggleIgnoreCategory("*", false); |
| 521 return this.runScheduledTasks(1); | 503 assert.ok(Prefs.notifications_showui, "Opt-out UI will be shown after enabli
ng global opt-out even if it got disabled again"); |
| 522 }).then(() => | 504 |
| 523 { | 505 let information = { |
| 524 test.deepEqual(showNotifications(), [critical], "Critical notifications are
not ignored"); | 506 id: 1, |
| 525 | 507 type: "information" |
| 526 Prefs.notificationdata = {}; | 508 }; |
| 527 registerHandler.call(this, [relentless]); | 509 let critical = { |
| 528 return this.runScheduledTasks(1); | 510 id: 2, |
| 529 }).then(() => | 511 type: "critical" |
| 530 { | 512 }; |
| 531 test.deepEqual(showNotifications(), [relentless], "Relentless notifications
are not ignored"); | 513 let relentless = { |
| 532 }).catch(unexpectedError.bind(test)).then(() => test.done()); | 514 id: 3, |
| 533 }; | 515 type: "relentless" |
| 534 | 516 }; |
| 535 exports.testMessageWithoutLocalization = function(test) | 517 |
| 536 { | 518 Notification.toggleIgnoreCategory("*", true); |
| 537 let notification = {message: "non-localized"}; | 519 registerHandler.call(this, [information]); |
| 538 let texts = Notification.getLocalizedTexts(notification); | 520 return runner.runScheduledTasks(1).then(() => |
| 539 test.equal(texts.message, "non-localized"); | 521 { |
| 540 test.done(); | 522 assert.deepEqual(showNotifications(), [], "Information notifications are i
gnored after enabling global opt-out"); |
| 541 }; | 523 Notification.toggleIgnoreCategory("*", false); |
| 542 | 524 assert.deepEqual(showNotifications(), [information], "Information notifica
tions are shown after disabling global opt-out"); |
| 543 exports.testLanguageOnly = function(test) | 525 |
| 544 { | 526 Notification.toggleIgnoreCategory("*", true); |
| 545 let notification = {message: {fr: "fr"}}; | 527 Prefs.notificationdata = {}; |
| 546 Utils.appLocale = "fr"; | 528 registerHandler.call(this, [critical]); |
| 547 let texts = Notification.getLocalizedTexts(notification); | 529 return runner.runScheduledTasks(1); |
| 548 test.equal(texts.message, "fr"); | 530 }).then(() => |
| 549 Utils.appLocale = "fr-CA"; | 531 { |
| 550 texts = Notification.getLocalizedTexts(notification); | 532 assert.deepEqual(showNotifications(), [critical], "Critical notifications
are not ignored"); |
| 551 test.equal(texts.message, "fr"); | 533 |
| 552 test.done(); | 534 Prefs.notificationdata = {}; |
| 553 }; | 535 registerHandler.call(this, [relentless]); |
| 554 | 536 return runner.runScheduledTasks(1); |
| 555 exports.testLanguageAndCountry = function(test) | 537 }).then(() => |
| 556 { | 538 { |
| 557 let notification = {message: {"fr": "fr", "fr-CA": "fr-CA"}}; | 539 assert.deepEqual(showNotifications(), [relentless], "Relentless notificati
ons are not ignored"); |
| 558 Utils.appLocale = "fr-CA"; | 540 }); |
| 559 let texts = Notification.getLocalizedTexts(notification); | 541 }); |
| 560 test.equal(texts.message, "fr-CA"); | 542 |
| 561 Utils.appLocale = "fr"; | 543 it("Message without localization", () => |
| 562 texts = Notification.getLocalizedTexts(notification); | 544 { |
| 563 test.equal(texts.message, "fr"); | 545 let notification = {message: "non-localized"}; |
| 564 test.done(); | 546 let texts = Notification.getLocalizedTexts(notification); |
| 565 }; | 547 assert.equal(texts.message, "non-localized"); |
| 566 | 548 }); |
| 567 exports.testMissingTranslation = function(test) | 549 |
| 568 { | 550 it("Language only", () => |
| 569 let notification = {message: {"en-US": "en-US"}}; | 551 { |
| 570 Utils.appLocale = "fr"; | 552 let notification = {message: {fr: "fr"}}; |
| 571 let texts = Notification.getLocalizedTexts(notification); | 553 Utils.appLocale = "fr"; |
| 572 test.equal(texts.message, "en-US"); | 554 let texts = Notification.getLocalizedTexts(notification); |
| 573 test.done(); | 555 assert.equal(texts.message, "fr"); |
| 574 }; | 556 Utils.appLocale = "fr-CA"; |
| 557 texts = Notification.getLocalizedTexts(notification); |
| 558 assert.equal(texts.message, "fr"); |
| 559 }); |
| 560 |
| 561 it("Language and Country", () => |
| 562 { |
| 563 let notification = {message: {"fr": "fr", "fr-CA": "fr-CA"}}; |
| 564 Utils.appLocale = "fr-CA"; |
| 565 let texts = Notification.getLocalizedTexts(notification); |
| 566 assert.equal(texts.message, "fr-CA"); |
| 567 Utils.appLocale = "fr"; |
| 568 texts = Notification.getLocalizedTexts(notification); |
| 569 assert.equal(texts.message, "fr"); |
| 570 }); |
| 571 |
| 572 it("Missing translation", () => |
| 573 { |
| 574 let notification = {message: {"en-US": "en-US"}}; |
| 575 Utils.appLocale = "fr"; |
| 576 let texts = Notification.getLocalizedTexts(notification); |
| 577 assert.equal(texts.message, "en-US"); |
| 578 }); |
| 579 }); |
| OLD | NEW |