Skip to content

Commit 81f9769

Browse files
authoredAug 4, 2022
Merge pull request #573 from NullVoxPopuli/glint-testing
add keepLatest util
2 parents 48cb340 + 3e3d02c commit 81f9769

File tree

4 files changed

+188
-0
lines changed

4 files changed

+188
-0
lines changed
 

‎ember-resources/package.json

+4
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"./core/function-based": "./dist/core/function-based/index.js",
1515
"./util": "./dist/util/index.js",
1616
"./util/cell": "./dist/util/cell.js",
17+
"./util/keep-latest": "./dist/util/keep-latest.js",
1718
"./util/map": "./dist/util/map.js",
1819
"./util/helper": "./dist/util/helper.js",
1920
"./util/remote-data": "./dist/util/remote-data.js",
@@ -37,6 +38,9 @@
3738
"util/cell": [
3839
"dist/util/cell.d.ts"
3940
],
41+
"util/keep-latest": [
42+
"dist/util/keep-latest.d.ts"
43+
],
4044
"util/function": [
4145
"dist/util/function.d.ts"
4246
],
+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { resource, resourceFactory } from '../core/function-based';
2+
3+
const isEmpty = (x: undefined | unknown | unknown[]) => {
4+
if (Array.isArray(x)) {
5+
return x.length === 0;
6+
}
7+
8+
if (typeof x === 'object') {
9+
if (x === null) return true;
10+
11+
return x || Object.keys(x).length > 0;
12+
}
13+
14+
return Boolean(x);
15+
};
16+
17+
interface Options<T = unknown> {
18+
/**
19+
* A function who's return value, when true, will
20+
* keep the latest truthy value as the "return value"
21+
* (as determined by the `value` option's return value)
22+
*/
23+
until: () => boolean;
24+
25+
/**
26+
* A function who's return value will be used as the value
27+
* of this resource.
28+
*/
29+
value: () => T;
30+
}
31+
32+
/**
33+
* A utility decorator for smoothing out changes in upstream data between
34+
* refreshes / reload.
35+
*
36+
* @example
37+
* when using [[RemoteData]] (or some other promise-based "eventually a value" resource),
38+
* the value returned from the API is what's useful to see to users. But if the URL
39+
* changes, the remote request will start anew, and isLoading becomes true, and the value is falsey until the request finishes. This can result in some flicker
40+
* until the new request finishes.
41+
*
42+
* To smooth that out, we can use [[keepLatest]]
43+
*
44+
* ```js
45+
* import { RemoteData } from 'ember-resources/util/remote-data';
46+
* import { keepLatest } from 'ember-resources/util/keep-latest';
47+
*
48+
* class A {
49+
* @use request = RemoteData(() => 'some url');
50+
* @use data = keepLatest({
51+
* value: () => this.request.value,
52+
* when: () => this.request.isLoading,
53+
* });
54+
*
55+
* get result() {
56+
* // after the initial request, this is always resolved
57+
* return this.data;
58+
* }
59+
* }
60+
* ```
61+
*/
62+
export function keepLatest<Return = unknown>({ until, value: valueFn }: Options<Return>) {
63+
return resource(() => {
64+
let previous: Return | undefined;
65+
66+
return () => {
67+
let value = valueFn();
68+
69+
if (until()) {
70+
return (previous = isEmpty(value) ? previous : value);
71+
}
72+
73+
return (previous = value);
74+
};
75+
});
76+
}
77+
78+
resourceFactory(keepLatest);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { tracked } from '@glimmer/tracking';
2+
import { module, test } from 'qunit';
3+
import { setupTest } from 'ember-qunit';
4+
5+
import { timeout } from 'ember-concurrency';
6+
import { use } from 'ember-resources';
7+
import { trackedFunction } from 'ember-resources/util/function';
8+
import { keepLatest } from 'ember-resources/util/keep-latest';
9+
10+
module('Utils | keepLatest | js', function (hooks) {
11+
setupTest(hooks);
12+
13+
test('it works', async function (assert) {
14+
class Test {
15+
@tracked x = 1;
16+
17+
// @use request = trackedFunction(async () => {
18+
request = trackedFunction(this, async () => {
19+
let value = this.x;
20+
21+
await timeout(30);
22+
23+
return value;
24+
});
25+
26+
@use data = keepLatest({
27+
until: () => this.request.isLoading,
28+
value: () => this.request.value,
29+
});
30+
}
31+
32+
let instance = new Test();
33+
34+
assert.strictEqual(instance.data, undefined);
35+
36+
await timeout(40);
37+
38+
assert.strictEqual(instance.data, 1);
39+
40+
instance.x = 2;
41+
42+
assert.strictEqual(instance.data, 1);
43+
await timeout(15);
44+
assert.strictEqual(instance.data, 1);
45+
await timeout(40);
46+
assert.strictEqual(instance.data, 2);
47+
});
48+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { tracked } from '@glimmer/tracking';
2+
// @ts-ignore
3+
import { fn, hash } from '@ember/helper';
4+
import { module, test } from 'qunit';
5+
import { render, settled } from '@ember/test-helpers';
6+
import { setupRenderingTest } from 'ember-qunit';
7+
8+
import { timeout } from 'ember-concurrency';
9+
import { trackedFunction } from 'ember-resources/util/function';
10+
import { keepLatest } from 'ember-resources/util/keep-latest';
11+
12+
module('Utils | keepLatest | rendering', function (hooks) {
13+
setupRenderingTest(hooks);
14+
15+
test('it works', async function (assert) {
16+
class Test {
17+
@tracked x = 1;
18+
19+
// @use request = trackedFunction(async () => {
20+
request = trackedFunction(this, async () => {
21+
let value = this.x;
22+
23+
await timeout(30);
24+
25+
return value;
26+
});
27+
}
28+
29+
let instance = new Test();
30+
31+
let passthrough = (x: unknown) => x;
32+
33+
render(<template>
34+
{{#let instance.request as |request|}}
35+
{{keepLatest (hash
36+
until=(fn passthrough request.isLoading)
37+
value=(fn passthrough request.value)
38+
)}}
39+
{{/let}}
40+
</template>);
41+
42+
await timeout(10);
43+
44+
assert.dom().hasNoText();
45+
46+
await settled();
47+
48+
assert.dom().hasText('1');
49+
50+
instance.x = 2;
51+
52+
assert.dom().hasText('1');
53+
await timeout(15);
54+
assert.dom().hasText('1');
55+
await timeout(40);
56+
assert.dom().hasText('2');
57+
});
58+
});

0 commit comments

Comments
 (0)
Failed to load comments.