@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,184 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import GetSequenceFileAndDatabaseIdComponent from './GetSequenceFileAndDatabaseIdComponent';
|
|
3
|
+
import { eLabFTWHttpClient } from './common';
|
|
4
|
+
import { clearAutocompleteValue, clickMultiSelectOption, setAutocompleteValue } from '../../../../../cypress/e2e/common_functions';
|
|
5
|
+
|
|
6
|
+
let uniqueId = 1;
|
|
7
|
+
const newUniqueId = () => uniqueId++;
|
|
8
|
+
const SEQUENCE_CATEGORY_ID = newUniqueId();
|
|
9
|
+
const OTHER_CATEGORY_ID = newUniqueId();
|
|
10
|
+
const SEQUENCE_RESOURCE_ID = newUniqueId();
|
|
11
|
+
const OTHER_RESOURCE_ID = newUniqueId();
|
|
12
|
+
const SEQUENCE_FILE_ID = newUniqueId();
|
|
13
|
+
const OTHER_FILE_ID = newUniqueId();
|
|
14
|
+
const TEST_FILE_CONTENT = 'test content';
|
|
15
|
+
|
|
16
|
+
const commonStubHandler = (url, config) => {
|
|
17
|
+
if (url === '/api/v2/items_types') {
|
|
18
|
+
return Promise.resolve({
|
|
19
|
+
data: [
|
|
20
|
+
{ id: SEQUENCE_CATEGORY_ID, title: 'Sequences' },
|
|
21
|
+
{ id: OTHER_CATEGORY_ID, title: 'Other Category' },
|
|
22
|
+
],
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
if (url === '/api/v2/items' && config?.params?.cat === 1) {
|
|
26
|
+
return Promise.resolve({
|
|
27
|
+
data: [{
|
|
28
|
+
id: SEQUENCE_RESOURCE_ID,
|
|
29
|
+
title: 'Test Sequence',
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
id: OTHER_RESOURCE_ID,
|
|
33
|
+
title: 'Other Resource',
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
if (url === `/api/v2/items/${SEQUENCE_RESOURCE_ID}`) {
|
|
39
|
+
return Promise.resolve({
|
|
40
|
+
data: {
|
|
41
|
+
uploads: [
|
|
42
|
+
{ id: SEQUENCE_FILE_ID, real_name: 'sequence.gb' },
|
|
43
|
+
{ id: OTHER_FILE_ID, real_name: 'other.txt' },
|
|
44
|
+
],
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
if (url === '/api/v2/info') {
|
|
49
|
+
return Promise.resolve({
|
|
50
|
+
data: {
|
|
51
|
+
elabftw_version_int: 50200,
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
describe('<GetSequenceFileAndDatabaseIdComponent />', () => {
|
|
59
|
+
it('normal case', () => {
|
|
60
|
+
const setFileSpy = cy.spy().as('setFileSpy');
|
|
61
|
+
const setDatabaseIdSpy = cy.spy().as('setDatabaseIdSpy');
|
|
62
|
+
|
|
63
|
+
// Stub API calls
|
|
64
|
+
cy.stub(eLabFTWHttpClient, 'get').callsFake((url, config) => {
|
|
65
|
+
const commonResponse = commonStubHandler(url, config);
|
|
66
|
+
if (commonResponse !== null) {
|
|
67
|
+
return commonResponse;
|
|
68
|
+
}
|
|
69
|
+
if (url === `/api/v2/items/${SEQUENCE_RESOURCE_ID}/uploads/${SEQUENCE_FILE_ID}?format=binary`) {
|
|
70
|
+
return Promise.resolve({ data: new Blob([TEST_FILE_CONTENT], { type: 'application/octet-stream' }) });
|
|
71
|
+
}
|
|
72
|
+
return Promise.resolve({ data: [] });
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
cy.mount(<GetSequenceFileAndDatabaseIdComponent setFile={setFileSpy} setDatabaseId={setDatabaseIdSpy} />);
|
|
76
|
+
|
|
77
|
+
// Initially, only category select should be visible
|
|
78
|
+
cy.get('.MuiAutocomplete-root').should('have.length', 1);
|
|
79
|
+
|
|
80
|
+
// Select a category
|
|
81
|
+
cy.get('.MuiAutocomplete-input').first().click();
|
|
82
|
+
cy.get('.MuiAutocomplete-input').first().type('Sequences');
|
|
83
|
+
cy.get('li').contains('Sequences').click();
|
|
84
|
+
|
|
85
|
+
// Resource select should appear
|
|
86
|
+
cy.get('.MuiAutocomplete-root').should('have.length', 2);
|
|
87
|
+
|
|
88
|
+
// Select a resource
|
|
89
|
+
cy.get('.MuiAutocomplete-input').last().type('Test');
|
|
90
|
+
cy.get('li').contains('Test Sequence').click();
|
|
91
|
+
|
|
92
|
+
// File select should appear
|
|
93
|
+
clickMultiSelectOption('File with sequence', 'sequence.gb', 'div');
|
|
94
|
+
// We must compare like this, because using have.been.calledWith does not compare files
|
|
95
|
+
cy.get('@setFileSpy').should((spy) => {
|
|
96
|
+
const calledFile = spy.lastCall.args[0]; // Get the first argument of the first call
|
|
97
|
+
expect(calledFile).to.be.instanceOf(File);
|
|
98
|
+
expect(calledFile.name).to.equal('sequence.gb');
|
|
99
|
+
// Read the file content and compare
|
|
100
|
+
const reader = new FileReader();
|
|
101
|
+
reader.onload = (e) => {
|
|
102
|
+
const actualContent = e.target.result;
|
|
103
|
+
expect(actualContent).to.equal(TEST_FILE_CONTENT);
|
|
104
|
+
};
|
|
105
|
+
reader.readAsText(calledFile);
|
|
106
|
+
});
|
|
107
|
+
cy.get('@setDatabaseIdSpy').should('have.been.calledWith', SEQUENCE_RESOURCE_ID);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('handles error when getting file', () => {
|
|
111
|
+
const setFileSpy = cy.spy().as('setFileSpy');
|
|
112
|
+
const setDatabaseIdSpy = cy.spy().as('setDatabaseIdSpy');
|
|
113
|
+
|
|
114
|
+
let firstCall = true;
|
|
115
|
+
cy.stub(eLabFTWHttpClient, 'get').callsFake((url, config) => {
|
|
116
|
+
const commonResponse = commonStubHandler(url, config);
|
|
117
|
+
if (commonResponse !== null) {
|
|
118
|
+
return commonResponse;
|
|
119
|
+
}
|
|
120
|
+
if (url === `/api/v2/items/${SEQUENCE_RESOURCE_ID}/uploads/${SEQUENCE_FILE_ID}?format=binary`) {
|
|
121
|
+
if (firstCall) {
|
|
122
|
+
firstCall = false;
|
|
123
|
+
return Promise.reject(new Error('File not found'));
|
|
124
|
+
}
|
|
125
|
+
return Promise.resolve({ data: new Blob([TEST_FILE_CONTENT], { type: 'application/octet-stream' }) });
|
|
126
|
+
}
|
|
127
|
+
return Promise.resolve({ data: [] });
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
cy.mount(<GetSequenceFileAndDatabaseIdComponent setFile={setFileSpy} setDatabaseId={setDatabaseIdSpy} />);
|
|
131
|
+
|
|
132
|
+
// Select a category
|
|
133
|
+
clickMultiSelectOption('Resource category', 'Sequences', 'div');
|
|
134
|
+
setAutocompleteValue('Resource', 'Test Sequence', '.elabftw-resource-select');
|
|
135
|
+
clickMultiSelectOption('File with sequence', 'sequence.gb', 'div');
|
|
136
|
+
cy.get('.MuiAlert-message').should('contain', 'Error loading file');
|
|
137
|
+
cy.get('button').contains('Retry').click();
|
|
138
|
+
cy.get('.MuiAlert-message').should('not.exist');
|
|
139
|
+
cy.get('@setFileSpy').should((spy) => {
|
|
140
|
+
const calledFile = spy.lastCall.args[0]; // Get the first argument of the first call
|
|
141
|
+
expect(calledFile).to.be.instanceOf(File);
|
|
142
|
+
expect(calledFile.name).to.equal('sequence.gb');
|
|
143
|
+
// Read the file content and compare
|
|
144
|
+
const reader = new FileReader();
|
|
145
|
+
reader.onload = (e) => {
|
|
146
|
+
const actualContent = e.target.result;
|
|
147
|
+
expect(actualContent).to.equal(TEST_FILE_CONTENT);
|
|
148
|
+
};
|
|
149
|
+
reader.readAsText(calledFile);
|
|
150
|
+
});
|
|
151
|
+
cy.get('@setDatabaseIdSpy').should('have.been.calledWith', SEQUENCE_RESOURCE_ID);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('unsets subsequent fields when an upper one changes', () => {
|
|
155
|
+
cy.stub(eLabFTWHttpClient, 'get').callsFake(commonStubHandler);
|
|
156
|
+
cy.mount(<GetSequenceFileAndDatabaseIdComponent setFile={cy.spy()} setDatabaseId={cy.spy()} />);
|
|
157
|
+
clickMultiSelectOption('Resource category', 'Sequences', 'div');
|
|
158
|
+
setAutocompleteValue('Resource', 'Test Sequence', '.elabftw-resource-select');
|
|
159
|
+
clickMultiSelectOption('File with sequence', 'sequence.gb', 'div');
|
|
160
|
+
|
|
161
|
+
// Unset category should clear the rest
|
|
162
|
+
clearAutocompleteValue('Resource category', 'div.elabftw-category-select');
|
|
163
|
+
cy.get('.elabftw-resource-select').should('not.exist');
|
|
164
|
+
cy.get('.elabftw-file-select').should('not.exist');
|
|
165
|
+
cy.get('.elabftw-category-select').click();
|
|
166
|
+
|
|
167
|
+
clickMultiSelectOption('Resource category', 'Sequences', 'div');
|
|
168
|
+
|
|
169
|
+
// Resource value should be unset
|
|
170
|
+
cy.get('.elabftw-resource-select input').should('have.value', '');
|
|
171
|
+
setAutocompleteValue('Resource', 'Test Sequence', '.elabftw-resource-select');
|
|
172
|
+
|
|
173
|
+
// File value should be unset
|
|
174
|
+
cy.get('.elabftw-file-select input').should('have.value', '');
|
|
175
|
+
clickMultiSelectOption('File with sequence', 'sequence.gb', 'div');
|
|
176
|
+
|
|
177
|
+
// Unset resource should clear file
|
|
178
|
+
clearAutocompleteValue('Resource', '.elabftw-resource-select');
|
|
179
|
+
cy.get('.elabftw-file-select').should('not.exist');
|
|
180
|
+
cy.get('.elabftw-resource-select').click();
|
|
181
|
+
setAutocompleteValue('Resource', 'Test Sequence', '.elabftw-resource-select');
|
|
182
|
+
cy.get('.elabftw-file-select input').should('have.value', '');
|
|
183
|
+
});
|
|
184
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import ELabFTWCategorySelect from './ELabFTWCategorySelect';
|
|
3
|
+
import ELabFTWResourceSelect from './ELabFTWResourceSelect';
|
|
4
|
+
import ELabFTWFileSelect from './ELabFTWFileSelect';
|
|
5
|
+
import { getFileFromELabFTW } from './utils';
|
|
6
|
+
import RetryAlert from '../form/RetryAlert';
|
|
7
|
+
|
|
8
|
+
function GetSequenceFileAndDatabaseIdComponent({ setFile, setDatabaseId }) {
|
|
9
|
+
const [category, setCategory] = React.useState(null);
|
|
10
|
+
const [resource, setResource] = React.useState(null);
|
|
11
|
+
const [fileInfo, setFileInfo] = React.useState(null);
|
|
12
|
+
const [fileLoadError, setFileLoadError] = React.useState('');
|
|
13
|
+
const [retry, setRetry] = React.useState(0);
|
|
14
|
+
|
|
15
|
+
// Reset if category changes
|
|
16
|
+
React.useEffect(() => {
|
|
17
|
+
setResource(null);
|
|
18
|
+
setFileInfo(null);
|
|
19
|
+
setFile(null);
|
|
20
|
+
setDatabaseId(null);
|
|
21
|
+
}, [category]);
|
|
22
|
+
|
|
23
|
+
// Reset if resource changes
|
|
24
|
+
React.useEffect(() => {
|
|
25
|
+
setFileInfo(null);
|
|
26
|
+
setFile(null);
|
|
27
|
+
setDatabaseId(null);
|
|
28
|
+
}, [resource]);
|
|
29
|
+
|
|
30
|
+
React.useEffect(() => {
|
|
31
|
+
setFile(null);
|
|
32
|
+
setDatabaseId(null);
|
|
33
|
+
}, [fileInfo]);
|
|
34
|
+
|
|
35
|
+
React.useEffect(() => {
|
|
36
|
+
const loadFile = async () => {
|
|
37
|
+
if (!resource || !fileInfo) return;
|
|
38
|
+
try {
|
|
39
|
+
const file = await getFileFromELabFTW(resource.id, fileInfo);
|
|
40
|
+
setFile(file);
|
|
41
|
+
setDatabaseId(resource.id);
|
|
42
|
+
setFileLoadError('');
|
|
43
|
+
} catch (error) {
|
|
44
|
+
setFileLoadError('Error loading file');
|
|
45
|
+
console.error('Error loading file', error);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
loadFile();
|
|
50
|
+
}, [resource, fileInfo, retry]);
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<>
|
|
54
|
+
<ELabFTWCategorySelect fullWidth setCategory={setCategory} className="elabftw-category-select" />
|
|
55
|
+
{category && <ELabFTWResourceSelect fullWidth setResource={setResource} categoryId={category.id} className="elabftw-resource-select" />}
|
|
56
|
+
{resource && <ELabFTWFileSelect fullWidth setFileInfo={setFileInfo} itemId={resource.id} className="elabftw-file-select" />}
|
|
57
|
+
{fileLoadError && <RetryAlert severity="error" onRetry={() => setRetry((prev) => prev + 1)}>{fileLoadError}</RetryAlert>}
|
|
58
|
+
</>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export default GetSequenceFileAndDatabaseIdComponent;
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import LoadHistoryComponent from './LoadHistoryComponent';
|
|
3
|
+
import { eLabFTWHttpClient } from './common';
|
|
4
|
+
|
|
5
|
+
let uniqueId = 1;
|
|
6
|
+
const newUniqueId = () => uniqueId++;
|
|
7
|
+
const DATABASE_ID = newUniqueId();
|
|
8
|
+
const HISTORY_FILE_ID = newUniqueId();
|
|
9
|
+
const OTHER_FILE_ID = newUniqueId();
|
|
10
|
+
const TEST_FILE_CONTENT = JSON.stringify({ test: 'content' });
|
|
11
|
+
|
|
12
|
+
describe('<LoadHistoryComponent />', () => {
|
|
13
|
+
it('loads history file successfully when one file exists', () => {
|
|
14
|
+
const handleCloseSpy = cy.spy().as('handleCloseSpy');
|
|
15
|
+
const loadDatabaseFileSpy = cy.spy().as('loadDatabaseFileSpy');
|
|
16
|
+
|
|
17
|
+
// Stub API calls
|
|
18
|
+
cy.stub(eLabFTWHttpClient, 'get').callsFake(async (url) => {
|
|
19
|
+
if (url === `/api/v2/items/${DATABASE_ID}`) {
|
|
20
|
+
return new Promise((resolve) => {
|
|
21
|
+
// Timeout to catch the loading state
|
|
22
|
+
setTimeout(() => {
|
|
23
|
+
resolve({
|
|
24
|
+
data: {
|
|
25
|
+
uploads: [
|
|
26
|
+
{
|
|
27
|
+
id: HISTORY_FILE_ID,
|
|
28
|
+
real_name: 'history.json',
|
|
29
|
+
comment: 'OpenCloning history',
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
}, 500);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
if (url === `/api/v2/items/${DATABASE_ID}/uploads/${HISTORY_FILE_ID}?format=binary`) {
|
|
38
|
+
return Promise.resolve({ data: new Blob([TEST_FILE_CONTENT], { type: 'application/json' }) });
|
|
39
|
+
}
|
|
40
|
+
return Promise.resolve({ data: [] });
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
cy.mount(
|
|
44
|
+
<LoadHistoryComponent
|
|
45
|
+
handleClose={handleCloseSpy}
|
|
46
|
+
databaseId={DATABASE_ID}
|
|
47
|
+
loadDatabaseFile={loadDatabaseFileSpy}
|
|
48
|
+
/>,
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
// Should show loading initially
|
|
52
|
+
cy.get('.MuiCircularProgress-root').should('exist');
|
|
53
|
+
|
|
54
|
+
// Should call loadDatabaseFile with the correct arguments
|
|
55
|
+
cy.get('@loadDatabaseFileSpy').should((spy) => {
|
|
56
|
+
const [file, id, isHistory] = spy.lastCall.args;
|
|
57
|
+
expect(file).to.be.instanceOf(File);
|
|
58
|
+
expect(file.name).to.equal('history.json');
|
|
59
|
+
expect(id).to.equal(DATABASE_ID);
|
|
60
|
+
expect(isHistory).to.equal(true);
|
|
61
|
+
|
|
62
|
+
// Verify file content
|
|
63
|
+
const reader = new FileReader();
|
|
64
|
+
reader.onload = (e) => {
|
|
65
|
+
const actualContent = e.target.result;
|
|
66
|
+
expect(actualContent).to.equal(TEST_FILE_CONTENT);
|
|
67
|
+
};
|
|
68
|
+
reader.readAsText(file);
|
|
69
|
+
return undefined; // Satisfy linter
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Loading indicator should be gone
|
|
73
|
+
cy.get('.MuiCircularProgress-root').should('not.exist');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('shows error when multiple history files found', () => {
|
|
77
|
+
const handleCloseSpy = cy.spy().as('handleCloseSpy');
|
|
78
|
+
const loadDatabaseFileSpy = cy.spy().as('loadDatabaseFileSpy');
|
|
79
|
+
|
|
80
|
+
cy.stub(eLabFTWHttpClient, 'get').callsFake(async (url) => {
|
|
81
|
+
if (url === `/api/v2/items/${DATABASE_ID}`) {
|
|
82
|
+
return Promise.resolve({
|
|
83
|
+
data: {
|
|
84
|
+
uploads: [
|
|
85
|
+
{
|
|
86
|
+
id: HISTORY_FILE_ID,
|
|
87
|
+
real_name: 'history1.json',
|
|
88
|
+
comment: 'OpenCloning history',
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
id: OTHER_FILE_ID,
|
|
92
|
+
real_name: 'history2.json',
|
|
93
|
+
comment: 'OpenCloning history',
|
|
94
|
+
},
|
|
95
|
+
],
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
return Promise.resolve({ data: [] });
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
cy.mount(
|
|
103
|
+
<LoadHistoryComponent
|
|
104
|
+
handleClose={handleCloseSpy}
|
|
105
|
+
databaseId={DATABASE_ID}
|
|
106
|
+
loadDatabaseFile={loadDatabaseFileSpy}
|
|
107
|
+
/>,
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
// Should show error message
|
|
111
|
+
cy.get('.MuiAlert-message').should('contain', 'Multiple history files found for this ancestor sequence');
|
|
112
|
+
cy.get('@loadDatabaseFileSpy').should('not.have.been.called');
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('shows error when no history files found', () => {
|
|
116
|
+
const handleCloseSpy = cy.spy().as('handleCloseSpy');
|
|
117
|
+
const loadDatabaseFileSpy = cy.spy().as('loadDatabaseFileSpy');
|
|
118
|
+
|
|
119
|
+
cy.stub(eLabFTWHttpClient, 'get').callsFake(async (url) => {
|
|
120
|
+
if (url === `/api/v2/items/${DATABASE_ID}`) {
|
|
121
|
+
return Promise.resolve({
|
|
122
|
+
data: {
|
|
123
|
+
uploads: [
|
|
124
|
+
{
|
|
125
|
+
id: OTHER_FILE_ID,
|
|
126
|
+
real_name: 'other.txt',
|
|
127
|
+
comment: 'Not a history file',
|
|
128
|
+
},
|
|
129
|
+
],
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
return Promise.resolve({ data: [] });
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
cy.mount(
|
|
137
|
+
<LoadHistoryComponent
|
|
138
|
+
handleClose={handleCloseSpy}
|
|
139
|
+
databaseId={DATABASE_ID}
|
|
140
|
+
loadDatabaseFile={loadDatabaseFileSpy}
|
|
141
|
+
/>,
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
// Should show error message
|
|
145
|
+
cy.get('.MuiAlert-message').should('contain', 'No history files found for this ancestor sequence');
|
|
146
|
+
cy.get('@loadDatabaseFileSpy').should('not.have.been.called');
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('handles API error and allows retry', () => {
|
|
150
|
+
const handleCloseSpy = cy.spy().as('handleCloseSpy');
|
|
151
|
+
const loadDatabaseFileSpy = cy.spy().as('loadDatabaseFileSpy');
|
|
152
|
+
|
|
153
|
+
let callNumber = 0;
|
|
154
|
+
cy.stub(eLabFTWHttpClient, 'get').callsFake(async (url) => {
|
|
155
|
+
if (url === `/api/v2/items/${DATABASE_ID}`) {
|
|
156
|
+
callNumber += 1;
|
|
157
|
+
// Network error
|
|
158
|
+
if (callNumber === 1) {
|
|
159
|
+
throw new Error('Access denied');
|
|
160
|
+
}
|
|
161
|
+
// Forbidden / deleted
|
|
162
|
+
else if (callNumber === 2) {
|
|
163
|
+
const error = new Error('Forbidden');
|
|
164
|
+
error.response = { status: 403 };
|
|
165
|
+
return Promise.reject(error);
|
|
166
|
+
}
|
|
167
|
+
return Promise.resolve({
|
|
168
|
+
data: {
|
|
169
|
+
uploads: [
|
|
170
|
+
{
|
|
171
|
+
id: HISTORY_FILE_ID,
|
|
172
|
+
real_name: 'history.json',
|
|
173
|
+
comment: 'OpenCloning history',
|
|
174
|
+
},
|
|
175
|
+
],
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
return Promise.resolve({ data: [] });
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
cy.mount(
|
|
183
|
+
<LoadHistoryComponent
|
|
184
|
+
handleClose={handleCloseSpy}
|
|
185
|
+
databaseId={DATABASE_ID}
|
|
186
|
+
loadDatabaseFile={loadDatabaseFileSpy}
|
|
187
|
+
/>,
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
// Should show error message
|
|
191
|
+
cy.get('.MuiAlert-message').should('contain', 'Failed to load history file.');
|
|
192
|
+
// Click retry
|
|
193
|
+
cy.get('button').contains('Retry').click();
|
|
194
|
+
|
|
195
|
+
// Should show error message
|
|
196
|
+
cy.get('.MuiAlert-message').should('contain', 'Ancestor sequence might have been deleted or you can no longer access it');
|
|
197
|
+
// Click retry
|
|
198
|
+
cy.get('button').contains('Retry').click();
|
|
199
|
+
|
|
200
|
+
// Should load successfully after retry
|
|
201
|
+
cy.get('@loadDatabaseFileSpy').should((spy) => {
|
|
202
|
+
const [file, id, isHistory] = spy.lastCall.args;
|
|
203
|
+
expect(file).to.be.instanceOf(File);
|
|
204
|
+
expect(file.name).to.equal('history.json');
|
|
205
|
+
expect(id).to.equal(DATABASE_ID);
|
|
206
|
+
expect(isHistory).to.equal(true);
|
|
207
|
+
return undefined; // Satisfy linter
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('close button works', () => {
|
|
212
|
+
const handleCloseSpy = cy.spy().as('handleCloseSpy');
|
|
213
|
+
const loadDatabaseFileSpy = cy.spy().as('loadDatabaseFileSpy');
|
|
214
|
+
|
|
215
|
+
cy.stub(eLabFTWHttpClient, 'get')
|
|
216
|
+
.withArgs(`/api/v2/items/${DATABASE_ID}`, { headers: { Authorization: 'test-read-key' } })
|
|
217
|
+
.resolves({
|
|
218
|
+
data: {
|
|
219
|
+
uploads: [],
|
|
220
|
+
},
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
cy.mount(
|
|
224
|
+
<LoadHistoryComponent
|
|
225
|
+
handleClose={handleCloseSpy}
|
|
226
|
+
databaseId={DATABASE_ID}
|
|
227
|
+
loadDatabaseFile={loadDatabaseFileSpy}
|
|
228
|
+
/>,
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
cy.get('button').contains('Close').click();
|
|
232
|
+
cy.get('@handleCloseSpy').should('have.been.called');
|
|
233
|
+
cy.get('@loadDatabaseFileSpy').should('not.have.been.called');
|
|
234
|
+
});
|
|
235
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Button, CircularProgress } from '@mui/material';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { eLabFTWHttpClient, readHeaders } from './common';
|
|
4
|
+
import RetryAlert from '../form/RetryAlert';
|
|
5
|
+
import { getFileFromELabFTW } from './utils';
|
|
6
|
+
|
|
7
|
+
function LoadHistoryComponent({ handleClose, databaseId, loadDatabaseFile }) {
|
|
8
|
+
const url = `/api/v2/items/${databaseId}`;
|
|
9
|
+
const [error, setError] = React.useState(null);
|
|
10
|
+
const [loading, setLoading] = React.useState(false);
|
|
11
|
+
const [retry, setRetry] = React.useState(0);
|
|
12
|
+
React.useEffect(() => {
|
|
13
|
+
const fetchData = async () => {
|
|
14
|
+
setLoading(true);
|
|
15
|
+
setError(null);
|
|
16
|
+
try {
|
|
17
|
+
const response = await eLabFTWHttpClient.get(url, { headers: readHeaders });
|
|
18
|
+
const { uploads } = response.data;
|
|
19
|
+
const historyFiles = uploads.filter((upload) => upload.real_name.endsWith('.json') && upload.comment.includes('OpenCloning'));
|
|
20
|
+
if (historyFiles.length === 1) {
|
|
21
|
+
const file = await getFileFromELabFTW(databaseId, historyFiles[0]);
|
|
22
|
+
loadDatabaseFile(file, databaseId, true);
|
|
23
|
+
} else if (historyFiles.length > 1) {
|
|
24
|
+
setError('Multiple history files found for this ancestor sequence.');
|
|
25
|
+
} else {
|
|
26
|
+
setError('No history files found for this ancestor sequence.');
|
|
27
|
+
}
|
|
28
|
+
setLoading(false);
|
|
29
|
+
} catch (e) {
|
|
30
|
+
console.error(e);
|
|
31
|
+
if (e.response?.status === 403) {
|
|
32
|
+
setError('Ancestor sequence might have been deleted or you can no longer access it');
|
|
33
|
+
} else {
|
|
34
|
+
setError('Failed to load history file.');
|
|
35
|
+
}
|
|
36
|
+
setLoading(false);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
fetchData();
|
|
40
|
+
}, [url, retry]);
|
|
41
|
+
return (
|
|
42
|
+
<div>
|
|
43
|
+
{loading && <CircularProgress />}
|
|
44
|
+
{error && <RetryAlert onRetry={() => setRetry((prev) => prev + 1)}>{error}</RetryAlert>}
|
|
45
|
+
<Button onClick={handleClose}>Close</Button>
|
|
46
|
+
|
|
47
|
+
</div>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export default LoadHistoryComponent;
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Provider } from 'react-redux';
|
|
3
|
+
import { configureStore } from '@reduxjs/toolkit';
|
|
4
|
+
import PrimersNotInDatabaseComponent from './PrimersNotInDatabaseComponent';
|
|
5
|
+
import { eLabFTWHttpClient } from './common';
|
|
6
|
+
import { mockSequences, mockSources, mockPrimers } from '../../../../../tests/mockNetworkData';
|
|
7
|
+
|
|
8
|
+
const PRIMER_CATEGORY_ID = 3;
|
|
9
|
+
|
|
10
|
+
const defaultState = {
|
|
11
|
+
sequences: mockSequences,
|
|
12
|
+
sources: mockSources,
|
|
13
|
+
primers: mockPrimers,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const createTestStore = (cloningState) => configureStore({
|
|
17
|
+
reducer: {
|
|
18
|
+
cloning: (state = cloningState) => state,
|
|
19
|
+
},
|
|
20
|
+
preloadedState: { cloning: cloningState },
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe('<PrimersNotInDatabaseComponent />', () => {
|
|
24
|
+
it('renders nothing when no primers need saving', () => {
|
|
25
|
+
cy.stub(eLabFTWHttpClient, 'get')
|
|
26
|
+
.withArgs('/api/v2/items_types')
|
|
27
|
+
.resolves({
|
|
28
|
+
data: [
|
|
29
|
+
{ id: PRIMER_CATEGORY_ID, title: 'Primers' },
|
|
30
|
+
{ id: 2, title: 'Other' },
|
|
31
|
+
],
|
|
32
|
+
});
|
|
33
|
+
const store = createTestStore(defaultState);
|
|
34
|
+
cy.mount(
|
|
35
|
+
<Provider store={store}>
|
|
36
|
+
<PrimersNotInDatabaseComponent
|
|
37
|
+
id={4}
|
|
38
|
+
submissionData={{}}
|
|
39
|
+
setSubmissionData={cy.spy().as('setSubmissionDataSpy')}
|
|
40
|
+
/>
|
|
41
|
+
</Provider>,
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
// Component should not render anything
|
|
45
|
+
cy.get('.MuiAlert-root').should('not.exist');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('shows primers that need saving', () => {
|
|
49
|
+
cy.stub(eLabFTWHttpClient, 'get')
|
|
50
|
+
.withArgs('/api/v2/items_types')
|
|
51
|
+
.resolves({
|
|
52
|
+
data: [
|
|
53
|
+
{ id: PRIMER_CATEGORY_ID, title: 'Primers' },
|
|
54
|
+
{ id: 2, title: 'Other' },
|
|
55
|
+
],
|
|
56
|
+
}).withArgs('/api/v2/info').resolves({
|
|
57
|
+
data: {
|
|
58
|
+
elabftw_version_int: 50200,
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
// In this case, it should show only 1, because substate goes only up to
|
|
62
|
+
// the sequence with database_id, and one of the primers already has a database_id
|
|
63
|
+
const store = createTestStore(defaultState);
|
|
64
|
+
|
|
65
|
+
cy.mount(
|
|
66
|
+
<Provider store={store}>
|
|
67
|
+
<PrimersNotInDatabaseComponent
|
|
68
|
+
id={1}
|
|
69
|
+
submissionData={{}}
|
|
70
|
+
setSubmissionData={cy.spy().as('setSubmissionDataSpy')}
|
|
71
|
+
/>
|
|
72
|
+
</Provider>,
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
// Component should not render anything
|
|
76
|
+
cy.get('.MuiAlert-root').contains('Do you want used primers to be saved to the database?').should('exist');
|
|
77
|
+
cy.get('.MuiAlert-root li').should('have.length', 1);
|
|
78
|
+
cy.get('.MuiAlert-root li').contains('Primer1').should('exist');
|
|
79
|
+
|
|
80
|
+
// Shows the categories to choose from
|
|
81
|
+
cy.get('input').click();
|
|
82
|
+
cy.get('li').contains('Other').should('exist');
|
|
83
|
+
cy.get('li').contains('Primers').click();
|
|
84
|
+
|
|
85
|
+
// Should update submission data
|
|
86
|
+
cy.get('@setSubmissionDataSpy').should((spy) => {
|
|
87
|
+
const updateFn = spy.lastCall.args[0];
|
|
88
|
+
const result = updateFn({ hello: 'world' });
|
|
89
|
+
expect(result).to.deep.equal({ primerCategoryId: PRIMER_CATEGORY_ID, hello: 'world' });
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Mount with new data (simulating a successful update)
|
|
93
|
+
cy.mount(
|
|
94
|
+
<Provider store={store}>
|
|
95
|
+
<PrimersNotInDatabaseComponent
|
|
96
|
+
id={1}
|
|
97
|
+
submissionData={{ primerCategoryId: PRIMER_CATEGORY_ID }}
|
|
98
|
+
/>
|
|
99
|
+
</Provider>,
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
// Should show success state
|
|
103
|
+
cy.get('.MuiAlert-colorSuccess').should('exist');
|
|
104
|
+
cy.contains('Do you want used primers to be saved to the database?').should('not.exist');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('shows error when update fails', () => {
|
|
108
|
+
let firstCall = true;
|
|
109
|
+
cy.stub(eLabFTWHttpClient, 'get')
|
|
110
|
+
.withArgs('/api/v2/items_types', { headers: { Authorization: 'test-read-key' }, params: { limit: 9999 } })
|
|
111
|
+
.callsFake((url, config) => {
|
|
112
|
+
if (url !== '/api/v2/items_types' || config.headers?.Authorization !== 'test-read-key') {
|
|
113
|
+
throw new Error('Unexpected call to get method with these parameters');
|
|
114
|
+
}
|
|
115
|
+
if (firstCall) {
|
|
116
|
+
firstCall = false;
|
|
117
|
+
const err = new Error('Failed to fetch items types');
|
|
118
|
+
err.response = {
|
|
119
|
+
status: 500,
|
|
120
|
+
};
|
|
121
|
+
return Promise.reject(err);
|
|
122
|
+
}
|
|
123
|
+
return Promise.resolve({
|
|
124
|
+
data: [
|
|
125
|
+
{ id: PRIMER_CATEGORY_ID, title: 'Primers' },
|
|
126
|
+
{ id: 2, title: 'Other' },
|
|
127
|
+
],
|
|
128
|
+
});
|
|
129
|
+
}).withArgs('/api/v2/info').resolves({
|
|
130
|
+
data: {
|
|
131
|
+
elabftw_version_int: 50200,
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
// Mount with new data (simulating a failed update)
|
|
135
|
+
const store = createTestStore(defaultState);
|
|
136
|
+
cy.mount(
|
|
137
|
+
<Provider store={store}>
|
|
138
|
+
<PrimersNotInDatabaseComponent
|
|
139
|
+
id={1}
|
|
140
|
+
submissionData={{}}
|
|
141
|
+
setSubmissionData={cy.spy().as('setSubmissionDataSpy')}
|
|
142
|
+
/>
|
|
143
|
+
</Provider>,
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
// Should show error state
|
|
147
|
+
cy.get('.MuiAlert-colorError').should('exist');
|
|
148
|
+
cy.contains('Could not retrieve categories from eLab').should('exist');
|
|
149
|
+
cy.contains('Retry').click();
|
|
150
|
+
// Should be normal again
|
|
151
|
+
cy.get('input').click();
|
|
152
|
+
cy.get('li').contains('Other').click();
|
|
153
|
+
cy.get('@setSubmissionDataSpy').should((spy) => {
|
|
154
|
+
const updateFn = spy.lastCall.args[0];
|
|
155
|
+
const result = updateFn({ hello: 'world' });
|
|
156
|
+
expect(result).to.deep.equal({ primerCategoryId: 2, hello: 'world' });
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
});
|