jbrowse-plugin-graphgenomeviewer 7.4.3 → 7.5.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/README.md +10 -7
- package/dist/chunks/{AddTrackWorkflow-LK25EC3H.js → AddTrackWorkflow-NLSZYK56.js} +2 -2
- package/dist/chunks/{GbzBaseSyntenyAdapter-EKLUEECW.js → GbzBaseSyntenyAdapter-4KVWVUN6.js} +2 -2
- package/dist/chunks/{GbzBaseSyntenyAdapter-EKLUEECW.js.map → GbzBaseSyntenyAdapter-4KVWVUN6.js.map} +1 -1
- package/dist/chunks/{GraphGenomeView-P3XMSKUQ.js → GraphGenomeView-KI4FQ5A7.js} +2 -2
- package/dist/chunks/GraphTrackSettingsDialog-UQNMDXTE.js +4 -0
- package/dist/chunks/GraphTrackSettingsDialog-UQNMDXTE.js.map +7 -0
- package/dist/chunks/{HighlightColorDialog-G6AFRYUD.js → HighlightColorDialog-D4RCUCUF.js} +2 -2
- package/dist/chunks/LinearGraphDisplay-RPZUZSM2.js +2 -0
- package/dist/chunks/{LinearGraphDisplay-O4SQSXJJ.js.map → LinearGraphDisplay-RPZUZSM2.js.map} +2 -2
- package/dist/chunks/MinigraphBubbleAdapter-TJYI7KEN.js +2 -0
- package/dist/chunks/{MinigraphBubbleAdapter-3NJ2VLN2.js.map → MinigraphBubbleAdapter-TJYI7KEN.js.map} +1 -1
- package/dist/chunks/RgfaTabixAdapter-5ZMJLFZP.js +2 -0
- package/dist/chunks/RgfaTabixAdapter-5ZMJLFZP.js.map +7 -0
- package/dist/chunks/WalkFormsAdapter-CAJOWOEX.js +2 -0
- package/dist/chunks/WalkFormsAdapter-CAJOWOEX.js.map +7 -0
- package/dist/chunks/WalkTabixSyntenyAdapter-VI3KA2GX.js +2 -0
- package/dist/chunks/{WalkTabixSyntenyAdapter-NHNSZZBX.js.map → WalkTabixSyntenyAdapter-VI3KA2GX.js.map} +2 -2
- package/dist/chunks/{chunk-MCE6STZR.js → chunk-22ANI2KL.js} +2 -2
- package/dist/chunks/{chunk-MCE6STZR.js.map → chunk-22ANI2KL.js.map} +1 -1
- package/dist/chunks/chunk-42KD4AVY.js +2 -0
- package/dist/chunks/{chunk-OKRGO7XA.js.map → chunk-42KD4AVY.js.map} +4 -4
- package/dist/chunks/chunk-4NIWMRKH.js +2 -0
- package/dist/chunks/chunk-4NIWMRKH.js.map +7 -0
- package/dist/chunks/chunk-BCOXRUNQ.js +3 -0
- package/dist/chunks/chunk-BCOXRUNQ.js.map +7 -0
- package/dist/chunks/chunk-DAPI7M4M.js +4 -0
- package/dist/chunks/chunk-DAPI7M4M.js.map +7 -0
- package/dist/chunks/chunk-DEWZOJSC.js +2 -0
- package/dist/chunks/{chunk-6OXXT2YP.js.map → chunk-DEWZOJSC.js.map} +4 -4
- package/dist/chunks/{chunk-K4TFAHGC.js → chunk-FCMXEFJA.js} +1 -1
- package/dist/chunks/{chunk-K4TFAHGC.js.map → chunk-FCMXEFJA.js.map} +1 -1
- package/dist/chunks/{chunk-UOFWYS6T.js → chunk-GYY6LF5L.js} +1 -1
- package/dist/chunks/{chunk-UOFWYS6T.js.map → chunk-GYY6LF5L.js.map} +1 -1
- package/dist/chunks/{chunk-F2DMEJEK.js → chunk-HHMJNAA3.js} +2 -2
- package/dist/chunks/{chunk-NPV7SRJD.js → chunk-HTTONSO4.js} +2 -2
- package/dist/chunks/{chunk-NPV7SRJD.js.map → chunk-HTTONSO4.js.map} +1 -1
- package/dist/chunks/{chunk-MVVXRZXV.js → chunk-JRJHXLGP.js} +2 -2
- package/dist/chunks/{chunk-MVVXRZXV.js.map → chunk-JRJHXLGP.js.map} +2 -2
- package/dist/chunks/chunk-K4JL57NR.js +2 -0
- package/dist/chunks/chunk-K4JL57NR.js.map +7 -0
- package/dist/chunks/chunk-KOC23YNN.js +4 -0
- package/dist/chunks/chunk-KOC23YNN.js.map +7 -0
- package/dist/chunks/{chunk-MNCPMOHJ.js → chunk-LTNFVL2F.js} +2 -2
- package/dist/chunks/chunk-OZEKEYT3.js +2 -0
- package/dist/chunks/{chunk-ZAYC5OMQ.js.map → chunk-OZEKEYT3.js.map} +4 -4
- package/dist/chunks/{chunk-DUPBXBYE.js → chunk-POUV7OPY.js} +2 -2
- package/dist/chunks/{chunk-DUPBXBYE.js.map → chunk-POUV7OPY.js.map} +1 -1
- package/dist/chunks/{chunk-CNNZDE6I.js → chunk-SPL3UO2D.js} +2 -2
- package/dist/chunks/{chunk-CNNZDE6I.js.map → chunk-SPL3UO2D.js.map} +1 -1
- package/dist/chunks/{chunk-QJ43NDYS.js → chunk-TYV6UXJA.js} +2 -2
- package/dist/chunks/chunk-UR2MJJUP.js +2 -0
- package/dist/chunks/chunk-VTMJQFN5.js +2 -0
- package/dist/chunks/chunk-VTMJQFN5.js.map +7 -0
- package/dist/chunks/{chunk-CXOMX2I6.js → chunk-XZGYJ3JC.js} +1 -1
- package/dist/chunks/{chunk-CXOMX2I6.js.map → chunk-XZGYJ3JC.js.map} +1 -1
- package/dist/chunks/{chunk-TGSDXZXI.js → chunk-Z3IKZAYC.js} +2 -2
- package/dist/chunks/{chunk-TGSDXZXI.js.map → chunk-Z3IKZAYC.js.map} +1 -1
- package/dist/chunks/chunk-ZV5FNOHS.js +3 -0
- package/dist/chunks/chunk-ZV5FNOHS.js.map +7 -0
- package/dist/chunks/{renderSvg-7G6NWBXF.js → renderSvg-3PPAEMBT.js} +2 -2
- package/dist/jbrowse-plugin-graphgenomeviewer.esm.js +4 -4
- package/dist/jbrowse-plugin-graphgenomeviewer.esm.js.map +4 -4
- package/package.json +3 -3
- package/src/GraphComputeLayout.test.ts +24 -0
- package/src/GraphComputeLayout.ts +4 -2
- package/src/GraphGenomeView/components/GraphSettingsDialog.tsx +16 -0
- package/src/GraphGenomeView/components/WalkRowsOverlay.tsx +83 -0
- package/src/GraphGenomeView/components/usePaneGestures.test.tsx +82 -5
- package/src/GraphGenomeView/components/usePaneGestures.ts +10 -4
- package/src/GraphGenomeView/graphLayers.ts +14 -1
- package/src/GraphGenomeView/legendSections.ts +32 -0
- package/src/GraphGenomeView/model.test.ts +294 -113
- package/src/GraphGenomeView/model.ts +3 -10
- package/src/GraphGenomeView/nodeSize.test.ts +21 -5
- package/src/GraphGenomeView/nodeSize.ts +24 -8
- package/src/GraphGenomeView/pane/fitViews.ts +5 -110
- package/src/GraphGenomeView/pane/graphViews.ts +96 -20
- package/src/GraphGenomeView/pane/launchActions.ts +55 -33
- package/src/GraphGenomeView/pane/launchViews.ts +46 -0
- package/src/GraphGenomeView/pane/loadActions.ts +85 -25
- package/src/GraphGenomeView/pane/morph.test.ts +25 -1
- package/src/GraphGenomeView/pane/morph.ts +3 -1
- package/src/GraphGenomeView/pane/paneBase.ts +5 -7
- package/src/GraphGenomeView/pane/renderingActions.ts +61 -2
- package/src/GraphGenomeView/pane/settingActions.ts +62 -4
- package/src/GraphGenomeView/pane/walkRowViews.ts +17 -6
- package/src/GraphGenomeView/tubeMapPanels.ts +2 -1
- package/src/GraphGenomeView/viewModel.ts +4 -9
- package/src/GraphGenomeView/walkRowBlocks.ts +50 -0
- package/src/LinearGraphDisplay/LinearGraphDisplay.test.ts +35 -5
- package/src/LinearGraphDisplay/components/GraphStatusChrome.test.tsx +18 -0
- package/src/LinearGraphDisplay/components/GraphStatusChrome.tsx +4 -2
- package/src/LinearGraphDisplay/components/SubgraphHaplotypesField.test.tsx +41 -1
- package/src/LinearGraphDisplay/components/SubgraphHaplotypesField.tsx +33 -19
- package/src/LinearGraphDisplay/configSchema.ts +4 -3
- package/src/RgfaTabixAdapter/RgfaTabixAdapter.ts +2 -9
- package/src/RgfaTabixAdapter/configSchema.ts +3 -3
- package/src/RgfaTabixAdapter/walkFiles.test.ts +6 -6
- package/src/WalkFormsAdapter/WalkFormsAdapter.test.ts +204 -0
- package/src/WalkFormsAdapter/WalkFormsAdapter.ts +232 -0
- package/src/WalkFormsAdapter/configSchema.ts +127 -0
- package/src/WalkFormsAdapter/formRows.test.ts +258 -0
- package/src/WalkFormsAdapter/formRows.ts +231 -0
- package/src/WalkFormsAdapter/formsMenu.test.ts +237 -0
- package/src/WalkFormsAdapter/formsMenu.ts +202 -0
- package/src/WalkFormsAdapter/index.ts +21 -0
- package/src/WalkFormsAdapter/pinned.ts +33 -0
- package/src/WalkTabixSyntenyAdapter/WalkTabixSyntenyAdapter.test.ts +5 -5
- package/src/WalkTabixSyntenyAdapter/WalkTabixSyntenyAdapter.ts +3 -6
- package/src/WalkTabixSyntenyAdapter/configSchema.ts +2 -2
- package/src/hoverSync/graphViewHighlights.test.ts +53 -0
- package/src/hoverSync/graphViewHighlights.ts +25 -0
- package/src/hoverSync/lgvHover.test.ts +100 -1
- package/src/hoverSync/lgvHover.ts +77 -6
- package/src/index.ts +4 -0
- package/src/launchFromGraph/contributors.test.ts +52 -0
- package/src/launchFromGraph/contributors.ts +105 -1
- package/src/launchFromGraph/launchFromGraph.ts +2 -2
- package/src/panSNAliases/panSNAliases.test.ts +2 -2
- package/src/panSNTabix.test.ts +28 -0
- package/src/panSNTabix.ts +26 -8
- package/src/version.ts +1 -1
- package/dist/chunks/GraphTrackSettingsDialog-UTYH6HEC.js +0 -6
- package/dist/chunks/GraphTrackSettingsDialog-UTYH6HEC.js.map +0 -7
- package/dist/chunks/LinearGraphDisplay-O4SQSXJJ.js +0 -2
- package/dist/chunks/MinigraphBubbleAdapter-3NJ2VLN2.js +0 -2
- package/dist/chunks/RgfaTabixAdapter-55DGDID2.js +0 -2
- package/dist/chunks/RgfaTabixAdapter-55DGDID2.js.map +0 -7
- package/dist/chunks/WalkTabixSyntenyAdapter-NHNSZZBX.js +0 -2
- package/dist/chunks/chunk-4I3K4RHE.js +0 -2
- package/dist/chunks/chunk-4I3K4RHE.js.map +0 -7
- package/dist/chunks/chunk-6OXXT2YP.js +0 -2
- package/dist/chunks/chunk-NFH2U74B.js +0 -2
- package/dist/chunks/chunk-OKRGO7XA.js +0 -2
- package/dist/chunks/chunk-P7VMOTUM.js +0 -3
- package/dist/chunks/chunk-P7VMOTUM.js.map +0 -7
- package/dist/chunks/chunk-RU4WSKBO.js +0 -3
- package/dist/chunks/chunk-RU4WSKBO.js.map +0 -7
- package/dist/chunks/chunk-WGTLNVST.js +0 -2
- package/dist/chunks/chunk-WGTLNVST.js.map +0 -7
- package/dist/chunks/chunk-XQFBLJU5.js +0 -4
- package/dist/chunks/chunk-XQFBLJU5.js.map +0 -7
- package/dist/chunks/chunk-Y5DNBFDY.js +0 -3
- package/dist/chunks/chunk-Y5DNBFDY.js.map +0 -7
- package/dist/chunks/chunk-ZAYC5OMQ.js +0 -2
- package/src/launchFromGraph/launchTracks.test.ts +0 -95
- package/src/launchFromGraph/launchTracks.ts +0 -49
- /package/dist/chunks/{AddTrackWorkflow-LK25EC3H.js.map → AddTrackWorkflow-NLSZYK56.js.map} +0 -0
- /package/dist/chunks/{GraphGenomeView-P3XMSKUQ.js.map → GraphGenomeView-KI4FQ5A7.js.map} +0 -0
- /package/dist/chunks/{HighlightColorDialog-G6AFRYUD.js.map → HighlightColorDialog-D4RCUCUF.js.map} +0 -0
- /package/dist/chunks/{chunk-F2DMEJEK.js.map → chunk-HHMJNAA3.js.map} +0 -0
- /package/dist/chunks/{chunk-MNCPMOHJ.js.map → chunk-LTNFVL2F.js.map} +0 -0
- /package/dist/chunks/{chunk-QJ43NDYS.js.map → chunk-TYV6UXJA.js.map} +0 -0
- /package/dist/chunks/{chunk-NFH2U74B.js.map → chunk-UR2MJJUP.js.map} +0 -0
- /package/dist/chunks/{renderSvg-7G6NWBXF.js.map → renderSvg-3PPAEMBT.js.map} +0 -0
- /package/src/RgfaTabixAdapter/test_data/{walks4_follow.GRCh38.walks.bed.gz → walks5_follow.GRCh38.walks.bed.gz} +0 -0
- /package/src/RgfaTabixAdapter/test_data/{walks4_follow.GRCh38.walks.bed.gz.tbi → walks5_follow.GRCh38.walks.bed.gz.tbi} +0 -0
- /package/src/RgfaTabixAdapter/test_data/{walks4_header.GRCh38.walks.bed.gz → walks5_header.GRCh38.walks.bed.gz} +0 -0
- /package/src/RgfaTabixAdapter/test_data/{walks4_header.GRCh38.walks.bed.gz.tbi → walks5_header.GRCh38.walks.bed.gz.tbi} +0 -0
- /package/src/RgfaTabixAdapter/test_data/{walks4_inversion.GRCh38.walks.bed.gz → walks5_inversion.GRCh38.walks.bed.gz} +0 -0
- /package/src/RgfaTabixAdapter/test_data/{walks4_inversion.GRCh38.walks.bed.gz.tbi → walks5_inversion.GRCh38.walks.bed.gz.tbi} +0 -0
- /package/src/RgfaTabixAdapter/test_data/{walks4_lanepairs.GRCh38.walks.bed.gz → walks5_lanepairs.GRCh38.walks.bed.gz} +0 -0
- /package/src/RgfaTabixAdapter/test_data/{walks4_lanepairs.GRCh38.walks.bed.gz.tbi → walks5_lanepairs.GRCh38.walks.bed.gz.tbi} +0 -0
- /package/src/RgfaTabixAdapter/test_data/{walks4_maxnode.GRCh38.walks.bed.gz → walks5_maxnode.GRCh38.walks.bed.gz} +0 -0
- /package/src/RgfaTabixAdapter/test_data/{walks4_maxnode.GRCh38.walks.bed.gz.tbi → walks5_maxnode.GRCh38.walks.bed.gz.tbi} +0 -0
- /package/src/RgfaTabixAdapter/test_data/{walks4_rejoin.GRCh38.walks.bed.gz → walks5_rejoin.GRCh38.walks.bed.gz} +0 -0
- /package/src/RgfaTabixAdapter/test_data/{walks4_rejoin.GRCh38.walks.bed.gz.tbi → walks5_rejoin.GRCh38.walks.bed.gz.tbi} +0 -0
- /package/src/RgfaTabixAdapter/test_data/{walks4_twopart.GRCh38.walks.bed.gz → walks5_twopart.GRCh38.walks.bed.gz} +0 -0
- /package/src/RgfaTabixAdapter/test_data/{walks4_twopart.GRCh38.walks.bed.gz.tbi → walks5_twopart.GRCh38.walks.bed.gz.tbi} +0 -0
|
@@ -868,8 +868,14 @@ test('walk rows fit their own bars in the track, since a walk can be longer than
|
|
|
868
868
|
expect(pane.viewportOwner).toBe('fit')
|
|
869
869
|
})
|
|
870
870
|
|
|
871
|
-
test('walk rows cut every snarl a walk takes past the window, so a walk comes whole', async () => {
|
|
872
|
-
const {
|
|
871
|
+
test('walk rows cut every snarl a GBZ walk takes past the window, so a walk comes whole', async () => {
|
|
872
|
+
const { view, cuts } = createEnvironment({ paths: true })
|
|
873
|
+
view.zoomTo(60_000 / WIDTH_PX)
|
|
874
|
+
view.scrollTo(1_000_000 / view.bpPerPx)
|
|
875
|
+
view.showTrack('walks')
|
|
876
|
+
const pane = view.tracks[0]!.displays[0] as LinearGraphDisplayModel
|
|
877
|
+
pane.startRenderingBackend(fakeRenderer())
|
|
878
|
+
await wait(SETTLE_MS)
|
|
873
879
|
await pane.switchLayout('force')
|
|
874
880
|
await wait(SETTLE_MS)
|
|
875
881
|
const before = cuts.length
|
|
@@ -884,6 +890,18 @@ test('walk rows cut every snarl a walk takes past the window, so a walk comes wh
|
|
|
884
890
|
expect(cuts.at(-1)!.snarls).toBeUndefined()
|
|
885
891
|
})
|
|
886
892
|
|
|
893
|
+
// A walk file cuts the same whatever the snarl option says
|
|
894
|
+
test('walk rows on an rGFA track cut for the step budget, not for snarls', async () => {
|
|
895
|
+
const { pane, cuts } = await shownGraph({ paths: true })
|
|
896
|
+
await pane.switchLayout('force')
|
|
897
|
+
await wait(SETTLE_MS)
|
|
898
|
+
const before = cuts.length
|
|
899
|
+
await pane.switchLayout('walkrows')
|
|
900
|
+
await wait(SETTLE_MS)
|
|
901
|
+
expect(cuts).toHaveLength(before + 1)
|
|
902
|
+
expect(cuts.at(-1)!.snarls).toBeUndefined()
|
|
903
|
+
})
|
|
904
|
+
|
|
887
905
|
test('a launch in the force layout cuts the window alone', async () => {
|
|
888
906
|
const { view, cuts } = createEnvironment()
|
|
889
907
|
view.zoomTo(60_000 / WIDTH_PX)
|
|
@@ -1018,9 +1036,11 @@ test('each grammar setter writes the track config, and the pane reads it back',
|
|
|
1018
1036
|
display.setLayoutMode('ordered')
|
|
1019
1037
|
display.setColorScheme('depth')
|
|
1020
1038
|
display.setNodeWidth('uniform')
|
|
1039
|
+
display.setLinkWidth('walks')
|
|
1021
1040
|
display.setLayer('bubbles', true)
|
|
1022
1041
|
display.setFacet('walk')
|
|
1023
1042
|
display.setWalkRowSamples(['HG1'])
|
|
1043
|
+
display.setWalkRowOrder('shared')
|
|
1024
1044
|
display.setHover('off')
|
|
1025
1045
|
const slots = [
|
|
1026
1046
|
'layoutMode',
|
|
@@ -1040,21 +1060,31 @@ test('each grammar setter writes the track config, and the pane reads it back',
|
|
|
1040
1060
|
).toEqual({
|
|
1041
1061
|
layoutMode: 'ordered',
|
|
1042
1062
|
color: { field: 'depth' },
|
|
1043
|
-
size: 6,
|
|
1063
|
+
size: { value: 6, links: 'walks' },
|
|
1044
1064
|
layers: { bubbles: true },
|
|
1045
1065
|
facet: { field: 'walk', domain: [] },
|
|
1046
|
-
rows: { kept: ['HG1'] },
|
|
1066
|
+
rows: { kept: ['HG1'], order: 'shared' },
|
|
1047
1067
|
hover: 'off',
|
|
1048
1068
|
})
|
|
1049
1069
|
expect([
|
|
1050
1070
|
display.chosenLayoutMode,
|
|
1051
1071
|
display.chosenColorScheme,
|
|
1052
1072
|
display.nodeWidth,
|
|
1073
|
+
display.linkWidth,
|
|
1053
1074
|
display.showBubbles,
|
|
1054
1075
|
display.facetSetting.field,
|
|
1055
1076
|
display.walkRowSamples,
|
|
1056
1077
|
display.chosenHover,
|
|
1057
|
-
]).toEqual([
|
|
1078
|
+
]).toEqual([
|
|
1079
|
+
'ordered',
|
|
1080
|
+
'depth',
|
|
1081
|
+
'uniform',
|
|
1082
|
+
'walks',
|
|
1083
|
+
true,
|
|
1084
|
+
'walk',
|
|
1085
|
+
['HG1'],
|
|
1086
|
+
'off',
|
|
1087
|
+
])
|
|
1058
1088
|
})
|
|
1059
1089
|
|
|
1060
1090
|
// Reset track settings, undo and the config editor write the slot without
|
|
@@ -39,6 +39,24 @@ test('a drawn graph sits in the chrome with its readiness attributes', () => {
|
|
|
39
39
|
expect(screen.getByTestId('graph-canvas')).toBeTruthy()
|
|
40
40
|
})
|
|
41
41
|
|
|
42
|
+
// GraphCanvas shows it, with the retry that rebuilds the backend
|
|
43
|
+
test('a render error alone is left to the canvas', () => {
|
|
44
|
+
render(
|
|
45
|
+
<GraphStatusChrome
|
|
46
|
+
model={model({ renderError: new Error('context lost') })}
|
|
47
|
+
phase="error"
|
|
48
|
+
drawn={false}
|
|
49
|
+
testid="linear-graph-display"
|
|
50
|
+
>
|
|
51
|
+
<canvas />
|
|
52
|
+
</GraphStatusChrome>,
|
|
53
|
+
)
|
|
54
|
+
expect(screen.queryByText(/context lost/)).toBeNull()
|
|
55
|
+
expect(screen.getByTestId('linear-graph-display').dataset.displayPhase).toBe(
|
|
56
|
+
'error',
|
|
57
|
+
)
|
|
58
|
+
})
|
|
59
|
+
|
|
42
60
|
test('a refused cut shows its notice over the graph', () => {
|
|
43
61
|
const error = Object.assign(
|
|
44
62
|
new Error('Zoom in to about 54Kbp to see the graph'),
|
|
@@ -31,7 +31,7 @@ const FORCE_LOAD_ADVICE =
|
|
|
31
31
|
// read `model` and `visible`; later hosts read values and callbacks. Each
|
|
32
32
|
// overlay gets both, so one build runs on either.
|
|
33
33
|
function overlayProps(model: LinearGraphDisplayModel) {
|
|
34
|
-
const error = model
|
|
34
|
+
const { error } = model
|
|
35
35
|
const regionTooLarge =
|
|
36
36
|
error instanceof Error &&
|
|
37
37
|
(error as { regionTooLarge?: unknown }).regionTooLarge === true
|
|
@@ -128,7 +128,9 @@ export default observer(function GraphStatusChrome({
|
|
|
128
128
|
{children}
|
|
129
129
|
</BottomRightCornerContext>
|
|
130
130
|
<TrackOverlayPortal>
|
|
131
|
-
{
|
|
131
|
+
{/* a backend's render error is the canvas's banner, whose retry
|
|
132
|
+
rebuilds the backend */}
|
|
133
|
+
{phase === 'error' && model.error !== undefined ? (
|
|
132
134
|
<ErrorBar model={model} visible {...props.error} />
|
|
133
135
|
) : null}
|
|
134
136
|
<Loading
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { fireEvent, render, screen } from '@testing-library/react'
|
|
1
|
+
import { act, fireEvent, render, screen } from '@testing-library/react'
|
|
2
|
+
import { observable, runInAction } from 'mobx'
|
|
2
3
|
|
|
3
4
|
import SubgraphHaplotypesField from './SubgraphHaplotypesField'
|
|
4
5
|
|
|
@@ -58,3 +59,42 @@ test('without names the field takes typed ones', () => {
|
|
|
58
59
|
fireEvent.keyDown(field, { key: 'Enter' })
|
|
59
60
|
expect(m.setSubgraphHaplotypes).toHaveBeenCalledWith(['HG002', 'NA19240#1'])
|
|
60
61
|
})
|
|
62
|
+
|
|
63
|
+
// A model whose set re-renders the field, as the display's does
|
|
64
|
+
function liveModel(haplotypeNames?: string[]) {
|
|
65
|
+
const m = observable({
|
|
66
|
+
sourceAdapter: { type: 'RgfaTabixAdapter', walksUri: 'chr22' },
|
|
67
|
+
cutHaplotypes: ['HG002'],
|
|
68
|
+
haplotypeNames,
|
|
69
|
+
setSubgraphHaplotypes(haplotypes: string[]) {
|
|
70
|
+
m.cutHaplotypes = haplotypes
|
|
71
|
+
},
|
|
72
|
+
recut: vi.fn(),
|
|
73
|
+
})
|
|
74
|
+
return m as unknown as LinearGraphCutModel & { cutHaplotypes: string[] }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
test('applying from the field keeps its focus', () => {
|
|
78
|
+
const m = liveModel()
|
|
79
|
+
render(<SubgraphHaplotypesField model={m} />)
|
|
80
|
+
const field = screen.getByTestId<HTMLInputElement>('graph-haplotypes-field')
|
|
81
|
+
field.focus()
|
|
82
|
+
fireEvent.change(field, { target: { value: 'HG002, NA19240#1' } })
|
|
83
|
+
fireEvent.keyDown(field, { key: 'Enter' })
|
|
84
|
+
expect(m.cutHaplotypes).toEqual(['HG002', 'NA19240#1'])
|
|
85
|
+
expect(screen.getByTestId('graph-haplotypes-field')).toBe(field)
|
|
86
|
+
expect(document.activeElement).toBe(field)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
test('a set changed elsewhere resets the draft', () => {
|
|
90
|
+
const m = liveModel()
|
|
91
|
+
render(<SubgraphHaplotypesField model={m} />)
|
|
92
|
+
act(() => {
|
|
93
|
+
runInAction(() => {
|
|
94
|
+
m.cutHaplotypes = ['HG00097#1']
|
|
95
|
+
})
|
|
96
|
+
})
|
|
97
|
+
expect(
|
|
98
|
+
screen.getByTestId<HTMLInputElement>('graph-haplotypes-field').value,
|
|
99
|
+
).toBe('HG00097#1')
|
|
100
|
+
})
|
|
@@ -18,20 +18,39 @@ function parseHaplotypeList(text: string) {
|
|
|
18
18
|
return text.split(/[\s,]+/).filter(name => name !== '')
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
const sameList = (a: readonly string[], b: readonly string[]) =>
|
|
22
|
+
a.join('\n') === b.join('\n')
|
|
23
|
+
|
|
24
|
+
// The cut's set, and the last one this field saw: an outside change (a
|
|
25
|
+
// restored session, a launch) resets the draft, while the field's own apply
|
|
26
|
+
// records what it set first, so it keeps its focus and draft
|
|
27
|
+
function useCutHaplotypes(model: LinearGraphCutModel, reset: () => void) {
|
|
28
|
+
const current = model.cutHaplotypes ?? []
|
|
29
|
+
const [seen, setSeen] = useState(current)
|
|
30
|
+
if (!sameList(current, seen)) {
|
|
31
|
+
setSeen(current)
|
|
32
|
+
reset()
|
|
33
|
+
}
|
|
34
|
+
return (haplotypes: string[]) => {
|
|
35
|
+
if (!sameList(haplotypes, current)) {
|
|
36
|
+
setSeen(haplotypes)
|
|
37
|
+
model.setSubgraphHaplotypes(haplotypes)
|
|
38
|
+
model.recut()
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
21
43
|
const HaplotypeListField = observer(function HaplotypeListField({
|
|
22
44
|
model,
|
|
23
45
|
}: {
|
|
24
46
|
model: LinearGraphCutModel
|
|
25
47
|
}) {
|
|
26
48
|
const [draft, setDraft] = useState(model.cutHaplotypes?.join(', ') ?? '')
|
|
49
|
+
const setCut = useCutHaplotypes(model, () => {
|
|
50
|
+
setDraft(model.cutHaplotypes?.join(', ') ?? '')
|
|
51
|
+
})
|
|
27
52
|
const apply = () => {
|
|
28
|
-
|
|
29
|
-
const unchanged =
|
|
30
|
-
parsed.join('\n') === (model.cutHaplotypes ?? []).join('\n')
|
|
31
|
-
if (!unchanged) {
|
|
32
|
-
model.setSubgraphHaplotypes(parsed)
|
|
33
|
-
model.recut()
|
|
34
|
-
}
|
|
53
|
+
setCut(parseHaplotypeList(draft))
|
|
35
54
|
}
|
|
36
55
|
return (
|
|
37
56
|
<TextField
|
|
@@ -72,12 +91,10 @@ const HaplotypePickField = observer(function HaplotypePickField({
|
|
|
72
91
|
const draftRef = useRef(draft)
|
|
73
92
|
const typed = useRef('')
|
|
74
93
|
const [open, setOpen] = useState(false)
|
|
75
|
-
const apply = (
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
80
|
-
}
|
|
94
|
+
const apply = useCutHaplotypes(model, () => {
|
|
95
|
+
draftRef.current = model.cutHaplotypes ?? []
|
|
96
|
+
setDraft(draftRef.current)
|
|
97
|
+
})
|
|
81
98
|
return (
|
|
82
99
|
<Autocomplete
|
|
83
100
|
multiple
|
|
@@ -126,9 +143,7 @@ const HaplotypePickField = observer(function HaplotypePickField({
|
|
|
126
143
|
)
|
|
127
144
|
})
|
|
128
145
|
|
|
129
|
-
// Only a GBZ cut and a walk-indexed rGFA cut read the set
|
|
130
|
-
// keyed on it so an outside change (a restored session, a launch) resets the
|
|
131
|
-
// draft rather than fighting it.
|
|
146
|
+
// Only a GBZ cut and a walk-indexed rGFA cut read the set
|
|
132
147
|
const SubgraphHaplotypesField = observer(function SubgraphHaplotypesField({
|
|
133
148
|
model,
|
|
134
149
|
}: {
|
|
@@ -136,14 +151,13 @@ const SubgraphHaplotypesField = observer(function SubgraphHaplotypesField({
|
|
|
136
151
|
}) {
|
|
137
152
|
const { classes } = useStyles()
|
|
138
153
|
const names = model.haplotypeNames
|
|
139
|
-
const key = model.cutHaplotypes?.join(',') ?? ''
|
|
140
154
|
const adapter = model.sourceAdapter
|
|
141
155
|
return adapter && cutsByHaplotype(adapter) ? (
|
|
142
156
|
<div className={classes.section}>
|
|
143
157
|
{names ? (
|
|
144
|
-
<HaplotypePickField
|
|
158
|
+
<HaplotypePickField model={model} names={names} />
|
|
145
159
|
) : (
|
|
146
|
-
<HaplotypeListField
|
|
160
|
+
<HaplotypeListField model={model} />
|
|
147
161
|
)}
|
|
148
162
|
<Typography variant="caption" component="div" color="text.secondary">
|
|
149
163
|
{names
|
|
@@ -44,7 +44,8 @@ export function configSchemaFactory() {
|
|
|
44
44
|
* #slot
|
|
45
45
|
* the node thickness: a px number draws every node that thick;
|
|
46
46
|
* `{ field: 'depth', value }` thickens a node the more paths carry it,
|
|
47
|
-
* around `value` px
|
|
47
|
+
* around `value` px; `links: 'walks'` thickens a link the more walks
|
|
48
|
+
* take it
|
|
48
49
|
*/
|
|
49
50
|
size: {
|
|
50
51
|
type: 'frozen',
|
|
@@ -54,8 +55,8 @@ export function configSchemaFactory() {
|
|
|
54
55
|
/**
|
|
55
56
|
* #slot
|
|
56
57
|
* what the drawing adds over its nodes and edges, stated as changes to
|
|
57
|
-
* the defaults: `paths`, `bubbles` and `
|
|
58
|
-
* `genes` and `referenceStrip` on
|
|
58
|
+
* the defaults: `paths`, `bubbles`, `walkStrip` and `walkRibbons` are
|
|
59
|
+
* off, `deletions`, `genes` and `referenceStrip` on
|
|
59
60
|
* ```js
|
|
60
61
|
* { layers: { bubbles: true, genes: false } }
|
|
61
62
|
* ```
|
|
@@ -4,11 +4,7 @@ import { ObservableCreate } from '@jbrowse/core/util/rxjs'
|
|
|
4
4
|
import { parseSegmentRow, rgfaCut, segmentSamples } from '@jbrowse/graph-walks'
|
|
5
5
|
|
|
6
6
|
import { locationIsSet } from '../locationName.ts'
|
|
7
|
-
import {
|
|
8
|
-
PanSNRefNames,
|
|
9
|
-
openTabixSlot,
|
|
10
|
-
unmappedRegionMessage,
|
|
11
|
-
} from '../panSNTabix.ts'
|
|
7
|
+
import { PanSNRefNames, openTabixSlot } from '../panSNTabix.ts'
|
|
12
8
|
import { getLines } from '../tabixRange.ts'
|
|
13
9
|
import { WalkIndex } from './walkReader.ts'
|
|
14
10
|
|
|
@@ -194,10 +190,7 @@ export default class RgfaTabixAdapter extends BaseFeatureDataAdapter<RgfaTabixAd
|
|
|
194
190
|
): Promise<string | WalkCut> {
|
|
195
191
|
const { hops, signal, tier } = opts
|
|
196
192
|
const index = this.index(tier)
|
|
197
|
-
const tabixRefName = await index.refNames.
|
|
198
|
-
if (tabixRefName === undefined) {
|
|
199
|
-
throw new Error(unmappedRegionMessage(region))
|
|
200
|
-
}
|
|
193
|
+
const tabixRefName = await index.refNames.resolveForCut(region, { signal })
|
|
201
194
|
if (index.walks) {
|
|
202
195
|
return index.walks.subgraph(tabixRefName, region, opts)
|
|
203
196
|
}
|
|
@@ -30,8 +30,8 @@ import type { Instance } from '@jbrowse/mobx-state-tree'
|
|
|
30
30
|
* `walksUri` takes the prefix of a walk file in place of `uri`, and resolves
|
|
31
31
|
* `<walksUri>.walks.bed.gz` and its `.tbi`. `gfa-to-tabix --walks -o <prefix>`
|
|
32
32
|
* writes one per reference sample, so a track names one as
|
|
33
|
-
* `<prefix>.<sample>`. The adapter reads walk format
|
|
34
|
-
* writes from 1.
|
|
33
|
+
* `<prefix>.<sample>`. The adapter reads walk format 5, which gfa-to-tabix
|
|
34
|
+
* writes from 1.4, through @jbrowse/graph-walks. Such a graph's cut carries
|
|
35
35
|
* the haplotypes' walks, and `defaultHaplotypes` names the ones it is cut for
|
|
36
36
|
* until the user picks others.
|
|
37
37
|
*
|
|
@@ -152,7 +152,7 @@ const RgfaTabixAdapter = ConfigurationSchema(
|
|
|
152
152
|
),
|
|
153
153
|
/**
|
|
154
154
|
* #slot
|
|
155
|
-
* a `gfa-to-tabix --walks` file in walk format
|
|
155
|
+
* a `gfa-to-tabix --walks` file in walk format 5: per reference chunk,
|
|
156
156
|
* the reference's path, the ways haplotypes leave it and each haplotype's
|
|
157
157
|
* piece. Unset means the graph has no walks.
|
|
158
158
|
*/
|
|
@@ -35,27 +35,27 @@ const fixture = (name: string) =>
|
|
|
35
35
|
// W HG002 1 chr1 >1>2>7>4>5>6 7 replaces 3
|
|
36
36
|
// W HG002 2 chr1 >1>2>3>4>8>6 8 replaces 5
|
|
37
37
|
// W HG00097 1 chr1 >1>2>3>4>5>6
|
|
38
|
-
const headerPrefix = fixture('
|
|
38
|
+
const headerPrefix = fixture('walks5_header')
|
|
39
39
|
|
|
40
|
-
//
|
|
40
|
+
// walks5_header's walks with the reference named as pggb names a P-line,
|
|
41
41
|
// GRCh38#chr1
|
|
42
|
-
const twoPartPrefix = fixture('
|
|
42
|
+
const twoPartPrefix = fixture('walks5_twopart')
|
|
43
43
|
|
|
44
44
|
// An unchopped graph, node 1 spanning 0-2600 and filed under chunk 0 alone:
|
|
45
45
|
// W GRCh38 0 chr1 >1>2>4
|
|
46
46
|
// W HG002 1 chr1 >1>3>4 3 replaces 2
|
|
47
|
-
const maxNodePrefix = fixture('
|
|
47
|
+
const maxNodePrefix = fixture('walks5_maxnode')
|
|
48
48
|
|
|
49
49
|
// Twelve 500 bp reference nodes over chr1:0-6000 in one 10 kb chunk:
|
|
50
50
|
// W GRCh38 0 chr1 >1>2>3>4>5>6>7>8>9>10>11>12
|
|
51
51
|
// W HG002 1 chr1 >1>2>3>20>21>9>10>11>12 20 and 21, 2 kb each, replace 4-8
|
|
52
52
|
// W HG002 2 chr1 >1>2>3>4>5>6>7>8>9>10>11>12
|
|
53
53
|
// W HG003 1 chr1 >1>2>3>22 the contig ends on 22, 300 bp
|
|
54
|
-
const rejoinPrefix = fixture('
|
|
54
|
+
const rejoinPrefix = fixture('walks5_rejoin')
|
|
55
55
|
|
|
56
56
|
// The same graph in 1 kb chunks, whose rows name the chunks their path goes on
|
|
57
57
|
// in: HG002#1 leaves chunk 1000 for 20 and 21 and rejoins at 9, in chunk 4000
|
|
58
|
-
const followPrefix = fixture('
|
|
58
|
+
const followPrefix = fixture('walks5_follow')
|
|
59
59
|
|
|
60
60
|
function makeAdapter(prefix: string, slots = {}) {
|
|
61
61
|
const local = (path: string) => ({
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import PluginManager from '@jbrowse/core/PluginManager'
|
|
2
|
+
import { ExpansionTooLargeError, formsCut } from '@jbrowse/graph-walks'
|
|
3
|
+
import { firstValueFrom } from 'rxjs'
|
|
4
|
+
import { toArray } from 'rxjs/operators'
|
|
5
|
+
|
|
6
|
+
import Adapter from './WalkFormsAdapter.ts'
|
|
7
|
+
import configSchema from './configSchema.ts'
|
|
8
|
+
import WalkFormsAdapterF from './index.ts'
|
|
9
|
+
|
|
10
|
+
import type * as GraphWalks from '@jbrowse/graph-walks'
|
|
11
|
+
|
|
12
|
+
vi.mock('@jbrowse/graph-walks', async importOriginal => {
|
|
13
|
+
const actual = await importOriginal<typeof GraphWalks>()
|
|
14
|
+
return { ...actual, formsCut: vi.fn(actual.formsCut) }
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
// Four walks over a 3.6 kb reference of six 600 bp nodes, in 1 kb chunks
|
|
18
|
+
// (RgfaTabixAdapter/walkFiles.test.ts builds on the same file):
|
|
19
|
+
// W GRCh38 0 chr1 >1>2>3>4>5>6
|
|
20
|
+
// W HG002 1 chr1 >1>2>7>4>5>6 7 (550 bp) replaces 3
|
|
21
|
+
// W HG002 2 chr1 >1>2>3>4>8>6 8 (20 bp) replaces 5
|
|
22
|
+
// W HG00097 1 chr1 >1>2>3>4>5>6
|
|
23
|
+
const walks =
|
|
24
|
+
require.resolve('../RgfaTabixAdapter/test_data/walks5_header.GRCh38.walks.bed.gz')
|
|
25
|
+
|
|
26
|
+
const local = (path: string) => ({
|
|
27
|
+
localPath: path,
|
|
28
|
+
locationType: 'LocalPathLocation',
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
// Each test pins its own window, since the cut cache outlives an adapter
|
|
32
|
+
function makeAdapter(slots: Record<string, unknown>) {
|
|
33
|
+
return new Adapter(
|
|
34
|
+
configSchema.create({
|
|
35
|
+
walksLocation: local(walks),
|
|
36
|
+
walksIndex: { location: local(`${walks}.tbi`) },
|
|
37
|
+
...slots,
|
|
38
|
+
}),
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const pinned = (start: number) => ({ refName: 'chr1', start, end: 3600 })
|
|
43
|
+
|
|
44
|
+
const region = (start: number, end: number, refName = 'chr1') => ({
|
|
45
|
+
refName,
|
|
46
|
+
assemblyName: 'hg38',
|
|
47
|
+
start,
|
|
48
|
+
end,
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
const featuresOf = (adapter: Adapter, query = region(0, 3600)) =>
|
|
52
|
+
firstValueFrom(adapter.getFeatures(query).pipe(toArray()))
|
|
53
|
+
|
|
54
|
+
const placeholders = (features: Awaited<ReturnType<typeof featuresOf>>) =>
|
|
55
|
+
features
|
|
56
|
+
.filter(f => f.id().startsWith('forms-reference-'))
|
|
57
|
+
.map(f => ({
|
|
58
|
+
start: f.get('start'),
|
|
59
|
+
end: f.get('end'),
|
|
60
|
+
genotypes: f.get('genotypes'),
|
|
61
|
+
}))
|
|
62
|
+
|
|
63
|
+
test('registers the adapter type', () => {
|
|
64
|
+
const pm = new PluginManager()
|
|
65
|
+
WalkFormsAdapterF(pm)
|
|
66
|
+
pm.createPluggableElements()
|
|
67
|
+
expect(pm.getAdapterType('WalkFormsAdapter').name).toBe('WalkFormsAdapter')
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
test('names the reference sample contigs as the assembly does', async () => {
|
|
71
|
+
expect(await makeAdapter({ pinned: pinned(0) }).getRefNames()).toEqual([
|
|
72
|
+
'chr1',
|
|
73
|
+
])
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
test('one source per form, labelled by share and change', async () => {
|
|
77
|
+
const adapter = makeAdapter({ pinned: pinned(1) })
|
|
78
|
+
expect(await adapter.getSourcesAndWarnings()).toEqual({
|
|
79
|
+
sources: [
|
|
80
|
+
{ name: 'F1', label: '33% · ref', haplotypes: 1, forms: 1 },
|
|
81
|
+
{
|
|
82
|
+
name: 'F2',
|
|
83
|
+
label: '33% · repl 600bp->550bp',
|
|
84
|
+
haplotypes: 1,
|
|
85
|
+
forms: 1,
|
|
86
|
+
},
|
|
87
|
+
{ name: 'F3', label: '33% · del 580bp', haplotypes: 1, forms: 1 },
|
|
88
|
+
],
|
|
89
|
+
warnings: [],
|
|
90
|
+
})
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
test('features are the pinned window changes, with a genotype per form', async () => {
|
|
94
|
+
const features = await featuresOf(makeAdapter({ pinned: pinned(2) }))
|
|
95
|
+
expect(
|
|
96
|
+
features.map(f => ({
|
|
97
|
+
start: f.get('start'),
|
|
98
|
+
end: f.get('end'),
|
|
99
|
+
consequence: f.get('consequence'),
|
|
100
|
+
svType: f.get('svType'),
|
|
101
|
+
type: f.get('type'),
|
|
102
|
+
genotypes: f.get('genotypes'),
|
|
103
|
+
})),
|
|
104
|
+
).toEqual([
|
|
105
|
+
{
|
|
106
|
+
start: 1200,
|
|
107
|
+
end: 1800,
|
|
108
|
+
consequence: 'loss',
|
|
109
|
+
svType: 'CPX',
|
|
110
|
+
type: 'complex_substitution',
|
|
111
|
+
genotypes: { F1: '0', F2: '1', F3: '0' },
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
start: 2400,
|
|
115
|
+
end: 3000,
|
|
116
|
+
consequence: 'loss',
|
|
117
|
+
svType: 'DEL',
|
|
118
|
+
type: 'deletion',
|
|
119
|
+
genotypes: { F1: '0', F2: '0', F3: '1' },
|
|
120
|
+
},
|
|
121
|
+
])
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
test('a query with no change gives one reference placeholder', async () => {
|
|
125
|
+
const adapter = makeAdapter({ pinned: pinned(3) })
|
|
126
|
+
expect(placeholders(await featuresOf(adapter, region(1900, 2300)))).toEqual([
|
|
127
|
+
{ start: 1900, end: 1901, genotypes: { F1: '0', F2: '0', F3: '0' } },
|
|
128
|
+
])
|
|
129
|
+
expect(
|
|
130
|
+
placeholders(await featuresOf(adapter, region(0, 3600, 'chr2'))),
|
|
131
|
+
).toHaveLength(1)
|
|
132
|
+
expect(
|
|
133
|
+
placeholders(await featuresOf(adapter, region(2900, 3600))),
|
|
134
|
+
).toHaveLength(0)
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
test('a floor change regroups the cut without cutting again', async () => {
|
|
138
|
+
vi.mocked(formsCut).mockClear()
|
|
139
|
+
const fine = makeAdapter({ pinned: pinned(4), floorBp: 50 })
|
|
140
|
+
const coarse = makeAdapter({ pinned: pinned(4), floorBp: 1000 })
|
|
141
|
+
expect((await fine.getSourcesAndWarnings()).sources).toHaveLength(3)
|
|
142
|
+
expect((await coarse.getSourcesAndWarnings()).sources).toEqual([
|
|
143
|
+
{ name: 'F1', label: '100% · ref', haplotypes: 3, forms: 1 },
|
|
144
|
+
])
|
|
145
|
+
expect(placeholders(await featuresOf(coarse))).toEqual([
|
|
146
|
+
{ start: 0, end: 1, genotypes: { F1: '0' } },
|
|
147
|
+
])
|
|
148
|
+
expect(formsCut).toHaveBeenCalledTimes(1)
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
test('exclude leaves samples out of every form', async () => {
|
|
152
|
+
const adapter = makeAdapter({ pinned: pinned(5), exclude: ['HG002'] })
|
|
153
|
+
expect((await adapter.getSourcesAndWarnings()).sources).toEqual([
|
|
154
|
+
{ name: 'F1', label: '100% · ref', haplotypes: 1, forms: 1 },
|
|
155
|
+
])
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
test('maxForms gives the smaller forms one empty row', async () => {
|
|
159
|
+
const adapter = makeAdapter({ pinned: pinned(6), maxForms: 1 })
|
|
160
|
+
const { sources, warnings } = await adapter.getSourcesAndWarnings()
|
|
161
|
+
expect(sources.map(s => s.label)).toEqual(['33% · ref', 'other forms · 67%'])
|
|
162
|
+
expect(warnings).toEqual([
|
|
163
|
+
expect.stringMatching(/^Forms of chr1:7-3,600: 2 smaller forms/),
|
|
164
|
+
])
|
|
165
|
+
expect(placeholders(await featuresOf(adapter))).toEqual([
|
|
166
|
+
{ start: 0, end: 1, genotypes: { F1: '0' } },
|
|
167
|
+
])
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
test('a pinned window past walkByteBudget says so, without a zoom-in notice', async () => {
|
|
171
|
+
const error = await makeAdapter({ pinned: pinned(7), walkByteBudget: 1 })
|
|
172
|
+
.getSourcesAndWarnings()
|
|
173
|
+
.catch((e: unknown) => e)
|
|
174
|
+
expect(error).toBeInstanceOf(Error)
|
|
175
|
+
expect(error).not.toHaveProperty('regionTooLarge')
|
|
176
|
+
expect(`${error}`).toMatch(
|
|
177
|
+
/^Error: The pinned window chr1:8-3,600 is too large to cut for structural forms: .*walkByteBudget, 0\.0 MB\. .*Raise walkByteBudget$/,
|
|
178
|
+
)
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
test('a window whose walks are too many to read in full says so', async () => {
|
|
182
|
+
vi.mocked(formsCut).mockClear()
|
|
183
|
+
vi.mocked(formsCut).mockRejectedValueOnce(
|
|
184
|
+
new ExpansionTooLargeError(24_000_000, 16_000_000, 3592),
|
|
185
|
+
)
|
|
186
|
+
const error = await makeAdapter({ pinned: pinned(9) })
|
|
187
|
+
.getSourcesAndWarnings()
|
|
188
|
+
.catch((e: unknown) => e)
|
|
189
|
+
expect(formsCut).toHaveBeenCalledWith(
|
|
190
|
+
expect.anything(),
|
|
191
|
+
expect.anything(),
|
|
192
|
+
expect.objectContaining({ partial: false }),
|
|
193
|
+
)
|
|
194
|
+
expect(error).not.toHaveProperty('regionTooLarge')
|
|
195
|
+
expect(`${error}`).toBe(
|
|
196
|
+
'Error: The pinned window chr1:10-3,600 is too large to read every walk in full for structural forms: its walks take 24.0 M steps, over the 16.0 M step budget, and about 2.39Kbp of it fits. Pin a narrower window',
|
|
197
|
+
)
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
test('an adapter with no pinned window says so', async () => {
|
|
201
|
+
await expect(makeAdapter({}).getSourcesAndWarnings()).rejects.toThrow(
|
|
202
|
+
/needs `pinned`/,
|
|
203
|
+
)
|
|
204
|
+
})
|