@allurereport/web-awesome 3.5.0 → 3.6.1

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 (62) hide show
  1. package/dist/multi/app-1c928f385beb78e2c2e8.js +2 -0
  2. package/dist/multi/{app-15cc636581486c011867.js.LICENSE.txt → app-1c928f385beb78e2c2e8.js.LICENSE.txt} +1 -1
  3. package/dist/multi/manifest.json +23 -23
  4. package/dist/multi/{styles-fd12e72f7e024e668bb4.css → styles-3515d3bdd45651cd4e66.css} +8 -8
  5. package/dist/single/app-16786ab64ac3e094685f.js +2 -0
  6. package/dist/single/{app-bbd6e33664f6d94cfaac.js.LICENSE.txt → app-16786ab64ac3e094685f.js.LICENSE.txt} +1 -1
  7. package/dist/single/manifest.json +1 -1
  8. package/package.json +6 -6
  9. package/src/components/Header/styles.scss +2 -1
  10. package/src/components/SectionSwitcher/styles.scss +1 -1
  11. package/src/components/SideBySide/index.tsx +1 -1
  12. package/src/components/SideBySide/styles.scss +6 -5
  13. package/src/components/TestResult/TrDropdown/index.tsx +7 -1
  14. package/src/components/TestResult/TrDropdown/styles.scss +7 -0
  15. package/src/components/TestResult/TrHeader/styles.scss +3 -0
  16. package/src/components/TestResult/TrRetriesView/TrRetriesItem.tsx +4 -3
  17. package/src/components/TestResult/TrRetriesView/index.tsx +2 -2
  18. package/src/components/TestResult/TrSetup/index.tsx +6 -16
  19. package/src/components/TestResult/TrSteps/TrAttachment.tsx +3 -3
  20. package/src/components/TestResult/TrSteps/TrErrorStep.tsx +9 -3
  21. package/src/components/TestResult/TrSteps/TrStep.tsx +113 -8
  22. package/src/components/TestResult/TrSteps/TrStepHeader.tsx +3 -0
  23. package/src/components/TestResult/TrSteps/index.tsx +90 -5
  24. package/src/components/TestResult/TrSteps/stepTreeExpansion.ts +101 -0
  25. package/src/components/TestResult/TrSteps/styles.scss +12 -0
  26. package/src/components/TestResult/TrTeardown/index.tsx +6 -16
  27. package/src/components/TestResult/bodyItems.ts +26 -1
  28. package/src/components/Tree/index.tsx +7 -2
  29. package/src/components/Tree/styles.scss +1 -1
  30. package/src/index.tsx +13 -2
  31. package/src/stores/env.ts +6 -3
  32. package/src/stores/envInfo.ts +2 -2
  33. package/src/stores/sections.ts +3 -1
  34. package/src/stores/stats.ts +3 -3
  35. package/src/stores/tree.ts +40 -8
  36. package/test/components/TestResult/bodyItems.test.ts +25 -2
  37. package/test/components/TestResult/stepTreeExpansion.test.ts +179 -0
  38. package/types.d.ts +2 -0
  39. package/webpack.config.js +15 -3
  40. package/dist/multi/app-15cc636581486c011867.js +0 -2
  41. package/dist/single/app-bbd6e33664f6d94cfaac.js +0 -2
  42. /package/dist/multi/{173.app-15cc636581486c011867.js → 173.app-1c928f385beb78e2c2e8.js} +0 -0
  43. /package/dist/multi/{174.app-15cc636581486c011867.js → 174.app-1c928f385beb78e2c2e8.js} +0 -0
  44. /package/dist/multi/{252.app-15cc636581486c011867.js → 252.app-1c928f385beb78e2c2e8.js} +0 -0
  45. /package/dist/multi/{282.app-15cc636581486c011867.js → 282.app-1c928f385beb78e2c2e8.js} +0 -0
  46. /package/dist/multi/{29.app-15cc636581486c011867.js → 29.app-1c928f385beb78e2c2e8.js} +0 -0
  47. /package/dist/multi/{310.app-15cc636581486c011867.js → 310.app-1c928f385beb78e2c2e8.js} +0 -0
  48. /package/dist/multi/{416.app-15cc636581486c011867.js → 416.app-1c928f385beb78e2c2e8.js} +0 -0
  49. /package/dist/multi/{507.app-15cc636581486c011867.js → 507.app-1c928f385beb78e2c2e8.js} +0 -0
  50. /package/dist/multi/{527.app-15cc636581486c011867.js → 527.app-1c928f385beb78e2c2e8.js} +0 -0
  51. /package/dist/multi/{600.app-15cc636581486c011867.js → 600.app-1c928f385beb78e2c2e8.js} +0 -0
  52. /package/dist/multi/{605.app-15cc636581486c011867.js → 605.app-1c928f385beb78e2c2e8.js} +0 -0
  53. /package/dist/multi/{638.app-15cc636581486c011867.js → 638.app-1c928f385beb78e2c2e8.js} +0 -0
  54. /package/dist/multi/{672.app-15cc636581486c011867.js → 672.app-1c928f385beb78e2c2e8.js} +0 -0
  55. /package/dist/multi/{686.app-15cc636581486c011867.js → 686.app-1c928f385beb78e2c2e8.js} +0 -0
  56. /package/dist/multi/{725.app-15cc636581486c011867.js → 725.app-1c928f385beb78e2c2e8.js} +0 -0
  57. /package/dist/multi/{741.app-15cc636581486c011867.js → 741.app-1c928f385beb78e2c2e8.js} +0 -0
  58. /package/dist/multi/{749.app-15cc636581486c011867.js → 749.app-1c928f385beb78e2c2e8.js} +0 -0
  59. /package/dist/multi/{755.app-15cc636581486c011867.js → 755.app-1c928f385beb78e2c2e8.js} +0 -0
  60. /package/dist/multi/{894.app-15cc636581486c011867.js → 894.app-1c928f385beb78e2c2e8.js} +0 -0
  61. /package/dist/multi/{943.app-15cc636581486c011867.js → 943.app-1c928f385beb78e2c2e8.js} +0 -0
  62. /package/dist/multi/{980.app-15cc636581486c011867.js → 980.app-1c928f385beb78e2c2e8.js} +0 -0
