Skip to content

Commit

Permalink
feat: find TemplateRef / ng-template
Browse files Browse the repository at this point in the history
Closes #290
  • Loading branch information
satanTime committed Feb 13, 2021
1 parent 51d090d commit 093eea7
Show file tree
Hide file tree
Showing 24 changed files with 896 additions and 49 deletions.
2 changes: 2 additions & 0 deletions docs/articles/api/ngMocks.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ access desired elements and instances in fixtures.
* [`findAll()`](ngMocks/findAll.md)
* [`findInstance()`](ngMocks/findInstance.md)
* [`findInstances()`](ngMocks/findInstances.md)
* [`findTemplateRef()`](ngMocks/findTemplateRef.md)
* [`findTemplateRefs()`](ngMocks/findTemplateRefs.md)

## Stubbing methods and properties

Expand Down
26 changes: 26 additions & 0 deletions docs/articles/api/ngMocks/find.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,40 @@ If a root element or a fixture are not specified, then the current fixture is us
- `ngMocks.find( debugElement?, component, notFoundValue? )`
- `ngMocks.find( debugElement?, cssSelector, notFoundValue? )`

## Component class

```ts
const element1 = ngMocks.find(Component1);
const element2 = ngMocks.find(fixture, Component2);
const element3 = ngMocks.find(fixture.debugElement, Component3);
```

## CSS Selector

```ts
const element1 = ngMocks.find('div.con1');
const element2 = ngMocks.find(fixture, 'div.con2');
const element3 = ngMocks.find(fixture.debugElement, 'div.con3');
```

## Tuple

`cssSelector` supports a tuple to generate an attribute css selector.
The first value is the name of an attribute,
the second value is optional and points to the desired value of the attribute.

```ts
const el1 = ngMocks.find(['data-key']);
// the same as
const el2 = ngMocks.find('[data-key]');

el1 === el2; // true
```

```ts
const el1 = ngMocks.find(['data-key', 5]);
// the same as
const el2 = ngMocks.find('[data-key="5"]');

el1 === el2; // true
```
14 changes: 14 additions & 0 deletions docs/articles/api/ngMocks/findAll.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,17 @@ const elements1 = ngMocks.findAll('div.item1');
const elements2 = ngMocks.findAll(fixture, 'div.item2');
const elements3 = ngMocks.findAll(fixture.debugElement, 'div.item3');
```

`cssSelector` supports a tuple to generate an attribute css selector:

```ts
const elements1 = ngMocks.findAll(['data-key']);
// the same as
const elements2 = ngMocks.findAll('[data-key]');
```

```ts
const elements1 = ngMocks.findAll(['data-key', 5]);
// the same as
const elements2 = ngMocks.findAll('[data-key="5"]');
```
83 changes: 83 additions & 0 deletions docs/articles/api/ngMocks/findTemplateRef.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
---
title: ngMocks.findTemplateRef
description: Documentation about ngMocks.findTemplateRef from ng-mocks library
---

Returns the first found `TemplateRef` which belongs to the current element or its any child.
If the element is not specified, then the current fixture is used.

- `ngMocks.findTemplateRef( fixture?, directive, notFoundValue? )`
- `ngMocks.findTemplateRef( debugElement?, id, notFoundValue? )`
- `ngMocks.findTemplateRef( debugElement?, [attribute, value?], notFoundValue? )`

## Directive

Assume, that a template has the next code.

```html
<ng-template my-directive-1></ng-template>
<span *my-directive-2></span>
```

Then we can find `ng-template` like that:

```ts
const tpl1 = ngMocks.findTemplateRef(MyDirective1);
const tpl2 = ngMocks.findTemplateRef(MyDirective2);
```

## Id

Assume, that a template has the next code.

```html
<ng-template #header></ng-template>
<ng-template #footer></ng-template>
```

Then we can find `ng-template` like that:

```ts
const tplHeader = ngMocks.findTemplateRef('header');
const tplFooter = ngMocks.findTemplateRef('footer');
```

## Attribute selector

Assume, that a template has the next code.

```html
<ng-template mat-row></ng-template>
<ng-template mat-cell></ng-template>
```

Then we can find `ng-template` like that:

```ts
const tplRow = ngMocks.findTemplateRef(['mat-row']);
const tplCell = ngMocks.findTemplateRef(['mat-cell']);
```

:::important
Pay attention that a tuple is used, otherwise it will be id look up.
:::

## Input with value

Assume, that a template has the next code.

```html
<ng-template myTpl="header"></ng-template>
<ng-template [myTpl]="property"></ng-template>
```

Then we can find `ng-template` like that:

```ts
const tplHeader = ngMocks.findTemplateRef(['myTpl', 'header']);
const tplInput = ngMocks.findTemplateRef(['myTpl', property]);
```

:::important
Pay attention that `property` is a variable.
:::
39 changes: 39 additions & 0 deletions docs/articles/api/ngMocks/findTemplateRefs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
---
title: ngMocks.findTemplateRefs
description: Documentation about ngMocks.findTemplateRefs from ng-mocks library
---

Returns an array of all found `TemplateRef` which belong to the current element and all its children.
If the element is not specified, then the current fixture is used.

- `ngMocks.findTemplateRefs( fixture?, directive )`
- `ngMocks.findTemplateRefs( debugElement?, id )`
- `ngMocks.findTemplateRefs( debugElement?, [attribute, value?] )`

Assume, that a template has the next code.

```html
<ng-template myTpl="header"></ng-template>
<ng-template myTpl="footer"></ng-template>
```

Then, we can find `ng-template` like that:

```ts
// returns 2 elements
const allByDirective = ngMocks.findTemplateRef(MyTplDirective);

