Skip to content

Commit

Permalink
Merge pull request #1051 from wagenet/fix-keep-latest-empty
Browse files Browse the repository at this point in the history
Fix issue with keepLatest and initial values
  • Loading branch information
NullVoxPopuli authored Jan 5, 2024
2 parents 0f42b44 + 6e16ad1 commit c737cbf
Show file tree
Hide file tree
Showing 3 changed files with 148 additions and 1 deletion.
18 changes: 18 additions & 0 deletions ember-resources/src/util/keep-latest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,29 @@ interface Options<T = unknown> {
export function keepLatest<Return = unknown>({ when, value: valueFn }: Options<Return>) {
return resource(() => {
let previous: Return;
let initial = true;

return () => {
let value = valueFn();

if (when()) {
/**
* Initially, if we may as well return the value instead
* of the "previous" value is there is no previous yet.
*
* We check against undefined, because that's what
* `previous` is "initialized" to.
*
* And then we never enter this block again, because
* we will have previous values in future invocations of this
* Formula.
*/
if (previous === undefined && initial) {
initial = false;

return value;
}

return (previous = isEmpty(value) ? previous : value);
}

Expand Down
2 changes: 1 addition & 1 deletion test-app/tests/utils/keep-latest/js-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ module('Utils | keepLatest | js', function (hooks) {

let instance = new Test();

assert.strictEqual(instance.data, undefined);
assert.strictEqual(instance.data, null);

await timeout(100);

Expand Down
129 changes: 129 additions & 0 deletions test-app/tests/utils/keep-latest/rendering-test.gts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { render, settled } from '@ember/test-helpers';
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';

import { use } from 'ember-resources';
import { trackedFunction } from 'ember-resources/util/function';
import { keepLatest } from 'ember-resources/util/keep-latest';

Expand Down Expand Up @@ -56,4 +57,132 @@ module('Utils | keepLatest | rendering', function (hooks) {
await timeout(40);
assert.dom().hasText('2');
});

test('if the previous value is not empty, and the current value is empty', async function (assert) {
class Test {
@tracked x: number[] | number = [];

// @use request = trackedFunction(async () => {
request = trackedFunction(this, async () => {
let value = this.x;

await timeout(10);

return value;
});

@use data = keepLatest({
when: () => this.request.isPending,
value: () => this.request.value,
})
}

let instance = new Test();

await render(<template>{{JSON.stringify instance.data}}</template>);

assert.dom().hasText('[]');

instance.x = [1];
await settled();
assert.dom().hasText('[1]');

instance.x = [];
await timeout(8);
assert.dom().hasText('[1]');
await settled();
assert.dom().hasText('[]');

instance.x = 0;
await timeout(8);
assert.dom().hasText('[]');

await settled();
assert.dom().hasText('0');

instance.x = [1];
await timeout(8);
assert.dom().hasText('0');

await settled();
assert.dom().hasText('[1]');

instance.x = [1, 2];
assert.dom().hasText('[1]');
await timeout(8);
assert.dom().hasText('[1]');

await settled();
assert.dom().hasText('[1,2]');
});

test('with a default value, if the previous value is not empty, and the current value is empty', async function (assert) {
class Test {
@tracked x: number[] | null | number = [];

// @use request = trackedFunction(async () => {
request = trackedFunction(this, async () => {
let value = this.x;

await timeout(10);

return value;
});

@use latest = keepLatest({
when: () => this.request.isPending,
value: () => this.request.value,
})

get data() {
return this.latest ?? 'default';
}
}

let instance = new Test();

render(<template>{{JSON.stringify instance.data}}</template>);

await timeout(8);
/**
* Initially, a `trackedFunction` returns null.
* we could craft a resource that returns something other than null,
* initially, but null ?? 'default' is 'default'.
*/
assert.dom().hasText('"default"', 'pre-settled, value exists ');
await settled();
assert.dom().hasText('[]', 'value exists, and set explicitly');

instance.x = [1];
await settled();
assert.dom().hasText('[1]', 'non-empty value exists and set explicitly');

instance.x = [];
await timeout(8);
assert.dom().hasText('[1]', 'retains previous value of [1]');
await settled();
assert.dom().hasText('[]', 'value resolved to empty []');

instance.x = null;
await timeout(8);
assert.dom().hasText('[]', 'retains previous value of []');

await settled();
assert.dom().hasText('"default"', 'empty value set, falling back to default');

instance.x = [1];
await timeout(8);
assert.dom().hasText('"default"', 'not yet resolved, previous value used');

await settled();
assert.dom().hasText('[1]', 'value resolved to [1]');

instance.x = [1, 2];
assert.dom().hasText('[1]', 'retains previous non-empty value');
await timeout(8);
assert.dom().hasText('[1]', 'retains previous non-empty value');

await settled();
assert.dom().hasText('[1,2]', 'new value is resolved');
});
});

0 comments on commit c737cbf

Please sign in to comment.