Do not reload page after clicking Cancel in apply-fix dialog

Change-Id: I5830caac655df9d3dc7058dc9674ba727685012b
(cherry picked from commit 131bf7a080f0f2772a846591979b7d3dcab964bf)
This commit is contained in:
Dmitrii Filippov 2021-04-01 18:02:27 +02:00 committed by Paladox none
parent 5c0c08071e
commit b2d7c2aadb
8 changed files with 449 additions and 299 deletions

View File

@ -155,6 +155,7 @@ import {
ThreadListModifiedEvent,
TabState,
EventType,
CloseFixPreviewEvent,
} from '../../../types/events';
import {GrButton} from '../../shared/gr-button/gr-button';
import {GrMessagesList} from '../gr-messages-list/gr-messages-list';
@ -676,7 +677,7 @@ export class GrChangeView extends KeyboardShortcutMixin(PolymerElement) {
this._handleCommitMessageCancel()
);
this.addEventListener('open-fix-preview', e => this._onOpenFixPreview(e));
this.addEventListener('close-fix-preview', () => this._onCloseFixPreview());
this.addEventListener('close-fix-preview', e => this._onCloseFixPreview(e));
window.addEventListener('scroll', this.handleScroll);
document.addEventListener('visibilitychange', this.handleVisibilityChange);
@ -742,8 +743,8 @@ export class GrChangeView extends KeyboardShortcutMixin(PolymerElement) {
this.$.applyFixDialog.open(e);
}
_onCloseFixPreview() {
this._reload();
_onCloseFixPreview(e: CloseFixPreviewEvent) {
if (e.detail.fixApplied) this._reload();
}
_handleToggleDiffMode(e: CustomKeyboardEvent) {

View File

@ -37,12 +37,14 @@ import {GrOverlay} from '../../shared/gr-overlay/gr-overlay';
import {isRobot} from '../../../utils/comment-util';
import {OpenFixPreviewEvent} from '../../../types/events';
import {appContext} from '../../../services/app-context';
import {fireEvent} from '../../../utils/event-util';
import {fireCloseFixPreview, fireEvent} from '../../../utils/event-util';
import {ParsedChangeInfo} from '../../../types/types';
import {GrButton} from '../../shared/gr-button/gr-button';
export interface GrApplyFixDialog {
$: {
applyFixOverlay: GrOverlay;
nextFix: GrButton;
};
}
@ -168,7 +170,7 @@ export class GrApplyFixDialog extends PolymerElement {
}
})
.catch(err => {
this._close();
this._close(false);
throw err;
});
}
@ -186,7 +188,7 @@ export class GrApplyFixDialog extends PolymerElement {
if (e) {
e.stopPropagation();
}
this._close();
this._close(false);
}
addOneTo(_selectedFixIdx: number) {
@ -225,12 +227,12 @@ export class GrApplyFixDialog extends PolymerElement {
return _selectedFixIdx === fixSuggestions.length - 1;
}
_close() {
_close(fixApplied: boolean) {
this._currentFix = undefined;
this._currentPreviews = [];
this._isApplyFixLoading = false;
fireEvent(this, 'close-fix-preview');
fireCloseFixPreview(this, fixApplied);
this.$.applyFixOverlay.close();
}
@ -282,7 +284,7 @@ export class GrApplyFixDialog extends PolymerElement {
EditPatchSetNum,
patchNum as BasePatchSetNum
);
this._close();
this._close(true);
}
this._isApplyFixLoading = false;
});

View File

