react-msaview 6.4.2 → 7.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/README.md +6 -5
- package/bundle/index.js +100 -101
- package/bundle/index.js.map +4 -4
- package/dist/components/Loading.js +2 -2
- package/dist/components/Loading.js.map +1 -1
- package/dist/components/MSAViewer.d.ts +10 -2
- package/dist/components/MSAViewer.js +90 -2
- package/dist/components/MSAViewer.js.map +1 -1
- package/dist/components/ResizeHandles.d.ts +3 -3
- package/dist/components/ResizeHandles.js +28 -21
- package/dist/components/ResizeHandles.js.map +1 -1
- package/dist/components/dialogs/{InterProScanDialog.d.ts → AnnotationFileDialog.d.ts} +2 -2
- package/dist/components/dialogs/AnnotationFileDialog.js +92 -0
- package/dist/components/dialogs/AnnotationFileDialog.js.map +1 -0
- package/dist/components/dialogs/FeatureDialog.js +13 -9
- package/dist/components/dialogs/FeatureDialog.js.map +1 -1
- package/dist/components/header/Header.js +4 -0
- package/dist/components/header/Header.js.map +1 -1
- package/dist/components/header/HeaderStatusArea.js +0 -2
- package/dist/components/header/HeaderStatusArea.js.map +1 -1
- package/dist/components/header/LoadWarnings.d.ts +6 -0
- package/dist/components/header/LoadWarnings.js +36 -0
- package/dist/components/header/LoadWarnings.js.map +1 -0
- package/dist/components/header/MultiAlignmentSelector.js +0 -0
- package/dist/components/header/MultiAlignmentSelector.js.map +1 -1
- package/dist/components/header/TreeSettingsMenu.js +9 -1
- package/dist/components/header/TreeSettingsMenu.js.map +1 -1
- package/dist/components/header/UnshareableDataWarning.d.ts +6 -0
- package/dist/components/header/UnshareableDataWarning.js +42 -0
- package/dist/components/header/UnshareableDataWarning.js.map +1 -0
- package/dist/components/header/getAnnotationsMenu.js +14 -14
- package/dist/components/header/getAnnotationsMenu.js.map +1 -1
- package/dist/components/import/ImportForm.js +1 -1
- package/dist/components/import/ImportForm.js.map +1 -1
- package/dist/components/import/ImportFormExamples.js +2 -6
- package/dist/components/import/ImportFormExamples.js.map +1 -1
- package/dist/components/import/util.js +3 -0
- package/dist/components/import/util.js.map +1 -1
- package/dist/components/msa/MSACanvasBlock.js +4 -5
- package/dist/components/msa/MSACanvasBlock.js.map +1 -1
- package/dist/components/msa/msaRaster.d.ts +13 -3
- package/dist/components/msa/msaRaster.js +173 -61
- package/dist/components/msa/msaRaster.js.map +1 -1
- package/dist/components/msa/renderHighlights.d.ts +39 -1
- package/dist/components/msa/renderHighlights.js +71 -6
- package/dist/components/msa/renderHighlights.js.map +1 -1
- package/dist/components/msa/renderMSABlock.d.ts +1 -2
- package/dist/components/msa/renderMSABlock.js +24 -28
- package/dist/components/msa/renderMSABlock.js.map +1 -1
- package/dist/components/msa/renderMSAMouseover.js +9 -30
- package/dist/components/msa/renderMSAMouseover.js.map +1 -1
- package/dist/components/renderCtx.d.ts +1 -1
- package/dist/components/tracks/TrackBlocks.js +10 -11
- package/dist/components/tracks/TrackBlocks.js.map +1 -1
- package/dist/components/tracks/drawTracks.d.ts +26 -8
- package/dist/components/tracks/drawTracks.js +123 -13
- package/dist/components/tracks/drawTracks.js.map +1 -1
- package/dist/components/tree/TreeCanvas.js +2 -3
- package/dist/components/tree/TreeCanvas.js.map +1 -1
- package/dist/components/tree/TreeCanvasBlock.js +4 -2
- package/dist/components/tree/TreeCanvasBlock.js.map +1 -1
- package/dist/components/tree/TreeRuler.js +13 -3
- package/dist/components/tree/TreeRuler.js.map +1 -1
- package/dist/components/tree/renderTreeCanvas.d.ts +0 -1
- package/dist/components/tree/renderTreeCanvas.js +70 -30
- package/dist/components/tree/renderTreeCanvas.js.map +1 -1
- package/dist/components/tree/scaleBar.d.ts +10 -0
- package/dist/components/tree/scaleBar.js +24 -0
- package/dist/components/tree/scaleBar.js.map +1 -0
- package/dist/constants.d.ts +3 -1
- package/dist/constants.js +18 -1
- package/dist/constants.js.map +1 -1
- package/dist/fetchUtils.d.ts +7 -7
- package/dist/fetchUtils.js +4 -32
- package/dist/fetchUtils.js.map +1 -1
- package/dist/hierarchy.d.ts +2 -1
- package/dist/hierarchy.js +16 -10
- package/dist/hierarchy.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +8 -2
- package/dist/index.js.map +1 -1
- package/dist/model/DataModel.js +5 -2
- package/dist/model/DataModel.js.map +1 -1
- package/dist/model.d.ts +454 -177
- package/dist/model.js +1180 -511
- package/dist/model.js.map +1 -1
- package/dist/renderToStaticMarkup.d.ts +11 -0
- package/dist/renderToStaticMarkup.js +30 -7
- package/dist/renderToStaticMarkup.js.map +1 -1
- package/dist/renderToSvg.js +25 -19
- package/dist/renderToSvg.js.map +1 -1
- package/dist/rowCoordinateCalculations.d.ts +25 -25
- package/dist/rowCoordinateCalculations.js +12 -36
- package/dist/rowCoordinateCalculations.js.map +1 -1
- package/dist/seqPosToGlobalCol.js +3 -1
- package/dist/seqPosToGlobalCol.js.map +1 -1
- package/dist/svgTestUtil.d.ts +144 -82
- package/dist/types.d.ts +97 -3
- package/dist/util.d.ts +11 -4
- package/dist/util.js +30 -10
- package/dist/util.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/wuss.d.ts +14 -0
- package/dist/wuss.js +55 -0
- package/dist/wuss.js.map +1 -0
- package/package.json +10 -8
- package/src/components/Loading.tsx +12 -2
- package/src/components/MSAViewer.tsx +106 -2
- package/src/components/ResizeHandles.tsx +31 -31
- package/src/components/dialogs/AnnotationFileDialog.tsx +162 -0
- package/src/components/dialogs/FeatureDialog.tsx +13 -9
- package/src/components/header/Header.tsx +4 -0
- package/src/components/header/HeaderStatusArea.tsx +0 -5
- package/src/components/header/LoadWarnings.tsx +49 -0
- package/src/components/header/MultiAlignmentSelector.tsx +0 -0
- package/src/components/header/TreeSettingsMenu.tsx +9 -1
- package/src/components/header/UnshareableDataWarning.tsx +58 -0
- package/src/components/header/getAnnotationsMenu.ts +16 -17
- package/src/components/import/ImportForm.tsx +2 -2
- package/src/components/import/ImportFormExamples.tsx +2 -6
- package/src/components/import/util.ts +3 -0
- package/src/components/msa/MSACanvasBlock.tsx +4 -5
- package/src/components/msa/msaRaster.ts +216 -59
- package/src/components/msa/renderHighlights.ts +103 -5
- package/src/components/msa/renderMSABlock.ts +25 -36
- package/src/components/msa/renderMSAMouseover.ts +9 -39
- package/src/components/renderCtx.ts +1 -0
- package/src/components/tracks/TrackBlocks.tsx +10 -13
- package/src/components/tracks/drawTracks.ts +165 -17
- package/src/components/tree/TreeCanvas.tsx +2 -3
- package/src/components/tree/TreeCanvasBlock.tsx +4 -2
- package/src/components/tree/TreeRuler.tsx +38 -3
- package/src/components/tree/renderTreeCanvas.ts +79 -35
- package/src/components/tree/scaleBar.ts +23 -0
- package/src/constants.ts +20 -1
- package/src/fetchUtils.ts +7 -40
- package/src/hierarchy.ts +20 -11
- package/src/index.ts +18 -2
- package/src/model/DataModel.ts +6 -2
- package/src/model.ts +1352 -616
- package/src/rawImports.d.ts +9 -0
- package/src/renderToStaticMarkup.ts +34 -7
- package/src/renderToSvg.tsx +33 -27
- package/src/rowCoordinateCalculations.ts +15 -59
- package/src/seqPosToGlobalCol.ts +3 -1
- package/src/types.ts +131 -4
- package/src/util.ts +31 -14
- package/src/version.ts +1 -1
- package/src/wuss.ts +62 -0
- package/dist/components/dialogs/InterProScanDialog.js +0 -137
- package/dist/components/dialogs/InterProScanDialog.js.map +0 -1
- package/dist/components/dialogs/InterProScanFileDialog.d.ts +0 -7
- package/dist/components/dialogs/InterProScanFileDialog.js +0 -64
- package/dist/components/dialogs/InterProScanFileDialog.js.map +0 -1
- package/dist/launchInterProScan.d.ts +0 -13
- package/dist/launchInterProScan.js +0 -110
- package/dist/launchInterProScan.js.map +0 -1
- package/dist/useColorContrast.d.ts +0 -10
- package/dist/useColorContrast.js +0 -13
- package/dist/useColorContrast.js.map +0 -1
- package/src/clustalX.test.ts +0 -74
- package/src/colSpace.test.ts +0 -67
- package/src/collapseLogic.test.ts +0 -115
- package/src/colorSchemes.docs.test.ts +0 -57
- package/src/columnCounts.test.ts +0 -73
- package/src/columnStats.test.ts +0 -94
- package/src/columnTracks.test.ts +0 -97
- package/src/components/dialogs/InterProScanDialog.tsx +0 -214
- package/src/components/dialogs/InterProScanFileDialog.tsx +0 -138
- package/src/components/minimap/minimapLayout.test.ts +0 -71
- package/src/components/msa/msaRaster.test.ts +0 -145
- package/src/components/msa/renderBoxFeatureCanvasBlock.test.ts +0 -51
- package/src/components/tree/renderTreeCanvas.test.ts +0 -296
- package/src/dataInitialized.test.ts +0 -42
- package/src/domainBands.test.ts +0 -122
- package/src/domainLetterColor.test.ts +0 -80
- package/src/exportFileName.test.ts +0 -40
- package/src/fetchUtils.test.ts +0 -83
- package/src/flatToTree.test.ts +0 -51
- package/src/hierarchy.test.ts +0 -228
- package/src/highlights.test.ts +0 -70
- package/src/impgRender.test.tsx +0 -68
- package/src/largeTree.test.ts +0 -67
- package/src/launchInterProScan.ts +0 -167
- package/src/loadErrorDisplay.test.tsx +0 -102
- package/src/modelFilehandleLoaders.test.ts +0 -137
- package/src/modelReset.test.ts +0 -120
- package/src/neighborJoining.test.ts +0 -164
- package/src/parseAsn1.test.ts +0 -36
- package/src/propertyConservation.test.ts +0 -36
- package/src/renderColumnTracksSvg.test.tsx +0 -108
- package/src/renderDomainsSvg.test.tsx +0 -118
- package/src/renderHighlightsSvg.test.tsx +0 -85
- package/src/renderMinimapSvg.test.tsx +0 -62
- package/src/renderRasterSvg.test.tsx +0 -144
- package/src/renderSequenceLogoSvg.test.tsx +0 -165
- package/src/renderToSvg.test.tsx +0 -65
- package/src/reset.test.ts +0 -61
- package/src/rowCoordinateCalculations.test.ts +0 -211
- package/src/rowSpace.test.ts +0 -46
- package/src/segmentOverlay.test.ts +0 -70
- package/src/seqPosToGlobalCol.test.ts +0 -59
- package/src/sequenceLogo.test.ts +0 -88
- package/src/setFontSize.test.ts +0 -33
- package/src/stripDefault.test.ts +0 -48
- package/src/stripDefaultSnapshot.test.ts +0 -87
- package/src/treeMetadata.test.ts +0 -39
- package/src/useCanvasAutorun.test.tsx +0 -98
- package/src/useColorContrast.ts +0 -18
- package/src/viewChrome.test.tsx +0 -134
- package/src/wheelZoom.test.ts +0 -46
package/src/domainBands.test.ts
DELETED
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
// @vitest-environment jsdom
|
|
2
|
-
// The domain overlay resolves each annotation to the visible column span it is
|
|
3
|
-
// drawn across once, on the model, instead of converting sequence positions
|
|
4
|
-
// inside every canvas block. These tests pin that span (including across hidden
|
|
5
|
-
// gappy columns) and the hit-test built on top of it.
|
|
6
|
-
import { expect, test } from 'vitest'
|
|
7
|
-
|
|
8
|
-
import MSAModelF from './model.ts'
|
|
9
|
-
|
|
10
|
-
// row `b` has a 4-residue insertion relative to `a`, which becomes a run of
|
|
11
|
-
// mostly-gap columns that gap hiding can remove
|
|
12
|
-
const msa = `>a
|
|
13
|
-
MKLV----WYAC
|
|
14
|
-
>b
|
|
15
|
-
MKLVQQQQWYAC`
|
|
16
|
-
|
|
17
|
-
const gff = `##gff-version 3
|
|
18
|
-
a Pfam protein_match 1 4 . . . Name=PF00001;signature_desc=First
|
|
19
|
-
a Pfam protein_match 5 8 . . . Name=PF00002;signature_desc=Second`
|
|
20
|
-
|
|
21
|
-
// row `b` only: PF00003 ends inside the insertion, PF00004 lies entirely within
|
|
22
|
-
// it, so gap hiding puts one end and then both ends into hidden columns
|
|
23
|
-
const insertionGff = `##gff-version 3
|
|
24
|
-
b Pfam protein_match 4 8 . . . Name=PF00003;signature_desc=Overlapping
|
|
25
|
-
b Pfam protein_match 5 8 . . . Name=PF00004;signature_desc=Inside`
|
|
26
|
-
|
|
27
|
-
function makeModel() {
|
|
28
|
-
const model = MSAModelF().create({
|
|
29
|
-
id: 'domain-bands-test',
|
|
30
|
-
type: 'MsaView',
|
|
31
|
-
msaFormat: 'fasta',
|
|
32
|
-
height: 400,
|
|
33
|
-
data: { msa },
|
|
34
|
-
})
|
|
35
|
-
model.setWidth(800)
|
|
36
|
-
model.applyGFFText(gff)
|
|
37
|
-
return model
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
test('bands span the columns their residues occupy', () => {
|
|
41
|
-
const model = makeModel()
|
|
42
|
-
const bands = model.domainBands.get('a')!
|
|
43
|
-
expect(
|
|
44
|
-
bands.map(b => [b.annotation.accession, b.startCol, b.endCol]),
|
|
45
|
-
).toEqual([
|
|
46
|
-
['PF00001', 0, 4],
|
|
47
|
-
['PF00002', 8, 12],
|
|
48
|
-
])
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
test('bands follow the columns when gappy columns are hidden', () => {
|
|
52
|
-
const model = makeModel()
|
|
53
|
-
model.setHideGaps(true)
|
|
54
|
-
model.setAllowedGappyness(50)
|
|
55
|
-
// the 4 insertion columns are gaps in half the rows, so they are hidden and
|
|
56
|
-
// everything to their right shifts left by 4
|
|
57
|
-
expect(model.blanks).toEqual([4, 5, 6, 7])
|
|
58
|
-
const bands = model.domainBands.get('a')!
|
|
59
|
-
expect(bands.map(b => [b.startCol, b.endCol])).toEqual([
|
|
60
|
-
[0, 4],
|
|
61
|
-
[4, 8],
|
|
62
|
-
])
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
test('a band whose end lands in a hidden column keeps its visible part', () => {
|
|
66
|
-
const model = makeModel()
|
|
67
|
-
model.applyGFFText(insertionGff)
|
|
68
|
-
expect(model.domainBands.get('b')!.map(b => [b.startCol, b.endCol])).toEqual([
|
|
69
|
-
[3, 8],
|
|
70
|
-
[4, 8],
|
|
71
|
-
])
|
|
72
|
-
|
|
73
|
-
model.setHideGaps(true)
|
|
74
|
-
model.setAllowedGappyness(50)
|
|
75
|
-
expect(model.blanks).toEqual([4, 5, 6, 7])
|
|
76
|
-
// PF00003 covers V + the 4 hidden Qs, so only the V column survives; PF00004
|
|
77
|
-
// is nothing but hidden columns and drops out entirely
|
|
78
|
-
expect(model.domainBands.get('b')!.map(b => [b.startCol, b.endCol])).toEqual([
|
|
79
|
-
[3, 4],
|
|
80
|
-
])
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
test('mouseOverDomains hit-tests against the drawn span', () => {
|
|
84
|
-
const model = makeModel()
|
|
85
|
-
const rowIndex = model.rowNames.indexOf('a')
|
|
86
|
-
|
|
87
|
-
model.setMousePos(0, rowIndex)
|
|
88
|
-
expect(model.mouseOverDomains.map(d => d.accession)).toEqual(['PF00001'])
|
|
89
|
-
|
|
90
|
-
// the columns between the two domains are the insertion gap: no domain there
|
|
91
|
-
model.setMousePos(5, rowIndex)
|
|
92
|
-
expect(model.mouseOverDomains).toEqual([])
|
|
93
|
-
|
|
94
|
-
model.setMousePos(8, rowIndex)
|
|
95
|
-
expect(model.mouseOverDomains.map(d => d.accession)).toEqual(['PF00002'])
|
|
96
|
-
|
|
97
|
-
// end column is exclusive
|
|
98
|
-
model.setMousePos(12, rowIndex)
|
|
99
|
-
expect(model.mouseOverDomains).toEqual([])
|
|
100
|
-
|
|
101
|
-
// the other row has no annotations of its own
|
|
102
|
-
model.setMousePos(0, model.rowNames.indexOf('b'))
|
|
103
|
-
expect(model.mouseOverDomains).toEqual([])
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
test('filtering a domain off removes its band', () => {
|
|
107
|
-
const model = makeModel()
|
|
108
|
-
model.setFilter('PF00001', false)
|
|
109
|
-
expect(model.domainBands.get('a')!.map(b => b.annotation.accession)).toEqual([
|
|
110
|
-
'PF00002',
|
|
111
|
-
])
|
|
112
|
-
})
|
|
113
|
-
|
|
114
|
-
test('highlighted columns collapse into contiguous runs', () => {
|
|
115
|
-
const model = makeModel()
|
|
116
|
-
model.setHighlightedColumns([5, 1, 2, 3, 9])
|
|
117
|
-
expect(model.highlightedColumnRuns).toEqual([
|
|
118
|
-
{ start: 1, end: 3 },
|
|
119
|
-
{ start: 5, end: 5 },
|
|
120
|
-
{ start: 9, end: 9 },
|
|
121
|
-
])
|
|
122
|
-
})
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
// @vitest-environment jsdom
|
|
2
|
-
//
|
|
3
|
-
// Letters drawn on top of the domain overlay take their color from the box
|
|
4
|
-
// underneath them. The overlay paints bands largest-first, so with nested
|
|
5
|
-
// signatures -- routine in InterProScan output, where a family encloses a
|
|
6
|
-
// domain -- the box actually visible at a column is the *last* one painted,
|
|
7
|
-
// not the first one covering it.
|
|
8
|
-
import { expect, test } from 'vitest'
|
|
9
|
-
|
|
10
|
-
import { domainBandCursor } from './components/msa/domainBandCursor.ts'
|
|
11
|
-
import MSAModelF from './model.ts'
|
|
12
|
-
|
|
13
|
-
const row = 'ACDEFGHIKLMNPQRSTVWY'
|
|
14
|
-
const msa = `>seq1\n${row}\n>seq2\n${row}`
|
|
15
|
-
|
|
16
|
-
// PF_OUTER spans the whole row, PF_INNER sits inside it. tidyInterProAnnotations
|
|
17
|
-
// orders by length descending, so the outer band paints first and the inner one
|
|
18
|
-
// covers it.
|
|
19
|
-
const gff = `##gff-version 3
|
|
20
|
-
seq1\tPfam\tprotein_match\t1\t20\t.\t.\t.\tName=PF_OUTER;signature_desc=Outer
|
|
21
|
-
seq1\tPfam\tprotein_match\t8\t12\t.\t.\t.\tName=PF_INNER;signature_desc=Inner`
|
|
22
|
-
|
|
23
|
-
function makeModel() {
|
|
24
|
-
const model = MSAModelF().create({
|
|
25
|
-
type: 'MsaView',
|
|
26
|
-
msaFormat: 'fasta',
|
|
27
|
-
data: { msa },
|
|
28
|
-
})
|
|
29
|
-
model.setWidth(800)
|
|
30
|
-
model.applyGFFText(gff)
|
|
31
|
-
return model
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function sweep(model: ReturnType<typeof makeModel>, from = 0) {
|
|
35
|
-
const bandAt = domainBandCursor(model.domainBandsByStart.get('seq1'))
|
|
36
|
-
const out: (string | undefined)[] = []
|
|
37
|
-
for (let col = from; col < 20; col++) {
|
|
38
|
-
out.push(bandAt(col)?.annotation.accession)
|
|
39
|
-
}
|
|
40
|
-
return out
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
test('the overlay paints largest-first, so stackIndex is paint order', () => {
|
|
44
|
-
const bands = makeModel().domainBands.get('seq1')!
|
|
45
|
-
expect(bands.map(b => [b.annotation.accession, b.stackIndex])).toEqual([
|
|
46
|
-
['PF_OUTER', 0],
|
|
47
|
-
['PF_INNER', 1],
|
|
48
|
-
])
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
test('a nested band wins the columns it covers', () => {
|
|
52
|
-
const model = makeModel()
|
|
53
|
-
// PF_INNER is seq positions 8-12 (1-based), i.e. columns 7..11
|
|
54
|
-
expect(sweep(model)).toEqual([
|
|
55
|
-
...Array.from({ length: 7 }, () => 'PF_OUTER'),
|
|
56
|
-
...Array.from({ length: 5 }, () => 'PF_INNER'),
|
|
57
|
-
...Array.from({ length: 8 }, () => 'PF_OUTER'),
|
|
58
|
-
])
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
test('a sweep starting mid-block picks up bands opened before it', () => {
|
|
62
|
-
// canvas blocks start at arbitrary columns, so the cursor has to catch up on
|
|
63
|
-
// its first call rather than assuming it began at column 0
|
|
64
|
-
const model = makeModel()
|
|
65
|
-
expect(sweep(model, 9)).toEqual([
|
|
66
|
-
'PF_INNER',
|
|
67
|
-
'PF_INNER',
|
|
68
|
-
'PF_INNER',
|
|
69
|
-
...Array.from({ length: 8 }, () => 'PF_OUTER'),
|
|
70
|
-
])
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
test('columns outside every band have no covering band', () => {
|
|
74
|
-
const model = makeModel()
|
|
75
|
-
model.setFilter('PF_OUTER', false)
|
|
76
|
-
const bandAt = domainBandCursor(model.domainBandsByStart.get('seq1'))
|
|
77
|
-
expect(bandAt(0)).toBeUndefined()
|
|
78
|
-
expect(bandAt(9)?.annotation.accession).toBe('PF_INNER')
|
|
79
|
-
expect(bandAt(19)).toBeUndefined()
|
|
80
|
-
})
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { expect, test } from 'vitest'
|
|
2
|
-
|
|
3
|
-
import { exportFileName } from './exportFileName.ts'
|
|
4
|
-
|
|
5
|
-
test('takes the alignment file name and swaps the extension', () => {
|
|
6
|
-
expect(
|
|
7
|
-
exportFileName(
|
|
8
|
-
{
|
|
9
|
-
uri: 'https://example.org/data/PF00069.stock?dl=1',
|
|
10
|
-
locationType: 'UriLocation',
|
|
11
|
-
},
|
|
12
|
-
'svg',
|
|
13
|
-
),
|
|
14
|
-
).toBe('PF00069.svg')
|
|
15
|
-
expect(
|
|
16
|
-
exportFileName(
|
|
17
|
-
{
|
|
18
|
-
localPath: 'C:\\aligns\\hemoglobin.fa',
|
|
19
|
-
locationType: 'LocalPathLocation',
|
|
20
|
-
},
|
|
21
|
-
'svg',
|
|
22
|
-
),
|
|
23
|
-
).toBe('hemoglobin.svg')
|
|
24
|
-
expect(
|
|
25
|
-
exportFileName(
|
|
26
|
-
{ blobId: 'b1', name: 'pasted.aln', locationType: 'BlobLocation' },
|
|
27
|
-
'svg',
|
|
28
|
-
),
|
|
29
|
-
).toBe('pasted.svg')
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
test('falls back to a plain name without a file', () => {
|
|
33
|
-
expect(exportFileName(undefined, 'svg')).toBe('image.svg')
|
|
34
|
-
expect(
|
|
35
|
-
exportFileName(
|
|
36
|
-
{ uri: 'https://example.org/', locationType: 'UriLocation' },
|
|
37
|
-
'svg',
|
|
38
|
-
),
|
|
39
|
-
).toBe('image.svg')
|
|
40
|
-
})
|
package/src/fetchUtils.test.ts
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import { openLocation } from '@jbrowse/core/util/io'
|
|
2
|
-
import { describe, expect, test } from 'vitest'
|
|
3
|
-
|
|
4
|
-
import { fetchTextWithProgress, isAbortError } from './fetchUtils.ts'
|
|
5
|
-
|
|
6
|
-
import type { FetchStatus } from './fetchUtils.ts'
|
|
7
|
-
|
|
8
|
-
const loc = openLocation({
|
|
9
|
-
uri: 'http://example.com/data.fa',
|
|
10
|
-
locationType: 'UriLocation',
|
|
11
|
-
})
|
|
12
|
-
|
|
13
|
-
describe('fetchTextWithProgress', () => {
|
|
14
|
-
test('reports progress messages then clears status', async () => {
|
|
15
|
-
const statuses: (FetchStatus | undefined)[] = []
|
|
16
|
-
const result = await fetchTextWithProgress(
|
|
17
|
-
loc,
|
|
18
|
-
s => statuses.push(s),
|
|
19
|
-
async (_loc, opts) => {
|
|
20
|
-
opts.statusCallback('Downloading file')
|
|
21
|
-
opts.statusCallback('')
|
|
22
|
-
return 'GREETINGS'
|
|
23
|
-
},
|
|
24
|
-
)
|
|
25
|
-
expect(result).toBe('GREETINGS')
|
|
26
|
-
expect(statuses.map(s => s?.msg)).toEqual([
|
|
27
|
-
'Downloading file',
|
|
28
|
-
undefined,
|
|
29
|
-
undefined,
|
|
30
|
-
])
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
test('reports the message from a determinate progress status', async () => {
|
|
34
|
-
const statuses: (FetchStatus | undefined)[] = []
|
|
35
|
-
await fetchTextWithProgress(
|
|
36
|
-
loc,
|
|
37
|
-
s => statuses.push(s),
|
|
38
|
-
async (_loc, opts) => {
|
|
39
|
-
opts.statusCallback({
|
|
40
|
-
message: 'Downloading file',
|
|
41
|
-
current: 5,
|
|
42
|
-
total: 10,
|
|
43
|
-
})
|
|
44
|
-
return 'GREETINGS'
|
|
45
|
-
},
|
|
46
|
-
)
|
|
47
|
-
expect(statuses[0]?.msg).toBe('Downloading file')
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
test('clears status even when the fetch throws', async () => {
|
|
51
|
-
const statuses: (FetchStatus | undefined)[] = []
|
|
52
|
-
await expect(
|
|
53
|
-
fetchTextWithProgress(
|
|
54
|
-
loc,
|
|
55
|
-
s => statuses.push(s),
|
|
56
|
-
async () => {
|
|
57
|
-
throw new Error('network down')
|
|
58
|
-
},
|
|
59
|
-
),
|
|
60
|
-
).rejects.toThrow('network down')
|
|
61
|
-
expect(statuses.at(-1)).toBeUndefined()
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
test('onCancel aborts the underlying request', async () => {
|
|
65
|
-
const statuses: (FetchStatus | undefined)[] = []
|
|
66
|
-
const promise = fetchTextWithProgress(
|
|
67
|
-
loc,
|
|
68
|
-
s => statuses.push(s),
|
|
69
|
-
(_loc, opts) =>
|
|
70
|
-
new Promise<string>((_resolve, reject) => {
|
|
71
|
-
opts.statusCallback('Downloading file')
|
|
72
|
-
opts.signal.addEventListener('abort', () => {
|
|
73
|
-
reject(new DOMException('Aborted', 'AbortError'))
|
|
74
|
-
})
|
|
75
|
-
}),
|
|
76
|
-
)
|
|
77
|
-
|
|
78
|
-
statuses.find(s => s?.onCancel)?.onCancel?.()
|
|
79
|
-
|
|
80
|
-
await expect(promise).rejects.toSatisfy(isAbortError)
|
|
81
|
-
expect(statuses.at(-1)).toBeUndefined()
|
|
82
|
-
})
|
|
83
|
-
})
|
package/src/flatToTree.test.ts
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from 'vitest'
|
|
2
|
-
|
|
3
|
-
import { flatToTree } from './flatToTree.ts'
|
|
4
|
-
|
|
5
|
-
describe('flatToTree', () => {
|
|
6
|
-
test('builds a tree from a flat parent list', () => {
|
|
7
|
-
const root = flatToTree([
|
|
8
|
-
{ id: 0 },
|
|
9
|
-
{ id: 1, parent: 0 },
|
|
10
|
-
{ id: 2, parent: 0 },
|
|
11
|
-
{ id: 3, parent: 1 },
|
|
12
|
-
])
|
|
13
|
-
expect(root.id).toBe('0')
|
|
14
|
-
expect(root.children.map(c => c.id)).toEqual(['1', '2'])
|
|
15
|
-
expect(root.children[0]!.children.map(c => c.id)).toEqual(['3'])
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
test('carries the label and branch length of a BioTreeContainer node', () => {
|
|
19
|
-
const root = flatToTree([
|
|
20
|
-
{ id: 0, label: 'Multiple organisms' },
|
|
21
|
-
{
|
|
22
|
-
id: 1,
|
|
23
|
-
parent: 0,
|
|
24
|
-
label: 'sodium/glucose cotransporter 4',
|
|
25
|
-
dist: '0.5',
|
|
26
|
-
},
|
|
27
|
-
{ id: 2, parent: 0 },
|
|
28
|
-
])
|
|
29
|
-
expect(root.name).toBe('Multiple organisms')
|
|
30
|
-
expect(root.children[0]).toMatchObject({
|
|
31
|
-
name: 'sodium/glucose cotransporter 4',
|
|
32
|
-
length: 0.5,
|
|
33
|
-
})
|
|
34
|
-
// no label falls back to the id, and no dist leaves the length unset
|
|
35
|
-
expect(root.children[1]!.name).toBe('2')
|
|
36
|
-
expect(root.children[1]!.length).toBeUndefined()
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
test('throws on empty input rather than returning undefined', () => {
|
|
40
|
-
expect(() => flatToTree([])).toThrow(/no root/)
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
test('throws when every node has a parent (cycle) rather than returning undefined', () => {
|
|
44
|
-
expect(() =>
|
|
45
|
-
flatToTree([
|
|
46
|
-
{ id: 0, parent: 1 },
|
|
47
|
-
{ id: 1, parent: 0 },
|
|
48
|
-
]),
|
|
49
|
-
).toThrow(/no root/)
|
|
50
|
-
})
|
|
51
|
-
})
|
package/src/hierarchy.test.ts
DELETED
|
@@ -1,228 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from 'vitest'
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
calcDepthToLeaf,
|
|
5
|
-
collapse,
|
|
6
|
-
collapsedSubtreeMaxLength,
|
|
7
|
-
find,
|
|
8
|
-
findMaxBranchLen,
|
|
9
|
-
hierarchy,
|
|
10
|
-
leaves,
|
|
11
|
-
maxLength,
|
|
12
|
-
setBrLength,
|
|
13
|
-
sort,
|
|
14
|
-
sum,
|
|
15
|
-
} from './hierarchy.ts'
|
|
16
|
-
|
|
17
|
-
import type { NodeWithIds } from './types.ts'
|
|
18
|
-
|
|
19
|
-
// Builds a fully unbalanced "caterpillar" tree of the given depth: each internal
|
|
20
|
-
// node has one leaf child and one internal child. Recursion over this would blow
|
|
21
|
-
// the call stack; the iterative traversals must handle it.
|
|
22
|
-
function makeDeepTree(depth: number): NodeWithIds {
|
|
23
|
-
let node: NodeWithIds = { id: 'leaf0', name: 'leaf0', children: [] }
|
|
24
|
-
for (let i = 1; i <= depth; i++) {
|
|
25
|
-
node = {
|
|
26
|
-
id: `node${i}`,
|
|
27
|
-
name: `node${i}`,
|
|
28
|
-
length: 1,
|
|
29
|
-
children: [{ id: `leaf${i}`, name: `leaf${i}`, children: [] }, node],
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
return node
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function makeTree(): NodeWithIds {
|
|
36
|
-
return {
|
|
37
|
-
id: 'root',
|
|
38
|
-
name: 'root',
|
|
39
|
-
children: [
|
|
40
|
-
{
|
|
41
|
-
id: 'A',
|
|
42
|
-
name: 'A',
|
|
43
|
-
children: [
|
|
44
|
-
{ id: 'A1', name: 'A1', children: [] },
|
|
45
|
-
{ id: 'A2', name: 'A2', children: [] },
|
|
46
|
-
],
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
id: 'B',
|
|
50
|
-
name: 'B',
|
|
51
|
-
children: [
|
|
52
|
-
{ id: 'B1', name: 'B1', children: [] },
|
|
53
|
-
{ id: 'B2', name: 'B2', children: [] },
|
|
54
|
-
],
|
|
55
|
-
},
|
|
56
|
-
],
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
describe('hierarchy', () => {
|
|
61
|
-
test('builds hierarchy from tree data', () => {
|
|
62
|
-
const h = hierarchy(makeTree(), d => d.children)
|
|
63
|
-
expect(h.data.id).toBe('root')
|
|
64
|
-
expect(h.children?.length).toBe(2)
|
|
65
|
-
expect(h.height).toBe(2)
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
test('sets parent references', () => {
|
|
69
|
-
const h = hierarchy(makeTree(), d => d.children)
|
|
70
|
-
expect(h.parent).toBeNull()
|
|
71
|
-
expect(h.children![0]!.parent).toBe(h)
|
|
72
|
-
expect(h.children![0]!.children![0]!.parent).toBe(h.children![0])
|
|
73
|
-
})
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
describe('leaves', () => {
|
|
77
|
-
test('returns leaf nodes', () => {
|
|
78
|
-
const h = hierarchy(makeTree(), d => d.children)
|
|
79
|
-
const l = leaves(h)
|
|
80
|
-
expect(l.map(n => n.data.name)).toEqual(['A1', 'A2', 'B1', 'B2'])
|
|
81
|
-
})
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
describe('find', () => {
|
|
85
|
-
test('finds node by id', () => {
|
|
86
|
-
const h = hierarchy(makeTree(), d => d.children)
|
|
87
|
-
const node = find(h, n => n.data.id === 'B1')
|
|
88
|
-
expect(node?.data.name).toBe('B1')
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
test('returns undefined for missing id', () => {
|
|
92
|
-
const h = hierarchy(makeTree(), d => d.children)
|
|
93
|
-
expect(find(h, n => n.data.id === 'Z')).toBeUndefined()
|
|
94
|
-
})
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
describe('collapse', () => {
|
|
98
|
-
test('hides children of a branch node', () => {
|
|
99
|
-
const h = hierarchy(makeTree(), d => d.children)
|
|
100
|
-
const nodeA = find(h, n => n.data.id === 'A')!
|
|
101
|
-
collapse(nodeA)
|
|
102
|
-
expect(nodeA.children).toBeNull()
|
|
103
|
-
expect(nodeA._children?.length).toBe(2)
|
|
104
|
-
expect(leaves(h).map(n => n.data.name)).toEqual(['A', 'B1', 'B2'])
|
|
105
|
-
})
|
|
106
|
-
|
|
107
|
-
test('does nothing on a leaf node', () => {
|
|
108
|
-
const h = hierarchy(makeTree(), d => d.children)
|
|
109
|
-
const leaf = find(h, n => n.data.id === 'A1')!
|
|
110
|
-
collapse(leaf)
|
|
111
|
-
expect(leaves(h).map(n => n.data.name)).toEqual(['A1', 'A2', 'B1', 'B2'])
|
|
112
|
-
})
|
|
113
|
-
|
|
114
|
-
test('preserves the subtree depth so the layout stays stable', () => {
|
|
115
|
-
const h = hierarchy(makeTree(), d => d.children)
|
|
116
|
-
const nodeA = find(h, n => n.data.id === 'A')!
|
|
117
|
-
collapse(nodeA)
|
|
118
|
-
// the collapsed node keeps its real depth (1) rather than becoming a depth-0
|
|
119
|
-
// leaf, so the cladogram apex sits at the true branch point
|
|
120
|
-
expect(nodeA.depthToLeaf).toBe(1)
|
|
121
|
-
// and the root still measures the tree as 2 deep, so collapsing A does not
|
|
122
|
-
// horizontally shift the rest of the tree
|
|
123
|
-
expect(calcDepthToLeaf(h)).toBe(2)
|
|
124
|
-
})
|
|
125
|
-
})
|
|
126
|
-
|
|
127
|
-
describe('collapsedSubtreeMaxLength', () => {
|
|
128
|
-
// root → A(1) → [ A1(2), C(3) → [ C1(1), C2(10) ] ]
|
|
129
|
-
function makeLenTree(): NodeWithIds {
|
|
130
|
-
return {
|
|
131
|
-
id: 'root',
|
|
132
|
-
name: 'root',
|
|
133
|
-
children: [
|
|
134
|
-
{
|
|
135
|
-
id: 'A',
|
|
136
|
-
name: 'A',
|
|
137
|
-
length: 1,
|
|
138
|
-
children: [
|
|
139
|
-
{ id: 'A1', name: 'A1', length: 2, children: [] },
|
|
140
|
-
{
|
|
141
|
-
id: 'C',
|
|
142
|
-
name: 'C',
|
|
143
|
-
length: 3,
|
|
144
|
-
children: [
|
|
145
|
-
{ id: 'C1', name: 'C1', length: 1, children: [] },
|
|
146
|
-
{ id: 'C2', name: 'C2', length: 10, children: [] },
|
|
147
|
-
],
|
|
148
|
-
},
|
|
149
|
-
],
|
|
150
|
-
},
|
|
151
|
-
],
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
test('cumulative branch length to the farthest tip below a node', () => {
|
|
156
|
-
const h = hierarchy(makeLenTree(), d => d.children)
|
|
157
|
-
const nodeA = find(h, n => n.data.id === 'A')!
|
|
158
|
-
collapse(nodeA)
|
|
159
|
-
// excludes A's own branch: farthest tip C2 = 3 + 10 = 13, vs A1 = 2
|
|
160
|
-
expect(collapsedSubtreeMaxLength(nodeA)).toBe(13)
|
|
161
|
-
})
|
|
162
|
-
|
|
163
|
-
test('returns zero for a node with no descendants', () => {
|
|
164
|
-
const h = hierarchy(makeLenTree(), d => d.children)
|
|
165
|
-
const leaf = find(h, n => n.data.id === 'A1')!
|
|
166
|
-
expect(collapsedSubtreeMaxLength(leaf)).toBe(0)
|
|
167
|
-
})
|
|
168
|
-
})
|
|
169
|
-
|
|
170
|
-
describe('leaf removal via parent.children filter', () => {
|
|
171
|
-
test('removing a leaf from its parent hides it from leaves()', () => {
|
|
172
|
-
const h = hierarchy(makeTree(), d => d.children)
|
|
173
|
-
const leaf = find(h, n => n.data.id === 'A1')!
|
|
174
|
-
leaf.parent!.children = leaf.parent!.children!.filter(
|
|
175
|
-
c => c.data.id !== 'A1',
|
|
176
|
-
)
|
|
177
|
-
expect(leaves(h).map(n => n.data.name)).toEqual(['A2', 'B1', 'B2'])
|
|
178
|
-
})
|
|
179
|
-
|
|
180
|
-
test('removing all leaves from a branch makes the branch a leaf', () => {
|
|
181
|
-
const h = hierarchy(makeTree(), d => d.children)
|
|
182
|
-
const nodeA = find(h, n => n.data.id === 'A')!
|
|
183
|
-
nodeA.children = []
|
|
184
|
-
expect(leaves(h).map(n => n.data.name)).toEqual(['B1', 'B2'])
|
|
185
|
-
})
|
|
186
|
-
})
|
|
187
|
-
|
|
188
|
-
describe('sort', () => {
|
|
189
|
-
test('sorts children', () => {
|
|
190
|
-
const h = hierarchy(makeTree(), d => d.children)
|
|
191
|
-
sort(h, (a, b) => b.data.name.localeCompare(a.data.name))
|
|
192
|
-
expect(h.children!.map(c => c.data.name)).toEqual(['B', 'A'])
|
|
193
|
-
})
|
|
194
|
-
})
|
|
195
|
-
|
|
196
|
-
describe('sum', () => {
|
|
197
|
-
test('computes leaf counts', () => {
|
|
198
|
-
const h = hierarchy(makeTree(), d => d.children)
|
|
199
|
-
sum(h, d => (d.children.length > 0 ? 0 : 1))
|
|
200
|
-
expect(h.value).toBe(4)
|
|
201
|
-
expect(find(h, n => n.data.id === 'A')?.value).toBe(2)
|
|
202
|
-
})
|
|
203
|
-
})
|
|
204
|
-
|
|
205
|
-
describe('deep (caterpillar) trees do not overflow the stack', () => {
|
|
206
|
-
const depth = 100_000
|
|
207
|
-
|
|
208
|
-
test('build, height, leaf count, and ordering', () => {
|
|
209
|
-
const h = hierarchy(makeDeepTree(depth), d => d.children)
|
|
210
|
-
expect(h.height).toBe(depth)
|
|
211
|
-
const l = leaves(h)
|
|
212
|
-
expect(l.length).toBe(depth + 1)
|
|
213
|
-
// each level contributes its own leafN before descending; deepest leaf last
|
|
214
|
-
expect(l[0]!.data.name).toBe(`leaf${depth}`)
|
|
215
|
-
expect(l.at(-1)!.data.name).toBe('leaf0')
|
|
216
|
-
})
|
|
217
|
-
|
|
218
|
-
test('accumulation helpers (sum, find, maxLength, setBrLength, depth)', () => {
|
|
219
|
-
const h = hierarchy(makeDeepTree(depth), d => d.children)
|
|
220
|
-
sum(h, d => (d.children.length > 0 ? 0 : 1))
|
|
221
|
-
expect(h.value).toBe(depth + 1)
|
|
222
|
-
expect(find(h, n => n.data.id === 'leaf0')?.data.name).toBe('leaf0')
|
|
223
|
-
expect(maxLength(h)).toBe(depth)
|
|
224
|
-
expect(calcDepthToLeaf(h)).toBe(depth)
|
|
225
|
-
setBrLength(h, 0, 1)
|
|
226
|
-
expect(findMaxBranchLen(h)).toBe(depth)
|
|
227
|
-
})
|
|
228
|
-
})
|
package/src/highlights.test.ts
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
// @vitest-environment jsdom
|
|
2
|
-
import { expect, test } from 'vitest'
|
|
3
|
-
|
|
4
|
-
import MSAModelF from './model.ts'
|
|
5
|
-
|
|
6
|
-
// seq2 skips columns 3-4 of the alignment, so its residue 3 sits at global
|
|
7
|
-
// column 5 (1-based). The all-gap column 4 is what hideGaps removes.
|
|
8
|
-
const msa = `>seq1
|
|
9
|
-
AC-DEFG
|
|
10
|
-
>seq2
|
|
11
|
-
AC--EFG
|
|
12
|
-
>seq3
|
|
13
|
-
AC-DEFG`
|
|
14
|
-
|
|
15
|
-
function makeModel(highlights: Record<string, unknown>[]) {
|
|
16
|
-
const model = MSAModelF().create({
|
|
17
|
-
id: 'highlights-test',
|
|
18
|
-
type: 'MsaView',
|
|
19
|
-
msaFormat: 'fasta',
|
|
20
|
-
data: { msa },
|
|
21
|
-
highlights,
|
|
22
|
-
})
|
|
23
|
-
model.setWidth(800)
|
|
24
|
-
return model
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
test('a column span is 1-based and lands on 0-based visible columns', () => {
|
|
28
|
-
const model = makeModel([{ start: 2, end: 4, label: 'x' }])
|
|
29
|
-
expect(model.resolvedHighlights).toEqual([
|
|
30
|
-
{ startCol: 1, endCol: 3, rowIndices: [], label: 'x', color: undefined },
|
|
31
|
-
])
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
test('a residue span projects through the named row', () => {
|
|
35
|
-
const model = makeModel([{ row: 'seq2', start: 3, end: 4 }])
|
|
36
|
-
expect(model.resolvedHighlights[0]).toMatchObject({ startCol: 4, endCol: 5 })
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
test('hidden columns shift a span and drop one they swallow whole', () => {
|
|
40
|
-
const model = makeModel([
|
|
41
|
-
{ start: 5, end: 6, label: 'after' },
|
|
42
|
-
{ start: 3, end: 3, label: 'gone' },
|
|
43
|
-
{ row: 'seq2', start: 2, end: 3, label: 'across' },
|
|
44
|
-
])
|
|
45
|
-
model.setHideGaps(true)
|
|
46
|
-
model.setAllowedGappyness(50)
|
|
47
|
-
expect(model.blanks).toEqual([2])
|
|
48
|
-
expect(
|
|
49
|
-
model.resolvedHighlights.map(h => [h.label, h.startCol, h.endCol]),
|
|
50
|
-
).toEqual([
|
|
51
|
-
['after', 3, 4],
|
|
52
|
-
['across', 1, 3],
|
|
53
|
-
])
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
test('a row set resolves to row indices and ignores unknown names', () => {
|
|
57
|
-
const model = makeModel([{ rows: ['seq3', 'nope', 'seq1'], color: 'red' }])
|
|
58
|
-
expect(model.resolvedHighlights).toEqual([
|
|
59
|
-
{ rowIndices: [2, 0], label: undefined, color: 'red' },
|
|
60
|
-
])
|
|
61
|
-
expect(makeModel([{ rows: ['nope'] }]).resolvedHighlights).toEqual([])
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
test('setHighlights replaces the list and reset clears it', () => {
|
|
65
|
-
const model = makeModel([])
|
|
66
|
-
model.setHighlights([{ start: 1, end: 1 }])
|
|
67
|
-
expect(model.highlights).toHaveLength(1)
|
|
68
|
-
model.reset()
|
|
69
|
-
expect(model.highlights).toHaveLength(0)
|
|
70
|
-
})
|