opencode/packages/quark/test/solid.test.ts
Kit Langton 1a28b03e05 refactor(tui): adopt reactive first index and read-only keyed surface
- quark: Collection.first(name) is now a stable Readable<A | undefined>;
  Keyed.ReadOnly names the read surface; useSlot takes plain constant keys;
  Layout.collectionOf<Target>() replaces the content plan double cast
- BackgroundToolHint reads a backgroundRunning first index instead of
  hand-tracking structure plus tool slots
- toolDisplay moves to util/tool-display (content.ts needs it for the index)
2026-07-21 16:25:49 -04:00

100 lines
2.9 KiB
TypeScript

import { describe, expect, it } from "bun:test"
import { createComputed, createRoot, createSignal } from "solid-js"
import { Computed, Keyed, State, Transaction, type Readable } from "../src"
import { useSlot, useValue } from "../src/solid"
describe("Solid adapter", () => {
it("bridges Quark values into Solid ownership", () => {
const left = State.make(1)
const right = State.make(2)
const total = Computed.make(() => left() + right())
const values: number[] = []
createRoot((dispose) => {
const value = useValue(total)
createComputed(() => values.push(value()))
Transaction.run(() => {
left.set(2)
right.set(3)
})
dispose()
})
left.set(4)
expect(values).toEqual([3, 5])
})
it("preserves function values", () => {
const first = () => 1
const second = () => 2
const state = State.make(first)
let value = first
createRoot((dispose) => {
const current = useValue(state)
createComputed(() => (value = current()))
state.set(second)
dispose()
})
expect(value).toBe(second)
})
it("accepts a plain constant key", () => {
const keyed = Keyed.make<{ readonly id: number; readonly value: string }, number>({ key: (value) => value.id })
keyed.set([{ id: 1, value: "one" }])
const values: Array<string | undefined> = []
createRoot((dispose) => {
const value = useSlot(keyed, 1)
createComputed(() => values.push(value()?.value))
keyed.modify(1, (item) => ({ ...item, value: "ONE" }))
dispose()
})
expect(values).toEqual(["one", "ONE"])
})
it("switches keyed slots and releases obsolete subscriptions", () => {
const keyed = Keyed.make<{ readonly id: number; readonly value: string }, number>({ key: (value) => value.id })
keyed.set([
{ id: 1, value: "one" },
{ id: 2, value: "two" },
])
const active = [0, 0]
track(keyed.get(1)!, 0)
track(keyed.get(2)!, 1)
const values: Array<string | undefined> = []
createRoot((dispose) => {
const [key, setKey] = createSignal(1)
const value = useSlot(keyed, key)
createComputed(() => values.push(value()?.value))
keyed.modify(1, (item) => ({ ...item, value: "ONE" }))
keyed.insert({ id: 3, value: "three" })
setKey(2)
keyed.modify(1, (item) => ({ ...item, value: "ignored" }))
keyed.modify(2, (item) => ({ ...item, value: "TWO" }))
expect(active).toEqual([0, 1])
dispose()
})
expect(active).toEqual([0, 0])
expect(values).toEqual(["one", "ONE", "two", "TWO"])
function track(slot: Readable<{ readonly id: number; readonly value: string }>, index: number) {
const subscribe = slot.subscribe.bind(slot)
slot.subscribe = (listener) => {
active[index]++
const dispose = subscribe(listener)
return () => {
active[index]--
dispose()
}
}
}
})
})