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:
parent
0f7d1f2420
commit
09e4eb4137
|
@ -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,
|
||||||
|
|
|
@ -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);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue