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 19, 2021
1 parent 47f6abe commit 71390b2
Show file tree
Hide file tree
Showing 36 changed files with 1,241 additions and 177 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,95 @@
import { MockedDebugNode } from '../../mock-render/types';

import crawlByAttributeValue from './crawl-by-attribute-value';

describe('crawl-by-attribute-value:ivy', () => {
const attribute = 'attribute';
const value = 'value';

let callback: (node: MockedDebugNode) => boolean;
beforeEach(
() => (callback = crawlByAttributeValue(attribute, value)),
);

it('ignores empty nodes', () => {
const node: any = {
injector: {},
};
expect(callback(node)).toEqual(false);
});

it('scans attrs with empty inputs', () => {
const node: any = {
injector: {
_tNode: {
attrs: [attribute, value],
},
},
};
expect(callback(node)).toEqual(false);
});

it('scans attrs with proper step switch on inputs', () => {
const node: any = {
injector: {
_lView: {
5: {
prop: value,
},
},
_tNode: {
attrs: ['1', '2', 3, attribute],
inputs: {
[attribute]: [5, 'prop'],
},
},
},
};
expect(callback(node)).toEqual(true);
});

it('scans attrs with inputs but w/o values', () => {
const node: any = {
injector: {
_tNode: {
attrs: [attribute, value],
inputs: {},
},
},
};
expect(callback(node)).toEqual(false);
});

it('scans attrs with inputs but w/ values w/o lView', () => {
const node: any = {
injector: {
_tNode: {
attrs: [attribute, value],
inputs: {
[attribute]: [5, 'prop'],
},
},
},
};
expect(callback(node)).toEqual(false);
});

it('scans attrs with inputs but w/ values w/ lView', () => {
const node: any = {
injector: {
_lView: {
5: {
prop: value,
},
},
_tNode: {
attrs: [attribute, value],
inputs: {
[attribute]: [5, 'prop'],
},
},
},
};
expect(callback(node)).toEqual(true);
});
});
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;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import crawlByDeclaration from './crawl-by-declaration';

describe('crawl-by-declaration', () => {
it('returns false on exceptions', () => {
const declaration: any = {};
const callback = crawlByDeclaration(declaration);

const node: any = {
injector: {
get: () => {
throw new Error();
},
},
providerTokens: [declaration],
};
expect(callback(node)).toEqual(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,44 @@
import detectSelectorsFromNode from './detect-selectors-from-node';

describe('detect-selectors-from-node:ivy', () => {
it('handles empty node', () => {
const node: any = {
injector: {},
providerTokens: [],
};
expect(detectSelectorsFromNode(node)).toEqual([[], []]);
});

it('scans attributes', () => {
const node: any = {
injector: {
_tNode: {
attrs: ['1', '2', '3', '4', 3, '5', '6'],
},
},
providerTokens: [],
};
expect(detectSelectorsFromNode(node)).toEqual([[], []]);
});

it('scans attributes and inputs', () => {
const node: any = {
injector: {
_tNode: {
attrs: ['1', '2', '3', '4', 3, '5', '6'],
inputs: {
1: true,
3: true,
5: true,
6: true,
},
},
},
providerTokens: [],
};
expect(detectSelectorsFromNode(node)).toEqual([
[],
['1', '3', '5', '6'],
]);
});
});
Loading

0 comments on commit 71390b2

Please sign in to comment.