lhcb-ntuple-wizard-test 2.1.0 → 2.2.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 (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 +16 -7
  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
@@ -1,4 +1,4 @@
1
- import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  /*****************************************************************************\
3
3
  * (c) Copyright 2021 CERN for the benefit of the LHCb Collaboration *
4
4
  * *
@@ -12,6 +12,7 @@ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
12
12
  import { createContext, useContext, useEffect, useState } from "react";
13
13
  import { config } from "../config.js";
14
14
  import pako from "pako";
15
+ import { LoadingIndicator } from "../components/LoadingIndicator";
15
16
  const MetadataContextType = createContext(null);
16
17
  export function MetadataProvider({ children }) {
17
18
  const [error, setError] = useState(null);
@@ -39,12 +40,17 @@ export function MetadataProvider({ children }) {
39
40
  .catch(setError);
40
41
  }, []);
41
42
  if (error) {
42
- return _jsxs("h1", { children: ["Oops: ", `${error}`] });
43
+ console.error("Failed to load metadata:", error);
44
+ return _jsx("h1", { children: "Error: failed to load metadata" });
43
45
  }
44
- else {
45
- return _jsx(MetadataContextType.Provider, { value: metadata, children: children });
46
+ if (!metadata) {
47
+ return _jsx(LoadingIndicator, { height: "70vh", message: "Loading metadata..." });
46
48
  }
49
+ return _jsx(MetadataContextType.Provider, { value: metadata, children: children });
47
50
  }
48
51
  export const useMetadata = () => {
49
- return useContext(MetadataContextType);
52
+ const ctx = useContext(MetadataContextType);
53
+ if (!ctx)
54
+ throw new Error("useMetadata must be used inside MetadataProvider");
55
+ return ctx;
50
56
  };
@@ -36,9 +36,9 @@ export const RequestProvider = ({ emailIsKnown, children }) => {
36
36
  const isEmptySession = !productionName && contactEmails.length === 0 && rows.length === 0;
37
37
  const isEmailValid = contactEmails.length > 0 && contactEmails.every(EmailValidator.validate);
38
38
  const allRowsHaveDtt = configuredRows.length > 0 && configuredRows.length === rows.length;
39
- const allRowsHaveStrippingLine = rows.every((r) => r.lines.length > 0);
39
+ const allRowsHaveStrippingLine = rows.every((r) => r.line);
40
40
  const allRowsHavePaths = rows.every((r) => r.paths.length > 0);
41
- const dttNames = configuredRows.map((r) => r.dtt.getName().trim());
41
+ const dttNames = configuredRows.map((r) => r.dtt?.getName()?.trim());
42
42
  const validDttNames = dttNames.every(Boolean) && new Set(dttNames).size === dttNames.length;
43
43
  return {
44
44
  isEmptySession,
@@ -3,8 +3,8 @@ import { RowData } from "../models/rowData";
3
3
  import { StrippingLine } from "../models/strippingLine";
4
4
  interface RowContextType {
5
5
  row: RowData;
6
- validateBkPath: (choice: string, lines?: StrippingLine[]) => boolean;
7
- updateBkPaths: (paths: string[], lines?: StrippingLine[]) => void;
6
+ validateBkPath: (choice: string, line?: StrippingLine) => boolean;
7
+ updateBkPaths: (paths: string[], line?: StrippingLine) => void;
8
8
  }
9
9
  interface RowProviderProps {
10
10
  row: RowData;
@@ -5,21 +5,22 @@ import { useRows } from "./RowsProvider";
5
5
  const RowContext = createContext(null);
6
6
  export const RowProvider = ({ row, children }) => {
7
7
  const { updateRow } = useRows();
8
- const validateBkPathsForLines = useCallback((choice, lines = row.lines) => {
8
+ const validateBkPathsForLines = useCallback((choice, line = row.line) => {
9
+ if (!line) {
10
+ return true;
11
+ }
9
12
  try {
10
13
  const path = new BkPath(choice);
11
- return lines.every((line) => {
12
- const streamName = line.stream.toLowerCase();
13
- const fileName = path.fileName.split(".")[0].toLowerCase();
14
- const matchingVersion = line.versions.some((v) => path.strippingVersion === v);
15
- return ["allstreams", streamName].includes(fileName) && matchingVersion;
16
- });
14
+ const streamName = line.stream.toLowerCase();
15
+ const fileName = path.fileName.split(".")[0].toLowerCase();
16
+ const matchingVersion = line.versions.some((v) => path.strippingVersion === v);
17
+ return ["allstreams", streamName].includes(fileName) && matchingVersion;
17
18
  }
18
19
  catch {
19
20
  return false;
20
21
  }
21
- }, [row.lines]);
22
- const updateBkPathsForLines = useCallback((paths, lines = row.lines) => {
22
+ }, [row.line]);
23
+ const updateBkPathsForLines = useCallback((paths, lines = row.line) => {
23
24
  updateRow(row.id, (r) => ({
24
25
  ...r,
25
26
  paths: paths.filter((p) => validateBkPathsForLines(p, lines)),
@@ -18,9 +18,6 @@ export const RowsProvider = ({ children }) => {
18
18
  setRows((prev) => prev.filter((r) => r.id !== id));
19
19
  };
20
20
  const generateAllFiles = () => {
21
- if (!metadata) {
22
- return [];
23
- }
24
21
  return [
25
22
  ...rows.filter((row) => row.dtt).map((row) => YamlFile.fromDtt(row.dtt)),
26
23
  YamlFile.createInfoYaml(rows, metadata),
@@ -0,0 +1,118 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { beforeEach, describe, expect, it, vi } from "vitest";
3
+ import { render, screen } from "@testing-library/react";
4
+ import { BookkeepingPathDropdown } from "../../components/BookkeepingPathDropdown";
5
+ import { mockStrippingLine } from "../testUtils";
6
+ const SAMPLE_PATH = "/LHCb/Collision16/Beam6500GeV-VeloClosed-MagDown/Real Data/Reco16/Stripping28r2/90000000/ALLSTREAMS.DST";
7
+ const mockUpdateBkPaths = vi.fn();
8
+ const mockValidateBkPath = vi.fn(() => true);
9
+ const mockUpdateRow = vi.fn();
10
+ const mockMetadataWithDataset = {
11
+ dataset: [SAMPLE_PATH],
12
+ decays: {},
13
+ embedding: {},
14
+ kgdoc: {},
15
+ lokiVariables: { applicationInfo: {}, lokiVariables: {} },
16
+ particleProperties: {},
17
+ stripping: {},
18
+ strippingHints: {},
19
+ tupleTools: { applicationInfo: {}, tupleTools: {} },
20
+ userHints: { decayTags: {}, particleTags: {} },
21
+ };
22
+ vi.mock("../../providers/MetadataProvider", () => ({
23
+ useMetadata: () => mockMetadataWithDataset,
24
+ }));
25
+ let mockRow = {
26
+ id: 0,
27
+ decay: { lines: {}, tags: [], descriptors: {} },
28
+ dtt: null,
29
+ line: mockStrippingLine,
30
+ paths: [],
31
+ pathOptions: [],
32
+ };
33
+ vi.mock("../../providers/RowProvider", () => ({
34
+ useRow: () => ({
35
+ row: mockRow,
36
+ validateBkPath: mockValidateBkPath,
37
+ updateBkPaths: mockUpdateBkPaths,
38
+ }),
39
+ }));
40
+ vi.mock("../../providers/RowsProvider", () => ({
41
+ useRows: () => ({
42
+ updateRow: mockUpdateRow,
43
+ }),
44
+ }));
45
+ vi.mock("../../components/DatasetInfo", () => ({
46
+ DatasetInfo: ({ pathString }) => _jsx("span", { children: pathString }),
47
+ }));
48
+ // Stub Creatable so we can invoke onCreateOption directly in tests
49
+ let capturedOnCreateOption = null;
50
+ vi.mock("react-select/creatable", () => ({
51
+ default: ({ onCreateOption, value, isDisabled, }) => {
52
+ capturedOnCreateOption = onCreateOption;
53
+ return (_jsxs(_Fragment, { children: [_jsx("input", { role: "combobox", disabled: isDisabled, readOnly: true }), value?.map((v) => (_jsx("span", { children: v.label }, v.value)))] }));
54
+ },
55
+ }));
56
+ describe("BookkeepingPathDropdown", () => {
57
+ beforeEach(() => {
58
+ mockRow = {
59
+ id: 0,
60
+ decay: { lines: {}, tags: [], descriptors: {} },
61
+ dtt: null,
62
+ line: mockStrippingLine,
63
+ paths: [],
64
+ pathOptions: [],
65
+ };
66
+ capturedOnCreateOption = null;
67
+ });
68
+ it("renders a combobox", () => {
69
+ render(_jsx(BookkeepingPathDropdown, {}));
70
+ expect(screen.getByRole("combobox")).toBeInTheDocument();
71
+ });
72
+ it("is enabled when a stripping line is selected", () => {
73
+ render(_jsx(BookkeepingPathDropdown, {}));
74
+ expect(screen.getByRole("combobox")).not.toBeDisabled();
75
+ });
76
+ it("renders without error when dataset has paths", () => {
77
+ expect(() => render(_jsx(BookkeepingPathDropdown, {}))).not.toThrow();
78
+ });
79
+ it("renders selected paths from row.paths", () => {
80
+ mockRow = { ...mockRow, paths: [SAMPLE_PATH] };
81
+ render(_jsx(BookkeepingPathDropdown, {}));
82
+ expect(document.body.textContent).toContain(SAMPLE_PATH);
83
+ });
84
+ });
85
+ describe("BookkeepingPathDropdown — handleCreatePath", () => {
86
+ beforeEach(() => {
87
+ mockRow = {
88
+ id: 0,
89
+ decay: { lines: {}, tags: [], descriptors: {} },
90
+ dtt: null,
91
+ line: mockStrippingLine,
92
+ paths: [],
93
+ pathOptions: [],
94
+ };
95
+ mockUpdateRow.mockClear();
96
+ mockValidateBkPath.mockReset();
97
+ capturedOnCreateOption = null;
98
+ });
99
+ it("calls updateRow with the new path when the path is valid", () => {
100
+ mockValidateBkPath.mockReturnValue(true);
101
+ render(_jsx(BookkeepingPathDropdown, {}));
102
+ capturedOnCreateOption(SAMPLE_PATH);
103
+ expect(mockUpdateRow).toHaveBeenCalledOnce();
104
+ const updater = mockUpdateRow.mock.calls[0][1];
105
+ const result = updater({ paths: [], pathOptions: [] });
106
+ expect(result.paths).toContain(SAMPLE_PATH);
107
+ expect(result.pathOptions).toContain(SAMPLE_PATH);
108
+ });
109
+ it("shows an alert and does not call updateRow when the path is invalid", () => {
110
+ mockValidateBkPath.mockReturnValue(false);
111
+ const alertSpy = vi.spyOn(window, "alert").mockImplementation(() => { });
112
+ render(_jsx(BookkeepingPathDropdown, {}));
113
+ capturedOnCreateOption("bad-path");
114
+ expect(alertSpy).toHaveBeenCalledWith("The path 'bad-path' is not valid for the selected stripping line.");
115
+ expect(mockUpdateRow).not.toHaveBeenCalled();
116
+ alertSpy.mockRestore();
117
+ });
118
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,38 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { describe, expect, it, vi } from "vitest";
3
+ import { render, screen } from "@testing-library/react";
4
+ import { DatasetInfo } from "../../components/DatasetInfo";
5
+ const VALID_PATH = "/LHCb/Collision16/Beam6500GeV-VeloClosed-MagDown/Real Data/Reco16/Stripping28r2/90000000/DIMUON.DST";
6
+ // Stub the badge components to keep tests focused on DatasetInfo's own logic
7
+ vi.mock("../../components/YearBadge", () => ({ YearBadge: ({ year }) => _jsx("span", { children: year }) }));
8
+ vi.mock("../../components/PolarityBadge", () => ({
9
+ PolarityBadge: ({ polarity }) => _jsx("span", { children: polarity }),
10
+ }));
11
+ vi.mock("../../components/StrippingLineVersionBadge", () => ({
12
+ StrippingLineVersionBadge: ({ version }) => _jsx("span", { children: version }),
13
+ }));
14
+ vi.mock("../../components/DatasetEventTypeBadge", () => ({
15
+ DatasetEventTypeBadge: ({ eventType }) => _jsx("span", { children: eventType }),
16
+ }));
17
+ describe("DatasetInfo", () => {
18
+ it("renders the file name for a valid path", () => {
19
+ render(_jsx(DatasetInfo, { pathString: VALID_PATH }));
20
+ expect(screen.getByText("DIMUON.DST")).toBeInTheDocument();
21
+ });
22
+ it("renders the year badge", () => {
23
+ render(_jsx(DatasetInfo, { pathString: VALID_PATH }));
24
+ expect(screen.getByText("2016")).toBeInTheDocument();
25
+ });
26
+ it("renders the polarity badge", () => {
27
+ render(_jsx(DatasetInfo, { pathString: VALID_PATH }));
28
+ expect(screen.getByText("MagDown")).toBeInTheDocument();
29
+ });
30
+ it("renders the stripping version badge", () => {
31
+ render(_jsx(DatasetInfo, { pathString: VALID_PATH }));
32
+ expect(screen.getByText("28r2")).toBeInTheDocument();
33
+ });
34
+ it("returns null (renders nothing) for an invalid path string", () => {
35
+ const { container } = render(_jsx(DatasetInfo, { pathString: "not/a/valid/path" }));
36
+ expect(container.firstChild).toBeNull();
37
+ });
38
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,115 @@
1
+ import { jsx as _jsx } 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 { DecayCard } from "../../components/DecayCard";
5
+ import { createMockDtt, createMockRow, mockDecay, mockStrippingLine } from "../testUtils";
6
+ import { MemoryRouter } from "react-router-dom";
7
+ // ---------------------------------------------------------------------------
8
+ // Module mocks
9
+ // ---------------------------------------------------------------------------
10
+ const mockNavigate = vi.fn();
11
+ vi.mock("react-router-dom", async (importOriginal) => {
12
+ const actual = await importOriginal();
13
+ return { ...actual, useNavigate: () => mockNavigate };
14
+ });
15
+ let mockRow = createMockRow();
16
+ let mockRows = [mockRow];
17
+ const mockUpdateRow = vi.fn();
18
+ const mockSetRows = vi.fn();
19
+ vi.mock("../../providers/RowProvider", () => ({
20
+ useRow: () => ({ row: mockRow, validateBkPath: vi.fn(() => true), updateBkPaths: vi.fn() }),
21
+ }));
22
+ vi.mock("../../providers/RowsProvider", () => ({
23
+ useRows: () => ({
24
+ rows: mockRows,
25
+ configuredRows: mockRows.filter((r) => r.dtt),
26
+ setRows: mockSetRows,
27
+ updateRow: mockUpdateRow,
28
+ removeRow: vi.fn(),
29
+ generateAllFiles: vi.fn(),
30
+ }),
31
+ }));
32
+ vi.mock("../../providers/WizardConfigProvider", () => ({
33
+ useWizardConfig: () => ({ variant: "standalone", basePath: "" }),
34
+ }));
35
+ // ---------------------------------------------------------------------------
36
+ // Helpers
37
+ // ---------------------------------------------------------------------------
38
+ function renderCard() {
39
+ return render(_jsx(MemoryRouter, { children: _jsx(DecayCard, {}) }));
40
+ }
41
+ // ---------------------------------------------------------------------------
42
+ // Tests — no DTT configured
43
+ // ---------------------------------------------------------------------------
44
+ describe("DecayCard — no DTT", () => {
45
+ beforeEach(() => {
46
+ mockRow = createMockRow({ decay: mockDecay, dtt: null });
47
+ mockRows = [mockRow];
48
+ });
49
+ it("renders without crashing", () => {
50
+ renderCard();
51
+ expect(screen.getByTestId("inline-math")).toBeInTheDocument();
52
+ });
53
+ it("shows a success-variant add button when there is no DTT", () => {
54
+ const { container } = renderCard();
55
+ expect(container.querySelector(".btn-success")).toBeInTheDocument();
56
+ });
57
+ it("shows the upload button (standalone mode) when there is no DTT", () => {
58
+ const { container } = renderCard();
59
+ // Two buttons: success (add) and default (upload)
60
+ const buttons = container.querySelectorAll(".card-header button");
61
+ expect(buttons.length).toBeGreaterThanOrEqual(2);
62
+ });
63
+ it("calls updateRow when the add button is clicked", async () => {
64
+ const { container } = renderCard();
65
+ const addButton = container.querySelector(".btn-success");
66
+ fireEvent.click(addButton);
67
+ await waitFor(() => expect(mockUpdateRow).toHaveBeenCalledOnce());
68
+ });
69
+ it("does not show the name input when there is no DTT", () => {
70
+ renderCard();
71
+ expect(screen.queryByPlaceholderText("MyDecayTree")).not.toBeInTheDocument();
72
+ });
73
+ it("does not show the configure (secondary) button when there is no DTT", () => {
74
+ const { container } = renderCard();
75
+ expect(container.querySelector(".btn-secondary")).not.toBeInTheDocument();
76
+ });
77
+ });
78
+ // ---------------------------------------------------------------------------
79
+ // Tests — DTT configured
80
+ // ---------------------------------------------------------------------------
81
+ describe("DecayCard — DTT configured", () => {
82
+ beforeEach(() => {
83
+ const dtt = createMockDtt("MyTree");
84
+ mockRow = createMockRow({ decay: mockDecay, dtt, line: mockStrippingLine });
85
+ mockRows = [mockRow];
86
+ });
87
+ it("shows the DTT name input field", () => {
88
+ renderCard();
89
+ expect(screen.getByPlaceholderText("MyDecayTree")).toBeInTheDocument();
90
+ });
91
+ it("shows the configure button (secondary variant)", () => {
92
+ const { container } = renderCard();
93
+ expect(container.querySelector(".btn-secondary")).toBeInTheDocument();
94
+ });
95
+ it("shows the download button (standalone mode)", () => {
96
+ const { container } = renderCard();
97
+ // In standalone+DTT mode: secondary (configure), default (download), danger (delete)
98
+ const buttons = container.querySelectorAll(".card-header button");
99
+ expect(buttons.length).toBeGreaterThanOrEqual(3);
100
+ });
101
+ it("shows a danger-variant delete button", () => {
102
+ const { container } = renderCard();
103
+ expect(container.querySelector(".btn-danger")).toBeInTheDocument();
104
+ });
105
+ it("navigates to /variables when the configure button is clicked", async () => {
106
+ const { container } = renderCard();
107
+ const configureButton = container.querySelector(".btn-secondary");
108
+ fireEvent.click(configureButton);
109
+ await waitFor(() => expect(mockNavigate).toHaveBeenCalledWith("/variables"));
110
+ });
111
+ it("does not show the add (+) button when DTT exists", () => {
112
+ const { container } = renderCard();
113
+ expect(container.querySelector(".btn-success")).not.toBeInTheDocument();
114
+ });
115
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,31 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { describe, expect, it } from "vitest";
3
+ import { render, screen } from "@testing-library/react";
4
+ import { DecayLatex } from "../../components/DecayLatex";
5
+ import { mockDecay } from "../testUtils";
6
+ describe("DecayLatex", () => {
7
+ it("renders without crashing", () => {
8
+ render(_jsx(DecayLatex, { decay: mockDecay }));
9
+ expect(screen.getByTestId("inline-math")).toBeInTheDocument();
10
+ });
11
+ it("renders the decay's LaTeX when no selection is provided", () => {
12
+ render(_jsx(DecayLatex, { decay: mockDecay }));
13
+ expect(screen.getByTestId("inline-math").textContent).toBe(mockDecay.descriptors.latex);
14
+ });
15
+ it("renders the decay's LaTeX when selection is an empty array", () => {
16
+ render(_jsx(DecayLatex, { decay: mockDecay, selection: [] }));
17
+ expect(screen.getByTestId("inline-math").textContent).toBe(mockDecay.descriptors.latex);
18
+ });
19
+ it("renders highlighted LaTeX when a branch is selected", () => {
20
+ render(_jsx(DecayLatex, { decay: mockDecay, selection: ["head"] }));
21
+ const math = screen.getByTestId("inline-math").textContent ?? "";
22
+ // The selected particle should be wrapped in \textcolor
23
+ expect(math).toContain("\\textcolor");
24
+ });
25
+ it("renders a span wrapper around the math (for the popover trigger)", () => {
26
+ const { container } = render(_jsx(DecayLatex, { decay: mockDecay }));
27
+ // The InlineMath is wrapped in a span so the OverlayTrigger can attach
28
+ const span = container.querySelector("span > span[data-testid='inline-math']");
29
+ expect(span).not.toBeNull();
30
+ });
31
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,76 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { describe, expect, it, vi } from "vitest";
3
+ import { act, fireEvent, render, screen } from "@testing-library/react";
4
+ import { DecayList } from "../../components/DecayList";
5
+ import { mockDecay } from "../testUtils";
6
+ vi.mock("../../components/DecayTagBadge", () => ({
7
+ DecayTagBadge: ({ tag }) => _jsx("span", { children: tag }),
8
+ }));
9
+ // react-infinite-scroll-component doesn't support scroll in jsdom — render children directly
10
+ // Capture the `next` callback so tests can trigger batch loading
11
+ let capturedNext = null;
12
+ vi.mock("react-infinite-scroll-component", () => ({
13
+ default: ({ children, next }) => {
14
+ capturedNext = next;
15
+ return _jsx("div", { "data-testid": "infinite-scroll", children: children });
16
+ },
17
+ }));
18
+ vi.mock("../../config", () => ({
19
+ config: { batch_size: 5 },
20
+ }));
21
+ const mockOnItemSelected = vi.fn();
22
+ const makeDecay = (name) => ({
23
+ ...mockDecay,
24
+ descriptors: { ...mockDecay.descriptors, plain: name },
25
+ });
26
+ describe("DecayList — empty", () => {
27
+ it("shows a 'no matching decays' alert when the list is empty", () => {
28
+ render(_jsx(DecayList, { decays: [], selectedDecays: [], onItemSelected: mockOnItemSelected }));
29
+ expect(screen.getByText(/no matching decays/i)).toBeInTheDocument();
30
+ });
31
+ });
32
+ describe("DecayList — with items", () => {
33
+ it("renders one list item per decay", () => {
34
+ const decays = [makeDecay("a"), makeDecay("b"), makeDecay("c")];
35
+ render(_jsx(DecayList, { decays: decays, selectedDecays: [], onItemSelected: mockOnItemSelected }));
36
+ expect(screen.getAllByRole("checkbox")).toHaveLength(3);
37
+ });
38
+ it("shows the count alert", () => {
39
+ const decays = [makeDecay("a"), makeDecay("b")];
40
+ render(_jsx(DecayList, { decays: decays, selectedDecays: [], onItemSelected: mockOnItemSelected }));
41
+ expect(screen.getByText(/showing 2 of 2/i)).toBeInTheDocument();
42
+ });
43
+ it("calls onItemSelected when a decay is clicked", () => {
44
+ mockOnItemSelected.mockClear();
45
+ const decays = [makeDecay("a")];
46
+ const { container } = render(_jsx(DecayList, { decays: decays, selectedDecays: [], onItemSelected: mockOnItemSelected }));
47
+ fireEvent.click(container.querySelector("li"));
48
+ expect(mockOnItemSelected).toHaveBeenCalled();
49
+ });
50
+ it("renders the initial batch when decay count exceeds batch_size", () => {
51
+ const decays = Array.from({ length: 8 }, (_, i) => makeDecay(`decay${i}`));
52
+ render(_jsx(DecayList, { decays: decays, selectedDecays: [], onItemSelected: mockOnItemSelected }));
53
+ // batch_size is 5, so only 5 items rendered initially
54
+ expect(screen.getAllByRole("checkbox")).toHaveLength(5);
55
+ expect(screen.getByText(/showing 5 of 8/i)).toBeInTheDocument();
56
+ });
57
+ it("loads more items when fetchMoreData is called", () => {
58
+ capturedNext = null;
59
+ const decays = Array.from({ length: 8 }, (_, i) => makeDecay(`decay${i}`));
60
+ render(_jsx(DecayList, { decays: decays, selectedDecays: [], onItemSelected: mockOnItemSelected }));
61
+ // Trigger the infinite scroll "next" callback to load more items
62
+ expect(capturedNext).not.toBeNull();
63
+ act(() => capturedNext());
64
+ expect(screen.getAllByRole("checkbox")).toHaveLength(8);
65
+ });
66
+ it("deselects a decay when it is in selectedDecays and is clicked", () => {
67
+ mockOnItemSelected.mockClear();
68
+ const decay = makeDecay("selected-decay");
69
+ render(_jsx(DecayList, { decays: [decay], selectedDecays: [decay], onItemSelected: mockOnItemSelected }));
70
+ const { container } = render(_jsx(DecayList, { decays: [decay], selectedDecays: [decay], onItemSelected: mockOnItemSelected }));
71
+ fireEvent.click(container.querySelector("li"));
72
+ // The deselect branch filters out the clicked decay from selectedDecays
73
+ const calledWith = mockOnItemSelected.mock.calls[mockOnItemSelected.mock.calls.length - 1][0];
74
+ expect(calledWith).toHaveLength(0);
75
+ });
76
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,51 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { describe, expect, it, vi } from "vitest";
3
+ import { fireEvent, render, screen } from "@testing-library/react";
4
+ import { DecayListItem } from "../../components/DecayListItem";
5
+ import { mockDecay } from "../testUtils";
6
+ vi.mock("../../components/DecayTagBadge", () => ({
7
+ DecayTagBadge: ({ tag }) => _jsx("span", { "data-testid": "decay-tag-badge", children: tag }),
8
+ }));
9
+ const mockOnItemSelected = vi.fn();
10
+ describe("DecayListItem", () => {
11
+ it("renders a checkbox", () => {
12
+ render(_jsx(DecayListItem, { decay: mockDecay, onItemSelected: mockOnItemSelected }));
13
+ expect(screen.getByRole("checkbox")).toBeInTheDocument();
14
+ });
15
+ it("checkbox is unchecked by default", () => {
16
+ render(_jsx(DecayListItem, { decay: mockDecay, onItemSelected: mockOnItemSelected }));
17
+ expect(screen.getByRole("checkbox")).not.toBeChecked();
18
+ });
19
+ it("checkbox is checked when initiallySelected=true", () => {
20
+ render(_jsx(DecayListItem, { decay: mockDecay, initiallySelected: true, onItemSelected: mockOnItemSelected }));
21
+ expect(screen.getByRole("checkbox")).toBeChecked();
22
+ });
23
+ it("calls onItemSelected with selected=false when clicking an unselected item", () => {
24
+ mockOnItemSelected.mockClear();
25
+ const { container } = render(_jsx(DecayListItem, { decay: mockDecay, onItemSelected: mockOnItemSelected }));
26
+ fireEvent.click(container.querySelector("li"));
27
+ expect(mockOnItemSelected).toHaveBeenCalledWith({ selected: false, decay: mockDecay });
28
+ });
29
+ it("calls onItemSelected with selected=true when clicking a selected item", () => {
30
+ mockOnItemSelected.mockClear();
31
+ const { container } = render(_jsx(DecayListItem, { decay: mockDecay, initiallySelected: true, onItemSelected: mockOnItemSelected }));
32
+ fireEvent.click(container.querySelector("li"));
33
+ expect(mockOnItemSelected).toHaveBeenCalledWith({ selected: true, decay: mockDecay });
34
+ });
35
+ it("toggles the checkbox state on click", () => {
36
+ const { container } = render(_jsx(DecayListItem, { decay: mockDecay, onItemSelected: mockOnItemSelected }));
37
+ const li = container.querySelector("li");
38
+ expect(screen.getByRole("checkbox")).not.toBeChecked();
39
+ fireEvent.click(li);
40
+ expect(screen.getByRole("checkbox")).toBeChecked();
41
+ });
42
+ it("renders tag badges for decay tags", () => {
43
+ const decayWithTags = { ...mockDecay, tags: ["B2OC", "SLOW"] };
44
+ render(_jsx(DecayListItem, { decay: decayWithTags, onItemSelected: mockOnItemSelected }));
45
+ expect(screen.getAllByTestId("decay-tag-badge")).toHaveLength(2);
46
+ });
47
+ it("renders the decay latex", () => {
48
+ render(_jsx(DecayListItem, { decay: mockDecay, onItemSelected: mockOnItemSelected }));
49
+ expect(screen.getByTestId("inline-math")).toBeInTheDocument();
50
+ });
51
+ });
@@ -0,0 +1 @@
1
+ export {};