UNPKG

@ngneat/spectator

Version:

A powerful tool to simplify your Angular tests

2,469 lines 91.8 kB
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