Merge "Introduce further colors for OOO icons"
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index a1dc27c..ace6995 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -2216,6 +2216,9 @@
other project managed by the running server. The name is
relative to `gerrit.basePath`.
+
+The link:#cache_names[persisted_projects cache] must be
+flushed after this setting is changed.
++
Defaults to `All-Projects` if not set.
[[gerrit.defaultBranch]]gerrit.defaultBranch::
diff --git a/java/com/google/gerrit/entities/AccessSection.java b/java/com/google/gerrit/entities/AccessSection.java
index 69a234a..8ae0a5d 100644
--- a/java/com/google/gerrit/entities/AccessSection.java
+++ b/java/com/google/gerrit/entities/AccessSection.java
@@ -18,12 +18,14 @@
import static java.util.Objects.requireNonNull;
import com.google.auto.value.AutoValue;
+import com.google.auto.value.extension.memoized.Memoized;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.common.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
+import java.util.regex.Pattern;
/** Portion of a {@link Project} describing access rules. */
@AutoValue
@@ -42,6 +44,20 @@
/** Name of the access section. It could be a ref pattern or something else. */
public abstract String getName();
+ /**
+ * A compiled regular expression in case {@link #getName()} is a regular expression. This is
+ * memoized to save callers from compiling patterns for every use.
+ */
+ @Memoized
+ public Optional<Pattern> getNamePattern() {
+ if (isValidRefSectionName(getName())
+ && getName().startsWith(REGEX_PREFIX)
+ && !getName().contains("${")) {
+ return Optional.of(Pattern.compile(getName()));
+ }
+ return Optional.empty();
+ }
+
public abstract ImmutableList<Permission> getPermissions();
public static AccessSection create(String name) {
diff --git a/java/com/google/gerrit/entities/CachedProjectConfig.java b/java/com/google/gerrit/entities/CachedProjectConfig.java
index cd65efc..be4a1cf 100644
--- a/java/com/google/gerrit/entities/CachedProjectConfig.java
+++ b/java/com/google/gerrit/entities/CachedProjectConfig.java
@@ -18,6 +18,7 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSortedMap;
import com.google.common.flogger.FluentLogger;
import java.util.Collection;
import java.util.List;
@@ -69,7 +70,7 @@
public abstract AccountsSection getAccountsSection();
/** Returns a map of {@link AccessSection}s keyed by their name. */
- public abstract ImmutableMap<String, AccessSection> getAccessSections();
+ public abstract ImmutableSortedMap<String, AccessSection> getAccessSections();
/** Returns the {@link AccessSection} with to the given name. */
public Optional<AccessSection> getAccessSection(String refName) {
@@ -235,7 +236,7 @@
protected abstract ImmutableMap.Builder<AccountGroup.UUID, GroupReference> groupsBuilder();
- protected abstract ImmutableMap.Builder<String, AccessSection> accessSectionsBuilder();
+ protected abstract ImmutableSortedMap.Builder<String, AccessSection> accessSectionsBuilder();
protected abstract ImmutableMap.Builder<String, ContributorAgreement>
contributorAgreementsBuilder();
diff --git a/java/com/google/gerrit/server/permissions/PermissionBackend.java b/java/com/google/gerrit/server/permissions/PermissionBackend.java
index 27c6793..1191db8 100644
--- a/java/com/google/gerrit/server/permissions/PermissionBackend.java
+++ b/java/com/google/gerrit/server/permissions/PermissionBackend.java
@@ -173,7 +173,13 @@
return ref(notes.getChange().getDest()).change(notes);
}
- /** Verify scoped user can {@code perm}, throwing if denied. */
+ /**
+ * Verify scoped user can {@code perm}, throwing if denied.
+ *
+ * <p>Should be used in REST API handlers where the thrown {@link AuthException} can be
+ * propagated. In business logic, where the exception would have to be caught, prefer using
+ * {@link #test(GlobalOrPluginPermission)}.
+ */
public abstract void check(GlobalOrPluginPermission perm)
throws AuthException, PermissionBackendException;
@@ -280,7 +286,13 @@
return ref(notes.getChange().getDest().branch()).change(notes);
}
- /** Verify scoped user can {@code perm}, throwing if denied. */
+ /**
+ * Verify scoped user can {@code perm}, throwing if denied.
+ *
+ * <p>Should be used in REST API handlers where the thrown {@link AuthException} can be
+ * propagated. In business logic, where the exception would have to be caught, prefer using
+ * {@link #test(CoreOrPluginProjectPermission)}.
+ */
public abstract void check(CoreOrPluginProjectPermission perm)
throws AuthException, PermissionBackendException;
@@ -368,7 +380,13 @@
/** Returns an instance scoped to change. */
public abstract ForChange change(ChangeNotes notes);
- /** Verify scoped user can {@code perm}, throwing if denied. */
+ /**
+ * Verify scoped user can {@code perm}, throwing if denied.
+ *
+ * <p>Should be used in REST API handlers where the thrown {@link AuthException} can be
+ * propagated. In business logic, where the exception would have to be caught, prefer using
+ * {@link #test(RefPermission)}.
+ */
public abstract void check(RefPermission perm) throws AuthException, PermissionBackendException;
/** Filter {@code permSet} to permissions scoped user might be able to perform. */
@@ -406,7 +424,13 @@
/** Returns the fully qualified resource path that this instance is scoped to. */
public abstract String resourcePath();
- /** Verify scoped user can {@code perm}, throwing if denied. */
+ /**
+ * Verify scoped user can {@code perm}, throwing if denied.
+ *
+ * <p>Should be used in REST API handlers where the thrown {@link AuthException} can be
+ * propagated. In business logic, where the exception would have to be caught, prefer using
+ * {@link #test(ChangePermissionOrLabel)}.
+ */
public abstract void check(ChangePermissionOrLabel perm)
throws AuthException, PermissionBackendException;
diff --git a/java/com/google/gerrit/server/project/ProjectCacheImpl.java b/java/com/google/gerrit/server/project/ProjectCacheImpl.java
index 31bbff5..3aa3783 100644
--- a/java/com/google/gerrit/server/project/ProjectCacheImpl.java
+++ b/java/com/google/gerrit/server/project/ProjectCacheImpl.java
@@ -130,7 +130,7 @@
.keySerializer(new ProtobufSerializer<>(Cache.ProjectCacheKeyProto.parser()))
.valueSerializer(PersistedProjectConfigSerializer.INSTANCE)
.diskLimit(1 << 30) // 1 GiB
- .version(3)
+ .version(4)
.maximumWeight(0);
cache(CACHE_LIST, ListKey.class, new TypeLiteral<ImmutableSortedSet<Project.NameKey>>() {})
diff --git a/java/com/google/gerrit/server/project/ProjectConfig.java b/java/com/google/gerrit/server/project/ProjectConfig.java
index 4a17b5c..123a14c 100644
--- a/java/com/google/gerrit/server/project/ProjectConfig.java
+++ b/java/com/google/gerrit/server/project/ProjectConfig.java
@@ -224,7 +224,8 @@
projectName,
projectName.equals(allProjectsName)
? allProjectsConfigProvider.get(allProjectsName)
- : Optional.empty());
+ : Optional.empty(),
+ allProjectsName);
}
public ProjectConfig read(MetaDataUpdate update) throws IOException, ConfigInvalidException {
@@ -250,6 +251,7 @@
}
private final Optional<StoredConfig> baseConfig;
+ private final AllProjectsName allProjectsName;
private Project project;
private AccountsSection accountsSection;
@@ -287,7 +289,6 @@
.setCheckReceivedObjects(checkReceivedObjects)
.setExtensionPanelSections(extensionPanelSections);
groupList.byUUID().values().forEach(g -> builder.addGroup(g));
- accessSections.values().forEach(a -> builder.addAccessSection(a));
contributorAgreements.values().forEach(c -> builder.addContributorAgreement(c));
notifySections.values().forEach(n -> builder.addNotifySection(n));
subscribeSections.values().forEach(s -> builder.addSubscribeSection(s));
@@ -300,6 +301,28 @@
projectLevelConfigs
.entrySet()
.forEach(c -> builder.addProjectLevelConfig(c.getKey(), c.getValue().toText()));
+
+ if (projectName.equals(allProjectsName)) {
+ // Filter out permissions that aren't allowed to be set on All-Projects
+ accessSections
+ .values()
+ .forEach(
+ a -> {
+ List<Permission.Builder> copy = new ArrayList<>();
+ for (Permission p : a.getPermissions()) {
+ if (Permission.canBeOnAllProjects(a.getName(), p.getName())) {
+ copy.add(p.toBuilder());
+ }
+ }
+ AccessSection section =
+ AccessSection.builder(a.getName())
+ .modifyPermissions(permissions -> permissions.addAll(copy))
+ .build();
+ builder.addAccessSection(section);
+ });
+ } else {
+ accessSections.values().forEach(a -> builder.addAccessSection(a));
+ }
return builder.build();
}
@@ -355,9 +378,13 @@
requireNonNull(commentLinkSections.remove(name));
}
- private ProjectConfig(Project.NameKey projectName, Optional<StoredConfig> baseConfig) {
+ private ProjectConfig(
+ Project.NameKey projectName,
+ Optional<StoredConfig> baseConfig,
+ AllProjectsName allProjectsName) {
this.projectName = projectName;
this.baseConfig = baseConfig;
+ this.allProjectsName = allProjectsName;
}
public void load(Repository repo) throws IOException, ConfigInvalidException {
diff --git a/java/com/google/gerrit/server/project/ProjectState.java b/java/com/google/gerrit/server/project/ProjectState.java
index 69e6036..6352f66 100644
--- a/java/com/google/gerrit/server/project/ProjectState.java
+++ b/java/com/google/gerrit/server/project/ProjectState.java
@@ -15,9 +15,7 @@
package com.google.gerrit.server.project;
import static com.google.common.base.Preconditions.checkState;
-import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.gerrit.entities.PermissionRule.Action.ALLOW;
-import static java.util.Comparator.comparing;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.FluentIterable;
@@ -268,35 +266,18 @@
/** Get the sections that pertain only to this project. */
List<SectionMatcher> getLocalAccessSections() {
- List<SectionMatcher> sm = localAccessSections;
- if (sm == null) {
- ImmutableList<AccessSection> fromConfig =
- cachedConfig.getAccessSections().values().stream()
- .sorted(comparing(AccessSection::getName))
- .collect(toImmutableList());
- sm = new ArrayList<>(fromConfig.size());
- for (AccessSection section : fromConfig) {
- if (isAllProjects) {
- List<Permission.Builder> copy = new ArrayList<>();
- for (Permission p : section.getPermissions()) {
- if (Permission.canBeOnAllProjects(section.getName(), p.getName())) {
- copy.add(p.toBuilder());
- }
- }
- section =
- AccessSection.builder(section.getName())
- .modifyPermissions(permissions -> permissions.addAll(copy))
- .build();
- }
-
- SectionMatcher matcher = SectionMatcher.wrap(getNameKey(), section);
- if (matcher != null) {
- sm.add(matcher);
- }
- }
- localAccessSections = sm;
+ if (localAccessSections != null) {
+ return localAccessSections;
}
- return sm;
+ List<SectionMatcher> sm = new ArrayList<>(cachedConfig.getAccessSections().values().size());
+ for (AccessSection section : cachedConfig.getAccessSections().values()) {
+ SectionMatcher matcher = SectionMatcher.wrap(getNameKey(), section);
+ if (matcher != null) {
+ sm.add(matcher);
+ }
+ }
+ localAccessSections = sm;
+ return localAccessSections;
}
/**
diff --git a/java/com/google/gerrit/server/project/RefPatternMatcher.java b/java/com/google/gerrit/server/project/RefPatternMatcher.java
index b9076b3..be840b5 100644
--- a/java/com/google/gerrit/server/project/RefPatternMatcher.java
+++ b/java/com/google/gerrit/server/project/RefPatternMatcher.java
@@ -22,6 +22,7 @@
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Streams;
import com.google.gerrit.common.data.ParameterizedString;
+import com.google.gerrit.entities.AccessSection;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.RefNames;
import com.google.gerrit.server.CurrentUser;
@@ -32,6 +33,13 @@
import java.util.stream.Stream;
public abstract class RefPatternMatcher {
+ public static RefPatternMatcher getMatcher(AccessSection section) {
+ if (section.getNamePattern().isPresent()) {
+ return new Regexp(section.getNamePattern().get());
+ }
+ return getMatcher(section.getName());
+ }
+
public static RefPatternMatcher getMatcher(String pattern) {
if (containsParameters(pattern)) {
return new ExpandParameters(pattern);
@@ -79,6 +87,10 @@
pattern = Pattern.compile(re);
}
+ Regexp(Pattern re) {
+ pattern = re;
+ }
+
@Override
public boolean match(String ref, CurrentUser user) {
return pattern.matcher(ref).matches() || (isRE(ref) && pattern.pattern().equals(ref));
diff --git a/java/com/google/gerrit/server/project/SectionMatcher.java b/java/com/google/gerrit/server/project/SectionMatcher.java
index 763957e..3d7175f 100644
--- a/java/com/google/gerrit/server/project/SectionMatcher.java
+++ b/java/com/google/gerrit/server/project/SectionMatcher.java
@@ -28,7 +28,7 @@
static SectionMatcher wrap(Project.NameKey project, AccessSection section) {
String ref = section.getName();
if (AccessSection.isValidRefSectionName(ref)) {
- return new SectionMatcher(project, section, getMatcher(ref));
+ return new SectionMatcher(project, section, getMatcher(section));
}
return null;
}
diff --git a/polygerrit-ui/app/api/rest-api.ts b/polygerrit-ui/app/api/rest-api.ts
index 5cbae90..9eb8aa4 100644
--- a/polygerrit-ui/app/api/rest-api.ts
+++ b/polygerrit-ui/app/api/rest-api.ts
@@ -558,7 +558,7 @@
export declare interface ContributorAgreementInfo {
name: string;
description: string;
- url: string;
+ url?: string;
auto_verify_group?: GroupInfo;
}
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-column-requirement/gr-change-list-column-requirement.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-column-requirement/gr-change-list-column-requirement.ts
new file mode 100644
index 0000000..c832f13
--- /dev/null
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-column-requirement/gr-change-list-column-requirement.ts
@@ -0,0 +1,97 @@
+/**
+ * @license
+ * Copyright (C) 2022 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 '../../change/gr-submit-requirement-dashboard-hovercard/gr-submit-requirement-dashboard-hovercard';
+import '../../shared/gr-change-status/gr-change-status';
+import {LitElement, css, html} from 'lit';
+import {customElement, property} from 'lit/decorators';
+import {ChangeInfo, SubmitRequirementStatus} from '../../../api/rest-api';
+import {submitRequirementsStyles} from '../../../styles/gr-submit-requirements-styles';
+import {getRequirements, iconForStatus} from '../../../utils/label-util';
+import {sharedStyles} from '../../../styles/shared-styles';
+
+@customElement('gr-change-list-column-requirement')
+export class GrChangeListColumnRequirement extends LitElement {
+ @property({type: Object})
+ change?: ChangeInfo;
+
+ @property()
+ labelName?: string;
+
+ static override get styles() {
+ return [
+ submitRequirementsStyles,
+ sharedStyles,
+ css`
+ iron-icon {
+ vertical-align: top;
+ }
+ .container.not-applicable {
+ background-color: var(--table-header-background-color);
+ height: calc(var(--line-height-normal) + var(--spacing-m));
+ }
+ `,
+ ];
+ }
+
+ override render() {
+ return html`<div class="container ${this.computeClass()}">
+ ${this.renderContent()}
+ </div>`;
+ }
+
+ private renderContent() {
+ if (!this.labelName) return;
+ const requirements = this.getRequirement(this.labelName);
+ if (requirements.length === 0) return;
+
+ const icon = iconForStatus(
+ requirements[0].status ?? SubmitRequirementStatus.ERROR
+ );
+ return html`<iron-icon
+ class="${icon}"
+ icon="gr-icons:${icon}"
+ ></iron-icon>`;
+ }
+
+ private computeClass(): string {
+ if (!this.labelName) return '';
+ const requirements = this.getRequirement(this.labelName);
+ if (requirements.length === 0) {
+ return 'not-applicable';
+ }
+ return '';
+ }
+
+ private getRequirement(labelName: string) {
+ const requirements = getRequirements(this.change).filter(
+ sr => sr.name === labelName
+ );
+ // TODO(milutin): Remove this after migration from legacy requirements.
+ if (requirements.length > 1) {
+ return requirements.filter(sr => !sr.is_legacy);
+ } else {
+ return requirements;
+ }
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ 'gr-change-list-column-requirement': GrChangeListColumnRequirement;
+ }
+}
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-column-requirement/gr-change-list-column-requirement_test.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-column-requirement/gr-change-list-column-requirement_test.ts
new file mode 100644
index 0000000..01fcd2c
--- /dev/null
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-column-requirement/gr-change-list-column-requirement_test.ts
@@ -0,0 +1,70 @@
+/**
+ * @license
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import '../../../test/common-test-setup-karma';
+import {fixture} from '@open-wc/testing-helpers';
+import {html} from 'lit';
+import './gr-change-list-column-requirement';
+import {GrChangeListColumnRequirement} from './gr-change-list-column-requirement';
+import {
+ createSubmitRequirementExpressionInfo,
+ createSubmitRequirementResultInfo,
+ createNonApplicableSubmitRequirementResultInfo,
+ createChange,
+} from '../../../test/test-data-generators';
+import {ChangeInfo, SubmitRequirementResultInfo} from '../../../api/rest-api';
+import {StandardLabels} from '../../../utils/label-util';
+
+suite('gr-change-list-column-requirement tests', () => {
+ let element: GrChangeListColumnRequirement;
+ let change: ChangeInfo;
+ setup(() => {
+ const submitRequirement: SubmitRequirementResultInfo = {
+ ...createSubmitRequirementResultInfo(),
+ name: StandardLabels.CODE_REVIEW,
+ submittability_expression_result: {
+ ...createSubmitRequirementExpressionInfo(),
+ expression: 'label:Verified=MAX -label:Verified=MIN',
+ },
+ };
+ change = {
+ ...createChange(),
+ submit_requirements: [
+ submitRequirement,
+ createNonApplicableSubmitRequirementResultInfo(),
+ ],
+ unresolved_comment_count: 1,
+ };
+ });
+
+ test('renders', async () => {
+ element = await fixture<GrChangeListColumnRequirement>(
+ html`<gr-change-list-column-requirement
+ .change=${change}
+ .labelName=${StandardLabels.CODE_REVIEW}
+ >
+ </gr-change-list-column-requirement>`
+ );
+ expect(element).shadowDom.to.equal(`<div class="container">
+ <iron-icon
+ class="check-circle-filled"
+ icon="gr-icons:check-circle-filled"
+ >
+ </iron-icon>
+ </div>`);
+ });
+});
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.ts
index a09466b..0eb0b136 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.ts
@@ -25,6 +25,7 @@
import '../../plugins/gr-endpoint-decorator/gr-endpoint-decorator';
import '../../plugins/gr-endpoint-param/gr-endpoint-param';
import '../gr-change-list-column-requirements-summary/gr-change-list-column-requirements-summary';
+import '../gr-change-list-column-requirement/gr-change-list-column-requirement';
import '../../shared/gr-tooltip-content/gr-tooltip-content';
import {GerritNav} from '../../core/gr-navigation/gr-navigation';
import {getDisplayName} from '../../../utils/display-name-util';
@@ -44,11 +45,7 @@
} from '../../../types/common';
import {hasOwnProperty} from '../../../utils/common-util';
import {pluralize} from '../../../utils/string-util';
-import {
- getRequirements,
- iconForStatus,
- showNewSubmitRequirements,
-} from '../../../utils/label-util';
+import {showNewSubmitRequirements} from '../../../utils/label-util';
import {changeListStyles} from '../../../styles/gr-change-list-styles';
import {sharedStyles} from '../../../styles/shared-styles';
import {LitElement, css, html} from 'lit';
@@ -256,6 +253,11 @@
.cell.label iron-icon {
vertical-align: top;
}
+ /* Requirement child needs whole area */
+ .cell.requirement {
+ padding: 0;
+ margin: 0;
+ }
@media only screen and (max-width: 50em) {
:host {
display: flex;
@@ -535,6 +537,15 @@
}
private renderChangeLabels(labelName: string) {
+ if (showNewSubmitRequirements(this.flagsService, this.change)) {
+ return html` <td class="cell label requirement">
+ <gr-change-list-column-requirement
+ .change=${this.change}
+ .labelName=${labelName}
+ >
+ </gr-change-list-column-requirement>
+ </td>`;
+ }
return html`
<td
title="${this.computeLabelTitle(labelName)}"
@@ -546,16 +557,6 @@
}
private renderChangeHasLabelIcon(labelName: string) {
- if (showNewSubmitRequirements(this.flagsService, this.change)) {
- const requirements = this.getRequirement(labelName);
- if (requirements.length === 1) {
- const icon = iconForStatus(requirements[0].status);
- return html`<iron-icon
- class="${icon}"
- icon="gr-icons:${icon}"
- ></iron-icon>`;
- }
- }
if (this.computeLabelIcon(labelName) === '')
return html`<span>${this.computeLabelValue(labelName)}</span>`;
@@ -611,14 +612,6 @@
// private but used in test
computeLabelClass(labelName: string) {
const classes = ['cell', 'label'];
- if (showNewSubmitRequirements(this.flagsService, this.change)) {
- const requirements = this.getRequirement(labelName);
- if (requirements.length === 1) {
- classes.push('requirement');
- // Do not add label category classes.
- return classes.sort().join(' ');
- }
- }
const category = this.computeLabelCategory(labelName);
switch (category) {
case LabelCategory.NOT_APPLICABLE:
@@ -900,16 +893,4 @@
const isLast = index === primaryCount - 1;
return isLast && additionalCount === 0;
}
-
- private getRequirement(labelName: string) {
- const requirements = getRequirements(this.change).filter(
- sr => sr.name === labelName
- );
- // TODO(milutin): Remove this after migration from legacy requirements.
- if (requirements.length > 1) {
- return requirements.filter(sr => !sr.is_legacy);
- } else {
- return requirements;
- }
- }
}
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.ts
index ab8d2d7..9c823ab 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.ts
@@ -498,9 +498,7 @@
);
const requirement = queryAndAssert(element, '.requirement');
- expect(requirement).dom.to.equal(`<iron-icon
- class="check-circle-filled"
- icon="gr-icons:check-circle-filled">
- </iron-icon>`);
+ expect(requirement).dom.to.equal(`<gr-change-list-column-requirement>
+ </gr-change-list-column-requirement>`);
});
});
diff --git a/polygerrit-ui/app/elements/change/gr-message-scores/gr-message-scores.ts b/polygerrit-ui/app/elements/change/gr-message-scores/gr-message-scores.ts
index 9492fa9..d01ae02 100644
--- a/polygerrit-ui/app/elements/change/gr-message-scores/gr-message-scores.ts
+++ b/polygerrit-ui/app/elements/change/gr-message-scores/gr-message-scores.ts
@@ -14,14 +14,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+import '../gr-trigger-vote/gr-trigger-vote';
import {LitElement, css, html} from 'lit';
import {customElement, property} from 'lit/decorators';
+import {ChangeInfo} from '../../../api/rest-api';
import {
ChangeMessage,
LabelExtreme,
PATCH_SET_PREFIX_PATTERN,
} from '../../../utils/comment-util';
import {hasOwnProperty} from '../../../utils/common-util';
+import {getTriggerVotes} from '../../../utils/label-util';
const VOTE_RESET_TEXT = '0 (vote reset)';
@@ -41,16 +44,22 @@
@property({type: Object})
message?: ChangeMessage;
+ @property({type: Object})
+ change?: ChangeInfo;
+
static override get styles() {
return css`
+ .score,
+ gr-trigger-vote {
+ padding: 0 var(--spacing-s);
+ margin-right: var(--spacing-s);
+ display: inline-block;
+ }
.score {
box-sizing: border-box;
border-radius: var(--border-radius);
color: var(--vote-text-color);
- display: inline-block;
- padding: 0 var(--spacing-s);
text-align: center;
- margin-right: var(--spacing-s);
min-width: 115px;
}
.score.removed {
@@ -93,10 +102,22 @@
override render() {
const scores = this._getScores(this.message, this.labelExtremes);
- return scores.map(score => this.renderScore(score));
+ const triggerVotes = getTriggerVotes(this.change);
+ return scores.map(score => this.renderScore(score, triggerVotes));
}
- private renderScore(score: Score) {
+ private renderScore(score: Score, triggerVotes: string[]) {
+ if (score.label && triggerVotes.includes(score.label)) {
+ const labels = this.change?.labels ?? {};
+ return html`<gr-trigger-vote
+ .label="${score.label}"
+ .labelInfo="${labels[score.label]}"
+ .change="${this.change}"
+ .mutable="${false}"
+ disable-hovercards
+ >
+ </gr-trigger-vote>`;
+ }
return html`<span
class="score ${this._computeScoreClass(score, this.labelExtremes)}"
>
diff --git a/polygerrit-ui/app/elements/change/gr-message/gr-message_html.ts b/polygerrit-ui/app/elements/change/gr-message/gr-message_html.ts
index 628af83..70e6381 100644
--- a/polygerrit-ui/app/elements/change/gr-message/gr-message_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-message/gr-message_html.ts
@@ -190,6 +190,7 @@
<gr-message-scores
label-extremes="[[labelExtremes]]"
message="[[message]]"
+ change="[[change]]"
></gr-message-scores>
</div>
<template is="dom-if" if="[[_commentCountText]]">
diff --git a/polygerrit-ui/app/elements/change/gr-submit-requirements/gr-submit-requirements.ts b/polygerrit-ui/app/elements/change/gr-submit-requirements/gr-submit-requirements.ts
index 8fa2fa4..c5e8b8c 100644
--- a/polygerrit-ui/app/elements/change/gr-submit-requirements/gr-submit-requirements.ts
+++ b/polygerrit-ui/app/elements/change/gr-submit-requirements/gr-submit-requirements.ts
@@ -356,6 +356,7 @@
.change="${this.change}"
.account="${this.account}"
.mutable="${this.mutable ?? false}"
+ .disableHovercards=${this.disableHovercards}
></gr-trigger-vote>`
)}
</section>`;
diff --git a/polygerrit-ui/app/elements/change/gr-trigger-vote/gr-trigger-vote.ts b/polygerrit-ui/app/elements/change/gr-trigger-vote/gr-trigger-vote.ts
index d920b46..a00de22 100644
--- a/polygerrit-ui/app/elements/change/gr-trigger-vote/gr-trigger-vote.ts
+++ b/polygerrit-ui/app/elements/change/gr-trigger-vote/gr-trigger-vote.ts
@@ -48,6 +48,9 @@
@property({type: Boolean})
mutable?: boolean;
+ @property({type: Boolean, attribute: 'disable-hovercards'})
+ disableHovercards = false;
+
static override get styles() {
return css`
:host {
@@ -84,26 +87,31 @@
if (!this.labelInfo) return;
return html`
<div class="container">
- <gr-trigger-vote-hovercard
- .labelName=${this.label}
- .labelInfo=${this.labelInfo}
- >
- <gr-label-info
- slot="label-info"
- .change=${this.change}
- .account=${this.account}
- .mutable=${this.mutable}
- .label=${this.label}
- .labelInfo=${this.labelInfo}
- .showAllReviewers=${false}
- ></gr-label-info>
- </gr-trigger-vote-hovercard>
+ ${this.renderHovercard()}
<span class="label">${this.label}</span>
${this.renderVotes()}
</div>
`;
}
+ private renderHovercard() {
+ if (this.disableHovercards) return;
+ return html`<gr-trigger-vote-hovercard
+ .labelName=${this.label}
+ .labelInfo=${this.labelInfo}
+ >
+ <gr-label-info
+ slot="label-info"
+ .change=${this.change}
+ .account=${this.account}
+ .mutable=${this.mutable}
+ .label=${this.label}
+ .labelInfo=${this.labelInfo}
+ .showAllReviewers=${false}
+ ></gr-label-info>
+ </gr-trigger-vote-hovercard>`;
+ }
+
private renderVotes() {
const {labelInfo} = this;
if (!labelInfo) return;
diff --git a/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list.ts b/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list.ts
index 43aefdc..2db7c76 100644
--- a/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list.ts
+++ b/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list.ts
@@ -62,7 +62,7 @@
return html`
<tr>
<td class="nameColumn">
- <a href="${this.getUrlBase(agreement.url)}" rel="external">
+ <a href="${this.getUrlBase(agreement?.url)}" rel="external">
${agreement.name}
</a>
</td>
@@ -94,8 +94,8 @@
return `${getBaseUrl()}/settings/new-agreement`;
}
- getUrlBase(item: string) {
- return `${getBaseUrl()}/${item}`;
+ getUrlBase(item?: string) {
+ return item ? `${getBaseUrl()}/${item}` : '';
}
}
diff --git a/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view.ts b/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view.ts
index 92d984d..677e0c1 100644
--- a/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view.ts
+++ b/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view.ts
@@ -196,4 +196,10 @@
? 'hideAgreementsTextBox'
: '';
}
+
+ _computeAgreements(serverConfig?: ServerInfo) {
+ return (serverConfig?.auth.contributor_agreements ?? []).filter(
+ agreement => agreement.url
+ );
+ }
}
diff --git a/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view_html.ts b/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view_html.ts
index ce95ccb..564297fa 100644
--- a/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view_html.ts
+++ b/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view_html.ts
@@ -66,10 +66,7 @@
<main>
<h1 class="heading-1">New Contributor Agreement</h1>
<h3 class="heading-3">Select an agreement type:</h3>
- <template
- is="dom-repeat"
- items="[[_serverConfig.auth.contributor_agreements]]"
- >
+ <template is="dom-repeat" items="[[_computeAgreements(_serverConfig)]]">
<span class="contributorAgreementButton">
<input
id$="claNewAgreementsInput[[item.name]]"
diff --git a/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.ts b/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.ts
index 9adb8ae..9520c618 100644
--- a/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.ts
+++ b/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.ts
@@ -15,11 +15,11 @@
* limitations under the License.
*/
import '../../../styles/shared-styles';
+import {PolymerElement} from '@polymer/polymer/polymer-element';
+import {htmlTemplate} from './gr-linked-text_html';
import {GrLinkTextParser, LinkTextParserConfig} from './link-text-parser';
import {GerritNav} from '../../core/gr-navigation/gr-navigation';
-import {LitElement, css, html, PropertyValues} from 'lit';
-import {customElement, property} from 'lit/decorators';
-import {assertIsDefined} from '../../../utils/common-util';
+import {customElement, property, observe} from '@polymer/decorators';
declare global {
interface HTMLElementTagNameMap {
@@ -27,111 +27,83 @@
}
}
+export interface GrLinkedText {
+ $: {
+ output: HTMLSpanElement;
+ };
+}
+
@customElement('gr-linked-text')
-export class GrLinkedText extends LitElement {
- private outputElement?: HTMLSpanElement;
+export class GrLinkedText extends PolymerElement {
+ static get template() {
+ return htmlTemplate;
+ }
@property({type: Boolean})
removeZeroWidthSpace?: boolean;
+ // content default is null, because this.$.output.textContent is string|null
@property({type: String})
- content = '';
+ content: string | null = null;
- @property({type: Boolean, attribute: true})
+ @property({type: Boolean, reflectToAttribute: true})
pre = false;
- @property({type: Boolean, attribute: true})
+ @property({type: Boolean, reflectToAttribute: true})
disabled = false;
- @property({type: Boolean, attribute: true})
+ @property({type: Boolean, reflectToAttribute: true})
inline = false;
@property({type: Object})
config?: LinkTextParserConfig;
- static override get styles() {
- return css`
- :host {
- display: block;
- }
- :host([inline]) {
- display: inline;
- }
- :host([pre]) ::slotted(span) {
- white-space: var(--linked-text-white-space, pre-wrap);
- word-wrap: var(--linked-text-word-wrap, break-word);
- }
- :host([disabled]) ::slotted(a) {
- color: inherit;
- text-decoration: none;
- pointer-events: none;
- }
- ::slotted(a) {
- color: var(--link-color);
- }
- `;
- }
-
- override render() {
- return html`<slot name="insert"></slot>`;
- }
-
- // NOTE: LinkTextParser dynamically creates HTML fragments based on backend
- // configuration commentLinks. These commentLinks can contain arbitrary HTML
- // fragments. This means that arbitrary HTML needs to be injected into the
- // DOM-tree, where this HTML is is controlled on the server-side in the
- // server-configuration rather than by arbitrary users.
- // To enable this injection of 'unsafe' HTML, LinkTextParser generates
- // HTML fragments. Lit does not support inserting html fragments directly
- // into its DOM-tree as it controls the DOM-tree that it generates.
- // Therefore, to get around this we create a single element that we slot into
- // the Lit-owned DOM. This element will not be part of this LitElement as
- // it's slotted in and thus can be modified on the fly by handleParseResult.
- override firstUpdated(_changedProperties: PropertyValues): void {
- this.outputElement = document.createElement('span');
- this.outputElement.id = 'output';
- this.outputElement.slot = 'insert';
- this.append(this.outputElement);
- }
-
- override updated(changedProperties: PropertyValues): void {
- if (changedProperties.has('content') || changedProperties.has('config')) {
- this._contentOrConfigChanged();
+ @observe('content')
+ _contentChanged(content: string | null) {
+ // In the case where the config may not be set (perhaps due to the
+ // request for it still being in flight), set the content anyway to
+ // prevent waiting on the config to display the text.
+ if (!this.config) {
+ return;
}
+ this.$.output.textContent = content;
}
/**
* Because either the source text or the linkification config has changed,
* the content should be re-parsed.
- * Private but used in tests.
*
* @param content The raw, un-linkified source string to parse.
* @param config The server config specifying commentLink patterns
*/
- _contentOrConfigChanged() {
- if (!this.config) {
- assertIsDefined(this.outputElement);
- this.outputElement.textContent = this.content;
+ @observe('content', 'config')
+ _contentOrConfigChanged(
+ content: string | null,
+ config?: LinkTextParserConfig
+ ) {
+ if (!config) {
return;
}
- const config = GerritNav.mapCommentlinks(this.config);
- assertIsDefined(this.outputElement);
- this.outputElement.textContent = '';
+ // TODO(TS): mapCommentlinks always has value, remove
+ if (!GerritNav.mapCommentlinks) return;
+ config = GerritNav.mapCommentlinks(config);
+ const output = this.$.output;
+ output.textContent = '';
const parser = new GrLinkTextParser(
config,
(text: string | null, href: string | null, fragment?: DocumentFragment) =>
this.handleParseResult(text, href, fragment),
this.removeZeroWidthSpace
);
- parser.parse(this.content);
+ parser.parse(content);
// Ensure that external links originating from HTML commentlink configs
// open in a new tab. @see Issue 5567
// Ensure links to the same host originating from commentlink configs
// open in the same tab. When target is not set - default is _self
// @see Issue 4616
- this.outputElement!.querySelectorAll('a').forEach(anchor => {
+ output.querySelectorAll('a').forEach(anchor => {
if (anchor.hostname === window.location.hostname) {
anchor.removeAttribute('target');
} else {
@@ -155,8 +127,7 @@
href: string | null,
fragment?: DocumentFragment
) {
- assertIsDefined(this.outputElement);
- const output = this.outputElement;
+ const output = this.$.output;
if (href) {
const a = document.createElement('a');
a.setAttribute('href', href);
diff --git a/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text_html.ts b/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text_html.ts
new file mode 100644
index 0000000..1893d14
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text_html.ts
@@ -0,0 +1,38 @@
+/**
+ * @license
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import {html} from '@polymer/polymer/lib/utils/html-tag';
+
+export const htmlTemplate = html`<style>
+ :host {
+ display: block;
+ }
+ :host([inline]) {
+ display: inline;
+ }
+ :host([pre]) span {
+ white-space: var(--linked-text-white-space, pre-wrap);
+ word-wrap: var(--linked-text-word-wrap, break-word);
+ }
+ :host([disabled]) a {
+ color: inherit;
+ text-decoration: none;
+ pointer-events: none;
+ }
+ a {
+ color: var(--link-color);
+ }</style
+ ><span id="output"></span>`;
diff --git a/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text_test.ts b/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text_test.ts
index 3bd6d2d..b2cdba1 100644
--- a/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text_test.ts
@@ -85,13 +85,11 @@
window.CANONICAL_PATH = originalCanonicalPath;
});
- test('URL pattern was parsed and linked.', async () => {
+ test('URL pattern was parsed and linked.', () => {
// Regular inline link.
const url = 'https://bugs.chromium.org/p/gerrit/issues/detail?id=3650';
element.content = url;
- await element.updateComplete;
-
- const linkEl = queryAndAssert(element, 'span#output')
+ const linkEl = queryAndAssert(element, '#output')
.childNodes[0] as HTMLAnchorElement;
assert.equal(linkEl.target, '_blank');
assert.equal(linkEl.rel, 'noopener');
@@ -99,12 +97,11 @@
assert.equal(linkEl.textContent, url);
});
- test('Bug pattern was parsed and linked', async () => {
+ test('Bug pattern was parsed and linked', () => {
// "Issue/Bug" pattern.
element.content = 'Issue 3650';
- await element.updateComplete;
- let linkEl = queryAndAssert(element, 'span#output')
+ let linkEl = queryAndAssert(element, '#output')
.childNodes[0] as HTMLAnchorElement;
const url = 'https://bugs.chromium.org/p/gerrit/issues/detail?id=3650';
assert.equal(linkEl.target, '_blank');
@@ -112,9 +109,7 @@
assert.equal(linkEl.textContent, 'Issue 3650');
element.content = 'Bug 3650';
- await element.updateComplete;
-
- linkEl = queryAndAssert(element, 'span#output')
+ linkEl = queryAndAssert(element, '#output')
.childNodes[0] as HTMLAnchorElement;
assert.equal(linkEl.target, '_blank');
assert.equal(linkEl.rel, 'noopener');
@@ -122,13 +117,12 @@
assert.equal(linkEl.textContent, 'Bug 3650');
});
- test('Pattern with same prefix as link was correctly parsed', async () => {
+ test('Pattern with same prefix as link was correctly parsed', () => {
// Pattern starts with the same prefix (`http`) as the url.
element.content = 'httpexample 3650';
- await element.updateComplete;
- assert.equal(queryAndAssert(element, 'span#output').childNodes.length, 1);
- const linkEl = queryAndAssert(element, 'span#output')
+ assert.equal(queryAndAssert(element, '#output').childNodes.length, 1);
+ const linkEl = queryAndAssert(element, '#output')
.childNodes[0] as HTMLAnchorElement;
const url = 'https://bugs.chromium.org/p/gerrit/issues/detail?id=3650';
assert.equal(linkEl.target, '_blank');
@@ -136,15 +130,14 @@
assert.equal(linkEl.textContent, 'httpexample 3650');
});
- test('Change-Id pattern was parsed and linked', async () => {
+ test('Change-Id pattern was parsed and linked', () => {
// "Change-Id:" pattern.
const changeID = 'I11d6a37f5e9b5df0486f6c922d8836dfa780e03e';
const prefix = 'Change-Id: ';
element.content = prefix + changeID;
- await element.updateComplete;
- const textNode = queryAndAssert(element, 'span#output').childNodes[0];
- const linkEl = queryAndAssert(element, 'span#output')
+ const textNode = queryAndAssert(element, '#output').childNodes[0];
+ const linkEl = queryAndAssert(element, '#output')
.childNodes[1] as HTMLAnchorElement;
assert.equal(textNode.textContent, prefix);
const url = '/q/' + changeID;
@@ -154,17 +147,16 @@
assert.equal(linkEl.textContent, changeID);
});
- test('Change-Id pattern was parsed and linked with base url', async () => {
+ test('Change-Id pattern was parsed and linked with base url', () => {
window.CANONICAL_PATH = '/r';
// "Change-Id:" pattern.
const changeID = 'I11d6a37f5e9b5df0486f6c922d8836dfa780e03e';
const prefix = 'Change-Id: ';
element.content = prefix + changeID;
- await element.updateComplete;
- const textNode = queryAndAssert(element, 'span#output').childNodes[0];
- const linkEl = queryAndAssert(element, 'span#output')
+ const textNode = queryAndAssert(element, '#output').childNodes[0];
+ const linkEl = queryAndAssert(element, '#output')
.childNodes[1] as HTMLAnchorElement;
assert.equal(textNode.textContent, prefix);
const url = '/r/q/' + changeID;
@@ -174,13 +166,11 @@
assert.equal(linkEl.textContent, changeID);
});
- test('Multiple matches', async () => {
+ test('Multiple matches', () => {
element.content = 'Issue 3650\nIssue 3450';
- await element.updateComplete;
-
- const linkEl1 = queryAndAssert(element, 'span#output')
+ const linkEl1 = queryAndAssert(element, '#output')
.childNodes[0] as HTMLAnchorElement;
- const linkEl2 = queryAndAssert(element, 'span#output')
+ const linkEl2 = queryAndAssert(element, '#output')
.childNodes[2] as HTMLAnchorElement;
assert.equal(linkEl1.target, '_blank');
@@ -198,7 +188,7 @@
assert.equal(linkEl2.textContent, 'Issue 3450');
});
- test('Change-Id pattern parsed before bug pattern', async () => {
+ test('Change-Id pattern parsed before bug pattern', () => {
// "Change-Id:" pattern.
const changeID = 'I11d6a37f5e9b5df0486f6c922d8836dfa780e03e';
const prefix = 'Change-Id: ';
@@ -210,12 +200,11 @@
const bugUrl = 'https://bugs.chromium.org/p/gerrit/issues/detail?id=3650';
element.content = prefix + changeID + bug;
- await element.updateComplete;
- const textNode = queryAndAssert(element, 'span#output').childNodes[0];
- const changeLinkEl = queryAndAssert(element, 'span#output')
+ const textNode = queryAndAssert(element, '#output').childNodes[0];
+ const changeLinkEl = queryAndAssert(element, '#output')
.childNodes[1] as HTMLAnchorElement;
- const bugLinkEl = queryAndAssert(element, 'span#output')
+ const bugLinkEl = queryAndAssert(element, '#output')
.childNodes[2] as HTMLAnchorElement;
assert.equal(textNode.textContent, prefix);
@@ -229,11 +218,9 @@
assert.equal(bugLinkEl.textContent, 'Issue 3650');
});
- test('html field in link config', async () => {
+ test('html field in link config', () => {
element.content = 'google:do a barrel roll';
- await element.updateComplete;
-
- const linkEl = queryAndAssert(element, 'span#output')
+ const linkEl = queryAndAssert(element, '#output')
.childNodes[0] as HTMLAnchorElement;
assert.equal(
linkEl.getAttribute('href'),
@@ -242,192 +229,155 @@
assert.equal(linkEl.textContent, 'do a barrel roll');
});
- test('removing hash from links', async () => {
+ test('removing hash from links', () => {
element.content = 'hash:foo';
- await element.updateComplete;
-
- const linkEl = queryAndAssert(element, 'span#output')
+ const linkEl = queryAndAssert(element, '#output')
.childNodes[0] as HTMLAnchorElement;
assert.isTrue(linkEl.href.endsWith('/awesomesauce'));
assert.equal(linkEl.textContent, 'foo');
});
- test('html with base url', async () => {
+ test('html with base url', () => {
window.CANONICAL_PATH = '/r';
element.content = 'test foo';
- await element.updateComplete;
-
- const linkEl = queryAndAssert(element, 'span#output')
+ const linkEl = queryAndAssert(element, '#output')
.childNodes[0] as HTMLAnchorElement;
assert.isTrue(linkEl.href.endsWith('/r/awesomesauce'));
assert.equal(linkEl.textContent, 'foo');
});
- test('a is not at start', async () => {
+ test('a is not at start', () => {
window.CANONICAL_PATH = '/r';
element.content = 'a test foo';
- await element.updateComplete;
-
- const linkEl = queryAndAssert(element, 'span#output')
+ const linkEl = queryAndAssert(element, '#output')
.childNodes[1] as HTMLAnchorElement;
assert.isTrue(linkEl.href.endsWith('/r/awesomesauce'));
assert.equal(linkEl.textContent, 'foo');
});
- test('hash html with base url', async () => {
+ test('hash html with base url', () => {
window.CANONICAL_PATH = '/r';
element.content = 'hash:foo';
- await element.updateComplete;
-
- const linkEl = queryAndAssert(element, 'span#output')
+ const linkEl = queryAndAssert(element, '#output')
.childNodes[0] as HTMLAnchorElement;
assert.isTrue(linkEl.href.endsWith('/r/awesomesauce'));
assert.equal(linkEl.textContent, 'foo');
});
- test('disabled config', async () => {
+ test('disabled config', () => {
element.content = 'foo:baz';
- await element.updateComplete;
-
- assert.equal(queryAndAssert(element, 'span#output').innerHTML, 'foo:baz');
+ assert.equal(queryAndAssert(element, '#output').innerHTML, 'foo:baz');
});
- test('R=email labels link correctly', async () => {
+ test('R=email labels link correctly', () => {
element.removeZeroWidthSpace = true;
element.content = 'R=\u200Btest@google.com';
- await element.updateComplete;
-
assert.equal(
- queryAndAssert(element, 'span#output').textContent,
+ queryAndAssert(element, '#output').textContent,
'R=test@google.com'
);
assert.equal(
- queryAndAssert(element, 'span#output').innerHTML.match(/(R=<a)/g)!.length,
+ queryAndAssert(element, '#output').innerHTML.match(/(R=<a)/g)!.length,
1
);
});
- test('CC=email labels link correctly', async () => {
+ test('CC=email labels link correctly', () => {
element.removeZeroWidthSpace = true;
element.content = 'CC=\u200Btest@google.com';
- await element.updateComplete;
-
assert.equal(
- queryAndAssert(element, 'span#output').textContent,
+ queryAndAssert(element, '#output').textContent,
'CC=test@google.com'
);
assert.equal(
- queryAndAssert(element, 'span#output').innerHTML.match(/(CC=<a)/g)!
- .length,
+ queryAndAssert(element, '#output').innerHTML.match(/(CC=<a)/g)!.length,
1
);
});
- test('only {http,https,mailto} protocols are linkified', async () => {
+ test('only {http,https,mailto} protocols are linkified', () => {
element.content = 'xx mailto:test@google.com yy';
- await element.updateComplete;
-
- let links = queryAndAssert(element, 'span#output').querySelectorAll('a');
+ let links = queryAndAssert(element, '#output').querySelectorAll('a');
assert.equal(links.length, 1);
assert.equal(links[0].getAttribute('href'), 'mailto:test@google.com');
assert.equal(links[0].innerHTML, 'mailto:test@google.com');
element.content = 'xx http://google.com yy';
- await element.updateComplete;
-
- links = queryAndAssert(element, 'span#output').querySelectorAll('a');
+ links = queryAndAssert(element, '#output').querySelectorAll('a');
assert.equal(links.length, 1);
assert.equal(links[0].getAttribute('href'), 'http://google.com');
assert.equal(links[0].innerHTML, 'http://google.com');
element.content = 'xx https://google.com yy';
- await element.updateComplete;
-
- links = queryAndAssert(element, 'span#output').querySelectorAll('a');
+ links = queryAndAssert(element, '#output').querySelectorAll('a');
assert.equal(links.length, 1);
assert.equal(links[0].getAttribute('href'), 'https://google.com');
assert.equal(links[0].innerHTML, 'https://google.com');
element.content = 'xx ssh://google.com yy';
- await element.updateComplete;
-
- links = queryAndAssert(element, 'span#output').querySelectorAll('a');
+ links = queryAndAssert(element, '#output').querySelectorAll('a');
assert.equal(links.length, 0);
element.content = 'xx ftp://google.com yy';
- await element.updateComplete;
-
- links = queryAndAssert(element, 'span#output').querySelectorAll('a');
+ links = queryAndAssert(element, '#output').querySelectorAll('a');
assert.equal(links.length, 0);
});
- test('links without leading whitespace are linkified', async () => {
+ test('links without leading whitespace are linkified', () => {
element.content = 'xx abcmailto:test@google.com yy';
- await element.updateComplete;
-
assert.equal(
- queryAndAssert(element, 'span#output').innerHTML.substr(0, 6),
+ queryAndAssert(element, '#output').innerHTML.substr(0, 6),
'xx abc'
);
- let links = queryAndAssert(element, 'span#output').querySelectorAll('a');
+ let links = queryAndAssert(element, '#output').querySelectorAll('a');
assert.equal(links.length, 1);
assert.equal(links[0].getAttribute('href'), 'mailto:test@google.com');
assert.equal(links[0].innerHTML, 'mailto:test@google.com');
element.content = 'xx defhttp://google.com yy';
- await element.updateComplete;
-
assert.equal(
- queryAndAssert(element, 'span#output').innerHTML.substr(0, 6),
+ queryAndAssert(element, '#output').innerHTML.substr(0, 6),
'xx def'
);
- links = queryAndAssert(element, 'span#output').querySelectorAll('a');
+ links = queryAndAssert(element, '#output').querySelectorAll('a');
assert.equal(links.length, 1);
assert.equal(links[0].getAttribute('href'), 'http://google.com');
assert.equal(links[0].innerHTML, 'http://google.com');
element.content = 'xx qwehttps://google.com yy';
- await element.updateComplete;
-
assert.equal(
- queryAndAssert(element, 'span#output').innerHTML.substr(0, 6),
+ queryAndAssert(element, '#output').innerHTML.substr(0, 6),
'xx qwe'
);
- links = queryAndAssert(element, 'span#output').querySelectorAll('a');
+ links = queryAndAssert(element, '#output').querySelectorAll('a');
assert.equal(links.length, 1);
assert.equal(links[0].getAttribute('href'), 'https://google.com');
assert.equal(links[0].innerHTML, 'https://google.com');
// Non-latin character
element.content = 'xx абвhttps://google.com yy';
- await element.updateComplete;
-
assert.equal(
- queryAndAssert(element, 'span#output').innerHTML.substr(0, 6),
+ queryAndAssert(element, '#output').innerHTML.substr(0, 6),
'xx абв'
);
- links = queryAndAssert(element, 'span#output').querySelectorAll('a');
+ links = queryAndAssert(element, '#output').querySelectorAll('a');
assert.equal(links.length, 1);
assert.equal(links[0].getAttribute('href'), 'https://google.com');
assert.equal(links[0].innerHTML, 'https://google.com');
element.content = 'xx ssh://google.com yy';
- await element.updateComplete;
-
- links = queryAndAssert(element, 'span#output').querySelectorAll('a');
+ links = queryAndAssert(element, '#output').querySelectorAll('a');
assert.equal(links.length, 0);
element.content = 'xx ftp://google.com yy';
- await element.updateComplete;
-
- links = queryAndAssert(element, 'span#output').querySelectorAll('a');
+ links = queryAndAssert(element, '#output').querySelectorAll('a');
assert.equal(links.length, 0);
});
- test('overlapping links', async () => {
+ test('overlapping links', () => {
element.config = {
b1: {
match: '(B:\\s*)(\\d+)',
@@ -439,9 +389,7 @@
},
};
element.content = '- B: 123, 45';
- await element.updateComplete;
-
- const links = element.querySelectorAll('a');
+ const links = element.root!.querySelectorAll('a');
assert.equal(links.length, 2);
assert.equal(
@@ -456,11 +404,11 @@
assert.equal(links[1].textContent, '45');
});
- test('_contentOrConfigChanged called with config', async () => {
+ test('_contentOrConfigChanged called with config', () => {
+ const contentStub = sinon.stub(element, '_contentChanged');
const contentConfigStub = sinon.stub(element, '_contentOrConfigChanged');
element.content = 'some text';
- await element.updateComplete;
-
+ assert.isTrue(contentStub.called);
assert.isTrue(contentConfigStub.called);
});
});