Merge "Convert gr-user-header to lit element."
diff --git a/.gitignore b/.gitignore
index b5204d1..95f94ba 100644
--- a/.gitignore
+++ b/.gitignore
@@ -32,9 +32,13 @@
/node_modules/
/package-lock.json
/plugins/*
+!/plugins/.eslintignore
+!/plugins/.eslintrc.js
+!/plugins/.prettierrc.js
!/plugins/package.json
!/plugins/rollup.config.js
!/plugins/tsconfig.json
+!/plugins/tsconfig-plugins-base.json
!/plugins/yarn.lock
!/plugins/BUILD
!/plugins/codemirror-editor
diff --git a/java/com/google/gerrit/server/comment/CommentContextLoader.java b/java/com/google/gerrit/server/comment/CommentContextLoader.java
index a5aca48..8fbb259 100644
--- a/java/com/google/gerrit/server/comment/CommentContextLoader.java
+++ b/java/com/google/gerrit/server/comment/CommentContextLoader.java
@@ -46,6 +46,8 @@
import java.util.List;
import java.util.Map;
import java.util.Optional;
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.Repository;
@@ -101,7 +103,16 @@
try (Repository repo = repoManager.openRepository(project);
RevWalk rw = new RevWalk(repo)) {
for (ObjectId commitId : commentsByCommitId.keySet()) {
- RevCommit commit = rw.parseCommit(commitId);
+ RevCommit commit;
+ try {
+ commit = rw.parseCommit(commitId);
+ } catch (IncorrectObjectTypeException | MissingObjectException e) {
+ logger.atWarning().log("Commit %s is missing or has an incorrect object type", commitId);
+ commentsByCommitId
+ .get(commitId)
+ .forEach(contextInput -> result.put(contextInput, CommentContext.empty()));
+ continue;
+ }
for (ContextInput contextInput : commentsByCommitId.get(commitId)) {
Optional<Range> range = getStartAndEndLines(contextInput);
if (!range.isPresent()) {
diff --git a/javatests/com/google/gerrit/acceptance/server/change/CommentContextIT.java b/javatests/com/google/gerrit/acceptance/server/change/CommentContextIT.java
index 29058ef..81cb7159 100644
--- a/javatests/com/google/gerrit/acceptance/server/change/CommentContextIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/change/CommentContextIT.java
@@ -30,6 +30,8 @@
import com.google.gerrit.extensions.api.changes.ReviewInput.CommentInput;
import com.google.gerrit.extensions.client.Comment;
import com.google.gerrit.extensions.client.Side;
+import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.extensions.common.ChangeInput;
import com.google.gerrit.extensions.common.CommentInfo;
import com.google.gerrit.extensions.common.ContextLineInfo;
import com.google.gerrit.server.change.FileContentUtil;
@@ -88,6 +90,28 @@
}
@Test
+ public void commentContextForRootCommitOnParentSideReturnsEmptyContext() throws Exception {
+ // Create a change in a new branch, making the patchset commit a root commit
+ ChangeInfo changeInfo = createChangeInNewBranch("newBranch");
+ String changeId = changeInfo.changeId;
+ String revision = changeInfo.revisions.keySet().iterator().next();
+
+ // Write a comment on the parent side of the commit message. Set parent=1 because if unset, our
+ // handler in PostReview assumes we want to write on the auto-merge commit and fails the
+ // pre-condition.
+ CommentInput comment = CommentsUtil.newComment(COMMIT_MSG, Side.PARENT, 0, "comment", false);
+ comment.parent = 1;
+ CommentsUtil.addComments(gApi, changeId, revision, comment);
+
+ List<CommentInfo> comments =
+ gApi.changes().id(changeId).commentsRequest().withContext(true).getAsList();
+ assertThat(comments).hasSize(1);
+ CommentInfo c = comments.stream().collect(MoreCollectors.onlyElement());
+ assertThat(c.commitId).isEqualTo(ObjectId.zeroId().name());
+ assertThat(c.contextLines).isEmpty();
+ }
+
+ @Test
public void commentContextForCommitMessageForLineComment() throws Exception {
PushOneCommit.Result result =
createChange(testRepo, "master", SUBJECT, FILE_NAME, FILE_CONTENT, "topic");
@@ -568,4 +592,13 @@
}
return result;
}
+
+ private ChangeInfo createChangeInNewBranch(String branchName) throws Exception {
+ ChangeInput in = new ChangeInput();
+ in.project = project.get();
+ in.branch = branchName;
+ in.newBranch = true;
+ in.subject = "New changes";
+ return gApi.changes().create(in).get();
+ }
}
diff --git a/plugins/.eslintignore b/plugins/.eslintignore
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/.eslintignore
diff --git a/plugins/.eslintrc.js b/plugins/.eslintrc.js
new file mode 100644
index 0000000..149a31e
--- /dev/null
+++ b/plugins/.eslintrc.js
@@ -0,0 +1,318 @@
+/**
+ * @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.
+ */
+
+/**
+ * This is a base template for TypeScript plugins.
+ *
+ * When extending this template you have to:
+ * - Set the __plugindir variable.
+ */
+
+const path = require('path');
+
+module.exports = {
+ extends: ['eslint:recommended', 'google'],
+ parserOptions: {
+ ecmaVersion: 9,
+ sourceType: 'module',
+ },
+ env: {
+ browser: true,
+ es6: true,
+ },
+ rules: {
+ // https://eslint.org/docs/rules/no-confusing-arrow
+ 'no-confusing-arrow': 'error',
+ // https://eslint.org/docs/rules/newline-per-chained-call
+ 'newline-per-chained-call': ['error', {ignoreChainWithDepth: 2}],
+ // https://eslint.org/docs/rules/arrow-body-style
+ 'arrow-body-style': ['error', 'as-needed',
+ {requireReturnForObjectLiteral: true}],
+ // https://eslint.org/docs/rules/arrow-parens
+ 'arrow-parens': ['error', 'as-needed'],
+ // https://eslint.org/docs/rules/block-spacing
+ 'block-spacing': ['error', 'always'],
+ // https://eslint.org/docs/rules/brace-style
+ 'brace-style': ['error', '1tbs', {allowSingleLine: true}],
+ // https://eslint.org/docs/rules/camelcase
+ 'camelcase': 'off',
+ // https://eslint.org/docs/rules/comma-dangle
+ 'comma-dangle': ['error', {
+ arrays: 'always-multiline',
+ objects: 'always-multiline',
+ imports: 'always-multiline',
+ exports: 'always-multiline',
+ functions: 'never',
+ }],
+ // https://eslint.org/docs/rules/eol-last
+ 'eol-last': 'off',
+ 'guard-for-in': 'error',
+ // https://eslint.org/docs/rules/indent
+ 'indent': ['error', 2, {
+ MemberExpression: 2,
+ FunctionDeclaration: {body: 1, parameters: 2},
+ FunctionExpression: {body: 1, parameters: 2},
+ CallExpression: {arguments: 2},
+ ArrayExpression: 1,
+ ObjectExpression: 1,
+ SwitchCase: 1,
+ }],
+ // https://eslint.org/docs/rules/keyword-spacing
+ 'keyword-spacing': ['error', {after: true, before: true}],
+ // https://eslint.org/docs/rules/lines-between-class-members
+ 'lines-between-class-members': ['error', 'always'],
+ // https://eslint.org/docs/rules/max-len
+ 'max-len': [
+ 'error',
+ 80,
+ 2,
+ {
+ ignoreComments: true,
+ ignorePattern: '^import .*;$',
+ },
+ ],
+ // https://eslint.org/docs/rules/new-cap
+ 'new-cap': ['error', {
+ capIsNewExceptions: ['Polymer'],
+ capIsNewExceptionPattern: '^.*Mixin$',
+ }],
+ // https://eslint.org/docs/rules/no-console
+ 'no-console': [
+ 'error',
+ {allow: ['warn', 'error', 'info', 'assert', 'group', 'groupEnd']},
+ ],
+ // https://eslint.org/docs/rules/no-multiple-empty-lines
+ 'no-multiple-empty-lines': ['error', {max: 1}],
+ // https://eslint.org/docs/rules/no-prototype-builtins
+ 'no-prototype-builtins': 'off',
+ // https://eslint.org/docs/rules/no-redeclare
+ 'no-redeclare': 'off',
+ // https://eslint.org/docs/rules/no-trailing-spaces
+ 'no-trailing-spaces': 'error',
+ // https://eslint.org/docs/rules/no-irregular-whitespace
+ 'no-irregular-whitespace': 'error',
+ // https://eslint.org/docs/rules/array-callback-return
+ 'array-callback-return': ['error', {allowImplicit: true}],
+ // https://eslint.org/docs/rules/no-restricted-syntax
+ 'no-restricted-syntax': [
+ 'error',
+ {
+ selector: 'ExpressionStatement > CallExpression > ' +
+ 'MemberExpression[object.name=\'test\'][property.name=\'only\']',
+ message: 'Remove test.only.',
+ },
+ {
+ selector: 'ExpressionStatement > CallExpression > ' +
+ 'MemberExpression[object.name=\'suite\'][property.name=\'only\']',
+ message: 'Remove suite.only.',
+ },
+ ],
+ // no-undef disables global variable.
+ // "globals" declares allowed global variables.
+ // https://eslint.org/docs/rules/no-undef
+ 'no-undef': ['error'],
+ // https://eslint.org/docs/rules/no-useless-escape
+ 'no-useless-escape': 'off',
+ // https://eslint.org/docs/rules/no-var
+ 'no-var': 'error',
+ // https://eslint.org/docs/rules/operator-linebreak
+ 'operator-linebreak': 'off',
+ // https://eslint.org/docs/rules/object-shorthand
+ 'object-shorthand': ['error', 'always'],
+ // https://eslint.org/docs/rules/padding-line-between-statements
+ 'padding-line-between-statements': [
+ 'error',
+ {
+ blankLine: 'always',
+ prev: 'class',
+ next: '*',
+ },
+ {
+ blankLine: 'always',
+ prev: '*',
+ next: 'class',
+ },
+ ],
+ // https://eslint.org/docs/rules/prefer-arrow-callback
+ 'prefer-arrow-callback': 'error',
+ // https://eslint.org/docs/rules/prefer-const
+ 'prefer-const': 'error',
+ // https://eslint.org/docs/rules/prefer-promise-reject-errors
+ 'prefer-promise-reject-errors': 'error',
+ // https://eslint.org/docs/rules/prefer-spread
+ 'prefer-spread': 'error',
+ // https://eslint.org/docs/rules/prefer-object-spread
+ 'prefer-object-spread': 'error',
+ // https://eslint.org/docs/rules/quote-props
+ 'quote-props': ['error', 'consistent-as-needed'],
+ // https://eslint.org/docs/rules/semi
+ 'semi': ['error', 'always'],
+ // https://eslint.org/docs/rules/template-curly-spacing
+ 'template-curly-spacing': 'error',
+
+ // https://eslint.org/docs/rules/require-jsdoc
+ 'require-jsdoc': 0,
+ // https://eslint.org/docs/rules/valid-jsdoc
+ 'valid-jsdoc': 0,
+ // https://github.com/gajus/eslint-plugin-jsdoc#eslint-plugin-jsdoc-rules-check-alignment
+ 'jsdoc/check-alignment': 2,
+ // https://github.com/gajus/eslint-plugin-jsdoc#eslint-plugin-jsdoc-rules-check-examples
+ 'jsdoc/check-examples': 0,
+ // https://github.com/gajus/eslint-plugin-jsdoc#eslint-plugin-jsdoc-rules-check-indentation
+ 'jsdoc/check-indentation': 0,
+ // https://github.com/gajus/eslint-plugin-jsdoc#eslint-plugin-jsdoc-rules-check-param-names
+ 'jsdoc/check-param-names': 0,
+ // https://github.com/gajus/eslint-plugin-jsdoc#eslint-plugin-jsdoc-rules-check-syntax
+ 'jsdoc/check-syntax': 0,
+ // https://github.com/gajus/eslint-plugin-jsdoc#eslint-plugin-jsdoc-rules-check-tag-names
+ 'jsdoc/check-tag-names': 0,
+ // https://github.com/gajus/eslint-plugin-jsdoc#eslint-plugin-jsdoc-rules-check-types
+ 'jsdoc/check-types': 0,
+ // https://github.com/gajus/eslint-plugin-jsdoc#eslint-plugin-jsdoc-rules-implements-on-classes
+ 'jsdoc/implements-on-classes': 2,
+ // https://github.com/gajus/eslint-plugin-jsdoc#eslint-plugin-jsdoc-rules-match-description
+ 'jsdoc/match-description': 0,
+ // https://github.com/gajus/eslint-plugin-jsdoc#eslint-plugin-jsdoc-rules-newline-after-description
+ 'jsdoc/newline-after-description': 2,
+ // https://github.com/gajus/eslint-plugin-jsdoc#eslint-plugin-jsdoc-rules-no-types
+ 'jsdoc/no-types': 0,
+ // https://github.com/gajus/eslint-plugin-jsdoc#eslint-plugin-jsdoc-rules-no-undefined-types
+ 'jsdoc/no-undefined-types': 0,
+ // https://github.com/gajus/eslint-plugin-jsdoc#eslint-plugin-jsdoc-rules-require-description
+ 'jsdoc/require-description': 0,
+ // https://github.com/gajus/eslint-plugin-jsdoc#eslint-plugin-jsdoc-rules-require-description-complete-sentence
+ 'jsdoc/require-description-complete-sentence': 0,
+ // https://github.com/gajus/eslint-plugin-jsdoc#eslint-plugin-jsdoc-rules-require-example
+ 'jsdoc/require-example': 0,
+ // https://github.com/gajus/eslint-plugin-jsdoc#eslint-plugin-jsdoc-rules-require-hyphen-before-param-description
+ 'jsdoc/require-hyphen-before-param-description': 0,
+ // https://github.com/gajus/eslint-plugin-jsdoc#eslint-plugin-jsdoc-rules-require-jsdoc
+ 'jsdoc/require-jsdoc': 0,
+ // https://github.com/gajus/eslint-plugin-jsdoc#eslint-plugin-jsdoc-rules-require-param
+ 'jsdoc/require-param': 0,
+ // https://github.com/gajus/eslint-plugin-jsdoc#eslint-plugin-jsdoc-rules-require-param-description
+ 'jsdoc/require-param-description': 0,
+ // https://github.com/gajus/eslint-plugin-jsdoc#eslint-plugin-jsdoc-rules-require-param-name
+ 'jsdoc/require-param-name': 2,
+ // https://github.com/gajus/eslint-plugin-jsdoc#eslint-plugin-jsdoc-rules-require-returns
+ 'jsdoc/require-returns': 0,
+ // https://github.com/gajus/eslint-plugin-jsdoc#eslint-plugin-jsdoc-rules-require-returns-check
+ 'jsdoc/require-returns-check': 0,
+ // https://github.com/gajus/eslint-plugin-jsdoc#eslint-plugin-jsdoc-rules-require-returns-description
+ 'jsdoc/require-returns-description': 0,
+ // https://github.com/gajus/eslint-plugin-jsdoc#eslint-plugin-jsdoc-rules-valid-types
+ 'jsdoc/valid-types': 2,
+ // https://github.com/gajus/eslint-plugin-jsdoc#eslint-plugin-jsdoc-rules-require-file-overview
+ 'jsdoc/require-file-overview': ['error', {
+ tags: {
+ license: {
+ mustExist: true,
+ preventDuplicates: true,
+ },
+ },
+ }],
+ // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-self-import.md
+ 'import/no-self-import': 2,
+ // The no-cycle rule is slow, because it doesn't cache dependencies.
+ // Disable it.
+ // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-cycle.md
+ 'import/no-cycle': 0,
+ // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-useless-path-segments.md
+ 'import/no-useless-path-segments': 2,
+ // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-unused-modules.md
+ 'import/no-unused-modules': 2,
+ // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-default-export.md
+ 'import/no-default-export': 2,
+ // Prevents certain identifiers being used.
+ // Prefer flush() over flushAsynchronousOperations().
+ 'id-blacklist': ['error', 'flushAsynchronousOperations'],
+ },
+
+ overrides: [
+ {
+ files: ['.eslintrc.js'],
+ env: {
+ browser: false,
+ es6: true,
+ node: true,
+ },
+ },
+ {
+ // .js-only rules
+ files: ['**/*.js'],
+ rules: {
+ // https://github.com/gajus/eslint-plugin-jsdoc#eslint-plugin-jsdoc-rules-require-param-type
+ 'jsdoc/require-param-type': 2,
+ // https://github.com/gajus/eslint-plugin-jsdoc#eslint-plugin-jsdoc-rules-require-returns-type
+ 'jsdoc/require-returns-type': 2,
+ // The rule is required for .js files only, because typescript compiler
+ // always checks import.
+ 'import/no-unresolved': 2,
+ 'import/named': 2,
+ },
+ },
+ {
+ files: ['**/*.ts'],
+ extends: [require.resolve('gts/.eslintrc.json')],
+ rules: {
+ 'no-restricted-imports': ['error', {
+ name: '@polymer/decorators/lib/decorators',
+ message: 'Use @polymer/decorators instead',
+ }],
+ '@typescript-eslint/no-explicit-any': 'error',
+ // See https://github.com/GoogleChromeLabs/shadow-selection-polyfill/issues/9
+ '@typescript-eslint/ban-ts-comment': 'off',
+ // The following rules is required to match internal google rules
+ '@typescript-eslint/restrict-plus-operands': 'error',
+ '@typescript-eslint/no-unused-vars': [
+ 'error',
+ {argsIgnorePattern: '^_'},
+ ],
+ // https://github.com/mysticatea/eslint-plugin-node/blob/master/docs/rules/no-unsupported-features/node-builtins.md
+ 'node/no-unsupported-features/node-builtins': 'off',
+ // Disable no-invalid-this for ts files, because it incorrectly reports
+ // errors in some cases (see https://github.com/typescript-eslint/typescript-eslint/issues/491)
+ // At the same time, we are using typescript in a strict mode and
+ // 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: {
+ // The __plugindir variable has to be defined by the plugin config.
+ project: path.resolve(__dirname, __plugindir, 'tsconfig.json'),
+ },
+ },
+ ],
+ plugins: [
+ 'html',
+ 'jsdoc',
+ 'import',
+ 'prettier',
+ ],
+ settings: {
+ 'html/report-bad-indent': 'error',
+ 'import/resolver': {
+ node: {},
+ },
+ },
+};
diff --git a/plugins/.prettierrc.js b/plugins/.prettierrc.js
new file mode 100644
index 0000000..64dbcb3
--- /dev/null
+++ b/plugins/.prettierrc.js
@@ -0,0 +1,30 @@
+/**
+ * @license
+ * Copyright (C) 2021 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 is a base template for TypeScript plugins.
+ */
+module.exports = {
+ "overrides": [
+ {
+ "files": ["**/*.ts"],
+ "options": {
+ ...require('gts/.prettierrc.json')
+ }
+ }
+ ]
+};
diff --git a/plugins/BUILD b/plugins/BUILD
index ad517ed..250d1a6 100644
--- a/plugins/BUILD
+++ b/plugins/BUILD
@@ -10,7 +10,13 @@
package(default_visibility = ["//visibility:public"])
-exports_files(["rollup.config.js"])
+exports_files([
+ ".eslintrc.js",
+ ".eslintignore",
+ ".prettierrc.js",
+ "rollup.config.js",
+ "tsconfig-plugins-base.json",
+])
ts_config(
name = "plugin-tsconfig",
diff --git a/plugins/delete-project b/plugins/delete-project
index 7f2f1c5..7dce6f7 160000
--- a/plugins/delete-project
+++ b/plugins/delete-project
@@ -1 +1 @@
-Subproject commit 7f2f1c5961f89c7f44ac4a26bf8e035db5e70e0c
+Subproject commit 7dce6f70611cd8dbf1d38628698155258ee8ef82
diff --git a/plugins/package.json b/plugins/package.json
index 2ed03e5..cd85e8c 100644
--- a/plugins/package.json
+++ b/plugins/package.json
@@ -1,6 +1,6 @@
{
- "name": "polygerrit-plugin-dependencies-placeholder",
- "description": "Gerrit Code Review - Polygerrit plugin dependencies placeholder, expected to be overridden by plugins",
+ "name": "gerrit-plugin-dependencies",
+ "description": "Gerrit Code Review - frontend plugin dependencies, each plugin may depend on a subset of these",
"browser": true,
"dependencies": {
"@polymer/decorators": "^3.0.0",
diff --git a/plugins/tsconfig-plugins-base.json b/plugins/tsconfig-plugins-base.json
new file mode 100644
index 0000000..b9d14e1
--- /dev/null
+++ b/plugins/tsconfig-plugins-base.json
@@ -0,0 +1,39 @@
+{
+ "compilerOptions": {
+ /* Basic Options */
+ "target": "es2018", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
+ "module": "es2015", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
+ "inlineSourceMap": true, /* Generates corresponding '.map' file. */
+ "rootDir": ".", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
+ "removeComments": false, /* Emit comments to output */
+
+ /* Strict Type-Checking Options */
+ "strict": true, /* Enable all strict type-checking options. */
+ "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
+ "strictNullChecks": true, /* Enable strict null checks. */
+ "strictFunctionTypes": true, /* Enable strict checking of function types. */
+ "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
+ "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
+ "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
+
+ /* Additional Checks */
+ "noUnusedLocals": true, /* Report errors on unused locals. */
+ "noUnusedParameters": true, /* Report errors on unused parameters. */
+ "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
+ "noFallthroughCasesInSwitch": true,/* Report errors for fallthrough cases in switch statement. */
+
+ "skipLibCheck": true, /* Do not check node_modules */
+
+ /* Module Resolution Options */
+ "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
+ "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
+ "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
+
+ /* Advanced Options */
+ "forceConsistentCasingInFileNames": true, /* Disallow inconsistently-cased references to the same file. */
+ "incremental": true,
+ "experimentalDecorators": true,
+
+ "allowUmdGlobalAccess": true
+ },
+}
diff --git a/polygerrit-ui/.gitignore b/polygerrit-ui/.gitignore
index fb20ac5..3e1bb74 100644
--- a/polygerrit-ui/.gitignore
+++ b/polygerrit-ui/.gitignore
@@ -1,8 +1,8 @@
+/.tmp/
+/.vscode/
+/bower_components/
+/dist/
+/fonts/
/node_modules/
+/npm-debug.log
/package-lock.json
-npm-debug.log
-dist
-fonts
-bower_components
-.tmp
-.vscode
diff --git a/polygerrit-ui/app/.gitignore b/polygerrit-ui/app/.gitignore
index 6b96e60..c45bac3 100644
--- a/polygerrit-ui/app/.gitignore
+++ b/polygerrit-ui/app/.gitignore
@@ -1,3 +1,4 @@
-/plugins/
/node_modules/
+/package-lock.json
+/plugins/
/tmpl_out/
diff --git a/polygerrit-ui/app/BUILD b/polygerrit-ui/app/BUILD
index 63d85a2..4a186c1 100644
--- a/polygerrit-ui/app/BUILD
+++ b/polygerrit-ui/app/BUILD
@@ -100,7 +100,6 @@
"elements/admin/gr-access-section/gr-access-section_html.ts",
"elements/admin/gr-admin-view/gr-admin-view_html.ts",
"elements/admin/gr-create-change-dialog/gr-create-change-dialog_html.ts",
- "elements/admin/gr-create-pointer-dialog/gr-create-pointer-dialog_html.ts",
"elements/admin/gr-create-repo-dialog/gr-create-repo-dialog_html.ts",
"elements/admin/gr-group-members/gr-group-members_html.ts",
"elements/admin/gr-group/gr-group_html.ts",
@@ -132,7 +131,6 @@
"elements/change/gr-thread-list/gr-thread-list_html.ts",
"elements/diff/gr-diff-builder/gr-diff-builder-element_html.ts",
"elements/diff/gr-diff-host/gr-diff-host_html.ts",
- "elements/diff/gr-diff-mode-selector/gr-diff-mode-selector_html.ts",
"elements/diff/gr-diff-preferences-dialog/gr-diff-preferences-dialog_html.ts",
"elements/diff/gr-diff-view/gr-diff-view_html.ts",
"elements/diff/gr-diff/gr-diff_html.ts",
diff --git a/polygerrit-ui/app/elements/admin/gr-create-pointer-dialog/gr-create-pointer-dialog.ts b/polygerrit-ui/app/elements/admin/gr-create-pointer-dialog/gr-create-pointer-dialog.ts
index e483fc4..7fce8e5 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-pointer-dialog/gr-create-pointer-dialog.ts
+++ b/polygerrit-ui/app/elements/admin/gr-create-pointer-dialog/gr-create-pointer-dialog.ts
@@ -94,7 +94,7 @@
throw new Error(`Invalid itemDetail: ${this.itemDetail}`);
}
- _computeHideItemClass(type: RepoDetailView.BRANCHES | RepoDetailView.TAGS) {
+ _computeHideItemClass(type?: RepoDetailView.BRANCHES | RepoDetailView.TAGS) {
return type === RepoDetailView.BRANCHES ? 'hideItem' : '';
}
}
diff --git a/polygerrit-ui/app/elements/admin/gr-create-pointer-dialog/gr-create-pointer-dialog_html.ts b/polygerrit-ui/app/elements/admin/gr-create-pointer-dialog/gr-create-pointer-dialog_html.ts
index 452aab7..0e2b157 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-pointer-dialog/gr-create-pointer-dialog_html.ts
+++ b/polygerrit-ui/app/elements/admin/gr-create-pointer-dialog/gr-create-pointer-dialog_html.ts
@@ -38,28 +38,14 @@
<div id="form">
<section id="itemNameSection">
<span class="title">[[detailType]] name</span>
- <iron-input
- placeholder="[[detailType]] Name"
- bind-value="{{_itemName}}"
- >
- <input
- is="iron-input"
- placeholder="[[detailType]] Name"
- bind-value="{{_itemName}}"
- />
+ <iron-input bind-value="{{_itemName}}">
+ <input placeholder="[[detailType]] Name" />
</iron-input>
</section>
<section id="itemRevisionSection">
<span class="title">Initial Revision</span>
- <iron-input
- placeholder="Revision (Branch or SHA-1)"
- bind-value="{{_itemRevision}}"
- >
- <input
- is="iron-input"
- placeholder="Revision (Branch or SHA-1)"
- bind-value="{{_itemRevision}}"
- />
+ <iron-input bind-value="{{_itemRevision}}">
+ <input placeholder="Revision (Branch or SHA-1)" />
</iron-input>
</section>
<section
@@ -67,15 +53,8 @@
class$="[[_computeHideItemClass(itemDetail)]]"
>
<span class="title">Annotation</span>
- <iron-input
- placeholder="Annotation (Optional)"
- bind-value="{{_itemAnnotation}}"
- >
- <input
- is="iron-input"
- placeholder="Annotation (Optional)"
- bind-value="{{_itemAnnotation}}"
- />
+ <iron-input bind-value="{{_itemAnnotation}}">
+ <input placeholder="Annotation (Optional)" />
</iron-input>
</section>
</div>
diff --git a/polygerrit-ui/app/elements/admin/gr-create-pointer-dialog/gr-create-pointer-dialog_test.js b/polygerrit-ui/app/elements/admin/gr-create-pointer-dialog/gr-create-pointer-dialog_test.ts
similarity index 60%
rename from polygerrit-ui/app/elements/admin/gr-create-pointer-dialog/gr-create-pointer-dialog_test.js
rename to polygerrit-ui/app/elements/admin/gr-create-pointer-dialog/gr-create-pointer-dialog_test.ts
index 60af4d5..b6a08b87 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-pointer-dialog/gr-create-pointer-dialog_test.js
+++ b/polygerrit-ui/app/elements/admin/gr-create-pointer-dialog/gr-create-pointer-dialog_test.ts
@@ -15,69 +15,72 @@
* limitations under the License.
*/
-import '../../../test/common-test-setup-karma.js';
-import './gr-create-pointer-dialog.js';
-import {stubRestApi} from '../../../test/test-utils.js';
+import '../../../test/common-test-setup-karma';
+import './gr-create-pointer-dialog';
+import {GrCreatePointerDialog} from './gr-create-pointer-dialog';
+import {queryAndAssert, stubRestApi} from '../../../test/test-utils';
+import {BranchName} from '../../../types/common';
+import {RepoDetailView} from '../../core/gr-navigation/gr-navigation';
+import {IronInputElement} from '@polymer/iron-input';
const basicFixture = fixtureFromElement('gr-create-pointer-dialog');
suite('gr-create-pointer-dialog tests', () => {
- let element;
+ let element: GrCreatePointerDialog;
- const ironInput = function(element) {
- return element.querySelector('iron-input');
- };
+ const ironInput = (element: Element) =>
+ queryAndAssert<IronInputElement>(element, 'iron-input');
setup(() => {
element = basicFixture.instantiate();
});
test('branch created', done => {
- stubRestApi('createRepoBranch').returns(Promise.resolve({}));
+ stubRestApi('createRepoBranch').returns(Promise.resolve(new Response()));
assert.isFalse(element.hasNewItemName);
- element._itemName = 'test-branch';
- element.itemDetail = 'branches';
+ element._itemName = 'test-branch' as BranchName;
+ element.itemDetail = 'branches' as RepoDetailView.BRANCHES;
ironInput(element.$.itemNameSection).bindValue = 'test-branch2';
ironInput(element.$.itemRevisionSection).bindValue = 'HEAD';
setTimeout(() => {
assert.isTrue(element.hasNewItemName);
- assert.equal(element._itemName, 'test-branch2');
+ assert.equal(element._itemName, 'test-branch2' as BranchName);
assert.equal(element._itemRevision, 'HEAD');
done();
});
});
test('tag created', done => {
- stubRestApi('createRepoTag').returns(Promise.resolve({}));
+ stubRestApi('createRepoTag').returns(Promise.resolve(new Response()));
assert.isFalse(element.hasNewItemName);
- element._itemName = 'test-tag';
- element.itemDetail = 'tags';
+ element._itemName = 'test-tag' as BranchName;
+ element.itemDetail = 'tags' as RepoDetailView.TAGS;
ironInput(element.$.itemNameSection).bindValue = 'test-tag2';
ironInput(element.$.itemRevisionSection).bindValue = 'HEAD';
setTimeout(() => {
assert.isTrue(element.hasNewItemName);
- assert.equal(element._itemName, 'test-tag2');
+ assert.equal(element._itemName, 'test-tag2' as BranchName);
assert.equal(element._itemRevision, 'HEAD');
done();
});
});
test('tag created with annotations', done => {
- stubRestApi('createRepoTag').returns(() => Promise.resolve({}));
+ stubRestApi('createRepoTag').returns(Promise.resolve(new Response()));
assert.isFalse(element.hasNewItemName);
- element._itemName = 'test-tag';
+ element._itemName = 'test-tag' as BranchName;
element._itemAnnotation = 'test-message';
- element.itemDetail = 'tags';
+ element.itemDetail = 'tags' as RepoDetailView.TAGS;
ironInput(element.$.itemNameSection).bindValue = 'test-tag2';
ironInput(element.$.itemAnnotationSection).bindValue = 'test-message2';
@@ -85,7 +88,7 @@
setTimeout(() => {
assert.isTrue(element.hasNewItemName);
- assert.equal(element._itemName, 'test-tag2');
+ assert.equal(element._itemName, 'test-tag2' as BranchName);
assert.equal(element._itemAnnotation, 'test-message2');
assert.equal(element._itemRevision, 'HEAD');
done();
@@ -93,11 +96,13 @@
});
test('_computeHideItemClass returns hideItem if type is branches', () => {
- assert.equal(element._computeHideItemClass('branches'), 'hideItem');
+ assert.equal(
+ element._computeHideItemClass(RepoDetailView.BRANCHES),
+ 'hideItem'
+ );
});
test('_computeHideItemClass returns strings if not branches', () => {
- assert.equal(element._computeHideItemClass('tags'), '');
+ assert.equal(element._computeHideItemClass(RepoDetailView.TAGS), '');
});
});
-
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.ts b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.ts
index 80c58ca..411bb27 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.ts
@@ -91,7 +91,7 @@
_groups?: ProjectAccessGroups;
@property({type: Object})
- _inheritsFrom?: ProjectInfo | null | {};
+ _inheritsFrom?: ProjectInfo;
@property({type: Object})
_labels?: LabelNameToLabelTypeInfoMap;
@@ -114,7 +114,7 @@
@property({type: Boolean})
_loading = true;
- private originalInheritsFrom?: ProjectInfo | null;
+ private originalInheritsFrom?: ProjectInfo;
private readonly restApiService = appContext.restApiService;
@@ -159,16 +159,13 @@
// Keep a copy of the original inherit from values separate from
// the ones data bound to gr-autocomplete, so the original value
// can be restored if the user cancels.
- this._inheritsFrom = res.inherits_from
- ? {
- ...res.inherits_from,
- }
- : null;
- this.originalInheritsFrom = res.inherits_from
- ? {
- ...res.inherits_from,
- }
- : null;
+ if (res.inherits_from) {
+ this._inheritsFrom = {...res.inherits_from};
+ this.originalInheritsFrom = {...res.inherits_from};
+ } else {
+ this._inheritsFrom = undefined;
+ this.originalInheritsFrom = undefined;
+ }
// Initialize the filter value so when the user clicks edit, the
// current value appears. If there is no parent repo, it is
// initialized as an empty string.
@@ -218,19 +215,11 @@
}
_handleUpdateInheritFrom(e: CustomEvent<{value: string}>) {
- const parentProject: ProjectInfo = {
+ this._inheritsFrom = {
+ ...(this._inheritsFrom ?? {}),
id: e.detail.value as UrlEncodedRepoName,
name: this._inheritFromFilter,
};
- if (!this._inheritsFrom) {
- this._inheritsFrom = parentProject;
- } else {
- // TODO(TS): replace with
- // this._inheritsFrom = {...this._inheritsFrom, ...parentProject};
- const projectInfo = this._inheritsFrom as ProjectInfo;
- projectInfo.id = parentProject.id;
- projectInfo.name = parentProject.name;
- }
this._handleAccessModified();
}
@@ -268,8 +257,8 @@
return weblinks && weblinks.length ? 'show' : '';
}
- _computeShowInherit(inheritsFrom?: RepoName) {
- return inheritsFrom ? 'show' : '';
+ _computeShowInherit(inheritsFrom?: ProjectInfo) {
+ return inheritsFrom?.id?.length ? 'show' : '';
}
// TODO(TS): Unclear what is model here, provide a better explanation
@@ -297,18 +286,10 @@
}
// Restore inheritFrom.
if (this._inheritsFrom) {
- // Can't assign this._inheritsFrom = {...this.originalInheritsFrom}
- // directly, because this._inheritsFrom is declared as
- // '...|null|undefined` and typescript reports error when trying
- // to access .name property (because 'name' in null and 'name' in undefined
- // lead to runtime error)
- // After migrating to Typescript v4.2 the code below can be rewritten as
- // const copy = {...this.originalInheritsFrom};
- const copy: ProjectInfo | {} = this.originalInheritsFrom
+ this._inheritsFrom = this.originalInheritsFrom
? {...this.originalInheritsFrom}
- : {};
- this._inheritsFrom = copy;
- this._inheritFromFilter = 'name' in copy ? copy.name : undefined;
+ : undefined;
+ this._inheritFromFilter = this.originalInheritsFrom?.name;
}
if (!this._local) {
return;
@@ -448,12 +429,10 @@
const originalInheritsFromId = this.originalInheritsFrom
? singleDecodeURL(this.originalInheritsFrom.id)
- : null;
- // TODO(TS): this._inheritsFrom as ProjectInfo might be a mistake.
- // _inheritsFrom can be {}
+ : undefined;
const inheritsFromId = this._inheritsFrom
- ? singleDecodeURL((this._inheritsFrom as ProjectInfo).id)
- : null;
+ ? singleDecodeURL(this._inheritsFrom.id)
+ : undefined;
const inheritFromChanged =
// Inherit from changed
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access_test.js b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access_test.js
index a4e019e..2b7fdf5 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access_test.js
+++ b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access_test.js
@@ -100,22 +100,24 @@
name: 'Create Account',
},
};
- setup(() => {
+ setup(async () => {
element = basicFixture.instantiate();
stubRestApi('getAccount').returns(Promise.resolve(null));
repoStub = stubRestApi('getRepo').returns(Promise.resolve(repoRes));
element._loading = false;
element._ownerOf = [];
element._canUpload = false;
+ await flush();
});
- test('_repoChanged called when repo name changes', () => {
+ test('_repoChanged called when repo name changes', async () => {
sinon.stub(element, '_repoChanged');
element.repo = 'New Repo';
+ await flush();
assert.isTrue(element._repoChanged.called);
});
- test('_repoChanged', done => {
+ test('_repoChanged', async () => {
const accessStub = stubRestApi(
'getRepoAccessRights');
@@ -127,31 +129,28 @@
'getCapabilities');
capabilitiesStub.returns(Promise.resolve(capabilitiesRes));
- element._repoChanged('New Repo').then(() => {
- assert.isTrue(accessStub.called);
- assert.isTrue(capabilitiesStub.called);
- assert.isTrue(repoStub.called);
- assert.isNotOk(element._inheritsFrom);
- assert.deepEqual(element._local, accessRes.local);
- assert.deepEqual(element._sections,
- toSortedPermissionsArray(accessRes.local));
- assert.deepEqual(element._labels, repoRes.labels);
- assert.equal(getComputedStyle(element.shadowRoot
- .querySelector('.weblinks')).display,
- 'block');
- return element._repoChanged('Another New Repo');
- })
- .then(() => {
- assert.deepEqual(element._sections,
- toSortedPermissionsArray(accessRes2.local));
- assert.equal(getComputedStyle(element.shadowRoot
- .querySelector('.weblinks')).display,
- 'none');
- done();
- });
+ await element._repoChanged('New Repo');
+ assert.isTrue(accessStub.called);
+ assert.isTrue(capabilitiesStub.called);
+ assert.isTrue(repoStub.called);
+ assert.isNotOk(element._inheritsFrom);
+ assert.deepEqual(element._local, accessRes.local);
+ assert.deepEqual(element._sections,
+ toSortedPermissionsArray(accessRes.local));
+ assert.deepEqual(element._labels, repoRes.labels);
+ assert.equal(getComputedStyle(element.shadowRoot
+ .querySelector('.weblinks')).display,
+ 'block');
+
+ await element._repoChanged('Another New Repo');
+ assert.deepEqual(element._sections,
+ toSortedPermissionsArray(accessRes2.local));
+ assert.equal(getComputedStyle(element.shadowRoot
+ .querySelector('.weblinks')).display,
+ 'none');
});
- test('_repoChanged when repo changes to undefined returns', done => {
+ test('_repoChanged when repo changes to undefined returns', async () => {
const capabilitiesRes = {
accessDatabase: {
id: 'accessDatabase',
@@ -163,12 +162,10 @@
const capabilitiesStub = stubRestApi(
'getCapabilities').returns(Promise.resolve(capabilitiesRes));
- element._repoChanged().then(() => {
- assert.isFalse(accessStub.called);
- assert.isFalse(capabilitiesStub.called);
- assert.isFalse(repoStub.called);
- done();
- });
+ await element._repoChanged();
+ assert.isFalse(accessStub.called);
+ assert.isFalse(capabilitiesStub.called);
+ assert.isFalse(repoStub.called);
});
test('_computeParentHref', () => {
@@ -190,24 +187,29 @@
'editing');
});
- test('inherit section', () => {
+ test('inherit section', async () => {
element._local = {};
element._ownerOf = [];
sinon.stub(element, '_computeParentHref');
+ await flush();
+
// Nothing should appear when no inherit from and not in edit mode.
assert.equal(getComputedStyle(element.$.inheritsFrom).display, 'none');
// The autocomplete should be hidden, and the link should be displayed.
assert.isFalse(element._computeParentHref.called);
- // When it edit mode, the autocomplete should appear.
+ // When in edit mode, the autocomplete should appear.
element._editing = true;
// When editing, the autocomplete should still not be shown.
assert.equal(getComputedStyle(element.$.inheritsFrom).display, 'none');
+
element._editing = false;
element._inheritsFrom = {
+ id: '1234',
name: 'another-repo',
};
+ await flush();
+
// When there is a parent project, the link should be displayed.
- flush();
assert.notEqual(getComputedStyle(element.$.inheritsFrom).display, 'none');
assert.notEqual(getComputedStyle(element.$.inheritFromName).display,
'none');
@@ -222,9 +224,10 @@
'none');
});
- test('_handleUpdateInheritFrom', () => {
+ test('_handleUpdateInheritFrom', async () => {
element._inheritFromFilter = 'foo bar baz';
element._handleUpdateInheritFrom({detail: {value: 'abc+123'}});
+ await flush();
assert.isOk(element._inheritsFrom);
assert.equal(element._inheritsFrom.id, 'abc+123');
assert.equal(element._inheritsFrom.name, 'foo bar baz');
@@ -251,46 +254,61 @@
});
suite('with defined sections', () => {
- const testEditSaveCancelBtns = (shouldShowSave, shouldShowSaveReview) => {
+ const testEditSaveCancelBtns = async (
+ shouldShowSave,
+ shouldShowSaveReview
+ ) => {
// Edit button is visible and Save button is hidden.
assert.equal(getComputedStyle(element.$.saveReviewBtn).display, 'none');
assert.equal(getComputedStyle(element.$.saveBtn).display, 'none');
assert.notEqual(getComputedStyle(element.$.editBtn).display, 'none');
assert.equal(element.$.editBtn.innerText, 'EDIT');
- assert.equal(getComputedStyle(element.$.editInheritFromInput).display,
- 'none');
+ assert.equal(
+ getComputedStyle(element.$.editInheritFromInput).display,
+ 'none'
+ );
element._inheritsFrom = {
id: 'test-project',
};
- flush();
- assert.equal(getComputedStyle(element.shadowRoot
- .querySelector('#editInheritFromInput'))
- .display, 'none');
+ await flush();
+ assert.equal(
+ getComputedStyle(
+ element.shadowRoot.querySelector('#editInheritFromInput')
+ ).display,
+ 'none'
+ );
MockInteractions.tap(element.$.editBtn);
- flush();
+ await flush();
// Edit button changes to Cancel button, and Save button is visible but
// disabled.
assert.equal(element.$.editBtn.innerText, 'CANCEL');
if (shouldShowSaveReview) {
- assert.notEqual(getComputedStyle(element.$.saveReviewBtn).display,
- 'none');
+ assert.notEqual(
+ getComputedStyle(element.$.saveReviewBtn).display,
+ 'none'
+ );
assert.isTrue(element.$.saveReviewBtn.disabled);
}
if (shouldShowSave) {
assert.notEqual(getComputedStyle(element.$.saveBtn).display, 'none');
assert.isTrue(element.$.saveBtn.disabled);
}
- assert.notEqual(getComputedStyle(element.shadowRoot
- .querySelector('#editInheritFromInput'))
- .display, 'none');
+ assert.notEqual(
+ getComputedStyle(
+ element.shadowRoot.querySelector('#editInheritFromInput')
+ ).display,
+ 'none'
+ );
// Save button should be enabled after access is modified
element.dispatchEvent(
new CustomEvent('access-modified', {
- composed: true, bubbles: true,
- }));
+ composed: true,
+ bubbles: true,
+ })
+ );
if (shouldShowSaveReview) {
assert.isFalse(element.$.saveReviewBtn.disabled);
}
@@ -299,7 +317,7 @@
}
};
- setup(() => {
+ setup(async () => {
// Create deep copies of these objects so the originals are not modified
// by any tests.
element._local = JSON.parse(JSON.stringify(accessRes.local));
@@ -308,18 +326,19 @@
element._groups = JSON.parse(JSON.stringify(accessRes.groups));
element._capabilities = JSON.parse(JSON.stringify(capabilitiesRes));
element._labels = JSON.parse(JSON.stringify(repoRes.labels));
- flush();
+ await flush();
});
- test('removing an added section', () => {
+ test('removing an added section', async () => {
element.editing = true;
+ await flush();
assert.equal(element._sections.length, 1);
element.shadowRoot
.querySelector('gr-access-section').dispatchEvent(
new CustomEvent('added-section-removed', {
composed: true, bubbles: true,
}));
- flush();
+ await flush();
assert.equal(element._sections.length, 0);
});
@@ -328,36 +347,41 @@
assert.equal(getComputedStyle(element.$.editBtn).display, 'none');
});
- test('button visibility for non ref owner with upload privilege', () => {
- element._canUpload = true;
- testEditSaveCancelBtns(false, true);
- });
+ test('button visibility for non ref owner with upload privilege',
+ async () => {
+ element._canUpload = true;
+ await flush();
+ testEditSaveCancelBtns(false, true);
+ });
- test('button visibility for ref owner', () => {
+ test('button visibility for ref owner', async () => {
element._ownerOf = ['refs/for/*'];
+ await flush();
testEditSaveCancelBtns(true, false);
});
- test('button visibility for ref owner and upload', () => {
+ test('button visibility for ref owner and upload', async () => {
element._ownerOf = ['refs/for/*'];
element._canUpload = true;
+ await flush();
testEditSaveCancelBtns(true, false);
});
- test('_handleAccessModified called with event fired', () => {
+ test('_handleAccessModified called with event fired', async () => {
sinon.spy(element, '_handleAccessModified');
element.dispatchEvent(
new CustomEvent('access-modified', {
composed: true, bubbles: true,
}));
+ await flush();
assert.isTrue(element._handleAccessModified.called);
});
- test('_handleAccessModified called when parent changes', () => {
+ test('_handleAccessModified called when parent changes', async () => {
element._inheritsFrom = {
id: 'test-project',
};
- flush();
+ await flush();
element.shadowRoot.querySelector('#editInheritFromInput').dispatchEvent(
new CustomEvent('commit', {
detail: {},
@@ -369,10 +393,11 @@
detail: {},
composed: true, bubbles: true,
}));
+ await flush();
assert.isTrue(element._handleAccessModified.called);
});
- test('_handleSaveForReview', () => {
+ test('_handleSaveForReview', async () => {
const saveStub =
stubRestApi('setRepoAccessRightsForReview');
sinon.stub(element, '_computeAddAndRemove').returns({
@@ -380,6 +405,7 @@
remove: {},
});
element._handleSaveForReview();
+ await flush();
assert.isFalse(saveStub.called);
});
@@ -487,29 +513,32 @@
assert.deepEqual(element._computeAddAndRemove(), {add: {}, remove: {}});
});
- test('_handleSaveForReview parent change', () => {
+ test('_handleSaveForReview parent change', async () => {
element._inheritsFrom = {
id: 'test-project',
};
element._originalInheritsFrom = {
id: 'test-project-original',
};
+ await flush();
assert.deepEqual(element._computeAddAndRemove(), {
parent: 'test-project', add: {}, remove: {},
});
});
- test('_handleSaveForReview new parent with spaces', () => {
+ test('_handleSaveForReview new parent with spaces', async () => {
element._inheritsFrom = {id: 'spaces+in+project+name'};
element._originalInheritsFrom = {id: 'old-project'};
+ await flush();
assert.deepEqual(element._computeAddAndRemove(), {
parent: 'spaces in project name', add: {}, remove: {},
});
});
- test('_handleSaveForReview rules', () => {
+ test('_handleSaveForReview rules', async () => {
// Delete a rule.
element._local['refs/*'].permissions.owner.rules[123].deleted = true;
+ await flush();
let expectedInput = {
add: {},
remove: {
@@ -531,6 +560,7 @@
// Modify a rule.
element._local['refs/*'].permissions.owner.rules[123].modified = true;
+ await flush();
expectedInput = {
add: {
'refs/*': {
@@ -558,7 +588,7 @@
assert.deepEqual(element._computeAddAndRemove(), expectedInput);
});
- test('_computeAddAndRemove permissions', () => {
+ test('_computeAddAndRemove permissions', async () => {
// Add a new rule to a permission.
let expectedInput = {
add: {
@@ -584,7 +614,7 @@
._handleAddRuleItem(
{detail: {value: 'Maintainers'}});
- flush();
+ await flush();
assert.deepEqual(element._computeAddAndRemove(), expectedInput);
// Remove the added rule.
@@ -592,6 +622,7 @@
// Delete a permission.
element._local['refs/*'].permissions.owner.deleted = true;
+ await flush();
expectedInput = {
add: {},
remove: {
@@ -609,6 +640,7 @@
// Modify a permission.
element._local['refs/*'].permissions.owner.modified = true;
+ await flush();
expectedInput = {
add: {
'refs/*': {
@@ -634,7 +666,7 @@
assert.deepEqual(element._computeAddAndRemove(), expectedInput);
});
- test('_computeAddAndRemove sections', () => {
+ test('_computeAddAndRemove sections', async () => {
// Add a new permission to a section
let expectedInput = {
add: {
@@ -652,7 +684,7 @@
};
element.shadowRoot
.querySelector('gr-access-section')._handleAddPermission();
- flush();
+ await flush();
assert.deepEqual(element._computeAddAndRemove(), expectedInput);
// Add a new rule to the new permission.
@@ -683,11 +715,13 @@
'gr-permission')[2];
newPermission._handleAddRuleItem(
{detail: {value: 'Maintainers'}});
+ await flush();
assert.deepEqual(element._computeAddAndRemove(), expectedInput);
// Modify a section reference.
element._local['refs/*'].updatedId = 'refs/for/bar';
element._local['refs/*'].modified = true;
+ await flush();
expectedInput = {
add: {
'refs/for/bar': {
@@ -726,10 +760,12 @@
},
},
};
+ await flush();
assert.deepEqual(element._computeAddAndRemove(), expectedInput);
// Delete a section.
element._local['refs/*'].deleted = true;
+ await flush();
expectedInput = {
add: {},
remove: {
@@ -741,7 +777,7 @@
assert.deepEqual(element._computeAddAndRemove(), expectedInput);
});
- test('_computeAddAndRemove new section', () => {
+ test('_computeAddAndRemove new section', async () => {
// Add a new permission to a section
let expectedInput = {
add: {
@@ -753,6 +789,7 @@
remove: {},
};
MockInteractions.tap(element.$.addReferenceBtn);
+ await flush();
assert.deepEqual(element._computeAddAndRemove(), expectedInput);
expectedInput = {
@@ -773,7 +810,7 @@
const newSection = dom(element.root)
.querySelectorAll('gr-access-section')[1];
newSection._handleAddPermission();
- flush();
+ await flush();
assert.deepEqual(element._computeAddAndRemove(), expectedInput);
// Add rule to the new permission.
@@ -803,12 +840,12 @@
newSection.shadowRoot
.querySelector('gr-permission')._handleAddRuleItem(
{detail: {value: 'Maintainers'}});
-
- flush();
+ await flush();
assert.deepEqual(element._computeAddAndRemove(), expectedInput);
// Modify a the reference from the default value.
element._local['refs/for/*'].updatedId = 'refs/for/new';
+ await flush();
expectedInput = {
add: {
'refs/for/new': {
@@ -835,10 +872,11 @@
assert.deepEqual(element._computeAddAndRemove(), expectedInput);
});
- test('_computeAddAndRemove combinations', () => {
+ test('_computeAddAndRemove combinations', async () => {
// Modify rule and delete permission that it is inside of.
element._local['refs/*'].permissions.owner.rules[123].modified = true;
element._local['refs/*'].permissions.owner.deleted = true;
+ await flush();
let expectedInput = {
add: {},
remove: {
@@ -853,10 +891,12 @@
// Delete rule and delete permission that it is inside of.
element._local['refs/*'].permissions.owner.rules[123].modified = false;
element._local['refs/*'].permissions.owner.rules[123].deleted = true;
+ await flush();
assert.deepEqual(element._computeAddAndRemove(), expectedInput);
// Also modify a different rule inside of another permission.
element._local['refs/*'].permissions.read.modified = true;
+ await flush();
expectedInput = {
add: {
'refs/*': {
@@ -886,6 +926,7 @@
element._local['refs/*'].permissions.owner.modified = true;
element._local['refs/*'].permissions.read.exclusive = true;
element._local['refs/*'].permissions.read.modified = true;
+ await flush();
expectedInput = {
add: {
'refs/*': {
@@ -918,6 +959,7 @@
'gr-permission')[1];
readPermission._handleAddRuleItem(
{detail: {value: 'Maintainers'}});
+ await flush();
expectedInput = {
add: {
@@ -948,6 +990,7 @@
// Change one of the refs
element._local['refs/*'].updatedId = 'refs/for/bar';
element._local['refs/*'].modified = true;
+ await flush();
expectedInput = {
add: {
@@ -983,6 +1026,7 @@
},
};
element._local['refs/*'].deleted = true;
+ await flush();
assert.deepEqual(element._computeAddAndRemove(), expectedInput);
// Add a new section.
@@ -990,12 +1034,13 @@
let newSection = dom(element.root)
.querySelectorAll('gr-access-section')[1];
newSection._handleAddPermission();
- flush();
+ await flush();
newSection.shadowRoot
.querySelector('gr-permission')._handleAddRuleItem(
{detail: {value: 'Maintainers'}});
// Modify a the reference from the default value.
element._local['refs/for/*'].updatedId = 'refs/for/new';
+ await flush();
expectedInput = {
add: {
@@ -1029,6 +1074,7 @@
// Modify newly added rule inside new ref.
element._local['refs/for/*'].permissions['label-Code-Review'].
rules['Maintainers'].modified = true;
+ await flush();
expectedInput = {
add: {
'refs/for/new': {
@@ -1061,15 +1107,17 @@
// Add a second new section.
MockInteractions.tap(element.$.addReferenceBtn);
+ await flush();
newSection = dom(element.root)
.querySelectorAll('gr-access-section')[2];
newSection._handleAddPermission();
- flush();
+ await flush();
newSection.shadowRoot
.querySelector('gr-permission')._handleAddRuleItem(
{detail: {value: 'Maintainers'}});
// Modify a the reference from the default value.
element._local['refs/for/**'].updatedId = 'refs/for/new2';
+ await flush();
expectedInput = {
add: {
'refs/for/new': {
@@ -1119,20 +1167,23 @@
assert.deepEqual(element._computeAddAndRemove(), expectedInput);
});
- test('Unsaved added refs are discarded when edit cancelled', () => {
+ test('Unsaved added refs are discarded when edit cancelled', async () => {
// Unsaved changes are discarded when editing is cancelled.
MockInteractions.tap(element.$.editBtn);
+ await flush();
assert.equal(element._sections.length, 1);
assert.equal(Object.keys(element._local).length, 1);
MockInteractions.tap(element.$.addReferenceBtn);
+ await flush();
assert.equal(element._sections.length, 2);
assert.equal(Object.keys(element._local).length, 2);
MockInteractions.tap(element.$.editBtn);
+ await flush();
assert.equal(element._sections.length, 1);
assert.equal(Object.keys(element._local).length, 1);
});
- test('_handleSave', done => {
+ test('_handleSave', async () => {
const repoAccessInput = {
add: {
'refs/*': {
@@ -1170,16 +1221,15 @@
element._modified = true;
MockInteractions.tap(element.$.saveBtn);
+ await flush();
assert.equal(element.$.saveBtn.hasAttribute('loading'), true);
resolver({_number: 1});
- flush(() => {
- assert.isTrue(saveStub.called);
- assert.isTrue(GerritNav.navigateToChange.notCalled);
- done();
- });
+ await flush();
+ assert.isTrue(saveStub.called);
+ assert.isTrue(GerritNav.navigateToChange.notCalled);
});
- test('_handleSaveForReview', done => {
+ test('_handleSaveForReview', async () => {
const repoAccessInput = {
add: {
'refs/*': {
@@ -1217,14 +1267,13 @@
element._modified = true;
MockInteractions.tap(element.$.saveReviewBtn);
+ await flush();
assert.equal(element.$.saveReviewBtn.hasAttribute('loading'), true);
resolver({_number: 1});
- flush(() => {
- assert.isTrue(saveForReviewStub.called);
- assert.isTrue(GerritNav.navigateToChange
- .lastCall.calledWithExactly({_number: 1}));
- done();
- });
+ await flush();
+ assert.isTrue(saveForReviewStub.called);
+ assert.isTrue(GerritNav.navigateToChange
+ .lastCall.calledWithExactly({_number: 1}));
});
});
});
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards.ts b/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards.ts
index 98f75ce..4cc0a80 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards.ts
@@ -15,16 +15,15 @@
* limitations under the License.
*/
-import '../../../styles/shared-styles';
-import {flush} from '@polymer/polymer/lib/legacy/polymer.dom';
-import {PolymerElement} from '@polymer/polymer/polymer-element';
-import {htmlTemplate} from './gr-repo-dashboards_html';
import {GerritNav} from '../../core/gr-navigation/gr-navigation';
-import {customElement, property} from '@polymer/decorators';
import {RepoName, DashboardId, DashboardInfo} from '../../../types/common';
import {firePageError} from '../../../utils/event-util';
import {appContext} from '../../../services/app-context';
import {ErrorCallback} from '../../../api/rest';
+import {sharedStyles} from '../../../styles/shared-styles';
+import {tableStyles} from '../../../styles/gr-table-styles';
+import {GrLitElement} from '../../lit/gr-lit-element';
+import {css, customElement, html, property, PropertyValues} from 'lit-element';
interface DashboardRef {
section: string;
@@ -32,12 +31,8 @@
}
@customElement('gr-repo-dashboards')
-export class GrRepoDashboards extends PolymerElement {
- static get template() {
- return htmlTemplate;
- }
-
- @property({type: String, observer: '_repoChanged'})
+export class GrRepoDashboards extends GrLitElement {
+ @property({type: String})
repo?: RepoName;
@property({type: Boolean})
@@ -48,7 +43,85 @@
private readonly restApiService = appContext.restApiService;
- _repoChanged(repo?: RepoName) {
+ static get styles() {
+ return [
+ sharedStyles,
+ tableStyles,
+ css`
+ :host {
+ display: block;
+ margin-bottom: var(--spacing-xxl);
+ }
+ .loading #dashboards,
+ #loadingContainer {
+ display: none;
+ }
+ .loading #loadingContainer {
+ display: block;
+ }
+ `,
+ ];
+ }
+
+ render() {
+ return html` <table
+ id="list"
+ class="genericList ${this._computeLoadingClass(this._loading)}"
+ >
+ <tbody>
+ <tr class="headerRow">
+ <th class="topHeader">Dashboard name</th>
+ <th class="topHeader">Dashboard title</th>
+ <th class="topHeader">Dashboard description</th>
+ <th class="topHeader">Inherited from</th>
+ <th class="topHeader">Default</th>
+ </tr>
+ <tr id="loadingContainer">
+ <td>Loading...</td>
+ </tr>
+ </tbody>
+ <tbody id="dashboards">
+ ${(this._dashboards ?? []).map(
+ item => html`
+ <tr class="groupHeader">
+ <td colspan="5">${item.section}</td>
+ </tr>
+ ${(item.dashboards ?? []).map(
+ info => html`
+ <tr class="table">
+ <td class="name">
+ <a href="${this._getUrl(info.project, info.id)}"
+ >${info.path}</a
+ >
+ </td>
+ <td class="title">${info.title}</td>
+ <td class="desc">${info.description}</td>
+ <td class="inherited">
+ ${this._computeInheritedFrom(
+ info.project,
+ info.defining_project
+ )}
+ </td>
+ <td class="default">
+ ${this._computeIsDefault(info.is_default)}
+ </td>
+ </tr>
+ `
+ )}
+ `
+ )}
+ </tbody>
+ </table>`;
+ }
+
+ updated(changedProperties: PropertyValues) {
+ if (changedProperties.has('repo')) {
+ this.repoChanged();
+ }
+ }
+
+ private repoChanged() {
+ const repo = this.repo;
this._loading = true;
if (!repo) {
return Promise.resolve();
@@ -89,7 +162,6 @@
this._dashboards = dashboardBuilder;
this._loading = false;
- flush();
});
}
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards_html.ts b/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards_html.ts
deleted file mode 100644
index 6657a20..0000000
--- a/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards_html.ts
+++ /dev/null
@@ -1,70 +0,0 @@
-/**
- * @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.
- */
-import {html} from '@polymer/polymer/lib/utils/html-tag';
-
-export const htmlTemplate = html`
- <style include="shared-styles">
- :host {
- display: block;
- margin-bottom: var(--spacing-xxl);
- }
- .loading #dashboards,
- #loadingContainer {
- display: none;
- }
- .loading #loadingContainer {
- display: block;
- }
- </style>
- <style include="gr-table-styles">
- /* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
- </style>
- <table id="list" class$="genericList [[_computeLoadingClass(_loading)]]">
- <tbody>
- <tr class="headerRow">
- <th class="topHeader">Dashboard name</th>
- <th class="topHeader">Dashboard title</th>
- <th class="topHeader">Dashboard description</th>
- <th class="topHeader">Inherited from</th>
- <th class="topHeader">Default</th>
- </tr>
- <tr id="loadingContainer">
- <td>Loading...</td>
- </tr>
- </tbody>
- <tbody id="dashboards">
- <template is="dom-repeat" items="[[_dashboards]]">
- <tr class="groupHeader">
- <td colspan="5">[[item.section]]</td>
- </tr>
- <template is="dom-repeat" items="[[item.dashboards]]" as="info">
- <tr class="table">
- <td class="name">
- <a href$="[[_getUrl(info.project, info.id)]]">[[info.path]]</a>
- </td>
- <td class="title">[[info.title]]</td>
- <td class="desc">[[info.description]]</td>
- <td class="inherited">
- [[_computeInheritedFrom(info.project, info.defining_project)]]
- </td>
- <td class="default">[[_computeIsDefault(info.is_default)]]</td>
- </tr>
- </template>
- </template>
- </tbody>
- </table>
-`;
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards_test.ts b/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards_test.ts
index 8c50db2..ede2bb9 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards_test.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards_test.ts
@@ -19,7 +19,11 @@
import './gr-repo-dashboards';
import {GrRepoDashboards} from './gr-repo-dashboards';
import {GerritNav} from '../../core/gr-navigation/gr-navigation';
-import {addListenerForTest, stubRestApi} from '../../../test/test-utils';
+import {
+ addListenerForTest,
+ queryAndAssert,
+ stubRestApi,
+} from '../../../test/test-utils';
import {DashboardId, DashboardInfo, RepoName} from '../../../types/common';
import {PageErrorEvent} from '../../../types/events.js';
@@ -28,8 +32,9 @@
suite('gr-repo-dashboards tests', () => {
let element: GrRepoDashboards;
- setup(() => {
+ setup(async () => {
element = basicFixture.instantiate();
+ await flush();
});
suite('dashboard table', () => {
@@ -90,17 +95,24 @@
test('loading, sections, and ordering', done => {
assert.isTrue(element._loading);
assert.notEqual(
- getComputedStyle(element.$.loadingContainer).display,
+ getComputedStyle(queryAndAssert(element, '#loadingContainer')).display,
'none'
);
- assert.equal(getComputedStyle(element.$.dashboards).display, 'none');
+ assert.equal(
+ getComputedStyle(queryAndAssert(element, '#dashboards')).display,
+ 'none'
+ );
element.repo = 'test' as RepoName;
flush(() => {
assert.equal(
- getComputedStyle(element.$.loadingContainer).display,
+ getComputedStyle(queryAndAssert(element, '#loadingContainer'))
+ .display,
'none'
);
- assert.notEqual(getComputedStyle(element.$.dashboards).display, 'none');
+ assert.notEqual(
+ getComputedStyle(queryAndAssert(element, '#dashboards')).display,
+ 'none'
+ );
const dashboard = element._dashboards!;
assert.equal(dashboard.length!, 2);
diff --git a/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info.ts b/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info.ts
index cc2f650..796b35d 100644
--- a/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info.ts
+++ b/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info.ts
@@ -14,13 +14,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import '../../../styles/shared-styles';
+
import '../../shared/gr-copy-clipboard/gr-copy-clipboard';
-import {PolymerElement} from '@polymer/polymer/polymer-element';
-import {htmlTemplate} from './gr-commit-info_html';
import {GerritNav} from '../../core/gr-navigation/gr-navigation';
-import {customElement, property, computed} from '@polymer/decorators';
import {ChangeInfo, CommitInfo, ServerInfo} from '../../../types/common';
+import {sharedStyles} from '../../../styles/shared-styles';
+import {GrLitElement} from '../../lit/gr-lit-element';
+import {css, customElement, html, property} from 'lit-element';
declare global {
interface HTMLElementTagNameMap {
@@ -29,11 +29,7 @@
}
@customElement('gr-commit-info')
-export class GrCommitInfo extends PolymerElement {
- static get template() {
- return htmlTemplate;
- }
-
+export class GrCommitInfo extends GrLitElement {
// TODO(TS): can not use `?` here as @computed require dependencies as
// not optional
@property({type: Object})
@@ -47,7 +43,48 @@
@property({type: Object})
serverConfig: ServerInfo | undefined;
- @computed('change', 'commitInfo', 'serverConfig')
+ static get styles() {
+ return [
+ sharedStyles,
+ css`
+ .container {
+ align-items: center;
+ display: flex;
+ }
+ `,
+ ];
+ }
+
+ render() {
+ return html` <div class="container">
+ <a
+ target="_blank"
+ rel="noopener"
+ href="${this.computeCommitLink(
+ this._webLink,
+ this.change,
+ this.commitInfo,
+ this.serverConfig
+ )}"
+ >${this._computeShortHash(
+ this.change,
+ this.commitInfo,
+ this.serverConfig
+ )}</a
+ >
+ <gr-copy-clipboard
+ hasTooltip=""
+ .buttonTitle="Copy full SHA to clipboard"
+ hideInput=""
+ .text="${this.commitInfo?.commit}"
+ >
+ </gr-copy-clipboard>
+ </div>`;
+ }
+
+ /**
+ * Used only within the tests.
+ */
get _showWebLink(): boolean {
if (!this.change || !this.commitInfo || !this.serverConfig) {
return false;
@@ -61,7 +98,6 @@
return !!weblink && !!weblink.url;
}
- @computed('change', 'commitInfo', 'serverConfig')
get _webLink(): string | undefined {
if (!this.change || !this.commitInfo || !this.serverConfig) {
return '';
diff --git a/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info_html.ts b/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info_html.ts
deleted file mode 100644
index 02fa090..0000000
--- a/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info_html.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-/**
- * @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.
- */
-import {html} from '@polymer/polymer/lib/utils/html-tag';
-
-export const htmlTemplate = html`
- <style include="shared-styles">
- .container {
- align-items: center;
- display: flex;
- }
- </style>
- <div class="container">
- <a
- target="_blank"
- rel="noopener"
- href$="[[computeCommitLink(_webLink, change, commitInfo, serverConfig)]]"
- >[[_computeShortHash(change, commitInfo, serverConfig)]]</a
- >
- <gr-copy-clipboard
- hasTooltip=""
- buttonTitle="Copy full SHA to clipboard"
- hideInput=""
- text="[[commitInfo.commit]]"
- >
- </gr-copy-clipboard>
- </div>
-`;
diff --git a/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info_test.js b/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info_test.js
deleted file mode 100644
index ffaed23..0000000
--- a/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info_test.js
+++ /dev/null
@@ -1,117 +0,0 @@
-/**
- * @license
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import '../../../test/common-test-setup-karma.js';
-import '../../core/gr-router/gr-router.js';
-import './gr-commit-info.js';
-import {GerritNav} from '../../core/gr-navigation/gr-navigation.js';
-
-const basicFixture = fixtureFromElement('gr-commit-info');
-
-suite('gr-commit-info tests', () => {
- let element;
-
- setup(() => {
- element = basicFixture.instantiate();
- });
-
- test('weblinks use GerritNav interface', () => {
- const weblinksStub = sinon.stub(GerritNav, '_generateWeblinks')
- .returns([{name: 'stubb', url: '#s'}]);
- element.change = {};
- element.commitInfo = {};
- element.serverConfig = {};
- assert.isTrue(weblinksStub.called);
- });
-
- test('no web link when unavailable', () => {
- element.commitInfo = {};
- element.serverConfig = {};
- element.change = {labels: [], project: ''};
-
- assert.isNotOk(element._showWebLink);
- });
-
- test('use web link when available', () => {
- const router = document.createElement('gr-router');
- sinon.stub(GerritNav, '_generateWeblinks').callsFake(
- router._generateWeblinks.bind(router));
-
- element.change = {labels: [], project: ''};
- element.commitInfo =
- {commit: 'commitsha', web_links: [{name: 'gitweb', url: 'link-url'}]};
- element.serverConfig = {};
-
- assert.isOk(element._showWebLink);
- assert.equal(element._webLink, 'link-url');
- });
-
- test('does not relativize web links that begin with scheme', () => {
- const router = document.createElement('gr-router');
- sinon.stub(GerritNav, '_generateWeblinks').callsFake(
- router._generateWeblinks.bind(router));
-
- element.change = {labels: [], project: ''};
- element.commitInfo = {
- commit: 'commitsha',
- web_links: [{name: 'gitweb', url: 'https://link-url'}],
- };
- element.serverConfig = {};
-
- assert.isOk(element._showWebLink);
- assert.equal(element._webLink, 'https://link-url');
- });
-
- test('ignore web links that are neither gitweb nor gitiles', () => {
- const router = document.createElement('gr-router');
- sinon.stub(GerritNav, '_generateWeblinks').callsFake(
- router._generateWeblinks.bind(router));
-
- element.change = {project: 'project-name'};
- element.commitInfo = {
- commit: 'commit-sha',
- web_links: [
- {
- name: 'ignore',
- url: 'ignore',
- },
- {
- name: 'gitiles',
- url: 'https://link-url',
- },
- ],
- };
- element.serverConfig = {};
-
- assert.isOk(element._showWebLink);
- assert.equal(element._webLink, 'https://link-url');
-
- // Remove gitiles link.
- element.commitInfo = {
- commit: 'commit-sha',
- web_links: [
- {
- name: 'ignore',
- url: 'ignore',
- },
- ],
- };
- assert.isNotOk(element._showWebLink);
- assert.isNotOk(element._webLink);
- });
-});
-
diff --git a/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info_test.ts b/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info_test.ts
new file mode 100644
index 0000000..cb6c9e4
--- /dev/null
+++ b/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info_test.ts
@@ -0,0 +1,134 @@
+/**
+ * @license
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import '../../../test/common-test-setup-karma';
+import '../../core/gr-router/gr-router';
+import './gr-commit-info';
+import {GrCommitInfo} from './gr-commit-info';
+import {GerritNav} from '../../core/gr-navigation/gr-navigation';
+import {
+ createChange,
+ createCommit,
+ createServerInfo,
+} from '../../../test/test-data-generators';
+import {CommitId, RepoName} from '../../../types/common';
+
+const basicFixture = fixtureFromElement('gr-commit-info');
+
+suite('gr-commit-info tests', () => {
+ let element: GrCommitInfo;
+
+ setup(() => {
+ element = basicFixture.instantiate();
+ });
+
+ test('weblinks use GerritNav interface', async () => {
+ const weblinksStub = sinon
+ .stub(GerritNav, '_generateWeblinks')
+ .returns([{name: 'stubb', url: '#s'}]);
+ element.change = createChange();
+ element.commitInfo = createCommit();
+ element.serverConfig = createServerInfo();
+ await flush();
+ assert.isTrue(weblinksStub.called);
+ });
+
+ test('no web link when unavailable', () => {
+ element.commitInfo = createCommit();
+ element.serverConfig = createServerInfo();
+ element.change = {...createChange(), labels: {}, project: '' as RepoName};
+
+ assert.isNotOk(element._showWebLink);
+ });
+
+ test('use web link when available', () => {
+ const router = document.createElement('gr-router');
+ sinon
+ .stub(GerritNav, '_generateWeblinks')
+ .callsFake(router._generateWeblinks.bind(router));
+
+ element.change = {...createChange(), labels: {}, project: '' as RepoName};
+ element.commitInfo = {
+ ...createCommit(),
+ commit: 'commitsha' as CommitId,
+ web_links: [{name: 'gitweb', url: 'link-url'}],
+ };
+ element.serverConfig = createServerInfo();
+
+ assert.isOk(element._showWebLink);
+ assert.equal(element._webLink, 'link-url');
+ });
+
+ test('does not relativize web links that begin with scheme', () => {
+ const router = document.createElement('gr-router');
+ sinon
+ .stub(GerritNav, '_generateWeblinks')
+ .callsFake(router._generateWeblinks.bind(router));
+
+ element.change = {...createChange(), labels: {}, project: '' as RepoName};
+ element.commitInfo = {
+ ...createCommit(),
+ commit: 'commitsha' as CommitId,
+ web_links: [{name: 'gitweb', url: 'https://link-url'}],
+ };
+ element.serverConfig = createServerInfo();
+
+ assert.isOk(element._showWebLink);
+ assert.equal(element._webLink, 'https://link-url');
+ });
+
+ test('ignore web links that are neither gitweb nor gitiles', () => {
+ const router = document.createElement('gr-router');
+ sinon
+ .stub(GerritNav, '_generateWeblinks')
+ .callsFake(router._generateWeblinks.bind(router));
+
+ element.change = {...createChange(), project: 'project-name' as RepoName};
+ element.commitInfo = {
+ ...createCommit(),
+ commit: 'commit-sha' as CommitId,
+ web_links: [
+ {
+ name: 'ignore',
+ url: 'ignore',
+ },
+ {
+ name: 'gitiles',
+ url: 'https://link-url',
+ },
+ ],
+ };
+ element.serverConfig = createServerInfo();
+
+ assert.isOk(element._showWebLink);
+ assert.equal(element._webLink, 'https://link-url');
+
+ // Remove gitiles link.
+ element.commitInfo = {
+ ...createCommit(),
+ commit: 'commit-sha' as CommitId,
+ web_links: [
+ {
+ name: 'ignore',
+ url: 'ignore',
+ },
+ ],
+ };
+ assert.isNotOk(element._showWebLink);
+ assert.isNotOk(element._webLink);
+ });
+});
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog.ts b/polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog.ts
index bd8eaac..95169ca 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog.ts
@@ -19,26 +19,19 @@
import '../../shared/gr-dialog/gr-dialog';
import '../../plugins/gr-endpoint-decorator/gr-endpoint-decorator';
import '../../plugins/gr-endpoint-param/gr-endpoint-param';
-import '../../../styles/shared-styles';
import '../gr-thread-list/gr-thread-list';
-import {PolymerElement} from '@polymer/polymer/polymer-element';
-import {htmlTemplate} from './gr-confirm-submit-dialog_html';
-import {customElement, property} from '@polymer/decorators';
import {ChangeInfo, ActionInfo} from '../../../types/common';
import {GrDialog} from '../../shared/gr-dialog/gr-dialog';
import {pluralize} from '../../../utils/string-util';
import {CommentThread, isUnresolved} from '../../../utils/comment-util';
+import {sharedStyles} from '../../../styles/shared-styles';
+import {GrLitElement} from '../../lit/gr-lit-element';
+import {css, customElement, html, property, query} from 'lit-element';
-export interface GrConfirmSubmitDialog {
- $: {
- dialog: GrDialog;
- };
-}
@customElement('gr-confirm-submit-dialog')
-export class GrConfirmSubmitDialog extends PolymerElement {
- static get template() {
- return htmlTemplate;
- }
+export class GrConfirmSubmitDialog extends GrLitElement {
+ @query('#dialog')
+ dialog?: GrDialog;
/**
* Fired when the confirm button is pressed.
@@ -64,12 +57,119 @@
@property({type: Boolean})
_initialised = false;
+ static get styles() {
+ return [
+ sharedStyles,
+ css`
+ #dialog {
+ min-width: 40em;
+ }
+ p {
+ margin-bottom: var(--spacing-l);
+ }
+ .warningBeforeSubmit {
+ color: var(--warning-foreground);
+ vertical-align: top;
+ margin-right: var(--spacing-s);
+ }
+ @media screen and (max-width: 50em) {
+ #dialog {
+ min-width: inherit;
+ width: 100%;
+ }
+ }
+ `,
+ ];
+ }
+
+ private renderPrivate() {
+ if (!this.change?.is_private) return '';
+ return html`
+ <p>
+ <iron-icon
+ icon="gr-icons:warning"
+ class="warningBeforeSubmit"
+ ></iron-icon>
+ <strong>Heads Up!</strong>
+ Submitting this private change will also make it public.
+ </p>
+ `;
+ }
+
+ private renderUnresolvedCommentCount() {
+ if (!this.change?.unresolved_comment_count) return '';
+ return html`
+ <p>
+ <iron-icon
+ icon="gr-icons:warning"
+ class="warningBeforeSubmit"
+ ></iron-icon>
+ ${this._computeUnresolvedCommentsWarning(this.change)}
+ </p>
+ <gr-thread-list
+ id="commentList"
+ .threads="${this._computeUnresolvedThreads(this.commentThreads)}"
+ .change="${this.change}"
+ .change-num="${this.change?._number}"
+ logged-in="true"
+ hide-dropdown
+ >
+ </gr-thread-list>
+ `;
+ }
+
+ private renderChangeEdit() {
+ if (!this._computeHasChangeEdit(this.change)) return '';
+ return html`
+ <iron-icon
+ icon="gr-icons:warning"
+ class="warningBeforeSubmit"
+ ></iron-icon>
+ Your unpublished edit will not be submitted. Did you forget to click
+ <b>PUBLISH</b>
+ `;
+ }
+
+ private renderInitialised() {
+ if (!this._initialised) return '';
+ return html`
+ <div class="header" slot="header">${this.action?.label}</div>
+ <div class="main" slot="main">
+ <gr-endpoint-decorator name="confirm-submit-change">
+ <p>Ready to submit “<strong>${this.change?.subject}</strong>”?</p>
+ ${this.renderPrivate()} ${this.renderUnresolvedCommentCount()}
+ ${this.renderChangeEdit()}
+ <gr-endpoint-param
+ name="change"
+ .value="${this.change}"
+ ></gr-endpoint-param>
+ <gr-endpoint-param
+ name="action"
+ .value="${this.action}"
+ ></gr-endpoint-param>
+ </gr-endpoint-decorator>
+ </div>
+ `;
+ }
+
+ render() {
+ return html` <gr-dialog
+ id="dialog"
+ confirm-label="Continue"
+ confirm-on-enter=""
+ @cancel=${this._handleCancelTap}
+ @confirm=${this._handleConfirmTap}
+ >
+ ${this.renderInitialised()}
+ </gr-dialog>`;
+ }
+
init() {
this._initialised = true;
}
resetFocus() {
- this.$.dialog.resetFocus();
+ this.dialog?.resetFocus();
}
_computeHasChangeEdit(change?: ChangeInfo) {
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog_html.ts b/polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog_html.ts
deleted file mode 100644
index 5f99ee6..0000000
--- a/polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog_html.ts
+++ /dev/null
@@ -1,99 +0,0 @@
-/**
- * @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.
- */
-import {html} from '@polymer/polymer/lib/utils/html-tag';
-
-export const htmlTemplate = html`
- <style include="shared-styles">
- #dialog {
- min-width: 40em;
- }
- p {
- margin-bottom: var(--spacing-l);
- }
- .warningBeforeSubmit {
- color: var(--warning-foreground);
- vertical-align: top;
- margin-right: var(--spacing-s);
- }
- @media screen and (max-width: 50em) {
- #dialog {
- min-width: inherit;
- width: 100%;
- }
- }
- </style>
- <gr-dialog
- id="dialog"
- confirm-label="Continue"
- confirm-on-enter=""
- on-cancel="_handleCancelTap"
- on-confirm="_handleConfirmTap"
- >
- <template is="dom-if" if="[[_initialised]]">
- <div class="header" slot="header">[[action.label]]</div>
- <div class="main" slot="main">
- <gr-endpoint-decorator name="confirm-submit-change">
- <p>Ready to submit “<strong>[[change.subject]]</strong>”?</p>
- <template is="dom-if" if="[[change.is_private]]">
- <p>
- <iron-icon
- icon="gr-icons:warning"
- class="warningBeforeSubmit"
- ></iron-icon>
- <strong>Heads Up!</strong>
- Submitting this private change will also make it public.
- </p>
- </template>
- <template is="dom-if" if="[[change.unresolved_comment_count]]">
- <p>
- <iron-icon
- icon="gr-icons:warning"
- class="warningBeforeSubmit"
- ></iron-icon>
- [[_computeUnresolvedCommentsWarning(change)]]
- </p>
- <gr-thread-list
- id="commentList"
- threads="[[_computeUnresolvedThreads(commentThreads)]]"
- change="[[change]]"
- change-num="[[change._number]]"
- logged-in="true"
- hide-dropdown
- >
- </gr-thread-list>
- </template>
- <template is="dom-if" if="[[_computeHasChangeEdit(change)]]">
- <iron-icon
- icon="gr-icons:warning"
- class="warningBeforeSubmit"
- ></iron-icon>
- Your unpublished edit will not be submitted. Did you forget to click
- <b>PUBLISH</b>?
- </template>
- <gr-endpoint-param
- name="change"
- value="[[change]]"
- ></gr-endpoint-param>
- <gr-endpoint-param
- name="action"
- value="[[action]]"
- ></gr-endpoint-param>
- </gr-endpoint-decorator>
- </div>
- </template>
- </gr-dialog>
-`;
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog_test.ts b/polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog_test.ts
index e9f3019..e1823b1 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog_test.ts
@@ -31,14 +31,14 @@
element._initialised = true;
});
- test('display', () => {
+ test('display', async () => {
element.action = {label: 'my-label'};
element.change = {
...createChange(),
subject: 'my-subject',
revisions: {},
};
- flush();
+ await flush();
const header = queryAndAssert(element, '.header');
assert.equal(header.textContent!.trim(), 'my-label');
diff --git a/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog_test.js b/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog_test.ts
similarity index 71%
rename from polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog_test.js
rename to polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog_test.ts
index c109538..283a133 100644
--- a/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog_test.js
+++ b/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog_test.ts
@@ -15,25 +15,37 @@
* limitations under the License.
*/
-import '../../../test/common-test-setup-karma.js';
-import './gr-included-in-dialog.js';
+import '../../../test/common-test-setup-karma';
+import './gr-included-in-dialog';
+import {GrIncludedInDialog} from './gr-included-in-dialog';
+import {BranchName, IncludedInInfo, TagName} from '../../../types/common';
+import {IronInputElement} from '@polymer/iron-input';
+import {queryAndAssert} from '../../../test/test-utils';
const basicFixture = fixtureFromElement('gr-included-in-dialog');
suite('gr-included-in-dialog', () => {
- let element;
+ let element: GrIncludedInDialog;
setup(() => {
element = basicFixture.instantiate();
});
test('_computeGroups', () => {
- const includedIn = {branches: [], tags: []};
+ const includedIn = {branches: [], tags: []} as IncludedInInfo;
let filterText = '';
assert.deepEqual(element._computeGroups(includedIn, filterText), []);
- includedIn.branches.push('master', 'development', 'stable-2.0');
- includedIn.tags.push('v1.9', 'v2.0', 'v2.1');
+ includedIn.branches.push(
+ 'master' as BranchName,
+ 'development' as BranchName,
+ 'stable-2.0' as BranchName
+ );
+ includedIn.tags.push(
+ 'v1.9' as TagName,
+ 'v2.0' as TagName,
+ 'v2.1' as TagName
+ );
assert.deepEqual(element._computeGroups(includedIn, filterText), [
{title: 'Branches', items: ['master', 'development', 'stable-2.0']},
{title: 'Tags', items: ['v1.9', 'v2.0', 'v2.1']},
@@ -65,9 +77,13 @@
});
test('_computeGroups with .bindValue', done => {
- element.$.filterInput.bindValue = 'stable-3.2';
- const includedIn = {branches: [], tags: []};
- includedIn.branches.push('master', 'stable-3.2');
+ queryAndAssert<IronInputElement>(element, '#filterInput')!.bindValue =
+ 'stable-3.2';
+ const includedIn = {branches: [], tags: []} as IncludedInInfo;
+ includedIn.branches.push(
+ 'master' as BranchName,
+ 'stable-3.2' as BranchName
+ );
setTimeout(() => {
const filterText = element._filterText;
@@ -79,4 +95,3 @@
});
});
});
-
diff --git a/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager_test.ts b/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager_test.ts
index 1f74b70..3ccc960 100644
--- a/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager_test.ts
+++ b/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager_test.ts
@@ -363,8 +363,8 @@
// toast
let toast = toastSpy.lastCall.returnValue;
assert.isOk(toast);
- assert.include(toast.root.textContent, 'Credentials expired.');
- assert.include(toast.root.textContent, 'Refresh credentials');
+ assert.include(toast.shadowRoot.textContent, 'Credentials expired.');
+ assert.include(toast.shadowRoot.textContent, 'Refresh credentials');
// noInteractionOverlay
const noInteractionOverlay = element.$.noInteractionOverlay;
@@ -401,7 +401,7 @@
assert.notStrictEqual(toastSpy.lastCall.returnValue, toast);
toast = toastSpy.lastCall.returnValue;
assert.isOk(toast);
- assert.include(toast.root.textContent, 'Credentials refreshed');
+ assert.include(toast.shadowRoot.textContent, 'Credentials refreshed');
// close overlay
assert.isTrue(noInteractionOverlayCloseSpy.called);
@@ -421,9 +421,10 @@
bubbles: true,
})
);
+ await flush();
let toast = toastSpy.lastCall.returnValue;
assert.isOk(toast);
- assert.include(toast.root.textContent, 'test reload');
+ assert.include(toast.shadowRoot.textContent, 'test reload');
// fake auth
fetchStub.returns(Promise.resolve({status: 403}));
@@ -452,11 +453,11 @@
await flush();
// toast
toast = toastSpy.lastCall.returnValue;
- assert.include(toast.root.textContent, 'Credentials expired.');
- assert.include(toast.root.textContent, 'Refresh credentials');
+ assert.include(toast.shadowRoot.textContent, 'Credentials expired.');
+ assert.include(toast.shadowRoot.textContent, 'Refresh credentials');
});
- test('regular toast should dismiss regular toast', () => {
+ test('regular toast should dismiss regular toast', async () => {
// Set status to AUTHED.
appContext.authService.authCheck();
@@ -468,9 +469,10 @@
bubbles: true,
})
);
+ await flush();
let toast = toastSpy.lastCall.returnValue;
assert.isOk(toast);
- assert.include(toast.root.textContent, 'test reload');
+ assert.include(toast.shadowRoot.textContent, 'test reload');
// new alert
element.dispatchEvent(
@@ -480,9 +482,9 @@
bubbles: true,
})
);
-
+ await flush();
toast = toastSpy.lastCall.returnValue;
- assert.include(toast.root.textContent, 'second-test');
+ assert.include(toast.shadowRoot.textContent, 'second-test');
});
test('regular toast should not dismiss auth toast', done => {
@@ -513,8 +515,8 @@
assert.equal(fetchStub.callCount, 2);
flush(() => {
let toast = toastSpy.lastCall.returnValue;
- assert.include(toast.root.textContent, 'Credentials expired.');
- assert.include(toast.root.textContent, 'Refresh credentials');
+ assert.include(toast.shadowRoot.textContent, 'Credentials expired.');
+ assert.include(toast.shadowRoot.textContent, 'Refresh credentials');
// fake an alert
element.dispatchEvent(
@@ -530,7 +532,10 @@
flush(() => {
toast = toastSpy.lastCall.returnValue;
assert.isOk(toast);
- assert.include(toast.root.textContent, 'Credentials expired.');
+ assert.include(
+ toast.shadowRoot.textContent,
+ 'Credentials expired.'
+ );
done();
});
});
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector.ts b/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector.ts
index 0f8752d..6fe9d27 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector.ts
@@ -73,19 +73,19 @@
}
}
- _computeSideBySideSelected(mode: DiffViewMode) {
+ _computeSideBySideSelected(mode?: DiffViewMode) {
return mode === DiffViewMode.SIDE_BY_SIDE ? 'selected' : '';
}
- _computeUnifiedSelected(mode: DiffViewMode) {
+ _computeUnifiedSelected(mode?: DiffViewMode) {
return mode === DiffViewMode.UNIFIED ? 'selected' : '';
}
- isSideBySideSelected(mode: DiffViewMode) {
+ isSideBySideSelected(mode?: DiffViewMode) {
return mode === DiffViewMode.SIDE_BY_SIDE;
}
- isUnifiedSelected(mode: DiffViewMode) {
+ isUnifiedSelected(mode?: DiffViewMode) {
return mode === DiffViewMode.UNIFIED;
}
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector_html.ts b/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector_html.ts
index 3ebb58f..4e2b6a1 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector_html.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector_html.ts
@@ -37,7 +37,7 @@
position-below="[[showTooltipBelow]]"
class$="[[_computeSideBySideSelected(mode)]]"
title="Side-by-side diff"
- aria-pressed="[[isSideBySideSelected(mode)]]"
+ aria-pressed$="[[isSideBySideSelected(mode)]]"
on-click="_handleSideBySideTap"
>
<iron-icon icon="gr-icons:side-by-side"></iron-icon>
@@ -49,7 +49,7 @@
position-below="[[showTooltipBelow]]"
title="Unified diff"
class$="[[_computeUnifiedSelected(mode)]]"
- aria-pressed="[[isUnifiedSelected(mode)]]"
+ aria-pressed$="[[isUnifiedSelected(mode)]]"
on-click="_handleUnifiedTap"
>
<iron-icon icon="gr-icons:unified"></iron-icon>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector_test.js b/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector_test.ts
similarity index 65%
rename from polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector_test.js
rename to polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector_test.ts
index f554227..aa97394 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector_test.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector_test.ts
@@ -15,29 +15,35 @@
* limitations under the License.
*/
-import '../../../test/common-test-setup-karma.js';
-import './gr-diff-mode-selector.js';
-import {DiffViewMode} from '../../../constants/constants.js';
-import {stubRestApi} from '../../../test/test-utils.js';
+import '../../../test/common-test-setup-karma';
+import './gr-diff-mode-selector';
+import {GrDiffModeSelector} from './gr-diff-mode-selector';
+import {DiffViewMode} from '../../../constants/constants';
+import {stubRestApi} from '../../../test/test-utils';
const basicFixture = fixtureFromElement('gr-diff-mode-selector');
suite('gr-diff-mode-selector tests', () => {
- let element;
+ let element: GrDiffModeSelector;
setup(() => {
element = basicFixture.instantiate();
});
test('_computeSelectedClass', () => {
- assert.equal(element._computeSideBySideSelected(DiffViewMode.SIDE_BY_SIDE),
- 'selected');
- assert.equal(element._computeSideBySideSelected(DiffViewMode.UNIFIED),
- '');
- assert.equal(element._computeUnifiedSelected(DiffViewMode.UNIFIED),
- 'selected');
- assert.equal(element._computeUnifiedSelected(DiffViewMode.SIDE_BY_SIDE),
- '');
+ assert.equal(
+ element._computeSideBySideSelected(DiffViewMode.SIDE_BY_SIDE),
+ 'selected'
+ );
+ assert.equal(element._computeSideBySideSelected(DiffViewMode.UNIFIED), '');
+ assert.equal(
+ element._computeUnifiedSelected(DiffViewMode.UNIFIED),
+ 'selected'
+ );
+ assert.equal(
+ element._computeUnifiedSelected(DiffViewMode.SIDE_BY_SIDE),
+ ''
+ );
});
test('setMode', () => {
@@ -45,24 +51,23 @@
// Setting the mode initially does not save prefs.
element.saveOnChange = true;
- element.setMode('SIDE_BY_SIDE');
+ element.setMode(DiffViewMode.SIDE_BY_SIDE);
assert.isFalse(saveStub.called);
// Setting the mode to itself does not save prefs.
- element.setMode('SIDE_BY_SIDE');
+ element.setMode(DiffViewMode.SIDE_BY_SIDE);
assert.isFalse(saveStub.called);
// Setting the mode to something else does not save prefs if saveOnChange
// is false.
element.saveOnChange = false;
- element.setMode('UNIFIED_DIFF');
+ element.setMode(DiffViewMode.UNIFIED);
assert.isFalse(saveStub.called);
// Setting the mode to something else does not save prefs if saveOnChange
// is false.
element.saveOnChange = true;
- element.setMode('SIDE_BY_SIDE');
+ element.setMode(DiffViewMode.SIDE_BY_SIDE);
assert.isTrue(saveStub.calledOnce);
});
});
-
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-preferences-dialog/gr-diff-preferences-dialog_test.js b/polygerrit-ui/app/elements/diff/gr-diff-preferences-dialog/gr-diff-preferences-dialog_test.ts
similarity index 80%
rename from polygerrit-ui/app/elements/diff/gr-diff-preferences-dialog/gr-diff-preferences-dialog_test.js
rename to polygerrit-ui/app/elements/diff/gr-diff-preferences-dialog/gr-diff-preferences-dialog_test.ts
index 07cca9a..5c62e7a 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-preferences-dialog/gr-diff-preferences-dialog_test.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-preferences-dialog/gr-diff-preferences-dialog_test.ts
@@ -15,17 +15,24 @@
* limitations under the License.
*/
-import '../../../test/common-test-setup-karma.js';
+import '../../../test/common-test-setup-karma';
+import './gr-diff-preferences-dialog';
+import {GrDiffPreferencesDialog} from './gr-diff-preferences-dialog';
+import {createDefaultDiffPrefs} from '../../../constants/constants';
+import * as MockInteractions from '@polymer/iron-test-helpers/mock-interactions';
const basicFixture = fixtureFromElement('gr-diff-preferences-dialog');
suite('gr-diff-preferences-dialog', () => {
- let element;
+ let element: GrDiffPreferencesDialog;
+
setup(() => {
element = basicFixture.instantiate();
});
+
test('changes applies only on save', async () => {
const originalDiffPrefs = {
+ ...createDefaultDiffPrefs(),
line_wrapping: true,
};
element.diffPrefs = originalDiffPrefs;
diff --git a/polygerrit-ui/app/elements/shared/gr-alert/gr-alert.ts b/polygerrit-ui/app/elements/shared/gr-alert/gr-alert.ts
index 4d2576a..008ddb0 100644
--- a/polygerrit-ui/app/elements/shared/gr-alert/gr-alert.ts
+++ b/polygerrit-ui/app/elements/shared/gr-alert/gr-alert.ts
@@ -16,11 +16,11 @@
*/
import '../gr-button/gr-button';
import '../../../styles/shared-styles';
-import {PolymerElement} from '@polymer/polymer/polymer-element';
-import {htmlTemplate} from './gr-alert_html';
import {getRootElement} from '../../../scripts/rootElement';
-import {customElement, property} from '@polymer/decorators';
import {ErrorType} from '../../../types/types';
+import {GrLitElement} from '../../lit/gr-lit-element';
+import {customElement, property, css, html} from 'lit-element';
+import {sharedStyles} from '../../../styles/shared-styles';
declare global {
interface HTMLElementTagNameMap {
@@ -29,9 +29,93 @@
}
@customElement('gr-alert')
-export class GrAlert extends PolymerElement {
- static get template() {
- return htmlTemplate;
+export class GrAlert extends GrLitElement {
+ static get styles() {
+ return [
+ sharedStyles,
+ css`
+ /**
+ * ALERT: DO NOT ADD TRANSITION PROPERTIES WITHOUT PROPERLY UNDERSTANDING
+ * HOW THEY ARE USED IN THE CODE.
+ */
+ :host([toast]) {
+ background-color: var(--tooltip-background-color);
+ bottom: 1.25rem;
+ border-radius: var(--border-radius);
+ box-shadow: var(--elevation-level-2);
+ color: var(--tooltip-text-color);
+ left: 1.25rem;
+ position: fixed;
+ transform: translateY(5rem);
+ transition: transform var(--gr-alert-transition-duration, 80ms)
+ ease-out;
+ z-index: 1000;
+ }
+ :host([shown]) {
+ transform: translateY(0);
+ }
+ /**
+ * NOTE: To avoid style being overwritten by outside of the shadow DOM
+ * (as outside styles always win), .content-wrapper is introduced as a
+ * wrapper around main content to have better encapsulation, styles that
+ * may be affected by outside should be defined on it.
+ * In this case, \`padding:0px\` is defined in main.css for all elements
+ * with the universal selector: *.
+ */
+ .content-wrapper {
+ padding: var(--spacing-l) var(--spacing-xl);
+ }
+ .text {
+ color: var(--tooltip-text-color);
+ display: inline-block;
+ max-height: 10rem;
+ max-width: 80vw;
+ vertical-align: bottom;
+ word-break: break-all;
+ }
+ .action {
+ color: var(--link-color);
+ font-weight: var(--font-weight-bold);
+ margin-left: var(--spacing-l);
+ text-decoration: none;
+ }
+ `,
+ ];
+ }
+
+ renderDismissButton() {
+ if (!this.showDismiss) return '';
+ return html`<gr-button
+ link=""
+ class="action"
+ @click=${this._handleDismissTap}
+ >Dismiss</gr-button
+ >`;
+ }
+
+ render() {
+ // To pass CSS mixins for @apply to Polymer components, they need to appear
+ // in <style> inside the template.
+ const style = html`<style>
+ .action {
+ --gr-button: {
+ padding: 0;
+ }
+ }
+ </style>`;
+ const {text, actionText} = this;
+ return html`${style}
+ <div class="content-wrapper">
+ <span class="text">${text}</span>
+ <gr-button
+ link=""
+ class="action"
+ ?hidden="${this._hideActionButton}"
+ @click=${this._handleActionTap}
+ >${actionText}
+ </gr-button>
+ ${this.renderDismissButton()}
+ </div> `;
}
/**
@@ -49,10 +133,10 @@
@property({type: String})
type?: ErrorType;
- @property({type: Boolean, reflectToAttribute: true})
+ @property({type: Boolean, reflect: true})
shown = true;
- @property({type: Boolean, reflectToAttribute: true})
+ @property({type: Boolean, reflect: true})
toast = true;
@property({type: Boolean})
diff --git a/polygerrit-ui/app/elements/shared/gr-alert/gr-alert_html.ts b/polygerrit-ui/app/elements/shared/gr-alert/gr-alert_html.ts
deleted file mode 100644
index bc517a8..0000000
--- a/polygerrit-ui/app/elements/shared/gr-alert/gr-alert_html.ts
+++ /dev/null
@@ -1,83 +0,0 @@
-/**
- * @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.
- */
-import {html} from '@polymer/polymer/lib/utils/html-tag';
-
-export const htmlTemplate = html`
- <style include="shared-styles">
- /**
- * ALERT: DO NOT ADD TRANSITION PROPERTIES WITHOUT PROPERLY UNDERSTANDING
- * HOW THEY ARE USED IN THE CODE.
- */
- :host([toast]) {
- background-color: var(--tooltip-background-color);
- bottom: 1.25rem;
- border-radius: var(--border-radius);
- box-shadow: var(--elevation-level-2);
- color: var(--tooltip-text-color);
- left: 1.25rem;
- position: fixed;
- transform: translateY(5rem);
- transition: transform var(--gr-alert-transition-duration, 80ms) ease-out;
- z-index: 1000;
- }
- :host([shown]) {
- transform: translateY(0);
- }
- /**
- * NOTE: To avoid style being overwritten by outside of the shadow DOM
- * (as outside styles always win), .content-wrapper is introduced as a
- * wrapper around main content to have better encapsulation, styles that
- * may be affected by outside should be defined on it.
- * In this case, \`padding:0px\` is defined in main.css for all elements
- * with the universal selector: *.
- */
- .content-wrapper {
- padding: var(--spacing-l) var(--spacing-xl);
- }
- .text {
- color: var(--tooltip-text-color);
- display: inline-block;
- max-height: 10rem;
- max-width: 80vw;
- vertical-align: bottom;
- word-break: break-all;
- }
- .action {
- color: var(--link-color);
- font-weight: var(--font-weight-bold);
- margin-left: var(--spacing-l);
- text-decoration: none;
- --gr-button: {
- padding: 0;
- }
- }
- </style>
- <div class="content-wrapper">
- <span class="text">[[text]]</span>
- <gr-button
- link=""
- class="action"
- hidden$="[[_hideActionButton]]"
- on-click="_handleActionTap"
- >[[actionText]]</gr-button
- ><template is="dom-if" if="[[showDismiss]]"
- ><gr-button link="" class="action" on-click="_handleDismissTap"
- >Dismiss</gr-button
- ></template
- >
- </div>
-`;
diff --git a/polygerrit-ui/app/elements/shared/gr-alert/gr-alert_test.ts b/polygerrit-ui/app/elements/shared/gr-alert/gr-alert_test.ts
index 3478a9a..d0fe563 100644
--- a/polygerrit-ui/app/elements/shared/gr-alert/gr-alert_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-alert/gr-alert_test.ts
@@ -33,18 +33,21 @@
}
});
- test('show/hide', () => {
+ test('show/hide', async () => {
assert.isNull(element.parentNode);
element.show('Alert text');
+ // wait for element to be rendered after being attached to DOM
+ await flush();
assert.equal(element.parentNode, document.body);
- element.updateStyles({'--gr-alert-transition-duration': '0ms'});
+ element.style.setProperty('--gr-alert-transition-duration', '0ms');
element.hide();
assert.isNull(element.parentNode);
});
- test('action event', () => {
+ test('action event', async () => {
const spy = sinon.spy();
element.show('Alert text');
+ await flush();
element._actionCallback = spy;
assert.isFalse(spy.called);
MockInteractions.tap(element.shadowRoot!.querySelector('.action')!);
diff --git a/polygerrit-ui/app/services/gr-auth/gr-auth_impl.ts b/polygerrit-ui/app/services/gr-auth/gr-auth_impl.ts
index 08f2e25..024fa2a 100644
--- a/polygerrit-ui/app/services/gr-auth/gr-auth_impl.ts
+++ b/polygerrit-ui/app/services/gr-auth/gr-auth_impl.ts
@@ -103,6 +103,15 @@
return this.authCheckPromise
.then(res => {
+ // Make a call that requires loading the body of the request. This makes it so that the browser
+ // can close the request even though callers of this method might only ever read headers.
+ // See https://stackoverflow.com/questions/45816743/how-to-solve-this-caution-request-is-not-finished-yet-in-chrome
+ try {
+ res.clone().text();
+ } catch (error) {
+ // Ignore error
+ }
+
// auth-check will return 204 if authed
// treat the rest as unauthed
if (res.status === 204) {
@@ -220,7 +229,7 @@
}
}
options.credentials = 'same-origin';
- return fetch(url, options);
+ return this._ensureBodyLoaded(fetch(url, options));
}
private _getAccessToken(): Promise<string | null> {
@@ -286,6 +295,22 @@
if (params.length) {
url = url + (url.indexOf('?') === -1 ? '?' : '&') + params.join('&');
}
- return fetch(url, options);
+ return this._ensureBodyLoaded(fetch(url, options));
+ }
+
+ private _ensureBodyLoaded(response: Promise<Response>): Promise<Response> {
+ return response.then(response => {
+ if (!response.ok) {
+ // Make a call that requires loading the body of the request. This makes it so that the browser
+ // can close the request even though callers of this method might only ever read headers.
+ // See https://stackoverflow.com/questions/45816743/how-to-solve-this-caution-request-is-not-finished-yet-in-chrome
+ try {
+ response.clone().text();
+ } catch (error) {
+ // Ignore error
+ }
+ }
+ return response;
+ });
}
}
diff --git a/polygerrit-ui/app/styles/gr-table-styles.ts b/polygerrit-ui/app/styles/gr-table-styles.ts
index 09d1161..d92bf55 100644
--- a/polygerrit-ui/app/styles/gr-table-styles.ts
+++ b/polygerrit-ui/app/styles/gr-table-styles.ts
@@ -15,6 +15,8 @@
* limitations under the License.
*/
+import {css} from 'lit-element';
+
// Mark the file as a module. Otherwise typescript assumes this is a script
// and $_documentContainer is a global variable.
// See: https://www.typescriptlang.org/docs/handbook/modules.html
@@ -22,95 +24,99 @@
const $_documentContainer = document.createElement('template');
+export const tableStyles = css`
+ .genericList {
+ background-color: var(--background-color-primary);
+ border-collapse: collapse;
+ width: 100%;
+ }
+ .genericList th,
+ .genericList td {
+ padding: var(--spacing-m) 0;
+ vertical-align: middle;
+ }
+ .genericList tr {
+ border-bottom: 1px solid var(--border-color);
+ }
+ .genericList tr:hover {
+ background-color: var(--hover-background-color);
+ }
+ .genericList th {
+ white-space: nowrap;
+ }
+ .genericList th,
+ .genericList td {
+ padding-right: var(--spacing-l);
+ }
+ .genericList tr th:first-of-type,
+ .genericList tr td:first-of-type {
+ padding-left: var(--spacing-l);
+ }
+ .genericList tr:first-of-type {
+ border-top: 1px solid var(--border-color);
+ }
+ .genericList tr th:last-of-type,
+ .genericList tr td:last-of-type {
+ border-left: 1px solid var(--border-color);
+ text-align: center;
+ padding-left: var(--spacing-l);
+ }
+ .genericList tr th.delete,
+ .genericList tr td.delete {
+ padding-top: 0;
+ padding-bottom: 0;
+ }
+ .genericList tr th.delete,
+ .genericList tr td.delete,
+ .genericList tr.loadingMsg td,
+ .genericList tr.groupHeader td {
+ border-left: none;
+ }
+ .genericList .loading {
+ border: none;
+ display: none;
+ }
+ .genericList td {
+ flex-shrink: 0;
+ }
+ .genericList .topHeader,
+ .genericList .groupHeader {
+ color: var(--primary-text-color);
+ font-weight: var(--font-weight-bold);
+ text-align: left;
+ vertical-align: middle;
+ }
+ .genericList .groupHeader {
+ background-color: var(--background-color-secondary);
+ font-family: var(--header-font-family);
+ font-size: var(--font-size-h3);
+ font-weight: var(--font-weight-h3);
+ line-height: var(--line-height-h3);
+ }
+ .genericList a {
+ color: var(--primary-text-color);
+ text-decoration: none;
+ }
+ .genericList a:hover {
+ text-decoration: underline;
+ }
+ .genericList .description {
+ width: 99%;
+ }
+ .genericList .loadingMsg {
+ color: var(--deemphasized-text-color);
+ display: block;
+ padding: var(--spacing-s) var(--spacing-l);
+ }
+ .genericList .loadingMsg:not(.loading) {
+ display: none;
+ }
+`;
+
$_documentContainer.innerHTML = `<dom-module id="gr-table-styles">
<template>
<style>
- .genericList {
- background-color: var(--background-color-primary);
- border-collapse: collapse;
- width: 100%;
- }
- .genericList th,
- .genericList td {
- padding: var(--spacing-m) 0;
- vertical-align: middle;
- }
- .genericList tr {
- border-bottom: 1px solid var(--border-color);
- }
- .genericList tr:hover {
- background-color: var(--hover-background-color);
- }
- .genericList th {
- white-space: nowrap;
- }
- .genericList th,
- .genericList td {
- padding-right: var(--spacing-l);
- }
- .genericList tr th:first-of-type,
- .genericList tr td:first-of-type {
- padding-left: var(--spacing-l);
- }
- .genericList tr:first-of-type {
- border-top: 1px solid var(--border-color);
- }
- .genericList tr th:last-of-type,
- .genericList tr td:last-of-type {
- border-left: 1px solid var(--border-color);
- text-align: center;
- padding-left: var(--spacing-l);
- }
- .genericList tr th.delete,
- .genericList tr td.delete {
- padding-top: 0;
- padding-bottom: 0;
- }
- .genericList tr th.delete,
- .genericList tr td.delete,
- .genericList tr.loadingMsg td,
- .genericList tr.groupHeader td {
- border-left: none;
- }
- .genericList .loading {
- border: none;
- display: none;
- }
- .genericList td {
- flex-shrink: 0;
- }
- .genericList .topHeader,
- .genericList .groupHeader {
- color: var(--primary-text-color);
- font-weight: var(--font-weight-bold);
- text-align: left;
- vertical-align: middle
- }
- .genericList .groupHeader {
- background-color: var(--background-color-secondary);
- font-family: var(--header-font-family);
- font-size: var(--font-size-h3);
- font-weight: var(--font-weight-h3);
- line-height: var(--line-height-h3);
- }
- .genericList a {
- color: var(--primary-text-color);
- text-decoration: none;
- }
- .genericList a:hover {
- text-decoration: underline;
- }
- .genericList .description {
- width: 99%;
- }
- .genericList .loadingMsg {
- color: var(--deemphasized-text-color);
- display: block;
- padding: var(--spacing-s) var(--spacing-l);
- }
- .genericList .loadingMsg:not(.loading) {
- display: none;
- }
+ ${tableStyles.cssText}
</style>
</template>
</dom-module>`;