jbrowse-plugin-msaview 3.5.0 → 3.7.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/README.md +8 -0
- package/dist/AddHighlightModel/index.js +3 -3
- package/dist/BgzipFastaMsaAdapter/BgzipFastaMsaAdapter.d.ts +8 -2
- package/dist/BgzipFastaMsaAdapter/configSchema.d.ts +3 -3
- package/dist/BgzipFastaMsaAdapter/configSchema.js +1 -1
- package/dist/LaunchMsaView/cleanProteinSequence.test.d.ts +1 -0
- package/dist/LaunchMsaView/cleanProteinSequence.test.js +77 -0
- package/dist/LaunchMsaView/codingFeature.d.ts +14 -0
- package/dist/LaunchMsaView/codingFeature.js +38 -0
- package/dist/LaunchMsaView/codingFeature.test.d.ts +1 -0
- package/dist/LaunchMsaView/codingFeature.test.js +75 -0
- package/dist/LaunchMsaView/components/BlastQuery/BlastAutomaticPanel.d.ts +3 -1
- package/dist/LaunchMsaView/components/BlastQuery/BlastAutomaticPanel.js +47 -55
- package/dist/LaunchMsaView/components/BlastQuery/BlastManualPanel.d.ts +3 -1
- package/dist/LaunchMsaView/components/BlastQuery/BlastManualPanel.js +16 -17
- package/dist/LaunchMsaView/components/BlastQuery/BlastPanel.d.ts +7 -3
- package/dist/LaunchMsaView/components/BlastQuery/BlastPanel.js +8 -7
- package/dist/LaunchMsaView/components/BlastQuery/BlastSettingsDialog.js +2 -2
- package/dist/LaunchMsaView/components/BlastQuery/CachedBlastResults.js +14 -13
- package/dist/LaunchMsaView/components/BlastQuery/consts.d.ts +2 -0
- package/dist/LaunchMsaView/components/BlastQuery/consts.js +28 -4
- package/dist/LaunchMsaView/components/HelpButton.d.ts +2 -0
- package/dist/LaunchMsaView/components/HelpButton.js +17 -0
- package/dist/LaunchMsaView/components/HelpDialog.d.ts +4 -0
- package/dist/LaunchMsaView/components/HelpDialog.js +18 -0
- package/dist/LaunchMsaView/components/LaunchMsaViewDialog.d.ts +3 -1
- package/dist/LaunchMsaView/components/LaunchMsaViewDialog.js +21 -16
- package/dist/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.d.ts +3 -1
- package/dist/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.js +34 -26
- package/dist/LaunchMsaView/components/OrthologQuery/OrthologPanel.d.ts +3 -1
- package/dist/LaunchMsaView/components/OrthologQuery/OrthologPanel.js +30 -26
- package/dist/LaunchMsaView/components/OrthologQuery/OrthologSourceSelect.d.ts +2 -1
- package/dist/LaunchMsaView/components/OrthologQuery/OrthologSourceSelect.js +18 -2
- package/dist/LaunchMsaView/components/OrthologQuery/OrthologSourceSelect.test.d.ts +1 -0
- package/dist/LaunchMsaView/components/OrthologQuery/OrthologSourceSelect.test.js +13 -0
- package/dist/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.d.ts +3 -1
- package/dist/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.js +50 -27
- package/dist/LaunchMsaView/components/SequenceStatus.d.ts +14 -0
- package/dist/LaunchMsaView/components/SequenceStatus.js +15 -0
- package/dist/LaunchMsaView/components/SubmitCancelActions.d.ts +6 -2
- package/dist/LaunchMsaView/components/SubmitCancelActions.js +28 -13
- package/dist/LaunchMsaView/components/SubmitCancelActions.test.js +52 -4
- package/dist/LaunchMsaView/components/TabPanel.js +15 -3
- package/dist/LaunchMsaView/components/TabPanel.test.d.ts +1 -0
- package/dist/LaunchMsaView/components/TabPanel.test.js +36 -0
- package/dist/LaunchMsaView/components/TranscriptSelector.d.ts +3 -1
- package/dist/LaunchMsaView/components/TranscriptSelector.js +4 -2
- package/dist/LaunchMsaView/components/calculateProteinSequence.d.ts +12 -2
- package/dist/LaunchMsaView/components/calculateProteinSequence.js +38 -23
- package/dist/LaunchMsaView/components/fetchSeq.d.ts +4 -1
- package/dist/LaunchMsaView/components/fetchSeq.js +14 -4
- package/dist/LaunchMsaView/components/launchConnectedView.d.ts +23 -0
- package/dist/LaunchMsaView/components/launchConnectedView.js +39 -0
- package/dist/LaunchMsaView/components/launchPlacement.d.ts +8 -0
- package/dist/LaunchMsaView/components/launchPlacement.js +25 -0
- package/dist/LaunchMsaView/components/useFeatureSequence.d.ts +2 -0
- package/dist/LaunchMsaView/components/useFeatureSequence.js +14 -11
- package/dist/LaunchMsaView/components/useTranscriptSelection.d.ts +10 -1
- package/dist/LaunchMsaView/components/useTranscriptSelection.js +21 -3
- package/dist/LaunchMsaView/components/useTranscriptSelection.test.d.ts +1 -0
- package/dist/LaunchMsaView/components/useTranscriptSelection.test.js +57 -0
- package/dist/LaunchMsaView/detectQueryRow.js +4 -7
- package/dist/LaunchMsaView/index.js +28 -9
- package/dist/LaunchMsaView/launchTarget.d.ts +36 -8
- package/dist/LaunchMsaView/launchTarget.js +23 -13
- package/dist/LaunchMsaView/launchTarget.test.js +80 -15
- package/dist/LaunchMsaView/useQueryRowName.d.ts +12 -0
- package/dist/LaunchMsaView/useQueryRowName.js +11 -0
- package/dist/LaunchMsaView/useQueryRowName.test.js +18 -1
- package/dist/LaunchMsaView/util.d.ts +6 -0
- package/dist/LaunchMsaView/util.js +12 -3
- package/dist/LaunchMsaViewExtensionPoint/index.test.js +15 -0
- package/dist/MsaViewPanel/afterCreateAutoruns.d.ts +6 -4
- package/dist/MsaViewPanel/afterCreateAutoruns.js +38 -14
- package/dist/MsaViewPanel/applyRegion.d.ts +23 -0
- package/dist/MsaViewPanel/applyRegion.js +19 -0
- package/dist/MsaViewPanel/applyRegion.test.d.ts +1 -0
- package/dist/MsaViewPanel/applyRegion.test.js +45 -0
- package/dist/MsaViewPanel/components/LaunchProgress.d.ts +4 -3
- package/dist/MsaViewPanel/components/LaunchProgress.js +10 -7
- package/dist/MsaViewPanel/components/MsaViewPanel.js +7 -3
- package/dist/MsaViewPanel/components/MsaViewPanel.test.js +24 -0
- package/dist/MsaViewPanel/doLaunchBlast.js +2 -1
- package/dist/MsaViewPanel/doLaunchBlast.test.js +12 -0
- package/dist/MsaViewPanel/doLaunchOrthologs.test.js +1 -1
- package/dist/MsaViewPanel/genomeToMSA.js +24 -6
- package/dist/MsaViewPanel/genomeToMSA.test.js +218 -44
- package/dist/MsaViewPanel/launchAutoruns.test.d.ts +1 -0
- package/dist/MsaViewPanel/launchAutoruns.test.js +36 -0
- package/dist/MsaViewPanel/model.d.ts +156 -61
- package/dist/MsaViewPanel/model.js +69 -3
- package/dist/MsaViewPanel/model.test.js +66 -0
- package/dist/MsaViewPanel/msaDataStore.d.ts +5 -3
- package/dist/MsaViewPanel/msaDataStore.js +3 -12
- package/dist/MsaViewPanel/msaDataStore.test.js +8 -1
- package/dist/MsaViewPanel/observeProteinHighlights.test.js +4 -4
- package/dist/MsaViewPanel/regionSnapshot.test.d.ts +1 -0
- package/dist/MsaViewPanel/regionSnapshot.test.js +29 -0
- package/dist/MsaViewPanel/resolveConnectedTranscript.d.ts +41 -4
- package/dist/MsaViewPanel/resolveConnectedTranscript.js +109 -68
- package/dist/MsaViewPanel/resolveConnectedTranscript.test.d.ts +1 -0
- package/dist/MsaViewPanel/resolveConnectedTranscript.test.js +149 -0
- package/dist/MsaViewPanel/runLaunch.d.ts +8 -4
- package/dist/MsaViewPanel/runLaunch.js +15 -4
- package/dist/MsaViewPanel/runLaunch.test.js +16 -0
- package/dist/MsaViewPanel/storedData.test.js +43 -0
- package/dist/MsaViewPanel/syncGenomeHoverToMsaColumn.test.js +15 -7
- package/dist/MsaViewPanel/util.d.ts +23 -23
- package/dist/MsaViewPanel/util.js +23 -24
- package/dist/index.d.ts +11 -11
- package/dist/index.js +17 -10
- package/dist/jbrowse-plugin-msaview.umd.production.min.js +54 -67
- package/dist/jbrowse-plugin-msaview.umd.production.min.js.map +4 -4
- package/dist/utils/blastCache.d.ts +26 -2
- package/dist/utils/blastCache.js +51 -39
- package/dist/utils/blastCache.test.js +46 -19
- package/dist/utils/domainCache.d.ts +1 -1
- package/dist/utils/domainCache.js +18 -14
- package/dist/utils/ebiJobDispatcher.d.ts +11 -0
- package/dist/utils/ebiJobDispatcher.js +12 -13
- package/dist/utils/ebiJobDispatcher.test.js +17 -3
- package/dist/utils/eutils.d.ts +14 -0
- package/dist/utils/eutils.js +48 -14
- package/dist/utils/eutils.test.d.ts +1 -0
- package/dist/utils/eutils.test.js +31 -0
- package/dist/utils/idb.d.ts +16 -3
- package/dist/utils/idb.js +51 -5
- package/dist/utils/idb.test.d.ts +1 -0
- package/dist/utils/idb.test.js +78 -0
- package/dist/utils/ncbiDomains.js +4 -4
- package/dist/utils/ncbiOrthologs.d.ts +1 -1
- package/dist/utils/ncbiOrthologs.js +18 -17
- package/dist/utils/ncbiOrthologs.test.js +17 -1
- package/dist/utils/ncbiTaxonomy.js +14 -15
- package/dist/utils/taxonomyNames.js +23 -19
- package/dist/utils/taxonomyNames.test.d.ts +1 -0
- package/dist/utils/taxonomyNames.test.js +33 -0
- package/dist/utils/unirefHomologs.js +2 -2
- package/dist/utils/unirefHomologs.test.js +8 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +23 -23
- package/src/AddHighlightModel/index.tsx +1 -1
- package/src/BgzipFastaMsaAdapter/configSchema.ts +1 -1
- package/src/LaunchMsaView/cleanProteinSequence.test.ts +91 -0
- package/src/LaunchMsaView/codingFeature.test.ts +96 -0
- package/src/LaunchMsaView/codingFeature.ts +49 -0
- package/src/LaunchMsaView/components/BlastQuery/BlastAutomaticPanel.tsx +60 -56
- package/src/LaunchMsaView/components/BlastQuery/BlastManualPanel.tsx +18 -15
- package/src/LaunchMsaView/components/BlastQuery/BlastPanel.tsx +24 -11
- package/src/LaunchMsaView/components/BlastQuery/BlastSettingsDialog.tsx +4 -4
- package/src/LaunchMsaView/components/BlastQuery/CachedBlastResults.tsx +16 -15
- package/src/LaunchMsaView/components/BlastQuery/consts.ts +31 -4
- package/src/LaunchMsaView/components/HelpButton.tsx +33 -0
- package/src/LaunchMsaView/components/HelpDialog.tsx +78 -0
- package/src/LaunchMsaView/components/LaunchMsaViewDialog.tsx +54 -34
- package/src/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.tsx +46 -23
- package/src/LaunchMsaView/components/OrthologQuery/OrthologPanel.tsx +41 -28
- package/src/LaunchMsaView/components/OrthologQuery/OrthologSourceSelect.test.ts +16 -0
- package/src/LaunchMsaView/components/OrthologQuery/OrthologSourceSelect.tsx +24 -2
- package/src/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.tsx +81 -25
- package/src/LaunchMsaView/components/SequenceStatus.tsx +29 -0
- package/src/LaunchMsaView/components/SubmitCancelActions.test.tsx +88 -4
- package/src/LaunchMsaView/components/SubmitCancelActions.tsx +43 -16
- package/src/LaunchMsaView/components/TabPanel.test.tsx +51 -0
- package/src/LaunchMsaView/components/TabPanel.tsx +16 -4
- package/src/LaunchMsaView/components/TranscriptSelector.tsx +6 -1
- package/src/LaunchMsaView/components/calculateProteinSequence.ts +51 -22
- package/src/LaunchMsaView/components/fetchSeq.ts +25 -5
- package/src/LaunchMsaView/components/launchConnectedView.ts +56 -0
- package/src/LaunchMsaView/components/launchPlacement.tsx +41 -0
- package/src/LaunchMsaView/components/useFeatureSequence.ts +18 -11
- package/src/LaunchMsaView/components/useTranscriptSelection.test.tsx +66 -0
- package/src/LaunchMsaView/components/useTranscriptSelection.ts +41 -2
- package/src/LaunchMsaView/detectQueryRow.ts +5 -7
- package/src/LaunchMsaView/index.ts +32 -13
- package/src/LaunchMsaView/launchTarget.test.ts +88 -15
- package/src/LaunchMsaView/launchTarget.ts +49 -14
- package/src/LaunchMsaView/useQueryRowName.test.ts +28 -1
- package/src/LaunchMsaView/useQueryRowName.ts +17 -0
- package/src/LaunchMsaView/util.ts +15 -5
- package/src/LaunchMsaViewExtensionPoint/index.test.ts +16 -0
- package/src/LaunchMsaViewExtensionPoint/index.ts +28 -1
- package/src/MsaViewPanel/afterCreateAutoruns.ts +46 -14
- package/src/MsaViewPanel/applyRegion.test.ts +55 -0
- package/src/MsaViewPanel/applyRegion.ts +33 -0
- package/src/MsaViewPanel/components/LaunchProgress.tsx +12 -5
- package/src/MsaViewPanel/components/MsaViewPanel.test.tsx +26 -0
- package/src/MsaViewPanel/components/MsaViewPanel.tsx +7 -5
- package/src/MsaViewPanel/doLaunchBlast.test.ts +20 -1
- package/src/MsaViewPanel/doLaunchBlast.ts +2 -1
- package/src/MsaViewPanel/doLaunchOrthologs.test.ts +1 -1
- package/src/MsaViewPanel/genomeToMSA.test.ts +255 -59
- package/src/MsaViewPanel/genomeToMSA.ts +30 -5
- package/src/MsaViewPanel/launchAutoruns.test.ts +45 -0
- package/src/MsaViewPanel/model.test.ts +75 -0
- package/src/MsaViewPanel/model.ts +76 -3
- package/src/MsaViewPanel/msaDataStore.test.ts +14 -2
- package/src/MsaViewPanel/msaDataStore.ts +4 -12
- package/src/MsaViewPanel/observeProteinHighlights.test.ts +4 -4
- package/src/MsaViewPanel/regionSnapshot.test.ts +34 -0
- package/src/MsaViewPanel/resolveConnectedTranscript.test.ts +183 -0
- package/src/MsaViewPanel/resolveConnectedTranscript.ts +160 -67
- package/src/MsaViewPanel/runLaunch.test.ts +19 -0
- package/src/MsaViewPanel/runLaunch.ts +21 -4
- package/src/MsaViewPanel/storedData.test.ts +58 -0
- package/src/MsaViewPanel/syncGenomeHoverToMsaColumn.test.ts +17 -7
- package/src/MsaViewPanel/util.ts +38 -29
- package/src/index.ts +19 -10
- package/src/utils/blastCache.test.ts +59 -20
- package/src/utils/blastCache.ts +62 -39
- package/src/utils/domainCache.ts +27 -15
- package/src/utils/ebiJobDispatcher.test.ts +18 -3
- package/src/utils/ebiJobDispatcher.ts +12 -16
- package/src/utils/eutils.test.ts +39 -0
- package/src/utils/eutils.ts +64 -14
- package/src/utils/idb.test.ts +106 -0
- package/src/utils/idb.ts +57 -5
- package/src/utils/ncbiDomains.ts +4 -4
- package/src/utils/ncbiOrthologs.test.ts +20 -0
- package/src/utils/ncbiOrthologs.ts +26 -28
- package/src/utils/ncbiTaxonomy.ts +17 -20
- package/src/utils/taxonomyNames.test.ts +41 -0
- package/src/utils/taxonomyNames.ts +33 -19
- package/src/utils/unirefHomologs.test.ts +11 -1
- package/src/utils/unirefHomologs.ts +2 -2
- package/src/version.ts +1 -1
- package/dist/LaunchMsaView/components/BlastQuery/blastLaunchView.d.ts +0 -16
- package/dist/LaunchMsaView/components/BlastQuery/blastLaunchView.js +0 -31
- package/dist/LaunchMsaView/components/ManualMSALoader/launchView.d.ts +0 -16
- package/dist/LaunchMsaView/components/ManualMSALoader/launchView.js +0 -16
- package/dist/LaunchMsaView/components/OrthologQuery/orthologLaunchView.d.ts +0 -9
- package/dist/LaunchMsaView/components/OrthologQuery/orthologLaunchView.js +0 -15
- package/dist/LaunchMsaView/components/PreLoadedMSA/preCalculatedLaunchView.d.ts +0 -11
- package/dist/LaunchMsaView/components/PreLoadedMSA/preCalculatedLaunchView.js +0 -21
- package/dist/LaunchMsaView/components/geneticCodes.d.ts +0 -15
- package/dist/LaunchMsaView/components/geneticCodes.js +0 -227
- package/src/LaunchMsaView/components/BlastQuery/blastLaunchView.ts +0 -59
- package/src/LaunchMsaView/components/ManualMSALoader/launchView.ts +0 -43
- package/src/LaunchMsaView/components/OrthologQuery/orthologLaunchView.ts +0 -31
- package/src/LaunchMsaView/components/PreLoadedMSA/preCalculatedLaunchView.ts +0 -38
- package/src/LaunchMsaView/components/geneticCodes.ts +0 -298
|
@@ -61,6 +61,39 @@ describe('a failed launch', () => {
|
|
|
61
61
|
expect(model.blastParams).toEqual(BLAST_PARAMS);
|
|
62
62
|
expect(doLaunchBlast).toHaveBeenCalledTimes(2);
|
|
63
63
|
});
|
|
64
|
+
// the params used to be cleared on success to mark the request done, which
|
|
65
|
+
// threw away the only durable statement of what the view is: a stored
|
|
66
|
+
// alignment expires after 7 days and there was nothing left to run again
|
|
67
|
+
test('a successful launch keeps its request and is not run twice', async () => {
|
|
68
|
+
vi.mocked(doLaunchBlast).mockResolvedValue({
|
|
69
|
+
msa: '>a\nMK\n>b\nMK',
|
|
70
|
+
tree: '(a,b);',
|
|
71
|
+
treeMetadata: '{}',
|
|
72
|
+
});
|
|
73
|
+
const model = view();
|
|
74
|
+
model.setBlastParams({ ...BLAST_PARAMS });
|
|
75
|
+
await new Promise(res => setTimeout(res, 0));
|
|
76
|
+
expect(model.blastParams).toEqual(BLAST_PARAMS);
|
|
77
|
+
expect(model.launchCompleted).toBe(true);
|
|
78
|
+
expect(doLaunchBlast).toHaveBeenCalledTimes(1);
|
|
79
|
+
vi.mocked(doLaunchBlast).mockReturnValue(new Promise(() => { }));
|
|
80
|
+
model.setError(new Error('alignment is no longer in browser storage'));
|
|
81
|
+
model.retryLaunch();
|
|
82
|
+
expect(model.error).toBeUndefined();
|
|
83
|
+
expect(model.launchCompleted).toBe(false);
|
|
84
|
+
expect(doLaunchBlast).toHaveBeenCalledTimes(2);
|
|
85
|
+
});
|
|
86
|
+
// a session reopens with the request still on it, so the mark is what stops
|
|
87
|
+
// the whole search running again unasked
|
|
88
|
+
test('a restored session does not rerun a request it already ran', () => {
|
|
89
|
+
stateModelFactory().create({
|
|
90
|
+
type: 'MsaView',
|
|
91
|
+
id: 'msaview2',
|
|
92
|
+
blastParams: { ...BLAST_PARAMS },
|
|
93
|
+
launchCompleted: true,
|
|
94
|
+
});
|
|
95
|
+
expect(doLaunchBlast).not.toHaveBeenCalled();
|
|
96
|
+
});
|
|
64
97
|
test('dismisses by dropping it', () => {
|
|
65
98
|
const model = view();
|
|
66
99
|
model.setBlastParams({ ...BLAST_PARAMS });
|
|
@@ -70,6 +103,39 @@ describe('a failed launch', () => {
|
|
|
70
103
|
expect(model.error).toBeUndefined();
|
|
71
104
|
});
|
|
72
105
|
});
|
|
106
|
+
// react-msaview drops a snapshot document past 50kb and reports it through
|
|
107
|
+
// `unshareableData`, which the header renders as "Not in the link". An indexed
|
|
108
|
+
// view's alignment IS in the link -- its init names the block and processInit
|
|
109
|
+
// refetches it -- so that view answers the question itself.
|
|
110
|
+
describe('what the snapshot cannot carry', () => {
|
|
111
|
+
const big = `>a\n${'M'.repeat(60_000)}`;
|
|
112
|
+
test('an indexed view says the host brings its alignment back', () => {
|
|
113
|
+
const model = view();
|
|
114
|
+
model.setInit({
|
|
115
|
+
msaIndexedLocation: { uri: 'msa.fa.gz' },
|
|
116
|
+
msaName: 'ENST1',
|
|
117
|
+
});
|
|
118
|
+
model.setMSA(big);
|
|
119
|
+
expect(model.hostRestoresData).toBe(true);
|
|
120
|
+
expect(model.unshareableData).toEqual([]);
|
|
121
|
+
});
|
|
122
|
+
// the base getter reads a volatile a host flips to say it brings the data
|
|
123
|
+
// back by its own means; overriding without it threw that switch away
|
|
124
|
+
test('a host that says it carries the data is still believed', () => {
|
|
125
|
+
const model = view();
|
|
126
|
+
model.setMSA(big);
|
|
127
|
+
expect(model.unshareableData).not.toEqual([]);
|
|
128
|
+
model.setHostCarriesData(true);
|
|
129
|
+
expect(model.hostRestoresData).toBe(true);
|
|
130
|
+
expect(model.unshareableData).toEqual([]);
|
|
131
|
+
});
|
|
132
|
+
test('a pasted alignment is reported, because a link really does lose it', () => {
|
|
133
|
+
const model = view();
|
|
134
|
+
model.setMSA(big);
|
|
135
|
+
expect(model.hostRestoresData).toBe(false);
|
|
136
|
+
expect(model.unshareableData.map(d => d.what)).toEqual(['alignment']);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
73
139
|
// react-msaview's reset keeps only what is on its own preservedOnReset list,
|
|
74
140
|
// and a downstream property is never on it; its volatiles survive instead,
|
|
75
141
|
// which is the direction that carries the last file's state into the next one
|
|
@@ -3,13 +3,15 @@ export interface MsaDataPayload {
|
|
|
3
3
|
msa?: string;
|
|
4
4
|
tree?: string;
|
|
5
5
|
treeMetadata?: string;
|
|
6
|
+
gff?: string;
|
|
6
7
|
}
|
|
7
8
|
export declare function generateDataStoreId(): string;
|
|
8
9
|
export declare function storeMsaData(id: string, data: MsaDataPayload): Promise<boolean>;
|
|
9
10
|
export declare function retrieveMsaData(id: string): Promise<{
|
|
10
|
-
msa
|
|
11
|
-
tree
|
|
12
|
-
treeMetadata
|
|
11
|
+
msa?: string;
|
|
12
|
+
tree?: string;
|
|
13
|
+
treeMetadata?: string;
|
|
14
|
+
gff?: string;
|
|
13
15
|
} | undefined>;
|
|
14
16
|
/** drop one view's row, for a reset that just orphaned it */
|
|
15
17
|
export declare function deleteMsaData(id: string): Promise<void>;
|
|
@@ -14,13 +14,7 @@ export function generateDataStoreId() {
|
|
|
14
14
|
export async function storeMsaData(id, data) {
|
|
15
15
|
try {
|
|
16
16
|
const db = await getDB();
|
|
17
|
-
const storedData = {
|
|
18
|
-
id,
|
|
19
|
-
msa: data.msa,
|
|
20
|
-
tree: data.tree,
|
|
21
|
-
treeMetadata: data.treeMetadata,
|
|
22
|
-
timestamp: Date.now(),
|
|
23
|
-
};
|
|
17
|
+
const storedData = { ...data, id, timestamp: Date.now() };
|
|
24
18
|
await db.put(STORE_NAME, storedData);
|
|
25
19
|
return true;
|
|
26
20
|
}
|
|
@@ -45,11 +39,8 @@ export async function retrieveMsaData(id) {
|
|
|
45
39
|
catch (e) {
|
|
46
40
|
console.warn('Failed to refresh MSA data timestamp:', e);
|
|
47
41
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
tree: result.tree,
|
|
51
|
-
treeMetadata: result.treeMetadata,
|
|
52
|
-
};
|
|
42
|
+
const { id: _id, timestamp: _timestamp, ...payload } = result;
|
|
43
|
+
return payload;
|
|
53
44
|
}
|
|
54
45
|
catch (e) {
|
|
55
46
|
console.warn('Failed to retrieve MSA data:', e);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { beforeEach, expect, test, vi } from 'vitest';
|
|
2
|
-
import { retrieveMsaData } from './msaDataStore';
|
|
2
|
+
import { retrieveMsaData, storeMsaData } from './msaDataStore';
|
|
3
3
|
// An in-memory stand-in for the one object store this module opens, with a
|
|
4
4
|
// switch for the write half so a browser that refuses writes can be played back.
|
|
5
5
|
const { rows, state } = vi.hoisted(() => ({
|
|
@@ -42,3 +42,10 @@ test('a refresh that fails still hands back the data it read', async () => {
|
|
|
42
42
|
state.putFails = true;
|
|
43
43
|
expect((await retrieveMsaData('msa-1'))?.msa).toBe('>a\nMK');
|
|
44
44
|
});
|
|
45
|
+
test('every document a view keeps comes back, and nothing else', async () => {
|
|
46
|
+
await storeMsaData('msa-1', { msa: '>a\nMK', gff: 'a\t.\tdomain\t1\t2' });
|
|
47
|
+
expect(await retrieveMsaData('msa-1')).toEqual({
|
|
48
|
+
msa: '>a\nMK',
|
|
49
|
+
gff: 'a\t.\tdomain\t1\t2',
|
|
50
|
+
});
|
|
51
|
+
});
|
|
@@ -19,7 +19,7 @@ function makeModel({ highlightColumns, querySeqOffset = 0, } = {}) {
|
|
|
19
19
|
const model = {
|
|
20
20
|
querySeqName: 'query',
|
|
21
21
|
querySeqOffset,
|
|
22
|
-
|
|
22
|
+
rowMap: new Map([['query', 'MKVLTAEEK']]),
|
|
23
23
|
connectedViewId: CONNECTED,
|
|
24
24
|
// g2p is indexed by genome coord; identity keeps the arithmetic out of the way
|
|
25
25
|
transcriptToMsaMap: {
|
|
@@ -213,11 +213,11 @@ describe('scope and redundant writes', () => {
|
|
|
213
213
|
run();
|
|
214
214
|
expect(calls).toEqual([]);
|
|
215
215
|
});
|
|
216
|
-
// seqPosToGlobalCol answers 0 for a row name it does not know, so without a
|
|
217
|
-
// guard a structure hover would light column 0 of whatever row is first
|
|
218
216
|
test('a query row this alignment does not have contributes no column', () => {
|
|
219
217
|
const { model, calls } = makeModel();
|
|
220
|
-
Object.assign(model, {
|
|
218
|
+
Object.assign(model, {
|
|
219
|
+
rowMap: new Map([['some_other_row', 'MKVLTAEEK']]),
|
|
220
|
+
});
|
|
221
221
|
const run = observeProteinHighlights(model);
|
|
222
222
|
session({ hover: [{ start: 10, end: 12 }] });
|
|
223
223
|
run();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { expect, test, vi } from 'vitest';
|
|
2
|
+
import stateModelFactory from './model';
|
|
3
|
+
vi.mock('@jbrowse/core/util', async (importOriginal) => ({
|
|
4
|
+
...(await importOriginal()),
|
|
5
|
+
getSession: () => ({ views: [], hovered: undefined }),
|
|
6
|
+
}));
|
|
7
|
+
vi.mock('./msaDataStore', () => ({
|
|
8
|
+
cleanupOldData: vi.fn(async () => { }),
|
|
9
|
+
deleteMsaData: vi.fn(async () => { }),
|
|
10
|
+
generateDataStoreId: vi.fn(),
|
|
11
|
+
retrieveMsaData: vi.fn(),
|
|
12
|
+
storeMsaData: vi.fn(async () => true),
|
|
13
|
+
}));
|
|
14
|
+
const MSA = `>Human\n${'MEEPQSDPSV'.repeat(20)}\n>Mouse\n${'MEE-QSDPSV'.repeat(20)}`;
|
|
15
|
+
test('a spec region waits for the view to have a width, zooms, and is dropped', () => {
|
|
16
|
+
const region = { row: 'Human', start: 150, end: 160 };
|
|
17
|
+
const model = stateModelFactory().create({
|
|
18
|
+
type: 'MsaView',
|
|
19
|
+
id: 'msaview1',
|
|
20
|
+
data: { msa: MSA },
|
|
21
|
+
region,
|
|
22
|
+
gffFilehandle: { uri: 'http://example.com/a.gff' },
|
|
23
|
+
});
|
|
24
|
+
expect(model.region).toEqual(region);
|
|
25
|
+
expect(model.gffFilehandle).toMatchObject({ locationType: 'UriLocation' });
|
|
26
|
+
model.setWidth(800);
|
|
27
|
+
expect(model.region).toBeUndefined();
|
|
28
|
+
expect(model.scrollX).toBeLessThan(0);
|
|
29
|
+
});
|
|
@@ -1,9 +1,46 @@
|
|
|
1
1
|
import type { JBrowsePluginMsaViewModel } from './model';
|
|
2
|
+
interface Region {
|
|
3
|
+
assemblyName: string;
|
|
4
|
+
refName: string;
|
|
5
|
+
start: number;
|
|
6
|
+
end: number;
|
|
7
|
+
}
|
|
8
|
+
interface GenomeView {
|
|
9
|
+
tracks: {
|
|
10
|
+
type: string;
|
|
11
|
+
}[];
|
|
12
|
+
displayedRegions: Region[];
|
|
13
|
+
dynamicBlocks?: {
|
|
14
|
+
contentBlocks?: Region[];
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The part of the genome on screen, then everything the view can scroll to.
|
|
19
|
+
* `displayedRegions` is usually whole chromosomes -- navigating to a locus
|
|
20
|
+
* displays its parent region -- so it is the fallback, not the first ask.
|
|
21
|
+
*/
|
|
22
|
+
export declare function searchWindows(view: GenomeView): {
|
|
23
|
+
assemblyName: string;
|
|
24
|
+
refName: string;
|
|
25
|
+
start: number;
|
|
26
|
+
end: number;
|
|
27
|
+
}[][];
|
|
28
|
+
/**
|
|
29
|
+
* Gene models live on feature tracks. Asking an alignments, variant or
|
|
30
|
+
* quantitative track for a chromosome's worth of features to look for a
|
|
31
|
+
* transcript among them is the slowest possible way to find nothing.
|
|
32
|
+
*/
|
|
33
|
+
export declare function transcriptTracks<T extends {
|
|
34
|
+
type: string;
|
|
35
|
+
}>(tracks: T[]): T[];
|
|
2
36
|
/**
|
|
3
37
|
* Resolve `connectedTranscript` into `connectedFeature` once the connected
|
|
4
|
-
* genome view has its assembly
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* readiness
|
|
38
|
+
* genome view has its assembly and tracks, then hand the translation to
|
|
39
|
+
* whichever launch is waiting for a query.
|
|
40
|
+
*
|
|
41
|
+
* The autorun re-fires on the view's readiness (a track opened after a miss)
|
|
42
|
+
* and on the request itself, which is what `retryLaunch` re-states. The lookup
|
|
43
|
+
* runs untracked, so scrolling the genome view is not a reason to look again.
|
|
8
44
|
*/
|
|
9
45
|
export declare function resolveConnectedTranscriptIfNeeded(self: JBrowsePluginMsaViewModel): void;
|
|
46
|
+
export {};
|
|
@@ -1,22 +1,10 @@
|
|
|
1
1
|
import { getConf } from '@jbrowse/core/configuration';
|
|
2
2
|
import { getSession } from '@jbrowse/core/util';
|
|
3
|
+
import { isAlive } from '@jbrowse/mobx-state-tree';
|
|
4
|
+
import { transaction, untracked } from 'mobx';
|
|
3
5
|
import { getProteinSequenceFromFeature } from '../LaunchMsaView/components/calculateProteinSequence';
|
|
4
6
|
import { fetchSeq } from '../LaunchMsaView/components/fetchSeq';
|
|
5
7
|
import { getTranscriptFeatures } from '../LaunchMsaView/util';
|
|
6
|
-
// The short form of a connected launch. A spec that says
|
|
7
|
-
//
|
|
8
|
-
// {"type":"MsaView","connectedViewId":"lgv1","connectedTranscript":"NM_000546.6", ...}
|
|
9
|
-
//
|
|
10
|
-
// gets its `connectedFeature` -- the exon model every genome<->column mapping
|
|
11
|
-
// runs through -- looked up here, from the gene tracks the connected genome
|
|
12
|
-
// view has open, and its query row translated from that transcript. Before
|
|
13
|
-
// this the spec had to carry the transcript's CDS list itself, ~1.5kB of
|
|
14
|
-
// coordinates a person cannot type and a tutorial cannot show.
|
|
15
|
-
//
|
|
16
|
-
// jbrowse-plugin-protein3d does the same for its `transcriptId` shorthand,
|
|
17
|
-
// against a `connectedView` spec of its own; here the genome view already
|
|
18
|
-
// exists (the spec pinned its id), so its tracks and regions are read off the
|
|
19
|
-
// live model instead.
|
|
20
8
|
function stripVersion(id) {
|
|
21
9
|
return id.replace(/\.\d+$/, '');
|
|
22
10
|
}
|
|
@@ -29,26 +17,51 @@ function transcriptMatches(transcript, id) {
|
|
|
29
17
|
transcript.id(),
|
|
30
18
|
].some(c => typeof c === 'string' && (c === id || stripVersion(c) === target));
|
|
31
19
|
}
|
|
20
|
+
function plainRegions(regions) {
|
|
21
|
+
return regions.map(r => ({
|
|
22
|
+
assemblyName: r.assemblyName,
|
|
23
|
+
refName: r.refName,
|
|
24
|
+
start: Math.floor(r.start),
|
|
25
|
+
end: Math.ceil(r.end),
|
|
26
|
+
}));
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The part of the genome on screen, then everything the view can scroll to.
|
|
30
|
+
* `displayedRegions` is usually whole chromosomes -- navigating to a locus
|
|
31
|
+
* displays its parent region -- so it is the fallback, not the first ask.
|
|
32
|
+
*/
|
|
33
|
+
export function searchWindows(view) {
|
|
34
|
+
const visible = plainRegions(view.dynamicBlocks?.contentBlocks ?? []);
|
|
35
|
+
const displayed = plainRegions(view.displayedRegions);
|
|
36
|
+
return visible.length > 0 ? [visible, displayed] : [displayed];
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Gene models live on feature tracks. Asking an alignments, variant or
|
|
40
|
+
* quantitative track for a chromosome's worth of features to look for a
|
|
41
|
+
* transcript among them is the slowest possible way to find nothing.
|
|
42
|
+
*/
|
|
43
|
+
export function transcriptTracks(tracks) {
|
|
44
|
+
return tracks.filter(t => t.type === 'FeatureTrack');
|
|
45
|
+
}
|
|
32
46
|
async function findTranscript(self, transcriptId) {
|
|
33
47
|
const view = self.connectedView;
|
|
34
48
|
const session = getSession(self);
|
|
35
49
|
const sessionId = 'msaview-connectedTranscript';
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
sessionId,
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
return hit;
|
|
50
|
+
const tracks = transcriptTracks(view.tracks);
|
|
51
|
+
for (const regions of searchWindows(view)) {
|
|
52
|
+
for (const track of tracks) {
|
|
53
|
+
// a named object keeps sessionId, which v4 hosts read from the args
|
|
54
|
+
const args = {
|
|
55
|
+
adapterConfig: getConf(track, 'adapter'),
|
|
56
|
+
sessionId,
|
|
57
|
+
regions,
|
|
58
|
+
};
|
|
59
|
+
const feats = await session.rpcManager.call(sessionId, 'CoreGetFeatures', args);
|
|
60
|
+
for (const feat of feats) {
|
|
61
|
+
const hit = getTranscriptFeatures(feat).find(t => transcriptMatches(t, transcriptId));
|
|
62
|
+
if (hit) {
|
|
63
|
+
return hit;
|
|
64
|
+
}
|
|
52
65
|
}
|
|
53
66
|
}
|
|
54
67
|
}
|
|
@@ -57,46 +70,43 @@ async function findTranscript(self, transcriptId) {
|
|
|
57
70
|
async function translate(self, transcript) {
|
|
58
71
|
const { start, end, refName } = transcript.toJSON();
|
|
59
72
|
const assemblyName = self.connectedView.assemblyNames[0];
|
|
60
|
-
const seq = await fetchSeq({
|
|
73
|
+
const { seq, assemblyGeneticCodeId } = await fetchSeq({
|
|
61
74
|
start,
|
|
62
75
|
end,
|
|
63
76
|
refName,
|
|
64
77
|
assemblyName,
|
|
65
78
|
session: getSession(self),
|
|
66
79
|
});
|
|
67
|
-
return getProteinSequenceFromFeature({
|
|
80
|
+
return getProteinSequenceFromFeature({
|
|
81
|
+
seq,
|
|
82
|
+
feature: transcript,
|
|
83
|
+
assemblyGeneticCodeId,
|
|
84
|
+
});
|
|
68
85
|
}
|
|
69
|
-
/**
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
!
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
if (!proteinSequence) {
|
|
94
|
-
throw new Error(`Transcript "${connectedTranscript}" has no CDS to translate`);
|
|
95
|
-
}
|
|
96
|
-
// toJSON, not the Feature: the params are frozen snapshot properties, so
|
|
97
|
-
// an instance in one is gone the moment the session reloads
|
|
98
|
-
const transcriptJson = transcript.toJSON();
|
|
99
|
-
const { blastParams, orthologParams } = self;
|
|
86
|
+
/** the latest lookup per view; an older one finishing late writes nothing */
|
|
87
|
+
const latestLookup = new WeakMap();
|
|
88
|
+
async function lookUp({ self, transcriptId, blastParams, orthologParams, isCurrent, }) {
|
|
89
|
+
const trackCount = transcriptTracks(self.connectedView.tracks).length;
|
|
90
|
+
try {
|
|
91
|
+
self.setProgress(`Looking up ${transcriptId} in the genome view...`);
|
|
92
|
+
const transcript = await findTranscript(self, transcriptId);
|
|
93
|
+
if (!transcript) {
|
|
94
|
+
throw new Error(`Transcript "${transcriptId}" was not found in the ${trackCount} feature track(s) open in the connected genome view. Open the gene track that carries it, and make sure the view is on its locus.`);
|
|
95
|
+
}
|
|
96
|
+
const proteinSequence = await translate(self, transcript);
|
|
97
|
+
if (!proteinSequence) {
|
|
98
|
+
throw new Error(`Transcript "${transcriptId}" has no CDS to translate`);
|
|
99
|
+
}
|
|
100
|
+
if (!isCurrent()) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
// toJSON, not the Feature: the params are frozen snapshot properties, so
|
|
104
|
+
// an instance in one is gone the moment the session reloads
|
|
105
|
+
const transcriptJson = transcript.toJSON();
|
|
106
|
+
// one transaction, so the launchers wake once, on a request that already
|
|
107
|
+
// carries its query
|
|
108
|
+
transaction(() => {
|
|
109
|
+
self.setProgress('');
|
|
100
110
|
if (blastParams && !blastParams.proteinSequence) {
|
|
101
111
|
self.setBlastParams({
|
|
102
112
|
...blastParams,
|
|
@@ -111,12 +121,43 @@ export function resolveConnectedTranscriptIfNeeded(self) {
|
|
|
111
121
|
proteinSequence,
|
|
112
122
|
});
|
|
113
123
|
}
|
|
114
|
-
// last, because the launchers wake on it
|
|
115
124
|
self.setConnectedFeature(transcriptJson);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
catch (e) {
|
|
128
|
+
console.error(e);
|
|
129
|
+
if (isCurrent()) {
|
|
130
|
+
self.setProgress('');
|
|
119
131
|
self.setError(e);
|
|
120
132
|
}
|
|
121
|
-
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Resolve `connectedTranscript` into `connectedFeature` once the connected
|
|
137
|
+
* genome view has its assembly and tracks, then hand the translation to
|
|
138
|
+
* whichever launch is waiting for a query.
|
|
139
|
+
*
|
|
140
|
+
* The autorun re-fires on the view's readiness (a track opened after a miss)
|
|
141
|
+
* and on the request itself, which is what `retryLaunch` re-states. The lookup
|
|
142
|
+
* runs untracked, so scrolling the genome view is not a reason to look again.
|
|
143
|
+
*/
|
|
144
|
+
export function resolveConnectedTranscriptIfNeeded(self) {
|
|
145
|
+
const { connectedTranscript, connectedFeature, connectedView, blastParams, orthologParams, } = self;
|
|
146
|
+
if (!connectedTranscript ||
|
|
147
|
+
connectedFeature ||
|
|
148
|
+
!connectedView?.initialized ||
|
|
149
|
+
connectedView.tracks.length === 0) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const lookup = (latestLookup.get(self) ?? 0) + 1;
|
|
153
|
+
latestLookup.set(self, lookup);
|
|
154
|
+
untracked(() => {
|
|
155
|
+
void lookUp({
|
|
156
|
+
self,
|
|
157
|
+
transcriptId: connectedTranscript,
|
|
158
|
+
blastParams,
|
|
159
|
+
orthologParams,
|
|
160
|
+
isCurrent: () => isAlive(self) && latestLookup.get(self) === lookup,
|
|
161
|
+
});
|
|
162
|
+
});
|
|
122
163
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
|
2
|
+
import stateModelFactory from './model';
|
|
3
|
+
const rpcCall = vi.fn();
|
|
4
|
+
const lgv = {
|
|
5
|
+
id: 'lgv1',
|
|
6
|
+
initialized: true,
|
|
7
|
+
assemblyNames: ['hg38'],
|
|
8
|
+
tracks: [],
|
|
9
|
+
displayedRegions: [
|
|
10
|
+
{ assemblyName: 'hg38', refName: 'chr17', start: 0, end: 83_257_441 },
|
|
11
|
+
],
|
|
12
|
+
dynamicBlocks: {
|
|
13
|
+
contentBlocks: [],
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
vi.mock('@jbrowse/core/util', async (importOriginal) => ({
|
|
17
|
+
...(await importOriginal()),
|
|
18
|
+
getSession: () => ({
|
|
19
|
+
views: [lgv],
|
|
20
|
+
hovered: undefined,
|
|
21
|
+
rpcManager: { call: rpcCall },
|
|
22
|
+
}),
|
|
23
|
+
}));
|
|
24
|
+
vi.mock('@jbrowse/core/configuration', async (importOriginal) => ({
|
|
25
|
+
...(await importOriginal()),
|
|
26
|
+
getConf: (track) => track.adapter,
|
|
27
|
+
}));
|
|
28
|
+
vi.mock('../LaunchMsaView/components/fetchSeq', () => ({
|
|
29
|
+
fetchSeq: vi.fn(async () => ({ seq: 'ATGAAAGTG' })),
|
|
30
|
+
}));
|
|
31
|
+
vi.mock('../LaunchMsaView/components/calculateProteinSequence', () => ({
|
|
32
|
+
getProteinSequenceFromFeature: vi.fn(() => 'MKV'),
|
|
33
|
+
}));
|
|
34
|
+
vi.mock('./doLaunchBlast', () => ({
|
|
35
|
+
doLaunchBlast: vi.fn(() => new Promise(() => { })),
|
|
36
|
+
}));
|
|
37
|
+
vi.mock('./msaDataStore', () => ({
|
|
38
|
+
cleanupOldData: vi.fn(async () => { }),
|
|
39
|
+
deleteMsaData: vi.fn(async () => { }),
|
|
40
|
+
generateDataStoreId: vi.fn(),
|
|
41
|
+
retrieveMsaData: vi.fn(),
|
|
42
|
+
storeMsaData: vi.fn(),
|
|
43
|
+
}));
|
|
44
|
+
function transcript(id) {
|
|
45
|
+
const json = { uniqueId: id, id, refName: 'chr17', start: 0, end: 9 };
|
|
46
|
+
return {
|
|
47
|
+
id: () => id,
|
|
48
|
+
get: (key) => (key === 'id' || key === 'name' ? id : undefined),
|
|
49
|
+
toJSON: () => json,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
const settle = () => new Promise(res => setTimeout(res, 0));
|
|
53
|
+
function connectedView() {
|
|
54
|
+
return stateModelFactory().create({
|
|
55
|
+
type: 'MsaView',
|
|
56
|
+
id: 'msa1',
|
|
57
|
+
connectedViewId: 'lgv1',
|
|
58
|
+
connectedTranscript: 'NM_000546.6',
|
|
59
|
+
blastParams: {
|
|
60
|
+
searchProgram: 'blastp',
|
|
61
|
+
blastDatabase: 'uniprotkb_swissprot',
|
|
62
|
+
msaAlgorithm: 'clustalo',
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
beforeEach(() => {
|
|
67
|
+
rpcCall.mockReset();
|
|
68
|
+
lgv.tracks = [{ type: 'FeatureTrack', adapter: 'genes' }];
|
|
69
|
+
lgv.dynamicBlocks.contentBlocks = [];
|
|
70
|
+
vi.spyOn(console, 'error').mockImplementation(() => { });
|
|
71
|
+
});
|
|
72
|
+
describe('a lookup that misses', () => {
|
|
73
|
+
test('reports the miss without leaving its progress up', async () => {
|
|
74
|
+
rpcCall.mockResolvedValue([]);
|
|
75
|
+
const model = connectedView();
|
|
76
|
+
await settle();
|
|
77
|
+
expect(model.error).toBeInstanceOf(Error);
|
|
78
|
+
expect(model.progress).toBe('');
|
|
79
|
+
});
|
|
80
|
+
test('Retry looks again', async () => {
|
|
81
|
+
rpcCall.mockResolvedValue([]);
|
|
82
|
+
const model = connectedView();
|
|
83
|
+
await settle();
|
|
84
|
+
const calls = rpcCall.mock.calls.length;
|
|
85
|
+
rpcCall.mockResolvedValue([transcript('NM_000546.6')]);
|
|
86
|
+
model.retryLaunch();
|
|
87
|
+
await settle();
|
|
88
|
+
expect(rpcCall.mock.calls.length).toBeGreaterThan(calls);
|
|
89
|
+
expect(model.connectedFeature).toMatchObject({ id: 'NM_000546.6' });
|
|
90
|
+
expect(model.blastParams?.proteinSequence).toBe('MKV');
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
describe('where a lookup searches', () => {
|
|
94
|
+
test('feature tracks only, on screen before the whole chromosome', async () => {
|
|
95
|
+
rpcCall.mockResolvedValue([]);
|
|
96
|
+
lgv.tracks = [
|
|
97
|
+
{ type: 'AlignmentsTrack', adapter: 'bam' },
|
|
98
|
+
{ type: 'FeatureTrack', adapter: 'genes' },
|
|
99
|
+
];
|
|
100
|
+
lgv.dynamicBlocks.contentBlocks = [
|
|
101
|
+
{
|
|
102
|
+
assemblyName: 'hg38',
|
|
103
|
+
refName: 'chr17',
|
|
104
|
+
start: 7_661_778.6,
|
|
105
|
+
end: 7_687_538.2,
|
|
106
|
+
},
|
|
107
|
+
];
|
|
108
|
+
connectedView();
|
|
109
|
+
await settle();
|
|
110
|
+
const asked = rpcCall.mock.calls.map(([, , args]) => [
|
|
111
|
+
args.adapterConfig,
|
|
112
|
+
args.regions,
|
|
113
|
+
]);
|
|
114
|
+
expect(asked).toEqual([
|
|
115
|
+
[
|
|
116
|
+
'genes',
|
|
117
|
+
[
|
|
118
|
+
{
|
|
119
|
+
assemblyName: 'hg38',
|
|
120
|
+
refName: 'chr17',
|
|
121
|
+
start: 7_661_778,
|
|
122
|
+
end: 7_687_539,
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
],
|
|
126
|
+
['genes', lgv.displayedRegions],
|
|
127
|
+
]);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
describe('two lookups in flight', () => {
|
|
131
|
+
test('only the later one writes', async () => {
|
|
132
|
+
const pending = [];
|
|
133
|
+
rpcCall.mockImplementation(() => new Promise(res => {
|
|
134
|
+
pending.push(res);
|
|
135
|
+
}));
|
|
136
|
+
const model = connectedView();
|
|
137
|
+
await settle();
|
|
138
|
+
model.retryLaunch();
|
|
139
|
+
await settle();
|
|
140
|
+
expect(pending).toHaveLength(2);
|
|
141
|
+
pending[1]([transcript('NM_000546.6')]);
|
|
142
|
+
await settle();
|
|
143
|
+
const written = model.connectedFeature;
|
|
144
|
+
pending[0]([transcript('NM_000546.5')]);
|
|
145
|
+
await settle();
|
|
146
|
+
expect(written).toMatchObject({ id: 'NM_000546.6' });
|
|
147
|
+
expect(model.connectedFeature).toBe(written);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
@@ -26,11 +26,15 @@ interface LaunchedData {
|
|
|
26
26
|
* Run one launch attempt, owning the AbortController that ties it to the view.
|
|
27
27
|
*
|
|
28
28
|
* The controller lives on the model because two things end a launch and neither
|
|
29
|
-
* is here: the Cancel button, and the disposer `afterCreate` registers.
|
|
30
|
-
* it
|
|
31
|
-
*
|
|
29
|
+
* is here: the Cancel button, and the disposer `afterCreate` registers. A launch
|
|
30
|
+
* already holding it is aborted first, so a re-fired autorun replaces the
|
|
31
|
+
* attempt rather than orphaning one that nothing can cancel.
|
|
32
|
+
*
|
|
33
|
+
* Runs untracked: it is called from the launch autoruns, and whatever the
|
|
34
|
+
* launch body reads before its first await would otherwise become a reason to
|
|
35
|
+
* launch again.
|
|
32
36
|
*/
|
|
33
|
-
export declare function runLaunch(
|
|
37
|
+
export declare function runLaunch(args: {
|
|
34
38
|
self: JBrowsePluginMsaViewModel;
|
|
35
39
|
message: string;
|
|
36
40
|
launch: (scope: LaunchScope) => Promise<LaunchedData>;
|