From 23201227a57093b47b76ac8fe5c05e8370456bd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Wed, 8 Nov 2023 20:19:06 +0100 Subject: [PATCH] tests --- .../extension-api-controller.test.ts | 158 ++++++++++++++++++ .../controller/extension-api-controller.ts | 53 ++---- .../extension-element-controller.ts | 36 ++-- .../extension-manifest-controller.ts | 7 +- .../controller/extensions-api-controller.ts | 17 +- 5 files changed, 200 insertions(+), 71 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/extension-api-controller.test.ts diff --git a/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/extension-api-controller.test.ts b/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/extension-api-controller.test.ts new file mode 100644 index 0000000000..d1f542eb66 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/extension-api-controller.test.ts @@ -0,0 +1,158 @@ +import { expect, fixture } from '@open-wc/testing'; +import { UmbExtensionRegistry } from '../registry/extension.registry.js'; +import { ManifestApi, ManifestWithDynamicConditions } from '../types.js'; +import { UmbExtensionApiController } from './index.js'; +import { UmbBaseController, UmbControllerHost, UmbControllerHostElement, UmbControllerHostElementMixin } from '@umbraco-cms/backoffice/controller-api'; +import { customElement, html } from '@umbraco-cms/backoffice/external/lit'; +import { type ManifestSection, UmbSwitchCondition } from '@umbraco-cms/backoffice/extension-registry'; + +@customElement('umb-test-controller-host') +export class UmbTestControllerHostElement extends UmbControllerHostElementMixin(HTMLElement) {} + + +export class UmbTestApiController extends UmbBaseController { + + public i_am_test_api_controller = true; + + constructor(host:UmbControllerHost) { + super(host); + } + +} + +interface TestManifest extends ManifestWithDynamicConditions, ManifestApi { + type: 'test-type' +} + +describe('UmbExtensionApiController', () => { + describe('Manifest without conditions', () => { + let hostElement: UmbControllerHostElement; + let extensionRegistry: UmbExtensionRegistry; + let manifest: TestManifest; + + beforeEach(async () => { + hostElement = await fixture(html``); + extensionRegistry = new UmbExtensionRegistry(); + manifest = { + type: 'test-type', + name: 'test-type-1', + alias: 'Umb.Test.Type-1', + api: UmbTestApiController + }; + + extensionRegistry.register(manifest); + }); + + it('permits when there is no conditions', (done) => { + let called = false; + const extensionController = new UmbExtensionApiController( + hostElement, + extensionRegistry, + 'Umb.Test.Type-1', + [hostElement], + (permitted) => { + if (called === false) { + called = true; + expect(permitted).to.be.true; + if (permitted) { + expect(extensionController?.manifest?.alias).to.eq('Umb.Test.Type-1'); + expect(extensionController.api?.i_am_test_api_controller).to.be.true; + done(); + extensionController.destroy(); + } + } + } + ); + /* + TODO: Consider if builder pattern would be a more nice way to setup this: + const extensionController = new UmbExtensionApiController( + hostElement, + extensionRegistry, + 'Umb.Test.Type-1' + ) + .withConstructorArguments([hostElement]) + .onPermitted((permitted) => { + if (called === false) { + called = true; + expect(permitted).to.be.true; + if (permitted) { + expect(extensionController?.manifest?.alias).to.eq('Umb.Test.Type-1'); + expect(extensionController.api?.i_am_test_api_controller).to.be.true; + done(); + extensionController.destroy(); + } + } + ).observe(); + */ + }); + }); + + describe('Manifest with multiple conditions that changes over time', () => { + let hostElement: UmbControllerHostElement; + let extensionRegistry: UmbExtensionRegistry; + let manifest: ManifestSection; + + beforeEach(async () => { + hostElement = await fixture(html``); + extensionRegistry = new UmbExtensionRegistry(); + + manifest = { + type: 'test-type', + name: 'test-type-1', + alias: 'Umb.Test.Type-1', + api: UmbTestApiController, + conditions: [ + { + alias: 'Umb.Test.Condition.Delay', + frequency: '100', + }, + { + alias: 'Umb.Test.Condition.Delay', + frequency: '200', + }, + ], + } as any; + + // A ASCII timeline for the conditions, when allowed and then not allowed: + // Condition 0ms 100ms 200ms 300ms 400ms 500ms + // First condition: - + - + - + + // Second condition: - - + + - - + // Sum: - - - + - - + + const conditionManifest = { + type: 'condition', + name: 'test-condition-delay', + alias: 'Umb.Test.Condition.Delay', + api: UmbSwitchCondition, + }; + + extensionRegistry.register(manifest); + extensionRegistry.register(conditionManifest); + }); + + it('does change permission as conditions change', (done) => { + let count = 0; + const extensionController = new UmbExtensionApiController( + hostElement, + extensionRegistry, + 'Umb.Test.Type-1', + [hostElement], + async () => { + count++; + // We want the controller callback to first fire when conditions are initialized. + expect(extensionController.manifest?.conditions?.length).to.be.equal(2); + expect(extensionController?.manifest?.alias).to.eq('Umb.Test.Type-1'); + if (count === 1) { + expect(extensionController?.permitted).to.be.true; + expect(extensionController.api?.i_am_test_api_controller).to.be.true; + } else if (count === 2) { + expect(extensionController?.permitted).to.be.false; + expect(extensionController.api).to.be.undefined; + done(); + extensionController.destroy(); // need to destroy the controller. + } + } + ); + }); + }); +}); diff --git a/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/extension-api-controller.ts b/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/extension-api-controller.ts index 826d231f95..d96f4e4f0b 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/extension-api-controller.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/extension-api-controller.ts @@ -6,12 +6,13 @@ import { UmbBaseExtensionController } from './base-extension-controller.js'; import { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; export class UmbExtensionApiController< - ManifestType extends (ManifestWithDynamicConditions & ManifestApi) = (ManifestWithDynamicConditions & ManifestApi), - ControllerType extends UmbExtensionApiController = any, - ApiType extends ExtensionApi = ExtensionApi, + ManifestType extends (ManifestWithDynamicConditions & ManifestApi) = (ManifestWithDynamicConditions & ManifestApi), + ControllerType extends UmbExtensionApiController = any, + ExtensionApiInterface extends ExtensionApi = ManifestType extends ManifestApi ? NonNullable : ExtensionApi > extends UmbBaseExtensionController { - _api?: ApiType; + #api?: ExtensionApiInterface; + #constructorArguments?: Array; /** * The api that is created for this extension. @@ -19,33 +20,9 @@ export class UmbExtensionApiController< * @type {(class | undefined)} */ public get api() { - return this._api; + return this.#api; } - /** - * The arguments passed to the class constructor. - * @type {Array} - * @memberof UmbApiExtensionController - * @example - * ```ts - * const controller = new UmbApiExtensionController(host, extensionRegistry, alias, onPermissionChanged); - * controller.props = { foo: 'bar' }; - * ``` - * Is equivalent to: - * ```ts - * controller.component.foo = 'bar'; - * ``` - */ - #constructorArguments?: Array; - get constructorArguments() { - return this.#constructorArguments; - } - set constructorArguments(newVal) { - this.#constructorArguments = newVal; - if(this._api) { - console.warn('Constructor Arguments was set after api class has been constructed') - } - } /** * The props that are passed to the class. @@ -77,9 +54,11 @@ export class UmbExtensionApiController< host: UmbControllerHost, extensionRegistry: UmbExtensionRegistry, alias: string, + constructorArguments: Array | undefined, onPermissionChanged: (isPermitted: boolean, controller: ControllerType) => void ) { super(host, extensionRegistry, alias, onPermissionChanged); + this.#constructorArguments = constructorArguments; this._init(); } @@ -98,18 +77,18 @@ export class UmbExtensionApiController< const manifest = this.manifest!; // In this case we are sure its not undefined. if (isManifestApiType(manifest)) { - const newApi = await createExtensionApi(manifest, this.#constructorArguments); + const newApi = await createExtensionApi(manifest as unknown as ManifestApi, this.#constructorArguments); if (!this._positive) { // We are not positive anymore, so we will back out of this creation. return false; } - this._api = newApi; + this.#api = newApi; } else { - this._api = undefined; + this.#api = undefined; console.warn('Manifest did not provide any useful data for a api class to construct.') } - if (this._api) { + if (this.#api) { //this.#assignProperties(); return true; // we will confirm we have a component and are still good to go. } @@ -119,11 +98,11 @@ export class UmbExtensionApiController< protected async _conditionsAreBad() { // Destroy the element: - if (this._api) { - if ('destroy' in this._api) { - (this._api as unknown as { destroy: () => void }).destroy(); + if (this.#api) { + if ('destroy' in this.#api) { + (this.#api as unknown as { destroy: () => void }).destroy(); } - this._api = undefined; + this.#api = undefined; } } } diff --git a/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/extension-element-controller.ts b/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/extension-element-controller.ts index 82a91934c0..37feea8270 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/extension-element-controller.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/extension-element-controller.ts @@ -7,10 +7,10 @@ import { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; export class UmbExtensionElementController< ManifestType extends ManifestWithDynamicConditions = ManifestWithDynamicConditions, - ControllerType extends UmbExtensionElementController = any + ControllerType extends UmbExtensionElementController = any > extends UmbBaseExtensionController { - _defaultElement?: string; - _component?: HTMLElement; + #defaultElement?: string; + #component?: HTMLElement; /** * The component that is created for this extension. @@ -18,7 +18,7 @@ export class UmbExtensionElementController< * @type {(HTMLElement | undefined)} */ public get component() { - return this._component; + return this.#component; } /** @@ -53,16 +53,16 @@ export class UmbExtensionElementController< defaultElement?: string ) { super(host, extensionRegistry, alias, onPermissionChanged); - this._defaultElement = defaultElement; + this.#defaultElement = defaultElement; this._init(); } #assignProperties = () => { - if (!this._component || !this.#properties) return; + if (!this.#component || !this.#properties) return; // TODO: we could optimize this so we only re-set the updated props. Object.keys(this.#properties).forEach((key) => { - (this._component as any)[key] = this.#properties![key]; + (this.#component as any)[key] = this.#properties![key]; }); }; @@ -70,22 +70,22 @@ export class UmbExtensionElementController< const manifest = this.manifest!; // In this case we are sure its not undefined. if (isManifestElementableType(manifest)) { - const newComponent = await createExtensionElement(manifest, this._defaultElement); + const newComponent = await createExtensionElement(manifest, this.#defaultElement); if (!this._positive) { // We are not positive anymore, so we will back out of this creation. return false; } - this._component = newComponent; + this.#component = newComponent; - } else if (this._defaultElement) { - this._component = document.createElement(this._defaultElement); + } else if (this.#defaultElement) { + this.#component = document.createElement(this.#defaultElement); } else { - this._component = undefined; + this.#component = undefined; console.warn('Manifest did not provide any useful data for a web component to be created.') } - if (this._component) { + if (this.#component) { this.#assignProperties(); - (this._component as any).manifest = manifest; + (this.#component as any).manifest = manifest; return true; // we will confirm we have a component and are still good to go. } @@ -94,11 +94,11 @@ export class UmbExtensionElementController< protected async _conditionsAreBad() { // Destroy the element: - if (this._component) { - if ('destroy' in this._component) { - (this._component as unknown as { destroy: () => void }).destroy(); + if (this.#component) { + if ('destroy' in this.#component) { + (this.#component as unknown as { destroy: () => void }).destroy(); } - this._component = undefined; + this.#component = undefined; } } } diff --git a/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/extension-manifest-controller.ts b/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/extension-manifest-controller.ts index 7ee6f5c520..09242945c1 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/extension-manifest-controller.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/extension-manifest-controller.ts @@ -4,13 +4,14 @@ import { UmbBaseExtensionController } from './base-extension-controller.js'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; export class UmbExtensionManifestController< - ManifestType extends ManifestWithDynamicConditions = ManifestWithDynamicConditions -> extends UmbBaseExtensionController { + ManifestType extends ManifestWithDynamicConditions = ManifestWithDynamicConditions, + ControllerType extends UmbBaseExtensionController = any +> extends UmbBaseExtensionController { constructor( host: UmbControllerHost, extensionRegistry: UmbExtensionRegistry, alias: string, - onPermissionChanged: (isPermitted: boolean, controller: UmbBaseExtensionController) => void + onPermissionChanged: (isPermitted: boolean, controller: ControllerType) => void ) { super(host, extensionRegistry, alias, onPermissionChanged); this._init(); diff --git a/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/extensions-api-controller.ts b/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/extensions-api-controller.ts index 0cf75ce477..6ef7c52bc8 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/extensions-api-controller.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/extensions-api-controller.ts @@ -42,27 +42,19 @@ export class UmbExtensionsApiController< } */ - #constructorArgs?: Array; - - public get constructorArguments() { - return this.#constructorArgs; - } - public set constructorArguments(args: Array | undefined) { - this.#constructorArgs = args; - this._extensions.forEach((controller) => { - controller.constructorArguments = args; - }); - } + #constructorArgs: Array | undefined; constructor( host: UmbControllerHost, extensionRegistry: UmbExtensionRegistry, type: ManifestTypeName | Array, + constructorArguments: Array | undefined, filter: undefined | null | ((manifest: ManifestTypeAsApi) => boolean), onChange: (permittedManifests: Array, controller: MyPermittedControllerType) => void ) { super(host, extensionRegistry, type, filter, onChange); this.#extensionRegistry = extensionRegistry; + this.#constructorArgs = constructorArguments; this._init(); } @@ -71,11 +63,10 @@ export class UmbExtensionsApiController< this, this.#extensionRegistry, manifest.alias, + this.#constructorArgs, this._extensionChanged ) as ControllerType; - extController.constructorArguments = this.#constructorArgs; - return extController; } }