Merge "Fix template problems with gr-confirm-delete-comment-dialog"
diff --git a/java/com/google/gerrit/acceptance/AbstractDaemonTest.java b/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
index 7ddf2ba..f476566 100644
--- a/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
+++ b/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
@@ -369,11 +369,11 @@
if (commonServer != null) {
try {
commonServer.close();
- } catch (Throwable t) {
+ } catch (Exception e) {
throw new AssertionError(
"Error stopping common server in "
+ (firstTest != null ? firstTest.getTestClass().getName() : "unknown test class"),
- t);
+ e);
} finally {
commonServer = null;
}
diff --git a/java/com/google/gerrit/entities/Change.java b/java/com/google/gerrit/entities/Change.java
index ca13db9..d1826bc 100644
--- a/java/com/google/gerrit/entities/Change.java
+++ b/java/com/google/gerrit/entities/Change.java
@@ -443,9 +443,6 @@
/** Globally assigned unique identifier of the change */
protected Key changeKey;
- /** optimistic locking */
- protected int rowVersion;
-
/** When this change was first introduced into the database. */
protected Timestamp createdOn;
@@ -526,7 +523,6 @@
assignee = other.assignee;
changeId = other.changeId;
changeKey = other.changeKey;
- rowVersion = other.rowVersion;
createdOn = other.createdOn;
lastUpdatedOn = other.lastUpdatedOn;
owner = other.owner;
@@ -587,10 +583,6 @@
lastUpdatedOn = now;
}
- public int getRowVersion() {
- return rowVersion;
- }
-
public Account.Id getOwner() {
return owner;
}
diff --git a/java/com/google/gerrit/entities/converter/ChangeProtoConverter.java b/java/com/google/gerrit/entities/converter/ChangeProtoConverter.java
index 25e68f9..689b4aa 100644
--- a/java/com/google/gerrit/entities/converter/ChangeProtoConverter.java
+++ b/java/com/google/gerrit/entities/converter/ChangeProtoConverter.java
@@ -43,7 +43,6 @@
Entities.Change.Builder builder =
Entities.Change.newBuilder()
.setChangeId(changeIdConverter.toProto(change.getId()))
- .setRowVersion(change.getRowVersion())
.setChangeKey(changeKeyConverter.toProto(change.getKey()))
.setCreatedOn(change.getCreatedOn().getTime())
.setLastUpdatedOn(change.getLastUpdatedOn().getTime())
diff --git a/java/com/google/gerrit/index/testing/AbstractFakeIndex.java b/java/com/google/gerrit/index/testing/AbstractFakeIndex.java
index 5cc8e3c..b727e96 100644
--- a/java/com/google/gerrit/index/testing/AbstractFakeIndex.java
+++ b/java/com/google/gerrit/index/testing/AbstractFakeIndex.java
@@ -36,6 +36,8 @@
import com.google.gerrit.index.query.Predicate;
import com.google.gerrit.index.query.ResultSet;
import com.google.gerrit.server.account.AccountState;
+import com.google.gerrit.server.change.MergeabilityComputationBehavior;
+import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.index.IndexUtils;
import com.google.gerrit.server.index.account.AccountIndex;
@@ -50,6 +52,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import org.eclipse.jgit.lib.Config;
/**
* Fake secondary index implementation for usage in tests. All values are kept in-memory.
@@ -179,14 +182,17 @@
public static class FakeChangeIndex
extends AbstractFakeIndex<Change.Id, ChangeData, Map<String, Object>> implements ChangeIndex {
private final ChangeData.Factory changeDataFactory;
+ private final boolean skipMergable;
@Inject
FakeChangeIndex(
SitePaths sitePaths,
ChangeData.Factory changeDataFactory,
- @Assisted Schema<ChangeData> schema) {
+ @Assisted Schema<ChangeData> schema,
+ @GerritServerConfig Config cfg) {
super(schema, sitePaths, "changes");
this.changeDataFactory = changeDataFactory;
+ this.skipMergable = !MergeabilityComputationBehavior.fromConfig(cfg).includeInIndex();
}
@Override
@@ -208,6 +214,9 @@
protected Map<String, Object> docFor(ChangeData value) {
ImmutableMap.Builder<String, Object> doc = ImmutableMap.builder();
for (FieldDef<ChangeData, ?> field : getSchema().getFields().values()) {
+ if (ChangeField.MERGEABLE.getName().equals(field.getName()) && skipMergable) {
+ continue;
+ }
Object docifiedValue = field.get(value);
if (docifiedValue != null) {
doc.put(field.getName(), field.get(value));
diff --git a/java/com/google/gerrit/lifecycle/LifecycleManager.java b/java/com/google/gerrit/lifecycle/LifecycleManager.java
index 4f09a09..42123d7 100644
--- a/java/com/google/gerrit/lifecycle/LifecycleManager.java
+++ b/java/com/google/gerrit/lifecycle/LifecycleManager.java
@@ -107,7 +107,7 @@
LifecycleListener obj = listeners.get(i).get();
try {
obj.stop();
- } catch (Throwable err) {
+ } catch (RuntimeException err) {
logger.atWarning().withCause(err).log("Failed to stop %s", obj.getClass());
}
startedIndex = i - 1;
diff --git a/java/com/google/gerrit/pgm/Daemon.java b/java/com/google/gerrit/pgm/Daemon.java
index 2b4cfef..a3605f7 100644
--- a/java/com/google/gerrit/pgm/Daemon.java
+++ b/java/com/google/gerrit/pgm/Daemon.java
@@ -310,7 +310,7 @@
RuntimeShutdown.waitFor();
}
return 0;
- } catch (Throwable err) {
+ } catch (RuntimeException err) {
logger.atSevere().withCause(err).log("Unable to start daemon");
return 1;
}
diff --git a/java/com/google/gerrit/pgm/http/jetty/JettyServer.java b/java/com/google/gerrit/pgm/http/jetty/JettyServer.java
index 89b4228..6f3514f 100644
--- a/java/com/google/gerrit/pgm/http/jetty/JettyServer.java
+++ b/java/com/google/gerrit/pgm/http/jetty/JettyServer.java
@@ -547,7 +547,7 @@
filterHolder.setInitParameters(initParams);
}
app.addFilter(filterHolder, "/*", EnumSet.of(DispatcherType.REQUEST, DispatcherType.ASYNC));
- } catch (Throwable e) {
+ } catch (Exception e) {
throw new IllegalArgumentException(
"Unable to instantiate front-end HTTP Filter " + filterClassName, e);
}
diff --git a/java/com/google/gerrit/server/RequestCleanup.java b/java/com/google/gerrit/server/RequestCleanup.java
index f405c57..e07d148 100644
--- a/java/com/google/gerrit/server/RequestCleanup.java
+++ b/java/com/google/gerrit/server/RequestCleanup.java
@@ -44,7 +44,7 @@
for (Iterator<Runnable> i = cleanup.iterator(); i.hasNext(); ) {
try {
i.next().run();
- } catch (Throwable err) {
+ } catch (Exception err) {
logger.atSevere().withCause(err).log("Failed to execute per-request cleanup");
}
i.remove();
diff --git a/java/com/google/gerrit/server/change/AbandonUtil.java b/java/com/google/gerrit/server/change/AbandonUtil.java
index 1bc1fad..d030ec1 100644
--- a/java/com/google/gerrit/server/change/AbandonUtil.java
+++ b/java/com/google/gerrit/server/change/AbandonUtil.java
@@ -93,7 +93,7 @@
try {
batchAbandon.batchAbandon(updateFactory, project, internalUser, changes, message);
count += changes.size();
- } catch (Throwable e) {
+ } catch (Exception e) {
StringBuilder msg = new StringBuilder("Failed to auto-abandon inactive change(s):");
for (ChangeData change : changes) {
msg.append(" ").append(change.getId().get());
diff --git a/java/com/google/gerrit/server/change/ChangeResource.java b/java/com/google/gerrit/server/change/ChangeResource.java
index 27b71d6..0d0df0d 100644
--- a/java/com/google/gerrit/server/change/ChangeResource.java
+++ b/java/com/google/gerrit/server/change/ChangeResource.java
@@ -167,7 +167,6 @@
public void prepareETag(Hasher h, CurrentUser user) {
h.putInt(JSON_FORMAT_VERSION)
.putLong(getChange().getLastUpdatedOn().getTime())
- .putInt(getChange().getRowVersion())
.putInt(user.isIdentifiedUser() ? user.getAccountId().get() : 0);
if (user.isIdentifiedUser()) {
diff --git a/java/com/google/gerrit/server/group/PeriodicGroupIndexer.java b/java/com/google/gerrit/server/group/PeriodicGroupIndexer.java
index cae213f..2823548 100644
--- a/java/com/google/gerrit/server/group/PeriodicGroupIndexer.java
+++ b/java/com/google/gerrit/server/group/PeriodicGroupIndexer.java
@@ -145,7 +145,7 @@
}
groupUuids = newGroupUuids;
logger.atInfo().log("Run group indexer, %s groups reindexed", reindexCounter);
- } catch (Throwable t) {
+ } catch (Exception t) {
logger.atSevere().withCause(t).log("Failed to reindex groups");
}
}
diff --git a/java/com/google/gerrit/server/logging/PerformanceLogContext.java b/java/com/google/gerrit/server/logging/PerformanceLogContext.java
index b6dafdc..65e033b15 100644
--- a/java/com/google/gerrit/server/logging/PerformanceLogContext.java
+++ b/java/com/google/gerrit/server/logging/PerformanceLogContext.java
@@ -92,7 +92,7 @@
p -> {
try (TraceContext traceContext = newPluginTrace(p)) {
performanceLogRecords.forEach(r -> r.writeTo(p.get()));
- } catch (Throwable e) {
+ } catch (RuntimeException e) {
logger.atWarning().withCause(e).log(
"Failure in %s of plugin %s", p.get().getClass(), p.getPluginName());
}
diff --git a/java/com/google/gerrit/server/plugincontext/PluginContext.java b/java/com/google/gerrit/server/plugincontext/PluginContext.java
index 90d56c8..1cfee65 100644
--- a/java/com/google/gerrit/server/plugincontext/PluginContext.java
+++ b/java/com/google/gerrit/server/plugincontext/PluginContext.java
@@ -204,7 +204,7 @@
try (TraceContext traceContext = newTrace(extension);
Timer3.Context<String, String, String> ctx = pluginMetrics.startLatency(extension)) {
extensionImplConsumer.run(extensionImpl);
- } catch (Throwable e) {
+ } catch (Exception e) {
pluginMetrics.incrementErrorCount(extension);
logger.atWarning().withCause(e).log(
"Failure in %s of plugin %s", extensionImpl.getClass(), extension.getPluginName());
@@ -233,7 +233,7 @@
try (TraceContext traceContext = newTrace(extension);
Timer3.Context<String, String, String> ctx = pluginMetrics.startLatency(extension)) {
extensionConsumer.run(extension);
- } catch (Throwable e) {
+ } catch (Exception e) {
pluginMetrics.incrementErrorCount(extension);
logger.atWarning().withCause(e).log(
"Failure in %s of plugin %s", extensionImpl.getClass(), extension.getPluginName());
@@ -267,7 +267,7 @@
try (TraceContext traceContext = newTrace(extension);
Timer3.Context<String, String, String> ctx = pluginMetrics.startLatency(extension)) {
extensionImplConsumer.run(extensionImpl);
- } catch (Throwable e) {
+ } catch (Exception e) {
Throwables.throwIfInstanceOf(e, exceptionClass);
Throwables.throwIfUnchecked(e);
pluginMetrics.incrementErrorCount(extension);
@@ -304,7 +304,7 @@
try (TraceContext traceContext = newTrace(extension);
Timer3.Context<String, String, String> ctx = pluginMetrics.startLatency(extension)) {
extensionConsumer.run(extension);
- } catch (Throwable e) {
+ } catch (Exception e) {
Throwables.throwIfInstanceOf(e, exceptionClass);
Throwables.throwIfUnchecked(e);
pluginMetrics.incrementErrorCount(extension);
diff --git a/java/com/google/gerrit/server/plugins/PluginLoader.java b/java/com/google/gerrit/server/plugins/PluginLoader.java
index 0a06081..8d17d85 100644
--- a/java/com/google/gerrit/server/plugins/PluginLoader.java
+++ b/java/com/google/gerrit/server/plugins/PluginLoader.java
@@ -253,7 +253,7 @@
FileSnapshot snapshot = FileSnapshot.save(off.toFile());
Plugin offPlugin = loadPlugin(name, off, snapshot);
disabled.put(name, offPlugin);
- } catch (Throwable e) {
+ } catch (Exception e) {
// This shouldn't happen, as the plugin was loaded earlier.
logger.atWarning().withCause(e.getCause()).log(
"Cannot load disabled plugin %s", active.getName());
@@ -510,7 +510,7 @@
if (!newPlugin.isDisabled()) {
try {
newPlugin.start(env);
- } catch (Throwable e) {
+ } catch (Exception e) {
newPlugin.stop(env);
throw e;
}
@@ -528,7 +528,7 @@
}
broken.remove(name);
return newPlugin;
- } catch (Throwable err) {
+ } catch (Exception err) {
broken.put(name, snapshot);
throw new PluginInstallException(err);
}
diff --git a/java/com/google/gerrit/server/query/change/ChangeData.java b/java/com/google/gerrit/server/query/change/ChangeData.java
index 852387f..8b4e4c7 100644
--- a/java/com/google/gerrit/server/query/change/ChangeData.java
+++ b/java/com/google/gerrit/server/query/change/ChangeData.java
@@ -38,7 +38,6 @@
import com.google.common.flogger.FluentLogger;
import com.google.common.primitives.Ints;
import com.google.gerrit.common.Nullable;
-import com.google.gerrit.common.UsedAt;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.AttentionSetUpdate;
import com.google.gerrit.entities.Change;
@@ -1287,12 +1286,6 @@
return refStates;
}
- @UsedAt(UsedAt.Project.GOOGLE)
- public void setRefStates(Iterable<byte[]> refStates) {
- // TODO(hanwen): remove Google use, and drop this method.
- setRefStates(RefState.parseStates(refStates));
- }
-
public void setRefStates(ImmutableSetMultimap<Project.NameKey, RefState> refStates) {
this.refStates = refStates;
if (draftsByUser == null) {
diff --git a/java/com/google/gerrit/server/rules/PrologEnvironment.java b/java/com/google/gerrit/server/rules/PrologEnvironment.java
index 1a563ad..7d626da 100644
--- a/java/com/google/gerrit/server/rules/PrologEnvironment.java
+++ b/java/com/google/gerrit/server/rules/PrologEnvironment.java
@@ -143,7 +143,7 @@
for (Iterator<Runnable> i = cleanup.iterator(); i.hasNext(); ) {
try {
i.next().run();
- } catch (Throwable err) {
+ } catch (Exception err) {
logger.atSevere().withCause(err).log("Failed to execute cleanup for PrologEnvironment");
}
i.remove();
diff --git a/java/com/google/gerrit/server/update/RetryableChangeAction.java b/java/com/google/gerrit/server/update/RetryableChangeAction.java
index 152db2c..84ec2bb 100644
--- a/java/com/google/gerrit/server/update/RetryableChangeAction.java
+++ b/java/com/google/gerrit/server/update/RetryableChangeAction.java
@@ -82,11 +82,11 @@
public T call() throws UpdateException, RestApiException {
try {
return super.call();
- } catch (Throwable t) {
- Throwables.throwIfUnchecked(t);
- Throwables.throwIfInstanceOf(t, UpdateException.class);
- Throwables.throwIfInstanceOf(t, RestApiException.class);
- throw new UpdateException(t);
+ } catch (Exception e) {
+ Throwables.throwIfUnchecked(e);
+ Throwables.throwIfInstanceOf(e, UpdateException.class);
+ Throwables.throwIfInstanceOf(e, RestApiException.class);
+ throw new UpdateException(e);
}
}
}
diff --git a/java/com/google/gerrit/server/update/RetryableIndexQueryAction.java b/java/com/google/gerrit/server/update/RetryableIndexQueryAction.java
index cf733a6..d66edcf 100644
--- a/java/com/google/gerrit/server/update/RetryableIndexQueryAction.java
+++ b/java/com/google/gerrit/server/update/RetryableIndexQueryAction.java
@@ -87,9 +87,9 @@
public T call() {
try {
return super.call();
- } catch (Throwable t) {
- Throwables.throwIfUnchecked(t);
- throw new StorageException(t);
+ } catch (Exception e) {
+ Throwables.throwIfUnchecked(e);
+ throw new StorageException(e);
}
}
}
diff --git a/java/com/google/gerrit/sshd/BaseCommand.java b/java/com/google/gerrit/sshd/BaseCommand.java
index 48a5512..42aabfb 100644
--- a/java/com/google/gerrit/sshd/BaseCommand.java
+++ b/java/com/google/gerrit/sshd/BaseCommand.java
@@ -370,7 +370,7 @@
err.flush();
} catch (IOException e2) {
// Ignored
- } catch (Throwable e2) {
+ } catch (RuntimeException e2) {
logger.atWarning().withCause(e2).log("Cannot send failure message to client");
}
return f.exitCode;
@@ -381,7 +381,7 @@
err.flush();
} catch (IOException e2) {
// Ignored
- } catch (Throwable e2) {
+ } catch (RuntimeException e2) {
logger.atWarning().withCause(e2).log("Cannot send internal server error message to client");
}
return 128;
@@ -500,15 +500,15 @@
out.flush();
err.flush();
- } catch (Throwable e) {
+ } catch (Exception e) {
try {
out.flush();
- } catch (Throwable e2) {
+ } catch (Exception e2) {
// Ignored
}
try {
err.flush();
- } catch (Throwable e2) {
+ } catch (Exception e2) {
// Ignored
}
rc = handleError(e);
diff --git a/java/com/google/gerrit/sshd/SshKeyCacheImpl.java b/java/com/google/gerrit/sshd/SshKeyCacheImpl.java
index 773c25b..d5f0ee8 100644
--- a/java/com/google/gerrit/sshd/SshKeyCacheImpl.java
+++ b/java/com/google/gerrit/sshd/SshKeyCacheImpl.java
@@ -138,7 +138,7 @@
// to do with the key object, and instead we must abort this load.
//
throw e;
- } catch (Throwable e) {
+ } catch (Exception e) {
markInvalid(k);
}
}
diff --git a/java/com/google/gerrit/sshd/commands/UploadArchive.java b/java/com/google/gerrit/sshd/commands/UploadArchive.java
index 0eda433..c1f4a7b 100644
--- a/java/com/google/gerrit/sshd/commands/UploadArchive.java
+++ b/java/com/google/gerrit/sshd/commands/UploadArchive.java
@@ -214,16 +214,16 @@
} catch (GitAPIException e) {
throw new Failure(7, "fatal: git api exception, " + e);
}
- } catch (Throwable t) {
+ } catch (Exception e) {
// Report the error in ERROR sideband channel. Catch Throwable too so we can also catch
// NoClassDefFound.
try (SideBandOutputStream sidebandError =
new SideBandOutputStream(
SideBandOutputStream.CH_ERROR, SideBandOutputStream.MAX_BUF, out)) {
- sidebandError.write(t.getMessage().getBytes(UTF_8));
+ sidebandError.write(e.getMessage().getBytes(UTF_8));
sidebandError.flush();
}
- throw t;
+ throw e;
} finally {
// In any case, cleanly close the packetOut channel
packetOut.end();
diff --git a/javatests/com/google/gerrit/entities/converter/ChangeProtoConverterTest.java b/javatests/com/google/gerrit/entities/converter/ChangeProtoConverterTest.java
index ae8e06d..8c5e449 100644
--- a/javatests/com/google/gerrit/entities/converter/ChangeProtoConverterTest.java
+++ b/javatests/com/google/gerrit/entities/converter/ChangeProtoConverterTest.java
@@ -60,7 +60,6 @@
Entities.Change.newBuilder()
.setChangeId(Entities.Change_Id.newBuilder().setId(14))
.setChangeKey(Entities.Change_Key.newBuilder().setId("change 1"))
- .setRowVersion(0)
.setCreatedOn(987654L)
.setLastUpdatedOn(1234567L)
.setOwnerAccountId(Entities.Account_Id.newBuilder().setId(35))
@@ -109,7 +108,6 @@
.setBranch("refs/heads/branch-74"))
// Default values which can't be unset.
.setCurrentPatchSetId(0)
- .setRowVersion(0)
.setStatus(Change.STATUS_NEW)
.setIsPrivate(false)
.setWorkInProgress(false)
@@ -147,7 +145,6 @@
.setBranch("refs/heads/branch-74"))
.setCurrentPatchSetId(0)
// Default values which can't be unset.
- .setRowVersion(0)
.setStatus(Change.STATUS_NEW)
.setIsPrivate(false)
.setWorkInProgress(false)
@@ -185,7 +182,6 @@
.setCurrentPatchSetId(23)
.setSubject("subject ABC")
// Default values which can't be unset.
- .setRowVersion(0)
.setStatus(Change.STATUS_NEW)
.setIsPrivate(false)
.setWorkInProgress(false)
@@ -251,7 +247,6 @@
assertThat(change.getSubject()).isNull();
assertThat(change.currentPatchSetId()).isNull();
// Default values for unset protobuf fields which can't be unset in the entity object.
- assertThat(change.getRowVersion()).isEqualTo(0);
assertThat(change.isNew()).isTrue();
assertThat(change.isPrivate()).isFalse();
assertThat(change.isWorkInProgress()).isFalse();
@@ -284,7 +279,6 @@
ImmutableMap.<String, Type>builder()
.put("changeId", Change.Id.class)
.put("changeKey", Change.Key.class)
- .put("rowVersion", int.class)
.put("createdOn", Timestamp.class)
.put("lastUpdatedOn", Timestamp.class)
.put("owner", Account.Id.class)
@@ -309,7 +303,6 @@
private static void assertEqualChange(Change change, Change expectedChange) {
assertThat(change.getChangeId()).isEqualTo(expectedChange.getChangeId());
assertThat(change.getKey()).isEqualTo(expectedChange.getKey());
- assertThat(change.getRowVersion()).isEqualTo(expectedChange.getRowVersion());
assertThat(change.getCreatedOn()).isEqualTo(expectedChange.getCreatedOn());
assertThat(change.getLastUpdatedOn()).isEqualTo(expectedChange.getLastUpdatedOn());
assertThat(change.getOwner()).isEqualTo(expectedChange.getOwner());
diff --git a/javatests/com/google/gerrit/server/update/BatchUpdateTest.java b/javatests/com/google/gerrit/server/update/BatchUpdateTest.java
index 287a7fe..10599c6 100644
--- a/javatests/com/google/gerrit/server/update/BatchUpdateTest.java
+++ b/javatests/com/google/gerrit/server/update/BatchUpdateTest.java
@@ -64,6 +64,7 @@
cfg.setInt("change", null, "maxFiles", 2);
cfg.setInt("change", null, "maxPatchSets", MAX_PATCH_SETS);
cfg.setInt("change", null, "maxUpdates", MAX_UPDATES);
+ cfg.setString("index", null, "type", "fake");
return cfg;
});
diff --git a/polygerrit-ui/app/BUILD b/polygerrit-ui/app/BUILD
index d685bab..6a64126 100644
--- a/polygerrit-ui/app/BUILD
+++ b/polygerrit-ui/app/BUILD
@@ -95,9 +95,6 @@
"elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog_html.ts",
"elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog_html.ts",
"elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog_html.ts",
- "elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_html.ts",
- "elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog_html.ts",
- "elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog_html.ts",
"elements/change/gr-file-list-header/gr-file-list-header_html.ts",
"elements/change/gr-file-list/gr-file-list_html.ts",
"elements/change/gr-label-score-row/gr-label-score-row_html.ts",
@@ -106,7 +103,6 @@
"elements/change/gr-reply-dialog/gr-reply-dialog_html.ts",
"elements/change/gr-reviewer-list/gr-reviewer-list_html.ts",
"elements/change/gr-thread-list/gr-thread-list_html.ts",
- "elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog_html.ts",
"elements/diff/gr-diff-builder/gr-diff-builder-element_html.ts",
"elements/diff/gr-diff-host/gr-diff-host_html.ts",
"elements/diff/gr-diff-mode-selector/gr-diff-mode-selector_html.ts",
@@ -122,7 +118,6 @@
"elements/shared/gr-comment-thread/gr-comment-thread_html.ts",
"elements/shared/gr-comment/gr-comment_html.ts",
"elements/shared/gr-dialog/gr-dialog_html.ts",
- "elements/shared/gr-diff-preferences/gr-diff-preferences_html.ts",
"elements/shared/gr-download-commands/gr-download-commands_html.ts",
"elements/shared/gr-dropdown-list/gr-dropdown-list_html.ts",
"elements/shared/gr-dropdown/gr-dropdown_html.ts",
@@ -131,7 +126,6 @@
"elements/shared/gr-labeled-autocomplete/gr-labeled-autocomplete_html.ts",
"elements/shared/gr-list-view/gr-list-view_html.ts",
"elements/shared/gr-repo-branch-picker/gr-repo-branch-picker_html.ts",
- "elements/shared/gr-textarea/gr-textarea_html.ts",
]
# Transform templates into a .ts files.
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.ts b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.ts
index 4e6c963..77b7717 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.ts
@@ -27,8 +27,9 @@
AutocompleteSuggestion,
} from '../../shared/gr-autocomplete/gr-autocomplete';
import {appContext} from '../../../services/app-context';
+import {GrDialog} from '../../shared/gr-dialog/gr-dialog';
-interface RebaseChange {
+export interface RebaseChange {
name: string;
value: NumericChangeId;
}
@@ -39,10 +40,15 @@
export interface GrConfirmRebaseDialog {
$: {
+ confirmDialog: GrDialog;
parentInput: GrAutocomplete;
+ parentUpToDateMsg: HTMLDivElement;
+ rebaseOnParent: HTMLDivElement;
rebaseOnParentInput: HTMLInputElement;
rebaseOnOtherInput: HTMLInputElement;
+ rebaseOnTip: HTMLDivElement;
rebaseOnTipInput: HTMLInputElement;
+ tipUpToDateMsg: HTMLDivElement;
};
}
@@ -77,10 +83,10 @@
rebaseOnCurrent?: boolean;
@property({type: String})
- _text?: string;
+ _text = '';
@property({type: Object})
- _query?: AutocompleteQuery;
+ _query: AutocompleteQuery = () => Promise.resolve([]);
@property({type: Array})
_recentChanges?: RebaseChange[];
@@ -146,15 +152,15 @@
);
}
- _displayParentOption(rebaseOnCurrent: boolean, hasParent: boolean) {
+ _displayParentOption(rebaseOnCurrent?: boolean, hasParent?: boolean) {
return hasParent && rebaseOnCurrent;
}
- _displayParentUpToDateMsg(rebaseOnCurrent: boolean, hasParent: boolean) {
+ _displayParentUpToDateMsg(rebaseOnCurrent?: boolean, hasParent?: boolean) {
return hasParent && !rebaseOnCurrent;
}
- _displayTipOption(rebaseOnCurrent: boolean, hasParent: boolean) {
+ _displayTipOption(rebaseOnCurrent?: boolean, hasParent?: boolean) {
return !(!rebaseOnCurrent && !hasParent);
}
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_html.ts b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_html.ts
index bed9240..1052201 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_html.ts
@@ -79,7 +79,6 @@
name="rebaseOptions"
type="radio"
disabled$="[[!_displayTipOption(rebaseOnCurrent, hasParent)]]"
- on-click="_handleRebaseOnTip"
/>
<label id="rebaseOnTipLabel" for="rebaseOnTipInput">
Rebase on top of the [[branch]] branch<span hidden$="[[!hasParent]]">
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.js b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.ts
similarity index 67%
rename from polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.js
rename to polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.ts
index 0faf604..74c1b3c 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.js
+++ b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.ts
@@ -15,14 +15,18 @@
* limitations under the License.
*/
-import '../../../test/common-test-setup-karma.js';
-import './gr-confirm-rebase-dialog.js';
-import {stubRestApi} from '../../../test/test-utils.js';
+import '../../../test/common-test-setup-karma';
+import './gr-confirm-rebase-dialog';
+import {GrConfirmRebaseDialog, RebaseChange} from './gr-confirm-rebase-dialog';
+import {stubRestApi} from '../../../test/test-utils';
+import * as MockInteractions from '@polymer/iron-test-helpers/mock-interactions';
+import {NumericChangeId} from '../../../types/common';
+import {createChangeViewChange} from '../../../test/test-data-generators';
const basicFixture = fixtureFromElement('gr-confirm-rebase-dialog');
suite('gr-confirm-rebase-dialog tests', () => {
- let element;
+ let element: GrConfirmRebaseDialog;
setup(() => {
element = basicFixture.instantiate();
@@ -75,16 +79,20 @@
test('input cleared on cancel or submit', () => {
element._text = '123';
element.$.confirmDialog.dispatchEvent(
- new CustomEvent('confirm', {
- composed: true, bubbles: true,
- }));
+ new CustomEvent('confirm', {
+ composed: true,
+ bubbles: true,
+ })
+ );
assert.equal(element._text, '');
element._text = '123';
element.$.confirmDialog.dispatchEvent(
- new CustomEvent('cancel', {
- composed: true, bubbles: true,
- }));
+ new CustomEvent('cancel', {
+ composed: true,
+ bubbles: true,
+ })
+ );
assert.equal(element._text, '');
});
@@ -102,55 +110,59 @@
});
suite('parent suggestions', () => {
- let recentChanges;
- let getChangesStub;
+ let recentChanges: RebaseChange[];
+ let getChangesStub: sinon.SinonStub;
setup(() => {
recentChanges = [
{
name: '123: my first awesome change',
- value: 123,
+ value: 123 as NumericChangeId,
},
{
name: '124: my second awesome change',
- value: 124,
+ value: 124 as NumericChangeId,
},
{
name: '245: my third awesome change',
- value: 245,
+ value: 245 as NumericChangeId,
},
];
- getChangesStub = stubRestApi('getChanges').returns(Promise.resolve(
- [
- {
- _number: 123,
- subject: 'my first awesome change',
- },
- {
- _number: 124,
- subject: 'my second awesome change',
- },
- {
- _number: 245,
- subject: 'my third awesome change',
- },
- ]
- ));
+ getChangesStub = stubRestApi('getChanges').returns(
+ Promise.resolve([
+ {
+ ...createChangeViewChange(),
+ _number: 123 as NumericChangeId,
+ subject: 'my first awesome change',
+ },
+ {
+ ...createChangeViewChange(),
+ _number: 124 as NumericChangeId,
+ subject: 'my second awesome change',
+ },
+ {
+ ...createChangeViewChange(),
+ _number: 245 as NumericChangeId,
+ subject: 'my third awesome change',
+ },
+ ])
+ );
});
test('_getRecentChanges', () => {
- sinon.spy(element, '_getRecentChanges');
- return element._getRecentChanges()
- .then(() => {
- assert.deepEqual(element._recentChanges, recentChanges);
- assert.equal(getChangesStub.callCount, 1);
- // When called a second time, should not re-request recent changes.
- element._getRecentChanges();
- })
- .then(() => {
- assert.equal(element._getRecentChanges.callCount, 2);
- assert.equal(getChangesStub.callCount, 1);
- });
+ const recentChangesSpy = sinon.spy(element, '_getRecentChanges');
+ return element
+ ._getRecentChanges()
+ .then(() => {
+ assert.deepEqual(element._recentChanges, recentChanges);
+ assert.equal(getChangesStub.callCount, 1);
+ // When called a second time, should not re-request recent changes.
+ element._getRecentChanges();
+ })
+ .then(() => {
+ assert.equal(recentChangesSpy.callCount, 2);
+ assert.equal(getChangesStub.callCount, 1);
+ });
});
test('_filterChanges', () => {
@@ -159,25 +171,25 @@
assert.equal(element._filterChanges('awesome', recentChanges).length, 3);
assert.equal(element._filterChanges('third', recentChanges).length, 1);
- element.changeNumber = 123;
+ element.changeNumber = 123 as NumericChangeId;
assert.equal(element._filterChanges('123', recentChanges).length, 0);
assert.equal(element._filterChanges('124', recentChanges).length, 1);
assert.equal(element._filterChanges('awesome', recentChanges).length, 2);
});
test('input text change triggers function', () => {
- sinon.spy(element, '_getRecentChanges');
+ const recentChangesSpy = sinon.spy(element, '_getRecentChanges');
element.$.parentInput.noDebounce = true;
MockInteractions.pressAndReleaseKeyOn(
- element.$.parentInput.$.input,
- 13,
- null,
- 'enter');
+ element.$.parentInput.$.input,
+ 13,
+ null,
+ 'enter'
+ );
element._text = '1';
- assert.isTrue(element._getRecentChanges.calledOnce);
+ assert.isTrue(recentChangesSpy.calledOnce);
element._text = '12';
- assert.isTrue(element._getRecentChanges.calledTwice);
+ assert.isTrue(recentChangesSpy.calledTwice);
});
});
});
-
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.ts b/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.ts
index 450551b..b971039 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.ts
@@ -59,7 +59,7 @@
/* The revert message updated by the user
The default value is set by the dialog */
@property({type: String})
- _message?: string;
+ _message = '';
@property({type: Number})
_revertType = RevertType.REVERT_SINGLE_CHANGE;
@@ -198,7 +198,7 @@
this._message = this._revertMessages[RevertType.REVERT_SUBMISSION];
}
- _handleConfirmTap(e: MouseEvent) {
+ _handleConfirmTap(e: Event) {
e.preventDefault();
e.stopPropagation();
if (this._message === this._originalRevertMessages[this._revertType]) {
@@ -218,7 +218,7 @@
);
}
- _handleCancelTap(e: MouseEvent) {
+ _handleCancelTap(e: Event) {
e.preventDefault();
e.stopPropagation();
this.dispatchEvent(
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog_html.ts b/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog_html.ts
index 3ec4f2c..b2acff2 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog_html.ts
@@ -85,8 +85,8 @@
<label for="revertSubmission" class="label revertSubmission">
Revert entire submission ([[_changesCount]] Changes)
</label>
- </div></template
- >
+ </div>
+ </template>
<gr-endpoint-decorator name="confirm-revert-change">
<label for="messageInput"> Revert Commit Message </label>
<iron-autogrow-textarea
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog.ts b/polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog.ts
index 33f7304..bd8eaac 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog.ts
@@ -85,19 +85,20 @@
return commentThreads.filter(thread => isUnresolved(thread));
}
- _computeUnresolvedCommentsWarning(change: ChangeInfo) {
+ _computeUnresolvedCommentsWarning(change?: ChangeInfo) {
+ if (!change) return '';
const unresolvedCount = change.unresolved_comment_count;
if (!unresolvedCount) throw new Error('unresolved comments undefined or 0');
return `Heads Up! ${pluralize(unresolvedCount, 'unresolved comment')}.`;
}
- _handleConfirmTap(e: MouseEvent) {
+ _handleConfirmTap(e: Event) {
e.preventDefault();
e.stopPropagation();
this.dispatchEvent(new CustomEvent('confirm', {bubbles: false}));
}
- _handleCancelTap(e: MouseEvent) {
+ _handleCancelTap(e: Event) {
e.preventDefault();
e.stopPropagation();
this.dispatchEvent(new CustomEvent('cancel', {bubbles: false}));
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts
index 5206fdb..f4f37c1 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts
@@ -82,13 +82,9 @@
import {Timing} from '../../../constants/reporting';
import {RevisionInfo} from '../../shared/revision-info/revision-info';
import {preferences$} from '../../../services/user/user-model';
-import {
- changeComments$,
- drafts$,
-} from '../../../services/comments/comments-model';
+import {changeComments$} from '../../../services/comments/comments-model';
import {Subject} from 'rxjs';
import {takeUntil} from 'rxjs/operators';
-import {UIDraft} from '../../../utils/comment-util';
export const DEFAULT_NUM_FILES_SHOWN = 200;
@@ -316,9 +312,6 @@
@property({type: Array})
_dynamicPrependedContentEndpoints?: string[];
- @property({type: Object})
- diffDrafts?: {[path: string]: UIDraft[]} = {};
-
private readonly reporting = appContext.reportingService;
private readonly restApiService = appContext.restApiService;
@@ -373,9 +366,6 @@
/** @override */
connectedCallback() {
super.connectedCallback();
- drafts$.pipe(takeUntil(this.disconnected$)).subscribe(drafts => {
- this.diffDrafts = drafts;
- });
changeComments$
.pipe(takeUntil(this.disconnected$))
.subscribe(changeComments => {
diff --git a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.ts b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.ts
index 5e6b076..d5c0171 100644
--- a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.ts
+++ b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.ts
@@ -33,6 +33,7 @@
AccountDetailInfo,
AccountInfo,
ChangeInfo,
+ NumericChangeId,
UrlEncodedCommentId,
} from '../../../types/common';
import {
@@ -82,7 +83,7 @@
threads: CommentThread[] = [];
@property({type: String})
- changeNum?: string;
+ changeNum?: NumericChangeId;
@property({type: Boolean})
loggedIn?: boolean;
diff --git a/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts b/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts
index 5cd7bfc..e60c2bb 100644
--- a/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts
+++ b/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts
@@ -97,7 +97,7 @@
'_computeDisableApplyFixButton(_isApplyFixLoading, change, ' +
'_patchNum)',
})
- _disableApplyFixButton?: boolean;
+ _disableApplyFixButton = false;
layers = appContext.flagsService.isEnabled(
KnownExperimentId.TOKEN_HIGHLIGHTING
@@ -187,12 +187,13 @@
return (_fixSuggestions || []).length === 1;
}
- overridePartialPrefs(prefs: DiffPreferencesInfo): DiffPreferencesInfo {
+ overridePartialPrefs(prefs?: DiffPreferencesInfo) {
+ if (!prefs) return undefined;
// generate a smaller gr-diff than fullscreen for dialog
return {...prefs, line_length: 50};
}
- onCancel(e: CustomEvent) {
+ onCancel(e: Event) {
if (e) {
e.stopPropagation();
}
@@ -203,7 +204,7 @@
return _selectedFixIdx + 1;
}
- _onPrevFixClick(e: CustomEvent) {
+ _onPrevFixClick(e: Event) {
if (e) e.stopPropagation();
if (this._selectedFixIdx >= 1 && this._fixSuggestions) {
this._selectedFixIdx -= 1;
@@ -213,7 +214,7 @@
}
}
- _onNextFixClick(e: CustomEvent) {
+ _onNextFixClick(e: Event) {
if (e) e.stopPropagation();
if (
this._fixSuggestions &&
@@ -257,7 +258,7 @@
}
_computeDisableApplyFixButton(
- isApplyFixLoading?: boolean,
+ isApplyFixLoading: boolean,
change?: ParsedChangeInfo,
patchNum?: PatchSetNum
) {
@@ -271,7 +272,7 @@
return isApplyFixLoading;
}
- _handleApplyFix(e: CustomEvent) {
+ _handleApplyFix(e: Event) {
if (e) {
e.stopPropagation();
}
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.ts b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.ts
index fdde355..888e514 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.ts
@@ -40,7 +40,12 @@
} from './gr-diff-utils';
import {getHiddenScroll} from '../../../scripts/hiddenscroll';
import {customElement, observe, property} from '@polymer/decorators';
-import {BlameInfo, CommentRange, ImageInfo} from '../../../types/common';
+import {
+ BlameInfo,
+ CommentRange,
+ ImageInfo,
+ NumericChangeId,
+} from '../../../types/common';
import {
DiffInfo,
DiffPreferencesInfo,
@@ -155,7 +160,7 @@
*/
@property({type: String})
- changeNum?: string;
+ changeNum?: NumericChangeId;
@property({type: Boolean})
noAutoRender = false;
diff --git a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.ts b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.ts
index da75914..16d54dd 100644
--- a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.ts
+++ b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.ts
@@ -28,7 +28,7 @@
GenerateUrlEditViewParameters,
} from '../../core/gr-navigation/gr-navigation';
import {computeTruncatedPath} from '../../../utils/path-list-util';
-import {customElement, property} from '@polymer/decorators';
+import {customElement, observe, property} from '@polymer/decorators';
import {
ChangeInfo,
PatchSetNum,
@@ -211,8 +211,8 @@
}
_editChange(value?: ChangeInfo | null) {
- if (!changeIsMerged(value) && !changeIsAbandoned(value)) return;
if (!value) return;
+ if (!changeIsMerged(value) && !changeIsAbandoned(value)) return;
fireAlert(
this,
'Change edits cannot be created if change is merged or abandoned. Redirected to non edit mode.'
@@ -220,6 +220,15 @@
GerritNav.navigateToChange(value);
}
+ @observe('_change', '_type')
+ _editType(change?: ChangeInfo | null, type?: string) {
+ if (!change || !type || !type.startsWith('image/')) return;
+
+ // Prevent editing binary files
+ fireAlert(this, 'You cannot edit binary files within the inline editor.');
+ GerritNav.navigateToChange(change);
+ }
+
_handlePathChanged(e: CustomEvent<string>) {
// TODO(TS) could be cleaned up, it was added for type requirements
if (this._changeNum === undefined || !this._path) {
diff --git a/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences.ts b/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences.ts
index 4909bef..7cfd1b3 100644
--- a/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences.ts
+++ b/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences.ts
@@ -35,6 +35,7 @@
showMatchBrackets: HTMLInputElement;
editShowLineWrapping: HTMLInputElement;
editShowTabs: HTMLInputElement;
+ editShowTrailingWhitespaceInput: HTMLInputElement;
};
}
@customElement('gr-edit-preferences')
@@ -89,6 +90,14 @@
this._handleEditPrefsChanged();
}
+ _handleEditShowTrailingWhitespaceTap() {
+ this.set(
+ 'editPrefs.show_whitespace_errors',
+ this.$.editShowTrailingWhitespaceInput.checked
+ );
+ this._handleEditPrefsChanged();
+ }
+
_handleMatchBracketsChanged() {
this.set('editPrefs.match_brackets', this.$.showMatchBrackets.checked);
this._handleEditPrefsChanged();
diff --git a/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences_html.ts b/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences_html.ts
index a51deaf..abd925f 100644
--- a/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences_html.ts
+++ b/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences_html.ts
@@ -85,6 +85,19 @@
</span>
</section>
<section>
+ <label for="showTrailingWhitespaceInput" class="title"
+ >Show trailing whitespace</label
+ >
+ <span class="value">
+ <input
+ id="editShowTrailingWhitespaceInput"
+ type="checkbox"
+ checked$="[[editPrefs.show_whitespace_errors]]"
+ on-change="_handleEditShowTrailingWhitespaceTap"
+ />
+ </span>
+ </section>
+ <section>
<label for="showMatchBrackets" class="title">Match brackets</label>
<span class="value">
<input
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.ts b/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.ts
index 73d1bf0..4d2aee7 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.ts
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.ts
@@ -39,7 +39,7 @@
}
}
-interface Item {
+export interface Item {
dataValue?: string;
name?: string;
text?: string;
@@ -47,6 +47,11 @@
value?: string;
}
+export interface ItemSelectedEvent {
+ trigger: string;
+ selected: HTMLElement | null;
+}
+
@customElement('gr-autocomplete-dropdown')
export class GrAutocompleteDropdown extends IronFitMixin(
KeyboardShortcutMixin(PolymerElement),
@@ -155,7 +160,7 @@
e.preventDefault();
e.stopPropagation();
this.dispatchEvent(
- new CustomEvent('item-selected', {
+ new CustomEvent<ItemSelectedEvent>('item-selected', {
detail: {
trigger: 'tab',
selected: this.cursor.target,
@@ -170,7 +175,7 @@
e.preventDefault();
e.stopPropagation();
this.dispatchEvent(
- new CustomEvent('item-selected', {
+ new CustomEvent<ItemSelectedEvent>('item-selected', {
detail: {
trigger: 'enter',
selected: this.cursor.target,
@@ -189,7 +194,7 @@
_handleClickItem(e: Event) {
e.preventDefault();
e.stopPropagation();
- let selected = e.target! as Element;
+ let selected = e.target! as HTMLElement;
while (!selected.classList.contains('autocompleteOption')) {
if (!selected || selected === this) {
return;
@@ -197,7 +202,7 @@
selected = selected.parentElement!;
}
this.dispatchEvent(
- new CustomEvent('item-selected', {
+ new CustomEvent<ItemSelectedEvent>('item-selected', {
detail: {
trigger: 'click',
selected,
diff --git a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.ts b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.ts
index 2ad3be6..afff6f3 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.ts
+++ b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.ts
@@ -225,18 +225,6 @@
this.addEventListener('comment-update', e =>
this._handleCommentUpdate(e as CustomEvent)
);
- // Wait for comment to be rendered before scrolling to it
- if (this.shouldScrollIntoView) {
- const resizeObserver = new ResizeObserver(
- (_entries: ResizeObserverEntry[], observer: ResizeObserver) => {
- if (this.offsetHeight > 0) {
- this.scrollIntoView();
- observer.unobserve(this);
- }
- }
- );
- resizeObserver.observe(this);
- }
}
/** @override */
diff --git a/polygerrit-ui/app/elements/shared/gr-diff-preferences/gr-diff-preferences.ts b/polygerrit-ui/app/elements/shared/gr-diff-preferences/gr-diff-preferences.ts
index 70a7bf3..e560773 100644
--- a/polygerrit-ui/app/elements/shared/gr-diff-preferences/gr-diff-preferences.ts
+++ b/polygerrit-ui/app/elements/shared/gr-diff-preferences/gr-diff-preferences.ts
@@ -21,18 +21,23 @@
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-diff-preferences_html';
import {customElement, property} from '@polymer/decorators';
-import {DiffPreferencesInfo} from '../../../types/diff';
+import {DiffPreferencesInfo, IgnoreWhitespaceType} from '../../../types/diff';
import {GrSelect} from '../gr-select/gr-select';
import {appContext} from '../../../services/app-context';
export interface GrDiffPreferences {
$: {
+ contextLineSelect: HTMLInputElement;
+ columnsInput: HTMLInputElement;
+ tabSizeInput: HTMLInputElement;
+ fontSizeInput: HTMLInputElement;
lineWrappingInput: HTMLInputElement;
showTabsInput: HTMLInputElement;
showTrailingWhitespaceInput: HTMLInputElement;
automaticReviewInput: HTMLInputElement;
syntaxHighlightInput: HTMLInputElement;
contextSelect: GrSelect;
+ ignoreWhiteSpace: HTMLInputElement;
};
save(): Promise<void>;
}
@@ -61,11 +66,31 @@
this.hasUnsavedChanges = true;
}
+ _handleDiffContextChanged() {
+ this.set('diffPrefs.context', Number(this.$.contextLineSelect.value));
+ this._handleDiffPrefsChanged();
+ }
+
_handleLineWrappingTap() {
this.set('diffPrefs.line_wrapping', this.$.lineWrappingInput.checked);
this._handleDiffPrefsChanged();
}
+ _handleDiffLineLengthChanged() {
+ this.set('diffPrefs.line_length', Number(this.$.columnsInput.value));
+ this._handleDiffPrefsChanged();
+ }
+
+ _handleDiffTabSizeChanged() {
+ this.set('diffPrefs.tab_size', Number(this.$.tabSizeInput.value));
+ this._handleDiffPrefsChanged();
+ }
+
+ _handleDiffFontSizeChanged() {
+ this.set('diffPrefs.font_size', Number(this.$.fontSizeInput.value));
+ this._handleDiffPrefsChanged();
+ }
+
_handleShowTabsTap() {
this.set('diffPrefs.show_tabs', this.$.showTabsInput.checked);
this._handleDiffPrefsChanged();
@@ -92,6 +117,14 @@
this._handleDiffPrefsChanged();
}
+ _handleDiffIgnoreWhitespaceChanged() {
+ this.set(
+ 'diffPrefs.ignore_whitespace',
+ this.$.ignoreWhiteSpace.value as IgnoreWhitespaceType
+ );
+ this._handleDiffPrefsChanged();
+ }
+
save() {
if (!this.diffPrefs)
return Promise.reject(new Error('Missing diff preferences'));
@@ -99,6 +132,27 @@
this.hasUnsavedChanges = false;
});
}
+
+ /**
+ * bind-value has type string so we have to convert
+ * anything inputed to string.
+ *
+ * This is so typescript checker doesn't fail.
+ */
+ _convertToString(key?: number | IgnoreWhitespaceType) {
+ return key !== undefined ? String(key) : '';
+ }
+
+ /**
+ * input 'checked' does not allow undefined,
+ * so we make sure the value is boolean
+ * by returning false if undefined.
+ *
+ * This is so typescript checker doesn't fail.
+ */
+ _convertToBoolean(key?: boolean) {
+ return key !== undefined ? key : false;
+ }
}
declare global {
diff --git a/polygerrit-ui/app/elements/shared/gr-diff-preferences/gr-diff-preferences_html.ts b/polygerrit-ui/app/elements/shared/gr-diff-preferences/gr-diff-preferences_html.ts
index ed3d695..51867c8 100644
--- a/polygerrit-ui/app/elements/shared/gr-diff-preferences/gr-diff-preferences_html.ts
+++ b/polygerrit-ui/app/elements/shared/gr-diff-preferences/gr-diff-preferences_html.ts
@@ -27,12 +27,12 @@
<section>
<label for="contextLineSelect" class="title">Context</label>
<span class="value">
- <gr-select id="contextSelect" bind-value="{{diffPrefs.context}}">
- <select
- id="contextLineSelect"
- on-keypress="_handleDiffPrefsChanged"
- on-change="_handleDiffPrefsChanged"
- >
+ <gr-select
+ id="contextSelect"
+ bind-value="[[_convertToString(diffPrefs.context)]]"
+ on-change="_handleDiffContextChanged"
+ >
+ <select id="contextLineSelect">
<option value="3">3 lines</option>
<option value="10">10 lines</option>
<option value="25">25 lines</option>
@@ -50,7 +50,7 @@
<input
id="lineWrappingInput"
type="checkbox"
- checked="[[diffPrefs.line_wrapping]]"
+ checked="[[_convertToBoolean(diffPrefs.line_wrapping)]]"
on-change="_handleLineWrappingTap"
/>
</span>
@@ -59,23 +59,11 @@
<label for="columnsInput" class="title">Diff width</label>
<span class="value">
<iron-input
- type="number"
- prevent-invalid-input=""
allowed-pattern="[0-9]"
- bind-value="{{diffPrefs.line_length}}"
- on-keypress="_handleDiffPrefsChanged"
- on-change="_handleDiffPrefsChanged"
+ bind-value="[[_convertToString(diffPrefs.line_length)]]"
+ on-change="_handleDiffLineLengthChanged"
>
- <input
- is="iron-input"
- type="number"
- id="columnsInput"
- prevent-invalid-input=""
- allowed-pattern="[0-9]"
- bind-value="{{diffPrefs.line_length}}"
- on-keypress="_handleDiffPrefsChanged"
- on-change="_handleDiffPrefsChanged"
- />
+ <input id="columnsInput" type="number" />
</iron-input>
</span>
</section>
@@ -83,23 +71,11 @@
<label for="tabSizeInput" class="title">Tab width</label>
<span class="value">
<iron-input
- type="number"
- prevent-invalid-input=""
allowed-pattern="[0-9]"
- bind-value="{{diffPrefs.tab_size}}"
- on-keypress="_handleDiffPrefsChanged"
- on-change="_handleDiffPrefsChanged"
+ bind-value="[[_convertToString(diffPrefs.tab_size)]]"
+ on-change="_handleDiffTabSizeChanged"
>
- <input
- is="iron-input"
- type="number"
- id="tabSizeInput"
- prevent-invalid-input=""
- allowed-pattern="[0-9]"
- bind-value="{{diffPrefs.tab_size}}"
- on-keypress="_handleDiffPrefsChanged"
- on-change="_handleDiffPrefsChanged"
- />
+ <input id="tabSizeInput" type="number" />
</iron-input>
</span>
</section>
@@ -107,23 +83,11 @@
<label for="fontSizeInput" class="title">Font size</label>
<span class="value">
<iron-input
- type="number"
- prevent-invalid-input=""
allowed-pattern="[0-9]"
- bind-value="{{diffPrefs.font_size}}"
- on-keypress="_handleDiffPrefsChanged"
- on-change="_handleDiffPrefsChanged"
+ bind-value="[[_convertToString(diffPrefs.font_size)]]"
+ on-change="_handleDiffFontSizeChanged"
>
- <input
- is="iron-input"
- type="number"
- id="fontSizeInput"
- prevent-invalid-input=""
- allowed-pattern="[0-9]"
- bind-value="{{diffPrefs.font_size}}"
- on-keypress="_handleDiffPrefsChanged"
- on-change="_handleDiffPrefsChanged"
- />
+ <input id="fontSizeInput" type="number" />
</iron-input>
</span>
</section>
@@ -133,7 +97,7 @@
<input
id="showTabsInput"
type="checkbox"
- checked="[[diffPrefs.show_tabs]]"
+ checked="[[_convertToBoolean(diffPrefs.show_tabs)]]"
on-change="_handleShowTabsTap"
/>
</span>
@@ -146,7 +110,7 @@
<input
id="showTrailingWhitespaceInput"
type="checkbox"
- checked="[[diffPrefs.show_whitespace_errors]]"
+ checked="[[_convertToBoolean(diffPrefs.show_whitespace_errors)]]"
on-change="_handleShowTrailingWhitespaceTap"
/>
</span>
@@ -159,7 +123,7 @@
<input
id="syntaxHighlightInput"
type="checkbox"
- checked="[[diffPrefs.syntax_highlighting]]"
+ checked="[[_convertToBoolean(diffPrefs.syntax_highlighting)]]"
on-change="_handleSyntaxHighlightTap"
/>
</span>
@@ -172,7 +136,7 @@
<input
id="automaticReviewInput"
type="checkbox"
- checked="[[!diffPrefs.manual_review]]"
+ checked="[[!_convertToBoolean(diffPrefs.manual_review)]]"
on-change="_handleAutomaticReviewTap"
/>
</span>
@@ -181,12 +145,11 @@
<div class="pref">
<label for="ignoreWhiteSpace" class="title">Ignore Whitespace</label>
<span class="value">
- <gr-select bind-value="{{diffPrefs.ignore_whitespace}}">
- <select
- id="ignoreWhiteSpace"
- on-keypress="_handleDiffPrefsChanged"
- on-change="_handleDiffPrefsChanged"
- >
+ <gr-select
+ bind-value="[[_convertToString(diffPrefs.ignore_whitespace)]]"
+ on-change="_handleDiffIgnoreWhitespaceChanged"
+ >
+ <select id="ignoreWhiteSpace">
<option value="IGNORE_NONE">None</option>
<option value="IGNORE_TRAILING">Trailing</option>
<option value="IGNORE_LEADING_AND_TRAILING">
diff --git a/polygerrit-ui/app/elements/shared/gr-diff-preferences/gr-diff-preferences_test.js b/polygerrit-ui/app/elements/shared/gr-diff-preferences/gr-diff-preferences_test.js
deleted file mode 100644
index 716ef2f..0000000
--- a/polygerrit-ui/app/elements/shared/gr-diff-preferences/gr-diff-preferences_test.js
+++ /dev/null
@@ -1,105 +0,0 @@
-/**
- * @license
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import '../../../test/common-test-setup-karma.js';
-import './gr-diff-preferences.js';
-import {stubRestApi} from '../../../test/test-utils.js';
-
-const basicFixture = fixtureFromElement('gr-diff-preferences');
-
-suite('gr-diff-preferences tests', () => {
- let element;
-
- let diffPreferences;
-
- function valueOf(title, fieldsetid) {
- const sections = element.$[fieldsetid].querySelectorAll('section');
- let titleEl;
- for (let i = 0; i < sections.length; i++) {
- titleEl = sections[i].querySelector('.title');
- if (titleEl.textContent.trim() === title) {
- return sections[i].querySelector('.value');
- }
- }
- }
-
- setup(() => {
- diffPreferences = {
- context: 10,
- line_wrapping: false,
- line_length: 100,
- tab_size: 8,
- font_size: 12,
- show_tabs: true,
- show_whitespace_errors: true,
- syntax_highlighting: true,
- manual_review: false,
- ignore_whitespace: 'IGNORE_NONE',
- };
-
- stubRestApi('getDiffPreferences').returns(Promise.resolve(diffPreferences));
-
- element = basicFixture.instantiate();
-
- return element.loadData();
- });
-
- test('renders', () => {
- // Rendered with the expected preferences selected.
- assert.equal(valueOf('Context', 'diffPreferences')
- .firstElementChild.bindValue, diffPreferences.context);
- assert.equal(valueOf('Fit to screen', 'diffPreferences')
- .firstElementChild.checked, diffPreferences.line_wrapping);
- assert.equal(valueOf('Diff width', 'diffPreferences')
- .firstElementChild.bindValue, diffPreferences.line_length);
- assert.equal(valueOf('Tab width', 'diffPreferences')
- .firstElementChild.bindValue, diffPreferences.tab_size);
- assert.equal(valueOf('Font size', 'diffPreferences')
- .firstElementChild.bindValue, diffPreferences.font_size);
- assert.equal(valueOf('Show tabs', 'diffPreferences')
- .firstElementChild.checked, diffPreferences.show_tabs);
- assert.equal(valueOf('Show trailing whitespace', 'diffPreferences')
- .firstElementChild.checked, diffPreferences.show_whitespace_errors);
- assert.equal(valueOf('Syntax highlighting', 'diffPreferences')
- .firstElementChild.checked, diffPreferences.syntax_highlighting);
- assert.equal(
- valueOf('Automatically mark viewed files reviewed', 'diffPreferences')
- .firstElementChild.checked, !diffPreferences.manual_review);
- assert.equal(valueOf('Ignore Whitespace', 'diffPreferences')
- .firstElementChild.bindValue, diffPreferences.ignore_whitespace);
-
- assert.isFalse(element.hasUnsavedChanges);
- });
-
- test('save changes', () => {
- stubRestApi('saveDiffPreferences')
- .returns(Promise.resolve());
- const showTrailingWhitespaceCheckbox =
- valueOf('Show trailing whitespace', 'diffPreferences')
- .firstElementChild;
- showTrailingWhitespaceCheckbox.checked = false;
- element._handleShowTrailingWhitespaceTap();
-
- assert.isTrue(element.hasUnsavedChanges);
-
- // Save the change.
- return element.save().then(() => {
- assert.isFalse(element.hasUnsavedChanges);
- });
- });
-});
-
diff --git a/polygerrit-ui/app/elements/shared/gr-diff-preferences/gr-diff-preferences_test.ts b/polygerrit-ui/app/elements/shared/gr-diff-preferences/gr-diff-preferences_test.ts
new file mode 100644
index 0000000..6c1404e
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-diff-preferences/gr-diff-preferences_test.ts
@@ -0,0 +1,134 @@
+/**
+ * @license
+ * Copyright (C) 2016 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 './gr-diff-preferences';
+import {GrDiffPreferences} from './gr-diff-preferences';
+import {stubRestApi} from '../../../test/test-utils';
+import {DiffPreferencesInfo} from '../../../types/diff';
+import {createDefaultDiffPrefs} from '../../../constants/constants';
+import {IronInputElement} from '@polymer/iron-input';
+import {GrSelect} from '../gr-select/gr-select';
+
+const basicFixture = fixtureFromElement('gr-diff-preferences');
+
+suite('gr-diff-preferences tests', () => {
+ let element: GrDiffPreferences;
+
+ let diffPreferences: DiffPreferencesInfo;
+
+ function valueOf(title: string, id: string) {
+ const sections = element.root?.querySelectorAll(`#${id} section`) ?? [];
+ let titleEl;
+ for (let i = 0; i < sections.length; i++) {
+ titleEl = sections[i].querySelector('.title');
+ if (titleEl?.textContent?.trim() === title) {
+ const el = sections[i].querySelector('.value');
+ if (el) return el;
+ }
+ }
+ assert.fail(`element with title ${title} not found`);
+ }
+
+ setup(async () => {
+ diffPreferences = createDefaultDiffPrefs();
+
+ stubRestApi('getDiffPreferences').returns(Promise.resolve(diffPreferences));
+
+ element = basicFixture.instantiate();
+
+ await element.loadData();
+ await flush();
+ });
+
+ test('renders', () => {
+ // Rendered with the expected preferences selected.
+ const contextInput = valueOf('Context', 'diffPreferences')
+ .firstElementChild as IronInputElement;
+ assert.equal(contextInput.bindValue, `${diffPreferences.context}`);
+
+ const lineWrappingInput = valueOf('Fit to screen', 'diffPreferences')
+ .firstElementChild as HTMLInputElement;
+ assert.equal(lineWrappingInput.checked, diffPreferences.line_wrapping);
+
+ const lineLengthInput = valueOf('Diff width', 'diffPreferences')
+ .firstElementChild as IronInputElement;
+ assert.equal(lineLengthInput.bindValue, `${diffPreferences.line_length}`);
+
+ const tabSizeInput = valueOf('Tab width', 'diffPreferences')
+ .firstElementChild as IronInputElement;
+ assert.equal(tabSizeInput.bindValue, `${diffPreferences.tab_size}`);
+
+ const fontSizeInput = valueOf('Font size', 'diffPreferences')
+ .firstElementChild as IronInputElement;
+ assert.equal(fontSizeInput.bindValue, `${diffPreferences.font_size}`);
+
+ const showTabsInput = valueOf('Show tabs', 'diffPreferences')
+ .firstElementChild as HTMLInputElement;
+ assert.equal(showTabsInput.checked, diffPreferences.show_tabs);
+
+ const showWhitespaceErrorsInput = valueOf(
+ 'Show trailing whitespace',
+ 'diffPreferences'
+ ).firstElementChild as HTMLInputElement;
+ assert.equal(
+ showWhitespaceErrorsInput.checked,
+ diffPreferences.show_whitespace_errors
+ );
+
+ const syntaxHighlightingInput = valueOf(
+ 'Syntax highlighting',
+ 'diffPreferences'
+ ).firstElementChild as HTMLInputElement;
+ assert.equal(
+ syntaxHighlightingInput.checked,
+ diffPreferences.syntax_highlighting
+ );
+
+ const manualReviewInput = valueOf(
+ 'Automatically mark viewed files reviewed',
+ 'diffPreferences'
+ ).firstElementChild as HTMLInputElement;
+ assert.equal(manualReviewInput.checked, !diffPreferences.manual_review);
+
+ const ignoreWhitespaceInput = valueOf(
+ 'Ignore Whitespace',
+ 'diffPreferences'
+ ).firstElementChild as GrSelect;
+ assert.equal(
+ ignoreWhitespaceInput.bindValue,
+ diffPreferences.ignore_whitespace
+ );
+
+ assert.isFalse(element.hasUnsavedChanges);
+ });
+
+ test('save changes', async () => {
+ const showTrailingWhitespaceCheckbox = valueOf(
+ 'Show trailing whitespace',
+ 'diffPreferences'
+ ).firstElementChild as HTMLInputElement;
+ showTrailingWhitespaceCheckbox.checked = false;
+ element._handleShowTrailingWhitespaceTap();
+
+ assert.isTrue(element.hasUnsavedChanges);
+
+ // Save the change.
+ await element.save();
+ assert.isFalse(element.hasUnsavedChanges);
+ });
+});
diff --git a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.ts b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.ts
index a747ac4..17c46c6 100644
--- a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.ts
+++ b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.ts
@@ -28,7 +28,12 @@
import {customElement, property} from '@polymer/decorators';
import {ReportingService} from '../../../services/gr-reporting/gr-reporting';
import {IronAutogrowTextareaElement} from '@polymer/iron-autogrow-textarea/iron-autogrow-textarea';
-import {GrAutocompleteDropdown} from '../gr-autocomplete-dropdown/gr-autocomplete-dropdown';
+import {
+ GrAutocompleteDropdown,
+ Item,
+ ItemSelectedEvent,
+} from '../gr-autocomplete-dropdown/gr-autocomplete-dropdown';
+import {CustomKeyboardEvent} from '../../../types/events';
const MAX_ITEMS_DROPDOWN = 10;
@@ -56,11 +61,8 @@
{value: '😜', match: 'winking tongue ;)'},
];
-interface EmojiSuggestion {
- value: string;
+interface EmojiSuggestion extends Item {
match: string;
- dataValue?: string;
- text?: string;
}
interface ValueChangeEvent {
@@ -76,6 +78,13 @@
};
}
+declare global {
+ interface HTMLElementEventMap {
+ 'item-selected': CustomEvent<ItemSelectedEvent>;
+ 'bind-value-changed': CustomEvent<ValueChangeEvent>;
+ }
+}
+
@customElement('gr-textarea')
export class GrTextarea extends KeyboardShortcutMixin(PolymerElement) {
static get template() {
@@ -85,8 +94,8 @@
/**
* @event bind-value-changed
*/
- @property({type: Boolean})
- autocomplete?: boolean;
+ @property({type: String})
+ autocomplete?: string;
@property({type: Boolean})
disabled?: boolean;
@@ -101,7 +110,7 @@
placeholder?: string;
@property({type: String, notify: true, observer: '_handleTextChanged'})
- text?: string;
+ text = '';
@property({type: Boolean})
hideBorder = false;
@@ -125,10 +134,10 @@
_hideEmojiAutocomplete = true;
@property({type: Number})
- _index?: number;
+ _index: number | null = null;
@property({type: Array})
- _suggestions?: EmojiSuggestion[];
+ _suggestions: EmojiSuggestion[] = [];
@property({type: Number})
readonly _verticalOffset = 20;
@@ -227,11 +236,14 @@
this._setEmoji(this.$.emojiSuggestions.getCurrentText());
}
- _handleEnterByKey(e: CustomEvent<{keyboardEvent: KeyboardEvent}>) {
+ _handleEnterByKey(e: CustomKeyboardEvent) {
// Enter should have newline behavior if the picker is closed or if the user
// has only typed ':'. Also make sure that shortcuts aren't clobbered.
if (this._hideEmojiAutocomplete || this.disableEnterKeyForSelectingEmoji) {
- if (!e.detail.keyboardEvent.metaKey && !e.detail.keyboardEvent.ctrlKey) {
+ if (
+ !e.detail.keyboardEvent?.metaKey &&
+ !e.detail.keyboardEvent?.ctrlKey
+ ) {
this.indent(e);
}
return;
@@ -242,8 +254,10 @@
this._setEmoji(this.$.emojiSuggestions.getCurrentText());
}
- _handleEmojiSelect(e: CustomEvent) {
- this._setEmoji(e.detail.selected.dataset['value']);
+ _handleEmojiSelect(e: CustomEvent<ItemSelectedEvent>) {
+ if (e.detail.selected?.dataset['value']) {
+ this._setEmoji(e.detail.selected?.dataset['value']);
+ }
}
_setEmoji(text: string) {
@@ -369,7 +383,7 @@
const suggestions = [];
for (const suggestion of matchedSuggestions) {
suggestion.dataValue = suggestion.value;
- suggestion.text = suggestion.value + ' ' + suggestion.match;
+ suggestion.text = `${suggestion.value} ${suggestion.match}`;
suggestions.push(suggestion);
}
this.set('_suggestions', suggestions);
@@ -404,7 +418,7 @@
);
}
- private indent(e: CustomEvent<{keyboardEvent: KeyboardEvent}>): void {
+ private indent(e: CustomKeyboardEvent): void {
if (!document.queryCommandSupported('insertText')) {
return;
}
diff --git a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea_test.js b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea_test.js
deleted file mode 100644
index 7c2f209..0000000
--- a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea_test.js
+++ /dev/null
@@ -1,373 +0,0 @@
-/**
- * @license
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import '../../../test/common-test-setup-karma.js';
-import './gr-textarea.js';
-import {html} from '@polymer/polymer/lib/utils/html-tag.js';
-
-const basicFixture = fixtureFromElement('gr-textarea');
-
-const monospaceFixture = fixtureFromTemplate(html`
-<gr-textarea monospace="true"></gr-textarea>
-`);
-
-const hideBorderFixture = fixtureFromTemplate(html`
-<gr-textarea hide-border="true"></gr-textarea>
-`);
-
-suite('gr-textarea tests', () => {
- let element;
-
- setup(() => {
- element = basicFixture.instantiate();
- sinon.stub(element.reporting, 'reportInteraction');
- });
-
- test('monospace is set properly', () => {
- assert.isFalse(element.classList.contains('monospace'));
- });
-
- test('hideBorder is set properly', () => {
- assert.isFalse(element.$.textarea.classList.contains('noBorder'));
- });
-
- test('emoji selector is not open with the textarea lacks focus', () => {
- element.$.textarea.selectionStart = 1;
- element.$.textarea.selectionEnd = 1;
- element.text = ':';
- assert.isFalse(!element.$.emojiSuggestions.isHidden);
- });
-
- test('emoji selector is not open when a general text is entered', () => {
- MockInteractions.focus(element.$.textarea);
- element.$.textarea.selectionStart = 9;
- element.$.textarea.selectionEnd = 9;
- element.text = 'some text';
- assert.isFalse(!element.$.emojiSuggestions.isHidden);
- });
-
- test('emoji selector opens when a colon is typed & the textarea has focus',
- () => {
- MockInteractions.focus(element.$.textarea);
- // Needed for Safari tests. selectionStart is not updated when text is
- // updated.
- element.$.textarea.selectionStart = 1;
- element.$.textarea.selectionEnd = 1;
- element.text = ':';
- flush();
- assert.isFalse(element.$.emojiSuggestions.isHidden);
- assert.equal(element._colonIndex, 0);
- assert.isFalse(element._hideEmojiAutocomplete);
- assert.equal(element._currentSearchString, '');
- });
-
- test('emoji selector opens when a colon is typed after space',
- () => {
- MockInteractions.focus(element.$.textarea);
- // Needed for Safari tests. selectionStart is not updated when text is
- // updated.
- element.$.textarea.selectionStart = 2;
- element.$.textarea.selectionEnd = 2;
- element.text = ' :';
- flush();
- assert.isFalse(element.$.emojiSuggestions.isHidden);
- assert.equal(element._colonIndex, 1);
- assert.isFalse(element._hideEmojiAutocomplete);
- assert.equal(element._currentSearchString, '');
- });
-
- test('emoji selector doesn\`t open when a colon is typed after character',
- () => {
- MockInteractions.focus(element.$.textarea);
- // Needed for Safari tests. selectionStart is not updated when text is
- // updated.
- element.$.textarea.selectionStart = 5;
- element.$.textarea.selectionEnd = 5;
- element.text = 'test:';
- flush();
- assert.isTrue(element.$.emojiSuggestions.isHidden);
- assert.isTrue(element._hideEmojiAutocomplete);
- });
-
- test('emoji selector opens when a colon is typed and some substring',
- () => {
- MockInteractions.focus(element.$.textarea);
- // Needed for Safari tests. selectionStart is not updated when text is
- // updated.
- element.$.textarea.selectionStart = 1;
- element.$.textarea.selectionEnd = 1;
- element.text = ':';
- element.$.textarea.selectionStart = 2;
- element.$.textarea.selectionEnd = 2;
- element.text = ':t';
- flush();
- assert.isFalse(element.$.emojiSuggestions.isHidden);
- assert.equal(element._colonIndex, 0);
- assert.isFalse(element._hideEmojiAutocomplete);
- assert.equal(element._currentSearchString, 't');
- });
-
- test('emoji selector opens when a colon is typed in middle of text',
- () => {
- MockInteractions.focus(element.$.textarea);
- // Needed for Safari tests. selectionStart is not updated when text is
- // updated.
- element.$.textarea.selectionStart = 1;
- element.$.textarea.selectionEnd = 1;
- // Since selectionStart is on Chrome set always on end of text, we
- // stub it to 1
- const text = ': hello';
- sinon.stub(element.$, 'textarea').value( {
- selectionStart: 1,
- value: text,
- textarea: {
- focus: () => {},
- },
- });
- element.text = text;
- flush();
- assert.isFalse(element.$.emojiSuggestions.isHidden);
- assert.equal(element._colonIndex, 0);
- assert.isFalse(element._hideEmojiAutocomplete);
- assert.equal(element._currentSearchString, '');
- });
- test('emoji selector closes when text changes before the colon', () => {
- const resetStub = sinon.stub(element, '_resetEmojiDropdown');
- MockInteractions.focus(element.$.textarea);
- flush();
- element.$.textarea.selectionStart = 10;
- element.$.textarea.selectionEnd = 10;
- element.text = 'test test ';
- element.$.textarea.selectionStart = 12;
- element.$.textarea.selectionEnd = 12;
- element.text = 'test test :';
- element.$.textarea.selectionStart = 15;
- element.$.textarea.selectionEnd = 15;
- element.text = 'test test :smi';
-
- assert.equal(element._currentSearchString, 'smi');
- assert.isFalse(resetStub.called);
- element.text = 'test test test :smi';
- assert.isTrue(resetStub.called);
- });
-
- test('_resetEmojiDropdown', () => {
- const closeSpy = sinon.spy(element, 'closeDropdown');
- element._resetEmojiDropdown();
- assert.equal(element._currentSearchString, '');
- assert.isTrue(element._hideEmojiAutocomplete);
- assert.equal(element._colonIndex, null);
-
- element.$.emojiSuggestions.open();
- flush();
- element._resetEmojiDropdown();
- assert.isTrue(closeSpy.called);
- });
-
- test('_determineSuggestions', () => {
- const emojiText = 'tear';
- const formatSpy = sinon.spy(element, '_formatSuggestions');
- element._determineSuggestions(emojiText);
- assert.isTrue(formatSpy.called);
- assert.isTrue(formatSpy.lastCall.calledWithExactly(
- [{dataValue: '😂', value: '😂', match: 'tears :\')',
- text: '😂 tears :\')'},
- {dataValue: '😢', value: '😢', match: 'tear', text: '😢 tear'},
- ]));
- });
-
- test('_formatSuggestions', () => {
- const matchedSuggestions = [{value: '😢', match: 'tear'},
- {value: '😂', match: 'tears'}];
- element._formatSuggestions(matchedSuggestions);
- assert.deepEqual(
- [{value: '😢', dataValue: '😢', match: 'tear', text: '😢 tear'},
- {value: '😂', dataValue: '😂', match: 'tears', text: '😂 tears'}],
- element._suggestions);
- });
-
- test('_handleEmojiSelect', () => {
- element.$.textarea.selectionStart = 16;
- element.$.textarea.selectionEnd = 16;
- element.text = 'test test :tears';
- element._colonIndex = 10;
- const selectedItem = {dataset: {value: '😂'}};
- const event = {detail: {selected: selectedItem}};
- element._handleEmojiSelect(event);
- assert.equal(element.text, 'test test 😂');
- });
-
- test('_updateCaratPosition', () => {
- element.$.textarea.selectionStart = 4;
- element.$.textarea.selectionEnd = 4;
- element.text = 'test';
- element._updateCaratPosition();
- assert.deepEqual(element.$.hiddenText.innerHTML, element.text +
- element.$.caratSpan.outerHTML);
- });
-
- test('newline receives matching indentation', async () => {
- const indentCommand = sinon.stub(document, 'execCommand');
- element.$.textarea.value = ' a';
- element._handleEnterByKey(
- new CustomEvent('keydown', {detail: {keyboardEvent: {keyCode: 13}}})
- );
- await flush();
- assert.deepEqual(indentCommand.args[0], ['insertText', false, '\n ']);
- });
-
- test('ctrl+enter and meta+enter do not indent', async () => {
- const indentCommand = sinon.stub(document, 'execCommand');
- element.$.textarea.value = ' a';
- element._handleEnterByKey(
- new CustomEvent('keydown', {
- detail: {keyboardEvent: {keyCode: 13, ctrlKey: true}},
- })
- );
- await flush();
- assert.isTrue(indentCommand.notCalled);
-
- element._handleEnterByKey(
- new CustomEvent('keydown', {
- detail: {keyboardEvent: {keyCode: 13, metaKey: true}},
- })
- );
- await flush();
- assert.isTrue(indentCommand.notCalled);
- });
-
- test('emoji dropdown is closed when iron-overlay-closed is fired', () => {
- const resetSpy = sinon.spy(element, '_resetEmojiDropdown');
- element.$.emojiSuggestions.dispatchEvent(
- new CustomEvent('dropdown-closed', {
- composed: true, bubbles: true,
- }));
- assert.isTrue(resetSpy.called);
- });
-
- test('_onValueChanged fires bind-value-changed', () => {
- const listenerStub = sinon.stub();
- const eventObject = {currentTarget: {focused: false}};
- element.addEventListener('bind-value-changed', listenerStub);
- element._onValueChanged(eventObject);
- assert.isTrue(listenerStub.called);
- });
-
- suite('keyboard shortcuts', () => {
- function setupDropdown(callback) {
- MockInteractions.focus(element.$.textarea);
- element.$.textarea.selectionStart = 1;
- element.$.textarea.selectionEnd = 1;
- element.text = ':';
- element.$.textarea.selectionStart = 1;
- element.$.textarea.selectionEnd = 2;
- element.text = ':1';
- flush();
- }
-
- test('escape key', () => {
- const resetSpy = sinon.spy(element, '_resetEmojiDropdown');
- MockInteractions.pressAndReleaseKeyOn(element.$.textarea, 27);
- assert.isFalse(resetSpy.called);
- setupDropdown();
- MockInteractions.pressAndReleaseKeyOn(element.$.textarea, 27);
- assert.isTrue(resetSpy.called);
- assert.isFalse(!element.$.emojiSuggestions.isHidden);
- });
-
- test('up key', () => {
- const upSpy = sinon.spy(element.$.emojiSuggestions, 'cursorUp');
- MockInteractions.pressAndReleaseKeyOn(element.$.textarea, 38);
- assert.isFalse(upSpy.called);
- setupDropdown();
- MockInteractions.pressAndReleaseKeyOn(element.$.textarea, 38);
- assert.isTrue(upSpy.called);
- });
-
- test('down key', () => {
- const downSpy = sinon.spy(element.$.emojiSuggestions, 'cursorDown');
- MockInteractions.pressAndReleaseKeyOn(element.$.textarea, 40);
- assert.isFalse(downSpy.called);
- setupDropdown();
- MockInteractions.pressAndReleaseKeyOn(element.$.textarea, 40);
- assert.isTrue(downSpy.called);
- });
-
- test('enter key', () => {
- const enterSpy = sinon.spy(element.$.emojiSuggestions,
- 'getCursorTarget');
- MockInteractions.pressAndReleaseKeyOn(element.$.textarea, 13);
- assert.isFalse(enterSpy.called);
- setupDropdown();
- MockInteractions.pressAndReleaseKeyOn(element.$.textarea, 13);
- assert.isTrue(enterSpy.called);
- flush();
- assert.equal(element.text, '💯');
- });
-
- test('enter key - ignored on just colon without more information', () => {
- const enterSpy = sinon.spy(element.$.emojiSuggestions,
- 'getCursorTarget');
- MockInteractions.pressAndReleaseKeyOn(element.$.textarea, 13);
- assert.isFalse(enterSpy.called);
- MockInteractions.focus(element.$.textarea);
- element.$.textarea.selectionStart = 1;
- element.$.textarea.selectionEnd = 1;
- element.text = ':';
- flush();
- MockInteractions.pressAndReleaseKeyOn(element.$.textarea, 13);
- assert.isFalse(enterSpy.called);
- });
- });
-
- suite('gr-textarea monospace', () => {
- // gr-textarea set monospace class in the ready() method.
- // In Polymer2, ready() is called from the fixture(...) method,
- // If ready() is called again later, some nested elements doesn't
- // handle it correctly. A separate test-fixture is used to set
- // properties before ready() is called.
-
- let element;
-
- setup(() => {
- element = monospaceFixture.instantiate();
- });
-
- test('monospace is set properly', () => {
- assert.isTrue(element.classList.contains('monospace'));
- });
- });
-
- suite('gr-textarea hideBorder', () => {
- // gr-textarea set noBorder class in the ready() method.
- // In Polymer2, ready() is called from the fixture(...) method,
- // If ready() is called again later, some nested elements doesn't
- // handle it correctly. A separate test-fixture is used to set
- // properties before ready() is called.
-
- let element;
-
- setup(() => {
- element = hideBorderFixture.instantiate();
- });
-
- test('hideBorder is set properly', () => {
- assert.isTrue(element.$.textarea.classList.contains('noBorder'));
- });
- });
-});
-
diff --git a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea_test.ts b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea_test.ts
new file mode 100644
index 0000000..506c348
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea_test.ts
@@ -0,0 +1,390 @@
+/**
+ * @license
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import '../../../test/common-test-setup-karma';
+import './gr-textarea';
+import {GrTextarea} from './gr-textarea';
+import {html} from '@polymer/polymer/lib/utils/html-tag';
+import * as MockInteractions from '@polymer/iron-test-helpers/mock-interactions';
+import {CustomKeyboardEvent} from '../../../types/events';
+import {ItemSelectedEvent} from '../gr-autocomplete-dropdown/gr-autocomplete-dropdown';
+
+const basicFixture = fixtureFromElement('gr-textarea');
+
+const monospaceFixture = fixtureFromTemplate(html`
+ <gr-textarea monospace="true"></gr-textarea>
+`);
+
+const hideBorderFixture = fixtureFromTemplate(html`
+ <gr-textarea hide-border="true"></gr-textarea>
+`);
+
+suite('gr-textarea tests', () => {
+ let element: GrTextarea;
+
+ setup(() => {
+ element = basicFixture.instantiate();
+ sinon.stub(element.reporting, 'reportInteraction');
+ });
+
+ test('monospace is set properly', () => {
+ assert.isFalse(element.classList.contains('monospace'));
+ });
+
+ test('hideBorder is set properly', () => {
+ assert.isFalse(element.$.textarea.classList.contains('noBorder'));
+ });
+
+ test('emoji selector is not open with the textarea lacks focus', () => {
+ element.$.textarea.selectionStart = 1;
+ element.$.textarea.selectionEnd = 1;
+ element.text = ':';
+ assert.isFalse(!element.$.emojiSuggestions.isHidden);
+ });
+
+ test('emoji selector is not open when a general text is entered', () => {
+ MockInteractions.focus(element.$.textarea);
+ element.$.textarea.selectionStart = 9;
+ element.$.textarea.selectionEnd = 9;
+ element.text = 'some text';
+ assert.isFalse(!element.$.emojiSuggestions.isHidden);
+ });
+
+ test('emoji selector opens when a colon is typed & the textarea has focus', () => {
+ MockInteractions.focus(element.$.textarea);
+ // Needed for Safari tests. selectionStart is not updated when text is
+ // updated.
+ element.$.textarea.selectionStart = 1;
+ element.$.textarea.selectionEnd = 1;
+ element.text = ':';
+ flush();
+ assert.isFalse(element.$.emojiSuggestions.isHidden);
+ assert.equal(element._colonIndex, 0);
+ assert.isFalse(element._hideEmojiAutocomplete);
+ assert.equal(element._currentSearchString, '');
+ });
+
+ test('emoji selector opens when a colon is typed after space', () => {
+ MockInteractions.focus(element.$.textarea);
+ // Needed for Safari tests. selectionStart is not updated when text is
+ // updated.
+ element.$.textarea.selectionStart = 2;
+ element.$.textarea.selectionEnd = 2;
+ element.text = ' :';
+ flush();
+ assert.isFalse(element.$.emojiSuggestions.isHidden);
+ assert.equal(element._colonIndex, 1);
+ assert.isFalse(element._hideEmojiAutocomplete);
+ assert.equal(element._currentSearchString, '');
+ });
+
+ test('emoji selector doesn`t open when a colon is typed after character', () => {
+ MockInteractions.focus(element.$.textarea);
+ // Needed for Safari tests. selectionStart is not updated when text is
+ // updated.
+ element.$.textarea.selectionStart = 5;
+ element.$.textarea.selectionEnd = 5;
+ element.text = 'test:';
+ flush();
+ assert.isTrue(element.$.emojiSuggestions.isHidden);
+ assert.isTrue(element._hideEmojiAutocomplete);
+ });
+
+ test('emoji selector opens when a colon is typed and some substring', () => {
+ MockInteractions.focus(element.$.textarea);
+ // Needed for Safari tests. selectionStart is not updated when text is
+ // updated.
+ element.$.textarea.selectionStart = 1;
+ element.$.textarea.selectionEnd = 1;
+ element.text = ':';
+ element.$.textarea.selectionStart = 2;
+ element.$.textarea.selectionEnd = 2;
+ element.text = ':t';
+ flush();
+ assert.isFalse(element.$.emojiSuggestions.isHidden);
+ assert.equal(element._colonIndex, 0);
+ assert.isFalse(element._hideEmojiAutocomplete);
+ assert.equal(element._currentSearchString, 't');
+ });
+
+ test('emoji selector opens when a colon is typed in middle of text', () => {
+ MockInteractions.focus(element.$.textarea);
+ // Needed for Safari tests. selectionStart is not updated when text is
+ // updated.
+ element.$.textarea.selectionStart = 1;
+ element.$.textarea.selectionEnd = 1;
+ // Since selectionStart is on Chrome set always on end of text, we
+ // stub it to 1
+ const text = ': hello';
+ sinon.stub(element.$, 'textarea').value({
+ selectionStart: 1,
+ value: text,
+ textarea: {
+ focus: () => {},
+ },
+ });
+ element.text = text;
+ flush();
+ assert.isFalse(element.$.emojiSuggestions.isHidden);
+ assert.equal(element._colonIndex, 0);
+ assert.isFalse(element._hideEmojiAutocomplete);
+ assert.equal(element._currentSearchString, '');
+ });
+ test('emoji selector closes when text changes before the colon', () => {
+ const resetStub = sinon.stub(element, '_resetEmojiDropdown');
+ MockInteractions.focus(element.$.textarea);
+ flush();
+ element.$.textarea.selectionStart = 10;
+ element.$.textarea.selectionEnd = 10;
+ element.text = 'test test ';
+ element.$.textarea.selectionStart = 12;
+ element.$.textarea.selectionEnd = 12;
+ element.text = 'test test :';
+ element.$.textarea.selectionStart = 15;
+ element.$.textarea.selectionEnd = 15;
+ element.text = 'test test :smi';
+
+ assert.equal(element._currentSearchString, 'smi');
+ assert.isFalse(resetStub.called);
+ element.text = 'test test test :smi';
+ assert.isTrue(resetStub.called);
+ });
+
+ test('_resetEmojiDropdown', () => {
+ const closeSpy = sinon.spy(element, 'closeDropdown');
+ element._resetEmojiDropdown();
+ assert.equal(element._currentSearchString, '');
+ assert.isTrue(element._hideEmojiAutocomplete);
+ assert.equal(element._colonIndex, null);
+
+ element.$.emojiSuggestions.open();
+ flush();
+ element._resetEmojiDropdown();
+ assert.isTrue(closeSpy.called);
+ });
+
+ test('_determineSuggestions', () => {
+ const emojiText = 'tear';
+ const formatSpy = sinon.spy(element, '_formatSuggestions');
+ element._determineSuggestions(emojiText);
+ assert.isTrue(formatSpy.called);
+ assert.isTrue(
+ formatSpy.lastCall.calledWithExactly([
+ {
+ dataValue: '😂',
+ value: '😂',
+ match: "tears :')",
+ text: "😂 tears :')",
+ },
+ {dataValue: '😢', value: '😢', match: 'tear', text: '😢 tear'},
+ ])
+ );
+ });
+
+ test('_formatSuggestions', () => {
+ const matchedSuggestions = [
+ {value: '😢', match: 'tear'},
+ {value: '😂', match: 'tears'},
+ ];
+ element._formatSuggestions(matchedSuggestions);
+ assert.deepEqual(
+ [
+ {value: '😢', dataValue: '😢', match: 'tear', text: '😢 tear'},
+ {value: '😂', dataValue: '😂', match: 'tears', text: '😂 tears'},
+ ],
+ element._suggestions
+ );
+ });
+
+ test('_handleEmojiSelect', () => {
+ element.$.textarea.selectionStart = 16;
+ element.$.textarea.selectionEnd = 16;
+ element.text = 'test test :tears';
+ element._colonIndex = 10;
+ const selectedItem = ({dataset: {value: '😂'}} as unknown) as HTMLElement;
+ const event = new CustomEvent<ItemSelectedEvent>('item-selected', {
+ detail: {trigger: 'click', selected: selectedItem},
+ });
+ element._handleEmojiSelect(event);
+ assert.equal(element.text, 'test test 😂');
+ });
+
+ test('_updateCaratPosition', () => {
+ element.$.textarea.selectionStart = 4;
+ element.$.textarea.selectionEnd = 4;
+ element.text = 'test';
+ element._updateCaratPosition();
+ assert.deepEqual(
+ element.$.hiddenText.innerHTML,
+ element.text + element.$.caratSpan.outerHTML
+ );
+ });
+
+ test('newline receives matching indentation', async () => {
+ const indentCommand = sinon.stub(document, 'execCommand');
+ element.$.textarea.value = ' a';
+ element._handleEnterByKey(
+ new CustomEvent('keydown', {
+ detail: {keyboardEvent: {keyCode: 13}},
+ }) as CustomKeyboardEvent
+ );
+ await flush();
+ assert.deepEqual(indentCommand.args[0], ['insertText', false, '\n ']);
+ });
+
+ test('ctrl+enter and meta+enter do not indent', async () => {
+ const indentCommand = sinon.stub(document, 'execCommand');
+ element.$.textarea.value = ' a';
+ element._handleEnterByKey(
+ new CustomEvent('keydown', {
+ detail: {keyboardEvent: {keyCode: 13, ctrlKey: true}},
+ }) as CustomKeyboardEvent
+ );
+ await flush();
+ assert.isTrue(indentCommand.notCalled);
+
+ element._handleEnterByKey(
+ new CustomEvent('keydown', {
+ detail: {keyboardEvent: {keyCode: 13, metaKey: true}},
+ }) as CustomKeyboardEvent
+ );
+ await flush();
+ assert.isTrue(indentCommand.notCalled);
+ });
+
+ test('emoji dropdown is closed when iron-overlay-closed is fired', () => {
+ const resetSpy = sinon.spy(element, '_resetEmojiDropdown');
+ element.$.emojiSuggestions.dispatchEvent(
+ new CustomEvent('dropdown-closed', {
+ composed: true,
+ bubbles: true,
+ })
+ );
+ assert.isTrue(resetSpy.called);
+ });
+
+ test('_onValueChanged fires bind-value-changed', () => {
+ const listenerStub = sinon.stub();
+ const eventObject = new CustomEvent('bind-value-changed', {
+ detail: {currentTarget: {focused: false}, value: ''},
+ });
+ element.addEventListener('bind-value-changed', listenerStub);
+ element._onValueChanged(eventObject);
+ assert.isTrue(listenerStub.called);
+ });
+
+ suite('keyboard shortcuts', () => {
+ function setupDropdown() {
+ MockInteractions.focus(element.$.textarea);
+ element.$.textarea.selectionStart = 1;
+ element.$.textarea.selectionEnd = 1;
+ element.text = ':';
+ element.$.textarea.selectionStart = 1;
+ element.$.textarea.selectionEnd = 2;
+ element.text = ':1';
+ flush();
+ }
+
+ test('escape key', () => {
+ const resetSpy = sinon.spy(element, '_resetEmojiDropdown');
+ MockInteractions.pressAndReleaseKeyOn(element.$.textarea, 27);
+ assert.isFalse(resetSpy.called);
+ setupDropdown();
+ MockInteractions.pressAndReleaseKeyOn(element.$.textarea, 27);
+ assert.isTrue(resetSpy.called);
+ assert.isFalse(!element.$.emojiSuggestions.isHidden);
+ });
+
+ test('up key', () => {
+ const upSpy = sinon.spy(element.$.emojiSuggestions, 'cursorUp');
+ MockInteractions.pressAndReleaseKeyOn(element.$.textarea, 38);
+ assert.isFalse(upSpy.called);
+ setupDropdown();
+ MockInteractions.pressAndReleaseKeyOn(element.$.textarea, 38);
+ assert.isTrue(upSpy.called);
+ });
+
+ test('down key', () => {
+ const downSpy = sinon.spy(element.$.emojiSuggestions, 'cursorDown');
+ MockInteractions.pressAndReleaseKeyOn(element.$.textarea, 40);
+ assert.isFalse(downSpy.called);
+ setupDropdown();
+ MockInteractions.pressAndReleaseKeyOn(element.$.textarea, 40);
+ assert.isTrue(downSpy.called);
+ });
+
+ test('enter key', () => {
+ const enterSpy = sinon.spy(element.$.emojiSuggestions, 'getCursorTarget');
+ MockInteractions.pressAndReleaseKeyOn(element.$.textarea, 13);
+ assert.isFalse(enterSpy.called);
+ setupDropdown();
+ MockInteractions.pressAndReleaseKeyOn(element.$.textarea, 13);
+ assert.isTrue(enterSpy.called);
+ flush();
+ assert.equal(element.text, '💯');
+ });
+
+ test('enter key - ignored on just colon without more information', () => {
+ const enterSpy = sinon.spy(element.$.emojiSuggestions, 'getCursorTarget');
+ MockInteractions.pressAndReleaseKeyOn(element.$.textarea, 13);
+ assert.isFalse(enterSpy.called);
+ MockInteractions.focus(element.$.textarea);
+ element.$.textarea.selectionStart = 1;
+ element.$.textarea.selectionEnd = 1;
+ element.text = ':';
+ flush();
+ MockInteractions.pressAndReleaseKeyOn(element.$.textarea, 13);
+ assert.isFalse(enterSpy.called);
+ });
+ });
+
+ suite('gr-textarea monospace', () => {
+ // gr-textarea set monospace class in the ready() method.
+ // In Polymer2, ready() is called from the fixture(...) method,
+ // If ready() is called again later, some nested elements doesn't
+ // handle it correctly. A separate test-fixture is used to set
+ // properties before ready() is called.
+
+ let element: GrTextarea;
+
+ setup(() => {
+ element = monospaceFixture.instantiate() as GrTextarea;
+ });
+
+ test('monospace is set properly', () => {
+ assert.isTrue(element.classList.contains('monospace'));
+ });
+ });
+
+ suite('gr-textarea hideBorder', () => {
+ // gr-textarea set noBorder class in the ready() method.
+ // In Polymer2, ready() is called from the fixture(...) method,
+ // If ready() is called again later, some nested elements doesn't
+ // handle it correctly. A separate test-fixture is used to set
+ // properties before ready() is called.
+
+ let element: GrTextarea;
+
+ setup(() => {
+ element = hideBorderFixture.instantiate() as GrTextarea;
+ });
+
+ test('hideBorder is set properly', () => {
+ assert.isTrue(element.$.textarea.classList.contains('noBorder'));
+ });
+ });
+});
diff --git a/proto/entities.proto b/proto/entities.proto
index 84c7fbd..d4ff736 100644
--- a/proto/entities.proto
+++ b/proto/entities.proto
@@ -35,7 +35,6 @@
message Change {
required Change_Id change_id = 1;
optional Change_Key change_key = 2;
- optional int32 row_version = 3;
optional fixed64 created_on = 4;
optional fixed64 last_updated_on = 5;
optional Account_Id owner_account_id = 7;
@@ -54,6 +53,7 @@
optional PatchSet_Id cherry_pick_of = 24;
// Deleted fields, should not be reused:
+ reserved 3; // row_version
reserved 6; // sortkey
reserved 9; // open
reserved 11; // nbrPatchSets