matterviz 0.3.5 → 0.3.6

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 (855) hide show
  1. package/.vscode/launch.json +13 -0
  2. package/.vscodeignore +7 -0
  3. package/dist/assets/STLExporter-BpTH3YHE.js +8 -0
  4. package/dist/assets/browser-DdDecX_W.js +1 -0
  5. package/dist/assets/export-qgn-H9y6.js +2 -0
  6. package/dist/assets/main-DiKYzti2.css +1 -0
  7. package/dist/assets/moyo_wasm_bg-0ocwg7xY.wasm +0 -0
  8. package/dist/extension.js +31293 -0
  9. package/dist/src/lib/FilePicker.svelte +360 -0
  10. package/dist/src/lib/MillerIndexInput.svelte +66 -0
  11. package/dist/src/lib/api/mp.ts +26 -0
  12. package/dist/src/lib/api/optimade.ts +204 -0
  13. package/dist/src/lib/brillouin/BrillouinZone.svelte +549 -0
  14. package/dist/src/lib/brillouin/BrillouinZoneControls.svelte +144 -0
  15. package/dist/src/lib/brillouin/BrillouinZoneExportPane.svelte +146 -0
  16. package/dist/src/lib/brillouin/BrillouinZoneInfoPane.svelte +146 -0
  17. package/dist/src/lib/brillouin/BrillouinZoneScene.svelte +476 -0
  18. package/dist/src/lib/brillouin/BrillouinZoneTooltip.svelte +92 -0
  19. package/dist/src/lib/brillouin/compute.ts +529 -0
  20. package/dist/src/lib/brillouin/index.ts +8 -0
  21. package/dist/src/lib/brillouin/types.ts +51 -0
  22. package/dist/src/lib/chempot-diagram/ChemPotDiagram.svelte +327 -0
  23. package/dist/src/lib/chempot-diagram/ChemPotDiagram2D.svelte +846 -0
  24. package/dist/src/lib/chempot-diagram/ChemPotDiagram3D.svelte +3193 -0
  25. package/dist/src/lib/chempot-diagram/async-compute.svelte.ts +94 -0
  26. package/dist/src/lib/chempot-diagram/chempot-worker.ts +11 -0
  27. package/dist/src/lib/chempot-diagram/color.ts +42 -0
  28. package/dist/src/lib/chempot-diagram/compute.ts +1014 -0
  29. package/dist/src/lib/chempot-diagram/index.ts +6 -0
  30. package/dist/src/lib/chempot-diagram/pointer.ts +56 -0
  31. package/dist/src/lib/chempot-diagram/temperature.ts +77 -0
  32. package/dist/src/lib/chempot-diagram/types.ts +130 -0
  33. package/dist/src/lib/colors/index.ts +249 -0
  34. package/dist/src/lib/composition/BarChart.svelte +297 -0
  35. package/dist/src/lib/composition/BubbleChart.svelte +218 -0
  36. package/dist/src/lib/composition/Composition.svelte +165 -0
  37. package/dist/src/lib/composition/Formula.svelte +268 -0
  38. package/dist/src/lib/composition/FormulaFilter.svelte +1257 -0
  39. package/dist/src/lib/composition/PieChart.svelte +323 -0
  40. package/dist/src/lib/composition/format.ts +155 -0
  41. package/dist/src/lib/composition/index.ts +37 -0
  42. package/dist/src/lib/composition/parse.ts +605 -0
  43. package/dist/src/lib/constants.ts +134 -0
  44. package/dist/src/lib/controls.ts +42 -0
  45. package/dist/src/lib/convex-hull/ConvexHull.svelte +157 -0
  46. package/dist/src/lib/convex-hull/ConvexHull2D.svelte +825 -0
  47. package/dist/src/lib/convex-hull/ConvexHull3D.svelte +1801 -0
  48. package/dist/src/lib/convex-hull/ConvexHull4D.svelte +1398 -0
  49. package/dist/src/lib/convex-hull/ConvexHullControls.svelte +535 -0
  50. package/dist/src/lib/convex-hull/ConvexHullInfoPane.svelte +125 -0
  51. package/dist/src/lib/convex-hull/ConvexHullStats.svelte +929 -0
  52. package/dist/src/lib/convex-hull/ConvexHullTooltip.svelte +131 -0
  53. package/dist/src/lib/convex-hull/GasPressureControls.svelte +247 -0
  54. package/dist/src/lib/convex-hull/StructurePopup.svelte +151 -0
  55. package/dist/src/lib/convex-hull/TemperatureSlider.svelte +140 -0
  56. package/dist/src/lib/convex-hull/barycentric-coords.ts +246 -0
  57. package/dist/src/lib/convex-hull/demo-temperature.ts +63 -0
  58. package/dist/src/lib/convex-hull/gas-thermodynamics.ts +405 -0
  59. package/dist/src/lib/convex-hull/helpers.ts +932 -0
  60. package/dist/src/lib/convex-hull/index.ts +202 -0
  61. package/dist/src/lib/convex-hull/thermodynamics.ts +2192 -0
  62. package/dist/src/lib/convex-hull/types.ts +267 -0
  63. package/dist/src/lib/coordination/CoordinationBarPlot.svelte +311 -0
  64. package/dist/src/lib/coordination/calc-coordination.ts +93 -0
  65. package/dist/src/lib/coordination/index.ts +9 -0
  66. package/dist/src/lib/effects.svelte.ts +48 -0
  67. package/dist/src/lib/element/BohrAtom.svelte +147 -0
  68. package/dist/src/lib/element/ElementHeading.svelte +26 -0
  69. package/dist/src/lib/element/ElementPhoto.svelte +57 -0
  70. package/dist/src/lib/element/ElementStats.svelte +80 -0
  71. package/dist/src/lib/element/ElementTile.svelte +484 -0
  72. package/dist/src/lib/element/data.json.gz.d.ts +4 -0
  73. package/dist/src/lib/element/data.ts +14 -0
  74. package/dist/src/lib/element/index.ts +8 -0
  75. package/dist/src/lib/element/types.ts +62 -0
  76. package/dist/src/lib/feedback/ClickFeedback.svelte +58 -0
  77. package/dist/src/lib/feedback/DragOverlay.svelte +42 -0
  78. package/dist/src/lib/feedback/index.ts +4 -0
  79. package/dist/src/lib/fermi-surface/FermiSlice.svelte +189 -0
  80. package/dist/src/lib/fermi-surface/FermiSurface.svelte +600 -0
  81. package/dist/src/lib/fermi-surface/FermiSurfaceControls.svelte +448 -0
  82. package/dist/src/lib/fermi-surface/FermiSurfaceScene.svelte +794 -0
  83. package/dist/src/lib/fermi-surface/FermiSurfaceTooltip.svelte +111 -0
  84. package/dist/src/lib/fermi-surface/compute.ts +728 -0
  85. package/dist/src/lib/fermi-surface/constants.ts +32 -0
  86. package/dist/src/lib/fermi-surface/export.ts +64 -0
  87. package/dist/src/lib/fermi-surface/index.ts +14 -0
  88. package/dist/src/lib/fermi-surface/marching-cubes.ts +3 -0
  89. package/dist/src/lib/fermi-surface/parse.ts +574 -0
  90. package/dist/src/lib/fermi-surface/symmetry.ts +56 -0
  91. package/dist/src/lib/fermi-surface/types.ts +159 -0
  92. package/dist/src/lib/heatmap-matrix/HeatmapMatrix.svelte +1545 -0
  93. package/dist/src/lib/heatmap-matrix/HeatmapMatrixControls.svelte +225 -0
  94. package/dist/src/lib/heatmap-matrix/index.ts +167 -0
  95. package/dist/src/lib/heatmap-matrix/shared.ts +7 -0
  96. package/dist/src/lib/icons.ts +650 -0
  97. package/dist/src/lib/index.ts +61 -0
  98. package/dist/src/lib/io/decompress.ts +92 -0
  99. package/dist/src/lib/io/export.ts +385 -0
  100. package/dist/src/lib/io/fetch.ts +46 -0
  101. package/dist/src/lib/io/file-drop.ts +51 -0
  102. package/dist/src/lib/io/index.ts +7 -0
  103. package/dist/src/lib/io/is-binary.ts +24 -0
  104. package/dist/src/lib/io/types.ts +8 -0
  105. package/dist/src/lib/io/url-drop.ts +141 -0
  106. package/dist/src/lib/isosurface/Isosurface.svelte +285 -0
  107. package/dist/src/lib/isosurface/IsosurfaceControls.svelte +277 -0
  108. package/dist/src/lib/isosurface/index.ts +7 -0
  109. package/dist/src/lib/isosurface/parse.ts +656 -0
  110. package/dist/src/lib/isosurface/slice.ts +175 -0
  111. package/dist/src/lib/isosurface/types.ts +309 -0
  112. package/dist/src/lib/labels.ts +320 -0
  113. package/dist/src/lib/layout/FullscreenToggle.svelte +50 -0
  114. package/dist/src/lib/layout/InfoCard.svelte +120 -0
  115. package/dist/src/lib/layout/InfoTag.svelte +185 -0
  116. package/dist/src/lib/layout/PropertyFilter.svelte +246 -0
  117. package/dist/src/lib/layout/SettingsSection.svelte +148 -0
  118. package/dist/src/lib/layout/SubpageGrid.svelte +82 -0
  119. package/dist/src/lib/layout/fullscreen.ts +65 -0
  120. package/dist/src/lib/layout/index.ts +11 -0
  121. package/dist/src/lib/layout/json-tree/JsonNode.svelte +548 -0
  122. package/dist/src/lib/layout/json-tree/JsonTree.svelte +1230 -0
  123. package/dist/src/lib/layout/json-tree/JsonValue.svelte +334 -0
  124. package/dist/src/lib/layout/json-tree/index.ts +3 -0
  125. package/dist/src/lib/layout/json-tree/types.ts +126 -0
  126. package/dist/src/lib/layout/json-tree/utils.ts +682 -0
  127. package/dist/src/lib/marching-cubes.ts +614 -0
  128. package/dist/src/lib/math.ts +1081 -0
  129. package/dist/src/lib/overlays/ContextMenu.svelte +162 -0
  130. package/dist/src/lib/overlays/CopyButton.svelte +45 -0
  131. package/dist/src/lib/overlays/DragControlTab.svelte +98 -0
  132. package/dist/src/lib/overlays/DraggablePane.svelte +487 -0
  133. package/dist/src/lib/overlays/InfoPaneCards.svelte +149 -0
  134. package/dist/src/lib/overlays/index.ts +3 -0
  135. package/dist/src/lib/periodic-table/PeriodicTable.svelte +469 -0
  136. package/dist/src/lib/periodic-table/PeriodicTableControls.svelte +557 -0
  137. package/dist/src/lib/periodic-table/PropertySelect.svelte +37 -0
  138. package/dist/src/lib/periodic-table/index.ts +12 -0
  139. package/dist/src/lib/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +1086 -0
  140. package/dist/src/lib/phase-diagram/PhaseDiagramControls.svelte +444 -0
  141. package/dist/src/lib/phase-diagram/PhaseDiagramEditorPane.svelte +126 -0
  142. package/dist/src/lib/phase-diagram/PhaseDiagramExportPane.svelte +184 -0
  143. package/dist/src/lib/phase-diagram/PhaseDiagramTooltip.svelte +391 -0
  144. package/dist/src/lib/phase-diagram/TdbInfoPanel.svelte +203 -0
  145. package/dist/src/lib/phase-diagram/build-diagram.ts +186 -0
  146. package/dist/src/lib/phase-diagram/colors.ts +58 -0
  147. package/dist/src/lib/phase-diagram/diagram-input.ts +40 -0
  148. package/dist/src/lib/phase-diagram/index.ts +13 -0
  149. package/dist/src/lib/phase-diagram/parse.ts +348 -0
  150. package/dist/src/lib/phase-diagram/svg-to-diagram.ts +1023 -0
  151. package/dist/src/lib/phase-diagram/types.ts +144 -0
  152. package/dist/src/lib/phase-diagram/utils.ts +775 -0
  153. package/dist/src/lib/plot/AxisLabel.svelte +51 -0
  154. package/dist/src/lib/plot/BarPlot.svelte +2113 -0
  155. package/dist/src/lib/plot/BarPlotControls.svelte +66 -0
  156. package/dist/src/lib/plot/BinnedScatterPlot.svelte +1114 -0
  157. package/dist/src/lib/plot/ColorBar.svelte +721 -0
  158. package/dist/src/lib/plot/ColorScaleSelect.svelte +54 -0
  159. package/dist/src/lib/plot/ElementScatter.svelte +63 -0
  160. package/dist/src/lib/plot/FillArea.svelte +223 -0
  161. package/dist/src/lib/plot/Histogram.svelte +1558 -0
  162. package/dist/src/lib/plot/HistogramControls.svelte +212 -0
  163. package/dist/src/lib/plot/InteractiveAxisLabel.svelte +96 -0
  164. package/dist/src/lib/plot/Line.svelte +84 -0
  165. package/dist/src/lib/plot/PlotAxis.svelte +169 -0
  166. package/dist/src/lib/plot/PlotControls.svelte +537 -0
  167. package/dist/src/lib/plot/PlotLegend.svelte +569 -0
  168. package/dist/src/lib/plot/PlotTooltip.svelte +67 -0
  169. package/dist/src/lib/plot/PortalSelect.svelte +253 -0
  170. package/dist/src/lib/plot/ReferenceLine3D.svelte +156 -0
  171. package/dist/src/lib/plot/ReferencePlane.svelte +175 -0
  172. package/dist/src/lib/plot/ScatterPlot.svelte +2778 -0
  173. package/dist/src/lib/plot/ScatterPlot3D.svelte +529 -0
  174. package/dist/src/lib/plot/ScatterPlot3DControls.svelte +437 -0
  175. package/dist/src/lib/plot/ScatterPlot3DScene.svelte +912 -0
  176. package/dist/src/lib/plot/ScatterPlotControls.svelte +306 -0
  177. package/dist/src/lib/plot/ScatterPoint.svelte +182 -0
  178. package/dist/src/lib/plot/SpacegroupBarPlot.svelte +293 -0
  179. package/dist/src/lib/plot/Surface3D.svelte +197 -0
  180. package/dist/src/lib/plot/ZeroLines.svelte +97 -0
  181. package/dist/src/lib/plot/ZoomRect.svelte +23 -0
  182. package/dist/src/lib/plot/adaptive-density.ts +316 -0
  183. package/dist/src/lib/plot/auto-place.ts +184 -0
  184. package/dist/src/lib/plot/axis-utils.ts +122 -0
  185. package/dist/src/lib/plot/binned-scatter-types.ts +83 -0
  186. package/dist/src/lib/plot/data-cleaning.ts +1069 -0
  187. package/dist/src/lib/plot/data-transform.ts +69 -0
  188. package/dist/src/lib/plot/defaults.ts +9 -0
  189. package/dist/src/lib/plot/fill-utils.ts +494 -0
  190. package/dist/src/lib/plot/hover-lock.svelte.ts +60 -0
  191. package/dist/src/lib/plot/index.ts +53 -0
  192. package/dist/src/lib/plot/interactions.ts +119 -0
  193. package/dist/src/lib/plot/layout.ts +425 -0
  194. package/dist/src/lib/plot/reference-line.ts +426 -0
  195. package/dist/src/lib/plot/scales.ts +654 -0
  196. package/dist/src/lib/plot/svg.ts +23 -0
  197. package/dist/src/lib/plot/types.ts +1144 -0
  198. package/dist/src/lib/plot/utils/label-placement.ts +541 -0
  199. package/dist/src/lib/plot/utils/series-visibility.ts +140 -0
  200. package/dist/src/lib/plot/utils.ts +11 -0
  201. package/dist/src/lib/rdf/RdfPlot.svelte +247 -0
  202. package/dist/src/lib/rdf/calc-rdf.ts +167 -0
  203. package/dist/src/lib/rdf/index.ts +27 -0
  204. package/dist/src/lib/sanitize.ts +126 -0
  205. package/dist/src/lib/settings.ts +1479 -0
  206. package/dist/src/lib/spectral/Bands.svelte +1040 -0
  207. package/dist/src/lib/spectral/BandsAndDos.svelte +134 -0
  208. package/dist/src/lib/spectral/BrillouinBandsDos.svelte +252 -0
  209. package/dist/src/lib/spectral/Dos.svelte +697 -0
  210. package/dist/src/lib/spectral/helpers.ts +1381 -0
  211. package/dist/src/lib/spectral/index.ts +8 -0
  212. package/dist/src/lib/spectral/types.ts +112 -0
  213. package/dist/src/lib/state.svelte.ts +64 -0
  214. package/dist/src/lib/structure/Arrow.svelte +72 -0
  215. package/dist/src/lib/structure/AtomLegend.svelte +815 -0
  216. package/dist/src/lib/structure/Bond.svelte +140 -0
  217. package/dist/src/lib/structure/CanvasTooltip.svelte +33 -0
  218. package/dist/src/lib/structure/CellSelect.svelte +349 -0
  219. package/dist/src/lib/structure/Cylinder.svelte +45 -0
  220. package/dist/src/lib/structure/Lattice.svelte +196 -0
  221. package/dist/src/lib/structure/Structure.svelte +2248 -0
  222. package/dist/src/lib/structure/StructureControls.svelte +1273 -0
  223. package/dist/src/lib/structure/StructureExportPane.svelte +252 -0
  224. package/dist/src/lib/structure/StructureInfoPane.svelte +737 -0
  225. package/dist/src/lib/structure/StructureScene.svelte +2255 -0
  226. package/dist/src/lib/structure/atom-properties.ts +316 -0
  227. package/dist/src/lib/structure/bond-order-perception.ts +447 -0
  228. package/dist/src/lib/structure/bonding.ts +944 -0
  229. package/dist/src/lib/structure/export.ts +861 -0
  230. package/dist/src/lib/structure/index.ts +291 -0
  231. package/dist/src/lib/structure/label-placement.ts +130 -0
  232. package/dist/src/lib/structure/measure.ts +45 -0
  233. package/dist/src/lib/structure/parse.ts +1705 -0
  234. package/dist/src/lib/structure/partial-occupancy.ts +183 -0
  235. package/dist/src/lib/structure/pbc.ts +164 -0
  236. package/dist/src/lib/structure/supercell.ts +226 -0
  237. package/dist/src/lib/structure/validation.ts +11 -0
  238. package/dist/src/lib/symmetry/SymmetryStats.svelte +226 -0
  239. package/dist/src/lib/symmetry/WyckoffTable.svelte +120 -0
  240. package/dist/src/lib/symmetry/cell-transform.ts +118 -0
  241. package/dist/src/lib/symmetry/index.ts +348 -0
  242. package/dist/src/lib/symmetry/spacegroups.ts +404 -0
  243. package/dist/src/lib/table/HeatmapTable.svelte +1833 -0
  244. package/dist/src/lib/table/ToggleMenu.svelte +385 -0
  245. package/dist/src/lib/table/index.ts +139 -0
  246. package/dist/src/lib/theme/ThemeControl.svelte +53 -0
  247. package/dist/src/lib/theme/index.ts +107 -0
  248. package/dist/src/lib/theme/themes.mjs +297 -0
  249. package/dist/src/lib/time.ts +71 -0
  250. package/dist/src/lib/tooltip/TooltipContent.svelte +58 -0
  251. package/dist/src/lib/tooltip/index.ts +2 -0
  252. package/dist/src/lib/tooltip/types.ts +13 -0
  253. package/dist/src/lib/trajectory/Trajectory.svelte +1545 -0
  254. package/dist/src/lib/trajectory/TrajectoryError.svelte +128 -0
  255. package/dist/src/lib/trajectory/TrajectoryExportPane.svelte +357 -0
  256. package/dist/src/lib/trajectory/TrajectoryInfoPane.svelte +313 -0
  257. package/dist/src/lib/trajectory/constants.ts +7 -0
  258. package/dist/src/lib/trajectory/extract.ts +196 -0
  259. package/dist/src/lib/trajectory/format-detect.ts +96 -0
  260. package/dist/src/lib/trajectory/frame-reader.ts +456 -0
  261. package/dist/src/lib/trajectory/helpers.ts +217 -0
  262. package/dist/src/lib/trajectory/index.ts +218 -0
  263. package/dist/src/lib/trajectory/parse/ase.ts +109 -0
  264. package/dist/src/lib/trajectory/parse/hdf5.ts +173 -0
  265. package/dist/src/lib/trajectory/parse/index.ts +411 -0
  266. package/dist/src/lib/trajectory/parse/lammps.ts +215 -0
  267. package/dist/src/lib/trajectory/parse/vasp.ts +102 -0
  268. package/dist/src/lib/trajectory/parse/xyz.ts +143 -0
  269. package/dist/src/lib/trajectory/plotting.ts +599 -0
  270. package/dist/src/lib/trajectory/types.ts +13 -0
  271. package/dist/src/lib/utils.ts +56 -0
  272. package/dist/src/lib/xrd/XrdPlot.svelte +615 -0
  273. package/dist/src/lib/xrd/broadening.ts +130 -0
  274. package/dist/src/lib/xrd/calc-xrd.ts +397 -0
  275. package/dist/src/lib/xrd/index.ts +38 -0
  276. package/dist/src/lib/xrd/parse.ts +858 -0
  277. package/dist/webview.js +29421 -0
  278. package/icon.png +0 -0
  279. package/license +1 -1
  280. package/matterviz-0.3.2.vsix +0 -0
  281. package/matterviz-0.3.4.vsix +0 -0
  282. package/matterviz-0.3.5.vsix +0 -0
  283. package/package.json +1460 -215
  284. package/readme.md +171 -98
  285. package/scripts/sync-config.ts +101 -0
  286. package/src/declarations.d.ts +2 -0
  287. package/src/extension.ts +972 -0
  288. package/src/node-io.ts +65 -0
  289. package/src/types.ts +17 -0
  290. package/src/webview/JsonBrowser.svelte +1079 -0
  291. package/src/webview/PlotPanel.svelte +346 -0
  292. package/src/webview/detect.ts +444 -0
  293. package/src/webview/main.ts +764 -0
  294. package/src/webview/plot-utils.ts +250 -0
  295. package/test-fixtures/all-viz-types.json.gz +0 -0
  296. package/test-fixtures/plot-demo-data.json.gz +0 -0
  297. package/tests/detect.test.ts +604 -0
  298. package/tests/extension.test.ts +2041 -0
  299. package/tests/node-io.test.ts +39 -0
  300. package/tests/plot-utils.test.ts +302 -0
  301. package/tests/vite-plugin-json-gz.test.ts +114 -0
  302. package/tests/vscode-mock.ts +18 -0
  303. package/tests/webview.test.ts +231 -0
  304. package/tsconfig.json +20 -0
  305. package/vite-plugin-json-gz.ts +29 -0
  306. package/vite.config.ts +34 -0
  307. package/vite.extension.config.ts +34 -0
  308. package/dist/EmptyState.svelte.d.ts +0 -9
  309. package/dist/FilePicker.svelte +0 -360
  310. package/dist/FilePicker.svelte.d.ts +0 -17
  311. package/dist/Icon.svelte.d.ts +0 -13
  312. package/dist/MillerIndexInput.svelte +0 -66
  313. package/dist/MillerIndexInput.svelte.d.ts +0 -7
  314. package/dist/api/mp.d.ts +0 -6
  315. package/dist/api/mp.js +0 -22
  316. package/dist/api/optimade.d.ts +0 -45
  317. package/dist/api/optimade.js +0 -135
  318. package/dist/brillouin/BrillouinZone.svelte +0 -546
  319. package/dist/brillouin/BrillouinZone.svelte.d.ts +0 -83
  320. package/dist/brillouin/BrillouinZoneControls.svelte +0 -144
  321. package/dist/brillouin/BrillouinZoneControls.svelte.d.ts +0 -17
  322. package/dist/brillouin/BrillouinZoneExportPane.svelte +0 -148
  323. package/dist/brillouin/BrillouinZoneExportPane.svelte.d.ts +0 -15
  324. package/dist/brillouin/BrillouinZoneInfoPane.svelte +0 -146
  325. package/dist/brillouin/BrillouinZoneInfoPane.svelte.d.ts +0 -13
  326. package/dist/brillouin/BrillouinZoneScene.svelte +0 -476
  327. package/dist/brillouin/BrillouinZoneScene.svelte.d.ts +0 -48
  328. package/dist/brillouin/BrillouinZoneTooltip.svelte +0 -92
  329. package/dist/brillouin/BrillouinZoneTooltip.svelte.d.ts +0 -8
  330. package/dist/brillouin/compute.d.ts +0 -17
  331. package/dist/brillouin/compute.js +0 -426
  332. package/dist/brillouin/index.d.ts +0 -8
  333. package/dist/brillouin/index.js +0 -8
  334. package/dist/brillouin/types.d.ts +0 -48
  335. package/dist/brillouin/types.js +0 -1
  336. package/dist/chempot-diagram/ChemPotDiagram.svelte +0 -327
  337. package/dist/chempot-diagram/ChemPotDiagram.svelte.d.ts +0 -13
  338. package/dist/chempot-diagram/ChemPotDiagram2D.svelte +0 -847
  339. package/dist/chempot-diagram/ChemPotDiagram2D.svelte.d.ts +0 -16
  340. package/dist/chempot-diagram/ChemPotDiagram3D.svelte +0 -3194
  341. package/dist/chempot-diagram/ChemPotDiagram3D.svelte.d.ts +0 -16
  342. package/dist/chempot-diagram/ChemPotScene3D.svelte.d.ts +0 -7
  343. package/dist/chempot-diagram/async-compute.svelte.d.ts +0 -3
  344. package/dist/chempot-diagram/async-compute.svelte.js +0 -78
  345. package/dist/chempot-diagram/chempot-worker.d.ts +0 -1
  346. package/dist/chempot-diagram/chempot-worker.js +0 -11
  347. package/dist/chempot-diagram/color.d.ts +0 -10
  348. package/dist/chempot-diagram/color.js +0 -32
  349. package/dist/chempot-diagram/compute.d.ts +0 -48
  350. package/dist/chempot-diagram/compute.js +0 -812
  351. package/dist/chempot-diagram/index.d.ts +0 -6
  352. package/dist/chempot-diagram/index.js +0 -6
  353. package/dist/chempot-diagram/pointer.d.ts +0 -16
  354. package/dist/chempot-diagram/pointer.js +0 -40
  355. package/dist/chempot-diagram/temperature.d.ts +0 -15
  356. package/dist/chempot-diagram/temperature.js +0 -36
  357. package/dist/chempot-diagram/types.d.ts +0 -86
  358. package/dist/chempot-diagram/types.js +0 -28
  359. package/dist/colors/index.d.ts +0 -47
  360. package/dist/colors/index.js +0 -203
  361. package/dist/composition/BarChart.svelte +0 -297
  362. package/dist/composition/BarChart.svelte.d.ts +0 -39
  363. package/dist/composition/BubbleChart.svelte +0 -218
  364. package/dist/composition/BubbleChart.svelte.d.ts +0 -28
  365. package/dist/composition/Composition.svelte +0 -164
  366. package/dist/composition/Composition.svelte.d.ts +0 -15
  367. package/dist/composition/Formula.svelte +0 -265
  368. package/dist/composition/Formula.svelte.d.ts +0 -19
  369. package/dist/composition/FormulaFilter.svelte +0 -1259
  370. package/dist/composition/FormulaFilter.svelte.d.ts +0 -51
  371. package/dist/composition/PieChart.svelte +0 -323
  372. package/dist/composition/PieChart.svelte.d.ts +0 -37
  373. package/dist/composition/format.d.ts +0 -15
  374. package/dist/composition/format.js +0 -109
  375. package/dist/composition/index.d.ts +0 -20
  376. package/dist/composition/index.js +0 -14
  377. package/dist/composition/parse.d.ts +0 -55
  378. package/dist/composition/parse.js +0 -459
  379. package/dist/constants.d.ts +0 -29
  380. package/dist/constants.js +0 -99
  381. package/dist/controls.d.ts +0 -14
  382. package/dist/controls.js +0 -30
  383. package/dist/convex-hull/ConvexHull.svelte +0 -157
  384. package/dist/convex-hull/ConvexHull.svelte.d.ts +0 -13
  385. package/dist/convex-hull/ConvexHull2D.svelte +0 -814
  386. package/dist/convex-hull/ConvexHull2D.svelte.d.ts +0 -11
  387. package/dist/convex-hull/ConvexHull3D.svelte +0 -1790
  388. package/dist/convex-hull/ConvexHull3D.svelte.d.ts +0 -8
  389. package/dist/convex-hull/ConvexHull4D.svelte +0 -1386
  390. package/dist/convex-hull/ConvexHull4D.svelte.d.ts +0 -8
  391. package/dist/convex-hull/ConvexHullControls.svelte +0 -546
  392. package/dist/convex-hull/ConvexHullControls.svelte.d.ts +0 -48
  393. package/dist/convex-hull/ConvexHullInfoPane.svelte +0 -122
  394. package/dist/convex-hull/ConvexHullInfoPane.svelte.d.ts +0 -18
  395. package/dist/convex-hull/ConvexHullStats.svelte +0 -922
  396. package/dist/convex-hull/ConvexHullStats.svelte.d.ts +0 -15
  397. package/dist/convex-hull/ConvexHullTooltip.svelte +0 -131
  398. package/dist/convex-hull/ConvexHullTooltip.svelte.d.ts +0 -33
  399. package/dist/convex-hull/GasPressureControls.svelte +0 -247
  400. package/dist/convex-hull/GasPressureControls.svelte.d.ts +0 -11
  401. package/dist/convex-hull/StructurePopup.svelte +0 -116
  402. package/dist/convex-hull/StructurePopup.svelte.d.ts +0 -18
  403. package/dist/convex-hull/TemperatureSlider.svelte +0 -137
  404. package/dist/convex-hull/TemperatureSlider.svelte.d.ts +0 -8
  405. package/dist/convex-hull/barycentric-coords.d.ts +0 -18
  406. package/dist/convex-hull/barycentric-coords.js +0 -182
  407. package/dist/convex-hull/demo-temperature.d.ts +0 -6
  408. package/dist/convex-hull/demo-temperature.js +0 -40
  409. package/dist/convex-hull/gas-thermodynamics.d.ts +0 -16
  410. package/dist/convex-hull/gas-thermodynamics.js +0 -316
  411. package/dist/convex-hull/helpers.d.ts +0 -103
  412. package/dist/convex-hull/helpers.js +0 -689
  413. package/dist/convex-hull/index.d.ts +0 -118
  414. package/dist/convex-hull/index.js +0 -57
  415. package/dist/convex-hull/thermodynamics.d.ts +0 -66
  416. package/dist/convex-hull/thermodynamics.js +0 -1752
  417. package/dist/convex-hull/types.d.ts +0 -162
  418. package/dist/convex-hull/types.js +0 -36
  419. package/dist/coordination/CoordinationBarPlot.svelte +0 -311
  420. package/dist/coordination/CoordinationBarPlot.svelte.d.ts +0 -30
  421. package/dist/coordination/calc-coordination.d.ts +0 -15
  422. package/dist/coordination/calc-coordination.js +0 -63
  423. package/dist/coordination/index.d.ts +0 -8
  424. package/dist/coordination/index.js +0 -7
  425. package/dist/element/BohrAtom.svelte +0 -147
  426. package/dist/element/BohrAtom.svelte.d.ts +0 -20
  427. package/dist/element/ElementHeading.svelte +0 -26
  428. package/dist/element/ElementHeading.svelte.d.ts +0 -8
  429. package/dist/element/ElementPhoto.svelte +0 -57
  430. package/dist/element/ElementPhoto.svelte.d.ts +0 -9
  431. package/dist/element/ElementStats.svelte +0 -80
  432. package/dist/element/ElementStats.svelte.d.ts +0 -8
  433. package/dist/element/ElementTile.svelte +0 -484
  434. package/dist/element/ElementTile.svelte.d.ts +0 -29
  435. package/dist/element/Nucleus.svelte.d.ts +0 -17
  436. package/dist/element/data.d.ts +0 -2
  437. package/dist/element/data.js +0 -2
  438. package/dist/element/data.json.gz.d.ts +0 -2
  439. package/dist/element/index.d.ts +0 -8
  440. package/dist/element/index.js +0 -8
  441. package/dist/element/types.d.ts +0 -57
  442. package/dist/element/types.js +0 -1
  443. package/dist/feedback/ClickFeedback.svelte +0 -58
  444. package/dist/feedback/ClickFeedback.svelte.d.ts +0 -12
  445. package/dist/feedback/DragOverlay.svelte +0 -42
  446. package/dist/feedback/DragOverlay.svelte.d.ts +0 -7
  447. package/dist/feedback/Spinner.svelte.d.ts +0 -7
  448. package/dist/feedback/StatusMessage.svelte.d.ts +0 -9
  449. package/dist/feedback/index.d.ts +0 -4
  450. package/dist/feedback/index.js +0 -4
  451. package/dist/fermi-surface/FermiSlice.svelte +0 -189
  452. package/dist/fermi-surface/FermiSlice.svelte.d.ts +0 -24
  453. package/dist/fermi-surface/FermiSurface.svelte +0 -600
  454. package/dist/fermi-surface/FermiSurface.svelte.d.ts +0 -83
  455. package/dist/fermi-surface/FermiSurfaceControls.svelte +0 -452
  456. package/dist/fermi-surface/FermiSurfaceControls.svelte.d.ts +0 -35
  457. package/dist/fermi-surface/FermiSurfaceScene.svelte +0 -792
  458. package/dist/fermi-surface/FermiSurfaceScene.svelte.d.ts +0 -50
  459. package/dist/fermi-surface/FermiSurfaceTooltip.svelte +0 -111
  460. package/dist/fermi-surface/FermiSurfaceTooltip.svelte.d.ts +0 -8
  461. package/dist/fermi-surface/compute.d.ts +0 -5
  462. package/dist/fermi-surface/compute.js +0 -538
  463. package/dist/fermi-surface/constants.d.ts +0 -9
  464. package/dist/fermi-surface/constants.js +0 -27
  465. package/dist/fermi-surface/export.d.ts +0 -5
  466. package/dist/fermi-surface/export.js +0 -63
  467. package/dist/fermi-surface/index.d.ts +0 -12
  468. package/dist/fermi-surface/index.js +0 -13
  469. package/dist/fermi-surface/marching-cubes.d.ts +0 -2
  470. package/dist/fermi-surface/marching-cubes.js +0 -2
  471. package/dist/fermi-surface/parse.d.ts +0 -2
  472. package/dist/fermi-surface/parse.js +0 -495
  473. package/dist/fermi-surface/symmetry.d.ts +0 -3
  474. package/dist/fermi-surface/symmetry.js +0 -46
  475. package/dist/fermi-surface/types.d.ts +0 -113
  476. package/dist/fermi-surface/types.js +0 -4
  477. package/dist/heatmap-matrix/HeatmapMatrix.svelte +0 -1527
  478. package/dist/heatmap-matrix/HeatmapMatrix.svelte.d.ts +0 -110
  479. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte +0 -225
  480. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte.d.ts +0 -30
  481. package/dist/heatmap-matrix/index.d.ts +0 -53
  482. package/dist/heatmap-matrix/index.js +0 -100
  483. package/dist/heatmap-matrix/shared.d.ts +0 -2
  484. package/dist/heatmap-matrix/shared.js +0 -4
  485. package/dist/icons.d.ts +0 -569
  486. package/dist/icons.js +0 -648
  487. package/dist/index.d.ts +0 -39
  488. package/dist/index.js +0 -39
  489. package/dist/io/decompress.d.ts +0 -10
  490. package/dist/io/decompress.js +0 -74
  491. package/dist/io/export.d.ts +0 -16
  492. package/dist/io/export.js +0 -316
  493. package/dist/io/fetch.d.ts +0 -5
  494. package/dist/io/fetch.js +0 -39
  495. package/dist/io/file-drop.d.ts +0 -7
  496. package/dist/io/file-drop.js +0 -43
  497. package/dist/io/index.d.ts +0 -7
  498. package/dist/io/index.js +0 -7
  499. package/dist/io/is-binary.d.ts +0 -1
  500. package/dist/io/is-binary.js +0 -20
  501. package/dist/io/types.d.ts +0 -8
  502. package/dist/io/types.js +0 -1
  503. package/dist/io/url-drop.d.ts +0 -2
  504. package/dist/io/url-drop.js +0 -117
  505. package/dist/isosurface/Isosurface.svelte +0 -285
  506. package/dist/isosurface/Isosurface.svelte.d.ts +0 -8
  507. package/dist/isosurface/IsosurfaceControls.svelte +0 -291
  508. package/dist/isosurface/IsosurfaceControls.svelte.d.ts +0 -9
  509. package/dist/isosurface/index.d.ts +0 -5
  510. package/dist/isosurface/index.js +0 -6
  511. package/dist/isosurface/parse.d.ts +0 -6
  512. package/dist/isosurface/parse.js +0 -553
  513. package/dist/isosurface/slice.d.ts +0 -11
  514. package/dist/isosurface/slice.js +0 -140
  515. package/dist/isosurface/types.d.ts +0 -56
  516. package/dist/isosurface/types.js +0 -227
  517. package/dist/labels.d.ts +0 -53
  518. package/dist/labels.js +0 -274
  519. package/dist/layout/FullscreenToggle.svelte +0 -50
  520. package/dist/layout/FullscreenToggle.svelte.d.ts +0 -7
  521. package/dist/layout/InfoCard.svelte +0 -120
  522. package/dist/layout/InfoCard.svelte.d.ts +0 -21
  523. package/dist/layout/InfoTag.svelte +0 -183
  524. package/dist/layout/InfoTag.svelte.d.ts +0 -19
  525. package/dist/layout/PropertyFilter.svelte +0 -244
  526. package/dist/layout/PropertyFilter.svelte.d.ts +0 -24
  527. package/dist/layout/SettingsSection.svelte +0 -148
  528. package/dist/layout/SettingsSection.svelte.d.ts +0 -17
  529. package/dist/layout/SubpageGrid.svelte +0 -82
  530. package/dist/layout/SubpageGrid.svelte.d.ts +0 -14
  531. package/dist/layout/fullscreen.d.ts +0 -9
  532. package/dist/layout/fullscreen.js +0 -53
  533. package/dist/layout/index.d.ts +0 -10
  534. package/dist/layout/index.js +0 -8
  535. package/dist/layout/json-tree/JsonNode.svelte +0 -548
  536. package/dist/layout/json-tree/JsonNode.svelte.d.ts +0 -11
  537. package/dist/layout/json-tree/JsonTree.svelte +0 -1222
  538. package/dist/layout/json-tree/JsonTree.svelte.d.ts +0 -6
  539. package/dist/layout/json-tree/JsonValue.svelte +0 -334
  540. package/dist/layout/json-tree/JsonValue.svelte.d.ts +0 -9
  541. package/dist/layout/json-tree/index.d.ts +0 -3
  542. package/dist/layout/json-tree/index.js +0 -3
  543. package/dist/layout/json-tree/types.d.ts +0 -73
  544. package/dist/layout/json-tree/types.js +0 -3
  545. package/dist/layout/json-tree/utils.d.ts +0 -29
  546. package/dist/layout/json-tree/utils.js +0 -649
  547. package/dist/marching-cubes.d.ts +0 -14
  548. package/dist/marching-cubes.js +0 -542
  549. package/dist/math.d.ts +0 -91
  550. package/dist/math.js +0 -896
  551. package/dist/overlays/ContextMenu.svelte +0 -162
  552. package/dist/overlays/ContextMenu.svelte.d.ts +0 -25
  553. package/dist/overlays/CopyButton.svelte +0 -45
  554. package/dist/overlays/CopyButton.svelte.d.ts +0 -8
  555. package/dist/overlays/DraggablePane.svelte +0 -564
  556. package/dist/overlays/DraggablePane.svelte.d.ts +0 -36
  557. package/dist/overlays/InfoPaneCards.svelte +0 -149
  558. package/dist/overlays/InfoPaneCards.svelte.d.ts +0 -22
  559. package/dist/overlays/index.d.ts +0 -2
  560. package/dist/overlays/index.js +0 -2
  561. package/dist/periodic-table/PeriodicTable.svelte +0 -469
  562. package/dist/periodic-table/PeriodicTable.svelte.d.ts +0 -55
  563. package/dist/periodic-table/PeriodicTableControls.svelte +0 -557
  564. package/dist/periodic-table/PeriodicTableControls.svelte.d.ts +0 -24
  565. package/dist/periodic-table/PropertySelect.svelte +0 -37
  566. package/dist/periodic-table/PropertySelect.svelte.d.ts +0 -13
  567. package/dist/periodic-table/TableInset.svelte.d.ts +0 -9
  568. package/dist/periodic-table/index.d.ts +0 -10
  569. package/dist/periodic-table/index.js +0 -4
  570. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +0 -1084
  571. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +0 -44
  572. package/dist/phase-diagram/PhaseDiagramControls.svelte +0 -449
  573. package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +0 -30
  574. package/dist/phase-diagram/PhaseDiagramEditorPane.svelte +0 -126
  575. package/dist/phase-diagram/PhaseDiagramEditorPane.svelte.d.ts +0 -15
  576. package/dist/phase-diagram/PhaseDiagramExportPane.svelte +0 -192
  577. package/dist/phase-diagram/PhaseDiagramExportPane.svelte.d.ts +0 -19
  578. package/dist/phase-diagram/PhaseDiagramTooltip.svelte +0 -392
  579. package/dist/phase-diagram/PhaseDiagramTooltip.svelte.d.ts +0 -16
  580. package/dist/phase-diagram/TdbInfoPanel.svelte +0 -203
  581. package/dist/phase-diagram/TdbInfoPanel.svelte.d.ts +0 -12
  582. package/dist/phase-diagram/build-diagram.d.ts +0 -11
  583. package/dist/phase-diagram/build-diagram.js +0 -167
  584. package/dist/phase-diagram/colors.d.ts +0 -35
  585. package/dist/phase-diagram/colors.js +0 -51
  586. package/dist/phase-diagram/diagram-input.d.ts +0 -33
  587. package/dist/phase-diagram/diagram-input.js +0 -3
  588. package/dist/phase-diagram/index.d.ts +0 -13
  589. package/dist/phase-diagram/index.js +0 -13
  590. package/dist/phase-diagram/parse.d.ts +0 -55
  591. package/dist/phase-diagram/parse.js +0 -276
  592. package/dist/phase-diagram/svg-to-diagram.d.ts +0 -2
  593. package/dist/phase-diagram/svg-to-diagram.js +0 -867
  594. package/dist/phase-diagram/types.d.ts +0 -99
  595. package/dist/phase-diagram/types.js +0 -1
  596. package/dist/phase-diagram/utils.d.ts +0 -118
  597. package/dist/phase-diagram/utils.js +0 -606
  598. package/dist/plot/AxisLabel.svelte +0 -51
  599. package/dist/plot/AxisLabel.svelte.d.ts +0 -16
  600. package/dist/plot/BarPlot.svelte +0 -2265
  601. package/dist/plot/BarPlot.svelte.d.ts +0 -83
  602. package/dist/plot/BarPlotControls.svelte +0 -66
  603. package/dist/plot/BarPlotControls.svelte.d.ts +0 -18
  604. package/dist/plot/ColorBar.svelte +0 -719
  605. package/dist/plot/ColorBar.svelte.d.ts +0 -31
  606. package/dist/plot/ColorScaleSelect.svelte +0 -54
  607. package/dist/plot/ColorScaleSelect.svelte.d.ts +0 -15
  608. package/dist/plot/ElementScatter.svelte +0 -63
  609. package/dist/plot/ElementScatter.svelte.d.ts +0 -14
  610. package/dist/plot/FillArea.svelte +0 -225
  611. package/dist/plot/FillArea.svelte.d.ts +0 -21
  612. package/dist/plot/Histogram.svelte +0 -1672
  613. package/dist/plot/Histogram.svelte.d.ts +0 -50
  614. package/dist/plot/HistogramControls.svelte +0 -212
  615. package/dist/plot/HistogramControls.svelte.d.ts +0 -22
  616. package/dist/plot/InteractiveAxisLabel.svelte +0 -94
  617. package/dist/plot/InteractiveAxisLabel.svelte.d.ts +0 -14
  618. package/dist/plot/Line.svelte +0 -84
  619. package/dist/plot/Line.svelte.d.ts +0 -15
  620. package/dist/plot/PlotControls.svelte +0 -537
  621. package/dist/plot/PlotControls.svelte.d.ts +0 -4
  622. package/dist/plot/PlotLegend.svelte +0 -569
  623. package/dist/plot/PlotLegend.svelte.d.ts +0 -29
  624. package/dist/plot/PlotTooltip.svelte +0 -67
  625. package/dist/plot/PlotTooltip.svelte.d.ts +0 -17
  626. package/dist/plot/PortalSelect.svelte +0 -253
  627. package/dist/plot/PortalSelect.svelte.d.ts +0 -16
  628. package/dist/plot/ReferenceLine.svelte.d.ts +0 -20
  629. package/dist/plot/ReferenceLine3D.svelte +0 -154
  630. package/dist/plot/ReferenceLine3D.svelte.d.ts +0 -14
  631. package/dist/plot/ReferencePlane.svelte +0 -178
  632. package/dist/plot/ReferencePlane.svelte.d.ts +0 -14
  633. package/dist/plot/ScatterPlot.svelte +0 -2845
  634. package/dist/plot/ScatterPlot.svelte.d.ts +0 -93
  635. package/dist/plot/ScatterPlot3D.svelte +0 -502
  636. package/dist/plot/ScatterPlot3D.svelte.d.ts +0 -94
  637. package/dist/plot/ScatterPlot3DControls.svelte +0 -437
  638. package/dist/plot/ScatterPlot3DControls.svelte.d.ts +0 -20
  639. package/dist/plot/ScatterPlot3DScene.svelte +0 -912
  640. package/dist/plot/ScatterPlot3DScene.svelte.d.ts +0 -74
  641. package/dist/plot/ScatterPlotControls.svelte +0 -307
  642. package/dist/plot/ScatterPlotControls.svelte.d.ts +0 -17
  643. package/dist/plot/ScatterPoint.svelte +0 -191
  644. package/dist/plot/ScatterPoint.svelte.d.ts +0 -21
  645. package/dist/plot/SpacegroupBarPlot.svelte +0 -293
  646. package/dist/plot/SpacegroupBarPlot.svelte.d.ts +0 -9
  647. package/dist/plot/Surface3D.svelte +0 -200
  648. package/dist/plot/Surface3D.svelte.d.ts +0 -13
  649. package/dist/plot/ZeroLines.svelte +0 -96
  650. package/dist/plot/ZeroLines.svelte.d.ts +0 -32
  651. package/dist/plot/ZoomRect.svelte +0 -23
  652. package/dist/plot/ZoomRect.svelte.d.ts +0 -8
  653. package/dist/plot/axis-utils.d.ts +0 -19
  654. package/dist/plot/axis-utils.js +0 -80
  655. package/dist/plot/data-cleaning.d.ts +0 -37
  656. package/dist/plot/data-cleaning.js +0 -855
  657. package/dist/plot/data-transform.d.ts +0 -16
  658. package/dist/plot/data-transform.js +0 -45
  659. package/dist/plot/defaults.d.ts +0 -19
  660. package/dist/plot/defaults.js +0 -9
  661. package/dist/plot/fill-utils.d.ts +0 -51
  662. package/dist/plot/fill-utils.js +0 -337
  663. package/dist/plot/hover-lock.svelte.d.ts +0 -14
  664. package/dist/plot/hover-lock.svelte.js +0 -46
  665. package/dist/plot/index.d.ts +0 -37
  666. package/dist/plot/index.js +0 -37
  667. package/dist/plot/interactions.d.ts +0 -12
  668. package/dist/plot/interactions.js +0 -100
  669. package/dist/plot/layout.d.ts +0 -60
  670. package/dist/plot/layout.js +0 -230
  671. package/dist/plot/reference-line.d.ts +0 -60
  672. package/dist/plot/reference-line.js +0 -316
  673. package/dist/plot/scales.d.ts +0 -48
  674. package/dist/plot/scales.js +0 -484
  675. package/dist/plot/svg.d.ts +0 -1
  676. package/dist/plot/svg.js +0 -11
  677. package/dist/plot/types.d.ts +0 -859
  678. package/dist/plot/types.js +0 -103
  679. package/dist/plot/utils/label-placement.d.ts +0 -47
  680. package/dist/plot/utils/label-placement.js +0 -256
  681. package/dist/plot/utils/series-visibility.d.ts +0 -9
  682. package/dist/plot/utils/series-visibility.js +0 -67
  683. package/dist/plot/utils.d.ts +0 -1
  684. package/dist/plot/utils.js +0 -14
  685. package/dist/rdf/RdfPlot.svelte +0 -247
  686. package/dist/rdf/RdfPlot.svelte.d.ts +0 -27
  687. package/dist/rdf/calc-rdf.d.ts +0 -4
  688. package/dist/rdf/calc-rdf.js +0 -111
  689. package/dist/rdf/index.d.ts +0 -23
  690. package/dist/rdf/index.js +0 -2
  691. package/dist/sanitize.d.ts +0 -4
  692. package/dist/sanitize.js +0 -114
  693. package/dist/settings.d.ts +0 -255
  694. package/dist/settings.js +0 -1132
  695. package/dist/spectral/Bands.svelte +0 -1040
  696. package/dist/spectral/Bands.svelte.d.ts +0 -40
  697. package/dist/spectral/BandsAndDos.svelte +0 -128
  698. package/dist/spectral/BandsAndDos.svelte.d.ts +0 -18
  699. package/dist/spectral/BrillouinBandsDos.svelte +0 -248
  700. package/dist/spectral/BrillouinBandsDos.svelte.d.ts +0 -20
  701. package/dist/spectral/Dos.svelte +0 -697
  702. package/dist/spectral/Dos.svelte.d.ts +0 -29
  703. package/dist/spectral/helpers.d.ts +0 -117
  704. package/dist/spectral/helpers.js +0 -1023
  705. package/dist/spectral/index.d.ts +0 -6
  706. package/dist/spectral/index.js +0 -7
  707. package/dist/spectral/types.d.ts +0 -84
  708. package/dist/spectral/types.js +0 -2
  709. package/dist/state.svelte.d.ts +0 -25
  710. package/dist/state.svelte.js +0 -45
  711. package/dist/structure/Arrow.svelte +0 -72
  712. package/dist/structure/Arrow.svelte.d.ts +0 -15
  713. package/dist/structure/AtomLegend.svelte +0 -798
  714. package/dist/structure/AtomLegend.svelte.d.ts +0 -34
  715. package/dist/structure/Bond.svelte +0 -140
  716. package/dist/structure/Bond.svelte.d.ts +0 -9
  717. package/dist/structure/CanvasTooltip.svelte +0 -33
  718. package/dist/structure/CanvasTooltip.svelte.d.ts +0 -12
  719. package/dist/structure/CellSelect.svelte +0 -351
  720. package/dist/structure/CellSelect.svelte.d.ts +0 -13
  721. package/dist/structure/Cylinder.svelte +0 -45
  722. package/dist/structure/Cylinder.svelte.d.ts +0 -10
  723. package/dist/structure/Lattice.svelte +0 -196
  724. package/dist/structure/Lattice.svelte.d.ts +0 -17
  725. package/dist/structure/Structure.svelte +0 -1999
  726. package/dist/structure/Structure.svelte.d.ts +0 -87
  727. package/dist/structure/StructureControls.svelte +0 -1298
  728. package/dist/structure/StructureControls.svelte.d.ts +0 -31
  729. package/dist/structure/StructureExportPane.svelte +0 -251
  730. package/dist/structure/StructureExportPane.svelte.d.ts +0 -17
  731. package/dist/structure/StructureInfoPane.svelte +0 -735
  732. package/dist/structure/StructureInfoPane.svelte.d.ts +0 -19
  733. package/dist/structure/StructureScene.svelte +0 -1905
  734. package/dist/structure/StructureScene.svelte.d.ts +0 -108
  735. package/dist/structure/atom-properties.d.ts +0 -37
  736. package/dist/structure/atom-properties.js +0 -200
  737. package/dist/structure/bond-order-perception.d.ts +0 -13
  738. package/dist/structure/bond-order-perception.js +0 -367
  739. package/dist/structure/bonding.d.ts +0 -42
  740. package/dist/structure/bonding.js +0 -525
  741. package/dist/structure/export.d.ts +0 -20
  742. package/dist/structure/export.js +0 -727
  743. package/dist/structure/index.d.ts +0 -125
  744. package/dist/structure/index.js +0 -171
  745. package/dist/structure/label-placement.d.ts +0 -14
  746. package/dist/structure/label-placement.js +0 -72
  747. package/dist/structure/measure.d.ts +0 -6
  748. package/dist/structure/measure.js +0 -29
  749. package/dist/structure/parse.d.ts +0 -66
  750. package/dist/structure/parse.js +0 -1363
  751. package/dist/structure/partial-occupancy.d.ts +0 -25
  752. package/dist/structure/partial-occupancy.js +0 -99
  753. package/dist/structure/pbc.d.ts +0 -9
  754. package/dist/structure/pbc.js +0 -123
  755. package/dist/structure/supercell.d.ts +0 -8
  756. package/dist/structure/supercell.js +0 -170
  757. package/dist/structure/validation.d.ts +0 -2
  758. package/dist/structure/validation.js +0 -10
  759. package/dist/symmetry/SymmetryStats.svelte +0 -226
  760. package/dist/symmetry/SymmetryStats.svelte.d.ts +0 -21
  761. package/dist/symmetry/WyckoffTable.svelte +0 -113
  762. package/dist/symmetry/WyckoffTable.svelte.d.ts +0 -11
  763. package/dist/symmetry/cell-transform.d.ts +0 -12
  764. package/dist/symmetry/cell-transform.js +0 -91
  765. package/dist/symmetry/index.d.ts +0 -43
  766. package/dist/symmetry/index.js +0 -229
  767. package/dist/symmetry/spacegroups.d.ts +0 -9
  768. package/dist/symmetry/spacegroups.js +0 -394
  769. package/dist/table/HeatmapTable.svelte +0 -1854
  770. package/dist/table/HeatmapTable.svelte.d.ts +0 -49
  771. package/dist/table/ToggleMenu.svelte +0 -376
  772. package/dist/table/ToggleMenu.svelte.d.ts +0 -11
  773. package/dist/table/index.d.ts +0 -74
  774. package/dist/table/index.js +0 -38
  775. package/dist/theme/ThemeControl.svelte +0 -53
  776. package/dist/theme/ThemeControl.svelte.d.ts +0 -9
  777. package/dist/theme/index.d.ts +0 -29
  778. package/dist/theme/index.js +0 -79
  779. package/dist/theme/themes.mjs +0 -285
  780. package/dist/time.d.ts +0 -4
  781. package/dist/time.js +0 -70
  782. package/dist/tooltip/TooltipContent.svelte +0 -58
  783. package/dist/tooltip/TooltipContent.svelte.d.ts +0 -31
  784. package/dist/tooltip/index.d.ts +0 -2
  785. package/dist/tooltip/index.js +0 -2
  786. package/dist/tooltip/types.d.ts +0 -8
  787. package/dist/tooltip/types.js +0 -1
  788. package/dist/trajectory/Trajectory.svelte +0 -1517
  789. package/dist/trajectory/Trajectory.svelte.d.ts +0 -77
  790. package/dist/trajectory/TrajectoryError.svelte +0 -128
  791. package/dist/trajectory/TrajectoryError.svelte.d.ts +0 -13
  792. package/dist/trajectory/TrajectoryExportPane.svelte +0 -357
  793. package/dist/trajectory/TrajectoryExportPane.svelte.d.ts +0 -17
  794. package/dist/trajectory/TrajectoryInfoPane.svelte +0 -313
  795. package/dist/trajectory/TrajectoryInfoPane.svelte.d.ts +0 -17
  796. package/dist/trajectory/constants.d.ts +0 -6
  797. package/dist/trajectory/constants.js +0 -7
  798. package/dist/trajectory/extract.d.ts +0 -5
  799. package/dist/trajectory/extract.js +0 -162
  800. package/dist/trajectory/format-detect.d.ts +0 -9
  801. package/dist/trajectory/format-detect.js +0 -76
  802. package/dist/trajectory/frame-reader.d.ts +0 -17
  803. package/dist/trajectory/frame-reader.js +0 -332
  804. package/dist/trajectory/helpers.d.ts +0 -14
  805. package/dist/trajectory/helpers.js +0 -172
  806. package/dist/trajectory/index.d.ts +0 -63
  807. package/dist/trajectory/index.js +0 -126
  808. package/dist/trajectory/parse/ase.d.ts +0 -2
  809. package/dist/trajectory/parse/ase.js +0 -73
  810. package/dist/trajectory/parse/hdf5.d.ts +0 -2
  811. package/dist/trajectory/parse/hdf5.js +0 -127
  812. package/dist/trajectory/parse/index.d.ts +0 -12
  813. package/dist/trajectory/parse/index.js +0 -299
  814. package/dist/trajectory/parse/lammps.d.ts +0 -5
  815. package/dist/trajectory/parse/lammps.js +0 -179
  816. package/dist/trajectory/parse/vasp.d.ts +0 -2
  817. package/dist/trajectory/parse/vasp.js +0 -68
  818. package/dist/trajectory/parse/xyz.d.ts +0 -2
  819. package/dist/trajectory/parse/xyz.js +0 -110
  820. package/dist/trajectory/plotting.d.ts +0 -28
  821. package/dist/trajectory/plotting.js +0 -423
  822. package/dist/trajectory/types.d.ts +0 -11
  823. package/dist/trajectory/types.js +0 -1
  824. package/dist/utils.d.ts +0 -5
  825. package/dist/utils.js +0 -36
  826. package/dist/xrd/XrdPlot.svelte +0 -615
  827. package/dist/xrd/XrdPlot.svelte.d.ts +0 -28
  828. package/dist/xrd/broadening.d.ts +0 -20
  829. package/dist/xrd/broadening.js +0 -97
  830. package/dist/xrd/calc-xrd.d.ts +0 -37
  831. package/dist/xrd/calc-xrd.js +0 -337
  832. package/dist/xrd/index.d.ts +0 -37
  833. package/dist/xrd/index.js +0 -4
  834. package/dist/xrd/parse.d.ts +0 -13
  835. package/dist/xrd/parse.js +0 -749
  836. /package/dist/{EmptyState.svelte → src/lib/EmptyState.svelte} +0 -0
  837. /package/dist/{Icon.svelte → src/lib/Icon.svelte} +0 -0
  838. /package/dist/{app.css → src/lib/app.css} +0 -0
  839. /package/dist/{chempot-diagram → src/lib/chempot-diagram}/ChemPotScene3D.svelte +0 -0
  840. /package/dist/{colors → src/lib/colors}/alloy-colors.json +0 -0
  841. /package/dist/{colors → src/lib/colors}/dark-mode-colors.json +0 -0
  842. /package/dist/{colors → src/lib/colors}/jmol-colors.json +0 -0
  843. /package/dist/{colors → src/lib/colors}/muted-colors.json +0 -0
  844. /package/dist/{colors → src/lib/colors}/pastel-colors.json +0 -0
  845. /package/dist/{colors → src/lib/colors}/vesta-colors.json +0 -0
  846. /package/dist/{element → src/lib/element}/Nucleus.svelte +0 -0
  847. /package/dist/{element → src/lib/element}/data.json +0 -0
  848. /package/dist/{element → src/lib/element}/data.json.gz +0 -0
  849. /package/dist/{element → src/lib/element}/data.schema.json +0 -0
  850. /package/dist/{element-image-urls.json → src/lib/element-image-urls.json} +0 -0
  851. /package/dist/{feedback → src/lib/feedback}/Spinner.svelte +0 -0
  852. /package/dist/{feedback → src/lib/feedback}/StatusMessage.svelte +0 -0
  853. /package/dist/{periodic-table → src/lib/periodic-table}/TableInset.svelte +0 -0
  854. /package/dist/{plot → src/lib/plot}/ReferenceLine.svelte +0 -0
  855. /package/dist/{xrd → src/lib/xrd}/atomic_scattering_params.json +0 -0
