DEV: Allow ember hash as a context in applyValueTransformer (#29922)

This unlocks the ability to use that function directly in templates:

```hbs
{{applyValueTransformer
  "foo-bar"
  @defaultValue
  (hash arg1=@arg1 arg2=@arg2)
}}
```
This commit is contained in:
Jarek Radosz 2024-11-25 22:48:00 +01:00 committed by GitHub
parent c9cede1c98
commit 613dea61a2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 28 additions and 20 deletions

View File

@ -317,10 +317,13 @@ export function applyValueTransformer(
if ( if (
typeof (context ?? undefined) !== "undefined" && typeof (context ?? undefined) !== "undefined" &&
!(typeof context === "object" && context.constructor === Object) !(
typeof context === "object" &&
(context.constructor === Object || context.constructor === undefined)
)
) { ) {
throw ( throw (
`${prefix}("${transformerName}", ...): context must be a simple JS object or nullish.\n` + `${prefix}("${transformerName}", ...): context must be a simple JS object/an Ember hash or nullish.\n` +
"Avoid passing complex objects in the context, like for example, component instances or objects that carry " + "Avoid passing complex objects in the context, like for example, component instances or objects that carry " +
"mutable state directly. This can induce users to registry transformers with callbacks causing side effects " + "mutable state directly. This can induce users to registry transformers with callbacks causing side effects " +
"and mutating the context directly. Inevitably, this leads to fragile integrations." "and mutating the context directly. Inevitably, this leads to fragile integrations."

View File

@ -14,6 +14,15 @@ import {
transformerWasAdded, transformerWasAdded,
} from "discourse/lib/transformer"; } from "discourse/lib/transformer";
function notThrows(testCallback) {
try {
testCallback();
return true;
} catch {
return false;
}
}
module("Unit | Utility | transformers", function (hooks) { module("Unit | Utility | transformers", function (hooks) {
setupTest(hooks); setupTest(hooks);
@ -208,15 +217,6 @@ module("Unit | Utility | transformers", function (hooks) {
}); });
test("accepts only simple objects as context", function (assert) { test("accepts only simple objects as context", function (assert) {
const notThrows = (testCallback) => {
try {
testCallback();
return true;
} catch {
return false;
}
};
assert.ok( assert.ok(
notThrows(() => notThrows(() =>
applyValueTransformer("test-value1-transformer", "foo") applyValueTransformer("test-value1-transformer", "foo")
@ -317,6 +317,20 @@ module("Unit | Utility | transformers", function (hooks) {
); );
}); });
test("accepts an ember hash proxy as context", function (assert) {
// A hash-like object
const hash = { a: 1 };
Object.setPrototypeOf(hash, null);
const proxy = new Proxy(hash, {});
assert.true(
notThrows(() =>
applyValueTransformer("test-value1-transformer", "foo", proxy)
),
"doesn't throw an error if context is a proxy to an ember hash object"
);
});
test("applying the transformer works", function (assert) { test("applying the transformer works", function (assert) {
class Testable { class Testable {
#value; #value;
@ -777,15 +791,6 @@ module("Unit | Utility | transformers", function (hooks) {
}); });
test("accepts only simple objects as context", function (assert) { test("accepts only simple objects as context", function (assert) {
const notThrows = (testCallback) => {
try {
testCallback();
return true;
} catch {
return false;
}
};
assert.ok( assert.ok(
notThrows(() => notThrows(() =>
applyBehaviorTransformer("test-behavior1-transformer", () => true) applyBehaviorTransformer("test-behavior1-transformer", () => true)