Skip to content

Commit

Permalink
feat(cdk/testing): add method to set the text of an element
Browse files Browse the repository at this point in the history
Adds the `TestElement.setText` method that sets the text content of a node. We need this in order to set the value on a `contenteditable` element.
  • Loading branch information
crisbeto committed Jan 18, 2023
1 parent a7842cc commit 62bd54e
Show file tree
Hide file tree
Showing 10 changed files with 71 additions and 0 deletions.
14 changes: 14 additions & 0 deletions src/cdk/testing/protractor/protractor-element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,20 @@ export class ProtractorElement implements TestElement {
return browser.executeScript(`return (arguments[0].textContent || '').trim()`, this.element);
}

/**
* Sets the value of a `contenteditable` element.
* @param value Value to be set on the element.
*/
async setContenteditableValue(value: string): Promise<void> {
const contenteditableAttr = await this.getAttribute('contenteditable');

if (contenteditableAttr !== '' && contenteditableAttr !== 'true') {
throw new Error('setContenteditableValue can only be called on a `contenteditable` element.');
}

return browser.executeScript(`arguments[0].textContent = arguments[1];`, this.element, value);
}

/** Gets the value for the given attribute from the element. */
async getAttribute(name: string): Promise<string | null> {
return browser.executeScript(
Expand Down
19 changes: 19 additions & 0 deletions src/cdk/testing/selenium-webdriver/selenium-web-driver-element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,25 @@ export class SeleniumWebDriverElement implements TestElement {
);
}

/**
* Sets the value of a `contenteditable` element.
* @param value Value to be set on the element.
*/
async setContenteditableValue(value: string): Promise<void> {
const contenteditableAttr = await this.getAttribute('contenteditable');

if (contenteditableAttr !== '' && contenteditableAttr !== 'true') {
throw new Error('setContenteditableValue can only be called on a `contenteditable` element.');
}

await this._stabilize();
return this._executeScript(
(element: Element, valueToSet: string) => (element.textContent = valueToSet),
this.element(),
value,
);
}

/** Gets the value for the given attribute from the element. */
async getAttribute(name: string): Promise<string | null> {
await this._stabilize();
Expand Down
7 changes: 7 additions & 0 deletions src/cdk/testing/test-element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,13 @@ export interface TestElement {
*/
text(options?: TextOptions): Promise<string>;

/**
* Sets the value of a `contenteditable` element.
* @param value Value to be set on the element.
* @breaking-change 16.0.0 Will become a required method.
*/
setContenteditableValue?(value: string): Promise<void>;

/** Gets the value for the given attribute from the element. */
getAttribute(name: string): Promise<string | null>;

Expand Down
15 changes: 15 additions & 0 deletions src/cdk/testing/testbed/unit-test-element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,21 @@ export class UnitTestElement implements TestElement {
return (this.element.textContent || '').trim();
}

/**
* Sets the value of a `contenteditable` element.
* @param value Value to be set on the element.
*/
async setContenteditableValue(value: string): Promise<void> {
const contenteditableAttr = await this.getAttribute('contenteditable');

if (contenteditableAttr !== '' && contenteditableAttr !== 'true') {
throw new Error('setContenteditableValue can only be called on a `contenteditable` element.');
}

await this._stabilize();
this.element.textContent = value;
}

/** Gets the value for the given attribute from the element. */
async getAttribute(name: string): Promise<string | null> {
await this._stabilize();
Expand Down
11 changes: 11 additions & 0 deletions src/cdk/testing/tests/cross-environment.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -716,6 +716,17 @@ export function crossEnvironmentSpecs(
const hiddenElement = await harness.hidden();
expect(await hiddenElement.text()).toBe('Hello');
});

it('should be able to set the value of a contenteditable element', async () => {
const element = await harness.contenteditable();
expect(await element.text()).not.toBe('hello');

// @breaking-change 16.0.0 Remove non-null assertion once `setContenteditableValue`
// becomes a required method.
await element.setContenteditableValue!('hello');

expect(await element.text()).toBe('hello');
});
});
}

Expand Down
1 change: 1 addition & 0 deletions src/cdk/testing/tests/harnesses/main-component-harness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export class MainComponentHarness extends ComponentHarness {
readonly numberInput = this.locatorFor('#number-input');
readonly numberInputValue = this.locatorFor('#number-input-value');
readonly contextmenuTestResult = this.locatorFor('.contextmenu-test-result');
readonly contenteditable = this.locatorFor('#contenteditable');
// Allow null for element
readonly nullItem = this.locatorForOptional('wrong locator');
// Allow null for component harness
Expand Down
1 change: 1 addition & 0 deletions src/cdk/testing/tests/test-main-component.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ <h1 style="height: 100px; width: 200px;">Main Component</h1>
<label>AsyncCounter:</label>
<div id="asyncCounter">{{asyncCounter}}</div>
</div>
<span id="contenteditable" contenteditable>initial value</span>
<div class="inputs">
<input [(ngModel)]="input" id="input" aria-label="input" (keydown)="onKeyDown($event)">
<span class="special-key">{{specialKey}}</span>
Expand Down
1 change: 1 addition & 0 deletions tools/public_api_guard/cdk/testing-selenium-webdriver.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export class SeleniumWebDriverElement implements TestElement {
selectOptions(...optionIndexes: number[]): Promise<void>;
sendKeys(...keys: (string | TestKey)[]): Promise<void>;
sendKeys(modifiers: ModifierKeys, ...keys: (string | TestKey)[]): Promise<void>;
setContenteditableValue(value: string): Promise<void>;
setInputValue(newValue: string): Promise<void>;
text(options?: TextOptions): Promise<string>;
}
Expand Down
1 change: 1 addition & 0 deletions tools/public_api_guard/cdk/testing-testbed.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export class UnitTestElement implements TestElement {
selectOptions(...optionIndexes: number[]): Promise<void>;
sendKeys(...keys: (string | TestKey)[]): Promise<void>;
sendKeys(modifiers: ModifierKeys, ...keys: (string | TestKey)[]): Promise<void>;
setContenteditableValue(value: string): Promise<void>;
setInputValue(value: string): Promise<void>;
text(options?: TextOptions): Promise<string>;
}
Expand Down
1 change: 1 addition & 0 deletions tools/public_api_guard/cdk/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@ export interface TestElement {
selectOptions(...optionIndexes: number[]): Promise<void>;
sendKeys(...keys: (string | TestKey)[]): Promise<void>;
sendKeys(modifiers: ModifierKeys, ...keys: (string | TestKey)[]): Promise<void>;
setContenteditableValue?(value: string): Promise<void>;
setInputValue(value: string): Promise<void>;
text(options?: TextOptions): Promise<string>;
}
Expand Down

0 comments on commit 62bd54e

Please sign in to comment.