react-msaview 6.5.0 → 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.
Files changed (212) hide show
  1. package/README.md +6 -5
  2. package/bundle/index.js +100 -101
  3. package/bundle/index.js.map +4 -4
  4. package/dist/components/Loading.js +2 -2
  5. package/dist/components/Loading.js.map +1 -1
  6. package/dist/components/MSAViewer.d.ts +10 -2
  7. package/dist/components/MSAViewer.js +90 -2
  8. package/dist/components/MSAViewer.js.map +1 -1
  9. package/dist/components/ResizeHandles.d.ts +3 -3
  10. package/dist/components/ResizeHandles.js +28 -21
  11. package/dist/components/ResizeHandles.js.map +1 -1
  12. package/dist/components/dialogs/{InterProScanDialog.d.ts → AnnotationFileDialog.d.ts} +2 -2
  13. package/dist/components/dialogs/AnnotationFileDialog.js +92 -0
  14. package/dist/components/dialogs/AnnotationFileDialog.js.map +1 -0
  15. package/dist/components/dialogs/FeatureDialog.js +13 -9
  16. package/dist/components/dialogs/FeatureDialog.js.map +1 -1
  17. package/dist/components/header/Header.js +4 -0
  18. package/dist/components/header/Header.js.map +1 -1
  19. package/dist/components/header/HeaderStatusArea.js +0 -2
  20. package/dist/components/header/HeaderStatusArea.js.map +1 -1
  21. package/dist/components/header/LoadWarnings.d.ts +6 -0
  22. package/dist/components/header/LoadWarnings.js +36 -0
  23. package/dist/components/header/LoadWarnings.js.map +1 -0
  24. package/dist/components/header/MultiAlignmentSelector.js +0 -0
  25. package/dist/components/header/MultiAlignmentSelector.js.map +1 -1
  26. package/dist/components/header/TreeSettingsMenu.js +9 -1
  27. package/dist/components/header/TreeSettingsMenu.js.map +1 -1
  28. package/dist/components/header/UnshareableDataWarning.d.ts +6 -0
  29. package/dist/components/header/UnshareableDataWarning.js +42 -0
  30. package/dist/components/header/UnshareableDataWarning.js.map +1 -0
  31. package/dist/components/header/getAnnotationsMenu.js +14 -14
  32. package/dist/components/header/getAnnotationsMenu.js.map +1 -1
  33. package/dist/components/import/ImportForm.js +1 -1
  34. package/dist/components/import/ImportForm.js.map +1 -1
  35. package/dist/components/import/ImportFormExamples.js +2 -6
  36. package/dist/components/import/ImportFormExamples.js.map +1 -1
  37. package/dist/components/import/util.js +3 -0
  38. package/dist/components/import/util.js.map +1 -1
  39. package/dist/components/msa/MSACanvasBlock.js +4 -5
  40. package/dist/components/msa/MSACanvasBlock.js.map +1 -1
  41. package/dist/components/msa/msaRaster.d.ts +13 -3
  42. package/dist/components/msa/msaRaster.js +173 -61
  43. package/dist/components/msa/msaRaster.js.map +1 -1
  44. package/dist/components/msa/renderHighlights.d.ts +39 -1
  45. package/dist/components/msa/renderHighlights.js +71 -6
  46. package/dist/components/msa/renderHighlights.js.map +1 -1
  47. package/dist/components/msa/renderMSABlock.d.ts +1 -2
  48. package/dist/components/msa/renderMSABlock.js +24 -28
  49. package/dist/components/msa/renderMSABlock.js.map +1 -1
  50. package/dist/components/msa/renderMSAMouseover.js +9 -30
  51. package/dist/components/msa/renderMSAMouseover.js.map +1 -1
  52. package/dist/components/renderCtx.d.ts +1 -1
  53. package/dist/components/tracks/TrackBlocks.js +10 -11
  54. package/dist/components/tracks/TrackBlocks.js.map +1 -1
  55. package/dist/components/tracks/drawTracks.d.ts +26 -8
  56. package/dist/components/tracks/drawTracks.js +123 -13
  57. package/dist/components/tracks/drawTracks.js.map +1 -1
  58. package/dist/components/tree/TreeCanvas.js +2 -3
  59. package/dist/components/tree/TreeCanvas.js.map +1 -1
  60. package/dist/components/tree/TreeCanvasBlock.js +4 -2
  61. package/dist/components/tree/TreeCanvasBlock.js.map +1 -1
  62. package/dist/components/tree/TreeRuler.js +13 -3
  63. package/dist/components/tree/TreeRuler.js.map +1 -1
  64. package/dist/components/tree/renderTreeCanvas.d.ts +0 -1
  65. package/dist/components/tree/renderTreeCanvas.js +70 -30
  66. package/dist/components/tree/renderTreeCanvas.js.map +1 -1
  67. package/dist/components/tree/scaleBar.d.ts +10 -0
  68. package/dist/components/tree/scaleBar.js +24 -0
  69. package/dist/components/tree/scaleBar.js.map +1 -0
  70. package/dist/constants.d.ts +3 -1
  71. package/dist/constants.js +18 -1
  72. package/dist/constants.js.map +1 -1
  73. package/dist/fetchUtils.d.ts +7 -7
  74. package/dist/fetchUtils.js +4 -32
  75. package/dist/fetchUtils.js.map +1 -1
  76. package/dist/hierarchy.d.ts +2 -1
  77. package/dist/hierarchy.js +16 -10
  78. package/dist/hierarchy.js.map +1 -1
  79. package/dist/index.d.ts +2 -1
  80. package/dist/index.js +8 -2
  81. package/dist/index.js.map +1 -1
  82. package/dist/model/DataModel.js +5 -2
  83. package/dist/model/DataModel.js.map +1 -1
  84. package/dist/model.d.ts +454 -177
  85. package/dist/model.js +1180 -511
  86. package/dist/model.js.map +1 -1
  87. package/dist/renderToStaticMarkup.d.ts +11 -0
  88. package/dist/renderToStaticMarkup.js +30 -7
  89. package/dist/renderToStaticMarkup.js.map +1 -1
  90. package/dist/renderToSvg.js +25 -19
  91. package/dist/renderToSvg.js.map +1 -1
  92. package/dist/rowCoordinateCalculations.d.ts +25 -25
  93. package/dist/rowCoordinateCalculations.js +12 -36
  94. package/dist/rowCoordinateCalculations.js.map +1 -1
  95. package/dist/seqPosToGlobalCol.js +3 -1
  96. package/dist/seqPosToGlobalCol.js.map +1 -1
  97. package/dist/svgTestUtil.d.ts +144 -82
  98. package/dist/types.d.ts +97 -3
  99. package/dist/util.d.ts +11 -4
  100. package/dist/util.js +30 -10
  101. package/dist/util.js.map +1 -1
  102. package/dist/version.d.ts +1 -1
  103. package/dist/version.js +1 -1
  104. package/dist/wuss.d.ts +14 -0
  105. package/dist/wuss.js +55 -0
  106. package/dist/wuss.js.map +1 -0
  107. package/package.json +10 -8
  108. package/src/components/Loading.tsx +12 -2
  109. package/src/components/MSAViewer.tsx +106 -2
  110. package/src/components/ResizeHandles.tsx +31 -31
  111. package/src/components/dialogs/AnnotationFileDialog.tsx +162 -0
  112. package/src/components/dialogs/FeatureDialog.tsx +13 -9
  113. package/src/components/header/Header.tsx +4 -0
  114. package/src/components/header/HeaderStatusArea.tsx +0 -5
  115. package/src/components/header/LoadWarnings.tsx +49 -0
  116. package/src/components/header/MultiAlignmentSelector.tsx +0 -0
  117. package/src/components/header/TreeSettingsMenu.tsx +9 -1
  118. package/src/components/header/UnshareableDataWarning.tsx +58 -0
  119. package/src/components/header/getAnnotationsMenu.ts +16 -17
  120. package/src/components/import/ImportForm.tsx +2 -2
  121. package/src/components/import/ImportFormExamples.tsx +2 -6
  122. package/src/components/import/util.ts +3 -0
  123. package/src/components/msa/MSACanvasBlock.tsx +4 -5
  124. package/src/components/msa/msaRaster.ts +216 -59
  125. package/src/components/msa/renderHighlights.ts +103 -5
  126. package/src/components/msa/renderMSABlock.ts +25 -36
  127. package/src/components/msa/renderMSAMouseover.ts +9 -39
  128. package/src/components/renderCtx.ts +1 -0
  129. package/src/components/tracks/TrackBlocks.tsx +10 -13
  130. package/src/components/tracks/drawTracks.ts +165 -17
  131. package/src/components/tree/TreeCanvas.tsx +2 -3
  132. package/src/components/tree/TreeCanvasBlock.tsx +4 -2
  133. package/src/components/tree/TreeRuler.tsx +38 -3
  134. package/src/components/tree/renderTreeCanvas.ts +79 -35
  135. package/src/components/tree/scaleBar.ts +23 -0
  136. package/src/constants.ts +20 -1
  137. package/src/fetchUtils.ts +7 -40
  138. package/src/hierarchy.ts +20 -11
  139. package/src/index.ts +18 -2
  140. package/src/model/DataModel.ts +6 -2
  141. package/src/model.ts +1352 -616
  142. package/src/rawImports.d.ts +9 -0
  143. package/src/renderToStaticMarkup.ts +34 -7
  144. package/src/renderToSvg.tsx +33 -27
  145. package/src/rowCoordinateCalculations.ts +15 -59
  146. package/src/seqPosToGlobalCol.ts +3 -1
  147. package/src/types.ts +131 -4
  148. package/src/util.ts +31 -14
  149. package/src/version.ts +1 -1
  150. package/src/wuss.ts +62 -0
  151. package/dist/components/dialogs/InterProScanDialog.js +0 -137
  152. package/dist/components/dialogs/InterProScanDialog.js.map +0 -1
  153. package/dist/components/dialogs/InterProScanFileDialog.d.ts +0 -7
  154. package/dist/components/dialogs/InterProScanFileDialog.js +0 -64
  155. package/dist/components/dialogs/InterProScanFileDialog.js.map +0 -1
  156. package/dist/launchInterProScan.d.ts +0 -13
  157. package/dist/launchInterProScan.js +0 -110
  158. package/dist/launchInterProScan.js.map +0 -1
  159. package/dist/useColorContrast.d.ts +0 -10
  160. package/dist/useColorContrast.js +0 -13
  161. package/dist/useColorContrast.js.map +0 -1
  162. package/src/clustalX.test.ts +0 -74
  163. package/src/colSpace.test.ts +0 -67
  164. package/src/collapseLogic.test.ts +0 -115
  165. package/src/colorSchemes.docs.test.ts +0 -57
  166. package/src/columnCounts.test.ts +0 -73
  167. package/src/columnStats.test.ts +0 -94
  168. package/src/columnTracks.test.ts +0 -97
  169. package/src/components/dialogs/InterProScanDialog.tsx +0 -214
  170. package/src/components/dialogs/InterProScanFileDialog.tsx +0 -138
  171. package/src/components/minimap/minimapLayout.test.ts +0 -71
  172. package/src/components/msa/msaRaster.test.ts +0 -145
  173. package/src/components/msa/renderBoxFeatureCanvasBlock.test.ts +0 -51
  174. package/src/components/tree/renderTreeCanvas.test.ts +0 -296
  175. package/src/dataInitialized.test.ts +0 -42
  176. package/src/domainBands.test.ts +0 -122
  177. package/src/domainLetterColor.test.ts +0 -80
  178. package/src/exportFileName.test.ts +0 -40
  179. package/src/fetchUtils.test.ts +0 -83
  180. package/src/flatToTree.test.ts +0 -51
  181. package/src/hierarchy.test.ts +0 -228
  182. package/src/highlights.test.ts +0 -70
  183. package/src/impgRender.test.tsx +0 -68
  184. package/src/largeTree.test.ts +0 -67
  185. package/src/launchInterProScan.ts +0 -167
  186. package/src/loadErrorDisplay.test.tsx +0 -102
  187. package/src/modelFilehandleLoaders.test.ts +0 -137
  188. package/src/modelReset.test.ts +0 -120
  189. package/src/neighborJoining.test.ts +0 -164
  190. package/src/parseAsn1.test.ts +0 -36
  191. package/src/propertyConservation.test.ts +0 -36
  192. package/src/renderColumnTracksSvg.test.tsx +0 -108
  193. package/src/renderDomainsSvg.test.tsx +0 -118
  194. package/src/renderHighlightsSvg.test.tsx +0 -85
  195. package/src/renderMinimapSvg.test.tsx +0 -62
  196. package/src/renderRasterSvg.test.tsx +0 -144
  197. package/src/renderSequenceLogoSvg.test.tsx +0 -165
  198. package/src/renderToSvg.test.tsx +0 -65
  199. package/src/reset.test.ts +0 -61
  200. package/src/rowCoordinateCalculations.test.ts +0 -211
  201. package/src/rowSpace.test.ts +0 -46
  202. package/src/segmentOverlay.test.ts +0 -70
  203. package/src/seqPosToGlobalCol.test.ts +0 -59
  204. package/src/sequenceLogo.test.ts +0 -88
  205. package/src/setFontSize.test.ts +0 -33
  206. package/src/stripDefault.test.ts +0 -48
  207. package/src/stripDefaultSnapshot.test.ts +0 -87
  208. package/src/treeMetadata.test.ts +0 -39
  209. package/src/useCanvasAutorun.test.tsx +0 -98
  210. package/src/useColorContrast.ts +0 -18
  211. package/src/viewChrome.test.tsx +0 -134
  212. package/src/wheelZoom.test.ts +0 -46
