Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit f634d36

Browse files
committedApr 22, 2021
feat(testing-library): add an overload for template rendering
1 parent ea504fc commit f634d36

File tree

4 files changed

+109
-30
lines changed

4 files changed

+109
-30
lines changed
 

‎apps/example-app/src/app/examples/08-directive.spec.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import { render, screen, fireEvent } from '@testing-library/angular';
33
import { SpoilerDirective } from './08-directive';
44

55
test('it is possible to test directives', async () => {
6-
await render(SpoilerDirective, {
7-
template: '<div appSpoiler data-testid="dir"></div>',
6+
await render('<div appSpoiler data-testid="dir"></div>', {
7+
declarations: [SpoilerDirective],
88
});
99

1010
const directive = screen.getByTestId('dir');
@@ -25,8 +25,8 @@ test('it is possible to test directives with props', async () => {
2525
const hidden = 'SPOILER ALERT';
2626
const visible = 'There is nothing to see here ...';
2727

28-
await render(SpoilerDirective, {
29-
template: '<div appSpoiler [hidden]="hidden" [visible]="visible"></div>',
28+
await render('<div appSpoiler [hidden]="hidden" [visible]="visible"></div>', {
29+
declarations: [SpoilerDirective],
3030
componentProperties: {
3131
hidden,
3232
visible,
@@ -49,8 +49,8 @@ test('it is possible to test directives with props in template', async () => {
4949
const hidden = 'SPOILER ALERT';
5050
const visible = 'There is nothing to see here ...';
5151

52-
await render(SpoilerDirective, {
53-
template: `<div appSpoiler hidden="${hidden}" visible="${visible}"></div>`,
52+
await render(`<div appSpoiler hidden="${hidden}" visible="${visible}"></div>`, {
53+
declarations: [SpoilerDirective],
5454
});
5555

5656
expect(screen.queryByText(visible)).not.toBeInTheDocument();

‎projects/testing-library/src/lib/models.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,9 @@ export interface RenderComponentOptions<ComponentType, Q extends Queries = typeo
250250
removeAngularAttributes?: boolean;
251251
}
252252

253+
/**
254+
* @deprecated Use `render(template, { declarations: [SomeDirective] })` instead.
255+
*/
253256
// eslint-disable-next-line @typescript-eslint/ban-types
254257
export interface RenderDirectiveOptions<WrapperType, Properties extends object = {}, Q extends Queries = typeof queries>
255258
extends RenderComponentOptions<Properties, Q> {
@@ -262,6 +265,8 @@ export interface RenderDirectiveOptions<WrapperType, Properties extends object =
262265
* const component = await render(SpoilerDirective, {
263266
* template: `<div spoiler message='SPOILER'></div>`
264267
* })
268+
*
269+
* @deprecated Use `render(template, { declarations: [SomeDirective] })` instead.
265270
*/
266271
template: string;
267272
/**
@@ -282,6 +287,27 @@ export interface RenderDirectiveOptions<WrapperType, Properties extends object =
282287
componentProperties?: Partial<WrapperType & Properties>;
283288
}
284289

290+
// eslint-disable-next-line @typescript-eslint/ban-types
291+
export interface RenderTemplateOptions<WrapperType, Properties extends object = {}, Q extends Queries = typeof queries>
292+
extends RenderComponentOptions<Properties, Q> {
293+
/**
294+
* @description
295+
* An Angular component to wrap the component in.
296+
* The template will be overridden with the `template` option.
297+
*
298+
* @default
299+
* `WrapperComponent`, an empty component that strips the `ng-version` attribute
300+
*
301+
* @example
302+
* const component = await render(SpoilerDirective, {
303+
* template: `<div spoiler message='SPOILER'></div>`
304+
* wrapper: CustomWrapperComponent
305+
* })
306+
*/
307+
wrapper?: Type<WrapperType>;
308+
componentProperties?: Partial<WrapperType & Properties>;
309+
}
310+
285311
export interface Config extends Pick<RenderComponentOptions<any>, 'excludeComponentDeclaration'> {
286312
/**
287313
* DOM Testing Library config

‎projects/testing-library/src/lib/testing-library.ts

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import {
2222
waitForOptions as dtlWaitForOptions,
2323
configure as dtlConfigure,
2424
} from '@testing-library/dom';
25-
import { RenderComponentOptions, RenderDirectiveOptions, RenderResult } from './models';
25+
import { RenderComponentOptions, RenderDirectiveOptions, RenderTemplateOptions, RenderResult } from './models';
2626
import { getConfig } from './config';
2727

2828
const mountedFixtures = new Set<ComponentFixture<any>>();
@@ -32,14 +32,24 @@ export async function render<ComponentType>(
3232
component: Type<ComponentType>,
3333
renderOptions?: RenderComponentOptions<ComponentType>,
3434
): Promise<RenderResult<ComponentType, ComponentType>>;
35+
/**
36+
* @deprecated Use `render(template, { declarations: [DirectiveType] })` instead.
37+
*/
3538
export async function render<DirectiveType, WrapperType = WrapperComponent>(
3639
component: Type<DirectiveType>,
3740
renderOptions?: RenderDirectiveOptions<WrapperType>,
3841
): Promise<RenderResult<WrapperType>>;
42+
export async function render<WrapperType = WrapperComponent>(
43+
template: string,
44+
renderOptions?: RenderTemplateOptions<WrapperType>,
45+
): Promise<RenderResult<WrapperType>>;
3946

4047
export async function render<SutType, WrapperType = SutType>(
41-
sut: Type<SutType>,
42-
renderOptions: RenderComponentOptions<SutType> | RenderDirectiveOptions<WrapperType> = {},
48+
sut: Type<SutType> | string,
49+
renderOptions:
50+
| RenderComponentOptions<SutType>
51+
| RenderDirectiveOptions<WrapperType>
52+
| RenderTemplateOptions<WrapperType> = {},
4353
): Promise<RenderResult<SutType>> {
4454
const { dom: domConfig, ...globalConfig } = getConfig();
4555
const {
@@ -69,7 +79,12 @@ export async function render<SutType, WrapperType = SutType>(
6979
});
7080

7181
TestBed.configureTestingModule({
72-
declarations: addAutoDeclarations(sut, { declarations, excludeComponentDeclaration, template, wrapper }),
82+
declarations: addAutoDeclarations(sut, {
83+
declarations,
84+
excludeComponentDeclaration,
85+
template,
86+
wrapper,
87+
}),
7388
imports: addAutoImports({
7489
imports: imports.concat(defaultImports),
7590
routes,
@@ -176,7 +191,7 @@ export async function render<SutType, WrapperType = SutType>(
176191
detectChanges,
177192
navigate,
178193
rerender,
179-
debugElement: fixture.debugElement.query(By.directive(sut)),
194+
debugElement: typeof sut === 'string' ? fixture.debugElement : fixture.debugElement.query(By.directive(sut)),
180195
container: fixture.nativeElement,
181196
debug: (element = fixture.nativeElement, maxLength, options) =>
182197
Array.isArray(element)
@@ -193,14 +208,18 @@ async function createComponent<SutType>(component: Type<SutType>): Promise<Compo
193208
}
194209

195210
async function createComponentFixture<SutType>(
196-
component: Type<SutType>,
211+
sut: Type<SutType> | string,
197212
{ template, wrapper }: Pick<RenderDirectiveOptions<any>, 'template' | 'wrapper'>,
198213
): Promise<ComponentFixture<SutType>> {
214+
if (typeof sut === 'string') {
215+
TestBed.overrideTemplate(wrapper, sut);
216+
return createComponent(wrapper);
217+
}
199218
if (template) {
200219
TestBed.overrideTemplate(wrapper, template);
201220
return createComponent(wrapper);
202221
}
203-
return createComponent(component);
222+
return createComponent(sut);
204223
}
205224

206225
function setComponentProperties<SutType>(
@@ -248,17 +267,21 @@ function getChangesObj<SutType>(oldProps: Partial<SutType> | null, newProps: Par
248267
}
249268

250269
function addAutoDeclarations<SutType>(
251-
component: Type<SutType>,
270+
sut: Type<SutType> | string,
252271
{
253272
declarations,
254273
excludeComponentDeclaration,
255274
template,
256275
wrapper,
257276
}: Pick<RenderDirectiveOptions<any>, 'declarations' | 'excludeComponentDeclaration' | 'template' | 'wrapper'>,
258277
) {
278+
if (typeof sut === 'string') {
279+
return [...declarations, wrapper];
280+
}
281+
259282
const wrappers = () => (template ? [wrapper] : []);
260283

261-
const components = () => (excludeComponentDeclaration ? [] : [component]);
284+
const components = () => (excludeComponentDeclaration ? [] : [sut]);
262285

263286
return [...declarations, ...wrappers(), ...components()];
264287
}

‎projects/testing-library/tests/directive.spec.ts renamed to ‎projects/testing-library/tests/render-template.spec.ts

Lines changed: 45 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
/* eslint-disable testing-library/no-container */
2+
/* eslint-disable testing-library/render-result-naming-convention */
13
import { Directive, HostListener, ElementRef, Input, Output, EventEmitter, Component } from '@angular/core';
24

35
import { render, fireEvent } from '../src/public_api';
46

57
@Directive({
8+
// eslint-disable-next-line @angular-eslint/directive-selector
69
selector: '[onOff]',
710
})
811
export class OnOffDirective {
@@ -21,6 +24,7 @@ export class OnOffDirective {
2124
}
2225

2326
@Directive({
27+
// eslint-disable-next-line @angular-eslint/directive-selector
2428
selector: '[update]',
2529
})
2630
export class UpdateInputDirective {
@@ -32,27 +36,53 @@ export class UpdateInputDirective {
3236
constructor(private el: ElementRef) {}
3337
}
3438

39+
@Component({
40+
// eslint-disable-next-line @angular-eslint/component-selector
41+
selector: 'greeting',
42+
template: 'Hello {{ name }}!',
43+
})
44+
export class GreetingComponent {
45+
@Input() name = 'World';
46+
}
47+
3548
test('the directive renders', async () => {
36-
const component = await render(OnOffDirective, {
37-
template: '<div onOff></div>',
49+
const component = await render('<div onOff></div>', {
50+
declarations: [OnOffDirective],
3851
});
3952

4053
expect(component.container.querySelector('[onoff]')).toBeInTheDocument();
4154
});
4255

43-
test('uses the default props', async () => {
56+
test('the component renders', async () => {
57+
const component = await render('<greeting name="Angular"></greeting>', {
58+
declarations: [GreetingComponent],
59+
});
60+
61+
expect(component.container.querySelector('greeting')).toBeInTheDocument();
62+
expect(component.getByText('Hello Angular!'));
63+
});
64+
65+
test('the directive renders (compatibility with the deprecated signature)', async () => {
4466
const component = await render(OnOffDirective, {
4567
template: '<div onOff></div>',
4668
});
4769

70+
expect(component.container.querySelector('[onoff]')).toBeInTheDocument();
71+
});
72+
73+
test.only('uses the default props', async () => {
74+
const component = await render('<div onOff></div>', {
75+
declarations: [OnOffDirective],
76+
});
77+
4878
fireEvent.click(component.getByText('init'));
4979
fireEvent.click(component.getByText('on'));
5080
fireEvent.click(component.getByText('off'));
5181
});
5282

5383
test('overrides input properties', async () => {
54-
const component = await render(OnOffDirective, {
55-
template: '<div onOff on="hello"></div>',
84+
const component = await render('<div onOff on="hello"></div>', {
85+
declarations: [OnOffDirective],
5686
});
5787

5888
fireEvent.click(component.getByText('init'));
@@ -62,8 +92,8 @@ test('overrides input properties', async () => {
6292

6393
test('overrides input properties via a wrapper', async () => {
6494
// `bar` will be set as a property on the wrapper component, the property will be used to pass to the directive
65-
const component = await render(OnOffDirective, {
66-
template: '<div onOff [on]="bar"></div>',
95+
const component = await render('<div onOff [on]="bar"></div>', {
96+
declarations: [OnOffDirective],
6797
componentProperties: {
6898
bar: 'hello',
6999
},
@@ -77,8 +107,8 @@ test('overrides input properties via a wrapper', async () => {
77107
test('overrides output properties', async () => {
78108
const clicked = jest.fn();
79109

80-
const component = await render(OnOffDirective, {
81-
template: '<div onOff (clicked)="clicked($event)"></div>',
110+
const component = await render('<div onOff (clicked)="clicked($event)"></div>', {
111+
declarations: [OnOffDirective],
82112
componentProperties: {
83113
clicked,
84114
},
@@ -93,8 +123,8 @@ test('overrides output properties', async () => {
93123

94124
describe('removeAngularAttributes', () => {
95125
test('should remove angular attributes', async () => {
96-
await render(OnOffDirective, {
97-
template: '<div onOff (clicked)="clicked($event)"></div>',
126+
await render('<div onOff (clicked)="clicked($event)"></div>', {
127+
declarations: [OnOffDirective],
98128
removeAngularAttributes: true,
99129
});
100130

@@ -103,8 +133,8 @@ describe('removeAngularAttributes', () => {
103133
});
104134

105135
test('is disabled by default', async () => {
106-
await render(OnOffDirective, {
107-
template: '<div onOff (clicked)="clicked($event)"></div>',
136+
await render('<div onOff (clicked)="clicked($event)"></div>', {
137+
declarations: [OnOffDirective],
108138
});
109139

110140
expect(document.querySelector('[ng-version]')).not.toBeNull();
@@ -113,8 +143,8 @@ describe('removeAngularAttributes', () => {
113143
});
114144

115145
test('updates properties and invokes change detection', async () => {
116-
const component = await render(UpdateInputDirective, {
117-
template: '<div [update]="value" ></div>',
146+
const component = await render('<div [update]="value" ></div>', {
147+
declarations: [UpdateInputDirective],
118148
componentProperties: {
119149
value: 'value1',
120150
},

0 commit comments

Comments
 (0)
Please sign in to comment.