From 4ccbb916919a357b4475afcf41b1466c0aa33c3d Mon Sep 17 00:00:00 2001 From: David Taylor Date: Thu, 1 Sep 2022 09:57:48 +0100 Subject: [PATCH] DEV: Improve testing and documentation of RenderGlimmer actions (#18145) --- .../discourse/app/widgets/render-glimmer.js | 38 +++++++++++++ .../components/widgets/render-glimmer-test.js | 53 +++++++++++++++++-- 2 files changed, 88 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/discourse/app/widgets/render-glimmer.js b/app/assets/javascripts/discourse/app/widgets/render-glimmer.js index ce96a1a5851..e4db7d87ca2 100644 --- a/app/assets/javascripts/discourse/app/widgets/render-glimmer.js +++ b/app/assets/javascripts/discourse/app/widgets/render-glimmer.js @@ -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``, + { + counter: state.counter, + incrementCounter: this.incrementCounter.bind(this), + } + ), + ] + }, + + incrementCounter() { + this.state.counter++; + this.scheduleRerender(); + }, +}); +``` + */ export default class RenderGlimmer { diff --git a/app/assets/javascripts/discourse/tests/integration/components/widgets/render-glimmer-test.js b/app/assets/javascripts/discourse/tests/integration/components/widgets/render-glimmer-test.js index db86f088640..53d8b8d62d8 100644 --- a/app/assets/javascripts/discourse/tests/integration/components/widgets/render-glimmer-test.js +++ b/app/assets/javascripts/discourse/tests/integration/components/widgets/render-glimmer-test.js @@ -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`
arg1={{@data.arg1}} dynamicArg={{@data.dynamicArg}}
- `, + `, { ...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``; + layout = hbs`

{{@widgetActionTriggered}}

`; 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`` + ); + + 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( () => {