Skip to content

Commit b386dac

Browse files
Merge pull request #1135 from NullVoxPopuli/non-block-clean-up-change
Fix issue where, in some situations, the immediate-invoker helper manager (used when you use `resourceFactory`) was not correctly destroying the previous instance of a resource (such as when args change))
2 parents 5dd68fa + 0951cd3 commit b386dac

File tree

9 files changed

+119
-15
lines changed

9 files changed

+119
-15
lines changed

ember-resources/src/function-based/immediate-invocation.ts

+15-6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// @ts-ignore
22
import { createCache, getValue } from '@glimmer/tracking/primitives/cache';
33
import { assert } from '@ember/debug';
4-
import { associateDestroyableChild } from '@ember/destroyable';
4+
import { associateDestroyableChild, destroy } from '@ember/destroyable';
55
// @ts-ignore
66
import { capabilities as helperCapabilities, invokeHelper, setHelperManager } from '@ember/helper';
77
import { dependencySatisfies, importSync, macroCondition } from '@embroider/macros';
@@ -14,9 +14,6 @@ type ResourceFactory<Value = any, Args = any> = (...args: SpreadFor<Args>) => Va
1414

1515
interface State {
1616
cache: ReturnType<typeof invokeHelper>;
17-
fn: any;
18-
args: any;
19-
_?: any;
2017
}
2118

2219
let setOwner: (context: unknown, owner: Owner) => void;
@@ -40,6 +37,7 @@ class ResourceInvokerManager {
4037
constructor(protected owner: Owner) {}
4138

4239
createHelper(fn: ResourceFactory, args: any): State {
40+
let previous: object | undefined;
4341
/**
4442
* This cache is for args passed to the ResourceInvoker/Factory
4543
*
@@ -51,12 +49,20 @@ class ResourceInvokerManager {
5149

5250
setOwner(resource, this.owner);
5351

54-
return invokeHelper(cache, resource);
52+
let result = invokeHelper(cache, resource);
53+
54+
if (previous) {
55+
destroy(previous);
56+
}
57+
58+
previous = result;
59+
60+
return result;
5561
});
5662

5763
setOwner(cache, this.owner);
5864

59-
return { fn, args, cache, _: getValue(cache) };
65+
return { cache };
6066
}
6167

6268
/**
@@ -72,6 +78,9 @@ class ResourceInvokerManager {
7278
}
7379

7480
getDestroyable({ cache }: State) {
81+
/**
82+
* This is the parent cache, from `createHelper`
83+
*/
7584
return cache;
7685
}
7786
}

ember-resources/src/function-based/resource.ts

+10
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ export function resource<Value>(
186186
let internalConfig: InternalFunctionResourceConfig<Value> = {
187187
definition: context as ResourceFunction<Value>,
188188
type: 'function-based',
189+
name: 'Resource',
189190
[INTERNAL]: true,
190191
};
191192

@@ -222,10 +223,19 @@ export function resource<Value>(
222223
let internalConfig: InternalFunctionResourceConfig<Value> = {
223224
definition: setup as ResourceFunction<Value>,
224225
type: TYPE,
226+
name: getDebugName(setup),
225227
[INTERNAL]: true,
226228
};
227229

228230
setHelperManager(ResourceManagerFactory, internalConfig);
229231

230232
return wrapForPlainUsage(context, internalConfig);
231233
}
234+
235+
function getDebugName(obj: object) {
236+
if ('name' in obj) {
237+
return `Resource Function: ${obj.name}`;
238+
}
239+
240+
return `Resource Function`;
241+
}

ember-resources/src/function-based/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export const INTERNAL = '__INTERNAL__';
77
export interface InternalFunctionResourceConfig<Value = unknown> {
88
definition: ResourceFunction<Value>;
99
type: 'function-based';
10+
name: string;
1011
[INTERNAL]: true;
1112
}
1213

ember-resources/tsconfig.json

+10-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"extends": "@tsconfig/ember/tsconfig.json",
3-
"include": ["src/**/*", "unpublished-development-types/**/*"],
3+
"include": ["src/**/*"],
44
"glint": {
55
"environment": []
66
},
@@ -42,15 +42,21 @@
4242
"allowImportingTsExtensions": true,
4343

4444
// require extensions
45-
"module": "Node16",
46-
"moduleResolution": "Node16",
45+
// "module": "Node16",
46+
// "moduleResolution": "Node16",
47+
// TODO: enable the above when this package gets to add type=module
48+
"moduleResolution": "bundler",
49+
"module": "esnext",
50+
"target": "esnext",
4751

4852
// https://www.typescriptlang.org/tsconfig#stripInternal
4953
"stripInternal": true,
5054

5155
// Build settings
5256
"noEmitOnError": false,
5357
// Just a good thing to do
54-
"isolatedModules": true
58+
"isolatedModules": true,
59+
60+
"types": ["ember-source/types"]
5561
}
5662
}

