Skip to content

Commit 206694f

Browse files
committed
test: add tests for subscriptionRef recomputation with derived state
Add tests to verify subscriptionRef atom behavior: 1. Test that subscriptionRef atoms properly recompute when their dependencies change (using get.some() to access Option atoms). 2. Test demonstrating that using multiple registries (i.e., not wrapping the app with RegistryProvider) causes atoms to not see each other's updates. This explains the user-reported issue where subscriptionRef atoms don't recompute - if there's no shared registry context, different parts of the app may use different registry instances.
1 parent 91aac3e commit 206694f

File tree

1 file changed

+130
-0
lines changed

1 file changed

+130
-0
lines changed

packages/atom/test/Atom.test.ts

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1446,6 +1446,136 @@ describe("Atom", () => {
14461446

14471447
unmount2()
14481448
})
1449+
1450+
it("subscriptionRef with runtime recomputes when dependencies change", async () => {
1451+
vitest.useRealTimers()
1452+
1453+
// Test that subscriptionRef atoms properly recompute when their dependencies change
1454+
// This test was added based on a user report that subscriptionRef atoms don't recompute
1455+
// when dependencies accessed via get.some() change.
1456+
const chatIdAtom = Atom.make<Option.Option<string>>(Option.none()).pipe(Atom.keepAlive)
1457+
1458+
// A regular derived atom using Effect.fnUntraced
1459+
let derivedRecomputes = 0
1460+
const derivedStateAtom = counterRuntime.atom(
1461+
Effect.fnUntraced(function*(get: Atom.Context) {
1462+
const chatId = yield* get.some(chatIdAtom)
1463+
derivedRecomputes++
1464+
return chatId
1465+
})
1466+
)
1467+
1468+
// A subscriptionRef atom that also depends on chatIdAtom
1469+
let subRefRecomputes = 0
1470+
const stateAtom = counterRuntime.subscriptionRef((get) =>
1471+
Effect.gen(function*() {
1472+
const chatId = yield* get.some(chatIdAtom)
1473+
subRefRecomputes++
1474+
return yield* SubscriptionRef.make(chatId)
1475+
})
1476+
)
1477+
1478+
const r = Registry.make()
1479+
const unmountDerived = r.mount(derivedStateAtom)
1480+
const unmountSubRef = r.mount(stateAtom)
1481+
1482+
// Initially, chatIdAtom is None, so both should fail with NoSuchElementException
1483+
let derivedResult = r.get(derivedStateAtom)
1484+
let subRefResult = r.get(stateAtom)
1485+
expect(derivedRecomputes).toEqual(0)
1486+
expect(subRefRecomputes).toEqual(0)
1487+
1488+
// Set chatIdAtom to Some("chat-1")
1489+
r.set(chatIdAtom, Option.some("chat-1"))
1490+
await new Promise((resolve) => resolve(null))
1491+
1492+
derivedResult = r.get(derivedStateAtom)
1493+
subRefResult = r.get(stateAtom)
1494+
1495+
assert(Result.isSuccess(derivedResult))
1496+
expect(derivedResult.value).toEqual("chat-1")
1497+
expect(derivedRecomputes).toEqual(1)
1498+
1499+
assert(Result.isSuccess(subRefResult))
1500+
expect(subRefResult.value).toEqual("chat-1")
1501+
expect(subRefRecomputes).toEqual(1)
1502+
1503+
// Change chatIdAtom to Some("chat-2") - both atoms should recompute
1504+
r.set(chatIdAtom, Option.some("chat-2"))
1505+
await new Promise((resolve) => resolve(null))
1506+
1507+
derivedResult = r.get(derivedStateAtom)
1508+
subRefResult = r.get(stateAtom)
1509+
1510+
// The derived atom should have recomputed
1511+
assert(Result.isSuccess(derivedResult))
1512+
expect(derivedResult.value).toEqual("chat-2")
1513+
expect(derivedRecomputes).toEqual(2)
1514+
1515+
// The subscriptionRef atom should ALSO have recomputed
1516+
assert(Result.isSuccess(subRefResult))
1517+
expect(subRefResult.value).toEqual("chat-2")
1518+
expect(subRefRecomputes).toEqual(2)
1519+
1520+
unmountDerived()
1521+
unmountSubRef()
1522+
})
1523+
1524+
it("subscriptionRef fails with multiple registries (no shared context)", async () => {
1525+
vitest.useRealTimers()
1526+
1527+
// This test simulates what happens when there's no RegistryProvider wrapping the app.
1528+
// Different parts of the app might end up using different registries, which could
1529+
// cause atoms to not see each other's updates.
1530+
const chatIdAtom = Atom.make<Option.Option<string>>(Option.none()).pipe(Atom.keepAlive)
1531+
1532+
// A regular derived atom using Effect.fnUntraced
1533+
let derivedRecomputes = 0
1534+
const derivedStateAtom = counterRuntime.atom(
1535+
Effect.fnUntraced(function*(get: Atom.Context) {
1536+
const chatId = yield* get.some(chatIdAtom)
1537+
derivedRecomputes++
1538+
return chatId
1539+
})
1540+
)
1541+
1542+
// A subscriptionRef atom that also depends on chatIdAtom
1543+
let subRefRecomputes = 0
1544+
const stateAtom = counterRuntime.subscriptionRef((get) =>
1545+
Effect.gen(function*() {
1546+
const chatId = yield* get.some(chatIdAtom)
1547+
subRefRecomputes++
1548+
return yield* SubscriptionRef.make(chatId)
1549+
})
1550+
)
1551+
1552+
// Simulate the problematic scenario: using different registries
1553+
// This is what happens when there's no RegistryProvider - each useAtom call
1554+
// might get a different registry instance
1555+
const r1 = Registry.make() // Registry for chatIdAtom updates
1556+
const r2 = Registry.make() // Registry for derivedStateAtom
1557+
const r3 = Registry.make() // Registry for stateAtom
1558+
1559+
// Mount atoms in their respective registries
1560+
const unmountDerived = r2.mount(derivedStateAtom)
1561+
const unmountSubRef = r3.mount(stateAtom)
1562+
1563+
// Set chatIdAtom in r1 - this update won't propagate to r2 and r3!
1564+
r1.set(chatIdAtom, Option.some("chat-1"))
1565+
await new Promise((resolve) => resolve(null))
1566+
1567+
// The atoms in r2 and r3 won't see the update because they're using different registries
1568+
// This demonstrates why RegistryProvider is important
1569+
r2.get(derivedStateAtom)
1570+
r3.get(stateAtom)
1571+
1572+
// Both atoms should still be in their initial state because they didn't see the update
1573+
expect(derivedRecomputes).toEqual(0)
1574+
expect(subRefRecomputes).toEqual(0)
1575+
1576+
unmountDerived()
1577+
unmountSubRef()
1578+
})
14491579
})
14501580

14511581
interface BuildCounter {

0 commit comments

Comments
 (0)