Merge changes from topic "gr-change-view_tests-to-ts"
* changes: Convert gr-change-view tests to typescript Rename gr-change-view_test.js to gr-change-view_test.ts Remove generateChange method and instead use test-data-generators.ts Add test-data-generators and convert several test files to TS Cleanup eslint rules and fix some eslint warnings Allow to write and run Typescript tests
This commit is contained in:
		@@ -280,28 +280,17 @@ module.exports = {
 | 
			
		||||
        // 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": {
 | 
			
		||||
        "project": path.resolve(__dirname, "./tsconfig_eslint.json"),
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "files": ["**/*.ts"],
 | 
			
		||||
      "excludedFiles": "*.d.ts",
 | 
			
		||||
      "rules": {
 | 
			
		||||
        // Custom rule from the //tools/js/eslint-rules directory.
 | 
			
		||||
        // See //tools/js/eslint-rules/README.md for details
 | 
			
		||||
        "ts-imports-js": 2,
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "files": ["**/*.d.ts"],
 | 
			
		||||
      "rules": {
 | 
			
		||||
        // See details in the //tools/js/eslint-rules/report-ts-error.js file.
 | 
			
		||||
        "report-ts-error": "error",
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "files": ["*.html", "test.js", "test-infra.js"],
 | 
			
		||||
      "rules": {
 | 
			
		||||
@@ -311,8 +300,6 @@ module.exports = {
 | 
			
		||||
    {
 | 
			
		||||
      "files": [
 | 
			
		||||
        "*.html",
 | 
			
		||||
        "common-test-setup.js",
 | 
			
		||||
        "common-test-setup-karma.js",
 | 
			
		||||
        "*_test.js",
 | 
			
		||||
        "a11y-test-utils.js",
 | 
			
		||||
      ],
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,8 @@ load("//tools/js:eslint.bzl", "eslint")
 | 
			
		||||
 | 
			
		||||
package(default_visibility = ["//visibility:public"])
 | 
			
		||||
 | 
			
		||||
# This list must be in sync with the "include" list in the tsconfig.json file
 | 
			
		||||
# This list must be in sync with the "include" list in the follwoing files:
 | 
			
		||||
# tsconfig.json, tsconfig_bazel.json, tsconfig_bazel_test.json
 | 
			
		||||
src_dirs = [
 | 
			
		||||
    "constants",
 | 
			
		||||
    "elements",
 | 
			
		||||
@@ -27,6 +28,7 @@ compiled_pg_srcs = compile_ts(
 | 
			
		||||
        ]],
 | 
			
		||||
        exclude = [
 | 
			
		||||
            "**/*_test.js",
 | 
			
		||||
            "**/*_test.ts",
 | 
			
		||||
        ],
 | 
			
		||||
    ),
 | 
			
		||||
    # The same outdir also appears in the following files:
 | 
			
		||||
@@ -40,6 +42,7 @@ compiled_pg_srcs_with_tests = compile_ts(
 | 
			
		||||
        [
 | 
			
		||||
            "**/*.js",
 | 
			
		||||
            "**/*.ts",
 | 
			
		||||
            "test/@types/*.d.ts",
 | 
			
		||||
        ],
 | 
			
		||||
        exclude = [
 | 
			
		||||
            "node_modules/**",
 | 
			
		||||
@@ -48,6 +51,7 @@ compiled_pg_srcs_with_tests = compile_ts(
 | 
			
		||||
            "rollup.config.js",
 | 
			
		||||
        ],
 | 
			
		||||
    ),
 | 
			
		||||
    include_tests = True,
 | 
			
		||||
    # The same outdir also appears in the following files:
 | 
			
		||||
    # wct_test.sh
 | 
			
		||||
    # karma.conf.js
 | 
			
		||||
 
 | 
			
		||||
@@ -369,3 +369,34 @@ export enum AuthType {
 | 
			
		||||
  CUSTOM_EXTENSION = 'CUSTOM_EXTENSION',
 | 
			
		||||
  DEVELOPMENT_BECOME_ANY_ACCOUNT = 'DEVELOPMENT_BECOME_ANY_ACCOUNT',
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Controls visibility of other users' dashboard pages and completion suggestions to web users
 | 
			
		||||
 * https://gerrit-review.googlesource.com/Documentation/config-gerrit.html#accounts.visibility
 | 
			
		||||
 */
 | 
			
		||||
export enum AccountsVisibility {
 | 
			
		||||
  ALL = 'ALL',
 | 
			
		||||
  SAME_GROUP = 'SAME_GROUP',
 | 
			
		||||
  VISIBLE_GROUP = 'VISIBLE_GROUP',
 | 
			
		||||
  NONE = 'NONE',
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Account fields that are editable
 | 
			
		||||
 * https://gerrit-review.googlesource.com/Documentation/rest-api-config.html#auth-info
 | 
			
		||||
 */
 | 
			
		||||
export enum EditableAccountField {
 | 
			
		||||
  FULL_NAME = 'FULL_NAME',
 | 
			
		||||
  USER_NAME = 'USER_NAME',
 | 
			
		||||
  REGISTER_NEW_EMAIL = 'REGISTER_NEW_EMAIL',
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This setting determines when Gerrit computes if a change is mergeable or not.
 | 
			
		||||
 * https://gerrit-review.googlesource.com/Documentation/config-gerrit.html#change.mergeabilityComputationBehavior
 | 
			
		||||
 */
 | 
			
		||||
export enum MergeabilityComputationBehavior {
 | 
			
		||||
  API_REF_UPDATED_AND_CHANGE_REINDEX = 'API_REF_UPDATED_AND_CHANGE_REINDEX',
 | 
			
		||||
  REF_UPDATED_AND_CHANGE_REINDEX = 'REF_UPDATED_AND_CHANGE_REINDEX',
 | 
			
		||||
  NEVER = 'NEVER',
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -37,6 +37,8 @@ import {
 | 
			
		||||
import {InheritedBooleanInfoConfiguredValue} from '../../../constants/constants';
 | 
			
		||||
import {hasOwnProperty} from '../../../utils/common-util';
 | 
			
		||||
import {RestApiService} from '../../../services/services/gr-rest-api/gr-rest-api';
 | 
			
		||||
import {GrAutocomplete} from '../../shared/gr-autocomplete/gr-autocomplete';
 | 
			
		||||
import {IronAutogrowTextareaElement} from '@polymer/iron-autogrow-textarea/iron-autogrow-textarea';
 | 
			
		||||
 | 
			
		||||
const SUGGESTIONS_LIMIT = 15;
 | 
			
		||||
const REF_PREFIX = 'refs/heads/';
 | 
			
		||||
@@ -45,6 +47,9 @@ export interface GrCreateChangeDialog {
 | 
			
		||||
  $: {
 | 
			
		||||
    restAPI: RestApiService & Element;
 | 
			
		||||
    privateChangeCheckBox: HTMLInputElement;
 | 
			
		||||
    branchInput: GrAutocomplete;
 | 
			
		||||
    tagNameInput: HTMLInputElement;
 | 
			
		||||
    messageInput: IronAutogrowTextareaElement;
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
@customElement('gr-create-change-dialog')
 | 
			
		||||
 
 | 
			
		||||
@@ -17,40 +17,48 @@
 | 
			
		||||
 | 
			
		||||
import '../../../test/common-test-setup-karma.js';
 | 
			
		||||
import './gr-create-change-dialog.js';
 | 
			
		||||
import {GrCreateChangeDialog} from './gr-create-change-dialog';
 | 
			
		||||
import {BranchName, GitRef, RepoName} from '../../../types/common';
 | 
			
		||||
import {InheritedBooleanInfoConfiguredValue} from '../../../constants/constants';
 | 
			
		||||
import {createChange, createConfig} from '../../../test/test-data-generators';
 | 
			
		||||
 | 
			
		||||
const basicFixture = fixtureFromElement('gr-create-change-dialog');
 | 
			
		||||
 | 
			
		||||
suite('gr-create-change-dialog tests', () => {
 | 
			
		||||
  let element;
 | 
			
		||||
  let element: GrCreateChangeDialog;
 | 
			
		||||
 | 
			
		||||
  setup(() => {
 | 
			
		||||
    stub('gr-rest-api-interface', {
 | 
			
		||||
      getLoggedIn() { return Promise.resolve(true); },
 | 
			
		||||
      getLoggedIn() {
 | 
			
		||||
        return Promise.resolve(true);
 | 
			
		||||
      },
 | 
			
		||||
      getRepoBranches(input) {
 | 
			
		||||
        if (input.startsWith('test')) {
 | 
			
		||||
          return Promise.resolve([
 | 
			
		||||
            {
 | 
			
		||||
              ref: 'refs/heads/test-branch',
 | 
			
		||||
              ref: 'refs/heads/test-branch' as GitRef,
 | 
			
		||||
              revision: '67ebf73496383c6777035e374d2d664009e2aa5c',
 | 
			
		||||
              can_delete: true,
 | 
			
		||||
            },
 | 
			
		||||
          ]);
 | 
			
		||||
        } else {
 | 
			
		||||
          return Promise.resolve({});
 | 
			
		||||
          return Promise.resolve([]);
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
    });
 | 
			
		||||
    element = basicFixture.instantiate();
 | 
			
		||||
    element.repoName = 'test-repo';
 | 
			
		||||
    element.repoName = 'test-repo' as RepoName;
 | 
			
		||||
    element._repoConfig = {
 | 
			
		||||
      ...createConfig(),
 | 
			
		||||
      private_by_default: {
 | 
			
		||||
        configured_value: 'FALSE',
 | 
			
		||||
        value: false,
 | 
			
		||||
        configured_value: InheritedBooleanInfoConfiguredValue.FALSE,
 | 
			
		||||
        inherited_value: false,
 | 
			
		||||
      },
 | 
			
		||||
    };
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  test('new change created with default', done => {
 | 
			
		||||
  test('new change created with default', async () => {
 | 
			
		||||
    const configInputObj = {
 | 
			
		||||
      branch: 'test-branch',
 | 
			
		||||
      subject: 'first change created with polygerrit ui',
 | 
			
		||||
@@ -59,35 +67,32 @@ suite('gr-create-change-dialog tests', () => {
 | 
			
		||||
      work_in_progress: true,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const saveStub = sinon.stub(element.$.restAPI,
 | 
			
		||||
        'createChange').callsFake(() => Promise.resolve({}));
 | 
			
		||||
    const saveStub = sinon
 | 
			
		||||
      .stub(element.$.restAPI, 'createChange')
 | 
			
		||||
      .callsFake(() => Promise.resolve(createChange()));
 | 
			
		||||
 | 
			
		||||
    element.branch = 'test-branch';
 | 
			
		||||
    element.branch = 'test-branch' as BranchName;
 | 
			
		||||
    element.topic = 'test-topic';
 | 
			
		||||
    element.subject = 'first change created with polygerrit ui';
 | 
			
		||||
    assert.isFalse(element.$.privateChangeCheckBox.checked);
 | 
			
		||||
 | 
			
		||||
    element.$.branchInput.bindValue = configInputObj.branch;
 | 
			
		||||
    element.$.tagNameInput.bindValue = configInputObj.topic;
 | 
			
		||||
    element.$.messageInput.bindValue = configInputObj.subject;
 | 
			
		||||
 | 
			
		||||
    element.handleCreateChange().then(() => {
 | 
			
		||||
      // Private change
 | 
			
		||||
      assert.isFalse(saveStub.lastCall.args[4]);
 | 
			
		||||
      // WIP Change
 | 
			
		||||
      assert.isTrue(saveStub.lastCall.args[5]);
 | 
			
		||||
      assert.isTrue(saveStub.called);
 | 
			
		||||
      done();
 | 
			
		||||
    });
 | 
			
		||||
    await element.handleCreateChange();
 | 
			
		||||
    // Private change
 | 
			
		||||
    assert.isFalse(saveStub.lastCall.args[4]);
 | 
			
		||||
    // WIP Change
 | 
			
		||||
    assert.isTrue(saveStub.lastCall.args[5]);
 | 
			
		||||
    assert.isTrue(saveStub.called);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  test('new change created with private', done => {
 | 
			
		||||
  test('new change created with private', async () => {
 | 
			
		||||
    element.privateByDefault = {
 | 
			
		||||
      configured_value: 'TRUE',
 | 
			
		||||
      configured_value: InheritedBooleanInfoConfiguredValue.TRUE,
 | 
			
		||||
      inherited_value: false,
 | 
			
		||||
      value: true,
 | 
			
		||||
    };
 | 
			
		||||
    sinon.stub(element, '_formatBooleanString')
 | 
			
		||||
        .callsFake(() => Promise.resolve(true));
 | 
			
		||||
    sinon.stub(element, '_formatBooleanString').callsFake(() => true);
 | 
			
		||||
    flush();
 | 
			
		||||
 | 
			
		||||
    const configInputObj = {
 | 
			
		||||
@@ -98,26 +103,23 @@ suite('gr-create-change-dialog tests', () => {
 | 
			
		||||
      work_in_progress: true,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const saveStub = sinon.stub(element.$.restAPI,
 | 
			
		||||
        'createChange').callsFake(() => Promise.resolve({}));
 | 
			
		||||
    const saveStub = sinon
 | 
			
		||||
      .stub(element.$.restAPI, 'createChange')
 | 
			
		||||
      .callsFake(() => Promise.resolve(createChange()));
 | 
			
		||||
 | 
			
		||||
    element.branch = 'test-branch';
 | 
			
		||||
    element.branch = 'test-branch' as BranchName;
 | 
			
		||||
    element.topic = 'test-topic';
 | 
			
		||||
    element.subject = 'first change created with polygerrit ui';
 | 
			
		||||
    assert.isTrue(element.$.privateChangeCheckBox.checked);
 | 
			
		||||
 | 
			
		||||
    element.$.branchInput.bindValue = configInputObj.branch;
 | 
			
		||||
    element.$.tagNameInput.bindValue = configInputObj.topic;
 | 
			
		||||
    element.$.messageInput.bindValue = configInputObj.subject;
 | 
			
		||||
 | 
			
		||||
    element.handleCreateChange().then(() => {
 | 
			
		||||
      // Private change
 | 
			
		||||
      assert.isTrue(saveStub.lastCall.args[4]);
 | 
			
		||||
      // WIP Change
 | 
			
		||||
      assert.isTrue(saveStub.lastCall.args[5]);
 | 
			
		||||
      assert.isTrue(saveStub.called);
 | 
			
		||||
      done();
 | 
			
		||||
    });
 | 
			
		||||
    await element.handleCreateChange();
 | 
			
		||||
    // Private change
 | 
			
		||||
    assert.isTrue(saveStub.lastCall.args[4]);
 | 
			
		||||
    // WIP Change
 | 
			
		||||
    assert.isTrue(saveStub.lastCall.args[5]);
 | 
			
		||||
    assert.isTrue(saveStub.called);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  test('_getRepoBranchesSuggestions empty', done => {
 | 
			
		||||
@@ -145,4 +147,3 @@ suite('gr-create-change-dialog tests', () => {
 | 
			
		||||
    assert.equal(element._computePrivateSectionClass(false), '');
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
@@ -20,7 +20,11 @@ import './gr-change-actions.js';
 | 
			
		||||
import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
 | 
			
		||||
import {GerritNav} from '../../core/gr-navigation/gr-navigation.js';
 | 
			
		||||
import {getPluginLoader} from '../../shared/gr-js-api-interface/gr-plugin-loader.js';
 | 
			
		||||
import {generateChange} from '../../../test/test-utils.js';
 | 
			
		||||
import {
 | 
			
		||||
  createChange,
 | 
			
		||||
  createChangeMessages,
 | 
			
		||||
  createRevisions,
 | 
			
		||||
} from '../../../test/test-data-generators.js';
 | 
			
		||||
 | 
			
		||||
const basicFixture = fixtureFromElement('gr-change-actions');
 | 
			
		||||
 | 
			
		||||
@@ -1810,10 +1814,11 @@ suite('gr-change-actions tests', () => {
 | 
			
		||||
        element.changeNum = 42;
 | 
			
		||||
        element.change._number = 42;
 | 
			
		||||
        element.latestPatchNum = 12;
 | 
			
		||||
        element.change = generateChange({
 | 
			
		||||
          revisionsCount: element.latestPatchNum,
 | 
			
		||||
          messagesCount: 1,
 | 
			
		||||
        });
 | 
			
		||||
        element.change = {
 | 
			
		||||
          ...createChange(),
 | 
			
		||||
          revisions: createRevisions(element.latestPatchNum),
 | 
			
		||||
          messages: createChangeMessages(1),
 | 
			
		||||
        };
 | 
			
		||||
        payload = {foo: 'bar'};
 | 
			
		||||
 | 
			
		||||
        onShowError = sinon.stub();
 | 
			
		||||
@@ -1826,12 +1831,12 @@ suite('gr-change-actions tests', () => {
 | 
			
		||||
        let sendStub;
 | 
			
		||||
        setup(() => {
 | 
			
		||||
          sinon.stub(element.$.restAPI, 'getChangeDetail')
 | 
			
		||||
              .returns(Promise.resolve(
 | 
			
		||||
                  generateChange({
 | 
			
		||||
                    // element has latest info
 | 
			
		||||
                    revisionsCount: element.latestPatchNum,
 | 
			
		||||
                    messagesCount: 1,
 | 
			
		||||
                  })));
 | 
			
		||||
              .returns(Promise.resolve({
 | 
			
		||||
                ...createChange(),
 | 
			
		||||
                // element has latest info
 | 
			
		||||
                revisions: createRevisions(element.latestPatchNum),
 | 
			
		||||
                messages: createChangeMessages(1),
 | 
			
		||||
              }));
 | 
			
		||||
          sendStub = sinon.stub(element.$.restAPI, 'executeChangeAction')
 | 
			
		||||
              .returns(Promise.resolve({}));
 | 
			
		||||
          getResponseObjectStub = sinon.stub(element.$.restAPI,
 | 
			
		||||
@@ -1945,12 +1950,12 @@ suite('gr-change-actions tests', () => {
 | 
			
		||||
      suite('failure modes', () => {
 | 
			
		||||
        test('non-latest', () => {
 | 
			
		||||
          sinon.stub(element.$.restAPI, 'getChangeDetail')
 | 
			
		||||
              .returns(Promise.resolve(
 | 
			
		||||
                  generateChange({
 | 
			
		||||
                    // new patchset was uploaded
 | 
			
		||||
                    revisionsCount: element.latestPatchNum + 1,
 | 
			
		||||
                    messagesCount: 1,
 | 
			
		||||
                  })));
 | 
			
		||||
              .returns(Promise.resolve({
 | 
			
		||||
                ...createChange(),
 | 
			
		||||
                // new patchset was uploaded
 | 
			
		||||
                revisions: createRevisions(element.latestPatchNum + 1),
 | 
			
		||||
                messages: createChangeMessages(1),
 | 
			
		||||
              }));
 | 
			
		||||
          const sendStub = sinon.stub(element.$.restAPI,
 | 
			
		||||
              'executeChangeAction');
 | 
			
		||||
 | 
			
		||||
@@ -1965,12 +1970,12 @@ suite('gr-change-actions tests', () => {
 | 
			
		||||
 | 
			
		||||
        test('send fails', () => {
 | 
			
		||||
          sinon.stub(element.$.restAPI, 'getChangeDetail')
 | 
			
		||||
              .returns(Promise.resolve(
 | 
			
		||||
                  generateChange({
 | 
			
		||||
                    // element has latest info
 | 
			
		||||
                    revisionsCount: element.latestPatchNum,
 | 
			
		||||
                    messagesCount: 1,
 | 
			
		||||
                  })));
 | 
			
		||||
              .returns(Promise.resolve({
 | 
			
		||||
                ...createChange(),
 | 
			
		||||
                // element has latest info
 | 
			
		||||
                revisions: createRevisions(element.latestPatchNum),
 | 
			
		||||
                messages: createChangeMessages(1),
 | 
			
		||||
              }));
 | 
			
		||||
          const sendStub = sinon.stub(element.$.restAPI,
 | 
			
		||||
              'executeChangeAction').callsFake(
 | 
			
		||||
              (num, method, patchNum, endpoint, payload, onErr) => {
 | 
			
		||||
 
 | 
			
		||||
@@ -50,7 +50,10 @@ import {
 | 
			
		||||
} from '../../../constants/constants';
 | 
			
		||||
import {changeIsOpen} from '../../../utils/change-util';
 | 
			
		||||
import {customElement, property, observe} from '@polymer/decorators';
 | 
			
		||||
import {ParsedChangeInfo} from '../../shared/gr-rest-api-interface/gr-reviewer-updates-parser';
 | 
			
		||||
import {
 | 
			
		||||
  EditRevisionInfo,
 | 
			
		||||
  ParsedChangeInfo,
 | 
			
		||||
} from '../../shared/gr-rest-api-interface/gr-reviewer-updates-parser';
 | 
			
		||||
import {
 | 
			
		||||
  AccountDetailInfo,
 | 
			
		||||
  AccountInfo,
 | 
			
		||||
@@ -135,7 +138,7 @@ export class GrChangeMetadata extends GestureEventListeners(
 | 
			
		||||
  account?: AccountDetailInfo;
 | 
			
		||||
 | 
			
		||||
  @property({type: Object})
 | 
			
		||||
  revision?: RevisionInfo;
 | 
			
		||||
  revision?: RevisionInfo | EditRevisionInfo;
 | 
			
		||||
 | 
			
		||||
  @property({type: Object})
 | 
			
		||||
  commitInfo?: CommitInfoWithRequiredCommit;
 | 
			
		||||
@@ -612,7 +615,7 @@ export class GrChangeMetadata extends GestureEventListeners(
 | 
			
		||||
 | 
			
		||||
  _computeParents(
 | 
			
		||||
    change?: ParsedChangeInfo,
 | 
			
		||||
    revision?: RevisionInfo
 | 
			
		||||
    revision?: RevisionInfo | EditRevisionInfo
 | 
			
		||||
  ): ParentCommitInfo[] {
 | 
			
		||||
    if (!revision || !revision.commit) {
 | 
			
		||||
      if (!change || !change.current_revision) {
 | 
			
		||||
 
 | 
			
		||||
@@ -132,7 +132,10 @@ import {
 | 
			
		||||
import {AppElementChangeViewParams} from '../../gr-app-types';
 | 
			
		||||
import {DropdownLink} from '../../shared/gr-dropdown/gr-dropdown';
 | 
			
		||||
import {PaperTabsElement} from '@polymer/paper-tabs/paper-tabs';
 | 
			
		||||
import {ParsedChangeInfo} from '../../shared/gr-rest-api-interface/gr-reviewer-updates-parser';
 | 
			
		||||
import {
 | 
			
		||||
  EditRevisionInfo,
 | 
			
		||||
  ParsedChangeInfo,
 | 
			
		||||
} from '../../shared/gr-rest-api-interface/gr-reviewer-updates-parser';
 | 
			
		||||
import {
 | 
			
		||||
  GrFileList,
 | 
			
		||||
  DEFAULT_NUM_FILES_SHOWN,
 | 
			
		||||
@@ -142,8 +145,12 @@ import {
 | 
			
		||||
  CustomKeyboardEvent,
 | 
			
		||||
  EditableContentSaveEvent,
 | 
			
		||||
  OpenFixPreviewEvent,
 | 
			
		||||
  ShowAlertEventDetail,
 | 
			
		||||
  SwitchTabEvent,
 | 
			
		||||
} from '../../../types/events';
 | 
			
		||||
import {GrButton} from '../../shared/gr-button/gr-button';
 | 
			
		||||
import {GrMessagesList} from '../gr-messages-list/gr-messages-list';
 | 
			
		||||
import {GrThreadList} from '../gr-thread-list/gr-thread-list';
 | 
			
		||||
 | 
			
		||||
const CHANGE_ID_ERROR = {
 | 
			
		||||
  MISMATCH: 'mismatch',
 | 
			
		||||
@@ -217,8 +224,15 @@ export interface GrChangeView {
 | 
			
		||||
    metadata: GrChangeMetadata;
 | 
			
		||||
    relatedChangesToggle: HTMLDivElement;
 | 
			
		||||
    mainChangeInfo: HTMLDivElement;
 | 
			
		||||
    commitCollapseToggleButton: GrButton;
 | 
			
		||||
    commitCollapseToggle: HTMLDivElement;
 | 
			
		||||
    relatedChangesToggleButton: GrButton;
 | 
			
		||||
    replyBtn: GrButton;
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type ChangeViewPatchRange = Partial<PatchRange>;
 | 
			
		||||
 | 
			
		||||
@customElement('gr-change-view')
 | 
			
		||||
export class GrChangeView extends KeyboardShortcutMixin(
 | 
			
		||||
  GestureEventListeners(LegacyElementMixin(PolymerElement))
 | 
			
		||||
@@ -377,7 +391,7 @@ export class GrChangeView extends KeyboardShortcutMixin(
 | 
			
		||||
  _changeIdCommitMessageError?: string;
 | 
			
		||||
 | 
			
		||||
  @property({type: Object})
 | 
			
		||||
  _patchRange?: PatchRange;
 | 
			
		||||
  _patchRange?: ChangeViewPatchRange;
 | 
			
		||||
 | 
			
		||||
  @property({type: String})
 | 
			
		||||
  _filesExpanded?: string;
 | 
			
		||||
@@ -386,7 +400,7 @@ export class GrChangeView extends KeyboardShortcutMixin(
 | 
			
		||||
  _basePatchNum?: string;
 | 
			
		||||
 | 
			
		||||
  @property({type: Object})
 | 
			
		||||
  _selectedRevision?: RevisionInfo;
 | 
			
		||||
  _selectedRevision?: RevisionInfo | EditRevisionInfo;
 | 
			
		||||
 | 
			
		||||
  @property({type: Object})
 | 
			
		||||
  _currentRevisionActions?: ActionNameToActionInfoMap;
 | 
			
		||||
@@ -431,7 +445,7 @@ export class GrChangeView extends KeyboardShortcutMixin(
 | 
			
		||||
    type: String,
 | 
			
		||||
    computed: '_computeChangeStatusChips(_change, _mergeable, _submitEnabled)',
 | 
			
		||||
  })
 | 
			
		||||
  _changeStatuses?: string;
 | 
			
		||||
  _changeStatuses?: string[];
 | 
			
		||||
 | 
			
		||||
  /** If false, then the "Show more" button was used to expand. */
 | 
			
		||||
  @property({type: Boolean})
 | 
			
		||||
@@ -653,11 +667,11 @@ export class GrChangeView extends KeyboardShortcutMixin(
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get messagesList() {
 | 
			
		||||
  get messagesList(): GrMessagesList | null {
 | 
			
		||||
    return this.shadowRoot!.querySelector('gr-messages-list');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get threadList() {
 | 
			
		||||
  get threadList(): GrThreadList | null {
 | 
			
		||||
    return this.shadowRoot!.querySelector('gr-thread-list');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -868,9 +882,9 @@ export class GrChangeView extends KeyboardShortcutMixin(
 | 
			
		||||
    loggedIn: boolean,
 | 
			
		||||
    editing: boolean,
 | 
			
		||||
    change: ChangeInfo,
 | 
			
		||||
    editMode: boolean,
 | 
			
		||||
    collapsed: boolean,
 | 
			
		||||
    collapsible: boolean
 | 
			
		||||
    editMode?: boolean,
 | 
			
		||||
    collapsed?: boolean,
 | 
			
		||||
    collapsible?: boolean
 | 
			
		||||
  ) {
 | 
			
		||||
    if (
 | 
			
		||||
      !loggedIn ||
 | 
			
		||||
@@ -1223,14 +1237,13 @@ export class GrChangeView extends KeyboardShortcutMixin(
 | 
			
		||||
        this._patchRange.basePatchNum !== value.basePatchNum);
 | 
			
		||||
    const changeChanged = this._changeNum !== value.changeNum;
 | 
			
		||||
 | 
			
		||||
    const patchRange = {
 | 
			
		||||
    const patchRange: ChangeViewPatchRange = {
 | 
			
		||||
      patchNum: value.patchNum,
 | 
			
		||||
      basePatchNum: value.basePatchNum || 'PARENT',
 | 
			
		||||
      basePatchNum: value.basePatchNum || ParentPatchSetNum,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    this.$.fileList.collapseAllDiffs();
 | 
			
		||||
    // TODO(TS): change patchRange to PatchRange.
 | 
			
		||||
    this._patchRange = patchRange as PatchRange;
 | 
			
		||||
    this._patchRange = patchRange;
 | 
			
		||||
 | 
			
		||||
    // If the change has already been loaded and the parameter change is only
 | 
			
		||||
    // in the patch range, then don't do a full reload.
 | 
			
		||||
@@ -1475,7 +1488,7 @@ export class GrChangeView extends KeyboardShortcutMixin(
 | 
			
		||||
   */
 | 
			
		||||
  _getBasePatchNum(
 | 
			
		||||
    change: ChangeInfo | ParsedChangeInfo,
 | 
			
		||||
    patchRange: PatchRange
 | 
			
		||||
    patchRange: ChangeViewPatchRange
 | 
			
		||||
  ) {
 | 
			
		||||
    if (patchRange.basePatchNum && patchRange.basePatchNum !== 'PARENT') {
 | 
			
		||||
      return patchRange.basePatchNum;
 | 
			
		||||
@@ -1575,7 +1588,7 @@ export class GrChangeView extends KeyboardShortcutMixin(
 | 
			
		||||
      GrChangeView,
 | 
			
		||||
      '_diffDrafts'
 | 
			
		||||
    > | null,
 | 
			
		||||
    canStartReview?: PolymerDeepPropertyChange<boolean, boolean>
 | 
			
		||||
    canStartReview?: boolean
 | 
			
		||||
  ) {
 | 
			
		||||
    if (changeRecord === undefined || canStartReview === undefined) {
 | 
			
		||||
      return 'Reply';
 | 
			
		||||
@@ -1685,11 +1698,12 @@ export class GrChangeView extends KeyboardShortcutMixin(
 | 
			
		||||
      throw new Error('missing required _patchRange property');
 | 
			
		||||
    const latestPatchNum = computeLatestPatchNum(this._allPatchSets);
 | 
			
		||||
    if (patchNumEquals(this._patchRange.patchNum, latestPatchNum)) {
 | 
			
		||||
      const detail: ShowAlertEventDetail = {
 | 
			
		||||
        message: 'Latest is already selected.',
 | 
			
		||||
      };
 | 
			
		||||
      this.dispatchEvent(
 | 
			
		||||
        new CustomEvent('show-alert', {
 | 
			
		||||
          detail: {
 | 
			
		||||
            message: 'Latest is already selected.',
 | 
			
		||||
          },
 | 
			
		||||
          detail,
 | 
			
		||||
          composed: true,
 | 
			
		||||
          bubbles: true,
 | 
			
		||||
        })
 | 
			
		||||
@@ -1927,7 +1941,7 @@ export class GrChangeView extends KeyboardShortcutMixin(
 | 
			
		||||
        basePatchNum: edit.base_patch_set_number,
 | 
			
		||||
        commit: edit.commit,
 | 
			
		||||
        fetch: edit.fetch,
 | 
			
		||||
      } as RevisionInfo;
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
    // If the edit is based on the most recent patchset, load it by
 | 
			
		||||
    // default, unless another patch set to load was specified in the URL.
 | 
			
		||||
@@ -2568,7 +2582,7 @@ export class GrChangeView extends KeyboardShortcutMixin(
 | 
			
		||||
    this.$.relatedChanges.reload();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _computeHeaderClass(editMode: boolean) {
 | 
			
		||||
  _computeHeaderClass(editMode?: boolean) {
 | 
			
		||||
    const classes = ['header'];
 | 
			
		||||
    if (editMode) {
 | 
			
		||||
      classes.push('editMode');
 | 
			
		||||
@@ -2577,7 +2591,10 @@ export class GrChangeView extends KeyboardShortcutMixin(
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _computeEditMode(
 | 
			
		||||
    patchRangeRecord: PolymerDeepPropertyChange<PatchRange, PatchRange>,
 | 
			
		||||
    patchRangeRecord: PolymerDeepPropertyChange<
 | 
			
		||||
      ChangeViewPatchRange,
 | 
			
		||||
      ChangeViewPatchRange
 | 
			
		||||
    >,
 | 
			
		||||
    paramsRecord: PolymerDeepPropertyChange<
 | 
			
		||||
      AppElementChangeViewParams,
 | 
			
		||||
      AppElementChangeViewParams
 | 
			
		||||
@@ -2734,7 +2751,10 @@ export class GrChangeView extends KeyboardShortcutMixin(
 | 
			
		||||
   * Wrapper for using in the element template and computed properties
 | 
			
		||||
   */
 | 
			
		||||
  _hasEditPatchsetLoaded(
 | 
			
		||||
    patchRangeRecord: PolymerDeepPropertyChange<PatchRange, PatchRange>
 | 
			
		||||
    patchRangeRecord: PolymerDeepPropertyChange<
 | 
			
		||||
      ChangeViewPatchRange,
 | 
			
		||||
      ChangeViewPatchRange
 | 
			
		||||
    >
 | 
			
		||||
  ) {
 | 
			
		||||
    const patchRange = patchRangeRecord.base;
 | 
			
		||||
    if (!patchRange) {
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -19,8 +19,8 @@ import '../../../test/common-test-setup-karma.js';
 | 
			
		||||
import './gr-file-list-header.js';
 | 
			
		||||
import {FilesExpandedState} from '../gr-file-list-constants.js';
 | 
			
		||||
import {GerritNav} from '../../core/gr-navigation/gr-navigation.js';
 | 
			
		||||
import {generateChange} from '../../../test/test-utils.js';
 | 
			
		||||
import 'lodash/lodash.js';
 | 
			
		||||
import {createRevisions} from '../../../test/test-data-generators.js';
 | 
			
		||||
 | 
			
		||||
const basicFixture = fixtureFromElement('gr-file-list-header');
 | 
			
		||||
 | 
			
		||||
@@ -269,7 +269,7 @@ suite('gr-file-list-header tests', () => {
 | 
			
		||||
 | 
			
		||||
    test('patch specific elements', () => {
 | 
			
		||||
      element.editMode = true;
 | 
			
		||||
      element.allPatchSets = generateChange({revisionsCount: 2}).revisions;
 | 
			
		||||
      element.allPatchSets = createRevisions(2);
 | 
			
		||||
      flush();
 | 
			
		||||
 | 
			
		||||
      assert.isFalse(isVisible(element.$.diffPrefsContainer));
 | 
			
		||||
 
 | 
			
		||||
@@ -36,6 +36,7 @@ import {
 | 
			
		||||
} from '../../shared/gr-autocomplete/gr-autocomplete';
 | 
			
		||||
import {getDocsBaseUrl} from '../../../utils/url-util';
 | 
			
		||||
import {CustomKeyboardEvent} from '../../../types/events';
 | 
			
		||||
import {MergeabilityComputationBehavior} from '../../../constants/constants';
 | 
			
		||||
 | 
			
		||||
// Possible static search options for auto complete, without negations.
 | 
			
		||||
const SEARCH_OPERATORS = [
 | 
			
		||||
@@ -197,8 +198,10 @@ export class GrSearchBar extends KeyboardShortcutMixin(
 | 
			
		||||
        serverConfig.change &&
 | 
			
		||||
        serverConfig.change.mergeability_computation_behavior;
 | 
			
		||||
      if (
 | 
			
		||||
        mergeability === 'API_REF_UPDATED_AND_CHANGE_REINDEX' ||
 | 
			
		||||
        mergeability === 'REF_UPDATED_AND_CHANGE_REINDEX'
 | 
			
		||||
        mergeability ===
 | 
			
		||||
          MergeabilityComputationBehavior.API_REF_UPDATED_AND_CHANGE_REINDEX ||
 | 
			
		||||
        mergeability ===
 | 
			
		||||
          MergeabilityComputationBehavior.REF_UPDATED_AND_CHANGE_REINDEX
 | 
			
		||||
      ) {
 | 
			
		||||
        // add 'is:mergeable' to SEARCH_OPERATORS_WITH_NEGATIONS_SET
 | 
			
		||||
        this._addOperator('is:mergeable');
 | 
			
		||||
 
 | 
			
		||||
@@ -22,7 +22,7 @@ import {GerritNav} from '../../core/gr-navigation/gr-navigation.js';
 | 
			
		||||
import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
 | 
			
		||||
import {sortComments} from '../../../utils/comment-util.js';
 | 
			
		||||
import {Side} from '../../../constants/constants.js';
 | 
			
		||||
import {generateChange} from '../../../test/test-utils';
 | 
			
		||||
import {createChange} from '../../../test/test-data-generators.js';
 | 
			
		||||
 | 
			
		||||
const basicFixture = fixtureFromElement('gr-diff-host');
 | 
			
		||||
 | 
			
		||||
@@ -55,7 +55,7 @@ suite('gr-diff-host tests', () => {
 | 
			
		||||
    });
 | 
			
		||||
    test('plugin layers requested', () => {
 | 
			
		||||
      element.patchRange = {};
 | 
			
		||||
      element.change = generateChange();
 | 
			
		||||
      element.change = createChange();
 | 
			
		||||
      element.reload();
 | 
			
		||||
      assert(element.$.jsAPI.getDiffLayers.called);
 | 
			
		||||
    });
 | 
			
		||||
@@ -323,7 +323,7 @@ suite('gr-diff-host tests', () => {
 | 
			
		||||
      sinon.stub(element.$.restAPI, 'getDiff').returns(
 | 
			
		||||
          Promise.resolve({content: []}));
 | 
			
		||||
      element.patchRange = {};
 | 
			
		||||
      element.change = generateChange();
 | 
			
		||||
      element.change = createChange();
 | 
			
		||||
      element.$.restAPI.getDiffPreferences().then(prefs => {
 | 
			
		||||
        element.prefs = prefs;
 | 
			
		||||
        return element.reload(true);
 | 
			
		||||
@@ -344,7 +344,7 @@ suite('gr-diff-host tests', () => {
 | 
			
		||||
      sinon.stub(element.$.restAPI, 'getDiff').returns(
 | 
			
		||||
          Promise.resolve({content: []}));
 | 
			
		||||
      element.patchRange = {};
 | 
			
		||||
      element.change = generateChange();
 | 
			
		||||
      element.change = createChange();
 | 
			
		||||
      element.reload();
 | 
			
		||||
      // Multiple cascading microtasks are scheduled.
 | 
			
		||||
      await flush();
 | 
			
		||||
@@ -366,7 +366,7 @@ suite('gr-diff-host tests', () => {
 | 
			
		||||
      sinon.stub(element.$.restAPI, 'getDiff').returns(
 | 
			
		||||
          Promise.resolve({content: []}));
 | 
			
		||||
      element.patchRange = {};
 | 
			
		||||
      element.change = generateChange();
 | 
			
		||||
      element.change = createChange();
 | 
			
		||||
      let reloadComplete = false;
 | 
			
		||||
      element.$.restAPI.getDiffPreferences()
 | 
			
		||||
          .then(prefs => {
 | 
			
		||||
@@ -392,7 +392,7 @@ suite('gr-diff-host tests', () => {
 | 
			
		||||
    // Stub the network calls into requests that never resolve.
 | 
			
		||||
    sinon.stub(element, '_getDiff').callsFake(() => new Promise(() => {}));
 | 
			
		||||
    element.patchRange = {};
 | 
			
		||||
    element.change = generateChange();
 | 
			
		||||
    element.change = createChange();
 | 
			
		||||
 | 
			
		||||
    // Needs to be set to something first for it to cancel.
 | 
			
		||||
    element.diff = {
 | 
			
		||||
@@ -410,7 +410,7 @@ suite('gr-diff-host tests', () => {
 | 
			
		||||
      getLoggedIn = false;
 | 
			
		||||
      element = basicFixture.instantiate();
 | 
			
		||||
      element.changeNum = 123;
 | 
			
		||||
      element.change = generateChange();
 | 
			
		||||
      element.change = createChange();
 | 
			
		||||
      element.path = 'some/path';
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
@@ -542,7 +542,7 @@ suite('gr-diff-host tests', () => {
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
        element.patchRange = {basePatchNum: 'PARENT', patchNum: 1};
 | 
			
		||||
        element.change = generateChange();
 | 
			
		||||
        element.change = createChange();
 | 
			
		||||
        element.comments = {
 | 
			
		||||
          left: [],
 | 
			
		||||
          right: [],
 | 
			
		||||
@@ -1447,7 +1447,7 @@ suite('gr-diff-host tests', () => {
 | 
			
		||||
      element.patchRange = {};
 | 
			
		||||
      element.prefs = prefs;
 | 
			
		||||
      element.changeNum = 123;
 | 
			
		||||
      element.change = generateChange();
 | 
			
		||||
      element.change = createChange();
 | 
			
		||||
      element.path = 'some/path';
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
@@ -1502,7 +1502,7 @@ suite('gr-diff-host tests', () => {
 | 
			
		||||
        }],
 | 
			
		||||
      };
 | 
			
		||||
      element.patchRange = {};
 | 
			
		||||
      element.change = generateChange();
 | 
			
		||||
      element.change = createChange();
 | 
			
		||||
      element.prefs = prefs;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
@@ -1559,7 +1559,7 @@ suite('gr-diff-host tests', () => {
 | 
			
		||||
      });
 | 
			
		||||
      element = basicFixture.instantiate();
 | 
			
		||||
      element.changeNum = 123;
 | 
			
		||||
      element.change = generateChange();
 | 
			
		||||
      element.change = createChange();
 | 
			
		||||
      element.path = 'some/path';
 | 
			
		||||
      const prefs = {
 | 
			
		||||
        line_length: 10,
 | 
			
		||||
 
 | 
			
		||||
@@ -19,12 +19,16 @@ import '../../../test/common-test-setup-karma.js';
 | 
			
		||||
import './gr-diff-view.js';
 | 
			
		||||
import {GerritNav} from '../../core/gr-navigation/gr-navigation.js';
 | 
			
		||||
import {ChangeStatus} from '../../../constants/constants.js';
 | 
			
		||||
import {generateChange, TestKeyboardShortcutBinder} from '../../../test/test-utils';
 | 
			
		||||
import {TestKeyboardShortcutBinder} from '../../../test/test-utils.js';
 | 
			
		||||
import {SPECIAL_PATCH_SET_NUM} from '../../../utils/patch-set-util.js';
 | 
			
		||||
import {Shortcut} from '../../../mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin.js';
 | 
			
		||||
import {_testOnly_findCommentById} from '../gr-comment-api/gr-comment-api.js';
 | 
			
		||||
import {appContext} from '../../../services/app-context.js';
 | 
			
		||||
import {GerritView} from '../../core/gr-navigation/gr-navigation.js';
 | 
			
		||||
import {
 | 
			
		||||
  createChange,
 | 
			
		||||
  createRevisions,
 | 
			
		||||
} from '../../../test/test-data-generators.js';
 | 
			
		||||
 | 
			
		||||
const basicFixture = fixtureFromElement('gr-diff-view');
 | 
			
		||||
 | 
			
		||||
@@ -184,8 +188,10 @@ suite('gr-diff-view tests', () => {
 | 
			
		||||
      sinon.stub(element.reporting, 'diffViewDisplayed');
 | 
			
		||||
      sinon.stub(element.$.diffHost, 'reload').returns(Promise.resolve());
 | 
			
		||||
      sinon.spy(element, '_paramsChanged');
 | 
			
		||||
      sinon.stub(element, '_getChangeDetail').returns(Promise.resolve(
 | 
			
		||||
          generateChange({revisionsCount: 11})));
 | 
			
		||||
      sinon.stub(element, '_getChangeDetail').returns(Promise.resolve({
 | 
			
		||||
        ...createChange(),
 | 
			
		||||
        revisions: createRevisions(11),
 | 
			
		||||
      }));
 | 
			
		||||
      element.params = {
 | 
			
		||||
        view: GerritNav.View.DIFF,
 | 
			
		||||
        changeNum: '42',
 | 
			
		||||
@@ -197,7 +203,10 @@ suite('gr-diff-view tests', () => {
 | 
			
		||||
        left: [{id: 'c1', __commentSide: 'left', line: 10}],
 | 
			
		||||
        right: [{id: 'c2', __commentSide: 'right', line: 11}],
 | 
			
		||||
      });
 | 
			
		||||
      element._change = generateChange({revisionsCount: 11});
 | 
			
		||||
      element._change = {
 | 
			
		||||
        ...createChange(),
 | 
			
		||||
        revisions: createRevisions(11),
 | 
			
		||||
      };
 | 
			
		||||
      return element._paramsChanged.returnValues[0].then(() => {
 | 
			
		||||
        assert.isTrue(initLineOfInterestAndCursorStub.
 | 
			
		||||
            calledWithExactly(true));
 | 
			
		||||
@@ -237,8 +246,10 @@ suite('gr-diff-view tests', () => {
 | 
			
		||||
          sinon.stub(element.$.diffHost, 'reload').returns(Promise.resolve());
 | 
			
		||||
          sinon.stub(element, '_isFileUnchanged').returns(true);
 | 
			
		||||
          sinon.spy(element, '_paramsChanged');
 | 
			
		||||
          sinon.stub(element, '_getChangeDetail').returns(Promise.resolve(
 | 
			
		||||
              generateChange({revisionsCount: 11})));
 | 
			
		||||
          sinon.stub(element, '_getChangeDetail').returns(Promise.resolve({
 | 
			
		||||
            ...createChange(),
 | 
			
		||||
            revisions: createRevisions(11),
 | 
			
		||||
          }));
 | 
			
		||||
          element.params = {
 | 
			
		||||
            view: GerritNav.View.DIFF,
 | 
			
		||||
            changeNum: '42',
 | 
			
		||||
@@ -247,7 +258,10 @@ suite('gr-diff-view tests', () => {
 | 
			
		||||
            commentId: 'c1',
 | 
			
		||||
          };
 | 
			
		||||
          sinon.stub(element.$.diffHost, '_commentsChanged');
 | 
			
		||||
          element._change = generateChange({revisionsCount: 11});
 | 
			
		||||
          element._change = {
 | 
			
		||||
            ...createChange(),
 | 
			
		||||
            revisions: createRevisions(11),
 | 
			
		||||
          };
 | 
			
		||||
          return element._paramsChanged.returnValues[0].then(() => {
 | 
			
		||||
            assert.isTrue(diffNavStub.lastCall.calledWithExactly(
 | 
			
		||||
                element._change, '/COMMIT_MSG', 2, 'PARENT', 10));
 | 
			
		||||
@@ -262,8 +276,10 @@ suite('gr-diff-view tests', () => {
 | 
			
		||||
          sinon.stub(element.$.diffHost, 'reload').returns(Promise.resolve());
 | 
			
		||||
          sinon.stub(element, '_isFileUnchanged').returns(true);
 | 
			
		||||
          sinon.spy(element, '_paramsChanged');
 | 
			
		||||
          sinon.stub(element, '_getChangeDetail').returns(Promise.resolve(
 | 
			
		||||
              generateChange({revisionsCount: 11})));
 | 
			
		||||
          sinon.stub(element, '_getChangeDetail').returns(Promise.resolve({
 | 
			
		||||
            ...createChange(),
 | 
			
		||||
            revisions: createRevisions(11),
 | 
			
		||||
          }));
 | 
			
		||||
          element.params = {
 | 
			
		||||
            view: GerritNav.View.DIFF,
 | 
			
		||||
            changeNum: '42',
 | 
			
		||||
@@ -272,7 +288,10 @@ suite('gr-diff-view tests', () => {
 | 
			
		||||
            commentId: 'c3',
 | 
			
		||||
          };
 | 
			
		||||
          sinon.stub(element.$.diffHost, '_commentsChanged');
 | 
			
		||||
          element._change = generateChange({revisionsCount: 11});
 | 
			
		||||
          element._change = {
 | 
			
		||||
            ...createChange(),
 | 
			
		||||
            revisions: createRevisions(11),
 | 
			
		||||
          };
 | 
			
		||||
          return element._paramsChanged.returnValues[0].then(() => {
 | 
			
		||||
            assert.isFalse(diffNavStub.called);
 | 
			
		||||
          });
 | 
			
		||||
@@ -317,7 +336,10 @@ suite('gr-diff-view tests', () => {
 | 
			
		||||
      element.$.restAPI.getDiffChangeDetail.restore();
 | 
			
		||||
      sinon.stub(element.$.restAPI, 'getDiffChangeDetail')
 | 
			
		||||
          .returns(
 | 
			
		||||
              Promise.resolve(generateChange({revisionsCount: 11})));
 | 
			
		||||
              Promise.resolve({
 | 
			
		||||
                ...createChange(),
 | 
			
		||||
                revisions: createRevisions(11),
 | 
			
		||||
              }));
 | 
			
		||||
      element._patchRange = {
 | 
			
		||||
        patchNum: 2,
 | 
			
		||||
        basePatchNum: 1,
 | 
			
		||||
@@ -472,7 +494,10 @@ suite('gr-diff-view tests', () => {
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    test('diff against latest', () => {
 | 
			
		||||
      element._change = generateChange({revisionsCount: 12});
 | 
			
		||||
      element._change = {
 | 
			
		||||
        ...createChange(),
 | 
			
		||||
        revisions: createRevisions(12),
 | 
			
		||||
      };
 | 
			
		||||
      element._patchRange = {
 | 
			
		||||
        basePatchNum: 5,
 | 
			
		||||
        patchNum: 10,
 | 
			
		||||
@@ -486,7 +511,10 @@ suite('gr-diff-view tests', () => {
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    test('_handleDiffBaseAgainstLeft', () => {
 | 
			
		||||
      element._change = generateChange({revisionsCount: 10});
 | 
			
		||||
      element._change = {
 | 
			
		||||
        ...createChange(),
 | 
			
		||||
        revisions: createRevisions(10),
 | 
			
		||||
      };
 | 
			
		||||
      element._patchRange = {
 | 
			
		||||
        patchNum: 3,
 | 
			
		||||
        basePatchNum: 1,
 | 
			
		||||
@@ -504,7 +532,10 @@ suite('gr-diff-view tests', () => {
 | 
			
		||||
 | 
			
		||||
    test('_handleDiffBaseAgainstLeft when initially navigating to a comment',
 | 
			
		||||
        () => {
 | 
			
		||||
          element._change = generateChange({revisionsCount: 10});
 | 
			
		||||
          element._change = {
 | 
			
		||||
            ...createChange(),
 | 
			
		||||
            revisions: createRevisions(10),
 | 
			
		||||
          };
 | 
			
		||||
          element._patchRange = {
 | 
			
		||||
            patchNum: 3,
 | 
			
		||||
            basePatchNum: 1,
 | 
			
		||||
@@ -523,7 +554,10 @@ suite('gr-diff-view tests', () => {
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
    test('_handleDiffRightAgainstLatest', () => {
 | 
			
		||||
      element._change = generateChange({revisionsCount: 10});
 | 
			
		||||
      element._change = {
 | 
			
		||||
        ...createChange(),
 | 
			
		||||
        revisions: createRevisions(10),
 | 
			
		||||
      };
 | 
			
		||||
      element._patchRange = {
 | 
			
		||||
        basePatchNum: 1,
 | 
			
		||||
        patchNum: 3,
 | 
			
		||||
@@ -538,7 +572,10 @@ suite('gr-diff-view tests', () => {
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    test('_handleDiffBaseAgainstLatest', () => {
 | 
			
		||||
      element._change = generateChange({revisionsCount: 10});
 | 
			
		||||
      element._change = {
 | 
			
		||||
        ...createChange(),
 | 
			
		||||
        revisions: createRevisions(10),
 | 
			
		||||
      };
 | 
			
		||||
      element._patchRange = {
 | 
			
		||||
        basePatchNum: 1,
 | 
			
		||||
        patchNum: 3,
 | 
			
		||||
@@ -1404,9 +1441,6 @@ suite('gr-diff-view tests', () => {
 | 
			
		||||
 | 
			
		||||
    suite('_initPatchRange', () => {
 | 
			
		||||
      setup(async () => {
 | 
			
		||||
        // const changeDetail = generateChange({revisionsCount: 5});
 | 
			
		||||
        // sinon.stub(element.$.restAPI, 'getDiffChangeDetail')
 | 
			
		||||
        //     .returns(Promise.resolve(changeDetail));
 | 
			
		||||
        element.params = {
 | 
			
		||||
          view: GerritView.DIFF,
 | 
			
		||||
          changeNum: '42',
 | 
			
		||||
@@ -1701,7 +1735,10 @@ suite('gr-diff-view tests', () => {
 | 
			
		||||
        patchNum: 1,
 | 
			
		||||
        basePatchNum: 'PARENT',
 | 
			
		||||
      };
 | 
			
		||||
      element._change = generateChange({revisionsCount: 1});
 | 
			
		||||
      element._change = {
 | 
			
		||||
        ...createChange(),
 | 
			
		||||
        revisions: createRevisions(1),
 | 
			
		||||
      };
 | 
			
		||||
      flush();
 | 
			
		||||
      assert.isTrue(GerritNav.navigateToDiff.notCalled);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -93,4 +93,9 @@ export interface PluginApi {
 | 
			
		||||
  attributeHelper(element: Element): GrAttributeHelper;
 | 
			
		||||
  restApi(): GrPluginRestApi;
 | 
			
		||||
  eventHelper(element: Node): GrEventHelper;
 | 
			
		||||
  registerDynamicCustomComponent(
 | 
			
		||||
    endpointName: string,
 | 
			
		||||
    moduleName?: string,
 | 
			
		||||
    options?: RegisterOptions
 | 
			
		||||
  ): HookApi;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,7 @@ import {GrPopupInterface} from './gr-popup-interface.js';
 | 
			
		||||
import {_testOnly_initGerritPluginApi} from '../../shared/gr-js-api-interface/gr-gerrit.js';
 | 
			
		||||
import {html} from '@polymer/polymer/lib/utils/html-tag.js';
 | 
			
		||||
import {PolymerElement} from '@polymer/polymer/polymer-element.js';
 | 
			
		||||
import {createIronOverlayBackdropStyleEl} from '../../../test/test-utils';
 | 
			
		||||
import {createIronOverlayBackdropStyleEl} from '../../../test/test-utils.js';
 | 
			
		||||
 | 
			
		||||
class GrUserTestPopupElement extends PolymerElement {
 | 
			
		||||
  static get is() { return 'gr-user-test-popup'; }
 | 
			
		||||
 
 | 
			
		||||
@@ -27,6 +27,7 @@ import {htmlTemplate} from './gr-account-info_html';
 | 
			
		||||
import {customElement, property, observe} from '@polymer/decorators';
 | 
			
		||||
import {AccountInfo, ServerInfo} from '../../../types/common';
 | 
			
		||||
import {RestApiService} from '../../../services/services/gr-rest-api/gr-rest-api';
 | 
			
		||||
import {EditableAccountField} from '../../../constants/constants';
 | 
			
		||||
 | 
			
		||||
export interface GrAccountInfo {
 | 
			
		||||
  $: {
 | 
			
		||||
@@ -211,12 +212,16 @@ export class GrAccountInfo extends GestureEventListeners(
 | 
			
		||||
 | 
			
		||||
    // Username may not be changed once it is set.
 | 
			
		||||
    return (
 | 
			
		||||
      config.auth.editable_account_fields.includes('USER_NAME') && !username
 | 
			
		||||
      config.auth.editable_account_fields.includes(
 | 
			
		||||
        EditableAccountField.USER_NAME
 | 
			
		||||
      ) && !username
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _computeNameMutable(config: ServerInfo) {
 | 
			
		||||
    return config.auth.editable_account_fields.includes('FULL_NAME');
 | 
			
		||||
    return config.auth.editable_account_fields.includes(
 | 
			
		||||
      EditableAccountField.FULL_NAME
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @observe('_account.status')
 | 
			
		||||
 
 | 
			
		||||
@@ -17,13 +17,14 @@
 | 
			
		||||
 | 
			
		||||
import '../../../test/common-test-setup-karma.js';
 | 
			
		||||
import './gr-email-editor.js';
 | 
			
		||||
import {GrEmailEditor} from './gr-email-editor';
 | 
			
		||||
 | 
			
		||||
const basicFixture = fixtureFromElement('gr-email-editor');
 | 
			
		||||
 | 
			
		||||
suite('gr-email-editor tests', () => {
 | 
			
		||||
  let element;
 | 
			
		||||
  let element: GrEmailEditor;
 | 
			
		||||
 | 
			
		||||
  setup(done => {
 | 
			
		||||
  setup(async () => {
 | 
			
		||||
    const emails = [
 | 
			
		||||
      {email: 'email@one.com'},
 | 
			
		||||
      {email: 'email@two.com', preferred: true},
 | 
			
		||||
@@ -31,36 +32,47 @@ suite('gr-email-editor tests', () => {
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    stub('gr-rest-api-interface', {
 | 
			
		||||
      getAccountEmails() { return Promise.resolve(emails); },
 | 
			
		||||
      getAccountEmails() {
 | 
			
		||||
        return Promise.resolve(emails);
 | 
			
		||||
      },
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    element = basicFixture.instantiate();
 | 
			
		||||
 | 
			
		||||
    element.loadData().then(flush(done));
 | 
			
		||||
    await element.loadData();
 | 
			
		||||
    await flush();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  test('renders', () => {
 | 
			
		||||
    const rows = element.shadowRoot
 | 
			
		||||
        .querySelector('table').querySelectorAll('tbody tr');
 | 
			
		||||
    const rows = element
 | 
			
		||||
      .shadowRoot!.querySelector('table')!
 | 
			
		||||
      .querySelectorAll('tbody tr') as NodeListOf<HTMLTableRowElement>;
 | 
			
		||||
 | 
			
		||||
    assert.equal(rows.length, 3);
 | 
			
		||||
 | 
			
		||||
    assert.isFalse(rows[0].querySelector('input[type=radio]').checked);
 | 
			
		||||
    assert.isNotOk(rows[0].querySelector('gr-button').disabled);
 | 
			
		||||
    assert.isFalse(
 | 
			
		||||
      (rows[0].querySelector('input[type=radio]') as HTMLInputElement).checked
 | 
			
		||||
    );
 | 
			
		||||
    assert.isNotOk(rows[0].querySelector('gr-button')!.disabled);
 | 
			
		||||
 | 
			
		||||
    assert.isTrue(rows[1].querySelector('input[type=radio]').checked);
 | 
			
		||||
    assert.isOk(rows[1].querySelector('gr-button').disabled);
 | 
			
		||||
    assert.isTrue(
 | 
			
		||||
      (rows[1].querySelector('input[type=radio]') as HTMLInputElement).checked
 | 
			
		||||
    );
 | 
			
		||||
    assert.isOk(rows[1].querySelector('gr-button')!.disabled);
 | 
			
		||||
 | 
			
		||||
    assert.isFalse(rows[2].querySelector('input[type=radio]').checked);
 | 
			
		||||
    assert.isNotOk(rows[2].querySelector('gr-button').disabled);
 | 
			
		||||
    assert.isFalse(
 | 
			
		||||
      (rows[2].querySelector('input[type=radio]') as HTMLInputElement).checked
 | 
			
		||||
    );
 | 
			
		||||
    assert.isNotOk(rows[2].querySelector('gr-button')!.disabled);
 | 
			
		||||
 | 
			
		||||
    assert.isFalse(element.hasUnsavedChanges);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  test('edit preferred', () => {
 | 
			
		||||
    const preferredChangedSpy = sinon.spy(element, '_handlePreferredChange');
 | 
			
		||||
    const radios = element.shadowRoot
 | 
			
		||||
        .querySelector('table').querySelectorAll('input[type=radio]');
 | 
			
		||||
    const radios = element
 | 
			
		||||
      .shadowRoot!.querySelector('table')!
 | 
			
		||||
      .querySelectorAll('input[type=radio]') as NodeListOf<HTMLInputElement>;
 | 
			
		||||
 | 
			
		||||
    assert.isFalse(element.hasUnsavedChanges);
 | 
			
		||||
    assert.isNotOk(element._newPreferred);
 | 
			
		||||
@@ -82,8 +94,9 @@ suite('gr-email-editor tests', () => {
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  test('delete email', () => {
 | 
			
		||||
    const buttons = element.shadowRoot
 | 
			
		||||
        .querySelector('table').querySelectorAll('gr-button');
 | 
			
		||||
    const buttons = element
 | 
			
		||||
      .shadowRoot!.querySelector('table')!
 | 
			
		||||
      .querySelectorAll('gr-button');
 | 
			
		||||
 | 
			
		||||
    assert.isFalse(element.hasUnsavedChanges);
 | 
			
		||||
    assert.isNotOk(element._newPreferred);
 | 
			
		||||
@@ -101,12 +114,15 @@ suite('gr-email-editor tests', () => {
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  test('save changes', done => {
 | 
			
		||||
    const deleteEmailStub =
 | 
			
		||||
        sinon.stub(element.$.restAPI, 'deleteAccountEmail');
 | 
			
		||||
    const setPreferredStub = sinon.stub(element.$.restAPI,
 | 
			
		||||
        'setPreferredAccountEmail');
 | 
			
		||||
    const rows = element.shadowRoot
 | 
			
		||||
        .querySelector('table').querySelectorAll('tbody tr');
 | 
			
		||||
    const deleteEmailStub = sinon.stub(element.$.restAPI, 'deleteAccountEmail');
 | 
			
		||||
    const setPreferredStub = sinon.stub(
 | 
			
		||||
      element.$.restAPI,
 | 
			
		||||
      'setPreferredAccountEmail'
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const rows = element
 | 
			
		||||
      .shadowRoot!.querySelector('table')!
 | 
			
		||||
      .querySelectorAll('tbody tr');
 | 
			
		||||
 | 
			
		||||
    assert.isFalse(element.hasUnsavedChanges);
 | 
			
		||||
    assert.isNotOk(element._newPreferred);
 | 
			
		||||
@@ -114,8 +130,8 @@ suite('gr-email-editor tests', () => {
 | 
			
		||||
    assert.equal(element._emails.length, 3);
 | 
			
		||||
 | 
			
		||||
    // Delete the first email and set the last as preferred.
 | 
			
		||||
    rows[0].querySelector('gr-button').click();
 | 
			
		||||
    rows[2].querySelector('input[type=radio]').click();
 | 
			
		||||
    rows[0].querySelector('gr-button')!.click();
 | 
			
		||||
    (rows[2].querySelector('input[type=radio]')! as HTMLInputElement).click();
 | 
			
		||||
 | 
			
		||||
    assert.isTrue(element.hasUnsavedChanges);
 | 
			
		||||
    assert.equal(element._newPreferred, 'email@three.com');
 | 
			
		||||
@@ -135,4 +151,3 @@ suite('gr-email-editor tests', () => {
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
@@ -26,6 +26,7 @@ import {htmlTemplate} from './gr-registration-dialog_html';
 | 
			
		||||
import {customElement, property, observe} from '@polymer/decorators';
 | 
			
		||||
import {ServerInfo, AccountDetailInfo} from '../../../types/common';
 | 
			
		||||
import {RestApiService} from '../../../services/services/gr-rest-api/gr-rest-api';
 | 
			
		||||
import {EditableAccountField} from '../../../constants/constants';
 | 
			
		||||
 | 
			
		||||
export interface GrRegistrationDialog {
 | 
			
		||||
  $: {
 | 
			
		||||
@@ -97,7 +98,9 @@ export class GrRegistrationDialog extends GestureEventListeners(
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
      config.auth.editable_account_fields.includes('USER_NAME') && !username
 | 
			
		||||
      config.auth.editable_account_fields.includes(
 | 
			
		||||
        EditableAccountField.USER_NAME
 | 
			
		||||
      ) && !username
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -351,9 +351,7 @@ export class GrRestApiInterface
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private _fetchSharedCacheURL(
 | 
			
		||||
    req: FetchJSONRequest
 | 
			
		||||
  ): Promise<ParsedJSON | undefined> {
 | 
			
		||||
  _fetchSharedCacheURL(req: FetchJSONRequest): Promise<ParsedJSON | undefined> {
 | 
			
		||||
    // Cache is shared across instances
 | 
			
		||||
    return this._restApiHelper.fetchCacheURL(req);
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -22,7 +22,10 @@ import {
 | 
			
		||||
  ChangeInfo,
 | 
			
		||||
  ChangeMessageInfo,
 | 
			
		||||
  ChangeViewChangeInfo,
 | 
			
		||||
  CommitInfo,
 | 
			
		||||
  PatchSetNum,
 | 
			
		||||
  ReviewerUpdateInfo,
 | 
			
		||||
  RevisionInfo,
 | 
			
		||||
  Timestamp,
 | 
			
		||||
} from '../../../types/common';
 | 
			
		||||
import {hasOwnProperty} from '../../../utils/common-util';
 | 
			
		||||
@@ -78,8 +81,16 @@ interface UpdateItem {
 | 
			
		||||
  prev_state?: ReviewerState;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface EditRevisionInfo extends Partial<RevisionInfo> {
 | 
			
		||||
  // EditRevisionInfo has less required properties then RevisionInfo
 | 
			
		||||
  _number: PatchSetNum;
 | 
			
		||||
  basePatchNum: PatchSetNum;
 | 
			
		||||
  commit: CommitInfo;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface ParsedChangeInfo
 | 
			
		||||
  extends Omit<ChangeViewChangeInfo, 'reviewer_updates'> {
 | 
			
		||||
  extends Omit<ChangeViewChangeInfo, 'reviewer_updates' | 'revisions'> {
 | 
			
		||||
  revisions: {[revisionId: string]: RevisionInfo | EditRevisionInfo};
 | 
			
		||||
  reviewer_updates?: ReviewerUpdateInfo[] | FormattedReviewerUpdateInfo[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -551,6 +551,14 @@ export class ShortcutManager {
 | 
			
		||||
 | 
			
		||||
  private readonly bindings = new Map<Shortcut, string[]>();
 | 
			
		||||
 | 
			
		||||
  public _testOnly_getBindings() {
 | 
			
		||||
    return this.bindings;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public _testOnly_isEmpty() {
 | 
			
		||||
    return this.activeHosts.size === 0 && this.listeners.size === 0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private readonly listeners = new Set<ShortcutListener>();
 | 
			
		||||
 | 
			
		||||
  bindShortcut(shortcut: Shortcut, ...bindings: string[]) {
 | 
			
		||||
 
 | 
			
		||||
@@ -34,7 +34,7 @@ def _get_ts_output_files(outdir, srcs):
 | 
			
		||||
        result.append(_get_ts_compiled_path(outdir, f))
 | 
			
		||||
    return result
 | 
			
		||||
 | 
			
		||||
def compile_ts(name, srcs, ts_outdir):
 | 
			
		||||
def compile_ts(name, srcs, ts_outdir, include_tests = False):
 | 
			
		||||
    """Compiles srcs files with the typescript compiler
 | 
			
		||||
 | 
			
		||||
    Args:
 | 
			
		||||
@@ -50,16 +50,31 @@ def compile_ts(name, srcs, ts_outdir):
 | 
			
		||||
    # List of files produced by the typescript compiler
 | 
			
		||||
    generated_js = _get_ts_output_files(ts_outdir, srcs)
 | 
			
		||||
 | 
			
		||||
    all_srcs = srcs + [
 | 
			
		||||
        ":tsconfig.json",
 | 
			
		||||
        ":tsconfig_bazel.json",
 | 
			
		||||
        "@ui_npm//:node_modules",
 | 
			
		||||
    ]
 | 
			
		||||
    ts_project = "tsconfig_bazel.json"
 | 
			
		||||
 | 
			
		||||
    if include_tests:
 | 
			
		||||
        all_srcs = all_srcs + [
 | 
			
		||||
            ":tsconfig_bazel_test.json",
 | 
			
		||||
            "@ui_dev_npm//:node_modules",
 | 
			
		||||
        ]
 | 
			
		||||
        ts_project = "tsconfig_bazel_test.json"
 | 
			
		||||
 | 
			
		||||
    # Run the compiler
 | 
			
		||||
    native.genrule(
 | 
			
		||||
        name = ts_rule_name,
 | 
			
		||||
        srcs = srcs + [
 | 
			
		||||
            ":tsconfig.json",
 | 
			
		||||
            "@ui_npm//:node_modules",
 | 
			
		||||
        ],
 | 
			
		||||
        srcs = all_srcs,
 | 
			
		||||
        outs = generated_js,
 | 
			
		||||
        cmd = " && ".join([
 | 
			
		||||
            "$(location //tools/node_tools:tsc-bin) --project $(location :tsconfig.json) --outdir $(RULEDIR)/" + ts_outdir + " --baseUrl ./external/ui_npm/node_modules",
 | 
			
		||||
            "$(location //tools/node_tools:tsc-bin) --project $(location :" +
 | 
			
		||||
            ts_project +
 | 
			
		||||
            ") --outdir $(RULEDIR)/" +
 | 
			
		||||
            ts_outdir +
 | 
			
		||||
            " --baseUrl ./external/ui_npm/node_modules/",
 | 
			
		||||
        ]),
 | 
			
		||||
        tools = ["//tools/node_tools:tsc-bin"],
 | 
			
		||||
    )
 | 
			
		||||
 
 | 
			
		||||
@@ -57,10 +57,16 @@ declare global {
 | 
			
		||||
 | 
			
		||||
const security = window.security;
 | 
			
		||||
 | 
			
		||||
export function installPolymerResin(safeTypesBridge: SafeTypeBridge) {
 | 
			
		||||
export const _testOnly_defaultResinReportHandler =
 | 
			
		||||
  security.polymer_resin.CONSOLE_LOGGING_REPORT_HANDLER;
 | 
			
		||||
 | 
			
		||||
export function installPolymerResin(
 | 
			
		||||
  safeTypesBridge: SafeTypeBridge,
 | 
			
		||||
  reportHandler = security.polymer_resin.CONSOLE_LOGGING_REPORT_HANDLER
 | 
			
		||||
) {
 | 
			
		||||
  window.security.polymer_resin.install({
 | 
			
		||||
    allowedIdentifierPrefixes: [''],
 | 
			
		||||
    reportHandler: security.polymer_resin.CONSOLE_LOGGING_REPORT_HANDLER,
 | 
			
		||||
    reportHandler,
 | 
			
		||||
    safeTypesBridge,
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -127,8 +127,8 @@ export function initErrorReporter(appContext: AppContext) {
 | 
			
		||||
      oldOnError(msg, url, line, column, error);
 | 
			
		||||
    }
 | 
			
		||||
    if (error) {
 | 
			
		||||
      line = line || (error as any).lineNumber;
 | 
			
		||||
      column = column || (error as any).columnNumber;
 | 
			
		||||
      line = line || error.lineNumber;
 | 
			
		||||
      column = column || error.columnNumber;
 | 
			
		||||
      let shortenedErrorStack = msg;
 | 
			
		||||
      if (error.stack) {
 | 
			
		||||
        const errorStackLines = error.stack.split('\n');
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,23 @@
 | 
			
		||||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
export const grReportingMock = {
 | 
			
		||||
import {ReportingService, Timer} from './gr-reporting';
 | 
			
		||||
 | 
			
		||||
export class MockTimer implements Timer {
 | 
			
		||||
  end(): this {
 | 
			
		||||
    return this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  reset(): this {
 | 
			
		||||
    return this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  withMaximum(_: number): this {
 | 
			
		||||
    return this;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const grReportingMock: ReportingService = {
 | 
			
		||||
  appStarted: () => {},
 | 
			
		||||
  beforeLocationChanged: () => {},
 | 
			
		||||
  changeDisplayed: () => {},
 | 
			
		||||
@@ -25,7 +41,7 @@ export const grReportingMock = {
 | 
			
		||||
  diffViewFullyLoaded: () => {},
 | 
			
		||||
  fileListDisplayed: () => {},
 | 
			
		||||
  getTimer: () => {
 | 
			
		||||
    return {end: () => {}};
 | 
			
		||||
    return new MockTimer();
 | 
			
		||||
  },
 | 
			
		||||
  locationChanged: () => {},
 | 
			
		||||
  onVisibilityChange: () => {},
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										27
									
								
								polygerrit-ui/app/test/@types/sinon-esm.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								polygerrit-ui/app/test/@types/sinon-esm.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
			
		||||
/**
 | 
			
		||||
 * @license
 | 
			
		||||
 * Copyright (C) 2020 The Android Open Source Project
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 * you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 *
 | 
			
		||||
 * http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
declare module 'sinon/pkg/sinon-esm' {
 | 
			
		||||
  // sinon-esm doesn't have it's own d.ts, reexport all types from sinon
 | 
			
		||||
  // This is a trick - @types/sinon adds interfaces and sinon instance
 | 
			
		||||
  // to a global variables/namespace. We reexport it here, so we
 | 
			
		||||
  // can use in our code when importing sinon-esm
 | 
			
		||||
  // eslint-disable-next-line import/no-default-export
 | 
			
		||||
  export default sinon;
 | 
			
		||||
  const sinon: Sinon.SinonStatic;
 | 
			
		||||
  export {SinonSpy, SinonFakeTimers, SinonStubbedMember};
 | 
			
		||||
}
 | 
			
		||||
@@ -14,14 +14,23 @@
 | 
			
		||||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
import './common-test-setup.js';
 | 
			
		||||
import '@polymer/test-fixture/test-fixture.js';
 | 
			
		||||
import 'chai/chai.js';
 | 
			
		||||
self.assert = window.chai.assert;
 | 
			
		||||
self.expect = window.chai.expect;
 | 
			
		||||
import './common-test-setup';
 | 
			
		||||
import '@polymer/test-fixture/test-fixture';
 | 
			
		||||
import 'chai/chai';
 | 
			
		||||
 | 
			
		||||
declare global {
 | 
			
		||||
  interface Window {
 | 
			
		||||
    flush: typeof flushImpl;
 | 
			
		||||
    fixtureFromTemplate: typeof fixtureFromTemplateImpl;
 | 
			
		||||
    fixtureFromElement: typeof fixtureFromElementImpl;
 | 
			
		||||
  }
 | 
			
		||||
  let flush: typeof flushImpl;
 | 
			
		||||
  let fixtureFromTemplate: typeof fixtureFromTemplateImpl;
 | 
			
		||||
  let fixtureFromElement: typeof fixtureFromElementImpl;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Workaround for https://github.com/karma-runner/karma-mocha/issues/227
 | 
			
		||||
let unhandledError = null;
 | 
			
		||||
let unhandledError: ErrorEvent;
 | 
			
		||||
 | 
			
		||||
window.addEventListener('error', e => {
 | 
			
		||||
  // For uncaught error mochajs doesn't print the full stack trace.
 | 
			
		||||
@@ -31,7 +40,7 @@ window.addEventListener('error', e => {
 | 
			
		||||
  unhandledError = e;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
let originalOnBeforeUnload;
 | 
			
		||||
let originalOnBeforeUnload: typeof window.onbeforeunload;
 | 
			
		||||
 | 
			
		||||
suiteSetup(() => {
 | 
			
		||||
  // This suiteSetup() method is called only once before all tests
 | 
			
		||||
@@ -39,7 +48,7 @@ suiteSetup(() => {
 | 
			
		||||
  // Can't use window.addEventListener("beforeunload",...) here,
 | 
			
		||||
  // the handler is raised too late.
 | 
			
		||||
  originalOnBeforeUnload = window.onbeforeunload;
 | 
			
		||||
  window.onbeforeunload = e => {
 | 
			
		||||
  window.onbeforeunload = function (e: BeforeUnloadEvent) {
 | 
			
		||||
    // If a test reloads a page, we can't prevent it.
 | 
			
		||||
    // However we can print earror and the stack trace with assert.fail
 | 
			
		||||
    try {
 | 
			
		||||
@@ -48,7 +57,9 @@ suiteSetup(() => {
 | 
			
		||||
      console.error('Page reloading attempt detected.');
 | 
			
		||||
      console.error(e.stack.toString());
 | 
			
		||||
    }
 | 
			
		||||
    originalOnBeforeUnload(e);
 | 
			
		||||
    if (originalOnBeforeUnload) {
 | 
			
		||||
      originalOnBeforeUnload.call(this, e);
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
@@ -64,18 +75,21 @@ suiteTeardown(() => {
 | 
			
		||||
// Keep the original one for use in test utils methods.
 | 
			
		||||
const nativeSetTimeout = window.setTimeout;
 | 
			
		||||
 | 
			
		||||
function flushImpl(): Promise<void>;
 | 
			
		||||
function flushImpl(callback: () => void): void;
 | 
			
		||||
/**
 | 
			
		||||
 * Triggers a flush of any pending events, observations, etc and calls you back
 | 
			
		||||
 * after they have been processed if callback is passed; otherwise returns
 | 
			
		||||
 * promise.
 | 
			
		||||
 *
 | 
			
		||||
 * @param {function()} callback
 | 
			
		||||
 */
 | 
			
		||||
function flush(callback) {
 | 
			
		||||
function flushImpl(callback?: () => void): Promise<void> | void {
 | 
			
		||||
  // Ideally, this function would be a call to Polymer.dom.flush, but that
 | 
			
		||||
  // doesn't support a callback yet
 | 
			
		||||
  // (https://github.com/Polymer/polymer-dev/issues/851)
 | 
			
		||||
  window.Polymer.dom.flush();
 | 
			
		||||
  // The type is used only in one place, disable eslint warning instead of
 | 
			
		||||
  // creating an interface
 | 
			
		||||
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
 | 
			
		||||
  (window as any).Polymer.dom.flush();
 | 
			
		||||
  if (callback) {
 | 
			
		||||
    nativeSetTimeout(callback, 0);
 | 
			
		||||
  } else {
 | 
			
		||||
@@ -85,19 +99,12 @@ function flush(callback) {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
self.flush = flush;
 | 
			
		||||
self.flush = flushImpl;
 | 
			
		||||
 | 
			
		||||
class TestFixtureIdProvider {
 | 
			
		||||
  static get instance() {
 | 
			
		||||
    if (!TestFixtureIdProvider._instance) {
 | 
			
		||||
      TestFixtureIdProvider._instance = new TestFixtureIdProvider();
 | 
			
		||||
    }
 | 
			
		||||
    return TestFixtureIdProvider._instance;
 | 
			
		||||
  }
 | 
			
		||||
  public static readonly instance: TestFixtureIdProvider = new TestFixtureIdProvider();
 | 
			
		||||
 | 
			
		||||
  constructor() {
 | 
			
		||||
    this.fixturesCount = 1;
 | 
			
		||||
  }
 | 
			
		||||
  private fixturesCount = 1;
 | 
			
		||||
 | 
			
		||||
  generateNewFixtureId() {
 | 
			
		||||
    this.fixturesCount++;
 | 
			
		||||
@@ -105,22 +112,24 @@ class TestFixtureIdProvider {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface TagTestFixture<T extends Element> {
 | 
			
		||||
  instantiate(model?: unknown): T;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class TestFixture {
 | 
			
		||||
  constructor(fixtureId) {
 | 
			
		||||
    this.fixtureId = fixtureId;
 | 
			
		||||
  }
 | 
			
		||||
  constructor(private readonly fixtureId: string) {}
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Create an instance of a fixture's template.
 | 
			
		||||
   *
 | 
			
		||||
   * @param {Object} model - see Data-bound sections at
 | 
			
		||||
   * @param model - see Data-bound sections at
 | 
			
		||||
   *   https://www.webcomponents.org/element/@polymer/test-fixture
 | 
			
		||||
   * @return {HTMLElement | HTMLElement[]} - if the fixture's template contains
 | 
			
		||||
   * @return - if the fixture's template contains
 | 
			
		||||
   *   a single element, returns the appropriated instantiated element.
 | 
			
		||||
   *   Otherwise, it return an array of all instantiated elements from the
 | 
			
		||||
   *   template.
 | 
			
		||||
   */
 | 
			
		||||
  instantiate(model) {
 | 
			
		||||
  instantiate(model?: unknown): HTMLElement | HTMLElement[] {
 | 
			
		||||
    // The window.fixture method is defined in common-test-setup.js
 | 
			
		||||
    return window.fixture(this.fixtureId, model);
 | 
			
		||||
  }
 | 
			
		||||
@@ -153,10 +162,9 @@ class TestFixture {
 | 
			
		||||
 *   });
 | 
			
		||||
 * }
 | 
			
		||||
 *
 | 
			
		||||
 * @param {HTMLTemplateElement} template - a template for a fixture
 | 
			
		||||
 * @return {TestFixture} - the instance of TestFixture class
 | 
			
		||||
 * @param template - a template for a fixture
 | 
			
		||||
 */
 | 
			
		||||
function fixtureFromTemplate(template) {
 | 
			
		||||
function fixtureFromTemplateImpl(template: HTMLTemplateElement): TestFixture {
 | 
			
		||||
  const fixtureId = TestFixtureIdProvider.instance.generateNewFixtureId();
 | 
			
		||||
  const testFixture = document.createElement('test-fixture');
 | 
			
		||||
  testFixture.setAttribute('id', fixtureId);
 | 
			
		||||
@@ -183,14 +191,17 @@ function fixtureFromTemplate(template) {
 | 
			
		||||
 *   });
 | 
			
		||||
 * }
 | 
			
		||||
 *
 | 
			
		||||
 * @param {HTMLTemplateElement} template - a template for a fixture
 | 
			
		||||
 * @return {TestFixture} - the instance of TestFixture class
 | 
			
		||||
 * @param tagName - a template for a fixture is <tagName></tagName>
 | 
			
		||||
 */
 | 
			
		||||
function fixtureFromElement(tagName) {
 | 
			
		||||
function fixtureFromElementImpl<T extends keyof HTMLElementTagNameMap>(
 | 
			
		||||
  tagName: T
 | 
			
		||||
): TagTestFixture<HTMLElementTagNameMap[T]> {
 | 
			
		||||
  const template = document.createElement('template');
 | 
			
		||||
  template.innerHTML = `<${tagName}></${tagName}>`;
 | 
			
		||||
  return fixtureFromTemplate(template);
 | 
			
		||||
  return (fixtureFromTemplate(template) as unknown) as TagTestFixture<
 | 
			
		||||
    HTMLElementTagNameMap[T]
 | 
			
		||||
  >;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
window.fixtureFromTemplate = fixtureFromTemplate;
 | 
			
		||||
window.fixtureFromElement = fixtureFromElement;
 | 
			
		||||
window.fixtureFromTemplate = fixtureFromTemplateImpl;
 | 
			
		||||
window.fixtureFromElement = fixtureFromElementImpl;
 | 
			
		||||
@@ -15,51 +15,81 @@
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
// This should be the first import to install handler before any other code
 | 
			
		||||
import './source-map-support-install.js';
 | 
			
		||||
import './source-map-support-install';
 | 
			
		||||
// TODO(dmfilippov): remove bundled-polymer.js imports when the following issue
 | 
			
		||||
// https://github.com/Polymer/polymer-resin/issues/9 is resolved.
 | 
			
		||||
import '../scripts/bundled-polymer.js';
 | 
			
		||||
import 'polymer-resin/standalone/polymer-resin.js';
 | 
			
		||||
import '@polymer/iron-test-helpers/iron-test-helpers.js';
 | 
			
		||||
import './test-router.js';
 | 
			
		||||
import '../scripts/bundled-polymer';
 | 
			
		||||
import '@polymer/iron-test-helpers/iron-test-helpers';
 | 
			
		||||
import './test-router';
 | 
			
		||||
import {_testOnlyInitAppContext} from './test-app-context-init';
 | 
			
		||||
import {_testOnly_resetPluginLoader} from '../elements/shared/gr-js-api-interface/gr-plugin-loader.js';
 | 
			
		||||
import {_testOnlyResetRestApi} from '../elements/shared/gr-js-api-interface/gr-plugin-rest-api.js';
 | 
			
		||||
import {_testOnlyResetGrRestApiSharedObjects} from '../elements/shared/gr-rest-api-interface/gr-rest-api-interface.js';
 | 
			
		||||
import {cleanupTestUtils, TestKeyboardShortcutBinder} from './test-utils.js';
 | 
			
		||||
import {_testOnly_resetPluginLoader} from '../elements/shared/gr-js-api-interface/gr-plugin-loader';
 | 
			
		||||
import {_testOnlyResetRestApi} from '../elements/shared/gr-js-api-interface/gr-plugin-rest-api';
 | 
			
		||||
import {_testOnlyResetGrRestApiSharedObjects} from '../elements/shared/gr-rest-api-interface/gr-rest-api-interface';
 | 
			
		||||
import {
 | 
			
		||||
  cleanupTestUtils,
 | 
			
		||||
  getCleanupsCount,
 | 
			
		||||
  registerTestCleanup,
 | 
			
		||||
  TestKeyboardShortcutBinder,
 | 
			
		||||
} from './test-utils';
 | 
			
		||||
import {flushDebouncers} from '@polymer/polymer/lib/utils/debounce';
 | 
			
		||||
import {_testOnly_getShortcutManagerInstance} from '../mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin.js';
 | 
			
		||||
import sinon from 'sinon/pkg/sinon-esm.js';
 | 
			
		||||
import {safeTypesBridge} from '../utils/safe-types-util.js';
 | 
			
		||||
import {_testOnly_initGerritPluginApi} from '../elements/shared/gr-js-api-interface/gr-gerrit.js';
 | 
			
		||||
import {initGlobalVariables} from '../elements/gr-app-global-var-init.js';
 | 
			
		||||
import {_testOnly_getShortcutManagerInstance} from '../mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin';
 | 
			
		||||
import sinon, {SinonSpy} from 'sinon/pkg/sinon-esm';
 | 
			
		||||
import {safeTypesBridge} from '../utils/safe-types-util';
 | 
			
		||||
import {_testOnly_initGerritPluginApi} from '../elements/shared/gr-js-api-interface/gr-gerrit';
 | 
			
		||||
import {initGlobalVariables} from '../elements/gr-app-global-var-init';
 | 
			
		||||
import 'chai/chai';
 | 
			
		||||
import {
 | 
			
		||||
  _testOnly_defaultResinReportHandler,
 | 
			
		||||
  installPolymerResin,
 | 
			
		||||
} from '../scripts/polymer-resin-install';
 | 
			
		||||
import {hasOwnProperty} from '../utils/common-util';
 | 
			
		||||
 | 
			
		||||
declare global {
 | 
			
		||||
  interface Window {
 | 
			
		||||
    assert: typeof chai.assert;
 | 
			
		||||
    expect: typeof chai.expect;
 | 
			
		||||
    fixture: typeof fixtureImpl;
 | 
			
		||||
    stub: typeof stubImpl;
 | 
			
		||||
    sinon: typeof sinon;
 | 
			
		||||
  }
 | 
			
		||||
  let assert: typeof chai.assert;
 | 
			
		||||
  let expect: typeof chai.expect;
 | 
			
		||||
  let stub: typeof stubImpl;
 | 
			
		||||
  let sinon: typeof sinon;
 | 
			
		||||
}
 | 
			
		||||
window.assert = chai.assert;
 | 
			
		||||
window.expect = chai.expect;
 | 
			
		||||
 | 
			
		||||
window.sinon = sinon;
 | 
			
		||||
 | 
			
		||||
security.polymer_resin.install({
 | 
			
		||||
  allowedIdentifierPrefixes: [''],
 | 
			
		||||
  reportHandler(isViolation, fmt, ...args) {
 | 
			
		||||
    const log = security.polymer_resin.CONSOLE_LOGGING_REPORT_HANDLER;
 | 
			
		||||
    log(isViolation, fmt, ...args);
 | 
			
		||||
    if (isViolation) {
 | 
			
		||||
      // This will cause the test to fail if there is a data binding
 | 
			
		||||
      // violation.
 | 
			
		||||
      throw new Error(
 | 
			
		||||
          'polymer-resin violation: ' + fmt +
 | 
			
		||||
        JSON.stringify(args));
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  safeTypesBridge,
 | 
			
		||||
installPolymerResin(safeTypesBridge, (isViolation, fmt, ...args) => {
 | 
			
		||||
  const log = _testOnly_defaultResinReportHandler;
 | 
			
		||||
  log(isViolation, fmt, ...args);
 | 
			
		||||
  if (isViolation) {
 | 
			
		||||
    // This will cause the test to fail if there is a data binding
 | 
			
		||||
    // violation.
 | 
			
		||||
    throw new Error('polymer-resin violation: ' + fmt + JSON.stringify(args));
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const cleanups = [];
 | 
			
		||||
interface TestFixtureElement extends HTMLElement {
 | 
			
		||||
  restore(): void;
 | 
			
		||||
  create(model?: unknown): HTMLElement | HTMLElement[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getFixtureElementById(fixtureId: string) {
 | 
			
		||||
  return document.getElementById(fixtureId) as TestFixtureElement;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// For karma always set our implementation
 | 
			
		||||
// (karma doesn't provide the fixture method)
 | 
			
		||||
window.fixture = function(fixtureId, model) {
 | 
			
		||||
function fixtureImpl(fixtureId: string, model: unknown) {
 | 
			
		||||
  // This method is inspired by web-component-tester method
 | 
			
		||||
  cleanups.push(() => document.getElementById(fixtureId).restore());
 | 
			
		||||
  return document.getElementById(fixtureId).create(model);
 | 
			
		||||
};
 | 
			
		||||
  registerTestCleanup(() => getFixtureElementById(fixtureId).restore());
 | 
			
		||||
  return getFixtureElementById(fixtureId).create(model);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
window.fixture = fixtureImpl;
 | 
			
		||||
 | 
			
		||||
setup(() => {
 | 
			
		||||
  window.Gerrit = {};
 | 
			
		||||
@@ -67,16 +97,18 @@ setup(() => {
 | 
			
		||||
 | 
			
		||||
  // If the following asserts fails - then window.stub is
 | 
			
		||||
  // overwritten by some other code.
 | 
			
		||||
  assert.equal(cleanups.length, 0);
 | 
			
		||||
  assert.equal(getCleanupsCount(), 0);
 | 
			
		||||
  // The following calls is nessecary to avoid influence of previously executed
 | 
			
		||||
  // tests.
 | 
			
		||||
  TestKeyboardShortcutBinder.push();
 | 
			
		||||
  _testOnlyInitAppContext();
 | 
			
		||||
  _testOnly_initGerritPluginApi();
 | 
			
		||||
  const mgr = _testOnly_getShortcutManagerInstance();
 | 
			
		||||
  assert.equal(mgr.activeHosts.size, 0);
 | 
			
		||||
  assert.equal(mgr.listeners.size, 0);
 | 
			
		||||
  document.getSelection().removeAllRanges();
 | 
			
		||||
  assert.isTrue(mgr._testOnly_isEmpty());
 | 
			
		||||
  const selection = document.getSelection();
 | 
			
		||||
  if (selection) {
 | 
			
		||||
    selection.removeAllRanges();
 | 
			
		||||
  }
 | 
			
		||||
  const pl = _testOnly_resetPluginLoader();
 | 
			
		||||
  // For testing, always init with empty plugin list
 | 
			
		||||
  // Since when serve in gr-app, we always retrieve the list
 | 
			
		||||
@@ -92,49 +124,66 @@ setup(() => {
 | 
			
		||||
 | 
			
		||||
// For karma always set our implementation
 | 
			
		||||
// (karma doesn't provide the stub method)
 | 
			
		||||
window.stub = function(tagName, implementation) {
 | 
			
		||||
function stubImpl<T extends keyof HTMLElementTagNameMap>(
 | 
			
		||||
  tagName: T,
 | 
			
		||||
  implementation: Partial<HTMLElementTagNameMap[T]>
 | 
			
		||||
) {
 | 
			
		||||
  // This method is inspired by web-component-tester method
 | 
			
		||||
  const proto = document.createElement(tagName).constructor.prototype;
 | 
			
		||||
  const stubs = Object.keys(implementation)
 | 
			
		||||
      .map(key => sinon.stub(proto, key).callsFake(implementation[key]));
 | 
			
		||||
  cleanups.push(() => {
 | 
			
		||||
  const proto = document.createElement(tagName).constructor
 | 
			
		||||
    .prototype as HTMLElementTagNameMap[T];
 | 
			
		||||
  let key: keyof HTMLElementTagNameMap[T];
 | 
			
		||||
  const stubs: SinonSpy[] = [];
 | 
			
		||||
  for (key in implementation) {
 | 
			
		||||
    if (hasOwnProperty(implementation, key)) {
 | 
			
		||||
      stubs.push(sinon.stub(proto, key).callsFake(implementation[key]));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  registerTestCleanup(() => {
 | 
			
		||||
    stubs.forEach(stub => {
 | 
			
		||||
      stub.restore();
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
window.stub = stubImpl;
 | 
			
		||||
 | 
			
		||||
// Very simple function to catch unexpected elements in documents body.
 | 
			
		||||
// It can't catch everything, but in most cases it is enough.
 | 
			
		||||
function checkChildAllowed(element) {
 | 
			
		||||
function checkChildAllowed(element: Element) {
 | 
			
		||||
  const allowedTags = ['SCRIPT', 'IRON-A11Y-ANNOUNCER'];
 | 
			
		||||
  if (allowedTags.includes(element.tagName)) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (element.tagName === 'TEST-FIXTURE') {
 | 
			
		||||
    if (element.children.length == 0 ||
 | 
			
		||||
        (element.children.length == 1 &&
 | 
			
		||||
        element.children[0].tagName === 'TEMPLATE')) {
 | 
			
		||||
    if (
 | 
			
		||||
      element.children.length === 0 ||
 | 
			
		||||
      (element.children.length === 1 &&
 | 
			
		||||
        element.children[0].tagName === 'TEMPLATE')
 | 
			
		||||
    ) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    assert.fail(`Test fixture
 | 
			
		||||
    assert.fail(
 | 
			
		||||
      `Test fixture
 | 
			
		||||
        ${element.outerHTML}` +
 | 
			
		||||
        `isn't resotred after the test is finished. Please ensure that ` +
 | 
			
		||||
        `restore() method is called for this test-fixture. Usually the call` +
 | 
			
		||||
        `happens automatically.`);
 | 
			
		||||
        "isn't resotred after the test is finished. Please ensure that " +
 | 
			
		||||
        'restore() method is called for this test-fixture. Usually the call' +
 | 
			
		||||
        'happens automatically.'
 | 
			
		||||
    );
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (element.tagName === 'DIV' && element.id === 'gr-hovercard-container' &&
 | 
			
		||||
      element.childNodes.length === 0) {
 | 
			
		||||
  if (
 | 
			
		||||
    element.tagName === 'DIV' &&
 | 
			
		||||
    element.id === 'gr-hovercard-container' &&
 | 
			
		||||
    element.childNodes.length === 0
 | 
			
		||||
  ) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  assert.fail(
 | 
			
		||||
      `The following node remains in document after the test:
 | 
			
		||||
    `The following node remains in document after the test:
 | 
			
		||||
      ${element.tagName}
 | 
			
		||||
      Outer HTML:
 | 
			
		||||
      ${element.outerHTML},
 | 
			
		||||
      Stack trace:
 | 
			
		||||
      ${element.stackTrace}`);
 | 
			
		||||
      ${element.outerHTML}`
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
function checkGlobalSpace() {
 | 
			
		||||
  for (const child of document.body.children) {
 | 
			
		||||
@@ -145,8 +194,6 @@ function checkGlobalSpace() {
 | 
			
		||||
teardown(() => {
 | 
			
		||||
  sinon.restore();
 | 
			
		||||
  cleanupTestUtils();
 | 
			
		||||
  cleanups.forEach(cleanup => cleanup());
 | 
			
		||||
  cleanups.splice(0);
 | 
			
		||||
  TestKeyboardShortcutBinder.pop();
 | 
			
		||||
  checkGlobalSpace();
 | 
			
		||||
  // Clean Polymer debouncer queue, so next tests will not be affected.
 | 
			
		||||
@@ -15,6 +15,19 @@
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
// Mark the file as a module. Otherwise typescript assumes this is a script
 | 
			
		||||
// and doesn't allow "declare global".
 | 
			
		||||
// See: https://www.typescriptlang.org/docs/handbook/modules.html
 | 
			
		||||
export {};
 | 
			
		||||
 | 
			
		||||
declare global {
 | 
			
		||||
  interface Window {
 | 
			
		||||
    sourceMapSupport: {
 | 
			
		||||
      install(): void;
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// The karma.conf.js file loads required module before any other modules
 | 
			
		||||
// The source-map-support.js can't be imported with import ... statement
 | 
			
		||||
window.sourceMapSupport.install();
 | 
			
		||||
@@ -16,14 +16,17 @@
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
// Init app context before any other imports
 | 
			
		||||
import {initAppContext} from '../services/app-context-init.js';
 | 
			
		||||
import {grReportingMock} from '../services/gr-reporting/gr-reporting_mock.js';
 | 
			
		||||
import {appContext} from '../services/app-context.js';
 | 
			
		||||
import {initAppContext} from '../services/app-context-init';
 | 
			
		||||
import {grReportingMock} from '../services/gr-reporting/gr-reporting_mock';
 | 
			
		||||
import {AppContext, appContext} from '../services/app-context';
 | 
			
		||||
 | 
			
		||||
export function _testOnlyInitAppContext() {
 | 
			
		||||
  initAppContext();
 | 
			
		||||
 | 
			
		||||
  function setMock(serviceName, setupMock) {
 | 
			
		||||
  function setMock<T extends keyof AppContext>(
 | 
			
		||||
    serviceName: T,
 | 
			
		||||
    setupMock: AppContext[T]
 | 
			
		||||
  ) {
 | 
			
		||||
    Object.defineProperty(appContext, serviceName, {
 | 
			
		||||
      get() {
 | 
			
		||||
        return setupMock;
 | 
			
		||||
							
								
								
									
										407
									
								
								polygerrit-ui/app/test/test-data-generators.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										407
									
								
								polygerrit-ui/app/test/test-data-generators.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,407 @@
 | 
			
		||||
/**
 | 
			
		||||
 * @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 {
 | 
			
		||||
  AccountId,
 | 
			
		||||
  AccountInfo,
 | 
			
		||||
  AccountsConfigInfo,
 | 
			
		||||
  ApprovalInfo,
 | 
			
		||||
  AuthInfo,
 | 
			
		||||
  BranchName,
 | 
			
		||||
  ChangeConfigInfo,
 | 
			
		||||
  ChangeId,
 | 
			
		||||
  ChangeInfo,
 | 
			
		||||
  ChangeInfoId,
 | 
			
		||||
  ChangeMessageId,
 | 
			
		||||
  ChangeMessageInfo,
 | 
			
		||||
  ChangeViewChangeInfo,
 | 
			
		||||
  CommentLinkInfo,
 | 
			
		||||
  CommentLinks,
 | 
			
		||||
  CommitId,
 | 
			
		||||
  CommitInfo,
 | 
			
		||||
  ConfigInfo,
 | 
			
		||||
  DownloadInfo,
 | 
			
		||||
  EditPatchSetNum,
 | 
			
		||||
  GerritInfo,
 | 
			
		||||
  EmailAddress,
 | 
			
		||||
  GitPersonInfo,
 | 
			
		||||
  GitRef,
 | 
			
		||||
  InheritedBooleanInfo,
 | 
			
		||||
  MaxObjectSizeLimitInfo,
 | 
			
		||||
  MergeableInfo,
 | 
			
		||||
  NumericChangeId,
 | 
			
		||||
  PatchSetNum,
 | 
			
		||||
  PluginConfigInfo,
 | 
			
		||||
  PreferencesInfo,
 | 
			
		||||
  RepoName,
 | 
			
		||||
  Reviewers,
 | 
			
		||||
  RevisionInfo,
 | 
			
		||||
  SchemesInfoMap,
 | 
			
		||||
  ServerInfo,
 | 
			
		||||
  SubmitTypeInfo,
 | 
			
		||||
  SuggestInfo,
 | 
			
		||||
  Timestamp,
 | 
			
		||||
  TimezoneOffset,
 | 
			
		||||
  UserConfigInfo,
 | 
			
		||||
} from '../types/common';
 | 
			
		||||
import {
 | 
			
		||||
  AccountsVisibility,
 | 
			
		||||
  AppTheme,
 | 
			
		||||
  AuthType,
 | 
			
		||||
  ChangeStatus,
 | 
			
		||||
  DateFormat,
 | 
			
		||||
  DefaultBase,
 | 
			
		||||
  DefaultDisplayNameConfig,
 | 
			
		||||
  DiffViewMode,
 | 
			
		||||
  EmailStrategy,
 | 
			
		||||
  InheritedBooleanInfoConfiguredValue,
 | 
			
		||||
  MergeabilityComputationBehavior,
 | 
			
		||||
  RevisionKind,
 | 
			
		||||
  SubmitType,
 | 
			
		||||
  TimeFormat,
 | 
			
		||||
} from '../constants/constants';
 | 
			
		||||
import {formatDate} from '../utils/date-util';
 | 
			
		||||
import {GetDiffCommentsOutput} from '../services/services/gr-rest-api/gr-rest-api';
 | 
			
		||||
import {AppElementChangeViewParams} from '../elements/gr-app-types';
 | 
			
		||||
import {GerritView} from '../elements/core/gr-navigation/gr-navigation';
 | 
			
		||||
import {
 | 
			
		||||
  EditRevisionInfo,
 | 
			
		||||
  ParsedChangeInfo,
 | 
			
		||||
} from '../elements/shared/gr-rest-api-interface/gr-reviewer-updates-parser';
 | 
			
		||||
 | 
			
		||||
export function dateToTimestamp(date: Date): Timestamp {
 | 
			
		||||
  const nanosecondSuffix = '.000000000';
 | 
			
		||||
  return (formatDate(date, 'YYYY-MM-DD HH:mm:ss') +
 | 
			
		||||
    nanosecondSuffix) as Timestamp;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function createCommentLink(match = 'test'): CommentLinkInfo {
 | 
			
		||||
  return {
 | 
			
		||||
    match,
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function createInheritedBoolean(value = false): InheritedBooleanInfo {
 | 
			
		||||
  return {
 | 
			
		||||
    value,
 | 
			
		||||
    configured_value: value
 | 
			
		||||
      ? InheritedBooleanInfoConfiguredValue.TRUE
 | 
			
		||||
      : InheritedBooleanInfoConfiguredValue.FALSE,
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function createMaxObjectSizeLimit(): MaxObjectSizeLimitInfo {
 | 
			
		||||
  return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function createSubmitType(
 | 
			
		||||
  value: Exclude<SubmitType, SubmitType.INHERIT> = SubmitType.MERGE_IF_NECESSARY
 | 
			
		||||
): SubmitTypeInfo {
 | 
			
		||||
  return {
 | 
			
		||||
    value,
 | 
			
		||||
    configured_value: SubmitType.INHERIT,
 | 
			
		||||
    inherited_value: value,
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function createCommentLinks(): CommentLinks {
 | 
			
		||||
  return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function createConfig(): ConfigInfo {
 | 
			
		||||
  return {
 | 
			
		||||
    private_by_default: createInheritedBoolean(),
 | 
			
		||||
    work_in_progress_by_default: createInheritedBoolean(),
 | 
			
		||||
    max_object_size_limit: createMaxObjectSizeLimit(),
 | 
			
		||||
    default_submit_type: createSubmitType(),
 | 
			
		||||
    submit_type: SubmitType.INHERIT,
 | 
			
		||||
    commentlinks: createCommentLinks(),
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function createAccountWithId(id = 5): AccountInfo {
 | 
			
		||||
  return {
 | 
			
		||||
    _account_id: id as AccountId,
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function createAccountWithEmail(email = 'test@'): AccountInfo {
 | 
			
		||||
  return {
 | 
			
		||||
    email: email as EmailAddress,
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function createAccountWithIdNameAndEmail(id = 5): AccountInfo {
 | 
			
		||||
  return {
 | 
			
		||||
    _account_id: id as AccountId,
 | 
			
		||||
    email: `user-${id}@` as EmailAddress,
 | 
			
		||||
    name: `User-${id}`,
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function createReviewers(): Reviewers {
 | 
			
		||||
  return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const TEST_PROJECT_NAME: RepoName = 'test-project' as RepoName;
 | 
			
		||||
export const TEST_BRANCH_ID: BranchName = 'test-branch' as BranchName;
 | 
			
		||||
export const TEST_CHANGE_ID: ChangeId = 'TestChangeId' as ChangeId;
 | 
			
		||||
export const TEST_CHANGE_INFO_ID: ChangeInfoId = `${TEST_PROJECT_NAME}~${TEST_BRANCH_ID}~${TEST_CHANGE_ID}` as ChangeInfoId;
 | 
			
		||||
export const TEST_SUBJECT = 'Test subject';
 | 
			
		||||
export const TEST_NUMERIC_CHANGE_ID = 42 as NumericChangeId;
 | 
			
		||||
 | 
			
		||||
export const TEST_CHANGE_CREATED = new Date(2020, 1, 1, 1, 2, 3);
 | 
			
		||||
export const TEST_CHANGE_UPDATED = new Date(2020, 10, 6, 5, 12, 34);
 | 
			
		||||
 | 
			
		||||
export function createGitPerson(name = 'Test name'): GitPersonInfo {
 | 
			
		||||
  return {
 | 
			
		||||
    name,
 | 
			
		||||
    email: `${name}@`,
 | 
			
		||||
    date: dateToTimestamp(new Date(2019, 11, 6, 14, 5, 8)),
 | 
			
		||||
    tz: 0 as TimezoneOffset,
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function createCommit(): CommitInfo {
 | 
			
		||||
  return {
 | 
			
		||||
    parents: [],
 | 
			
		||||
    author: createGitPerson(),
 | 
			
		||||
    committer: createGitPerson(),
 | 
			
		||||
    subject: 'Test commit subject',
 | 
			
		||||
    message: 'Test commit message',
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function createRevision(patchSetNum = 1): RevisionInfo {
 | 
			
		||||
  return {
 | 
			
		||||
    _number: patchSetNum as PatchSetNum,
 | 
			
		||||
    commit: createCommit(),
 | 
			
		||||
    created: dateToTimestamp(TEST_CHANGE_CREATED),
 | 
			
		||||
    kind: RevisionKind.REWORK,
 | 
			
		||||
    ref: 'refs/changes/5/6/1' as GitRef,
 | 
			
		||||
    uploader: createAccountWithId(),
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function createEditRevision(): EditRevisionInfo {
 | 
			
		||||
  return {
 | 
			
		||||
    _number: EditPatchSetNum,
 | 
			
		||||
    basePatchNum: 1 as PatchSetNum,
 | 
			
		||||
    commit: createCommit(),
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function createChangeMessage(id = 'cm_id_1'): ChangeMessageInfo {
 | 
			
		||||
  return {
 | 
			
		||||
    id: id as ChangeMessageId,
 | 
			
		||||
    date: dateToTimestamp(TEST_CHANGE_CREATED),
 | 
			
		||||
    message: `This is a message with id ${id}`,
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function createRevisions(
 | 
			
		||||
  count: number
 | 
			
		||||
): {[revisionId: string]: RevisionInfo} {
 | 
			
		||||
  const revisions: {[revisionId: string]: RevisionInfo} = {};
 | 
			
		||||
  const revisionDate = TEST_CHANGE_CREATED;
 | 
			
		||||
  const revisionIdStart = 1; // The same as getCurrentRevision
 | 
			
		||||
  for (let i = 0; i < count; i++) {
 | 
			
		||||
    const revisionId = (i + revisionIdStart).toString(16);
 | 
			
		||||
    const revision: RevisionInfo = {
 | 
			
		||||
      ...createRevision(i + 1),
 | 
			
		||||
      created: dateToTimestamp(revisionDate),
 | 
			
		||||
      ref: `refs/changes/5/6/${i + 1}` as GitRef,
 | 
			
		||||
    };
 | 
			
		||||
    revisions[revisionId] = revision;
 | 
			
		||||
    // advance 1 day
 | 
			
		||||
    revisionDate.setDate(revisionDate.getDate() + 1);
 | 
			
		||||
  }
 | 
			
		||||
  return revisions;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function getCurrentRevision(count: number): CommitId {
 | 
			
		||||
  const revisionIdStart = 1; // The same as createRevisions
 | 
			
		||||
  return (count + revisionIdStart).toString(16) as CommitId;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function createChangeMessages(count: number): ChangeMessageInfo[] {
 | 
			
		||||
  const messageIdStart = 1000;
 | 
			
		||||
  const messages: ChangeMessageInfo[] = [];
 | 
			
		||||
  const messageDate = TEST_CHANGE_CREATED;
 | 
			
		||||
  for (let i = 0; i < count; i++) {
 | 
			
		||||
    messages.push({
 | 
			
		||||
      ...createChangeMessage((i + messageIdStart).toString(16)),
 | 
			
		||||
      date: dateToTimestamp(messageDate),
 | 
			
		||||
    });
 | 
			
		||||
    messageDate.setDate(messageDate.getDate() + 1);
 | 
			
		||||
  }
 | 
			
		||||
  return messages;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function createChange(): ChangeInfo {
 | 
			
		||||
  return {
 | 
			
		||||
    id: TEST_CHANGE_INFO_ID,
 | 
			
		||||
    project: TEST_PROJECT_NAME,
 | 
			
		||||
    branch: TEST_BRANCH_ID,
 | 
			
		||||
    change_id: TEST_CHANGE_ID,
 | 
			
		||||
    subject: TEST_SUBJECT,
 | 
			
		||||
    status: ChangeStatus.NEW,
 | 
			
		||||
    created: dateToTimestamp(TEST_CHANGE_CREATED),
 | 
			
		||||
    updated: dateToTimestamp(TEST_CHANGE_UPDATED),
 | 
			
		||||
    insertions: 0,
 | 
			
		||||
    deletions: 0,
 | 
			
		||||
    _number: TEST_NUMERIC_CHANGE_ID,
 | 
			
		||||
    owner: createAccountWithId(),
 | 
			
		||||
    // This is documented as optional, but actually always set.
 | 
			
		||||
    reviewers: createReviewers(),
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function createChangeViewChange(): ChangeViewChangeInfo {
 | 
			
		||||
  return {
 | 
			
		||||
    ...createChange(),
 | 
			
		||||
    revisions: {
 | 
			
		||||
      abc: createRevision(),
 | 
			
		||||
    },
 | 
			
		||||
    current_revision: 'abc' as CommitId,
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function createParsedChange(): ParsedChangeInfo {
 | 
			
		||||
  return createChangeViewChange();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function createAccountsConfig(): AccountsConfigInfo {
 | 
			
		||||
  return {
 | 
			
		||||
    visibility: AccountsVisibility.ALL,
 | 
			
		||||
    default_display_name: DefaultDisplayNameConfig.FULL_NAME,
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function createAuth(): AuthInfo {
 | 
			
		||||
  return {
 | 
			
		||||
    auth_type: AuthType.OPENID,
 | 
			
		||||
    editable_account_fields: [],
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function createChangeConfig(): ChangeConfigInfo {
 | 
			
		||||
  return {
 | 
			
		||||
    large_change: 500,
 | 
			
		||||
    reply_label: 'Reply',
 | 
			
		||||
    reply_tooltip: 'Reply and score',
 | 
			
		||||
    // The default update_delay is 5 minutes, but we don't want to accidentally
 | 
			
		||||
    // start polling in tests
 | 
			
		||||
    update_delay: 0,
 | 
			
		||||
    mergeability_computation_behavior:
 | 
			
		||||
      MergeabilityComputationBehavior.REF_UPDATED_AND_CHANGE_REINDEX,
 | 
			
		||||
    enable_attention_set: false,
 | 
			
		||||
    enable_assignee: false,
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function createDownloadSchemes(): SchemesInfoMap {
 | 
			
		||||
  return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function createDownloadInfo(): DownloadInfo {
 | 
			
		||||
  return {
 | 
			
		||||
    schemes: createDownloadSchemes(),
 | 
			
		||||
    archives: ['tgz', 'tar'],
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function createGerritInfo(): GerritInfo {
 | 
			
		||||
  return {
 | 
			
		||||
    all_projects: 'All-Projects',
 | 
			
		||||
    all_users: 'All-Users',
 | 
			
		||||
    doc_search: false,
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function createPluginConfig(): PluginConfigInfo {
 | 
			
		||||
  return {
 | 
			
		||||
    has_avatars: false,
 | 
			
		||||
    js_resource_paths: [],
 | 
			
		||||
    html_resource_paths: [],
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function createSuggestInfo(): SuggestInfo {
 | 
			
		||||
  return {
 | 
			
		||||
    from: 0,
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function createUserConfig(): UserConfigInfo {
 | 
			
		||||
  return {
 | 
			
		||||
    anonymous_coward_name: 'Name of user not set',
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function createServerInfo(): ServerInfo {
 | 
			
		||||
  return {
 | 
			
		||||
    accounts: createAccountsConfig(),
 | 
			
		||||
    auth: createAuth(),
 | 
			
		||||
    change: createChangeConfig(),
 | 
			
		||||
    download: createDownloadInfo(),
 | 
			
		||||
    gerrit: createGerritInfo(),
 | 
			
		||||
    plugin: createPluginConfig(),
 | 
			
		||||
    suggest: createSuggestInfo(),
 | 
			
		||||
    user: createUserConfig(),
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function createGetDiffCommentsOutput(): GetDiffCommentsOutput {
 | 
			
		||||
  return {
 | 
			
		||||
    baseComments: [],
 | 
			
		||||
    comments: [],
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function createMergeable(): MergeableInfo {
 | 
			
		||||
  return {
 | 
			
		||||
    submit_type: SubmitType.MERGE_IF_NECESSARY,
 | 
			
		||||
    mergeable: false,
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function createPreferences(): PreferencesInfo {
 | 
			
		||||
  return {
 | 
			
		||||
    changes_per_page: 10,
 | 
			
		||||
    theme: AppTheme.LIGHT,
 | 
			
		||||
    date_format: DateFormat.ISO,
 | 
			
		||||
    time_format: TimeFormat.HHMM_24,
 | 
			
		||||
    diff_view: DiffViewMode.SIDE_BY_SIDE,
 | 
			
		||||
    my: [],
 | 
			
		||||
    change_table: [],
 | 
			
		||||
    email_strategy: EmailStrategy.ENABLED,
 | 
			
		||||
    default_base_for_merges: DefaultBase.AUTO_MERGE,
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function createApproval(): ApprovalInfo {
 | 
			
		||||
  return createAccountWithId();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function createAppElementChangeViewParams(): AppElementChangeViewParams {
 | 
			
		||||
  return {
 | 
			
		||||
    view: GerritView.CHANGE,
 | 
			
		||||
    changeNum: TEST_NUMERIC_CHANGE_ID,
 | 
			
		||||
    project: TEST_PROJECT_NAME,
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
@@ -14,6 +14,15 @@
 | 
			
		||||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
import {GerritNav} from '../elements/core/gr-navigation/gr-navigation.js';
 | 
			
		||||
import {GerritNav} from '../elements/core/gr-navigation/gr-navigation';
 | 
			
		||||
 | 
			
		||||
GerritNav.setup(url => { /* noop */ }, params => '', () => []);
 | 
			
		||||
GerritNav.setup(
 | 
			
		||||
  () => {
 | 
			
		||||
    /* noop */
 | 
			
		||||
  },
 | 
			
		||||
  () => '',
 | 
			
		||||
  () => [],
 | 
			
		||||
  () => {
 | 
			
		||||
    return {};
 | 
			
		||||
  }
 | 
			
		||||
);
 | 
			
		||||
@@ -14,48 +14,62 @@
 | 
			
		||||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
import '../types/globals';
 | 
			
		||||
import {_testOnly_resetPluginLoader} from '../elements/shared/gr-js-api-interface/gr-plugin-loader';
 | 
			
		||||
import {testOnly_resetInternalState} from '../elements/shared/gr-js-api-interface/gr-api-utils';
 | 
			
		||||
import {_testOnly_resetEndpoints} from '../elements/shared/gr-js-api-interface/gr-plugin-endpoints';
 | 
			
		||||
import {
 | 
			
		||||
  _testOnly_getShortcutManagerInstance,
 | 
			
		||||
  Shortcut,
 | 
			
		||||
} from '../mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin';
 | 
			
		||||
 | 
			
		||||
import {_testOnly_resetPluginLoader} from '../elements/shared/gr-js-api-interface/gr-plugin-loader.js';
 | 
			
		||||
import {testOnly_resetInternalState} from '../elements/shared/gr-js-api-interface/gr-api-utils.js';
 | 
			
		||||
import {_testOnly_resetEndpoints} from '../elements/shared/gr-js-api-interface/gr-plugin-endpoints.js';
 | 
			
		||||
import {_testOnly_getShortcutManagerInstance} from '../mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin.js';
 | 
			
		||||
export interface MockPromise extends Promise<unknown> {
 | 
			
		||||
  resolve: (value?: unknown) => void;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const mockPromise = () => {
 | 
			
		||||
  let res;
 | 
			
		||||
  const promise = new Promise(resolve => {
 | 
			
		||||
  let res: (value?: unknown) => void;
 | 
			
		||||
  const promise: MockPromise = new Promise(resolve => {
 | 
			
		||||
    res = resolve;
 | 
			
		||||
  });
 | 
			
		||||
  promise.resolve = res;
 | 
			
		||||
  }) as MockPromise;
 | 
			
		||||
  promise.resolve = res!;
 | 
			
		||||
  return promise;
 | 
			
		||||
};
 | 
			
		||||
export const isHidden = el => getComputedStyle(el).display === 'none';
 | 
			
		||||
export const isHidden = (el: Element) =>
 | 
			
		||||
  getComputedStyle(el).display === 'none';
 | 
			
		||||
 | 
			
		||||
// Some tests/elements can define its own binding. We want to restore bindings
 | 
			
		||||
// at the end of the test. The TestKeyboardShortcutBinder store bindings in
 | 
			
		||||
// stack, so it is possible to override bindings in nested suites.
 | 
			
		||||
export class TestKeyboardShortcutBinder {
 | 
			
		||||
  private static stack: TestKeyboardShortcutBinder[] = [];
 | 
			
		||||
 | 
			
		||||
  static push() {
 | 
			
		||||
    if (!this.stack) {
 | 
			
		||||
      this.stack = [];
 | 
			
		||||
    }
 | 
			
		||||
    const testBinder = new TestKeyboardShortcutBinder();
 | 
			
		||||
    this.stack.push(testBinder);
 | 
			
		||||
    return _testOnly_getShortcutManagerInstance();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static pop() {
 | 
			
		||||
    this.stack.pop()._restoreShortcuts();
 | 
			
		||||
    const item = this.stack.pop();
 | 
			
		||||
    if (!item) {
 | 
			
		||||
      throw new Error('stack is empty');
 | 
			
		||||
    }
 | 
			
		||||
    item._restoreShortcuts();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private readonly originalBinding: Map<Shortcut, string[]>;
 | 
			
		||||
 | 
			
		||||
  constructor() {
 | 
			
		||||
    this._originalBinding = new Map(
 | 
			
		||||
        _testOnly_getShortcutManagerInstance().bindings);
 | 
			
		||||
    this.originalBinding = new Map(
 | 
			
		||||
      _testOnly_getShortcutManagerInstance()._testOnly_getBindings()
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _restoreShortcuts() {
 | 
			
		||||
    const bindings = _testOnly_getShortcutManagerInstance().bindings;
 | 
			
		||||
    const bindings = _testOnly_getShortcutManagerInstance()._testOnly_getBindings();
 | 
			
		||||
    bindings.clear();
 | 
			
		||||
    this._originalBinding.forEach((value, key) => {
 | 
			
		||||
    this.originalBinding.forEach((value, key) => {
 | 
			
		||||
      bindings.set(key, value);
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
@@ -70,9 +84,15 @@ export const resetPlugins = () => {
 | 
			
		||||
  pl.loadPlugins([]);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const cleanups = [];
 | 
			
		||||
export type CleanupCallback = () => void;
 | 
			
		||||
 | 
			
		||||
function registerTestCleanup(cleanupCallback) {
 | 
			
		||||
const cleanups: CleanupCallback[] = [];
 | 
			
		||||
 | 
			
		||||
export function getCleanupsCount() {
 | 
			
		||||
  return cleanups.length;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function registerTestCleanup(cleanupCallback: CleanupCallback) {
 | 
			
		||||
  cleanups.push(cleanupCallback);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -81,48 +101,10 @@ export function cleanupTestUtils() {
 | 
			
		||||
  cleanups.splice(0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function stubBaseUrl(newUrl) {
 | 
			
		||||
export function stubBaseUrl(newUrl: string) {
 | 
			
		||||
  const originalCanonicalPath = window.CANONICAL_PATH;
 | 
			
		||||
  window.CANONICAL_PATH = newUrl;
 | 
			
		||||
  registerTestCleanup(() => window.CANONICAL_PATH = originalCanonicalPath);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function generateChange(options) {
 | 
			
		||||
  const change = {
 | 
			
		||||
    _number: 42,
 | 
			
		||||
    project: 'testRepo',
 | 
			
		||||
  };
 | 
			
		||||
  const revisionIdStart = 1;
 | 
			
		||||
  const messageIdStart = 1000;
 | 
			
		||||
  // We want to distinguish between empty arrays/objects and undefined
 | 
			
		||||
  // If an option is not set - the appropriate property is not set
 | 
			
		||||
  // If an options is set - the property always set
 | 
			
		||||
  if (options && typeof options.revisionsCount !== 'undefined') {
 | 
			
		||||
    const revisions = {};
 | 
			
		||||
    for (let i = 0; i < options.revisionsCount; i++) {
 | 
			
		||||
      const revisionId = (i + revisionIdStart).toString(16);
 | 
			
		||||
      revisions[revisionId] = {
 | 
			
		||||
        _number: i+1,
 | 
			
		||||
        commit: {parents: []},
 | 
			
		||||
      };
 | 
			
		||||
    }
 | 
			
		||||
    change.revisions = revisions;
 | 
			
		||||
  }
 | 
			
		||||
  if (options && typeof options.messagesCount !== 'undefined') {
 | 
			
		||||
    const messages = [];
 | 
			
		||||
    for (let i = 0; i < options.messagesCount; i++) {
 | 
			
		||||
      messages.push({
 | 
			
		||||
        id: (i + messageIdStart).toString(16),
 | 
			
		||||
        date: new Date(2020, 1, 1),
 | 
			
		||||
        message: `This is a message N${i + 1}`,
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
    change.messages = messages;
 | 
			
		||||
  }
 | 
			
		||||
  if (options && options.status) {
 | 
			
		||||
    change.status = options.status;
 | 
			
		||||
  }
 | 
			
		||||
  return change;
 | 
			
		||||
  registerTestCleanup(() => (window.CANONICAL_PATH = originalCanonicalPath));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -134,7 +116,8 @@ export function generateChange(options) {
 | 
			
		||||
export function createIronOverlayBackdropStyleEl() {
 | 
			
		||||
  const ironOverlayBackdropStyleEl = document.createElement('style');
 | 
			
		||||
  document.head.appendChild(ironOverlayBackdropStyleEl);
 | 
			
		||||
  ironOverlayBackdropStyleEl.sheet.insertRule(
 | 
			
		||||
      'body { --iron-overlay-backdrop-opacity: 0; }');
 | 
			
		||||
  ironOverlayBackdropStyleEl.sheet!.insertRule(
 | 
			
		||||
    'body { --iron-overlay-backdrop-opacity: 0; }'
 | 
			
		||||
  );
 | 
			
		||||
  return ironOverlayBackdropStyleEl;
 | 
			
		||||
}
 | 
			
		||||
@@ -37,14 +37,18 @@
 | 
			
		||||
    /* Advanced Options */
 | 
			
		||||
    "forceConsistentCasingInFileNames": true, /* Disallow inconsistently-cased references to the same file. */
 | 
			
		||||
    "incremental": true,
 | 
			
		||||
    "experimentalDecorators": true
 | 
			
		||||
    "experimentalDecorators": true,
 | 
			
		||||
 | 
			
		||||
    "allowUmdGlobalAccess": true
 | 
			
		||||
  },
 | 
			
		||||
  // With the * pattern (without an extension), only supported files
 | 
			
		||||
  // are included. The supported files are .ts, .tsx, .d.ts.
 | 
			
		||||
  // If allowJs is set to true, .js and .jsx files are included as well.
 | 
			
		||||
  // Note: gerrit doesn't have .tsx and .jsx files
 | 
			
		||||
  "include": [
 | 
			
		||||
    // This items below must be in sync with the src_dirs list in the BUILD file
 | 
			
		||||
    // Items below must be in sync with the src_dirs list in the BUILD file
 | 
			
		||||
    // Also items must be in sync with tsconfig_bazel.json, tsconfig_bazel_test.json
 | 
			
		||||
    // (include and exclude arrays are overriden when extends)
 | 
			
		||||
    "constants/**/*",
 | 
			
		||||
    "elements/**/*",
 | 
			
		||||
    "embed/**/*",
 | 
			
		||||
@@ -56,7 +60,6 @@
 | 
			
		||||
    "styles/**/*",
 | 
			
		||||
    "types/**/*",
 | 
			
		||||
    "utils/**/*",
 | 
			
		||||
    // Directory for test utils (not included in src_dirs in the BUILD file)
 | 
			
		||||
    "test/**/*"
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										29
									
								
								polygerrit-ui/app/tsconfig_bazel.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								polygerrit-ui/app/tsconfig_bazel.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,29 @@
 | 
			
		||||
{
 | 
			
		||||
  "extends": "./tsconfig.json",
 | 
			
		||||
  "compilerOptions": {
 | 
			
		||||
    "typeRoots": [
 | 
			
		||||
      "../../external/ui_npm/node_modules/@types",
 | 
			
		||||
      "../../external/ui_dev_npm/node_modules/@types"
 | 
			
		||||
    ]
 | 
			
		||||
  },
 | 
			
		||||
  "include": [
 | 
			
		||||
    // Items below must be in sync with the src_dirs list in the BUILD file
 | 
			
		||||
    // Also items must be in sync with tsconfig.json, tsconfig_bazel_test.json
 | 
			
		||||
    // (include and exclude arrays are overriden when extends)
 | 
			
		||||
    "constants/**/*",
 | 
			
		||||
    "elements/**/*",
 | 
			
		||||
    "embed/**/*",
 | 
			
		||||
    "gr-diff/**/*",
 | 
			
		||||
    "mixins/**/*",
 | 
			
		||||
    "samples/**/*",
 | 
			
		||||
    "scripts/**/*",
 | 
			
		||||
    "services/**/*",
 | 
			
		||||
    "styles/**/*",
 | 
			
		||||
    "types/**/*",
 | 
			
		||||
    "utils/**/*"
 | 
			
		||||
  ],
 | 
			
		||||
  "exclude": [
 | 
			
		||||
    "**/*_test.ts",
 | 
			
		||||
    "**/*_test.js"
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										32
									
								
								polygerrit-ui/app/tsconfig_bazel_test.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								polygerrit-ui/app/tsconfig_bazel_test.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,32 @@
 | 
			
		||||
{
 | 
			
		||||
  "extends": "./tsconfig_bazel.json",
 | 
			
		||||
  "compilerOptions": {
 | 
			
		||||
    "typeRoots": [
 | 
			
		||||
      "./test/@types",
 | 
			
		||||
      "../../external/ui_dev_npm/node_modules/@polymer/iron-test-helpers",
 | 
			
		||||
      "../../external/ui_npm/node_modules/@types",
 | 
			
		||||
      "../../external/ui_dev_npm/node_modules/@types"
 | 
			
		||||
    ],
 | 
			
		||||
    "paths": {
 | 
			
		||||
      "@polymer/iron-test-helpers/*": ["../../ui_dev_npm/node_modules/@polymer/iron-test-helpers/*"]
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  "include": [
 | 
			
		||||
    // Items below must be in sync with the src_dirs list in the BUILD file
 | 
			
		||||
    // Also items must be in sync with tsconfig.json, tsconfig_test.json
 | 
			
		||||
    // (include and exclude arrays are overriden when extends)
 | 
			
		||||
    "constants/**/*",
 | 
			
		||||
    "elements/**/*",
 | 
			
		||||
    "embed/**/*",
 | 
			
		||||
    "gr-diff/**/*",
 | 
			
		||||
    "mixins/**/*",
 | 
			
		||||
    "samples/**/*",
 | 
			
		||||
    "scripts/**/*",
 | 
			
		||||
    "services/**/*",
 | 
			
		||||
    "styles/**/*",
 | 
			
		||||
    "types/**/*",
 | 
			
		||||
    "utils/**/*",
 | 
			
		||||
    "test/**/*"
 | 
			
		||||
  ],
 | 
			
		||||
  "exclude": []
 | 
			
		||||
}
 | 
			
		||||
@@ -45,6 +45,8 @@ import {
 | 
			
		||||
  EmailFormat,
 | 
			
		||||
  AuthType,
 | 
			
		||||
  MergeStrategy,
 | 
			
		||||
  EditableAccountField,
 | 
			
		||||
  MergeabilityComputationBehavior,
 | 
			
		||||
} from '../constants/constants';
 | 
			
		||||
import {PolymerDeepPropertyChange} from '@polymer/polymer/interfaces';
 | 
			
		||||
 | 
			
		||||
@@ -248,7 +250,7 @@ export interface ChangeInfo {
 | 
			
		||||
  created: Timestamp;
 | 
			
		||||
  updated: Timestamp;
 | 
			
		||||
  submitted?: Timestamp;
 | 
			
		||||
  submitter: AccountInfo;
 | 
			
		||||
  submitter?: AccountInfo;
 | 
			
		||||
  starred?: boolean; // not set if false
 | 
			
		||||
  stars?: StarLabel[];
 | 
			
		||||
  reviewed?: boolean; // not set if false
 | 
			
		||||
@@ -540,7 +542,7 @@ export interface RevisionInfo {
 | 
			
		||||
  fetch?: {[protocol: string]: FetchInfo};
 | 
			
		||||
  commit?: CommitInfo;
 | 
			
		||||
  files?: {[filename: string]: FileInfo};
 | 
			
		||||
  actions?: ActionInfo[];
 | 
			
		||||
  actions?: ActionNameToActionInfoMap;
 | 
			
		||||
  reviewed?: boolean;
 | 
			
		||||
  commit_with_footers?: boolean;
 | 
			
		||||
  push_certificate?: PushCertificateInfo;
 | 
			
		||||
@@ -736,7 +738,7 @@ export interface VotingRangeInfo {
 | 
			
		||||
/**
 | 
			
		||||
 * The AccountsConfigInfo entity contains information about Gerrit configuration
 | 
			
		||||
 * from the accounts section.
 | 
			
		||||
 * https://gerrit-review.googlesource.com/Documentation/rest-api-config.html
 | 
			
		||||
 * https://gerrit-review.googlesource.com/Documentation/rest-api-config.html#accounts-config-info
 | 
			
		||||
 */
 | 
			
		||||
export interface AccountsConfigInfo {
 | 
			
		||||
  visibility: string;
 | 
			
		||||
@@ -750,9 +752,9 @@ export interface AccountsConfigInfo {
 | 
			
		||||
 */
 | 
			
		||||
export interface AuthInfo {
 | 
			
		||||
  auth_type: AuthType; // docs incorrectly names it 'type'
 | 
			
		||||
  use_contributor_agreements: boolean;
 | 
			
		||||
  use_contributor_agreements?: boolean;
 | 
			
		||||
  contributor_agreements?: ContributorAgreementInfo;
 | 
			
		||||
  editable_account_fields: string;
 | 
			
		||||
  editable_account_fields: EditableAccountField[];
 | 
			
		||||
  login_url?: string;
 | 
			
		||||
  login_text?: string;
 | 
			
		||||
  switch_account_url?: string;
 | 
			
		||||
@@ -799,17 +801,17 @@ export type CapabilityInfoMap = {[id: string]: CapabilityInfo};
 | 
			
		||||
/**
 | 
			
		||||
 * The ChangeConfigInfo entity contains information about Gerrit configuration
 | 
			
		||||
 * from the change section.
 | 
			
		||||
 * https://gerrit-review.googlesource.com/Documentation/rest-api-config.html
 | 
			
		||||
 * https://gerrit-review.googlesource.com/Documentation/rest-api-config.html#change-config-info
 | 
			
		||||
 */
 | 
			
		||||
export interface ChangeConfigInfo {
 | 
			
		||||
  allow_blame: boolean;
 | 
			
		||||
  large_change: string;
 | 
			
		||||
  allow_blame?: boolean;
 | 
			
		||||
  large_change: number;
 | 
			
		||||
  reply_label: string;
 | 
			
		||||
  reply_tooltip: string;
 | 
			
		||||
  update_delay: number;
 | 
			
		||||
  submit_whole_topic: boolean;
 | 
			
		||||
  disable_private_changes: boolean;
 | 
			
		||||
  mergeability_computation_behavior: string;
 | 
			
		||||
  submit_whole_topic?: boolean;
 | 
			
		||||
  disable_private_changes?: boolean;
 | 
			
		||||
  mergeability_computation_behavior: MergeabilityComputationBehavior;
 | 
			
		||||
  enable_attention_set: boolean;
 | 
			
		||||
  enable_assignee: boolean;
 | 
			
		||||
}
 | 
			
		||||
@@ -817,10 +819,10 @@ export interface ChangeConfigInfo {
 | 
			
		||||
/**
 | 
			
		||||
 * The ChangeIndexConfigInfo entity contains information about Gerrit
 | 
			
		||||
 * configuration from the index.change section.
 | 
			
		||||
 * https://gerrit-review.googlesource.com/Documentation/rest-api-config.html
 | 
			
		||||
 * https://gerrit-review.googlesource.com/Documentation/rest-api-config.html#change-index-config-info
 | 
			
		||||
 */
 | 
			
		||||
export interface ChangeIndexConfigInfo {
 | 
			
		||||
  index_mergeable: boolean;
 | 
			
		||||
  index_mergeable?: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -906,11 +908,11 @@ export type SchemesInfoMap = {[name: string]: DownloadSchemeInfo};
 | 
			
		||||
/**
 | 
			
		||||
 * The DownloadInfo entity contains information about supported download
 | 
			
		||||
 * options.
 | 
			
		||||
 * https://gerrit-review.googlesource.com/Documentation/rest-api-config.html
 | 
			
		||||
 * https://gerrit-review.googlesource.com/Documentation/rest-api-config.html#download-info
 | 
			
		||||
 */
 | 
			
		||||
export interface DownloadInfo {
 | 
			
		||||
  schemes: SchemesInfoMap;
 | 
			
		||||
  archives: string;
 | 
			
		||||
  archives: string[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type CloneCommandMap = {[name: string]: string};
 | 
			
		||||
@@ -954,9 +956,9 @@ export interface EntriesInfo {
 | 
			
		||||
export interface GerritInfo {
 | 
			
		||||
  all_projects: string; // Doc contains incorrect name
 | 
			
		||||
  all_users: string; // Doc contains incorrect name
 | 
			
		||||
  doc_search: string;
 | 
			
		||||
  doc_search: boolean;
 | 
			
		||||
  doc_url?: string;
 | 
			
		||||
  edit_gpg_keys: boolean;
 | 
			
		||||
  edit_gpg_keys?: boolean;
 | 
			
		||||
  report_bug_url?: string;
 | 
			
		||||
  // The following property is missed in doc
 | 
			
		||||
  primary_weblink_name?: string;
 | 
			
		||||
@@ -965,7 +967,7 @@ export interface GerritInfo {
 | 
			
		||||
/**
 | 
			
		||||
 * The IndexConfigInfo entity contains information about Gerrit configuration
 | 
			
		||||
 * from the index section.
 | 
			
		||||
 * https://gerrit-review.googlesource.com/Documentation/rest-api-config.html
 | 
			
		||||
 * https://gerrit-review.googlesource.com/Documentation/rest-api-config.html#index-config-info
 | 
			
		||||
 */
 | 
			
		||||
export interface IndexConfigInfo {
 | 
			
		||||
  change: ChangeIndexConfigInfo;
 | 
			
		||||
@@ -1022,10 +1024,11 @@ export interface MemSummaryInfo {
 | 
			
		||||
/**
 | 
			
		||||
 * The PluginConfigInfo entity contains information about Gerrit extensions by
 | 
			
		||||
 * plugins.
 | 
			
		||||
 * https://gerrit-review.googlesource.com/Documentation/rest-api-config.html
 | 
			
		||||
 * https://gerrit-review.googlesource.com/Documentation/rest-api-config.html#plugin-config-info
 | 
			
		||||
 */
 | 
			
		||||
export interface PluginConfigInfo {
 | 
			
		||||
  has_avatars: boolean;
 | 
			
		||||
  // The following 2 properies exists in Java class, but don't mention in docs
 | 
			
		||||
  js_resource_paths: string[];
 | 
			
		||||
  html_resource_paths: string[];
 | 
			
		||||
}
 | 
			
		||||
@@ -1050,7 +1053,8 @@ export interface ServerInfo {
 | 
			
		||||
  change: ChangeConfigInfo;
 | 
			
		||||
  download: DownloadInfo;
 | 
			
		||||
  gerrit: GerritInfo;
 | 
			
		||||
  index: IndexConfigInfo;
 | 
			
		||||
  // docs mentions index property, but it doesn't exists in Java class
 | 
			
		||||
  // index: IndexConfigInfo;
 | 
			
		||||
  note_db_enabled?: boolean;
 | 
			
		||||
  plugin: PluginConfigInfo;
 | 
			
		||||
  receive?: ReceiveInfo;
 | 
			
		||||
@@ -1074,7 +1078,7 @@ export type SshdInfo = {};
 | 
			
		||||
 * https://gerrit-review.googlesource.com/Documentation/rest-api-config.html#suggest-info
 | 
			
		||||
 */
 | 
			
		||||
export interface SuggestInfo {
 | 
			
		||||
  from: string;
 | 
			
		||||
  from: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -1149,7 +1153,7 @@ export interface TopMenuItemInfo {
 | 
			
		||||
/**
 | 
			
		||||
 * The UserConfigInfo entity contains information about Gerrit configuration
 | 
			
		||||
 * from the user section.
 | 
			
		||||
 * https://gerrit-review.googlesource.com/Documentation/rest-api-config.html
 | 
			
		||||
 * https://gerrit-review.googlesource.com/Documentation/rest-api-config.html#user-config-info
 | 
			
		||||
 */
 | 
			
		||||
export interface UserConfigInfo {
 | 
			
		||||
  anonymous_coward_name: string;
 | 
			
		||||
@@ -1373,7 +1377,7 @@ export interface InheritedBooleanInfo {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The MaxObjectSizeLimitInfo entity contains information about themax object
 | 
			
		||||
 * The MaxObjectSizeLimitInfo entity contains information about the max object
 | 
			
		||||
 * size limit of a project.
 | 
			
		||||
 * https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#max-object-size-limit-info
 | 
			
		||||
 */
 | 
			
		||||
@@ -2040,8 +2044,8 @@ export interface EditInfo {
 | 
			
		||||
  base_patch_set_number: PatchSetNum;
 | 
			
		||||
  base_revision: string;
 | 
			
		||||
  ref: GitRef;
 | 
			
		||||
  fetch: ProtocolToFetchInfoMap;
 | 
			
		||||
  files: FileNameToFileInfoMap;
 | 
			
		||||
  fetch?: ProtocolToFetchInfoMap;
 | 
			
		||||
  files?: FileNameToFileInfoMap;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type ProtocolToFetchInfoMap = {[protocol: string]: FetchInfo};
 | 
			
		||||
 
 | 
			
		||||
@@ -142,6 +142,18 @@ declare global {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface ShowAlertEventDetail {
 | 
			
		||||
  message: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type ShowAlertEvent = CustomEvent<ShowAlertEventDetail>;
 | 
			
		||||
 | 
			
		||||
declare global {
 | 
			
		||||
  interface HTMLElementEventMap {
 | 
			
		||||
    'show-alert': ShowAlertEvent;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Keyboard events emitted from polymer elements.
 | 
			
		||||
 */
 | 
			
		||||
 
 | 
			
		||||
@@ -75,50 +75,50 @@ declare global {
 | 
			
		||||
    // TODO(TS): should clean up those and removing them may break certain plugin behaviors
 | 
			
		||||
    // TODO(TS): as @brohlfs suggested, to avoid importing anything from elements/ to types/
 | 
			
		||||
    // use any for them for now
 | 
			
		||||
    GrDisplayNameUtils: any;
 | 
			
		||||
    GrAnnotation: any;
 | 
			
		||||
    GrAttributeHelper: any;
 | 
			
		||||
    GrDiffLine: any;
 | 
			
		||||
    GrDiffLineType: any;
 | 
			
		||||
    GrDiffGroup: any;
 | 
			
		||||
    GrDiffGroupType: any;
 | 
			
		||||
    GrDiffBuilder: any;
 | 
			
		||||
    GrDiffBuilderSideBySide: any;
 | 
			
		||||
    GrDiffBuilderImage: any;
 | 
			
		||||
    GrDiffBuilderUnified: any;
 | 
			
		||||
    GrDiffBuilderBinary: any;
 | 
			
		||||
    GrChangeActionsInterface: any;
 | 
			
		||||
    GrChangeReplyInterface: any;
 | 
			
		||||
    GrEditConstants: any;
 | 
			
		||||
    GrDomHooksManager: any;
 | 
			
		||||
    GrDomHook: any;
 | 
			
		||||
    GrEtagDecorator: any;
 | 
			
		||||
    GrThemeApi: any;
 | 
			
		||||
    SiteBasedCache: any;
 | 
			
		||||
    FetchPromisesCache: any;
 | 
			
		||||
    GrRestApiHelper: any;
 | 
			
		||||
    GrLinkTextParser: any;
 | 
			
		||||
    GrPluginEndpoints: any;
 | 
			
		||||
    GrReviewerUpdatesParser: any;
 | 
			
		||||
    GrPopupInterface: any;
 | 
			
		||||
    GrCountStringFormatter: any;
 | 
			
		||||
    GrReviewerSuggestionsProvider: any;
 | 
			
		||||
    util: any;
 | 
			
		||||
    Auth: any;
 | 
			
		||||
    EventEmitter: any;
 | 
			
		||||
    GrAdminApi: any;
 | 
			
		||||
    GrAnnotationActionsContext: any;
 | 
			
		||||
    GrAnnotationActionsInterface: any;
 | 
			
		||||
    GrChangeMetadataApi: any;
 | 
			
		||||
    GrEmailSuggestionsProvider: any;
 | 
			
		||||
    GrGroupSuggestionsProvider: any;
 | 
			
		||||
    GrEventHelper: any;
 | 
			
		||||
    GrPluginRestApi: any;
 | 
			
		||||
    GrRepoApi: any;
 | 
			
		||||
    GrSettingsApi: any;
 | 
			
		||||
    GrStylesApi: any;
 | 
			
		||||
    PluginLoader: any;
 | 
			
		||||
    GrPluginActionContext: any;
 | 
			
		||||
    GrDisplayNameUtils: unknown;
 | 
			
		||||
    GrAnnotation: unknown;
 | 
			
		||||
    GrAttributeHelper: unknown;
 | 
			
		||||
    GrDiffLine: unknown;
 | 
			
		||||
    GrDiffLineType: unknown;
 | 
			
		||||
    GrDiffGroup: unknown;
 | 
			
		||||
    GrDiffGroupType: unknown;
 | 
			
		||||
    GrDiffBuilder: unknown;
 | 
			
		||||
    GrDiffBuilderSideBySide: unknown;
 | 
			
		||||
    GrDiffBuilderImage: unknown;
 | 
			
		||||
    GrDiffBuilderUnified: unknown;
 | 
			
		||||
    GrDiffBuilderBinary: unknown;
 | 
			
		||||
    GrChangeActionsInterface: unknown;
 | 
			
		||||
    GrChangeReplyInterface: unknown;
 | 
			
		||||
    GrEditConstants: unknown;
 | 
			
		||||
    GrDomHooksManager: unknown;
 | 
			
		||||
    GrDomHook: unknown;
 | 
			
		||||
    GrEtagDecorator: unknown;
 | 
			
		||||
    GrThemeApi: unknown;
 | 
			
		||||
    SiteBasedCache: unknown;
 | 
			
		||||
    FetchPromisesCache: unknown;
 | 
			
		||||
    GrRestApiHelper: unknown;
 | 
			
		||||
    GrLinkTextParser: unknown;
 | 
			
		||||
    GrPluginEndpoints: unknown;
 | 
			
		||||
    GrReviewerUpdatesParser: unknown;
 | 
			
		||||
    GrPopupInterface: unknown;
 | 
			
		||||
    GrCountStringFormatter: unknown;
 | 
			
		||||
    GrReviewerSuggestionsProvider: unknown;
 | 
			
		||||
    util: unknown;
 | 
			
		||||
    Auth: unknown;
 | 
			
		||||
    EventEmitter: unknown;
 | 
			
		||||
    GrAdminApi: unknown;
 | 
			
		||||
    GrAnnotationActionsContext: unknown;
 | 
			
		||||
    GrAnnotationActionsInterface: unknown;
 | 
			
		||||
    GrChangeMetadataApi: unknown;
 | 
			
		||||
    GrEmailSuggestionsProvider: unknown;
 | 
			
		||||
    GrGroupSuggestionsProvider: unknown;
 | 
			
		||||
    GrEventHelper: unknown;
 | 
			
		||||
    GrPluginRestApi: unknown;
 | 
			
		||||
    GrRepoApi: unknown;
 | 
			
		||||
    GrSettingsApi: unknown;
 | 
			
		||||
    GrStylesApi: unknown;
 | 
			
		||||
    PluginLoader: unknown;
 | 
			
		||||
    GrPluginActionContext: unknown;
 | 
			
		||||
    _apiUtils: {};
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -138,4 +138,9 @@ declare global {
 | 
			
		||||
    // TODO(TS): replace with composedPath if possible
 | 
			
		||||
    readonly path: EventTarget[];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  interface Error {
 | 
			
		||||
    lineNumber?: number; // non-standard property
 | 
			
		||||
    columnNumber?: number; // non-standard property
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -15,28 +15,37 @@
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import '../test/common-test-setup-karma.js';
 | 
			
		||||
import {toSortedPermissionsArray} from './access-util.js';
 | 
			
		||||
import '../test/common-test-setup-karma';
 | 
			
		||||
import {toSortedPermissionsArray} from './access-util';
 | 
			
		||||
 | 
			
		||||
suite('access-util tests', () => {
 | 
			
		||||
  test('toSortedPermissionsArray', () => {
 | 
			
		||||
    const rules = {
 | 
			
		||||
      'global:Project-Owners': {
 | 
			
		||||
        action: 'ALLOW', force: false,
 | 
			
		||||
        action: 'ALLOW',
 | 
			
		||||
        force: false,
 | 
			
		||||
      },
 | 
			
		||||
      '4c97682e6ce6b7247f3381b6f1789356666de7f': {
 | 
			
		||||
        action: 'ALLOW', force: false,
 | 
			
		||||
        action: 'ALLOW',
 | 
			
		||||
        force: false,
 | 
			
		||||
      },
 | 
			
		||||
    };
 | 
			
		||||
    const expectedResult = [
 | 
			
		||||
      {id: '4c97682e6ce6b7247f3381b6f1789356666de7f', value: {
 | 
			
		||||
        action: 'ALLOW', force: false,
 | 
			
		||||
      }},
 | 
			
		||||
      {id: 'global:Project-Owners', value: {
 | 
			
		||||
        action: 'ALLOW', force: false,
 | 
			
		||||
      }},
 | 
			
		||||
      {
 | 
			
		||||
        id: '4c97682e6ce6b7247f3381b6f1789356666de7f',
 | 
			
		||||
        value: {
 | 
			
		||||
          action: 'ALLOW',
 | 
			
		||||
          force: false,
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        id: 'global:Project-Owners',
 | 
			
		||||
        value: {
 | 
			
		||||
          action: 'ALLOW',
 | 
			
		||||
          force: false,
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
    ];
 | 
			
		||||
    assert.deepEqual(toSortedPermissionsArray(rules), expectedResult);
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
@@ -33,7 +33,8 @@ export function hasOwnProperty(obj: any, prop: PropertyKey) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TODO(TS): move to common types once we have type utils
 | 
			
		||||
// tslint:disable-next-line:no-any Required for constructor signature.
 | 
			
		||||
//  Required for constructor signature.
 | 
			
		||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
 | 
			
		||||
export type Constructor<T> = new (...args: any[]) => T;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 
 | 
			
		||||
@@ -141,6 +141,7 @@ export function formatDate(date: Date, format: string) {
 | 
			
		||||
  if (format.includes('ss')) {
 | 
			
		||||
    options.second = '2-digit';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  let locale = 'en-US';
 | 
			
		||||
  // Workaround for Chrome 80, en-US is using h24 (midnight is 24:00),
 | 
			
		||||
  // en-GB is using h23 (midnight is 00:00)
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,10 @@ import {
 | 
			
		||||
  ParentPatchSetNum,
 | 
			
		||||
} from '../types/common';
 | 
			
		||||
import {RestApiService} from '../services/services/gr-rest-api/gr-rest-api';
 | 
			
		||||
import {ParsedChangeInfo} from '../elements/shared/gr-rest-api-interface/gr-reviewer-updates-parser';
 | 
			
		||||
import {
 | 
			
		||||
  EditRevisionInfo,
 | 
			
		||||
  ParsedChangeInfo,
 | 
			
		||||
} from '../elements/shared/gr-rest-api-interface/gr-reviewer-updates-parser';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @license
 | 
			
		||||
@@ -49,10 +52,6 @@ export interface PatchSet {
 | 
			
		||||
  wip?: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface RevisionWithSha extends RevisionInfo {
 | 
			
		||||
  sha: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface PatchRange {
 | 
			
		||||
  patchNum?: PatchSetNum;
 | 
			
		||||
  basePatchNum?: PatchSetNum;
 | 
			
		||||
@@ -128,7 +127,9 @@ export function getRevisionByPatchNum(
 | 
			
		||||
 *     doesn't exist.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
export function findEditParentRevision(revisions: RevisionInfo[]) {
 | 
			
		||||
export function findEditParentRevision(
 | 
			
		||||
  revisions: Array<RevisionInfo | EditRevisionInfo>
 | 
			
		||||
) {
 | 
			
		||||
  const editInfo = revisions.find(info => info._number === EditPatchSetNum);
 | 
			
		||||
 | 
			
		||||
  if (!editInfo) {
 | 
			
		||||
@@ -144,7 +145,9 @@ export function findEditParentRevision(revisions: RevisionInfo[]) {
 | 
			
		||||
 * @return Change edit patch set number or -1.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
export function findEditParentPatchNum(revisions: RevisionInfo[]) {
 | 
			
		||||
export function findEditParentPatchNum(
 | 
			
		||||
  revisions: Array<RevisionInfo | EditRevisionInfo>
 | 
			
		||||
) {
 | 
			
		||||
  const revisionInfo = findEditParentRevision(revisions);
 | 
			
		||||
  // finding parent of 'edit' patchset, hence revisionInfo._number cannot be
 | 
			
		||||
  // 'edit' and must be a number
 | 
			
		||||
@@ -162,7 +165,9 @@ export function findEditParentPatchNum(revisions: RevisionInfo[]) {
 | 
			
		||||
 * 3, edit, 2, 1.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
export function sortRevisions<T extends RevisionInfo>(revisions: T[]): T[] {
 | 
			
		||||
export function sortRevisions<T extends RevisionInfo | EditRevisionInfo>(
 | 
			
		||||
  revisions: T[]
 | 
			
		||||
): T[] {
 | 
			
		||||
  const editParent: number = findEditParentPatchNum(revisions);
 | 
			
		||||
  // Map a normal patchNum to 2 * (patchNum - 1) + 1... I.e. 1 -> 1,
 | 
			
		||||
  // 2 -> 3, 3 -> 5, etc.
 | 
			
		||||
@@ -200,12 +205,10 @@ export function computeAllPatchSets(
 | 
			
		||||
  let patchNums: PatchSet[] = [];
 | 
			
		||||
  if (change.revisions && Object.keys(change.revisions).length) {
 | 
			
		||||
    const changeRevisions = change.revisions;
 | 
			
		||||
    const revisions: RevisionWithSha[] = Object.keys(change.revisions).map(
 | 
			
		||||
      sha => {
 | 
			
		||||
        return {sha, ...changeRevisions[sha]};
 | 
			
		||||
      }
 | 
			
		||||
    );
 | 
			
		||||
    patchNums = sortRevisions(revisions).map((e: RevisionWithSha) => {
 | 
			
		||||
    const revisions = Object.keys(change.revisions).map(sha => {
 | 
			
		||||
      return {sha, ...changeRevisions[sha]};
 | 
			
		||||
    });
 | 
			
		||||
    patchNums = sortRevisions(revisions).map(e => {
 | 
			
		||||
      // TODO(kaspern): Mark which patchset an edit was made on, if an
 | 
			
		||||
      // edit exists -- perhaps with a temporary description.
 | 
			
		||||
      return {
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,12 @@
 | 
			
		||||
  "name": "polygerrit-ui-dev-dependencies",
 | 
			
		||||
  "description": "Gerrit Code Review - Polygerrit dev dependencies",
 | 
			
		||||
  "browser": true,
 | 
			
		||||
  "dependencies": {},
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@types/chai": "^4.2.14",
 | 
			
		||||
    "@types/lodash": "^4.14.162",
 | 
			
		||||
    "@types/mocha": "^8.0.3",
 | 
			
		||||
    "@types/sinon": "^9.0.8"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@open-wc/karma-esm": "^2.16.16",
 | 
			
		||||
    "@polymer/iron-test-helpers": "^3.0.1",
 | 
			
		||||
 
 | 
			
		||||
@@ -989,6 +989,11 @@
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@types/caniuse-api/-/caniuse-api-3.0.0.tgz#af31cc52062be0ab24583be072fd49b634dcc2fe"
 | 
			
		||||
  integrity sha512-wT1VfnScjAftZsvLYaefu/UuwYJdYBwD2JDL2OQd01plGmuAoir5V6HnVHgrfh7zEwcasoiyO2wQ+W58sNh2sw==
 | 
			
		||||
 | 
			
		||||
"@types/chai@^4.2.14":
 | 
			
		||||
  version "4.2.14"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.2.14.tgz#44d2dd0b5de6185089375d976b4ec5caf6861193"
 | 
			
		||||
  integrity sha512-G+ITQPXkwTrslfG5L/BksmbLUA0M1iybEsmCWPqzSxsRRhJZimBKJkoMi8fr/CPygPTj4zO5pJH7I2/cm9M7SQ==
 | 
			
		||||
 | 
			
		||||
"@types/command-line-args@^5.0.0":
 | 
			
		||||
  version "5.0.0"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@types/command-line-args/-/command-line-args-5.0.0.tgz#484e704d20dbb8754a8f091eee45cdd22bcff28c"
 | 
			
		||||
@@ -1125,6 +1130,11 @@
 | 
			
		||||
  dependencies:
 | 
			
		||||
    "@types/koa" "*"
 | 
			
		||||
 | 
			
		||||
"@types/lodash@^4.14.162":
 | 
			
		||||
  version "4.14.162"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.162.tgz#65d78c397e0d883f44afbf1f7ba9867022411470"
 | 
			
		||||
  integrity sha512-alvcho1kRUnnD1Gcl4J+hK0eencvzq9rmzvFPRmP5rPHx9VVsJj6bKLTATPVf9ktgv4ujzh7T+XWKp+jhuODig==
 | 
			
		||||
 | 
			
		||||
"@types/lru-cache@^5.1.0":
 | 
			
		||||
  version "5.1.0"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@types/lru-cache/-/lru-cache-5.1.0.tgz#57f228f2b80c046b4a1bd5cac031f81f207f4f03"
 | 
			
		||||
@@ -1140,6 +1150,11 @@
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
 | 
			
		||||
  integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==
 | 
			
		||||
 | 
			
		||||
"@types/mocha@^8.0.3":
 | 
			
		||||
  version "8.0.3"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-8.0.3.tgz#51b21b6acb6d1b923bbdc7725c38f9f455166402"
 | 
			
		||||
  integrity sha512-vyxR57nv8NfcU0GZu8EUXZLTbCMupIUwy95LJ6lllN+JRPG25CwMHoB1q5xKh8YKhQnHYRAn4yW2yuHbf/5xgg==
 | 
			
		||||
 | 
			
		||||
"@types/node@*":
 | 
			
		||||
  version "14.0.14"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.14.tgz#24a0b5959f16ac141aeb0c5b3cd7a15b7c64cbce"
 | 
			
		||||
@@ -1175,6 +1190,18 @@
 | 
			
		||||
    "@types/express-serve-static-core" "*"
 | 
			
		||||
    "@types/mime" "*"
 | 
			
		||||
 | 
			
		||||
"@types/sinon@^9.0.8":
 | 
			
		||||
  version "9.0.8"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-9.0.8.tgz#1ed0038d356784f75b086104ef83bfd4130bb81b"
 | 
			
		||||
  integrity sha512-IVnI820FZFMGI+u1R+2VdRaD/82YIQTdqLYC9DLPszZuynAJDtCvCtCs3bmyL66s7FqRM3+LPX7DhHnVTaagDw==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    "@types/sinonjs__fake-timers" "*"
 | 
			
		||||
 | 
			
		||||
"@types/sinonjs__fake-timers@*":
 | 
			
		||||
  version "6.0.2"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.2.tgz#3a84cf5ec3249439015e14049bd3161419bf9eae"
 | 
			
		||||
  integrity sha512-dIPoZ3g5gcx9zZEszaxLSVTvMReD3xxyyDnQUjA6IYDG9Ba2AV0otMPs+77sG9ojB4Qr2N2Vk5RnKeuA0X/0bg==
 | 
			
		||||
 | 
			
		||||
"@types/whatwg-url@^6.4.0":
 | 
			
		||||
  version "6.4.0"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@types/whatwg-url/-/whatwg-url-6.4.0.tgz#1e59b8c64bc0dbdf66d037cf8449d1c3d5270237"
 | 
			
		||||
 
 | 
			
		||||
@@ -1,160 +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.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
const fs = require('fs');
 | 
			
		||||
const path = require('path');
 | 
			
		||||
const jsExt = '.js';
 | 
			
		||||
 | 
			
		||||
class NonJsValidator {
 | 
			
		||||
  onProgramEnd(context, node) {
 | 
			
		||||
  }
 | 
			
		||||
  onGoogDeclareModuleId(context, node) {
 | 
			
		||||
    context.report({
 | 
			
		||||
      message: 'goog.declareModuleId is allowed only in .js files',
 | 
			
		||||
      node: node,
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class JsOnlyValidator {
 | 
			
		||||
  onProgramEnd(context, node) {
 | 
			
		||||
  }
 | 
			
		||||
  onGoogDeclareModuleId(context, node) {
 | 
			
		||||
    context.report({
 | 
			
		||||
      message: 'goog.declareModuleId present, but .d.ts file doesn\'t exist. '
 | 
			
		||||
        + 'Either remove goog.declareModuleId or add the .d.ts file.',
 | 
			
		||||
      node: node,
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class JsWithDtsValidator {
 | 
			
		||||
  constructor() {
 | 
			
		||||
    this._googDeclareModuleIdExists = false;
 | 
			
		||||
  }
 | 
			
		||||
  onProgramEnd(context, node) {
 | 
			
		||||
    if(!this._googDeclareModuleIdExists) {
 | 
			
		||||
      context.report({
 | 
			
		||||
        message: 'goog.declareModuleId(...) is missed. ' +
 | 
			
		||||
            'Either add it or remove the associated .d.ts file.',
 | 
			
		||||
        node: node,
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  onGoogDeclareModuleId(context, node) {
 | 
			
		||||
    if(this._googDeclareModuleIdExists) {
 | 
			
		||||
      context.report({
 | 
			
		||||
        message: 'Duplicated goog.declareModuleId.',
 | 
			
		||||
        node: node,
 | 
			
		||||
      });
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const filename = context.getFilename();
 | 
			
		||||
    this._googDeclareModuleIdExists = true;
 | 
			
		||||
 | 
			
		||||
    const scope = context.getScope();
 | 
			
		||||
    if(scope.type !== 'global' && scope.type !== 'module') {
 | 
			
		||||
      context.report({
 | 
			
		||||
        message: 'goog.declareModuleId is allowed only at the root level.',
 | 
			
		||||
        node: node,
 | 
			
		||||
      });
 | 
			
		||||
      // no return - other problems are possible
 | 
			
		||||
    }
 | 
			
		||||
    if(node.arguments.length !== 1) {
 | 
			
		||||
      context.report({
 | 
			
		||||
        message: 'goog.declareModuleId must have exactly one parameter.',
 | 
			
		||||
        node: node,
 | 
			
		||||
      });
 | 
			
		||||
      if(node.arguments.length === 0) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const argument = node.arguments[0];
 | 
			
		||||
    if(argument.type !== 'Literal') {
 | 
			
		||||
      context.report({
 | 
			
		||||
        message: 'The argument for the declareModuleId method '
 | 
			
		||||
            + 'must be a string literal.',
 | 
			
		||||
        node: argument,
 | 
			
		||||
      });
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    const pathStart = '/polygerrit-ui/app/';
 | 
			
		||||
    const index = filename.lastIndexOf(pathStart);
 | 
			
		||||
    if(index < 0) {
 | 
			
		||||
      context.report({
 | 
			
		||||
        message: 'The file located outside of polygerrit-ui/app directory. ' +
 | 
			
		||||
          'Please check eslint config.',
 | 
			
		||||
        node: argument,
 | 
			
		||||
      });
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    const expectedName = 'polygerrit.' +
 | 
			
		||||
        filename.slice(index + pathStart.length, -jsExt.length)
 | 
			
		||||
            .replace(/\//g, '.') // Replace all occurrences of '/' with '.'
 | 
			
		||||
            .replace(/-/g, '$2d'); // Replace all occurrences of '-' with '$2d'
 | 
			
		||||
    if(argument.value !== expectedName) {
 | 
			
		||||
      context.report({
 | 
			
		||||
        message: `Invalid module id. It must be '${expectedName}'.`,
 | 
			
		||||
        node: argument,
 | 
			
		||||
        fix: function(fixer) {
 | 
			
		||||
          return fixer.replaceText(argument, `'${expectedName}'`);
 | 
			
		||||
        },
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
  meta: {
 | 
			
		||||
    type: 'problem',
 | 
			
		||||
    docs: {
 | 
			
		||||
      description: 'Check that goog.declareModuleId is valid',
 | 
			
		||||
      category: 'TS imports JS errors',
 | 
			
		||||
      recommended: false,
 | 
			
		||||
    },
 | 
			
		||||
    fixable: "code",
 | 
			
		||||
    schema: [],
 | 
			
		||||
  },
 | 
			
		||||
  create: function (context) {
 | 
			
		||||
    let fileValidator;
 | 
			
		||||
    return {
 | 
			
		||||
      Program: function(node) {
 | 
			
		||||
        const filename = context.getFilename();
 | 
			
		||||
        if(filename.endsWith(jsExt)) {
 | 
			
		||||
          const dtsFilename = filename.slice(0, -jsExt.length) + ".d.ts";
 | 
			
		||||
          if(fs.existsSync(dtsFilename)) {
 | 
			
		||||
            fileValidator = new JsWithDtsValidator();
 | 
			
		||||
          } else {
 | 
			
		||||
            fileValidator = new JsOnlyValidator();
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
          fileValidator = new NonJsValidator();
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "Program:exit": function(node) {
 | 
			
		||||
        fileValidator.onProgramEnd(context, node);
 | 
			
		||||
        fileValidator = null;
 | 
			
		||||
      },
 | 
			
		||||
      'ExpressionStatement > CallExpression[callee.property.name="declareModuleId"][callee.object.name="goog"]': function(node) {
 | 
			
		||||
        fileValidator.onGoogDeclareModuleId(context, node);
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
@@ -1,101 +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.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
// While we are migrating to typescript, gerrit can have .d.ts files.
 | 
			
		||||
// The option "skipLibCheck" is set to true  In the tsconfig.json.
 | 
			
		||||
// This is required, because we want to skip type checking in node_modules
 | 
			
		||||
// directory - some .d.ts files in 3rd-party modules are incorrect.
 | 
			
		||||
// Unfortunately, this options also excludes our own .d.ts files from type
 | 
			
		||||
// checking. This rule reports all .ts errors in a file as tslint errors.
 | 
			
		||||
 | 
			
		||||
function getMassageTextFromChain(chainNode, prefix) {
 | 
			
		||||
  let nestedMessages = prefix + chainNode.messageText;
 | 
			
		||||
  if (chainNode.next && chainNode.next.length > 0) {
 | 
			
		||||
    nestedMessages += "\n";
 | 
			
		||||
    for (const node of chainNode.next) {
 | 
			
		||||
      nestedMessages +=
 | 
			
		||||
          getMassageTextFromChain(node, prefix + " ");
 | 
			
		||||
      if(!nestedMessages.endsWith('\n')) {
 | 
			
		||||
        nestedMessages += "\n";
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return nestedMessages;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getMessageText(diagnostic) {
 | 
			
		||||
  if (typeof diagnostic.messageText === 'string') {
 | 
			
		||||
    return diagnostic.messageText;
 | 
			
		||||
  }
 | 
			
		||||
  return getMassageTextFromChain(diagnostic.messageText, "");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getDiagnosticStartAndEnd(diagnostic) {
 | 
			
		||||
  if(diagnostic.start) {
 | 
			
		||||
    const file = diagnostic.file;
 | 
			
		||||
    const start = file.getLineAndCharacterOfPosition(diagnostic.start);
 | 
			
		||||
    const length = diagnostic.length ? diagnostic.length : 0;
 | 
			
		||||
    return {
 | 
			
		||||
      start,
 | 
			
		||||
      end: file.getLineAndCharacterOfPosition(diagnostic.start + length),
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
  return {
 | 
			
		||||
    start: {line:0, character: 0},
 | 
			
		||||
    end: {line:0, character: 0},
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
  meta: {
 | 
			
		||||
    type: "problem",
 | 
			
		||||
    docs: {
 | 
			
		||||
      description: "Reports all typescript problems as linter problems",
 | 
			
		||||
      category: ".d.ts",
 | 
			
		||||
      recommended: false
 | 
			
		||||
    },
 | 
			
		||||
    schema: [],
 | 
			
		||||
  },
 | 
			
		||||
  create: function (context) {
 | 
			
		||||
    const program = context.parserServices.program;
 | 
			
		||||
    return {
 | 
			
		||||
      Program: function(node) {
 | 
			
		||||
        const sourceFile =
 | 
			
		||||
            context.parserServices.esTreeNodeToTSNodeMap.get(node);
 | 
			
		||||
        const allDiagnostics = [
 | 
			
		||||
            ...program.getDeclarationDiagnostics(sourceFile),
 | 
			
		||||
            ...program.getSemanticDiagnostics(sourceFile)];
 | 
			
		||||
        for(const diagnostic of allDiagnostics) {
 | 
			
		||||
          const {start, end } = getDiagnosticStartAndEnd(diagnostic);
 | 
			
		||||
          context.report({
 | 
			
		||||
            message: getMessageText(diagnostic),
 | 
			
		||||
            loc: {
 | 
			
		||||
              start: {
 | 
			
		||||
                line: start.line + 1,
 | 
			
		||||
                column: start.character,
 | 
			
		||||
              },
 | 
			
		||||
              end: {
 | 
			
		||||
                line: end.line + 1,
 | 
			
		||||
                column: end.character,
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
          });
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
@@ -1,109 +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.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
const path = require('path');
 | 
			
		||||
const fs = require('fs');
 | 
			
		||||
 | 
			
		||||
function checkImportValid(context, node) {
 | 
			
		||||
  const file = context.getFilename();
 | 
			
		||||
  const importSource = node.source.value;
 | 
			
		||||
 | 
			
		||||
  if(importSource.startsWith('/')) {
 | 
			
		||||
    return {
 | 
			
		||||
      message: 'Do not use absolute path for import.',
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const targetFile = path.resolve(path.dirname(file), importSource);
 | 
			
		||||
  const extName = path.extname(targetFile);
 | 
			
		||||
  // There is a polymer.dom.js file, so .dom is not an extension
 | 
			
		||||
  if(extName !== '' && !targetFile.endsWith('polymer.dom')) {
 | 
			
		||||
    return {
 | 
			
		||||
      message: 'Do not specify extensions for import path.',
 | 
			
		||||
      fix: function(fixer) {
 | 
			
		||||
        return fixer.replaceText(node.source, `'${importSource.slice(0, -extName.length)}'`);
 | 
			
		||||
      },
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if(!importSource.startsWith('./') && !importSource.startsWith('../')) {
 | 
			
		||||
    // Import from node_modules - nothing else to check
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  if(fs.existsSync(targetFile + ".ts")) {
 | 
			
		||||
    // .ts file exists - nothing to check
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const jsFileExists = fs.existsSync(targetFile + '.js');
 | 
			
		||||
  const dtsFileExists = fs.existsSync(targetFile + '.d.ts');
 | 
			
		||||
 | 
			
		||||
  if(jsFileExists && !dtsFileExists) {
 | 
			
		||||
    return {
 | 
			
		||||
      message: `The '${importSource}.d.ts' file doesn't exist.`
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if(!jsFileExists && dtsFileExists) {
 | 
			
		||||
    return {
 | 
			
		||||
      message: `The '${importSource}.js' file doesn't exist.`
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
  // If both files (.js and .d.ts) don't exist, the error is reported by
 | 
			
		||||
  // the typescript compiler. Do not report anything from the rule.
 | 
			
		||||
  return null;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
  meta: {
 | 
			
		||||
    type: "problem",
 | 
			
		||||
    docs: {
 | 
			
		||||
      description: "Check that TS file can import specific JS file",
 | 
			
		||||
      category: "TS imports JS errors",
 | 
			
		||||
      recommended: false
 | 
			
		||||
    },
 | 
			
		||||
    schema: [],
 | 
			
		||||
    fixable: "code",
 | 
			
		||||
  },
 | 
			
		||||
  create: function (context) {
 | 
			
		||||
    return {
 | 
			
		||||
      Program: function(node) {
 | 
			
		||||
        const filename = context.getFilename();
 | 
			
		||||
        if(filename.endsWith('.ts') && !filename.endsWith('.d.ts')) {
 | 
			
		||||
          return;
 | 
			
		||||
        }
 | 
			
		||||
        context.report({
 | 
			
		||||
          message: 'The rule must be used only with .ts files. ' +
 | 
			
		||||
              'Check eslint settings.',
 | 
			
		||||
          node: node,
 | 
			
		||||
        });
 | 
			
		||||
      },
 | 
			
		||||
      ImportDeclaration: function (node) {
 | 
			
		||||
        const importProblem = checkImportValid(context, node);
 | 
			
		||||
        if(importProblem) {
 | 
			
		||||
          context.report({
 | 
			
		||||
            message: importProblem.message,
 | 
			
		||||
            node: node.source,
 | 
			
		||||
            fix: importProblem.fix,
 | 
			
		||||
          });
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
		Reference in New Issue
	
	Block a user