diff options
Diffstat (limited to 'polygerrit-ui/app/elements/core/gr-router/gr-router_test.ts')
-rw-r--r-- | polygerrit-ui/app/elements/core/gr-router/gr-router_test.ts | 1823 |
1 files changed, 1823 insertions, 0 deletions
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router_test.ts b/polygerrit-ui/app/elements/core/gr-router/gr-router_test.ts new file mode 100644 index 0000000000..4c147bb9c3 --- /dev/null +++ b/polygerrit-ui/app/elements/core/gr-router/gr-router_test.ts @@ -0,0 +1,1823 @@ +/** + * @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'; +import './gr-router'; +import {page} from '../../../utils/page-wrapper-utils'; +import { + GenerateUrlChangeViewParameters, + GenerateUrlDashboardViewParameters, + GenerateUrlDiffViewParameters, + GenerateUrlEditViewParameters, + GenerateUrlGroupViewParameters, + GenerateUrlParameters, + GenerateUrlSearchViewParameters, + GerritNav, + GroupDetailView, + WeblinkType, +} from '../gr-navigation/gr-navigation'; +import { + stubBaseUrl, + stubRestApi, + addListenerForTest, +} from '../../../test/test-utils'; +import { + GrRouter, + PageContextWithQueryMap, + PatchRangeParams, + _testOnly_RoutePattern, +} from './gr-router'; +import {GerritView} from '../../../services/router/router-model'; +import { + BasePatchSetNum, + BranchName, + CommitId, + DashboardId, + GroupId, + NumericChangeId, + ParentPatchSetNum, + PatchSetNum, + RepoName, + RevisionPatchSetNum, + TopicName, + UrlEncodedCommentId, + WebLinkInfo, +} from '../../../types/common'; +import { + createGerritInfo, + createServerInfo, +} from '../../../test/test-data-generators'; +import {AppElementParams} from '../../gr-app-types'; + +suite('gr-router tests', () => { + let router: GrRouter; + + setup(() => { + router = new GrRouter(); + }); + + test('firstCodeBrowserWeblink', () => { + assert.deepEqual( + router.firstCodeBrowserWeblink([ + {name: 'gitweb'}, + {name: 'gitiles'}, + {name: 'browse'}, + {name: 'test'}, + ]), + {name: 'gitiles'} + ); + + assert.deepEqual( + router.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 = { + ...createServerInfo(), + gerrit: {...createGerritInfo(), primary_weblink_name: browserLink.name}, + }; + sinon.stub(router, 'firstCodeBrowserWeblink').returns(link); + + assert.deepEqual( + router.getBrowseCommitWeblink(weblinks, config), + browserLink + ); + + assert.deepEqual(router.getBrowseCommitWeblink(weblinks), link); + }); + + test('getChangeWeblinks', () => { + const link = {name: 'test', url: 'test/url'}; + const browserLink = {name: 'browser', url: 'browser/url'}; + const mapLinksToConfig = (weblinks: WebLinkInfo[]) => { + return { + type: 'change' as WeblinkType.CHANGE, + repo: 'test' as RepoName, + commit: '111' as CommitId, + options: {weblinks}, + }; + }; + sinon.stub(router, 'getBrowseCommitWeblink').returns(browserLink); + + assert.deepEqual( + router.getChangeWeblinks(mapLinksToConfig([link, browserLink]))[0], + {name: 'test', url: 'test/url'} + ); + + assert.deepEqual(router.getChangeWeblinks(mapLinksToConfig([link]))[0], { + name: 'test', + url: 'test/url', + }); + + link.url = `https://${link.url}`; + assert.deepEqual(router.getChangeWeblinks(mapLinksToConfig([link]))[0], { + name: 'test', + url: 'https://test/url', + }); + }); + + test('getHashFromCanonicalPath', () => { + let url = '/foo/bar'; + let hash = router.getHashFromCanonicalPath(url); + assert.equal(hash, ''); + + url = ''; + hash = router.getHashFromCanonicalPath(url); + assert.equal(hash, ''); + + url = '/foo#bar'; + hash = router.getHashFromCanonicalPath(url); + assert.equal(hash, 'bar'); + + url = '/foo#bar#baz'; + hash = router.getHashFromCanonicalPath(url); + assert.equal(hash, 'bar#baz'); + + url = '#foo#bar#baz'; + hash = router.getHashFromCanonicalPath(url); + assert.equal(hash, 'foo#bar#baz'); + }); + + suite('parseLineAddress', () => { + test('returns null for empty and invalid hashes', () => { + let actual = router.parseLineAddress(''); + assert.isNull(actual); + + actual = router.parseLineAddress('foobar'); + assert.isNull(actual); + + actual = router.parseLineAddress('foo123'); + assert.isNull(actual); + + actual = router.parseLineAddress('123bar'); + assert.isNull(actual); + }); + + test('parses correctly', () => { + let actual = router.parseLineAddress('1234'); + assert.isOk(actual); + assert.equal(actual!.lineNum, 1234); + assert.isFalse(actual!.leftSide); + + actual = router.parseLineAddress('a4'); + assert.isOk(actual); + assert.equal(actual!.lineNum, 4); + assert.isTrue(actual!.leftSide); + + actual = router.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: any = {}; + const doesNotRequireAuth: any = {}; + sinon.stub(GerritNav, 'setup'); + sinon.stub(page, 'start'); + sinon.stub(page, 'base'); + sinon + .stub(router, 'mapRoute') + .callsFake((_pattern, methodName, _method, usesAuth) => { + if (usesAuth) { + requiresAuth[methodName] = true; + } else { + doesNotRequireAuth[methodName] = true; + } + }); + router.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', + 'handleChangeIdQueryRoute', + 'handleChangeNumberLegacyRoute', + 'handleChangeRoute', + 'handleCommentRoute', + 'handleCommentsRoute', + 'handleDiffRoute', + 'handleDefaultRoute', + 'handleChangeLegacyRoute', + 'handleDocumentationRedirectRoute', + 'handleDocumentationSearchRoute', + 'handleDocumentationSearchRedirectRoute', + 'handleLegacyLinenum', + 'handleImproperlyEncodedPlusRoute', + 'handlePassThroughRoute', + 'handleProjectDashboardRoute', + 'handleLegacyProjectDashboardRoute', + 'handleProjectsOldRoute', + 'handleRepoAccessRoute', + 'handleRepoDashboardsRoute', + 'handleRepoGeneralRoute', + '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', () => { + stubRestApi('getLoggedIn').returns(Promise.resolve(true)); + const data = { + save() {}, + handled: true, + canonicalPath: '', + path: '', + querystring: '', + pathname: '', + state: '', + title: '', + hash: '', + params: {test: 'test'}, + }; + const redirectStub = sinon.stub(router, 'redirectToLogin'); + return router.redirectIfNotLoggedIn(data).then(() => { + assert.isFalse(redirectStub.called); + }); + }); + + test('redirectIfNotLoggedIn while logged out', () => { + stubRestApi('getLoggedIn').returns(Promise.resolve(false)); + const redirectStub = sinon.stub(router, 'redirectToLogin'); + const data = { + save() {}, + handled: true, + canonicalPath: '', + path: '', + querystring: '', + pathname: '', + state: '', + title: '', + hash: '', + params: {test: 'test'}, + }; + return new Promise(resolve => { + router + .redirectIfNotLoggedIn(data) + .then(() => { + assert.isTrue(false, 'Should never execute'); + }) + .catch(() => { + assert.isTrue(redirectStub.calledOnce); + resolve(Promise.resolve()); + }); + }); + }); + + suite('generateUrl', () => { + test('search', () => { + let params: GenerateUrlSearchViewParameters = { + view: GerritView.SEARCH, + owner: 'a%b', + project: 'c%d' as RepoName, + branch: 'e%f' as BranchName, + topic: 'g%h' as TopicName, + statuses: ['op%en'], + }; + assert.equal( + router.generateUrl(params), + '/q/owner:a%2525b+project:c%2525d+branch:e%2525f+' + + 'topic:g%2525h+status:op%2525en' + ); + + params.offset = 100; + assert.equal( + router.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(router.generateUrl(params), '/q/foo%2524bar'); + + params.offset = 100; + assert.equal(router.generateUrl(params), '/q/foo%2524bar,100'); + + params = { + view: GerritNav.View.SEARCH, + statuses: ['a', 'b', 'c'], + }; + assert.equal( + router.generateUrl(params), + '/q/(status:a OR status:b OR status:c)' + ); + + params = { + view: GerritNav.View.SEARCH, + topic: 'test' as TopicName, + }; + assert.equal(router.generateUrl(params), '/q/topic:test'); + params = { + view: GerritNav.View.SEARCH, + topic: 'test test' as TopicName, + }; + assert.equal(router.generateUrl(params), '/q/topic:"test+test"'); + params = { + view: GerritNav.View.SEARCH, + topic: 'test:test' as TopicName, + }; + assert.equal(router.generateUrl(params), '/q/topic:"test:test"'); + }); + + test('change', () => { + const params: GenerateUrlChangeViewParameters = { + view: GerritView.CHANGE, + changeNum: 1234 as NumericChangeId, + project: 'test' as RepoName, + }; + + assert.equal(router.generateUrl(params), '/c/test/+/1234'); + + params.patchNum = 10 as PatchSetNum; + assert.equal(router.generateUrl(params), '/c/test/+/1234/10'); + + params.basePatchNum = 5 as BasePatchSetNum; + assert.equal(router.generateUrl(params), '/c/test/+/1234/5..10'); + + params.messageHash = '#123'; + assert.equal(router.generateUrl(params), '/c/test/+/1234/5..10#123'); + }); + + test('change with repo name encoding', () => { + const params: GenerateUrlChangeViewParameters = { + view: GerritView.CHANGE, + changeNum: 1234 as NumericChangeId, + project: 'x+/y+/z+/w' as RepoName, + }; + assert.equal( + router.generateUrl(params), + '/c/x%252B/y%252B/z%252B/w/+/1234' + ); + }); + + test('diff', () => { + const params: GenerateUrlDiffViewParameters = { + view: GerritView.DIFF, + changeNum: 42 as NumericChangeId, + path: 'x+y/path.cpp' as RepoName, + patchNum: 12 as PatchSetNum, + project: '' as RepoName, + }; + assert.equal(router.generateUrl(params), '/c/42/12/x%252By/path.cpp'); + + params.project = 'test' as RepoName; + assert.equal( + router.generateUrl(params), + '/c/test/+/42/12/x%252By/path.cpp' + ); + + params.basePatchNum = 6 as BasePatchSetNum; + assert.equal( + router.generateUrl(params), + '/c/test/+/42/6..12/x%252By/path.cpp' + ); + + params.path = 'foo bar/my+file.txt%'; + params.patchNum = 2 as PatchSetNum; + delete params.basePatchNum; + assert.equal( + router.generateUrl(params), + '/c/test/+/42/2/foo+bar/my%252Bfile.txt%2525' + ); + + params.path = 'file.cpp'; + params.lineNum = 123; + assert.equal(router.generateUrl(params), '/c/test/+/42/2/file.cpp#123'); + + params.leftSide = true; + assert.equal(router.generateUrl(params), '/c/test/+/42/2/file.cpp#b123'); + }); + + test('diff with repo name encoding', () => { + const params: GenerateUrlDiffViewParameters = { + view: GerritView.DIFF, + changeNum: 42 as NumericChangeId, + path: 'x+y/path.cpp', + patchNum: 12 as PatchSetNum, + project: 'x+/y' as RepoName, + }; + assert.equal( + router.generateUrl(params), + '/c/x%252B/y/+/42/12/x%252By/path.cpp' + ); + }); + + test('edit', () => { + const params: GenerateUrlEditViewParameters = { + view: GerritView.EDIT, + changeNum: 42 as NumericChangeId, + project: 'test' as RepoName, + path: 'x+y/path.cpp', + patchNum: 'edit' as PatchSetNum, + }; + assert.equal( + router.generateUrl(params), + '/c/test/+/42/edit/x%252By/path.cpp,edit' + ); + }); + + test('getPatchRangeExpression', () => { + const params: PatchRangeParams = {}; + let actual = router.getPatchRangeExpression(params); + assert.equal(actual, ''); + + params.patchNum = 4 as PatchSetNum; + actual = router.getPatchRangeExpression(params); + assert.equal(actual, '4'); + + params.basePatchNum = 2 as BasePatchSetNum; + actual = router.getPatchRangeExpression(params); + assert.equal(actual, '2..4'); + + delete params.patchNum; + actual = router.getPatchRangeExpression(params); + assert.equal(actual, '2..'); + }); + + suite('dashboard', () => { + test('self dashboard', () => { + const params: GenerateUrlDashboardViewParameters = { + view: GerritView.DASHBOARD, + }; + assert.equal(router.generateUrl(params), '/dashboard/self'); + }); + + test('user dashboard', () => { + const params: GenerateUrlDashboardViewParameters = { + view: GerritView.DASHBOARD, + user: 'user', + }; + assert.equal(router.generateUrl(params), '/dashboard/user'); + }); + + test('custom self dashboard, no title', () => { + const params: GenerateUrlDashboardViewParameters = { + view: GerritView.DASHBOARD, + sections: [ + {name: 'section 1', query: 'query 1'}, + {name: 'section 2', query: 'query 2'}, + ], + }; + assert.equal( + router.generateUrl(params), + '/dashboard/?section%201=query%201§ion%202=query%202' + ); + }); + + test('custom repo dashboard', () => { + const params: GenerateUrlDashboardViewParameters = { + view: GerritView.DASHBOARD, + sections: [ + {name: 'section 1', query: 'query 1 ${project}'}, + {name: 'section 2', query: 'query 2 ${repo}'}, + ], + repo: 'repo-name' as RepoName, + }; + assert.equal( + router.generateUrl(params), + '/dashboard/?section%201=query%201%20repo-name&' + + 'section%202=query%202%20repo-name' + ); + }); + + test('custom user dashboard, with title', () => { + const params: GenerateUrlDashboardViewParameters = { + view: GerritView.DASHBOARD, + user: 'user', + sections: [{name: 'name', query: 'query'}], + title: 'custom dashboard', + }; + assert.equal( + router.generateUrl(params), + '/dashboard/user?name=query&title=custom%20dashboard' + ); + }); + + test('repo dashboard', () => { + const params: GenerateUrlDashboardViewParameters = { + view: GerritView.DASHBOARD, + repo: 'gerrit/repo' as RepoName, + dashboard: 'default:main' as DashboardId, + }; + assert.equal( + router.generateUrl(params), + '/p/gerrit/repo/+/dashboard/default:main' + ); + }); + + test('project dashboard (legacy)', () => { + const params: GenerateUrlDashboardViewParameters = { + view: GerritView.DASHBOARD, + project: 'gerrit/project' as RepoName, + dashboard: 'default:main' as DashboardId, + }; + assert.equal( + router.generateUrl(params), + '/p/gerrit/project/+/dashboard/default:main' + ); + }); + }); + + suite('groups', () => { + test('group info', () => { + const params: GenerateUrlGroupViewParameters = { + view: GerritView.GROUP, + groupId: '1234' as GroupId, + }; + assert.equal(router.generateUrl(params), '/admin/groups/1234'); + }); + + test('group members', () => { + const params: GenerateUrlGroupViewParameters = { + view: GerritView.GROUP, + groupId: '1234' as GroupId, + detail: 'members' as GroupDetailView, + }; + assert.equal(router.generateUrl(params), '/admin/groups/1234,members'); + }); + + test('group audit log', () => { + const params: GenerateUrlGroupViewParameters = { + view: GerritView.GROUP, + groupId: '1234' as GroupId, + detail: 'log' as GroupDetailView, + }; + assert.equal( + router.generateUrl(params), + '/admin/groups/1234,audit-log' + ); + }); + }); + }); + + suite('param normalization', () => { + suite('normalizePatchRangeParams', () => { + test('range n..n normalizes to n', () => { + const params: PatchRangeParams = { + basePatchNum: 4 as BasePatchSetNum, + patchNum: 4 as PatchSetNum, + }; + const needsRedirect = router.normalizePatchRangeParams(params); + assert.isTrue(needsRedirect); + assert.equal(params.basePatchNum, ParentPatchSetNum); + assert.equal(params.patchNum, 4 as PatchSetNum); + }); + + test('range n.. normalizes to n', () => { + const params: PatchRangeParams = {basePatchNum: 4 as BasePatchSetNum}; + const needsRedirect = router.normalizePatchRangeParams(params); + assert.isFalse(needsRedirect); + assert.equal(params.basePatchNum, ParentPatchSetNum); + assert.equal(params.patchNum, 4 as PatchSetNum); + }); + }); + }); + + suite('route handlers', () => { + let redirectStub: sinon.SinonStub; + let setParamsStub: sinon.SinonStub; + let handlePassThroughRoute: sinon.SinonStub; + + // 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: PageContextWithQueryMap, + methodName: string, + params: AppElementParams | GenerateUrlParameters + ) { + (router as any)[methodName](data); + assert.deepEqual(setParamsStub.lastCall.args[0], params); + } + + function createPageContext(): PageContextWithQueryMap { + return { + queryMap: new Map(), + save() {}, + handled: true, + canonicalPath: '', + path: '', + querystring: '', + pathname: '', + state: '', + title: '', + hash: '', + params: {}, + }; + } + + setup(() => { + redirectStub = sinon.stub(router, 'redirect'); + setParamsStub = sinon.stub(router, 'setParams'); + handlePassThroughRoute = sinon.stub(router, 'handlePassThroughRoute'); + }); + + test('handleLegacyProjectDashboardRoute', () => { + const params = { + ...createPageContext(), + params: {0: 'gerrit/project', 1: 'dashboard:main'}, + }; + router.handleLegacyProjectDashboardRoute(params); + assert.isTrue(redirectStub.calledOnce); + assert.equal( + redirectStub.lastCall.args[0], + '/p/gerrit/project/+/dashboard/dashboard:main' + ); + }); + + test('handleAgreementsRoute', () => { + router.handleAgreementsRoute(); + assert.isTrue(redirectStub.calledOnce); + assert.equal(redirectStub.lastCall.args[0], '/settings/#Agreements'); + }); + + test('handleNewAgreementsRoute', () => { + const params = createPageContext(); + router.handleNewAgreementsRoute(params); + assert.isTrue(setParamsStub.calledOnce); + assert.equal( + setParamsStub.lastCall.args[0].view, + GerritNav.View.AGREEMENTS + ); + }); + + test('handleSettingsLegacyRoute', () => { + const data = {...createPageContext(), params: {0: 'my-token'}}; + assertDataToParams(data, 'handleSettingsLegacyRoute', { + view: GerritNav.View.SETTINGS, + emailToken: 'my-token', + }); + }); + + test('handleSettingsLegacyRoute with +', () => { + const data = {...createPageContext(), params: {0: 'my-token test'}}; + assertDataToParams(data, 'handleSettingsLegacyRoute', { + view: GerritNav.View.SETTINGS, + emailToken: 'my-token+test', + }); + }); + + test('handleSettingsRoute', () => { + const data = createPageContext(); + assertDataToParams(data, 'handleSettingsRoute', { + view: GerritNav.View.SETTINGS, + }); + }); + + test('handleDefaultRoute on first load', () => { + const spy = sinon.spy(); + addListenerForTest(document, 'page-error', spy); + router.handleDefaultRoute(); + assert.isTrue(spy.calledOnce); + assert.equal(spy.lastCall.args[0].detail.response.status, 404); + }); + + test('handleDefaultRoute after internal navigation', () => { + let onExit: Function | null = null; + const onRegisteringExit = ( + _match: string | RegExp, + _onExit: Function + ) => { + onExit = _onExit; + }; + sinon.stub(page, 'exit').callsFake(onRegisteringExit); + sinon.stub(GerritNav, 'setup'); + sinon.stub(page, 'start'); + sinon.stub(page, 'base'); + router.startRouter(); + + router.handleDefaultRoute(); + + onExit!('', () => {}); // we left page; + + router.handleDefaultRoute(); + assert.isTrue(handlePassThroughRoute.calledOnce); + }); + + test('handleImproperlyEncodedPlusRoute', () => { + const params = { + ...createPageContext(), + canonicalPath: '/c/test/%20/42', + params: {0: 'test', 1: '42'}, + }; + // Regression test for Issue 7100. + router.handleImproperlyEncodedPlusRoute(params); + assert.isTrue(redirectStub.calledOnce); + assert.equal(redirectStub.lastCall.args[0], '/c/test/+/42'); + + sinon.stub(router, 'getHashFromCanonicalPath').returns('foo'); + router.handleImproperlyEncodedPlusRoute(params); + assert.equal(redirectStub.lastCall.args[0], '/c/test/+/42#foo'); + }); + + test('handleQueryRoute', () => { + const data: PageContextWithQueryMap = { + ...createPageContext(), + params: {0: 'project:foo/bar/baz'}, + }; + assertDataToParams(data, 'handleQueryRoute', { + view: GerritNav.View.SEARCH, + query: 'project:foo/bar/baz', + offset: undefined, + }); + + data.params[1] = '123'; + data.params[2] = '123'; + assertDataToParams(data, 'handleQueryRoute', { + view: GerritNav.View.SEARCH, + query: 'project:foo/bar/baz', + offset: '123', + }); + }); + + test('handleQueryLegacySuffixRoute', () => { + const params = {...createPageContext(), path: '/q/foo+bar,n,z'}; + router.handleQueryLegacySuffixRoute(params); + assert.isTrue(redirectStub.calledOnce); + assert.equal(redirectStub.lastCall.args[0], '/q/foo+bar'); + }); + + test('handleChangeIdQueryRoute', () => { + const data = { + ...createPageContext(), + params: {0: 'I0123456789abcdef0123456789abcdef01234567'}, + }; + assertDataToParams(data, 'handleChangeIdQueryRoute', { + view: GerritNav.View.SEARCH, + query: 'I0123456789abcdef0123456789abcdef01234567', + }); + }); + + suite('handleRegisterRoute', () => { + test('happy path', () => { + const ctx = {...createPageContext(), params: {0: '/foo/bar'}}; + router.handleRegisterRoute(ctx); + assert.isTrue(redirectStub.calledWithExactly('/foo/bar')); + assert.isTrue(setParamsStub.calledOnce); + assert.isTrue(setParamsStub.lastCall.args[0].justRegistered); + }); + + test('no param', () => { + const ctx = createPageContext(); + router.handleRegisterRoute(ctx); + assert.isTrue(redirectStub.calledWithExactly('/')); + assert.isTrue(setParamsStub.calledOnce); + assert.isTrue(setParamsStub.lastCall.args[0].justRegistered); + }); + + test('prevent redirect', () => { + const ctx = {...createPageContext(), params: {0: '/register'}}; + router.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 = {...createPageContext(), querystring: 'closeAfterLogin'}; + const closeStub = sinon.stub(window, 'close'); + const result = router.handleRootRoute(data); + assert.isNotOk(result); + assert.isTrue(closeStub.called); + assert.isFalse(redirectStub.called); + }); + + test('redirects to dashboard if logged in', () => { + const data = {...createPageContext(), canonicalPath: '/', path: '/'}; + const result = router.handleRootRoute(data); + assert.isOk(result); + return result!.then(() => { + assert.isTrue(redirectStub.calledWithExactly('/dashboard/self')); + }); + }); + + test('redirects to open changes if not logged in', () => { + stubRestApi('getLoggedIn').returns(Promise.resolve(false)); + const data = {...createPageContext(), canonicalPath: '/', path: '/'}; + const result = router.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 = { + ...createPageContext(), + canonicalPath: '/#/foo/bar/baz', + hash: '/foo/bar/baz', + }; + const result = router.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 = { + ...createPageContext(), + canonicalPath: '/#foo/bar/baz', + hash: 'foo/bar/baz', + }; + const result = router.handleRootRoute(data); + assert.isNotOk(result); + assert.isTrue(redirectStub.called); + assert.isTrue(redirectStub.calledWithExactly('/foo/bar/baz')); + }); + + test('normalizes "/ /" in hash to "/+/"', () => { + const data = { + ...createPageContext(), + canonicalPath: '/#/foo/bar/+/123/4', + hash: '/foo/bar/ /123/4', + }; + const result = router.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 = { + ...createPageContext(), + canonicalPath: '/#/foo/bar', + hash: '/foo/bar', + }; + stubBaseUrl('/baz'); + const result = router.handleRootRoute(data); + assert.isNotOk(result); + assert.isTrue(redirectStub.called); + assert.isTrue(redirectStub.calledWithExactly('/baz/foo/bar')); + }); + + test('normalizes /VE/ settings hash-paths', () => { + const data = { + ...createPageContext(), + canonicalPath: '/#/VE/foo/bar', + hash: '/VE/foo/bar', + }; + const result = router.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 = { + ...createPageContext(), + canonicalPath: '/#/foo/bar#baz', + hash: '/foo/bar', + }; + const result = router.handleRootRoute(data); + assert.isNotOk(result); + assert.isTrue(redirectStub.called); + assert.isTrue(redirectStub.calledWithExactly('/foo/bar#baz')); + }); + }); + }); + + suite('handleDashboardRoute', () => { + let redirectToLoginStub: sinon.SinonStub; + + setup(() => { + redirectToLoginStub = sinon.stub(router, 'redirectToLogin'); + }); + + test('own dashboard but signed out redirects to login', () => { + stubRestApi('getLoggedIn').returns(Promise.resolve(false)); + const data = { + ...createPageContext(), + canonicalPath: '/dashboard/', + params: {0: 'seLF'}, + }; + return router.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', () => { + stubRestApi('getLoggedIn').returns(Promise.resolve(false)); + const data = { + ...createPageContext(), + canonicalPath: '/dashboard/', + params: {0: 'foo'}, + }; + return router.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', () => { + const data = { + ...createPageContext(), + canonicalPath: '/dashboard/', + params: {0: 'foo'}, + }; + return router.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: sinon.SinonStub; + + setup(() => { + redirectToLoginStub = sinon.stub(router, 'redirectToLogin'); + }); + + test('no user specified', () => { + const data = { + ...createPageContext(), + canonicalPath: '/dashboard/', + params: {0: ''}, + }; + return router.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 = { + ...createPageContext(), + canonicalPath: '/dashboard/', + params: {0: ''}, + }; + return router + .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 = { + ...createPageContext(), + canonicalPath: '/dashboard/', + params: {0: ''}, + }; + return router + .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 = { + ...createPageContext(), + canonicalPath: '/dashboard/', + params: {0: ''}, + }; + return router + .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 = {...createPageContext(), params: {0: '1234'}}; + router.handleGroupInfoRoute(data); + assert.isTrue(redirectStub.calledOnce); + assert.equal(redirectStub.lastCall.args[0], '/admin/groups/1234'); + }); + + test('handleGroupAuditLogRoute', () => { + const data = {...createPageContext(), params: {0: '1234'}}; + assertDataToParams(data, 'handleGroupAuditLogRoute', { + view: GerritView.GROUP, + detail: GroupDetailView.LOG, + groupId: '1234' as GroupId, + }); + }); + + test('handleGroupMembersRoute', () => { + const data = {...createPageContext(), params: {0: '1234'}}; + assertDataToParams(data, 'handleGroupMembersRoute', { + view: GerritView.GROUP, + detail: GroupDetailView.MEMBERS, + groupId: '1234' as GroupId, + }); + }); + + test('handleGroupListOffsetRoute', () => { + const data = createPageContext(); + assertDataToParams(data, 'handleGroupListOffsetRoute', { + view: GerritView.ADMIN, + adminView: 'gr-admin-group-list', + offset: 0, + filter: null, + openCreateModal: false, + }); + + data.params[1] = '42'; + assertDataToParams(data, 'handleGroupListOffsetRoute', { + view: GerritView.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 = { + ...createPageContext(), + params: {filter: 'foo', offset: '42'}, + }; + assertDataToParams(data, 'handleGroupListFilterOffsetRoute', { + view: GerritView.ADMIN, + adminView: 'gr-admin-group-list', + offset: '42', + filter: 'foo', + }); + }); + + test('handleGroupListFilterRoute', () => { + const data = {...createPageContext(), params: {filter: 'foo'}}; + assertDataToParams(data, 'handleGroupListFilterRoute', { + view: GerritView.ADMIN, + adminView: 'gr-admin-group-list', + filter: 'foo', + }); + }); + + test('handleGroupRoute', () => { + const data = {...createPageContext(), params: {0: '4321'}}; + assertDataToParams(data, 'handleGroupRoute', { + view: GerritView.GROUP, + groupId: '4321' as GroupId, + }); + }); + }); + + suite('repo routes', () => { + test('handleProjectsOldRoute', () => { + const data = {...createPageContext(), params: {}}; + router.handleProjectsOldRoute(data); + assert.isTrue(redirectStub.calledOnce); + assert.equal(redirectStub.lastCall.args[0], '/admin/repos/'); + }); + + test('handleProjectsOldRoute test', () => { + const data = {...createPageContext(), params: {1: 'test'}}; + router.handleProjectsOldRoute(data); + assert.isTrue(redirectStub.calledOnce); + assert.equal(redirectStub.lastCall.args[0], '/admin/repos/test'); + }); + + test('handleProjectsOldRoute test,branches', () => { + const data = {...createPageContext(), params: {1: 'test,branches'}}; + router.handleProjectsOldRoute(data); + assert.isTrue(redirectStub.calledOnce); + assert.equal( + redirectStub.lastCall.args[0], + '/admin/repos/test,branches' + ); + }); + + test('handleRepoRoute', () => { + const data = {...createPageContext(), path: '/admin/repos/test'}; + router.handleRepoRoute(data); + assert.isTrue(redirectStub.calledOnce); + assert.equal( + redirectStub.lastCall.args[0], + '/admin/repos/test,general' + ); + }); + + test('handleRepoGeneralRoute', () => { + const data = {...createPageContext(), params: {0: '4321'}}; + assertDataToParams(data, 'handleRepoGeneralRoute', { + view: GerritView.REPO, + detail: GerritNav.RepoDetailView.GENERAL, + repo: '4321' as RepoName, + }); + }); + + test('handleRepoCommandsRoute', () => { + const data = {...createPageContext(), params: {0: '4321'}}; + assertDataToParams(data, 'handleRepoCommandsRoute', { + view: GerritView.REPO, + detail: GerritNav.RepoDetailView.COMMANDS, + repo: '4321' as RepoName, + }); + }); + + test('handleRepoAccessRoute', () => { + const data = {...createPageContext(), params: {0: '4321'}}; + assertDataToParams(data, 'handleRepoAccessRoute', { + view: GerritView.REPO, + detail: GerritNav.RepoDetailView.ACCESS, + repo: '4321' as RepoName, + }); + }); + + suite('branch list routes', () => { + test('handleBranchListOffsetRoute', () => { + const data: PageContextWithQueryMap = { + ...createPageContext(), + params: {0: '4321'}, + }; + assertDataToParams(data, 'handleBranchListOffsetRoute', { + view: GerritView.REPO, + detail: GerritNav.RepoDetailView.BRANCHES, + repo: '4321' as RepoName, + offset: 0, + filter: null, + }); + + data.params[2] = '42'; + assertDataToParams(data, 'handleBranchListOffsetRoute', { + view: GerritView.REPO, + detail: GerritNav.RepoDetailView.BRANCHES, + repo: '4321' as RepoName, + offset: '42', + filter: null, + }); + }); + + test('handleBranchListFilterOffsetRoute', () => { + const data = { + ...createPageContext(), + params: {repo: '4321', filter: 'foo', offset: '42'}, + }; + assertDataToParams(data, 'handleBranchListFilterOffsetRoute', { + view: GerritView.REPO, + detail: GerritNav.RepoDetailView.BRANCHES, + repo: '4321' as RepoName, + offset: '42', + filter: 'foo', + }); + }); + + test('handleBranchListFilterRoute', () => { + const data = { + ...createPageContext(), + params: {repo: '4321', filter: 'foo'}, + }; + assertDataToParams(data, 'handleBranchListFilterRoute', { + view: GerritView.REPO, + detail: GerritNav.RepoDetailView.BRANCHES, + repo: '4321' as RepoName, + filter: 'foo', + }); + }); + }); + + suite('tag list routes', () => { + test('handleTagListOffsetRoute', () => { + const data = {...createPageContext(), params: {0: '4321'}}; + assertDataToParams(data, 'handleTagListOffsetRoute', { + view: GerritView.REPO, + detail: GerritNav.RepoDetailView.TAGS, + repo: '4321' as RepoName, + offset: 0, + filter: null, + }); + }); + + test('handleTagListFilterOffsetRoute', () => { + const data = { + ...createPageContext(), + params: {repo: '4321', filter: 'foo', offset: '42'}, + }; + assertDataToParams(data, 'handleTagListFilterOffsetRoute', { + view: GerritView.REPO, + detail: GerritNav.RepoDetailView.TAGS, + repo: '4321' as RepoName, + offset: '42', + filter: 'foo', + }); + }); + + test('handleTagListFilterRoute', () => { + const data: PageContextWithQueryMap = { + ...createPageContext(), + params: {repo: '4321'}, + }; + assertDataToParams(data, 'handleTagListFilterRoute', { + view: GerritView.REPO, + detail: GerritNav.RepoDetailView.TAGS, + repo: '4321' as RepoName, + filter: null, + }); + + data.params.filter = 'foo'; + assertDataToParams(data, 'handleTagListFilterRoute', { + view: GerritView.REPO, + detail: GerritNav.RepoDetailView.TAGS, + repo: '4321' as RepoName, + filter: 'foo', + }); + }); + }); + + suite('repo list routes', () => { + test('handleRepoListOffsetRoute', () => { + const data = createPageContext(); + assertDataToParams(data, 'handleRepoListOffsetRoute', { + view: GerritView.ADMIN, + adminView: 'gr-repo-list', + offset: 0, + filter: null, + openCreateModal: false, + }); + + data.params[1] = '42'; + assertDataToParams(data, 'handleRepoListOffsetRoute', { + view: GerritView.ADMIN, + adminView: 'gr-repo-list', + offset: '42', + filter: null, + openCreateModal: false, + }); + + data.hash = 'create'; + assertDataToParams(data, 'handleRepoListOffsetRoute', { + view: GerritView.ADMIN, + adminView: 'gr-repo-list', + offset: '42', + filter: null, + openCreateModal: true, + }); + }); + + test('handleRepoListFilterOffsetRoute', () => { + const data = { + ...createPageContext(), + params: {filter: 'foo', offset: '42'}, + }; + assertDataToParams(data, 'handleRepoListFilterOffsetRoute', { + view: GerritView.ADMIN, + adminView: 'gr-repo-list', + offset: '42', + filter: 'foo', + }); + }); + + test('handleRepoListFilterRoute', () => { + const data = createPageContext(); + assertDataToParams(data, 'handleRepoListFilterRoute', { + view: GerritView.ADMIN, + adminView: 'gr-repo-list', + filter: null, + }); + + data.params.filter = 'foo'; + assertDataToParams(data, 'handleRepoListFilterRoute', { + view: GerritView.ADMIN, + adminView: 'gr-repo-list', + filter: 'foo', + }); + }); + }); + }); + + suite('plugin routes', () => { + test('handlePluginListOffsetRoute', () => { + const data = createPageContext(); + assertDataToParams(data, 'handlePluginListOffsetRoute', { + view: GerritView.ADMIN, + adminView: 'gr-plugin-list', + offset: 0, + filter: null, + }); + + data.params[1] = '42'; + assertDataToParams(data, 'handlePluginListOffsetRoute', { + view: GerritView.ADMIN, + adminView: 'gr-plugin-list', + offset: '42', + filter: null, + }); + }); + + test('handlePluginListFilterOffsetRoute', () => { + const data = { + ...createPageContext(), + params: {filter: 'foo', offset: '42'}, + }; + assertDataToParams(data, 'handlePluginListFilterOffsetRoute', { + view: GerritView.ADMIN, + adminView: 'gr-plugin-list', + offset: '42', + filter: 'foo', + }); + }); + + test('handlePluginListFilterRoute', () => { + const data = createPageContext(); + assertDataToParams(data, 'handlePluginListFilterRoute', { + view: GerritView.ADMIN, + adminView: 'gr-plugin-list', + filter: null, + }); + + data.params.filter = 'foo'; + assertDataToParams(data, 'handlePluginListFilterRoute', { + view: GerritView.ADMIN, + adminView: 'gr-plugin-list', + filter: 'foo', + }); + }); + + test('handlePluginListRoute', () => { + const data = createPageContext(); + assertDataToParams(data, 'handlePluginListRoute', { + view: GerritView.ADMIN, + adminView: 'gr-plugin-list', + }); + }); + }); + + suite('change/diff routes', () => { + test('handleChangeNumberLegacyRoute', () => { + const data = {...createPageContext(), params: {0: '12345'}}; + router.handleChangeNumberLegacyRoute(data); + assert.isTrue(redirectStub.calledOnce); + assert.isTrue(redirectStub.calledWithExactly('/c/12345')); + }); + + test('handleChangeLegacyRoute', async () => { + stubRestApi('getFromProjectLookup').returns( + Promise.resolve('project' as RepoName) + ); + const ctx = { + ...createPageContext(), + params: {0: '1234', 1: 'comment/6789'}, + }; + router.handleChangeLegacyRoute(ctx); + await flush(); + assert.isTrue( + redirectStub.calledWithExactly('/c/project/+/1234' + '/comment/6789') + ); + }); + + test('handleLegacyLinenum w/ @321', () => { + const ctx = {...createPageContext(), path: '/c/1234/3..8/foo/bar@321'}; + router.handleLegacyLinenum(ctx); + assert.isTrue(redirectStub.calledOnce); + assert.isTrue( + redirectStub.calledWithExactly('/c/1234/3..8/foo/bar#321') + ); + }); + + test('handleLegacyLinenum w/ @b123', () => { + const ctx = {...createPageContext(), path: '/c/1234/3..8/foo/bar@b123'}; + router.handleLegacyLinenum(ctx); + assert.isTrue(redirectStub.calledOnce); + assert.isTrue( + redirectStub.calledWithExactly('/c/1234/3..8/foo/bar#b123') + ); + }); + + suite('handleChangeRoute', () => { + let normalizeRangeStub: sinon.SinonStub; + + function makeParams( + _path: string, + _hash: string + ): PageContextWithQueryMap { + return { + ...createPageContext(), + params: { + 0: 'foo/bar', // 0 Project + 1: '1234', // 1 Change number + 2: '', // 2 Unused + 3: '', // 3 Unused + 4: '4', // 4 Base patch number + 5: '', // 5 Unused + 6: '7', // 6 Patch number + }, + }; + } + + setup(() => { + normalizeRangeStub = sinon.stub(router, 'normalizePatchRangeParams'); + stubRestApi('setInProjectLookup'); + }); + + test('needs redirect', () => { + normalizeRangeStub.returns(true); + sinon.stub(router, 'generateUrl').returns('foo'); + const ctx = makeParams('', ''); + router.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(router, 'generateUrl').returns('foo'); + const ctx = makeParams('', ''); + assertDataToParams(ctx, 'handleChangeRoute', { + view: GerritView.CHANGE, + project: 'foo/bar' as RepoName, + changeNum: 1234 as NumericChangeId, + basePatchNum: 4 as BasePatchSetNum, + patchNum: 7 as RevisionPatchSetNum, + }); + assert.isFalse(redirectStub.called); + assert.isTrue(normalizeRangeStub.called); + }); + + test('params', () => { + normalizeRangeStub.returns(false); + sinon.stub(router, 'generateUrl').returns('foo'); + const ctx = makeParams('', ''); + ctx.queryMap.set('tab', 'checks'); + ctx.queryMap.set('filter', 'fff'); + ctx.queryMap.set('select', 'sss'); + ctx.queryMap.set('attempt', '1'); + assertDataToParams(ctx, 'handleChangeRoute', { + view: GerritView.CHANGE, + project: 'foo/bar' as RepoName, + changeNum: 1234 as NumericChangeId, + basePatchNum: 4 as BasePatchSetNum, + patchNum: 7 as RevisionPatchSetNum, + attempt: 1, + filter: 'fff', + select: 'sss', + tab: 'checks', + }); + }); + }); + + suite('handleDiffRoute', () => { + let normalizeRangeStub: sinon.SinonStub; + + function makeParams( + path: string, + hash: string + ): PageContextWithQueryMap { + return { + ...createPageContext(), + hash, + params: { + 0: 'foo/bar', // 0 Project + 1: '1234', // 1 Change number + 2: '', // 2 Unused + 3: '', // 3 Unused + 4: '4', // 4 Base patch number + 5: '', // 5 Unused + 6: '7', // 6 Patch number + 7: '', // 7 Unused, + 8: path, // 8 Diff path + }, + }; + } + + setup(() => { + normalizeRangeStub = sinon.stub(router, 'normalizePatchRangeParams'); + stubRestApi('setInProjectLookup'); + }); + + test('needs redirect', () => { + normalizeRangeStub.returns(true); + sinon.stub(router, 'generateUrl').returns('foo'); + const ctx = makeParams('', ''); + router.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(router, 'generateUrl').returns('foo'); + const ctx = makeParams('foo/bar/baz', 'b44'); + assertDataToParams(ctx, 'handleDiffRoute', { + view: GerritView.DIFF, + project: 'foo/bar' as RepoName, + changeNum: 1234 as NumericChangeId, + basePatchNum: 4 as BasePatchSetNum, + patchNum: 7 as RevisionPatchSetNum, + path: 'foo/bar/baz', + leftSide: true, + lineNum: 44, + }); + assert.isFalse(redirectStub.called); + assert.isTrue(normalizeRangeStub.called); + }); + + test('comment route', () => { + const url = '/c/gerrit/+/264833/comment/00049681_f34fd6a9/'; + const groups = url.match(_testOnly_RoutePattern.COMMENT); + assert.deepEqual(groups!.slice(1), [ + 'gerrit', // project + '264833', // changeNum + '00049681_f34fd6a9', // commentId + ]); + assertDataToParams( + {params: groups!.slice(1)} as any, + 'handleCommentRoute', + { + project: 'gerrit' as RepoName, + changeNum: 264833 as NumericChangeId, + commentId: '00049681_f34fd6a9' as UrlEncodedCommentId, + commentLink: true, + view: GerritView.DIFF, + } + ); + }); + + test('comments route', () => { + const url = '/c/gerrit/+/264833/comments/00049681_f34fd6a9/'; + const groups = url.match(_testOnly_RoutePattern.COMMENTS_TAB); + assert.deepEqual(groups!.slice(1), [ + 'gerrit', // project + '264833', // changeNum + '00049681_f34fd6a9', // commentId + ]); + assertDataToParams( + {params: groups!.slice(1)} as any, + 'handleCommentsRoute', + { + project: 'gerrit' as RepoName, + changeNum: 264833 as NumericChangeId, + commentId: '00049681_f34fd6a9' as UrlEncodedCommentId, + view: GerritView.CHANGE, + } + ); + }); + }); + + test('handleDiffEditRoute', () => { + const normalizeRangeSpy = sinon.spy( + router, + 'normalizePatchRangeParams' + ); + stubRestApi('setInProjectLookup'); + const ctx = { + ...createPageContext(), + hash: '', + params: { + 0: 'foo/bar', // 0 Project + 1: '1234', // 1 Change number + 2: '3', // 2 Patch num + 3: 'foo/bar/baz', // 3 File path + }, + }; + const appParams: GenerateUrlEditViewParameters = { + project: 'foo/bar' as RepoName, + changeNum: 1234 as NumericChangeId, + view: GerritNav.View.EDIT, + path: 'foo/bar/baz', + patchNum: 3 as PatchSetNum, + lineNum: '', + }; + + router.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( + router, + 'normalizePatchRangeParams' + ); + stubRestApi('setInProjectLookup'); + const ctx = { + ...createPageContext(), + hash: '4', + params: { + 0: 'foo/bar', // 0 Project + 1: '1234', // 1 Change number + 2: '3', // 2 Patch num + 3: 'foo/bar/baz', // 3 File path + }, + }; + const appParams: GenerateUrlEditViewParameters = { + project: 'foo/bar' as RepoName, + changeNum: 1234 as NumericChangeId, + view: GerritNav.View.EDIT, + path: 'foo/bar/baz', + patchNum: 3 as PatchSetNum, + lineNum: '4', + }; + + router.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( + router, + 'normalizePatchRangeParams' + ); + stubRestApi('setInProjectLookup'); + const ctx = { + ...createPageContext(), + params: { + 0: 'foo/bar', // 0 Project + 1: '1234', // 1 Change number + 2: '', + 3: '3', // 3 Patch num + }, + }; + const appParams: GenerateUrlChangeViewParameters = { + project: 'foo/bar' as RepoName, + changeNum: 1234 as NumericChangeId, + view: GerritView.CHANGE, + patchNum: 3 as PatchSetNum, + edit: true, + tab: '', + }; + + router.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 = {...createPageContext(), params: {0: 'foo', 1: 'bar'}}; + assertDataToParams(ctx, 'handlePluginScreen', { + view: GerritNav.View.PLUGIN_SCREEN, + plugin: 'foo', + screen: 'bar', + }); + assert.isFalse(redirectStub.called); + }); + }); + + suite('parseQueryString', () => { + test('empty queries', () => { + assert.deepEqual(router.parseQueryString(''), []); + assert.deepEqual(router.parseQueryString('?'), []); + assert.deepEqual(router.parseQueryString('??'), []); + assert.deepEqual(router.parseQueryString('&&&'), []); + }); + + test('url decoding', () => { + assert.deepEqual(router.parseQueryString('+'), [[' ', '']]); + assert.deepEqual(router.parseQueryString('???+%3d+'), [[' = ', '']]); + assert.deepEqual( + router.parseQueryString('%6e%61%6d%65=%76%61%6c%75%65'), + [['name', 'value']] + ); + }); + + test('multiple parameters', () => { + assert.deepEqual(router.parseQueryString('a=b&c=d&e=f'), [ + ['a', 'b'], + ['c', 'd'], + ['e', 'f'], + ]); + assert.deepEqual(router.parseQueryString('&a=b&&&e=f&c'), [ + ['a', 'b'], + ['e', 'f'], + ['c', ''], + ]); + }); + }); +}); |