Skip to content

Commit 98d0bf5

Browse files
committed
fix(cdk/testing): TestElement sendKeys method should throw if no keys 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).
1 parent 5f5f5aa commit 98d0bf5

File tree

5 files changed

+57
-9
lines changed

5 files changed

+57
-9
lines changed

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

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,13 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {ElementDimensions, ModifierKeys, TestElement, TestKey} from '@angular/cdk/testing';
9+
import {
10+
ElementDimensions,
11+
getNoKeysSpecifiedError,
12+
ModifierKeys,
13+
TestElement,
14+
TestKey
15+
} from '@angular/cdk/testing';
1016
import {browser, ElementFinder, Key} from 'protractor';
1117

1218
/** Maps the `TestKey` constants to Protractor's `Key` constants. */
@@ -111,7 +117,7 @@ export class ProtractorElement implements TestElement {
111117
const first = modifiersAndKeys[0];
112118
let modifiers: ModifierKeys;
113119
let rest: (string | TestKey)[];
114-
if (typeof first !== 'string' && typeof first !== 'number') {
120+
if (first !== undefined && typeof first !== 'string' && typeof first !== 'number') {
115121
modifiers = first;
116122
rest = modifiersAndKeys.slice(1);
117123
} else {
@@ -126,6 +132,12 @@ export class ProtractorElement implements TestElement {
126132
// so avoid it if no modifier keys are required.
127133
.map(k => modifierKeys.length > 0 ? Key.chord(...modifierKeys, k) : k);
128134

135+
// Throw an error if no keys have been specified. Calling this function with no
136+
// keys should not result in a focus event being dispatched unexpectedly.
137+
if (keys.length === 0) {
138+
throw getNoKeysSpecifiedError();
139+
}
140+
129141
return this.element.sendKeys(...keys);
130142
}
131143

src/cdk/testing/test-element.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,13 +91,13 @@ export interface TestElement {
9191

9292
/**
9393
* Sends the given string to the input as a series of key presses. Also fires input events
94-
* and attempts to add the string to the Element's value.
94+
* and attempts to add the string to the Element's value. Throws if no keys have been specified.
9595
*/
9696
sendKeys(...keys: (string | TestKey)[]): Promise<void>;
9797

9898
/**
9999
* Sends the given string to the input as a series of key presses. Also fires input events
100-
* and attempts to add the string to the Element's value.
100+
* and attempts to add the string to the Element's value. Throws if no keys have been specified.
101101
*/
102102
sendKeys(modifiers: ModifierKeys, ...keys: (string | TestKey)[]): Promise<void>;
103103

@@ -128,3 +128,11 @@ export interface TestElement {
128128
/** Checks whether the element is focused. */
129129
isFocused(): Promise<boolean>;
130130
}
131+
132+
/**
133+
* Returns an error which reports that no keys have been specified.
134+
* @docs-private
135+
*/
136+
export function getNoKeysSpecifiedError() {
137+
return Error('No keys have been specified.');
138+
}

src/cdk/testing/testbed/fake-events/type-in-element.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

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

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

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

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

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

60+
// Throw an error if no keys have been specified. Calling this function with no
61+
// keys should not result in a focus event being dispatched unexpectedly.
62+
if (keys.length === 0) {
63+
throw getNoKeysSpecifiedError();
64+
}
65+
5966
triggerFocus(element);
6067
for (const key of keys) {
6168
dispatchKeyboardEvent(element, 'keydown', key.keyCode, key.key, modifiers);

src/cdk/testing/tests/cross-environment.spec.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import {
1010
ComponentHarness,
1111
ComponentHarnessConstructor,
12+
getNoKeysSpecifiedError,
1213
HarnessLoader,
1314
TestElement,
1415
} from '@angular/cdk/testing';
@@ -303,6 +304,17 @@ export function crossEnvironmentSpecs(
303304
harness = await getMainComponentHarnessFromEnvironment();
304305
});
305306

307+
async function expectAsyncError(fn: () => Promise<void>, expected: Error) {
308+
let error: Error|null = null;
309+
try {
310+
await fn();
311+
} catch (e) {
312+
error = e;
313+
}
314+
expect(error).not.toBe(null);
315+
expect(error!.message).toBe(expected.message);
316+
}
317+
306318
it('should be able to clear', async () => {
307319
const input = await harness.input();
308320
await input.sendKeys('Yi');
@@ -312,6 +324,13 @@ export function crossEnvironmentSpecs(
312324
expect(await input.getProperty('value')).toBe('');
313325
});
314326

327+
it('sendKeys method should throw if no keys have been specified', async () => {
328+
const input = await harness.input();
329+
await expectAsyncError(() => input.sendKeys(), getNoKeysSpecifiedError());
330+
await expectAsyncError(() => input.sendKeys(''), getNoKeysSpecifiedError());
331+
await expectAsyncError(() => input.sendKeys('', ''), getNoKeysSpecifiedError());
332+
});
333+
315334
it('should be able to click', async () => {
316335
const counter = await harness.counter();
317336
expect(await counter.text()).toBe('0');

tools/public_api_guard/cdk/testing.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ export interface ElementDimensions {
4040
width: number;
4141
}
4242

43+
export declare function getNoKeysSpecifiedError(): Error;
44+
4345
export declare abstract class HarnessEnvironment<E> implements HarnessLoader, LocatorFactory {
4446
protected rawRootElement: E;
4547
rootElement: TestElement;

0 commit comments

Comments
 (0)