Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
81b83ca
feat: add lcms layout
Aug 20, 2024
72bdee6
fix: reset threshold button fixed
Nicolass67 Mar 3, 2025
702354e
fix: Display graphs in minutes
Nicolass67 Mar 3, 2025
3ccd938
fix: Display third graph on opening
Nicolass67 Mar 3, 2025
0b6766f
fix: zoom through graphes fixed
Nicolass67 Mar 5, 2025
c2d9c41
fix: add integration feature, add info on right panel
Nicolass67 Mar 25, 2025
6722e90
Features:
Nicolass67 May 26, 2025
d2509bc
feat: Save peak and integration + code cleaning
Nicolass67 Oct 29, 2025
dcec27c
fix: WIP save integrals
Nicolass67 Oct 29, 2025
b28b195
feat: Enhance LC/MS functionality with new Chemstation support and im…
Nicolass67 Feb 9, 2026
0bcba0a
dist folder
Nicolass67 Feb 9, 2026
d1ff3d1
feat: Add LC/MS support for UV-Vis wavelength, TIC polarity, and curr…
Nicolass67 Feb 16, 2026
9c21320
fix: Correct TIC polarity string and enhance page value parsing in LC…
Nicolass67 Feb 16, 2026
81c2ea9
feat: Enhance HPLC and Layer components with description handling and…
Nicolass67 Feb 16, 2026
f7c34c6
LCMS X-only zoom, TIC legend, decimal peaks, keep zoom
Nicolass67 Feb 23, 2026
0baffba
feat: Add page marker drawing functionality to MultiFocus component
Nicolass67 Feb 23, 2026
4621d4b
typo
Nicolass67 Feb 23, 2026
52f75ef
refactor: Remove TIC polarity handling from LC/MS submission logic
Nicolass67 Feb 23, 2026
fbec9d1
feat: Implement page marker drawing in LineFocus component using HPLC…
Nicolass67 Feb 23, 2026
59a46fb
refactor: Remove theoryMass prop from LayerInit and PanelViewer compo…
Nicolass67 Feb 23, 2026
d790dad
dist folder
Nicolass67 Feb 23, 2026
73fbf03
feat: Integrate layout update functionality in LayerInit component
Nicolass67 Feb 23, 2026
30ccf73
dist folder
Nicolass67 Feb 23, 2026
933e529
yarn.lock
Nicolass67 Feb 23, 2026
12bc58a
refactor: Rename mouseover and mouseout event handlers for clarity in…
Nicolass67 Feb 23, 2026
3e17743
test: align unit tests with current helper APIs
Nicolass67 Mar 4, 2026
82aee26
refactor: clean and harden LCMS flow with general codebase cleanup
Nicolass67 Mar 4, 2026
156408c
refactor: Enhance data handling and validation in LineFocus and Multi…
Nicolass67 Mar 4, 2026
d927c5b
refactor: Improve zoom reset functionality and enhance wavelength and…
Nicolass67 Mar 4, 2026
58ad6ab
refactor: Enhance LCMS state management and improve threshold value h…
Nicolass67 Mar 6, 2026
c8567d5
dist folder
Nicolass67 Mar 6, 2026
c0b2b27
refactor: Update LCMS and TIC data structures for improved consistenc…
Nicolass67 Mar 25, 2026
1e741f7
dist folder
Nicolass67 Mar 25, 2026
3b99848
refactor: Update JCAMP data structures for LC/MS, TIC, and UVVIS; enh…
Nicolass67 Mar 30, 2026
a377008
dist folder
Nicolass67 Mar 30, 2026
9bc2bb7
refactor: Enhance LCMS integration handling and UI components; add un…
Nicolass67 Apr 15, 2026
92d41ad
refactor: Implement LCMS page request handling and enhance data flow;…
Nicolass67 Apr 15, 2026
2990f69
refactor: Remove console logging from UI and reducer actions to clean…
Nicolass67 Apr 15, 2026
ba81645
refactor: Enhance LCMS data handling and UI components; implement wav…
Nicolass67 Apr 22, 2026
0433f6c
chore: Update dependencies in yarn.lock for Babel packages; include n…
Nicolass67 Apr 22, 2026
8106b46
refactor: Improve LCMS component lifecycle handling; add initial load…
Nicolass67 Apr 22, 2026
ce72e3e
test: Add unit tests for LCMS parsing and backend payload formatting;…
Nicolass67 Apr 22, 2026
c19ce5e
feat: Add clearHplcMsState action and integrate state clearing logic …
Nicolass67 May 18, 2026
ad2b9b1
build: recompile dist with cleaned LCMS state-reset logic
Nicolass67 May 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/e2e_testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ jobs:
- name: Cypress run
uses: cypress-io/github-action@v6
with:
build: npm run build
start: npm start
build: npm run --max_old_space_size=12288 build
start: npm --max_old_space_size=12288 start
browser: chrome
8 changes: 7 additions & 1 deletion cypress.config.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import { defineConfig } from "cypress";

