Skip to content

Commit 78b3039

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 39dd216 commit 78b3039

File tree

6 files changed

+54
-10
lines changed

6 files changed

+54
-10
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. */
@@ -100,7 +106,7 @@ export class ProtractorElement implements TestElement {
100106
const first = modifiersAndKeys[0];
101107
let modifiers: ModifierKeys;
102108
let rest: (string | TestKey)[];
103-
if (typeof first !== 'string' && typeof first !== 'number') {
109+
if (first !== undefined && typeof first !== 'string' && typeof first !== 'number') {
104110
modifiers = first;
105111
rest = modifiersAndKeys.slice(1);
106112
} else {
@@ -113,6 +119,12 @@ export class ProtractorElement implements TestElement {
113119
.reduce((arr, k) => arr.concat(k), [])
114120
.map(k => Key.chord(...modifierKeys, k));
115121

122+
// Throw an error if no keys have been specified. Calling this function with no
123+
// keys should not result in a focus event being dispatched unexpectedly.
124+
if (keys.length === 0) {
125+
throw getNoKeysSpecifiedError();
126+
}
127+
116128
return this.element.sendKeys(...keys);
117129
}
118130

src/cdk/testing/test-element.ts

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

8585
/**
8686
* Sends the given string to the input as a series of key presses. Also fires input events
87-
* and attempts to add the string to the Element's value.
87+
* and attempts to add the string to the Element's value. Throws if no keys have been specified.
8888
*/
8989
sendKeys(...keys: (string | TestKey)[]): Promise<void>;
9090

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

@@ -115,3 +115,11 @@ export interface TestElement {
115115
/** Checks whether the element is focused. */
116116
isFocused(): Promise<boolean>;
117117
}
118+
119+
/**
120+
* Returns an error which reports that no keys have been specified.
121+
* @docs-private
122+
*/
123+
export function getNoKeysSpecifiedError() {
124+
return Error('No keys have been specified.');
125+
}

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, element, modifiers);

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
ComponentHarness,
33
ComponentHarnessConstructor,
4+
getNoKeysSpecifiedError,
45
HarnessLoader,
56
TestElement
67
} from '@angular/cdk/testing';
@@ -197,6 +198,13 @@ describe('ProtractorHarnessEnvironment', () => {
197198
expect(await input.getProperty('value')).toBe('');
198199
});
199200

201+
it('sendKeys method should throw if no keys have been specified', async () => {
202+
const input = await harness.input();
203+
await expectAsyncError(() => input.sendKeys(), getNoKeysSpecifiedError().toString());
204+
await expectAsyncError(() => input.sendKeys(''), getNoKeysSpecifiedError().toString());
205+
await expectAsyncError(() => input.sendKeys('', ''), getNoKeysSpecifiedError().toString());
206+
});
207+
200208
it('should be able to click', async () => {
201209
const counter = await harness.counter();
202210
expect(await counter.text()).toBe('0');

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
ComponentHarness,
33
ComponentHarnessConstructor,
4+
getNoKeysSpecifiedError,
45
HarnessLoader,
56
TestElement
67
} from '@angular/cdk/testing';
@@ -16,7 +17,6 @@ import {TestMainComponent} from './test-main-component';
1617
function activeElementText() {
1718
return document.activeElement && (document.activeElement as HTMLElement).innerText || '';
1819
}
19-
2020
describe('TestbedHarnessEnvironment', () => {
2121
let fixture: ComponentFixture<{}>;
2222

@@ -294,6 +294,13 @@ describe('TestbedHarnessEnvironment', () => {
294294
expect(await input.getProperty('value')).toBe('');
295295
});
296296

297+
it('sendKeys method should throw if no keys have been specified', async () => {
298+
const input = await harness.input();
299+
await expectAsyncError(() => input.sendKeys(), getNoKeysSpecifiedError().toString());
300+
await expectAsyncError(() => input.sendKeys(''), getNoKeysSpecifiedError().toString());
301+
await expectAsyncError(() => input.sendKeys('', ''), getNoKeysSpecifiedError().toString());
302+
});
303+
297304
it('should be able to click', async () => {
298305
const counter = await harness.counter();
299306
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
@@ -33,6 +33,8 @@ export interface ElementDimensions {
3333
width: number;
3434
}
3535

36+
export declare function getNoKeysSpecifiedError(): Error;
37+
3638
export declare abstract class HarnessEnvironment<E> implements HarnessLoader, LocatorFactory {
3739
protected rawRootElement: E;
3840
rootElement: TestElement;

0 commit comments

Comments
 (0)