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.
- package/dist/components/AddTupleToolButton.d.ts +1 -0
- package/dist/components/AddTupleToolButton.js +16 -0
- package/dist/components/BookkeepingPathDropdown.d.ts +1 -1
- package/dist/components/BookkeepingPathDropdown.js +1 -4
- package/dist/components/DatasetEventTypeBadge.js +1 -1
- package/dist/components/DatasetInfo.js +3 -3
- package/dist/components/DecayCard.d.ts +1 -1
- package/dist/components/DecayCard.js +12 -40
- package/dist/components/DecayLatex.d.ts +1 -1
- package/dist/components/DecayLatex.js +4 -87
- package/dist/components/DecayListItem.js +2 -2
- package/dist/components/DecayTagBadge.d.ts +1 -1
- package/dist/components/DecayTagBadge.js +0 -3
- package/dist/components/DecayTreeCard.d.ts +1 -7
- package/dist/components/DecayTreeCard.js +13 -18
- package/dist/components/DecayTreeCardHeader.d.ts +2 -3
- package/dist/components/DecayTreeCardHeader.js +2 -2
- package/dist/components/DecayTreeGraph.d.ts +1 -4
- package/dist/components/DecayTreeGraph.js +66 -41
- package/dist/components/DeleteButton.d.ts +2 -1
- package/dist/components/DeleteButton.js +2 -2
- package/dist/components/DttNameInput.d.ts +9 -0
- package/dist/components/DttNameInput.js +43 -0
- package/dist/components/GuideLinkButton.d.ts +15 -0
- package/dist/components/GuideLinkButton.js +16 -0
- package/dist/components/NtupleWizard.js +1 -7
- package/dist/components/ParticleDropdown.d.ts +11 -1
- package/dist/components/ParticleDropdown.js +3 -6
- package/dist/components/ParticleLatex.d.ts +6 -0
- package/dist/components/ParticleLatex.js +13 -0
- package/dist/components/ParticleTagBadge.d.ts +2 -1
- package/dist/components/ParticleTagBadge.js +5 -7
- package/dist/components/ParticleTagFilters.d.ts +1 -6
- package/dist/components/ParticleTagFilters.js +16 -17
- package/dist/components/RequestButtonGroup.d.ts +1 -1
- package/dist/components/RequestButtonGroup.js +0 -3
- package/dist/components/RequestRow.d.ts +1 -1
- package/dist/components/RequestRow.js +4 -9
- package/dist/components/StrippingLineDropdown.js +14 -16
- package/dist/components/StrippingLineInfo.d.ts +5 -5
- package/dist/components/StrippingLineInfo.js +14 -4
- package/dist/components/StrippingLineInfoButton.d.ts +6 -0
- package/dist/components/StrippingLineInfoButton.js +7 -0
- package/dist/components/StrippingLineVersionBadge.d.ts +7 -0
- package/dist/components/{StrippingLineBadge.js → StrippingLineVersionBadge.js} +5 -5
- package/dist/components/StrippingLinesCountBadge.d.ts +8 -0
- package/dist/components/StrippingLinesCountBadge.js +11 -0
- package/dist/components/TagDropdown.d.ts +3 -3
- package/dist/components/TagDropdown.js +2 -2
- package/dist/components/TupleToolClassDropdown.js +11 -14
- package/dist/components/TupleToolDocsAccordion.d.ts +1 -1
- package/dist/components/TupleToolDocsAccordion.js +4 -8
- package/dist/components/TupleToolGroupsAccordion.d.ts +5 -0
- package/dist/components/TupleToolGroupsAccordion.js +31 -0
- package/dist/components/TupleToolLabel.d.ts +1 -1
- package/dist/components/TupleToolLabel.js +2 -5
- package/dist/components/TupleToolsAccordion.d.ts +1 -0
- package/dist/components/TupleToolsAccordion.js +22 -0
- package/dist/components/modals/AddTupleToolModal.js +3 -2
- package/dist/components/modals/ConfigureTupleToolModal.js +1 -1
- package/dist/components/modals/UploadDttConfigModal.d.ts +1 -1
- package/dist/components/modals/UploadDttConfigModal.js +1 -4
- package/dist/config.d.ts +1 -0
- package/dist/config.js +3 -2
- package/dist/models/bkPath.js +1 -1
- package/dist/models/dtt.d.ts +5 -2
- package/dist/models/dtt.js +37 -18
- package/dist/models/rowData.d.ts +1 -1
- package/dist/models/yamlFile.js +1 -1
- package/dist/pages/DecaySearchPage.js +4 -9
- package/dist/pages/DecayTreeConfigPage.js +4 -38
- package/dist/pages/RequestPage.js +2 -4
- package/dist/providers/DttProvider.d.ts +3 -3
- package/dist/providers/DttProvider.js +30 -55
- package/dist/providers/MetadataProvider.d.ts +1 -1
- package/dist/providers/MetadataProvider.js +11 -5
- package/dist/providers/RequestProvider.js +2 -2
- package/dist/providers/RowProvider.d.ts +2 -2
- package/dist/providers/RowProvider.js +10 -9
- package/dist/providers/RowsProvider.js +0 -3
- package/dist/tests/components/BookkeepingPathDropdown.test.d.ts +1 -0
- package/dist/tests/components/BookkeepingPathDropdown.test.js +118 -0
- package/dist/tests/components/DatasetInfo.test.d.ts +1 -0
- package/dist/tests/components/DatasetInfo.test.js +38 -0
- package/dist/tests/components/DecayCard.test.d.ts +1 -0
- package/dist/tests/components/DecayCard.test.js +115 -0
- package/dist/tests/components/DecayLatex.test.d.ts +1 -0
- package/dist/tests/components/DecayLatex.test.js +31 -0
- package/dist/tests/components/DecayList.test.d.ts +1 -0
- package/dist/tests/components/DecayList.test.js +76 -0
- package/dist/tests/components/DecayListItem.test.d.ts +1 -0
- package/dist/tests/components/DecayListItem.test.js +51 -0
- package/dist/tests/components/DecayTreeCard.test.d.ts +1 -0
- package/dist/tests/components/DecayTreeCard.test.js +119 -0
- package/dist/tests/components/DecayTreeGraph.test.d.ts +1 -0
- package/dist/tests/components/DecayTreeGraph.test.js +125 -0
- package/dist/tests/components/DeleteButton.test.d.ts +1 -0
- package/dist/tests/components/DeleteButton.test.js +45 -0
- package/dist/tests/components/DttNameInput.test.d.ts +1 -0
- package/dist/tests/components/DttNameInput.test.js +75 -0
- package/dist/tests/components/NtupleWizard.test.d.ts +1 -0
- package/dist/tests/components/NtupleWizard.test.js +57 -0
- package/dist/tests/components/ParticleDropdown.test.d.ts +1 -0
- package/dist/tests/components/ParticleDropdown.test.js +23 -0
- package/dist/tests/components/ParticleTagFilters.test.d.ts +1 -0
- package/dist/tests/components/ParticleTagFilters.test.js +87 -0
- package/dist/tests/components/RequestButtonGroup.test.d.ts +1 -0
- package/dist/tests/components/RequestButtonGroup.test.js +132 -0
- package/dist/tests/components/RequestRow.test.d.ts +1 -0
- package/dist/tests/components/RequestRow.test.js +58 -0
- package/dist/tests/components/StrippingLineDropdown.test.d.ts +1 -0
- package/dist/tests/components/StrippingLineDropdown.test.js +88 -0
- package/dist/tests/components/badges.test.d.ts +1 -0
- package/dist/tests/components/badges.test.js +120 -0
- package/dist/tests/components/dropdowns.test.d.ts +1 -0
- package/dist/tests/components/dropdowns.test.js +110 -0
- package/dist/tests/components/dttComponents.test.d.ts +1 -0
- package/dist/tests/components/dttComponents.test.js +287 -0
- package/dist/tests/components/formInputs.test.d.ts +1 -0
- package/dist/tests/components/formInputs.test.js +96 -0
- package/dist/tests/components/metadataComponents.test.d.ts +1 -0
- package/dist/tests/components/metadataComponents.test.js +137 -0
- package/dist/tests/components/miscComponents.test.d.ts +1 -0
- package/dist/tests/components/miscComponents.test.js +134 -0
- package/dist/tests/components/modals.test.d.ts +1 -0
- package/dist/tests/components/modals.test.js +554 -0
- package/dist/tests/components/tupleToolParams.test.d.ts +1 -0
- package/dist/tests/components/tupleToolParams.test.js +213 -0
- package/dist/tests/config.test.d.ts +1 -0
- package/dist/tests/config.test.js +31 -0
- package/dist/tests/mockSetup.d.ts +1 -0
- package/dist/tests/mockSetup.js +30 -0
- package/dist/tests/models/BkPath.test.d.ts +1 -0
- package/dist/tests/models/BkPath.test.js +87 -0
- package/dist/tests/models/Dtt.test.d.ts +1 -0
- package/dist/tests/models/Dtt.test.js +376 -0
- package/dist/tests/models/TupleTool.test.d.ts +1 -0
- package/dist/tests/models/TupleTool.test.js +80 -0
- package/dist/tests/models/YamlFile.test.d.ts +1 -0
- package/dist/tests/models/YamlFile.test.js +123 -0
- package/dist/tests/pages/DecaySearchPage.test.d.ts +1 -0
- package/dist/tests/pages/DecaySearchPage.test.js +228 -0
- package/dist/tests/pages/DecayTreeConfigPage.test.d.ts +1 -0
- package/dist/tests/pages/DecayTreeConfigPage.test.js +105 -0
- package/dist/tests/pages/RequestPage.test.d.ts +1 -0
- package/dist/tests/pages/RequestPage.test.js +439 -0
- package/dist/tests/providers/DttProvider.test.d.ts +1 -0
- package/dist/tests/providers/DttProvider.test.js +105 -0
- package/dist/tests/providers/MetadataProvider.test.d.ts +1 -0
- package/dist/tests/providers/MetadataProvider.test.js +129 -0
- package/dist/tests/providers/RequestProvider.test.d.ts +1 -0
- package/dist/tests/providers/RequestProvider.test.js +306 -0
- package/dist/tests/providers/RowProvider.test.d.ts +1 -0
- package/dist/tests/providers/RowProvider.test.js +110 -0
- package/dist/tests/providers/RowsProvider.test.d.ts +1 -0
- package/dist/tests/providers/RowsProvider.test.js +84 -0
- package/dist/tests/providers/WizardConfigProvider.test.d.ts +1 -0
- package/dist/tests/providers/WizardConfigProvider.test.js +36 -0
- package/dist/tests/setupTests.d.ts +1 -0
- package/dist/tests/setupTests.js +15 -0
- package/dist/tests/testUtils.d.ts +33 -0
- package/dist/tests/testUtils.js +196 -0
- package/dist/tests/utils/latexUtils.test.d.ts +1 -0
- package/dist/tests/utils/latexUtils.test.js +62 -0
- package/dist/tests/utils/utils.test.d.ts +1 -0
- package/dist/tests/utils/utils.test.js +394 -0
- package/dist/utils/latexUtils.d.ts +13 -0
- package/dist/utils/latexUtils.js +86 -0
- package/dist/utils/utils.d.ts +1 -0
- package/dist/utils/utils.js +4 -1
- package/package.json +16 -7
- package/dist/components/NumStrippingLinesBadge.d.ts +0 -8
- package/dist/components/NumStrippingLinesBadge.js +0 -10
- package/dist/components/StrippingLineBadge.d.ts +0 -7
- package/dist/components/TupleToolGroup.d.ts +0 -5
- package/dist/components/TupleToolGroup.js +0 -22
- package/dist/components/TupleToolList.d.ts +0 -6
- package/dist/components/TupleToolList.js +0 -42
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
|
+
import { fireEvent, render, screen } from "@testing-library/react";
|
|
4
|
+
import { Accordion } from "react-bootstrap";
|
|
5
|
+
import { TupleToolDocsAccordion } from "../../components/TupleToolDocsAccordion";
|
|
6
|
+
import { TupleToolsAccordion } from "../../components/TupleToolsAccordion";
|
|
7
|
+
import { TupleToolGroupsAccordion } from "../../components/TupleToolGroupsAccordion";
|
|
8
|
+
import { ParticleLatex } from "../../components/ParticleLatex";
|
|
9
|
+
import { createMockDtt, mockDecay, mockMetadata } from "../testUtils";
|
|
10
|
+
import { TupleTool } from "../../models/tupleTool";
|
|
11
|
+
// Helper: DTT with zero global tools (branches still present)
|
|
12
|
+
function createEmptyDtt() {
|
|
13
|
+
const dtt = createMockDtt("EmptyTree");
|
|
14
|
+
// Remove all 5 default tools
|
|
15
|
+
return dtt
|
|
16
|
+
.withRemovedTool(new TupleTool("TupleToolKinematic", ""))
|
|
17
|
+
.withRemovedTool(new TupleTool("TupleToolPid", ""))
|
|
18
|
+
.withRemovedTool(new TupleTool("TupleToolANNPID", ""))
|
|
19
|
+
.withRemovedTool(new TupleTool("TupleToolGeometry", ""))
|
|
20
|
+
.withRemovedTool(new TupleTool("TupleToolEventInfo", ""));
|
|
21
|
+
}
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// Shared mocks
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
let currentMockMetadata = mockMetadata;
|
|
26
|
+
vi.mock("../../providers/MetadataProvider", () => ({
|
|
27
|
+
useMetadata: () => currentMockMetadata,
|
|
28
|
+
}));
|
|
29
|
+
vi.mock("../../components/modals/ConfigureTupleToolModal", () => ({
|
|
30
|
+
ConfigureTupleToolModal: ({ onClose }) => (_jsx("div", { "data-testid": "config-modal", children: _jsx("button", { onClick: onClose, children: "CloseConfig" }) })),
|
|
31
|
+
}));
|
|
32
|
+
vi.mock("../../components/AddTupleToolButton", () => ({
|
|
33
|
+
AddTupleToolButton: () => _jsx("button", { "data-testid": "add-tool-button", children: "Add tool" }),
|
|
34
|
+
}));
|
|
35
|
+
vi.mock("../../components/DeleteButton", () => ({
|
|
36
|
+
DeleteButton: ({ action }) => (_jsx("button", { "data-testid": "delete-tool-button", onClick: action, children: "Delete" })),
|
|
37
|
+
}));
|
|
38
|
+
// Mutable dtt state for DttProvider mock
|
|
39
|
+
let mockDtt = createMockDtt("MyTree");
|
|
40
|
+
let mockSelectedBranch = [];
|
|
41
|
+
let mockHoveredBranch = [];
|
|
42
|
+
const mockSetSelectedBranch = vi.fn((b) => {
|
|
43
|
+
mockSelectedBranch = b;
|
|
44
|
+
});
|
|
45
|
+
const mockSetHoveredBranch = vi.fn((b) => {
|
|
46
|
+
mockHoveredBranch = b;
|
|
47
|
+
});
|
|
48
|
+
const mockRemoveTool = vi.fn();
|
|
49
|
+
vi.mock("../../providers/DttProvider", () => ({
|
|
50
|
+
useDtt: () => ({
|
|
51
|
+
dtt: mockDtt,
|
|
52
|
+
decay: mockDecay,
|
|
53
|
+
selectedBranch: mockSelectedBranch,
|
|
54
|
+
setSelectedBranch: mockSetSelectedBranch,
|
|
55
|
+
hoveredBranch: mockHoveredBranch,
|
|
56
|
+
setHoveredBranch: mockSetHoveredBranch,
|
|
57
|
+
highlightedBranch: mockSelectedBranch,
|
|
58
|
+
addTool: vi.fn(),
|
|
59
|
+
removeTool: mockRemoveTool,
|
|
60
|
+
updateToolParam: vi.fn(),
|
|
61
|
+
}),
|
|
62
|
+
}));
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
// TupleToolDocsAccordion
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
describe("TupleToolDocsAccordion", () => {
|
|
67
|
+
it("renders Documentation header for the given tool class", () => {
|
|
68
|
+
render(_jsx(TupleToolDocsAccordion, { toolClass: "TupleToolKinematic" }));
|
|
69
|
+
expect(screen.getByText(/documentation for tupleToolKinematic/i)).toBeInTheDocument();
|
|
70
|
+
});
|
|
71
|
+
it("renders 'No documentation available' when documentation is empty", () => {
|
|
72
|
+
render(_jsx(TupleToolDocsAccordion, { toolClass: "TupleToolKinematic" }));
|
|
73
|
+
// TupleToolKinematic has no documentation in mockMetadata
|
|
74
|
+
expect(screen.getByText("No documentation available.")).toBeInTheDocument();
|
|
75
|
+
});
|
|
76
|
+
it("renders documentation HTML when the tool has documentation", () => {
|
|
77
|
+
const metaWithDocs = {
|
|
78
|
+
...mockMetadata,
|
|
79
|
+
tupleTools: {
|
|
80
|
+
...mockMetadata.tupleTools,
|
|
81
|
+
tupleTools: {
|
|
82
|
+
...mockMetadata.tupleTools.tupleTools,
|
|
83
|
+
TupleToolKinematic: {
|
|
84
|
+
...mockMetadata.tupleTools.tupleTools.TupleToolKinematic,
|
|
85
|
+
documentation: "<p>Detailed kinematic documentation</p>",
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
currentMockMetadata = metaWithDocs;
|
|
91
|
+
render(_jsx(TupleToolDocsAccordion, { toolClass: "TupleToolKinematic" }));
|
|
92
|
+
expect(screen.getByText("Detailed kinematic documentation")).toBeInTheDocument();
|
|
93
|
+
currentMockMetadata = mockMetadata;
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
// ParticleLatex
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
describe("ParticleLatex", () => {
|
|
100
|
+
beforeEach(() => {
|
|
101
|
+
mockDtt = createMockDtt("MyTree");
|
|
102
|
+
});
|
|
103
|
+
it("renders an InlineMath element", () => {
|
|
104
|
+
render(_jsx(ParticleLatex, { particleId: "head" }));
|
|
105
|
+
expect(screen.getByTestId("inline-math")).toBeInTheDocument();
|
|
106
|
+
});
|
|
107
|
+
it("renders the particle latex when highlighted=true", () => {
|
|
108
|
+
render(_jsx(ParticleLatex, { particleId: "head", highlighted: true }));
|
|
109
|
+
expect(screen.getByTestId("inline-math")).toBeInTheDocument();
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
// TupleToolsAccordion
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
describe("TupleToolsAccordion — no tools", () => {
|
|
116
|
+
beforeEach(() => {
|
|
117
|
+
mockDtt = createEmptyDtt();
|
|
118
|
+
mockSelectedBranch = [];
|
|
119
|
+
mockHoveredBranch = [];
|
|
120
|
+
});
|
|
121
|
+
it("renders the accordion header containing tool count", () => {
|
|
122
|
+
const { container } = render(_jsx("div", { children: _jsx(TupleToolsAccordion, {}) }));
|
|
123
|
+
const btn = container.querySelector("button.accordion-button");
|
|
124
|
+
expect(btn?.textContent).toMatch(/0 TupleTools/);
|
|
125
|
+
});
|
|
126
|
+
it("renders 'No TupleTools have been added' message", () => {
|
|
127
|
+
render(_jsx("div", { children: _jsx(TupleToolsAccordion, {}) }));
|
|
128
|
+
expect(screen.getByText(/no tupletools have been added/i)).toBeInTheDocument();
|
|
129
|
+
});
|
|
130
|
+
it("renders the Add tool button", () => {
|
|
131
|
+
render(_jsx("div", { children: _jsx(TupleToolsAccordion, {}) }));
|
|
132
|
+
expect(screen.getByTestId("add-tool-button")).toBeInTheDocument();
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
describe("TupleToolsAccordion — with selected group branch", () => {
|
|
136
|
+
beforeEach(() => {
|
|
137
|
+
mockDtt = createMockDtt("MyTree");
|
|
138
|
+
mockSelectedBranch = ["head", "k"];
|
|
139
|
+
mockHoveredBranch = [];
|
|
140
|
+
});
|
|
141
|
+
it("shows '(group)' in the header when multiple branches are selected", () => {
|
|
142
|
+
const { container } = render(_jsx("div", { children: _jsx(TupleToolsAccordion, {}) }));
|
|
143
|
+
const btn = container.querySelector("button.accordion-button");
|
|
144
|
+
expect(btn?.textContent).toMatch(/\(group\)/);
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
describe("TupleToolsAccordion — with tools", () => {
|
|
148
|
+
beforeEach(() => {
|
|
149
|
+
mockDtt = createMockDtt("MyTree").withAddedTool(new TupleTool("TupleToolKinematic", ""));
|
|
150
|
+
mockSelectedBranch = [];
|
|
151
|
+
mockHoveredBranch = [];
|
|
152
|
+
});
|
|
153
|
+
it("renders the tool name", () => {
|
|
154
|
+
render(_jsx("div", { children: _jsx(TupleToolsAccordion, {}) }));
|
|
155
|
+
expect(screen.getByText("TupleToolKinematic")).toBeInTheDocument();
|
|
156
|
+
});
|
|
157
|
+
it("shows configure modal when configure button is clicked", () => {
|
|
158
|
+
const { container } = render(_jsx("div", { children: _jsx(TupleToolsAccordion, {}) }));
|
|
159
|
+
// Find the outline-secondary button (configure), not the delete button
|
|
160
|
+
const configBtn = container.querySelector(".btn-outline-secondary");
|
|
161
|
+
if (configBtn)
|
|
162
|
+
fireEvent.click(configBtn);
|
|
163
|
+
expect(screen.queryByTestId("config-modal")).toBeInTheDocument();
|
|
164
|
+
});
|
|
165
|
+
it("calls removeTool when delete button is clicked", () => {
|
|
166
|
+
render(_jsx("div", { children: _jsx(TupleToolsAccordion, {}) }));
|
|
167
|
+
fireEvent.click(screen.getAllByTestId("delete-tool-button")[0]);
|
|
168
|
+
expect(mockRemoveTool).toHaveBeenCalled();
|
|
169
|
+
});
|
|
170
|
+
it("shows configure modal when configure button is clicked (Accordion open)", () => {
|
|
171
|
+
const { container } = render(_jsx(Accordion, { defaultActiveKey: "0", children: _jsx(TupleToolsAccordion, {}) }));
|
|
172
|
+
const configBtn = container.querySelector(".btn-outline-secondary");
|
|
173
|
+
expect(configBtn).not.toBeNull();
|
|
174
|
+
fireEvent.click(configBtn);
|
|
175
|
+
expect(screen.getByTestId("config-modal")).toBeInTheDocument();
|
|
176
|
+
// Also test that onClose hides the modal
|
|
177
|
+
fireEvent.click(screen.getByText("CloseConfig"));
|
|
178
|
+
expect(screen.queryByTestId("config-modal")).not.toBeInTheDocument();
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
// ---------------------------------------------------------------------------
|
|
182
|
+
// TupleToolGroupsAccordion
|
|
183
|
+
// ---------------------------------------------------------------------------
|
|
184
|
+
describe("TupleToolGroupsAccordion — no groups", () => {
|
|
185
|
+
beforeEach(() => {
|
|
186
|
+
mockDtt = createEmptyDtt();
|
|
187
|
+
mockSelectedBranch = [];
|
|
188
|
+
mockHoveredBranch = [];
|
|
189
|
+
});
|
|
190
|
+
it("renders header containing group count", () => {
|
|
191
|
+
const { container } = render(_jsx("div", { children: _jsx(TupleToolGroupsAccordion, { setActiveKey: vi.fn() }) }));
|
|
192
|
+
const btn = container.querySelector("button.accordion-button");
|
|
193
|
+
expect(btn?.textContent).toMatch(/0 groups/);
|
|
194
|
+
});
|
|
195
|
+
it("shows message when no groups exist and no branch is selected", () => {
|
|
196
|
+
render(_jsx("div", { children: _jsx(TupleToolGroupsAccordion, { setActiveKey: vi.fn() }) }));
|
|
197
|
+
expect(screen.getByText(/no groups have been created yet/i)).toBeInTheDocument();
|
|
198
|
+
});
|
|
199
|
+
it("shows 'Select multiple particles' when exactly 1 branch is selected", () => {
|
|
200
|
+
mockSelectedBranch = ["head"];
|
|
201
|
+
render(_jsx("div", { children: _jsx(TupleToolGroupsAccordion, { setActiveKey: vi.fn() }) }));
|
|
202
|
+
expect(screen.getByText(/select multiple particles/i)).toBeInTheDocument();
|
|
203
|
+
});
|
|
204
|
+
it("shows AddTupleToolButton when multiple branches are selected but no group exists", () => {
|
|
205
|
+
mockSelectedBranch = ["head", "k"];
|
|
206
|
+
render(_jsx("div", { children: _jsx(TupleToolGroupsAccordion, { setActiveKey: vi.fn() }) }));
|
|
207
|
+
expect(screen.getByTestId("add-tool-button")).toBeInTheDocument();
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
describe("TupleToolGroupsAccordion — with groups", () => {
|
|
211
|
+
beforeEach(() => {
|
|
212
|
+
// Create a DTT with a group by adding a tool to a multi-branch selection
|
|
213
|
+
mockDtt = createEmptyDtt().withAddedTool(new TupleTool("TupleToolKinematic", ""), ["head", "k"]);
|
|
214
|
+
mockSelectedBranch = [];
|
|
215
|
+
mockHoveredBranch = [];
|
|
216
|
+
});
|
|
217
|
+
it("renders '1 group' in the header", () => {
|
|
218
|
+
const { container } = render(_jsx("div", { children: _jsx(TupleToolGroupsAccordion, { setActiveKey: vi.fn() }) }));
|
|
219
|
+
const btn = container.querySelector("button.accordion-button");
|
|
220
|
+
expect(btn?.textContent).toMatch(/1 group/);
|
|
221
|
+
});
|
|
222
|
+
it("renders the group entry in the list", () => {
|
|
223
|
+
render(_jsx("div", { children: _jsx(TupleToolGroupsAccordion, { setActiveKey: vi.fn() }) }));
|
|
224
|
+
// The group list item should be present (rendered by ParticleLatex → InlineMath)
|
|
225
|
+
expect(screen.getAllByTestId("inline-math").length).toBeGreaterThan(0);
|
|
226
|
+
});
|
|
227
|
+
it("calls setActiveKey and setSelectedBranch when a group is clicked", () => {
|
|
228
|
+
const mockSetActiveKey = vi.fn();
|
|
229
|
+
const { container } = render(_jsx("div", { children: _jsx(TupleToolGroupsAccordion, { setActiveKey: mockSetActiveKey }) }));
|
|
230
|
+
// Click the list item
|
|
231
|
+
const listItem = container.querySelector(".list-group-item");
|
|
232
|
+
if (listItem)
|
|
233
|
+
fireEvent.click(listItem);
|
|
234
|
+
expect(mockSetActiveKey).toHaveBeenCalledWith("0");
|
|
235
|
+
expect(mockSetSelectedBranch).toHaveBeenCalled();
|
|
236
|
+
});
|
|
237
|
+
it("applies hover background color when hoveredBranch matches the group", () => {
|
|
238
|
+
mockHoveredBranch = ["head", "k"];
|
|
239
|
+
const { container } = render(_jsx("div", { children: _jsx(TupleToolGroupsAccordion, { setActiveKey: vi.fn() }) }));
|
|
240
|
+
const listItem = container.querySelector(".list-group-item");
|
|
241
|
+
expect(listItem?.style.backgroundColor).toBe("var(--bs-gray-100)");
|
|
242
|
+
});
|
|
243
|
+
it("calls setHoveredBranch when mouseEnter on group item", () => {
|
|
244
|
+
const { container } = render(_jsx("div", { children: _jsx(TupleToolGroupsAccordion, { setActiveKey: vi.fn() }) }));
|
|
245
|
+
const listItem = container.querySelector(".list-group-item");
|
|
246
|
+
if (listItem)
|
|
247
|
+
fireEvent.mouseEnter(listItem);
|
|
248
|
+
expect(mockSetHoveredBranch).toHaveBeenCalled();
|
|
249
|
+
});
|
|
250
|
+
it("calls setHoveredBranch([]) when mouseLeave on group item", () => {
|
|
251
|
+
const { container } = render(_jsx("div", { children: _jsx(TupleToolGroupsAccordion, { setActiveKey: vi.fn() }) }));
|
|
252
|
+
const listItem = container.querySelector(".list-group-item");
|
|
253
|
+
if (listItem)
|
|
254
|
+
fireEvent.mouseLeave(listItem);
|
|
255
|
+
expect(mockSetHoveredBranch).toHaveBeenCalledWith([]);
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
describe("TupleToolGroupsAccordion — new group selection (lines 76-104)", () => {
|
|
259
|
+
beforeEach(() => {
|
|
260
|
+
// DTT with a group at "head,k", but selectedBranch is a different pair "mup,mum"
|
|
261
|
+
mockDtt = createEmptyDtt().withAddedTool(new TupleTool("TupleToolKinematic", ""), ["head", "k"]);
|
|
262
|
+
mockSelectedBranch = ["mup", "mum"];
|
|
263
|
+
mockHoveredBranch = [];
|
|
264
|
+
});
|
|
265
|
+
it("shows a new group item when selectedBranch is a multi-selection not yet a group", () => {
|
|
266
|
+
const { container } = render(_jsx("div", { children: _jsx(TupleToolGroupsAccordion, { setActiveKey: vi.fn() }) }));
|
|
267
|
+
// Should have 2 list items: the existing group + the new selection
|
|
268
|
+
const listItems = container.querySelectorAll(".list-group-item");
|
|
269
|
+
expect(listItems.length).toBe(2);
|
|
270
|
+
});
|
|
271
|
+
it("the new group item shows a PlusLg (success) button", () => {
|
|
272
|
+
const { container } = render(_jsx("div", { children: _jsx(TupleToolGroupsAccordion, { setActiveKey: vi.fn() }) }));
|
|
273
|
+
// The success button is the 'create group' button on the new selection item
|
|
274
|
+
const successBtn = container.querySelector(".btn-success");
|
|
275
|
+
expect(successBtn).toBeInTheDocument();
|
|
276
|
+
});
|
|
277
|
+
it("clicking the new group item calls setActiveKey and setSelectedBranch", () => {
|
|
278
|
+
const mockSetActiveKey = vi.fn();
|
|
279
|
+
const { container } = render(_jsx("div", { children: _jsx(TupleToolGroupsAccordion, { setActiveKey: mockSetActiveKey }) }));
|
|
280
|
+
const listItems = container.querySelectorAll(".list-group-item");
|
|
281
|
+
// The second list item is the new selection
|
|
282
|
+
if (listItems[1])
|
|
283
|
+
fireEvent.click(listItems[1]);
|
|
284
|
+
expect(mockSetActiveKey).toHaveBeenCalledWith("0");
|
|
285
|
+
expect(mockSetSelectedBranch).toHaveBeenCalled();
|
|
286
|
+
});
|
|
287
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,96 @@
|
|
|
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 { ProductionNameInput } from "../../components/ProductionNameInput";
|
|
5
|
+
import { RequestEmailInput } from "../../components/RequestEmailInput";
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// Shared mock state for RequestProvider
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
const mockSetProductionName = vi.fn();
|
|
10
|
+
const mockSetContactEmails = vi.fn();
|
|
11
|
+
let mockProductionName = "";
|
|
12
|
+
let mockContactEmails = [];
|
|
13
|
+
let mockShowErrors = false;
|
|
14
|
+
let mockIsEmailValid = true;
|
|
15
|
+
vi.mock("../../providers/RequestProvider", () => ({
|
|
16
|
+
useRequest: () => ({
|
|
17
|
+
productionName: mockProductionName,
|
|
18
|
+
setProductionName: mockSetProductionName,
|
|
19
|
+
contactEmails: mockContactEmails,
|
|
20
|
+
setContactEmails: mockSetContactEmails,
|
|
21
|
+
showErrors: mockShowErrors,
|
|
22
|
+
validation: { isEmailValid: mockIsEmailValid },
|
|
23
|
+
}),
|
|
24
|
+
}));
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// ProductionNameInput
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
describe("ProductionNameInput", () => {
|
|
29
|
+
it("renders an input with the current production name", () => {
|
|
30
|
+
mockProductionName = "MyAnalysis";
|
|
31
|
+
render(_jsx(ProductionNameInput, {}));
|
|
32
|
+
expect(screen.getByDisplayValue("MyAnalysis")).toBeInTheDocument();
|
|
33
|
+
});
|
|
34
|
+
it("renders placeholder when production name is empty", () => {
|
|
35
|
+
mockProductionName = "";
|
|
36
|
+
render(_jsx(ProductionNameInput, {}));
|
|
37
|
+
expect(screen.getByPlaceholderText("MyAnalysis")).toBeInTheDocument();
|
|
38
|
+
});
|
|
39
|
+
it("calls setProductionName when the user types (sanitizing special chars)", () => {
|
|
40
|
+
mockProductionName = "";
|
|
41
|
+
render(_jsx(ProductionNameInput, {}));
|
|
42
|
+
fireEvent.change(screen.getByRole("textbox"), { target: { value: "My Analysis!" } });
|
|
43
|
+
// Non-word characters are stripped by the onChange handler
|
|
44
|
+
expect(mockSetProductionName).toHaveBeenCalledWith("MyAnalysis");
|
|
45
|
+
});
|
|
46
|
+
it("shows validation error when showErrors is true and name is empty", () => {
|
|
47
|
+
mockProductionName = "";
|
|
48
|
+
mockShowErrors = true;
|
|
49
|
+
render(_jsx(ProductionNameInput, {}));
|
|
50
|
+
expect(screen.getByText(/please enter a production name/i)).toBeInTheDocument();
|
|
51
|
+
mockShowErrors = false;
|
|
52
|
+
});
|
|
53
|
+
it("does not mark input as invalid when showErrors is false", () => {
|
|
54
|
+
mockProductionName = "";
|
|
55
|
+
mockShowErrors = false;
|
|
56
|
+
render(_jsx(ProductionNameInput, {}));
|
|
57
|
+
expect(screen.getByRole("textbox")).not.toHaveClass("is-invalid");
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
// RequestEmailInput
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
describe("RequestEmailInput", () => {
|
|
64
|
+
it("renders the current emails joined by ', '", () => {
|
|
65
|
+
mockContactEmails = ["a@b.com", "c@d.com"];
|
|
66
|
+
render(_jsx(RequestEmailInput, {}));
|
|
67
|
+
expect(screen.getByDisplayValue("a@b.com, c@d.com")).toBeInTheDocument();
|
|
68
|
+
});
|
|
69
|
+
it("renders placeholder when no emails", () => {
|
|
70
|
+
mockContactEmails = [];
|
|
71
|
+
render(_jsx(RequestEmailInput, {}));
|
|
72
|
+
expect(screen.getByPlaceholderText("name@example.com")).toBeInTheDocument();
|
|
73
|
+
});
|
|
74
|
+
it("calls setContactEmails with split values on change", () => {
|
|
75
|
+
mockContactEmails = [];
|
|
76
|
+
render(_jsx(RequestEmailInput, {}));
|
|
77
|
+
fireEvent.change(screen.getByRole("textbox"), { target: { value: "a@b.com, c@d.com" } });
|
|
78
|
+
expect(mockSetContactEmails).toHaveBeenCalledWith(["a@b.com", "c@d.com"]);
|
|
79
|
+
});
|
|
80
|
+
it("shows validation error when showErrors=true and email is invalid", () => {
|
|
81
|
+
mockContactEmails = ["notvalid"];
|
|
82
|
+
mockShowErrors = true;
|
|
83
|
+
mockIsEmailValid = false;
|
|
84
|
+
render(_jsx(RequestEmailInput, {}));
|
|
85
|
+
expect(screen.getByText(/please enter valid email addresses/i)).toBeInTheDocument();
|
|
86
|
+
mockShowErrors = false;
|
|
87
|
+
mockIsEmailValid = true;
|
|
88
|
+
});
|
|
89
|
+
it("does not mark input as invalid when email is valid", () => {
|
|
90
|
+
mockContactEmails = ["a@b.com"];
|
|
91
|
+
mockShowErrors = false;
|
|
92
|
+
mockIsEmailValid = true;
|
|
93
|
+
render(_jsx(RequestEmailInput, {}));
|
|
94
|
+
expect(screen.getByRole("textbox")).not.toHaveClass("is-invalid");
|
|
95
|
+
});
|
|
96
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,137 @@
|
|
|
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 { TupleToolLabel } from "../../components/TupleToolLabel";
|
|
5
|
+
import { DecayTagBadge } from "../../components/DecayTagBadge";
|
|
6
|
+
import { ParticleTagBadge } from "../../components/ParticleTagBadge";
|
|
7
|
+
import { StrippingLineVersionBadge } from "../../components/StrippingLineVersionBadge";
|
|
8
|
+
import { StrippingLineInfo } from "../../components/StrippingLineInfo";
|
|
9
|
+
import { StrippingLineInfoButton } from "../../components/StrippingLineInfoButton";
|
|
10
|
+
import { mockStrippingLine, mockStrippingLinePrescaled } from "../testUtils";
|
|
11
|
+
import { TupleTool } from "../../models/tupleTool";
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// TupleToolLabel
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
describe("TupleToolLabel", () => {
|
|
16
|
+
it("renders the tool class name", () => {
|
|
17
|
+
render(_jsx(TupleToolLabel, { tool: new TupleTool("TupleToolKinematic", "") }));
|
|
18
|
+
expect(screen.getByText("TupleToolKinematic")).toBeInTheDocument();
|
|
19
|
+
});
|
|
20
|
+
it("renders the tool instance name when set", () => {
|
|
21
|
+
render(_jsx(TupleToolLabel, { tool: new TupleTool("TupleToolKinematic", "myKin") }));
|
|
22
|
+
expect(screen.getByText("myKin")).toBeInTheDocument();
|
|
23
|
+
});
|
|
24
|
+
it("renders an empty name slot when name is not set", () => {
|
|
25
|
+
const { container } = render(_jsx(TupleToolLabel, { tool: new TupleTool("TupleToolKinematic", "") }));
|
|
26
|
+
const nameParagraph = container.querySelector("p.text-muted");
|
|
27
|
+
expect(nameParagraph?.textContent).toBe("");
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
// DecayTagBadge
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
describe("DecayTagBadge", () => {
|
|
34
|
+
it("renders the tag label text", () => {
|
|
35
|
+
render(_jsx(DecayTagBadge, { tag: "B2OC" }));
|
|
36
|
+
expect(screen.getByText("B2OC")).toBeInTheDocument();
|
|
37
|
+
});
|
|
38
|
+
it("uses warning background for non-warn tags", () => {
|
|
39
|
+
render(_jsx(DecayTagBadge, { tag: "B2OC" }));
|
|
40
|
+
// B2OC has warn: false → bg-warning
|
|
41
|
+
expect(screen.getByText("B2OC")).toHaveClass("bg-warning");
|
|
42
|
+
});
|
|
43
|
+
it("uses danger background for warn tags", () => {
|
|
44
|
+
render(_jsx(DecayTagBadge, { tag: "SLOW" }));
|
|
45
|
+
// SLOW has warn: true → bg-danger
|
|
46
|
+
expect(screen.getByText("SLOW")).toHaveClass("bg-danger");
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
// ParticleTagBadge
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
describe("ParticleTagBadge", () => {
|
|
53
|
+
it("renders the tag label", () => {
|
|
54
|
+
render(_jsx(ParticleTagBadge, { tag: { label: "muon", group: "category" } }));
|
|
55
|
+
expect(screen.getByText("muon")).toBeInTheDocument();
|
|
56
|
+
});
|
|
57
|
+
it("is fully opaque when enabled", () => {
|
|
58
|
+
const { container } = render(_jsx(ParticleTagBadge, { tag: { label: "muon", group: "category" }, enabled: true }));
|
|
59
|
+
const badge = container.querySelector(".badge");
|
|
60
|
+
expect(badge.style.opacity).toBe("1");
|
|
61
|
+
});
|
|
62
|
+
it("is dimmed when disabled", () => {
|
|
63
|
+
const { container } = render(_jsx(ParticleTagBadge, { tag: { label: "muon", group: "category" }, enabled: false }));
|
|
64
|
+
const badge = container.querySelector(".badge");
|
|
65
|
+
expect(badge.style.opacity).toBe("0.15");
|
|
66
|
+
});
|
|
67
|
+
it("calls onClick when clicked", () => {
|
|
68
|
+
const onClick = vi.fn();
|
|
69
|
+
render(_jsx(ParticleTagBadge, { tag: { label: "kaon", group: "flavour" }, onClick: onClick }));
|
|
70
|
+
screen.getByText("kaon").click();
|
|
71
|
+
expect(onClick).toHaveBeenCalledOnce();
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
// StrippingLineVersionBadge
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
describe("StrippingLineVersionBadge", () => {
|
|
78
|
+
it("renders the version number prefixed with S", () => {
|
|
79
|
+
render(_jsx(StrippingLineVersionBadge, { version: "28r2" }));
|
|
80
|
+
expect(screen.getByText("S28r2")).toBeInTheDocument();
|
|
81
|
+
});
|
|
82
|
+
it("renders without a link when showLink is false", () => {
|
|
83
|
+
const { container } = render(_jsx(StrippingLineVersionBadge, { version: "28r2", showLink: false }));
|
|
84
|
+
expect(container.querySelector("a")).toBeNull();
|
|
85
|
+
});
|
|
86
|
+
it("renders a link when showLink is true and line is provided", () => {
|
|
87
|
+
const { container } = render(_jsx(StrippingLineVersionBadge, { version: "28r2", line: mockStrippingLine, showLink: true }));
|
|
88
|
+
expect(container.querySelector("a")).not.toBeNull();
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
// StrippingLineInfo
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
describe("StrippingLineInfo", () => {
|
|
95
|
+
it("renders the line name in compact mode", () => {
|
|
96
|
+
render(_jsx(StrippingLineInfo, { line: mockStrippingLine }));
|
|
97
|
+
expect(screen.getByText(mockStrippingLine.line)).toBeInTheDocument();
|
|
98
|
+
});
|
|
99
|
+
it("renders version badges in compact mode", () => {
|
|
100
|
+
render(_jsx(StrippingLineInfo, { line: mockStrippingLine }));
|
|
101
|
+
expect(screen.getByText("S28r2")).toBeInTheDocument();
|
|
102
|
+
});
|
|
103
|
+
it("renders verbose information when verbose=true", () => {
|
|
104
|
+
render(_jsx(StrippingLineInfo, { line: mockStrippingLine, verbose: true }));
|
|
105
|
+
expect(screen.getByText(/stream/i, { exact: false })).toBeInTheDocument();
|
|
106
|
+
expect(screen.getByText("Leptonic")).toBeInTheDocument();
|
|
107
|
+
});
|
|
108
|
+
it("shows DaVinci version hint in verbose mode", () => {
|
|
109
|
+
render(_jsx(StrippingLineInfo, { line: mockStrippingLine, verbose: true }));
|
|
110
|
+
expect(screen.getByText(/DaVinci version/i, { exact: false })).toBeInTheDocument();
|
|
111
|
+
});
|
|
112
|
+
it("shows stripping description in verbose mode", () => {
|
|
113
|
+
render(_jsx(StrippingLineInfo, { line: mockStrippingLine, verbose: true }));
|
|
114
|
+
expect(screen.getByText(/Stripping 28r2/i, { exact: false })).toBeInTheDocument();
|
|
115
|
+
});
|
|
116
|
+
it("shows a prescale warning when prescale is not 1", () => {
|
|
117
|
+
render(_jsx(StrippingLineInfo, { line: mockStrippingLinePrescaled, verbose: true }));
|
|
118
|
+
expect(screen.getByText(/Prescale: 0\.5/)).toBeInTheDocument();
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
// StrippingLineInfoButton
|
|
123
|
+
// ---------------------------------------------------------------------------
|
|
124
|
+
describe("StrippingLineInfoButton", () => {
|
|
125
|
+
it("renders a button", () => {
|
|
126
|
+
render(_jsx(StrippingLineInfoButton, { line: null }));
|
|
127
|
+
expect(screen.getByRole("button")).toBeInTheDocument();
|
|
128
|
+
});
|
|
129
|
+
it("is disabled when line is null", () => {
|
|
130
|
+
render(_jsx(StrippingLineInfoButton, { line: null }));
|
|
131
|
+
expect(screen.getByRole("button")).toBeDisabled();
|
|
132
|
+
});
|
|
133
|
+
it("is enabled when a line is provided", () => {
|
|
134
|
+
render(_jsx(StrippingLineInfoButton, { line: mockStrippingLine }));
|
|
135
|
+
expect(screen.getByRole("button")).not.toBeDisabled();
|
|
136
|
+
});
|
|
137
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|