@opencloning/ui 1.0.2-test.0 → 1.1.0-dev.5

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 (61) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/package.json +11 -7
  3. package/scripts/inject-version.js +17 -0
  4. package/scripts/reset-version.js +16 -0
  5. package/src/components/DescriptionEditor.jsx +1 -2
  6. package/src/components/DragAndDropCloningHistoryWrapper.jsx +1 -1
  7. package/src/components/ExternalServicesStatusCheck.jsx +2 -1
  8. package/src/components/MainSequenceCheckBox.jsx +2 -5
  9. package/src/components/NetworkNode.jsx +1 -3
  10. package/src/components/OpenCloning.jsx +3 -3
  11. package/src/components/assembler/AssemblePartWidget.jsx +1 -1
  12. package/src/components/assembler/Assembler.jsx +1 -2
  13. package/src/components/dummy/DummyInterface.js +1 -2
  14. package/src/components/eLabFTW/eLabFTWInterface.js +1 -2
  15. package/src/components/form/EnzymeMultiSelect.cy.jsx +20 -9
  16. package/src/components/form/GetRequestMultiSelect.jsx +1 -3
  17. package/src/components/form/LabelWithTooltip.jsx +1 -1
  18. package/src/components/form/PostRequestSelect.jsx +1 -3
  19. package/src/components/index.js +1 -0
  20. package/src/components/navigation/ButtonWithMenu.jsx +1 -3
  21. package/src/components/navigation/MainAppBar.jsx +2 -9
  22. package/src/components/navigation/SelectTemplateDialog.jsx +1 -1
  23. package/src/components/primers/PrimerForm.jsx +2 -4
  24. package/src/components/primers/PrimerList.cy.jsx +86 -78
  25. package/src/components/primers/PrimerList.jsx +1 -1
  26. package/src/components/primers/PrimerTableRow.cy.jsx +31 -18
  27. package/src/components/primers/PrimerTableRow.jsx +1 -4
  28. package/src/components/primers/SelectPrimerForm.jsx +1 -1
  29. package/src/components/primers/import_primers/ImportPrimersButton.jsx +1 -4
  30. package/src/components/primers/import_primers/ImportPrimersTable.jsx +2 -5
  31. package/src/components/primers/import_primers/PrimerDatabaseImportForm.jsx +1 -2
  32. package/src/components/primers/primer_design/SequenceTabComponents/CollapsableLabel.jsx +1 -1
  33. package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignForm.jsx +1 -1
  34. package/src/components/primers/primer_design/SequenceTabComponents/RestrictionSpacerForm.jsx +1 -1
  35. package/src/components/primers/primer_details/PrimerInfoIcon.jsx +1 -2
  36. package/src/components/settings/SettingsTab.jsx +2 -3
  37. package/src/components/sources/CollectionSource.jsx +1 -1
  38. package/src/components/sources/MultipleOutputsSelector.jsx +1 -2
  39. package/src/components/sources/NewSourceBox.jsx +2 -3
  40. package/src/components/sources/PCRUnitForm.jsx +1 -3
  41. package/src/components/sources/Source.jsx +0 -3
  42. package/src/components/sources/SourceBox.jsx +2 -2
  43. package/src/components/sources/SourceFile.jsx +1 -2
  44. package/src/components/sources/SourceGenomeRegion.cy.jsx +24 -9
  45. package/src/components/sources/SourceGenomeRegion.jsx +1 -6
  46. package/src/components/sources/SourceRepositoryId.jsx +2 -9
  47. package/src/components/sources/SourceTypeSelector.jsx +3 -6
  48. package/src/components/verification/SequencingFileRow.jsx +1 -2
  49. package/src/components/verification/VerificationFileDialog.cy.jsx +35 -8
  50. package/src/hooks/useBackendRoute.js +3 -2
  51. package/src/hooks/useConfig.js +2 -0
  52. package/src/hooks/useDatabase.js +2 -2
  53. package/src/hooks/useHttpClient.js +9 -3
  54. package/src/hooks/useInitializeApp.js +15 -0
  55. package/src/hooks/useUrlParamsLoader.js +149 -0
  56. package/src/index.css +314 -0
  57. package/src/index.js +8 -0
  58. package/src/providers/ConfigProvider.jsx +51 -0
  59. package/src/providers/index.js +3 -0
  60. package/src/version.js +2 -0
  61. package/src/components/sources/KnownSourceErrors.jsx +0 -50
