jbrowse-plugin-msaview 3.4.1 → 3.6.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 (222) hide show
  1. package/README.md +7 -0
  2. package/dist/AddHighlightModel/index.js +3 -3
  3. package/dist/BgzipFastaMsaAdapter/BgzipFastaMsaAdapter.d.ts +8 -2
  4. package/dist/BgzipFastaMsaAdapter/configSchema.d.ts +3 -3
  5. package/dist/BgzipFastaMsaAdapter/configSchema.js +1 -1
  6. package/dist/LaunchMsaView/codingFeature.d.ts +14 -0
  7. package/dist/LaunchMsaView/codingFeature.js +38 -0
  8. package/dist/LaunchMsaView/codingFeature.test.js +75 -0
  9. package/dist/LaunchMsaView/components/BlastQuery/BlastAutomaticPanel.d.ts +3 -1
  10. package/dist/LaunchMsaView/components/BlastQuery/BlastAutomaticPanel.js +29 -26
  11. package/dist/LaunchMsaView/components/BlastQuery/BlastManualPanel.d.ts +3 -1
  12. package/dist/LaunchMsaView/components/BlastQuery/BlastManualPanel.js +7 -2
  13. package/dist/LaunchMsaView/components/BlastQuery/BlastPanel.d.ts +7 -3
  14. package/dist/LaunchMsaView/components/BlastQuery/BlastPanel.js +8 -7
  15. package/dist/LaunchMsaView/components/BlastQuery/BlastSettingsDialog.js +2 -2
  16. package/dist/LaunchMsaView/components/BlastQuery/MsaAlgorithmSelect.js +5 -3
  17. package/dist/LaunchMsaView/components/BlastQuery/consts.d.ts +26 -4
  18. package/dist/LaunchMsaView/components/BlastQuery/consts.js +68 -2
  19. package/dist/LaunchMsaView/components/BlastQuery/searchChoiceStorage.d.ts +1 -1
  20. package/dist/LaunchMsaView/components/HelpButton.d.ts +2 -0
  21. package/dist/LaunchMsaView/components/HelpButton.js +17 -0
  22. package/dist/LaunchMsaView/components/HelpDialog.d.ts +4 -0
  23. package/dist/LaunchMsaView/components/HelpDialog.js +18 -0
  24. package/dist/LaunchMsaView/components/LaunchMsaViewDialog.d.ts +3 -1
  25. package/dist/LaunchMsaView/components/LaunchMsaViewDialog.js +21 -16
  26. package/dist/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.d.ts +3 -1
  27. package/dist/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.js +10 -4
  28. package/dist/LaunchMsaView/components/ManualMSALoader/launchView.d.ts +3 -1
  29. package/dist/LaunchMsaView/components/ManualMSALoader/launchView.js +2 -1
  30. package/dist/LaunchMsaView/components/OrthologQuery/OrthologPanel.d.ts +3 -1
  31. package/dist/LaunchMsaView/components/OrthologQuery/OrthologPanel.js +21 -7
  32. package/dist/LaunchMsaView/components/OrthologQuery/OrthologSourceSelect.js +4 -1
  33. package/dist/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.d.ts +3 -1
  34. package/dist/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.js +33 -15
  35. package/dist/LaunchMsaView/components/PreLoadedMSA/preCalculatedLaunchView.d.ts +3 -1
  36. package/dist/LaunchMsaView/components/PreLoadedMSA/preCalculatedLaunchView.js +2 -1
  37. package/dist/LaunchMsaView/components/QueryRowSelector.js +21 -5
  38. package/dist/LaunchMsaView/components/SequenceStatus.d.ts +14 -0
  39. package/dist/LaunchMsaView/components/SequenceStatus.js +15 -0
  40. package/dist/LaunchMsaView/components/SubmitCancelActions.d.ts +3 -1
  41. package/dist/LaunchMsaView/components/SubmitCancelActions.js +25 -13
  42. package/dist/LaunchMsaView/components/SubmitCancelActions.test.js +33 -3
  43. package/dist/LaunchMsaView/components/TabPanel.js +15 -3
  44. package/dist/LaunchMsaView/components/TabPanel.test.d.ts +1 -0
  45. package/dist/LaunchMsaView/components/TabPanel.test.js +36 -0
  46. package/dist/LaunchMsaView/components/TranscriptSelector.d.ts +3 -1
  47. package/dist/LaunchMsaView/components/TranscriptSelector.js +4 -2
  48. package/dist/LaunchMsaView/components/calculateProteinSequence.d.ts +4 -1
  49. package/dist/LaunchMsaView/components/calculateProteinSequence.js +20 -10
  50. package/dist/LaunchMsaView/components/fetchSeq.d.ts +4 -1
  51. package/dist/LaunchMsaView/components/fetchSeq.js +14 -4
  52. package/dist/LaunchMsaView/components/launchPlacement.d.ts +8 -0
  53. package/dist/LaunchMsaView/components/launchPlacement.js +25 -0
  54. package/dist/LaunchMsaView/components/useFeatureSequence.d.ts +2 -0
  55. package/dist/LaunchMsaView/components/useFeatureSequence.js +14 -11
  56. package/dist/LaunchMsaView/components/useTranscriptSelection.d.ts +10 -1
  57. package/dist/LaunchMsaView/components/useTranscriptSelection.js +21 -3
  58. package/dist/LaunchMsaView/components/useTranscriptSelection.test.d.ts +1 -0
  59. package/dist/LaunchMsaView/components/useTranscriptSelection.test.js +57 -0
  60. package/dist/LaunchMsaView/detectQueryRow.d.ts +11 -0
  61. package/dist/LaunchMsaView/detectQueryRow.js +11 -4
  62. package/dist/LaunchMsaView/detectQueryRow.test.js +30 -0
  63. package/dist/LaunchMsaView/index.js +28 -9
  64. package/dist/LaunchMsaView/launchTarget.d.ts +36 -8
  65. package/dist/LaunchMsaView/launchTarget.js +23 -13
  66. package/dist/LaunchMsaView/launchTarget.test.js +80 -15
  67. package/dist/LaunchMsaView/useQueryRowName.d.ts +13 -0
  68. package/dist/LaunchMsaView/useQueryRowName.js +16 -1
  69. package/dist/LaunchMsaView/useQueryRowName.test.d.ts +1 -0
  70. package/dist/LaunchMsaView/useQueryRowName.test.js +40 -0
  71. package/dist/LaunchMsaView/util.js +5 -2
  72. package/dist/LaunchMsaViewExtensionPoint/index.js +8 -6
  73. package/dist/LaunchMsaViewExtensionPoint/index.test.js +21 -0
  74. package/dist/MsaViewPanel/afterCreateAutoruns.d.ts +9 -6
  75. package/dist/MsaViewPanel/afterCreateAutoruns.js +72 -28
  76. package/dist/MsaViewPanel/components/LaunchProgress.d.ts +4 -3
  77. package/dist/MsaViewPanel/components/LaunchProgress.js +22 -10
  78. package/dist/MsaViewPanel/components/MsaViewPanel.js +10 -2
  79. package/dist/MsaViewPanel/components/MsaViewPanel.test.js +47 -2
  80. package/dist/MsaViewPanel/doLaunchBlast.d.ts +8 -0
  81. package/dist/MsaViewPanel/doLaunchBlast.js +78 -93
  82. package/dist/MsaViewPanel/doLaunchBlast.test.d.ts +1 -0
  83. package/dist/MsaViewPanel/doLaunchBlast.test.js +158 -0
  84. package/dist/MsaViewPanel/doLaunchOrthologs.js +40 -5
  85. package/dist/MsaViewPanel/doLaunchOrthologs.test.js +10 -0
  86. package/dist/MsaViewPanel/genomeToMSA.js +25 -7
  87. package/dist/MsaViewPanel/genomeToMSA.test.js +214 -19
  88. package/dist/MsaViewPanel/loadProteinDomains.d.ts +8 -2
  89. package/dist/MsaViewPanel/loadProteinDomains.js +18 -9
  90. package/dist/MsaViewPanel/loadProteinDomains.test.d.ts +1 -0
  91. package/dist/MsaViewPanel/loadProteinDomains.test.js +41 -0
  92. package/dist/MsaViewPanel/model.d.ts +265 -103
  93. package/dist/MsaViewPanel/model.js +174 -48
  94. package/dist/MsaViewPanel/model.test.d.ts +1 -0
  95. package/dist/MsaViewPanel/model.test.js +166 -0
  96. package/dist/MsaViewPanel/msaCoordToGenomeCoord.d.ts +12 -1
  97. package/dist/MsaViewPanel/msaCoordToGenomeCoord.js +8 -8
  98. package/dist/MsaViewPanel/msaCoordToGenomeCoord.test.js +133 -32
  99. package/dist/MsaViewPanel/msaDataStore.d.ts +2 -0
  100. package/dist/MsaViewPanel/msaDataStore.js +10 -0
  101. package/dist/MsaViewPanel/observeProteinHighlights.test.js +20 -3
  102. package/dist/MsaViewPanel/processInit.test.d.ts +1 -0
  103. package/dist/MsaViewPanel/processInit.test.js +72 -0
  104. package/dist/MsaViewPanel/resolveConnectedTranscript.d.ts +9 -0
  105. package/dist/MsaViewPanel/resolveConnectedTranscript.js +128 -0
  106. package/dist/MsaViewPanel/runLaunch.d.ts +1 -0
  107. package/dist/MsaViewPanel/runLaunch.js +35 -0
  108. package/dist/MsaViewPanel/runLaunch.test.js +46 -0
  109. package/dist/MsaViewPanel/storedData.test.js +20 -0
  110. package/dist/MsaViewPanel/structureConnection.d.ts +0 -4
  111. package/dist/MsaViewPanel/structureConnection.js +0 -19
  112. package/dist/MsaViewPanel/syncGenomeHoverToMsaColumn.test.js +12 -5
  113. package/dist/MsaViewPanel/util.d.ts +39 -0
  114. package/dist/MsaViewPanel/util.js +44 -0
  115. package/dist/index.d.ts +11 -11
  116. package/dist/jbrowse-plugin-msaview.umd.production.min.js +56 -68
  117. package/dist/jbrowse-plugin-msaview.umd.production.min.js.map +4 -4
  118. package/dist/utils/browserAlign.d.ts +42 -0
  119. package/dist/utils/browserAlign.js +297 -0
  120. package/dist/utils/browserAlign.test.d.ts +1 -0
  121. package/dist/utils/browserAlign.test.js +85 -0
  122. package/dist/utils/ebiBlast.d.ts +10 -1
  123. package/dist/utils/ebiBlast.js +27 -1
  124. package/dist/utils/homologSearch.d.ts +31 -0
  125. package/dist/utils/homologSearch.js +6 -0
  126. package/dist/utils/msa.d.ts +3 -11
  127. package/dist/utils/msa.js +14 -29
  128. package/dist/utils/msaRows.d.ts +11 -8
  129. package/dist/utils/msaRows.js +14 -12
  130. package/dist/utils/phmmer.d.ts +11 -1
  131. package/dist/utils/phmmer.js +49 -12
  132. package/dist/utils/unirefHomologs.d.ts +94 -0
  133. package/dist/utils/unirefHomologs.js +193 -0
  134. package/dist/utils/unirefHomologs.test.d.ts +1 -0
  135. package/dist/utils/unirefHomologs.test.js +118 -0
  136. package/dist/version.d.ts +1 -1
  137. package/dist/version.js +1 -1
  138. package/package.json +23 -22
  139. package/src/AddHighlightModel/index.tsx +1 -1
  140. package/src/BgzipFastaMsaAdapter/configSchema.ts +1 -1
  141. package/src/LaunchMsaView/codingFeature.test.ts +96 -0
  142. package/src/LaunchMsaView/codingFeature.ts +49 -0
  143. package/src/LaunchMsaView/components/BlastQuery/BlastAutomaticPanel.tsx +52 -26
  144. package/src/LaunchMsaView/components/BlastQuery/BlastManualPanel.tsx +9 -1
  145. package/src/LaunchMsaView/components/BlastQuery/BlastPanel.tsx +24 -11
  146. package/src/LaunchMsaView/components/BlastQuery/BlastSettingsDialog.tsx +4 -4
  147. package/src/LaunchMsaView/components/BlastQuery/MsaAlgorithmSelect.tsx +7 -2
  148. package/src/LaunchMsaView/components/BlastQuery/consts.ts +77 -2
  149. package/src/LaunchMsaView/components/HelpButton.tsx +33 -0
  150. package/src/LaunchMsaView/components/HelpDialog.tsx +78 -0
  151. package/src/LaunchMsaView/components/LaunchMsaViewDialog.tsx +54 -34
  152. package/src/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.tsx +13 -2
  153. package/src/LaunchMsaView/components/ManualMSALoader/launchView.ts +4 -0
  154. package/src/LaunchMsaView/components/OrthologQuery/OrthologPanel.tsx +28 -5
  155. package/src/LaunchMsaView/components/OrthologQuery/OrthologSourceSelect.tsx +4 -1
  156. package/src/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.tsx +63 -13
  157. package/src/LaunchMsaView/components/PreLoadedMSA/preCalculatedLaunchView.ts +4 -0
  158. package/src/LaunchMsaView/components/QueryRowSelector.tsx +35 -7
  159. package/src/LaunchMsaView/components/SequenceStatus.tsx +29 -0
  160. package/src/LaunchMsaView/components/SubmitCancelActions.test.tsx +62 -3
  161. package/src/LaunchMsaView/components/SubmitCancelActions.tsx +37 -15
  162. package/src/LaunchMsaView/components/TabPanel.test.tsx +51 -0
  163. package/src/LaunchMsaView/components/TabPanel.tsx +16 -4
  164. package/src/LaunchMsaView/components/TranscriptSelector.tsx +6 -1
  165. package/src/LaunchMsaView/components/calculateProteinSequence.ts +29 -10
  166. package/src/LaunchMsaView/components/fetchSeq.ts +25 -5
  167. package/src/LaunchMsaView/components/launchPlacement.tsx +41 -0
  168. package/src/LaunchMsaView/components/useFeatureSequence.ts +18 -11
  169. package/src/LaunchMsaView/components/useTranscriptSelection.test.tsx +66 -0
  170. package/src/LaunchMsaView/components/useTranscriptSelection.ts +41 -2
  171. package/src/LaunchMsaView/detectQueryRow.test.ts +34 -0
  172. package/src/LaunchMsaView/detectQueryRow.ts +22 -4
  173. package/src/LaunchMsaView/index.ts +32 -13
  174. package/src/LaunchMsaView/launchTarget.test.ts +88 -15
  175. package/src/LaunchMsaView/launchTarget.ts +49 -14
  176. package/src/LaunchMsaView/useQueryRowName.test.ts +54 -0
  177. package/src/LaunchMsaView/useQueryRowName.ts +22 -1
  178. package/src/LaunchMsaView/util.ts +8 -4
  179. package/src/LaunchMsaViewExtensionPoint/index.test.ts +23 -0
  180. package/src/LaunchMsaViewExtensionPoint/index.ts +32 -6
  181. package/src/MsaViewPanel/afterCreateAutoruns.ts +85 -27
  182. package/src/MsaViewPanel/components/LaunchProgress.tsx +38 -7
  183. package/src/MsaViewPanel/components/MsaViewPanel.test.tsx +50 -2
  184. package/src/MsaViewPanel/components/MsaViewPanel.tsx +10 -2
  185. package/src/MsaViewPanel/doLaunchBlast.test.ts +207 -0
  186. package/src/MsaViewPanel/doLaunchBlast.ts +92 -142
  187. package/src/MsaViewPanel/doLaunchOrthologs.test.ts +13 -0
  188. package/src/MsaViewPanel/doLaunchOrthologs.ts +55 -5
  189. package/src/MsaViewPanel/genomeToMSA.test.ts +251 -31
  190. package/src/MsaViewPanel/genomeToMSA.ts +35 -7
  191. package/src/MsaViewPanel/loadProteinDomains.test.ts +52 -0
  192. package/src/MsaViewPanel/loadProteinDomains.ts +24 -11
  193. package/src/MsaViewPanel/model.test.ts +196 -0
  194. package/src/MsaViewPanel/model.ts +204 -55
  195. package/src/MsaViewPanel/msaCoordToGenomeCoord.test.ts +152 -32
  196. package/src/MsaViewPanel/msaCoordToGenomeCoord.ts +21 -11
  197. package/src/MsaViewPanel/msaDataStore.ts +10 -0
  198. package/src/MsaViewPanel/observeProteinHighlights.test.ts +27 -3
  199. package/src/MsaViewPanel/processInit.test.ts +84 -0
  200. package/src/MsaViewPanel/resolveConnectedTranscript.ts +160 -0
  201. package/src/MsaViewPanel/runLaunch.test.ts +54 -0
  202. package/src/MsaViewPanel/runLaunch.ts +39 -0
  203. package/src/MsaViewPanel/storedData.test.ts +26 -0
  204. package/src/MsaViewPanel/structureConnection.ts +0 -26
  205. package/src/MsaViewPanel/syncGenomeHoverToMsaColumn.test.ts +14 -5
  206. package/src/MsaViewPanel/util.ts +75 -0
  207. package/src/utils/browserAlign.test.ts +102 -0
  208. package/src/utils/browserAlign.ts +352 -0
  209. package/src/utils/ebiBlast.ts +34 -0
  210. package/src/utils/homologSearch.ts +51 -0
  211. package/src/utils/msa.ts +19 -41
  212. package/src/utils/msaRows.ts +20 -16
  213. package/src/utils/phmmer.ts +57 -11
  214. package/src/utils/unirefHomologs.test.ts +149 -0
  215. package/src/utils/unirefHomologs.ts +323 -0
  216. package/src/version.ts +1 -1
  217. package/dist/LaunchMsaView/components/geneticCodes.d.ts +0 -15
  218. package/dist/LaunchMsaView/components/geneticCodes.js +0 -227
  219. package/dist/MsaViewPanel/structureConnection.test.js +0 -53
  220. package/src/LaunchMsaView/components/geneticCodes.ts +0 -298
  221. package/src/MsaViewPanel/structureConnection.test.ts +0 -62
  222. /package/dist/{MsaViewPanel/structureConnection.test.d.ts → LaunchMsaView/codingFeature.test.d.ts} +0 -0
