@@ -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
14271662interface BuildCounter {
0 commit comments