blob: 6a86ad1db9d6d09d480b81d3765f1f7941a861ea [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 */
Kamil Musin935205b2024-10-25 12:07:32 +02006import * as sinon from 'sinon';
Frank Bordenbe9451a2022-09-12 15:44:29 +02007import '../../../test/common-test-setup';
paladox68b968e2022-02-20 17:31:50 +00008import './gr-router';
Ben Rohlfs1da030b2023-01-31 13:09:53 +01009import {Page, PageContext} from './gr-page';
paladox68b968e2022-02-20 17:31:50 +000010import {
11 stubBaseUrl,
12 stubRestApi,
13 addListenerForTest,
Ben Rohlfsadbde152023-01-24 19:23:32 +010014 waitUntilCalled,
Ben Rohlfsf6657362023-04-12 14:01:35 +020015 mockPromise,
16 MockPromise,
paladox68b968e2022-02-20 17:31:50 +000017} from '../../../test/test-utils';
Ben Rohlfsf27798d2023-01-26 12:45:21 +010018import {GrRouter, routerToken} from './gr-router';
paladox68b968e2022-02-20 17:31:50 +000019import {GerritView} from '../../../services/router/router-model';
20import {
21 BasePatchSetNum,
paladox68b968e2022-02-20 17:31:50 +000022 NumericChangeId,
Ben Rohlfs58267b72022-05-27 15:59:18 +020023 PARENT,
paladox68b968e2022-02-20 17:31:50 +000024 RepoName,
25 RevisionPatchSetNum,
paladox68b968e2022-02-20 17:31:50 +000026 UrlEncodedCommentId,
paladox68b968e2022-02-20 17:31:50 +000027} from '../../../types/common';
Ben Rohlfsf27798d2023-01-26 12:45:21 +010028import {AppElementJustRegisteredParams} from '../../gr-app-types';
Frank Bordene1ba8212022-08-29 15:20:01 +020029import {assert} from '@open-wc/testing';
Ben Rohlfsadbde152023-01-24 19:23:32 +010030import {AdminChildView, AdminViewState} from '../../../models/views/admin';
Ben Rohlfsade94cb2022-09-14 19:59:42 +020031import {RepoDetailView} from '../../../models/views/repo';
32import {GroupDetailView} from '../../../models/views/group';
Ben Rohlfsf27798d2023-01-26 12:45:21 +010033import {ChangeChildView} from '../../../models/views/change';
Ben Rohlfsbd8dbcf2022-09-16 09:01:14 +020034import {PatchRangeParams} from '../../../utils/url-util';
Chris Poucet9b0707c2022-10-27 14:53:55 +020035import {testResolver} from '../../../test/common-test-setup';
Ben Rohlfs196dc722022-12-20 18:01:33 +010036import {
Ben Rohlfsf27798d2023-01-26 12:45:21 +010037 createAdminPluginsViewState,
38 createAdminReposViewState,
39 createChangeViewState,
Ben Rohlfs196dc722022-12-20 18:01:33 +010040 createComment,
Ben Rohlfsf27798d2023-01-26 12:45:21 +010041 createDashboardViewState,
Ben Rohlfs196dc722022-12-20 18:01:33 +010042 createDiff,
Ben Rohlfsf27798d2023-01-26 12:45:21 +010043 createDiffViewState,
44 createEditViewState,
45 createGroupViewState,
Ben Rohlfs196dc722022-12-20 18:01:33 +010046 createParsedChange,
Ben Rohlfsf27798d2023-01-26 12:45:21 +010047 createRepoBranchesViewState,
48 createRepoTagsViewState,
49 createRepoViewState,
Ben Rohlfs196dc722022-12-20 18:01:33 +010050 createRevision,
Ben Rohlfsf27798d2023-01-26 12:45:21 +010051 createSearchViewState,
Ben Rohlfs196dc722022-12-20 18:01:33 +010052} from '../../../test/test-data-generators';
53import {ParsedChangeInfo} from '../../../types/types';
Ben Rohlfsadbde152023-01-24 19:23:32 +010054import {ViewState} from '../../../models/views/base';
Kamil Musin3e3da5a2023-10-18 16:19:13 +020055import {DashboardType} from '../../../models/views/dashboard';
paladox68b968e2022-02-20 17:31:50 +000056
paladox68b968e2022-02-20 17:31:50 +000057suite('gr-router tests', () => {
Frank Borden79448112022-04-12 16:59:32 +020058 let router: GrRouter;
Ben Rohlfs0d174e02023-01-24 18:18:28 +010059 let page: Page;
paladox68b968e2022-02-20 17:31:50 +000060
61 setup(() => {
Chris Poucet9b0707c2022-10-27 14:53:55 +020062 router = testResolver(routerToken);
Ben Rohlfs0d174e02023-01-24 18:18:28 +010063 page = router.page;
paladox68b968e2022-02-20 17:31:50 +000064 });
65
Ben Rohlfsf27798d2023-01-26 12:45:21 +010066 teardown(async () => {
67 router.finalize();
68 });
69
Frank Borden79448112022-04-12 16:59:32 +020070 test('getHashFromCanonicalPath', () => {
paladox68b968e2022-02-20 17:31:50 +000071 let url = '/foo/bar';
Frank Borden79448112022-04-12 16:59:32 +020072 let hash = router.getHashFromCanonicalPath(url);
paladox68b968e2022-02-20 17:31:50 +000073 assert.equal(hash, '');
74
75 url = '';
Frank Borden79448112022-04-12 16:59:32 +020076 hash = router.getHashFromCanonicalPath(url);
paladox68b968e2022-02-20 17:31:50 +000077 assert.equal(hash, '');
78
79 url = '/foo#bar';
Frank Borden79448112022-04-12 16:59:32 +020080 hash = router.getHashFromCanonicalPath(url);
paladox68b968e2022-02-20 17:31:50 +000081 assert.equal(hash, 'bar');
82
83 url = '/foo#bar#baz';
Frank Borden79448112022-04-12 16:59:32 +020084 hash = router.getHashFromCanonicalPath(url);
paladox68b968e2022-02-20 17:31:50 +000085 assert.equal(hash, 'bar#baz');
86
87 url = '#foo#bar#baz';
Frank Borden79448112022-04-12 16:59:32 +020088 hash = router.getHashFromCanonicalPath(url);
paladox68b968e2022-02-20 17:31:50 +000089 assert.equal(hash, 'foo#bar#baz');
90 });
91
Frank Borden79448112022-04-12 16:59:32 +020092 suite('parseLineAddress', () => {
paladox68b968e2022-02-20 17:31:50 +000093 test('returns null for empty and invalid hashes', () => {
Frank Borden79448112022-04-12 16:59:32 +020094 let actual = router.parseLineAddress('');
paladox68b968e2022-02-20 17:31:50 +000095 assert.isNull(actual);
96
Frank Borden79448112022-04-12 16:59:32 +020097 actual = router.parseLineAddress('foobar');
paladox68b968e2022-02-20 17:31:50 +000098 assert.isNull(actual);
99
Frank Borden79448112022-04-12 16:59:32 +0200100 actual = router.parseLineAddress('foo123');
paladox68b968e2022-02-20 17:31:50 +0000101 assert.isNull(actual);
102
Frank Borden79448112022-04-12 16:59:32 +0200103 actual = router.parseLineAddress('123bar');
paladox68b968e2022-02-20 17:31:50 +0000104 assert.isNull(actual);
105 });
106
107 test('parses correctly', () => {
Frank Borden79448112022-04-12 16:59:32 +0200108 let actual = router.parseLineAddress('1234');
paladox68b968e2022-02-20 17:31:50 +0000109 assert.isOk(actual);
110 assert.equal(actual!.lineNum, 1234);
111 assert.isFalse(actual!.leftSide);
112
Frank Borden79448112022-04-12 16:59:32 +0200113 actual = router.parseLineAddress('a4');
paladox68b968e2022-02-20 17:31:50 +0000114 assert.isOk(actual);
115 assert.equal(actual!.lineNum, 4);
116 assert.isTrue(actual!.leftSide);
117
Frank Borden79448112022-04-12 16:59:32 +0200118 actual = router.parseLineAddress('b77');
paladox68b968e2022-02-20 17:31:50 +0000119 assert.isOk(actual);
120 assert.equal(actual!.lineNum, 77);
121 assert.isTrue(actual!.leftSide);
122 });
123 });
124
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100125 test('startRouterForTesting requires auth for the right handlers', () => {
paladox68b968e2022-02-20 17:31:50 +0000126 // This test encodes the lists of route handler methods that gr-router
127 // automatically checks for authentication before triggering.
128
129 const requiresAuth: any = {};
130 const doesNotRequireAuth: any = {};
paladox68b968e2022-02-20 17:31:50 +0000131 sinon.stub(page, 'start');
paladox68b968e2022-02-20 17:31:50 +0000132 sinon
Frank Borden79448112022-04-12 16:59:32 +0200133 .stub(router, 'mapRoute')
134 .callsFake((_pattern, methodName, _method, usesAuth) => {
paladox68b968e2022-02-20 17:31:50 +0000135 if (usesAuth) {
136 requiresAuth[methodName] = true;
137 } else {
138 doesNotRequireAuth[methodName] = true;
139 }
140 });
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100141 router._testOnly_startRouter();
paladox68b968e2022-02-20 17:31:50 +0000142
143 const actualRequiresAuth = Object.keys(requiresAuth);
144 actualRequiresAuth.sort();
145 const actualDoesNotRequireAuth = Object.keys(doesNotRequireAuth);
146 actualDoesNotRequireAuth.sort();
147
148 const shouldRequireAutoAuth = [
Frank Borden79448112022-04-12 16:59:32 +0200149 'handleAgreementsRoute',
150 'handleChangeEditRoute',
151 'handleCreateGroupRoute',
152 'handleCreateProjectRoute',
153 'handleDiffEditRoute',
154 'handleGroupAuditLogRoute',
155 'handleGroupInfoRoute',
Ben Rohlfs8f064482023-01-27 09:52:51 +0100156 'handleGroupListRoute',
Frank Borden79448112022-04-12 16:59:32 +0200157 'handleGroupMembersRoute',
158 'handleGroupRoute',
159 'handleGroupSelfRedirectRoute',
160 'handleNewAgreementsRoute',
Frank Borden79448112022-04-12 16:59:32 +0200161 'handlePluginListFilterRoute',
Frank Borden79448112022-04-12 16:59:32 +0200162 'handlePluginListRoute',
163 'handleRepoCommandsRoute',
Ben Rohlfsc35ed182022-11-25 12:02:52 +0000164 'handleRepoEditFileRoute',
Edwin Kempinc2fce752024-09-17 14:39:13 +0000165 'handleServerInfoRoute',
Frank Borden79448112022-04-12 16:59:32 +0200166 'handleSettingsLegacyRoute',
167 'handleSettingsRoute',
paladox68b968e2022-02-20 17:31:50 +0000168 ];
169 assert.deepEqual(actualRequiresAuth, shouldRequireAutoAuth);
170
171 const unauthenticatedHandlers = [
Ben Rohlfsb57102f2023-01-27 12:26:56 +0100172 'handleBranchListRoute',
Frank Borden79448112022-04-12 16:59:32 +0200173 'handleChangeIdQueryRoute',
Milutin Kristofic602145d2023-10-25 07:18:08 +0000174 'handleChangeNumberLegacyRoute',
Frank Borden79448112022-04-12 16:59:32 +0200175 'handleChangeRoute',
176 'handleCommentRoute',
177 'handleCommentsRoute',
178 'handleDiffRoute',
179 'handleDefaultRoute',
Milutin Kristofic602145d2023-10-25 07:18:08 +0000180 'handleChangeLegacyRoute',
Frank Borden79448112022-04-12 16:59:32 +0200181 'handleDocumentationRedirectRoute',
182 'handleDocumentationSearchRoute',
183 'handleDocumentationSearchRedirectRoute',
184 'handleLegacyLinenum',
185 'handleImproperlyEncodedPlusRoute',
Frank Borden79448112022-04-12 16:59:32 +0200186 'handleProjectDashboardRoute',
187 'handleLegacyProjectDashboardRoute',
188 'handleProjectsOldRoute',
189 'handleRepoAccessRoute',
190 'handleRepoDashboardsRoute',
191 'handleRepoGeneralRoute',
Ben Rohlfs26306a42023-01-27 12:13:50 +0100192 'handleRepoListRoute',
Frank Borden79448112022-04-12 16:59:32 +0200193 'handleRepoRoute',
194 'handleQueryLegacySuffixRoute',
195 'handleQueryRoute',
196 'handleRegisterRoute',
Ben Rohlfsb57102f2023-01-27 12:26:56 +0100197 'handleTagListRoute',
Frank Borden79448112022-04-12 16:59:32 +0200198 'handlePluginScreen',
paladox68b968e2022-02-20 17:31:50 +0000199 ];
200
201 // Handler names that check authentication themselves, and thus don't need
202 // it performed for them.
203 const selfAuthenticatingHandlers = [
Frank Borden79448112022-04-12 16:59:32 +0200204 'handleDashboardRoute',
205 'handleCustomDashboardRoute',
206 'handleRootRoute',
paladox68b968e2022-02-20 17:31:50 +0000207 ];
208
209 const shouldNotRequireAuth = unauthenticatedHandlers.concat(
210 selfAuthenticatingHandlers
211 );
212 shouldNotRequireAuth.sort();
213 assert.deepEqual(actualDoesNotRequireAuth, shouldNotRequireAuth);
214 });
215
Frank Borden79448112022-04-12 16:59:32 +0200216 test('redirectIfNotLoggedIn while logged in', () => {
paladox68b968e2022-02-20 17:31:50 +0000217 stubRestApi('getLoggedIn').returns(Promise.resolve(true));
Frank Borden79448112022-04-12 16:59:32 +0200218 const redirectStub = sinon.stub(router, 'redirectToLogin');
Ben Rohlfs1da030b2023-01-31 13:09:53 +0100219 return router.redirectIfNotLoggedIn('somepath').then(() => {
paladox68b968e2022-02-20 17:31:50 +0000220 assert.isFalse(redirectStub.called);
221 });
222 });
223
Frank Borden79448112022-04-12 16:59:32 +0200224 test('redirectIfNotLoggedIn while logged out', () => {
paladox68b968e2022-02-20 17:31:50 +0000225 stubRestApi('getLoggedIn').returns(Promise.resolve(false));
Frank Borden79448112022-04-12 16:59:32 +0200226 const redirectStub = sinon.stub(router, 'redirectToLogin');
paladox68b968e2022-02-20 17:31:50 +0000227 return new Promise(resolve => {
Frank Borden79448112022-04-12 16:59:32 +0200228 router
Ben Rohlfs1da030b2023-01-31 13:09:53 +0100229 .redirectIfNotLoggedIn('somepath')
paladox68b968e2022-02-20 17:31:50 +0000230 .then(() => {
231 assert.isTrue(false, 'Should never execute');
232 })
233 .catch(() => {
234 assert.isTrue(redirectStub.calledOnce);
235 resolve(Promise.resolve());
236 });
237 });
238 });
239
paladox68b968e2022-02-20 17:31:50 +0000240 suite('param normalization', () => {
Frank Borden79448112022-04-12 16:59:32 +0200241 suite('normalizePatchRangeParams', () => {
paladox68b968e2022-02-20 17:31:50 +0000242 test('range n..n normalizes to n', () => {
243 const params: PatchRangeParams = {
244 basePatchNum: 4 as BasePatchSetNum,
Ben Rohlfsabaeacf2022-05-27 15:24:22 +0200245 patchNum: 4 as RevisionPatchSetNum,
paladox68b968e2022-02-20 17:31:50 +0000246 };
Ben Rohlfs2a431342022-09-16 10:47:01 +0200247 router.normalizePatchRangeParams(params);
Ben Rohlfs58267b72022-05-27 15:59:18 +0200248 assert.equal(params.basePatchNum, PARENT);
Ben Rohlfsabaeacf2022-05-27 15:24:22 +0200249 assert.equal(params.patchNum, 4 as RevisionPatchSetNum);
paladox68b968e2022-02-20 17:31:50 +0000250 });
251
252 test('range n.. normalizes to n', () => {
253 const params: PatchRangeParams = {basePatchNum: 4 as BasePatchSetNum};
Ben Rohlfs2a431342022-09-16 10:47:01 +0200254 router.normalizePatchRangeParams(params);
Ben Rohlfs58267b72022-05-27 15:59:18 +0200255 assert.equal(params.basePatchNum, PARENT);
Ben Rohlfsabaeacf2022-05-27 15:24:22 +0200256 assert.equal(params.patchNum, 4 as RevisionPatchSetNum);
paladox68b968e2022-02-20 17:31:50 +0000257 });
258 });
259 });
260
Ben Rohlfsf6657362023-04-12 14:01:35 +0200261 suite('navigation blockers', () => {
262 let clock: sinon.SinonFakeTimers;
263 let redirectStub: sinon.SinonStub;
264 let urlPromise: MockPromise<string>;
265
266 setup(() => {
Kamil Musin48677cb2024-02-27 17:06:12 +0100267 stubRestApi('addRepoNameToCache');
Ben Rohlfsf6657362023-04-12 14:01:35 +0200268 urlPromise = mockPromise<string>();
269 redirectStub = sinon
270 .stub(router, 'redirect')
271 .callsFake(urlPromise.resolve);
272 router._testOnly_startRouter();
273 clock = sinon.useFakeTimers();
274 });
275
276 test('no blockers: normal redirect', async () => {
277 router.page.show('/settings/agreements');
278 const url = await urlPromise;
279 assert.isTrue(redirectStub.calledOnce);
280 assert.equal(url, '/settings/#Agreements');
281 });
282
283 test('redirect blocked', async () => {
284 const firstAlertPromise = mockPromise<Event>();
285 addListenerForTest(document, 'show-alert', firstAlertPromise.resolve);
286
287 router.blockNavigation('a good reason');
288 router.page.show('/settings/agreements');
289
290 const firstAlert = (await firstAlertPromise) as CustomEvent;
291 assert.equal(
292 firstAlert.detail.message,
293 'Waiting 1 second for navigation blockers to resolve ...'
294 );
295
296 const secondAlertPromise = mockPromise<Event>();
297 addListenerForTest(document, 'show-alert', secondAlertPromise.resolve);
298
299 clock.tick(2000);
300
301 const secondAlert = (await secondAlertPromise) as CustomEvent;
302 assert.equal(
303 secondAlert.detail.message,
304 'Navigation is blocked by: a good reason'
305 );
306
307 assert.isFalse(redirectStub.called);
308 });
309
310 test('redirect blocked, but resolved within one second', async () => {
311 const firstAlertPromise = mockPromise<Event>();
312 addListenerForTest(document, 'show-alert', firstAlertPromise.resolve);
313
314 router.blockNavigation('a good reason');
315 router.page.show('/settings/agreements');
316
317 const firstAlert = (await firstAlertPromise) as CustomEvent;
318 assert.equal(
319 firstAlert.detail.message,
320 'Waiting 1 second for navigation blockers to resolve ...'
321 );
322
323 const secondAlertPromise = mockPromise<Event>();
324 addListenerForTest(document, 'show-alert', secondAlertPromise.resolve);
325
326 clock.tick(500);
327 router.releaseNavigation('a good reason');
328 clock.tick(2000);
329
330 await urlPromise;
331 assert.isTrue(redirectStub.calledOnce);
332 });
333 });
334
paladox68b968e2022-02-20 17:31:50 +0000335 suite('route handlers', () => {
336 let redirectStub: sinon.SinonStub;
Ben Rohlfs2586e572022-09-16 12:55:37 +0200337 let setStateStub: sinon.SinonStub;
Ben Rohlfs90b2b7d2024-03-13 08:42:32 +0100338 let windowReloadStub: sinon.SinonStub;
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100339 let redirectToLoginStub: sinon.SinonStub;
paladox68b968e2022-02-20 17:31:50 +0000340
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100341 async function checkUrlToState<T extends ViewState>(
342 url: string,
343 state: T | AppElementJustRegisteredParams
paladox68b968e2022-02-20 17:31:50 +0000344 ) {
Ben Rohlfsadbde152023-01-24 19:23:32 +0100345 setStateStub.reset();
346 router.page.show(url);
347 await waitUntilCalled(setStateStub, 'setState');
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100348 assert.isTrue(setStateStub.calledOnce);
Ben Rohlfsadbde152023-01-24 19:23:32 +0100349 assert.deepEqual(setStateStub.lastCall.firstArg, state);
350 }
351
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100352 async function checkRedirect(fromUrl: string, toUrl: string) {
353 redirectStub.reset();
354 router.page.show(fromUrl);
355 await waitUntilCalled(redirectStub, 'redirect');
356 assert.isTrue(redirectStub.calledOnce);
357 assert.isFalse(setStateStub.called);
358 assert.equal(redirectStub.lastCall.firstArg, toUrl);
359 }
360
361 async function checkRedirectToLogin(fromUrl: string, toUrl: string) {
362 redirectToLoginStub.reset();
363 router.page.show(fromUrl);
364 await waitUntilCalled(redirectToLoginStub, 'redirectToLogin');
365 assert.isTrue(redirectToLoginStub.calledOnce);
366 assert.isFalse(redirectStub.called);
367 assert.isFalse(setStateStub.called);
368 assert.equal(redirectToLoginStub.lastCall.firstArg, toUrl);
369 }
370
paladox68b968e2022-02-20 17:31:50 +0000371 setup(() => {
Kamil Musin48677cb2024-02-27 17:06:12 +0100372 stubRestApi('addRepoNameToCache');
Frank Borden79448112022-04-12 16:59:32 +0200373 redirectStub = sinon.stub(router, 'redirect');
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100374 redirectToLoginStub = sinon.stub(router, 'redirectToLogin');
Ben Rohlfs2586e572022-09-16 12:55:37 +0200375 setStateStub = sinon.stub(router, 'setState');
Ben Rohlfs90b2b7d2024-03-13 08:42:32 +0100376 windowReloadStub = sinon.stub(router, 'windowReload');
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100377 router._testOnly_startRouter();
paladox68b968e2022-02-20 17:31:50 +0000378 });
379
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100380 test('LEGACY_PROJECT_DASHBOARD', async () => {
381 // LEGACY_PROJECT_DASHBOARD: /^\/projects\/(.+),dashboards\/(.+)/,
382 await checkRedirect(
383 '/projects/gerrit/project,dashboards/dashboard:main',
paladox68b968e2022-02-20 17:31:50 +0000384 '/p/gerrit/project/+/dashboard/dashboard:main'
385 );
386 });
387
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100388 test('AGREEMENTS', async () => {
389 // AGREEMENTS: /^\/settings\/agreements\/?/,
390 await checkRedirect('/settings/agreements', '/settings/#Agreements');
paladox68b968e2022-02-20 17:31:50 +0000391 });
392
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100393 test('NEW_AGREEMENTS', async () => {
394 // NEW_AGREEMENTS: /^\/settings\/new-agreement\/?/,
395 await checkUrlToState('/settings/new-agreement', {
396 view: GerritView.AGREEMENTS,
397 });
398 await checkUrlToState('/settings/new-agreement/', {
399 view: GerritView.AGREEMENTS,
paladox68b968e2022-02-20 17:31:50 +0000400 });
401 });
402
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100403 test('SETTINGS', async () => {
404 // SETTINGS: /^\/settings\/?/,
405 // SETTINGS_LEGACY: /^\/settings\/VE\/(\S+)/,
406 await checkUrlToState('/settings', {view: GerritView.SETTINGS});
407 await checkUrlToState('/settings/', {view: GerritView.SETTINGS});
408 await checkUrlToState('/settings/VE/asdf', {
Ben Rohlfs8163cd42022-08-18 16:04:36 +0200409 view: GerritView.SETTINGS,
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100410 emailToken: 'asdf',
paladox68b968e2022-02-20 17:31:50 +0000411 });
Ben Rohlfsc02facb2023-01-27 18:46:02 +0100412 await checkUrlToState('/settings/VE/asdf%40qwer', {
Ben Rohlfs8163cd42022-08-18 16:04:36 +0200413 view: GerritView.SETTINGS,
Ben Rohlfsc02facb2023-01-27 18:46:02 +0100414 emailToken: 'asdf@qwer',
paladox68b968e2022-02-20 17:31:50 +0000415 });
416 });
417
Frank Borden79448112022-04-12 16:59:32 +0200418 test('handleDefaultRoute on first load', () => {
paladox68b968e2022-02-20 17:31:50 +0000419 const spy = sinon.spy();
420 addListenerForTest(document, 'page-error', spy);
Frank Borden79448112022-04-12 16:59:32 +0200421 router.handleDefaultRoute();
paladox68b968e2022-02-20 17:31:50 +0000422 assert.isTrue(spy.calledOnce);
423 assert.equal(spy.lastCall.args[0].detail.response.status, 404);
424 });
425
Frank Borden79448112022-04-12 16:59:32 +0200426 test('handleDefaultRoute after internal navigation', () => {
paladox68b968e2022-02-20 17:31:50 +0000427 let onExit: Function | null = null;
428 const onRegisteringExit = (
429 _match: string | RegExp,
430 _onExit: Function
431 ) => {
432 onExit = _onExit;
433 };
Ben Rohlfs1da030b2023-01-31 13:09:53 +0100434 sinon.stub(page, 'registerExitRoute').callsFake(onRegisteringExit);
paladox68b968e2022-02-20 17:31:50 +0000435 sinon.stub(page, 'start');
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100436 router._testOnly_startRouter();
paladox68b968e2022-02-20 17:31:50 +0000437
Frank Borden79448112022-04-12 16:59:32 +0200438 router.handleDefaultRoute();
paladox68b968e2022-02-20 17:31:50 +0000439
440 onExit!('', () => {}); // we left page;
441
Frank Borden79448112022-04-12 16:59:32 +0200442 router.handleDefaultRoute();
Ben Rohlfs90b2b7d2024-03-13 08:42:32 +0100443 assert.isTrue(windowReloadStub.calledOnce);
paladox68b968e2022-02-20 17:31:50 +0000444 });
445
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100446 test('IMPROPERLY_ENCODED_PLUS', async () => {
447 // IMPROPERLY_ENCODED_PLUS: /^\/c\/(.+)\/ \/(.+)$/,
448 await checkRedirect('/c/repo/ /42', '/c/repo/+/42');
449 await checkRedirect('/c/repo/%20/42', '/c/repo/+/42');
450 await checkRedirect('/c/repo/ /42#foo', '/c/repo/+/42#foo');
paladox68b968e2022-02-20 17:31:50 +0000451 });
452
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100453 test('QUERY', async () => {
Ben Rohlfsd47f9b72023-02-01 15:46:09 +0100454 // QUERY: /^\/q\/(.+?)(,(\d+))?$/,
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100455 await checkUrlToState('/q/asdf', {
456 ...createSearchViewState(),
457 query: 'asdf',
458 });
459 await checkUrlToState('/q/project:foo/bar/baz', {
460 ...createSearchViewState(),
paladox68b968e2022-02-20 17:31:50 +0000461 query: 'project:foo/bar/baz',
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100462 });
463 await checkUrlToState('/q/asdf,123', {
464 ...createSearchViewState(),
465 query: 'asdf',
paladox68b968e2022-02-20 17:31:50 +0000466 offset: '123',
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100467 });
Ben Rohlfsd47f9b72023-02-01 15:46:09 +0100468 await checkUrlToState('/q/asdf,qwer', {
469 ...createSearchViewState(),
470 query: 'asdf,qwer',
471 });
472 await checkUrlToState('/q/asdf,qwer,123', {
473 ...createSearchViewState(),
474 query: 'asdf,qwer',
475 offset: '123',
476 });
paladox68b968e2022-02-20 17:31:50 +0000477 });
478
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100479 test('QUERY_LEGACY_SUFFIX', async () => {
480 // QUERY_LEGACY_SUFFIX: /^\/q\/.+,n,z$/,
481 await checkRedirect('/q/foo+bar,n,z', '/q/foo+bar');
paladox68b968e2022-02-20 17:31:50 +0000482 });
483
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100484 test('CHANGE_ID_QUERY', async () => {
485 // CHANGE_ID_QUERY: /^\/id\/(I[0-9a-f]{40})$/,
486 await checkUrlToState('/id/I0123456789abcdef0123456789abcdef01234567', {
487 ...createSearchViewState(),
paladox68b968e2022-02-20 17:31:50 +0000488 query: 'I0123456789abcdef0123456789abcdef01234567',
paladox68b968e2022-02-20 17:31:50 +0000489 });
490 });
491
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100492 test('REGISTER', async () => {
493 // REGISTER: /^\/register(\/.*)?$/,
494 await checkUrlToState('/register/foo/bar', {
495 justRegistered: true,
496 });
497 assert.isTrue(redirectStub.calledWithExactly('/foo/bar'));
498
499 await checkUrlToState('/register', {
500 justRegistered: true,
501 });
502 assert.isTrue(redirectStub.calledWithExactly('/'));
503
504 await checkUrlToState('/register/register', {
505 justRegistered: true,
506 });
507 assert.isTrue(redirectStub.calledWithExactly('/'));
508 });
509
510 suite('ROOT', () => {
paladox68b968e2022-02-20 17:31:50 +0000511 test('closes for closeAfterLogin', () => {
Ben Rohlfs1da030b2023-01-31 13:09:53 +0100512 const ctx = {
513 querystring: 'closeAfterLogin',
514 canonicalPath: '',
515 } as PageContext;
paladox68b968e2022-02-20 17:31:50 +0000516 const closeStub = sinon.stub(window, 'close');
Ben Rohlfs5940c532022-09-20 09:19:50 +0200517 const result = router.handleRootRoute(ctx);
paladox68b968e2022-02-20 17:31:50 +0000518 assert.isNotOk(result);
519 assert.isTrue(closeStub.called);
520 assert.isFalse(redirectStub.called);
521 });
522
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100523 test('ROOT logged in', async () => {
524 stubRestApi('getLoggedIn').resolves(true);
525 await checkRedirect('/', '/dashboard/self');
paladox68b968e2022-02-20 17:31:50 +0000526 });
527
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100528 test('ROOT not logged in', async () => {
529 stubRestApi('getLoggedIn').resolves(false);
530 await checkRedirect('/', '/q/status:open+-is:wip');
paladox68b968e2022-02-20 17:31:50 +0000531 });
532
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100533 suite('ROOT GWT hash-path URLs', () => {
534 test('ROOT hash-path URLs', async () => {
535 await checkRedirect('/#/foo/bar/baz', '/foo/bar/baz');
paladox68b968e2022-02-20 17:31:50 +0000536 });
537
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100538 test('ROOT hash-path URLs w/o leading slash', async () => {
539 await checkRedirect('/#foo/bar/baz', '/foo/bar/baz');
paladox68b968e2022-02-20 17:31:50 +0000540 });
541
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100542 test('ROOT normalizes "/ /" in hash to "/+/"', async () => {
543 await checkRedirect('/#/foo/bar/+/123/4', '/foo/bar/+/123/4');
paladox68b968e2022-02-20 17:31:50 +0000544 });
545
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100546 test('ROOT prepends baseurl to hash-path', async () => {
paladox68b968e2022-02-20 17:31:50 +0000547 stubBaseUrl('/baz');
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100548 await checkRedirect('/#/foo/bar', '/baz/foo/bar');
paladox68b968e2022-02-20 17:31:50 +0000549 });
550
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100551 test('ROOT normalizes /VE/ settings hash-paths', async () => {
552 await checkRedirect('/#/VE/foo/bar', '/settings/VE/foo/bar');
paladox68b968e2022-02-20 17:31:50 +0000553 });
554
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100555 test('ROOT does not drop "inner hashes"', async () => {
556 await checkRedirect('/#/foo/bar#baz', '/foo/bar#baz');
paladox68b968e2022-02-20 17:31:50 +0000557 });
558 });
559 });
560
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100561 suite('DASHBOARD', () => {
562 test('DASHBOARD own dashboard but signed out redirects to login', async () => {
563 stubRestApi('getLoggedIn').resolves(false);
564 await checkRedirectToLogin('/dashboard/seLF', '/dashboard/seLF');
paladox68b968e2022-02-20 17:31:50 +0000565 });
566
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100567 test('DASHBOARD non-self dashboard but signed out redirects', async () => {
568 stubRestApi('getLoggedIn').resolves(false);
569 await checkRedirect('/dashboard/foo', '/q/owner:foo');
paladox68b968e2022-02-20 17:31:50 +0000570 });
571
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100572 test('DASHBOARD', async () => {
573 // DASHBOARD: /^\/dashboard\/(.+)$/,
574 await checkUrlToState('/dashboard/foo', {
575 ...createDashboardViewState(),
576 user: 'foo',
paladox68b968e2022-02-20 17:31:50 +0000577 });
578 });
579 });
580
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100581 suite('CUSTOM_DASHBOARD', () => {
582 test('CUSTOM_DASHBOARD no user specified', async () => {
583 await checkRedirect('/dashboard/', '/dashboard/self');
paladox68b968e2022-02-20 17:31:50 +0000584 });
585
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100586 test('CUSTOM_DASHBOARD', async () => {
587 // CUSTOM_DASHBOARD: /^\/dashboard\/?$/,
588 await checkUrlToState('/dashboard?title=Custom Dashboard&a=b&d=e', {
589 ...createDashboardViewState(),
Kamil Musin3e3da5a2023-10-18 16:19:13 +0200590 type: DashboardType.CUSTOM,
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100591 sections: [
592 {name: 'a', query: 'b'},
593 {name: 'd', query: 'e'},
594 ],
595 title: 'Custom Dashboard',
paladox68b968e2022-02-20 17:31:50 +0000596 });
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100597 await checkUrlToState('/dashboard?a=b&c&d=&=e&foreach=is:open', {
598 ...createDashboardViewState(),
Kamil Musin3e3da5a2023-10-18 16:19:13 +0200599 type: DashboardType.CUSTOM,
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100600 sections: [{name: 'a', query: 'is:open b'}],
601 title: 'Custom Dashboard',
Ben Rohlfs82b46492022-09-20 09:13:56 +0200602 });
paladox68b968e2022-02-20 17:31:50 +0000603 });
604 });
605
606 suite('group routes', () => {
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100607 test('GROUP_INFO', async () => {
608 // GROUP_INFO: /^\/admin\/groups\/(?:uuid-)?(.+),info$/,
609 await checkRedirect('/admin/groups/1234,info', '/admin/groups/1234');
paladox68b968e2022-02-20 17:31:50 +0000610 });
611
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100612 test('GROUP_AUDIT_LOG', async () => {
613 // GROUP_AUDIT_LOG: /^\/admin\/groups\/(?:uuid-)?(.+),audit-log$/,
614 await checkUrlToState('/admin/groups/1234,audit-log', {
615 ...createGroupViewState(),
paladox68b968e2022-02-20 17:31:50 +0000616 detail: GroupDetailView.LOG,
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100617 groupId: '1234',
paladox68b968e2022-02-20 17:31:50 +0000618 });
619 });
620
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100621 test('GROUP_MEMBERS', async () => {
622 // GROUP_MEMBERS: /^\/admin\/groups\/(?:uuid-)?(.+),members$/,
623 await checkUrlToState('/admin/groups/1234,members', {
624 ...createGroupViewState(),
paladox68b968e2022-02-20 17:31:50 +0000625 detail: GroupDetailView.MEMBERS,
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100626 groupId: '1234',
paladox68b968e2022-02-20 17:31:50 +0000627 });
628 });
629
Ben Rohlfs8f064482023-01-27 09:52:51 +0100630 test('GROUP_LIST', async () => {
631 // GROUP_LIST: /^\/admin\/groups(\/q\/filter:(.*?))?(,(\d+))?(\/)?$/,
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100632
Ben Rohlfsadbde152023-01-24 19:23:32 +0100633 const defaultState: AdminViewState = {
paladox68b968e2022-02-20 17:31:50 +0000634 view: GerritView.ADMIN,
Ben Rohlfsdcb88ac2022-09-12 11:50:26 +0200635 adminView: AdminChildView.GROUPS,
Ben Rohlfsadbde152023-01-24 19:23:32 +0100636 offset: '0',
paladox68b968e2022-02-20 17:31:50 +0000637 openCreateModal: false,
Ben Rohlfs1da030b2023-01-31 13:09:53 +0100638 filter: '',
Ben Rohlfsadbde152023-01-24 19:23:32 +0100639 };
paladox68b968e2022-02-20 17:31:50 +0000640
Ben Rohlfsadbde152023-01-24 19:23:32 +0100641 await checkUrlToState('/admin/groups', defaultState);
642 await checkUrlToState('/admin/groups/', defaultState);
643 await checkUrlToState('/admin/groups#create', {
644 ...defaultState,
paladox68b968e2022-02-20 17:31:50 +0000645 openCreateModal: true,
646 });
Ben Rohlfs8f064482023-01-27 09:52:51 +0100647 await checkUrlToState('/admin/groups,42', {
Ben Rohlfsadbde152023-01-24 19:23:32 +0100648 ...defaultState,
Ben Rohlfs8f064482023-01-27 09:52:51 +0100649 offset: '42',
paladox68b968e2022-02-20 17:31:50 +0000650 });
Ben Rohlfs8f064482023-01-27 09:52:51 +0100651 // #create is ignored when there is an offset
652 await checkUrlToState('/admin/groups,42#create', {
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 Rohlfsadbde152023-01-24 19:23:32 +0100656
Ben Rohlfs8f064482023-01-27 09:52:51 +0100657 await checkUrlToState('/admin/groups/q/filter:foo', {
Ben Rohlfsadbde152023-01-24 19:23:32 +0100658 ...defaultState,
Ben Rohlfs8f064482023-01-27 09:52:51 +0100659 filter: 'foo',
Ben Rohlfsadbde152023-01-24 19:23:32 +0100660 });
Ben Rohlfs8f064482023-01-27 09:52:51 +0100661 await checkUrlToState('/admin/groups/q/filter:foo/%2F%20%2525%252F', {
Ben Rohlfsadbde152023-01-24 19:23:32 +0100662 ...defaultState,
Ben Rohlfs8f064482023-01-27 09:52:51 +0100663 filter: 'foo// %/',
664 });
665 await checkUrlToState('/admin/groups/q/filter:foo,42', {
666 ...defaultState,
667 filter: 'foo',
668 offset: '42',
Ben Rohlfsadbde152023-01-24 19:23:32 +0100669 });
670 // #create is ignored when filtering
Ben Rohlfs8f064482023-01-27 09:52:51 +0100671 await checkUrlToState('/admin/groups/q/filter:foo,42#create', {
Ben Rohlfsadbde152023-01-24 19:23:32 +0100672 ...defaultState,
Ben Rohlfs8f064482023-01-27 09:52:51 +0100673 filter: 'foo',
674 offset: '42',
Ben Rohlfsadbde152023-01-24 19:23:32 +0100675 });
paladox68b968e2022-02-20 17:31:50 +0000676 });
677
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100678 test('GROUP', async () => {
679 // GROUP: /^\/admin\/groups\/(?:uuid-)?([^,]+)$/,
680 await checkUrlToState('/admin/groups/4321', {
681 ...createGroupViewState(),
682 groupId: '4321',
paladox68b968e2022-02-20 17:31:50 +0000683 });
684 });
685 });
686
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100687 suite('REPO*', () => {
688 test('PROJECT_OLD', async () => {
689 // PROJECT_OLD: /^\/admin\/(projects)\/?(.+)?$/,
690 await checkRedirect('/admin/projects/', '/admin/repos/');
691 await checkRedirect('/admin/projects/test', '/admin/repos/test');
692 await checkRedirect(
693 '/admin/projects/test,branches',
paladox68b968e2022-02-20 17:31:50 +0000694 '/admin/repos/test,branches'
695 );
696 });
697
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100698 test('REPO', async () => {
699 // REPO: /^\/admin\/repos\/([^,]+)$/,
700 await checkRedirect('/admin/repos/test', '/admin/repos/test,general');
paladox68b968e2022-02-20 17:31:50 +0000701 });
702
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100703 test('REPO_GENERAL', async () => {
704 // REPO_GENERAL: /^\/admin\/repos\/(.+),general$/,
705 await checkUrlToState('/admin/repos/4321,general', {
706 ...createRepoViewState(),
Ben Rohlfs8163cd42022-08-18 16:04:36 +0200707 detail: RepoDetailView.GENERAL,
paladox68b968e2022-02-20 17:31:50 +0000708 repo: '4321' as RepoName,
709 });
710 });
711
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100712 test('REPO_COMMANDS', async () => {
713 // REPO_COMMANDS: /^\/admin\/repos\/(.+),commands$/,
714 await checkUrlToState('/admin/repos/4321,commands', {
715 ...createRepoViewState(),
Ben Rohlfs8163cd42022-08-18 16:04:36 +0200716 detail: RepoDetailView.COMMANDS,
paladox68b968e2022-02-20 17:31:50 +0000717 repo: '4321' as RepoName,
718 });
719 });
720
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100721 test('REPO_ACCESS', async () => {
722 // REPO_ACCESS: /^\/admin\/repos\/(.+),access$/,
723 await checkUrlToState('/admin/repos/4321,access', {
724 ...createRepoViewState(),
Ben Rohlfs8163cd42022-08-18 16:04:36 +0200725 detail: RepoDetailView.ACCESS,
paladox68b968e2022-02-20 17:31:50 +0000726 repo: '4321' as RepoName,
727 });
728 });
729
Ben Rohlfsb57102f2023-01-27 12:26:56 +0100730 test('BRANCH_LIST', async () => {
731 await checkUrlToState('/admin/repos/4321,branches', {
732 ...createRepoBranchesViewState(),
733 repo: '4321' as RepoName,
paladox68b968e2022-02-20 17:31:50 +0000734 });
Ben Rohlfsb57102f2023-01-27 12:26:56 +0100735 await checkUrlToState('/admin/repos/4321,branches,42', {
736 ...createRepoBranchesViewState(),
737 repo: '4321' as RepoName,
738 offset: '42',
paladox68b968e2022-02-20 17:31:50 +0000739 });
Ben Rohlfsb57102f2023-01-27 12:26:56 +0100740 await checkUrlToState('/admin/repos/4321,branches/q/filter:foo,42', {
741 ...createRepoBranchesViewState(),
742 repo: '4321' as RepoName,
743 offset: '42',
744 filter: 'foo',
paladox68b968e2022-02-20 17:31:50 +0000745 });
Ben Rohlfsb57102f2023-01-27 12:26:56 +0100746 await checkUrlToState('/admin/repos/4321,branches/q/filter:foo', {
747 ...createRepoBranchesViewState(),
748 repo: '4321' as RepoName,
749 filter: 'foo',
750 });
751 await checkUrlToState(
752 '/admin/repos/asdf/%2F%20%2525%252Fqwer,branches/q/filter:foo/%2F%20%2525%252F',
753 {
754 ...createRepoBranchesViewState(),
755 repo: 'asdf// %/qwer' as RepoName,
756 filter: 'foo// %/',
757 }
758 );
paladox68b968e2022-02-20 17:31:50 +0000759 });
760
Ben Rohlfsb57102f2023-01-27 12:26:56 +0100761 test('TAG_LIST', async () => {
762 await checkUrlToState('/admin/repos/4321,tags', {
763 ...createRepoTagsViewState(),
764 repo: '4321' as RepoName,
paladox68b968e2022-02-20 17:31:50 +0000765 });
Ben Rohlfsb57102f2023-01-27 12:26:56 +0100766 await checkUrlToState('/admin/repos/4321,tags,42', {
767 ...createRepoTagsViewState(),
768 repo: '4321' as RepoName,
769 offset: '42',
paladox68b968e2022-02-20 17:31:50 +0000770 });
Ben Rohlfsb57102f2023-01-27 12:26:56 +0100771 await checkUrlToState('/admin/repos/4321,tags/q/filter:foo,42', {
772 ...createRepoTagsViewState(),
773 repo: '4321' as RepoName,
774 offset: '42',
775 filter: 'foo',
paladox68b968e2022-02-20 17:31:50 +0000776 });
Ben Rohlfsb57102f2023-01-27 12:26:56 +0100777 await checkUrlToState('/admin/repos/4321,tags/q/filter:foo', {
778 ...createRepoTagsViewState(),
779 repo: '4321' as RepoName,
780 filter: 'foo',
781 });
782 await checkUrlToState(
783 '/admin/repos/asdf/%2F%20%2525%252Fqwer,tags/q/filter:foo/%2F%20%2525%252F',
784 {
785 ...createRepoTagsViewState(),
786 repo: 'asdf// %/qwer' as RepoName,
787 filter: 'foo// %/',
788 }
789 );
paladox68b968e2022-02-20 17:31:50 +0000790 });
791
Ben Rohlfs26306a42023-01-27 12:13:50 +0100792 test('REPO_LIST', async () => {
793 await checkUrlToState('/admin/repos', {
794 ...createAdminReposViewState(),
paladox68b968e2022-02-20 17:31:50 +0000795 });
Ben Rohlfs26306a42023-01-27 12:13:50 +0100796 await checkUrlToState('/admin/repos/', {
797 ...createAdminReposViewState(),
paladox68b968e2022-02-20 17:31:50 +0000798 });
Ben Rohlfs26306a42023-01-27 12:13:50 +0100799 await checkUrlToState('/admin/repos,42', {
800 ...createAdminReposViewState(),
801 offset: '42',
802 });
803 await checkUrlToState('/admin/repos#create', {
804 ...createAdminReposViewState(),
805 openCreateModal: true,
806 });
807 await checkUrlToState('/admin/repos/q/filter:foo', {
808 ...createAdminReposViewState(),
809 filter: 'foo',
810 });
811 await checkUrlToState('/admin/repos/q/filter:foo/%2F%20%2525%252F', {
812 ...createAdminReposViewState(),
813 filter: 'foo// %/',
814 });
815 await checkUrlToState('/admin/repos/q/filter:foo,42', {
816 ...createAdminReposViewState(),
817 filter: 'foo',
818 offset: '42',
paladox68b968e2022-02-20 17:31:50 +0000819 });
820 });
821 });
822
Ben Rohlfs4591b042023-01-27 09:39:06 +0100823 test('PLUGIN_LIST', async () => {
824 await checkUrlToState('/admin/plugins', {
825 ...createAdminPluginsViewState(),
paladox68b968e2022-02-20 17:31:50 +0000826 });
Ben Rohlfs4591b042023-01-27 09:39:06 +0100827 await checkUrlToState('/admin/plugins/', {
828 ...createAdminPluginsViewState(),
paladox68b968e2022-02-20 17:31:50 +0000829 });
Ben Rohlfs4591b042023-01-27 09:39:06 +0100830 await checkUrlToState('/admin/plugins,42', {
831 ...createAdminPluginsViewState(),
832 offset: '42',
833 });
834 await checkUrlToState('/admin/plugins/q/filter:foo', {
835 ...createAdminPluginsViewState(),
836 filter: 'foo',
837 });
838 await checkUrlToState('/admin/plugins/q/filter:foo%2F%20%2525%252F', {
839 ...createAdminPluginsViewState(),
840 filter: 'foo/ %/',
841 });
842 await checkUrlToState('/admin/plugins/q/filter:foo,42', {
843 ...createAdminPluginsViewState(),
844 offset: '42',
845 filter: 'foo',
846 });
847 await checkUrlToState('/admin/plugins/q/filter:foo,asdf', {
848 ...createAdminPluginsViewState(),
849 filter: 'foo,asdf',
paladox68b968e2022-02-20 17:31:50 +0000850 });
paladox68b968e2022-02-20 17:31:50 +0000851 });
852
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100853 suite('CHANGE* / DIFF*', () => {
Milutin Kristofic602145d2023-10-25 07:18:08 +0000854 test('CHANGE_NUMBER_LEGACY', async () => {
855 // CHANGE_NUMBER_LEGACY: /^\/(\d+)\/?/,
856 await checkRedirect('/12345', '/c/12345');
857 });
858
859 test('CHANGE_LEGACY', async () => {
860 // CHANGE_LEGACY: /^\/c\/(\d+)\/?(.*)$/,
Kamil Musin48677cb2024-02-27 17:06:12 +0100861 stubRestApi('getRepoName').resolves('project' as RepoName);
Milutin Kristofic602145d2023-10-25 07:18:08 +0000862 await checkRedirect('/c/1234', '/c/project/+/1234/');
863 await checkRedirect(
864 '/c/1234/comment/6789',
865 '/c/project/+/1234/comment/6789'
866 );
867 });
868
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100869 test('DIFF_LEGACY_LINENUM', async () => {
870 await checkRedirect(
871 '/c/1234/3..8/foo/bar@321',
872 '/c/1234/3..8/foo/bar#321'
873 );
874 await checkRedirect(
875 '/c/1234/3..8/foo/bar@b321',
876 '/c/1234/3..8/foo/bar#b321'
paladox68b968e2022-02-20 17:31:50 +0000877 );
878 });
879
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100880 test('CHANGE', async () => {
881 // CHANGE: /^\/c\/(.+)\/\+\/(\d+)(\/?((-?\d+|edit)(\.\.(\d+|edit))?))?\/?$/,
882 await checkUrlToState('/c/test-project/+/42', {
883 ...createChangeViewState(),
884 basePatchNum: undefined,
885 patchNum: undefined,
paladox68b968e2022-02-20 17:31:50 +0000886 });
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100887 await checkUrlToState('/c/test-project/+/42/7', {
888 ...createChangeViewState(),
889 basePatchNum: PARENT,
890 patchNum: 7,
paladox68b968e2022-02-20 17:31:50 +0000891 });
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100892 await checkUrlToState('/c/test-project/+/42/4..7', {
893 ...createChangeViewState(),
894 basePatchNum: 4,
895 patchNum: 7,
896 });
897 await checkUrlToState(
898 '/c/test-project/+/42/4..7?tab=checks&filter=fff&attempt=1&checksRunsSelected=asdf,qwer&checksResultsFilter=asdf.*qwer',
899 {
900 ...createChangeViewState(),
901 basePatchNum: 4,
902 patchNum: 7,
paladox68b968e2022-02-20 17:31:50 +0000903 attempt: 1,
904 filter: 'fff',
paladox68b968e2022-02-20 17:31:50 +0000905 tab: 'checks',
Ben Rohlfs20fe8e82022-10-14 11:13:10 +0200906 checksRunsSelected: new Set(['asdf', 'qwer']),
Ben Rohlfs209f1412022-09-30 12:25:46 +0200907 checksResultsFilter: 'asdf.*qwer',
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100908 }
909 );
910 });
911
912 test('COMMENTS_TAB', async () => {
913 // COMMENTS_TAB: /^\/c\/(.+)\/\+\/(\d+)\/comments(?:\/)?(\w+)?\/?$/,
914 await checkUrlToState(
915 '/c/gerrit/+/264833/comments/00049681_f34fd6a9/',
916 {
917 ...createChangeViewState(),
918 repo: 'gerrit' as RepoName,
919 changeNum: 264833 as NumericChangeId,
920 commentId: '00049681_f34fd6a9' as UrlEncodedCommentId,
921 view: GerritView.CHANGE,
922 childView: ChangeChildView.OVERVIEW,
923 }
924 );
paladox68b968e2022-02-20 17:31:50 +0000925 });
926
Frank Borden79448112022-04-12 16:59:32 +0200927 suite('handleDiffRoute', () => {
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100928 test('DIFF', async () => {
929 // DIFF: /^\/c\/(.+)\/\+\/(\d+)(\/((-?\d+|edit)(\.\.(\d+|edit))?(\/(.+))))\/?$/,
930 await checkUrlToState('/c/test-project/+/42/4..7/foo/bar/baz#b44', {
931 ...createDiffViewState(),
paladox68b968e2022-02-20 17:31:50 +0000932 basePatchNum: 4 as BasePatchSetNum,
933 patchNum: 7 as RevisionPatchSetNum,
Ben Rohlfsecc67992022-12-16 10:10:16 +0100934 diffView: {
935 path: 'foo/bar/baz',
936 lineNum: 44,
937 leftSide: true,
938 },
paladox68b968e2022-02-20 17:31:50 +0000939 });
paladox68b968e2022-02-20 17:31:50 +0000940 });
941
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100942 test('COMMENT base..1', async () => {
Ben Rohlfs196dc722022-12-20 18:01:33 +0100943 const change: ParsedChangeInfo = createParsedChange();
944 const repo = change.project;
945 const changeNum = change._number;
946 const ps = 1 as RevisionPatchSetNum;
947 const line = 23;
948 const id = '00049681_f34fd6a9' as UrlEncodedCommentId;
949 stubRestApi('getChangeDetail').resolves(change);
950 stubRestApi('getDiffComments').resolves({
951 filepath: [{...createComment(), id, patch_set: ps, line}],
952 });
953
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100954 await checkRedirect(
955 `/c/${repo}/+/${changeNum}/comment/${id}/`,
Ben Rohlfs196dc722022-12-20 18:01:33 +0100956 `/c/${repo}/+/${changeNum}/${ps}/filepath#${line}`
957 );
958 });
959
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100960 test('COMMENT 1..2', async () => {
Ben Rohlfs196dc722022-12-20 18:01:33 +0100961 const change: ParsedChangeInfo = {
962 ...createParsedChange(),
963 revisions: {
964 abc: createRevision(1),
965 def: createRevision(2),
966 },
967 };
968 const repo = change.project;
969 const changeNum = change._number;
970 const ps = 1 as RevisionPatchSetNum;
971 const line = 23;
972 const id = '00049681_f34fd6a9' as UrlEncodedCommentId;
973
974 stubRestApi('getChangeDetail').resolves(change);
975 stubRestApi('getDiffComments').resolves({
976 filepath: [{...createComment(), id, patch_set: ps, line}],
977 });
978 const diffStub = stubRestApi('getDiff');
979
Ben Rohlfs196dc722022-12-20 18:01:33 +0100980 // If getDiff() returns a diff with changes, then we will compare
981 // the patchset of the comment (1) against latest (2).
982 diffStub.onFirstCall().resolves(createDiff());
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100983 await checkRedirect(
984 `/c/${repo}/+/${changeNum}/comment/${id}/`,
Ben Rohlfs196dc722022-12-20 18:01:33 +0100985 `/c/${repo}/+/${changeNum}/${ps}..2/filepath#b${line}`
986 );
987
988 // If getDiff() returns an unchanged diff, then we will compare
989 // the patchset of the comment (1) against base.
990 diffStub.onSecondCall().resolves({
991 ...createDiff(),
992 content: [],
993 });
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100994 await checkRedirect(
995 `/c/${repo}/+/${changeNum}/comment/${id}/`,
Ben Rohlfs196dc722022-12-20 18:01:33 +0100996 `/c/${repo}/+/${changeNum}/${ps}/filepath#${line}`
paladox68b968e2022-02-20 17:31:50 +0000997 );
998 });
paladox68b968e2022-02-20 17:31:50 +0000999 });
1000
Ben Rohlfsf27798d2023-01-26 12:45:21 +01001001 test('DIFF_EDIT', async () => {
1002 // DIFF_EDIT: /^\/c\/(.+)\/\+\/(\d+)\/(\d+|edit)\/(.+),edit(#\d+)?$/,
1003 await checkUrlToState('/c/foo/bar/+/1234/3/foo/bar/baz,edit', {
1004 ...createEditViewState(),
Ben Rohlfsbfc688b2022-10-21 12:38:37 +02001005 repo: 'foo/bar' as RepoName,
paladox68b968e2022-02-20 17:31:50 +00001006 changeNum: 1234 as NumericChangeId,
Ben Rohlfsecc67992022-12-16 10:10:16 +01001007 view: GerritView.CHANGE,
1008 childView: ChangeChildView.EDIT,
Ben Rohlfsabaeacf2022-05-27 15:24:22 +02001009 patchNum: 3 as RevisionPatchSetNum,
Ben Rohlfsecc67992022-12-16 10:10:16 +01001010 editView: {path: 'foo/bar/baz', lineNum: 0},
Ben Rohlfsf27798d2023-01-26 12:45:21 +01001011 });
1012 await checkUrlToState('/c/foo/bar/+/1234/3/foo/bar/baz,edit#4', {
1013 ...createEditViewState(),
Ben Rohlfsbfc688b2022-10-21 12:38:37 +02001014 repo: 'foo/bar' as RepoName,
paladox68b968e2022-02-20 17:31:50 +00001015 changeNum: 1234 as NumericChangeId,
Ben Rohlfsecc67992022-12-16 10:10:16 +01001016 view: GerritView.CHANGE,
1017 childView: ChangeChildView.EDIT,
Ben Rohlfsabaeacf2022-05-27 15:24:22 +02001018 patchNum: 3 as RevisionPatchSetNum,
Ben Rohlfsecc67992022-12-16 10:10:16 +01001019 editView: {path: 'foo/bar/baz', lineNum: 4},
Ben Rohlfsf27798d2023-01-26 12:45:21 +01001020 });
paladox68b968e2022-02-20 17:31:50 +00001021 });
1022
Ben Rohlfsf27798d2023-01-26 12:45:21 +01001023 test('CHANGE_EDIT', async () => {
1024 // CHANGE_EDIT: /^\/c\/(.+)\/\+\/(\d+)(\/(\d+))?,edit\/?$/,
1025 await checkUrlToState('/c/foo/bar/+/1234/3,edit', {
1026 ...createChangeViewState(),
Ben Rohlfsbfc688b2022-10-21 12:38:37 +02001027 repo: 'foo/bar' as RepoName,
paladox68b968e2022-02-20 17:31:50 +00001028 changeNum: 1234 as NumericChangeId,
1029 view: GerritView.CHANGE,
Ben Rohlfsecc67992022-12-16 10:10:16 +01001030 childView: ChangeChildView.OVERVIEW,
Ben Rohlfsabaeacf2022-05-27 15:24:22 +02001031 patchNum: 3 as RevisionPatchSetNum,
paladox68b968e2022-02-20 17:31:50 +00001032 edit: true,
Ben Rohlfsf27798d2023-01-26 12:45:21 +01001033 });
paladox68b968e2022-02-20 17:31:50 +00001034 });
1035 });
1036
Ben Rohlfsf27798d2023-01-26 12:45:21 +01001037 test('PLUGIN_SCREEN', async () => {
1038 // PLUGIN_SCREEN: /^\/x\/([\w-]+)\/([\w-]+)\/?/,
1039 await checkUrlToState('/x/foo/bar', {
Ben Rohlfs8163cd42022-08-18 16:04:36 +02001040 view: GerritView.PLUGIN_SCREEN,
paladox68b968e2022-02-20 17:31:50 +00001041 plugin: 'foo',
1042 screen: 'bar',
1043 });
paladox68b968e2022-02-20 17:31:50 +00001044 });
Ben Rohlfs0173d2a2023-01-27 09:10:40 +01001045
1046 test('DOCUMENTATION_SEARCH*', async () => {
1047 // DOCUMENTATION_SEARCH_FILTER: '/Documentation/q/filter::filter',
1048 // DOCUMENTATION_SEARCH: /^\/Documentation\/q\/(.*)$/,
1049 await checkRedirect(
1050 '/Documentation/q/asdf',
1051 '/Documentation/q/filter:asdf'
1052 );
1053 await checkRedirect(
Ben Rohlfsc02facb2023-01-27 18:46:02 +01001054 '/Documentation/q/as%3Fdf',
1055 '/Documentation/q/filter:as%3Fdf'
Ben Rohlfs0173d2a2023-01-27 09:10:40 +01001056 );
1057
1058 await checkUrlToState('/Documentation/q/filter:', {
1059 view: GerritView.DOCUMENTATION_SEARCH,
1060 filter: '',
1061 });
1062 await checkUrlToState('/Documentation/q/filter:asdf', {
1063 view: GerritView.DOCUMENTATION_SEARCH,
1064 filter: 'asdf',
1065 });
Ben Rohlfs1da030b2023-01-31 13:09:53 +01001066 // Percent decoding works fine. gr-page decodes twice, so the only problem
Ben Rohlfs0173d2a2023-01-27 09:10:40 +01001067 // is having `%25` in the URL, because the first decoding pass will yield
1068 // `%`, and then the second decoding pass will throw `URI malformed`.
1069 await checkUrlToState('/Documentation/q/filter:as%20%2fdf', {
1070 view: GerritView.DOCUMENTATION_SEARCH,
1071 filter: 'as /df',
1072 });
1073 // We accept and process double-encoded values, but only *require* it for
1074 // the percent symbol `%`.
1075 await checkUrlToState('/Documentation/q/filter:as%252f%2525df', {
1076 view: GerritView.DOCUMENTATION_SEARCH,
1077 filter: 'as/%df',
1078 });
1079 });
paladox68b968e2022-02-20 17:31:50 +00001080 });
paladox68b968e2022-02-20 17:31:50 +00001081});