@allurereport/web-awesome 3.8.2 → 3.10.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 (227) hide show
  1. package/README.md +112 -0
  2. package/allurerc-dev.mjs +10 -0
  3. package/dist/multi/121.app-b18cce138691927e8759.js +1 -0
  4. package/dist/multi/173.app-b18cce138691927e8759.js +1 -0
  5. package/dist/multi/174.app-b18cce138691927e8759.js +1 -0
  6. package/dist/multi/252.app-b18cce138691927e8759.js +1 -0
  7. package/dist/multi/282.app-b18cce138691927e8759.js +1 -0
  8. package/dist/multi/29.app-b18cce138691927e8759.js +1 -0
  9. package/dist/multi/310.app-b18cce138691927e8759.js +1 -0
  10. package/dist/multi/416.app-b18cce138691927e8759.js +1 -0
  11. package/dist/multi/507.app-b18cce138691927e8759.js +1 -0
  12. package/dist/multi/527.app-b18cce138691927e8759.js +1 -0
  13. package/dist/multi/600.app-b18cce138691927e8759.js +1 -0
  14. package/dist/multi/605.app-b18cce138691927e8759.js +1 -0
  15. package/dist/multi/638.app-b18cce138691927e8759.js +1 -0
  16. package/dist/multi/672.app-b18cce138691927e8759.js +1 -0
  17. package/dist/multi/686.app-b18cce138691927e8759.js +1 -0
  18. package/dist/multi/725.app-b18cce138691927e8759.js +1 -0
  19. package/dist/multi/741.app-b18cce138691927e8759.js +1 -0
  20. package/dist/multi/749.app-b18cce138691927e8759.js +1 -0
  21. package/dist/multi/755.app-b18cce138691927e8759.js +1 -0
  22. package/dist/multi/779.app-b18cce138691927e8759.js +1 -0
  23. package/dist/multi/894.app-b18cce138691927e8759.js +1 -0
  24. package/dist/multi/943.app-b18cce138691927e8759.js +1 -0
  25. package/dist/multi/980.app-b18cce138691927e8759.js +1 -0
  26. package/dist/multi/app-b18cce138691927e8759.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-5c882b14b6f3112e40c4.css +1 -0
  30. package/dist/multi/styles-a4f65de86208f79dd2be.css +58 -0
  31. package/dist/single/app-733f473da7b51f98876d.js +2 -0
  32. package/dist/single/manifest.json +1 -1
  33. package/package.json +19 -14
  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/FooterVersion.tsx +5 -10
  47. package/src/components/Footer/index.tsx +7 -1
  48. package/src/components/Footer/styles.scss +8 -2
  49. package/src/components/Header/CiInfo/index.tsx +17 -13
  50. package/src/components/Header/CiInfo/styles.scss +1 -1
  51. package/src/components/Header/styles.scss +2 -2
  52. package/src/components/HeaderControls/index.tsx +1 -3
  53. package/src/components/HotkeysProvider/index.tsx +556 -0
  54. package/src/components/KeyboardShortcuts/index.tsx +73 -0
  55. package/src/components/KeyboardShortcuts/shortcutsConfig.ts +91 -0
  56. package/src/components/KeyboardShortcuts/styles.scss +69 -0
  57. package/src/components/MainReport/index.tsx +89 -72
  58. package/src/components/MainReport/styles.scss +20 -5
  59. package/src/components/Metadata/index.tsx +27 -6
  60. package/src/components/Metadata/styles.scss +21 -9
  61. package/src/components/MetadataButton/index.tsx +2 -0
  62. package/src/components/MetadataButton/styles.scss +1 -1
  63. package/src/components/NavTabs/styles.scss +8 -8
  64. package/src/components/ReportBody/styles.scss +3 -4
  65. package/src/components/ReportCategories/styles.scss +1 -1
  66. package/src/components/ReportFilters/styles.scss +1 -1
  67. package/src/components/ReportGlobalAttachments/styles.scss +1 -1
  68. package/src/components/ReportGlobalErrors/styles.scss +1 -1
  69. package/src/components/ReportHeader/index.tsx +25 -13
  70. package/src/components/ReportHeader/styles.scss +2 -2
  71. package/src/components/ReportMetadata/index.tsx +44 -15
  72. package/src/components/ReportMetadata/styles.scss +6 -6
  73. package/src/components/ReportQualityGateResults/styles.scss +2 -2
  74. package/src/components/ReportSearch/index.tsx +1 -5
  75. package/src/components/ReportTabs/styles.scss +9 -9
  76. package/src/components/SectionSwitcher/index.tsx +87 -10
  77. package/src/components/SideBySide/index.tsx +20 -2
  78. package/src/components/SideBySide/styles.scss +9 -1
  79. package/src/components/SplitLayout/index.tsx +11 -2
  80. package/src/components/SplitLayout/styles.scss +23 -4
  81. package/src/components/TestResult/TestStepsEmpty/styles.scss +1 -1
  82. package/src/components/TestResult/TrDescription/styles.scss +1 -1
  83. package/src/components/TestResult/TrDropdown/index.tsx +2 -2
  84. package/src/components/TestResult/TrDropdown/styles.scss +1 -1
  85. package/src/components/TestResult/TrEmpty/styles.scss +1 -1
  86. package/src/components/TestResult/TrEnvironmentItem/styles.scss +4 -4
  87. package/src/components/TestResult/TrError/index.tsx +32 -7
  88. package/src/components/TestResult/TrError/styles.scss +23 -23
  89. package/src/components/TestResult/TrHeader/styles.scss +2 -2
  90. package/src/components/TestResult/TrHistory/styles.scss +6 -6
  91. package/src/components/TestResult/TrInfo/styles.scss +8 -8
  92. package/src/components/TestResult/TrLinks/index.tsx +2 -2
  93. package/src/components/TestResult/TrLinks/styles.scss +2 -2
  94. package/src/components/TestResult/TrMetadata/index.tsx +1 -1
  95. package/src/components/TestResult/TrMetadata/styles.scss +1 -1
  96. package/src/components/TestResult/TrNavigation/index.tsx +1 -1
  97. package/src/components/TestResult/TrNavigation/styles.scss +2 -2
  98. package/src/components/TestResult/TrOverview.tsx +2 -0
  99. package/src/components/TestResult/TrParameters/index.tsx +1 -1
  100. package/src/components/TestResult/TrParameters/styles.scss +1 -1
  101. package/src/components/TestResult/TrPrevStatuses/styles.scss +8 -8
  102. package/src/components/TestResult/TrPwTraces/styles.scss +1 -1
  103. package/src/components/TestResult/TrRetriesView/TrRetriesItem.tsx +27 -1
  104. package/src/components/TestResult/TrRetriesView/styles.scss +20 -10
  105. package/src/components/TestResult/TrSetup/index.tsx +10 -4
  106. package/src/components/TestResult/TrSeverity/styles.scss +7 -7
  107. package/src/components/TestResult/TrStatus/styles.scss +2 -35
  108. package/src/components/TestResult/TrSteps/TrAttachment.tsx +79 -43
  109. package/src/components/TestResult/TrSteps/TrAttachmentInfo.tsx +44 -17
  110. package/src/components/TestResult/TrSteps/TrBodyItems.tsx +5 -2
  111. package/src/components/TestResult/TrSteps/TrErrorStep.tsx +3 -0
  112. package/src/components/TestResult/TrSteps/TrStep.tsx +15 -6
  113. package/src/components/TestResult/TrSteps/TrStepHeader.tsx +8 -5
  114. package/src/components/TestResult/TrSteps/index.tsx +7 -5
  115. package/src/components/TestResult/TrSteps/stepTreeExpansion.ts +27 -9
  116. package/src/components/TestResult/TrSteps/styles.scss +80 -20
  117. package/src/components/TestResult/TrTeardown/index.tsx +10 -4
  118. package/src/components/TestResult/bodyItems.ts +1 -1
  119. package/src/components/TestResult/index.tsx +8 -2
  120. package/src/components/TestResult/styles.scss +10 -1
  121. package/src/components/TestResult/trOverviewFocus.scss +4 -0
  122. package/src/components/Timeline/styles.scss +6 -6
  123. package/src/components/Tree/index.tsx +79 -5
  124. package/src/components/Tree/styles.scss +55 -35
  125. package/src/hooks/useTestResultOverviewFocusScroll.ts +23 -0
  126. package/src/index.html +30 -33
  127. package/src/index.tsx +12 -6
  128. package/src/locales/ar.json +62 -1
  129. package/src/locales/az.json +62 -1
  130. package/src/locales/de.json +62 -1
  131. package/src/locales/en.json +62 -1
  132. package/src/locales/es.json +62 -1
  133. package/src/locales/fr.json +62 -1
  134. package/src/locales/he.json +62 -1
  135. package/src/locales/hy.json +62 -1
  136. package/src/locales/it.json +62 -1
  137. package/src/locales/ja.json +62 -1
  138. package/src/locales/ka.json +62 -1
  139. package/src/locales/kr.json +62 -1
  140. package/src/locales/nl.json +62 -1
  141. package/src/locales/pl.json +62 -1
  142. package/src/locales/pt.json +62 -1
  143. package/src/locales/ru.json +62 -1
  144. package/src/locales/sv.json +62 -1
  145. package/src/locales/tr.json +62 -1
  146. package/src/locales/uk.json +62 -1
  147. package/src/locales/zh-TW.json +62 -1
  148. package/src/locales/zh.json +62 -1
  149. package/src/stores/keyboard.ts +371 -0
  150. package/src/stores/keyboardActions.ts +769 -0
  151. package/src/stores/locale.ts +5 -2
  152. package/src/stores/reportEnvSections.ts +6 -0
  153. package/src/stores/reportRootTabs.ts +95 -0
  154. package/src/stores/search.ts +147 -0
  155. package/src/stores/testResultOverviewNav.ts +119 -0
  156. package/src/stores/testResultTabs.ts +62 -0
  157. package/src/stores/timeline.ts +1 -1
  158. package/src/stores/tree.ts +42 -4
  159. package/src/stores/treeFilters/store.ts +3 -36
  160. package/src/stores/treeSort.ts +7 -1
  161. package/src/styles/_pane-active.scss +8 -0
  162. package/src/styles.scss +1 -1
  163. package/src/utils/atSeparator.ts +4 -0
  164. package/src/utils/flattenTestResultOverview.ts +182 -0
  165. package/src/utils/time.ts +2 -1
  166. package/src/utils/trOverviewFocus.ts +18 -0
  167. package/src/utils/treeFilters.ts +15 -4
  168. package/test/components/EnvironmentPicker.test.tsx +21 -3
  169. package/test/components/Footer.test.tsx +26 -0
  170. package/test/components/Header/CiInfo.test.tsx +56 -0
  171. package/test/components/Header.test.tsx +8 -0
  172. package/test/components/HeaderControls.test.tsx +28 -0
  173. package/test/components/ReportGlobals.test.tsx +9 -1
  174. package/test/components/ReportHeader.test.tsx +77 -0
  175. package/test/components/ReportMetadata.test.tsx +131 -0
  176. package/test/components/TestResult/PwTraceButton.test.tsx +8 -0
  177. package/test/components/TestResult/TrErrorStep.test.tsx +8 -0
  178. package/test/components/TestResult/TrOverview.test.tsx +30 -10
  179. package/test/components/TestResult/TrRetriesItem.test.tsx +163 -0
  180. package/test/components/TestResult/TrSteps.test.tsx +108 -0
  181. package/test/components/TestResult/bodyItems.test.ts +9 -1
  182. package/test/components/TestResult/openPwTraceInNewTab.test.ts +8 -0
  183. package/test/components/TestResult/stepTreeExpansion.test.ts +10 -2
  184. package/test/components/Timeline.test.tsx +15 -7
  185. package/test/stores/keyboard/keyboardActions.test.ts +615 -0
  186. package/test/stores/search.test.ts +143 -0
  187. package/test/stores/treeFilters/actions.test.ts +8 -0
  188. package/test/stores/treeSort.test.ts +58 -0
  189. package/test/utils/flattenTestResultOverview.test.ts +57 -0
  190. package/test/utils/ownerAddress.test.ts +9 -1
  191. package/test/utils/time.test.ts +52 -0
  192. package/test/utils/treeFilters.test.ts +113 -1
  193. package/types.d.ts +39 -0
  194. package/webpack.config.js +12 -7
  195. package/CONTRIBUTING.md +0 -34
  196. package/dist/multi/173.app-f008fb8342025f2b1ace.js +0 -1
  197. package/dist/multi/174.app-f008fb8342025f2b1ace.js +0 -1
  198. package/dist/multi/252.app-f008fb8342025f2b1ace.js +0 -1
  199. package/dist/multi/282.app-f008fb8342025f2b1ace.js +0 -1
  200. package/dist/multi/29.app-f008fb8342025f2b1ace.js +0 -1
  201. package/dist/multi/310.app-f008fb8342025f2b1ace.js +0 -1
  202. package/dist/multi/416.app-f008fb8342025f2b1ace.js +0 -1
  203. package/dist/multi/507.app-f008fb8342025f2b1ace.js +0 -1
  204. package/dist/multi/527.app-f008fb8342025f2b1ace.js +0 -1
  205. package/dist/multi/600.app-f008fb8342025f2b1ace.js +0 -1
  206. package/dist/multi/605.app-f008fb8342025f2b1ace.js +0 -1
  207. package/dist/multi/638.app-f008fb8342025f2b1ace.js +0 -1
  208. package/dist/multi/672.app-f008fb8342025f2b1ace.js +0 -1
  209. package/dist/multi/686.app-f008fb8342025f2b1ace.js +0 -1
  210. package/dist/multi/725.app-f008fb8342025f2b1ace.js +0 -1
  211. package/dist/multi/741.app-f008fb8342025f2b1ace.js +0 -1
  212. package/dist/multi/749.app-f008fb8342025f2b1ace.js +0 -1
  213. package/dist/multi/755.app-f008fb8342025f2b1ace.js +0 -1
  214. package/dist/multi/894.app-f008fb8342025f2b1ace.js +0 -1
  215. package/dist/multi/943.app-f008fb8342025f2b1ace.js +0 -1
  216. package/dist/multi/980.app-f008fb8342025f2b1ace.js +0 -1
  217. package/dist/multi/app-f008fb8342025f2b1ace.js +0 -2
  218. package/dist/multi/styles-9f7a23a0c8b79fa76981.css +0 -58
  219. package/dist/single/app-07332238da9897064301.js +0 -2
  220. package/src/assets/scss/day.scss +0 -53
  221. package/src/assets/scss/fonts.scss +0 -3
  222. package/src/assets/scss/night.scss +0 -63
  223. package/src/assets/scss/palette.scss +0 -393
  224. package/src/assets/scss/theme.scss +0 -330
  225. package/src/assets/scss/vars.scss +0 -11
  226. /package/dist/multi/{app-f008fb8342025f2b1ace.js.LICENSE.txt → app-b18cce138691927e8759.js.LICENSE.txt} +0 -0
  227. /package/dist/single/{app-07332238da9897064301.js.LICENSE.txt → app-733f473da7b51f98876d.js.LICENSE.txt} +0 -0
