Allow to write and run Typescript tests
Change-Id: I695901dca367b342705b7d86b001d2b400510112
diff --git a/polygerrit-ui/app/.eslintrc.js b/polygerrit-ui/app/.eslintrc.js
index 8486019c..fe6bd36 100644
--- a/polygerrit-ui/app/.eslintrc.js
+++ b/polygerrit-ui/app/.eslintrc.js
@@ -280,6 +280,11 @@
// it catches almost all errors related to invalid usage of this.
"no-invalid-this": "off",
+ "node/no-extraneous-import": "off",
+
+ // Typescript already checks for undef
+ "no-undef": "off",
+
"jsdoc/no-types": 2,
},
"parserOptions": {
@@ -287,22 +292,6 @@
}
},
{
- "files": ["**/*.ts"],
- "excludedFiles": "*.d.ts",
- "rules": {
- // Custom rule from the //tools/js/eslint-rules directory.
- // See //tools/js/eslint-rules/README.md for details
- "ts-imports-js": 2,
- }
- },
- {
- "files": ["**/*.d.ts"],
- "rules": {
- // See details in the //tools/js/eslint-rules/report-ts-error.js file.
- "report-ts-error": "error",
- }
- },
- {
"files": ["*.html", "test.js", "test-infra.js"],
"rules": {
"jsdoc/require-file-overview": "off"
diff --git a/polygerrit-ui/app/BUILD b/polygerrit-ui/app/BUILD
index 41c3f17..c29663c 100644
--- a/polygerrit-ui/app/BUILD
+++ b/polygerrit-ui/app/BUILD
@@ -3,7 +3,8 @@
package(default_visibility = ["//visibility:public"])
-# This list must be in sync with the "include" list in the tsconfig.json file
+# This list must be in sync with the "include" list in the follwoing files:
+# tsconfig.json, tsconfig_bazel.json, tsconfig_bazel_test.json
src_dirs = [
"constants",
"elements",
@@ -27,6 +28,7 @@
]],
exclude = [
"**/*_test.js",
+ "**/*_test.ts",
],
),
# The same outdir also appears in the following files:
@@ -40,6 +42,7 @@
[
"**/*.js",
"**/*.ts",
+ "test/@types/*.d.ts",
],
exclude = [
"node_modules/**",
@@ -48,6 +51,7 @@
"rollup.config.js",
],
),
+ include_tests = True,
# The same outdir also appears in the following files:
# wct_test.sh
# karma.conf.js
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.js b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.js
index e4dda13..badf8dc 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.js
@@ -22,7 +22,7 @@
import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
import {sortComments} from '../../../utils/comment-util.js';
import {Side} from '../../../constants/constants.js';
-import {generateChange} from '../../../test/test-utils';
+import {generateChange} from '../../../test/test-utils.js';
const basicFixture = fixtureFromElement('gr-diff-host');
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.js b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.js
index d7fa86b..02b07dd 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.js
@@ -19,7 +19,7 @@
import './gr-diff-view.js';
import {GerritNav} from '../../core/gr-navigation/gr-navigation.js';
import {ChangeStatus} from '../../../constants/constants.js';
-import {generateChange, TestKeyboardShortcutBinder} from '../../../test/test-utils';
+import {generateChange, TestKeyboardShortcutBinder} from '../../../test/test-utils.js';
import {SPECIAL_PATCH_SET_NUM} from '../../../utils/patch-set-util.js';
import {Shortcut} from '../../../mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin.js';
import {_testOnly_findCommentById} from '../gr-comment-api/gr-comment-api.js';
diff --git a/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface_test.js b/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface_test.js
index 65f517a..be8836b 100644
--- a/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface_test.js
+++ b/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface_test.js
@@ -21,7 +21,7 @@
import {_testOnly_initGerritPluginApi} from '../../shared/gr-js-api-interface/gr-gerrit.js';
import {html} from '@polymer/polymer/lib/utils/html-tag.js';
import {PolymerElement} from '@polymer/polymer/polymer-element.js';
-import {createIronOverlayBackdropStyleEl} from '../../../test/test-utils';
+import {createIronOverlayBackdropStyleEl} from '../../../test/test-utils.js';
class GrUserTestPopupElement extends PolymerElement {
static get is() { return 'gr-user-test-popup'; }
diff --git a/polygerrit-ui/app/mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin.ts b/polygerrit-ui/app/mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin.ts
index a926fdb..7aade93 100644
--- a/polygerrit-ui/app/mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin.ts
+++ b/polygerrit-ui/app/mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin.ts
@@ -551,6 +551,14 @@
private readonly bindings = new Map<Shortcut, string[]>();
+ public _testOnly_getBindings() {
+ return this.bindings;
+ }
+
+ public _testOnly_isEmpty() {
+ return this.activeHosts.size === 0 && this.listeners.size === 0;
+ }
+
private readonly listeners = new Set<ShortcutListener>();
bindShortcut(shortcut: Shortcut, ...bindings: string[]) {
diff --git a/polygerrit-ui/app/rules.bzl b/polygerrit-ui/app/rules.bzl
index 8d9be62..feb1a82 100644
--- a/polygerrit-ui/app/rules.bzl
+++ b/polygerrit-ui/app/rules.bzl
@@ -34,7 +34,7 @@
result.append(_get_ts_compiled_path(outdir, f))
return result
-def compile_ts(name, srcs, ts_outdir):
+def compile_ts(name, srcs, ts_outdir, include_tests = False):
"""Compiles srcs files with the typescript compiler
Args:
@@ -50,16 +50,31 @@
# List of files produced by the typescript compiler
generated_js = _get_ts_output_files(ts_outdir, srcs)
+ all_srcs = srcs + [
+ ":tsconfig.json",
+ ":tsconfig_bazel.json",
+ "@ui_npm//:node_modules",
+ ]
+ ts_project = "tsconfig_bazel.json"
+
+ if include_tests:
+ all_srcs = all_srcs + [
+ ":tsconfig_bazel_test.json",
+ "@ui_dev_npm//:node_modules",
+ ]
+ ts_project = "tsconfig_bazel_test.json"
+
# Run the compiler
native.genrule(
name = ts_rule_name,
- srcs = srcs + [
- ":tsconfig.json",
- "@ui_npm//:node_modules",
- ],
+ srcs = all_srcs,
outs = generated_js,
cmd = " && ".join([
- "$(location //tools/node_tools:tsc-bin) --project $(location :tsconfig.json) --outdir $(RULEDIR)/" + ts_outdir + " --baseUrl ./external/ui_npm/node_modules",
+ "$(location //tools/node_tools:tsc-bin) --project $(location :" +
+ ts_project +
+ ") --outdir $(RULEDIR)/" +
+ ts_outdir +
+ " --baseUrl ./external/ui_npm/node_modules/",
]),
tools = ["//tools/node_tools:tsc-bin"],
)
diff --git a/polygerrit-ui/app/scripts/polymer-resin-install.ts b/polygerrit-ui/app/scripts/polymer-resin-install.ts
index 8a30254..ee03171 100644
--- a/polygerrit-ui/app/scripts/polymer-resin-install.ts
+++ b/polygerrit-ui/app/scripts/polymer-resin-install.ts
@@ -57,10 +57,16 @@
const security = window.security;
-export function installPolymerResin(safeTypesBridge: SafeTypeBridge) {
+export const _testOnly_defaultResinReportHandler =
+ security.polymer_resin.CONSOLE_LOGGING_REPORT_HANDLER;
+
+export function installPolymerResin(
+ safeTypesBridge: SafeTypeBridge,
+ reportHandler = security.polymer_resin.CONSOLE_LOGGING_REPORT_HANDLER
+) {
window.security.polymer_resin.install({
allowedIdentifierPrefixes: [''],
- reportHandler: security.polymer_resin.CONSOLE_LOGGING_REPORT_HANDLER,
+ reportHandler,
safeTypesBridge,
});
}
diff --git a/polygerrit-ui/app/services/gr-reporting/gr-reporting_mock.ts b/polygerrit-ui/app/services/gr-reporting/gr-reporting_mock.ts
index 1ef2483..924ddd9 100644
--- a/polygerrit-ui/app/services/gr-reporting/gr-reporting_mock.ts
+++ b/polygerrit-ui/app/services/gr-reporting/gr-reporting_mock.ts
@@ -14,7 +14,23 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-export const grReportingMock = {
+import {ReportingService, Timer} from './gr-reporting';
+
+export class MockTimer implements Timer {
+ end(): this {
+ return this;
+ }
+
+ reset(): this {
+ return this;
+ }
+
+ withMaximum(_: number): this {
+ return this;
+ }
+}
+
+export const grReportingMock: ReportingService = {
appStarted: () => {},
beforeLocationChanged: () => {},
changeDisplayed: () => {},
@@ -25,7 +41,7 @@
diffViewFullyLoaded: () => {},
fileListDisplayed: () => {},
getTimer: () => {
- return {end: () => {}};
+ return new MockTimer();
},
locationChanged: () => {},
onVisibilityChange: () => {},
diff --git a/polygerrit-ui/app/test/@types/sinon-esm.d.ts b/polygerrit-ui/app/test/@types/sinon-esm.d.ts
new file mode 100644
index 0000000..c7ac374
--- /dev/null
+++ b/polygerrit-ui/app/test/@types/sinon-esm.d.ts
@@ -0,0 +1,27 @@
+/**
+ * @license
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+declare module 'sinon/pkg/sinon-esm' {
+ // sinon-esm doesn't have it's own d.ts, reexport all types from sinon
+ // This is a trick - @types/sinon adds interfaces and sinon instance
+ // to a global variables/namespace. We reexport it here, so we
+ // can use in our code when importing sinon-esm
+ // eslint-disable-next-line import/no-default-export
+ export default sinon;
+ const sinon: Sinon.SinonStatic;
+ export {SinonSpy};
+}
diff --git a/polygerrit-ui/app/test/common-test-setup-karma.js b/polygerrit-ui/app/test/common-test-setup-karma.ts
similarity index 70%
rename from polygerrit-ui/app/test/common-test-setup-karma.js
rename to polygerrit-ui/app/test/common-test-setup-karma.ts
index 2335f28..f553ead 100644
--- a/polygerrit-ui/app/test/common-test-setup-karma.js
+++ b/polygerrit-ui/app/test/common-test-setup-karma.ts
@@ -14,14 +14,23 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import './common-test-setup.js';
-import '@polymer/test-fixture/test-fixture.js';
-import 'chai/chai.js';
-self.assert = window.chai.assert;
-self.expect = window.chai.expect;
+import './common-test-setup';
+import '@polymer/test-fixture/test-fixture';
+import 'chai/chai';
+
+declare global {
+ interface Window {
+ flush: typeof flushImpl;
+ fixtureFromTemplate: typeof fixtureFromTemplateImpl;
+ fixtureFromElement: typeof fixtureFromElementImpl;
+ }
+ let flush: typeof flushImpl;
+ let fixtureFromTemplate: typeof fixtureFromTemplateImpl;
+ let fixtureFromElement: typeof fixtureFromElementImpl;
+}
// Workaround for https://github.com/karma-runner/karma-mocha/issues/227
-let unhandledError = null;
+let unhandledError: ErrorEvent;
window.addEventListener('error', e => {
// For uncaught error mochajs doesn't print the full stack trace.
@@ -31,7 +40,7 @@
unhandledError = e;
});
-let originalOnBeforeUnload;
+let originalOnBeforeUnload: typeof window.onbeforeunload;
suiteSetup(() => {
// This suiteSetup() method is called only once before all tests
@@ -39,7 +48,7 @@
// Can't use window.addEventListener("beforeunload",...) here,
// the handler is raised too late.
originalOnBeforeUnload = window.onbeforeunload;
- window.onbeforeunload = e => {
+ window.onbeforeunload = function (e: BeforeUnloadEvent) {
// If a test reloads a page, we can't prevent it.
// However we can print earror and the stack trace with assert.fail
try {
@@ -48,7 +57,9 @@
console.error('Page reloading attempt detected.');
console.error(e.stack.toString());
}
- originalOnBeforeUnload(e);
+ if (originalOnBeforeUnload) {
+ originalOnBeforeUnload.call(this, e);
+ }
};
});
@@ -64,18 +75,18 @@
// Keep the original one for use in test utils methods.
const nativeSetTimeout = window.setTimeout;
+function flushImpl(): Promise<void>;
+function flushImpl(callback: () => void): void;
/**
* Triggers a flush of any pending events, observations, etc and calls you back
* after they have been processed if callback is passed; otherwise returns
* promise.
- *
- * @param {function()} callback
*/
-function flush(callback) {
+function flushImpl(callback?: () => void): Promise<void> | void {
// Ideally, this function would be a call to Polymer.dom.flush, but that
// doesn't support a callback yet
// (https://github.com/Polymer/polymer-dev/issues/851)
- window.Polymer.dom.flush();
+ (window as any).Polymer.dom.flush();
if (callback) {
nativeSetTimeout(callback, 0);
} else {
@@ -85,19 +96,12 @@
}
}
-self.flush = flush;
+self.flush = flushImpl;
class TestFixtureIdProvider {
- static get instance() {
- if (!TestFixtureIdProvider._instance) {
- TestFixtureIdProvider._instance = new TestFixtureIdProvider();
- }
- return TestFixtureIdProvider._instance;
- }
+ public static readonly instance: TestFixtureIdProvider = new TestFixtureIdProvider();
- constructor() {
- this.fixturesCount = 1;
- }
+ private fixturesCount = 1;
generateNewFixtureId() {
this.fixturesCount++;
@@ -105,22 +109,24 @@
}
}
+interface TagTestFixture<T extends Element> {
+ instantiate(model?: unknown): T;
+}
+
class TestFixture {
- constructor(fixtureId) {
- this.fixtureId = fixtureId;
- }
+ constructor(private readonly fixtureId: string) {}
/**
* Create an instance of a fixture's template.
*
- * @param {Object} model - see Data-bound sections at
+ * @param model - see Data-bound sections at
* https://www.webcomponents.org/element/@polymer/test-fixture
- * @return {HTMLElement | HTMLElement[]} - if the fixture's template contains
+ * @return - if the fixture's template contains
* a single element, returns the appropriated instantiated element.
* Otherwise, it return an array of all instantiated elements from the
* template.
*/
- instantiate(model) {
+ instantiate(model?: unknown): HTMLElement | HTMLElement[] {
// The window.fixture method is defined in common-test-setup.js
return window.fixture(this.fixtureId, model);
}
@@ -153,10 +159,9 @@
* });
* }
*
- * @param {HTMLTemplateElement} template - a template for a fixture
- * @return {TestFixture} - the instance of TestFixture class
+ * @param template - a template for a fixture
*/
-function fixtureFromTemplate(template) {
+function fixtureFromTemplateImpl(template: HTMLTemplateElement): TestFixture {
const fixtureId = TestFixtureIdProvider.instance.generateNewFixtureId();
const testFixture = document.createElement('test-fixture');
testFixture.setAttribute('id', fixtureId);
@@ -183,14 +188,17 @@
* });
* }
*
- * @param {HTMLTemplateElement} template - a template for a fixture
- * @return {TestFixture} - the instance of TestFixture class
+ * @param tagName - a template for a fixture is <tagName></tagName>
*/
-function fixtureFromElement(tagName) {
+function fixtureFromElementImpl<T extends keyof HTMLElementTagNameMap>(
+ tagName: T
+): TagTestFixture<HTMLElementTagNameMap[T]> {
const template = document.createElement('template');
template.innerHTML = `<${tagName}></${tagName}>`;
- return fixtureFromTemplate(template);
+ return (fixtureFromTemplate(template) as unknown) as TagTestFixture<
+ HTMLElementTagNameMap[T]
+ >;
}
-window.fixtureFromTemplate = fixtureFromTemplate;
-window.fixtureFromElement = fixtureFromElement;
+window.fixtureFromTemplate = fixtureFromTemplateImpl;
+window.fixtureFromElement = fixtureFromElementImpl;
diff --git a/polygerrit-ui/app/test/common-test-setup.js b/polygerrit-ui/app/test/common-test-setup.js
deleted file mode 100644
index eead4f8..0000000
--- a/polygerrit-ui/app/test/common-test-setup.js
+++ /dev/null
@@ -1,154 +0,0 @@
-/**
- * @license
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-// This should be the first import to install handler before any other code
-import './source-map-support-install.js';
-// TODO(dmfilippov): remove bundled-polymer.js imports when the following issue
-// https://github.com/Polymer/polymer-resin/issues/9 is resolved.
-import '../scripts/bundled-polymer.js';
-import 'polymer-resin/standalone/polymer-resin.js';
-import '@polymer/iron-test-helpers/iron-test-helpers.js';
-import './test-router.js';
-import {_testOnlyInitAppContext} from './test-app-context-init';
-import {_testOnly_resetPluginLoader} from '../elements/shared/gr-js-api-interface/gr-plugin-loader.js';
-import {_testOnlyResetRestApi} from '../elements/shared/gr-js-api-interface/gr-plugin-rest-api.js';
-import {_testOnlyResetGrRestApiSharedObjects} from '../elements/shared/gr-rest-api-interface/gr-rest-api-interface.js';
-import {cleanupTestUtils, TestKeyboardShortcutBinder} from './test-utils.js';
-import {flushDebouncers} from '@polymer/polymer/lib/utils/debounce';
-import {_testOnly_getShortcutManagerInstance} from '../mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin.js';
-import sinon from 'sinon/pkg/sinon-esm.js';
-import {safeTypesBridge} from '../utils/safe-types-util.js';
-import {_testOnly_initGerritPluginApi} from '../elements/shared/gr-js-api-interface/gr-gerrit.js';
-import {initGlobalVariables} from '../elements/gr-app-global-var-init.js';
-window.sinon = sinon;
-
-security.polymer_resin.install({
- allowedIdentifierPrefixes: [''],
- reportHandler(isViolation, fmt, ...args) {
- const log = security.polymer_resin.CONSOLE_LOGGING_REPORT_HANDLER;
- log(isViolation, fmt, ...args);
- if (isViolation) {
- // This will cause the test to fail if there is a data binding
- // violation.
- throw new Error(
- 'polymer-resin violation: ' + fmt +
- JSON.stringify(args));
- }
- },
- safeTypesBridge,
-});
-
-const cleanups = [];
-
-// For karma always set our implementation
-// (karma doesn't provide the fixture method)
-window.fixture = function(fixtureId, model) {
- // This method is inspired by web-component-tester method
- cleanups.push(() => document.getElementById(fixtureId).restore());
- return document.getElementById(fixtureId).create(model);
-};
-
-setup(() => {
- window.Gerrit = {};
- initGlobalVariables();
-
- // If the following asserts fails - then window.stub is
- // overwritten by some other code.
- assert.equal(cleanups.length, 0);
- // The following calls is nessecary to avoid influence of previously executed
- // tests.
- TestKeyboardShortcutBinder.push();
- _testOnlyInitAppContext();
- _testOnly_initGerritPluginApi();
- const mgr = _testOnly_getShortcutManagerInstance();
- assert.equal(mgr.activeHosts.size, 0);
- assert.equal(mgr.listeners.size, 0);
- document.getSelection().removeAllRanges();
- const pl = _testOnly_resetPluginLoader();
- // For testing, always init with empty plugin list
- // Since when serve in gr-app, we always retrieve the list
- // from project config and init loading after that, all
- // `awaitPluginsLoaded` will rely on that to kick off,
- // in testing, we want to kick start this earlier.
- // You still can manually call _testOnly_resetPluginLoader
- // to reset this behavior if you need to test something specific.
- pl.loadPlugins([]);
- _testOnlyResetGrRestApiSharedObjects();
- _testOnlyResetRestApi();
-});
-
-// For karma always set our implementation
-// (karma doesn't provide the stub method)
-window.stub = function(tagName, implementation) {
- // This method is inspired by web-component-tester method
- const proto = document.createElement(tagName).constructor.prototype;
- const stubs = Object.keys(implementation)
- .map(key => sinon.stub(proto, key).callsFake(implementation[key]));
- cleanups.push(() => {
- stubs.forEach(stub => {
- stub.restore();
- });
- });
-};
-
-// Very simple function to catch unexpected elements in documents body.
-// It can't catch everything, but in most cases it is enough.
-function checkChildAllowed(element) {
- const allowedTags = ['SCRIPT', 'IRON-A11Y-ANNOUNCER'];
- if (allowedTags.includes(element.tagName)) {
- return;
- }
- if (element.tagName === 'TEST-FIXTURE') {
- if (element.children.length == 0 ||
- (element.children.length == 1 &&
- element.children[0].tagName === 'TEMPLATE')) {
- return;
- }
- assert.fail(`Test fixture
- ${element.outerHTML}` +
- `isn't resotred after the test is finished. Please ensure that ` +
- `restore() method is called for this test-fixture. Usually the call` +
- `happens automatically.`);
- return;
- }
- if (element.tagName === 'DIV' && element.id === 'gr-hovercard-container' &&
- element.childNodes.length === 0) {
- return;
- }
- assert.fail(
- `The following node remains in document after the test:
- ${element.tagName}
- Outer HTML:
- ${element.outerHTML},
- Stack trace:
- ${element.stackTrace}`);
-}
-function checkGlobalSpace() {
- for (const child of document.body.children) {
- checkChildAllowed(child);
- }
-}
-
-teardown(() => {
- sinon.restore();
- cleanupTestUtils();
- cleanups.forEach(cleanup => cleanup());
- cleanups.splice(0);
- TestKeyboardShortcutBinder.pop();
- checkGlobalSpace();
- // Clean Polymer debouncer queue, so next tests will not be affected.
- flushDebouncers();
-});
diff --git a/polygerrit-ui/app/test/common-test-setup.ts b/polygerrit-ui/app/test/common-test-setup.ts
new file mode 100644
index 0000000..e8a35dc
--- /dev/null
+++ b/polygerrit-ui/app/test/common-test-setup.ts
@@ -0,0 +1,203 @@
+/**
+ * @license
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+// This should be the first import to install handler before any other code
+import './source-map-support-install';
+// TODO(dmfilippov): remove bundled-polymer.js imports when the following issue
+// https://github.com/Polymer/polymer-resin/issues/9 is resolved.
+import '../scripts/bundled-polymer';
+import '@polymer/iron-test-helpers/iron-test-helpers';
+import './test-router';
+import {_testOnlyInitAppContext} from './test-app-context-init';
+import {_testOnly_resetPluginLoader} from '../elements/shared/gr-js-api-interface/gr-plugin-loader';
+import {_testOnlyResetRestApi} from '../elements/shared/gr-js-api-interface/gr-plugin-rest-api';
+import {_testOnlyResetGrRestApiSharedObjects} from '../elements/shared/gr-rest-api-interface/gr-rest-api-interface';
+import {
+ cleanupTestUtils,
+ getCleanupsCount,
+ registerTestCleanup,
+ TestKeyboardShortcutBinder,
+} from './test-utils';
+import {flushDebouncers} from '@polymer/polymer/lib/utils/debounce';
+import {_testOnly_getShortcutManagerInstance} from '../mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin';
+import sinon, {SinonSpy} from 'sinon/pkg/sinon-esm';
+import {safeTypesBridge} from '../utils/safe-types-util';
+import {_testOnly_initGerritPluginApi} from '../elements/shared/gr-js-api-interface/gr-gerrit';
+import {initGlobalVariables} from '../elements/gr-app-global-var-init';
+import 'chai/chai';
+import {
+ _testOnly_defaultResinReportHandler,
+ installPolymerResin,
+} from '../scripts/polymer-resin-install';
+import {hasOwnProperty} from '../utils/common-util';
+
+declare global {
+ interface Window {
+ assert: typeof chai.assert;
+ expect: typeof chai.expect;
+ fixture: typeof fixtureImpl;
+ stub: typeof stubImpl;
+ sinon: typeof sinon;
+ }
+ let assert: typeof chai.assert;
+ let expect: typeof chai.expect;
+ let stub: typeof stubImpl;
+ let sinon: typeof sinon;
+}
+window.assert = chai.assert;
+window.expect = chai.expect;
+
+window.sinon = sinon;
+
+installPolymerResin(safeTypesBridge, (isViolation, fmt, ...args) => {
+ const log = _testOnly_defaultResinReportHandler;
+ log(isViolation, fmt, ...args);
+ if (isViolation) {
+ // This will cause the test to fail if there is a data binding
+ // violation.
+ throw new Error('polymer-resin violation: ' + fmt + JSON.stringify(args));
+ }
+});
+
+interface TestFixtureElement extends HTMLElement {
+ restore(): void;
+ create(model?: unknown): HTMLElement | HTMLElement[];
+}
+
+function getFixtureElementById(fixtureId: string) {
+ return document.getElementById(fixtureId) as TestFixtureElement;
+}
+
+// For karma always set our implementation
+// (karma doesn't provide the fixture method)
+function fixtureImpl(fixtureId: string, model: unknown) {
+ // This method is inspired by web-component-tester method
+ registerTestCleanup(() => getFixtureElementById(fixtureId).restore());
+ return getFixtureElementById(fixtureId).create(model);
+}
+
+window.fixture = fixtureImpl;
+
+setup(() => {
+ window.Gerrit = {};
+ initGlobalVariables();
+
+ // If the following asserts fails - then window.stub is
+ // overwritten by some other code.
+ assert.equal(getCleanupsCount(), 0);
+ // The following calls is nessecary to avoid influence of previously executed
+ // tests.
+ TestKeyboardShortcutBinder.push();
+ _testOnlyInitAppContext();
+ _testOnly_initGerritPluginApi();
+ const mgr = _testOnly_getShortcutManagerInstance();
+ assert.isTrue(mgr._testOnly_isEmpty());
+ const selection = document.getSelection();
+ if (selection) {
+ selection.removeAllRanges();
+ }
+ const pl = _testOnly_resetPluginLoader();
+ // For testing, always init with empty plugin list
+ // Since when serve in gr-app, we always retrieve the list
+ // from project config and init loading after that, all
+ // `awaitPluginsLoaded` will rely on that to kick off,
+ // in testing, we want to kick start this earlier.
+ // You still can manually call _testOnly_resetPluginLoader
+ // to reset this behavior if you need to test something specific.
+ pl.loadPlugins([]);
+ _testOnlyResetGrRestApiSharedObjects();
+ _testOnlyResetRestApi();
+});
+
+// For karma always set our implementation
+// (karma doesn't provide the stub method)
+function stubImpl<T extends keyof HTMLElementTagNameMap>(
+ tagName: T,
+ implementation: Partial<HTMLElementTagNameMap[T]>
+) {
+ // This method is inspired by web-component-tester method
+ const proto = document.createElement(tagName).constructor
+ .prototype as HTMLElementTagNameMap[T];
+ let key: keyof HTMLElementTagNameMap[T];
+ const stubs: SinonSpy[] = [];
+ for (key in implementation) {
+ if (hasOwnProperty(implementation, key)) {
+ stubs.push(sinon.stub(proto, key).callsFake(implementation[key]));
+ }
+ }
+ registerTestCleanup(() => {
+ stubs.forEach(stub => {
+ stub.restore();
+ });
+ });
+}
+
+window.stub = stubImpl;
+
+// Very simple function to catch unexpected elements in documents body.
+// It can't catch everything, but in most cases it is enough.
+function checkChildAllowed(element: Element) {
+ const allowedTags = ['SCRIPT', 'IRON-A11Y-ANNOUNCER'];
+ if (allowedTags.includes(element.tagName)) {
+ return;
+ }
+ if (element.tagName === 'TEST-FIXTURE') {
+ if (
+ element.children.length === 0 ||
+ (element.children.length === 1 &&
+ element.children[0].tagName === 'TEMPLATE')
+ ) {
+ return;
+ }
+ assert.fail(
+ `Test fixture
+ ${element.outerHTML}` +
+ "isn't resotred after the test is finished. Please ensure that " +
+ 'restore() method is called for this test-fixture. Usually the call' +
+ 'happens automatically.'
+ );
+ return;
+ }
+ if (
+ element.tagName === 'DIV' &&
+ element.id === 'gr-hovercard-container' &&
+ element.childNodes.length === 0
+ ) {
+ return;
+ }
+ assert.fail(
+ `The following node remains in document after the test:
+ ${element.tagName}
+ Outer HTML:
+ ${element.outerHTML},
+ Stack trace:
+ ${(element as any).stackTrace}`
+ );
+}
+function checkGlobalSpace() {
+ for (const child of document.body.children) {
+ checkChildAllowed(child);
+ }
+}
+
+teardown(() => {
+ sinon.restore();
+ cleanupTestUtils();
+ TestKeyboardShortcutBinder.pop();
+ checkGlobalSpace();
+ // Clean Polymer debouncer queue, so next tests will not be affected.
+ flushDebouncers();
+});
diff --git a/polygerrit-ui/app/test/source-map-support-install.js b/polygerrit-ui/app/test/source-map-support-install.ts
similarity index 73%
rename from polygerrit-ui/app/test/source-map-support-install.js
rename to polygerrit-ui/app/test/source-map-support-install.ts
index a8f147382..b8798e2 100644
--- a/polygerrit-ui/app/test/source-map-support-install.js
+++ b/polygerrit-ui/app/test/source-map-support-install.ts
@@ -15,6 +15,19 @@
* limitations under the License.
*/
+// Mark the file as a module. Otherwise typescript assumes this is a script
+// and doesn't allow "declare global".
+// See: https://www.typescriptlang.org/docs/handbook/modules.html
+export {};
+
+declare global {
+ interface Window {
+ sourceMapSupport: {
+ install(): void;
+ };
+ }
+}
+
// The karma.conf.js file loads required module before any other modules
// The source-map-support.js can't be imported with import ... statement
window.sourceMapSupport.install();
diff --git a/polygerrit-ui/app/test/test-app-context-init.js b/polygerrit-ui/app/test/test-app-context-init.ts
similarity index 79%
rename from polygerrit-ui/app/test/test-app-context-init.js
rename to polygerrit-ui/app/test/test-app-context-init.ts
index 68e68f0..7f19903 100644
--- a/polygerrit-ui/app/test/test-app-context-init.js
+++ b/polygerrit-ui/app/test/test-app-context-init.ts
@@ -16,14 +16,17 @@
*/
// Init app context before any other imports
-import {initAppContext} from '../services/app-context-init.js';
-import {grReportingMock} from '../services/gr-reporting/gr-reporting_mock.js';
-import {appContext} from '../services/app-context.js';
+import {initAppContext} from '../services/app-context-init';
+import {grReportingMock} from '../services/gr-reporting/gr-reporting_mock';
+import {AppContext, appContext} from '../services/app-context';
export function _testOnlyInitAppContext() {
initAppContext();
- function setMock(serviceName, setupMock) {
+ function setMock<T extends keyof AppContext>(
+ serviceName: T,
+ setupMock: AppContext[T]
+ ) {
Object.defineProperty(appContext, serviceName, {
get() {
return setupMock;
diff --git a/polygerrit-ui/app/test/test-router.js b/polygerrit-ui/app/test/test-router.ts
similarity index 85%
rename from polygerrit-ui/app/test/test-router.js
rename to polygerrit-ui/app/test/test-router.ts
index 9b89744..a378e2d 100644
--- a/polygerrit-ui/app/test/test-router.js
+++ b/polygerrit-ui/app/test/test-router.ts
@@ -14,6 +14,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import {GerritNav} from '../elements/core/gr-navigation/gr-navigation.js';
+import {GerritNav} from '../elements/core/gr-navigation/gr-navigation';
-GerritNav.setup(url => { /* noop */ }, params => '', () => []);
+GerritNav.setup(
+ () => {
+ /* noop */
+ },
+ () => '',
+ () => [],
+ () => {
+ return {};
+ }
+);
diff --git a/polygerrit-ui/app/test/test-utils.js b/polygerrit-ui/app/test/test-utils.js
deleted file mode 100644
index 32430f1..0000000
--- a/polygerrit-ui/app/test/test-utils.js
+++ /dev/null
@@ -1,140 +0,0 @@
-/**
- * @license
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import {_testOnly_resetPluginLoader} from '../elements/shared/gr-js-api-interface/gr-plugin-loader.js';
-import {testOnly_resetInternalState} from '../elements/shared/gr-js-api-interface/gr-api-utils.js';
-import {_testOnly_resetEndpoints} from '../elements/shared/gr-js-api-interface/gr-plugin-endpoints.js';
-import {_testOnly_getShortcutManagerInstance} from '../mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin.js';
-
-export const mockPromise = () => {
- let res;
- const promise = new Promise(resolve => {
- res = resolve;
- });
- promise.resolve = res;
- return promise;
-};
-export const isHidden = el => getComputedStyle(el).display === 'none';
-
-// Some tests/elements can define its own binding. We want to restore bindings
-// at the end of the test. The TestKeyboardShortcutBinder store bindings in
-// stack, so it is possible to override bindings in nested suites.
-export class TestKeyboardShortcutBinder {
- static push() {
- if (!this.stack) {
- this.stack = [];
- }
- const testBinder = new TestKeyboardShortcutBinder();
- this.stack.push(testBinder);
- return _testOnly_getShortcutManagerInstance();
- }
-
- static pop() {
- this.stack.pop()._restoreShortcuts();
- }
-
- constructor() {
- this._originalBinding = new Map(
- _testOnly_getShortcutManagerInstance().bindings);
- }
-
- _restoreShortcuts() {
- const bindings = _testOnly_getShortcutManagerInstance().bindings;
- bindings.clear();
- this._originalBinding.forEach((value, key) => {
- bindings.set(key, value);
- });
- }
-}
-
-// Provide reset plugins function to clear installed plugins between tests.
-// No gr-app found (running tests)
-export const resetPlugins = () => {
- testOnly_resetInternalState();
- _testOnly_resetEndpoints();
- const pl = _testOnly_resetPluginLoader();
- pl.loadPlugins([]);
-};
-
-const cleanups = [];
-
-function registerTestCleanup(cleanupCallback) {
- cleanups.push(cleanupCallback);
-}
-
-export function cleanupTestUtils() {
- cleanups.forEach(cleanup => cleanup());
- cleanups.splice(0);
-}
-
-export function stubBaseUrl(newUrl) {
- const originalCanonicalPath = window.CANONICAL_PATH;
- window.CANONICAL_PATH = newUrl;
- registerTestCleanup(() => window.CANONICAL_PATH = originalCanonicalPath);
-}
-
-export function generateChange(options) {
- const change = {
- _number: 42,
- project: 'testRepo',
- };
- const revisionIdStart = 1;
- const messageIdStart = 1000;
- // We want to distinguish between empty arrays/objects and undefined
- // If an option is not set - the appropriate property is not set
- // If an options is set - the property always set
- if (options && typeof options.revisionsCount !== 'undefined') {
- const revisions = {};
- for (let i = 0; i < options.revisionsCount; i++) {
- const revisionId = (i + revisionIdStart).toString(16);
- revisions[revisionId] = {
- _number: i+1,
- commit: {parents: []},
- };
- }
- change.revisions = revisions;
- }
- if (options && typeof options.messagesCount !== 'undefined') {
- const messages = [];
- for (let i = 0; i < options.messagesCount; i++) {
- messages.push({
- id: (i + messageIdStart).toString(16),
- date: new Date(2020, 1, 1),
- message: `This is a message N${i + 1}`,
- });
- }
- change.messages = messages;
- }
- if (options && options.status) {
- change.status = options.status;
- }
- return change;
-}
-
-/**
- * Forcing an opacity of 0 onto the ironOverlayBackdrop is required, because
- * otherwise the backdrop stays around in the DOM for too long waiting for
- * an animation to finish. This could be considered to be moved to a
- * common-test-setup file.
- */
-export function createIronOverlayBackdropStyleEl() {
- const ironOverlayBackdropStyleEl = document.createElement('style');
- document.head.appendChild(ironOverlayBackdropStyleEl);
- ironOverlayBackdropStyleEl.sheet.insertRule(
- 'body { --iron-overlay-backdrop-opacity: 0; }');
- return ironOverlayBackdropStyleEl;
-}
diff --git a/polygerrit-ui/app/test/test-utils.ts b/polygerrit-ui/app/test/test-utils.ts
new file mode 100644
index 0000000..76fd8a8
--- /dev/null
+++ b/polygerrit-ui/app/test/test-utils.ts
@@ -0,0 +1,233 @@
+/**
+ * @license
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import '../types/globals';
+import {_testOnly_resetPluginLoader} from '../elements/shared/gr-js-api-interface/gr-plugin-loader';
+import {testOnly_resetInternalState} from '../elements/shared/gr-js-api-interface/gr-api-utils';
+import {_testOnly_resetEndpoints} from '../elements/shared/gr-js-api-interface/gr-plugin-endpoints';
+import {
+ _testOnly_getShortcutManagerInstance,
+ Shortcut,
+} from '../mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin';
+import {ChangeStatus, RevisionKind} from '../constants/constants';
+import {
+ AccountInfo,
+ BranchName,
+ ChangeId,
+ ChangeInfo,
+ ChangeInfoId,
+ ChangeMessageId,
+ ChangeMessageInfo,
+ CommitInfo,
+ GitPersonInfo,
+ GitRef,
+ NumericChangeId,
+ PatchSetNum,
+ RepoName,
+ RevisionInfo,
+ Timestamp,
+ TimezoneOffset,
+} from '../types/common';
+import {formatDate} from '../utils/date-util';
+
+export interface MockPromise extends Promise<unknown> {
+ resolve: (value?: unknown) => void;
+}
+
+export const mockPromise = () => {
+ let res: (value?: unknown) => void;
+ const promise: MockPromise = new Promise(resolve => {
+ res = resolve;
+ }) as MockPromise;
+ promise.resolve = res!;
+ return promise;
+};
+export const isHidden = (el: Element) =>
+ getComputedStyle(el).display === 'none';
+
+// Some tests/elements can define its own binding. We want to restore bindings
+// at the end of the test. The TestKeyboardShortcutBinder store bindings in
+// stack, so it is possible to override bindings in nested suites.
+export class TestKeyboardShortcutBinder {
+ private static stack: TestKeyboardShortcutBinder[] = [];
+
+ static push() {
+ const testBinder = new TestKeyboardShortcutBinder();
+ this.stack.push(testBinder);
+ return _testOnly_getShortcutManagerInstance();
+ }
+
+ static pop() {
+ const item = this.stack.pop();
+ if (!item) {
+ throw new Error('stack is empty');
+ }
+ item._restoreShortcuts();
+ }
+
+ private readonly originalBinding: Map<Shortcut, string[]>;
+
+ constructor() {
+ this.originalBinding = new Map(
+ _testOnly_getShortcutManagerInstance()._testOnly_getBindings()
+ );
+ }
+
+ _restoreShortcuts() {
+ const bindings = _testOnly_getShortcutManagerInstance()._testOnly_getBindings();
+ bindings.clear();
+ this.originalBinding.forEach((value, key) => {
+ bindings.set(key, value);
+ });
+ }
+}
+
+// Provide reset plugins function to clear installed plugins between tests.
+// No gr-app found (running tests)
+export const resetPlugins = () => {
+ testOnly_resetInternalState();
+ _testOnly_resetEndpoints();
+ const pl = _testOnly_resetPluginLoader();
+ pl.loadPlugins([]);
+};
+
+export type CleanupCallback = () => void;
+
+const cleanups: CleanupCallback[] = [];
+
+export function getCleanupsCount() {
+ return cleanups.length;
+}
+
+export function registerTestCleanup(cleanupCallback: CleanupCallback) {
+ cleanups.push(cleanupCallback);
+}
+
+export function cleanupTestUtils() {
+ cleanups.forEach(cleanup => cleanup());
+ cleanups.splice(0);
+}
+
+export function stubBaseUrl(newUrl: string) {
+ const originalCanonicalPath = window.CANONICAL_PATH;
+ window.CANONICAL_PATH = newUrl;
+ registerTestCleanup(() => (window.CANONICAL_PATH = originalCanonicalPath));
+}
+
+export interface GenerateChangeOptions {
+ revisionsCount?: number;
+ messagesCount?: number;
+ status: ChangeStatus;
+}
+
+export function dateToTimestamp(date: Date): Timestamp {
+ const nanosecondSuffix = '.000000000';
+ return (formatDate(date, 'YYYY-MM-DD HH:mm:ss') +
+ nanosecondSuffix) as Timestamp;
+}
+
+export function generateChange(options: GenerateChangeOptions) {
+ const project = 'testRepo' as RepoName;
+ const branch = 'test_branch' as BranchName;
+ const changeId = 'abcdef' as ChangeId;
+ const id = `${project}~${branch}~${changeId}` as ChangeInfoId;
+ const owner: AccountInfo = {};
+ const createdDate = new Date(2020, 1, 1, 1, 2, 3);
+
+ const change: ChangeInfo = {
+ _number: 42 as NumericChangeId,
+ project,
+ branch,
+ change_id: changeId,
+ created: dateToTimestamp(createdDate),
+ deletions: 0,
+ id,
+ insertions: 0,
+ owner,
+ reviewers: {},
+ status: options?.status ?? ChangeStatus.NEW,
+ subject: '',
+ submitter: owner,
+ updated: dateToTimestamp(new Date(2020, 10, 5, 1, 2, 3)),
+ };
+ const revisionIdStart = 1;
+ const messageIdStart = 1000;
+ // We want to distinguish between empty arrays/objects and undefined
+ // If an option is not set - the appropriate property is not set
+ // If an options is set - the property always set
+ if (options && typeof options.revisionsCount !== 'undefined') {
+ const revisions: {[revisionId: string]: RevisionInfo} = {};
+ const revisionDate = createdDate;
+ for (let i = 0; i < options.revisionsCount; i++) {
+ const revisionId = (i + revisionIdStart).toString(16);
+ const person: GitPersonInfo = {
+ name: 'Test person',
+ email: 'email@google.com',
+ date: dateToTimestamp(new Date(2019, 11, 6, 14, 5, 8)),
+ tz: 0 as TimezoneOffset,
+ };
+ const commit: CommitInfo = {
+ parents: [],
+ author: person,
+ committer: person,
+ subject: 'Test commit subject',
+ message: 'Test commit message',
+ };
+ const revision: RevisionInfo = {
+ _number: (i + 1) as PatchSetNum,
+ commit,
+ created: dateToTimestamp(revisionDate),
+ kind: RevisionKind.REWORK,
+ ref: `refs/changes/5/6/${i + 1}` as GitRef,
+ uploader: owner,
+ };
+ revisions[revisionId] = revision;
+ // advance 1 day
+ revisionDate.setDate(revisionDate.getDate() + 1);
+ }
+ change.revisions = revisions;
+ }
+ if (options && typeof options.messagesCount !== 'undefined') {
+ const messages: ChangeMessageInfo[] = [];
+ for (let i = 0; i < options.messagesCount; i++) {
+ messages.push({
+ id: (i + messageIdStart).toString(16) as ChangeMessageId,
+ date: '2020-01-01 00:00:00.000000000' as Timestamp,
+ message: `This is a message N${i + 1}`,
+ });
+ }
+ change.messages = messages;
+ }
+ if (options && options.status) {
+ change.status = options.status;
+ }
+ return change;
+}
+
+/**
+ * Forcing an opacity of 0 onto the ironOverlayBackdrop is required, because
+ * otherwise the backdrop stays around in the DOM for too long waiting for
+ * an animation to finish. This could be considered to be moved to a
+ * common-test-setup file.
+ */
+export function createIronOverlayBackdropStyleEl() {
+ const ironOverlayBackdropStyleEl = document.createElement('style');
+ document.head.appendChild(ironOverlayBackdropStyleEl);
+ ironOverlayBackdropStyleEl.sheet!.insertRule(
+ 'body { --iron-overlay-backdrop-opacity: 0; }'
+ );
+ return ironOverlayBackdropStyleEl;
+}
diff --git a/polygerrit-ui/app/tsconfig.json b/polygerrit-ui/app/tsconfig.json
index bc6c2df..ac966d2 100644
--- a/polygerrit-ui/app/tsconfig.json
+++ b/polygerrit-ui/app/tsconfig.json
@@ -44,7 +44,9 @@
// If allowJs is set to true, .js and .jsx files are included as well.
// Note: gerrit doesn't have .tsx and .jsx files
"include": [
- // This items below must be in sync with the src_dirs list in the BUILD file
+ // Items below must be in sync with the src_dirs list in the BUILD file
+ // Also items must be in sync with tsconfig_bazel.json, tsconfig_bazel_test.json
+ // (include and exclude arrays are overriden when extends)
"constants/**/*",
"elements/**/*",
"embed/**/*",
@@ -56,7 +58,6 @@
"styles/**/*",
"types/**/*",
"utils/**/*",
- // Directory for test utils (not included in src_dirs in the BUILD file)
"test/**/*"
]
}
diff --git a/polygerrit-ui/app/tsconfig_bazel.json b/polygerrit-ui/app/tsconfig_bazel.json
new file mode 100644
index 0000000..6365bf0
--- /dev/null
+++ b/polygerrit-ui/app/tsconfig_bazel.json
@@ -0,0 +1,29 @@
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "typeRoots": [
+ "../../external/ui_npm/node_modules/@types",
+ "../../external/ui_dev_npm/node_modules/@types"
+ ]
+ },
+ "include": [
+ // Items below must be in sync with the src_dirs list in the BUILD file
+ // Also items must be in sync with tsconfig.json, tsconfig_bazel_test.json
+ // (include and exclude arrays are overriden when extends)
+ "constants/**/*",
+ "elements/**/*",
+ "embed/**/*",
+ "gr-diff/**/*",
+ "mixins/**/*",
+ "samples/**/*",
+ "scripts/**/*",
+ "services/**/*",
+ "styles/**/*",
+ "types/**/*",
+ "utils/**/*"
+ ],
+ "exclude": [
+ "**/*_test.ts",
+ "**/*_test.js"
+ ]
+}
diff --git a/polygerrit-ui/app/tsconfig_bazel_test.json b/polygerrit-ui/app/tsconfig_bazel_test.json
new file mode 100644
index 0000000..123e063
--- /dev/null
+++ b/polygerrit-ui/app/tsconfig_bazel_test.json
@@ -0,0 +1,28 @@
+{
+ "extends": "./tsconfig_bazel.json",
+ "compilerOptions": {
+ "typeRoots": [
+ "./test/@types",
+ "../../external/ui_npm/node_modules/@types",
+ "../../external/ui_dev_npm/node_modules/@types"
+ ],
+ },
+ "include": [
+ // Items below must be in sync with the src_dirs list in the BUILD file
+ // Also items must be in sync with tsconfig.json, tsconfig_test.json
+ // (include and exclude arrays are overriden when extends)
+ "constants/**/*",
+ "elements/**/*",
+ "embed/**/*",
+ "gr-diff/**/*",
+ "mixins/**/*",
+ "samples/**/*",
+ "scripts/**/*",
+ "services/**/*",
+ "styles/**/*",
+ "types/**/*",
+ "utils/**/*",
+ "test/**/*"
+ ],
+ "exclude": []
+}
diff --git a/polygerrit-ui/app/utils/access-util_test.js b/polygerrit-ui/app/utils/access-util_test.ts
similarity index 66%
rename from polygerrit-ui/app/utils/access-util_test.js
rename to polygerrit-ui/app/utils/access-util_test.ts
index 209c2ff..f098d89 100644
--- a/polygerrit-ui/app/utils/access-util_test.js
+++ b/polygerrit-ui/app/utils/access-util_test.ts
@@ -15,28 +15,37 @@
* limitations under the License.
*/
-import '../test/common-test-setup-karma.js';
-import {toSortedPermissionsArray} from './access-util.js';
+import '../test/common-test-setup-karma';
+import {toSortedPermissionsArray} from './access-util';
suite('access-util tests', () => {
test('toSortedPermissionsArray', () => {
const rules = {
'global:Project-Owners': {
- action: 'ALLOW', force: false,
+ action: 'ALLOW',
+ force: false,
},
'4c97682e6ce6b7247f3381b6f1789356666de7f': {
- action: 'ALLOW', force: false,
+ action: 'ALLOW',
+ force: false,
},
};
const expectedResult = [
- {id: '4c97682e6ce6b7247f3381b6f1789356666de7f', value: {
- action: 'ALLOW', force: false,
- }},
- {id: 'global:Project-Owners', value: {
- action: 'ALLOW', force: false,
- }},
+ {
+ id: '4c97682e6ce6b7247f3381b6f1789356666de7f',
+ value: {
+ action: 'ALLOW',
+ force: false,
+ },
+ },
+ {
+ id: 'global:Project-Owners',
+ value: {
+ action: 'ALLOW',
+ force: false,
+ },
+ },
];
assert.deepEqual(toSortedPermissionsArray(rules), expectedResult);
});
});
-
diff --git a/polygerrit-ui/app/utils/date-util.ts b/polygerrit-ui/app/utils/date-util.ts
index 3cad21a..1dd2d2f 100644
--- a/polygerrit-ui/app/utils/date-util.ts
+++ b/polygerrit-ui/app/utils/date-util.ts
@@ -141,6 +141,7 @@
if (format.includes('ss')) {
options.second = '2-digit';
}
+
let locale = 'en-US';
// Workaround for Chrome 80, en-US is using h24 (midnight is 24:00),
// en-GB is using h23 (midnight is 00:00)
diff --git a/polygerrit-ui/package.json b/polygerrit-ui/package.json
index 91b8579..534db57 100644
--- a/polygerrit-ui/package.json
+++ b/polygerrit-ui/package.json
@@ -2,7 +2,11 @@
"name": "polygerrit-ui-dev-dependencies",
"description": "Gerrit Code Review - Polygerrit dev dependencies",
"browser": true,
- "dependencies": {},
+ "dependencies": {
+ "@types/chai": "^4.2.14",
+ "@types/mocha": "^8.0.3",
+ "@types/sinon": "^9.0.8"
+ },
"devDependencies": {
"@open-wc/karma-esm": "^2.16.16",
"@polymer/iron-test-helpers": "^3.0.1",
diff --git a/polygerrit-ui/yarn.lock b/polygerrit-ui/yarn.lock
index a70ded8..2243be9 100644
--- a/polygerrit-ui/yarn.lock
+++ b/polygerrit-ui/yarn.lock
@@ -989,6 +989,11 @@
resolved "https://registry.yarnpkg.com/@types/caniuse-api/-/caniuse-api-3.0.0.tgz#af31cc52062be0ab24583be072fd49b634dcc2fe"
integrity sha512-wT1VfnScjAftZsvLYaefu/UuwYJdYBwD2JDL2OQd01plGmuAoir5V6HnVHgrfh7zEwcasoiyO2wQ+W58sNh2sw==
+"@types/chai@^4.2.14":
+ version "4.2.14"
+ resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.2.14.tgz#44d2dd0b5de6185089375d976b4ec5caf6861193"
+ integrity sha512-G+ITQPXkwTrslfG5L/BksmbLUA0M1iybEsmCWPqzSxsRRhJZimBKJkoMi8fr/CPygPTj4zO5pJH7I2/cm9M7SQ==
+
"@types/command-line-args@^5.0.0":
version "5.0.0"
resolved "https://registry.yarnpkg.com/@types/command-line-args/-/command-line-args-5.0.0.tgz#484e704d20dbb8754a8f091eee45cdd22bcff28c"
@@ -1140,6 +1145,11 @@
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==
+"@types/mocha@^8.0.3":
+ version "8.0.3"
+ resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-8.0.3.tgz#51b21b6acb6d1b923bbdc7725c38f9f455166402"
+ integrity sha512-vyxR57nv8NfcU0GZu8EUXZLTbCMupIUwy95LJ6lllN+JRPG25CwMHoB1q5xKh8YKhQnHYRAn4yW2yuHbf/5xgg==
+
"@types/node@*":
version "14.0.14"
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.14.tgz#24a0b5959f16ac141aeb0c5b3cd7a15b7c64cbce"
@@ -1175,6 +1185,18 @@
"@types/express-serve-static-core" "*"
"@types/mime" "*"
+"@types/sinon@^9.0.8":
+ version "9.0.8"
+ resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-9.0.8.tgz#1ed0038d356784f75b086104ef83bfd4130bb81b"
+ integrity sha512-IVnI820FZFMGI+u1R+2VdRaD/82YIQTdqLYC9DLPszZuynAJDtCvCtCs3bmyL66s7FqRM3+LPX7DhHnVTaagDw==
+ dependencies:
+ "@types/sinonjs__fake-timers" "*"
+
+"@types/sinonjs__fake-timers@*":
+ version "6.0.2"
+ resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.2.tgz#3a84cf5ec3249439015e14049bd3161419bf9eae"
+ integrity sha512-dIPoZ3g5gcx9zZEszaxLSVTvMReD3xxyyDnQUjA6IYDG9Ba2AV0otMPs+77sG9ojB4Qr2N2Vk5RnKeuA0X/0bg==
+
"@types/whatwg-url@^6.4.0":
version "6.4.0"
resolved "https://registry.yarnpkg.com/@types/whatwg-url/-/whatwg-url-6.4.0.tgz#1e59b8c64bc0dbdf66d037cf8449d1c3d5270237"