react-msaview 5.0.5 → 5.0.7

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 (47) hide show
  1. package/bundle/index.js +106 -106
  2. package/bundle/index.js.LICENSE.txt +1 -1
  3. package/bundle/index.js.map +1 -1
  4. package/dist/components/ConservationTrack.js +15 -1
  5. package/dist/components/ConservationTrack.js.map +1 -1
  6. package/dist/components/Track.d.ts +0 -4
  7. package/dist/components/Track.js +2 -3
  8. package/dist/components/Track.js.map +1 -1
  9. package/dist/components/msa/MSACanvas.js +22 -27
  10. package/dist/components/msa/MSACanvas.js.map +1 -1
  11. package/dist/components/tree/TreeCanvas.js +32 -38
  12. package/dist/components/tree/TreeCanvas.js.map +1 -1
  13. package/dist/components/tree/TreeCanvasBlock.js +33 -1
  14. package/dist/components/tree/TreeCanvasBlock.js.map +1 -1
  15. package/dist/model.d.ts +18 -0
  16. package/dist/model.js +34 -1
  17. package/dist/model.js.map +1 -1
  18. package/dist/neighborJoining.js +2 -4
  19. package/dist/neighborJoining.js.map +1 -1
  20. package/dist/version.d.ts +1 -1
  21. package/dist/version.js +1 -1
  22. package/package.json +11 -9
  23. package/src/components/ConservationTrack.tsx +18 -0
  24. package/src/components/Track.tsx +2 -5
  25. package/src/components/msa/MSACanvas.tsx +24 -30
  26. package/src/components/tree/TreeCanvas.tsx +42 -40
  27. package/src/components/tree/TreeCanvasBlock.tsx +44 -0
  28. package/src/model.ts +36 -1
  29. package/src/neighborJoining.test.ts +15 -7
  30. package/src/neighborJoining.ts +2 -4
  31. package/src/parseAsn1.test.ts +2 -2
  32. package/src/version.ts +1 -1
  33. package/dist/createPaletteMap.test.d.ts +0 -1
  34. package/dist/createPaletteMap.test.js +0 -49
  35. package/dist/createPaletteMap.test.js.map +0 -1
  36. package/dist/neighborJoining.test.d.ts +0 -1
  37. package/dist/neighborJoining.test.js +0 -110
  38. package/dist/neighborJoining.test.js.map +0 -1
  39. package/dist/parseAsn1.test.d.ts +0 -1
  40. package/dist/parseAsn1.test.js +0 -8
  41. package/dist/parseAsn1.test.js.map +0 -1
  42. package/dist/rowCoordinateCalculations.test.d.ts +0 -1
  43. package/dist/rowCoordinateCalculations.test.js +0 -224
  44. package/dist/rowCoordinateCalculations.test.js.map +0 -1
  45. package/dist/seqPosToGlobalCol.test.d.ts +0 -1
  46. package/dist/seqPosToGlobalCol.test.js +0 -60
  47. package/dist/seqPosToGlobalCol.test.js.map +0 -1
