Skip to content

Commit

Permalink
feat(#314): ngMocks.formatText
Browse files Browse the repository at this point in the history
  • Loading branch information
satanTime committed Mar 21, 2021
1 parent 64d79d0 commit fa3cea7
Show file tree
Hide file tree
Showing 17 changed files with 379 additions and 96 deletions.
74 changes: 74 additions & 0 deletions docs/articles/api/ngMocks/formatText.md
@@ -0,0 +1,74 @@
---
title: ngMocks.formatText
description: Documentation about ngMocks.formatText from ng-mocks library
---

`ngMocks.formatText` is intended to normalize `textContent` in order to provide simpler text expectations in tests.

`ngMocks.formatText` removes:
- all html comments
- all html tags
- sequences of spaces, new lines, tabs will be replaced by a single space symbol

`ngMocks.formatText` accepts:
- a string
- `HTMLElement`, `Text`, `Comment`
- `DebugElement`, `DebugNode`, `ComponentFixture`
- an array of them

## dirty html

a html like

```html
<div>
<!-- binding1 -->
<strong>header</strong>
<!-- binding2 -->
<ul>
<li>1</li>
<li>2</li>
</ul>
<!-- binding3 -->
</div>
```

becomes

```html
header 12
```

## ng-container

A cool thing is that `ngMocks.formatText` uses [`ngMocks.crawl`](./crawl.md) under the hood
and respects `ng-container`.

so if we have a pointer to `ng-container`, we can assert its content.

```html
<div>
&lt;
<ng-container block1>1</ng-container>
&amp;
<ng-container block2>2</ng-container>
&gt;
</div>
```

```ts
const div = ngMocks.find('div');
const block1 = ngMocks.reveal(div, ['block1']);
const block2 = ngMocks.reveal(div, ['block2']);

ngMocks.format(div);
// returns
// < 1 & 2 >

ngMocks.formatHtml(block1);
// returns
// 1
ngMocks.formatHtml(block2);
// returns
// 2
```
1 change: 1 addition & 0 deletions docs/sidebars.js
Expand Up @@ -58,6 +58,7 @@ module.exports = {
'api/ngMocks/faster',
'api/ngMocks/throwOnConsole',
'api/ngMocks/formatHtml',
'api/ngMocks/formatText',
'api/ngMocks/flushTestBed',
'api/ngMocks/reset',
],
Expand Down
29 changes: 29 additions & 0 deletions libs/ng-mocks/src/lib/mock-helper/format/format-handler.ts
@@ -0,0 +1,29 @@
import nestedCheckChildren from '../crawl/nested-check-children';

import handleArray from './handle-array';
import isDebugNode from './is-debug-node';
import isFixture from './is-fixture';
import { FORMAT_SET, FORMAT_SINGLE } from './types';

export default (handlePrimitives: any) => (html: any, outer = false) => {
const format = (value: Text | Comment | FORMAT_SINGLE | FORMAT_SET, innerOuter = false): any => {
if (Array.isArray(value)) {
return handleArray(format, value);
}
if (isFixture(value)) {
return format(value.debugElement, outer);
}
const result = handlePrimitives(format, value, innerOuter);
if (result !== undefined) {
return result;
}

if (isDebugNode(value) && value.nativeNode.nodeName === '#comment') {
return format(nestedCheckChildren(value), true);
}

return isDebugNode(value) ? format(value.nativeNode, innerOuter) : '';
};

return Array.isArray(html) ? html.map((item: any) => format(item, outer)) : format(html, outer);
};
3 changes: 3 additions & 0 deletions libs/ng-mocks/src/lib/mock-helper/format/handle-array.ts
@@ -0,0 +1,3 @@
export default (format: any, html: any) => {
return format((html as any[]).map(item => format(item, true)).join(''));
};
1 change: 1 addition & 0 deletions libs/ng-mocks/src/lib/mock-helper/format/handle-text.ts
@@ -0,0 +1 @@
export default (html: Text): string => html.nodeValue ?? html.textContent ?? html.wholeText;
5 changes: 5 additions & 0 deletions libs/ng-mocks/src/lib/mock-helper/format/is-debug-node.ts
@@ -0,0 +1,5 @@
import { MockedDebugNode } from '../../mock-render/types';

export default (value: any): value is MockedDebugNode => {
return !!value?.nativeElement || !!value?.nativeNode;
};
5 changes: 5 additions & 0 deletions libs/ng-mocks/src/lib/mock-helper/format/is-fixture.ts
@@ -0,0 +1,5 @@
import { MockedDebugNode } from '../../mock-render/types';

export default (value: any): value is { debugElement: MockedDebugNode } => {
return !!value && typeof value === 'object' && value.debugElement !== undefined;
};
3 changes: 3 additions & 0 deletions libs/ng-mocks/src/lib/mock-helper/format/is-html-element.ts
@@ -0,0 +1,3 @@
export default (value: any): value is HTMLElement => {
return !!value && typeof value === 'object' && value.innerHTML !== undefined;
};
3 changes: 3 additions & 0 deletions libs/ng-mocks/src/lib/mock-helper/format/is-text.ts
@@ -0,0 +1,3 @@
export default (value: any): value is Text => {
return !!value && typeof value === 'object' && value.nodeName === '#text';
};
@@ -0,0 +1,43 @@
import formatHandler from './format-handler';
import handleText from './handle-text';
import isHtmlElement from './is-html-element';
import isText from './is-text';

const normalizeValue = (html: string | undefined): string =>
html
? html
.replace(new RegExp('\\s+', 'mg'), ' ')
.replace(new RegExp('<!--.*?-->', 'mg'), '')
.replace(new RegExp('\\s+', 'mg'), ' ')
.replace(new RegExp('>\\s+<', 'mg'), '><')
: '';

const normalizeText = (text: string): string =>
text
.replace(new RegExp('&', 'mg'), '&amp;')
.replace(new RegExp('"', 'mg'), '&quot;')
.replace(new RegExp('<', 'mg'), '&lt;')
.replace(new RegExp('>', 'mg'), '&gt;')
// tslint:disable-next-line quotemark
.replace(new RegExp("'", 'mg'), '&#39;');

const getElementValue = (element: HTMLElement, outer: boolean): string =>
outer ? element.outerHTML : element.innerHTML;

const handlePrimitives = (format: any, value: any, outer: boolean): string | undefined => {
if (typeof value === 'string' || value === undefined) {
const result = normalizeValue(value);

return outer ? result : result.trim();
}
if (isHtmlElement(value)) {
return format(getElementValue(value, outer));
}
if (isText(value)) {
return handlePrimitives(format, normalizeText(handleText(value)), outer);
}

return undefined;
};

export default (() => formatHandler(handlePrimitives))();
@@ -0,0 +1,30 @@
import formatHandler from './format-handler';
import handleText from './handle-text';
import isHtmlElement from './is-html-element';
import isText from './is-text';

const normalizeValue = (html: string | undefined): string => (html ? html.replace(new RegExp('\\s+', 'mg'), ' ') : '');

const getElementValue = (element: HTMLElement, outer: boolean): string => {
const value = element.textContent ?? '';

return outer ? value : value.trim();
};

const handlePrimitives = (format: any, value: any, outer: boolean): string | undefined => {
if (typeof value === 'string' || value === undefined) {
const result = normalizeValue(value);

return outer ? result : result.trim();
}
if (isHtmlElement(value)) {
return format(getElementValue(value, outer));
}
if (isText(value)) {
return handlePrimitives(format, handleText(value), outer);
}

return undefined;
};

export default (() => formatHandler(handlePrimitives))();
7 changes: 7 additions & 0 deletions libs/ng-mocks/src/lib/mock-helper/format/types.ts
@@ -0,0 +1,7 @@
export type FORMAT_SINGLE = string | HTMLElement | { nativeNode: any } | { nativeElement: any } | { debugElement: any };
export type FORMAT_SET =
| string[]
| HTMLElement[]
| Array<{ nativeNode: any }>
| Array<{ nativeElement: any }>
| Array<{ debugElement: any }>;
90 changes: 0 additions & 90 deletions libs/ng-mocks/src/lib/mock-helper/mock-helper.format-html.ts

This file was deleted.

4 changes: 3 additions & 1 deletion libs/ng-mocks/src/lib/mock-helper/mock-helper.object.ts
Expand Up @@ -3,6 +3,8 @@ import mockHelperReveal from './crawl/mock-helper.reveal';
import mockHelperRevealAll from './crawl/mock-helper.reveal-all';
import mockHelperChange from './cva/mock-helper.change';
import mockHelperTouch from './cva/mock-helper.touch';
import mockHelperFormatHtml from './format/mock-helper.format-html';
import mockHelperFormatText from './format/mock-helper.format-text';
import mockHelperAutoSpy from './mock-helper.auto-spy';
import mockHelperDefaultMock from './mock-helper.default-mock';
import mockHelperFaster from './mock-helper.faster';
Expand All @@ -11,7 +13,6 @@ import mockHelperFindAll from './mock-helper.find-all';
import mockHelperFindInstance from './mock-helper.find-instance';
import mockHelperFindInstances from './mock-helper.find-instances';
import mockHelperFlushTestBed from './mock-helper.flush-test-bed';
import mockHelperFormatHtml from './mock-helper.format-html';
import mockHelperGet from './mock-helper.get';
import mockHelperGlobalExclude from './mock-helper.global-exclude';
import mockHelperGlobalKeep from './mock-helper.global-keep';
Expand Down Expand Up @@ -44,6 +45,7 @@ export default {
findTemplateRefs: mockHelperFindTemplateRefs,
flushTestBed: mockHelperFlushTestBed,
formatHtml: mockHelperFormatHtml,
formatText: mockHelperFormatText,
get: mockHelperGet,
globalExclude: mockHelperGlobalExclude,
globalKeep: mockHelperGlobalKeep,
Expand Down
21 changes: 17 additions & 4 deletions libs/ng-mocks/src/lib/mock-helper/mock-helper.ts
Expand Up @@ -8,6 +8,7 @@ import { NgModuleWithProviders } from '../common/func.is-ng-module-def-with-prov
import { MockedDebugElement, MockedDebugNode } from '../mock-render/types';
import { CustomMockFunction, MockedFunction } from '../mock-service/types';

import { FORMAT_SET, FORMAT_SINGLE } from './format/types';
import mockHelperObject from './mock-helper.object';

/**
Expand Down Expand Up @@ -234,10 +235,22 @@ export const ngMocks: {
/**
* @see https://ng-mocks.sudo.eu/api/ngMocks/formatHtml
*/
formatHtml(
html: string | HTMLElement | { nativeNode: any } | { nativeElement: any } | { debugElement: any },
outer?: boolean,
): string;
formatHtml(html: FORMAT_SINGLE, outer?: boolean): string;

/**
* @see https://ng-mocks.sudo.eu/api/ngMocks/formatHtml
*/
formatHtml(html: FORMAT_SET, outer?: boolean): string[];

/**
* @see https://ng-mocks.sudo.eu/api/ngMocks/formatText
*/
formatText(text: FORMAT_SINGLE, outer?: boolean): string;

/**
* @see https://ng-mocks.sudo.eu/api/ngMocks/formatText
*/
formatText(text: FORMAT_SET, outer?: boolean): string[];

/**
* @see https://ng-mocks.sudo.eu/api/ngMocks/get
Expand Down

0 comments on commit fa3cea7

Please sign in to comment.