jbrowse-plugin-msaview 3.5.0 → 3.7.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 (242) hide show
  1. package/README.md +8 -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/cleanProteinSequence.test.d.ts +1 -0
  7. package/dist/LaunchMsaView/cleanProteinSequence.test.js +77 -0
  8. package/dist/LaunchMsaView/codingFeature.d.ts +14 -0
  9. package/dist/LaunchMsaView/codingFeature.js +38 -0
  10. package/dist/LaunchMsaView/codingFeature.test.d.ts +1 -0
  11. package/dist/LaunchMsaView/codingFeature.test.js +75 -0
  12. package/dist/LaunchMsaView/components/BlastQuery/BlastAutomaticPanel.d.ts +3 -1
  13. package/dist/LaunchMsaView/components/BlastQuery/BlastAutomaticPanel.js +47 -55
  14. package/dist/LaunchMsaView/components/BlastQuery/BlastManualPanel.d.ts +3 -1
  15. package/dist/LaunchMsaView/components/BlastQuery/BlastManualPanel.js +16 -17
  16. package/dist/LaunchMsaView/components/BlastQuery/BlastPanel.d.ts +7 -3
  17. package/dist/LaunchMsaView/components/BlastQuery/BlastPanel.js +8 -7
  18. package/dist/LaunchMsaView/components/BlastQuery/BlastSettingsDialog.js +2 -2
  19. package/dist/LaunchMsaView/components/BlastQuery/CachedBlastResults.js +14 -13
  20. package/dist/LaunchMsaView/components/BlastQuery/consts.d.ts +2 -0
  21. package/dist/LaunchMsaView/components/BlastQuery/consts.js +28 -4
  22. package/dist/LaunchMsaView/components/HelpButton.d.ts +2 -0
  23. package/dist/LaunchMsaView/components/HelpButton.js +17 -0
  24. package/dist/LaunchMsaView/components/HelpDialog.d.ts +4 -0
  25. package/dist/LaunchMsaView/components/HelpDialog.js +18 -0
  26. package/dist/LaunchMsaView/components/LaunchMsaViewDialog.d.ts +3 -1
  27. package/dist/LaunchMsaView/components/LaunchMsaViewDialog.js +21 -16
  28. package/dist/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.d.ts +3 -1
  29. package/dist/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.js +34 -26
  30. package/dist/LaunchMsaView/components/OrthologQuery/OrthologPanel.d.ts +3 -1
  31. package/dist/LaunchMsaView/components/OrthologQuery/OrthologPanel.js +30 -26
  32. package/dist/LaunchMsaView/components/OrthologQuery/OrthologSourceSelect.d.ts +2 -1
  33. package/dist/LaunchMsaView/components/OrthologQuery/OrthologSourceSelect.js +18 -2
  34. package/dist/LaunchMsaView/components/OrthologQuery/OrthologSourceSelect.test.d.ts +1 -0
  35. package/dist/LaunchMsaView/components/OrthologQuery/OrthologSourceSelect.test.js +13 -0
  36. package/dist/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.d.ts +3 -1
  37. package/dist/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.js +50 -27
  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 +6 -2
  41. package/dist/LaunchMsaView/components/SubmitCancelActions.js +28 -13
  42. package/dist/LaunchMsaView/components/SubmitCancelActions.test.js +52 -4
  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 +12 -2
  49. package/dist/LaunchMsaView/components/calculateProteinSequence.js +38 -23
  50. package/dist/LaunchMsaView/components/fetchSeq.d.ts +4 -1
  51. package/dist/LaunchMsaView/components/fetchSeq.js +14 -4
  52. package/dist/LaunchMsaView/components/launchConnectedView.d.ts +23 -0
  53. package/dist/LaunchMsaView/components/launchConnectedView.js +39 -0
  54. package/dist/LaunchMsaView/components/launchPlacement.d.ts +8 -0
  55. package/dist/LaunchMsaView/components/launchPlacement.js +25 -0
  56. package/dist/LaunchMsaView/components/useFeatureSequence.d.ts +2 -0
  57. package/dist/LaunchMsaView/components/useFeatureSequence.js +14 -11
  58. package/dist/LaunchMsaView/components/useTranscriptSelection.d.ts +10 -1
  59. package/dist/LaunchMsaView/components/useTranscriptSelection.js +21 -3
  60. package/dist/LaunchMsaView/components/useTranscriptSelection.test.d.ts +1 -0
  61. package/dist/LaunchMsaView/components/useTranscriptSelection.test.js +57 -0
  62. package/dist/LaunchMsaView/detectQueryRow.js +4 -7
  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 +12 -0
  68. package/dist/LaunchMsaView/useQueryRowName.js +11 -0
  69. package/dist/LaunchMsaView/useQueryRowName.test.js +18 -1
  70. package/dist/LaunchMsaView/util.d.ts +6 -0
  71. package/dist/LaunchMsaView/util.js +12 -3
  72. package/dist/LaunchMsaViewExtensionPoint/index.test.js +15 -0
  73. package/dist/MsaViewPanel/afterCreateAutoruns.d.ts +6 -4
  74. package/dist/MsaViewPanel/afterCreateAutoruns.js +38 -14
  75. package/dist/MsaViewPanel/applyRegion.d.ts +23 -0
  76. package/dist/MsaViewPanel/applyRegion.js +19 -0
  77. package/dist/MsaViewPanel/applyRegion.test.d.ts +1 -0
  78. package/dist/MsaViewPanel/applyRegion.test.js +45 -0
  79. package/dist/MsaViewPanel/components/LaunchProgress.d.ts +4 -3
  80. package/dist/MsaViewPanel/components/LaunchProgress.js +10 -7
  81. package/dist/MsaViewPanel/components/MsaViewPanel.js +7 -3
  82. package/dist/MsaViewPanel/components/MsaViewPanel.test.js +24 -0
  83. package/dist/MsaViewPanel/doLaunchBlast.js +2 -1
  84. package/dist/MsaViewPanel/doLaunchBlast.test.js +12 -0
  85. package/dist/MsaViewPanel/doLaunchOrthologs.test.js +1 -1
  86. package/dist/MsaViewPanel/genomeToMSA.js +24 -6
  87. package/dist/MsaViewPanel/genomeToMSA.test.js +218 -44
  88. package/dist/MsaViewPanel/launchAutoruns.test.d.ts +1 -0
  89. package/dist/MsaViewPanel/launchAutoruns.test.js +36 -0
  90. package/dist/MsaViewPanel/model.d.ts +156 -61
  91. package/dist/MsaViewPanel/model.js +69 -3
  92. package/dist/MsaViewPanel/model.test.js +66 -0
  93. package/dist/MsaViewPanel/msaDataStore.d.ts +5 -3
  94. package/dist/MsaViewPanel/msaDataStore.js +3 -12
  95. package/dist/MsaViewPanel/msaDataStore.test.js +8 -1
  96. package/dist/MsaViewPanel/observeProteinHighlights.test.js +4 -4
  97. package/dist/MsaViewPanel/regionSnapshot.test.d.ts +1 -0
  98. package/dist/MsaViewPanel/regionSnapshot.test.js +29 -0
  99. package/dist/MsaViewPanel/resolveConnectedTranscript.d.ts +41 -4
  100. package/dist/MsaViewPanel/resolveConnectedTranscript.js +109 -68
  101. package/dist/MsaViewPanel/resolveConnectedTranscript.test.d.ts +1 -0
  102. package/dist/MsaViewPanel/resolveConnectedTranscript.test.js +149 -0
  103. package/dist/MsaViewPanel/runLaunch.d.ts +8 -4
  104. package/dist/MsaViewPanel/runLaunch.js +15 -4
  105. package/dist/MsaViewPanel/runLaunch.test.js +16 -0
  106. package/dist/MsaViewPanel/storedData.test.js +43 -0
  107. package/dist/MsaViewPanel/syncGenomeHoverToMsaColumn.test.js +15 -7
  108. package/dist/MsaViewPanel/util.d.ts +23 -23
  109. package/dist/MsaViewPanel/util.js +23 -24
  110. package/dist/index.d.ts +11 -11
  111. package/dist/index.js +17 -10
  112. package/dist/jbrowse-plugin-msaview.umd.production.min.js +54 -67
  113. package/dist/jbrowse-plugin-msaview.umd.production.min.js.map +4 -4
  114. package/dist/utils/blastCache.d.ts +26 -2
  115. package/dist/utils/blastCache.js +51 -39
  116. package/dist/utils/blastCache.test.js +46 -19
  117. package/dist/utils/domainCache.d.ts +1 -1
  118. package/dist/utils/domainCache.js +18 -14
  119. package/dist/utils/ebiJobDispatcher.d.ts +11 -0
  120. package/dist/utils/ebiJobDispatcher.js +12 -13
  121. package/dist/utils/ebiJobDispatcher.test.js +17 -3
  122. package/dist/utils/eutils.d.ts +14 -0
  123. package/dist/utils/eutils.js +48 -14
  124. package/dist/utils/eutils.test.d.ts +1 -0
  125. package/dist/utils/eutils.test.js +31 -0
  126. package/dist/utils/idb.d.ts +16 -3
  127. package/dist/utils/idb.js +51 -5
  128. package/dist/utils/idb.test.d.ts +1 -0
  129. package/dist/utils/idb.test.js +78 -0
  130. package/dist/utils/ncbiDomains.js +4 -4
  131. package/dist/utils/ncbiOrthologs.d.ts +1 -1
  132. package/dist/utils/ncbiOrthologs.js +18 -17
  133. package/dist/utils/ncbiOrthologs.test.js +17 -1
  134. package/dist/utils/ncbiTaxonomy.js +14 -15
  135. package/dist/utils/taxonomyNames.js +23 -19
  136. package/dist/utils/taxonomyNames.test.d.ts +1 -0
  137. package/dist/utils/taxonomyNames.test.js +33 -0
  138. package/dist/utils/unirefHomologs.js +2 -2
  139. package/dist/utils/unirefHomologs.test.js +8 -1
  140. package/dist/version.d.ts +1 -1
  141. package/dist/version.js +1 -1
  142. package/package.json +23 -23
  143. package/src/AddHighlightModel/index.tsx +1 -1
  144. package/src/BgzipFastaMsaAdapter/configSchema.ts +1 -1
  145. package/src/LaunchMsaView/cleanProteinSequence.test.ts +91 -0
  146. package/src/LaunchMsaView/codingFeature.test.ts +96 -0
  147. package/src/LaunchMsaView/codingFeature.ts +49 -0
  148. package/src/LaunchMsaView/components/BlastQuery/BlastAutomaticPanel.tsx +60 -56
  149. package/src/LaunchMsaView/components/BlastQuery/BlastManualPanel.tsx +18 -15
  150. package/src/LaunchMsaView/components/BlastQuery/BlastPanel.tsx +24 -11
  151. package/src/LaunchMsaView/components/BlastQuery/BlastSettingsDialog.tsx +4 -4
  152. package/src/LaunchMsaView/components/BlastQuery/CachedBlastResults.tsx +16 -15
  153. package/src/LaunchMsaView/components/BlastQuery/consts.ts +31 -4
  154. package/src/LaunchMsaView/components/HelpButton.tsx +33 -0
  155. package/src/LaunchMsaView/components/HelpDialog.tsx +78 -0
  156. package/src/LaunchMsaView/components/LaunchMsaViewDialog.tsx +54 -34
  157. package/src/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.tsx +46 -23
  158. package/src/LaunchMsaView/components/OrthologQuery/OrthologPanel.tsx +41 -28
  159. package/src/LaunchMsaView/components/OrthologQuery/OrthologSourceSelect.test.ts +16 -0
  160. package/src/LaunchMsaView/components/OrthologQuery/OrthologSourceSelect.tsx +24 -2
  161. package/src/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.tsx +81 -25
  162. package/src/LaunchMsaView/components/SequenceStatus.tsx +29 -0
  163. package/src/LaunchMsaView/components/SubmitCancelActions.test.tsx +88 -4
  164. package/src/LaunchMsaView/components/SubmitCancelActions.tsx +43 -16
  165. package/src/LaunchMsaView/components/TabPanel.test.tsx +51 -0
  166. package/src/LaunchMsaView/components/TabPanel.tsx +16 -4
  167. package/src/LaunchMsaView/components/TranscriptSelector.tsx +6 -1
  168. package/src/LaunchMsaView/components/calculateProteinSequence.ts +51 -22
  169. package/src/LaunchMsaView/components/fetchSeq.ts +25 -5
  170. package/src/LaunchMsaView/components/launchConnectedView.ts +56 -0
  171. package/src/LaunchMsaView/components/launchPlacement.tsx +41 -0
  172. package/src/LaunchMsaView/components/useFeatureSequence.ts +18 -11
  173. package/src/LaunchMsaView/components/useTranscriptSelection.test.tsx +66 -0
  174. package/src/LaunchMsaView/components/useTranscriptSelection.ts +41 -2
  175. package/src/LaunchMsaView/detectQueryRow.ts +5 -7
  176. package/src/LaunchMsaView/index.ts +32 -13
  177. package/src/LaunchMsaView/launchTarget.test.ts +88 -15
  178. package/src/LaunchMsaView/launchTarget.ts +49 -14
  179. package/src/LaunchMsaView/useQueryRowName.test.ts +28 -1
  180. package/src/LaunchMsaView/useQueryRowName.ts +17 -0
  181. package/src/LaunchMsaView/util.ts +15 -5
  182. package/src/LaunchMsaViewExtensionPoint/index.test.ts +16 -0
  183. package/src/LaunchMsaViewExtensionPoint/index.ts +28 -1
  184. package/src/MsaViewPanel/afterCreateAutoruns.ts +46 -14
  185. package/src/MsaViewPanel/applyRegion.test.ts +55 -0
  186. package/src/MsaViewPanel/applyRegion.ts +33 -0
  187. package/src/MsaViewPanel/components/LaunchProgress.tsx +12 -5
  188. package/src/MsaViewPanel/components/MsaViewPanel.test.tsx +26 -0
  189. package/src/MsaViewPanel/components/MsaViewPanel.tsx +7 -5
  190. package/src/MsaViewPanel/doLaunchBlast.test.ts +20 -1
  191. package/src/MsaViewPanel/doLaunchBlast.ts +2 -1
  192. package/src/MsaViewPanel/doLaunchOrthologs.test.ts +1 -1
  193. package/src/MsaViewPanel/genomeToMSA.test.ts +255 -59
  194. package/src/MsaViewPanel/genomeToMSA.ts +30 -5
  195. package/src/MsaViewPanel/launchAutoruns.test.ts +45 -0
  196. package/src/MsaViewPanel/model.test.ts +75 -0
  197. package/src/MsaViewPanel/model.ts +76 -3
  198. package/src/MsaViewPanel/msaDataStore.test.ts +14 -2
  199. package/src/MsaViewPanel/msaDataStore.ts +4 -12
  200. package/src/MsaViewPanel/observeProteinHighlights.test.ts +4 -4
  201. package/src/MsaViewPanel/regionSnapshot.test.ts +34 -0
  202. package/src/MsaViewPanel/resolveConnectedTranscript.test.ts +183 -0
  203. package/src/MsaViewPanel/resolveConnectedTranscript.ts +160 -67
  204. package/src/MsaViewPanel/runLaunch.test.ts +19 -0
  205. package/src/MsaViewPanel/runLaunch.ts +21 -4
  206. package/src/MsaViewPanel/storedData.test.ts +58 -0
  207. package/src/MsaViewPanel/syncGenomeHoverToMsaColumn.test.ts +17 -7
  208. package/src/MsaViewPanel/util.ts +38 -29
  209. package/src/index.ts +19 -10
  210. package/src/utils/blastCache.test.ts +59 -20
  211. package/src/utils/blastCache.ts +62 -39
  212. package/src/utils/domainCache.ts +27 -15
  213. package/src/utils/ebiJobDispatcher.test.ts +18 -3
  214. package/src/utils/ebiJobDispatcher.ts +12 -16
  215. package/src/utils/eutils.test.ts +39 -0
  216. package/src/utils/eutils.ts +64 -14
  217. package/src/utils/idb.test.ts +106 -0
  218. package/src/utils/idb.ts +57 -5
  219. package/src/utils/ncbiDomains.ts +4 -4
  220. package/src/utils/ncbiOrthologs.test.ts +20 -0
  221. package/src/utils/ncbiOrthologs.ts +26 -28
  222. package/src/utils/ncbiTaxonomy.ts +17 -20
  223. package/src/utils/taxonomyNames.test.ts +41 -0
  224. package/src/utils/taxonomyNames.ts +33 -19
  225. package/src/utils/unirefHomologs.test.ts +11 -1
  226. package/src/utils/unirefHomologs.ts +2 -2
  227. package/src/version.ts +1 -1
  228. package/dist/LaunchMsaView/components/BlastQuery/blastLaunchView.d.ts +0 -16
  229. package/dist/LaunchMsaView/components/BlastQuery/blastLaunchView.js +0 -31
  230. package/dist/LaunchMsaView/components/ManualMSALoader/launchView.d.ts +0 -16
  231. package/dist/LaunchMsaView/components/ManualMSALoader/launchView.js +0 -16
  232. package/dist/LaunchMsaView/components/OrthologQuery/orthologLaunchView.d.ts +0 -9
  233. package/dist/LaunchMsaView/components/OrthologQuery/orthologLaunchView.js +0 -15
  234. package/dist/LaunchMsaView/components/PreLoadedMSA/preCalculatedLaunchView.d.ts +0 -11
  235. package/dist/LaunchMsaView/components/PreLoadedMSA/preCalculatedLaunchView.js +0 -21
  236. package/dist/LaunchMsaView/components/geneticCodes.d.ts +0 -15
  237. package/dist/LaunchMsaView/components/geneticCodes.js +0 -227
  238. package/src/LaunchMsaView/components/BlastQuery/blastLaunchView.ts +0 -59
  239. package/src/LaunchMsaView/components/ManualMSALoader/launchView.ts +0 -43
  240. package/src/LaunchMsaView/components/OrthologQuery/orthologLaunchView.ts +0 -31
  241. package/src/LaunchMsaView/components/PreLoadedMSA/preCalculatedLaunchView.ts +0 -38
  242. package/src/LaunchMsaView/components/geneticCodes.ts +0 -298
