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