Skip to content

Commit 26157ef

Browse files
committed
test: add more unit tests for Atom functionalities
1 parent ff078cb commit 26157ef

File tree

1 file changed

+235
-0
lines changed

1 file changed

+235
-0
lines changed

packages/atom/test/Atom.test.ts

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1422,6 +1422,241 @@ describe("Atom", () => {
14221422

14231423
unmount2()
14241424
})
1425+
1426+
it("map", () => {
1427+
const r = Registry.make()
1428+
const state = Atom.make(1)
1429+
const doubled = Atom.map(state, (n) => n * 2)
1430+
1431+
expect(r.get(doubled)).toBe(2)
1432+
r.set(state, 5)
1433+
expect(r.get(doubled)).toBe(10)
1434+
})
1435+
1436+
it("mapResult", () => {
1437+
const r = Registry.make()
1438+
const effect = Atom.make(Effect.succeed(42))
1439+
const mapped = Atom.mapResult(effect, (n) => n * 2)
1440+
1441+
const result = r.get(mapped)
1442+
assert(Result.isSuccess(result))
1443+
expect(result.value).toBe(84)
1444+
})
1445+
1446+
it("transform", () => {
1447+
const r = Registry.make()
1448+
const state = Atom.make(1)
1449+
const derived = Atom.transform(state, (get) => get(state) * 2 + 1)
1450+
1451+
expect(r.get(derived)).toBe(3)
1452+
r.set(state, 5)
1453+
expect(r.get(derived)).toBe(11)
1454+
})
1455+
1456+
it("debounce", async () => {
1457+
const r = Registry.make()
1458+
const state = Atom.make(0)
1459+
const debounced = Atom.debounce(state, "100 millis")
1460+
1461+
// Mount the debounced atom first
1462+
const unmount = r.mount(debounced)
1463+
1464+
let updates = 0
1465+
const cancel = r.subscribe(debounced, () => updates++)
1466+
1467+
r.set(state, 1)
1468+
r.set(state, 2)
1469+
r.set(state, 3)
1470+
1471+
expect(updates).toBe(0) // Should not update immediately
1472+
1473+
await vitest.advanceTimersByTimeAsync(150)
1474+
expect(r.get(debounced)).toBe(3)
1475+
expect(updates).toBe(1)
1476+
1477+
cancel()
1478+
unmount()
1479+
})
1480+
1481+
it("effect failure", () => {
1482+
const r = Registry.make()
1483+
const failing = Atom.make(Effect.fail("error"))
1484+
1485+
const result = r.get(failing)
1486+
assert(Result.isFailure(result))
1487+
assert(Cause.isFailType(result.cause))
1488+
expect(result.cause.error).toBe("error")
1489+
})
1490+
1491+
it("effect failure with previousSuccess", () => {
1492+
const r = Registry.make()
1493+
const atom = Atom.fn((shouldFail: boolean) => shouldFail ? Effect.fail("error") : Effect.succeed(42))
1494+
// First success
1495+
r.set(atom, false)
1496+
let result = r.get(atom)
1497+
assert(Result.isSuccess(result))
1498+
expect(result.value).toBe(42)
1499+
1500+
// Then failure - should keep previous success
1501+
r.set(atom, true)
1502+
result = r.get(atom)
1503+
assert(Result.isFailure(result))
1504+
const value = Result.value(result)
1505+
assert(Option.isSome(value))
1506+
expect(value.value).toBe(42)
1507+
})
1508+
1509+
it("context.once", () => {
1510+
const r = Registry.make()
1511+
const state = Atom.make(1)
1512+
let getCount = 0
1513+
1514+
const derived = Atom.make((get) => {
1515+
getCount++
1516+
return get.once(state) * 2
1517+
})
1518+
1519+
expect(r.get(derived)).toBe(2)
1520+
expect(getCount).toBe(1)
1521+
1522+
// Should not trigger rebuild on state change since we used once
1523+
r.set(state, 5)
1524+
expect(r.get(derived)).toBe(2) // Still old value
1525+
expect(getCount).toBe(1) // No rebuild
1526+
})
1527+
1528+
it("context.refresh", () => {
1529+
const r = Registry.make()
1530+
let counter = 0
1531+
const state = Atom.make(() => ++counter)
1532+
const other = Atom.make(() => counter * 10)
1533+
1534+
const derived = Atom.make((get) => {
1535+
const stateValue = get(state)
1536+
get.refresh(other) // Refresh a different atom
1537+
return stateValue
1538+
})
1539+
1540+
expect(r.get(derived)).toBe(1)
1541+
expect(counter).toBe(1)
1542+
})
1543+
1544+
it("custom refresh function", () => {
1545+
const r = Registry.make()
1546+
let refreshCalled = false
1547+
let otherRefreshed = false
1548+
const otherAtom = Atom.readable(
1549+
() => "other",
1550+
() => {
1551+
otherRefreshed = true
1552+
}
1553+
)
1554+
1555+
const atom = Atom.readable(
1556+
() => "value",
1557+
(refresh) => {
1558+
refreshCalled = true
1559+
refresh(otherAtom)
1560+
}
1561+
)
1562+
1563+
r.get(atom)
1564+
expect(refreshCalled).toBe(false)
1565+
expect(otherRefreshed).toBe(false)
1566+
1567+
r.refresh(atom)
1568+
expect(refreshCalled).toBe(true)
1569+
expect(otherRefreshed).toBe(true)
1570+
})
1571+
1572+
it("isAtom type guard", () => {
1573+
const atom = Atom.make(1)
1574+
const notAtom = { value: 1 }
1575+
1576+
expect(Atom.isAtom(atom)).toBe(true)
1577+
expect(Atom.isAtom(notAtom)).toBe(false)
1578+
expect(Atom.isAtom(null)).toBe(false)
1579+
expect(Atom.isAtom(undefined)).toBe(false)
1580+
})
1581+
1582+
it("isWritable type guard", () => {
1583+
const readable = Atom.readable(() => 1)
1584+
const writable = Atom.make(1)
1585+
1586+
expect(Atom.isWritable(readable)).toBe(false)
1587+
expect(Atom.isWritable(writable)).toBe(true)
1588+
})
1589+
1590+
it("atom properties", () => {
1591+
const atom = Atom.make(1)
1592+
1593+
expect(atom.keepAlive).toBe(false)
1594+
expect(atom.lazy).toBe(true)
1595+
expect(typeof atom.read).toBe("function")
1596+
})
1597+
1598+
it("keepAlive modifier", () => {
1599+
const atom = Atom.make(1)
1600+
const keepAliveAtom = Atom.keepAlive(atom)
1601+
1602+
expect(atom.keepAlive).toBe(false)
1603+
expect(keepAliveAtom.keepAlive).toBe(true)
1604+
expect(atom !== keepAliveAtom).toBe(true) // Should be new instance
1605+
})
1606+
1607+
it("autoDispose modifier", () => {
1608+
const atom = Atom.keepAlive(Atom.make(1))
1609+
const autoDisposeAtom = Atom.autoDispose(atom)
1610+
1611+
expect(atom.keepAlive).toBe(true)
1612+
expect(autoDisposeAtom.keepAlive).toBe(false)
1613+
})
1614+
1615+
it("makeRefreshOnSignal", () => {
1616+
const r = Registry.make()
1617+
const signal = Atom.make(0)
1618+
let computations = 0
1619+
1620+
const atom = Atom.make(() => {
1621+
computations++
1622+
return "value"
1623+
})
1624+
1625+
const refreshing = Atom.makeRefreshOnSignal(signal)(atom)
1626+
1627+
expect(r.get(refreshing)).toBe("value")
1628+
expect(computations).toBe(1)
1629+
1630+
// Trigger signal - should cause refresh
1631+
r.set(signal, 1)
1632+
expect(r.get(refreshing)).toBe("value")
1633+
expect(computations).toBe(2) // Should have recomputed
1634+
})
1635+
1636+
it("Reset symbol", () => {
1637+
const r = Registry.make()
1638+
const atom = Atom.fn((arg: number) => Effect.succeed(arg * 2))
1639+
1640+
r.set(atom, 5)
1641+
const result1 = r.get(atom)
1642+
expect(Result.isSuccess(result1)).toBe(true)
1643+
1644+
r.set(atom, Atom.Reset)
1645+
const result2 = r.get(atom)
1646+
expect(Result.isInitial(result2)).toBe(true)
1647+
})
1648+
1649+
it("Interrupt symbol", () => {
1650+
const r = Registry.make()
1651+
const atom = Atom.fn((arg: number) => Effect.delay(Effect.succeed(arg * 2), "100 millis"))
1652+
r.set(atom, 5)
1653+
const result1 = r.get(atom)
1654+
expect(Result.isWaiting(result1)).toBe(true)
1655+
1656+
r.set(atom, Atom.Interrupt)
1657+
const result2 = r.get(atom)
1658+
expect(Result.isInterrupted(result2)).toBe(true)
1659+
})
14251660
})
14261661

14271662
interface BuildCounter {

0 commit comments

Comments
 (0)