@complat/react-spectra-editor 1.7.0 → 1.8.0

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 (111) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/LICENSE +21 -662
  3. package/README.md +5 -0
  4. package/dist/__tests__/fixtures/lc_ms_jcamp.js +2329 -0
  5. package/dist/__tests__/fixtures/lc_ms_jcamp_2.js +2874 -0
  6. package/dist/__tests__/fixtures/lc_ms_jcamp_mz_chemstation.js +3242 -0
  7. package/dist/__tests__/fixtures/lc_ms_jcamp_tic_chemstation.js +854 -0
  8. package/dist/__tests__/fixtures/lc_ms_jcamp_tic_neg.js +1233 -0
  9. package/dist/__tests__/fixtures/lc_ms_jcamp_tic_pos.js +1233 -0
  10. package/dist/__tests__/fixtures/lc_ms_jcamp_uvvis.js +48908 -0
  11. package/dist/__tests__/fixtures/lc_ms_jcamp_uvvis_chemstation.js +15424 -0
  12. package/dist/__tests__/units/components/cmd_bar/r05_submit_btn.test.js +158 -3
  13. package/dist/__tests__/units/components/d3_line_rect.test.js +85 -0
  14. package/dist/__tests__/units/components/d3_multi/multi_focus.test.js +43 -0
  15. package/dist/__tests__/units/components/hplc_viewer.test.js +123 -0
  16. package/dist/__tests__/units/components/panel/info.test.js +55 -15
  17. package/dist/__tests__/units/features/lc-ms/parsing/lcmsCategory.test.js +73 -0
  18. package/dist/__tests__/units/features/lc-ms/submit/peaks.test.js +310 -0
  19. package/dist/actions/curve.js +5 -2
  20. package/dist/actions/hplc_ms.js +66 -0
  21. package/dist/actions/integration.js +17 -2
  22. package/dist/actions/ui.js +39 -11
  23. package/dist/app.js +21 -8
  24. package/dist/components/cmd_bar/01_viewer.js +15 -11
  25. package/dist/components/cmd_bar/03_peak.js +25 -16
  26. package/dist/components/cmd_bar/04_integration.js +116 -17
  27. package/dist/components/cmd_bar/08_peak_group.js +83 -0
  28. package/dist/components/cmd_bar/collapsible_toolbar_group.js +101 -0
  29. package/dist/components/cmd_bar/common.js +4 -3
  30. package/dist/components/cmd_bar/index.js +76 -18
  31. package/dist/components/cmd_bar/r01_layout.js +6 -0
  32. package/dist/components/cmd_bar/r02_scan.js +15 -11
  33. package/dist/components/cmd_bar/r03_threshold.js +19 -3
  34. package/dist/components/cmd_bar/r04_submit.js +61 -6
  35. package/dist/components/cmd_bar/r05_submit_btn.js +60 -13
  36. package/dist/components/cmd_bar/r06_predict_btn.js +4 -4
  37. package/dist/components/cmd_bar/r07_wavelength_btn.js +10 -9
  38. package/dist/components/cmd_bar/r09_detector.js +2 -2
  39. package/dist/components/cmd_bar/tri_btn.js +4 -1
  40. package/dist/components/common/draw.js +6 -5
  41. package/dist/components/d3_line/index.js +70 -15
  42. package/dist/components/d3_line/line_focus.js +104 -67
  43. package/dist/components/d3_line_rect/index.js +836 -0
  44. package/dist/components/d3_line_rect/line_focus.js +526 -0
  45. package/dist/components/d3_line_rect/multi_focus.js +433 -0
  46. package/dist/components/d3_line_rect/rect_focus.js +247 -0
  47. package/dist/components/d3_multi/index.js +154 -163
  48. package/dist/components/d3_multi/multi_focus.js +117 -68
  49. package/dist/components/d3_rect/index.js +6 -0
  50. package/dist/components/d3_rect/rect_focus.js +17 -5
  51. package/dist/components/hplc_viewer.js +169 -0
  52. package/dist/components/lc_ms_uv_tools_bar.js +175 -0
  53. package/dist/components/multi_jcamps_viewer.js +17 -5
  54. package/dist/components/panel/compare.js +84 -62
  55. package/dist/components/panel/cyclic_voltamery_data.js +15 -14
  56. package/dist/components/panel/graph_selection.js +32 -30
  57. package/dist/components/panel/index.js +28 -22
  58. package/dist/components/panel/info.js +90 -31
  59. package/dist/constants/action_type.js +23 -2
  60. package/dist/constants/list_graph.js +16 -0
  61. package/dist/constants/list_layout.js +2 -1
  62. package/dist/constants/list_ui.js +5 -2
  63. package/dist/features/lc-ms/entities/extractEntityLCMS.js +187 -0
  64. package/dist/features/lc-ms/parsing/chemstation.js +108 -0
  65. package/dist/features/lc-ms/parsing/index.js +46 -0
  66. package/dist/features/lc-ms/parsing/lcmsCategory.js +44 -0
  67. package/dist/features/lc-ms/parsing/lcmsJcampInfo.js +18 -0
  68. package/dist/features/lc-ms/parsing/lcmsMsPage.js +241 -0
  69. package/dist/features/lc-ms/parsing/pageValue.js +27 -0
  70. package/dist/features/lc-ms/submit/index.js +24 -0
  71. package/dist/features/lc-ms/submit/peaks.js +66 -0
  72. package/dist/features/lc-ms/ui/wavelengthSelect.js +54 -0
  73. package/dist/fn.js +4 -1
  74. package/dist/helpers/brush.js +74 -19
  75. package/dist/helpers/calc.js +4 -2
  76. package/dist/helpers/cfg.js +5 -4
  77. package/dist/helpers/chem.js +449 -71
  78. package/dist/helpers/compass.js +181 -17
  79. package/dist/helpers/extractEntityLCMS.js +24 -0
  80. package/dist/helpers/extractParams.js +118 -33
  81. package/dist/helpers/extractPeaksEdit.js +29 -9
  82. package/dist/helpers/format.js +189 -4
  83. package/dist/helpers/init.js +25 -5
  84. package/dist/helpers/integration.js +130 -17
  85. package/dist/helpers/integration_draft.js +43 -0
  86. package/dist/helpers/integration_focus.js +155 -0
  87. package/dist/helpers/integration_jcamp.js +68 -0
  88. package/dist/helpers/integration_split.js +202 -0
  89. package/dist/helpers/mount.js +8 -1
  90. package/dist/helpers/sweep.js +39 -0
  91. package/dist/index.js +284 -26
  92. package/dist/layer_content.js +17 -3
  93. package/dist/layer_init.js +124 -14
  94. package/dist/layer_prism.js +31 -5
  95. package/dist/reducers/index.js +3 -1
  96. package/dist/reducers/reducer_curve.js +54 -36
  97. package/dist/reducers/reducer_hplc_ms/hydrate.js +289 -0
  98. package/dist/reducers/reducer_hplc_ms/index.js +122 -0
  99. package/dist/reducers/reducer_hplc_ms/persistence.js +74 -0
  100. package/dist/reducers/reducer_hplc_ms/tic.js +70 -0
  101. package/dist/reducers/reducer_hplc_ms/utils.js +70 -0
  102. package/dist/reducers/reducer_hplc_ms/uvvis.js +377 -0
  103. package/dist/reducers/reducer_integration.js +265 -7
  104. package/dist/reducers/reducer_layout.js +1 -1
  105. package/dist/reducers/reducer_submit.js +2 -1
  106. package/dist/reducers/reducer_ui.js +149 -13
  107. package/dist/reducers/undo_redo_config.js +2 -2
  108. package/dist/sagas/saga_lcms_ui.js +85 -0
  109. package/dist/sagas/saga_ui.js +177 -74
  110. package/dist/setupTests.js +5 -5
  111. package/package.json +10 -7
