Skip to content

Commit

Permalink
feat: improved TemplateRef render
Browse files Browse the repository at this point in the history
check ngMocks.render and ngMocks.hide

closes #291
  • Loading branch information
satanTime committed Feb 16, 2021
1 parent 6908e5f commit 151ff34
Show file tree
Hide file tree
Showing 43 changed files with 1,847 additions and 618 deletions.
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ The best way would be to discuss an issue or an improvement first:
```shell
export IE_BIN="/c/Program Files/Internet Explorer/iexplore.exe"
cd /c/ && rm -Rf ng-mocks && mkdir ng-mocks && cd ng-mocks
find /z/ng-mocks -maxdepth 1 -not -name ng-mocks -not -name .git -not -name e2e -not -name node_modules -exec cp -r {} . \;
find /z/ng-mocks -maxdepth 1 -not -name ng-mocks -not -name .git -not -name docs -not -name e2e -not -name node_modules -exec cp -r {} . \;
npm ci --no-optional --ignore-scripts
npm run test
```
Expand Down
11 changes: 3 additions & 8 deletions docs/articles/api/MockComponent.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,7 @@ The class of a mock component has:
- the same `selector`
- the same `@Inputs` and `@Outputs` with alias support
- templates with pure `<ng-content>` tags to allow transclusion
- support of `@ContentChild` with an `$implicit` context
- `__render('id', $implicit?, variables?)` - renders a template
- `__hide('id')` - hides a rendered template
- support of `@ContentChild` and `@ContentChildren`
- support of `ControlValueAccessor`, `Validator` and `AsyncValidator`
- support of `exportAs`

