@morscherlab/mint-sdk 1.0.0-alpha.2

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 (491) hide show
  1. package/README.md +326 -0
  2. package/dist/__stories__/experiment-helpers.d.ts +25 -0
  3. package/dist/__tests__/components/AppLayout.test.d.ts +1 -0
  4. package/dist/__tests__/components/AppSidebar.test.d.ts +1 -0
  5. package/dist/__tests__/components/AppTopBar.test.d.ts +1 -0
  6. package/dist/__tests__/components/BaseInput.test.d.ts +1 -0
  7. package/dist/__tests__/components/BasePill.test.d.ts +1 -0
  8. package/dist/__tests__/components/Calendar.test.d.ts +1 -0
  9. package/dist/__tests__/components/CollapsibleCard.test.d.ts +1 -0
  10. package/dist/__tests__/components/DataFrame.test.d.ts +1 -0
  11. package/dist/__tests__/components/DropdownButton.test.d.ts +1 -0
  12. package/dist/__tests__/composables/formBuilderRegistry.test.d.ts +1 -0
  13. package/dist/__tests__/composables/useAppExperiment.test.d.ts +1 -0
  14. package/dist/__tests__/composables/useAuth.test.d.ts +1 -0
  15. package/dist/__tests__/composables/useAutoGroup.test.d.ts +1 -0
  16. package/dist/__tests__/composables/useExperimentData.test.d.ts +13 -0
  17. package/dist/__tests__/composables/useExperimentSave.test.d.ts +1 -0
  18. package/dist/__tests__/composables/useForm.test.d.ts +1 -0
  19. package/dist/__tests__/composables/useFormBuilder.test.d.ts +1 -0
  20. package/dist/__tests__/composables/usePlatformContext.test.d.ts +1 -0
  21. package/dist/__tests__/composables/usePluginApi.test.d.ts +13 -0
  22. package/dist/__tests__/composables/usePluginConfig.test.d.ts +14 -0
  23. package/dist/__tests__/utils/color.test.d.ts +1 -0
  24. package/dist/auth-BYmxZdJl.js +297 -0
  25. package/dist/auth-BYmxZdJl.js.map +1 -0
  26. package/dist/components/AlertBox.vue.d.ts +34 -0
  27. package/dist/components/AppAvatarMenu.vue.d.ts +58 -0
  28. package/dist/components/AppContainer.vue.d.ts +28 -0
  29. package/dist/components/AppLayout.vue.d.ts +31 -0
  30. package/dist/components/AppPageSelector.vue.d.ts +43 -0
  31. package/dist/components/AppPillNav.vue.d.ts +11 -0
  32. package/dist/components/AppPluginSwitcher.vue.d.ts +38 -0
  33. package/dist/components/AppSidebar.vue.d.ts +47 -0
  34. package/dist/components/AppTopBar.vue.d.ts +111 -0
  35. package/dist/components/AuditTrail.vue.d.ts +38 -0
  36. package/dist/components/AutoGroupModal.vue.d.ts +124 -0
  37. package/dist/components/Avatar.vue.d.ts +14 -0
  38. package/dist/components/BaseButton.vue.d.ts +37 -0
  39. package/dist/components/BaseCheckbox.vue.d.ts +17 -0
  40. package/dist/components/BaseInput.vue.d.ts +34 -0
  41. package/dist/components/BaseModal.vue.d.ts +46 -0
  42. package/dist/components/BasePill.vue.d.ts +57 -0
  43. package/dist/components/BaseRadioGroup.vue.d.ts +21 -0
  44. package/dist/components/BaseSelect.vue.d.ts +20 -0
  45. package/dist/components/BaseSlider.vue.d.ts +22 -0
  46. package/dist/components/BaseTabs.vue.d.ts +14 -0
  47. package/dist/components/BaseTextarea.vue.d.ts +30 -0
  48. package/dist/components/BaseToggle.vue.d.ts +19 -0
  49. package/dist/components/BatchProgressList.vue.d.ts +43 -0
  50. package/dist/components/Breadcrumb.vue.d.ts +33 -0
  51. package/dist/components/Calendar.vue.d.ts +107 -0
  52. package/dist/components/ChartContainer.vue.d.ts +31 -0
  53. package/dist/components/ChemicalFormula.vue.d.ts +8 -0
  54. package/dist/components/CollapsibleCard.vue.d.ts +41 -0
  55. package/dist/components/ColorSlider.vue.d.ts +34 -0
  56. package/dist/components/ConcentrationInput.vue.d.ts +25 -0
  57. package/dist/components/ConfirmDialog.vue.d.ts +42 -0
  58. package/dist/components/DataFrame.vue.d.ts +107 -0
  59. package/dist/components/DatePicker.vue.d.ts +25 -0
  60. package/dist/components/DateTimePicker.vue.d.ts +30 -0
  61. package/dist/components/Divider.vue.d.ts +14 -0
  62. package/dist/components/DoseCalculator.vue.d.ts +19 -0
  63. package/dist/components/DropdownButton.vue.d.ts +47 -0
  64. package/dist/components/EmptyState.vue.d.ts +36 -0
  65. package/dist/components/ExperimentCodeBadge.vue.d.ts +14 -0
  66. package/dist/components/ExperimentDataViewer.vue.d.ts +29 -0
  67. package/dist/components/ExperimentPopover.vue.d.ts +32 -0
  68. package/dist/components/ExperimentSelectorModal.vue.d.ts +28 -0
  69. package/dist/components/ExperimentTimeline.vue.d.ts +44 -0
  70. package/dist/components/FileUploader.vue.d.ts +40 -0
  71. package/dist/components/FitPanel.vue.d.ts +46 -0
  72. package/dist/components/FormActions.vue.d.ts +33 -0
  73. package/dist/components/FormBuilder.vue.d.ts +287 -0
  74. package/dist/components/FormField.vue.d.ts +28 -0
  75. package/dist/components/FormFieldRenderer.vue.d.ts +31 -0
  76. package/dist/components/FormSection.vue.d.ts +43 -0
  77. package/dist/components/FormulaInput.vue.d.ts +25 -0
  78. package/dist/components/GroupAssigner.vue.d.ts +25 -0
  79. package/dist/components/GroupingModal.vue.d.ts +12 -0
  80. package/dist/components/IconButton.vue.d.ts +34 -0
  81. package/dist/components/LoadingSpinner.vue.d.ts +12 -0
  82. package/dist/components/MoleculeInput.vue.d.ts +27 -0
  83. package/dist/components/MultiSelect.vue.d.ts +19 -0
  84. package/dist/components/NumberInput.vue.d.ts +22 -0
  85. package/dist/components/PlateMapEditor.vue.d.ts +50 -0
  86. package/dist/components/ProgressBar.vue.d.ts +23 -0
  87. package/dist/components/ProtocolStepEditor.vue.d.ts +24 -0
  88. package/dist/components/RackEditor.vue.d.ts +40 -0
  89. package/dist/components/ReagentEditor.vue.d.ts +30 -0
  90. package/dist/components/ReagentList.vue.d.ts +32 -0
  91. package/dist/components/ResourceCard.vue.d.ts +50 -0
  92. package/dist/components/SampleHierarchyTree.vue.d.ts +26 -0
  93. package/dist/components/SampleLegend.vue.d.ts +32 -0
  94. package/dist/components/SampleSelector.vue.d.ts +29 -0
  95. package/dist/components/ScheduleCalendar.vue.d.ts +110 -0
  96. package/dist/components/ScientificNumber.vue.d.ts +14 -0
  97. package/dist/components/SegmentedControl.vue.d.ts +20 -0
  98. package/dist/components/SequenceInput.vue.d.ts +54 -0
  99. package/dist/components/SettingsButton.vue.d.ts +30 -0
  100. package/dist/components/SettingsModal.vue.d.ts +36 -0
  101. package/dist/components/Skeleton.vue.d.ts +11 -0
  102. package/dist/components/StatusIndicator.vue.d.ts +13 -0
  103. package/dist/components/StepWizard.vue.d.ts +65 -0
  104. package/dist/components/TagsInput.vue.d.ts +39 -0
  105. package/dist/components/ThemeToggle.vue.d.ts +7 -0
  106. package/dist/components/TimePicker.vue.d.ts +29 -0
  107. package/dist/components/TimeRangeInput.vue.d.ts +27 -0
  108. package/dist/components/ToastNotification.vue.d.ts +2 -0
  109. package/dist/components/Tooltip.vue.d.ts +35 -0
  110. package/dist/components/UnitInput.vue.d.ts +39 -0
  111. package/dist/components/WellEditPopup.vue.d.ts +25 -0
  112. package/dist/components/WellPlate.vue.d.ts +73 -0
  113. package/dist/components/index.d.ts +87 -0
  114. package/dist/components/index.js +3 -0
  115. package/dist/components-CKf-UpGi.js +15089 -0
  116. package/dist/components-CKf-UpGi.js.map +1 -0
  117. package/dist/composables/experiment-utils.d.ts +8 -0
  118. package/dist/composables/formBuilderRegistry.d.ts +13 -0
  119. package/dist/composables/index.d.ts +28 -0
  120. package/dist/composables/index.js +3 -0
  121. package/dist/composables/useApi.d.ts +20 -0
  122. package/dist/composables/useAppExperiment.d.ts +37 -0
  123. package/dist/composables/useAsync.d.ts +128 -0
  124. package/dist/composables/useAuth.d.ts +47 -0
  125. package/dist/composables/useAutoGroup.d.ts +106 -0
  126. package/dist/composables/useChemicalFormula.d.ts +21 -0
  127. package/dist/composables/useConcentrationUnits.d.ts +29 -0
  128. package/dist/composables/useDoseCalculator.d.ts +58 -0
  129. package/dist/composables/useExperimentData.d.ts +18 -0
  130. package/dist/composables/useExperimentSave.d.ts +36 -0
  131. package/dist/composables/useExperimentSelector.d.ts +30 -0
  132. package/dist/composables/useForm.d.ts +92 -0
  133. package/dist/composables/useFormBuilder.d.ts +24 -0
  134. package/dist/composables/usePasskey.d.ts +10 -0
  135. package/dist/composables/usePlatformContext.d.ts +131 -0
  136. package/dist/composables/usePluginApi.d.ts +29 -0
  137. package/dist/composables/usePluginConfig.d.ts +13 -0
  138. package/dist/composables/useProtocolTemplates.d.ts +44 -0
  139. package/dist/composables/useRackEditor.d.ts +31 -0
  140. package/dist/composables/useReagentSeries.d.ts +23 -0
  141. package/dist/composables/useScheduleDrag.d.ts +78 -0
  142. package/dist/composables/useSequenceUtils.d.ts +14 -0
  143. package/dist/composables/useTheme.d.ts +8 -0
  144. package/dist/composables/useTimeUtils.d.ts +29 -0
  145. package/dist/composables/useToast.d.ts +22 -0
  146. package/dist/composables/useWellPlateEditor.d.ts +33 -0
  147. package/dist/composables-D0QfFzq1.js +805 -0
  148. package/dist/composables-D0QfFzq1.js.map +1 -0
  149. package/dist/histoire.setup.d.ts +1 -0
  150. package/dist/index.d.ts +6 -0
  151. package/dist/index.js +7 -0
  152. package/dist/install.d.ts +16 -0
  153. package/dist/install.js +23 -0
  154. package/dist/install.js.map +1 -0
  155. package/dist/stores/auth.d.ts +146 -0
  156. package/dist/stores/index.d.ts +2 -0
  157. package/dist/stores/index.js +2 -0
  158. package/dist/stores/settings.d.ts +75 -0
  159. package/dist/styles.css +29728 -0
  160. package/dist/tailwind.preset.d.ts +58 -0
  161. package/dist/tailwind.preset.js +66 -0
  162. package/dist/tailwind.preset.js.map +1 -0
  163. package/dist/types/auth.d.ts +42 -0
  164. package/dist/types/auto-group.d.ts +34 -0
  165. package/dist/types/components.d.ts +528 -0
  166. package/dist/types/form-builder.d.ts +167 -0
  167. package/dist/types/index.d.ts +5 -0
  168. package/dist/types/index.js +0 -0
  169. package/dist/types/platform.d.ts +75 -0
  170. package/dist/useScheduleDrag-DAJueTbK.js +7181 -0
  171. package/dist/useScheduleDrag-DAJueTbK.js.map +1 -0
  172. package/dist/utils/color.d.ts +24 -0
  173. package/package.json +114 -0
  174. package/src/__stories__/experiment-helpers.ts +83 -0
  175. package/src/__tests__/components/AppLayout.test.ts +163 -0
  176. package/src/__tests__/components/AppSidebar.test.ts +292 -0
  177. package/src/__tests__/components/AppTopBar.test.ts +683 -0
  178. package/src/__tests__/components/BaseInput.test.ts +99 -0
  179. package/src/__tests__/components/BasePill.test.ts +291 -0
  180. package/src/__tests__/components/Calendar.test.ts +566 -0
  181. package/src/__tests__/components/CollapsibleCard.test.ts +524 -0
  182. package/src/__tests__/components/DataFrame.test.ts +767 -0
  183. package/src/__tests__/components/DropdownButton.test.ts +471 -0
  184. package/src/__tests__/composables/formBuilderRegistry.test.ts +187 -0
  185. package/src/__tests__/composables/useAppExperiment.test.ts +560 -0
  186. package/src/__tests__/composables/useAuth.test.ts +188 -0
  187. package/src/__tests__/composables/useAutoGroup.test.ts +860 -0
  188. package/src/__tests__/composables/useExperimentData.test.ts +127 -0
  189. package/src/__tests__/composables/useExperimentSave.test.ts +347 -0
  190. package/src/__tests__/composables/useForm.test.ts +205 -0
  191. package/src/__tests__/composables/useFormBuilder.test.ts +917 -0
  192. package/src/__tests__/composables/usePlatformContext.test.ts +116 -0
  193. package/src/__tests__/composables/usePluginApi.test.ts +81 -0
  194. package/src/__tests__/composables/usePluginConfig.test.ts +176 -0
  195. package/src/__tests__/utils/color.test.ts +96 -0
  196. package/src/components/AlertBox.story.vue +204 -0
  197. package/src/components/AlertBox.vue +88 -0
  198. package/src/components/AppAvatarMenu.story.vue +155 -0
  199. package/src/components/AppAvatarMenu.vue +184 -0
  200. package/src/components/AppContainer.story.vue +104 -0
  201. package/src/components/AppContainer.vue +34 -0
  202. package/src/components/AppLayout.story.vue +292 -0
  203. package/src/components/AppLayout.vue +75 -0
  204. package/src/components/AppPageSelector.vue +159 -0
  205. package/src/components/AppPillNav.vue +66 -0
  206. package/src/components/AppPluginSwitcher.vue +241 -0
  207. package/src/components/AppSidebar.story.vue +309 -0
  208. package/src/components/AppSidebar.vue +119 -0
  209. package/src/components/AppTopBar.story.vue +304 -0
  210. package/src/components/AppTopBar.vue +661 -0
  211. package/src/components/AuditTrail.story.vue +163 -0
  212. package/src/components/AuditTrail.vue +151 -0
  213. package/src/components/AutoGroupModal.story.vue +273 -0
  214. package/src/components/AutoGroupModal.vue +566 -0
  215. package/src/components/Avatar.story.vue +115 -0
  216. package/src/components/Avatar.vue +79 -0
  217. package/src/components/BaseButton.story.vue +96 -0
  218. package/src/components/BaseButton.vue +73 -0
  219. package/src/components/BaseCheckbox.story.vue +73 -0
  220. package/src/components/BaseCheckbox.vue +69 -0
  221. package/src/components/BaseInput.story.vue +98 -0
  222. package/src/components/BaseInput.vue +74 -0
  223. package/src/components/BaseModal.story.vue +237 -0
  224. package/src/components/BaseModal.vue +182 -0
  225. package/src/components/BasePill.story.vue +142 -0
  226. package/src/components/BasePill.vue +89 -0
  227. package/src/components/BaseRadioGroup.story.vue +145 -0
  228. package/src/components/BaseRadioGroup.vue +124 -0
  229. package/src/components/BaseSelect.story.vue +120 -0
  230. package/src/components/BaseSelect.vue +71 -0
  231. package/src/components/BaseSlider.story.vue +122 -0
  232. package/src/components/BaseSlider.vue +126 -0
  233. package/src/components/BaseTabs.story.vue +127 -0
  234. package/src/components/BaseTabs.vue +59 -0
  235. package/src/components/BaseTextarea.story.vue +91 -0
  236. package/src/components/BaseTextarea.vue +62 -0
  237. package/src/components/BaseToggle.story.vue +81 -0
  238. package/src/components/BaseToggle.vue +76 -0
  239. package/src/components/BatchProgressList.story.vue +92 -0
  240. package/src/components/BatchProgressList.vue +184 -0
  241. package/src/components/Breadcrumb.story.vue +106 -0
  242. package/src/components/Breadcrumb.vue +75 -0
  243. package/src/components/Calendar.story.vue +106 -0
  244. package/src/components/Calendar.vue +363 -0
  245. package/src/components/ChartContainer.story.vue +113 -0
  246. package/src/components/ChartContainer.vue +64 -0
  247. package/src/components/ChemicalFormula.story.vue +102 -0
  248. package/src/components/ChemicalFormula.vue +39 -0
  249. package/src/components/CollapsibleCard.story.vue +135 -0
  250. package/src/components/CollapsibleCard.vue +167 -0
  251. package/src/components/ColorSlider.story.vue +120 -0
  252. package/src/components/ColorSlider.vue +164 -0
  253. package/src/components/ConcentrationInput.story.vue +77 -0
  254. package/src/components/ConcentrationInput.vue +185 -0
  255. package/src/components/ConfirmDialog.story.vue +248 -0
  256. package/src/components/ConfirmDialog.vue +93 -0
  257. package/src/components/DataFrame.story.vue +148 -0
  258. package/src/components/DataFrame.vue +419 -0
  259. package/src/components/DatePicker.story.vue +119 -0
  260. package/src/components/DatePicker.vue +330 -0
  261. package/src/components/DateTimePicker.story.vue +112 -0
  262. package/src/components/DateTimePicker.vue +392 -0
  263. package/src/components/Divider.story.vue +80 -0
  264. package/src/components/Divider.vue +49 -0
  265. package/src/components/DoseCalculator.story.vue +68 -0
  266. package/src/components/DoseCalculator.vue +476 -0
  267. package/src/components/DropdownButton.story.vue +102 -0
  268. package/src/components/DropdownButton.vue +181 -0
  269. package/src/components/EmptyState.story.vue +135 -0
  270. package/src/components/EmptyState.vue +69 -0
  271. package/src/components/ExperimentCodeBadge.story.vue +77 -0
  272. package/src/components/ExperimentCodeBadge.vue +64 -0
  273. package/src/components/ExperimentDataViewer.story.vue +174 -0
  274. package/src/components/ExperimentDataViewer.vue +288 -0
  275. package/src/components/ExperimentPopover.story.vue +384 -0
  276. package/src/components/ExperimentPopover.vue +241 -0
  277. package/src/components/ExperimentSelectorModal.story.vue +391 -0
  278. package/src/components/ExperimentSelectorModal.vue +387 -0
  279. package/src/components/ExperimentTimeline.story.vue +161 -0
  280. package/src/components/ExperimentTimeline.vue +382 -0
  281. package/src/components/FileUploader.story.vue +107 -0
  282. package/src/components/FileUploader.vue +386 -0
  283. package/src/components/FitPanel.story.vue +125 -0
  284. package/src/components/FitPanel.vue +120 -0
  285. package/src/components/FormActions.vue +92 -0
  286. package/src/components/FormBuilder.vue +214 -0
  287. package/src/components/FormField.story.vue +132 -0
  288. package/src/components/FormField.vue +59 -0
  289. package/src/components/FormFieldRenderer.vue +58 -0
  290. package/src/components/FormSection.vue +90 -0
  291. package/src/components/FormulaInput.story.vue +96 -0
  292. package/src/components/FormulaInput.vue +125 -0
  293. package/src/components/GroupAssigner.story.vue +83 -0
  294. package/src/components/GroupAssigner.vue +284 -0
  295. package/src/components/GroupingModal.story.vue +52 -0
  296. package/src/components/GroupingModal.vue +422 -0
  297. package/src/components/IconButton.story.vue +135 -0
  298. package/src/components/IconButton.vue +73 -0
  299. package/src/components/LoadingSpinner.story.vue +70 -0
  300. package/src/components/LoadingSpinner.vue +50 -0
  301. package/src/components/MoleculeInput.story.vue +66 -0
  302. package/src/components/MoleculeInput.vue +426 -0
  303. package/src/components/MultiSelect.story.vue +132 -0
  304. package/src/components/MultiSelect.vue +118 -0
  305. package/src/components/NumberInput.story.vue +122 -0
  306. package/src/components/NumberInput.vue +160 -0
  307. package/src/components/PlateMapEditor.story.vue +92 -0
  308. package/src/components/PlateMapEditor.vue +513 -0
  309. package/src/components/ProgressBar.story.vue +148 -0
  310. package/src/components/ProgressBar.vue +114 -0
  311. package/src/components/ProtocolStepEditor.story.vue +69 -0
  312. package/src/components/ProtocolStepEditor.vue +522 -0
  313. package/src/components/RackEditor.story.vue +100 -0
  314. package/src/components/RackEditor.vue +371 -0
  315. package/src/components/ReagentEditor.story.vue +153 -0
  316. package/src/components/ReagentEditor.vue +418 -0
  317. package/src/components/ReagentList.story.vue +137 -0
  318. package/src/components/ReagentList.vue +463 -0
  319. package/src/components/ResourceCard.story.vue +150 -0
  320. package/src/components/ResourceCard.vue +161 -0
  321. package/src/components/SampleHierarchyTree.story.vue +161 -0
  322. package/src/components/SampleHierarchyTree.vue +256 -0
  323. package/src/components/SampleLegend.story.vue +91 -0
  324. package/src/components/SampleLegend.vue +119 -0
  325. package/src/components/SampleSelector.story.vue +111 -0
  326. package/src/components/SampleSelector.vue +1033 -0
  327. package/src/components/ScheduleCalendar.story.vue +195 -0
  328. package/src/components/ScheduleCalendar.vue +569 -0
  329. package/src/components/ScientificNumber.story.vue +127 -0
  330. package/src/components/ScientificNumber.vue +197 -0
  331. package/src/components/SegmentedControl.story.vue +132 -0
  332. package/src/components/SegmentedControl.vue +79 -0
  333. package/src/components/SequenceInput.story.vue +119 -0
  334. package/src/components/SequenceInput.vue +209 -0
  335. package/src/components/SettingsButton.story.vue +58 -0
  336. package/src/components/SettingsButton.vue +76 -0
  337. package/src/components/SettingsModal.story.vue +145 -0
  338. package/src/components/SettingsModal.vue +146 -0
  339. package/src/components/Skeleton.story.vue +141 -0
  340. package/src/components/Skeleton.vue +74 -0
  341. package/src/components/StatusIndicator.story.vue +99 -0
  342. package/src/components/StatusIndicator.vue +40 -0
  343. package/src/components/StepWizard.story.vue +155 -0
  344. package/src/components/StepWizard.vue +223 -0
  345. package/src/components/TagsInput.story.vue +155 -0
  346. package/src/components/TagsInput.vue +265 -0
  347. package/src/components/ThemeToggle.story.vue +36 -0
  348. package/src/components/ThemeToggle.vue +54 -0
  349. package/src/components/TimePicker.story.vue +96 -0
  350. package/src/components/TimePicker.vue +273 -0
  351. package/src/components/TimeRangeInput.story.vue +104 -0
  352. package/src/components/TimeRangeInput.vue +122 -0
  353. package/src/components/ToastNotification.story.vue +157 -0
  354. package/src/components/ToastNotification.vue +62 -0
  355. package/src/components/Tooltip.story.vue +138 -0
  356. package/src/components/Tooltip.vue +119 -0
  357. package/src/components/UnitInput.story.vue +194 -0
  358. package/src/components/UnitInput.vue +213 -0
  359. package/src/components/WellEditPopup.vue +234 -0
  360. package/src/components/WellPlate.story.vue +282 -0
  361. package/src/components/WellPlate.vue +830 -0
  362. package/src/components/index.ts +118 -0
  363. package/src/composables/experiment-utils.ts +57 -0
  364. package/src/composables/formBuilderRegistry.ts +79 -0
  365. package/src/composables/index.ts +140 -0
  366. package/src/composables/useApi.ts +167 -0
  367. package/src/composables/useAppExperiment.ts +159 -0
  368. package/src/composables/useAsync.ts +323 -0
  369. package/src/composables/useAuth.ts +445 -0
  370. package/src/composables/useAutoGroup.ts +641 -0
  371. package/src/composables/useChemicalFormula.ts +275 -0
  372. package/src/composables/useConcentrationUnits.ts +246 -0
  373. package/src/composables/useDoseCalculator.ts +370 -0
  374. package/src/composables/useExperimentData.ts +86 -0
  375. package/src/composables/useExperimentSave.ts +192 -0
  376. package/src/composables/useExperimentSelector.ts +292 -0
  377. package/src/composables/useForm.ts +416 -0
  378. package/src/composables/useFormBuilder.ts +383 -0
  379. package/src/composables/usePasskey.ts +216 -0
  380. package/src/composables/usePlatformContext.ts +299 -0
  381. package/src/composables/usePluginApi.ts +39 -0
  382. package/src/composables/usePluginConfig.ts +93 -0
  383. package/src/composables/useProtocolTemplates.ts +518 -0
  384. package/src/composables/useRackEditor.ts +222 -0
  385. package/src/composables/useReagentSeries.ts +91 -0
  386. package/src/composables/useScheduleDrag.ts +245 -0
  387. package/src/composables/useSequenceUtils.ts +105 -0
  388. package/src/composables/useTheme.ts +58 -0
  389. package/src/composables/useTimeUtils.ts +131 -0
  390. package/src/composables/useToast.ts +40 -0
  391. package/src/composables/useWellPlateEditor.ts +421 -0
  392. package/src/histoire.setup.ts +17 -0
  393. package/src/index.ts +367 -0
  394. package/src/install.ts +32 -0
  395. package/src/stores/auth.ts +152 -0
  396. package/src/stores/index.ts +2 -0
  397. package/src/stores/settings.ts +218 -0
  398. package/src/styles/components/alert-box.css +150 -0
  399. package/src/styles/components/app-avatar-menu.css +155 -0
  400. package/src/styles/components/app-container.css +33 -0
  401. package/src/styles/components/app-layout.css +98 -0
  402. package/src/styles/components/app-page-selector.css +191 -0
  403. package/src/styles/components/app-pill-nav.css +57 -0
  404. package/src/styles/components/app-plugin-switcher.css +209 -0
  405. package/src/styles/components/app-sidebar.css +145 -0
  406. package/src/styles/components/app-top-bar.css +492 -0
  407. package/src/styles/components/audit-trail.css +143 -0
  408. package/src/styles/components/auto-group-modal.css +644 -0
  409. package/src/styles/components/avatar.css +73 -0
  410. package/src/styles/components/batch-progress-list.css +196 -0
  411. package/src/styles/components/breadcrumb.css +64 -0
  412. package/src/styles/components/button.css +188 -0
  413. package/src/styles/components/calendar.css +192 -0
  414. package/src/styles/components/chart-container.css +69 -0
  415. package/src/styles/components/checkbox.css +123 -0
  416. package/src/styles/components/chemical-formula.css +46 -0
  417. package/src/styles/components/collapsible-card.css +253 -0
  418. package/src/styles/components/color-slider.css +110 -0
  419. package/src/styles/components/concentration-input.css +156 -0
  420. package/src/styles/components/confirm-dialog.css +183 -0
  421. package/src/styles/components/dataframe.css +382 -0
  422. package/src/styles/components/date-picker.css +243 -0
  423. package/src/styles/components/datetime-picker.css +229 -0
  424. package/src/styles/components/divider.css +63 -0
  425. package/src/styles/components/dose-calculator.css +301 -0
  426. package/src/styles/components/dropdown-button.css +280 -0
  427. package/src/styles/components/empty-state.css +151 -0
  428. package/src/styles/components/experiment-code-badge.css +33 -0
  429. package/src/styles/components/experiment-data-viewer.css +138 -0
  430. package/src/styles/components/experiment-popover.css +562 -0
  431. package/src/styles/components/experiment-selector-modal.css +285 -0
  432. package/src/styles/components/experiment-timeline.css +529 -0
  433. package/src/styles/components/file-uploader.css +310 -0
  434. package/src/styles/components/fit-panel.css +67 -0
  435. package/src/styles/components/form-builder.css +69 -0
  436. package/src/styles/components/form-field.css +48 -0
  437. package/src/styles/components/formula-input.css +103 -0
  438. package/src/styles/components/group-assigner.css +200 -0
  439. package/src/styles/components/grouping-modal.css +323 -0
  440. package/src/styles/components/icon-button.css +192 -0
  441. package/src/styles/components/input.css +66 -0
  442. package/src/styles/components/loading-spinner.css +67 -0
  443. package/src/styles/components/modal.css +350 -0
  444. package/src/styles/components/molecule-input.css +186 -0
  445. package/src/styles/components/multi-select.css +131 -0
  446. package/src/styles/components/number-input.css +199 -0
  447. package/src/styles/components/pill.css +188 -0
  448. package/src/styles/components/plate-map-editor.css +464 -0
  449. package/src/styles/components/progress-bar.css +133 -0
  450. package/src/styles/components/protocol-step-editor.css +449 -0
  451. package/src/styles/components/rack-editor.css +265 -0
  452. package/src/styles/components/radio-group.css +240 -0
  453. package/src/styles/components/reagent-editor.css +510 -0
  454. package/src/styles/components/reagent-list.css +407 -0
  455. package/src/styles/components/resource-card.css +360 -0
  456. package/src/styles/components/sample-hierarchy-tree.css +314 -0
  457. package/src/styles/components/sample-legend.css +201 -0
  458. package/src/styles/components/sample-selector.css +751 -0
  459. package/src/styles/components/schedule-calendar.css +478 -0
  460. package/src/styles/components/scientific-number.css +63 -0
  461. package/src/styles/components/segmented-control.css +197 -0
  462. package/src/styles/components/select.css +77 -0
  463. package/src/styles/components/sequence-input.css +184 -0
  464. package/src/styles/components/settings-button.css +94 -0
  465. package/src/styles/components/settings-modal.css +95 -0
  466. package/src/styles/components/skeleton.css +49 -0
  467. package/src/styles/components/slider.css +74 -0
  468. package/src/styles/components/status-indicator.css +66 -0
  469. package/src/styles/components/step-wizard.css +192 -0
  470. package/src/styles/components/tabs.css +95 -0
  471. package/src/styles/components/tags-input.css +195 -0
  472. package/src/styles/components/textarea.css +82 -0
  473. package/src/styles/components/theme-toggle.css +69 -0
  474. package/src/styles/components/time-picker.css +171 -0
  475. package/src/styles/components/time-range-input.css +42 -0
  476. package/src/styles/components/toast.css +91 -0
  477. package/src/styles/components/toggle.css +146 -0
  478. package/src/styles/components/tooltip.css +91 -0
  479. package/src/styles/components/unit-input.css +123 -0
  480. package/src/styles/components/well-edit-popup.css +252 -0
  481. package/src/styles/components/well-plate.css +307 -0
  482. package/src/styles/index.css +87 -0
  483. package/src/styles/variables.css +1117 -0
  484. package/src/tailwind.preset.ts +61 -0
  485. package/src/types/auth.ts +55 -0
  486. package/src/types/auto-group.ts +40 -0
  487. package/src/types/components.ts +710 -0
  488. package/src/types/form-builder.ts +197 -0
  489. package/src/types/index.ts +207 -0
  490. package/src/types/platform.ts +116 -0
  491. package/src/utils/color.ts +96 -0
