@marimo-team/islands 0.25.3-dev28 → 0.25.3-dev281

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (840) hide show
  1. package/dist/{ConnectedDataExplorerComponent-7EaP9VWb.js → ConnectedDataExplorerComponent-CErm132k.js} +116 -113
  2. package/dist/{ErrorBoundary-CUdSBv06.js → ErrorBoundary-fSq1YI-S.js} +76 -82
  3. package/dist/_baseSet-BfI5FjBM.js +328 -0
  4. package/dist/{abnfDiagram-VCTEODGH-z_OSynK8.js → abnfDiagram-VCTEODGH-d-Qn2fwb.js} +2 -2
  5. package/dist/any-language-editor-6k6F12Z3.js +75 -0
  6. package/dist/{architectureDiagram-5GKGNRK7-Cs8J8BZr.js → architectureDiagram-5GKGNRK7-DxragHYv.js} +1 -1
  7. package/dist/{arrays-CN1djC4k.js → arrays-DoQ-AILx.js} +15 -27
  8. package/dist/assertNever-B-YzWzbL.js +507 -0
  9. package/dist/assets/__vite-browser-external-BKBpimif.js +1 -0
  10. package/dist/assets/worker-CgoMtD-_.js +113 -0
  11. package/dist/{blockDiagram-I7D4REHJ-Cg25UrxT.js → blockDiagram-I7D4REHJ-Dkhe6_PP.js} +1 -1
  12. package/dist/{button-CvjkwdSk.js → button-DZuNnIZO.js} +56 -48
  13. package/dist/{capabilities-CQ5IjhzL.js → capabilities-D9FskwsK.js} +2 -2
  14. package/dist/{chat-ui-BzjAsTFN.js → chat-ui-ByGe2rP1.js} +2100 -1810
  15. package/dist/{check-BMKBBrdc.js → check-XFzZeH-y.js} +1 -1
  16. package/dist/{chunk-IMKFNOWR-aoSFasqF.js → chunk-IMKFNOWR-C0x0feFg.js} +1 -1
  17. package/dist/{chunk-SHT3W25Y-CMaybO3O.js → chunk-SHT3W25Y-D0ZdASqB.js} +1 -1
  18. package/dist/{chunk-SVP7TREG-BNBrTAw1.js → chunk-SVP7TREG-BHjL96zx.js} +1 -1
  19. package/dist/{chunk-TICWLB2K-DXNxvUJn.js → chunk-TICWLB2K-CzcrwGfp.js} +1 -1
  20. package/dist/{classDiagram-ZZMXUADV-DXdCCDOg.js → classDiagram-ZZMXUADV-BTqFAZTP.js} +2 -2
  21. package/dist/{classDiagram-v2-VYDZK3BY-DyZqrtLP.js → classDiagram-v2-VYDZK3BY-EPXb0Elt.js} +2 -2
  22. package/dist/{code-visibility-BuxqA3v5.js → code-visibility-BhtW7mrV.js} +43 -50
  23. package/dist/{common-xtZHxsM2.js → common-BIZJLLSc.js} +10605 -10045
  24. package/dist/config-C8MFHQ2E.js +637 -0
  25. package/dist/{copy-icon-BgUO8jsi.js → copy-icon-CtllOdmg.js} +3 -3
  26. package/dist/createReducer-DRqwmaTc.js +67 -0
  27. package/dist/{cynefinDiagram-5FMLGOSQ-DWHuKJs8.js → cynefinDiagram-5FMLGOSQ-CBSD344q.js} +1 -1
  28. package/dist/{dagre-GXQ25YYZ-DYLu-OCU.js → dagre-GXQ25YYZ-JItjh3Ge.js} +3 -3
  29. package/dist/{dagre-BGSfXNEb.js → dagre-mqiMAsFY.js} +531 -680
  30. package/dist/{data-grid-overlay-editor-qnbf5J94.js → data-grid-overlay-editor-B8b_mHmv.js} +1 -1
  31. package/dist/{debounce-C3jyATpc.js → debounce-DrhhziJv.js} +2 -2
  32. package/dist/{diagram-S7CK7UJ4-PwwcUOHP.js → diagram-S7CK7UJ4-CTD9xTVb.js} +1 -1
  33. package/dist/{diagram-UQ7AKVKN-CvJbL_IM.js → diagram-UQ7AKVKN-DTfnFai8.js} +1 -1
  34. package/dist/{diagram-VX7I27RA-B5RkqnzN.js → diagram-VX7I27RA-BcSnucV-.js} +1 -1
  35. package/dist/{diagram-Z3DM3KII-CyijwTR2.js → diagram-Z3DM3KII-C2ATIE8A.js} +1 -1
  36. package/dist/{dialog-Cc479ZEH.js → dialog-CaYm5imE.js} +32 -32
  37. package/dist/{dist-Bvs24R_E.js → dist-11T5wq3_.js} +31 -30
  38. package/dist/{dist-F8Ep36rk.js → dist-17ugwGZc.js} +59 -58
  39. package/dist/{dist-BMQf5zIS.js → dist-7cRjf--y.js} +123 -122
  40. package/dist/{dist-diIx5qJW.js → dist-B8pkfkzc.js} +31 -30
  41. package/dist/{dist-CN_BeG2I.js → dist-B9UoM_wq.js} +58 -57
  42. package/dist/{dist-CLdpGQVr.js → dist-BGuMTs1i.js} +118 -117
  43. package/dist/{dist-BK4Oj99o.js → dist-BHW11fni.js} +18 -17
  44. package/dist/dist-BLY4m1Wi.js +850 -0
  45. package/dist/{dist-PXYI_yuP.js → dist-BNlcVoxs.js} +24 -23
  46. package/dist/{dist-D6d86eNJ.js → dist-BQYe5Bju.js} +47 -46
  47. package/dist/{dist-CkTTIdBe.js → dist-BX1mn8na.js} +337 -337
  48. package/dist/{dist-DiDibOYr.js → dist-Bh0PTKUW.js} +151 -150
  49. package/dist/{dist-CSJ0MDRw.js → dist-CKaXK92p.js} +56 -55
  50. package/dist/{dist-C8fAKSH1.js → dist-CLhwfhpJ.js} +53 -52
  51. package/dist/{dist-DoE4XJBC.js → dist-CYeqK4Xw.js} +9801 -10649
  52. package/dist/{dist-BLfKmR-l.js → dist-Cxlp3WH6.js} +16 -15
  53. package/dist/{dist-D-wwqlzJ.js → dist-DSJPGj42.js} +158 -157
  54. package/dist/{dist-BorMD-uX.js → dist-DU9ICsWZ.js} +75 -74
  55. package/dist/{dist-DBtSl3sf.js → dist-DWv0ALBi.js} +47 -46
  56. package/dist/{dist-OR1OnN-y.js → dist-DiBpWEsB.js} +31 -30
  57. package/dist/{dist-CrSP_H1M.js → dist-DiLFTlnl.js} +242 -242
  58. package/dist/{dist-CN8zI-XC.js → dist-X9CsotJl.js} +239 -239
  59. package/dist/{dist-CSbh1PNP.js → dist-cVDV5IuI.js} +23 -22
  60. package/dist/{dist-BuaRHxOc.js → dist-fRfaOoq8.js} +44 -43
  61. package/dist/{dist-Cg7gzw-a.js → dist-gTTI-9Px.js} +78 -77
  62. package/dist/{dropdown-menu-BuE2b8Yb.js → dropdown-menu-Bb0bFOIk.js} +2904 -1493
  63. package/dist/{ebnfDiagram-PWID7BFC-DG90ELFp.js → ebnfDiagram-PWID7BFC-B7B9JPxc.js} +2 -2
  64. package/dist/{erDiagram-RLTQ6QDP-FMo_SpyX.js → erDiagram-RLTQ6QDP-v6hDno1O.js} +1 -1
  65. package/dist/{error-banner-CFPHFw4h.js → error-banner-CHCdHVFB.js} +87 -87
  66. package/dist/error-entries-9U4BFHjf.js +2586 -0
  67. package/dist/{esm-AMFssoNp.js → esm-BDqqT86j.js} +329 -329
  68. package/dist/{esm-D2gTsXEV.js → esm-BgKjMvWw.js} +530 -529
  69. package/dist/feedback-button-CY92zeC6.js +374 -0
  70. package/dist/{flowDiagram-HODETNUW-DQ4JuadE.js → flowDiagram-HODETNUW-aIFlAtOy.js} +2 -2
  71. package/dist/{formats-Bv_5N2Ff.js → formats-PzBQMUnr.js} +6 -6
  72. package/dist/{glide-data-editor-DQ_cEi05.js → glide-data-editor-BdgfR8uu.js} +6630 -2846
  73. package/dist/{glide-utils-pisbYVjH.js → glide-utils-BSQr4d90.js} +107 -76
  74. package/dist/{globals-D7vITxEX.js → globals-CjnqsurJ.js} +3 -3
  75. package/dist/{graphlib-BmY_CCV9.js → graphlib-nlwT3Wvy.js} +177 -220
  76. package/dist/hasIn-C20JsQ2m.js +28 -0
  77. package/dist/{error-entries-j85LP88u.js → html-to-image-BYh_MorV.js} +26294 -26451
  78. package/dist/{infoDiagram-27XIBGKW-CRSDtRrd.js → infoDiagram-27XIBGKW-D6YHLeCP.js} +1 -1
  79. package/dist/isArrayLikeObject-B-HXCsex.js +626 -0
  80. package/dist/isEmpty-CdX7MTE1.js +14 -0
  81. package/dist/{ishikawaDiagram-5VMMS53U-DxLEUldB.js → ishikawaDiagram-5VMMS53U-D1ZvprOX.js} +1 -1
  82. package/dist/{kanban-definition-UXKFOSKX-BLhvu8o4.js → kanban-definition-UXKFOSKX-Bna_3BMr.js} +1 -1
  83. package/dist/{loader-BPdQhmlq.js → loader-DZvtzQoD.js} +3 -3
  84. package/dist/main.js +10276 -8171
  85. package/dist/{mermaid-Bjm6C1qN.js → mermaid-A36unKXJ.js} +62 -62
  86. package/dist/{mindmap-definition-YA3MSWOX-g2LwxnYQ.js → mindmap-definition-YA3MSWOX-CyVCs__G.js} +1 -1
  87. package/dist/{now-jM8-M7IR.js → now-BKZCLE5P.js} +12 -13
  88. package/dist/{number-overlay-editor-DJHB-CGK.js → number-overlay-editor-BQBhWG2O.js} +1 -1
  89. package/dist/{objects-CzUgoMIF.js → objects-DCmMvZtn.js} +1 -3
  90. package/dist/{once-BtE-wKRL.js → once-NnpX_HnX.js} +1 -1
  91. package/dist/{pegDiagram-XKGWAZYB-CpMAt3Qs.js → pegDiagram-XKGWAZYB-9hNBD2wZ.js} +2 -2
  92. package/dist/pencil-DYUlSKxZ.js +10 -0
  93. package/dist/pick-tCszMUbm.js +35 -0
  94. package/dist/{pieDiagram-E7YTZNPT-CVTthoZo.js → pieDiagram-E7YTZNPT-8tr-NYo7.js} +1 -1
  95. package/dist/{process-output-DGzGHArA.js → process-output-bS3MGJ3f.js} +4 -4
  96. package/dist/{railroadDiagram-O6MQD6OU-ao8cqqFB.js → railroadDiagram-O6MQD6OU-Dzg1dyyp.js} +2 -2
  97. package/dist/{react-vega-BDpbvBCb.js → react-vega-C4T9Rb_w.js} +1 -1
  98. package/dist/{requirementDiagram-BXWQKSXE--J8vyfm-.js → requirementDiagram-BXWQKSXE-B7sNqwGG.js} +1 -1
  99. package/dist/{reveal-component-BxYUpAPg.js → reveal-component-BqyL-_xE.js} +1151 -1128
  100. package/dist/select-DsoP1qe8.js +1200 -0
  101. package/dist/{sequenceDiagram-WJ2MYXX4-CiQbn62x.js → sequenceDiagram-WJ2MYXX4-oP9shbaG.js} +1 -1
  102. package/dist/{spec-D_u2ipzF.js → spec-BZMDxAu7.js} +41 -11
  103. package/dist/{stateDiagram-D77RDMKH-DVEeGs5b.js → stateDiagram-D77RDMKH-CjCv4ogv.js} +4 -4
  104. package/dist/{stateDiagram-v2-MP3YSRHH-1OQRe6iA.js → stateDiagram-v2-MP3YSRHH-BW9X162a.js} +2 -2
  105. package/dist/static-state-D-Q-Ajaa.js +72 -0
  106. package/dist/style.css +1 -1
  107. package/dist/{swimlanes-42K2YHIH-CqkGoGqn.js → swimlanes-42K2YHIH-Ckfs9Wrn.js} +1 -1
  108. package/dist/{swimlanesDiagram-VR7AAH4N-CszjkrKo.js → swimlanesDiagram-VR7AAH4N-DpTNG9YP.js} +2 -2
  109. package/dist/{swiper-component-DKntyn8p.js → swiper-component-DQbK_ZWm.js} +7 -7
  110. package/dist/{timeline-definition-24CTP7MA-DLRUNVAx.js → timeline-definition-24CTP7MA-BXaBlV6U.js} +1 -1
  111. package/dist/toast-BhjIy3Tq.js +491 -0
  112. package/dist/{tooltip-CtBZ5q8p.js → tooltip-DMMiOn2f.js} +321 -297
  113. package/dist/use-restore-focus-CosX7-gx.js +7568 -0
  114. package/dist/{useAsyncData-BneN349z.js → useAsyncData-Ci4AvJff.js} +18 -17
  115. package/dist/{useDateFormatter-DkajGwwd.js → useDateFormatter-CPlava52.js} +2 -2
  116. package/dist/{useDeepCompareMemoize-Z_7R8wTt.js → useDeepCompareMemoize-NaaWoFlX.js} +1 -1
  117. package/dist/useEvent-BRH-aCqi.js +19 -0
  118. package/dist/{useIframeCapabilities-DBETvvPl.js → useIframeCapabilities-BXhDDoCG.js} +1 -1
  119. package/dist/useLifecycle-SniivYmC.js +112 -0
  120. package/dist/{vega-component-DPd52jvr.js → vega-component-DIxaDQNs.js} +161 -160
  121. package/dist/{vega-embed-container-BgKnsDbB.js → vega-embed-container--LYZqguO.js} +2 -2
  122. package/dist/{vega-loader.browser-CpbZAXub.js → vega-loader.browser-B9Pz0EGP.js} +1 -1
  123. package/dist/{vennDiagram-4TSXK5OY-CypgoBVZ.js → vennDiagram-4TSXK5OY-C-hQ4iNJ.js} +1 -1
  124. package/dist/{wardleyDiagram-VM6X3IG4-B6xjiQ_g.js → wardleyDiagram-VM6X3IG4-C2MQ_Umv.js} +1 -1
  125. package/dist/{xychartDiagram-S5SC5T6Z-e0PefDVs.js → xychartDiagram-S5SC5T6Z-BtTTBn7Q.js} +1 -1
  126. package/package.json +16 -20
  127. package/src/__mocks__/document.tsx +63 -0
  128. package/src/__mocks__/executions.ts +197 -0
  129. package/src/__mocks__/notebook.ts +3 -7
  130. package/src/__mocks__/partial.ts +26 -0
  131. package/src/__mocks__/requests.ts +1 -4
  132. package/src/__tests__/CellStatus.test.tsx +25 -26
  133. package/src/__tests__/fixtures/pair-config.ts +1 -1
  134. package/src/__tests__/main.test.tsx +24 -1
  135. package/src/__tests__/module-entry.test.ts +44 -0
  136. package/src/__tests__/mount.test.ts +1 -21
  137. package/src/__tests__/{host-imports.test.ts → replica-imports.test.ts} +12 -11
  138. package/src/components/ai/__tests__/ai-model-dropdown.test.tsx +63 -0
  139. package/src/components/ai/ai-model-dropdown.tsx +11 -6
  140. package/src/components/app-config/__tests__/state.test.tsx +50 -0
  141. package/src/components/app-config/ai-config.tsx +156 -150
  142. package/src/components/app-config/app-config-button.tsx +6 -5
  143. package/src/components/app-config/app-config-form.tsx +5 -9
  144. package/src/components/app-config/common.tsx +7 -9
  145. package/src/components/app-config/data-form.tsx +112 -113
  146. package/src/components/app-config/mcp-config.tsx +30 -53
  147. package/src/components/app-config/user-config-form.css +91 -0
  148. package/src/components/app-config/user-config-form.tsx +266 -349
  149. package/src/components/chat/__tests__/capabilities-popover.test.tsx +0 -1
  150. package/src/components/chat/__tests__/chat-display.test.tsx +196 -0
  151. package/src/components/chat/__tests__/chat-markdown.test.tsx +43 -0
  152. package/src/components/chat/__tests__/chat-utils.test.ts +93 -1
  153. package/src/components/chat/__tests__/message-queue.test.tsx +17 -0
  154. package/src/components/chat/__tests__/notebook-access-selector.test.tsx +64 -0
  155. package/src/components/chat/__tests__/turn-timing.test.ts +84 -0
  156. package/src/components/chat/__tests__/use-assistant-turn.test.ts +95 -0
  157. package/src/components/chat/__tests__/user-message.test.tsx +27 -0
  158. package/src/components/chat/acp/__tests__/agent-panel-initialization.test.tsx +1 -1
  159. package/src/components/chat/acp/__tests__/agent-panel.test.tsx +22 -17
  160. package/src/components/chat/acp/__tests__/session-tabs.test.tsx +43 -0
  161. package/src/components/chat/acp/__tests__/state.test.ts +2 -2
  162. package/src/components/chat/acp/agent-panel.tsx +6 -3
  163. package/src/components/chat/acp/blocks.tsx +13 -12
  164. package/src/components/chat/acp/common.tsx +4 -5
  165. package/src/components/chat/acp/model-selector.tsx +1 -1
  166. package/src/components/chat/acp/session-tabs.tsx +8 -3
  167. package/src/components/chat/agent-activity.tsx +132 -0
  168. package/src/components/chat/capabilities-popover.tsx +2 -11
  169. package/src/components/chat/chat-components.tsx +9 -4
  170. package/src/components/chat/chat-display.tsx +131 -57
  171. package/src/components/chat/chat-history-popover.tsx +6 -1
  172. package/src/components/chat/chat-markdown.tsx +15 -0
  173. package/src/components/chat/chat-panel.tsx +216 -294
  174. package/src/components/chat/chat-typography.css +108 -0
  175. package/src/components/chat/chat-utils.ts +24 -35
  176. package/src/components/chat/notebook-access-selector.stories.tsx +22 -0
  177. package/src/components/chat/notebook-access-selector.tsx +100 -0
  178. package/src/components/chat/tool-call/__tests__/tool-labels.test.ts +76 -0
  179. package/src/components/chat/tool-call/__tests__/tool-step.test.tsx +57 -0
  180. package/src/components/chat/tool-call/shared.ts +6 -0
  181. package/src/components/chat/tool-call/tool-call-view.tsx +22 -76
  182. package/src/components/chat/tool-call/tool-labels.ts +172 -0
  183. package/src/components/chat/tool-call/tool-result.tsx +1 -1
  184. package/src/components/chat/tool-call/tool-step.tsx +145 -0
  185. package/src/components/chat/turn-timing.ts +73 -0
  186. package/src/components/chat/use-assistant-turn.ts +65 -0
  187. package/src/components/chat/use-chat-generation.ts +20 -0
  188. package/src/components/chat/user-message.tsx +145 -0
  189. package/src/components/data-table/TableBottomBar.tsx +4 -1
  190. package/src/components/data-table/TableTopBar.tsx +2 -0
  191. package/src/components/data-table/__tests__/chart-spec-model.test.ts +195 -195
  192. package/src/components/data-table/__tests__/column-explorer.test.tsx +27 -1
  193. package/src/components/data-table/__tests__/columns.test.tsx +105 -0
  194. package/src/components/data-table/__tests__/context-menu-interaction.test.tsx +130 -0
  195. package/src/components/data-table/__tests__/data-table.test.tsx +1 -0
  196. package/src/components/data-table/__tests__/date-popover.test.tsx +72 -0
  197. package/src/components/data-table/__tests__/export-actions.test.tsx +105 -0
  198. package/src/components/data-table/__tests__/keyboard-selection.test.tsx +188 -0
  199. package/src/components/data-table/__tests__/pagination.test.tsx +28 -1
  200. package/src/components/data-table/__tests__/search-highlight.test.tsx +109 -0
  201. package/src/components/data-table/__tests__/url-detector.test.tsx +74 -0
  202. package/src/components/data-table/cell-selection/feature.ts +2 -2
  203. package/src/components/data-table/cell-selection/types.ts +2 -2
  204. package/src/components/data-table/charts/__tests__/chart-panel.test.tsx +74 -1
  205. package/src/components/data-table/charts/__tests__/spec-snapshot.test.ts +20 -20
  206. package/src/components/data-table/charts/__tests__/spec.test.ts +78 -52
  207. package/src/components/data-table/charts/__tests__/storage.test.ts +4 -1
  208. package/src/components/data-table/charts/chart-spec/spec.ts +68 -43
  209. package/src/components/data-table/charts/charts.tsx +40 -30
  210. package/src/components/data-table/charts/storage.ts +16 -5
  211. package/src/components/data-table/column-explorer-panel/column-explorer.tsx +1 -1
  212. package/src/components/data-table/column-formatting/types.ts +1 -2
  213. package/src/components/data-table/column-summary/chart-spec-model.tsx +23 -16
  214. package/src/components/data-table/column-summary/column-summary.tsx +2 -4
  215. package/src/components/data-table/column-visibility-dropdown.tsx +8 -13
  216. package/src/components/data-table/columns.tsx +45 -46
  217. package/src/components/data-table/context-menu.tsx +36 -20
  218. package/src/components/data-table/data-table.tsx +17 -1
  219. package/src/components/data-table/date-popover.tsx +6 -4
  220. package/src/components/data-table/export-actions.tsx +76 -48
  221. package/src/components/data-table/pagination.tsx +44 -33
  222. package/src/components/data-table/range-focus/atoms.ts +4 -6
  223. package/src/components/data-table/range-focus/use-cell-range-selection.ts +65 -38
  224. package/src/components/data-table/range-focus/use-scroll-into-view.ts +21 -2
  225. package/src/components/data-table/renderers.tsx +16 -7
  226. package/src/components/data-table/row-viewer-panel/__tests__/row-viewer.test.tsx +24 -1
  227. package/src/components/data-table/row-viewer-panel/row-viewer.tsx +4 -4
  228. package/src/components/data-table/search-highlight.tsx +79 -0
  229. package/src/components/data-table/types.ts +1 -0
  230. package/src/components/data-table/url-detector.tsx +49 -9
  231. package/src/components/datasources/__tests__/column-actions.test.tsx +97 -0
  232. package/src/components/datasources/__tests__/column-preview.test.tsx +96 -5
  233. package/src/components/datasources/__tests__/datasources-interactions.test.tsx +181 -0
  234. package/src/components/datasources/__tests__/table-list.test.tsx +118 -0
  235. package/src/components/datasources/__tests__/utils.test.ts +57 -6
  236. package/src/components/datasources/column-preview.tsx +17 -12
  237. package/src/components/datasources/datasources.tsx +56 -43
  238. package/src/components/datasources/utils.ts +32 -23
  239. package/src/components/debugger/debugger-code.tsx +5 -0
  240. package/src/components/dependency-graph/custom-node.tsx +1 -1
  241. package/src/components/dependency-graph/dependency-graph-tree.tsx +8 -8
  242. package/src/components/dependency-graph/elements.ts +24 -21
  243. package/src/components/dependency-graph/minimap-content.tsx +15 -20
  244. package/src/components/dependency-graph/panels.tsx +4 -2
  245. package/src/components/dependency-graph/utils/__tests__/layout.test.ts +18 -11
  246. package/src/components/editor/SortableCell.tsx +1 -3
  247. package/src/components/editor/__tests__/Output.test.tsx +50 -32
  248. package/src/{core/attachments/__tests__/toast.test.tsx → components/editor/__tests__/agent-toast.test.tsx} +1 -1
  249. package/src/components/editor/__tests__/data-attributes.test.tsx +28 -23
  250. package/src/components/editor/__tests__/dynamic-favicon.test.tsx +111 -18
  251. package/src/components/editor/__tests__/output-persistence.test.tsx +28 -9
  252. package/src/components/editor/__tests__/package-alert.test.tsx +22 -1
  253. package/src/components/editor/actions/__tests__/name-cell-input.test.tsx +69 -0
  254. package/src/components/editor/actions/__tests__/pair-with-agent-commands.test.ts +21 -40
  255. package/src/components/editor/actions/__tests__/pair-with-agent-modal.test.tsx +41 -14
  256. package/src/components/editor/actions/__tests__/pair-with-agent.test.tsx +6 -24
  257. package/src/components/editor/actions/__tests__/useCellActionButton.test.tsx +131 -0
  258. package/src/components/editor/actions/name-cell-input.tsx +4 -3
  259. package/src/components/editor/actions/pair-with-agent-commands.ts +10 -7
  260. package/src/components/editor/actions/pair-with-agent-modal.tsx +29 -30
  261. package/src/components/editor/actions/pair-with-agent.tsx +16 -39
  262. package/src/components/editor/actions/useCellActionButton.tsx +47 -35
  263. package/src/components/editor/actions/useConfigActions.tsx +3 -4
  264. package/src/components/editor/actions/useNotebookActions.tsx +25 -29
  265. package/src/components/editor/actions/useRestartKernel.tsx +1 -1
  266. package/src/components/editor/actions/useSetCodeVisibility.ts +11 -12
  267. package/src/components/editor/actions/useSetOutputsExpanded.ts +14 -19
  268. package/src/components/editor/ai/ai-completion-editor.tsx +2 -1
  269. package/src/components/editor/ai/completion-handlers.tsx +0 -37
  270. package/src/components/editor/ai/prompt-input.tsx +189 -0
  271. package/src/components/editor/alerts/__tests__/connection-notice.test.tsx +1 -1
  272. package/src/components/editor/alerts/connection-notice.tsx +1 -1
  273. package/src/components/editor/alerts/startup-logs-alert.tsx +1 -0
  274. package/src/components/editor/alerts/stdin-blocking-alert.tsx +4 -2
  275. package/src/components/editor/app-container.tsx +1 -1
  276. package/src/components/editor/boundary/__tests__/ErrorBoundary.test.tsx +1 -1
  277. package/src/components/editor/cell/CellStatus.tsx +28 -23
  278. package/src/components/editor/cell/CreateCellButton.tsx +15 -21
  279. package/src/components/editor/cell/DeleteButton.tsx +3 -2
  280. package/src/components/editor/cell/RunButton.tsx +18 -8
  281. package/src/components/editor/cell/StagedAICell.tsx +52 -137
  282. package/src/components/editor/cell/StopButton.tsx +2 -2
  283. package/src/components/editor/cell/__tests__/useRunCells.test.tsx +169 -0
  284. package/src/components/editor/cell/__tests__/useShouldShowInterrupt.test.ts +39 -0
  285. package/src/components/editor/cell/cell-actions.tsx +12 -1
  286. package/src/components/editor/cell/cell-context-menu.tsx +4 -1
  287. package/src/components/editor/cell/code/__tests__/language-toggle.test.tsx +54 -0
  288. package/src/components/editor/cell/code/cell-editor.tsx +65 -169
  289. package/src/components/editor/cell/code/language-toggle.tsx +8 -7
  290. package/src/components/editor/cell/collapse.tsx +16 -6
  291. package/src/components/editor/cell/useAddCell.ts +4 -8
  292. package/src/components/editor/cell/useDeleteCell.tsx +10 -36
  293. package/src/components/editor/cell/useRunCells.ts +49 -8
  294. package/src/components/editor/cell/useShouldShowInterrupt.ts +2 -12
  295. package/src/components/editor/cell/useSplitCell.tsx +10 -6
  296. package/src/components/editor/chrome/components/__tests__/feedback-button.test.tsx +1 -1
  297. package/src/components/editor/chrome/components/feedback-button.tsx +2 -8
  298. package/src/components/editor/chrome/panels/__tests__/packages-panel.test.tsx +31 -2
  299. package/src/components/editor/chrome/panels/__tests__/runtime-details.test.tsx +1 -1
  300. package/src/components/editor/chrome/panels/__tests__/sandbox-recovery.test.tsx +1 -1
  301. package/src/components/editor/chrome/panels/cache-panel.tsx +1 -0
  302. package/src/components/editor/chrome/panels/dependency-graph-panel.tsx +1 -1
  303. package/src/components/editor/chrome/panels/logs-panel.tsx +2 -1
  304. package/src/components/editor/chrome/panels/outline/__tests__/outline-keyboard.test.tsx +84 -0
  305. package/src/components/editor/chrome/panels/outline/floating-outline.tsx +39 -14
  306. package/src/components/editor/chrome/panels/outline/useActiveOutline.tsx +3 -2
  307. package/src/components/editor/chrome/panels/packages-panel.tsx +4 -7
  308. package/src/components/editor/chrome/panels/panel-header.tsx +13 -0
  309. package/src/components/editor/chrome/panels/secrets-panel.tsx +1 -0
  310. package/src/components/editor/chrome/panels/session-panel.tsx +1 -1
  311. package/src/components/editor/chrome/panels/snippets-panel.tsx +10 -12
  312. package/src/components/editor/chrome/panels/variable-panel.tsx +1 -5
  313. package/src/components/editor/chrome/types.ts +1 -1
  314. package/src/components/editor/chrome/wrapper/__tests__/attachment-status.test.tsx +7 -9
  315. package/src/components/editor/chrome/wrapper/__tests__/document-sync-status.test.tsx +92 -0
  316. package/src/components/editor/chrome/wrapper/__tests__/pending-ai-cells.test.tsx +265 -37
  317. package/src/components/editor/chrome/wrapper/__tests__/useAskAiAboutCell.test.tsx +58 -0
  318. package/src/components/editor/chrome/wrapper/app-chrome.tsx +91 -74
  319. package/src/components/editor/chrome/wrapper/footer-items/attachment-status.tsx +4 -4
  320. package/src/components/editor/chrome/wrapper/footer-items/backend-status.tsx +1 -1
  321. package/src/components/editor/chrome/wrapper/footer-items/document-sync-status.tsx +54 -0
  322. package/src/components/editor/chrome/wrapper/footer-items/lsp-status.tsx +1 -1
  323. package/src/components/editor/chrome/wrapper/footer-items/machine-stats.tsx +1 -1
  324. package/src/components/editor/chrome/wrapper/footer-items/runtime-settings.tsx +1 -7
  325. package/src/components/editor/chrome/wrapper/footer.tsx +2 -2
  326. package/src/components/editor/chrome/wrapper/minimap-state.ts +1 -1
  327. package/src/components/editor/chrome/wrapper/pending-ai-cells.tsx +143 -72
  328. package/src/components/editor/chrome/wrapper/pending-ai-changes-badge.tsx +25 -0
  329. package/src/components/editor/chrome/wrapper/sidebar.tsx +8 -4
  330. package/src/components/editor/chrome/wrapper/storage.ts +1 -1
  331. package/src/components/editor/chrome/wrapper/useAskAiAboutCell.ts +36 -0
  332. package/src/components/editor/columns/__tests__/cell-column.test.tsx +1 -1
  333. package/src/components/editor/columns/cell-column.tsx +1 -1
  334. package/src/components/editor/columns/sortable-column.tsx +2 -2
  335. package/src/components/editor/connections/components.tsx +5 -10
  336. package/src/components/editor/controls/Controls.tsx +4 -4
  337. package/src/components/editor/controls/__tests__/command-palette.test.tsx +53 -0
  338. package/src/components/editor/controls/__tests__/keyboard-shortcuts.test.tsx +61 -0
  339. package/src/components/editor/controls/__tests__/notebook-menu-dropdown.test.tsx +10 -4
  340. package/src/components/editor/controls/command-palette.tsx +22 -10
  341. package/src/components/editor/controls/keyboard-shortcuts.tsx +14 -5
  342. package/src/components/editor/documentation.css +34 -11
  343. package/src/components/editor/dynamic-favicon.tsx +21 -40
  344. package/src/components/editor/errors/__tests__/send-error-report-button.test.tsx +26 -22
  345. package/src/components/editor/errors/auto-fix.tsx +4 -8
  346. package/src/components/editor/errors/send-error-report-button.tsx +3 -6
  347. package/src/components/editor/file-tree/__tests__/file-explorer-interactions.test.tsx +6 -0
  348. package/src/components/editor/file-tree/__tests__/file-explorer-navigation.test.tsx +47 -0
  349. package/src/components/editor/file-tree/__tests__/use-file-search.test.tsx +91 -0
  350. package/src/components/editor/file-tree/file-explorer.tsx +48 -15
  351. package/src/components/editor/file-tree/file-name-input.tsx +1 -0
  352. package/src/components/editor/file-tree/file-viewer.tsx +4 -2
  353. package/src/components/editor/file-tree/use-file-search.ts +35 -21
  354. package/src/components/editor/header/__tests__/filename-form.test.tsx +1 -4
  355. package/src/components/editor/header/__tests__/status.test.tsx +5 -3
  356. package/src/components/editor/header/app-header.tsx +1 -1
  357. package/src/components/editor/header/filename-form.tsx +1 -1
  358. package/src/components/editor/header/status.tsx +44 -22
  359. package/src/components/editor/links/__tests__/cell-link.test.tsx +148 -0
  360. package/src/components/editor/links/cell-link-list.tsx +1 -1
  361. package/src/components/editor/links/cell-link.tsx +21 -11
  362. package/src/components/editor/navigation/__tests__/clipboard.test.ts +60 -38
  363. package/src/components/editor/navigation/__tests__/navigation.test.ts +108 -85
  364. package/src/components/editor/navigation/__tests__/selection.test.ts +43 -21
  365. package/src/components/editor/navigation/clipboard.ts +17 -14
  366. package/src/components/editor/navigation/focus-utils.ts +23 -4
  367. package/src/components/editor/navigation/multi-cell-action-toolbar.tsx +17 -33
  368. package/src/components/editor/navigation/navigation.ts +74 -72
  369. package/src/components/editor/navigation/selection.ts +30 -29
  370. package/src/components/editor/notebook-banner.tsx +1 -0
  371. package/src/components/editor/notebook-cell.tsx +126 -152
  372. package/src/components/editor/output/MarimoErrorOutput.tsx +2 -1
  373. package/src/components/editor/output/MarimoTracebackOutput.tsx +19 -17
  374. package/src/components/editor/output/Outputs.css +8 -0
  375. package/src/components/editor/output/VideoOutput.tsx +6 -0
  376. package/src/components/editor/output/__tests__/VideoOutput.test.tsx +131 -0
  377. package/src/components/editor/output/__tests__/traceback.test.tsx +27 -1
  378. package/src/components/editor/output/ansi-reduce.ts +33 -20
  379. package/src/components/editor/output/console/ConsoleOutput.tsx +19 -25
  380. package/src/components/editor/output/console/__tests__/ConsoleOutput.test.tsx +125 -69
  381. package/src/components/editor/package-alert.tsx +14 -7
  382. package/src/components/editor/renderers/__tests__/add-cell-buttons.test.tsx +18 -0
  383. package/src/components/editor/renderers/cell-array.tsx +32 -100
  384. package/src/components/editor/renderers/grid-layout/__tests__/grid-layout.test.tsx +75 -0
  385. package/src/components/editor/renderers/grid-layout/grid-layout.tsx +28 -22
  386. package/src/components/editor/renderers/slides-layout/__tests__/plugin.test.ts +0 -4
  387. package/src/components/editor/renderers/slides-layout/slides-layout.tsx +1 -1
  388. package/src/components/editor/renderers/vertical-layout/__tests__/useFocusFirstEditor.test.ts +4 -4
  389. package/src/components/editor/renderers/vertical-layout/sidebar/toggle.tsx +7 -1
  390. package/src/components/editor/renderers/vertical-layout/useFocusFirstEditor.ts +1 -1
  391. package/src/components/editor/renderers/vertical-layout/vertical-layout.tsx +19 -37
  392. package/src/components/editor/stdin-blocking-alert.tsx +4 -2
  393. package/src/components/home/components.tsx +17 -31
  394. package/src/components/icons/__tests__/large-spinner.test.tsx +39 -0
  395. package/src/components/icons/large-spinner.tsx +3 -14
  396. package/src/components/icons/spinner.tsx +1 -1
  397. package/src/components/markdown/markdown-renderer.tsx +39 -28
  398. package/src/components/mcp/mcp-status-indicator.tsx +18 -14
  399. package/src/components/pages/__tests__/home-page.test.tsx +116 -0
  400. package/src/components/pages/edit-page.tsx +19 -17
  401. package/src/components/pages/home-page.tsx +164 -91
  402. package/src/components/scratchpad/scratchpad.test.tsx +89 -0
  403. package/src/components/scratchpad/scratchpad.tsx +47 -33
  404. package/src/components/scratchpad/state.ts +7 -0
  405. package/src/components/slides/__tests__/minimap-actions.test.tsx +22 -29
  406. package/src/components/slides/__tests__/minimap.test.ts +38 -19
  407. package/src/components/slides/__tests__/slide-scroll-hint.test.tsx +37 -0
  408. package/src/components/slides/minimap.tsx +22 -14
  409. package/src/components/slides/reveal-component.tsx +73 -36
  410. package/src/components/slides/reveal-slides.css +5 -4
  411. package/src/components/slides/slide-cell-view.tsx +37 -34
  412. package/src/components/slides/slide-form.tsx +1 -0
  413. package/src/components/slides/slide-scroll-hint.tsx +3 -1
  414. package/src/components/slides/slide.tsx +3 -4
  415. package/src/components/sort/SortableCellsProvider.tsx +8 -25
  416. package/src/components/tracing/tracing-keyboard.test.tsx +85 -0
  417. package/src/components/tracing/tracing.tsx +46 -42
  418. package/src/components/ui/__tests__/draggable-popover.test.tsx +24 -0
  419. package/src/components/ui/__tests__/fullscreen.test.tsx +44 -2
  420. package/src/components/ui/__tests__/range-slider.test.tsx +165 -0
  421. package/src/components/ui/alert-dialog.tsx +1 -1
  422. package/src/components/ui/draggable-popover.tsx +24 -2
  423. package/src/components/ui/fullscreen.tsx +25 -1
  424. package/src/components/ui/range-slider.tsx +22 -9
  425. package/src/components/ui/toast.tsx +1 -1
  426. package/src/core/MarimoApp.tsx +3 -1
  427. package/src/core/ai/__tests__/staged-cells.test.ts +89 -608
  428. package/src/core/ai/context/context.ts +2 -0
  429. package/src/core/ai/context/providers/__tests__/cell-output.test.ts +8 -12
  430. package/src/core/ai/context/providers/__tests__/cell.test.ts +49 -0
  431. package/src/core/ai/context/providers/cell-output.ts +2 -2
  432. package/src/core/ai/context/providers/cell.ts +54 -0
  433. package/src/core/ai/context/providers/datasource.ts +6 -2
  434. package/src/core/ai/staged-cells.ts +56 -323
  435. package/src/core/ai/state.ts +3 -0
  436. package/src/core/ai/tools/__tests__/edit-notebook-tool.test.ts +40 -88
  437. package/src/core/ai/tools/__tests__/registry.test.ts +17 -0
  438. package/src/core/ai/tools/base.ts +18 -15
  439. package/src/core/ai/tools/edit-notebook-tool.ts +86 -80
  440. package/src/core/ai/tools/registry.ts +13 -10
  441. package/src/core/ai/tools/sample-tool.ts +6 -7
  442. package/src/core/cells/__tests__/__snapshots__/cells.test.ts.snap +4 -527
  443. package/src/core/cells/__tests__/actions.test.ts +623 -35
  444. package/src/core/cells/__tests__/add-missing-import.test.ts +7 -8
  445. package/src/core/cells/__tests__/arrange.test.ts +432 -0
  446. package/src/core/cells/__tests__/cell.test.ts +148 -150
  447. package/src/core/cells/__tests__/cells.test.ts +225 -2723
  448. package/src/core/cells/__tests__/collapseConsoleOutputs.test.ts +54 -0
  449. package/src/core/cells/__tests__/document.test.ts +158 -0
  450. package/src/core/cells/__tests__/editor.test.ts +57 -0
  451. package/src/core/cells/__tests__/flush-document.test.ts +40 -0
  452. package/src/core/cells/__tests__/harness.ts +106 -0
  453. package/src/core/cells/__tests__/layout.test.ts +177 -0
  454. package/src/core/cells/__tests__/notebook-outline.test.ts +122 -0
  455. package/src/core/cells/__tests__/outputs.test.tsx +71 -53
  456. package/src/core/cells/__tests__/pending-delete-service.test.tsx +11 -20
  457. package/src/core/cells/__tests__/session.test.ts +445 -211
  458. package/src/core/cells/__tests__/utils.test.ts +135 -337
  459. package/src/core/cells/actions.ts +677 -6
  460. package/src/core/cells/add-missing-import.ts +47 -43
  461. package/src/core/cells/arrange.ts +529 -0
  462. package/src/core/cells/cell.ts +78 -146
  463. package/src/core/cells/cells.ts +116 -1457
  464. package/src/core/cells/columns.ts +38 -0
  465. package/src/core/cells/document.ts +167 -0
  466. package/src/core/cells/edit.ts +30 -0
  467. package/src/core/cells/editor.ts +82 -0
  468. package/src/core/cells/focus.ts +27 -6
  469. package/src/core/cells/layout.ts +221 -0
  470. package/src/core/cells/outputs.ts +12 -28
  471. package/src/core/cells/pending-delete-service.ts +8 -14
  472. package/src/core/cells/scrollCellIntoView.ts +6 -0
  473. package/src/core/cells/session.ts +76 -53
  474. package/src/core/cells/types.ts +21 -51
  475. package/src/core/cells/utils.ts +85 -87
  476. package/src/core/codemirror/__tests__/format.test.ts +0 -34
  477. package/src/core/codemirror/__tests__/setup.test.ts +61 -29
  478. package/src/core/codemirror/ai/__tests__/resources.test.ts +117 -0
  479. package/src/core/codemirror/ai/resources.ts +1 -1
  480. package/src/core/codemirror/cells/__tests__/extensions.test.ts +2 -6
  481. package/src/core/codemirror/cells/extensions.ts +18 -30
  482. package/src/core/codemirror/cells/state.ts +2 -6
  483. package/src/core/codemirror/cm.ts +5 -1
  484. package/src/core/codemirror/config/types.ts +1 -1
  485. package/src/core/codemirror/copilot/__tests__/getCodes.test.ts +6 -7
  486. package/src/core/codemirror/copilot/getCodes.ts +2 -2
  487. package/src/core/codemirror/format.ts +6 -23
  488. package/src/core/codemirror/keymaps/vim.ts +4 -8
  489. package/src/core/codemirror/language/__tests__/sql.test.ts +22 -20
  490. package/src/core/codemirror/language/__tests__/utils.test.ts +187 -15
  491. package/src/core/codemirror/language/cell-code.ts +114 -0
  492. package/src/core/codemirror/language/extension.ts +52 -35
  493. package/src/core/codemirror/language/languages/markdown.ts +8 -6
  494. package/src/core/codemirror/language/languages/python.ts +19 -13
  495. package/src/core/codemirror/language/languages/sql/sql.ts +16 -24
  496. package/src/core/codemirror/language/panel/panel.tsx +1 -0
  497. package/src/core/codemirror/language/types.ts +7 -7
  498. package/src/core/codemirror/language/utils.ts +9 -39
  499. package/src/core/codemirror/lsp/__tests__/notebook-lsp.test.ts +1 -2
  500. package/src/core/codemirror/lsp/notebook-lsp.ts +6 -5
  501. package/src/core/codemirror/lsp/utils.ts +1 -1
  502. package/src/core/codemirror/misc/__tests__/paste.test.ts +3 -1
  503. package/src/core/codemirror/misc/paste.ts +1 -1
  504. package/src/core/codemirror/placeholder/__tests__/extensions.test.ts +75 -0
  505. package/src/core/codemirror/placeholder/extensions.ts +80 -19
  506. package/src/core/codemirror/readonly/__tests__/extension.test.ts +1 -1
  507. package/src/core/codemirror/readonly/extension.ts +2 -2
  508. package/src/core/codemirror/utils.ts +1 -0
  509. package/src/core/config/__tests__/app-config.test.ts +64 -0
  510. package/src/core/config/__tests__/config-schema.test.ts +56 -6
  511. package/src/core/config/config-schema.ts +17 -4
  512. package/src/core/config/config.ts +65 -20
  513. package/src/core/config/feature-flag.tsx +0 -2
  514. package/src/core/datasets/__tests__/data-source-discovery.test.ts +5 -5
  515. package/src/core/datasets/data-source-discovery.ts +4 -6
  516. package/src/core/documentation/doc-lookup.ts +1 -1
  517. package/src/core/edit-app.tsx +22 -28
  518. package/src/core/errors/__tests__/error-entries.test.ts +40 -1
  519. package/src/core/errors/error-entries.ts +6 -4
  520. package/src/core/executions/__tests__/atoms.test.ts +99 -0
  521. package/src/core/executions/__tests__/freshness.test.ts +482 -0
  522. package/src/core/executions/__tests__/hooks.test.tsx +106 -0
  523. package/src/core/executions/__tests__/select.test.ts +268 -0
  524. package/src/core/executions/atoms.ts +228 -0
  525. package/src/core/executions/freshness.ts +171 -0
  526. package/src/core/executions/hooks.ts +47 -0
  527. package/src/core/executions/index.ts +33 -0
  528. package/src/core/executions/select.ts +175 -0
  529. package/src/core/export/__tests__/hooks.test.ts +0 -5
  530. package/src/core/export/hooks.ts +1 -1
  531. package/src/core/export/layout.ts +2 -2
  532. package/src/core/hotkeys/actions.ts +2 -4
  533. package/src/core/hotkeys/hotkeys.ts +2 -1
  534. package/src/core/islands/__tests__/bootstrap.test.ts +69 -17
  535. package/src/core/islands/__tests__/bridge.test.ts +85 -27
  536. package/src/core/islands/bootstrap.ts +49 -54
  537. package/src/core/islands/bridge.ts +84 -25
  538. package/src/core/islands/components/__tests__/IslandControls.test.tsx +7 -5
  539. package/src/core/islands/components/__tests__/web-components.test.tsx +63 -0
  540. package/src/core/islands/components/output-wrapper.tsx +5 -5
  541. package/src/core/islands/components/web-components.tsx +12 -1
  542. package/src/core/islands/worker/__tests__/controller.test.ts +62 -4
  543. package/src/core/islands/worker/controller.ts +4 -10
  544. package/src/core/islands/worker/worker.tsx +33 -33
  545. package/src/core/kernel/__tests__/RuntimeState.test.ts +1 -1
  546. package/src/core/kernel/__tests__/handlers.test.ts +7 -153
  547. package/src/core/kernel/handlers.ts +13 -79
  548. package/src/core/kernel/state.ts +0 -15
  549. package/src/core/lifecycle/render-policy.ts +12 -26
  550. package/src/core/meta/__tests__/code-visibility.test.tsx +78 -70
  551. package/src/core/meta/code-visibility.ts +18 -9
  552. package/src/core/network/__tests__/connection.test.ts +1 -1
  553. package/src/core/network/__tests__/requests-lazy.test.ts +6 -8
  554. package/src/core/network/connection-notice.ts +1 -1
  555. package/src/core/network/connection.ts +1 -1
  556. package/src/core/network/requests-lazy.ts +5 -8
  557. package/src/core/network/requests-network.ts +5 -31
  558. package/src/core/network/requests-static.ts +1 -4
  559. package/src/core/network/requests-toasting.tsx +1 -4
  560. package/src/core/network/types.ts +6 -14
  561. package/src/core/network/useConnectionNotice.ts +8 -2
  562. package/src/core/notebook/__tests__/operations.test.ts +51 -0
  563. package/src/core/notebook/attachments.ts +38 -0
  564. package/src/core/notebook/document/__tests__/codemirror.test.ts +759 -0
  565. package/src/core/notebook/document/__tests__/draft.test.ts +363 -0
  566. package/src/core/notebook/document/__tests__/fixtures.ts +92 -0
  567. package/src/core/notebook/document/__tests__/hooks.test.tsx +391 -0
  568. package/src/core/notebook/document/__tests__/lifecycle-scenarios.test.ts +61 -0
  569. package/src/core/notebook/document/__tests__/model.test.ts +247 -0
  570. package/src/core/notebook/document/__tests__/protocol-fixtures.ts +38 -0
  571. package/src/core/notebook/document/__tests__/recovery-scenarios.test.ts +230 -0
  572. package/src/core/notebook/document/__tests__/scenario-fixture.ts +43 -0
  573. package/src/core/notebook/document/__tests__/scenarios.test.ts +205 -0
  574. package/src/core/notebook/document/__tests__/state.test.ts +459 -0
  575. package/src/core/notebook/document/__tests__/submission.test.ts +203 -0
  576. package/src/core/notebook/document/__tests__/test-host.test.ts +92 -0
  577. package/src/core/notebook/document/__tests__/test-host.ts +247 -0
  578. package/src/core/notebook/document/__tests__/test-notebook.ts +149 -0
  579. package/src/core/notebook/document/__tests__/user.ts +61 -0
  580. package/src/core/notebook/document/codemirror.ts +216 -0
  581. package/src/core/notebook/document/draft.ts +502 -0
  582. package/src/core/notebook/document/hooks.tsx +118 -0
  583. package/src/core/notebook/document/index.ts +24 -0
  584. package/src/core/notebook/document/model.ts +102 -0
  585. package/src/core/notebook/document/select.ts +41 -0
  586. package/src/core/notebook/document/state.ts +378 -0
  587. package/src/core/notebook/document/sync.ts +369 -0
  588. package/src/core/notebook/index.ts +19 -0
  589. package/src/core/notebook/operations.ts +30 -0
  590. package/src/core/notebook/replica/AGENTS.md +11 -0
  591. package/src/core/{host → notebook/replica}/__tests__/attachments.test.ts +9 -10
  592. package/src/core/{host → notebook/replica}/__tests__/document.test.ts +18 -16
  593. package/src/core/notebook/replica/__tests__/executions.test.ts +200 -0
  594. package/src/core/notebook/replica/__tests__/fixtures.ts +79 -0
  595. package/src/core/notebook/replica/__tests__/replica.test.ts +559 -0
  596. package/src/core/notebook/replica/__tests__/results.test.ts +193 -0
  597. package/src/core/notebook/replica/__tests__/runtime.test.ts +78 -0
  598. package/src/core/notebook/replica/backend.ts +19 -0
  599. package/src/core/notebook/replica/connection.ts +42 -0
  600. package/src/core/notebook/replica/index.ts +20 -0
  601. package/src/core/notebook/replica/replica.ts +336 -0
  602. package/src/core/{host → notebook/replica}/resources/attachments.ts +6 -3
  603. package/src/core/{host → notebook/replica}/resources/document.ts +3 -3
  604. package/src/core/notebook/replica/resources/executions.ts +157 -0
  605. package/src/core/notebook/replica/resources/results.ts +48 -0
  606. package/src/core/notebook/replica/resources/runtime.ts +14 -0
  607. package/src/core/packages/__tests__/useSandboxController.test.tsx +1 -1
  608. package/src/core/packages/useSandboxController.ts +1 -1
  609. package/src/core/run-app.tsx +49 -51
  610. package/src/core/runtime/__tests__/adapter.test.ts +4 -4
  611. package/src/core/runtime/__tests__/createWsUrl.test.ts +77 -7
  612. package/src/core/runtime/__tests__/runtime.test.ts +32 -74
  613. package/src/core/runtime/adapter.ts +5 -5
  614. package/src/core/runtime/config.ts +2 -2
  615. package/src/core/runtime/runtime.ts +33 -46
  616. package/src/core/runtime/types.ts +0 -2
  617. package/src/core/saving/__tests__/document.test.tsx +123 -0
  618. package/src/core/saving/__tests__/state.test.ts +111 -0
  619. package/src/core/saving/__tests__/useAutoSave.test.tsx +67 -0
  620. package/src/core/saving/document.ts +33 -0
  621. package/src/core/saving/filename.ts +1 -1
  622. package/src/core/saving/save-component.tsx +39 -60
  623. package/src/core/saving/state.ts +38 -39
  624. package/src/core/saving/useAutoSave.ts +17 -53
  625. package/src/core/session/__tests__/backend.test.ts +236 -0
  626. package/src/core/session/__tests__/useNotebookReplica.hook.test.tsx +186 -0
  627. package/src/core/{websocket → session}/__tests__/useNotebookSession.hook.test.tsx +247 -262
  628. package/src/core/session/active-notebook.ts +28 -0
  629. package/src/core/session/backend.ts +123 -0
  630. package/src/core/session/embedded.ts +7 -0
  631. package/src/core/{websocket → session}/types.ts +2 -2
  632. package/src/core/session/useNotebookReplica.ts +74 -0
  633. package/src/core/{websocket → session}/useNotebookSession.tsx +80 -67
  634. package/src/core/state/__tests__/jotai.test.tsx +108 -0
  635. package/src/core/state/jotai.ts +1 -1
  636. package/src/core/static/__tests__/files.test.ts +38 -10
  637. package/src/core/static/files.ts +2 -0
  638. package/src/core/{websocket → transports}/__tests__/connection-state.test.ts +1 -13
  639. package/src/core/transports/__tests__/server.test.ts +408 -0
  640. package/src/core/transports/__tests__/static.test.ts +306 -0
  641. package/src/core/transports/__tests__/wasm.test.ts +166 -0
  642. package/src/core/{websocket → transports}/connection-state.ts +9 -16
  643. package/src/core/transports/server.ts +152 -0
  644. package/src/core/transports/static.ts +166 -0
  645. package/src/core/transports/wasm.ts +97 -0
  646. package/src/core/vscode/__tests__/vscode-bindings.test.ts +125 -0
  647. package/src/core/vscode/vscode-bindings.ts +14 -2
  648. package/src/core/wasm/__tests__/bridge.test.ts +210 -64
  649. package/src/core/wasm/__tests__/worker.test.ts +188 -0
  650. package/src/core/wasm/bridge.ts +120 -73
  651. package/src/core/wasm/rpc.ts +15 -3
  652. package/src/core/wasm/store.ts +3 -3
  653. package/src/core/wasm/worker/bootstrap.ts +55 -51
  654. package/src/core/wasm/worker/getMarimoWheel.ts +1 -1
  655. package/src/core/wasm/worker/save-worker.ts +9 -10
  656. package/src/core/wasm/worker/types.ts +30 -18
  657. package/src/core/wasm/worker/worker.ts +118 -72
  658. package/src/css/app/Cell.css +7 -36
  659. package/src/css/app/codemirror.css +1 -1
  660. package/src/css/common.css +1 -1
  661. package/src/css/globals.css +72 -24
  662. package/src/hooks/__tests__/useAsyncData.test.tsx +148 -0
  663. package/src/hooks/__tests__/useDebounce.test.tsx +51 -0
  664. package/src/hooks/__tests__/useDeepCompareMemoize.test.tsx +39 -0
  665. package/src/hooks/__tests__/useDelayElapsed.test.tsx +28 -0
  666. package/src/hooks/__tests__/useInternalStateWithSync.test.tsx +20 -0
  667. package/src/hooks/__tests__/useScript.test.tsx +77 -0
  668. package/src/hooks/__tests__/useStateForKey.test.tsx +257 -0
  669. package/src/hooks/useAsyncData.ts +3 -3
  670. package/src/hooks/useDataSourceDiscovery.ts +6 -4
  671. package/src/hooks/useDebounce.ts +6 -10
  672. package/src/hooks/useDeepCompareMemoize.ts +1 -1
  673. package/src/hooks/useDelayElapsed.ts +14 -10
  674. package/src/hooks/useElapsedTime.ts +3 -3
  675. package/src/hooks/useEventListener.ts +1 -2
  676. package/src/hooks/useInputHistory.ts +4 -7
  677. package/src/hooks/useInternalStateWithSync.ts +6 -6
  678. package/src/hooks/useResizeObserver.ts +4 -7
  679. package/src/hooks/useScript.ts +67 -70
  680. package/src/hooks/useStateForKey.ts +93 -0
  681. package/src/mount.tsx +20 -15
  682. package/src/plugins/__tests__/plugin-object-schemas.test.ts +104 -0
  683. package/src/plugins/core/RenderHTML.tsx +3 -5
  684. package/src/plugins/core/__test__/RenderHTML-copy.test.tsx +60 -0
  685. package/src/plugins/core/registerReactComponent.tsx +5 -14
  686. package/src/plugins/impl/CodeEditorPlugin.tsx +1 -1
  687. package/src/plugins/impl/DataEditorPlugin.tsx +34 -13
  688. package/src/plugins/impl/DataTablePlugin.tsx +69 -37
  689. package/src/plugins/impl/DropdownPlugin.tsx +2 -6
  690. package/src/plugins/impl/FileBrowserPlugin.tsx +4 -16
  691. package/src/plugins/impl/FormPlugin.tsx +1 -1
  692. package/src/plugins/impl/RangeSliderPlugin.tsx +16 -2
  693. package/src/plugins/impl/RefreshPlugin.tsx +1 -0
  694. package/src/plugins/impl/SliderPlugin.tsx +6 -8
  695. package/src/plugins/impl/__tests__/DataEditorPlugin.test.ts +93 -4
  696. package/src/plugins/impl/__tests__/DataTablePlugin.test.tsx +387 -1
  697. package/src/plugins/impl/__tests__/FileBrowserPlugin.test.tsx +45 -0
  698. package/src/plugins/impl/__tests__/RangeSliderPlugin.test.tsx +109 -0
  699. package/src/plugins/impl/__tests__/SliderPlugin.test.tsx +24 -3
  700. package/src/plugins/impl/anywidget/__tests__/registry.test.ts +153 -0
  701. package/src/plugins/impl/anywidget/__tests__/widget-binding.test.ts +241 -2
  702. package/src/plugins/impl/anywidget/model.ts +3 -1
  703. package/src/plugins/impl/anywidget/registry.ts +18 -14
  704. package/src/plugins/impl/anywidget/runtime.ts +21 -9
  705. package/src/plugins/impl/anywidget/widget-binding.ts +160 -82
  706. package/src/plugins/impl/chat/__tests__/chat-prompts.test.tsx +93 -0
  707. package/src/plugins/impl/chat/chat-ui.tsx +17 -15
  708. package/src/plugins/impl/code/any-language-editor.tsx +2 -2
  709. package/src/plugins/impl/common/error-banner.tsx +5 -3
  710. package/src/plugins/impl/data-editor/__tests__/column-types.test.ts +42 -0
  711. package/src/plugins/impl/data-editor/__tests__/editor-state.test.ts +52 -1
  712. package/src/plugins/impl/data-editor/__tests__/glide-data-editor.test.tsx +120 -4
  713. package/src/plugins/impl/data-editor/__tests__/glide-utils.test.ts +116 -0
  714. package/src/plugins/impl/data-editor/column-types.ts +55 -0
  715. package/src/plugins/impl/data-editor/editor-state.ts +18 -2
  716. package/src/plugins/impl/data-editor/glide-data-editor.tsx +74 -12
  717. package/src/plugins/impl/data-editor/glide-utils.ts +39 -38
  718. package/src/plugins/impl/data-editor/types.ts +5 -0
  719. package/src/plugins/impl/data-explorer/ConnectedDataExplorerComponent.tsx +1 -1
  720. package/src/plugins/impl/data-explorer/components/column-summary.tsx +4 -2
  721. package/src/plugins/impl/data-explorer/state/reducer.ts +1 -1
  722. package/src/plugins/impl/data-frames/DataFramePlugin.tsx +1 -2
  723. package/src/plugins/impl/data-frames/__tests__/sidebar.test.tsx +160 -0
  724. package/src/plugins/impl/data-frames/forms/__tests__/__snapshots__/form.test.tsx.snap +66 -66
  725. package/src/plugins/impl/data-frames/panel.tsx +44 -16
  726. package/src/plugins/impl/mpl-interactive/MplInteractivePlugin.tsx +5 -3
  727. package/src/plugins/impl/panel/PanelPlugin.tsx +16 -17
  728. package/src/plugins/impl/panel/__tests__/utils.test.ts +39 -1
  729. package/src/plugins/impl/panel/utils.ts +17 -0
  730. package/src/plugins/impl/plotly/PlotlyPlugin.tsx +2 -5
  731. package/src/plugins/impl/plotly/selection.ts +25 -19
  732. package/src/plugins/impl/vega/VegaPlugin.tsx +2 -3
  733. package/src/plugins/impl/vega/make-selectable.ts +4 -7
  734. package/src/plugins/layout/DownloadPlugin.tsx +7 -11
  735. package/src/plugins/layout/RoutesPlugin.tsx +1 -1
  736. package/src/plugins/layout/__test__/DownloadPlugin.test.tsx +205 -0
  737. package/src/stories/__tests__/editor-story.test.tsx +33 -0
  738. package/src/stories/button.stories.tsx +1 -0
  739. package/src/stories/cell-status.stories.tsx +29 -24
  740. package/src/stories/cell.stories.tsx +46 -46
  741. package/src/stories/data-table.stories.tsx +31 -0
  742. package/src/stories/editor.stories.tsx +24 -17
  743. package/src/stories/form-wrapper.stories.tsx +2 -2
  744. package/src/stories/layout/vertical/one-column.stories.tsx +28 -42
  745. package/src/stories/runtime-story.tsx +23 -17
  746. package/src/stories/select.stories.tsx +2 -2
  747. package/src/stories/switchable-multi-select.stories.tsx +1 -1
  748. package/src/theme/__tests__/islands-theme.test.tsx +276 -0
  749. package/src/theme/namespace.tsx +4 -3
  750. package/src/theme/useTheme.ts +71 -41
  751. package/src/utils/__tests__/arrays.test.ts +35 -0
  752. package/src/utils/__tests__/batch-requests.test.ts +3 -3
  753. package/src/utils/__tests__/{createReducer.test.ts → createReducer.test.tsx} +127 -1
  754. package/src/utils/__tests__/download.test.tsx +183 -23
  755. package/src/utils/__tests__/numbers.test.ts +105 -1
  756. package/src/utils/__tests__/objects.test.ts +21 -0
  757. package/src/utils/arrays.ts +11 -2
  758. package/src/utils/blob.ts +1 -1
  759. package/src/utils/createReducer.ts +115 -79
  760. package/src/utils/download.ts +67 -6
  761. package/src/utils/maps.ts +0 -3
  762. package/src/utils/numbers.ts +79 -18
  763. package/src/utils/objects.ts +4 -9
  764. package/src/utils/time.ts +10 -0
  765. package/src/utils/tracer.ts +9 -5
  766. package/src/utils/zod-utils.ts +1 -3
  767. package/dist/_baseMerge-BiGRwuqv.js +0 -165
  768. package/dist/any-language-editor-D4q-IFcM.js +0 -76
  769. package/dist/assets/__vite-browser-external-BXYkiFsk.js +0 -1
  770. package/dist/assets/worker-HPY9fbhz.js +0 -114
  771. package/dist/feedback-button-g9eH5l7Q.js +0 -379
  772. package/dist/hasIn-o2Us0uVx.js +0 -132
  773. package/dist/html-to-image-B5H-xKRH.js +0 -3251
  774. package/dist/isArrayLikeObject-BZl-15ir.js +0 -450
  775. package/dist/isEmpty-BSZ1LaUo.js +0 -35
  776. package/dist/isSymbol-kQcfYbNX.js +0 -8
  777. package/dist/label-DoysmLMW.js +0 -281
  778. package/dist/pick-CJeoxYOS.js +0 -51
  779. package/dist/select-DemoKT4G.js +0 -1344
  780. package/dist/toDate-BvdkR0aH.js +0 -1185
  781. package/dist/toast-PkdZraM8.js +0 -1830
  782. package/dist/use-restore-focus-CKjfFjLM.js +0 -6369
  783. package/dist/useLifecycle-Dfq8UD-b.js +0 -169
  784. package/dist/useTheme-Bu5bBgrh.js +0 -904
  785. package/src/components/chat/reasoning-accordion.tsx +0 -61
  786. package/src/components/chat/tool-call/tool-error-card.tsx +0 -81
  787. package/src/components/chat/tool-call/tool-history-row.tsx +0 -174
  788. package/src/components/editor/ai/__tests__/staged-cell-submission.test.ts +0 -106
  789. package/src/components/editor/ai/add-cell-with-ai.tsx +0 -506
  790. package/src/components/editor/ai/staged-cell-submission.ts +0 -46
  791. package/src/components/editor/cell/__tests__/StagedAICell.test.tsx +0 -64
  792. package/src/components/editor/cell/code/__tests__/rtc-editor-config.test.ts +0 -50
  793. package/src/components/editor/chrome/wrapper/footer-items/rtc-status.tsx +0 -65
  794. package/src/components/editor/navigation/__tests__/state.test.ts +0 -81
  795. package/src/components/editor/navigation/state.ts +0 -65
  796. package/src/components/icons/loading-ellipsis.tsx +0 -46
  797. package/src/core/ai/tools/__tests__/run-cells-tool.test.ts +0 -656
  798. package/src/core/ai/tools/run-cells-tool.ts +0 -303
  799. package/src/core/attachments/state.ts +0 -18
  800. package/src/core/cells/__tests__/apply-transaction.test.ts +0 -991
  801. package/src/core/cells/__tests__/document-changes.test.ts +0 -1140
  802. package/src/core/cells/__tests__/document-roundtrip.test.ts +0 -392
  803. package/src/core/cells/document-changes.ts +0 -944
  804. package/src/core/codemirror/rtc/extension.ts +0 -453
  805. package/src/core/codemirror/rtc/loro/__tests__/sync.test.ts +0 -52
  806. package/src/core/codemirror/rtc/loro/awareness.ts +0 -531
  807. package/src/core/codemirror/rtc/loro/colors.ts +0 -38
  808. package/src/core/codemirror/rtc/loro/sync.ts +0 -168
  809. package/src/core/host/AGENTS.md +0 -11
  810. package/src/core/host/__tests__/events.test.ts +0 -46
  811. package/src/core/host/__tests__/fixtures.ts +0 -39
  812. package/src/core/host/__tests__/notebook-client.test.ts +0 -433
  813. package/src/core/host/__tests__/protocol.test.ts +0 -214
  814. package/src/core/host/__tests__/stream.test.ts +0 -209
  815. package/src/core/host/connection.ts +0 -81
  816. package/src/core/host/events.ts +0 -61
  817. package/src/core/host/notebook-client.ts +0 -290
  818. package/src/core/host/protocol.ts +0 -1945
  819. package/src/core/host/resources/resource.ts +0 -47
  820. package/src/core/host/stream.ts +0 -211
  821. package/src/core/rtc/state.ts +0 -34
  822. package/src/core/state/__tests__/jotai.test.ts +0 -25
  823. package/src/core/wasm/worker/message-buffer.ts +0 -33
  824. package/src/core/websocket/__tests__/createConnectionTransport.test.ts +0 -49
  825. package/src/core/websocket/__tests__/useNotebookClient.hook.test.tsx +0 -170
  826. package/src/core/websocket/state.ts +0 -7
  827. package/src/core/websocket/transports/__tests__/basic.test.ts +0 -275
  828. package/src/core/websocket/transports/__tests__/sse.test.ts +0 -335
  829. package/src/core/websocket/transports/__tests__/transport.test.ts +0 -308
  830. package/src/core/websocket/transports/__tests__/ws.test.ts +0 -125
  831. package/src/core/websocket/transports/basic.ts +0 -102
  832. package/src/core/websocket/transports/create.ts +0 -31
  833. package/src/core/websocket/transports/sse.ts +0 -308
  834. package/src/core/websocket/transports/transport.ts +0 -50
  835. package/src/core/websocket/transports/ws.ts +0 -98
  836. package/src/core/websocket/useNotebookClient.ts +0 -88
  837. package/src/utils/__tests__/id-tree.test.ts +0 -2145
  838. package/src/utils/id-tree.tsx +0 -1134
  839. /package/src/{core/attachments/toast.ts → components/editor/agent-toast.ts} +0 -0
  840. /package/src/core/{websocket → session}/connection-utils.ts +0 -0
