diff --git a/libs/ng-mocks/src/lib/mock-helper/crawl/mock-helper.crawl.ts b/libs/ng-mocks/src/lib/mock-helper/crawl/mock-helper.crawl.ts index 3feca7605a..ad71309a6a 100644 --- a/libs/ng-mocks/src/lib/mock-helper/crawl/mock-helper.crawl.ts +++ b/libs/ng-mocks/src/lib/mock-helper/crawl/mock-helper.crawl.ts @@ -8,7 +8,7 @@ import nestedCheck from './nested-check'; export default ( sel: DebugNode | DebugNodeSelector, - callback: (node: DebugNode) => void | boolean, + callback: (node: DebugNode, parent?: DebugNode) => void | boolean, includeTextNode = false, ): void => { const el = mockHelperFind(funcGetLastFixture(), sel, undefined); diff --git a/libs/ng-mocks/src/lib/mock-helper/find-instance/mock-helper.find-instance.ts b/libs/ng-mocks/src/lib/mock-helper/find-instance/mock-helper.find-instance.ts index 1720b80f84..7e748e6ca1 100644 --- a/libs/ng-mocks/src/lib/mock-helper/find-instance/mock-helper.find-instance.ts +++ b/libs/ng-mocks/src/lib/mock-helper/find-instance/mock-helper.find-instance.ts @@ -21,8 +21,11 @@ export default (...args: any[]) => { const result: any[] = []; mockHelperCrawl( mockHelperFind(funcGetLastFixture(), el, undefined), - node => { + (node, parent) => { funcGetFromNode(result, node, declaration); + if (result.length === 0 && parent && parent.nativeNode.nodeName === '#comment') { + funcGetFromNode(result, parent, declaration); + } return result.length > 0; }, diff --git a/libs/ng-mocks/src/lib/mock-helper/find-instance/mock-helper.find-instances.ts b/libs/ng-mocks/src/lib/mock-helper/find-instance/mock-helper.find-instances.ts index 1e6ea2a42d..bed715fcc4 100644 --- a/libs/ng-mocks/src/lib/mock-helper/find-instance/mock-helper.find-instances.ts +++ b/libs/ng-mocks/src/lib/mock-helper/find-instance/mock-helper.find-instances.ts @@ -21,11 +21,15 @@ export default (...args: any[]): T[] => { for (const element of elements) { mockHelperCrawl( element, - node => { + (node, parent) => { if (scanned.indexOf(node) === -1) { funcGetFromNode(result, node, declaration); scanned.push(node); } + if (parent && parent.nativeNode.nodeName === '#comment' && scanned.indexOf(parent) === -1) { + funcGetFromNode(result, parent, declaration); + scanned.push(parent); + } }, true, ); diff --git a/libs/ng-mocks/src/lib/mock-helper/func.get-from-node-scan.ts b/libs/ng-mocks/src/lib/mock-helper/func.get-from-node-scan.ts index 57830aa492..8e41138d99 100644 --- a/libs/ng-mocks/src/lib/mock-helper/func.get-from-node-scan.ts +++ b/libs/ng-mocks/src/lib/mock-helper/func.get-from-node-scan.ts @@ -3,6 +3,17 @@ import { DebugNode } from '@angular/core'; import { Type } from '../common/core.types'; const detectGatherFlag = (gather: boolean, el: DebugNode | null, node: any): boolean => { + // LContainer for structural directives can be a trigger for pipes. + if ( + el && + el.nativeNode && + el.nativeNode.nodeName === '#comment' && + Array.isArray(node) && + node[0] === el.nativeNode + ) { + return true; + } + // LContainer should stop the scan. if (Array.isArray(node)) { return false; @@ -44,8 +55,13 @@ const scan = ( scanned.push(nodes); let gather = gatherDefault; - for (const raw of nodes) { - const node = normalize(raw); + let nodesLength = nodes.length; + if (nodes.length > 1 && nodes[1] && typeof nodes[1] === 'object' && nodes[1].bindingStartIndex) { + nodesLength = nodes[1].bindingStartIndex; + } + + for (let index = 0; index < nodesLength; index += 1) { + const node = normalize(nodes[index]); if (isNotObject(node)) { continue; } diff --git a/tests/issue-2314/test.spec.ts b/tests/issue-2314/test.spec.ts new file mode 100644 index 0000000000..d004fad585 --- /dev/null +++ b/tests/issue-2314/test.spec.ts @@ -0,0 +1,136 @@ +import { AsyncPipe, NgIf } from '@angular/common'; +import { Component, Pipe, PipeTransform } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { BehaviorSubject } from 'rxjs'; + +import { ngMocks } from 'ng-mocks'; + +@Pipe({ + name: 'nothing', + pure: false, +}) +class NothingPipe implements PipeTransform { + transform(value: T): T { + return value; + } +} + +@Component({ + selector: 'target', + template: ` +
+ item: {{ item }} +
+
+ false +
+
+ {{ text$ | nothing | async | nothing | nothing }} +
+
+ true +
+ `, +}) +export class TargetComponent { + public array$ = new BehaviorSubject([1]); + public false$ = new BehaviorSubject(false); + public text$ = new BehaviorSubject('text'); + public true$ = new BehaviorSubject(true); +} + +// @see https://github.com/ike18t/ng-mocks/issues/2314 +describe('issue-2314', () => { + let fixture: ComponentFixture; + + beforeEach(async () => { + return TestBed.configureTestingModule({ + declarations: [TargetComponent, NothingPipe], + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(TargetComponent); + fixture.detectChanges(); + }); + + it(`finds all pipes`, () => { + const allPipes = ngMocks.findInstances(AsyncPipe); + expect(allPipes.length).toBe(4); + }); + + describe('ngMocks.findInstance', () => { + it(`finds pipes on '.array'`, () => { + const arrayPipe = ngMocks.findInstance( + '.array', + AsyncPipe, + undefined, + ); + expect(arrayPipe).toBeDefined(); + }); + + it(`finds pipes on '.false'`, () => { + // Because it isn't rendered, we cannot find the element with `.false`, therefore, we need to rely on NgIf itself. + const ngIf = ngMocks.reveal(NgIf); + const falsePipe = ngMocks.findInstance( + ngIf, + AsyncPipe, + undefined, + ); + expect(falsePipe).toBeDefined(); + }); + + it(`find pipes on '.text'`, () => { + const textPipe = ngMocks.findInstance( + '.text', + AsyncPipe, + undefined, + ); + expect(textPipe).toBeDefined(); + }); + + it(`should find pipe on '.true'`, () => { + const truePipe = ngMocks.findInstance( + '.true', + AsyncPipe, + undefined, + ); + expect(truePipe).toBeDefined(); + }); + }); + + describe('ngMocks.findInstances', () => { + it(`finds pipes on '.array'`, () => { + const arrayPipe = ngMocks.findInstances('.array', AsyncPipe); + expect(arrayPipe.length).toEqual(1); + }); + + it(`finds pipes on '.false'`, () => { + // Because it isn't rendered, we cannot find the element with `.false`, therefore, we need to rely on NgIf itself. + const ngIf = ngMocks.reveal(NgIf); + const falsePipe = ngMocks.findInstances(ngIf, AsyncPipe); + expect(falsePipe.length).toEqual(1); + }); + + it(`find pipes on '.text'`, () => { + const textPipe = ngMocks.findInstances('.text', AsyncPipe); + expect(textPipe.length).toEqual(1); + }); + + it(`should find pipe on '.true'`, () => { + const truePipe = ngMocks.findInstances('.true', AsyncPipe); + expect(truePipe.length).toEqual(1); + }); + }); +});