@morscherlab/mint-sdk 1.0.0-rc.4 → 1.0.0-rc.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 (324) hide show
  1. package/dist/__tests__/components/AppTopBar.navigation.test.d.ts +1 -0
  2. package/dist/__tests__/components/DoseCalculatorVolumeField.test.d.ts +1 -0
  3. package/dist/__tests__/components/PlateMapEditorToolbarInternal.test.d.ts +1 -0
  4. package/dist/__tests__/components/PluginWorkspaceView.controls.test.d.ts +1 -0
  5. package/dist/__tests__/components/PluginWorkspaceView.navigation.test.d.ts +1 -0
  6. package/dist/__tests__/components/PluginWorkspaceView.shell.test.d.ts +1 -0
  7. package/dist/__tests__/components/ProtocolStep.presentation.test.d.ts +1 -0
  8. package/dist/__tests__/components/ProtocolStepEditor.state.test.d.ts +1 -0
  9. package/dist/__tests__/components/ProtocolStepParameterField.test.d.ts +1 -0
  10. package/dist/__tests__/components/ReagentList.presentation.test.d.ts +1 -0
  11. package/dist/__tests__/components/SampleSelector.colors.test.d.ts +1 -0
  12. package/dist/__tests__/components/SampleSelector.drag.test.d.ts +1 -0
  13. package/dist/__tests__/components/SampleSelector.groups.test.d.ts +1 -0
  14. package/dist/__tests__/components/SampleSelector.selection.test.d.ts +1 -0
  15. package/dist/__tests__/components/SampleSelectorSampleRow.test.d.ts +1 -0
  16. package/dist/__tests__/components/ScheduleCalendar.test.d.ts +1 -0
  17. package/dist/__tests__/components/SettingsModal.schema.test.d.ts +1 -0
  18. package/dist/__tests__/components/WellPlate.colors.test.d.ts +1 -0
  19. package/dist/__tests__/components/WellPlate.conditions.test.d.ts +1 -0
  20. package/dist/__tests__/components/WellPlate.geometry.test.d.ts +1 -0
  21. package/dist/__tests__/components/WellPlate.interaction.test.d.ts +1 -0
  22. package/dist/__tests__/components/WellPlate.legend.test.d.ts +1 -0
  23. package/dist/__tests__/components/WellPlate.rendering.test.d.ts +1 -0
  24. package/dist/__tests__/components/WellPlate.sampleDrop.test.d.ts +1 -0
  25. package/dist/__tests__/composables/autoGroup/classify.test.d.ts +1 -0
  26. package/dist/__tests__/composables/autoGroup/columns.test.d.ts +1 -0
  27. package/dist/__tests__/composables/autoGroup/compose.test.d.ts +1 -0
  28. package/dist/__tests__/composables/autoGroup/cooccurrence.test.d.ts +1 -0
  29. package/dist/__tests__/composables/autoGroup/fingerprint.test.d.ts +1 -0
  30. package/dist/__tests__/composables/autoGroup/integration.test.d.ts +1 -0
  31. package/dist/__tests__/composables/autoGroup/template.test.d.ts +1 -0
  32. package/dist/__tests__/composables/autoGroup/tokenize.test.d.ts +1 -0
  33. package/dist/__tests__/composables/useAutoGroupInputSources.test.d.ts +1 -0
  34. package/dist/__tests__/composables/useScheduleCalendarLayout.test.d.ts +1 -0
  35. package/dist/__tests__/docs/extractDocsComponents.test.d.ts +1 -0
  36. package/dist/__tests__/docs/extractDocsExports.test.d.ts +1 -0
  37. package/dist/__tests__/docs/extractDocsParsing.test.d.ts +1 -0
  38. package/dist/__tests__/docs/extractDocsTemplates.test.d.ts +1 -0
  39. package/dist/__tests__/docs/extractDocsTheme.test.d.ts +1 -0
  40. package/dist/components/AppSidebar.vue.d.ts +9 -6
  41. package/dist/components/AppTopBar.navigation.d.ts +11 -0
  42. package/dist/components/BaseButton.vue.d.ts +1 -1
  43. package/dist/components/BaseCheckbox.vue.d.ts +7 -2
  44. package/dist/components/BaseInput.vue.d.ts +2 -2
  45. package/dist/components/BasePill.vue.d.ts +2 -2
  46. package/dist/components/BaseRadioGroup.vue.d.ts +2 -2
  47. package/dist/components/BaseSelect.vue.d.ts +1 -1
  48. package/dist/components/BaseSlider.vue.d.ts +2 -2
  49. package/dist/components/BaseTextarea.vue.d.ts +2 -2
  50. package/dist/components/BaseToggle.vue.d.ts +7 -2
  51. package/dist/components/BioTemplateExperimentWorkspaceView.vue.d.ts +2 -2
  52. package/dist/components/BioTemplatePackWorkspaceView.vue.d.ts +1 -1
  53. package/dist/components/CollapsibleCard.vue.d.ts +9 -0
  54. package/dist/components/ColorSlider.vue.d.ts +2 -2
  55. package/dist/components/ConcentrationInput.vue.d.ts +2 -2
  56. package/dist/components/ControlWorkspaceView.vue.d.ts +4 -4
  57. package/dist/components/DatePicker.vue.d.ts +1 -1
  58. package/dist/components/DateTimePicker.vue.d.ts +2 -2
  59. package/dist/components/DoseCalculatorVolumeField.vue.d.ts +15 -0
  60. package/dist/components/DoseDesignWorkspaceView.vue.d.ts +2 -2
  61. package/dist/components/DropdownButton.vue.d.ts +1 -1
  62. package/dist/components/FileUploader.vue.d.ts +2 -2
  63. package/dist/components/FormulaInput.vue.d.ts +2 -2
  64. package/dist/components/IconButton.vue.d.ts +1 -1
  65. package/dist/components/LoadingSpinner.vue.d.ts +1 -1
  66. package/dist/components/MoleculeInput.vue.d.ts +2 -2
  67. package/dist/components/MultiSelect.vue.d.ts +1 -1
  68. package/dist/components/NumberInput.vue.d.ts +1 -1
  69. package/dist/components/PlateMapEditor.vue.d.ts +6 -6
  70. package/dist/components/PluginWorkspaceView.controls.d.ts +28 -0
  71. package/dist/components/PluginWorkspaceView.navigation.d.ts +29 -0
  72. package/dist/components/PluginWorkspaceView.props.d.ts +151 -0
  73. package/dist/components/PluginWorkspaceView.shell.d.ts +19 -0
  74. package/dist/components/PluginWorkspaceView.vue.d.ts +51 -196
  75. package/dist/components/ProgressBar.vue.d.ts +1 -1
  76. package/dist/components/ProtocolStep.presentation.d.ts +4 -0
  77. package/dist/components/ProtocolStepEditor.state.d.ts +18 -0
  78. package/dist/components/ProtocolStepParameterField.vue.d.ts +12 -0
  79. package/dist/components/ReagentList.presentation.d.ts +16 -0
  80. package/dist/components/ResourceCard.vue.d.ts +1 -1
  81. package/dist/components/SampleSelector.colors.d.ts +13 -0
  82. package/dist/components/SampleSelector.drag.d.ts +24 -0
  83. package/dist/components/SampleSelector.groups.d.ts +15 -0
  84. package/dist/components/SampleSelector.selection.d.ts +26 -0
  85. package/dist/components/SampleSelector.vue.d.ts +4 -1
  86. package/dist/components/SampleSelectorSampleRow.vue.d.ts +21 -0
  87. package/dist/components/SegmentedControl.vue.d.ts +1 -1
  88. package/dist/components/SequenceInput.vue.d.ts +2 -2
  89. package/dist/components/SequenceProgressBar.vue.d.ts +1 -1
  90. package/dist/components/SettingsModal.schema.d.ts +9 -0
  91. package/dist/components/StatusIndicator.vue.d.ts +1 -1
  92. package/dist/components/TagsInput.vue.d.ts +2 -2
  93. package/dist/components/TimePicker.vue.d.ts +2 -2
  94. package/dist/components/TimeRangeInput.vue.d.ts +1 -1
  95. package/dist/components/UnitInput.vue.d.ts +2 -2
  96. package/dist/components/WellPlate.colors.d.ts +9 -0
  97. package/dist/components/WellPlate.conditions.d.ts +26 -0
  98. package/dist/components/WellPlate.geometry.d.ts +23 -0
  99. package/dist/components/WellPlate.interaction.d.ts +71 -0
  100. package/dist/components/WellPlate.legend.d.ts +2 -0
  101. package/dist/components/WellPlate.rendering.d.ts +24 -0
  102. package/dist/components/WellPlate.sampleDrop.d.ts +8 -0
  103. package/dist/components/WellPlate.vue.d.ts +1 -1
  104. package/dist/components/index.js +2 -2
  105. package/dist/components/internal/ActionItemInternal.vue.d.ts +1 -1
  106. package/dist/components/internal/PlateMapEditorToolbarInternal.vue.d.ts +28 -0
  107. package/dist/{components-DafPc4rM.js → components-Blx4MG--.js} +4073 -4222
  108. package/dist/components-Blx4MG--.js.map +1 -0
  109. package/dist/composables/autoGroup/classKey.d.ts +4 -0
  110. package/dist/composables/autoGroup/classify.d.ts +28 -0
  111. package/dist/composables/autoGroup/colors.d.ts +2 -0
  112. package/dist/composables/autoGroup/columns.d.ts +10 -0
  113. package/dist/composables/autoGroup/compose.d.ts +8 -0
  114. package/dist/composables/autoGroup/cooccurrence.d.ts +2 -0
  115. package/dist/composables/autoGroup/csv-shim.d.ts +2 -0
  116. package/dist/composables/autoGroup/fingerprint.d.ts +3 -0
  117. package/dist/composables/autoGroup/index.d.ts +16 -0
  118. package/dist/composables/autoGroup/replicatePreGroup.d.ts +38 -0
  119. package/dist/composables/autoGroup/template.d.ts +15 -0
  120. package/dist/composables/autoGroup/tokenize.d.ts +8 -0
  121. package/dist/composables/autoGroupConstants.d.ts +1 -0
  122. package/dist/composables/autoGroupGrouping.d.ts +3 -0
  123. package/dist/composables/controlComponentBindings.d.ts +1 -1
  124. package/dist/composables/controlSchemaAdapters.d.ts +1 -1
  125. package/dist/composables/controlSchemaDoseDesign.d.ts +11 -0
  126. package/dist/composables/controlSchemaLayout.d.ts +1 -1
  127. package/dist/composables/controlSchemaModel.d.ts +5 -0
  128. package/dist/composables/controlSchemaNormalize.d.ts +1 -1
  129. package/dist/composables/controlSchemaTypes.d.ts +311 -0
  130. package/dist/composables/controlWorkspaceOptions.d.ts +1 -1
  131. package/dist/composables/formBuilderSchema.d.ts +18 -0
  132. package/dist/composables/index.js +3 -3
  133. package/dist/composables/pluginEndpointBuilder.d.ts +13 -0
  134. package/dist/composables/protocolTemplateCatalog.d.ts +26 -0
  135. package/dist/composables/useAutoGroup.d.ts +61 -74
  136. package/dist/composables/useAutoGroupInputSources.d.ts +32 -0
  137. package/dist/composables/useBioTemplateControls.d.ts +1 -1
  138. package/dist/composables/useBioTemplatePresetWorkspace.d.ts +1 -1
  139. package/dist/composables/useBioTemplateWorkspace.d.ts +1 -1
  140. package/dist/composables/useControlSchema.d.ts +4 -316
  141. package/dist/composables/useControlWorkspace.d.ts +1 -1
  142. package/dist/composables/useForm.d.ts +2 -33
  143. package/dist/composables/useFormBuilder.d.ts +2 -9
  144. package/dist/composables/useFormValidation.d.ts +34 -0
  145. package/dist/composables/usePluginClient.d.ts +1 -4
  146. package/dist/composables/useProtocolTemplates.d.ts +2 -24
  147. package/dist/composables/useScheduleCalendarLayout.d.ts +49 -0
  148. package/dist/{composables-BMkPQhVK.js → composables-CHDjDIQT.js} +33 -31
  149. package/dist/composables-CHDjDIQT.js.map +1 -0
  150. package/dist/index.js +4 -4
  151. package/dist/install.js +2 -2
  152. package/dist/styles.css +1432 -660
  153. package/dist/templates/controlSchemaTypes.d.ts +1 -1
  154. package/dist/templates/index.js +1 -1
  155. package/dist/templates/templateAdapterTypes.d.ts +48 -0
  156. package/dist/templates/templateCreateOptions.d.ts +165 -0
  157. package/dist/templates/templateQpcrTypes.d.ts +42 -0
  158. package/dist/templates/types.d.ts +5 -250
  159. package/dist/{templates-bUAWMn5L.js → templates-DSbHJC4v.js} +1536 -297
  160. package/dist/templates-DSbHJC4v.js.map +1 -0
  161. package/dist/types/auto-group.d.ts +79 -9
  162. package/dist/types/componentLabTypes.d.ts +161 -0
  163. package/dist/types/componentWorkflowTypes.d.ts +150 -0
  164. package/dist/types/components.d.ts +14 -311
  165. package/dist/types/form-builder.d.ts +3 -0
  166. package/dist/types/index.d.ts +1 -1
  167. package/dist/{useProtocolTemplates-QZtHFFH2.js → useProtocolTemplates-BbPOYPzO.js} +1220 -454
  168. package/dist/useProtocolTemplates-BbPOYPzO.js.map +1 -0
  169. package/package.json +1 -1
  170. package/src/__tests__/components/AppSidebar.test.ts +67 -0
  171. package/src/__tests__/components/AppTopBar.navigation.test.ts +70 -0
  172. package/src/__tests__/components/CollapsibleCard.test.ts +47 -0
  173. package/src/__tests__/components/DoseCalculatorVolumeField.test.ts +53 -0
  174. package/src/__tests__/components/FormBuilder.test.ts +57 -0
  175. package/src/__tests__/components/PlateMapEditorToolbarInternal.test.ts +54 -0
  176. package/src/__tests__/components/PluginWorkspaceView.controls.test.ts +156 -0
  177. package/src/__tests__/components/PluginWorkspaceView.navigation.test.ts +102 -0
  178. package/src/__tests__/components/PluginWorkspaceView.shell.test.ts +43 -0
  179. package/src/__tests__/components/ProtocolStep.presentation.test.ts +31 -0
  180. package/src/__tests__/components/ProtocolStepEditor.state.test.ts +165 -0
  181. package/src/__tests__/components/ProtocolStepParameterField.test.ts +44 -0
  182. package/src/__tests__/components/ReagentList.presentation.test.ts +68 -0
  183. package/src/__tests__/components/SampleSelector.colors.test.ts +49 -0
  184. package/src/__tests__/components/SampleSelector.drag.test.ts +100 -0
  185. package/src/__tests__/components/SampleSelector.groups.test.ts +81 -0
  186. package/src/__tests__/components/SampleSelector.selection.test.ts +70 -0
  187. package/src/__tests__/components/SampleSelector.test.ts +32 -0
  188. package/src/__tests__/components/SampleSelectorSampleRow.test.ts +37 -0
  189. package/src/__tests__/components/ScheduleCalendar.test.ts +44 -0
  190. package/src/__tests__/components/SettingsModal.schema.test.ts +97 -0
  191. package/src/__tests__/components/WellPlate.colors.test.ts +28 -0
  192. package/src/__tests__/components/WellPlate.conditions.test.ts +68 -0
  193. package/src/__tests__/components/WellPlate.geometry.test.ts +54 -0
  194. package/src/__tests__/components/WellPlate.interaction.test.ts +171 -0
  195. package/src/__tests__/components/WellPlate.legend.test.ts +13 -0
  196. package/src/__tests__/components/WellPlate.rendering.test.ts +122 -0
  197. package/src/__tests__/components/WellPlate.sampleDrop.test.ts +70 -0
  198. package/src/__tests__/composables/autoGroup/classify.test.ts +107 -0
  199. package/src/__tests__/composables/autoGroup/columns.test.ts +135 -0
  200. package/src/__tests__/composables/autoGroup/compose.test.ts +227 -0
  201. package/src/__tests__/composables/autoGroup/cooccurrence.test.ts +91 -0
  202. package/src/__tests__/composables/autoGroup/fingerprint.test.ts +50 -0
  203. package/src/__tests__/composables/autoGroup/integration.test.ts +79 -0
  204. package/src/__tests__/composables/autoGroup/template.test.ts +70 -0
  205. package/src/__tests__/composables/autoGroup/tokenize.test.ts +33 -0
  206. package/src/__tests__/composables/useAutoGroup.test.ts +129 -625
  207. package/src/__tests__/composables/useAutoGroupInputSources.test.ts +107 -0
  208. package/src/__tests__/composables/useControlSchema.test.ts +23 -0
  209. package/src/__tests__/composables/useScheduleCalendarLayout.test.ts +89 -0
  210. package/src/__tests__/docs/extractDocsComponents.test.ts +142 -0
  211. package/src/__tests__/docs/extractDocsExports.test.ts +77 -0
  212. package/src/__tests__/docs/extractDocsParsing.test.ts +69 -0
  213. package/src/__tests__/docs/extractDocsTemplates.test.ts +54 -0
  214. package/src/__tests__/docs/extractDocsTheme.test.ts +89 -0
  215. package/src/__tests__/docs/frontendDocsCatalog.test.ts +1 -1
  216. package/src/__tests__/fixtures/auto-group/mixed-lc-ms-batch.txt +187 -0
  217. package/src/components/AppSidebar.story.vue +79 -6
  218. package/src/components/AppSidebar.vue +74 -6
  219. package/src/components/AppTopBar.navigation.ts +62 -0
  220. package/src/components/AppTopBar.vue +17 -44
  221. package/src/components/AutoGroupModal.story.vue +50 -0
  222. package/src/components/AutoGroupModal.vue +441 -158
  223. package/src/components/BaseCheckbox.story.vue +27 -0
  224. package/src/components/BaseCheckbox.vue +63 -1
  225. package/src/components/BaseToggle.story.vue +27 -0
  226. package/src/components/BaseToggle.vue +66 -1
  227. package/src/components/CollapsibleCard.vue +123 -45
  228. package/src/components/ControlWorkspaceView.vue +2 -6
  229. package/src/components/DoseCalculator.vue +13 -73
  230. package/src/components/DoseCalculatorVolumeField.vue +61 -0
  231. package/src/components/ExperimentTimeline.vue +6 -31
  232. package/src/components/FormBuilder.story.vue +13 -0
  233. package/src/components/FormBuilder.vue +2 -7
  234. package/src/components/PlateMapEditor.vue +32 -106
  235. package/src/components/PluginWorkspaceView.controls.ts +182 -0
  236. package/src/components/PluginWorkspaceView.navigation.ts +106 -0
  237. package/src/components/PluginWorkspaceView.props.ts +174 -0
  238. package/src/components/PluginWorkspaceView.shell.ts +67 -0
  239. package/src/components/PluginWorkspaceView.vue +88 -404
  240. package/src/components/ProtocolStep.presentation.ts +31 -0
  241. package/src/components/ProtocolStepEditor.state.ts +104 -0
  242. package/src/components/ProtocolStepEditor.vue +48 -179
  243. package/src/components/ProtocolStepParameterField.vue +134 -0
  244. package/src/components/ReagentList.presentation.ts +105 -0
  245. package/src/components/ReagentList.vue +16 -79
  246. package/src/components/SampleSelector.colors.ts +43 -0
  247. package/src/components/SampleSelector.drag.ts +164 -0
  248. package/src/components/SampleSelector.groups.ts +109 -0
  249. package/src/components/SampleSelector.selection.ts +103 -0
  250. package/src/components/SampleSelector.vue +82 -349
  251. package/src/components/SampleSelectorSampleRow.vue +64 -0
  252. package/src/components/ScheduleCalendar.vue +44 -199
  253. package/src/components/SettingsModal.schema.ts +71 -0
  254. package/src/components/SettingsModal.vue +16 -46
  255. package/src/components/WellPlate.colors.ts +56 -0
  256. package/src/components/WellPlate.conditions.ts +100 -0
  257. package/src/components/WellPlate.geometry.ts +91 -0
  258. package/src/components/WellPlate.interaction.ts +272 -0
  259. package/src/components/WellPlate.legend.ts +8 -0
  260. package/src/components/WellPlate.rendering.ts +105 -0
  261. package/src/components/WellPlate.sampleDrop.ts +73 -0
  262. package/src/components/WellPlate.vue +102 -550
  263. package/src/components/internal/FormFieldRendererInternal.vue +23 -5
  264. package/src/components/internal/PlateMapEditorToolbarInternal.vue +128 -0
  265. package/src/composables/autoGroup/classKey.ts +5 -0
  266. package/src/composables/autoGroup/classify.ts +205 -0
  267. package/src/composables/autoGroup/colors.ts +6 -0
  268. package/src/composables/autoGroup/columns.ts +226 -0
  269. package/src/composables/autoGroup/compose.ts +156 -0
  270. package/src/composables/autoGroup/cooccurrence.ts +46 -0
  271. package/src/composables/autoGroup/csv-shim.ts +44 -0
  272. package/src/composables/autoGroup/fingerprint.ts +49 -0
  273. package/src/composables/autoGroup/index.ts +20 -0
  274. package/src/composables/autoGroup/replicatePreGroup.ts +90 -0
  275. package/src/composables/autoGroup/template.ts +126 -0
  276. package/src/composables/autoGroup/tokenize.ts +41 -0
  277. package/src/composables/autoGroup/vocab.json +67 -0
  278. package/src/composables/autoGroupConstants.ts +4 -0
  279. package/src/composables/autoGroupGrouping.ts +148 -0
  280. package/src/composables/controlComponentBindings.ts +1 -1
  281. package/src/composables/controlSchemaAdapters.ts +4 -1
  282. package/src/composables/controlSchemaDoseDesign.ts +215 -0
  283. package/src/composables/controlSchemaFormFields.ts +4 -1
  284. package/src/composables/controlSchemaLayout.ts +1 -1
  285. package/src/composables/controlSchemaModel.ts +163 -0
  286. package/src/composables/controlSchemaNormalize.ts +1 -1
  287. package/src/composables/controlSchemaTypes.ts +372 -0
  288. package/src/composables/controlWorkspaceOptions.ts +1 -1
  289. package/src/composables/formBuilderSchema.ts +153 -0
  290. package/src/composables/pluginEndpointBuilder.ts +203 -0
  291. package/src/composables/protocolTemplateCatalog.ts +325 -0
  292. package/src/composables/useAutoGroup.ts +395 -549
  293. package/src/composables/useAutoGroupInputSources.ts +147 -0
  294. package/src/composables/useBioTemplateControls.ts +1 -1
  295. package/src/composables/useBioTemplatePresetWorkspace.ts +1 -1
  296. package/src/composables/useBioTemplateWorkspace.ts +1 -1
  297. package/src/composables/useControlSchema.ts +21 -692
  298. package/src/composables/useControlWorkspace.ts +7 -13
  299. package/src/composables/useForm.ts +5 -187
  300. package/src/composables/useFormBuilder.ts +11 -153
  301. package/src/composables/useFormValidation.ts +154 -0
  302. package/src/composables/usePluginClient.ts +10 -193
  303. package/src/composables/useProtocolTemplates.ts +10 -328
  304. package/src/composables/useScheduleCalendarLayout.ts +287 -0
  305. package/src/styles/components/app-sidebar.css +134 -6
  306. package/src/styles/components/auto-group-modal.css +248 -310
  307. package/src/styles/components/checkbox.css +87 -0
  308. package/src/styles/components/collapsible-card.css +154 -14
  309. package/src/styles/components/toggle.css +80 -0
  310. package/src/templates/controlSchemaTypes.ts +1 -1
  311. package/src/templates/templateAdapterTypes.ts +58 -0
  312. package/src/templates/templateCreateOptions.ts +208 -0
  313. package/src/templates/templateQpcrTypes.ts +48 -0
  314. package/src/templates/types.ts +79 -275
  315. package/src/types/auto-group.ts +107 -9
  316. package/src/types/componentLabTypes.ts +235 -0
  317. package/src/types/componentWorkflowTypes.ts +190 -0
  318. package/src/types/components.ts +95 -424
  319. package/src/types/form-builder.ts +3 -0
  320. package/src/types/index.ts +2 -0
  321. package/dist/components-DafPc4rM.js.map +0 -1
  322. package/dist/composables-BMkPQhVK.js.map +0 -1
  323. package/dist/templates-bUAWMn5L.js.map +0 -1
  324. package/dist/useProtocolTemplates-QZtHFFH2.js.map +0 -1
