Skip to content

Commit 1bf37ce

Browse files
committed
fix(cdk/testing): harness click events inconsistently causing clicks on disabled elements
Currently the CDK harness implementation for `TestBed` tries to mimic user interactions, like `click`. These helpers can then be called by harness authors or consumers. Currently there is some inconistency across harness environments where `testElement.click()` on disabled buttons, inputs etc. causes `click` events to be still emitted in `TestBed` for Chromium-based browsers, while this is not the case for `TestBed` in `Firefox` and `Safari`, neither in e2e-based clicks (like protractor or webdriver). This seems to be inconsistency caused by browsers in the ecosystem. There are bug reports in Firefox where they explicitly kept emitting `click` when explicitly dispatched programmatically (seems reasonable to me). Chromium has a bug report where users complain about events being prevented, even though dispatched programmatically. https://bugzilla.mozilla.org/show_bug.cgi?id=329509. https://bugs.chromium.org/p/chromium/issues/detail?id=1115661 In either way though, we should make this consistent with an actual user interaction (like with e2e environments) and just skip the click event when the underlying element is disabled (we assume the `disabled` property is a sufficient indicator for this). Note that the mousedown, mouseup event sequence continues to be emulated/synthesized.
1 parent 15bc94b commit 1bf37ce

File tree

7 files changed

+20
-35
lines changed

7 files changed

+20
-35
lines changed

src/cdk/testing/testbed/unit-test-element.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,15 @@ export class UnitTestElement implements TestElement {
100100
async click(
101101
...args: [ModifierKeys?] | ['center', ModifierKeys?] | [number, number, ModifierKeys?]
102102
): Promise<void> {
103-
await this._dispatchMouseEventSequence('click', args, 0);
103+
const isDisabled = (this.element as Partial<{disabled?: boolean}>).disabled === true;
104+
105+
// If the element is `disabled` and has a `disabled` property, we emit the mouse event
106+
// sequence but not dispatch the `click` event. This is necessary to keep the behavior
107+
// consistent with an actual user interaction. The click event is not necessarily
108+
// automatically prevented by the browser. There is mismatch between Firefox and Chromium:
109+
// https://bugzilla.mozilla.org/show_bug.cgi?id=329509.
110+
// https://bugs.chromium.org/p/chromium/issues/detail?id=1115661.
111+
await this._dispatchMouseEventSequence(isDisabled ? null : 'click', args, 0);
104112
await this._stabilize();
105113
}
106114

@@ -284,9 +292,12 @@ export class UnitTestElement implements TestElement {
284292
}
285293
}
286294

287-
/** Dispatches all the events that are part of a mouse event sequence. */
295+
/**
296+
* Dispatches all the events that are part of a mouse event sequence
297+
* and then emits a given primary event at the end, if speciifed.
298+
*/
288299
private async _dispatchMouseEventSequence(
289-
name: string,
300+
primaryEventName: string | null,
290301
args: [ModifierKeys?] | ['center', ModifierKeys?] | [number, number, ModifierKeys?],
291302
button?: number,
292303
) {
@@ -313,11 +324,16 @@ export class UnitTestElement implements TestElement {
313324
dispatchMouseEvent(this.element, 'mousedown', clientX, clientY, button, modifiers);
314325
this._dispatchPointerEventIfSupported('pointerup', clientX, clientY, button);
315326
dispatchMouseEvent(this.element, 'mouseup', clientX, clientY, button, modifiers);
316-
dispatchMouseEvent(this.element, name, clientX, clientY, button, modifiers);
327+
328+
// If a primary event name is specified, emit it after the mouse event sequence.
329+
if (primaryEventName !== null) {
330+
dispatchMouseEvent(this.element, primaryEventName, clientX, clientY, button, modifiers);
331+
}
317332

318333
// This call to _stabilize should not be needed since the callers will already do that them-
319334
// selves. Nevertheless it breaks some tests in g3 without it. It needs to be investigated
320335
// why removing breaks those tests.
336+
// See: https://github.com/angular/components/pull/20758/files#r520886256.
321337
await this._stabilize();
322338
}
323339
}

