Skip to content

Commit

Permalink
feat: find ng-container via ngMocks.reveal and revealAll
Browse files Browse the repository at this point in the history
closes #289
  • Loading branch information
satanTime committed Feb 18, 2021
1 parent 86a7e59 commit f69c21f
Show file tree
Hide file tree
Showing 31 changed files with 1,210 additions and 176 deletions.
12 changes: 6 additions & 6 deletions libs/ng-mocks/src/lib/common/func.get-source-of-mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,42 +3,42 @@ import { MockedDirective } from '../mock-directive/types';
import { MockedModule } from '../mock-module/types';
import { MockedPipe } from '../mock-pipe/types';

import { Type } from './core.types';
import { AnyType, Type } from './core.types';

/**
* Returns an original type.
*
* @see https://ng-mocks.sudo.eu/api/helpers/getSourceOfMock
*/
export function getSourceOfMock<T>(declaration: Type<MockedModule<T>>): Type<T>;
export function getSourceOfMock<T>(declaration: AnyType<MockedModule<T>>): Type<T>;

/**
* Returns an original type.
*
* @see https://ng-mocks.sudo.eu/api/helpers/getSourceOfMock
*/
export function getSourceOfMock<T>(declaration: Type<MockedComponent<T>>): Type<T>;
export function getSourceOfMock<T>(declaration: AnyType<MockedComponent<T>>): Type<T>;

/**
* Returns an original type.
*
* @see https://ng-mocks.sudo.eu/api/helpers/getSourceOfMock
*/
export function getSourceOfMock<T>(declaration: Type<MockedDirective<T>>): Type<T>;
export function getSourceOfMock<T>(declaration: AnyType<MockedDirective<T>>): Type<T>;

/**
* Returns an original type.
*
* @see https://ng-mocks.sudo.eu/api/helpers/getSourceOfMock
*/
export function getSourceOfMock<T>(declaration: Type<MockedPipe<T>>): Type<T>;
export function getSourceOfMock<T>(declaration: AnyType<MockedPipe<T>>): Type<T>;

/**
* Returns an original type.
*
* @see https://ng-mocks.sudo.eu/api/helpers/getSourceOfMock
*/
export function getSourceOfMock<T>(declaration: Type<T>): Type<T>;
export function getSourceOfMock<T>(declaration: AnyType<T>): Type<T>;

