blob: 50e506bd8f237c090acf1cb8c453a1058ac683a4 [file] [log] [blame]
paladox68b968e2022-02-20 17:31:50 +00001/**
2 * @license
Ben Rohlfs94fcbbc2022-05-27 10:45:03 +02003 * Copyright 2017 Google LLC
4 * SPDX-License-Identifier: Apache-2.0
paladox68b968e2022-02-20 17:31:50 +00005 */
Frank Bordenbe9451a2022-09-12 15:44:29 +02006import '../../../test/common-test-setup';
paladox68b968e2022-02-20 17:31:50 +00007import './gr-router';
Ben Rohlfs1da030b2023-01-31 13:09:53 +01008import {Page, PageContext} from './gr-page';
paladox68b968e2022-02-20 17:31:50 +00009import {
10 stubBaseUrl,
11 stubRestApi,
12 addListenerForTest,
Ben Rohlfsadbde152023-01-24 19:23:32 +010013 waitUntilCalled,
Ben Rohlfsf6657362023-04-12 14:01:35 +020014 mockPromise,
15 MockPromise,
paladox68b968e2022-02-20 17:31:50 +000016} from '../../../test/test-utils';
Ben Rohlfsf27798d2023-01-26 12:45:21 +010017import {GrRouter, routerToken} from './gr-router';
paladox68b968e2022-02-20 17:31:50 +000018import {GerritView} from '../../../services/router/router-model';
19import {
20 BasePatchSetNum,
paladox68b968e2022-02-20 17:31:50 +000021 NumericChangeId,
Ben Rohlfs58267b72022-05-27 15:59:18 +020022 PARENT,
paladox68b968e2022-02-20 17:31:50 +000023 RepoName,
24 RevisionPatchSetNum,
paladox68b968e2022-02-20 17:31:50 +000025 UrlEncodedCommentId,
paladox68b968e2022-02-20 17:31:50 +000026} from '../../../types/common';
Ben Rohlfsf27798d2023-01-26 12:45:21 +010027import {AppElementJustRegisteredParams} from '../../gr-app-types';
Frank Bordene1ba8212022-08-29 15:20:01 +020028import {assert} from '@open-wc/testing';
Ben Rohlfsadbde152023-01-24 19:23:32 +010029import {AdminChildView, AdminViewState} from '../../../models/views/admin';
Ben Rohlfsade94cb2022-09-14 19:59:42 +020030import {RepoDetailView} from '../../../models/views/repo';
31import {GroupDetailView} from '../../../models/views/group';
Ben Rohlfsf27798d2023-01-26 12:45:21 +010032import {ChangeChildView} from '../../../models/views/change';
Ben Rohlfsbd8dbcf2022-09-16 09:01:14 +020033import {PatchRangeParams} from '../../../utils/url-util';
Chris Poucet9b0707c2022-10-27 14:53:55 +020034import {testResolver} from '../../../test/common-test-setup';
Ben Rohlfs196dc722022-12-20 18:01:33 +010035import {
Ben Rohlfsf27798d2023-01-26 12:45:21 +010036 createAdminPluginsViewState,
37 createAdminReposViewState,
38 createChangeViewState,
Ben Rohlfs196dc722022-12-20 18:01:33 +010039 createComment,
Ben Rohlfsf27798d2023-01-26 12:45:21 +010040 createDashboardViewState,
Ben Rohlfs196dc722022-12-20 18:01:33 +010041 createDiff,
Ben Rohlfsf27798d2023-01-26 12:45:21 +010042 createDiffViewState,
43 createEditViewState,
44 createGroupViewState,
Ben Rohlfs196dc722022-12-20 18:01:33 +010045 createParsedChange,
Ben Rohlfsf27798d2023-01-26 12:45:21 +010046 createRepoBranchesViewState,
47 createRepoTagsViewState,
48 createRepoViewState,
Ben Rohlfs196dc722022-12-20 18:01:33 +010049 createRevision,
Ben Rohlfsf27798d2023-01-26 12:45:21 +010050 createSearchViewState,
Ben Rohlfs196dc722022-12-20 18:01:33 +010051} from '../../../test/test-data-generators';
52import {ParsedChangeInfo} from '../../../types/types';
Ben Rohlfsadbde152023-01-24 19:23:32 +010053import {ViewState} from '../../../models/views/base';
Kamil Musin3e3da5a2023-10-18 16:19:13 +020054import {DashboardType} from '../../../models/views/dashboard';
paladox68b968e2022-02-20 17:31:50 +000055
paladox68b968e2022-02-20 17:31:50 +000056suite('gr-router tests', () => {
Frank Borden79448112022-04-12 16:59:32 +020057 let router: GrRouter;
Ben Rohlfs0d174e02023-01-24 18:18:28 +010058 let page: Page;
paladox68b968e2022-02-20 17:31:50 +000059
60 setup(() => {
Chris Poucet9b0707c2022-10-27 14:53:55 +020061 router = testResolver(routerToken);
Ben Rohlfs0d174e02023-01-24 18:18:28 +010062 page = router.page;
paladox68b968e2022-02-20 17:31:50 +000063 });
64
Ben Rohlfsf27798d2023-01-26 12:45:21 +010065 teardown(async () => {
66 router.finalize();
67 });
68
Frank Borden79448112022-04-12 16:59:32 +020069 test('getHashFromCanonicalPath', () => {
paladox68b968e2022-02-20 17:31:50 +000070 let url = '/foo/bar';
Frank Borden79448112022-04-12 16:59:32 +020071 let hash = router.getHashFromCanonicalPath(url);
paladox68b968e2022-02-20 17:31:50 +000072 assert.equal(hash, '');
73
74 url = '';
Frank Borden79448112022-04-12 16:59:32 +020075 hash = router.getHashFromCanonicalPath(url);
paladox68b968e2022-02-20 17:31:50 +000076 assert.equal(hash, '');
77
78 url = '/foo#bar';
Frank Borden79448112022-04-12 16:59:32 +020079 hash = router.getHashFromCanonicalPath(url);
paladox68b968e2022-02-20 17:31:50 +000080 assert.equal(hash, 'bar');
81
82 url = '/foo#bar#baz';
Frank Borden79448112022-04-12 16:59:32 +020083 hash = router.getHashFromCanonicalPath(url);
paladox68b968e2022-02-20 17:31:50 +000084 assert.equal(hash, 'bar#baz');
85
86 url = '#foo#bar#baz';
Frank Borden79448112022-04-12 16:59:32 +020087 hash = router.getHashFromCanonicalPath(url);
paladox68b968e2022-02-20 17:31:50 +000088 assert.equal(hash, 'foo#bar#baz');
89 });
90
Frank Borden79448112022-04-12 16:59:32 +020091 suite('parseLineAddress', () => {
paladox68b968e2022-02-20 17:31:50 +000092 test('returns null for empty and invalid hashes', () => {
Frank Borden79448112022-04-12 16:59:32 +020093 let actual = router.parseLineAddress('');
paladox68b968e2022-02-20 17:31:50 +000094 assert.isNull(actual);
95
Frank Borden79448112022-04-12 16:59:32 +020096 actual = router.parseLineAddress('foobar');
paladox68b968e2022-02-20 17:31:50 +000097 assert.isNull(actual);
98
Frank Borden79448112022-04-12 16:59:32 +020099 actual = router.parseLineAddress('foo123');
paladox68b968e2022-02-20 17:31:50 +0000100 assert.isNull(actual);
101
Frank Borden79448112022-04-12 16:59:32 +0200102 actual = router.parseLineAddress('123bar');
paladox68b968e2022-02-20 17:31:50 +0000103 assert.isNull(actual);
104 });
105
106 test('parses correctly', () => {
Frank Borden79448112022-04-12 16:59:32 +0200107 let actual = router.parseLineAddress('1234');
paladox68b968e2022-02-20 17:31:50 +0000108 assert.isOk(actual);
109 assert.equal(actual!.lineNum, 1234);
110 assert.isFalse(actual!.leftSide);
111
Frank Borden79448112022-04-12 16:59:32 +0200112 actual = router.parseLineAddress('a4');
paladox68b968e2022-02-20 17:31:50 +0000113 assert.isOk(actual);
114 assert.equal(actual!.lineNum, 4);
115 assert.isTrue(actual!.leftSide);
116
Frank Borden79448112022-04-12 16:59:32 +0200117 actual = router.parseLineAddress('b77');
paladox68b968e2022-02-20 17:31:50 +0000118 assert.isOk(actual);
119 assert.equal(actual!.lineNum, 77);
120 assert.isTrue(actual!.leftSide);
121 });
122 });
123
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100124 test('startRouterForTesting requires auth for the right handlers', () => {
paladox68b968e2022-02-20 17:31:50 +0000125 // This test encodes the lists of route handler methods that gr-router
126 // automatically checks for authentication before triggering.
127
128 const requiresAuth: any = {};
129 const doesNotRequireAuth: any = {};
paladox68b968e2022-02-20 17:31:50 +0000130 sinon.stub(page, 'start');
paladox68b968e2022-02-20 17:31:50 +0000131 sinon
Frank Borden79448112022-04-12 16:59:32 +0200132 .stub(router, 'mapRoute')
133 .callsFake((_pattern, methodName, _method, usesAuth) => {
paladox68b968e2022-02-20 17:31:50 +0000134 if (usesAuth) {
135 requiresAuth[methodName] = true;
136 } else {
137 doesNotRequireAuth[methodName] = true;
138 }
139 });
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100140 router._testOnly_startRouter();
paladox68b968e2022-02-20 17:31:50 +0000141
142 const actualRequiresAuth = Object.keys(requiresAuth);
143 actualRequiresAuth.sort();
144 const actualDoesNotRequireAuth = Object.keys(doesNotRequireAuth);
145 actualDoesNotRequireAuth.sort();
146
147 const shouldRequireAutoAuth = [
Frank Borden79448112022-04-12 16:59:32 +0200148 'handleAgreementsRoute',
149 'handleChangeEditRoute',
150 'handleCreateGroupRoute',
151 'handleCreateProjectRoute',
152 'handleDiffEditRoute',
153 'handleGroupAuditLogRoute',
154 'handleGroupInfoRoute',
Ben Rohlfs8f064482023-01-27 09:52:51 +0100155 'handleGroupListRoute',
Frank Borden79448112022-04-12 16:59:32 +0200156 'handleGroupMembersRoute',
157 'handleGroupRoute',
158 'handleGroupSelfRedirectRoute',
159 'handleNewAgreementsRoute',
Frank Borden79448112022-04-12 16:59:32 +0200160 'handlePluginListFilterRoute',
Frank Borden79448112022-04-12 16:59:32 +0200161 'handlePluginListRoute',
162 'handleRepoCommandsRoute',
Ben Rohlfsc35ed182022-11-25 12:02:52 +0000163 'handleRepoEditFileRoute',
Frank Borden79448112022-04-12 16:59:32 +0200164 'handleSettingsLegacyRoute',
165 'handleSettingsRoute',
paladox68b968e2022-02-20 17:31:50 +0000166 ];
167 assert.deepEqual(actualRequiresAuth, shouldRequireAutoAuth);
168
169 const unauthenticatedHandlers = [
Ben Rohlfsb57102f2023-01-27 12:26:56 +0100170 'handleBranchListRoute',
Frank Borden79448112022-04-12 16:59:32 +0200171 'handleChangeIdQueryRoute',
172 'handleChangeNumberLegacyRoute',
173 'handleChangeRoute',
174 'handleCommentRoute',
175 'handleCommentsRoute',
176 'handleDiffRoute',
177 'handleDefaultRoute',
178 'handleChangeLegacyRoute',
179 'handleDocumentationRedirectRoute',
180 'handleDocumentationSearchRoute',
181 'handleDocumentationSearchRedirectRoute',
182 'handleLegacyLinenum',
183 'handleImproperlyEncodedPlusRoute',
184 'handlePassThroughRoute',
185 'handleProjectDashboardRoute',
186 'handleLegacyProjectDashboardRoute',
187 'handleProjectsOldRoute',
188 'handleRepoAccessRoute',
189 'handleRepoDashboardsRoute',
190 'handleRepoGeneralRoute',
Ben Rohlfs26306a42023-01-27 12:13:50 +0100191 'handleRepoListRoute',
Frank Borden79448112022-04-12 16:59:32 +0200192 'handleRepoRoute',
193 'handleQueryLegacySuffixRoute',
194 'handleQueryRoute',
195 'handleRegisterRoute',
Ben Rohlfsb57102f2023-01-27 12:26:56 +0100196 'handleTagListRoute',
Frank Borden79448112022-04-12 16:59:32 +0200197 'handlePluginScreen',
paladox68b968e2022-02-20 17:31:50 +0000198 ];
199
200 // Handler names that check authentication themselves, and thus don't need
201 // it performed for them.
202 const selfAuthenticatingHandlers = [
Frank Borden79448112022-04-12 16:59:32 +0200203 'handleDashboardRoute',
204 'handleCustomDashboardRoute',
205 'handleRootRoute',
paladox68b968e2022-02-20 17:31:50 +0000206 ];
207
208 const shouldNotRequireAuth = unauthenticatedHandlers.concat(
209 selfAuthenticatingHandlers
210 );
211 shouldNotRequireAuth.sort();
212 assert.deepEqual(actualDoesNotRequireAuth, shouldNotRequireAuth);
213 });
214
Frank Borden79448112022-04-12 16:59:32 +0200215 test('redirectIfNotLoggedIn while logged in', () => {
paladox68b968e2022-02-20 17:31:50 +0000216 stubRestApi('getLoggedIn').returns(Promise.resolve(true));
Frank Borden79448112022-04-12 16:59:32 +0200217 const redirectStub = sinon.stub(router, 'redirectToLogin');
Ben Rohlfs1da030b2023-01-31 13:09:53 +0100218 return router.redirectIfNotLoggedIn('somepath').then(() => {
paladox68b968e2022-02-20 17:31:50 +0000219 assert.isFalse(redirectStub.called);
220 });
221 });
222
Frank Borden79448112022-04-12 16:59:32 +0200223 test('redirectIfNotLoggedIn while logged out', () => {
paladox68b968e2022-02-20 17:31:50 +0000224 stubRestApi('getLoggedIn').returns(Promise.resolve(false));
Frank Borden79448112022-04-12 16:59:32 +0200225 const redirectStub = sinon.stub(router, 'redirectToLogin');
paladox68b968e2022-02-20 17:31:50 +0000226 return new Promise(resolve => {
Frank Borden79448112022-04-12 16:59:32 +0200227 router
Ben Rohlfs1da030b2023-01-31 13:09:53 +0100228 .redirectIfNotLoggedIn('somepath')
paladox68b968e2022-02-20 17:31:50 +0000229 .then(() => {
230 assert.isTrue(false, 'Should never execute');
231 })
232 .catch(() => {
233 assert.isTrue(redirectStub.calledOnce);
234 resolve(Promise.resolve());
235 });
236 });
237 });
238
paladox68b968e2022-02-20 17:31:50 +0000239 suite('param normalization', () => {
Frank Borden79448112022-04-12 16:59:32 +0200240 suite('normalizePatchRangeParams', () => {
paladox68b968e2022-02-20 17:31:50 +0000241 test('range n..n normalizes to n', () => {
242 const params: PatchRangeParams = {
243 basePatchNum: 4 as BasePatchSetNum,
Ben Rohlfsabaeacf2022-05-27 15:24:22 +0200244 patchNum: 4 as RevisionPatchSetNum,
paladox68b968e2022-02-20 17:31:50 +0000245 };
Ben Rohlfs2a431342022-09-16 10:47:01 +0200246 router.normalizePatchRangeParams(params);
Ben Rohlfs58267b72022-05-27 15:59:18 +0200247 assert.equal(params.basePatchNum, PARENT);
Ben Rohlfsabaeacf2022-05-27 15:24:22 +0200248 assert.equal(params.patchNum, 4 as RevisionPatchSetNum);
paladox68b968e2022-02-20 17:31:50 +0000249 });
250
251 test('range n.. normalizes to n', () => {
252 const params: PatchRangeParams = {basePatchNum: 4 as BasePatchSetNum};
Ben Rohlfs2a431342022-09-16 10:47:01 +0200253 router.normalizePatchRangeParams(params);
Ben Rohlfs58267b72022-05-27 15:59:18 +0200254 assert.equal(params.basePatchNum, PARENT);
Ben Rohlfsabaeacf2022-05-27 15:24:22 +0200255 assert.equal(params.patchNum, 4 as RevisionPatchSetNum);
paladox68b968e2022-02-20 17:31:50 +0000256 });
257 });
258 });
259
Ben Rohlfsf6657362023-04-12 14:01:35 +0200260 suite('navigation blockers', () => {
261 let clock: sinon.SinonFakeTimers;
262 let redirectStub: sinon.SinonStub;
263 let urlPromise: MockPromise<string>;
264
265 setup(() => {
266 stubRestApi('setInProjectLookup');
267 urlPromise = mockPromise<string>();
268 redirectStub = sinon
269 .stub(router, 'redirect')
270 .callsFake(urlPromise.resolve);
271 router._testOnly_startRouter();
272 clock = sinon.useFakeTimers();
273 });
274
275 test('no blockers: normal redirect', async () => {
276 router.page.show('/settings/agreements');
277 const url = await urlPromise;
278 assert.isTrue(redirectStub.calledOnce);
279 assert.equal(url, '/settings/#Agreements');
280 });
281
282 test('redirect blocked', async () => {
283 const firstAlertPromise = mockPromise<Event>();
284 addListenerForTest(document, 'show-alert', firstAlertPromise.resolve);
285
286 router.blockNavigation('a good reason');
287 router.page.show('/settings/agreements');
288
289 const firstAlert = (await firstAlertPromise) as CustomEvent;
290 assert.equal(
291 firstAlert.detail.message,
292 'Waiting 1 second for navigation blockers to resolve ...'
293 );
294
295 const secondAlertPromise = mockPromise<Event>();
296 addListenerForTest(document, 'show-alert', secondAlertPromise.resolve);
297
298 clock.tick(2000);
299
300 const secondAlert = (await secondAlertPromise) as CustomEvent;
301 assert.equal(
302 secondAlert.detail.message,
303 'Navigation is blocked by: a good reason'
304 );
305
306 assert.isFalse(redirectStub.called);
307 });
308
309 test('redirect blocked, but resolved within one second', async () => {
310 const firstAlertPromise = mockPromise<Event>();
311 addListenerForTest(document, 'show-alert', firstAlertPromise.resolve);
312
313 router.blockNavigation('a good reason');
314 router.page.show('/settings/agreements');
315
316 const firstAlert = (await firstAlertPromise) as CustomEvent;
317 assert.equal(
318 firstAlert.detail.message,
319 'Waiting 1 second for navigation blockers to resolve ...'
320 );
321
322 const secondAlertPromise = mockPromise<Event>();
323 addListenerForTest(document, 'show-alert', secondAlertPromise.resolve);
324
325 clock.tick(500);
326 router.releaseNavigation('a good reason');
327 clock.tick(2000);
328
329 await urlPromise;
330 assert.isTrue(redirectStub.calledOnce);
331 });
332 });
333
paladox68b968e2022-02-20 17:31:50 +0000334 suite('route handlers', () => {
335 let redirectStub: sinon.SinonStub;
Ben Rohlfs2586e572022-09-16 12:55:37 +0200336 let setStateStub: sinon.SinonStub;
paladox68b968e2022-02-20 17:31:50 +0000337 let handlePassThroughRoute: sinon.SinonStub;
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100338 let redirectToLoginStub: sinon.SinonStub;
paladox68b968e2022-02-20 17:31:50 +0000339
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100340 async function checkUrlToState<T extends ViewState>(
341 url: string,
342 state: T | AppElementJustRegisteredParams
paladox68b968e2022-02-20 17:31:50 +0000343 ) {
Ben Rohlfsadbde152023-01-24 19:23:32 +0100344 setStateStub.reset();
345 router.page.show(url);
346 await waitUntilCalled(setStateStub, 'setState');
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100347 assert.isTrue(setStateStub.calledOnce);
Ben Rohlfsadbde152023-01-24 19:23:32 +0100348 assert.deepEqual(setStateStub.lastCall.firstArg, state);
349 }
350
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100351 async function checkRedirect(fromUrl: string, toUrl: string) {
352 redirectStub.reset();
353 router.page.show(fromUrl);
354 await waitUntilCalled(redirectStub, 'redirect');
355 assert.isTrue(redirectStub.calledOnce);
356 assert.isFalse(setStateStub.called);
357 assert.equal(redirectStub.lastCall.firstArg, toUrl);
358 }
359
360 async function checkRedirectToLogin(fromUrl: string, toUrl: string) {
361 redirectToLoginStub.reset();
362 router.page.show(fromUrl);
363 await waitUntilCalled(redirectToLoginStub, 'redirectToLogin');
364 assert.isTrue(redirectToLoginStub.calledOnce);
365 assert.isFalse(redirectStub.called);
366 assert.isFalse(setStateStub.called);
367 assert.equal(redirectToLoginStub.lastCall.firstArg, toUrl);
368 }
369
Ben Rohlfsadbde152023-01-24 19:23:32 +0100370 async function checkUrlNotMatched(url: string) {
371 handlePassThroughRoute.reset();
372 router.page.show(url);
373 await waitUntilCalled(handlePassThroughRoute, 'handlePassThroughRoute');
374 }
375
paladox68b968e2022-02-20 17:31:50 +0000376 setup(() => {
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100377 stubRestApi('setInProjectLookup');
Frank Borden79448112022-04-12 16:59:32 +0200378 redirectStub = sinon.stub(router, 'redirect');
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100379 redirectToLoginStub = sinon.stub(router, 'redirectToLogin');
Ben Rohlfs2586e572022-09-16 12:55:37 +0200380 setStateStub = sinon.stub(router, 'setState');
Frank Borden79448112022-04-12 16:59:32 +0200381 handlePassThroughRoute = sinon.stub(router, 'handlePassThroughRoute');
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100382 router._testOnly_startRouter();
paladox68b968e2022-02-20 17:31:50 +0000383 });
384
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100385 test('LEGACY_PROJECT_DASHBOARD', async () => {
386 // LEGACY_PROJECT_DASHBOARD: /^\/projects\/(.+),dashboards\/(.+)/,
387 await checkRedirect(
388 '/projects/gerrit/project,dashboards/dashboard:main',
paladox68b968e2022-02-20 17:31:50 +0000389 '/p/gerrit/project/+/dashboard/dashboard:main'
390 );
391 });
392
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100393 test('AGREEMENTS', async () => {
394 // AGREEMENTS: /^\/settings\/agreements\/?/,
395 await checkRedirect('/settings/agreements', '/settings/#Agreements');
paladox68b968e2022-02-20 17:31:50 +0000396 });
397
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100398 test('NEW_AGREEMENTS', async () => {
399 // NEW_AGREEMENTS: /^\/settings\/new-agreement\/?/,
400 await checkUrlToState('/settings/new-agreement', {
401 view: GerritView.AGREEMENTS,
402 });
403 await checkUrlToState('/settings/new-agreement/', {
404 view: GerritView.AGREEMENTS,
paladox68b968e2022-02-20 17:31:50 +0000405 });
406 });
407
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100408 test('SETTINGS', async () => {
409 // SETTINGS: /^\/settings\/?/,
410 // SETTINGS_LEGACY: /^\/settings\/VE\/(\S+)/,
411 await checkUrlToState('/settings', {view: GerritView.SETTINGS});
412 await checkUrlToState('/settings/', {view: GerritView.SETTINGS});
413 await checkUrlToState('/settings/VE/asdf', {
Ben Rohlfs8163cd42022-08-18 16:04:36 +0200414 view: GerritView.SETTINGS,
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100415 emailToken: 'asdf',
paladox68b968e2022-02-20 17:31:50 +0000416 });
Ben Rohlfsc02facb2023-01-27 18:46:02 +0100417 await checkUrlToState('/settings/VE/asdf%40qwer', {
Ben Rohlfs8163cd42022-08-18 16:04:36 +0200418 view: GerritView.SETTINGS,
Ben Rohlfsc02facb2023-01-27 18:46:02 +0100419 emailToken: 'asdf@qwer',
paladox68b968e2022-02-20 17:31:50 +0000420 });
421 });
422
Frank Borden79448112022-04-12 16:59:32 +0200423 test('handleDefaultRoute on first load', () => {
paladox68b968e2022-02-20 17:31:50 +0000424 const spy = sinon.spy();
425 addListenerForTest(document, 'page-error', spy);
Frank Borden79448112022-04-12 16:59:32 +0200426 router.handleDefaultRoute();
paladox68b968e2022-02-20 17:31:50 +0000427 assert.isTrue(spy.calledOnce);
428 assert.equal(spy.lastCall.args[0].detail.response.status, 404);
429 });
430
Frank Borden79448112022-04-12 16:59:32 +0200431 test('handleDefaultRoute after internal navigation', () => {
paladox68b968e2022-02-20 17:31:50 +0000432 let onExit: Function | null = null;
433 const onRegisteringExit = (
434 _match: string | RegExp,
435 _onExit: Function
436 ) => {
437 onExit = _onExit;
438 };
Ben Rohlfs1da030b2023-01-31 13:09:53 +0100439 sinon.stub(page, 'registerExitRoute').callsFake(onRegisteringExit);
paladox68b968e2022-02-20 17:31:50 +0000440 sinon.stub(page, 'start');
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100441 router._testOnly_startRouter();
paladox68b968e2022-02-20 17:31:50 +0000442
Frank Borden79448112022-04-12 16:59:32 +0200443 router.handleDefaultRoute();
paladox68b968e2022-02-20 17:31:50 +0000444
445 onExit!('', () => {}); // we left page;
446
Frank Borden79448112022-04-12 16:59:32 +0200447 router.handleDefaultRoute();
paladox68b968e2022-02-20 17:31:50 +0000448 assert.isTrue(handlePassThroughRoute.calledOnce);
449 });
450
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100451 test('IMPROPERLY_ENCODED_PLUS', async () => {
452 // IMPROPERLY_ENCODED_PLUS: /^\/c\/(.+)\/ \/(.+)$/,
453 await checkRedirect('/c/repo/ /42', '/c/repo/+/42');
454 await checkRedirect('/c/repo/%20/42', '/c/repo/+/42');
455 await checkRedirect('/c/repo/ /42#foo', '/c/repo/+/42#foo');
paladox68b968e2022-02-20 17:31:50 +0000456 });
457
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100458 test('QUERY', async () => {
Ben Rohlfsd47f9b72023-02-01 15:46:09 +0100459 // QUERY: /^\/q\/(.+?)(,(\d+))?$/,
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100460 await checkUrlToState('/q/asdf', {
461 ...createSearchViewState(),
462 query: 'asdf',
463 });
464 await checkUrlToState('/q/project:foo/bar/baz', {
465 ...createSearchViewState(),
paladox68b968e2022-02-20 17:31:50 +0000466 query: 'project:foo/bar/baz',
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100467 });
468 await checkUrlToState('/q/asdf,123', {
469 ...createSearchViewState(),
470 query: 'asdf',
paladox68b968e2022-02-20 17:31:50 +0000471 offset: '123',
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100472 });
Ben Rohlfsd47f9b72023-02-01 15:46:09 +0100473 await checkUrlToState('/q/asdf,qwer', {
474 ...createSearchViewState(),
475 query: 'asdf,qwer',
476 });
477 await checkUrlToState('/q/asdf,qwer,123', {
478 ...createSearchViewState(),
479 query: 'asdf,qwer',
480 offset: '123',
481 });
paladox68b968e2022-02-20 17:31:50 +0000482 });
483
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100484 test('QUERY_LEGACY_SUFFIX', async () => {
485 // QUERY_LEGACY_SUFFIX: /^\/q\/.+,n,z$/,
486 await checkRedirect('/q/foo+bar,n,z', '/q/foo+bar');
paladox68b968e2022-02-20 17:31:50 +0000487 });
488
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100489 test('CHANGE_ID_QUERY', async () => {
490 // CHANGE_ID_QUERY: /^\/id\/(I[0-9a-f]{40})$/,
491 await checkUrlToState('/id/I0123456789abcdef0123456789abcdef01234567', {
492 ...createSearchViewState(),
paladox68b968e2022-02-20 17:31:50 +0000493 query: 'I0123456789abcdef0123456789abcdef01234567',
paladox68b968e2022-02-20 17:31:50 +0000494 });
495 });
496
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100497 test('REGISTER', async () => {
498 // REGISTER: /^\/register(\/.*)?$/,
499 await checkUrlToState('/register/foo/bar', {
500 justRegistered: true,
501 });
502 assert.isTrue(redirectStub.calledWithExactly('/foo/bar'));
503
504 await checkUrlToState('/register', {
505 justRegistered: true,
506 });
507 assert.isTrue(redirectStub.calledWithExactly('/'));
508
509 await checkUrlToState('/register/register', {
510 justRegistered: true,
511 });
512 assert.isTrue(redirectStub.calledWithExactly('/'));
513 });
514
515 suite('ROOT', () => {
paladox68b968e2022-02-20 17:31:50 +0000516 test('closes for closeAfterLogin', () => {
Ben Rohlfs1da030b2023-01-31 13:09:53 +0100517 const ctx = {
518 querystring: 'closeAfterLogin',
519 canonicalPath: '',
520 } as PageContext;
paladox68b968e2022-02-20 17:31:50 +0000521 const closeStub = sinon.stub(window, 'close');
Ben Rohlfs5940c532022-09-20 09:19:50 +0200522 const result = router.handleRootRoute(ctx);
paladox68b968e2022-02-20 17:31:50 +0000523 assert.isNotOk(result);
524 assert.isTrue(closeStub.called);
525 assert.isFalse(redirectStub.called);
526 });
527
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100528 test('ROOT logged in', async () => {
529 stubRestApi('getLoggedIn').resolves(true);
530 await checkRedirect('/', '/dashboard/self');
paladox68b968e2022-02-20 17:31:50 +0000531 });
532
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100533 test('ROOT not logged in', async () => {
534 stubRestApi('getLoggedIn').resolves(false);
535 await checkRedirect('/', '/q/status:open+-is:wip');
paladox68b968e2022-02-20 17:31:50 +0000536 });
537
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100538 suite('ROOT GWT hash-path URLs', () => {
539 test('ROOT hash-path URLs', async () => {
540 await checkRedirect('/#/foo/bar/baz', '/foo/bar/baz');
paladox68b968e2022-02-20 17:31:50 +0000541 });
542
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100543 test('ROOT hash-path URLs w/o leading slash', async () => {
544 await checkRedirect('/#foo/bar/baz', '/foo/bar/baz');
paladox68b968e2022-02-20 17:31:50 +0000545 });
546
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100547 test('ROOT normalizes "/ /" in hash to "/+/"', async () => {
548 await checkRedirect('/#/foo/bar/+/123/4', '/foo/bar/+/123/4');
paladox68b968e2022-02-20 17:31:50 +0000549 });
550
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100551 test('ROOT prepends baseurl to hash-path', async () => {
paladox68b968e2022-02-20 17:31:50 +0000552 stubBaseUrl('/baz');
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100553 await checkRedirect('/#/foo/bar', '/baz/foo/bar');
paladox68b968e2022-02-20 17:31:50 +0000554 });
555
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100556 test('ROOT normalizes /VE/ settings hash-paths', async () => {
557 await checkRedirect('/#/VE/foo/bar', '/settings/VE/foo/bar');
paladox68b968e2022-02-20 17:31:50 +0000558 });
559
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100560 test('ROOT does not drop "inner hashes"', async () => {
561 await checkRedirect('/#/foo/bar#baz', '/foo/bar#baz');
paladox68b968e2022-02-20 17:31:50 +0000562 });
563 });
564 });
565
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100566 suite('DASHBOARD', () => {
567 test('DASHBOARD own dashboard but signed out redirects to login', async () => {
568 stubRestApi('getLoggedIn').resolves(false);
569 await checkRedirectToLogin('/dashboard/seLF', '/dashboard/seLF');
paladox68b968e2022-02-20 17:31:50 +0000570 });
571
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100572 test('DASHBOARD non-self dashboard but signed out redirects', async () => {
573 stubRestApi('getLoggedIn').resolves(false);
574 await checkRedirect('/dashboard/foo', '/q/owner:foo');
paladox68b968e2022-02-20 17:31:50 +0000575 });
576
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100577 test('DASHBOARD', async () => {
578 // DASHBOARD: /^\/dashboard\/(.+)$/,
579 await checkUrlToState('/dashboard/foo', {
580 ...createDashboardViewState(),
581 user: 'foo',
paladox68b968e2022-02-20 17:31:50 +0000582 });
583 });
584 });
585
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100586 suite('CUSTOM_DASHBOARD', () => {
587 test('CUSTOM_DASHBOARD no user specified', async () => {
588 await checkRedirect('/dashboard/', '/dashboard/self');
paladox68b968e2022-02-20 17:31:50 +0000589 });
590
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100591 test('CUSTOM_DASHBOARD', async () => {
592 // CUSTOM_DASHBOARD: /^\/dashboard\/?$/,
593 await checkUrlToState('/dashboard?title=Custom Dashboard&a=b&d=e', {
594 ...createDashboardViewState(),
Kamil Musin3e3da5a2023-10-18 16:19:13 +0200595 type: DashboardType.CUSTOM,
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100596 sections: [
597 {name: 'a', query: 'b'},
598 {name: 'd', query: 'e'},
599 ],
600 title: 'Custom Dashboard',
paladox68b968e2022-02-20 17:31:50 +0000601 });
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100602 await checkUrlToState('/dashboard?a=b&c&d=&=e&foreach=is:open', {
603 ...createDashboardViewState(),
Kamil Musin3e3da5a2023-10-18 16:19:13 +0200604 type: DashboardType.CUSTOM,
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100605 sections: [{name: 'a', query: 'is:open b'}],
606 title: 'Custom Dashboard',
Ben Rohlfs82b46492022-09-20 09:13:56 +0200607 });
paladox68b968e2022-02-20 17:31:50 +0000608 });
609 });
610
611 suite('group routes', () => {
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100612 test('GROUP_INFO', async () => {
613 // GROUP_INFO: /^\/admin\/groups\/(?:uuid-)?(.+),info$/,
614 await checkRedirect('/admin/groups/1234,info', '/admin/groups/1234');
paladox68b968e2022-02-20 17:31:50 +0000615 });
616
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100617 test('GROUP_AUDIT_LOG', async () => {
618 // GROUP_AUDIT_LOG: /^\/admin\/groups\/(?:uuid-)?(.+),audit-log$/,
619 await checkUrlToState('/admin/groups/1234,audit-log', {
620 ...createGroupViewState(),
paladox68b968e2022-02-20 17:31:50 +0000621 detail: GroupDetailView.LOG,
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100622 groupId: '1234',
paladox68b968e2022-02-20 17:31:50 +0000623 });
624 });
625
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100626 test('GROUP_MEMBERS', async () => {
627 // GROUP_MEMBERS: /^\/admin\/groups\/(?:uuid-)?(.+),members$/,
628 await checkUrlToState('/admin/groups/1234,members', {
629 ...createGroupViewState(),
paladox68b968e2022-02-20 17:31:50 +0000630 detail: GroupDetailView.MEMBERS,
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100631 groupId: '1234',
paladox68b968e2022-02-20 17:31:50 +0000632 });
633 });
634
Ben Rohlfs8f064482023-01-27 09:52:51 +0100635 test('GROUP_LIST', async () => {
636 // GROUP_LIST: /^\/admin\/groups(\/q\/filter:(.*?))?(,(\d+))?(\/)?$/,
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100637
Ben Rohlfsadbde152023-01-24 19:23:32 +0100638 const defaultState: AdminViewState = {
paladox68b968e2022-02-20 17:31:50 +0000639 view: GerritView.ADMIN,
Ben Rohlfsdcb88ac2022-09-12 11:50:26 +0200640 adminView: AdminChildView.GROUPS,
Ben Rohlfsadbde152023-01-24 19:23:32 +0100641 offset: '0',
paladox68b968e2022-02-20 17:31:50 +0000642 openCreateModal: false,
Ben Rohlfs1da030b2023-01-31 13:09:53 +0100643 filter: '',
Ben Rohlfsadbde152023-01-24 19:23:32 +0100644 };
paladox68b968e2022-02-20 17:31:50 +0000645
Ben Rohlfsadbde152023-01-24 19:23:32 +0100646 await checkUrlToState('/admin/groups', defaultState);
647 await checkUrlToState('/admin/groups/', defaultState);
648 await checkUrlToState('/admin/groups#create', {
649 ...defaultState,
paladox68b968e2022-02-20 17:31:50 +0000650 openCreateModal: true,
651 });
Ben Rohlfs8f064482023-01-27 09:52:51 +0100652 await checkUrlToState('/admin/groups,42', {
Ben Rohlfsadbde152023-01-24 19:23:32 +0100653 ...defaultState,
Ben Rohlfs8f064482023-01-27 09:52:51 +0100654 offset: '42',
paladox68b968e2022-02-20 17:31:50 +0000655 });
Ben Rohlfs8f064482023-01-27 09:52:51 +0100656 // #create is ignored when there is an offset
657 await checkUrlToState('/admin/groups,42#create', {
Ben Rohlfsadbde152023-01-24 19:23:32 +0100658 ...defaultState,
Ben Rohlfs8f064482023-01-27 09:52:51 +0100659 offset: '42',
paladox68b968e2022-02-20 17:31:50 +0000660 });
Ben Rohlfsadbde152023-01-24 19:23:32 +0100661
Ben Rohlfs8f064482023-01-27 09:52:51 +0100662 await checkUrlToState('/admin/groups/q/filter:foo', {
Ben Rohlfsadbde152023-01-24 19:23:32 +0100663 ...defaultState,
Ben Rohlfs8f064482023-01-27 09:52:51 +0100664 filter: 'foo',
Ben Rohlfsadbde152023-01-24 19:23:32 +0100665 });
Ben Rohlfs8f064482023-01-27 09:52:51 +0100666 await checkUrlToState('/admin/groups/q/filter:foo/%2F%20%2525%252F', {
Ben Rohlfsadbde152023-01-24 19:23:32 +0100667 ...defaultState,
Ben Rohlfs8f064482023-01-27 09:52:51 +0100668 filter: 'foo// %/',
669 });
670 await checkUrlToState('/admin/groups/q/filter:foo,42', {
671 ...defaultState,
672 filter: 'foo',
673 offset: '42',
Ben Rohlfsadbde152023-01-24 19:23:32 +0100674 });
675 // #create is ignored when filtering
Ben Rohlfs8f064482023-01-27 09:52:51 +0100676 await checkUrlToState('/admin/groups/q/filter:foo,42#create', {
Ben Rohlfsadbde152023-01-24 19:23:32 +0100677 ...defaultState,
Ben Rohlfs8f064482023-01-27 09:52:51 +0100678 filter: 'foo',
679 offset: '42',
Ben Rohlfsadbde152023-01-24 19:23:32 +0100680 });
paladox68b968e2022-02-20 17:31:50 +0000681 });
682
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100683 test('GROUP', async () => {
684 // GROUP: /^\/admin\/groups\/(?:uuid-)?([^,]+)$/,
685 await checkUrlToState('/admin/groups/4321', {
686 ...createGroupViewState(),
687 groupId: '4321',
paladox68b968e2022-02-20 17:31:50 +0000688 });
689 });
690 });
691
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100692 suite('REPO*', () => {
693 test('PROJECT_OLD', async () => {
694 // PROJECT_OLD: /^\/admin\/(projects)\/?(.+)?$/,
695 await checkRedirect('/admin/projects/', '/admin/repos/');
696 await checkRedirect('/admin/projects/test', '/admin/repos/test');
697 await checkRedirect(
698 '/admin/projects/test,branches',
paladox68b968e2022-02-20 17:31:50 +0000699 '/admin/repos/test,branches'
700 );
701 });
702
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100703 test('REPO', async () => {
704 // REPO: /^\/admin\/repos\/([^,]+)$/,
705 await checkRedirect('/admin/repos/test', '/admin/repos/test,general');
paladox68b968e2022-02-20 17:31:50 +0000706 });
707
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100708 test('REPO_GENERAL', async () => {
709 // REPO_GENERAL: /^\/admin\/repos\/(.+),general$/,
710 await checkUrlToState('/admin/repos/4321,general', {
711 ...createRepoViewState(),
Ben Rohlfs8163cd42022-08-18 16:04:36 +0200712 detail: RepoDetailView.GENERAL,
paladox68b968e2022-02-20 17:31:50 +0000713 repo: '4321' as RepoName,
714 });
715 });
716
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100717 test('REPO_COMMANDS', async () => {
718 // REPO_COMMANDS: /^\/admin\/repos\/(.+),commands$/,
719 await checkUrlToState('/admin/repos/4321,commands', {
720 ...createRepoViewState(),
Ben Rohlfs8163cd42022-08-18 16:04:36 +0200721 detail: RepoDetailView.COMMANDS,
paladox68b968e2022-02-20 17:31:50 +0000722 repo: '4321' as RepoName,
723 });
724 });
725
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100726 test('REPO_ACCESS', async () => {
727 // REPO_ACCESS: /^\/admin\/repos\/(.+),access$/,
728 await checkUrlToState('/admin/repos/4321,access', {
729 ...createRepoViewState(),
Ben Rohlfs8163cd42022-08-18 16:04:36 +0200730 detail: RepoDetailView.ACCESS,
paladox68b968e2022-02-20 17:31:50 +0000731 repo: '4321' as RepoName,
732 });
733 });
734
Ben Rohlfsb57102f2023-01-27 12:26:56 +0100735 test('BRANCH_LIST', async () => {
736 await checkUrlToState('/admin/repos/4321,branches', {
737 ...createRepoBranchesViewState(),
738 repo: '4321' as RepoName,
paladox68b968e2022-02-20 17:31:50 +0000739 });
Ben Rohlfsb57102f2023-01-27 12:26:56 +0100740 await checkUrlToState('/admin/repos/4321,branches,42', {
741 ...createRepoBranchesViewState(),
742 repo: '4321' as RepoName,
743 offset: '42',
paladox68b968e2022-02-20 17:31:50 +0000744 });
Ben Rohlfsb57102f2023-01-27 12:26:56 +0100745 await checkUrlToState('/admin/repos/4321,branches/q/filter:foo,42', {
746 ...createRepoBranchesViewState(),
747 repo: '4321' as RepoName,
748 offset: '42',
749 filter: 'foo',
paladox68b968e2022-02-20 17:31:50 +0000750 });
Ben Rohlfsb57102f2023-01-27 12:26:56 +0100751 await checkUrlToState('/admin/repos/4321,branches/q/filter:foo', {
752 ...createRepoBranchesViewState(),
753 repo: '4321' as RepoName,
754 filter: 'foo',
755 });
756 await checkUrlToState(
757 '/admin/repos/asdf/%2F%20%2525%252Fqwer,branches/q/filter:foo/%2F%20%2525%252F',
758 {
759 ...createRepoBranchesViewState(),
760 repo: 'asdf// %/qwer' as RepoName,
761 filter: 'foo// %/',
762 }
763 );
paladox68b968e2022-02-20 17:31:50 +0000764 });
765
Ben Rohlfsb57102f2023-01-27 12:26:56 +0100766 test('TAG_LIST', async () => {
767 await checkUrlToState('/admin/repos/4321,tags', {
768 ...createRepoTagsViewState(),
769 repo: '4321' as RepoName,
paladox68b968e2022-02-20 17:31:50 +0000770 });
Ben Rohlfsb57102f2023-01-27 12:26:56 +0100771 await checkUrlToState('/admin/repos/4321,tags,42', {
772 ...createRepoTagsViewState(),
773 repo: '4321' as RepoName,
774 offset: '42',
paladox68b968e2022-02-20 17:31:50 +0000775 });
Ben Rohlfsb57102f2023-01-27 12:26:56 +0100776 await checkUrlToState('/admin/repos/4321,tags/q/filter:foo,42', {
777 ...createRepoTagsViewState(),
778 repo: '4321' as RepoName,
779 offset: '42',
780 filter: 'foo',
paladox68b968e2022-02-20 17:31:50 +0000781 });
Ben Rohlfsb57102f2023-01-27 12:26:56 +0100782 await checkUrlToState('/admin/repos/4321,tags/q/filter:foo', {
783 ...createRepoTagsViewState(),
784 repo: '4321' as RepoName,
785 filter: 'foo',
786 });
787 await checkUrlToState(
788 '/admin/repos/asdf/%2F%20%2525%252Fqwer,tags/q/filter:foo/%2F%20%2525%252F',
789 {
790 ...createRepoTagsViewState(),
791 repo: 'asdf// %/qwer' as RepoName,
792 filter: 'foo// %/',
793 }
794 );
paladox68b968e2022-02-20 17:31:50 +0000795 });
796
Ben Rohlfs26306a42023-01-27 12:13:50 +0100797 test('REPO_LIST', async () => {
798 await checkUrlToState('/admin/repos', {
799 ...createAdminReposViewState(),
paladox68b968e2022-02-20 17:31:50 +0000800 });
Ben Rohlfs26306a42023-01-27 12:13:50 +0100801 await checkUrlToState('/admin/repos/', {
802 ...createAdminReposViewState(),
paladox68b968e2022-02-20 17:31:50 +0000803 });
Ben Rohlfs26306a42023-01-27 12:13:50 +0100804 await checkUrlToState('/admin/repos,42', {
805 ...createAdminReposViewState(),
806 offset: '42',
807 });
808 await checkUrlToState('/admin/repos#create', {
809 ...createAdminReposViewState(),
810 openCreateModal: true,
811 });
812 await checkUrlToState('/admin/repos/q/filter:foo', {
813 ...createAdminReposViewState(),
814 filter: 'foo',
815 });
816 await checkUrlToState('/admin/repos/q/filter:foo/%2F%20%2525%252F', {
817 ...createAdminReposViewState(),
818 filter: 'foo// %/',
819 });
820 await checkUrlToState('/admin/repos/q/filter:foo,42', {
821 ...createAdminReposViewState(),
822 filter: 'foo',
823 offset: '42',
paladox68b968e2022-02-20 17:31:50 +0000824 });
825 });
826 });
827
Ben Rohlfs4591b042023-01-27 09:39:06 +0100828 test('PLUGIN_LIST', async () => {
829 await checkUrlToState('/admin/plugins', {
830 ...createAdminPluginsViewState(),
paladox68b968e2022-02-20 17:31:50 +0000831 });
Ben Rohlfs4591b042023-01-27 09:39:06 +0100832 await checkUrlToState('/admin/plugins/', {
833 ...createAdminPluginsViewState(),
paladox68b968e2022-02-20 17:31:50 +0000834 });
Ben Rohlfs4591b042023-01-27 09:39:06 +0100835 await checkUrlToState('/admin/plugins,42', {
836 ...createAdminPluginsViewState(),
837 offset: '42',
838 });
839 await checkUrlToState('/admin/plugins/q/filter:foo', {
840 ...createAdminPluginsViewState(),
841 filter: 'foo',
842 });
843 await checkUrlToState('/admin/plugins/q/filter:foo%2F%20%2525%252F', {
844 ...createAdminPluginsViewState(),
845 filter: 'foo/ %/',
846 });
847 await checkUrlToState('/admin/plugins/q/filter:foo,42', {
848 ...createAdminPluginsViewState(),
849 offset: '42',
850 filter: 'foo',
851 });
852 await checkUrlToState('/admin/plugins/q/filter:foo,asdf', {
853 ...createAdminPluginsViewState(),
854 filter: 'foo,asdf',
paladox68b968e2022-02-20 17:31:50 +0000855 });
paladox68b968e2022-02-20 17:31:50 +0000856 });
857
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100858 suite('CHANGE* / DIFF*', () => {
859 test('CHANGE_NUMBER_LEGACY', async () => {
860 // CHANGE_NUMBER_LEGACY: /^\/(\d+)\/?/,
861 await checkRedirect('/12345', '/c/12345');
paladox68b968e2022-02-20 17:31:50 +0000862 });
863
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100864 test('CHANGE_LEGACY', async () => {
865 // CHANGE_LEGACY: /^\/c\/(\d+)\/?(.*)$/,
866 stubRestApi('getFromProjectLookup').resolves('project' as RepoName);
867 await checkRedirect('/c/1234', '/c/project/+/1234/');
868 await checkRedirect(
869 '/c/1234/comment/6789',
870 '/c/project/+/1234/comment/6789'
paladox68b968e2022-02-20 17:31:50 +0000871 );
872 });
873
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100874 test('DIFF_LEGACY_LINENUM', async () => {
875 await checkRedirect(
876 '/c/1234/3..8/foo/bar@321',
877 '/c/1234/3..8/foo/bar#321'
878 );
879 await checkRedirect(
880 '/c/1234/3..8/foo/bar@b321',
881 '/c/1234/3..8/foo/bar#b321'
paladox68b968e2022-02-20 17:31:50 +0000882 );
883 });
884
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100885 test('CHANGE', async () => {
886 // CHANGE: /^\/c\/(.+)\/\+\/(\d+)(\/?((-?\d+|edit)(\.\.(\d+|edit))?))?\/?$/,
887 await checkUrlToState('/c/test-project/+/42', {
888 ...createChangeViewState(),
889 basePatchNum: undefined,
890 patchNum: undefined,
paladox68b968e2022-02-20 17:31:50 +0000891 });
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100892 await checkUrlToState('/c/test-project/+/42/7', {
893 ...createChangeViewState(),
894 basePatchNum: PARENT,
895 patchNum: 7,
paladox68b968e2022-02-20 17:31:50 +0000896 });
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100897 await checkUrlToState('/c/test-project/+/42/4..7', {
898 ...createChangeViewState(),
899 basePatchNum: 4,
900 patchNum: 7,
901 });
902 await checkUrlToState(
903 '/c/test-project/+/42/4..7?tab=checks&filter=fff&attempt=1&checksRunsSelected=asdf,qwer&checksResultsFilter=asdf.*qwer',
904 {
905 ...createChangeViewState(),
906 basePatchNum: 4,
907 patchNum: 7,
paladox68b968e2022-02-20 17:31:50 +0000908 attempt: 1,
909 filter: 'fff',
paladox68b968e2022-02-20 17:31:50 +0000910 tab: 'checks',
Ben Rohlfs20fe8e82022-10-14 11:13:10 +0200911 checksRunsSelected: new Set(['asdf', 'qwer']),
Ben Rohlfs209f1412022-09-30 12:25:46 +0200912 checksResultsFilter: 'asdf.*qwer',
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100913 }
914 );
915 });
916
917 test('COMMENTS_TAB', async () => {
918 // COMMENTS_TAB: /^\/c\/(.+)\/\+\/(\d+)\/comments(?:\/)?(\w+)?\/?$/,
919 await checkUrlToState(
920 '/c/gerrit/+/264833/comments/00049681_f34fd6a9/',
921 {
922 ...createChangeViewState(),
923 repo: 'gerrit' as RepoName,
924 changeNum: 264833 as NumericChangeId,
925 commentId: '00049681_f34fd6a9' as UrlEncodedCommentId,
926 view: GerritView.CHANGE,
927 childView: ChangeChildView.OVERVIEW,
928 }
929 );
paladox68b968e2022-02-20 17:31:50 +0000930 });
931
Frank Borden79448112022-04-12 16:59:32 +0200932 suite('handleDiffRoute', () => {
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100933 test('DIFF', async () => {
934 // DIFF: /^\/c\/(.+)\/\+\/(\d+)(\/((-?\d+|edit)(\.\.(\d+|edit))?(\/(.+))))\/?$/,
935 await checkUrlToState('/c/test-project/+/42/4..7/foo/bar/baz#b44', {
936 ...createDiffViewState(),
paladox68b968e2022-02-20 17:31:50 +0000937 basePatchNum: 4 as BasePatchSetNum,
938 patchNum: 7 as RevisionPatchSetNum,
Ben Rohlfsecc67992022-12-16 10:10:16 +0100939 diffView: {
940 path: 'foo/bar/baz',
941 lineNum: 44,
942 leftSide: true,
943 },
paladox68b968e2022-02-20 17:31:50 +0000944 });
paladox68b968e2022-02-20 17:31:50 +0000945 });
946
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100947 test('COMMENT base..1', async () => {
Ben Rohlfs196dc722022-12-20 18:01:33 +0100948 const change: ParsedChangeInfo = createParsedChange();
949 const repo = change.project;
950 const changeNum = change._number;
951 const ps = 1 as RevisionPatchSetNum;
952 const line = 23;
953 const id = '00049681_f34fd6a9' as UrlEncodedCommentId;
954 stubRestApi('getChangeDetail').resolves(change);
955 stubRestApi('getDiffComments').resolves({
956 filepath: [{...createComment(), id, patch_set: ps, line}],
957 });
958
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100959 await checkRedirect(
960 `/c/${repo}/+/${changeNum}/comment/${id}/`,
Ben Rohlfs196dc722022-12-20 18:01:33 +0100961 `/c/${repo}/+/${changeNum}/${ps}/filepath#${line}`
962 );
963 });
964
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100965 test('COMMENT 1..2', async () => {
Ben Rohlfs196dc722022-12-20 18:01:33 +0100966 const change: ParsedChangeInfo = {
967 ...createParsedChange(),
968 revisions: {
969 abc: createRevision(1),
970 def: createRevision(2),
971 },
972 };
973 const repo = change.project;
974 const changeNum = change._number;
975 const ps = 1 as RevisionPatchSetNum;
976 const line = 23;
977 const id = '00049681_f34fd6a9' as UrlEncodedCommentId;
978
979 stubRestApi('getChangeDetail').resolves(change);
980 stubRestApi('getDiffComments').resolves({
981 filepath: [{...createComment(), id, patch_set: ps, line}],
982 });
983 const diffStub = stubRestApi('getDiff');
984
Ben Rohlfs196dc722022-12-20 18:01:33 +0100985 // If getDiff() returns a diff with changes, then we will compare
986 // the patchset of the comment (1) against latest (2).
987 diffStub.onFirstCall().resolves(createDiff());
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100988 await checkRedirect(
989 `/c/${repo}/+/${changeNum}/comment/${id}/`,
Ben Rohlfs196dc722022-12-20 18:01:33 +0100990 `/c/${repo}/+/${changeNum}/${ps}..2/filepath#b${line}`
991 );
992
993 // If getDiff() returns an unchanged diff, then we will compare
994 // the patchset of the comment (1) against base.
995 diffStub.onSecondCall().resolves({
996 ...createDiff(),
997 content: [],
998 });
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100999 await checkRedirect(
1000 `/c/${repo}/+/${changeNum}/comment/${id}/`,
Ben Rohlfs196dc722022-12-20 18:01:33 +01001001 `/c/${repo}/+/${changeNum}/${ps}/filepath#${line}`
paladox68b968e2022-02-20 17:31:50 +00001002 );
1003 });
paladox68b968e2022-02-20 17:31:50 +00001004 });
1005
Ben Rohlfsf27798d2023-01-26 12:45:21 +01001006 test('DIFF_EDIT', async () => {
1007 // DIFF_EDIT: /^\/c\/(.+)\/\+\/(\d+)\/(\d+|edit)\/(.+),edit(#\d+)?$/,
1008 await checkUrlToState('/c/foo/bar/+/1234/3/foo/bar/baz,edit', {
1009 ...createEditViewState(),
Ben Rohlfsbfc688b2022-10-21 12:38:37 +02001010 repo: 'foo/bar' as RepoName,
paladox68b968e2022-02-20 17:31:50 +00001011 changeNum: 1234 as NumericChangeId,
Ben Rohlfsecc67992022-12-16 10:10:16 +01001012 view: GerritView.CHANGE,
1013 childView: ChangeChildView.EDIT,
Ben Rohlfsabaeacf2022-05-27 15:24:22 +02001014 patchNum: 3 as RevisionPatchSetNum,
Ben Rohlfsecc67992022-12-16 10:10:16 +01001015 editView: {path: 'foo/bar/baz', lineNum: 0},
Ben Rohlfsf27798d2023-01-26 12:45:21 +01001016 });
1017 await checkUrlToState('/c/foo/bar/+/1234/3/foo/bar/baz,edit#4', {
1018 ...createEditViewState(),
Ben Rohlfsbfc688b2022-10-21 12:38:37 +02001019 repo: 'foo/bar' as RepoName,
paladox68b968e2022-02-20 17:31:50 +00001020 changeNum: 1234 as NumericChangeId,
Ben Rohlfsecc67992022-12-16 10:10:16 +01001021 view: GerritView.CHANGE,
1022 childView: ChangeChildView.EDIT,
Ben Rohlfsabaeacf2022-05-27 15:24:22 +02001023 patchNum: 3 as RevisionPatchSetNum,
Ben Rohlfsecc67992022-12-16 10:10:16 +01001024 editView: {path: 'foo/bar/baz', lineNum: 4},
Ben Rohlfsf27798d2023-01-26 12:45:21 +01001025 });
paladox68b968e2022-02-20 17:31:50 +00001026 });
1027
Ben Rohlfsf27798d2023-01-26 12:45:21 +01001028 test('CHANGE_EDIT', async () => {
1029 // CHANGE_EDIT: /^\/c\/(.+)\/\+\/(\d+)(\/(\d+))?,edit\/?$/,
1030 await checkUrlToState('/c/foo/bar/+/1234/3,edit', {
1031 ...createChangeViewState(),
Ben Rohlfsbfc688b2022-10-21 12:38:37 +02001032 repo: 'foo/bar' as RepoName,
paladox68b968e2022-02-20 17:31:50 +00001033 changeNum: 1234 as NumericChangeId,
1034 view: GerritView.CHANGE,
Ben Rohlfsecc67992022-12-16 10:10:16 +01001035 childView: ChangeChildView.OVERVIEW,
Ben Rohlfsabaeacf2022-05-27 15:24:22 +02001036 patchNum: 3 as RevisionPatchSetNum,
paladox68b968e2022-02-20 17:31:50 +00001037 edit: true,
Ben Rohlfsf27798d2023-01-26 12:45:21 +01001038 });
paladox68b968e2022-02-20 17:31:50 +00001039 });
1040 });
1041
Ben Rohlfs9f085ff2023-01-27 08:23:33 +01001042 test('LOG_IN_OR_OUT pass through', async () => {
1043 // LOG_IN_OR_OUT: /^\/log(in|out)(\/(.+))?$/,
1044 await checkUrlNotMatched('/login/asdf');
1045 await checkUrlNotMatched('/logout/asdf');
1046 });
1047
Ben Rohlfsf27798d2023-01-26 12:45:21 +01001048 test('PLUGIN_SCREEN', async () => {
1049 // PLUGIN_SCREEN: /^\/x\/([\w-]+)\/([\w-]+)\/?/,
1050 await checkUrlToState('/x/foo/bar', {
Ben Rohlfs8163cd42022-08-18 16:04:36 +02001051 view: GerritView.PLUGIN_SCREEN,
paladox68b968e2022-02-20 17:31:50 +00001052 plugin: 'foo',
1053 screen: 'bar',
1054 });
paladox68b968e2022-02-20 17:31:50 +00001055 });
Ben Rohlfs0173d2a2023-01-27 09:10:40 +01001056
1057 test('DOCUMENTATION_SEARCH*', async () => {
1058 // DOCUMENTATION_SEARCH_FILTER: '/Documentation/q/filter::filter',
1059 // DOCUMENTATION_SEARCH: /^\/Documentation\/q\/(.*)$/,
1060 await checkRedirect(
1061 '/Documentation/q/asdf',
1062 '/Documentation/q/filter:asdf'
1063 );
1064 await checkRedirect(
Ben Rohlfsc02facb2023-01-27 18:46:02 +01001065 '/Documentation/q/as%3Fdf',
1066 '/Documentation/q/filter:as%3Fdf'
Ben Rohlfs0173d2a2023-01-27 09:10:40 +01001067 );
1068
1069 await checkUrlToState('/Documentation/q/filter:', {
1070 view: GerritView.DOCUMENTATION_SEARCH,
1071 filter: '',
1072 });
1073 await checkUrlToState('/Documentation/q/filter:asdf', {
1074 view: GerritView.DOCUMENTATION_SEARCH,
1075 filter: 'asdf',
1076 });
Ben Rohlfs1da030b2023-01-31 13:09:53 +01001077 // Percent decoding works fine. gr-page decodes twice, so the only problem
Ben Rohlfs0173d2a2023-01-27 09:10:40 +01001078 // is having `%25` in the URL, because the first decoding pass will yield
1079 // `%`, and then the second decoding pass will throw `URI malformed`.
1080 await checkUrlToState('/Documentation/q/filter:as%20%2fdf', {
1081 view: GerritView.DOCUMENTATION_SEARCH,
1082 filter: 'as /df',
1083 });
1084 // We accept and process double-encoded values, but only *require* it for
1085 // the percent symbol `%`.
1086 await checkUrlToState('/Documentation/q/filter:as%252f%2525df', {
1087 view: GerritView.DOCUMENTATION_SEARCH,
1088 filter: 'as/%df',
1089 });
1090 });
paladox68b968e2022-02-20 17:31:50 +00001091 });
paladox68b968e2022-02-20 17:31:50 +00001092});