VulpesOS/gaia/apps/system/test/unit/app_window_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

3443 lines
114 KiB
JavaScript
Executable file

/* global AppWindow, ScreenLayout, MockService, IconsHelper,
MocksHelper, BaseModule, MockContextMenu,
MockAppTransitionController, MockPermissionSettings, DocumentFragment,
MockAudioChannelController, AppChrome,
MockWebManifestHelper, MockPromise */
'use strict';
requireApp('system/shared/test/unit/mocks/mock_manifest_helper.js');
requireApp('system/shared/test/unit/mocks/mock_settings_helper.js');
requireApp('system/shared/test/unit/mocks/mock_settings_listener.js');
requireApp('system/test/unit/mock_context_menu.js');
requireApp('system/test/unit/mock_applications.js');
requireApp('system/test/unit/mock_app_chrome.js');
requireApp('system/test/unit/mock_screen_layout.js');
requireApp('system/test/unit/mock_lazy_loader.js');
requireApp('system/test/unit/mock_app_transition_controller.js');
requireApp('system/test/unit/mock_audio_channel_controller.js');
requireApp('system/shared/test/unit/mocks/mock_service.js');
requireApp('system/shared/test/unit/mocks/mock_permission_settings.js');
require('/shared/test/unit/mocks/mock_web_manifest_helper.js');
require('/shared/test/unit/mocks/mock_promise.js');
require('/shared/test/unit/mocks/mock_icons_helper.js');
var mocksForAppWindow = new MocksHelper([
'Applications', 'SettingsListener',
'ManifestHelper', 'WebManifestHelper',
'ScreenLayout', 'AppChrome', 'IconsHelper',
'AppTransitionController', 'Service', 'LazyLoader'
]).init();
suite('system/AppWindow', function() {
var realPermissionSettings;
mocksForAppWindow.attachTestHelpers();
var fakeDOMRequest = {
onsuccess: function() {},
onerror: function() {},
then: function(success, error) {
this.onsuccess = function(evt) {
success(evt.target.result);
};
this.onerror = function() {
error();
};
}
};
setup(function(done) {
this.sinon.useFakeTimers();
window.Service = MockService;
realPermissionSettings = navigator.mozPermissionSettings;
navigator.mozPermissionSettings = MockPermissionSettings;
MockPermissionSettings.mSetup();
this.sinon.stub(document, 'getElementById', function() {
return document.createElement('div');
});
this.sinon.stub(DocumentFragment.prototype, 'getElementById', function() {
return document.createElement('div');
});
this.sinon.stub(HTMLElement.prototype, 'querySelector',
function() {
return document.createElement('div');
});
requireApp('system/js/browser_config_helper.js');
requireApp('system/js/browser_frame.js');
requireApp('system/js/base_module.js');
requireApp('system/js/app_window.js');
requireApp('system/js/browser_mixin.js', function() {
this.sinon.stub(BaseModule, 'instantiate', function(name) {
if (name === 'BrowserContextMenu') {
return MockContextMenu;
}
});
done();
}.bind(this));
});
teardown(function() {
navigator.mozPermissionSettings = realPermissionSettings;
delete window.Service;
});
var fakeAppConfig1 = {
url: 'app://www.fake/index.html',
manifest: {},
manifestURL: 'app://wwww.fake/ManifestURL',
origin: 'app://www.fake'
};
var fakeAppConfig2 = {
url: 'app://www.fake2/index.html',
manifest: {},
manifestURL: 'app://wwww.fake2/ManifestURL',
origin: 'app://www.fake2'
};
var fakeAppConfig3 = {
url: 'app://www.fake3/index.html',
manifest: {},
manifestURL: 'app://wwww.fake3/ManifestURL',
origin: 'app://www.fake3'
};
var fakeAppConfig4 = {
url: 'app://www.fake4/index.html',
manifest: {},
origin: 'app://www.fake4'
};
var fakePrivateConfig = {
url: 'http://www.private/index.html',
manifest: null,
origin: 'http://www.private',
isPrivate: true
};
var fakeAppConfigBackground = {
url: 'app://www.fakebackground/index.html',
manifest: {},
origin: 'app://www.fakebackground',
stayBackground: true
};
var fakeAppConfigWithIcon = {
url: 'app://www.fake4/index.html',
manifest: {
icons: {
'100': '/xxxx.png'
}
},
manifestURL: 'app://wwww.fake4/ManifestURL',
origin: 'app://www.fake4'
};
var fakeAppConfigWithMultiIcon = {
url: 'app://www.fake4/index.html',
manifest: {
icons: {
'30': '/foo.jpg',
'60': '/bar.jpg',
'120': '/xxxx.png',
'240': '/yyyy.ico',
'300': '/lol.gif',
'400': '/xd.img'
}
},
manifestURL: 'app://wwww.fake4/ManifestURL',
origin: 'app://www.fake4'
};
var fakeSearchAppConfig = {
url: 'app://search.gaiamobile.org/newtab.html',
manifest: {
role: 'search'
},
manifestURL: 'app://search.gaiamobile.org/ManifestURL',
origin: 'app://search.gaiamobile.org'
};
var fakeWrapperConfig = {
url: 'http://www.fake5/index.html',
origin: 'http://www.fake5',
title: 'Fakebook'
};
var fakeChromeConfigWithoutNavigation = {
url: 'http://www.fakeChrome2/index.html',
origin: 'http://www.fakeChrome2',
manifest: {
chrome: { 'navigation': false }
}
};
var fakeChromeConfigWithNavigationBar = {
url: 'http://www.fakeChrome2/index.html',
origin: 'http://www.fakeChrome2',
manifest: {
chrome: { 'navigation': false, 'bar': true }
}
};
var fakeAppConfigCertified = {
url: 'app://www.fakecertified/index.html',
manifest: {
type: 'certified'
},
origin: 'app://www.fake4'
};
var fakeAppWithName = {
url: 'http://fakeapp.com/index.html',
manifest: {
name: 'Fake App Name'
},
origin: 'http://fakeapp.com'
};
var fakeInputAppConfig = {
url: 'app://www.fakeinput/index.html',
manifest: {},
manifestURL: 'app://www.fakeinput/ManifestURL',
origin: 'app://www.fakeinput',
isInputMethod: true
};
test('Sanity check instances dont share .element', function() {
var app1 = new AppWindow(fakeAppConfig1);
var app2 = new AppWindow(fakeAppConfig2);
assert.isFalse(app1.element === app2.element);
});
test('App created with instanceID', function() {
var app1 = new AppWindow(fakeAppConfig1);
var app2 = new AppWindow(fakeAppConfig2);
assert.isTrue(app1.instanceID !== app2.instanceID);
});
test('Automatically enable navigation for a brower window.', function() {
var app1 = new AppWindow(fakeWrapperConfig);
assert.equal(app1.config.chrome.scrollable, true);
});
test('setActive', function() {
var app1 = new AppWindow(fakeAppConfig1);
var app2 = new AppWindow(fakeAppConfig2);
this.sinon.stub(app2, '_setActive');
this.sinon.stub(app1, 'getTopMostWindow').returns(app2);
app1.setActive(true);
assert.isTrue(app2._setActive.calledWith(true));
});
suite('Resize', function() {
var app1;
setup(function() {
app1 = new AppWindow(fakeAppConfig1);
// Inject mozBrowser API to app iframe
injectFakeMozBrowserAPI(app1.browser.element);
this.sinon.stub(app1.browser.element, 'addNextPaintListener');
});
teardown(function() {});
test('Resize in foreground', function(done) {
var stubIsActive = this.sinon.stub(app1, 'isActive');
this.sinon.stub(app1, 'reviveBrowser');
stubIsActive.returns(true);
var p = app1.resize();
assert.isTrue(app1.resized);
assert.isTrue(app1.reviveBrowser.called);
assert.isTrue(app1.browser.element.addNextPaintListener.calledOnce);
app1.browser.element.addNextPaintListener.firstCall.args[0]();
p.catch((e) => { throw e || 'Should not reject'; }).then(done, done);
});
test('Resize in background', function() {
var stubIsActive = this.sinon.stub(app1, 'isActive');
stubIsActive.returns(false);
app1.resize();
assert.isUndefined(app1.resized);
assert.isFalse(app1.browser.element.addNextPaintListener.calledOnce);
});
test('Resize if we are fullscreen', function(done) {
MockService.mockQueryWith('getHeightFor', 480);
var stubIsFullScreen = this.sinon.stub(app1, 'isFullScreen');
stubIsFullScreen.returns(true);
var stubIsActive = this.sinon.stub(app1, 'isActive');
stubIsActive.returns(true);
var p = app1.resize();
assert.equal(app1.height, MockService.mockQueryWith('getHeightFor'));
assert.isTrue(app1.browser.element.addNextPaintListener.calledOnce);
app1.browser.element.addNextPaintListener.firstCall.args[0]();
p.catch((e) => { throw e || 'Should not reject'; }).then(done, done);
});
test('Resize if we are not fullscreen', function(done) {
MockService.mockQueryWith('getHeightFor', 430);
var stubIsFullScreen = this.sinon.stub(app1, 'isFullScreen');
stubIsFullScreen.returns(false);
var stubIsActive = this.sinon.stub(app1, 'isActive');
stubIsActive.returns(true);
var p = app1.resize();
assert.equal(app1.height, MockService.mockQueryWith('getHeightFor'));
assert.isTrue(app1.browser.element.addNextPaintListener.calledOnce);
app1.browser.element.addNextPaintListener.firstCall.args[0]();
p.catch((e) => { throw e || 'Should not reject'; }).then(done, done);
});
test('Send message to appChrome: w/o keyboard', function(done) {
MockService.mockQueryWith('keyboardEnabled', false);
var stubIsActive = this.sinon.stub(app1, 'isActive');
var stubbroadcast = this.sinon.stub(app1, 'broadcast');
stubIsActive.returns(true);
var p = app1.resize();
assert.isTrue(stubbroadcast.calledWith('withoutkeyboard'));
assert.isTrue(app1.browser.element.addNextPaintListener.calledOnce);
app1.browser.element.addNextPaintListener.firstCall.args[0]();
p.catch((e) => { throw e || 'Should not reject'; }).then(done, done);
});
test('Send message to appChrome: w/ keyboard', function(done) {
MockService.mockQueryWith('keyboardEnabled', true);
var stubIsActive = this.sinon.stub(app1, 'isActive');
var stubbroadcast = this.sinon.stub(app1, 'broadcast');
stubIsActive.returns(true);
var p = app1.resize();
assert.isTrue(stubbroadcast.calledWith('withkeyboard'));
app1.browser.element.addNextPaintListener.firstCall.args[0]();
p.catch((e) => { throw e || 'Should not reject'; }).then(done, done);
});
test('Reset the screenshot overlay visibilty', function(done) {
app1.screenshotOverlay.style.visibility = 'hidden';
var stubIsActive = this.sinon.stub(app1, 'isActive');
stubIsActive.returns(true);
var p = app1.resize();
assert.equal(app1.screenshotOverlay.style.visibility, '');
assert.isTrue(app1.browser.element.addNextPaintListener.calledOnce);
app1.browser.element.addNextPaintListener.firstCall.args[0]();
p.catch((e) => { throw e || 'Should not reject'; }).then(done, done);
});
test('No navigation setting in manifest', function() {
var spy = this.sinon.spy(window, 'AppChrome');
new AppWindow(fakeChromeConfigWithoutNavigation); // jshint ignore:line
assert.isFalse(spy.calledWithNew());
});
test('No navigation setting in manifest - app - appChrome', function() {
var spy = this.sinon.spy(window, 'AppChrome');
var aw = new AppWindow(fakeChromeConfigWithoutNavigation);
assert.isFalse(spy.calledWithNew());
aw.element.dispatchEvent(new CustomEvent('_opened'));
assert.isTrue(spy.calledWithNew());
});
test('No navigation setting in manifest - wrapper - appChrome', function() {
var spy = this.sinon.spy(window, 'AppChrome');
new AppWindow(fakeWrapperConfig); // jshint ignore:line
assert.isTrue(spy.calledWithNew());
});
test('Navigation bar in manifest - appChrome', function() {
var spy = this.sinon.spy(window, 'AppChrome');
var aw = new AppWindow(fakeChromeConfigWithNavigationBar);
aw.element.dispatchEvent(new CustomEvent('_opened'));
assert.isTrue(spy.calledWithNew());
});
test('resize to bottom window thru top most window', function(done) {
var popups = openPopups(5);
injectFakeMozBrowserAPI(popups[0].browser.element);
this.sinon.stub(popups[0].browser.element, 'addNextPaintListener');
injectFakeMozBrowserAPI(popups[1].browser.element);
this.sinon.stub(popups[1].browser.element, 'addNextPaintListener');
injectFakeMozBrowserAPI(popups[2].browser.element);
this.sinon.stub(popups[2].browser.element, 'addNextPaintListener');
injectFakeMozBrowserAPI(popups[3].browser.element);
this.sinon.stub(popups[3].browser.element, 'addNextPaintListener');
injectFakeMozBrowserAPI(popups[4].browser.element);
this.sinon.stub(popups[4].browser.element, 'addNextPaintListener');
var stubAppIsActive = this.sinon.stub(popups[0], 'isActive');
stubAppIsActive.returns(true);
var p = popups[0].resize();
assert.isTrue(popups[0].browser.element.addNextPaintListener.calledOnce);
popups[0].browser.element.addNextPaintListener.firstCall.args[0]();
assert.isTrue(popups[1].browser.element.addNextPaintListener.calledOnce);
popups[1].browser.element.addNextPaintListener.firstCall.args[0]();
assert.isTrue(popups[2].browser.element.addNextPaintListener.calledOnce);
popups[2].browser.element.addNextPaintListener.firstCall.args[0]();
assert.isTrue(popups[3].browser.element.addNextPaintListener.calledOnce);
popups[3].browser.element.addNextPaintListener.firstCall.args[0]();
assert.isTrue(popups[4].browser.element.addNextPaintListener.calledOnce);
popups[4].browser.element.addNextPaintListener.firstCall.args[0]();
p.then(() => undefined, (e) => { throw e || 'Should not reject'; })
.then(done, done);
});
});
suite('Render', function() {
var visibleSpy;
setup(function() {
visibleSpy = this.sinon.stub(AppWindow.prototype, 'setVisible');
});
test('apps with background flag launched in background', function() {
new AppWindow(fakeAppConfigBackground); // jshint ignore:line
sinon.assert.calledWith(visibleSpy, false);
});
test('homescreen is launched at background', function() {
var renderSpy = this.sinon.stub(AppWindow.prototype, 'render');
var app = new AppWindow(fakeAppConfig1);
renderSpy.restore();
app.isHomescreen = true;
app.render();
sinon.assert.calledWith(visibleSpy, false);
});
});
suite('Orientations', function() {
var fakeAppConfigWithPortraitOrientation = {
url: 'app://www.fake.portrait/index.html',
manifest: {
orientation: 'portrait'
},
manifestURL: 'app://www.fake.portrait/ManifestURL',
origin: 'app://www.fake.portrait'
};
var fakeAppConfigWithLandscapeOrientation = {
url: 'app://www.fake.landscape/index.html',
manifest: {
orientation: 'landscape'
},
manifestURL: 'app://www.fake.landscape/ManifestURL',
origin: 'app://www.fake.landscape'
};
var fakeAppConfigWithLandscapePrimaryOrientation = {
url: 'app://www.fake.landscape.primary/index.html',
manifest: {
orientation: 'landscape-primary'
},
manifestURL: 'app://www.fake.landscape.primary/ManifestURL',
origin: 'app://www.fake.landscape.primary'
};
var fakeAppConfigWithDefaultOrientation = {
url: 'app://www.fake.default/index.html',
manifest: {
orientation: 'default'
},
manifestURL: 'app://www.fake.default/ManifestURL',
origin: 'app://www.fake.default'
};
var fakeAppConfigWithLandscapeSecondaryOrientation = {
url: 'app://www.fake.landscape.secondary/index.html',
manifest: {
orientation: 'landscape-secondary'
},
manifestURL: 'app://wwww.fake.landscape.secondary/ManifestURL',
origin: 'app://www.fake.landscape.secondary'
};
var fakeAppConfigWithPortraitPrimaryOrientation = {
url: 'app://www.fake.portrait.primary/index.html',
manifest: {
orientation: 'portrait-primary'
},
manifestURL: 'app://wwww.fake.portrait.primary/ManifestURL',
origin: 'app://wwww.fake.portrait.primary'
};
var fakeAppConfigWithPortraitSecondaryOrientation = {
url: 'app://www.fake.portrait.secondary/index.html',
manifest: {
orientation: 'portrait-secondary'
},
manifestURL: 'app://wwww.fake.portrait.secondary/ManifestURL',
origin: 'app://wwww.fake.portrait.secondary'
};
var fakeAppConfigWithOrientationArray = {
url: 'app://www.fake.array/index.html',
manifest: {
orientation: ['portrait-secondary', 'landscape']
},
manifestURL: 'app://wwww.fake.array/ManifestURL',
origin: 'app://wwww.fake.array'
};
test('setOrientation()', function() {
var popups = openPopups(4);
this.sinon.stub(popups[0], 'isActive').returns(true);
this.sinon.stub(popups[1], 'isActive').returns(true);
this.sinon.stub(popups[2], 'isActive').returns(true);
var stubLockOrientation1 = this.sinon.stub(popups[1], 'lockOrientation');
var stubLockOrientation2 = this.sinon.stub(popups[2], 'lockOrientation');
popups[0].setOrientation();
assert.isFalse(stubLockOrientation1.called);
assert.isTrue(stubLockOrientation2.called);
});
var stubScreenMozLockOrientation, stubScreenMozUnlockOrientation;
suite('lockOrientation()', function() {
setup(function() {
MockService.mockQueryWith('defaultOrientation', 'portrait-primary');
MockService.mockQueryWith('globalOrientation', null);
stubScreenMozLockOrientation =
this.sinon.stub(screen, 'mozLockOrientation');
stubScreenMozUnlockOrientation =
this.sinon.stub(screen, 'mozUnlockOrientation');
});
test('No orientation entry in manifest should unlock screen orientation.',
function() {
var app = new AppWindow(fakeAppConfig1);
app.lockOrientation();
assert.isTrue(stubScreenMozUnlockOrientation.called);
});
test('default orientation', function() {
var app = new AppWindow(fakeAppConfigWithDefaultOrientation);
app.lockOrientation();
assert.isTrue(stubScreenMozLockOrientation
.calledWith('default'));
});
test('portrait orientation', function() {
var app = new AppWindow(fakeAppConfigWithPortraitOrientation);
app.lockOrientation();
assert.isTrue(stubScreenMozLockOrientation
.calledWith('portrait'));
});
test('landscape orientation', function() {
var app = new AppWindow(fakeAppConfigWithLandscapeOrientation);
app.lockOrientation();
assert.isTrue(stubScreenMozLockOrientation
.calledWith('landscape'));
});
test('portrait-primary orientation', function() {
var app = new AppWindow(fakeAppConfigWithPortraitPrimaryOrientation);
app.lockOrientation();
assert.isTrue(stubScreenMozLockOrientation
.calledWith('portrait-primary'));
});
test('portrait-secondary orientation', function() {
var app = new AppWindow(fakeAppConfigWithPortraitSecondaryOrientation);
app.lockOrientation();
assert.isTrue(stubScreenMozLockOrientation
.calledWith('portrait-secondary'));
});
test('landscape-primary orientation', function() {
var app = new AppWindow(fakeAppConfigWithLandscapePrimaryOrientation);
app.lockOrientation();
assert.isTrue(stubScreenMozLockOrientation
.calledWith('landscape-primary'));
});
test('landscape-secondary orientation', function() {
var app = new AppWindow(fakeAppConfigWithLandscapeSecondaryOrientation);
app.lockOrientation();
assert.isTrue(stubScreenMozLockOrientation
.calledWith('landscape-secondary'));
});
test('array of orientations', function() {
var app = new AppWindow(fakeAppConfigWithOrientationArray);
app.lockOrientation();
assert.isTrue(stubScreenMozLockOrientation
.calledWith(['portrait-secondary', 'landscape']));
});
});
test('rotatingDegree on / default is portrait-primary', function() {
MockService.mockQueryWith('defaultOrientation', 'portrait-primary');
MockService.mockQueryWith('isDefaultPortrait', true);
var app1 = new AppWindow(fakeAppConfig1);
assert.isTrue(typeof(app1.rotatingDegree) !== 'undefined');
var app2 = new AppWindow(fakeAppConfigWithDefaultOrientation);
assert.isTrue(app2.rotatingDegree === 0);
var app3 = new AppWindow(fakeAppConfigWithPortraitOrientation);
assert.isTrue(app3.rotatingDegree === 0);
var app4 = new AppWindow(fakeAppConfigWithLandscapeOrientation);
assert.isTrue(app4.rotatingDegree === 90);
var app5 = new AppWindow(fakeAppConfigWithPortraitPrimaryOrientation);
assert.isTrue(app5.rotatingDegree === 0);
var app6 = new AppWindow(fakeAppConfigWithPortraitSecondaryOrientation);
assert.isTrue(app6.rotatingDegree === 180);
var app7 = new AppWindow(fakeAppConfigWithLandscapePrimaryOrientation);
assert.isTrue(app7.rotatingDegree === 90);
var app8 = new AppWindow(fakeAppConfigWithLandscapeSecondaryOrientation);
assert.isTrue(app8.rotatingDegree === 270);
});
test('rotatingDegree on / default is landscape-primary', function() {
MockService.mockQueryWith('defaultOrientation', 'landscape-primary');
MockService.mockQueryWith('isDefaultPortrait', false);
var app1 = new AppWindow(fakeAppConfig1);
assert.isTrue(typeof(app1.rotatingDegree) !== 'undefined');
var app3 = new AppWindow(fakeAppConfigWithPortraitOrientation);
assert.isTrue(app3.rotatingDegree === 270);
var app4 = new AppWindow(fakeAppConfigWithLandscapeOrientation);
assert.isTrue(app4.rotatingDegree === 0);
var app5 = new AppWindow(fakeAppConfigWithPortraitPrimaryOrientation);
assert.isTrue(app5.rotatingDegree === 270);
var app6 = new AppWindow(fakeAppConfigWithPortraitSecondaryOrientation);
assert.isTrue(app6.rotatingDegree === 90);
var app7 = new AppWindow(fakeAppConfigWithLandscapePrimaryOrientation);
assert.isTrue(app7.rotatingDegree === 0);
var app8 = new AppWindow(fakeAppConfigWithLandscapeSecondaryOrientation);
assert.isTrue(app8.rotatingDegree === 180);
});
test('closing Rotation Degree / default is portrait-primary', function() {
MockService.mockQueryWith('defaultOrientation', 'portrait-primary');
MockService.mockQueryWith('fetchCurrentOrientation', 'portrait-primary');
MockService.mockQueryWith('isDefaultPortrait', true);
var app1 = new AppWindow(fakeAppConfig1);
var angle1 = app1.determineClosingRotationDegree();
assert.equal(angle1, 0);
MockService.mockQueryWith('fetchCurrentOrientation',
'portrait-secondary');
var angle2 = app1.determineClosingRotationDegree();
assert.equal(angle2, 180);
MockService.mockQueryWith('fetchCurrentOrientation', 'landscape-primary');
var angle3 = app1.determineClosingRotationDegree();
assert.equal(angle3, 270);
MockService.mockQueryWith('fetchCurrentOrientation',
'landscape-secondary');
var angle4 = app1.determineClosingRotationDegree();
assert.equal(angle4, 90);
});
test('closing Rotation Degree / default is landscape-primary', function() {
MockService.mockQueryWith('defaultOrientation', 'landscape-primary');
MockService.mockQueryWith('fetchCurrentOrientation', 'portrait-primary');
MockService.mockQueryWith('isDefaultPortrait', false);
var app1 = new AppWindow(fakeAppConfig1);
var angle1 = app1.determineClosingRotationDegree();
assert.equal(angle1, 90);
MockService.mockQueryWith('fetchCurrentOrientation',
'portrait-secondary');
var angle2 = app1.determineClosingRotationDegree();
assert.equal(angle2, 270);
MockService.mockQueryWith('fetchCurrentOrientation', 'landscape-primary');
var angle3 = app1.determineClosingRotationDegree();
assert.equal(angle3, 0);
MockService.mockQueryWith('fetchCurrentOrientation',
'landscape-secondary');
var angle4 = app1.determineClosingRotationDegree();
assert.equal(angle4, 180);
});
});
suite('Fullscreen, FullscreenLayout', function() {
var fakeAppConfig1FullScreen = {
url: 'app://www.fake/index.html',
manifest: {
'fullscreen': true
},
manifestURL: 'app://wwww.fake/ManifestURL',
origin: 'app://www.fake'
};
var fakeAppConfig1FullScreenLayout = {
url: 'app://www.fake/index.html',
manifest: {
'fullscreen_layout': true
},
manifestURL: 'app://wwww.fake/ManifestURL',
origin: 'app://www.fake'
};
var fakeWrapper = {
url: 'app://www.fake.com/index.html',
origin: 'app://www.fake.com'
};
test('isFullScreen', function() {
var app1 = new AppWindow(fakeAppConfig1);
assert.isFalse(app1.isFullScreen());
assert.isFalse(app1.element.classList.contains('fullscreen-app'));
var appW = new AppWindow(fakeWrapper);
assert.isFalse(appW.isFullScreen());
assert.isFalse(appW.element.classList.contains('fullscreen-app'));
var app1f = new AppWindow(fakeAppConfig1FullScreen);
assert.isTrue(app1f.isFullScreen());
assert.isTrue(app1f.element.classList.contains('fullscreen-app'));
var app1fl = new AppWindow(fakeAppConfig1FullScreenLayout);
assert.isTrue(app1fl.isFullScreen());
assert.isTrue(app1fl.element.classList.contains('fullscreen-app'));
});
test('isFullScreenLayout', function() {
var app1 = new AppWindow(fakeAppConfig1);
assert.isFalse(app1.isFullScreenLayout());
var appW = new AppWindow(fakeWrapper);
assert.isFalse(appW.isFullScreenLayout());
assert.isFalse(app1.element.classList.contains('fullscreen-app'));
var app1f = new AppWindow(fakeAppConfig1FullScreenLayout);
assert.isTrue(app1f.isFullScreenLayout());
});
});
suite('Display Modes', function() {
var fakeAppConfigDisplayMinimalUi = {
url: 'http://www.fake/index.html',
manifest: {
'display': 'minimal-ui'
},
manifestURL: 'app://wwww.fake/ManifestURL',
origin: 'app://www.fake'
};
var fakeAppConfigDisplayStandalone = {
url: 'http://www.fake/index.html',
manifest: {
'display': 'standalone'
},
manifestURL: 'app://wwww.fake/ManifestURL',
origin: 'app://www.fake'
};
var fakeAppConfigDisplayFullscreen = {
url: 'http://www.fake/index.html',
manifest: {
'display': 'fullscreen'
},
manifestURL: 'app://wwww.fake/ManifestURL',
origin: 'app://www.fake'
};
test('minimal-ui', function() {
var app1 = new AppWindow(fakeAppConfigDisplayMinimalUi);
assert.isFalse(app1.isFullScreen());
assert.isTrue(app1.config.chrome.scrollable);
assert.isTrue(app1.config.chrome.maximized);
});
test('standalone', function() {
var app1 = new AppWindow(fakeAppConfigDisplayStandalone);
assert.isFalse(app1.isFullScreen());
assert.isFalse(app1.config.chrome.scrollable);
assert.isFalse(app1.config.chrome.maximized);
});
test('fullscreen', function() {
var app1 = new AppWindow(fakeAppConfigDisplayFullscreen);
assert.isTrue(app1.isFullScreen());
assert.isFalse(app1.config.chrome.scrollable);
assert.isFalse(app1.config.chrome.maximized);
});
});
suite('ScreenshotOverlay State Control', function() {
var app1;
var app2;
setup(function() {
app1 = new AppWindow(fakeAppConfig1);
app2 = new AppWindow(fakeAppConfig2);
// Inject mozBrowser API to app iframe
injectFakeMozBrowserAPI(app1.browser.element);
});
test('when cards view is shown and hidden', function() {
var fakeScreenshotBlob = 'blob:d3958f5c-0777-0845-9dcf-2cb28783acaf';
this.sinon.stub(app1, 'requestScreenshotURL', function() {
return fakeScreenshotBlob;
});
app1.element.dispatchEvent(new CustomEvent('_cardviewbeforeshow'));
assert.isTrue(app1.screenshotOverlay.classList.contains('visible'),
'Overlay should be visible after beforeshow is received');
assert.isFalse(app1.element.classList.contains('no-screenshot'),
'Shouldnt have no-screenshot class when there is one');
app1.element.dispatchEvent(new CustomEvent('_cardviewclosed'));
assert.isFalse(app1.screenshotOverlay.classList.contains('visible'),
'Overlay should be hidden after closed is received');
});
test('when cards view is shown and theres no screenshot', function() {
this.sinon.stub(app1, 'requestScreenshotURL', function() {
return null;
});
app1.element.dispatchEvent(new CustomEvent('_cardviewbeforeshow'));
assert.isTrue(app1.element.classList.contains('no-screenshot'),
'has no-screenshot class ' +
'when requestScreenshotURL returns falsey');
app1.element.dispatchEvent(new CustomEvent('_cardviewshown'));
app1.element.dispatchEvent(new CustomEvent('_cardviewclosed'));
assert.isFalse(app1.element.classList.contains('no-screenshot'),
'removes no-screenshot class');
});
test('show overlay when revealed by an edge swipe', function() {
app1.screenshotOverlay.classList.remove('visible');
app1.element.dispatchEvent(new CustomEvent('_sheetdisplayed'));
assert.isTrue(app1.screenshotOverlay.classList.contains('visible'),
'Overlay should be visible after sheetdisplayed');
});
test('hide overlay when a sheet gesture ends', function() {
app1.screenshotOverlay.classList.add('visible');
app1.element.dispatchEvent(new CustomEvent('_sheetsgestureend'));
assert.isFalse(app1.screenshotOverlay.classList.contains('visible'));
assert.isFalse(app1.element.classList.contains('overlay'));
});
test('hide overlay when a sheet gesture ends even if the app is active',
function() {
this.sinon.stub(app1, 'isActive').returns(true);
app1.screenshotOverlay.classList.add('visible');
app1.element.dispatchEvent(new CustomEvent('_sheetsgestureend'));
assert.isFalse(app1.screenshotOverlay.classList.contains('visible'));
assert.isFalse(app1.element.classList.contains('overlay'));
});
test('showScreenshotOverlay', function() {
var stubRequestScreenshotURL =
this.sinon.stub(app1, 'requestScreenshotURL');
app1._showScreenshotOverlay();
assert.isTrue(stubRequestScreenshotURL.called);
assert.isTrue(app1.screenshotOverlay.classList.contains('visible'));
});
test('show the frontest app ScreenshotOverlay', function() {
app1.frontWindow = app2;
this.sinon.stub(app2, 'isActive').returns(true);
var stubRequestScreenshotURL =
this.sinon.stub(app2, 'requestScreenshotURL');
app1._showScreenshotOverlay();
assert.isTrue(stubRequestScreenshotURL.called);
assert.isTrue(app2.screenshotOverlay.classList.contains('visible'));
});
test('should not show the frontest app ScreenshotOverlay if it is ' +
'not active', function() {
app1.frontWindow = app2;
this.sinon.stub(app2, 'isActive').returns(false);
app1._showScreenshotOverlay();
assert.isFalse(app2.screenshotOverlay.classList.contains('visible'));
});
test('should return promise and resolve directly if the frontest app is' +
' active and its screenshot is visible', function(done) {
app1.frontWindow = app2;
this.sinon.stub(app2, 'isActive').returns(true);
app2.screenshotOverlay.classList.add('visible');
app1._showScreenshotOverlay().then(() => {
done();
});
});
test('hideScreenshotOverlay', function() {
app1.screenshotOverlay.classList.add('visible');
app1.element.classList.add('overlay');
app1._hideScreenshotOverlay();
assert.isFalse(app1.screenshotOverlay.classList.contains('visible'));
assert.isTrue(app1.element.classList.contains('overlay'));
this.sinon.clock.tick(); // We wait for the next tick
assert.isFalse(app1.element.classList.contains('overlay'));
});
test('hideScreenshotOverlay and its front window', function() {
app1.frontWindow = app2;
this.sinon.stub(app2, 'isActive').returns(true);
app2.screenshotOverlay.classList.add('visible');
app1.screenshotOverlay.classList.add('visible');
app2.element.classList.add('overlay');
app1.element.classList.add('overlay');
app1._hideScreenshotOverlay();
assert.isFalse(app1.screenshotOverlay.classList.contains('visible'));
assert.isFalse(app2.screenshotOverlay.classList.contains('visible'));
assert.isTrue(app1.element.classList.contains('overlay'));
assert.isTrue(app2.element.classList.contains('overlay'));
this.sinon.clock.tick(); // We wait for the next tick
assert.isFalse(app1.element.classList.contains('overlay'));
assert.isFalse(app2.element.classList.contains('overlay'));
});
test('hideScreenshotOverlay noop when the screenshot is not displayed',
function() {
app1._screenshotOverlayState = 'none';
app1.screenshotOverlay.classList.remove('visible');
app1.element.classList.add('overlay');
app1._hideScreenshotOverlay();
this.sinon.clock.tick(); // We wait for the next tick
assert.isTrue(app1.element.classList.contains('overlay'));
});
test('Request screenshotURL', function() {
assert.isNull(app1.requestScreenshotURL());
var stubCreateObjectURL = this.sinon.stub(URL, 'createObjectURL');
var stubRevokeObjectURL = this.sinon.stub(URL, 'revokeObjectURL');
app1._screenshotBlob = 'fakeBlob';
stubCreateObjectURL.returns('fakeURL');
var url = app1.requestScreenshotURL();
assert.isNotNull(url);
assert.isTrue(stubCreateObjectURL.calledWith('fakeBlob'));
this.sinon.clock.tick(200);
assert.isTrue(stubRevokeObjectURL.calledWith('fakeURL'));
});
test('Show identification overlay when showing screenshot', function() {
app1._showScreenshotOverlay();
assert.isTrue(app1.element.classList.contains('overlay'));
});
});
suite('Repaint', function() {
test('full repaint', function() {
var app1 = new AppWindow(fakeAppConfig1);
injectFakeMozBrowserAPI(app1.browser.element);
var callback = this.sinon.spy();
var stubGetScreenshot = this.sinon.stub(app1, 'getScreenshot');
app1.tryWaitForFullRepaint(callback);
stubGetScreenshot.getCall(0).args[0]();
this.sinon.clock.tick(0);
assert.isTrue(callback.called);
});
});
suite('Transition', function() {
test('Open', function() {
var app1 = new AppWindow(fakeAppConfig1);
var transitionController = new MockAppTransitionController();
app1.transitionController = transitionController;
var stubRequireOpen =
this.sinon.stub(transitionController, 'requireOpen');
app1.open();
assert.isTrue(stubRequireOpen.called);
app1.open('Orz');
assert.isTrue(stubRequireOpen.calledWith('Orz'));
});
test('Close', function() {
var app1 = new AppWindow(fakeAppConfig1);
var transitionController = new MockAppTransitionController();
app1.transitionController = transitionController;
var stubRequireClose =
this.sinon.stub(transitionController, 'requireClose');
app1.close();
assert.isTrue(stubRequireClose.called);
app1.close('XD');
assert.isTrue(stubRequireClose.calledWith('XD'));
});
});
test('publish', function() {
var app1 = new AppWindow(fakeAppConfig1);
var stubbroadcast = this.sinon.stub(app1, 'broadcast');
var stubDispatchEvent = this.sinon.stub(window, 'dispatchEvent');
app1.publish('I-hate-you');
assert.isTrue(stubDispatchEvent.called);
assert.equal(stubDispatchEvent.getCall(0).args[0].type, 'appI-hate-you');
assert.isTrue(stubbroadcast.calledWith('I-hate-you'));
});
test('broadcast', function() {
var app1 = new AppWindow(fakeAppConfig1);
var stubDispatchEvent = this.sinon.stub(app1.element, 'dispatchEvent');
app1.broadcast('I-love-you');
assert.isTrue(stubDispatchEvent.called);
assert.equal(stubDispatchEvent.getCall(0).args[0].type, '_I-love-you');
});
test('Focus should be delivered to front active window', function() {
var app1 = new AppWindow(fakeAppConfig1);
var app2 = new AppWindow(fakeAppConfig2);
var stubFrontFocus = this.sinon.stub(app2, 'focus');
app1.frontWindow = app2;
this.sinon.stub(app1, 'isActive').returns(true);
this.sinon.stub(app2, 'isActive').returns(true);
app1.broadcast('focus');
assert.isTrue(stubFrontFocus.called);
});
test('setFrameBackground', function() {
ScreenLayout.setDefault({
tiny: true
});
var app = new AppWindow(fakeAppConfigWithIcon);
this.sinon.clock.tick(0);
var background = app.element.style.backgroundImage;
var backgroundSize = app.element.style.backgroundSize;
var idBackground = app.identificationIcon.style.backgroundImage;
assert.isTrue(app.splashed);
assert.isDefined(app._splash);
assert.equal(background, 'url("' + app._splash + '")');
assert.equal(backgroundSize, '120px 120px');
assert.equal(idBackground, 'url("' + app._splash + '")');
});
test('get Icon Splash with Multi Icons, dppx=1', function() {
// Overwrite value for testing dppx
var _devicePixelRatio = window.devicePixelRatio;
Object.defineProperty(window, 'devicePixelRatio', {
configurable: true,
value: 1
});
ScreenLayout.setDefault({
tiny: true
});
var app = new AppWindow(fakeAppConfigWithMultiIcon);
this.sinon.clock.tick(0);
assert.isTrue(
app._splash.indexOf(fakeAppConfigWithMultiIcon.
manifest.icons['120']) >= 0);
Object.defineProperty(window, 'devicePixelRatio', {
configurable: true,
value: _devicePixelRatio
});
});
test('get frame for screenshot should fetch top most window', function() {
var popups = openPopups(5);
assert.equal(popups[0].getFrameForScreenshot(),
popups[4].browser.element);
});
test('get Icon Splash with Multi Icons, dppx=2', function() {
// Overwrite value for testing dppx
var _devicePixelRatio = window.devicePixelRatio;
Object.defineProperty(window, 'devicePixelRatio', {
configurable: true,
value: 2
});
ScreenLayout.setDefault({
tiny: true
});
var app = new AppWindow(fakeAppConfigWithMultiIcon);
this.sinon.clock.tick(0);
assert.isTrue(
app._splash.indexOf(fakeAppConfigWithMultiIcon.
manifest.icons['240']) >= 0);
Object.defineProperty(window, 'devicePixelRatio', {
configurable: true,
value: _devicePixelRatio
});
});
test('get Private Browser Splash Icon', function() {
var app = new AppWindow(fakePrivateConfig);
assert.isTrue(app._splash.indexOf('pb_icon.png') !== -1);
});
var fakeMozBrowserIframe = {
focus: function() {},
blur: function() {},
setVisible: function() {},
setVisibleForScreenReader: function() {},
goBack: function() {},
goForward: function() {},
reload: function() {},
stop: function() {},
getCanGoForward: function() {
return fakeDOMRequest;
},
getCanGoBack: function() {
return fakeDOMRequest;
},
getScreenshot: function() {
return fakeDOMRequest;
},
addNextPaintListener: function() {},
removeNextPaintListener: function() {},
setActive: function() {},
setNFCFocus: function() {}
};
function injectFakeMozBrowserAPI(iframe) {
for (var method in fakeMozBrowserIframe) {
iframe[method] = fakeMozBrowserIframe[method];
}
}
test('we do not need to wait if there is screenshot layer covered',
function() {
var app1 = new AppWindow(fakeAppConfig1);
var callback = this.sinon.spy();
var stubWaitForNextPaint = this.sinon.stub(app1, 'waitForNextPaint');
var stubEnsureFullRepaint =
this.sinon.stub(app1, 'tryWaitForFullRepaint');
app1.loaded = true;
app1.screenshotOverlay.classList.add('visible');
app1.ready(callback);
assert.isFalse(stubEnsureFullRepaint.called);
assert.isFalse(stubWaitForNextPaint.called);
this.sinon.clock.tick(0);
assert.isTrue(callback.calledOnce);
});
suite('ready', function() {
var app1;
var stubWaitForNextPaint;
var stubEnsureFullRepaint;
var showScreenshotOverlay;
setup(function() {
app1 = new AppWindow(fakeAppConfig1);
stubWaitForNextPaint = this.sinon.stub(app1, 'waitForNextPaint');
stubEnsureFullRepaint = this.sinon.stub(app1, 'tryWaitForFullRepaint');
showScreenshotOverlay = this.sinon.stub(app1, '_showScreenshotOverlay');
app1.loaded = true;
});
teardown(function() {
stubWaitForNextPaint.restore();
stubEnsureFullRepaint.restore();
showScreenshotOverlay.restore();
});
test('Full repaint', function() {
var callback = this.sinon.spy();
app1.ready(callback);
assert.isTrue(stubEnsureFullRepaint.called);
assert.isTrue(stubWaitForNextPaint.called);
stubEnsureFullRepaint.getCall(0).args[0]();
stubWaitForNextPaint.getCall(0).args[0]();
this.sinon.clock.tick(0);
assert.isTrue(callback.calledOnce);
});
});
suite('Browser Mixin', function() {
var fakeDOMRequest;
setup(function() {
fakeDOMRequest = {
onsuccess: function() {},
onerror: function() {}
};
});
test('MozBrowser API: setActive', function() {
var app1 = new AppWindow(fakeAppConfig1);
injectFakeMozBrowserAPI(app1.browser.element);
this.sinon.stub(app1.browser.element, 'setActive');
MockService.mockQueryWith('getTopMostUI', { name: 'Rocketbar' });
app1._setActive(false);
assert.isTrue(app1.browser.element.setActive.calledWith(false));
MockService.mockQueryWith('getTopMostUI', { name: 'AppWindowManager' });
app1._setActive(true);
assert.isTrue(app1.browser.element.setActive.calledWith(true));
});
test('MozBrowser API: simple methods', function() {
var app1 = new AppWindow(fakeAppConfig1);
injectFakeMozBrowserAPI(app1.browser.element);
var stubBack = this.sinon.stub(app1.browser.element, 'goBack');
var stubForward = this.sinon.stub(app1.browser.element, 'goForward');
var stubReload = this.sinon.stub(app1.browser.element, 'reload');
var stubStop = this.sinon.stub(app1.browser.element, 'stop');
app1.back();
assert.isTrue(stubBack.called);
app1.forward();
assert.isTrue(stubForward.called);
app1.reload();
assert.isTrue(stubReload.called);
app1.stop();
assert.isTrue(stubStop.called);
});
suite('focus/blur', function() {
var app1;
var stubBlur;
var stubFocus;
setup(function() {
app1 = new AppWindow(fakeAppConfig1);
stubBlur = this.sinon.stub(app1.browser.element, 'blur');
stubFocus = this.sinon.stub(app1.browser.element, 'focus');
});
teardown(function() {
stubBlur.restore();
stubFocus.restore();
});
test('blur should not be called without focus', function() {
var stub = this.sinon.stub(app1, 'getActiveElement', function() {
return document.body;
});
app1.blur();
assert.isFalse(stubBlur.called);
stub.restore();
});
test('blur should be called with focus', function() {
var stub = this.sinon.stub(app1, 'getActiveElement', function() {
return app1.browser.element;
});
app1.blur();
assert.isTrue(stubBlur.called);
stub.restore();
});
test('focus should be called without focus', function() {
var stub = this.sinon.stub(app1, 'getActiveElement', function() {
return document.body;
});
app1.focus();
assert.isTrue(stubFocus.called);
stub.restore();
});
test('focus should not be called with focus', function() {
var stub = this.sinon.stub(app1, 'getActiveElement', function() {
return app1.browser.element;
});
app1.focus();
assert.isFalse(stubFocus.called);
stub.restore();
});
});
suite('focus and context menu', function() {
var app1;
var stubFocus;
setup(function() {
app1 = new AppWindow(fakeAppConfig1);
injectFakeMozBrowserAPI(app1.browser.element);
stubFocus = this.sinon.stub(app1.browser.element, 'focus');
});
teardown(function() {
stubFocus.restore();
app1.contextmenu = undefined;
});
test('Context menu is not shown', function() {
app1.focus();
assert.isTrue(stubFocus.called);
});
test('Context menu is shown', function() {
app1.contextmenu = {
isShown: function() {
return true;
},
focus: function() {
}
};
var stubContextMenuFocus = this.sinon.stub(app1.contextmenu, 'focus');
app1.focus();
assert.isFalse(stubFocus.called);
assert.isTrue(stubContextMenuFocus.called);
stubContextMenuFocus.restore();
});
});
test('MozBrowser API: getScreenshot', function() {
MockService.mockQueryWith('LayoutManager.width', 320);
MockService.mockQueryWith('LayoutManager.height', 480);
var app1 = new AppWindow(fakeAppConfig1);
injectFakeMozBrowserAPI(app1.browser.element);
var stubScreenshot = this.sinon.stub(app1.browser.element,
'getScreenshot').returns(fakeDOMRequest);
stubScreenshot.returns(fakeDOMRequest);
var callback1 = this.sinon.spy();
app1.getScreenshot(callback1);
sinon.assert.calledWith(stubScreenshot,
sinon.match.number, sinon.match.number, 'image/jpeg');
fakeDOMRequest.onsuccess({ target: { result: 'fakeBlob' } });
assert.equal(app1._screenshotBlob, 'fakeBlob');
assert.isTrue(callback1.calledWith('fakeBlob'));
fakeDOMRequest.onerror();
assert.isTrue(callback1.called);
});
test('MozBrowser API: getScreenshot (with frontWindow active)', function() {
var app1 = new AppWindow(fakeAppConfig1);
var app2 = new AppWindow(fakeAppConfig2);
injectFakeMozBrowserAPI(app1.browser.element);
injectFakeMozBrowserAPI(app2.browser.element);
var stubScreenshot = this.sinon.stub(app1.browser.element,
'getScreenshot').returns(fakeDOMRequest);
var stubScreenshot2 = this.sinon.stub(app2.browser.element,
'getScreenshot').returns(fakeDOMRequest);
var callback1 = this.sinon.spy();
app1.frontWindow = app2;
app1.getScreenshot(callback1);
assert.isFalse(stubScreenshot.called);
assert.isTrue(stubScreenshot2.called);
fakeDOMRequest.onsuccess({ target: { result: 'fakeBlob' } });
assert.equal(app2._screenshotBlob, 'fakeBlob');
assert.isTrue(callback1.calledWith('fakeBlob'));
fakeDOMRequest.onerror();
assert.isTrue(callback1.called);
});
test('MozBrowser API: getScreenshot (ignore frontWindow)', function() {
var app1 = new AppWindow(fakeAppConfig1);
var app2 = new AppWindow(fakeAppConfig2);
injectFakeMozBrowserAPI(app1.browser.element);
injectFakeMozBrowserAPI(app2.browser.element);
var stubScreenshot = this.sinon.stub(app1.browser.element,
'getScreenshot').returns(fakeDOMRequest);
var stubScreenshot2 = this.sinon.stub(app2.browser.element,
'getScreenshot').returns(fakeDOMRequest);
var callback1 = this.sinon.spy();
app1.frontWindow = app2;
app1.getScreenshot(callback1, null, null, null, true);
assert.isTrue(stubScreenshot.called);
assert.isFalse(stubScreenshot2.called);
fakeDOMRequest.onsuccess({ target: { result: 'fakeBlob' } });
assert.equal(app1._screenshotBlob, 'fakeBlob');
assert.isTrue(callback1.calledWith('fakeBlob'));
fakeDOMRequest.onerror();
assert.isTrue(callback1.called);
});
test('MozBrowser API: getScreenshot for homescreen', function() {
MockService.mockQueryWith('LayoutManager.width', 320);
MockService.mockQueryWith('LayoutManager.height', 480);
var home = new AppWindow(fakeAppConfig1);
home.isHomescreen = true;
injectFakeMozBrowserAPI(home.browser.element);
var stubScreenshot = this.sinon.stub(home.browser.element,
'getScreenshot').returns(fakeDOMRequest);
home.getScreenshot();
// should take screenshot blob with png fromat
sinon.assert.calledWith(stubScreenshot,
sinon.match.number, sinon.match.number, 'image/png');
});
test('MozBrowser API: getGoForward', function() {
var app1 = new AppWindow(fakeAppConfig1);
injectFakeMozBrowserAPI(app1.browser.element);
var stubCanGoForward = this.sinon.stub(app1.browser.element,
'getCanGoForward').returns(fakeDOMRequest);
stubCanGoForward.returns(fakeDOMRequest);
var callback = this.sinon.spy();
app1.canGoForward(callback);
assert.isTrue(stubCanGoForward.called);
fakeDOMRequest.onsuccess({ target: { result: true } });
assert.isTrue(callback.calledWith(true));
fakeDOMRequest.onsuccess({ target: { result: false } });
assert.isTrue(callback.calledWith(false));
fakeDOMRequest.onerror();
assert.isTrue(callback.called);
});
test('MozBrowser API: getGoBack', function() {
var app1 = new AppWindow(fakeAppConfig1);
injectFakeMozBrowserAPI(app1.browser.element);
var stubCanGoBack = this.sinon.stub(app1.browser.element,
'getCanGoBack').returns(fakeDOMRequest);
var callback = this.sinon.spy();
app1.canGoBack(callback);
assert.isTrue(stubCanGoBack.called);
fakeDOMRequest.onsuccess({ target: { result: true } });
assert.isTrue(callback.calledWith(true));
fakeDOMRequest.onsuccess({ target: { result: false } });
assert.isTrue(callback.calledWith(false));
fakeDOMRequest.onerror();
assert.isTrue(callback.called);
});
test('MozBrowser API: NextPaint (pass callback)', function() {
var app1 = new AppWindow(fakeAppConfig1);
injectFakeMozBrowserAPI(app1.browser.element);
var callback = this.sinon.spy();
var stubAddNextPaintListener = this.sinon.stub(app1.browser.element,
'addNextPaintListener');
app1.waitForNextPaint(callback);
assert.isTrue(stubAddNextPaintListener.called);
stubAddNextPaintListener.getCall(0).args[0]();
assert.isTrue(callback.called);
var callback2 = this.sinon.spy();
app1.waitForNextPaint(callback2);
this.sinon.clock.tick(app1.NEXTPAINT_TIMEOUT);
assert.isTrue(callback2.called);
});
test('MozBrowser API: NextPaint (Promise)', function(done) {
var app1 = new AppWindow(fakeAppConfig1);
injectFakeMozBrowserAPI(app1.browser.element);
var stubAddNextPaintListener = this.sinon.stub(app1.browser.element,
'addNextPaintListener');
var p = app1.waitForNextPaint();
assert.isTrue(stubAddNextPaintListener.called);
stubAddNextPaintListener.getCall(0).args[0]();
p.then(function() {
var p2 = app1.waitForNextPaint();
this.sinon.clock.tick(app1.NEXTPAINT_TIMEOUT);
return p2;
}.bind(this)).then(done, done);
});
});
suite('setVisible', function() {
test('setVisible: true should revive browser', function() {
var app1 = new AppWindow(fakeAppConfig1);
injectFakeMozBrowserAPI(app1.browser.element);
app1.setVisible(false);
this.sinon.stub(app1, 'reviveBrowser');
app1.setVisible(true);
assert.isTrue(app1.reviveBrowser.called);
});
test('setVisible: true should call handleScrollAreaChanged', function() {
var app1 = new AppWindow(fakeChromeConfigWithNavigationBar);
app1.element.dispatchEvent(new CustomEvent('_opened'));
app1.setVisible(false);
var handleScrollAreaChangedCalled = false;
app1.appChrome.handleScrollAreaChanged = () => {
handleScrollAreaChangedCalled = true;
};
app1.setVisible(true);
assert.isTrue(handleScrollAreaChangedCalled);
});
test('setVisible: true', function() {
var app1 = new AppWindow(fakeAppConfig1);
injectFakeMozBrowserAPI(app1.browser.element);
app1.setVisible(false);
var spyShowFrame = this.sinon.spy(app1,
'_showFrame');
var mozBrowserSetVisible = this.sinon.stub(app1.browser.element,
'setVisible');
app1.setVisible(true);
assert.isFalse(app1.screenshotOverlay.classList.contains('visible'));
assert.isTrue(spyShowFrame.called);
assert.isFalse(app1.element.classList.contains('inactive'));
assert.equal(app1.browser.element.getAttribute('aria-hidden'), 'false');
assert.isTrue(mozBrowserSetVisible.calledOnce);
assert.isTrue(mozBrowserSetVisible.calledWith(true));
});
test('setVisible: true, multiple times', function() {
var app1 = new AppWindow(fakeAppConfig1);
injectFakeMozBrowserAPI(app1.browser.element);
app1.setVisible(false);
var stub_showFrame = this.sinon.stub(app1,
'_showFrame');
app1.setVisible(true);
this.sinon.clock.tick();
app1.setVisible(true);
assert.isFalse(app1.screenshotOverlay.classList.contains('visible'));
assert.isTrue(stub_showFrame.calledOnce);
});
test('setVisible: false', function() {
var app1 = new AppWindow(fakeAppConfig1);
injectFakeMozBrowserAPI(app1.browser.element);
var spyHideFrame = this.sinon.spy(app1,
'_hideFrame');
var mozBrowserSetVisible = this.sinon.stub(app1.browser.element,
'setVisible');
app1.setVisible(false);
assert.isTrue(spyHideFrame.called);
assert.isTrue(app1.element.classList.contains('inactive'));
assert.equal(app1.browser.element.getAttribute('aria-hidden'), 'true');
assert.isTrue(mozBrowserSetVisible.calledOnce);
assert.isTrue(mozBrowserSetVisible.calledWith(false));
});
test('setVisible: false, multiple times', function() {
var app1 = new AppWindow(fakeAppConfig1);
injectFakeMozBrowserAPI(app1.browser.element);
var stub_hideFrame = this.sinon.stub(app1,
'_hideFrame');
app1.setVisible(false);
this.sinon.clock.tick();
app1.setVisible(false);
assert.isTrue(stub_hideFrame.calledOnce);
});
test('setVisible to front window', function() {
var app1 = new AppWindow(fakeAppConfig1);
var app2 = new AppWindow(fakeAppConfig2);
app1.setVisible(false);
var stubApp2SetVisible = this.sinon.stub(app2, 'setVisible');
app1.frontWindow = app2;
this.sinon.stub(app2, 'isActive').returns(true);
app2.rearWindow = app1;
app1.setVisible(true);
assert.isTrue(stubApp2SetVisible.calledWith(true));
app1.setVisible(false);
assert.isTrue(stubApp2SetVisible.calledWith(false));
});
test('doNotPropagate', function() {
var app1 = new AppWindow(fakeAppConfig1);
var app2 = new AppWindow(fakeAppConfig2);
app1.setVisible(false);
this.sinon.stub(app2, 'setVisible');
app1.frontWindow = app2;
this.sinon.stub(app2, 'isActive').returns(true);
app2.rearWindow = app1;
app1.setVisible(true, true);
assert.isFalse(app2.setVisible.called);
});
test('setVisible: homescreen', function() {
var app1 = new AppWindow(fakeAppConfig1);
injectFakeMozBrowserAPI(app1.browser.element);
app1.isHomescreen = true;
app1.setVisible(true);
var stub_hideFrame = this.sinon.stub(app1,
'_hideFrame');
app1.setVisible(false);
assert.isFalse(app1.screenshotOverlay.classList.contains('visible'));
assert.isTrue(stub_hideFrame.calledOnce);
});
test('setVisible: homescreen child', function() {
var app1 = new AppWindow(fakeAppConfig1);
var app2 = new AppWindow(fakeAppConfig2);
app1.frontWindow = app2;
app2.rearWindow = app1;
app1.isHomescreen = true;
app1.setVisible(false);
assert.isFalse(app1.screenshotOverlay.classList.contains('visible'));
assert.isFalse(app2.screenshotOverlay.classList.contains('visible'));
});
test('setVisible: called twice', function() {
var app1 = new AppWindow(fakeAppConfig1);
injectFakeMozBrowserAPI(app1.browser.element);
app1.setVisible(false);
var stubMixinSetVisible = this.sinon.stub(app1, '_setVisible');
app1.setVisible(true);
assert.isTrue(stubMixinSetVisible.calledOnce,
'should call _setVisible once!');
app1.setVisible(true);
assert.isTrue(stubMixinSetVisible.calledOnce,
'calling setVisible again should *not* call _setVisible!');
});
});
suite('setVisibleForScreenReader', function() {
test('setVisibleForScreenReader: false', function() {
var app1 = new AppWindow(fakeAppConfig1);
injectFakeMozBrowserAPI(app1.browser.element);
app1.setVisibleForScreenReader(false);
assert.equal(app1.element.getAttribute('aria-hidden'), 'true');
assert.equal(app1.browser.element.getAttribute('aria-hidden'), 'true');
});
test('setVisibleForScreenReader: true', function() {
var app1 = new AppWindow(fakeAppConfig1);
injectFakeMozBrowserAPI(app1.browser.element);
app1.setVisibleForScreenReader(true);
assert.equal(app1.element.getAttribute('aria-hidden'), 'false');
assert.equal(app1.browser.element.getAttribute('aria-hidden'), 'false');
});
test('preserve screenreader access for frontWindow', function() {
var app1 = new AppWindow(fakeAppConfig1);
injectFakeMozBrowserAPI(app1.browser.element);
var app2 = new AppWindow(fakeAppConfig2);
this.sinon.stub(app2, 'isVisible').returns(true);
app1.frontWindow = app2;
app1.setVisibleForScreenReader(false);
assert.equal(app1.element.getAttribute('aria-hidden'), 'false');
assert.equal(app1.browser.element.getAttribute('aria-hidden'), 'false');
});
});
suite('apply and unapplyStyle', function() {
test('applyStyle', function() {
var app = new AppWindow(fakeAppConfig1);
app.element.style.opacity = '0.5';
app.applyStyle({
MozTransform: 'scale(2)',
fontSize: 'large'
});
assert.equal(app.element.style.MozTransform, 'scale(2)');
assert.equal(app.element.style.fontSize, 'large');
// is non-destructive
assert.equal(app.element.style.opacity, '0.5');
});
test('unapplyStyle', function() {
var app = new AppWindow(fakeAppConfig1);
app.applyStyle({
MozTransform: 'scale(2)',
fontSize: 'large',
scale: '0.5'
});
app.unapplyStyle({ MozTransform: true, fontSize: true });
assert.ok(!app.element.style.MozTransform);
assert.ok(!app.element.style.fontSize);
assert.equal(app.element.style.scale, '0.5');
});
});
suite('enter/leaveTaskManager', function() {
test('class gets added and removed', function() {
var app = new AppWindow(fakeAppConfig1);
var closeStub = this.sinon.stub(app, 'close');
app.transitionController = new MockAppTransitionController();
assert.isFalse(app.element.classList.contains('in-task-manager'));
app.enterTaskManager();
assert.isTrue(app.element.classList.contains('in-task-manager'));
app.leaveTaskManager();
assert.isFalse(app.element.classList.contains('in-task-manager'));
assert.isTrue(closeStub.calledOnce, 'app.close was called');
});
test('leaveTaskManager: element.style cleanup', function() {
var app = new AppWindow(fakeAppConfig1);
app.transitionController = new MockAppTransitionController();
var unapplyStyleStub = sinon.stub(app, 'unapplyStyle');
app.applyStyle({
fontSize: '11',
MozTransform: 'scale(2)'
});
app.applyStyle({
pointerEvents: 'none'
});
app.leaveTaskManager();
// ensure unapplyStyle gets called with aggregated property list
assert.isTrue(unapplyStyleStub.calledOnce);
var unapplyProps = unapplyStyleStub.getCall(0).args[0];
assert.equal(Object.keys(unapplyProps).length, 3);
assert.ok('fontSize' in unapplyProps);
assert.ok('MozTransform' in unapplyProps);
assert.ok('pointerEvents' in unapplyProps);
});
});
suite('transform', function(){
test('transform composes correct string value', function(){
var app = new AppWindow(fakeAppConfig1);
var transformProps = {
scale: 0.5,
translateX: '10px',
rotateY: '10deg'
};
// although order isn't important, it should come out looking like this:
var expectedStr = 'scale(0.5) translateX(10px) rotateY(10deg)';
app.transform(transformProps);
assert.equal(app.element.style.MozTransform, expectedStr);
});
});
suite('Event handlers', function() {
var fakeTransitionController = {
requireOpen: function() {},
requireClose: function() {},
CLOSING_TRANSITION_TIMEOUT: 350
};
suite('Hidewindow event', function() {
var app1;
var attention;
var stubSetVisible;
var stubIsActive;
setup(function() {
app1 = new AppWindow(fakeAppConfig1);
attention = new AppWindow(fakeAppConfig2);
stubSetVisible = this.sinon.stub(app1, 'setVisible');
stubIsActive = this.sinon.stub(app1, 'isActive');
stubIsActive.returns(true);
});
teardown(function() {
stubSetVisible.restore();
stubIsActive.restore();
});
test('Set window as invisible', function() {
app1.handleEvent({
type: '_hidewindow',
detail: attention
});
assert.isTrue(stubSetVisible.calledWith(false));
});
test('Do not set the visibility ' +
'when attention window show up on its opener', function() {
attention.parentWindow = app1;
app1.handleEvent({
type: '_hidewindow',
detail: attention
});
assert.isFalse(stubSetVisible.called);
});
test('Do not set the visibility ' +
'when the window is not active', function() {
stubIsActive.returns(false);
app1.handleEvent({
type: '_hidewindow',
detail: attention
});
assert.isFalse(stubSetVisible.called);
});
});
test('ActivityDone event', function() {
var app1 = new AppWindow(fakeAppConfig1);
var app2 = new AppWindow(fakeAppConfig2);
var spyOpen = this.sinon.spy(app1, 'open');
var spyClose = this.sinon.spy(app2, 'close');
var stubIsActive = this.sinon.stub(app2, 'isActive');
app1.setCalleeWindow(app2);
stubIsActive.returns(true);
assert.deepEqual(app1.calleeWindow, app2);
assert.deepEqual(app2.callerWindow, app1);
app2.handleEvent({
type: 'mozbrowseractivitydone'
});
assert.isNull(app1.calleeWindow);
assert.isNull(app2.callerWindow);
assert.isTrue(spyOpen.calledWith('in-from-left'));
assert.isTrue(spyClose.calledWith('out-to-right'));
});
test('No transition when the callee is caller', function() {
var app1 = new AppWindow(fakeAppConfig1);
var spyOpen = this.sinon.spy(app1, 'open');
var spyClose = this.sinon.spy(app1, 'close');
var stubIsActive = this.sinon.stub(app1, 'isActive');
app1.setCalleeWindow(app1);
stubIsActive.returns(true);
assert.deepEqual(app1.calleeWindow, app1);
assert.deepEqual(app1.callerWindow, app1);
app1.handleEvent({
type: 'mozbrowseractivitydone'
});
assert.isNull(app1.calleeWindow);
assert.isNull(app1.callerWindow);
assert.isFalse(spyOpen.calledWith('in-from-left'));
assert.isFalse(spyClose.calledWith('out-to-right'));
});
test('We should open the base window if we are not', function() {
var app1 = new AppWindow(fakeAppConfig1);
var app1Base = new AppWindow(fakeAppConfig2);
var app2 = new AppWindow(fakeAppConfig2);
var spyOpen = this.sinon.spy(app1Base, 'open');
var spyClose = this.sinon.spy(app2, 'close');
app1.CLASS_NAME = 'ActivityWindow';
var stubIsActive = this.sinon.stub(app2, 'isActive');
app1Base.calleeWindow = app2;
app2.callerWindow = app1Base;
stubIsActive.returns(true);
app2.handleEvent({
type: 'mozbrowseractivitydone'
});
assert.isTrue(spyOpen.calledWith('in-from-left'));
assert.isTrue(spyClose.calledWith('out-to-right'));
});
test('Destroy should clear rearWindow.', function() {
var popups = openPopups(2);
popups[1].destroy();
assert.isNull(popups[0].frontWindow);
assert.isNull(popups[1].rearWindow);
});
test('Destroy should clear previousWindow.', function() {
var sheets = openSheets(2);
sheets[1].destroy();
assert.isNull(sheets[0].nextWindow);
assert.isNull(sheets[1].previousWindow);
});
test('App crashed and suspending is disabled', function() {
var app1 = new AppWindow(fakeAppConfig1);
var stubKill = this.sinon.stub(app1, 'kill');
var stubPublish = this.sinon.stub(app1, 'publish');
MockService.mockQueryWith('suspendingAppWindow', false);
app1.handleEvent({
type: 'mozbrowsererror',
detail: {
type: 'fatal'
}
});
assert.isTrue(app1.isCrashed);
assert.isTrue(stubKill.called);
assert.isTrue(stubPublish.calledWith('crashed'));
});
test('App crashed and suspending is enabled' +
'when app is inactive',
function() {
var apps = openPopups(2);
var app1 = apps[0];
var popup1 = apps[1];
var stubKill = this.sinon.stub(popup1, 'kill');
var stubDestroyBrowser = this.sinon.stub(app1, 'destroyBrowser');
var stubIsActive = this.sinon.stub(app1, 'isActive');
stubIsActive.returns(false);
MockService.mockQueryWith('suspendingAppWindow', true);
AppWindow.SUSPENDING_ENABLED = true;
app1.handleEvent({
type: 'mozbrowsererror',
detail: {
type: 'fatal'
}
});
assert.isFalse(app1.isCrashed);
assert.isTrue(stubDestroyBrowser.called);
assert.isTrue(stubKill.called);
AppWindow.SUSPENDING_ENABLED = false;
});
test('App crashed and suspending is enabled ' +
'when the app is active',
function() {
var app1 = new AppWindow(fakeAppConfig1);
var stubKill = this.sinon.stub(app1, 'kill');
var stubIsActive = this.sinon.stub(app1, 'isActive');
stubIsActive.returns(true);
AppWindow.SUSPENDING_ENABLED = true;
app1.handleEvent({
type: 'mozbrowsererror',
detail: {
type: 'fatal'
}
});
assert.isTrue(stubKill.called);
assert.isTrue(app1.isCrashed);
AppWindow.SUSPENDING_ENABLED = false;
});
test('Close event', function() {
var app1 = new AppWindow(fakeAppConfig1);
var stubKill = this.sinon.stub(app1, 'kill');
app1.handleEvent({
type: 'mozbrowserclose'
});
assert.isTrue(stubKill.called);
});
test('Closed while homescreen at bottom', function() {
var app1 = new AppWindow(fakeAppConfig1);
app1.isHomescreen = true;
app1.loaded = true;
var app2 = new AppWindow(fakeAppConfig2);
injectFakeMozBrowserAPI(app1.browser.element);
injectFakeMozBrowserAPI(app2.browser.element);
var stubScreenshot = this.sinon.stub(app1.browser.element,
'getScreenshot');
var stubScreenshot2 = this.sinon.stub(app2.browser.element,
'getScreenshot');
app1.handleEvent({
type: '_closed'
});
assert.isFalse(stubScreenshot.called,
'should never take screenshot on _closed when homescreen');
assert.isFalse(stubScreenshot2.called,
'should never take screenshot on _closed when homescreen');
});
test('Closed while system is busy and homescreen at bottom', function() {
var app1 = new AppWindow(fakeAppConfig1);
app1.isHomescreen = true;
var app2 = new AppWindow(fakeAppConfig2);
MockService.mockQueryWith('isBusyLoading', true);
injectFakeMozBrowserAPI(app1.browser.element);
injectFakeMozBrowserAPI(app2.browser.element);
var stubScreenshot = this.sinon.stub(app1.browser.element,
'getScreenshot');
var stubScreenshot2 = this.sinon.stub(app2.browser.element,
'getScreenshot');
app2.rearWindow = app1;
app2.handleEvent({
type: '_closed'
});
assert.isFalse(stubScreenshot.called,
'should never take screenshot on _closed when homescreen');
assert.isFalse(stubScreenshot2.called,
'should never take screenshot on _closed when homescreen');
});
test('Kill a child window.', function() {
var app1 = new AppWindow(fakeAppConfig1);
injectFakeMozBrowserAPI(app1.browser.element);
var app1parent = new AppWindow(fakeAppConfig2);
injectFakeMozBrowserAPI(app1parent.browser.element);
var app1child = new AppWindow(fakeAppConfig3);
injectFakeMozBrowserAPI(app1child.browser.element);
app1.nextWindow = app1child;
app1child.previousWindow = app1;
app1.previousWindow = app1parent;
app1parent.nextWindow = app1;
var stubIsActive = this.sinon.stub(app1, 'isActive');
var stubKillChild = this.sinon.stub(app1child, 'kill');
var stubOpenParent = this.sinon.stub(app1parent, 'open');
var stubCloseSelf = this.sinon.stub(app1, 'close');
stubIsActive.returns(true);
app1.transitionController = fakeTransitionController;
app1.kill();
assert.isTrue(stubOpenParent.calledWith('in-from-left'));
assert.isTrue(stubCloseSelf.calledWith('out-to-right'));
assert.isTrue(stubKillChild.called);
assert.isNull(app1.nextWindow);
var stubDestroy = this.sinon.stub(app1, 'destroy');
/** global */
app1.element.dispatchEvent(new CustomEvent('_closed'));
assert.isTrue(stubDestroy.called);
});
test('kill guards against missed transitions', function() {
var app = new AppWindow(fakeAppConfig1);
// Ensure that the closed event does not trigger the destroy method.
this.sinon.stub(app.element, 'addEventListener');
this.sinon.stub(app, 'isActive').returns(true);
this.sinon.stub(app, 'close');
app.transitionController = fakeTransitionController;
app.kill();
assert.ok(app.close.notCalled);
this.sinon.clock.tick(
fakeTransitionController.CLOSING_TRANSITION_TIMEOUT);
assert.ok(app.close.calledOnce);
assert.ok(app.close.calledWith('immediate'));
});
test('Destroy active window directly when the bottom app is not Active',
function() {
var app = new AppWindow(fakeAppConfig1);
// Ensure that the closed event does not trigger the destroy method.
this.sinon.stub(app, 'getBottomMostWindow').returns({
isActive: function() {
return false;
}
});
var destroyStub = this.sinon.stub(app, 'destroy');
app.kill();
assert.ok(destroyStub.calledOnce);
});
test('Load event', function() {
var app1 = new AppWindow(fakeAppConfig1);
app1.handleEvent({
type: 'mozbrowserloadstart'
});
assert.isTrue(app1.loading);
assert.isTrue(!app1.loaded);
app1.handleEvent({
type: 'mozbrowserloadend',
detail: {
backgroundColor: 'transparent'
}
});
assert.isTrue(app1.loaded);
assert.isFalse(app1.loading);
});
test('mozbrowser events before _opened', function() {
var spy = this.sinon.spy(window, 'AppChrome');
var app1 = new AppWindow(fakeChromeConfigWithNavigationBar);
var events = [{ type: 'mozbrowserloadstart' },
{ type: 'mozbrowsererror',
detail: { type: '' } },
{ type: 'mozbrowsermetachange',
detail: {} },
{ type: 'mozbrowsermetachange',
detail: {different: 'detail'} }];
var expected = [{ type: 'mozbrowserloadstart' },
{ type: '_loading' },
{ type: 'mozbrowsererror' },
{ type: 'mozbrowsermetachange',
detail: {} },
{ type: 'mozbrowsermetachange',
detail: {different: 'detail'} }];
events.forEach(app1.handleEvent, app1);
assert.isFalse(spy.calledWithNew());
var chromeEventSpy = this.sinon.stub(AppChrome.prototype, 'handleEvent');
app1.element.dispatchEvent(new CustomEvent('_opened'));
expected.forEach((event) => {
sinon.assert.calledWith(chromeEventSpy, event);
});
});
test('metachange event', function() {
var app1 = new AppWindow(fakeAppConfig1);
this.sinon.stub(app1, 'publish');
var evt = {
type: 'mozbrowsermetachange',
detail: {
name: 'name',
content: 'content'
}
};
app1.handleEvent(evt);
assert.equal(app1.meta[evt.detail.name], evt.detail.content);
assert(app1.publish.calledWith('metachange'));
});
test('Locationchange event', function() {
var app1 = new AppWindow(fakeAppConfig1);
var url = app1.config.url;
app1.handleEvent({
type: 'mozbrowserlocationchange',
detail: 'http://fakeURL.changed'
});
assert.equal(app1.config.url, 'http://fakeURL.changed');
app1.config.url = url;
});
test('Locationchange event resets favicons', function() {
var app1 = new AppWindow(fakeAppConfig1);
var url = app1.config.url;
app1.handleEvent({
type: 'mozbrowserlocationchange',
detail: 'http://fakeURL.changed'
});
app1.handleEvent({
type: 'mozbrowsericonchange',
detail: {
href: 'http://fakeURL.favicon',
sizes: 60
}
});
assert.equal(Object.keys(app1.favicons).length, 1);
app1.handleEvent({
type: 'mozbrowserlocationchange',
detail: 'http://fakeURL.changed2'
});
assert.equal(Object.keys(app1.favicons).length, 0);
app1.config.url = url;
});
test('Locationchange event to same URL', function() {
var app1 = new AppWindow(fakeAppConfig1);
var url = app1.config.url;
app1.handleEvent({
type: 'mozbrowserlocationchange',
detail: 'http://fakeURL.changed'
});
app1.handleEvent({
type: 'mozbrowsericonchange',
detail: {
href: 'http://fakeURL.favicon',
sizes: 60
}
});
var favicons = app1.favicons;
assert.equal(Object.keys(favicons).length, 1);
this.sinon.stub(app1, 'publish');
app1.handleEvent({
type: 'mozbrowserlocationchange',
detail: 'http://fakeURL.changed'
});
assert.deepEqual(app1.favicons, favicons);
// we *do* expect publish to be called,
// not doing so seems to break marionette tests
assert.isTrue(app1.publish.calledOnce);
app1.config.url = url;
});
test('Locationchange event resets name and title', function() {
var app1 = new AppWindow(fakeWrapperConfig);
app1.handleEvent({
type: 'mozbrowserlocationchange',
detail: 'http://example.com/page.html'
});
assert.equal(app1.name, 'example.com');
assert.equal(app1.title, 'http://example.com/page.html');
});
test('VisibilityChange event', function() {
var app1 = new AppWindow(fakeAppConfig1);
var stubPublish = this.sinon.stub(app1, 'publish');
app1.handleEvent({
type: 'mozbrowservisibilitychange',
detail: {
visible: false
}
});
assert.isTrue(stubPublish.calledWith('background'));
app1.handleEvent({
type: 'mozbrowservisibilitychange',
detail: {
visible: true
}
});
assert.isTrue(stubPublish.calledWith('foreground'));
});
test('Localized event', function() {
var app1 = new AppWindow(fakeAppConfig1);
var spyManifestHelper = this.sinon.stub(window, 'ManifestHelper');
spyManifestHelper.returns({
name: 'Mon Application',
displayName: 'Bref',
short_name: 'Bref'
});
var stubPublish = this.sinon.stub(app1, 'publish');
app1.handleEvent({
type: '_localized'
});
assert.isTrue(spyManifestHelper.calledWithNew());
assert.isTrue(spyManifestHelper.calledWithExactly(app1.manifest));
assert.isTrue(stubPublish.calledWithExactly('namechanged'));
assert.equal(app1.identificationTitle.textContent, 'Bref');
assert.equal(app1.name, 'Bref');
});
test('focus event', function() {
var app1 = new AppWindow(fakeAppConfig1);
var stubFocus = this.sinon.stub(app1, 'focus');
app1.handleEvent({
type: '_focus'
});
assert.isTrue(stubFocus.calledOnce);
});
test('blur event', function() {
var app1 = new AppWindow(fakeAppConfig1);
var stubBlur = this.sinon.stub(app1, 'blur');
app1.handleEvent({
type: '_blur'
});
assert.isTrue(stubBlur.calledOnce);
});
test('titlechange event', function() {
var app1 = new AppWindow(fakeWrapperConfig);
var stubPublish = this.sinon.stub(app1, 'publish');
app1.handleEvent({
type: 'mozbrowsertitlechange',
detail: 'newtitile'
});
assert.isTrue(stubPublish.calledWithExactly('titlechange'));
assert.equal(
app1.identificationTitle.textContent, 'newtitile',
'title should be changed since it is not an app'
);
var app2 = new AppWindow(fakeAppConfig1);
app2.handleEvent({
type: 'mozbrowsertitlechange',
detail: 'newtitile'
});
assert.equal(
app2.identificationTitle.textContent, '',
'title should not be changed since it is an app'
);
});
test('titlechange event > private browser', function() {
var defaultPbTitle = 'Private Browser Splash';
var privateApp = new AppWindow(fakePrivateConfig);
privateApp.identificationTitle.textContent = defaultPbTitle;
privateApp.handleEvent({
type: 'mozbrowsertitlechange',
detail: 'new title - does not update overlay'
});
assert.equal(privateApp.identificationTitle.textContent, defaultPbTitle);
});
test('iconchange event', function() {
var app1 = new AppWindow(fakeWrapperConfig);
var stubPublish = this.sinon.stub(app1, 'publish');
app1.handleEvent({
type: 'mozbrowsericonchange',
detail: {
sizes: '32x32',
href: 'favicon.ico'
}
});
assert.isTrue(stubPublish.calledWithExactly('iconchange'));
assert.deepEqual(app1.favicons, {'favicon.ico': {sizes: ['32x32']}});
app1.handleEvent({
type: 'mozbrowsericonchange',
detail: {
sizes: '32x32',
href: 'another.ico'
}
});
assert.isTrue(stubPublish.calledWithExactly('iconchange'));
assert.deepEqual(app1.favicons, {
'favicon.ico': {sizes: ['32x32']},
'another.ico': {sizes: ['32x32']}
});
app1.handleEvent({
type: 'mozbrowsericonchange',
detail: {
sizes: '16x16',
href: 'favicon.ico'
}
});
assert.isTrue(stubPublish.calledWithExactly('iconchange'));
assert.deepEqual(app1.favicons, {
'favicon.ico': {sizes: ['32x32', '16x16']},
'another.ico': {sizes: ['32x32']}
});
});
test('Orientation change event on app', function() {
var app1 = new AppWindow(fakeAppConfig1);
this.sinon.stub(app1, 'isActive').returns(false);
app1.width = 320;
app1.height = 460;
MockService.mockQueryWith('keyboardEnabled', false);
MockService.mockQueryWith('LayoutManager.width', 480);
MockService.mockQueryWith('getHeightFor', 300);
app1.handleEvent({
type: '_orientationchange'
});
assert.equal(app1.element.style.width, '480px');
assert.equal(app1.element.style.height, '300px');
assert.equal(app1.screenshotOverlay.style.visibility, 'hidden');
});
test('Orientation change event on app with match orientation', function() {
var app1 = new AppWindow(fakeAppConfig1);
this.sinon.stub(app1, 'isActive').returns(false);
app1.width = 320;
app1.height = 460;
MockService.mockQueryWith('LayoutManager.width', 320);
MockService.mockQueryWith('getHeightFor', 460);
app1.screenshotOverlay.style.visibility = 'hidden';
app1.handleEvent({
type: '_orientationchange'
});
assert.equal(app1.screenshotOverlay.style.visibility, '');
});
test('Orientation change event on active app', function() {
MockService.mockQueryWith('LayoutManager.width', 320);
MockService.mockQueryWith('getHeightFor', 480);
var app1 = new AppWindow(fakeAppConfig1);
var app2 = new AppWindow(fakeAppConfig2);
// Inject mozBrowser API to app iframe
injectFakeMozBrowserAPI(app1.browser.element);
injectFakeMozBrowserAPI(app2.browser.element);
app1.frontWindow = app2;
this.sinon.stub(app1, 'isActive').returns(true);
this.sinon.stub(app2, 'broadcast');
MockService.mockQueryWith('getHeightFor', 380);
app1.handleEvent({
type: '_orientationchange'
});
assert.isTrue(app2.broadcast.calledWith('orientationchange'));
assert.equal(app1.element.style.width,
MockService.mQueries['LayoutManager.width'] + 'px');
assert.equal(app1.element.style.height, '380px');
});
test('Orientation change event on active but not top most app', function() {
MockService.mockQueryWith('LayoutManager.width', 480);
MockService.mockQueryWith('getHeightFor', 320);
var app1 = new AppWindow(fakeAppConfig1);
// Inject mozBrowser API to app iframe
injectFakeMozBrowserAPI(app1.browser.element);
this.sinon.stub(app1, 'isActive').returns(true);
MockService.mockQueryWith('keyboardHeight', 100);
app1.handleEvent({
type: '_orientationchange',
detail: true
});
assert.equal(app1.element.style.width,
MockService.mQueries['LayoutManager.width'] + 'px');
assert.equal(app1.element.style.height,
MockService.mQueries.getHeightFor + 'px');
});
test('Orientation change event on active homescreen app', function() {
MockService.mockQueryWith('LayoutManager.width', 480);
MockService.mockQueryWith('getHeightFor', 320);
var app1 = new AppWindow(fakeAppConfig1);
var stubLockOrientation = this.sinon.stub(app1, 'lockOrientation');
this.sinon.stub(app1, 'isActive').returns(true);
app1.isHomescreen = true;
app1.width = 320;
app1.height = 460;
MockService.mockQueryWith('LayoutManager.width', 460);
MockService.mockQueryWith('LayoutManager.height', 320);
MockService.mockQueryWith('getTopMostWindow', app1);
app1.handleEvent({
type: '_orientationchange'
});
assert.isTrue(stubLockOrientation.calledOnce,
'when active app is homescreen, we should call lockOrientation to' +
'prevent it is modified by other background app');
assert.equal(app1.element.style.width, '460px');
assert.equal(app1.element.style.height, '320px');
MockService.mockQueryWith('getTopMostWindow', null);
});
test('Orientation change event on fullscreen app', function() {
MockService.mockQueryWith('LayoutManager.width', 480);
MockService.mockQueryWith('getHeightFor', 320);
var app1 = new AppWindow(fakeAppConfig1);
this.sinon.stub(app1, 'isActive').returns(false);
this.sinon.stub(app1, 'isFullScreen').returns(true);
app1.width = 320;
app1.height = 480;
MockService.mockQueryWith('LayoutManager.width', 480);
MockService.mockQueryWith('LayoutManager.height', 320);
app1.handleEvent({
type: '_orientationchange'
});
assert.equal(app1.element.style.width, '480px');
assert.equal(app1.element.style.height, '320px');
assert.equal(app1.screenshotOverlay.style.visibility, 'hidden');
});
test('Swipe in event', function() {
var app1 = new AppWindow(fakeAppConfig1);
var atc1 = {
switchTransitionState: function() {}
};
var publishStub = this.sinon.stub(app1, 'publish');
var switchTransitionState =
this.sinon.stub(atc1, 'switchTransitionState');
var revive = this.sinon.stub(app1, 'reviveBrowser');
app1.transitionController = atc1;
app1.handleEvent({
type: '_swipein'
});
assert.isTrue(switchTransitionState.calledWith('opened'));
assert.isTrue(revive.called);
sinon.assert.calledTwice(publishStub);
sinon.assert.calledWith(publishStub, 'opening');
sinon.assert.calledWith(publishStub, 'opened');
});
test('Swipe in event while app is being crashed', function(){
var app1 = new AppWindow(fakeAppConfig1);
var transitionController = new MockAppTransitionController();
app1.transitionController = transitionController;
var stubClearTransitionClasses =
this.sinon.stub(transitionController, 'clearTransitionClasses');
app1.isCrashed = true;
app1.handleEvent({
type: '_swipein'
});
assert.isTrue(stubClearTransitionClasses.called);
});
test('Swipe out event', function() {
var app1 = new AppWindow(fakeAppConfig1);
injectFakeMozBrowserAPI(app1.browser.element);
var atc1 = {
switchTransitionState: function() {}
};
var publishStub = this.sinon.stub(app1, 'publish');
var switchTransitionState =
this.sinon.stub(atc1, 'switchTransitionState');
app1.transitionController = atc1;
app1.handleEvent({
type: '_swipeout'
});
assert.isTrue(switchTransitionState.calledWith('closed'));
sinon.assert.calledWith(publishStub, 'closing');
sinon.assert.calledWith(publishStub, 'closed');
});
test('Shrinking start event', function() {
var app1 = new AppWindow(fakeAppConfig1);
var stubGetScreenshot = this.sinon.stub(app1,'getScreenshot',
// We want to check if callback provided as getScreenshot argument
// calls all the necessary methods
(callback) => { callback(); }
);
this.sinon.stub(app1, '_showScreenshotOverlay')
.returns(new Promise(function(resolve) {
resolve();
assert.isTrue(stubSetVisible.calledWith(false),
'setVisble in callback');
}));
var stubSetVisible = this.sinon.stub(app1, 'setVisible');
var stubBroadcast = this.sinon.stub(app1, 'broadcast');
app1.handleEvent({
type: '_shrinkingstart'
});
assert.isTrue(stubBroadcast.calledWith('blur'));
assert.isTrue(stubGetScreenshot.calledOnce, 'getScreenshot');
});
test('Shrinking stop event', function() {
var app1 = new AppWindow(fakeAppConfig1);
var stubSetVisible = this.sinon.stub(app1, 'setVisible');
var stubBroadcast = this.sinon.stub(app1, 'broadcast');
app1.handleEvent({
type: '_shrinkingstop'
});
assert.isTrue(stubSetVisible.calledWith(true), 'setVisible');
assert.isTrue(stubBroadcast.calledWith('focus'));
});
test('browsersecuritychange event', function() {
var app1 = new AppWindow(fakeAppConfig1);
// expect indeterminate state before first event
var publishStub = this.sinon.stub(app1, 'publish');
assert.equal(app1.getSSLState(), '',
'SSL state is empty before the first security change');
['broken', 'secure', 'insecure'].forEach(function(state, i) {
app1.handleEvent({
type: 'mozbrowsersecuritychange',
detail: {
'state': state
}
});
assert.equal(app1.getSSLState(), state);
assert.equal(publishStub.getCall(i).args[0], 'securitychange');
assert.equal(publishStub.getCall(i).args[1], state);
});
});
suite('mozbrowsermanifestchange event', function() {
var manifestURL = 'https://examples.com/webapp.json';
var manifestResult = {
'short_name': 'App', 'name': 'My App',
'icons': [{ src: new window.URL('icon.png', manifestURL) }]
};
setup(function() {
this.sinon.stub(window, 'Promise', MockPromise);
});
test('valid web manifest', function() {
var app1 = new AppWindow(fakeAppConfig1);
var fakeManifestResultPromise = new MockPromise();
this.sinon.stub(window.Promise, 'resolve')
.returns(fakeManifestResultPromise);
app1.handleEvent({
type: 'mozbrowsermanifestchange',
detail: {
'href': manifestURL
}
});
fakeManifestResultPromise.mFulfillToValue(manifestResult);
assert.equal(app1.webManifestURL, manifestURL);
assert.ok(app1.webManifest);
assert.equal(app1.webManifest.name, 'My App');
assert.equal(app1.name, 'App');
assert.ok(app1.webManifest.icons[0]);
});
test('invalid web manifest url', function() {
var app1 = new AppWindow(fakeAppConfig1);
this.sinon.stub(MockWebManifestHelper, 'getManifest').throws();
app1.handleEvent({
type: 'mozbrowsermanifestchange',
detail: {
'href': null
}
});
assert.ok(!app1.webManifest,
'webManifest property not populated without webManifestURL');
});
});
});
test('getIconBlob', function() {
var blobPromise = new MockPromise();
this.sinon.stub(window, 'Promise', MockPromise);
this.sinon.stub(IconsHelper, 'getIconBlob', function() {
return blobPromise;
});
var app1 = new AppWindow(fakeAppConfig1);
var dataURI = 'data:image/png;base64,abc+';
this.sinon.stub(app1, 'getIconBlob').returns(blobPromise);
var promisedIcon = app1.getSiteIconUrl();
promisedIcon.then(function(icon) {
assert.ok(icon && icon.url);
assert.equal(icon.url, dataURI);
});
blobPromise.mFulfillToValue({ url: dataURI });
});
suite('getSiteIconUrl', function() {
var app1;
var SIZE = 64;
var origin = 'http://test.com';
setup(function() {
app1 = new AppWindow(fakeAppConfig1);
});
test('getSiteIconUrl', function() {
this.sinon.stub(window, 'Promise', MockPromise);
app1.webManifestURL = 'https://example.com/webapp.json';
app1.webManifest = {};
var blobPromise = new MockPromise();
var dataURI = 'data:image/png;base64,abc+';
this.sinon.stub(app1, 'getIconBlob').returns(blobPromise);
var promisedIcon = app1.getSiteIconUrl();
promisedIcon.then(function(icon) {
assert.ok(icon && icon.url);
assert.equal(icon.url, dataURI);
});
blobPromise.mFulfillToValue({ url: dataURI });
});
test('getSiteIconUrl passes along correct origin', function() {
app1.manifestURL = 'https://example.com/webapp.json';
app1.origin = 'https://app-origin.com/with#hash';
app1.manifest = {
origin: origin,
icons: {
'64': '/test.png'
}
};
this.sinon.stub(app1, 'getIconBlob', function (url, size, place, site) {
assert.equal(site.origin, 'https://app-origin.com');
});
app1.getSiteIconUrl(SIZE);
});
test('getSiteIconUrl uses manifest icons if available', function() {
app1.manifestURL = 'https://example.com/webapp.json';
app1.manifest = {
origin: origin,
icons: {
'64': '/test.png'
}
};
this.sinon.stub(app1, 'getIconBlob', function (url, size, place, site) {
assert.isTrue(size === SIZE);
assert.isTrue(site.manifest === app1.manifest);
});
app1.getSiteIconUrl(SIZE);
});
});
test('Change URL at run time', function() {
var app1 = new AppWindow(fakeAppConfig1);
var stubIsActive = this.sinon.stub(app1, 'isActive');
stubIsActive.returns(true);
app1.modifyURLatBackground('http://changed.url');
assert.isTrue(app1.browser.element.src.indexOf('http://changed.url') < 0);
stubIsActive.returns(false);
app1.modifyURLatBackground('http://changed.url');
assert.isTrue(app1.browser.element.src.indexOf('http://changed.url') >= 0);
});
test('Launch wrapper should take title from app name', function() {
var app1 = new AppWindow(fakeAppWithName);
this.sinon.clock.tick();
assert.equal(app1.name, 'Fake App Name');
assert.equal(app1.identificationTitle.textContent, 'Fake App Name');
});
test('Launch wrapper should take title from hostname if no name provided',
function() {
var app1 = new AppWindow(fakeWrapperConfig);
this.sinon.clock.tick();
assert.equal(app1.name, 'www.fake5');
assert.equal(app1.identificationTitle.textContent, 'www.fake5');
});
test('revive browser', function() {
var app1 = new AppWindow(fakeWrapperConfig);
var stubPublish = this.sinon.stub(app1, 'publish');
var stub_setVisble = this.sinon.stub(app1, '_setVisible');
app1.browser = null;
app1.reviveBrowser();
assert.isNotNull(app1.browser);
assert.isFalse(app1.isCrashed);
assert.isFalse(app1.suspended);
assert.isTrue(stub_setVisble.calledWith(false));
assert.isTrue(stubPublish.calledWith('resumed'));
assert.isTrue(app1.browser.element.classList.contains('hidden'));
});
test('destroy browser', function() {
var app1 = new AppWindow(fakeWrapperConfig);
var stubPublish = this.sinon.stub(app1, 'publish');
app1.destroyBrowser();
assert.isNull(app1.browser);
assert.isNull(app1.iframe);
assert.isTrue(app1.suspended);
assert.isTrue(stubPublish.calledWith('suspended'));
assert.equal(app1._sslState, '');
});
test('set child window', function() {
var app1 = new AppWindow(fakeAppConfig1);
var child = new AppWindow(fakeAppConfig2);
app1.setNextWindow(child);
assert.deepEqual(app1.nextWindow, child);
var childNew = new AppWindow(fakeAppConfig3);
var stubKillOldChild = this.sinon.stub(child, 'kill');
app1.setNextWindow(childNew);
assert.isTrue(stubKillOldChild.called);
assert.deepEqual(app1.nextWindow, childNew);
});
suite('isActive', function() {
var testApp;
setup(function() {
testApp = new AppWindow(fakeAppConfig1);
});
test('dom is removed', function() {
testApp.element = null;
assert.isFalse(testApp.isActive());
});
test('app is in queue to show', function() {
testApp.element.classList.add('will-become-active');
assert.isTrue(testApp.isActive());
});
test('app is in queue to hide', function() {
testApp.transitionController = {
'_transitionState': 'opened'
};
testApp.element.classList.add('will-become-inactive');
assert.isFalse(testApp.isActive());
});
test('app doesnot have transitionController', function() {
testApp.transitionController = null;
assert.isFalse(testApp.isActive());
});
test('app doesnot is in opened state', function() {
testApp.transitionController = {
'_transitionState': 'opened'
};
assert.isTrue(testApp.isActive());
});
test('app doesnot is in opening state', function() {
testApp.transitionController = {
'_transitionState': 'opening'
};
assert.isTrue(testApp.isActive());
});
test('app doesnot is in closing state', function() {
testApp.transitionController = {
'_transitionState': 'closing'
};
assert.isFalse(testApp.isActive());
});
});
suite('shouldResize', function() {
var testApp;
setup(function() {
testApp = new AppWindow(fakeAppConfig1);
});
test('app is in queue to hide', function() {
testApp.element.classList.add('will-become-inactive');
assert.isTrue(testApp.shouldResize());
});
test('app is active', function() {
this.sinon.stub(testApp, 'isActive').returns(true);
assert.isTrue(testApp.shouldResize());
});
test('app is inactive', function() {
this.sinon.stub(testApp, 'isActive').returns(false);
assert.isFalse(testApp.shouldResize());
});
});
test('isSheetTransitioning', function() {
var testApp = new AppWindow(fakeAppConfig1);
testApp.element.classList.add('inside-edges');
assert.isTrue(testApp.isSheetTransitioning());
});
suite('isTransitioning', function() {
var testApp;
setup(function() {
testApp = new AppWindow(fakeAppConfig1);
});
test('app is opening', function() {
testApp.transitionController = null;
testApp.element.classList.add('transition-opening');
assert.isTrue(testApp.isTransitioning());
testApp.transitionController = {
_transitionState: undefined
};
assert.isFalse(testApp.isTransitioning());
testApp.transitionController._transitionState = 'opening';
assert.isTrue(testApp.isTransitioning());
});
test('app is closing', function() {
testApp.transitionController = null;
testApp.element.classList.add('transition-closing');
assert.isTrue(testApp.isTransitioning());
testApp.transitionController = {
_transitionState: undefined
};
assert.isFalse(testApp.isTransitioning());
testApp.transitionController._transitionState = 'closing';
assert.isTrue(testApp.isTransitioning());
});
});
test('isBrowser', function() {
var app1 = new AppWindow(fakeAppConfig1);
var app2 = new AppWindow(fakeAppConfig4);
assert.isFalse(app1.isBrowser());
assert.isTrue(app2.isBrowser());
assert.isTrue(app2.element.classList.contains('browser'));
});
test('isPrivateBrowser', function() {
var app1 = new AppWindow(fakeAppConfig1);
var app2 = new AppWindow(fakePrivateConfig);
assert.isFalse(app1.isPrivateBrowser());
assert.isTrue(app2.isPrivateBrowser());
assert.isTrue(app2.element.classList.contains('private'));
});
test('isCertified', function() {
var app1 = new AppWindow(fakeAppConfig1);
var app2 = new AppWindow(fakeAppConfigCertified);
assert.isFalse(app1.isCertified());
assert.isTrue(app2.isCertified());
});
test('navigate', function() {
var app1 = new AppWindow(fakeAppConfig1);
var app2 = new AppWindow(fakeAppConfig4);
var popup = new AppWindow(fakeAppConfig2);
var url = 'http://changed.url';
this.sinon.stub(app1, 'reConfig');
app1.navigate(url);
assert.isTrue(app1.browser.element.src.indexOf(url) < 0);
assert.isTrue(app1.reConfig.notCalled);
app2.navigate(url);
assert.isTrue(app2.browser.element.src.indexOf(url) !== -1);
app2.frontWindow = popup;
app2.navigate(url);
assert.isNull(app2.frontWindow);
});
test('navigate app -> browser', function() {
var app1 = new AppWindow(fakeSearchAppConfig);
var url = 'http://changed.url';
this.sinon.stub(app1, 'reConfig');
this.sinon.stub(app1, '_unregisterAudioChannels');
this.sinon.stub(app1, '_registerAudioChannels');
app1.navigate(url);
assert.ok(app1.reConfig.calledOnce);
assert.ok(app1._unregisterAudioChannels.calledOnce);
assert.ok(app1._registerAudioChannels.calledOnce);
assert.ok(app1.element.classList.contains('browser'));
});
suite('Sub component', function() {
var app;
setup(function() {
window.AudioChannelController = MockAudioChannelController;
app = new AppWindow(fakeAppConfig1);
var element = app.browser.element;
element.allowedAudioChannels = [
{ name: 'normal' }, { name: 'content' }
];
app.installSubComponents();
element.dispatchEvent(new CustomEvent('mozbrowserloadstart'));
});
teardown(function() {
delete window.AudioChannelController;
});
test('installSubComponents', function() {
assert.equal(app.audioChannels.size, 2);
assert.equal(app.audioChannels.get('normal').name, 'normal');
assert.equal(app.audioChannels.get('content').name, 'content');
});
test('uninstallSubComponents', function() {
var spy = {};
for (let propertyName in AppWindow.SUB_COMPONENTS) {
if (app[propertyName] && app[propertyName].destroy) {
spy[propertyName] = this.sinon.spy(app[propertyName], 'destroy');
}
}
for (let propertyName in AppWindow.SUB_MODULES) {
if (app[propertyName] && app[propertyName].stop) {
spy[propertyName] = this.sinon.spy(app[propertyName], 'stop');
}
}
var normalChannel = app.audioChannels.get('normal');
var contentChannel = app.audioChannels.get('content');
this.sinon.spy(normalChannel, 'destroy');
this.sinon.spy(contentChannel, 'destroy');
app.uninstallSubComponents();
for (let propertyName in AppWindow.SUB_COMPONENTS) {
if (spy[propertyName]) {
assert.isTrue(spy[propertyName].calledOnce);
}
assert.isNull(app[propertyName]);
}
for (let propertyName in AppWindow.SUB_MODULES) {
if (spy[propertyName]) {
assert.isTrue(spy[propertyName].calledOnce);
}
assert.isNull(app[propertyName]);
}
assert.ok(normalChannel.destroy.calledOnce);
assert.ok(contentChannel.destroy.calledOnce);
assert.deepEqual(app.audioChannels, null);
});
});
suite('fadeOut', function() {
var app1;
setup(function() {
app1 = new AppWindow(fakeAppConfig1);
});
test('app is active', function() {
this.sinon.stub(app1, 'isActive', function() {return true;});
app1.fadeOut();
assert.isFalse(app1.element.classList.contains('fadeout'));
});
test('app not active', function() {
this.sinon.stub(app1, 'isActive', function() {return false;});
app1.fadeOut();
assert.isTrue(app1.element.classList.contains('fadeout'));
});
});
test('showDefaultContextMenu', function() {
var app = new AppWindow(fakeAppConfig1);
// Nothing goes wrong if contextmenu is undefined
app.showDefaultContextMenu();
app.contextmenu = MockContextMenu;
var stubCtx = this.sinon.stub(app.contextmenu, 'showDefaultMenu');
app.showDefaultContextMenu();
assert.isTrue(stubCtx.called);
});
test('hideContextMenu', function() {
var app = new AppWindow(fakeAppConfig1);
// Nothing goes wrong if contextmenu is undefined
app.showDefaultContextMenu();
app.contextmenu = MockContextMenu;
var stubCtxShow = this.sinon.stub(app.contextmenu, 'showDefaultMenu');
app.showDefaultContextMenu();
assert.isTrue(stubCtxShow.called);
var stubCtxHide = this.sinon.stub(app.contextmenu, 'hide');
app.hideContextMenu();
assert.isTrue(stubCtxHide.called);
});
function genFakeConfig(id) {
return {
url: 'app://www.fake' + id + '/index.html',
manifest: {},
manifestURL: 'app://wwww.fake' + id + '/ManifestURL',
origin: 'app://www.fake' + id
};
}
function openPopups(count) {
var popups = [];
for (var i = 0; i < count; i++) {
popups.push(new AppWindow(genFakeConfig(i)));
if (i > 0) {
popups[i - 1].frontWindow = popups[i];
popups[i].rearWindow = popups[i - 1];
}
}
return popups;
}
suite('Kill embedded windows', function() {
test('kill popup from root', function() {
var popups = openPopups(3);
var stubKill = this.sinon.stub(popups[2], 'kill');
popups[0].kill();
assert.isTrue(stubKill.called);
});
test('kill chain from a node', function() {
var popups = openPopups(5);
var stubKill = this.sinon.stub(popups[4], 'kill');
popups[2].kill();
assert.isTrue(stubKill.called);
});
});
test('getTopMostWindow()', function() {
var popups = openPopups(5);
assert.deepEqual(popups[0].getTopMostWindow(), popups[4]);
assert.deepEqual(popups[1].getTopMostWindow(), popups[4]);
assert.deepEqual(popups[4].getTopMostWindow(), popups[4]);
});
test('getBottomMostWindow()', function() {
var popups = openPopups(5);
assert.deepEqual(popups[4].getBottomMostWindow(), popups[0]);
assert.deepEqual(popups[3].getBottomMostWindow(), popups[0]);
assert.deepEqual(popups[0].getBottomMostWindow(), popups[0]);
});
function openSheets(count) {
var sheets = [];
for (var i = 0; i < count; i++) {
sheets.push(new AppWindow(genFakeConfig(i)));
if (i > 0) {
sheets[i - 1].nextWindow = sheets[i];
sheets[i].previousWindow = sheets[i - 1];
}
}
return sheets;
}
suite('Test child windows', function() {
test('kill chain from root', function() {
var sheets = openSheets(3);
var stubKill = this.sinon.stub(sheets[2], 'kill');
sheets[0].kill();
assert.isTrue(stubKill.called);
});
test('kill chain from a node', function() {
var sheets = openSheets(5);
var stubKill = this.sinon.stub(sheets[4], 'kill');
sheets[2].kill();
assert.isTrue(stubKill.called);
});
test('get root and leaf window', function() {
var sheets = openSheets(7);
assert.equal(sheets[0].getLeafWindow(), sheets[6]);
assert.equal(sheets[3].getRootWindow(), sheets[0]);
});
test('get active window', function() {
var sheets = openSheets(7);
var stubIsActive = this.sinon.stub(sheets[2], 'isActive');
stubIsActive.returns(true);
assert.equal(sheets[0].getActiveWindow(), sheets[2]);
});
test('get next window', function() {
var sheets = openSheets(7);
var stubIsActive = this.sinon.stub(sheets[2], 'isActive');
stubIsActive.returns(true);
assert.equal(sheets[0].getNext(), sheets[3]);
});
test('get previous window', function() {
var sheets = openSheets(7);
var stubIsActive = this.sinon.stub(sheets[2], 'isActive');
stubIsActive.returns(true);
assert.equal(sheets[0].getPrev(), sheets[1]);
});
});
test('Popup is visible only when bottom and itself are both active',
function() {
var popups = openPopups(3);
this.sinon.stub(popups[0], 'isActive').returns(false);
this.sinon.stub(popups[1], 'isActive').returns(true);
this.sinon.stub(popups[2], 'isActive').returns(true);
assert.isFalse(popups[2].isVisible());
var popups2 = openPopups(4);
this.sinon.stub(popups2[0], 'isActive').returns(true);
this.sinon.stub(popups2[1], 'isActive').returns(true);
this.sinon.stub(popups2[2], 'isActive').returns(true);
this.sinon.stub(popups2[3], 'isActive').returns(false);
assert.isTrue(popups2[2].isVisible());
assert.isFalse(popups2[3].isVisible());
});
test('front window should dispatch events on its element',
function() {
var popups = openPopups(2);
var caught = false;
var caughtOnParent = false;
popups[1].element.addEventListener('appfake', function() {
caught = true;
});
popups[0].element.addEventListener('appfake', function() {
caughtOnParent = true;
});
popups[1].publish('fake');
assert.isTrue(caught);
assert.isFalse(caughtOnParent);
});
suite('Theme Group', function() {
test('Added', function() {
var app1 = new AppWindow(fakeAppConfig1);
app1.handleEvent({
type: 'mozbrowsermetachange',
detail: {
name: 'theme-group',
content: 'theme-media',
type: 'added'
}
});
assert.isTrue(app1.element.classList.contains('theme-media'));
});
test('Sanitazation', function() {
var app1 = new AppWindow(fakeAppConfig1);
app1.handleEvent({
type: 'mozbrowsermetachange',
detail: {
name: 'theme-group',
content: 'hidden',
type: 'added'
}
});
assert.isFalse(app1.element.classList.contains('hidden'));
});
test('Changed', function() {
var app1 = new AppWindow(fakeAppConfig1);
app1.handleEvent({
type: 'mozbrowsermetachange',
detail: {
name: 'theme-group',
content: 'theme-settings',
type: 'changed'
}
});
assert.isTrue(app1.element.classList.contains('theme-settings'));
});
test('Removed', function() {
var app1 = new AppWindow(fakeAppConfig1);
app1.handleEvent({
type: 'mozbrowsermetachange',
detail: {
name: 'theme-group',
content: 'theme-media',
type: 'added'
}
});
app1.handleEvent({
type: 'mozbrowsermetachange',
detail: {
name: 'theme-group',
content: 'theme-media',
type: 'removed'
}
});
assert.isFalse(app1.element.classList.contains('theme-media'));
});
});
suite('inScope', function() {
test('Same domain is in the scope', function() {
var scope = 'http://domain.com';
var appConfig = {
url: 'http://domain.com/test'
};
var app = new AppWindow(appConfig);
this.sinon.stub(app, 'isBrowser').returns(true);
assert.isTrue(app.inScope(scope));
});
test('Scope with paths are allowed', function() {
var scope = 'http://domain.com/test';
var appConfig = {
url: 'http://domain.com/test/page1'
};
var app = new AppWindow(appConfig);
this.sinon.stub(app, 'isBrowser').returns(true);
assert.isTrue(app.inScope(scope));
});
test('Different domain is not in the scope', function() {
var scope = 'http://domain2.com';
var appConfig = {
url: 'http://domain.com/test'
};
var app = new AppWindow(appConfig);
this.sinon.stub(app, 'isBrowser').returns(true);
assert.isFalse(app.inScope(scope));
});
test('Subdomains are not in the scope', function() {
var scope = 'http://test.domain.com';
var appConfig = {
url: 'http://domain.com/test'
};
var app = new AppWindow(appConfig);
this.sinon.stub(app, 'isBrowser').returns(true);
assert.isFalse(app.inScope(scope));
});
test('Different schemes are not in the scope', function() {
var scope = 'https://domain.com';
var appConfig = {
url: 'http://domain.com/test'
};
var app = new AppWindow(appConfig);
this.sinon.stub(app, 'isBrowser').returns(true);
assert.isFalse(app.inScope(scope));
});
test('Returns false on non browser windows', function() {
var scope = 'http://domain.com/test';
var appConfig = {
url: 'http://domain.com/test/page1'
};
var app = new AppWindow(appConfig);
this.sinon.stub(app, 'isBrowser').returns(false);
assert.isFalse(app.inScope(scope));
});
test('Scope changes on locationchange', function() {
var scope = 'http://test.domain.com';
var appConfig = {
url: 'http://domain.com/test'
};
var app = new AppWindow(appConfig);
this.sinon.stub(app, 'isBrowser').returns(true);
assert.isFalse(app.inScope(scope));
app.handleEvent({
type: 'mozbrowserlocationchange',
detail: 'http://test.domain.com/test'
});
assert.isTrue(app.inScope(scope));
});
});
suite('application-name', function() {
test('application-name for browser window', function() {
var browser1 = new AppWindow(fakeWrapperConfig);
var stubPublish = this.sinon.stub(browser1, 'publish');
browser1.handleEvent({
type: 'mozbrowsermetachange',
detail: {
name: 'application-name',
content: 'title1'
}
});
assert.equal(browser1.name, 'title1');
assert.isTrue(stubPublish.calledWith('namechanged'));
stubPublish.restore();
});
test('application-name with empty string does not update', function() {
var browser1 = new AppWindow(fakeWrapperConfig);
var stubPublish = this.sinon.stub(browser1, 'publish');
browser1.handleEvent({
type: 'mozbrowsermetachange',
detail: {
name: ' ',
content: 'title1'
}
});
var hostname = new URL(fakeWrapperConfig.url).hostname;
assert.equal(browser1.name, hostname);
assert.isFalse(stubPublish.calledWith('namechanged'));
});
test('application-name for app window', function() {
var app1 = new AppWindow(fakeAppConfig1);
var stubPublish = this.sinon.stub(app1, 'publish');
app1.handleEvent({
type: 'mozbrowsermetachange',
detail: {
name: 'application-name',
content: 'title1'
}
});
assert.isFalse(app1.name == 'title1');
assert.isFalse(stubPublish.calledOnce);
stubPublish.restore();
});
});
test('Should not be killable if it has an attention window', function() {
var app1 = new AppWindow(fakeAppConfig1);
var attention = new AppWindow(fakeAppConfig1);
app1.attentionWindow = attention;
assert.isFalse(app1.killable());
});
test('Should not be killable if we are homescreen window', function() {
var app1 = new AppWindow(fakeAppConfig1);
app1.isHomescreen = true;
assert.isFalse(app1.killable());
});
test('Has attention permission', function() {
MockPermissionSettings.permissions.attention = 'deny';
var app1 = new AppWindow(fakeAppConfig1);
assert.isFalse(app1.hasPermission('attention'));
MockPermissionSettings.permissions.attention = 'allow';
var app2 = new AppWindow(fakeAppConfig2);
assert.isTrue(app2.hasPermission('attention'));
});
test('Show()', function() {
var app1 = new AppWindow(fakeAppConfig1);
app1.hide();
assert.isTrue(app1.element.classList.contains('hidden'));
app1.show();
assert.isFalse(app1.element.classList.contains('hidden'));
});
test('Sub-component destruction', function() {
var app1 = new AppWindow(fakeAppConfig1);
var stub = this.sinon.stub(app1.transitionController, 'destroy');
app1.destroy();
assert.ok(stub.calledOnce);
});
test('No AppChrome for InputMethod AppWindows', function() {
var appInput = new AppWindow(fakeInputAppConfig);
appInput.element.dispatchEvent(new CustomEvent('_opened'));
assert.equal(appInput.appChrome, undefined);
});
test('Should bypass touch event to statusbar submodule', function() {
var app = new AppWindow(fakeAppConfig1);
app.statusbar = {
handleStatusbarTouch: this.sinon.spy()
};
var fakeTouchEvt = new CustomEvent('touchstart');
app.handleStatusbarTouch(fakeTouchEvt, 24);
assert.isTrue(app.statusbar.handleStatusbarTouch.calledWith(
fakeTouchEvt, 24));
});
});