-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add the helper from ember-resources (#31)
* Add the helper from ember-resources * updates
- Loading branch information
1 parent
fa156a4
commit 4fcd3a7
Showing
2 changed files
with
130 additions
and
0 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,96 @@ | ||
import { getValue } from '@glimmer/tracking/primitives/cache'; | ||
import { invokeHelper } from '@ember/helper'; | ||
|
||
import { DEFAULT_THUNK, normalizeThunk } from './utils.ts'; | ||
|
||
import type ClassBasedHelper from '@ember/component/helper'; | ||
import type { FunctionBasedHelper } from '@ember/component/helper'; | ||
import type { HelperLike } from '@glint/template'; | ||
import type { Thunk } from 'ember-resources'; | ||
|
||
// Should be from | ||
// @glimmer/tracking/primitives/cache | ||
type Cache = ReturnType<typeof invokeHelper>; | ||
|
||
type Get<T, K, Otherwise = unknown> = K extends keyof T ? T[K] : Otherwise; | ||
|
||
/** | ||
* <div class="callout note"> | ||
* | ||
* This is not a core part of ember-resources, but is an example utility to demonstrate a concept when authoring your own resources. However, this utility is still under the broader library's SemVer policy. | ||
* | ||
* A consuming app will not pay for the bytes of this utility unless imported. | ||
* | ||
* </div> | ||
* | ||
* implemented with raw `invokeHelper` API, no classes from `ember-resources` used. | ||
* | ||
* ----------------------- | ||
* | ||
* Enables the use of template-helpers in JavaScript | ||
* | ||
* Note that it should be preferred to use regular functions in javascript | ||
* whenever possible, as the runtime cost of "things as resources" is non-0. | ||
* For example, if using `@ember/component/helper` utilities, it's a common p | ||
* practice to split the actual behavior from the framework construct | ||
* ```js | ||
* export function plainJs() {} | ||
* | ||
* export default helper(() => plainJs()) | ||
* ``` | ||
* so in this case `plainJs` can be used separately. | ||
* | ||
* This differentiation makes less of a difference since | ||
* [plain functions as helpers](https://github.com/emberjs/rfcs/pull/756) | ||
* will be supported soon. | ||
* | ||
* @example | ||
* ```js | ||
* import intersect from 'ember-composable-helpers/addon/helpers/intersect'; | ||
* | ||
* import { helper } from 'ember-resources/util/helper'; | ||
* | ||
* class Demo { | ||
* @tracked listA = [...]; | ||
* @tracked listB = [...] | ||
* | ||
* intersection = helper(this, intersect, () => [this.listA, this.listB]) | ||
* | ||
* toString = (array) => array.join(', '); | ||
* } | ||
* ``` | ||
* ```hbs | ||
* {{this.toString this.intersection.value}} | ||
* ``` | ||
*/ | ||
export function helper<T = unknown, S = InferSignature<T>, Return = Get<S, 'Return'>>( | ||
context: object, | ||
helper: T, | ||
thunk: Thunk = DEFAULT_THUNK | ||
): { value: Return } { | ||
let resource: Cache; | ||
|
||
return { | ||
get value(): Return { | ||
if (!resource) { | ||
resource = invokeHelper(context, helper as object, () => { | ||
return normalizeThunk(thunk); | ||
}); | ||
} | ||
|
||
// SAFETY: we want whatever the Return type is to be forwarded. | ||
// getValue could technically be undefined, but we *def* | ||
// have an invokedHelper, so we can safely defer to the helper. | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
return getValue<Return>(resource as any) as Return; | ||
}, | ||
}; | ||
} | ||
|
||
type InferSignature<T> = T extends HelperLike<infer S> | ||
? S | ||
: T extends FunctionBasedHelper<infer S> | ||
? S | ||
: T extends ClassBasedHelper<infer S> | ||
? S | ||
: 'Signature not found'; |
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,34 @@ | ||
import { tracked } from '@glimmer/tracking'; | ||
import { setOwner } from '@ember/application'; | ||
import { helper as emberHelper } from '@ember/component/helper'; | ||
import { settled } from '@ember/test-helpers'; | ||
import { module, test } from 'qunit'; | ||
import { setupTest } from 'ember-qunit'; | ||
|
||
import { helper } from 'ember-resources/util/helper'; | ||
|
||
// not testing in template, because that's the easy part | ||
module('Utils | helper | js', function (hooks) { | ||
setupTest(hooks); | ||
|
||
test('it works', async function (assert) { | ||
class Test { | ||
@tracked count = 1; | ||
|
||
_doubler = emberHelper(([num]: number[]) => (num ? num * 2 : num)); | ||
|
||
doubler = helper(this, this._doubler, () => [this.count]); | ||
} | ||
|
||
let foo = new Test(); | ||
|
||
setOwner(foo, this.owner); | ||
|
||
assert.strictEqual(foo.doubler.value, 2); | ||
|
||
foo.count = 4; | ||
await settled(); | ||
|
||
assert.strictEqual(foo.doubler.value, 8); | ||
}); | ||
}); |