blob: b53b269152f38cfefd17643679fee6e92d4fad9a [file] [log] [blame]
/**
* @license
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import '../../../test/common-test-setup-karma.js';
import './gr-router.js';
import page from 'page/page.mjs';
import {GerritNav} from '../gr-navigation/gr-navigation.js';
const basicFixture = fixtureFromElement('gr-router');
suite('gr-router tests', () => {
let element;
setup(() => {
element = basicFixture.instantiate();
});
test('_firstCodeBrowserWeblink', () => {
assert.deepEqual(element._firstCodeBrowserWeblink([
{name: 'gitweb'},
{name: 'gitiles'},
{name: 'browse'},
{name: 'test'}]), {name: 'gitiles'});
assert.deepEqual(element._firstCodeBrowserWeblink([
{name: 'gitweb'},
{name: 'test'}]), {name: 'gitweb'});
});
test('_getBrowseCommitWeblink', () => {
const browserLink = {name: 'browser', url: 'browser/url'};
const link = {name: 'test', url: 'test/url'};
const weblinks = [browserLink, link];
const config = {gerrit: {primary_weblink_name: browserLink.name}};
sinon.stub(element, '_firstCodeBrowserWeblink').returns(link);
assert.deepEqual(element._getBrowseCommitWeblink(weblinks, config),
browserLink);
assert.deepEqual(element._getBrowseCommitWeblink(weblinks, {}), link);
});
test('_getChangeWeblinks', () => {
const link = {name: 'test', url: 'test/url'};
const browserLink = {name: 'browser', url: 'browser/url'};
const mapLinksToConfig = weblinks => { return {options: {weblinks}}; };
sinon.stub(element, '_getBrowseCommitWeblink').returns(browserLink);
assert.deepEqual(
element._getChangeWeblinks(mapLinksToConfig([link, browserLink]))[0],
{name: 'test', url: 'test/url'});
assert.deepEqual(element._getChangeWeblinks(mapLinksToConfig([link]))[0],
{name: 'test', url: 'test/url'});
link.url = 'https://' + link.url;
assert.deepEqual(element._getChangeWeblinks(mapLinksToConfig([link]))[0],
{name: 'test', url: 'https://test/url'});
});
test('_getHashFromCanonicalPath', () => {
let url = '/foo/bar';
let hash = element._getHashFromCanonicalPath(url);
assert.equal(hash, '');
url = '';
hash = element._getHashFromCanonicalPath(url);
assert.equal(hash, '');
url = '/foo#bar';
hash = element._getHashFromCanonicalPath(url);
assert.equal(hash, 'bar');
url = '/foo#bar#baz';
hash = element._getHashFromCanonicalPath(url);
assert.equal(hash, 'bar#baz');
url = '#foo#bar#baz';
hash = element._getHashFromCanonicalPath(url);
assert.equal(hash, 'foo#bar#baz');
});
suite('_parseLineAddress', () => {
test('returns null for empty and invalid hashes', () => {
let actual = element._parseLineAddress('');
assert.isNull(actual);
actual = element._parseLineAddress('foobar');
assert.isNull(actual);
actual = element._parseLineAddress('foo123');
assert.isNull(actual);
actual = element._parseLineAddress('123bar');
assert.isNull(actual);
});
test('parses correctly', () => {
let actual = element._parseLineAddress('1234');
assert.isOk(actual);
assert.equal(actual.lineNum, 1234);
assert.isFalse(actual.leftSide);
actual = element._parseLineAddress('a4');
assert.isOk(actual);
assert.equal(actual.lineNum, 4);
assert.isTrue(actual.leftSide);
actual = element._parseLineAddress('b77');
assert.isOk(actual);
assert.equal(actual.lineNum, 77);
assert.isTrue(actual.leftSide);
});
});
test('_startRouter requires auth for the right handlers', () => {
// This test encodes the lists of route handler methods that gr-router
// automatically checks for authentication before triggering.
const requiresAuth = {};
const doesNotRequireAuth = {};
sinon.stub(GerritNav, 'setup');
sinon.stub(page, 'start');
sinon.stub(page, 'base');
sinon.stub(element, '_mapRoute').callsFake(
(pattern, methodName, usesAuth) => {
if (usesAuth) {
requiresAuth[methodName] = true;
} else {
doesNotRequireAuth[methodName] = true;
}
});
element._startRouter();
const actualRequiresAuth = Object.keys(requiresAuth);
actualRequiresAuth.sort();
const actualDoesNotRequireAuth = Object.keys(doesNotRequireAuth);
actualDoesNotRequireAuth.sort();
const shouldRequireAutoAuth = [
'_handleAgreementsRoute',
'_handleChangeEditRoute',
'_handleCreateGroupRoute',
'_handleCreateProjectRoute',
'_handleDiffEditRoute',
'_handleGroupAuditLogRoute',
'_handleGroupInfoRoute',
'_handleGroupListFilterOffsetRoute',
'_handleGroupListFilterRoute',
'_handleGroupListOffsetRoute',
'_handleGroupMembersRoute',
'_handleGroupRoute',
'_handleGroupSelfRedirectRoute',
'_handleNewAgreementsRoute',
'_handlePluginListFilterOffsetRoute',
'_handlePluginListFilterRoute',
'_handlePluginListOffsetRoute',
'_handlePluginListRoute',
'_handleRepoCommandsRoute',
'_handleSettingsLegacyRoute',
'_handleSettingsRoute',
];
assert.deepEqual(actualRequiresAuth, shouldRequireAutoAuth);
const unauthenticatedHandlers = [
'_handleBranchListFilterOffsetRoute',
'_handleBranchListFilterRoute',
'_handleBranchListOffsetRoute',
'_handleChangeNumberLegacyRoute',
'_handleChangeRoute',
'_handleDiffRoute',
'_handleDefaultRoute',
'_handleChangeLegacyRoute',
'_handleDiffLegacyRoute',
'_handleDocumentationRedirectRoute',
'_handleDocumentationSearchRoute',
'_handleDocumentationSearchRedirectRoute',
'_handleLegacyLinenum',
'_handleImproperlyEncodedPlusRoute',
'_handlePassThroughRoute',
'_handleProjectDashboardRoute',
'_handleProjectsOldRoute',
'_handleRepoAccessRoute',
'_handleRepoDashboardsRoute',
'_handleRepoListFilterOffsetRoute',
'_handleRepoListFilterRoute',
'_handleRepoListOffsetRoute',
'_handleRepoRoute',
'_handleQueryLegacySuffixRoute',
'_handleQueryRoute',
'_handleRegisterRoute',
'_handleTagListFilterOffsetRoute',
'_handleTagListFilterRoute',
'_handleTagListOffsetRoute',
'_handlePluginScreen',
];
// Handler names that check authentication themselves, and thus don't need
// it performed for them.
const selfAuthenticatingHandlers = [
'_handleDashboardRoute',
'_handleCustomDashboardRoute',
'_handleRootRoute',
];
const shouldNotRequireAuth = unauthenticatedHandlers
.concat(selfAuthenticatingHandlers);
shouldNotRequireAuth.sort();
assert.deepEqual(actualDoesNotRequireAuth, shouldNotRequireAuth);
});
test('_redirectIfNotLoggedIn while logged in', () => {
sinon.stub(element.$.restAPI, 'getLoggedIn')
.returns(Promise.resolve(true));
const data = {canonicalPath: ''};
const redirectStub = sinon.stub(element, '_redirectToLogin');
return element._redirectIfNotLoggedIn(data).then(() => {
assert.isFalse(redirectStub.called);
});
});
test('_redirectIfNotLoggedIn while logged out', () => {
sinon.stub(element.$.restAPI, 'getLoggedIn')
.returns(Promise.resolve(false));
const redirectStub = sinon.stub(element, '_redirectToLogin');
const data = {canonicalPath: ''};
return new Promise(resolve => {
element._redirectIfNotLoggedIn(data)
.then(() => {
assert.isTrue(false, 'Should never execute');
})
.catch(() => {
assert.isTrue(redirectStub.calledOnce);
resolve();
});
});
});
suite('generateUrl', () => {
test('search', () => {
let params = {
view: GerritNav.View.SEARCH,
owner: 'a%b',
project: 'c%d',
branch: 'e%f',
topic: 'g%h',
statuses: ['op%en'],
};
assert.equal(element._generateUrl(params),
'/q/owner:a%2525b+project:c%2525d+branch:e%2525f+' +
'topic:"g%2525h"+status:op%2525en');
params.offset = 100;
assert.equal(element._generateUrl(params),
'/q/owner:a%2525b+project:c%2525d+branch:e%2525f+' +
'topic:"g%2525h"+status:op%2525en,100');
delete params.offset;
// The presence of the query param overrides other params.
params.query = 'foo$bar';
assert.equal(element._generateUrl(params), '/q/foo%2524bar');
params.offset = 100;
assert.equal(element._generateUrl(params), '/q/foo%2524bar,100');
params = {
view: GerritNav.View.SEARCH,
statuses: ['a', 'b', 'c'],
};
assert.equal(element._generateUrl(params),
'/q/(status:a OR status:b OR status:c)');
});
test('change', () => {
const params = {
view: GerritNav.View.CHANGE,
changeNum: '1234',
project: 'test',
};
const paramsWithQuery = {
view: GerritNav.View.CHANGE,
changeNum: '1234',
project: 'test',
querystring: 'revert&foo=bar',
};
assert.equal(element._generateUrl(params), '/c/test/+/1234');
assert.equal(element._generateUrl(paramsWithQuery),
'/c/test/+/1234?revert&foo=bar');
params.patchNum = 10;
assert.equal(element._generateUrl(params), '/c/test/+/1234/10');
paramsWithQuery.patchNum = 10;
assert.equal(element._generateUrl(paramsWithQuery),
'/c/test/+/1234/10?revert&foo=bar');
params.basePatchNum = 5;
assert.equal(element._generateUrl(params), '/c/test/+/1234/5..10');
paramsWithQuery.basePatchNum = 5;
assert.equal(element._generateUrl(paramsWithQuery),
'/c/test/+/1234/5..10?revert&foo=bar');
params.messageHash = '#123';
assert.equal(element._generateUrl(params), '/c/test/+/1234/5..10#123');
});
test('change with repo name encoding', () => {
const params = {
view: GerritNav.View.CHANGE,
changeNum: '1234',
project: 'x+/y+/z+/w',
};
assert.equal(element._generateUrl(params),
'/c/x%252B/y%252B/z%252B/w/+/1234');
});
test('diff', () => {
const params = {
view: GerritNav.View.DIFF,
changeNum: '42',
path: 'x+y/path.cpp',
patchNum: 12,
};
assert.equal(element._generateUrl(params),
'/c/42/12/x%252By/path.cpp');
params.project = 'test';
assert.equal(element._generateUrl(params),
'/c/test/+/42/12/x%252By/path.cpp');
params.basePatchNum = 6;
assert.equal(element._generateUrl(params),
'/c/test/+/42/6..12/x%252By/path.cpp');
params.path = 'foo bar/my+file.txt%';
params.patchNum = 2;
delete params.basePatchNum;
assert.equal(element._generateUrl(params),
'/c/test/+/42/2/foo+bar/my%252Bfile.txt%2525');
params.path = 'file.cpp';
params.lineNum = 123;
assert.equal(element._generateUrl(params),
'/c/test/+/42/2/file.cpp#123');
params.leftSide = true;
assert.equal(element._generateUrl(params),
'/c/test/+/42/2/file.cpp#b123');
});
test('diff with repo name encoding', () => {
const params = {
view: GerritNav.View.DIFF,
changeNum: '42',
path: 'x+y/path.cpp',
patchNum: 12,
project: 'x+/y',
};
assert.equal(element._generateUrl(params),
'/c/x%252B/y/+/42/12/x%252By/path.cpp');
});
test('edit', () => {
const params = {
view: GerritNav.View.EDIT,
changeNum: '42',
project: 'test',
path: 'x+y/path.cpp',
};
assert.equal(element._generateUrl(params),
'/c/test/+/42/x%252By/path.cpp,edit');
});
test('_getPatchRangeExpression', () => {
const params = {};
let actual = element._getPatchRangeExpression(params);
assert.equal(actual, '');
params.patchNum = 4;
actual = element._getPatchRangeExpression(params);
assert.equal(actual, '4');
params.basePatchNum = 2;
actual = element._getPatchRangeExpression(params);
assert.equal(actual, '2..4');
delete params.patchNum;
actual = element._getPatchRangeExpression(params);
assert.equal(actual, '2..');
});
suite('dashboard', () => {
test('self dashboard', () => {
const params = {
view: GerritNav.View.DASHBOARD,
};
assert.equal(element._generateUrl(params), '/dashboard/self');
});
test('user dashboard', () => {
const params = {
view: GerritNav.View.DASHBOARD,
user: 'user',
};
assert.equal(element._generateUrl(params), '/dashboard/user');
});
test('custom self dashboard, no title', () => {
const params = {
view: GerritNav.View.DASHBOARD,
sections: [
{name: 'section 1', query: 'query 1'},
{name: 'section 2', query: 'query 2'},
],
};
assert.equal(
element._generateUrl(params),
'/dashboard/?section%201=query%201&section%202=query%202');
});
test('custom repo dashboard', () => {
const params = {
view: GerritNav.View.DASHBOARD,
sections: [
{name: 'section 1', query: 'query 1 ${project}'},
{name: 'section 2', query: 'query 2 ${repo}'},
],
repo: 'repo-name',
};
assert.equal(
element._generateUrl(params),
'/dashboard/?section%201=query%201%20repo-name&' +
'section%202=query%202%20repo-name');
});
test('custom user dashboard, with title', () => {
const params = {
view: GerritNav.View.DASHBOARD,
user: 'user',
sections: [{name: 'name', query: 'query'}],
title: 'custom dashboard',
};
assert.equal(
element._generateUrl(params),
'/dashboard/user?name=query&title=custom%20dashboard');
});
test('repo dashboard', () => {
const params = {
view: GerritNav.View.DASHBOARD,
repo: 'gerrit/repo',
dashboard: 'default:main',
};
assert.equal(
element._generateUrl(params),
'/p/gerrit/repo/+/dashboard/default:main');
});
test('project dashboard (legacy)', () => {
const params = {
view: GerritNav.View.DASHBOARD,
project: 'gerrit/project',
dashboard: 'default:main',
};
assert.equal(
element._generateUrl(params),
'/p/gerrit/project/+/dashboard/default:main');
});
});
suite('groups', () => {
test('group info', () => {
const params = {
view: GerritNav.View.GROUP,
groupId: 1234,
};
assert.equal(element._generateUrl(params), '/admin/groups/1234');
});
test('group members', () => {
const params = {
view: GerritNav.View.GROUP,
groupId: 1234,
detail: 'members',
};
assert.equal(element._generateUrl(params),
'/admin/groups/1234,members');
});
test('group audit log', () => {
const params = {
view: GerritNav.View.GROUP,
groupId: 1234,
detail: 'log',
};
assert.equal(element._generateUrl(params),
'/admin/groups/1234,audit-log');
});
});
});
suite('param normalization', () => {
let projectLookupStub;
setup(() => {
projectLookupStub = sinon
.stub(element.$.restAPI, 'getFromProjectLookup');
sinon.stub(element, '_generateUrl');
});
suite('_normalizeLegacyRouteParams', () => {
let rangeStub;
let redirectStub;
let show404Stub;
setup(() => {
rangeStub = sinon.stub(element, '_normalizePatchRangeParams')
.returns(Promise.resolve());
redirectStub = sinon.stub(element, '_redirect');
show404Stub = sinon.stub(element, '_show404');
});
test('w/o changeNum', () => {
projectLookupStub.returns(Promise.resolve('foo/bar'));
const params = {};
return element._normalizeLegacyRouteParams(params).then(() => {
assert.isFalse(projectLookupStub.called);
assert.isFalse(rangeStub.called);
assert.isNotOk(params.project);
assert.isFalse(redirectStub.called);
assert.isFalse(show404Stub.called);
});
});
test('w/ changeNum', () => {
projectLookupStub.returns(Promise.resolve('foo/bar'));
const params = {changeNum: 1234};
return element._normalizeLegacyRouteParams(params).then(() => {
assert.isTrue(projectLookupStub.called);
assert.isTrue(rangeStub.called);
assert.equal(params.project, 'foo/bar');
assert.isTrue(redirectStub.calledOnce);
assert.isFalse(show404Stub.called);
});
});
test('halts on project lookup failure', () => {
projectLookupStub.returns(Promise.resolve(undefined));
const params = {changeNum: 1234};
return element._normalizeLegacyRouteParams(params).then(() => {
assert.isTrue(projectLookupStub.called);
assert.isFalse(rangeStub.called);
assert.isUndefined(params.project);
assert.isFalse(redirectStub.called);
assert.isTrue(show404Stub.calledOnce);
});
});
});
suite('_normalizePatchRangeParams', () => {
test('range n..n normalizes to n', () => {
const params = {basePatchNum: 4, patchNum: 4};
const needsRedirect = element._normalizePatchRangeParams(params);
assert.isTrue(needsRedirect);
assert.isNotOk(params.basePatchNum);
assert.equal(params.patchNum, 4);
});
test('range n.. normalizes to n', () => {
const params = {basePatchNum: 4};
const needsRedirect = element._normalizePatchRangeParams(params);
assert.isFalse(needsRedirect);
assert.isNotOk(params.basePatchNum);
assert.equal(params.patchNum, 4);
});
});
});
suite('route handlers', () => {
let redirectStub;
let setParamsStub;
let handlePassThroughRoute;
// Simple route handlers are direct mappings from parsed route data to a
// new set of app.params. This test helper asserts that passing `data`
// into `methodName` results in setting the params specified in `params`.
function assertDataToParams(data, methodName, params) {
element[methodName](data);
assert.deepEqual(setParamsStub.lastCall.args[0], params);
}
setup(() => {
redirectStub = sinon.stub(element, '_redirect');
setParamsStub = sinon.stub(element, '_setParams');
handlePassThroughRoute = sinon.stub(element, '_handlePassThroughRoute');
});
test('_handleAgreementsRoute', () => {
const data = {params: {}};
element._handleAgreementsRoute(data);
assert.isTrue(redirectStub.calledOnce);
assert.equal(redirectStub.lastCall.args[0], '/settings/#Agreements');
});
test('_handleNewAgreementsRoute', () => {
element._handleNewAgreementsRoute({params: {}});
assert.isTrue(setParamsStub.calledOnce);
assert.equal(setParamsStub.lastCall.args[0].view,
GerritNav.View.AGREEMENTS);
});
test('_handleSettingsLegacyRoute', () => {
const data = {params: {0: 'my-token'}};
assertDataToParams(data, '_handleSettingsLegacyRoute', {
view: GerritNav.View.SETTINGS,
emailToken: 'my-token',
});
});
test('_handleSettingsLegacyRoute with +', () => {
const data = {params: {0: 'my-token test'}};
assertDataToParams(data, '_handleSettingsLegacyRoute', {
view: GerritNav.View.SETTINGS,
emailToken: 'my-token+test',
});
});
test('_handleSettingsRoute', () => {
const data = {};
assertDataToParams(data, '_handleSettingsRoute', {
view: GerritNav.View.SETTINGS,
});
});
test('_handleDefaultRoute on first load', () => {
const appElementStub = {dispatchEvent: sinon.stub()};
element._appElement = () => appElementStub;
element._handleDefaultRoute();
assert.isTrue(appElementStub.dispatchEvent.calledOnce);
assert.equal(
appElementStub.dispatchEvent.lastCall.args[0].detail.response.status,
404);
});
test('_handleDefaultRoute after internal navigation', () => {
let onExit = null;
const onRegisteringExit = (match, _onExit) => {
onExit = _onExit;
};
sinon.stub(page, 'exit').callsFake( onRegisteringExit);
sinon.stub(GerritNav, 'setup');
sinon.stub(page, 'start');
sinon.stub(page, 'base');
element._startRouter();
const appElementStub = {dispatchEvent: sinon.stub()};
element._appElement = () => appElementStub;
element._handleDefaultRoute();
onExit('', () => {}); // we left page;
element._handleDefaultRoute();
assert.isTrue(handlePassThroughRoute.calledOnce);
});
test('_handleImproperlyEncodedPlusRoute', () => {
// Regression test for Issue 7100.
element._handleImproperlyEncodedPlusRoute(
{canonicalPath: '/c/test/%20/42', params: ['test', '42']});
assert.isTrue(redirectStub.calledOnce);
assert.equal(
redirectStub.lastCall.args[0],
'/c/test/+/42');
sinon.stub(element, '_getHashFromCanonicalPath').returns('foo');
element._handleImproperlyEncodedPlusRoute(
{canonicalPath: '/c/test/%20/42', params: ['test', '42']});
assert.equal(
redirectStub.lastCall.args[0],
'/c/test/+/42#foo');
});
test('_handleQueryRoute', () => {
const data = {params: ['project:foo/bar/baz']};
assertDataToParams(data, '_handleQueryRoute', {
view: GerritNav.View.SEARCH,
query: 'project:foo/bar/baz',
offset: undefined,
});
data.params.push(',123', '123');
assertDataToParams(data, '_handleQueryRoute', {
view: GerritNav.View.SEARCH,
query: 'project:foo/bar/baz',
offset: '123',
});
});
test('_handleQueryLegacySuffixRoute', () => {
element._handleQueryLegacySuffixRoute({path: '/q/foo+bar,n,z'});
assert.isTrue(redirectStub.calledOnce);
assert.equal(redirectStub.lastCall.args[0], '/q/foo+bar');
});
test('_handleQueryRoute', () => {
const data = {params: ['project:foo/bar/baz']};
assertDataToParams(data, '_handleQueryRoute', {
view: GerritNav.View.SEARCH,
query: 'project:foo/bar/baz',
offset: undefined,
});
data.params.push(',123', '123');
assertDataToParams(data, '_handleQueryRoute', {
view: GerritNav.View.SEARCH,
query: 'project:foo/bar/baz',
offset: '123',
});
});
suite('_handleRegisterRoute', () => {
test('happy path', () => {
const ctx = {params: ['/foo/bar']};
element._handleRegisterRoute(ctx);
assert.isTrue(redirectStub.calledWithExactly('/foo/bar'));
assert.isTrue(setParamsStub.calledOnce);
assert.isTrue(setParamsStub.lastCall.args[0].justRegistered);
});
test('no param', () => {
const ctx = {params: ['']};
element._handleRegisterRoute(ctx);
assert.isTrue(redirectStub.calledWithExactly('/'));
assert.isTrue(setParamsStub.calledOnce);
assert.isTrue(setParamsStub.lastCall.args[0].justRegistered);
});
test('prevent redirect', () => {
const ctx = {params: ['/register']};
element._handleRegisterRoute(ctx);
assert.isTrue(redirectStub.calledWithExactly('/'));
assert.isTrue(setParamsStub.calledOnce);
assert.isTrue(setParamsStub.lastCall.args[0].justRegistered);
});
});
suite('_handleRootRoute', () => {
test('closes for closeAfterLogin', () => {
const data = {querystring: 'closeAfterLogin', canonicalPath: ''};
const closeStub = sinon.stub(window, 'close');
const result = element._handleRootRoute(data);
assert.isNotOk(result);
assert.isTrue(closeStub.called);
assert.isFalse(redirectStub.called);
});
test('redirects to dashboard if logged in', () => {
sinon.stub(element.$.restAPI, 'getLoggedIn')
.returns(Promise.resolve(true));
const data = {
canonicalPath: '/', path: '/', querystring: '', hash: '',
};
const result = element._handleRootRoute(data);
assert.isOk(result);
return result.then(() => {
assert.isTrue(redirectStub.calledWithExactly('/dashboard/self'));
});
});
test('redirects to open changes if not logged in', () => {
sinon.stub(element.$.restAPI, 'getLoggedIn')
.returns(Promise.resolve(false));
const data = {
canonicalPath: '/', path: '/', querystring: '', hash: '',
};
const result = element._handleRootRoute(data);
assert.isOk(result);
return result.then(() => {
assert.isTrue(
redirectStub.calledWithExactly('/q/status:open+-is:wip'));
});
});
suite('GWT hash-path URLs', () => {
test('redirects hash-path URLs', () => {
const data = {
canonicalPath: '/#/foo/bar/baz',
hash: '/foo/bar/baz',
querystring: '',
};
const result = element._handleRootRoute(data);
assert.isNotOk(result);
assert.isTrue(redirectStub.called);
assert.isTrue(redirectStub.calledWithExactly('/foo/bar/baz'));
});
test('redirects hash-path URLs w/o leading slash', () => {
const data = {
canonicalPath: '/#foo/bar/baz',
querystring: '',
hash: 'foo/bar/baz',
};
const result = element._handleRootRoute(data);
assert.isNotOk(result);
assert.isTrue(redirectStub.called);
assert.isTrue(redirectStub.calledWithExactly('/foo/bar/baz'));
});
test('normalizes "/ /" in hash to "/+/"', () => {
const data = {
canonicalPath: '/#/foo/bar/+/123/4',
querystring: '',
hash: '/foo/bar/ /123/4',
};
const result = element._handleRootRoute(data);
assert.isNotOk(result);
assert.isTrue(redirectStub.called);
assert.isTrue(redirectStub.calledWithExactly('/foo/bar/+/123/4'));
});
test('prepends baseurl to hash-path', () => {
const data = {
canonicalPath: '/#/foo/bar',
querystring: '',
hash: '/foo/bar',
};
sinon.stub(element, 'getBaseUrl').returns('/baz');
const result = element._handleRootRoute(data);
assert.isNotOk(result);
assert.isTrue(redirectStub.called);
assert.isTrue(redirectStub.calledWithExactly('/baz/foo/bar'));
});
test('normalizes /VE/ settings hash-paths', () => {
const data = {
canonicalPath: '/#/VE/foo/bar',
querystring: '',
hash: '/VE/foo/bar',
};
const result = element._handleRootRoute(data);
assert.isNotOk(result);
assert.isTrue(redirectStub.called);
assert.isTrue(redirectStub.calledWithExactly(
'/settings/VE/foo/bar'));
});
test('does not drop "inner hashes"', () => {
const data = {
canonicalPath: '/#/foo/bar#baz',
querystring: '',
hash: '/foo/bar',
};
const result = element._handleRootRoute(data);
assert.isNotOk(result);
assert.isTrue(redirectStub.called);
assert.isTrue(redirectStub.calledWithExactly('/foo/bar#baz'));
});
});
});
suite('_handleDashboardRoute', () => {
let redirectToLoginStub;
setup(() => {
redirectToLoginStub = sinon.stub(element, '_redirectToLogin');
});
test('own dashboard but signed out redirects to login', () => {
sinon.stub(element.$.restAPI, 'getLoggedIn')
.returns(Promise.resolve(false));
const data = {canonicalPath: '/dashboard/', params: {0: 'seLF'}};
return element._handleDashboardRoute(data, '').then(() => {
assert.isTrue(redirectToLoginStub.calledOnce);
assert.isFalse(redirectStub.called);
assert.isFalse(setParamsStub.called);
});
});
test('non-self dashboard but signed out does not redirect', () => {
sinon.stub(element.$.restAPI, 'getLoggedIn')
.returns(Promise.resolve(false));
const data = {canonicalPath: '/dashboard/', params: {0: 'foo'}};
return element._handleDashboardRoute(data, '').then(() => {
assert.isFalse(redirectToLoginStub.called);
assert.isFalse(setParamsStub.called);
assert.isTrue(redirectStub.calledOnce);
assert.equal(redirectStub.lastCall.args[0], '/q/owner:foo');
});
});
test('dashboard while signed in sets params', () => {
sinon.stub(element.$.restAPI, 'getLoggedIn')
.returns(Promise.resolve(true));
const data = {canonicalPath: '/dashboard/', params: {0: 'foo'}};
return element._handleDashboardRoute(data, '').then(() => {
assert.isFalse(redirectToLoginStub.called);
assert.isFalse(redirectStub.called);
assert.isTrue(setParamsStub.calledOnce);
assert.deepEqual(setParamsStub.lastCall.args[0], {
view: GerritNav.View.DASHBOARD,
user: 'foo',
});
});
});
});
suite('_handleCustomDashboardRoute', () => {
let redirectToLoginStub;
setup(() => {
redirectToLoginStub = sinon.stub(element, '_redirectToLogin');
});
test('no user specified', () => {
const data = {canonicalPath: '/dashboard/', params: {0: ''}};
return element._handleCustomDashboardRoute(data, '').then(() => {
assert.isFalse(setParamsStub.called);
assert.isTrue(redirectStub.called);
assert.equal(redirectStub.lastCall.args[0], '/dashboard/self');
});
});
test('custom dashboard without title', () => {
const data = {canonicalPath: '/dashboard/', params: {0: ''}};
return element._handleCustomDashboardRoute(data, '?a=b&c&d=e')
.then(() => {
assert.isFalse(redirectStub.called);
assert.isTrue(setParamsStub.calledOnce);
assert.deepEqual(setParamsStub.lastCall.args[0], {
view: GerritNav.View.DASHBOARD,
user: 'self',
sections: [
{name: 'a', query: 'b'},
{name: 'd', query: 'e'},
],
title: 'Custom Dashboard',
});
});
});
test('custom dashboard with title', () => {
const data = {canonicalPath: '/dashboard/', params: {0: ''}};
return element._handleCustomDashboardRoute(data,
'?a=b&c&d=&=e&title=t')
.then(() => {
assert.isFalse(redirectToLoginStub.called);
assert.isFalse(redirectStub.called);
assert.isTrue(setParamsStub.calledOnce);
assert.deepEqual(setParamsStub.lastCall.args[0], {
view: GerritNav.View.DASHBOARD,
user: 'self',
sections: [
{name: 'a', query: 'b'},
],
title: 't',
});
});
});
test('custom dashboard with foreach', () => {
const data = {canonicalPath: '/dashboard/', params: {0: ''}};
return element._handleCustomDashboardRoute(data,
'?a=b&c&d=&=e&foreach=is:open')
.then(() => {
assert.isFalse(redirectToLoginStub.called);
assert.isFalse(redirectStub.called);
assert.isTrue(setParamsStub.calledOnce);
assert.deepEqual(setParamsStub.lastCall.args[0], {
view: GerritNav.View.DASHBOARD,
user: 'self',
sections: [
{name: 'a', query: 'is:open b'},
],
title: 'Custom Dashboard',
});
});
});
});
suite('group routes', () => {
test('_handleGroupInfoRoute', () => {
const data = {params: {0: 1234}};
element._handleGroupInfoRoute(data);
assert.isTrue(redirectStub.calledOnce);
assert.equal(redirectStub.lastCall.args[0], '/admin/groups/1234');
});
test('_handleGroupAuditLogRoute', () => {
const data = {params: {0: 1234}};
assertDataToParams(data, '_handleGroupAuditLogRoute', {
view: GerritNav.View.GROUP,
detail: 'log',
groupId: 1234,
});
});
test('_handleGroupMembersRoute', () => {
const data = {params: {0: 1234}};
assertDataToParams(data, '_handleGroupMembersRoute', {
view: GerritNav.View.GROUP,
detail: 'members',
groupId: 1234,
});
});
test('_handleGroupListOffsetRoute', () => {
const data = {params: {}};
assertDataToParams(data, '_handleGroupListOffsetRoute', {
view: GerritNav.View.ADMIN,
adminView: 'gr-admin-group-list',
offset: 0,
filter: null,
openCreateModal: false,
});
data.params[1] = 42;
assertDataToParams(data, '_handleGroupListOffsetRoute', {
view: GerritNav.View.ADMIN,
adminView: 'gr-admin-group-list',
offset: 42,
filter: null,
openCreateModal: false,
});
data.hash = 'create';
assertDataToParams(data, '_handleGroupListOffsetRoute', {
view: GerritNav.View.ADMIN,
adminView: 'gr-admin-group-list',
offset: 42,
filter: null,
openCreateModal: true,
});
});
test('_handleGroupListFilterOffsetRoute', () => {
const data = {params: {filter: 'foo', offset: 42}};
assertDataToParams(data, '_handleGroupListFilterOffsetRoute', {
view: GerritNav.View.ADMIN,
adminView: 'gr-admin-group-list',
offset: 42,
filter: 'foo',
});
});
test('_handleGroupListFilterRoute', () => {
const data = {params: {filter: 'foo'}};
assertDataToParams(data, '_handleGroupListFilterRoute', {
view: GerritNav.View.ADMIN,
adminView: 'gr-admin-group-list',
filter: 'foo',
});
});
test('_handleGroupRoute', () => {
const data = {params: {0: 4321}};
assertDataToParams(data, '_handleGroupRoute', {
view: GerritNav.View.GROUP,
groupId: 4321,
});
});
});
suite('repo routes', () => {
test('_handleProjectsOldRoute', () => {
const data = {params: {}};
element._handleProjectsOldRoute(data);
assert.isTrue(redirectStub.calledOnce);
assert.equal(redirectStub.lastCall.args[0], '/admin/repos/');
});
test('_handleProjectsOldRoute test', () => {
const data = {params: {1: 'test'}};
element._handleProjectsOldRoute(data);
assert.isTrue(redirectStub.calledOnce);
assert.equal(redirectStub.lastCall.args[0], '/admin/repos/test');
});
test('_handleProjectsOldRoute test,branches', () => {
const data = {params: {1: 'test,branches'}};
element._handleProjectsOldRoute(data);
assert.isTrue(redirectStub.calledOnce);
assert.equal(
redirectStub.lastCall.args[0], '/admin/repos/test,branches');
});
test('_handleRepoRoute', () => {
const data = {params: {0: 4321}};
assertDataToParams(data, '_handleRepoRoute', {
view: GerritNav.View.REPO,
repo: 4321,
});
});
test('_handleRepoCommandsRoute', () => {
const data = {params: {0: 4321}};
assertDataToParams(data, '_handleRepoCommandsRoute', {
view: GerritNav.View.REPO,
detail: GerritNav.RepoDetailView.COMMANDS,
repo: 4321,
});
});
test('_handleRepoAccessRoute', () => {
const data = {params: {0: 4321}};
assertDataToParams(data, '_handleRepoAccessRoute', {
view: GerritNav.View.REPO,
detail: GerritNav.RepoDetailView.ACCESS,
repo: 4321,
});
});
suite('branch list routes', () => {
test('_handleBranchListOffsetRoute', () => {
const data = {params: {0: 4321}};
assertDataToParams(data, '_handleBranchListOffsetRoute', {
view: GerritNav.View.REPO,
detail: GerritNav.RepoDetailView.BRANCHES,
repo: 4321,
offset: 0,
filter: null,
});
data.params[2] = 42;
assertDataToParams(data, '_handleBranchListOffsetRoute', {
view: GerritNav.View.REPO,
detail: GerritNav.RepoDetailView.BRANCHES,
repo: 4321,
offset: 42,
filter: null,
});
});
test('_handleBranchListFilterOffsetRoute', () => {
const data = {params: {repo: 4321, filter: 'foo', offset: 42}};
assertDataToParams(data, '_handleBranchListFilterOffsetRoute', {
view: GerritNav.View.REPO,
detail: GerritNav.RepoDetailView.BRANCHES,
repo: 4321,
offset: 42,
filter: 'foo',
});
});
test('_handleBranchListFilterRoute', () => {
const data = {params: {repo: 4321, filter: 'foo'}};
assertDataToParams(data, '_handleBranchListFilterRoute', {
view: GerritNav.View.REPO,
detail: GerritNav.RepoDetailView.BRANCHES,
repo: 4321,
filter: 'foo',
});
});
});
suite('tag list routes', () => {
test('_handleTagListOffsetRoute', () => {
const data = {params: {0: 4321}};
assertDataToParams(data, '_handleTagListOffsetRoute', {
view: GerritNav.View.REPO,
detail: GerritNav.RepoDetailView.TAGS,
repo: 4321,
offset: 0,
filter: null,
});
});
test('_handleTagListFilterOffsetRoute', () => {
const data = {params: {repo: 4321, filter: 'foo', offset: 42}};
assertDataToParams(data, '_handleTagListFilterOffsetRoute', {
view: GerritNav.View.REPO,
detail: GerritNav.RepoDetailView.TAGS,
repo: 4321,
offset: 42,
filter: 'foo',
});
});
test('_handleTagListFilterRoute', () => {
const data = {params: {repo: 4321}};
assertDataToParams(data, '_handleTagListFilterRoute', {
view: GerritNav.View.REPO,
detail: GerritNav.RepoDetailView.TAGS,
repo: 4321,
filter: null,
});
data.params.filter = 'foo';
assertDataToParams(data, '_handleTagListFilterRoute', {
view: GerritNav.View.REPO,
detail: GerritNav.RepoDetailView.TAGS,
repo: 4321,
filter: 'foo',
});
});
});
suite('repo list routes', () => {
test('_handleRepoListOffsetRoute', () => {
const data = {params: {}};
assertDataToParams(data, '_handleRepoListOffsetRoute', {
view: GerritNav.View.ADMIN,
adminView: 'gr-repo-list',
offset: 0,
filter: null,
openCreateModal: false,
});
data.params[1] = 42;
assertDataToParams(data, '_handleRepoListOffsetRoute', {
view: GerritNav.View.ADMIN,
adminView: 'gr-repo-list',
offset: 42,
filter: null,
openCreateModal: false,
});
data.hash = 'create';
assertDataToParams(data, '_handleRepoListOffsetRoute', {
view: GerritNav.View.ADMIN,
adminView: 'gr-repo-list',
offset: 42,
filter: null,
openCreateModal: true,
});
});
test('_handleRepoListFilterOffsetRoute', () => {
const data = {params: {filter: 'foo', offset: 42}};
assertDataToParams(data, '_handleRepoListFilterOffsetRoute', {
view: GerritNav.View.ADMIN,
adminView: 'gr-repo-list',
offset: 42,
filter: 'foo',
});
});
test('_handleRepoListFilterRoute', () => {
const data = {params: {}};
assertDataToParams(data, '_handleRepoListFilterRoute', {
view: GerritNav.View.ADMIN,
adminView: 'gr-repo-list',
filter: null,
});
data.params.filter = 'foo';
assertDataToParams(data, '_handleRepoListFilterRoute', {
view: GerritNav.View.ADMIN,
adminView: 'gr-repo-list',
filter: 'foo',
});
});
});
});
suite('plugin routes', () => {
test('_handlePluginListOffsetRoute', () => {
const data = {params: {}};
assertDataToParams(data, '_handlePluginListOffsetRoute', {
view: GerritNav.View.ADMIN,
adminView: 'gr-plugin-list',
offset: 0,
filter: null,
});
data.params[1] = 42;
assertDataToParams(data, '_handlePluginListOffsetRoute', {
view: GerritNav.View.ADMIN,
adminView: 'gr-plugin-list',
offset: 42,
filter: null,
});
});
test('_handlePluginListFilterOffsetRoute', () => {
const data = {params: {filter: 'foo', offset: 42}};
assertDataToParams(data, '_handlePluginListFilterOffsetRoute', {
view: GerritNav.View.ADMIN,
adminView: 'gr-plugin-list',
offset: 42,
filter: 'foo',
});
});
test('_handlePluginListFilterRoute', () => {
const data = {params: {}};
assertDataToParams(data, '_handlePluginListFilterRoute', {
view: GerritNav.View.ADMIN,
adminView: 'gr-plugin-list',
filter: null,
});
data.params.filter = 'foo';
assertDataToParams(data, '_handlePluginListFilterRoute', {
view: GerritNav.View.ADMIN,
adminView: 'gr-plugin-list',
filter: 'foo',
});
});
test('_handlePluginListRoute', () => {
const data = {params: {}};
assertDataToParams(data, '_handlePluginListRoute', {
view: GerritNav.View.ADMIN,
adminView: 'gr-plugin-list',
});
});
});
suite('change/diff routes', () => {
test('_handleChangeNumberLegacyRoute', () => {
const data = {params: {0: 12345}};
element._handleChangeNumberLegacyRoute(data);
assert.isTrue(redirectStub.calledOnce);
assert.isTrue(redirectStub.calledWithExactly('/c/12345'));
});
test('_handleChangeLegacyRoute', () => {
const normalizeRouteStub = sinon.stub(element,
'_normalizeLegacyRouteParams');
const ctx = {
params: [
1234, // 0 Change number
null, // 1 Unused
null, // 2 Unused
6, // 3 Base patch number
null, // 4 Unused
9, // 5 Patch number
],
querystring: '',
};
element._handleChangeLegacyRoute(ctx);
assert.isTrue(normalizeRouteStub.calledOnce);
assert.deepEqual(normalizeRouteStub.lastCall.args[0], {
changeNum: 1234,
basePatchNum: 6,
patchNum: 9,
view: GerritNav.View.CHANGE,
querystring: '',
});
});
test('_handleDiffLegacyRoute', () => {
const normalizeRouteStub = sinon.stub(element,
'_normalizeLegacyRouteParams');
const ctx = {
params: [
1234, // 0 Change number
null, // 1 Unused
3, // 2 Base patch number
null, // 3 Unused
8, // 4 Patch number
'foo/bar', // 5 Diff path
],
path: '/c/1234/3..8/foo/bar',
hash: 'b123',
};
element._handleDiffLegacyRoute(ctx);
assert.isFalse(redirectStub.called);
assert.isTrue(normalizeRouteStub.calledOnce);
assert.deepEqual(normalizeRouteStub.lastCall.args[0], {
changeNum: 1234,
basePatchNum: 3,
patchNum: 8,
view: GerritNav.View.DIFF,
path: 'foo/bar',
lineNum: 123,
leftSide: true,
});
});
test('_handleLegacyLinenum w/ @321', () => {
const ctx = {path: '/c/1234/3..8/foo/bar@321'};
element._handleLegacyLinenum(ctx);
assert.isTrue(redirectStub.calledOnce);
assert.isTrue(redirectStub.calledWithExactly(
'/c/1234/3..8/foo/bar#321'));
});
test('_handleLegacyLinenum w/ @b123', () => {
const ctx = {path: '/c/1234/3..8/foo/bar@b123'};
element._handleLegacyLinenum(ctx);
assert.isTrue(redirectStub.calledOnce);
assert.isTrue(redirectStub.calledWithExactly(
'/c/1234/3..8/foo/bar#b123'));
});
suite('_handleChangeRoute', () => {
let normalizeRangeStub;
function makeParams(path, hash) {
return {
params: [
'foo/bar', // 0 Project
1234, // 1 Change number
null, // 2 Unused
null, // 3 Unused
4, // 4 Base patch number
null, // 5 Unused
7, // 6 Patch number
],
queryMap: new Map(),
};
}
setup(() => {
normalizeRangeStub = sinon.stub(element,
'_normalizePatchRangeParams');
sinon.stub(element.$.restAPI, 'setInProjectLookup');
});
test('needs redirect', () => {
normalizeRangeStub.returns(true);
sinon.stub(element, '_generateUrl').returns('foo');
const ctx = makeParams(null, '');
element._handleChangeRoute(ctx);
assert.isTrue(normalizeRangeStub.called);
assert.isFalse(setParamsStub.called);
assert.isTrue(redirectStub.calledOnce);
assert.isTrue(redirectStub.calledWithExactly('foo'));
});
test('change view', () => {
normalizeRangeStub.returns(false);
sinon.stub(element, '_generateUrl').returns('foo');
const ctx = makeParams(null, '');
assertDataToParams(ctx, '_handleChangeRoute', {
view: GerritNav.View.CHANGE,
project: 'foo/bar',
changeNum: 1234,
basePatchNum: 4,
patchNum: 7,
queryMap: new Map(),
});
assert.isFalse(redirectStub.called);
assert.isTrue(normalizeRangeStub.called);
});
});
suite('_handleDiffRoute', () => {
let normalizeRangeStub;
function makeParams(path, hash) {
return {
params: [
'foo/bar', // 0 Project
1234, // 1 Change number
null, // 2 Unused
null, // 3 Unused
4, // 4 Base patch number
null, // 5 Unused
7, // 6 Patch number
null, // 7 Unused,
path, // 8 Diff path
],
hash,
};
}
setup(() => {
normalizeRangeStub = sinon.stub(element,
'_normalizePatchRangeParams');
sinon.stub(element.$.restAPI, 'setInProjectLookup');
});
test('needs redirect', () => {
normalizeRangeStub.returns(true);
sinon.stub(element, '_generateUrl').returns('foo');
const ctx = makeParams(null, '');
element._handleDiffRoute(ctx);
assert.isTrue(normalizeRangeStub.called);
assert.isFalse(setParamsStub.called);
assert.isTrue(redirectStub.calledOnce);
assert.isTrue(redirectStub.calledWithExactly('foo'));
});
test('diff view', () => {
normalizeRangeStub.returns(false);
sinon.stub(element, '_generateUrl').returns('foo');
const ctx = makeParams('foo/bar/baz', 'b44');
assertDataToParams(ctx, '_handleDiffRoute', {
view: GerritNav.View.DIFF,
project: 'foo/bar',
changeNum: 1234,
basePatchNum: 4,
patchNum: 7,
path: 'foo/bar/baz',
leftSide: true,
lineNum: 44,
});
assert.isFalse(redirectStub.called);
assert.isTrue(normalizeRangeStub.called);
});
});
test('_handleDiffEditRoute', () => {
const normalizeRangeSpy =
sinon.spy(element, '_normalizePatchRangeParams');
sinon.stub(element.$.restAPI, 'setInProjectLookup');
const ctx = {
params: [
'foo/bar', // 0 Project
1234, // 1 Change number
3, // 2 Patch num
'foo/bar/baz', // 3 File path
],
};
const appParams = {
project: 'foo/bar',
changeNum: 1234,
view: GerritNav.View.EDIT,
path: 'foo/bar/baz',
patchNum: 3,
lineNum: undefined,
};
element._handleDiffEditRoute(ctx);
assert.isFalse(redirectStub.called);
assert.isTrue(normalizeRangeSpy.calledOnce);
assert.deepEqual(normalizeRangeSpy.lastCall.args[0], appParams);
assert.isFalse(normalizeRangeSpy.lastCall.returnValue);
assert.deepEqual(setParamsStub.lastCall.args[0], appParams);
});
test('_handleDiffEditRoute with lineNum', () => {
const normalizeRangeSpy =
sinon.spy(element, '_normalizePatchRangeParams');
sinon.stub(element.$.restAPI, 'setInProjectLookup');
const ctx = {
params: [
'foo/bar', // 0 Project
1234, // 1 Change number
3, // 2 Patch num
'foo/bar/baz', // 3 File path
],
hash: 4,
};
const appParams = {
project: 'foo/bar',
changeNum: 1234,
view: GerritNav.View.EDIT,
path: 'foo/bar/baz',
patchNum: 3,
lineNum: 4,
};
element._handleDiffEditRoute(ctx);
assert.isFalse(redirectStub.called);
assert.isTrue(normalizeRangeSpy.calledOnce);
assert.deepEqual(normalizeRangeSpy.lastCall.args[0], appParams);
assert.isFalse(normalizeRangeSpy.lastCall.returnValue);
assert.deepEqual(setParamsStub.lastCall.args[0], appParams);
});
test('_handleChangeEditRoute', () => {
const normalizeRangeSpy =
sinon.spy(element, '_normalizePatchRangeParams');
sinon.stub(element.$.restAPI, 'setInProjectLookup');
const ctx = {
params: [
'foo/bar', // 0 Project
1234, // 1 Change number
null,
3, // 3 Patch num
],
};
const appParams = {
project: 'foo/bar',
changeNum: 1234,
view: GerritNav.View.CHANGE,
patchNum: 3,
edit: true,
};
element._handleChangeEditRoute(ctx);
assert.isFalse(redirectStub.called);
assert.isTrue(normalizeRangeSpy.calledOnce);
assert.deepEqual(normalizeRangeSpy.lastCall.args[0], appParams);
assert.isFalse(normalizeRangeSpy.lastCall.returnValue);
assert.deepEqual(setParamsStub.lastCall.args[0], appParams);
});
});
test('_handlePluginScreen', () => {
const ctx = {params: ['foo', 'bar']};
assertDataToParams(ctx, '_handlePluginScreen', {
view: GerritNav.View.PLUGIN_SCREEN,
plugin: 'foo',
screen: 'bar',
});
assert.isFalse(redirectStub.called);
});
});
suite('_parseQueryString', () => {
test('empty queries', () => {
assert.deepEqual(element._parseQueryString(''), []);
assert.deepEqual(element._parseQueryString('?'), []);
assert.deepEqual(element._parseQueryString('??'), []);
assert.deepEqual(element._parseQueryString('&&&'), []);
});
test('url decoding', () => {
assert.deepEqual(element._parseQueryString('+'), [[' ', '']]);
assert.deepEqual(element._parseQueryString('???+%3d+'), [[' = ', '']]);
assert.deepEqual(
element._parseQueryString('%6e%61%6d%65=%76%61%6c%75%65'),
[['name', 'value']]);
});
test('multiple parameters', () => {
assert.deepEqual(
element._parseQueryString('a=b&c=d&e=f'),
[['a', 'b'], ['c', 'd'], ['e', 'f']]);
assert.deepEqual(
element._parseQueryString('&a=b&&&e=f&c'),
[['a', 'b'], ['e', 'f'], ['c', '']]);
});
});
});