Skip to content
This repository was archived by the owner on Nov 6, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
9282f57
visual corrections
nielslyngsoe Nov 5, 2024
9a347bd
validation translators ctrl alias
nielslyngsoe Nov 5, 2024
0683946
remember provider host for validation context
nielslyngsoe Nov 5, 2024
24fc8ac
remove event listener
nielslyngsoe Nov 5, 2024
39acc15
can be destroyed multiple times
nielslyngsoe Nov 5, 2024
7788661
error to help general error discovery
nielslyngsoe Nov 5, 2024
2b07810
update to life cycle of extension initializers
nielslyngsoe Nov 5, 2024
099483d
update style
nielslyngsoe Nov 5, 2024
cc0dfd2
Merge branch 'main' into v15/feature/grid-inline-editing-fixes
nielslyngsoe Nov 5, 2024
54f6b1b
temp removal of umbDestroyOnDisconnect
nielslyngsoe Nov 5, 2024
b42ab48
remove unused import
nielslyngsoe Nov 5, 2024
74119ea
fix infinite layout update
nielslyngsoe Nov 5, 2024
03269e3
Merge branch 'main' into v15/feature/grid-inline-editing-fixes
nielslyngsoe Nov 6, 2024
b74ad48
make host optionally be a method
nielslyngsoe Nov 6, 2024
a828eb9
update JSDocs
nielslyngsoe Nov 6, 2024
24c9902
give block workspace origin data to work from
nielslyngsoe Nov 6, 2024
15242d0
update destroy method
nielslyngsoe Nov 6, 2024
fb460d5
Merge branch 'main' into v15/feature/grid-inline-editing-fixes
nielslyngsoe Nov 6, 2024
558e1fa
comment on risky business
nielslyngsoe Nov 6, 2024
a93d3cf
Merge branch 'main' into v15/feature/grid-inline-editing-fixes
nielslyngsoe Nov 7, 2024
fd17610
fix const name
nielslyngsoe Nov 7, 2024
85fe0c6
Merge branch 'main' into v15/feature/grid-inline-editing-fixes
nielslyngsoe Nov 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export class UmbContextConsumerController<BaseType = unknown, ResultType extends
contextAlias: string | UmbContextToken<BaseType, ResultType>,
callback?: UmbContextCallback<ResultType>,
) {
super(host.getHostElement(), contextAlias, callback);
super(() => host.getHostElement(), contextAlias, callback);
this.#host = host;
host.addUmbController(this);
}
Expand Down
45 changes: 43 additions & 2 deletions src/libs/context-api/consume/context-consumer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { expect, oneEvent } from '@open-wc/testing';

const testContextAlias = 'my-test-context';
const testContextAliasAndApiAlias = 'my-test-context#testApi';
const testContextAliasAndNotExstingApiAlias = 'my-test-context#notExistingTestApi';
const testContextAliasAndNotExistingApiAlias = 'my-test-context#notExistingTestApi';

