-
Notifications
You must be signed in to change notification settings - Fork 92
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
b65505e
commit 41365e3
Showing
11 changed files
with
396 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { fireEvent } from '@testing-library/dom'; | ||
import { createType } from './type'; | ||
|
||
export interface UserEvents { | ||
type: ReturnType<typeof createType>; | ||
} | ||
|
||
const type = createType(fireEvent); | ||
|
||
export { createType, type }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import { FireFunction, FireObject } from '@testing-library/dom'; | ||
|
||
function wait(time) { | ||
return new Promise(function(resolve) { | ||
setTimeout(() => resolve(), time); | ||
}); | ||
} | ||
|
||
// implementation from https://github.com/testing-library/user-event | ||
export function createType(fireEvent: FireFunction & FireObject) { | ||
function createFireChangeEvent(value: string) { | ||
return function fireChangeEvent(event) { | ||
if (value !== event.target.value) { | ||
fireEvent.change(event.target); | ||
} | ||
event.target.removeEventListener('blur', fireChangeEvent); | ||
}; | ||
} | ||
|
||
return async function type(element: HTMLElement, value: string, { allAtOnce = false, delay = 0 } = {}) { | ||
const initialValue = (element as HTMLInputElement).value; | ||
|
||
if (allAtOnce) { | ||
fireEvent.input(element, { target: { value } }); | ||
element.addEventListener('blur', createFireChangeEvent(initialValue)); | ||
return; | ||
} | ||
|
||
let actuallyTyped = ''; | ||
for (let index = 0; index < value.length; index++) { | ||
const char = value[index]; | ||
const key = char; | ||
const keyCode = char.charCodeAt(0); | ||
|
||
if (delay > 0) { | ||
await wait(delay); | ||
} | ||
|
||
const downEvent = fireEvent.keyDown(element, { | ||
key: key, | ||
keyCode: keyCode, | ||
which: keyCode, | ||
}); | ||
|
||
if (downEvent) { | ||
const pressEvent = fireEvent.keyPress(element, { | ||
key: key, | ||
keyCode, | ||
charCode: keyCode, | ||
}); | ||
|
||
if (pressEvent) { | ||
actuallyTyped += key; | ||
fireEvent.input(element, { | ||
target: { | ||
value: actuallyTyped, | ||
}, | ||
bubbles: true, | ||
cancelable: true, | ||
}); | ||
} | ||
} | ||
|
||
fireEvent.keyUp(element, { | ||
key: key, | ||
keyCode: keyCode, | ||
which: keyCode, | ||
}); | ||
} | ||
|
||
element.addEventListener('blur', createFireChangeEvent(initialValue)); | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
221 changes: 221 additions & 0 deletions
221
projects/testing-library/tests/user-events/type.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,221 @@ | ||
import { ReactiveFormsModule, FormsModule, FormControl } from '@angular/forms'; | ||
import { render, RenderResult } from '../../src/public_api'; | ||
import { Component, ViewChild, Input } from '@angular/core'; | ||
import { fakeAsync, tick } from '@angular/core/testing'; | ||
|
||
describe('updates the value', () => { | ||
test('with a template-driven form', async () => { | ||
@Component({ | ||
selector: 'fixture', | ||
template: ` | ||
<input type="text" [(ngModel)]="value" data-testid="input" /> | ||
<p data-testid="text">{{ value }}</p> | ||
`, | ||
}) | ||
class FixtureComponent { | ||
value: string; | ||
} | ||
|
||
const component = await render(FixtureComponent, { | ||
imports: [FormsModule], | ||
}); | ||
|
||
assertType(component, () => component.fixture.componentInstance.value); | ||
}); | ||
|
||
test('with a reactive form', async () => { | ||
@Component({ | ||
selector: 'fixture', | ||
template: ` | ||
<input type="text" [formControl]="value" data-testid="input" /> | ||
<p data-testid="text">{{ value.value }}</p> | ||
`, | ||
}) | ||
class FixtureComponent { | ||
value = new FormControl(''); | ||
} | ||
|
||
const component = await render(FixtureComponent, { | ||
imports: [ReactiveFormsModule], | ||
}); | ||
|
||
assertType(component, () => component.fixture.componentInstance.value.value); | ||
}); | ||
|
||
test('with events', async () => { | ||
@Component({ | ||
selector: 'fixture', | ||
template: ` | ||
<input type="text" (input)="onInput($event)" data-testid="input" /> | ||
<p data-testid="text">{{ value }}</p> | ||
`, | ||
}) | ||
class FixtureComponent { | ||
value = ''; | ||
|
||
onInput(event: KeyboardEvent) { | ||
this.value = (<HTMLInputElement>event.target).value; | ||
} | ||
} | ||
|
||
const component = await render(FixtureComponent); | ||
|
||
assertType(component, () => component.fixture.componentInstance.value); | ||
}); | ||
|
||
test('by reference', async () => { | ||
@Component({ | ||
selector: 'fixture', | ||
template: ` | ||
<input type="text" data-testid="input" #input /> | ||
<p data-testid="text">{{ input.value }}</p> | ||
`, | ||
}) | ||
class FixtureComponent { | ||
@ViewChild('input', { static: false }) value; | ||
} | ||
|
||
const component = await render(FixtureComponent); | ||
|
||
assertType(component, () => component.fixture.componentInstance.value.nativeElement.value); | ||
}); | ||
|
||
function assertType(component: RenderResult, value: () => string) { | ||
const input = '@testing-library/angular'; | ||
const inputControl = component.getByTestId('input') as HTMLInputElement; | ||
component.type(inputControl, input); | ||
|
||
expect(value()).toBe(input); | ||
expect(component.getByTestId('text').textContent).toBe(input); | ||
expect(inputControl.value).toBe(input); | ||
expect(inputControl).toHaveProperty('value', input); | ||
} | ||
}); | ||
|
||
describe('options', () => { | ||
@Component({ | ||
selector: 'fixture', | ||
template: ` | ||
<input | ||
type="text" | ||
data-testid="input" | ||
(input)="onInput($event)" | ||
(change)="onChange($event)" | ||
(keydown)="onKeyDown($event)" | ||
(keypress)="onKeyPress($event)" | ||
(keyup)="onKeyUp($event)" | ||
/> | ||
`, | ||
}) | ||
class FixtureComponent { | ||
onInput($event) {} | ||
onChange($event) {} | ||
onKeyDown($event) {} | ||
onKeyPress($event) {} | ||
onKeyUp($event) {} | ||
} | ||
|
||
async function setup() { | ||
const componentProperties = { | ||
onInput: jest.fn(), | ||
onChange: jest.fn(), | ||
onKeyDown: jest.fn(), | ||
onKeyPress: jest.fn(), | ||
onKeyUp: jest.fn(), | ||
}; | ||
const component = await render(FixtureComponent, { componentProperties }); | ||
|
||
return { component, ...componentProperties }; | ||
} | ||
|
||
describe('allAtOnce', () => { | ||
test('false: updates the value one char at a time', async () => { | ||
const { component, onInput, onChange, onKeyDown, onKeyPress, onKeyUp } = await setup(); | ||
|
||
const inputControl = component.getByTestId('input') as HTMLInputElement; | ||
const inputValue = 'foobar'; | ||
component.type(inputControl, inputValue); | ||
|
||
expect(onInput).toBeCalledTimes(inputValue.length); | ||
expect(onKeyDown).toBeCalledTimes(inputValue.length); | ||
expect(onKeyPress).toBeCalledTimes(inputValue.length); | ||
expect(onKeyUp).toBeCalledTimes(inputValue.length); | ||
|
||
component.blur(inputControl); | ||
expect(onChange).toBeCalledTimes(1); | ||
}); | ||
|
||
test('true: updates the value in one time and does not trigger other events', async () => { | ||
const { component, onInput, onChange, onKeyDown, onKeyPress, onKeyUp } = await setup(); | ||
|
||
const inputControl = component.getByTestId('input') as HTMLInputElement; | ||
const inputValue = 'foobar'; | ||
component.type(inputControl, inputValue, { allAtOnce: true }); | ||
|
||
expect(onInput).toBeCalledTimes(1); | ||
expect(onKeyDown).toBeCalledTimes(0); | ||
expect(onKeyPress).toBeCalledTimes(0); | ||
expect(onKeyUp).toBeCalledTimes(0); | ||
|
||
component.blur(inputControl); | ||
expect(onChange).toBeCalledTimes(1); | ||
}); | ||
}); | ||
|
||
describe('delay', () => { | ||
test('delays the input', fakeAsync(async () => { | ||
const { component } = await setup(); | ||
|
||
const inputControl = component.getByTestId('input') as HTMLInputElement; | ||
const inputValue = 'foobar'; | ||
component.type(inputControl, inputValue, { delay: 25 }); | ||
|
||
[...inputValue].forEach((_, i) => { | ||
expect(inputControl.value).toBe(inputValue.substr(0, i)); | ||
tick(25); | ||
}); | ||
})); | ||
}); | ||
}); | ||
|
||
test('should not type when event.preventDefault() is called', async () => { | ||
@Component({ | ||
selector: 'fixture', | ||
template: ` | ||
<input | ||
type="text" | ||
data-testid="input" | ||
(input)="onInput($event)" | ||
(change)="onChange($event)" | ||
(keydown)="onKeyDown($event)" | ||
(keypress)="onKeyPress($event)" | ||
(keyup)="onKeyUp($event)" | ||
/> | ||
`, | ||
}) | ||
class FixtureComponent { | ||
onInput($event) {} | ||
onChange($event) {} | ||
onKeyDown($event) {} | ||
onKeyPress($event) {} | ||
onKeyUp($event) {} | ||
} | ||
|
||
const componentProperties = { | ||
onChange: jest.fn(), | ||
onKeyDown: jest.fn().mockImplementation(event => event.preventDefault()), | ||
}; | ||
|
||
const component = await render(FixtureComponent, { componentProperties }); | ||
|
||
const inputControl = component.getByTestId('input') as HTMLInputElement; | ||
const inputValue = 'foobar'; | ||
component.type(inputControl, inputValue); | ||
|
||
expect(componentProperties.onKeyDown).toHaveBeenCalledTimes(inputValue.length); | ||
|
||
component.blur(inputControl); | ||
expect(componentProperties.onChange).toBeCalledTimes(0); | ||
|
||
expect(inputControl.value).toBe(''); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.