Skip to content

Commit

Permalink
feat: respect of virtual dom with ngMocks.crawl
Browse files Browse the repository at this point in the history
closes #289
  • Loading branch information
satanTime committed Feb 21, 2021
1 parent 71390b2 commit 030b29f
Show file tree
Hide file tree
Showing 25 changed files with 775 additions and 214 deletions.

This file was deleted.

15 changes: 1 addition & 14 deletions libs/ng-mocks/src/lib/mock-helper/crawl/crawl-by-declaration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,5 @@ import { MockedDebugNode } from '../../mock-render/types';
export default (declaration: AnyType<any>): ((node: MockedDebugNode) => boolean) => {
const source = getSourceOfMock(declaration);

return node => {
try {
if (node.providerTokens.indexOf(source) === -1) {
return false;
}
node.injector.get(source);

return true;
} catch (e) {
// nothing to do.
}

return false;
};
return node => !!node && node.providerTokens.indexOf(source) !== -1 && !!node.injector.get(source, null);
};
7 changes: 7 additions & 0 deletions libs/ng-mocks/src/lib/mock-helper/crawl/el-def-compare.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default (a: any, b: any): boolean => {
if (!a || !b) {
return false;
}

return a === b;
};
10 changes: 10 additions & 0 deletions libs/ng-mocks/src/lib/mock-helper/crawl/el-def-get-node.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import detectTextNode from './detect-text-node';

export default (node: any) => {
return detectTextNode(node)
? undefined
: (undefined as any) ||
node.injector._tNode || // ivy
node.injector.elDef || // classic
undefined;
};
51 changes: 51 additions & 0 deletions libs/ng-mocks/src/lib/mock-helper/crawl/el-def-get-parent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { DebugElement, ViewContainerRef } from '@angular/core';

import elDefGetNode from './el-def-get-node';

const getVcr = (node: any, child: any): undefined | ViewContainerRef => {
if (node === child) {
return undefined;
}
if (child.nativeNode.nodeName !== '#comment') {
return undefined;
}

return child.injector.get(ViewContainerRef, null) || undefined;
};

const scanViewRef = (node: DebugElement) => {
let result: any;
let index: any;

for (const child of node.parent?.childNodes || []) {
const vcr = getVcr(node, child);
if (!vcr) {
continue;
}

for (let vrIndex = 0; vrIndex < vcr.length; vrIndex += 1) {
const vr = vcr.get(vrIndex);
for (let rnIndex = 0; rnIndex < (vr as any).rootNodes.length; rnIndex += 1) {
const rootNode = (vr as any).rootNodes[rnIndex];
if (rootNode === node.nativeNode && (index === undefined || rnIndex < index)) {
result = elDefGetNode(child);
index = rnIndex;
}
}
}
}

return result;
};

export default (node: any) => {
return (
(undefined as any) ||
node.injector._tNode?.parent || // ivy
node.injector.elDef?.parent || // classic
scanViewRef(node) ||
node.parent?.injector._tNode || // ivy
node.parent?.injector.elDef || // classic
undefined
);
};
36 changes: 7 additions & 29 deletions libs/ng-mocks/src/lib/mock-helper/crawl/mock-helper.crawl.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,11 @@
import funcParseFindArgs from '../func.parse-find-args';
import funcParseFindArgsName from '../func.parse-find-args-name';
import { DebugNode } from '@angular/core';

import detectCrawler from './detect-crawler';
import detectTextNode from './detect-text-node';
import nestedCheck from './nested-check';

const defaultNotFoundValue = {}; // simulating Symbol

