@@ -1348,6 +1348,294 @@ describe("Atom", () => {
13481348 const result = r . get ( atom )
13491349 expect ( Result . isInterrupted ( result ) ) . toBeTruthy ( )
13501350 } )
1351+
1352+ it ( "map" , ( ) => {
1353+ const r = Registry . make ( )
1354+ const state = Atom . make ( 1 )
1355+ const doubled = Atom . map ( state , ( n ) => n * 2 )
1356+
1357+ expect ( r . get ( doubled ) ) . toBe ( 2 )
1358+ r . set ( state , 5 )
1359+ expect ( r . get ( doubled ) ) . toBe ( 10 )
1360+ } )
1361+
1362+ it ( "mapResult" , ( ) => {
1363+ const r = Registry . make ( )
1364+ const effect = Atom . make ( Effect . succeed ( 42 ) )
1365+ const mapped = Atom . mapResult ( effect , ( n ) => n * 2 )
1366+
1367+ const result = r . get ( mapped )
1368+ expect ( Result . isSuccess ( result ) ) . toBe ( true )
1369+ if ( Result . isSuccess ( result ) ) {
1370+ expect ( result . value ) . toBe ( 84 )
1371+ }
1372+ } )
1373+
1374+ it ( "transform" , ( ) => {
1375+ const r = Registry . make ( )
1376+ const state = Atom . make ( 1 )
1377+ const derived = Atom . transform ( state , ( get ) => get ( state ) * 2 + 1 )
1378+
1379+ expect ( r . get ( derived ) ) . toBe ( 3 )
1380+ r . set ( state , 5 )
1381+ expect ( r . get ( derived ) ) . toBe ( 11 )
1382+ } )
1383+
1384+ it ( "debounce" , async ( ) => {
1385+ const r = Registry . make ( )
1386+ const state = Atom . make ( 0 )
1387+ const debounced = Atom . debounce ( state , "100 millis" )
1388+
1389+ // Mount the debounced atom first
1390+ const unmount = r . mount ( debounced )
1391+
1392+ let updates = 0
1393+ const cancel = r . subscribe ( debounced , ( ) => updates ++ )
1394+
1395+ r . set ( state , 1 )
1396+ r . set ( state , 2 )
1397+ r . set ( state , 3 )
1398+
1399+ expect ( updates ) . toBe ( 0 ) // Should not update immediately
1400+
1401+ await vitest . advanceTimersByTimeAsync ( 150 )
1402+ expect ( r . get ( debounced ) ) . toBe ( 3 )
1403+ expect ( updates ) . toBe ( 1 )
1404+
1405+ cancel ( )
1406+ unmount ( )
1407+ } )
1408+
1409+ it ( "writable direct" , ( ) => {
1410+ const r = Registry . make ( )
1411+ let value = 0
1412+
1413+ const atom = Atom . writable (
1414+ ( ) => value ,
1415+ ( _ , newValue : number ) => {
1416+ value = newValue
1417+ }
1418+ )
1419+
1420+ expect ( r . get ( atom ) ) . toBe ( 0 )
1421+ r . set ( atom , 42 )
1422+ r . refresh ( atom ) // Need to refresh to see the new value
1423+ expect ( r . get ( atom ) ) . toBe ( 42 )
1424+ expect ( value ) . toBe ( 42 )
1425+ } )
1426+
1427+ it ( "readable direct" , ( ) => {
1428+ const r = Registry . make ( )
1429+ let value = 0
1430+
1431+ const atom = Atom . readable ( ( ) => value )
1432+
1433+ expect ( r . get ( atom ) ) . toBe ( 0 )
1434+ value = 42
1435+ r . refresh ( atom ) // Need to refresh to see the new value
1436+ expect ( r . get ( atom ) ) . toBe ( 42 )
1437+ } )
1438+
1439+ it ( "effect failure" , ( ) => {
1440+ const r = Registry . make ( )
1441+ const failing = Atom . make ( Effect . fail ( "error" ) )
1442+
1443+ const result = r . get ( failing )
1444+ expect ( Result . isFailure ( result ) ) . toBe ( true )
1445+ if ( Result . isFailure ( result ) ) {
1446+ expect ( Cause . isFailType ( result . cause ) && result . cause . error ) . toBe ( "error" )
1447+ }
1448+ } )
1449+
1450+ it ( "effect failure with previousSuccess" , ( ) => {
1451+ const r = Registry . make ( )
1452+ const atom = Atom . fn ( ( shouldFail : boolean ) => shouldFail ? Effect . fail ( "error" ) : Effect . succeed ( 42 ) )
1453+ // First success
1454+ r . set ( atom , false )
1455+ let result = r . get ( atom )
1456+ expect ( Result . isSuccess ( result ) ) . toBe ( true )
1457+ if ( Result . isSuccess ( result ) ) {
1458+ expect ( result . value ) . toBe ( 42 )
1459+ }
1460+
1461+ // Then failure - should keep previous success
1462+ r . set ( atom , true )
1463+ result = r . get ( atom )
1464+ expect ( Result . isFailure ( result ) ) . toBe ( true )
1465+ const value = Result . value ( result )
1466+ expect ( Option . isSome ( value ) ) . toBe ( true )
1467+ if ( Option . isSome ( value ) ) {
1468+ expect ( value . value ) . toBe ( 42 )
1469+ }
1470+ } )
1471+
1472+ it ( "context.once" , ( ) => {
1473+ const r = Registry . make ( )
1474+ const state = Atom . make ( 1 )
1475+ let getCount = 0
1476+
1477+ const derived = Atom . make ( ( get ) => {
1478+ getCount ++
1479+ return get . once ( state ) * 2
1480+ } )
1481+
1482+ expect ( r . get ( derived ) ) . toBe ( 2 )
1483+ expect ( getCount ) . toBe ( 1 )
1484+
1485+ // Should not trigger rebuild on state change since we used once
1486+ r . set ( state , 5 )
1487+ expect ( r . get ( derived ) ) . toBe ( 2 ) // Still old value
1488+ expect ( getCount ) . toBe ( 1 ) // No rebuild
1489+ } )
1490+
1491+ it ( "context.mount" , ( ) => {
1492+ const r = Registry . make ( )
1493+ let mounted = false
1494+ let unmounted = false
1495+
1496+ const atom = Atom . make ( ( get ) => {
1497+ const other = Atom . make ( Effect . sync ( ( ) => {
1498+ mounted = true
1499+ return "value"
1500+ } ) )
1501+
1502+ get . addFinalizer ( ( ) => {
1503+ unmounted = true
1504+ } )
1505+
1506+ get . mount ( other )
1507+ return get ( other )
1508+ } )
1509+
1510+ const result = r . get ( atom )
1511+ expect ( Result . isSuccess ( result ) ) . toBe ( true )
1512+ expect ( mounted ) . toBe ( true )
1513+ expect ( unmounted ) . toBe ( false )
1514+ } )
1515+
1516+ it ( "context.refresh" , ( ) => {
1517+ const r = Registry . make ( )
1518+ let counter = 0
1519+ const state = Atom . make ( ( ) => ++ counter )
1520+
1521+ const derived = Atom . make ( ( get ) => {
1522+ get ( state ) // Access state first
1523+ get . refresh ( state ) // Force refresh of dependency
1524+ return get ( state ) // Get the refreshed value
1525+ } )
1526+
1527+ expect ( r . get ( derived ) ) . toBe ( 2 )
1528+ expect ( counter ) . toBe ( 2 ) // Should have been called twice due to refresh
1529+ } )
1530+
1531+ it ( "custom refresh function" , ( ) => {
1532+ const r = Registry . make ( )
1533+ let refreshCalled = false
1534+ const otherValue = "initial"
1535+
1536+ const otherAtom = Atom . make ( otherValue )
1537+ const atom = Atom . readable (
1538+ ( ) => "value" ,
1539+ ( refresh ) => {
1540+ refreshCalled = true
1541+ refresh ( otherAtom ) // Refresh a different atom to avoid recursion
1542+ }
1543+ )
1544+
1545+ r . get ( atom )
1546+ expect ( refreshCalled ) . toBe ( false )
1547+
1548+ r . refresh ( atom )
1549+ expect ( refreshCalled ) . toBe ( true )
1550+ } )
1551+
1552+ it ( "isAtom type guard" , ( ) => {
1553+ const atom = Atom . make ( 1 )
1554+ const notAtom = { value : 1 }
1555+
1556+ expect ( Atom . isAtom ( atom ) ) . toBe ( true )
1557+ expect ( Atom . isAtom ( notAtom ) ) . toBe ( false )
1558+ expect ( Atom . isAtom ( null ) ) . toBe ( false )
1559+ expect ( Atom . isAtom ( undefined ) ) . toBe ( false )
1560+ } )
1561+
1562+ it ( "isWritable type guard" , ( ) => {
1563+ const readable = Atom . readable ( ( ) => 1 )
1564+ const writable = Atom . make ( 1 )
1565+
1566+ expect ( Atom . isWritable ( readable ) ) . toBe ( false )
1567+ expect ( Atom . isWritable ( writable ) ) . toBe ( true )
1568+ } )
1569+
1570+ it ( "atom properties" , ( ) => {
1571+ const atom = Atom . make ( 1 )
1572+
1573+ expect ( atom . keepAlive ) . toBe ( false )
1574+ expect ( atom . lazy ) . toBe ( true )
1575+ expect ( typeof atom . read ) . toBe ( "function" )
1576+ } )
1577+
1578+ it ( "keepAlive modifier" , ( ) => {
1579+ const atom = Atom . make ( 1 )
1580+ const keepAliveAtom = Atom . keepAlive ( atom )
1581+
1582+ expect ( atom . keepAlive ) . toBe ( false )
1583+ expect ( keepAliveAtom . keepAlive ) . toBe ( true )
1584+ expect ( atom !== keepAliveAtom ) . toBe ( true ) // Should be new instance
1585+ } )
1586+
1587+ it ( "autoDispose modifier" , ( ) => {
1588+ const atom = Atom . keepAlive ( Atom . make ( 1 ) )
1589+ const autoDisposeAtom = Atom . autoDispose ( atom )
1590+
1591+ expect ( atom . keepAlive ) . toBe ( true )
1592+ expect ( autoDisposeAtom . keepAlive ) . toBe ( false )
1593+ } )
1594+
1595+ it ( "makeRefreshOnSignal" , ( ) => {
1596+ const r = Registry . make ( )
1597+ const signal = Atom . make ( 0 )
1598+ let computations = 0
1599+
1600+ const atom = Atom . make ( ( ) => {
1601+ computations ++
1602+ return "value"
1603+ } )
1604+
1605+ const refreshing = Atom . makeRefreshOnSignal ( signal ) ( atom )
1606+
1607+ expect ( r . get ( refreshing ) ) . toBe ( "value" )
1608+ expect ( computations ) . toBe ( 1 )
1609+
1610+ // Trigger signal - should cause refresh
1611+ r . set ( signal , 1 )
1612+ expect ( r . get ( refreshing ) ) . toBe ( "value" )
1613+ expect ( computations ) . toBe ( 2 ) // Should have recomputed
1614+ } )
1615+
1616+ it ( "Reset symbol" , ( ) => {
1617+ const r = Registry . make ( )
1618+ const atom = Atom . fn ( ( arg : number ) => Effect . succeed ( arg * 2 ) )
1619+
1620+ r . set ( atom , 5 )
1621+ const result1 = r . get ( atom )
1622+ expect ( Result . isSuccess ( result1 ) ) . toBe ( true )
1623+
1624+ r . set ( atom , Atom . Reset )
1625+ const result2 = r . get ( atom )
1626+ expect ( Result . isInitial ( result2 ) ) . toBe ( true )
1627+ } )
1628+
1629+ it ( "Interrupt symbol" , ( ) => {
1630+ const r = Registry . make ( )
1631+ const atom = Atom . fn ( ( arg : number ) => Effect . delay ( Effect . succeed ( arg * 2 ) , "100 millis" ) )
1632+ r . set ( atom , 5 )
1633+ const result1 = r . get ( atom )
1634+ expect ( Result . isWaiting ( result1 ) ) . toBe ( true )
1635+
1636+ r . set ( atom , Atom . Interrupt )
1637+ // Should interrupt the running effect
1638+ } )
13511639} )
13521640
13531641interface BuildCounter {
0 commit comments