Skip to content
Merged
Show file tree
Hide file tree
Changes from 35 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
7100844
feat: adds first draft of a context consume decorator
iOvergaard Oct 10, 2025
0d9ca79
feat: uses an options pattern
iOvergaard Oct 10, 2025
90b4f70
feat: changes approach to use `addInitializer` and `queueMicroTask` i…
iOvergaard Oct 10, 2025
5844d4e
feat: adds extra warning if context is consumed on disconnected contr…
iOvergaard Oct 10, 2025
2169a0a
feat: example implementation of consume decorator
iOvergaard Oct 10, 2025
b320f21
feat: adds support for 'subscribe'
iOvergaard Oct 10, 2025
54913cd
feat: initial work on provide decorator
iOvergaard Oct 13, 2025
4c0286f
docs: adds license to consume decorator
iOvergaard Oct 13, 2025
d2d62d5
feat: adds support for umbraco controllers with `hostConnected`
iOvergaard Oct 13, 2025
e6310df
feat: uses asPromise to handle one-time subscription instead
iOvergaard Oct 13, 2025
821c109
test: adds unit tests for consume decorator
iOvergaard Oct 13, 2025
2e204d7
feat: adds support for controllers through hostConnected injection
iOvergaard Oct 13, 2025
66c77c3
feat: adds support for controllers through hostConnected injection
iOvergaard Oct 13, 2025
1d50ab8
test: adds unit tests for provide decorator
iOvergaard Oct 13, 2025
0d4bdb9
docs: adds more documentation around usage and adds a few warnings in…
iOvergaard Oct 14, 2025
fd423fe
feat: removes unused controllerMap
iOvergaard Oct 14, 2025
a900c0c
docs: adds wording on standard vs legacy decorators
iOvergaard Oct 14, 2025
46ece83
docs: clarifies usage around internal state
iOvergaard Oct 14, 2025
398a84a
feat: adds proper return types for decorators
iOvergaard Oct 14, 2025
b861538
docs: adds more types
iOvergaard Oct 14, 2025
04197b2
feat: makes element optional
iOvergaard Oct 14, 2025
9b002d0
feat: makes element optional
iOvergaard Oct 14, 2025
2383f93
feat: uses @consume in the log viewer to showcase
iOvergaard Oct 14, 2025
131f9ec
Merge remote-tracking branch 'origin/v17/dev' into v17/feature/helpfu…
iOvergaard Oct 14, 2025
db9fbbc
Merge remote-tracking branch 'origin/v17/dev' into v17/feature/helpfu…
iOvergaard Oct 15, 2025
1f71008
chore: cleans up debug info
iOvergaard Oct 15, 2025
ea61c89
feat: renames to `consumeContext` and `provideContext` to stay inline…
iOvergaard Oct 15, 2025
b4723b1
chore: removes unneeded typings
iOvergaard Oct 15, 2025
1889d1d
Merge branch 'v17/dev' into v17/feature/helpful-decorators
iOvergaard Oct 15, 2025
be522af
chore: removes not needed check
iOvergaard Oct 16, 2025
65552f6
chore: removes not needed check
iOvergaard Oct 16, 2025
4b8e1bf
Merge remote-tracking branch 'origin/v17/dev' into v17/feature/helpfu…
iOvergaard Oct 16, 2025
04190a5
test: adds test for rendered value
iOvergaard Oct 16, 2025
740ae78
feat: splits up code into several smaller functions
iOvergaard Oct 16, 2025
06aa7f6
Merge branch 'v17/dev' into v17/feature/helpful-decorators
iOvergaard Oct 16, 2025
6bbd2e0
Apply suggestion from @Copilot
iOvergaard Oct 16, 2025
6ea48be
Apply suggestion from @Copilot
iOvergaard Oct 16, 2025
cf7a3c6
Apply suggestion from @Copilot
iOvergaard Oct 16, 2025
af4d425
Apply suggestion from @Copilot
iOvergaard Oct 16, 2025
a93dba7
docs: augments code example for creating a context
iOvergaard Oct 16, 2025
3f50613
Update src/Umbraco.Web.UI.Client/src/packages/log-viewer/workspace/vi…
iOvergaard Oct 16, 2025
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
@@ -0,0 +1,133 @@
import { UmbContextToken } from '../token/context-token.js';
import type { UmbContextMinimal } from '../types.js';
import { UmbContextProvider } from '../provide/context-provider.js';
import { consumeContext } from './context-consume.decorator.js';
import { aTimeout, elementUpdated, expect, fixture } from '@open-wc/testing';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
import { html, state } from '@umbraco-cms/backoffice/external/lit';