@@ -1,7 +1,7 @@
1
1
  import { getSession } from '@jbrowse/core/util';
2
- import { hasHoverPosition, hasQueryRow } from './util';
2
+ import { hasHoverPosition, hasQueryRow, transcriptPosToVisibleCol, } from './util';
3
3
  export function genomeToMSA({ model }) {
4
- const { hovered } = getSession(model);
4
+ const { assemblyManager, hovered } = getSession(model);
5
5
  const { querySeqName, transcriptToMsaMap, connectedView, mafRegion } = model;
6
6
  if (!connectedView?.initialized ||
7
7
  !hasHoverPosition(hovered) ||
@@ -12,8 +12,25 @@ export function genomeToMSA({ model }) {
12
12
  // hoverPosition.coord is a 1-based display coordinate (core's pxToBp adds the
13
13
  // +1), while g2p and mafRegion are keyed by 0-based genome position
14
14
  const genomePos = coord - 1;
15
+ // The two sides name the chromosome differently. A hover carries the
16
+ // assembly's canonical name -- `1` on jbrowse.org's hg38 -- while mafRegion
17
+ // and the transcript's g2p map carry the feature's, straight out of the
18
+ // annotation file, which is `chr1` in GENCODE. Compared raw they agree only
19
+ // where a config happens to pair files that agree, and everywhere else
20
+ // hovering a codon lit nothing at all, with no throw and no console line.
21
+ //
22
+ // `initialized` is the gate rather than a try/catch: getCanonicalRefName
23
+ // throws until the assembly's aliases load, and this runs from a hover
24
+ // autorun over an assembly nothing here waited for -- mafRegion names its own
25
+ // on a frozen spec, which need not be one the connected view loaded. Until
26
+ // the aliases arrive the raw name is the whole answer, and the hover lights
27
+ // nothing for those frames rather than throwing out of the autorun.
28
+ const assembly = assemblyManager.get(mafRegion?.assemblyName ?? connectedView.assemblyNames[0] ?? '');
29
+ const canonical = (name) => (assembly?.initialized ? assembly.getCanonicalRefName(name) : undefined) ??
30
+ name;
31
+ const hoveredRefName = canonical(refName);
15
32
  if (mafRegion) {
16
- if (refName !== mafRegion.refName ||
33
+ if (hoveredRefName !== canonical(mafRegion.refName) ||
17
34
  !connectedView.assemblyNames.includes(mafRegion.assemblyName) ||
18
35
  genomePos < mafRegion.start ||
19
36
  genomePos >= mafRegion.end) {
@@ -25,10 +42,11 @@ export function genomeToMSA({ model }) {
25
42
  // was last over, on any assembly -- so the refName gate is load bearing:
26
43
  // without it the same numeric coordinate on an unrelated chromosome matches a
27
44
  // g2p key and lights up a column for a different locus
28
- if (refName === transcriptToMsaMap?.refName) {
29
- const seqPos = transcriptToMsaMap.g2p[genomePos];
30
- if (seqPos !== undefined) {
31
- return model.seqPosToVisibleCol(querySeqName, seqPos);
45
+ if (transcriptToMsaMap &&
46
+ hoveredRefName === canonical(transcriptToMsaMap.refName)) {
47
+ const proteinPos = transcriptToMsaMap.g2p[genomePos];
48
+ if (proteinPos !== undefined) {
49
+ return transcriptPosToVisibleCol(model, proteinPos);
32
50
  }
33
51
  }
34
52
  return undefined;
@@ -6,12 +6,40 @@ vi.mock('@jbrowse/core/util', () => ({
6
6
  getSession: vi.fn(),
7
7
  }));
8
8
  const mockGetSession = vi.mocked(getSession);
9
+ // What the assembly calls each chromosome. Empty means every name is already
10
+ // canonical, which is the config where the raw comparison this replaced
11
+ // happened to work.
12
+ let aliases = {};
13
+ // core's assembly throws from getCanonicalRefName until its aliases load, and
14
+ // answers `initialized` false until then -- so the stand-in throws too, or the
15
+ // guard is asserted against a method that never had the failure mode
16
+ let assemblyInitialized = true;
17
+ function mockSession(session) {
18
+ mockGetSession.mockReturnValue({
19
+ assemblyManager: {
20
+ get: () => ({
21
+ get initialized() {
22
+ return assemblyInitialized;
23
+ },
24
+ getCanonicalRefName: (refName) => {
25
+ if (!assemblyInitialized) {
26
+ throw new Error('aliases not loaded');
27
+ }
28
+ return aliases[refName] ?? refName;
29
+ },
30
+ }),
31
+ },
32
+ ...session,
33
+ });
34
+ }
9
35
  describe('genomeToMSA', () => {
10
36
  beforeEach(() => {
11
37
  vi.clearAllMocks();
38
+ aliases = {};
39
+ assemblyInitialized = true;
12
40
  });
13
41
  test('returns undefined when connectedView is not initialized', () => {
14
- mockGetSession.mockReturnValue({
42
+ mockSession({
15
43
  hovered: {
16
44
  hoverFeature: {},
17
45
  hoverPosition: { coord: 1005, refName: 'chr1' },
@@ -27,14 +55,14 @@ describe('genomeToMSA', () => {
27
55
  end: 1010,
28
56
  assemblyName: 'hg38',
29
57
  },
30
- connectedView: { initialized: false },
58
+ connectedView: { initialized: false, assemblyNames: ['hg38'] },
31
59
  seqPosToVisibleCol: vi.fn(),
32
60
  };
33
61
  const result = genomeToMSA({ model });
34
62
  expect(result).toBeUndefined();
35
63
  });
36
64
  test('returns undefined when hovered is not valid', () => {
37
- mockGetSession.mockReturnValue({
65
+ mockSession({
38
66
  hovered: null,
39
67
  });
40
68
  const model = {
@@ -47,7 +75,7 @@ describe('genomeToMSA', () => {
47
75
  end: 1010,
48
76
  assemblyName: 'hg38',
49
77
  },
50
- connectedView: { initialized: true },
78
+ connectedView: { initialized: true, assemblyNames: ['hg38'] },
51
79
  seqPosToVisibleCol: vi.fn(),
52
80
  };
53
81
  const result = genomeToMSA({ model });
@@ -55,7 +83,7 @@ describe('genomeToMSA', () => {
55
83
  });
56
84
  describe('mafRegion mapping', () => {
57
85
  test('returns visible column for valid hover within mafRegion', () => {
58
- mockGetSession.mockReturnValue({
86
+ mockSession({
59
87
  hovered: {
60
88
  hoverFeature: {},
61
89
  hoverPosition: { coord: 1005, refName: 'chr1' },
@@ -85,7 +113,7 @@ describe('genomeToMSA', () => {
85
113
  expect(result).toBe(5);
86
114
  });
87
115
  test('returns undefined when hover refName does not match mafRegion', () => {
88
- mockGetSession.mockReturnValue({
116
+ mockSession({
89
117
  hovered: {
90
118
  hoverFeature: {},
91
119
  hoverPosition: { coord: 1005, refName: 'chr2' },
@@ -112,7 +140,7 @@ describe('genomeToMSA', () => {
112
140
  });
113
141
  test('returns undefined when hover coord is before mafRegion start', () => {
114
142
  // 1-based coord 1000 is the 0-based base 999, one before the region
115
- mockGetSession.mockReturnValue({
143
+ mockSession({
116
144
  hovered: {
117
145
  hoverFeature: {},
118
146
  hoverPosition: { coord: 1000, refName: 'chr1' },
@@ -139,7 +167,7 @@ describe('genomeToMSA', () => {
139
167
  });
140
168
  test('returns undefined when hover coord is at or after mafRegion end', () => {
141
169
  // 1-based coord 1011 is the 0-based base 1010, one past the region
142
- mockGetSession.mockReturnValue({
170
+ mockSession({
143
171
  hovered: {
144
172
  hoverFeature: {},
145
173
  hoverPosition: { coord: 1011, refName: 'chr1' },
@@ -165,7 +193,7 @@ describe('genomeToMSA', () => {
165
193
  expect(result).toBeUndefined();
166
194
  });
167
195
  test('returns undefined when assembly does not match', () => {
168
- mockGetSession.mockReturnValue({
196
+ mockSession({
169
197
  hovered: {
170
198
  hoverFeature: {},
171
199
  hoverPosition: { coord: 1005, refName: 'chr1' },
@@ -193,7 +221,7 @@ describe('genomeToMSA', () => {
193
221
  });
194
222
  describe('transcriptToMsaMap mapping (original behavior)', () => {
195
223
  test('returns visible column using g2p mapping', () => {
196
- mockGetSession.mockReturnValue({
224
+ mockSession({
197
225
  hovered: {
198
226
  hoverFeature: {},
199
227
  hoverPosition: { coord: 1005, refName: 'chr1' },
@@ -202,6 +230,7 @@ describe('genomeToMSA', () => {
202
230
  const mockSeqPosToVisibleCol = vi.fn().mockReturnValue(10);
203
231
  const model = {
204
232
  querySeqName: 'QUERY',
233
+ querySeqOffset: 0,
205
234
  rows: [['QUERY', 'MKVLTAEEK']],
206
235
  transcriptToMsaMap: {
207
236
  refName: 'chr1',
@@ -209,8 +238,9 @@ describe('genomeToMSA', () => {
209
238
  g2p: { 1004: 10 },
210
239
  },
211
240
  mafRegion: undefined,
212
- connectedView: { initialized: true },
241
+ connectedView: { initialized: true, assemblyNames: ['hg38'] },
213
242
  seqPosToVisibleCol: mockSeqPosToVisibleCol,
243
+ visibleColToSeqPos: (_name, col) => col,
214
244
  };
215
245
  const result = genomeToMSA({ model });
216
246
  expect(mockSeqPosToVisibleCol).toHaveBeenCalledWith('QUERY', 10);
@@ -219,7 +249,7 @@ describe('genomeToMSA', () => {
219
249
  test('returns undefined when the hover is on another refName', () => {
220
250
  // session.hovered is global, so a hover on an unrelated chromosome can
221
251
  // carry a coordinate that happens to be a g2p key
222
- mockGetSession.mockReturnValue({
252
+ mockSession({
223
253
  hovered: {
224
254
  hoverFeature: {},
225
255
  hoverPosition: { coord: 1005, refName: 'chr2' },
@@ -234,14 +264,79 @@ describe('genomeToMSA', () => {
234
264
  g2p: { 1004: 10 },
235
265
  },
236
266
  mafRegion: undefined,
237
- connectedView: { initialized: true },
267
+ connectedView: { initialized: true, assemblyNames: ['hg38'] },
238
268
  seqPosToVisibleCol: mockSeqPosToVisibleCol,
239
269
  };
240
270
  expect(genomeToMSA({ model })).toBeUndefined();
241
271
  expect(mockSeqPosToVisibleCol).not.toHaveBeenCalled();
242
272
  });
273
+ // a pasted alignment often carries only the region BLAST aligned, and
274
+ // querySeqOffset says how much of the protein comes before it
275
+ test('shifts a trimmed query row by querySeqOffset', () => {
276
+ mockSession({
277
+ hovered: {
278
+ hoverFeature: {},
279
+ hoverPosition: { coord: 1005, refName: 'chr1' },
280
+ },
281
+ });
282
+ const seqPosToVisibleCol = vi.fn((_name, pos) => pos);
283
+ const model = {
284
+ querySeqName: 'QUERY',
285
+ querySeqOffset: 4,
286
+ rows: [['QUERY', 'MKVLTAEEK']],
287
+ transcriptToMsaMap: { refName: 'chr1', g2p: { 1004: 10 } },
288
+ mafRegion: undefined,
289
+ connectedView: { initialized: true, assemblyNames: ['hg38'] },
290
+ seqPosToVisibleCol,
291
+ visibleColToSeqPos: (_name, col) => col,
292
+ };
293
+ expect(genomeToMSA({ model })).toBe(6);
294
+ expect(seqPosToVisibleCol).toHaveBeenCalledWith('QUERY', 6);
295
+ });
296
+ test('maps nothing for a residue before the trimmed row starts', () => {
297
+ mockSession({
298
+ hovered: {
299
+ hoverFeature: {},
300
+ hoverPosition: { coord: 1005, refName: 'chr1' },
301
+ },
302
+ });
303
+ const seqPosToVisibleCol = vi.fn((_name, pos) => pos);
304
+ const model = {
305
+ querySeqName: 'QUERY',
306
+ querySeqOffset: 20,
307
+ rows: [['QUERY', 'MKVLTAEEK']],
308
+ transcriptToMsaMap: { refName: 'chr1', g2p: { 1004: 10 } },
309
+ mafRegion: undefined,
310
+ connectedView: { initialized: true, assemblyNames: ['hg38'] },
311
+ seqPosToVisibleCol,
312
+ visibleColToSeqPos: (_name, col) => col,
313
+ };
314
+ expect(genomeToMSA({ model })).toBeUndefined();
315
+ expect(seqPosToVisibleCol).not.toHaveBeenCalled();
316
+ });
317
+ // react-msaview answers one column past the end for a residue the row does
318
+ // not have, which would light the last column for everything beyond it
319
+ test('maps nothing for a residue the row stops short of', () => {
320
+ mockSession({
321
+ hovered: {
322
+ hoverFeature: {},
323
+ hoverPosition: { coord: 1005, refName: 'chr1' },
324
+ },
325
+ });
326
+ const model = {
327
+ querySeqName: 'QUERY',
328
+ querySeqOffset: 0,
329
+ rows: [['QUERY', 'MKV']],
330
+ transcriptToMsaMap: { refName: 'chr1', g2p: { 1004: 10 } },
331
+ mafRegion: undefined,
332
+ connectedView: { initialized: true, assemblyNames: ['hg38'] },
333
+ seqPosToVisibleCol: () => 3,
334
+ visibleColToSeqPos: () => undefined,
335
+ };
336
+ expect(genomeToMSA({ model })).toBeUndefined();
337
+ });
243
338
  test('returns undefined when g2p has no mapping for coord', () => {
244
- mockGetSession.mockReturnValue({
339
+ mockSession({
245
340
  hovered: {
246
341
  hoverFeature: {},
247
342
  hoverPosition: { coord: 1005, refName: 'chr1' },
@@ -255,7 +350,7 @@ describe('genomeToMSA', () => {
255
350
  g2p: { 1000: 0 }, // No entry for 1004
256
351
  },
257
352
  mafRegion: undefined,
258
- connectedView: { initialized: true },
353
+ connectedView: { initialized: true, assemblyNames: ['hg38'] },
259
354
  seqPosToVisibleCol: vi.fn(),
260
355
  };
261
356
  const result = genomeToMSA({ model });
@@ -263,7 +358,7 @@ describe('genomeToMSA', () => {
263
358
  });
264
359
  });
265
360
  test('returns undefined when neither mafRegion nor transcriptToMsaMap is set', () => {
266
- mockGetSession.mockReturnValue({
361
+ mockSession({
267
362
  hovered: {
268
363
  hoverFeature: {},
269
364
  hoverPosition: { coord: 1005, refName: 'chr1' },
@@ -274,7 +369,7 @@ describe('genomeToMSA', () => {
274
369
  rows: [['QUERY', 'MKVLTAEEK']],
275
370
  transcriptToMsaMap: undefined,
276
371
  mafRegion: undefined,
277
- connectedView: { initialized: true },
372
+ connectedView: { initialized: true, assemblyNames: ['hg38'] },
278
373
  seqPosToVisibleCol: vi.fn(),
279
374
  };
280
375
  const result = genomeToMSA({ model });
@@ -285,7 +380,7 @@ describe('genomeToMSA', () => {
285
380
  // uploaded file, or the empty name the manual panel leaves when it matches
286
381
  // nothing -- lights column 0 on every genome hover
287
382
  test('returns undefined when querySeqName names no row here', () => {
288
- mockGetSession.mockReturnValue({
383
+ mockSession({
289
384
  hovered: {
290
385
  hoverFeature: {},
291
386
  hoverPosition: { coord: 1005, refName: 'chr1' },
@@ -297,10 +392,110 @@ describe('genomeToMSA', () => {
297
392
  rows: [['some_other_row', 'MKVLTAEEK']],
298
393
  transcriptToMsaMap: { refName: 'chr1', g2p: { 1004: 3 } },
299
394
  mafRegion: undefined,
300
- connectedView: { initialized: true },
395
+ connectedView: { initialized: true, assemblyNames: ['hg38'] },
301
396
  seqPosToVisibleCol,
302
397
  };
303
398
  expect(genomeToMSA({ model })).toBeUndefined();
304
399
  expect(seqPosToVisibleCol).not.toHaveBeenCalled();
305
400
  });
401
+ // A hover names the chromosome the assembly's way and the transcript names it
402
+ // the annotation file's way, so on GENCODE-on-hg38 -- jbrowse.org's own
403
+ // config -- the two are '1' and 'chr1' and pointing at a codon lit nothing.
404
+ describe('a config whose assembly and annotation spell the refName apart', () => {
405
+ beforeEach(() => {
406
+ aliases = { chr1: '1', chr2: '2' };
407
+ });
408
+ test('maps a hover named 1 onto a transcript named chr1', () => {
409
+ mockSession({
410
+ hovered: {
411
+ hoverFeature: {},
412
+ hoverPosition: { coord: 1005, refName: '1' },
413
+ },
414
+ });
415
+ const seqPosToVisibleCol = vi.fn().mockReturnValue(10);
416
+ const model = {
417
+ querySeqName: 'QUERY',
418
+ querySeqOffset: 0,
419
+ rows: [['QUERY', 'MKVLTAEEK']],
420
+ transcriptToMsaMap: { refName: 'chr1', g2p: { 1004: 10 } },
421
+ mafRegion: undefined,
422
+ connectedView: { initialized: true, assemblyNames: ['hg38'] },
423
+ seqPosToVisibleCol,
424
+ visibleColToSeqPos: (_name, col) => col,
425
+ };
426
+ expect(genomeToMSA({ model })).toBe(10);
427
+ expect(seqPosToVisibleCol).toHaveBeenCalledWith('QUERY', 10);
428
+ });
429
+ // the gate still has to reject another chromosome, which is the whole
430
+ // reason it is there
431
+ test('still rejects a hover on another chromosome', () => {
432
+ mockSession({
433
+ hovered: {
434
+ hoverFeature: {},
435
+ hoverPosition: { coord: 1005, refName: '2' },
436
+ },
437
+ });
438
+ const seqPosToVisibleCol = vi.fn();
439
+ const model = {
440
+ querySeqName: 'QUERY',
441
+ rows: [['QUERY', 'MKVLTAEEK']],
442
+ transcriptToMsaMap: { refName: 'chr1', g2p: { 1004: 10 } },
443
+ mafRegion: undefined,
444
+ connectedView: { initialized: true, assemblyNames: ['hg38'] },
445
+ seqPosToVisibleCol,
446
+ };
447
+ expect(genomeToMSA({ model })).toBeUndefined();
448
+ expect(seqPosToVisibleCol).not.toHaveBeenCalled();
449
+ });
450
+ // this runs from a hover autorun, and mafRegion names an assembly off a
451
+ // frozen spec that nothing here waited for -- so the aliases may not be
452
+ // loaded, and asking for them throws
453
+ test('falls back to the raw name while the aliases are still loading', () => {
454
+ assemblyInitialized = false;
455
+ mockSession({
456
+ hovered: {
457
+ hoverFeature: {},
458
+ hoverPosition: { coord: 1005, refName: 'chr1' },
459
+ },
460
+ });
461
+ const seqPosToVisibleCol = vi.fn().mockReturnValue(10);
462
+ const model = {
463
+ querySeqName: 'QUERY',
464
+ querySeqOffset: 0,
465
+ rows: [['QUERY', 'MKVLTAEEK']],
466
+ transcriptToMsaMap: { refName: 'chr1', g2p: { 1004: 10 } },
467
+ mafRegion: undefined,
468
+ connectedView: { initialized: true, assemblyNames: ['hg38'] },
469
+ seqPosToVisibleCol,
470
+ visibleColToSeqPos: (_name, col) => col,
471
+ };
472
+ expect(() => genomeToMSA({ model })).not.toThrow();
473
+ // both sides spell it 'chr1' here, so the raw comparison still answers
474
+ expect(genomeToMSA({ model })).toBe(10);
475
+ });
476
+ test('maps a hover named 1 into a mafRegion named chr1', () => {
477
+ mockSession({
478
+ hovered: {
479
+ hoverFeature: {},
480
+ hoverPosition: { coord: 1005, refName: '1' },
481
+ },
482
+ });
483
+ const seqPosToVisibleCol = vi.fn().mockReturnValue(5);
484
+ const model = {
485
+ querySeqName: 'hg38.chr1',
486
+ rows: [['hg38.chr1', 'ACGTACGTAC']],
487
+ transcriptToMsaMap: undefined,
488
+ mafRegion: {
489
+ refName: 'chr1',
490
+ start: 1000,
491
+ end: 1010,
492
+ assemblyName: 'hg38',
493
+ },
494
+ connectedView: { initialized: true, assemblyNames: ['hg38'] },
495
+ seqPosToVisibleCol,
496
+ };
497
+ expect(genomeToMSA({ model })).toBe(5);
498
+ expect(seqPosToVisibleCol).toHaveBeenCalledWith('hg38.chr1', 4);
499
+ });
500
+ });
306
501
  });
@@ -1,16 +1,22 @@
1
- import type { InterProScanResults } from 'react-msaview';
1
+ import type { Annotation } from 'react-msaview';
2
2
  interface DomainModel {
3
3
  data: {
4
4
  treeMetadata?: string;
5
5
  };
6
6
  setProgress: (arg: string) => void;
7
- setDomains: (data: Record<string, InterProScanResults>) => void;
7
+ setAnnotations: (annotations: Annotation[]) => void;
8
8
  }
9
9
  /**
10
10
  * Overlay protein domains on the alignment using NCBI's pre-computed CDD
11
11
  * annotations. The BLAST workflow stores each hit's accession in treeMetadata,
12
12
  * so we look those up via efetch and key the results by MSA row name (which is
13
13
  * what react-msaview matches domains against).
14
+ *
15
+ * The overlay is handed over as `Annotation[]`, the shape every source flattens
16
+ * to. It used to be dressed up as an InterProScan response — an `xref` invented
17
+ * to carry the row name — so that `setDomains` could unwrap it again; that entry
18
+ * point exists for plugins holding the actual EBI wire format, which this is
19
+ * not.
14
20
  */
15
21
  export declare function loadProteinDomains(self: DomainModel): Promise<void>;
16
22
  export {};
@@ -4,6 +4,12 @@ import { fetchProteinDomains } from '../utils/ncbiDomains';
4
4
  * annotations. The BLAST workflow stores each hit's accession in treeMetadata,
5
5
  * so we look those up via efetch and key the results by MSA row name (which is
6
6
  * what react-msaview matches domains against).
7
+ *
8
+ * The overlay is handed over as `Annotation[]`, the shape every source flattens
9
+ * to. It used to be dressed up as an InterProScan response — an `xref` invented
10
+ * to carry the row name — so that `setDomains` could unwrap it again; that entry
11
+ * point exists for plugins holding the actual EBI wire format, which this is
12
+ * not.
7
13
  */
8
14
  export async function loadProteinDomains(self) {
9
15
  const metadataJson = self.data.treeMetadata;
@@ -19,15 +25,18 @@ export async function loadProteinDomains(self) {
19
25
  }
20
26
  self.setProgress(`Fetching protein domains from NCBI for ${rowAccessions.length} sequences...`);
21
27
  const byAccession = await fetchProteinDomains(rowAccessions.map(r => r.accession));
22
- const annotations = {};
23
- for (const { rowName, accession } of rowAccessions) {
24
- const matches = byAccession.get(accession);
25
- if (matches && matches.length > 0) {
26
- annotations[rowName] = { matches, xref: [{ id: rowName }] };
27
- }
28
- }
29
- if (Object.keys(annotations).length === 0) {
28
+ const annotations = rowAccessions.flatMap(({ rowName, accession }) => (byAccession.get(accession) ?? []).flatMap(({ signature, locations }) => signature.entry
29
+ ? locations.map(({ start, end }) => ({
30
+ id: rowName,
31
+ accession: signature.entry.accession,
32
+ name: signature.entry.name,
33
+ description: signature.entry.description,
34
+ start,
35
+ end,
36
+ }))
37
+ : []));
38
+ if (annotations.length === 0) {
30
39
  throw new Error('No CDD domain annotations found for these proteins');
31
40
  }
32
- self.setDomains(annotations);
41
+ self.setAnnotations(annotations);
33
42
  }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,41 @@
1
+ import { expect, test, vi } from 'vitest';
2
+ import { fetchProteinDomains } from '../utils/ncbiDomains';
3
+ import { loadProteinDomains } from './loadProteinDomains';
4
+ vi.mock('../utils/ncbiDomains', () => ({ fetchProteinDomains: vi.fn() }));
5
+ const MATCH = {
6
+ signature: {
7
+ entry: { accession: 'cd00093', name: 'HTH_XRE', description: 'a domain' },
8
+ },
9
+ locations: [{ start: 5, end: 40 }],
10
+ };
11
+ function model(treeMetadata) {
12
+ return {
13
+ data: { treeMetadata: JSON.stringify(treeMetadata) },
14
+ setProgress: vi.fn(),
15
+ setAnnotations: vi.fn(),
16
+ };
17
+ }
18
+ test('an accession row becomes one annotation per location, keyed by row name', async () => {
19
+ vi.mocked(fetchProteinDomains).mockResolvedValue(new Map([['NP_1', [MATCH]]]));
20
+ const self = model({
21
+ QUERY: { Accession: 'NP_1' },
22
+ other: { 'Scientific name': 'Mus musculus' },
23
+ });
24
+ await loadProteinDomains(self);
25
+ expect(self.setAnnotations).toHaveBeenCalledWith([
26
+ {
27
+ id: 'QUERY',
28
+ accession: 'cd00093',
29
+ name: 'HTH_XRE',
30
+ description: 'a domain',
31
+ start: 5,
32
+ end: 40,
33
+ },
34
+ ]);
35
+ });
36
+ test('says so rather than clearing the overlay when NCBI has nothing', async () => {
37
+ vi.mocked(fetchProteinDomains).mockResolvedValue(new Map());
38
+ const self = model({ QUERY: { Accession: 'NP_1' } });
39
+ await expect(loadProteinDomains(self)).rejects.toThrow('No CDD domain annotations found');
40
+ expect(self.setAnnotations).not.toHaveBeenCalled();
41
+ });