-
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.
feat: add selectOptions function (#41)
- Loading branch information
1 parent
6d3d71a
commit ddbc1fc
Showing
13 changed files
with
666 additions
and
12 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 |
---|---|---|
@@ -1,10 +1,13 @@ | ||
import { fireEvent } from '@testing-library/dom'; | ||
import { createType } from './type'; | ||
import { createSelectOptions } from './selectOptions'; | ||
|
||
export interface UserEvents { | ||
type: ReturnType<typeof createType>; | ||
selectOptions: ReturnType<typeof createSelectOptions>; | ||
} | ||
|
||
const type = createType(fireEvent); | ||
const selectOptions = createSelectOptions(fireEvent); | ||
|
||
export { createType, type }; | ||
export { createType, type, createSelectOptions, selectOptions }; |
67 changes: 67 additions & 0 deletions
67
projects/testing-library/src/lib/user-events/selectOptions.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,67 @@ | ||
import { | ||
FireFunction, | ||
FireObject, | ||
Matcher, | ||
getByText, | ||
SelectorMatcherOptions, | ||
queryByText, | ||
} from '@testing-library/dom'; | ||
|
||
// implementation from https://github.com/testing-library/user-event | ||
export function createSelectOptions(fireEvent: FireFunction & FireObject) { | ||
function clickElement(element: HTMLElement) { | ||
fireEvent.mouseOver(element); | ||
fireEvent.mouseMove(element); | ||
fireEvent.mouseDown(element); | ||
fireEvent.focus(element); | ||
fireEvent.mouseUp(element); | ||
fireEvent.click(element); | ||
} | ||
|
||
function selectOption(select: HTMLSelectElement, index: number, matcher: Matcher, options?: SelectorMatcherOptions) { | ||
// fallback to document.body, because libraries as Angular Material will have their custom select component | ||
const option = (queryByText(select, matcher, options) || | ||
getByText(document.body, matcher, options)) as HTMLOptionElement; | ||
|
||
fireEvent.mouseOver(option); | ||
fireEvent.mouseMove(option); | ||
fireEvent.mouseDown(option); | ||
fireEvent.focus(option); | ||
fireEvent.mouseUp(option); | ||
fireEvent.click(option, { ctrlKey: index > 0 }); | ||
|
||
option.selected = true; | ||
fireEvent.change(select); | ||
} | ||
|
||
return async function selectOptions( | ||
element: HTMLElement, | ||
matcher: Matcher | Matcher[], | ||
matcherOptions?: SelectorMatcherOptions, | ||
) { | ||
const selectElement = element as HTMLSelectElement; | ||
|
||
if (selectElement.selectedOptions) { | ||
Array.from(selectElement.selectedOptions).forEach(option => (option.selected = false)); | ||
} | ||
|
||
const focusedElement = document.activeElement; | ||
const wasAnotherElementFocused = focusedElement !== document.body && focusedElement !== selectElement; | ||
|
||
if (wasAnotherElementFocused) { | ||
fireEvent.mouseMove(focusedElement); | ||
fireEvent.mouseLeave(focusedElement); | ||
} | ||
|
||
clickElement(selectElement); | ||
|
||
const values = Array.isArray(matcher) ? matcher : [matcher]; | ||
values | ||
.filter((_, index) => index === 0 || selectElement.multiple) | ||
.forEach((val, index) => selectOption(selectElement, index, val, matcherOptions)); | ||
|
||
if (wasAnotherElementFocused) { | ||
fireEvent.blur(focusedElement); | ||
} | ||
}; | ||
} |
236 changes: 236 additions & 0 deletions
236
projects/testing-library/tests/user-events/selectOptions.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,236 @@ | ||
import { ReactiveFormsModule, FormsModule, FormControl } from '@angular/forms'; | ||
import { render, RenderResult } from '../../src/public_api'; | ||
import { Component, ViewChild, Input } from '@angular/core'; | ||
|
||
describe('selectOption: single', () => { | ||
test('with a template-driven form', async () => { | ||
@Component({ | ||
selector: 'fixture', | ||
template: ` | ||
<select data-testid="select" [(ngModel)]="value"> | ||
<option value="1" data-testid="apples">Apples</option> | ||
<option value="2" data-testid="oranges">Oranges</option> | ||
<option value="3" data-testid="lemons">Lemons</option> | ||
</select> | ||
<p data-testid="text">{{ value }}</p> | ||
`, | ||
}) | ||
class FixtureComponent { | ||
value: string; | ||
} | ||
|
||
const component = await render(FixtureComponent, { | ||
imports: [FormsModule], | ||
}); | ||
|
||
assertSelectOptions(component, () => component.fixture.componentInstance.value); | ||
}); | ||
|
||
test('with a reactive form', async () => { | ||
@Component({ | ||
selector: 'fixture', | ||
template: ` | ||
<select data-testid="select" [formControl]="value"> | ||
<option value="1" data-testid="apples">Apples</option> | ||
<option value="2" data-testid="oranges">Oranges</option> | ||
<option value="3" data-testid="lemons">Lemons</option> | ||
</select> | ||
<p data-testid="text">{{ value.value }}</p> | ||
`, | ||
}) | ||
class FixtureComponent { | ||
value = new FormControl(''); | ||
} | ||
|
||
const component = await render(FixtureComponent, { | ||
imports: [ReactiveFormsModule], | ||
}); | ||
|
||
assertSelectOptions(component, () => component.fixture.componentInstance.value.value); | ||
}); | ||
|
||
test('with change event', async () => { | ||
@Component({ | ||
selector: 'fixture', | ||
template: ` | ||
<select data-testid="select" (change)="onChange($event)"> | ||
<option value="1" data-testid="apples">Apples</option> | ||
<option value="2" data-testid="oranges">Oranges</option> | ||
<option value="3" data-testid="lemons">Lemons</option> | ||
</select> | ||
<p data-testid="text">{{ value }}</p> | ||
`, | ||
}) | ||
class FixtureComponent { | ||
value = ''; | ||
|
||
onChange(event: KeyboardEvent) { | ||
this.value = (<HTMLInputElement>event.target).value; | ||
} | ||
} | ||
|
||
const component = await render(FixtureComponent); | ||
|
||
assertSelectOptions(component, () => component.fixture.componentInstance.value); | ||
}); | ||
|
||
test('by reference', async () => { | ||
@Component({ | ||
selector: 'fixture', | ||
template: ` | ||
<select data-testid="select" #input> | ||
<option value="1" data-testid="apples">Apples</option> | ||
<option value="2" data-testid="oranges">Oranges</option> | ||
<option value="3" data-testid="lemons">Lemons</option> | ||
</select> | ||
<p data-testid="text">{{ input.value }}</p> | ||
`, | ||
}) | ||
class FixtureComponent { | ||
@ViewChild('input', { static: false }) value; | ||
} | ||
|
||
const component = await render(FixtureComponent); | ||
|
||
assertSelectOptions(component, () => component.fixture.componentInstance.value.nativeElement.value); | ||
}); | ||
|
||
function assertSelectOptions(component: RenderResult, value: () => string) { | ||
const inputControl = component.getByTestId('select') as HTMLSelectElement; | ||
component.selectOptions(inputControl, /apples/i); | ||
component.selectOptions(inputControl, 'Oranges'); | ||
|
||
expect(value()).toBe('2'); | ||
expect(component.getByTestId('text').textContent).toBe('2'); | ||
expect(inputControl.value).toBe('2'); | ||
|
||
expect((component.getByTestId('apples') as HTMLOptionElement).selected).toBe(false); | ||
expect((component.getByTestId('oranges') as HTMLOptionElement).selected).toBe(true); | ||
expect((component.getByTestId('lemons') as HTMLOptionElement).selected).toBe(false); | ||
} | ||
}); | ||
|
||
describe('selectOption: multiple', () => { | ||
test('with a template-driven form', async () => { | ||
@Component({ | ||
selector: 'fixture', | ||
template: ` | ||
<select data-testid="select" multiple [(ngModel)]="value"> | ||
<option value="1" data-testid="apples">Apples</option> | ||
<option value="2" data-testid="oranges">Oranges</option> | ||
<option value="3" data-testid="lemons">Lemons</option> | ||
</select> | ||
<p data-testid="text">{{ value }}</p> | ||
`, | ||
}) | ||
class FixtureComponent { | ||
value: string; | ||
} | ||
|
||
const component = await render(FixtureComponent, { | ||
imports: [FormsModule], | ||
}); | ||
assertSelectOptions(component, () => component.fixture.componentInstance.value); | ||
}); | ||
|
||
test('with a reactive form', async () => { | ||
@Component({ | ||
selector: 'fixture', | ||
template: ` | ||
<select data-testid="select" multiple [formControl]="value"> | ||
<option value="1" data-testid="apples">Apples</option> | ||
<option value="2" data-testid="oranges">Oranges</option> | ||
<option value="3" data-testid="lemons">Lemons</option> | ||
</select> | ||
<p data-testid="text">{{ value.value }}</p> | ||
`, | ||
}) | ||
class FixtureComponent { | ||
value = new FormControl(''); | ||
} | ||
|
||
const component = await render(FixtureComponent, { | ||
imports: [ReactiveFormsModule], | ||
}); | ||
|
||
assertSelectOptions(component, () => component.fixture.componentInstance.value.value); | ||
}); | ||
|
||
test('with change event', async () => { | ||
@Component({ | ||
selector: 'fixture', | ||
template: ` | ||
<select data-testid="select" multiple (change)="onChange($event)"> | ||
<option value="1" data-testid="apples">Apples</option> | ||
<option value="2" data-testid="oranges">Oranges</option> | ||
<option value="3" data-testid="lemons">Lemons</option> | ||
</select> | ||
<p data-testid="text">{{ value }}</p> | ||
`, | ||
}) | ||
class FixtureComponent { | ||
value = []; | ||
|
||
onChange(event: KeyboardEvent) { | ||
this.value = Array.from((<HTMLSelectElement>event.target).selectedOptions).map(o => o.value); | ||
} | ||
} | ||
|
||
const component = await render(FixtureComponent); | ||
|
||
assertSelectOptions(component, () => component.fixture.componentInstance.value); | ||
}); | ||
|
||
test('by reference', async () => { | ||
@Component({ | ||
selector: 'fixture', | ||
template: ` | ||
<select data-testid="select" multiple #input> | ||
<option value="1" data-testid="apples">Apples</option> | ||
<option value="2" data-testid="oranges">Oranges</option> | ||
<option value="3" data-testid="lemons">Lemons</option> | ||
</select> | ||
<p data-testid="text">{{ input.value }}</p> | ||
`, | ||
}) | ||
class FixtureComponent { | ||
@ViewChild('input', { static: false }) value; | ||
} | ||
|
||
const component = await render(FixtureComponent); | ||
|
||
const inputControl = component.getByTestId('select') as HTMLSelectElement; | ||
component.selectOptions(inputControl, /apples/i); | ||
component.selectOptions(inputControl, ['Oranges', 'Lemons']); | ||
|
||
const options = component.fixture.componentInstance.value.nativeElement.selectedOptions; | ||
const value = Array.from(options).map((o: any) => o.value); | ||
|
||
expect(value).toEqual(['2', '3']); | ||
// shouldn't this be an empty string? - https://stackblitz.com/edit/angular-pdvm9n | ||
expect(component.getByTestId('text').textContent).toBe('2'); | ||
expect((component.getByTestId('apples') as HTMLOptionElement).selected).toBe(false); | ||
expect((component.getByTestId('oranges') as HTMLOptionElement).selected).toBe(true); | ||
expect((component.getByTestId('lemons') as HTMLOptionElement).selected).toBe(true); | ||
}); | ||
|
||
function assertSelectOptions(component: RenderResult, value: () => string) { | ||
const inputControl = component.getByTestId('select') as HTMLSelectElement; | ||
component.selectOptions(inputControl, /apples/i); | ||
component.selectOptions(inputControl, ['Oranges', 'Lemons']); | ||
|
||
expect(value()).toEqual(['2', '3']); | ||
expect(component.getByTestId('text').textContent).toBe('2,3'); | ||
expect((component.getByTestId('apples') as HTMLOptionElement).selected).toBe(false); | ||
expect((component.getByTestId('oranges') as HTMLOptionElement).selected).toBe(true); | ||
expect((component.getByTestId('lemons') as HTMLOptionElement).selected).toBe(true); | ||
} | ||
}); |
Oops, something went wrong.