@@ -14,8 +14,8 @@ const TreeCanvas = observer(function ({ model }: { model: MsaViewModel }) {
14
14
  const mouseoverRef = useRef<HTMLCanvasElement>(null)
15
15
  const scheduled = useRef(false)
16
16
  const deltaY = useRef(0)
17
- const prevY = useRef<number>(0)
18
- const { treeWidth, height, blocksY, treeAreaWidth, scrollY } = model
17
+ const prevY = useRef(0)
18
+ const { treeWidth, height, blocksY, treeAreaWidth } = model
19
19
  const [mouseDragging, setMouseDragging] = useState(false)
20
20
 
21
21
  useEffect(() => {
@@ -37,71 +37,75 @@ const TreeCanvas = observer(function ({ model }: { model: MsaViewModel }) {
37
37
  event.preventDefault()
38
38
  event.stopPropagation()
39
39
  }
40
- curr.addEventListener('wheel', onWheel)
40
+ curr.addEventListener('wheel', onWheel, { passive: false })
41
41
  return () => {
42
42
  curr.removeEventListener('wheel', onWheel)
43
43
  }
44
44
  }, [model])
45
45
 
46
46
  useEffect(() => {
47
- let cleanup = () => {}
48
-
49
- function globalMouseMove(event: MouseEvent) {
50
- event.preventDefault()
51
- const currY = event.clientY
52
- const distanceY = currY - prevY.current
53
- if (distanceY) {
54
- // use rAF to make it so multiple event handlers aren't fired per-frame
55
- // see
56
- // https://calendar.perfplanet.com/2013/the-runtime-performance-checklist/
57
- if (!scheduled.current) {
58
- scheduled.current = true
59
- window.requestAnimationFrame(() => {
60
- model.doScrollY(distanceY)
61
- scheduled.current = false
62
- prevY.current = event.clientY
63
- })
47
+ if (mouseDragging) {
48
+ function globalMouseMove(event: MouseEvent) {
49
+ event.preventDefault()
50
+ const currY = event.clientY
51
+ const distanceY = currY - prevY.current
52
+ if (distanceY) {
53
+ if (!scheduled.current) {
54
+ scheduled.current = true
55
+ window.requestAnimationFrame(() => {
56
+ model.doScrollY(distanceY)
57
+ scheduled.current = false
58
+ prevY.current = event.clientY
59
+ })
60
+ }
64
61
  }
65
62
  }
66
- }
67
63
 
68
- function globalMouseUp() {
69
- prevY.current = 0
70
- if (mouseDragging) {
64
+ function globalMouseUp() {
65
+ prevY.current = 0
71
66
  setMouseDragging(false)
72
67
  }
73
- }
74
68
 
75
- if (mouseDragging) {
76
69
  window.addEventListener('mousemove', globalMouseMove, true)
77
70
  window.addEventListener('mouseup', globalMouseUp, true)
78
- cleanup = () => {
71
+ return () => {
79
72
  window.removeEventListener('mousemove', globalMouseMove, true)
80
73
  window.removeEventListener('mouseup', globalMouseUp, true)
81
74
  }
82
75
  }
83
- return cleanup
76
+ return undefined
84
77
  }, [model, mouseDragging])
85
78
 
86
- // Global tree mouseover effect
79
+ // Global tree mouseover effect. Only [model] is needed in the dependency
80
+ // array because autorun internally tracks all accessed observables
81
+ // (treeAreaWidth, height, scrollY, etc.) and re-runs when they change
87
82
  useEffect(() => {
88
83
  const ctx = mouseoverRef.current?.getContext('2d')
89
84
  return ctx
90
85
  ? autorun(() => {
91
86
  if (isAlive(model)) {
87
+ const {
88
+ relativeTo,
89
+ leaves,
90
+ rowHeight,
91
+ hoveredTreeNode,
92
+ treeAreaWidth: w,
93
+ height: h,
94
+ scrollY: sy,
95
+ mouseOverRowName,
96
+ } = model
92
97
  ctx.resetTransform()
93
- ctx.clearRect(0, 0, treeAreaWidth, height)
98
+ ctx.clearRect(0, 0, w, h)
94
99
 
95
100
  // Highlight reference row (relativeTo) persistently
96
- const { relativeTo, leaves, rowHeight, hoveredTreeNode } = model
97
101
  if (relativeTo) {
98
102
  const referenceLeaf = leaves.find(
99
103
  leaf => leaf.data.name === relativeTo,
100
104
  )
101
105
  if (referenceLeaf) {
102
- const y = referenceLeaf.x! + scrollY
106
+ const y = referenceLeaf.x! + sy
103
107
  ctx.fillStyle = 'rgba(0,128,255,0.3)' // Blue highlight for reference row
104
- ctx.fillRect(0, y - rowHeight / 2, treeAreaWidth, rowHeight)
108
+ ctx.fillRect(0, y - rowHeight / 2, w, rowHeight)
105
109
  }
106
110
  }
107
111
 
@@ -113,33 +117,31 @@ const TreeCanvas = observer(function ({ model }: { model: MsaViewModel }) {
113
117
  leaf => leaf.data.name === descendantName,
114
118
  )
115
119
  if (matchingLeaf) {
116
- const y = matchingLeaf.x! + scrollY
117
- ctx.fillRect(0, y - rowHeight / 2, treeAreaWidth, rowHeight)
120
+ const y = matchingLeaf.x! + sy
121
+ ctx.fillRect(0, y - rowHeight / 2, w, rowHeight)
118
122
  }
119
123
  }
120
124
  }
121
125
 
122
126
  // Highlight single tree row corresponding to MSA mouseover (if not part of multi-row hover)
123
- const { mouseOverRowName } = model
124
127
  if (
125
128
  mouseOverRowName &&
126
129
  mouseOverRowName !== relativeTo &&
127
130
  !hoveredTreeNode?.descendantNames.includes(mouseOverRowName)
128
131
  ) {
129
- // Find the leaf node that matches the hovered row
130
132
  const matchingLeaf = leaves.find(
131
133
  leaf => leaf.data.name === mouseOverRowName,
132
134
  )
133
135
  if (matchingLeaf) {
134
- const y = matchingLeaf.x! + scrollY
136
+ const y = matchingLeaf.x! + sy
135
137
  ctx.fillStyle = 'rgba(255,165,0,0.2)' // Orange highlight for MSA sync
136
- ctx.fillRect(0, y - rowHeight / 2, treeAreaWidth, rowHeight)
138
+ ctx.fillRect(0, y - rowHeight / 2, w, rowHeight)
137
139
  }
138
140
  }
139
141
  }
140
142
  })
141
143
  : undefined
142
- }, [model, treeAreaWidth, height, scrollY])
144
+ }, [model])
143
145
 
144
146
  function mouseDown(event: React.MouseEvent) {
145
147
  // check if clicking a draggable element or a resize handle
@@ -4,6 +4,7 @@ import { useTheme } from '@mui/material'
4
4
  import Flatbush from 'flatbush'
5
5
  import { autorun } from 'mobx'
6
6
  import { observer } from 'mobx-react'
7
+ import { makeStyles } from 'tss-react/mui'
7
8
 
8
9
  import TreeBranchMenu from './TreeBranchMenu.tsx'
9
10
  import TreeNodeMenu from './TreeNodeMenu.tsx'
@@ -11,6 +12,23 @@ import { padding, renderTreeCanvas } from './renderTreeCanvas.ts'
11
12
 
12
13
  import type { MsaViewModel } from '../../model.ts'
13
14
 
15
+ const useStyles = makeStyles()(theme => ({
16
+ tooltip: {
17
+ position: 'fixed',
18
+ pointerEvents: 'none',
19
+ zIndex: 10000,
20
+ backgroundColor: theme.palette.grey[700],
21
+ color: theme.palette.common.white,
22
+ padding: '4px 8px',
23
+ borderRadius: 4,
24
+ fontSize: 12,
25
+ whiteSpace: 'nowrap',
26
+ maxWidth: 300,
27
+ overflow: 'hidden',
28
+ textOverflow: 'ellipsis',
29
+ },
30
+ }))
31
+
14
32
  interface TooltipData {
15
33
  name: string
16
34
  id: string
@@ -75,6 +93,7 @@ const TreeCanvasBlock = observer(function ({
75
93
  model: MsaViewModel
76
94
  offsetY: number
77
95
  }) {
96
+ const { classes } = useStyles()
78
97
  const theme = useTheme()
79
98
  const ref = useRef<HTMLCanvasElement>(null)
80
99
  const clickMap = useRef(new ClickMapIndex())
@@ -82,6 +101,11 @@ const TreeCanvasBlock = observer(function ({
82
101
  const [branchMenu, setBranchMenu] = useState<TooltipData>()
83
102
  const [toggleNodeMenu, setToggleNodeMenu] = useState<TooltipData>()
84
103
  const [hoverElt, setHoverElt] = useState<ClickEntry>()
104
+ const [tooltipInfo, setTooltipInfo] = useState<{
105
+ name: string
106
+ x: number
107
+ y: number
108
+ }>()
85
109
 
86
110
  const { scrollY, treeAreaWidth, blockSize, highResScaleFactor } = model
87
111
 
@@ -217,6 +241,17 @@ const TreeCanvasBlock = observer(function ({
217
241
  ref.current.style.cursor = hoveredAny ? 'pointer' : 'default'
218
242
  setHoverElt(hoveredLeaf) // Only show direct hover highlight for leaf nodes
219
243
 
244
+ // Set tooltip info
245
+ if (hoveredAny) {
246
+ setTooltipInfo({
247
+ name: hoveredAny.name,
248
+ x: event.clientX,
249
+ y: event.clientY,
250
+ })
251
+ } else {
252
+ setTooltipInfo(undefined)
253
+ }
254
+
220
255
  // Handle tree node hover for multi-row highlighting
221
256
  if (hoveredAny) {
222
257
  model.setHoveredTreeNode(hoveredAny.id)
@@ -249,6 +284,7 @@ const TreeCanvasBlock = observer(function ({
249
284
  }}
250
285
  onMouseLeave={() => {
251
286
  setHoverElt(undefined)
287
+ setTooltipInfo(undefined)
252
288
  // Clear all highlighting when leaving tree area
253
289
  model.setHoveredTreeNode(undefined)
254
290
  model.setMousePos(undefined, undefined)
@@ -265,6 +301,14 @@ const TreeCanvasBlock = observer(function ({
265
301
  height={height}
266
302
  ref={mouseoverRef}
267
303
  />
304
+ {tooltipInfo ? (
305
+ <div
306
+ className={classes.tooltip}
307
+ style={{ left: tooltipInfo.x + 12, top: tooltipInfo.y + 12 }}
308
+ >
309
+ {tooltipInfo.name}
310
+ </div>
311
+ ) : null}
268
312
  </>
269
313
  )
270
314
  })
package/src/model.ts CHANGED
@@ -344,6 +344,11 @@ function stateModelFactory() {
344
344
  */
345
345
  minimapHeight: 56,
346
346
 
347
+ /**
348
+ * #volatile
349
+ */
350
+ conservationTrackHeight: 40,
351
+
347
352
  /**
348
353
  * #volatile
349
354
  */
@@ -1546,7 +1551,7 @@ function stateModelFactory() {
1546
1551
  model: {
1547
1552
  id: 'conservation',
1548
1553
  name: 'Conservation',
1549
- height: 40,
1554
+ height: self.conservationTrackHeight,
1550
1555
  },
1551
1556
  ReactComponent: ConservationTrack,
1552
1557
  }
@@ -1671,6 +1676,30 @@ function stateModelFactory() {
1671
1676
  }))
1672
1677
 
1673
1678
  .views(self => ({
1679
+ /**
1680
+ * #getter
1681
+ * Returns information about the currently hovered cell
1682
+ */
1683
+ get hoveredCell() {
1684
+ const { mouseCol, mouseRow } = self
1685
+ if (mouseCol === undefined || mouseRow === undefined) {
1686
+ return undefined
1687
+ }
1688
+ const rowName = self.rowNames[mouseRow]
1689
+ if (!rowName) {
1690
+ return undefined
1691
+ }
1692
+ const seq = self.columns[rowName]
1693
+ const base = seq?.[mouseCol]
1694
+ const seqPos = self.visibleColToSeqPosOneBased(rowName, mouseCol)
1695
+ return {
1696
+ rowName,
1697
+ col: mouseCol,
1698
+ base,
1699
+ seqPos,
1700
+ }
1701
+ },
1702
+
1674
1703
  /**
1675
1704
  * #getter
1676
1705
  * widget width minus the tree area gives the space for the MSA
@@ -1792,6 +1821,12 @@ function stateModelFactory() {
1792
1821
  setHeaderHeight(arg: number) {
1793
1822
  self.headerHeight = arg
1794
1823
  },
1824
+ /**
1825
+ * #action
1826
+ */
1827
+ setConservationTrackHeight(arg: number) {
1828
+ self.conservationTrackHeight = arg
1829
+ },
1795
1830
  /**
1796
1831
  * #action
1797
1832
  */
@@ -1,3 +1,4 @@
1
+ import { parseNewick } from 'msa-parsers'
1
2
  import { describe, expect, test } from 'vitest'
2
3
 
3
4
  import { calculateNeighborJoiningTree } from './neighborJoining.ts'
@@ -80,17 +81,24 @@ describe('calculateNeighborJoiningTree', () => {
80
81
  expect(tree).toContain('seq3')
81
82
  })
82
83
 
83
- test('handles special characters in sequence names', () => {
84
+ test('names with special characters round-trip through Newick parsing', () => {
84
85
  const rows: [string, string][] = [
85
- ['seq:1', 'MKAA'],
86
+ ['EU105457.1|chr09:67680268..67675529_LTR/Copia', 'MKAA'],
86
87
  ['seq(2)', 'MKAA'],
87
88
  ]
88
- const tree = calculateNeighborJoiningTree(rows)
89
+ const newick = calculateNeighborJoiningTree(rows)
90
+ const tree = parseNewick(newick)
89
91
 
90
- expect(tree).toMatch(/;$/)
91
- // Special characters should be escaped
92
- expect(tree).not.toMatch(/seq:1/)
93
- expect(tree).not.toMatch(/seq\(2\)/)
92
+ function getLeafNames(node: Record<string, unknown>): string[] {
93
+ const children = node.children as Record<string, unknown>[] | undefined
94
+ if (!children?.length) {
95
+ return [node.name as string]
96
+ }
97
+ return children.flatMap(c => getLeafNames(c))
98
+ }
99
+ const names = getLeafNames(tree)
100
+ expect(names).toContain('EU105457.1|chr09:67680268..67675529_LTR/Copia')
101
+ expect(names).toContain('seq(2)')
94
102
  })
95
103
 
96
104
  test('identical sequences have zero distance', () => {
@@ -851,11 +851,9 @@ function nodeToNewick(node: NJNode, branchLength?: number): string {
851
851
  let result: string
852
852
 
853
853
  if (node.name !== undefined && !node.left && !node.right) {
854
- // Leaf node - escape special characters in name
855
- const escapedName = node.name.replace(/[():,;[\]]/g, '_')
856
- result = escapedName
854
+ const quotedName = node.name.replaceAll("'", "''")
855
+ result = `'${quotedName}'`
857
856
  } else {
858
- // Internal node
859
857
  const leftNewick = node.left ? nodeToNewick(node.left, node.leftLength) : ''
860
858
  const rightNewick = node.right
861
859
  ? nodeToNewick(node.right, node.rightLength)
@@ -1,10 +1,10 @@
1
- import fs from 'fs'
1
+ import { readFileSync } from 'node:fs'
2
2
 
3
3
  import { expect, test } from 'vitest'
4
4
 
5
5
  import { parseAsn1 } from './parseAsn1.ts'
6
6
 
7
- const r = fs.readFileSync(require.resolve('../test/data/tree.asn'), 'utf8')
7
+ const r = readFileSync(new URL('../test/data/tree.asn', import.meta.url), 'utf8')
8
8
 
9
9
  test('real data file', () => {
10
10
  expect(parseAsn1(r)).toMatchSnapshot()
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const version = '5.0.5'
1
+ export const version = '5.0.7'
@@ -1 +0,0 @@
1
- export {};
@@ -1,49 +0,0 @@
1
- import { expect, test } from 'vitest';
2
- import { createPaletteMap } from "./createPaletteMap.js";
3
- import palettes from "./ggplotPalettes.js";
4
- // Original implementation for comparison
5
- function originalFillPalette(keys) {
6
- let i = 0;
7
- const map = {};
8
- for (const key of keys) {
9
- const k = Math.min(keys.length - 1, palettes.length - 1);
10
- map[key] = palettes[k][i];
11
- i++;
12
- }
13
- return map;
14
- }
15
- test('createPaletteMap matches original implementation with 1 key', () => {
16
- const keys = ['IPR001234'];
17
- expect(createPaletteMap(keys)).toEqual(originalFillPalette(keys));
18
- });
19
- test('createPaletteMap matches original implementation with 3 keys', () => {
20
- const keys = ['IPR001234', 'IPR005678', 'IPR009012'];
21
- expect(createPaletteMap(keys)).toEqual(originalFillPalette(keys));
22
- });
23
- test('createPaletteMap matches original implementation with 8 keys (max palette size)', () => {
24
- const keys = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'];
25
- expect(createPaletteMap(keys)).toEqual(originalFillPalette(keys));
26
- });
27
- test('createPaletteMap matches original implementation with more keys than palette colors', () => {
28
- const keys = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'];
29
- expect(createPaletteMap(keys)).toEqual(originalFillPalette(keys));
30
- });
31
- test('createPaletteMap returns correct colors for 3 keys', () => {
32
- const keys = ['X', 'Y', 'Z'];
33
- const result = createPaletteMap(keys);
34
- // With 3 keys, uses palette index 2 (0-indexed)
35
- expect(result).toEqual({
36
- X: '#F8766D',
37
- Y: '#00BA38',
38
- Z: '#619CFF',
39
- });
40
- });
41
- test('createPaletteMap returns empty object for empty keys', () => {
42
- expect(createPaletteMap([])).toEqual({});
43
- });
44
- test('createPaletteMap preserves key order', () => {
45
- const keys = ['Z', 'A', 'M'];
46
- const result = createPaletteMap(keys);
47
- expect(Object.keys(result)).toEqual(['Z', 'A', 'M']);
48
- });
49
- //# sourceMappingURL=createPaletteMap.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"createPaletteMap.test.js","sourceRoot":"","sources":["../src/createPaletteMap.test.ts"],"names":[],"mappings":"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"}
@@ -1 +0,0 @@
1
- export {};
@@ -1,110 +0,0 @@
1
- import { describe, expect, test } from 'vitest';
2
- import { calculateNeighborJoiningTree } from "./neighborJoining.js";
3
- describe('calculateNeighborJoiningTree', () => {
4
- test('generates valid Newick tree for 2 sequences', () => {
5
- const rows = [
6
- ['seq1', 'MKAA'],
7
- ['seq2', 'MKAA'],
8
- ];
9
- const tree = calculateNeighborJoiningTree(rows);
10
- expect(tree).toMatch(/;$/);
11
- expect(tree).toContain('seq1');
12
- expect(tree).toContain('seq2');
13
- });
14
- test('generates valid Newick tree for 3 sequences', () => {
15
- const rows = [
16
- ['human', 'MKAAYLSMFG'],
17
- ['mouse', 'MKAAYLSMFG'],
18
- ['chicken', 'MKAAFLSMFG'],
19
- ];
20
- const tree = calculateNeighborJoiningTree(rows);
21
- expect(tree).toMatch(/;$/);
22
- expect(tree).toContain('human');
23
- expect(tree).toContain('mouse');
24
- expect(tree).toContain('chicken');
25
- expect(tree).toContain('(');
26
- expect(tree).toContain(')');
27
- });
28
- test('generates valid Newick tree for 4 sequences', () => {
29
- const rows = [
30
- ['A', 'MKAAYLSMFGKED'],
31
- ['B', 'MKAAYLSMFGKED'],
32
- ['C', 'MKAAFLSMFGKEE'],
33
- ['D', 'MKAAFLSMFGKEE'],
34
- ];
35
- const tree = calculateNeighborJoiningTree(rows);
36
- expect(tree).toMatch(/;$/);
37
- expect(tree).toContain('A');
38
- expect(tree).toContain('B');
39
- expect(tree).toContain('C');
40
- expect(tree).toContain('D');
41
- });
42
- test('includes branch lengths in tree', () => {
43
- const rows = [
44
- ['seq1', 'MKAAYLSMFG'],
45
- ['seq2', 'MKAAFLSMFG'],
46
- ['seq3', 'MKBBFLSMFG'],
47
- ];
48
- const tree = calculateNeighborJoiningTree(rows);
49
- // Branch lengths are formatted as :0.123456
50
- expect(tree).toMatch(/:\d+\.\d+/);
51
- });
52
- test('throws error for less than 2 sequences', () => {
53
- const rows = [['seq1', 'MKAA']];
54
- expect(() => calculateNeighborJoiningTree(rows)).toThrow('Need at least 2 sequences');
55
- });
56
- test('handles sequences with gaps', () => {
57
- const rows = [
58
- ['seq1', 'MK-AYLSMFG'],
59
- ['seq2', 'MKAAYLSMFG'],
60
- ['seq3', 'MKA-YLSMFG'],
61
- ];
62
- const tree = calculateNeighborJoiningTree(rows);
63
- expect(tree).toMatch(/;$/);
64
- expect(tree).toContain('seq1');
65
- expect(tree).toContain('seq2');
66
- expect(tree).toContain('seq3');
67
- });
68
- test('handles special characters in sequence names', () => {
69
- const rows = [
70
- ['seq:1', 'MKAA'],
71
- ['seq(2)', 'MKAA'],
72
- ];
73
- const tree = calculateNeighborJoiningTree(rows);
74
- expect(tree).toMatch(/;$/);
75
- // Special characters should be escaped
76
- expect(tree).not.toMatch(/seq:1/);
77
- expect(tree).not.toMatch(/seq\(2\)/);
78
- });
79
- test('identical sequences have zero distance', () => {
80
- const rows = [
81
- ['seq1', 'MKAAYLSMFG'],
82
- ['seq2', 'MKAAYLSMFG'],
83
- ];
84
- const tree = calculateNeighborJoiningTree(rows);
85
- // Identical sequences should have equal branch lengths
86
- const match = tree.match(/:(\d+\.\d+)/g);
87
- expect(match).not.toBeNull();
88
- if (match) {
89
- const lengths = match.map(m => parseFloat(m.slice(1)));
90
- // Branch lengths should be small or zero for identical sequences
91
- for (const len of lengths) {
92
- expect(len).toBeGreaterThanOrEqual(0);
93
- }
94
- }
95
- });
96
- test('more divergent sequences have larger distances', () => {
97
- const rows = [
98
- ['similar1', 'MKAAYLSMFGKED'],
99
- ['similar2', 'MKAAYLSMFGKED'],
100
- ['different', 'WWWWWWWWWWWWW'],
101
- ];
102
- const tree = calculateNeighborJoiningTree(rows);
103
- expect(tree).toMatch(/;$/);
104
- // The tree should be valid even with very different sequences
105
- expect(tree).toContain('similar1');
106
- expect(tree).toContain('similar2');
107
- expect(tree).toContain('different');
108
- });
109
- });
110
- //# sourceMappingURL=neighborJoining.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"neighborJoining.test.js","sourceRoot":"","sources":["../src/neighborJoining.test.ts"],"names":[],"mappings":"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"}
@@ -1 +0,0 @@
1
- export {};
@@ -1,8 +0,0 @@
1
- import fs from 'fs';
2
- import { expect, test } from 'vitest';
3
- import { parseAsn1 } from "./parseAsn1.js";
4
- const r = fs.readFileSync(require.resolve('../test/data/tree.asn'), 'utf8');
5
- test('real data file', () => {
6
- expect(parseAsn1(r)).toMatchSnapshot();
7
- });
8
- //# sourceMappingURL=parseAsn1.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"parseAsn1.test.js","sourceRoot":"","sources":["../src/parseAsn1.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAA;AAEnB,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AAErC,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAE1C,MAAM,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,OAAO,CAAC,OAAO,CAAC,uBAAuB,CAAC,EAAE,MAAM,CAAC,CAAA;AAE3E,IAAI,CAAC,gBAAgB,EAAE,GAAG,EAAE;IAC1B,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,eAAe,EAAE,CAAA;AACxC,CAAC,CAAC,CAAA"}
@@ -1 +0,0 @@
1
- export {};