@opencloning/ui 1.0.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.
- package/CHANGELOG.md +18 -0
- package/package.json +37 -0
- package/src/components/AppAlerts.jsx +19 -0
- package/src/components/CloningHistory.jsx +40 -0
- package/src/components/DataModelDisplayer.jsx +30 -0
- package/src/components/DescriptionEditor.jsx +68 -0
- package/src/components/DownloadCloningStrategyDialog.jsx +84 -0
- package/src/components/DownloadSequenceFileDialog.jsx +90 -0
- package/src/components/DragAndDropCloningHistoryWrapper.jsx +39 -0
- package/src/components/DraggableDialogPaper.jsx +16 -0
- package/src/components/EditSequenceNameDialog.jsx +90 -0
- package/src/components/ExternalServicesStatusCheck.jsx +92 -0
- package/src/components/HistoryLoadedDialog.jsx +49 -0
- package/src/components/LoadCloningHistoryWrapper.jsx +166 -0
- package/src/components/MainSequenceCheckBox.jsx +83 -0
- package/src/components/MainSequenceEditor.jsx +165 -0
- package/src/components/NetworkNode.jsx +159 -0
- package/src/components/NetworkTree.css +127 -0
- package/src/components/ObjectTable.jsx +24 -0
- package/src/components/OpenCloning.jsx +102 -0
- package/src/components/OverhangsDisplay.jsx +25 -0
- package/src/components/SequenceEditor.jsx +120 -0
- package/src/components/SequenceTab.jsx +14 -0
- package/src/components/TemplateSequence.jsx +38 -0
- package/src/components/annotation/PlannotateAnnotationReport.jsx +33 -0
- package/src/components/annotation/useUpdateAnnotationInMainSequence.js +39 -0
- package/src/components/assembler/AssemblePartWidget.jsx +252 -0
- package/src/components/assembler/Assembler.jsx +273 -0
- package/src/components/assembler/AssemblerPart.jsx +99 -0
- package/src/components/assembler/StopIcon.jsx +34 -0
- package/src/components/assembler/assembler_data2.json +50 -0
- package/src/components/assembler/assembly_component.module.css +81 -0
- package/src/components/assembler/moclo.json +110 -0
- package/src/components/assembler/sbol_visual_glyphs/LICENSE.html +21 -0
- package/src/components/assembler/sbol_visual_glyphs/assembly-scar.svg +63 -0
- package/src/components/assembler/sbol_visual_glyphs/cds-stop.svg +85 -0
- package/src/components/assembler/sbol_visual_glyphs/cds.svg +60 -0
- package/src/components/assembler/sbol_visual_glyphs/chromosomal-locus.svg +78 -0
- package/src/components/assembler/sbol_visual_glyphs/engineered-region.svg +56 -0
- package/src/components/assembler/sbol_visual_glyphs/five-prime-sticky-restriction-site.svg +56 -0
- package/src/components/assembler/sbol_visual_glyphs/origin-of-replication.svg +57 -0
- package/src/components/assembler/sbol_visual_glyphs/primer-binding-site.svg +59 -0
- package/src/components/assembler/sbol_visual_glyphs/promoter.svg +60 -0
- package/src/components/assembler/sbol_visual_glyphs/ribosome-entry-site.svg +56 -0
- package/src/components/assembler/sbol_visual_glyphs/specific-recombination-site.svg +59 -0
- package/src/components/assembler/sbol_visual_glyphs/terminator.svg +60 -0
- package/src/components/assembler/sbol_visual_glyphs/three-prime-sticky-restriction-site.svg +56 -0
- package/src/components/assembler/sbol_visual_glyphs.js +36 -0
- package/src/components/assembler/useAssembler.js +71 -0
- package/src/components/dummy/DummyInterface.js +41 -0
- package/src/components/dummy/GetSequenceFileAndDatabaseIdComponent.jsx +59 -0
- package/src/components/eLabFTW/ELabFTWCategorySelect.cy.jsx +86 -0
- package/src/components/eLabFTW/ELabFTWCategorySelect.jsx +43 -0
- package/src/components/eLabFTW/ELabFTWFileSelect.cy.jsx +43 -0
- package/src/components/eLabFTW/ELabFTWFileSelect.jsx +29 -0
- package/src/components/eLabFTW/ELabFTWResourceSelect.cy.jsx +107 -0
- package/src/components/eLabFTW/ELabFTWResourceSelect.jsx +23 -0
- package/src/components/eLabFTW/GetPrimerComponent.cy.jsx +261 -0
- package/src/components/eLabFTW/GetPrimerComponent.jsx +55 -0
- package/src/components/eLabFTW/GetSequenceFileAndDatabaseIdComponent.cy.jsx +184 -0
- package/src/components/eLabFTW/GetSequenceFileAndDatabaseIdComponent.jsx +62 -0
- package/src/components/eLabFTW/LoadHistoryComponent.cy.jsx +235 -0
- package/src/components/eLabFTW/LoadHistoryComponent.jsx +51 -0
- package/src/components/eLabFTW/PrimersNotInDatabaseComponent.cy.jsx +159 -0
- package/src/components/eLabFTW/PrimersNotInDatabaseComponent.jsx +54 -0
- package/src/components/eLabFTW/SubmitToDatabaseComponent.cy.jsx +185 -0
- package/src/components/eLabFTW/SubmitToDatabaseComponent.jsx +51 -0
- package/src/components/eLabFTW/common.js +26 -0
- package/src/components/eLabFTW/eLabFTWInterface.js +294 -0
- package/src/components/eLabFTW/eLabFTWInterface.test.js +839 -0
- package/src/components/eLabFTW/envValues.js +7 -0
- package/src/components/eLabFTW/utils.js +39 -0
- package/src/components/form/CustomFormHelperText.jsx +10 -0
- package/src/components/form/EnzymeMultiSelect.cy.jsx +61 -0
- package/src/components/form/EnzymeMultiSelect.jsx +34 -0
- package/src/components/form/GetRequestMultiSelect.jsx +107 -0
- package/src/components/form/LabelWithTooltip.jsx +16 -0
- package/src/components/form/PostRequestSelect.cy.jsx +70 -0
- package/src/components/form/PostRequestSelect.jsx +86 -0
- package/src/components/form/RequestStatusWrapper.jsx +17 -0
- package/src/components/form/RetryAlert.jsx +20 -0
- package/src/components/form/ServerErrorMessage.jsx +10 -0
- package/src/components/form/SubmitButtonBackendAPI.jsx +15 -0
- package/src/components/form/SubmitToDatabaseDialog.jsx +133 -0
- package/src/components/form/TextFieldValidate.jsx +67 -0
- package/src/components/form/ValidatedTextField.jsx +33 -0
- package/src/components/form/intermediates_disclaimer.svg +181 -0
- package/src/components/navigation/ButtonWithMenu.jsx +43 -0
- package/src/components/navigation/CustomTab.jsx +14 -0
- package/src/components/navigation/FeedbackDialog.jsx +34 -0
- package/src/components/navigation/GithubCornerRight.jsx +29 -0
- package/src/components/navigation/MainAppBar.css +26 -0
- package/src/components/navigation/MainAppBar.jsx +205 -0
- package/src/components/navigation/SelectExampleDialog.jsx +69 -0
- package/src/components/navigation/SelectTemplateDialog.jsx +107 -0
- package/src/components/navigation/TabPanel.jsx +28 -0
- package/src/components/navigation/VersionDialog.jsx +33 -0
- package/src/components/primers/CreatePrimerFromSequenceForm.jsx +42 -0
- package/src/components/primers/DownloadPrimersButton.jsx +104 -0
- package/src/components/primers/PrimerForm.css +14 -0
- package/src/components/primers/PrimerForm.jsx +107 -0
- package/src/components/primers/PrimerList.css +46 -0
- package/src/components/primers/PrimerList.cy.jsx +95 -0
- package/src/components/primers/PrimerList.jsx +126 -0
- package/src/components/primers/PrimerTableRow.cy.jsx +57 -0
- package/src/components/primers/PrimerTableRow.jsx +84 -0
- package/src/components/primers/SelectPrimerForm.jsx +66 -0
- package/src/components/primers/import_primers/ImportPrimersButton.jsx +101 -0
- package/src/components/primers/import_primers/ImportPrimersTable.jsx +51 -0
- package/src/components/primers/import_primers/PrimerDatabaseImportForm.jsx +107 -0
- package/src/components/primers/import_primers/styles.css +60 -0
- package/src/components/primers/primer_design/SequenceTabComponents/CollapsableLabel.jsx +31 -0
- package/src/components/primers/primer_design/SequenceTabComponents/GatewayRoiSelect.jsx +164 -0
- package/src/components/primers/primer_design/SequenceTabComponents/OrientationPicker.jsx +37 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignContext.jsx +369 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignEBIC.jsx +24 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignForm.jsx +29 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignGatewayBP.jsx +36 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignGibsonAssembly.jsx +26 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignHomologousRecombination.jsx +32 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignRestriction.jsx +25 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignSimplePair.jsx +25 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignStepper.jsx +53 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesigner.jsx +80 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerResultForm.jsx +49 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerSettingsForm.jsx +68 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerSpacerForm.jsx +84 -0
- package/src/components/primers/primer_design/SequenceTabComponents/RestrictionSpacerForm.jsx +85 -0
- package/src/components/primers/primer_design/SequenceTabComponents/StepNavigation.jsx +48 -0
- package/src/components/primers/primer_design/SequenceTabComponents/TabPanelEBICSettings.jsx +216 -0
- package/src/components/primers/primer_design/SequenceTabComponents/TabPanelResults.jsx +42 -0
- package/src/components/primers/primer_design/SequenceTabComponents/TabPanelSelectRoi.jsx +61 -0
- package/src/components/primers/primer_design/SequenceTabComponents/TabPannelSettings.jsx +59 -0
- package/src/components/primers/primer_design/SequenceTabComponents/primerDesignMinimalValues.json +5 -0
- package/src/components/primers/primer_design/SequenceTabComponents/useEBICPrimerDesignSettings.js +31 -0
- package/src/components/primers/primer_design/SequenceTabComponents/useEnzymePrimerDesignSettings.js +57 -0
- package/src/components/primers/primer_design/SequenceTabComponents/useGatewayPrimerDesignSettings.js +18 -0
- package/src/components/primers/primer_design/SequenceTabComponents/usePrimerDesignSettings.js +31 -0
- package/src/components/primers/primer_design/SourceComponents/PrimerDesignGatewayBP.jsx +88 -0
- package/src/components/primers/primer_design/SourceComponents/PrimerDesignGibsonAssembly.jsx +65 -0
- package/src/components/primers/primer_design/SourceComponents/PrimerDesignHomologousRecombination.jsx +84 -0
- package/src/components/primers/primer_design/SourceComponents/PrimerDesignSourceForm.jsx +74 -0
- package/src/components/primers/primer_design/common/NoAttPSitesError.jsx +31 -0
- package/src/components/primers/primer_details/PCRTable.cy.jsx +51 -0
- package/src/components/primers/primer_details/PCRTable.jsx +35 -0
- package/src/components/primers/primer_details/Primer3Figure.jsx +25 -0
- package/src/components/primers/primer_details/PrimerDetailsTds.jsx +39 -0
- package/src/components/primers/primer_details/PrimerInfoIcon.cy.jsx +137 -0
- package/src/components/primers/primer_details/PrimerInfoIcon.jsx +132 -0
- package/src/components/primers/primer_details/TableSection.jsx +17 -0
- package/src/components/primers/primer_details/primerDetailsFormatting.js +3 -0
- package/src/components/primers/primer_details/useMultiplePrimerDetails.js +29 -0
- package/src/components/primers/primer_details/usePCRDetails.js +47 -0
- package/src/components/primers/primer_details/usePrimerDetailsEndpoints.js +49 -0
- package/src/components/primers/primer_details/useSinglePrimerSequenceDetails.js +25 -0
- package/src/components/primers/primersToTabularFile.js +49 -0
- package/src/components/primers/primersToTabularFile.test.js +108 -0
- package/src/components/settings/SettingsTab.cy.jsx +267 -0
- package/src/components/settings/SettingsTab.jsx +170 -0
- package/src/components/sources/AssemblyPlanDisplayer.cy.jsx +22 -0
- package/src/components/sources/AssemblyPlanDisplayer.jsx +27 -0
- package/src/components/sources/CollectionSource.jsx +97 -0
- package/src/components/sources/FinishedSource.jsx +397 -0
- package/src/components/sources/KnownSourceErrors.jsx +50 -0
- package/src/components/sources/MultipleInputsSelector.jsx +63 -0
- package/src/components/sources/MultipleOutputsSelector.jsx +63 -0
- package/src/components/sources/NewSourceBox.jsx +37 -0
- package/src/components/sources/PCRUnitForm.jsx +102 -0
- package/src/components/sources/SingleInputSelector.jsx +36 -0
- package/src/components/sources/Source.jsx +125 -0
- package/src/components/sources/SourceAnnotation.jsx +44 -0
- package/src/components/sources/SourceAssembly.jsx +201 -0
- package/src/components/sources/SourceBox.css +18 -0
- package/src/components/sources/SourceBox.jsx +60 -0
- package/src/components/sources/SourceCopySequence.jsx +38 -0
- package/src/components/sources/SourceDatabase.jsx +28 -0
- package/src/components/sources/SourceFile.jsx +188 -0
- package/src/components/sources/SourceGenomeRegion.cy.jsx +131 -0
- package/src/components/sources/SourceGenomeRegion.jsx +486 -0
- package/src/components/sources/SourceHomologousRecombination.jsx +125 -0
- package/src/components/sources/SourceKnownGenomeRegion.jsx +60 -0
- package/src/components/sources/SourceManuallyTyped.jsx +116 -0
- package/src/components/sources/SourcePCRorHybridization.jsx +165 -0
- package/src/components/sources/SourcePolymeraseExtension.jsx +44 -0
- package/src/components/sources/SourceRepositoryId.jsx +409 -0
- package/src/components/sources/SourceRestriction.jsx +41 -0
- package/src/components/sources/SourceReverseComplement.jsx +33 -0
- package/src/components/sources/SourceTypeSelector.jsx +94 -0
- package/src/components/sources/SubSequenceDisplayer.jsx +70 -0
- package/src/components/sources/VerifyDeleteDialog.jsx +23 -0
- package/src/components/sources/repositoryMetadata.js +14 -0
- package/src/components/verification/LoadFromDatabaseButton.jsx +90 -0
- package/src/components/verification/SequencingFileRow.jsx +34 -0
- package/src/components/verification/VerificationFileDialog.cy.jsx +176 -0
- package/src/components/verification/VerificationFileDialog.jsx +248 -0
- package/src/config/defaultMainEditorProps.js +44 -0
- package/src/hooks/useAlerts.js +16 -0
- package/src/hooks/useBackendAPI.js +51 -0
- package/src/hooks/useBackendRoute.js +22 -0
- package/src/hooks/useDatabase.js +18 -0
- package/src/hooks/useDragAndDropFile.js +31 -0
- package/src/hooks/useGatewaySites.js +40 -0
- package/src/hooks/useHttpClient.js +12 -0
- package/src/hooks/useLoadDatabaseFile.js +108 -0
- package/src/hooks/useStoreEditor.js +101 -0
- package/src/hooks/useValidateState.js +43 -0
- package/vitest.config.js +18 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import ELabFTWCategorySelect from './ELabFTWCategorySelect';
|
|
3
|
+
import { eLabFTWHttpClient } from './common';
|
|
4
|
+
|
|
5
|
+
describe('<ELabFTWCategorySelect />', () => {
|
|
6
|
+
it('Allows to retry if the request fails', () => {
|
|
7
|
+
cy.stub(eLabFTWHttpClient, 'get').withArgs('/api/v2/info', { headers: { Authorization: 'test-read-key' } }).as('eLabFTWHttpClientSpy');
|
|
8
|
+
cy.mount(<ELabFTWCategorySelect fullWidth />);
|
|
9
|
+
cy.get('@eLabFTWHttpClientSpy.all').should('have.callCount', 1);
|
|
10
|
+
cy.get('button').contains('Retry').click();
|
|
11
|
+
cy.get('@eLabFTWHttpClientSpy.all').should('have.callCount', 2);
|
|
12
|
+
});
|
|
13
|
+
it('shows the right options for eLabFTW version 50300', () => {
|
|
14
|
+
const setCategorySpy = cy.spy().as('setCategorySpy');
|
|
15
|
+
cy.stub(eLabFTWHttpClient, 'get').withArgs('/api/v2/info', { headers: { Authorization: 'test-read-key' } }).resolves({
|
|
16
|
+
data: {
|
|
17
|
+
elabftw_version_int: 50300,
|
|
18
|
+
},
|
|
19
|
+
}).withArgs('/api/v2/teams/current/resources_categories', { headers: { Authorization: 'test-read-key' }, params: { limit: 9999 } }).resolves({
|
|
20
|
+
data: [
|
|
21
|
+
{ id: 1, title: 'Category 1' },
|
|
22
|
+
{ id: 2, title: 'Category 2' },
|
|
23
|
+
],
|
|
24
|
+
});
|
|
25
|
+
cy.mount(<ELabFTWCategorySelect fullWidth setCategory={setCategorySpy} />);
|
|
26
|
+
cy.get('.MuiAutocomplete-root').click();
|
|
27
|
+
cy.get('li').contains('Category 1').should('exist');
|
|
28
|
+
cy.get('li').contains('Category 2').should('exist');
|
|
29
|
+
cy.get('li').contains('Category 1').click();
|
|
30
|
+
cy.get('@setCategorySpy').should('have.been.calledWith', { id: 1, title: 'Category 1' });
|
|
31
|
+
});
|
|
32
|
+
it('shows the right options', () => {
|
|
33
|
+
const setCategorySpy = cy.spy().as('setCategorySpy');
|
|
34
|
+
cy.stub(eLabFTWHttpClient, 'get')
|
|
35
|
+
.withArgs('/api/v2/items_types', { headers: { Authorization: 'test-read-key' }, params: { limit: 9999 } }).resolves({
|
|
36
|
+
data: [
|
|
37
|
+
{ id: 1, title: 'Category 1' },
|
|
38
|
+
{ id: 2, title: 'Category 2' },
|
|
39
|
+
],
|
|
40
|
+
})
|
|
41
|
+
.withArgs('/api/v2/info', { headers: { Authorization: 'test-read-key' } }).resolves({
|
|
42
|
+
data: {
|
|
43
|
+
elabftw_version_int: 50200,
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
cy.mount(<ELabFTWCategorySelect fullWidth setCategory={setCategorySpy} />);
|
|
47
|
+
cy.get('.MuiAutocomplete-root').click();
|
|
48
|
+
cy.get('li').contains('Category 1').should('exist');
|
|
49
|
+
cy.get('li').contains('Category 2').should('exist');
|
|
50
|
+
cy.get('li').contains('Category 1').click();
|
|
51
|
+
cy.get('@setCategorySpy').should('have.been.calledWith', { id: 1, title: 'Category 1' });
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('shows empty options if no categories are found', () => {
|
|
55
|
+
cy.mount(<ELabFTWCategorySelect fullWidth />);
|
|
56
|
+
cy.stub(eLabFTWHttpClient, 'get')
|
|
57
|
+
.withArgs('/api/v2/items_types', { headers: { Authorization: 'test-read-key' }, params: { limit: 9999 } }).resolves({
|
|
58
|
+
data: [],
|
|
59
|
+
})
|
|
60
|
+
.withArgs('/api/v2/info', { headers: { Authorization: 'test-read-key' } }).resolves({
|
|
61
|
+
data: {
|
|
62
|
+
elabftw_version_int: 50200,
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
cy.get('.MuiAutocomplete-root').click();
|
|
66
|
+
cy.get('li').should('not.exist');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('shows an error message if the request fails and can retry', () => {
|
|
70
|
+
cy.mount(<ELabFTWCategorySelect fullWidth />);
|
|
71
|
+
cy.stub(eLabFTWHttpClient, 'get')
|
|
72
|
+
.withArgs('/api/v2/info', { headers: { Authorization: 'test-read-key' } })
|
|
73
|
+
.resolves({
|
|
74
|
+
data: {
|
|
75
|
+
elabftw_version_int: 50300,
|
|
76
|
+
},
|
|
77
|
+
}).as('eLabFTWHttpClientSpyInfo')
|
|
78
|
+
.withArgs('/api/v2/teams/current/resources_categories', { headers: { Authorization: 'test-read-key' }, params: { limit: 9999 } })
|
|
79
|
+
.rejects(new Error('Connection error')).as('eLabFTWHttpClientSpy');
|
|
80
|
+
cy.get('.MuiAlert-message').should('contain', 'Could not retrieve categories');
|
|
81
|
+
// Clicking the retry button makes the request again
|
|
82
|
+
cy.get('button').contains('Retry').click();
|
|
83
|
+
cy.get('@eLabFTWHttpClientSpy').should('have.callCount', 2);
|
|
84
|
+
cy.get('@eLabFTWHttpClientSpyInfo').should('have.callCount', 1);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import GetRequestMultiSelect from '../form/GetRequestMultiSelect';
|
|
3
|
+
import { eLabFTWHttpClient, getELabFTWVersion, readHeaders } from './common';
|
|
4
|
+
import RequestStatusWrapper from '../form/RequestStatusWrapper';
|
|
5
|
+
|
|
6
|
+
function ELabFTWCategorySelect({ setCategory, label = 'Resource category', ...rest }) {
|
|
7
|
+
const [eLabFTWVersion, setELabFTWVersion] = React.useState(null);
|
|
8
|
+
const [requestStatus, setRequestStatus] = React.useState({ status: 'loading' });
|
|
9
|
+
const [retry, setRetry] = React.useState(0);
|
|
10
|
+
React.useEffect(() => {
|
|
11
|
+
setRequestStatus({ status: 'loading' });
|
|
12
|
+
getELabFTWVersion().then(
|
|
13
|
+
(version) => {
|
|
14
|
+
setELabFTWVersion(version);
|
|
15
|
+
setRequestStatus({ status: 'success' });
|
|
16
|
+
}
|
|
17
|
+
).catch(() => setRequestStatus({ status: 'error', message: 'Could not retrieve eLabFTW version' }));
|
|
18
|
+
}, [retry]);
|
|
19
|
+
const url = eLabFTWVersion && eLabFTWVersion >= 50300 ? '/api/v2/teams/current/resources_categories' : '/api/v2/items_types';
|
|
20
|
+
const getOptionsFromResponse = (data) => data;
|
|
21
|
+
const messages = { loadingMessage: 'retrieving categories', errorMessage: 'Could not retrieve categories from eLab' };
|
|
22
|
+
const onChange = (value) => setCategory(value);
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<RequestStatusWrapper requestStatus={requestStatus} retry={() => { setRetry(retry + 1); }}>
|
|
26
|
+
<GetRequestMultiSelect
|
|
27
|
+
getOptionsFromResponse={getOptionsFromResponse}
|
|
28
|
+
httpClient={eLabFTWHttpClient}
|
|
29
|
+
requestHeaders={readHeaders}
|
|
30
|
+
url={url}
|
|
31
|
+
requestParams={{ limit: 9999 }}
|
|
32
|
+
label={label}
|
|
33
|
+
messages={messages}
|
|
34
|
+
onChange={onChange}
|
|
35
|
+
getOptionLabel={(option) => (option === '' ? '' : option.title)}
|
|
36
|
+
multiple={false}
|
|
37
|
+
{...rest}
|
|
38
|
+
/>
|
|
39
|
+
</RequestStatusWrapper>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export default ELabFTWCategorySelect;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import ELabFTWFileSelect from './ELabFTWFileSelect';
|
|
3
|
+
import { eLabFTWHttpClient } from './common';
|
|
4
|
+
|
|
5
|
+
describe('<ELabFTWFileSelect />', () => {
|
|
6
|
+
it('shows the right options', () => {
|
|
7
|
+
const setFileInfoSpy = cy.spy().as('setFileInfoSpy');
|
|
8
|
+
// Stub the eLabFTWHttpClient similar to the pattern in eLabFTWInterface.test.js
|
|
9
|
+
cy.stub(eLabFTWHttpClient, 'get').withArgs('/api/v2/items/1', { headers: { Authorization: 'test-read-key' }, params: {} }).resolves({
|
|
10
|
+
data: {
|
|
11
|
+
uploads: [
|
|
12
|
+
{ id: 1, real_name: 'file1.txt' },
|
|
13
|
+
{ id: 2, real_name: 'file2.txt' },
|
|
14
|
+
],
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
cy.mount(<ELabFTWFileSelect fullWidth itemId={1} setFileInfo={setFileInfoSpy} />);
|
|
18
|
+
cy.get('div.MuiSelect-select').click();
|
|
19
|
+
cy.get('li').contains('file1.txt').should('exist');
|
|
20
|
+
cy.get('li').contains('file2.txt').should('exist');
|
|
21
|
+
cy.get('li').contains('file1.txt').click();
|
|
22
|
+
cy.get('@setFileInfoSpy').should('have.been.calledWith', { id: 1, real_name: 'file1.txt' });
|
|
23
|
+
});
|
|
24
|
+
it('shows empty options if no files are found', () => {
|
|
25
|
+
cy.mount(<ELabFTWFileSelect fullWidth itemId={1} />);
|
|
26
|
+
cy.stub(eLabFTWHttpClient, 'get').withArgs('/api/v2/items/1', { headers: { Authorization: 'test-read-key' }, params: {} }).resolves({
|
|
27
|
+
data: {
|
|
28
|
+
uploads: [],
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
cy.get('div.MuiSelect-select').click();
|
|
32
|
+
cy.get('li').should('not.exist');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('shows an error message if the request fails and can retry', () => {
|
|
36
|
+
cy.mount(<ELabFTWFileSelect fullWidth itemId={1} />);
|
|
37
|
+
cy.get('.MuiAlert-message').should('contain', 'Could not retrieve attachment');
|
|
38
|
+
// Clicking the retry button makes the request again
|
|
39
|
+
cy.spy(eLabFTWHttpClient, 'get').as('eLabFTWHttpClientSpy');
|
|
40
|
+
cy.get('button').contains('Retry').click();
|
|
41
|
+
cy.get('@eLabFTWHttpClientSpy').should('have.been.calledWith', '/api/v2/items/1', { headers: { Authorization: 'test-read-key' }, params: {} });
|
|
42
|
+
});
|
|
43
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import GetRequestMultiSelect from '../form/GetRequestMultiSelect';
|
|
3
|
+
import { eLabFTWHttpClient, readHeaders } from './common';
|
|
4
|
+
|
|
5
|
+
function ELabFTWFileSelect({ itemId, setFileInfo, ...rest }) {
|
|
6
|
+
const url = `/api/v2/items/${itemId}`;
|
|
7
|
+
const getOptionsFromResponse = ({ uploads }) => uploads;
|
|
8
|
+
const label = 'File with sequence';
|
|
9
|
+
const messages = { loadingMessage: 'retrieving attachments', errorMessage: 'Could not retrieve attachment from eLab' };
|
|
10
|
+
const onChange = (realName, options) => setFileInfo(options.find((option) => option.real_name === realName));
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<GetRequestMultiSelect
|
|
14
|
+
getOptionsFromResponse={getOptionsFromResponse}
|
|
15
|
+
httpClient={eLabFTWHttpClient}
|
|
16
|
+
requestHeaders={readHeaders}
|
|
17
|
+
url={url}
|
|
18
|
+
label={label}
|
|
19
|
+
messages={messages}
|
|
20
|
+
onChange={onChange}
|
|
21
|
+
getOptionLabel={(option) => (option === '' ? '' : option.real_name)}
|
|
22
|
+
multiple={false}
|
|
23
|
+
autoComplete={false}
|
|
24
|
+
{...rest}
|
|
25
|
+
/>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export default ELabFTWFileSelect;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import ELabFTWResourceSelect from './ELabFTWResourceSelect';
|
|
3
|
+
import { eLabFTWHttpClient } from './common';
|
|
4
|
+
|
|
5
|
+
describe('<ELabFTWResourceSelect />', () => {
|
|
6
|
+
it('shows the right options when searching', () => {
|
|
7
|
+
const setResourceSpy = cy.spy().as('setResourceSpy');
|
|
8
|
+
cy.stub(eLabFTWHttpClient, 'get')
|
|
9
|
+
.withArgs('/api/v2/items', {
|
|
10
|
+
headers: { Authorization: 'test-read-key' },
|
|
11
|
+
params: { cat: 1, extended: 'title:test', limit: 9999 },
|
|
12
|
+
})
|
|
13
|
+
.resolves({
|
|
14
|
+
data: [
|
|
15
|
+
{ id: 1, title: 'Test Resource 1' },
|
|
16
|
+
{ id: 2, title: 'Test Resource 2' },
|
|
17
|
+
],
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
cy.mount(<ELabFTWResourceSelect fullWidth categoryId={1} setResource={setResourceSpy} />);
|
|
21
|
+
|
|
22
|
+
// Type in the search field
|
|
23
|
+
cy.get('.MuiAutocomplete-input').type('test');
|
|
24
|
+
|
|
25
|
+
// Check if options are displayed
|
|
26
|
+
cy.get('li').contains('Test Resource 1').should('exist');
|
|
27
|
+
cy.get('li').contains('Test Resource 2').should('exist');
|
|
28
|
+
|
|
29
|
+
// Select an option
|
|
30
|
+
cy.get('li').contains('Test Resource 1').click();
|
|
31
|
+
cy.get('@setResourceSpy').should('have.been.calledWith', { id: 1, title: 'Test Resource 1' });
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('shows empty options if no resources are found', () => {
|
|
35
|
+
cy.stub(eLabFTWHttpClient, 'get')
|
|
36
|
+
.withArgs('/api/v2/items', {
|
|
37
|
+
headers: { Authorization: 'test-read-key' },
|
|
38
|
+
params: { cat: 1, extended: 'title:nonexistent', limit: 9999 },
|
|
39
|
+
})
|
|
40
|
+
.resolves({
|
|
41
|
+
data: [],
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
cy.mount(<ELabFTWResourceSelect fullWidth categoryId={1} />);
|
|
45
|
+
cy.get('.MuiAutocomplete-input').type('nonexistent');
|
|
46
|
+
cy.get('li').should('not.exist');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('handles API errors gracefully', () => {
|
|
50
|
+
let firstCall = true;
|
|
51
|
+
cy.stub(eLabFTWHttpClient, 'get')
|
|
52
|
+
.withArgs('/api/v2/items', {
|
|
53
|
+
headers: { Authorization: 'test-read-key' },
|
|
54
|
+
params: { cat: 1, extended: 'title:test', limit: 9999 },
|
|
55
|
+
})
|
|
56
|
+
.callsFake(() => {
|
|
57
|
+
if (firstCall) {
|
|
58
|
+
firstCall = false;
|
|
59
|
+
return Promise.reject(new Error('API Error'));
|
|
60
|
+
}
|
|
61
|
+
return Promise.resolve({ data: [
|
|
62
|
+
{ id: 1, title: 'Test Resource 1' },
|
|
63
|
+
{ id: 2, title: 'Test Resource 2' },
|
|
64
|
+
] });
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
cy.mount(<ELabFTWResourceSelect fullWidth categoryId={1} />);
|
|
68
|
+
cy.get('.MuiAutocomplete-input').type('test');
|
|
69
|
+
// The component should handle the error gracefully
|
|
70
|
+
cy.get('.MuiAlert-message').should('contain', 'Could not retrieve data');
|
|
71
|
+
// Clicking the retry button makes the request again
|
|
72
|
+
cy.get('button').contains('Retry').click();
|
|
73
|
+
// Verify the second call was made
|
|
74
|
+
cy.get('div.MuiInputBase-root').click();
|
|
75
|
+
cy.get('li').contains('Test Resource 1').should('exist');
|
|
76
|
+
cy.get('li').contains('Test Resource 2').should('exist');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('updates search results when categoryId changes', () => {
|
|
80
|
+
const getStub = cy.stub(eLabFTWHttpClient, 'get');
|
|
81
|
+
|
|
82
|
+
// First category results
|
|
83
|
+
getStub.withArgs('/api/v2/items', {
|
|
84
|
+
headers: { Authorization: 'test-read-key' },
|
|
85
|
+
params: { cat: 1, extended: 'title:test', limit: 9999 },
|
|
86
|
+
}).resolves({
|
|
87
|
+
data: [{ id: 1, title: 'Category 1 Resource' }],
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Second category results
|
|
91
|
+
getStub.withArgs('/api/v2/items', {
|
|
92
|
+
headers: { Authorization: 'test-read-key' },
|
|
93
|
+
params: { cat: 2, extended: 'title:test', limit: 9999 },
|
|
94
|
+
}).resolves({
|
|
95
|
+
data: [{ id: 2, title: 'Category 2 Resource' }],
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
cy.mount(<ELabFTWResourceSelect fullWidth categoryId={1} />);
|
|
99
|
+
cy.get('.MuiAutocomplete-input').type('test');
|
|
100
|
+
cy.get('li').contains('Category 1 Resource').should('exist');
|
|
101
|
+
|
|
102
|
+
// Change category and verify new results
|
|
103
|
+
cy.mount(<ELabFTWResourceSelect fullWidth categoryId={2} />);
|
|
104
|
+
cy.get('.MuiAutocomplete-input').type('test');
|
|
105
|
+
cy.get('li').contains('Category 2 Resource').should('exist');
|
|
106
|
+
});
|
|
107
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import PostRequestSelect from '../form/PostRequestSelect';
|
|
3
|
+
import { eLabFTWHttpClient, readHeaders } from './common';
|
|
4
|
+
|
|
5
|
+
function ELabFTWResourceSelect({ setResource, categoryId, ...rest }) {
|
|
6
|
+
const url = '/api/v2/items';
|
|
7
|
+
|
|
8
|
+
const resourcePostRequestSettings = React.useMemo(() => ({
|
|
9
|
+
setValue: setResource,
|
|
10
|
+
getOptions: async (userInput) => {
|
|
11
|
+
const resp = await eLabFTWHttpClient.get(url, { headers: readHeaders, params: { cat: categoryId, extended: `title:${userInput}`, limit: 9999 } });
|
|
12
|
+
return resp.data;
|
|
13
|
+
},
|
|
14
|
+
getOptionLabel: (option) => (option ? option.title : ''),
|
|
15
|
+
isOptionEqualToValue: (option, value) => option?.id === value?.id,
|
|
16
|
+
textLabel: 'Resource',
|
|
17
|
+
}), [setResource, categoryId]);
|
|
18
|
+
return (
|
|
19
|
+
<PostRequestSelect {...resourcePostRequestSettings} {...rest} />
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export default ELabFTWResourceSelect;
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import GetPrimerComponent from './GetPrimerComponent';
|
|
3
|
+
import { eLabFTWHttpClient } from './common';
|
|
4
|
+
|
|
5
|
+
const PRIMER_CATEGORY_ID = 3;
|
|
6
|
+
|
|
7
|
+
describe('<GetPrimerComponent />', () => {
|
|
8
|
+
it('shows category select and then resource select after category is chosen', () => {
|
|
9
|
+
const setPrimerSpy = cy.spy().as('setPrimerSpy');
|
|
10
|
+
const setErrorSpy = cy.spy().as('setErrorSpy');
|
|
11
|
+
|
|
12
|
+
// Stub both endpoints in a single stub
|
|
13
|
+
cy.stub(eLabFTWHttpClient, 'get').callsFake((url) => {
|
|
14
|
+
if (url === '/api/v2/items_types') {
|
|
15
|
+
return Promise.resolve({
|
|
16
|
+
data: [
|
|
17
|
+
{ id: PRIMER_CATEGORY_ID, title: 'Primers' },
|
|
18
|
+
{ id: 2, title: 'Other Category' },
|
|
19
|
+
],
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
return Promise.resolve({ data: [] });
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
cy.mount(<GetPrimerComponent setPrimer={setPrimerSpy} setError={setErrorSpy} />);
|
|
26
|
+
|
|
27
|
+
// Initially, only category select should be visible
|
|
28
|
+
cy.get('.MuiAutocomplete-root').should('have.length', 1);
|
|
29
|
+
|
|
30
|
+
// Select a category
|
|
31
|
+
cy.get('.MuiAutocomplete-input').first().click();
|
|
32
|
+
cy.get('.MuiAutocomplete-input').first().type('Primers');
|
|
33
|
+
cy.get('li').contains('Primers').click();
|
|
34
|
+
|
|
35
|
+
// Now resource select should appear
|
|
36
|
+
cy.get('.MuiAutocomplete-root').should('have.length', 2);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('successfully selects a primer with valid metadata', () => {
|
|
40
|
+
const setPrimerSpy = cy.spy().as('setPrimerSpy');
|
|
41
|
+
const setErrorSpy = cy.spy().as('setErrorSpy');
|
|
42
|
+
|
|
43
|
+
// Stub both endpoints in a single stub
|
|
44
|
+
cy.stub(eLabFTWHttpClient, 'get').callsFake((url, config) => {
|
|
45
|
+
if (url === '/api/v2/items_types') {
|
|
46
|
+
return Promise.resolve({
|
|
47
|
+
data: [{ id: PRIMER_CATEGORY_ID, title: 'Primers' }],
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
if (url === '/api/v2/items' && config?.params?.cat === PRIMER_CATEGORY_ID) {
|
|
51
|
+
return Promise.resolve({
|
|
52
|
+
data: [{
|
|
53
|
+
id: 1,
|
|
54
|
+
title: 'Test Primer',
|
|
55
|
+
metadata: JSON.stringify({
|
|
56
|
+
extra_fields: {
|
|
57
|
+
sequence: { value: 'ATCG' },
|
|
58
|
+
},
|
|
59
|
+
}),
|
|
60
|
+
}],
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
return Promise.resolve({ data: [] });
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
cy.mount(<GetPrimerComponent setPrimer={setPrimerSpy} setError={setErrorSpy} />);
|
|
67
|
+
|
|
68
|
+
// Select category
|
|
69
|
+
cy.get('.MuiAutocomplete-input').first().click();
|
|
70
|
+
cy.get('.MuiAutocomplete-input').first().type('Primers');
|
|
71
|
+
cy.get('li').contains('Primers').click();
|
|
72
|
+
|
|
73
|
+
// Select primer
|
|
74
|
+
cy.get('.MuiAutocomplete-input').last().type('test');
|
|
75
|
+
cy.get('li').contains('Test Primer').click();
|
|
76
|
+
|
|
77
|
+
// Check if setPrimer was called with correct data
|
|
78
|
+
cy.get('@setPrimerSpy').should('have.been.calledWith', {
|
|
79
|
+
name: 'Test Primer',
|
|
80
|
+
sequence: 'ATCG',
|
|
81
|
+
database_id: 1,
|
|
82
|
+
});
|
|
83
|
+
cy.get('@setErrorSpy').should('not.have.been.called');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('handles primer with invalid metadata', () => {
|
|
87
|
+
const setPrimerSpy = cy.spy().as('setPrimerSpy');
|
|
88
|
+
const setErrorSpy = cy.spy().as('setErrorSpy');
|
|
89
|
+
|
|
90
|
+
// Stub both endpoints in a single stub
|
|
91
|
+
cy.stub(eLabFTWHttpClient, 'get').callsFake((url, config) => {
|
|
92
|
+
if (url === '/api/v2/items_types') {
|
|
93
|
+
return Promise.resolve({
|
|
94
|
+
data: [{ id: PRIMER_CATEGORY_ID, title: 'Primers' }],
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
if (url === '/api/v2/items' && config?.params?.cat === PRIMER_CATEGORY_ID) {
|
|
98
|
+
return Promise.resolve({
|
|
99
|
+
data: [{
|
|
100
|
+
id: 1,
|
|
101
|
+
title: 'Invalid Primer',
|
|
102
|
+
metadata: JSON.stringify({
|
|
103
|
+
extra_fields: {
|
|
104
|
+
// Missing sequence field
|
|
105
|
+
},
|
|
106
|
+
}),
|
|
107
|
+
}],
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
if (url === '/api/v2/info') {
|
|
111
|
+
return Promise.resolve({
|
|
112
|
+
data: {
|
|
113
|
+
elabftw_version_int: 50200,
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
cy.mount(<GetPrimerComponent setPrimer={setPrimerSpy} setError={setErrorSpy} />);
|
|
120
|
+
|
|
121
|
+
// Select category
|
|
122
|
+
cy.get('.MuiAutocomplete-input').first().click();
|
|
123
|
+
cy.get('.MuiAutocomplete-input').first().type('Primers');
|
|
124
|
+
cy.get('li').contains('Primers').click();
|
|
125
|
+
|
|
126
|
+
// Select primer
|
|
127
|
+
cy.get('.MuiAutocomplete-input').last().type('test');
|
|
128
|
+
cy.get('li').contains('Invalid Primer').click();
|
|
129
|
+
|
|
130
|
+
// Check if error was set and primer was cleared
|
|
131
|
+
cy.get('@setErrorSpy').should('have.been.calledWith', 'No sequence found in metadata');
|
|
132
|
+
cy.get('@setPrimerSpy').should('have.been.calledWith', null);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('clears primer when category is cleared', () => {
|
|
136
|
+
const setPrimerSpy = cy.spy().as('setPrimerSpy');
|
|
137
|
+
const setErrorSpy = cy.spy().as('setErrorSpy');
|
|
138
|
+
|
|
139
|
+
cy.stub(eLabFTWHttpClient, 'get').callsFake((url) => {
|
|
140
|
+
if (url === '/api/v2/items_types') {
|
|
141
|
+
return Promise.resolve({
|
|
142
|
+
data: [{ id: PRIMER_CATEGORY_ID, title: 'Primers' }],
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
if (url === '/api/v2/info') {
|
|
146
|
+
return Promise.resolve({
|
|
147
|
+
data: {
|
|
148
|
+
elabftw_version_int: 50200,
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
cy.mount(<GetPrimerComponent setPrimer={setPrimerSpy} setError={setErrorSpy} />);
|
|
155
|
+
|
|
156
|
+
// Select category
|
|
157
|
+
cy.get('.MuiAutocomplete-input').first().click();
|
|
158
|
+
cy.get('.MuiAutocomplete-input').first().type('Primers');
|
|
159
|
+
cy.get('li').contains('Primers').click();
|
|
160
|
+
|
|
161
|
+
// Clear category
|
|
162
|
+
cy.get('.MuiAutocomplete-clearIndicator').first().click();
|
|
163
|
+
|
|
164
|
+
// Check if primer was cleared
|
|
165
|
+
cy.get('@setPrimerSpy').should('have.been.calledWith', null);
|
|
166
|
+
cy.get('.MuiAutocomplete-root').should('have.length', 1);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('clears error when resource is cleared', () => {
|
|
170
|
+
const setPrimerSpy = cy.spy().as('setPrimerSpy');
|
|
171
|
+
const setErrorSpy = cy.spy().as('setErrorSpy');
|
|
172
|
+
|
|
173
|
+
// Stub both endpoints in a single stub
|
|
174
|
+
cy.stub(eLabFTWHttpClient, 'get').callsFake((url, config) => {
|
|
175
|
+
if (url === '/api/v2/items_types') {
|
|
176
|
+
return Promise.resolve({
|
|
177
|
+
data: [{ id: PRIMER_CATEGORY_ID, title: 'Primers' }],
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
if (url === '/api/v2/items' && config?.params?.cat === PRIMER_CATEGORY_ID) {
|
|
181
|
+
return Promise.resolve({
|
|
182
|
+
data: [{
|
|
183
|
+
id: 1,
|
|
184
|
+
title: 'Invalid Primer',
|
|
185
|
+
metadata: '{invalid json}',
|
|
186
|
+
}],
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
if (url === '/api/v2/info') {
|
|
190
|
+
return Promise.resolve({
|
|
191
|
+
data: {
|
|
192
|
+
elabftw_version_int: 50200,
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
cy.mount(<GetPrimerComponent setPrimer={setPrimerSpy} setError={setErrorSpy} />);
|
|
199
|
+
|
|
200
|
+
// Select category
|
|
201
|
+
cy.get('.MuiAutocomplete-input').first().click();
|
|
202
|
+
cy.get('.MuiAutocomplete-input').first().type('Primers');
|
|
203
|
+
cy.get('li').contains('Primers').click();
|
|
204
|
+
|
|
205
|
+
// Select and then clear resource
|
|
206
|
+
cy.get('.MuiAutocomplete-input').last().type('test');
|
|
207
|
+
cy.get('li').contains('Invalid Primer').click();
|
|
208
|
+
cy.get('.MuiAutocomplete-clearIndicator').last().click();
|
|
209
|
+
|
|
210
|
+
// Check if error was cleared
|
|
211
|
+
cy.get('@setErrorSpy').should('have.been.calledWith', '');
|
|
212
|
+
cy.get('@setPrimerSpy').should('have.been.calledWith', null);
|
|
213
|
+
});
|
|
214
|
+
it('handles network errors', () => {
|
|
215
|
+
const setPrimerSpy = cy.spy().as('setPrimerSpy');
|
|
216
|
+
const setErrorSpy = cy.spy().as('setErrorSpy');
|
|
217
|
+
|
|
218
|
+
let firstCallCategory = true;
|
|
219
|
+
let firstCallPrimer = true;
|
|
220
|
+
cy.stub(eLabFTWHttpClient, 'get').callsFake((url, config) => {
|
|
221
|
+
if (url === '/api/v2/items_types') {
|
|
222
|
+
if (firstCallCategory) {
|
|
223
|
+
firstCallCategory = false;
|
|
224
|
+
return Promise.reject(new Error('Network error'));
|
|
225
|
+
}
|
|
226
|
+
return Promise.resolve({
|
|
227
|
+
data: [{ id: PRIMER_CATEGORY_ID, title: 'Primers' }],
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
if (url === '/api/v2/items' && config?.params?.cat === PRIMER_CATEGORY_ID) {
|
|
231
|
+
if (firstCallPrimer) {
|
|
232
|
+
firstCallPrimer = false;
|
|
233
|
+
return Promise.reject(new Error('Network error'));
|
|
234
|
+
}
|
|
235
|
+
return Promise.resolve({
|
|
236
|
+
data: [{ id: 1, title: 'Test Primer' }],
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
if (url === '/api/v2/info') {
|
|
240
|
+
return Promise.resolve({
|
|
241
|
+
data: {
|
|
242
|
+
elabftw_version_int: 50200,
|
|
243
|
+
},
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
cy.mount(<GetPrimerComponent setPrimer={setPrimerSpy} setError={setErrorSpy} />);
|
|
249
|
+
|
|
250
|
+
cy.get('.MuiAlert-message').should('contain', 'Could not retrieve categories');
|
|
251
|
+
cy.get('button').contains('Retry').click();
|
|
252
|
+
cy.get('.MuiAutocomplete-input').first().click();
|
|
253
|
+
cy.get('.MuiAutocomplete-input').first().type('Primers');
|
|
254
|
+
cy.get('li').contains('Primers').click();
|
|
255
|
+
cy.get('.MuiAutocomplete-input').last().type('test');
|
|
256
|
+
cy.get('.MuiAlert-message').should('contain', 'Could not retrieve');
|
|
257
|
+
cy.get('button').contains('Retry').click();
|
|
258
|
+
cy.get('.MuiAutocomplete-root').eq(1).click();
|
|
259
|
+
cy.get('li').contains('Test Primer').click();
|
|
260
|
+
});
|
|
261
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import React, { useCallback } from 'react';
|
|
2
|
+
import ELabFTWCategorySelect from './ELabFTWCategorySelect';
|
|
3
|
+
import ELabFTWResourceSelect from './ELabFTWResourceSelect';
|
|
4
|
+
|
|
5
|
+
function GetPrimerComponent({ primer, setPrimer, setError }) {
|
|
6
|
+
const [category, setCategory] = React.useState(null);
|
|
7
|
+
|
|
8
|
+
React.useEffect(() => {
|
|
9
|
+
if (category === null) {
|
|
10
|
+
setPrimer(null);
|
|
11
|
+
}
|
|
12
|
+
}, [category]);
|
|
13
|
+
|
|
14
|
+
const handleResourceSelect = useCallback(async (resource) => {
|
|
15
|
+
if (resource === null) {
|
|
16
|
+
setPrimer(null);
|
|
17
|
+
setError('');
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
let sequence;
|
|
21
|
+
try {
|
|
22
|
+
sequence = JSON.parse(resource.metadata).extra_fields?.sequence?.value;
|
|
23
|
+
if (!sequence) {
|
|
24
|
+
setError('No sequence found in metadata');
|
|
25
|
+
setPrimer(null);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
} catch (e) {
|
|
29
|
+
setError('No sequence found in metadata');
|
|
30
|
+
setPrimer(null);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
setPrimer({ name: resource.title, sequence, database_id: resource.id });
|
|
35
|
+
}, [setPrimer, setError]);
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<>
|
|
39
|
+
<ELabFTWCategorySelect
|
|
40
|
+
setCategory={setCategory}
|
|
41
|
+
fullWidth
|
|
42
|
+
/>
|
|
43
|
+
|
|
44
|
+
{category && (
|
|
45
|
+
<ELabFTWResourceSelect
|
|
46
|
+
setResource={handleResourceSelect}
|
|
47
|
+
categoryId={category.id}
|
|
48
|
+
fullWidth
|
|
49
|
+
/>
|
|
50
|
+
)}
|
|
51
|
+
</>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export default GetPrimerComponent;
|