Expand Down Expand Up @@ -191,14 +189,11 @@ describe('MockComponent', () => {
// componentInstance is a MockedComponent<T> to access
// its `__render` method. `isMockOf` function helps here.
const mockComponent = fixture.point.componentInstance;
if (isMockOf(mockComponent, DependencyComponent, 'c')) {
mockComponent.__render('something');
fixture.detectChanges();
}
ngMocks.render(mockComponent, ngMocks.findTemplateRef('something'));

// The rendered template is wrapped by <div data-key="something">.
// We can use this selector to assert exactly its content.
const mockNgTemplate = ngMocks.find('[data-key="something"]')
const mockNgTemplate = ngMocks.find(DependencyComponent)
.nativeElement.innerHTML;
expect(mockNgTemplate).toContain('<p>inside template</p>');
});
Expand Down
9 changes: 3 additions & 6 deletions docs/articles/api/MockDirective.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ TestBed.configureTestingModule({

A mock directive has:

- support of attribute and structural directives
- the same `selector`
- the same `Inputs` and `Outputs` with alias support
- supports structural directives
- `__render($implicit?, variables?)` - renders content
- support of `@ContentChild` and `@ContentChildren`
- support of `ControlValueAccessor`, `Validator` and `AsyncValidator`
- supports `exportAs`

Expand Down Expand Up @@ -194,10 +194,7 @@ describe('MockDirective:Structural', () => {

// And let's render it manually now.
const mockDirective = ngMocks.findInstance(DependencyDirective);
if (isMockOf(mockDirective, DependencyDirective, 'd')) {
mockDirective.__render();
fixture.detectChanges();
}
ngMocks.render(mockDirective, mockDirective);

// The content of the structural directive should be rendered.
expect(fixture.nativeElement.innerHTML).toContain('>content<');
Expand Down
6 changes: 2 additions & 4 deletions docs/articles/api/helpers/isMockOf.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,13 @@ if (isMockOf(instance, SomeClass, 'm')) {
// checks whether `instance` is
// an instance of `MockedComponent<SomeClass>`
if (isMockOf(instance, SomeClass, 'c')) {
instance.__render('block', '$implicit');
instance.__hide('block');
// yes it is
}

// checks whether `instance` is
// an instance of `MockedDirective<SomeClass>`
if (isMockOf(instance, SomeClass, 'd')) {
instance.__render('$implicit');
instance.__hide();
// yes it is
}

// checks whether `instance` is
Expand Down
14 changes: 11 additions & 3 deletions docs/articles/api/ngMocks.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,23 @@ access desired elements and instances in fixtures.
* [`globalReplace()`](ngMocks/globalReplace.md)
* [`globalWipe()`](ngMocks/globalWipe.md)

## Manipulating `ng-template`

- [`render()`](ngMocks/render.md)
- [`hide()`](ngMocks/hide.md)

## Accessing elements and instances

- [`input()`](ngMocks/input.md)
- [`output()`](ngMocks/output.md)

* [`get()`](ngMocks/get.md)
* [`find()`](ngMocks/find.md)
* [`findAll()`](ngMocks/findAll.md)
* [`findInstance()`](ngMocks/findInstance.md)
* [`findInstances()`](ngMocks/findInstances.md)

- [`get()`](ngMocks/get.md)
- [`findInstance()`](ngMocks/findInstance.md)
- [`findInstances()`](ngMocks/findInstances.md)

* [`findTemplateRef()`](ngMocks/findTemplateRef.md)
* [`findTemplateRefs()`](ngMocks/findTemplateRefs.md)

Expand All @@ -40,6 +47,7 @@ access desired elements and instances in fixtures.
- [`guts()`](ngMocks/guts.md)
- [`faster()`](ngMocks/faster.md)
- [`throwOnConsole()`](ngMocks/throwOnConsole.md)
- `formatHtml()` - removes comments and sequences of spaces and new lines

* [`flushTestBed()`](ngMocks/flushTestBed.md)
* [`reset()`](ngMocks/reset.md)
48 changes: 48 additions & 0 deletions docs/articles/api/ngMocks/hide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
---
title: ngMocks.hide
description: Documentation about ngMocks.hide from ng-mocks library
---

`ngMocks.hide` hides what [`ngMocks.render`](./render.md) has rendered.

```ts
ngMocks.hide(declarationInst);
ngMocks.hide(declarationInst, templateRef);
ngMocks.hide(declarationInst, structuralDir);
```

- `declarationInst` should be an instance of a component or attribute directive
- `templateRef` should be a `TemplateRef` instance
- `structuralDir` should be an instance of a structural directive

## No parameter

If no parameter has been given, then **all rendered** templates and structural directives
which are reachable via queries will **be hidden**.

```ts
ngMocks.hide(declarationInst);
```

## TemplateRef

If the second parameter is `TemplateRef`, then **only the template** will be **hidden**.

```ts
ngMocks.hide(componentInst, templateRef);
ngMocks.hide(directiveInst, templateRef);
```

## Structural directive

If the second parameter is an instance of **structural directive**, then only its template will be **hidden**.

```ts
ngMocks.hide(componentInst, structuralDir);
```

To hide a structural directive **itself**, simply pass it as the second parameter.

```ts
ngMocks.hide(structuralDir, structuralDir);
```
223 changes: 223 additions & 0 deletions docs/articles/api/ngMocks/render.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
---
title: ngMocks.render
description: Documentation about ngMocks.render from ng-mocks library
---

`ngMocks.render` goes through **all queries**, such as `ContentChild` and `ContentChildren`,
tries to find related `TemplateRef` or a **structural directive**, and render it with a given context.

In order to hide them, use [`ngMocks.hide`](./hide.md).

```ts
ngMocks.render(declarationInst, templateRef);
ngMocks.render(declarationInst, templateRef, context);
ngMocks.render(declarationInst, templateRef, context, variables);
```

```ts
ngMocks.render(declarationInst, structuralDir);
ngMocks.render(declarationInst, structuralDir, context);
ngMocks.render(declarationInst, structuralDir, context, variables);
```

- `declarationInst` should be an instance of a component or attribute directive
- `templateRef` should be a `TemplateRef` instance
- `structuralDir` should be an instance of a structural directive
- `context` an optional context variable
- `variables` additional context variables

The **first** and the **second** parameter **cannot be empty**.

- [Try it on StackBlitz](https://stackblitz.com/github/ng-mocks/examples?file=src/examples/TestTemplateRefByRender/test.spec.ts&initialpath=%3Fspec%3DTestTemplateRefByRender)
- [Try it on CodeSandbox](https://codesandbox.io/s/github/ng-mocks/examples?file=/src/examples/TestTemplateRefByRender/test.spec.ts&initialpath=%3Fspec%3DTestTemplateRefByRender)

## Render TemplateRef / ng-template

To render a `TemplateRef` / `ng-template` we need to have 2 things:

- a variable which points to a component / directive with the query
- a variable which points to the template

The first task can be solved by [`ngMocks.find`](./find.md) or [`ngMocks.findInstance`](./findInstance.md),
the second task can be solved by [`ngMocks.findTemplateRef`](./findTemplateRef.md).

Let's assume, that we have the next template:

```html
<xd-card>
<ng-template #id let-label="label">
rendered-id-{{ label }}
</ng-template>

<ng-template myTpl="header" let-label>
rendered-header-{{ label }}
</ng-template>

<span my-tpl *myTpl="'footer'; let label">
rendered-footer-{{ label }}
</span>
</xd-card>
```

### Component pointer

Let's find `xd-card` component:

```ts
const xdCardEl = ngMocks.find('xd-card');
```

Or we can use its class:

```ts
const xdCardEl = ngMocks.find(XdCardComponent);
```


**Please note**, it can be not only an instance of a component,
but an instance of **attribute directive** too.

### Template pointer

Now, let's find the 3 templates:

```ts
const tplId = ngMocks.findTemplateRef(
xdCardEl,
// id of the template
'id',
);

const tplHeader = ngMocks.findTemplateRef(
xdCardEl,
// attr and value on the template
['myTpl', 'header'],
);

const tplFooter = ngMocks.findTemplateRef(
xdCardEl,
// attr and value on the template
['myTpl', 'footer'],
);
```

Please note, that we cannot find `tplFooter` by `my-tpl`,
because `*myTpl` is syntactic sugar and `my-tpl` belongs **not** to the desired template, but to its nested `span`.

### Rendering id

To render `TemplateRef` of a **mock component**, we need to pass it as the **second parameter** of `ngMocks.render`.
The third and the fourth parameters are used to **provide context** for the template.

```ts
ngMocks.render(
xdCardEl.componentInstance,
tplId,
undefined,
{label: 'test'},
);
```

Now we can assert the rendered html:

```ts
expect(xdCardEl.nativeElement.innerHTML)
.toContain('rendered-id-test');
```

### Rendering header

The process is the same as above:

```ts
ngMocks.render(
xdCardEl.componentInstance,
tplHeader,
'test',
);
expect(xdCardEl.nativeElement.innerHTML)
.toContain('rendered-header-test');
```

### Rendering footer

The process is the same as above:

```ts
ngMocks.render(
xdCardEl.componentInstance,
tplFooter,
'test',
);
expect(xdCardEl.nativeElement.innerHTML)
.toContain('<span my-tpl=""> rendered-footer-test </span>');
```

## Render structural directives

`ngMocks.render` renders not only `TemplateRef`, but also structural directives.

Let's find all instances of `MyTplDirective`.

```ts
const [header, footer] = ngMocks.findInstances(
xdCardEl,
MyTplDirective,
);
```

Because they are structural directives, we have 2 options:

- to render it from the component
- to render it directly

The **difference** is that the first option also ensures that the component
has **linked queries** to reach the directive.

### Via queries

To verify queries, we need to pass the component as the first parameter,
and the **desired structural directive** as the second one.

```ts
ngMocks.render(xdCardEl.componentInstance, header, 'test');
expect(xdCardEl.nativeElement.innerHTML)
.toContain('rendered-header-test');
```

### Directly

To render a **structural directive directly** we need to pass its instance
as the first and as the **second parameter** of `ngMocks.render`.

```ts
ngMocks.render(footer, footer, 'test');
expect(xdCardEl.nativeElement.innerHTML)
.toContain('rendered-footer-test');
```

## Deeply nested templates

It is possible to render any `TemplateRef` or structural directive on **any depth**,
the only requirement is to have **enough queries** to reach it from the desired instance.

Let's consider the next template:

```html
<xd-card>
<xd-header>
<xd-cell>
<ng-template icon>(i)</ng-template>
</xd-cell>
</xd-header>
</xd-card>
```

Then, if `xdCard` points to its component instance and `icon` points to
the nested `ng-template`, we can render it like that:

```ts
ngMocks.render(xdCard, icon);
```

A useful thing here is that the render will fail if a query is removed between elements.
Loading

0 comments on commit 151ff34

Please sign in to comment.