@@ -0,0 +1,143 @@
1
+ import type { AwesomeSearchDocument } from "types";
2
+ import { beforeEach, describe, expect, it, vi } from "vitest";
3
+
4
+ const { fetchReportJsonDataMock } = vi.hoisted(() => ({
5
+ fetchReportJsonDataMock: vi.fn(),
6
+ }));
7
+
8
+ vi.mock("@allurereport/web-commons", async () => {
9
+ const actual = await vi.importActual<typeof import("@allurereport/web-commons")>("@allurereport/web-commons");
10
+
11
+ return {
12
+ ...actual,
13
+ fetchReportJsonData: fetchReportJsonDataMock,
14
+ };
15
+ });
16
+
17
+ import { ReportFetchError } from "@allurereport/web-commons";
18
+
19
+ import {
20
+ createSearchIndex,
21
+ fetchEnvSearchIndexes,
22
+ resetSearchIndexes,
23
+ searchIndexesStore,
24
+ searchNodeIds,
25
+ } from "../../src/stores/search.js";
26
+
27
+ const documents: AwesomeSearchDocument[] = [
28
+ {
29
+ id: "tr-1",
30
+ nodeId: "tr-1",
31
+ name: "renders request form",
32
+ fullName: "forms.RequestWithDatesFormTest.shouldRender",
33
+ historyId: "history-request-form",
34
+ owner: "Igor Martynov",
35
+ labels: "feature:Forms Forms",
36
+ tags: "smoke",
37
+ parameters: "browser:chromium browser chromium",
38
+ categories: "Product defects",
39
+ statusMessage: "Date format assertion failed",
40
+ links: "ISSUE-42 https://example.com/ISSUE-42 issue",
41
+ },
42
+ {
43
+ id: "tr-2",
44
+ nodeId: "tr-2",
45
+ name: "submits checkout",
46
+ fullName: "checkout.SubmitCheckoutTest.shouldSubmit",
47
+ owner: "Jane Smith",
48
+ labels: "feature:Checkout Checkout",
49
+ tags: "regression",
50
+ parameters: "region:eu region eu",
51
+ categories: "Infrastructure",
52
+ statusMessage: "Network timeout",
53
+ links: "PAYMENTS-932 https://example.com/PAYMENTS-932 issue",
54
+ },
55
+ ];
56
+
57
+ type SearchCase = [string, AwesomeSearchDocument[], string, string[]];
58
+ type SearchFindCase = [string, string[]];
59
+
60
+ describe("stores > search", () => {
61
+ beforeEach(() => {
62
+ fetchReportJsonDataMock.mockReset();
63
+ resetSearchIndexes();
64
+ });
65
+
66
+ it.each<SearchFindCase>([
67
+ ["RequestWithDatesFormTest", ["tr-1"]],
68
+ ["history-request-form", ["tr-1"]],
69
+ ["Igor Martynov", ["tr-1"]],
70
+ ["smoke", ["tr-1"]],
71
+ ["browser:chromium", ["tr-1"]],
72
+ ["region eu", ["tr-2"]],
73
+ ["Product defects", ["tr-1"]],
74
+ ["Date format assertion", ["tr-1"]],
75
+ ["PAYMENTS-932", ["tr-2"]],
76
+ ])("should find tests by %s", (query, expectedNodeIds) => {
77
+ const searchIndex = createSearchIndex(documents);
78
+
79
+ expect(searchNodeIds(searchIndex, query)).toEqual(new Set(expectedNodeIds));
80
+ });
81
+
82
+ it.each<SearchCase>([
83
+ ["empty index", [], "anything", []],
84
+ ["empty query", documents, "", []],
85
+ ["blank query", documents, " ", []],
86
+ ["no-match query", documents, "does-not-exist", []],
87
+ ["prefix query", documents, "subm", ["tr-2"]],
88
+ ["fuzzy query", documents, "chekout", ["tr-2"]],
89
+ ["case-insensitive query", documents, "igor martynov", ["tr-1"]],
90
+ ])("should support %s", (_caseName, sourceDocuments, query, expectedNodeIds) => {
91
+ const searchIndex = createSearchIndex(sourceDocuments);
92
+
93
+ expect(searchNodeIds(searchIndex, query)).toEqual(new Set(expectedNodeIds));
94
+ });
95
+
96
+ it("should not retry missing environment index fetches on later calls", async () => {
97
+ fetchReportJsonDataMock.mockRejectedValue(
98
+ new ReportFetchError("missing search index", new Response(null, { status: 404, statusText: "Not Found" })),
99
+ );
100
+
101
+ await fetchEnvSearchIndexes(["missing-env"]);
102
+ await fetchEnvSearchIndexes(["missing-env"]);
103
+
104
+ expect(fetchReportJsonDataMock).toHaveBeenCalledOnce();
105
+ expect(searchIndexesStore.value.data).toEqual({});
106
+ expect(searchIndexesStore.value.error).toContain("missing search index");
107
+ });
108
+
109
+ it("should retry transient environment index fetch failures on later calls", async () => {
110
+ fetchReportJsonDataMock.mockRejectedValueOnce(new Error("temporary search index error")).mockResolvedValueOnce([]);
111
+
112
+ await fetchEnvSearchIndexes(["unstable-env"]);
113
+ await fetchEnvSearchIndexes(["unstable-env"]);
114
+
115
+ expect(fetchReportJsonDataMock).toHaveBeenCalledTimes(2);
116
+ expect(searchIndexesStore.value.data?.["unstable-env"]).toBeDefined();
117
+ expect(searchIndexesStore.value.error).toBeUndefined();
118
+ });
119
+
120
+ it("should preserve indexes loaded by overlapping fetches", async () => {
121
+ let resolveFirstFetch: (documents: AwesomeSearchDocument[]) => void = () => {};
122
+ let resolveSecondFetch: (documents: AwesomeSearchDocument[]) => void = () => {};
123
+ const firstFetch = new Promise<AwesomeSearchDocument[]>((resolve) => {
124
+ resolveFirstFetch = resolve;
125
+ });
126
+ const secondFetch = new Promise<AwesomeSearchDocument[]>((resolve) => {
127
+ resolveSecondFetch = resolve;
128
+ });
129
+
130
+ fetchReportJsonDataMock.mockReturnValueOnce(firstFetch).mockReturnValueOnce(secondFetch);
131
+
132
+ const firstIndexFetch = fetchEnvSearchIndexes(["env-a"]);
133
+ const secondIndexFetch = fetchEnvSearchIndexes(["env-b"]);
134
+
135
+ resolveSecondFetch([documents[1]]);
136
+ await secondIndexFetch;
137
+ resolveFirstFetch([documents[0]]);
138
+ await firstIndexFetch;
139
+
140
+ expect(searchIndexesStore.value.data?.["env-a"]).toBeDefined();
141
+ expect(searchIndexesStore.value.data?.["env-b"]).toBeDefined();
142
+ });
143
+ });
@@ -1,5 +1,13 @@
1
+ import { epic, feature, label, story } from "allure-js-commons";
1
2
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
3
 