@@ -0,0 +1,101 @@
1
+ import type { TestStatus } from "@allurereport/core-api";
2
+ import { getReportOptions } from "@allurereport/web-commons";
3
+ import {
4
+ getNextSubtreeToggleState,
5
+ getSubtreeToggleIcon,
6
+ isSubtreeFirstLevelOnlyOpened,
7
+ type SubtreeNodeState,
8
+ type SubtreeToggleState,
9
+ } from "@allurereport/web-commons";
10
+
11
+ import { hasTestLevelErrorContent, type TrBodyItem, type TrStepItem } from "@/components/TestResult/bodyItems";
12
+
13
+ import type { AwesomeReportOptions, StepTreeExpansion } from "../../../../types";
14
+
15
+ const DEFAULT_STEP_TREE_EXPANSION_POLICY: StepTreeExpansion = "expand_failed_only";
16
+
17
+ const isFailedStatus = (status: TestStatus) => status === "failed" || status === "broken";
18
+
19
+ export const hasFailedStepContext = (bodyItems: TrBodyItem[]): boolean =>
20
+ bodyItems.some((bodyItem) => {
21
+ if (bodyItem.type === "step") {
22
+ return isFailedStatus(bodyItem.item.status) || hasFailedStepContext(bodyItem.bodyItems);
23
+ }
24
+
25
+ if (bodyItem.type === "error") {
26
+ return isFailedStatus(bodyItem.status);
27
+ }
28
+
29
+ return false;
30
+ });
31
+
32
+ const hasInlineStepError = (stepItem: TrStepItem) => {
33
+ const { item: stepData, suppressInlineError } = stepItem;
34
+ return Boolean((stepData.message || stepData.trace) && !stepData.hasSimilarErrorInSubSteps && !suppressInlineError);
35
+ };
36
+
37
+ export const hasStepContent = (stepItem: TrStepItem): boolean => {
38
+ return Boolean(stepItem.bodyItems.length || stepItem.item.parameters?.length || hasInlineStepError(stepItem));
39
+ };
40
+
41
+ export const isStepOpenedByDefault = (
42
+ policy: StepTreeExpansion,
43
+ status: TestStatus,
44
+ bodyItems: TrBodyItem[],
45
+ ): boolean => {
46
+ const hasFailedContext = status === "failed" || status === "broken" || hasFailedStepContext(bodyItems);
47
+ return isOpenByDefaultForPolicy(policy, hasFailedContext);
48
+ };
49
+
50
+ export type ExpandableStepNode = {
51
+ id: string;
52
+ openedByDefault: boolean;
53
+ };
54
+
55
+ export type SubtreeNode = SubtreeNodeState;
56
+
57
+ export const collectExpandableStepNodes = (
58
+ bodyItems: TrBodyItem[],
59
+ policy: StepTreeExpansion,
60
+ ): ExpandableStepNode[] => {
61
+ const nodes: ExpandableStepNode[] = [];
62
+
63
+ bodyItems.forEach((bodyItem) => {
64
+ if (bodyItem.type === "step") {
65
+ if (hasStepContent(bodyItem)) {
66
+ nodes.push({
67
+ id: bodyItem.item.stepId,
68
+ openedByDefault: isStepOpenedByDefault(policy, bodyItem.item.status, bodyItem.bodyItems),
69
+ });
70
+ }
71
+ nodes.push(...collectExpandableStepNodes(bodyItem.bodyItems, policy));
72
+ return;
73
+ }
74
+
75
+ if (bodyItem.type === "error" && hasTestLevelErrorContent(bodyItem.error)) {
76
+ nodes.push({
77
+ id: bodyItem.id,
78
+ openedByDefault: isOpenByDefaultForPolicy(policy, bodyItem.status === "failed" || bodyItem.status === "broken"),
79
+ });
80
+ }
81
+ });
82
+
83
+ return nodes;
84
+ };
85
+
86
+ export { getNextSubtreeToggleState, getSubtreeToggleIcon, isSubtreeFirstLevelOnlyOpened, type SubtreeToggleState };
87
+
88
+ export const getStepTreeExpansionPolicy = (): StepTreeExpansion =>
89
+ getReportOptions<AwesomeReportOptions>()?.stepTreeExpansion ?? DEFAULT_STEP_TREE_EXPANSION_POLICY;
90
+
91
+ export const isOpenByDefaultForPolicy = (policy: StepTreeExpansion, hasFailedContext: boolean): boolean => {
92
+ if (policy === "expanded") {
93
+ return true;
94
+ }
95
+
96
+ if (policy === "collapsed") {
97
+ return false;
98
+ }
99
+
100
+ return hasFailedContext;
101
+ };
@@ -54,6 +54,18 @@
54
54
  }