ember-resources/unpublished-development-types/index.d.ts

-2
This file was deleted.

test-app/ember-cli-build.js

+5
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
'use strict';
22

3+
const path = require('path');
34
const EmberApp = require('ember-cli/lib/broccoli/ember-app');
45

56
const packageJson = require('./package');
67

78
module.exports = function (defaults) {
9+
const sideWatch = require('@embroider/broccoli-side-watch');
810
let app = new EmberApp(defaults, {
11+
trees: {
12+
app: sideWatch('app', { watching: [path.join(__dirname, '../ember-resources')] }),
13+
},
914
// Add options here
1015
autoImport: {
1116
watchDependencies: Object.keys(packageJson.dependencies),

test-app/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
"@ember/optional-features": "^2.0.0",
4545
"@ember/string": "^3.0.1",
4646
"@ember/test-helpers": "^3.2.1",
47+
"@embroider/broccoli-side-watch": "0.0.2-unstable.ba9fd29",
4748
"@embroider/compat": "^3.1.5",
4849
"@embroider/core": "^3.1.3",
4950
"@embroider/test-setup": "^3.0.1",

test-app/pnpm-lock.yaml

+13-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { clearRender,render, settled } from '@ember/test-helpers';
2+
import { module, test } from 'qunit';
3+
import { setupRenderingTest } from 'ember-qunit';
4+
5+
import { cell, resource, resourceFactory } from 'ember-resources';
6+
7+
8+
module('issues/1134', function(hooks) {
9+
setupRenderingTest(hooks);
10+
11+
test('it works as a vanilla resource', async function (assert) {
12+
let value = cell(0);
13+
14+
const Data = resource(({ on }) => {
15+
let arg = value.current;
16+
17+
assert.step(`setup: ${arg}`);
18+
on.cleanup(() => assert.step(`cleanup: ${arg}`));
19+
20+
return 0;
21+
});
22+
23+
await render(<template>{{Data}}</template>);
24+
25+
assert.verifySteps(['setup: 0']);
26+
27+
value.current = 1;
28+
await settled();
29+
assert.verifySteps(['setup: 1', 'cleanup: 0']);
30+
31+
await clearRender();
32+
assert.verifySteps([`cleanup: ${value.current}`]);
33+
34+
});
35+
36+
test('it works with a Wrapper (resourceFactory)', async function (assert) {
37+
let value = cell(0);
38+
39+
function Wrapper(arg: number) {
40+
assert.step(`wrapper: ${arg}`);
41+
42+
return resource(({ on }) => {
43+
assert.step(`setup: ${arg}`);
44+
on.cleanup(() => assert.step(`cleanup: ${arg}`));
45+
46+
return 0;
47+
});
48+
}
49+
50+
resourceFactory(Wrapper);
51+
52+
await render(<template>{{Wrapper value.current}}</template>);
53+
54+
assert.verifySteps(['wrapper: 0', 'setup: 0']);
55+
56+
value.current = 1;
57+
await settled();
58+
assert.verifySteps(['wrapper: 1', 'setup: 1', 'cleanup: 0']);
59+
60+
await clearRender();
61+
assert.verifySteps([`cleanup: ${value.current}`]);
62+
});
63+
64+
});

0 commit comments

Comments
 (0)