@@ -5,13 +5,24 @@ var _reduxMockStore = _interopRequireDefault(require("redux-mock-store"));
5
5
  var _reactRedux = require("react-redux");
6
6
  var _react = require("@testing-library/react");
7
7
  require("@testing-library/jest-dom");
8
- var _r05_submit_btn = _interopRequireDefault(require("../../../../components/cmd_bar/r05_submit_btn"));
8
+ var _r05_submit_btn = _interopRequireWildcard(require("../../../../components/cmd_bar/r05_submit_btn"));
9
+ var _format = _interopRequireDefault(require("../../../../helpers/format"));
9
10
  var _jsxRuntime = require("react/jsx-runtime");
11
+ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
10
12
  jest.mock('../../../../helpers/extractPeaksEdit', () => ({
11
- extractPeaksEdit: jest.fn(() => [{
13
+ extractPeaksEdit: () => [{
12
14
  x: 1,
13
15
  y: 2
14
- }])
16
+ }],
17
+ formatLcmsPeaksForBackend: () => [{
18
+ peakMock: true
19
+ }],
20
+ formatLcmsIntegralsForBackend: () => [{
21
+ integralMock: true
22
+ }],
23
+ getLcmsMzPageData: () => ({
24
+ mzPageDataMock: true
25
+ })
15
26
  }));
