DEV: Introduce a `@debounce(delay)` decorator (#18667)

An example from tests:

```js
class TestStub {
  counter = 0;

  @debounce(50)
  increment() {
    this.counter++;
  }
}

const stub = new TestStub();

stub.increment();
stub.increment();
stub.increment();
await settled();

assert.strictEqual(stub.counter, 1);
```
This commit is contained in:
Jarek Radosz 2022-10-19 20:43:58 +02:00 committed by GitHub
parent 0f7d1f2420
commit 09e4eb4137
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 71 additions and 2 deletions

View File

@ -36,6 +36,7 @@ import extractValue from "discourse-common/utils/extract-value";
import handleDescriptor from "discourse-common/utils/handle-descriptor"; import handleDescriptor from "discourse-common/utils/handle-descriptor";
import isDescriptor from "discourse-common/utils/is-descriptor"; import isDescriptor from "discourse-common/utils/is-descriptor";
import macroAlias from "discourse-common/utils/macro-alias"; import macroAlias from "discourse-common/utils/macro-alias";
import discourseDebounce from "discourse-common/lib/debounce";
export default function discourseComputedDecorator(...params) { export default function discourseComputedDecorator(...params) {
// determine if user called as @discourseComputed('blah', 'blah') or @discourseComputed // determine if user called as @discourseComputed('blah', 'blah') or @discourseComputed
@ -87,6 +88,24 @@ export function readOnly(target, name, desc) {
}; };
} }
export function debounce(delay) {
return function (target, name, descriptor) {
return {
enumerable: descriptor.enumerable,
configurable: descriptor.configurable,
writable: descriptor.writable,
initializer() {
const originalFunction = descriptor.value;
const debounced = function (...args) {
return discourseDebounce(this, originalFunction, ...args, delay);
};
return debounced;
},
};
};
}
export const on = decoratorAlias(emberOn, "Can not `on` without event names"); export const on = decoratorAlias(emberOn, "Can not `on` without event names");
export const observes = decoratorAlias( export const observes = decoratorAlias(
observer, observer,

View File

@ -1,12 +1,15 @@
import { module, test } from "qunit"; import { module, test } from "qunit";
import { setupRenderingTest } from "discourse/tests/helpers/component-test"; import { setupRenderingTest } from "discourse/tests/helpers/component-test";
import Component from "@ember/component"; import Component from "@ember/component";
import { clearRender, render } from "@ember/test-helpers"; import { clearRender, render, settled } from "@ember/test-helpers";
import discourseComputed, { import discourseComputed, {
afterRender, afterRender,
debounce,
observes,
} from "discourse-common/utils/decorators"; } from "discourse-common/utils/decorators";
import { exists } from "discourse/tests/helpers/qunit-helpers"; import { exists } from "discourse/tests/helpers/qunit-helpers";
import { hbs } from "ember-cli-htmlbars"; import { hbs } from "ember-cli-htmlbars";
import EmberObject from "@ember/object";
const fooComponent = Component.extend({ const fooComponent = Component.extend({
classNames: ["foo-component"], classNames: ["foo-component"],
@ -51,6 +54,24 @@ class NativeComponent extends Component {
} }
} }
const TestStub = EmberObject.extend({
counter: 0,
otherCounter: 0,
@debounce(50)
increment(value) {
this.counter += value;
},
// Note: it only works in this particular order:
// `@observes()` first, then `@debounce()`
@observes("prop")
@debounce(50)
react() {
this.otherCounter++;
},
});
module("Unit | Utils | decorators", function (hooks) { module("Unit | Utils | decorators", function (hooks) {
setupRenderingTest(hooks); setupRenderingTest(hooks);
@ -58,7 +79,7 @@ module("Unit | Utils | decorators", function (hooks) {
this.registry.register("component:foo-component", fooComponent); this.registry.register("component:foo-component", fooComponent);
this.set("baz", 0); this.set("baz", 0);
await render(hbs`{{foo-component baz=baz}}`); await render(hbs`<FooComponent @baz={{this.baz}} />`);
assert.ok(exists(document.querySelector(".foo-component"))); assert.ok(exists(document.querySelector(".foo-component")));
assert.strictEqual(this.baz, 1); assert.strictEqual(this.baz, 1);
@ -109,4 +130,33 @@ module("Unit | Utils | decorators", function (hooks) {
"rerenders the component when arguments change" "rerenders the component when arguments change"
); );
}); });
test("debounce", async function (assert) {
const stub = TestStub.create();
stub.increment(1);
stub.increment(1);
stub.increment(1);
await settled();
assert.strictEqual(stub.counter, 1);
stub.increment(500);
stub.increment(1000);
stub.increment(5);
await settled();
assert.strictEqual(stub.counter, 6);
});
test("debounce works with @observe", async function (assert) {
const stub = TestStub.create();
stub.set("prop", 1);
stub.set("prop", 2);
stub.set("prop", 3);
await settled();
assert.strictEqual(stub.otherCounter, 1);
});
}); });