DEV: Implement DeferredTrackedSet (#27372)

For cases where you'd be using a TrackedSet to render something and then modifying that set throughout the same render cycle. (specifically it will be used in #27365)
This commit is contained in:
Jarek Radosz 2024-06-06 15:59:20 +02:00 committed by GitHub
parent ffec8163b0
commit 46ab4f0c4a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 128 additions and 1 deletions

View File

@ -1,4 +1,6 @@
import { tracked } from "@glimmer/tracking";
import { next } from "@ember/runloop";
import { TrackedSet } from "@ember-compat/tracked-built-ins";
/**
* Define a tracked property on an object without needing to use the @tracked decorator.
@ -120,3 +122,57 @@ export function dedupeTracked(target, key, desc) {
},
};
}
export class DeferredTrackedSet {
#set;
constructor(value) {
this.#set = new TrackedSet(value);
}
has(value) {
return this.#set.has(value);
}
entries() {
return this.#set.entries();
}
keys() {
return this.#set.keys();
}
values() {
return this.#set.values();
}
forEach(fn) {
return this.#set.forEach(fn);
}
get size() {
return this.#set.size;
}
[Symbol.iterator]() {
return this.#set[Symbol.iterator]();
}
get [Symbol.toStringTag]() {
return this.#set[Symbol.toStringTag];
}
add(value) {
next(() => this.#set.add(value));
return this;
}
delete(value) {
next(() => this.#set.delete(value));
return this;
}
clear() {
next(() => this.#set.clear());
}
}

View File

@ -1,6 +1,8 @@
import { cached } from "@glimmer/tracking";
import { run } from "@ember/runloop";
import { settled } from "@ember/test-helpers";
import { module, test } from "qunit";
import { dedupeTracked } from "discourse/lib/tracked-tools";
import { dedupeTracked, DeferredTrackedSet } from "discourse/lib/tracked-tools";
module("Unit | tracked-tools", function () {
test("@dedupeTracked", async function (assert) {
@ -45,4 +47,73 @@ module("Unit | tracked-tools", function () {
"Initials getter re-evaluated"
);
});
test("DeferredTrackedSet", async function (assert) {
class Player {
evaluationsCount = 0;
letters = new DeferredTrackedSet();
@cached
get score() {
this.evaluationsCount++;
return this.letters.size;
}
}
const player = new Player();
assert.strictEqual(player.score, 0, "score is correct");
assert.strictEqual(player.evaluationsCount, 1, "getter evaluated once");
run(() => {
player.letters.add("a");
assert.strictEqual(player.score, 0, "score does not change");
assert.strictEqual(
player.evaluationsCount,
1,
"getter does not evaluate"
);
player.letters.add("b");
player.letters.add("c");
assert.strictEqual(player.score, 0, "score still does not change");
assert.strictEqual(
player.evaluationsCount,
1,
"getter still does not evaluate"
);
});
await settled();
assert.strictEqual(player.score, 3, "score is correct");
assert.strictEqual(player.evaluationsCount, 2, "getter evaluated again");
run(() => {
player.letters.add("d");
});
await settled();
assert.strictEqual(player.score, 4, "score is correct");
assert.strictEqual(player.evaluationsCount, 3, "getter evaluated again");
run(() => {
player.letters.add("e");
assert.strictEqual(player.score, 4, "score is correct");
assert.strictEqual(
player.evaluationsCount,
3,
"getter does not evaluate"
);
player.letters.add("f");
});
await settled();
assert.strictEqual(player.score, 6, "score is correct");
assert.strictEqual(player.evaluationsCount, 4, "getter evaluated");
assert.deepEqual([...player.letters], ["a", "b", "c", "d", "e", "f"]);
});
});