-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #17941 from rwjblue/add-fn-helper
[FEATURE EMBER_GLIMMER_FN_HELPER] Initial implementation of fn helper.
- Loading branch information
Showing
4 changed files
with
235 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import { HAS_NATIVE_PROXY } from '@ember/-internals/utils'; | ||
import { assert } from '@ember/debug'; | ||
import { DEBUG } from '@glimmer/env'; | ||
import { Arguments, VM } from '@glimmer/runtime'; | ||
import { ICapturedArguments } from '@glimmer/runtime/dist/types/lib/vm/arguments'; | ||
import { InternalHelperReference } from '../utils/references'; | ||
|
||
let context: any = null; | ||
if (DEBUG && HAS_NATIVE_PROXY) { | ||
let assertOnProperty = (property: string | number | symbol) => { | ||
assert( | ||
`You accessed \`this.${String( | ||
property | ||
)}\` from a function passed to the \`fn\` helper, but the function itself was not bound to a valid \`this\` context. Consider updating to usage of \`@action\`.` | ||
); | ||
}; | ||
|
||
context = new Proxy( | ||
{}, | ||
{ | ||
get(_target: {}, property: string | symbol) { | ||
assertOnProperty(property); | ||
}, | ||
|
||
set(_target: {}, property: string | symbol) { | ||
assertOnProperty(property); | ||
|
||
return false; | ||
}, | ||
|
||
has(_target: {}, property: string | symbol) { | ||
assertOnProperty(property); | ||
|
||
return false; | ||
}, | ||
} | ||
); | ||
} | ||
|
||
function fnHelper({ positional }: ICapturedArguments) { | ||
assert( | ||
`You must pass a function as the \`fn\` helpers first argument, you passed ${positional | ||
.at(0) | ||
.value()}`, | ||
typeof positional.at(0).value() === 'function' | ||
); | ||
|
||
return () => { | ||
let [fn, ...args] = positional.value(); | ||
|
||
return fn!['apply'](context, args); | ||
}; | ||
} | ||
|
||
export default function(_vm: VM, args: Arguments) { | ||
return new InternalHelperReference(fnHelper, args.capture()); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
163 changes: 163 additions & 0 deletions
163
packages/@ember/-internals/glimmer/tests/integration/helpers/fn-test.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
import { EMBER_GLIMMER_FN_HELPER } from '@ember/canary-features'; | ||
import { Component } from '../../utils/helpers'; | ||
import { RenderingTestCase, moduleFor, runTask } from 'internal-test-helpers'; | ||
|
||
import { set } from '@ember/-internals/metal'; | ||
|
||
if (EMBER_GLIMMER_FN_HELPER) { | ||
moduleFor( | ||
'Helpers test: {{fn}}', | ||
class extends RenderingTestCase { | ||
beforeEach() { | ||
this.registerHelper('invoke', function([fn]) { | ||
return fn(); | ||
}); | ||
|
||
let testContext = this; | ||
this.registerComponent('stash', { | ||
ComponentClass: Component.extend({ | ||
init() { | ||
this._super(...arguments); | ||
testContext.stashedFn = this.stashedFn; | ||
}, | ||
}), | ||
}); | ||
} | ||
|
||
'@test updates when arguments change'() { | ||
this.render(`{{invoke (fn this.myFunc this.arg1 this.arg2)}}`, { | ||
myFunc(arg1, arg2) { | ||
return `arg1: ${arg1}, arg2: ${arg2}`; | ||
}, | ||
|
||
arg1: 'foo', | ||
arg2: 'bar', | ||
}); | ||
|
||
this.assertText('arg1: foo, arg2: bar'); | ||
|
||
this.assertStableRerender(); | ||
|
||
runTask(() => set(this.context, 'arg1', 'qux')); | ||
this.assertText('arg1: qux, arg2: bar'); | ||
|
||
runTask(() => set(this.context, 'arg2', 'derp')); | ||
this.assertText('arg1: qux, arg2: derp'); | ||
|
||
runTask(() => { | ||
set(this.context, 'arg1', 'foo'); | ||
set(this.context, 'arg2', 'bar'); | ||
}); | ||
|
||
this.assertText('arg1: foo, arg2: bar'); | ||
} | ||
|
||
'@test updates when the function changes'() { | ||
let func1 = (arg1, arg2) => `arg1: ${arg1}, arg2: ${arg2}`; | ||
let func2 = (arg1, arg2) => `arg2: ${arg2}, arg1: ${arg1}`; | ||
|
||
this.render(`{{invoke (fn this.myFunc this.arg1 this.arg2)}}`, { | ||
myFunc: func1, | ||
|
||
arg1: 'foo', | ||
arg2: 'bar', | ||
}); | ||
|
||
this.assertText('arg1: foo, arg2: bar'); | ||
this.assertStableRerender(); | ||
|
||
runTask(() => set(this.context, 'myFunc', func2)); | ||
this.assertText('arg2: bar, arg1: foo'); | ||
|
||
runTask(() => set(this.context, 'myFunc', func1)); | ||
this.assertText('arg1: foo, arg2: bar'); | ||
} | ||
|
||
'@test a stashed fn result update arguments when invoked'(assert) { | ||
this.render(`{{stash stashedFn=(fn this.myFunc this.arg1 this.arg2)}}`, { | ||
myFunc(arg1, arg2) { | ||
return `arg1: ${arg1}, arg2: ${arg2}`; | ||
}, | ||
|
||
arg1: 'foo', | ||
arg2: 'bar', | ||
}); | ||
|
||
assert.equal(this.stashedFn(), 'arg1: foo, arg2: bar'); | ||
|
||
runTask(() => set(this.context, 'arg1', 'qux')); | ||
assert.equal(this.stashedFn(), 'arg1: qux, arg2: bar'); | ||
|
||
runTask(() => set(this.context, 'arg2', 'derp')); | ||
assert.equal(this.stashedFn(), 'arg1: qux, arg2: derp'); | ||
|
||
runTask(() => { | ||
set(this.context, 'arg1', 'foo'); | ||
set(this.context, 'arg2', 'bar'); | ||
}); | ||
|
||
assert.equal(this.stashedFn(), 'arg1: foo, arg2: bar'); | ||
} | ||
|
||
'@test a stashed fn result invokes the correct function when the bound function changes'( | ||
assert | ||
) { | ||
let func1 = (arg1, arg2) => `arg1: ${arg1}, arg2: ${arg2}`; | ||
let func2 = (arg1, arg2) => `arg2: ${arg2}, arg1: ${arg1}`; | ||
|
||
this.render(`{{stash stashedFn=(fn this.myFunc this.arg1 this.arg2)}}`, { | ||
myFunc: func1, | ||
|
||
arg1: 'foo', | ||
arg2: 'bar', | ||
}); | ||
|
||
assert.equal(this.stashedFn(), 'arg1: foo, arg2: bar'); | ||
|
||
runTask(() => set(this.context, 'myFunc', func2)); | ||
assert.equal(this.stashedFn(), 'arg2: bar, arg1: foo'); | ||
|
||
runTask(() => set(this.context, 'myFunc', func1)); | ||
assert.equal(this.stashedFn(), 'arg1: foo, arg2: bar'); | ||
} | ||
|
||
'@test asserts if the first argument is not a function'() { | ||
expectAssertion(() => { | ||
this.render(`{{invoke (fn this.myFunc this.arg1 this.arg2)}}`, { | ||
myFunc: null, | ||
arg1: 'foo', | ||
arg2: 'bar', | ||
}); | ||
}, /You must pass a function as the `fn` helpers first argument, you passed null/); | ||
} | ||
|
||
'@test asserts if the provided function accesses `this` without being bound prior to passing to fn'() { | ||
this.render(`{{stash stashedFn=(fn this.myFunc this.arg1)}}`, { | ||
myFunc(arg1) { | ||
return `arg1: ${arg1}, arg2: ${this.arg2}`; | ||
}, | ||
|
||
arg1: 'foo', | ||
arg2: 'bar', | ||
}); | ||
|
||
expectAssertion(() => { | ||
this.stashedFn(); | ||
}, /You accessed `this.arg2` from a function passed to the `fn` helper, but the function itself was not bound to a valid `this` context. Consider updating to usage of `@action`./); | ||
} | ||
|
||
'@test can use `this` if bound prior to passing to fn'(assert) { | ||
this.render(`{{stash stashedFn=(fn (action this.myFunc) this.arg1)}}`, { | ||
myFunc(arg1) { | ||
return `arg1: ${arg1}, arg2: ${this.arg2}`; | ||
}, | ||
|
||
arg1: 'foo', | ||
arg2: 'bar', | ||
}); | ||
|
||
assert.equal(this.stashedFn(), 'arg1: foo, arg2: bar'); | ||
} | ||
} | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters