diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 78249f9962..7ade4e33e0 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -19,7 +19,7 @@ jobs: id: cache uses: actions/cache@v2 with: - path: ./node_modules + path: node_modules key: ${{ runner.os }}-root-v1-${{ hashFiles('./package-lock.json') }} - name: Install dependencies @@ -52,21 +52,22 @@ jobs: env: WITH_COVERAGE: '1' - - name: Building Docs - run: npm run build:docs - - name: Publish Test Report uses: mikepenz/action-junit-report@v2 + if: always() with: - check_name: Core Coverage + check_name: Core Unit Tests report_paths: test-reports/specs-junit.xml github_token: ${{ secrets.GITHUB_TOKEN }} + - name: Building Docs + run: npm run build:docs + - name: Workspace uses: actions/upload-artifact@v2 with: name: workspace - path: ./dist + path: dist - name: Artifacts uses: actions/upload-artifact@v2 @@ -112,7 +113,7 @@ jobs: id: cache uses: actions/cache@v2 with: - path: ./node_modules + path: node_modules key: ${{ runner.os }}-e2e-v1-${{ hashFiles('./package-lock.json') }} - name: Install dependencies @@ -140,7 +141,7 @@ jobs: id: cache uses: actions/cache@v2 with: - path: ./node_modules + path: node_modules key: ${{ runner.os }}-root-v1-${{ hashFiles('./package-lock.json') }} - name: Install dependencies @@ -154,8 +155,9 @@ jobs: - name: Publish Test Report uses: mikepenz/action-junit-report@v2 + if: always() with: - check_name: IE11 Coverage + check_name: IE11 Unit Tests report_paths: test-reports/specs-junit.xml github_token: ${{ secrets.GITHUB_TOKEN }} @@ -181,7 +183,7 @@ jobs: id: a5es5 uses: actions/cache@v2 with: - path: ./e2e/a5es5/node_modules + path: e2e/a5es5/node_modules key: ${{ runner.os }}-a5es5-v1-${{ hashFiles('./e2e/a5es5/package-lock.json') }} - name: Install A5 es5 dependencies @@ -192,7 +194,7 @@ jobs: id: a5es2015 uses: actions/cache@v2 with: - path: ./e2e/a5es2015/node_modules + path: e2e/a5es2015/node_modules key: ${{ runner.os }}-a5es2015-v1-${{ hashFiles('./e2e/a5es2015/package-lock.json') }} - name: Install A5 es2015 dependencies @@ -203,7 +205,7 @@ jobs: id: a6 uses: actions/cache@v2 with: - path: ./e2e/a6/node_modules + path: e2e/a6/node_modules key: ${{ runner.os }}-a6-v1-${{ hashFiles('./e2e/a6/package-lock.json') }} - name: Install A6 dependencies @@ -214,7 +216,7 @@ jobs: id: a7 uses: actions/cache@v2 with: - path: ./e2e/a7/node_modules + path: e2e/a7/node_modules key: ${{ runner.os }}-a7-v1-${{ hashFiles('./e2e/a7/package-lock.json') }} - name: Install A7 dependencies @@ -225,7 +227,7 @@ jobs: id: a8 uses: actions/cache@v2 with: - path: ./e2e/a8/node_modules + path: e2e/a8/node_modules key: ${{ runner.os }}-a8-v1-${{ hashFiles('./e2e/a8/package-lock.json') }} - name: Install A8 dependencies @@ -236,7 +238,7 @@ jobs: id: a9 uses: actions/cache@v2 with: - path: ./e2e/a9/node_modules + path: e2e/a9/node_modules key: ${{ runner.os }}-a9-v1-${{ hashFiles('./e2e/a9/package-lock.json') }} - name: Install A9 dependencies @@ -247,7 +249,7 @@ jobs: id: a10 uses: actions/cache@v2 with: - path: ./e2e/a10/node_modules + path: e2e/a10/node_modules key: ${{ runner.os }}-a10-v1-${{ hashFiles('./e2e/a10/package-lock.json') }} - name: Install A10 dependencies @@ -258,7 +260,7 @@ jobs: id: a11 uses: actions/cache@v2 with: - path: ./e2e/a11/node_modules + path: e2e/a11/node_modules key: ${{ runner.os }}-a11-v1-${{ hashFiles('./e2e/a11/package-lock.json') }} - name: Install A11 dependencies @@ -269,7 +271,7 @@ jobs: id: a12 uses: actions/cache@v2 with: - path: ./e2e/a12/node_modules + path: e2e/a12/node_modules key: ${{ runner.os }}-a12-v1-${{ hashFiles('./e2e/a12/package-lock.json') }} - name: Install A12 dependencies @@ -280,7 +282,7 @@ jobs: id: am uses: actions/cache@v2 with: - path: ./e2e/am/node_modules + path: e2e/am/node_modules key: ${{ runner.os }}-am-v1-${{ hashFiles('./e2e/am/package-lock.json') }} - name: Install A Min dependencies @@ -303,14 +305,14 @@ jobs: id: cache uses: actions/cache@v2 with: - path: ./e2e/a5es5/node_modules + path: e2e/a5es5/node_modules key: ${{ runner.os }}-a5es5-v1-${{ hashFiles('./e2e/a5es5/package-lock.json') }} - name: Workspace uses: actions/download-artifact@v2 with: name: workspace - path: ./dist + path: dist - name: Spreading Build run: npm run s:a5es5 @@ -335,14 +337,14 @@ jobs: id: cache uses: actions/cache@v2 with: - path: ./e2e/a5es2015/node_modules + path: e2e/a5es2015/node_modules key: ${{ runner.os }}-a5es2015-v1-${{ hashFiles('./e2e/a5es2015/package-lock.json') }} - name: Workspace uses: actions/download-artifact@v2 with: name: workspace - path: ./dist + path: dist - name: Spreading Build run: npm run s:a5es2015 @@ -367,14 +369,14 @@ jobs: id: cache uses: actions/cache@v2 with: - path: ./e2e/a6/node_modules + path: e2e/a6/node_modules key: ${{ runner.os }}-a6-v1-${{ hashFiles('./e2e/a6/package-lock.json') }} - name: Workspace uses: actions/download-artifact@v2 with: name: workspace - path: ./dist + path: dist - name: Spreading Build run: npm run s:a6 @@ -399,14 +401,14 @@ jobs: id: cache uses: actions/cache@v2 with: - path: ./e2e/a6/node_modules + path: e2e/a6/node_modules key: ${{ runner.os }}-a6-v1-${{ hashFiles('./e2e/a6/package-lock.json') }} - name: Workspace uses: actions/download-artifact@v2 with: name: workspace - path: ./dist + path: dist - name: Spreading Build run: npm run s:a6 @@ -431,14 +433,14 @@ jobs: id: cache uses: actions/cache@v2 with: - path: ./e2e/a7/node_modules + path: e2e/a7/node_modules key: ${{ runner.os }}-a7-v1-${{ hashFiles('./e2e/a7/package-lock.json') }} - name: Workspace uses: actions/download-artifact@v2 with: name: workspace - path: ./dist + path: dist - name: Spreading Build run: npm run s:a7 @@ -463,14 +465,14 @@ jobs: id: cache uses: actions/cache@v2 with: - path: ./e2e/a7/node_modules + path: e2e/a7/node_modules key: ${{ runner.os }}-a7-v1-${{ hashFiles('./e2e/a7/package-lock.json') }} - name: Workspace uses: actions/download-artifact@v2 with: name: workspace - path: ./dist + path: dist - name: Spreading Build run: npm run s:a7 @@ -495,14 +497,14 @@ jobs: id: cache uses: actions/cache@v2 with: - path: ./e2e/a8/node_modules + path: e2e/a8/node_modules key: ${{ runner.os }}-a8-v1-${{ hashFiles('./e2e/a8/package-lock.json') }} - name: Workspace uses: actions/download-artifact@v2 with: name: workspace - path: ./dist + path: dist - name: Spreading Build run: npm run s:a8 @@ -527,14 +529,14 @@ jobs: id: cache uses: actions/cache@v2 with: - path: ./e2e/a8/node_modules + path: e2e/a8/node_modules key: ${{ runner.os }}-a8-v1-${{ hashFiles('./e2e/a8/package-lock.json') }} - name: Workspace uses: actions/download-artifact@v2 with: name: workspace - path: ./dist + path: dist - name: Spreading Build run: npm run s:a8 @@ -559,14 +561,14 @@ jobs: id: cache uses: actions/cache@v2 with: - path: ./e2e/a9/node_modules + path: e2e/a9/node_modules key: ${{ runner.os }}-a9-v1-${{ hashFiles('./e2e/a9/package-lock.json') }} - name: Workspace uses: actions/download-artifact@v2 with: name: workspace - path: ./dist + path: dist - name: Spreading Build run: npm run s:a9 @@ -591,14 +593,14 @@ jobs: id: cache uses: actions/cache@v2 with: - path: ./e2e/a9/node_modules + path: e2e/a9/node_modules key: ${{ runner.os }}-a9-v1-${{ hashFiles('./e2e/a9/package-lock.json') }} - name: Workspace uses: actions/download-artifact@v2 with: name: workspace - path: ./dist + path: dist - name: Spreading Build run: npm run s:a9 @@ -623,14 +625,14 @@ jobs: id: cache uses: actions/cache@v2 with: - path: ./e2e/a9/node_modules + path: e2e/a9/node_modules key: ${{ runner.os }}-a9-v1-${{ hashFiles('./e2e/a9/package-lock.json') }} - name: Workspace uses: actions/download-artifact@v2 with: name: workspace - path: ./dist + path: dist - name: Spreading Build run: npm run s:a9 @@ -655,14 +657,14 @@ jobs: id: cache uses: actions/cache@v2 with: - path: ./e2e/a9/node_modules + path: e2e/a9/node_modules key: ${{ runner.os }}-a9-v1-${{ hashFiles('./e2e/a9/package-lock.json') }} - name: Workspace uses: actions/download-artifact@v2 with: name: workspace - path: ./dist + path: dist - name: Spreading Build run: npm run s:a9 @@ -687,14 +689,14 @@ jobs: id: cache uses: actions/cache@v2 with: - path: ./e2e/a10/node_modules + path: e2e/a10/node_modules key: ${{ runner.os }}-a10-v1-${{ hashFiles('./e2e/a10/package-lock.json') }} - name: Workspace uses: actions/download-artifact@v2 with: name: workspace - path: ./dist + path: dist - name: Spreading Build run: npm run s:a10 @@ -719,14 +721,14 @@ jobs: id: cache uses: actions/cache@v2 with: - path: ./e2e/a10/node_modules + path: e2e/a10/node_modules key: ${{ runner.os }}-a10-v1-${{ hashFiles('./e2e/a10/package-lock.json') }} - name: Workspace uses: actions/download-artifact@v2 with: name: workspace - path: ./dist + path: dist - name: Spreading Build run: npm run s:a10 @@ -751,14 +753,14 @@ jobs: id: cache uses: actions/cache@v2 with: - path: ./e2e/a10/node_modules + path: e2e/a10/node_modules key: ${{ runner.os }}-a10-v1-${{ hashFiles('./e2e/a10/package-lock.json') }} - name: Workspace uses: actions/download-artifact@v2 with: name: workspace - path: ./dist + path: dist - name: Spreading Build run: npm run s:a10 @@ -783,14 +785,14 @@ jobs: id: cache uses: actions/cache@v2 with: - path: ./e2e/a10/node_modules + path: e2e/a10/node_modules key: ${{ runner.os }}-a10-v1-${{ hashFiles('./e2e/a10/package-lock.json') }} - name: Workspace uses: actions/download-artifact@v2 with: name: workspace - path: ./dist + path: dist - name: Spreading Build run: npm run s:a10 @@ -815,14 +817,14 @@ jobs: id: cache uses: actions/cache@v2 with: - path: ./e2e/a11/node_modules + path: e2e/a11/node_modules key: ${{ runner.os }}-a11-v1-${{ hashFiles('./e2e/a11/package-lock.json') }} - name: Workspace uses: actions/download-artifact@v2 with: name: workspace - path: ./dist + path: dist - name: Spreading Build run: npm run s:a11 @@ -847,14 +849,14 @@ jobs: id: cache uses: actions/cache@v2 with: - path: ./e2e/a11/node_modules + path: e2e/a11/node_modules key: ${{ runner.os }}-a11-v1-${{ hashFiles('./e2e/a11/package-lock.json') }} - name: Workspace uses: actions/download-artifact@v2 with: name: workspace - path: ./dist + path: dist - name: Spreading Build run: npm run s:a11 @@ -879,14 +881,14 @@ jobs: id: cache uses: actions/cache@v2 with: - path: ./e2e/a11/node_modules + path: e2e/a11/node_modules key: ${{ runner.os }}-a11-v1-${{ hashFiles('./e2e/a11/package-lock.json') }} - name: Workspace uses: actions/download-artifact@v2 with: name: workspace - path: ./dist + path: dist - name: Spreading Build run: npm run s:a11 @@ -911,14 +913,14 @@ jobs: id: cache uses: actions/cache@v2 with: - path: ./e2e/a11/node_modules + path: e2e/a11/node_modules key: ${{ runner.os }}-a11-v1-${{ hashFiles('./e2e/a11/package-lock.json') }} - name: Workspace uses: actions/download-artifact@v2 with: name: workspace - path: ./dist + path: dist - name: Spreading Build run: npm run s:a11 @@ -943,14 +945,14 @@ jobs: id: cache uses: actions/cache@v2 with: - path: ./e2e/a12/node_modules + path: e2e/a12/node_modules key: ${{ runner.os }}-a12-v1-${{ hashFiles('./e2e/a12/package-lock.json') }} - name: Workspace uses: actions/download-artifact@v2 with: name: workspace - path: ./dist + path: dist - name: Spreading Build run: npm run s:a12 @@ -975,14 +977,14 @@ jobs: id: cache uses: actions/cache@v2 with: - path: ./e2e/a12/node_modules + path: e2e/a12/node_modules key: ${{ runner.os }}-a12-v1-${{ hashFiles('./e2e/a12/package-lock.json') }} - name: Workspace uses: actions/download-artifact@v2 with: name: workspace - path: ./dist + path: dist - name: Spreading Build run: npm run s:a12 @@ -1007,14 +1009,14 @@ jobs: id: cache uses: actions/cache@v2 with: - path: ./e2e/a12/node_modules + path: e2e/a12/node_modules key: ${{ runner.os }}-a12-v1-${{ hashFiles('./e2e/a12/package-lock.json') }} - name: Workspace uses: actions/download-artifact@v2 with: name: workspace - path: ./dist + path: dist - name: Spreading Build run: npm run s:a12 @@ -1039,14 +1041,14 @@ jobs: id: cache uses: actions/cache@v2 with: - path: ./e2e/a12/node_modules + path: e2e/a12/node_modules key: ${{ runner.os }}-a12-v1-${{ hashFiles('./e2e/a12/package-lock.json') }} - name: Workspace uses: actions/download-artifact@v2 with: name: workspace - path: ./dist + path: dist - name: Spreading Build run: npm run s:a12 @@ -1071,14 +1073,14 @@ jobs: id: cache uses: actions/cache@v2 with: - path: ./e2e/am/node_modules + path: e2e/am/node_modules key: ${{ runner.os }}-am-v1-${{ hashFiles('./e2e/am/package-lock.json') }} - name: Workspace uses: actions/download-artifact@v2 with: name: workspace - path: ./dist + path: dist - name: Spreading Build run: npm run s:am @@ -1103,14 +1105,14 @@ jobs: id: cache uses: actions/cache@v2 with: - path: ./e2e/am/node_modules + path: e2e/am/node_modules key: ${{ runner.os }}-am-v1-${{ hashFiles('./e2e/am/package-lock.json') }} - name: Workspace uses: actions/download-artifact@v2 with: name: workspace - path: ./dist + path: dist - name: Spreading Build run: npm run s:am @@ -1135,14 +1137,14 @@ jobs: id: cache uses: actions/cache@v2 with: - path: ./e2e/am/node_modules + path: e2e/am/node_modules key: ${{ runner.os }}-am-v1-${{ hashFiles('./e2e/am/package-lock.json') }} - name: Workspace uses: actions/download-artifact@v2 with: name: workspace - path: ./dist + path: dist - name: Spreading Build run: npm run s:am @@ -1167,14 +1169,14 @@ jobs: id: cache uses: actions/cache@v2 with: - path: ./e2e/am/node_modules + path: e2e/am/node_modules key: ${{ runner.os }}-am-v1-${{ hashFiles('./e2e/am/package-lock.json') }} - name: Workspace uses: actions/download-artifact@v2 with: name: workspace - path: ./dist + path: dist - name: Spreading Build run: npm run s:am diff --git a/docs/articles/api/ngMocks.md b/docs/articles/api/ngMocks.md index da7b43ba34..992b0a5384 100644 --- a/docs/articles/api/ngMocks.md +++ b/docs/articles/api/ngMocks.md @@ -22,6 +22,12 @@ access desired elements and instances in fixtures. - [`change()`](ngMocks/change.md) - [`touch()`](ngMocks/touch.md) +## Simulating HTML events + +- [`click()`](ngMocks/click.md) +- [`trigger()`](ngMocks/trigger.md) +- [`event()`](ngMocks/event.md) + ## Manipulating `ng-template` - [`render()`](ngMocks/render.md) diff --git a/docs/articles/api/ngMocks/click.md b/docs/articles/api/ngMocks/click.md new file mode 100644 index 0000000000..4781b264f5 --- /dev/null +++ b/docs/articles/api/ngMocks/click.md @@ -0,0 +1,31 @@ +--- +title: ngMocks.click +description: Documentation about ngMocks.click, a simple tool to click any element in unit tests +--- + +There are several ways how to click an element in Angular unit tests. +However, `.triggerEventHandler` does not respect `disabled` state and does not call a native `click` event. +And `.click` on a `nativeElement` does not allow customizing event properties. + +`ngMocks.click` is a simple tool which covers these limitations: + +- it respects disabled state +- it allows customizations of events +- it causes native events + +```ts +const el = ngMocks.find('a'); + +// we can click debug elements +ngMocks.click(el); + +// we can click native elements +// with custom coordinates +ngMocks.click(el.nativeElement, { + x: 150, + y: 150, +}); +``` + +Under the hood `ngMocks.click` uses [`ngMocks.trigger`](./trigger.md), +therefore all features of [`ngMocks.trigger`](./trigger.md) can be used. diff --git a/docs/articles/api/ngMocks/event.md b/docs/articles/api/ngMocks/event.md new file mode 100644 index 0000000000..8dec3c515a --- /dev/null +++ b/docs/articles/api/ngMocks/event.md @@ -0,0 +1,17 @@ +--- +title: ngMocks.event +description: Documentation about ngMocks.event, a simple interface to create custom events in unit tests +--- + +`ngMocks.event` solves the legacy of IE11, when an event object cannot be created via `new CustomEvent`, but via `document.createEvent`. + +Besides that, `ngMocks.event` provides a simple interface to customize the event properties. + +```ts +const event = ngMocks.event('click', { + x: 1, + y: 2, +}); +``` + +The created event can be dispatched via [`ngMocks.trigger`](./trigger.md#custom-events). diff --git a/docs/articles/api/ngMocks/trigger.md b/docs/articles/api/ngMocks/trigger.md new file mode 100644 index 0000000000..5298ba5513 --- /dev/null +++ b/docs/articles/api/ngMocks/trigger.md @@ -0,0 +1,42 @@ +--- +title: ngMocks.trigger +description: Documentation about ngMocks.trigger, a tool with a simple interface to trigger any events in unit tests +--- + +`ngMocks.trigger` provides a simple interface which allows to trigger all the variety of events and to customize their properties. + +## Common events + +For example, a focus event can be triggered like that: + +```ts +const el = ngMocks.find('input'); +ngMocks.trigger(el, 'focus'); +ngMocks.trigger(el, 'blur'); +ngMocks.trigger(el, 'mouseleave', { + x: 1, + y: 2, +}); +``` + +## Key combinations + +In order to simulate shot keys and test their handlers, +for example, `Control+Shift+Z`: + +```ts +const el = ngMocks.find('input'); +ngMocks.trigger(el, 'keydown.control.shift.z'); +ngMocks.trigger(el, 'keyup.meta.s'); +``` + +## Custom events + +Instead of the name of an event, an event object can be passed. +In order to create an event object [`ngMocks.event`](./event.md) can be used. + +```ts +const el = ngMocks.find('input'); +const event = new CustomEvent('my-event'); +ngMocks.trigger(el, event); +``` diff --git a/docs/sidebars.js b/docs/sidebars.js index b78ca574ca..bb5845b3ca 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -38,6 +38,9 @@ module.exports = { 'api/ngMocks/globalWipe', 'api/ngMocks/change', 'api/ngMocks/touch', + 'api/ngMocks/click', + 'api/ngMocks/trigger', + 'api/ngMocks/event', 'api/ngMocks/render', 'api/ngMocks/hide', 'api/ngMocks/input', diff --git a/libs/ng-mocks/src/lib/mock-helper/cva/mock-helper.change.ts b/libs/ng-mocks/src/lib/mock-helper/cva/mock-helper.change.ts index 6b2b3a4e6b..d422ce4d4d 100644 --- a/libs/ng-mocks/src/lib/mock-helper/cva/mock-helper.change.ts +++ b/libs/ng-mocks/src/lib/mock-helper/cva/mock-helper.change.ts @@ -2,30 +2,25 @@ import { DebugElement } from '@angular/core'; import coreForm from '../../common/core.form'; import { isMockControlValueAccessor } from '../../common/func.is-mock-control-value-accessor'; +import mockHelperTrigger from '../events/mock-helper.trigger'; import mockHelperStubMember from '../mock-helper.stub-member'; import funcGetVca from './func.get-vca'; // default html behavior const triggerInput = (el: DebugElement, value: any): void => { - const target = el.nativeElement; - el.triggerEventHandler('focus', { - target, - }); - - const descriptor = Object.getOwnPropertyDescriptor(target, 'value'); - mockHelperStubMember(target, 'value', value); - el.triggerEventHandler('input', { - target, - }); + mockHelperTrigger(el, 'focus'); + + const descriptor = Object.getOwnPropertyDescriptor(el.nativeElement, 'value'); + mockHelperStubMember(el.nativeElement, 'value', value); + mockHelperTrigger(el, 'input'); + mockHelperTrigger(el, 'change'); if (descriptor) { - Object.defineProperty(target, 'value', descriptor); - target.value = value; + Object.defineProperty(el.nativeElement, 'value', descriptor); + el.nativeElement.value = value; } - el.triggerEventHandler('blur', { - target, - }); + mockHelperTrigger(el, 'blur'); }; const handleKnown = (valueAccessor: any, value: any): boolean => { @@ -51,7 +46,7 @@ const handleKnown = (valueAccessor: any, value: any): boolean => { }; const hasListener = (el: DebugElement): boolean => - el.listeners.filter(listener => listener.name === 'input').length > 0; + el.listeners.filter(listener => listener.name === 'input' || listener.name === 'change').length > 0; const keys = ['onChange', '_onChange', 'changeFn', '_onChangeCallback', 'onModelChange']; diff --git a/libs/ng-mocks/src/lib/mock-helper/cva/mock-helper.touch.ts b/libs/ng-mocks/src/lib/mock-helper/cva/mock-helper.touch.ts index 9fdb39f58d..f32d33511c 100644 --- a/libs/ng-mocks/src/lib/mock-helper/cva/mock-helper.touch.ts +++ b/libs/ng-mocks/src/lib/mock-helper/cva/mock-helper.touch.ts @@ -2,19 +2,14 @@ import { DebugElement } from '@angular/core'; import coreForm from '../../common/core.form'; import { isMockControlValueAccessor } from '../../common/func.is-mock-control-value-accessor'; +import mockHelperTrigger from '../events/mock-helper.trigger'; import funcGetVca from './func.get-vca'; // default html behavior const triggerTouch = (el: DebugElement): void => { - const target = el.nativeElement; - el.triggerEventHandler('focus', { - target, - }); - - el.triggerEventHandler('blur', { - target, - }); + mockHelperTrigger(el, 'focus'); + mockHelperTrigger(el, 'blur'); }; const handleKnown = (valueAccessor: any): boolean => { diff --git a/libs/ng-mocks/src/lib/mock-helper/events/mock-helper.click.ts b/libs/ng-mocks/src/lib/mock-helper/events/mock-helper.click.ts new file mode 100644 index 0000000000..0fd5ec9e4e --- /dev/null +++ b/libs/ng-mocks/src/lib/mock-helper/events/mock-helper.click.ts @@ -0,0 +1,5 @@ +import mockHelperTrigger from './mock-helper.trigger'; + +export default (selector: any, payload?: object) => { + mockHelperTrigger(selector, 'click', payload); +}; diff --git a/libs/ng-mocks/src/lib/mock-helper/events/mock-helper.event.ts b/libs/ng-mocks/src/lib/mock-helper/events/mock-helper.event.ts new file mode 100644 index 0000000000..f25db6b42a --- /dev/null +++ b/libs/ng-mocks/src/lib/mock-helper/events/mock-helper.event.ts @@ -0,0 +1,179 @@ +import mockHelperStub from '../mock-helper.stub'; + +/** + * @see https://developer.mozilla.org/de/docs/Web/Events + */ +const preventBubble = ['focus', 'blur', 'load', 'unload', 'change', 'reset', 'scroll']; + +// istanbul ignore next +function customEvent(event: string, params?: EventInit) { + const initParams = { + bubbles: false, + cancelable: false, + ...params, + }; + const eventObj = document.createEvent('CustomEvent'); + eventObj.initCustomEvent(event, initParams.bubbles, initParams.cancelable, null); + + return eventObj; +} + +const eventCtor = + typeof (Event as any) === 'function' + ? (event: string, init?: EventInit): CustomEvent => new CustomEvent(event, init) + : /* istanbul ignore next */ customEvent; + +const keyMap: Record = { + alt: { + altKey: true, + code: 'AltLeft', + key: 'Alt', + location: 1, + which: 18, + }, + arrowdown: { + code: 'ArrowDown', + key: 'ArrowDown', + location: 0, + which: 40, + }, + arrowleft: { + code: 'ArrowLeft', + key: 'ArrowLeft', + location: 0, + which: 37, + }, + arrowright: { + code: 'ArrowRight', + key: 'ArrowRight', + location: 0, + which: 39, + }, + arrowup: { + code: 'ArrowUp', + key: 'ArrowUp', + location: 0, + which: 38, + }, + backspace: { + code: 'Backspace', + key: 'Backspace', + location: 0, + which: 8, + }, + control: { + code: 'ControlLeft', + ctrlKey: true, + key: 'Control', + location: 1, + which: 17, + }, + enter: { + code: 'Enter', + key: 'Enter', + location: 0, + which: 13, + }, + esc: { + code: 'Escape', + key: 'Escape', + location: 0, + which: 27, + }, + meta: { + code: 'MetaLeft', + key: 'Meta', + location: 1, + metaKey: true, + which: 91, + }, + shift: { + code: 'ShiftLeft', + key: 'Shift', + location: 1, + shiftKey: true, + which: 16, + }, + space: { + code: 'Space', + key: ' ', + location: 0, + which: 32, + }, + tab: { + code: 'Tab', + key: 'Tab', + location: 0, + which: 9, + }, +}; +for (let f = 1; f <= 12; f += 1) { + keyMap[`f${f}`] = { + code: `F${f}`, + key: `F${f}`, + location: 0, + which: f + 111, + }; +} + +const getCode = (char: string): string => { + const code = char.codePointAt(0); + // a-z + if (code && code >= 97 && code <= 122) { + return `Key${char.toUpperCase()}`; + } + // A-Z + if (code && code >= 65 && code <= 90) { + return `Key${char.toUpperCase()}`; + } + // A-Z + if (code && code >= 48 && code <= 57) { + return `Digit${char}`; + } + + return 'Unknown'; +}; + +const applyPayload = (event: Event, payload?: string): void => { + const keyData: object = {}; + for (const key of payload ? payload.split('.') : []) { + let map = keyMap[key]; + if (!map && key.length === 1) { + map = { + code: getCode(key), + key, + }; + } + + if (!map) { + throw new Error(`Unknown event part ${key}`); + } + + mockHelperStub(keyData, map); + } + + if (payload) { + mockHelperStub(event, keyData); + } +}; + +export default ( + event: string, + init?: EventInit, + overrides?: Partial, +): CustomEvent => { + const dot = event.indexOf('.'); + const [eventName, eventPayload] = dot === -1 ? [event] : [event.substr(0, dot), event.substr(dot + 1)]; + const eventObj = eventCtor(eventName, { + bubbles: preventBubble.indexOf(event) === -1, + cancelable: true, + ...init, + }); + applyPayload(eventObj, eventPayload); + + if (overrides) { + mockHelperStub(eventObj, overrides); + } + + return eventObj; +}; diff --git a/libs/ng-mocks/src/lib/mock-helper/events/mock-helper.trigger.ts b/libs/ng-mocks/src/lib/mock-helper/events/mock-helper.trigger.ts new file mode 100644 index 0000000000..f29b064b57 --- /dev/null +++ b/libs/ng-mocks/src/lib/mock-helper/events/mock-helper.trigger.ts @@ -0,0 +1,66 @@ +import { DebugElement } from '@angular/core'; +import { ComponentFixture } from '@angular/core/testing'; + +import isDebugNode from '../format/is-debug-node'; +import isFixture from '../format/is-fixture'; +import isHtmlElement from '../format/is-html-element'; +import mockHelperStub from '../mock-helper.stub'; + +import mockHelperEvent from './mock-helper.event'; + +/** + * @see https://developer.mozilla.org/de/docs/Web/Events + */ +const preventBubble = ['focus', 'blur', 'load', 'unload', 'change', 'reset', 'scroll']; + +const toEventObj = (event: string | UIEvent | KeyboardEvent | MouseEvent | TouchEvent | Event): Event => { + return typeof event === 'string' + ? mockHelperEvent(event, { + bubbles: preventBubble.indexOf(event) === -1, + cancelable: true, + }) + : event; +}; + +const getNativeElement = ( + debugElement: DebugElement | HTMLElement | ComponentFixture | undefined | null, +): HTMLElement | undefined => { + if (isDebugNode(debugElement) || isFixture(debugElement)) { + return debugElement.nativeElement; + } + if (isHtmlElement(debugElement)) { + return debugElement; + } + + return undefined; +}; + +export default ( + debugElement: DebugElement | HTMLElement | ComponentFixture | undefined | null, + eventName: string | UIEvent | KeyboardEvent | MouseEvent | TouchEvent | Event, + payload?: Partial, +) => { + const nativeElement = getNativeElement(debugElement); + + if (!nativeElement) { + throw new Error( + `Cannot trigger ${typeof eventName === 'string' ? eventName : eventName.type} event undefined element`, + ); + } + + // nothing to emit on disabled elements + if ((nativeElement as HTMLInputElement).disabled) { + return; + } + + const event = toEventObj(eventName); + if (!event.target) { + mockHelperStub(event, { + target: nativeElement, + }); + } + if (payload) { + mockHelperStub(event, payload); + } + nativeElement.dispatchEvent(event); +}; diff --git a/libs/ng-mocks/src/lib/mock-helper/format/mock-helper.format-html.ts b/libs/ng-mocks/src/lib/mock-helper/format/mock-helper.format-html.ts index d4d8c06194..ca3b68d575 100644 --- a/libs/ng-mocks/src/lib/mock-helper/format/mock-helper.format-html.ts +++ b/libs/ng-mocks/src/lib/mock-helper/format/mock-helper.format-html.ts @@ -8,6 +8,7 @@ const normalizeValue = (html: string | undefined): string => ? html .replace(new RegExp('\\s+', 'mg'), ' ') .replace(new RegExp('', 'mg'), '') + .replace(new RegExp('