jbrowse-plugin-msaview 3.7.1 → 3.9.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/dist/AddHighlightModel/MsaToGenomeHighlight.js +6 -22
- package/dist/AddHighlightModel/connectedHighlights.d.ts +9 -0
- package/dist/AddHighlightModel/connectedHighlights.js +15 -0
- package/dist/AddHighlightModel/connectedHighlights.test.d.ts +1 -0
- package/dist/AddHighlightModel/connectedHighlights.test.js +36 -0
- package/dist/AddHighlightModel/index.js +0 -1
- package/dist/LaunchMsaView/components/BlastQuery/consts.d.ts +4 -4
- package/dist/LaunchMsaView/components/BlastQuery/consts.js +6 -4
- package/dist/LaunchMsaView/components/BlastQuery/searchChoiceStorage.d.ts +0 -2
- package/dist/LaunchMsaView/components/BlastQuery/searchChoiceStorage.js +2 -2
- package/dist/LaunchMsaView/components/OrthologQuery/OrthologSourceSelect.d.ts +0 -1
- package/dist/LaunchMsaView/components/OrthologQuery/OrthologSourceSelect.js +1 -1
- package/dist/LaunchMsaView/components/OrthologQuery/QuerySpeciesSelect.js +5 -5
- package/dist/LaunchMsaView/components/calculateProteinSequence.d.ts +0 -6
- package/dist/LaunchMsaView/components/calculateProteinSequence.js +1 -1
- package/dist/LaunchMsaView/components/types.d.ts +0 -3
- package/dist/LaunchMsaView/launchTarget.d.ts +3 -0
- package/dist/LaunchMsaView/launchTarget.js +1 -0
- package/dist/LaunchMsaView/launchTarget.test.js +13 -0
- package/dist/LaunchMsaView/util.d.ts +0 -1
- package/dist/LaunchMsaView/util.js +1 -1
- package/dist/MsaViewPanel/afterCreateAutoruns.d.ts +5 -14
- package/dist/MsaViewPanel/afterCreateAutoruns.js +77 -86
- package/dist/MsaViewPanel/components/MsaViewPanel.js +1 -1
- package/dist/MsaViewPanel/doLaunchBlast.js +42 -16
- package/dist/MsaViewPanel/doLaunchBlast.test.js +54 -0
- package/dist/MsaViewPanel/doLaunchOrthologs.js +18 -11
- package/dist/MsaViewPanel/launchAutoruns.test.js +0 -1
- package/dist/MsaViewPanel/model.d.ts +13 -15
- package/dist/MsaViewPanel/model.js +39 -22
- package/dist/MsaViewPanel/model.test.js +3 -4
- package/dist/MsaViewPanel/msaDataStore.d.ts +1 -2
- package/dist/MsaViewPanel/msaDataStore.js +11 -26
- package/dist/MsaViewPanel/msaDataStore.test.js +10 -2
- package/dist/MsaViewPanel/observeProteinHighlights.test.js +1 -0
- package/dist/MsaViewPanel/processInit.test.js +1 -7
- package/dist/MsaViewPanel/regionSnapshot.test.js +0 -1
- package/dist/MsaViewPanel/resolveConnectedTranscript.test.js +0 -1
- package/dist/MsaViewPanel/storedData.test.js +238 -145
- package/dist/MsaViewPanel/transcriptMap.d.ts +7 -0
- package/dist/MsaViewPanel/transcriptMap.js +89 -0
- package/dist/MsaViewPanel/transcriptMap.test.d.ts +1 -0
- package/dist/MsaViewPanel/transcriptMap.test.js +108 -0
- package/dist/jbrowse-plugin-msaview.umd.production.min.js +38 -38
- package/dist/jbrowse-plugin-msaview.umd.production.min.js.map +4 -4
- package/dist/utils/browserAlign.d.ts +5 -3
- package/dist/utils/browserAlign.js +29 -23
- package/dist/utils/browserAlign.test.js +14 -1
- package/dist/utils/ebiBlast.d.ts +0 -8
- package/dist/utils/ebiBlast.js +10 -21
- package/dist/utils/ebiJobDispatcher.d.ts +9 -9
- package/dist/utils/ebiJobDispatcher.js +17 -3
- package/dist/utils/eutils.d.ts +0 -2
- package/dist/utils/eutils.js +11 -3
- package/dist/utils/eutils.test.js +22 -0
- package/dist/utils/msa.js +6 -14
- package/dist/utils/msaRows.d.ts +0 -8
- package/dist/utils/msaRows.js +1 -1
- package/dist/utils/ncbiOrthologs.d.ts +7 -5
- package/dist/utils/ncbiOrthologs.js +20 -14
- package/dist/utils/ncbiOrthologs.test.js +13 -0
- package/dist/utils/ncbiTaxonomy.d.ts +2 -2
- package/dist/utils/ncbiTaxonomy.js +5 -5
- package/dist/utils/pantherOrthologs.d.ts +2 -3
- package/dist/utils/pantherOrthologs.js +18 -12
- package/dist/utils/phmmer.js +7 -14
- package/dist/utils/searchCache.d.ts +18 -0
- package/dist/utils/searchCache.js +33 -0
- package/dist/utils/searchCache.test.d.ts +1 -0
- package/dist/utils/searchCache.test.js +80 -0
- package/dist/utils/taxonomyNames.d.ts +1 -1
- package/dist/utils/taxonomyNames.js +7 -2
- package/dist/utils/taxonomyNamesAbort.test.d.ts +1 -0
- package/dist/utils/taxonomyNamesAbort.test.js +36 -0
- package/dist/utils/unirefHomologs.d.ts +1 -6
- package/dist/utils/unirefHomologs.js +2 -2
- package/dist/utils/useFetch.d.ts +1 -1
- package/dist/utils/useFetch.js +5 -5
- package/dist/utils/useFetchAbort.test.d.ts +1 -0
- package/dist/utils/useFetchAbort.test.js +29 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/src/AddHighlightModel/MsaToGenomeHighlight.tsx +10 -23
- package/src/AddHighlightModel/connectedHighlights.test.ts +46 -0
- package/src/AddHighlightModel/connectedHighlights.ts +35 -0
- package/src/AddHighlightModel/index.tsx +0 -1
- package/src/LaunchMsaView/components/BlastQuery/consts.ts +6 -4
- package/src/LaunchMsaView/components/BlastQuery/searchChoiceStorage.ts +2 -2
- package/src/LaunchMsaView/components/OrthologQuery/OrthologSourceSelect.tsx +1 -1
- package/src/LaunchMsaView/components/OrthologQuery/QuerySpeciesSelect.tsx +5 -5
- package/src/LaunchMsaView/components/calculateProteinSequence.ts +1 -1
- package/src/LaunchMsaView/components/types.ts +0 -4
- package/src/LaunchMsaView/launchTarget.test.ts +14 -0
- package/src/LaunchMsaView/launchTarget.ts +8 -4
- package/src/LaunchMsaView/util.ts +1 -1
- package/src/MsaViewPanel/afterCreateAutoruns.ts +90 -87
- package/src/MsaViewPanel/components/MsaViewPanel.tsx +1 -1
- package/src/MsaViewPanel/doLaunchBlast.test.ts +71 -0
- package/src/MsaViewPanel/doLaunchBlast.ts +71 -20
- package/src/MsaViewPanel/doLaunchOrthologs.ts +28 -11
- package/src/MsaViewPanel/launchAutoruns.test.ts +0 -1
- package/src/MsaViewPanel/model.test.ts +3 -4
- package/src/MsaViewPanel/model.ts +40 -22
- package/src/MsaViewPanel/msaDataStore.test.ts +13 -2
- package/src/MsaViewPanel/msaDataStore.ts +11 -25
- package/src/MsaViewPanel/observeProteinHighlights.test.ts +1 -0
- package/src/MsaViewPanel/processInit.test.ts +1 -8
- package/src/MsaViewPanel/regionSnapshot.test.ts +0 -1
- package/src/MsaViewPanel/resolveConnectedTranscript.test.ts +0 -1
- package/src/MsaViewPanel/storedData.test.ts +273 -158
- package/src/MsaViewPanel/transcriptMap.test.ts +137 -0
- package/src/MsaViewPanel/transcriptMap.ts +117 -0
- package/src/utils/browserAlign.test.ts +17 -0
- package/src/utils/browserAlign.ts +39 -25
- package/src/utils/ebiBlast.ts +12 -35
- package/src/utils/ebiJobDispatcher.ts +26 -7
- package/src/utils/eutils.test.ts +25 -0
- package/src/utils/eutils.ts +10 -3
- package/src/utils/msa.ts +6 -16
- package/src/utils/msaRows.ts +1 -1
- package/src/utils/ncbiOrthologs.test.ts +16 -0
- package/src/utils/ncbiOrthologs.ts +37 -15
- package/src/utils/ncbiTaxonomy.ts +8 -2
- package/src/utils/pantherOrthologs.ts +28 -12
- package/src/utils/phmmer.ts +7 -16
- package/src/utils/searchCache.test.ts +98 -0
- package/src/utils/searchCache.ts +75 -0
- package/src/utils/taxonomyNames.ts +7 -0
- package/src/utils/taxonomyNamesAbort.test.ts +46 -0
- package/src/utils/unirefHomologs.ts +2 -2
- package/src/utils/useFetch.ts +6 -6
- package/src/utils/useFetchAbort.test.tsx +33 -0
- package/src/version.ts +1 -1
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { cleanProteinSequence } from '../LaunchMsaView/util';
|
|
2
2
|
import { saveBlastResult } from '../utils/blastCache';
|
|
3
|
+
import { checkPairSize } from '../utils/browserAlign';
|
|
3
4
|
import { searchBackends } from '../utils/homologSearch';
|
|
4
5
|
import { launchMSA } from '../utils/msa';
|
|
5
6
|
import { buildSearchMsa } from '../utils/msaRows';
|
|
7
|
+
import { getCachedSearch, saveSearch, searchKey } from '../utils/searchCache';
|
|
6
8
|
import { fetchTaxonomyInfo } from '../utils/taxonomyNames';
|
|
7
9
|
import { resolveUniProtEntry } from '../utils/unirefHomologs';
|
|
8
10
|
import { transcriptFields, transcriptName } from './util';
|
|
@@ -38,23 +40,13 @@ async function resolveQuery(self, scope) {
|
|
|
38
40
|
}
|
|
39
41
|
throw new Error('No query: a search needs a proteinSequence, a UniProt accession, or a connectedTranscript to translate');
|
|
40
42
|
}
|
|
41
|
-
|
|
42
|
-
* A similarity search, then an alignment of what it found. The program is a
|
|
43
|
-
* backend behind one interface (utils/homologSearch.ts); what differs between
|
|
44
|
-
* them is settled by whether the result came back aligned. A program that
|
|
45
|
-
* aligns as it searches (phmmer) hands over the alignment and the tree is
|
|
46
|
-
* built from it in the browser; one that does not (blastp) hands over bare
|
|
47
|
-
* hits and the chosen aligner runs on them.
|
|
48
|
-
*/
|
|
49
|
-
export async function doLaunchBlast({ self, scope, }) {
|
|
43
|
+
async function search({ self, scope, query, querySeqName, key, }) {
|
|
50
44
|
const params = self.blastParams;
|
|
51
|
-
const { selectedTranscript, maxHits, searchProgram = 'blastp' } = params;
|
|
52
|
-
const { sequence: query, name: querySeqName } = await resolveQuery(self, scope);
|
|
53
45
|
const { onProgress, onRid, signal } = scope;
|
|
54
|
-
const { hits, queryRow, rid } = await searchBackends[searchProgram]({
|
|
46
|
+
const { hits, queryRow, rid } = await searchBackends[params.searchProgram ?? 'blastp']({
|
|
55
47
|
query,
|
|
56
48
|
database: params.blastDatabase,
|
|
57
|
-
maxHits,
|
|
49
|
+
maxHits: params.maxHits,
|
|
58
50
|
onProgress,
|
|
59
51
|
onRid,
|
|
60
52
|
signal,
|
|
@@ -63,7 +55,7 @@ export async function doLaunchBlast({ self, scope, }) {
|
|
|
63
55
|
throw new Error('No hits found');
|
|
64
56
|
}
|
|
65
57
|
onProgress('Fetching species taxonomy info...');
|
|
66
|
-
const taxonomyInfo = await fetchTaxonomyInfo(hits.map(h => h.taxid).filter((t) => t !== undefined));
|
|
58
|
+
const taxonomyInfo = await fetchTaxonomyInfo(hits.map(h => h.taxid).filter((t) => t !== undefined), signal);
|
|
67
59
|
const { msa: fasta, treeMetadata } = buildSearchMsa({
|
|
68
60
|
hits,
|
|
69
61
|
query,
|
|
@@ -71,10 +63,44 @@ export async function doLaunchBlast({ self, scope, }) {
|
|
|
71
63
|
taxonomyInfo,
|
|
72
64
|
querySeqName,
|
|
73
65
|
});
|
|
66
|
+
if (!queryRow) {
|
|
67
|
+
await saveSearch({ id: key, fasta, treeMetadata, rid });
|
|
68
|
+
}
|
|
69
|
+
return { fasta, treeMetadata, rid, queryRow };
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* A similarity search, then an alignment of what it found. The program is a
|
|
73
|
+
* backend behind one interface (utils/homologSearch.ts); what differs between
|
|
74
|
+
* them is settled by whether the result came back aligned. A program that
|
|
75
|
+
* aligns as it searches (phmmer) hands over the alignment and the tree is
|
|
76
|
+
* built from it in the browser; one that does not (blastp) hands over bare
|
|
77
|
+
* hits and the chosen aligner runs on them.
|
|
78
|
+
*/
|
|
79
|
+
export async function doLaunchBlast({ self, scope, }) {
|
|
80
|
+
const params = self.blastParams;
|
|
81
|
+
const { selectedTranscript, maxHits, searchProgram = 'blastp' } = params;
|
|
82
|
+
const msaAlgorithm = params.msaAlgorithm ?? 'browser';
|
|
83
|
+
const { sequence: query, name: querySeqName } = await resolveQuery(self, scope);
|
|
84
|
+
if (searchProgram === 'blastp' && msaAlgorithm === 'browser') {
|
|
85
|
+
checkPairSize(querySeqName, query.length, query.length);
|
|
86
|
+
}
|
|
87
|
+
const { onProgress, signal } = scope;
|
|
88
|
+
const key = searchKey({
|
|
89
|
+
searchProgram,
|
|
90
|
+
database: params.blastDatabase,
|
|
91
|
+
maxHits,
|
|
92
|
+
querySeqName,
|
|
93
|
+
query,
|
|
94
|
+
});
|
|
95
|
+
const found = await getCachedSearch(key);
|
|
96
|
+
if (found) {
|
|
97
|
+
onProgress(`Reusing the ${searchProgram} search from ${new Date(found.timestamp).toLocaleString()}...`);
|
|
98
|
+
}
|
|
99
|
+
const { fasta, treeMetadata, rid, queryRow } = found ?? (await search({ self, scope, query, querySeqName, key }));
|
|
74
100
|
const { msa, tree } = queryRow
|
|
75
101
|
? { msa: fasta, tree: '' }
|
|
76
102
|
: await launchMSA({
|
|
77
|
-
algorithm:
|
|
103
|
+
algorithm: msaAlgorithm,
|
|
78
104
|
sequence: fasta,
|
|
79
105
|
onProgress,
|
|
80
106
|
signal,
|
|
@@ -84,7 +110,7 @@ export async function doLaunchBlast({ self, scope, }) {
|
|
|
84
110
|
await saveBlastResult({
|
|
85
111
|
proteinSequence: query,
|
|
86
112
|
blastDatabase: params.blastDatabase,
|
|
87
|
-
msaAlgorithm: queryRow ? undefined :
|
|
113
|
+
msaAlgorithm: queryRow ? undefined : msaAlgorithm,
|
|
88
114
|
searchProgram: params.searchProgram,
|
|
89
115
|
maxHits,
|
|
90
116
|
msa,
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { beforeEach, expect, test, vi } from 'vitest';
|
|
2
2
|
import { saveBlastResult } from '../utils/blastCache';
|
|
3
|
+
import { MAX_PAIR_CELLS } from '../utils/browserAlign';
|
|
3
4
|
import { searchBackends } from '../utils/homologSearch';
|
|
4
5
|
import { launchMSA } from '../utils/msa';
|
|
6
|
+
import { getCachedSearch, saveSearch } from '../utils/searchCache';
|
|
5
7
|
import { fetchTaxonomyInfo } from '../utils/taxonomyNames';
|
|
6
8
|
import { resolveUniProtEntry } from '../utils/unirefHomologs';
|
|
7
9
|
import { doLaunchBlast } from './doLaunchBlast';
|
|
@@ -15,6 +17,11 @@ vi.mock('../utils/msa', () => ({ launchMSA: vi.fn() }));
|
|
|
15
17
|
vi.mock('../utils/taxonomyNames', () => ({ fetchTaxonomyInfo: vi.fn() }));
|
|
16
18
|
vi.mock('../utils/blastCache', () => ({ saveBlastResult: vi.fn() }));
|
|
17
19
|
vi.mock('../utils/unirefHomologs', () => ({ resolveUniProtEntry: vi.fn() }));
|
|
20
|
+
vi.mock('../utils/searchCache', async (importOriginal) => ({
|
|
21
|
+
...(await importOriginal()),
|
|
22
|
+
getCachedSearch: vi.fn(),
|
|
23
|
+
saveSearch: vi.fn(),
|
|
24
|
+
}));
|
|
18
25
|
const blastp = vi.mocked(searchBackends.blastp);
|
|
19
26
|
const phmmer = vi.mocked(searchBackends.phmmer);
|
|
20
27
|
const mockLaunchMSA = vi.mocked(launchMSA);
|
|
@@ -44,6 +51,8 @@ beforeEach(() => {
|
|
|
44
51
|
vi.clearAllMocks();
|
|
45
52
|
vi.mocked(fetchTaxonomyInfo).mockResolvedValue(new Map());
|
|
46
53
|
vi.mocked(saveBlastResult).mockResolvedValue(undefined);
|
|
54
|
+
vi.mocked(getCachedSearch).mockResolvedValue(undefined);
|
|
55
|
+
vi.mocked(saveSearch).mockResolvedValue(undefined);
|
|
47
56
|
});
|
|
48
57
|
test('bare hits go to the chosen aligner, with the query first', async () => {
|
|
49
58
|
blastp.mockResolvedValue({ rid: 'job', hits: [{ ...HIT, sequence: 'MKWV' }] });
|
|
@@ -168,3 +177,48 @@ test('a live Feature handed over in the same session labels it the same way', as
|
|
|
168
177
|
geneName: 'TP53',
|
|
169
178
|
}));
|
|
170
179
|
});
|
|
180
|
+
const BLASTP_BROWSER = {
|
|
181
|
+
searchProgram: 'blastp',
|
|
182
|
+
blastDatabase: 'uniprotkb_swissprot',
|
|
183
|
+
msaAlgorithm: 'browser',
|
|
184
|
+
proteinSequence: 'MKWVTF',
|
|
185
|
+
};
|
|
186
|
+
test('the hits are saved before the aligner runs, so a failed alignment keeps them', async () => {
|
|
187
|
+
blastp.mockResolvedValue({ rid: 'job', hits: [{ ...HIT, sequence: 'MKWV' }] });
|
|
188
|
+
mockLaunchMSA.mockRejectedValue(new Error('aligner fell over'));
|
|
189
|
+
await expect(launch(makeModel(BLASTP_BROWSER))).rejects.toThrow('aligner fell over');
|
|
190
|
+
expect(saveSearch).toHaveBeenCalledWith(expect.objectContaining({
|
|
191
|
+
fasta: '>QUERY\nMKWVTF\n>P1-Mus_musculus\nMKWV',
|
|
192
|
+
rid: 'job',
|
|
193
|
+
}));
|
|
194
|
+
});
|
|
195
|
+
test('a relaunch with saved hits aligns them without searching again', async () => {
|
|
196
|
+
vi.mocked(getCachedSearch).mockResolvedValue({
|
|
197
|
+
id: 'k',
|
|
198
|
+
fasta: '>QUERY\nMKWVTF\n>P1\nMKWV',
|
|
199
|
+
treeMetadata: {},
|
|
200
|
+
rid: 'job',
|
|
201
|
+
timestamp: 0,
|
|
202
|
+
});
|
|
203
|
+
mockLaunchMSA.mockResolvedValue({ msa: 'aligned', tree: '' });
|
|
204
|
+
const result = await launch(makeModel(BLASTP_BROWSER));
|
|
205
|
+
expect(blastp).not.toHaveBeenCalled();
|
|
206
|
+
expect(mockLaunchMSA).toHaveBeenCalledWith(expect.objectContaining({ sequence: '>QUERY\nMKWVTF\n>P1\nMKWV' }));
|
|
207
|
+
expect(result.msa).toBe('aligned');
|
|
208
|
+
});
|
|
209
|
+
test('a query too long for the in-browser aligner is refused before the search', async () => {
|
|
210
|
+
const side = Math.ceil(Math.sqrt(MAX_PAIR_CELLS)) + 1;
|
|
211
|
+
await expect(launch(makeModel({ ...BLASTP_BROWSER, proteinSequence: 'M'.repeat(side) }))).rejects.toThrow(/too large to align in the browser/);
|
|
212
|
+
expect(blastp).not.toHaveBeenCalled();
|
|
213
|
+
});
|
|
214
|
+
test('the same long query goes to an EBI aligner without complaint', async () => {
|
|
215
|
+
const side = Math.ceil(Math.sqrt(MAX_PAIR_CELLS)) + 1;
|
|
216
|
+
blastp.mockResolvedValue({ rid: 'job', hits: [{ ...HIT, sequence: 'MKWV' }] });
|
|
217
|
+
mockLaunchMSA.mockResolvedValue({ msa: 'aligned', tree: 'tree' });
|
|
218
|
+
await launch(makeModel({
|
|
219
|
+
...BLASTP_BROWSER,
|
|
220
|
+
msaAlgorithm: 'clustalo',
|
|
221
|
+
proteinSequence: 'M'.repeat(side),
|
|
222
|
+
}));
|
|
223
|
+
expect(blastp).toHaveBeenCalled();
|
|
224
|
+
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { cleanProteinSequence } from '../LaunchMsaView/util';
|
|
2
|
+
import { isAbortError } from '../utils/fetch';
|
|
2
3
|
import { launchMSA } from '../utils/msa';
|
|
3
4
|
import { dedupeLabels, fetchOrthologRows, fetchProteinForGene, resolveGeneId, } from '../utils/ncbiOrthologs';
|
|
4
5
|
import { fetchPantherOrthologs } from '../utils/pantherOrthologs';
|
|
@@ -33,6 +34,7 @@ export async function doLaunchOrthologs({ self, scope, }) {
|
|
|
33
34
|
exclude: taxId,
|
|
34
35
|
limit: maxSpecies,
|
|
35
36
|
onProgress,
|
|
37
|
+
signal,
|
|
36
38
|
};
|
|
37
39
|
const { geneId, representative, rows } = source === 'panther'
|
|
38
40
|
? await findPantherOrthologs(request)
|
|
@@ -41,7 +43,6 @@ export async function doLaunchOrthologs({ self, scope, }) {
|
|
|
41
43
|
...request,
|
|
42
44
|
identity,
|
|
43
45
|
referenceProteomesOnly,
|
|
44
|
-
signal,
|
|
45
46
|
})
|
|
46
47
|
: await findNcbiOrthologs(request);
|
|
47
48
|
// The query row. The dialog always supplies it — it is the user's OWN
|
|
@@ -68,7 +69,7 @@ export async function doLaunchOrthologs({ self, scope, }) {
|
|
|
68
69
|
// taxon is excluded from that set, but a subspecies can sanitize to the same
|
|
69
70
|
// token, and a collision would silently point the coordinate mapping at
|
|
70
71
|
// another animal's row.
|
|
71
|
-
const queryLabel = await queryRowLabel(taxId, rows);
|
|
72
|
+
const queryLabel = await queryRowLabel(taxId, rows, signal);
|
|
72
73
|
act(() => {
|
|
73
74
|
self.setQuerySeqName(queryLabel);
|
|
74
75
|
});
|
|
@@ -98,16 +99,17 @@ export async function doLaunchOrthologs({ self, scope, }) {
|
|
|
98
99
|
* wants "this gene across species" gets NCBI's own order, which leads with the
|
|
99
100
|
* reference organisms.
|
|
100
101
|
*/
|
|
101
|
-
async function findNcbiOrthologs({ taxId, geneCandidates, onProgress, ...rest }) {
|
|
102
|
+
async function findNcbiOrthologs({ taxId, geneCandidates, onProgress, signal, ...rest }) {
|
|
102
103
|
onProgress('Resolving gene at NCBI...');
|
|
103
|
-
const resolved = await resolveGeneId(geneCandidates, taxId);
|
|
104
|
+
const resolved = await resolveGeneId(geneCandidates, taxId, signal);
|
|
104
105
|
if (!resolved) {
|
|
105
106
|
throw new Error(`Could not resolve any of ${geneCandidates.join(', ')} to an NCBI gene in taxon ${taxId}. Try the NCBI BLAST tab, which needs no gene identifier.`);
|
|
106
107
|
}
|
|
107
|
-
const representative = await fetchRepresentativeQueryProtein(resolved.geneId);
|
|
108
|
+
const representative = await fetchRepresentativeQueryProtein(resolved.geneId, signal);
|
|
108
109
|
const rows = await fetchOrthologRows({
|
|
109
110
|
geneId: resolved.geneId,
|
|
110
111
|
onProgress,
|
|
112
|
+
signal,
|
|
111
113
|
...rest,
|
|
112
114
|
});
|
|
113
115
|
return { geneId: resolved.geneId, representative, rows };
|
|
@@ -122,12 +124,11 @@ const sourceNames = {
|
|
|
122
124
|
* entry the cluster was looked up from, whose sequence a spec-launched row
|
|
123
125
|
* takes and whose accession earns the CDD overlay when the two match.
|
|
124
126
|
*/
|
|
125
|
-
async function findUnirefHomologs({ geneCandidates, identity, referenceProteomesOnly,
|
|
127
|
+
async function findUnirefHomologs({ geneCandidates, identity, referenceProteomesOnly, ...rest }) {
|
|
126
128
|
const found = await fetchUnirefHomologs({
|
|
127
129
|
candidates: geneCandidates,
|
|
128
130
|
identity,
|
|
129
131
|
referenceProteomesOnly,
|
|
130
|
-
signal,
|
|
131
132
|
...rest,
|
|
132
133
|
});
|
|
133
134
|
return {
|
|
@@ -160,13 +161,16 @@ async function findPantherOrthologs({ geneCandidates, ...rest }) {
|
|
|
160
161
|
* marker when NCBI cannot name the taxon, which is a naming failure and must not
|
|
161
162
|
* take down the launch.
|
|
162
163
|
*/
|
|
163
|
-
async function queryRowLabel(taxId, rows) {
|
|
164
|
+
async function queryRowLabel(taxId, rows, signal) {
|
|
164
165
|
let name;
|
|
165
166
|
try {
|
|
166
|
-
const info = (await fetchTaxonomyInfo([taxId])).get(taxId);
|
|
167
|
+
const info = (await fetchTaxonomyInfo([taxId], signal)).get(taxId);
|
|
167
168
|
name = info?.commonName ?? info?.sciname;
|
|
168
169
|
}
|
|
169
170
|
catch (e) {
|
|
171
|
+
if (isAbortError(e)) {
|
|
172
|
+
throw e;
|
|
173
|
+
}
|
|
170
174
|
console.warn('[msaview-orthologs] taxonomy name lookup failed:', e);
|
|
171
175
|
}
|
|
172
176
|
return dedupeLabels([
|
|
@@ -179,11 +183,14 @@ async function queryRowLabel(taxId, rows) {
|
|
|
179
183
|
* that supplied no sequence of its own, the alignment — so it is reported by
|
|
180
184
|
* returning nothing rather than by throwing here.
|
|
181
185
|
*/
|
|
182
|
-
async function fetchRepresentativeQueryProtein(geneId) {
|
|
186
|
+
async function fetchRepresentativeQueryProtein(geneId, signal) {
|
|
183
187
|
try {
|
|
184
|
-
return await fetchProteinForGene(geneId);
|
|
188
|
+
return await fetchProteinForGene(geneId, signal);
|
|
185
189
|
}
|
|
186
190
|
catch (e) {
|
|
191
|
+
if (isAbortError(e)) {
|
|
192
|
+
throw e;
|
|
193
|
+
}
|
|
187
194
|
console.warn('[msaview-orthologs] query protein lookup failed:', e);
|
|
188
195
|
return undefined;
|
|
189
196
|
}
|
|
@@ -8,7 +8,6 @@ vi.mock('@jbrowse/core/util', async (importOriginal) => ({
|
|
|
8
8
|
vi.mock('./doLaunchBlast', () => ({ doLaunchBlast: vi.fn() }));
|
|
9
9
|
vi.mock('./msaDataStore', () => ({
|
|
10
10
|
cleanupOldData: vi.fn(async () => { }),
|
|
11
|
-
deleteMsaData: vi.fn(async () => { }),
|
|
12
11
|
generateDataStoreId: vi.fn(),
|
|
13
12
|
retrieveMsaData: vi.fn(),
|
|
14
13
|
storeMsaData: vi.fn(),
|
|
@@ -933,19 +933,14 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
|
|
|
933
933
|
loadingStoredData: boolean;
|
|
934
934
|
isStoringData: boolean;
|
|
935
935
|
lastStoredData: MsaDataPayload | undefined;
|
|
936
|
+
ownsDataStoreRow: boolean;
|
|
936
937
|
launchController: AbortController | undefined;
|
|
937
938
|
domainsRequested: boolean;
|
|
938
939
|
} & {
|
|
939
940
|
/**
|
|
940
941
|
* #getter
|
|
941
942
|
*/
|
|
942
|
-
readonly transcriptToMsaMap:
|
|
943
|
-
g2p: Record<number, number>;
|
|
944
|
-
p2g: Record<number, number>;
|
|
945
|
-
p2gCodon: Record<number, number[]>;
|
|
946
|
-
refName: string;
|
|
947
|
-
strand: number;
|
|
948
|
-
} | undefined;
|
|
943
|
+
readonly transcriptToMsaMap: import("./transcriptMap").TranscriptMap | undefined;
|
|
949
944
|
/**
|
|
950
945
|
* #getter
|
|
951
946
|
*/
|
|
@@ -983,6 +978,13 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
|
|
|
983
978
|
* alignment the link does carry.
|
|
984
979
|
*/
|
|
985
980
|
readonly hostRestoresData: boolean;
|
|
981
|
+
/**
|
|
982
|
+
* #getter
|
|
983
|
+
* the documents a reload would lose: too big for react-msaview to keep
|
|
984
|
+
* in the snapshot, and with no filehandle or kept init to refetch them
|
|
985
|
+
* from. IndexedDB holds exactly these.
|
|
986
|
+
*/
|
|
987
|
+
readonly unsavedDocuments: MsaDataPayload;
|
|
986
988
|
} & {
|
|
987
989
|
/**
|
|
988
990
|
* #action
|
|
@@ -1016,10 +1018,6 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
|
|
|
1016
1018
|
* #action
|
|
1017
1019
|
*/
|
|
1018
1020
|
setQuerySeqName(arg: string): void;
|
|
1019
|
-
/**
|
|
1020
|
-
* #action
|
|
1021
|
-
*/
|
|
1022
|
-
setQuerySeqOffset(arg: number): void;
|
|
1023
1021
|
/**
|
|
1024
1022
|
* #action
|
|
1025
1023
|
*/
|
|
@@ -1032,10 +1030,6 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
|
|
|
1032
1030
|
* #action
|
|
1033
1031
|
*/
|
|
1034
1032
|
setLaunchCompleted(arg: boolean): void;
|
|
1035
|
-
/**
|
|
1036
|
-
* #action
|
|
1037
|
-
*/
|
|
1038
|
-
setMafRegion(arg?: MafRegion): void;
|
|
1039
1033
|
/**
|
|
1040
1034
|
* #action
|
|
1041
1035
|
*/
|
|
@@ -1048,6 +1042,10 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
|
|
|
1048
1042
|
* #action
|
|
1049
1043
|
*/
|
|
1050
1044
|
setLastStoredData(arg?: MsaDataPayload): void;
|
|
1045
|
+
/**
|
|
1046
|
+
* #action
|
|
1047
|
+
*/
|
|
1048
|
+
setOwnsDataStoreRow(arg: boolean): void;
|
|
1051
1049
|
/**
|
|
1052
1050
|
* #action
|
|
1053
1051
|
*/
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { BaseViewModel } from '@jbrowse/core/pluggableElementTypes';
|
|
2
2
|
import { getSession } from '@jbrowse/core/util';
|
|
3
|
-
import { addDisposer, types } from '@jbrowse/mobx-state-tree';
|
|
4
|
-
import { genomeToTranscriptSeqMapping } from 'g2p_mapper';
|
|
3
|
+
import { addDisposer, getSnapshot, types } from '@jbrowse/mobx-state-tree';
|
|
5
4
|
import { autorun } from 'mobx';
|
|
6
5
|
import { MSAModelF } from 'react-msaview';
|
|
7
6
|
import { autoLoadProteinDomains, launchBlastIfNeeded, launchOrthologsIfNeeded, loadStoredData, observeProteinHighlights, processInit, runCleanup, storeDataToIndexedDB, syncGenomeHoverToMsaColumn, } from './afterCreateAutoruns';
|
|
8
7
|
import { msaCoordToGenomeCoord, msaCoordToGenomeRegions, } from './msaCoordToGenomeCoord';
|
|
9
|
-
import { deleteMsaData } from './msaDataStore';
|
|
10
8
|
import { resolveConnectedTranscriptIfNeeded } from './resolveConnectedTranscript';
|
|
9
|
+
import { transcriptMap } from './transcriptMap';
|
|
11
10
|
/**
|
|
12
11
|
* Where the homolog set comes from. NCBI's ortholog sets cover vertebrates and
|
|
13
12
|
* insects; PANTHER's span its 144 reference proteomes, human to yeast to
|
|
@@ -132,6 +131,13 @@ export default function stateModelFactory() {
|
|
|
132
131
|
* neighbor-joining tree, say) is recognized as needing a new one
|
|
133
132
|
*/
|
|
134
133
|
lastStoredData: undefined,
|
|
134
|
+
/**
|
|
135
|
+
* #volatile
|
|
136
|
+
* whether this view wrote the row `dataStoreId` names, this session;
|
|
137
|
+
* a restored row may be shared with a copied view or a duplicated
|
|
138
|
+
* session, so a write to one the view did not create takes a fresh id
|
|
139
|
+
*/
|
|
140
|
+
ownsDataStoreRow: false,
|
|
135
141
|
/**
|
|
136
142
|
* #volatile
|
|
137
143
|
* aborts the launch currently in flight -- from the Cancel button, and
|
|
@@ -153,7 +159,7 @@ export default function stateModelFactory() {
|
|
|
153
159
|
*/
|
|
154
160
|
get transcriptToMsaMap() {
|
|
155
161
|
return self.connectedFeature
|
|
156
|
-
?
|
|
162
|
+
? transcriptMap(self.connectedFeature)
|
|
157
163
|
: undefined;
|
|
158
164
|
},
|
|
159
165
|
/**
|
|
@@ -219,6 +225,26 @@ export default function stateModelFactory() {
|
|
|
219
225
|
// away for every view this one does not speak for
|
|
220
226
|
return self.hostCarriesData || !!(msaIndexedLocation && msaName);
|
|
221
227
|
},
|
|
228
|
+
/**
|
|
229
|
+
* #getter
|
|
230
|
+
* the documents a reload would lose: too big for react-msaview to keep
|
|
231
|
+
* in the snapshot, and with no filehandle or kept init to refetch them
|
|
232
|
+
* from. IndexedDB holds exactly these.
|
|
233
|
+
*/
|
|
234
|
+
get unsavedDocuments() {
|
|
235
|
+
const { data, init } = self;
|
|
236
|
+
const inSnapshot = getSnapshot(data);
|
|
237
|
+
const unsaved = (key, source) => {
|
|
238
|
+
const text = data[key];
|
|
239
|
+
return text && !source && !inSnapshot[key] ? text : undefined;
|
|
240
|
+
};
|
|
241
|
+
return {
|
|
242
|
+
msa: unsaved('msa', self.msaFilehandle ?? init?.msaIndexedLocation),
|
|
243
|
+
tree: unsaved('tree', self.treeFilehandle),
|
|
244
|
+
treeMetadata: unsaved('treeMetadata', self.treeMetadataFilehandle),
|
|
245
|
+
gff: unsaved('gff', self.gffFilehandle),
|
|
246
|
+
};
|
|
247
|
+
},
|
|
222
248
|
}))
|
|
223
249
|
.actions(self => ({
|
|
224
250
|
/**
|
|
@@ -269,12 +295,6 @@ export default function stateModelFactory() {
|
|
|
269
295
|
setQuerySeqName(arg) {
|
|
270
296
|
self.querySeqName = arg;
|
|
271
297
|
},
|
|
272
|
-
/**
|
|
273
|
-
* #action
|
|
274
|
-
*/
|
|
275
|
-
setQuerySeqOffset(arg) {
|
|
276
|
-
self.querySeqOffset = arg;
|
|
277
|
-
},
|
|
278
298
|
/**
|
|
279
299
|
* #action
|
|
280
300
|
*/
|
|
@@ -293,12 +313,6 @@ export default function stateModelFactory() {
|
|
|
293
313
|
setLaunchCompleted(arg) {
|
|
294
314
|
self.launchCompleted = arg;
|
|
295
315
|
},
|
|
296
|
-
/**
|
|
297
|
-
* #action
|
|
298
|
-
*/
|
|
299
|
-
setMafRegion(arg) {
|
|
300
|
-
self.mafRegion = arg;
|
|
301
|
-
},
|
|
302
316
|
/**
|
|
303
317
|
* #action
|
|
304
318
|
*/
|
|
@@ -317,6 +331,12 @@ export default function stateModelFactory() {
|
|
|
317
331
|
setLastStoredData(arg) {
|
|
318
332
|
self.lastStoredData = arg;
|
|
319
333
|
},
|
|
334
|
+
/**
|
|
335
|
+
* #action
|
|
336
|
+
*/
|
|
337
|
+
setOwnsDataStoreRow(arg) {
|
|
338
|
+
self.ownsDataStoreRow = arg;
|
|
339
|
+
},
|
|
320
340
|
/**
|
|
321
341
|
* #action
|
|
322
342
|
*/
|
|
@@ -426,7 +446,7 @@ export default function stateModelFactory() {
|
|
|
426
446
|
* once per view, never fired again.
|
|
427
447
|
*/
|
|
428
448
|
reset() {
|
|
429
|
-
const { displayName, minimized, zoomToBaseLevel
|
|
449
|
+
const { displayName, minimized, zoomToBaseLevel } = self;
|
|
430
450
|
superReset();
|
|
431
451
|
if (displayName !== undefined) {
|
|
432
452
|
self.setDisplayName(displayName);
|
|
@@ -435,10 +455,7 @@ export default function stateModelFactory() {
|
|
|
435
455
|
self.setZoomToBaseLevel(zoomToBaseLevel);
|
|
436
456
|
self.setDomainsRequested(false);
|
|
437
457
|
self.setLastStoredData(undefined);
|
|
438
|
-
|
|
439
|
-
// nothing points at that row now
|
|
440
|
-
void deleteMsaData(dataStoreId);
|
|
441
|
-
}
|
|
458
|
+
self.setOwnsDataStoreRow(false);
|
|
442
459
|
},
|
|
443
460
|
};
|
|
444
461
|
})
|
|
@@ -476,8 +493,8 @@ export default function stateModelFactory() {
|
|
|
476
493
|
addDisposer(self, () => {
|
|
477
494
|
self.launchController?.abort();
|
|
478
495
|
});
|
|
496
|
+
loadStoredData(self);
|
|
479
497
|
for (const fn of [
|
|
480
|
-
loadStoredData,
|
|
481
498
|
storeDataToIndexedDB,
|
|
482
499
|
resolveConnectedTranscriptIfNeeded,
|
|
483
500
|
launchBlastIfNeeded,
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
|
2
2
|
import { doLaunchBlast } from './doLaunchBlast';
|
|
3
3
|
import stateModelFactory from './model';
|
|
4
|
-
import { deleteMsaData } from './msaDataStore';
|
|
5
4
|
// the launch autoruns are live on a real model, and a blastParams write is what
|
|
6
5
|
// wakes them -- so the search is mocked rather than sent to EBI. The session is
|
|
7
6
|
// mocked for the same reason: the hover and highlight autoruns reach for one on
|
|
@@ -13,7 +12,6 @@ vi.mock('@jbrowse/core/util', async (importOriginal) => ({
|
|
|
13
12
|
vi.mock('./doLaunchBlast', () => ({ doLaunchBlast: vi.fn() }));
|
|
14
13
|
vi.mock('./msaDataStore', () => ({
|
|
15
14
|
cleanupOldData: vi.fn(async () => { }),
|
|
16
|
-
deleteMsaData: vi.fn(async () => { }),
|
|
17
15
|
generateDataStoreId: vi.fn(),
|
|
18
16
|
retrieveMsaData: vi.fn(),
|
|
19
17
|
storeMsaData: vi.fn(),
|
|
@@ -152,15 +150,16 @@ describe('returning to the import form', () => {
|
|
|
152
150
|
expect(model.zoomToBaseLevel).toBe(true);
|
|
153
151
|
expect(model.dataInitialized).toBe(false);
|
|
154
152
|
});
|
|
155
|
-
test('clears the volatiles applySnapshot cannot reach, and the stored
|
|
153
|
+
test('clears the volatiles applySnapshot cannot reach, and the stored id', () => {
|
|
156
154
|
const model = view();
|
|
157
155
|
model.setDomainsRequested(true);
|
|
158
156
|
model.setLastStoredData({ msa: '>a\nMK' });
|
|
159
157
|
model.setDataStoreId('msa-1');
|
|
158
|
+
model.setOwnsDataStoreRow(true);
|
|
160
159
|
model.reset();
|
|
161
160
|
expect(model.domainsRequested).toBe(false);
|
|
162
161
|
expect(model.lastStoredData).toBeUndefined();
|
|
162
|
+
expect(model.ownsDataStoreRow).toBe(false);
|
|
163
163
|
expect(model.dataStoreId).toBeUndefined();
|
|
164
|
-
expect(deleteMsaData).toHaveBeenCalledWith('msa-1');
|
|
165
164
|
});
|
|
166
165
|
});
|
|
@@ -7,12 +7,11 @@ export interface MsaDataPayload {
|
|
|
7
7
|
}
|
|
8
8
|
export declare function generateDataStoreId(): string;
|
|
9
9
|
export declare function storeMsaData(id: string, data: MsaDataPayload): Promise<boolean>;
|
|
10
|
+
/** undefined only when no row has the id; a failed read throws */
|
|
10
11
|
export declare function retrieveMsaData(id: string): Promise<{
|
|
11
12
|
msa?: string;
|
|
12
13
|
tree?: string;
|
|
13
14
|
treeMetadata?: string;
|
|
14
15
|
gff?: string;
|
|
15
16
|
} | undefined>;
|
|
16
|
-
/** drop one view's row, for a reset that just orphaned it */
|
|
17
|
-
export declare function deleteMsaData(id: string): Promise<void>;
|
|
18
17
|
export declare function cleanupOldData(maxAgeMs?: number): Promise<number>;
|
|
@@ -23,39 +23,24 @@ export async function storeMsaData(id, data) {
|
|
|
23
23
|
return false;
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
|
+
/** undefined only when no row has the id; a failed read throws */
|
|
26
27
|
export async function retrieveMsaData(id) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
if (!result) {
|
|
31
|
-
return undefined;
|
|
32
|
-
}
|
|
33
|
-
// reading counts as use, which is what makes cleanupOldData's policy
|
|
34
|
-
// "unused for 7 days" rather than "written 7 days ago" -- a session opened
|
|
35
|
-
// every day used to lose its alignment on the eighth
|
|
36
|
-
try {
|
|
37
|
-
await db.put(STORE_NAME, { ...result, timestamp: Date.now() });
|
|
38
|
-
}
|
|
39
|
-
catch (e) {
|
|
40
|
-
console.warn('Failed to refresh MSA data timestamp:', e);
|
|
41
|
-
}
|
|
42
|
-
const { id: _id, timestamp: _timestamp, ...payload } = result;
|
|
43
|
-
return payload;
|
|
44
|
-
}
|
|
45
|
-
catch (e) {
|
|
46
|
-
console.warn('Failed to retrieve MSA data:', e);
|
|
28
|
+
const db = await getDB();
|
|
29
|
+
const result = await db.get(STORE_NAME, id);
|
|
30
|
+
if (!result) {
|
|
47
31
|
return undefined;
|
|
48
32
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
33
|
+
// reading counts as use, which is what makes cleanupOldData's policy
|
|
34
|
+
// "unused for 7 days" rather than "written 7 days ago" -- a session opened
|
|
35
|
+
// every day used to lose its alignment on the eighth
|
|
52
36
|
try {
|
|
53
|
-
|
|
54
|
-
await db.delete(STORE_NAME, id);
|
|
37
|
+
await db.put(STORE_NAME, { ...result, timestamp: Date.now() });
|
|
55
38
|
}
|
|
56
39
|
catch (e) {
|
|
57
|
-
console.warn('Failed to
|
|
40
|
+
console.warn('Failed to refresh MSA data timestamp:', e);
|
|
58
41
|
}
|
|
42
|
+
const { id: _id, timestamp: _timestamp, ...payload } = result;
|
|
43
|
+
return payload;
|
|
59
44
|
}
|
|
60
45
|
export async function cleanupOldData(maxAgeMs = 7 * 24 * 60 * 60 * 1000) {
|
|
61
46
|
try {
|
|
@@ -4,11 +4,13 @@ import { retrieveMsaData, storeMsaData } from './msaDataStore';
|
|
|
4
4
|
// switch for the write half so a browser that refuses writes can be played back.
|
|
5
5
|
const { rows, state } = vi.hoisted(() => ({
|
|
6
6
|
rows: new Map(),
|
|
7
|
-
state: { putFails: false },
|
|
7
|
+
state: { putFails: false, getFails: false },
|
|
8
8
|
}));
|
|
9
9
|
vi.mock('../utils/idb', () => ({
|
|
10
10
|
createDbOpener: () => () => Promise.resolve({
|
|
11
|
-
get: (_store, id) =>
|
|
11
|
+
get: (_store, id) => state.getFails
|
|
12
|
+
? Promise.reject(new Error('InvalidStateError'))
|
|
13
|
+
: Promise.resolve(rows.get(id)),
|
|
12
14
|
put: (_store, value) => {
|
|
13
15
|
if (state.putFails) {
|
|
14
16
|
return Promise.reject(new Error('QuotaExceededError'));
|
|
@@ -21,6 +23,7 @@ vi.mock('../utils/idb', () => ({
|
|
|
21
23
|
beforeEach(() => {
|
|
22
24
|
rows.clear();
|
|
23
25
|
state.putFails = false;
|
|
26
|
+
state.getFails = false;
|
|
24
27
|
});
|
|
25
28
|
// cleanupOldData deletes by timestamp, so without this a session opened every
|
|
26
29
|
// single day still lost its alignment on the eighth
|
|
@@ -34,6 +37,11 @@ test('a row that is no longer there reads as undefined and writes nothing', asyn
|
|
|
34
37
|
expect(await retrieveMsaData('msa-gone')).toBeUndefined();
|
|
35
38
|
expect(rows.size).toBe(0);
|
|
36
39
|
});
|
|
40
|
+
test('a read that fails throws rather than reading as a missing row', async () => {
|
|
41
|
+
rows.set('msa-1', { id: 'msa-1', msa: '>a\nMK', timestamp: 1000 });
|
|
42
|
+
state.getFails = true;
|
|
43
|
+
await expect(retrieveMsaData('msa-1')).rejects.toThrow('InvalidStateError');
|
|
44
|
+
});
|
|
37
45
|
// the refresh is housekeeping; failing it must not turn a readable alignment
|
|
38
46
|
// into a missing one, which is what the caller reports as expired
|
|
39
47
|
test('a refresh that fails still hands back the data it read', async () => {
|
|
@@ -24,6 +24,7 @@ function makeModel({ highlightColumns, querySeqOffset = 0, } = {}) {
|
|
|
24
24
|
// g2p is indexed by genome coord; identity keeps the arithmetic out of the way
|
|
25
25
|
transcriptToMsaMap: {
|
|
26
26
|
g2p: Object.fromEntries([...Array(200).keys()].map(i => [i, i])),
|
|
27
|
+
codingPositions: [...Array(200).keys()],
|
|
27
28
|
},
|
|
28
29
|
highlightColumns,
|
|
29
30
|
highlightedColumns: undefined,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { beforeEach, expect, test, vi } from 'vitest';
|
|
2
|
-
import { processInit
|
|
2
|
+
import { processInit } from './afterCreateAutoruns';
|
|
3
3
|
import { fetchIndexedMsa } from './fetchIndexedMsa';
|
|
4
4
|
vi.mock('./fetchIndexedMsa', () => ({ fetchIndexedMsa: vi.fn() }));
|
|
5
5
|
vi.mock('./msaDataStore', () => ({
|
|
@@ -64,9 +64,3 @@ test('a url init is still resolved once and cleared', async () => {
|
|
|
64
64
|
expect(model.setMSAFilehandle).toHaveBeenCalled();
|
|
65
65
|
expect(model.setInit).toHaveBeenCalledWith(undefined);
|
|
66
66
|
});
|
|
67
|
-
test('an indexed view writes no IndexedDB row', async () => {
|
|
68
|
-
const model = makeModel({ rows: [['a', 'MK']], data: { msa: '>a\nMK' } });
|
|
69
|
-
storeDataToIndexedDB(model);
|
|
70
|
-
await settle();
|
|
71
|
-
expect(model.setDataStoreId).not.toHaveBeenCalled();
|
|
72
|
-
});
|
|
@@ -6,7 +6,6 @@ vi.mock('@jbrowse/core/util', async (importOriginal) => ({
|
|
|
6
6
|
}));
|
|
7
7
|
vi.mock('./msaDataStore', () => ({
|
|
8
8
|
cleanupOldData: vi.fn(async () => { }),
|
|
9
|
-
deleteMsaData: vi.fn(async () => { }),
|
|
10
9
|
generateDataStoreId: vi.fn(),
|
|
11
10
|
retrieveMsaData: vi.fn(),
|
|
12
11
|
storeMsaData: vi.fn(async () => true),
|