From 3975e6592bc71f8a525fd9debb6ab4f296489189 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli Date: Thu, 19 May 2022 00:14:30 -0400 Subject: [PATCH 1/2] fix(RemoteData, function-resource): wrapped template usage In order to use RemoteData as template-resource, we need _another helper manager_, and we need to register it as well. This ends up resulting in _nested_ helper-manager invocation for a single helper-invocation call. --- .npmrc | 5 ++ ember-resources/src/util/function-resource.ts | 32 ++++++++++ ember-resources/src/util/remote-data.ts | 4 +- pnpm-lock.yaml | 25 ++++++-- .../utils/function-resource/rendering-test.ts | 58 ++++++++++++++++++- .../tests/utils/remote-data/rendering-test.ts | 50 ++++++++++++++++ 6 files changed, 168 insertions(+), 6 deletions(-) create mode 100644 testing/ember-app/tests/utils/remote-data/rendering-test.ts diff --git a/.npmrc b/.npmrc index 6a4052e50..c0cc4f7f0 100644 --- a/.npmrc +++ b/.npmrc @@ -1 +1,6 @@ public-hoist-pattern[]=*@types* + +# Required because broccoli has hard-coded paths to *all* packages +# not just relevant ones.......... +public-hoist-pattern[]=eslint-plugin* +public-hoist-pattern[]=eslint-config* diff --git a/ember-resources/src/util/function-resource.ts b/ember-resources/src/util/function-resource.ts index 5f1891de3..3a1459323 100644 --- a/ember-resources/src/util/function-resource.ts +++ b/ember-resources/src/util/function-resource.ts @@ -313,8 +313,40 @@ class FunctionResourceManager { } } +type ResourceFactory = (...args: any[]) => ReturnType; + +class ResourceInvokerManager { + capabilities = helperCapabilities('3.23', { + hasValue: true, + hasDestroyable: true, + }); + + createHelper(fn: ResourceFactory, args: any): ReturnType { + // this calls `resource`, which registers + // with the other helper manager + return fn(...args.positional); + } + + getValue(helper: ReturnType) { + let result = invokeHelper(this, helper, () => ({})); + + return getValue(result); + } + + getDestroyable(helper: ReturnType) { + return helper; + } +} + // Provide a singleton manager. const MANAGER = new FunctionResourceManager(); +const ResourceInvoker = new ResourceInvokerManager(); + +export function registerResourceWrapper(wrapperFn: ResourceFactory) { + setHelperManager(() => ResourceInvoker, wrapperFn); + + return wrapperFn; +} interface Descriptor { initializer: () => unknown; diff --git a/ember-resources/src/util/remote-data.ts b/ember-resources/src/util/remote-data.ts index 0f26d5848..d4a80fd52 100644 --- a/ember-resources/src/util/remote-data.ts +++ b/ember-resources/src/util/remote-data.ts @@ -1,7 +1,7 @@ import { tracked } from '@glimmer/tracking'; import { waitForPromise } from '@ember/test-waiters'; -import { resource } from './function-resource'; +import { registerResourceWrapper, resource } from './function-resource'; import type { Hooks } from './function-resource'; @@ -188,3 +188,5 @@ export function RemoteData( return remoteData(hooks, targetUrl, options); }); } + +registerResourceWrapper(RemoteData); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 13134147d..a001514e1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -310,10 +310,10 @@ importers: ember-try: 2.0.0 ember-welcome-page: 6.1.0 eslint: 7.32.0 - eslint-config-prettier: 8.4.0_eslint@7.32.0 - eslint-plugin-ember: 10.5.9_eslint@7.32.0 + eslint-config-prettier: 8.5.0_eslint@7.32.0 + eslint-plugin-ember: 10.6.1_eslint@7.32.0 eslint-plugin-node: 11.1.0_eslint@7.32.0 - eslint-plugin-prettier: 4.0.0_5u7oomrk4c6c4veizye6iqamma + eslint-plugin-prettier: 4.0.0_z7eskpggz6hgh3lpmotlmahgxe eslint-plugin-qunit: 7.2.0_eslint@7.32.0 loader.js: 4.7.0 msw: 0.39.2 @@ -9758,6 +9758,23 @@ packages: prettier-linter-helpers: 1.0.0 dev: true + /eslint-plugin-prettier/4.0.0_z7eskpggz6hgh3lpmotlmahgxe: + resolution: {integrity: sha512-98MqmCJ7vJodoQK359bqQWaxOE0CS8paAz/GgjaZLyex4TTk3g9HugoO89EqWCrFiOqn9EVvcoo7gZzONCWVwQ==} + engines: {node: '>=6.0.0'} + peerDependencies: + eslint: '>=7.28.0' + eslint-config-prettier: '*' + prettier: '>=2.0.0' + peerDependenciesMeta: + eslint-config-prettier: + optional: true + dependencies: + eslint: 7.32.0 + eslint-config-prettier: 8.5.0_eslint@7.32.0 + prettier: 2.5.1 + prettier-linter-helpers: 1.0.0 + dev: true + /eslint-plugin-qunit/7.2.0_eslint@7.32.0: resolution: {integrity: sha512-ebT6aOpmMj4vchG0hVw9Ukbutk/lgywrc8gc9w9hH2/4WjKqwMlyM7iVwqB7OAXv6gtQMJZuziT0wNjjymAuWA==} engines: {node: 12.x || 14.x || >=16.0.0} @@ -14775,7 +14792,7 @@ packages: /rxjs/7.5.5: resolution: {integrity: sha512-sy+H0pQofO95VDmFLzyaw9xNJU4KTRSwQIGM6+iG3SypAtCiLDzpeG8sJrNCWn2Up9km+KhkvTdbkrdy+yzZdw==} dependencies: - tslib: 2.3.1 + tslib: 2.4.0 dev: true /safe-buffer/5.1.2: diff --git a/testing/ember-app/tests/utils/function-resource/rendering-test.ts b/testing/ember-app/tests/utils/function-resource/rendering-test.ts index 7739bce9d..bac29afe7 100644 --- a/testing/ember-app/tests/utils/function-resource/rendering-test.ts +++ b/testing/ember-app/tests/utils/function-resource/rendering-test.ts @@ -4,7 +4,7 @@ import { hbs } from 'ember-cli-htmlbars'; import { module, test } from 'qunit'; import { setupRenderingTest } from 'ember-qunit'; -import { resource } from 'ember-resources/util/function-resource'; +import { registerResourceWrapper, resource } from 'ember-resources/util/function-resource'; module('Utils | resource | rendering', function (hooks) { setupRenderingTest(hooks); @@ -57,4 +57,60 @@ module('Utils | resource | rendering', function (hooks) { 'destroy 7', ]); }); + + module('with a registered wrapper', function () { + test('lifecycle', async function (assert) { + function Wrapper(initial: number) { + return resource(({ on }) => { + on.cleanup(() => assert.step(`destroy ${initial}`)); + + assert.step(`resolve ${initial}`); + + return initial + 1; + }); + } + + registerResourceWrapper(Wrapper); + + class Test { + @tracked num = 0; + } + + let foo = new Test(); + + this.setProperties({ Wrapper, foo }); + + await render(hbs` + {{#let (this.Wrapper this.foo.num) as |state|}} + {{state}} + {{/let}} + `); + + assert.dom('out').containsText('1'); + + foo.num = 2; + await settled(); + + assert.dom('out').containsText('3'); + + foo.num = 7; + await settled(); + + assert.dom('out').containsText('8'); + + await clearRender(); + + /** + * As a reminder, destruction is async + */ + assert.verifySteps([ + 'resolve 0', + 'resolve 2', + 'destroy 0', + 'resolve 7', + 'destroy 2', + 'destroy 7', + ]); + }); + }); }); diff --git a/testing/ember-app/tests/utils/remote-data/rendering-test.ts b/testing/ember-app/tests/utils/remote-data/rendering-test.ts new file mode 100644 index 000000000..14965c7ac --- /dev/null +++ b/testing/ember-app/tests/utils/remote-data/rendering-test.ts @@ -0,0 +1,50 @@ +import { render } from '@ember/test-helpers'; +import { hbs } from 'ember-cli-htmlbars'; +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'ember-qunit'; + +import { setupMSW } from 'ember-app/tests/msw'; +import { RemoteData } from 'ember-resources/util/remote-data'; + +let data = [ + { id: '1', type: 'blogs', attributes: { name: `name:1` } }, + { id: '2', type: 'blogs', attributes: { name: `name:2` } }, + { id: '3', type: 'blogs', attributes: { name: `name:3` } }, +]; + +module('Utils | remote-data | rendering', function (hooks) { + setupRenderingTest(hooks); + setupMSW(hooks, ({ rest }) => [ + rest.get('/blogs/:id', (req, res, ctx) => { + let record = data.find((datum) => datum.id === req.params.id); + + return res(ctx.json({ ...record })); + }), + ]); + + module('RemoteData', function () { + test('works with static url', async function (assert) { + this.setProperties({ RemoteData }); + + await render(hbs` + {{#let (this.RemoteData "/blogs/1") as |blog|}} + {{blog.value.attributes.name}} + {{/let}} + `); + + assert.dom().hasText('name:1'); + }); + + test('works with dynamic url', async function (assert) { + this.setProperties({ RemoteData }); + + await render(hbs` + {{#let (this.RemoteData "/blogs/1") as |blog|}} + {{blog.value.attributes.name}} + {{/let}} + `); + + assert.dom().hasText('name:1'); + }); + }); +}); From aa806e3ec7429918005751a49d29d9a2bfa09627 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli Date: Fri, 20 May 2022 13:55:24 -0400 Subject: [PATCH 2/2] chore(RemoteData, resource): updated docs --- ember-resources/src/util/function-resource.ts | 56 +++++++++++++++++++ ember-resources/src/util/remote-data.ts | 18 ++++++ 2 files changed, 74 insertions(+) diff --git a/ember-resources/src/util/function-resource.ts b/ember-resources/src/util/function-resource.ts index 3a1459323..f5e693636 100644 --- a/ember-resources/src/util/function-resource.ts +++ b/ember-resources/src/util/function-resource.ts @@ -342,6 +342,62 @@ class ResourceInvokerManager { const MANAGER = new FunctionResourceManager(); const ResourceInvoker = new ResourceInvokerManager(); +/** + * Allows wrapper functions to provide a [[resource]] for use in templates. + * + * Only library authors may care about this, but helper function is needed to "register" + * the wrapper function with a helper manager that specifically handles invoking both the + * resource wrapper function as well as the underlying resource. + * + * _App-devs / consumers may not ever need to know this utility function exists_ + * + * Example using strict mode +