Skip to content

Commit 7c9b946

Browse files
committed
Improve not-a-function error message
When the passed `fn` to the helper is not a function, this commit improves the error message users will see, hopefully helping with the debugging process. Fixes emberjs#23
1 parent f21c434 commit 7c9b946

File tree

7 files changed

+154
-4
lines changed

7 files changed

+154
-4
lines changed

addon/-private/assert-function.js

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export default function assertFunction(modifierName, maybeFunction) {
2+
if (typeof maybeFunction === 'function') return;
3+
4+
throw new TypeError(`${modifierName} expected a function, instead received "${maybeFunction}"`);
5+
}

addon/modifiers/did-insert.js

+4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { setModifierManager, capabilities } from '@ember/modifier';
22
import { gte } from 'ember-compatibility-helpers';
33

4+
import assertFunction from '../-private/assert-function';
5+
46
/**
57
The `{{did-insert}}` element modifier is activated when an element is
68
inserted into the DOM.
@@ -52,6 +54,8 @@ export default setModifierManager(
5254
createModifier() {},
5355

5456
installModifier(_state, element, { positional: [fn, ...args], named }) {
57+
assertFunction('did-insert', fn);
58+
5559
fn(element, args, named);
5660
},
5761

addon/modifiers/did-update.js

+8
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { setModifierManager, capabilities } from '@ember/modifier';
22
import { gte } from 'ember-compatibility-helpers';
33

4+
import assertFunction from '../-private/assert-function';
5+
46
/**
57
The `{{did-update}}` element modifier is activated when any of its arguments
68
are updated. It does not run on initial render.
@@ -67,6 +69,10 @@ export default setModifierManager(
6769
return { element: null };
6870
},
6971
installModifier(state, element, args) {
72+
const [fn] = args.positional;
73+
74+
assertFunction('did-update', fn);
75+
7076
// save element into state bucket
7177
state.element = element;
7278

@@ -90,6 +96,8 @@ export default setModifierManager(
9096

9197
let [fn, ...positional] = args.positional;
9298

99+
assertFunction('did-update', fn);
100+
93101
fn(element, positional, args.named);
94102
},
95103

addon/modifiers/will-destroy.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { setModifierManager, capabilities } from '@ember/modifier';
22
import { gte } from 'ember-compatibility-helpers';
33

4+
import assertFunction from '../-private/assert-function';
5+
46
/**
57
The `{{will-destroy}}` element modifier is activated immediately before the element
68
is removed from the DOM.
@@ -47,7 +49,9 @@ export default setModifierManager(
4749
return { element: null };
4850
},
4951

50-
installModifier(state, element) {
52+
installModifier(state, element, { positional: [fn] }) {
53+
assertFunction('did-destroy', fn);
54+
5155
state.element = element;
5256
},
5357

@@ -56,6 +60,8 @@ export default setModifierManager(
5660
destroyModifier({ element }, args) {
5761
let [fn, ...positional] = args.positional;
5862

63+
assertFunction('did-destroy', fn);
64+
5965
fn(element, positional, args.named);
6066
},
6167
}),

tests/integration/modifiers/did-insert-test.js

+31-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { module, test } from 'qunit';
22
import { setupRenderingTest } from 'ember-qunit';
3-
import { render } from '@ember/test-helpers';
3+
import { render, setupOnerror } from '@ember/test-helpers';
44
import hbs from 'htmlbars-inline-precompile';
55

66
// We want to use ember classic components in this test
@@ -84,4 +84,34 @@ module('Integration | Modifier | did-insert', function (hooks) {
8484

8585
assert.dom('.alert').hasClass('fade-in');
8686
});
87+
88+
test('provides a useful error on insert', async function (assert) {
89+
assert.expect(1);
90+
91+
// Setup error capturing
92+
setupOnerror(function (err) {
93+
assert.equal(
94+
err.toString(),
95+
`TypeError: did-insert expected a function, instead received "undefined"`
96+
);
97+
});
98+
99+
this.owner.register('component:undefined-method-call', Component);
100+
this.owner.register(
101+
'template:components/undefined-method-call',
102+
hbs`
103+
<div {{did-insert this.nonExistentMethod}} class="alert">
104+
{{yield}}
105+
</div>
106+
`
107+
);
108+
109+
await render(hbs`
110+
{{!-- template-lint-disable no-curly-component-invocation --}}
111+
{{undefined-method-call}}
112+
`);
113+
114+
// Reset error capturing
115+
setupOnerror();
116+
});
87117
});

tests/integration/modifiers/did-update-test.js

+45-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { module, test } from 'qunit';
22
import { setupRenderingTest } from 'ember-qunit';
3-
import { render } from '@ember/test-helpers';
3+
import { render, setupOnerror } from '@ember/test-helpers';
44
import hbs from 'htmlbars-inline-precompile';
55

66
module('Integration | Modifier | did-update', function (hooks) {
@@ -24,4 +24,48 @@ module('Integration | Modifier | did-update', function (hooks) {
2424

2525
this.set('boundValue', 'update');
2626
});
27+
28+
test('provides a useful error on install', async function (assert) {
29+
assert.expect(1);
30+
31+
// Setup error capturing
32+
setupOnerror(function (err) {
33+
assert.equal(
34+
err.toString(),
35+
`TypeError: did-update expected a function, instead received "undefined"`
36+
);
37+
});
38+
39+
await render(hbs`
40+
<div {{did-update this.nonExistentMethod}}></div>
41+
`);
42+
43+
// Reset error capturing
44+
setupOnerror();
45+
});
46+
47+
test('provides a useful error on update', async function (assert) {
48+
assert.expect(1);
49+
50+
// Start with a valid function so that install works
51+
this.set('nonExistentMethod', () => {});
52+
53+
// Setup error capturing
54+
setupOnerror(function (err) {
55+
assert.equal(
56+
err.toString(),
57+
`TypeError: did-update expected a function, instead received "undefined"`
58+
);
59+
});
60+
61+
await render(hbs`
62+
<div {{did-update this.nonExistentMethod}}></div>
63+
`);
64+
65+
// Remove the function to trigger an error on update
66+
this.set('nonExistentMethod', undefined);
67+
68+
// Reset error capturing
69+
setupOnerror();
70+
});
2771
});

tests/integration/modifiers/will-destroy-test.js

+54-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { module, test } from 'qunit';
22
import { setupRenderingTest } from 'ember-qunit';
3-
import { render } from '@ember/test-helpers';
3+
import { render, setupOnerror } from '@ember/test-helpers';
44
import hbs from 'htmlbars-inline-precompile';
55

66
module('Integration | Modifier | will-destroy', function (hooks) {
@@ -43,4 +43,57 @@ module('Integration | Modifier | will-destroy', function (hooks) {
4343
// trigger destroy
4444
this.set('show', false);
4545
});
46+
47+
test('provides a useful error on install', async function (assert) {
48+
assert.expect(1);
49+
50+
// Setup error capturing
51+
setupOnerror(function (err) {
52+
assert.equal(
53+
err.toString(),
54+
`TypeError: did-destroy expected a function, instead received "undefined"`
55+
);
56+
});
57+
58+
await render(hbs`
59+
<div {{will-destroy this.nonExistentMethod}}></div>
60+
`);
61+
62+
// Prevent double error on test teardown
63+
this.set('nonExistentMethod', () => {});
64+
65+
// Reset error capturing
66+
setupOnerror();
67+
});
68+
69+
test('provides a useful error on destroy', async function (assert) {
70+
assert.expect(1);
71+
72+
// Start with a valid function so that install works
73+
this.set('nonExistentMethod', () => {});
74+
75+
// Setup error capturing
76+
setupOnerror(function (err) {
77+
assert.equal(
78+
err.toString(),
79+
`TypeError: did-destroy expected a function, instead received "undefined"`
80+
);
81+
});
82+
83+
this.set('show', true);
84+
await render(hbs`
85+
{{#if this.show}}
86+
<div {{will-destroy this.nonExistentMethod}}></div>
87+
{{/if}}
88+
`);
89+
90+
// Remove the function to trigger an error on destroy
91+
this.setProperties({
92+
nonExistentMethod: undefined,
93+
show: false,
94+
});
95+
96+
// Reset error capturing
97+
setupOnerror();
98+
});
4699
});

0 commit comments

Comments
 (0)