package/CHANGELOG.md CHANGED
@@ -1,5 +1,54 @@
1
1
  # @opencloning/ui
2
2
 
3
+ ## 1.1.0-dev.5
4
+
5
+ ### Patch Changes
6
+
7
+ - 6df1c20: Last dummy test to check if auto pre-release works
8
+ - Updated dependencies [6df1c20]
9
+ - @opencloning/store@1.1.0-dev.5
10
+ - @opencloning/utils@1.1.0-dev.5
11
+
12
+ ## 1.1.0-dev.4
13
+
14
+ ### Patch Changes
15
+
16
+ - 07106ac: Dummy change to test that new action works
17
+ - @opencloning/store@1.1.0-dev.4
18
+ - @opencloning/utils@1.1.0-dev.4
19
+
20
+ ## 1.1.0-test.3
21
+
22
+ ### Patch Changes
23
+
24
+ - a870a5e: Fix imports from mui and icons library, and add husky hook to validate that it does not happen again
25
+ - @opencloning/store@1.1.0-test.3
26
+ - @opencloning/utils@1.1.0-test.3
27
+
28
+ ## 1.1.0-test.2
29
+
30
+ ### Minor Changes
31
+
32
+ - d46f09d: Handle version display with scripts
33
+
34
+ ### Patch Changes
35
+
36
+ - Updated dependencies [d46f09d]
37
+ - @opencloning/store@1.1.0-test.2
38
+ - @opencloning/utils@1.1.0-test.2
39
+
40
+ ## 1.1.0-test.1
41
+
42
+ ### Minor Changes
43
+
44
+ - 02dbc55: Switch to using provider for configuration rather than state
45
+
46
+ ### Patch Changes
47
+
48
+ - Updated dependencies [02dbc55]
49
+ - @opencloning/store@1.1.0-test.1
50
+ - @opencloning/utils@1.1.0-test.1
51
+
3
52
  ## 1.0.2-test.0
4
53
 
5
54
  ### Patch Changes