@@ -0,0 +1,70 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import {
3
+ createTextDroppedSample,
4
+ normalizeDroppedSample,
5
+ parseDroppedSampleTransfer,
6
+ } from '../../components/WellPlate.sampleDrop'
7
+
8
+ function createTransfer(data: Record<string, string>) {
9
+ return {
10
+ getData(format: string) {
11
+ return data[format] ?? ''
12
+ },
13
+ }
14
+ }
15
+
16
+ describe('WellPlate sample drop helpers', () => {
17
+ it('normalizes JSON sample payloads', () => {
18
+ const raw = {
19
+ id: 'sample-1',
20
+ sample_name: 'Sample 1',
21
+ sample_type: 'qc',
22
+ injection_volume: 5,
23
+ injection_count: 2,
24
+ custom_method: 'fast',
25
+ metadata: { batch: 'A' },
26
+ }
27
+
28
+ expect(normalizeDroppedSample(raw)).toEqual({
29
+ id: 'sample-1',
30
+ sampleName: 'Sample 1',
31
+ label: 'Sample 1',
32
+ sampleType: 'qc',
33
+ injectionVolume: 5,
34
+ injectionCount: 2,
35
+ customMethod: 'fast',
36
+ metadata: { batch: 'A' },
37
+ raw,
38
+ })
39
+ })
40
+
41
+ it('parses JSON payloads before text fallback', () => {
42
+ const transfer = createTransfer({
43
+ 'application/json': JSON.stringify({ name: 'From JSON' }),
44
+ 'text/plain': 'From Text',
45
+ })
46
+
47
+ expect(parseDroppedSampleTransfer(transfer)).toMatchObject({
48
+ sampleName: 'From JSON',
49
+ label: 'From JSON',
50
+ })
51
+ })
52
+
53
+ it('creates a text sample when no JSON payload exists', () => {
54
+ expect(createTextDroppedSample(' Sample A ')).toEqual({
55
+ sampleName: 'Sample A',
56
+ label: 'Sample A',
57
+ raw: 'Sample A',
58
+ })
59
+ expect(parseDroppedSampleTransfer(createTransfer({ 'text/plain': 'Sample B' }))).toMatchObject({
60
+ sampleName: 'Sample B',
61
+ label: 'Sample B',
62
+ })
63
+ })
64
+
65
+ it('rejects invalid or empty payloads', () => {
66
+ expect(normalizeDroppedSample({ metadata: { batch: 'A' } })).toBeNull()
67
+ expect(createTextDroppedSample(' ')).toBeNull()
68
+ expect(parseDroppedSampleTransfer(createTransfer({ 'application/json': '{' }))).toBeNull()
69
+ })
70
+ })
@@ -0,0 +1,107 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { detectClass } from '../../../composables/autoGroup/classify'
3
+
4
+ describe('detectClass', () => {
5
+ it('detects IQC and EQC by exact vocab match', () => {
6
+ const result = detectClass(
7
+ [
8
+ ['exp281', 'SAM_SAH', 'Tissues', 'IQC', '003'],
9
+ ['exp281', 'SAM_SAH', 'Tissues', 'IQC', '056'],
10
+ ['exp280', 'Experiment', 'EQC', '002'],
11
+ ],
12
+ {},
13
+ )
14
+ const iqc = result.find(c => c.kind === 'iqc')
15
+ const eqc = result.find(c => c.kind === 'eqc')
16
+ expect(iqc?.members).toEqual([0, 1])
17
+ expect(eqc?.members).toEqual([2])
18
+ })
19
+
20
+ it('detects Biological with subKind from matrix vocab', () => {
21
+ const result = detectClass(
22
+ [
23
+ ['exp281', 'SAM_SAH', 'Tissues', 'Kidney', 'M10', '085'],
24
+ ['exp281', 'SAM_SAH', 'Tissues', 'Liver', 'M10', '047'],
25
+ ['exp280', 'Experiment', 'Plasma', 'M10', '031'],
26
+ ],
27
+ {},
28
+ )
29
+ const tissues = result.find(c => c.kind === 'biological' && c.subKind === 'Tissues')
30
+ const plasma = result.find(c => c.kind === 'biological' && c.subKind === 'Plasma')
31
+ expect(tissues?.members).toEqual([0, 1])
32
+ expect(plasma?.members).toEqual([2])
33
+ })
34
+
35
+ it('classifies SAHStandard, SAMstandard, ExtractionS, STD1 as Standard (not Biological)', () => {
36
+ const result = detectClass(
37
+ [
38
+ ['exp281', 'SAM_SAH', 'SAHStandard', '5nM', '002'],
39
+ ['exp281', 'SAM_SAH', 'SAMstandard', '5nM', '001'],
40
+ ['exp281', 'SAM_SAH', 'Tissues', 'ExtractionS', '00B'],
41
+ ['exp', 'STD1', 'inject'],
42
+ ],
43
+ {},
44
+ )
45
+ const std = result.find(c => c.kind === 'standard')
46
+ expect(std?.members.sort()).toEqual([0, 1, 2, 3])
47
+ })
48
+
49
+ it('classifies SpikeIn and Stock', () => {
50
+ const result = detectClass(
51
+ [
52
+ ['exp280', 'SK', 'ESwithSpikeIn', '02'],
53
+ ['exp280', 'SK', 'ESwithSpikeIn', '1to100', 'Stock', '05'],
54
+ ],
55
+ {},
56
+ )
57
+ // SpikeIn matches first, so both go to spike-in (first match wins).
58
+ const spike = result.find(c => c.kind === 'spike-in')
59
+ expect(spike?.members.sort()).toEqual([0, 1])
60
+ })
61
+
62
+ it('classifies samples with no recognized vocab as Unknown', () => {
63
+ const result = detectClass([['foo', 'bar', 'baz']], {})
64
+ expect(result[0].kind).toBe('unknown')
65
+ expect(result[0].members).toEqual([0])
66
+ })
67
+
68
+ it('records classTagPositions for the position(s) that matched vocab', () => {
69
+ const result = detectClass(
70
+ [['exp281', 'SAM_SAH', 'Tissues', 'Kidney', 'M10', '085']],
71
+ {},
72
+ )
73
+ const tissues = result.find(c => c.kind === 'biological')
74
+ // Tissues at position 2 (class tag) and Kidney at position 3 (matrix tag).
75
+ expect(tissues?.classTagPositions).toContain(2)
76
+ expect(tissues?.classTagPositions).toContain(3)
77
+ })
78
+
79
+ it('honors sample_type hint short-circuit', () => {
80
+ const result = detectClass(
81
+ [['weird', 'name', 'with', 'no', 'vocab']],
82
+ { sampleTypeHints: ['qc'] },
83
+ )
84
+ expect(result[0].kind).toBe('iqc')
85
+ })
86
+
87
+ it('does not classify bare A1/M10/QC1/STD1 as Biological', () => {
88
+ // Names that only contain a `[A-Z]+\d+` token without matrix vocab must fall to Unknown,
89
+ // not Biological — prevents misclassifying calibration/QC labels.
90
+ const result = detectClass([['exp', 'foo', 'A1']], {})
91
+ expect(result[0].kind).toBe('unknown')
92
+ })
93
+
94
+ it('Biological requires an accompanying matrix vocab token', () => {
95
+ const result = detectClass([['exp', 'Plasma', 'M10']], {})
96
+ expect(result[0].kind).toBe('biological')
97
+ expect(result[0].subKind).toBe('Plasma')
98
+ })
99
+
100
+ it('first-match-wins between SpikeIn and Stock when both tokens appear', () => {
101
+ const result = detectClass(
102
+ [['exp', 'SK', 'ESwithSpikeIn', '1to100', 'Stock', '05']],
103
+ {},
104
+ )
105
+ expect(result[0].kind).toBe('spike-in')
106
+ })
107
+ })
@@ -0,0 +1,135 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { inferRole, parseNumeric, buildClassSchema } from '../../../composables/autoGroup/columns'
3
+
4
+ describe('parseNumeric', () => {
5
+ it('parses value+unit tokens', () => {
6
+ expect(parseNumeric('5nM')).toEqual({ value: 5, unit: 'nM', raw: '5nM' })
7
+ expect(parseNumeric('400ulES')).toEqual({ value: 400, unit: 'ulES', raw: '400ulES' })
8
+ expect(parseNumeric('1ul')).toEqual({ value: 1, unit: 'ul', raw: '1ul' })
9
+ })
10
+
11
+ it('decodes the dot-encoded decimal point', () => {
12
+ expect(parseNumeric('0dot5ul')).toEqual({ value: 0.5, unit: 'ul', raw: '0dot5ul' })
13
+ })
14
+
15
+ it('parses ratios via the dedicated branch', () => {
16
+ expect(parseNumeric('1to100')).toEqual({ value: 0.01, unit: 'ratio', raw: '1to100' })
17
+ expect(parseNumeric('1to10')).toEqual({ value: 0.1, unit: 'ratio', raw: '1to10' })
18
+ })
19
+
20
+ it('rejects bare IDs, dates, and pure numbers', () => {
21
+ expect(parseNumeric('M10')).toBeNull()
22
+ expect(parseNumeric('exp281')).toBeNull()
23
+ expect(parseNumeric('260415')).toBeNull()
24
+ expect(parseNumeric('123')).toBeNull()
25
+ })
26
+ })
27
+
28
+ describe('inferRole', () => {
29
+ const make = (values: string[]) => ({
30
+ values,
31
+ position: 0,
32
+ totalPositions: 1,
33
+ classSize: values.length,
34
+ })
35
+
36
+ it('returns constant when every value is the same', () => {
37
+ expect(inferRole(make(['exp281', 'exp281', 'exp281']))).toBe('constant')
38
+ })
39
+
40
+ it('returns run-order when trailing position has high-card numeric values', () => {
41
+ expect(inferRole({
42
+ values: ['001', '002', '003', '004', '005'],
43
+ position: 4,
44
+ totalPositions: 5,
45
+ classSize: 5,
46
+ })).toBe('run-order')
47
+ })
48
+
49
+ it('returns numeric when many distinct value+unit values are present', () => {
50
+ // 6+ distinct numeric levels read as numeric (binning candidates).
51
+ expect(inferRole(make(['1nM', '5nM', '10nM', '20nM', '50nM', '100nM']))).toBe('numeric')
52
+ })
53
+
54
+ it('returns factor for low-cardinality numerics (<=5 unique levels)', () => {
55
+ // 5min / 10min and similar small dose ladders read as factors by default;
56
+ // users opt into binning via the column popover for the cases that want it.
57
+ expect(inferRole(make(['5min', '10min']))).toBe('factor')
58
+ expect(inferRole(make(['5nM', '10nM', '20nM']))).toBe('factor')
59
+ })
60
+
61
+ it('returns replicate ONLY for explicit replicate-named tokens (rep1, Rep2, replicate3, R1)', () => {
62
+ // The replicate pre-grouping pass collapses real replicates by name
63
+ // before tokenisation, so the cardinality-based ID_PATTERN heuristic
64
+ // (M1, M2, …) was removed — those reach inferRole now only when they
65
+ // represent distinct conditions, and should be treated as factors.
66
+ expect(inferRole({
67
+ values: ['M1', 'M2', 'M3', 'M4', 'M5'],
68
+ position: 1,
69
+ totalPositions: 3,
70
+ classSize: 5,
71
+ })).toBe('factor')
72
+ expect(inferRole({
73
+ values: ['rep1', 'rep2', 'rep3'],
74
+ position: 1,
75
+ totalPositions: 3,
76
+ classSize: 3,
77
+ })).toBe('replicate')
78
+ expect(inferRole({
79
+ values: ['Replicate1', 'Replicate2'],
80
+ position: 1,
81
+ totalPositions: 3,
82
+ classSize: 2,
83
+ })).toBe('replicate')
84
+ })
85
+
86
+ it('returns factor for low-cardinality categoricals', () => {
87
+ expect(inferRole(make(['Kidney', 'Liver', 'Tumor', 'Kidney', 'Liver']))).toBe('factor')
88
+ })
89
+
90
+ it('returns ignore for very-high-cardinality opaque values', () => {
91
+ // Real-world LC-MS batches routinely have 30–50 distinct conditions, so the
92
+ // factor cap was raised to 50 (was 20). 80 unique values still reads as
93
+ // ignore — likely a sample-ID column rather than a meaningful factor.
94
+ const values = Array.from({ length: 80 }, (_, i) => `opaque-${i}`)
95
+ expect(inferRole({ values, position: 0, totalPositions: 1, classSize: 80 })).toBe('ignore')
96
+ })
97
+ })
98
+
99
+ describe('buildClassSchema', () => {
100
+ it('builds one ColumnInfo per position, constant class-tag positions get class-tag role', () => {
101
+ const schema = buildClassSchema(
102
+ [
103
+ ['exp281', 'SAM_SAH', 'Tissues', 'Kidney', 'M10', '085'],
104
+ ['exp281', 'SAM_SAH', 'Tissues', 'Liver', 'M10', '047'],
105
+ ],
106
+ 'biological',
107
+ 'Tissues',
108
+ [2, 3], // positions flagged as class-related tags
109
+ )
110
+ expect(schema.classKind).toBe('biological')
111
+ expect(schema.subKind).toBe('Tissues')
112
+ // Position 2 ('Tissues') is constant → class-tag.
113
+ // Position 3 ('Kidney'/'Liver') is multi-valued → falls through to factor inference.
114
+ const tagCols = schema.columns.filter(c => c.role === 'class-tag')
115
+ expect(tagCols.length).toBe(1)
116
+ expect(tagCols[0].index).toBe(2)
117
+ const factorCols = schema.columns.filter(c => c.role === 'factor')
118
+ expect(factorCols.some(c => c.index === 3)).toBe(true)
119
+ })
120
+
121
+ it('default groupBy is every Factor column', () => {
122
+ const schema = buildClassSchema(
123
+ [
124
+ ['exp', 'Kidney', '1'],
125
+ ['exp', 'Liver', '2'],
126
+ ['exp', 'Tumor', '3'],
127
+ ],
128
+ 'biological',
129
+ 'Tissues',
130
+ [],
131
+ )
132
+ const factorIndices = schema.columns.filter(c => c.role === 'factor').map(c => c.index)
133
+ expect(schema.groupBy).toEqual(factorIndices)
134
+ })
135
+ })
@@ -0,0 +1,227 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { composeGroups } from '../../../composables/autoGroup/compose'
3
+ import type { ClassSchema } from '../../../types/auto-group'
4
+
5
+ const tissueSchema: ClassSchema = {
6
+ classKind: 'biological',
7
+ subKind: 'Tissues',
8
+ columns: [
9
+ { index: 0, name: 'Tissue', sourceIndices: [0], uniqueValues: ['Kidney', 'Liver'], cardinality: 2, role: 'factor' },
10
+ { index: 1, name: 'Animal', sourceIndices: [1], uniqueValues: ['M1', 'M2', 'M3'], cardinality: 3, role: 'replicate' },
11
+ ],
12
+ groupBy: [0],
13
+ }
14
+
15
+ const iqcSchema: ClassSchema = {
16
+ classKind: 'iqc',
17
+ columns: [
18
+ { index: 0, name: 'IQC', sourceIndices: [0], uniqueValues: ['IQC'], cardinality: 1, role: 'class-tag' },
19
+ { index: 1, name: 'Run', sourceIndices: [1], uniqueValues: ['001', '002'], cardinality: 2, role: 'run-order' },
20
+ ],
21
+ groupBy: [],
22
+ }
23
+
24
+ describe('composeGroups', () => {
25
+ it('builds groups by enabled factor columns, ignoring replicate and run-order roles', () => {
26
+ const result = composeGroups({
27
+ tokenizedSamples: [
28
+ ['Kidney', 'M1'],
29
+ ['Kidney', 'M2'],
30
+ ['Liver', 'M1'],
31
+ ],
32
+ sampleNames: ['s1', 's2', 's3'],
33
+ schemas: { 'biological:Tissues': tissueSchema },
34
+ classes: [
35
+ {
36
+ kind: 'biological',
37
+ subKind: 'Tissues',
38
+ label: 'Biological / Tissues',
39
+ members: [0, 1, 2],
40
+ classTagPositions: [],
41
+ disposition: 'group',
42
+ },
43
+ ],
44
+ })
45
+ // Group names carry class context: "Biological / Tissues / <factor>".
46
+ // The class label is prepended so labels from different classes don't
47
+ // collide when they share a factor value.
48
+ expect(result.experimentalGroups!.map(g => g.name).sort()).toEqual([
49
+ 'Biological / Tissues / Kidney',
50
+ 'Biological / Tissues / Liver',
51
+ ])
52
+ expect(
53
+ result.experimentalGroups!.find(g => g.name === 'Biological / Tissues / Kidney')!.samples,
54
+ ).toEqual(['s1', 's2'])
55
+ })
56
+
57
+ it('routes overlay classes to qcGroups (separate from experimental)', () => {
58
+ const result = composeGroups({
59
+ tokenizedSamples: [
60
+ ['Kidney', 'M1'],
61
+ ['IQC', '001'],
62
+ ['IQC', '002'],
63
+ ],
64
+ sampleNames: ['bio1', 'iqc1', 'iqc2'],
65
+ schemas: { 'biological:Tissues': tissueSchema, iqc: iqcSchema },
66
+ classes: [
67
+ {
68
+ kind: 'biological',
69
+ subKind: 'Tissues',
70
+ label: 'Biological / Tissues',
71
+ members: [0],
72
+ classTagPositions: [],
73
+ disposition: 'group',
74
+ },
75
+ {
76
+ kind: 'iqc',
77
+ label: 'IQC',
78
+ members: [1, 2],
79
+ classTagPositions: [0],
80
+ disposition: 'overlay',
81
+ },
82
+ ],
83
+ })
84
+ expect(result.qcGroups!.length).toBe(1)
85
+ expect(result.qcGroups![0].name).toBe('IQC')
86
+ expect(result.qcGroups![0].samples).toEqual(['iqc1', 'iqc2'])
87
+ expect(result.experimentalGroups!.length).toBe(1)
88
+ })
89
+
90
+ it('routes excluded-disposition classes to excludedSamples', () => {
91
+ const result = composeGroups({
92
+ tokenizedSamples: [
93
+ ['Kidney', 'M1'],
94
+ ['IQC', '001'],
95
+ ],
96
+ sampleNames: ['bio1', 'iqc1'],
97
+ schemas: { 'biological:Tissues': tissueSchema, iqc: iqcSchema },
98
+ classes: [
99
+ {
100
+ kind: 'biological',
101
+ subKind: 'Tissues',
102
+ label: 'Biological / Tissues',
103
+ members: [0],
104
+ classTagPositions: [],
105
+ disposition: 'group',
106
+ },
107
+ {
108
+ kind: 'iqc',
109
+ label: 'IQC',
110
+ members: [1],
111
+ classTagPositions: [0],
112
+ disposition: 'exclude',
113
+ },
114
+ ],
115
+ })
116
+ expect(result.excludedSamples).toEqual(['iqc1'])
117
+ expect(result.qcGroups!.length).toBe(0)
118
+ })
119
+
120
+ it('legacy groups field equals experimentalGroups concat qcGroups', () => {
121
+ const result = composeGroups({
122
+ tokenizedSamples: [
123
+ ['Kidney', 'M1'],
124
+ ['IQC', '001'],
125
+ ],
126
+ sampleNames: ['bio1', 'iqc1'],
127
+ schemas: { 'biological:Tissues': tissueSchema, iqc: iqcSchema },
128
+ classes: [
129
+ {
130
+ kind: 'biological',
131
+ subKind: 'Tissues',
132
+ label: 'Biological / Tissues',
133
+ members: [0],
134
+ classTagPositions: [],
135
+ disposition: 'group',
136
+ },
137
+ {
138
+ kind: 'iqc',
139
+ label: 'IQC',
140
+ members: [1],
141
+ classTagPositions: [0],
142
+ disposition: 'overlay',
143
+ },
144
+ ],
145
+ })
146
+ const concatNames = [
147
+ ...result.experimentalGroups!.map(g => g.name),
148
+ ...result.qcGroups!.map(g => g.name),
149
+ ]
150
+ expect(result.groups.map(g => g.name)).toEqual(concatNames)
151
+ })
152
+
153
+ it('applies alias before exclude before bin in the operation pipeline', () => {
154
+ const schema: ClassSchema = {
155
+ classKind: 'biological',
156
+ columns: [
157
+ {
158
+ index: 0,
159
+ name: 'Treatment',
160
+ sourceIndices: [0],
161
+ uniqueValues: ['Drug-A', 'drug-A', 'CTL', 'CTL'],
162
+ cardinality: 3,
163
+ role: 'factor',
164
+ ops: {
165
+ alias: { 'drug-A': 'Drug-A' },
166
+ exclude: ['CTL'],
167
+ },
168
+ },
169
+ ],
170
+ groupBy: [0],
171
+ }
172
+ const result = composeGroups({
173
+ tokenizedSamples: [['Drug-A'], ['drug-A'], ['CTL']],
174
+ sampleNames: ['s1', 's2', 's3'],
175
+ schemas: { biological: schema },
176
+ classes: [
177
+ {
178
+ kind: 'biological',
179
+ label: 'Biological',
180
+ members: [0, 1, 2],
181
+ classTagPositions: [],
182
+ disposition: 'group',
183
+ },
184
+ ],
185
+ })
186
+ expect(result.experimentalGroups!.length).toBe(1)
187
+ // Class-prefixed group name (cls.label === 'Biological' → "Biological / Drug-A").
188
+ expect(result.experimentalGroups![0].name).toBe('Biological / Drug-A')
189
+ expect(result.experimentalGroups![0].samples).toEqual(['s1', 's2'])
190
+ expect(result.excludedSamples).toEqual(['s3'])
191
+ })
192
+
193
+ it('stores post-alias / post-bin display values in MetadataRow.fields', () => {
194
+ const schema: ClassSchema = {
195
+ classKind: 'biological',
196
+ columns: [
197
+ {
198
+ index: 0,
199
+ name: 'Tissue',
200
+ sourceIndices: [0],
201
+ uniqueValues: ['Kidney', 'Liver'],
202
+ cardinality: 2,
203
+ role: 'factor',
204
+ ops: { alias: { Kidney: 'kidney_panel' } },
205
+ },
206
+ ],
207
+ groupBy: [0],
208
+ }
209
+ const result = composeGroups({
210
+ tokenizedSamples: [['Kidney']],
211
+ sampleNames: ['s1'],
212
+ schemas: { biological: schema },
213
+ classes: [
214
+ {
215
+ kind: 'biological',
216
+ label: 'Biological',
217
+ members: [0],
218
+ classTagPositions: [],
219
+ disposition: 'group',
220
+ },
221
+ ],
222
+ })
223
+ expect(result.metadata[0].fields.Tissue).toBe('kidney_panel')
224
+ // Group label is class-prefixed: cls.label ("Biological") + post-alias value.
225
+ expect(result.metadata[0].group).toBe('Biological / kidney_panel')
226
+ })
227
+ })
@@ -0,0 +1,91 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { findMerges } from '../../../composables/autoGroup/cooccurrence'
3
+ import type { ClassSchema } from '../../../types/auto-group'
4
+
5
+ const schema = (overrides: Partial<ClassSchema> = {}): ClassSchema => ({
6
+ classKind: 'biological',
7
+ columns: [
8
+ { index: 0, name: 'A', sourceIndices: [0], uniqueValues: ['SAM'], cardinality: 1, role: 'factor' },
9
+ { index: 1, name: 'B', sourceIndices: [1], uniqueValues: ['SAH'], cardinality: 1, role: 'factor' },
10
+ ],
11
+ groupBy: [],
12
+ ...overrides,
13
+ })
14
+
15
+ describe('findMerges', () => {
16
+ it('suggests merging SAM + SAH when both columns are factors and mutually paired', () => {
17
+ const s = schema({
18
+ columns: [
19
+ { index: 0, name: 'A', sourceIndices: [0], uniqueValues: ['SAM', 'WT'], cardinality: 2, role: 'factor' },
20
+ { index: 1, name: 'B', sourceIndices: [1], uniqueValues: ['SAH', 'CTL'], cardinality: 2, role: 'factor' },
21
+ ],
22
+ })
23
+ const rows = [
24
+ ['SAM', 'SAH'],
25
+ ['SAM', 'SAH'],
26
+ ['SAM', 'SAH'],
27
+ ['SAM', 'SAH'],
28
+ ['WT', 'CTL'],
29
+ ['WT', 'CTL'],
30
+ ['WT', 'CTL'],
31
+ ['WT', 'CTL'],
32
+ ]
33
+ const out = findMerges(s, rows)
34
+ expect(out.length).toBe(1)
35
+ expect(out[0].columnIndices).toEqual([0, 1])
36
+ expect(out[0].confidence).toBeGreaterThanOrEqual(0.95)
37
+ })
38
+
39
+ it('rejects merge between two adjacent constants (no factor among them)', () => {
40
+ const s = schema({
41
+ columns: [
42
+ { index: 0, name: 'A', sourceIndices: [0], uniqueValues: ['exp281'], cardinality: 1, role: 'constant' },
43
+ { index: 1, name: 'B', sourceIndices: [1], uniqueValues: ['260415'], cardinality: 1, role: 'constant' },
44
+ ],
45
+ })
46
+ const rows = Array.from({ length: 10 }, () => ['exp281', '260415'])
47
+ expect(findMerges(s, rows)).toEqual([])
48
+ })
49
+
50
+ it('rejects asymmetric pairing (forward functional but not backward)', () => {
51
+ const s = schema({
52
+ columns: [
53
+ { index: 0, name: 'A', sourceIndices: [0], uniqueValues: ['a', 'b'], cardinality: 2, role: 'factor' },
54
+ { index: 1, name: 'B', sourceIndices: [1], uniqueValues: ['x'], cardinality: 1, role: 'constant' },
55
+ ],
56
+ })
57
+ const rows = [
58
+ ['a', 'x'],
59
+ ['a', 'x'],
60
+ ['b', 'x'],
61
+ ['b', 'x'],
62
+ ]
63
+ expect(findMerges(s, rows)).toEqual([])
64
+ })
65
+
66
+ it('rejects suggestions when class size < 4', () => {
67
+ const s = schema({
68
+ columns: [
69
+ { index: 0, name: 'A', sourceIndices: [0], uniqueValues: ['a', 'b'], cardinality: 2, role: 'factor' },
70
+ { index: 1, name: 'B', sourceIndices: [1], uniqueValues: ['x', 'y'], cardinality: 2, role: 'factor' },
71
+ ],
72
+ })
73
+ const rows = [
74
+ ['a', 'x'],
75
+ ['b', 'y'],
76
+ ['a', 'x'],
77
+ ]
78
+ expect(findMerges(s, rows)).toEqual([])
79
+ })
80
+
81
+ it('rejects merge that would collapse two adjacent factors into a single constant', () => {
82
+ const s = schema({
83
+ columns: [
84
+ { index: 0, name: 'A', sourceIndices: [0], uniqueValues: ['x'], cardinality: 1, role: 'factor' },
85
+ { index: 1, name: 'B', sourceIndices: [1], uniqueValues: ['y'], cardinality: 1, role: 'factor' },
86
+ ],
87
+ })
88
+ const rows = Array.from({ length: 6 }, () => ['x', 'y'])
89
+ expect(findMerges(s, rows)).toEqual([])
90
+ })
91
+ })
@@ -0,0 +1,50 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { serializeFingerprint, restoreFingerprint } from '../../../composables/autoGroup/fingerprint'
3
+ import type { ClassSchema } from '../../../types/auto-group'
4
+
5
+ const schemas: ClassSchema[] = [
6
+ {
7
+ classKind: 'biological',
8
+ subKind: 'Tissues',
9
+ columns: [
10
+ {
11
+ index: 0,
12
+ name: 'Tissue',
13
+ sourceIndices: [0],
14
+ uniqueValues: ['Kidney', 'Liver'],
15
+ cardinality: 2,
16
+ role: 'factor',
17
+ ops: { alias: { Kidney: 'kidney_panel' } },
18
+ },
19
+ ],
20
+ groupBy: [0],
21
+ },
22
+ ]
23
+
24
+ describe('fingerprint round-trip', () => {
25
+ it('serializes to a JSON-stringify-safe object (no Maps)', () => {
26
+ const fp = serializeFingerprint(schemas)
27
+ const stringified = JSON.stringify(fp)
28
+ const reparsed = JSON.parse(stringified)
29
+ expect(reparsed.version).toBe(1)
30
+ expect(reparsed.classes[0].columns[0].ops.alias.Kidney).toBe('kidney_panel')
31
+ })
32
+
33
+ it('round-trips through restore preserving group-by and ops', () => {
34
+ const fp = serializeFingerprint(schemas)
35
+ const restored = restoreFingerprint(fp, schemas)
36
+ expect(restored[0].groupBy).toEqual([0])
37
+ expect(restored[0].columns[0].ops?.alias?.Kidney).toBe('kidney_panel')
38
+ })
39
+
40
+ it('reports a clear error when current schema column count differs', () => {
41
+ const fp = serializeFingerprint(schemas)
42
+ const mismatched: ClassSchema[] = [
43
+ {
44
+ ...schemas[0],
45
+ columns: [], // empty
46
+ },
47
+ ]
48
+ expect(() => restoreFingerprint(fp, mismatched)).toThrow(/column count/i)
49
+ })
50
+ })