@@ -4,29 +4,32 @@ import { python } from "@codemirror/lang-python";
4
4
  import { EditorState } from "@codemirror/state";
5
5
  import { EditorView } from "@codemirror/view";
6
6
  import { createStore } from "jotai";
7
+ import { createRef } from "react";
7
8
  import {
8
9
  afterAll,
9
- afterEach,
10
10
  beforeAll,
11
+ afterEach,
11
12
  beforeEach,
12
13
  describe,
13
14
  expect,
14
15
  it,
15
16
  vi,
16
17
  } from "vitest";
18
+ import { MockExecutions } from "@/__mocks__/executions";
19
+ import {
20
+ notebookQueuedOrRunningCountAtom,
21
+ submitCells,
22
+ } from "@/core/executions";
23
+ import { store } from "@/core/state/jotai";
17
24
  import { MockNotebook } from "@/__mocks__/notebook";
18
25
  import { cellId } from "@/__tests__/branded";
19
26
  import type { CellHandle } from "@/components/editor/notebook-cell";
20
- import { CellId, SCRATCH_CELL_ID, SETUP_CELL_ID } from "@/core/cells/ids";
27
+ import { CellId, SCRATCH_CELL_ID } from "@/core/cells/ids";
21
28
  import { foldAllBulk, unfoldAllBulk } from "@/core/codemirror/editing/commands";
