blob: aaafba6ff9a615294d29dd7ebdb596abde8433c1 [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('extension_navigation_helper_tests', function() {
/** @enum {string} */
const TestNames = {
Basic: 'basic',
Conversions: 'conversions',
PushAndReplaceState: 'push and replace state',
SupportedRoutes: 'supported routes'
};
/**
* @return {!Promise<void>} A promise that resolves after the next popstate
* event.
*/
function getOnPopState() {
return new Promise(function(resolve, reject) {
window.addEventListener('popstate', function listener() {
window.removeEventListener('popstate', listener);
// Resolve asynchronously to allow all other listeners to run.
window.setTimeout(resolve, 0);
});
});
}
const suiteName = 'ExtensionNavigationHelperTest';
suite(suiteName, function() {
let navigationHelper;
setup(function() {
PolymerTest.clearBody();
Polymer.dom.flush();
navigationHelper = new extensions.NavigationHelper();
});
test(assert(TestNames.Basic), function() {
const id = 'a'.repeat(32);
const mock = new MockMethod();
const changePage = function(state) {
mock.recordCall([state]);
};
navigationHelper.addListener(changePage);
expectDeepEquals({page: Page.LIST}, navigationHelper.getCurrentPage());
let currentLength = history.length;
navigationHelper.updateHistory({page: Page.DETAILS, extensionId: id});
expectEquals(++currentLength, history.length);
navigationHelper.updateHistory({page: Page.ERRORS, extensionId: id});
expectEquals(++currentLength, history.length);
mock.addExpectation({page: Page.DETAILS, extensionId: id});
const waitForPop = getOnPopState();
history.back();
return waitForPop
.then(() => {
mock.verifyMock();
mock.addExpectation({page: Page.LIST});
const waitForNextPop = getOnPopState();
history.back();
return waitForNextPop;
})
.then(() => {
mock.verifyMock();
});
});
test(assert(TestNames.Conversions), function() {
const id = 'a'.repeat(32);
const stateUrlPairs = {
extensions: {
url: 'chrome://extensions/',
state: {page: Page.LIST},
},
details: {
url: 'chrome://extensions/?id=' + id,
state: {page: Page.DETAILS, extensionId: id},
},
options: {
url: 'chrome://extensions/?options=' + id,
state: {
page: Page.DETAILS,
extensionId: id,
subpage: Dialog.OPTIONS,
},
},
errors: {
url: 'chrome://extensions/?errors=' + id,
state: {page: Page.ERRORS, extensionId: id},
},
shortcuts: {
url: 'chrome://extensions/shortcuts',
state: {page: Page.SHORTCUTS},
},
};
// Test url -> state.
for (let key in stateUrlPairs) {
let entry = stateUrlPairs[key];
history.pushState({}, '', entry.url);
expectDeepEquals(entry.state, navigationHelper.getCurrentPage(), key);
}
// Test state -> url.
for (let key in stateUrlPairs) {
let entry = stateUrlPairs[key];
navigationHelper.updateHistory(entry.state);
expectEquals(entry.url, location.href, key);
}
});
test(assert(TestNames.PushAndReplaceState), function() {
const id1 = 'a'.repeat(32);
const id2 = 'b'.repeat(32);
history.pushState({}, '', 'chrome://extensions/');
expectDeepEquals({page: Page.LIST}, navigationHelper.getCurrentPage());
let expectedLength = history.length;
// Navigating to a new page pushes new state.
navigationHelper.updateHistory({page: Page.DETAILS, extensionId: id1});
expectEquals(++expectedLength, history.length);
// Navigating to a subpage (like the options page) just opens a dialog,
// and shouldn't push new state.
navigationHelper.updateHistory(
{page: Page.DETAILS, extensionId: id1, subpage: Dialog.OPTIONS});
expectEquals(expectedLength, history.length);
// Navigating away from a subpage also shouldn't push state (it just
// closes the dialog).
navigationHelper.updateHistory({page: Page.DETAILS, extensionId: id1});
expectEquals(expectedLength, history.length);
// Navigating away should push new state.
navigationHelper.updateHistory({page: Page.LIST});
expectEquals(++expectedLength, history.length);
// Navigating to a subpage of a different page should push state.
navigationHelper.updateHistory(
{page: Page.DETAILS, extensionId: id1, subpage: Dialog.OPTIONS});
expectEquals(++expectedLength, history.length);
// Navigating away from a subpage to a page for a different item should
// push state.
navigationHelper.updateHistory({page: Page.DETAILS, extensionId: id2});
expectEquals(++expectedLength, history.length);
// Using replaceWith, which passes true for replaceState should not push
// state.
navigationHelper.updateHistory(
{page: Page.DETAILS, extensionId: id1}, true /* replaceState */);
expectEquals(expectedLength, history.length);
});
test(assert(TestNames.SupportedRoutes), function() {
function removeEndSlash(url) {
const CANONICAL_PATH_REGEX = /([\/-\w]+)\/$/;
return url.replace(CANONICAL_PATH_REGEX, '$1');
}
// If it should not redirect, leave newUrl as undefined.
function testIfRedirected(url, newUrl) {
history.pushState({}, '', url);
const testNavigationHelper = new extensions.NavigationHelper();
expectEquals(
removeEndSlash(window.location.href),
removeEndSlash(newUrl || url));
}
testIfRedirected('chrome://extensions');
testIfRedirected('chrome://extensions/');
testIfRedirected('chrome://extensions/shortcuts');
testIfRedirected('chrome://extensions/shortcuts/');
testIfRedirected('chrome://extensions/fake-route', 'chrome://extensions');
// Test trailing slash works.
// Test legacy paths
testIfRedirected(
'chrome://extensions/configureCommands',
'chrome://extensions/shortcuts');
});
});
return {
suiteName: suiteName,
TestNames: TestNames,
};
});