Skip to content

Commit 0473410

Browse files
crisbetoandrewseguin
authored andcommitted
fix(input/testing): unable to set the value of inputs that don't respond to typing (#18812)
The `setValue` method of the input harness is set up so that it types out the value in the input. This won't work for input that don't respond to typing (e.g. `color`). These changes add an extra call that also sets the `value` property of the input, on top of typing into it. Fixes #18790.
1 parent b4d0852 commit 0473410

File tree

11 files changed

+70
-10
lines changed

11 files changed

+70
-10
lines changed

src/cdk/testing/protractor/protractor-element.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,10 @@ export class ProtractorElement implements TestElement {
147147
return browser.executeScript(`return arguments[0][arguments[1]]`, this.element, name);
148148
}
149149

150+
async setInputValue(value: string): Promise<void> {
151+
return browser.executeScript(`arguments[0].value = arguments[1]`, this.element, value);
152+
}
153+
150154
async matchesSelector(selector: string): Promise<boolean> {
151155
return browser.executeScript(`
152156
return (Element.prototype.matches ||

src/cdk/testing/test-element.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,12 @@ export interface TestElement {
113113
/** Gets the value of a property of an element. */
114114
getProperty(name: string): Promise<any>;
115115

116+
/**
117+
* Sets the value of a property of an input.
118+
* @breaking-change 11.0.0 To become a required method.
119+
*/
120+
setInputValue?(value: string): Promise<void>;
121+
116122
/** Checks whether this element matches the given selector. */
117123
matchesSelector(selector: string): Promise<boolean>;
118124

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,11 @@ export class UnitTestElement implements TestElement {
153153
return (this.element as any)[name];
154154
}
155155

156+
async setInputValue(value: string): Promise<void> {
157+
await this._stabilize();
158+
(this.element as any).value = value;
159+
}
160+
156161
async matchesSelector(selector: string): Promise<boolean> {
157162
await this._stabilize();
158163
const elementPrototype = Element.prototype as any;

src/cdk/testing/tests/protractor.e2e.spec.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,14 @@ describe('ProtractorHarnessEnvironment', () => {
283283
expect(await input.getProperty('value')).toBe('Hello');
284284
});
285285

286+
it('should be able to set the value of an input', async () => {
287+
const input = await harness.input();
288+
289+
// @breaking-change 11.0.0 Remove non-null assertion once `setInputValue` is required.
290+
await input.setInputValue!('hello');
291+
expect(await input.getProperty('value')).toBe('hello');
292+
});
293+
286294
it('should check if selector matches', async () => {
287295
const button = await harness.button();
288296
expect(await button.matchesSelector('button:not(.fake-class)')).toBe(true);

src/cdk/testing/tests/testbed.spec.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,14 @@ describe('TestbedHarnessEnvironment', () => {
368368
expect(await input.getProperty('value')).toBe('Hello');
369369
});
370370

371+
it('should be able to set the value of an input', async () => {
372+
const input = await harness.input();
373+
374+
// @breaking-change 11.0.0 Remove non-null assertion once `setInputValue` is required.
375+
await input.setInputValue!('hello');
376+
expect(await input.getProperty('value')).toBe('hello');
377+
});
378+
371379
it('should check if selector matches', async () => {
372380
const button = await harness.button();
373381
expect(await button.matchesSelector('button:not(.fake-class)')).toBe(true);

src/material/input/testing/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ ng_test_library(
2626
srcs = ["shared.spec.ts"],
2727
deps = [
2828
":testing",
29+
"//src/cdk/platform",
2930
"//src/cdk/testing",
3031
"//src/cdk/testing/testbed",
3132
"//src/material/input",

src/material/input/testing/input-harness.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,5 +111,13 @@ export class MatInputHarness extends MatFormFieldControlHarness {
111111
if (newValue) {
112112
await inputEl.sendKeys(newValue);
113113
}
114+
115+
// Some input types won't respond to key presses (e.g. `color`) so to be sure that the
116+
// value is set, we also set the property after the keyboard sequence. Note that we don't
117+
// want to do it before, because it can cause the value to be entered twice.
118+
// @breaking-change 11.0.0 Remove non-null assertion once `setInputValue` is required.
119+
if (inputEl.setInputValue) {
120+
await inputEl.setInputValue(newValue);
121+
}
114122
}
115123
}

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

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {Component} from '@angular/core';
44
import {ComponentFixture, TestBed} from '@angular/core/testing';
55
import {ReactiveFormsModule} from '@angular/forms';
66
import {MatInputModule} from '@angular/material/input';
7+
import {getSupportedInputTypes} from '@angular/cdk/platform';
78
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
89
import {MatInputHarness} from './input-harness';
910

@@ -28,7 +29,7 @@ export function runHarnessTests(
2829

2930
it('should load all input harnesses', async () => {
3031
const inputs = await loader.getAllHarnesses(inputHarness);
31-
expect(inputs.length).toBe(5);
32+
expect(inputs.length).toBe(6);
3233
});
3334

3435
it('should load input with specific id', async () => {
@@ -49,7 +50,7 @@ export function runHarnessTests(
4950

5051
it('should be able to get id of input', async () => {
5152
const inputs = await loader.getAllHarnesses(inputHarness);
52-
expect(inputs.length).toBe(5);
53+
expect(inputs.length).toBe(6);
5354
expect(await inputs[0].getId()).toMatch(/mat-input-\d+/);
5455
expect(await inputs[1].getId()).toMatch(/mat-input-\d+/);
5556
expect(await inputs[2].getId()).toBe('myTextarea');
@@ -59,7 +60,7 @@ export function runHarnessTests(
5960

6061
it('should be able to get name of input', async () => {
6162
const inputs = await loader.getAllHarnesses(inputHarness);
62-
expect(inputs.length).toBe(5);
63+
expect(inputs.length).toBe(6);
6364
expect(await inputs[0].getName()).toBe('favorite-food');
6465
expect(await inputs[1].getName()).toBe('');
6566
expect(await inputs[2].getName()).toBe('');
@@ -69,7 +70,7 @@ export function runHarnessTests(
6970

7071
it('should be able to get value of input', async () => {
7172
const inputs = await loader.getAllHarnesses(inputHarness);
72-
expect(inputs.length).toBe(5);
73+
expect(inputs.length).toBe(6);
7374
expect(await inputs[0].getValue()).toBe('Sushi');
7475
expect(await inputs[1].getValue()).toBe('');
7576
expect(await inputs[2].getValue()).toBe('');
@@ -79,7 +80,7 @@ export function runHarnessTests(
7980

8081
it('should be able to set value of input', async () => {
8182
const inputs = await loader.getAllHarnesses(inputHarness);
82-
expect(inputs.length).toBe(5);
83+
expect(inputs.length).toBe(6);
8384
expect(await inputs[0].getValue()).toBe('Sushi');
8485
expect(await inputs[1].getValue()).toBe('');
8586
expect(await inputs[3].getValue()).toBe('');
@@ -98,7 +99,7 @@ export function runHarnessTests(
9899

99100
it('should be able to get disabled state', async () => {
100101
const inputs = await loader.getAllHarnesses(inputHarness);
101-
expect(inputs.length).toBe(5);
102+
expect(inputs.length).toBe(6);
102103

103104
expect(await inputs[0].isDisabled()).toBe(false);
104105
expect(await inputs[1].isDisabled()).toBe(false);
@@ -113,7 +114,7 @@ export function runHarnessTests(
113114

114115
it('should be able to get readonly state', async () => {
115116
const inputs = await loader.getAllHarnesses(inputHarness);
116-
expect(inputs.length).toBe(5);
117+
expect(inputs.length).toBe(6);
117118

118119
expect(await inputs[0].isReadonly()).toBe(false);
119120
expect(await inputs[1].isReadonly()).toBe(false);
@@ -128,7 +129,7 @@ export function runHarnessTests(
128129

129130
it('should be able to get required state', async () => {
130131
const inputs = await loader.getAllHarnesses(inputHarness);
131-
expect(inputs.length).toBe(5);
132+
expect(inputs.length).toBe(6);
132133

133134
expect(await inputs[0].isRequired()).toBe(false);
134135
expect(await inputs[1].isRequired()).toBe(false);
@@ -143,7 +144,7 @@ export function runHarnessTests(
143144

144145
it('should be able to get placeholder of input', async () => {
145146
const inputs = await loader.getAllHarnesses(inputHarness);
146-
expect(inputs.length).toBe(5);
147+
expect(inputs.length).toBe(6);
147148
expect(await inputs[0].getPlaceholder()).toBe('Favorite food');
148149
expect(await inputs[1].getPlaceholder()).toBe('');
149150
expect(await inputs[2].getPlaceholder()).toBe('Leave a comment');
@@ -153,7 +154,7 @@ export function runHarnessTests(
153154

154155
it('should be able to get type of input', async () => {
155156
const inputs = await loader.getAllHarnesses(inputHarness);
156-
expect(inputs.length).toBe(5);
157+
expect(inputs.length).toBe(6);
157158
expect(await inputs[0].getType()).toBe('text');
158159
expect(await inputs[1].getType()).toBe('number');
159160
expect(await inputs[2].getType()).toBe('textarea');
@@ -180,6 +181,18 @@ export function runHarnessTests(
180181
await input.blur();
181182
expect(getActiveElementTagName()).not.toBe('input');
182183
});
184+
185+
it('should be able to set the value of a control that cannot be typed in', async () => {
186+
// We can't test this against browsers that don't support color inputs.
187+
if (!getSupportedInputTypes().has('color')) {
188+
return;
189+
}
190+
191+
const input = await loader.getHarness(inputHarness.with({selector: '#colorControl'}));
192+
expect(await input.getValue()).toBe('#000000'); // Color inputs default to black.
193+
await input.setValue('#00ff00');
194+
expect((await input.getValue()).toLowerCase()).toBe('#00ff00');
195+
});
183196
}
184197

185198
function getActiveElementTagName() {
@@ -220,6 +233,10 @@ function getActiveElementTagName() {
220233
<option value="first">First</option>
221234
</select>
222235
</mat-form-field>
236+
237+
<mat-form-field>
238+
<input matNativeControl placeholder="Color control" id="colorControl" type="color">
239+
</mat-form-field>
223240
`
224241
})
225242
class InputHarnessTest {

tools/public_api_guard/cdk/testing.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ export interface TestElement {
120120
matchesSelector(selector: string): Promise<boolean>;
121121
sendKeys(...keys: (string | TestKey)[]): Promise<void>;
122122
sendKeys(modifiers: ModifierKeys, ...keys: (string | TestKey)[]): Promise<void>;
123+
setInputValue?(value: string): Promise<void>;
123124
text(): Promise<string>;
124125
}
125126

tools/public_api_guard/cdk/testing/protractor.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export declare class ProtractorElement implements TestElement {
1515
matchesSelector(selector: string): Promise<boolean>;
1616
sendKeys(...keys: (string | TestKey)[]): Promise<void>;
1717
sendKeys(modifiers: ModifierKeys, ...keys: (string | TestKey)[]): Promise<void>;
18+
setInputValue(value: string): Promise<void>;
1819
text(): Promise<string>;
1920
}
2021

tools/public_api_guard/cdk/testing/testbed.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,6 @@ export declare class UnitTestElement implements TestElement {
3232
matchesSelector(selector: string): Promise<boolean>;
3333
sendKeys(...keys: (string | TestKey)[]): Promise<void>;
3434
sendKeys(modifiers: ModifierKeys, ...keys: (string | TestKey)[]): Promise<void>;
35+
setInputValue(value: string): Promise<void>;
3536
text(): Promise<string>;
3637
}

0 commit comments

Comments
 (0)