UNPKG

shallow-render

Version:

Shallow rendering test utility for Angular

167 lines 9.45 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Renderer = exports.MissingTestComponentError = exports.InvalidBindOnEntryComponentError = exports.InvalidInputBindError = void 0; const core_1 = require("@angular/core"); const testing_1 = require("@angular/core/testing"); const platform_browser_1 = require("@angular/platform-browser"); const test_framework_1 = require("../test-frameworks/test-framework"); const create_container_1 = require("../tools/create-container"); const create_test_module_1 = require("../tools/create-test-module"); const mock_provider_1 = require("../tools/mock-provider"); const reflect_1 = require("../tools/reflect"); const custom_error_1 = require("./custom-error"); const rendering_1 = require("./rendering"); const mock_statics_1 = require("../tools/mock-statics"); const inject_root_providers_1 = require("../tools/inject-root-providers"); const ng_mock_1 = require("../tools/ng-mock"); class InvalidInputBindError extends custom_error_1.CustomError { constructor(availableInputs, key) { super(`Tried to bind to a property that is not marked as @Input: ${String(key)}\nAvailable input bindings: ${availableInputs}`); } } exports.InvalidInputBindError = InvalidInputBindError; class InvalidBindOnEntryComponentError extends custom_error_1.CustomError { constructor(component) { super(`Tried to bind @Inputs to component that has no selector (${component.name}). EntryComponents cannot have template-bound inputs.\nIf you need to set properties on an EntryComponent, you must set them on the \`instance\`.\nIf this is not meant to be an EntryComoponent, please add a selector in th component definition.\n\nFor more details see the docs:\nhttps://github.com/getsaf/shallow-render/wiki/FAQ#bindings-on-entrycomponents`); } } exports.InvalidBindOnEntryComponentError = InvalidBindOnEntryComponentError; class MissingTestComponentError extends custom_error_1.CustomError { constructor(testComponent) { super(`${testComponent.name} was not found in test template`); } } exports.MissingTestComponentError = MissingTestComponentError; class Renderer { constructor(_setup) { this._setup = _setup; } _createTemplateString(directive, bindings) { const componentInputs = reflect_1.reflect .getInputsAndOutputs(this._setup.testComponentOrService) .inputs.reduce((acc, input) => (Object.assign(Object.assign({}, acc), { [input.propertyName]: input.alias || input.propertyName })), {}); const inputBindings = Object.keys(bindings) .map(key => `[${componentInputs[key]}]="${key}"`) .join(' '); return `<${directive.selector} ${inputBindings}></${directive.selector}>`; } render(templateOrOptions, optionsOrUndefined) { return __awaiter(this, void 0, void 0, function* () { const [template, options] = typeof templateOrOptions === 'string' ? [templateOrOptions, optionsOrUndefined] : [undefined, templateOrOptions]; const finalOptions = Object.assign({ detectChanges: true, whenStable: true, bind: {} }, options); (0, mock_statics_1.mockStatics)(this._setup); (0, inject_root_providers_1.injectRootProviders)(this._setup); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const resolvedTestComponent = reflect_1.reflect.resolveDirective(this._setup.testComponentOrService); if (!template) { // If no template is used, the bindings should be verified to match the // component @Input properties this._verifyComponentBindings(resolvedTestComponent, finalOptions.bind); } const ComponentClass = resolvedTestComponent.selector ? (0, create_container_1.createContainer)(template || this._createTemplateString(resolvedTestComponent, finalOptions.bind), finalOptions.bind, [this._setup.testComponentOrService], resolvedTestComponent.standalone) : this._setup.testComponentOrService; // Components may have their own providers, If the test component does, // we will mock them out here.. if (resolvedTestComponent.providers && resolvedTestComponent.providers.length) { testing_1.TestBed.overrideComponent(this._setup.testComponentOrService, { set: { providers: resolvedTestComponent.providers.map(p => (0, mock_provider_1.mockProvider)(p, this._setup)), }, }); } if (reflect_1.reflect.isStandalone(this._setup.testComponentOrService)) { const componentImports = reflect_1.reflect.resolveComponent(this._setup.testComponentOrService).imports; // Standalone components may have their own imports if (componentImports === null || componentImports === void 0 ? void 0 : componentImports.length) { testing_1.TestBed.overrideComponent(this._setup.testComponentOrService, { set: { imports: componentImports.flat().map(m => (0, ng_mock_1.ngMock)(m, this._setup)), }, }); } testing_1.TestBed.configureTestingModule({ imports: [this._setup.testComponentOrService, (0, create_test_module_1.createTestModule)(this._setup, [])], }); } else { testing_1.TestBed.configureTestingModule({ imports: [(0, create_test_module_1.createTestModule)(this._setup, [this._setup.testComponentOrService, ComponentClass])], }); } yield testing_1.TestBed.compileComponents(); const fixture = testing_1.TestBed.createComponent(ComponentClass); const instance = this._getInstance(fixture); this._spyOnOutputs(instance); yield this._runComponentLifecycle(fixture, finalOptions); const element = this._getElement(fixture); return new rendering_1.Rendering(fixture, element, instance, finalOptions.bind, this._setup); }); } _spyOnOutputs(instance) { const outputs = reflect_1.reflect.getInputsAndOutputs(this._setup.testComponentOrService).outputs; outputs.forEach(({ propertyName }) => { const value = instance[propertyName]; if (value && (value instanceof core_1.EventEmitter || value instanceof core_1.OutputEmitterRef)) { test_framework_1.testFramework.spyOn(value, 'emit'); } }); } _verifyComponentBindings(directive, bindings) { if (!directive.selector && Object.keys(bindings).length) { throw new InvalidBindOnEntryComponentError(this._setup.testComponentOrService); } const inputPropertyNames = reflect_1.reflect .getInputsAndOutputs(this._setup.testComponentOrService) .inputs.map(i => i.propertyName); Object.keys(bindings).forEach(k => { if (!inputPropertyNames.includes(k)) { throw new InvalidInputBindError(inputPropertyNames, k); } }); } _runComponentLifecycle(fixture, options) { return __awaiter(this, void 0, void 0, function* () { if (options.whenStable) { if (options.detectChanges) { fixture.detectChanges(); } yield fixture.whenStable(); } if (options.detectChanges) { fixture.detectChanges(); } }); } _getElement(fixture) { return fixture.componentInstance instanceof this._setup.testComponentOrService ? fixture.debugElement : fixture.debugElement.query(platform_browser_1.By.directive(this._setup.testComponentOrService)) || fixture.debugElement.children[0]; } _getInstance(fixture) { const element = this._getElement(fixture); const instance = element ? element.injector.get(this._setup.testComponentOrService) : this._getStructuralDirectiveInstance(fixture); return instance; } _getStructuralDirectiveInstance(fixture) { const [debugNode] = fixture.debugElement.queryAllNodes(platform_browser_1.By.directive(this._setup.testComponentOrService)); if (debugNode) { return debugNode.injector.get(this._setup.testComponentOrService); } throw new MissingTestComponentError(this._setup.testComponentOrService); } } exports.Renderer = Renderer; //# sourceMappingURL=renderer.js.map