This commit is contained in:
Niels Lyngsø
2023-11-08 20:19:06 +01:00
parent 400470a14a
commit 23201227a5
5 changed files with 200 additions and 71 deletions

View File

@@ -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<UmbTestApiController> {
type: 'test-type'
}
describe('UmbExtensionApiController', () => {
describe('Manifest without conditions', () => {
let hostElement: UmbControllerHostElement;
let extensionRegistry: UmbExtensionRegistry<TestManifest>;
let manifest: TestManifest;
beforeEach(async () => {
hostElement = await fixture(html`<umb-test-controller-host></umb-test-controller-host>`);
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<TestManifest>(
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<TestManifest>(
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<ManifestSection>;
let manifest: ManifestSection;
beforeEach(async () => {
hostElement = await fixture(html`<umb-test-controller-host></umb-test-controller-host>`);
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<TestManifest>(
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.
}
}
);
});
});
});

View File

@@ -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<ApiType>) = (ManifestWithDynamicConditions & ManifestApi<any>),
ControllerType extends UmbExtensionApiController<ManifestType> = any,
ApiType extends ExtensionApi = ExtensionApi,
ManifestType extends (ManifestWithDynamicConditions & ManifestApi) = (ManifestWithDynamicConditions & ManifestApi),
ControllerType extends UmbExtensionApiController<ManifestType, any> = any,
ExtensionApiInterface extends ExtensionApi = ManifestType extends ManifestApi ? NonNullable<ManifestType['API_TYPE']> : ExtensionApi
> extends UmbBaseExtensionController<ManifestType, ControllerType> {
_api?: ApiType;
#api?: ExtensionApiInterface;
#constructorArguments?: Array<unknown>;
/**
* 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<any>}
* @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<unknown>;
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<ManifestCondition>,
alias: string,
constructorArguments: Array<unknown> | 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<ApiType>(manifest, this.#constructorArguments);
const newApi = await createExtensionApi<ExtensionApiInterface>(manifest as unknown as ManifestApi<ExtensionApiInterface>, 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;
}
}
}

View File

@@ -7,10 +7,10 @@ import { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
export class UmbExtensionElementController<
ManifestType extends ManifestWithDynamicConditions = ManifestWithDynamicConditions,
ControllerType extends UmbExtensionElementController<ManifestType> = any
ControllerType extends UmbExtensionElementController<ManifestType, any> = any
> extends UmbBaseExtensionController<ManifestType, ControllerType> {
_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;
}
}
}

View File

@@ -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> {
ManifestType extends ManifestWithDynamicConditions = ManifestWithDynamicConditions,
ControllerType extends UmbBaseExtensionController<ManifestType, any> = any
> extends UmbBaseExtensionController<ManifestType, ControllerType> {
constructor(
host: UmbControllerHost,
extensionRegistry: UmbExtensionRegistry<ManifestCondition>,
alias: string,
onPermissionChanged: (isPermitted: boolean, controller: UmbBaseExtensionController<ManifestType>) => void
onPermissionChanged: (isPermitted: boolean, controller: ControllerType) => void
) {
super(host, extensionRegistry, alias, onPermissionChanged);
this._init();

View File

@@ -42,27 +42,19 @@ export class UmbExtensionsApiController<
}
*/
#constructorArgs?: Array<unknown>;
public get constructorArguments() {
return this.#constructorArgs;
}
public set constructorArguments(args: Array<unknown> | undefined) {
this.#constructorArgs = args;
this._extensions.forEach((controller) => {
controller.constructorArguments = args;
});
}
#constructorArgs: Array<unknown> | undefined;
constructor(
host: UmbControllerHost,
extensionRegistry: UmbExtensionRegistry<ManifestTypes>,
type: ManifestTypeName | Array<ManifestTypeName>,
constructorArguments: Array<unknown> | undefined,
filter: undefined | null | ((manifest: ManifestTypeAsApi) => boolean),
onChange: (permittedManifests: Array<MyPermittedControllerType>, 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;
}
}