Skip to content

Instantly share code, notes, and snippets.

@wycats
Created April 6, 2024 23:11
Show Gist options
  • Save wycats/e23acaa30a66c89c10cbbef227da296f to your computer and use it in GitHub Desktop.
Save wycats/e23acaa30a66c89c10cbbef227da296f to your computer and use it in GitHub Desktop.
import { CachedFormula, Cell, Formula } from "@starbeam-lite/core";
import { EventRecorder, TestScheduler } from "@workspace/test-utils";
import { describe, expect, it } from "vitest";
import { subscribe } from "../src/subtle";
import { TAG } from "@starbeam-lite/shared";
describe("subscribe", () => {
describe("equivalent to Signal.subtle.Watcher (ported tests)", () => {
it("should work", () => {
const events = new EventRecorder();
const scheduler = new TestScheduler();
const cell = Cell.create(1);
cell.set(100);
cell.set(5);
const formula = Formula.create(() => cell.read() * 2);
// This represents an external stateful sink that is outside of the
// reactivity system.
const sink = {
cell: null as number | null,
formula: null as number | null,
};
const sync = SyncOut(() => {
sink.cell = cell.read();
sink.formula = formula.read();
events.record("SyncOut");
});
// Schedule the initial sync to happen on the next flush. In a real-world
// integration, this would be used to schedule the flush to happen in a
// framework-appropriate timing (something like `useEffect` or
// `onMounted`).
scheduler.schedule(sync.read);
// Since the scheduler has not flushed yet, there should be no recorded
// events yet.
events.expect([]);
// Flush the schedule. This is a stand-in for a framework's internal
// scheduling of after mount callbacks.
scheduler.flush();
events.expect("SyncOut");
// The sink has not yet been updated.
expect(sink).toEqual({
cell: 5,
formula: 10,
});
const unsubscribe = subscribe(sync[TAG], () => {
events.record("ready");
// Use the test scheduler to accumulate `sync.read()` as a pending
// event. In a real-world integration, this would be used to schedule
// the flush to happen in a framework-appropriate timing (something like
// `useEffect` or `onBeforeUpdate`).
scheduler.schedule(sync.read);
});
events.expect([]);
// Updating the cell should trigger the `ready` event, which will schedule
// the sync, but not yet run it.
cell.set(10);
events.expect("ready");
// but the *formula* is always up to date
expect(formula.read()).toBe(20);
expect(sink).toEqual({
cell: 5,
formula: 10,
});
// Flush the scheduler. This will run the sync and update the sink.
scheduler.flush();
events.expect("SyncOut");
// the *formula* is still up to date
expect(formula.read()).toBe(20);
expect(sink).toEqual({
cell: 10,
formula: 20,
});
cell.set(20);
events.expect("ready");
// the *formula* is always up to date
expect(formula.read()).toBe(40);
expect(sink).toEqual({
cell: 10,
formula: 20,
});
});
});
});
function SyncOut(flush: () => void): CachedFormula<void> {
return CachedFormula.create(() => {
// in principle, this should have a write barrier
flush();
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment