VulpesOS/gaia/apps/system/test/unit/lockscreen/lockscreen_test.js
OverSu b38dea0597 Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
2026-09-30 23:13:03 +02:00

957 lines
34 KiB
JavaScript
Executable file

/* 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();
});
});