22
29
  import { adaptiveLanguageConfiguration } from "@/core/codemirror/language/extension";
23
30
  import { OverridingHotkeyProvider } from "@/core/hotkeys/hotkeys";
24
31
  import type { OutputMessage } from "@/core/kernel/messages";
25
- import {
26
- type CollapsibleTree,
27
- MultiColumn,
28
- type CellColumnId,
29
- } from "@/utils/id-tree";
32
+ import type { CellConfig } from "@/core/network/types";
30
33
  import type { Seconds } from "@/utils/time";
31
34
  import {
32
35
  exportedForTesting,
@@ -34,13 +37,12 @@ import {
34
37
  type NotebookState,
35
38
  notebookAtom,
36
39
  } from "../cells";
37
- import { exportedForTesting as documentTransactionTestExports } from "../document-changes";
38
40
  import {
39
- focusAndScrollCellIntoView,
40
- scrollToBottom,
41
- scrollToTop,
42
- } from "../scrollCellIntoView";
43
- import type { CellData } from "../types";
41
+ type CellData,
42
+ createCell,
43
+ createCellConfig,
44
+ createCellRuntimeState,
45
+ } from "../types";
44
46
 
45
47
  vi.mock("@/core/codemirror/editing/commands", () => ({
46
48
  foldAllBulk: vi.fn(),
@@ -60,9 +62,6 @@ vi.mock("../scrollCellIntoView", async (importOriginal) => {
60
62
  };
61
63
  });
62
64
 
63
- const FIRST_COLUMN = 0;
64
- const SECOND_COLUMN = 1;
65
-
66
65
  const { initialNotebookState, reducer, createActions } = exportedForTesting;
67
66
 
68
67
  function formatCells(notebook: NotebookState) {
@@ -78,14 +77,7 @@ function formatCells(notebook: NotebookState) {
78
77
  const cells = cellsId.map((cellId) => cellData[cellId]);
79
78
  return cells.map((cell) => printCell(cell)).join("\n\n");
80
79
  };
81
- const printColumn = (column: CollapsibleTree<CellId>, columnIdx: number) => {
82
- return `> col ${columnIdx}\n${printCells(column.inOrderIds)}`;
83
- };
84
- const columns = cellIds.getColumns();
85
- if (columns.length > 1) {
86
- return wrap(columns.map(printColumn).join("\n\n"));
87
- }
88
- return wrap(printCells(cellIds.inOrderIds));
80
+ return wrap(printCells(cellIds));
89
81
  }
90
82
 
91
83
  function createEditor(content: string) {
@@ -121,8 +113,7 @@ describe("cell reducer", () => {
121
113
  let cells: ReturnType<typeof flattenTopLevelNotebookCells>;
122
114
  let firstCellId: CellId;
123
115
 
124
- const actions = createActions((action) => {
125
- state = reducer(state, action);
116
+ const mountEditors = () => {
126
117
  for (const [cellIdString, handle] of Object.entries(state.cellHandles)) {
127
118
  // @ts-expect-error - Typescript Object.entries doesn't know that keys are CellId
128
119
  const cid: CellId = cellIdString;
@@ -136,7 +127,83 @@ describe("cell reducer", () => {
136
127
  }
137
128
  }
138
129
  cells = flattenTopLevelNotebookCells(state);
139
- });
130
+ };
131
+ const actions = createActions((action) => {
132
+ state = reducer(state, action);
133
+ mountEditors();
134
+ });
135
+
136
+ /** Put a cell into the notebook the way the document would. */
137
+ function seedCell({
138
+ cellId,
139
+ before,
140
+ code = "",
141
+ name,
142
+ config,
143
+ newCellId,
144
+ hideCode,
145
+ autoFocus = true,
146
+ }: {
147
+ cellId: CellId | "__end__";
148
+ before: boolean;
149
+ code?: string;
150
+ name?: string;
151
+ config?: CellConfig;
152
+ newCellId?: CellId;
153
+ hideCode?: boolean;
154
+ autoFocus?: boolean;
155
+ }): CellId {
156
+ const cellIndex =
157
+ cellId === "__end__"
158
+ ? state.cellIds.length
159
+ : state.cellIds.indexOf(cellId);
160
+ const id = newCellId ?? CellId.create();
161
+ const cellIds = [...state.cellIds];
162
+ cellIds.splice(before ? cellIndex : cellIndex + 1, 0, id);
163
+ state = {
164
+ ...state,
165
+ cellIds,
166
+ cellData: {
167
+ ...state.cellData,
168
+ [id]: createCell({
169
+ id,
170
+ code,
171
+ name,
172
+ config: createCellConfig({
173
+ ...config,
174
+ ...(hideCode != null && { hide_code: hideCode }),
175
+ }),
176
+ }),
177
+ },
178
+ cellRuntime: { ...state.cellRuntime, [id]: createCellRuntimeState() },
179
+ cellHandles: { ...state.cellHandles, [id]: createRef() },
180
+ scrollKey: autoFocus ? id : null,
181
+ };
182
+ mountEditors();
183
+ return id;
184
+ }
185
+
186
+ /** Edit a cell's code as the document delivers it. */
187
+ function editCode(cellId: CellId, code: string) {
188
+ const cells = state.cellIds.map((id) => {
189
+ const cell = state.cellData[id];
190
+ return {
191
+ id,
192
+ code: id === cellId ? code : cell.code,
193
+ name: cell.name,
194
+ config: {
195
+ column: cell.config.column ?? null,
196
+ disabled: cell.config.disabled ?? false,
197
+ hide_code: cell.config.hide_code ?? false,
198
+ expand_output: cell.config.expand_output ?? false,
199
+ },
200
+ };
201
+ });
202
+ actions.reconcileDocument({
203
+ order: state.cellIds,
204
+ cells: new Map(cells.map((cell) => [cell.id, cell])),
205
+ });
206
+ }
140
207
 
141
208
  let i = 0;
142
209
  const originalCreate = CellId.create.bind(CellId);
@@ -151,200 +218,16 @@ describe("cell reducer", () => {
151
218
  i = 0;
152
219
 
153
220
  state = initialNotebookState();
154
- state.cellIds = MultiColumn.from([]);
155
- actions.createNewCell({ cellId: "__end__", before: false });
156
- firstCellId = state.cellIds.inOrderIds[0];
157
- });
158
-
159
- afterEach(() => {
160
- documentTransactionTestExports.cancelPendingChanges();
221
+ seedCell({ cellId: "__end__", before: false });
222
+ firstCellId = state.cellIds[0];
161
223
  });
162
224
 
163
225
  afterAll(() => {
164
226
  CellId.create = originalCreate;
165
227
  });
166
228
 
167
- it("can add a cell after another cell", () => {
168
- actions.createNewCell({
169
- cellId: firstCellId,
170
- before: false,
171
- });
172
- expect(formatCells(state)).toMatchInlineSnapshot(`
173
- "
174
- [0] ''
175
-
176
- [1] ''
177
- "
178
- `);
179
- });
180
-
181
- it("can add a cell to the end with code", () => {
182
- actions.createNewCell({
183
- cellId: "__end__",
184
- code: "import numpy as np",
185
- before: false,
186
- });
187
- expect(formatCells(state)).toMatchInlineSnapshot(`
188
- "
189
- [0] ''
190
-
191
- [1] 'import numpy as np'
192
- "
193
- `);
194
-
195
- // Cell should be added to the end and edited
196
- expect(cells[1].code).toBe("import numpy as np");
197
- expect(cells[1].edited).toBe(true);
198
- expect(cells[1].lastCodeRun).toBe(null);
199
- });
200
-
201
- it("can add a cell before another cell", () => {
202
- actions.createNewCell({
203
- cellId: firstCellId,
204
- before: true,
205
- });
206
- expect(formatCells(state)).toMatchInlineSnapshot(`
207
- "
208
- [1] ''
209
-
210
- [0] ''
211
- "
212
- `);
213
- });
214
-
215
- it("can add a cell with name and config", () => {
216
- actions.createNewCell({
217
- cellId: firstCellId,
218
- before: false,
219
- code: "x = 1",
220
- name: "My Cell",
221
- config: { hide_code: true, disabled: false },
222
- });
223
- const newCellId = state.cellIds.inOrderIds[1];
224
- expect(state.cellData[newCellId].name).toBe("My Cell");
225
- expect(state.cellData[newCellId].config.hide_code).toBe(true);
226
- expect(state.cellData[newCellId].config.disabled).toBe(false);
227
- });
228
-
229
- it("can delete a Python cell and undo delete", () => {
230
- actions.createNewCell({
231
- cellId: firstCellId,
232
- before: false,
233
- });
234
- actions.deleteCell({
235
- cellId: firstCellId,
236
- });
237
- expect(formatCells(state)).toMatchInlineSnapshot(`
238
- "
239
- [1] ''
240
- "
241
- `);
242
-
243
- // undo
244
- actions.undoDeleteCell();
245
- expect(formatCells(state)).toMatchInlineSnapshot(`
246
- "
247
- [2] ''
248
-
249
- [1] ''
250
- "
251
- `);
252
-
253
- // Verify scrollKey is set to the restored cell
254
- expect(state.scrollKey).toBe("2");
255
- });
256
-
257
- it("can delete a SQL cell and undo delete", () => {
258
- actions.createNewCell({
259
- cellId: firstCellId,
260
- before: false,
261
- code: `df = mo.sql("""SELECT * FROM table""")`,
262
- });
263
- const newCellId = state.cellIds.getColumns()[0].atOrThrow(1);
264
- actions.deleteCell({
265
- cellId: newCellId,
266
- });
267
- expect(formatCells(state)).toMatchInlineSnapshot(`
268
- "
269
- [0] ''
270
- "
271
- `);
272
-
273
- // undo
274
- actions.undoDeleteCell();
275
- expect(formatCells(state)).toMatchInlineSnapshot(`
276
- "
277
- [0] ''
278
-
279
- [2] 'df = mo.sql("""SELECT * FROM table""")'
280
- "
281
- `);
282
- });
283
-
284
- it("can delete a Markdown cell and undo delete", () => {
285
- const text = "The quick brown fox jumps over the lazy dog.";
286
- actions.createNewCell({
287
- cellId: firstCellId,
288
- before: false,
289
- code: `mo.md(r"""${text}""")`,
290
- });
291
- const newCellId = state.cellIds.getColumns()[0].atOrThrow(1);
292
- actions.deleteCell({
293
- cellId: newCellId,
294
- });
295
- expect(formatCells(state)).toMatchInlineSnapshot(`
296
- "
297
- [0] ''
298
- "
299
- `);
300
-
301
- // undo
302
- actions.undoDeleteCell();
303
- expect(formatCells(state)).toMatchInlineSnapshot(`
304
- "
305
- [0] ''
306
-
307
- [2] 'mo.md(r"""The quick brown fox jumps over the lazy dog.""")'
308
- "
309
- `);
310
- });
311
-
312
- it("undo delete restores cell config (hide_code, disabled)", () => {
313
- actions.createNewCell({
314
- cellId: firstCellId,
315
- before: false,
316
- code: "x = 1",
317
- });
318
- const newCellId = state.cellIds.getColumns()[0].atOrThrow(1);
319
-
320
- // Set config to hidden and disabled
321
- actions.updateCellConfig({
322
- cellId: newCellId,
323
- config: { hide_code: true, disabled: true },
324
- });
325
- expect(state.cellData[newCellId].config.hide_code).toBe(true);
326
- expect(state.cellData[newCellId].config.disabled).toBe(true);
327
-
328
- // Delete the cell
329
- actions.deleteCell({ cellId: newCellId });
330
- expect(state.cellIds.inOrderIds).not.toContain(newCellId);
331
-
332
- // Undo delete
333
- actions.undoDeleteCell();
334
- const restoredCellId =
335
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
336
-
337
- // Config should be preserved
338
- expect(state.cellData[restoredCellId].config.hide_code).toBe(true);
339
- expect(state.cellData[restoredCellId].config.disabled).toBe(true);
340
- });
341
-
342
229
  it("can update a cell", () => {
343
- actions.updateCellCode({
344
- cellId: firstCellId,
345
- code: "import numpy as np",
346
- formattingChange: false,
347
- });
230
+ editCode(firstCellId, "import numpy as np");
348
231
  expect(formatCells(state)).toMatchInlineSnapshot(`
349
232
  "
350
233
  [0] 'import numpy as np'
@@ -352,443 +235,6 @@ describe("cell reducer", () => {
352
235
  `);
353
236
  });
354
237
 
355
- it("can move a cell", () => {
356
- actions.createNewCell({
357
- cellId: firstCellId,
358
- before: false,
359
- });
360
- expect(formatCells(state)).toMatchInlineSnapshot(`
361
- "
362
- [0] ''
363
-
364
- [1] ''
365
- "
366
- `);
367
-
368
- // move first cell to the end
369
- actions.moveCell({
370
- cellId: firstCellId,
371
- before: false,
372
- });
373
- expect(formatCells(state)).toMatchInlineSnapshot(`
374
- "
375
- [1] ''
376
-
377
- [0] ''
378
- "
379
- `);
380
-
381
- // move it back
382
- actions.moveCell({
383
- cellId: firstCellId,
384
- before: true,
385
- });
386
- expect(formatCells(state)).toMatchInlineSnapshot(`
387
- "
388
- [0] ''
389
-
390
- [1] ''
391
- "
392
- `);
393
-
394
- // Add a column breakpoint to test left/right movement
395
- actions.addColumnBreakpoint({ cellId: cellId("1") });
396
- expect(formatCells(state)).toMatchInlineSnapshot(`
397
- "
398
- > col 0
399
- [0] ''
400
-
401
- > col 1
402
- [1] ''
403
- "
404
- `);
405
-
406
- // Move cell right
407
- actions.moveCell({
408
- cellId: firstCellId,
409
- direction: "right",
410
- });
411
- expect(formatCells(state)).toMatchInlineSnapshot(`
412
- "
413
- > col 0
414
-
415
-
416
- > col 1
417
- [0] ''
418
-
419
- [1] ''
420
- "
421
- `);
422
-
423
- // Move cell left
424
- actions.moveCell({
425
- cellId: firstCellId,
426
- direction: "left",
427
- });
428
- expect(formatCells(state)).toMatchInlineSnapshot(`
429
- "
430
- > col 0
431
- [0] ''
432
-
433
- > col 1
434
- [1] ''
435
- "
436
- `);
437
-
438
- // Try to move cell left when it's already in leftmost column (should noop)
439
- actions.moveCell({
440
- cellId: firstCellId,
441
- direction: "left",
442
- });
443
- expect(formatCells(state)).toMatchInlineSnapshot(`
444
- "
445
- > col 0
446
- [0] ''
447
-
448
- > col 1
449
- [1] ''
450
- "
451
- `);
452
-
453
- // Try to move cell right when it's already in rightmost column (should noop)
454
- actions.moveCell({
455
- cellId: cellId("1"),
456
- direction: "right",
457
- });
458
- expect(formatCells(state)).toMatchInlineSnapshot(`
459
- "
460
- > col 0
461
- [0] ''
462
-
463
- > col 1
464
- [1] ''
465
- "
466
- `);
467
- });
468
-
469
- it("can drag and drop a cell", () => {
470
- actions.createNewCell({
471
- cellId: firstCellId,
472
- before: false,
473
- });
474
- actions.createNewCell({
475
- cellId: cellId("1"),
476
- before: false,
477
- });
478
- expect(formatCells(state)).toMatchInlineSnapshot(`
479
- "
480
- [0] ''
481
-
482
- [1] ''
483
-
484
- [2] ''
485
- "
486
- `);
487
-
488
- // drag first cell to the end
489
- actions.dropCellOverCell({
490
- cellId: firstCellId,
491
- overCellId: cellId("2"),
492
- });
493
- expect(formatCells(state)).toMatchInlineSnapshot(`
494
- "
495
- [1] ''
496
-
497
- [2] ''
498
-
499
- [0] ''
500
- "
501
- `);
502
-
503
- // drag it back to the middle
504
- actions.dropCellOverCell({
505
- cellId: firstCellId,
506
- overCellId: cellId("2"),
507
- });
508
- expect(formatCells(state)).toMatchInlineSnapshot(`
509
- "
510
- [1] ''
511
-
512
- [0] ''
513
-
514
- [2] ''
515
- "
516
- `);
517
- });
518
-
519
- it("can move a cell to an exact index within a column", () => {
520
- actions.createNewCell({
521
- cellId: firstCellId,
522
- before: false,
523
- });
524
- actions.createNewCell({
525
- cellId: cellId("1"),
526
- before: false,
527
- });
528
-
529
- const columnId = state.cellIds.atOrThrow(0).id;
530
- actions.moveCellToIndex({
531
- cellId: firstCellId,
532
- columnId,
533
- index: 3,
534
- });
535
-
536
- expect(formatCells(state)).toMatchInlineSnapshot(`
537
- "
538
- [1] ''
539
-
540
- [2] ''
541
-
542
- [0] ''
543
- "
544
- `);
545
- });
546
-
547
- it("can move a cell to an exact index across columns", () => {
548
- actions.createNewCell({
549
- cellId: firstCellId,
550
- before: false,
551
- });
552
- actions.createNewCell({
553
- cellId: cellId("1"),
554
- before: false,
555
- });
556
- actions.addColumnBreakpoint({ cellId: cellId("1") });
557
-
558
- const secondColumnId = state.cellIds.atOrThrow(1).id;
559
- actions.moveCellToIndex({
560
- cellId: firstCellId,
561
- columnId: secondColumnId,
562
- index: 1,
563
- });
564
-
565
- expect(formatCells(state)).toMatchInlineSnapshot(`
566
- "
567
- > col 0
568
-
569
-
570
- > col 1
571
- [1] ''
572
-
573
- [0] ''
574
-
575
- [2] ''
576
- "
577
- `);
578
- });
579
-
580
- it("moveCellToIndex is a no-op when moving to the same position", () => {
581
- actions.createNewCell({
582
- cellId: firstCellId,
583
- before: false,
584
- });
585
- actions.createNewCell({
586
- cellId: cellId("1"),
587
- before: false,
588
- });
589
-
590
- const columnId = state.cellIds.atOrThrow(0).id;
591
- const before = formatCells(state);
592
-
593
- actions.moveCellToIndex({
594
- cellId: firstCellId,
595
- columnId,
596
- index: 0,
597
- });
598
-
599
- expect(formatCells(state)).toBe(before);
600
- });
601
-
602
- it("moveCellToIndex is a no-op for an invalid columnId", () => {
603
- actions.createNewCell({
604
- cellId: firstCellId,
605
- before: false,
606
- });
607
-
608
- const before = formatCells(state);
609
-
610
- actions.moveCellToIndex({
611
- cellId: firstCellId,
612
- columnId: "nonexistent-column" as CellColumnId,
613
- index: 0,
614
- });
615
-
616
- expect(formatCells(state)).toBe(before);
617
- });
618
-
619
- it("can move multiple cells relative to target", () => {
620
- actions.createNewCell({
621
- cellId: firstCellId,
622
- before: false,
623
- });
624
- actions.createNewCell({
625
- cellId: cellId("1"),
626
- before: false,
627
- });
628
- expect(formatCells(state)).toMatchInlineSnapshot(`
629
- "
630
- [0] ''
631
-
632
- [1] ''
633
-
634
- [2] ''
635
- "
636
- `);
637
-
638
- // Move first two cells after the third
639
- actions.moveCellsRelativeTo({
640
- cellIds: [firstCellId, cellId("1")],
641
- targetCellId: cellId("2"),
642
- position: "after",
643
- });
644
- expect(formatCells(state)).toMatchInlineSnapshot(`
645
- "
646
- [2] ''
647
-
648
- [0] ''
649
-
650
- [1] ''
651
- "
652
- `);
653
- });
654
-
655
- it("can undo cut-paste (move with previousPlacements)", () => {
656
- actions.createNewCell({
657
- cellId: firstCellId,
658
- before: false,
659
- });
660
- actions.createNewCell({
661
- cellId: cellId("1"),
662
- before: false,
663
- });
664
- expect(formatCells(state)).toMatchInlineSnapshot(`
665
- "
666
- [0] ''
667
-
668
- [1] ''
669
-
670
- [2] ''
671
- "
672
- `);
673
-
674
- const col = state.cellIds.findWithId(firstCellId);
675
- const previousPlacements = [
676
- {
677
- columnId: col.id,
678
- index: col.indexOfOrThrow(
679
- firstCellId,
680
- ) as import("@/utils/id-tree").CellIndex,
681
- },
682
- {
683
- columnId: col.id,
684
- index: col.indexOfOrThrow(
685
- cellId("1"),
686
- ) as import("@/utils/id-tree").CellIndex,
687
- },
688
- ];
689
-
690
- actions.moveCellsRelativeTo({
691
- cellIds: [firstCellId, cellId("1")],
692
- targetCellId: cellId("2"),
693
- position: "after",
694
- previousPlacements,
695
- });
696
- expect(formatCells(state)).toMatchInlineSnapshot(`
697
- "
698
- [2] ''
699
-
700
- [0] ''
701
-
702
- [1] ''
703
- "
704
- `);
705
-
706
- actions.undoDeleteCell();
707
- expect(formatCells(state)).toMatchInlineSnapshot(`
708
- "
709
- [0] ''
710
-
711
- [1] ''
712
-
713
- [2] ''
714
- "
715
- `);
716
- });
717
-
718
- it("undo order: cut-paste then delete — first undo restores delete, second undo undoes move", () => {
719
- actions.createNewCell({
720
- cellId: firstCellId,
721
- before: false,
722
- });
723
- actions.createNewCell({
724
- cellId: cellId("1"),
725
- before: false,
726
- });
727
-
728
- const col = state.cellIds.findWithId(firstCellId);
729
- const previousPlacements = [
730
- {
731
- columnId: col.id,
732
- index: col.indexOfOrThrow(
733
- firstCellId,
734
- ) as import("@/utils/id-tree").CellIndex,
735
- },
736
- {
737
- columnId: col.id,
738
- index: col.indexOfOrThrow(
739
- cellId("1"),
740
- ) as import("@/utils/id-tree").CellIndex,
741
- },
742
- ];
743
-
744
- actions.moveCellsRelativeTo({
745
- cellIds: [firstCellId, cellId("1")],
746
- targetCellId: cellId("2"),
747
- position: "after",
748
- previousPlacements,
749
- });
750
- expect(formatCells(state)).toMatchInlineSnapshot(`
751
- "
752
- [2] ''
753
-
754
- [0] ''
755
-
756
- [1] ''
757
- "
758
- `);
759
-
760
- actions.deleteCell({ cellId: cellId("2") });
761
- expect(formatCells(state)).toMatchInlineSnapshot(`
762
- "
763
- [0] ''
764
-
765
- [1] ''
766
- "
767
- `);
768
-
769
- actions.undoDeleteCell();
770
- expect(formatCells(state)).toMatchInlineSnapshot(`
771
- "
772
- [3] ''
773
-
774
- [0] ''
775
-
776
- [1] ''
777
- "
778
- `);
779
-
780
- actions.undoDeleteCell();
781
- expect(formatCells(state)).toMatchInlineSnapshot(`
782
- "
783
- [0] ''
784
-
785
- [1] ''
786
-
787
- [3] ''
788
- "
789
- `);
790
- });
791
-
792
238
  it("keeps error evidence after an edit and a new queued run", () => {
793
239
  const code = "raise ValueError('old failure')";
794
240
  const output: OutputMessage = {
@@ -820,11 +266,7 @@ describe("cell reducer", () => {
820
266
  stale_inputs: null,
821
267
  timestamp: 1,
822
268
  });
823
- actions.updateCellCode({
824
- cellId: firstCellId,
825
- code: "print('new code')",
826
- formattingChange: false,
827
- });
269
+ editCode(firstCellId, "print('new code')");
828
270
  actions.prepareForRun({ cellId: firstCellId });
829
271
  actions.handleCellMessage({
830
272
  cell_id: firstCellId,
@@ -835,7 +277,6 @@ describe("cell reducer", () => {
835
277
  timestamp: 2,
836
278
  });
837
279
  const runtime = state.cellRuntime[firstCellId];
838
- expect(state.cellData[firstCellId].lastCodeRun).toBe("print('new code')");
839
280
  expect(runtime.output).toEqual(output);
840
281
  expect(runtime.consoleOutputs[0].code).toBe(code);
841
282
  expect(runtime.consoleOutputs[0].data).toContain("old failure");
@@ -846,21 +287,11 @@ describe("cell reducer", () => {
846
287
  /////////////////
847
288
  // Initial state
848
289
  let cell = cells[0];
849
- expect(cell.status).toBe("idle");
850
- expect(cell.lastCodeRun).toBe(null);
851
- expect(cell.edited).toBe(false);
852
290
  expect(cell).toMatchSnapshot("initial state");
853
291
 
854
292
  // Update code
855
- actions.updateCellCode({
856
- cellId: firstCellId,
857
- code: "import marimo as mo",
858
- formattingChange: false,
859
- });
293
+ editCode(firstCellId, "import marimo as mo");
860
294
  cell = cells[0];
861
- expect(cell.status).toBe("idle");
862
- expect(cell.lastCodeRun).toBe(null);
863
- expect(cell.edited).toBe(true);
864
295
  expect(cell).toMatchSnapshot("code edited");
865
296
 
866
297
  // Prepare for run
@@ -868,9 +299,6 @@ describe("cell reducer", () => {
868
299
  cellId: firstCellId,
869
300
  });
870
301
  cell = cells[0];
871
- expect(cell.status).toBe("queued");
872
- expect(cell.lastCodeRun).toBe("import marimo as mo");
873
- expect(cell.edited).toBe(false);
874
302
  expect(cell).toMatchSnapshot("prepared for run");
875
303
 
876
304
  // Receive queued messages
@@ -883,12 +311,6 @@ describe("cell reducer", () => {
883
311
  timestamp: new Date(10).getTime() as Seconds,
884
312
  });
885
313
  cell = cells[0];
886
- expect(cell.status).toBe("queued");
887
- expect(cell.lastCodeRun).toBe("import marimo as mo");
888
- expect(cell.edited).toBe(false);
889
- expect(cell.runElapsedTimeMs).toBe(null);
890
- expect(cell.runStartTimestamp).toBe(null);
891
- expect(cell.lastRunStartTimestamp).toBe(null);
892
314
  expect(cell).toMatchSnapshot("queued message received");
893
315
 
894
316
  // Receive running messages
@@ -901,12 +323,6 @@ describe("cell reducer", () => {
901
323
  timestamp: new Date(20).getTime() as Seconds,
902
324
  });
903
325
  cell = cells[0];
904
- expect(cell.status).toBe("running");
905
- expect(cell.lastCodeRun).toBe("import marimo as mo");
906
- expect(cell.edited).toBe(false);
907
- expect(cell.runElapsedTimeMs).toBe(null);
908
- expect(cell.runStartTimestamp).toBe(20);
909
- expect(cell.lastRunStartTimestamp).toBe(20);
910
326
  expect(cell).toMatchSnapshot("running message received");
911
327
 
912
328
  // Console messages shouldn't transition status
@@ -924,11 +340,6 @@ describe("cell reducer", () => {
924
340
  timestamp: new Date(22).getTime() as Seconds,
925
341
  });
926
342
  cell = cells[0];
927
- expect(cell.status).toBe("running");
928
- expect(cell.edited).toBe(false);
929
- expect(cell.runElapsedTimeMs).toBe(null);
930
- expect(cell.runStartTimestamp).toBe(20);
931
- expect(cell.lastRunStartTimestamp).toBe(20);
932
343
  expect(cell).toMatchSnapshot("console message received");
933
344
 
934
345
  // Receive output messages
@@ -951,46 +362,23 @@ describe("cell reducer", () => {
951
362
  timestamp: new Date(33).getTime() as Seconds,
952
363
  });
953
364
  cell = cells[0];
954
- expect(cell.status).toBe("idle");
955
- expect(cell.edited).toBe(false);
956
- expect(cell.runElapsedTimeMs).toBe(13_000);
957
- expect(cell.runStartTimestamp).toBe(null);
958
- expect(cell.lastRunStartTimestamp).toBe(20);
959
365
  expect(cell).toMatchSnapshot("output received");
960
366
 
961
367
  // EDITING BACK AND FORTH
962
368
  /////////////////
963
369
  // Update code again
964
- actions.updateCellCode({
965
- cellId: firstCellId,
966
- code: "import marimo as mo\nimport numpy",
967
- formattingChange: false,
968
- });
370
+ editCode(firstCellId, "import marimo as mo\nimport numpy");
969
371
  cell = cells[0];
970
- expect(cell.status).toBe("idle");
971
- expect(cell.edited).toBe(true);
972
372
  expect(cell).toMatchSnapshot("code edited again");
973
373
 
974
374
  // Update code should be unedited
975
- actions.updateCellCode({
976
- cellId: firstCellId,
977
- code: "import marimo as mo",
978
- formattingChange: false,
979
- });
375
+ editCode(firstCellId, "import marimo as mo");
980
376
  cell = cells[0];
981
- expect(cell.edited).toBe(false);
982
377
  expect(cell).toMatchSnapshot("code reverted");
983
378
 
984
379
  // Update code should be edited again
985
- actions.updateCellCode({
986
- cellId: firstCellId,
987
- code: "import marimo as mo\nimport numpy",
988
- formattingChange: false,
989
- });
380
+ editCode(firstCellId, "import marimo as mo\nimport numpy");
990
381
  cell = cells[0];
991
- expect(cell.status).toBe("idle");
992
- expect(cell.lastCodeRun).toBe("import marimo as mo");
993
- expect(cell.edited).toBe(true);
994
382
  expect(cell).toMatchSnapshot("code changed after revert");
995
383
 
996
384
  // ERROR RESPONSE
@@ -1041,11 +429,6 @@ describe("cell reducer", () => {
1041
429
  });
1042
430
  cell = cells[0];
1043
431
  expect(cell.output).not.toBe(null); // keep old output
1044
- expect(cell.status).toBe("idle");
1045
- expect(cell.lastCodeRun).toBe("import marimo as mo\nimport numpy");
1046
- expect(cell.edited).toBe(false);
1047
- expect(cell.runElapsedTimeMs).toBe(11_000);
1048
- expect(cell.runStartTimestamp).toBe(null);
1049
432
  expect(cell).toMatchSnapshot("error response received");
1050
433
 
1051
434
  // INTERRUPT RESPONSE
@@ -1093,96 +476,20 @@ describe("cell reducer", () => {
1093
476
  timestamp: new Date(61).getTime() as Seconds,
1094
477
  });
1095
478
  cell = cells[0];
1096
- expect(cell.status).toBe("idle");
1097
- expect(cell.lastCodeRun).toBe(cell.code);
1098
- expect(cell.edited).toBe(false);
1099
- expect(cell.runElapsedTimeMs).toBe(11_000);
1100
- expect(cell.runStartTimestamp).toBe(null);
1101
479
  expect(cell).toMatchSnapshot("interruption response received");
1102
480
  });
1103
481
 
1104
- it("errors reset status to idle", () => {
1105
- // Initial state
1106
- let cell = cells[0];
1107
- expect(cell.status).toBe("idle");
1108
- expect(cell.lastCodeRun).toBe(null);
1109
- expect(cell.edited).toBe(false);
1110
- expect(cell).toMatchSnapshot("initial state");
1111
-
1112
- // Update code
1113
- actions.updateCellCode({
1114
- cellId: firstCellId,
1115
- code: "import marimo as mo",
1116
- formattingChange: false,
1117
- });
1118
- cell = cells[0];
1119
- expect(cell.status).toBe("idle");
1120
- expect(cell.lastCodeRun).toBe(null);
1121
- expect(cell.edited).toBe(true);
1122
- expect(cell).toMatchSnapshot("code edited");
1123
-
1124
- // Prepare for run
1125
- actions.prepareForRun({
1126
- cellId: firstCellId,
1127
- });
1128
- cell = cells[0];
1129
- expect(cell.status).toBe("queued");
1130
- expect(cell.lastCodeRun).toBe("import marimo as mo");
1131
- expect(cell.edited).toBe(false);
1132
- expect(cell).toMatchSnapshot("prepared for run");
1133
-
1134
- // ERROR RESPONSE
1135
- //
1136
- // should reset status to idle
1137
- /////////////////
1138
- // Prepare for run
1139
- actions.prepareForRun({
1140
- cellId: firstCellId,
1141
- });
1142
- cell = cells[0];
1143
- // Receive error
1144
- actions.handleCellMessage({
1145
- cell_id: firstCellId,
1146
- output: {
1147
- channel: "marimo-error",
1148
- mimetype: "application/vnd.marimo+error",
1149
- data: [
1150
- { type: "exception", exception_type: "SyntaxError", msg: "Oh no!" },
1151
- ],
1152
- timestamp: 0,
1153
- },
1154
- console: null,
1155
- stale_inputs: null,
1156
- timestamp: new Date(61).getTime() as Seconds,
1157
- });
1158
- cell = cells[0];
1159
- expect(cell.status).toBe("idle");
1160
- expect(cell.lastCodeRun).toBe("import marimo as mo");
1161
- expect(cell.edited).toBe(false);
1162
- expect(cell.runElapsedTimeMs).toBe(null);
1163
- expect(cell.runStartTimestamp).toBe(null);
1164
- expect(cell).toMatchSnapshot("error response received");
1165
- });
1166
-
1167
482
  it("can run a stale cell", () => {
1168
- // Update code of first cell
1169
- actions.updateCellCode({
1170
- cellId: firstCellId,
1171
- code: "import marimo as mo",
1172
- formattingChange: false,
1173
- });
483
+ // Update code of first cell
484
+ editCode(firstCellId, "import marimo as mo");
1174
485
  // Add cell
1175
- actions.createNewCell({
486
+ seedCell({
1176
487
  cellId: firstCellId,
1177
488
  before: false,
1178
489
  });
1179
490
  const secondCell = cells[1].id;
1180
491
  // Update code
1181
- actions.updateCellCode({
1182
- code: "mo.slider()",
1183
- cellId: secondCell,
1184
- formattingChange: false,
1185
- });
492
+ editCode(secondCell, "mo.slider()");
1186
493
 
1187
494
  // Prepare for run
1188
495
  actions.prepareForRun({
@@ -1199,11 +506,6 @@ describe("cell reducer", () => {
1199
506
  timestamp: new Date(10).getTime() as Seconds,
1200
507
  });
1201
508
  let cell = cells[1];
1202
- expect(cell.status).toBe("queued");
1203
- expect(cell.lastCodeRun).toBe("mo.slider()");
1204
- expect(cell.edited).toBe(false);
1205
- expect(cell.runElapsedTimeMs).toBe(null);
1206
- expect(cell.runStartTimestamp).toBe(null);
1207
509
  expect(cell).toMatchSnapshot("queued message received");
1208
510
 
1209
511
  // Receive stale message
@@ -1216,101 +518,12 @@ describe("cell reducer", () => {
1216
518
  timestamp: new Date(20).getTime() as Seconds,
1217
519
  });
1218
520
  cell = cells[1];
1219
- expect(cell.status).toBe("queued");
1220
- expect(cell.lastCodeRun).toBe("mo.slider()");
1221
- expect(cell.edited).toBe(false);
1222
- expect(cell.runElapsedTimeMs).toBe(null);
1223
- expect(cell.runStartTimestamp).toBe(null);
1224
521
  expect(cell).toMatchSnapshot("stale inputs received");
1225
522
  });
1226
523
 
1227
- it("can format code and update cell", () => {
1228
- const firstCellId = cells[0].id;
1229
- actions.updateCellCode({
1230
- cellId: firstCellId,
1231
- code: "import marimo as mo",
1232
- formattingChange: false,
1233
- });
1234
- let cell = cells[0];
1235
- expect(cell.status).toBe("idle");
1236
- expect(cell.lastCodeRun).toBe(null);
1237
- expect(cell.edited).toBe(true);
1238
-
1239
- // Run
1240
- actions.prepareForRun({
1241
- cellId: firstCellId,
1242
- });
1243
- actions.handleCellMessage({
1244
- cell_id: firstCellId,
1245
- output: {
1246
- channel: "output",
1247
- mimetype: "text/plain",
1248
- data: "ok",
1249
- timestamp: 0,
1250
- },
1251
- console: {
1252
- channel: "stdout",
1253
- mimetype: "text/plain",
1254
- data: "hello!",
1255
- timestamp: 0,
1256
- },
1257
- status: "idle",
1258
- stale_inputs: null,
1259
- timestamp: new Date(33).getTime() as Seconds,
1260
- });
1261
-
1262
- // Check steady state
1263
- cell = cells[0];
1264
- expect(cell.status).toBe("idle");
1265
- expect(cell.edited).toBe(false);
1266
- expect(cell.lastCodeRun).toBe("import marimo as mo");
1267
-
1268
- // Format code
1269
- actions.updateCellCode({
1270
- cellId: firstCellId,
1271
- code: "import marimo as mo",
1272
- formattingChange: true,
1273
- });
1274
- cell = cells[0];
1275
- expect(cell.status).toBe("idle");
1276
- expect(cell.lastCodeRun).toBe("import marimo as mo");
1277
- expect(cell.edited).toBe(false);
1278
- });
1279
-
1280
- it("can update a cells config", () => {
1281
- const firstCellId = cells[0].id;
1282
- let cell = cells[0];
1283
- // Starts empty
1284
- expect(cell.config).toEqual({
1285
- disabled: false,
1286
- hide_code: false,
1287
- column: null,
1288
- expand_output: false,
1289
- });
1290
-
1291
- actions.updateCellConfig({
1292
- cellId: firstCellId,
1293
- config: { disabled: true },
1294
- });
1295
- cell = cells[0];
1296
- expect(cell.config.disabled).toBe(true);
1297
-
1298
- // Revert
1299
- actions.updateCellConfig({
1300
- cellId: firstCellId,
1301
- config: { disabled: false },
1302
- });
1303
- cell = cells[0];
1304
- expect(cell.config.disabled).toBe(false);
1305
- });
1306
-
1307
524
  it("can run a stopped cell", () => {
1308
525
  // Update code of first cell
1309
- actions.updateCellCode({
1310
- cellId: firstCellId,
1311
- code: "mo.md('This has an ancestor that was stopped')",
1312
- formattingChange: false,
1313
- });
526
+ editCode(firstCellId, "mo.md('This has an ancestor that was stopped')");
1314
527
 
1315
528
  // Prepare for run
1316
529
  actions.prepareForRun({
@@ -1327,13 +540,6 @@ describe("cell reducer", () => {
1327
540
  timestamp: new Date(10).getTime() as Seconds,
1328
541
  });
1329
542
  let cell = cells[0];
1330
- expect(cell.status).toBe("queued");
1331
- expect(cell.lastCodeRun).toBe(
1332
- "mo.md('This has an ancestor that was stopped')",
1333
- );
1334
- expect(cell.edited).toBe(false);
1335
- expect(cell.runElapsedTimeMs).toBe(null);
1336
- expect(cell.runStartTimestamp).toBe(null);
1337
543
  expect(cell).toMatchSnapshot("queued message received");
1338
544
 
1339
545
  // Receive idle message
@@ -1369,12 +575,10 @@ describe("cell reducer", () => {
1369
575
  timestamp: new Date(20).getTime() as Seconds,
1370
576
  });
1371
577
  cell = cells[0];
1372
- expect(cell.status).toBe("idle");
1373
578
  // oxlint-disable-next-line typescript/no-explicit-any no-unsafe-optional-chaining
1374
579
  expect((cell.output?.data as any)[0].msg).toBe(
1375
580
  "This cell wasn't run because an ancestor was stopped with `mo.stop`: ",
1376
581
  );
1377
- expect(cell.stopped).toBe(true);
1378
582
  expect(cell).toMatchSnapshot("ancestor stopped");
1379
583
 
1380
584
  // Receive queued message
@@ -1387,8 +591,6 @@ describe("cell reducer", () => {
1387
591
  timestamp: new Date(30).getTime() as Seconds,
1388
592
  });
1389
593
  cell = cells[0];
1390
- expect(cell.status).toBe("queued");
1391
- expect(cell.stopped).toBe(true);
1392
594
  expect(cell).toMatchSnapshot("queued after stop");
1393
595
 
1394
596
  // Receive running message
@@ -1401,8 +603,6 @@ describe("cell reducer", () => {
1401
603
  timestamp: new Date(40).getTime() as Seconds,
1402
604
  });
1403
605
  cell = cells[0];
1404
- expect(cell.status).toBe("running");
1405
- expect(cell.stopped).toBe(false);
1406
606
  expect(cell.output).toBe(null);
1407
607
  expect(cell).toMatchSnapshot("running after stop");
1408
608
  });
@@ -1429,7 +629,6 @@ describe("cell reducer", () => {
1429
629
 
1430
630
  // Initial state
1431
631
  let cell = cells[0];
1432
- expect(cell.status).toBe("idle");
1433
632
  expect(cell.consoleOutputs).toEqual([]);
1434
633
  expect(cell).toMatchSnapshot("initial state");
1435
634
 
@@ -1438,7 +637,6 @@ describe("cell reducer", () => {
1438
637
  cellId: firstCellId,
1439
638
  });
1440
639
  cell = cells[0];
1441
- expect(cell.status).toBe("queued");
1442
640
  expect(cell.consoleOutputs).toEqual([]);
1443
641
  expect(cell).toMatchSnapshot("prepared for run");
1444
642
 
@@ -1452,7 +650,6 @@ describe("cell reducer", () => {
1452
650
  timestamp: new Date(10).getTime() as Seconds,
1453
651
  });
1454
652
  cell = cells[0];
1455
- expect(cell.status).toBe("queued");
1456
653
  expect(cell).toMatchSnapshot("queued message received");
1457
654
 
1458
655
  // Receive running messages
@@ -1465,7 +662,6 @@ describe("cell reducer", () => {
1465
662
  timestamp: new Date(20).getTime() as Seconds,
1466
663
  });
1467
664
  cell = cells[0];
1468
- expect(cell.status).toBe("running");
1469
665
  expect(cell).toMatchSnapshot("running message received");
1470
666
 
1471
667
  // Add console
@@ -1478,12 +674,10 @@ describe("cell reducer", () => {
1478
674
  timestamp: new Date(22).getTime() as Seconds,
1479
675
  });
1480
676
  cell = cells[0];
1481
- expect(cell.status).toBe("running");
1482
677
  expect(cell).toMatchSnapshot("stdout received");
1483
678
 
1484
679
  cell = cells[0];
1485
680
  expect(cell.consoleOutputs[0]).toMatchObject(STDOUT);
1486
- expect(cell.status).toBe("running");
1487
681
  expect(cell).toMatchSnapshot("stdout verified");
1488
682
 
1489
683
  // Ask via stdin
@@ -1498,7 +692,6 @@ describe("cell reducer", () => {
1498
692
  cell = cells[0];
1499
693
  expect(cell.consoleOutputs[0]).toMatchObject(STDOUT);
1500
694
  expect(cell.consoleOutputs[1]).toMatchObject(STD_IN_1);
1501
- expect(cell.status).toBe("running");
1502
695
  expect(cell).toMatchSnapshot("stdin requested");
1503
696
 
1504
697
  // Response to stdin
@@ -1512,7 +705,6 @@ describe("cell reducer", () => {
1512
705
  ...STD_IN_1,
1513
706
  response: "Marimo!",
1514
707
  });
1515
- expect(cell.status).toBe("running");
1516
708
  expect(cell).toMatchSnapshot("stdin answered");
1517
709
 
1518
710
  // Ask via stdin, again
@@ -1530,7 +722,6 @@ describe("cell reducer", () => {
1530
722
  { ...STD_IN_1, response: "Marimo!" },
1531
723
  STD_IN_2,
1532
724
  ]);
1533
- expect(cell.status).toBe("running");
1534
725
  expect(cell).toMatchSnapshot("second stdin requested");
1535
726
 
1536
727
  // Interrupt, so we respond with ""
@@ -1548,7 +739,6 @@ describe("cell reducer", () => {
1548
739
  timestamp: new Date(61).getTime() as Seconds,
1549
740
  });
1550
741
  cell = cells[0];
1551
- expect(cell.status).toBe("idle");
1552
742
  expect(cell.consoleOutputs).toMatchObject([
1553
743
  STDOUT,
1554
744
  { ...STD_IN_1, response: "Marimo!" },
@@ -1579,7 +769,9 @@ describe("cell reducer", () => {
1579
769
  response: secret,
1580
770
  });
1581
771
 
1582
- expect(cells[0].consoleOutputs).toEqual([{ ...prompt, response: "" }]);
772
+ expect(cells[0].consoleOutputs).toEqual([
773
+ { ...prompt, id: 1, response: "" },
774
+ ]);
1583
775
  expect(JSON.stringify(cells[0].consoleOutputs)).not.toContain(secret);
1584
776
  });
1585
777
 
@@ -1640,7 +832,6 @@ describe("cell reducer", () => {
1640
832
 
1641
833
  // Initial state
1642
834
  let cell = cells[0];
1643
- expect(cell.status).toBe("idle");
1644
835
  expect(cell.consoleOutputs).toEqual([]);
1645
836
  expect(cell).toMatchSnapshot("initial state");
1646
837
 
@@ -1659,7 +850,6 @@ describe("cell reducer", () => {
1659
850
  cellId: firstCellId,
1660
851
  });
1661
852
  cell = cells[0];
1662
- expect(cell.status).toBe("queued");
1663
853
  expect(cell.consoleOutputs).toMatchObject([OLD_STDOUT]); // Old stays there until it starts running
1664
854
  expect(cell).toMatchSnapshot("prepared for run");
1665
855
 
@@ -1673,7 +863,6 @@ describe("cell reducer", () => {
1673
863
  timestamp: new Date(10).getTime() as Seconds,
1674
864
  });
1675
865
  cell = cells[0];
1676
- expect(cell.status).toBe("queued");
1677
866
  expect(cell.consoleOutputs).toMatchObject([OLD_STDOUT]); // Old stays there until it starts running
1678
867
  expect(cell).toMatchSnapshot("queued message received");
1679
868
 
@@ -1687,7 +876,6 @@ describe("cell reducer", () => {
1687
876
  timestamp: new Date(20).getTime() as Seconds,
1688
877
  });
1689
878
  cell = cells[0];
1690
- expect(cell.status).toBe("running");
1691
879
  expect(cell.consoleOutputs).toMatchObject([]);
1692
880
  expect(cell).toMatchSnapshot("console cleared while running");
1693
881
 
@@ -1697,610 +885,39 @@ describe("cell reducer", () => {
1697
885
  output: undefined,
1698
886
  console: [STDOUT],
1699
887
  status: "idle",
1700
- stale_inputs: null,
1701
- timestamp: new Date(22).getTime() as Seconds,
1702
- });
1703
- cell = cells[0];
1704
- expect(cell.consoleOutputs).toMatchObject([STDOUT]);
1705
- expect(cell.status).toBe("idle");
1706
- expect(cell).toMatchSnapshot("console received while idle");
1707
- });
1708
-
1709
- it("can send a cell to the top", () => {
1710
- actions.createNewCell({ cellId: firstCellId, before: false });
1711
- actions.createNewCell({ cellId: cellId("1"), before: false });
1712
- actions.sendToTop({ cellId: cellId("2") });
1713
- expect(formatCells(state)).toMatchInlineSnapshot(`
1714
- "
1715
- [2] ''
1716
-
1717
- [0] ''
1718
-
1719
- [1] ''
1720
- "
1721
- `);
1722
- });
1723
-
1724
- it("can send a cell to the bottom", () => {
1725
- actions.createNewCell({ cellId: firstCellId, before: false });
1726
- actions.createNewCell({ cellId: cellId("1"), before: false });
1727
- actions.sendToBottom({ cellId: firstCellId });
1728
- expect(formatCells(state)).toMatchInlineSnapshot(`
1729
- "
1730
- [1] ''
1731
-
1732
- [2] ''
1733
-
1734
- [0] ''
1735
- "
1736
- `);
1737
- });
1738
-
1739
- it("can focus cells", () => {
1740
- actions.createNewCell({ cellId: firstCellId, before: false });
1741
- actions.createNewCell({ cellId: cellId("1"), before: false });
1742
-
1743
- actions.focusCell({ cellId: cellId("1"), where: "before" });
1744
- expect(focusAndScrollCellIntoView).toHaveBeenCalledWith(
1745
- expect.objectContaining({
1746
- cellId: cellId("0"),
1747
- }),
1748
- );
1749
-
1750
- actions.focusTopCell();
1751
- expect(scrollToTop).toHaveBeenCalled();
1752
-
1753
- actions.focusBottomCell();
1754
- expect(scrollToBottom).toHaveBeenCalled();
1755
- });
1756
-
1757
- it("can update cell name", () => {
1758
- actions.updateCellName({ cellId: firstCellId, name: "Test Cell" });
1759
- expect(state.cellData[firstCellId].name).toBe("Test Cell");
1760
- });
1761
-
1762
- it("can set cell IDs and codes", () => {
1763
- const newIds = [cellId("3"), cellId("4"), cellId("5")];
1764
- const newCodes = ["code1", "code2", "code3"];
1765
-
1766
- actions.setCellIds({ cellIds: newIds });
1767
- expect(state.cellIds.atOrThrow(FIRST_COLUMN).topLevelIds).toEqual(newIds);
1768
-
1769
- // When codeIsStale is false, lastCodeRun should match code
1770
- actions.setCellCodes({
1771
- codes: newCodes,
1772
- ids: newIds,
1773
- codeIsStale: false,
1774
- });
1775
- newIds.forEach((id, index) => {
1776
- expect(state.cellData[id].code).toBe(newCodes[index]);
1777
- expect(state.cellData[id].lastCodeRun).toBe(newCodes[index]);
1778
- expect(state.cellData[id].edited).toBe(false);
1779
- });
1780
-
1781
- // When codeIsStale is true, lastCodeRun should not change
1782
- const staleCodes = ["stale1", "stale2", "stale3"];
1783
- actions.setCellCodes({
1784
- codes: staleCodes,
1785
- ids: newIds,
1786
- codeIsStale: true,
1787
- });
1788
- newIds.forEach((id, index) => {
1789
- expect(state.cellData[id].code).toBe(staleCodes[index]);
1790
- expect(state.cellData[id].lastCodeRun).toBe(newCodes[index]);
1791
- expect(state.cellData[id].edited).toBe(true);
1792
- });
1793
- });
1794
-
1795
- it("can can add a new cell with/without stale code", () => {
1796
- actions.setCellCodes({
1797
- codes: ["new code"],
1798
- ids: [cellId("2")],
1799
- codeIsStale: false,
1800
- });
1801
-
1802
- expect(state.cellData[cellId("2")].code).toBe("new code");
1803
- expect(state.cellData[cellId("2")].edited).toBe(false);
1804
- expect(state.cellData[cellId("2")].lastCodeRun).toBe("new code");
1805
-
1806
- actions.setCellCodes({
1807
- codes: ["new code 2"],
1808
- ids: [cellId("9")],
1809
- codeIsStale: true,
1810
- });
1811
-
1812
- expect(state.cellData[cellId("9")].code).toBe("new code 2");
1813
- expect(state.cellData[cellId("9")].edited).toBe(true);
1814
- expect(state.cellData[cellId("9")].lastCodeRun).toBe(null);
1815
- });
1816
-
1817
- it("can partial update cell codes", () => {
1818
- actions.createNewCell({ cellId: firstCellId, before: false });
1819
- actions.createNewCell({ cellId: cellId("1"), before: false });
1820
-
1821
- expect(state.cellIds.inOrderIds).toEqual([
1822
- cellId("0"),
1823
- cellId("1"),
1824
- cellId("2"),
1825
- ]);
1826
- expect(state.cellData[cellId("0")].code).toBe("");
1827
- expect(state.cellData[cellId("1")].code).toBe("");
1828
- expect(state.cellData[cellId("2")].code).toBe("");
1829
-
1830
- // Update cell 1
1831
- actions.setCellCodes({
1832
- codes: ["new code 2"],
1833
- ids: [cellId("1")],
1834
- codeIsStale: false,
1835
- });
1836
-
1837
- expect(state.cellIds.inOrderIds).toEqual([
1838
- cellId("0"),
1839
- cellId("1"),
1840
- cellId("2"),
1841
- ]);
1842
- expect(state.cellData[cellId("0")].code).toBe("");
1843
- expect(state.cellData[cellId("1")].code).toBe("new code 2");
1844
- expect(state.cellData[cellId("1")].edited).toBe(false);
1845
- expect(state.cellData[cellId("2")].code).toBe("");
1846
- });
1847
-
1848
- it("can set cell codes with new cell ids, while preserving the old cell data", () => {
1849
- actions.setCellCodes({
1850
- codes: ["code1", "code2", "code3"],
1851
- ids: [cellId("3"), cellId("4"), cellId("5")],
1852
- codeIsStale: false,
1853
- });
1854
- expect(state.cellData[cellId("3")].code).toBe("code1");
1855
- expect(state.cellData[cellId("4")].code).toBe("code2");
1856
- expect(state.cellData[cellId("5")].code).toBe("code3");
1857
-
1858
- // Update with some new cell ids and some old cell ids
1859
- actions.setCellIds({
1860
- cellIds: [cellId("1"), cellId("2"), cellId("3"), cellId("4")],
1861
- });
1862
- actions.setCellCodes({
1863
- codes: ["new1", "new2", "code1", "code2"],
1864
- ids: [cellId("1"), cellId("2"), cellId("3"), cellId("4")],
1865
- codeIsStale: false,
1866
- });
1867
- expect(state.cellData[cellId("1")].code).toBe("new1");
1868
- expect(state.cellData[cellId("2")].code).toBe("new2");
1869
- expect(state.cellData[cellId("3")].code).toBe("code1");
1870
- expect(state.cellData[cellId("4")].code).toBe("code2");
1871
- expect(state.cellIds.inOrderIds).toEqual([
1872
- cellId("1"),
1873
- cellId("2"),
1874
- cellId("3"),
1875
- cellId("4"),
1876
- ]);
1877
- // Cell 5 data is preserved (possibly used for tracing), but it's not in the cellIds
1878
- expect(state.cellData[cellId("5")]).not.toBeUndefined();
1879
- });
1880
-
1881
- it("can set cell codes with names and configs", () => {
1882
- const newIds = [cellId("3"), cellId("4")];
1883
- actions.setCellIds({ cellIds: newIds });
1884
- actions.setCellCodes({
1885
- codes: ["code1", "code2"],
1886
- ids: newIds,
1887
- codeIsStale: false,
1888
- names: ["setup_cell", "analysis"],
1889
- configs: [
1890
- { hide_code: true, disabled: false, column: null },
1891
- { hide_code: false, disabled: true, column: null },
1892
- ],
1893
- });
1894
-
1895
- expect(state.cellData[cellId("3")].name).toBe("setup_cell");
1896
- expect(state.cellData[cellId("3")].config.hide_code).toBe(true);
1897
- expect(state.cellData[cellId("3")].config.disabled).toBe(false);
1898
-
1899
- expect(state.cellData[cellId("4")].name).toBe("analysis");
1900
- expect(state.cellData[cellId("4")].config.hide_code).toBe(false);
1901
- expect(state.cellData[cellId("4")].config.disabled).toBe(true);
1902
- });
1903
-
1904
- it("can set cell codes without names/configs (backward compat)", () => {
1905
- const newIds = [cellId("3")];
1906
- actions.setCellIds({ cellIds: newIds });
1907
- actions.setCellCodes({
1908
- codes: ["code1"],
1909
- ids: newIds,
1910
- codeIsStale: false,
1911
- });
1912
-
1913
- // Should use defaults when names/configs not provided
1914
- expect(state.cellData[cellId("3")].code).toBe("code1");
1915
- expect(state.cellData[cellId("3")].config.hide_code).toBe(false);
1916
- expect(state.cellData[cellId("3")].config.disabled).toBe(false);
1917
- });
1918
-
1919
- it("can update names and configs on existing cells via setCellCodes", () => {
1920
- // Set initial state
1921
- actions.setCellCodes({
1922
- codes: ["x = 1"],
1923
- ids: [firstCellId],
1924
- codeIsStale: false,
1925
- names: ["old_name"],
1926
- configs: [{ hide_code: false, disabled: false, column: null }],
1927
- });
1928
- expect(state.cellData[firstCellId].name).toBe("old_name");
1929
- expect(state.cellData[firstCellId].config.hide_code).toBe(false);
1930
-
1931
- // Update with new name and config (same code)
1932
- actions.setCellCodes({
1933
- codes: ["x = 1"],
1934
- ids: [firstCellId],
1935
- codeIsStale: true,
1936
- names: ["new_name"],
1937
- configs: [{ hide_code: true, disabled: false, column: null }],
1938
- });
1939
- expect(state.cellData[firstCellId].name).toBe("new_name");
1940
- expect(state.cellData[firstCellId].config.hide_code).toBe(true);
1941
- });
1942
-
1943
- it("can fold and unfold all cells", () => {
1944
- actions.foldAll();
1945
- expect(foldAllBulk).toHaveBeenCalled();
1946
-
1947
- actions.unfoldAll();
1948
- expect(unfoldAllBulk).toHaveBeenCalled();
1949
- });
1950
-
1951
- it("can clear logs", () => {
1952
- state.cellLogs = [
1953
- {
1954
- level: "stderr",
1955
- message: "log1",
1956
- timestamp: 0,
1957
- source: { type: "cell", cellId: firstCellId },
1958
- },
1959
- {
1960
- level: "stderr",
1961
- message: "log1",
1962
- timestamp: 0,
1963
- source: { type: "cell", cellId: firstCellId },
1964
- },
1965
- ];
1966
- actions.clearLogs();
1967
- expect(state.cellLogs).toEqual([]);
1968
- });
1969
-
1970
- it("can collapse and expand cells", () => {
1971
- actions.createNewCell({ cellId: firstCellId, before: false });
1972
- actions.createNewCell({
1973
- cellId: cellId("1"),
1974
- before: false,
1975
- code: "# Header",
1976
- });
1977
- actions.createNewCell({
1978
- cellId: cellId("2"),
1979
- before: false,
1980
- code: "## Subheader",
1981
- });
1982
-
1983
- const id = state.cellIds.atOrThrow(FIRST_COLUMN).atOrThrow(1);
1984
- state.cellRuntime[id] = {
1985
- ...state.cellRuntime[id],
1986
- outline: {
1987
- items: [{ name: "Header", level: 1, by: { id: "header" } }],
1988
- },
1989
- };
1990
-
1991
- actions.collapseCell({ cellId: id });
1992
- expect(state.cellIds.atOrThrow(FIRST_COLUMN).isCollapsed(id)).toBe(true);
1993
-
1994
- actions.expandCell({ cellId: id });
1995
- expect(state.cellIds.atOrThrow(FIRST_COLUMN).isCollapsed(id)).toBe(false);
1996
- });
1997
-
1998
- it("can collapse and expand all cells in multiple columns", () => {
1999
- actions.createNewCell({ cellId: firstCellId, before: false });
2000
- actions.createNewCell({
2001
- cellId: cellId("1"),
2002
- before: false,
2003
- code: "# First Column Header",
2004
- });
2005
- actions.createNewCell({
2006
- cellId: cellId("2"),
2007
- before: false,
2008
- code: "## First Column Subheader",
2009
- });
2010
-
2011
- actions.addColumnBreakpoint({ cellId: cellId("2") });
2012
-
2013
- actions.createNewCell({
2014
- cellId: cellId("3"),
2015
- before: false,
2016
- code: "# Second Column Header",
2017
- });
2018
- actions.createNewCell({
2019
- cellId: cellId("4"),
2020
- before: false,
2021
- code: "## Second Column Subheader",
2022
- });
2023
-
2024
- const firstColumnHeaderId = state.cellIds
2025
- .atOrThrow(FIRST_COLUMN)
2026
- .atOrThrow(1);
2027
- state.cellRuntime[firstColumnHeaderId] = {
2028
- ...state.cellRuntime[firstColumnHeaderId],
2029
- outline: {
2030
- items: [
2031
- { name: "First Column Header", level: 1, by: { id: "header" } },
2032
- ],
2033
- },
2034
- };
2035
-
2036
- const secondColumnHeaderId = state.cellIds
2037
- .atOrThrow(SECOND_COLUMN)
2038
- .atOrThrow(0);
2039
- state.cellRuntime[secondColumnHeaderId] = {
2040
- ...state.cellRuntime[secondColumnHeaderId],
2041
- outline: {
2042
- items: [
2043
- { name: "Second Column Header", level: 1, by: { id: "header" } },
2044
- ],
2045
- },
2046
- };
2047
-
2048
- actions.collapseAllCells();
2049
- expect(
2050
- state.cellIds.atOrThrow(FIRST_COLUMN).isCollapsed(firstColumnHeaderId),
2051
- ).toBe(true);
2052
- expect(
2053
- state.cellIds.atOrThrow(SECOND_COLUMN).isCollapsed(secondColumnHeaderId),
2054
- ).toBe(true);
2055
-
2056
- actions.expandAllCells();
2057
- expect(
2058
- state.cellIds.atOrThrow(FIRST_COLUMN).isCollapsed(firstColumnHeaderId),
2059
- ).toBe(false);
2060
- expect(
2061
- state.cellIds.atOrThrow(SECOND_COLUMN).isCollapsed(secondColumnHeaderId),
2062
- ).toBe(false);
2063
- });
2064
-
2065
- it("can collapse and expand nested cells in one call", () => {
2066
- actions.createNewCell({ cellId: firstCellId, before: false });
2067
- actions.createNewCell({
2068
- cellId: cellId("1"),
2069
- before: false,
2070
- code: "# Header",
2071
- });
2072
- actions.createNewCell({
2073
- cellId: cellId("2"),
2074
- before: false,
2075
- code: "## Subheader",
2076
- });
2077
- actions.createNewCell({
2078
- cellId: cellId("3"),
2079
- before: false,
2080
- code: "### Subsubheader",
2081
- });
2082
-
2083
- const headerId = state.cellIds.atOrThrow(FIRST_COLUMN).atOrThrow(1);
2084
- state.cellRuntime[headerId] = {
2085
- ...state.cellRuntime[headerId],
2086
- outline: {
2087
- items: [{ name: "Header", level: 1, by: { id: "header" } }],
2088
- },
2089
- };
2090
-
2091
- const subheaderId = state.cellIds.atOrThrow(FIRST_COLUMN).atOrThrow(2);
2092
- state.cellRuntime[subheaderId] = {
2093
- ...state.cellRuntime[subheaderId],
2094
- outline: {
2095
- items: [{ name: "Subheader", level: 2, by: { id: "subheader" } }],
2096
- },
2097
- };
2098
-
2099
- // Check if both the parent and child are collapsed
2100
- actions.collapseAllCells();
2101
- expect(state.cellIds.atOrThrow(FIRST_COLUMN).isCollapsed(headerId)).toBe(
2102
- true,
2103
- );
2104
- actions.expandCell({ cellId: headerId });
2105
- expect(state.cellIds.atOrThrow(FIRST_COLUMN).isCollapsed(subheaderId)).toBe(
2106
- true,
2107
- );
2108
- actions.collapseCell({ cellId: headerId });
2109
-
2110
- // Check if both the parent and child are expanded
2111
- actions.expandAllCells();
2112
- expect(state.cellIds.atOrThrow(FIRST_COLUMN).isCollapsed(headerId)).toBe(
2113
- false,
2114
- );
2115
- expect(state.cellIds.atOrThrow(FIRST_COLUMN).isCollapsed(subheaderId)).toBe(
2116
- false,
2117
- );
2118
- });
2119
-
2120
- it("can collapse all cells when nested heading ranges end at the same cell", () => {
2121
- actions.createNewCell({ cellId: firstCellId, before: false });
2122
- actions.createNewCell({
2123
- cellId: cellId("1"),
2124
- before: false,
2125
- code: "# Header",
2126
- });
2127
- actions.createNewCell({
2128
- cellId: cellId("2"),
2129
- before: false,
2130
- code: "## Subheader",
2131
- });
2132
- actions.createNewCell({
2133
- cellId: cellId("3"),
2134
- before: false,
2135
- code: "### Subsubheader",
2136
- });
2137
-
2138
- const headerId = state.cellIds.atOrThrow(FIRST_COLUMN).atOrThrow(1);
2139
- state.cellRuntime[headerId] = {
2140
- ...state.cellRuntime[headerId],
2141
- outline: {
2142
- items: [{ name: "Header", level: 1, by: { id: "header" } }],
2143
- },
2144
- };
2145
-
2146
- const subheaderId = state.cellIds.atOrThrow(FIRST_COLUMN).atOrThrow(2);
2147
- state.cellRuntime[subheaderId] = {
2148
- ...state.cellRuntime[subheaderId],
2149
- outline: {
2150
- items: [{ name: "Subheader", level: 2, by: { id: "subheader" } }],
2151
- },
2152
- };
2153
-
2154
- const subsubheaderId = state.cellIds.atOrThrow(FIRST_COLUMN).atOrThrow(3);
2155
- state.cellRuntime[subsubheaderId] = {
2156
- ...state.cellRuntime[subsubheaderId],
2157
- outline: {
2158
- items: [{ name: "Subsubheader", level: 3, by: { id: "subsubheader" } }],
2159
- },
2160
- };
2161
-
2162
- // The header, subheader and subsubheader ranges all end at the last
2163
- // cell. Collapsing all used to throw "Node ... not found in tree" and
2164
- // leave the whole notebook unchanged.
2165
- actions.collapseAllCells();
2166
- expect(state.cellIds.atOrThrow(FIRST_COLUMN).isCollapsed(headerId)).toBe(
2167
- true,
2168
- );
2169
- expect(state.cellIds.atOrThrow(FIRST_COLUMN).topLevelIds).toEqual([
2170
- firstCellId,
2171
- headerId,
2172
- ]);
2173
-
2174
- // Each level is nested under its parent, collapsed
2175
- actions.expandCell({ cellId: headerId });
2176
- expect(state.cellIds.atOrThrow(FIRST_COLUMN).isCollapsed(subheaderId)).toBe(
2177
- true,
2178
- );
2179
- actions.expandCell({ cellId: subheaderId });
2180
- expect(
2181
- state.cellIds.atOrThrow(FIRST_COLUMN).isCollapsed(subsubheaderId),
2182
- ).toBe(true);
2183
- });
2184
-
2185
- it("can collapse all cells when a deep chain is followed by a later section", () => {
2186
- actions.createNewCell({ cellId: firstCellId, before: false });
2187
- actions.createNewCell({
2188
- cellId: cellId("1"),
2189
- before: false,
2190
- code: "# Header",
2191
- });
2192
- actions.createNewCell({
2193
- cellId: cellId("2"),
2194
- before: false,
2195
- code: "## Section A",
2196
- });
2197
- actions.createNewCell({
2198
- cellId: cellId("3"),
2199
- before: false,
2200
- code: "### Sub of A",
2201
- });
2202
- actions.createNewCell({
2203
- cellId: cellId("4"),
2204
- before: false,
2205
- code: "## Section B",
2206
- });
2207
-
2208
- const headerId = state.cellIds.atOrThrow(FIRST_COLUMN).atOrThrow(1);
2209
- state.cellRuntime[headerId] = {
2210
- ...state.cellRuntime[headerId],
2211
- outline: {
2212
- items: [{ name: "Header", level: 1, by: { id: "header" } }],
2213
- },
2214
- };
2215
-
2216
- const sectionAId = state.cellIds.atOrThrow(FIRST_COLUMN).atOrThrow(2);
2217
- state.cellRuntime[sectionAId] = {
2218
- ...state.cellRuntime[sectionAId],
2219
- outline: {
2220
- items: [{ name: "Section A", level: 2, by: { id: "section-a" } }],
2221
- },
2222
- };
2223
-
2224
- const subsectionId = state.cellIds.atOrThrow(FIRST_COLUMN).atOrThrow(3);
2225
- state.cellRuntime[subsectionId] = {
2226
- ...state.cellRuntime[subsectionId],
2227
- outline: {
2228
- items: [{ name: "Sub of A", level: 3, by: { id: "sub-of-a" } }],
2229
- },
2230
- };
2231
-
2232
- const sectionBId = state.cellIds.atOrThrow(FIRST_COLUMN).atOrThrow(4);
2233
- state.cellRuntime[sectionBId] = {
2234
- ...state.cellRuntime[sectionBId],
2235
- outline: {
2236
- items: [{ name: "Section B", level: 2, by: { id: "section-b" } }],
2237
- },
2238
- };
2239
-
2240
- actions.collapseAllCells();
2241
- expect(state.cellIds.atOrThrow(FIRST_COLUMN).isCollapsed(headerId)).toBe(
2242
- true,
2243
- );
2244
- expect(state.cellIds.atOrThrow(FIRST_COLUMN).topLevelIds).toEqual([
2245
- firstCellId,
2246
- headerId,
2247
- ]);
2248
-
2249
- // Both sections sit under the header, collapsed, with the subsection
2250
- // nested under section A
2251
- actions.expandCell({ cellId: headerId });
2252
- expect(state.cellIds.atOrThrow(FIRST_COLUMN).isCollapsed(sectionAId)).toBe(
2253
- true,
2254
- );
2255
- expect(state.cellIds.atOrThrow(FIRST_COLUMN).isCollapsed(sectionBId)).toBe(
2256
- true,
2257
- );
2258
- actions.expandCell({ cellId: sectionAId });
2259
- expect(
2260
- state.cellIds.atOrThrow(FIRST_COLUMN).isCollapsed(subsectionId),
2261
- ).toBe(true);
888
+ stale_inputs: null,
889
+ timestamp: new Date(22).getTime() as Seconds,
890
+ });
891
+ cell = cells[0];
892
+ expect(cell.consoleOutputs).toMatchObject([STDOUT]);
893
+ expect(cell).toMatchSnapshot("console received while idle");
2262
894
  });
2263
895
 
2264
- it("can show hidden cells", () => {
2265
- actions.createNewCell({ cellId: firstCellId, before: false });
2266
- actions.createNewCell({ cellId: cellId("1"), before: false });
2267
- actions.collapseCell({ cellId: firstCellId });
896
+ it("can fold and unfold all cells", () => {
897
+ actions.foldAll();
898
+ expect(foldAllBulk).toHaveBeenCalled();
2268
899
 
2269
- actions.showCellIfHidden({ cellId: cellId("1") });
2270
- expect(state.cellIds.atOrThrow(FIRST_COLUMN).isCollapsed(firstCellId)).toBe(
2271
- false,
2272
- );
900
+ actions.unfoldAll();
901
+ expect(unfoldAllBulk).toHaveBeenCalled();
2273
902
  });
2274
903
 
2275
- it("can split and undo split cells", () => {
2276
- actions.createNewCell({
2277
- cellId: firstCellId,
2278
- before: false,
2279
- code: "line1\nline2",
2280
- });
2281
- const nextCellId = state.cellIds.atOrThrow(FIRST_COLUMN).atOrThrow(1);
2282
-
2283
- const originalCellCount = state.cellIds.atOrThrow(FIRST_COLUMN).length;
2284
- // Move cursor to the second line
2285
- const editor = state.cellHandles[nextCellId].current?.editorView;
2286
- if (!editor) {
2287
- throw new Error("Editor not found");
2288
- }
2289
- editor.dispatch({ selection: { anchor: 5, head: 5 } });
2290
- actions.splitCell({ cellId: nextCellId });
2291
- expect(state.cellIds.atOrThrow(FIRST_COLUMN).length).toBe(
2292
- originalCellCount + 1,
2293
- );
2294
- expect(state.cellData[nextCellId].code).toBe("line1");
2295
- expect(
2296
- state.cellData[state.cellIds.atOrThrow(FIRST_COLUMN).atOrThrow(2)].code,
2297
- ).toBe("line2");
2298
-
2299
- actions.undoSplitCell({ cellId: nextCellId, snapshot: "line1\nline2" });
2300
- expect(state.cellIds.atOrThrow(FIRST_COLUMN).length).toBe(
2301
- originalCellCount,
2302
- );
2303
- expect(state.cellData[nextCellId].code).toBe("line1\nline2");
904
+ it("can clear logs", () => {
905
+ state.cellLogs = [
906
+ {
907
+ level: "stderr",
908
+ message: "log1",
909
+ timestamp: 0,
910
+ source: { type: "cell", cellId: firstCellId },
911
+ },
912
+ {
913
+ level: "stderr",
914
+ message: "log1",
915
+ timestamp: 0,
916
+ source: { type: "cell", cellId: firstCellId },
917
+ },
918
+ ];
919
+ actions.clearLogs();
920
+ expect(state.cellLogs).toEqual([]);
2304
921
  });
2305
922
 
2306
923
  it("can handle multiple console outputs", () => {
@@ -2339,407 +956,6 @@ describe("cell reducer", () => {
2339
956
  `);
2340
957
  });
2341
958
 
2342
- it("can add a column breakpoint", () => {
2343
- actions.createNewCell({ cellId: firstCellId, before: false });
2344
- actions.createNewCell({ cellId: cellId("1"), before: false });
2345
- actions.createNewCell({ cellId: cellId("2"), before: false });
2346
-
2347
- expect(state.cellIds.getColumns().length).toBe(1);
2348
- expect(formatCells(state)).toMatchInlineSnapshot(`
2349
- "
2350
- [0] ''
2351
-
2352
- [1] ''
2353
-
2354
- [2] ''
2355
-
2356
- [3] ''
2357
- "
2358
- `);
2359
-
2360
- actions.addColumnBreakpoint({ cellId: cellId("2") });
2361
-
2362
- expect(state.cellIds.getColumns().length).toBe(2);
2363
- expect(formatCells(state)).toMatchInlineSnapshot(`
2364
- "
2365
- > col 0
2366
- [0] ''
2367
-
2368
- [1] ''
2369
-
2370
- > col 1
2371
- [2] ''
2372
-
2373
- [3] ''
2374
- "
2375
- `);
2376
-
2377
- // Check that the cells are in the correct columns
2378
- expect(state.cellIds.getColumns()[0].topLevelIds).toEqual([
2379
- cellId("0"),
2380
- cellId("1"),
2381
- ]);
2382
- expect(state.cellIds.getColumns()[1].topLevelIds).toEqual([
2383
- cellId("2"),
2384
- cellId("3"),
2385
- ]);
2386
- });
2387
-
2388
- it("cannot add a column breakpoint before the first cell", () => {
2389
- expect(state.cellIds.getColumns().length).toBe(1);
2390
- actions.createNewCell({ cellId: firstCellId, before: false });
2391
- actions.createNewCell({ cellId: cellId("1"), before: false });
2392
- actions.addColumnBreakpoint({ cellId: firstCellId });
2393
- expect(state.cellIds.getColumns().length).toBe(1);
2394
- });
2395
-
2396
- it("can delete a column", () => {
2397
- actions.createNewCell({ cellId: firstCellId, before: false });
2398
- actions.createNewCell({ cellId: cellId("1"), before: false });
2399
- actions.createNewCell({ cellId: cellId("2"), before: false });
2400
- actions.addColumnBreakpoint({ cellId: cellId("2") });
2401
-
2402
- expect(state.cellIds.getColumns().length).toBe(2);
2403
- expect(formatCells(state)).toMatchInlineSnapshot(`
2404
- "
2405
- > col 0
2406
- [0] ''
2407
-
2408
- [1] ''
2409
-
2410
- > col 1
2411
- [2] ''
2412
-
2413
- [3] ''
2414
- "
2415
- `);
2416
-
2417
- const columnId = state.cellIds.atOrThrow(0).id;
2418
- actions.deleteColumn({ columnId: columnId });
2419
-
2420
- expect(state.cellIds.getColumns().length).toBe(1);
2421
- expect(formatCells(state)).toMatchInlineSnapshot(`
2422
- "
2423
- [2] ''
2424
-
2425
- [3] ''
2426
-
2427
- [0] ''
2428
-
2429
- [1] ''
2430
- "
2431
- `);
2432
-
2433
- // Check that all cells are now in the remaining column
2434
- expect(state.cellIds.getColumns()[0].topLevelIds).toEqual([
2435
- "2",
2436
- "3",
2437
- "0",
2438
- "1",
2439
- ]);
2440
- });
2441
-
2442
- it("deleting the last column does nothing", () => {
2443
- actions.createNewCell({ cellId: firstCellId, before: false });
2444
- actions.createNewCell({ cellId: cellId("1"), before: false });
2445
-
2446
- const initialState = { ...state };
2447
-
2448
- actions.deleteColumn({ columnId: initialState.cellIds.atOrThrow(0).id });
2449
-
2450
- // State should not change
2451
- expect(state).toEqual(initialState);
2452
- });
2453
-
2454
- it("can drop a cell over another cell", () => {
2455
- actions.createNewCell({ cellId: firstCellId, before: false });
2456
- actions.createNewCell({ cellId: cellId("1"), before: false });
2457
- actions.createNewCell({ cellId: cellId("2"), before: false });
2458
-
2459
- expect(formatCells(state)).toMatchInlineSnapshot(`
2460
- "
2461
- [0] ''
2462
-
2463
- [1] ''
2464
-
2465
- [2] ''
2466
-
2467
- [3] ''
2468
- "
2469
- `);
2470
-
2471
- actions.dropCellOverCell({
2472
- cellId: cellId("0"),
2473
- overCellId: cellId("3"),
2474
- });
2475
-
2476
- expect(formatCells(state)).toMatchInlineSnapshot(`
2477
- "
2478
- [1] ''
2479
-
2480
- [2] ''
2481
-
2482
- [3] ''
2483
-
2484
- [0] ''
2485
- "
2486
- `);
2487
- });
2488
-
2489
- it("can drop a cell over a new column", () => {
2490
- actions.createNewCell({ cellId: firstCellId, before: false });
2491
- actions.createNewCell({ cellId: cellId("1"), before: false });
2492
-
2493
- expect(state.cellIds.getColumns().length).toBe(1);
2494
- expect(formatCells(state)).toMatchInlineSnapshot(`
2495
- "
2496
- [0] ''
2497
-
2498
- [1] ''
2499
-
2500
- [2] ''
2501
- "
2502
- `);
2503
-
2504
- actions.dropOverNewColumn({ cellId: cellId("1") });
2505
-
2506
- expect(state.cellIds.getColumns().length).toBe(2);
2507
- expect(formatCells(state)).toMatchInlineSnapshot(`
2508
- "
2509
- > col 0
2510
- [0] ''
2511
-
2512
- [2] ''
2513
-
2514
- > col 1
2515
- [1] ''
2516
- "
2517
- `);
2518
-
2519
- // Check that the cells are in the correct columns
2520
- expect(state.cellIds.getColumns()[0].topLevelIds).toEqual([
2521
- cellId("0"),
2522
- cellId("2"),
2523
- ]);
2524
- expect(state.cellIds.getColumns()[1].topLevelIds).toEqual([cellId("1")]);
2525
- });
2526
-
2527
- it("can drop a column over another column", () => {
2528
- actions.createNewCell({ cellId: firstCellId, before: false });
2529
- actions.createNewCell({ cellId: cellId("1"), before: false });
2530
- actions.createNewCell({ cellId: cellId("2"), before: false });
2531
- actions.addColumnBreakpoint({ cellId: cellId("2") });
2532
-
2533
- expect(state.cellIds.getColumns().length).toBe(2);
2534
- expect(formatCells(state)).toMatchInlineSnapshot(`
2535
- "
2536
- > col 0
2537
- [0] ''
2538
-
2539
- [1] ''
2540
-
2541
- > col 1
2542
- [2] ''
2543
-
2544
- [3] ''
2545
- "
2546
- `);
2547
-
2548
- const columnId0 = state.cellIds.atOrThrow(0).id;
2549
- const columnId1 = state.cellIds.atOrThrow(1).id;
2550
- expect(columnId0).not.toBe(columnId1);
2551
- actions.moveColumn({ column: columnId1, overColumn: columnId0 });
2552
-
2553
- expect(state.cellIds.getColumns().length).toBe(2);
2554
- expect(formatCells(state)).toMatchInlineSnapshot(`
2555
- "
2556
- > col 0
2557
- [2] ''
2558
-
2559
- [3] ''
2560
-
2561
- > col 1
2562
- [0] ''
2563
-
2564
- [1] ''
2565
- "
2566
- `);
2567
-
2568
- // Check that the columns have swapped positions
2569
- expect(state.cellIds.getColumns()[0].topLevelIds).toEqual([
2570
- cellId("2"),
2571
- cellId("3"),
2572
- ]);
2573
- expect(state.cellIds.getColumns()[1].topLevelIds).toEqual([
2574
- cellId("0"),
2575
- cellId("1"),
2576
- ]);
2577
- });
2578
-
2579
- it("can compact columns", () => {
2580
- // Create initial state with 3 columns, including an empty one
2581
- actions.createNewCell({ cellId: firstCellId, before: false });
2582
- actions.createNewCell({ cellId: cellId("1"), before: false });
2583
- actions.addColumnBreakpoint({ cellId: cellId("1") });
2584
- actions.addColumnBreakpoint({ cellId: cellId("2") });
2585
- actions.dropOverNewColumn({ cellId: cellId("2") });
2586
-
2587
- expect(state.cellIds.getColumns().length).toBe(4);
2588
- expect(formatCells(state)).toMatchInlineSnapshot(`
2589
- "
2590
- > col 0
2591
- [0] ''
2592
-
2593
- > col 1
2594
- [1] ''
2595
-
2596
- > col 2
2597
-
2598
-
2599
- > col 3
2600
- [2] ''
2601
- "
2602
- `);
2603
-
2604
- // Check initial column structure
2605
- expect(state.cellIds.getColumns()[0].topLevelIds).toEqual([cellId("0")]);
2606
- expect(state.cellIds.getColumns()[1].topLevelIds).toEqual([cellId("1")]);
2607
- expect(state.cellIds.getColumns()[2].topLevelIds).toEqual([]);
2608
- expect(state.cellIds.getColumns()[3].topLevelIds).toEqual([cellId("2")]);
2609
-
2610
- // Compact columns
2611
- actions.compactColumns();
2612
-
2613
- expect(state.cellIds.getColumns().length).toBe(3);
2614
- expect(formatCells(state)).toMatchInlineSnapshot(`
2615
- "
2616
- > col 0
2617
- [0] ''
2618
-
2619
- > col 1
2620
- [1] ''
2621
-
2622
- > col 2
2623
- [2] ''
2624
- "
2625
- `);
2626
-
2627
- // Check compacted column structure
2628
- expect(state.cellIds.getColumns()[0].topLevelIds).toEqual([cellId("0")]);
2629
- expect(state.cellIds.getColumns()[1].topLevelIds).toEqual([cellId("1")]);
2630
- expect(state.cellIds.getColumns()[2].topLevelIds).toEqual([cellId("2")]);
2631
- });
2632
-
2633
- it("rebuildCellColumns regroups cells by config.column", () => {
2634
- // Create four cells in a single column.
2635
- actions.createNewCell({ cellId: firstCellId, before: false });
2636
- actions.createNewCell({ cellId: cellId("1"), before: false });
2637
- actions.createNewCell({ cellId: cellId("2"), before: false });
2638
- expect(state.cellIds.getColumns().length).toBe(1);
2639
-
2640
- // Explicitly set config.column on each cell. This ONLY updates metadata —
2641
- // updateCellConfig does not touch the MultiColumn tree.
2642
- actions.updateCellConfig({
2643
- cellId: cellId("0"),
2644
- config: { column: 0 },
2645
- });
2646
- actions.updateCellConfig({
2647
- cellId: cellId("2"),
2648
- config: { column: 1 },
2649
- });
2650
-
2651
- // Tree is still a single column at this point.
2652
- expect(state.cellIds.getColumns().length).toBe(1);
2653
-
2654
- // Now rebuild the column tree from metadata.
2655
- actions.rebuildCellColumns({
2656
- cellIds: [cellId("0"), cellId("1"), cellId("2"), cellId("3")],
2657
- });
2658
-
2659
- expect(state.cellIds.getColumns().length).toBe(2);
2660
- // Cell 1 inherits from 0 (col 0), cell 3 inherits from 2 (col 1).
2661
- expect(state.cellIds.getColumns()[0].topLevelIds).toEqual([
2662
- cellId("0"),
2663
- cellId("1"),
2664
- ]);
2665
- expect(state.cellIds.getColumns()[1].topLevelIds).toEqual([
2666
- cellId("2"),
2667
- cellId("3"),
2668
- ]);
2669
- });
2670
-
2671
- it("rebuildCellColumns with explicit column on every cell", () => {
2672
- actions.createNewCell({ cellId: firstCellId, before: false });
2673
- actions.createNewCell({ cellId: cellId("1"), before: false });
2674
- actions.createNewCell({ cellId: cellId("2"), before: false });
2675
-
2676
- for (const [id, col] of [
2677
- ["0", 1],
2678
- ["1", 0],
2679
- ["2", 1],
2680
- ["3", 0],
2681
- ] as const) {
2682
- actions.updateCellConfig({ cellId: cellId(id), config: { column: col } });
2683
- }
2684
-
2685
- actions.rebuildCellColumns({
2686
- cellIds: [cellId("0"), cellId("1"), cellId("2"), cellId("3")],
2687
- });
2688
-
2689
- expect(state.cellIds.getColumns().length).toBe(2);
2690
- expect(state.cellIds.getColumns()[0].topLevelIds).toEqual([
2691
- cellId("1"),
2692
- cellId("3"),
2693
- ]);
2694
- expect(state.cellIds.getColumns()[1].topLevelIds).toEqual([
2695
- cellId("0"),
2696
- cellId("2"),
2697
- ]);
2698
- });
2699
-
2700
- it("rebuildCellColumns collapses to one column when all cells are col 0", () => {
2701
- actions.createNewCell({ cellId: firstCellId, before: false });
2702
- actions.addColumnBreakpoint({ cellId: cellId("1") });
2703
- expect(state.cellIds.getColumns().length).toBe(2);
2704
-
2705
- // Wipe column metadata back to 0 for both cells.
2706
- actions.updateCellConfig({ cellId: cellId("0"), config: { column: 0 } });
2707
- actions.updateCellConfig({ cellId: cellId("1"), config: { column: 0 } });
2708
-
2709
- actions.rebuildCellColumns({
2710
- cellIds: [cellId("0"), cellId("1")],
2711
- });
2712
-
2713
- expect(state.cellIds.getColumns().length).toBe(1);
2714
- expect(state.cellIds.getColumns()[0].topLevelIds).toEqual([
2715
- cellId("0"),
2716
- cellId("1"),
2717
- ]);
2718
- });
2719
-
2720
- it("rebuildCellColumns follows the provided order, not current tree order", () => {
2721
- // Start with a single column.
2722
- actions.createNewCell({ cellId: firstCellId, before: false });
2723
- actions.createNewCell({ cellId: cellId("1"), before: false });
2724
- actions.updateCellConfig({ cellId: cellId("0"), config: { column: 0 } });
2725
- actions.updateCellConfig({ cellId: cellId("2"), config: { column: 1 } });
2726
-
2727
- // Pass the reversed order. The rebuild should honor it.
2728
- actions.rebuildCellColumns({
2729
- cellIds: [cellId("2"), cellId("1"), cellId("0")],
2730
- });
2731
-
2732
- // cellIds iteration:
2733
- // 2 → col=1, pushed to col1, prev=1
2734
- // 1 → col=null, pushed to col[prev=1] = col1
2735
- // 0 → col=0, pushed to col0
2736
- expect(state.cellIds.getColumns()[0].topLevelIds).toEqual([cellId("0")]);
2737
- expect(state.cellIds.getColumns()[1].topLevelIds).toEqual([
2738
- cellId("2"),
2739
- cellId("1"),
2740
- ]);
2741
- });
2742
-
2743
959
  it("can clear output of a single cell", () => {
2744
960
  // Set up initial state with output
2745
961
  actions.handleCellMessage({
@@ -2828,8 +1044,8 @@ describe("cell reducer", () => {
2828
1044
 
2829
1045
  it("can clear output of all cells", () => {
2830
1046
  // Create multiple cells with output
2831
- actions.createNewCell({ cellId: firstCellId, before: false });
2832
- const secondCellId = state.cellIds.atOrThrow(FIRST_COLUMN).atOrThrow(1);
1047
+ seedCell({ cellId: firstCellId, before: false });
1048
+ const secondCellId = state.cellIds[1];
2833
1049
 
2834
1050
  // Add output to both cells
2835
1051
  const outputMessage = {
@@ -2854,142 +1070,30 @@ describe("cell reducer", () => {
2854
1070
  ...outputMessage,
2855
1071
  cell_id: firstCellId,
2856
1072
  });
2857
- actions.handleCellMessage({
2858
- ...outputMessage,
2859
- cell_id: secondCellId,
2860
- });
2861
-
2862
- // Verify initial state has output
2863
- expect(state.cellRuntime[firstCellId].output).not.toBeNull();
2864
- expect(state.cellRuntime[firstCellId].consoleOutputs.length).toBe(1);
2865
- expect(state.cellRuntime[secondCellId].output).not.toBeNull();
2866
- expect(state.cellRuntime[secondCellId].consoleOutputs.length).toBe(1);
2867
-
2868
- // Clear all outputs
2869
- actions.clearAllCellOutputs();
2870
-
2871
- // Verify all outputs are cleared
2872
- expect(state.cellRuntime[firstCellId].output).toBeNull();
2873
- expect(state.cellRuntime[firstCellId].consoleOutputs).toEqual([]);
2874
- expect(state.cellRuntime[secondCellId].output).toBeNull();
2875
- expect(state.cellRuntime[secondCellId].consoleOutputs).toEqual([]);
2876
- });
2877
-
2878
- it("skips creating new cell if code exists and skipIfCodeExists is true", () => {
2879
- // Add initial cell with code
2880
- actions.updateCellCode({
2881
- cellId: firstCellId,
2882
- code: "import numpy as np",
2883
- formattingChange: false,
2884
- });
2885
-
2886
- // Try to create new cell with same code and skipIfCodeExists
2887
- actions.createNewCell({
2888
- cellId: "__end__",
2889
- code: "import numpy as np",
2890
- before: false,
2891
- skipIfCodeExists: true,
2892
- });
2893
-
2894
- // Should still only have one cell
2895
- expect(state.cellIds.inOrderIds.length).toBe(1);
2896
- expect(formatCells(state)).toMatchInlineSnapshot(`
2897
- "
2898
- [0] 'import numpy as np'
2899
- "
2900
- `);
2901
-
2902
- // Verify we can still add cell with different code
2903
- actions.createNewCell({
2904
- cellId: "__end__",
2905
- code: "import pandas as pd",
2906
- before: false,
2907
- skipIfCodeExists: true,
2908
- });
2909
-
2910
- expect(state.cellIds.inOrderIds.length).toBe(2);
2911
- expect(formatCells(state)).toMatchInlineSnapshot(`
2912
- "
2913
- [0] 'import numpy as np'
2914
-
2915
- [1] 'import pandas as pd'
2916
- "
2917
- `);
2918
- });
2919
-
2920
- it("can create and noop-update a setup cell", () => {
2921
- // Create the setup cell
2922
- actions.addSetupCellIfDoesntExist({ code: "# Setup code" });
2923
-
2924
- // Check that setup cell was created
2925
- expect(state.cellData[SETUP_CELL_ID].id).toBe(SETUP_CELL_ID);
2926
- expect(state.cellData[SETUP_CELL_ID].name).toBe("setup");
2927
- expect(state.cellData[SETUP_CELL_ID].code).toBe("# Setup code");
2928
- expect(state.cellData[SETUP_CELL_ID].edited).toBe(true);
2929
- expect(state.cellIds.inOrderIds).toContain(SETUP_CELL_ID);
2930
-
2931
- // Update the setup cell
2932
- actions.addSetupCellIfDoesntExist({ code: "# Updated setup code" });
2933
-
2934
- // Check that the setup cell did not change, since it already exists
2935
- expect(state.cellData[SETUP_CELL_ID].code).toBe("# Setup code");
2936
- expect(state.cellData[SETUP_CELL_ID].edited).toBe(true);
2937
- expect(state.cellIds.inOrderIds).toContain(SETUP_CELL_ID);
2938
- });
2939
-
2940
- it("can delete and undelete the setup cell", () => {
2941
- // Create the setup cell
2942
- actions.addSetupCellIfDoesntExist({ code: "# Setup code" });
2943
-
2944
- // Check that setup cell was created
2945
- expect(state.cellData[SETUP_CELL_ID].id).toBe(SETUP_CELL_ID);
2946
- expect(state.cellData[SETUP_CELL_ID].name).toBe("setup");
2947
- expect(state.cellData[SETUP_CELL_ID].code).toBe("# Setup code");
2948
- expect(state.cellData[SETUP_CELL_ID].edited).toBe(true);
2949
- expect(state.cellIds.inOrderIds).toContain(SETUP_CELL_ID);
2950
-
2951
- // Delete the setup cell
2952
- actions.deleteCell({ cellId: SETUP_CELL_ID });
2953
-
2954
- // Check that setup cell was deleted
2955
- expect(state.cellData[SETUP_CELL_ID]).toBeDefined(); // we keep old state
2956
- expect(state.cellIds.inOrderIds).not.toContain(SETUP_CELL_ID);
2957
-
2958
- // Undo delete the setup cell
2959
- actions.undoDeleteCell();
2960
-
2961
- // Check that setup cell was restored
2962
- expect(state.cellData[SETUP_CELL_ID].id).toBe(SETUP_CELL_ID);
2963
- expect(state.cellData[SETUP_CELL_ID].name).toBe("setup");
2964
- expect(state.cellData[SETUP_CELL_ID].code).toBe("# Setup code");
2965
- expect(state.cellData[SETUP_CELL_ID].edited).toBe(true);
2966
- expect(state.cellIds.inOrderIds).toContain(SETUP_CELL_ID);
2967
-
2968
- // Verify scrollKey is set to the restored setup cell
2969
- expect(state.scrollKey).toBe(SETUP_CELL_ID);
2970
- });
2971
-
2972
- it("can delete and then create a new setup cell", () => {
2973
- // Create the setup cell
2974
- actions.addSetupCellIfDoesntExist({ code: "# Setup code" });
1073
+ actions.handleCellMessage({
1074
+ ...outputMessage,
1075
+ cell_id: secondCellId,
1076
+ });
2975
1077
 
2976
- // Delete the setup cell
2977
- actions.deleteCell({ cellId: SETUP_CELL_ID });
1078
+ // Verify initial state has output
1079
+ expect(state.cellRuntime[firstCellId].output).not.toBeNull();
1080
+ expect(state.cellRuntime[firstCellId].consoleOutputs.length).toBe(1);
1081
+ expect(state.cellRuntime[secondCellId].output).not.toBeNull();
1082
+ expect(state.cellRuntime[secondCellId].consoleOutputs.length).toBe(1);
2978
1083
 
2979
- // Create a new setup cell
2980
- actions.addSetupCellIfDoesntExist({ code: "# New code" });
1084
+ // Clear all outputs
1085
+ actions.clearAllCellOutputs();
2981
1086
 
2982
- // Check that the new setup cell was created
2983
- expect(state.cellData[SETUP_CELL_ID].id).toBe(SETUP_CELL_ID);
2984
- expect(state.cellData[SETUP_CELL_ID].name).toBe("setup");
2985
- expect(state.cellData[SETUP_CELL_ID].code).toBe("# New code");
2986
- expect(state.cellData[SETUP_CELL_ID].edited).toBe(true);
2987
- expect(state.cellIds.inOrderIds).toContain(SETUP_CELL_ID);
1087
+ // Verify all outputs are cleared
1088
+ expect(state.cellRuntime[firstCellId].output).toBeNull();
1089
+ expect(state.cellRuntime[firstCellId].consoleOutputs).toEqual([]);
1090
+ expect(state.cellRuntime[secondCellId].output).toBeNull();
1091
+ expect(state.cellRuntime[secondCellId].consoleOutputs).toEqual([]);
2988
1092
  });
2989
1093
 
2990
1094
  it("can clear all outputs", () => {
2991
1095
  // Add a cell and give it output
2992
- actions.createNewCell({
1096
+ seedCell({
2993
1097
  cellId: firstCellId,
2994
1098
  before: false,
2995
1099
  });
@@ -3017,522 +1121,13 @@ describe("cell reducer", () => {
3017
1121
  expect(state.cellRuntime[cell2Id].output).toBeNull();
3018
1122
  });
3019
1123
 
3020
- describe("moveToNextCell", () => {
3021
- let cell1Id: CellId;
3022
- let cell2Id: CellId;
3023
- let cell3Id: CellId;
3024
-
3025
- beforeEach(() => {
3026
- // Create a few cells to work with
3027
- actions.createNewCell({ cellId: "__end__", before: false });
3028
- actions.createNewCell({ cellId: "__end__", before: false });
3029
-
3030
- cell1Id = state.cellIds.inOrderIds[0];
3031
- cell2Id = state.cellIds.inOrderIds[1];
3032
- cell3Id = state.cellIds.inOrderIds[2];
3033
- });
3034
-
3035
- it("creates new cell when moving after last cell with noCreate=false", () => {
3036
- const initialCellCount = state.cellIds.inOrderIds.length;
3037
-
3038
- actions.moveToNextCell({
3039
- cellId: cell3Id,
3040
- before: false,
3041
- noCreate: false,
3042
- });
3043
-
3044
- expect(state.cellIds.inOrderIds.length).toBe(initialCellCount + 1);
3045
- });
3046
-
3047
- it("creates new cell when moving before first cell with noCreate=false", () => {
3048
- const initialCellCount = state.cellIds.inOrderIds.length;
3049
-
3050
- actions.moveToNextCell({
3051
- cellId: cell1Id,
3052
- before: true,
3053
- noCreate: false,
3054
- });
3055
-
3056
- expect(state.cellIds.inOrderIds.length).toBe(initialCellCount + 1);
3057
- });
3058
-
3059
- it("does not create new cell when moving after last cell with noCreate=true", () => {
3060
- const initialCellCount = state.cellIds.inOrderIds.length;
3061
- const initialState = { ...state };
3062
-
3063
- actions.moveToNextCell({
3064
- cellId: cell3Id,
3065
- before: false,
3066
- noCreate: true,
3067
- });
3068
-
3069
- // Should not create a new cell
3070
- expect(state.cellIds.inOrderIds.length).toBe(initialCellCount);
3071
- // Should not crash or throw an error
3072
- expect(state.cellIds.inOrderIds).toEqual(initialState.cellIds.inOrderIds);
3073
- });
3074
-
3075
- it("does not create new cell when moving before first cell with noCreate=true", () => {
3076
- const initialCellCount = state.cellIds.inOrderIds.length;
3077
- const initialState = { ...state };
3078
-
3079
- actions.moveToNextCell({ cellId: cell1Id, before: true, noCreate: true });
3080
-
3081
- // Should not create a new cell
3082
- expect(state.cellIds.inOrderIds.length).toBe(initialCellCount);
3083
- // Should not crash or throw an error
3084
- expect(state.cellIds.inOrderIds).toEqual(initialState.cellIds.inOrderIds);
3085
- });
3086
-
3087
- it("focuses next cell when moving within bounds", () => {
3088
- const focusSpy = vi.mocked(focusAndScrollCellIntoView);
3089
- focusSpy.mockClear();
3090
-
3091
- actions.moveToNextCell({
3092
- cellId: cell1Id,
3093
- before: false,
3094
- noCreate: true,
3095
- });
3096
-
3097
- expect(focusSpy).toHaveBeenCalledWith({
3098
- cellId: cell2Id,
3099
- cell: state.cellHandles[cell2Id],
3100
- isCodeHidden: false,
3101
- codeFocus: "top",
3102
- variableName: undefined,
3103
- });
3104
- });
3105
-
3106
- it("focuses previous cell when moving backward within bounds", () => {
3107
- const focusSpy = vi.mocked(focusAndScrollCellIntoView);
3108
- focusSpy.mockClear();
3109
-
3110
- actions.moveToNextCell({ cellId: cell2Id, before: true, noCreate: true });
3111
-
3112
- expect(focusSpy).toHaveBeenCalledWith({
3113
- cellId: cell1Id,
3114
- cell: state.cellHandles[cell1Id],
3115
- isCodeHidden: false,
3116
- codeFocus: "bottom",
3117
- variableName: undefined,
3118
- });
3119
- });
3120
-
3121
- it("does not move focus from scratch cell", () => {
3122
- const focusSpy = vi.mocked(focusAndScrollCellIntoView);
3123
- focusSpy.mockClear();
3124
- const initialState = { ...state };
3125
-
3126
- actions.moveToNextCell({
3127
- cellId: SCRATCH_CELL_ID,
3128
- before: false,
3129
- noCreate: false,
3130
- });
3131
-
3132
- expect(focusSpy).not.toHaveBeenCalled();
3133
- expect(state.cellIds.inOrderIds).toEqual(initialState.cellIds.inOrderIds);
3134
- });
3135
- });
3136
-
3137
- describe("untouched cells functionality", () => {
3138
- it("starts with empty untouchedNewCells set", () => {
3139
- expect(state.untouchedNewCells).toEqual(new Set());
3140
- });
3141
-
3142
- it("can create a new cell with hideCode option", () => {
3143
- const initialCellCount = state.cellIds.inOrderIds.length;
3144
-
3145
- actions.createNewCell({
3146
- cellId: "__end__",
3147
- before: false,
3148
- hideCode: true,
3149
- });
3150
-
3151
- expect(state.cellIds.inOrderIds.length).toBe(initialCellCount + 1);
3152
- const newCellId =
3153
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
3154
- expect(state.untouchedNewCells.has(newCellId)).toBe(true);
3155
- });
3156
-
3157
- it("adds interactively-created hidden cell with boilerplate code to untouchedNewCells", () => {
3158
- // Markdown cells are created with hideCode and non-empty default code
3159
- // (e.g. `mo.md(r"""\n""")`). They are user-initiated, so their editor
3160
- // should be shown until first blur.
3161
- actions.createNewCell({
3162
- cellId: "__end__",
3163
- before: false,
3164
- code: 'mo.md(r"""\n""")',
3165
- hideCode: true,
3166
- autoFocus: true,
3167
- });
3168
-
3169
- const newCellId =
3170
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
3171
- expect(state.untouchedNewCells.has(newCellId)).toBe(true);
3172
- });
3173
-
3174
- it("does not add programmatically-created hidden cell with code to untouchedNewCells", () => {
3175
- // Cells created by the kernel (e.g. via code_mode) carry code and
3176
- // autoFocus=false; their hide_code must take effect immediately.
3177
- actions.createNewCell({
3178
- cellId: "__end__",
3179
- before: false,
3180
- code: "x = 1",
3181
- hideCode: true,
3182
- autoFocus: false,
3183
- });
3184
-
3185
- const newCellId =
3186
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
3187
- expect(state.untouchedNewCells.has(newCellId)).toBe(false);
3188
- });
3189
-
3190
- it("does not add cell to untouchedNewCells when hideCode is false", () => {
3191
- actions.createNewCell({
3192
- cellId: "__end__",
3193
- before: false,
3194
- hideCode: false,
3195
- });
3196
-
3197
- const newCellId =
3198
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
3199
- expect(state.untouchedNewCells.has(newCellId)).toBe(false);
3200
- });
3201
-
3202
- it("does not add cell to untouchedNewCells when hideCode is undefined", () => {
3203
- actions.createNewCell({
3204
- cellId: "__end__",
3205
- before: false,
3206
- });
3207
-
3208
- const newCellId =
3209
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
3210
- expect(state.untouchedNewCells.has(newCellId)).toBe(false);
3211
- });
3212
-
3213
- it("can mark a cell as touched", () => {
3214
- // Create a cell with hideCode
3215
- actions.createNewCell({
3216
- cellId: "__end__",
3217
- before: false,
3218
- hideCode: true,
3219
- });
3220
-
3221
- const newCellId =
3222
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
3223
- expect(state.untouchedNewCells.has(newCellId)).toBe(true);
3224
-
3225
- // Mark it as touched
3226
- actions.markTouched({ cellId: newCellId });
3227
-
3228
- expect(state.untouchedNewCells.has(newCellId)).toBe(false);
3229
- });
3230
-
3231
- it("markTouched is idempotent", () => {
3232
- // Create a cell with hideCode
3233
- actions.createNewCell({
3234
- cellId: "__end__",
3235
- before: false,
3236
- hideCode: true,
3237
- });
3238
-
3239
- const newCellId =
3240
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
3241
- expect(state.untouchedNewCells.has(newCellId)).toBe(true);
3242
-
3243
- // Mark it as touched multiple times
3244
- actions.markTouched({ cellId: newCellId });
3245
- actions.markTouched({ cellId: newCellId });
3246
- actions.markTouched({ cellId: newCellId });
3247
-
3248
- expect(state.untouchedNewCells.has(newCellId)).toBe(false);
3249
- });
3250
-
3251
- it("can mark a non-existent cell as touched without error", () => {
3252
- const nonExistentCellId = cellId("non-existent");
3253
-
3254
- expect(() => {
3255
- actions.markTouched({ cellId: nonExistentCellId });
3256
- }).not.toThrow();
3257
-
3258
- expect(state.untouchedNewCells.has(nonExistentCellId)).toBe(false);
3259
- });
3260
-
3261
- it("can handle multiple untouched cells", () => {
3262
- // Create multiple cells with hideCode
3263
- actions.createNewCell({
3264
- cellId: "__end__",
3265
- before: false,
3266
- hideCode: true,
3267
- });
3268
- actions.createNewCell({
3269
- cellId: "__end__",
3270
- before: false,
3271
- hideCode: true,
3272
- });
3273
- actions.createNewCell({
3274
- cellId: "__end__",
3275
- before: false,
3276
- hideCode: false,
3277
- });
3278
-
3279
- const cellIds = state.cellIds.inOrderIds;
3280
- const cell1Id = cellIds[cellIds.length - 3];
3281
- const cell2Id = cellIds[cellIds.length - 2];
3282
- const cell3Id = cellIds[cellIds.length - 1];
3283
-
3284
- expect(state.untouchedNewCells.has(cell1Id)).toBe(true);
3285
- expect(state.untouchedNewCells.has(cell2Id)).toBe(true);
3286
- expect(state.untouchedNewCells.has(cell3Id)).toBe(false);
3287
-
3288
- // Mark one as touched
3289
- actions.markTouched({ cellId: cell1Id });
3290
-
3291
- expect(state.untouchedNewCells.has(cell1Id)).toBe(false);
3292
- expect(state.untouchedNewCells.has(cell2Id)).toBe(true);
3293
- expect(state.untouchedNewCells.has(cell3Id)).toBe(false);
3294
- });
3295
-
3296
- it("preserves untouched state when cells are moved", () => {
3297
- // Create cells with hideCode
3298
- actions.createNewCell({
3299
- cellId: "__end__",
3300
- before: false,
3301
- hideCode: true,
3302
- });
3303
- actions.createNewCell({
3304
- cellId: "__end__",
3305
- before: false,
3306
- hideCode: true,
3307
- });
3308
-
3309
- const cellIds = state.cellIds.inOrderIds;
3310
- const cell1Id = cellIds[cellIds.length - 2];
3311
- const cell2Id = cellIds[cellIds.length - 1];
3312
-
3313
- expect(state.untouchedNewCells.has(cell1Id)).toBe(true);
3314
- expect(state.untouchedNewCells.has(cell2Id)).toBe(true);
3315
-
3316
- // Move cell1 to the end
3317
- actions.moveCell({
3318
- cellId: cell1Id,
3319
- before: false,
3320
- });
3321
-
3322
- // Both cells should still be untouched
3323
- expect(state.untouchedNewCells.has(cell1Id)).toBe(true);
3324
- expect(state.untouchedNewCells.has(cell2Id)).toBe(true);
3325
- });
3326
-
3327
- it("does not remove untouched state when cell is deleted", () => {
3328
- // We could implement this, but it doesn't actually affect downstream behavior.
3329
- // It is easier to leave it, so `undo` works as expected.
3330
-
3331
- // Create a cell with hideCode
3332
- actions.createNewCell({
3333
- cellId: "__end__",
3334
- before: false,
3335
- hideCode: true,
3336
- });
3337
-
3338
- const newCellId =
3339
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
3340
- expect(state.untouchedNewCells.has(newCellId)).toBe(true);
3341
-
3342
- // Delete the cell
3343
- actions.deleteCell({ cellId: newCellId });
3344
-
3345
- // Does not actually remove untouched state
3346
- expect(state.untouchedNewCells.has(newCellId)).toBe(true);
3347
- });
3348
-
3349
- it("restores untouched state when cell deletion is undone", () => {
3350
- // Create a cell with hideCode
3351
- actions.createNewCell({
3352
- cellId: "__end__",
3353
- before: false,
3354
- hideCode: true,
3355
- });
3356
-
3357
- const newCellId =
3358
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
3359
- expect(state.untouchedNewCells.has(newCellId)).toBe(true);
3360
-
3361
- // Delete the cell
3362
- actions.deleteCell({ cellId: newCellId });
3363
- // Still exists in untouchedNewCells
3364
- expect(state.untouchedNewCells.has(newCellId)).toBe(true);
3365
-
3366
- // Undo the deletion
3367
- actions.undoDeleteCell();
3368
-
3369
- // The cell should be restored but no longer untouched
3370
- // (this is expected behavior - undoing doesn't restore untouched state)
3371
- const restoredCellId =
3372
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
3373
- expect(state.untouchedNewCells.has(restoredCellId)).toBe(false);
3374
- });
3375
-
3376
- it("untouched cell behavior with hide_code config", () => {
3377
- // Create a cell with hideCode and hide_code config
3378
- actions.createNewCell({
3379
- cellId: "__end__",
3380
- before: false,
3381
- hideCode: true,
3382
- });
3383
-
3384
- const newCellId =
3385
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
3386
-
3387
- // Update the cell config to hide code
3388
- actions.updateCellConfig({
3389
- cellId: newCellId,
3390
- config: { hide_code: true },
3391
- });
3392
-
3393
- // Cell should be untouched and have hide_code config
3394
- expect(state.untouchedNewCells.has(newCellId)).toBe(true);
3395
- expect(state.cellData[newCellId].config.hide_code).toBe(true);
3396
-
3397
- // Code should not be hidden because cell is untouched
3398
- expect(exportedForTesting.isCellCodeHidden(state, newCellId)).toBe(false);
3399
-
3400
- // Mark as touched
3401
- actions.markTouched({ cellId: newCellId });
3402
-
3403
- // Now code should be hidden
3404
- expect(state.untouchedNewCells.has(newCellId)).toBe(false);
3405
- expect(exportedForTesting.isCellCodeHidden(state, newCellId)).toBe(true);
3406
- });
3407
-
3408
- it("can mark an existing cell as untouched", () => {
3409
- // Create a cell without hideCode (not in untouchedNewCells)
3410
- actions.createNewCell({
3411
- cellId: "__end__",
3412
- before: false,
3413
- hideCode: false,
3414
- });
3415
-
3416
- const newCellId =
3417
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
3418
- expect(state.untouchedNewCells.has(newCellId)).toBe(false);
3419
-
3420
- // Mark it as untouched
3421
- actions.markUntouched({ cellId: newCellId });
3422
-
3423
- expect(state.untouchedNewCells.has(newCellId)).toBe(true);
3424
- });
3425
-
3426
- it("markUntouched is idempotent", () => {
3427
- // Create a cell without hideCode
3428
- actions.createNewCell({
3429
- cellId: "__end__",
3430
- before: false,
3431
- hideCode: false,
3432
- });
3433
-
3434
- const newCellId =
3435
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
3436
- expect(state.untouchedNewCells.has(newCellId)).toBe(false);
3437
-
3438
- // Mark as untouched multiple times
3439
- actions.markUntouched({ cellId: newCellId });
3440
- actions.markUntouched({ cellId: newCellId });
3441
- actions.markUntouched({ cellId: newCellId });
3442
-
3443
- expect(state.untouchedNewCells.has(newCellId)).toBe(true);
3444
- });
3445
-
3446
- it("markUntouched does not affect already untouched cells", () => {
3447
- // Create a cell with hideCode (already in untouchedNewCells)
3448
- actions.createNewCell({
3449
- cellId: "__end__",
3450
- before: false,
3451
- hideCode: true,
3452
- });
3453
-
3454
- const newCellId =
3455
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
3456
- expect(state.untouchedNewCells.has(newCellId)).toBe(true);
3457
-
3458
- // Calling markUntouched should not change anything
3459
- actions.markUntouched({ cellId: newCellId });
3460
-
3461
- expect(state.untouchedNewCells.has(newCellId)).toBe(true);
3462
- });
3463
-
3464
- it("markTouched and markUntouched can toggle cell state", () => {
3465
- // Create a cell without hideCode
3466
- actions.createNewCell({
3467
- cellId: "__end__",
3468
- before: false,
3469
- hideCode: false,
3470
- });
3471
-
3472
- const newCellId =
3473
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
3474
-
3475
- // Initially not untouched
3476
- expect(state.untouchedNewCells.has(newCellId)).toBe(false);
3477
-
3478
- // Mark as untouched
3479
- actions.markUntouched({ cellId: newCellId });
3480
- expect(state.untouchedNewCells.has(newCellId)).toBe(true);
3481
-
3482
- // Mark as touched
3483
- actions.markTouched({ cellId: newCellId });
3484
- expect(state.untouchedNewCells.has(newCellId)).toBe(false);
3485
-
3486
- // Mark as untouched again
3487
- actions.markUntouched({ cellId: newCellId });
3488
- expect(state.untouchedNewCells.has(newCellId)).toBe(true);
3489
- });
3490
-
3491
- it("markUntouched works for markdown cell conversion scenario", () => {
3492
- // Simulates converting a Python cell to Markdown
3493
- // 1. Create a regular cell (no hideCode)
3494
- actions.createNewCell({
3495
- cellId: "__end__",
3496
- before: false,
3497
- hideCode: false,
3498
- });
3499
-
3500
- const cellId =
3501
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
3502
-
3503
- // Cell starts without hide_code and not in untouchedNewCells
3504
- expect(state.cellData[cellId].config.hide_code).toBe(false);
3505
- expect(state.untouchedNewCells.has(cellId)).toBe(false);
3506
- expect(exportedForTesting.isCellCodeHidden(state, cellId)).toBe(false);
3507
-
3508
- // 2. Convert to markdown: set hide_code and mark as untouched
3509
- actions.updateCellConfig({
3510
- cellId,
3511
- config: { hide_code: true },
3512
- });
3513
- actions.markUntouched({ cellId });
3514
-
3515
- // Code should NOT be hidden because cell is untouched (user can edit)
3516
- expect(state.cellData[cellId].config.hide_code).toBe(true);
3517
- expect(state.untouchedNewCells.has(cellId)).toBe(true);
3518
- expect(exportedForTesting.isCellCodeHidden(state, cellId)).toBe(false);
3519
-
3520
- // 3. User blurs the cell (markTouched)
3521
- actions.markTouched({ cellId });
3522
-
3523
- // Now code should be hidden
3524
- expect(state.untouchedNewCells.has(cellId)).toBe(false);
3525
- expect(exportedForTesting.isCellCodeHidden(state, cellId)).toBe(true);
3526
- });
3527
- });
3528
-
3529
1124
  describe("releaseCellAtoms", () => {
3530
1125
  it("atom families cache atoms until explicitly removed", () => {
3531
1126
  const { cellDataAtom, cellRuntimeAtom, cellHandleAtom } =
3532
1127
  exportedForTesting;
3533
1128
 
3534
- actions.createNewCell({ cellId: firstCellId, before: false });
3535
- const newCellId = state.cellIds.inOrderIds[1];
1129
+ seedCell({ cellId: firstCellId, before: false });
1130
+ const newCellId = state.cellIds[1];
3536
1131
 
3537
1132
  const dataAtom1 = cellDataAtom(newCellId);
3538
1133
  const runtimeAtom1 = cellRuntimeAtom(newCellId);
@@ -3547,116 +1142,15 @@ describe("cell reducer", () => {
3547
1142
  expect(runtimeAtom2).toBe(runtimeAtom1);
3548
1143
  expect(handleAtom2).toBe(handleAtom1);
3549
1144
  });
3550
-
3551
- it("cleans up atom family cache when cells are deleted", () => {
3552
- const { cellDataAtom, cellRuntimeAtom, cellHandleAtom } =
3553
- exportedForTesting;
3554
-
3555
- actions.createNewCell({ cellId: firstCellId, before: false });
3556
- const newCellId = state.cellIds.inOrderIds[1];
3557
-
3558
- // Access to create cache entries
3559
- const dataAtom1 = cellDataAtom(newCellId);
3560
- const runtimeAtom1 = cellRuntimeAtom(newCellId);
3561
- const handleAtom1 = cellHandleAtom(newCellId);
3562
-
3563
- // Triggers purge
3564
- actions.deleteCell({ cellId: newCellId });
3565
-
3566
- // Access again (should be new instances after cleanup)
3567
- const dataAtom2 = cellDataAtom(newCellId);
3568
- const runtimeAtom2 = cellRuntimeAtom(newCellId);
3569
- const handleAtom2 = cellHandleAtom(newCellId);
3570
-
3571
- expect(dataAtom2).not.toBe(dataAtom1);
3572
- expect(runtimeAtom2).not.toBe(runtimeAtom1);
3573
- expect(handleAtom2).not.toBe(handleAtom1);
3574
- });
3575
- });
3576
- });
3577
-
3578
- describe("isCellCodeHidden", () => {
3579
- const state = initialNotebookState();
3580
- const firstCellId = state.cellIds.inOrderIds[0];
3581
-
3582
- it("returns false when hide_code is false and cell is not untouched", () => {
3583
- const testCellId = cellId("test-cell");
3584
- const testState: NotebookState = {
3585
- ...state,
3586
- cellData: {
3587
- ...state.cellData,
3588
- [testCellId]: {
3589
- ...state.cellData[firstCellId],
3590
- id: testCellId,
3591
- config: { hide_code: false, disabled: false, column: null },
3592
- },
3593
- },
3594
- untouchedNewCells: new Set(),
3595
- };
3596
-
3597
- expect(exportedForTesting.isCellCodeHidden(testState, testCellId)).toBe(
3598
- false,
3599
- );
3600
- });
3601
-
3602
- it("returns true when hide_code is true and cell is not untouched", () => {
3603
- const testCellId = cellId("test-cell");
3604
- const testState: NotebookState = {
3605
- ...state,
3606
- cellData: {
3607
- ...state.cellData,
3608
- [testCellId]: {
3609
- ...state.cellData[firstCellId],
3610
- id: testCellId,
3611
- config: { hide_code: true, disabled: false, column: null },
3612
- },
3613
- },
3614
- untouchedNewCells: new Set(),
3615
- };
3616
-
3617
- expect(exportedForTesting.isCellCodeHidden(testState, testCellId)).toBe(
3618
- true,
3619
- );
3620
- });
3621
-
3622
- it("returns false when hide_code is true but cell is untouched", () => {
3623
- const testCellId = cellId("test-cell");
3624
- const testState: NotebookState = {
3625
- ...state,
3626
- cellData: {
3627
- ...state.cellData,
3628
- [testCellId]: {
3629
- ...state.cellData[firstCellId],
3630
- id: testCellId,
3631
- config: { hide_code: true, disabled: false, column: null },
3632
- },
3633
- },
3634
- untouchedNewCells: new Set([testCellId]),
3635
- };
3636
-
3637
- expect(exportedForTesting.isCellCodeHidden(testState, testCellId)).toBe(
3638
- false,
3639
- );
3640
1145
  });
1146
+ it("withdraws the submission of a cell that leaves the document", () => {
1147
+ MockExecutions.seed(store, []);
1148
+ submitCells(store, [firstCellId]);
1149
+ expect(store.get(notebookQueuedOrRunningCountAtom)).toBe(1);
3641
1150
 
3642
- it("returns false when hide_code is false and cell is untouched", () => {
3643
- const testCellId = cellId("test-cell");
3644
- const testState: NotebookState = {
3645
- ...state,
3646
- cellData: {
3647
- ...state.cellData,
3648
- [testCellId]: {
3649
- ...state.cellData[firstCellId],
3650
- id: testCellId,
3651
- config: { hide_code: false, disabled: false, column: null },
3652
- },
3653
- },
3654
- untouchedNewCells: new Set([testCellId]),
3655
- };
1151
+ actions.reconcileDocument({ order: [], cells: new Map() });
3656
1152
 
3657
- expect(exportedForTesting.isCellCodeHidden(testState, testCellId)).toBe(
3658
- false,
3659
- );
1153
+ expect(store.get(notebookQueuedOrRunningCountAtom)).toBe(0);
3660
1154
  });
3661
1155
  });
3662
1156
 
@@ -3839,11 +1333,11 @@ describe("createTracebackInfoAtom", () => {
3839
1333
  data: [{ type: "syntax", msg: "Syntax error", lineno: 1 }],
3840
1334
  mimetype: "application/vnd.marimo+error",
3841
1335
  },
3842
- status: "queued",
3843
1336
  },
3844
1337
  },
3845
1338
  }),
3846
1339
  );
1340
+ MockExecutions.seed(store, [MockExecutions.queued(cellId("cell1"))]);
3847
1341
 
3848
1342
  const tracebackAtom = exportedForTesting.createTracebackInfoAtom(
3849
1343
  cellId("cell1"),
@@ -3871,11 +1365,11 @@ describe("createTracebackInfoAtom", () => {
3871
1365
  data: [{ type: "syntax", msg: "Syntax error", lineno: 1 }],
3872
1366
  mimetype: "application/vnd.marimo+error",
3873
1367
  },
3874
- status: "running",
3875
1368
  },
3876
1369
  },
3877
1370
  }),
3878
1371
  );
1372
+ MockExecutions.seed(store, [MockExecutions.running(cellId("cell1"))]);
3879
1373
 
3880
1374
  const tracebackAtom = exportedForTesting.createTracebackInfoAtom(
3881
1375
  cellId("cell1"),
@@ -3886,31 +1380,9 @@ describe("createTracebackInfoAtom", () => {
3886
1380
  });
3887
1381
  });
3888
1382
 
3889
- describe("setCells snapshot preservation", () => {
1383
+ describe("kernel-ready", () => {
3890
1384
  const CELL_A = cellId("A");
3891
1385
  const CELL_B = cellId("B");
3892
- const newCells: CellData[] = [
3893
- {
3894
- id: CELL_A,
3895
- name: "a",
3896
- code: "1",
3897
- edited: false,
3898
- lastCodeRun: null,
3899
- lastExecutionTime: null,
3900
- config: { hide_code: false, disabled: false, column: null },
3901
- serializedEditorState: null,
3902
- },
3903
- {
3904
- id: CELL_B,
3905
- name: "b",
3906
- code: "2",
3907
- edited: false,
3908
- lastCodeRun: null,
3909
- lastExecutionTime: null,
3910
- config: { hide_code: false, disabled: false, column: null },
3911
- serializedEditorState: null,
3912
- },
3913
- ];
3914
1386
 
3915
1387
  const hydratedState = () =>
3916
1388
  MockNotebook.notebookState({
@@ -3940,44 +1412,74 @@ describe("setCells snapshot preservation", () => {
3940
1412
  },
3941
1413
  });
3942
1414
 
3943
- beforeEach(async () => {
3944
- const { isWasm } = await import("@/core/wasm/utils");
3945
- vi.mocked(isWasm).mockReturnValue(true);
3946
- });
3947
-
3948
- it("preserves hydrated output in WASM", () => {
1415
+ it("resetCellRuntimes starts every cell's runtime over", () => {
3949
1416
  const next = exportedForTesting.reducer(hydratedState(), {
3950
- type: "setCells",
3951
- payload: newCells,
1417
+ type: "resetCellRuntimes",
1418
+ payload: undefined,
3952
1419
  });
3953
1420
 
3954
- expect(next.cellRuntime[CELL_A].output).toMatchObject({
3955
- data: "hydrated-A",
1421
+ expect(next.cellRuntime[CELL_A].output).toBeNull();
1422
+ expect(next.cellRuntime[CELL_B].consoleOutputs).toEqual([]);
1423
+ });
1424
+
1425
+ it("resetCellRuntimes starts the scratch cell over too", () => {
1426
+ const state = MockNotebook.notebookState({
1427
+ cellData: { [CELL_A]: { id: CELL_A, code: "1" } },
1428
+ cellRuntime: {
1429
+ [SCRATCH_CELL_ID]: {
1430
+ output: {
1431
+ channel: "output",
1432
+ mimetype: "text/plain",
1433
+ data: "scratch",
1434
+ timestamp: 0,
1435
+ },
1436
+ },
1437
+ },
3956
1438
  });
1439
+ const next = exportedForTesting.reducer(state, {
1440
+ type: "resetCellRuntimes",
1441
+ payload: undefined,
1442
+ });
1443
+
1444
+ expect(next.cellRuntime[SCRATCH_CELL_ID].output).toBeNull();
3957
1445
  });
3958
1446
 
3959
- it("preserves console-only hydration in WASM", () => {
3960
- const next = exportedForTesting.reducer(hydratedState(), {
3961
- type: "setCells",
3962
- payload: newCells,
1447
+ describe("resetCellRuntimes in WASM", () => {
1448
+ beforeEach(async () => {
1449
+ const { isWasm } = await import("@/core/wasm/utils");
1450
+ vi.mocked(isWasm).mockReturnValue(true);
3963
1451
  });
3964
1452
 
3965
- expect(next.cellRuntime[CELL_B].consoleOutputs).toHaveLength(1);
3966
- expect(next.cellRuntime[CELL_B].consoleOutputs[0]).toMatchObject({
3967
- data: "hydrated-B-stdout",
1453
+ afterEach(async () => {
1454
+ const { isWasm } = await import("@/core/wasm/utils");
1455
+ vi.mocked(isWasm).mockReturnValue(false);
3968
1456
  });
3969
- });
3970
1457
 
3971
- it("resets cells with no prior runtime even in WASM", () => {
3972
- const empty = MockNotebook.notebookState({ cellData: {} });
3973
- const next = exportedForTesting.reducer(empty, {
3974
- type: "setCells",
3975
- payload: newCells,
1458
+ it("keeps hydrated outputs and console", () => {
1459
+ const next = exportedForTesting.reducer(hydratedState(), {
1460
+ type: "resetCellRuntimes",
1461
+ payload: undefined,
1462
+ });
1463
+
1464
+ expect(next.cellRuntime[CELL_A].output).toMatchObject({
1465
+ data: "hydrated-A",
1466
+ });
1467
+ expect(next.cellRuntime[CELL_B].consoleOutputs[0]).toMatchObject({
1468
+ data: "hydrated-B-stdout",
1469
+ });
3976
1470
  });
3977
1471
 
3978
- expect(next.cellRuntime[CELL_A].output).toBeNull();
3979
- expect(next.cellRuntime[CELL_A].consoleOutputs).toEqual([]);
3980
- expect(next.cellRuntime[CELL_B].output).toBeNull();
3981
- expect(next.cellRuntime[CELL_B].consoleOutputs).toEqual([]);
1472
+ it("starts cells with no prior runtime over", () => {
1473
+ const state = MockNotebook.notebookState({
1474
+ cellData: { [CELL_A]: { id: CELL_A, code: "1" } },
1475
+ });
1476
+ const next = exportedForTesting.reducer(state, {
1477
+ type: "resetCellRuntimes",
1478
+ payload: undefined,
1479
+ });
1480
+
1481
+ expect(next.cellRuntime[CELL_A].output).toBeNull();
1482
+ expect(next.cellRuntime[CELL_A].consoleOutputs).toEqual([]);
1483
+ });
3982
1484
  });
3983
1485
  });