From a5c9f3ad8d24861b9fd0ee5ceb298578ccc43299 Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Tue, 11 Jun 2024 02:54:02 -0300 Subject: [PATCH 1/2] feat: support wire decorator --- .../modules/x/wireAdapters/wireAdapters.ts | 77 +++++++++++++++++++ example/src/modules/x/wireView/wireView.html | 22 ++++++ example/src/modules/x/wireView/wireView.js | 27 +++++++ .../src/modules/x/wireView/wireView.spec.js | 40 ++++++++++ .../src/node/plugins/lwc/resolve-engine.js | 2 +- 5 files changed, 167 insertions(+), 1 deletion(-) create mode 100644 example/src/modules/x/wireAdapters/wireAdapters.ts create mode 100644 example/src/modules/x/wireView/wireView.html create mode 100644 example/src/modules/x/wireView/wireView.js create mode 100644 example/src/modules/x/wireView/wireView.spec.js diff --git a/example/src/modules/x/wireAdapters/wireAdapters.ts b/example/src/modules/x/wireAdapters/wireAdapters.ts new file mode 100644 index 0000000..95dab2b --- /dev/null +++ b/example/src/modules/x/wireAdapters/wireAdapters.ts @@ -0,0 +1,77 @@ +import type { WireAdapter, WireDataCallback, StringKeyedRecord } from 'lwc'; + +class BaseWireAdapter< + Value, + Config extends StringKeyedRecord = StringKeyedRecord, + Context extends StringKeyedRecord = { tagName: string }, +> implements WireAdapter +{ + protected connected = false; + protected config?: Config; + + constructor( + protected dataCallback: WireDataCallback, + protected sourceContext?: Context, + ) {} + + connect() { + this.connected = true; + } + + disconnect() { + this.connected = false; + } + + update(config: Config) { + this.config = config; + } +} + +export class getCurrentTime extends BaseWireAdapter { + interval?: NodeJS.Timeout; + + async update(config: { refresh: boolean; interval: number }) { + super.update(config); + clearInterval(this.interval); + + this.emit(); + + if (config.refresh) { + this.interval = setInterval(() => this.emit(), config.interval); + } + } + + async emit() { + this.dataCallback(new Date()); + } +} + +export class getSourceContext extends BaseWireAdapter<{ tagName: string }> { + connect() { + super.connect(); + if (this.sourceContext) { + this.dataCallback(this.sourceContext); + } + } + + disconnect() { + super.disconnect(); + this.dataCallback({ tagName: 'unknown' }); + } + + update() { + super.update({}); + if (this.sourceContext) { + this.dataCallback(this.sourceContext); + } + } +} + +export class getResponse extends BaseWireAdapter { + async update(config: { url: string }) { + super.update(config); + const response = await fetch(config.url); + const text = await response.text(); + this.dataCallback(text); + } +} diff --git a/example/src/modules/x/wireView/wireView.html b/example/src/modules/x/wireView/wireView.html new file mode 100644 index 0000000..dcfe582 --- /dev/null +++ b/example/src/modules/x/wireView/wireView.html @@ -0,0 +1,22 @@ + \ No newline at end of file diff --git a/example/src/modules/x/wireView/wireView.js b/example/src/modules/x/wireView/wireView.js new file mode 100644 index 0000000..b3187b4 --- /dev/null +++ b/example/src/modules/x/wireView/wireView.js @@ -0,0 +1,27 @@ +import { LightningElement, api, wire } from 'lwc'; +import { getCurrentTime, getSourceContext, getResponse } from 'x/wireAdapters'; + +export default class WireView extends LightningElement { + @api refresh = false; + @api interval = 1000; + @api url = '/virtual/empty.html'; + + @wire(getCurrentTime, { refresh: '$refresh', interval: '$interval' }) + currentTime; + @wire(getSourceContext) + sourceContext; + @wire(getResponse, { url: '$url' }) + response; + + handleRefreshChange(event) { + this.refresh = event.target.checked; + } + + handleIntervalChange(event) { + this.interval = event.target.value; + } + + handleUrlChange(event) { + this.url = event.target.value; + } +} diff --git a/example/src/modules/x/wireView/wireView.spec.js b/example/src/modules/x/wireView/wireView.spec.js new file mode 100644 index 0000000..b84d021 --- /dev/null +++ b/example/src/modules/x/wireView/wireView.spec.js @@ -0,0 +1,40 @@ +import { + expect, + hydrateElement, + insertMarkupIntoDom, + querySelectorDeep, + renderToMarkup, +} from '@lwc/test-runner'; + +const componentPath = import.meta.resolve('./wireView.js'); + +describe('', () => { + it('renders to SSR & hydrates successfully', async () => { + const markup = await renderToMarkup(componentPath, {}); + // Make assertions about raw HTML markup. + expect(markup).to.contain(''); + + const el = await insertMarkupIntoDom(markup); + // Make assertions about pre-hydrated DOM. + // expect(el).to.haveShadowChild("p.child-content"); + + const hydratedWithSsrDOM = await hydrateElement(el, componentPath); + // Ensure hydration occurred without validation errors. + expect(hydratedWithSsrDOM).to.be.true; + // Make assertions about post-hydrated DOM. + expect(querySelectorDeep('#currentTime')).to.have.text(''); + + await Promise.resolve(); + + // Make assertions about post-update DOM. + expect(querySelectorDeep('#currentTime')).to.have.text(new Date().toString()); + + expect(querySelectorDeep('#tagName')).to.have.text('x-wire-view'); + + // Clean up the DOM. + el.remove(); + + // Make assertions about post-cleanup DOM. + expect(document.querySelector('x-wire-config')).to.be.null; + }); +}); diff --git a/packages/@lwc/wds-core/src/node/plugins/lwc/resolve-engine.js b/packages/@lwc/wds-core/src/node/plugins/lwc/resolve-engine.js index c0f2bda..1424497 100644 --- a/packages/@lwc/wds-core/src/node/plugins/lwc/resolve-engine.js +++ b/packages/@lwc/wds-core/src/node/plugins/lwc/resolve-engine.js @@ -55,7 +55,7 @@ const { function patchedRegisterDecorators(Ctor, meta) { const patchedMeta = { ...meta, - wire: undefined, + wire: meta.wire, }; return registerDecorators(Ctor, patchedMeta); } From 6dcb6c52ebd7639a0eee248cfcaefcac9812945c Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Tue, 11 Jun 2024 04:09:19 -0300 Subject: [PATCH 2/2] fix: don't remove element in test --- .../modules/x/wireAdapters/wireAdapters.ts | 45 ++++++++----------- .../src/modules/x/wireView/wireView.spec.js | 8 ---- 2 files changed, 19 insertions(+), 34 deletions(-) diff --git a/example/src/modules/x/wireAdapters/wireAdapters.ts b/example/src/modules/x/wireAdapters/wireAdapters.ts index 95dab2b..a41f3bd 100644 --- a/example/src/modules/x/wireAdapters/wireAdapters.ts +++ b/example/src/modules/x/wireAdapters/wireAdapters.ts @@ -24,43 +24,34 @@ class BaseWireAdapter< update(config: Config) { this.config = config; + this.emit(); + } + + emit() { + throw new Error('Not implemented'); } } export class getCurrentTime extends BaseWireAdapter { - interval?: NodeJS.Timeout; + interval?: ReturnType; - async update(config: { refresh: boolean; interval: number }) { - super.update(config); + emit() { clearInterval(this.interval); - this.emit(); + this.dataCallback(new Date()); - if (config.refresh) { - this.interval = setInterval(() => this.emit(), config.interval); + if (!this.config) { + return; } - } - async emit() { - this.dataCallback(new Date()); + if (this.config.refresh) { + this.interval = setInterval(() => this.dataCallback(new Date()), this.config.interval); + } } } export class getSourceContext extends BaseWireAdapter<{ tagName: string }> { - connect() { - super.connect(); - if (this.sourceContext) { - this.dataCallback(this.sourceContext); - } - } - - disconnect() { - super.disconnect(); - this.dataCallback({ tagName: 'unknown' }); - } - - update() { - super.update({}); + emit() { if (this.sourceContext) { this.dataCallback(this.sourceContext); } @@ -68,9 +59,11 @@ export class getSourceContext extends BaseWireAdapter<{ tagName: string }> { } export class getResponse extends BaseWireAdapter { - async update(config: { url: string }) { - super.update(config); - const response = await fetch(config.url); + async emit() { + if (!this.config) { + return; + } + const response = await fetch(this.config.url); const text = await response.text(); this.dataCallback(text); } diff --git a/example/src/modules/x/wireView/wireView.spec.js b/example/src/modules/x/wireView/wireView.spec.js index b84d021..0ee93ee 100644 --- a/example/src/modules/x/wireView/wireView.spec.js +++ b/example/src/modules/x/wireView/wireView.spec.js @@ -16,8 +16,6 @@ describe('', () => { const el = await insertMarkupIntoDom(markup); // Make assertions about pre-hydrated DOM. - // expect(el).to.haveShadowChild("p.child-content"); - const hydratedWithSsrDOM = await hydrateElement(el, componentPath); // Ensure hydration occurred without validation errors. expect(hydratedWithSsrDOM).to.be.true; @@ -30,11 +28,5 @@ describe('', () => { expect(querySelectorDeep('#currentTime')).to.have.text(new Date().toString()); expect(querySelectorDeep('#tagName')).to.have.text('x-wire-view'); - - // Clean up the DOM. - el.remove(); - - // Make assertions about post-cleanup DOM. - expect(document.querySelector('x-wire-config')).to.be.null; }); });