context-provider-controller prevents replacement (#519)

* context-provider-controller prevents replacement

* correct import order

* do not re-provide self providing context

* define and test controller-host-test element
This commit is contained in:
Niels Lyngsø
2023-02-15 09:47:00 +01:00
committed by GitHub
parent 81654c95dc
commit 5df2f3a4f1
12 changed files with 177 additions and 21 deletions

View File

@@ -0,0 +1,64 @@
import { expect, fixture, html } from '@open-wc/testing';
import { UmbContextConsumer } from '../consume/context-consumer';
import { UmbContextProviderController } from './context-provider.controller';
import { UmbControllerHostTestElement, UmbLitElement } from '@umbraco-cms/element';
class MyClass {
prop = 'value from provider';
}
describe('UmbContextProviderController', () => {
let instance: MyClass;
let provider: UmbContextProviderController;
let element: UmbLitElement;
beforeEach(async () => {
element = await fixture(html`<umb-controller-host-test></umb-controller-host-test>`);
instance = new MyClass();
provider = new UmbContextProviderController(element, 'my-test-context', instance);
});
it('is defined with its own instance', () => {
expect(element).to.be.instanceOf(UmbControllerHostTestElement);
});
describe('Public API', () => {
describe('properties', () => {
it('has a unique property', () => {
expect(provider).to.have.property('unique');
});
it('has a unique property, is equal to the unique', () => {
expect(provider.unique).to.eq('my-test-context');
});
});
describe('methods', () => {
it('has an providerInstance method', () => {
expect(provider).to.have.property('providerInstance').that.is.a('function');
});
});
});
it('works with UmbContextConsumer', (done) => {
const localConsumer = new UmbContextConsumer(element, 'my-test-context', (_instance: MyClass) => {
expect(_instance.prop).to.eq('value from provider');
done();
localConsumer.hostDisconnected();
});
localConsumer.hostConnected();
});
it('Fails providing the same instance with another controller using the same unique', () => {
let secondCtrl;
// Tests that the creations throws:
expect(() => {
secondCtrl = new UmbContextProviderController(element, 'my-test-context', instance);
}).to.throw();
// Still has the initial controller:
expect(element.hasController(provider)).to.be.true;
// The secondCtrl was never set as a result of the creation failing:
expect(secondCtrl).to.be.undefined;
});
});

View File

@@ -13,10 +13,19 @@ export class UmbContextProviderController<T = unknown>
constructor(host: UmbControllerHostInterface, contextAlias: string | UmbContextToken<T>, instance: T) {
super(host, contextAlias, instance);
// TODO: What if this API is already provided with this alias? maybe handle this in the controller:
// TODO: Remove/destroy existing controller of same alias.
host.addController(this);
// If this API is already provided with this alias? Then we do not want to register this controller:
const existingControllers = host.getControllers((x) => x.unique === this.unique);
if (
existingControllers.length > 0 &&
(existingControllers[0] as UmbContextProviderController).providerInstance?.() === instance
) {
// Back out, this instance is already provided, by another controller.
throw new Error(
`Context API: The context of '${this.unique}' is already provided with the same API by another Context Provider Controller.`
);
} else {
host.addController(this);
}
}
public destroy() {

View File

@@ -8,10 +8,12 @@ class MyClass {
}
describe('UmbContextProvider', () => {
let instance: MyClass;
let provider: UmbContextProvider;
beforeEach(() => {
provider = new UmbContextProvider(document.body, 'my-test-context', new MyClass());
instance = new MyClass();
provider = new UmbContextProvider(document.body, 'my-test-context', instance);
provider.hostConnected();
});

View File

@@ -12,6 +12,15 @@ export class UmbContextProvider<HostType extends EventTarget = EventTarget> {
protected _contextAlias: string;
#instance: unknown;
/**
* Method to enable comparing the context providers by the instance they provide.
* Note this method should have a unique name for the provider controller, for it not to be confused with a consumer.
* @returns {*}
*/
public providerInstance() {
return this.#instance;
}
/**
* Creates an instance of UmbContextProvider.
* @param {EventTarget} host
@@ -54,9 +63,8 @@ export class UmbContextProvider<HostType extends EventTarget = EventTarget> {
event.callback(this.#instance);
};
destroy(): void {
// I want to make sure to call this, but for now it was too overwhelming to require the destroy method on context instances.
(this.#instance as any).destroy?.();
};
}
}