Skip to content

fix(input/testing): unable to set the value of inputs that don't respond to typing #18812

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/cdk/testing/protractor/protractor-element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,10 @@ export class ProtractorElement implements TestElement {
return browser.executeScript(`return arguments[0][arguments[1]]`, this.element, name);
}

async setInputValue(value: string): Promise<void> {
return browser.executeScript(`arguments[0].value = arguments[1]`, this.element, value);
}

async matchesSelector(selector: string): Promise<boolean> {
return browser.executeScript(`
return (Element.prototype.matches ||
Expand Down
6 changes: 6 additions & 0 deletions src/cdk/testing/test-element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,12 @@ export interface TestElement {
/** Gets the value of a property of an element. */
getProperty(name: string): Promise<any>;

/**
* Sets the value of a property of an input.
* @breaking-change 11.0.0 To become a required method.
*/
setInputValue?(value: string): Promise<void>;

/** Checks whether this element matches the given selector. */
matchesSelector(selector: string): Promise<boolean>;

Expand Down
5 changes: 5 additions & 0 deletions src/cdk/testing/testbed/unit-test-element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,11 @@ export class UnitTestElement implements TestElement {
return (this.element as any)[name];
}

async setInputValue(value: string): Promise<void> {
await this._stabilize();
(this.element as any).value = value;
}

async matchesSelector(selector: string): Promise<boolean> {
await this._stabilize();
const elementPrototype = Element.prototype as any;
Expand Down
8 changes: 8 additions & 0 deletions src/cdk/testing/tests/protractor.e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,14 @@ describe('ProtractorHarnessEnvironment', () => {
expect(await input.getProperty('value')).toBe('Hello');
});

it('should be able to set the value of an input', async () => {
const input = await harness.input();

// @breaking-change 11.0.0 Remove non-null assertion once `setInputValue` is required.
await input.setInputValue!('hello');
expect(await input.getProperty('value')).toBe('hello');
});

it('should check if selector matches', async () => {
const button = await harness.button();
expect(await button.matchesSelector('button:not(.fake-class)')).toBe(true);
Expand Down
8 changes: 8 additions & 0 deletions src/cdk/testing/tests/testbed.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,14 @@ describe('TestbedHarnessEnvironment', () => {
expect(await input.getProperty('value')).toBe('Hello');
});

it('should be able to set the value of an input', async () => {
const input = await harness.input();

// @breaking-change 11.0.0 Remove non-null assertion once `setInputValue` is required.
await input.setInputValue!('hello');
expect(await input.getProperty('value')).toBe('hello');
});

it('should check if selector matches', async () => {
const button = await harness.button();
expect(await button.matchesSelector('button:not(.fake-class)')).toBe(true);
Expand Down
1 change: 1 addition & 0 deletions src/material/input/testing/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ ng_test_library(
srcs = ["shared.spec.ts"],
deps = [
":testing",
"//src/cdk/platform",
"//src/cdk/testing",
"//src/cdk/testing/testbed",
"//src/material/input",
Expand Down
8 changes: 8 additions & 0 deletions src/material/input/testing/input-harness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,5 +111,13 @@ export class MatInputHarness extends MatFormFieldControlHarness {
if (newValue) {
await inputEl.sendKeys(newValue);
}

// Some input types won't respond to key presses (e.g. `color`) so to be sure that the
// value is set, we also set the property after the keyboard sequence. Note that we don't
// want to do it before, because it can cause the value to be entered twice.
// @breaking-change 11.0.0 Remove non-null assertion once `setInputValue` is required.
if (inputEl.setInputValue) {
await inputEl.setInputValue(newValue);
}
}
}
37 changes: 27 additions & 10 deletions src/material/input/testing/shared.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {Component} from '@angular/core';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {ReactiveFormsModule} from '@angular/forms';
import {MatInputModule} from '@angular/material/input';
import {getSupportedInputTypes} from '@angular/cdk/platform';
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
import {MatInputHarness} from './input-harness';

Expand All @@ -28,7 +29,7 @@ export function runHarnessTests(

it('should load all input harnesses', async () => {
const inputs = await loader.getAllHarnesses(inputHarness);
expect(inputs.length).toBe(5);
expect(inputs.length).toBe(6);
});

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

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

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

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

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

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

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

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

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

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

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

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

it('should be able to get type of input', async () => {
const inputs = await loader.getAllHarnesses(inputHarness);
expect(inputs.length).toBe(5);
expect(inputs.length).toBe(6);
expect(await inputs[0].getType()).toBe('text');
expect(await inputs[1].getType()).toBe('number');
expect(await inputs[2].getType()).toBe('textarea');
Expand All @@ -180,6 +181,18 @@ export function runHarnessTests(
await input.blur();
expect(getActiveElementTagName()).not.toBe('input');
});

it('should be able to set the value of a control that cannot be typed in', async () => {
// We can't test this against browsers that don't support color inputs.
if (!getSupportedInputTypes().has('color')) {
return;
}

const input = await loader.getHarness(inputHarness.with({selector: '#colorControl'}));
expect(await input.getValue()).toBe('#000000'); // Color inputs default to black.
await input.setValue('#00ff00');
expect((await input.getValue()).toLowerCase()).toBe('#00ff00');
});
}

function getActiveElementTagName() {
Expand Down Expand Up @@ -220,6 +233,10 @@ function getActiveElementTagName() {
<option value="first">First</option>
</select>
</mat-form-field>

<mat-form-field>
<input matNativeControl placeholder="Color control" id="colorControl" type="color">
</mat-form-field>
`
})
class InputHarnessTest {
Expand Down
1 change: 1 addition & 0 deletions tools/public_api_guard/cdk/testing.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ export interface TestElement {
matchesSelector(selector: string): Promise<boolean>;
sendKeys(...keys: (string | TestKey)[]): Promise<void>;
sendKeys(modifiers: ModifierKeys, ...keys: (string | TestKey)[]): Promise<void>;
setInputValue?(value: string): Promise<void>;
text(): Promise<string>;
}

Expand Down
1 change: 1 addition & 0 deletions tools/public_api_guard/cdk/testing/protractor.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export declare class ProtractorElement implements TestElement {
matchesSelector(selector: string): Promise<boolean>;
sendKeys(...keys: (string | TestKey)[]): Promise<void>;
sendKeys(modifiers: ModifierKeys, ...keys: (string | TestKey)[]): Promise<void>;
setInputValue(value: string): Promise<void>;
text(): Promise<string>;
}

Expand Down
1 change: 1 addition & 0 deletions tools/public_api_guard/cdk/testing/testbed.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,6 @@ export declare class UnitTestElement implements TestElement {
matchesSelector(selector: string): Promise<boolean>;
sendKeys(...keys: (string | TestKey)[]): Promise<void>;
sendKeys(modifiers: ModifierKeys, ...keys: (string | TestKey)[]): Promise<void>;
setInputValue(value: string): Promise<void>;
text(): Promise<string>;
}