Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ngMocks.render and ngMocks.hide #301

Merged
merged 2 commits into from
Feb 16, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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