@allurereport/web-awesome 3.8.2 → 3.9.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 (206) hide show
  1. package/README.md +112 -0
  2. package/allurerc-dev.mjs +10 -0
  3. package/dist/multi/121.app-d36b0855e3e7a53eeee9.js +1 -0
  4. package/dist/multi/173.app-d36b0855e3e7a53eeee9.js +1 -0
  5. package/dist/multi/174.app-d36b0855e3e7a53eeee9.js +1 -0
  6. package/dist/multi/252.app-d36b0855e3e7a53eeee9.js +1 -0
  7. package/dist/multi/282.app-d36b0855e3e7a53eeee9.js +1 -0
  8. package/dist/multi/29.app-d36b0855e3e7a53eeee9.js +1 -0
  9. package/dist/multi/310.app-d36b0855e3e7a53eeee9.js +1 -0
  10. package/dist/multi/{416.app-f008fb8342025f2b1ace.js → 416.app-d36b0855e3e7a53eeee9.js} +1 -1
  11. package/dist/multi/{507.app-f008fb8342025f2b1ace.js → 507.app-d36b0855e3e7a53eeee9.js} +1 -1
  12. package/dist/multi/527.app-d36b0855e3e7a53eeee9.js +1 -0
  13. package/dist/multi/600.app-d36b0855e3e7a53eeee9.js +1 -0
  14. package/dist/multi/605.app-d36b0855e3e7a53eeee9.js +1 -0
  15. package/dist/multi/638.app-d36b0855e3e7a53eeee9.js +1 -0
  16. package/dist/multi/672.app-d36b0855e3e7a53eeee9.js +1 -0
  17. package/dist/multi/686.app-d36b0855e3e7a53eeee9.js +1 -0
  18. package/dist/multi/725.app-d36b0855e3e7a53eeee9.js +1 -0
  19. package/dist/multi/741.app-d36b0855e3e7a53eeee9.js +1 -0
  20. package/dist/multi/749.app-d36b0855e3e7a53eeee9.js +1 -0
  21. package/dist/multi/755.app-d36b0855e3e7a53eeee9.js +1 -0
  22. package/dist/multi/779.app-d36b0855e3e7a53eeee9.js +1 -0
  23. package/dist/multi/{894.app-f008fb8342025f2b1ace.js → 894.app-d36b0855e3e7a53eeee9.js} +1 -1
  24. package/dist/multi/943.app-d36b0855e3e7a53eeee9.js +1 -0
  25. package/dist/multi/980.app-d36b0855e3e7a53eeee9.js +1 -0
  26. package/dist/multi/app-d36b0855e3e7a53eeee9.js +2 -0
  27. package/dist/multi/manifest.json +26 -23
  28. package/dist/multi/styles-212da6c68fa0beb4c6c5.css +1 -0
  29. package/dist/multi/styles-468416ffee9a9dea6cae.css +58 -0
  30. package/dist/multi/styles-5c882b14b6f3112e40c4.css +1 -0
  31. package/dist/single/app-62171f5f51b5954a787c.js +2 -0
  32. package/dist/single/manifest.json +1 -1
  33. package/package.json +12 -7
  34. package/src/assets/scss/_common.scss +2 -2
  35. package/src/assets/scss/index.scss +8 -6
  36. package/src/components/BaseLayout/index.tsx +14 -2
  37. package/src/components/BaseLayout/styles.scss +5 -5
  38. package/src/components/Categories/CategoryHeaderItem/styles.scss +2 -2
  39. package/src/components/Categories/CategoryTreeItem/styles.scss +2 -2
  40. package/src/components/Categories/GroupTreeItem/styles.scss +4 -5
  41. package/src/components/Categories/HistoryTreeItem/styles.scss +2 -2
  42. package/src/components/Categories/LabelTreeItem/styles.scss +2 -2
  43. package/src/components/Categories/MessageTreeItem/index.tsx +1 -1
  44. package/src/components/Categories/MessageTreeItem/styles.scss +18 -18
  45. package/src/components/Categories/sticky.ts +1 -1
  46. package/src/components/Footer/styles.scss +2 -2
  47. package/src/components/Header/CiInfo/styles.scss +1 -1
  48. package/src/components/Header/styles.scss +2 -2
  49. package/src/components/HotkeysProvider/index.tsx +556 -0
  50. package/src/components/KeyboardShortcuts/index.tsx +73 -0
  51. package/src/components/KeyboardShortcuts/shortcutsConfig.ts +91 -0
  52. package/src/components/KeyboardShortcuts/styles.scss +69 -0
  53. package/src/components/MainReport/index.tsx +89 -72
  54. package/src/components/MainReport/styles.scss +40 -4
  55. package/src/components/Metadata/styles.scss +9 -9
  56. package/src/components/MetadataButton/index.tsx +2 -0
  57. package/src/components/MetadataButton/styles.scss +1 -1
  58. package/src/components/NavTabs/styles.scss +8 -8
  59. package/src/components/ReportBody/index.tsx +11 -2
  60. package/src/components/ReportBody/styles.scss +24 -4
  61. package/src/components/ReportCategories/styles.scss +1 -1
  62. package/src/components/ReportFilters/styles.scss +1 -1
  63. package/src/components/ReportGlobalAttachments/styles.scss +1 -1
  64. package/src/components/ReportGlobalErrors/styles.scss +1 -1
  65. package/src/components/ReportHeader/styles.scss +2 -2
  66. package/src/components/ReportMetadata/index.tsx +9 -11
  67. package/src/components/ReportMetadata/styles.scss +6 -6
  68. package/src/components/ReportQualityGateResults/styles.scss +2 -2
  69. package/src/components/ReportSearch/index.tsx +1 -5
  70. package/src/components/ReportTabs/styles.scss +9 -9
  71. package/src/components/SectionSwitcher/index.tsx +87 -10
  72. package/src/components/SideBySide/index.tsx +20 -2
  73. package/src/components/SideBySide/styles.scss +9 -1
  74. package/src/components/SplitLayout/index.tsx +10 -1
  75. package/src/components/SplitLayout/styles.scss +20 -4
  76. package/src/components/TestResult/TestStepsEmpty/styles.scss +1 -1
  77. package/src/components/TestResult/TrDescription/styles.scss +1 -1
  78. package/src/components/TestResult/TrDropdown/index.tsx +2 -2
  79. package/src/components/TestResult/TrDropdown/styles.scss +1 -1
  80. package/src/components/TestResult/TrEmpty/styles.scss +1 -1
  81. package/src/components/TestResult/TrEnvironmentItem/styles.scss +4 -4
  82. package/src/components/TestResult/TrError/index.tsx +32 -7
  83. package/src/components/TestResult/TrError/styles.scss +23 -23
  84. package/src/components/TestResult/TrHeader/styles.scss +2 -2
  85. package/src/components/TestResult/TrHistory/styles.scss +6 -6
  86. package/src/components/TestResult/TrInfo/styles.scss +8 -8
  87. package/src/components/TestResult/TrLinks/index.tsx +2 -2
  88. package/src/components/TestResult/TrLinks/styles.scss +2 -2
  89. package/src/components/TestResult/TrMetadata/index.tsx +1 -1
  90. package/src/components/TestResult/TrMetadata/styles.scss +1 -1
  91. package/src/components/TestResult/TrNavigation/index.tsx +1 -1
  92. package/src/components/TestResult/TrNavigation/styles.scss +2 -2
  93. package/src/components/TestResult/TrOverview.tsx +2 -0
  94. package/src/components/TestResult/TrParameters/index.tsx +1 -1
  95. package/src/components/TestResult/TrParameters/styles.scss +1 -1
  96. package/src/components/TestResult/TrPrevStatuses/styles.scss +8 -8
  97. package/src/components/TestResult/TrPwTraces/styles.scss +1 -1
  98. package/src/components/TestResult/TrRetriesView/styles.scss +3 -3
  99. package/src/components/TestResult/TrSetup/index.tsx +9 -3
  100. package/src/components/TestResult/TrSeverity/styles.scss +7 -7
  101. package/src/components/TestResult/TrStatus/styles.scss +2 -35
  102. package/src/components/TestResult/TrSteps/TrAttachment.tsx +79 -43
  103. package/src/components/TestResult/TrSteps/TrAttachmentInfo.tsx +44 -17
  104. package/src/components/TestResult/TrSteps/TrErrorStep.tsx +3 -0
  105. package/src/components/TestResult/TrSteps/TrStep.tsx +9 -4
  106. package/src/components/TestResult/TrSteps/TrStepHeader.tsx +8 -5
  107. package/src/components/TestResult/TrSteps/index.tsx +7 -4
  108. package/src/components/TestResult/TrSteps/stepTreeExpansion.ts +27 -9
  109. package/src/components/TestResult/TrSteps/styles.scss +80 -20
  110. package/src/components/TestResult/TrTeardown/index.tsx +9 -3
  111. package/src/components/TestResult/bodyItems.ts +1 -1
  112. package/src/components/TestResult/index.tsx +8 -2
  113. package/src/components/TestResult/styles.scss +10 -1
  114. package/src/components/TestResult/trOverviewFocus.scss +4 -0
  115. package/src/components/Timeline/styles.scss +6 -6
  116. package/src/components/Tree/index.tsx +54 -5
  117. package/src/components/Tree/styles.scss +55 -35
  118. package/src/hooks/useTestResultOverviewFocusScroll.ts +23 -0
  119. package/src/index.html +30 -33
  120. package/src/index.tsx +12 -6
  121. package/src/locales/ar.json +61 -1
  122. package/src/locales/az.json +61 -1
  123. package/src/locales/de.json +61 -1
  124. package/src/locales/en.json +61 -1
  125. package/src/locales/es.json +61 -1
  126. package/src/locales/fr.json +61 -1
  127. package/src/locales/he.json +61 -1
  128. package/src/locales/hy.json +61 -1
  129. package/src/locales/it.json +61 -1
  130. package/src/locales/ja.json +61 -1
  131. package/src/locales/ka.json +61 -1
  132. package/src/locales/kr.json +61 -1
  133. package/src/locales/nl.json +61 -1
  134. package/src/locales/pl.json +61 -1
  135. package/src/locales/pt.json +61 -1
  136. package/src/locales/ru.json +61 -1
  137. package/src/locales/sv.json +61 -1
  138. package/src/locales/tr.json +61 -1
  139. package/src/locales/uk.json +61 -1
  140. package/src/locales/zh-TW.json +61 -1
  141. package/src/locales/zh.json +61 -1
  142. package/src/stores/keyboard.ts +371 -0
  143. package/src/stores/keyboardActions.ts +769 -0
  144. package/src/stores/locale.ts +1 -0
  145. package/src/stores/reportEnvSections.ts +6 -0
  146. package/src/stores/reportRootTabs.ts +95 -0
  147. package/src/stores/search.ts +147 -0
  148. package/src/stores/testResultOverviewNav.ts +119 -0
  149. package/src/stores/testResultTabs.ts +62 -0
  150. package/src/stores/timeline.ts +1 -1
  151. package/src/stores/tree.ts +42 -4
  152. package/src/stores/treeFilters/store.ts +3 -36
  153. package/src/styles/_pane-active.scss +8 -0
  154. package/src/styles.scss +1 -1
  155. package/src/utils/flattenTestResultOverview.ts +182 -0
  156. package/src/utils/trOverviewFocus.ts +18 -0
  157. package/test/components/EnvironmentPicker.test.tsx +21 -3
  158. package/test/components/Header/CiInfo.test.tsx +8 -0
  159. package/test/components/Header.test.tsx +8 -0
  160. package/test/components/ReportGlobals.test.tsx +9 -1
  161. package/test/components/TestResult/PwTraceButton.test.tsx +8 -0
  162. package/test/components/TestResult/TrErrorStep.test.tsx +8 -0
  163. package/test/components/TestResult/TrOverview.test.tsx +30 -10
  164. package/test/components/TestResult/TrSteps.test.tsx +73 -0
  165. package/test/components/TestResult/bodyItems.test.ts +9 -1
  166. package/test/components/TestResult/openPwTraceInNewTab.test.ts +8 -0
  167. package/test/components/TestResult/stepTreeExpansion.test.ts +10 -2
  168. package/test/components/Timeline.test.tsx +15 -7
  169. package/test/stores/keyboard/keyboardActions.test.ts +615 -0
  170. package/test/stores/search.test.ts +143 -0
  171. package/test/stores/treeFilters/actions.test.ts +8 -0
  172. package/test/utils/flattenTestResultOverview.test.ts +57 -0
  173. package/test/utils/ownerAddress.test.ts +9 -1
  174. package/test/utils/treeFilters.test.ts +9 -1
  175. package/types.d.ts +17 -0
  176. package/webpack.config.js +3 -0
  177. package/CONTRIBUTING.md +0 -34
  178. package/dist/multi/173.app-f008fb8342025f2b1ace.js +0 -1
  179. package/dist/multi/174.app-f008fb8342025f2b1ace.js +0 -1
  180. package/dist/multi/252.app-f008fb8342025f2b1ace.js +0 -1
  181. package/dist/multi/282.app-f008fb8342025f2b1ace.js +0 -1
  182. package/dist/multi/29.app-f008fb8342025f2b1ace.js +0 -1
  183. package/dist/multi/310.app-f008fb8342025f2b1ace.js +0 -1
  184. package/dist/multi/527.app-f008fb8342025f2b1ace.js +0 -1
  185. package/dist/multi/600.app-f008fb8342025f2b1ace.js +0 -1
  186. package/dist/multi/605.app-f008fb8342025f2b1ace.js +0 -1
  187. package/dist/multi/638.app-f008fb8342025f2b1ace.js +0 -1
  188. package/dist/multi/672.app-f008fb8342025f2b1ace.js +0 -1
  189. package/dist/multi/686.app-f008fb8342025f2b1ace.js +0 -1
  190. package/dist/multi/725.app-f008fb8342025f2b1ace.js +0 -1
  191. package/dist/multi/741.app-f008fb8342025f2b1ace.js +0 -1
  192. package/dist/multi/749.app-f008fb8342025f2b1ace.js +0 -1
  193. package/dist/multi/755.app-f008fb8342025f2b1ace.js +0 -1
  194. package/dist/multi/943.app-f008fb8342025f2b1ace.js +0 -1
  195. package/dist/multi/980.app-f008fb8342025f2b1ace.js +0 -1
  196. package/dist/multi/app-f008fb8342025f2b1ace.js +0 -2
  197. package/dist/multi/styles-9f7a23a0c8b79fa76981.css +0 -58
  198. package/dist/single/app-07332238da9897064301.js +0 -2
  199. package/src/assets/scss/day.scss +0 -53
  200. package/src/assets/scss/fonts.scss +0 -3
  201. package/src/assets/scss/night.scss +0 -63
  202. package/src/assets/scss/palette.scss +0 -393
  203. package/src/assets/scss/theme.scss +0 -330
  204. package/src/assets/scss/vars.scss +0 -11
  205. /package/dist/multi/{app-f008fb8342025f2b1ace.js.LICENSE.txt → app-d36b0855e3e7a53eeee9.js.LICENSE.txt} +0 -0
  206. /package/dist/single/{app-07332238da9897064301.js.LICENSE.txt → app-62171f5f51b5954a787c.js.LICENSE.txt} +0 -0