class UmbTestContextConsumerClass implements UmbContextMinimal {
public prop: string = 'value from provider';
getHostElement() {
return undefined as any;
}
}

const testToken = new UmbContextToken<UmbTestContextConsumerClass>('my-test-context');

class MyTestElement extends UmbLitElement {
@consumeContext({
context: testToken,
})
@state()
contextValue?: UmbTestContextConsumerClass;

override render() {
return html`<div>${this.contextValue?.prop ?? 'no context'}</div>`;
}
}

customElements.define('my-consume-test-element', MyTestElement);

describe('@consume decorator', () => {
let provider: UmbContextProvider;
let element: MyTestElement;

beforeEach(async () => {
provider = new UmbContextProvider(document.body, testToken, new UmbTestContextConsumerClass());
provider.hostConnected();

element = await fixture<MyTestElement>(`<my-consume-test-element></my-consume-test-element>`);
});

afterEach(() => {
provider.destroy();
(provider as any) = undefined;
});

it('should receive a context value when provided on the host', () => {
expect(element.contextValue).to.equal(provider.providerInstance());
expect(element.contextValue?.prop).to.equal('value from provider');
});

it('should render the value from the context', async () => {
expect(element).shadowDom.to.equal('<div>value from provider</div>');
});

it('should work when the decorator is used in a controller', async () => {
class MyController extends UmbControllerBase {
@consumeContext({ context: testToken })
contextValue?: UmbTestContextConsumerClass;
}

const controller = new MyController(element);

await elementUpdated(element);

expect(element.contextValue).to.equal(provider.providerInstance());
expect(controller.contextValue).to.equal(provider.providerInstance());
});

it('should have called the callback first', async () => {
let callbackCalled = false;

class MyCallbackTestElement extends UmbLitElement {
@consumeContext({
context: testToken,
callback: () => {
callbackCalled = true;
},
})
contextValue?: UmbTestContextConsumerClass;
}

customElements.define('my-callback-consume-test-element', MyCallbackTestElement);

const callbackElement = await fixture<MyCallbackTestElement>(
`<my-callback-consume-test-element></my-callback-consume-test-element>`,
);

await elementUpdated(callbackElement);

expect(callbackCalled).to.be.true;
expect(callbackElement.contextValue).to.equal(provider.providerInstance());
});

it('should update the context value when the provider instance changes', async () => {
const newProviderInstance = new UmbTestContextConsumerClass();
newProviderInstance.prop = 'new value from provider';

const newProvider = new UmbContextProvider(element, testToken, newProviderInstance);
newProvider.hostConnected();

await elementUpdated(element);

expect(element.contextValue).to.equal(newProvider.providerInstance());
expect(element.contextValue?.prop).to.equal(newProviderInstance.prop);
});

it('should be able to consume without subscribing', async () => {
class MyNoSubscribeTestController extends UmbControllerBase {
@consumeContext({ context: testToken, subscribe: false })
contextValue?: UmbTestContextConsumerClass;
}

const controller = new MyNoSubscribeTestController(element);
await aTimeout(0); // Wait a tick for promise to resolve

expect(controller.contextValue).to.equal(provider.providerInstance());

const newProviderInstance = new UmbTestContextConsumerClass();
newProviderInstance.prop = 'new value from provider';

const newProvider = new UmbContextProvider(element, testToken, newProviderInstance);
newProvider.hostConnected();

await aTimeout(0); // Wait a tick for promise to resolve

// Should still be the old value
expect(controller.contextValue).to.not.equal(newProvider.providerInstance());
expect(controller.contextValue?.prop).to.equal('value from provider');
});
});
Loading
Loading