Skip to content

Commit

Permalink
feat: support componentImports option for standalone components
Browse files Browse the repository at this point in the history
  • Loading branch information
jadengis committed Jul 6, 2022
1 parent ed262f6 commit 57c77e2
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 16 deletions.
15 changes: 15 additions & 0 deletions projects/testing-library/src/lib/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,21 @@ export interface RenderComponentOptions<ComponentType, Q extends Queries = typeo
* })
*/
componentProviders?: any[];
/**
* @description
* A collection of imports to override a standalone component's imports with.
*
* @default
* undefined
*
* @example
* const component = await render(AppComponent, {
* componentImports: [
* MockChildComponent
* ]
* })
*/
componentImports?: (Type<any> | any[])[];
/**
* @description
* Queries to bind. Overrides the default set from DOM Testing Library unless merged.
Expand Down
42 changes: 28 additions & 14 deletions projects/testing-library/src/lib/testing-library.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export async function render<SutType, WrapperType = SutType>(
wrapper = WrapperComponent as Type<WrapperType>,
componentProperties = {},
componentProviders = [],
componentImports,
excludeComponentDeclaration = false,
routes = [],
removeAngularAttributes = false,
Expand Down Expand Up @@ -83,6 +84,7 @@ export async function render<SutType, WrapperType = SutType>(
providers: [...providers],
schemas: [...schemas],
});
overrideComponentImports(sut, componentImports);

await TestBed.compileComponents();

Expand Down Expand Up @@ -128,23 +130,23 @@ export async function render<SutType, WrapperType = SutType>(
const [path, params] = (basePath + href).split('?');
const queryParams = params
? params.split('&').reduce((qp, q) => {
const [key, value] = q.split('=');
const currentValue = qp[key];
if (typeof currentValue === 'undefined') {
qp[key] = value;
} else if (Array.isArray(currentValue)) {
qp[key] = [...currentValue, value];
} else {
qp[key] = [currentValue, value];
}
return qp;
}, {} as Record<string, string | string[]>)
const [key, value] = q.split('=');
const currentValue = qp[key];
if (typeof currentValue === 'undefined') {
qp[key] = value;
} else if (Array.isArray(currentValue)) {
qp[key] = [...currentValue, value];
} else {
qp[key] = [currentValue, value];
}
return qp;
}, {} as Record<string, string | string[]>)
: undefined;

const navigateOptions: NavigationExtras | undefined = queryParams
? {
queryParams,
}
queryParams,
}
: undefined;

const doNavigate = () => {
Expand Down Expand Up @@ -264,6 +266,18 @@ function setComponentProperties<SutType>(
return fixture;
}

function overrideComponentImports<SutType>(sut: Type<SutType> | string, imports: (Type<any> | any[])[] | undefined) {
if (imports) {
if (typeof sut === 'function' && ɵisStandalone(sut)) {
TestBed.overrideComponent(sut, { set: { imports } });
} else {
throw new Error(
`Error while rendering ${sut}: Cannot specify componentImports on a template or non-standalone component.`,
);
}
}
}

function hasOnChangesHook<SutType>(componentInstance: SutType): componentInstance is SutType & OnChanges {
return (
'ngOnChanges' in componentInstance && typeof (componentInstance as SutType & OnChanges).ngOnChanges === 'function'
Expand Down Expand Up @@ -397,7 +411,7 @@ if (typeof process === 'undefined' || !process.env?.ATL_SKIP_AUTO_CLEANUP) {
}

@Component({ selector: 'atl-wrapper-component', template: '' })
class WrapperComponent {}
class WrapperComponent { }

/**
* Wrap findBy queries to poke the Angular change detection cycle
Expand Down
49 changes: 47 additions & 2 deletions projects/testing-library/tests/render.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { render, fireEvent, screen } from '../src/public_api';
<button>button</button>
`,
})
class FixtureComponent {}
class FixtureComponent { }

test('creates queries and events', async () => {
const view = await render(FixtureComponent);
Expand Down Expand Up @@ -48,6 +48,51 @@ describe('standalone', () => {
});
});

describe('standalone with child', () => {
@Component({
selector: 'child-fixture',
template: `<span>A child fixture</span>`,
standalone: true,
})
class ChildFixture { }

@Component({
selector: 'child-fixture',
template: `<span>A mock child fixture</span>`,
standalone: true,
})
class MockChildFixture { }

@Component({
selector: 'parent-fixture',
template: `<h1>Parent fixture</h1>
<div><child-fixture></child-fixture></div> `,
standalone: true,
imports: [ChildFixture],
})
class ParentFixture { }

it('renders the standalone component with child', async () => {
await render(ParentFixture);
expect(screen.getByText('Parent fixture'));
expect(screen.getByText('A child fixture'));
});

it('renders the standalone component with child', async () => {
await render(ParentFixture, { componentImports: [MockChildFixture] });
expect(screen.getByText('Parent fixture'));
expect(screen.getByText('A mock child fixture'));
});

it('rejects render of template with componentImports set', () => {
const result = render(`<div><parent-fixture></parent-fixture></div>`, {
imports: [ParentFixture],
componentImports: [MockChildFixture],
});
return expect(result).rejects.toMatchObject({ message: /Error while rendering/ });
});
});

describe('removeAngularAttributes', () => {
it('should remove angular attribute', async () => {
await render(FixtureComponent, {
Expand All @@ -72,7 +117,7 @@ describe('animationModule', () => {
@NgModule({
declarations: [FixtureComponent],
})
class FixtureModule {}
class FixtureModule { }
describe('excludeComponentDeclaration', () => {
it('does not throw if component is declared in an imported module', async () => {
await render(FixtureComponent, {
Expand Down

0 comments on commit 57c77e2

Please sign in to comment.