Skip to content

Commit

Permalink
fix(cdk/testing): TestElement sendKeys method should throw if no keys…
Browse files Browse the repository at this point in the history
… have been specified

Previously, calling `sendKeys` without any keys resulted in a runtime
exception `Cannot read X of undefined` error being thrown. This is because
the first element of the passed arguments to `sendKeys` has been considered
always truthy. This assumption is wrong since arbitrary amount of arguments
can be passed due to the spread parameter.

To ensure consistent and reasonable behavior of this function, we fix
the runtime exception mentioned above, but throw if no keys have been
determined (not necessarily only if the arguments length is zero).
  • Loading branch information
devversion committed Sep 11, 2020
1 parent f951bf4 commit 43b5665
Show file tree
Hide file tree
Showing 7 changed files with 63 additions and 10 deletions.
9 changes: 8 additions & 1 deletion src/cdk/testing/protractor/protractor-element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import {
_getTextWithExcludedElements,
ElementDimensions,
getNoKeysSpecifiedError,
ModifierKeys,
TestElement,
TestKey,
Expand Down Expand Up @@ -118,7 +119,7 @@ export class ProtractorElement implements TestElement {
const first = modifiersAndKeys[0];
let modifiers: ModifierKeys;
let rest: (string | TestKey)[];
if (typeof first !== 'string' && typeof first !== 'number') {
if (first !== undefined && typeof first !== 'string' && typeof first !== 'number') {
modifiers = first;
rest = modifiersAndKeys.slice(1);
} else {
Expand All @@ -133,6 +134,12 @@ export class ProtractorElement implements TestElement {
// so avoid it if no modifier keys are required.
.map(k => modifierKeys.length > 0 ? Key.chord(...modifierKeys, k) : k);

// Throw an error if no keys have been specified. Calling this function with no
// keys should not result in a focus event being dispatched unexpectedly.
if (keys.length === 0) {
throw getNoKeysSpecifiedError();
}

return this.element.sendKeys(...keys);
}

Expand Down
1 change: 1 addition & 0 deletions src/cdk/testing/public-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@
export * from './component-harness';
export * from './harness-environment';
export * from './test-element';
export * from './test-element-errors';
export * from './element-dimensions';
export * from './text-filtering';
15 changes: 15 additions & 0 deletions src/cdk/testing/test-element-errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

/**
* Returns an error which reports that no keys have been specified.
* @docs-private
*/
export function getNoKeysSpecifiedError() {
return Error('No keys have been specified.');
}
10 changes: 6 additions & 4 deletions src/cdk/testing/test-element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,14 +90,16 @@ export interface TestElement {
mouseAway(): Promise<void>;

/**
* Sends the given string to the input as a series of key presses. Also fires input events
* and attempts to add the string to the Element's value.
* Sends the given string to the input as a series of key presses. Also fires input
* events and attempts to add the string to the Element's value.
* @throws An error if no keys have been specified.
*/
sendKeys(...keys: (string | TestKey)[]): Promise<void>;

/**
* Sends the given string to the input as a series of key presses. Also fires input events
* and attempts to add the string to the Element's value.
* Sends the given string to the input as a series of key presses. Also fires input
* events and attempts to add the string to the Element's value.
* @throws An error if no keys have been specified.
*/
sendKeys(modifiers: ModifierKeys, ...keys: (string | TestKey)[]): Promise<void>;

Expand Down
17 changes: 12 additions & 5 deletions src/cdk/testing/testbed/fake-events/type-in-element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/

import {ModifierKeys} from '@angular/cdk/testing';
import {getNoKeysSpecifiedError, ModifierKeys} from '@angular/cdk/testing';
import {dispatchFakeEvent, dispatchKeyboardEvent} from './dispatch-events';
import {triggerFocus} from './element-focus';

Expand All @@ -20,7 +20,7 @@ export function isTextInput(element: Element): element is HTMLInputElement | HTM
}

/**
* Focuses an input, sets its value and dispatches
* If keys have been specified, focuses an input, sets its value and dispatches
* the `input` event, simulating the user typing.
* @param element Element onto which to set the value.
* @param keys The keys to send to the element.
Expand All @@ -30,7 +30,7 @@ export function typeInElement(
element: HTMLElement, ...keys: (string | {keyCode?: number, key?: string})[]): void;

/**
* Focuses an input, sets its value and dispatches
* If keys have been specified, focuses an input, sets its value and dispatches
* the `input` event, simulating the user typing.
* @param element Element onto which to set the value.
* @param modifiers Modifier keys that are held while typing.
Expand All @@ -40,11 +40,12 @@ export function typeInElement(
export function typeInElement(element: HTMLElement, modifiers: ModifierKeys,
...keys: (string | {keyCode?: number, key?: string})[]): void;

export function typeInElement(element: HTMLElement, ...modifiersAndKeys: any) {
export function typeInElement(element: HTMLElement, ...modifiersAndKeys: any[]) {
const first = modifiersAndKeys[0];
let modifiers: ModifierKeys;
let rest: (string | {keyCode?: number, key?: string})[];
if (typeof first !== 'string' && first.keyCode === undefined && first.key === undefined) {
if (first !== undefined && typeof first !== 'string' && first.keyCode === undefined &&
first.key === undefined) {
modifiers = first;
rest = modifiersAndKeys.slice(1);
} else {
Expand All @@ -56,6 +57,12 @@ export function typeInElement(element: HTMLElement, ...modifiersAndKeys: any) {
k.split('').map(c => ({keyCode: c.toUpperCase().charCodeAt(0), key: c})) : [k])
.reduce((arr, k) => arr.concat(k), []);

// Throw an error if no keys have been specified. Calling this function with no
// keys should not result in a focus event being dispatched unexpectedly.
if (keys.length === 0) {
throw getNoKeysSpecifiedError();
}

triggerFocus(element);
for (const key of keys) {
dispatchKeyboardEvent(element, 'keydown', key.keyCode, key.key, modifiers);
Expand Down
19 changes: 19 additions & 0 deletions src/cdk/testing/tests/cross-environment.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import {
ComponentHarness,
ComponentHarnessConstructor,
getNoKeysSpecifiedError,
HarnessLoader,
TestElement,
} from '@angular/cdk/testing';
Expand Down Expand Up @@ -303,6 +304,17 @@ export function crossEnvironmentSpecs(
harness = await getMainComponentHarnessFromEnvironment();
});

async function expectAsyncError(fn: () => Promise<void>, expected: Error) {
let error: Error|null = null;
try {
await fn();
} catch (e) {
error = e;
}
expect(error).not.toBe(null);
expect(error!.message).toBe(expected.message);
}

it('should be able to clear', async () => {
const input = await harness.input();
await input.sendKeys('Yi');
Expand All @@ -312,6 +324,13 @@ export function crossEnvironmentSpecs(
expect(await input.getProperty('value')).toBe('');
});

it('sendKeys method should throw if no keys have been specified', async () => {
const input = await harness.input();
await expectAsyncError(() => input.sendKeys(), getNoKeysSpecifiedError());
await expectAsyncError(() => input.sendKeys(''), getNoKeysSpecifiedError());
await expectAsyncError(() => input.sendKeys('', ''), getNoKeysSpecifiedError());
});

it('should be able to click', async () => {
const counter = await harness.counter();
expect(await counter.text()).toBe('0');
Expand Down
2 changes: 2 additions & 0 deletions tools/public_api_guard/cdk/testing.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ export interface ElementDimensions {
width: number;
}

export declare function getNoKeysSpecifiedError(): Error;

export declare abstract class HarnessEnvironment<E> implements HarnessLoader, LocatorFactory {
protected rawRootElement: E;
rootElement: TestElement;
Expand Down

0 comments on commit 43b5665

Please sign in to comment.