react-msaview 5.7.3 → 5.8.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/bundle/index.js +91 -91
- package/bundle/index.js.map +4 -4
- package/dist/components/SequenceTextArea.js +2 -2
- package/dist/components/SequenceTextArea.js.map +1 -1
- package/dist/components/dialogs/UserProvidedDomainsDialog.js +6 -1
- package/dist/components/dialogs/UserProvidedDomainsDialog.js.map +1 -1
- package/dist/components/header/getDomainsMenu.js +1 -1
- package/dist/components/header/getDomainsMenu.js.map +1 -1
- package/dist/components/import/ImportForm.js +7 -11
- package/dist/components/import/ImportForm.js.map +1 -1
- package/dist/components/import/ImportFormExamples.js +7 -11
- package/dist/components/import/ImportFormExamples.js.map +1 -1
- package/dist/components/import/util.d.ts +1 -1
- package/dist/components/import/util.js +1 -1
- package/dist/components/import/util.js.map +1 -1
- package/dist/components/msa/renderBoxFeatureCanvasBlock.js +2 -2
- package/dist/components/msa/renderBoxFeatureCanvasBlock.js.map +1 -1
- package/dist/components/tree/renderTreeCanvas.d.ts +9 -9
- package/dist/components/tree/renderTreeCanvas.js +36 -24
- package/dist/components/tree/renderTreeCanvas.js.map +1 -1
- package/dist/flatToTree.d.ts +3 -1
- package/dist/flatToTree.js +16 -9
- package/dist/flatToTree.js.map +1 -1
- package/dist/hierarchy.d.ts +12 -17
- package/dist/hierarchy.js +12 -145
- package/dist/hierarchy.js.map +1 -1
- package/dist/model.d.ts +1 -0
- package/dist/model.js +17 -3
- package/dist/model.js.map +1 -1
- package/dist/neighborJoining.js +11 -2
- package/dist/neighborJoining.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +4 -3
- package/src/colSpace.test.ts +40 -0
- package/src/components/SequenceTextArea.tsx +2 -2
- package/src/components/dialogs/UserProvidedDomainsDialog.tsx +8 -1
- package/src/components/header/getDomainsMenu.ts +1 -1
- package/src/components/import/ImportForm.tsx +6 -9
- package/src/components/import/ImportFormExamples.tsx +11 -14
- package/src/components/import/util.ts +1 -1
- package/src/components/msa/renderBoxFeatureCanvasBlock.ts +3 -2
- package/src/components/tree/renderTreeCanvas.test.ts +54 -1
- package/src/components/tree/renderTreeCanvas.ts +41 -28
- package/src/flatToTree.test.ts +21 -0
- package/src/flatToTree.ts +21 -10
- package/src/hierarchy.ts +44 -179
- package/src/largeTree.test.ts +32 -0
- package/src/model.ts +17 -3
- package/src/neighborJoining.test.ts +21 -6
- package/src/neighborJoining.ts +12 -2
- package/src/parseAsn1.test.ts +16 -0
- package/src/stripDefaultSnapshot.test.ts +19 -0
- package/src/version.ts +1 -1
package/src/hierarchy.ts
CHANGED
|
@@ -1,12 +1,28 @@
|
|
|
1
|
-
import
|
|
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
|
-
|
|
4
|
-
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
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
|
@@ -1438,15 +1438,28 @@ function stateModelFactory() {
|
|
|
1438
1438
|
: new Map(
|
|
1439
1439
|
leaves.map(node => {
|
|
1440
1440
|
const { name } = node.data
|
|
1441
|
-
|
|
1441
|
+
// `||`, matching renderTreeLabels: an empty genome falls back
|
|
1442
|
+
// to the row name, and measuring '' would size the gutter (and
|
|
1443
|
+
// the label's click target) to nothing
|
|
1444
|
+
const displayName = treeMetadata[name]?.genome || name
|
|
1442
1445
|
return [name, measureTextCanvas(displayName, fontSize)] as const
|
|
1443
1446
|
}),
|
|
1444
1447
|
)
|
|
1445
1448
|
},
|
|
1446
1449
|
|
|
1447
1450
|
get labelsWidth() {
|
|
1448
|
-
|
|
1449
|
-
|
|
1451
|
+
// a loop, not Math.max(...widths.values()): spreading a map of every
|
|
1452
|
+
// leaf passes one argument per row, and the argument limit is somewhere
|
|
1453
|
+
// around 125k -- so the bundled 230k-tip COVID tree threw
|
|
1454
|
+
// "RangeError: Maximum call stack size exceeded" out of a getter the
|
|
1455
|
+
// treeWidth autorun reads on load
|
|
1456
|
+
let max = 0
|
|
1457
|
+
for (const width of this.labelWidthMap.values()) {
|
|
1458
|
+
if (width > max) {
|
|
1459
|
+
max = width
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
return max
|
|
1450
1463
|
},
|
|
1451
1464
|
|
|
1452
1465
|
/**
|
|
@@ -2298,6 +2311,7 @@ function stateModelFactory() {
|
|
|
2298
2311
|
...(rest.treeMetadataFilehandle
|
|
2299
2312
|
? {}
|
|
2300
2313
|
: { treeMetadata: data.treeMetadata }),
|
|
2314
|
+
...(rest.gffFilehandle ? {} : { gff: data.gff }),
|
|
2301
2315
|
},
|
|
2302
2316
|
}))
|
|
2303
2317
|
}
|
|
@@ -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:
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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')
|
package/src/neighborJoining.ts
CHANGED
|
@@ -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
|
-
|
|
69
|
+
const aGap = isGap(a)
|
|
70
|
+
const bGap = isGap(b)
|
|
71
|
+
if (aGap && bGap) {
|
|
62
72
|
continue
|
|
63
73
|
}
|
|
64
74
|
|
|
65
|
-
if (
|
|
75
|
+
if (aGap || bGap) {
|
|
66
76
|
mismatches++
|
|
67
77
|
continue
|
|
68
78
|
}
|
package/src/parseAsn1.test.ts
CHANGED
|
@@ -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,22 @@ 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
|
+
})
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '5.
|
|
1
|
+
export const version = '5.8.0'
|