Skip to content

Commit 5ae5bc2

Browse files
committed
Updates
1 parent d173e9e commit 5ae5bc2

File tree

10 files changed

+261
-588
lines changed

10 files changed

+261
-588
lines changed

packages/core/scripts/generateExports.mjs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ const repoRoot = join(packageRoot, '../..')
99
const srcDir = join(packageRoot, 'src')
1010

1111
// Exports to keep even if not used internally (for backwards compatibility)
12-
const preservedExports = ['@jbrowse/core/util/nanoid']
12+
const preservedExports = [
13+
'@jbrowse/core/util/nanoid',
14+
'@jbrowse/core/ReExports/list',
15+
]
1316

1417
// Scan the codebase for all @jbrowse/core imports
1518
function findAllImports() {
Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
/**
2+
* Tests for vendored IntervalTree
3+
* Based on tests from @flatten-js/interval-tree
4+
*/
5+
6+
import { IntervalTree } from './IntervalTree.ts'
7+
8+
describe('IntervalTree', () => {
9+
it('creates new instance', () => {
10+
const tree = new IntervalTree()
11+
expect(tree).toBeInstanceOf(IntervalTree)
12+
})
13+
14+
it('inserts one entry with numeric tuple key', () => {
15+
const tree = new IntervalTree<string>()
16+
tree.insert([1, 2], 'val')
17+
expect(tree.search([1, 2])).toEqual(['val'])
18+
})
19+
20+
it('inserts many entries', () => {
21+
const tree = new IntervalTree<string>()
22+
const intervals: [number, number][] = [
23+
[6, 8],
24+
[1, 4],
25+
[5, 12],
26+
[1, 1],
27+
[5, 7],
28+
]
29+
for (let i = 0, l = intervals.length; i < l; i++) {
30+
tree.insert(intervals[i]!, `val${i}`)
31+
}
32+
// [1,1] intersects both [1,1] and [1,4]
33+
expect(tree.search([1, 1])).toContain('val3')
34+
expect(tree.search([1, 1])).toContain('val1')
35+
expect(tree.search([6, 8])).toContain('val0')
36+
})
37+
38+
it('searches interval and returns array of values', () => {
39+
const tree = new IntervalTree<string>()
40+
const intervals: [number, number][] = [
41+
[6, 8],
42+
[1, 4],
43+
[5, 12],
44+
[1, 1],
45+
[5, 7],
46+
]
47+
for (let i = 0, l = intervals.length; i < l; i++) {
48+
tree.insert(intervals[i]!, `val${i}`)
49+
}
50+
expect(tree.search([2, 3])).toEqual(['val1'])
51+
})
52+
53+
it('returns empty array when search interval does not intersect any', () => {
54+
const tree = new IntervalTree<string>()
55+
const intervals: [number, number][] = [
56+
[6, 8],
57+
[1, 2],
58+
[7, 12],
59+
[1, 1],
60+
[5, 7],
61+
]
62+
for (let i = 0, l = intervals.length; i < l; i++) {
63+
tree.insert(intervals[i]!, `val${i}`)
64+
}
65+
expect(tree.search([3, 4])).toEqual([])
66+
})
67+
68+
it('buckets multiple values for identical keys', () => {
69+
const tree = new IntervalTree<number>()
70+
tree.insert([2, 5], 10)
71+
tree.insert([2, 5], 20)
72+
tree.insert([2, 5], 30)
73+
tree.insert([2, 5], 40)
74+
tree.insert([2, 5], 50)
75+
76+
const results = tree.search([2, 5])
77+
expect(results).toContain(10)
78+
expect(results).toContain(20)
79+
expect(results).toContain(30)
80+
expect(results).toContain(40)
81+
expect(results).toContain(50)
82+
expect(results).toHaveLength(5)
83+
})
84+
85+
it('handles storing 0 as a value', () => {
86+
const tree = new IntervalTree<number>()
87+
tree.insert([0, 0], 0)
88+
tree.insert([0, 0], 1)
89+
90+
const results = tree.search([0, 0])
91+
expect(results).toEqual([0, 1])
92+
})
93+
94+
it('stores falsy values: 0, false, NaN, null', () => {
95+
const tree = new IntervalTree<number | boolean | null>()
96+
tree.insert([0, 0], 0)
97+
tree.insert([0, 0], false)
98+
tree.insert([0, 0], NaN)
99+
tree.insert([0, 0], null)
100+
101+
const results = tree.search([0, 0])
102+
expect(results).toHaveLength(4)
103+
expect(results).toContain(0)
104+
expect(results).toContain(false)
105+
expect(results).toContain(null)
106+
expect(results.some(v => Number.isNaN(v))).toBe(true)
107+
})
108+
109+
it('stores custom objects', () => {
110+
interface Item {
111+
name: string
112+
value: number
113+
}
114+
const tree = new IntervalTree<Item>()
115+
const data: Item[] = [
116+
{ name: 'A', value: 111 },
117+
{ name: 'B', value: 333 },
118+
{ name: 'C', value: 222 },
119+
]
120+
121+
tree.insert([2, 5], data[0]!)
122+
tree.insert([2, 5], data[1]!)
123+
tree.insert([2, 5], data[2]!)
124+
125+
const results = tree.search([2, 5])
126+
expect(results).toContain(data[0])
127+
expect(results).toContain(data[1])
128+
expect(results).toContain(data[2])
129+
})
130+
131+
it('normalizes reversed intervals', () => {
132+
const tree = new IntervalTree<string>()
133+
tree.insert([8, 2], 'reversed')
134+
tree.insert([2, 8], 'normal')
135+
136+
// Both should be stored under [2, 8] and retrieved together
137+
const results = tree.search([3, 5])
138+
expect(results).toContain('reversed')
139+
expect(results).toContain('normal')
140+
})
141+
142+
it('handles overlapping intervals correctly', () => {
143+
const tree = new IntervalTree<string>()
144+
tree.insert([1, 5], 'a')
145+
tree.insert([3, 8], 'b')
146+
tree.insert([6, 10], 'c')
147+
tree.insert([12, 15], 'd')
148+
149+
expect(tree.search([4, 4])).toEqual(['a', 'b'])
150+
expect(tree.search([7, 7])).toEqual(['b', 'c'])
151+
expect(tree.search([11, 11])).toEqual([])
152+
expect(tree.search([1, 15])).toEqual(['a', 'b', 'c', 'd'])
153+
})
154+
155+
it('works with genomic-style intervals', () => {
156+
const tree = new IntervalTree<string>()
157+
tree.insert([1000, 2000], 'gene1')
158+
tree.insert([1500, 3000], 'gene2')
159+
tree.insert([5000, 6000], 'gene3')
160+
161+
expect(tree.search([1700, 1800])).toEqual(['gene1', 'gene2'])
162+
expect(tree.search([4000, 4500])).toEqual([])
163+
expect(tree.search([5500, 5600])).toEqual(['gene3'])
164+
})
165+
166+
it('returns empty array when searching empty tree', () => {
167+
const tree = new IntervalTree<string>()
168+
expect(tree.search([1, 10])).toEqual([])
169+
})
170+
171+
it('handles adjacent non-overlapping intervals', () => {
172+
const tree = new IntervalTree<string>()
173+
tree.insert([1, 5], 'a')
174+
tree.insert([6, 10], 'b')
175+
176+
expect(tree.search([5, 5])).toEqual(['a'])
177+
expect(tree.search([6, 6])).toEqual(['b'])
178+
// gap between them
179+
expect(tree.search([5.5, 5.5])).toEqual([])
180+
})
181+
182+
it('handles intervals that touch at exactly one point', () => {
183+
const tree = new IntervalTree<string>()
184+
tree.insert([1, 5], 'a')
185+
tree.insert([5, 10], 'b')
186+
187+
// point 5 is in both intervals
188+
expect(tree.search([5, 5])).toContain('a')
189+
expect(tree.search([5, 5])).toContain('b')
190+
})
191+
192+
it('handles negative coordinates', () => {
193+
const tree = new IntervalTree<string>()
194+
tree.insert([-10, -5], 'neg')
195+
tree.insert([-3, 3], 'span')
196+
tree.insert([5, 10], 'pos')
197+
198+
expect(tree.search([-7, -6])).toEqual(['neg'])
199+
expect(tree.search([0, 0])).toEqual(['span'])
200+
expect(tree.search([-20, 20])).toEqual(['neg', 'span', 'pos'])
201+
})
202+
203+
it('handles nested/containing intervals', () => {
204+
const tree = new IntervalTree<string>()
205+
tree.insert([1, 100], 'outer')
206+
tree.insert([25, 75], 'middle')
207+
tree.insert([40, 60], 'inner')
208+
209+
expect(tree.search([50, 50])).toContain('outer')
210+
expect(tree.search([50, 50])).toContain('middle')
211+
expect(tree.search([50, 50])).toContain('inner')
212+
expect(tree.search([10, 20])).toEqual(['outer'])
213+
})
214+
215+
it('handles large coordinates', () => {
216+
const tree = new IntervalTree<string>()
217+
tree.insert([100000000, 100001000], 'gene1')
218+
tree.insert([200000000, 200001000], 'gene2')
219+
220+
expect(tree.search([100000500, 100000600])).toEqual(['gene1'])
221+
expect(tree.search([150000000, 150000000])).toEqual([])
222+
})
223+
224+
it('handles many intervals (stress test)', () => {
225+
const tree = new IntervalTree<number>()
226+
// insert 1000 intervals
227+
for (let i = 0; i < 1000; i++) {
228+
tree.insert([i * 10, i * 10 + 5], i)
229+
}
230+
231+
expect(tree.search([500, 505])).toEqual([50])
232+
expect(tree.search([0, 10000])).toHaveLength(1000)
233+
})
234+
235+
it('handles single interval tree', () => {
236+
const tree = new IntervalTree<string>()
237+
tree.insert([5, 10], 'only')
238+
239+
expect(tree.search([1, 4])).toEqual([])
240+
expect(tree.search([7, 8])).toEqual(['only'])
241+
expect(tree.search([11, 15])).toEqual([])
242+
})
243+
})

packages/core/src/util/colord.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ function rgbToHsl(
183183
function parseColor(input: string | { h: number; s: number; l: number }): RGBA {
184184
if (typeof input === 'object') {
185185
const { h, s, l } = input
186-
return { ...hslToRgb(h, s * 100, l * 100), a: 1 }
186+
return { ...hslToRgb(h, s / 100, l / 100), a: 1 }
187187
}
188188

189189
const str = input.trim().toLowerCase()
@@ -248,7 +248,12 @@ function createColord(rgba: RGBA): Colord {
248248

249249
toHsl(): HSLA {
250250
const { h, s, l } = rgbToHsl(rgba.r, rgba.g, rgba.b)
251-
return { h: round(h, 1), s: round(s, 3), l: round(l, 3), a: rgba.a }
251+
return {
252+
h: round(h, 1),
253+
s: round(s * 100, 1),
254+
l: round(l * 100, 1),
255+
a: rgba.a,
256+
}
252257
},
253258

254259
toHslString(): string {

products/jbrowse-cli/bin/run

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
#!/usr/bin/env node
22

3-
require('../dist/index.js').main(process.argv.slice(2))
3+
import { main } from '../dist/index.js'
4+
main(process.argv.slice(2))

products/jbrowse-cli/tsconfig.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
{
22
"compilerOptions": {
3-
"module": "commonjs",
43
"outDir": "dist",
54
"rootDir": "src",
65
"strict": true,

0 commit comments

Comments
 (0)