react-msaview 5.7.3 → 5.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/bundle/index.js +99 -99
  2. package/bundle/index.js.map +4 -4
  3. package/dist/components/SequenceTextArea.js +2 -2
  4. package/dist/components/SequenceTextArea.js.map +1 -1
  5. package/dist/components/dialogs/UserProvidedDomainsDialog.js +6 -1
  6. package/dist/components/dialogs/UserProvidedDomainsDialog.js.map +1 -1
  7. package/dist/components/header/getDomainsMenu.js +1 -1
  8. package/dist/components/header/getDomainsMenu.js.map +1 -1
  9. package/dist/components/import/ImportForm.js +7 -11
  10. package/dist/components/import/ImportForm.js.map +1 -1
  11. package/dist/components/import/ImportFormExamples.js +7 -11
  12. package/dist/components/import/ImportFormExamples.js.map +1 -1
  13. package/dist/components/import/util.d.ts +1 -1
  14. package/dist/components/import/util.js +1 -1
  15. package/dist/components/import/util.js.map +1 -1
  16. package/dist/components/msa/DomainLegend.js +4 -5
  17. package/dist/components/msa/DomainLegend.js.map +1 -1
  18. package/dist/components/msa/renderBoxFeatureCanvasBlock.js +2 -2
  19. package/dist/components/msa/renderBoxFeatureCanvasBlock.js.map +1 -1
  20. package/dist/components/tree/renderTreeCanvas.d.ts +9 -9
  21. package/dist/components/tree/renderTreeCanvas.js +36 -24
  22. package/dist/components/tree/renderTreeCanvas.js.map +1 -1
  23. package/dist/constants.d.ts +1 -0
  24. package/dist/constants.js +1 -0
  25. package/dist/constants.js.map +1 -1
  26. package/dist/flatToTree.d.ts +3 -1
  27. package/dist/flatToTree.js +16 -9
  28. package/dist/flatToTree.js.map +1 -1
  29. package/dist/hierarchy.d.ts +12 -17
  30. package/dist/hierarchy.js +12 -145
  31. package/dist/hierarchy.js.map +1 -1
  32. package/dist/model.d.ts +17 -1
  33. package/dist/model.js +34 -4
  34. package/dist/model.js.map +1 -1
  35. package/dist/neighborJoining.js +11 -2
  36. package/dist/neighborJoining.js.map +1 -1
  37. package/dist/version.d.ts +1 -1
  38. package/dist/version.js +1 -1
  39. package/package.json +4 -3
  40. package/src/colSpace.test.ts +40 -0
  41. package/src/components/SequenceTextArea.tsx +2 -2
  42. package/src/components/dialogs/UserProvidedDomainsDialog.tsx +8 -1
  43. package/src/components/header/getDomainsMenu.ts +1 -1
  44. package/src/components/import/ImportForm.tsx +6 -9
  45. package/src/components/import/ImportFormExamples.tsx +11 -14
  46. package/src/components/import/util.ts +1 -1
  47. package/src/components/msa/DomainLegend.tsx +4 -3
  48. package/src/components/msa/renderBoxFeatureCanvasBlock.ts +3 -2
  49. package/src/components/tree/renderTreeCanvas.test.ts +54 -1
  50. package/src/components/tree/renderTreeCanvas.ts +41 -28
  51. package/src/constants.ts +1 -0
  52. package/src/flatToTree.test.ts +21 -0
  53. package/src/flatToTree.ts +21 -10
  54. package/src/hierarchy.ts +44 -179
  55. package/src/largeTree.test.ts +32 -0
  56. package/src/model.ts +34 -3
  57. package/src/neighborJoining.test.ts +21 -6
  58. package/src/neighborJoining.ts +12 -2
  59. package/src/parseAsn1.test.ts +16 -0
  60. package/src/stripDefaultSnapshot.test.ts +30 -0
  61. package/src/version.ts +1 -1
package/src/flatToTree.ts CHANGED
@@ -1,26 +1,37 @@
1
1
  interface FlatItem {
2
2
  id: number
3
3
  parent?: number
4
+ // NCBI BioTreeContainer features, already un-remapped by parseAsn1. Both are
5
+ // strings there: the ASN.1 encodes every feature value as one.
6
+ label?: string
7
+ dist?: string
4
8
  }