/**
* Returns an original type.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { MockedDebugNode } from '../../mock-render/types';

import funcGetPublicProviderKeys from './func.get-public-provider-keys';
import funcParseInputsAndRequiresAttributes from './func.parse-inputs-and-requires-attributes';

const detectInClassic = (node: MockedDebugNode, attribute: string, value: any): boolean => {
for (const key of funcGetPublicProviderKeys(node)) {
const [inputs, expectedAttributes, nodeIndex] = funcParseInputsAndRequiresAttributes(node, key);
for (const input of inputs || []) {
const [prop, alias] = input.split(': ');
if (attribute !== (alias || prop) || expectedAttributes.indexOf(prop) === -1) {
continue;
}
if (value === (node.injector as any).view.nodes[nodeIndex].instance[prop]) {
return true;
}
}
}

return false;
};

const detectInIvy = (node: MockedDebugNode, attribute: string, value: any): boolean => {
const attrs = (node.injector as any)._tNode?.attrs || [];
let step = 2;
for (let index = 0; index < attrs.length; index += step) {
// 3 is a divider between static and dynamic bindings
if (typeof attrs[index] === 'number') {
step = 1;
continue;
}
const attr = attrs[index];
if (attr !== attribute || !(node.injector as any)._tNode.inputs[attr]) {
continue;
}
const [attrIndex, attrProp] = (node.injector as any)._tNode.inputs[attr];

if (value === (node.injector as any)._lView[attrIndex][attrProp]) {
return true;
}
}

return false;
};

export default (attribute: string, value: any): ((node: MockedDebugNode) => boolean) => node => {
if (detectInClassic(node, attribute, value)) {
return true;
}

return detectInIvy(node, attribute, value);
};
18 changes: 18 additions & 0 deletions libs/ng-mocks/src/lib/mock-helper/crawl/crawl-by-attribute.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { MockedDebugNode } from '../../mock-render/types';

import detectAttributeInSelectors from './detect-attribute-in-selectors';
import detectSelectorsFromNode from './detect-selectors-from-node';

export default (attribute: string): ((node: MockedDebugNode) => boolean) => node => {
const [selectors, attributes] = detectSelectorsFromNode(node);

if (attributes.indexOf(attribute) !== -1) {
return true;
}

if (detectAttributeInSelectors(selectors, attribute)) {
return true;
}

return false;
};
22 changes: 22 additions & 0 deletions libs/ng-mocks/src/lib/mock-helper/crawl/crawl-by-declaration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { AnyType } from '../../common/core.types';
import { getSourceOfMock } from '../../common/func.get-source-of-mock';
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;
};
};
5 changes: 5 additions & 0 deletions libs/ng-mocks/src/lib/mock-helper/crawl/crawl-by-id.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { MockedDebugNode } from '../../mock-render/types';

export default (id: string): ((node: MockedDebugNode) => boolean) => node => {
return !!node.references[id];
};
10 changes: 10 additions & 0 deletions libs/ng-mocks/src/lib/mock-helper/crawl/crawl-by-tag-name.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { MockedDebugNode } from '../../mock-render/types';

import detectSelectorsFromNode from './detect-selectors-from-node';
import detectTagNameInSelectors from './detect-tag-name-in-selectors';

export default (attribute: string): ((node: MockedDebugNode) => boolean) => node => {
const [selectors] = detectSelectorsFromNode(node);

return detectTagNameInSelectors(selectors, attribute);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export default (selectors: string[], query: string): boolean => {
for (const selector of selectors) {
const attributes = selector.match(/\[([^\]=]+)/g);
if (!attributes) {
continue;
}

for (const attribute of attributes) {
if (attribute === `[${query}`) {
return true;
}
}
}

return false;
};
50 changes: 50 additions & 0 deletions libs/ng-mocks/src/lib/mock-helper/crawl/detect-crawler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { AnyType } from '../../common/core.types';
import { MockedDebugNode } from '../../mock-render/types';

import crawlByAttribute from './crawl-by-attribute';
import crawlByAttributeValue from './crawl-by-attribute-value';
import crawlByDeclaration from './crawl-by-declaration';
import crawlById from './crawl-by-id';
import crawlByTagName from './crawl-by-tag-name';

type SELECTOR = string | AnyType<any> | [any] | [any, any];

const isCrawlByAttribute = (selector: SELECTOR): selector is [string] => {
return Array.isArray(selector) && selector.length === 1 && typeof selector[0] === 'string';
};

const isCrawlByAttributeValue = (selector: SELECTOR): selector is [string, any] => {
return Array.isArray(selector) && selector.length === 2 && typeof selector[0] === 'string';
};

const isCrawlById = (selector: SELECTOR): selector is string => {
return typeof selector === 'string' && selector.indexOf('#') === 0 && selector.length > 1;
};

const isCrawlByTagName = (selector: SELECTOR): selector is string => {
return typeof selector === 'string' && selector.indexOf('#') !== 0 && selector.length > 0;
};

const isCrawlByDeclaration = (selector: SELECTOR): selector is AnyType<any> => {
return typeof selector === 'function';
};

export default (selector: SELECTOR): ((node: MockedDebugNode) => boolean) => {
if (isCrawlByAttribute(selector)) {
return crawlByAttribute(selector[0]);
}
if (isCrawlByAttributeValue(selector)) {
return crawlByAttributeValue(selector[0], selector[1]);
}
if (isCrawlById(selector)) {
return crawlById(selector.substr(1));
}
if (isCrawlByTagName(selector)) {
return crawlByTagName(selector);
}
if (isCrawlByDeclaration(selector)) {
return crawlByDeclaration(selector);
}

throw new Error(`Unknown selector`);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { MockedDebugNode } from '../../mock-render/types';
import funcParseProviderTokensDirectives from '../func.parse-provider-tokens-directives';

import funcGetPublicProviderKeys from './func.get-public-provider-keys';
import funcParseInputsAndRequiresAttributes from './func.parse-inputs-and-requires-attributes';

const collectSelectors = (node: MockedDebugNode): string[] => {
const selectors: string[] = [];

for (const token of node.providerTokens) {
const meta = funcParseProviderTokensDirectives(node, token);
if (meta?.selector && selectors.indexOf(meta.selector) === -1) {
selectors.push(meta.selector);
}
}

return selectors;
};

const collectAttributesClassic = (node: MockedDebugNode): string[] => {
const result: string[] = [];

for (const key of funcGetPublicProviderKeys(node)) {
const [inputs, expectedAttributes] = funcParseInputsAndRequiresAttributes(node, key);
for (const input of inputs) {
const [prop, alias] = input.split(': ');
const attr = alias || prop;
if (expectedAttributes.indexOf(prop) !== -1 && result.indexOf(attr) === -1) {
result.push(attr);
}
}
}

return result;
};

const collectAttributesIvy = (node: MockedDebugNode): string[] => {
const result: string[] = [];

const attrs = (node.injector as any)._tNode?.attrs || [];
let step = 2;
for (let index = 0; index < attrs.length; index += step) {
// 3 is a divider between static and dynamic bindings
if (typeof attrs[index] === 'number') {
step = 1;
continue;
}
const attr = attrs[index];
if ((node.injector as any)._tNode.inputs[attr] && result.indexOf(attr) === -1) {
result.push(attr);
}
}

return result;
};

export default (node: MockedDebugNode): [string[], string[]] => {
const selectors = collectSelectors(node);
const attributes = [...collectAttributesClassic(node), ...collectAttributesIvy(node)];

return [selectors, attributes];
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const regExp = new RegExp('\\[.*?\\]', 'g');

export default (selectors: string[], query: string): boolean => {
for (const selector of selectors) {
const attributes = selector.replace(regExp, '').split(',');

for (const attribute of attributes) {
if (attribute.trim() === query) {
return true;
}
}
}

return false;
};
5 changes: 5 additions & 0 deletions libs/ng-mocks/src/lib/mock-helper/crawl/detect-text-node.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { MockedDebugNode } from '../../mock-render/types';

export default (node: MockedDebugNode): boolean => {
return node.nativeNode.nodeName === '#text';
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { MockedDebugNode } from '../../mock-render/types';

export default (node: MockedDebugNode): string[] => {
return (node.injector as any).elDef ? Object.keys((node.injector as any).elDef.element.publicProviders) : [];
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { MockedDebugNode } from '../../mock-render/types';
import funcParseProviderTokensDirectives from '../func.parse-provider-tokens-directives';

export default (node: MockedDebugNode, key: string): [string[], string[], number] => {
const config = (node.injector as any).elDef.element.publicProviders[key];
const token = config.provider.value;
if (!token) {
return [[], [], 0];
}
const meta = funcParseProviderTokensDirectives(node, token);

const requiredAttributes = config.bindings.map((binding: any) => binding.nonMinifiedName || binding.name);

return [meta?.inputs || [], requiredAttributes, config.nodeIndex];
};
20 changes: 20 additions & 0 deletions libs/ng-mocks/src/lib/mock-helper/crawl/mock-helper.crawl-all.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import funcParseFindArgs from '../func.parse-find-args';

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

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)) {
result.push(node);
}
});

return result;
};
33 changes: 33 additions & 0 deletions libs/ng-mocks/src/lib/mock-helper/crawl/mock-helper.crawl.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 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)})`);
};
Loading

0 comments on commit f69c21f

Please sign in to comment.