@ -1,272 +0,0 @@
/**
* @license
* Copyright (C) 2019 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-apply-fix-dialog.js';
import {GerritNav} from '../../core/gr-navigation/gr-navigation.js';
import {stubRestApi} from '../../../test/test-utils.js';
const basicFixture = fixtureFromElement('gr-apply-fix-dialog');
suite('gr-apply-fix-dialog tests', () => {
let element;
const ROBOT_COMMENT_WITH_TWO_FIXES = {
robot_id: 'robot_1',
fix_suggestions: [{fix_id: 'fix_1'}, {fix_id: 'fix_2'}],
};
const ROBOT_COMMENT_WITH_ONE_FIX = {
robot_id: 'robot_1',
fix_suggestions: [{fix_id: 'fix_1'}],
};
setup(() => {
element = basicFixture.instantiate();
element.changeNum = '1';
element._patchNum = 2;
element.change = {
_number: '1',
project: 'project',
revisions: {
abcd: {_number: 1},
efgh: {_number: 2},
},
current_revision: 'efgh',
};
element.prefs = {
font_size: 12,
line_length: 100,
tab_size: 4,
};
});
suite('dialog open', () => {
setup(() => {
stubRestApi('getRobotCommentFixPreview')
.returns(Promise.resolve({
f1: {
meta_a: {},
meta_b: {},
content: [
{
ab: ['loqlwkqll'],
},
{
b: ['qwqqsqw'],
},
{
ab: ['qwqqsqw', 'qweqeqweqeq', 'qweqweq'],
},
],
},
f2: {
meta_a: {},
meta_b: {},
content: [
{
ab: ['eqweqweqwex'],
},
{
b: ['zassdasd'],
},
{
ab: ['zassdasd', 'dasdasda', 'asdasdad'],
},
],
},
}));
sinon.stub(element.$.applyFixOverlay, 'open')
.returns(Promise.resolve());
});
test('dialog opens fetch and sets previews', done => {
element.open({detail: {patchNum: 2,
comment: ROBOT_COMMENT_WITH_TWO_FIXES}})
.then(() => {
assert.equal(element._currentFix.fix_id, 'fix_1');
assert.equal(element._currentPreviews.length, 2);
assert.equal(element._robotId, 'robot_1');
const button = element.shadowRoot.querySelector(
'#applyFixDialog').shadowRoot.querySelector('#confirm');
assert.isFalse(button.hasAttribute('disabled'));
assert.equal(button.getAttribute('title'), '');
done();
});
});
test('tooltip is hidden if apply fix is loading', done => {
element.open({detail: {patchNum: 2,
comment: ROBOT_COMMENT_WITH_TWO_FIXES}})
.then(() => {
element._isApplyFixLoading = true;
const button = element.shadowRoot.querySelector(
'#applyFixDialog').shadowRoot.querySelector('#confirm');
assert.isTrue(button.hasAttribute('disabled'));
assert.equal(button.getAttribute('title'), '');
done();
});
});
test('apply fix button is disabled on older patchset', done => {
element.change = {
_number: '1',
project: 'project',
revisions: {
abcd: {_number: 1},
efgh: {_number: 2},
},
current_revision: 'abcd',
};
element.open({detail: {patchNum: 2,
comment: ROBOT_COMMENT_WITH_ONE_FIX}})
.then(() => {
flush(() => {
const button = element.shadowRoot.querySelector(
'#applyFixDialog').shadowRoot.querySelector('#confirm');
assert.isTrue(button.hasAttribute('disabled'));
assert.equal(button.getAttribute('title'),
'Fix can only be applied to the latest patchset');
done();
});
});
});
});
test('next button state updated when suggestions changed', done => {
stubRestApi('getRobotCommentFixPreview').returns(Promise.resolve({}));
sinon.stub(element.$.applyFixOverlay, 'open').returns(Promise.resolve());
element.open({detail: {patchNum: 2, comment: ROBOT_COMMENT_WITH_ONE_FIX}})
.then(() => assert.isTrue(element.$.nextFix.disabled))
.then(() =>
element.open({detail: {patchNum: 2,
comment: ROBOT_COMMENT_WITH_TWO_FIXES}}))
.then(() => {
assert.isFalse(element.$.nextFix.disabled);
done();
});
});
test('preview endpoint throws error should reset dialog', async () => {
stubRestApi('getRobotCommentFixPreview').returns(
Promise.reject(new Error('backend error')));
element.open({detail: {patchNum: 2,
comment: ROBOT_COMMENT_WITH_TWO_FIXES}});
await flush();
assert.equal(element._currentFix, undefined);
});
test('apply fix button should call apply ' +
'and navigate to change view', () => {
const stub = stubRestApi('applyFixSuggestion').returns(
Promise.resolve({ok: true}));
sinon.stub(GerritNav, 'navigateToChange');
element._currentFix = {fix_id: '123'};
return element._handleApplyFix().then(() => {
assert.isTrue(stub.calledWithExactly('1', 2, '123'));
assert.isTrue(GerritNav.navigateToChange.calledWithExactly({
_number: '1',
project: 'project',
revisions: {
abcd: {_number: 1},
efgh: {_number: 2},
},
current_revision: 'efgh',
}, 'edit', 2));
// reset gr-apply-fix-dialog and close
assert.equal(element._currentFix, undefined);
assert.equal(element._currentPreviews.length, 0);
});
});
test('should not navigate to change view if incorect reponse', done => {
const stub = stubRestApi('applyFixSuggestion').returns(Promise.resolve({}));
sinon.stub(GerritNav, 'navigateToChange');
element._currentFix = {fix_id: '123'};
element._handleApplyFix().then(() => {
assert.isTrue(stub.calledWithExactly('1', 2, '123'));
assert.isTrue(GerritNav.navigateToChange.notCalled);
assert.equal(element._isApplyFixLoading, false);
done();
});
});
test('select fix forward and back of multiple suggested fixes', done => {
stubRestApi('getRobotCommentFixPreview')
.returns(Promise.resolve({
f1: {
meta_a: {},
meta_b: {},
content: [
{
ab: ['loqlwkqll'],
},
{
b: ['qwqqsqw'],
},
{
ab: ['qwqqsqw', 'qweqeqweqeq', 'qweqweq'],
},
],
},
f2: {
meta_a: {},
meta_b: {},
content: [
{
ab: ['eqweqweqwex'],
},
{
b: ['zassdasd'],
},
{
ab: ['zassdasd', 'dasdasda', 'asdasdad'],
},
],
},
}));
sinon.stub(element.$.applyFixOverlay, 'open').returns(Promise.resolve());
element.open({detail: {patchNum: 2, comment: ROBOT_COMMENT_WITH_TWO_FIXES}})
.then(() => {
element._onNextFixClick();
assert.equal(element._currentFix.fix_id, 'fix_2');
element._onPrevFixClick();
assert.equal(element._currentFix.fix_id, 'fix_1');
done();
});
});
test('server-error should throw for failed apply call', async () => {
stubRestApi('applyFixSuggestion').returns(
Promise.reject(new Error('backend error')));
sinon.stub(GerritNav, 'navigateToChange');
element._currentFix = {fix_id: '123'};
let expectedError;
await element._handleApplyFix().catch(e => {
expectedError = e;
});
assert.isOk(expectedError);
assert.isFalse(GerritNav.navigateToChange.called);
});
});

View File

@ -0,0 +1,376 @@
/**
* @license
* Copyright (C) 2019 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-apply-fix-dialog';
import {GerritNav} from '../../core/gr-navigation/gr-navigation';
import {queryAndAssert, stubRestApi} from '../../../test/test-utils';
import {GrApplyFixDialog} from './gr-apply-fix-dialog';
import {
BasePatchSetNum,
EditPatchSetNum,
PatchSetNum,
RobotId,
RobotRunId,
Timestamp,
UrlEncodedCommentId,
} from '../../../types/common';
import {
createFixSuggestionInfo,
createParsedChange,
createRevisions,
getCurrentRevision,
} from '../../../test/test-data-generators';
import {createDefaultDiffPrefs} from '../../../constants/constants';
import {DiffInfo} from '../../../types/diff';
import {UIRobot} from '../../../utils/comment-util';
import {
CloseFixPreviewEventDetail,
EventType,
OpenFixPreviewEventDetail,
} from '../../../types/events';
import {GrButton} from '../../shared/gr-button/gr-button';
const basicFixture = fixtureFromElement('gr-apply-fix-dialog');
suite('gr-apply-fix-dialog tests', () => {
let element: GrApplyFixDialog;
const ROBOT_COMMENT_WITH_TWO_FIXES: UIRobot = {
id: '1' as UrlEncodedCommentId,
updated: '2018-02-08 18:49:18.000000000' as Timestamp,
robot_id: 'robot_1' as RobotId,
robot_run_id: 'run_1' as RobotRunId,
properties: {},
fix_suggestions: [
createFixSuggestionInfo('fix_1'),
createFixSuggestionInfo('fix_2'),
],
};
const ROBOT_COMMENT_WITH_ONE_FIX: UIRobot = {
id: '2' as UrlEncodedCommentId,
updated: '2018-02-08 18:49:18.000000000' as Timestamp,
robot_id: 'robot_1' as RobotId,
robot_run_id: 'run_1' as RobotRunId,
properties: {},
fix_suggestions: [createFixSuggestionInfo('fix_1')],
};
function getConfirmButton(): GrButton {
return queryAndAssert(
queryAndAssert(element, '#applyFixDialog'),
'#confirm'
);
}
setup(() => {
element = basicFixture.instantiate();
const change = {
...createParsedChange(),
revisions: createRevisions(2),
current_revision: getCurrentRevision(1),
};
element.changeNum = change._number;
element._patchNum = change.revisions[change.current_revision]._number;
element.change = change;
element.prefs = {
...createDefaultDiffPrefs(),
font_size: 12,
line_length: 100,
tab_size: 4,
};
});
suite('dialog open', () => {
setup(() => {
const diffInfo1: DiffInfo = {
meta_a: {
name: 'f1',
content_type: 'text',
lines: 10,
},
meta_b: {
name: 'f1',
content_type: 'text',
lines: 12,
},
content: [
{
ab: ['loqlwkqll'],
},
{
b: ['qwqqsqw'],
},
{
ab: ['qwqqsqw', 'qweqeqweqeq', 'qweqweq'],
},
],
change_type: 'MODIFIED',
intraline_status: 'OK',
};
const diffInfo2: DiffInfo = {
meta_a: {
name: 'f2',
content_type: 'text',
lines: 10,
},
meta_b: {
name: 'f2',
content_type: 'text',
lines: 12,
},
content: [
{
ab: ['eqweqweqwex'],
},
{
b: ['zassdasd'],
},
{
ab: ['zassdasd', 'dasdasda', 'asdasdad'],
},
],
change_type: 'MODIFIED',
intraline_status: 'OK',
};
stubRestApi('getRobotCommentFixPreview').returns(
Promise.resolve({
f1: diffInfo1,
f2: diffInfo2,
})
);
sinon.stub(element.$.applyFixOverlay, 'open').returns(Promise.resolve());
});
test('dialog opens fetch and sets previews', async () => {
await element.open(
new CustomEvent<OpenFixPreviewEventDetail>(EventType.OPEN_FIX_PREVIEW, {
detail: {
patchNum: 2 as PatchSetNum,
comment: ROBOT_COMMENT_WITH_TWO_FIXES,
},
})
);
assert.equal(element._currentFix!.fix_id, 'fix_1');
assert.equal(element._currentPreviews.length, 2);
assert.equal(element._robotId, 'robot_1' as RobotId);
const button = getConfirmButton();
assert.isFalse(button.hasAttribute('disabled'));
assert.equal(button.getAttribute('title'), '');
});
test('tooltip is hidden if apply fix is loading', async () => {
await element.open(
new CustomEvent<OpenFixPreviewEventDetail>(EventType.OPEN_FIX_PREVIEW, {
detail: {
patchNum: 2 as PatchSetNum,
comment: ROBOT_COMMENT_WITH_TWO_FIXES,
},
})
);
element._isApplyFixLoading = true;
const button = getConfirmButton();
assert.isTrue(button.hasAttribute('disabled'));
assert.equal(button.getAttribute('title'), '');
});
test('apply fix button is disabled on older patchset', async () => {
element.change = element.change = {
...createParsedChange(),
revisions: createRevisions(2),
current_revision: getCurrentRevision(0),
};
await element.open(
new CustomEvent<OpenFixPreviewEventDetail>(EventType.OPEN_FIX_PREVIEW, {
detail: {
patchNum: 2 as PatchSetNum,
comment: ROBOT_COMMENT_WITH_ONE_FIX,
},
})
);
await flush();
const button = getConfirmButton();
assert.isTrue(button.hasAttribute('disabled'));
assert.equal(
button.getAttribute('title'),
'Fix can only be applied to the latest patchset'
);
});
});
test('next button state updated when suggestions changed', async () => {
stubRestApi('getRobotCommentFixPreview').returns(Promise.resolve({}));
sinon.stub(element.$.applyFixOverlay, 'open').returns(Promise.resolve());
await element.open(
new CustomEvent<OpenFixPreviewEventDetail>(EventType.OPEN_FIX_PREVIEW, {
detail: {
patchNum: 2 as PatchSetNum,
comment: ROBOT_COMMENT_WITH_ONE_FIX,
},
})
);
assert.isTrue(element.$.nextFix.disabled);
await element.open(
new CustomEvent<OpenFixPreviewEventDetail>(EventType.OPEN_FIX_PREVIEW, {
detail: {
patchNum: 2 as PatchSetNum,
comment: ROBOT_COMMENT_WITH_TWO_FIXES,
},
})
);
assert.isFalse(element.$.nextFix.disabled);
});
test('preview endpoint throws error should reset dialog', async () => {
stubRestApi('getRobotCommentFixPreview').returns(
Promise.reject(new Error('backend error'))
);
element.open(
new CustomEvent<OpenFixPreviewEventDetail>(EventType.OPEN_FIX_PREVIEW, {
detail: {
patchNum: 2 as PatchSetNum,
comment: ROBOT_COMMENT_WITH_TWO_FIXES,
},
})
);
await flush();
assert.equal(element._currentFix, undefined);
});
test('apply fix button should call apply, navigate to change view and fire close', async () => {
const applyFixSuggestionStub = stubRestApi('applyFixSuggestion').returns(
Promise.resolve(new Response(null, {status: 200}))
);
const navigateToChangeStub = sinon.stub(GerritNav, 'navigateToChange');
element._currentFix = createFixSuggestionInfo('123');
const closeFixPreviewEventSpy = sinon.spy();
// Element is recreated after each test, removeEventListener isn't required
element.addEventListener(
EventType.CLOSE_FIX_PREVIEW,
closeFixPreviewEventSpy
);
await element._handleApplyFix(new CustomEvent('confirm'));
sinon.assert.calledOnceWithExactly(
applyFixSuggestionStub,
element.change!._number,
2 as PatchSetNum,
'123'
);
sinon.assert.calledWithExactly(
navigateToChangeStub,
element.change!,
EditPatchSetNum,
element.change!.revisions[2]._number as BasePatchSetNum
);
sinon.assert.calledOnceWithExactly(
closeFixPreviewEventSpy,
new CustomEvent<CloseFixPreviewEventDetail>(EventType.CLOSE_FIX_PREVIEW, {
detail: {
fixApplied: true,
},
})
);
// reset gr-apply-fix-dialog and close
assert.equal(element._currentFix, undefined);
assert.equal(element._currentPreviews.length, 0);
});
test('should not navigate to change view if incorect reponse', async () => {
const applyFixSuggestionStub = stubRestApi('applyFixSuggestion').returns(
Promise.resolve(new Response(null, {status: 500}))
);
const navigateToChangeStub = sinon.stub(GerritNav, 'navigateToChange');
element._currentFix = createFixSuggestionInfo('fix_123');
await element._handleApplyFix(new CustomEvent('confirm'));
sinon.assert.calledWithExactly(
applyFixSuggestionStub,
element.change!._number,
2 as PatchSetNum,
'fix_123'
);
assert.isTrue(navigateToChangeStub.notCalled);
assert.equal(element._isApplyFixLoading, false);
});
test('select fix forward and back of multiple suggested fixes', async () => {
sinon.stub(element.$.applyFixOverlay, 'open').returns(Promise.resolve());
await element.open(
new CustomEvent<OpenFixPreviewEventDetail>(EventType.OPEN_FIX_PREVIEW, {
detail: {
patchNum: 2 as PatchSetNum,
comment: ROBOT_COMMENT_WITH_TWO_FIXES,
},
})
);
element._onNextFixClick(new CustomEvent('click'));
assert.equal(element._currentFix!.fix_id, 'fix_2');
element._onPrevFixClick(new CustomEvent('click'));
assert.equal(element._currentFix!.fix_id, 'fix_1');
});
test('server-error should throw for failed apply call', async () => {
stubRestApi('applyFixSuggestion').returns(
Promise.reject(new Error('backend error'))
);
const navigateToChangeStub = sinon.stub(GerritNav, 'navigateToChange');
element._currentFix = createFixSuggestionInfo('fix_123');
const closeFixPreviewEventSpy = sinon.spy();
// Element is recreated after each test, removeEventListener isn't required
element.addEventListener(
EventType.CLOSE_FIX_PREVIEW,
closeFixPreviewEventSpy
);
let expectedError;
await element._handleApplyFix(new CustomEvent('click')).catch(e => {
expectedError = e;
});
assert.isOk(expectedError);
assert.isFalse(navigateToChangeStub.called);
sinon.assert.notCalled(closeFixPreviewEventSpy);
});
test('onCancel fires close with correct parameters', () => {
const closeFixPreviewEventSpy = sinon.spy();
// Element is recreated after each test, removeEventListener isn't required
element.addEventListener(
EventType.CLOSE_FIX_PREVIEW,
closeFixPreviewEventSpy
);
element.onCancel(new CustomEvent('cancel'));
sinon.assert.calledOnceWithExactly(
closeFixPreviewEventSpy,
new CustomEvent<CloseFixPreviewEventDetail>(EventType.CLOSE_FIX_PREVIEW, {
detail: {
fixApplied: false,
},
})
);
});
});

View File

@ -72,6 +72,7 @@ export interface DiffContextExpandedEventDetail {
declare global {
interface HTMLElementEventMap {
'diff-context-expanded': CustomEvent<DiffContextExpandedEventDetail>;
'content-load-needed': CustomEvent<ContentLoadNeededEventDetail>;
}
}
@ -511,14 +512,14 @@ export abstract class GrDiffBuilder {
end_line: lastRange.right.end_line,
},
};
fire<ContentLoadNeededEventDetail>(button, 'content-load-needed', {
fire(button, 'content-load-needed', {
lineRange,
});
});
} else {
button.addEventListener('click', e => {
e.stopPropagation();
fire<DiffContextExpandedEventDetail>(button, 'diff-context-expanded', {
fire(button, 'diff-context-expanded', {
groups,
section,
numLines,

View File

@ -65,6 +65,8 @@ import {
RelatedChangeAndCommitInfo,
SubmittedTogetherInfo,
RelatedChangesInfo,
FixSuggestionInfo,
FixId,
} from '../types/common';
import {
AccountsVisibility,
@ -614,3 +616,11 @@ export function createSubmittedTogetherInfo(): SubmittedTogetherInfo {
non_visible_changes: 0,
};
}
export function createFixSuggestionInfo(fixId = 'fix_1'): FixSuggestionInfo {
return {
fix_id: fixId as FixId,
description: `Fix ${fixId}`,
replacements: [],
};
}

View File

@ -32,6 +32,7 @@ export enum EventType {
MOVED_LINK_CLICKED = 'moved-link-clicked',
NETWORK_ERROR = 'network-error',
OPEN_FIX_PREVIEW = 'open-fix-preview',
CLOSE_FIX_PREVIEW = 'close-fix-preview',
PAGE_ERROR = 'page-error',
RELOAD = 'reload',
REPLY = 'reply',
@ -54,6 +55,7 @@ declare global {
'iron-announce': IronAnnounceEvent;
'moved-link-clicked': MovedLinkClickedEvent;
'open-fix-preview': OpenFixPreviewEvent;
'close-fix-preview': CloseFixPreviewEvent;
/* prettier-ignore */
'reload': ReloadEvent;
/* prettier-ignore */
@ -133,6 +135,11 @@ export interface OpenFixPreviewEventDetail {
}
export type OpenFixPreviewEvent = CustomEvent<OpenFixPreviewEventDetail>;
export interface CloseFixPreviewEventDetail {
fixApplied: boolean;
}
export type CloseFixPreviewEvent = CustomEvent<CloseFixPreviewEventDetail>;
export interface PageErrorEventDetail {
response?: Response;
}