4
+ beforeEach(async () => {
5
+ await epic("coverage");
6
+ await feature("ui-state");
7
+ await story("actions");
8
+ await label("coverage", "ui-state");
9
+ });
10
+
3
11
  const { fetchReportJsonDataMock, setParamsMock } = vi.hoisted(() => ({
4
12
  fetchReportJsonDataMock: vi.fn(),
5
13
  setParamsMock: vi.fn(),
@@ -0,0 +1,58 @@
1
+ import { epic, feature, label, story } from "allure-js-commons";
2
+ import { beforeEach, describe, expect, it, vi } from "vitest";
3
+
4
+ const STORAGE_KEY = "ALLURE_REPORT_SORT_BY";
5
+
6
+ beforeEach(async () => {
7
+ await epic("coverage");
8
+ await feature("sort");
9
+ await story("treeSort");
10
+ await label("coverage", "sort");
11
+ });
12
+
13
+ describe("stores > treeSort", () => {
14
+ beforeEach(() => {
15
+ vi.resetModules();
16
+ localStorage.clear();
17
+ delete (globalThis as any).allureReportOptions;
18
+ });
19
+
20
+ it("defaults to order,asc when no config and no localStorage value", async () => {
21
+ const { sortBy } = await import("../../src/stores/treeSort.js");
22
+
23
+ expect(sortBy.value).toBe("order,asc");
24
+ });
25
+
26
+ it("uses defaultSortBy from reportOptions when localStorage is empty", async () => {
27
+ (globalThis as any).allureReportOptions = { defaultSortBy: "name,asc" };
28
+
29
+ const { sortBy } = await import("../../src/stores/treeSort.js");
30
+
31
+ expect(sortBy.value).toBe("name,asc");
32
+ });
33
+
34
+ it("is case-insensitive for defaultSortBy", async () => {
35
+ (globalThis as any).allureReportOptions = { defaultSortBy: "Name,ASC" };
36
+
37
+ const { sortBy } = await import("../../src/stores/treeSort.js");
38
+
39
+ expect(sortBy.value).toBe("name,asc");
40
+ });
41
+
42
+ it("ignores invalid defaultSortBy and falls back to order,asc", async () => {
43
+ (globalThis as any).allureReportOptions = { defaultSortBy: "invalid,value" };
44
+
45
+ const { sortBy } = await import("../../src/stores/treeSort.js");
46
+
47
+ expect(sortBy.value).toBe("order,asc");
48
+ });
49
+
50
+ it("localStorage takes priority over defaultSortBy from reportOptions", async () => {
51
+ localStorage.setItem(STORAGE_KEY, "duration,desc");
52
+ (globalThis as any).allureReportOptions = { defaultSortBy: "name,asc" };
53
+
54
+ const { sortBy } = await import("../../src/stores/treeSort.js");
55
+
56
+ expect(sortBy.value).toBe("duration,desc");
57
+ });
58
+ });
@@ -0,0 +1,57 @@
1
+ import { describe, expect, it } from "vitest";
2
+
3
+ import { flattenTestResultOverview } from "@/utils/flattenTestResultOverview";
4
+
5
+ describe("flattenTestResultOverview", () => {
6
+ it("skips metadata sections and lists execution blocks with nested steps", () => {
7
+ const flat = flattenTestResultOverview({
8
+ testResultId: "tr-1",
9
+ hasSetup: false,
10
+ setupBodyItems: [],
11
+ bodyItems: [
12
+ {
13
+ type: "step",
14
+ item: {
15
+ stepId: "step-a",
16
+ name: "Step A",
17
+ status: "passed",
18
+ },
19
+ bodyItems: [],
20
+ suppressInlineError: false,
21
+ },
22
+ ],
23
+ hasTeardown: false,
24
+ teardownBodyItems: [],
25
+ isGroupOpened: () => true,
26
+ stepExpansionPolicy: "expanded",
27
+ });
28
+
29
+ expect(flat.map((node) => node.id)).toEqual(["tr-1-steps", "step-a"]);
30
+ });
31
+
32
+ it("hides nested steps when the steps block is collapsed", () => {
33
+ const flat = flattenTestResultOverview({
34
+ testResultId: "tr-2",
35
+ hasSetup: false,
36
+ setupBodyItems: [],
37
+ bodyItems: [
38
+ {
39
+ type: "step",
40
+ item: {
41
+ stepId: "step-b",
42
+ name: "Step B",
43
+ status: "passed",
44
+ },
45
+ bodyItems: [],
46
+ suppressInlineError: false,
47
+ },
48
+ ],
49
+ hasTeardown: false,
50
+ teardownBodyItems: [],
51
+ isGroupOpened: () => false,
52
+ stepExpansionPolicy: "collapsed",
53
+ });
54
+
55
+ expect(flat.map((node) => node.id)).toEqual(["tr-2-steps"]);
56
+ });
57
+ });
@@ -1,7 +1,15 @@
1
- import { describe, expect, it } from "vitest";
1
+ import { epic, feature, label, story } from "allure-js-commons";
2
+ import { beforeEach, describe, expect, it } from "vitest";
2
3
 
3
4
  import { parseOwnerAddress } from "../../src/utils/ownerAddress.js";
4
5
 
6
+ beforeEach(async () => {
7
+ await epic("coverage");
8
+ await feature("labels-and-tags");
9
+ await story("ownerAddress");
10
+ await label("coverage", "labels-and-tags");
11
+ });
12
+
5
13
  describe("utils > ownerAddress", () => {
6
14
  describe("parseOwnerAddress", () => {
7
15
  it("returns none type with empty displayName for null input", () => {
@@ -0,0 +1,52 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+
3
+ import { timestampToDate } from "@/utils/time";
4
+
5
+ const numericDateTimeOptions: Intl.DateTimeFormatOptions = {
6
+ month: "numeric",
7
+ day: "numeric",
8
+ year: "numeric",
9
+ hour: "numeric",
10
+ minute: "numeric",
11
+ second: "numeric",
12
+ hour12: false,
13
+ };
14
+
15
+ vi.mock("@/stores/locale", () => ({
16
+ currentLocale: { value: "en" },
17
+ currentLocaleIso: { value: "en-US" },
18
+ useI18n: () => ({
19
+ t: (key: string) => (key === "at" ? "at" : key),
20
+ }),
21
+ }));
22
+
23
+ vi.mock("@allurereport/web-commons", async (importOriginal) => ({
24
+ ...(await importOriginal()),
25
+ getLocaleDateTimeOverride: () => undefined,
26
+ }));
27
+
28
+ describe("utils > timestampToDate", () => {
29
+ it("should keep Intl-provided at separator for long English dates", () => {
30
+ const timestamp = 1527776360360;
31
+ const options: Intl.DateTimeFormatOptions = {
32
+ month: "long",
33
+ day: "numeric",
34
+ year: "numeric",
35
+ hour: "numeric",
36
+ minute: "numeric",
37
+ second: "numeric",
38
+ };
39
+ const intlFormatted = new Intl.DateTimeFormat("en-US", options).format(new Date(timestamp));
40
+ const formatted = timestampToDate(timestamp, options);
41
+
42
+ expect(formatted).toBe(intlFormatted);
43
+ expect(formatted).not.toContain("May 31 at 2018 at");
44
+ });
45
+
46
+ it("should add at separator when Intl returns comma-separated date and time", () => {
47
+ const timestamp = 1779887536194;
48
+ const intlFormatted = new Intl.DateTimeFormat("en-US", numericDateTimeOptions).format(new Date(timestamp));
49
+
50
+ expect(timestampToDate(timestamp, numericDateTimeOptions)).toBe(intlFormatted.replace(",", " at"));
51
+ });
52
+ });
@@ -1,4 +1,5 @@
1
- import { describe, expect, it } from "vitest";
1
+ import { epic, feature, label, story } from "allure-js-commons";
2
+ import { beforeEach, describe, expect, it } from "vitest";
2
3
 
3
4
  import { createRecursiveTree, filterLeaves } from "../../src/utils/treeFilters.js";
4
5
  import type { AwesomeTestResult } from "../../types.js";
@@ -6,6 +7,13 @@ import type { AwesomeTestResult } from "../../types.js";
6
7
  // Predicate that always returns true (no filtering)
7
8
  const alwaysTruePredicate = () => true;
8
9
 
10
+ beforeEach(async () => {
11
+ await epic("coverage");
12
+ await feature("filters");
13
+ await story("treeFilters");
14
+ await label("coverage", "filters");
15
+ });
16
+
9
17
  describe("utils > treeFilters", () => {
10
18
  describe("filterLeaves", () => {
11
19
  it("sorts leave by duration in ascending order", () => {
@@ -337,6 +345,110 @@ describe("utils > treeFilters", () => {
337
345
  );
338
346
  });
339
347
 
348
+ it("sorts groups by earliest leaf groupOrder when sorting by order ascending", () => {
349
+ const group = {
350
+ leaves: [],
351
+ groups: ["groupA", "groupB", "groupC"],
352
+ };
353
+ const leavesById = {
354
+ t1: { name: "t1", status: "passed", groupOrder: 5 } as AwesomeTestResult,
355
+ t2: { name: "t2", status: "passed", groupOrder: 2 } as AwesomeTestResult,
356
+ t3: { name: "t3", status: "passed", groupOrder: 8 } as AwesomeTestResult,
357
+ };
358
+ const groupsById = {
359
+ groupA: { name: "groupA", leaves: ["t1"], groups: [] as string[] },
360
+ groupB: { name: "groupB", leaves: ["t2"], groups: [] as string[] },
361
+ groupC: { name: "groupC", leaves: ["t3"], groups: [] as string[] },
362
+ };
363
+ const result = createRecursiveTree({
364
+ group: group as any,
365
+ leavesById: leavesById as any,
366
+ groupsById: groupsById as any,
367
+ filterPredicate: alwaysTruePredicate,
368
+ sortBy: "order,asc",
369
+ });
370
+
371
+ expect(result.trees.map((t) => t.name)).toEqual(["groupB", "groupA", "groupC"]);
372
+ });
373
+
374
+ it("sorts groups by earliest leaf groupOrder when sorting by order descending", () => {
375
+ const group = {
376
+ leaves: [],
377
+ groups: ["groupA", "groupB", "groupC"],
378
+ };
379
+ const leavesById = {
380
+ t1: { name: "t1", status: "passed", groupOrder: 5 } as AwesomeTestResult,
381
+ t2: { name: "t2", status: "passed", groupOrder: 2 } as AwesomeTestResult,
382
+ t3: { name: "t3", status: "passed", groupOrder: 8 } as AwesomeTestResult,
383
+ };
384
+ const groupsById = {
385
+ groupA: { name: "groupA", leaves: ["t1"], groups: [] as string[] },
386
+ groupB: { name: "groupB", leaves: ["t2"], groups: [] as string[] },
387
+ groupC: { name: "groupC", leaves: ["t3"], groups: [] as string[] },
388
+ };
389
+ const result = createRecursiveTree({
390
+ group: group as any,
391
+ leavesById: leavesById as any,
392
+ groupsById: groupsById as any,
393
+ filterPredicate: alwaysTruePredicate,
394
+ sortBy: "order,desc",
395
+ });
396
+
397
+ expect(result.trees.map((t) => t.name)).toEqual(["groupC", "groupA", "groupB"]);
398
+ });
399
+
400
+ it("sorts groups by aggregated duration when sorting by duration ascending", () => {
401
+ const group = {
402
+ leaves: [],
403
+ groups: ["groupA", "groupB", "groupC"],
404
+ };
405
+ const leavesById = {
406
+ t1: { name: "t1", status: "passed", duration: 3000 } as AwesomeTestResult,
407
+ t2: { name: "t2", status: "passed", duration: 1000 } as AwesomeTestResult,
408
+ t3: { name: "t3", status: "passed", duration: 5000 } as AwesomeTestResult,
409
+ };
410
+ const groupsById = {
411
+ groupA: { name: "groupA", leaves: ["t1"], groups: [] as string[] },
412
+ groupB: { name: "groupB", leaves: ["t2"], groups: [] as string[] },
413
+ groupC: { name: "groupC", leaves: ["t3"], groups: [] as string[] },
414
+ };
415
+ const result = createRecursiveTree({
416
+ group: group as any,
417
+ leavesById: leavesById as any,
418
+ groupsById: groupsById as any,
419
+ filterPredicate: alwaysTruePredicate,
420
+ sortBy: "duration,asc",
421
+ });
422
+
423
+ expect(result.trees.map((t) => t.name)).toEqual(["groupB", "groupA", "groupC"]);
424
+ });
425
+
426
+ it("sorts groups by aggregated duration when sorting by duration descending", () => {
427
+ const group = {
428
+ leaves: [],
429
+ groups: ["groupA", "groupB", "groupC"],
430
+ };
431
+ const leavesById = {
432
+ t1: { name: "t1", status: "passed", duration: 3000 } as AwesomeTestResult,
433
+ t2: { name: "t2", status: "passed", duration: 1000 } as AwesomeTestResult,
434
+ t3: { name: "t3", status: "passed", duration: 5000 } as AwesomeTestResult,
435
+ };
436
+ const groupsById = {
437
+ groupA: { name: "groupA", leaves: ["t1"], groups: [] as string[] },
438
+ groupB: { name: "groupB", leaves: ["t2"], groups: [] as string[] },
439
+ groupC: { name: "groupC", leaves: ["t3"], groups: [] as string[] },
440
+ };
441
+ const result = createRecursiveTree({
442
+ group: group as any,
443
+ leavesById: leavesById as any,
444
+ groupsById: groupsById as any,
445
+ filterPredicate: alwaysTruePredicate,
446
+ sortBy: "duration,desc",
447
+ });
448
+
449
+ expect(result.trees.map((t) => t.name)).toEqual(["groupC", "groupA", "groupB"]);
450
+ });
451
+
340
452
  it("keeps problem-heavy groups first when sorting by status in descending order", () => {
341
453
  const group = {
342
454
  leaves: [],
package/types.d.ts CHANGED
@@ -14,6 +14,23 @@ import type {
14
14
  export type Layout = "base" | "split";
15
15
  export type StepTreeExpansion = "collapsed" | "expand_failed_only" | "expanded";
16
16
 
17
+ export type AwesomeRunSummary = {
18
+ start: number;
19
+ stop: number;
20
+ duration: number;
21
+ };
22
+
23
+ export type AwesomeExecutorInfo = {
24
+ name?: string;
25
+ type?: string;
26
+ url?: string;
27
+ buildOrder?: number;
28
+ buildName?: string;
29
+ buildUrl?: string;
30
+ reportName?: string;
31
+ reportUrl?: string;
32
+ };
33
+
17
34
  export type AwesomeReportOptions = {
18
35
  allureVersion: string;
19
36
  reportName?: string;
@@ -28,7 +45,10 @@ export type AwesomeReportOptions = {
28
45
  sections?: string[];
29
46
  cacheKey: string;
30
47
  ci?: CiDescriptor;
48
+ executor?: AwesomeExecutorInfo;
49
+ runSummary?: AwesomeRunSummary;
31
50
  stepTreeExpansion?: StepTreeExpansion;
51
+ defaultSortBy?: string;
32
52
  };
33
53
 
34
54
  export type AwesomeFixtureResult = Omit<
@@ -67,6 +87,7 @@ export type AwesomeTestResult = Omit<
67
87
  | "steps"
68
88
  | "environment"
69
89
  > & {
90
+ isRetry: boolean;
70
91
  setup: AwesomeFixtureResult[];
71
92
  teardown: AwesomeFixtureResult[];
72
93
  steps: AwesomeTestStepResult[];
@@ -98,6 +119,22 @@ export type AwesomeTreeLeaf = Pick<
98
119
  export type AwesomeTreeGroup = WithChildren & DefaultTreeGroup & { nodeId: string };
99
120
 
100
121
  export type AwesomeTree = TreeData<AwesomeTreeLeaf, AwesomeTreeGroup>;
122
+
123
+ export type AwesomeSearchDocument = {
124
+ id: string;
125
+ nodeId: string;
126
+ name: string;
127
+ fullName?: string;
128
+ historyId?: string;
129
+ labels?: string;
130
+ owner?: string;
131
+ tags?: string;
132
+ parameters?: string;
133
+ categories?: string;
134
+ statusMessage?: string;
135
+ links?: string;
136
+ };
137
+
101
138
  /**
102
139
  * Tree which contains tree leaves instead of their IDs and recursive trees structure instead of groups
103
140
  */
@@ -105,6 +142,8 @@ export type AwesomeRecursiveTree = DefaultTreeGroup & {
105
142
  nodeId: string;
106
143
  leaves: AwesomeTreeLeaf[];
107
144
  trees: AwesomeRecursiveTree[];
145
+ duration: number;
146
+ groupOrder: number;
108
147
  };
109
148
 
110
149
  // TODO: maybe it should call `TestCase` instead of Group
package/webpack.config.js CHANGED
@@ -1,14 +1,16 @@
1
+ import { createRequire } from "node:module";
1
2
  import { dirname, join } from "node:path";
2
3
  import { env } from "node:process";
3
4
  import { fileURLToPath } from "node:url";
4
5
 
5
- import ForkTsCheckerPlugin from "fork-ts-checker-webpack-plugin";
6
- import HtmlWebpackPlugin from "html-webpack-plugin";
7
- import MiniCssExtractPlugin from "mini-css-extract-plugin";
8
- import SpriteLoaderPlugin from "svg-sprite-loader/plugin.js";
9
- import TerserPlugin from "terser-webpack-plugin";
10
- import webpack from "webpack";
11
- import { WebpackManifestPlugin } from "webpack-manifest-plugin";
6
+ const require = createRequire(import.meta.url);
7
+ const ForkTsCheckerPlugin = require("fork-ts-checker-webpack-plugin");
8
+ const HtmlWebpackPlugin = require("html-webpack-plugin");
9
+ const MiniCssExtractPlugin = require("mini-css-extract-plugin");
10
+ const SpriteLoaderPlugin = require("svg-sprite-loader/plugin");
11
+ const TerserPlugin = require("terser-webpack-plugin");
12
+ const webpack = require("webpack");
13
+ const { WebpackManifestPlugin } = require("webpack-manifest-plugin");
12
14
 
13
15
  const { SINGLE_FILE_MODE } = env;
14
16
  const baseDir = dirname(fileURLToPath(import.meta.url));
@@ -24,6 +26,7 @@ export default (env, argv) => {
24
26
  path: join(baseDir, SINGLE_FILE_MODE ? "dist/single" : "dist/multi"),
25
27
  filename: devMode ? "app.js" : "app-[fullhash].js",
26
28
  assetModuleFilename: "[name][ext]",
29
+ publicPath: devMode ? "auto" : undefined,
27
30
  },
28
31
  devtool: devMode ? "eval-source-map" : false,
29
32
  optimization: {
@@ -146,6 +149,8 @@ export default (env, argv) => {
146
149
  config.plugins.push(
147
150
  new HtmlWebpackPlugin({
148
151
  template: "src/index.html",
152
+ inject: "body",
153
+ scriptLoading: "defer",
149
154
  }),
150
155
  );
151
156
  }
package/CONTRIBUTING.md DELETED
@@ -1,34 +0,0 @@
1
- # Contributing
2
-
3
- ## Development
4
-
5
- If you want to develop the report directly, using webpack with hot module replacement feature, follow the next steps:
6
-
7
- ### 1. Install the dependencies:
8
-
9
- ```bash
10
- $ yarn install
11
- ```
12
-
13
- ### 2. Copy generated Allure Awesome data files
14
-
15
- Copy `data` and `widgets` directories from the previously generated Allure Awesome report to `out/dev` directory.
16
-
17
- If you don't have one, you can generate Allure Awesome report in the sandbox package:
18
-
19
- ```bash
20
- # cd to the web-awesome package
21
- yarn workspace @allurereport/sandbox t
22
- yarn workspace @allurereport/sandbox report
23
- mkdir -p out/dev
24
- cp -rf ../sandbox/allure-report/awesome/data out/dev
25
- cp -rf ../sandbox/allure-report/awesome/widgets out/dev
26
- ```
27
-
28
- ### 3. Run dev script
29
-
30
- ```bash
31
- yarn workspace @allurereport/web-awesome dev
32
- ```
33
-
34
- Open the started report in the browser: http://localhost:8080.