Skip to content

Commit 2797e41

Browse files
Merge pull request #79 from universal-ember/fix-debounce-cleanup-timing
Fix debounce cleanup timing
2 parents d95589f + 021bda1 commit 2797e41

File tree

3 files changed

+80
-24
lines changed

3 files changed

+80
-24
lines changed

reactiveweb/src/debounce.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,15 +60,23 @@ class TrackedValue<T> {
6060
*/
6161
export function debounce<Value = unknown>(ms: number, thunk: () => Value) {
6262
let lastValue: Value;
63-
let timer: number;
6463
let state = new TrackedValue<Value>();
6564

6665
return resource(({ on }) => {
66+
let timer: number;
67+
6768
lastValue = thunk();
6869

69-
on.cleanup(() => timer && clearTimeout(timer));
70-
timer = setTimeout(() => (state.value = lastValue), ms);
70+
on.cleanup(() => {
71+
if (timer) {
72+
clearTimeout(timer);
73+
}
74+
});
75+
76+
timer = setTimeout(() => {
77+
state.value = lastValue;
78+
}, ms);
7179

72-
return state.value;
80+
return () => state.value;
7381
});
7482
}

tests/test-app/tests/utils/debounce/js-test.ts

Lines changed: 13 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,40 +9,33 @@ import { debounce } from 'reactiveweb/debounce';
99
module('Utils | debounce | js', function (hooks) {
1010
setupTest(hooks);
1111

12-
let someTime = (ms = 25) => new Promise((resolve) => setTimeout(resolve, ms));
12+
const timeout = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
1313

1414
module('debounce', function () {
15-
test('works with @use', async function (assert) {
15+
test('value is returned after x ms', async function (assert) {
1616
class Test {
17-
@tracked data = '';
17+
@tracked value = 'initial';
1818

19-
@use text = debounce(100, () => this.data);
19+
@use debouncedValue = debounce(100, () => this.value);
2020
}
2121

2222
let test = new Test();
2323

2424
setOwner(test, this.owner);
2525

26-
assert.strictEqual(test.text, undefined);
26+
assert.strictEqual(test.debouncedValue, undefined, 'Value is undefined at first');
2727

28-
test.data = 'b';
29-
await someTime();
30-
assert.strictEqual(test.text, undefined);
31-
test.data = 'bo';
32-
await someTime();
33-
assert.strictEqual(test.text, undefined);
34-
test.data = 'boo';
35-
await someTime();
36-
assert.strictEqual(test.text, undefined);
28+
await timeout(50);
3729

38-
await someTime(110);
39-
assert.strictEqual(test.text, 'boo');
30+
assert.strictEqual(test.debouncedValue, undefined, 'Value is still undefined after ~50ms');
4031

41-
test.data = 'boop';
42-
assert.strictEqual(test.text, 'boo');
32+
await timeout(50);
4333

44-
await someTime(110);
45-
assert.strictEqual(test.text, 'boop');
34+
assert.strictEqual(
35+
test.debouncedValue,
36+
test.value,
37+
`Value is "${test.debouncedValue}" after ~100ms`
38+
);
4639
});
4740
});
4841
});
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { tracked } from '@glimmer/tracking';
2+
import { setOwner } from '@ember/application';
3+
import { render } from '@ember/test-helpers';
4+
import { module, test } from 'qunit';
5+
import { setupRenderingTest } from 'ember-qunit';
6+
7+
import { use } from 'ember-resources';
8+
import { debounce } from 'reactiveweb/debounce';
9+
10+
module('debounce | rendering', function (hooks) {
11+
setupRenderingTest(hooks);
12+
13+
const timeout = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
14+
15+
test('value is returned *after* delay has passed', async function(assert) {
16+
class Test {
17+
@tracked value = 'initial';
18+
19+
@use debouncedValue = debounce(100, () => this.value);
20+
}
21+
22+
let test = new Test();
23+
24+
setOwner(test, this.owner);
25+
26+
await render(<template><output>{{test.debouncedValue}}</output></template>);
27+
28+
assert.dom('output').hasNoText();
29+
assert.strictEqual(test.debouncedValue, undefined, 'Value is undefined at first');
30+
31+
await timeout(25);
32+
33+
assert.dom('output').hasNoText();
34+
assert.strictEqual(test.debouncedValue, undefined, 'Value is still undefined after ~25ms');
35+
36+
// Changing the value resets the debounce timer
37+
test.value = 'new';
38+
39+
// not quite enough
40+
await timeout(80);
41+
42+
assert.dom('output').hasNoText();
43+
assert.strictEqual(test.debouncedValue, undefined, `Value is still undefined after ~105ms (because we changed something at ~25ms)`);
44+
45+
await timeout(50);
46+
47+
assert.dom('output').hasText('new');
48+
assert.strictEqual(test.debouncedValue, test.value, `Value is "${test.value}" after ~155ms`);
49+
50+
await timeout(50);
51+
52+
assert.dom('output').hasText('new');
53+
assert.strictEqual(test.debouncedValue, test.value, `Value is "${test.value}" after ~205ms`);
54+
});
55+
})

0 commit comments

Comments
 (0)