@bespunky/angular-zen
Version:
The Angular tools you always wished were there.
139 lines (131 loc) • 5.36 kB
JavaScript
import { TestBed } from '@angular/core/testing';
import { NgZone } from '@angular/core';
import { CoreModule, DOCUMENT } from '@bespunky/angular-zen/core';
class MockElement {
constructor(tagName) {
this.tagName = tagName;
this.children = [];
}
remove() {
if (this.parentElement)
this.parentElement.removeChild(this);
}
removeChild(node) {
if (node.parentElement !== this)
return;
const index = this.children.indexOf(node);
if (index > -1) {
this.children.splice(index, 1);
node.parentElement = null;
}
}
appendChild(node) {
this.children.push(node);
node.parentElement = this;
if (node.onload instanceof Function)
setTimeout(node.onload, 0);
}
querySelectorAll(selector) {
throw new Error(`
Providing a general implementation for querySelectorAll() to support all cases is to complex.
Use jest.spyOn() and fake this to provide an implementation for the specific use case.
See MockElement.extractXXXFromSelector() methods for utils.
`);
}
/**
* Extracts an array of {name, value} objects mapping the attributes from the specified selector string.
* Attributes with no value will be mapped with wildcard value (i.e. '**').
*
* @param {string} selector
* @returns {*}
*/
extractAttributesFromSelector(selector) {
// Searches for [key="value"] and [key] groups and extracts the attribute and value from each
const regex = /(?:(\[(?<attr>\w+)(?:="(?<value>[^\]]+)")?)\]*)/g;
let match;
const attributes = [];
while ((match = regex.exec(selector)) !== null) {
// This is necessary to avoid infinite loops with zero-width matches
if (match.index === regex.lastIndex)
regex.lastIndex++;
attributes.push({ name: match.groups?.['attr'], value: match.groups?.['value'] || '**' });
}
return attributes;
}
}
class MockScriptElement extends MockElement {
constructor() { super('script'); }
}
class MockLinkElement extends MockElement {
constructor() {
super('link');
this.relList = {
add: (...tokens) => this.rel = tokens.join(' ')
};
}
}
class MockHeadElement extends MockElement {
constructor() { super('head'); }
}
/**
* Configures a testing module provided with a ready-to-use mock for [`DOCUMENT`](/miscellaneous/variables.html#DOCUMENT).
*
* Any element created using `DocumentRef.nativeDocument.createElement()` will go through this mock.
* If a script element was requested, a `MockScriptElement` object is returned.
* If a link element was requested, the `MockLinkElement` object is returned.
* If any other tag name is requested, a new `MockElement` object is returned.
*
* Used when testing head related services (e.g. HeadService, LazyLoaderService).
*
* Internally, this plants the following structure in [`DocumentRef`](/additional-documentation/coremodule/documentref.html):
*
* `DocumentRef.nativeDocument.head -> MockHeadElement`
*
* `DocumentRef.nativeDocument.createElement -> () => MockScriptElement | MockLinkElement | MockElement(<tagName>)`
*
* The returned mocks can be deconstructed like so:
* @example
* let mockHeadElement: MockHeadElement;
* let mockDocument : any;
* ({ mockHeadElement, mockDocument } = setupDocumentRefMock()); // mockDocument is also a jest.SpyInstance
*/
function setupDocumentRefMock() {
const createElement = jest.fn((tagName) => {
return tagName === 'script' ? new MockScriptElement() :
tagName === 'link' ? new MockLinkElement() :
new MockElement(tagName);
});
// Create a stub for the head element
const mockHeadElement = new MockHeadElement();
// Mock for the DocumentRef.nativeDocument object
// Create the document object allowing to spy on its createElement() function.
// When an element should be created, substitute it for the appropriate mock
const mockDocument = { createElement, head: mockHeadElement };
TestBed.configureTestingModule({
imports: [CoreModule],
providers: [
{ provide: DOCUMENT, useValue: mockDocument }
]
});
return { mockHeadElement, mockDocument };
}
/**
* Wraps the `navigate` and `navigateByUrl` methods of the router with a call to `NgZone.run()`.
* Fixes the warning when using the router in unit tests.
*
* @export
* @param {Router} router The router instance.
*/
function forceRoutingInsideAngularZone(router) {
const zone = TestBed.inject(NgZone);
const navigate = router.navigate.bind(router);
const navigateByUrl = router.navigateByUrl.bind(router);
// Fix for angular's warning of running navigation outside angular's zone
jest.spyOn(router, 'navigate').mockImplementation((...args) => zone.run(() => navigate(...args)));
jest.spyOn(router, 'navigateByUrl').mockImplementation((...args) => zone.run(() => navigateByUrl(...args)));
}
/**
* Generated bundle index. Do not edit.
*/
export { MockElement, MockHeadElement, MockLinkElement, MockScriptElement, forceRoutingInsideAngularZone, setupDocumentRefMock };
//# sourceMappingURL=bespunky-angular-zen-core-testing.mjs.map