FIX: Ensure DButton uses the correct target for string actions (#19126)

Our `triggerAction` backwards-compatibility was firing the action on
`parentView`. In most cases this worked, but it doesn't match the
classic behaviour when the DButton is included inside a 'wrapper'
component. In that case, the action should be triggered on the current
'this' context of the template that called the DButton.

This commit mimics the Ember Classic Component manager's behaviour. It
adds the `createCaller` capability to the custom component manager, and
then uses the `callerSelfRef` for dispatching the action.
This commit is contained in:
David Taylor 2022-11-21 13:31:18 +00:00 committed by GitHub
parent 5573257158
commit 269f65f14c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 56 additions and 9 deletions

View File

@ -109,8 +109,8 @@ export default class DButton extends GlimmerComponentWithDeprecatedParentView {
if (typeof actionVal === "string") {
deprecated(...ACTION_AS_STRING_DEPRECATION_ARGS);
if (this.parentView?.send) {
this.parentView.send(actionVal, actionParam);
if (this._target?.send) {
this._target.send(actionVal, actionParam);
} else {
throw new Error(
"DButton could not find a target for the action. Use a closure action instead"

View File

@ -4,16 +4,30 @@ import {
setInternalComponentManager,
} from "@glimmer/manager";
import EmberGlimmerComponentManager from "@glimmer/component/-private/ember-component-manager";
import { valueForRef } from "@glimmer/reference";
class GlimmerComponentWithParentViewManager extends CustomComponentManager {
create(owner, componentClass, args, environment, dynamicScope) {
create(
owner,
componentClass,
args,
environment,
dynamicScope,
callerSelfRef
) {
const result = super.create(...arguments);
result.component.parentView = dynamicScope.view;
dynamicScope.view = result.component;
result.component._target = valueForRef(callerSelfRef);
return result;
}
getCapabilities() {
return { ...super.getCapabilities(), createCaller: true };
}
}
/**

View File

@ -5,6 +5,7 @@ import { exists, query } from "discourse/tests/helpers/qunit-helpers";
import I18n from "I18n";
import { hbs } from "ember-cli-htmlbars";
import ClassicComponent from "@ember/component";
import { withSilencedDeprecationsAsync } from "discourse-common/lib/deprecated";
module("Integration | Component | d-button", function (hooks) {
setupRenderingTest(hooks);
@ -266,16 +267,48 @@ module("Integration | Component | d-button", function (hooks) {
this.legacyActionTriggered();
},
},
layout: hbs`<DButton @action="myLegacyAction" />`,
});
await render(
hbs`<this.classicComponent @legacyActionTriggered={{this.legacyActionTriggered}}>
<DButton @action="myLegacyAction" />
</this.classicComponent>
`
await withSilencedDeprecationsAsync(
"discourse.d-button-action-string",
async () => {
await render(
hbs`<this.classicComponent @legacyActionTriggered={{this.legacyActionTriggered}} />`
);
await click(".btn");
}
);
await click(".btn");
assert.strictEqual(this.foo, "bar");
});
test("Uses correct target with @action string when component called with block", async function (assert) {
this.set("foo", null);
this.set("legacyActionTriggered", () => this.set("foo", "bar"));
this.simpleWrapperComponent = ClassicComponent.extend();
this.classicComponent = ClassicComponent.extend({
actions: {
myLegacyAction() {
this.legacyActionTriggered();
},
},
layout: hbs`<@simpleWrapperComponent><DButton @action="myLegacyAction" /></@simpleWrapperComponent>`,
});
await withSilencedDeprecationsAsync(
"discourse.d-button-action-string",
async () => {
await render(
hbs`<this.classicComponent @legacyActionTriggered={{this.legacyActionTriggered}} @simpleWrapperComponent={{this.simpleWrapperComponent}} />`
);
await click(".btn");
}
);
assert.strictEqual(this.foo, "bar");
});