Replace Polymer Legacy this.debounce with simple debounce utility
Change-Id: I23e4388d047062cb093144978d0c3a646bf8517f
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
index a1ab580..883e518 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
@@ -174,6 +174,7 @@
import {aPluginHasRegistered$} from '../../../services/checks/checks-model';
import {Subject} from 'rxjs';
import {GrRelatedChangesListExperimental} from '../gr-related-changes-list-experimental/gr-related-changes-list-experimental';
+import {debounce, DelayedTask} from '../../../utils/async-util';
const CHANGE_ID_ERROR = {
MISMATCH: 'mismatch',
@@ -244,10 +245,6 @@
export type ChangeViewPatchRange = Partial<PatchRange>;
-const DEBOUNCER_REPLY_OVERLAY_REFIT = 'reply-overlay-refit';
-
-const DEBOUNCER_SCROLL = 'scroll';
-
@customElement('gr-change-view')
export class GrChangeView extends KeyboardShortcutMixin(
LegacyElementMixin(PolymerElement)
@@ -593,6 +590,10 @@
disconnected$ = new Subject();
+ private replyRefitTask?: DelayedTask;
+
+ private scrollTask?: DelayedTask;
+
/** @override */
ready() {
super.ready();
@@ -715,8 +716,8 @@
'visibilitychange',
this.handleVisibilityChange
);
- this.cancelDebouncer(DEBOUNCER_REPLY_OVERLAY_REFIT);
- this.cancelDebouncer(DEBOUNCER_SCROLL);
+ this.replyRefitTask?.cancel();
+ this.scrollTask?.cancel();
if (this._updateCheckTimerHandle) {
this._cancelUpdateCheckTimer();
@@ -1241,11 +1242,9 @@
_handleReplyAutogrow() {
// If the textarea resizes, we need to re-fit the overlay.
- this.debounce(
- DEBOUNCER_REPLY_OVERLAY_REFIT,
- () => {
- this.$.replyOverlay.refit();
- },
+ this.replyRefitTask = debounce(
+ this.replyRefitTask,
+ () => this.$.replyOverlay.refit(),
REPLY_REFIT_DEBOUNCE_INTERVAL_MS
);
}
@@ -1259,11 +1258,9 @@
}
readonly handleScroll = () => {
- this.debounce(
- DEBOUNCER_SCROLL,
- () => {
- this.viewState.scrollTop = document.body.scrollTop;
- },
+ this.scrollTask = debounce(
+ this.scrollTask,
+ () => (this.viewState.scrollTop = document.body.scrollTop),
150
);
};
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 defaf0fd..7c5d30f 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
@@ -31,7 +31,7 @@
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-file-list_html';
-import {asyncForeach} from '../../../utils/async-util';
+import {asyncForeach, debounce, DelayedTask} from '../../../utils/async-util';
import {
KeyboardShortcutMixin,
Modifier,
@@ -154,8 +154,6 @@
export type FileNameToReviewedFileInfoMap = {[name: string]: ReviewedFileInfo};
-const DEBOUNCER_LOADING_CHANGE = 'loading-change';
-
/**
* Type for FileInfo
*
@@ -283,6 +281,8 @@
private _cancelForEachDiff?: () => void;
+ loadingTask?: DelayedTask;
+
@property({
type: Boolean,
computed:
@@ -418,7 +418,7 @@
disconnectedCallback() {
this.fileCursor.unsetCursor();
this._cancelDiffs();
- this.cancelDebouncer(DEBOUNCER_LOADING_CHANGE);
+ this.loadingTask?.cancel();
super.disconnectedCallback();
}
@@ -1606,8 +1606,8 @@
* are reasonably fast.
*/
_loadingChanged(loading?: boolean) {
- this.debounce(
- DEBOUNCER_LOADING_CHANGE,
+ this.loadingTask = debounce(
+ this.loadingTask,
() => {
// Only show set the loading if there have been files loaded to show. In
// this way, the gray loading style is not shown on initial loads.
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.js b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.js
index ce20435..a1e227b 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.js
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.js
@@ -1103,13 +1103,13 @@
element.reload().then(() => {
assert.isFalse(element._loading);
- element.flushDebouncer('loading-change');
+ element.loadingTask.flush();
assert.isFalse(element.classList.contains('loading'));
done();
});
assert.isTrue(element._loading);
assert.isFalse(element.classList.contains('loading'));
- element.flushDebouncer('loading-change');
+ element.loadingTask.flush();
assert.isTrue(element.classList.contains('loading'));
});
@@ -1119,7 +1119,7 @@
element.patchRange = {patchNum: 12};
element.reload();
assert.isTrue(element._loading);
- element.flushDebouncer('loading-change');
+ element.loadingTask.flush();
assert.isFalse(element.classList.contains('loading'));
});
});
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts
index a735147..61c7a25 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts
@@ -109,6 +109,7 @@
import {pluralize} from '../../../utils/string-util';
import {fireAlert, fireEvent, fireServerError} from '../../../utils/event-util';
import {ErrorCallback} from '../../../api/rest';
+import {debounce, DelayedTask} from '../../../utils/async-util';
const STORAGE_DEBOUNCE_INTERVAL_MS = 400;
@@ -167,8 +168,6 @@
};
}
-const DEBOUNCER_STORE = 'store';
-
@customElement('gr-reply-dialog')
export class GrReplyDialog extends KeyboardShortcutMixin(
LegacyElementMixin(PolymerElement)
@@ -377,6 +376,8 @@
private readonly jsAPI = appContext.jsApiService;
+ private storeTask?: DelayedTask;
+
get keyBindings() {
return {
esc: '_handleEscKey',
@@ -428,7 +429,7 @@
/** @override */
disconnectedCallback() {
- this.cancelDebouncer(DEBOUNCER_STORE);
+ this.storeTask?.cancel();
super.disconnectedCallback();
}
@@ -1338,8 +1339,8 @@
}
_draftChanged(newDraft: string, oldDraft?: string) {
- this.debounce(
- DEBOUNCER_STORE,
+ this.storeTask = debounce(
+ this.storeTask,
() => {
if (!newDraft.length && oldDraft) {
// If the draft has been modified to be empty, then erase the storage
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.js b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.js
index 466f7bd..8d59bc8 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.js
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.js
@@ -801,12 +801,12 @@
const location = element._getStorageLocation();
element.draft = firstEdit;
- element.flushDebouncer('store');
+ element.storeTask.flush();
assert.isTrue(setDraftCommentStub.calledWith(location, firstEdit));
element.draft = '';
- element.flushDebouncer('store');
+ element.storeTask.flush();
assert.isTrue(eraseDraftCommentStub.calledWith(location));
});
diff --git a/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager.ts b/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager.ts
index 9b7bc69..ae808ba 100644
--- a/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager.ts
+++ b/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager.ts
@@ -39,6 +39,7 @@
ShowErrorEvent,
} from '../../../types/events';
import {windowLocationReload} from '../../../utils/dom-util';
+import {debounce, DelayedTask} from '../../../utils/async-util';
const HIDE_ALERT_TIMEOUT_MS = 5000;
const CHECK_SIGN_IN_INTERVAL_MS = 60 * 1000;
@@ -74,8 +75,6 @@
};
}
-const DEBOUNCER_CHECK_LOGGED_IN = 'checkLoggedIn';
-
@customElement('gr-error-manager')
export class GrErrorManager extends LegacyElementMixin(PolymerElement) {
static get template() {
@@ -117,6 +116,8 @@
private readonly restApiService = appContext.restApiService;
+ private checkLoggedInTask?: DelayedTask;
+
/** @override */
connectedCallback() {
super.connectedCallback();
@@ -157,7 +158,7 @@
this.handleVisibilityChange
);
document.removeEventListener('show-auth-required', this.handleAuthRequired);
- this.cancelDebouncer(DEBOUNCER_CHECK_LOGGED_IN);
+ this.checkLoggedInTask?.cancel();
if (this._authErrorHandlerDeregistrationHook) {
this._authErrorHandlerDeregistrationHook();
@@ -414,9 +415,9 @@
};
_requestCheckLoggedIn() {
- this.debounce(
- DEBOUNCER_CHECK_LOGGED_IN,
- this._checkSignedIn,
+ this.checkLoggedInTask = debounce(
+ this.checkLoggedInTask,
+ () => this._checkSignedIn(),
CHECK_SIGN_IN_INTERVAL_MS
);
}
@@ -491,7 +492,7 @@
}
private readonly handleWindowFocus = () => {
- this.flushDebouncer(DEBOUNCER_CHECK_LOGGED_IN);
+ this.checkLoggedInTask?.flush();
};
private readonly handleShowErrorDialog = (e: ShowErrorEvent) => {
diff --git a/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager_test.js b/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager_test.js
index 9ddc628..7d643a0 100644
--- a/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager_test.js
+++ b/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager_test.js
@@ -298,7 +298,7 @@
// now fake authed
fetchStub.returns(Promise.resolve({status: 204}));
element.handleWindowFocus();
- element.flushDebouncer('checkLoggedIn');
+ element.checkLoggedInTask.flush();
await flush();
assert.isTrue(refreshStub.called);
assert.isTrue(hideToastSpy.called);
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.ts b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.ts
index fb28e2b..5ccb3b3 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.ts
@@ -30,6 +30,7 @@
import {GrDiffBuilderElement} from '../gr-diff-builder/gr-diff-builder-element';
import {FILE} from '../gr-diff/gr-diff-line';
import {getRange, getSide} from '../gr-diff/gr-diff-utils';
+import {debounce, DelayedTask} from '../../../utils/async-util';
interface SidedRange {
side: Side;
@@ -53,8 +54,6 @@
rootId: string;
}
-const DEBOUNCER_SELECTION_CHANGE = 'selectionChange';
-
@customElement('gr-diff-highlight')
export class GrDiffHighlight extends LegacyElementMixin(PolymerElement) {
static get template() {
@@ -73,6 +72,8 @@
@property({type: Object, notify: true})
selectedRange?: SidedRange;
+ private selectionChangeTask?: DelayedTask;
+
/** @override */
created() {
super.created();
@@ -89,7 +90,7 @@
/** @override */
disconnectedCallback() {
- this.cancelDebouncer(DEBOUNCER_SELECTION_CHANGE);
+ this.selectionChangeTask?.cancel();
super.disconnectedCallback();
}
@@ -127,8 +128,8 @@
// quick 'c' press after the selection change. If you wait less than 10
// ms, then you will have about 50 _handleSelection calls when doing a
// simple drag for select.
- this.debounce(
- DEBOUNCER_SELECTION_CHANGE,
+ this.selectionChangeTask = debounce(
+ this.selectionChangeTask,
() => this._handleSelection(selection, isMouseUp),
10
);
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.js b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.js
index 0bb7e7e..53a2404 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.js
@@ -158,6 +158,7 @@
element.reload();
// Multiple cascading microtasks are scheduled.
await flush();
+ await flush();
// Reporting can be called with other parameters (ex. PluginsLoaded),
// but only 'Diff Total Render' is important in this test.
assert.equal(
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor.ts b/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor.ts
index 178f1aa..4b91ead 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor.ts
@@ -32,6 +32,7 @@
import {customElement, property} from '@polymer/decorators';
import {DiffContent} from '../../../types/diff';
import {Side} from '../../../constants/constants';
+import {debounce, DelayedTask} from '../../../utils/async-util';
const WHOLE_FILE = -1;
@@ -63,8 +64,6 @@
*/
const MAX_GROUP_SIZE = 120;
-const DEBOUNCER_RESET_IS_SCROLLING = 'resetIsScrolling';
-
/**
* Converts the API's `DiffContent`s to `GrDiffGroup`s for rendering.
*
@@ -113,6 +112,8 @@
@property({type: Boolean})
_isScrolling?: boolean;
+ private resetIsScrollingTask?: DelayedTask;
+
/** @override */
connectedCallback() {
super.connectedCallback();
@@ -121,7 +122,7 @@
/** @override */
disconnectedCallback() {
- this.cancelDebouncer(DEBOUNCER_RESET_IS_SCROLLING);
+ this.resetIsScrollingTask?.cancel();
this.cancel();
window.removeEventListener('scroll', this.handleWindowScroll);
super.disconnectedCallback();
@@ -129,11 +130,9 @@
private readonly handleWindowScroll = () => {
this._isScrolling = true;
- this.debounce(
- DEBOUNCER_RESET_IS_SCROLLING,
- () => {
- this._isScrolling = false;
- },
+ this.resetIsScrollingTask = debounce(
+ this.resetIsScrollingTask,
+ () => (this._isScrolling = false),
50
);
};
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 d36c1fd..3ee87c0 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.ts
@@ -75,6 +75,7 @@
} from '../../../api/diff';
import {isSafari} from '../../../utils/dom-util';
import {assertIsDefined} from '../../../utils/common-util';
+import {debounce, DelayedTask} from '../../../utils/async-util';
const NO_NEWLINE_BASE = 'No newline at end of base file.';
const NO_NEWLINE_REVISION = 'No newline at end of revision file.';
@@ -92,8 +93,6 @@
*/
const COMMIT_MSG_LINE_LENGTH = 72;
-const RENDER_DIFF_TABLE_DEBOUNCE_NAME = 'renderDiffTable';
-
export interface LineOfInterest {
number: number;
leftSide: boolean;
@@ -283,6 +282,8 @@
@property({type: Array})
layers?: DiffLayer[];
+ private renderDiffTableTask?: DelayedTask;
+
/** @override */
created() {
super.created();
@@ -302,7 +303,7 @@
/** @override */
disconnectedCallback() {
- this.cancelDebouncer(RENDER_DIFF_TABLE_DEBOUNCE_NAME);
+ this.renderDiffTableTask?.cancel();
this._unobserveIncrementalNodes();
this._unobserveNodes();
super.disconnectedCallback();
@@ -475,7 +476,7 @@
/** Cancel any remaining diff builder rendering work. */
cancel() {
this.$.diffBuilder.cancel();
- this.cancelDebouncer(RENDER_DIFF_TABLE_DEBOUNCE_NAME);
+ this.renderDiffTableTask?.cancel();
}
getCursorStops(): Array<HTMLElement | AbortStop> {
@@ -774,7 +775,7 @@
* render once.
*/
_debounceRenderDiffTable() {
- this.debounce(RENDER_DIFF_TABLE_DEBOUNCE_NAME, () =>
+ this.renderDiffTableTask = debounce(this.renderDiffTableTask, () =>
this._renderDiffTable()
);
}
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.js b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.js
index c6bd8d6..1827def 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.js
@@ -660,14 +660,14 @@
change_type: 'MODIFIED',
content: [{skip: 66}],
};
- element.flushDebouncer('renderDiffTable');
+ element.renderDiffTableTask.flush();
});
test('change in preferences re-renders diff', () => {
sinon.stub(element, '_renderDiffTable');
element.prefs = {
...MINIMAL_PREFS, time_format: 'HHMM_12'};
- element.flushDebouncer('renderDiffTable');
+ element.renderDiffTableTask.flush();
assert.isTrue(element._renderDiffTable.called);
});
@@ -676,14 +676,14 @@
const newPrefs1 = {...MINIMAL_PREFS,
line_wrapping: true};
element.prefs = newPrefs1;
- element.flushDebouncer('renderDiffTable');
+ element.renderDiffTableTask.flush();
assert.isTrue(element._renderDiffTable.called);
stub.reset();
const newPrefs2 = {...newPrefs1};
delete newPrefs2.line_wrapping;
element.prefs = newPrefs2;
- element.flushDebouncer('renderDiffTable');
+ element.renderDiffTableTask.flush();
assert.isTrue(element._renderDiffTable.called);
});
@@ -693,7 +693,7 @@
element.noRenderOnPrefsChange = true;
element.prefs = {
...MINIMAL_PREFS, time_format: 'HHMM_12'};
- element.flushDebouncer('renderDiffTable');
+ element.renderDiffTableTask.flush();
assert.isFalse(element._renderDiffTable.called);
});
});
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 86dd5b6..6b29e85 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
@@ -45,6 +45,7 @@
import {appContext} from '../../../services/app-context';
import {ErrorCallback} from '../../../api/rest';
import {assertIsDefined} from '../../../utils/common-util';
+import {debounce, DelayedTask} from '../../../utils/async-util';
const RESTORED_MESSAGE = 'Content restored from a previous edit.';
const SAVING_MESSAGE = 'Saving changes...';
@@ -55,8 +56,6 @@
const STORAGE_DEBOUNCE_INTERVAL_MS = 100;
-const DEBOUNCER_STORE = 'store';
-
@customElement('gr-editor-view')
export class GrEditorView extends KeyboardShortcutMixin(
LegacyElementMixin(PolymerElement)
@@ -123,6 +122,8 @@
private readonly storage = new GrStorage();
+ private storeTask?: DelayedTask;
+
reporting = appContext.reportingService;
get keyBindings() {
@@ -149,7 +150,7 @@
/** @override */
disconnectedCallback() {
- this.cancelDebouncer(DEBOUNCER_STORE);
+ this.storeTask?.cancel();
super.disconnectedCallback();
}
@@ -355,8 +356,8 @@
}
_handleContentChange(e: CustomEvent<{value: string}>) {
- this.debounce(
- DEBOUNCER_STORE,
+ this.storeTask = debounce(
+ this.storeTask,
() => {
const content = e.detail.value;
if (content) {
diff --git a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_test.js b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_test.js
index 8512545..a29d45d 100644
--- a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_test.js
+++ b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_test.js
@@ -109,7 +109,7 @@
bubbles: true, composed: true,
detail: {value: 'new content value'},
}));
- element.flushDebouncer('store');
+ element.storeTask.flush();
flush();
assert.equal(element._newContent, 'new content value');
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.ts b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.ts
index 9993509..a4b0446 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.ts
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.ts
@@ -29,6 +29,7 @@
import {PaperInputElementExt} from '../../../types/types';
import {CustomKeyboardEvent} from '../../../types/events';
import {fireEvent} from '../../../utils/event-util';
+import {debounce, DelayedTask} from '../../../utils/async-util';
const TOKENIZE_REGEX = /(?:[^\s"]+|"[^"]*")+/g;
const DEBOUNCE_WAIT_MS = 200;
@@ -65,8 +66,6 @@
AutocompleteCommitEventDetail
>;
-const DEBOUNCER_UPDATE_SUGGESTIONS = 'update-suggestions';
-
@customElement('gr-autocomplete')
export class GrAutocomplete extends KeyboardShortcutMixin(
LegacyElementMixin(PolymerElement)
@@ -200,6 +199,8 @@
@property({type: Object})
_selected: HTMLElement | null = null;
+ private updateSuggestionsTask?: DelayedTask;
+
get _nativeInput() {
// In Polymer 2 inputElement isn't nativeInput anymore
return (this.$.input.$.nativeInput ||
@@ -215,7 +216,7 @@
/** @override */
disconnectedCallback() {
document.removeEventListener('click', this.handleBodyClick);
- this.cancelDebouncer(DEBOUNCER_UPDATE_SUGGESTIONS);
+ this.updateSuggestionsTask?.cancel();
super.disconnectedCallback();
}
@@ -329,7 +330,11 @@
if (noDebounce) {
update();
} else {
- this.debounce(DEBOUNCER_UPDATE_SUGGESTIONS, update, DEBOUNCE_WAIT_MS);
+ this.updateSuggestionsTask = debounce(
+ this.updateSuggestionsTask,
+ update,
+ DEBOUNCE_WAIT_MS
+ );
}
}
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.js b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.js
index 2dda586..d72007e 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.js
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.js
@@ -215,19 +215,18 @@
});
test('noDebounce=false debounces the query', () => {
+ const clock = sinon.useFakeTimers();
const queryStub = sinon.spy(() => Promise.resolve([]));
- let callback;
- const debounceStub = sinon.stub(element, 'debounce').callsFake(
- (name, cb) => { callback = cb; });
element.query = queryStub;
element.noDebounce = false;
focusOnInput(element);
element.text = 'a';
+
+ // not called right away
assert.isFalse(queryStub.called);
- assert.isTrue(debounceStub.called);
- assert.equal(debounceStub.lastCall.args[2], 200);
- assert.isFunction(callback);
- callback();
+
+ // but called after a while
+ clock.tick(1000);
assert.isTrue(queryStub.called);
});
diff --git a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts
index 2807730..b64f399 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts
+++ b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts
@@ -62,6 +62,7 @@
import {fireAlert} from '../../../utils/event-util';
import {pluralize} from '../../../utils/string-util';
import {assertIsDefined} from '../../../utils/common-util';
+import {debounce, DelayedTask} from '../../../utils/async-util';
const STORAGE_DEBOUNCE_INTERVAL = 400;
const TOAST_DEBOUNCE_INTERVAL = 200;
@@ -101,12 +102,6 @@
};
}
-const DEBOUNCER_FIRE_UPDATE = 'fire-update';
-
-const DEBOUNCER_STORE = 'store';
-
-const DEBOUNCER_DRAFT_TOAST = 'draft-toast';
-
@customElement('gr-comment')
export class GrComment extends KeyboardShortcutMixin(
LegacyElementMixin(PolymerElement)
@@ -281,6 +276,12 @@
reporting = appContext.reportingService;
+ private fireUpdateTask?: DelayedTask;
+
+ private storeTask?: DelayedTask;
+
+ private draftToastTask?: DelayedTask;
+
/** @override */
connectedCallback() {
super.connectedCallback();
@@ -299,9 +300,9 @@
/** @override */
disconnectedCallback() {
- this.cancelDebouncer(DEBOUNCER_FIRE_UPDATE);
- this.cancelDebouncer(DEBOUNCER_STORE);
- this.cancelDebouncer(DEBOUNCER_DRAFT_TOAST);
+ this.fireUpdateTask?.cancel();
+ this.storeTask?.cancel();
+ this.draftToastTask?.cancel();
if (this.textarea) {
this.textarea.closeDropdown();
}
@@ -495,7 +496,7 @@
_eraseDraftComment() {
// Prevents a race condition in which removing the draft comment occurs
// prior to it being saved.
- this.cancelDebouncer(DEBOUNCER_STORE);
+ this.storeTask?.cancel();
assertIsDefined(this.comment?.path, 'comment.path');
assertIsDefined(this.changeNum, 'changeNum');
@@ -545,7 +546,7 @@
}
_fireUpdate() {
- this.debounce(DEBOUNCER_FIRE_UPDATE, () => {
+ this.fireUpdateTask = debounce(this.fireUpdateTask, () => {
this.dispatchEvent(
new CustomEvent('comment-update', {
detail: this._getEventPayload(),
@@ -653,8 +654,8 @@
: this._getPatchNum();
const {path, line, range} = this.comment;
if (path) {
- this.debounce(
- DEBOUNCER_STORE,
+ this.storeTask = debounce(
+ this.storeTask,
() => {
const message = this._messageText;
if (this.changeNum === undefined) {
@@ -736,7 +737,7 @@
}
_fireDiscard() {
- this.cancelDebouncer(DEBOUNCER_FIRE_UPDATE);
+ this.fireUpdateTask?.cancel();
this.dispatchEvent(
new CustomEvent('comment-discard', {
detail: this._getEventPayload(),
@@ -859,7 +860,7 @@
// Cancel the debouncer so that error toasts from the error-manager will
// not be overridden.
- this.cancelDebouncer(DEBOUNCER_DRAFT_TOAST);
+ this.draftToastTask?.cancel();
this._updateRequestToast(
this._numPendingDraftRequests.number,
/* requestFailed=*/ true
@@ -868,8 +869,8 @@
_updateRequestToast(numPending: number, requestFailed?: boolean) {
const message = this._getSavingMessage(numPending, requestFailed);
- this.debounce(
- DEBOUNCER_DRAFT_TOAST,
+ this.draftToastTask = debounce(
+ this.draftToastTask,
() => {
// Note: the event is fired on the body rather than this element because
// this element may not be attached by the time this executes, in which
diff --git a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.js b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.js
index f28a7fe..b5205a6 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.js
+++ b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.js
@@ -768,7 +768,7 @@
});
MockInteractions.tap(element.shadowRoot
.querySelector('.cancel'));
- element.flushDebouncer('fire-update');
+ element.fireUpdateTask.flush();
element._messageText = '';
flush();
MockInteractions.pressAndReleaseKeyOn(element.textarea, 27); // esc
@@ -867,21 +867,20 @@
test('draft saving/editing', done => {
const dispatchEventStub = sinon.stub(element, 'dispatchEvent');
- const cancelDebounce = sinon.stub(element, 'cancelDebouncer');
element.draft = true;
flush();
MockInteractions.tap(element.shadowRoot
.querySelector('.edit'));
element._messageText = 'good news, everyone!';
- element.flushDebouncer('fire-update');
- element.flushDebouncer('store');
+ element.fireUpdateTask.flush();
+ element.storeTask.flush();
assert.equal(dispatchEventStub.lastCall.args[0].type, 'comment-update');
assert.isTrue(dispatchEventStub.calledTwice);
element._messageText = 'good news, everyone!';
- element.flushDebouncer('fire-update');
- element.flushDebouncer('store');
+ element.fireUpdateTask.flush();
+ element.storeTask.flush();
assert.isTrue(dispatchEventStub.calledTwice);
MockInteractions.tap(element.shadowRoot
@@ -892,7 +891,7 @@
element._xhrPromise.then(draft => {
assert.equal(dispatchEventStub.lastCall.args[0].type, 'comment-save');
- assert(cancelDebounce.calledWith('store'));
+ assert.isFalse(element.storeTask.isActive());
assert.deepEqual(dispatchEventStub.lastCall.args[0].detail, {
comment: {
@@ -940,8 +939,8 @@
MockInteractions.tap(element.shadowRoot
.querySelector('.edit'));
element._messageText = 'good news, everyone!';
- element.flushDebouncer('fire-update');
- element.flushDebouncer('store');
+ element.fireUpdateTask.flush();
+ element.storeTask.flush();
element.disabled = true;
MockInteractions.tap(element.shadowRoot
@@ -1027,7 +1026,7 @@
const eraseStub = sinon.stub(element.storage, 'eraseDraftComment');
element._messageText = 'test text';
flush();
- element.flushDebouncer('store');
+ element.storeTask.flush();
assert.isTrue(storeStub.called);
assert.equal(storeStub.lastCall.args[1], 'test text');
@@ -1042,7 +1041,7 @@
const storeStub = sinon.stub(element.storage, 'setDraftComment');
element._messageText = 'test text';
flush();
- element.flushDebouncer('store');
+ if (element.storeTask) element.storeTask.flush();
assert.isFalse(storeStub.called);
element._handleCancel({preventDefault: () => {}});
diff --git a/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.ts b/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.ts
index c849fac..06a91ef 100644
--- a/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.ts
+++ b/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.ts
@@ -26,6 +26,7 @@
import {fireAlert, fireEvent} from '../../../utils/event-util';
import {appContext} from '../../../services/app-context';
import {KnownExperimentId} from '../../../services/flags/flags';
+import {debounce, DelayedTask} from '../../../utils/async-util';
const RESTORED_MESSAGE = 'Content restored from a previous edit.';
const STORAGE_DEBOUNCE_INTERVAL_MS = 400;
@@ -36,8 +37,6 @@
}
}
-const DEBOUNCER_STORE = 'store';
-
@customElement('gr-editable-content')
export class GrEditableContent extends LegacyElementMixin(PolymerElement) {
static get template() {
@@ -119,6 +118,8 @@
private readonly reporting = appContext.reportingService;
+ private storeTask?: DelayedTask;
+
/** @override */
ready() {
super.ready();
@@ -129,7 +130,7 @@
/** @override */
disconnectedCallback() {
- this.cancelDebouncer(DEBOUNCER_STORE);
+ this.storeTask?.cancel();
super.disconnectedCallback();
}
@@ -149,8 +150,8 @@
if (!this.storageKey) return;
const storageKey = this.storageKey;
- this.debounce(
- DEBOUNCER_STORE,
+ this.storeTask = debounce(
+ this.storeTask,
() => {
if (newContent.length) {
this.storage.setEditableContentItem(storageKey, newContent);
diff --git a/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content_test.js b/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content_test.js
index b99b119..7977f4d 100644
--- a/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content_test.js
+++ b/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content_test.js
@@ -126,7 +126,7 @@
element._newContent = 'new content';
flush();
- element.flushDebouncer('store');
+ element.storeTask.flush();
assert.isTrue(storeStub.called);
assert.deepEqual(
@@ -135,7 +135,7 @@
element._newContent = '';
flush();
- element.flushDebouncer('store');
+ element.storeTask.flush();
assert.isTrue(eraseStub.called);
assert.deepEqual([element.storageKey], eraseStub.lastCall.args);
diff --git a/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard-behavior.ts b/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard-behavior.ts
index 8763bbc..b1c695e 100644
--- a/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard-behavior.ts
+++ b/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard-behavior.ts
@@ -16,8 +16,6 @@
*/
import '../../../styles/shared-styles';
import {flush} from '@polymer/polymer/lib/legacy/polymer.dom';
-import {Debouncer} from '@polymer/polymer/lib/utils/debounce';
-import {timeOut} from '@polymer/polymer/lib/utils/async';
import {getRootElement} from '../../../scripts/rootElement';
import {Constructor} from '../../../utils/common-util';
import {PolymerElement} from '@polymer/polymer/polymer-element';
@@ -29,6 +27,7 @@
removeScrollLock,
} from '@polymer/iron-overlay-behavior/iron-scroll-manager';
import {ShowAlertEventDetail} from '../../../types/events';
+import {debounce, DelayedTask} from '../../../utils/async-util';
interface ReloadEventDetail {
clearPatchset?: boolean;
}
@@ -110,9 +109,9 @@
@property({type: String})
containerId = 'gr-hovercard-container';
- private hideDebouncer: Debouncer | null = null;
+ private hideTask?: DelayedTask;
- private showDebouncer: Debouncer | null = null;
+ private showTask?: DelayedTask;
private isScheduledToShow?: boolean;
@@ -142,8 +141,8 @@
}
disconnectedCallback() {
- this.cancelShowDebouncer();
- this.cancelHideDebouncer();
+ this.cancelShowTask();
+ this.cancelHideTask();
this.unlock();
super.disconnectedCallback();
}
@@ -173,24 +172,24 @@
}
readonly debounceHide = () => {
- this.cancelShowDebouncer();
+ this.cancelShowTask();
if (!this._isShowing || this.isScheduledToHide) return;
this.isScheduledToHide = true;
- this.hideDebouncer = Debouncer.debounce(
- this.hideDebouncer,
- timeOut.after(HIDE_DELAY_MS),
+ this.hideTask = debounce(
+ this.hideTask,
() => {
// This happens when hide immediately through click or mouse leave
// on the hovercard
if (!this.isScheduledToHide) return;
this.hide();
- }
+ },
+ HIDE_DELAY_MS
);
};
- cancelHideDebouncer() {
- if (this.hideDebouncer) {
- this.hideDebouncer.cancel();
+ cancelHideTask() {
+ if (this.hideTask) {
+ this.hideTask.cancel();
this.isScheduledToHide = false;
}
}
@@ -258,8 +257,8 @@
*
*/
readonly hide = (e?: MouseEvent) => {
- this.cancelHideDebouncer();
- this.cancelShowDebouncer();
+ this.cancelHideTask();
+ this.cancelShowTask();
if (!this._isShowing) {
return;
}
@@ -304,23 +303,23 @@
* Shows/opens the hovercard with the given delay.
*/
debounceShowBy(delayMs: number) {
- this.cancelHideDebouncer();
+ this.cancelHideTask();
if (this._isShowing || this.isScheduledToShow) return;
this.isScheduledToShow = true;
- this.showDebouncer = Debouncer.debounce(
- this.showDebouncer,
- timeOut.after(delayMs),
+ this.showTask = debounce(
+ this.showTask,
() => {
// This happens when the mouse leaves the target before the delay is over.
if (!this.isScheduledToShow) return;
this.show();
- }
+ },
+ delayMs
);
}
- cancelShowDebouncer() {
- if (this.showDebouncer) {
- this.showDebouncer.cancel();
+ cancelShowTask() {
+ if (this.showTask) {
+ this.showTask.cancel();
this.isScheduledToShow = false;
}
}
@@ -337,8 +336,8 @@
* `mousenter` event on the hovercard's `target` element.
*/
readonly show = () => {
- this.cancelHideDebouncer();
- this.cancelShowDebouncer();
+ this.cancelHideTask();
+ this.cancelShowTask();
if (this._isShowing || !this.container) {
return;
}
@@ -483,12 +482,12 @@
ready(): void;
removeListeners(): void;
debounceHide(): void;
- cancelHideDebouncer(): void;
+ cancelHideTask(): void;
dispatchEventThroughTarget(eventName: string, detail?: unknown): void;
hide(e?: MouseEvent): void;
debounceShow(): void;
debounceShowBy(delayMs: number): void;
- cancelShowDebouncer(): void;
+ cancelShowTask(): void;
show(): void;
updatePosition(): void;
updatePositionTo(position: string): void;
diff --git a/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard_test.js b/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard_test.js
index 628b1e9..27ef23f 100644
--- a/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard_test.js
+++ b/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard_test.js
@@ -121,7 +121,7 @@
await enterPromise;
assert.isTrue(element.isScheduledToShow);
- element.showDebouncer.flush();
+ element.showTask.flush();
assert.isTrue(element._isShowing);
assert.isFalse(element.isScheduledToShow);
@@ -130,7 +130,7 @@
await leavePromise;
assert.isTrue(element.isScheduledToHide);
assert.isTrue(element._isShowing);
- element.hideDebouncer.flush();
+ element.hideTask.flush();
assert.isFalse(element.isScheduledToShow);
assert.isFalse(element._isShowing);
diff --git a/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view.ts b/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view.ts
index 71b8bc7..86a2d1a 100644
--- a/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view.ts
+++ b/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view.ts
@@ -25,6 +25,7 @@
import {page} from '../../../utils/page-wrapper-utils';
import {property, customElement} from '@polymer/decorators';
import {fireEvent} from '../../../utils/event-util';
+import {debounce, DelayedTask} from '../../../utils/async-util';
const REQUEST_DEBOUNCE_INTERVAL_MS = 200;
@@ -34,8 +35,6 @@
}
}
-const DEBOUNCER_RELOAD = 'reload';
-
@customElement('gr-list-view')
class GrListView extends LegacyElementMixin(PolymerElement) {
static get template() {
@@ -63,9 +62,11 @@
@property({type: String})
path?: string;
+ private reloadTask?: DelayedTask;
+
/** @override */
disconnectedCallback() {
- this.cancelDebouncer(DEBOUNCER_RELOAD);
+ this.reloadTask?.cancel();
super.disconnectedCallback();
}
@@ -79,8 +80,8 @@
}
_debounceReload(filter?: string) {
- this.debounce(
- DEBOUNCER_RELOAD,
+ this.reloadTask = debounce(
+ this.reloadTask,
() => {
if (this.path) {
if (filter) {
diff --git a/polygerrit-ui/app/test/common-test-setup.ts b/polygerrit-ui/app/test/common-test-setup.ts
index 4e1662c..848eeaf 100644
--- a/polygerrit-ui/app/test/common-test-setup.ts
+++ b/polygerrit-ui/app/test/common-test-setup.ts
@@ -32,7 +32,6 @@
removeIronOverlayBackdropStyleEl,
TestKeyboardShortcutBinder,
} from './test-utils';
-import {flushDebouncers} from '@polymer/polymer/lib/utils/debounce';
import {_testOnly_getShortcutManagerInstance} from '../mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin';
import sinon, {SinonSpy} from 'sinon/pkg/sinon-esm';
import {safeTypesBridge} from '../utils/safe-types-util';
@@ -43,6 +42,7 @@
_testOnly_defaultResinReportHandler,
installPolymerResin,
} from '../scripts/polymer-resin-install';
+import {_testOnly_allTasks} from '../utils/async-util';
declare global {
interface Window {
@@ -190,19 +190,20 @@
}
}
+function cancelAllTasks() {
+ for (const task of _testOnly_allTasks.values()) {
+ console.warn('ATTENTION! A task was still active at the end of the test!');
+ task.cancel();
+ }
+}
+
teardown(() => {
sinon.restore();
cleanupTestUtils();
TestKeyboardShortcutBinder.pop();
checkGlobalSpace();
removeIronOverlayBackdropStyleEl();
- // Clean Polymer debouncer queue, so next tests will not be affected.
- // WARNING! This will most likely not do what you expect. `flushDebouncers()`
- // will only flush debouncers that were added using `enqueueDebouncer()`. So
- // this will not affect "normal" debouncers that were added using
- // `this.debounce()`. For those please be careful and cancel them using
- // `this.cancelDebouncer()` in the `detached()` lifecycle hook.
- flushDebouncers();
+ cancelAllTasks();
const testTeardownTimestampMs = new Date().getTime();
const elapsedMs = testTeardownTimestampMs - testSetupTimestampMs;
if (elapsedMs > 1000) {
diff --git a/polygerrit-ui/app/utils/async-util.ts b/polygerrit-ui/app/utils/async-util.ts
index 119b09b..2b36fee 100644
--- a/polygerrit-ui/app/utils/async-util.ts
+++ b/polygerrit-ui/app/utils/async-util.ts
@@ -42,3 +42,71 @@
return asyncForeach(array.slice(1), fn);
});
}
+
+export const _testOnly_allTasks = new Map<number, DelayedTask>();
+
+/**
+ * This is just a very simple and small wrapper around setTimeout(). Instead of
+ * the usual:
+ *
+ * const timer = window.setTimeout(() => {...do stuff...}, 123);
+ * window.clearTimeout(timer);
+ *
+ * With this class you can do:
+ *
+ * const task = new Task(() => {...do stuff...}, 123);
+ * task.cancel();
+ *
+ * It is just nicer to have an object for this instead of a number as a handle.
+ */
+export class DelayedTask {
+ private timer?: number;
+
+ constructor(private callback: () => void, waitMs = 0) {
+ this.timer = window.setTimeout(() => {
+ if (this.timer) _testOnly_allTasks.delete(this.timer);
+ this.timer = undefined;
+ if (this.callback) this.callback();
+ }, waitMs);
+ _testOnly_allTasks.set(this.timer, this);
+ }
+
+ cancel() {
+ if (this.isActive()) {
+ window.clearTimeout(this.timer);
+ if (this.timer) _testOnly_allTasks.delete(this.timer);
+ this.timer = undefined;
+ }
+ }
+
+ flush() {
+ if (this.isActive()) {
+ this.cancel();
+ if (this.callback) this.callback();
+ }
+ }
+
+ isActive() {
+ return this.timer !== undefined;
+ }
+}
+
+/**
+ * The usage pattern is:
+ *
+ * this.myDebouncedTask = debounce(this.myDebouncedTask, () => {...}, 123);
+ *
+ * It is identical to:
+ *
+ * this.myTask = new DelayedTask(() => {...}, 123);
+ *
+ * But it would cancel a potentially scheduled task beforehand.
+ */
+export function debounce(
+ existingTask: DelayedTask | undefined,
+ callback: () => void,
+ waitMs = 0
+) {
+ existingTask?.cancel();
+ return new DelayedTask(callback, waitMs);
+}