export default (...args: any[]): any => {
const { el, sel, notFoundValue } = funcParseFindArgs(args, defaultNotFoundValue);

const detector = detectCrawler(sel);

let result;
nestedCheck(el, node => {
if (!detectTextNode(node) && detector(node)) {
result = node;

return true;
}

return false;
});
if (result) {
return result;
}
if (notFoundValue !== defaultNotFoundValue) {
return notFoundValue;
}

throw new Error(`Cannot find a DebugElement via ngMocks.reveal(${funcParseFindArgsName(sel)})`);
export default (
el: DebugNode | undefined,
callback: (node: DebugNode) => void | boolean,
includeTextNode = false,
): void => {
nestedCheck(el, undefined, callback, includeTextNode);
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@ import funcParseFindArgs from '../func.parse-find-args';

import detectCrawler from './detect-crawler';
import detectTextNode from './detect-text-node';
import nestedCheck from './nested-check';
import mockHelperCrawl from './mock-helper.crawl';

export default (...args: any[]): any[] => {
const { el, sel } = funcParseFindArgs(args);

const detector = detectCrawler(sel);

const result: any[] = [];
nestedCheck(el, node => {
if (!detectTextNode(node) && detector(node)) {
mockHelperCrawl(el, node => {
if (node !== el && !detectTextNode(node) && detector(node)) {
result.push(node);
}
});
Expand Down
33 changes: 33 additions & 0 deletions libs/ng-mocks/src/lib/mock-helper/crawl/mock-helper.reveal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import funcParseFindArgs from '../func.parse-find-args';
import funcParseFindArgsName from '../func.parse-find-args-name';

import detectCrawler from './detect-crawler';
import detectTextNode from './detect-text-node';
import mockHelperCrawl from './mock-helper.crawl';

const defaultNotFoundValue = {}; // simulating Symbol

export default (...args: any[]): any => {
const { el, sel, notFoundValue } = funcParseFindArgs(args, defaultNotFoundValue);

const detector = detectCrawler(sel);

let result;
mockHelperCrawl(el, node => {
if (node !== el && !detectTextNode(node) && detector(node)) {
result = node;

return true;
}

return false;
});
if (result) {
return result;
}
if (notFoundValue !== defaultNotFoundValue) {
return notFoundValue;
}

throw new Error(`Cannot find a DebugElement via ngMocks.reveal(${funcParseFindArgsName(sel)})`);
};
30 changes: 30 additions & 0 deletions libs/ng-mocks/src/lib/mock-helper/crawl/nested-check-children.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { MockedDebugNode } from '../../mock-render/types';

import detectTextNode from './detect-text-node';
import elDefCompare from './el-def-compare';
import elDefGetNode from './el-def-get-node';
import elDefGetParent from './el-def-get-parent';

export default (node: MockedDebugNode): MockedDebugNode[] => {
const elDef = elDefGetNode(node);
if (!elDef || detectTextNode(node)) {
return [];
}

const isDirect = (node as any).childNodes !== undefined;
const children: MockedDebugNode[] = [];
for (const childNode of (node as any).childNodes || node.parent?.childNodes || []) {
const childNodeParent = elDefGetParent(childNode);

if (!isDirect && !elDefCompare(elDef, childNodeParent)) {
continue;
}
if (childNodeParent && !elDefCompare(elDef, childNodeParent)) {
continue;
}

children.push(childNode);
}

return children;
};
4 changes: 2 additions & 2 deletions libs/ng-mocks/src/lib/mock-helper/crawl/nested-check.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ describe('nested-check', () => {
};
const check = jasmine.createSpy();

nestedCheck(node, check);
nestedCheck(node, undefined, check);
});

it('handles missed parent def', () => {
Expand All @@ -23,6 +23,6 @@ describe('nested-check', () => {
};
const check = jasmine.createSpy();

nestedCheck(node, check);
nestedCheck(node, undefined, check);
});
});
73 changes: 34 additions & 39 deletions libs/ng-mocks/src/lib/mock-helper/crawl/nested-check.ts
Original file line number Diff line number Diff line change
@@ -1,54 +1,49 @@
import { DebugElement } from '@angular/core';

import { MockedDebugNode } from '../../mock-render/types';

import detectTextNode from './detect-text-node';
import elDefCompare from './el-def-compare';
import elDefGetNode from './el-def-get-node';
import elDefGetParent from './el-def-get-parent';
import nestedCheckChildren from './nested-check-children';

const detectParent = (node: MockedDebugNode, parent: MockedDebugNode | undefined): MockedDebugNode | undefined => {
if (parent) {
return parent;
}

interface ElDef {
nodeIndex: number;
parent: null | ElDef;
}

interface DebugNode {
childNodes?: DebugNode[];
injector?: {
elDef?: ElDef;
};
parent: null | DebugNode;
}

const isDebugNode = (value: unknown): value is DebugElement & DebugNode => {
return !!value && typeof value === 'object';
};

// normal and ivy
const getNodeElDef = (node: any) => {
return node.injector.elDef || node.injector._tNode || undefined;
};

const getParentElDef = (node: any) => {
return detectTextNode(node) ? undefined : node.injector.elDef?.parent || node.injector._tNode?.parent;
};
const expected = elDefGetParent(node);
const currentParent = node.parent ? elDefGetNode(node.parent) : undefined;
if (node.parent && elDefCompare(expected, currentParent)) {
return node.parent;
}
for (const childNode of node.parent?.childNodes || []) {
const childElDef = elDefGetNode(childNode);
if (elDefCompare(expected, childElDef)) {
return childNode;
}
}

const getElDef = (node: any): [any, any] => {
return [getNodeElDef(node), getParentElDef(node)];
return undefined;
};

const nestedCheck = (node: MockedDebugNode | undefined, check: (node: MockedDebugNode) => void | boolean): boolean => {
if (!isDebugNode(node)) {
const nestedCheck = (
node: MockedDebugNode | undefined,
parent: MockedDebugNode | undefined,
check: (node: MockedDebugNode, parent?: MockedDebugNode) => void | boolean,
includeTextNode = false,
): boolean => {
if (!node) {
return false;
}
if (check(node)) {
if (!includeTextNode && detectTextNode(node)) {
return false;
}
if (check(node, detectParent(node, parent))) {
return true;
}

const [elDef, elDefParent] = getElDef(node);
for (const childNode of node.childNodes || (elDefParent && node.parent?.childNodes) || []) {
const childNodeParent = getParentElDef(childNode);
if (childNodeParent && childNodeParent !== elDef) {
continue;
}
if (nestedCheck(childNode, check)) {
for (const childNode of nestedCheckChildren(node)) {
if (nestedCheck(childNode, node, check, includeTextNode)) {
return true;
}
}
Expand Down
10 changes: 3 additions & 7 deletions libs/ng-mocks/src/lib/mock-helper/func.get-from-node-injector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,8 @@ export default <T>(result: T[], node: (DebugNode & Node) | null | undefined, pro
return;
}

try {
const instance = node.injector.get(proto);
if (result.indexOf(instance) === -1) {
result.push(instance);
}
} catch (error) {
// nothing to do
const instance = node.injector.get(proto, null);
if (instance && result.indexOf(instance) === -1) {
result.push(instance);
}
};
20 changes: 18 additions & 2 deletions libs/ng-mocks/src/lib/mock-helper/mock-helper.find-instance.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,29 @@
import { getSourceOfMock } from '../common/func.get-source-of-mock';

import mockHelperCrawl from './crawl/mock-helper.crawl';
import funcGetFromNode from './func.get-from-node';
import funcParseFindArgs from './func.parse-find-args';
import funcParseFindArgsName from './func.parse-find-args-name';
import mockHelperFindInstances from './mock-helper.find-instances';

const defaultNotFoundValue = {}; // simulating Symbol

export default (...args: any[]) => {
const { el, sel, notFoundValue } = funcParseFindArgs(args, defaultNotFoundValue);
if (typeof sel !== 'function') {
throw new Error('Only classes are accepted');
}

const declaration = getSourceOfMock(sel);
const result: any[] = [];
mockHelperCrawl(
el,
node => {
funcGetFromNode(result, node, declaration);

const result = mockHelperFindInstances(el, sel);
return result.length > 0;
},
true,
);
if (result.length) {
return result[0];
}
Expand Down
Loading

0 comments on commit 030b29f

Please sign in to comment.