From efa63379ae157c72cdb41579bc109751b3a99b65 Mon Sep 17 00:00:00 2001 From: MG Date: Tue, 1 Dec 2020 17:33:37 +0100 Subject: [PATCH] feat: impure pipes support + `.get`, `.findInstance` can find them in fixtures closes #240 --- README.md | 8 +- lib/mock-helper/func.get-from-node-element.ts | 4 + .../func.get-from-node-injector.ts | 16 +++ .../func.get-from-node-ivy.spec.ts | 88 ++++++++++++ lib/mock-helper/func.get-from-node-ivy.ts | 39 +++++ lib/mock-helper/func.get-from-node-scan.ts | 60 ++++++++ .../func.get-from-node-standard.ts | 40 ++++++ lib/mock-helper/func.get-from-node.ts | 36 +++++ lib/mock-helper/mock-helper.find-instances.ts | 29 ++-- lib/mock-helper/mock-helper.get.ts | 19 +-- lib/mock-pipe/mock-pipe.ts | 2 +- package.release.json | 8 +- tests/issue-240/fixtures.ts | 31 ++++ tests/issue-240/test.builder.spec.ts | 33 +++++ tests/issue-240/test.guts.spec.ts | 38 +++++ tests/issue-240/test.nodes.spec.ts | 134 ++++++++++++++++++ tests/issue-240/test.real.spec.ts | 38 +++++ 17 files changed, 590 insertions(+), 33 deletions(-) create mode 100644 lib/mock-helper/func.get-from-node-element.ts create mode 100644 lib/mock-helper/func.get-from-node-injector.ts create mode 100644 lib/mock-helper/func.get-from-node-ivy.spec.ts create mode 100644 lib/mock-helper/func.get-from-node-ivy.ts create mode 100644 lib/mock-helper/func.get-from-node-scan.ts create mode 100644 lib/mock-helper/func.get-from-node-standard.ts create mode 100644 lib/mock-helper/func.get-from-node.ts create mode 100644 tests/issue-240/fixtures.ts create mode 100644 tests/issue-240/test.builder.spec.ts create mode 100644 tests/issue-240/test.guts.spec.ts create mode 100644 tests/issue-240/test.nodes.spec.ts create mode 100644 tests/issue-240/test.real.spec.ts diff --git a/README.md b/README.md index 953c1161a0..2e5cddab7e 100644 --- a/README.md +++ b/README.md @@ -2435,7 +2435,7 @@ const directive = ngMocks.get(fixture.debugElement, Directive); #### ngMocks.findInstance -Returns the first found attribute or structural directive which belongs to the current element or its any child. +Returns the first found component, directive, pipe or service which belongs to the current element or its any child. If the element isn't specified then the current fixture is used. - `ngMocks.findInstance( fixture?, directive, notFoundValue? )` @@ -2448,11 +2448,13 @@ const directive3 = ngMocks.findInstance( fixture.debugElement, Directive3, ); +const pipe = ngMocks.findInstance(fixture.debugElement, MyPipe); +const service = ngMocks.findInstance(fixture, MyService); ``` #### ngMocks.findInstances -Returns an array of all found attribute or structural directives which belong to the current element and all its children. +Returns an array of all found components, directives, pipes or services which belong to the current element and all its children. If the element isn't specified then the current fixture is used. - `ngMocks.findInstances( fixture?, directive )` @@ -2465,6 +2467,8 @@ const directives3 = ngMocks.findInstances( fixture.debugElement, Directive3, ); +const pipes = ngMocks.findInstances(fixture.debugElement, MyPipe); +const services = ngMocks.findInstance(fixture, MyService); ``` #### ngMocks.find diff --git a/lib/mock-helper/func.get-from-node-element.ts b/lib/mock-helper/func.get-from-node-element.ts new file mode 100644 index 0000000000..4c133f5e70 --- /dev/null +++ b/lib/mock-helper/func.get-from-node-element.ts @@ -0,0 +1,4 @@ +import { DebugNode } from '@angular/core'; + +export default (node: DebugNode): DebugNode => + node.nativeNode.nodeName === '#text' && node.parent ? node.parent : node; diff --git a/lib/mock-helper/func.get-from-node-injector.ts b/lib/mock-helper/func.get-from-node-injector.ts new file mode 100644 index 0000000000..ff1493a455 --- /dev/null +++ b/lib/mock-helper/func.get-from-node-injector.ts @@ -0,0 +1,16 @@ +import { DebugNode } from '@angular/core'; + +import { Type } from '../common/core.types'; + +import { Node } from './func.get-from-node'; + +export default (result: T[], node: DebugNode & Node, proto: Type): void => { + try { + const instance = node.injector.get(proto); + if (result.indexOf(instance) === -1) { + result.push(instance); + } + } catch (error) { + // nothing to do + } +}; diff --git a/lib/mock-helper/func.get-from-node-ivy.spec.ts b/lib/mock-helper/func.get-from-node-ivy.spec.ts new file mode 100644 index 0000000000..56513988f4 --- /dev/null +++ b/lib/mock-helper/func.get-from-node-ivy.spec.ts @@ -0,0 +1,88 @@ +import funcGetFromNodeIvy from './func.get-from-node-ivy'; + +describe('func.get-from-node-ivy', () => { + class Proto {} + + it('finds parent context', () => { + const result: any[] = []; + const proto = new Proto(); + const node: any = { + nativeNode: {}, + parent: { + nativeNode: { + __ngContext__: [proto], + }, + }, + }; + + funcGetFromNodeIvy(result, node, Proto); + + expect(result).toEqual([proto]); + }); + + it('handles lView context', () => { + const result: any[] = []; + const proto = new Proto(); + const node: any = { + nativeNode: {}, + parent: { + nativeNode: { + __ngContext__: { + lView: [proto], + }, + }, + }, + }; + + funcGetFromNodeIvy(result, node, Proto); + + expect(result).toEqual([proto]); + }); + + it('handles empty context', () => { + const result: any[] = []; + const node: any = { + nativeNode: {}, + parent: { + nativeNode: {}, + }, + }; + + funcGetFromNodeIvy(result, node, Proto); + + expect(result).toEqual([]); + }); + + it('scans nested arrays', () => { + const result: any[] = []; + const proto = new Proto(); + const node: any = { + nativeNode: {}, + parent: { + nativeNode: { + __ngContext__: [[[proto]]], + }, + }, + }; + + funcGetFromNodeIvy(result, node, Proto); + expect(result).toEqual([proto]); + }); + + it('skips node with _debugContext', () => { + const result: any[] = []; + const proto = new Proto(); + const node: any = { + _debugContext: {}, + nativeNode: {}, + parent: { + nativeNode: { + __ngContext__: [[[proto]]], + }, + }, + }; + + funcGetFromNodeIvy(result, node, Proto); + expect(result).toEqual([]); + }); +}); diff --git a/lib/mock-helper/func.get-from-node-ivy.ts b/lib/mock-helper/func.get-from-node-ivy.ts new file mode 100644 index 0000000000..ecb0846656 --- /dev/null +++ b/lib/mock-helper/func.get-from-node-ivy.ts @@ -0,0 +1,39 @@ +import { DebugNode } from '@angular/core'; + +import { Type } from '../common/core.types'; + +import { Node } from './func.get-from-node'; +import funcGetFromNodeElement from './func.get-from-node-element'; +import funcGetFromNodeScan from './func.get-from-node-scan'; + +const detectContext = (node: DebugNode): any => { + let current = node; + let context = current.nativeNode.__ngContext__; + while (!context && current.parent) { + current = current.parent; + context = current.nativeNode.__ngContext__; + } + + return context; +}; + +const contextToNodes = (context: any): any => (Array.isArray(context) ? context : context?.lView); + +export default (result: T[], node: DebugNode & Node, proto: Type): void => { + if (!node || node._debugContext) { + return; + } + + const el = funcGetFromNodeElement(node); + + funcGetFromNodeScan( + { + el, + nodes: contextToNodes(detectContext(node)) || [], + normalize: item => item, + proto, + result, + }, + true, + ); +}; diff --git a/lib/mock-helper/func.get-from-node-scan.ts b/lib/mock-helper/func.get-from-node-scan.ts new file mode 100644 index 0000000000..aa584f31d8 --- /dev/null +++ b/lib/mock-helper/func.get-from-node-scan.ts @@ -0,0 +1,60 @@ +import { DebugNode } from '@angular/core'; + +import { Type } from '../common/core.types'; + +const detectGatherFlag = (gather: boolean, el: DebugNode | null, node: any): boolean => { + if (!el || !node.nodeName) { + return gather; + } + + // checking if a textNode belongs to the current element. + if (node.nodeName === '#text') { + return node.parentNode === el.nativeNode; + } + + // checking if an injectedNode belongs to the current element. + return node === el.nativeNode; +}; + +const scan = ( + { + result, + el, + nodes, + normalize, + proto, + }: { + el: DebugNode | null; + nodes: any[]; + normalize: (item: any) => any; + proto: Type; + result: T[]; + }, + gatherDefault: boolean, + scanned: any[] = [], +): void => { + scanned.push(nodes); + let gather = gatherDefault; + + for (const raw of nodes) { + const node = normalize(raw); + if (!node || typeof node !== 'object') { + continue; + } + + if (scanned.indexOf(node) === -1 && Array.isArray(node)) { + scan({ result, el, nodes: node, normalize, proto }, gather, scanned); + } + + gather = detectGatherFlag(gather, el, node); + if (!gather) { + continue; + } + + if (result.indexOf(node) === -1 && node instanceof proto) { + result.push(node); + } + } +}; + +export default (() => scan)(); diff --git a/lib/mock-helper/func.get-from-node-standard.ts b/lib/mock-helper/func.get-from-node-standard.ts new file mode 100644 index 0000000000..3fe85a8311 --- /dev/null +++ b/lib/mock-helper/func.get-from-node-standard.ts @@ -0,0 +1,40 @@ +import { DebugNode } from '@angular/core'; + +import { Type } from '../common/core.types'; + +import { Node } from './func.get-from-node'; +import funcGetFromNodeElement from './func.get-from-node-element'; +import funcGetFromNodeScan from './func.get-from-node-scan'; + +const normalize = (item: any): any => { + if (!item || typeof item !== 'object') { + return item; + } + + for (const key of ['renderElement', 'renderText', 'instance']) { + if (item[key]) { + return item[key]; + } + } + + return null; +}; + +export default (result: T[], node: DebugNode & Node, proto: Type): void => { + if (!node || !node._debugContext) { + return; + } + + const el = funcGetFromNodeElement(node); + + funcGetFromNodeScan( + { + el, + nodes: node._debugContext.view.nodes, + normalize, + proto, + result, + }, + true, + ); +}; diff --git a/lib/mock-helper/func.get-from-node.ts b/lib/mock-helper/func.get-from-node.ts new file mode 100644 index 0000000000..58cc8042c2 --- /dev/null +++ b/lib/mock-helper/func.get-from-node.ts @@ -0,0 +1,36 @@ +import { DebugNode } from '@angular/core'; + +import { Type } from '../common/core.types'; + +import funcGetFromNodeInjector from './func.get-from-node-injector'; +import funcGetFromNodeIvy from './func.get-from-node-ivy'; +import funcGetFromNodeStandard from './func.get-from-node-standard'; + +export interface Node { + _debugContext?: { + elDef: { + nodeIndex: number; + }; + nodeDef: { + nodeIndex: number; + }; + nodeIndex: number; + view: { + nodes: Array<{ + instance?: any; + renderElement?: any; + renderText?: any; + value?: any; + }>; + }; + }; + parent?: (DebugNode & Node) | null; +} + +export default (result: T[], node: DebugNode & Node, proto: Type): T[] => { + funcGetFromNodeInjector(result, node, proto); + funcGetFromNodeStandard(result, node, proto); + funcGetFromNodeIvy(result, node, proto); + + return result; +}; diff --git a/lib/mock-helper/mock-helper.find-instances.ts b/lib/mock-helper/mock-helper.find-instances.ts index 839c2b7667..15b4559a52 100644 --- a/lib/mock-helper/mock-helper.find-instances.ts +++ b/lib/mock-helper/mock-helper.find-instances.ts @@ -2,21 +2,18 @@ import { Type } from '../common/core.types'; import { getSourceOfMock } from '../common/func.get-source-of-mock'; import { MockedDebugNode } from '../mock-render/types'; +import funcGetFromNode from './func.get-from-node'; import funcGetLastFixture from './func.get-last-fixture'; import funcParseFindArgs from './func.parse-find-args'; -function nestedCheck( - result: T[], - node: MockedDebugNode & { childNodes?: MockedDebugNode[] }, - callback: (node: MockedDebugNode) => undefined | T, -) { - const element = callback(node); - if (element) { - result.push(element); - } - const childNodes = node?.childNodes || []; - for (const childNode of childNodes) { - nestedCheck(result, childNode, callback); +interface DebugNode { + childNodes?: MockedDebugNode[]; +} + +function nestedCheck(result: T[], node: MockedDebugNode & DebugNode, proto: Type) { + funcGetFromNode(result, node, proto); + for (const childNode of node?.childNodes || []) { + nestedCheck(result, childNode, proto); } } @@ -25,13 +22,7 @@ export default (...args: any[]): T[] => { const debugElement = el || funcGetLastFixture()?.debugElement; const result: T[] = []; - nestedCheck(result, debugElement, node => { - try { - return node.injector.get(getSourceOfMock(sel)); - } catch (error) { - return undefined; - } - }); + nestedCheck(result, debugElement, getSourceOfMock(sel)); return result; }; diff --git a/lib/mock-helper/mock-helper.get.ts b/lib/mock-helper/mock-helper.get.ts index 56d0a3c0bf..4501b22680 100644 --- a/lib/mock-helper/mock-helper.get.ts +++ b/lib/mock-helper/mock-helper.get.ts @@ -2,6 +2,8 @@ import { Type } from '../common/core.types'; import { getSourceOfMock } from '../common/func.get-source-of-mock'; import { MockedDebugElement } from '../mock-render/types'; +import funcGetFromNode from './func.get-from-node'; + const defaultNotFoundValue = {}; // simulating Symbol const parseArgs = ( @@ -18,10 +20,10 @@ const parseArgs = ( export default (...args: any[]) => { const { el, sel, notFoundValue } = parseArgs(args); - try { - return el.injector.get(getSourceOfMock(sel)); - } catch (error) { - // looks like the directive is structural. + + const res1 = funcGetFromNode([], el, getSourceOfMock(sel)); + if (res1.length) { + return res1[0]; } // Looking for related structural directive. @@ -31,13 +33,12 @@ export default (...args: any[]) => { ? [] : el.parent.queryAllNodes(node => node.nativeNode === prevNode); const matchedNode = matches[0]; - try { - return matchedNode.injector.get(getSourceOfMock(sel)); - } catch (error) { - // nothing to do + const res2 = funcGetFromNode([], matchedNode, getSourceOfMock(sel)); + if (res2.length) { + return res2[0]; } if (notFoundValue !== defaultNotFoundValue) { return notFoundValue; } - throw new Error(`Cannot find ${sel.name} directive via ngMocks.get`); + throw new Error(`Cannot find ${sel.name} instance via ngMocks.get`); }; diff --git a/lib/mock-pipe/mock-pipe.ts b/lib/mock-pipe/mock-pipe.ts index 47ec1c0635..b42bbad8d7 100644 --- a/lib/mock-pipe/mock-pipe.ts +++ b/lib/mock-pipe/mock-pipe.ts @@ -20,7 +20,7 @@ export function MockPipes(...pipes: Array>): Array undefined; const getMockClass = (pipe: Type, transform: PipeTransform['transform']): Type => { - @Pipe({ name: coreReflectPipeResolve(pipe).name }) + @Pipe(coreReflectPipeResolve(pipe)) @MockOf(pipe) class PipeMock extends Mock implements PipeTransform { public constructor(@Optional() injector?: Injector) { diff --git a/package.release.json b/package.release.json index aa0dfcad2c..556c46e452 100644 --- a/package.release.json +++ b/package.release.json @@ -1,7 +1,7 @@ { "name": "ng-mocks", "version": "0.0.0", - "description": "An Angular 5+ library for creating mock components, directives, pipes, services, providers and modules in unit tests, which also includes shallow rendering and precise stubs to dump child dependencies.", + "description": "An Angular 5+ library for creating mock components, directives, pipes, services, providers and modules in unit tests, which also includes shallow rendering, precise stubs to dump child dependencies, and supports jasmine and jest.", "keywords": [ "angular", "test", @@ -17,7 +17,11 @@ "how-to", "frontend", "front-end", - "create a stub-dummy child dependency via stubbing dumb enzyme-render" + "jest", + "jasmine", + "enzyme", + "create a stub-dummy child dependency", + "stubbing via dumb render" ], "author": { "name": "Isaac Datlof", diff --git a/tests/issue-240/fixtures.ts b/tests/issue-240/fixtures.ts new file mode 100644 index 0000000000..37c0331d27 --- /dev/null +++ b/tests/issue-240/fixtures.ts @@ -0,0 +1,31 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ + name: 'pure', + pure: true, +}) +export class PurePipe implements PipeTransform { + public readonly name = 'PurePipe'; + public value: any; + + public transform(value: string): string { + this.value = value; + + return `${this.name}:${value}`; + } +} + +@Pipe({ + name: 'impure', + pure: false, +}) +export class ImpurePipe implements PipeTransform { + public readonly name = 'ImpurePipe'; + public value: any; + + public transform(value: string): string { + this.value = value; + + return `${this.name}:${value}`; + } +} diff --git a/tests/issue-240/test.builder.spec.ts b/tests/issue-240/test.builder.spec.ts new file mode 100644 index 0000000000..0546aa4564 --- /dev/null +++ b/tests/issue-240/test.builder.spec.ts @@ -0,0 +1,33 @@ +import { MockBuilder, MockRender, ngMocks } from 'ng-mocks'; + +import { ImpurePipe, PurePipe } from './fixtures'; + +describe('issue-240:builder', () => { + beforeEach(() => MockBuilder().mock(PurePipe).mock(ImpurePipe)); + + it('mocks impure pipes correctly', () => { + const fixture = MockRender( + ` + "pure:{{ "str" | pure }}" + "impure:{{ "str" | impure }}" + `, + ); + + const pure = ngMocks.findInstance(PurePipe); + const impure = ngMocks.get(fixture.point, ImpurePipe); + + spyOn(pure, 'transform'); + spyOn(impure, 'transform'); + + expect(pure.transform).toHaveBeenCalledTimes(0); + expect(impure.transform).toHaveBeenCalledTimes(0); + + fixture.detectChanges(); + expect(pure.transform).toHaveBeenCalledTimes(0); + expect(impure.transform).toHaveBeenCalledTimes(2); + + fixture.detectChanges(); + expect(pure.transform).toHaveBeenCalledTimes(0); + expect(impure.transform).toHaveBeenCalledTimes(4); + }); +}); diff --git a/tests/issue-240/test.guts.spec.ts b/tests/issue-240/test.guts.spec.ts new file mode 100644 index 0000000000..3716c790dd --- /dev/null +++ b/tests/issue-240/test.guts.spec.ts @@ -0,0 +1,38 @@ +import { TestBed } from '@angular/core/testing'; +import { MockRender, ngMocks } from 'ng-mocks'; + +import { ImpurePipe, PurePipe } from './fixtures'; + +describe('issue-240:guts', () => { + beforeEach(() => + TestBed.configureTestingModule( + ngMocks.guts(null, [PurePipe, ImpurePipe]), + ).compileComponents(), + ); + + it('mocks impure pipes correctly', () => { + const fixture = MockRender( + ` + "pure:{{ "str" | pure }}" + "impure:{{ "str" | impure }}" + `, + ); + + const pure = ngMocks.get(fixture.point, PurePipe); + const impure = ngMocks.findInstance(ImpurePipe); + + spyOn(pure, 'transform'); + spyOn(impure, 'transform'); + + expect(pure.transform).toHaveBeenCalledTimes(0); + expect(impure.transform).toHaveBeenCalledTimes(0); + + fixture.detectChanges(); + expect(pure.transform).toHaveBeenCalledTimes(0); + expect(impure.transform).toHaveBeenCalledTimes(2); + + fixture.detectChanges(); + expect(pure.transform).toHaveBeenCalledTimes(0); + expect(impure.transform).toHaveBeenCalledTimes(4); + }); +}); diff --git a/tests/issue-240/test.nodes.spec.ts b/tests/issue-240/test.nodes.spec.ts new file mode 100644 index 0000000000..0af49c3edc --- /dev/null +++ b/tests/issue-240/test.nodes.spec.ts @@ -0,0 +1,134 @@ +import { TestBed } from '@angular/core/testing'; +import { MockRender, ngMocks } from 'ng-mocks'; + +import { ImpurePipe } from './fixtures'; + +describe('issue-240:nodes', () => { + beforeEach(() => + TestBed.configureTestingModule({ + declarations: [ImpurePipe], + }).compileComponents(), + ); + + it('calls pipes differently', () => { + MockRender( + ` +
+ "parent-1:{{ "parent-1" | impure }}" +
+ + "child-1:{{ "child-1" | impure }}" +
+
+ "child-2:{{ "child-2" | impure }}" + +
+ text before + "child-3:{{ "child-3" | impure }}" +
+
+ + "parent-2:{{ "parent-2" | impure }}" +
+ + "parent-3:{{ "parent-3" | impure }}" + text after + +
+
+ `, + ); + + const parent = ngMocks.find('.parent'); + const child1 = ngMocks.find('.child-1'); + const child2 = ngMocks.find('.child-2'); + const child3 = ngMocks.find('.child-3'); + + expect(parent.nativeElement.innerHTML).toContain( + '"parent-1:ImpurePipe:parent-1"', + ); + expect(parent.nativeElement.innerHTML).toContain( + '"parent-2:ImpurePipe:parent-2"', + ); + expect(parent.nativeElement.innerHTML).toContain( + '"child-1:ImpurePipe:child-1"', + ); + expect(parent.nativeElement.innerHTML).toContain( + '"child-2:ImpurePipe:child-2"', + ); + expect(parent.nativeElement.innerHTML).toContain( + '"child-3:ImpurePipe:child-3"', + ); + + expect(child1.nativeElement.innerHTML).not.toContain( + '"parent-1:ImpurePipe:parent-1"', + ); + expect(child1.nativeElement.innerHTML).not.toContain( + '"parent-2:ImpurePipe:parent-2"', + ); + expect(child1.nativeElement.innerHTML).toContain( + '"child-1:ImpurePipe:child-1"', + ); + expect(child1.nativeElement.innerHTML).not.toContain( + '"child-2:ImpurePipe:child-2"', + ); + expect(child1.nativeElement.innerHTML).not.toContain( + '"child-3:ImpurePipe:child-3"', + ); + + expect(child2.nativeElement.innerHTML).not.toContain( + '"parent-1:ImpurePipe:parent-1"', + ); + expect(child2.nativeElement.innerHTML).not.toContain( + '"parent-2:ImpurePipe:parent-2"', + ); + expect(child2.nativeElement.innerHTML).not.toContain( + '"child-1:ImpurePipe:child-1"', + ); + expect(child2.nativeElement.innerHTML).toContain( + '"child-2:ImpurePipe:child-2"', + ); + expect(child2.nativeElement.innerHTML).toContain( + '"child-3:ImpurePipe:child-3"', + ); + + expect(child3.nativeElement.innerHTML).not.toContain( + '"parent-1:ImpurePipe:parent-1"', + ); + expect(child3.nativeElement.innerHTML).not.toContain( + '"parent-2:ImpurePipe:parent-2"', + ); + expect(child3.nativeElement.innerHTML).not.toContain( + '"child-1:ImpurePipe:child-1"', + ); + expect(child3.nativeElement.innerHTML).not.toContain( + '"child-2:ImpurePipe:child-2"', + ); + expect(child3.nativeElement.innerHTML).toContain( + '"child-3:ImpurePipe:child-3"', + ); + + const parentPipes = ngMocks.findInstances(parent, ImpurePipe); + expect(parentPipes.map(item => item.value)).toEqual([ + // all in the root node first + 'parent-1', + 'parent-2', + 'parent-3', + 'child-1', + 'child-2', + 'child-3', + ]); + + const child1Pipes = ngMocks.findInstances(child1, ImpurePipe); + expect(child1Pipes.map(item => item.value)).toEqual(['child-1']); + + const child2Pipes = ngMocks.findInstances(child2, ImpurePipe); + expect(child2Pipes.map(item => item.value)).toEqual([ + 'child-2', + 'child-3', + ]); + + const child3Pipes = ngMocks.findInstances(child3, ImpurePipe); + expect(child3Pipes.map(item => item.value)).toEqual(['child-3']); + }); +}); diff --git a/tests/issue-240/test.real.spec.ts b/tests/issue-240/test.real.spec.ts new file mode 100644 index 0000000000..3e433fc7da --- /dev/null +++ b/tests/issue-240/test.real.spec.ts @@ -0,0 +1,38 @@ +import { TestBed } from '@angular/core/testing'; +import { MockRender, ngMocks } from 'ng-mocks'; + +import { ImpurePipe, PurePipe } from './fixtures'; + +describe('issue-240:real', () => { + beforeEach(() => + TestBed.configureTestingModule({ + declarations: [PurePipe, ImpurePipe], + }).compileComponents(), + ); + + it('calls pipes differently', () => { + const fixture = MockRender( + ` + "pure:{{ "str" | pure }}" + "impure:{{ "str" | impure }}" + `, + ); + + const pure = ngMocks.findInstance(PurePipe); + const impure = ngMocks.findInstance(ImpurePipe); + + spyOn(pure, 'transform'); + spyOn(impure, 'transform'); + + expect(pure.transform).toHaveBeenCalledTimes(0); + expect(impure.transform).toHaveBeenCalledTimes(0); + + fixture.detectChanges(); + expect(pure.transform).toHaveBeenCalledTimes(0); + expect(impure.transform).toHaveBeenCalledTimes(2); + + fixture.detectChanges(); + expect(pure.transform).toHaveBeenCalledTimes(0); + expect(impure.transform).toHaveBeenCalledTimes(4); + }); +});