DEV: Improve testing and documentation of RenderGlimmer actions (#18145)

This commit is contained in:
David Taylor 2022-09-01 09:57:48 +01:00 committed by GitHub
parent 3aaf4dcfd0
commit 4ccbb91691
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 88 additions and 3 deletions

View File

@ -34,6 +34,44 @@ html(){
} }
``` ```
You can also include function references in the `data` object, and use them as actions within the Ember component.
You will need to `bind` the function to ensure it maintains a reference to the widget, and you'll need to manually
call `this.scheduleRerender()` after making any changes to widget state (the normal widget auto-rerendering does not apply).
Note that the @bind decorator will only work if you're using class-based Widget syntax. When using createWidget, you'll need to
call `.bind(this)` manually when passing the function to RenderGlimmer.
For example:
```
createWidget("my-widget", {
tagName: "div",
buildKey: () => `my-widget`,
defaultState() {
return { counter: 0 };
},
html(args, state){
return [
new RenderGlimmer(
this,
"div.my-wrapper-class",
hbs`<MyComponent @counter={{@data.counter}} @incrementCounter={{@data.incrementCounter}} />`,
{
counter: state.counter,
incrementCounter: this.incrementCounter.bind(this),
}
),
]
},
incrementCounter() {
this.state.counter++;
this.scheduleRerender();
},
});
```
*/ */
export default class RenderGlimmer { export default class RenderGlimmer {

View File

@ -7,12 +7,23 @@ import widgetHbs from "discourse/widgets/hbs-compiler";
import Widget from "discourse/widgets/widget"; import Widget from "discourse/widgets/widget";
import ClassicComponent from "@ember/component"; import ClassicComponent from "@ember/component";
import RenderGlimmer from "discourse/widgets/render-glimmer"; import RenderGlimmer from "discourse/widgets/render-glimmer";
import { bind } from "discourse-common/utils/decorators";
class DemoWidget extends Widget { class DemoWidget extends Widget {
static actionTriggered = false; static actionTriggered = false;
tagName = "div.my-widget"; tagName = "div.my-widget";
html(attrs) { buildKey() {
return "abc";
}
defaultState() {
return {
actionTriggered: false,
};
}
html(attrs, state) {
return [ return [
this.attach("button", { this.attach("button", {
label: "rerender", label: "rerender",
@ -25,24 +36,29 @@ class DemoWidget extends Widget {
hbs`<div class='glimmer-content'> hbs`<div class='glimmer-content'>
arg1={{@data.arg1}} dynamicArg={{@data.dynamicArg}} arg1={{@data.arg1}} dynamicArg={{@data.dynamicArg}}
</div> </div>
<DemoComponent @arg1={{@data.arg1}} @dynamicArg={{@data.dynamicArg}} @action={{@data.actionForComponentToTrigger}}/>`, <DemoComponent @arg1={{@data.arg1}} @dynamicArg={{@data.dynamicArg}} @action={{@data.actionForComponentToTrigger}} @widgetActionTriggered={{@data.widgetActionTriggered}}/>`,
{ {
...attrs, ...attrs,
actionForComponentToTrigger: this.actionForComponentToTrigger, actionForComponentToTrigger: this.actionForComponentToTrigger,
widgetActionTriggered: state.actionTriggered,
} }
), ),
]; ];
} }
dummyAction() {} dummyAction() {}
@bind
actionForComponentToTrigger() { actionForComponentToTrigger() {
this.state.actionTriggered = true;
DemoWidget.actionTriggered = true; DemoWidget.actionTriggered = true;
this.scheduleRerender();
} }
} }
class DemoComponent extends ClassicComponent { class DemoComponent extends ClassicComponent {
static eventLog = []; static eventLog = [];
classNames = ["demo-component"]; classNames = ["demo-component"];
layout = hbs`<DButton class="component-action-button" @label="component_action" @action={{@action}} />`; layout = hbs`<DButton class="component-action-button" @label="component_action" @action={{@action}} /><p class='action-state'>{{@widgetActionTriggered}}</p>`;
init() { init() {
DemoComponent.eventLog.push("init"); DemoComponent.eventLog.push("init");
@ -231,6 +247,37 @@ module("Integration | Component | Widget | render-glimmer", function (hooks) {
assert.true(DemoWidget.actionTriggered, "widget event is triggered"); assert.true(DemoWidget.actionTriggered, "widget event is triggered");
}); });
test("modify widget state with component action", async function (assert) {
await render(
hbs`<MountWidget @widget="demo-widget" @args={{hash arg1="val1"}} />`
);
assert.false(
DemoWidget.actionTriggered,
"widget event has not been triggered yet"
);
assert.strictEqual(
query(".action-state").innerText,
"false",
"eventTriggered is false in nested component"
);
assert.true(
exists("div.demo-component button"),
"component button is rendered"
);
await click("div.demo-component button");
assert.true(DemoWidget.actionTriggered, "widget event is triggered");
assert.strictEqual(
query(".action-state").innerText,
"true",
"eventTriggered is true in nested component"
);
});
test("developer ergonomics", function (assert) { test("developer ergonomics", function (assert) {
assert.throws( assert.throws(
() => { () => {