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 {

View File

@ -7,12 +7,23 @@ import widgetHbs from "discourse/widgets/hbs-compiler";
import Widget from "discourse/widgets/widget";
import ClassicComponent from "@ember/component";
import RenderGlimmer from "discourse/widgets/render-glimmer";
import { bind } from "discourse-common/utils/decorators";
class DemoWidget extends Widget {
static actionTriggered = false;
tagName = "div.my-widget";
html(attrs) {
buildKey() {
return "abc";
}
defaultState() {
return {
actionTriggered: false,
};
}
html(attrs, state) {
return [
this.attach("button", {
label: "rerender",
@ -25,24 +36,29 @@ class DemoWidget extends Widget {
hbs`<div class='glimmer-content'>
arg1={{@data.arg1}} dynamicArg={{@data.dynamicArg}}
</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,
actionForComponentToTrigger: this.actionForComponentToTrigger,
widgetActionTriggered: state.actionTriggered,
}
),
];
}
dummyAction() {}
@bind
actionForComponentToTrigger() {
this.state.actionTriggered = true;
DemoWidget.actionTriggered = true;
this.scheduleRerender();
}
}
class DemoComponent extends ClassicComponent {
static eventLog = [];
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() {
DemoComponent.eventLog.push("init");
@ -231,6 +247,37 @@ module("Integration | Component | Widget | render-glimmer", function (hooks) {
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) {
assert.throws(
() => {