View File

@ -20,15 +20,8 @@ import {FetchRequest} from '../types/types';
import {
DialogChangeEventDetail,
EventType,
IronAnnounceEventDetail,
NetworkErrorEventDetail,
PageErrorEventDetail,
ServerErrorEventDetail,
ShowAlertEventDetail,
SwitchTabEventDetail,
TabState,
ThreadListModifiedDetail,
TitleChangeEventDetail,
} from '../types/events';
export function fireEvent(target: EventTarget, type: string) {
@ -40,6 +33,34 @@ export function fireEvent(target: EventTarget, type: string) {
);
}
type HTMLElementEventDetailType<
K extends keyof HTMLElementEventMap
> = HTMLElementEventMap[K] extends CustomEvent<infer DT>
? unknown extends DT
? never
: DT
: never;
type DocumentEventDetailType<
K extends keyof DocumentEventMap
> = DocumentEventMap[K] extends CustomEvent<infer DT>
? unknown extends DT
? never
: DT
: never;
export function fire<K extends keyof DocumentEventMap>(
target: Document,
type: K,
detail: DocumentEventDetailType<K>
): void;
export function fire<K extends keyof HTMLElementEventMap>(
target: EventTarget,
type: K,
detail: HTMLElementEventDetailType<K>
): void;
export function fire<T>(target: EventTarget, type: string, detail: T) {
target.dispatchEvent(
new CustomEvent<T>(type, {
@ -51,27 +72,27 @@ export function fire<T>(target: EventTarget, type: string, detail: T) {
}
export function fireAlert(target: EventTarget, message: string) {
fire<ShowAlertEventDetail>(target, EventType.SHOW_ALERT, {message});
fire(target, EventType.SHOW_ALERT, {message});
}
export function firePageError(response?: Response | null) {
if (response === null) response = undefined;
fire<PageErrorEventDetail>(document, EventType.PAGE_ERROR, {response});
fire(document, EventType.PAGE_ERROR, {response});
}
export function fireServerError(response: Response, request?: FetchRequest) {
fire<ServerErrorEventDetail>(document, EventType.SERVER_ERROR, {
fire(document, EventType.SERVER_ERROR, {
response,
request,
});
}
export function fireNetworkError(error: Error) {
fire<NetworkErrorEventDetail>(document, EventType.NETWORK_ERROR, {error});
fire(document, EventType.NETWORK_ERROR, {error});
}
export function fireTitleChange(target: EventTarget, title: string) {
fire<TitleChangeEventDetail>(target, EventType.TITLE_CHANGE, {title});
fire(target, EventType.TITLE_CHANGE, {title});
}
// TODO(milutin) - remove once new gr-dialog will do it out of the box
@ -80,11 +101,11 @@ export function fireDialogChange(
target: EventTarget,
detail: DialogChangeEventDetail
) {
fire<DialogChangeEventDetail>(target, EventType.DIALOG_CHANGE, detail);
fire(target, EventType.DIALOG_CHANGE, detail);
}
export function fireIronAnnounce(target: EventTarget, text: string) {
fire<IronAnnounceEventDetail>(target, EventType.IRON_ANNOUNCE, {text});
fire(target, EventType.IRON_ANNOUNCE, {text});
}
export function fireThreadListModifiedEvent(
@ -92,7 +113,7 @@ export function fireThreadListModifiedEvent(
rootId: UrlEncodedCommentId,
path: string
) {
fire<ThreadListModifiedDetail>(target, EventType.THREAD_LIST_MODIFIED, {
fire(target, EventType.THREAD_LIST_MODIFIED, {
rootId,
path,
});
@ -105,7 +126,11 @@ export function fireShowPrimaryTab(
tabState?: TabState
) {
const detail: SwitchTabEventDetail = {tab, scrollIntoView, tabState};
fire<SwitchTabEventDetail>(target, EventType.SHOW_PRIMARY_TAB, detail);
fire(target, EventType.SHOW_PRIMARY_TAB, detail);
}
export function fireCloseFixPreview(target: EventTarget, fixApplied: boolean) {
fire(target, EventType.CLOSE_FIX_PREVIEW, {fixApplied});
}
export function waitForEventOnce<K extends keyof HTMLElementEventMap>(