5
9
 
6
10
  interface TreeNode {
7
11
  id: string
8
12
  name: string
9
- parent?: string
13
+ length?: number
10
14
  children: TreeNode[]
11
15
  }
12
16
 
13
17
  export function flatToTree(items: FlatItem[]): TreeNode {
14
18
  const nodeMap = new Map(
15
- items.map(item => [
16
- item.id,
17
- {
18
- id: `${item.id}`,
19
- name: `${item.id}`,
20
- parent: item.parent !== undefined ? `${item.parent}` : undefined,
21
- children: [] as TreeNode[],
22
- },
23
- ]),
19
+ items.map(item => {
20
+ const length = item.dist === undefined ? undefined : Number(item.dist)
21
+ return [
22
+ item.id,
23
+ {
24
+ id: `${item.id}`,
25
+ // the label is what NCBI's own tree viewer shows; falling back to the
26
+ // node id would render a tree of bare numbers
27
+ name: item.label || `${item.id}`,
28
+ ...(length !== undefined && Number.isFinite(length)
29
+ ? { length }
30
+ : {}),
31
+ children: [] as TreeNode[],
32
+ },
33
+ ]
34
+ }),
24
35
  )
25
36
 
26
37
  let root: TreeNode | undefined
package/src/hierarchy.ts CHANGED
@@ -1,12 +1,28 @@
1
- import type { NodeWithIds } from './types.ts'
1
+ import {
2
+ descendants,
3
+ find,
4
+ forEachDescendant,
5
+ forEachLink,
6
+ hierarchy as coreHierarchy,
7
+ leaves,
8
+ links,
9
+ sort,
10
+ sum,
11
+ } from '@gmod/newick'
2
12
 
3
- export interface HierarchyNode<T = NodeWithIds> {
4
- data: T
13
+ import type { NodeWithIds } from './types.ts'
14
+ import type { HierarchyNode as CoreHierarchyNode } from '@gmod/newick'
15
+
16
+ /**
17
+ * A hierarchy node plus the fields this viewer hangs off it during layout.
18
+ *
19
+ * The traversals in `@gmod/newick` are generic over the node type rather than
20
+ * over its data, so they take and return this extended node rather than the base
21
+ * one, and no call site has to cast.
22
+ */
23
+ export interface HierarchyNode<T = NodeWithIds> extends CoreHierarchyNode<T> {
5
24
  children: HierarchyNode<T>[] | null
6
25
  parent: HierarchyNode<T> | null
7
- depth: number
8
- height: number
9
- value?: number
10
26
  x?: number
11
27
  y?: number
12
28
  len?: number
@@ -18,183 +34,32 @@ export interface HierarchyNode<T = NodeWithIds> {
18
34
  collapsedTipXFar?: number
19
35
  }
20
36
 
21
- export interface HierarchyLink<T = NodeWithIds> {
22
- source: HierarchyNode<T>
23
- target: HierarchyNode<T>
24
- }
25
-
26
- // All traversals below are iterative (explicit stack / reversed-preorder) rather
27
- // than recursive: phylogenetic trees can be deeply unbalanced (a caterpillar tree
28
- // has depth ~= leaf count), which overflows the JS call stack on recursion.
29
-
30
- // Pre-order: every parent precedes all of its descendants. Iterating the result
31
- // in reverse therefore yields a valid post-order (children before parents), which
32
- // the accumulation helpers rely on.
33
- export function descendants<T>(node: HierarchyNode<T>): HierarchyNode<T>[] {
34
- const result: HierarchyNode<T>[] = []
35
- const stack = [node]
36
- while (stack.length > 0) {
37
- const n = stack.pop()!
38
- result.push(n)
39
- if (n.children) {
40
- for (let i = n.children.length - 1; i >= 0; i--) {
41
- stack.push(n.children[i]!)
42
- }
43
- }
44
- }
45
- return result
46
- }
47
-
48
- function computeHeight<T>(node: HierarchyNode<T>) {
49
- const nodes = descendants(node)
50
- for (let i = nodes.length - 1; i >= 0; i--) {
51
- const n = nodes[i]!
52
- let h = 0
53
- if (n.children) {
54
- for (const child of n.children) {
55
- if (child.height + 1 > h) {
56
- h = child.height + 1
57
- }
58
- }
59
- }
60
- n.height = h
61
- }
62
- }
63
-
37
+ // The generic traversals live in @gmod/newick now, shared with the tree sidebar
38
+ // in jbrowse-components. They are iterative there for the reason they were here:
39
+ // a phylogenetic tree can be a caterpillar, whose depth equals its leaf count,
40
+ // and the recursive form overflows the stack somewhere past 5000 tips.
41
+ export {
42
+ descendants,
43
+ find,
44
+ forEachDescendant,
45
+ forEachLink,
46
+ leaves,
47
+ links,
48
+ sort,
49
+ sum,
50
+ }
51
+ export type { HierarchyLink } from '@gmod/newick'
52
+
53
+ // coreHierarchy builds base nodes, and the layout fields above are written onto
54
+ // them afterwards by this package. Every one of those fields is optional, so the
55
+ // two node types are mutually assignable and this needs no cast -- it exists
56
+ // only to declare the wider return type, which is what lets those later
57
+ // assignments typecheck.
64
58
  export function hierarchy<T>(
65
59
  data: T,
66
60
  childrenAccessor: (d: T) => T[] | undefined,
67
61
  ): HierarchyNode<T> {
68
- const root: HierarchyNode<T> = {
69
- data,
70
- children: null,
71
- parent: null,
72
- depth: 0,
73
- height: 0,
74
- }
75
- const stack = [root]
76
- while (stack.length > 0) {
77
- const node = stack.pop()!
78
- const kids = childrenAccessor(node.data)
79
- if (kids?.length) {
80
- node.children = kids.map(d => ({
81
- data: d,
82
- children: null,
83
- parent: node,
84
- depth: node.depth + 1,
85
- height: 0,
86
- }))
87
- for (const child of node.children) {
88
- stack.push(child)
89
- }
90
- }
91
- }
92
- computeHeight(root)
93
- return root
94
- }
95
-
96
- export function sum<T>(
97
- node: HierarchyNode<T>,
98
- valueFn: (d: T) => number,
99
- ): HierarchyNode<T> {
100
- const nodes = descendants(node)
101
- for (let i = nodes.length - 1; i >= 0; i--) {
102
- const n = nodes[i]!
103
- let s = valueFn(n.data)
104
- if (n.children) {
105
- for (const child of n.children) {
106
- s += child.value!
107
- }
108
- }
109
- n.value = s
110
- }
111
- return node
112
- }
113
-
114
- export function sort<T>(
115
- node: HierarchyNode<T>,
116
- compareFn: (a: HierarchyNode<T>, b: HierarchyNode<T>) => number,
117
- ): HierarchyNode<T> {
118
- const stack = [node]
119
- while (stack.length > 0) {
120
- const n = stack.pop()!
121
- if (n.children) {
122
- n.children.sort(compareFn)
123
- for (const child of n.children) {
124
- stack.push(child)
125
- }
126
- }
127
- }
128
- return node
129
- }
130
-
131
- export function find<T>(
132
- node: HierarchyNode<T>,
133
- predicate: (n: HierarchyNode<T>) => boolean,
134
- ): HierarchyNode<T> | undefined {
135
- const stack = [node]
136
- let found: HierarchyNode<T> | undefined
137
- while (stack.length > 0 && found === undefined) {
138
- const n = stack.pop()!
139
- if (predicate(n)) {
140
- found = n
141
- } else if (n.children) {
142
- for (let i = n.children.length - 1; i >= 0; i--) {
143
- stack.push(n.children[i]!)
144
- }
145
- }
146
- }
147
- return found
148
- }
149
-
150
- export function leaves<T>(node: HierarchyNode<T>): HierarchyNode<T>[] {
151
- const result: HierarchyNode<T>[] = []
152
- const stack = [node]
153
- while (stack.length > 0) {
154
- const n = stack.pop()!
155
- if (n.children) {
156
- for (let i = n.children.length - 1; i >= 0; i--) {
157
- stack.push(n.children[i]!)
158
- }
159
- } else {
160
- result.push(n)
161
- }
162
- }
163
- return result
164
- }
165
-
166
- export function links<T>(node: HierarchyNode<T>): HierarchyLink<T>[] {
167
- const result: HierarchyLink<T>[] = []
168
- forEachLink(node, (source, target) => {
169
- result.push({ source, target })
170
- })
171
- return result
172
- }
173
-
174
- export function forEachLink<T>(
175
- node: HierarchyNode<T>,
176
- cb: (source: HierarchyNode<T>, target: HierarchyNode<T>) => void,
177
- ) {
178
- const stack = [node]
179
- while (stack.length > 0) {
180
- const n = stack.pop()!
181
- if (n.children) {
182
- for (let i = n.children.length - 1; i >= 0; i--) {
183
- const child = n.children[i]!
184
- cb(n, child)
185
- stack.push(child)
186
- }
187
- }
188
- }
189
- }
190
-
191
- export function forEachDescendant<T>(
192
- node: HierarchyNode<T>,
193
- cb: (n: HierarchyNode<T>) => void,
194
- ) {
195
- for (const n of descendants(node)) {
196
- cb(n)
197
- }
62
+ return coreHierarchy(data, childrenAccessor)
198
63
  }
199
64
 
200
65
  export function clusterLayout<T>(
@@ -0,0 +1,32 @@
1
+ // @vitest-environment jsdom
2
+ import { expect, test } from 'vitest'
3
+
4
+ import MSAModelF from './model.ts'
5
+
6
+ // jsdom has no 2d context, and measureTextCanvas throws without one. Only the
7
+ // width matters here, so a stub keeps the test about tree size.
8
+ HTMLCanvasElement.prototype.getContext = (() => ({
9
+ font: '12px sans-serif',
10
+ measureText: (t: string) => ({ width: t.length * 7 }),
11
+ })) as unknown as typeof HTMLCanvasElement.prototype.getContext
12
+
13
+ // The import form ships a "230k COVID-19 samples (tree only)" example, so a tree
14
+ // this size is a supported input rather than a hypothetical. Every leaf gets a
15
+ // measured label, and anything that then reduces over them per row has to do it
16
+ // without passing one argument per row.
17
+ function flatNewick(n: number) {
18
+ return `(${Array.from({ length: n }, (_, i) => `s${i}:0.1`).join(',')});`
19
+ }
20
+
21
+ test('a tree far past the argument limit still lays out', () => {
22
+ const model = MSAModelF().create({
23
+ type: 'MsaView',
24
+ data: { tree: flatNewick(200_000) },
25
+ })
26
+ model.setWidth(1000)
27
+
28
+ expect(model.numRows).toBe(200_000)
29
+ // 's199999' is the longest name, at 7 chars * 7px
30
+ expect(model.labelsWidth).toBe(49)
31
+ expect(model.treeWidth).toBeGreaterThan(0)
32
+ })
package/src/model.ts CHANGED
@@ -31,6 +31,7 @@ import {
31
31
  defaultScrollX,
32
32
  defaultScrollY,
33
33
  defaultScrollZoom,
34
+ defaultShowDomainLegend,
34
35
  defaultShowDomains,
35
36
  defaultSubFeatureRows,
36
37
  maxCellSize,
@@ -132,6 +133,15 @@ function stateModelFactory() {
132
133
  * #property
133
134
  */
134
135
  showDomains: stripDefault(types.boolean, defaultShowDomains),
136
+ /**
137
+ * #property
138
+ * whether the domain legend is expanded. The legend floats over the
139
+ * top-right of the alignment, so on a tall panel it covers real
140
+ * residues -- persisting the state is what lets a reader collapse it
141
+ * and keep it collapsed, and what lets a session or a figure open with
142
+ * it already out of the way.
143
+ */
144
+ showDomainLegend: stripDefault(types.boolean, defaultShowDomainLegend),
135
145
  /**
136
146
  * #property
137
147
  */
@@ -475,6 +485,13 @@ function stateModelFactory() {
475
485
  setShowDomains(arg: boolean) {
476
486
  self.showDomains = arg
477
487
  },
488
+ /**
489
+ * #action
490
+ * expand or collapse the domain legend that floats over the alignment
491
+ */
492
+ setShowDomainLegend(arg: boolean) {
493
+ self.showDomainLegend = arg
494
+ },
478
495
  /**
479
496
  * #action
480
497
  */
@@ -1438,15 +1455,28 @@ function stateModelFactory() {
1438
1455
  : new Map(
1439
1456
  leaves.map(node => {
1440
1457
  const { name } = node.data
1441
- const displayName = treeMetadata[name]?.genome ?? name
1458
+ // `||`, matching renderTreeLabels: an empty genome falls back
1459
+ // to the row name, and measuring '' would size the gutter (and
1460
+ // the label's click target) to nothing
1461
+ const displayName = treeMetadata[name]?.genome || name
1442
1462
  return [name, measureTextCanvas(displayName, fontSize)] as const
1443
1463
  }),
1444
1464
  )
1445
1465
  },
1446
1466
 
1447
1467
  get labelsWidth() {
1448
- const widths = this.labelWidthMap
1449
- return widths.size === 0 ? 0 : Math.max(...widths.values())
1468
+ // a loop, not Math.max(...widths.values()): spreading a map of every
1469
+ // leaf passes one argument per row, and the argument limit is somewhere
1470
+ // around 125k -- so the bundled 230k-tip COVID tree threw
1471
+ // "RangeError: Maximum call stack size exceeded" out of a getter the
1472
+ // treeWidth autorun reads on load
1473
+ let max = 0
1474
+ for (const width of this.labelWidthMap.values()) {
1475
+ if (width > max) {
1476
+ max = width
1477
+ }
1478
+ }
1479
+ return max
1450
1480
  },
1451
1481
 
1452
1482
  /**
@@ -2298,6 +2328,7 @@ function stateModelFactory() {
2298
2328
  ...(rest.treeMetadataFilehandle
2299
2329
  ? {}
2300
2330
  : { treeMetadata: data.treeMetadata }),
2331
+ ...(rest.gffFilehandle ? {} : { gff: data.gff }),
2301
2332
  },
2302
2333
  }))
2303
2334
  }
@@ -3,6 +3,8 @@ import { describe, expect, test } from 'vitest'
3
3
 
4
4
  import { calculateNeighborJoiningTree } from './neighborJoining.ts'
5
5
 
6
+ import type { NewickNode } from '@gmod/newick'
7
+
6
8
  describe('calculateNeighborJoiningTree', () => {
7
9
  test('generates valid Newick tree for 2 sequences', () => {
8
10
  const rows: [string, string][] = [
@@ -81,6 +83,21 @@ describe('calculateNeighborJoiningTree', () => {
81
83
  expect(tree).toContain('seq3')
82
84
  })
83
85
 
86
+ test('treats a dot gap the same as a dash gap', () => {
87
+ // stockholm and a3m pad with '.', so the two spellings have to score alike
88
+ const withDashes: [string, string][] = [
89
+ ['seq1', 'MK-AYLSMFG'],
90
+ ['seq2', 'MKAAYLSMFG'],
91
+ ['seq3', 'MKA-YLSMFG'],
92
+ ]
93
+ const withDots = withDashes.map(
94
+ ([name, seq]) => [name, seq.replaceAll('-', '.')] as [string, string],
95
+ )
96
+ expect(calculateNeighborJoiningTree(withDots)).toBe(
97
+ calculateNeighborJoiningTree(withDashes),
98
+ )
99
+ })
100
+
84
101
  test('names with special characters round-trip through Newick parsing', () => {
85
102
  const rows: [string, string][] = [
86
103
  ['EU105457.1|chr09:67680268..67675529_LTR/Copia', 'MKAA'],
@@ -89,12 +106,10 @@ describe('calculateNeighborJoiningTree', () => {
89
106
  const newick = calculateNeighborJoiningTree(rows)
90
107
  const tree = parseNewick(newick)
91
108
 
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))
109
+ function getLeafNames(node: NewickNode): (string | undefined)[] {
110
+ return node.children?.length
111
+ ? node.children.flatMap(c => getLeafNames(c))
112
+ : [node.name]
98
113
  }
99
114
  const names = getLeafNames(tree)
100
115
  expect(names).toContain('EU105457.1|chr09:67680268..67675529_LTR/Copia')
@@ -44,6 +44,14 @@ function getBlosum62Score(a: string, b: string) {
44
44
  return BLOSUM62.get(a.toUpperCase())?.get(b.toUpperCase()) ?? -4
45
45
  }
46
46
 
47
+ // stockholm and a3m write gaps as '.', not just '-'. Scoring a '.' as a residue
48
+ // charges the pair the unknown-symbol penalty on every padded column, so two
49
+ // sequences that differ only in where an unrelated row forced padding come out
50
+ // as divergent.
51
+ function isGap(c: string) {
52
+ return c === '-' || c === '.'
53
+ }
54
+
47
55
  function computePairwiseDistance(seq1: string, seq2: string) {
48
56
  if (seq1.length !== seq2.length) {
49
57
  throw new Error('Sequences must have the same length (aligned)')
@@ -58,11 +66,13 @@ function computePairwiseDistance(seq1: string, seq2: string) {
58
66
  const a = seq1[i]!
59
67
  const b = seq2[i]!
60
68
 
61
- if (a === '-' && b === '-') {
69
+ const aGap = isGap(a)
70
+ const bGap = isGap(b)
71
+ if (aGap && bGap) {
62
72
  continue
63
73
  }
64
74
 
65
- if (a === '-' || b === '-') {
75
+ if (aGap || bGap) {
66
76
  mismatches++
67
77
  continue
68
78
  }
@@ -2,6 +2,8 @@ import { readFileSync } from 'node:fs'
2
2
 
3
3
  import { expect, test } from 'vitest'
4
4
 
5
+ import { flatToTree } from './flatToTree.ts'
6
+ import MSAModelF from './model.ts'
5
7
  import { parseAsn1 } from './parseAsn1.ts'
6
8
 
7
9
  const r = readFileSync(
@@ -18,3 +20,17 @@ test('throws a clear error on input missing required sections', () => {
18
20
  /missing/,
19
21
  )
20
22
  })
23
+
24
+ test('the tree keeps the labels and branch lengths the file carries', () => {
25
+ // the parsed features have to survive into the tree the viewer draws, or a
26
+ // BLAST tree renders as a lengthless list of node ids
27
+ const model = MSAModelF().create({ type: 'MsaView', data: { tree: r } })
28
+ model.setWidth(1000)
29
+
30
+ expect(model.numRows).toBe(101)
31
+ expect(model.rowNames[0]).toBe(
32
+ 'sodium/glucose cotransporter 4 [Gouania willdenowi]',
33
+ )
34
+ expect(model.allBranchesLength0).toBe(false)
35
+ expect(flatToTree(parseAsn1(r)).length).toBeUndefined()
36
+ })
@@ -55,3 +55,33 @@ test('inline data is stripped when a filehandle can refetch it', () => {
55
55
  expect(snap.data.msa).toBeUndefined()
56
56
  expect(snap.data.tree).toBe('(a,b);')
57
57
  })
58
+
59
+ test('inline gff survives the snapshot when no gffFilehandle can refetch it', () => {
60
+ const gff = 's1\tx\tprotein_match\t1\t3\t.\t.\t.\tName=PF1'
61
+ const withInlineGff = MsaView.create({
62
+ type: 'MsaView',
63
+ data: { msa: '>s1\nACGT', gff },
64
+ })
65
+ expect(getSnapshot(withInlineGff).data.gff).toBe(gff)
66
+
67
+ const withFilehandle = MsaView.create({
68
+ type: 'MsaView',
69
+ data: { msa: '>s1\nACGT', gff },
70
+ gffFilehandle: {
71
+ uri: 'http://example.com/x.gff',
72
+ locationType: 'UriLocation',
73
+ },
74
+ })
75
+ expect(getSnapshot(withFilehandle).data.gff).toBeUndefined()
76
+ })
77
+
78
+ // The legend floats over the top-right of the alignment, so collapsing it is a
79
+ // way of seeing the residues under it. As component state that choice was lost
80
+ // on every reload and could not be set by a session or a figure at all.
81
+ test('a collapsed domain legend survives into the snapshot', () => {
82
+ const model = MsaView.create({ type: 'MsaView' })
83
+ expect(getSnapshot(model).showDomainLegend).toBeUndefined()
84
+ model.setShowDomainLegend(false)
85
+ expect(getSnapshot(model).showDomainLegend).toBe(false)
86
+ expect(MsaView.create(getSnapshot(model)).showDomainLegend).toBe(false)
87
+ })
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const version = '5.7.3'
1
+ export const version = '5.9.0'