@ngneat/spectator
Version:
A powerful tool to simplify your Angular tests
2,469 lines • 91.8 kB
JavaScript
import * as i0 from '@angular/core';
import { isStandalone, OutputEmitterRef, DebugElement, ElementRef, ChangeDetectorRef, NO_ERRORS_SCHEMA, reflectComponentType, Component, NgModule, NgZone } from '@angular/core';
import { TestBed, tick, DeferBlockState, DeferBlockBehavior, waitForAsync } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { Observable, ReplaySubject, Subject } from 'rxjs';
import { queries, getDefaultNormalizer } from '@testing-library/dom';
import { BrowserDynamicTestingModule } from '@angular/platform-browser-dynamic/testing';
import { restoreSetTimeout } from '@ngneat/spectator/internals';
import $ from 'jquery';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { Router, ActivatedRoute, convertToParamMap, ActivatedRouteSnapshot } from '@angular/router';
import { map } from 'rxjs/operators';
import { RouterTestingModule } from '@angular/router/testing';
import { HttpClient } from '@angular/common/http';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
function doesServiceImplementsOnDestroy(testedService) {
return 'ngOnDestroy' in testedService && typeof testedService['ngOnDestroy'] === 'function';
}
function isString(value) {
return typeof value === 'string';
}
function isNumber(value) {
return typeof value === 'number';
}
function isType(v) {
return typeof v === 'function';
}
function isHTMLOptionElementArray(value) {
return Array.isArray(value) && !!value.length && value.every((item) => item instanceof HTMLOptionElement);
}
function isObject(v) {
return v && typeof v === 'object';
}
const parseKeyOptions = (keyOrKeyCode) => {
if (isNumber(keyOrKeyCode) && keyOrKeyCode) {
return { key: false, keyCode: keyOrKeyCode, modifiers: {} };
}
if (isString(keyOrKeyCode) && keyOrKeyCode) {
return parseKey(keyOrKeyCode);
}
if (isObject(keyOrKeyCode)) {
const parsedKey = parseKey(keyOrKeyCode.key);
return {
...parsedKey,
keyCode: keyOrKeyCode.keyCode,
};
}
throw new Error('keyboard.pressKey() requires a valid key or keyCode');
};
const parseKey = (keyStr) => {
if (keyStr.indexOf('.') < 0 || '.' === keyStr) {
return { key: keyStr, keyCode: false, modifiers: {} };
}
const keyParts = keyStr.split('.');
const key = keyParts.pop();
const modifiers = keyParts.reduce((mods, part) => {
switch (part) {
case 'control':
case 'ctrl':
mods.control = true;
return mods;
case 'shift':
mods.shift = true;
return mods;
case 'alt':
mods.alt = true;
return mods;
case 'meta':
case 'cmd':
case 'win':
mods.meta = true;
return mods;
default:
throw new Error(`invalid key modifier: ${part ? part : 'undefined'}, keyStr: ${keyStr}`);
}
}, { alt: false, control: false, shift: false, meta: false });
return { key, keyCode: false, modifiers };
};
/**
* Credit - Angular Material
*/
/** Creates a browser MouseEvent with the specified options. */
function createMouseEvent(type, x = 0, y = 0, button = 0) {
const event = document.createEvent('MouseEvent');
event.initMouseEvent(type, true, false, window, 0, x, y, x, y, false, false, false, false, button, null);
// `initMouseEvent` doesn't allow us to pass the `buttons` and
// defaults it to 0 which looks like a fake event.
Object.defineProperty(event, 'buttons', { get: () => 1 });
return event;
}
/**
* Creates a browser TouchEvent with the specified pointer coordinates.
*/
function createTouchEvent(type, pageX = 0, pageY = 0) {
// In favor of creating events that work for most of the browsers, the event is created
// as a basic UI Event. The necessary details for the event will be set manually.
const event = new UIEvent(type, {
bubbles: true,
cancelable: true,
view: window,
detail: 0,
});
// Most of the browsers don't have a "initTouchEvent" method that can be used to define
// the touch details.
Object.defineProperties(event, {
touches: { value: [{ pageX, pageY }] },
});
return event;
}
/** Dispatches a keydown event from an element. */
function createKeyboardEvent(type, keyOrKeyCode, target) {
const { key, keyCode, modifiers } = parseKeyOptions(keyOrKeyCode);
const event = document.createEvent('KeyboardEvent');
const originalPreventDefault = event.preventDefault;
// Firefox does not support `initKeyboardEvent`, but supports `initKeyEvent`.
if (event.initKeyEvent) {
event.initKeyEvent(type, true, true, window, modifiers.control, modifiers.alt, modifiers.shift, modifiers.meta, keyCode);
}
else {
// `initKeyboardEvent` expects to receive modifiers as a whitespace-delimited string
// See https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/initKeyboardEvent
const modifiersStr = (modifiers.control ? 'Control ' : '' + modifiers.alt ? 'Alt ' : '' + modifiers.shift ? 'Shift ' : '' + modifiers.meta ? 'Meta' : '').trim();
event.initKeyboardEvent(type, true /* canBubble */, true /* cancelable */, window /* view */, 0 /* char */, key /* key */, 0 /* location */, modifiersStr /* modifiersList */, false /* repeat */);
}
// Webkit Browsers don't set the keyCode when calling the init function.
// See related bug https://bugs.webkit.org/show_bug.cgi?id=16735
Object.defineProperties(event, {
code: { get: () => keyCode },
keyCode: { get: () => keyCode },
key: { get: () => key },
target: { get: () => target },
altKey: { get: () => !!modifiers.alt },
ctrlKey: { get: () => !!modifiers.control },
shiftKey: { get: () => !!modifiers.shift },
metaKey: { get: () => !!modifiers.meta },
});
// IE won't set `defaultPrevented` on synthetic events so we need to do it manually.
// eslint-disable-next-line
event.preventDefault = function () {
Object.defineProperty(event, 'defaultPrevented', { configurable: true, get: () => true });
return originalPreventDefault.apply(this, arguments);
};
return event;
}
/** Creates a fake event object with any desired event type. */
function createFakeEvent(type, canBubble = false, cancelable = true) {
const event = document.createEvent('Event');
event.initEvent(type, canBubble, cancelable);
return event;
}
/**
* Credit - Angular Material
*/
/**
* Utility to dispatch any event on a Node.
*
* @publicApi
*/
function dispatchEvent(node, event) {
node.dispatchEvent(event);
return event;
}
/**
* Shorthand to dispatch a fake event on a specified node.
*
* dispatchFakeEvent(element, 'mousedown');
*
* @publicApi
*/
function dispatchFakeEvent(node, type, canBubble) {
return dispatchEvent(node, createFakeEvent(type, canBubble));
}
/**
* Shorthand to dispatch a keyboard event with a specified key.
*
* dispatchKeyboardEvent(calendarBodyEl, 'keydown', 'LEFT_ARROW');
*
* @publicApi
*/
function dispatchKeyboardEvent(node, type, keyOrKeyCode, target) {
return dispatchEvent(node, createKeyboardEvent(type, keyOrKeyCode, target));
}
/**
* Shorthand to dispatch a mouse event on the specified coordinates.
*
* dispatchMouseEvent(rippleTarget, 'mousedown', 50, 75);
* dispatchMouseEvent(rippleTarget, 'mouseup');
*
* @publicApi
*/
function dispatchMouseEvent(node, type, x = 0, y = 0, event = createMouseEvent(type, x, y)) {
return dispatchEvent(node, event);
}
/**
* Shorthand to dispatch a touch event on the specified coordinates.
*
* dispatchTouchEvent(rippleTarget, 'touchstart');
*
* @publicApi
*/
function dispatchTouchEvent(node, type, x = 0, y = 0) {
return dispatchEvent(node, createTouchEvent(type, x, y));
}
class DOMSelector {
// Wrap selector functions in a class to make reflection easier in getChild
constructor(execute) {
this.execute = execute;
}
}
const byLabel = (matcher, options) => new DOMSelector((el) => queries.queryAllByLabelText(el, matcher, options));
const byPlaceholder = (matcher, options) => new DOMSelector((el) => queries.queryAllByPlaceholderText(el, matcher, options));
const byText = (matcher, options) => new DOMSelector((el) => queries.queryAllByText(el, matcher, options));
const byTextContent = (matcher, options) => {
let textContentMatcher;
const normalizer = options?.normalizer || getDefaultNormalizer(options);
const getTextContent = (elem) => normalizer(elem?.textContent ?? '');
if (typeof matcher === 'string' || typeof matcher === 'number') {
textContentMatcher = (_, elem) => {
if (options?.exact === false) {
return getTextContent(elem).toLowerCase().indexOf(matcher.toString().toLowerCase()) >= 0;
}
return getTextContent(elem) === matcher.toString();
};
}
else if (matcher instanceof RegExp) {
textContentMatcher = (_, elem) => matcher.test(getTextContent(elem));
}
else if (typeof matcher === 'function') {
textContentMatcher = (_, elem) => matcher(getTextContent(elem), elem);
}
else {
throw new Error(`Matcher type not supported: ${typeof matcher}`);
}
return new DOMSelector((el) => queries.queryAllByText(el, textContentMatcher, options));
};
const byAltText = (matcher, options) => new DOMSelector((el) => queries.queryAllByAltText(el, matcher, options));
const byTitle = (matcher, options) => new DOMSelector((el) => queries.queryAllByTitle(el, matcher, options));
const byTestId = (matcher, options) => new DOMSelector((el) => queries.queryAllByTestId(el, matcher, options));
const byValue = (matcher, options) => new DOMSelector((el) => queries.queryAllByDisplayValue(el, matcher, options));
const byRole = (matcher, options) => new DOMSelector((el) => queries.queryAllByRole(el, matcher, options));
function isRunningInJsDom() {
return navigator.userAgent.includes('Node.js') || navigator.userAgent.includes('jsdom');
}
function coerceArray(value) {
return Array.isArray(value) ? value : [value];
}
function declareInModule(moduleMetadata, type) {
if (isStandalone(type)) {
moduleMetadata.imports.push(type);
}
else {
moduleMetadata.declarations.push(type);
}
}
/** Property added to HTML Elements to ensure we don't double-patch focus methods on an element. */
const IS_FOCUS_PATCHED_PROP = Symbol('isFocusPatched');
/** Ensures that a single set of matching focus and blur events occur when HTMLElement.focus() is called. */
class FocusEventWatcher {
constructor(element) {
this.element = element;
/** Set to true when browser sends a blur event for priorActiveElement */
this._blurred = false;
/** Set to true when browser sends a focus event for element */
this._focused = false;
this.element.addEventListener('focus', this);
this.priorActiveElement = element.ownerDocument.activeElement;
this.priorActiveElement?.addEventListener('blur', this);
}
handleEvent({ type }) {
if (type === 'focus') {
this._focused = true;
}
else if (type === 'blur') {
this._blurred = true;
}
}
/**
* If focus and blur events haven't occurred, fire fake ones.
*/
ensureFocusEvents() {
this.element.removeEventListener('focus', this);
this.priorActiveElement?.removeEventListener('blur', this);
// Ensure priorActiveElement is blurred
if (!this._blurred && this.priorActiveElement) {
dispatchFakeEvent(this.priorActiveElement, 'blur');
}
if (!this._focused) {
dispatchFakeEvent(this.element, 'focus'); // Needed to cause focus event
}
}
}
/**
* Patches an element's focus and blur methods to emit events consistently and predictably in tests.
* This is necessary, because some browsers, like IE11, will call the focus handlers asynchronously,
* while others won't fire them at all if the browser window is not focused.
*
* patchElementFocus(triggerEl);
*/
function patchElementFocus(element) {
// https://github.com/ngneat/spectator/issues/373 - Don't patch when using JSDOM, eg in Jest
if (!isRunningInJsDom() && element[IS_FOCUS_PATCHED_PROP] === undefined) {
const originalFocus = element.focus.bind(element);
element.focus = (options) => {
const focusEventWatcher = new FocusEventWatcher(element);
// Sets document.activeElement. May or may not send focus + blur events
originalFocus(options);
focusEventWatcher.ensureFocusEvents();
};
element.blur = () => dispatchFakeEvent(element, 'blur');
element[IS_FOCUS_PATCHED_PROP] = true;
}
}
function getChildren(debugElementRoot) {
return (directiveOrSelector, options = { root: false, read: undefined }) => {
if (directiveOrSelector instanceof DOMSelector) {
return directiveOrSelector.execute(debugElementRoot.nativeElement);
}
const debugElements = debugElementRoot.queryAll(isString(directiveOrSelector) ? By.css(directiveOrSelector) : By.directive(directiveOrSelector));
if (options.read) {
return debugElements.map((debug) => debug.injector.get(options.read));
}
if (isString(directiveOrSelector)) {
return debugElements.map((debug) => debug.nativeElement);
}
return debugElements.map((debug) => debug.injector.get(directiveOrSelector));
};
}
function setProps(componentRef, keyOrKeyValues, value) {
if (isString(keyOrKeyValues)) {
componentRef.setInput(keyOrKeyValues, value);
}
else {
// eslint-disable-next-line guard-for-in
for (const p in keyOrKeyValues) {
componentRef.setInput(p, keyOrKeyValues[p]);
}
}
return componentRef.instance;
}
function setHostProps(componentRef, keyOrKeyValues, value) {
if (isString(keyOrKeyValues)) {
componentRef.instance[keyOrKeyValues] = value;
}
else {
// eslint-disable-next-line guard-for-in
for (const p in keyOrKeyValues) {
componentRef.instance[p] = keyOrKeyValues[p];
}
}
return componentRef.instance;
}
/**
* Focuses a select element, selects the correct options and dispatches
* the `change` event, simulating the user selecting an option
* @param options Options to be selected.
* @param element Element onto which to select the options.
* @param config Object with extra config to dispatch change event when option selected
*
* selectOption('al' | ['al', 'ab'], select, config);
*/
function selectOption(options, element, config) {
if (!(element instanceof HTMLSelectElement)) {
return;
}
element.focus();
if (isString(options)) {
const option = element.querySelector(`option[value="${options}"]`);
if (!option) {
return;
}
setOptionSelected(option, element, config);
}
else if (options instanceof HTMLOptionElement) {
setOptionSelected(options, element, config);
}
else {
if (!element.multiple) {
return;
}
if (isHTMLOptionElementArray(options)) {
options.forEach((option) => setOptionSelected(option, element, config));
}
else {
element.querySelectorAll('option').forEach((opt) => {
if (options.includes(opt.value)) {
setOptionSelected(opt, element, config);
}
});
}
}
}
/**
* Set the option in the HTMLSelectElement to selected
* @param option HTMLOptionElement to select
* @param select HTMLSelectElement to add the options to
* @param config Object with extra config to dispatch change event when option selected
*
* setOptionSelected(option, element, config);
*/
function setOptionSelected(option, select, config) {
option.selected = true;
if (config.emitEvents) {
dispatchFakeEvent(select, 'change', true);
}
}
/**
* Focuses an input or textarea, sets its value and dispatches
* the `input` or `textarea` event, simulating the user typing.
* @param value Value to be set on the input.
* @param element Element onto which to set the value.
*
* typeInElement('al', input);
*/
function typeInElement(value, element) {
if (!(element instanceof HTMLInputElement) && !(element instanceof HTMLTextAreaElement)) {
return;
}
element.focus();
element.value = value;
dispatchFakeEvent(element, 'input', true);
}
/**
* @internal
*/
class BaseSpectator {
inject(token) {
return TestBed.inject ? TestBed.inject(token) : TestBed.get(token);
}
/**
* Execute any pending effects.
*/
flushEffects() {
TestBed.flushEffects();
}
}
const KEY_UP = 'keyup';
/**
* @internal
*/
class DomSpectator extends BaseSpectator {
constructor(fixture, debugElement, instance, element) {
super();
this.fixture = fixture;
this.debugElement = debugElement;
this.instance = instance;
this.element = element;
}
inject(token) {
return super.inject(token);
}
detectChanges() {
this.fixture.detectChanges();
}
query(directiveOrSelector, options) {
if ((options || {}).root) {
if (isString(directiveOrSelector)) {
return document.querySelector(directiveOrSelector);
}
if (directiveOrSelector instanceof DOMSelector) {
return directiveOrSelector.execute(document)[0] || null;
}
return getChildren(this.getRootDebugElement())(directiveOrSelector, options)[0] || null;
}
if (options?.parentSelector) {
const debugElement = this.getDebugElement(options.parentSelector);
if (!debugElement) {
/* eslint-disable no-console */
console.error(`${directiveOrSelector} does not exists`);
return null;
}
return (getChildren(debugElement)(directiveOrSelector, {
root: options.root,
read: options.read,
})[0] || null);
}
return getChildren(this.debugElement)(directiveOrSelector, options)[0] || null;
}
queryAll(directiveOrSelector, options) {
if ((options || {}).root) {
if (isString(directiveOrSelector)) {
return Array.from(document.querySelectorAll(directiveOrSelector));
}
if (directiveOrSelector instanceof DOMSelector) {
return directiveOrSelector.execute(document);
}
return getChildren(this.getRootDebugElement())(directiveOrSelector, options);
}
if (options?.parentSelector) {
const debugElement = this.getDebugElement(options.parentSelector);
if (!debugElement) {
/* eslint-disable no-console */
console.error(`${directiveOrSelector} does not exists`);
return [];
}
return getChildren(debugElement)(directiveOrSelector, {
root: options.root,
read: options.read,
});
}
return getChildren(this.debugElement)(directiveOrSelector, options);
}
queryLast(directiveOrSelector, options) {
let result = [];
if ((options || {}).root) {
if (isString(directiveOrSelector)) {
result = Array.from(document.querySelectorAll(directiveOrSelector));
}
else if (directiveOrSelector instanceof DOMSelector) {
result = directiveOrSelector.execute(document);
}
else {
result = getChildren(this.getRootDebugElement())(directiveOrSelector, options);
}
}
else if (options?.parentSelector) {
const debugElement = this.getDebugElement(options.parentSelector);
if (!debugElement) {
/* eslint-disable no-console */
console.error(`${directiveOrSelector} does not exists`);
result = [];
}
else {
result = getChildren(debugElement)(directiveOrSelector, {
root: options.root,
read: options.read,
});
}
}
else {
result = getChildren(this.debugElement)(directiveOrSelector, options);
}
if (result && result.length) {
return result[result.length - 1];
}
return null;
}
output(output) {
const eventEmitter = this.instance[output];
if (!(eventEmitter instanceof Observable) && !(eventEmitter instanceof OutputEmitterRef)) {
throw new Error(`${String(output)} is not an @Output or an output function`);
}
return eventEmitter;
}
tick(millis) {
tick(millis);
this.detectChanges();
}
click(selector = this.element) {
const element = this.getNativeElement(selector);
if (!(element instanceof HTMLElement)) {
throw new Error(`Cannot click: ${selector} is not a HTMLElement`);
}
element.click();
this.detectChanges();
}
blur(selector = this.element) {
const element = this.getNativeElement(selector);
if (!(element instanceof HTMLElement)) {
throw new Error(`Cannot blur: ${selector} is not a HTMLElement`);
}
patchElementFocus(element);
element.blur();
this.detectChanges();
}
focus(selector = this.element) {
const element = this.getNativeElement(selector);
if (!(element instanceof HTMLElement)) {
throw new Error(`Cannot focus: ${selector} is not a HTMLElement`);
}
patchElementFocus(element);
element.focus();
this.detectChanges();
}
dispatchMouseEvent(selector = this.element, type, x = 0, y = 0, event = createMouseEvent(type, x, y)) {
const element = this.getNativeElement(selector);
if (!(element instanceof Node)) {
throw new Error(`Cannot dispatch mouse event: ${selector} is not a node`);
}
const dispatchedEvent = dispatchMouseEvent(element, type, x, y, event);
this.detectChanges();
return dispatchedEvent;
}
dispatchKeyboardEvent(selector = this.element, type, keyOrKeyCode, target) {
const element = this.getNativeElement(selector);
if (!(element instanceof Node)) {
throw new Error(`Cannot dispatch keyboard event: ${selector} is not a node`);
}
const event = dispatchKeyboardEvent(element, type, keyOrKeyCode, target);
this.detectChanges();
return event;
}
dispatchFakeEvent(selector = this.element, type, canBubble) {
const event = dispatchFakeEvent(this.getNativeElement(selector), type, canBubble);
this.detectChanges();
return event;
}
triggerEventHandler(directiveOrSelector, eventName, eventObj, options) {
const triggerDebugElement = this.getDebugElement(directiveOrSelector, options);
if (!triggerDebugElement) {
/* eslint-disable no-console */
console.error(`${directiveOrSelector} does not exists`);
return;
}
triggerDebugElement.triggerEventHandler(eventName, eventObj);
this.detectChanges();
}
get keyboard() {
return {
pressKey: (key, selector = this.element, event = KEY_UP) => {
this.dispatchKeyboardEvent(selector, event, key);
},
pressEscape: (selector = this.element, event = KEY_UP) => {
this.dispatchKeyboardEvent(selector, event, { key: 'Escape', keyCode: 27 });
},
pressEnter: (selector = this.element, event = KEY_UP) => {
this.dispatchKeyboardEvent(selector, event, { key: 'Enter', keyCode: 13 });
},
pressTab: (selector = this.element, event = KEY_UP) => {
this.dispatchKeyboardEvent(selector, event, { key: 'Tab', keyCode: 9 });
},
pressBackspace: (selector = this.element, event = KEY_UP) => {
this.dispatchKeyboardEvent(selector, event, { key: 'Backspace', keyCode: 8 });
},
};
}
get mouse() {
return {
contextmenu: (selector = this.element) => {
this.dispatchMouseEvent(selector, 'contextmenu');
},
dblclick: (selector = this.element) => {
this.dispatchMouseEvent(selector, 'dblclick');
},
};
}
dispatchTouchEvent(selector = this.element, type, x = 0, y = 0) {
dispatchTouchEvent(this.getNativeElement(selector), type, x, y);
this.detectChanges();
}
typeInElement(value, selector = this.element) {
typeInElement(value, this.getNativeElement(selector));
this.detectChanges();
}
selectOption(selector = this.element, options, config = { emitEvents: true }) {
if (!selector) {
throw new Error(`Cannot find select: ${selector}`);
}
selectOption(options, this.getNativeElement(selector), config);
this.detectChanges();
}
getNativeElement(selector) {
let element;
// Support global objects window and document
if (selector === window || selector === document) {
return selector;
}
if (isString(selector)) {
const exists = this.debugElement.query(By.css(selector));
if (exists) {
element = exists.nativeElement;
}
else {
/* eslint-disable no-console */
console.error(`${selector} does not exists`);
}
}
else if (selector instanceof DOMSelector) {
element = selector.execute(document)[0] || null;
}
else {
if (selector instanceof DebugElement || selector instanceof ElementRef) {
element = selector.nativeElement;
}
else {
element = selector;
}
}
return element;
}
getDebugElement(directiveOrSelector, options) {
const debugElement = options?.root ? this.getRootDebugElement() : this.debugElement;
if (isString(directiveOrSelector)) {
return debugElement.query(By.css(directiveOrSelector));
}
else if (directiveOrSelector instanceof DebugElement) {
return directiveOrSelector;
}
else {
return debugElement.query(By.directive(directiveOrSelector));
}
}
getRootDebugElement() {
let element = this.debugElement;
/**
* This bounded loop call is required to access the debug element for
* root dom element
*/
while (true) {
if (!element) {
throw Error('Unable to find root element');
}
if (!element.parent) {
// Found the root element
return element;
}
element = element.parent;
}
}
}
/**
* @publicApi
*/
class Spectator extends DomSpectator {
constructor(fixture, debugElement, instance, element) {
super(fixture, debugElement, instance, element);
this.fixture = fixture;
this.debugElement = debugElement;
this.instance = instance;
this.element = element;
}
get component() {
return this.instance;
}
inject(token, fromComponentInjector = false) {
if (fromComponentInjector) {
return this.debugElement.injector.get(token);
}
return super.inject(token);
}
detectComponentChanges() {
if (this.debugElement) {
this.debugElement.injector.get(ChangeDetectorRef).detectChanges();
}
else {
this.detectChanges();
}
}
setInput(input, value) {
setProps(this.fixture.componentRef, input, value);
// Force cd on the host component for cases such as: https://github.com/ngneat/spectator/issues/539
this.detectChanges();
// Force cd on the tested component
this.debugElement.injector.get(ChangeDetectorRef).detectChanges();
}
deferBlock(deferBlockIndex = 0) {
return this._deferBlocksForGivenFixture(deferBlockIndex, this.fixture.getDeferBlocks());
}
/**
*
* @param deferBlockFixtures Defer block fixture
* @returns deferBlock object with methods to access the defer blocks
*/
_deferBlocksForGivenFixture(deferBlockIndex = 0, deferBlockFixtures) {
return {
renderComplete: async () => {
const renderedDeferFixture = await this._renderDeferStateAndGetFixture(DeferBlockState.Complete, deferBlockIndex, deferBlockFixtures);
return this._childrenDeferFixtures(renderedDeferFixture);
},
renderPlaceholder: async () => {
const renderedDeferFixture = await this._renderDeferStateAndGetFixture(DeferBlockState.Placeholder, deferBlockIndex, deferBlockFixtures);
return this._childrenDeferFixtures(renderedDeferFixture);
},
renderLoading: async () => {
const renderedDeferFixture = await this._renderDeferStateAndGetFixture(DeferBlockState.Loading, deferBlockIndex, deferBlockFixtures);
return this._childrenDeferFixtures(renderedDeferFixture);
},
renderError: async () => {
const renderedDeferFixture = await this._renderDeferStateAndGetFixture(DeferBlockState.Error, deferBlockIndex, deferBlockFixtures);
return this._childrenDeferFixtures(renderedDeferFixture);
},
};
}
/**
* Renders the given defer block state and returns the defer block fixture
*
* @param deferBlockState complete, placeholder, loading or error
* @param deferBlockIndex index of the defer block to render
* @param deferBlockFixtures Defer block fixture
* @returns Defer block fixture
*/
async _renderDeferStateAndGetFixture(deferBlockState, deferBlockIndex = 0, deferBlockFixtures) {
const deferFixture = (await deferBlockFixtures)[deferBlockIndex];
await deferFixture.render(deferBlockState);
return deferFixture;
}
/**
*
* @param deferFixture Defer block fixture
* @returns deferBlock object with methods to access the nested defer blocks
*/
_childrenDeferFixtures(deferFixture) {
return {
deferBlock: (deferBlockIndex = 0) => this._deferBlocksForGivenFixture(deferBlockIndex, deferFixture.getDeferBlocks()),
};
}
}
function addMatchers(matchers) {
if (!matchers)
return;
if (typeof jasmine !== 'undefined') {
jasmine.addMatchers(matchers);
}
else {
// Jest isn't on the global scope when using ESM so we
// assume that it's Jest if Jasmine is not defined
const jestExpectExtend = {};
for (const key of Object.keys(matchers)) {
if (key.startsWith('to'))
jestExpectExtend[key] = matchers[key]().compare;
}
expect.extend(jestExpectExtend);
}
}
/**
* @license
* Copyright Netanel Basal. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://github.com/NetanelBasal/spectator/blob/master/LICENSE
*/
function hex2rgb(hex) {
const h = hex.replace('#', '');
const matches = h.match(new RegExp('(.{' + h.length / 3 + '})', 'g'));
const [r, g, b] = matches.map((match) => parseInt(match.length === 1 ? match + match : match, 16));
return `rgb(${r},${g},${b})`;
}
function isHex(value) {
return /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/.test(value);
}
function trim(value) {
return (value || '').replace(/\s/g, '');
}
/** Credit: https://github.com/unindented/custom-jquery-matchers/tree/master/packages/custom-jquery-matchers */
// This should be imported before `jquery` since this library unpatches the `setTimeout`,
// so jQuery won't setup a timer, that might be captured by zone.js.
restoreSetTimeout();
const hasProperty = (actual, expected) => {
return expected === undefined ? actual !== undefined : actual === expected;
};
const containsProperty = (actual, expected) => {
return expected === undefined ? true : actual.includes(expected);
};
const checkProperty = (el, prop, predicate) => {
let pass = false;
let failing = '';
for (const key of Object.keys(prop)) {
const actual = $(el).prop(key);
const addendum = prop[key] !== undefined ? ` with value '${prop[key]}'` : '';
pass = predicate(actual, prop[key]);
failing = !pass ? `'${prop}'${addendum}, but had '${actual}'` : '';
}
const message = () => `Expected element${pass ? ' not' : ''} to have property ${failing}`;
return { pass, message };
};
const hasCss = (el, css) => {
let prop;
let value;
const $el = $(el);
for (prop in css) {
if (css.hasOwnProperty(prop)) {
value = css[prop];
if (isHex(value)) {
value = hex2rgb(css[prop]);
}
if (value === 'auto' && $el.get(0).style[prop] === 'auto') {
continue;
}
if (trim($el.get(0).style[prop]) !== trim(value) &&
trim(el.style[prop]) !== trim(value) &&
trim(el.style.getPropertyValue(prop)) !== trim(value)) {
return false;
}
}
}
return true;
};
const hasSameText = (el, expected, options) => {
if (expected && Array.isArray(expected)) {
let actual;
let pass = false;
let failing;
$(el).each((i, e) => {
actual = options.exact && !options.trim ? $(e).text() : $.trim($(e).text());
pass = options.exact ? actual === expected[i] : actual.includes(expected[i]);
if (!pass) {
failing = expected[i];
return false;
}
});
const message = () => `Expected element${pass ? ' not' : ''} to have${options.exact ? ' exact' : ''} text '${failing}', but had '${actual}'`;
return { pass, message };
}
const actual = options.exact && !options.trim ? $(el).text() : $.trim($(el).text());
if (expected && typeof expected !== 'string') {
const pass = expected(actual);
const message = () => `Expected element${pass ? ' not' : ''} to have${options.exact ? ' exact' : ''} text matching '${expected}',` + ` but had '${actual}'`;
return { pass, message };
}
const pass = options.exact && !Array.isArray(expected) ? actual === expected : actual.indexOf(expected) !== -1;
const message = () => `Expected element${pass ? ' not' : ''} to have${options.exact ? ' exact' : ''} text '${expected}', but had '${actual}'`;
return { pass, message };
};
const comparator = (func) => () => ({
compare: func,
});
/**
*
* expect('.zippy__content').not.toExist();
*/
const toExist = comparator((el) => {
const actual = $(el).length;
const pass = actual > 0;
const message = () => `Expected ${el} element${pass ? ' not' : ''} to exist`;
return { pass, message };
});
/**
*
* expect('.zippy__content').toHaveLength(3);
*/
const toHaveLength = comparator((el, expected) => {
const actual = $(el).length;
const pass = actual === expected;
const message = () => `Expected element${pass ? ' not' : ''} to have length ${expected}, but had ${actual}`;
return { pass, message };
});
/**
*
* expect('.zippy__content').toHaveId('ID');
*/
const toHaveId = comparator((el, expected) => {
const actual = $(el).attr('id');
const pass = actual === expected;
const message = () => `Expected element${pass ? ' not' : ''} to have ID '${expected}', but had '${actual}'`;
return { pass, message };
});
/**
* This validates classes in strict order. If you want to validate classes in any order,
* just set the strict config option to false.
*
* expect('.zippy__content').toHaveClass('class');
* expect('.zippy__content').toHaveClass('class-a, class-b');
* expect('.zippy__content').toHaveClass(['class-a, class-b']);
* expect('.zippy__content').toHaveClass(['class-b, class-a']);
* expect('.zippy__content').not.toHaveClass(['class-b, class-a'], { strict: true });
*/
const toHaveClass = comparator((el, expected, options = { strict: true }) => {
if (expected && Array.isArray(expected)) {
const actual = $(el).attr('class');
const expectedClasses = expected.join(' ');
const pass = options.strict ? $(el).hasClass(expectedClasses) : expected.every((e) => $(el).hasClass(e));
const message = () => `Expected element${pass ? ' not' : ''} to have value '${expectedClasses}', but had '${actual}'`;
return { pass, message };
}
const actual = $(el).attr('class');
const pass = $(el).hasClass(expected);
const message = () => `Expected element${pass ? ' not' : ''} to have class '${expected}', but had '${actual}'`;
return { pass, message };
});
/**
* expect(host.query('.zippy')).toHaveAttribute('id', 'zippy');
*/
const toHaveAttribute = comparator((el, attr, val) => {
if (isObject(attr)) {
let pass = false;
let failing;
for (const key of Object.keys(attr)) {
const actual = $(el).attr(key);
const addendum = attr[key] !== undefined ? ` with value '${attr[key]}'` : '';
pass = hasProperty(actual, attr[key]);
failing = !pass ? `'${attr}'${addendum}, but had '${actual}'` : '';
}
const message = () => `Expected element${pass ? ' not' : ''} to have attribute ${failing}`;
return { pass, message };
}
const actual = $(el).attr(attr);
const addendum = val !== undefined ? ` with value '${val}'` : '';
const pass = hasProperty(actual, val);
const message = () => `Expected element${pass ? ' not' : ''} to have attribute '${attr}'${addendum}, but had '${actual}'`;
return { pass, message };
});
/**
* expect(host.query('.checkbox')).toHaveProperty('checked', true);
* expect(host.query('.checkbox')).toHaveProperty({checked: true});
*/
const toHaveProperty = comparator((el, prop, val) => {
if (isObject(prop)) {
return checkProperty(el, prop, hasProperty);
}
const actual = $(el).prop(prop);
const addendum = val !== undefined ? ` with value '${val}'` : '';
const pass = hasProperty(actual, val);
const message = () => `Expected element${pass ? ' not' : ''} to have property '${prop}'${addendum}, but had '${actual}'`;
return { pass, message };
});
const toContainProperty = comparator((el, prop, val) => {
if (isObject(prop)) {
return checkProperty(el, prop, containsProperty);
}
const actual = $(el).prop(prop);
const addendum = val !== undefined ? ` with value '${val}'` : '';
const pass = containsProperty(actual, val);
const message = () => `Expected element${pass ? ' not' : ''} to have property '${prop}'${addendum}, but had '${actual}'`;
return { pass, message };
});
/**
*
* expect('.zippy__content').toHaveText('Content');
* expect('.zippy__content').toHaveText(['Content A', 'Content B']);
*
* expect('.zippy__content').toHaveText((text) => text.includes('..');
*/
const toHaveText = comparator((el, expected, exact = false) => hasSameText(el, expected, { exact, trim: false }));
const toHaveExactText = comparator((el, expected, options = { trim: false }) => hasSameText(el, expected, { exact: true, trim: options.trim }));
const toHaveExactTrimmedText = comparator((el, expected) => hasSameText(el, expected, { exact: true, trim: true }));
const toContainText = toHaveText;
/**
*
* expect('.zippy__content').toHaveValue('value');
* expect('.zippy__content').toHaveValue(['value a', 'value b']);
*/
const toHaveValue = comparator((el, expected) => {
if (expected && Array.isArray(expected)) {
let actual;
let pass = false;
let failing;
$(el).each((i, e) => {
actual = $(e).val();
pass = actual === expected[i];
if (!pass) {
failing = expected[i];
return false;
}
});
const message = () => `Expected element${pass ? ' not' : ''} to have value '${failing}', but had '${actual}'`;
return { pass, message };
}
const actual = $(el).val();
const pass = actual === expected;
const message = () => `Expected element${pass ? ' not' : ''} to have value '${expected}', but had '${actual}'`;
return { pass, message };
});
const toContainValue = toHaveValue;
/**
*
* expect(host.element).toHaveStyle({
* backgroundColor: 'rgba(0, 0, 0, 0.1)'
* });
*/
const toHaveStyle = comparator((el, expected) => {
const pass = hasCss(el, expected);
const message = () => `Expected element${pass ? ' not' : ''} to have CSS ${JSON.stringify(expected)}`;
return { pass, message };
});
/**
*
* expect('.zippy__content').toHaveData({data: 'role', val: 'admin'});
*/
const toHaveData = comparator((el, { data, val }) => {
const actual = $(el).data(data);
const addendum = val !== undefined ? ` with value '${val}'` : '';
const pass = hasProperty(actual, val);
const message = () => `Expected element${pass ? ' not' : ''} to have data '${data}'${addendum}, but had '${actual}'`;
return { pass, message };
});
/**
*
* expect('.checkbox').toBeChecked();
*/
const toBeChecked = comparator((el) => {
const pass = $(el).is(':checked');
const message = () => `Expected element${pass ? ' not' : ''} to be checked`;
return { pass, message };
});
/**
*
* expect('.checkbox').toBeIndeterminate();
*/
const toBeIndeterminate = comparator((el) => {
const pass = $(el).is(':indeterminate');
const message = () => `Expected element${pass ? ' not' : ''} to be indeterminate`;
return { pass, message };
});
/**
*
* expect('.checkbox').toBeDisabled();
*/
const toBeDisabled = comparator((el) => {
const pass = $(el).is(':disabled');
const message = () => `Expected element${pass ? ' not' : ''} to be disabled`;
return { pass, message };
});
/**
* An empty element is an element without child elements or text.
*
* expect('div').toBeEmpty();
*/
const toBeEmpty = comparator((el) => {
const pass = $(el).is(':empty');
const message = () => `Expected element${pass ? ' not' : ''} to be empty`;
return { pass, message };
});
/**
* Verify if an object has some expected properties.
*
* const actual = { lorem: 'first', ipsum: 'second' };
* expect(actual).toBePartial({ lorem: 'first' });
*/
const toBePartial = comparator((actual, expected) => {
const mapToPropsAndValues = (values, properties) => {
return properties.map((prop) => {
return {
name: prop,
value: values[prop],
type: typeof values[prop],
};
});
};
const actualProps = Object.getOwnPropertyNames(actual);
const actualPropsAndValues = mapToPropsAndValues(actual, actualProps);
const expectedProps = Object.getOwnPropertyNames(expected);
const expectedPropsAndValues = mapToPropsAndValues(expected, expectedProps);
const pass = expectedProps.every((expectedProp) => actual[expectedProp] === expected[expectedProp]);
const message = () => `Expected element${pass ? ' not' : ''} to contain properties: ${JSON.stringify(expectedPropsAndValues)}.`.concat(` Actual properties: ${JSON.stringify(actualPropsAndValues)}`);
return { pass, message };
});
/**
* Hidden elements are elements that have:
* 1. Display or visibility style properties set to "none" or "hidden"
* 2. Display or visibility computed styles set to "none" or "hidden"
* 3. Width and height set to 0 (check not applied in jest)
* 4. A hidden parent element (this also hides child elements)
* 5. Type equal to "hidden" (only for form elements)
* 6. A "hidden" attribute
*/
function isHidden(elOrSelector) {
let el = $(elOrSelector)[0];
if (!el) {
return true;
}
const hiddenWhen = [
(el) => !(el.offsetWidth || el.offsetHeight || el.getClientRects().length),
(el) => el.style.display === 'none' || window.getComputedStyle(el).getPropertyValue?.('display') === 'none',
(el) => el.style.visibility === 'hidden' || window.getComputedStyle(el).getPropertyValue?.('visibility') === 'hidden',
(el) => el.type === 'hidden',
(el) => el.hasAttribute('hidden'),
];
if (isRunningInJsDom()) {
// When running in JSDOM (Jest), offset-properties and client rects are always reported as 0
// - hence, let's take a more "naive" approach here. (https://github.com/jsdom/jsdom/issues/135)
hiddenWhen.shift();
}
while (el) {
if (el === document) {
break;
}
if (el.nodeType === Node.ELEMENT_NODE && hiddenWhen.some((rule) => rule(el))) {
return true;
}
el = el.parentNode || el.host;
}
return false;
}
/**
* Hidden elements are elements that have:
* 1. Display or visibility style properties set to "none" or "hidden"
* 2. Display or visibility computed styles set to "none" or "hidden"
* 3. Width and height set to 0 (check not applied in jest)
* 4. A hidden parent element (this also hides child elements)
* 5. Type equal to "hidden" (only for form elements)
* 6. A "hidden" attribute
*
* expect('div').toBeHidden();
*
*/
const toBeHidden = comparator((el) => {
const pass = isHidden(el);
const message = () => `Expected element${pass ? ' not' : ''} to be hidden`;
return { pass, message };
});
/**
* The :selected selector selects option elements that are pre-selected.
*
* expect('div').toBeSelected();
*
*/
const toBeSelected = comparator((el) => {
const pass = $(el).is(':selected');
const message = () => `Expected element${pass ? ' not' : ''} to be selected`;
return { pass, message };
});
/**
* Hidden elements are elements that have:
* 1. Display property set to "none"
* 2. Width and height set to 0
* 3. A hidden parent element (this also hides child elements)
* 4. Type equal to "hidden" (only for form elements)
* 5. A "hidden" attribute
*
* expect('div').toBeVisible();
*
*/
const toBeVisible = comparator((el) => {
const pass = !isHidden(el);
const message = () => `Expected element${pass ? ' not' : ''} to be visible`;
return { pass, message };
});
/**
* The :focus selector selects the element that currently has focus.
*
* expect('input').toBeFocused();
*/
const toBeFocused = comparator((el) => {
const element = $(el).get(0);
const pass = element === element.ownerDocument.activeElement;
const message = () => `Expected element${pass ? ' not' : ''} to be focused`;
return { pass, message };
});
/**
* Check to see if the set of matched elements matches the given selector
* returns true if the dom contains the element
*
* expect('div').toBeMatchedBy('.js-something')
*/
const toBeMatchedBy = comparator((el, expected) => {
const actual = $(el).filter(expected).length;
const pass = actual > 0;
const message = () => `Expected element${pass ? ' not' : ''} to be matched by '${expected}'`;
return { pass, message };
});
/**
*
* expect('div').toHaveDescendant('.child')
*/
const toHaveDescendant = comparator((el, selector) => {
const actual = $(el).find(selector).length;
const pass = actual > 0;
const message = () => `Expected element${pass ? ' not' : ''} to contain child '${selector}'`;
return { pass, message };
});
/**
*
* expect('div').toHaveDescendantWithText({selector: '.child', text: 'text'})
*/
const toHaveDescendantWithText = comparator((el, { selector, text }) => {
const actual = $.trim($(el).find(selector).text());
if (text && $.isFunction(text.test)) {
const pass = text.test(actual);
const message = () => `Expected element${pass ? ' not' : ''} to have descendant '${selector}' with text matching '${text}',` + ` but had '${actual}'`;
return { pass, message };
}
const pass = actual.indexOf(text) !== -1;
const message = () => `Expected element${pass ? ' not' : ''} to have descendant '${selector}' with text '${text}', but had '${actual}'`;
return { pass, message };
});
const toHaveSelectedOptions = comparator((el, expected) => {
if (expected instanceof HTMLOptionElement) {
const actual = $(el).find(':selected');
const pass = actual.is($(expected));
const message = () => `Expected element${pass ? ' not' : ''} to have options '[${expected.outerHTML}]' but had '[${actual[0].outerHTML}]'`;
return { pass, message };
}
if (isHTMLOptionElementArray(expected)) {
const actual = $(el).find(':selected');
const pass = actual.length === expected.length && actual.toArray().every((_, index) => $(actual[index]).is(expected[index]));
const expectedOptionsString = $(expected)
.get()
.map((option) => option.outerHTML)
.join(',');
const actualOptionsString = actual
.get()
.map((option) => option.outerHTML)
.join(',');
const message = () => `Expected element${pass ? ' not' : ''} to have options '[${expectedOptionsString}]' but had '[${actualOptionsString}]'`;
return { pass, message };
}
const actual = $(el).val();
const pass = coerceArray(expected)?.every((v) => actual.includes(v));
const expectedOptionsString = Array.isArray(expected)
? expected.reduce((acc, val, i) => acc + `${i === expected.length ? '' : ','}${val}`)
: expected;
const message = () => `Expected element${pass ? ' not' : ''} to have options '[${expectedOptionsString}]' but had '[${actual}]'`;
return { pass, message };
});
var customMatchers = /*#__PURE__*/Object.freeze({
__proto__: null,
toBeChecked: toBeChecked,
toBeDisabled: toBeDisabled,
toBeEmpty: toBeEmpty,
toBeFocused: toBeFocused,
toBeHidden: toBeHidden,
toBeIndeterminate: toBeIndeterminate,
toBeMatchedBy: toBeMatchedBy,
toBePartial: toBePartial,
toBeSelected: toBeSelected,
toBeVisible: toBeVisible,
toContainProperty: toContainProperty,
toContainText: toContainText,
toContainValue: toContainValue,
toExist: toExist,
toHaveAttribute: toHaveAttribute,
toHaveClass: toHaveClass,
toHaveData: toHaveData,
toHaveDescendant: toHaveDescendant,
toHaveDescendantWithText: toHaveDescendantWithText,
toHaveExactText: toHaveExactText,
toHaveExactTrimmedText: toHaveExactTrimmedText,
toHaveId: toHaveId,
toHaveLength: toHaveLength,
toHaveProperty: toHaveProperty,
toHaveSelectedOptions: toHaveSelectedOptions,
toHaveStyle: toHaveStyle,
toHaveText: toHaveText,
toHaveValue: toHaveValue
});
let globals = {
providers: [],
declarations: [],
imports: [],
};
function defineGlobalsInjections(config) {
globals = { ...globals, ...config };
}
function getGlobalsInjections() {
return globals;
}
/**
* @internal
*/
function initialModule(options) {
const globals = { imports: [], declarations: [], providers: [], ...getGlobalsInjections() };
return {
declarations: [...globals.declarations, ...options.declarations, ...options.entryComponents],
imports: [...(options.disableAnimations ? [NoopAnimationsModule] : []), ...globals.imports, ...options.imports],
providers: [...globals.providers, ...options.providers, ...options.mocks.map((type) => options.mockProvider(type))],
entryComponents: [...options.entryComponents],
teardown:
// Caretaker note: we don't want to merge the `globals.teardown` and `options.teardown`, since `options.teardown`
// is always defined. If the user calls `defineGlobalsInjections({ teardown: { ... } })` and we merge it with
// `options.teardown`, then `options.teardown` will always override global options.
{ ...(globals.teardown || options.teardown) },
deferBlockBehavior: globals.deferBlockBehavior || options.deferBlockBehavior,
errorOnUnknownElements: globals.errorOnUnknownElements || options.errorOnUnknownElements,
errorOnUnknownProperties: globals.errorOnUnknownProperties || options.errorOnUnknownProperties,
};
}
/**
* @internal
*/
function initialSpectatorModule(options) {
const moduleMetadata = initialModule(options);
if (options.declareComponent) {
declareInModule(moduleMetadata, options.component);
}
moduleMetadata.schemas = [options.shallow ? NO_ERRORS_SCHEMA : options.schemas || []];
return moduleMetadata;
}
/**
* @internal
*/
function merge(defaults, overrides) {
return { ...defaults, ...overrides };
}
/**
* @internal
*/
function installProtoMethods(mock, proto, createSpyFn) {
if (proto === null || proto === Object.prototype) {
return;
}
for (const key of Object.getOwnPropertyNames(proto)) {
const descriptor = Object.getOwnPropertyDescriptor(proto, key);
if (!descriptor) {
continue;
}
if (typeof descriptor.value === 'function' && key !== 'constructor' && typeof mock[key] === 'undefined') {
mock[key] = createSpyFn(key);
}
else if (descriptor.get && !mock.hasOwnProperty(key)) {
Object.defineProperty(mock, key, {
set: (value) => (mock[`_${key}`] = value),
get: () => mock[`_${key}`],
configurable: true,
});
}
}
installProtoMethods(mock, Object.getPrototypeOf(proto), createSpyFn);
mock.castToWritable = () => mock;
}
/**
* @publicApi
*/
function createSpyObject(type, template) {
const mock = { ...template } || {};
installProtoMethods(mock, type.prototype, (name) => {
const newSpy = jasmine.createSpy(name);
newSpy.andCallFake = (fn) => newSpy.and.callFake(fn);
newSpy.andReturn = (val) => newSpy.and.returnValue(val);
newSpy.reset = () => newSpy.calls.reset();
// revisit return null here (previously needed for rtts_assert).
newSpy.and.returnValue(null);
return newSpy;
});
return mock;
}
/**
* @publicApi
*/
function mockProvider(type, properties) {
return {
provide: type,
useFactory: () => createSpyObject(type, properties),
};
}
const defaultOptions = {
disableAnimations: true,
entryComponents: [],
mocks: [],
mockProvider,
providers: [],
declarations: [],
imports: [],
schemas: [],
overrideModules: [],
overrideComponents: [],
overrideDirectives: [],
overridePipes: [],
teardown: { destroyAfterEach: false },
errorOnUnknownElements: false,
errorOnUnknownProperties: false,
deferBlockBehavior: DeferBlockBehavior.Playthrough,
};
/**
* @internal
*/
function getDefaultBaseOptions(options) {
return merge(defaultOptions, options);
}
const defaultSpectatorOptions = {
...getDefaultBaseOptions(),
shallow: false,
declareComponent: true,
detectChanges: true,
componentProviders: [],
componentViewProviders: [],
componentMocks: [],
componentViewProvidersMocks: [],
};
/**
* @internal
*/
function getSpectatorDefaultOptions(overrides) {
return merge(defaultSpectatorOptions, overrides);
}
/**
* @internal
*/
function overrideComponentIfProviderOverridesSpecified(options) {
const hasProviderOverrides = options.componentProviders.length || options.componentMocks.length;
const hasViewProviders = options.componentViewProviders.length || options.componentViewProvidersMocks.length;
if (hasProviderOverrides || hasViewProviders) {
let providerConfiguration = {};
if (hasProviderOverrides) {
providerConfiguration = {
providers: [...options.componentProviders, ...options.componentMocks.map((p) => options.mockProvider(p))],
};
}
if (hasViewProviders) {
providerConfiguration = {
...providerConfiguration,
viewProviders: [...options.componentViewProviders, ...options.componentViewProvidersMocks.map((p) => options.mockProvider(p))],
};
}
TestBed.overrideComponent(options.component, {
set: providerConfiguration,
});
}
}
/**
* @internal
*/
function overrideModules(options) {
if (options.overrideModules.length) {
options.overrideModules.forEach((overrideModule) => {
const [ngModule, override] = overrideModule;
TestBed.overrideModule(ngModule, override);
});
}
}
/**
* @internal
*/
function overrideComponents(options) {
if (options.overrideComponents.length) {
options.overrideComponents.forEach((overrideComponent) => {
const [component, override] = overrideComponent;
if (!reflectComponentType(component)?.isStandalone) {
throw new Error(`Can not override non standalone component`);
}
TestBed.overrideComponent(component, override);
});
}
}
/**
* @internal
*/
function overrideDirectives(options) {
if (options.overrideDirectives.length) {
options.overrideDirectives.forEach((overrideDirective) => {
const [directive, override] = overrideDirective;
if (!isStandalone(directive)) {
throw new Error(`Can not override non standalone directive`);
}
TestBed.overrideDirective(directive, override);
});
}
}
/**
* @internal
*/
function overridePipes(options) {
if (options.overridePipes.length) {
options.overridePipes.forEach((overridePipe) => {
const [pipe, override] = overridePipe;
if (!isStandalone(pipe)) {
throw new Error(`Can not override non standalone pipe`);
}
TestBed.overridePipe(pipe, override);
});
}
}
/**
* @publicApi
*/
function createComponentFactory(typeOrOptions) {
const options = isType(typeOrOptions)
? getSpectatorDefaultOptions({ component: typeOrOptions })
: getSpectatorDefaultOptions(typeOrOptions);
const moduleMetadata = initialSpectatorModule(options);
beforeEach(waitForAsync(() => {
addMatchers(customMatchers);
TestBed.configureTestingModule(moduleMetadata).overrideModule(BrowserDynamicTestingModule, {});
overrideModules(options);
overrideComponents(options);
overrideDirectives(options);
overridePipes(options);
overrideComponentIfProviderOverridesSpecified(options);
TestBed.compileComponents();
}));
return (overrides) => {
const defaults = { props: {}, detectChanges: true, providers: [] };
const { detectChanges, props, providers } = { ...defaults, ...overrides };
if (providers && providers.length) {
providers.forEach((provider) => {
TestBed.overrideProvider(provider.provide, provider);
});
}
const spectator = createSpectator(options, props);
if (options.detectChanges && detectChanges) {
spectator.detectChanges();
}
return spectator;
};
}
function createSpectator(options, props) {
const fixture = TestBed.createComponent(options.component);
const debugElement = fixture.debugElement;
const component = setProps(fixture.componentRef, props);
return new Spectator(fixture, debugElement, component, debugElement.nativeElement);
}
/**
* @publicApi
*/
class SpectatorHost extends DomSpectator {
constructor(hostComponent, hostDebugElement, hostElement, hostFixture, debugElement, componentInstance, element) {
super(hostFixture, debugElement, componentInstance, element);
this.hostComponent = hostComponent;
this.hostDebugElement = hostDebugElement;
this.hostElement = hostElement;
this.hostFixture = hostFixture;
this.debugElement = debugElement;
this.element = element;
}
get component() {
return this.instance;
}
inject(token, fromComponentInjector = false) {
if (fromComponentInjector) {
return this.debugElement.injector.get(token);
}
return super.inject(token);
}
detectComponentChanges() {
if (this.debugElement) {
this.debugElement.injector.get(ChangeDetectorRef).detectChanges();
}
else {
this.detectChanges();
}
}
queryHost(directiveOrSelector, options) {
if ((options || {}).root && isString(directiveOrSelector)) {
return document.querySelector(directiveOrSelector);
}
return getChildren(this.hostDebugElement)(directiveOrSelector, options)[0] || null;
}
queryHostAll(directiveOrSelector, options) {
if ((options || {}).root && isString(directiveOrSelector)) {
return Array.from(document.querySelectorAll(directiveOrSelector));
}
return getChildren(this.hostDebugElement)(directiveOrSelector, options);
}
setHostInput(input, value) {
setHostProps(this.fixture.componentRef, input, value);
this.detectChanges();
}
}
// TODO (dirkluijk): remove after upgrading to Angular 8.2
// see: https://github.com/angular/angular/commit/10a1e1974b816ebb979dc10586b160ee07ad8356
function nodeByDirective(type) {
return (debugNode) => debugNode.providerTokens.includes(type);
}
/**
* @internal
*/
function initialSpectatorWithHostModule(options) {
const moduleMetadata = initialSpectatorModule(options);
moduleMetadata.declarations.push(options.host);
return moduleMetadata;
}
class HostComponent {
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.1", ngImport: i0, type: HostComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.0.1", type: HostComponent, selector: "lib-ngneat-host-component", ngImport: i0, template: '', isInline: true }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.1", ngImport: i0, type: HostComponent, decorators: [{
type: Component,
args: [{
selector: 'lib-ngneat-host-component',
template: '',
}]
}] });
/*
This is an unused module to resolve the ng build error:
'Cannot determine the module for class HostComponent'
Reference: https://github.com/angular/issues/13590
*/
class HostModule {
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.1", ngImport: i0, type: HostModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "18.0.1", ngImport: i0, type: HostModule, declarations: [HostComponent] }); }
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "18.0.1", ngImport: i0, type: HostModule }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.1", ngImport: i0, type: HostModule, decorators: [{
type: NgModule,
args: [{
declarations: [HostComponent],
}]
}] });
const defaultSpectatorHostOptions = {
...getSpectatorDefaultOptions(),
host: HostComponent,
template: '',
};
/**
* @internal
*/
function getSpectatorHostDefaultOptions(overrides) {
return merge(defaultSpectatorHostOptions, overrides);
}
function createHostFactory(typeOrOptions) {
const options = isType(typeOrOptions)
? getSpectatorHostDefaultOptions({ component: typeOrOptions })
: getSpectatorHostDefaultOptions(typeOrOptions);
const moduleMetadata = initialSpectatorWithHostModule(options);
beforeEach(waitForAsync(() => {
addMatchers(customMatchers);
TestBed.configureTestingModule(moduleMetadata).overrideModule(BrowserDynamicTestingModule, {});
overrideModules(options);
overrideComponents(options);
overrideDirectives(options);
overridePipes(options);
overrideComponentIfProviderOverridesSpecified(options);
if (options.template) {
TestBed.overrideComponent(options.host, {
set: { template: options.template },
});
}
}));
return (template, overrides) => {
const defaults = { hostProps: {}, detectChanges: true, providers: [] };
const { detectChanges, hostProps, providers } = { ...defaults, ...overrides };
if (providers && providers.length) {
providers.forEach((provider) => {
TestBed.overrideProvider(provider.provide, provider);
});
}
if (template) {
TestBed.overrideComponent(options.host, {
set: { template: template },
});
}
const spectator = createSpectatorHost(options, hostProps);
if (options.detectChanges && detectChanges) {
spectator.detectChanges();
}
return spectator;
};
}
function createSpectatorHost(options, hostProps) {
const hostFixture = TestBed.createComponent(options.host);
const debugElement = hostFixture.debugElement.query(By.directive(options.component)) || hostFixture.debugElement;
const debugNode = hostFixture.debugElement.queryAllNodes(nodeByDirective(options.component))[0];
if (!debugNode) {
throw new Error(`Cannot find component/directive ${options.component} in host template 😔`);
}
const hostComponent = setHostProps(hostFixture.componentRef, hostProps);
const component = debugNode.injector.get(options.component);
return new SpectatorHost(hostComponent, hostFixture.debugElement, hostFixture.nativeElement, hostFixture, debugElement, component, debugElement.nativeElement);
}
/**
* @publicApi
*/
class SpectatorDirective extends DomSpectator {
constructor(hostComponent, fixture, debugElement, instance, element) {
super(fixture, debugElement, instance, element);
this.hostComponent = hostComponent;
this.fixture = fixture;
this.debugElement = debugElement;
this.instance = instance;
this.element = element;
}
get directive() {
return this.instance;
}
inject(token, fromDirectiveInjector = false) {
if (fromDirectiveInjector) {
return this.debugElement.injector.get(token);
}
return super.inject(token);
}
setHostInput(input, value) {
setHostProps(this.fixture.componentRef, input, value);
this.detectChanges();
}
}
/**
* @internal
*/
function initialSpectatorDirectiveModule(options) {
const moduleMetadata = initialModule(options);
if (options.declareDirective) {
declareInModule(moduleMetadata, options.directive);
}
moduleMetadata.declarations.push(options.host);
moduleMetadata.schemas = [options.shallow ? NO_ERRORS_SCHEMA : options.schemas || []];
return moduleMetadata;
}
const defaultSpectatorRoutingOptions = {
...getDefaultBaseOptions(),
host: HostComponent,
template: '',
shallow: false,
detectChanges: true,
directiveProviders: [],
directiveMocks: [],
declareDirective: true,
};
/**
* @internal
*/
function getSpectatorDirectiveDefaultOptions(overrides) {
return merge(defaultSpectatorRoutingOptions, overrides);
}
function createDirectiveFactory(typeOrOptions) {
const options = isType(typeOrOptions)
? getSpectatorDirectiveDefaultOptions({ directive: typeOrOptions })
: getSpectatorDirectiveDefaultOptions(typeOrOptions);
const moduleMetadata = initialSpectatorDirectiveModule(options);
beforeEach(waitForAsync(() => {
addMatchers(customMatchers);
TestBed.configureTestingModule(moduleMetadata);
overrideModules(options);
overrideComponents(options);
overrideDirectives(options);
overridePipes(options);
}));
return (template, overrides) => {
const defaults = {
hostProps: {},
detectChanges: true,
providers: [],
};
const { detectChanges, hostProps, providers } = { ...defaults, ...overrides };
if (providers && providers.length) {
providers.forEach((provider) => {
TestBed.overrideProvider(provider.provide, provider);
});
}
TestBed.overrideModule(BrowserDynamicTestingModule, {}).overrideComponent(options.host, {
set: { template: template || options.template },
});
if (options.directiveProviders.length || options.directiveMocks.length) {
TestBed.overrideDirective(options.directive, {
set: { providers: [...options.directiveProviders, ...options.directiveMocks.map((p) => options.mockProvider(p))] },
});
}
const spectator = createSpectatorDirective(options, hostProps);
if (options.detectChanges && detectChanges) {
spectator.detectChanges();
}
return spectator;
};
}
function createSpectatorDirective(options, hostProps) {
const hostFixture = TestBed.createComponent(options.host);
const debugElement = hostFixture.debugElement.query(By.directive(options.directive)) || hostFixture.debugElement;
const debugNode = hostFixture.debugElement.queryAllNodes(nodeByDirective(options.directive))[0];
if (!debugNode) {
throw new Error(`Cannot find directive ${options.directive} in host template 😔`);
}
const hostComponent = setHostProps(hostFixture.componentRef, hostProps);
const directive = debugNode.injector.get(options.directive);
return new SpectatorDirective(hostComponent, hostFixture, hostFixture.debugElement, directive, debugElement.nativeElement);
}
/**
* @publicApi
*/
class SpectatorService extends BaseSpectator {
constructor(service) {
super();
this.service = service;
}
}
/**
* @internal
*/
function initialServiceModule(options) {
const moduleMetadata = initialModule(options);
moduleMetadata.providers.push(options.service);
return moduleMetadata;
}
const defaultServiceOptions = {
...getDefaultBaseOptions(),
};
/**
* @internal
*/
function getDefaultServiceOptions(overrides) {
return merge(defaultServiceOptions, overrides);
}
/**
* @publicApi
*/
function createServiceFactory(typeOrOptions) {
const service = isType(typeOrOptions) ? typeOrOptions : typeOrOptions.service;
const options = isType(typeOrOptions) ? getDefaultServiceOptions({ service }) : getDefaultServiceOptions(typeOrOptions);
const moduleMetadata = initialServiceModule(options);
beforeEach(() => {
TestBed.configureTestingModule(moduleMetadata);
overrideModules(options);
});
afterEach(() => {
const testedService = TestBed.inject
? TestBed.inject(service)
: TestBed.get(service);
if (doesServiceImplementsOnDestroy(testedService)) {
// eslint-disable-next-line
testedService.ngOnDestroy();
}
});
return (overrides) => {
const defaults = { providers: [] };
const { providers } = { ...defaults, ...overrides };
if (providers && providers.length) {
providers.forEach((provider) => {
TestBed.overrideProvider(provider.provide, provider);
});
}
return new SpectatorService(TestBed.inject ? TestBed.inject(service) : TestBed.get(service));
};
}
class RouterStub extends Router {
}
function isRouterStub(router) {
return 'emitRouterEvent' in router;
}
/**
* @publicApi
*/
class SpectatorRouting extends Spectator {
constructor(fixture, debugElement, instance, router, activatedRouteStub) {
super(fixture, debugElement, instance, debugElement.nativeElement);
this.router = router;
this.activatedRouteStub = activatedRouteStub;
}
/**
* Simulates a route navigation by updating the Params, QueryParams and Data observable streams.
*/
triggerNavigation(options) {
if (!this.checkStubPresent()) {
return;
}
if (options && options.params) {
this.activatedRouteStub.setParams(options.params);
}
if (options && options.queryParams) {
this.activatedRouteStub.setQueryParams(options.queryParams);
}
if (options && options.data) {
this.activatedRouteStub.setAllData(options.data);
}
if (options && options.fragment) {
this.activatedRouteStub.setFragment(options.fragment);
}
this.triggerNavigationAndUpdate();
}
/**
* Updates the route params and triggers a route navigation.
*/
setRouteParam(name, value) {
if (this.checkStubPresent()) {
this.activatedRouteStub.setParam(name, value);
this.triggerNavigationAndUpdate();
}
}
/**
* Updates the route query params and triggers a route navigation.
*/
setRouteQueryParam(name, value) {
if (this.checkStubPresent()) {
this.activatedRouteStub.setQueryParam(name, value);
this.triggerNavigationAndUpdate();
}
}
/**
* Updates the route data and triggers a route navigation.
* The `value` is typed as `any` since the `Route#data` is a record with `any` values.
* There's no sense to make it generic until `Route#data` starts supporting generic types.
*/
setRouteData(name, value) {
if (this.checkStubPresent()) {
this.activatedRouteStub.setData(name, value);
this.triggerNavigationAndUpdate();
}
}
/**
* Updates the route fragment and triggers a route navigation.
*/
setRouteFragment(fragment) {
if (this.checkStubPresent()) {
this.activatedRouteStub.setFragment(fragment);
this.triggerNavigationAndUpdate();
}
}
/**
* Updates the route url and triggers a route navigation.
*/
setRouteUrl(url) {
if (this.checkStubPresent()) {
this.activatedRouteStub.setUrl(url);
this.triggerNavigationAndUpdate();
}
}
/**
* Emits a router event
*/
emitRouterEvent(event) {
if (!isRouterStub(this.router)) {
// eslint-disable-next-line no-console
console.warn('No stub for Router present. Set Spectator option "stubsEnabled" to true if you want to use this ' +
'helper, or use Router navigation to trigger events.');
return;
}
this.router.emitRouterEvent(event);
}
triggerNavigationAndUpdate() {
this.activatedRouteStub.triggerNavigation();
this.detectChanges();
}
checkStubPresent() {
if (!this.activatedRouteStub) {
// eslint-disable-next-line no-console
console.warn('No stub for ActivatedRoute present. Set Spectator option "stubsEnabled" to true if you want to use this ' +
'helper, or use Router to trigger navigation.');
return false;
}
return true;
}
}
/**
* @publicApi
*
* Utility class for stubbing ActivatedRoute of @angular/router
*/
class ActivatedRouteStub extends ActivatedRoute {
constructor(options) {
super();
this.testParams = {};
this.testQueryParams = {};
this.testData = {};
this.testFragment = null;
this.testUrl = [];
this.testRoot = null;
this.testParent = null;
this.testFirstChild = null;
this.testChildren = null;
this.paramsSubject = new ReplaySubject(1);
this.queryParamsSubject = new ReplaySubject(1);
this.dataSubject = new ReplaySubject(1);
this.fragmentSubject = new ReplaySubject(1);
this.urlSubject = new ReplaySubject(1);
if (options) {
this.testParams = options.params || {};
this.testQueryParams = options.queryParams || {};
this.testData = options.data || {};
this.testFragment = options.fragment || null;
this.testUrl = options.url || [];
this.testRoot = options.root || null;
this.testParent = options.parent || null;
this.testFirstChild = options.firstChild || null;
this.testChildren = options.children || null;
}
this.params = this.paramsSubject.asObservable();
this.queryParams = this.queryParamsSubject.asObservable();
this.data = this.dataSubject.asObservable();
this.fragment = this.fragmentSubject.asObservable();
this.url = this.urlSubject.asObservable();
this.snapshot = this.buildSnapshot();
this.triggerNavigation();
}
get paramMap() {
return this.paramsSubject.asObservable().pipe(map((params) => convertToParamMap(params)));
}
setParams(params) {
this.testParams = params;
this.snapshot = this.buildSnapshot();
}
setParam(name, value) {
this.testParams = { ...this.testParams, [name]: value };
this.snapshot = this.buildSnapshot();
}
setQueryParams(queryParams) {
this.testQueryParams = queryParams;
this.snapshot = this.buildSnapshot();
}
setQueryParam(name, value) {
this.testQueryParams = { ...this.testQueryParams, [name]: value };
this.snapshot = this.buildSnapshot();
}
setAllData(data) {
this.testData = data;
this.snapshot = this.buildSnapshot();
}
setData(name, value) {
this.testData = { ...this.testData, [name]: value };
this.snapshot = this.buildSnapshot();
}
setFragment(fragment) {
this.testFragment = fragment;
this.snapshot = this.buildSnapshot();
}
setUrl(url) {
this.testUrl = url;
this.snapshot = this.buildSnapshot();
}
get root() {
return this.testRoot || this;
}
get parent() {
return this.testParent || null;
}
get children() {
return this.testChildren || [this];
}
get firstChild() {
return this.testFirstChild || null;
}
/**
* Simulates a route navigation by updating the Params, QueryParams and Data observable streams.
*/
triggerNavigation() {
this.paramsSubject.next(this.testParams);
this.queryParamsSubject.next(this.testQueryParams);
this.dataSubject.next(this.testData);
this.fragmentSubject.next(this.testFragment);
this.urlSubject.next(this.testUrl);
}
toString() {
return 'activatedRouteStub';
}
buildSnapshot() {
const snapshot = new ActivatedRouteSnapshot();
snapshot.params = this.testParams;
snapshot.queryParams = this.testQueryParams;
snapshot.data = this.testData;
snapshot.fragment = this.testFragment;
snapshot.url = this.testUrl;
return snapshot;
}
}
/**
* @internal
*/
function initialRoutingModule(options) {
const moduleMetadata = initialSpectatorModule(options);
if (options.stubsEnabled) {
moduleMetadata.imports.push(RouterTestingModule);
moduleMetadata.providers.push(options.mockProvider(RouterStub, {
events: new Subject(),
emitRouterEvent(event) {
this.events.next(event);
},
serializeUrl() {
return '/';
},
}), {
provide: Router,
useExisting: RouterStub,
});
moduleMetadata.providers.push({
provide: ActivatedRouteStub,
useValue: new ActivatedRouteStub({
params: options.params,
queryParams: options.queryParams,
data: options.data,
}),
}, {
provide: ActivatedRoute,
useExisting: ActivatedRouteStub,
});
}
else {
moduleMetadata.imports.push(RouterTestingModule.withRoutes(options.routes));
}
return moduleMetadata;
}
const defaultRoutingOptions = {
...getSpectatorDefaultOptions(),
params: {},
queryParams: {},
data: {},
fragment: null,
stubsEnabled: true,
routes: [],
url: [],
root: null,
parent: null,
children: null,
firstChild: null,
};
/**
* @internal
*/
function getRoutingDefaultOptions(overrides) {
return merge(defaultRoutingOptions, overrides);
}
/**
* @publicApi
*/
function createRoutingFactory(typeOrOptions) {
const options = isType(typeOrOptions)
? getRoutingDefaultOptions({ component: typeOrOptions })
: getRoutingDefaultOptions(typeOrOptions);
const moduleMetadata = initialRoutingModule(options);
beforeEach(waitForAsync(() => {
addMatchers(customMatchers);
TestBed.configureTestingModule(moduleMetadata);
overrideModules(options);
overrideComponents(options);
overrideDirectives(options);
overridePipes(options);
overrideComponentIfProviderOverridesSpecified(options);
TestBed.compileComponents();
}));
return (overrides) => {
const defaults = {
props: {},
detectChanges: true,
providers: [],
};
const { detectChanges, props, providers } = { ...defaults, ...overrides };
if (providers && providers.length) {
providers.forEach((provider) => {
TestBed.overrideProvider(provider.provide, provider);
});
}
const { params, queryParams, data, fragment, url, root, parent, children, firstChild } = { ...options, ...overrides };
TestBed.overrideProvider(ActivatedRoute, {
useValue: new ActivatedRouteStub({ params, queryParams, data, fragment, url, root, parent, children, firstChild }),
});
const ngZone = TestBed.inject ? TestBed.inject(NgZone) : TestBed.get(NgZone);
return ngZone.run(() => {
const spectator = createSpectatorRouting(options, props);
spectator.router.initialNavigation();
if (options.detectChanges && detectChanges) {
spectator.detectChanges();
}
return spectator;
});
};
}
function createSpectatorRouting(options, props) {
const fixture = TestBed.createComponent(options.component);
const debugElement = fixture.debugElement;
const component = setProps(fixture.componentRef, props);
/**
* Back compatibility, angular under 9 version doesnt have a inject function
*/
if (!TestBed.inject) {
return new SpectatorRouting(fixture, debugElement, component, TestBed.get(Router), TestBed.get(ActivatedRoute));
}
return new SpectatorRouting(fixture, debugElement, component, TestBed.inject(Router), TestBed.inject(ActivatedRoute));
}
/**
* @publicApi
*/
var HttpMethod;
(function (HttpMethod) {
HttpMethod["GET"] = "GET";
HttpMethod["POST"] = "POST";
HttpMethod["DELETE"] = "DELETE";
HttpMethod["PUT"] = "PUT";
HttpMethod["PATCH"] = "PATCH";
HttpMethod["HEAD"] = "HEAD";
HttpMethod["JSONP"] = "JSONP";
HttpMethod["OPTIONS"] = "OPTIONS";
})(HttpMethod || (HttpMethod = {}));
/**
* @publicApi
*/
class SpectatorHttp extends BaseSpectator {
constructor(service, httpClient, controller) {
super();
this.service = service;
this.httpClient = httpClient;
this.controller = controller;
// small workaround to prevent issues if destructuring SpectatorHttp, which was common in Spectator 3
// remove in v5?
this.expectOne = this.expectOne.bind(this);
this.expectConcurrent = this.expectConcurrent.bind(this);
}
expectOne(url, method) {
expect(true).toBe(true); // workaround to avoid `Spec has no expectations` https://github.com/NetanelBasal/spectator/issues/75
const req = this.controller.expectOne({
url,
method,
});
// assert that there are no outstanding requests.
this.controller.verify();
return req;
}
expectConcurrent(expectations) {
const requests = expectations.map((expectation) => {
return this.controller.expectOne({
url: expectation.url,
method: expectation.method,
});
});
this.controller.verify();
return requests;
}
flushAll(requests, args) {
requests.forEach((request, idx) => {
request.flush(args[idx]);
});
}
}
/**
* @internal
*/
function initialHttpModule(options) {
const moduleMetadata = initialModule(options);
moduleMetadata.providers.push(options.service);
moduleMetadata.imports.push(HttpClientTestingModule);
return moduleMetadata;
}
const defaultHttpOptions = {
...getDefaultBaseOptions(),
};
/**
* @internal
*/
function getDefaultHttpOptions(overrides) {
return merge(defaultHttpOptions, overrides);
}
/**
* @publicApi
*/
function createHttpFactory(typeOrOptions) {
const service = isType(typeOrOptions) ? typeOrOptions : typeOrOptions.service;
const options = isType(typeOrOptions) ? getDefaultHttpOptions({ service }) : getDefaultHttpOptions(typeOrOptions);
const moduleMetadata = initialHttpModule(options);
beforeEach(() => {
TestBed.configureTestingModule(moduleMetadata);
overrideModules(options);
});
afterEach(() => {
if (TestBed.inject) {
TestBed.inject(HttpTestingController).verify();
}
else {
TestBed.get(HttpTestingController).verify();
}
});
return (overrides) => {
const defaults = { providers: [] };
const { providers } = { ...defaults, ...overrides };
if (providers && providers.length) {
providers.forEach((provider) => {
TestBed.overrideProvider(provider.provide, provider);
});
}
/**
* Back compatibility, angular under 9 version doesnt have a inject function
*/
if (!TestBed.inject) {
return new SpectatorHttp(TestBed.get(service), TestBed.get(HttpClient), TestBed.get(HttpTestingController));
}
return new SpectatorHttp(TestBed.inject(service), TestBed.inject(HttpClient), TestBed.inject(HttpTestingController));
};
}
/**
* @publicApi
*/
class SpectatorPipe extends BaseSpectator {
constructor(hostComponent, fixture, debugElement, element) {
super();
this.hostComponent = hostComponent;
this.fixture = fixture;
this.debugElement = debugElement;
this.element = element;
}
detectChanges() {
this.fixture.detectChanges();
}
setHostInput(input, value) {
setHostProps(this.fixture.componentRef, input, value);
this.detectChanges();
}
}
/**
* @internal
*/
function initialSpectatorPipeModule(options) {
const moduleMetadata = initialModule(options);
declareInModule(moduleMetadata, options.pipe);
moduleMetadata.declarations.push(options.host);
return moduleMetadata;
}
const defaultSpectatorPipeOptions = {
...getDefaultBaseOptions(),
host: HostComponent,
detectChanges: true,
template: '',
};
/**
* @internal
*/
function getSpectatorPipeDefaultOptions(overrides) {
return merge(defaultSpectatorPipeOptions, overrides);
}
/**
* @publicApi
*/
function createPipeFactory(typeOrOptions) {
const options = isType(typeOrOptions)
? getSpectatorPipeDefaultOptions({ pipe: typeOrOptions })
: getSpectatorPipeDefaultOptions(typeOrOptions);
const moduleMetadata = initialSpectatorPipeModule(options);
beforeEach(waitForAsync(() => {
addMatchers(customMatchers);
TestBed.configureTestingModule(moduleMetadata);
overrideModules(options);
overridePipes(options);
}));
return (templateOrOverrides, overrides) => {
const defaults = {
hostProps: {},
detectChanges: true,
providers: [],
};
const resolvedOverrides = typeof templateOrOverrides === 'object' ? templateOrOverrides : overrides;
const { detectChanges, hostProps, providers } = { ...defaults, ...resolvedOverrides };
const template = typeof templateOrOverrides === 'string' ? templateOrOverrides : options.template;
if (providers && providers.length) {
providers.forEach((provider) => {
TestBed.overrideProvider(provider.provide, provider);
});
}
if (template) {
TestBed.overrideModule(BrowserDynamicTestingModule, {}).overrideComponent(options.host, {
set: { template },
});
}
const spectator = createSpectatorPipe(options, hostProps);
if (options.detectChanges && detectChanges) {
spectator.detectChanges();
}
return spectator;
};
}
function createSpectatorPipe(options, hostProps) {
const hostFixture = TestBed.createComponent(options.host);
const debugElement = hostFixture.debugElement;
const hostComponent = setHostProps(hostFixture.componentRef, hostProps);
return new SpectatorPipe(hostComponent, hostFixture, hostFixture.debugElement, debugElement.nativeElement);
}
/// <reference path="./lib/matchers-types.ts" />
/**
* Generated bundle index. Do not edit.
*/
export { ActivatedRouteStub, DOMSelector, HostComponent, HostModule, HttpMethod, Spectator, SpectatorDirective, SpectatorHost, SpectatorHttp, SpectatorPipe, SpectatorRouting, SpectatorService, byAltText, byLabel, byPlaceholder, byRole, byTestId, byText, byTextContent, byTitle, byValue, createComponentFactory, createDirectiveFactory, createFakeEvent, createHostFactory, createHttpFactory, createKeyboardEvent, createMouseEvent, createPipeFactory, createRoutingFactory, createServiceFactory, createSpyObject, createTouchEvent, defineGlobalsInjections, dispatchEvent, dispatchFakeEvent, dispatchKeyboardEvent, dispatchMouseEvent, dispatchTouchEvent, doesServiceImplementsOnDestroy, initialSpectatorDirectiveModule, initialSpectatorModule, initialSpectatorPipeModule, initialSpectatorWithHostModule, installProtoMethods, isHTMLOptionElementArray, isNumber, isObject, isString, isType, mockProvider, toBeChecked, toBeDisabled, toBeEmpty, toBeFocused, toBeHidden, toBeIndeterminate, toBeMatchedBy, toBePartial, toBeSelected, toBeVisible, toContainProperty, toContainText, toContainValue, toExist, toHaveAttribute, toHaveClass, toHaveData, toHaveDescendant, toHaveDescendantWithText, toHaveExactText, toHaveExactTrimmedText, toHaveId, toHaveLength, toHaveProperty, toHaveSelectedOptions, toHaveStyle, toHaveText, toHaveValue, typeInElement };
//# sourceMappingURL=ngneat-spectator.mjs.map