@@ -1,144 +0,0 @@
1
- // @vitest-environment jsdom
2
- //
3
- // The SVG export's raster background. jsdom has no canvas, so the export falls
4
- // back to a <rect> per cell there and every other export test exercises that
5
- // path; this file supplies the canvas the raster needs and checks the export
6
- // takes it instead.
7
- import { createJBrowseTheme } from '@jbrowse/core/ui/theme'
8
- import { beforeAll, expect, test } from 'vitest'
9
-
10
- import { renderToSvg } from './renderToSvg.tsx'
11
- import {
12
- createTestModel,
13
- installSvgTestEnv,
14
- syntheticProteinMsa,
15
- } from './svgTestUtil.ts'
16
-
17
- const dataUrl = 'data:image/png;base64,STUB'
18
-
19
- // enough of a 2d context for the raster: a color parser, an ImageData that is
20
- // really backed by bytes, and a canvas that reads back
21
- beforeAll(() => {
22
- installSvgTestEnv({
23
- fillStyle: '#000000',
24
- clearRect: () => {},
25
- fillRect: () => {},
26
- createImageData: ((width: number, height: number) => ({
27
- data: new Uint8ClampedArray(width * height * 4),
28
- width,
29
- height,
30
- })) as unknown as CanvasRenderingContext2D['createImageData'],
31
- putImageData: () => {},
32
- getImageData: (() => ({
33
- data: Uint8ClampedArray.from([1, 2, 3, 255]),
34
- })) as unknown as CanvasRenderingContext2D['getImageData'],
35
- })
36
- HTMLCanvasElement.prototype.toDataURL = () => dataUrl
37
- })
38
-
39
- function makeModel(rows: number, cols: number) {
40
- return createTestModel({
41
- id: 'raster',
42
- data: { msa: syntheticProteinMsa(rows, cols) },
43
- })
44
- }
45
-
46
- function exportEntire(model: ReturnType<typeof makeModel>) {
47
- return renderToSvg(model, {
48
- theme: createJBrowseTheme(),
49
- exportType: 'entire',
50
- })
51
- }
52
-
53
- test('the background is one image, not a rect per cell', async () => {
54
- const model = makeModel(40, 300)
55
- expect(model.bgColor).toBe(true)
56
- expect(model.actuallyShowDomains).toBe(false)
57
-
58
- const svg = await exportEntire(model)
59
- const images = [...svg.matchAll(/<image[^>]*>/g)].map(m => m[0])
60
-
61
- expect(images).toHaveLength(1)
62
- expect(images[0]).toContain(dataUrl)
63
- // the alignment's own rectangle, one pixel per cell scaled up by whole cells
64
- expect(images[0]).toContain(`width="${300 * model.colWidth}"`)
65
- expect(images[0]).toContain(`height="${40 * model.rowHeight}"`)
66
- expect(images[0]).toContain('image-rendering="pixelated"')
67
-
68
- // 12000 cells would have been 12000 rects
69
- expect((svg.match(/<rect/g) ?? []).length).toBeLessThan(10)
70
- })
71
-
72
- test('letters still draw on top of the raster', async () => {
73
- const model = makeModel(4, 10)
74
- expect(model.showMsaLetters).toBe(true)
75
-
76
- const svg = await exportEntire(model)
77
-
78
- expect(svg).toContain('<image')
79
- expect((svg.match(/<text/g) ?? []).length).toBeGreaterThanOrEqual(40)
80
- })
81
-
82
- test('the domain overlay keeps the vector path, which paints its own boxes', async () => {
83
- const model = makeModel(4, 10)
84
- model.setDomains({
85
- seq0: {
86
- xref: [{ id: 'seq0' }],
87
- matches: [
88
- {
89
- signature: {
90
- entry: { name: 'Kinase', accession: 'PF00069', description: '' },
91
- },
92
- locations: [{ start: 1, end: 5 }],
93
- },
94
- ],
95
- },
96
- })
97
- expect(model.actuallyShowDomains).toBe(true)
98
-
99
- expect(await exportEntire(model)).not.toContain('<image')
100
- })
101
-
102
- test('turning background color off leaves nothing for a raster to draw', async () => {
103
- const model = makeModel(4, 10)
104
- model.setBgColor(false)
105
-
106
- expect(await exportEntire(model)).not.toContain('<image')
107
- })
108
-
109
- test('the exported minimap carries the alignment thumbnail', async () => {
110
- const model = makeModel(20, 2000)
111
- expect(model.showHorizontalScrollbar).toBe(true)
112
-
113
- const svg = await renderToSvg(model, {
114
- theme: createJBrowseTheme(),
115
- exportType: 'viewport',
116
- includeMinimap: true,
117
- })
118
-
119
- // the bar is the same downsampled alignment the live minimap draws, rather
120
- // than the empty outline the export used to leave
121
- const bar = [...svg.matchAll(/<image[^>]*>/g)]
122
- .map(m => m[0])
123
- .find(i => i.includes('height="12"'))
124
- expect(bar).toBeDefined()
125
- expect(bar).toContain(dataUrl)
126
- expect(bar).toContain(`width="${model.msaCanvasWidth}"`)
127
- })
128
-
129
- test('each track is named in the tree column beside it', async () => {
130
- const model = makeModel(4, 40)
131
- model.toggleTrack('sequence-logo')
132
- const names = model.turnedOnTracks.map(t => t.model.name)
133
- expect(names.length).toBeGreaterThan(1)
134
-
135
- const svg = await renderToSvg(model, {
136
- theme: createJBrowseTheme(),
137
- exportType: 'entire',
138
- includeTracks: true,
139
- })
140
-
141
- for (const name of names) {
142
- expect(svg).toContain(`>${name}</text>`)
143
- }
144
- })
@@ -1,165 +0,0 @@
1
- // @vitest-environment jsdom
2
- //
3
- // The logo track is the first track kind whose letters are drawn under a
4
- // per-letter transform rather than at a fixed size, so it is also the first that
5
- // can silently draw nothing if the export context loses that transform. These
6
- // tests go through the real SVG export to pin the whole path: the track is off
7
- // until asked for, the stack is ordered and scaled, and the glyphs land inside
8
- // the track's own band.
9
- import { createJBrowseTheme } from '@jbrowse/core/ui/theme'
10
- import { beforeAll, expect, test } from 'vitest'
11
-
12
- import { CHAR_WIDTH_RATIO } from './headlessRenderEnv.ts'
13
- import { renderToSvg } from './renderToSvg.tsx'
14
- import { createTestModel, installSvgTestEnv } from './svgTestUtil.ts'
15
-
16
- beforeAll(() => {
17
- installSvgTestEnv()
18
- })
19
-
20
- // column 0 is fully conserved A, column 1 is a 3:1 split of C over G, and
21
- // column 2 is an even four-way split carrying no information at all
22
- const msa = `>s1\nACA\n>s2\nACC\n>s3\nACG\n>s4\nAGT\n`
23
- const colWidth = 40
24
-
25
- function makeModel() {
26
- return createTestModel({ colWidth, data: { msa } }, 1000)
27
- }
28
-
29
- async function exportSvg(model: ReturnType<typeof makeModel>) {
30
- return renderToSvg(model, {
31
- theme: createJBrowseTheme(),
32
- exportType: 'entire',
33
- includeTracks: true,
34
- })
35
- }
36
-
37
- // The logo's glyphs are the only <text> drawn at the track's own font size, and
38
- // the only ones carrying a non-unit vertical scale, which is what separates them
39
- // from the alignment letters and tree labels in the same document.
40
- function logoGlyphs(svg: string, fontSize: number) {
41
- return [
42
- ...svg.matchAll(
43
- /<text[^>]*font-size="([\d.]+)px"[^>]*transform="matrix\(([^)]*)\)"[^>]*>([A-Z])<\/text>/g,
44
- ),
45
- ]
46
- .filter(m => Number(m[1]) === fontSize)
47
- .map(m => {
48
- const [sx, , , sy, tx, ty] = m[2]!.split(/[\s,]+/).map(Number)
49
- return { letter: m[3]!, sx: sx!, sy: sy!, x: tx!, y: ty! }
50
- })
51
- }
52
-
53
- async function glyphsOf(model: ReturnType<typeof makeModel>) {
54
- return logoGlyphs(await exportSvg(model), model.sequenceLogoTrackHeight)
55
- }
56
-
57
- test('the logo track is hidden until it is asked for', () => {
58
- const model = makeModel()
59
- expect(model.tracks.map(t => t.model.id)).toContain('sequence-logo')
60
- expect(model.turnedOnTracks.map(t => t.model.id)).not.toContain(
61
- 'sequence-logo',
62
- )
63
- // and being default-off writes nothing into the shareable snapshot
64
- expect(model.turnedOffTracks.size).toBe(0)
65
- })
66
-
67
- test('toggling it on shows it, and off again hides it', () => {
68
- const model = makeModel()
69
- model.toggleTrack('sequence-logo')
70
- expect(model.turnedOnTracks.map(t => t.model.id)).toContain('sequence-logo')
71
- model.toggleTrack('sequence-logo')
72
- expect(model.turnedOnTracks.map(t => t.model.id)).not.toContain(
73
- 'sequence-logo',
74
- )
75
- })
76
-
77
- test('an on-by-default track still toggles off and back on', () => {
78
- const model = makeModel()
79
- expect(model.turnedOnTracks.map(t => t.model.id)).toContain('conservation')
80
- model.toggleTrack('conservation')
81
- expect(model.turnedOnTracks.map(t => t.model.id)).not.toContain(
82
- 'conservation',
83
- )
84
- model.toggleTrack('conservation')
85
- expect(model.turnedOnTracks.map(t => t.model.id)).toContain('conservation')
86
- })
87
-
88
- test('the conserved column draws one full-height letter', async () => {
89
- const model = makeModel()
90
- model.toggleTrack('sequence-logo')
91
- const col0 = (await glyphsOf(model)).filter(g => g.x === 0)
92
-
93
- expect(col0.map(g => g.letter)).toEqual(['A'])
94
- // a fully conserved nucleotide column is 2 of 2 bits, so the glyph stretches
95
- // to the whole track height over the cap height of the reference font
96
- expect(col0[0]!.sy).toBeCloseTo(1 / 0.72, 5)
97
- })
98
-
99
- test('a split column stacks the minor letter below the major one', async () => {
100
- const model = makeModel()
101
- model.toggleTrack('sequence-logo')
102
- const col1 = (await glyphsOf(model)).filter(g => g.x === colWidth)
103
-
104
- // ascending order means C (3 of 4) is drawn last, so it ends up on top
105
- expect(col1.map(g => g.letter)).toEqual(['G', 'C'])
106
- const [g, c] = col1
107
- expect(c!.sy).toBeGreaterThan(g!.sy)
108
- // and the taller letter sits higher up the track (smaller y is nearer the top)
109
- expect(c!.y).toBeLessThan(g!.y)
110
- })
111
-
112
- test('the uninformative column draws nothing', async () => {
113
- const model = makeModel()
114
- model.toggleTrack('sequence-logo')
115
- const glyphs = await glyphsOf(model)
116
- expect(glyphs.filter(g => g.x === colWidth * 2)).toEqual([])
117
- // the other two columns did draw, so an empty column 2 is the logo's own
118
- // verdict rather than the whole track missing
119
- expect(glyphs).toHaveLength(3)
120
- })
121
-
122
- test('every glyph stays inside the track band', async () => {
123
- const model = makeModel()
124
- model.toggleTrack('sequence-logo')
125
-
126
- // the export stacks the tracks, so the logo's band is offset by whatever is
127
- // drawn above it -- assert against that band rather than against 0..height,
128
- // which would pass only while the logo happened to be the first track
129
- const above = model.turnedOnTracks
130
- .slice(
131
- 0,
132
- model.turnedOnTracks.findIndex(t => t.model.id === 'sequence-logo'),
133
- )
134
- .reduce((a, t) => a + t.model.height, 0)
135
- const bandTop = above
136
- const bandBottom = above + model.sequenceLogoTrackHeight
137
-
138
- const glyphs = await glyphsOf(model)
139
- expect(glyphs.length).toBeGreaterThan(0)
140
- for (const glyph of glyphs) {
141
- // baselines run from the band's bottom edge upward, never past either end
142
- expect(glyph.y).toBeGreaterThan(bandTop)
143
- expect(glyph.y).toBeLessThanOrEqual(bandBottom)
144
- }
145
- })
146
-
147
- test('letters are stretched to fill their column', async () => {
148
- const model = makeModel()
149
- model.toggleTrack('sequence-logo')
150
- const glyphs = await glyphsOf(model)
151
-
152
- const expected = colWidth / (model.sequenceLogoTrackHeight * CHAR_WIDTH_RATIO)
153
- for (const glyph of glyphs) {
154
- expect(glyph.sx).toBeCloseTo(expected, 5)
155
- }
156
- })
157
-
158
- test('the logo appears in the export only when the track is on', async () => {
159
- const off = makeModel()
160
- expect(await glyphsOf(off)).toEqual([])
161
-
162
- const on = makeModel()
163
- on.toggleTrack('sequence-logo')
164
- expect((await glyphsOf(on)).length).toBeGreaterThan(0)
165
- })
@@ -1,65 +0,0 @@
1
- // @vitest-environment jsdom
2
- //
3
- // Layout and palette of the exported figure itself, as opposed to what any one
4
- // layer draws inside it.
5
- import { createJBrowseTheme } from '@jbrowse/core/ui/theme'
6
- import { beforeAll, expect, test } from 'vitest'
7
-
8
- import { renderToSvg } from './renderToSvg.tsx'
9
- import {
10
- createTestModel,
11
- installSvgTestEnv,
12
- syntheticProteinMsa,
13
- } from './svgTestUtil.ts'
14
-
15
- beforeAll(() => {
16
- installSvgTestEnv()
17
- })
18
-
19
- function makeModel({ rows = 60, cols = 400 } = {}) {
20
- return createTestModel({ data: { msa: syntheticProteinMsa(rows, cols) } })
21
- }
22
-
23
- const rootRect = (svg: string) => /<rect[^>]*height="100%"[^>]*>/.exec(svg)?.[0]
24
-
25
- test('the page takes the theme background, not a hardcoded white', async () => {
26
- const model = makeModel()
27
- const dark = createJBrowseTheme({ palette: { mode: 'dark' } })
28
- expect(dark.palette.background.default).not.toBe('white')
29
-
30
- const svg = await renderToSvg(model, { theme: dark, exportType: 'entire' })
31
-
32
- // the layers below draw in theme colors: text.primary is near-white in a dark
33
- // theme, so a white page would export white on white
34
- expect(rootRect(svg)).toContain(`fill="${dark.palette.background.default}"`)
35
- })
36
-
37
- test('a viewport export is the alignment canvas, not the whole widget', async () => {
38
- const model = makeModel()
39
- expect(model.showHorizontalScrollbar).toBe(true)
40
- expect(model.showVerticalScrollbar).toBe(true)
41
-
42
- const svg = await renderToSvg(model, {
43
- theme: createJBrowseTheme(),
44
- exportType: 'viewport',
45
- includeMinimap: true,
46
- })
47
-
48
- // the widget box also covers the resize handle and the scrollbars; exporting
49
- // it drew the rows and columns those hide
50
- const { treeAreaWidth, msaCanvasWidth, msaAreaHeight, minimapHeight } = model
51
- expect(svg).toContain(`width="${treeAreaWidth + msaCanvasWidth}"`)
52
- expect(svg).toContain(`height="${msaAreaHeight + minimapHeight}"`)
53
- expect(msaAreaHeight + minimapHeight).toBe(model.height)
54
- })
55
-
56
- test('no attribute is serialized as the string "undefined"', async () => {
57
- const model = makeModel({ rows: 4, cols: 20 })
58
- const svg = await renderToSvg(model, {
59
- theme: createJBrowseTheme(),
60
- exportType: 'entire',
61
- })
62
-
63
- expect(svg).toContain('<text')
64
- expect(svg).not.toContain('="undefined"')
65
- })
package/src/reset.test.ts DELETED
@@ -1,61 +0,0 @@
1
- import { expect, test } from 'vitest'
2
-
3
- import stateModelFactory from './model.ts'
4
-
5
- const MsaView = stateModelFactory()
6
-
7
- function loaded() {
8
- const model = MsaView.create({
9
- type: 'MsaView',
10
- data: { msa: '>a\nAC\n>b\nAC\n>c\nAC\n>d\nAC', tree: '((a,b),(c,d));' },
11
- })
12
- model.setWidth(1000)
13
- return model
14
- }
15
-
16
- test('reset returns to the import form', () => {
17
- const model = loaded()
18
- expect(model.dataInitialized).toBe(true)
19
- model.reset()
20
- expect(model.dataInitialized).toBe(false)
21
- })
22
-
23
- // generateNodeIds derives ids from the path, so 'node-0-0-1' is the root's
24
- // first child in EVERY tree. Carrying collapsed/showOnly across a reset opens
25
- // the next file collapsed, or focused on a subtree, with nothing on screen
26
- // saying why
27
- test('reset drops view state that names the old data', () => {
28
- const model = loaded()
29
- const innerNode = model.hierarchy.children![0]!.data.id
30
- model.toggleCollapsed(innerNode)
31
- model.setShowOnly(innerNode)
32
- model.drawRelativeTo('a')
33
- model.setHighlightedColumns([0, 1])
34
- model.setMousePos(1, 1)
35
- model.setMouseClickPos(1, 1)
36
- model.setHoveredTreeNode(innerNode)
37
-
38
- model.reset()
39
-
40
- expect([...model.collapsed]).toEqual([])
41
- expect(model.showOnly).toBeUndefined()
42
- expect(model.relativeTo).toBeUndefined()
43
- expect(model.highlightedColumns).toBeUndefined()
44
- expect(model.mouseCol).toBeUndefined()
45
- expect(model.mouseClickCol).toBeUndefined()
46
- expect(model.hoveredTreeNode).toBeUndefined()
47
- })
48
-
49
- test('a file opened after a reset is not collapsed by the previous one', () => {
50
- const model = loaded()
51
- model.toggleCollapsed(model.hierarchy.children![0]!.data.id)
52
- expect(model.numRows).toBeLessThan(4)
53
-
54
- model.reset()
55
- model.setData({
56
- msa: '>x\nGT\n>y\nGT\n>z\nGT\n>w\nGT',
57
- tree: '((x,y),(z,w));',
58
- })
59
- expect(model.numRows).toBe(4)
60
- expect(model.rowNames).toEqual(['x', 'y', 'z', 'w'])
61
- })
@@ -1,211 +0,0 @@
1
- import { expect, test } from 'vitest'
2
-
3
- import {
4
- globalColToVisibleCol,
5
- visibleColToGlobalCol,
6
- visibleColToSeqPos,
7
- } from './rowCoordinateCalculations.ts'
8
-
9
- // Tests for visibleColToGlobalCol (visible → global)
10
- test('visibleColToGlobalCol with blanks at positions [2, 5, 8]', () => {
11
- const blanks = [2, 5, 8]
12
- ;(
13
- [
14
- [0, 0],
15
- [1, 1],
16
- [2, 3],
17
- [3, 4],
18
- [4, 6],
19
- [5, 7],
20
- [6, 9],
21
- [7, 10],
22
- ] as const
23
- ).forEach(r => {
24
- expect(visibleColToGlobalCol(blanks, r[0])).toBe(r[1])
25
- })
26
- })
27
-
28
- test('visibleColToGlobalCol with no blanks', () => {
29
- const blanks: number[] = []
30
- ;(
31
- [
32
- [0, 0],
33
- [1, 1],
34
- [5, 5],
35
- [10, 10],
36
- ] as const
37
- ).forEach(r => {
38
- expect(visibleColToGlobalCol(blanks, r[0])).toBe(r[1])
39
- })
40
- })
41
-
42
- test('visibleColToGlobalCol with consecutive blanks', () => {
43
- const blanks = [2, 3, 4, 7, 8]
44
- ;(
45
- [
46
- [0, 0],
47
- [1, 1],
48
- [2, 5], // After position 1, skip 3 blanks (2,3,4)
49
- [3, 6], // Next position
50
- [4, 9], // After position 3, skip 2 blanks (7,8)
51
- [5, 10],
52
- ] as const
53
- ).forEach(r => {
54
- expect(visibleColToGlobalCol(blanks, r[0])).toBe(r[1])
55
- })
56
- })
57
-
58
- test('visibleColToGlobalCol with blanks at the beginning', () => {
59
- const blanks = [1, 2, 5]
60
- ;(
61
- [
62
- [0, 0],
63
- [1, 3], // After position 0, skip 2 blanks (1,2)
64
- [2, 4],
65
- [3, 6], // After position 2, skip 1 blank (5)
66
- [4, 7],
67
- ] as const
68
- ).forEach(r => {
69
- expect(visibleColToGlobalCol(blanks, r[0])).toBe(r[1])
70
- })
71
- })
72
-
73
- test('visibleColToGlobalCol with position exceeding blanks array', () => {
74
- const blanks = [2, 5]
75
- ;(
76
- [
77
- [0, 0],
78
- [1, 1],
79
- [2, 3], // After position 1, skip 1 blank (2)
80
- [3, 4],
81
- [4, 6], // After position 3, skip 1 blank (5)
82
- [10, 12], // Far beyond blanks array
83
- ] as const
84
- ).forEach(r => {
85
- expect(visibleColToGlobalCol(blanks, r[0])).toBe(r[1])
86
- })
87
- })
88
-
89
- // Tests for globalColToVisibleCol (global → visible)
90
- test('globalColToVisibleCol with blanks at positions [2, 5, 8]', () => {
91
- const blanks = [2, 5, 8]
92
- // Inverse of visibleColToGlobalCol
93
- expect(globalColToVisibleCol(blanks, 0)).toBe(0)
94
- expect(globalColToVisibleCol(blanks, 1)).toBe(1)
95
- expect(globalColToVisibleCol(blanks, 2)).toBe(undefined) // Hidden
96
- expect(globalColToVisibleCol(blanks, 3)).toBe(2)
97
- expect(globalColToVisibleCol(blanks, 4)).toBe(3)
98
- expect(globalColToVisibleCol(blanks, 5)).toBe(undefined) // Hidden
99
- expect(globalColToVisibleCol(blanks, 6)).toBe(4)
100
- expect(globalColToVisibleCol(blanks, 7)).toBe(5)
101
- expect(globalColToVisibleCol(blanks, 8)).toBe(undefined) // Hidden
102
- expect(globalColToVisibleCol(blanks, 9)).toBe(6)
103
- expect(globalColToVisibleCol(blanks, 10)).toBe(7)
104
- })
105
-
106
- test('globalColToVisibleCol with no blanks', () => {
107
- const blanks: number[] = []
108
- expect(globalColToVisibleCol(blanks, 0)).toBe(0)
109
- expect(globalColToVisibleCol(blanks, 5)).toBe(5)
110
- expect(globalColToVisibleCol(blanks, 10)).toBe(10)
111
- })
112
-
113
- test('globalColToVisibleCol with consecutive blanks', () => {
114
- const blanks = [2, 3, 4, 7, 8]
115
- expect(globalColToVisibleCol(blanks, 0)).toBe(0)
116
- expect(globalColToVisibleCol(blanks, 1)).toBe(1)
117
- expect(globalColToVisibleCol(blanks, 2)).toBe(undefined) // Hidden
118
- expect(globalColToVisibleCol(blanks, 3)).toBe(undefined) // Hidden
119
- expect(globalColToVisibleCol(blanks, 4)).toBe(undefined) // Hidden
120
- expect(globalColToVisibleCol(blanks, 5)).toBe(2)
121
- expect(globalColToVisibleCol(blanks, 6)).toBe(3)
122
- expect(globalColToVisibleCol(blanks, 7)).toBe(undefined) // Hidden
123
- expect(globalColToVisibleCol(blanks, 8)).toBe(undefined) // Hidden
124
- expect(globalColToVisibleCol(blanks, 9)).toBe(4)
125
- expect(globalColToVisibleCol(blanks, 10)).toBe(5)
126
- })
127
-
128
- // Tests for visibleColToSeqPos (visible column → sequence position)
129
- test('visibleColToSeqPos returns sequence position or undefined for gaps', () => {
130
- const seq = 'AC--GT--CT'
131
- expect(visibleColToSeqPos({ seq, visibleCol: 0, blanks: [] })).toBe(0)
132
- expect(visibleColToSeqPos({ seq, visibleCol: 1, blanks: [] })).toBe(1)
133
- expect(visibleColToSeqPos({ seq, visibleCol: 2, blanks: [] })).toBe(undefined)
134
- expect(visibleColToSeqPos({ seq, visibleCol: 3, blanks: [] })).toBe(undefined)
135
- expect(visibleColToSeqPos({ seq, visibleCol: 4, blanks: [] })).toBe(2)
136
- expect(visibleColToSeqPos({ seq, visibleCol: 5, blanks: [] })).toBe(3)
137
- expect(visibleColToSeqPos({ seq, visibleCol: 6, blanks: [] })).toBe(undefined)
138
- expect(visibleColToSeqPos({ seq, visibleCol: 7, blanks: [] })).toBe(undefined)
139
- expect(visibleColToSeqPos({ seq, visibleCol: 8, blanks: [] })).toBe(4)
140
- expect(visibleColToSeqPos({ seq, visibleCol: 9, blanks: [] })).toBe(5)
141
- })
142
-
143
- // Round-trip tests: visible → global → visible should be identity for valid columns
144
- test('round-trip: visibleColToGlobalCol and globalColToVisibleCol are inverses', () => {
145
- const blanks = [2, 5, 8]
146
- // For each visible column, going to global and back should return the same value
147
- for (let visibleCol = 0; visibleCol < 10; visibleCol++) {
148
- const globalCol = visibleColToGlobalCol(blanks, visibleCol)
149
- const backToVisible = globalColToVisibleCol(blanks, globalCol)
150
- expect(backToVisible).toBe(visibleCol)
151
- }
152
- })
153
-
154
- test('round-trip: globalColToVisibleCol and visibleColToGlobalCol for non-hidden columns', () => {
155
- const blanks = [2, 5, 8]
156
- // For each global column that is NOT hidden, going to visible and back should return the same value
157
- for (let globalCol = 0; globalCol < 15; globalCol++) {
158
- const visibleCol = globalColToVisibleCol(blanks, globalCol)
159
- if (visibleCol !== undefined) {
160
- const backToGlobal = visibleColToGlobalCol(blanks, visibleCol)
161
- expect(backToGlobal).toBe(globalCol)
162
- }
163
- }
164
- })
165
-
166
- // Edge case: blanks at position 0
167
- test('globalColToVisibleCol with blank at position 0', () => {
168
- const blanks = [0, 1, 5]
169
- expect(globalColToVisibleCol(blanks, 0)).toBe(undefined) // Hidden
170
- expect(globalColToVisibleCol(blanks, 1)).toBe(undefined) // Hidden
171
- expect(globalColToVisibleCol(blanks, 2)).toBe(0) // First visible column
172
- expect(globalColToVisibleCol(blanks, 3)).toBe(1)
173
- expect(globalColToVisibleCol(blanks, 4)).toBe(2)
174
- expect(globalColToVisibleCol(blanks, 5)).toBe(undefined) // Hidden
175
- expect(globalColToVisibleCol(blanks, 6)).toBe(3)
176
- })
177
-
178
- test('visibleColToGlobalCol with blank at position 0', () => {
179
- const blanks = [0, 1, 5]
180
- expect(visibleColToGlobalCol(blanks, 0)).toBe(2) // Skips 0, 1
181
- expect(visibleColToGlobalCol(blanks, 1)).toBe(3)
182
- expect(visibleColToGlobalCol(blanks, 2)).toBe(4)
183
- expect(visibleColToGlobalCol(blanks, 3)).toBe(6) // Skips 5
184
- })
185
-
186
- // Edge case: all columns before a position are blanks
187
- test('globalColToVisibleCol with many leading blanks', () => {
188
- const blanks = [0, 1, 2, 3, 4]
189
- expect(globalColToVisibleCol(blanks, 0)).toBe(undefined)
190
- expect(globalColToVisibleCol(blanks, 4)).toBe(undefined)
191
- expect(globalColToVisibleCol(blanks, 5)).toBe(0) // First visible
192
- expect(globalColToVisibleCol(blanks, 6)).toBe(1)
193
- expect(globalColToVisibleCol(blanks, 10)).toBe(5)
194
- })
195
-
196
- // Test visibleColToSeqPos with blanks (combined gap hiding and row gaps)
197
- test('visibleColToSeqPos with both blanks and row gaps', () => {
198
- // Sequence: A-C-G (global cols 0,1,2,3,4)
199
- // If blanks = [1, 3], visible sequence becomes: A C G (visible cols 0,1,2)
200
- const seq = 'A-C-G'
201
- const blanks = [1, 3]
202
-
203
- // Visible col 0 → global col 0 → 'A' → seqPos 0
204
- expect(visibleColToSeqPos({ seq, visibleCol: 0, blanks })).toBe(0)
205
-
206
- // Visible col 1 → global col 2 → 'C' → seqPos 1
207
- expect(visibleColToSeqPos({ seq, visibleCol: 1, blanks })).toBe(1)
208
-
209
- // Visible col 2 → global col 4 → 'G' → seqPos 2
210
- expect(visibleColToSeqPos({ seq, visibleCol: 2, blanks })).toBe(2)
211
- })
@@ -1,46 +0,0 @@
1
- // @vitest-environment jsdom
2
- import { expect, test } from 'vitest'
3
-
4
- import MSAModelF from './model.ts'
5
-
6
- // the tree names a tip the alignment has no sequence for. It still occupies a
7
- // row of vertical space (drawn blank), so anything that maps y-pixels to rows has
8
- // to count it, or the last row becomes unhoverable and fit-to-height overshoots.
9
- const msa = `>a
10
- ACGT
11
- >b
12
- ACGT`
13
- const tree = '((a:1,b:1):1,ghost:1);'
14
-
15
- function makeModel() {
16
- const model = MSAModelF().create({
17
- id: 'row-space-test',
18
- type: 'MsaView',
19
- msaFormat: 'fasta',
20
- height: 400,
21
- data: { msa, tree },
22
- })
23
- model.setWidth(800)
24
- return model
25
- }
26
-
27
- test('row space counts tree leaves, not just rows with sequence', () => {
28
- const model = makeModel()
29
- expect(model.rowNames).toEqual(['a', 'b', 'ghost'])
30
- expect(model.rows.map(r => r[0])).toEqual(['a', 'b'])
31
- expect(model.numRows).toBe(3)
32
- expect(model.totalHeight).toBe(3 * model.rowHeight)
33
- })
34
-
35
- test('the last row is addressable', () => {
36
- const model = makeModel()
37
- const lastRow = model.numRows - 1
38
- model.setMousePos(0, lastRow)
39
- expect(model.mouseOverRowName).toBe('ghost')
40
- })
41
-
42
- test('fit vertically leaves every row on screen', () => {
43
- const model = makeModel()
44
- model.fitVertically()
45
- expect(model.totalHeight).toBeLessThanOrEqual(model.msaAreaHeight)
46
- })