shallow-render
Version:
Shallow rendering test utility for Angular
167 lines • 9.45 kB
JavaScript
;
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