package/package.json CHANGED
@@ -1,11 +1,17 @@
1
1
  {
2
2
  "name": "@opencloning/ui",
3
- "version": "1.0.2-test.0",
3
+ "version": "1.1.0-dev.5",
4
4
  "type": "module",
5
5
  "main": "./src/index.js",
6
+ "scripts": {
7
+ "prepack": "node scripts/inject-version.js",
8
+ "postpack": "node scripts/reset-version.js"
9
+ },
6
10
  "exports": {
7
11
  ".": "./src/index.js",
8
- "./components": "./src/components/index.js"
12
+ "./components": "./src/components/index.js",
13
+ "./providers/ConfigProvider": "./src/providers/index.js",
14
+ "./hooks/useConfig": "./src/hooks/useConfig.js"
9
15
  },
10
16
  "repository": {
11
17
  "type": "git",
@@ -17,8 +23,8 @@
17
23
  "@emotion/styled": "^11.14.0",
18
24
  "@mui/icons-material": "^5.15.17",
19
25
  "@mui/material": "^5.15.17",
20
- "@opencloning/store": "1.0.2-test.0",
21
- "@opencloning/utils": "1.0.2-test.0",
26
+ "@opencloning/store": "1.1.0-dev.5",
27
+ "@opencloning/utils": "1.1.0-dev.5",
22
28
  "@teselagen/bio-parsers": "^0.4.32",
23
29
  "@teselagen/ove": "^0.8.18",
24
30
  "@teselagen/range-utils": "^0.3.13",
@@ -26,9 +32,7 @@
26
32
  "@zip.js/zip.js": "^2.7.62",
27
33
  "axios": "^1.12.2",
28
34
  "lodash-es": "^4.17.21",
29
- "react": "^18.3.1",
30
- "react-draggable": "^4.4.6",
31
- "react-redux": "^8.1.3"
35
+ "react-draggable": "^4.4.6"
32
36
  },
33
37
  "peerDependencies": {
34
38
  "react": "^18.3.1",
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync, writeFileSync } from 'fs';
3
+ import { fileURLToPath } from 'url';
4
+ import { dirname, join } from 'path';
5
+
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = dirname(__filename);
8
+
9
+ const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'));
10
+ const file = join(__dirname, '..', 'src', 'version.js');
11
+
12
+ const content = `// Version placeholder - replaced at publish time via prepack script
13
+ export const version = "${pkg.version}";
14
+ `;
15
+
16
+ writeFileSync(file, content, 'utf-8');
17
+ console.log(`✓ Injected version ${pkg.version}`);
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env node
2
+ import { writeFileSync } from 'fs';
3
+ import { fileURLToPath } from 'url';
4
+ import { dirname, join } from 'path';
5
+
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = dirname(__filename);
8
+
9
+ const file = join(__dirname, '..', 'src', 'version.js');
10
+
11
+ const content = `// Version placeholder - replaced at publish time via prepack script
12
+ export const version = "__VERSION__";
13
+ `;
14
+
15
+ writeFileSync(file, content, 'utf-8');
16
+ console.log('✓ Reset version placeholder');
@@ -1,7 +1,6 @@
1
1
  import React, { useEffect } from 'react';
2
2
  import { useDispatch, useSelector, shallowEqual } from 'react-redux';
3
- import TextField from '@mui/material/TextField';
4
- import { Button, FormControl } from '@mui/material';
3
+ import { TextField, Button, FormControl } from '@mui/material';
5
4
  import { cloningActions } from '@opencloning/store/cloning';
6
5
 
7
6
  function DescriptionEditor() {
@@ -1,7 +1,7 @@
1
1
  import React from 'react';
2
2
  import { Tooltip } from '@mui/material';
3
3
  import { UploadFile } from '@mui/icons-material';
4
- import CancelIcon from '@mui/icons-material/Cancel';
4
+ import { Cancel as CancelIcon } from '@mui/icons-material';
5
5
  import useDragAndDropFile from '../hooks/useDragAndDropFile';
6
6
  import LoadCloningHistoryWrapper from './LoadCloningHistoryWrapper';
7
7
 
@@ -4,6 +4,7 @@ import { useDispatch } from 'react-redux';
4
4
  import useBackendRoute from '../hooks/useBackendRoute';
5
5
  import useHttpClient from '../hooks/useHttpClient';
6
6
  import { cloningActions } from '@opencloning/store/cloning';
7
+ import { version } from '../index';
7
8
 
8
9
  const { updateAppInfo } = cloningActions;
9
10
 
@@ -16,7 +17,7 @@ function ExternalServicesStatusCheck() {
16
17
  const backendRoute = useBackendRoute();
17
18
  const httpClient = useHttpClient();
18
19
  React.useEffect(() => {
19
- dispatch(updateAppInfo({ frontendVersion: __APP_VERSION__ }));
20
+ dispatch(updateAppInfo({ frontendVersion: version }));
20
21
  setLoading(true);
21
22
  const checkServices = async () => {
22
23
  const services = [
@@ -1,10 +1,7 @@
1
1
  import React from 'react';
2
- import VisibilityIcon from '@mui/icons-material/Visibility';
3
- import DownloadIcon from '@mui/icons-material/Download';
4
- import EditIcon from '@mui/icons-material/Edit';
5
- import CheckIcon from '@mui/icons-material/Rule';
6
- import Tooltip from '@mui/material/Tooltip';
2
+ import { Visibility as VisibilityIcon, Download as DownloadIcon, Edit as EditIcon, Rule as CheckIcon } from '@mui/icons-material';
7
3
  import { useDispatch, useSelector, useStore } from 'react-redux';
4
+ import { Tooltip } from '@mui/material';
8
5
  import { isEqual } from 'lodash-es';
9
6
  import { cloningActions } from '@opencloning/store/cloning';
10
7
  import useStoreEditor from '../hooks/useStoreEditor';
@@ -1,9 +1,7 @@
1
1
  import React, { useRef } from 'react';
2
2
  import { useDispatch, useSelector } from 'react-redux';
3
3
  import { Box, Tooltip } from '@mui/material';
4
- import VisibilityOffIcon from '@mui/icons-material/VisibilityOff';
5
- import VisibilityIcon from '@mui/icons-material/Visibility';
6
- import AddCircleIcon from '@mui/icons-material/AddCircle';
4
+ import { VisibilityOff as VisibilityOffIcon, Visibility as VisibilityIcon, AddCircle as AddCircleIcon } from '@mui/icons-material';
7
5
  import { isEqual } from 'lodash-es';
8
6
  import Source from './sources/Source';
9
7
  import './NetworkTree.css';
@@ -1,6 +1,6 @@
1
1
  import React, { useRef, useState } from 'react';
2
2
  import { useDispatch, useSelector } from 'react-redux';
3
- import Tabs from '@mui/material/Tabs';
3
+ import { Tabs } from '@mui/material';
4
4
  import { isEqual } from 'lodash-es';
5
5
  import DescriptionEditor from './DescriptionEditor';
6
6
  import PrimerList from './primers/PrimerList';
@@ -13,6 +13,7 @@ import CloningHistory from './CloningHistory';
13
13
  import SequenceTab from './SequenceTab';
14
14
  import AppAlerts from './AppAlerts';
15
15
  import Assembler from './assembler/Assembler';
16
+ import { useConfig } from '../hooks/useConfig';
16
17
 
17
18
  const { setCurrentTab } = cloningActions;
18
19
 
@@ -21,8 +22,7 @@ function OpenCloning() {
21
22
  const currentTab = useSelector((state) => state.cloning.currentTab);
22
23
  const tabPanelsRef = useRef(null);
23
24
  const [smallDevice, setSmallDevice] = useState(window.innerWidth < 600);
24
- const hasAppBar = useSelector((state) => state.cloning.config.showAppBar, isEqual);
25
- const enableAssembler = useSelector((state) => state.cloning.config.enableAssembler);
25
+ const { showAppBar: hasAppBar, enableAssembler } = useConfig();
26
26
 
27
27
  React.useEffect(() => {
28
28
  const handleResize = () => {
@@ -1,6 +1,6 @@
1
1
  import React from 'react'
2
2
  import { TextField, FormControl, InputLabel, Select, MenuItem, Box, Grid, Paper, Typography, Table, TableContainer, TableHead, TableBody, TableRow, TableCell, Button } from '@mui/material'
3
- import ContentCopyIcon from '@mui/icons-material/ContentCopy'
3
+ import { ContentCopy as ContentCopyIcon } from '@mui/icons-material'
4
4
  import AssemblerPart from './AssemblerPart'
5
5
 
6
6
  /* eslint-disable camelcase */
@@ -1,10 +1,9 @@
1
1
  import React from 'react'
2
2
  import data2 from './assembler_data2.json'
3
3
  import { Alert, Autocomplete, Box, Button, CircularProgress, FormControl, IconButton, InputAdornment, InputLabel, MenuItem, Select, Stack, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TextField } from '@mui/material'
4
- import ClearIcon from '@mui/icons-material/Clear';
4
+ import { Clear as ClearIcon, Visibility as VisibilityIcon } from '@mui/icons-material';
5
5
  import { useAssembler } from './useAssembler';
6
6
  import { arrayCombinations } from '../eLabFTW/utils';
7
- import VisibilityIcon from '@mui/icons-material/Visibility';
8
7
  import { useDispatch } from 'react-redux';
9
8
  import { cloningActions } from '@opencloning/store/cloning';
10
9
  import RequestStatusWrapper from '../form/RequestStatusWrapper';
@@ -1,5 +1,4 @@
1
- import SaveIcon from '@mui/icons-material/Save';
2
- import LinkIcon from '@mui/icons-material/Link';
1
+ import { Save as SaveIcon, Link as LinkIcon } from '@mui/icons-material';
3
2
  import GetSequenceFileAndDatabaseIdComponent from './GetSequenceFileAndDatabaseIdComponent';
4
3
 
5
4
  export default {
@@ -1,5 +1,4 @@
1
- import SaveIcon from '@mui/icons-material/Save';
2
- import LinkIcon from '@mui/icons-material/Link';
1
+ import { Save as SaveIcon, Link as LinkIcon } from '@mui/icons-material';
3
2
  import GetSequenceFileAndDatabaseIdComponent from './GetSequenceFileAndDatabaseIdComponent';
4
3
  import SubmitToDatabaseComponent from './SubmitToDatabaseComponent';
5
4
  import PrimersNotInDatabaseComponent from './PrimersNotInDatabaseComponent';
@@ -1,17 +1,20 @@
1
1
  import React from 'react';
2
2
  import EnzymeMultiSelect from './EnzymeMultiSelect';
3
- import store from '@opencloning/store';
4
- import { cloningActions } from '@opencloning/store/cloning';
3
+ import { ConfigProvider } from '@opencloning/ui/providers/ConfigProvider';
4
+
5
+ const config = {
6
+ backendUrl: 'http://127.0.0.1:8000',
7
+ };
5
8
 
6
- const { setConfig } = cloningActions;
7
9
  describe('<EnzymeMultiSelect />', () => {
8
- beforeEach(() => {
9
- store.dispatch(setConfig({ backendUrl: 'http://127.0.0.1:8000' }));
10
- });
11
10
  it('can add and remove enzymes, sets enzymes', () => {
12
11
  // see: https://on.cypress.io/mounting-react
13
12
  const setEnzymesSpy = cy.spy().as('setEnzymesSpy');
14
- cy.mount(<EnzymeMultiSelect setEnzymes={setEnzymesSpy} />);
13
+ cy.mount(
14
+ <ConfigProvider config={config}>
15
+ <EnzymeMultiSelect setEnzymes={setEnzymesSpy} />
16
+ </ConfigProvider>
17
+ );
15
18
  cy.get('.MuiInputBase-root').click();
16
19
  // All enzymes shown
17
20
  cy.get('div[role="presentation"]', { timeout: 20000 }).contains('AanI');
@@ -46,7 +49,11 @@ describe('<EnzymeMultiSelect />', () => {
46
49
  statusCode: 500,
47
50
  body: 'Server down',
48
51
  });
49
- cy.mount(<EnzymeMultiSelect setEnzymes={() => {}} />);
52
+ cy.mount(
53
+ <ConfigProvider config={config}>
54
+ <EnzymeMultiSelect setEnzymes={() => {}} />
55
+ </ConfigProvider>
56
+ );
50
57
  cy.get('.MuiAlert-message').contains('Could not retrieve enzymes from server');
51
58
  });
52
59
  it('shows loading message', () => {
@@ -54,7 +61,11 @@ describe('<EnzymeMultiSelect />', () => {
54
61
  delayMs: 1000,
55
62
  body: { enzyme_names: ['EcoRI', 'SalI'] },
56
63
  });
57
- cy.mount(<EnzymeMultiSelect setEnzymes={() => {}} />);
64
+ cy.mount(
65
+ <ConfigProvider config={config}>
66
+ <EnzymeMultiSelect setEnzymes={() => {}} />
67
+ </ConfigProvider>
68
+ );
58
69
  cy.get('.MuiCircularProgress-svg');
59
70
  cy.contains('retrieving enzymes...').should('exist');
60
71
  });
@@ -1,7 +1,5 @@
1
1
  import * as React from 'react';
2
- import Autocomplete from '@mui/material/Autocomplete';
3
- import TextField from '@mui/material/TextField';
4
- import { Alert, Button, CircularProgress, FormControl, FormHelperText, InputLabel, MenuItem, Select } from '@mui/material';
2
+ import { Autocomplete, TextField, Alert, Button, CircularProgress, FormControl, FormHelperText, InputLabel, MenuItem, Select } from '@mui/material';
5
3
 
6
4
  export default function GetRequestMultiSelect({
7
5
  getOptionsFromResponse,
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import { Tooltip } from '@mui/material';
3
- import InfoIcon from '@mui/icons-material/Info';
3
+ import { Info as InfoIcon } from '@mui/icons-material';
4
4
 
5
5
  function LabelWithTooltip({ label, tooltip }) {
6
6
  return (
@@ -1,7 +1,5 @@
1
1
  import * as React from 'react';
2
- import Autocomplete from '@mui/material/Autocomplete';
3
- import TextField from '@mui/material/TextField';
4
- import { Alert, Button, FormControl } from '@mui/material';
2
+ import { Autocomplete, TextField, Alert, Button, FormControl } from '@mui/material';
5
3
 
6
4
  export default function PostRequestSelect({ setValue, getOptions, getOptionLabel, isOptionEqualToValue, textLabel, disableFiltering = true, ...rest }) {
7
5
  // The reason for disableFiltering is that we allow the server to filter the options,
@@ -1 +1,2 @@
1
1
  export { default as OpenCloning } from './OpenCloning';
2
+ export { default as MainAppBar } from './navigation/MainAppBar';
@@ -1,7 +1,5 @@
1
1
  import * as React from 'react';
2
- import Button from '@mui/material/Button';
3
- import Menu from '@mui/material/Menu';
4
- import MenuItem from '@mui/material/MenuItem';
2
+ import { Button, Menu, MenuItem } from '@mui/material';
5
3
 
6
4
  export default function ButtonWithMenu({ children, menuItems }) {
7
5
  const [anchorEl, setAnchorEl] = React.useState(null);
@@ -1,15 +1,8 @@
1
1
  import React from 'react';
2
- import AppBar from '@mui/material/AppBar';
3
- import Box from '@mui/material/Box';
4
- import Toolbar from '@mui/material/Toolbar';
5
- import Container from '@mui/material/Container';
6
- import { Button, Tooltip } from '@mui/material';
2
+ import { AppBar, Box, Toolbar, Container, Button, Tooltip, IconButton, Menu, MenuItem } from '@mui/material';
3
+ import { Menu as MenuIcon } from '@mui/icons-material';
7
4
  import './MainAppBar.css';
8
5
  import { useDispatch } from 'react-redux';
9
- import IconButton from '@mui/material/IconButton';
10
- import Menu from '@mui/material/Menu';
11
- import MenuItem from '@mui/material/MenuItem';
12
- import MenuIcon from '@mui/icons-material/Menu';
13
6
  import ButtonWithMenu from './ButtonWithMenu';
14
7
  import { downloadCloningStrategyAsSvg, formatTemplate, loadHistoryFile, loadFilesToSessionStorage } from '@opencloning/utils/readNwrite';
15
8
  import SelectExampleDialog from './SelectExampleDialog';
@@ -1,7 +1,7 @@
1
1
  import { Accordion, AccordionDetails, AccordionSummary, Alert, Button, CircularProgress, Dialog, DialogContent, DialogTitle, IconButton, List, ListItem, ListItemButton, ListItemText } from '@mui/material';
2
2
 
3
3
  import React from 'react';
4
- import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
4
+ import { ExpandMore as ExpandMoreIcon } from '@mui/icons-material';
5
5
  import useHttpClient from '../../hooks/useHttpClient';
6
6
 
7
7
  function SelectTemplateDialog({ onClose, open }) {
@@ -1,8 +1,6 @@
1
1
  import React from 'react';
2
- import CheckCircleIcon from '@mui/icons-material/CheckCircle';
3
- import CancelIcon from '@mui/icons-material/Cancel';
4
- import Tooltip from '@mui/material/Tooltip';
5
- import { IconButton } from '@mui/material';
2
+ import { CheckCircle as CheckCircleIcon, Cancel as CancelIcon } from '@mui/icons-material';
3
+ import { IconButton, Tooltip } from '@mui/material';
6
4
  import ValidatedTextField from '../form/ValidatedTextField';
7
5
 
8
6
  import './PrimerForm.css';
@@ -3,93 +3,101 @@ import PrimerList from './PrimerList';
3
3
  import store from '@opencloning/store';
4
4
  import { cloningActions } from '@opencloning/store/cloning';
5
5
  import { Provider } from 'react-redux';
6
+ import { ConfigProvider } from '@opencloning/ui/providers/ConfigProvider';
6
7
 
7
- const { setConfig, setPrimers, setGlobalPrimerSettings } = cloningActions;
8
+ const { setPrimers, setGlobalPrimerSettings } = cloningActions;
9
+
10
+ const config = {
11
+ backendUrl: 'http://127.0.0.1:8000',
12
+ };
8
13
 
9
14
  const mockReply = {
10
- statusCode: 200, body: {
11
- melting_temperature: 60, gc_content: .5, homodimer: {
12
- melting_temperature: 0,
13
- deltaG: 0,
14
- figure: "dummy_figure"
15
- },
16
- hairpin: {
17
- melting_temperature: 0,
18
- deltaG: 0,
19
- figure: "dummy_figure"
20
- },
21
- }
15
+ statusCode: 200, body: {
16
+ melting_temperature: 60, gc_content: .5, homodimer: {
17
+ melting_temperature: 0,
18
+ deltaG: 0,
19
+ figure: "dummy_figure"
20
+ },
21
+ hairpin: {
22
+ melting_temperature: 0,
23
+ deltaG: 0,
24
+ figure: "dummy_figure"
25
+ },
26
+ }
22
27
  }
23
28
 
24
29
  describe('PrimerList', () => {
25
- beforeEach(() => {
26
- store.dispatch(setConfig({ backendUrl: 'http://127.0.0.1:8000' }));
27
- });
28
- it('displays the right information', () => {
29
- store.dispatch(setPrimers([
30
- { id: 1, name: 'P1', sequence: 'TCATTAAAGTTAACG' },
31
- ]));
30
+ it('displays the right information', () => {
31
+ store.dispatch(setPrimers([
32
+ { id: 1, name: 'P1', sequence: 'TCATTAAAGTTAACG' },
33
+ ]));
32
34
 
33
- cy.mount(
34
- <Provider store={store}>
35
- <PrimerList />
36
- </Provider>
37
- );
38
- cy.get('td.name').contains('P1');
39
- cy.get('td.length').contains('15');
40
- cy.get('td.gc-content').contains('27');
41
- cy.get('td.melting-temperature').contains('37.5');
42
- cy.get('td.sequence').contains('TCATTAAAGTTAACG');
43
- });
44
-
45
- it('caches primer details across re-renders and re-renders on global settings change', () => {
46
- let calls = 0;
47
- store.dispatch(setPrimers([
48
- { id: 1, name: 'P1', sequence: 'AAA' },
49
- ]));
50
- cy.intercept('POST', 'http://127.0.0.1:8000/primer_details*', (req) => {
51
- calls += 1;
52
- const respReply = calls === 1 ? mockReply : {
53
- statusCode: 200, body: {
54
- ...mockReply.body,
55
- melting_temperature: calls === 1 ? 60 : 70,
56
- }
57
- }
58
- expect(req.body).to.deep.equal({
59
- sequence: 'AAA',
60
- settings: {
61
- primer_dna_conc: calls === 1 ? 50 : 100,
62
- primer_salt_monovalent: 50,
63
- primer_salt_divalent: 1.5,
64
- },
65
- });
66
- req.reply(respReply);
67
- }).as('primerDetails');
35
+ cy.mount(
36
+ <Provider store={store}>
37
+ <ConfigProvider config={config}>
38
+ <PrimerList />
39
+ </ConfigProvider>
40
+ </Provider>
41
+ );
42
+ cy.get('td.name').contains('P1');
43
+ cy.get('td.length').contains('15');
44
+ cy.get('td.gc-content').contains('27');
45
+ cy.get('td.melting-temperature').contains('37.5');
46
+ cy.get('td.sequence').contains('TCATTAAAGTTAACG');
47
+ });
68
48
 
69
- // First mount triggers two network calls (one per unique primer sequence)
70
- cy.mount(
71
- <Provider store={store}>
72
- <PrimerList />
73
- </Provider>)
74
- cy.contains('Loading...').should('not.exist');
75
- cy.wait('@primerDetails');
76
- cy.then(() => {
77
- expect(calls).to.equal(1);
78
- cy.mount(
79
- <Provider store={store}>
80
- <PrimerList />
81
- </Provider>)
82
- cy.then(() => {
83
- expect(calls).to.equal(1);
84
- });
85
- store.dispatch(setGlobalPrimerSettings({ primer_dna_conc: 100 }))
86
- cy.wait('@primerDetails');
87
- cy.then(() => {
88
- expect(calls).to.equal(2);
89
- cy.get('td.melting-temperature').contains('70');
90
- });
49
+ it('caches primer details across re-renders and re-renders on global settings change', () => {
50
+ let calls = 0;
51
+ store.dispatch(setPrimers([
52
+ { id: 1, name: 'P1', sequence: 'AAA' },
53
+ ]));
54
+ cy.intercept('POST', 'http://127.0.0.1:8000/primer_details*', (req) => {
55
+ calls += 1;
56
+ const respReply = calls === 1 ? mockReply : {
57
+ statusCode: 200, body: {
58
+ ...mockReply.body,
59
+ melting_temperature: calls === 1 ? 60 : 70,
60
+ }
61
+ }
62
+ expect(req.body).to.deep.equal({
63
+ sequence: 'AAA',
64
+ settings: {
65
+ primer_dna_conc: calls === 1 ? 50 : 100,
66
+ primer_salt_monovalent: 50,
67
+ primer_salt_divalent: 1.5,
68
+ },
69
+ });
70
+ req.reply(respReply);
71
+ }).as('primerDetails');
91
72
 
92
- });
73
+ // First mount triggers two network calls (one per unique primer sequence)
74
+ cy.mount(
75
+ <Provider store={store}>
76
+ <ConfigProvider config={config}>
77
+ <PrimerList />
78
+ </ConfigProvider>
79
+ </Provider>)
80
+ cy.contains('Loading...').should('not.exist');
81
+ cy.wait('@primerDetails');
82
+ cy.then(() => {
83
+ expect(calls).to.equal(1);
84
+ cy.mount(
85
+ <Provider store={store}>
86
+ <ConfigProvider config={config}>
87
+ <PrimerList />
88
+ </ConfigProvider>
89
+ </Provider>)
90
+ cy.then(() => {
91
+ expect(calls).to.equal(1);
92
+ });
93
+ store.dispatch(setGlobalPrimerSettings({ primer_dna_conc: 100 }))
94
+ cy.wait('@primerDetails');
95
+ cy.then(() => {
96
+ expect(calls).to.equal(2);
97
+ cy.get('td.melting-temperature').contains('70');
98
+ });
93
99
 
94
100
  });
101
+
102
+ });
95
103
  });
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import { shallowEqual, useDispatch, useSelector } from 'react-redux';
3
- import Button from '@mui/material/Button';
3
+ import { Button } from '@mui/material';
4
4
  import PrimerForm from './PrimerForm';
5
5
  import PrimerTableRow from './PrimerTableRow';
6
6
  import './PrimerList.css';