Skip to content

Commit

Permalink
feat: allow to pass component properties with the template syntax
Browse files Browse the repository at this point in the history
BREAKING CHANGE:

The parameters parameter has been renamed to componentProperties and has also been moved to RenderOptions.
To create a component via the component syntax, just pass it to the render function.

BEFORE:

```ts
const component = createComponent(
  {
    component: HomeComponent,
    parameters: { welcomeText: 'hello' }
  },
  {
    declarations: [ HomeComponent ]
  }
)
```

AFTER:

```ts
const component = render(HomeComponent, { componentProperties: { welcomeText: 'hello' }, declarations: [ HomeComponent ] })
```
  • Loading branch information
timdeschryver committed May 5, 2019
1 parent 9251f06 commit a6a6fda
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 52 deletions.
10 changes: 3 additions & 7 deletions projects/testing-library/src/lib/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,13 @@ export interface RenderResult extends RenderResultQueries, FireObject {
fixture: ComponentFixture<any>;
}

export interface RenderOptions<W = any, Q extends Queries = typeof queries> {
export interface RenderOptions<C, Q extends Queries = typeof queries> {
detectChanges?: boolean;
declarations: any[];
providers?: any[];
imports?: any[];
schemas?: any[];
componentProperties?: Partial<C>;
queries?: Q;
wrapper?: Type<W>;
}

export interface ComponentInput<T> {
component: Type<T>;
parameters?: Partial<T>;
wrapper?: Type<any>;
}
72 changes: 56 additions & 16 deletions projects/testing-library/src/lib/testing-library.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { Component, OnInit, ElementRef, Type } from '@angular/core';
import { TestBed } from '@angular/core/testing';
import { Component, OnInit, ElementRef, Type, DebugElement } from '@angular/core';
import { TestBed, ComponentFixture } from '@angular/core/testing';
import { getQueriesForElement, prettyDOM, fireEvent, FireObject, FireFunction } from 'dom-testing-library';

import { RenderResult, RenderOptions, ComponentInput } from './models';
import { RenderResult, RenderOptions } from './models';
import { By } from '@angular/platform-browser';

@Component({ selector: 'wrapper-component', template: '' })
class WrapperComponent implements OnInit {
Expand All @@ -13,10 +14,8 @@ class WrapperComponent implements OnInit {
}
}

export async function render<T>(template: string, renderOptions: RenderOptions): Promise<RenderResult>;
export async function render<T>(component: ComponentInput<T>, renderOptions: RenderOptions): Promise<RenderResult>;
export async function render<T>(
templateOrComponent: string | ComponentInput<T>,
templateOrComponent: string | Type<T>,
{
detectChanges = true,
declarations = [],
Expand All @@ -25,7 +24,8 @@ export async function render<T>(
schemas = [],
queries,
wrapper = WrapperComponent,
}: RenderOptions,
componentProperties = {},
}: RenderOptions<T>,
): Promise<RenderResult> {
const isTemplate = typeof templateOrComponent === 'string';
const testComponent = isTemplate ? [wrapper] : [];
Expand All @@ -38,8 +38,8 @@ export async function render<T>(
});

const fixture = isTemplate
? createWrapperComponentFixture(wrapper, <string>templateOrComponent)
: createComponentFixture(<ComponentInput<T>>templateOrComponent);
? createWrapperComponentFixture(templateOrComponent as string, { wrapper, componentProperties })
: createComponentFixture(templateOrComponent as Type<T>, { componentProperties });

await TestBed.compileComponents();

Expand Down Expand Up @@ -71,23 +71,63 @@ export async function render<T>(
/**
* Creates the wrapper component and sets its the template to the to-be-tested component
*/
function createWrapperComponentFixture<T>(wrapper: Type<T>, template: string) {
function createWrapperComponentFixture<T>(
template: string,
{
wrapper,
componentProperties,
}: {
wrapper: RenderOptions<T>['wrapper'];
componentProperties: RenderOptions<T>['componentProperties'];
},
): ComponentFixture<any> {
TestBed.overrideComponent(wrapper, {
set: {
template: template,
},
});
return TestBed.createComponent(wrapper);

const fixture = TestBed.createComponent(wrapper);
// get the component selector, e.g. <foo color="green"> and <foo> results in foo
const componentSelector = template.match(/\<(.*?)\ /) || template.match(/\<(.*?)\>/);
if (!componentSelector) {
throw Error(`Template ${template} is not valid.`);
}

const sut = fixture.debugElement.query(By.css(componentSelector[1]));
setComponentProperties(sut, { componentProperties });
return fixture;
}

/**
* Creates the components and sets its properties via the provided properties from `componentInput`
* Creates the components and sets its properties
*/
function createComponentFixture<T>(componentInput: ComponentInput<T>) {
const { component, parameters = {} } = componentInput;
function createComponentFixture<T>(
component: Type<T>,
{
componentProperties = {},
}: {
componentProperties: RenderOptions<T>['componentProperties'];
},
): ComponentFixture<T> {
const fixture = TestBed.createComponent(component);
for (const key of Object.keys(parameters)) {
fixture.componentInstance[key] = parameters[key];
setComponentProperties(fixture, { componentProperties });
return fixture;
}

/**
* Set the component properties
*/
function setComponentProperties<T>(
fixture: ComponentFixture<T> | DebugElement,
{
componentProperties = {},
}: {
componentProperties: RenderOptions<T>['componentProperties'];
},
) {
for (const key of Object.keys(componentProperties)) {
fixture.componentInstance[key] = componentProperties[key];
}
return fixture;
}
43 changes: 25 additions & 18 deletions projects/testing-library/tests/counter/counter.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,30 @@ test('Counter actions via template syntax', async () => {
expect(getByTestId('count').textContent).toBe('Current Count: 10');
});

test('Counter actions via component syntax', async () => {
const { getByText, getByTestId, click } = await render(
{
component: CounterComponent,
parameters: {
counter: 10,
},
test('Counter actions via template syntax with parameters', async () => {
const { getByText, getByTestId, click } = await render<CounterComponent>('<counter></counter>', {
declarations: [CounterComponent],
componentProperties: {
counter: 10,
},
{
declarations: [CounterComponent],
});

click(getByText('+'));
expect(getByText('Current Count: 11')).toBeTruthy();
expect(getByTestId('count').textContent).toBe('Current Count: 11');

click(getByText('-'));
expect(getByText('Current Count: 10')).toBeTruthy();
expect(getByTestId('count').textContent).toBe('Current Count: 10');
});

test('Counter actions via component syntax', async () => {
const { getByText, getByTestId, click } = await render(CounterComponent, {
declarations: [CounterComponent],
componentProperties: {
counter: 10,
},
);
});

click(getByText('+'));
expect(getByText('Current Count: 11')).toBeTruthy();
Expand All @@ -56,14 +68,9 @@ test('Counter actions via component syntax', async () => {
});

test('Counter actions via component syntax without parameters', async () => {
const { getByText, getByTestId, click } = await render(
{
component: CounterComponent,
},
{
declarations: [CounterComponent],
},
);
const { getByText, getByTestId, click } = await render(CounterComponent, {
declarations: [CounterComponent],
});

click(getByText('+'));
expect(getByText('Current Count: 1')).toBeTruthy();
Expand Down
17 changes: 6 additions & 11 deletions projects/testing-library/tests/form/form.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,13 @@ test('login form submits using the component syntax', async () => {
emit: jest.fn(),
};

const { container, getByLabelText, getByText, input, submit } = await render(
{
component: LoginFormComponent,
parameters: {
handleLogin: handleLogin as any,
},
const { container, getByLabelText, getByText, input, submit } = await render(LoginFormComponent, {
declarations: [LoginFormComponent],
imports: [ReactiveFormsModule],
componentProperties: {
handleLogin: handleLogin as any,
},
{
declarations: [LoginFormComponent],
imports: [ReactiveFormsModule],
},
);
});

const usernameNode = getByLabelText(/username/i) as HTMLInputElement;
const passwordNode = getByLabelText(/password/i) as HTMLInputElement;
Expand Down

0 comments on commit a6a6fda

Please sign in to comment.