@@ -0,0 +1,615 @@
1
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
2
+
3
+ import type { AwesomeTree, AwesomeTreeLeaf } from "../../../../types";
4
+ import { environmentsStore } from "../../../src/stores/env";
5
+ import { activePane, treeFocusId } from "../../../src/stores/keyboard";
6
+ import {
7
+ applyTestResultOverviewNavigation,
8
+ applyTreeNavigation,
9
+ getHotkeyScope,
10
+ goToNextTestResult,
11
+ goToPrevTestResult,
12
+ navigateDownInTestResultPane,
13
+ navigateUpInTestResultPane,
14
+ toggleMetadataSection,
15
+ } from "../../../src/stores/keyboardActions";
16
+ import { layoutStore } from "../../../src/stores/layout";
17
+ import { testResultFocusId } from "../../../src/stores/testResultOverviewNav";
18
+ import { testResultNavStore, testResultStore } from "../../../src/stores/testResults";
19
+ import { collapsedTrees, treeStore } from "../../../src/stores/tree";
20
+
21
+ // ---------------------------------------------------------------------------
22
+ // Helpers
23
+ // ---------------------------------------------------------------------------
24
+
25
+ const setHash = (hash: string) => {
26
+ window.history.pushState(null, "", hash ? `#${hash}` : "/");
27
+ window.dispatchEvent(new Event("pushState"));
28
+ };
29
+
30
+ const setNav = (ids: string[]) => {
31
+ testResultNavStore.value = { loading: false, error: undefined, data: ids };
32
+ };
33
+
34
+ const makeLeaf = (id: string, order = 0): AwesomeTreeLeaf => ({
35
+ nodeId: id,
36
+ id,
37
+ name: `Test ${id}`,
38
+ status: "passed",
39
+ duration: 100,
40
+ start: order * 1000,
41
+ groupOrder: order,
42
+ flaky: false,
43
+ transition: false,
44
+ retry: false,
45
+ retriesCount: 0,
46
+ });
47
+
48
+ const setupTree = (leafIds: string[]) => {
49
+ environmentsStore.value = {
50
+ loading: false,
51
+ error: undefined,
52
+ data: [{ id: "default", name: "default" }],
53
+ };
54
+ treeStore.value = {
55
+ loading: false,
56
+ error: undefined,
57
+ data: {
58
+ default: {
59
+ root: { groups: [], leaves: leafIds },
60
+ leavesById: Object.fromEntries(leafIds.map((id, i) => [id, makeLeaf(id, i)])),
61
+ groupsById: {},
62
+ } as AwesomeTree,
63
+ },
64
+ };
65
+ };
66
+
67
+ const resetAll = () => {
68
+ setHash("");
69
+ activePane.value = "tree";
70
+ layoutStore.value = "base";
71
+ treeFocusId.value = undefined;
72
+ testResultFocusId.value = undefined;
73
+ testResultNavStore.value = { loading: true, error: undefined, data: undefined };
74
+ testResultStore.value = { loading: true, error: undefined, data: undefined };
75
+ treeStore.value = { loading: true, error: undefined, data: undefined };
76
+ environmentsStore.value = { loading: false, error: undefined, data: [] };
77
+ };
78
+
79
+ // ---------------------------------------------------------------------------
80
+ // goToNextTestResult
81
+ // ---------------------------------------------------------------------------
82
+
83
+ describe("goToNextTestResult", () => {
84
+ beforeEach(resetAll);
85
+ afterEach(resetAll);
86
+
87
+ it("[base] does nothing when no test result is open", () => {
88
+ setNav(["tr-1", "tr-2", "tr-3"]);
89
+ goToNextTestResult();
90
+ expect(window.location.hash).toBe("");
91
+ });
92
+
93
+ it("[base] does nothing when nav list is empty", () => {
94
+ setHash("tr-1");
95
+ goToNextTestResult();
96
+ expect(window.location.hash).toBe("#tr-1");
97
+ });
98
+
99
+ it("[base] does nothing when current result is last in the list", () => {
100
+ setHash("tr-3");
101
+ setNav(["tr-1", "tr-2", "tr-3"]);
102
+ goToNextTestResult();
103
+ expect(window.location.hash).toBe("#tr-3");
104
+ });
105
+
106
+ it("[base] navigates to next test result", () => {
107
+ setHash("tr-1");
108
+ setNav(["tr-1", "tr-2", "tr-3"]);
109
+ goToNextTestResult();
110
+ expect(window.location.hash).toBe("#tr-2");
111
+ });
112
+
113
+ it("[base] navigates to next test result from middle of list", () => {
114
+ setHash("tr-2");
115
+ setNav(["tr-1", "tr-2", "tr-3"]);
116
+ goToNextTestResult();
117
+ expect(window.location.hash).toBe("#tr-3");
118
+ });
119
+
120
+ it("[base] preserves non-overview tab when navigating", () => {
121
+ setHash("tr-1/history");
122
+ setNav(["tr-1", "tr-2", "tr-3"]);
123
+ goToNextTestResult();
124
+ expect(window.location.hash).toBe("#tr-2/history");
125
+ });
126
+
127
+ it("[base] clears treeFocusId after navigating", () => {
128
+ setHash("tr-1");
129
+ setNav(["tr-1", "tr-2"]);
130
+ treeFocusId.value = "tr-1";
131
+ goToNextTestResult();
132
+ expect(treeFocusId.value).toBeUndefined();
133
+ });
134
+
135
+ it("[base] does not clear treeFocusId when no-op (last in list)", () => {
136
+ setHash("tr-3");
137
+ setNav(["tr-1", "tr-2", "tr-3"]);
138
+ treeFocusId.value = "tr-3";
139
+ goToNextTestResult();
140
+ expect(treeFocusId.value).toBe("tr-3");
141
+ });
142
+
143
+ it("[split] does nothing when no test result is open", () => {
144
+ layoutStore.value = "split";
145
+ setupTree(["tr-1", "tr-2", "tr-3"]);
146
+ goToNextTestResult();
147
+ expect(window.location.hash).toBe("");
148
+ });
149
+
150
+ it("[split] does nothing when current result is not in visible tree", () => {
151
+ layoutStore.value = "split";
152
+ activePane.value = "testResult";
153
+ setHash("tr-99");
154
+ setupTree(["tr-1", "tr-2", "tr-3"]);
155
+ goToNextTestResult();
156
+ expect(window.location.hash).toBe("#tr-99");
157
+ });
158
+
159
+ it("[split] navigates to next test result in tree order", () => {
160
+ layoutStore.value = "split";
161
+ activePane.value = "testResult";
162
+ setHash("tr-1");
163
+ setupTree(["tr-1", "tr-2", "tr-3"]);
164
+ goToNextTestResult();
165
+ expect(window.location.hash).toBe("#tr-2");
166
+ });
167
+
168
+ it("[split] does nothing when current result is last visible", () => {
169
+ layoutStore.value = "split";
170
+ activePane.value = "testResult";
171
+ setHash("tr-3");
172
+ setupTree(["tr-1", "tr-2", "tr-3"]);
173
+ goToNextTestResult();
174
+ expect(window.location.hash).toBe("#tr-3");
175
+ });
176
+ });
177
+
178
+ // ---------------------------------------------------------------------------
179
+ // goToPrevTestResult
180
+ // ---------------------------------------------------------------------------
181
+
182
+ describe("goToPrevTestResult", () => {
183
+ beforeEach(resetAll);
184
+ afterEach(resetAll);
185
+
186
+ it("[base] does nothing when no test result is open", () => {
187
+ setNav(["tr-1", "tr-2", "tr-3"]);
188
+ goToPrevTestResult();
189
+ expect(window.location.hash).toBe("");
190
+ });
191
+
192
+ it("[base] does nothing when current result is first in the list", () => {
193
+ setHash("tr-1");
194
+ setNav(["tr-1", "tr-2", "tr-3"]);
195
+ goToPrevTestResult();
196
+ expect(window.location.hash).toBe("#tr-1");
197
+ });
198
+
199
+ it("[base] navigates to previous test result", () => {
200
+ setHash("tr-3");
201
+ setNav(["tr-1", "tr-2", "tr-3"]);
202
+ goToPrevTestResult();
203
+ expect(window.location.hash).toBe("#tr-2");
204
+ });
205
+
206
+ it("[base] navigates to previous test result from middle of list", () => {
207
+ setHash("tr-2");
208
+ setNav(["tr-1", "tr-2", "tr-3"]);
209
+ goToPrevTestResult();
210
+ expect(window.location.hash).toBe("#tr-1");
211
+ });
212
+
213
+ it("[base] preserves non-overview tab when navigating", () => {
214
+ setHash("tr-3/retries");
215
+ setNav(["tr-1", "tr-2", "tr-3"]);
216
+ goToPrevTestResult();
217
+ expect(window.location.hash).toBe("#tr-2/retries");
218
+ });
219
+
220
+ it("[base] clears treeFocusId after navigating", () => {
221
+ setHash("tr-3");
222
+ setNav(["tr-1", "tr-2", "tr-3"]);
223
+ treeFocusId.value = "tr-3";
224
+ goToPrevTestResult();
225
+ expect(treeFocusId.value).toBeUndefined();
226
+ });
227
+
228
+ it("[base] does not clear treeFocusId when no-op (first in list)", () => {
229
+ setHash("tr-1");
230
+ setNav(["tr-1", "tr-2", "tr-3"]);
231
+ treeFocusId.value = "tr-1";
232
+ goToPrevTestResult();
233
+ expect(treeFocusId.value).toBe("tr-1");
234
+ });
235
+
236
+ it("[split] navigates to previous test result in tree order", () => {
237
+ layoutStore.value = "split";
238
+ activePane.value = "testResult";
239
+ setHash("tr-3");
240
+ setupTree(["tr-1", "tr-2", "tr-3"]);
241
+ goToPrevTestResult();
242
+ expect(window.location.hash).toBe("#tr-2");
243
+ });
244
+
245
+ it("[split] does nothing when current result is first visible", () => {
246
+ layoutStore.value = "split";
247
+ activePane.value = "testResult";
248
+ setHash("tr-1");
249
+ setupTree(["tr-1", "tr-2", "tr-3"]);
250
+ goToPrevTestResult();
251
+ expect(window.location.hash).toBe("#tr-1");
252
+ });
253
+ });
254
+
255
+ // ---------------------------------------------------------------------------
256
+ // navigateDownInTestResultPane (ArrowDown / j)
257
+ // ---------------------------------------------------------------------------
258
+
259
+ describe("navigateDownInTestResultPane", () => {
260
+ beforeEach(resetAll);
261
+ afterEach(resetAll);
262
+
263
+ it("does nothing when no test result is open", () => {
264
+ setNav(["tr-1", "tr-2"]);
265
+ navigateDownInTestResultPane();
266
+ expect(window.location.hash).toBe("");
267
+ });
268
+
269
+ it("navigates to next test result on non-overview tab", () => {
270
+ setHash("tr-1/history");
271
+ setNav(["tr-1", "tr-2", "tr-3"]);
272
+ navigateDownInTestResultPane();
273
+ expect(window.location.hash).toBe("#tr-2/history");
274
+ });
275
+
276
+ it("navigates to next test result on overview tab", () => {
277
+ setHash("tr-1");
278
+ setNav(["tr-1", "tr-2", "tr-3"]);
279
+ navigateDownInTestResultPane();
280
+ expect(window.location.hash).toBe("#tr-2");
281
+ });
282
+
283
+ it("does nothing when current result is last in list", () => {
284
+ setHash("tr-3/history");
285
+ setNav(["tr-1", "tr-2", "tr-3"]);
286
+ navigateDownInTestResultPane();
287
+ expect(window.location.hash).toBe("#tr-3/history");
288
+ });
289
+ });
290
+
291
+ // ---------------------------------------------------------------------------
292
+ // navigateUpInTestResultPane (ArrowUp / k)
293
+ // ---------------------------------------------------------------------------
294
+
295
+ describe("navigateUpInTestResultPane", () => {
296
+ beforeEach(resetAll);
297
+ afterEach(resetAll);
298
+
299
+ it("does nothing when no test result is open", () => {
300
+ setNav(["tr-1", "tr-2"]);
301
+ navigateUpInTestResultPane();
302
+ expect(window.location.hash).toBe("");
303
+ });
304
+
305
+ it("navigates to previous test result on non-overview tab", () => {
306
+ setHash("tr-3/history");
307
+ setNav(["tr-1", "tr-2", "tr-3"]);
308
+ navigateUpInTestResultPane();
309
+ expect(window.location.hash).toBe("#tr-2/history");
310
+ });
311
+
312
+ it("navigates to previous test result on overview tab", () => {
313
+ setHash("tr-2");
314
+ setNav(["tr-1", "tr-2", "tr-3"]);
315
+ navigateUpInTestResultPane();
316
+ expect(window.location.hash).toBe("#tr-1");
317
+ });
318
+
319
+ it("does nothing when current result is first in list", () => {
320
+ setHash("tr-1/history");
321
+ setNav(["tr-1", "tr-2", "tr-3"]);
322
+ navigateUpInTestResultPane();
323
+ expect(window.location.hash).toBe("#tr-1/history");
324
+ });
325
+ });
326
+
327
+ // ---------------------------------------------------------------------------
328
+ // switching base → split: focus current test in tree
329
+ // ---------------------------------------------------------------------------
330
+
331
+ describe("switching to split mode focuses current test in tree", () => {
332
+ beforeEach(resetAll);
333
+ afterEach(resetAll);
334
+
335
+ it("sets treeFocusId to current test result when test is visible in tree", () => {
336
+ setHash("tr-2");
337
+ setupTree(["tr-1", "tr-2", "tr-3"]);
338
+ // switch base → split
339
+ layoutStore.value = "split";
340
+ expect(treeFocusId.value).toBe("tr-2");
341
+ });
342
+
343
+ it("does nothing when no test result is open", () => {
344
+ setupTree(["tr-1", "tr-2", "tr-3"]);
345
+ layoutStore.value = "split";
346
+ expect(treeFocusId.value).not.toBe("tr-2");
347
+ });
348
+
349
+ it("does not re-expand or re-focus when already in split mode", () => {
350
+ layoutStore.value = "split";
351
+ activePane.value = "testResult";
352
+ setHash("tr-1");
353
+ setupTree(["tr-1", "tr-2", "tr-3"]);
354
+ treeFocusId.value = "tr-3";
355
+ layoutStore.value = "split";
356
+ expect(treeFocusId.value).toBe("tr-3");
357
+ });
358
+ });
359
+
360
+ // ---------------------------------------------------------------------------
361
+ // getHotkeyScope
362
+ // ---------------------------------------------------------------------------
363
+
364
+ describe("getHotkeyScope", () => {
365
+ beforeEach(resetAll);
366
+ afterEach(resetAll);
367
+
368
+ it("returns 'tree' at root URL with tree pane active", () => {
369
+ activePane.value = "tree";
370
+ expect(getHotkeyScope()).toBe("tree");
371
+ });
372
+
373
+ it("returns 'tree' at root URL with testResult pane active (no open test result)", () => {
374
+ activePane.value = "testResult";
375
+ expect(getHotkeyScope()).toBe("tree");
376
+ });
377
+
378
+ it("returns 'testResult' when viewing a test result (non-split, testResult pane active)", () => {
379
+ setHash("tr-1");
380
+ activePane.value = "testResult";
381
+ expect(getHotkeyScope()).toBe("testResult");
382
+ });
383
+
384
+ it("returns 'global' when viewing a test result with tree pane active (non-split)", () => {
385
+ setHash("tr-1");
386
+ activePane.value = "tree";
387
+ expect(getHotkeyScope()).toBe("global");
388
+ });
389
+
390
+ it("returns 'tree' in split mode with tree pane active", () => {
391
+ layoutStore.value = "split";
392
+ activePane.value = "tree";
393
+ expect(getHotkeyScope()).toBe("tree");
394
+ });
395
+
396
+ it("returns 'testResult' in split mode with testResult pane active", () => {
397
+ layoutStore.value = "split";
398
+ activePane.value = "testResult";
399
+ expect(getHotkeyScope()).toBe("testResult");
400
+ });
401
+
402
+ it("returns 'tree' in split mode regardless of open test result", () => {
403
+ setHash("tr-1");
404
+ layoutStore.value = "split";
405
+ activePane.value = "tree";
406
+ expect(getHotkeyScope()).toBe("tree");
407
+ });
408
+ });
409
+
410
+ // ---------------------------------------------------------------------------
411
+ // applyTreeNavigation
412
+ // ---------------------------------------------------------------------------
413
+
414
+ describe("applyTreeNavigation", () => {
415
+ beforeEach(resetAll);
416
+ afterEach(resetAll);
417
+
418
+ it("does nothing outside tree context (on test result route, non-split)", () => {
419
+ setupTree(["tr-1", "tr-2", "tr-3"]);
420
+ treeFocusId.value = "tr-1";
421
+ setHash("tr-1");
422
+ applyTreeNavigation("down");
423
+ expect(treeFocusId.value).toBe("tr-1");
424
+ });
425
+
426
+ it("moves focus down through tree leaves", () => {
427
+ setupTree(["tr-1", "tr-2", "tr-3"]);
428
+ treeFocusId.value = "tr-1";
429
+ applyTreeNavigation("down");
430
+ expect(treeFocusId.value).toBe("tr-2");
431
+ });
432
+
433
+ it("moves focus down again to third leaf", () => {
434
+ setupTree(["tr-1", "tr-2", "tr-3"]);
435
+ treeFocusId.value = "tr-2";
436
+ applyTreeNavigation("down");
437
+ expect(treeFocusId.value).toBe("tr-3");
438
+ });
439
+
440
+ it("stays on last leaf when moving down past end", () => {
441
+ setupTree(["tr-1", "tr-2", "tr-3"]);
442
+ treeFocusId.value = "tr-3";
443
+ applyTreeNavigation("down");
444
+ expect(treeFocusId.value).toBe("tr-3");
445
+ });
446
+
447
+ it("moves focus up through tree leaves", () => {
448
+ setupTree(["tr-1", "tr-2", "tr-3"]);
449
+ treeFocusId.value = "tr-3";
450
+ applyTreeNavigation("up");
451
+ expect(treeFocusId.value).toBe("tr-2");
452
+ });
453
+
454
+ it("stays on first leaf when moving up past start", () => {
455
+ setupTree(["tr-1", "tr-2", "tr-3"]);
456
+ treeFocusId.value = "tr-1";
457
+ applyTreeNavigation("up");
458
+ expect(treeFocusId.value).toBe("tr-1");
459
+ });
460
+
461
+ it("moves focus to first leaf with 'home'", () => {
462
+ setupTree(["tr-1", "tr-2", "tr-3"]);
463
+ treeFocusId.value = "tr-3";
464
+ applyTreeNavigation("home");
465
+ expect(treeFocusId.value).toBe("tr-1");
466
+ });
467
+
468
+ it("moves focus to last leaf with 'end'", () => {
469
+ setupTree(["tr-1", "tr-2", "tr-3"]);
470
+ treeFocusId.value = "tr-1";
471
+ applyTreeNavigation("end");
472
+ expect(treeFocusId.value).toBe("tr-3");
473
+ });
474
+
475
+ it("moves focus to first leaf with 'firstLeaf'", () => {
476
+ setupTree(["tr-1", "tr-2", "tr-3"]);
477
+ treeFocusId.value = "tr-2";
478
+ applyTreeNavigation("firstLeaf");
479
+ expect(treeFocusId.value).toBe("tr-1");
480
+ });
481
+
482
+ it("moves focus to last leaf with 'lastLeaf'", () => {
483
+ setupTree(["tr-1", "tr-2", "tr-3"]);
484
+ treeFocusId.value = "tr-1";
485
+ applyTreeNavigation("lastLeaf");
486
+ expect(treeFocusId.value).toBe("tr-3");
487
+ });
488
+ });
489
+
490
+ // ---------------------------------------------------------------------------
491
+ // applyTestResultOverviewNavigation
492
+ // ---------------------------------------------------------------------------
493
+
494
+ describe("applyTestResultOverviewNavigation", () => {
495
+ beforeEach(resetAll);
496
+ afterEach(resetAll);
497
+
498
+ it("does nothing when not on overview tab (history tab)", () => {
499
+ setHash("tr-1/history");
500
+ setNav(["tr-1", "tr-2"]);
501
+ testResultFocusId.value = "step-1";
502
+ applyTestResultOverviewNavigation("down");
503
+ expect(testResultFocusId.value).toBe("step-1");
504
+ expect(window.location.hash).toBe("#tr-1/history");
505
+ });
506
+
507
+ it("does nothing when not on overview tab (retries tab)", () => {
508
+ setHash("tr-1/retries");
509
+ testResultFocusId.value = "step-2";
510
+ applyTestResultOverviewNavigation("up");
511
+ expect(testResultFocusId.value).toBe("step-2");
512
+ expect(window.location.hash).toBe("#tr-1/retries");
513
+ });
514
+
515
+ it("does nothing when no test result is open", () => {
516
+ applyTestResultOverviewNavigation("down");
517
+ expect(testResultFocusId.value).toBeUndefined();
518
+ });
519
+
520
+ it("on overview tab with no test data: focus stays undefined (empty flat overview)", () => {
521
+ setHash("tr-1");
522
+ testResultFocusId.value = undefined;
523
+ applyTestResultOverviewNavigation("down");
524
+ expect(testResultFocusId.value).toBeUndefined();
525
+ expect(window.location.hash).toBe("#tr-1");
526
+ });
527
+ });
528
+
529
+ // ---------------------------------------------------------------------------
530
+ // toggleMetadataSection (l / p / i in testResult scope)
531
+ // ---------------------------------------------------------------------------
532
+
533
+ describe("toggleMetadataSection", () => {
534
+ beforeEach(() => {
535
+ resetAll();
536
+ collapsedTrees.value = new Set();
537
+ });
538
+ afterEach(() => {
539
+ collapsedTrees.value = new Set();
540
+ resetAll();
541
+ });
542
+
543
+ it("does nothing when no test result is open", () => {
544
+ toggleMetadataSection("labels");
545
+ expect(collapsedTrees.value.has("tr-1-labels")).toBe(false);
546
+ });
547
+
548
+ it("does nothing on a non-overview tab (history)", () => {
549
+ setHash("tr-1/history");
550
+ toggleMetadataSection("labels");
551
+ expect(collapsedTrees.value.has("tr-1-labels")).toBe(false);
552
+ });
553
+
554
+ it("does nothing on a non-overview tab (retries)", () => {
555
+ setHash("tr-1/retries");
556
+ toggleMetadataSection("links");
557
+ expect(collapsedTrees.value.has("tr-1-links")).toBe(false);
558
+ });
559
+
560
+ it("collapses labels section on first toggle", () => {
561
+ setHash("tr-1");
562
+ toggleMetadataSection("labels");
563
+ expect(collapsedTrees.value.has("tr-1-labels")).toBe(true);
564
+ });
565
+
566
+ it("re-opens labels section on second toggle", () => {
567
+ setHash("tr-1");
568
+ toggleMetadataSection("labels");
569
+ toggleMetadataSection("labels");
570
+ expect(collapsedTrees.value.has("tr-1-labels")).toBe(false);
571
+ });
572
+
573
+ it("collapses links section", () => {
574
+ setHash("tr-1");
575
+ toggleMetadataSection("links");
576
+ expect(collapsedTrees.value.has("tr-1-links")).toBe(true);
577
+ });
578
+
579
+ it("collapses parameters section", () => {
580
+ setHash("tr-1");
581
+ toggleMetadataSection("parameters");
582
+ expect(collapsedTrees.value.has("tr-1-parameters")).toBe(true);
583
+ });
584
+
585
+ it("does not affect a different section's state", () => {
586
+ setHash("tr-1");
587
+ toggleMetadataSection("labels");
588
+ expect(collapsedTrees.value.has("tr-1-links")).toBe(false);
589
+ expect(collapsedTrees.value.has("tr-1-parameters")).toBe(false);
590
+ });
591
+
592
+ it("does nothing in split mode when testResult pane is not focused", () => {
593
+ layoutStore.value = "split";
594
+ activePane.value = "tree";
595
+ setHash("tr-1");
596
+ toggleMetadataSection("labels");
597
+ expect(collapsedTrees.value.has("tr-1-labels")).toBe(false);
598
+ });
599
+
600
+ it("toggles labels in split mode when testResult pane is focused", () => {
601
+ layoutStore.value = "split";
602
+ activePane.value = "testResult";
603
+ setHash("tr-1");
604
+ toggleMetadataSection("labels");
605
+ expect(collapsedTrees.value.has("tr-1-labels")).toBe(true);
606
+ });
607
+
608
+ it("toggles links in split mode when testResult pane is focused", () => {
609
+ layoutStore.value = "split";
610
+ activePane.value = "testResult";
611
+ setHash("tr-1");
612
+ toggleMetadataSection("links");
613
+ expect(collapsedTrees.value.has("tr-1-links")).toBe(true);
614
+ });
615
+ });