/* global MockCanvas, MockCanvasRenderingContext2D, MockImage, MockService, PasscodeHelper */ 'use strict'; require('/shared/test/unit/mocks/mock_l10n.js'); require('/shared/test/unit/mocks/mock_image.js'); require('/shared/test/unit/mocks/mock_canvas.js'); require('/shared/test/unit/mocks/mock_canvas_rendering_context_2d.js'); require('/shared/js/passcode_helper.js'); requireApp('system/lockscreen/js/lockscreen_charging.js'); requireApp('system/shared/test/unit/mocks/mock_service.js'); requireApp('system/shared/test/unit/mocks/mock_settings_listener.js'); requireApp('system/shared/test/unit/mocks/mock_navigator_moz_settings.js'); requireApp('system/shared/test/unit/mocks/mock_navigator_moz_telephony.js'); requireApp('system/test/unit/mock_app_window.js'); requireApp('system/test/unit/mock_lockscreen_slide.js'); requireApp('system/test/unit/mock_lazy_loader.js'); var mocksForLockScreen = new window.MocksHelper([ 'AppWindow', 'LockScreenSlide', 'LazyLoader', 'SettingsListener', 'Image', 'Canvas', 'Service' ]).init(); var stub = sinon.stub(document, 'getElementById'); stub.returns(document.createElement('div')); requireApp('system/lockscreen/js/lockscreen.js', () => { stub.restore(); }); suite('system/LockScreen >', function() { var subject; var realAudio; var realL10n; var realMozTelephony; var realClock; var realSettingsListener; var realMozSettings; var domPasscodePad; var domEmergencyCallBtn; var domOverlay; var domPasscodeCode; var domMainScreen; var domMaskedBackground; var domCamera; var stubById; var domMessage; var mockGetAllElements; mocksForLockScreen.attachTestHelpers(); setup(function() { stubById = sinon.stub(document, 'getElementById'); stubById.returns(document.createElement('div')); mockGetAllElements = function() { ['area', 'areaCamera', 'areaUnlock', 'altCameraButton', 'iconContainer', 'overlay', 'clockTime', 'date'].forEach(function(name) { subject[name] = document.createElement('div'); }); }; window.LockScreenClockWidget = function() { this.stop = function() { return this; }; this.start = function() { return this; }; this.destroy = function() { return this; }; this.next = function(cb) { cb(); return this; }; }; window.lockScreenNotifications = { bindLockScreen: function() {} }; window.LockScreenConnInfoManager = function() { this.updateConnStates = function() {}; }; window.LockScreenMediaPlaybackWidget = function() {}; window.SettingsURL = function() {}; realAudio = window.Audio; window.Audio = function(src) { this.src = src; }; realL10n = navigator.mozL10n; navigator.mozL10n = window.MockL10n; realMozTelephony = navigator.mozTelephony; navigator.mozTelephony = window.MockNavigatorMozTelephony; realSettingsListener = window.SettingsListener; window.SettingsListener = window.MockSettingsListener; realMozSettings = navigator.mozSettings; navigator.mozSettings = window.MockNavigatorSettings; subject = new window.LockScreen(); domCamera = document.createElement('div'); domPasscodePad = document.createElement('div'); domPasscodePad.id = 'lockscreen-passcode-pad'; domEmergencyCallBtn = document.createElement('a'); domEmergencyCallBtn.dataset.key = 'e'; domPasscodePad.appendChild(domEmergencyCallBtn); domOverlay = document.createElement('div'); domPasscodeCode = document.createElement('div'); document.body.appendChild(domPasscodePad); domMainScreen = document.createElement('div'); domMaskedBackground = document.createElement('div'); domMaskedBackground.id = 'lockscreen-masked-background'; subject.passcodePad = domPasscodePad; domMessage = document.createElement('div'); subject.message = domMessage; subject.lockScreenClockWidget = new window.LockScreenClockWidget(); var mockClock = { start: function() {}, stop: function() {} }; realClock = window.Clock; window.Clock = mockClock; subject.overlay = domOverlay; subject.mainScreen = domMainScreen; subject.maskedBackground = domMaskedBackground; subject.camera = domCamera; subject.lock(); }); test('L10n initialization: it should NOT init the conn info manager if ' + 'there is already one', function() { var stubConnInfoManager = this.sinon.stub(window, 'LockScreenConnInfoManager'); var originalMozl10n = window.navigator.mozL10n; window.navigator.mozL10n = { get: function() { return ''; } }; var originalMozMobileConnections = window.navigator.mozMobileConnections; window.navigator.mozMobileConnections = {}; subject._lockscreenConnInfoManager = {}; assert.isTrue(!!(window.navigator.mozMobileConnections), 'the first condition is not satisfied: ' + !!(window.navigator.mozMobileConnections)); assert.isTrue(!!(subject._lockscreenConnInfoManager), 'the second condition is not satisfied: ' + !!(subject._lockscreenConnInfoManager)); subject.l10nInit(); assert.isFalse(stubConnInfoManager.called, 'the l10nInit still instantiate the conn info manager even it\'s NOT' + 'undefined'); window.navigator.mozMobileConnections = originalMozMobileConnections; window.navigator.mozL10n = originalMozl10n; delete subject._lockscreenConnInfoManager; }); test('When checkPassCodeTimeout, it would check intervals', function() { var method = window.LockScreen.prototype.checkPassCodeTimeout; var mockThis = { fetchLockedInterval: function() {}, fetchUnlockedInterval: function() {} }; var stubFetchLockedInterval = this.sinon.stub().returns(61 * 1000); var stubFetchUnlockedInterval = this.sinon.stub().returns(59 * 1000); mockThis.fetchLockedInterval = stubFetchLockedInterval; mockThis.fetchUnlockedInterval = stubFetchUnlockedInterval; assert.isTrue(method.call(mockThis, 60)); stubFetchLockedInterval = this.sinon.stub().returns(59 * 1000); stubFetchUnlockedInterval = this.sinon.stub().returns(61 * 1000); mockThis.fetchLockedInterval = stubFetchLockedInterval; mockThis.fetchUnlockedInterval = stubFetchUnlockedInterval; assert.isTrue(method.call(mockThis, 60)); stubFetchLockedInterval = this.sinon.stub().returns(59 * 1000); stubFetchUnlockedInterval = this.sinon.stub().returns(59 * 1000); mockThis.fetchLockedInterval = stubFetchLockedInterval; mockThis.fetchUnlockedInterval = stubFetchUnlockedInterval; assert.isFalse(method.call(mockThis, 60)); }); test('Fetch locked interval update the interval when it\'s still locked', function() { var method = window.LockScreen.prototype.fetchLockedInterval; var mockThis = { locked: true, _lastLockedInterval: -1, _lastLockedTimeStamp: 0 }; method.call(mockThis); assert.isTrue(mockThis._lastLockedInterval > -1); }); test('Fetch locked interval update no interval after it\'s unlocked', function() { var method = window.LockScreen.prototype.fetchLockedInterval; var mockThis = { locked: false, _lastLockedInterval: -1, _lastLockedTimeStamp: 0 }; method.call(mockThis); assert.equal(mockThis._lastLockedInterval, -1); }); test('Fetch unlocked interval update the interval when it\'s still unlocked', function() { var method = window.LockScreen.prototype.fetchUnlockedInterval; var mockThis = { locked: false, _lastUnlockedInterval: -1, _lastUnlockedTimeStamp: 0 }; method.call(mockThis); assert.isTrue(mockThis._lastUnlockedInterval > -1); }); test('Fetch unlocked interval update no interval after it\'s locked', function() { var method = window.LockScreen.prototype.fetchUnlockedInterval; var mockThis = { locked: true, _lastUnlockedInterval: -1, _lastUnlockedTimeStamp: 0 }; method.call(mockThis); assert.equal(mockThis._lastUnlockedInterval, -1); }); test('L10n initialization: it should init the conn info manager if it\'s' + ' undefined', function() { var stubConnInfoManager = this.sinon.stub(window, 'LockScreenConnInfoManager'); var originalMozl10n = window.navigator.mozL10n; window.navigator.mozL10n = { get: function() { return ''; } }; var originalMozMobileConnections = window.navigator.mozMobileConnections; window.navigator.mozMobileConnections = {}; window.SIMSlotManager = {}; assert.isTrue(!!(window.navigator.mozMobileConnections), 'the first condition is not satisfied: ' + !!(window.navigator.mozMobileConnections)); assert.isTrue(!subject._lockscreenConnInfoManager, 'the second condition is not satisfied: ' + !(subject._lockscreenConnInfoManager)); subject.l10nInit(); assert.isTrue(stubConnInfoManager.called, 'the l10nInit doesn\'t instantiate the conn info manager even it\'s ' + 'undefined'); window.navigator.mozMobileConnections = originalMozMobileConnections; window.navigator.mozL10n = originalMozl10n; delete subject._lockscreenConnInfoManager; }); test('Lock: can actually lock', function() { subject._lastUnlockedInterval = -1; subject._lastUnlockedTimeStamp = 0; subject._lastLockedTimeStamp = -1; subject.overlay = domOverlay; subject.locked = false; subject.lock(); assert.isTrue(subject.locked); assert.isTrue(subject._lastUnlockedInterval > -1, 'it didn\'t update "_lastUnlockedInterval" when ends the unlocked session'); assert.isTrue(subject._lastLockedTimeStamp > -1, 'it didn\'t update "_lastLockedTimeStamp" when it locks'); }); test('Lock: would create the clock widget', function() { subject.overlay = domOverlay; var stubCreateClockWidget = this.sinon.stub(subject, 'createClockWidget'); MockService.mockQueryWith('screenEnabled', true); subject.locked = false; subject.lock(); assert.isTrue(stubCreateClockWidget.called); }); test('Unlock: can actually unlock', function() { subject._lastLockedInterval = -1; subject._lastLockedTimeStamp = 0; subject._lastUnlockedTimeStamp = -1; subject.overlay = domOverlay; subject.unlock(true); assert.isFalse(subject.locked); assert.isTrue(subject._lastLockedInterval > -1, 'it didn\'t update "_lastLockedInterval" when it ends the locked session'); assert.isTrue(subject._lastUnlockedTimeStamp > -1, 'it didn\'t update "_lastUnlockedTimeStamp" when it unlocks'); }); test('Unlock: uses PasscodeHelper', function() { var StubPasscodeHelper = this.sinon.stub(PasscodeHelper, 'check', function() { return Promise.resolve(true); }); subject._lastLockedInterval = -1; subject._lastLockedTimeStamp = 0; subject._lastUnlockedTimeStamp = -1; subject.overlay = domOverlay; subject.checkPassCode('0000'); assert.isTrue(StubPasscodeHelper.called, 'lockscreen did not call PasscodeHelper to validate passcode'); }); test('Unlock: play sound in system audio channel', function() { window.Audio.prototype.play = function() { assert.equal(this.mozAudioChannelType, 'system'); }; subject.unlock(true, { unlockSoundEnabled: true }); }); suite('Pass code validation >', function() { setup(function() { subject.init(); }); test('validation fail increases error count and timeout', function() { subject.kPassCodeErrorCounter = 20; var oldTimeout = 1; subject.kPassCodeErrorTimeout = oldTimeout; subject.overlay.dataset.passcodeStatus = 'foofoo'; subject.onPasscodeValidationFailed(); assert.isTrue(subject.kPassCodeErrorTimeout > oldTimeout, 'validation fail does not increase error timeout'); assert.isTrue(subject.kPassCodeErrorCounter == 21, 'validation fail does not increase error counter'); assert.isTrue(subject.overlay.dataset.passcodeStatus !== 'foofoo', 'validation fail does not change pass code error status'); }); test('validation success resets error count and timeout', function() { subject.kPassCodeErrorCounter = 20; var oldTimeout = 100000; subject.kPassCodeErrorTimeout = oldTimeout; subject.onPasscodeValidationSuccess(); assert.isTrue(subject.kPassCodeErrorTimeout < oldTimeout/10, 'validation success does not reset error timeout'); assert.isTrue(subject.kPassCodeErrorCounter === 0, 'validation success does not reset error counter'); }); test('validation fail triggers validationfailed/reset events', function() { subject.enabled = true; subject.lock(); subject.kPassCodeErrorCounter = 0; subject.kPassCodeErrorTimeout = 1; var stubDispatch = this.sinon.stub(window, 'dispatchEvent'); // Force setTimeout to run in sync var setTimeoutStub = this.sinon.stub(window, 'setTimeout', function(f){ f(); }); subject.onPasscodeValidationFailed(); assert.isTrue(stubDispatch.firstCall.calledWithMatch(sinon.match( function(e) { return e.type === 'lockscreen-notify-passcode-validationfailed'; })), 'validation fail does not trigger validationfailed as 1st event'); assert.isTrue(stubDispatch.secondCall.calledWithMatch(sinon.match( function(e) { return e.type === 'lockscreen-notify-passcode-validationreset'; })), 'validation fail does not trigger validationreset as 2nd event'); stubDispatch.restore(); setTimeoutStub.restore(); }); }); test('Unlock: would destroy the clock widget', function() { var stubDestroy = this.sinon.stub(subject.lockScreenClockWidget, 'destroy'); subject.overlay = domOverlay; subject.locked = true; subject.unlock(true); assert.isTrue(stubDestroy.called); assert.isUndefined(subject.lockScreenClockWidget); }); suite('Charging status updates', function() { test('When lockscreen is on, start charging status updates', function() { var spy = this.sinon.spy(subject.chargingStatus, 'start'); subject.handleEvent({ type: 'screenchange', detail: {screenEnabled: true} }); assert.isTrue(spy.called); subject.chargingStatus.start.restore(); }); test('When lockscreen is off, stop charging status updates', function() { var spy = this.sinon.spy(subject.chargingStatus, 'stop'); subject.handleEvent({ type: 'screenchange', detail: {screenEnabled: false} }); assert.isTrue(spy.called); subject.chargingStatus.stop.restore(); }); test('When unlocked, stop charging status updates', function() { var spy = this.sinon.spy(subject.chargingStatus, 'stop'); subject.unlock(); assert.isTrue(spy.called); subject.chargingStatus.stop.restore(); }); test('When charging starts, refresh charging status', function() { // we should mock navigator.battery and set charging=true and // see if the element's hidden is removed, but // navigator.battery is read only. See bug 1115921 subject.chargingStatus.start(); var spy = this.sinon.spy(subject.chargingStatus, 'refresh'); navigator.battery.dispatchEvent(new CustomEvent('chargingchange', { charging: true })); assert.isTrue(spy.called); subject.chargingStatus.refresh.restore(); subject.chargingStatus.stop(); }); test('When charging stops, refresh charging status', function() { subject.chargingStatus.start(); var spy = this.sinon.spy(subject.chargingStatus, 'refresh'); navigator.battery.dispatchEvent(new CustomEvent('chargingchange', { charging: false })); assert.isTrue(spy.called); subject.chargingStatus.refresh.restore(); subject.chargingStatus.stop(); }); test('When charging level changes, refresh charging status', function() { subject.chargingStatus.start(); var spy = this.sinon.spy(subject.chargingStatus, 'refresh'); navigator.battery.dispatchEvent(new CustomEvent('levelchange', { level: 0.5 })); assert.isTrue(spy.called); subject.chargingStatus.refresh.restore(); subject.chargingStatus.stop(); }); test('When charging time changes, refresh charging status', function() { subject.chargingStatus.start(); var spy = this.sinon.spy(subject.chargingStatus, 'refresh'); navigator.battery.dispatchEvent(new CustomEvent('chargingtimechange', { chargeTime: 1200 })); assert.isTrue(spy.called); subject.chargingStatus.refresh.restore(); subject.chargingStatus.stop(); }); }); suite('Handle event: screenchange should propogate to _screenEnabled prop', function() { var stubDispatch; setup(function() { stubDispatch = this.sinon.stub(window, 'dispatchEvent'); subject._screenEnabled = undefined; }); test('True', function() { subject.handleEvent({ type: 'screenchange', detail: {screenEnabled: true} }); assert.isTrue(subject._screenEnabled); }); test('False', function() { subject.handleEvent({ type: 'screenchange', detail: {screenEnabled: false} }); assert.isFalse(subject._screenEnabled); }); }); test('Handle event: when screen changed,' + 'would fire event to kill all secure apps', function() { var stubDispatch = this.sinon.stub(window, 'dispatchEvent'); subject.handleEvent({type: 'screenchange', detail: {}}); assert.isTrue(stubDispatch.calledWithMatch(sinon.match( function(e) { return e.type === 'secure-killapps'; })), 'the event was not fired'); stubDispatch.restore(); }); test('Handle event: when press home,' + 'would fire event to close all secure apps', function() { subject.lock(); var stubDispatch = this.sinon.stub(window, 'dispatchEvent'); subject.handleEvent({type: 'home', detail: {}, stopImmediatePropagation: function() {}}); assert.isTrue(stubDispatch.calledWithMatch(sinon.match( function(e) { return e.type === 'secure-closeapps'; })), 'the event was not fired'); stubDispatch.restore(); }); test('Handle event: when unlock,' + 'would fire event to turn secure mode off', function() { var stubDispatch = this.sinon.stub(window, 'dispatchEvent'); subject.unlock(); assert.isTrue(stubDispatch.calledWithMatch(sinon.match( function(e) { return e.type === 'secure-modeoff'; })), 'the event was not fired'); stubDispatch.restore(); }); test('invokeSecureApp: checking manifest and app URL of the fired Event' + 'on secure mode', function() { var urlSamples = [ 'app://system.gaiamobile.org/index.html', 'app://system.gaiamobile.org/index.html#' ]; var expectedManifest = 'app://system.gaiamobile.org/manifest.webapp'; urlSamples.forEach(function(url) { var manifestUrl = url.replace(/(\/)*(index.html#?)*$/, '/manifest.webapp'); assert.equal(manifestUrl, expectedManifest, 'the manifestURL generated is not correct'); }); }); test('Handle event: when lock,' + 'would fire event to turn secure mode on', function() { subject.unlock(); var stubDispatch = this.sinon.stub(window, 'dispatchEvent'); subject.lock(); assert.isTrue(stubDispatch.calledWithMatch(sinon.match( function(e) { return e.type === 'secure-modeon'; })), 'the event was not fired'); stubDispatch.restore(); }); test('Handle event: when unlock animation ends,' + 'it would add `unlocked` class', function() { assert.isFalse(subject.overlay.classList.contains('unlocked')); subject.locked = false; subject.handleEvent({ type: 'transitionend', target: subject.overlay }); assert.isTrue(subject.overlay.classList.contains('unlocked')); }); test('Unlock method will add `unlocked` class if it is not an' + ' instant request', function() { assert.isFalse(subject.overlay.classList.contains('unlocked')); subject.enabled = true; subject.unlock(true); // or lock screen is already enabled assert.isTrue(subject.overlay.classList.contains('unlocked')); }); test('Switch panel: to Camera; should notify SecureWindowFactory\'s method', function() { var stubDispatch = this.sinon.stub(window, 'dispatchEvent'); subject.invokeSecureApp('camera'); assert.isTrue(stubDispatch.calledWithMatch(sinon.match(function(e) { return 'secure-launchapp' === e.type; })), 'the corresponding creation method was no invoked'); stubDispatch.restore(); }); test('Message: message should appear on screen when set', function() { var message = 'message'; subject.setLockMessage(message); assert.equal(subject.message.hidden, false); assert.equal(subject.message.textContent, message); }); test('Message: message should disappear when unset', function() { subject.setLockMessage(''); assert.equal(subject.message.textContent, ''); assert.equal(subject.message.hidden, true); }); suite('lockscreen.lock-immediately settings observer >', function() { setup(function() { subject.init(); // to register observers }); test('Locks when lock-immediately setting is set to true', function () { var stubDispatch = this.sinon.stub(window, 'dispatchEvent'); subject.enabled = true; subject.unlock(); // or lock screen is already enabled window.MockSettingsListener.mTriggerCallback( 'lockscreen.lock-immediately', true); assert.isTrue(stubDispatch.calledWithMatch(sinon.match( function(e) { return e.type === 'lockscreen-request-lock'; })), 'does not request lock after lock-immediately setting is changed'); stubDispatch.restore(); }); test('resets lock/unlock timestamps', function () { subject.enabled = true; subject.passCodeEnabled = true; var spy = this.sinon.spy(subject, 'resetTimeoutForcibly'); window.MockSettingsListener.mTriggerCallback( 'lockscreen.lock-immediately', true); assert.isTrue(spy.called, 'lock-immediately does not reset timestamps'); }); }); test('resetTimeoutForcibly triggers password check after lock', function () { subject.enabled = true; subject.passCodeEnabled = true; subject.passCodeRequestTimeout = 60 * 1000; // 60 seconds subject.unlock(); // or .lock() won't update timestamps subject.lock(); assert.isFalse(subject.checkPassCodeTimeout(subject.passCodeRequestTimeout), 'pass code timeout check triggers right after lock'); subject.resetTimeoutForcibly(); assert.isTrue(subject.checkPassCodeTimeout(subject.passCodeRequestTimeout), 'resetTimeoutForcibly does not trigger pass code after lock'); }); test('Locks the screen: the overlay would be set as locked', function() { var method = window.LockScreen.prototype.lock; var stubOverlayLocked = this.sinon.stub(); var mockThis = { locked: false, overlayLocked: stubOverlayLocked, mainScreen: document.createElement('div'), maskedBackground : document.createElement('div'), createClockWidget: function() {}, dispatchEvent: function() {}, _checkGenerateMaskedBackgroundColor: function() { return false; } }; method.call(mockThis); assert.isTrue(stubOverlayLocked.called); }); suite('Wallpaper', function() { test('Should update background when inited', function() { this.sinon.stub(subject, 'updateBackground'); MockService.mockQueryWith('getWallpaper', 'blob:app://wallpaper.gaiamobile.org/b10b-1d'); subject.init(); assert.isTrue(subject.updateBackground.calledWith( 'blob:app://wallpaper.gaiamobile.org/b10b-1d')); }); test('Should update background when wallpaper changed', function() { this.sinon.stub(subject, 'updateBackground'); subject.init(); window.dispatchEvent(new CustomEvent('wallpaperchange', { detail: { url: 'blob:app://wallpaper.gaiamobile.org/b10b-1d' } })); assert.isTrue(subject.updateBackground.calledWith( 'blob:app://wallpaper.gaiamobile.org/b10b-1d')); }); }); // XXX: Test 'Screen off: by proximity sensor'. suite('Background functionality', function() { var bgURL = 'blob:app://wallpaper.gaiamobile.org/b10b-1d'; suite('updateBackground', function() { var stubGenerate; setup(function() { stubGenerate = this.sinon.stub(subject, '_generateMaskedBackgroundColor'); }); test('updateBackground, Screen is not enabled and not locked', function() { subject._screenEnabled = false; subject.locked = false; subject.updateBackground(bgURL); var bgElem = stubById.getCall(0).returnValue; assert.equal(bgElem.style.backgroundImage, 'url("' + bgURL + '")'); assert.equal(subject._regenerateMaskedBackgroundColorFrom, bgURL); assert.isFalse(stubGenerate.called); assert.isTrue(subject._shouldRegenerateMaskedBackgroundColor); }); test('updateBackground, Screen is enabled and not locked', function() { subject._screenEnabled = true; subject.locked = false; subject.updateBackground(bgURL); var bgElem = stubById.getCall(0).returnValue; assert.equal(bgElem.style.backgroundImage, 'url("' + bgURL + '")'); assert.equal(subject._regenerateMaskedBackgroundColorFrom, bgURL); assert.isFalse(stubGenerate.called); assert.isTrue(subject._shouldRegenerateMaskedBackgroundColor); }); test('updateBackground, Screen is not enabled and is locked', function() { subject._screenEnabled = false; subject.locked = true; subject.updateBackground(bgURL); var bgElem = stubById.getCall(0).returnValue; assert.equal(bgElem.style.backgroundImage, 'url("' + bgURL + '")'); assert.equal(subject._regenerateMaskedBackgroundColorFrom, bgURL); assert.isFalse(stubGenerate.called); assert.isTrue(subject._shouldRegenerateMaskedBackgroundColor); }); var testScreenEnabledAndLocked = function testScreenEnabledAndLocked() { subject.locked = true; subject.updateBackground(bgURL); var bgElem = stubById.getCall(0).returnValue; assert.equal(bgElem.style.backgroundImage, 'url("' + bgURL + '")'); assert.equal(subject._regenerateMaskedBackgroundColorFrom, bgURL); assert.isTrue(stubGenerate.called); assert.isFalse(subject._shouldRegenerateMaskedBackgroundColor); }; test('updateBackground, Screen is enabled and is locked', function() { subject._screenEnabled = true; testScreenEnabledAndLocked(); }); test('updateBackground, _screenEnabled = undefined is regarded as ' + 'screen is enabled', function() { subject._screenEnabled = undefined; testScreenEnabledAndLocked(); }); }); test('checkGenerateMaskedBackgroundColor', function() { subject._shouldRegenerateMaskedBackgroundColor = true; subject._regenerateMaskedBackgroundColorFrom = bgURL; assert.isTrue(subject._checkGenerateMaskedBackgroundColor()); subject._shouldRegenerateMaskedBackgroundColor = false; subject._regenerateMaskedBackgroundColorFrom = undefined; assert.isFalse(subject._checkGenerateMaskedBackgroundColor()); }); test('Lock : Removes masked Overlay if there are no notifications', function() { var method = window.LockScreen.prototype.lock; subject.maskedBackground.classList.add('blank'); var mockThis = { locked: false, overlayLocked: function() {}, mainScreen: document.createElement('div'), maskedBackground : subject.maskedBackground, createClockWidget: function() {}, dispatchEvent: function() {}, _checkGenerateMaskedBackgroundColor: function() { return false; } }; method.call(mockThis); assert.strictEqual(subject.maskedBackground.style.backgroundColor, 'transparent'); }); suite('generateMaskedBackgroundColor', function() { // unit-test the function is a bit tricky: first we use the mocked image // object (and set fake height/width). then we use the mocked canvas // object that returns a image data that would calculate into red // (#ff0000), green (#00ff00), and blue (#0000ff) for each of the three // tests respectively, and we match the calculatd hsl against the known // result. // i.e. the image src fed into the function is actually not used in the // calculation, for the test's sake. var mockCanvas; var mockContext; var mockBgElem; var img; var canvasWidth; var canvasHeight; var fakeImageData; setup(function() { mockCanvas = new MockCanvas(); this.sinon.stub(document, 'createElement').returns(mockCanvas); mockContext = new MockCanvasRenderingContext2D(); mockBgElem = { dataset: {}, classList: { contains: this.sinon.stub().returns(true) }, style: {} }; subject.maskedBackground = mockBgElem; MockImage.teardown(); subject._regenerateMaskedBackgroundColorFrom = bgURL; subject._generateMaskedBackgroundColor(); assert.isFalse(subject._shouldRegenerateMaskedBackgroundColor); assert.strictEqual( subject._regenerateMaskedBackgroundColorFrom, undefined ); img = MockImage.instances[0]; img.width = 100; img.height = 100; canvasWidth = img.width * window.devicePixelRatio; canvasHeight = img.height * window.devicePixelRatio; this.sinon.stub(mockCanvas, 'getContext').returns(mockContext); // fill the fake imagedata with all red fakeImageData = []; }); test('red', function() { for (var row = 0; row < canvasHeight; row++) { for (var col = 0; col < canvasWidth; col++) { fakeImageData[((canvasWidth * row) + col) * 4] = 255; // r fakeImageData[((canvasWidth * row) + col) * 4 + 1] = 0; // g fakeImageData[((canvasWidth * row) + col) * 4 + 2] = 0; // b } } this.sinon.stub(mockContext, 'getImageData').returns({ data: fakeImageData }); img.triggerEvent('onload'); assert.equal( mockBgElem.dataset.wallpaperColor, 'hsla(0, 100%, 45%, 0.7)' ); assert.isFalse('backgroundColor' in mockBgElem.style); }); test('green', function() { for (var row = 0; row < canvasHeight; row++) { for (var col = 0; col < canvasWidth; col++) { fakeImageData[((canvasWidth * row) + col) * 4] = 0; // r fakeImageData[((canvasWidth * row) + col) * 4 + 1] = 255; // g fakeImageData[((canvasWidth * row) + col) * 4 + 2] = 0; // b } } this.sinon.stub(mockContext, 'getImageData').returns({ data: fakeImageData }); img.triggerEvent('onload'); assert.equal( mockBgElem.dataset.wallpaperColor, 'hsla(120, 100%, 45%, 0.7)' ); assert.isFalse('backgroundColor' in mockBgElem.style); }); test('blue', function() { for (var row = 0; row < canvasHeight; row++) { for (var col = 0; col < canvasWidth; col++) { fakeImageData[((canvasWidth * row) + col) * 4] = 0; // r fakeImageData[((canvasWidth * row) + col) * 4 + 1] = 0; // g fakeImageData[((canvasWidth * row) + col) * 4 + 2] = 255; // b } } this.sinon.stub(mockContext, 'getImageData').returns({ data: fakeImageData }); img.triggerEvent('onload'); assert.equal( mockBgElem.dataset.wallpaperColor, 'hsla(240, 100%, 45%, 0.7)' ); assert.isFalse('backgroundColor' in mockBgElem.style); }); test('red & update background style directly', function() { for (var row = 0; row < canvasHeight; row++) { for (var col = 0; col < canvasWidth; col++) { fakeImageData[((canvasWidth * row) + col) * 4] = 255; // r fakeImageData[((canvasWidth * row) + col) * 4 + 1] = 0; // g fakeImageData[((canvasWidth * row) + col) * 4 + 2] = 0; // b } } this.sinon.stub(mockContext, 'getImageData').returns({ data: fakeImageData }); mockBgElem.classList.contains.returns(false); img.triggerEvent('onload'); assert.isTrue(mockBgElem.classList.contains.calledWith('blank')); assert.equal( mockBgElem.style.backgroundColor, 'hsla(0, 100%, 45%, 0.7)' ); }); }); }); teardown(function() { navigator.mozL10n = realL10n; navigator.mozTelephony = realMozTelephony; window.Audio = realAudio; window.Clock = realClock; window.SettingsListener = realSettingsListener; navigator.mozSettings = realMozSettings; document.body.removeChild(domPasscodePad); subject.passcodePad = null; window.MockSettingsListener.mTeardown(); window.MockNavigatorSettings.mTeardown(); stubById.restore(); }); });