From 6908e5f7577b382ae5dfe8f3857b3f4f3e0b1959 Mon Sep 17 00:00:00 2001 From: MG Date: Mon, 15 Feb 2021 22:02:17 +0100 Subject: [PATCH 1/2] fix: correct replacement of useExisting providers --- libs/ng-mocks/src/lib/mock/clone-providers.ts | 2 +- tests/ng-mocks-render/use-existing.spec.ts | 72 +++++++++++++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 tests/ng-mocks-render/use-existing.spec.ts diff --git a/libs/ng-mocks/src/lib/mock/clone-providers.ts b/libs/ng-mocks/src/lib/mock/clone-providers.ts index 1fa60f568e..0534e7cecb 100644 --- a/libs/ng-mocks/src/lib/mock/clone-providers.ts +++ b/libs/ng-mocks/src/lib/mock/clone-providers.ts @@ -49,7 +49,7 @@ const processTokens = (mockType: AnyType, provider: any) => { const processOwnUseExisting = (sourceType: AnyType, mockType: AnyType, provider: any) => { const provide = funcGetProvider(provider); if (provider !== provide && provider.useExisting === sourceType) { - return toExistingProvider(provider, mockType); + return toExistingProvider(provide, mockType); } if ( provider !== provide && diff --git a/tests/ng-mocks-render/use-existing.spec.ts b/tests/ng-mocks-render/use-existing.spec.ts new file mode 100644 index 0000000000..369f275efe --- /dev/null +++ b/tests/ng-mocks-render/use-existing.spec.ts @@ -0,0 +1,72 @@ +import { CommonModule } from '@angular/common'; +import { + Component, + ContentChild, + Directive, + Input, + NgModule, + TemplateRef, +} from '@angular/core'; +import { MockBuilder, MockRender, ngMocks } from 'ng-mocks'; + +@Directive({ + selector: '[tpl]', +}) +class TplDirective { + @Input('tpl') public readonly name: string | null = null; + + public constructor(public readonly tpl: TemplateRef) {} +} + +@Directive({ + providers: [ + { + provide: TplDirective, + useExisting: MockDirective, + }, + ], + selector: '[mock]', +}) +class MockDirective { + public constructor(public readonly tpl: TemplateRef) {} +} + +@Component({ + selector: 'target', + template: ` + + rendered-mock + + `, +}) +class TargetComponent {} + +@Component({ + selector: 'component', + template: ``, +}) +class MockComponent { + @ContentChild(TplDirective, {} as any) + public readonly directive?: TplDirective; +} + +@NgModule({ + declarations: [ + TargetComponent, + MockComponent, + MockDirective, + TplDirective, + ], + imports: [CommonModule], +}) +class TargetModule {} + +describe('ng-mocks-render:use-existing', () => { + beforeEach(() => MockBuilder(TargetComponent, TargetModule)); + + it('substitutes in mocks correctly', () => { + MockRender(TargetComponent); + const component = ngMocks.findInstance(MockComponent); + expect(component.directive).toEqual(jasmine.any(MockDirective)); + }); +}); From 151ff34ee4ab7438e2a08982f643e4aca9bd22c0 Mon Sep 17 00:00:00 2001 From: MG Date: Sun, 14 Feb 2021 22:06:45 +0100 Subject: [PATCH 2/2] feat: improved TemplateRef render check ngMocks.render and ngMocks.hide closes #291 --- CONTRIBUTING.md | 2 +- docs/articles/api/MockComponent.md | 11 +- docs/articles/api/MockDirective.md | 9 +- docs/articles/api/helpers/isMockOf.md | 6 +- docs/articles/api/ngMocks.md | 14 +- docs/articles/api/ngMocks/hide.md | 48 +++ docs/articles/api/ngMocks/render.md | 223 +++++++++++ docs/articles/extra/templateref.md | 32 +- .../guides/libraries/angular-material.md | 99 ++--- docs/articles/guides/libraries/ng-select.md | 154 ++++---- docs/articles/guides/libraries/primeng.md | 52 ++- docs/sidebars.js | 13 +- examples/MockComponent/test.spec.ts | 14 +- .../MockDirective-Structural/test.spec.ts | 7 +- .../TestTemplateRefByDirective/test.spec.ts | 10 +- examples/TestTemplateRefById/test.spec.ts | 16 +- examples/TestTemplateRefByRender/test.spec.ts | 110 ++++++ .../src/lib/common/decorate.queries.ts | 48 ++- libs/ng-mocks/src/lib/common/func.is-mock.ts | 11 +- libs/ng-mocks/src/lib/common/mock.ts | 4 + .../lib/mock-component/mock-component.spec.ts | 104 +++-- .../src/lib/mock-component/mock-component.ts | 42 +- libs/ng-mocks/src/lib/mock-component/types.ts | 4 +- .../lib/mock-directive/mock-directive.spec.ts | 125 +++--- .../src/lib/mock-directive/mock-directive.ts | 4 +- libs/ng-mocks/src/lib/mock-directive/types.ts | 3 +- .../mock-helper/mock-helper.format-html.ts | 12 + .../src/lib/mock-helper/mock-helper.ts | 59 ++- .../lib/mock-helper/render/func.find-deep.ts | 74 ++++ .../mock-helper/render/func.parse-template.ts | 19 + .../mock-helper/render/mock-helper.hide.ts | 28 ++ .../mock-helper/render/mock-helper.render.ts | 25 ++ .../src/lib/mock/decorate-declaration.ts | 27 +- tests-angular/e2e/src/mat-table/test.spec.ts | 106 +++-- tests-angular/e2e/src/ng-select/test.spec.ts | 136 +++---- tests-angular/e2e/src/p-calendar/test.spec.ts | 48 +-- tests/context-with-directives/test.spec.ts | 46 ++- tests/module-with-factory-tokens/test.spec.ts | 12 +- tests/module-with-tokens/test.spec.ts | 22 +- tests/ng-mocks-render/component.spec.ts | 367 ++++++++++++++++++ tests/ng-mocks-render/directive.spec.ts | 159 ++++++++ tests/ng-mocks-render/idea.spec.ts | 114 ++++++ tests/structural-directives/test.spec.ts | 46 +-- 43 files changed, 1847 insertions(+), 618 deletions(-) create mode 100644 docs/articles/api/ngMocks/hide.md create mode 100644 docs/articles/api/ngMocks/render.md create mode 100644 examples/TestTemplateRefByRender/test.spec.ts create mode 100644 libs/ng-mocks/src/lib/mock-helper/mock-helper.format-html.ts create mode 100644 libs/ng-mocks/src/lib/mock-helper/render/func.find-deep.ts create mode 100644 libs/ng-mocks/src/lib/mock-helper/render/func.parse-template.ts create mode 100644 libs/ng-mocks/src/lib/mock-helper/render/mock-helper.hide.ts create mode 100644 libs/ng-mocks/src/lib/mock-helper/render/mock-helper.render.ts create mode 100644 tests/ng-mocks-render/component.spec.ts create mode 100644 tests/ng-mocks-render/directive.spec.ts create mode 100644 tests/ng-mocks-render/idea.spec.ts diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8c0a66ebb7..51b635d37d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -33,7 +33,7 @@ The best way would be to discuss an issue or an improvement first: ```shell export IE_BIN="/c/Program Files/Internet Explorer/iexplore.exe" cd /c/ && rm -Rf ng-mocks && mkdir ng-mocks && cd ng-mocks - find /z/ng-mocks -maxdepth 1 -not -name ng-mocks -not -name .git -not -name e2e -not -name node_modules -exec cp -r {} . \; + find /z/ng-mocks -maxdepth 1 -not -name ng-mocks -not -name .git -not -name docs -not -name e2e -not -name node_modules -exec cp -r {} . \; npm ci --no-optional --ignore-scripts npm run test ``` diff --git a/docs/articles/api/MockComponent.md b/docs/articles/api/MockComponent.md index 0bdf1a4837..16ddb88f6c 100644 --- a/docs/articles/api/MockComponent.md +++ b/docs/articles/api/MockComponent.md @@ -27,9 +27,7 @@ The class of a mock component has: - the same `selector` - the same `@Inputs` and `@Outputs` with alias support - templates with pure `` tags to allow transclusion -- support of `@ContentChild` with an `$implicit` context - - `__render('id', $implicit?, variables?)` - renders a template - - `__hide('id')` - hides a rendered template +- support of `@ContentChild` and `@ContentChildren` - support of `ControlValueAccessor`, `Validator` and `AsyncValidator` - support of `exportAs` @@ -191,14 +189,11 @@ describe('MockComponent', () => { // componentInstance is a MockedComponent to access // its `__render` method. `isMockOf` function helps here. const mockComponent = fixture.point.componentInstance; - if (isMockOf(mockComponent, DependencyComponent, 'c')) { - mockComponent.__render('something'); - fixture.detectChanges(); - } + ngMocks.render(mockComponent, ngMocks.findTemplateRef('something')); // The rendered template is wrapped by
. // We can use this selector to assert exactly its content. - const mockNgTemplate = ngMocks.find('[data-key="something"]') + const mockNgTemplate = ngMocks.find(DependencyComponent) .nativeElement.innerHTML; expect(mockNgTemplate).toContain('

inside template

'); }); diff --git a/docs/articles/api/MockDirective.md b/docs/articles/api/MockDirective.md index 149bc0069a..bdcf3e17a5 100644 --- a/docs/articles/api/MockDirective.md +++ b/docs/articles/api/MockDirective.md @@ -24,10 +24,10 @@ TestBed.configureTestingModule({ A mock directive has: +- support of attribute and structural directives - the same `selector` - the same `Inputs` and `Outputs` with alias support -- supports structural directives - - `__render($implicit?, variables?)` - renders content +- support of `@ContentChild` and `@ContentChildren` - support of `ControlValueAccessor`, `Validator` and `AsyncValidator` - supports `exportAs` @@ -194,10 +194,7 @@ describe('MockDirective:Structural', () => { // And let's render it manually now. const mockDirective = ngMocks.findInstance(DependencyDirective); - if (isMockOf(mockDirective, DependencyDirective, 'd')) { - mockDirective.__render(); - fixture.detectChanges(); - } + ngMocks.render(mockDirective, mockDirective); // The content of the structural directive should be rendered. expect(fixture.nativeElement.innerHTML).toContain('>content<'); diff --git a/docs/articles/api/helpers/isMockOf.md b/docs/articles/api/helpers/isMockOf.md index 74673513ff..8f9a6c2892 100644 --- a/docs/articles/api/helpers/isMockOf.md +++ b/docs/articles/api/helpers/isMockOf.md @@ -22,15 +22,13 @@ if (isMockOf(instance, SomeClass, 'm')) { // checks whether `instance` is // an instance of `MockedComponent` if (isMockOf(instance, SomeClass, 'c')) { - instance.__render('block', '$implicit'); - instance.__hide('block'); + // yes it is } // checks whether `instance` is // an instance of `MockedDirective` if (isMockOf(instance, SomeClass, 'd')) { - instance.__render('$implicit'); - instance.__hide(); + // yes it is } // checks whether `instance` is diff --git a/docs/articles/api/ngMocks.md b/docs/articles/api/ngMocks.md index a5762dcb68..ae55e24976 100644 --- a/docs/articles/api/ngMocks.md +++ b/docs/articles/api/ngMocks.md @@ -17,16 +17,23 @@ access desired elements and instances in fixtures. * [`globalReplace()`](ngMocks/globalReplace.md) * [`globalWipe()`](ngMocks/globalWipe.md) +## Manipulating `ng-template` + +- [`render()`](ngMocks/render.md) +- [`hide()`](ngMocks/hide.md) + ## Accessing elements and instances - [`input()`](ngMocks/input.md) - [`output()`](ngMocks/output.md) -* [`get()`](ngMocks/get.md) * [`find()`](ngMocks/find.md) * [`findAll()`](ngMocks/findAll.md) -* [`findInstance()`](ngMocks/findInstance.md) -* [`findInstances()`](ngMocks/findInstances.md) + +- [`get()`](ngMocks/get.md) +- [`findInstance()`](ngMocks/findInstance.md) +- [`findInstances()`](ngMocks/findInstances.md) + * [`findTemplateRef()`](ngMocks/findTemplateRef.md) * [`findTemplateRefs()`](ngMocks/findTemplateRefs.md) @@ -40,6 +47,7 @@ access desired elements and instances in fixtures. - [`guts()`](ngMocks/guts.md) - [`faster()`](ngMocks/faster.md) - [`throwOnConsole()`](ngMocks/throwOnConsole.md) +- `formatHtml()` - removes comments and sequences of spaces and new lines * [`flushTestBed()`](ngMocks/flushTestBed.md) * [`reset()`](ngMocks/reset.md) diff --git a/docs/articles/api/ngMocks/hide.md b/docs/articles/api/ngMocks/hide.md new file mode 100644 index 0000000000..85829bef16 --- /dev/null +++ b/docs/articles/api/ngMocks/hide.md @@ -0,0 +1,48 @@ +--- +title: ngMocks.hide +description: Documentation about ngMocks.hide from ng-mocks library +--- + +`ngMocks.hide` hides what [`ngMocks.render`](./render.md) has rendered. + +```ts +ngMocks.hide(declarationInst); +ngMocks.hide(declarationInst, templateRef); +ngMocks.hide(declarationInst, structuralDir); +``` + +- `declarationInst` should be an instance of a component or attribute directive +- `templateRef` should be a `TemplateRef` instance +- `structuralDir` should be an instance of a structural directive + +## No parameter + +If no parameter has been given, then **all rendered** templates and structural directives +which are reachable via queries will **be hidden**. + +```ts +ngMocks.hide(declarationInst); +``` + +## TemplateRef + +If the second parameter is `TemplateRef`, then **only the template** will be **hidden**. + +```ts +ngMocks.hide(componentInst, templateRef); +ngMocks.hide(directiveInst, templateRef); +``` + +## Structural directive + +If the second parameter is an instance of **structural directive**, then only its template will be **hidden**. + +```ts +ngMocks.hide(componentInst, structuralDir); +``` + +To hide a structural directive **itself**, simply pass it as the second parameter. + +```ts +ngMocks.hide(structuralDir, structuralDir); +``` diff --git a/docs/articles/api/ngMocks/render.md b/docs/articles/api/ngMocks/render.md new file mode 100644 index 0000000000..b8e51a4460 --- /dev/null +++ b/docs/articles/api/ngMocks/render.md @@ -0,0 +1,223 @@ +--- +title: ngMocks.render +description: Documentation about ngMocks.render from ng-mocks library +--- + +`ngMocks.render` goes through **all queries**, such as `ContentChild` and `ContentChildren`, +tries to find related `TemplateRef` or a **structural directive**, and render it with a given context. + +In order to hide them, use [`ngMocks.hide`](./hide.md). + +```ts +ngMocks.render(declarationInst, templateRef); +ngMocks.render(declarationInst, templateRef, context); +ngMocks.render(declarationInst, templateRef, context, variables); +``` + +```ts +ngMocks.render(declarationInst, structuralDir); +ngMocks.render(declarationInst, structuralDir, context); +ngMocks.render(declarationInst, structuralDir, context, variables); +``` + +- `declarationInst` should be an instance of a component or attribute directive +- `templateRef` should be a `TemplateRef` instance +- `structuralDir` should be an instance of a structural directive +- `context` an optional context variable +- `variables` additional context variables + +The **first** and the **second** parameter **cannot be empty**. + +- [Try it on StackBlitz](https://stackblitz.com/github/ng-mocks/examples?file=src/examples/TestTemplateRefByRender/test.spec.ts&initialpath=%3Fspec%3DTestTemplateRefByRender) +- [Try it on CodeSandbox](https://codesandbox.io/s/github/ng-mocks/examples?file=/src/examples/TestTemplateRefByRender/test.spec.ts&initialpath=%3Fspec%3DTestTemplateRefByRender) + +## Render TemplateRef / ng-template + +To render a `TemplateRef` / `ng-template` we need to have 2 things: + +- a variable which points to a component / directive with the query +- a variable which points to the template + +The first task can be solved by [`ngMocks.find`](./find.md) or [`ngMocks.findInstance`](./findInstance.md), +the second task can be solved by [`ngMocks.findTemplateRef`](./findTemplateRef.md). + +Let's assume, that we have the next template: + +```html + + + rendered-id-{{ label }} + + + + rendered-header-{{ label }} + + + + rendered-footer-{{ label }} + + +``` + +### Component pointer + +Let's find `xd-card` component: + +```ts +const xdCardEl = ngMocks.find('xd-card'); +``` + +Or we can use its class: + +```ts +const xdCardEl = ngMocks.find(XdCardComponent); +``` + + +**Please note**, it can be not only an instance of a component, +but an instance of **attribute directive** too. + +### Template pointer + +Now, let's find the 3 templates: + +```ts +const tplId = ngMocks.findTemplateRef( + xdCardEl, + // id of the template + 'id', +); + +const tplHeader = ngMocks.findTemplateRef( + xdCardEl, + // attr and value on the template + ['myTpl', 'header'], +); + +const tplFooter = ngMocks.findTemplateRef( + xdCardEl, + // attr and value on the template + ['myTpl', 'footer'], +); +``` + +Please note, that we cannot find `tplFooter` by `my-tpl`, +because `*myTpl` is syntactic sugar and `my-tpl` belongs **not** to the desired template, but to its nested `span`. + +### Rendering id + +To render `TemplateRef` of a **mock component**, we need to pass it as the **second parameter** of `ngMocks.render`. +The third and the fourth parameters are used to **provide context** for the template. + +```ts +ngMocks.render( + xdCardEl.componentInstance, + tplId, + undefined, + {label: 'test'}, +); +``` + +Now we can assert the rendered html: + +```ts +expect(xdCardEl.nativeElement.innerHTML) + .toContain('rendered-id-test'); +``` + +### Rendering header + +The process is the same as above: + +```ts +ngMocks.render( + xdCardEl.componentInstance, + tplHeader, + 'test', +); +expect(xdCardEl.nativeElement.innerHTML) + .toContain('rendered-header-test'); +``` + +### Rendering footer + +The process is the same as above: + +```ts +ngMocks.render( + xdCardEl.componentInstance, + tplFooter, + 'test', +); +expect(xdCardEl.nativeElement.innerHTML) + .toContain(' rendered-footer-test '); +``` + +## Render structural directives + +`ngMocks.render` renders not only `TemplateRef`, but also structural directives. + +Let's find all instances of `MyTplDirective`. + +```ts +const [header, footer] = ngMocks.findInstances( + xdCardEl, + MyTplDirective, +); +``` + +Because they are structural directives, we have 2 options: + +- to render it from the component +- to render it directly + +The **difference** is that the first option also ensures that the component +has **linked queries** to reach the directive. + +### Via queries + +To verify queries, we need to pass the component as the first parameter, +and the **desired structural directive** as the second one. + +```ts +ngMocks.render(xdCardEl.componentInstance, header, 'test'); +expect(xdCardEl.nativeElement.innerHTML) + .toContain('rendered-header-test'); +``` + +### Directly + +To render a **structural directive directly** we need to pass its instance +as the first and as the **second parameter** of `ngMocks.render`. + +```ts +ngMocks.render(footer, footer, 'test'); +expect(xdCardEl.nativeElement.innerHTML) + .toContain('rendered-footer-test'); +``` + +## Deeply nested templates + +It is possible to render any `TemplateRef` or structural directive on **any depth**, +the only requirement is to have **enough queries** to reach it from the desired instance. + +Let's consider the next template: + +```html + + + + (i) + + + +``` + +Then, if `xdCard` points to its component instance and `icon` points to +the nested `ng-template`, we can render it like that: + +```ts +ngMocks.render(xdCard, icon); +``` + +A useful thing here is that the render will fail if a query is removed between elements. diff --git a/docs/articles/extra/templateref.md b/docs/articles/extra/templateref.md index 2861fc46a1..b0363526ee 100644 --- a/docs/articles/extra/templateref.md +++ b/docs/articles/extra/templateref.md @@ -4,6 +4,15 @@ description: Information how to render ng-templates and structural directives wh sidebar_label: Testing TemplateRef --- +:::warning This functionality has been **deprecated** + +Please use: + +- [ngMocks.render](../api/ngMocks/render.md) +- [ngMocks.hide](../api/ngMocks/hide.md) + +::: + Templates are often used in UI component libraries such as **Angular Material**, **NG Bootstrap**, **PrimeNG**, **ng-select** etc. They bring **flexibility via templates** when we want @@ -129,17 +138,13 @@ const [header, footer] = ngMocks.findInstances( // asserting header expect(header.xdTpl).toEqual('header'); -if (isMockOf(header, XdTplDirective, 'd')) { - header.__render(); -} +ngMocks.render(header, header); expect(container.nativeElement.innerHTML) .toContain('My Header'); // asserting footer expect(footer.xdTpl).toEqual('footer'); -if (isMockOf(footer, XdTplDirective, 'd')) { - footer.__render(); -} +ngMocks.render(footer, footer); expect(container.nativeElement.innerHTML) .toContain('My Footer'); ``` @@ -166,9 +171,7 @@ For example, we know that `XdCardComponent` uses `ContentChildren` on `tpls` pro Then we can use a special interface of `__render` method for properties. To do so, we need to a tuple as the first parameter. ```ts -if (isMockOf(component, XdCardComponent, 'c')) { - component.__render(['tpls']); -} +ngMocks.render(component, ngMocks.findTemplateRef('header')); ``` This call will render the whole `QueryList` if it is `ContentChildren` or `TemplateRef` if it is `ContentChild`. @@ -176,18 +179,13 @@ This call will render the whole `QueryList` if it is `ContentChildren` or `Templ If we want to render only particular element, then we can pass its index in the tuple. ```ts -if (isMockOf(component, XdCardComponent, 'c')) { - component.__render(['tpls', 1]); // footer -} +ngMocks.render(component, ngMocks.findTemplateRef('footer')); ``` -The sample approach works for `__hide`. +The sample approach works for `hide`. ```ts -if (isMockOf(component, XdCardComponent, 'c')) { - component.__hide(['tpls', 1]); // footer - component.__hide(['tpls']); // all -} +ngMocks.hide(component); ``` The templates will be rendered under a special element with `data-prop` attribute. diff --git a/docs/articles/guides/libraries/angular-material.md b/docs/articles/guides/libraries/angular-material.md index a3fbe38b67..fa699c7185 100644 --- a/docs/articles/guides/libraries/angular-material.md +++ b/docs/articles/guides/libraries/angular-material.md @@ -35,7 +35,7 @@ A test of such a template requires to: - assert the rest of templates :::note -Information about testing `ng-template` and its `TemplateRef` is taken from the [guide about testing TemplateRef](../../extra/templateref.md). +Information about testing `ng-template` and its `TemplateRef` is taken from the [ngMocks.render](../../api/ngMocks/render.md). ::: ## Spec file @@ -77,52 +77,39 @@ it('binds inputs', () => { ## Testing matColumnDef and matCellDef templates -To test the `ng-template`, we should find which directives belong to `matColumnDef` and `matCellDef` attributes. -They are `MatHeaderCellDef` and `MatCellDef`. - -The test repeats steps for [Templates by directive](../../extra/templateref.md#templates-by-directive). +To test the `ng-template`, +we should find `TemplateRef` which belongs to `matColumnDef` and `matCellDef` attributes, +render them, and assert the rendered html. The tools from `ng-mocks` we need: - [`MockRender`](../../api/MockRender.md): to render `TargetComponent` and get its instance -- [`ngMocks.find`](../../api/ngMocks/find.md): to find a debug element of `p-calendar` -- [`ngMocks.findInstances`](../../api/ngMocks/findInstances.md): to find the instance of `PrimeTemplate` -- [`isMockOf`](../../api/helpers/isMockOf.md): to verify that `PrimeTemplate` has been mocked to render it +- [`ngMocks.find`](../../api/ngMocks/find.md): to find a debug element of `mat-table` +- [`ngMocks.findTemplateRefs`](../../api/ngMocks/findTemplateRefs.md): to find a templates which belong `matColumnDef` and `matCellDef` +- [`ngMocks.render`](../../api/ngMocks/render.md): to render the templates ```ts it('provides correct template for matColumnDef="position"', () => { - // Rendering TargetComponent. MockRender(TargetComponent); + const tableEl = ngMocks.find('[mat-table]'); - // Looking for a debug element of `MatTable`. - const tableEl = ngMocks.find(MatTable); - - // Looking for the instance of MatHeaderCellDef. - // The first one belongs to 'position'. - const [header] = ngMocks.findInstances(tableEl, MatHeaderCellDef); - - // Verifying that the directive has been mocked. - // And rendering it. - if (isMockOf(header, MatHeaderCellDef, 'd')) { - header.__render(); - } - - // Asserting the rendered template. + expect(tableEl.nativeElement.innerHTML).not.toContain( + 'No.', + ); + const [header] = ngMocks.findTemplateRefs( + tableEl, + ['matHeaderCellDef'], + ); + ngMocks.render(tableEl.componentInstance, header); expect(tableEl.nativeElement.innerHTML).toContain( 'No.', ); - // Looking for the instance of MatCellDef. - // The first one belongs to 'position'. - const [cell] = ngMocks.findInstances(tableEl, MatCellDef); - - // Verifying that the directive has been mocked. - // And rendering it. - if (isMockOf(cell, MatCellDef, 'd')) { - cell.__render({ position: 'testPosition' }); - } - - // Asserting the rendered template. + expect(tableEl.nativeElement.innerHTML).not.toContain( + ' testPosition ', + ); + const [cell] = ngMocks.findTemplateRefs(tableEl, ['matCellDef']); + ngMocks.render(tableEl.componentInstance, cell, { position: 'testPosition' }); expect(tableEl.nativeElement.innerHTML).toContain( ' testPosition ', ); @@ -136,28 +123,22 @@ The approach to test `mat-header-row` is the same as above. We need to find which directive belongs to `mat-header-row`, it is `MatHeaderRowDef`. +The tools from `ng-mocks` we need: + +- [`ngMocks.findInstance`](../../api/ngMocks/findInstance.md): to find the instance of `MatHeaderRowDef` + ```ts it('provides correct template for mat-header-row', () => { - // Rendering TargetComponent and accessing its instance. const targetComponent = MockRender(TargetComponent).point .componentInstance; + const tableEl = ngMocks.find('[mat-table]'); - // Looking for a debug element of `MatTable`. - const tableEl = ngMocks.find(MatTable); - - // Looking for the instance of `MatHeaderRowDef`. const header = ngMocks.findInstance(tableEl, MatHeaderRowDef); - - // Asserting its inputs. expect(header.columns).toBe(targetComponent.displayedColumns); - - // Verifying that the instance has been mocked. - // And rendering it. - if (isMockOf(header, MatHeaderRowDef, 'd')) { - header.__render(); - } - - // Asserting the rendered html. + expect(tableEl.nativeElement.innerHTML).not.toContain( + '', + ); + ngMocks.render(tableEl.componentInstance, header); expect(tableEl.nativeElement.innerHTML).toContain( '', ); @@ -173,26 +154,16 @@ it is `MatRowDef`. ```ts it('provides correct template for mat-row', () => { - // Rendering TargetComponent and accessing its instance. const targetComponent = MockRender(TargetComponent).point .componentInstance; + const tableEl = ngMocks.find('[mat-table]'); - // Looking for a debug element of `MatTable`. - const tableEl = ngMocks.find(MatTable); - - // Looking for the instance of `MatRowDef`. const row = ngMocks.findInstance(tableEl, MatRowDef); - - // Asserting its inputs. expect(row.columns).toBe(targetComponent.displayedColumns); - - // Verifying that the instance has been mocked. - // And rendering it. - if (isMockOf(row, MatRowDef, 'd')) { - row.__render(); - } - - // Asserting the rendered html. + expect(tableEl.nativeElement.innerHTML).not.toContain( + '', + ); + ngMocks.render(tableEl.componentInstance, row); expect(tableEl.nativeElement.innerHTML).toContain( '', ); diff --git a/docs/articles/guides/libraries/ng-select.md b/docs/articles/guides/libraries/ng-select.md index 460d1c2678..2c15b032cf 100644 --- a/docs/articles/guides/libraries/ng-select.md +++ b/docs/articles/guides/libraries/ng-select.md @@ -50,7 +50,7 @@ Therefore, to test it we need to: - assert templates :::note -Information about testing `ng-template` and its `TemplateRef` is taken from the [section about testing TemplateRef](../../extra/templateref.md). +Information about testing `ng-template` and its `TemplateRef` is taken from the [ngMocks.render](../../api/ngMocks/render.md). ::: ## Spec file @@ -91,8 +91,8 @@ it('binds inputs', () => { const targetComponent = MockRender(TargetComponent).point .componentInstance; - // Looking for a debug element of `NgSelectComponent`. - const ngSelectEl = ngMocks.find(NgSelectComponent); + // Looking for a debug element of the ng-select. + const ngSelectEl = ngMocks.find('ng-select'); // Asserting bound properties. expect(ngMocks.input(ngSelectEl, 'items')).toBe( @@ -129,8 +129,8 @@ it('binds outputs', () => { const targetComponent = MockRender(TargetComponent).point .componentInstance; - // Looking for a debug element of `NgSelectComponent`. - const ngSelectEl = ngMocks.find(NgSelectComponent); + // Looking for a debug element of the ng-select. + const ngSelectEl = ngMocks.find('ng-select'); // Simulating an emit. ngMocks.output(ngSelectEl, 'ngModelChange').emit('test'); @@ -142,52 +142,45 @@ it('binds outputs', () => { ## Testing ng-label-tmp template -To test a template of `ng-select` is not as easy as it could be. -The problem is that the library does not export directives -which are used to define templates. - -To solve this, `ng-mocks` provides an interface to render a property with `TemplateRef`. -It requires a short investigation -in order to find which property is used by `ng-select` -to store `ng-label-tmp`, the short answer is `labelTemplate`. - -Now, when we know the property, we can render it via `__render`, -but instead of calling `__render('labelTemplate')`, -we should pass `labelTemplate` as a tuple: `__render(['labelTemplate'])`. -Then `ng-mocks` knows that instead of looking for an id it should use the property. +To test a template of `ng-select`, +we need to find a debug element of `ng-select`, +then to find a template which belongs to `ng-label-tmp`, +and to render it with a proper context. To write a test, we need to use: - [`MockRender`](../../api/MockRender.md): to render `TargetComponent` and get its instance -- [`ngMocks.findInstance`](../../api/ngMocks/findInstance.md): to find the instance of `NgSelectComponent` -- [`isMockOf`](../../api/helpers/isMockOf.md): to verify that `NgSelectComponent` has been mocked to render it -- [`ngMocks.find`](../../api/ngMocks/find.md): to find a debug element of the rendered template +- [`ngMocks.find`](../../api/ngMocks/find.md): to find a debug element which belongs to `NgSelectComponent` +- [`ngMocks.findTemplateRef`](../../api/ngMocks/findTemplateRef.md): to find the template which belongs to `ng-label-tmp` +- [`ngMocks.render`](../../api/ngMocks/render.md): to render the template ```ts it('provides correct template for ng-label-tmp', () => { // Rendering TargetComponent. MockRender(TargetComponent); - // Looking for the instance of `NgSelectComponent`. - const ngSelect = ngMocks.findInstance(NgSelectComponent); + // Looking for a debug element of the ng-select. + const ngSelectEl = ngMocks.find('ng-select'); - // Verifying that the instance has been mocked. - // And rendering its property, - // which points to the desired TemplateRef. - if (isMockOf(ngSelect, NgSelectComponent, 'c')) { - ngSelect.__render( - ['labelTemplate'], - {}, - // Providing context variables. - { item: { name: 'test' } }, - ); - } - - // Looking for a debug element of the rendered TemplateRef. - const tplEl = ngMocks.find('[data-prop="labelTemplate"]'); + // Looking for the ng-label-tmp template + const ngLabelTmp = ngMocks.findTemplateRef( + ngSelectEl, + // attr name + ['ng-label-tmp'], + ); + + // Verifies that ngSelect can access ngLabelTmp, + // and renders it. + ngMocks.render( + ngSelectEl.componentInstance, + ngLabelTmp, + {}, + // Providing context variables. + { item: { name: 'test' }}, + ); // Asserting the rendered html. - expect(tplEl.nativeElement.innerHTML).toContain( + expect(ngSelectEl.nativeElement.innerHTML).toContain( 'test', ); }); @@ -202,32 +195,34 @@ it('provides correct template for ng-optgroup-tmp', () => { // Rendering TargetComponent and accessing its instance. MockRender(TargetComponent); - // Looking for the instance of `NgSelectComponent`. - const ngSelect = ngMocks.findInstance(NgSelectComponent); + // Looking for a debug element of the ng-select. + const ngSelectEl = ngMocks.find('ng-select'); - // Verifying that the instance has been mocked. - // And rendering its property, - // which points to the desired TemplateRef. - if (isMockOf(ngSelect, NgSelectComponent, 'c')) { - ngSelect.__render( - ['optgroupTemplate'], - {}, - // Providing context variables. - { - index: 7, - item: { - avatar: 'test.jpeg', - name: 'test', - }, - }, - ); - } + // Looking for the ng-optgroup-tmp template + const ngOptgroupTmp = ngMocks.findTemplateRef( + ngSelectEl, + // attr name + ['ng-optgroup-tmp'], + ); - // Looking for a debug element of the rendered TemplateRef. - const tplEl = ngMocks.find('[data-prop="optgroupTemplate"]'); + // Verifies that ngSelect can access ngOptgroupTmp, + // and renders it. + ngMocks.render( + ngSelectEl.componentInstance, + ngOptgroupTmp, + {}, + // Providing context variables. + { + index: 7, + item: { + avatar: 'test.jpeg', + name: 'test', + }, + }, + ); // Asserting the rendered html. - expect(tplEl.nativeElement.innerHTML).toContain( + expect(ngSelectEl.nativeElement.innerHTML).toContain( '7 test', ); }); @@ -242,31 +237,34 @@ it('provides correct template for ng-option-tmp', () => { // Rendering TargetComponent and accessing its instance. MockRender(TargetComponent); - // Looking for the instance of `NgSelectComponent`. - const ngSelect = ngMocks.findInstance(NgSelectComponent); + // Looking for a debug element of the ng-select. + const ngSelectEl = ngMocks.find('ng-select'); + + // Looking for the ng-option-tmp template + const ngOptionTmp = ngMocks.findTemplateRef( + ngSelectEl, + // attr name + ['ng-option-tmp'], + ); // Verifying that the instance has been mocked. // And rendering its property, // which points to the desired TemplateRef. - if (isMockOf(ngSelect, NgSelectComponent, 'c')) { - ngSelect.__render( - ['optionTemplate'], - {}, - // Providing context variables. - { - item: { - name: 'test', - }, - searchTerm: 'search', + ngMocks.render( + ngSelectEl.componentInstance, + ngOptionTmp, + {}, + // Providing context variables. + { + item: { + name: 'test', }, - ); - } - - // Looking for a debug element of the rendered TemplateRef. - const labelEl = ngMocks.find('[data-prop="optionTemplate"]'); + searchTerm: 'search', + }, + ); // Asserting the rendered html. - expect(labelEl.nativeElement.innerHTML).toContain( + expect(ngSelectEl.nativeElement.innerHTML).toContain( 'search test', ); }); diff --git a/docs/articles/guides/libraries/primeng.md b/docs/articles/guides/libraries/primeng.md index feeab80cbd..638ea28842 100644 --- a/docs/articles/guides/libraries/primeng.md +++ b/docs/articles/guides/libraries/primeng.md @@ -27,7 +27,7 @@ To test it, we need to: - assert templates :::note -Information about testing `ng-template` and its `TemplateRef` is taken from the [section about testing TemplateRef](../../extra/templateref.md). +Information about testing `ng-template` and its `TemplateRef` is taken from the [ngMocks.render](../../api/ngMocks/render.md). ::: ## Spec file @@ -92,17 +92,16 @@ it('binds outputs', () => { ## Testing pTemplate="header" template -To test the `ng-template`, we should find which directive belongs to `pTemplate` attribute. -It is `PrimeTemplate`. - -The test repeats steps for [Templates by directive](../../extra/templateref.md#templates-by-directive). +To test the `ng-template`, +we should find `TemplateRef` which belongs to `pTemplate` attribute with the provided value, +render it, and assert the rendered html. The tools from `ng-mocks` we need: - [`MockRender`](../../api/MockRender.md): to render `TargetComponent` and get its instance - [`ngMocks.find`](../../api/ngMocks/find.md): to find a debug element of `p-calendar` -- [`ngMocks.findInstances`](../../api/ngMocks/findInstances.md): to find the instance of `PrimeTemplate` -- [`isMockOf`](../../api/helpers/isMockOf.md): to verify that `PrimeTemplate` has been mocked to render it +- [`ngMocks.findTemplateRef`](../../api/ngMocks/findTemplateRef.md): to find a template which belongs `pTemplate` +- [`ngMocks.render`](../../api/ngMocks/render.md): to render the template ```ts it('provides correct template for pTemplate="header"', () => { @@ -112,18 +111,16 @@ it('provides correct template for pTemplate="header"', () => { // Looking for a debug element of `p-calendar`. const calendarEl = ngMocks.find('p-calendar'); - // Looking for the instance of PrimeTemplate. - // 'header' is the first one. - const [header] = ngMocks.findInstances(calendarEl, PrimeTemplate); - - // Asserting that it is the header. - expect(header.name).toEqual('header'); + // Looking for the template of 'header'. + const header = ngMocks.findTemplateRef( + calendarEl, + // attr name and its value + ['pTemplate', 'header'], + ); - // Verifying that the directive has been mocked. - // And rendering it. - if (isMockOf(header, PrimeTemplate, 'd')) { - header.__render(); - } + // Verifies that the directive has been mocked. + // And renders it. + ngMocks.render(calendarEl.componentInstance, header); // Asserting the rendered template. expect(calendarEl.nativeElement.innerHTML).toContain('Header'); @@ -142,21 +139,16 @@ it('provides correct template for pTemplate="footer"', () => { // Looking for a debug element of `p-calendar`. const calendarEl = ngMocks.find('p-calendar'); - // Looking for the instance of PrimeTemplate. - // 'footer' is the second one. - const [, footer] = ngMocks.findInstances( + // Looking for the template of 'footer'. + const footer = ngMocks.findTemplateRef( calendarEl, - PrimeTemplate, + // attr name and its value + ['pTemplate', 'footer'], ); - // Asserting that it is the footer. - expect(footer.name).toEqual('footer'); - - // Verifying that the directive has been mocked. - // And rendering it. - if (isMockOf(footer, PrimeTemplate, 'd')) { - footer.__render(); - } + // Verifies that the directive has been mocked. + // And renders it. + ngMocks.render(calendarEl.componentInstance, footer); // Asserting the rendered template. expect(calendarEl.nativeElement.innerHTML).toContain('Footer'); diff --git a/docs/sidebars.js b/docs/sidebars.js index a812b39b24..3ba821db2a 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -16,6 +16,7 @@ module.exports = { 'api/MockProvider', 'api/MockService', 'api/MockModule', + 'extra/templateref', ], }, { @@ -35,11 +36,13 @@ module.exports = { 'api/ngMocks/globalMock', 'api/ngMocks/globalReplace', 'api/ngMocks/globalWipe', + 'api/ngMocks/render', + 'api/ngMocks/hide', 'api/ngMocks/input', 'api/ngMocks/output', - 'api/ngMocks/get', 'api/ngMocks/find', 'api/ngMocks/findAll', + 'api/ngMocks/get', 'api/ngMocks/findInstance', 'api/ngMocks/findInstances', 'api/ngMocks/findTemplateRef', @@ -74,13 +77,7 @@ module.exports = { { type: 'category', label: 'Extra', - items: [ - 'extra/customize-mocks', - 'extra/mock-observables', - 'extra/mock-form-controls', - 'extra/templateref', - 'extra/with-3rd-party', - ], + items: ['extra/customize-mocks', 'extra/mock-observables', 'extra/mock-form-controls', 'extra/with-3rd-party'], }, { type: 'category', diff --git a/examples/MockComponent/test.spec.ts b/examples/MockComponent/test.spec.ts index d7bbdfe69d..22c28ce0e9 100644 --- a/examples/MockComponent/test.spec.ts +++ b/examples/MockComponent/test.spec.ts @@ -6,7 +6,7 @@ import { Output, TemplateRef, } from '@angular/core'; -import { isMockOf, MockBuilder, MockRender, ngMocks } from 'ng-mocks'; +import { MockBuilder, MockRender, ngMocks } from 'ng-mocks'; @Component({ selector: 'app-child', @@ -58,7 +58,7 @@ describe('MockComponent', () => { // Let's pretend that DependencyComponent has 'someInput' as // an input. TestedComponent sets its value via // `[someInput]="value"`. The input's value will be passed into - // the mock component so you can assert on it. + // the mock component so we can assert on it. component.value = 'foo'; fixture.detectChanges(); @@ -123,14 +123,14 @@ describe('MockComponent', () => { // componentInstance is a MockedComponent to access // its `__render` method. `isMockOf` function helps here. const mockComponent = fixture.point.componentInstance; - if (isMockOf(mockComponent, DependencyComponent, 'c')) { - mockComponent.__render('something'); - fixture.detectChanges(); - } + ngMocks.render( + mockComponent, + ngMocks.findTemplateRef('something'), + ); // The rendered template is wrapped by
. // We can use this selector to assert exactly its content. - const mockNgTemplate = ngMocks.find('[data-key="something"]') + const mockNgTemplate = ngMocks.find(DependencyComponent) .nativeElement.innerHTML; expect(mockNgTemplate).toContain('

inside template

'); }); diff --git a/examples/MockDirective-Structural/test.spec.ts b/examples/MockDirective-Structural/test.spec.ts index 1872f7810e..39812069d0 100644 --- a/examples/MockDirective-Structural/test.spec.ts +++ b/examples/MockDirective-Structural/test.spec.ts @@ -5,7 +5,7 @@ import { Input, Output, } from '@angular/core'; -import { isMockOf, MockBuilder, MockRender, ngMocks } from 'ng-mocks'; +import { MockBuilder, MockRender, ngMocks } from 'ng-mocks'; @Directive({ selector: '[dependency]', @@ -53,10 +53,7 @@ describe('MockDirective:Structural', () => { // And let's render it manually now. const mockDirective = ngMocks.findInstance(DependencyDirective); - if (isMockOf(mockDirective, DependencyDirective, 'd')) { - mockDirective.__render(); - fixture.detectChanges(); - } + ngMocks.render(mockDirective, mockDirective); // The content of the structural directive should be rendered. expect(fixture.nativeElement.innerHTML).toContain('>content<'); diff --git a/examples/TestTemplateRefByDirective/test.spec.ts b/examples/TestTemplateRefByDirective/test.spec.ts index 8a2d902ac3..dc39013ae3 100644 --- a/examples/TestTemplateRefByDirective/test.spec.ts +++ b/examples/TestTemplateRefByDirective/test.spec.ts @@ -8,7 +8,7 @@ import { QueryList, TemplateRef, } from '@angular/core'; -import { isMockOf, MockBuilder, MockRender, ngMocks } from 'ng-mocks'; +import { MockBuilder, MockRender, ngMocks } from 'ng-mocks'; @Directive({ selector: '[xdTpl]', @@ -64,16 +64,12 @@ describe('TestTemplateRefByDirective', () => { // asserting header expect(header.xdTpl).toEqual('header'); - if (isMockOf(header, XdTplDirective, 'd')) { - header.__render(); - } + ngMocks.render(header, header); expect(container.nativeElement.innerHTML).toContain('My Header'); // asserting footer expect(footer.xdTpl).toEqual('footer'); - if (isMockOf(footer, XdTplDirective, 'd')) { - footer.__render(); - } + ngMocks.render(footer, footer); expect(container.nativeElement.innerHTML).toContain('My Footer'); }); }); diff --git a/examples/TestTemplateRefById/test.spec.ts b/examples/TestTemplateRefById/test.spec.ts index 03690cc9eb..b1d2d9f909 100644 --- a/examples/TestTemplateRefById/test.spec.ts +++ b/examples/TestTemplateRefById/test.spec.ts @@ -5,7 +5,7 @@ import { NgModule, TemplateRef, } from '@angular/core'; -import { isMockOf, MockBuilder, MockRender, ngMocks } from 'ng-mocks'; +import { MockBuilder, MockRender, ngMocks } from 'ng-mocks'; @Component({ selector: 'xd-card', @@ -46,17 +46,15 @@ describe('TestTemplateRefById', () => { const component = ngMocks.findInstance(XdCardComponent); // checking that the component is a mock - if (isMockOf(component, XdCardComponent, 'c')) { - component.__render('header'); - component.__render('footer'); - } + ngMocks.render(component, ngMocks.findTemplateRef('header')); + ngMocks.render(component, ngMocks.findTemplateRef('footer')); + + const container = ngMocks.find(XdCardComponent); // asserting header - const header = ngMocks.find('[data-key="header"]'); - expect(header.nativeElement.innerHTML).toContain('My Header'); + expect(container.nativeElement.innerHTML).toContain('My Header'); // asserting footer - const footer = ngMocks.find('[data-key="footer"]'); - expect(footer.nativeElement.innerHTML).toContain('My Footer'); + expect(container.nativeElement.innerHTML).toContain('My Footer'); }); }); diff --git a/examples/TestTemplateRefByRender/test.spec.ts b/examples/TestTemplateRefByRender/test.spec.ts new file mode 100644 index 0000000000..e47dae2634 --- /dev/null +++ b/examples/TestTemplateRefByRender/test.spec.ts @@ -0,0 +1,110 @@ +import { CommonModule } from '@angular/common'; +import { + Component, + ContentChild, + ContentChildren, + Directive, + Input, + NgModule, + QueryList, + TemplateRef, +} from '@angular/core'; +import { MockBuilder, MockRender, ngMocks } from 'ng-mocks'; + +@Directive({ + selector: '[myTpl]', +}) +class MyTplDirective { + @Input('myTpl') public readonly name: string | null = null; + + public constructor(public readonly tpl: TemplateRef) {} +} + +@Component({ + selector: 'xd-card', + template: '', +}) +class XdCardComponent { + @ContentChild('id', {} as any) + public readonly id?: TemplateRef; + + @ContentChildren(MyTplDirective, {} as any) + public readonly templates?: QueryList; +} + +@NgModule({ + declarations: [MyTplDirective, XdCardComponent], + imports: [CommonModule], +}) +class TargetModule {} + +describe('TestTemplateRefByRender', () => { + beforeEach(() => MockBuilder(null, TargetModule)); + + beforeEach(() => + MockRender(` + + + rendered-id-{{ label }} + + + + rendered-header-{{ label }} + + + + rendered-footer-{{ label }} + + + `), + ); + + it('renders templates', () => { + const xdCardEl = ngMocks.find('xd-card'); + + const tplId = ngMocks.findTemplateRef(xdCardEl, 'id'); + const tplHeader = ngMocks.findTemplateRef(xdCardEl, [ + 'myTpl', + 'header', + ]); + const tplFooter = ngMocks.findTemplateRef(xdCardEl, [ + 'myTpl', + 'footer', + ]); + + ngMocks.render(xdCardEl.componentInstance, tplId, undefined, { + label: 'test', + }); + expect(xdCardEl.nativeElement.innerHTML).toContain( + 'rendered-id-test', + ); + + ngMocks.render(xdCardEl.componentInstance, tplHeader, 'test'); + expect(xdCardEl.nativeElement.innerHTML).toContain( + 'rendered-header-test', + ); + + ngMocks.render(xdCardEl.componentInstance, tplFooter, 'test'); + expect(ngMocks.formatHtml(xdCardEl)).toContain( + ' rendered-footer-test ', + ); + }); + + it('renders structural directives', () => { + const xdCardEl = ngMocks.find('xd-card'); + const [header, footer] = ngMocks.findInstances( + xdCardEl, + MyTplDirective, + ); + + ngMocks.render(xdCardEl.componentInstance, header, 'test'); + expect(xdCardEl.nativeElement.innerHTML).toContain( + 'rendered-header-test', + ); + + ngMocks.render(footer, footer, 'test'); + expect(xdCardEl.nativeElement.innerHTML).toContain( + 'rendered-footer-test', + ); + }); +}); diff --git a/libs/ng-mocks/src/lib/common/decorate.queries.ts b/libs/ng-mocks/src/lib/common/decorate.queries.ts index cde3a24340..2ec0c3813d 100644 --- a/libs/ng-mocks/src/lib/common/decorate.queries.ts +++ b/libs/ng-mocks/src/lib/common/decorate.queries.ts @@ -1,4 +1,4 @@ -import { ContentChild, ContentChildren, Query, ViewChild, ViewChildren } from '@angular/core'; +import { ContentChild, ContentChildren, Query, ViewChild, ViewChildren, ViewContainerRef } from '@angular/core'; import { AnyType } from './core.types'; @@ -9,18 +9,54 @@ const map: any = { ViewChildren, }; +const isInternalKey = (key: string): boolean => { + return key.indexOf('__mock') === 0; +}; + +const cloneVcrQuery = (query: Query & { ngMetadataName?: string }) => ({ + ...query, + ngMetadataName: query.ngMetadataName, + read: ViewContainerRef, +}); + +const generateFinalQueries = (queries: { + [key: string]: Query; +}): [Array<[string, Query & { ngMetadataName?: string }]>, string[]] => { + const final: Array<[string, Query & { ngMetadataName?: string }]> = []; + const scanKeys: string[] = []; + + for (const key of Object.keys(queries)) { + const query: Query & { ngMetadataName?: string } = queries[key]; + final.push([key, query]); + + if (!query.isViewQuery && !isInternalKey(key)) { + scanKeys.push(key); + final.push([`__ngMocksVcr_${key}`, cloneVcrQuery(query)]); + } + } + + return [final, scanKeys]; +}; + // Looks like an A9 bug, that queries from @Component are not processed. // Also we have to pass prototype, not the class. // The same issue happens with outputs, but time to time // (when I restart tests with refreshing browser manually). // https://github.com/ike18t/ng-mocks/issues/109 -export default function (cls: AnyType, queries?: { [key: string]: Query }) { - // istanbul ignore else - if (queries) { - for (const key of Object.keys(queries)) { - const query: any = queries[key]; +export default function (cls: AnyType, queries?: { [key: string]: Query }): string[] { + // istanbul ignore if + if (!queries) { + return []; + } + const [final, keys] = generateFinalQueries(queries); + + for (const [key, query] of final) { + // istanbul ignore else + if (query.ngMetadataName) { const decorator = map[query.ngMetadataName]; decorator(query.selector, query)(cls.prototype, key); } } + + return keys; } diff --git a/libs/ng-mocks/src/lib/common/func.is-mock.ts b/libs/ng-mocks/src/lib/common/func.is-mock.ts index 6fc5e835f8..81900e3ed7 100644 --- a/libs/ng-mocks/src/lib/common/func.is-mock.ts +++ b/libs/ng-mocks/src/lib/common/func.is-mock.ts @@ -1,5 +1,14 @@ +import { Injector, TemplateRef, ViewContainerRef } from '@angular/core'; + import { MockConfig } from './mock'; -export default (value: T): value is T & MockConfig => { +export default ( + value: T, +): value is T & + MockConfig & { + __ngMocksInjector?: Injector; + __template?: TemplateRef; + __vcr?: ViewContainerRef; + } => { return value && typeof value === 'object' && !!(value as any).__ngMocksConfig; }; diff --git a/libs/ng-mocks/src/lib/common/mock.ts b/libs/ng-mocks/src/lib/common/mock.ts index 73c60078c3..c0c213c4d4 100644 --- a/libs/ng-mocks/src/lib/common/mock.ts +++ b/libs/ng-mocks/src/lib/common/mock.ts @@ -147,6 +147,7 @@ export type ngMocksMockConfig = { isControlValueAccessor?: boolean; isValidator?: boolean; outputs?: string[]; + queryScanKeys?: string[]; setControlValueAccessor?: boolean; }; @@ -177,6 +178,9 @@ export class Mock { public constructor(injector?: Injector) { const mockOf = (this.constructor as any).mockOf; coreDefineProperty(this, '__ngMocksInjector', injector); + for (const key of this.__ngMocksConfig.queryScanKeys || /* istanbul ignore next */ []) { + coreDefineProperty(this, `__ngMocksVcr_${key}`, undefined); + } // istanbul ignore else if (funcIsMock(this)) { diff --git a/libs/ng-mocks/src/lib/mock-component/mock-component.spec.ts b/libs/ng-mocks/src/lib/mock-component/mock-component.spec.ts index 222083ec12..bb8a218dbd 100644 --- a/libs/ng-mocks/src/lib/mock-component/mock-component.spec.ts +++ b/libs/ng-mocks/src/lib/mock-component/mock-component.spec.ts @@ -10,6 +10,7 @@ import { TemplateRef, ViewChild, ViewChildren, + ViewContainerRef, } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { @@ -376,128 +377,147 @@ describe('MockComponent', () => { expect(actual.__prop__metadata__).toEqual({ o1: [ jasmine.objectContaining({ - descendants: true, - first: true, + selector: 'i1', isViewQuery: false, read: TemplateRef, - selector: 'i1', + ngMetadataName: 'ContentChild', }), ], o2: [ jasmine.objectContaining({ - descendants: false, - first: false, + selector: 'i2', isViewQuery: false, read: TemplateRef, - selector: 'i2', + ngMetadataName: 'ContentChildren', }), ], o3: [ jasmine.objectContaining({ - descendants: true, - first: true, + selector: 'i3', isViewQuery: true, read: TemplateRef, - selector: 'i3', + ngMetadataName: 'ViewChild', }), ], o4: [ jasmine.objectContaining({ - descendants: true, - first: false, + selector: 'i4', isViewQuery: true, read: TemplateRef, - selector: 'i4', + ngMetadataName: 'ViewChildren', }), ], o5: [ jasmine.objectContaining({ - descendants: true, - first: true, + selector: 'i5', isViewQuery: false, read: ElementRef, - selector: 'i5', + ngMetadataName: 'ContentChild', }), ], o6: [ jasmine.objectContaining({ - descendants: false, - first: false, + selector: 'i6', isViewQuery: false, read: ElementRef, - selector: 'i6', + ngMetadataName: 'ContentChildren', }), ], o7: [ jasmine.objectContaining({ - descendants: true, - first: true, + selector: 'i7', isViewQuery: true, read: ElementRef, - selector: 'i7', + ngMetadataName: 'ViewChild', }), ], o8: [ jasmine.objectContaining({ - descendants: true, - first: false, + selector: 'i8', isViewQuery: true, read: ElementRef, - selector: 'i8', + ngMetadataName: 'ViewChildren', + }), + ], + + __ngMocksVcr_o1: [ + jasmine.objectContaining({ + selector: 'i1', + isViewQuery: false, + read: ViewContainerRef, + ngMetadataName: 'ContentChild', + }), + ], + __ngMocksVcr_o2: [ + jasmine.objectContaining({ + selector: 'i2', + isViewQuery: false, + read: ViewContainerRef, + ngMetadataName: 'ContentChildren', + }), + ], + __ngMocksVcr_o5: [ + jasmine.objectContaining({ + selector: 'i5', + isViewQuery: false, + read: ViewContainerRef, + ngMetadataName: 'ContentChild', + }), + ], + __ngMocksVcr_o6: [ + jasmine.objectContaining({ + selector: 'i6', + isViewQuery: false, + read: ViewContainerRef, + ngMetadataName: 'ContentChildren', }), ], __mockView_key_i1: [ jasmine.objectContaining({ - descendants: true, - first: true, - isViewQuery: true, selector: 'key_i1', + isViewQuery: true, static: false, + ngMetadataName: 'ViewChild', }), ], __mockTpl_key_i1: [ jasmine.objectContaining({ - descendants: true, - first: true, - isViewQuery: false, selector: 'i1', + isViewQuery: false, + ngMetadataName: 'ContentChild', }), ], __mockView_prop_o1: [ jasmine.objectContaining({ - descendants: true, - first: true, - isViewQuery: true, selector: 'prop_o1', + isViewQuery: true, static: false, + ngMetadataName: 'ViewChild', }), ], __mockView_key_i2: [ jasmine.objectContaining({ - descendants: true, - first: true, - isViewQuery: true, selector: 'key_i2', + isViewQuery: true, static: false, + ngMetadataName: 'ViewChild', }), ], __mockTpl_key_i2: [ jasmine.objectContaining({ - descendants: false, - first: false, - isViewQuery: false, selector: 'i2', + isViewQuery: false, + ngMetadataName: 'ContentChildren', }), ], __mockView_prop_o2: [ jasmine.objectContaining({ - descendants: true, - first: true, - isViewQuery: true, selector: 'prop_o2', + isViewQuery: true, static: false, + ngMetadataName: 'ViewChild', }), ], }); diff --git a/libs/ng-mocks/src/lib/mock-component/mock-component.ts b/libs/ng-mocks/src/lib/mock-component/mock-component.ts index 45cb73ce97..ecd8065f5f 100644 --- a/libs/ng-mocks/src/lib/mock-component/mock-component.ts +++ b/libs/ng-mocks/src/lib/mock-component/mock-component.ts @@ -99,28 +99,28 @@ const mixRenderHandleViews = ( const mixRender = (instance: MockConfig & Record, cdr: ChangeDetectorRef): void => { // Providing a method to render any @ContentChild based on its selector. - instance.__render = ( - contentChildSelector: string | [string, ...number[]], - $implicit?: any, - variables?: Record, - ) => { - const [type, key, selector, indices] = getKey(contentChildSelector); - - const vcr = mixRenderPrepareVcr(instance, type, selector, cdr); - if (!vcr) { - return; - } + coreDefineProperty( + instance, + '__render', + (contentChildSelector: string | [string, ...number[]], $implicit?: any, variables?: Record) => { + const [type, key, selector, indices] = getKey(contentChildSelector); + + const vcr = mixRenderPrepareVcr(instance, type, selector, cdr); + if (!vcr) { + return; + } - const property: any = instance[key]; - const templates = property instanceof QueryList ? property.toArray() : [property]; + const property: any = instance[key]; + const templates = property instanceof QueryList ? property.toArray() : [property]; - const views = instance[`ngMocksRender_${type}_${selector}_views`] || []; - const index = mixRenderHandleViews(vcr, cdr, templates, views, indices, { ...variables, $implicit }); + const views = instance[`ngMocksRender_${type}_${selector}_views`] || []; + const index = mixRenderHandleViews(vcr, cdr, templates, views, indices, { ...variables, $implicit }); - mixRenderReorderViews(vcr, views, index); - instance[`ngMocksRender_${type}_${selector}_views`] = views; - cdr.detectChanges(); - }; + mixRenderReorderViews(vcr, views, index); + instance[`ngMocksRender_${type}_${selector}_views`] = views; + cdr.detectChanges(); + }, + ); }; const mixHideHandler = ( @@ -143,7 +143,7 @@ const mixHideHandler = ( const mixHide = (instance: MockConfig & Record, changeDetector: ChangeDetectorRef): void => { // Providing method to hide any @ContentChild based on its selector. - instance.__hide = (contentChildSelector: string | [string, ...number[]]) => { + coreDefineProperty(instance, '__hide', (contentChildSelector: string | [string, ...number[]]) => { const [type, , selector, indices] = getKey(contentChildSelector); if (!instance[`ngMocksRender_${type}_${selector}`]) { @@ -155,7 +155,7 @@ const mixHide = (instance: MockConfig & Record, changeDetector: instance[`ngMocksRender_${type}_${selector}`] = false; } changeDetector.detectChanges(); - }; + }); }; class ComponentMockBase extends LegacyControlValueAccessor implements AfterContentInit { diff --git a/libs/ng-mocks/src/lib/mock-component/types.ts b/libs/ng-mocks/src/lib/mock-component/types.ts index 0464c070ad..ad2de3cee1 100644 --- a/libs/ng-mocks/src/lib/mock-component/types.ts +++ b/libs/ng-mocks/src/lib/mock-component/types.ts @@ -17,12 +17,12 @@ export type MockedComponentSelector = export type MockedComponent = T & LegacyControlValueAccessor & { /** - * Helper function to hide rendered @ContentChild() template. + * @deprecated use ngMocks.hide instead */ __hide(contentChildSelector: MockedComponentSelector): void; /** - * Helper function to render any @ContentChild() template with any context. + * @deprecated use ngMocks.render instead */ __render( contentChildSelector: MockedComponentSelector, diff --git a/libs/ng-mocks/src/lib/mock-directive/mock-directive.spec.ts b/libs/ng-mocks/src/lib/mock-directive/mock-directive.spec.ts index 030aba8ef9..04216f6bbc 100644 --- a/libs/ng-mocks/src/lib/mock-directive/mock-directive.spec.ts +++ b/libs/ng-mocks/src/lib/mock-directive/mock-directive.spec.ts @@ -1,10 +1,11 @@ -// tslint:disable max-file-line-count +// tslint:disable max-file-line-count object-literal-sort-keys import { Component, ContentChild, ContentChildren, Directive, + ElementRef, EventEmitter, Injectable, Input, @@ -13,6 +14,7 @@ import { TemplateRef, ViewChild, ViewChildren, + ViewContainerRef, } from '@angular/core'; import { async, @@ -267,97 +269,122 @@ describe('MockDirective', () => { selector: 'never', }) class MyClass { - @ContentChild('i1', { read: true } as any) + @ContentChild('i1', { read: TemplateRef } as any) public o1?: TemplateRef; - @ContentChildren('i2', { read: true } as any) - public o2?: TemplateRef; - @ViewChild('i3', { read: true } as any) - public o3?: QueryList; - @ViewChildren('i4', { read: true } as any) - public o4?: QueryList; - - @ContentChild('i5', { read: false } as any) - public o5?: TemplateRef; - @ContentChildren('i6', { read: false } as any) - public o6?: TemplateRef; - @ViewChild('i7', { read: false } as any) - public o7?: QueryList; - @ViewChildren('i8', { read: false } as any) - public o8?: QueryList; + @ContentChildren('i2', { read: TemplateRef } as any) + public o2?: QueryList>; + @ViewChild('i3', { read: TemplateRef } as any) + public o3?: TemplateRef; + @ViewChildren('i4', { read: TemplateRef } as any) + public o4?: QueryList>; + + @ContentChild('i5', { read: ElementRef } as any) + public o5?: ElementRef; + @ContentChildren('i6', { read: ElementRef } as any) + public o6?: QueryList; + @ViewChild('i7', { read: ElementRef } as any) + public o7?: ElementRef; + @ViewChildren('i8', { read: ElementRef } as any) + public o8?: QueryList; } const actual = MockDirective(MyClass) as any; expect(actual.__prop__metadata__).toEqual({ o1: [ jasmine.objectContaining({ - descendants: true, - first: true, - isViewQuery: false, - read: true, selector: 'i1', + isViewQuery: false, + read: TemplateRef, + ngMetadataName: 'ContentChild', }), ], o2: [ jasmine.objectContaining({ - descendants: false, - first: false, - isViewQuery: false, - read: true, selector: 'i2', + isViewQuery: false, + read: TemplateRef, + ngMetadataName: 'ContentChildren', }), ], o3: [ jasmine.objectContaining({ - descendants: true, - first: true, - isViewQuery: true, - read: true, selector: 'i3', + isViewQuery: true, + read: TemplateRef, + ngMetadataName: 'ViewChild', }), ], o4: [ jasmine.objectContaining({ - descendants: true, - first: false, - isViewQuery: true, - read: true, selector: 'i4', + isViewQuery: true, + read: TemplateRef, + ngMetadataName: 'ViewChildren', }), ], o5: [ jasmine.objectContaining({ - descendants: true, - first: true, - isViewQuery: false, - read: false, selector: 'i5', + isViewQuery: false, + read: ElementRef, + ngMetadataName: 'ContentChild', }), ], o6: [ jasmine.objectContaining({ - descendants: false, - first: false, - isViewQuery: false, - read: false, selector: 'i6', + isViewQuery: false, + read: ElementRef, + ngMetadataName: 'ContentChildren', }), ], o7: [ jasmine.objectContaining({ - descendants: true, - first: true, - isViewQuery: true, - read: false, selector: 'i7', + isViewQuery: true, + read: ElementRef, + ngMetadataName: 'ViewChild', }), ], o8: [ jasmine.objectContaining({ - descendants: true, - first: false, - isViewQuery: true, - read: false, selector: 'i8', + isViewQuery: true, + read: ElementRef, + ngMetadataName: 'ViewChildren', + }), + ], + + __ngMocksVcr_o1: [ + jasmine.objectContaining({ + selector: 'i1', + isViewQuery: false, + read: ViewContainerRef, + ngMetadataName: 'ContentChild', + }), + ], + __ngMocksVcr_o2: [ + jasmine.objectContaining({ + selector: 'i2', + isViewQuery: false, + read: ViewContainerRef, + ngMetadataName: 'ContentChildren', + }), + ], + __ngMocksVcr_o5: [ + jasmine.objectContaining({ + selector: 'i5', + isViewQuery: false, + read: ViewContainerRef, + ngMetadataName: 'ContentChild', + }), + ], + __ngMocksVcr_o6: [ + jasmine.objectContaining({ + selector: 'i6', + isViewQuery: false, + read: ViewContainerRef, + ngMetadataName: 'ContentChildren', }), ], }); diff --git a/libs/ng-mocks/src/lib/mock-directive/mock-directive.ts b/libs/ng-mocks/src/lib/mock-directive/mock-directive.ts index 81aa1d30e0..ddf87bf592 100644 --- a/libs/ng-mocks/src/lib/mock-directive/mock-directive.ts +++ b/libs/ng-mocks/src/lib/mock-directive/mock-directive.ts @@ -64,13 +64,13 @@ class DirectiveMockBase extends LegacyControlValueAccessor implements OnInit { coreDefineProperty(this, '__isStructural', template && vcr); // Providing method to render mock values. - (this as any).__render = ($implicit?: any, variables?: Record) => { + coreDefineProperty(this, '__render', ($implicit?: any, variables?: Record) => { if (vcr && template) { vcr.clear(); vcr.createEmbeddedView(template, { ...variables, $implicit }); cdr.detectChanges(); } - }; + }); } } diff --git a/libs/ng-mocks/src/lib/mock-directive/types.ts b/libs/ng-mocks/src/lib/mock-directive/types.ts index f85d2b4462..e3578b6bb1 100644 --- a/libs/ng-mocks/src/lib/mock-directive/types.ts +++ b/libs/ng-mocks/src/lib/mock-directive/types.ts @@ -30,13 +30,12 @@ export type MockedDirective = T & __vcr?: ViewContainerRef; /** - * Pointer to the view of Structural Directives. * @deprecated use this.__vcr */ __viewContainer?: ViewContainerRef; /** - * Helper function to render any Structural Directive with any context. + * @deprecated use ngMocks.hide instead */ __render($implicit?: any, variables?: Record): void; }; diff --git a/libs/ng-mocks/src/lib/mock-helper/mock-helper.format-html.ts b/libs/ng-mocks/src/lib/mock-helper/mock-helper.format-html.ts new file mode 100644 index 0000000000..230e4d4f91 --- /dev/null +++ b/libs/ng-mocks/src/lib/mock-helper/mock-helper.format-html.ts @@ -0,0 +1,12 @@ +const isDebugElement = (value: any): value is { nativeElement: HTMLElement } => { + return !!value.nativeElement; +}; + +export default (html: string | HTMLElement | { nativeElement: HTMLElement }): string => { + const value = typeof html === 'string' ? html : isDebugElement(html) ? html.nativeElement.innerHTML : html.innerHTML; + + return value + .replace(new RegExp('\\s+', 'mg'), ' ') + .replace(new RegExp('', 'mg'), '') + .replace(new RegExp('\\s+', 'mg'), ' '); +}; diff --git a/libs/ng-mocks/src/lib/mock-helper/mock-helper.ts b/libs/ng-mocks/src/lib/mock-helper/mock-helper.ts index 5b42d54617..f27972b003 100644 --- a/libs/ng-mocks/src/lib/mock-helper/mock-helper.ts +++ b/libs/ng-mocks/src/lib/mock-helper/mock-helper.ts @@ -18,6 +18,7 @@ import mockHelperFindInstances from './mock-helper.find-instances'; import mockHelperFindTemplateRef from './mock-helper.find-template-ref'; import mockHelperFindTemplateRefs from './mock-helper.find-template-refs'; import mockHelperFlushTestBed from './mock-helper.flush-test-bed'; +import mockHelperFormatHtml from './mock-helper.format-html'; import mockHelperGet from './mock-helper.get'; import mockHelperGlobalExclude from './mock-helper.global-exclude'; import mockHelperGlobalKeep from './mock-helper.global-keep'; @@ -31,6 +32,8 @@ import mockHelperReset from './mock-helper.reset'; import mockHelperStub from './mock-helper.stub'; import mockHelperStubMember from './mock-helper.stub-member'; import mockHelperThrowOnConsole from './mock-helper.throw-on-console'; +import mockHelperHide from './render/mock-helper.hide'; +import mockHelperRender from './render/mock-helper.render'; /** * @see https://ng-mocks.sudo.eu/api/ngMocks @@ -190,20 +193,16 @@ export const ngMocks: { findInstances(debugNode: MockedDebugNode | ComponentFixture | undefined | null, instanceClass: Type): T[]; /** - * TODO @see https://ng-mocks.sudo.eu/api/ngMocks/findInstance - */ - findTemplateRef(selector: string | [string] | [string, any] | AnyType): TemplateRef; - - /** - * TODO @see https://ng-mocks.sudo.eu/api/ngMocks/findInstance + * @see https://ng-mocks.sudo.eu/api/ngMocks/findTemplateRef */ findTemplateRef( + debugNode: MockedDebugNode | ComponentFixture | undefined | null, selector: string | [string] | [string, any] | AnyType, notFoundValue: D, ): D | TemplateRef; /** - * TODO @see https://ng-mocks.sudo.eu/api/ngMocks/findInstance + * @see https://ng-mocks.sudo.eu/api/ngMocks/findTemplateRef */ findTemplateRef( debugNode: MockedDebugNode | ComponentFixture | undefined | null, @@ -211,37 +210,40 @@ export const ngMocks: { ): TemplateRef; /** - * TODO @see https://ng-mocks.sudo.eu/api/ngMocks/findInstance + * @see https://ng-mocks.sudo.eu/api/ngMocks/findTemplateRef */ findTemplateRef( - debugNode: MockedDebugNode | ComponentFixture | undefined | null, selector: string | [string] | [string, any] | AnyType, notFoundValue: D, ): D | TemplateRef; /** - * TODO @see https://ng-mocks.sudo.eu/api/ngMocks/findInstances + * @see https://ng-mocks.sudo.eu/api/ngMocks/findTemplateRef */ - findTemplateRefs(selector: string | [string] | [string, any] | AnyType): Array>; - - /** - * TODO @see https://ng-mocks.sudo.eu/api/ngMocks/findInstances - */ - findTemplateRefs(selector: string | [string] | [string, any] | AnyType): Array>; + findTemplateRef(selector: string | [string] | [string, any] | AnyType): TemplateRef; /** - * TODO @see https://ng-mocks.sudo.eu/api/ngMocks/findInstances + * @see https://ng-mocks.sudo.eu/api/ngMocks/findTemplateRefs */ findTemplateRefs( debugNode: MockedDebugNode | ComponentFixture | undefined | null, selector: string | [string] | [string, any] | AnyType, ): Array>; + /** + * @see https://ng-mocks.sudo.eu/api/ngMocks/findTemplateRefs + */ + findTemplateRefs(selector: string | [string] | [string, any] | AnyType): Array>; /** * @see https://ng-mocks.sudo.eu/api/ngMocks/flushTestBed */ flushTestBed(): void; + /** + * Removes comments and sequences of spaces and new lines. + */ + formatHtml(html: string | HTMLElement | { nativeElement: HTMLElement }): string; + /** * @see https://ng-mocks.sudo.eu/api/ngMocks/get */ @@ -299,6 +301,16 @@ export const ngMocks: { exclude?: AnyType | InjectionToken | Array | InjectionToken> | null | undefined, ): TestModuleMetadata; + /** + * @see https://ng-mocks.sudo.eu/api/ngMocks/hide + */ + hide(instance: object, tpl?: TemplateRef): void; + + /** + * @see https://ng-mocks.sudo.eu/api/ngMocks/hide + */ + hide(instance: object, directive: object): void; + /** * @see https://ng-mocks.sudo.eu/api/ngMocks/input */ @@ -323,6 +335,16 @@ export const ngMocks: { notFoundValue: D, ): D | EventEmitter; + /** + * @see https://ng-mocks.sudo.eu/api/ngMocks/render + */ + render(instance: object, template: TemplateRef, $implicit?: any, variables?: Record): void; + + /** + * @see https://ng-mocks.sudo.eu/api/ngMocks/render + */ + render(instance: object, directive: object, $implicit?: any, variables?: Record): void; + /** * @see https://ng-mocks.sudo.eu/api/ngMocks/reset */ @@ -380,6 +402,7 @@ export const ngMocks: { findTemplateRef: mockHelperFindTemplateRef, findTemplateRefs: mockHelperFindTemplateRefs, flushTestBed: mockHelperFlushTestBed, + formatHtml: mockHelperFormatHtml, get: mockHelperGet, globalExclude: mockHelperGlobalExclude, globalKeep: mockHelperGlobalKeep, @@ -387,8 +410,10 @@ export const ngMocks: { globalReplace: mockHelperGlobalReplace, globalWipe: mockHelperGlobalWipe, guts: mockHelperGuts, + hide: mockHelperHide, input: mockHelperInput, output: mockHelperOutput, + render: mockHelperRender, reset: mockHelperReset, stub: mockHelperStub, stubMember: mockHelperStubMember, diff --git a/libs/ng-mocks/src/lib/mock-helper/render/func.find-deep.ts b/libs/ng-mocks/src/lib/mock-helper/render/func.find-deep.ts new file mode 100644 index 0000000000..209b30578a --- /dev/null +++ b/libs/ng-mocks/src/lib/mock-helper/render/func.find-deep.ts @@ -0,0 +1,74 @@ +import { QueryList, TemplateRef, ViewContainerRef } from '@angular/core'; + +import funcIsMock from '../../common/func.is-mock'; +import { MockConfig } from '../../common/mock'; + +const getValVcr = (entryPoint: MockConfig): Array<[any, ViewContainerRef]> => { + const result: Array<[any, ViewContainerRef]> = []; + + for (const key of entryPoint.__ngMocksConfig.queryScanKeys || /* istanbul ignore next */ []) { + const value = (entryPoint as any)[key]; + const vcr = (entryPoint as any)[`__ngMocksVcr_${key}`]; + + const scanValue = value instanceof QueryList ? value.toArray() : [value]; + const scanVcr = vcr instanceof QueryList ? vcr.toArray() : [vcr]; + + for (let index = 0; index < scanValue.length; index += 1) { + result.push([scanValue[index], scanVcr[index]]); + } + } + + return result; +}; + +const handleDirective = ( + entryPoint: { + __template?: TemplateRef; + __vcr?: ViewContainerRef; + }, + isExpectedTemplate: (tpl: TemplateRef) => boolean, + callback: (vcr: ViewContainerRef, tpl: TemplateRef) => boolean, +): boolean => { + return ( + !!entryPoint.__template && + !!entryPoint.__vcr && + isExpectedTemplate(entryPoint.__template) && + callback(entryPoint.__vcr, entryPoint.__template) + ); +}; + +const isRightTemplate = ( + localVcr: ViewContainerRef | undefined, + localValue: any, + isExpectedTemplate: (tpl: TemplateRef) => boolean, +): boolean => { + return !!localVcr && localValue instanceof TemplateRef && isExpectedTemplate(localValue); +}; + +const findDeep = ( + entryPoint: object, + isExpectedTemplate: (tpl: TemplateRef) => boolean, + callback: (vcr: ViewContainerRef, tpl: TemplateRef) => boolean, +): boolean => { + if (!funcIsMock(entryPoint)) { + throw new Error('Only instances of mock declarations are accepted'); + } + + // structural directive + if (handleDirective(entryPoint, isExpectedTemplate, callback)) { + return true; + } + + for (const [localValue, localVcr] of getValVcr(entryPoint)) { + if (funcIsMock<{}>(localValue) && findDeep(localValue, isExpectedTemplate, callback)) { + return true; + } + if (isRightTemplate(localVcr, localValue, isExpectedTemplate)) { + return callback(localVcr, localValue); + } + } + + return false; +}; + +export default ((): typeof findDeep => findDeep)(); diff --git a/libs/ng-mocks/src/lib/mock-helper/render/func.parse-template.ts b/libs/ng-mocks/src/lib/mock-helper/render/func.parse-template.ts new file mode 100644 index 0000000000..ec13974417 --- /dev/null +++ b/libs/ng-mocks/src/lib/mock-helper/render/func.parse-template.ts @@ -0,0 +1,19 @@ +import { TemplateRef } from '@angular/core'; + +import funcIsMock from '../../common/func.is-mock'; + +export default (param: any): TemplateRef => { + if (param instanceof TemplateRef) { + return param; + } + if (funcIsMock(param) && param.__template) { + return param.__template; + } + + const error = new Error( + 'Unknown template has been passed, only TemplateRef or a mock structural directive are supported', + ); + (error as any).param = param; + + throw error; +}; diff --git a/libs/ng-mocks/src/lib/mock-helper/render/mock-helper.hide.ts b/libs/ng-mocks/src/lib/mock-helper/render/mock-helper.hide.ts new file mode 100644 index 0000000000..3e2fe55097 --- /dev/null +++ b/libs/ng-mocks/src/lib/mock-helper/render/mock-helper.hide.ts @@ -0,0 +1,28 @@ +import funcFindDeep from './func.find-deep'; +import funcParseTemplate from './func.parse-template'; + +export default (instance: object, param?: object) => { + const template = param ? funcParseTemplate(param) : undefined; + + let result = false; + funcFindDeep( + instance, + tpl => { + if (!template) { + return true; + } + + return tpl.elementRef.nativeElement === template.elementRef.nativeElement; + }, + vcr => { + vcr.clear(); + result = true; + + return false; + }, + ); + + if (!result) { + throw new Error('Cannot find path to the TemplateRef'); + } +}; diff --git a/libs/ng-mocks/src/lib/mock-helper/render/mock-helper.render.ts b/libs/ng-mocks/src/lib/mock-helper/render/mock-helper.render.ts new file mode 100644 index 0000000000..6d4e5d85d6 --- /dev/null +++ b/libs/ng-mocks/src/lib/mock-helper/render/mock-helper.render.ts @@ -0,0 +1,25 @@ +import funcFindDeep from './func.find-deep'; +import funcParseTemplate from './func.parse-template'; + +export default (instance: object, param: object, $implicit?: any, variables?: Record) => { + const template = funcParseTemplate(param); + + const result = funcFindDeep( + instance, + tpl => tpl.elementRef.nativeElement === template.elementRef.nativeElement, + (vcr, tpl) => { + const context = { + ...variables, + $implicit, + }; + vcr.clear(); + vcr.createEmbeddedView(tpl, context).detectChanges(); + + return true; + }, + ); + + if (!result) { + throw new Error('Cannot find path to the TemplateRef'); + } +}; diff --git a/libs/ng-mocks/src/lib/mock/decorate-declaration.ts b/libs/ng-mocks/src/lib/mock/decorate-declaration.ts index 4f3b9ea99a..9b5c3e2f2d 100644 --- a/libs/ng-mocks/src/lib/mock/decorate-declaration.ts +++ b/libs/ng-mocks/src/lib/mock/decorate-declaration.ts @@ -5,12 +5,29 @@ import decorateInputs from '../common/decorate.inputs'; import decorateMock from '../common/decorate.mock'; import decorateOutputs from '../common/decorate.outputs'; import decorateQueries from '../common/decorate.queries'; +import { ngMocksMockConfig } from '../common/mock'; import ngMocksUniverse from '../common/ng-mocks-universe'; import helperMockService from '../mock-service/helper.mock-service'; import cloneProviders from './clone-providers'; import toExistingProvider from './to-existing-provider'; +const buildConfig = ( + source: AnyType, + meta: { + inputs?: string[]; + outputs?: string[]; + providers?: Provider[]; + queries?: Record; + }, + setControlValueAccessor: boolean, +): ngMocksMockConfig => ({ + config: ngMocksUniverse.config.get(source), + outputs: meta.outputs, + queryScanKeys: [], + setControlValueAccessor, +}); + export default ( source: AnyType, mock: AnyType, @@ -30,18 +47,16 @@ export default ( data.setControlValueAccessor = helperMockService.extractMethodsFromPrototype(source.prototype).indexOf('writeValue') !== -1; } - decorateMock(mock, source, { - config: ngMocksUniverse.config.get(source), - outputs: meta.outputs, - setControlValueAccessor: data.setControlValueAccessor, - }); + + const config: ngMocksMockConfig = buildConfig(source, meta, data.setControlValueAccessor); + decorateMock(mock, source, config); // istanbul ignore else if (meta.queries) { decorateInputs(mock, meta.inputs, Object.keys(meta.queries)); } decorateOutputs(mock, meta.outputs); - decorateQueries(mock, meta.queries); + config.queryScanKeys = decorateQueries(mock, meta.queries); return options; }; diff --git a/tests-angular/e2e/src/mat-table/test.spec.ts b/tests-angular/e2e/src/mat-table/test.spec.ts index b65392767b..23e08d1b65 100644 --- a/tests-angular/e2e/src/mat-table/test.spec.ts +++ b/tests-angular/e2e/src/mat-table/test.spec.ts @@ -1,13 +1,10 @@ import { Component, NgModule } from '@angular/core'; import { - MatCellDef, - MatHeaderCellDef, MatHeaderRowDef, MatRowDef, - MatTable, MatTableModule, } from '@angular/material/table'; -import { isMockOf, MockBuilder, MockRender, ngMocks } from 'ng-mocks'; +import { MockBuilder, MockRender, ngMocks } from 'ng-mocks'; export interface PeriodicElement { name: string; @@ -90,7 +87,7 @@ describe('mat-table:props', () => { it('binds inputs', () => { const targetComponent = MockRender(TargetComponent).point .componentInstance; - const tableEl = ngMocks.find(MatTable); + const tableEl = ngMocks.find('[mat-table]'); expect(ngMocks.input(tableEl, 'dataSource')).toBe( targetComponent.dataSource, @@ -99,26 +96,26 @@ describe('mat-table:props', () => { it('provides correct template for matColumnDef="position"', () => { MockRender(TargetComponent); - const tableEl = ngMocks.find(MatTable); + const tableEl = ngMocks.find('[mat-table]'); - const [header] = ngMocks.findInstances(tableEl, MatHeaderCellDef); expect(tableEl.nativeElement.innerHTML).not.toContain( 'No.', ); - if (isMockOf(header, MatHeaderCellDef, 'd')) { - header.__render(); - } + const [header] = ngMocks.findTemplateRefs(tableEl, [ + 'matHeaderCellDef', + ]); + ngMocks.render(tableEl.componentInstance, header); expect(tableEl.nativeElement.innerHTML).toContain( 'No.', ); - const [cell] = ngMocks.findInstances(tableEl, MatCellDef); expect(tableEl.nativeElement.innerHTML).not.toContain( ' testPosition ', ); - if (isMockOf(cell, MatCellDef, 'd')) { - cell.__render({ position: 'testPosition' }); - } + const [cell] = ngMocks.findTemplateRefs(tableEl, ['matCellDef']); + ngMocks.render(tableEl.componentInstance, cell, { + position: 'testPosition', + }); expect(tableEl.nativeElement.innerHTML).toContain( ' testPosition ', ); @@ -126,29 +123,28 @@ describe('mat-table:props', () => { it('provides correct template for matColumnDef="name"', () => { MockRender(TargetComponent); - const tableEl = ngMocks.find(MatTable); + const tableEl = ngMocks.find('[mat-table]'); - const [, header] = ngMocks.findInstances( - tableEl, - MatHeaderCellDef, - ); expect(tableEl.nativeElement.innerHTML).not.toContain( 'Name', ); - if (isMockOf(header, MatHeaderCellDef, 'd')) { - header.__render(); - } + const [, header] = ngMocks.findTemplateRefs(tableEl, [ + 'matHeaderCellDef', + ]); + ngMocks.render(tableEl.componentInstance, header); expect(tableEl.nativeElement.innerHTML).toContain( 'Name', ); - const [, cell] = ngMocks.findInstances(tableEl, MatCellDef); expect(tableEl.nativeElement.innerHTML).not.toContain( 'testName', ); - if (isMockOf(cell, MatCellDef, 'd')) { - cell.__render({ name: 'testName' }); - } + const [, cell] = ngMocks.findTemplateRefs(tableEl, [ + 'matCellDef', + ]); + ngMocks.render(tableEl.componentInstance, cell, { + name: 'testName', + }); expect(tableEl.nativeElement.innerHTML).toContain( 'testName', ); @@ -156,29 +152,28 @@ describe('mat-table:props', () => { it('provides correct template for matColumnDef="weight"', () => { MockRender(TargetComponent); - const tableEl = ngMocks.find(MatTable); + const tableEl = ngMocks.find('[mat-table]'); - const [, , header] = ngMocks.findInstances( - tableEl, - MatHeaderCellDef, - ); expect(tableEl.nativeElement.innerHTML).not.toContain( 'Weight', ); - if (isMockOf(header, MatHeaderCellDef, 'd')) { - header.__render(); - } + const [, , header] = ngMocks.findTemplateRefs(tableEl, [ + 'matHeaderCellDef', + ]); + ngMocks.render(tableEl.componentInstance, header); expect(tableEl.nativeElement.innerHTML).toContain( 'Weight', ); - const [, , cell] = ngMocks.findInstances(tableEl, MatCellDef); expect(tableEl.nativeElement.innerHTML).not.toContain( ' testWeight ', ); - if (isMockOf(cell, MatCellDef, 'd')) { - cell.__render({ weight: 'testWeight' }); - } + const [, , cell] = ngMocks.findTemplateRefs(tableEl, [ + 'matCellDef', + ]); + ngMocks.render(tableEl.componentInstance, cell, { + weight: 'testWeight', + }); expect(tableEl.nativeElement.innerHTML).toContain( ' testWeight ', ); @@ -186,29 +181,28 @@ describe('mat-table:props', () => { it('provides correct template for matColumnDef="symbol"', () => { MockRender(TargetComponent); - const tableEl = ngMocks.find(MatTable); + const tableEl = ngMocks.find('[mat-table]'); - const [, , , header] = ngMocks.findInstances( - tableEl, - MatHeaderCellDef, - ); expect(tableEl.nativeElement.innerHTML).not.toContain( 'Symbol', ); - if (isMockOf(header, MatHeaderCellDef, 'd')) { - header.__render(); - } + const [, , , header] = ngMocks.findTemplateRefs(tableEl, [ + 'matHeaderCellDef', + ]); + ngMocks.render(tableEl.componentInstance, header); expect(tableEl.nativeElement.innerHTML).toContain( 'Symbol', ); - const [, , , cell] = ngMocks.findInstances(tableEl, MatCellDef); expect(tableEl.nativeElement.innerHTML).not.toContain( ' testSymbol ', ); - if (isMockOf(cell, MatCellDef, 'd')) { - cell.__render({ symbol: 'testSymbol' }); - } + const [, , , cell] = ngMocks.findTemplateRefs(tableEl, [ + 'matCellDef', + ]); + ngMocks.render(tableEl.componentInstance, cell, { + symbol: 'testSymbol', + }); expect(tableEl.nativeElement.innerHTML).toContain( ' testSymbol ', ); @@ -217,16 +211,14 @@ describe('mat-table:props', () => { it('provides correct template for mat-header-row', () => { const targetComponent = MockRender(TargetComponent).point .componentInstance; - const tableEl = ngMocks.find(MatTable); + const tableEl = ngMocks.find('[mat-table]'); const header = ngMocks.findInstance(tableEl, MatHeaderRowDef); expect(header.columns).toBe(targetComponent.displayedColumns); expect(tableEl.nativeElement.innerHTML).not.toContain( '', ); - if (isMockOf(header, MatHeaderRowDef, 'd')) { - header.__render(); - } + ngMocks.render(tableEl.componentInstance, header); expect(tableEl.nativeElement.innerHTML).toContain( '', ); @@ -235,16 +227,14 @@ describe('mat-table:props', () => { it('provides correct template for mat-row', () => { const targetComponent = MockRender(TargetComponent).point .componentInstance; - const tableEl = ngMocks.find(MatTable); + const tableEl = ngMocks.find('[mat-table]'); const row = ngMocks.findInstance(tableEl, MatRowDef); expect(row.columns).toBe(targetComponent.displayedColumns); expect(tableEl.nativeElement.innerHTML).not.toContain( '', ); - if (isMockOf(row, MatRowDef, 'd')) { - row.__render(); - } + ngMocks.render(tableEl.componentInstance, row); expect(tableEl.nativeElement.innerHTML).toContain( '', ); diff --git a/tests-angular/e2e/src/ng-select/test.spec.ts b/tests-angular/e2e/src/ng-select/test.spec.ts index 388326f437..a239ca9e90 100644 --- a/tests-angular/e2e/src/ng-select/test.spec.ts +++ b/tests-angular/e2e/src/ng-select/test.spec.ts @@ -1,10 +1,7 @@ import { Component, NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; -import { - NgSelectComponent, - NgSelectModule, -} from '@ng-select/ng-select'; -import { isMockOf, MockBuilder, MockRender, ngMocks } from 'ng-mocks'; +import { NgSelectModule } from '@ng-select/ng-select'; +import { MockBuilder, MockRender, ngMocks } from 'ng-mocks'; @Component({ selector: 'target', @@ -64,8 +61,8 @@ describe('ng-select:props', () => { const targetComponent = MockRender(TargetComponent).point .componentInstance; - // Looking for a debug element of `NgSelectComponent`. - const ngSelectEl = ngMocks.find(NgSelectComponent); + // Looking for a debug element of the ng-select. + const ngSelectEl = ngMocks.find('ng-select'); // Asserting bound properties. expect(ngMocks.input(ngSelectEl, 'items')).toBe( @@ -86,8 +83,8 @@ describe('ng-select:props', () => { const targetComponent = MockRender(TargetComponent).point .componentInstance; - // Looking for a debug element of `NgSelectComponent`. - const ngSelectEl = ngMocks.find(NgSelectComponent); + // Looking for a debug element of the ng-select. + const ngSelectEl = ngMocks.find('ng-select'); // Simulating an emit. ngMocks.output(ngSelectEl, 'ngModelChange').emit('test'); @@ -100,26 +97,28 @@ describe('ng-select:props', () => { // Rendering TargetComponent. MockRender(TargetComponent); - // Looking for the instance of `NgSelectComponent`. - const ngSelect = ngMocks.findInstance(NgSelectComponent); + // Looking for a debug element of the ng-select. + const ngSelectEl = ngMocks.find('ng-select'); - // Verifying that the instance has been mocked. - // And rendering its property, - // which points to the desired TemplateRef. - if (isMockOf(ngSelect, NgSelectComponent, 'c')) { - ngSelect.__render( - ['labelTemplate'], - {}, - // Providing context variables. - { item: { name: 'test' } }, - ); - } - - // Looking for a debug element of the rendered TemplateRef. - const tplEl = ngMocks.find('[data-prop="labelTemplate"]'); + // Looking for the ng-label-tmp template + const ngLabelTmp = ngMocks.findTemplateRef( + ngSelectEl, + // attr name + ['ng-label-tmp'], + ); + + // Verifies that ngSelect can access ngLabelTmp, + // and renders it. + ngMocks.render( + ngSelectEl.componentInstance, + ngLabelTmp, + {}, + // Providing context variables. + { item: { name: 'test' } }, + ); // Asserting the rendered html. - expect(tplEl.nativeElement.innerHTML).toContain( + expect(ngSelectEl.nativeElement.innerHTML).toContain( 'test', ); }); @@ -128,32 +127,34 @@ describe('ng-select:props', () => { // Rendering TargetComponent and accessing its instance. MockRender(TargetComponent); - // Looking for the instance of `NgSelectComponent`. - const ngSelect = ngMocks.findInstance(NgSelectComponent); + // Looking for a debug element of the ng-select. + const ngSelectEl = ngMocks.find('ng-select'); - // Verifying that the instance has been mocked. - // And rendering its property, - // which points to the desired TemplateRef. - if (isMockOf(ngSelect, NgSelectComponent, 'c')) { - ngSelect.__render( - ['optgroupTemplate'], - {}, - // Providing context variables. - { - index: 7, - item: { - avatar: 'test.jpeg', - name: 'test', - }, - }, - ); - } + // Looking for the ng-optgroup-tmp template + const ngOptgroupTmp = ngMocks.findTemplateRef( + ngSelectEl, + // attr name + ['ng-optgroup-tmp'], + ); - // Looking for a debug element of the rendered TemplateRef. - const tplEl = ngMocks.find('[data-prop="optgroupTemplate"]'); + // Verifies that ngSelect can access ngOptgroupTmp, + // and renders it. + ngMocks.render( + ngSelectEl.componentInstance, + ngOptgroupTmp, + {}, + // Providing context variables. + { + index: 7, + item: { + avatar: 'test.jpeg', + name: 'test', + }, + }, + ); // Asserting the rendered html. - expect(tplEl.nativeElement.innerHTML).toContain( + expect(ngSelectEl.nativeElement.innerHTML).toContain( '7 test', ); }); @@ -162,31 +163,34 @@ describe('ng-select:props', () => { // Rendering TargetComponent and accessing its instance. MockRender(TargetComponent); - // Looking for the instance of `NgSelectComponent`. - const ngSelect = ngMocks.findInstance(NgSelectComponent); + // Looking for a debug element of the ng-select. + const ngSelectEl = ngMocks.find('ng-select'); + + // Looking for the ng-option-tmp template + const ngOptionTmp = ngMocks.findTemplateRef( + ngSelectEl, + // attr name + ['ng-option-tmp'], + ); // Verifying that the instance has been mocked. // And rendering its property, // which points to the desired TemplateRef. - if (isMockOf(ngSelect, NgSelectComponent, 'c')) { - ngSelect.__render( - ['optionTemplate'], - {}, - // Providing context variables. - { - item: { - name: 'test', - }, - searchTerm: 'search', + ngMocks.render( + ngSelectEl.componentInstance, + ngOptionTmp, + {}, + // Providing context variables. + { + item: { + name: 'test', }, - ); - } - - // Looking for a debug element of the rendered TemplateRef. - const labelEl = ngMocks.find('[data-prop="optionTemplate"]'); + searchTerm: 'search', + }, + ); // Asserting the rendered html. - expect(labelEl.nativeElement.innerHTML).toContain( + expect(ngSelectEl.nativeElement.innerHTML).toContain( 'search test', ); }); diff --git a/tests-angular/e2e/src/p-calendar/test.spec.ts b/tests-angular/e2e/src/p-calendar/test.spec.ts index a17681f2c1..9d6a8e88fe 100644 --- a/tests-angular/e2e/src/p-calendar/test.spec.ts +++ b/tests-angular/e2e/src/p-calendar/test.spec.ts @@ -1,8 +1,7 @@ import { Component, NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; -import { isMockOf, MockBuilder, MockRender, ngMocks } from 'ng-mocks'; -import { PrimeTemplate } from 'primeng/api'; -import { Calendar, CalendarModule } from 'primeng/calendar'; +import { MockBuilder, MockRender, ngMocks } from 'ng-mocks'; +import { CalendarModule } from 'primeng/calendar'; @Component({ selector: 'target', @@ -64,18 +63,15 @@ describe('p-calendar:directives', () => { // Looking for a debug element of `p-calendar`. const calendarEl = ngMocks.find('p-calendar'); - // Looking for the instance of PrimeTemplate. - // 'header' is the first one. - const [header] = ngMocks.findInstances(calendarEl, PrimeTemplate); + // Looking for the template of 'header'. + const header = ngMocks.findTemplateRef(calendarEl, [ + 'pTemplate', + 'header', + ]); - // Asserting that it is the header. - expect(header.name).toEqual('header'); - - // Verifying that the directive has been mocked. - // And rendering it. - if (isMockOf(header, PrimeTemplate, 'd')) { - header.__render(); - } + // Verifies that the directive has been mocked. + // And renders it. + ngMocks.render(calendarEl.componentInstance, header); // Asserting the rendered template. expect(calendarEl.nativeElement.innerHTML).toContain('Header'); @@ -88,21 +84,15 @@ describe('p-calendar:directives', () => { // Looking for a debug element of `p-calendar`. const calendarEl = ngMocks.find('p-calendar'); - // Looking for the instance of PrimeTemplate. - // 'footer' is the second one. - const [, footer] = ngMocks.findInstances( - calendarEl, - PrimeTemplate, - ); - - // Asserting that it is the footer. - expect(footer.name).toEqual('footer'); - - // Verifying that the directive has been mocked. - // And rendering it. - if (isMockOf(footer, PrimeTemplate, 'd')) { - footer.__render(); - } + // Looking for the template of 'footer'. + const footer = ngMocks.findTemplateRef(calendarEl, [ + 'pTemplate', + 'footer', + ]); + + // Verifies that the directive has been mocked. + // And renders it. + ngMocks.render(calendarEl.componentInstance, footer); // Asserting the rendered template. expect(calendarEl.nativeElement.innerHTML).toContain('Footer'); diff --git a/tests/context-with-directives/test.spec.ts b/tests/context-with-directives/test.spec.ts index 9f29e4a561..114dca3400 100644 --- a/tests/context-with-directives/test.spec.ts +++ b/tests/context-with-directives/test.spec.ts @@ -36,41 +36,41 @@ describe('context-with-directives:real', () => { // template should be rendered under .template expect( - ngMocks - .find(fixture.debugElement, '.template') - .nativeElement.innerHTML.replace(/\s+/gm, ' '), + ngMocks.formatHtml( + ngMocks.find(fixture.debugElement, '.template'), + ), ).toContain(' template w/ directive w/o binding '); // template1 should be rendered under .template1 expect( - ngMocks - .find(fixture.debugElement, '.template1') - .nativeElement.innerHTML.replace(/\s+/gm, ' '), + ngMocks.formatHtml( + ngMocks.find(fixture.debugElement, '.template1'), + ), ).toContain(' template w/ directive w/ binding 1 '); // template2 should not be rendered - expect( - fixture.nativeElement.innerHTML.replace(/\s+/gm, ' '), - ).not.toContain(' template w/ directive w/ binding w/o render '); + expect(ngMocks.formatHtml(fixture)).not.toContain( + ' template w/ directive w/ binding w/o render ', + ); // unused ng-templates should not be rendered at all - expect( - fixture.nativeElement.innerHTML.replace(/\s+/gm, ' '), - ).not.toContain(' template w/o directive w/o binding '); - expect( - fixture.nativeElement.innerHTML.replace(/\s+/gm, ' '), - ).not.toContain(' template w/o directive w/ binding '); + expect(ngMocks.formatHtml(fixture)).not.toContain( + ' template w/o directive w/o binding ', + ); + expect(ngMocks.formatHtml(fixture)).not.toContain( + ' template w/o directive w/ binding ', + ); // ng-content contains header and footer expect( - ngMocks - .find(fixture.debugElement, '.nested') - .nativeElement.innerHTML.replace(/\s+/, ' '), + ngMocks.formatHtml( + ngMocks.find(fixture.debugElement, '.nested'), + ), ).toContain('
header
'); expect( - ngMocks - .find(fixture.debugElement, '.nested') - .nativeElement.innerHTML.replace(/\s+/, ' '), + ngMocks.formatHtml( + ngMocks.find(fixture.debugElement, '.nested'), + ), ).toContain('
footer
'); }); }); @@ -113,8 +113,6 @@ describe('context-with-directives:mock', () => { // No templates should be rendered when we mock them. // The reason for that is that only directive knows when to render it, that means if we want to render, // we should do that manually. - expect( - fixture.nativeElement.innerHTML.replace(/\s+/gm, ' '), - ).not.toContain(' template '); + expect(ngMocks.formatHtml(fixture)).not.toContain(' template '); }); }); diff --git a/tests/module-with-factory-tokens/test.spec.ts b/tests/module-with-factory-tokens/test.spec.ts index 2b6b0c5f51..14fce47f98 100644 --- a/tests/module-with-factory-tokens/test.spec.ts +++ b/tests/module-with-factory-tokens/test.spec.ts @@ -1,5 +1,5 @@ import { VERSION } from '@angular/core'; -import { MockBuilder, MockRender } from 'ng-mocks'; +import { MockBuilder, MockRender, ngMocks } from 'ng-mocks'; import { MY_TOKEN_MULTI, @@ -73,9 +73,7 @@ describe('module-with-factory-tokens:mock-0', () => { it('fails to render all tokens', () => { const fixture = MockRender(TargetComponent); - expect( - fixture.nativeElement.innerHTML.replace(/\s+/gm, ' '), - ).toContain('"V1" [ "V2" ]'); + expect(ngMocks.formatHtml(fixture)).toContain('"V1" [ "V2" ]'); }); }); @@ -91,9 +89,9 @@ describe('module-with-factory-tokens:mock-1', () => { it('renders all tokens', () => { const fixture = MockRender(TargetComponent); - expect( - fixture.nativeElement.innerHTML.replace(/\s+/gm, ' '), - ).toEqual(' '); + expect(ngMocks.formatHtml(fixture)).toEqual( + ' ', + ); }); }); diff --git a/tests/module-with-tokens/test.spec.ts b/tests/module-with-tokens/test.spec.ts index 7e378724f0..5a6e0ac8b8 100644 --- a/tests/module-with-tokens/test.spec.ts +++ b/tests/module-with-tokens/test.spec.ts @@ -1,4 +1,4 @@ -import { MockBuilder, MockRender } from 'ng-mocks'; +import { MockBuilder, MockRender, ngMocks } from 'ng-mocks'; import { MY_TOKEN_MULTI, @@ -30,9 +30,7 @@ describe('module-with-tokens:mock-0', () => { it('fails to render all tokens', () => { const fixture = MockRender(TargetComponent); - expect( - fixture.nativeElement.innerHTML.replace(/\s+/gm, ' '), - ).toEqual( + expect(ngMocks.formatHtml(fixture)).toEqual( '"V1" [ "V2", "V3" ]', ); }); @@ -49,9 +47,7 @@ describe('module-with-tokens:mock-1', () => { it('renders all tokens', () => { const fixture = MockRender(TargetComponent); - expect( - fixture.nativeElement.innerHTML.replace(/\s+/gm, ' '), - ).toEqual( + expect(ngMocks.formatHtml(fixture)).toEqual( ' [ null, null ]', ); }); @@ -67,9 +63,7 @@ describe('module-with-tokens:mock-2', () => { it('renders all tokens', () => { const fixture = MockRender(TargetComponent); - expect( - fixture.nativeElement.innerHTML.replace(/\s+/gm, ' '), - ).toEqual( + expect(ngMocks.formatHtml(fixture)).toEqual( '' + '"MOCK_MY_TOKEN_SINGLE" [ "MOCK_MY_TOKEN_MULTI", "MOCK_MY_TOKEN_MULTI" ]' + '', @@ -104,9 +98,7 @@ describe('module-with-tokens:real', () => { it('renders all tokens', () => { const fixture = MockRender(TargetComponent); - expect( - fixture.nativeElement.innerHTML.replace(/\s+/gm, ' '), - ).toEqual( + expect(ngMocks.formatHtml(fixture)).toEqual( '"MY_TOKEN_SINGLE" [ "MY_TOKEN_MULTI", "MY_TOKEN_MULTI_2" ]', ); }); @@ -122,9 +114,7 @@ describe('module-with-tokens:keep', () => { it('renders all tokens', () => { const fixture = MockRender(TargetComponent); - expect( - fixture.nativeElement.innerHTML.replace(/\s+/gm, ' '), - ).toEqual( + expect(ngMocks.formatHtml(fixture)).toEqual( '"MY_TOKEN_SINGLE" [ "MY_TOKEN_MULTI", "MY_TOKEN_MULTI_2" ]', ); }); diff --git a/tests/ng-mocks-render/component.spec.ts b/tests/ng-mocks-render/component.spec.ts new file mode 100644 index 0000000000..d6dfea0d60 --- /dev/null +++ b/tests/ng-mocks-render/component.spec.ts @@ -0,0 +1,367 @@ +// tslint:disable max-file-line-count + +import { CommonModule } from '@angular/common'; +import { + Component, + ContentChild, + ContentChildren, + Directive, + Input, + NgModule, + QueryList, + TemplateRef, +} from '@angular/core'; +import { isMockOf, MockBuilder, MockRender, ngMocks } from 'ng-mocks'; + +@Directive({ + selector: '[tpl1]', +}) +class Mock1Directive { + @Input('tpl1') public readonly name: string | null = null; + + public constructor(public readonly tpl: TemplateRef) {} +} + +@Directive({ + selector: '[tpl2]', +}) +class Mock2Directive { + @Input('tpl2') public readonly name: string | null = null; + + public constructor(public readonly tpl: TemplateRef) {} +} + +@Directive({ + selector: '[tpl3]', +}) +class Mock3Directive { + @Input('tpl3') public readonly name: string | null = null; + + @ContentChild('info', {} as any) + public readonly tpl?: TemplateRef; +} + +@Component({ + selector: 'component', + template: ` +
+ +
+
+ +
+
+ + + +
+
+ + + +
+ `, +}) +class MockComponent { + @ContentChildren(Mock2Directive, {} as any) + public readonly directives?: QueryList; + + @ContentChild('header', {} as any) + public readonly header?: TemplateRef; + + @ContentChild(Mock3Directive, {} as any) + public readonly info?: Mock3Directive; + + @ContentChildren(Mock1Directive, { + read: TemplateRef, + } as any) + public readonly templates?: QueryList>; +} + +@Component({ + selector: 'target', + template: ` + + :step:1: + rendered-header + :step:2: + rendered-tpl1-1-{{ param }} + :step:3: +
rendered-tpl1-2-{{ param }}
+ :step:4: + rendered-tpl2-1-{{ param }} + :step:5: +
rendered-tpl2-2-{{ param }}
+ :step:6: +
+ :step:7: + rendered-info-{{ param }} + :step:8: +
+ :step:9: +
+ `, +}) +class TargetComponent {} + +@NgModule({ + declarations: [ + Mock1Directive, + Mock2Directive, + Mock3Directive, + MockComponent, + TargetComponent, + ], + imports: [CommonModule], +}) +class TargetModule {} + +describe('ng-mocks-render:component:real', () => { + beforeEach(() => MockBuilder(TargetComponent).keep(TargetModule)); + + it('renders templates properly', () => { + const fixture = MockRender(TargetComponent); + const html = ngMocks.formatHtml(fixture.nativeElement); + expect(html).not.toContain(':step:'); + expect(html).toContain('rendered-header'); + expect(html).toContain('rendered-info-info'); + expect(html).toContain('rendered-tpl1-1-template'); + expect(html).toContain('rendered-tpl1-2-template'); + expect(html).toContain('rendered-tpl2-1-tpl1'); + expect(html).toContain('rendered-tpl2-2-tpl2'); + }); +}); + +describe('ng-mocks-render:component:mock', () => { + beforeEach(() => MockBuilder(TargetComponent, TargetModule)); + + it('renders directives on their positions', () => { + let html = ''; + const fixture = MockRender(TargetComponent); + + html = ngMocks.formatHtml(fixture.nativeElement); + expect(html).toContain(':step:2: :step:3: :step:4:'); + for (const directive of ngMocks.findInstances(Mock1Directive)) { + if (isMockOf(directive, Mock1Directive, 'd')) { + directive.__render('mock'); + } + } + html = ngMocks.formatHtml(fixture.nativeElement); + expect(html).toContain( + ':step:2: rendered-tpl1-1-mock :step:3:
rendered-tpl1-2-mock
:step:4:', + ); + }); + + it('renders queries of components in the end', () => { + let html = ''; + const fixture = MockRender(TargetComponent); + + html = ngMocks.formatHtml(fixture.nativeElement); + expect(html).toContain(':step:1: :step:2:'); + const component = ngMocks.findInstance(MockComponent); + if (isMockOf(component, MockComponent, 'c')) { + component.__render('header'); + component.__render(['templates'], ':templates:'); + } + html = ngMocks.formatHtml(fixture.nativeElement); + + // component renders own queries in the end of own template. + expect(html).toContain(':step:1: :step:2:'); + expect(html).toContain( + ':step:9: ' + + '
rendered-header
' + + '
' + + 'rendered-tpl1-1-:templates:' + + '
rendered-tpl1-2-:templates:
' + + '
', + ); + }); + + it('renders all desired templates', () => { + let html = ''; + const fixture = MockRender(TargetComponent); + + const tplHeader = ngMocks.findTemplateRef('header'); + const tpl1tpl1 = ngMocks.findTemplateRef(['tpl1', 'tpl1']); + const tpl1tpl2 = ngMocks.findTemplateRef(['tpl1', 'tpl2']); + const tpl2tpl1 = ngMocks.findTemplateRef(['tpl2', 'tpl1']); + const tpl2tpl2 = ngMocks.findTemplateRef(['tpl2', 'tpl2']); + const tpl3 = ngMocks.findTemplateRef('info'); + expect(tplHeader).toEqual(jasmine.any(TemplateRef)); + expect(tpl1tpl1).toEqual(jasmine.any(TemplateRef)); + expect(tpl1tpl2).toEqual(jasmine.any(TemplateRef)); + expect(tpl2tpl1).toEqual(jasmine.any(TemplateRef)); + expect(tpl2tpl2).toEqual(jasmine.any(TemplateRef)); + expect(tpl3).toEqual(jasmine.any(TemplateRef)); + + // our render entrypoint component + const component = ngMocks.findInstance(MockComponent); + + // render tplHeader + html = ngMocks.formatHtml(fixture.nativeElement); + expect(html).toContain(':step:1: :step:2:'); + ngMocks.render(component, tplHeader); + html = ngMocks.formatHtml(fixture.nativeElement); + expect(html).toContain(':step:1: rendered-header :step:2:'); + + // render tpl1tpl1 + html = ngMocks.formatHtml(fixture.nativeElement); + expect(html).toContain(':step:2: :step:3:'); + ngMocks.render(component, tpl1tpl1, 'tpl1tpl1'); + html = ngMocks.formatHtml(fixture.nativeElement); + expect(html).toContain( + ':step:2: rendered-tpl1-1-tpl1tpl1 :step:3:', + ); + + // render tpl1tpl2 + html = ngMocks.formatHtml(fixture.nativeElement); + expect(html).toContain(':step:3: :step:4:'); + ngMocks.render(component, tpl1tpl2, 'tpl1tpl2'); + html = ngMocks.formatHtml(fixture.nativeElement); + expect(html).toContain( + ':step:3:
rendered-tpl1-2-tpl1tpl2
:step:4:', + ); + + // render tpl2tpl1 + html = ngMocks.formatHtml(fixture.nativeElement); + expect(html).toContain(':step:4: :step:5:'); + ngMocks.render(component, tpl2tpl1, 'tpl2tpl1'); + html = ngMocks.formatHtml(fixture.nativeElement); + expect(html).toContain( + ':step:4: rendered-tpl2-1-tpl2tpl1 :step:5:', + ); + + // render tpl2tpl2 + html = ngMocks.formatHtml(fixture.nativeElement); + expect(html).toContain(':step:5: :step:6:'); + ngMocks.render(component, tpl2tpl2, 'tpl2tpl2'); + html = ngMocks.formatHtml(fixture.nativeElement); + expect(html).toContain( + ':step:5:
rendered-tpl2-2-tpl2tpl2
:step:6:', + ); + + // render tpl3 + html = ngMocks.formatHtml(fixture.nativeElement); + expect(html).toContain(':step:7: :step:8:'); + ngMocks.render(component, tpl3, 'tpl3'); + html = ngMocks.formatHtml(fixture.nativeElement); + expect(html).toContain(':step:7: rendered-info-tpl3 :step:8:'); + + // update tpl1tpl1 as directive + const tpl1tpl1dir = ngMocks.findInstance(Mock1Directive); + ngMocks.render(tpl1tpl1dir, tpl1tpl1dir, 'tpl1tpl1dir'); + html = ngMocks.formatHtml(fixture.nativeElement); + expect(html).toContain( + ':step:2: rendered-tpl1-1-tpl1tpl1dir :step:3:', + ); + + // update tpl3 + ngMocks.render(component, tpl3, 'tpl3-updated'); + html = ngMocks.formatHtml(fixture.nativeElement); + expect(html).toContain( + ':step:7: rendered-info-tpl3-updated :step:8:', + ); + + // hide tplHeader + expect(html).not.toContain(':step:1: :step:2:'); + ngMocks.hide(component, tplHeader); + html = ngMocks.formatHtml(fixture.nativeElement); + expect(html).toContain(':step:1: :step:2:'); + + // hide tpl1tpl1 + expect(html).not.toContain(':step:2: :step:3:'); + ngMocks.hide(component, tpl1tpl1); + html = ngMocks.formatHtml(fixture.nativeElement); + expect(html).toContain(':step:2: :step:3:'); + + // hide tpl1tpl2 + const [, tpl1tpl2dir] = ngMocks.findInstances(Mock1Directive); + expect(html).not.toContain(':step:3: :step:4:'); + ngMocks.hide(tpl1tpl2dir); + html = ngMocks.formatHtml(fixture.nativeElement); + expect(html).toContain(':step:3: :step:4:'); + + // hide tpl2tpl1 + const [tpl2tpl1dir] = ngMocks.findInstances(Mock2Directive); + expect(html).not.toContain(':step:4: :step:5:'); + ngMocks.hide(tpl2tpl1dir); + html = ngMocks.formatHtml(fixture.nativeElement); + expect(html).toContain(':step:4: :step:5:'); + + // hide tpl2tpl2 + expect(html).not.toContain(':step:5: :step:6:'); + ngMocks.hide( + component, + ngMocks.findTemplateRef(['tpl2', 'tpl2']), + ); + html = ngMocks.formatHtml(fixture.nativeElement); + expect(html).toContain(':step:5: :step:6:'); + + // hide tpl3 + expect(html).not.toContain(':step:7: :step:8:'); + ngMocks.hide(component, tpl3); + html = ngMocks.formatHtml(fixture.nativeElement); + expect(html).toContain(':step:7: :step:8:'); + }); + + it('throws if not a mock instance has been passed', () => { + MockRender(TargetComponent); + + const tpl = ngMocks.findTemplateRef('header'); + expect(() => ngMocks.render({}, tpl)).toThrowError( + 'Only instances of mock declarations are accepted', + ); + }); + + it('throws if TemplateRef cannot be found on render request', () => { + MockRender(TargetComponent); + + const directive = ngMocks.findInstance(Mock3Directive); + const tpl = ngMocks.findTemplateRef('header'); + expect(() => ngMocks.render(directive, tpl)).toThrowError( + 'Cannot find path to the TemplateRef', + ); + }); + + it('throws if no template has been passed on render request', () => { + MockRender(TargetComponent); + + const directive = ngMocks.findInstance(Mock3Directive); + expect(() => + ngMocks.render(directive, undefined as any), + ).toThrowError( + 'Unknown template has been passed, only TemplateRef or a mock structural directive are supported', + ); + }); + + it('throws if TemplateRef cannot be found on hide request', () => { + MockRender(TargetComponent); + + const directive = ngMocks.findInstance(Mock3Directive); + const tpl = ngMocks.findTemplateRef('header'); + expect(() => ngMocks.hide(directive, tpl)).toThrowError( + 'Cannot find path to the TemplateRef', + ); + }); +}); diff --git a/tests/ng-mocks-render/directive.spec.ts b/tests/ng-mocks-render/directive.spec.ts new file mode 100644 index 0000000000..95d7216808 --- /dev/null +++ b/tests/ng-mocks-render/directive.spec.ts @@ -0,0 +1,159 @@ +import { CommonModule } from '@angular/common'; +import { + Component, + ContentChild, + ContentChildren, + Directive, + Input, + NgModule, + QueryList, + TemplateRef, +} from '@angular/core'; +import { MockBuilder, MockRender, ngMocks } from 'ng-mocks'; + +@Directive({ + selector: '[tpl]', +}) +class TplDirective { + @Input('tpl') public readonly name: string | null = null; + + public constructor(public readonly tpl: TemplateRef) {} +} + +@Directive({ + selector: '[mock]', +}) +class MockDirective { + @ContentChild(TplDirective, {} as any) + public readonly tpl?: TplDirective; +} + +@Component({ + selector: 'component', + template: ``, +}) +class MockComponent { + @ContentChildren(MockDirective, {} as any) + public readonly directives?: QueryList; + + @ContentChildren(TplDirective, { + read: TemplateRef, + } as any) + public readonly templates?: QueryList>; +} + +@Component({ + selector: 'target', + template: ` + + :step:1: + rendered-header + :step:2: +
+ :step:3: + rendered-body + :step:4: +
+ :step:5: +
+ `, +}) +class TargetComponent {} + +@NgModule({ + declarations: [ + TargetComponent, + MockComponent, + MockDirective, + TplDirective, + ], + imports: [CommonModule], +}) +class TargetModule {} + +describe('ng-mocks-render:directive', () => { + beforeEach(() => MockBuilder(TargetComponent, TargetModule)); + + it('renders directives in components', () => { + let html = ''; + const fixture = MockRender(TargetComponent); + const component = ngMocks.findInstance(MockComponent); + + html = ngMocks.formatHtml(fixture.nativeElement); + expect(html).toContain(':step:1: :step:2:'); + const directiveHeader = ngMocks.findInstance(TplDirective); + expect(directiveHeader.name).toEqual('header'); + ngMocks.render(component, directiveHeader); + html = ngMocks.formatHtml(fixture.nativeElement); + expect(html).toContain(':step:1: rendered-header :step:2:'); + + expect(html).toContain(':step:3: :step:4:'); + const directiveEl = ngMocks.find(MockDirective); + const directiveBody = ngMocks.findInstance( + directiveEl, + TplDirective, + ); + expect(directiveBody.name).toEqual('body'); + ngMocks.render(component, directiveBody); + html = ngMocks.formatHtml(fixture.nativeElement); + expect(html).toContain(':step:3: rendered-body :step:4:'); + + expect(html).toContain(':step:1: rendered-header :step:2:'); + ngMocks.hide(component, directiveHeader); + html = ngMocks.formatHtml(fixture.nativeElement); + expect(html).toContain(':step:1: :step:2:'); + + expect(html).toContain(':step:3: rendered-body :step:4:'); + ngMocks.hide(component, directiveBody); + html = ngMocks.formatHtml(fixture.nativeElement); + expect(html).toContain(':step:3: :step:4:'); + }); + + it('renders directives in directives', () => { + let html = ''; + const fixture = MockRender(TargetComponent); + const directive = ngMocks.findInstance(MockDirective); + + html = ngMocks.formatHtml(fixture.nativeElement); + expect(html).toContain(':step:3: :step:4:'); + const [, directiveBody] = ngMocks.findInstances(TplDirective); + expect(directiveBody.name).toEqual('body'); + ngMocks.render(directive, directiveBody); + html = ngMocks.formatHtml(fixture.nativeElement); + expect(html).toContain(':step:3: rendered-body :step:4:'); + + expect(html).toContain(':step:3: rendered-body :step:4:'); + ngMocks.hide(directive, directiveBody); + html = ngMocks.formatHtml(fixture.nativeElement); + expect(html).toContain(':step:3: :step:4:'); + }); + + it('renders self directives', () => { + let html = ''; + const fixture = MockRender(TargetComponent); + const [directiveHeader, directiveBody] = ngMocks.findInstances( + TplDirective, + ); + + html = ngMocks.formatHtml(fixture.nativeElement); + expect(html).toContain(':step:1: :step:2:'); + ngMocks.render(directiveHeader, directiveHeader); + html = ngMocks.formatHtml(fixture.nativeElement); + expect(html).toContain(':step:1: rendered-header :step:2:'); + + expect(html).toContain(':step:3: :step:4:'); + ngMocks.render(directiveBody, directiveBody); + html = ngMocks.formatHtml(fixture.nativeElement); + expect(html).toContain(':step:3: rendered-body :step:4:'); + + expect(html).toContain(':step:1: rendered-header :step:2:'); + ngMocks.hide(directiveHeader, directiveHeader); + html = ngMocks.formatHtml(fixture.nativeElement); + expect(html).toContain(':step:1: :step:2:'); + + expect(html).toContain(':step:3: rendered-body :step:4:'); + ngMocks.hide(directiveBody, directiveBody); + html = ngMocks.formatHtml(fixture.nativeElement); + expect(html).toContain(':step:3: :step:4:'); + }); +}); diff --git a/tests/ng-mocks-render/idea.spec.ts b/tests/ng-mocks-render/idea.spec.ts new file mode 100644 index 0000000000..c53ef15040 --- /dev/null +++ b/tests/ng-mocks-render/idea.spec.ts @@ -0,0 +1,114 @@ +import { + Component, + ContentChild, + ContentChildren, + Directive, + Input, + NgModule, + QueryList, + TemplateRef, + ViewContainerRef, +} from '@angular/core'; +import { MockBuilder, MockRender, ngMocks } from 'ng-mocks'; + +@Directive({ + selector: '[tpl]', +}) +class MockDirective { + @Input('tpl') public readonly name: string | null = null; + + public constructor(public readonly tpl: TemplateRef) {} +} + +@Component({ + selector: 'component', + template: ` `, +}) +class MockComponent { + @ContentChildren(MockDirective, {} as any) + public readonly directives?: QueryList; + + @ContentChildren(MockDirective, { + read: ViewContainerRef, + } as any) + public readonly directivesVcr?: QueryList; + + @ContentChild('header', { + read: TemplateRef, + } as any) + public readonly id?: TemplateRef; + + @ContentChild('header', { + read: ViewContainerRef, + } as any) + public readonly idVcr?: ViewContainerRef; +} + +@Component({ + selector: 'target', + template: ` + + :step:1: + rendered-header + :step:2: + tpl-header + :step:3: + tpl-footer + :step:4: + + `, +}) +class TargetComponent {} + +@NgModule({ + declarations: [MockDirective, MockComponent, TargetComponent], +}) +class TargetModule {} + +describe('ng-mocks-render:idea', () => { + beforeEach(() => MockBuilder(TargetComponent).keep(TargetModule)); + + it('renders ids properly', () => { + let html: any; + const fixture = MockRender(TargetComponent); + const component = ngMocks.findInstance(MockComponent); + + html = ngMocks.formatHtml(fixture.nativeElement); + expect(html).toContain(':step:1: :step:2:'); + + // The idea is that the template will be rendered on its place. + if (component.idVcr && component.id) { + component.idVcr.createEmbeddedView(component.id); + fixture.detectChanges(); + } + html = ngMocks.formatHtml(fixture.nativeElement); + expect(html).toContain(':step:1: rendered-header :step:2:'); + }); + + it('renders query lists properly', () => { + let html: any; + const fixture = MockRender(TargetComponent); + const component = ngMocks.findInstance(MockComponent); + + html = ngMocks.formatHtml(fixture.nativeElement); + expect(html).toContain(':step:2: :step:3: :step:4:'); + + // The idea is that the template will be rendered on its place. + if (component.directivesVcr && component.directives) { + const directivesVcr = component.directivesVcr.toArray(); + const directives = component.directives.toArray(); + for (let index = 0; index < directivesVcr.length; index += 1) { + const vcr = directivesVcr[index]; + const directive = directives[index]; + if (vcr && directive && directive.tpl) { + vcr.createEmbeddedView(directive.tpl); + } + } + fixture.detectChanges(); + } + html = ngMocks.formatHtml(fixture.nativeElement); + expect(html).toContain( + ':step:2: tpl-header :step:3: tpl-footer :step:4:', + ); + }); +}); diff --git a/tests/structural-directives/test.spec.ts b/tests/structural-directives/test.spec.ts index f1f934ed4f..1ff0311ee7 100644 --- a/tests/structural-directives/test.spec.ts +++ b/tests/structural-directives/test.spec.ts @@ -128,9 +128,9 @@ describe('structural-directive-as-ng-for:mock', () => { fixture.detectChanges(); // By default mock structural directives are rendered with undefined variables. - expect( - fixture.nativeElement.innerHTML.replace(/\s+/gm, ' '), - ).toContain(' $implicit: fromDirective: '); + expect(ngMocks.formatHtml(fixture)).toContain( + ' $implicit: fromDirective: ', + ); // Extracting mock. const debugElement = ngMocks.find(fixture.debugElement, 'div'); @@ -155,18 +155,18 @@ describe('structural-directive-as-ng-for:mock', () => { fromDirective: false, }); fixture.detectChanges(); - expect( - fixture.nativeElement.innerHTML.replace(/\s+/gm, ' '), - ).toContain(' $implicit:true fromDirective:false '); + expect(ngMocks.formatHtml(fixture.nativeElement)).toContain( + ' $implicit:true fromDirective:false ', + ); // And we want dynamically change variables for render. directive.__render(false, { fromDirective: true, }); fixture.detectChanges(); - expect( - fixture.nativeElement.innerHTML.replace(/\s+/gm, ' '), - ).toContain(' $implicit:false fromDirective:true '); + expect(ngMocks.formatHtml(fixture.nativeElement)).toContain( + ' $implicit:false fromDirective:true ', + ); }); it('mocks CustomNgForWithOfDirective properly', () => { @@ -195,9 +195,9 @@ describe('structural-directive-as-ng-for:mock', () => { fixture.detectChanges(); // By default mock structural directives are rendered with undefined variables. - expect( - fixture.nativeElement.innerHTML.replace(/\s+/gm, ' '), - ).toContain(' w/ 00 '); + expect(ngMocks.formatHtml(fixture.debugElement)).toContain( + ' w/ 00 ', + ); const debugElement = ngMocks.find(fixture.debugElement, 'div'); @@ -225,9 +225,9 @@ describe('structural-directive-as-ng-for:mock', () => { myLast: false, }); fixture.detectChanges(); - expect( - fixture.nativeElement.innerHTML.replace(/\s+/gm, ' '), - ).toContain(' w/ MainValueMyIndex10 '); + expect(ngMocks.formatHtml(fixture.nativeElement)).toContain( + ' w/ MainValueMyIndex10 ', + ); // And we want dynamically change variables for render. directive.__render('MainValue2', { @@ -237,7 +237,7 @@ describe('structural-directive-as-ng-for:mock', () => { }); fixture.detectChanges(); expect( - fixture.nativeElement.innerHTML.replace(/\s+/gm, ' '), + ngMocks.formatHtml(fixture.nativeElement.innerHTML), ).toContain(' w/ MainValue2MyIndex201 '); }); @@ -268,7 +268,7 @@ describe('structural-directive-as-ng-for:mock', () => { // By default mock structural directives are rendered with undefined variables. expect( - fixture.nativeElement.innerHTML.replace(/\s+/gm, ' '), + ngMocks.formatHtml(fixture.debugElement.nativeElement), ).toContain(' w/o 00 '); const debugElement = ngMocks.find(fixture.debugElement, 'div'); @@ -297,9 +297,9 @@ describe('structural-directive-as-ng-for:mock', () => { myLast: false, }); fixture.detectChanges(); - expect( - fixture.nativeElement.innerHTML.replace(/\s+/gm, ' '), - ).toContain(' w/o MainValueMyIndex10 '); + expect(ngMocks.formatHtml(fixture)).toContain( + ' w/o MainValueMyIndex10 ', + ); // And we want dynamically change variables for render. directive.__render('MainValue2', { @@ -308,9 +308,9 @@ describe('structural-directive-as-ng-for:mock', () => { myLast: true, }); fixture.detectChanges(); - expect( - fixture.nativeElement.innerHTML.replace(/\s+/gm, ' '), - ).toContain(' w/o MainValue2MyIndex201 '); + expect(ngMocks.formatHtml(fixture)).toContain( + ' w/o MainValue2MyIndex201 ', + ); }); it('searches for related directive', () => {