16
27
  const mockStore = (0, _reduxMockStore.default)([]);
17
28
  const buildBaseState = (overrides = {}) => ({
@@ -83,6 +94,7 @@ const buildBaseState = (overrides = {}) => ({
83
94
  meta: {
84
95
  dscMetaData: {}
85
96
  },
97
+ hplcMs: {},
86
98
  ...overrides
87
99
  });
88
100
  const renderBtnSubmit = (state, operationValue) => {
@@ -214,4 +226,147 @@ describe('<BtnSubmit payload contract />', () => {
214
226
  expect(noFlagsPayload.spectra_list[0]).not.toHaveProperty('keepPred');
215
227
  expect(noFlagsPayload.spectra_list[0]).not.toHaveProperty('simulatenmr');
216
228
  });
229
+ describe('layout LC/MS', () => {
230
+ const uvvisFeature = {
231
+ xUnit: 'min',
232
+ yUnit: 'mAU',
233
+ scanAutoTarget: 1,
234
+ thresRef: 3
235
+ };
236
+ let formatedLcmsSpy;
237
+ beforeEach(() => {
238
+ formatedLcmsSpy = jest.spyOn(_format.default, 'formatedLCMS').mockReturnValue('lcms_head_text');
239
+ });
240
+ afterEach(() => {
241
+ formatedLcmsSpy.mockRestore();
242
+ });
243
+ it('replicates lcms_* onto each UVVIS entry in spectra_list', () => {
244
+ const operationValue = jest.fn();
245
+ const state = buildBaseState({
246
+ layout: 'LC/MS',
247
+ curve: {
248
+ curveIdx: 0,
249
+ listCurves: [{
250
+ lcmsKind: 'uvvis',
251
+ feature: {
252
+ ...uvvisFeature,
253
+ scanAutoTarget: 1
254
+ }
255
+ }, {
256
+ lcmsKind: 'uvvis',
257
+ feature: {
258
+ ...uvvisFeature,
259
+ scanAutoTarget: 2
260
+ }
261
+ }]
262
+ },
263
+ hplcMs: {
264
+ uvvis: {
265
+ selectedWaveLength: 220
266
+ },
267
+ tic: {
268
+ currentPageValue: 1.25
269
+ }
270
+ }
271
+ });
272
+ renderBtnSubmit(state, operationValue);
273
+ const payload = operationValue.mock.calls[0][0];
274
+ expect(payload.spectra_list).toHaveLength(2);
275
+ payload.spectra_list.forEach(entry => {
276
+ expect(entry.lcms_peaks).toEqual([{
277
+ peakMock: true
278
+ }]);
279
+ expect(entry.lcms_integrals).toEqual([{
280
+ integralMock: true
281
+ }]);
282
+ expect(entry.lcms_integrations_export).toBe('percent');
283
+ expect(entry.lcms_peaks_text).toBe('lcms_head_text');
284
+ expect(entry.lcms_uvvis_wavelength).toBe(220);
285
+ expect(entry.lcms_mz_page).toBe(1.25);
286
+ expect(entry.lcms_mz_page_data).toEqual({
287
+ mzPageDataMock: true
288
+ });
289
+ });
290
+ expect(payload.lcms_peaks).toEqual([{
291
+ peakMock: true
292
+ }]);
293
+ expect(formatedLcmsSpy).toHaveBeenCalled();
294
+ });
295
+ it('with no UVVIS in listCurves (fallback to feature), no lcms_* on the spectrum item', () => {
296
+ const operationValue = jest.fn();
297
+ const state = buildBaseState({
298
+ layout: 'LC/MS',
299
+ curve: {
300
+ curveIdx: 0,
301
+ listCurves: [{
302
+ lcmsKind: 'tic',
303
+ feature: uvvisFeature
304
+ }]
305
+ },
306
+ hplcMs: {
307
+ uvvis: {
308
+ selectedWaveLength: 210
309
+ },
310
+ tic: {
311
+ currentPageValue: 0.5
312
+ }
313
+ }
314
+ });
315
+ renderBtnSubmit(state, operationValue);
316
+ const payload = operationValue.mock.calls[0][0];
317
+ expect(payload.spectra_list).toHaveLength(1);
318
+ const entry = payload.spectra_list[0];
319
+ expect(entry).not.toHaveProperty('lcms_peaks');
320
+ expect(entry).not.toHaveProperty('lcms_integrals');
321
+ expect(entry).not.toHaveProperty('lcms_peaks_text');
322
+ expect(payload.lcms_peaks).toEqual([{
323
+ peakMock: true
324
+ }]);
325
+ });
326
+ });
327
+
328
+ // Regression for review finding RR-1 (B5 remainder): the CV current-density factor
329
+ // used on submit/export must match the chart (multi_focus.computeYTransformFactor)
330
+ // and panel — i.e. one conversion to A/cm², not a double /100 for mm².
331
+ describe('computeCvYScaleFactor — CV current-density factor (RR-1 / B5)', () => {
332
+ const feature = {
333
+ yUnit: 'A'
334
+ };
335
+ it('returns 1.0 when current density is off', () => {
336
+ expect((0, _r05_submit_btn.computeCvYScaleFactor)(feature, {
337
+ useCurrentDensity: false
338
+ })).toEqual(1.0);
339
+ });
340
+ it('treats 100 mm² the same as 1 cm² (no double /100)', () => {
341
+ const fMm2 = (0, _r05_submit_btn.computeCvYScaleFactor)(feature, {
342
+ useCurrentDensity: true,
343
+ areaValue: 100,
344
+ areaUnit: 'mm²'
345
+ });
346
+ const fCm2 = (0, _r05_submit_btn.computeCvYScaleFactor)(feature, {
347
+ useCurrentDensity: true,
348
+ areaValue: 1,
349
+ areaUnit: 'cm²'
350
+ });
351
+ expect(fMm2).toBeCloseTo(fCm2);
352
+ expect(fMm2).toBeCloseTo(1.0); // pre-fix this was 0.01 (100x too small)
353
+ });
354
+ it('gives A/cm² for a mm² area (factor = 100 / area_mm²)', () => {
355
+ // 50 mm² == 0.5 cm² → factor 1/0.5 = 2
356
+ expect((0, _r05_submit_btn.computeCvYScaleFactor)(feature, {
357
+ useCurrentDensity: true,
358
+ areaValue: 50,
359
+ areaUnit: 'mm²'
360
+ })).toBeCloseTo(2.0);
361
+ });
362
+ it('scales by 1000 for mA while keeping the area conversion', () => {
363
+ expect((0, _r05_submit_btn.computeCvYScaleFactor)({
364
+ yUnit: 'mA'
365
+ }, {
366
+ useCurrentDensity: true,
367
+ areaValue: 100,
368
+ areaUnit: 'mm²'
369
+ })).toBeCloseTo(1000.0);
370
+ });
371
+ });
217
372
  });
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ var _index = require("../../../components/d3_line_rect/index");
5
+ var _multi_focus = require("../../../components/d3_line_rect/multi_focus");
6
+ var _rect_focus = _interopRequireDefault(require("../../../components/d3_line_rect/rect_focus"));
7
+ describe('isLcmsMsPageLoading', () => {
8
+ const buildMzEntity = (polarity, pageValues) => ({
9
+ layout: 'LC/MS',
10
+ lcmsKind: 'mz',
11
+ lcmsPolarity: polarity,
12
+ features: pageValues.map(pageValue => ({
13
+ pageValue,
14
+ data: [{
15
+ x: [100, 101],
16
+ y: [10, 5]
17
+ }]
18
+ }))
19
+ });
20
+ it('returns true when the requested RT is not available yet', () => {
21
+ const state = {
22
+ tic: {
23
+ polarity: 'positive',
24
+ currentPageValue: 2.5
25
+ }
26
+ };
27
+ expect((0, _index.isLcmsMsPageLoading)([buildMzEntity('positive', [1.2])], state)).toEqual(true);
28
+ });
29
+ it('returns false when the requested RT is already present', () => {
30
+ const state = {
31
+ tic: {
32
+ polarity: 'negative',
33
+ currentPageValue: 2.5
34
+ }
35
+ };
36
+ expect((0, _index.isLcmsMsPageLoading)([buildMzEntity('positive', [1.2]), buildMzEntity('negative', [2.5])], state)).toEqual(false);
37
+ });
38
+ it('prefers persisted polarity over default curveIdx when reopening the editor', () => {
39
+ const ticEntities = [{
40
+ lcmsPolarity: 'positive'
41
+ }, {
42
+ lcmsPolarity: 'negative'
43
+ }];
44
+ expect((0, _multi_focus.pickTicIndex)(ticEntities, 0, 'negative')).toEqual(1);
45
+ });
46
+ it('falls back to curveIdx when polarity is unavailable', () => {
47
+ const ticEntities = [{
48
+ lcmsPolarity: 'positive'
49
+ }, {
50
+ lcmsPolarity: 'negative'
51
+ }];
52
+ expect((0, _multi_focus.pickTicIndex)(ticEntities, 1, null)).toEqual(1);
53
+ });
54
+ it('returns true when no MS page has been received yet', () => {
55
+ const state = {
56
+ tic: {
57
+ polarity: 'positive',
58
+ currentPageValue: 2.5
59
+ }
60
+ };
61
+ expect((0, _index.isLcmsMsPageLoading)([], state)).toEqual(true);
62
+ });
63
+ });
64
+
65
+ // Review finding B7 (#232): drawBar reads tTrEndPts[0].y. Clearing the
66
+ // threshold makes convertThresEndPts return [] (see chem.test.tsx) while the
67
+ // MS bars are still present, so drawBar must guard the empty endpoint list
68
+ // instead of crashing.
69
+ describe('RectFocus.drawBar with an empty threshold-endpoint list (B7)', () => {
70
+ it('does not crash when tTrEndPts is empty but bars exist', () => {
71
+ const rf = Object.create(_rect_focus.default.prototype);
72
+ rf.bars = {}; // truthy → passes the `if (!this.bars)` guard
73
+ rf.scales = {
74
+ x: v => v,
75
+ y: v => v
76
+ }; // TfRescale reads focus.scales.{x,y}
77
+ rf.updatePathCall = () => {}; // stub out the d3 path update
78
+ rf.data = [{
79
+ x: 1,
80
+ y: 2
81
+ }]; // bars present
82
+ rf.tTrEndPts = []; // cleared threshold → empty endpoints
83
+ expect(() => rf.drawBar()).not.toThrow();
84
+ });
85
+ });
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ var _multi_focus = _interopRequireDefault(require("../../../../components/d3_multi/multi_focus"));
5
+ var _list_layout = require("../../../../constants/list_layout");
6
+ // Review finding B5 (#232): the CV current-density factor on the chart
7
+ // (computeYTransformFactor) must convert the electrode area to cm² exactly
8
+ // once. Physically 100 mm² == 1 cm², so the factor for the two must match —
9
+ // double-dividing by 100 for mm² made it 100x too small.
10
+ // (computeYTransformFactor is pure — it ignores `this` — so it is called via
11
+ // the prototype.)
12
+ describe('MultiFocus.computeYTransformFactor — CV current density (B5)', () => {
13
+ const compute = cvSt => _multi_focus.default.prototype.computeYTransformFactor.call({}, _list_layout.LIST_LAYOUT.CYCLIC_VOLTAMMETRY, cvSt, {
14
+ yUnit: 'A'
15
+ });
16
+ it('treats 100 mm² the same as 1 cm²', () => {
17
+ const fMm2 = compute({
18
+ useCurrentDensity: true,
19
+ areaValue: 100,
20
+ areaUnit: 'mm²'
21
+ });
22
+ const fCm2 = compute({
23
+ useCurrentDensity: true,
24
+ areaValue: 1,
25
+ areaUnit: 'cm²'
26
+ });
27
+ expect(fMm2).toBeCloseTo(fCm2);
28
+ expect(fMm2).toBeCloseTo(1.0); // double /100 for mm² gives 0.01
29
+ });
30
+ it('gives A/cm² for a mm² area (factor = 100 / area_mm²)', () => {
31
+ // 50 mm² == 0.5 cm² → factor 1 / 0.5 = 2
32
+ expect(compute({
33
+ useCurrentDensity: true,
34
+ areaValue: 50,
35
+ areaUnit: 'mm²'
36
+ })).toBeCloseTo(2.0);
37
+ });
38
+ it('returns 1.0 when current density is off', () => {
39
+ expect(compute({
40
+ useCurrentDensity: false
41
+ })).toBeCloseTo(1.0);
42
+ });
43
+ });
@@ -0,0 +1,123 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ var _react = _interopRequireDefault(require("react"));
5
+ var _react2 = require("@testing-library/react");
6
+ require("@testing-library/jest-dom");
7
+ var _reduxMockStore = _interopRequireDefault(require("redux-mock-store"));
8
+ var _reactRedux = require("react-redux");
9
+ var _material = require("@mui/material");
10
+ var _styles = require("@mui/styles");
11
+ var _hplc_viewer = _interopRequireDefault(require("../../../components/hplc_viewer"));
12
+ var _list_layout = require("../../../constants/list_layout");
13
+ var _jsxRuntime = require("react/jsx-runtime");
14
+ jest.mock('../../../components/cmd_bar/index', () => props => /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
15
+ "data-testid": "cmd-bar",
16
+ children: [props.editorOnly ? 'editorOnly' : 'editable', "-", props.hideThreshold ? 'hideThreshold' : 'showThreshold']
17
+ }));
18
+ jest.mock('../../../components/d3_line_rect/index', () => props => /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
19
+ "data-testid": "viewer-line-rect",
20
+ children: ["tic:", props.ticEntities?.length || 0, "-uvvis:", props.uvvisEntities?.length || 0, "-mz:", props.mzEntities?.length || 0]
21
+ }));
22
+ jest.mock('../../../components/panel/index', () => props => /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
23
+ "data-testid": "panel-viewer",
24
+ children: props.integration ? 'hasIntegration' : 'noIntegration'
25
+ }));
26
+ const mockStore = (0, _reduxMockStore.default)([]);
27
+ const theme = (0, _material.createTheme)();
28
+ const renderWithStore = (state, extraProps = {}) => {
29
+ const store = mockStore(state);
30
+ return (0, _react2.render)(/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactRedux.Provider, {
31
+ store: store,
32
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_styles.ThemeProvider, {
33
+ theme: theme,
34
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_hplc_viewer.default, {
35
+ operations: [],
36
+ entityFileNames: [],
37
+ userManualLink: {},
38
+ molSvg: "",
39
+ theoryMass: "",
40
+ descriptions: [],
41
+ canChangeDescription: false,
42
+ onDescriptionChanged: () => {},
43
+ editorOnly: true,
44
+ ...extraProps
45
+ })
46
+ })
47
+ }));
48
+ };
49
+ describe('<HPLCViewer />', () => {
50
+ it('renders empty view when there are no entities', () => {
51
+ renderWithStore({
52
+ curve: {
53
+ listCurves: [],
54
+ curveIdx: 0
55
+ },
56
+ layout: _list_layout.LIST_LAYOUT.LC_MS,
57
+ integration: {
58
+ present: {
59
+ integrations: []
60
+ }
61
+ },
62
+ hplcMs: {}
63
+ });
64
+ expect(_react2.screen.queryByTestId('cmd-bar')).not.toBeInTheDocument();
65
+ expect(_react2.screen.queryByTestId('viewer-line-rect')).not.toBeInTheDocument();
66
+ expect(_react2.screen.queryByTestId('panel-viewer')).not.toBeInTheDocument();
67
+ });
68
+ it('renders LCMS blocks with split entities and selected integration', () => {
69
+ const entities = [{
70
+ csCategory: ['tic', 'positive'],
71
+ feature: {
72
+ xUnit: 'min',
73
+ yUnit: 'intensity'
74
+ },
75
+ topic: {
76
+ x: [1, 2],
77
+ y: [10, 20]
78
+ }
79
+ }, {
80
+ csCategory: ['uvvis'],
81
+ feature: {
82
+ xUnit: 'nm',
83
+ yUnit: 'AU'
84
+ },
85
+ topic: {
86
+ x: [210],
87
+ y: [0.4]
88
+ }
89
+ }, {
90
+ csCategory: ['mz', 'negative'],
91
+ feature: {
92
+ xUnit: 'm/z',
93
+ yUnit: 'counts'
94
+ },
95
+ topic: {
96
+ x: [100],
97
+ y: [5]
98
+ }
99
+ }];
100
+ renderWithStore({
101
+ curve: {
102
+ listCurves: entities,
103
+ curveIdx: 1
104
+ },
105
+ layout: _list_layout.LIST_LAYOUT.LC_MS,
106
+ integration: {
107
+ present: {
108
+ integrations: [null, {
109
+ id: 'int-2'
110
+ }, null]
111
+ }
112
+ },
113
+ hplcMs: {
114
+ tic: {
115
+ polarity: 'positive'
116
+ }
117
+ }
118
+ });
119
+ expect(_react2.screen.getByTestId('cmd-bar')).toHaveTextContent('editorOnly-hideThreshold');
120
+ expect(_react2.screen.getByTestId('viewer-line-rect')).toHaveTextContent('tic:1-uvvis:1-mz:1');
121
+ expect(_react2.screen.getByTestId('panel-viewer')).toHaveTextContent('hasIntegration');
122
+ });
123
+ });
@@ -23,7 +23,9 @@ const store = mockStore({
23
23
  shift: {
24
24
  shifts: []
25
25
  },
26
- simulation: {},
26
+ simulation: {
27
+ nmrSimPeaks: []
28
+ },
27
29
  detector: {
28
30
  curves: [{
29
31
  curveIdx: 0,
@@ -33,9 +35,16 @@ const store = mockStore({
33
35
  }
34
36
  }]
35
37
  },
36
- meta: {}
38
+ meta: {
39
+ dscMetaData: {}
40
+ },
41
+ hplcMs: {
42
+ uvvis: {
43
+ spectraList: [],
44
+ listWaveLength: []
45
+ }
46
+ }
37
47
  });
38
- const failedStore = mockStore({});
39
48
  const dispatchMock = () => Promise.resolve({});
40
49
  store.dispatch = jest.fn(dispatchMock);
41
50
  const theme = (0, _material.createTheme)({
@@ -44,6 +53,14 @@ const theme = (0, _material.createTheme)({
44
53
  }
45
54
  });
46
55
  const feature = {};
56
+ const baseProps = {
57
+ feature,
58
+ integration: {},
59
+ editorOnly: false,
60
+ molSvg: '',
61
+ descriptions: '',
62
+ canChangeDescription: false
63
+ };
47
64
  describe("<InfoPanel />", () => {
48
65
  let AppWrapper;
49
66
  beforeEach(() => {
@@ -57,37 +74,60 @@ describe("<InfoPanel />", () => {
57
74
  });
58
75
  };
59
76
  });
60
- test('Cannot render info panel', () => {
77
+ test('Render info panel', () => {
61
78
  const renderer = /*#__PURE__*/(0, _jsxRuntime.jsx)(AppWrapper, {
62
79
  store: store,
63
80
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_styles.ThemeProvider, {
64
81
  theme: theme,
65
82
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_info.default, {
66
83
  expand: false,
67
- onExapnd: () => {}
84
+ onExpand: () => {},
85
+ ...baseProps
68
86
  })
69
87
  })
70
88
  });
71
89
  const {
72
90
  queryByTestId
73
91
  } = (0, _react2.render)(renderer);
74
- expect(queryByTestId('PanelInfo')).not.toBeInTheDocument();
92
+ expect(queryByTestId('PanelInfo')).toBeInTheDocument();
75
93
  });
76
- test('Render info panel', () => {
94
+ test('Render M+ exact mass for MS layout', () => {
95
+ const msStore = mockStore({
96
+ curve: {
97
+ listCurves: [{
98
+ feature: {}
99
+ }],
100
+ curveIdx: 0
101
+ },
102
+ layout: _list_layout.LIST_LAYOUT.MS,
103
+ shift: {
104
+ shifts: []
105
+ },
106
+ simulation: [],
107
+ detector: {
108
+ curves: [{
109
+ curveIdx: 0,
110
+ selectedDetector: {
111
+ name: 'Refractive index',
112
+ label: 'RI'
113
+ }
114
+ }]
115
+ },
116
+ meta: {}
117
+ });
77
118
  const renderer = /*#__PURE__*/(0, _jsxRuntime.jsx)(AppWrapper, {
78
- store: store,
119
+ store: msStore,
79
120
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_styles.ThemeProvider, {
80
121
  theme: theme,
81
122
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_info.default, {
82
- expand: false,
83
- onExapnd: () => {},
84
- feature: feature
123
+ expand: true,
124
+ onExpand: () => {},
125
+ feature: feature,
126
+ exactMass: "121.0653"
85
127
  })
86
128
  })
87
129
  });
88
- const {
89
- queryByTestId
90
- } = (0, _react2.render)(renderer);
91
- expect(queryByTestId('PanelInfo')).toBeInTheDocument();
130
+ (0, _react2.render)(renderer);
131
+ expect(_react2.screen.getByText('121.064751 g/mol')).toBeInTheDocument();
92
132
  });
93
133
  });
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+
3
+ var _lcmsCategory = require("../../../../../features/lc-ms/parsing/lcmsCategory");
4
+ describe('lc-ms/parsing lcmsCategory', () => {
5
+ describe('normalizeLcMsMode', () => {
6
+ it('maps vendor strings to NEGATIVE / POSITIVE / NEUTRAL', () => {
7
+ expect((0, _lcmsCategory.normalizeLcMsMode)('Positive Mode')).toBe('POSITIVE');
8
+ expect((0, _lcmsCategory.normalizeLcMsMode)('NEGATIV')).toBe('NEGATIVE');
9
+ expect((0, _lcmsCategory.normalizeLcMsMode)('neutral scan')).toBe('NEUTRAL');
10
+ });
11
+ it('returns NEUTRAL for empty or unknown', () => {
12
+ expect((0, _lcmsCategory.normalizeLcMsMode)('')).toBe('NEUTRAL');
13
+ expect((0, _lcmsCategory.normalizeLcMsMode)(null)).toBe('NEUTRAL');
14
+ expect((0, _lcmsCategory.normalizeLcMsMode)('unknown')).toBe('NEUTRAL');
15
+ });
16
+ });
17
+ describe('inferLcMsKind', () => {
18
+ it('detects TIC from MASS TIC or CHROMATOGRAM', () => {
19
+ expect((0, _lcmsCategory.inferLcMsKind)({
20
+ dataType: 'MASS TIC'
21
+ }, {})).toBe('TIC');
22
+ expect((0, _lcmsCategory.inferLcMsKind)({}, {
23
+ dataType: 'mass tic'
24
+ })).toBe('TIC');
25
+ expect((0, _lcmsCategory.inferLcMsKind)({}, {
26
+ info: {
27
+ TYPE: 'CHROMATOGRAM'
28
+ }
29
+ })).toBe('TIC');
30
+ });
31
+ it('detects MZ from MASS SPECTRUM', () => {
32
+ expect((0, _lcmsCategory.inferLcMsKind)({
33
+ dataType: 'MASS SPECTRUM'
34
+ }, {})).toBe('MZ');
35
+ });
36
+ it('uses time + intensity units as TIC', () => {
37
+ expect((0, _lcmsCategory.inferLcMsKind)({
38
+ xUnit: 'Minutes',
39
+ yUnit: 'Intensity'
40
+ }, {})).toBe('TIC');
41
+ });
42
+ it('uses m/z axis as MZ', () => {
43
+ expect((0, _lcmsCategory.inferLcMsKind)({
44
+ xUnit: 'm/z'
45
+ }, {})).toBe('MZ');
46
+ });
47
+ it('defaults to SPECTRUM when nothing matches', () => {
48
+ expect((0, _lcmsCategory.inferLcMsKind)({}, {})).toBe('SPECTRUM');
49
+ });
50
+ });
51
+ describe('inferLcMsCategory', () => {
52
+ it('returns existing csCategory when present', () => {
53
+ expect((0, _lcmsCategory.inferLcMsCategory)({
54
+ csCategory: 'CUSTOM LABEL'
55
+ }, {})).toBe('CUSTOM LABEL');
56
+ });
57
+ it('builds TIC / MZ + mode + SPECTRUM when not preset', () => {
58
+ const jcamp = {
59
+ info: {
60
+ SCAN_MODE: 'Positive'
61
+ }
62
+ };
63
+ const ticKind = {
64
+ dataType: 'MASS TIC'
65
+ };
66
+ expect((0, _lcmsCategory.inferLcMsCategory)(ticKind, jcamp)).toBe('TIC POSITIVE SPECTRUM');
67
+ const mzKind = {
68
+ dataType: 'MASS SPECTRUM'
69
+ };
70
+ expect((0, _lcmsCategory.inferLcMsCategory)(mzKind, jcamp)).toBe('MZ POSITIVE SPECTRUM');
71
+ });
72
+ });
73
+ });