lhcb-ntuple-wizard-test 2.1.0 → 2.2.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 (178) hide show
  1. package/dist/components/AddTupleToolButton.d.ts +1 -0
  2. package/dist/components/AddTupleToolButton.js +16 -0
  3. package/dist/components/BookkeepingPathDropdown.d.ts +1 -1
  4. package/dist/components/BookkeepingPathDropdown.js +1 -4
  5. package/dist/components/DatasetEventTypeBadge.js +1 -1
  6. package/dist/components/DatasetInfo.js +3 -3
  7. package/dist/components/DecayCard.d.ts +1 -1
  8. package/dist/components/DecayCard.js +12 -40
  9. package/dist/components/DecayLatex.d.ts +1 -1
  10. package/dist/components/DecayLatex.js +4 -87
  11. package/dist/components/DecayListItem.js +2 -2
  12. package/dist/components/DecayTagBadge.d.ts +1 -1
  13. package/dist/components/DecayTagBadge.js +0 -3
  14. package/dist/components/DecayTreeCard.d.ts +1 -7
  15. package/dist/components/DecayTreeCard.js +13 -18
  16. package/dist/components/DecayTreeCardHeader.d.ts +2 -3
  17. package/dist/components/DecayTreeCardHeader.js +2 -2
  18. package/dist/components/DecayTreeGraph.d.ts +1 -4
  19. package/dist/components/DecayTreeGraph.js +66 -41
  20. package/dist/components/DeleteButton.d.ts +2 -1
  21. package/dist/components/DeleteButton.js +2 -2
  22. package/dist/components/DttNameInput.d.ts +9 -0
  23. package/dist/components/DttNameInput.js +43 -0
  24. package/dist/components/GuideLinkButton.d.ts +15 -0
  25. package/dist/components/GuideLinkButton.js +16 -0
  26. package/dist/components/NtupleWizard.js +1 -7
  27. package/dist/components/ParticleDropdown.d.ts +11 -1
  28. package/dist/components/ParticleDropdown.js +3 -6
  29. package/dist/components/ParticleLatex.d.ts +6 -0
  30. package/dist/components/ParticleLatex.js +13 -0
  31. package/dist/components/ParticleTagBadge.d.ts +2 -1
  32. package/dist/components/ParticleTagBadge.js +5 -7
  33. package/dist/components/ParticleTagFilters.d.ts +1 -6
  34. package/dist/components/ParticleTagFilters.js +16 -17
  35. package/dist/components/RequestButtonGroup.d.ts +1 -1
  36. package/dist/components/RequestButtonGroup.js +0 -3
  37. package/dist/components/RequestRow.d.ts +1 -1
  38. package/dist/components/RequestRow.js +4 -9
  39. package/dist/components/StrippingLineDropdown.js +14 -16
  40. package/dist/components/StrippingLineInfo.d.ts +5 -5
  41. package/dist/components/StrippingLineInfo.js +14 -4
  42. package/dist/components/StrippingLineInfoButton.d.ts +6 -0
  43. package/dist/components/StrippingLineInfoButton.js +7 -0
  44. package/dist/components/StrippingLineVersionBadge.d.ts +7 -0
  45. package/dist/components/{StrippingLineBadge.js → StrippingLineVersionBadge.js} +5 -5
  46. package/dist/components/StrippingLinesCountBadge.d.ts +8 -0
  47. package/dist/components/StrippingLinesCountBadge.js +11 -0
  48. package/dist/components/TagDropdown.d.ts +3 -3
  49. package/dist/components/TagDropdown.js +2 -2
  50. package/dist/components/TupleToolClassDropdown.js +11 -14
  51. package/dist/components/TupleToolDocsAccordion.d.ts +1 -1
  52. package/dist/components/TupleToolDocsAccordion.js +4 -8
  53. package/dist/components/TupleToolGroupsAccordion.d.ts +5 -0
  54. package/dist/components/TupleToolGroupsAccordion.js +31 -0
  55. package/dist/components/TupleToolLabel.d.ts +1 -1
  56. package/dist/components/TupleToolLabel.js +2 -5
  57. package/dist/components/TupleToolsAccordion.d.ts +1 -0
  58. package/dist/components/TupleToolsAccordion.js +22 -0
  59. package/dist/components/modals/AddTupleToolModal.js +3 -2
  60. package/dist/components/modals/ConfigureTupleToolModal.js +1 -1
  61. package/dist/components/modals/UploadDttConfigModal.d.ts +1 -1
  62. package/dist/components/modals/UploadDttConfigModal.js +1 -4
  63. package/dist/config.d.ts +1 -0
  64. package/dist/config.js +3 -2
  65. package/dist/models/bkPath.js +1 -1
  66. package/dist/models/dtt.d.ts +5 -2
  67. package/dist/models/dtt.js +37 -18
  68. package/dist/models/rowData.d.ts +1 -1
  69. package/dist/models/yamlFile.js +1 -1
  70. package/dist/pages/DecaySearchPage.js +4 -9
  71. package/dist/pages/DecayTreeConfigPage.js +4 -38
  72. package/dist/pages/RequestPage.js +2 -4
  73. package/dist/providers/DttProvider.d.ts +3 -3
  74. package/dist/providers/DttProvider.js +30 -55
  75. package/dist/providers/MetadataProvider.d.ts +1 -1
  76. package/dist/providers/MetadataProvider.js +11 -5
  77. package/dist/providers/RequestProvider.js +2 -2
  78. package/dist/providers/RowProvider.d.ts +2 -2
  79. package/dist/providers/RowProvider.js +10 -9
  80. package/dist/providers/RowsProvider.js +0 -3
  81. package/dist/tests/components/BookkeepingPathDropdown.test.d.ts +1 -0
  82. package/dist/tests/components/BookkeepingPathDropdown.test.js +118 -0
  83. package/dist/tests/components/DatasetInfo.test.d.ts +1 -0
  84. package/dist/tests/components/DatasetInfo.test.js +38 -0
  85. package/dist/tests/components/DecayCard.test.d.ts +1 -0
  86. package/dist/tests/components/DecayCard.test.js +115 -0
  87. package/dist/tests/components/DecayLatex.test.d.ts +1 -0
  88. package/dist/tests/components/DecayLatex.test.js +31 -0
  89. package/dist/tests/components/DecayList.test.d.ts +1 -0
  90. package/dist/tests/components/DecayList.test.js +76 -0
  91. package/dist/tests/components/DecayListItem.test.d.ts +1 -0
  92. package/dist/tests/components/DecayListItem.test.js +51 -0
  93. package/dist/tests/components/DecayTreeCard.test.d.ts +1 -0
  94. package/dist/tests/components/DecayTreeCard.test.js +119 -0
  95. package/dist/tests/components/DecayTreeGraph.test.d.ts +1 -0
  96. package/dist/tests/components/DecayTreeGraph.test.js +125 -0
  97. package/dist/tests/components/DeleteButton.test.d.ts +1 -0
  98. package/dist/tests/components/DeleteButton.test.js +45 -0
  99. package/dist/tests/components/DttNameInput.test.d.ts +1 -0
  100. package/dist/tests/components/DttNameInput.test.js +75 -0
  101. package/dist/tests/components/NtupleWizard.test.d.ts +1 -0
  102. package/dist/tests/components/NtupleWizard.test.js +57 -0
  103. package/dist/tests/components/ParticleDropdown.test.d.ts +1 -0
  104. package/dist/tests/components/ParticleDropdown.test.js +23 -0
  105. package/dist/tests/components/ParticleTagFilters.test.d.ts +1 -0
  106. package/dist/tests/components/ParticleTagFilters.test.js +87 -0
  107. package/dist/tests/components/RequestButtonGroup.test.d.ts +1 -0
  108. package/dist/tests/components/RequestButtonGroup.test.js +132 -0
  109. package/dist/tests/components/RequestRow.test.d.ts +1 -0
  110. package/dist/tests/components/RequestRow.test.js +58 -0
  111. package/dist/tests/components/StrippingLineDropdown.test.d.ts +1 -0
  112. package/dist/tests/components/StrippingLineDropdown.test.js +88 -0
  113. package/dist/tests/components/badges.test.d.ts +1 -0
  114. package/dist/tests/components/badges.test.js +120 -0
  115. package/dist/tests/components/dropdowns.test.d.ts +1 -0
  116. package/dist/tests/components/dropdowns.test.js +110 -0
  117. package/dist/tests/components/dttComponents.test.d.ts +1 -0
  118. package/dist/tests/components/dttComponents.test.js +287 -0
  119. package/dist/tests/components/formInputs.test.d.ts +1 -0
  120. package/dist/tests/components/formInputs.test.js +96 -0
  121. package/dist/tests/components/metadataComponents.test.d.ts +1 -0
  122. package/dist/tests/components/metadataComponents.test.js +137 -0
  123. package/dist/tests/components/miscComponents.test.d.ts +1 -0
  124. package/dist/tests/components/miscComponents.test.js +134 -0
  125. package/dist/tests/components/modals.test.d.ts +1 -0
  126. package/dist/tests/components/modals.test.js +554 -0
  127. package/dist/tests/components/tupleToolParams.test.d.ts +1 -0
  128. package/dist/tests/components/tupleToolParams.test.js +213 -0
  129. package/dist/tests/config.test.d.ts +1 -0
  130. package/dist/tests/config.test.js +31 -0
  131. package/dist/tests/mockSetup.d.ts +1 -0
  132. package/dist/tests/mockSetup.js +30 -0
  133. package/dist/tests/models/BkPath.test.d.ts +1 -0
  134. package/dist/tests/models/BkPath.test.js +87 -0
  135. package/dist/tests/models/Dtt.test.d.ts +1 -0
  136. package/dist/tests/models/Dtt.test.js +376 -0
  137. package/dist/tests/models/TupleTool.test.d.ts +1 -0
  138. package/dist/tests/models/TupleTool.test.js +80 -0
  139. package/dist/tests/models/YamlFile.test.d.ts +1 -0
  140. package/dist/tests/models/YamlFile.test.js +123 -0
  141. package/dist/tests/pages/DecaySearchPage.test.d.ts +1 -0
  142. package/dist/tests/pages/DecaySearchPage.test.js +228 -0
  143. package/dist/tests/pages/DecayTreeConfigPage.test.d.ts +1 -0
  144. package/dist/tests/pages/DecayTreeConfigPage.test.js +105 -0
  145. package/dist/tests/pages/RequestPage.test.d.ts +1 -0
  146. package/dist/tests/pages/RequestPage.test.js +439 -0
  147. package/dist/tests/providers/DttProvider.test.d.ts +1 -0
  148. package/dist/tests/providers/DttProvider.test.js +105 -0
  149. package/dist/tests/providers/MetadataProvider.test.d.ts +1 -0
  150. package/dist/tests/providers/MetadataProvider.test.js +129 -0
  151. package/dist/tests/providers/RequestProvider.test.d.ts +1 -0
  152. package/dist/tests/providers/RequestProvider.test.js +306 -0
  153. package/dist/tests/providers/RowProvider.test.d.ts +1 -0
  154. package/dist/tests/providers/RowProvider.test.js +110 -0
  155. package/dist/tests/providers/RowsProvider.test.d.ts +1 -0
  156. package/dist/tests/providers/RowsProvider.test.js +84 -0
  157. package/dist/tests/providers/WizardConfigProvider.test.d.ts +1 -0
  158. package/dist/tests/providers/WizardConfigProvider.test.js +36 -0
  159. package/dist/tests/setupTests.d.ts +1 -0
  160. package/dist/tests/setupTests.js +15 -0
  161. package/dist/tests/testUtils.d.ts +33 -0
  162. package/dist/tests/testUtils.js +196 -0
  163. package/dist/tests/utils/latexUtils.test.d.ts +1 -0
  164. package/dist/tests/utils/latexUtils.test.js +62 -0
  165. package/dist/tests/utils/utils.test.d.ts +1 -0
  166. package/dist/tests/utils/utils.test.js +394 -0
  167. package/dist/utils/latexUtils.d.ts +13 -0
  168. package/dist/utils/latexUtils.js +86 -0
  169. package/dist/utils/utils.d.ts +1 -0
  170. package/dist/utils/utils.js +4 -1
  171. package/package.json +19 -9
  172. package/dist/components/NumStrippingLinesBadge.d.ts +0 -8
  173. package/dist/components/NumStrippingLinesBadge.js +0 -10
  174. package/dist/components/StrippingLineBadge.d.ts +0 -7
  175. package/dist/components/TupleToolGroup.d.ts +0 -5
  176. package/dist/components/TupleToolGroup.js +0 -22
  177. package/dist/components/TupleToolList.d.ts +0 -6
  178. package/dist/components/TupleToolList.js +0 -42