@@ -0,0 +1,39 @@
1
+ import { useState } from 'react';
2
+ import { getSession } from '@jbrowse/core/util';
3
+ import { launchMsaView } from '../../utils/launchMsaView';
4
+ /** how a view whose alignment the plugin builds opens */
5
+ export const builtAlignmentLook = {
6
+ drawNodeBubbles: true,
7
+ colWidth: 10,
8
+ rowHeight: 12,
9
+ };
10
+ /**
11
+ * Every dialog launch: an MSA view tied to the genome view it came from, and
12
+ * through `feature` to the transcript whose codons its query row is read by.
13
+ */
14
+ export function launchConnectedView({ view, feature, placement, ...snapshot }) {
15
+ launchMsaView(getSession(view), {
16
+ placement,
17
+ connectedViewId: view.id,
18
+ connectedFeature: feature?.toJSON(),
19
+ ...snapshot,
20
+ });
21
+ }
22
+ /** runs a panel's launch, closing the dialog on success and keeping the error */
23
+ export function useLaunchSubmit(handleClose) {
24
+ const [launchError, setLaunchError] = useState();
25
+ return {
26
+ launchError,
27
+ submit: (launch) => {
28
+ try {
29
+ setLaunchError(undefined);
30
+ launch();
31
+ handleClose();
32
+ }
33
+ catch (e) {
34
+ console.error(e);
35
+ setLaunchError(e);
36
+ }
37
+ },
38
+ };
39
+ }
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ type PlacementState = [boolean, React.Dispatch<React.SetStateAction<boolean>>];
3
+ export declare function LaunchPlacementProvider({ children, }: {
4
+ children: React.ReactNode;
5
+ }): React.JSX.Element;
6
+ /** the dialog's answer, or a private one for a panel rendered outside it */
7
+ export declare function useLaunchPlacement(): PlacementState;
8
+ export {};
@@ -0,0 +1,25 @@
1
+ import React, { createContext, useContext, useState } from 'react';
2
+ import { readLaunchPlacement } from '../../utils/workspaces';
3
+ /**
4
+ * Whether the launch opens beside the genome view — one answer for the whole
5
+ * dialog, not one per tab.
6
+ *
7
+ * Every tab the user visits stays mounted, and the checkbox lives in each
8
+ * panel's actions row, so a `useState` there gave each tab its own copy of the
9
+ * answer: ticking the box on one tab and submitting from another wrote the
10
+ * other tab's stale one.
11
+ *
12
+ * Context rather than a prop through all five panels, none of which has any
13
+ * other business with placement.
14
+ */
15
+ const LaunchPlacementContext = createContext(undefined);
16
+ export function LaunchPlacementProvider({ children, }) {
17
+ const state = useState(() => readLaunchPlacement() === 'splitRight');
18
+ return (React.createElement(LaunchPlacementContext.Provider, { value: state }, children));
19
+ }
20
+ /** the dialog's answer, or a private one for a panel rendered outside it */
21
+ export function useLaunchPlacement() {
22
+ const shared = useContext(LaunchPlacementContext);
23
+ const own = useState(() => readLaunchPlacement() === 'splitRight');
24
+ return shared ?? own;
25
+ }
@@ -9,7 +9,9 @@ export declare function useFeatureSequence({ view, feature, }: {
9
9
  proteinSequence: string;
10
10
  sequence: {
11
11
  seq: string;
12
+ assemblyGeneticCodeId: number | undefined;
12
13
  } | undefined;
13
14
  error: unknown;
15
+ isLoading: boolean;
14
16
  };
15
17
  export {};
@@ -4,25 +4,28 @@ import { getProteinSequenceFromFeature } from './calculateProteinSequence';
4
4
  import { fetchSeq } from './fetchSeq';
5
5
  export function useFeatureSequence({ view, feature, }) {
6
6
  const assemblyName = view?.assemblyNames?.[0];
7
- const { data: sequence, error } = useFetch(feature && assemblyName
7
+ const { data: sequence, error, isLoading, } = useFetch(feature && assemblyName
8
8
  ? [feature.id(), assemblyName, 'feature-sequence']
9
9
  : null, async () => {
10
10
  const { start, end, refName } = feature.toJSON();
11
- return {
12
- seq: await fetchSeq({
13
- start,
14
- end,
15
- refName,
16
- assemblyName: assemblyName,
17
- session: getSession(view),
18
- }),
19
- };
11
+ return fetchSeq({
12
+ start,
13
+ end,
14
+ refName,
15
+ assemblyName: assemblyName,
16
+ session: getSession(view),
17
+ });
20
18
  });
21
19
  return {
22
20
  proteinSequence: sequence && feature
23
- ? getProteinSequenceFromFeature({ seq: sequence.seq, feature })
21
+ ? getProteinSequenceFromFeature({
22
+ seq: sequence.seq,
23
+ feature,
24
+ assemblyGeneticCodeId: sequence.assemblyGeneticCodeId,
25
+ })
24
26
  : '',
25
27
  sequence,
26
28
  error,
29
+ isLoading,
27
30
  };
28
31
  }
@@ -1,10 +1,17 @@
1
+ import type { SequenceStatus } from './SequenceStatus';
1
2
  import type { Feature } from '@jbrowse/core/util';
2
- export declare function useTranscriptSelection({ feature, view, validIds, }: {
3
+ export declare function useTranscriptSelection({ feature, view, validIds, preferredTranscriptId, }: {
3
4
  feature: Feature;
4
5
  view: {
5
6
  assemblyNames?: string[];
6
7
  } | undefined;
7
8
  validIds?: string[];
9
+ /**
10
+ * the isoform the user right-clicked, when the dialog opened on its gene.
11
+ * Ignored when this gene does not carry it, so a stale id falls back to the
12
+ * longest transcript rather than selecting nothing.
13
+ */
14
+ preferredTranscriptId?: string;
8
15
  }): {
9
16
  options: Feature[];
10
17
  selectedId: string;
@@ -12,5 +19,7 @@ export declare function useTranscriptSelection({ feature, view, validIds, }: {
12
19
  selectedTranscript: Feature | undefined;
13
20
  proteinSequence: string;
14
21
  error: unknown;
22
+ isLoading: boolean;
23
+ sequenceStatus: SequenceStatus;
15
24
  validIds: string[] | undefined;
16
25
  };
@@ -16,12 +16,28 @@ function pickSelectedId(currentId, options, validIds) {
16
16
  const firstValid = options.find(isValid);
17
17
  return firstValid ? getId(firstValid) : currentId;
18
18
  }
19
- export function useTranscriptSelection({ feature, view, validIds, }) {
19
+ /**
20
+ * Submit is disabled until the query sequence is in hand, and a grey button
21
+ * reads the same whether the translation is on its way or was never going to
22
+ * arrive. `missing` is the second case: the feature has no CDS to translate,
23
+ * which the dialog never said.
24
+ */
25
+ function sequenceStatus({ error, isLoading, proteinSequence, }) {
26
+ if (error) {
27
+ return 'error';
28
+ }
29
+ if (isLoading) {
30
+ return 'loading';
31
+ }
32
+ return proteinSequence ? 'ready' : 'missing';
33
+ }
34
+ export function useTranscriptSelection({ feature, view, validIds, preferredTranscriptId, }) {
20
35
  const options = useMemo(() => getSortedTranscriptFeatures(feature), [feature]);
21
- const [selectedId, setSelectedId] = useState(() => getId(options[0]));
36
+ const [selectedId, setSelectedId] = useState(() => getId(options.find(opt => featureMatchesId(opt, preferredTranscriptId ?? '')) ??
37
+ options[0]));
22
38
  const validatedSelectedId = pickSelectedId(selectedId, options, validIds);
23
39
  const selectedTranscript = options.find(val => getId(val) === validatedSelectedId);
24
- const { proteinSequence, error } = useFeatureSequence({
40
+ const { proteinSequence, error, isLoading } = useFeatureSequence({
25
41
  view,
26
42
  feature: selectedTranscript,
27
43
  });
@@ -32,6 +48,8 @@ export function useTranscriptSelection({ feature, view, validIds, }) {
32
48
  selectedTranscript,
33
49
  proteinSequence,
34
50
  error,
51
+ isLoading,
52
+ sequenceStatus: sequenceStatus({ error, isLoading, proteinSequence }),
35
53
  validIds,
36
54
  };
37
55
  }
@@ -0,0 +1,57 @@
1
+ // @vitest-environment jsdom
2
+ import { SimpleFeature } from '@jbrowse/core/util';
3
+ import { renderHook } from '@testing-library/react';
4
+ import { describe, expect, test } from 'vitest';
5
+ import { useTranscriptSelection } from './useTranscriptSelection';
6
+ function transcript(id, cdsLength) {
7
+ return {
8
+ uniqueId: id,
9
+ name: id,
10
+ refName: 'chr1',
11
+ start: 0,
12
+ end: cdsLength,
13
+ type: 'mRNA',
14
+ subfeatures: [
15
+ {
16
+ uniqueId: `${id}-cds`,
17
+ refName: 'chr1',
18
+ start: 0,
19
+ end: cdsLength,
20
+ type: 'CDS',
21
+ },
22
+ ],
23
+ };
24
+ }
25
+ // the picker sorts by translated length, so the clicked isoform is deliberately
26
+ // not the one that would be chosen by default
27
+ const gene = new SimpleFeature({
28
+ uniqueId: 'gene1',
29
+ name: 'TP53',
30
+ refName: 'chr1',
31
+ start: 0,
32
+ end: 900,
33
+ type: 'gene',
34
+ subfeatures: [transcript('short', 300), transcript('long', 900)],
35
+ });
36
+ // `view` is left out so useFeatureSequence has no assembly to fetch against and
37
+ // never reaches the session; only the selection is under test here
38
+ function selection(preferredTranscriptId) {
39
+ return renderHook(() => useTranscriptSelection({
40
+ feature: gene,
41
+ view: undefined,
42
+ preferredTranscriptId,
43
+ })).result.current;
44
+ }
45
+ describe('useTranscriptSelection', () => {
46
+ test('opens on the longest transcript with nothing preferred', () => {
47
+ expect(selection().selectedId).toBe('long');
48
+ });
49
+ // climbing to the gene is what puts every isoform in the picker, and it threw
50
+ // away which one the right-click landed on
51
+ test('opens on the isoform the user clicked', () => {
52
+ expect(selection('short').selectedId).toBe('short');
53
+ });
54
+ test('ignores a preferred id this gene does not carry', () => {
55
+ expect(selection('some_other_transcript').selectedId).toBe('long');
56
+ });
57
+ });
@@ -1,14 +1,11 @@
1
1
  import { getUngappedSequence, parseMSA } from 'msa-parsers';
2
+ import { cleanProteinSequence } from './util';
2
3
  /**
3
- * A stop codon is present in the transcript's translation and absent from
4
- * anything an aligner returns, and case is not meaningful in either.
4
+ * The translation as a search launch would have sent it, so a row that launch
5
+ * built matches exactly; case means nothing on either side.
5
6
  */
6
7
  function normalize(seq) {
7
- return seq
8
- .replaceAll('*', '')
9
- .replaceAll('-', '')
10
- .replaceAll('.', '')
11
- .toUpperCase();
8
+ return cleanProteinSequence(seq).toUpperCase();
12
9
  }
13
10
  function identityOverOverlap(a, b) {
14
11
  const len = Math.min(a.length, b.length);
@@ -1,5 +1,6 @@
1
1
  import { getContainingTrack, getSession } from '@jbrowse/core/util';
2
2
  import AddIcon from '@mui/icons-material/Add';
3
+ import { isCodingFeature } from './codingFeature';
3
4
  import LaunchMsaViewDialog from './components/LaunchMsaViewDialog';
4
5
  import { launchTarget } from './launchTarget';
5
6
  function isDisplay(elt) {
@@ -9,19 +10,37 @@ function isDisplay(elt) {
9
10
  // built: contextMenuItems runs on every right-click and, on a host whose base
10
11
  // method reads `this`, is the one place a plugin can take the whole menu down.
11
12
  // Keeping it to a pure read of the display is also what lets a test call it.
12
- function openDialog(self, feature) {
13
+ function featureName(feature) {
14
+ return feature.get('name') ?? feature.get('id') ?? 'This feature';
15
+ }
16
+ function openDialog(self, target) {
13
17
  const track = getContainingTrack(self);
14
18
  const session = getSession(track);
15
- feature()
16
- .then(f => {
17
- if (f) {
18
- session.queueDialog(handleClose => [
19
- LaunchMsaViewDialog,
20
- { model: track, handleClose, feature: f },
21
- ]);
19
+ const { preferredTranscriptId } = target;
20
+ const open = (feature) => {
21
+ session.queueDialog(handleClose => [
22
+ LaunchMsaViewDialog,
23
+ { model: track, handleClose, feature, preferredTranscriptId },
24
+ ]);
25
+ };
26
+ if ('feature' in target) {
27
+ open(target.feature);
28
+ return;
29
+ }
30
+ target
31
+ .fetchFeature()
32
+ .then(feature => {
33
+ if (!feature) {
34
+ session.notify('Could not load feature for MSA view', 'warning');
35
+ }
36
+ else if (!isCodingFeature(feature)) {
37
+ // the canvas hit test carries a type and nothing else, so this is the
38
+ // first point at which the CDS can be looked for. Saying so beats
39
+ // opening a dialog whose Submit never leaves grey.
40
+ session.notify(`${featureName(feature)} has no coding sequence, so there is no protein to align`, 'info');
22
41
  }
23
42
  else {
24
- session.notify('Could not load feature for MSA view', 'warning');
43
+ open(feature);
25
44
  }
26
45
  })
27
46
  .catch((e) => {
@@ -1,3 +1,4 @@
1
+ import { isGeneLikeType } from './codingFeature';
1
2
  import type { MenuItem } from '@jbrowse/core/ui';
2
3
  import type { Feature } from '@jbrowse/core/util';
3
4
  export interface ContextMenuInfo {
@@ -13,13 +14,40 @@ export interface DisplayModel {
13
14
  fetchFullFeature?: (featureId: string, displayedRegionIndex: number) => Promise<Feature | undefined>;
14
15
  contextMenuFeature?: Feature;
15
16
  }
16
- export declare function isGeneLikeType(type: unknown): boolean;
17
17
  /**
18
- * How to get the right-clicked feature, or nothing when there is no gene to
19
- * launch on. Both host shapes reduce to a thunk, so the menu item is built and
20
- * the dialog is opened by one code path — and the same gene test decides both.
21
- * The strict three-name set the legacy branch used to carry disagreed with the
22
- * loose one above, so a `lnc_RNA` offered the menu item on a 4.3 host and not
23
- * on a 3.7 one.
18
+ * What the menu item launches on. A legacy host hands over the whole feature,
19
+ * so whether it codes for anything is known while the menu is built; a canvas
20
+ * host's hit test carries a type and an id, so that question can only be
21
+ * answered after the fetch, and the caller answers it there.
24
22
  */
25
- export declare function launchTarget(self: DisplayModel): (() => Promise<Feature | undefined>) | undefined;
23
+ interface ClickedTranscript {
24
+ /**
25
+ * The isoform the click actually landed on, when the dialog opens on its
26
+ * gene. Climbing to the gene is what puts every isoform in the picker, and it
27
+ * threw away which one the user pointed at: the picker then opened on the
28
+ * longest transcript, and on a v4 host that is the only place the choice was
29
+ * ever stated.
30
+ */
31
+ preferredTranscriptId?: string;
32
+ }
33
+ export type MenuTarget = (ClickedTranscript & {
34
+ feature: Feature;
35
+ }) | (ClickedTranscript & {
36
+ fetchFeature: () => Promise<Feature | undefined>;
37
+ });
38
+ export { isGeneLikeType };
39
+ /**
40
+ * How to get the right-clicked feature, or nothing when there is nothing to
41
+ * launch on. Both host shapes reduce to one target, so the menu item is built
42
+ * and the dialog is opened by one code path — and the same gene test decides
43
+ * both. The strict three-name set the legacy branch used to carry disagreed
44
+ * with the loose one above, so a `lnc_RNA` offered the menu item on a 4.3 host
45
+ * and not on a 3.7 one.
46
+ *
47
+ * Gene-like is not enough on its own: an lncRNA has no protein to align, and
48
+ * accepting it opened a dialog whose Submit never left grey, with nothing
49
+ * saying why. Where the whole feature is in hand the CDS decides here -- but
50
+ * only where there are subfeatures to read it off, since a feature that came
51
+ * with none has not answered the question.
52
+ */
53
+ export declare function launchTarget(self: DisplayModel): MenuTarget | undefined;
@@ -1,3 +1,4 @@
1
+ import { geneLikeRoot, isGeneLikeType, isKnownNonCoding } from './codingFeature';
1
2
  // Read off the clicked item rather than off the display.
2
3
  //
3
4
  // LinearBasicDisplay used to publish an `isGeneLike` getter and this gated on
@@ -12,17 +13,20 @@
12
13
  // costs one comparison. Deliberately the same loose case-insensitive test the
13
14
  // host applies (`isGeneLikeType` in collapseIntronsMenu.ts): real GFFs carry
14
15
  // 'mRNA', 'lnc_RNA', 'protein_coding_gene', 'transcript'.
15
- export function isGeneLikeType(type) {
16
- const t = String(type ?? '').toLowerCase();
17
- return t.includes('gene') || t.includes('rna') || t.includes('transcript');
18
- }
16
+ export { isGeneLikeType };
19
17
  /**
20
- * How to get the right-clicked feature, or nothing when there is no gene to
21
- * launch on. Both host shapes reduce to a thunk, so the menu item is built and
22
- * the dialog is opened by one code path — and the same gene test decides both.
23
- * The strict three-name set the legacy branch used to carry disagreed with the
24
- * loose one above, so a `lnc_RNA` offered the menu item on a 4.3 host and not
25
- * on a 3.7 one.
18
+ * How to get the right-clicked feature, or nothing when there is nothing to
19
+ * launch on. Both host shapes reduce to one target, so the menu item is built
20
+ * and the dialog is opened by one code path — and the same gene test decides
21
+ * both. The strict three-name set the legacy branch used to carry disagreed
22
+ * with the loose one above, so a `lnc_RNA` offered the menu item on a 4.3 host
23
+ * and not on a 3.7 one.
24
+ *
25
+ * Gene-like is not enough on its own: an lncRNA has no protein to align, and
26
+ * accepting it opened a dialog whose Submit never left grey, with nothing
27
+ * saying why. Where the whole feature is in hand the CDS decides here -- but
28
+ * only where there are subfeatures to read it off, since a feature that came
29
+ * with none has not answered the question.
26
30
  */
27
31
  export function launchTarget(self) {
28
32
  const info = self.contextMenuInfo;
@@ -32,11 +36,17 @@ export function launchTarget(self) {
32
36
  // rejects the click can only answer with some other feature
33
37
  if (info && fetchFullFeature) {
34
38
  return isGeneLikeType(info.item.type)
35
- ? () => fetchFullFeature(info.item.featureId, info.displayedRegionIndex)
39
+ ? {
40
+ fetchFeature: () => fetchFullFeature(info.item.featureId, info.displayedRegionIndex),
41
+ }
36
42
  : undefined;
37
43
  }
38
44
  const legacy = self.contextMenuFeature;
39
- return legacy && isGeneLikeType(legacy.get('type'))
40
- ? () => Promise.resolve(legacy)
45
+ if (!legacy) {
46
+ return undefined;
47
+ }
48
+ const root = geneLikeRoot(legacy);
49
+ return isGeneLikeType(root.get('type')) && !isKnownNonCoding(root)
50
+ ? { feature: root, preferredTranscriptId: legacy.id() }
41
51
  : undefined;
42
52
  }
@@ -1,19 +1,36 @@
1
+ import { SimpleFeature } from '@jbrowse/core/util';
1
2
  import { describe, expect, test } from 'vitest';
2
3
  import { isGeneLikeType, launchTarget } from './launchTarget';
3
- function feature(type) {
4
- return {
5
- get: (key) => (key === 'type' ? type : undefined),
6
- };
4
+ function feature(type, subfeatures = ['CDS']) {
5
+ return new SimpleFeature({
6
+ uniqueId: type,
7
+ refName: 'chr1',
8
+ start: 0,
9
+ end: 100,
10
+ type,
11
+ subfeatures: subfeatures.map((t, i) => ({
12
+ uniqueId: `${type}-${i}`,
13
+ refName: 'chr1',
14
+ start: 0,
15
+ end: 100,
16
+ type: t,
17
+ })),
18
+ });
7
19
  }
8
20
  const modernHost = (type) => ({
9
21
  contextMenuItems: () => [],
10
22
  contextMenuInfo: { item: { featureId: 'f1', type }, displayedRegionIndex: 0 },
11
23
  fetchFullFeature: (featureId) => Promise.resolve(feature(`fetched:${featureId}`)),
12
24
  });
13
- const legacyHost = (type) => ({
25
+ const legacyHost = (feat) => ({
14
26
  contextMenuItems: () => [],
15
- contextMenuFeature: feature(type),
27
+ contextMenuFeature: feat,
16
28
  });
29
+ async function fetched(target) {
30
+ return target && 'fetchFeature' in target
31
+ ? await target.fetchFeature()
32
+ : undefined;
33
+ }
17
34
  describe('isGeneLikeType', () => {
18
35
  test.each(['gene', 'mRNA', 'transcript', 'lnc_RNA', 'protein_coding_gene'])('accepts %s', type => {
19
36
  expect(isGeneLikeType(type)).toBe(true);
@@ -25,8 +42,8 @@ describe('isGeneLikeType', () => {
25
42
  describe('launchTarget', () => {
26
43
  test('resolves the clicked feature through fetchFullFeature', async () => {
27
44
  const target = launchTarget(modernHost('mRNA'));
28
- expect(target).toBeDefined();
29
- expect((await target())?.get('type')).toBe('fetched:f1');
45
+ expect(await fetched(target)).toBeDefined();
46
+ expect((await fetched(target))?.get('type')).toBe('fetched:f1');
30
47
  });
31
48
  test('offers nothing for a non-gene click', () => {
32
49
  expect(launchTarget(modernHost('CDS'))).toBeUndefined();
@@ -37,18 +54,66 @@ describe('launchTarget', () => {
37
54
  });
38
55
  // v3.7.0 hosts have contextMenuFeature and nothing else; dropping this
39
56
  // fallback once took "Launch MSA view" off every host in the wild.
40
- test('falls back to a synchronous contextMenuFeature', async () => {
41
- const target = launchTarget(legacyHost('mRNA'));
42
- expect(target).toBeDefined();
43
- expect((await target())?.get('type')).toBe('mRNA');
57
+ test('falls back to a synchronous contextMenuFeature', () => {
58
+ const target = launchTarget(legacyHost(feature('mRNA')));
59
+ expect(target && 'feature' in target && target.feature.get('type')).toBe('mRNA');
44
60
  });
45
61
  test('applies the same gene test on both host shapes', () => {
46
- expect(launchTarget(legacyHost('lnc_RNA'))).toBeDefined();
47
- expect(launchTarget(legacyHost('exon'))).toBeUndefined();
62
+ expect(launchTarget(legacyHost(feature('lnc_RNA')))).toBeDefined();
63
+ expect(launchTarget(legacyHost(feature('exon')))).toBeUndefined();
64
+ });
65
+ // gene-like is not enough: an lncRNA translates to nothing, and offering the
66
+ // item opened a dialog whose Submit never left grey
67
+ test('declines a gene-like feature with no CDS below it', () => {
68
+ expect(launchTarget(legacyHost(feature('lnc_RNA', ['exon'])))).toBeUndefined();
69
+ });
70
+ // a legacy host can hand over a bare record whose children are a separate
71
+ // query, and declining it takes the item off an ordinary gene with no word
72
+ test('offers the item for a gene that arrived with no subfeatures', () => {
73
+ expect(launchTarget(legacyHost(feature('gene', [])))).toBeDefined();
74
+ });
75
+ // a click on an isoform should open the dialog on the gene, so every
76
+ // transcript is there to pick from
77
+ test('climbs to the gene from a clicked isoform', () => {
78
+ const gene = new SimpleFeature({
79
+ uniqueId: 'gene',
80
+ refName: 'chr1',
81
+ start: 0,
82
+ end: 100,
83
+ type: 'gene',
84
+ subfeatures: [
85
+ {
86
+ uniqueId: 'mrna',
87
+ refName: 'chr1',
88
+ start: 0,
89
+ end: 100,
90
+ type: 'mRNA',
91
+ subfeatures: [
92
+ {
93
+ uniqueId: 'cds',
94
+ refName: 'chr1',
95
+ start: 0,
96
+ end: 100,
97
+ type: 'CDS',
98
+ },
99
+ ],
100
+ },
101
+ ],
102
+ });
103
+ const mrna = gene.get('subfeatures')[0];
104
+ const target = launchTarget(legacyHost(mrna));
105
+ expect(target && 'feature' in target && target.feature).toBe(gene);
106
+ // the gene is what the dialog opens on, but the picker still opens on the
107
+ // isoform the click landed on -- on a v4 host that click is the only place
108
+ // the choice is ever stated
109
+ expect(target?.preferredTranscriptId).toBe(mrna.id());
48
110
  });
49
111
  // a host that has both shapes must not fall through to the legacy branch and
50
112
  // launch on a stale feature when the click was not on a gene
51
113
  test('a non-gene click on a host carrying both shapes offers nothing', () => {
52
- expect(launchTarget({ ...modernHost('CDS'), ...legacyHost('mRNA') })).toBeUndefined();
114
+ expect(launchTarget({
115
+ ...modernHost('CDS'),
116
+ ...legacyHost(feature('mRNA')),
117
+ })).toBeUndefined();
53
118
  });
54
119
  });
@@ -6,6 +6,18 @@
6
6
  * effect writing back into state, and an override survives later edits to the
7
7
  * alignment because it is the one thing actually stored.
8
8
  */
9
+ /**
10
+ * The row to launch with. Detection by residues is the answer wherever it has
11
+ * one -- aligners rename the query on the way through, so a name proves
12
+ * nothing -- but a caller with a naming convention of its own can offer it as a
13
+ * fallback, and it is taken only when the alignment really carries that row.
14
+ * An empty result means there is no query row, which a launch has to refuse:
15
+ * the view opens, renders, and then never navigates.
16
+ */
17
+ export declare function resolveQueryRowName(queryRow: {
18
+ querySeqName: string;
19
+ names: string[];
20
+ }, fallback: string): string;
9
21
  export declare function useQueryRowName(msaText: string, proteinSequence: string): {
10
22
  detected: import("./detectQueryRow").QueryRowMatch | undefined;
11
23
  names: string[];
@@ -8,6 +8,17 @@ import { findQueryRow } from './detectQueryRow';
8
8
  * effect writing back into state, and an override survives later edits to the
9
9
  * alignment because it is the one thing actually stored.
10
10
  */
11
+ /**
12
+ * The row to launch with. Detection by residues is the answer wherever it has
13
+ * one -- aligners rename the query on the way through, so a name proves
14
+ * nothing -- but a caller with a naming convention of its own can offer it as a
15
+ * fallback, and it is taken only when the alignment really carries that row.
16
+ * An empty result means there is no query row, which a launch has to refuse:
17
+ * the view opens, renders, and then never navigates.
18
+ */
19
+ export function resolveQueryRowName(queryRow, fallback) {
20
+ return (queryRow.querySeqName || (queryRow.names.includes(fallback) ? fallback : ''));
21
+ }
11
22
  export function useQueryRowName(msaText, proteinSequence) {
12
23
  const [override, setOverride] = useState();
13
24
  // parsing runs on every keystroke in the paste box otherwise, and an
@@ -1,7 +1,7 @@
1
1
  // @vitest-environment jsdom
2
2
  import { act, renderHook } from '@testing-library/react';
3
3
  import { describe, expect, test } from 'vitest';
4
- import { useQueryRowName } from './useQueryRowName';
4
+ import { resolveQueryRowName, useQueryRowName } from './useQueryRowName';
5
5
  const protein = 'MKWVTFISLLLLFSSAYSRGVFRRDTHKSEIAHRFKDLGEEHFKGLVLIAFSQYLQQCPFD';
6
6
  const trimmed = `>aligned_query\n${protein.slice(5, 45)}\n>other\nWRONGWRONGWRONGWRONGWRONGWRONGWRONGWRONG\n`;
7
7
  describe('useQueryRowName', () => {
@@ -21,3 +21,20 @@ describe('useQueryRowName', () => {
21
21
  expect(result.current.querySeqOffset).toBe(0);
22
22
  });
23
23
  });
24
+ // a pre-loaded dataset names its rows by a convention of its own, and the
25
+ // residues can be too diverged for detection to find the row that is right
26
+ // there
27
+ describe('resolveQueryRowName', () => {
28
+ const names = ['ENST1_hg38', 'mm39', 'rn7'];
29
+ test('prefers the detected or picked row over any fallback', () => {
30
+ expect(resolveQueryRowName({ querySeqName: 'mm39', names }, 'ENST1_hg38')).toBe('mm39');
31
+ });
32
+ test('falls back to a name the alignment actually carries', () => {
33
+ expect(resolveQueryRowName({ querySeqName: '', names }, 'ENST1_hg38')).toBe('ENST1_hg38');
34
+ });
35
+ // the whole bug: the old panel launched with this name whether or not the
36
+ // file had the row, and a name no row carries navigates nowhere
37
+ test('answers nothing for a fallback the alignment does not carry', () => {
38
+ expect(resolveQueryRowName({ querySeqName: '', names }, 'ENST9_hg38')).toBe('');
39
+ });
40
+ });
@@ -13,5 +13,11 @@ export declare function getTranscriptDisplayName(val?: Feature): string;
13
13
  export declare function getGeneDisplayName(val?: Feature): string;
14
14
  export declare function getBlastViewTitle(feature: Feature, transcript: Feature): string;
15
15
  export declare function getSortedTranscriptFeatures(feature: Feature): Feature[];
16
+ /**
17
+ * A translation as a query, still one residue per codon: g2p numbers codons,
18
+ * and the query row is read through it. So the partial first codon (`&`), an
19
+ * internal stop and an unreadable codon become X, and only what trails the
20
+ * last whole codon is dropped.
21
+ */
16
22
  export declare function cleanProteinSequence(seq: string): string;
17
23
  export declare function getGeneIdentifiers(feature: Feature): string[];