src/material/checkbox/testing/BUILD.bazel

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ ng_test_library(
2424
srcs = ["shared.spec.ts"],
2525
deps = [
2626
":testing",
27-
"//src/cdk/platform",
2827
"//src/cdk/testing",
2928
"//src/cdk/testing/testbed",
3029
"//src/material/checkbox",

src/material/checkbox/testing/shared.spec.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import {Platform} from '@angular/cdk/platform';
21
import {HarnessLoader} from '@angular/cdk/testing';
32
import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed';
43
import {Component} from '@angular/core';
@@ -15,7 +14,6 @@ export function runHarnessTests(
1514
checkboxModule: typeof MatCheckboxModule,
1615
checkboxHarness: typeof MatCheckboxHarness,
1716
) {
18-
let platform: Platform;
1917
let fixture: ComponentFixture<CheckboxHarnessTest>;
2018
let loader: HarnessLoader;
2119

@@ -25,7 +23,6 @@ export function runHarnessTests(
2523
declarations: [CheckboxHarnessTest],
2624
}).compileComponents();
2725

28-
platform = TestBed.inject(Platform);
2926
fixture = TestBed.createComponent(CheckboxHarnessTest);
3027
fixture.detectChanges();
3128
loader = TestbedHarnessEnvironment.loader(fixture);
@@ -155,11 +152,6 @@ export function runHarnessTests(
155152
});
156153

157154
it('should not toggle disabled checkbox', async () => {
158-
if (platform.FIREFOX) {
159-
// do run this test on firefox as click events on the label of a disabled checkbox
160-
// cause the value to be changed. https://bugzilla.mozilla.org/show_bug.cgi?id=1540995
161-
return;
162-
}
163155
const disabledCheckbox = await loader.getHarness(checkboxHarness.with({label: 'Second'}));
164156
expect(await disabledCheckbox.isChecked()).toBe(false);
165157
await disabledCheckbox.toggle();

src/material/radio/testing/BUILD.bazel

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ ng_test_library(
2424
srcs = ["shared.spec.ts"],
2525
deps = [
2626
":testing",
27-
"//src/cdk/platform",
2827
"//src/cdk/testing",
2928
"//src/cdk/testing/private",
3029
"//src/cdk/testing/testbed",

src/material/radio/testing/shared.spec.ts

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import {Platform} from '@angular/cdk/platform';
21
import {HarnessLoader} from '@angular/cdk/testing';
32
import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed';
43
import {Component} from '@angular/core';
@@ -13,7 +12,6 @@ export function runHarnessTests(
1312
radioGroupHarness: typeof MatRadioGroupHarness,
1413
radioButtonHarness: typeof MatRadioButtonHarness,
1514
) {
16-
let platform: Platform;
1715
let fixture: ComponentFixture<MultipleRadioButtonsHarnessTest>;
1816
let loader: HarnessLoader;
1917

@@ -23,7 +21,6 @@ export function runHarnessTests(
2321
declarations: [MultipleRadioButtonsHarnessTest],
2422
}).compileComponents();
2523

26-
platform = TestBed.inject(Platform);
2724
fixture = TestBed.createComponent(MultipleRadioButtonsHarnessTest);
2825
fixture.detectChanges();
2926
loader = TestbedHarnessEnvironment.loader(fixture);
@@ -243,13 +240,6 @@ export function runHarnessTests(
243240
});
244241

245242
it('should not be able to check disabled radio-button', async () => {
246-
if (platform.FIREFOX) {
247-
// do run this test on firefox as click events on the label of the underlying
248-
// input checkbox cause the value to be changed. Read more in the bug report:
249-
// https://bugzilla.mozilla.org/show_bug.cgi?id=1540995
250-
return;
251-
}
252-
253243
fixture.componentInstance.disableAll = true;
254244
fixture.detectChanges();
255245

src/material/slide-toggle/testing/BUILD.bazel

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ ng_test_library(
2424
srcs = ["shared.spec.ts"],
2525
deps = [
2626
":testing",
27-
"//src/cdk/platform",
2827
"//src/cdk/testing",
2928
"//src/cdk/testing/testbed",
3029
"//src/material/slide-toggle",

src/material/slide-toggle/testing/shared.spec.ts

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import {Platform} from '@angular/cdk/platform';
21
import {HarnessLoader} from '@angular/cdk/testing';
32
import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed';
43
import {Component} from '@angular/core';
@@ -12,7 +11,6 @@ export function runHarnessTests(
1211
slideToggleModule: typeof MatSlideToggleModule,
1312
slideToggleHarness: typeof MatSlideToggleHarness,
1413
) {
15-
let platform: Platform;
1614
let fixture: ComponentFixture<SlideToggleHarnessTest>;
1715
let loader: HarnessLoader;
1816

@@ -22,7 +20,6 @@ export function runHarnessTests(
2220
declarations: [SlideToggleHarnessTest],
2321
}).compileComponents();
2422

25-
platform = TestBed.inject(Platform);
2623
fixture = TestBed.createComponent(SlideToggleHarnessTest);
2724
fixture.detectChanges();
2825
loader = TestbedHarnessEnvironment.loader(fixture);
@@ -149,13 +146,6 @@ export function runHarnessTests(
149146
});
150147

151148
it('should not toggle disabled slide-toggle', async () => {
152-
if (platform.FIREFOX) {
153-
// do not run this test on firefox as click events on the label of the underlying
154-
// input checkbox cause the value to be changed. Read more in the bug report:
155-
// https://bugzilla.mozilla.org/show_bug.cgi?id=1540995
156-
return;
157-
}
158-
159149
const disabledToggle = await loader.getHarness(slideToggleHarness.with({label: 'Second'}));
160150
expect(await disabledToggle.isChecked()).toBe(false);
161151
await disabledToggle.toggle();

0 commit comments

Comments
 (0)