@@ -0,0 +1,439 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { beforeEach, describe, expect, it, vi } from "vitest";
3
+ import { fireEvent, render, screen, waitFor } from "@testing-library/react";
4
+ import { RequestPage } from "../../pages/RequestPage";
5
+ import { MemoryRouter } from "react-router-dom";
6
+ import { createMockDtt, createMockRow, mockStrippingLine } from "../testUtils";
7
+ import { processProductionFiles } from "../../utils/utils";
8
+ // ---------------------------------------------------------------------------
9
+ // Provider mocks
10
+ // ---------------------------------------------------------------------------
11
+ let mockRows = [];
12
+ const mockSetRows = vi.fn();
13
+ const mockGenerateAllFiles = vi.fn(() => []);
14
+ vi.mock("../../providers/RowsProvider", () => ({
15
+ useRows: () => ({
16
+ rows: mockRows,
17
+ configuredRows: mockRows.filter((r) => r.dtt),
18
+ setRows: mockSetRows,
19
+ updateRow: vi.fn(),
20
+ removeRow: vi.fn(),
21
+ generateAllFiles: mockGenerateAllFiles,
22
+ }),
23
+ }));
24
+ let mockValidation = {
25
+ isEmptySession: true,
26
+ isEmailValid: false,
27
+ allRowsHaveDtt: true,
28
+ allRowsHaveStrippingLine: true,
29
+ allRowsHavePaths: true,
30
+ validDttNames: true,
31
+ };
32
+ let mockShowErrors = false;
33
+ const mockTrySubmit = vi.fn(() => false);
34
+ const mockClearAll = vi.fn();
35
+ const mockSetProductionName = vi.fn();
36
+ vi.mock("../../providers/RequestProvider", () => ({
37
+ useRequest: () => ({
38
+ emailIsKnown: false,
39
+ productionName: "MyProd",
40
+ setProductionName: mockSetProductionName,
41
+ validation: mockValidation,
42
+ showErrors: mockShowErrors,
43
+ clearAll: mockClearAll,
44
+ trySubmit: mockTrySubmit,
45
+ contactEmails: [],
46
+ setContactEmails: vi.fn(),
47
+ reasonForRequest: "",
48
+ setReasonForRequest: vi.fn(),
49
+ }),
50
+ }));
51
+ let mockVariant = "standalone";
52
+ vi.mock("../../providers/WizardConfigProvider", () => ({
53
+ useWizardConfig: () => ({
54
+ variant: mockVariant,
55
+ basePath: "",
56
+ requestSubmittedMessage: _jsx("span", { children: "Your request has been submitted." }),
57
+ onRequestSubmitted: vi.fn(),
58
+ }),
59
+ }));
60
+ // ---------------------------------------------------------------------------
61
+ // Child component mocks
62
+ // ---------------------------------------------------------------------------
63
+ vi.mock("../../providers/RowProvider", () => ({ RowProvider: ({ children }) => children }));
64
+ vi.mock("../../components/RequestRow", () => ({
65
+ RequestRow: () => _jsx("div", { "data-testid": "request-row" }),
66
+ }));
67
+ vi.mock("../../components/ProductionNameInput", () => ({
68
+ ProductionNameInput: () => _jsx("input", { "data-testid": "production-name-input", placeholder: "Production name" }),
69
+ }));
70
+ vi.mock("../../components/RequestEmailInput", () => ({
71
+ RequestEmailInput: () => _jsx("input", { "data-testid": "email-input", placeholder: "Email" }),
72
+ }));
73
+ vi.mock("../../components/RequestButtonGroup", () => ({
74
+ RequestButtonGroup: () => _jsx("div", { "data-testid": "request-button-group" }),
75
+ }));
76
+ vi.mock("../../components/DeleteButton", () => ({
77
+ DeleteButton: ({ action, disabled, children }) => (_jsx("button", { "data-testid": "clear-all-button", onClick: action, disabled: disabled, children: children })),
78
+ }));
79
+ vi.mock("../../components/modals/UploadDttConfigModal", () => ({
80
+ UploadDttConfigModal: ({ onClose }) => (_jsx("div", { "data-testid": "upload-modal", children: _jsx("button", { onClick: onClose, children: "Close upload modal" }) })),
81
+ }));
82
+ vi.mock("../../components/modals/ReasonForRequestModal", () => ({
83
+ ReasonForRequestModal: ({ onClose }) => (_jsxs("div", { "data-testid": "reason-modal", children: [_jsx("button", { "data-testid": "reason-cancel", onClick: () => onClose(false), children: "Cancel" }), _jsx("button", { "data-testid": "reason-submit", onClick: () => onClose(true), children: "Submit" })] })),
84
+ }));
85
+ vi.mock("../../components/ConfigFilesUploadingAlert", () => ({
86
+ ConfigFilesUploadingAlert: () => _jsx("div", { "data-testid": "uploading-alert" }),
87
+ }));
88
+ vi.mock("../../utils/utils", async (importOriginal) => {
89
+ const actual = await importOriginal();
90
+ return { ...actual, downloadZip: vi.fn(), processProductionFiles: vi.fn(() => ({ rows: [], emails: [] })) };
91
+ });
92
+ // ---------------------------------------------------------------------------
93
+ // Helpers
94
+ // ---------------------------------------------------------------------------
95
+ function renderPage() {
96
+ return render(_jsx(MemoryRouter, { children: _jsx(RequestPage, {}) }));
97
+ }
98
+ // Wait for the useEffect to set checkedIfCloneNeeded=true, exposing the main content
99
+ async function renderPageReady() {
100
+ const result = renderPage();
101
+ // The page renders a LoadingIndicator until its useEffect fires; wait for it to clear
102
+ await waitFor(() => expect(screen.queryByTestId("loading-indicator")).not.toBeInTheDocument());
103
+ return result;
104
+ }
105
+ // ---------------------------------------------------------------------------
106
+ // Tests — initial load
107
+ // ---------------------------------------------------------------------------
108
+ describe("RequestPage — initial render", () => {
109
+ beforeEach(() => {
110
+ mockRows = [];
111
+ mockValidation = { ...mockValidation, isEmptySession: true };
112
+ mockShowErrors = false;
113
+ });
114
+ it("removes the loading indicator after the clone check completes", async () => {
115
+ await renderPageReady();
116
+ expect(screen.queryByTestId("loading-indicator")).not.toBeInTheDocument();
117
+ });
118
+ it("renders the production name input", async () => {
119
+ await renderPageReady();
120
+ expect(screen.getByTestId("production-name-input")).toBeInTheDocument();
121
+ });
122
+ it("renders the email input when emailIsKnown is false", async () => {
123
+ await renderPageReady();
124
+ expect(screen.getByTestId("email-input")).toBeInTheDocument();
125
+ });
126
+ });
127
+ // ---------------------------------------------------------------------------
128
+ // Tests — standalone variant (Download button)
129
+ // ---------------------------------------------------------------------------
130
+ describe("RequestPage — standalone variant", () => {
131
+ beforeEach(() => {
132
+ mockRows = [];
133
+ mockVariant = "standalone";
134
+ mockShowErrors = false;
135
+ mockValidation = { ...mockValidation, isEmptySession: true };
136
+ });
137
+ it("renders a Download button", async () => {
138
+ await renderPageReady();
139
+ expect(screen.getByRole("button", { name: /download/i })).toBeInTheDocument();
140
+ });
141
+ it("does not render a Submit button", async () => {
142
+ await renderPageReady();
143
+ expect(screen.queryByRole("button", { name: /^submit$/i })).not.toBeInTheDocument();
144
+ });
145
+ });
146
+ // ---------------------------------------------------------------------------
147
+ // Tests — embedded variant (Submit button)
148
+ // ---------------------------------------------------------------------------
149
+ describe("RequestPage — embedded variant", () => {
150
+ beforeEach(() => {
151
+ mockRows = [];
152
+ mockVariant = "embedded";
153
+ mockShowErrors = false;
154
+ mockValidation = { ...mockValidation, isEmptySession: true };
155
+ });
156
+ it("renders a Submit button", async () => {
157
+ await renderPageReady();
158
+ expect(screen.getByRole("button", { name: /submit/i })).toBeInTheDocument();
159
+ });
160
+ it("does not render a Download button", async () => {
161
+ await renderPageReady();
162
+ expect(screen.queryByRole("button", { name: /download/i })).not.toBeInTheDocument();
163
+ });
164
+ });
165
+ // ---------------------------------------------------------------------------
166
+ // Tests — rows rendering
167
+ // ---------------------------------------------------------------------------
168
+ describe("RequestPage — rows", () => {
169
+ beforeEach(() => {
170
+ mockVariant = "standalone";
171
+ mockShowErrors = false;
172
+ });
173
+ it("renders no RequestRow components when there are no rows", async () => {
174
+ mockRows = [];
175
+ await renderPageReady();
176
+ expect(screen.queryByTestId("request-row")).not.toBeInTheDocument();
177
+ });
178
+ it("renders one RequestRow per row", async () => {
179
+ mockRows = [createMockRow({ id: 0 }), createMockRow({ id: 1 })];
180
+ await renderPageReady();
181
+ expect(screen.getAllByTestId("request-row")).toHaveLength(2);
182
+ });
183
+ it("shows the guide link button when there is at least one row", async () => {
184
+ mockRows = [createMockRow({ id: 0 })];
185
+ await renderPageReady();
186
+ expect(screen.getByTestId("guide-link")).toBeInTheDocument();
187
+ });
188
+ it("does not show the guide link button when there are no rows", async () => {
189
+ mockRows = [];
190
+ await renderPageReady();
191
+ expect(screen.queryByTestId("guide-link")).not.toBeInTheDocument();
192
+ });
193
+ });
194
+ // ---------------------------------------------------------------------------
195
+ // Tests — validation error display
196
+ // ---------------------------------------------------------------------------
197
+ describe("RequestPage — validation errors", () => {
198
+ beforeEach(() => {
199
+ mockVariant = "standalone";
200
+ });
201
+ it("does not show an error alert before the user attempts to submit", async () => {
202
+ mockRows = [];
203
+ mockShowErrors = false;
204
+ await renderPageReady();
205
+ expect(screen.queryByRole("alert")).not.toBeInTheDocument();
206
+ });
207
+ it("shows 'Please select at least one decay' when submitted with no rows", async () => {
208
+ mockRows = [];
209
+ mockShowErrors = true;
210
+ await renderPageReady();
211
+ expect(screen.getByText(/please select at least one decay/i)).toBeInTheDocument();
212
+ });
213
+ it("shows the config error when rows exist but are missing DTT/line/paths", async () => {
214
+ mockRows = [createMockRow({ id: 0, dtt: null })];
215
+ mockShowErrors = true;
216
+ mockValidation = { ...mockValidation, allRowsHaveDtt: false };
217
+ await renderPageReady();
218
+ expect(screen.getByText(/please name all decaytreetuples/i)).toBeInTheDocument();
219
+ });
220
+ it("shows no error alert when all rows are fully configured", async () => {
221
+ const row = createMockRow({
222
+ id: 0,
223
+ dtt: createMockDtt("TreeA"),
224
+ line: mockStrippingLine,
225
+ paths: ["/some/path/DIMUON.DST"],
226
+ });
227
+ mockRows = [row];
228
+ mockShowErrors = false;
229
+ mockValidation = {
230
+ isEmptySession: false,
231
+ isEmailValid: true,
232
+ allRowsHaveDtt: true,
233
+ allRowsHaveStrippingLine: true,
234
+ allRowsHavePaths: true,
235
+ validDttNames: true,
236
+ };
237
+ await renderPageReady();
238
+ expect(screen.queryByRole("alert")).not.toBeInTheDocument();
239
+ });
240
+ });
241
+ // ---------------------------------------------------------------------------
242
+ // Tests — Clear all button
243
+ // ---------------------------------------------------------------------------
244
+ describe("RequestPage — Clear all button", () => {
245
+ beforeEach(() => {
246
+ mockVariant = "standalone";
247
+ mockShowErrors = false;
248
+ });
249
+ it("is disabled when the session is empty", async () => {
250
+ mockRows = [];
251
+ mockValidation = { ...mockValidation, isEmptySession: true };
252
+ await renderPageReady();
253
+ expect(screen.getByTestId("clear-all-button")).toBeDisabled();
254
+ });
255
+ it("is enabled when the session is not empty", async () => {
256
+ mockRows = [createMockRow({ id: 0 })];
257
+ mockValidation = { ...mockValidation, isEmptySession: false };
258
+ await renderPageReady();
259
+ expect(screen.getByTestId("clear-all-button")).not.toBeDisabled();
260
+ });
261
+ it("calls clearAll when clicked", async () => {
262
+ mockRows = [createMockRow({ id: 0 })];
263
+ mockValidation = { ...mockValidation, isEmptySession: false };
264
+ await renderPageReady();
265
+ fireEvent.click(screen.getByTestId("clear-all-button"));
266
+ expect(mockClearAll).toHaveBeenCalledOnce();
267
+ });
268
+ });
269
+ // ---------------------------------------------------------------------------
270
+ // Tests — primary action (Download / Submit flow)
271
+ // ---------------------------------------------------------------------------
272
+ describe("RequestPage — primary action", () => {
273
+ beforeEach(() => {
274
+ mockShowErrors = false;
275
+ mockValidation = { ...mockValidation, isEmptySession: false };
276
+ });
277
+ it("calls trySubmit when the Download button is clicked", async () => {
278
+ mockVariant = "standalone";
279
+ mockRows = [];
280
+ await renderPageReady();
281
+ fireEvent.click(screen.getByRole("button", { name: /download/i }));
282
+ expect(mockTrySubmit).toHaveBeenCalledOnce();
283
+ });
284
+ it("calls trySubmit when the Submit button is clicked", async () => {
285
+ mockVariant = "embedded";
286
+ mockRows = [];
287
+ await renderPageReady();
288
+ fireEvent.click(screen.getByRole("button", { name: /submit/i }));
289
+ expect(mockTrySubmit).toHaveBeenCalledOnce();
290
+ });
291
+ it("shows the ReasonForRequest modal when embedded variant and trySubmit returns true", async () => {
292
+ mockVariant = "embedded";
293
+ mockTrySubmit.mockReturnValueOnce(true);
294
+ mockRows = [];
295
+ await renderPageReady();
296
+ fireEvent.click(screen.getByRole("button", { name: /submit/i }));
297
+ await waitFor(() => expect(screen.getByTestId("reason-modal")).toBeInTheDocument());
298
+ });
299
+ it("shows the success alert when the reason modal is submitted", async () => {
300
+ mockVariant = "embedded";
301
+ mockTrySubmit.mockReturnValueOnce(true);
302
+ mockRows = [];
303
+ await renderPageReady();
304
+ fireEvent.click(screen.getByRole("button", { name: /submit/i }));
305
+ await waitFor(() => screen.getByTestId("reason-modal"));
306
+ fireEvent.click(screen.getByTestId("reason-submit"));
307
+ await waitFor(() => expect(screen.getByText(/request submitted/i)).toBeInTheDocument());
308
+ });
309
+ it("hides the reason modal without showing success when cancelled", async () => {
310
+ mockVariant = "embedded";
311
+ mockTrySubmit.mockReturnValueOnce(true);
312
+ mockRows = [];
313
+ await renderPageReady();
314
+ fireEvent.click(screen.getByRole("button", { name: /submit/i }));
315
+ await waitFor(() => screen.getByTestId("reason-modal"));
316
+ fireEvent.click(screen.getByTestId("reason-cancel"));
317
+ await waitFor(() => expect(screen.queryByTestId("reason-modal")).not.toBeInTheDocument());
318
+ expect(screen.queryByText(/request submitted/i)).not.toBeInTheDocument();
319
+ });
320
+ it("calls generateAllFiles when standalone variant and trySubmit returns true", async () => {
321
+ mockVariant = "standalone";
322
+ mockTrySubmit.mockReturnValueOnce(true);
323
+ mockRows = [];
324
+ await renderPageReady();
325
+ fireEvent.click(screen.getByRole("button", { name: /download/i }));
326
+ await waitFor(() => expect(mockGenerateAllFiles).toHaveBeenCalled());
327
+ });
328
+ });
329
+ // ---------------------------------------------------------------------------
330
+ // Tests — success state
331
+ // ---------------------------------------------------------------------------
332
+ describe("RequestPage — success alert", () => {
333
+ it("shows the success alert and request submitted message after a successful submission", async () => {
334
+ mockVariant = "embedded";
335
+ mockTrySubmit.mockReturnValueOnce(true);
336
+ mockRows = [];
337
+ mockShowErrors = false;
338
+ mockValidation = { ...mockValidation, isEmptySession: false };
339
+ await renderPageReady();
340
+ fireEvent.click(screen.getByRole("button", { name: /submit/i }));
341
+ await waitFor(() => screen.getByTestId("reason-modal"));
342
+ fireEvent.click(screen.getByTestId("reason-submit"));
343
+ await waitFor(() => {
344
+ expect(screen.getByText(/request submitted!/i)).toBeInTheDocument();
345
+ expect(screen.getByText(/your request has been submitted/i)).toBeInTheDocument();
346
+ });
347
+ });
348
+ it("dismisses the success alert when its close button is clicked", async () => {
349
+ mockVariant = "embedded";
350
+ mockTrySubmit.mockReturnValueOnce(true);
351
+ mockRows = [];
352
+ await renderPageReady();
353
+ fireEvent.click(screen.getByRole("button", { name: /submit/i }));
354
+ await waitFor(() => screen.getByTestId("reason-modal"));
355
+ fireEvent.click(screen.getByTestId("reason-submit"));
356
+ await waitFor(() => screen.getByText(/request submitted!/i));
357
+ fireEvent.click(screen.getByLabelText(/close/i));
358
+ await waitFor(() => expect(screen.queryByText(/request submitted!/i)).not.toBeInTheDocument());
359
+ });
360
+ });
361
+ // ---------------------------------------------------------------------------
362
+ // Tests — localStorage clone (lines 52-73)
363
+ // ---------------------------------------------------------------------------
364
+ describe("RequestPage — localStorage clone", () => {
365
+ beforeEach(() => {
366
+ mockRows = [];
367
+ mockVariant = "standalone";
368
+ mockShowErrors = false;
369
+ localStorage.removeItem("dttConfigsToClone");
370
+ localStorage.removeItem("infoYamlToClone");
371
+ mockSetRows.mockClear();
372
+ mockSetProductionName.mockClear();
373
+ vi.mocked(processProductionFiles).mockReturnValue({ rows: [], emails: [] });
374
+ });
375
+ it("processes dttConfigs from localStorage and calls setRows", async () => {
376
+ localStorage.setItem("dttConfigsToClone", JSON.stringify([]));
377
+ await renderPageReady();
378
+ expect(mockSetRows).toHaveBeenCalled();
379
+ });
380
+ it("removes the localStorage items after processing", async () => {
381
+ localStorage.setItem("dttConfigsToClone", JSON.stringify([]));
382
+ localStorage.setItem("infoYamlToClone", JSON.stringify({ production_name: "test" }));
383
+ await renderPageReady();
384
+ expect(localStorage.getItem("dttConfigsToClone")).toBeNull();
385
+ expect(localStorage.getItem("infoYamlToClone")).toBeNull();
386
+ });
387
+ it("calls setProductionName with empty string on success", async () => {
388
+ localStorage.setItem("dttConfigsToClone", JSON.stringify([]));
389
+ await renderPageReady();
390
+ expect(mockSetProductionName).toHaveBeenCalledWith("");
391
+ });
392
+ it("shows an alert and does not call setRows when processProductionFiles returns an error string", async () => {
393
+ vi.mocked(processProductionFiles).mockReturnValue("Something went wrong");
394
+ const alertSpy = vi.spyOn(window, "alert").mockImplementation(() => { });
395
+ localStorage.setItem("dttConfigsToClone", JSON.stringify([]));
396
+ renderPage();
397
+ await waitFor(() => expect(alertSpy).toHaveBeenCalledWith("Something went wrong"));
398
+ alertSpy.mockRestore();
399
+ });
400
+ it("skips the clone block when localStorage has no dttConfigsToClone", async () => {
401
+ await renderPageReady();
402
+ // setRows not called from the clone block (may be called with initial value)
403
+ expect(localStorage.getItem("dttConfigsToClone")).toBeNull();
404
+ });
405
+ });
406
+ // ---------------------------------------------------------------------------
407
+ // Tests — validation error branches for allRowsHaveStrippingLine / allRowsHavePaths
408
+ // ---------------------------------------------------------------------------
409
+ describe("RequestPage — validation error branches (lines 140-141)", () => {
410
+ beforeEach(() => {
411
+ mockShowErrors = true;
412
+ });
413
+ it("shows error when rows exist but allRowsHaveStrippingLine=false", async () => {
414
+ mockRows = [createMockRow({ id: 0, dtt: createMockDtt() })];
415
+ mockValidation = {
416
+ isEmptySession: false,
417
+ isEmailValid: true,
418
+ allRowsHaveDtt: true,
419
+ allRowsHaveStrippingLine: false,
420
+ allRowsHavePaths: true,
421
+ validDttNames: true,
422
+ };
423
+ await renderPageReady();
424
+ expect(screen.getByRole("alert")).toBeInTheDocument();
425
+ });
426
+ it("shows error when rows exist but allRowsHavePaths=false", async () => {
427
+ mockRows = [createMockRow({ id: 0, dtt: createMockDtt() })];
428
+ mockValidation = {
429
+ isEmptySession: false,
430
+ isEmailValid: true,
431
+ allRowsHaveDtt: true,
432
+ allRowsHaveStrippingLine: true,
433
+ allRowsHavePaths: false,
434
+ validDttNames: true,
435
+ };
436
+ await renderPageReady();
437
+ expect(screen.getByRole("alert")).toBeInTheDocument();
438
+ });
439
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,105 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { describe, expect, it, vi } from "vitest";
3
+ import { act, renderHook } from "@testing-library/react";
4
+ import { DttProvider, useDtt } from "../../providers/DttProvider";
5
+ import { RowProvider } from "../../providers/RowProvider";
6
+ import { createMockDtt, createMockRow, mockDecay, mockMetadata } from "../testUtils";
7
+ import { TupleTool } from "../../models/tupleTool";
8
+ import Dtt from "../../models/dtt";
9
+ const mockUpdateRow = vi.fn();
10
+ vi.mock("../../providers/RowsProvider", () => ({
11
+ useRows: () => ({
12
+ rows: [],
13
+ configuredRows: [],
14
+ setRows: vi.fn(),
15
+ updateRow: mockUpdateRow,
16
+ removeRow: vi.fn(),
17
+ generateAllFiles: vi.fn(),
18
+ }),
19
+ }));
20
+ const mockRow = createMockRow({ id: 5, dtt: createMockDtt("TestTree") });
21
+ const mockInitialConfig = createMockDtt("TestTree").config;
22
+ function makeWrapper() {
23
+ return function Wrapper({ children }) {
24
+ return (_jsx(RowProvider, { row: mockRow, children: _jsx(DttProvider, { initialConfig: mockInitialConfig, decay: mockDecay, metadata: mockMetadata, children: children }) }));
25
+ };
26
+ }
27
+ function renderProvider() {
28
+ return renderHook(() => useDtt(), { wrapper: makeWrapper() });
29
+ }
30
+ describe("DttProvider", () => {
31
+ it("exposes the initial dtt", () => {
32
+ const { result } = renderProvider();
33
+ expect(result.current.dtt).toBeInstanceOf(Dtt);
34
+ });
35
+ it("exposes the decay", () => {
36
+ const { result } = renderProvider();
37
+ expect(result.current.decay).toBe(mockDecay);
38
+ });
39
+ it("selectedBranch defaults to empty array", () => {
40
+ const { result } = renderProvider();
41
+ expect(result.current.selectedBranch).toEqual([]);
42
+ });
43
+ it("setSelectedBranch updates selectedBranch", () => {
44
+ const { result } = renderProvider();
45
+ act(() => result.current.setSelectedBranch(["head"]));
46
+ expect(result.current.selectedBranch).toEqual(["head"]);
47
+ });
48
+ it("hoveredBranch defaults to empty array", () => {
49
+ const { result } = renderProvider();
50
+ expect(result.current.hoveredBranch).toEqual([]);
51
+ });
52
+ it("setHoveredBranch updates hoveredBranch", () => {
53
+ const { result } = renderProvider();
54
+ act(() => result.current.setHoveredBranch(["k"]));
55
+ expect(result.current.hoveredBranch).toEqual(["k"]);
56
+ });
57
+ it("highlightedBranch returns hoveredBranch when it's non-empty", () => {
58
+ const { result } = renderProvider();
59
+ act(() => result.current.setSelectedBranch(["head"]));
60
+ act(() => result.current.setHoveredBranch(["k"]));
61
+ expect(result.current.highlightedBranch).toEqual(["k"]);
62
+ });
63
+ it("highlightedBranch falls back to selectedBranch when hoveredBranch is empty", () => {
64
+ const { result } = renderProvider();
65
+ act(() => result.current.setSelectedBranch(["head"]));
66
+ act(() => result.current.setHoveredBranch([]));
67
+ expect(result.current.highlightedBranch).toEqual(["head"]);
68
+ });
69
+ it("addTool adds a tool and calls updateRow", () => {
70
+ const { result } = renderProvider();
71
+ const tool = new TupleTool("TupleToolKinematic", "");
72
+ act(() => result.current.addTool([], tool));
73
+ expect(mockUpdateRow).toHaveBeenCalled();
74
+ expect(result.current.dtt.listTools().some((t) => t.class === "TupleToolKinematic")).toBe(true);
75
+ });
76
+ it("removeTool removes a tool and calls updateRow", () => {
77
+ const { result } = renderProvider();
78
+ const tool = new TupleTool("TupleToolKinematic", "");
79
+ act(() => result.current.addTool([], tool));
80
+ mockUpdateRow.mockClear();
81
+ act(() => result.current.removeTool([], tool));
82
+ expect(mockUpdateRow).toHaveBeenCalled();
83
+ expect(result.current.dtt.listTools().some((t) => t.class === "TupleToolKinematic")).toBe(false);
84
+ });
85
+ it("updateToolParam updates a param and calls updateRow", () => {
86
+ const { result } = renderProvider();
87
+ const tool = new TupleTool("TupleToolKinematic", "");
88
+ act(() => result.current.addTool([], tool));
89
+ mockUpdateRow.mockClear();
90
+ act(() => result.current.updateToolParam([], tool, "Verbose", true));
91
+ expect(mockUpdateRow).toHaveBeenCalled();
92
+ });
93
+ it("save callback creates a new Dtt instance inside updateRow", () => {
94
+ mockUpdateRow.mockImplementationOnce((_id, fn) => {
95
+ fn(mockRow);
96
+ });
97
+ const { result } = renderProvider();
98
+ const tool = new TupleTool("TupleToolKinematic", "");
99
+ act(() => result.current.addTool([], tool));
100
+ expect(mockUpdateRow).toHaveBeenCalled();
101
+ });
102
+ it("throws when useDtt is used outside DttProvider", () => {
103
+ expect(() => renderHook(() => useDtt())).toThrow("useDtt must be used inside DttProvider");
104
+ });
105
+ });
@@ -0,0 +1 @@
1
+ export {};