UNPKG

ngx-testing-tools

Version:
1,310 lines (1,209 loc) 47.9 kB
import * as i0 from '@angular/core'; import { isStandalone, Injector, InjectionToken, ElementRef, Injectable, Inject, ViewContainerRef, Component, ɵisInjectable as _isInjectable, inject, signal } from '@angular/core'; import * as i1 from '@angular/core/testing'; import { TestBed, ComponentFixture } from '@angular/core/testing'; import { provideNoopAnimations } from '@angular/platform-browser/animations'; import { provideHttpClient, HttpClient, HttpErrorResponse, HttpRequest, HttpResponse, HTTP_INTERCEPTORS, withInterceptorsFromDi, withInterceptors } from '@angular/common/http'; import { provideHttpClientTesting, HttpTestingController } from '@angular/common/http/testing'; import { Subscription, throwError, of, filter } from 'rxjs'; import { By } from '@angular/platform-browser'; import { provideLocationMocks } from '@angular/common/testing'; import { NavigationEnd, Router, provideRouter, ActivatedRouteSnapshot } from '@angular/router'; import { RouterTestingHarness } from '@angular/router/testing'; async function postAsync(value, postAction) { await value; postAction(); } function buildJasmineDone(done, postAction) { const doneWrapper = (() => { done(); postAction(); }); doneWrapper.fail = (message) => { try { done.fail(message); } catch (err) { postAction(); throw err; } }; return doneWrapper; } function buildJasmineCallback(args) { const { callback, deferredTools, preTest, postTest } = args; const callbackWrapper = (done) => { const tools = deferredTools(); preTest?.(tools); return (done) ? callback(tools, buildJasmineDone(done, () => postTest?.(tools))) : postAsync(callback(tools, null), () => postTest?.(tools)); }; return (callback.length > 1) ? (done) => callbackWrapper(done) : () => callbackWrapper(null); } function mergeBaseFactory(factory, tb) { tb.import = (imports) => { factory.import(imports); return tb; }; tb.provide = (providers) => { factory.provide(providers); return tb; }; tb.inject = (name, token) => { factory.inject(name, token); return tb; }; tb._compileEach = factory._compileEach.bind(factory); tb._shouldCreate = factory._shouldCreate.bind(factory); tb.compile = factory.compile.bind(factory); tb.setup = factory.setup.bind(factory); return tb; } function mergeRendererFactory(factory, tb) { mergeBaseFactory(factory, tb); tb.declare = (declarations) => { factory.declare(declarations); return tb; }; return tb; } function getAnnotation(AnyCtor, metadataName) { const annotations = AnyCtor['__annotations__']; if (!annotations) return null; for (let i = annotations.length - 1; i >= 0; i--) { const annotation = annotations[i]; if (isSameMetadataName(annotation, metadataName)) return annotation; } return null; } function isSameMetadataName(annotation, metadataName) { return Object.getPrototypeOf(annotation)['ngMetadataName'] === metadataName; } function getComponentAnnotation(ComponentCtor) { return getAnnotation(ComponentCtor, 'Component'); } function throwCtorError(options) { const { name, type, testBedName } = options; throw new ReferenceError(`The provided "${name}" is not a ${type}. The ${testBedName} cannot be created.`); } function assertComponentCtor(ComponentCtor) { const annotation = getComponentAnnotation(ComponentCtor); if (!annotation) throwCtorError({ name: ComponentCtor.name ?? ComponentCtor, type: 'Component', testBedName: 'ComponentTestBed' }); } function throwInstanceError(options) { const { name } = options; throw new ReferenceError(`${name} instance is falsy. You need to set \`autoCompile = true\` (default) or set \`beforeEach(() => tb.compile());\` and \`autoCompile = false\` before running expectations.`); } function assertInstance(instance, Ctor) { if (!(instance instanceof Ctor)) throwInstanceError({ name: Ctor.name }); } function shouldCreate(factory) { globalThis.it('should create', () => { expect(factory()).toBeTruthy(); }); } function makeArray(itemS) { return (Array.isArray(itemS)) ? itemS : [itemS]; } function appendSet(set, itemS) { makeArray(itemS).forEach(v => set.add(v)); } class BaseTestBedFactory { constructor(described, options) { this.described = described; this.testBed = TestBed; this._instance = null; this.injectedMap = new Map(); const { imports = [], providers = [], autoCompile = true, checkCreate = true, } = options; this.imports = new Set(imports); this.providers = new Set(providers); if (autoCompile) this._compileEach(); if (checkCreate) this._shouldCreate(); } get instance() { assertInstance(this._instance, this.described); return this._instance; } injectDescribed() { this._instance = this.testBed.inject(this.described); } import(oneOrManyImports) { appendSet(this.imports, oneOrManyImports); return this; } provide(oneOrManyProviders) { appendSet(this.providers, oneOrManyProviders); return this; } /** * Injects an instance by token into the custom test bed. * * Retrieve it into the tools `injected` by autocompletion. * @param name the key to access the instance. * @param token the provider token. */ inject(name, token) { this.injectedMap.set(name, token); return this; } _compileEach() { globalThis.beforeEach(() => this.compile()); } /** * Manually compiles the custom test bed to make enhanced tools available. * * To be used when `autoCompile = false`. * * **To be called inside jasmine `beforeEach` callback.** */ async compile() { this.testBed.configureTestingModule({ imports: [...this.imports.values()], providers: [...this.providers.values()], }); } /** * Sets up extra stuffs using the enhanced tools. * * **Works only for `beforeEach` and `afterEach`**. */ setup(action) { return buildJasmineCallback({ callback: action, deferredTools: this.deferredTools, }); } _shouldCreate() { shouldCreate(() => this.instance); } } class RendererTestBedFactory extends BaseTestBedFactory { constructor(described, host, options) { super(described, options); this.host = host; this._fixture = null; const { declarations = [], schemas = [], startDetectChanges = true, noopAnimations = true, noTemplate = false, } = options; this.startDetectChanges = startDetectChanges; this.noTemplate = noTemplate; this.declarations = new Set(declarations); this.schemas = new Set(schemas); if (noopAnimations) this.provide(provideNoopAnimations()); (isStandalone(this.host)) ? this.import(this.host) : this.declare(this.host); (isStandalone(this.described)) ? this.import(this.described) : this.declare(this.described); } get fixture() { assertInstance(this._fixture, ComponentFixture); return this._fixture; } createHostFixture() { this._fixture = this.testBed.createComponent(this.host); } declare(oneOrManyDeclarations) { appendSet(this.declarations, oneOrManyDeclarations); return this; } async compile() { await super.compile(); this.testBed.configureTestingModule({ declarations: [...this.declarations.values()], schemas: [...this.schemas.values()], }); if (this.noTemplate) this.testBed.overrideTemplateUsingTestingModule(this.host, ''); await this.testBed.compileComponents(); } _shouldCreate() { shouldCreate(() => this.fixture.componentInstance); } } function httpProviders(...features) { return [ provideHttpClient(...features), provideHttpClientTesting(), ]; } const HTTP_PROVIDERS = httpProviders(); function buildInjected(injector, injectedMap) { const injected = {}; for (const [name, token] of injectedMap.entries()) { injected[name] = injector.get(token); } return injected; } class RxBox { constructor() { this.subs = []; this.subjects = []; } set remind(value) { (value instanceof Subscription) ? this.subs.push(value) : this.subjects.push(value); } bigRemind(values) { values.forEach(v => (this.remind = v)); } cleanAll() { this.unsubscribe(); this.complete(); } unsubscribe() { this.clean(this.subs, 'unsubscribe'); } complete() { this.clean(this.subjects, 'complete'); } clean(array, cleanFnKey) { array.forEach(v => v[cleanFnKey]()); array.length = 0; } } function buildBaseTools(factory, options) { const injector = options?.thisInjector ?? factory['testBed'].inject(Injector); const injected = buildInjected(injector, factory['injectedMap']); const rx = new RxBox(); return { injected, injector, rx }; } function emitFakeErrorResponse(httpController, config) { const { url, method, status, statusText = 'Error' } = config; const error = new ProgressEvent(statusText); httpController .expectOne({ url, method }) .error(error, { status, statusText }); } function emitFakeSuccessResponse(httpController, config) { const { url, method, body, status, headers, statusText } = config; httpController .expectOne({ url, method }) .flush(body, { status, headers, statusText }); } function buildHttpTools(injector, options) { if (!options.httpTesting) return unusableHttpTools(); const client = injector.get(HttpClient); const controller = injector.get(HttpTestingController); return { client, controller, emitSuccessResponse: (config) => emitFakeSuccessResponse(controller, config), emitErrorResponse: (config) => emitFakeErrorResponse(controller, config), }; } function unusableHttpTools() { return { get client() { return throwDisabledHttpTesting('client'); }, get controller() { return throwDisabledHttpTesting('controller'); }, emitSuccessResponse: () => throwDisabledHttpTesting('emitSuccessResponse'), emitErrorResponse: () => throwDisabledHttpTesting('emitErrorResponse'), }; } function throwDisabledHttpTesting(key) { throw new ReferenceError(`Cannot use \`http.${key}\` because HttpTools is not initialized. You need to set \`httpTesting:true\` into the test bed options.`); } function throwCannotFind(selectorOrDirective, quantifier = 'one') { const input = (typeof selectorOrDirective === 'string') ? `selector "${selectorOrDirective}"` : `directive "${selectorOrDirective.name}"`; throw new Error(`Cannot find ${quantifier} DebugElement with : ${input}.`); } function findDebugElement(fixture, selectorOrDirective) { const element = (typeof selectorOrDirective === 'string') ? fixture.debugElement.query(By.css(selectorOrDirective)) : fixture.debugElement.query(By.directive(selectorOrDirective)); if (!element) throwCannotFind(selectorOrDirective); return element; } function emitEvent(fixture, selectorOrDirective, name, value) { findDebugElement(fixture, selectorOrDirective) .triggerEventHandler(name, value); } function emitOutput(fixture, selectorOrDirective, name, $event) { emitEvent(fixture, selectorOrDirective, name, $event); } function click(fixture, selectorOrDirective) { emitOutput(fixture, selectorOrDirective, 'click'); } function buildActionTools(fixture) { return { click: (input) => click(fixture, input), emitOutput: (directive, name, val) => emitOutput(fixture, directive, name, val), // setInput: (name: string, $value: any) => {} }; } function findAllDebugElements(fixture, selectorOrDirective) { const debugs = (typeof selectorOrDirective === 'string') ? fixture.debugElement.queryAll(By.css(selectorOrDirective)) : fixture.debugElement.queryAll(By.directive(selectorOrDirective)); if (debugs.length === 0) throwCannotFind(selectorOrDirective, 'many'); return debugs; } function findAllComponents(fixture, selectorOrDirective) { return findAllDebugElements(fixture, selectorOrDirective).map(e => e.componentInstance); } function findAllElements(fixture, selectorOrDirective) { return findAllDebugElements(fixture, selectorOrDirective).map(e => e.nativeElement); } function findComponent(fixture, selectorOrDirective) { return findDebugElement(fixture, selectorOrDirective).componentInstance; } function findElement(fixture, selectorOrDirective) { return findDebugElement(fixture, selectorOrDirective).nativeElement; } function buildQueryTools(fixture) { return { findComponent: (input) => findComponent(fixture, input), findAllComponents: (input) => findAllComponents(fixture, input), findElement: (input) => findElement(fixture, input), findAllElements: (input) => findAllElements(fixture, input), findDebugElement: (input) => findDebugElement(fixture, input), findAllDebugElements: (input) => findAllDebugElements(fixture, input), }; } function buildRendererTools(factory) { const fixture = factory['fixture']; const host = fixture.componentInstance; const element = fixture.debugElement.children.at(0)?.nativeElement; const query = buildQueryTools(fixture); const action = buildActionTools(fixture); return { action, element, fixture, host, query }; } function buildComponentTools(factory, httpOptions) { const { action, element, fixture, host: component, query } = buildRendererTools(factory); const { injected, injector, rx } = buildBaseTools(factory, { thisInjector: fixture.debugElement.injector }); const http = buildHttpTools(injector, httpOptions); return { action, component, element, fixture, http, injected, injector, query, rx }; } class ComponentTestBedFactory extends RendererTestBedFactory { constructor(rootComponent, options) { assertComponentCtor(rootComponent); super(rootComponent, rootComponent, options); this.deferredTools = () => buildComponentTools(this, this.httpOptions); const { httpTesting = false, verifyHttp = true, } = options; if (httpTesting) this.provide(HTTP_PROVIDERS); this.httpOptions = { httpTesting, verifyHttp }; } async compile() { await super.compile(); this.createHostFixture(); } } /** * Creates a new `ComponentTestBed` to configure the custom test bed and wrap the assertion test. * @param rootComponent - The described Component. * @param options */ function componentTestBed(rootComponent, options = {}) { const factory = new ComponentTestBedFactory(rootComponent, options); const { httpTesting, verifyHttp: defaultVerifyHttp, } = factory['httpOptions']; const defaultStartDetectChanges = factory['startDetectChanges']; const noTemplate = factory['noTemplate']; const tb = ((assertion, opts = {}) => { const { startDetectChanges = defaultStartDetectChanges, verifyHttp = defaultVerifyHttp, } = opts; return buildJasmineCallback({ callback: assertion, deferredTools: factory['deferredTools'], preTest: (tools) => { if (!noTemplate && startDetectChanges) tools.fixture.detectChanges(); }, postTest: (tools) => { if (httpTesting && verifyHttp) tools.http.controller.verify(); tools.rx['cleanAll'](); }, }); }); return mergeRendererFactory(factory, tb); } /** * Only invokes the "should create" test. * * To be used when there are no noticeable or relevant tests to be performed on this component. * * The usage of this function and `componentTestBed` function must be mutually exclusive. * * @param rootComponent - The described Component. * @param options */ function itShouldCreateComponent(rootComponent, options) { componentTestBed(rootComponent, options); } function getDirectiveAnnotation(DirectiveCtor) { return getAnnotation(DirectiveCtor, 'Directive'); } function assertDirectiveCtor(DirectiveCtor) { const annotation = getDirectiveAnnotation(DirectiveCtor); if (!annotation) throwCtorError({ name: DirectiveCtor.name ?? DirectiveCtor, type: 'Directive', testBedName: 'DirectiveTestBed' }); } const HOST_FIXTURE = new InjectionToken('HOST_FIXTURE'); class HostElementRef extends ElementRef { constructor(fixture) { // Use `innerHTML` to remove the fixture element wrapper (<div id="rootXX" ng-version="X.X.X">..</div>) super(fixture.nativeElement.innerHTML); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.1", ngImport: i0, type: HostElementRef, deps: [{ token: HOST_FIXTURE }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.0.1", ngImport: i0, type: HostElementRef }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.1", ngImport: i0, type: HostElementRef, decorators: [{ type: Injectable }], ctorParameters: () => [{ type: i1.ComponentFixture, decorators: [{ type: Inject, args: [HOST_FIXTURE] }] }] }); function buildDirectiveTools(factory) { const directive = factory['instance']; const { action, element, fixture, host, query } = buildRendererTools(factory); const { injected, injector, rx } = buildBaseTools(factory, { thisInjector: fixture.debugElement.injector }); return { action, directive, element, fixture, host, injected, injector, query, rx }; } class DirectiveTestBedFactory extends RendererTestBedFactory { constructor(rootDirective, hostComponent, options) { assertDirectiveCtor(rootDirective); super(rootDirective, hostComponent, options); this.deferredTools = () => buildDirectiveTools(this); this.provide([ rootDirective, { provide: HOST_FIXTURE, useFactory: () => this.fixture }, { provide: ElementRef, useClass: HostElementRef }, ViewContainerRef, ]); } async compile() { await super.compile(); this.createHostFixture(); this.injectDescribed(); } } /** * Creates a new `DirectiveTestBed` to configure the custom test bed and wrap the assertion test. * @param rootDirective - The described Directive. * @param hostComponent - The host component on which the directive is tested. * @param options */ function directiveTestBed(rootDirective, hostComponent, options = {}) { const factory = new DirectiveTestBedFactory(rootDirective, hostComponent, options); const defaultStartDetectChanges = factory['startDetectChanges']; const noTemplate = factory['noTemplate']; const tb = ((assertion, opts = {}) => { const { startDetectChanges = defaultStartDetectChanges } = opts; return buildJasmineCallback({ callback: assertion, deferredTools: factory['deferredTools'], preTest: (tools) => { if (!noTemplate && startDetectChanges) tools.fixture.detectChanges(); }, postTest: (tools) => { tools.rx['cleanAll'](); }, }); }); return mergeRendererFactory(factory, tb); } /** * Only invokes the "should create" test. * * To be used when there are no noticeable or relevant tests to be performed on this directive. * * The usage of this function and `directiveTestBed` function must be mutually exclusive. * * @param rootDirective - The described Directive. * @param options */ function itShouldCreateDirective(rootDirective, options) { 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, isStandalone: true, selector: "host-component:not(div)", ngImport: i0, template: '', isInline: true }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.1", ngImport: i0, type: HostComponent, decorators: [{ type: Component, args: [{ template: '', standalone: true, selector: 'host-component:not(div)' }] }] }); directiveTestBed(rootDirective, HostComponent, options); } function getInjectableAnnotation(InjectableCtor) { return getAnnotation(InjectableCtor, 'Injectable'); } function assertServiceCtor(ServiceCtor) { const annotation = getInjectableAnnotation(ServiceCtor); if (!annotation) throwCtorError({ name: ServiceCtor.name ?? ServiceCtor, type: 'Injectable Service', testBedName: 'ServiceTestBed' }); } function buildServiceTools(factory, httpOptions) { const service = factory['instance']; const { injected, injector, rx } = buildBaseTools(factory); const http = buildHttpTools(injector, httpOptions); return { http, injected, injector, rx, service }; } class ServiceTestBedFactory extends BaseTestBedFactory { constructor(rootService, options) { assertServiceCtor(rootService); super(rootService, options); this.deferredTools = () => buildServiceTools(this, this.httpOptions); const { httpTesting = false, verifyHttp = true, } = options; this.httpOptions = { httpTesting, verifyHttp }; if (httpTesting) this.provide(HTTP_PROVIDERS); this.provide(this.described); } async compile() { await super.compile(); this.injectDescribed(); } } /** * Creates a new `ServiceTestBed` to configure the custom test bed and wrap the assertion test. * @param rootService - The described Service. * @param options */ function serviceTestBed(rootService, options = {}) { const factory = new ServiceTestBedFactory(rootService, options); const { httpTesting, verifyHttp: defaultVerifyHttp, } = factory['httpOptions']; const tb = ((assertion, opts = {}) => { const { verifyHttp = defaultVerifyHttp } = opts; return buildJasmineCallback({ callback: assertion, deferredTools: factory['deferredTools'], postTest: (tools) => { if (httpTesting && verifyHttp) tools.http.controller.verify(); tools.rx['cleanAll'](); }, }); }); return mergeBaseFactory(factory, tb); } /** * Only invokes the "should create" test. * * To be used when there are no noticeable or relevant tests to be performed on this service. * * The usage of this function and `serviceTestBed` function must be mutually exclusive. * * @param rootService - The described Service. * @param options */ function itShouldCreateService(rootService, options) { serviceTestBed(rootService, options); } function getNgModuleAnnotation(NgModuleCtor) { return getAnnotation(NgModuleCtor, 'NgModule'); } function assertModuleCtor(ModuleCtor) { const annotation = getNgModuleAnnotation(ModuleCtor); if (!annotation) throwCtorError({ name: ModuleCtor.name ?? ModuleCtor, type: 'Module', testBedName: 'ModuleTestBed' }); } function buildModuleTools(factory) { const module = factory['instance']; const { injected, injector, rx } = buildBaseTools(factory); return { injected, injector, module, rx }; } class ModuleTestBedFactory extends BaseTestBedFactory { constructor(rootModule, options) { assertModuleCtor(rootModule); super(rootModule, options); this.deferredTools = () => buildModuleTools(this); this.import(rootModule); } async compile() { await super.compile(); this.injectDescribed(); } } /** * Creates a new `ModuleTestBed` to configure the custom test bed and wrap the assertion test. * @param rootModule - The described Module. * @param options */ function moduleTestBed(rootModule, options = {}) { const factory = new ModuleTestBedFactory(rootModule, options); const tb = ((assertion) => { return buildJasmineCallback({ callback: assertion, deferredTools: factory['deferredTools'], postTest: (tools) => { tools.rx['cleanAll'](); }, }); }); return mergeBaseFactory(factory, tb); } /** * Only invokes the "should create" test. * * To be used when there are no noticeable or relevant tests to be performed on this module. * * The usage of this function and `moduleTestBed` function must be mutually exclusive. * * @param rootModule - The described Module. * @param options */ function itShouldCreateModule(rootModule, options) { moduleTestBed(rootModule, options); } function getPipeAnnotation(PipeCtor) { return getAnnotation(PipeCtor, 'Pipe'); } function assertPipeCtor(PipeCtor) { const annotation = getPipeAnnotation(PipeCtor); if (!annotation) throwCtorError({ name: PipeCtor.name ?? PipeCtor, type: 'Pipe', testBedName: 'PipeTestBed' }); } function buildVerifyTools(pipe) { const verify = ({ expected, data, parameters }) => { expect(pipe.transform(data, ...parameters)).toEqual(expected); }; verify.many = (matchers) => { matchers.forEach(({ expected, data, parameters }) => { verify({ expected, data, parameters }); }); }; return verify; } function buildPipeTools(factory) { const pipe = factory['instance']; const { injected, injector, rx } = buildBaseTools(factory); const verify = buildVerifyTools(pipe); return { injected, injector, pipe, rx, verify }; } class PipeTestBedFactory extends BaseTestBedFactory { constructor(rootPipe, options) { assertPipeCtor(rootPipe); super(rootPipe, options); this.deferredTools = () => buildPipeTools(this); this.provide(rootPipe); } async compile() { await super.compile(); this.injectDescribed(); } } /** * Creates a new `PipeTestBed` to configure the custom test bed and wrap the assertion test. * @param rootPipe - The described Pipe. * @param options */ function pipeTestBed(rootPipe, options = {}) { const factory = new PipeTestBedFactory(rootPipe, options); const tb = ((assertion) => { return buildJasmineCallback({ callback: assertion, deferredTools: factory['deferredTools'], postTest: (tools) => { tools.rx['cleanAll'](); }, }); }); return mergeBaseFactory(factory, tb); } /** * Only invokes the "should create" test. * * To be used when there are no noticeable or relevant tests to be performed on this pipe. * * The usage of this function and `pipeTestBed` function must be mutually exclusive. * * @param rootPipe - The described Pipe. * @param options */ function itShouldCreatePipe(rootPipe, options) { pipeTestBed(rootPipe, options); } function assertFn(fn) { if (typeof fn !== 'function') throw new ReferenceError(`The provided "${fn?.toString()}" is not a valid function.`); } function assertInjectableCtor(InjectableCtor, testBedName) { const annotation = getInjectableAnnotation(InjectableCtor); if (!annotation) throwCtorError({ name: InjectableCtor.name ?? InjectableCtor, type: 'Injectable class', testBedName }); } function assertInterceptorCtor(InterceptorCtor) { assertInjectableCtor(InterceptorCtor, 'InterceptorTestBed'); } function isConstructor(target) { return _isInjectable(target); } const INTERCEPTOR_INFO = new InjectionToken('ROOT_INTERCEPTOR_INFO'); class InterceptorProxy { constructor() { this.info = inject(INTERCEPTOR_INFO); this.isRootCtor = this.info.isRootCtor; this.instance = (this.isRootCtor) ? inject(this.info.rootInterceptor) : this.info.rootInterceptor; } intercept(req, next) { return (this.isRootCtor) ? this.instance.intercept(req, next) : TestBed.runInInjectionContext(() => this.instance(req, next.handle)); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.1", ngImport: i0, type: InterceptorProxy, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.0.1", ngImport: i0, type: InterceptorProxy }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.1", ngImport: i0, type: InterceptorProxy, decorators: [{ type: Injectable }] }); function mockErrorResponseHandlerFactory(res) { return { handle: () => throwError(() => res) }; } function inspectErrorResponse(interceptor, resOrUrl, error) { const res = (resOrUrl instanceof HttpErrorResponse) ? resOrUrl : new HttpErrorResponse({ url: resOrUrl, error, status: 500 }); const req = new HttpRequest('GET', (typeof resOrUrl === 'string') ? resOrUrl : '/test'); return interceptor.intercept(req, mockErrorResponseHandlerFactory(res)); } function mockRequestHandlerFactory() { return { handle: (req) => of(req) }; } function inspectRequest(interceptor, reqOrUrl, url, body = null) { const req = (reqOrUrl instanceof HttpRequest) ? reqOrUrl : new HttpRequest(reqOrUrl, url, body); return interceptor.intercept(req, mockRequestHandlerFactory()); } function mockSuccessResponseHandlerFactory(res) { return { handle: () => of(res) }; } function inspectSuccessResponse(interceptor, resOrUrl, body = null) { const res = (resOrUrl instanceof HttpResponse) ? resOrUrl : new HttpResponse({ url: resOrUrl, body, status: 200 }); const req = new HttpRequest('GET', (typeof resOrUrl === 'string') ? resOrUrl : '/test'); return interceptor.intercept(req, mockSuccessResponseHandlerFactory(res)); } function buildInspectTools(interceptor) { return { request: (reqOrMethod, url, body) => inspectRequest(interceptor, reqOrMethod, url, body), successResponse: (resOrUrl, body) => inspectSuccessResponse(interceptor, resOrUrl, body), errorResponse: (resOrUrl, error) => inspectErrorResponse(interceptor, resOrUrl, error), }; } function buildInterceptorTools(factory) { const interceptorProxy = factory['instance']; const interceptor = interceptorProxy.instance; const { injected, injector, rx } = buildBaseTools(factory); const http = buildHttpTools(injector, { httpTesting: true }); const inspect = buildInspectTools(interceptorProxy); return { http, injected, injector, inspect, interceptor, rx }; } class InterceptorTestBedFactory extends BaseTestBedFactory { constructor(rootInterceptor, options) { const isRootCtor = isConstructor(rootInterceptor); if (isRootCtor) assertInterceptorCtor(rootInterceptor); else assertFn(rootInterceptor); super(InterceptorProxy, options); this.deferredTools = () => buildInterceptorTools(this); this.provide([ InterceptorProxy, { provide: INTERCEPTOR_INFO, useValue: { rootInterceptor, isRootCtor } }, ]); if (isRootCtor) { this.provide([ rootInterceptor, { provide: HTTP_INTERCEPTORS, useClass: rootInterceptor, multi: true }, httpProviders(withInterceptorsFromDi()), ]); } else { this.provide(httpProviders(withInterceptors([rootInterceptor]))); } } async compile() { await super.compile(); this.injectDescribed(); } } function interceptorTestBed(rootInterceptor, options = {}) { const { verifyHttp: globalVerifyHttp, } = options; const factory = new InterceptorTestBedFactory(rootInterceptor, options); const tb = ((assertion, opts = {}) => { const { verifyHttp = globalVerifyHttp ?? true } = opts; return buildJasmineCallback({ callback: assertion, deferredTools: factory['deferredTools'], postTest: (tools) => { if (verifyHttp) tools.http.controller.verify(); tools.rx['cleanAll'](); }, }); }); return mergeBaseFactory(factory, tb); } function itShouldCreateInterceptor(rootInterceptor, options) { interceptorTestBed(rootInterceptor, options); } function buildRouterTools(factory) { const router = factory['instance']; const routes = factory['_routes']; const harness = factory['_harness']; const { injected, injector, rx } = buildBaseTools(factory); const urlSignal = signal(router.url); rx.remind = router.events .pipe(filter((event) => (event instanceof NavigationEnd))) .subscribe({ next: (event) => urlSignal.set(event.url), }); const $url = urlSignal.asReadonly(); const navigateByUrl = harness.navigateByUrl.bind(harness); return { $url, harness, injected, injector, navigateByUrl, routes, rx }; } class RouterTestBedFactory extends BaseTestBedFactory { constructor(routes, options) { super(Router, options); this._harness = null; this.deferredTools = () => buildRouterTools(this); const { initialUrl = '', startDetectChanges = true, } = options; this._routes = routes; this.initialUrl = initialUrl; this.startDetectChanges = startDetectChanges; this.provide([ provideRouter(routes), provideLocationMocks(), ]); this.createHarnessBeforeEachTest(); } async compile() { await super.compile(); this.injectDescribed(); } createHarnessBeforeEachTest() { globalThis.beforeEach(async () => { this._harness = await RouterTestingHarness.create(this.initialUrl); }); } } /** * Creates a new `RouterTestBed` to configure the custom test bed and wrap the assertion test. * @param routes - the routes to be tested * @param options - check `RouterTestBedOptions` */ function routerTestBed(routes, options = {}) { const factory = new RouterTestBedFactory(routes, options); const defaultStartDetectChanges = factory['startDetectChanges']; const tb = ((assertion, opts = {}) => { const { startDetectChanges = defaultStartDetectChanges, } = opts; return buildJasmineCallback({ callback: assertion, deferredTools: factory['deferredTools'], preTest: (tools) => { if (startDetectChanges) tools.harness.detectChanges(); }, postTest: (tools) => { tools.rx['cleanAll'](); }, }); }); return mergeBaseFactory(factory, tb); } function assertGuardCto(GuardCtor) { assertInjectableCtor(GuardCtor, 'GuardTestBed'); } const GUARD_INFO = new InjectionToken('ROOT_GUARD_INFO'); class GuardProxy { constructor() { this.info = inject(GUARD_INFO); this.isRootCtor = this.info.isRootCtor; this.instance = (this.isRootCtor) ? inject(this.info.rootGuard) : this.info.rootGuard; } canActivate(route, state) { return (this.isRootCtor) ? this.instance.canActivate(route, state) : TestBed.runInInjectionContext(() => this.instance(route, state)); } canActivateChild(childRoute, state) { return (this.isRootCtor) ? this.instance.canActivateChild(childRoute, state) : TestBed.runInInjectionContext(() => this.instance(childRoute, state)); } canDeactivate(component, currentRoute, currentState, nextState) { return (this.isRootCtor) ? this.instance.canDeactivate(component, currentRoute, currentState, nextState) : TestBed.runInInjectionContext(() => this.instance(component, currentRoute, currentState, nextState)); } canLoad(route, segments) { return (this.isRootCtor) ? this.instance.canLoad(route, segments) : TestBed.runInInjectionContext(() => this.instance(route, segments)); } canMatch(route, segments) { return (this.isRootCtor) ? this.instance.canMatch(route, segments) : TestBed.runInInjectionContext(() => this.instance(route, segments)); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.1", ngImport: i0, type: GuardProxy, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.0.1", ngImport: i0, type: GuardProxy }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.1", ngImport: i0, type: GuardProxy, decorators: [{ type: Injectable }] }); function getRouterState(injector) { return injector.get(Router).routerState.snapshot; } function buildRouteSnapshot(config = {}) { const { data = {}, params = {}, queryParams = {}, } = config; const route = new ActivatedRouteSnapshot(); route.data = data; route.params = params; route.queryParams = queryParams; return route; } function isCanActivateGuard(guard, type) { return (type === 'CanActivate') || (type === 'ctor' && !!guard.canActivate); } function isCanActivateChildGuard(guard, type) { return (type === 'CanActivateChild') || (type === 'ctor' && !!guard.canActivateChild); } function isCanDeactivateGuard(guard, type) { return (type === 'CanDeactivate') || (type === 'ctor' && !!guard.canDeactivate); } function isCanLoadGuard(guard, type) { return (type === 'CanLoad') || (type === 'ctor' && !!guard.canLoad); } function isCanMatchGuard(guard, type) { return (type === 'CanMatch') || (type === 'ctor' && !!guard.canMatch); } function buildChallengeTools(guardProxy, injector, guardType) { const guard = guardProxy.instance; switch (true) { case isCanActivateGuard(guard, guardType): return buildChallengeToolsForActivate(guardProxy, injector, 'canActivate'); case isCanActivateChildGuard(guard, guardType): return buildChallengeToolsForActivate(guardProxy, injector, 'canActivateChild'); case isCanDeactivateGuard(guard, guardType): return buildChallengeToolsForDeactivate(guardProxy, injector); case isCanLoadGuard(guard, guardType): return buildChallengeToolsForLoad(guardProxy, 'canLoad'); case isCanMatchGuard(guard, guardType): return buildChallengeToolsForLoad(guardProxy, 'canMatch'); default: throw new Error(`Unknown guard type (${guardType}) or invalid passed guard (${guard.name ?? guard}).`); } } function buildChallengeToolsForActivate(guardProxy, injector, key) { const challenge = () => { const route = buildRouteSnapshot(); const state = getRouterState(injector); return guardProxy[key](route, state); }; challenge.withInfo = (info) => { const { currentState: state = getRouterState(injector), data, params, queryParams, } = info; const route = buildRouteSnapshot({ data, params, queryParams }); return guardProxy[key](route, state); }; return challenge; } function buildChallengeToolsForDeactivate(guardProxy, injector) { const challenge = () => { const currentRoute = buildRouteSnapshot(); const state = getRouterState(injector); return guardProxy.canDeactivate({}, currentRoute, state, state); }; challenge.withInfo = (info) => { const { currentState = getRouterState(injector), nextState = currentState, component: ComponentCtor, data, params, queryParams, } = info; const component = (isConstructor(ComponentCtor)) ? TestBed.runInInjectionContext(() => inject(ComponentCtor)) : ComponentCtor; const currentRoute = buildRouteSnapshot({ data, params, queryParams }); return guardProxy.canDeactivate(component ?? {}, currentRoute, currentState, nextState); }; return challenge; } function buildChallengeToolsForLoad(guardProxy, key) { const challenge = () => { return guardProxy[key]({ data: {} }, []); }; challenge.withInfo = (info) => { const { route = {}, data = {}, segments = [], } = info; route.data ??= data; return guardProxy[key](route, segments); }; return challenge; } function buildGuardTools(factory, httpOptions) { const guardProxy = factory['instance']; const guardType = factory['type']; const guard = guardProxy.instance; const { injected, injector, rx } = buildBaseTools(factory); const http = buildHttpTools(injector, httpOptions); const challenge = buildChallengeTools(guardProxy, injector, guardType); return { challenge, guard, http, injected, injector, rx }; } class GuardTestBedFactory extends BaseTestBedFactory { constructor(rootGuard, options) { const isRootCtor = isConstructor(rootGuard); if (isRootCtor) assertGuardCto(rootGuard); else assertFn(rootGuard); super(GuardProxy, options); this.deferredTools = () => buildGuardTools(this, this.httpOptions); const { httpTesting = false, verifyHttp = true, type = 'ctor', } = options; this.type = type; this.provide([ GuardProxy, { provide: GUARD_INFO, useValue: { rootGuard, isRootCtor } }, ]); if (isRootCtor) this.provide(rootGuard); if (httpTesting) this.provide(HTTP_PROVIDERS); this.httpOptions = { httpTesting, verifyHttp }; } async compile() { await super.compile(); this.injectDescribed(); } } function guardTestBed(rootGuard, options = {}) { const factory = new GuardTestBedFactory(rootGuard, options); const { httpTesting, verifyHttp: defaultVerifyHttp, } = factory['httpOptions']; const tb = ((assertion, opts = {}) => { const { verifyHttp = defaultVerifyHttp, } = opts; return buildJasmineCallback({ callback: assertion, deferredTools: factory['deferredTools'], postTest: (tools) => { if (httpTesting && verifyHttp) tools.http.controller.verify(); tools.rx['cleanAll'](); }, }); }); return mergeBaseFactory(factory, tb); } function itShouldCreateGuard(rootGuard, options) { guardTestBed(rootGuard, options); } function assertResolverCtor(ResolverCtor) { assertInjectableCtor(ResolverCtor, 'ResolverTestBed'); } const RESOLVER_INFO = new InjectionToken('ROOT_RESOLVER_INFO'); class ResolverProxy { constructor() { this.info = inject(RESOLVER_INFO); this.isRootCtor = this.info.isRootCtor; this.instance = (this.isRootCtor) ? inject(this.info.rootResolver) : this.info.rootResolver; } resolve(route, state) { return (this.isRootCtor) ? this.instance.resolve(route, state) : TestBed.runInInjectionContext(() => this.instance(route, state)); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.1", ngImport: i0, type: ResolverProxy, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.0.1", ngImport: i0, type: ResolverProxy }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.1", ngImport: i0, type: ResolverProxy, decorators: [{ type: Injectable }] }); function buildTriggerTools(resolver, injector) { const tools = () => { const route = buildRouteSnapshot(); const state = getRouterState(injector); return resolver.resolve(route, state); }; tools.withInfo = (config) => { const { data, params, queryParams } = config; const route = buildRouteSnapshot({ data, params, queryParams }); const state = getRouterState(injector); return resolver.resolve(route, state); }; return tools; } function buildResolverTools(factory, httpOptions) { const resolverProxy = factory['instance']; const resolver = resolverProxy.instance; const { injected, injector, rx } = buildBaseTools(factory); const http = buildHttpTools(injector, httpOptions); const trigger = buildTriggerTools(resolverProxy, injector); return { resolver, trigger, http, injected, injector, rx }; } class ResolverTestBedFactory extends BaseTestBedFactory { constructor(resolver, options) { const isRootCtor = isConstructor(resolver); if (isRootCtor) assertResolverCtor(resolver); else assertFn(resolver); super(ResolverProxy, options); this.deferredTools = () => buildResolverTools(this, this.httpOptions); const { httpTesting = false, verifyHttp = true, } = options; this.provide([ ResolverProxy, { provide: RESOLVER_INFO, useValue: { rootResolver: resolver, isRootCtor } }, ]); if (isRootCtor) this.provide(resolver); if (httpTesting) this.provide(HTTP_PROVIDERS); this.httpOptions = { httpTesting, verifyHttp }; } async compile() { await super.compile(); this.injectDescribed(); } } /** * Creates a new `ResolverTestBed` to configure the custom test bed and wrap the assertion test. * @param resolver - the described resolver * @param options - check `ResolverTestBedOptions` */ function resolverTestBed(resolver, options = {}) { const factory = new ResolverTestBedFactory(resolver, options); const { httpTesting, verifyHttp: defaultVerifyHttp, } = factory['httpOptions']; const tb = ((assertion, opts = {}) => { const { verifyHttp = defaultVerifyHttp, } = opts; return buildJasmineCallback({ callback: assertion, deferredTools: factory['deferredTools'], postTest: (tools) => { if (httpTesting && verifyHttp) tools.http.controller.verify(); tools.rx['cleanAll'](); }, }); }); return mergeBaseFactory(factory, tb); } function itShouldCreateResolver(resolver, options) { resolverTestBed(resolver, options); } /* * Public API Surface of ngx-testing-tools */ /** * Generated bundle index. Do not edit. */ export { componentTestBed, directiveTestBed, guardTestBed, interceptorTestBed, itShouldCreateComponent, itShouldCreateDirective, itShouldCreateGuard, itShouldCreateInterceptor, itShouldCreateModule, itShouldCreatePipe, itShouldCreateResolver, itShouldCreateService, moduleTestBed, pipeTestBed, resolverTestBed, routerTestBed, serviceTestBed }; //# sourceMappingURL=ngx-testing-tools.mjs.map