Merge changes Icc1470e5,I537733c0
* changes:
Stop using page.js directly from random components
Extract route handling into view model
diff --git a/polygerrit-ui/app/elements/admin/gr-create-group-dialog/gr-create-group-dialog.ts b/polygerrit-ui/app/elements/admin/gr-create-group-dialog/gr-create-group-dialog.ts
index 8d5689c..96688e9 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-group-dialog/gr-create-group-dialog.ts
+++ b/polygerrit-ui/app/elements/admin/gr-create-group-dialog/gr-create-group-dialog.ts
@@ -6,7 +6,6 @@
import '@polymer/iron-input/iron-input';
import '../../../styles/gr-form-styles';
import '../../../styles/shared-styles';
-import {page} from '../../../utils/page-wrapper-utils';
import {GroupId, GroupName} from '../../../types/common';
import {getAppContext} from '../../../services/app-context';
import {formStyles} from '../../../styles/gr-form-styles';
@@ -16,6 +15,8 @@
import {BindValueChangeEvent} from '../../../types/events';
import {fireEvent} from '../../../utils/event-util';
import {createGroupUrl} from '../../../models/views/group';
+import {resolve} from '../../../models/dependency';
+import {navigationToken} from '../../core/gr-navigation/gr-navigation';
declare global {
interface HTMLElementTagNameMap {
@@ -32,6 +33,8 @@
private readonly restApiService = getAppContext().restApiService;
+ private readonly getNavigation = resolve(this, navigationToken);
+
static override get styles() {
return [
formStyles,
@@ -86,8 +89,7 @@
return this.restApiService.getGroupConfig(name).then(group => {
if (!group) return;
const groupId = String(group.group_id!) as GroupId;
- // TODO: Use navigation service instead of `page.show()` directly.
- page.show(createGroupUrl({groupId}));
+ this.getNavigation().setUrl(createGroupUrl({groupId}));
});
});
}
diff --git a/polygerrit-ui/app/elements/admin/gr-create-group-dialog/gr-create-group-dialog_test.ts b/polygerrit-ui/app/elements/admin/gr-create-group-dialog/gr-create-group-dialog_test.ts
index 2a0b539..6e36d8c 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-group-dialog/gr-create-group-dialog_test.ts
+++ b/polygerrit-ui/app/elements/admin/gr-create-group-dialog/gr-create-group-dialog_test.ts
@@ -6,7 +6,6 @@
import '../../../test/common-test-setup';
import './gr-create-group-dialog';
import {GrCreateGroupDialog} from './gr-create-group-dialog';
-import {page} from '../../../utils/page-wrapper-utils';
import {
mockPromise,
queryAndAssert,
@@ -15,6 +14,8 @@
import {IronInputElement} from '@polymer/iron-input';
import {GroupId} from '../../../types/common';
import {fixture, html, assert} from '@open-wc/testing';
+import {testResolver} from '../../../test/common-test-setup';
+import {navigationToken} from '../../core/gr-navigation/gr-navigation';
suite('gr-create-group-dialog tests', () => {
let element: GrCreateGroupDialog;
@@ -68,9 +69,9 @@
Promise.resolve({id: 'testId551' as GroupId, group_id: 551})
);
- const showStub = sinon.stub(page, 'show');
+ const setUrlStub = sinon.stub(testResolver(navigationToken), 'setUrl');
await element.handleCreateGroup();
- assert.isTrue(showStub.calledWith('/admin/groups/551'));
+ assert.isTrue(setUrlStub.calledWith('/admin/groups/551'));
});
test('test for unsuccessful group creation', async () => {
@@ -81,8 +82,8 @@
Promise.resolve({id: 'testId551' as GroupId, group_id: 551})
);
- const showStub = sinon.stub(page, 'show');
+ const setUrlStub = sinon.stub(testResolver(navigationToken), 'setUrl');
await element.handleCreateGroup();
- assert.isFalse(showStub.called);
+ assert.isFalse(setUrlStub.called);
});
});
diff --git a/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog.ts b/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog.ts
index bb59ccc..ed57830 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog.ts
+++ b/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog.ts
@@ -7,7 +7,6 @@
import '../../shared/gr-autocomplete/gr-autocomplete';
import '../../shared/gr-button/gr-button';
import '../../shared/gr-select/gr-select';
-import {page} from '../../../utils/page-wrapper-utils';
import {
BranchName,
GroupId,
@@ -24,6 +23,8 @@
import {fireEvent} from '../../../utils/event-util';
import {throwingErrorCallback} from '../../shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper';
import {createRepoUrl} from '../../../models/views/repo';
+import {resolve} from '../../../models/dependency';
+import {navigationToken} from '../../core/gr-navigation/gr-navigation';
declare global {
interface HTMLElementTagNameMap {
@@ -71,6 +72,8 @@
private readonly restApiService = getAppContext().restApiService;
+ private readonly getNavigation = resolve(this, navigationToken);
+
constructor() {
super();
this.query = (input: string) => this.getRepoSuggestions(input);
@@ -195,8 +198,7 @@
);
if (repoRegistered.status === 201) {
this.repoCreated = true;
- // TODO: Use navigation service instead of `page.show()` directly.
- page.show(createRepoUrl({repo: this.repoConfig.name}));
+ this.getNavigation().setUrl(createRepoUrl({repo: this.repoConfig.name}));
}
return repoRegistered;
}
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list_test.ts b/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list_test.ts
index 9b9cf29..9a67fda 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list_test.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list_test.ts
@@ -6,7 +6,6 @@
import '../../../test/common-test-setup';
import './gr-repo-detail-list';
import {GrRepoDetailList} from './gr-repo-detail-list';
-import {page} from '../../../utils/page-wrapper-utils';
import {
addListenerForTest,
mockPromise,
@@ -34,6 +33,8 @@
import {GrListView} from '../../shared/gr-list-view/gr-list-view';
import {fixture, html, assert} from '@open-wc/testing';
import {RepoDetailView} from '../../../models/views/repo';
+import {testResolver} from '../../../test/common-test-setup';
+import {navigationToken} from '../../core/gr-navigation/gr-navigation';
function branchGenerator(counter: number) {
return {
@@ -95,7 +96,7 @@
html`<gr-repo-detail-list></gr-repo-detail-list>`
);
element.detailType = RepoDetailView.BRANCHES;
- sinon.stub(page, 'show');
+ sinon.stub(testResolver(navigationToken), 'setUrl');
});
suite('list of repo branches', () => {
@@ -2338,7 +2339,7 @@
html`<gr-repo-detail-list></gr-repo-detail-list>`
);
element.detailType = RepoDetailView.TAGS;
- sinon.stub(page, 'show');
+ sinon.stub(testResolver(navigationToken), 'setUrl');
});
suite('list of repo tags', () => {
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list_test.ts b/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list_test.ts
index 65f5a8c..906b733 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list_test.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list_test.ts
@@ -6,7 +6,6 @@
import '../../../test/common-test-setup';
import './gr-repo-list';
import {GrRepoList} from './gr-repo-list';
-import {page} from '../../../utils/page-wrapper-utils';
import {
mockPromise,
queryAndAssert,
@@ -23,6 +22,8 @@
import {GrListView} from '../../shared/gr-list-view/gr-list-view';
import {fixture, html, assert} from '@open-wc/testing';
import {AdminChildView, AdminViewState} from '../../../models/views/admin';
+import {testResolver} from '../../../test/common-test-setup';
+import {navigationToken} from '../../core/gr-navigation/gr-navigation';
function createRepo(name: string, counter: number) {
return {
@@ -51,7 +52,7 @@
let repos: ProjectInfoWithName[];
setup(async () => {
- sinon.stub(page, 'show');
+ sinon.stub(testResolver(navigationToken), 'setUrl');
element = await fixture(html`<gr-repo-list></gr-repo-list>`);
});
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.ts
index faaee0b..d2ba2c9 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.ts
@@ -6,7 +6,6 @@
import '../gr-change-list/gr-change-list';
import '../gr-repo-header/gr-repo-header';
import '../gr-user-header/gr-user-header';
-import {page} from '../../../utils/page-wrapper-utils';
import {
AccountDetailInfo,
AccountId,
@@ -28,6 +27,7 @@
import {resolve} from '../../../models/dependency';
import {subscribe} from '../../lit/subscription-controller';
import {userModelToken} from '../../../models/user/user-model';
+import {navigationToken} from '../../core/gr-navigation/gr-navigation';
const LIMIT_OPERATOR_PATTERN = /\blimit:(\d+)/i;
@@ -81,6 +81,8 @@
private readonly getViewModel = resolve(this, searchViewModelToken);
+ private readonly getNavigation = resolve(this, navigationToken);
+
constructor() {
super();
this.addEventListener('next-page', () => this.handleNextPage());
@@ -282,15 +284,13 @@
// private but used in test
handleNextPage() {
if (!this.nextArrow || !this.changesPerPage) return;
- // TODO: Use navigation service instead of `page.show()` directly.
- page.show(this.computeNavLink(1));
+ this.getNavigation().setUrl(this.computeNavLink(1));
}
// private but used in test
handlePreviousPage() {
if (!this.prevArrow || !this.changesPerPage) return;
- // TODO: Use navigation service instead of `page.show()` directly.
- page.show(this.computeNavLink(-1));
+ this.getNavigation().setUrl(this.computeNavLink(-1));
}
// private but used in test
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view_test.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view_test.ts
index f4bd8bd..decc253 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view_test.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view_test.ts
@@ -6,7 +6,6 @@
import '../../../test/common-test-setup';
import './gr-change-list-view';
import {GrChangeListView} from './gr-change-list-view';
-import {page} from '../../../utils/page-wrapper-utils';
import {query, queryAndAssert} from '../../../test/test-utils';
import {createChange} from '../../../test/test-data-generators';
import {ChangeInfo} from '../../../api/rest-api';
@@ -14,6 +13,8 @@
import {GrChangeList} from '../gr-change-list/gr-change-list';
import {GrChangeListSection} from '../gr-change-list-section/gr-change-list-section';
import {GrChangeListItem} from '../gr-change-list-item/gr-change-list-item';
+import {testResolver} from '../../../test/common-test-setup';
+import {navigationToken} from '../../core/gr-navigation/gr-navigation';
suite('gr-change-list-view tests', () => {
let element: GrChangeListView;
@@ -158,7 +159,7 @@
});
test('handleNextPage', async () => {
- const showStub = sinon.stub(page, 'show');
+ const setUrlStub = sinon.stub(testResolver(navigationToken), 'setUrl');
element.changes = Array(25)
.fill(0)
.map(_ => createChange());
@@ -166,7 +167,7 @@
element.loading = false;
await element.updateComplete;
element.handleNextPage();
- assert.isFalse(showStub.called);
+ assert.isFalse(setUrlStub.called);
element.changes = Array(25)
.fill(0)
@@ -174,11 +175,11 @@
element.loading = false;
await element.updateComplete;
element.handleNextPage();
- assert.isTrue(showStub.called);
+ assert.isTrue(setUrlStub.called);
});
test('handlePreviousPage', async () => {
- const showStub = sinon.stub(page, 'show');
+ const setUrlStub = sinon.stub(testResolver(navigationToken), 'setUrl');
element.offset = 0;
element.changes = Array(25)
.fill(0)
@@ -187,11 +188,11 @@
element.loading = false;
await element.updateComplete;
element.handlePreviousPage();
- assert.isFalse(showStub.called);
+ assert.isFalse(setUrlStub.called);
element.offset = 25;
await element.updateComplete;
element.handlePreviousPage();
- assert.isTrue(showStub.called);
+ assert.isTrue(setUrlStub.called);
});
});
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router.ts b/polygerrit-ui/app/elements/core/gr-router/gr-router.ts
index bcf6937..fac7a83 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router.ts
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router.ts
@@ -49,6 +49,7 @@
AdminChildView,
AdminViewModel,
AdminViewState,
+ PLUGIN_LIST_ROUTE,
} from '../../../models/views/admin';
import {
AgreementViewModel,
@@ -99,6 +100,8 @@
isInBaseOfPatchRange,
} from '../../../utils/comment-util';
import {isFileUnchanged} from '../../../embed/diff/gr-diff/gr-diff-utils';
+import {Route, ViewState} from '../../../models/views/base';
+import {Model} from '../../../models/model';
const RoutePattern = {
ROOT: '/',
@@ -184,8 +187,6 @@
PLUGINS: /^\/plugins\/(.+)$/,
- PLUGIN_LIST: /^\/admin\/plugins(\/)?$/,
-
// Matches /admin/plugins[,<offset>][/].
PLUGIN_LIST_OFFSET: /^\/admin\/plugins(,(\d+))?(\/)?$/,
PLUGIN_LIST_FILTER: '/admin/plugins/q/filter::filter',
@@ -369,6 +370,7 @@
}
setState(state: AppElementParams) {
+ // TODO: Move this logic into the change model.
if ('repo' in state && state.repo !== undefined && 'changeNum' in state)
this.restApiService.setInProjectLookup(state.changeNum, state.repo);
@@ -488,6 +490,9 @@
* route is matched, the handler will be executed with `this` referring
* to the component. Its return value will be discarded so that it does
* not interfere with page.js.
+ * TODO: Get rid of this parameter. This is really not something that the
+ * router wants to be concerned with. The reporting service and the view
+ * models should figure that out between themselves.
* @param authRedirect If true, then auth is checked before
* executing the handler. If the user is not logged in, it will redirect
* to the login flow and the handler will not be executed. The login
@@ -515,6 +520,32 @@
}
/**
+ * Convenience wrapper of `mapRoute()` for when you have a `Route` object that
+ * can deal with state creation. Takes care of setting the view model state,
+ * which is currently duplicated lots of times for direct callers of
+ * `mapRoute()`.
+ */
+ mapRouteState<T extends ViewState>(
+ route: Route<T>,
+ viewModel: Model<T | undefined>,
+ handlerName: string,
+ authRedirect?: boolean
+ ) {
+ const handler = (ctx: PageContext) => {
+ const state = route.createState(ctx);
+ // Note that order is important: `this.setState()` must be called before
+ // `viewModel.setState()`. Otherwise the chain of model subscriptions
+ // would be very different. Some views may want app element to swap the
+ // top level view first. Also, `this.setState()` has some special change
+ // view model resetting logic. Eventually the order might not be important
+ // anymore, but be careful! :-)
+ this.setState(state as AppElementParams);
+ viewModel.setState(state);
+ };
+ this.mapRoute(route.urlPattern, handlerName, handler, authRedirect);
+ }
+
+ /**
* This is similar to letting the browser navigate to this URL when the user
* clicks it, or to just setting `window.location.href` directly.
*
@@ -814,10 +845,10 @@
true
);
- this.mapRoute(
- RoutePattern.PLUGIN_LIST,
+ this.mapRouteState(
+ PLUGIN_LIST_ROUTE,
+ this.adminViewModel,
'handlePluginListRoute',
- ctx => this.handlePluginListRoute(ctx),
true
);
@@ -1399,16 +1430,6 @@
this.adminViewModel.setState(state);
}
- handlePluginListRoute(_: PageContext) {
- const state: AdminViewState = {
- view: GerritView.ADMIN,
- adminView: AdminChildView.PLUGINS,
- };
- // Note that router model view must be updated before view models.
- this.setState(state);
- this.adminViewModel.setState(state);
- }
-
handleQueryRoute(ctx: PageContext) {
const state: Partial<SearchViewState> = {
view: GerritView.SEARCH,
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router_test.ts b/polygerrit-ui/app/elements/core/gr-router/gr-router_test.ts
index d8761bf..112c776 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router_test.ts
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router_test.ts
@@ -1065,14 +1065,6 @@
filter: 'foo',
});
});
-
- test('handlePluginListRoute', () => {
- const ctx = createPageContext();
- assertctxToParams(ctx, 'handlePluginListRoute', {
- view: GerritView.ADMIN,
- adminView: AdminChildView.PLUGINS,
- });
- });
});
suite('change/diff routes', () => {
diff --git a/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search_test.ts b/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search_test.ts
index 0092193..ea5e9f3 100644
--- a/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search_test.ts
+++ b/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search_test.ts
@@ -6,10 +6,11 @@
import '../../../test/common-test-setup';
import './gr-documentation-search';
import {GrDocumentationSearch} from './gr-documentation-search';
-import {page} from '../../../utils/page-wrapper-utils';
import {queryAndAssert, stubRestApi} from '../../../test/test-utils';
import {DocResult} from '../../../types/common';
import {fixture, html, assert} from '@open-wc/testing';
+import {testResolver} from '../../../test/common-test-setup';
+import {navigationToken} from '../../core/gr-navigation/gr-navigation';
function documentationGenerator(counter: number) {
return {
@@ -31,7 +32,7 @@
let documentationSearches: DocResult[];
setup(async () => {
- sinon.stub(page, 'show');
+ sinon.stub(testResolver(navigationToken), 'setUrl');
element = await fixture(
html`<gr-documentation-search></gr-documentation-search>`
);
diff --git a/polygerrit-ui/app/elements/gr-app-types.ts b/polygerrit-ui/app/elements/gr-app-types.ts
index 0261992..68e7309 100644
--- a/polygerrit-ui/app/elements/gr-app-types.ts
+++ b/polygerrit-ui/app/elements/gr-app-types.ts
@@ -28,6 +28,7 @@
justRegistered: boolean;
}
+// TODO: Get rid of this type. <gr-app-element> needs to be refactored for that.
export type AppElementParams =
| DashboardViewState
| GroupViewState
diff --git a/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view.ts b/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view.ts
index dad802a..f5837e3 100644
--- a/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view.ts
+++ b/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view.ts
@@ -7,13 +7,14 @@
import '../gr-button/gr-button';
import '../gr-icon/gr-icon';
import {encodeURL, getBaseUrl} from '../../../utils/url-util';
-import {page} from '../../../utils/page-wrapper-utils';
import {fireEvent} from '../../../utils/event-util';
import {debounce, DelayedTask} from '../../../utils/async-util';
import {sharedStyles} from '../../../styles/shared-styles';
import {LitElement, PropertyValues, css, html} from 'lit';
import {customElement, property} from 'lit/decorators.js';
import {BindValueChangeEvent} from '../../../types/events';
+import {resolve} from '../../../models/dependency';
+import {navigationToken} from '../../core/gr-navigation/gr-navigation';
const REQUEST_DEBOUNCE_INTERVAL_MS = 200;
@@ -48,6 +49,8 @@
private reloadTask?: DelayedTask;
+ private readonly getNavigation = resolve(this, navigationToken);
+
override disconnectedCallback() {
this.reloadTask?.cancel();
super.disconnectedCallback();
@@ -165,12 +168,12 @@
() => {
if (!this.isConnected || !this.path) return;
if (filter) {
- // TODO: Use navigation service instead of `page.show()` directly.
- page.show(`${this.path}/q/filter:${encodeURL(filter, false)}`);
+ this.getNavigation().setUrl(
+ `${this.path}/q/filter:${encodeURL(filter, false)}`
+ );
return;
}
- // TODO: Use navigation service instead of `page.show()` directly.
- page.show(this.path);
+ this.getNavigation().setUrl(this.path);
},
REQUEST_DEBOUNCE_INTERVAL_MS
);
diff --git a/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view_test.ts b/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view_test.ts
index bf94e8f..ecc1c25 100644
--- a/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view_test.ts
@@ -6,10 +6,11 @@
import '../../../test/common-test-setup';
import './gr-list-view';
import {GrListView} from './gr-list-view';
-import {page} from '../../../utils/page-wrapper-utils';
import {queryAndAssert, stubBaseUrl} from '../../../test/test-utils';
import {GrButton} from '../gr-button/gr-button';
import {fixture, html, assert} from '@open-wc/testing';
+import {testResolver} from '../../../test/common-test-setup';
+import {navigationToken} from '../../core/gr-navigation/gr-navigation';
suite('gr-list-view tests', () => {
let element: GrListView;
@@ -84,7 +85,9 @@
let resolve: (url: string) => void;
const promise = new Promise(r => (resolve = r));
element.path = '/admin/projects';
- sinon.stub(page, 'show').callsFake(r => resolve(r));
+ sinon
+ .stub(testResolver(navigationToken), 'setUrl')
+ .callsFake(r => resolve(r));
element.filter = 'test';
await element.updateComplete;
diff --git a/polygerrit-ui/app/models/views/admin.ts b/polygerrit-ui/app/models/views/admin.ts
index 3380637..de9ec5d 100644
--- a/polygerrit-ui/app/models/views/admin.ts
+++ b/polygerrit-ui/app/models/views/admin.ts
@@ -7,7 +7,18 @@
import {getBaseUrl} from '../../utils/url-util';
import {define} from '../dependency';
import {Model} from '../model';
-import {ViewState} from './base';
+import {Route, ViewState} from './base';
+
+export const PLUGIN_LIST_ROUTE: Route<AdminViewState> = {
+ urlPattern: /^\/admin\/plugins(\/)?$/,
+ createState: () => {
+ const state: AdminViewState = {
+ view: GerritView.ADMIN,
+ adminView: AdminChildView.PLUGINS,
+ };
+ return state;
+ },
+};
export enum AdminChildView {
REPOS = 'gr-repo-list',
diff --git a/polygerrit-ui/app/models/views/admin_test.ts b/polygerrit-ui/app/models/views/admin_test.ts
new file mode 100644
index 0000000..c9b7801
--- /dev/null
+++ b/polygerrit-ui/app/models/views/admin_test.ts
@@ -0,0 +1,29 @@
+/**
+ * @license
+ * Copyright 2022 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+import {assert} from '@open-wc/testing';
+import {GerritView} from '../../services/router/router-model';
+import '../../test/common-test-setup';
+import {AdminChildView, PLUGIN_LIST_ROUTE} from './admin';
+
+suite('admin view model', () => {
+ suite('routes', () => {
+ test('PLUGIN_LIST', () => {
+ const {urlPattern: pattern, createState} = PLUGIN_LIST_ROUTE;
+
+ assert.isTrue(pattern.test('/admin/plugins'));
+ assert.isTrue(pattern.test('/admin/plugins/'));
+ assert.isFalse(pattern.test('admin/plugins'));
+ assert.isFalse(pattern.test('//admin/plugins'));
+ assert.isFalse(pattern.test('//admin/plugins?'));
+ assert.isFalse(pattern.test('/admin/plugins//'));
+
+ assert.deepEqual(createState({}), {
+ view: GerritView.ADMIN,
+ adminView: AdminChildView.PLUGINS,
+ });
+ });
+ });
+});
diff --git a/polygerrit-ui/app/models/views/base.ts b/polygerrit-ui/app/models/views/base.ts
index 065495d..72bec33 100644
--- a/polygerrit-ui/app/models/views/base.ts
+++ b/polygerrit-ui/app/models/views/base.ts
@@ -8,3 +8,24 @@
export interface ViewState {
view: GerritView;
}
+
+/**
+ * While we are using page.js this interface will normally be implemented by
+ * PageContext, but it helps testing and independence to have our own type
+ * here.
+ */
+export interface UrlInfo {
+ querystring?: string;
+ hash?: string;
+ /** What the regular expression matching returns. */
+ params?: {[paramIndex: string]: string};
+}
+
+/**
+ * Based on `urlPattern` knows whether a URL matches and if so, then
+ * `createState()` can produce a `ViewState` from the matched URL.
+ */
+export interface Route<T extends ViewState> {
+ urlPattern: RegExp;
+ createState: (info: UrlInfo) => T;
+}
diff --git a/polygerrit-ui/app/services/router/router-model.ts b/polygerrit-ui/app/services/router/router-model.ts
index c3c1cb6..5e2cc10 100644
--- a/polygerrit-ui/app/services/router/router-model.ts
+++ b/polygerrit-ui/app/services/router/router-model.ts
@@ -21,12 +21,17 @@
SETTINGS = 'settings',
}
+// TODO: Consider renaming this to AppElementState or something similar.
+// Or maybe RootViewState. This class does *not* model the state of the router.
export interface RouterState {
// Note that this router model view must be updated before view model state.
view?: GerritView;
}
export const routerModelToken = define<RouterModel>('router-model');
+
+// TODO: Consider renaming this to AppElementViewModel or something similar.
+// Or maybe RootViewModel. This class is *not* a view model of the router.
export class RouterModel extends Model<RouterState> {
readonly routerView$: Observable<GerritView | undefined> = select(
this.state$,