Skip to content

Commit

Permalink
Merge pull request #300 from satanTime/issues/298
Browse files Browse the repository at this point in the history
fix(#298): better behavior of search functions in normal and ivy mode
  • Loading branch information
satanTime authored Feb 13, 2021
2 parents a110f1d + 952986e commit b031389
Show file tree
Hide file tree
Showing 21 changed files with 580 additions and 69 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { DebugNode } from '@angular/core';

export default (node: DebugNode): DebugNode =>
node.nativeNode.nodeName === '#text' && node.parent ? node.parent : node;
node.nativeNode?.nodeName === '#text' && node.parent ? node.parent : node;
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ import { Type } from '../common/core.types';

import { Node } from './func.get-from-node';

export default <T>(result: T[], node: DebugNode & Node, proto: Type<T>): void => {
export default <T>(result: T[], node: (DebugNode & Node) | null | undefined, proto: Type<T>): void => {
if (!node) {
return;
}

try {
const instance = node.injector.get(proto);
if (result.indexOf(instance) === -1) {
Expand Down
36 changes: 36 additions & 0 deletions libs/ng-mocks/src/lib/mock-helper/func.get-from-node-ivy.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,28 @@ describe('func.get-from-node-ivy', () => {
expect(result).toEqual([proto]);
});

it('disables on arrays', () => {
const result: any[] = [];
const proto = new Proto();
const node: any = {
nativeNode: {},
parent: {
nativeNode: {
__ngContext__: {
lView: [
[] /* now proto should not be collected */,
proto,
],
},
},
},
};

funcGetFromNodeIvy(result, node, Proto);

expect(result).toEqual([]);
});

it('handles empty context', () => {
const result: any[] = [];
const node: any = {
Expand Down Expand Up @@ -69,6 +91,20 @@ describe('func.get-from-node-ivy', () => {
expect(result).toEqual([proto]);
});

it('handles missed nativeNode', () => {
const result: any[] = [];
const node: any = {
parent: {
nativeNode: {
__ngContext__: [],
},
},
};

funcGetFromNodeIvy(result, node, Proto);
expect(result).toEqual([]);
});

it('skips node with _debugContext', () => {
const result: any[] = [];
const proto = new Proto();
Expand Down
5 changes: 2 additions & 3 deletions libs/ng-mocks/src/lib/mock-helper/func.get-from-node-ivy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import funcGetFromNodeScan from './func.get-from-node-scan';

const detectContext = (node: DebugNode): any => {
let current = node;
let context = current.nativeNode.__ngContext__;
let context = current.nativeNode?.__ngContext__;
while (!context && current.parent) {
current = current.parent;
context = current.nativeNode.__ngContext__;
Expand All @@ -19,13 +19,12 @@ const detectContext = (node: DebugNode): any => {

const contextToNodes = (context: any): any => (Array.isArray(context) ? context : context?.lView);

export default <T>(result: T[], node: DebugNode & Node, proto: Type<T>): void => {
export default <T>(result: T[], node: (DebugNode & Node) | null | undefined, proto: Type<T>): void => {
if (!node || node._debugContext) {
return;
}

const el = funcGetFromNodeElement(node);

funcGetFromNodeScan(
{
el,
Expand Down
8 changes: 6 additions & 2 deletions libs/ng-mocks/src/lib/mock-helper/func.get-from-node-scan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ import { DebugNode } from '@angular/core';
import { Type } from '../common/core.types';

const detectGatherFlag = (gather: boolean, el: DebugNode | null, node: any): boolean => {
// LContainer should stop the scan.
if (Array.isArray(node)) {
return false;
}

if (!el || !node.nodeName) {
return gather;
}
Expand All @@ -12,8 +17,7 @@ const detectGatherFlag = (gather: boolean, el: DebugNode | null, node: any): boo
return node.parentNode === el.nativeNode;
}

// checking if an injectedNode belongs to the current element.
return node === el.nativeNode;
return false;
};

const isNotObject = (node: any): boolean => !node || typeof node !== 'object';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const normalize = (item: any): any => {
return null;
};

export default <T>(result: T[], node: DebugNode & Node, proto: Type<T>): void => {
export default <T>(result: T[], node: (DebugNode & Node) | null | undefined, proto: Type<T>): void => {
if (!node || !node._debugContext) {
return;
}
Expand Down
2 changes: 1 addition & 1 deletion libs/ng-mocks/src/lib/mock-helper/func.get-from-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export interface Node {
parent?: (DebugNode & Node) | null;
}

export default <T>(result: T[], node: DebugNode & Node, proto: Type<T>): T[] => {
export default <T>(result: T[], node: (DebugNode & Node) | null | undefined, proto: Type<T>): T[] => {
funcGetFromNodeInjector(result, node, proto);
funcGetFromNodeStandard(result, node, proto);
funcGetFromNodeIvy(result, node, proto);
Expand Down
31 changes: 21 additions & 10 deletions libs/ng-mocks/src/lib/mock-helper/func.parse-find-args.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,23 @@
import { Type } from '../common/core.types';
import funcGetLastFixture from '../mock-helper/func.get-last-fixture';
import { MockedDebugElement } from '../mock-render/types';

const detectEl = (args: any[]): undefined | MockedDebugElement => {
return Array.isArray(args[0]) || typeof args[0] !== 'object'
? undefined
: args[0]?.debugElement
? args[0].debugElement
: args[0];
};

const detectSel = (args: any[], el: any) => {
return el || !args[0] ? args[1] : args[0];
};

const detectNotFound = (args: any[], el: any, defaultNotFoundValue: any): any => {
return args.length === 3 ? args[2] : !el && args[0] && args.length === 2 ? args[1] : defaultNotFoundValue;
};

export default (
args: any[],
defaultNotFoundValue?: any,
Expand All @@ -9,18 +26,12 @@ export default (
notFoundValue: any;
sel: string | Type<any> | [string] | [string, any];
} => {
const el: undefined | MockedDebugElement =
Array.isArray(args[0]) || typeof args[0] !== 'object'
? undefined
: args[0].debugElement
? args[0].debugElement
: args[0];
const sel = el || !args[0] ? args[1] : args[0];
const notFoundValue: any =
el && args.length === 3 ? args[2] : !el && args.length === 2 ? args[1] : defaultNotFoundValue;
const el = detectEl(args);
const sel = detectSel(args, el);
const notFoundValue = detectNotFound(args, el, defaultNotFoundValue);

return {
el,
el: el ?? (args[0] && funcGetLastFixture()?.debugElement) ?? undefined,
notFoundValue,
sel,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,12 @@ const getMeta = (token: any): Directive | undefined => {
return undefined;
};

export default (el: DebugNode, token: any): Directive | undefined => {
export default (el: DebugNode | null | undefined, token: any): Directive | undefined => {
// istanbul ignore if
if (!el) {
return undefined;
}

try {
const provider = funcGetProvider(token);
const instance = el.injector.get(provider);
Expand Down
36 changes: 27 additions & 9 deletions libs/ng-mocks/src/lib/mock-helper/mock-helper.attributes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,48 @@ import mockHelperGet from './mock-helper.get';

const defaultNotFoundValue = {}; // simulating Symbol

const parseArgs = (args: any[]): [MockedDebugElement, string, any] => [
const parseArgs = (args: any[]): [MockedDebugElement | null | undefined, string, any] => [
args[0],
args[1],
args.length === 3 ? args[2] : defaultNotFoundValue,
];

export default (label: string, attr: 'inputs' | 'outputs', ...args: any[]) => {
const [el, sel, notFoundValue] = parseArgs(args);
const attrMatches = (attribute: string, selector: string): string | undefined => {
const [prop, alias = ''] = attribute.split(':', 2).map(v => v.trim());

if ((!alias && prop === selector) || (!!alias && alias === selector)) {
return prop;
}

for (const token of el.providerTokens) {
return undefined;
};

const detectAttribute = (el: MockedDebugElement | null | undefined, attr: 'inputs' | 'outputs', sel: string) => {
for (const token of el?.providerTokens || []) {
const meta = funcParseProviderTokensDirectives(el, token);
if (!meta) {
continue;
}

for (const attrDef of meta[attr] || /* istanbul ignore next */ []) {
const [prop, alias = ''] = attrDef.split(':', 2).map(v => v.trim());
if ((!alias && prop !== sel) || (alias && alias !== sel)) {
continue;
const prop = attrMatches(attrDef, sel);
if (prop) {
return mockHelperGet(el, token)[prop];
}

return mockHelperGet(el, token)[prop];
}
}

throw new Error('Not found');
};

export default (label: string, attr: 'inputs' | 'outputs', ...args: any[]) => {
const [el, sel, notFoundValue] = parseArgs(args);

try {
return detectAttribute(el, attr, sel);
} catch (e) {
// nothing to do
}
if (notFoundValue !== defaultNotFoundValue) {
return notFoundValue;
}
Expand Down
4 changes: 1 addition & 3 deletions libs/ng-mocks/src/lib/mock-helper/mock-helper.find-all.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import funcGetLastFixture from './func.get-last-fixture';
import funcParseFindArgs from './func.parse-find-args';
import funcParseFindTerm from './func.parse-find-term';

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

return debugElement?.queryAll(funcParseFindTerm(sel)) || [];
return el?.queryAll(funcParseFindTerm(sel)) || [];
};
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import funcGetLastFixture from './func.get-last-fixture';
import funcParseFindArgs from './func.parse-find-args';
import funcParseFindArgsName from './func.parse-find-args-name';
import mockHelperFindInstances from './mock-helper.find-instances';
Expand All @@ -7,9 +6,8 @@ const defaultNotFoundValue = {}; // simulating Symbol

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

const result = mockHelperFindInstances(debugElement, sel);
const result = mockHelperFindInstances(el, sel);
if (result.length) {
return result[0];
}
Expand Down
10 changes: 5 additions & 5 deletions libs/ng-mocks/src/lib/mock-helper/mock-helper.find-instances.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,29 @@ 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';

interface DebugNode {
childNodes?: MockedDebugNode[];
}

function nestedCheck<T>(result: T[], node: MockedDebugNode & DebugNode, proto: Type<T>) {
funcGetFromNode(result, node, proto);
function nestedCheck<T>(result: T[], node: (MockedDebugNode & DebugNode) | undefined, proto: Type<T>) {
if (node) {
funcGetFromNode(result, node, proto);
}
for (const childNode of node?.childNodes || []) {
nestedCheck(result, childNode, proto);
}
}

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

const result: T[] = [];
nestedCheck(result, debugElement, getSourceOfMock(sel));
nestedCheck(result, el, getSourceOfMock(sel));

return result;
};
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import funcGetLastFixture from './func.get-last-fixture';
import funcParseFindArgs from './func.parse-find-args';
import funcParseFindArgsName from './func.parse-find-args-name';
import mockHelperFindTemplateRefs from './mock-helper.find-template-refs';
Expand All @@ -7,9 +6,8 @@ const defaultNotFoundValue = {}; // simulating Symbol

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

const result = debugElement ? mockHelperFindTemplateRefs(debugElement, sel) : [];
const result = el ? mockHelperFindTemplateRefs(el, sel) : [];
if (result.length) {
return result[0];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { TemplateRef } from '@angular/core';
import { Type } from '../common/core.types';
import { MockedDebugNode } from '../mock-render/types';

import funcGetLastFixture from './func.get-last-fixture';
import funcParseFindArgs from './func.parse-find-args';
import funcParseProviderTokensDirectives from './func.parse-provider-tokens-directives';
import mockHelperInput from './mock-helper.input';
Expand Down Expand Up @@ -121,12 +120,11 @@ const getDetector = (selector: string | Type<any> | [string] | [string, any] | a

export default (...args: any[]): Array<TemplateRef<any>> => {
const { el, sel } = funcParseFindArgs(args);
const debugElement = el || funcGetLastFixture()?.debugElement;

const detector = getDetector(sel);

const result: Array<TemplateRef<any>> = [];
nestedCheck(result, debugElement, node => {
nestedCheck(result, el, node => {
const value = detector(node);
if (value) {
result.push(value);
Expand Down
4 changes: 1 addition & 3 deletions libs/ng-mocks/src/lib/mock-helper/mock-helper.find.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import funcGetLastFixture from './func.get-last-fixture';
import funcParseFindArgs from './func.parse-find-args';
import funcParseFindArgsName from './func.parse-find-args-name';
import funcParseFindTerm from './func.parse-find-term';
Expand All @@ -7,9 +6,8 @@ const defaultNotFoundValue = {}; // simulating Symbol

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

const result = debugElement?.query(funcParseFindTerm(sel));
const result = el?.query(funcParseFindTerm(sel));
if (result) {
return result;
}
Expand Down
4 changes: 2 additions & 2 deletions libs/ng-mocks/src/lib/mock-helper/mock-helper.get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const defaultNotFoundValue = {}; // simulating Symbol
const parseArgs = <T>(
args: any[],
): {
el: MockedDebugElement;
el: MockedDebugElement | null | undefined;
notFoundValue: any;
sel: Type<T>;
} => ({
Expand All @@ -27,7 +27,7 @@ export default <T>(...args: any[]) => {
}

// Looking for related structural directive.
const prevNode = el.nativeNode.previousSibling;
const prevNode = el?.nativeNode.previousSibling;
const matches =
!prevNode || prevNode.nodeName !== '#comment' || !el || !el.parent
? []
Expand Down
Loading

0 comments on commit b031389

Please sign in to comment.