export default defineConfig({
pageLoadTimeout: 100000,
requestTimeout: 100000,
responseTimeout: 100000,
e2e: {
setupNodeEvents(on, config) {
// implement node event listeners here
},
experimentalStudio: true
experimentalStudio: true,
pageLoadTimeout: 100000,
requestTimeout: 100000,
responseTimeout: 100000,
},
});
187 changes: 187 additions & 0 deletions cypress/e2e/lcms_spec.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
/// <reference types="cypress" />

describe('LC/MS layouts', () => {
const getAppState = () => cy.window().then((win) => (win as any).__spectraStore.getState())

const openLayout = (buttonText: string) => {
cy.viewport(2000, 2000)
cy.visit('http://localhost:3000/')
cy.contains('button', buttonText).click()
}

const assertNoNaNInPaths = (selector: string) => {
cy.get(`${selector} path[d]`).each(($path) => {
const d = $path.attr('d') || ''
expect(d).to.not.contain('NaN')
})
}

const assertLcmsViewerIsStable = () => {
cy.get('.d3Line .d3Svg').should('exist')
cy.get('.d3Multi .d3SvgMulti').should('exist')
cy.get('.d3Rect .d3SvgRect').should('exist')

assertNoNaNInPaths('.d3Line .d3Svg')
assertNoNaNInPaths('.d3Multi .d3SvgMulti')
assertNoNaNInPaths('.d3Rect .d3SvgRect')
}

it('OpenLab renders LC/MS line, multi and rect graphs', () => {
openLayout('LC/MS OpenLab')
assertLcmsViewerIsStable()
})

it('Chemstation renders LC/MS line, multi and rect graphs', () => {
openLayout('LC/MS Chemstation')
assertLcmsViewerIsStable()
})

it('requests MS page on initial load and TIC click in standalone simulation', () => {
openLayout('LC/MS OpenLab')
assertLcmsViewerIsStable()

// Waits until React has run componentDidUpdate (initial_load on __lcmsDemoRequests).
cy.window().its('__lcmsDemoRequests').should('have.length.above', 0)

cy.window().then((win) => {
(win as any).__spectraStore.dispatch({
type: 'DISPLAY_VIEWER_AT',
payload: { x: 0.8, y: 0, graphIndex: 1 },
})
})

cy.wait(400)
cy.window().its('__lcmsDemoRequests').should('have.length.above', 1)
cy.window().then((win) => {
const requests = (win as any).__lcmsDemoRequests || []
const latestRequest = requests[requests.length - 1]
expect(latestRequest.trigger).to.equal('user_click')
expect(Number.isFinite(latestRequest.retentionTime)).to.equal(true)
})

getAppState().then((stateAfter) => {
expect(Number.isFinite(stateAfter.hplcMs.tic.currentPageValue)).to.equal(true)
})
})

it('updates reducer when adding peak and integration action', () => {
openLayout('LC/MS OpenLab')
assertLcmsViewerIsStable()

cy.window().then((win) => {
getAppState().then((stateBefore) => {
const peaksBefore = stateBefore.hplcMs.uvvis.currentSpectrum?.peaks?.length || 0
const integrationsBefore = stateBefore.hplcMs.uvvis.currentSpectrum?.integrations?.length || 0

cy.get('.btn-sv-bar-addpeak').click()
cy.get('.d3Line .d3Svg').trigger('click', 600, 350, { which: 1, view: win })

const selectedWaveLength = stateBefore.hplcMs.uvvis.selectedWaveLength
const spectrumData = stateBefore.hplcMs.uvvis.currentSpectrum?.data
const xValues = Array.isArray(spectrumData?.x) ? spectrumData.x : []
const yValues = Array.isArray(spectrumData?.y) ? spectrumData.y : []
const mid = Math.floor(xValues.length / 2)
const xL = xValues[Math.max(0, mid - 20)]
const xU = xValues[Math.min(xValues.length - 1, mid + 20)]
const hasIntegrationPayload = Number.isFinite(xL)
&& Number.isFinite(xU)
&& xU !== xL
&& xValues.length > 0
&& yValues.length > 0

if (hasIntegrationPayload) {
(win as any).__spectraStore.dispatch({
type: 'UPDATE_HPLCMS_INTEGRATIONS',
payload: {
spectrumId: selectedWaveLength,
integration: {
xExtent: { xL, xU },
data: spectrumData,
},
},
})
}

getAppState().then((stateAfter) => {
const peaksAfter = stateAfter.hplcMs.uvvis.currentSpectrum?.peaks?.length || 0
const integrationsAfter = stateAfter.hplcMs.uvvis.currentSpectrum?.integrations?.length || 0
expect(peaksAfter).to.be.greaterThan(peaksBefore)
expect(integrationsAfter).to.be.at.least(integrationsBefore)
})
})
})
})

it('updates reducer when switching wavelength', () => {
openLayout('LC/MS OpenLab')
assertLcmsViewerIsStable()

getAppState().then((stateBefore) => {
const wavelengths = stateBefore.hplcMs.uvvis.listWaveLength || []
expect(wavelengths.length).to.be.greaterThan(1)
const initialWaveLength = stateBefore.hplcMs.uvvis.selectedWaveLength
const nextWaveLength = wavelengths.find((value) => value !== initialWaveLength)
expect(nextWaveLength).to.not.equal(undefined)

cy.contains('.MuiFormControl-root', 'Wavelength (nm)')
.find('.MuiSelect-select')
.click({ force: true })
cy.get('li[role="option"]')
.contains(new RegExp(`^\\s*${nextWaveLength}\\s*$`))
.click({ force: true })

getAppState().then((stateAfter) => {
expect(stateAfter.hplcMs.uvvis.selectedWaveLength).to.equal(nextWaveLength)
expect(stateAfter.hplcMs.uvvis.currentSpectrum).to.not.equal(null)
})
})
})

it('updates TIC polarity on Chemstation', () => {
openLayout('LC/MS Chemstation')
assertLcmsViewerIsStable()

getAppState().then((stateBefore) => {
const available = stateBefore.hplcMs.tic?.available || {}
const currentPolarity = stateBefore.hplcMs.tic?.polarity
const polarityOrder = ['positive', 'negative', 'neutral']
const nextPolarity = polarityOrder.find((value) => available[value] && value !== currentPolarity)
if (!nextPolarity) return

const uiLabelByPolarity: Record<string, string> = {
positive: 'PLUS',
negative: 'MINUS',
neutral: 'NEUTRAL',
}
const nextLabel = uiLabelByPolarity[nextPolarity]

cy.contains('.MuiFormControl-root', 'TIC')
.find('.MuiSelect-select')
.click({ force: true })
cy.get('li[role="option"]').contains(new RegExp(`^\\s*${nextLabel}\\s*$`)).click({ force: true })

getAppState().then((stateAfter) => {
expect(stateAfter.hplcMs.tic.polarity).to.equal(nextPolarity)
})
})
})

it('keeps zoom usable after leaving LC/MS', () => {
openLayout('LC/MS OpenLab')
assertLcmsViewerIsStable()

cy.contains('button', 'NMR 1H').click()
cy.get('.d3Line .d3Svg').should('exist')

cy.window().then((win) => {
cy.get('.btn-sv-bar-zoomin').click()
cy.get('.d3Line .brush').should('exist')
cy.get('.d3Line .d3Svg')
.trigger('mousedown', 400, 100, { which: 1, view: win, force: true })
.trigger('mousemove', { clientX: 800, clientY: 1000, view: win, force: true })
.trigger('mouseup', { view: win, force: true })

cy.get('.btn-sv-bar-zoomreset').click()
})
})
})
2 changes: 1 addition & 1 deletion cypress/e2e/nmr1h_spec.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ describe('NMR 1H', () => {
cy.get('.btn-sv-bar-spctrum').click()
cy.get('.input-sv-bar-layout').click()
cy.get('.option-sv-bar-layout').should($li => {
expect($li).to.have.length(24)
expect($li).to.have.length(25)
})
cy.get('ul li:nth-child(9)').click()

Expand Down
19 changes: 19 additions & 0 deletions dist/actions/axes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
"use strict";

Object.defineProperty(exports, "__esModule", {
value: true
});
exports.updateYAxis = exports.updateXAxis = void 0;
var _action_type = require("../constants/action_type");
const updateXAxis = payload => ({
type: _action_type.AXES.UPDATE_X_AXIS,
payload
});
exports.updateXAxis = updateXAxis;
const updateYAxis = payload => ({
type: _action_type.AXES.UPDATE_Y_AXIS,
payload
});

// eslint-disable-line
exports.updateYAxis = updateYAxis;
25 changes: 25 additions & 0 deletions dist/actions/curve.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"use strict";

Object.defineProperty(exports, "__esModule", {
value: true
});
exports.toggleShowAllCurves = exports.setAllCurves = exports.selectCurve = void 0;
var _action_type = require("../constants/action_type");
const selectCurve = payload => ({
type: _action_type.CURVE.SELECT_WORKING_CURVE,
payload
});
exports.selectCurve = selectCurve;
const setAllCurves = (payload, meta) => ({
type: _action_type.CURVE.SET_ALL_CURVES,
payload,
...(meta && typeof meta === 'object' ? {
meta
} : {})
});
exports.setAllCurves = setAllCurves;
const toggleShowAllCurves = payload => ({
type: _action_type.CURVE.SET_SHOULD_SHOW_ALL_CURVES,
payload
});
exports.toggleShowAllCurves = toggleShowAllCurves;
91 changes: 91 additions & 0 deletions dist/actions/cyclic_voltammetry.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
"use strict";

Object.defineProperty(exports, "__esModule", {
value: true
});
exports.toggleCyclicVoltaDensity = exports.setWorkWithMaxPeak = exports.setCylicVoltaRefFactor = exports.setCylicVoltaRef = exports.setCyclicVoltaAreaValue = exports.setCyclicVoltaAreaUnit = exports.selectRefPeaks = exports.selectPairPeak = exports.removeCylicVoltaPecker = exports.removeCylicVoltaPairPeak = exports.removeCylicVoltaMinPeak = exports.removeCylicVoltaMaxPeak = exports.addNewCylicVoltaPairPeak = exports.addCylicVoltaPecker = exports.addCylicVoltaMinPeak = exports.addCylicVoltaMaxPeak = void 0;
var _action_type = require("../constants/action_type");
const addNewCylicVoltaPairPeak = payload => ({
type: _action_type.CYCLIC_VOLTA_METRY.ADD_PAIR_PEAKS,
payload
});
exports.addNewCylicVoltaPairPeak = addNewCylicVoltaPairPeak;
const removeCylicVoltaPairPeak = payload => ({
type: _action_type.CYCLIC_VOLTA_METRY.REMOVE_PAIR_PEAKS,
payload
});
exports.removeCylicVoltaPairPeak = removeCylicVoltaPairPeak;
const addCylicVoltaMaxPeak = payload => ({
type: _action_type.CYCLIC_VOLTA_METRY.ADD_MAX_PEAK,
payload
});
exports.addCylicVoltaMaxPeak = addCylicVoltaMaxPeak;
const removeCylicVoltaMaxPeak = payload => ({
type: _action_type.CYCLIC_VOLTA_METRY.REMOVE_MAX_PEAK,
payload
});
exports.removeCylicVoltaMaxPeak = removeCylicVoltaMaxPeak;
const addCylicVoltaMinPeak = payload => ({
type: _action_type.CYCLIC_VOLTA_METRY.ADD_MIN_PEAK,
payload
});
exports.addCylicVoltaMinPeak = addCylicVoltaMinPeak;
const removeCylicVoltaMinPeak = payload => ({
type: _action_type.CYCLIC_VOLTA_METRY.REMOVE_MIN_PEAK,
payload
});
exports.removeCylicVoltaMinPeak = removeCylicVoltaMinPeak;
const setWorkWithMaxPeak = payload => ({
type: _action_type.CYCLIC_VOLTA_METRY.WORK_WITH_MAX_PEAK,
payload
});
exports.setWorkWithMaxPeak = setWorkWithMaxPeak;
const selectPairPeak = payload => ({
type: _action_type.CYCLIC_VOLTA_METRY.SELECT_PAIR_PEAK,
payload
});
exports.selectPairPeak = selectPairPeak;
const addCylicVoltaPecker = payload => ({
type: _action_type.CYCLIC_VOLTA_METRY.ADD_PECKER,
payload
});
exports.addCylicVoltaPecker = addCylicVoltaPecker;
const removeCylicVoltaPecker = payload => ({
type: _action_type.CYCLIC_VOLTA_METRY.REMOVE_PECKER,
payload
});
exports.removeCylicVoltaPecker = removeCylicVoltaPecker;
const selectRefPeaks = payload => ({
type: _action_type.CYCLIC_VOLTA_METRY.SELECT_REF_PEAK,
payload
});
exports.selectRefPeaks = selectRefPeaks;
const setCylicVoltaRefFactor = payload => ({
type: _action_type.CYCLIC_VOLTA_METRY.SET_FACTOR,
payload
});
exports.setCylicVoltaRefFactor = setCylicVoltaRefFactor;
const setCylicVoltaRef = payload => ({
type: _action_type.CYCLIC_VOLTA_METRY.SET_REF,
payload
});
exports.setCylicVoltaRef = setCylicVoltaRef;
const setCyclicVoltaAreaValue = value => ({
type: _action_type.CYCLIC_VOLTA_METRY.SET_AREA_VALUE,
payload: {
value
}
});
exports.setCyclicVoltaAreaValue = setCyclicVoltaAreaValue;
const setCyclicVoltaAreaUnit = unit => ({
type: _action_type.CYCLIC_VOLTA_METRY.SET_AREA_UNIT,
payload: {
unit
}
});
exports.setCyclicVoltaAreaUnit = setCyclicVoltaAreaUnit;
const toggleCyclicVoltaDensity = payload => ({
type: _action_type.CYCLIC_VOLTA_METRY.TOGGLE_DENSITY,
payload
});
exports.toggleCyclicVoltaDensity = toggleCyclicVoltaDensity;
14 changes: 14 additions & 0 deletions dist/actions/detector.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
"use strict";

Object.defineProperty(exports, "__esModule", {
value: true
});
exports.updateDetector = void 0;
var _action_type = require("../constants/action_type");
/* eslint-disable import/prefer-default-export */

const updateDetector = payload => ({
type: _action_type.SEC.UPDATE_DETECTOR,
payload
});
exports.updateDetector = updateDetector;
Loading
Loading