class UmbTestContextConsumerClass {
public prop: string = 'value from provider';
Expand Down Expand Up @@ -74,6 +74,47 @@ describe('UmbContextConsumer', () => {
localConsumer.hostConnected();
});

it('works with host as a method', (done) => {
const provider = new UmbContextProvider(document.body, testContextAlias, new UmbTestContextConsumerClass());
provider.hostConnected();

const element = document.createElement('div');
document.body.appendChild(element);

const localConsumer = new UmbContextConsumer(
() => element,
testContextAlias,
(_instance: UmbTestContextConsumerClass | undefined) => {
if (_instance) {
expect(_instance.prop).to.eq('value from provider');
done();
localConsumer.hostDisconnected();
provider.hostDisconnected();
}
},
);
localConsumer.hostConnected();
});

it('works with host method returning undefined', async () => {
const element = undefined;

const localConsumer = new UmbContextConsumer(
() => element,
testContextAlias,
(_instance: UmbTestContextConsumerClass | undefined) => {
if (_instance) {
expect.fail('Callback should not be called when never permitted');
}
},
);
localConsumer.hostConnected();

await Promise.resolve();

localConsumer.hostDisconnected();
});

/*
Unprovided feature is out commented currently. I'm not sure there is a use case. So lets leave the code around until we know for sure.
it('acts to Context API disconnected', (done) => {
Expand Down Expand Up @@ -139,7 +180,7 @@ describe('UmbContextConsumer', () => {
const element = document.createElement('div');
document.body.appendChild(element);

const localConsumer = new UmbContextConsumer(element, testContextAliasAndNotExstingApiAlias, () => {
const localConsumer = new UmbContextConsumer(element, testContextAliasAndNotExistingApiAlias, () => {
expect(false).to.be.true;
});
localConsumer.hostConnected();
Expand Down
20 changes: 15 additions & 5 deletions src/libs/context-api/consume/context-consumer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ import { isUmbContextProvideEventType, UMB_CONTEXT_PROVIDE_EVENT_TYPE } from '..
import type { UmbContextCallback } from './context-request.event.js';
import { UmbContextRequestEventImplementation } from './context-request.event.js';

type HostElementMethod = () => Element | undefined;

/**
* @class UmbContextConsumer
*/
export class UmbContextConsumer<BaseType = unknown, ResultType extends BaseType = BaseType> {
protected _host: Element;
protected _retrieveHost: HostElementMethod;

#skipHost?: boolean;
#stopAtContextMatch = true;
Expand All @@ -33,11 +35,15 @@ export class UmbContextConsumer<BaseType = unknown, ResultType extends BaseType
* @memberof UmbContextConsumer
*/
constructor(
host: Element,
host: Element | HostElementMethod,
contextIdentifier: string | UmbContextToken<BaseType, ResultType>,
callback?: UmbContextCallback<ResultType>,
) {
this._host = host;
if (typeof host === 'function') {
this._retrieveHost = host;
} else {
this._retrieveHost = () => host;
}
const idSplit = contextIdentifier.toString().split('#');
this.#contextAlias = idSplit[0];
this.#apiAlias = idSplit[1] ?? 'default';
Expand Down Expand Up @@ -72,6 +78,10 @@ export class UmbContextConsumer<BaseType = unknown, ResultType extends BaseType
if (this.#instance === instance) {
return true;
}

if (instance === undefined) {
throw new Error('Not allowed to set context api instance to undefined.');
}
if (this.#discriminator) {
// Notice if discriminator returns false, we do not want to setInstance.
if (this.#discriminator(instance)) {
Expand Down Expand Up @@ -126,7 +136,7 @@ export class UmbContextConsumer<BaseType = unknown, ResultType extends BaseType
this._onResponse,
this.#stopAtContextMatch,
);
(this.#skipHost ? this._host.parentNode : this._host)?.dispatchEvent(event);
(this.#skipHost ? this._retrieveHost()?.parentNode : this._retrieveHost())?.dispatchEvent(event);
}

public hostConnected(): void {
Expand Down Expand Up @@ -172,7 +182,7 @@ export class UmbContextConsumer<BaseType = unknown, ResultType extends BaseType

public destroy(): void {
this.hostDisconnected();
this._host = undefined as any;
this._retrieveHost = undefined as any;
this.#callback = undefined;
this.#promise = undefined;
this.#promiseResolver = undefined;
Expand Down
6 changes: 4 additions & 2 deletions src/libs/context-api/provide/context-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export class UmbContextProvider<BaseType = unknown, ResultType extends BaseType
* @memberof UmbContextProvider
*/
public hostConnected(): void {
//this.hostElement.addEventListener(UMB_CONTENT_REQUEST_EVENT_TYPE, this.#handleContextRequest);
//this.hostElement.addEventListener(UMB_CONTEXT_REQUEST_EVENT_TYPE, this.#handleContextRequest);
this.#eventTarget.dispatchEvent(new UmbContextProvideEventImplementation(this.#contextAlias));

// Listen to our debug event 'umb:debug-contexts'
Expand All @@ -79,7 +79,7 @@ export class UmbContextProvider<BaseType = unknown, ResultType extends BaseType
* @memberof UmbContextProvider
*/
public hostDisconnected(): void {
//this.hostElement.removeEventListener(UMB_CONTENT_REQUEST_EVENT_TYPE, this.#handleContextRequest);
//this.hostElement.removeEventListener(UMB_CONTEXT_REQUEST_EVENT_TYPE, this.#handleContextRequest);
// Out-commented for now, but kept if we like to reintroduce this:
//window.dispatchEvent(new UmbContextUnprovidedEventImplementation(this._contextAlias, this.#instance));

Expand All @@ -103,6 +103,8 @@ export class UmbContextProvider<BaseType = unknown, ResultType extends BaseType

destroy(): void {
this.hostDisconnected();
// Note we are not removing the event listener in the hostDisconnected, therefor we do it here [NL].
this.#eventTarget?.removeEventListener(UMB_CONTEXT_REQUEST_EVENT_TYPE, this.#handleContextRequest);
// We want to call a destroy method on the instance, if it has one.
(this.#instance as any)?.destroy?.();
this.#instance = undefined;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,12 @@ class UmbTestConditionAlwaysInvalid extends UmbControllerBase implements UmbExte

describe('UmbBaseExtensionController', () => {
describe('Manifest without conditions', () => {
//let hostElement: UmbControllerHostElement;
let hostElement: UmbControllerHostElement;
let extensionRegistry: UmbExtensionRegistry<ManifestWithDynamicConditions>;
let manifest: ManifestWithDynamicConditions;

beforeEach(async () => {
//hostElement = await fixture(html`<umb-test-controller-host></umb-test-controller-host>`);
hostElement = await fixture(html`<umb-test-controller-host></umb-test-controller-host>`);
extensionRegistry = new UmbExtensionRegistry();
manifest = {
type: 'section',
Expand All @@ -74,7 +74,7 @@ describe('UmbBaseExtensionController', () => {

extensionRegistry.register(manifest);
});
/*

it('permits when there is no conditions', (done) => {
const extensionController = new UmbTestExtensionController(
hostElement,
Expand All @@ -92,16 +92,15 @@ describe('UmbBaseExtensionController', () => {
},
);
});
*/
});

describe('Manifest with empty conditions', () => {
//let hostElement: UmbControllerHostElement;
let hostElement: UmbControllerHostElement;
let extensionRegistry: UmbExtensionRegistry<ManifestWithDynamicConditions>;
let manifest: ManifestWithDynamicConditions;

beforeEach(async () => {
//hostElement = await fixture(html`<umb-test-controller-host></umb-test-controller-host>`);
hostElement = await fixture(html`<umb-test-controller-host></umb-test-controller-host>`);
extensionRegistry = new UmbExtensionRegistry();
manifest = {
type: 'section',
Expand All @@ -113,7 +112,6 @@ describe('UmbBaseExtensionController', () => {
extensionRegistry.register(manifest);
});

/*
it('permits when there is empty conditions', (done) => {
const extensionController = new UmbTestExtensionController(
hostElement,
Expand All @@ -124,15 +122,14 @@ describe('UmbBaseExtensionController', () => {
if (extensionController.permitted) {
expect(extensionController?.manifest?.alias).to.eq('Umb.Test.Section.1');

// Also verifying that the promise gets resolved.
// Also verifying that the promise gets resolved. [NL]
extensionController.asPromise().then(() => {
done();
});
}
},
);
});
*/
});

describe('Manifest with valid conditions', () => {
Expand Down Expand Up @@ -225,14 +222,14 @@ describe('UmbBaseExtensionController', () => {
if (isPermitted) {
count++;
if (count === 1) {
// First time render, there is no conditions.
// First time render, there is no conditions. [NL]
expect(extensionController.manifest?.weight).to.be.equal(2);
expect(extensionController.manifest?.conditions?.length).to.be.equal(1);
} else if (count === 2) {
// Second time render, there is conditions and weight is 22.
// Second time render, there is conditions and weight is 22. [NL]
expect(extensionController.manifest?.weight).to.be.equal(22);
expect(extensionController.manifest?.conditions?.length).to.be.equal(1);
// Check that the promise has been resolved for the first render to ensure timing is right.
// Check that the promise has been resolved for the first render to ensure timing is right. [NL]
expect(initialPromiseResolved).to.be.true;
done();
extensionController.destroy();
Expand Down Expand Up @@ -270,14 +267,14 @@ describe('UmbBaseExtensionController', () => {
if (isPermitted) {
count++;
if (count === 1) {
// First time render, there is no conditions.
// First time render, there is no conditions. [NL]
expect(extensionController.manifest?.weight).to.be.equal(3);
expect(extensionController.manifest?.conditions?.length).to.be.equal(0);
} else if (count === 2) {
// Second time render, there is conditions and weight is 33.
// Second time render, there is conditions and weight is 33. [NL]
expect(extensionController.manifest?.weight).to.be.equal(33);
expect(extensionController.manifest?.conditions?.length).to.be.equal(0);
// Check that the promise has been resolved for the first render to ensure timing is right.
// Check that the promise has been resolved for the first render to ensure timing is right. [NL]
expect(initialPromiseResolved).to.be.true;
done();
extensionController.destroy();
Expand Down Expand Up @@ -315,14 +312,14 @@ describe('UmbBaseExtensionController', () => {
if (isPermitted) {
count++;
if (count === 1) {
// First time render, there is no conditions.
// First time render, there is no conditions. [NL]
expect(extensionController.manifest?.weight).to.be.equal(4);
expect(extensionController.manifest?.conditions?.length).to.be.equal(0);
} else if (count === 2) {
// Second time render, there is conditions and weight is 33.
// Second time render, there is conditions and weight is updated. [NL]
expect(extensionController.manifest?.weight).to.be.equal(44);
expect(extensionController.manifest?.conditions?.length).to.be.equal(1);
// Check that the promise has been resolved for the first render to ensure timing is right.
// Check that the promise has been resolved for the first render to ensure timing is right. [NL]
expect(initialPromiseResolved).to.be.true;
done();
extensionController.destroy();
Expand Down Expand Up @@ -370,14 +367,14 @@ describe('UmbBaseExtensionController', () => {
if (isPermitted) {
count++;
if (count === 1) {
// First time render, there is no conditions.
// First time render, there is no conditions. [NL]
expect(extensionController.manifest?.weight).to.be.undefined;
expect(extensionController.manifest?.conditions?.length).to.be.equal(0);
} else if (count === 2) {
// Second time render, there is a matching kind and then weight is 123.
// Second time render, there is a matching kind and then weight is 123. [NL]
expect(extensionController.manifest?.weight).to.be.equal(123);
expect(extensionController.manifest?.conditions?.length).to.be.equal(0);
// Check that the promise has been resolved for the first render to ensure timing is right.
// Check that the promise has been resolved for the first render to ensure timing is right. [NL]
expect(initialPromiseResolved).to.be.true;
done();
extensionController.destroy();
Expand Down Expand Up @@ -433,7 +430,7 @@ describe('UmbBaseExtensionController', () => {
'Umb.Test.Section.1',
() => {
// This should not be called.
expect(true).to.be.false;
expect.fail('Callback should not be called when never permitted');
},
);
Promise.resolve().then(() => {
Expand All @@ -451,7 +448,7 @@ describe('UmbBaseExtensionController', () => {
'Umb.Test.Section.1',
() => {
// This should not be called.
expect(true).to.be.false;
expect.fail('Callback should not be called when never permitted');
},
);

Expand Down Expand Up @@ -531,7 +528,7 @@ describe('UmbBaseExtensionController', () => {
'Umb.Test.Section.1',
async () => {
count++;
// We want the controller callback to first fire when conditions are initialized.
// We want the controller callback to first fire when conditions are initialized. [NL]
expect(extensionController.manifest?.conditions?.length).to.be.equal(1);
expect(extensionController?.manifest?.alias).to.eq('Umb.Test.Section.1');
if (count === 1) {
Expand Down Expand Up @@ -596,29 +593,30 @@ describe('UmbBaseExtensionController', () => {
'Umb.Test.Section.1',
async (isPermitted) => {
count++;
// We want the controller callback to first fire when conditions are initialized.
// We want the controller callback to first fire when conditions are initialized. [NL]
expect(extensionController.manifest?.conditions?.length).to.be.equal(2);
expect(extensionController?.manifest?.alias).to.eq('Umb.Test.Section.1');
if (count === 1) {
expect(isPermitted).to.be.true;
expect(extensionController?.permitted).to.be.true;
// Hack to double check that its two conditions that make up the state:
// Hack to double check that its two conditions that make up the state: [NL]
expect(
extensionController.getUmbControllers((controller) => (controller as any).permitted).length,
).to.equal(2);
} else if (count === 2) {
expect(isPermitted).to.be.false;
expect(extensionController?.permitted).to.be.false;
// Hack to double check that its two conditions that make up the state, in this case its one, cause we already got the callback when one of the conditions changed. meaning in this split second one is still good:
// Hack to double check that its two conditions that make up the state, in this case its one, cause we already got the callback when one of the conditions changed. meaning in this split second one is still good: [NL]
expect(
extensionController.getUmbControllers((controller) => (controller as any).permitted).length,
).to.equal(1);

// Then we are done:
extensionController.destroy(); // End this test.
setTimeout(() => done(), 60); // Lets wait another round of the conditions approve/disapprove, just to see if the destroy stopped the conditions. (60ms, as that should be enough to test that another round does not happen.)
setTimeout(() => done(), 60); // Lets wait another round of the conditions approve/disapprove, just to see if the destroy stopped the conditions. (60ms, as that should be enough to test that another round does not happen.) [NL]
} else if (count === 5) {
expect(false).to.be.true; // This should not be called.
// This should not be called.
expect.fail('Callback should not be called when never permitted');
}
},
);
Expand Down
Loading
Loading