@@ -0,0 +1,2041 @@
1
+ import {
2
+ STRUCTURE_EXTENSIONS,
3
+ TRAJ_EXTENSIONS,
4
+ TRAJ_KEYWORDS,
5
+ VASP_VOLUMETRIC_REGEX,
6
+ } from '$lib/constants'
7
+ import type { ThemeName } from '$lib/theme/index'
8
+ import { is_trajectory_file } from '$lib/trajectory/parse'
9
+ import { Buffer } from 'node:buffer'
10
+ import { beforeEach, describe, expect, test, vi } from 'vitest'
11
+ import type { ExtensionContext, Tab, TextEditor, Uri } from 'vscode'
12
+ import pkg_json from '../package.json' with { type: 'json' }
13
+ import {
14
+ activate,
15
+ create_html,
16
+ get_defaults,
17
+ get_file,
18
+ get_theme,
19
+ handle_msg,
20
+ MessageData,
21
+ read_file,
22
+ render,
23
+ should_auto_render,
24
+ } from '../src/extension'
25
+ import { VOLUMETRIC_VASP_RE } from '../src/types'
26
+ import type { FileData } from '../src/webview/main'
27
+
28
+ // Mock modules
29
+ const mock_fs = vi.hoisted(() => ({
30
+ readFileSync: vi.fn().mockReturnValue(`mock content`),
31
+ existsSync: vi.fn().mockReturnValue(true),
32
+ readdirSync: vi.fn().mockReturnValue([]),
33
+ }))
34
+
35
+ vi.mock(`node:fs`, () => mock_fs)
36
+ vi.mock(`node:path`, async (importOriginal) => {
37
+ const actual = (await importOriginal()) as Record<string, unknown>
38
+ return {
39
+ ...actual,
40
+ basename: vi.fn((p: string) => p.split(`/`).pop() || ``),
41
+ dirname: vi.fn((p: string) => p.split(`/`).slice(0, -1).join(`/`) || `/`),
42
+ join: vi.fn((...paths: string[]) => paths.join(`/`)),
43
+ isAbsolute: vi.fn((p: string) => p.startsWith(`/`)),
44
+ }
45
+ })
46
+
47
+ const msg_args = {
48
+ // generic placeholder arguments for all messages
49
+ filename: `filename`,
50
+ request_id: `request_id`,
51
+ file_path: `file_path`,
52
+ frame_index: 0,
53
+ } as const
54
+
55
+ const mock_vscode = vi.hoisted(() => ({
56
+ window: {
57
+ showInformationMessage: vi.fn(),
58
+ showErrorMessage: vi.fn(),
59
+ showTextDocument: vi.fn(),
60
+ showSaveDialog: vi.fn(),
61
+ createWebviewPanel: vi.fn(),
62
+ activeTextEditor: null as TextEditor | null,
63
+ tabGroups: { activeTabGroup: { activeTab: null as Tab | null } },
64
+ registerCustomEditorProvider: vi.fn(),
65
+ activeColorTheme: { kind: 1 }, // Light theme by default
66
+ onDidChangeActiveColorTheme: vi.fn(() => ({ dispose: vi.fn() })),
67
+ onDidChangeActiveTextEditor: vi.fn(() => ({ dispose: vi.fn() })),
68
+ },
69
+ workspace: {
70
+ getConfiguration: vi.fn(() => ({
71
+ get: vi.fn((_key: string, default_val: string) => default_val),
72
+ })),
73
+ onDidChangeConfiguration: vi.fn(() => ({ dispose: vi.fn() })),
74
+ openTextDocument: vi.fn(),
75
+ onDidOpenTextDocument: vi.fn(() => ({ dispose: vi.fn() })),
76
+ onDidCreateFiles: vi.fn(() => ({ dispose: vi.fn() })),
77
+ onDidRenameFiles: vi.fn(() => ({ dispose: vi.fn() })),
78
+ createFileSystemWatcher: vi.fn(() => ({
79
+ onDidChange: vi.fn(),
80
+ onDidDelete: vi.fn(),
81
+ dispose: vi.fn(),
82
+ })),
83
+ fs: { stat: vi.fn(), readFile: vi.fn(), writeFile: vi.fn() },
84
+ },
85
+ commands: { registerCommand: vi.fn(), executeCommand: vi.fn() },
86
+ Uri: {
87
+ file: vi.fn((p: string) => ({ fsPath: p })),
88
+ joinPath: vi.fn((_base: unknown, ...paths: string[]) => ({
89
+ fsPath: paths.join(`/`),
90
+ })),
91
+ parse: vi.fn((url: string) => ({ toString: () => url })),
92
+ },
93
+ ViewColumn: { Active: 1, Beside: 2 },
94
+ ColorThemeKind: { Light: 1, Dark: 2, HighContrast: 3, HighContrastLight: 4 },
95
+ UIKind: { Desktop: 1, Web: 2 },
96
+ RelativePattern: class {
97
+ constructor(
98
+ public base: unknown,
99
+ public pattern: string,
100
+ ) {}
101
+ },
102
+ version: `1.99.0`,
103
+ env: {
104
+ appName: `VSCode`,
105
+ remoteName: undefined,
106
+ uiKind: 1, // Desktop
107
+ clipboard: {
108
+ writeText: vi.fn(() => Promise.resolve()),
109
+ readText: vi.fn(() => Promise.resolve(``)),
110
+ },
111
+ openExternal: vi.fn(() => Promise.resolve(true)),
112
+ },
113
+ }))
114
+
115
+ vi.mock(`vscode`, () => mock_vscode)
116
+
117
+ describe(`MatterViz Extension`, () => {
118
+ let mock_file_system_watcher: {
119
+ onDidChange: ReturnType<typeof vi.fn>
120
+ onDidDelete: ReturnType<typeof vi.fn>
121
+ dispose: ReturnType<typeof vi.fn>
122
+ }
123
+ const supported_volumetric_filenames: [string, string][] = [
124
+ [`CHGCAR`, `VASP volumetric`],
125
+ [`AECCAR0`, `VASP volumetric`],
126
+ [`AECCAR1`, `VASP volumetric`],
127
+ [`AECCAR2`, `VASP volumetric`],
128
+ [`ELFCAR`, `VASP volumetric`],
129
+ [`LOCPOT`, `VASP volumetric`],
130
+ [`PARCHG`, `VASP volumetric`],
131
+ [`PARCHG.gz`, `VASP volumetric`],
132
+ [`PARCHG.BAND_1`, `VASP volumetric`],
133
+ [`run_PARCHG_001`, `VASP volumetric`],
134
+ [`density.cube`, `Gaussian cube`],
135
+ [`density.cube.gz`, `Gaussian cube`],
136
+ ]
137
+ const volumetric_auto_render_filenames: [string, boolean][] =
138
+ supported_volumetric_filenames.map(([filename]) => [filename, true] as [string, boolean])
139
+
140
+ test(`extensionKind should be configured as ["workspace"] to work locally and in remote SSH sessions`, () => {
141
+ // https://github.com/janosh/matterviz/issues/129#issuecomment-3193473225
142
+ expect(pkg_json.extensionKind).toEqual([`workspace`])
143
+ })
144
+
145
+ test(`extension volumetric regex stays in sync with app detection`, () => {
146
+ expect(VOLUMETRIC_VASP_RE.source).toBe(VASP_VOLUMETRIC_REGEX.source)
147
+ expect(VOLUMETRIC_VASP_RE.flags).toBe(VASP_VOLUMETRIC_REGEX.flags)
148
+ })
149
+
150
+ describe(`Custom Editor File Patterns`, () => {
151
+ const custom_editors = pkg_json.contributes.customEditors
152
+ const matterviz_editor = custom_editors.find(
153
+ (editor) => editor.viewType === `matterviz.viewer`,
154
+ )
155
+ const patterns = matterviz_editor?.selector.map((sel) => sel.filenamePattern) ?? []
156
+
157
+ // Tests if a filename matches any pattern (simplified glob matching).
158
+ const matches_any_pattern = (filename: string): boolean => {
159
+ for (const pattern of patterns) {
160
+ // Convert glob pattern to regex
161
+ const regex_str = pattern
162
+ .replace(/\./g, `\\.`) // escape dots
163
+ .replace(/\*/g, `.*`) // * → .*
164
+ .replace(/\{([^}]+)\}/g, (_, group) => `(${group.split(`,`).join(`|`)})`) // {a,b} → (a|b)
165
+ const regex = new RegExp(`^${regex_str}$`, `i`)
166
+ if (regex.test(filename)) return true
167
+ }
168
+ return false
169
+ }
170
+
171
+ test(`should have matterviz.viewer custom editor defined`, () => {
172
+ expect(matterviz_editor).toBeDefined()
173
+ expect(matterviz_editor?.displayName).toBe(`MatterViz Viewer`)
174
+ })
175
+
176
+ // Tests auto-synced with library constants to prevent regressions
177
+ test.each<[string, string]>([
178
+ // All trajectory extensions from library
179
+ ...TRAJ_EXTENSIONS.map(
180
+ (ext: string) => [`test${ext}`, `TRAJ_EXT ${ext}`] as [string, string],
181
+ ),
182
+ // All structure extensions from library (uncompressed, .gz, .bz2)
183
+ ...STRUCTURE_EXTENSIONS.flatMap(
184
+ (ext: string) =>
185
+ [
186
+ [`test${ext}`, `STRUCT_EXT ${ext}`],
187
+ [`test${ext}.gz`, `${ext}.gz`],
188
+ ] as [string, string][],
189
+ ),
190
+ // All trajectory keywords in filenames
191
+ ...TRAJ_KEYWORDS.map(
192
+ (kw: string) => [`${kw}_output.dat`, `TRAJ_KW ${kw}`] as [string, string],
193
+ ),
194
+ // VASP special filenames + additional VS Code-only formats
195
+ [`POSCAR`, `VASP`],
196
+ [`CONTCAR`, `VASP`],
197
+ [`XDATCAR`, `VASP`],
198
+ [`OUTCAR`, `VASP`],
199
+ [`simulation.h5`, `HDF5`],
200
+ [`data.hdf5`, `HDF5`],
201
+ [`dynamics.dcd`, `DCD`],
202
+ [`run.trr`, `TRR`],
203
+ [`molecule.xyz`, `XYZ`],
204
+ [`atoms.extxyz`, `extXYZ`],
205
+ ...supported_volumetric_filenames,
206
+ ])(`pattern matches "%s" (%s)`, (filename) => {
207
+ expect(matches_any_pattern(filename)).toBe(true)
208
+ })
209
+
210
+ test.each([
211
+ [`myCHGCARfile`],
212
+ [`prefixPARCHGsuffix`],
213
+ [`notes_ELFCARbackup`],
214
+ [`report-AECCARnotes`],
215
+ [`density.cube.bz2`],
216
+ [`structure.cif.bz2`],
217
+ ])(`pattern does not match unsupported near miss "%s"`, (filename) => {
218
+ expect(matches_any_pattern(filename)).toBe(false)
219
+ })
220
+ })
221
+
222
+ beforeEach(async () => {
223
+ vi.clearAllMocks()
224
+
225
+ // Import extension module and clear all watchers
226
+ const ext = await import(`../src/extension`)
227
+ ext.active_watchers.clear()
228
+ ext.active_frame_loaders.clear()
229
+ ext.auto_render_timers.clear()
230
+ ext.active_auto_render_panels.clear()
231
+
232
+ mock_fs.readFileSync.mockReturnValue(`mock content`)
233
+ mock_fs.existsSync.mockReturnValue(true)
234
+ mock_fs.readdirSync.mockReturnValue([])
235
+ mock_vscode.window.activeTextEditor = null
236
+
237
+ // Reset theme to default Light theme to avoid inter-test coupling
238
+ mock_vscode.window.activeColorTheme = { kind: 1 } // Light theme by default
239
+
240
+ // Set up file system watcher mock
241
+ mock_file_system_watcher = {
242
+ onDidChange: vi.fn(),
243
+ onDidDelete: vi.fn(),
244
+ dispose: vi.fn(),
245
+ }
246
+ mock_vscode.workspace.createFileSystemWatcher.mockReturnValue(mock_file_system_watcher)
247
+
248
+ // Set up default mock for vscode.workspace.fs.stat to return file stats
249
+ mock_vscode.workspace.fs.stat.mockResolvedValue({ size: 1000, type: 1 })
250
+ mock_vscode.workspace.fs.readFile.mockResolvedValue(
251
+ new Uint8Array(Buffer.from(`mock content`)),
252
+ )
253
+ })
254
+
255
+ // Test data consolidation
256
+ const mock_webview = {
257
+ cspSource: `vscode-webview:`,
258
+ asWebviewUri: vi.fn(
259
+ (uri: { fsPath: string }) =>
260
+ `vscode-webview://unit-test${encodeURIComponent(uri.fsPath)}`,
261
+ ),
262
+ onDidReceiveMessage: vi.fn(),
263
+ postMessage: vi.fn(),
264
+ html: ``,
265
+ }
266
+ const mock_context = {
267
+ extensionUri: { fsPath: `/test` },
268
+ subscriptions: [],
269
+ } as unknown as ExtensionContext
270
+
271
+ test.each([
272
+ [`test.gz`, true],
273
+ [`test.h5`, true],
274
+ [`test.traj`, true], // ASE binary files should be treated as compressed
275
+ [`test.hdf5`, true],
276
+ [`md_npt_300K.traj`, true], // Specific ASE ULM binary file
277
+ [`ase-LiMnO2-chgnet-relax.traj`, true], // Another ASE ULM binary file
278
+ [`test.cif`, false],
279
+ [`test.xyz`, false], // .xyz files are text format, not compressed binary
280
+ [`test.json`, false],
281
+ [``, false],
282
+ ])(`file reading: "%s" → compressed:%s`, async (filename, expected_compressed) => {
283
+ const result = await read_file(`/test/${filename}`)
284
+ expect(result.filename).toBe(filename)
285
+ expect(result.is_base64).toBe(expected_compressed)
286
+ // Assert payload differences instead of redundant API calls
287
+ if (expected_compressed) {
288
+ expect(result.content).toBe(Buffer.from(`mock content`).toString(`base64`))
289
+ } else {
290
+ expect(result.content).toBe(`mock content`)
291
+ }
292
+ })
293
+
294
+ test(`file reading: large file should return sentinel`, async () => {
295
+ const large_file_size = 2 * 1024 * 1024 * 1024 // 2GB, above MAX_VSCODE_FILE_SIZE (1GB)
296
+ const filename = `large-structure.cif`
297
+ const file_path = `/test/${filename}`
298
+
299
+ // Mock fs.stat to return a size above the threshold
300
+ mock_vscode.workspace.fs.stat.mockResolvedValue({
301
+ size: large_file_size,
302
+ type: 1,
303
+ })
304
+
305
+ const result = await read_file(file_path)
306
+
307
+ expect(result.filename).toBe(filename)
308
+ expect(result.content).toBe(`LARGE_FILE:${file_path}:${large_file_size}`)
309
+ expect(result.is_base64).toBe(false)
310
+ // readFile should not be called for large files
311
+ expect(mock_vscode.workspace.fs.readFile).not.toHaveBeenCalled()
312
+ })
313
+
314
+ test(`file reading: large binary file should return sentinel with base64 flag`, async () => {
315
+ const large_file_size = 2 * 1024 * 1024 * 1024 // 2GB, above MAX_VSCODE_FILE_SIZE (1GB)
316
+ const filename = `large-trajectory.traj`
317
+ const file_path = `/test/${filename}`
318
+
319
+ // Mock fs.stat to return a size above the threshold
320
+ mock_vscode.workspace.fs.stat.mockResolvedValue({
321
+ size: large_file_size,
322
+ type: 1,
323
+ })
324
+
325
+ const result = await read_file(file_path)
326
+
327
+ expect(result.filename).toBe(filename)
328
+ expect(result.content).toBe(`LARGE_FILE:${file_path}:${large_file_size}`)
329
+ expect(result.is_base64).toBe(true) // Binary files should have base64 flag
330
+ // readFile should not be called for large files
331
+ expect(mock_vscode.workspace.fs.readFile).not.toHaveBeenCalled()
332
+ })
333
+
334
+ test.each([
335
+ [`md_npt_300K.traj`, true, true], // ASE binary trajectory
336
+ [`ase-LiMnO2-chgnet-relax.traj`, true, true], // ASE binary trajectory
337
+ [`simulation_nvt_250K.traj`, true, true], // ASE binary trajectory
338
+ [`water_cluster_md.traj`, true, true], // ASE binary trajectory
339
+ [`optimization_relax.traj`, true, true], // ASE binary trajectory
340
+ [`regular_text.traj`, true, true], // .traj files are always binary
341
+ // filename-only based .xyz/.extxyz detection always assumes structure, requires file content to look for frames and recognize as trajectory
342
+ [`test.xyz`, false, false],
343
+ [`test.extxyz`, false, false],
344
+ [`test.cif`, false, false], // Not a trajectory file
345
+ ])(
346
+ `ASE trajectory file handling: "%s" → trajectory:%s, binary:%s`,
347
+ async (filename, is_trajectory, is_binary) => {
348
+ expect(is_trajectory_file(filename)).toBe(is_trajectory)
349
+ if (is_trajectory) {
350
+ const result = await read_file(`/test/${filename}`)
351
+ expect(result.is_base64).toBe(is_binary)
352
+ }
353
+ },
354
+ )
355
+
356
+ // Integration test for ASE trajectory file processing (simulates the exact failing scenario)
357
+ test(`ASE trajectory file end-to-end processing`, async () => {
358
+ const ase_filename = `ase-LiMnO2-chgnet-relax.traj`
359
+
360
+ // Step 1: Extension should detect this as a trajectory file
361
+ expect(is_trajectory_file(ase_filename)).toBe(true)
362
+
363
+ // Step 2: Extension should read this as binary (compressed)
364
+ const file_result = await read_file(`/test/${ase_filename}`)
365
+ expect(file_result.filename).toBe(ase_filename)
366
+ expect(file_result.is_base64).toBe(true)
367
+ expect(file_result.content).toBe(Buffer.from(`mock content`).toString(`base64`)) // base64 encoded binary data
368
+
369
+ // Step 3: Verify webview data structure matches expected format
370
+ const webview_data = {
371
+ type: `trajectory` as const,
372
+ data: file_result,
373
+ }
374
+
375
+ // Step 4: HTML generation should work with this data
376
+ const webview_data_with_theme = { ...webview_data, theme: `light` as const }
377
+ const html = create_html(mock_webview, mock_context, webview_data_with_theme)
378
+
379
+ expect(html).toContain(`<!DOCTYPE html>`)
380
+ expect(html).toContain(JSON.stringify(webview_data_with_theme))
381
+
382
+ // Step 5: Verify the exact data structure that would be sent to webview
383
+ const parsed_data = JSON.parse(/matterviz_data=(\{[\s\S]*?\});/.exec(html)?.[1] ?? `{}`)
384
+ expect(parsed_data.type).toBe(`trajectory`)
385
+ expect(parsed_data.data.filename).toBe(ase_filename)
386
+ expect(parsed_data.data.is_base64).toBe(true)
387
+ expect(parsed_data.data.content).toBe(Buffer.from(`mock content`).toString(`base64`))
388
+ expect(parsed_data.theme).toBe(`light`)
389
+ })
390
+
391
+ test.each([
392
+ [{ fsPath: `/test/file.cif` } as unknown as Uri, `file.cif`],
393
+ [{ fsPath: `/test/structure.xyz` } as unknown as Uri, `structure.xyz`],
394
+ ])(`get_file with URI`, async (uri, expected_filename) => {
395
+ const result = await get_file(uri)
396
+ expect(result.filename).toBe(expected_filename)
397
+ })
398
+
399
+ test(`get_file with active editor`, async () => {
400
+ mock_vscode.window.activeTextEditor = {
401
+ document: { fileName: `/test/active.cif`, getText: () => `active content` },
402
+ } as TextEditor
403
+ const result = await get_file()
404
+ expect(result.filename).toBe(`active.cif`)
405
+ expect(result.content).toBe(`active content`)
406
+ expect(result.is_base64).toBe(false)
407
+ })
408
+
409
+ test(`get_file with active tab`, async () => {
410
+ mock_vscode.window.tabGroups.activeTabGroup.activeTab = {
411
+ input: { uri: { fsPath: `/test/tab.cif` } },
412
+ } as unknown as Tab
413
+ const result = await get_file()
414
+ expect(result.filename).toBe(`tab.cif`)
415
+ })
416
+
417
+ test(`get_file throws when no file found`, async () => {
418
+ mock_vscode.window.tabGroups.activeTabGroup.activeTab = null
419
+ await expect(get_file()).rejects.toThrow(
420
+ `No file selected. MatterViz needs an active editor to know what to render.`,
421
+ )
422
+ })
423
+
424
+ test.each([
425
+ [`structure`, { filename: `test.cif`, content: `content`, is_base64: false }],
426
+ [`trajectory`, { filename: `test.traj`, content: `YmluYXJ5`, is_base64: true }],
427
+ [
428
+ `structure`,
429
+ {
430
+ filename: `test"quotes.cif`,
431
+ content: `content`,
432
+ is_base64: false,
433
+ },
434
+ ],
435
+ [`structure`, { filename: `test.cif`, content: ``, is_base64: false }],
436
+ [
437
+ `structure`,
438
+ {
439
+ filename: `test.cif`,
440
+ content: `<script>alert("xss")</script>`,
441
+ is_base64: false,
442
+ },
443
+ ],
444
+ [
445
+ `structure`,
446
+ {
447
+ filename: `large.cif`,
448
+ content: `x`.repeat(100_000),
449
+ is_base64: false,
450
+ },
451
+ ],
452
+ ] as const)(`HTML generation: %s files`, (type, data) => {
453
+ const webview_data = { type, data, theme: `light` } as const
454
+ const html = create_html(mock_webview, mock_context, webview_data)
455
+ expect(html).toContain(`<!DOCTYPE html>`)
456
+ expect(html).toContain(`Content-Security-Policy`)
457
+ expect(html).toContain(`default-src 'none'`)
458
+ expect(html).toContain(`script-src 'nonce-`)
459
+ expect(html).toMatch(/nonce="[a-zA-Z0-9]{8,32}"/)
460
+ expect(html).toContain(JSON.stringify(webview_data).replace(/<\//g, `<\\/`))
461
+ expect(html).toContain(`matterviz-app`)
462
+ })
463
+
464
+ test.each([
465
+ [{ command: `info`, text: `Test message` }, `showInformationMessage`],
466
+ [{ command: `error`, text: `Error message` }, `showErrorMessage`],
467
+ [{ command: `info`, text: `"><script>alert(1)</script>` }, `showInformationMessage`],
468
+ [{ command: `error`, text: `javascript:alert(1)` }, `showErrorMessage`],
469
+ ] as const)(`message handling: %s`, async (message, expected_method) => {
470
+ await handle_msg(message)
471
+ expect(
472
+ mock_vscode.window[expected_method as keyof typeof mock_vscode.window],
473
+ ).toHaveBeenCalledWith(message.text)
474
+ })
475
+
476
+ test.each([
477
+ [{ command: `saveAs`, content: `content`, filename: `test.cif` }, true, `Saved: save.cif`],
478
+ [
479
+ {
480
+ command: `saveAs`,
481
+ content: `<script>alert("XSS")</script>`,
482
+ filename: `test.cif`,
483
+ },
484
+ true,
485
+ `Saved: save.cif`,
486
+ ],
487
+ ] as const)(`saveAs success: %s`, async (message, should_succeed, expected_info) => {
488
+ mock_vscode.window.showSaveDialog.mockResolvedValue({ fsPath: `/test/save.cif` })
489
+ await handle_msg(message)
490
+ if (should_succeed) {
491
+ const enc = new TextEncoder()
492
+ expect(mock_vscode.workspace.fs.writeFile).toHaveBeenCalledWith(
493
+ { fsPath: `/test/save.cif` },
494
+ enc.encode(message.content),
495
+ )
496
+ expect(mock_vscode.window.showInformationMessage).toHaveBeenCalledWith(expected_info)
497
+ }
498
+ })
499
+
500
+ test.each([
501
+ [
502
+ `PNG image`,
503
+ `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==`,
504
+ `structure.png`,
505
+ true,
506
+ ],
507
+ [
508
+ `JPEG image`,
509
+ `data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAAD/2wBDAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEB/8QAFBABAAAAAAAAAAAAAAAAAAAAAP/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AP/Z`,
510
+ `plot.jpg`,
511
+ true,
512
+ ],
513
+ [
514
+ `PDF document`,
515
+ `data:application/pdf;base64,JVBERi0xLjQKJcOkw7zDtsO8DQoxIDAgb2JqDQo8PA0KL1R5cGUgL0NhdGFsb2cNCi9QYWdlcyAyIDAgUg0KPj4NCmVuZG9iag0KMiAwIG9iag0KPDwNCi9UeXBlIC9QYWdlcw0KL0tpZHMgWzMgMCBSXQ0KL0NvdW50IDENCi9NZWRpYUJveCBbMCAwIDYxMiA3OTJdDQo+Pg0KZW5kb2JqDQozIDAgb2JqDQo8PA0KL1R5cGUgL1BhZ2UNCi9QYXJlbnQgMiAwIFINCi9SZXNvdXJjZXMgPDwNCi9Gb250IDw8DQovRjEgNCAwIFINCj4+DQo+Pg0KL0NvbnRlbnRzIDUgMCBSDQo+Pg0KZW5kb2JqDQo0IDAgb2JqDQo8PA0KL1R5cGUgL0ZvbnQNCi9TdWJ0eXBlIC9UeXBlMQ0KL0Jhc2VGb250IC9IZWx2ZXRpY2ENCi9FbmNvZGluZyAvV2luQW5zaUVuY29kaW5nDQo+Pg0KZW5kb2JqDQo1IDAgb2JqDQo8PA0KL0xlbmd0aCA0NA0KPj4NCnN0cmVhbQ0KQlQNCjEyIDAgVGQKL0YxIDEyIFRqDQooSGVsbG8gV29ybGQpIFRqDQpFVA0KZW5kc3RyZWFtDQplbmRvYmoNCnhyZWYNCjAgNg0KMDAwMDAwMDAwMCA2NTUzNSBmDQowMDAwMDAwMDEwIDAwMDAwIG4NCjAwMDAwMDAwNzkgMDAwMDAgbg0KMDAwMDAwMDE3MyAwMDAwMCBuDQowMDAwMDAwMzAxIDAwMDAwIG4NCjAwMDAwMDAzODAgMDAwMDAgbg0KdHJhaWxlcg0KPDwNCi9TaXplIDYNCi9Sb290IDEgMCBSDQo+Pg0Kc3RhcnR4cmVmDQo0OTINCiUlRU9G`,
516
+ `report.pdf`,
517
+ true,
518
+ ],
519
+ ])(`saveAs binary data: %s`, async (_description, data_url, filename, is_binary) => {
520
+ mock_vscode.window.showSaveDialog.mockResolvedValue({ fsPath: `/test/${filename}` })
521
+ await handle_msg({
522
+ command: `saveAs`,
523
+ content: data_url,
524
+ ...msg_args,
525
+ filename,
526
+ is_binary,
527
+ })
528
+ const base64_data = data_url.replace(/^data:[^;]+;base64,/, ``)
529
+ const expected_buffer = Uint8Array.from(Buffer.from(base64_data, `base64`))
530
+ expect(mock_vscode.workspace.fs.writeFile).toHaveBeenCalledWith(
531
+ { fsPath: `/test/${filename}` },
532
+ expected_buffer,
533
+ )
534
+ expect(mock_vscode.window.showInformationMessage).toHaveBeenCalledWith(
535
+ `Saved: ${filename}`,
536
+ )
537
+ })
538
+
539
+ test(`saveAs error handling`, async () => {
540
+ mock_vscode.window.showSaveDialog.mockResolvedValue({
541
+ fsPath: `/test/save.cif`,
542
+ })
543
+ mock_vscode.workspace.fs.writeFile.mockRejectedValue(new Error(`Write failed`))
544
+
545
+ await handle_msg({
546
+ command: `saveAs`,
547
+ content: `content`,
548
+ ...msg_args,
549
+ filename: `test.cif`,
550
+ })
551
+ expect(mock_vscode.window.showErrorMessage).toHaveBeenCalledWith(
552
+ `Failed to save text file: Write failed`,
553
+ )
554
+ })
555
+
556
+ test(`saveAs user cancellation`, async () => {
557
+ mock_vscode.window.showSaveDialog.mockResolvedValue(undefined)
558
+
559
+ await handle_msg({
560
+ command: `saveAs`,
561
+ content: `content`,
562
+ ...msg_args,
563
+ filename: `test.cif`,
564
+ })
565
+ expect(mock_vscode.workspace.fs.writeFile).not.toHaveBeenCalled()
566
+ })
567
+
568
+ test(`saveAs binary data validation: empty base64 data`, async () => {
569
+ mock_vscode.window.showSaveDialog.mockResolvedValue({ fsPath: `/test/test.png` })
570
+
571
+ await handle_msg({
572
+ command: `saveAs`,
573
+ content: `data:image/png;base64,`,
574
+ ...msg_args,
575
+ filename: `test.png`,
576
+ is_binary: true,
577
+ })
578
+
579
+ expect(mock_vscode.window.showErrorMessage).toHaveBeenCalledWith(
580
+ `Failed to save binary data: Invalid data URL: missing base64 data`,
581
+ )
582
+ expect(mock_vscode.workspace.fs.writeFile).not.toHaveBeenCalled()
583
+ })
584
+
585
+ test.each([[{ command: `info` }], [{ command: `saveAs` }], [{ command: `unknown` }]])(
586
+ `malformed message handling: %s`,
587
+ async (msg) => {
588
+ await expect(
589
+ handle_msg({ ...msg, ...msg_args } as unknown as MessageData),
590
+ ).resolves.toBeUndefined()
591
+ },
592
+ )
593
+
594
+ test(`render creates webview panel`, async () => {
595
+ const mock_panel = {
596
+ webview: { ...mock_webview },
597
+ onDidDispose: vi.fn(),
598
+ }
599
+ mock_vscode.window.createWebviewPanel.mockReturnValue(mock_panel)
600
+ mock_vscode.window.activeTextEditor = {
601
+ document: { fileName: `/test/active.cif`, getText: () => `content` },
602
+ } as TextEditor
603
+
604
+ await render(mock_context)
605
+ expect(mock_vscode.window.createWebviewPanel).toHaveBeenCalledWith(
606
+ `matterviz`,
607
+ `MatterViz - active.cif`,
608
+ mock_vscode.ViewColumn.Active,
609
+ expect.any(Object),
610
+ )
611
+ })
612
+
613
+ test(`render handles errors`, async () => {
614
+ mock_vscode.window.activeTextEditor = null
615
+ mock_vscode.window.tabGroups.activeTabGroup.activeTab = null
616
+ await render(mock_context)
617
+ expect(mock_vscode.window.showErrorMessage).toHaveBeenCalledWith(
618
+ `Failed: No file selected. MatterViz needs an active editor to know what to render.`,
619
+ )
620
+ })
621
+
622
+ test(`extension activation`, () => {
623
+ activate(mock_context)
624
+ expect(mock_vscode.commands.registerCommand).toHaveBeenCalledWith(
625
+ `matterviz.open`,
626
+ expect.any(Function),
627
+ )
628
+ expect(mock_vscode.commands.registerCommand).toHaveBeenCalledWith(
629
+ `matterviz.report_bug`,
630
+ expect.any(Function),
631
+ )
632
+ expect(mock_vscode.window.registerCustomEditorProvider).toHaveBeenCalledWith(
633
+ `matterviz.viewer`,
634
+ expect.any(Object),
635
+ expect.any(Object),
636
+ )
637
+ })
638
+
639
+ describe(`Bug Reporting`, () => {
640
+ let mock_opened_document: { content: string; language: string } | null = null
641
+ let report_bug_command: (() => Promise<void>) | null = null
642
+ let mock_env: {
643
+ appName: string
644
+ remoteName: string | undefined
645
+ uiKind: number
646
+ clipboard: {
647
+ writeText: ReturnType<typeof vi.fn>
648
+ readText: ReturnType<typeof vi.fn>
649
+ }
650
+ openExternal: ReturnType<typeof vi.fn>
651
+ }
652
+
653
+ beforeEach(async () => {
654
+ // Reset state
655
+ mock_opened_document = null
656
+ report_bug_command = null
657
+
658
+ // Mock clipboard API (must be set up before activation)
659
+ mock_env = {
660
+ appName: `Cursor`,
661
+ remoteName: undefined,
662
+ uiKind: 1, // Desktop
663
+ clipboard: {
664
+ writeText: vi.fn(() => Promise.resolve()),
665
+ readText: vi.fn(() => Promise.resolve(``)),
666
+ },
667
+ openExternal: vi.fn(() => Promise.resolve(true)),
668
+ }
669
+ mock_vscode.env = mock_env as unknown as typeof mock_vscode.env
670
+
671
+ // Capture the report_bug command during activation
672
+ const command_registry = new Map<string, () => Promise<void>>()
673
+ mock_vscode.commands.registerCommand = vi.fn(
674
+ (command_name: string, callback: () => Promise<void>) => {
675
+ command_registry.set(command_name, callback)
676
+ return { dispose: vi.fn() }
677
+ },
678
+ )
679
+
680
+ // Mock workspace.openTextDocument to capture the document content (BEFORE activation)
681
+ mock_vscode.workspace.openTextDocument = vi.fn(
682
+ (options: { content: string; language: string }) => {
683
+ mock_opened_document = {
684
+ content: options.content,
685
+ language: options.language,
686
+ }
687
+ return Promise.resolve({
688
+ uri: { fsPath: `/tmp/bug-report.md` },
689
+ getText: () => options.content,
690
+ })
691
+ },
692
+ )
693
+
694
+ // Mock window.showTextDocument (BEFORE activation)
695
+ mock_vscode.window.showTextDocument = vi.fn(() => Promise.resolve())
696
+
697
+ // Mock showInformationMessage to return action choices (BEFORE activation)
698
+ mock_vscode.window.showInformationMessage = vi.fn(() => Promise.resolve(undefined))
699
+
700
+ // Activate extension to register commands
701
+ activate(mock_context)
702
+
703
+ // Get the report_bug command
704
+ report_bug_command = command_registry.get(`matterviz.report_bug`) ?? null
705
+ })
706
+
707
+ test(`should generate bug report with environment information`, async () => {
708
+ expect(report_bug_command).not.toBeNull()
709
+ if (!report_bug_command) return
710
+
711
+ await report_bug_command()
712
+
713
+ expect(mock_opened_document).not.toBeNull()
714
+ expect(mock_opened_document?.language).toBe(`markdown`)
715
+
716
+ const content = mock_opened_document?.content ?? ``
717
+
718
+ // Check for main sections
719
+ expect(content).toContain(`### Environment`)
720
+ expect(content).toContain(`### System Resources`)
721
+ expect(content).toContain(`### Active Files & Extension State`)
722
+ expect(content).toContain(`### Console Logs`)
723
+
724
+ // Check environment details
725
+ expect(content).toContain(`- **Editor**: Cursor`)
726
+ expect(content).toContain(`- **MatterViz Version**: ${pkg_json.version}`)
727
+ expect(content).toContain(`- **UI Kind**: Desktop`)
728
+ expect(content).toContain(`- **Remote Session**: No (Local)`)
729
+
730
+ // Check system resources
731
+ expect(content).toContain(`- **Total Memory**:`)
732
+ expect(content).toContain(`- **Free Memory**:`)
733
+ expect(content).toContain(`- **Process RSS**:`)
734
+ expect(content).toContain(`- **Process Heap Used**:`)
735
+ expect(content).toContain(`- **Process Heap Total**:`)
736
+
737
+ // Check console logs instructions
738
+ expect(content).toContain(`**Please check for console errors/warnings:**`)
739
+ expect(content).toContain(`Toggle Developer Tools`)
740
+
741
+ // Check GitHub link
742
+ expect(content).toContain(`https://github.com/janosh/matterviz/issues`)
743
+
744
+ // Check timestamp
745
+ expect(content).toMatch(/\*\*Generated\*\*: \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/)
746
+ })
747
+
748
+ test(`should detect remote session correctly`, async () => {
749
+ // Mock remote session
750
+ mock_env.remoteName = `ssh-remote`
751
+
752
+ expect(report_bug_command).not.toBeNull()
753
+ if (!report_bug_command) return
754
+
755
+ await report_bug_command()
756
+
757
+ const content = mock_opened_document?.content ?? ``
758
+ expect(content).toContain(`- **Remote Session**: Yes (ssh-remote)`)
759
+ })
760
+
761
+ test(`should include active files in report`, async () => {
762
+ // Start watching some files first
763
+ const file1_path = `/test/structure.cif`
764
+ const file2_path = `/test/trajectory.traj`
765
+
766
+ await handle_msg(
767
+ {
768
+ command: `startWatching`,
769
+ file_path: file1_path,
770
+ filename: `structure.cif`,
771
+ request_id: `req1`,
772
+ frame_index: 0,
773
+ } as MessageData,
774
+ mock_webview,
775
+ )
776
+
777
+ await handle_msg(
778
+ {
779
+ command: `startWatching`,
780
+ file_path: file2_path,
781
+ filename: `trajectory.traj`,
782
+ request_id: `req2`,
783
+ frame_index: 0,
784
+ } as MessageData,
785
+ mock_webview,
786
+ )
787
+
788
+ expect(report_bug_command).not.toBeNull()
789
+ if (!report_bug_command) return
790
+
791
+ await report_bug_command()
792
+
793
+ const content = mock_opened_document?.content ?? ``
794
+
795
+ // Should list both files with bold filenames (not headers)
796
+ expect(content).toContain(`**structure.cif**`)
797
+ expect(content).toContain(`**trajectory.traj**`)
798
+ expect(content).toContain(`- **Path**: \`${file1_path}\``)
799
+ expect(content).toContain(`- **Path**: \`${file2_path}\``)
800
+ expect(content).toContain(`- **Has Watcher**: true`)
801
+
802
+ // Check for combined section and extension state counters
803
+ expect(content).toContain(`### Active Files & Extension State`)
804
+ expect(content).toContain(`- **Active Watchers**: 2`)
805
+ })
806
+
807
+ test(`should handle files with no active watchers`, async () => {
808
+ expect(report_bug_command).not.toBeNull()
809
+ if (!report_bug_command) return
810
+
811
+ await report_bug_command()
812
+
813
+ const content = mock_opened_document?.content ?? ``
814
+ expect(content).toContain(`No files currently being watched/rendered.`)
815
+ })
816
+
817
+ test(`should copy to clipboard when user selects that option`, async () => {
818
+ mock_vscode.window.showInformationMessage = vi.fn(() =>
819
+ Promise.resolve(`Copy to Clipboard`),
820
+ )
821
+
822
+ expect(report_bug_command).not.toBeNull()
823
+ if (!report_bug_command) return
824
+
825
+ await report_bug_command()
826
+
827
+ const content = mock_opened_document?.content ?? ``
828
+
829
+ // Should have called clipboard.writeText with the content
830
+ expect(mock_env.clipboard.writeText).toHaveBeenCalledWith(content)
831
+
832
+ // Should show success message
833
+ expect(mock_vscode.window.showInformationMessage).toHaveBeenCalledWith(
834
+ `Debug information copied to clipboard!`,
835
+ )
836
+ })
837
+
838
+ test(`should open GitHub issues when user selects that option`, async () => {
839
+ mock_vscode.window.showInformationMessage = vi.fn(() =>
840
+ Promise.resolve(`Open GitHub Issues`),
841
+ )
842
+
843
+ expect(report_bug_command).not.toBeNull()
844
+ if (!report_bug_command) return
845
+
846
+ await report_bug_command()
847
+
848
+ // Should have opened GitHub issues URL
849
+ expect(mock_env.openExternal).toHaveBeenCalledWith(
850
+ expect.objectContaining({
851
+ toString: expect.any(Function),
852
+ }),
853
+ )
854
+
855
+ // Verify the URL contains the GitHub issues path
856
+ const call_args = mock_env.openExternal.mock.calls[0]
857
+ expect(call_args[0].toString()).toContain(
858
+ `https://github.com/janosh/matterviz/issues/new`,
859
+ )
860
+ })
861
+
862
+ test(`should format file sizes correctly`, async () => {
863
+ // Mock different file sizes
864
+ const test_cases = [
865
+ { size: 500, expected: `500 B` },
866
+ { size: 1024, expected: `1.00 KiB` },
867
+ { size: 1024 * 1024, expected: `1.00 MiB` },
868
+ { size: 1024 * 1024 * 1024, expected: `1.00 GiB` },
869
+ ]
870
+
871
+ // Create a map to track sizes for each file
872
+ const file_sizes = new Map<string, number>()
873
+
874
+ // Set up persistent mock that uses the file_sizes map
875
+ mock_vscode.workspace.fs.stat.mockImplementation((uri) => {
876
+ const size = file_sizes.get(uri.fsPath) ?? 1000
877
+ return Promise.resolve({ size, type: 1 })
878
+ })
879
+
880
+ // Add files to watchers with their sizes
881
+ const watcher_promises = test_cases.map((test_case) => {
882
+ const file_path = `/test/file_${test_case.size}.cif`
883
+ file_sizes.set(file_path, test_case.size)
884
+
885
+ return handle_msg(
886
+ {
887
+ command: `startWatching`,
888
+ file_path,
889
+ filename: `file_${test_case.size}.cif`,
890
+ request_id: `req_${test_case.size}`,
891
+ frame_index: 0,
892
+ } as MessageData,
893
+ mock_webview,
894
+ )
895
+ })
896
+
897
+ await Promise.all(watcher_promises)
898
+
899
+ expect(report_bug_command).not.toBeNull()
900
+ if (!report_bug_command) return
901
+
902
+ await report_bug_command()
903
+
904
+ const content = mock_opened_document?.content ?? ``
905
+
906
+ // Check that sizes are formatted correctly
907
+ for (const test_case of test_cases) {
908
+ if (test_case.size >= 1024) {
909
+ // Only check KB and above (bytes might be rounded)
910
+ expect(content).toContain(test_case.expected)
911
+ }
912
+ }
913
+ })
914
+
915
+ test(`should handle file stat errors gracefully`, async () => {
916
+ // Mock file that exists but throws error on stat
917
+ const file_path = `/test/error-file.cif`
918
+
919
+ await handle_msg(
920
+ {
921
+ command: `startWatching`,
922
+ file_path,
923
+ filename: `error-file.cif`,
924
+ request_id: `req_error`,
925
+ frame_index: 0,
926
+ } as MessageData,
927
+ mock_webview,
928
+ )
929
+
930
+ // Mock stat to throw error for this specific file
931
+ mock_vscode.workspace.fs.stat.mockRejectedValue(new Error(`File not found`))
932
+
933
+ expect(report_bug_command).not.toBeNull()
934
+ if (!report_bug_command) return
935
+
936
+ await report_bug_command()
937
+
938
+ const content = mock_opened_document?.content ?? ``
939
+
940
+ // Should still include the file but with "Unknown" size
941
+ expect(content).toContain(`**error-file.cif**`)
942
+ expect(content).toContain(`- **Size**: Unknown`)
943
+ })
944
+
945
+ test(`should include extension state counters`, async () => {
946
+ // Start watching multiple files
947
+ await handle_msg(
948
+ {
949
+ command: `startWatching`,
950
+ file_path: `/test/file1.cif`,
951
+ filename: `file1.cif`,
952
+ request_id: `req1`,
953
+ frame_index: 0,
954
+ } as MessageData,
955
+ mock_webview,
956
+ )
957
+
958
+ await handle_msg(
959
+ {
960
+ command: `startWatching`,
961
+ file_path: `/test/file2.cif`,
962
+ filename: `file2.cif`,
963
+ request_id: `req2`,
964
+ frame_index: 0,
965
+ } as MessageData,
966
+ mock_webview,
967
+ )
968
+
969
+ expect(report_bug_command).not.toBeNull()
970
+ if (!report_bug_command) return
971
+
972
+ await report_bug_command()
973
+
974
+ const content = mock_opened_document?.content ?? ``
975
+
976
+ // Check combined Active Files & Extension State section
977
+ expect(content).toContain(`### Active Files & Extension State`)
978
+ expect(content).toContain(`- **Active Watchers**: 2`)
979
+ expect(content).toMatch(/- \*\*Active Frame Loaders\*\*: \d+/)
980
+ expect(content).toMatch(/- \*\*Auto-Render Timers\*\*: \d+/)
981
+ expect(content).toMatch(/- \*\*Active Auto-Render Panels\*\*: \d+/)
982
+ })
983
+
984
+ test(`should handle errors during report generation`, async () => {
985
+ // Mock openTextDocument to throw an error
986
+ mock_vscode.workspace.openTextDocument = vi.fn(() =>
987
+ Promise.reject(new Error(`Failed to create document`)),
988
+ )
989
+
990
+ expect(report_bug_command).not.toBeNull()
991
+ if (!report_bug_command) return
992
+
993
+ await report_bug_command()
994
+
995
+ // Should show error message
996
+ expect(mock_vscode.window.showErrorMessage).toHaveBeenCalledWith(
997
+ expect.stringContaining(`Failed to collect debug information`),
998
+ )
999
+ })
1000
+
1001
+ test(`should include console logs instructions in bug report`, async () => {
1002
+ expect(report_bug_command).not.toBeNull()
1003
+ if (!report_bug_command) return
1004
+
1005
+ await report_bug_command()
1006
+
1007
+ const content = mock_opened_document?.content ?? ``
1008
+
1009
+ // Check for console logs section with instructions
1010
+ expect(content).toContain(`### Console Logs`)
1011
+ expect(content).toContain(`**Please check for console errors/warnings:**`)
1012
+ expect(content).toContain(`Toggle Developer Tools`)
1013
+ expect(content).toContain(`Tip: You can filter console messages`)
1014
+ })
1015
+ })
1016
+
1017
+ test(`performance benchmarks`, () => {
1018
+ // Trajectory detection performance
1019
+ const filenames = Array.from({ length: 10000 }, (_, idx) => `test_${idx}.xyz`)
1020
+ const start = performance.now()
1021
+ filenames.forEach((name) => is_trajectory_file(name))
1022
+ expect(performance.now() - start).toBeLessThan(100)
1023
+
1024
+ // HTML generation performance
1025
+ const large_data = {
1026
+ type: `structure`,
1027
+ data: { filename: `large.cif`, content: `x`.repeat(100_000), is_base64: false },
1028
+ theme: `light`,
1029
+ } as const
1030
+ const html_start = performance.now()
1031
+ create_html(mock_webview, mock_context, large_data)
1032
+ expect(performance.now() - html_start).toBeLessThan(50)
1033
+ })
1034
+
1035
+ test(`nonce uniqueness`, () => {
1036
+ const data = {
1037
+ type: `structure`,
1038
+ data: { filename: `test.cif`, content: `content`, is_base64: false },
1039
+ theme: `light`,
1040
+ } as const
1041
+ const nonces = new Set<string>()
1042
+
1043
+ for (let idx = 0; idx < 1000; idx++) {
1044
+ const html = create_html(mock_webview, mock_context, data)
1045
+ const nonce_match = /nonce="([a-zA-Z0-9]+)"/.exec(html)
1046
+ if (nonce_match) nonces.add(nonce_match[1])
1047
+ }
1048
+
1049
+ expect(nonces.size).toBe(1000)
1050
+ })
1051
+
1052
+ test(`XSS prevention`, () => {
1053
+ const dangerous_payloads = [
1054
+ `<script>alert("XSS")</script>`,
1055
+ `<img src="x" onerror="alert(1)">`,
1056
+ `javascript:alert(1)`,
1057
+ `"><script>alert(1)</script>`,
1058
+ `';alert(1);//`,
1059
+ `</script><script>alert(document.cookie)</script>`,
1060
+ ]
1061
+
1062
+ dangerous_payloads.forEach((payload) => {
1063
+ const data = {
1064
+ type: `structure`,
1065
+ data: { filename: `test.cif`, content: payload, is_base64: false },
1066
+ theme: `light`,
1067
+ } as const
1068
+ const html = create_html(mock_webview, mock_context, data)
1069
+ const escaped_json = JSON.stringify(data).replace(/<\//g, `<\\/`)
1070
+
1071
+ expect(html).toContain(escaped_json)
1072
+ // Ensure no raw </script> inside the JSON data breaks out of the script tag
1073
+ const data_script = /window\.matterviz_data=(.*?);/s.exec(html)
1074
+ if (data_script) {
1075
+ expect(data_script[1]).not.toContain(`</script>`)
1076
+ }
1077
+ })
1078
+ })
1079
+
1080
+ test(`concurrent operations`, async () => {
1081
+ const promises = Array.from({ length: 50 }, (_, idx) =>
1082
+ handle_msg({ command: `info`, text: `Message ${idx}`, ...msg_args }),
1083
+ )
1084
+ await Promise.all(promises)
1085
+ expect(mock_vscode.window.showInformationMessage).toHaveBeenCalledTimes(50)
1086
+ })
1087
+
1088
+ describe(`Theme functionality`, () => {
1089
+ test.each([
1090
+ [mock_vscode.ColorThemeKind.Light, `auto`, `light`], // Light VSCode theme, auto setting → light
1091
+ [mock_vscode.ColorThemeKind.Dark, `auto`, `dark`], // Dark VSCode theme, auto setting → dark
1092
+ [mock_vscode.ColorThemeKind.HighContrast, `auto`, `black`], // High contrast VSCode theme, auto setting → black
1093
+ [mock_vscode.ColorThemeKind.HighContrastLight, `auto`, `white`], // High contrast light VSCode theme, auto setting → white
1094
+ [mock_vscode.ColorThemeKind.Light, `light`, `light`], // Light VSCode theme, light setting → light
1095
+ [mock_vscode.ColorThemeKind.Light, `dark`, `dark`], // Light VSCode theme, dark setting → dark
1096
+ [mock_vscode.ColorThemeKind.Light, `white`, `white`], // Light VSCode theme, white setting → white
1097
+ [mock_vscode.ColorThemeKind.Light, `black`, `black`], // Light VSCode theme, black setting → black
1098
+ [mock_vscode.ColorThemeKind.Dark, `light`, `light`], // Dark VSCode theme, light setting → light
1099
+ [mock_vscode.ColorThemeKind.Dark, `dark`, `dark`], // Dark VSCode theme, dark setting → dark
1100
+ [mock_vscode.ColorThemeKind.Dark, `white`, `white`], // Dark VSCode theme, white setting → white
1101
+ [mock_vscode.ColorThemeKind.Dark, `black`, `black`], // Dark VSCode theme, black setting → black
1102
+ ] as const)(
1103
+ `theme detection: VSCode theme %i, setting '%s' → '%s'`,
1104
+ (vscode_theme_kind: number, setting: string, expected: ThemeName) => {
1105
+ const mock_config = {
1106
+ get: vi.fn((key: string, default_value?: string) =>
1107
+ key === `theme` ? setting : default_value,
1108
+ ),
1109
+ }
1110
+ mock_vscode.workspace.getConfiguration = vi.fn(() => mock_config) as ReturnType<
1111
+ typeof vi.fn
1112
+ >
1113
+ mock_vscode.window.activeColorTheme = { kind: vscode_theme_kind }
1114
+
1115
+ const result = get_theme()
1116
+ expect(result).toBe(expected)
1117
+ },
1118
+ )
1119
+
1120
+ test(`webview data includes theme`, () => {
1121
+ const mock_config = {
1122
+ get: vi.fn((key: string, default_value?: string) =>
1123
+ key === `theme` ? `dark` : default_value,
1124
+ ),
1125
+ }
1126
+ mock_vscode.workspace.getConfiguration = vi.fn(() => mock_config) as ReturnType<
1127
+ typeof vi.fn
1128
+ >
1129
+
1130
+ const data = {
1131
+ type: `structure` as const,
1132
+ data: { filename: `test.cif`, content: `content`, is_base64: false },
1133
+ theme: get_theme(),
1134
+ }
1135
+
1136
+ const html = create_html(mock_webview, mock_context, data)
1137
+
1138
+ const parsed_data = JSON.parse(/matterviz_data=(\{[\s\S]*?\});/.exec(html)?.[1] || `{}`)
1139
+ expect(parsed_data.theme).toBe(`dark`)
1140
+ })
1141
+
1142
+ test(`invalid theme setting falls back to auto`, () => {
1143
+ const mock_config = {
1144
+ get: vi.fn((key: string, default_value?: string) =>
1145
+ key === `theme` ? `invalid-theme` : default_value,
1146
+ ),
1147
+ }
1148
+ mock_vscode.workspace.getConfiguration = vi.fn(() => mock_config) as ReturnType<
1149
+ typeof vi.fn
1150
+ >
1151
+ mock_vscode.window.activeColorTheme = {
1152
+ kind: mock_vscode.ColorThemeKind.Light,
1153
+ }
1154
+
1155
+ const result = get_theme()
1156
+ expect(result).toBe(`light`) // Should fall back to system theme
1157
+ })
1158
+
1159
+ test(`high contrast themes are mapped correctly`, () => {
1160
+ const mock_config = {
1161
+ get: vi.fn((key: string, default_value?: string) =>
1162
+ key === `theme` ? `auto` : default_value,
1163
+ ),
1164
+ }
1165
+ mock_vscode.workspace.getConfiguration = vi.fn(() => mock_config) as ReturnType<
1166
+ typeof vi.fn
1167
+ >
1168
+
1169
+ // Test high contrast dark → black
1170
+ mock_vscode.window.activeColorTheme = {
1171
+ kind: mock_vscode.ColorThemeKind.HighContrast,
1172
+ }
1173
+ expect(get_theme()).toBe(`black`)
1174
+
1175
+ // Test high contrast light → white
1176
+ mock_vscode.window.activeColorTheme = {
1177
+ kind: mock_vscode.ColorThemeKind.HighContrastLight,
1178
+ }
1179
+ expect(get_theme()).toBe(`white`)
1180
+ })
1181
+ })
1182
+
1183
+ describe(`Theme listener cleanup`, () => {
1184
+ const setup_panel = (options = {}) => {
1185
+ const mock_dispose = vi.fn()
1186
+ const mock_panel = {
1187
+ webview: { ...mock_webview },
1188
+ onDidDispose: vi.fn(),
1189
+ visible: true,
1190
+ ...options,
1191
+ }
1192
+
1193
+ mock_vscode.window.createWebviewPanel.mockReturnValue(mock_panel)
1194
+ mock_vscode.window.onDidChangeActiveColorTheme.mockReturnValue({
1195
+ dispose: mock_dispose,
1196
+ })
1197
+ mock_vscode.workspace.onDidChangeConfiguration.mockReturnValue({
1198
+ dispose: mock_dispose,
1199
+ })
1200
+ mock_vscode.window.activeTextEditor = {
1201
+ document: { fileName: `/test/active.cif`, getText: () => `content` },
1202
+ } as TextEditor
1203
+
1204
+ return { mock_dispose, mock_panel }
1205
+ }
1206
+
1207
+ test(`sets up and cleans up theme listeners`, async () => {
1208
+ const { mock_dispose, mock_panel } = setup_panel()
1209
+
1210
+ await render(mock_context)
1211
+
1212
+ expect(mock_vscode.window.onDidChangeActiveColorTheme).toHaveBeenCalled()
1213
+ expect(mock_panel.onDidDispose).toHaveBeenCalled()
1214
+
1215
+ // Test cleanup
1216
+ mock_panel.onDidDispose.mock.calls[0][0]()
1217
+ expect(mock_dispose).toHaveBeenCalledTimes(2)
1218
+ })
1219
+
1220
+ test(`respects panel visibility for theme updates`, async () => {
1221
+ const mock_panel = {
1222
+ webview: { ...mock_webview },
1223
+ onDidDispose: vi.fn(),
1224
+ visible: false,
1225
+ }
1226
+
1227
+ mock_vscode.window.createWebviewPanel.mockReturnValue(mock_panel)
1228
+ mock_vscode.window.onDidChangeActiveColorTheme.mockReturnValue({
1229
+ dispose: vi.fn(),
1230
+ })
1231
+ mock_vscode.workspace.onDidChangeConfiguration.mockReturnValue({
1232
+ dispose: vi.fn(),
1233
+ })
1234
+ mock_vscode.window.activeTextEditor = {
1235
+ document: { fileName: `/test/active.cif`, getText: () => `content` },
1236
+ } as TextEditor
1237
+
1238
+ await render(mock_context)
1239
+
1240
+ // Store initial HTML after render (render always sets HTML initially)
1241
+ const initial_html = mock_panel.webview.html
1242
+
1243
+ const theme_calls = mock_vscode.window.onDidChangeActiveColorTheme.mock
1244
+ .calls as unknown as unknown[][]
1245
+ const theme_callback = theme_calls[0]?.[0] as (() => Promise<void>) | undefined
1246
+ expect(theme_callback).toBeDefined()
1247
+
1248
+ // Should not update when invisible
1249
+ await theme_callback?.()
1250
+ expect(mock_panel.webview.html).toBe(initial_html)
1251
+
1252
+ // Should update when visible
1253
+ mock_panel.visible = true
1254
+ await theme_callback?.()
1255
+ expect(mock_panel.webview.html).not.toBe(initial_html)
1256
+ })
1257
+
1258
+ test(`multiple panels dispose independently`, async () => {
1259
+ const dispose1 = vi.fn()
1260
+ const dispose2 = vi.fn()
1261
+ const panel1 = { webview: { ...mock_webview }, onDidDispose: vi.fn() }
1262
+ const panel2 = { webview: { ...mock_webview }, onDidDispose: vi.fn() }
1263
+
1264
+ mock_vscode.window.createWebviewPanel
1265
+ .mockReturnValueOnce(panel1)
1266
+ .mockReturnValueOnce(panel2)
1267
+ mock_vscode.window.onDidChangeActiveColorTheme
1268
+ .mockReturnValueOnce({ dispose: dispose1 })
1269
+ .mockReturnValueOnce({
1270
+ dispose: dispose2,
1271
+ })
1272
+ mock_vscode.workspace.onDidChangeConfiguration
1273
+ .mockReturnValueOnce({ dispose: dispose1 })
1274
+ .mockReturnValueOnce({
1275
+ dispose: dispose2,
1276
+ })
1277
+
1278
+ mock_vscode.window.activeTextEditor = {
1279
+ document: { fileName: `/test/active.cif`, getText: () => `content` },
1280
+ } as TextEditor
1281
+
1282
+ await render(mock_context)
1283
+ await render(mock_context)
1284
+
1285
+ panel1.onDidDispose.mock.calls[0][0]()
1286
+ expect(dispose1).toHaveBeenCalledTimes(2)
1287
+ expect(dispose2).not.toHaveBeenCalled()
1288
+ })
1289
+ })
1290
+
1291
+ describe(`File Watching`, () => {
1292
+ describe(`message handling`, () => {
1293
+ test(`should handle startWatching message`, async () => {
1294
+ const message = {
1295
+ command: `startWatching` as const,
1296
+ ...msg_args,
1297
+ file_path: `/test/file.cif`,
1298
+ }
1299
+ await handle_msg(message, mock_webview)
1300
+
1301
+ expect(mock_vscode.workspace.createFileSystemWatcher).toHaveBeenCalledWith(
1302
+ expect.objectContaining({
1303
+ base: expect.anything(),
1304
+ pattern: `file.cif`,
1305
+ }),
1306
+ )
1307
+ expect(mock_file_system_watcher.onDidChange).toHaveBeenCalledWith(expect.any(Function))
1308
+ expect(mock_file_system_watcher.onDidDelete).toHaveBeenCalledWith(expect.any(Function))
1309
+ })
1310
+
1311
+ test(`should handle stopWatching message`, async () => {
1312
+ // First start watching
1313
+ const start_message = {
1314
+ command: `startWatching` as const,
1315
+ ...msg_args,
1316
+ file_path: `/test/file.cif`,
1317
+ }
1318
+ await handle_msg(start_message, mock_webview)
1319
+
1320
+ // Then test stopping
1321
+ const stop_message = {
1322
+ command: `stopWatching` as const,
1323
+ ...msg_args,
1324
+ file_path: `/test/file.cif`,
1325
+ }
1326
+ await handle_msg(stop_message, mock_webview)
1327
+
1328
+ expect(mock_file_system_watcher.dispose).toHaveBeenCalled()
1329
+ })
1330
+
1331
+ test(`should handle startWatching without webview gracefully`, async () => {
1332
+ const message = {
1333
+ command: `startWatching` as const,
1334
+ ...msg_args,
1335
+ file_path: `/test/file.cif`,
1336
+ }
1337
+
1338
+ await expect(handle_msg(message)).resolves.not.toThrow()
1339
+ expect(mock_vscode.workspace.createFileSystemWatcher).not.toHaveBeenCalled()
1340
+ })
1341
+
1342
+ test(`should handle startWatching without file_path gracefully`, async () => {
1343
+ const message = {
1344
+ command: `startWatching` as const,
1345
+ ...msg_args,
1346
+ }
1347
+
1348
+ await expect(handle_msg(message, mock_webview)).resolves.not.toThrow()
1349
+ expect(mock_vscode.workspace.createFileSystemWatcher).not.toHaveBeenCalled()
1350
+ })
1351
+
1352
+ test(`should send error message when file watching fails`, async () => {
1353
+ mock_vscode.workspace.createFileSystemWatcher.mockImplementation(() => {
1354
+ throw new Error(`File system watcher creation failed`)
1355
+ })
1356
+
1357
+ const message = {
1358
+ command: `startWatching` as const,
1359
+ ...msg_args,
1360
+ file_path: `/test/large-file.cif`,
1361
+ }
1362
+
1363
+ await handle_msg(message, mock_webview)
1364
+
1365
+ expect(mock_webview.postMessage).toHaveBeenCalledWith({
1366
+ command: `error`,
1367
+ text: expect.stringContaining(`Failed to start watching file`),
1368
+ })
1369
+ })
1370
+ })
1371
+
1372
+ describe(`file change notifications`, () => {
1373
+ test(`should send file change notification to webview`, async () => {
1374
+ const message = {
1375
+ command: `startWatching` as const,
1376
+ ...msg_args,
1377
+ file_path: `/test/file.cif`,
1378
+ }
1379
+
1380
+ await handle_msg(message, mock_webview)
1381
+
1382
+ // Get the change handler
1383
+ const change_handler = mock_file_system_watcher.onDidChange.mock.calls[0][0]
1384
+
1385
+ // Trigger file change
1386
+ await change_handler()
1387
+
1388
+ // Wait for postMessage to be called (it's async)
1389
+ await vi.waitFor(() => {
1390
+ expect(mock_webview.postMessage).toHaveBeenCalledWith({
1391
+ command: `fileUpdated`,
1392
+ data: expect.objectContaining({
1393
+ filename: `file.cif`,
1394
+ content: `mock content`,
1395
+ is_base64: false,
1396
+ }),
1397
+ type: `structure`,
1398
+ ...msg_args,
1399
+ file_path: `/test/file.cif`,
1400
+ theme: `light`,
1401
+ })
1402
+ })
1403
+ })
1404
+
1405
+ test(`should handle file deletion notifications`, async () => {
1406
+ const message = {
1407
+ command: `startWatching` as const,
1408
+ ...msg_args,
1409
+ file_path: `/test/file.cif`,
1410
+ }
1411
+
1412
+ await handle_msg(message, mock_webview)
1413
+
1414
+ // Get the delete handler
1415
+ const delete_handler = mock_file_system_watcher.onDidDelete.mock.calls[0][0]
1416
+
1417
+ // Trigger file deletion
1418
+ delete_handler()
1419
+
1420
+ expect(mock_webview.postMessage).toHaveBeenCalledWith(
1421
+ expect.objectContaining({
1422
+ command: `fileDeleted`,
1423
+ file_path: `/test/file.cif`,
1424
+ }),
1425
+ )
1426
+ })
1427
+ })
1428
+
1429
+ describe(`lifecycle management`, () => {
1430
+ test(`should handle activation gracefully`, () => {
1431
+ const mock_context = {
1432
+ extensionUri: { fsPath: `/test/extension` },
1433
+ subscriptions: [],
1434
+ } as unknown as ExtensionContext
1435
+
1436
+ expect(() => activate(mock_context)).not.toThrow()
1437
+
1438
+ expect(mock_vscode.commands.registerCommand).toHaveBeenCalledWith(
1439
+ `matterviz.open`,
1440
+ expect.any(Function),
1441
+ )
1442
+ })
1443
+ })
1444
+ })
1445
+
1446
+ describe(`Auto-Render Functionality`, () => {
1447
+ test.each([
1448
+ // Supported structure files
1449
+ [`structure.cif`, true],
1450
+ [`molecule.xyz`, true],
1451
+ [`crystal.poscar`, true],
1452
+ [`data.json`, false], // "data" is too broad, will not auto-render without structure-specific keywords in filename
1453
+ [`structure.xml`, true],
1454
+ [`molecule.pdb`, true],
1455
+ [`compound.mol`, true],
1456
+ [`structure.mol2`, true],
1457
+ [`data.sdf`, true],
1458
+ [`crystal.mmcif`, true],
1459
+ // Supported trajectory files
1460
+ [`trajectory.traj`, true],
1461
+ [`simulation.h5`, true],
1462
+ [`data.hdf5`, false],
1463
+ [`traj.xtc`, true],
1464
+ // Compressed supported files
1465
+ [`trajectory.xyz.gz`, true],
1466
+ [`data.json.gz`, false], // "data" is too broad, will not auto-render without structure-specific keywords in filename
1467
+ [`structure.cif.gz`, true],
1468
+ // Special filenames
1469
+ [`POSCAR`, true],
1470
+ [`CONTCAR`, true],
1471
+ [`XDATCAR`, true],
1472
+ [`trajectory.dat`, true],
1473
+ [`md.xyz`, true],
1474
+ [`relax.out`, true],
1475
+ [`npt.log`, true],
1476
+ [`nvt.data`, true],
1477
+ [`nve.traj`, true],
1478
+ // Files with special characters
1479
+ [`structure (1).cif`, true],
1480
+ [`trajectory[test].xyz.gz`, true],
1481
+ [`crystal@test.poscar`, true],
1482
+ [`molecule#test.xyz`, true],
1483
+ [`structure$test.json`, true],
1484
+ [`trajectory%test.h5`, true],
1485
+ [`crystal^test.traj`, true],
1486
+ [`molecule&test.extxyz`, true],
1487
+ [`structure*test.xml`, true],
1488
+ [`trajectory+test.pdb`, true],
1489
+ [`crystal=test.mol`, true],
1490
+ [`molecule|test.mol2`, true],
1491
+ [`structure\`test.sdf`, true],
1492
+ [`trajectory~test.mmcif`, true],
1493
+ // Case sensitivity tests
1494
+ [`STRUCTURE.CIF`, true],
1495
+ [`structure.CIF`, true],
1496
+ [`Structure.cif`, true],
1497
+ [`TRAJECTORY.XYZ`, true],
1498
+ [`trajectory.XYZ`, true],
1499
+ [`Trajectory.xyz`, true],
1500
+ [`POSCAR`, true],
1501
+ [`poscar`, true],
1502
+ [`Poscar`, true],
1503
+ [`CONTCAR`, true],
1504
+ [`contcar`, true],
1505
+ [`Contcar`, true],
1506
+ [`XDATCAR`, true],
1507
+ [`xdatcar`, true],
1508
+ [`Xdatcar`, true],
1509
+ // Fermi surface files
1510
+ [`band.bxsf`, true],
1511
+ [`fermi.frmsf`, true],
1512
+ [`BAND.BXSF`, true],
1513
+ [`fermi.FRMSF`, true],
1514
+ [`band.bxsf.gz`, true],
1515
+ [`fermi.frmsf.gz`, true],
1516
+ // Volumetric data files
1517
+ ...volumetric_auto_render_filenames,
1518
+ [`DENSITY.CUBE`, true],
1519
+ [`CHGCAR.lobster`, true],
1520
+ // Files that look like structure files but are supported
1521
+ [`structure_copy.cif`, true],
1522
+ [`trajectory_backup.xyz`, true],
1523
+ [`trajectory.log`, true], // Contains "trajectory" keyword
1524
+ // Very long filenames
1525
+ [`structure`.repeat(100) + `.cif`, true],
1526
+ // Unsupported files
1527
+ [`config.yaml`, false],
1528
+ [`simulation.trr`, false], // .trr files not supported
1529
+ [`md.dcd`, false], // .dcd files not supported
1530
+ [`document.txt`, false],
1531
+ [`script.py`, false],
1532
+ [`data.csv`, false],
1533
+ [`image.png`, false],
1534
+ [`archive.zip`, false],
1535
+ [`fake.gz`, false],
1536
+ [`config.ini`, false],
1537
+ [`log.txt`, false],
1538
+ [`README.md`, false],
1539
+ [`readme.md`, false],
1540
+ [`ReadMe.Md`, false],
1541
+ [`vite.config.ts`, false],
1542
+ [`test.spec.ts`, false],
1543
+ [`index.html`, false],
1544
+ [`style.css`, false],
1545
+ [`app.js`, false],
1546
+ [`data.sql`, false],
1547
+ [`backup.tar`, false],
1548
+ [`compressed.7z`, false],
1549
+ [`binary.bin`, false],
1550
+ [`.pre-commit-config.yaml`, false],
1551
+ [`changelog.md`, false],
1552
+ [`.prettierrc`, false],
1553
+ [`.gitignore`, false],
1554
+ [`dockerfile`, false],
1555
+ [`makefile`, false],
1556
+ [`.env`, false],
1557
+ [`.env.local`, false],
1558
+ [`.env.production`, false],
1559
+ [`.github/workflows/ci.yml`, false],
1560
+ [`dist/bundle.js`, false],
1561
+ [`build/index.html`, false],
1562
+ [`coverage/lcov.info`, false],
1563
+ [`.cache/build.js`, false],
1564
+ [`structure.json.bak`, false],
1565
+ [`crystal.poscar.lock`, true],
1566
+ [`simulation.log`, true],
1567
+ [`backup.old`, false],
1568
+ [`original.orig`, false],
1569
+ [`patch.diff`, false],
1570
+ [`structure.txt`, false],
1571
+ [`crystal.md`, false],
1572
+ [`molecule.doc`, false],
1573
+ [`poscar.bak`, true],
1574
+ [`contcar.old`, true],
1575
+ [`document.txt.gz`, false],
1576
+ [`script.py.gz`, false],
1577
+ [`data.csv.gz`, false],
1578
+ [`image.png.gz`, false],
1579
+ [`archive.zip.gz`, false],
1580
+ [`structure.cif.bz2`, false],
1581
+ [`density.cube.bz2`, false],
1582
+ [`PARCHG.bz2`, false],
1583
+ [`myCHGCARfile`, false],
1584
+ [`prefixPARCHGsuffix`, false],
1585
+ [`structure.cif.bak`, false],
1586
+ [`crystal.poscar.old`, true],
1587
+ [`molecule.xyz~`, false],
1588
+ [`structure.cif.swp`, false],
1589
+ [`DOCUMENT.TXT`, false],
1590
+ [`document.TXT`, false],
1591
+ [`Document.txt`, false],
1592
+ [`SCRIPT.PY`, false],
1593
+ [`script.PY`, false],
1594
+ [`Script.py`, false],
1595
+ [`DATA.CSV`, false],
1596
+ [`data.CSV`, false],
1597
+ [`Data.csv`, false],
1598
+ // Configuration files that should never auto-render
1599
+ [`package.json`, false],
1600
+ [`tsconfig.json`, false],
1601
+ [`vite.config.ts`, false],
1602
+ [`webpack.config.js`, false],
1603
+ [`rollup.config.js`, false],
1604
+ [`eslint.config.js`, false],
1605
+ [`prettier.config.js`, false],
1606
+ [`babel.config.js`, false],
1607
+ [`jest.config.js`, false],
1608
+ [`karma.conf.js`, false],
1609
+ [`cypress.json`, false],
1610
+ [`playwright.config.ts`, false],
1611
+ [`.eslintrc.json`, false],
1612
+ [`.prettierrc`, false],
1613
+ [`.babelrc`, false],
1614
+ [`.jest.config.js`, false],
1615
+ [`.karma.conf.js`, false],
1616
+ [`.cypress.json`, false],
1617
+ [`.playwright.config.ts`, false],
1618
+ [`.npmrc`, false],
1619
+ [`.yarnrc`, false],
1620
+ [`.vscode/settings.json`, false],
1621
+ [`.idea/workspace.xml`, false],
1622
+ [`.nyc_output/coverage.json`, false],
1623
+ [`.tmp/temp.json`, false],
1624
+ [`.temp/structure.json`, false],
1625
+ [`node_modules/package.json`, false],
1626
+ // Edge cases
1627
+ [``, false],
1628
+ [` `, false],
1629
+ [`.`, false],
1630
+ [`..`, false],
1631
+ [`/`, false],
1632
+ [`\\`, false],
1633
+ [`a`.repeat(1000) + `.txt`, false],
1634
+ // Null/undefined inputs
1635
+ [null as unknown as string, false],
1636
+ [undefined as unknown as string, false],
1637
+ ])(`should detect auto-render for "%s" as %s`, (filename, expected) => {
1638
+ expect(should_auto_render(filename)).toBe(expected)
1639
+ })
1640
+
1641
+ test(`should register auto-render functionality`, () => {
1642
+ const mock_context = {
1643
+ subscriptions: { push: vi.fn() },
1644
+ } as unknown as ExtensionContext
1645
+ activate(mock_context)
1646
+ expect(mock_vscode.workspace.onDidOpenTextDocument).toHaveBeenCalledWith(
1647
+ expect.any(Function),
1648
+ )
1649
+ })
1650
+
1651
+ test(`should handle rapid file detection efficiently`, () => {
1652
+ const filenames = Array.from({ length: 100 }, (_, idx) => `test_${idx}.cif`)
1653
+ const start = performance.now()
1654
+ filenames.forEach(should_auto_render)
1655
+ expect(performance.now() - start).toBeLessThan(10)
1656
+ })
1657
+
1658
+ test(`should not trigger on non-file URIs`, () => {
1659
+ const mock_context = {
1660
+ subscriptions: { push: vi.fn() },
1661
+ } as unknown as ExtensionContext
1662
+
1663
+ activate(mock_context)
1664
+
1665
+ // Get the registered callback
1666
+ const open_doc_calls = mock_vscode.workspace.onDidOpenTextDocument.mock
1667
+ .calls as unknown as unknown[][]
1668
+ const on_did_open_text_document_callback = open_doc_calls[0]?.[0] as
1669
+ | ((doc: unknown) => void)
1670
+ | undefined
1671
+ expect(on_did_open_text_document_callback).toBeDefined()
1672
+
1673
+ // Mock document with non-file URI
1674
+ const mock_document = {
1675
+ uri: { scheme: `untitled` },
1676
+ }
1677
+
1678
+ expect(() => on_did_open_text_document_callback?.(mock_document)).not.toThrow()
1679
+ })
1680
+
1681
+ test(`should respect auto_render configuration setting`, () => {
1682
+ const mock_context = {
1683
+ subscriptions: { push: vi.fn() },
1684
+ } as unknown as ExtensionContext
1685
+
1686
+ // Mock configuration to disable auto_render
1687
+ mock_vscode.workspace.getConfiguration.mockReturnValue({
1688
+ get: vi.fn((key: string, default_val: string) => {
1689
+ if (key === `auto_render`) return false
1690
+ return default_val
1691
+ }),
1692
+ })
1693
+
1694
+ activate(mock_context)
1695
+
1696
+ const open_doc_calls = mock_vscode.workspace.onDidOpenTextDocument.mock
1697
+ .calls as unknown as unknown[][]
1698
+ const on_did_open_text_document_callback = open_doc_calls[0]?.[0] as
1699
+ | ((doc: unknown) => void)
1700
+ | undefined
1701
+ expect(on_did_open_text_document_callback).toBeDefined()
1702
+
1703
+ // Mock document with supported file
1704
+ const mock_document = {
1705
+ uri: { scheme: `file`, fsPath: `/test/structure.cif` },
1706
+ }
1707
+
1708
+ expect(() => on_did_open_text_document_callback?.(mock_document)).not.toThrow()
1709
+ })
1710
+
1711
+ test(`should handle file reading errors gracefully during auto-render`, async () => {
1712
+ const mock_context = {
1713
+ subscriptions: { push: vi.fn() },
1714
+ } as unknown as ExtensionContext
1715
+
1716
+ // Mock vscode.workspace.fs.stat to throw an error
1717
+ mock_vscode.workspace.fs.stat.mockRejectedValue(new Error(`File not found`))
1718
+
1719
+ // Enable auto_render in config
1720
+ mock_vscode.workspace.getConfiguration.mockReturnValue({
1721
+ get: vi.fn((_key: string, default_val: string) =>
1722
+ _key === `auto_render` ? `true` : default_val,
1723
+ ),
1724
+ })
1725
+
1726
+ activate(mock_context)
1727
+
1728
+ const open_doc_calls = mock_vscode.workspace.onDidOpenTextDocument.mock
1729
+ .calls as unknown as unknown[][]
1730
+ const on_did_open_text_document_callback = open_doc_calls[0]?.[0] as
1731
+ | ((doc: unknown) => void)
1732
+ | undefined
1733
+ expect(on_did_open_text_document_callback).toBeDefined()
1734
+
1735
+ // Mock document with supported file
1736
+ const mock_document = {
1737
+ uri: { scheme: `file`, fsPath: `/test/structure.cif` },
1738
+ }
1739
+
1740
+ // Should show error message when file reading fails
1741
+ expect(() => on_did_open_text_document_callback?.(mock_document)).not.toThrow()
1742
+
1743
+ await vi.waitFor(() => {
1744
+ expect(mock_vscode.window.showErrorMessage).toHaveBeenCalledWith(
1745
+ expect.stringContaining(`MatterViz auto-render failed:`),
1746
+ )
1747
+ })
1748
+ })
1749
+ })
1750
+
1751
+ describe(`Multi-frame xyz/extxyz handling`, () => {
1752
+ test(`should correctly identify multi-frame XYZ as trajectory using content`, () => {
1753
+ // Multi-frame XYZ content (2 frames)
1754
+ const multi_frame_xyz_content = `3
1755
+ frame 1
1756
+ H 0.0 0.0 0.0
1757
+ O 0.0 0.0 1.0
1758
+ H 0.0 1.0 0.0
1759
+ 3
1760
+ frame 2
1761
+ H 0.1 0.0 0.0
1762
+ O 0.0 0.1 1.0
1763
+ H 0.0 1.0 0.1`
1764
+
1765
+ // Single-frame XYZ content
1766
+ const single_frame_xyz_content = `3
1767
+ water molecule
1768
+ H 0.0 0.0 0.0
1769
+ O 0.0 0.0 1.0
1770
+ H 0.0 1.0 0.0`
1771
+
1772
+ // Test 1: Verify is_trajectory_file directly detects multi-frame content
1773
+ expect(is_trajectory_file(`multi-frame.xyz`, multi_frame_xyz_content)).toBe(true)
1774
+ expect(is_trajectory_file(`single-frame.xyz`, single_frame_xyz_content)).toBe(false)
1775
+
1776
+ // Test 2: Verify filename-only detection doesn't identify .xyz as trajectory
1777
+ expect(is_trajectory_file(`multi-frame.xyz`)).toBe(false) // filename-only should be false
1778
+ expect(is_trajectory_file(`single-frame.xyz`)).toBe(false) // filename-only should be false
1779
+
1780
+ // Test 3: Test with FileData objects (simulating what infer_view_type receives)
1781
+ const multi_frame_file: FileData = {
1782
+ filename: `multi-frame.xyz`,
1783
+ content: multi_frame_xyz_content,
1784
+ is_base64: false,
1785
+ }
1786
+
1787
+ const single_frame_file: FileData = {
1788
+ filename: `single-frame.xyz`,
1789
+ content: single_frame_xyz_content,
1790
+ is_base64: false,
1791
+ }
1792
+
1793
+ const compressed_file: FileData = {
1794
+ filename: `trajectory.xyz.gz`,
1795
+ content: `base64encodedcontent`,
1796
+ is_base64: true,
1797
+ }
1798
+
1799
+ // Test what infer_view_type logic would do:
1800
+ // For non-compressed files, pass content
1801
+ expect(is_trajectory_file(multi_frame_file.filename, multi_frame_file.content)).toBe(
1802
+ true,
1803
+ )
1804
+ expect(is_trajectory_file(single_frame_file.filename, single_frame_file.content)).toBe(
1805
+ false,
1806
+ )
1807
+
1808
+ // For compressed files, don't pass content (falls back to filename-only)
1809
+ expect(is_trajectory_file(compressed_file.filename)).toBe(true) // .xyz.gz with trajectory keyword is detected as trajectory by filename
1810
+
1811
+ // Test 4: Test webview creation scenario directly with content-based detection
1812
+ const multi_frame_html = create_html(mock_webview, mock_context, {
1813
+ type: is_trajectory_file(multi_frame_file.filename, multi_frame_file.content)
1814
+ ? `trajectory`
1815
+ : `structure`,
1816
+ data: multi_frame_file,
1817
+ theme: `light`,
1818
+ })
1819
+
1820
+ const multi_frame_parsed_data = JSON.parse(
1821
+ /matterviz_data=(\{[\s\S]*?\});/.exec(multi_frame_html)?.[1] ?? `{}`,
1822
+ )
1823
+
1824
+ expect(multi_frame_parsed_data.type).toBe(`trajectory`)
1825
+ expect(multi_frame_parsed_data.data.filename).toBe(`multi-frame.xyz`)
1826
+ expect(multi_frame_parsed_data.data.content).toBe(multi_frame_xyz_content)
1827
+
1828
+ // Test 5: Test single-frame for comparison
1829
+ const single_frame_html = create_html(mock_webview, mock_context, {
1830
+ type: is_trajectory_file(single_frame_file.filename, single_frame_file.content)
1831
+ ? `trajectory`
1832
+ : `structure`,
1833
+ data: single_frame_file,
1834
+ theme: `light`,
1835
+ })
1836
+
1837
+ const single_frame_parsed_data = JSON.parse(
1838
+ /matterviz_data=(\{[\s\S]*?\});/.exec(single_frame_html)?.[1] ?? `{}`,
1839
+ )
1840
+
1841
+ expect(single_frame_parsed_data.type).toBe(`structure`)
1842
+
1843
+ // Test 6: Test compressed file falls back correctly
1844
+ const compressed_html = create_html(mock_webview, mock_context, {
1845
+ type: is_trajectory_file(compressed_file.filename) ? `trajectory` : `structure`,
1846
+ data: compressed_file,
1847
+ theme: `light`,
1848
+ })
1849
+
1850
+ const compressed_parsed_data = JSON.parse(
1851
+ /matterviz_data=(\{[\s\S]*?\});/.exec(compressed_html)?.[1] ?? `{}`,
1852
+ )
1853
+
1854
+ expect(compressed_parsed_data.type).toBe(`trajectory`) // Should be trajectory since filename contains trajectory keyword
1855
+ })
1856
+
1857
+ test(`should handle compressed XYZ files by falling back to filename-only detection`, () => {
1858
+ // Test compressed file (should fall back to filename-only detection)
1859
+ const compressed_file = {
1860
+ filename: `trajectory.xyz.gz`,
1861
+ content: `base64encodedcontent`, // This is binary/compressed
1862
+ is_base64: true,
1863
+ }
1864
+
1865
+ // For compressed files, infer_view_type should fall back to filename-only detection
1866
+ // Since .xyz.gz with trajectory keyword is detected as trajectory by filename, it should be 'trajectory'
1867
+ expect(is_trajectory_file(compressed_file.filename)).toBe(true) // filename-only detection
1868
+
1869
+ // Test the HTML generation scenario
1870
+ const html = create_html(mock_webview, mock_context, {
1871
+ type: is_trajectory_file(compressed_file.filename) ? `trajectory` : `structure`,
1872
+ data: compressed_file,
1873
+ theme: `light`,
1874
+ })
1875
+
1876
+ const parsed_data = JSON.parse(/matterviz_data=(\{[\s\S]*?\});/.exec(html)?.[1] ?? `{}`)
1877
+
1878
+ // Should be 'trajectory' since filename contains trajectory keyword
1879
+ expect(parsed_data.type).toBe(`trajectory`)
1880
+ })
1881
+ })
1882
+
1883
+ describe(`Default Settings`, () => {
1884
+ // Helper to create mock config and test setting
1885
+ const test_setting = (
1886
+ result_path: string,
1887
+ expected_value: unknown,
1888
+ config_key: string,
1889
+ ) => {
1890
+ const parts = config_key.split(`.`)
1891
+
1892
+ const mock_config = {
1893
+ get: vi.fn((key: string, default_val?: unknown): unknown => {
1894
+ if (parts.length === 2 && key === parts[0]) {
1895
+ return { [parts[1]]: expected_value }
1896
+ } else if (parts.length === 1 && key === parts[0]) return expected_value
1897
+ return default_val
1898
+ }),
1899
+ }
1900
+ // @ts-expect-error: Mock type override needed for testing
1901
+ mock_vscode.workspace.getConfiguration.mockReturnValue(mock_config)
1902
+
1903
+ const result = get_defaults()
1904
+ const value = result_path
1905
+ .split(`.`)
1906
+ .reduce(
1907
+ (obj: Record<string, unknown>, key: string) => obj?.[key] as Record<string, unknown>,
1908
+ result,
1909
+ )
1910
+
1911
+ return Array.isArray(expected_value)
1912
+ ? expect(value).toEqual(expected_value)
1913
+ : expect(value).toBe(expected_value)
1914
+ }
1915
+
1916
+ test(`should merge user settings with defaults`, () => {
1917
+ const user_config = {
1918
+ structure: { atom_radius: 1.5, show_bonds: `always`, bond_color: `#ff0000` },
1919
+ trajectory: { auto_play: true },
1920
+ }
1921
+ const mock_config = {
1922
+ get: vi.fn((key: string, default_val?: unknown) => {
1923
+ if (key === `structure`) return user_config.structure
1924
+ if (key === `trajectory`) return user_config.trajectory
1925
+ return default_val
1926
+ }),
1927
+ }
1928
+ // @ts-expect-error: Mock type override needed for testing
1929
+ mock_vscode.workspace.getConfiguration.mockReturnValue(mock_config)
1930
+
1931
+ const result = get_defaults()
1932
+
1933
+ expect(result.structure.atom_radius).toBe(1.5)
1934
+ expect(result.structure.show_bonds).toBe(`always`)
1935
+ expect(result.structure.bond_color).toBe(`#ff0000`)
1936
+ expect(result.trajectory.auto_play).toBe(true)
1937
+ expect(result.structure.same_size_atoms).toBe(false) // Falls back to default
1938
+ })
1939
+
1940
+ test.each([
1941
+ // Numbers
1942
+ [`structure.atom_radius`, 1.5],
1943
+ [`structure.sphere_segments`, 24],
1944
+ [`structure.bond_thickness`, 0.2],
1945
+ [`structure.rotation_damping`, 0.2],
1946
+ [`structure.zoom_speed`, 1.0],
1947
+ [`structure.pan_speed`, 1.0],
1948
+ [`structure.auto_rotate`, 2.0],
1949
+ [`structure.site_label_size`, 14],
1950
+ [`structure.site_label_padding`, 4],
1951
+ [`structure.ambient_light`, 0.6],
1952
+ [`structure.directional_light`, 0.8],
1953
+ [`structure.vector_scale`, 2.0],
1954
+ [`structure.vector_origin_gap`, 0.25],
1955
+ [`structure.cell_edge_opacity`, 0.5],
1956
+ [`structure.cell_surface_opacity`, 0.2],
1957
+ [`background_opacity`, 0.8],
1958
+ [`trajectory.fps`, 10],
1959
+ [`trajectory.step_labels`, 10],
1960
+
1961
+ // Booleans
1962
+ [`structure.same_size_atoms`, true],
1963
+ [`structure.show_atoms`, false],
1964
+ [`structure.show_bonds`, `always`],
1965
+ [`structure.show_site_labels`, true],
1966
+ [`structure.show_cell`, true],
1967
+ [`structure.show_cell_vectors`, true],
1968
+ [`structure.show_image_atoms`, true],
1969
+ [`structure.show_gizmo`, false],
1970
+ [`trajectory.auto_play`, true],
1971
+ [`trajectory.show_controls`, false],
1972
+
1973
+ // Colors (strings)
1974
+ [`structure.bond_color`, `#ff0000`],
1975
+ [`structure.site_label_color`, `#00ff00`],
1976
+ [`structure.site_label_bg_color`, `#333333`],
1977
+ [`structure.cell_edge_color`, `#aaaaaa`],
1978
+ [`structure.cell_surface_color`, `#bbbbbb`],
1979
+ [`structure.vector_color`, `#ffff00`],
1980
+ [`background_color`, `#111111`],
1981
+
1982
+ // String enums
1983
+ [`structure.bonding_strategy`, `solid_angle`],
1984
+ [`structure.camera_projection`, `orthographic`],
1985
+ [`color_scheme`, `Jmol`],
1986
+ [`composition.color_scheme`, `Alloy`],
1987
+ [`trajectory.display_mode`, `scatter`],
1988
+ [`trajectory.layout`, `vertical`],
1989
+ [`composition.display_mode`, `bar`],
1990
+
1991
+ // Arrays
1992
+ [`structure.camera_position`, [1, 2, 3]],
1993
+ [`structure.site_label_offset`, [0.5, 1.0, 0]],
1994
+ [`trajectory.fps_range`, [0.5, 60]],
1995
+ ])(`should handle setting: %s = %s`, (result_path, expected_value) => {
1996
+ test_setting(result_path, expected_value, result_path)
1997
+ })
1998
+
1999
+ test.each([
2000
+ [{ get: vi.fn(() => undefined) }, `missing config`],
2001
+ [
2002
+ {
2003
+ get: vi.fn((key: string, default_val?: unknown) =>
2004
+ key === `defaults`
2005
+ ? {
2006
+ structure: {
2007
+ atom_radius: `invalid`,
2008
+ show_bonds: `invalid-value`,
2009
+ bond_color: 123,
2010
+ },
2011
+ }
2012
+ : default_val,
2013
+ ),
2014
+ },
2015
+ `invalid values`,
2016
+ ],
2017
+ ])(`should handle %s gracefully`, (mock_config, _description) => {
2018
+ // @ts-expect-error: Mock type override needed for testing
2019
+ mock_vscode.workspace.getConfiguration.mockReturnValue(mock_config)
2020
+
2021
+ expect(() => get_defaults()).not.toThrow()
2022
+ const result = get_defaults()
2023
+
2024
+ expect(result).toEqual(
2025
+ expect.objectContaining({
2026
+ structure: expect.any(Object),
2027
+ trajectory: expect.any(Object),
2028
+ composition: expect.any(Object),
2029
+ }),
2030
+ )
2031
+ })
2032
+
2033
+ test(`should handle workspace config errors`, () => {
2034
+ mock_vscode.workspace.getConfiguration.mockImplementation(() => {
2035
+ throw new Error(`Config access failed`)
2036
+ })
2037
+
2038
+ expect(() => get_defaults()).not.toThrow()
2039
+ })
2040
+ })
2041
+ })