55
55
  }
56
56
 
57
+ .test-result-step-subtree-toggle {
58
+ flex: 0 0 auto;
59
+ opacity: 0;
60
+ pointer-events: none;
61
+ transition: opacity 200ms;
62
+ }
63
+
64
+ .test-result-step-header:hover .test-result-step-subtree-toggle {
65
+ opacity: 1;
66
+ pointer-events: auto;
67
+ }
68
+
57
69
  .test-result-header-text {
58
70
  padding-left: 4px;
59
71
  word-break: break-word;
@@ -3,21 +3,14 @@ import type { FunctionalComponent } from "preact";
3
3
  import { useState } from "preact/hooks";
4
4
  import type { AwesomeTestResult } from "types";
5
5
 
6
+ import { fixtureResultToTrStepItem } from "@/components/TestResult/bodyItems";
6
7
  import { TrDropdown } from "@/components/TestResult/TrDropdown";
7
- import { TrAttachment } from "@/components/TestResult/TrSteps/TrAttachment";
8
8
  import { TrStep } from "@/components/TestResult/TrSteps/TrStep";
9
9
  import { useI18n } from "@/stores/locale";
10
10
  import { collapsedTrees, toggleTree } from "@/stores/tree";
11
11
 
12
12
  import * as styles from "@/components/TestResult/TrSteps/styles.scss";
13
13
 
14
- const typeMap = {
15
- before: TrStep,
16
- after: TrStep,
17
- step: TrStep,
18
- attachment: TrAttachment,
19
- };
20
-
21
14
  export type TrTeardownProps = {
22
15
  teardown: AwesomeTestResult["teardown"];
23
16
  id: string;
@@ -46,14 +39,11 @@ export const TrTeardown: FunctionalComponent<TrTeardownProps> = ({ teardown, id
46
39
  />
47
40
  {isOpened && (
48
41
  <div className={styles["test-result-steps-root"]}>
49
- {teardown?.map((item, key) => {
50
- const StepComponent = typeMap[item.type];
51
- return StepComponent ? (
52
- // FIXME: use proper type in the StepComponent component
53
- // @ts-ignore
54
- <StepComponent item={item} stepIndex={key + 1} key={key} className={styles["test-result-step-root"]} />
55
- ) : null;
56
- })}
42
+ {teardown?.map((fixture, key) => (
43
+ <div className={styles["test-result-step-root"]} key={fixture.id}>
44
+ <TrStep item={fixtureResultToTrStepItem(fixture)} stepIndex={key + 1} />
45
+ </div>
46
+ ))}
57
47
  </div>
58
48
  )}
59
49
  </div>
@@ -1,5 +1,5 @@
1
1
  import type { AttachmentTestStepResult, DefaultTestStepResult, TestError, TestStatus } from "@allurereport/core-api";
2
- import type { AwesomeTestResult } from "types";
2
+ import type { AwesomeFixtureResult, AwesomeTestResult } from "types";
3
3
 
4
4
  export type TestLevelErrorItem = {
5
5
  type: "error";
@@ -128,6 +128,31 @@ const buildStepBodyItems = (
128
128
  return { bodyItems, didPlaceSyntheticError };
129
129
  };
130
130
 
131
+ export const getStepBodyItems = (steps: AwesomeTestResult["steps"]): TrBodyItem[] =>
132
+ buildStepBodyItems(steps, undefined).bodyItems;
133
+
134
+ export const fixtureResultToTrStepItem = (fixture: AwesomeFixtureResult): TrStepItem => {
135
+ const err = fixture.error;
136
+
137
+ return {
138
+ type: "step",
139
+ item: {
140
+ type: "step",
141
+ name: fixture.name,
142
+ status: fixture.status,
143
+ parameters: [],
144
+ steps: fixture.steps,
145
+ stepId: fixture.id,
146
+ duration: fixture.duration,
147
+ message: err?.message,
148
+ trace: err?.trace,
149
+ error: err,
150
+ },
151
+ bodyItems: getStepBodyItems(fixture.steps),
152
+ suppressInlineError: false,
153
+ };
154
+ };
155
+
131
156
  export const getBodyItems = (
132
157
  testResult?: Pick<AwesomeTestResult, "id" | "status" | "steps" | "error">,
133
158
  fallbackTitle = "Error",
@@ -72,15 +72,20 @@ export const TreeList = () => {
72
72
 
73
73
  // render single tree for single environment
74
74
  if (environmentsStore.value.data.length === 1) {
75
+ const soleId = environmentsStore.value.data[0]!.id;
76
+ const soleStatistic = currentEnvironment.value
77
+ ? statsByEnvStore.value.data[currentEnvironment.value]
78
+ : statsByEnvStore.value.data[soleId];
79
+
75
80
  return (
76
81
  <div>
77
82
  <Tree
78
83
  reportStatistic={reportStatsStore.value.data}
79
- statistic={statsByEnvStore.value.data[currentEnvironment.value]}
84
+ statistic={soleStatistic}
80
85
  collapsedTrees={collapsedTrees.value}
81
86
  toggleTree={toggleTree}
82
87
  navigateTo={treeNavigateTo}
83
- tree={treeLocalizer(filteredTree.value.default)}
88
+ tree={treeLocalizer(filteredTree.value[soleId])}
84
89
  statusFilter={currentTreeStatus}
85
90
  routeId={trId}
86
91
  root
@@ -201,8 +201,8 @@
201
201
  gap: 8px;
202
202
 
203
203
  > :first-child {
204
- flex: 1 1 auto;
205
204
  min-width: 0;
205
+ width: max-content;
206
206
  }
207
207
 
208
208
  > :last-child {
package/src/index.tsx CHANGED
@@ -68,7 +68,7 @@ const App = () => {
68
68
  await waitForI18next;
69
69
  await Promise.all(fns.map((fn) => fn(currentEnvironment.value)));
70
70
 
71
- const environmentIds = environmentsStore.value.data.map(({ id }) => id);
71
+ const environmentIds = environmentsStore.value.data.map(({ id }) => id).filter((id): id is string => Boolean(id));
72
72
 
73
73
  if (currentEnvironment.value) {
74
74
  await fetchEnvTreesData([currentEnvironment.value]);
@@ -83,7 +83,18 @@ const App = () => {
83
83
 
84
84
  useEffect(() => {
85
85
  prefetchData();
86
- }, [currentEnvironment.value]);
86
+ }, []);
87
+
88
+ useSignalEffect(() => {
89
+ const envId = currentEnvironment.value;
90
+
91
+ if (!prefetched || !envId) {
92
+ return;
93
+ }
94
+
95
+ fetchEnvTreesData([envId]);
96
+ fetchEnvStats([envId]);
97
+ });
87
98
 
88
99
  useSignalEffect(() => {
89
100
  const testResultId = currentTrId.value;
package/src/stores/env.ts CHANGED
@@ -1,8 +1,10 @@
1
1
  import { type EnvironmentIdentity, type TestEnvGroup } from "@allurereport/core-api";
2
2
  import {
3
3
  environmentNameById as resolveEnvironmentNameById,
4
+ errorMessageFromUnknown,
4
5
  fetchReportJsonData,
5
6
  migrateStoredEnvironmentSelection,
7
+ normalizeEnvironmentsWidget,
6
8
  } from "@allurereport/web-commons";
7
9
  import { effect, signal } from "@preact/signals";
8
10
 
@@ -40,7 +42,8 @@ export const fetchEnvironments = async () => {
40
42
  };
41
43
 
42
44
  try {
43
- const res = await fetchReportJsonData<EnvironmentIdentity[]>("widgets/environments.json", { bustCache: true });
45
+ const raw = await fetchReportJsonData<unknown>("widgets/environments.json", { bustCache: true });
46
+ const res = normalizeEnvironmentsWidget(raw);
44
47
 
45
48
  environmentsStore.value = {
46
49
  data: res,
@@ -52,7 +55,7 @@ export const fetchEnvironments = async () => {
52
55
  } catch (e) {
53
56
  environmentsStore.value = {
54
57
  ...environmentsStore.peek(),
55
- error: e.message,
58
+ error: errorMessageFromUnknown(e),
56
59
  loading: false,
57
60
  };
58
61
  }
@@ -83,7 +86,7 @@ export const fetchTestEnvGroup = async (id: string) => {
83
86
  } catch (e) {
84
87
  testEnvGroupsStore.value = {
85
88
  ...testEnvGroupsStore.peek(),
86
- error: e.message,
89
+ error: errorMessageFromUnknown(e),
87
90
  loading: false,
88
91
  };
89
92
  }
@@ -1,5 +1,5 @@
1
1
  import type { EnvironmentItem } from "@allurereport/core-api";
2
- import { fetchReportJsonData } from "@allurereport/web-commons";
2
+ import { errorMessageFromUnknown, fetchReportJsonData } from "@allurereport/web-commons";
3
3
  import { signal } from "@preact/signals";
4
4
 
5
5
  import type { StoreSignalState } from "@/stores/types";
@@ -28,7 +28,7 @@ export const fetchEnvInfo = async () => {
28
28
  } catch (e) {
29
29
  envInfoStore.value = {
30
30
  ...envInfoStore.peek(),
31
- error: e.message,
31
+ error: errorMessageFromUnknown(e),
32
32
  loading: false,
33
33
  };
34
34
  }
@@ -25,7 +25,9 @@ const onInit = () => {
25
25
 
26
26
  onInit();
27
27
 
28
- export const currentSection = computed(() => sectionRoute.value.params.section ?? "default");
28
+ export const currentSection = computed(() =>
29
+ sectionRoute.value.matches ? (sectionRoute.value.params.section ?? "default") : "default",
30
+ );
29
31
 
30
32
  effect(() => {
31
33
  const section = currentSection.value;
@@ -1,5 +1,5 @@
1
1
  import type { Statistic } from "@allurereport/core-api";
2
- import { fetchReportJsonData } from "@allurereport/web-commons";
2
+ import { errorMessageFromUnknown, fetchReportJsonData } from "@allurereport/web-commons";
3
3
  import { signal } from "@preact/signals";
4
4
 
5
5
  import type { StoreSignalState } from "@/stores/types";
@@ -38,7 +38,7 @@ export const fetchReportStats = async () => {
38
38
  } catch (err) {
39
39
  reportStatsStore.value = {
40
40
  data: { total: 0 },
41
- error: err.message,
41
+ error: errorMessageFromUnknown(err),
42
42
  loading: false,
43
43
  };
44
44
  }
@@ -80,7 +80,7 @@ export const fetchEnvStats = async (envs: string[]) => {
80
80
  } catch (err) {
81
81
  statsByEnvStore.value = {
82
82
  ...statsByEnvStore.peek(),
83
- error: err.message,
83
+ error: errorMessageFromUnknown(err),
84
84
  loading: false,
85
85
  };
86
86
  }
@@ -1,4 +1,4 @@
1
- import { buildFilterPredicate, fetchReportJsonData } from "@allurereport/web-commons";
1
+ import { buildFilterPredicate, errorMessageFromUnknown, fetchReportJsonData } from "@allurereport/web-commons";
2
2
  import type { RecursiveTree } from "@allurereport/web-components/global";
3
3
  import { computed, effect, signal } from "@preact/signals";
4
4
  import type { AwesomeTree, AwesomeTreeGroup } from "types";
@@ -23,19 +23,51 @@ export const noTests = computed(() => {
23
23
  });
24
24
 
25
25
  export const collapsedTrees = signal(new Set(loadFromLocalStorage<string[]>("collapsedTrees", [])));
26
+ export const expandedTrees = signal(new Set(loadFromLocalStorage<string[]>("expandedTrees", [])));
26
27
 
27
28
  effect(() => {
28
29
  localStorage.setItem("collapsedTrees", JSON.stringify([...collapsedTrees.value]));
29
30
  });
30
31
 
31
- export const toggleTree = (id: string) => {
32
- const newSet = new Set(collapsedTrees.value);
33
- if (newSet.has(id)) {
34
- newSet.delete(id);
32
+ effect(() => {
33
+ localStorage.setItem("expandedTrees", JSON.stringify([...expandedTrees.value]));
34
+ });
35
+
36
+ export const isTreeOpened = (id: string, openedByDefault = true): boolean => {
37
+ if (openedByDefault) {
38
+ return !collapsedTrees.value.has(id);
39
+ }
40
+
41
+ return expandedTrees.value.has(id);
42
+ };
43
+
44
+ const setTreeStoredState = (id: string, shouldBeOpened: boolean, openedByDefault: boolean) => {
45
+ if (openedByDefault) {
46
+ const nextCollapsedTrees = new Set(collapsedTrees.value);
47
+ if (shouldBeOpened) {
48
+ nextCollapsedTrees.delete(id);
49
+ } else {
50
+ nextCollapsedTrees.add(id);
51
+ }
52
+ collapsedTrees.value = nextCollapsedTrees;
53
+ return;
54
+ }
55
+
56
+ const nextExpandedTrees = new Set(expandedTrees.value);
57
+ if (shouldBeOpened) {
58
+ nextExpandedTrees.add(id);
35
59
  } else {
36
- newSet.add(id);
60
+ nextExpandedTrees.delete(id);
37
61
  }
38
- collapsedTrees.value = newSet;
62
+ expandedTrees.value = nextExpandedTrees;
63
+ };
64
+
65
+ export const toggleTree = (id: string, openedByDefault = true) => {
66
+ setTreeStoredState(id, !isTreeOpened(id, openedByDefault), openedByDefault);
67
+ };
68
+
69
+ export const setTreeOpened = (id: string, shouldBeOpened: boolean, openedByDefault = true) => {
70
+ setTreeStoredState(id, shouldBeOpened, openedByDefault);
39
71
  };
40
72
 
41
73
  export const fetchEnvTreesData = async (envs: string[]) => {
@@ -74,7 +106,7 @@ export const fetchEnvTreesData = async (envs: string[]) => {
74
106
  } catch (e) {
75
107
  treeStore.value = {
76
108
  ...treeStore.peek(),
77
- error: e.message,
109
+ error: errorMessageFromUnknown(e),
78
110
  loading: false,
79
111
  };
80
112
  }
@@ -1,8 +1,13 @@
1
1
  import type { AttachmentTestStepResult, DefaultTestStepResult } from "@allurereport/core-api";
2
- import type { AwesomeTestResult } from "types";
2
+ import type { AwesomeFixtureResult, AwesomeTestResult } from "types";
3
3
  import { describe, expect, it } from "vitest";
4
4
 
5
- import { getBodyItems, getTestLevelErrorId } from "@/components/TestResult/bodyItems";
5
+ import {
6
+ fixtureResultToTrStepItem,
7
+ getBodyItems,
8
+ getStepBodyItems,
9
+ getTestLevelErrorId,
10
+ } from "@/components/TestResult/bodyItems";
6
11
 
7
12
  const sampleStep: DefaultTestStepResult = {
8
13
  type: "step",
@@ -191,4 +196,22 @@ describe("components > TestResult > bodyItems", () => {
191
196
  },
192
197
  ]);
193
198
  });
199
+
200
+ it("fixtureResultToTrStepItem builds TrStepItem from setup or teardown fixture", () => {
201
+ const fixture: AwesomeFixtureResult = {
202
+ id: "fixture-before-1",
203
+ type: "before",
204
+ name: "before suite",
205
+ status: "passed",
206
+ steps: [sampleStep],
207
+ };
208
+
209
+ const wrapped = fixtureResultToTrStepItem(fixture);
210
+
211
+ expect(wrapped.type).toBe("step");
212
+ expect(wrapped.item.name).toBe("before suite");
213
+ expect(wrapped.item.stepId).toBe("fixture-before-1");
214
+ expect(wrapped.item.type).toBe("step");
215
+ expect(wrapped.bodyItems).toEqual(getStepBodyItems([sampleStep]));
216
+ });
194
217
  });
@@ -0,0 +1,179 @@
1
+ import { describe, expect, it } from "vitest";
2
+
3
+ import type { TrBodyItem } from "@/components/TestResult/bodyItems";
4
+ import {
5
+ collectExpandableStepNodes,
6
+ getNextSubtreeToggleState,
7
+ hasFailedStepContext,
8
+ isOpenByDefaultForPolicy,
9
+ isStepOpenedByDefault,
10
+ } from "@/components/TestResult/TrSteps/stepTreeExpansion";
11
+
12
+ describe("components > TestResult > stepTreeExpansion", () => {
13
+ it("should detect failed context in nested steps", () => {
14
+ const bodyItems = [
15
+ {
16
+ type: "step",
17
+ item: {
18
+ name: "top-level step",
19
+ status: "passed",
20
+ },
21
+ suppressInlineError: false,
22
+ bodyItems: [
23
+ {
24
+ type: "step",
25
+ item: {
26
+ name: "failed nested step",
27
+ status: "failed",
28
+ },
29
+ suppressInlineError: false,
30
+ bodyItems: [],
31
+ },
32
+ ],
33
+ },
34
+ ] as TrBodyItem[];
35
+
36
+ expect(hasFailedStepContext(bodyItems)).toBe(true);
37
+ });
38
+
39
+ it("should not detect failed context for only passed steps and attachments", () => {
40
+ const bodyItems = [
41
+ {
42
+ type: "step",
43
+ item: {
44
+ name: "top-level step",
45
+ status: "passed",
46
+ },
47
+ suppressInlineError: false,
48
+ bodyItems: [],
49
+ },
50
+ {
51
+ type: "attachment",
52
+ link: {
53
+ id: "attachment-1",
54
+ source: "attachment-1.txt",
55
+ name: "attachment",
56
+ type: "text/plain",
57
+ },
58
+ },
59
+ ] as TrBodyItem[];
60
+
61
+ expect(hasFailedStepContext(bodyItems)).toBe(false);
62
+ });
63
+
64
+ it("should resolve default expansion state according to policy", () => {
65
+ expect(isOpenByDefaultForPolicy("expanded", false)).toBe(true);
66
+ expect(isOpenByDefaultForPolicy("collapsed", true)).toBe(false);
67
+ expect(isOpenByDefaultForPolicy("expand_failed_only", true)).toBe(true);
68
+ expect(isOpenByDefaultForPolicy("expand_failed_only", false)).toBe(false);
69
+ });
70
+
71
+ it("should resolve step default opening according to status and nested failures", () => {
72
+ const failedBodyItems = [
73
+ {
74
+ type: "step",
75
+ item: {
76
+ name: "nested failed step",
77
+ status: "failed",
78
+ },
79
+ suppressInlineError: false,
80
+ bodyItems: [],
81
+ },
82
+ ] as TrBodyItem[];
83
+
84
+ expect(isStepOpenedByDefault("expand_failed_only", "passed", failedBodyItems)).toBe(true);
85
+ expect(isStepOpenedByDefault("expand_failed_only", "passed", [])).toBe(false);
86
+ });
87
+
88
+ it("should collect expandable step and error nodes with correct defaults", () => {
89
+ const bodyItems = [
90
+ {
91
+ type: "step",
92
+ item: {
93
+ stepId: "parent-step",
94
+ name: "parent step",
95
+ status: "passed",
96
+ parameters: [],
97
+ message: "",
98
+ trace: "",
99
+ hasSimilarErrorInSubSteps: false,
100
+ },
101
+ suppressInlineError: false,
102
+ bodyItems: [
103
+ {
104
+ type: "step",
105
+ item: {
106
+ stepId: "failed-child-step",
107
+ name: "failed child step",
108
+ status: "failed",
109
+ parameters: [],
110
+ message: "child failed",
111
+ trace: "trace",
112
+ hasSimilarErrorInSubSteps: false,
113
+ },
114
+ suppressInlineError: false,
115
+ bodyItems: [],
116
+ },
117
+ {
118
+ type: "error",
119
+ id: "test-error",
120
+ title: "Error",
121
+ status: "failed",
122
+ error: {
123
+ message: "failure",
124
+ trace: "trace",
125
+ },
126
+ },
127
+ ],
128
+ },
129
+ ] as TrBodyItem[];
130
+
131
+ expect(collectExpandableStepNodes(bodyItems, "expand_failed_only")).toEqual([
132
+ { id: "parent-step", openedByDefault: true },
133
+ { id: "failed-child-step", openedByDefault: true },
134
+ { id: "test-error", openedByDefault: true },
135
+ ]);
136
+ });
137
+
138
+ it("should calculate next subtree toggle state like categories", () => {
139
+ expect(
140
+ getNextSubtreeToggleState({
141
+ hasOnlyLeafResults: false,
142
+ isSubtreeCollapsedAll: true,
143
+ isSubtreeFirstLevelOnly: false,
144
+ isSubtreeExpandedAll: false,
145
+ lastSubtreeToggle: null,
146
+ }),
147
+ ).toBe("first");
148
+
149
+ expect(
150
+ getNextSubtreeToggleState({
151
+ hasOnlyLeafResults: false,
152
+ isSubtreeCollapsedAll: false,
153
+ isSubtreeFirstLevelOnly: true,
154
+ isSubtreeExpandedAll: false,
155
+ lastSubtreeToggle: null,
156
+ }),
157
+ ).toBe("all");
158
+
159
+ expect(
160
+ getNextSubtreeToggleState({
161
+ hasOnlyLeafResults: false,
162
+ isSubtreeCollapsedAll: false,
163
+ isSubtreeFirstLevelOnly: true,
164
+ isSubtreeExpandedAll: false,
165
+ lastSubtreeToggle: "all",
166
+ }),
167
+ ).toBe("none");
168
+
169
+ expect(
170
+ getNextSubtreeToggleState({
171
+ hasOnlyLeafResults: true,
172
+ isSubtreeCollapsedAll: false,
173
+ isSubtreeFirstLevelOnly: false,
174
+ isSubtreeExpandedAll: true,
175
+ lastSubtreeToggle: "all",
176
+ }),
177
+ ).toBe("none");
178
+ });
179
+ });
package/types.d.ts CHANGED
@@ -12,6 +12,7 @@ import type {
12
12
  } from "@allurereport/core-api";
13
13
 
14
14
  export type Layout = "base" | "split";
15
+ export type StepTreeExpansion = "collapsed" | "expand_failed_only" | "expanded";
15
16
 
16
17
  export type AwesomeReportOptions = {
17
18
  allureVersion: string;
@@ -27,6 +28,7 @@ export type AwesomeReportOptions = {
27
28
  sections?: string[];
28
29
  cacheKey: string;
29
30
  ci?: CiDescriptor;
31
+ stepTreeExpansion?: StepTreeExpansion;
30
32
  };
31
33
 
32
34
  export type AwesomeFixtureResult = Omit<