Skip to content

Commit bd2bd82

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

File tree

1 file changed

+243
-0
lines changed

1 file changed

+243
-0
lines changed

packages/atom/test/Atom.test.ts

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

14271670
interface BuildCounter {

0 commit comments

Comments
 (0)