@@ -0,0 +1,767 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { mount } from '@vue/test-utils'
3
+ import DataFrame from '../../components/DataFrame.vue'
4
+ import type { DataFrameColumn, SortState, PaginationState } from '../../types'
5
+
6
+ describe('DataFrame', () => {
7
+ const mockData = [
8
+ { id: 1, name: 'Alice', age: 30, email: 'alice@example.com' },
9
+ { id: 2, name: 'Bob', age: 25, email: 'bob@example.com' },
10
+ { id: 3, name: 'Charlie', age: 35, email: 'charlie@example.com' },
11
+ ]
12
+
13
+ const mockColumns: DataFrameColumn[] = [
14
+ { key: 'name', label: 'Name' },
15
+ { key: 'age', label: 'Age', align: 'right' },
16
+ { key: 'email', label: 'Email' },
17
+ ]
18
+
19
+ describe('rendering with default props', () => {
20
+ it('should render table container', () => {
21
+ const wrapper = mount(DataFrame, {
22
+ props: { data: mockData, columns: mockColumns },
23
+ })
24
+ expect(wrapper.find('.mld-dataframe').exists()).toBe(true)
25
+ expect(wrapper.find('.mld-dataframe__table').exists()).toBe(true)
26
+ })
27
+
28
+ it('should render column headers', () => {
29
+ const wrapper = mount(DataFrame, {
30
+ props: { data: mockData, columns: mockColumns },
31
+ })
32
+ const headers = wrapper.findAll('.mld-dataframe__th')
33
+ expect(headers).toHaveLength(3)
34
+ expect(headers[0].text()).toBe('Name')
35
+ expect(headers[1].text()).toBe('Age')
36
+ expect(headers[2].text()).toBe('Email')
37
+ })
38
+
39
+ it('should render data rows', () => {
40
+ const wrapper = mount(DataFrame, {
41
+ props: { data: mockData, columns: mockColumns },
42
+ })
43
+ const rows = wrapper.findAll('.mld-dataframe__row')
44
+ expect(rows).toHaveLength(3)
45
+ })
46
+
47
+ it('should render cell values', () => {
48
+ const wrapper = mount(DataFrame, {
49
+ props: { data: mockData, columns: mockColumns },
50
+ })
51
+ const firstRow = wrapper.findAll('.mld-dataframe__row')[0]
52
+ const cells = firstRow.findAll('.mld-dataframe__td')
53
+ expect(cells[0].text()).toBe('Alice')
54
+ expect(cells[1].text()).toBe('30')
55
+ expect(cells[2].text()).toBe('alice@example.com')
56
+ })
57
+
58
+ it('should apply bordered class by default', () => {
59
+ const wrapper = mount(DataFrame, {
60
+ props: { data: mockData, columns: mockColumns },
61
+ })
62
+ expect(wrapper.find('.mld-dataframe--bordered').exists()).toBe(true)
63
+ })
64
+
65
+ it('should apply striped rows by default', () => {
66
+ const wrapper = mount(DataFrame, {
67
+ props: { data: mockData, columns: mockColumns },
68
+ })
69
+ const rows = wrapper.findAll('.mld-dataframe__row')
70
+ expect(rows[1].classes()).toContain('mld-dataframe__row--striped')
71
+ })
72
+ })
73
+
74
+ describe('column configuration', () => {
75
+ it('should apply column alignment', () => {
76
+ const wrapper = mount(DataFrame, {
77
+ props: { data: mockData, columns: mockColumns },
78
+ })
79
+ const ageHeader = wrapper.findAll('.mld-dataframe__th')[1]
80
+ expect(ageHeader.classes()).toContain('mld-dataframe__th--align-right')
81
+ })
82
+
83
+ it('should apply column width', () => {
84
+ const columns: DataFrameColumn[] = [
85
+ { key: 'name', label: 'Name', width: 200 },
86
+ ]
87
+ const wrapper = mount(DataFrame, {
88
+ props: { data: mockData, columns },
89
+ })
90
+ const header = wrapper.find('.mld-dataframe__th')
91
+ expect(header.attributes('style')).toContain('width: 200px')
92
+ })
93
+
94
+ it('should support string width values', () => {
95
+ const columns: DataFrameColumn[] = [
96
+ { key: 'name', label: 'Name', width: '20rem' },
97
+ ]
98
+ const wrapper = mount(DataFrame, {
99
+ props: { data: mockData, columns },
100
+ })
101
+ const header = wrapper.find('.mld-dataframe__th')
102
+ expect(header.attributes('style')).toContain('width: 20rem')
103
+ })
104
+
105
+ it('should apply minWidth', () => {
106
+ const columns: DataFrameColumn[] = [
107
+ { key: 'name', label: 'Name', minWidth: 150 },
108
+ ]
109
+ const wrapper = mount(DataFrame, {
110
+ props: { data: mockData, columns },
111
+ })
112
+ const header = wrapper.find('.mld-dataframe__th')
113
+ expect(header.attributes('style')).toContain('min-width: 150px')
114
+ })
115
+
116
+ it('should apply ellipsis class to cells', () => {
117
+ const columns: DataFrameColumn[] = [
118
+ { key: 'name', label: 'Name', ellipsis: true },
119
+ ]
120
+ const wrapper = mount(DataFrame, {
121
+ props: { data: mockData, columns },
122
+ })
123
+ const cell = wrapper.find('.mld-dataframe__td')
124
+ expect(cell.classes()).toContain('mld-dataframe__td--ellipsis')
125
+ })
126
+
127
+ it('should use custom formatter', () => {
128
+ const columns: DataFrameColumn[] = [
129
+ {
130
+ key: 'age',
131
+ label: 'Age',
132
+ formatter: (value) => `${value} years old`,
133
+ },
134
+ ]
135
+ const wrapper = mount(DataFrame, {
136
+ props: { data: mockData, columns },
137
+ })
138
+ const ageCell = wrapper.findAll('.mld-dataframe__row')[0].findAll('.mld-dataframe__td')[0]
139
+ expect(ageCell.text()).toBe('30 years old')
140
+ })
141
+ })
142
+
143
+ describe('sorting', () => {
144
+ const sortableColumns: DataFrameColumn[] = [
145
+ { key: 'name', label: 'Name', sortable: true },
146
+ { key: 'age', label: 'Age', sortable: true },
147
+ ]
148
+
149
+ it('should show sort icon on sortable columns', () => {
150
+ const wrapper = mount(DataFrame, {
151
+ props: { data: mockData, columns: sortableColumns },
152
+ })
153
+ const sortIcons = wrapper.findAll('.mld-dataframe__sort-icon')
154
+ expect(sortIcons).toHaveLength(2)
155
+ })
156
+
157
+ it('should apply sortable class to sortable headers', () => {
158
+ const wrapper = mount(DataFrame, {
159
+ props: { data: mockData, columns: sortableColumns },
160
+ })
161
+ const headers = wrapper.findAll('.mld-dataframe__th')
162
+ expect(headers[0].classes()).toContain('mld-dataframe__th--sortable')
163
+ })
164
+
165
+ it('should sort ascending on first click', async () => {
166
+ const wrapper = mount(DataFrame, {
167
+ props: { data: mockData, columns: sortableColumns },
168
+ })
169
+ const nameHeader = wrapper.findAll('.mld-dataframe__th')[0]
170
+ await nameHeader.trigger('click')
171
+
172
+ expect(wrapper.emitted('update:sort')).toHaveLength(1)
173
+ const sortState = wrapper.emitted('update:sort')?.[0]?.[0] as SortState
174
+ expect(sortState.key).toBe('name')
175
+ expect(sortState.direction).toBe('asc')
176
+ })
177
+
178
+ it('should sort descending on second click', async () => {
179
+ const wrapper = mount(DataFrame, {
180
+ props: {
181
+ data: mockData,
182
+ columns: sortableColumns,
183
+ sort: { key: 'name', direction: 'asc' },
184
+ },
185
+ })
186
+ const nameHeader = wrapper.findAll('.mld-dataframe__th')[0]
187
+ await nameHeader.trigger('click')
188
+
189
+ const sortState = wrapper.emitted('update:sort')?.[0]?.[0] as SortState
190
+ expect(sortState.direction).toBe('desc')
191
+ })
192
+
193
+ it('should clear sort on third click', async () => {
194
+ const wrapper = mount(DataFrame, {
195
+ props: {
196
+ data: mockData,
197
+ columns: sortableColumns,
198
+ sort: { key: 'name', direction: 'desc' },
199
+ },
200
+ })
201
+ const nameHeader = wrapper.findAll('.mld-dataframe__th')[0]
202
+ await nameHeader.trigger('click')
203
+
204
+ const sortState = wrapper.emitted('update:sort')?.[0]?.[0]
205
+ expect(sortState).toBeNull()
206
+ })
207
+
208
+ it('should sort data internally when sort prop is not provided', async () => {
209
+ const wrapper = mount(DataFrame, {
210
+ props: { data: mockData, columns: sortableColumns },
211
+ })
212
+ const nameHeader = wrapper.findAll('.mld-dataframe__th')[0]
213
+ await nameHeader.trigger('click')
214
+
215
+ // After sorting, first row should have name starting with 'A'
216
+ const firstRow = wrapper.findAll('.mld-dataframe__row')[0]
217
+ const cells = firstRow.findAll('.mld-dataframe__td')
218
+ expect(cells[0].text()).toBe('Alice')
219
+ })
220
+
221
+ it('should sort numeric columns correctly', async () => {
222
+ const wrapper = mount(DataFrame, {
223
+ props: { data: mockData, columns: sortableColumns },
224
+ })
225
+ const ageHeader = wrapper.findAll('.mld-dataframe__th')[1]
226
+ await ageHeader.trigger('click')
227
+
228
+ // After sorting ascending, first row should have age 25
229
+ const firstRow = wrapper.findAll('.mld-dataframe__row')[0]
230
+ const cells = firstRow.findAll('.mld-dataframe__td')
231
+ expect(cells[1].text()).toBe('25')
232
+ })
233
+
234
+ it('should apply sorted class to active column', () => {
235
+ const wrapper = mount(DataFrame, {
236
+ props: {
237
+ data: mockData,
238
+ columns: sortableColumns,
239
+ sort: { key: 'name', direction: 'asc' },
240
+ },
241
+ })
242
+ const nameHeader = wrapper.findAll('.mld-dataframe__th')[0]
243
+ expect(nameHeader.classes()).toContain('mld-dataframe__th--sorted')
244
+ })
245
+
246
+ it('should enable sorting globally with sortable prop', () => {
247
+ const columns: DataFrameColumn[] = [
248
+ { key: 'name', label: 'Name' },
249
+ { key: 'age', label: 'Age' },
250
+ ]
251
+ const wrapper = mount(DataFrame, {
252
+ props: { data: mockData, columns, sortable: true },
253
+ })
254
+ const sortableHeaders = wrapper.findAll('.mld-dataframe__th--sortable')
255
+ expect(sortableHeaders).toHaveLength(2)
256
+ })
257
+ })
258
+
259
+ describe('search filtering', () => {
260
+ it('should show search input when searchable is true', () => {
261
+ const wrapper = mount(DataFrame, {
262
+ props: { data: mockData, columns: mockColumns, searchable: true },
263
+ })
264
+ expect(wrapper.find('.mld-dataframe__search').exists()).toBe(true)
265
+ })
266
+
267
+ it('should filter data based on search query', async () => {
268
+ const wrapper = mount(DataFrame, {
269
+ props: { data: mockData, columns: mockColumns, searchable: true },
270
+ })
271
+ const searchInput = wrapper.find('.mld-dataframe__search')
272
+ await searchInput.setValue('Alice')
273
+
274
+ const rows = wrapper.findAll('.mld-dataframe__row')
275
+ expect(rows).toHaveLength(1)
276
+ expect(rows[0].text()).toContain('Alice')
277
+ })
278
+
279
+ it('should search across all columns by default', async () => {
280
+ const wrapper = mount(DataFrame, {
281
+ props: { data: mockData, columns: mockColumns, searchable: true },
282
+ })
283
+ const searchInput = wrapper.find('.mld-dataframe__search')
284
+ await searchInput.setValue('bob@example.com')
285
+
286
+ const rows = wrapper.findAll('.mld-dataframe__row')
287
+ expect(rows).toHaveLength(1)
288
+ expect(rows[0].text()).toContain('Bob')
289
+ })
290
+
291
+ it('should search only specified columns', async () => {
292
+ const wrapper = mount(DataFrame, {
293
+ props: {
294
+ data: mockData,
295
+ columns: mockColumns,
296
+ searchable: true,
297
+ searchKeys: ['name'],
298
+ },
299
+ })
300
+ const searchInput = wrapper.find('.mld-dataframe__search')
301
+ await searchInput.setValue('bob@example.com')
302
+
303
+ // Should not find Bob by email since we're only searching name
304
+ const rows = wrapper.findAll('.mld-dataframe__row')
305
+ expect(rows).toHaveLength(0)
306
+ })
307
+
308
+ it('should be case-insensitive', async () => {
309
+ const wrapper = mount(DataFrame, {
310
+ props: { data: mockData, columns: mockColumns, searchable: true },
311
+ })
312
+ const searchInput = wrapper.find('.mld-dataframe__search')
313
+ await searchInput.setValue('ALICE')
314
+
315
+ const rows = wrapper.findAll('.mld-dataframe__row')
316
+ expect(rows).toHaveLength(1)
317
+ })
318
+
319
+ it('should use custom placeholder', () => {
320
+ const wrapper = mount(DataFrame, {
321
+ props: {
322
+ data: mockData,
323
+ columns: mockColumns,
324
+ searchable: true,
325
+ searchPlaceholder: 'Type to search...',
326
+ },
327
+ })
328
+ const searchInput = wrapper.find('.mld-dataframe__search')
329
+ expect(searchInput.attributes('placeholder')).toBe('Type to search...')
330
+ })
331
+ })
332
+
333
+ describe('pagination', () => {
334
+ const paginationState: PaginationState = {
335
+ page: 1,
336
+ pageSize: 2,
337
+ total: 3,
338
+ }
339
+
340
+ it('should show pagination controls', () => {
341
+ const wrapper = mount(DataFrame, {
342
+ props: { data: mockData, columns: mockColumns, pagination: paginationState },
343
+ })
344
+ expect(wrapper.find('.mld-dataframe__footer').exists()).toBe(true)
345
+ expect(wrapper.find('.mld-dataframe__page-controls').exists()).toBe(true)
346
+ })
347
+
348
+ it('should display page info', () => {
349
+ const wrapper = mount(DataFrame, {
350
+ props: { data: mockData, columns: mockColumns, pagination: paginationState },
351
+ })
352
+ const pageInfo = wrapper.find('.mld-dataframe__page-info')
353
+ expect(pageInfo.text()).toContain('1')
354
+ expect(pageInfo.text()).toContain('3')
355
+ })
356
+
357
+ it('should paginate data', () => {
358
+ const wrapper = mount(DataFrame, {
359
+ props: { data: mockData, columns: mockColumns, pagination: paginationState },
360
+ })
361
+ const rows = wrapper.findAll('.mld-dataframe__row')
362
+ expect(rows).toHaveLength(2) // pageSize is 2
363
+ })
364
+
365
+ it('should emit pagination event on page change', async () => {
366
+ const wrapper = mount(DataFrame, {
367
+ props: { data: mockData, columns: mockColumns, pagination: paginationState },
368
+ })
369
+ const nextButton = wrapper.findAll('.mld-dataframe__page-btn')[1]
370
+ await nextButton.trigger('click')
371
+
372
+ expect(wrapper.emitted('update:pagination')).toHaveLength(1)
373
+ const newPagination = wrapper.emitted('update:pagination')?.[0]?.[0] as PaginationState
374
+ expect(newPagination.page).toBe(2)
375
+ })
376
+
377
+ it('should disable prev button on first page', () => {
378
+ const wrapper = mount(DataFrame, {
379
+ props: { data: mockData, columns: mockColumns, pagination: paginationState },
380
+ })
381
+ const prevButton = wrapper.findAll('.mld-dataframe__page-btn')[0]
382
+ expect(prevButton.attributes('disabled')).toBeDefined()
383
+ })
384
+
385
+ it('should disable next button on last page', () => {
386
+ const lastPageState: PaginationState = { page: 2, pageSize: 2, total: 3 }
387
+ const wrapper = mount(DataFrame, {
388
+ props: { data: mockData, columns: mockColumns, pagination: lastPageState },
389
+ })
390
+ const nextButton = wrapper.findAll('.mld-dataframe__page-btn')[1]
391
+ expect(nextButton.attributes('disabled')).toBeDefined()
392
+ })
393
+
394
+ it('should not show pagination when false', () => {
395
+ const wrapper = mount(DataFrame, {
396
+ props: { data: mockData, columns: mockColumns, pagination: false },
397
+ })
398
+ expect(wrapper.find('.mld-dataframe__footer').exists()).toBe(false)
399
+ })
400
+ })
401
+
402
+ describe('row selection', () => {
403
+ it('should show checkbox column when selectable', () => {
404
+ const wrapper = mount(DataFrame, {
405
+ props: { data: mockData, columns: mockColumns, selectable: true },
406
+ })
407
+ expect(wrapper.find('.mld-dataframe__th--checkbox').exists()).toBe(true)
408
+ })
409
+
410
+ it('should show checkboxes for each row', () => {
411
+ const wrapper = mount(DataFrame, {
412
+ props: { data: mockData, columns: mockColumns, selectable: true },
413
+ })
414
+ const checkboxes = wrapper.findAll('.mld-dataframe__checkbox')
415
+ // One for header + one for each row
416
+ expect(checkboxes.length).toBeGreaterThan(mockData.length)
417
+ })
418
+
419
+ it('should emit selectedKeys on row selection', async () => {
420
+ const wrapper = mount(DataFrame, {
421
+ props: { data: mockData, columns: mockColumns, selectable: true, rowKey: 'id' },
422
+ })
423
+ const rowCheckboxes = wrapper.findAll('.mld-dataframe__td--checkbox .mld-dataframe__checkbox')
424
+ await rowCheckboxes[0].trigger('change')
425
+
426
+ expect(wrapper.emitted('update:selectedKeys')).toHaveLength(1)
427
+ const keys = wrapper.emitted('update:selectedKeys')?.[0]?.[0] as number[]
428
+ expect(keys).toContain(1)
429
+ })
430
+
431
+ it('should toggle selection on row checkbox click', async () => {
432
+ const wrapper = mount(DataFrame, {
433
+ props: {
434
+ data: mockData,
435
+ columns: mockColumns,
436
+ selectable: true,
437
+ rowKey: 'id',
438
+ selectedKeys: [1],
439
+ },
440
+ })
441
+ const rowCheckboxes = wrapper.findAll('.mld-dataframe__td--checkbox .mld-dataframe__checkbox')
442
+ await rowCheckboxes[0].trigger('change')
443
+
444
+ const keys = wrapper.emitted('update:selectedKeys')?.[0]?.[0] as number[]
445
+ expect(keys).not.toContain(1)
446
+ })
447
+
448
+ it('should select all rows with header checkbox', async () => {
449
+ const wrapper = mount(DataFrame, {
450
+ props: { data: mockData, columns: mockColumns, selectable: true, rowKey: 'id' },
451
+ })
452
+ const headerCheckbox = wrapper.find('.mld-dataframe__th--checkbox .mld-dataframe__checkbox')
453
+ await headerCheckbox.trigger('change')
454
+
455
+ const keys = wrapper.emitted('update:selectedKeys')?.[0]?.[0] as number[]
456
+ expect(keys).toHaveLength(3)
457
+ })
458
+
459
+ it('should apply selected class to selected rows', () => {
460
+ const wrapper = mount(DataFrame, {
461
+ props: {
462
+ data: mockData,
463
+ columns: mockColumns,
464
+ selectable: true,
465
+ rowKey: 'id',
466
+ selectedKeys: [1],
467
+ },
468
+ })
469
+ const selectedRow = wrapper.find('.mld-dataframe__row--selected')
470
+ expect(selectedRow.exists()).toBe(true)
471
+ })
472
+ })
473
+
474
+ describe('events', () => {
475
+ it('should emit row-click event', async () => {
476
+ const wrapper = mount(DataFrame, {
477
+ props: { data: mockData, columns: mockColumns },
478
+ })
479
+ const firstRow = wrapper.findAll('.mld-dataframe__row')[0]
480
+ await firstRow.trigger('click')
481
+
482
+ expect(wrapper.emitted('row-click')).toHaveLength(1)
483
+ const [row, index] = wrapper.emitted('row-click')?.[0] as [Record<string, unknown>, number]
484
+ expect(row.name).toBe('Alice')
485
+ expect(index).toBe(0)
486
+ })
487
+
488
+ it('should emit cell-click event', async () => {
489
+ const wrapper = mount(DataFrame, {
490
+ props: { data: mockData, columns: mockColumns },
491
+ })
492
+ const firstCell = wrapper.find('.mld-dataframe__td')
493
+ await firstCell.trigger('click')
494
+
495
+ expect(wrapper.emitted('cell-click')).toHaveLength(1)
496
+ const [value, column, row] = wrapper.emitted('cell-click')?.[0] as [unknown, DataFrameColumn, Record<string, unknown>]
497
+ expect(value).toBe('Alice')
498
+ expect(column.key).toBe('name')
499
+ expect(row.name).toBe('Alice')
500
+ })
501
+ })
502
+
503
+ describe('empty state', () => {
504
+ it('should show empty state when no data', () => {
505
+ const wrapper = mount(DataFrame, {
506
+ props: { data: [], columns: mockColumns },
507
+ })
508
+ expect(wrapper.find('.mld-dataframe__empty').exists()).toBe(true)
509
+ })
510
+
511
+ it('should display empty text', () => {
512
+ const wrapper = mount(DataFrame, {
513
+ props: { data: [], columns: mockColumns },
514
+ })
515
+ expect(wrapper.find('.mld-dataframe__empty-text').text()).toBe('No data')
516
+ })
517
+
518
+ it('should use custom empty text', () => {
519
+ const wrapper = mount(DataFrame, {
520
+ props: { data: [], columns: mockColumns, emptyText: 'No records found' },
521
+ })
522
+ expect(wrapper.find('.mld-dataframe__empty-text').text()).toBe('No records found')
523
+ })
524
+
525
+ it('should not show empty state when loading', () => {
526
+ const wrapper = mount(DataFrame, {
527
+ props: { data: [], columns: mockColumns, loading: true },
528
+ })
529
+ expect(wrapper.find('.mld-dataframe__empty').exists()).toBe(false)
530
+ })
531
+ })
532
+
533
+ describe('loading state', () => {
534
+ it('should show loading overlay', () => {
535
+ const wrapper = mount(DataFrame, {
536
+ props: { data: mockData, columns: mockColumns, loading: true },
537
+ })
538
+ expect(wrapper.find('.mld-dataframe__loading').exists()).toBe(true)
539
+ })
540
+
541
+ it('should show loading spinner', () => {
542
+ const wrapper = mount(DataFrame, {
543
+ props: { data: mockData, columns: mockColumns, loading: true },
544
+ })
545
+ expect(wrapper.find('.mld-dataframe__loading-spinner').exists()).toBe(true)
546
+ })
547
+ })
548
+
549
+ describe('styling props', () => {
550
+ it('should not apply striped when striped is false', () => {
551
+ const wrapper = mount(DataFrame, {
552
+ props: { data: mockData, columns: mockColumns, striped: false },
553
+ })
554
+ expect(wrapper.find('.mld-dataframe__row--striped').exists()).toBe(false)
555
+ })
556
+
557
+ it('should not apply bordered when bordered is false', () => {
558
+ const wrapper = mount(DataFrame, {
559
+ props: { data: mockData, columns: mockColumns, bordered: false },
560
+ })
561
+ expect(wrapper.find('.mld-dataframe--bordered').exists()).toBe(false)
562
+ })
563
+
564
+ it('should apply size class to cells', () => {
565
+ const wrapper = mount(DataFrame, {
566
+ props: { data: mockData, columns: mockColumns, size: 'sm' },
567
+ })
568
+ const cell = wrapper.find('.mld-dataframe__td')
569
+ expect(cell.classes()).toContain('mld-dataframe__td--sm')
570
+ })
571
+
572
+ it('should apply sticky header class', () => {
573
+ const wrapper = mount(DataFrame, {
574
+ props: { data: mockData, columns: mockColumns, stickyHeader: true },
575
+ })
576
+ expect(wrapper.find('.mld-dataframe__thead--sticky').exists()).toBe(true)
577
+ })
578
+
579
+ it('should apply maxHeight style', () => {
580
+ const wrapper = mount(DataFrame, {
581
+ props: { data: mockData, columns: mockColumns, maxHeight: 400 },
582
+ })
583
+ const tableWrapper = wrapper.find('.mld-dataframe__table-wrapper')
584
+ expect(tableWrapper.attributes('style')).toContain('max-height: 400px')
585
+ })
586
+
587
+ it('should support string maxHeight', () => {
588
+ const wrapper = mount(DataFrame, {
589
+ props: { data: mockData, columns: mockColumns, maxHeight: '50vh' },
590
+ })
591
+ const tableWrapper = wrapper.find('.mld-dataframe__table-wrapper')
592
+ expect(tableWrapper.attributes('style')).toContain('max-height: 50vh')
593
+ })
594
+ })
595
+
596
+ describe('nested keys', () => {
597
+ it('should access nested object properties', () => {
598
+ const nestedData = [
599
+ { id: 1, user: { name: 'Alice', email: 'alice@example.com' } },
600
+ ]
601
+ const nestedColumns: DataFrameColumn[] = [
602
+ { key: 'user.name', label: 'Name' },
603
+ { key: 'user.email', label: 'Email' },
604
+ ]
605
+ const wrapper = mount(DataFrame, {
606
+ props: { data: nestedData, columns: nestedColumns },
607
+ })
608
+ const cells = wrapper.findAll('.mld-dataframe__td')
609
+ expect(cells[0].text()).toBe('Alice')
610
+ expect(cells[1].text()).toBe('alice@example.com')
611
+ })
612
+ })
613
+
614
+ describe('rowKey prop', () => {
615
+ it('should use string rowKey', () => {
616
+ const wrapper = mount(DataFrame, {
617
+ props: {
618
+ data: mockData,
619
+ columns: mockColumns,
620
+ selectable: true,
621
+ rowKey: 'id',
622
+ },
623
+ })
624
+ expect(wrapper.find('.mld-dataframe').exists()).toBe(true)
625
+ })
626
+
627
+ it('should use function rowKey', () => {
628
+ const wrapper = mount(DataFrame, {
629
+ props: {
630
+ data: mockData,
631
+ columns: mockColumns,
632
+ selectable: true,
633
+ rowKey: (row) => `row-${row.id}`,
634
+ },
635
+ })
636
+ expect(wrapper.find('.mld-dataframe').exists()).toBe(true)
637
+ })
638
+ })
639
+
640
+ describe('slots', () => {
641
+ it('should support header slot', () => {
642
+ const wrapper = mount(DataFrame, {
643
+ props: { data: mockData, columns: mockColumns },
644
+ slots: {
645
+ 'header-name': '<div class="custom-header">Custom Name</div>',
646
+ },
647
+ })
648
+ expect(wrapper.find('.custom-header').exists()).toBe(true)
649
+ expect(wrapper.find('.custom-header').text()).toBe('Custom Name')
650
+ })
651
+
652
+ it('should support cell slot', () => {
653
+ const wrapper = mount(DataFrame, {
654
+ props: { data: mockData, columns: mockColumns },
655
+ slots: {
656
+ 'cell-name': '<div class="custom-cell">{{ value }}</div>',
657
+ },
658
+ })
659
+ expect(wrapper.find('.custom-cell').exists()).toBe(true)
660
+ })
661
+
662
+ it('should support empty slot', () => {
663
+ const wrapper = mount(DataFrame, {
664
+ props: { data: [], columns: mockColumns },
665
+ slots: {
666
+ empty: '<div class="custom-empty">Nothing here</div>',
667
+ },
668
+ })
669
+ expect(wrapper.find('.custom-empty').exists()).toBe(true)
670
+ expect(wrapper.find('.custom-empty').text()).toBe('Nothing here')
671
+ })
672
+
673
+ it('should support loading slot', () => {
674
+ const wrapper = mount(DataFrame, {
675
+ props: { data: mockData, columns: mockColumns, loading: true },
676
+ slots: {
677
+ loading: '<div class="custom-loading">Loading...</div>',
678
+ },
679
+ })
680
+ expect(wrapper.find('.custom-loading').exists()).toBe(true)
681
+ })
682
+
683
+ it('should support footer slot', () => {
684
+ const wrapper = mount(DataFrame, {
685
+ props: {
686
+ data: mockData,
687
+ columns: mockColumns,
688
+ pagination: { page: 1, pageSize: 2, total: 3 },
689
+ },
690
+ slots: {
691
+ footer: '<div class="custom-footer">Custom Footer</div>',
692
+ },
693
+ })
694
+ expect(wrapper.find('.custom-footer').exists()).toBe(true)
695
+ })
696
+ })
697
+
698
+ describe('edge cases', () => {
699
+ it('should handle null and undefined values', () => {
700
+ const dataWithNulls = [
701
+ { id: 1, name: null, age: undefined, email: 'test@example.com' },
702
+ ]
703
+ const wrapper = mount(DataFrame, {
704
+ props: { data: dataWithNulls, columns: mockColumns },
705
+ })
706
+ const cells = wrapper.findAll('.mld-dataframe__td')
707
+ expect(cells[0].text()).toBe('')
708
+ expect(cells[1].text()).toBe('')
709
+ })
710
+
711
+ it('should handle empty columns array', () => {
712
+ const wrapper = mount(DataFrame, {
713
+ props: { data: mockData, columns: [] },
714
+ })
715
+ expect(wrapper.find('.mld-dataframe__table').exists()).toBe(true)
716
+ })
717
+
718
+ it('should handle large datasets efficiently', () => {
719
+ const largeData = Array.from({ length: 1000 }, (_, i) => ({
720
+ id: i,
721
+ name: `User ${i}`,
722
+ age: 20 + (i % 50),
723
+ }))
724
+ const wrapper = mount(DataFrame, {
725
+ props: {
726
+ data: largeData,
727
+ columns: mockColumns,
728
+ pagination: { page: 1, pageSize: 10, total: 1000 },
729
+ },
730
+ })
731
+ const rows = wrapper.findAll('.mld-dataframe__row')
732
+ expect(rows).toHaveLength(10)
733
+ })
734
+
735
+ it('should handle search with filtered pagination', async () => {
736
+ const wrapper = mount(DataFrame, {
737
+ props: {
738
+ data: mockData,
739
+ columns: mockColumns,
740
+ searchable: true,
741
+ pagination: { page: 1, pageSize: 2, total: 3 },
742
+ },
743
+ })
744
+ const searchInput = wrapper.find('.mld-dataframe__search')
745
+ await searchInput.setValue('Alice')
746
+
747
+ const rows = wrapper.findAll('.mld-dataframe__row')
748
+ expect(rows).toHaveLength(1)
749
+ })
750
+
751
+ it('should stop propagation on checkbox clicks', async () => {
752
+ const wrapper = mount(DataFrame, {
753
+ props: {
754
+ data: mockData,
755
+ columns: mockColumns,
756
+ selectable: true,
757
+ rowKey: 'id',
758
+ },
759
+ })
760
+ const checkbox = wrapper.find('.mld-dataframe__td--checkbox .mld-dataframe__checkbox')
761
+ await checkbox.trigger('click')
762
+
763
+ // Row click should not be emitted
764
+ expect(wrapper.emitted('row-click')).toBeUndefined()
765
+ })
766
+ })
767
+ })