Skip to content

Commit f9c4357

Browse files
Merge pull request #21108 from johanrd/fix/20783
[BUGFIX] Regression [Ember 5.12+] Non helpful error when the function for on modifier was forgot as param
2 parents 0531e62 + 8847937 commit f9c4357

File tree

2 files changed

+78
-14
lines changed

2 files changed

+78
-14
lines changed

packages/@glimmer/runtime/lib/modifiers/on.ts

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
CheckString,
1515
CheckUndefined,
1616
} from '@glimmer/debug';
17-
import { buildUntouchableThis, localAssert } from '@glimmer/debug-util';
17+
import { buildUntouchableThis } from '@glimmer/debug-util';
1818
import { registerDestructor } from '@glimmer/destroyable';
1919
import { setInternalModifierManager } from '@glimmer/manager';
2020
import { valueForRef } from '@glimmer/reference';
@@ -57,17 +57,30 @@ export class OnModifierState {
5757
updateListener(): void {
5858
let { element, args, listener } = this;
5959

60-
localAssert(
61-
args.positional[0],
62-
'You must pass a valid DOM event name as the first argument to the `on` modifier'
63-
);
60+
let selector: string | undefined;
61+
if (DEBUG) {
62+
const el = this.element;
63+
selector =
64+
el.tagName.toLowerCase() +
65+
(el.id ? `#${el.id}` : '') +
66+
Array.from(el.classList)
67+
.map((c) => `.${c}`)
68+
.join('');
69+
}
6470

71+
let arg0 = args.positional[0];
6572
let eventName = check(
66-
valueForRef(args.positional[0]),
73+
arg0 ? valueForRef(arg0) : undefined,
6774
CheckString,
6875
() => 'You must pass a valid DOM event name as the first argument to the `on` modifier'
6976
);
7077

78+
if (DEBUG && !eventName) {
79+
throw new Error(
80+
`You must pass a valid DOM event name as the first argument to the \`on\` modifier on ${selector}`
81+
);
82+
}
83+
7184
let arg1 = args.positional[1];
7285
let userProvidedCallback = check(
7386
arg1 ? valueForRef(arg1) : undefined,
@@ -79,16 +92,17 @@ export class OnModifierState {
7992
}
8093
) as EventListener;
8194

82-
localAssert(
83-
typeof userProvidedCallback === 'function',
84-
`You must pass a function as the second argument to the \`on\` modifier; you passed ${
85-
userProvidedCallback === null ? 'null' : typeof userProvidedCallback
86-
}. While rendering:\n\n${args.positional[1]?.debugLabel ?? `{unlabeled value}`}`
87-
);
95+
if (DEBUG && typeof userProvidedCallback !== 'function') {
96+
throw new Error(
97+
`You must pass a function as the second argument to the \`on\` modifier; you passed ${
98+
userProvidedCallback === null ? 'null' : typeof userProvidedCallback
99+
}. While rendering:\n\n${args.positional[1]?.debugLabel ?? '(unknown)'} on ${selector}`
100+
);
101+
}
88102

89103
if (DEBUG && args.positional.length !== 2) {
90104
throw new Error(
91-
`You can only pass two positional arguments (event name and callback) to the \`on\` modifier, but you provided ${args.positional.length}. Consider using the \`fn\` helper to provide additional arguments to the \`on\` callback.`
105+
`You can only pass two positional arguments (event name and callback) to the \`on\` modifier, but you provided ${args.positional.length}. Consider using the \`fn\` helper to provide additional arguments to the \`on\` callback on ${selector}`
92106
);
93107
}
94108

@@ -124,7 +138,7 @@ export class OnModifierState {
124138
throw new Error(
125139
`You can only \`once\`, \`passive\` or \`capture\` named arguments to the \`on\` modifier, but you provided ${Object.keys(
126140
extra
127-
).join(', ')}.`
141+
).join(', ')} on ${selector}`
128142
);
129143
}
130144
} else {

smoke-tests/scenarios/basic-test.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,56 @@ function basicTest(scenarios: Scenarios, appName: string) {
269269
let allNodes = flattenTree(tree);
270270
let names = allNodes.filter(n => n.type === 'component').map(n => n.name);
271271
assert.true(names.includes('HelloWorld'), 'HelloWorld component name is preserved in the render tree (found: ' + names.join(', ') + ')');
272+
});
273+
});
274+
`,
275+
'on-modifier-error-test.gjs': `
276+
import { module, test } from 'qunit';
277+
import { render, setupOnerror, resetOnerror } from '@ember/test-helpers';
278+
import { setupRenderingTest } from 'ember-qunit';
279+
import { on } from '@ember/modifier';
280+
281+
module('on modifier | error handling', function (hooks) {
282+
setupRenderingTest(hooks);
283+
284+
hooks.afterEach(function () {
285+
resetOnerror();
286+
});
287+
288+
test('throws helpful error when callback is missing', async function (assert) {
289+
assert.expect(1);
290+
const noop = undefined;
291+
setupOnerror((error) => {
292+
assert.true(
293+
/You must pass a function as the second argument to the \`on\` modifier/.test(error.message),
294+
'Expected helpful error message, got: ' + error.message
295+
);
296+
});
297+
await render(<template><div {{on "click" noop}}>Click</div></template>);
298+
});
299+
300+
test('throws helpful error when event name is missing', async function (assert) {
301+
assert.expect(1);
302+
const noop = () => {};
303+
setupOnerror((error) => {
304+
assert.true(
305+
/You must pass a valid DOM event name as the first argument to the \`on\` modifier/.test(error.message),
306+
'Expected helpful error message, got: ' + error.message
307+
);
308+
});
309+
await render(<template><div {{on}}>Click</div></template>);
310+
});
311+
312+
test('error message includes element selector', async function (assert) {
313+
assert.expect(1);
314+
const noop = undefined;
315+
setupOnerror((error) => {
316+
assert.true(
317+
/button#my-id\\.class1\\.class2/.test(error.message),
318+
'Expected element selector in error, got: ' + error.message
319+
);
320+
});
321+
await render(<template><button id="my-id" class="class1 class2" {{on "click" noop}}>Click</button></template>);
272322
});
273323
});
274324
`,

0 commit comments

Comments
 (0)