// returns 2 elements
const allByAttribute = ngMocks.findTemplateRef(['myTpl']);

// returns 1 element
const onlyHeaders = ngMocks.findTemplateRef(['myTpl', 'header']);

// returns 1 element
const onlyFooters = ngMocks.findTemplateRef(['myTpl', 'footer']);

// returns 0 elements
const empty = ngMocks.findTemplateRef(['myTpl', 'body']);
```

More information can be found in [`ngMocks.findTemplateRef`](./findTemplateRef.md).
2 changes: 2 additions & 0 deletions docs/sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ module.exports = {
'api/ngMocks/findAll',
'api/ngMocks/findInstance',
'api/ngMocks/findInstances',
'api/ngMocks/findTemplateRef',
'api/ngMocks/findTemplateRefs',
'api/ngMocks/stub',
'api/ngMocks/stubMember',
'api/ngMocks/guts',
Expand Down
2 changes: 1 addition & 1 deletion karma.conf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ process.on('infrastructure_error', error => {

process.env.CHROME_BIN = require('puppeteer').executablePath();

module.exports = (config: KarmaTypescriptConfig) => {
export default (config: KarmaTypescriptConfig) => {
config.set({
autoWatch: false,
browsers: [process.env.IE_BIN ? 'IECi' : 'ChromeCi'],
Expand Down
12 changes: 12 additions & 0 deletions libs/ng-mocks/src/lib/mock-helper/func.parse-find-args-name.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Type } from '../common/core.types';

export default (selector: string | Type<any> | [string] | [string, any]): string => {
if (typeof selector === 'string') {
return selector;
}
if (typeof selector === 'function') {
return selector.name;
}

return selector[0];
};
12 changes: 8 additions & 4 deletions libs/ng-mocks/src/lib/mock-helper/func.parse-find-args.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
import { Type } from '../common/core.types';
import { MockedDebugElement } from '../mock-render/types';

export default <T = string | Type<any>>(
export default (
args: any[],
defaultNotFoundValue?: any,
): {
el: undefined | MockedDebugElement;
notFoundValue: any;
sel: T;
sel: string | Type<any> | [string] | [string, any];
} => {
const el: undefined | MockedDebugElement =
typeof args[0] !== 'object' ? undefined : args[0].debugElement ? args[0].debugElement : args[0];
const sel: T = el ? args[1] : args[0];
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;

Expand Down
13 changes: 13 additions & 0 deletions libs/ng-mocks/src/lib/mock-helper/func.parse-find-term.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { DebugElement, Predicate } from '@angular/core';
import { By } from '@angular/platform-browser';

import { Type } from '../common/core.types';
import { getSourceOfMock } from '../common/func.get-source-of-mock';

export default (selector: string | Type<any> | [string] | [string, any]): Predicate<DebugElement> => {
return Array.isArray(selector)
? By.css(selector.length === 1 ? `[${selector[0]}]` : `[${selector[0]}="${selector[1]}"]`)
: typeof selector === 'string'
? By.css(selector)
: By.directive(getSourceOfMock(selector));
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { DebugNode, Directive } from '@angular/core';

import coreReflectDirectiveResolve from '../common/core.reflect.directive-resolve';
import funcGetProvider from '../common/func.get-provider';

const getMeta = (token: any): Directive | undefined => {
try {
return coreReflectDirectiveResolve(token);
} catch (e) {
// Looks like it is a token.
}

return undefined;
};

export default (el: DebugNode, token: any): Directive | undefined => {
try {
const provider = funcGetProvider(token);
const instance = el.injector.get(provider);

return getMeta(instance.constructor);
} catch (e) {
return undefined;
}
};
17 changes: 2 additions & 15 deletions libs/ng-mocks/src/lib/mock-helper/mock-helper.attributes.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { Directive } from '@angular/core';

import coreReflectDirectiveResolve from '../common/core.reflect.directive-resolve';
import { MockedDebugElement } from '../mock-render/types';

import funcParseProviderTokensDirectives from './func.parse-provider-tokens-directives';
import mockHelperGet from './mock-helper.get';

const defaultNotFoundValue = {}; // simulating Symbol
Expand All @@ -13,26 +11,15 @@ const parseArgs = (args: any[]): [MockedDebugElement, string, any] => [
args.length === 3 ? args[2] : defaultNotFoundValue,
];

const getMeta = (token: any): Directive | undefined => {
try {
return coreReflectDirectiveResolve(token);
} catch (e) {
// Looks like it is a token.
}

return undefined;
};

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

for (const token of el.providerTokens) {
const meta = getMeta(token);
const meta = funcParseProviderTokensDirectives(el, token);
if (!meta) {
continue;
}

// istanbul ignore if
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)) {
Expand Down
9 changes: 2 additions & 7 deletions libs/ng-mocks/src/lib/mock-helper/mock-helper.find-all.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
import { By } from '@angular/platform-browser';

import { getSourceOfMock } from '../common/func.get-source-of-mock';

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;

const term = typeof sel === 'string' ? By.css(sel) : By.directive(getSourceOfMock(sel));

return debugElement?.queryAll(term) || [];
return debugElement?.queryAll(funcParseFindTerm(sel)) || [];
};
10 changes: 4 additions & 6 deletions libs/ng-mocks/src/lib/mock-helper/mock-helper.find-instance.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,20 @@
import { Type } from '../common/core.types';
import { getSourceOfMock } from '../common/func.get-source-of-mock';

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';

const defaultNotFoundValue = {}; // simulating Symbol

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

const result = mockHelperFindInstances(debugElement, getSourceOfMock(sel));
const result = mockHelperFindInstances(debugElement, sel);
if (result.length) {
return result[0];
}
if (notFoundValue !== defaultNotFoundValue) {
return notFoundValue;
}
throw new Error(`Cannot find an instance via ngMocks.findInstance(${sel.name})`);
throw new Error(`Cannot find an instance via ngMocks.findInstance(${funcParseFindArgsName(sel)})`);
};
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,14 @@ function nestedCheck<T>(result: T[], node: MockedDebugNode & DebugNode, proto: T
}

export default <T>(...args: any[]): T[] => {
const { el, sel } = funcParseFindArgs<Type<T>>(args);
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<T>(result, debugElement, getSourceOfMock(sel));
nestedCheck(result, debugElement, getSourceOfMock(sel));

return result;
};

0 comments on commit 093eea7

Please sign in to comment.