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,129 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { describe, expect, it, vi, afterEach } from "vitest";
|
|
3
|
+
import { render, screen, waitFor } from "@testing-library/react";
|
|
4
|
+
import { renderHook } from "@testing-library/react";
|
|
5
|
+
import { MetadataProvider, useMetadata } from "../../providers/MetadataProvider";
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// Mock pako — inflate returns raw bytes of an empty JSON object so the
|
|
8
|
+
// provider can call JSON.parse without real gzip decompression.
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
const mockInflatedBytes = new TextEncoder().encode(JSON.stringify({}));
|
|
11
|
+
// Restore the real module — the global mock in mockSetup.tsx is overridden here
|
|
12
|
+
vi.mock("../../providers/MetadataProvider", async (importOriginal) => await importOriginal());
|
|
13
|
+
vi.mock("pako", () => ({
|
|
14
|
+
default: { inflate: vi.fn(() => mockInflatedBytes) },
|
|
15
|
+
}));
|
|
16
|
+
// Stub config so the provider only fetches one file — keeps tests fast.
|
|
17
|
+
vi.mock("../../config", () => ({
|
|
18
|
+
config: {
|
|
19
|
+
metadata_baseurl: "https://example.com/",
|
|
20
|
+
metadata_files: { particleProperties: "particles" },
|
|
21
|
+
batch_size: 10,
|
|
22
|
+
particleTagGroupStyles: {},
|
|
23
|
+
dttGraphOptions: {},
|
|
24
|
+
disabledTupleTools: [],
|
|
25
|
+
tupleToolValidation: {},
|
|
26
|
+
},
|
|
27
|
+
}));
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
// Helpers
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
const successResponse = {
|
|
32
|
+
ok: true,
|
|
33
|
+
arrayBuffer: () => Promise.resolve(new ArrayBuffer(0)),
|
|
34
|
+
};
|
|
35
|
+
function wrapper({ children }) {
|
|
36
|
+
return _jsx(MetadataProvider, { children: children });
|
|
37
|
+
}
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
// Tests — loading state
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
describe("MetadataProvider — loading state", () => {
|
|
42
|
+
afterEach(() => {
|
|
43
|
+
vi.restoreAllMocks();
|
|
44
|
+
});
|
|
45
|
+
it("shows a loading indicator while the fetch is pending", () => {
|
|
46
|
+
// fetch never resolves — component stays in loading state
|
|
47
|
+
vi.stubGlobal("fetch", vi.fn(() => new Promise(() => { })));
|
|
48
|
+
render(_jsx(MetadataProvider, { children: _jsx("div", { "data-testid": "children" }) }));
|
|
49
|
+
expect(screen.getByTestId("loading-indicator")).toBeInTheDocument();
|
|
50
|
+
expect(screen.queryByTestId("children")).not.toBeInTheDocument();
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
// Tests — success state
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
describe("MetadataProvider — success state", () => {
|
|
57
|
+
afterEach(() => {
|
|
58
|
+
vi.restoreAllMocks();
|
|
59
|
+
});
|
|
60
|
+
it("renders children once metadata has loaded", async () => {
|
|
61
|
+
vi.stubGlobal("fetch", vi.fn().mockResolvedValue(successResponse));
|
|
62
|
+
render(_jsx(MetadataProvider, { children: _jsx("div", { "data-testid": "children" }) }));
|
|
63
|
+
await waitFor(() => expect(screen.getByTestId("children")).toBeInTheDocument());
|
|
64
|
+
expect(screen.queryByTestId("loading-indicator")).not.toBeInTheDocument();
|
|
65
|
+
});
|
|
66
|
+
it("hides the loading indicator after metadata loads", async () => {
|
|
67
|
+
vi.stubGlobal("fetch", vi.fn().mockResolvedValue(successResponse));
|
|
68
|
+
render(_jsx(MetadataProvider, { children: _jsx("div", { "data-testid": "children" }) }));
|
|
69
|
+
await waitFor(() => screen.getByTestId("children"));
|
|
70
|
+
expect(screen.queryByTestId("loading-indicator")).not.toBeInTheDocument();
|
|
71
|
+
});
|
|
72
|
+
it("provides metadata context to children via useMetadata()", async () => {
|
|
73
|
+
vi.stubGlobal("fetch", vi.fn().mockResolvedValue(successResponse));
|
|
74
|
+
const { result } = renderHook(() => useMetadata(), { wrapper });
|
|
75
|
+
// Wait for metadata to load (hook result changes from throwing to returning)
|
|
76
|
+
await waitFor(() => expect(result.current).toBeTruthy());
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
// Tests — error state
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
describe("MetadataProvider — error state", () => {
|
|
83
|
+
afterEach(() => {
|
|
84
|
+
vi.restoreAllMocks();
|
|
85
|
+
});
|
|
86
|
+
it("shows an error message when the fetch rejects", async () => {
|
|
87
|
+
vi.stubGlobal("fetch", vi.fn().mockRejectedValue(new Error("Network error")));
|
|
88
|
+
render(_jsx(MetadataProvider, { children: _jsx("div", { "data-testid": "children" }) }));
|
|
89
|
+
await waitFor(() => expect(screen.getByText(/error/i)).toBeInTheDocument());
|
|
90
|
+
expect(screen.queryByTestId("children")).not.toBeInTheDocument();
|
|
91
|
+
expect(screen.queryByTestId("loading-indicator")).not.toBeInTheDocument();
|
|
92
|
+
});
|
|
93
|
+
it("does not render children when an error occurs", async () => {
|
|
94
|
+
vi.stubGlobal("fetch", vi.fn().mockRejectedValue(new Error("Network error")));
|
|
95
|
+
render(_jsx(MetadataProvider, { children: _jsx("div", { "data-testid": "children" }) }));
|
|
96
|
+
await waitFor(() => screen.getByText(/error/i));
|
|
97
|
+
expect(screen.queryByTestId("children")).not.toBeInTheDocument();
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
// ---------------------------------------------------------------------------
|
|
101
|
+
// Tests — useMetadata hook
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
describe("useMetadata()", () => {
|
|
104
|
+
it("throws when used outside a MetadataProvider", () => {
|
|
105
|
+
expect(() => renderHook(() => useMetadata())).toThrow("useMetadata must be used inside MetadataProvider");
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
// ---------------------------------------------------------------------------
|
|
109
|
+
// Tests — non-OK response (lines 73-75)
|
|
110
|
+
// ---------------------------------------------------------------------------
|
|
111
|
+
describe("MetadataProvider — non-OK fetch response", () => {
|
|
112
|
+
afterEach(() => {
|
|
113
|
+
vi.restoreAllMocks();
|
|
114
|
+
});
|
|
115
|
+
it("handles a non-OK response (e.g. 404) gracefully", async () => {
|
|
116
|
+
const notFoundResponse = {
|
|
117
|
+
ok: false,
|
|
118
|
+
status: 404,
|
|
119
|
+
arrayBuffer: () => Promise.resolve(new ArrayBuffer(0)),
|
|
120
|
+
};
|
|
121
|
+
vi.stubGlobal("fetch", vi.fn().mockResolvedValue(notFoundResponse));
|
|
122
|
+
const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => { });
|
|
123
|
+
render(_jsx(MetadataProvider, { children: _jsx("div", { "data-testid": "children" }) }));
|
|
124
|
+
// The provider should eventually show an error (loadDict returns null, setMetadata never fires)
|
|
125
|
+
// or the error boundary triggers; just verify the component doesn't throw
|
|
126
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
127
|
+
consoleSpy.mockRestore();
|
|
128
|
+
});
|
|
129
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,306 @@
|
|
|
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 { RequestProvider, useRequest } from "../../providers/RequestProvider";
|
|
5
|
+
import { createMockDtt, createMockRow, mockStrippingLine } from "../testUtils";
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// Mock RowsProvider — each test mutates these to set up scenarios
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
let mockRows = [createMockRow()];
|
|
10
|
+
let mockConfiguredRows = mockRows.filter((r) => r.dtt);
|
|
11
|
+
const mockSetRows = vi.fn();
|
|
12
|
+
vi.mock("../../providers/RowsProvider", () => ({
|
|
13
|
+
useRows: () => ({
|
|
14
|
+
rows: mockRows,
|
|
15
|
+
configuredRows: mockConfiguredRows,
|
|
16
|
+
setRows: mockSetRows,
|
|
17
|
+
updateRow: vi.fn(),
|
|
18
|
+
removeRow: vi.fn(),
|
|
19
|
+
generateAllFiles: vi.fn(),
|
|
20
|
+
}),
|
|
21
|
+
}));
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// Helpers
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
function makeWrapper(emailIsKnown = false) {
|
|
26
|
+
return function Wrapper({ children }) {
|
|
27
|
+
return _jsx(RequestProvider, { emailIsKnown: emailIsKnown, children: children });
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function renderProvider(emailIsKnown = false) {
|
|
31
|
+
return renderHook(() => useRequest(), { wrapper: makeWrapper(emailIsKnown) });
|
|
32
|
+
}
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// Validation: isEmptySession
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
describe("RequestProvider — isEmptySession", () => {
|
|
37
|
+
it("is true when no name, no emails and no rows", () => {
|
|
38
|
+
mockRows = [];
|
|
39
|
+
mockConfiguredRows = [];
|
|
40
|
+
const { result } = renderProvider();
|
|
41
|
+
expect(result.current.validation.isEmptySession).toBe(true);
|
|
42
|
+
});
|
|
43
|
+
it("is false when there is at least one row", () => {
|
|
44
|
+
mockRows = [createMockRow()];
|
|
45
|
+
mockConfiguredRows = [];
|
|
46
|
+
const { result } = renderProvider();
|
|
47
|
+
expect(result.current.validation.isEmptySession).toBe(false);
|
|
48
|
+
});
|
|
49
|
+
it("is false after a production name is entered", () => {
|
|
50
|
+
mockRows = [];
|
|
51
|
+
mockConfiguredRows = [];
|
|
52
|
+
const { result } = renderProvider();
|
|
53
|
+
act(() => result.current.setProductionName("TestProd"));
|
|
54
|
+
expect(result.current.validation.isEmptySession).toBe(false);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
// Validation: isEmailValid
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
describe("RequestProvider — isEmailValid", () => {
|
|
61
|
+
it("is false when contact emails list is empty", () => {
|
|
62
|
+
mockRows = [];
|
|
63
|
+
mockConfiguredRows = [];
|
|
64
|
+
const { result } = renderProvider();
|
|
65
|
+
expect(result.current.validation.isEmailValid).toBe(false);
|
|
66
|
+
});
|
|
67
|
+
it("is true for a valid email address", () => {
|
|
68
|
+
mockRows = [];
|
|
69
|
+
mockConfiguredRows = [];
|
|
70
|
+
const { result } = renderProvider();
|
|
71
|
+
act(() => result.current.setContactEmails(["user@cern.ch"]));
|
|
72
|
+
expect(result.current.validation.isEmailValid).toBe(true);
|
|
73
|
+
});
|
|
74
|
+
it("is false when any email is invalid", () => {
|
|
75
|
+
mockRows = [];
|
|
76
|
+
mockConfiguredRows = [];
|
|
77
|
+
const { result } = renderProvider();
|
|
78
|
+
act(() => result.current.setContactEmails(["user@cern.ch", "notvalid"]));
|
|
79
|
+
expect(result.current.validation.isEmailValid).toBe(false);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
// Validation: allRowsHaveDtt
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
describe("RequestProvider — allRowsHaveDtt", () => {
|
|
86
|
+
it("is false when no rows have a DTT", () => {
|
|
87
|
+
mockRows = [createMockRow({ dtt: null })];
|
|
88
|
+
mockConfiguredRows = [];
|
|
89
|
+
const { result } = renderProvider();
|
|
90
|
+
expect(result.current.validation.allRowsHaveDtt).toBe(false);
|
|
91
|
+
});
|
|
92
|
+
it("is true when all rows have a DTT", () => {
|
|
93
|
+
const row = createMockRow({ dtt: createMockDtt() });
|
|
94
|
+
mockRows = [row];
|
|
95
|
+
mockConfiguredRows = [row];
|
|
96
|
+
const { result } = renderProvider();
|
|
97
|
+
expect(result.current.validation.allRowsHaveDtt).toBe(true);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
// ---------------------------------------------------------------------------
|
|
101
|
+
// Validation: allRowsHaveStrippingLine
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
describe("RequestProvider — allRowsHaveStrippingLine", () => {
|
|
104
|
+
it("is false when a row has no stripping line", () => {
|
|
105
|
+
mockRows = [createMockRow({ line: null })];
|
|
106
|
+
mockConfiguredRows = [];
|
|
107
|
+
const { result } = renderProvider();
|
|
108
|
+
expect(result.current.validation.allRowsHaveStrippingLine).toBe(false);
|
|
109
|
+
});
|
|
110
|
+
it("is true when all rows have a stripping line", () => {
|
|
111
|
+
mockRows = [createMockRow({ line: mockStrippingLine })];
|
|
112
|
+
mockConfiguredRows = [];
|
|
113
|
+
const { result } = renderProvider();
|
|
114
|
+
expect(result.current.validation.allRowsHaveStrippingLine).toBe(true);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
118
|
+
// Validation: allRowsHavePaths
|
|
119
|
+
// ---------------------------------------------------------------------------
|
|
120
|
+
describe("RequestProvider — allRowsHavePaths", () => {
|
|
121
|
+
it("is false when a row has no paths", () => {
|
|
122
|
+
mockRows = [createMockRow({ paths: [] })];
|
|
123
|
+
mockConfiguredRows = [];
|
|
124
|
+
const { result } = renderProvider();
|
|
125
|
+
expect(result.current.validation.allRowsHavePaths).toBe(false);
|
|
126
|
+
});
|
|
127
|
+
it("is true when all rows have at least one path", () => {
|
|
128
|
+
mockRows = [createMockRow({ paths: ["/some/path/DIMUON.DST"] })];
|
|
129
|
+
mockConfiguredRows = [];
|
|
130
|
+
const { result } = renderProvider();
|
|
131
|
+
expect(result.current.validation.allRowsHavePaths).toBe(true);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
// ---------------------------------------------------------------------------
|
|
135
|
+
// Validation: validDttNames
|
|
136
|
+
// ---------------------------------------------------------------------------
|
|
137
|
+
describe("RequestProvider — validDttNames", () => {
|
|
138
|
+
it("is true when there are no configured rows (vacuous truth)", () => {
|
|
139
|
+
mockRows = [];
|
|
140
|
+
mockConfiguredRows = [];
|
|
141
|
+
const { result } = renderProvider();
|
|
142
|
+
expect(result.current.validation.validDttNames).toBe(true);
|
|
143
|
+
});
|
|
144
|
+
it("is true when all DTT names are unique and non-empty", () => {
|
|
145
|
+
const row1 = createMockRow({ id: 0, dtt: createMockDtt("TreeA") });
|
|
146
|
+
const row2 = createMockRow({ id: 1, dtt: createMockDtt("TreeB") });
|
|
147
|
+
mockRows = [row1, row2];
|
|
148
|
+
mockConfiguredRows = [
|
|
149
|
+
row1,
|
|
150
|
+
row2,
|
|
151
|
+
];
|
|
152
|
+
const { result } = renderProvider();
|
|
153
|
+
expect(result.current.validation.validDttNames).toBe(true);
|
|
154
|
+
});
|
|
155
|
+
it("is false when two rows have the same DTT name", () => {
|
|
156
|
+
const row1 = createMockRow({ id: 0, dtt: createMockDtt("SameName") });
|
|
157
|
+
const row2 = createMockRow({ id: 1, dtt: createMockDtt("SameName") });
|
|
158
|
+
mockRows = [row1, row2];
|
|
159
|
+
mockConfiguredRows = [
|
|
160
|
+
row1,
|
|
161
|
+
row2,
|
|
162
|
+
];
|
|
163
|
+
const { result } = renderProvider();
|
|
164
|
+
expect(result.current.validation.validDttNames).toBe(false);
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
// ---------------------------------------------------------------------------
|
|
168
|
+
// trySubmit / showErrors
|
|
169
|
+
// ---------------------------------------------------------------------------
|
|
170
|
+
describe("RequestProvider — trySubmit / showErrors", () => {
|
|
171
|
+
it("showErrors is false before trySubmit is called", () => {
|
|
172
|
+
mockRows = [];
|
|
173
|
+
mockConfiguredRows = [];
|
|
174
|
+
const { result } = renderProvider();
|
|
175
|
+
expect(result.current.showErrors).toBe(false);
|
|
176
|
+
});
|
|
177
|
+
it("showErrors becomes true after trySubmit when form is incomplete", () => {
|
|
178
|
+
mockRows = [];
|
|
179
|
+
mockConfiguredRows = [];
|
|
180
|
+
const { result } = renderProvider();
|
|
181
|
+
act(() => void result.current.trySubmit());
|
|
182
|
+
expect(result.current.showErrors).toBe(true);
|
|
183
|
+
});
|
|
184
|
+
it("trySubmit returns false when form is not ready to submit", () => {
|
|
185
|
+
mockRows = [];
|
|
186
|
+
mockConfiguredRows = [];
|
|
187
|
+
const { result } = renderProvider();
|
|
188
|
+
let canSubmit = true;
|
|
189
|
+
act(() => {
|
|
190
|
+
canSubmit = result.current.trySubmit();
|
|
191
|
+
});
|
|
192
|
+
expect(canSubmit).toBe(false);
|
|
193
|
+
});
|
|
194
|
+
it("trySubmit returns true when emailIsKnown=true and form is otherwise complete", () => {
|
|
195
|
+
const row = createMockRow({
|
|
196
|
+
dtt: createMockDtt("MyTree"),
|
|
197
|
+
line: mockStrippingLine,
|
|
198
|
+
paths: ["/some/path/DIMUON.DST"],
|
|
199
|
+
});
|
|
200
|
+
mockRows = [row];
|
|
201
|
+
mockConfiguredRows = [row];
|
|
202
|
+
const { result } = renderProvider(true /* emailIsKnown */);
|
|
203
|
+
act(() => result.current.setProductionName("MyProd"));
|
|
204
|
+
let canSubmit = false;
|
|
205
|
+
act(() => {
|
|
206
|
+
canSubmit = result.current.trySubmit();
|
|
207
|
+
});
|
|
208
|
+
expect(canSubmit).toBe(true);
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
// ---------------------------------------------------------------------------
|
|
212
|
+
// clearAll
|
|
213
|
+
// ---------------------------------------------------------------------------
|
|
214
|
+
describe("RequestProvider — clearAll", () => {
|
|
215
|
+
it("resets productionName to empty string", () => {
|
|
216
|
+
mockRows = [];
|
|
217
|
+
mockConfiguredRows = [];
|
|
218
|
+
const { result } = renderProvider();
|
|
219
|
+
act(() => result.current.setProductionName("TestProd"));
|
|
220
|
+
act(() => result.current.clearAll());
|
|
221
|
+
expect(result.current.productionName).toBe("");
|
|
222
|
+
});
|
|
223
|
+
it("resets contactEmails to empty array", () => {
|
|
224
|
+
mockRows = [];
|
|
225
|
+
mockConfiguredRows = [];
|
|
226
|
+
const { result } = renderProvider();
|
|
227
|
+
act(() => result.current.setContactEmails(["a@b.com"]));
|
|
228
|
+
act(() => result.current.clearAll());
|
|
229
|
+
expect(result.current.contactEmails).toEqual([]);
|
|
230
|
+
});
|
|
231
|
+
it("resets showErrors to false", () => {
|
|
232
|
+
mockRows = [];
|
|
233
|
+
mockConfiguredRows = [];
|
|
234
|
+
const { result } = renderProvider();
|
|
235
|
+
act(() => void result.current.trySubmit());
|
|
236
|
+
act(() => result.current.clearAll());
|
|
237
|
+
expect(result.current.showErrors).toBe(false);
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
// ---------------------------------------------------------------------------
|
|
241
|
+
// localStorage persistence
|
|
242
|
+
// ---------------------------------------------------------------------------
|
|
243
|
+
describe("RequestProvider — localStorage", () => {
|
|
244
|
+
it("persists productionName to localStorage on update", () => {
|
|
245
|
+
mockRows = [];
|
|
246
|
+
mockConfiguredRows = [];
|
|
247
|
+
const { result } = renderProvider();
|
|
248
|
+
act(() => result.current.setProductionName("Persisted"));
|
|
249
|
+
expect(localStorage.getItem("name")).toBe("Persisted");
|
|
250
|
+
});
|
|
251
|
+
it("persists contactEmails to localStorage on update", () => {
|
|
252
|
+
mockRows = [];
|
|
253
|
+
mockConfiguredRows = [];
|
|
254
|
+
const { result } = renderProvider();
|
|
255
|
+
act(() => result.current.setContactEmails(["x@y.com"]));
|
|
256
|
+
expect(localStorage.getItem("email")).toBe("x@y.com");
|
|
257
|
+
});
|
|
258
|
+
it("reads initial productionName from localStorage", () => {
|
|
259
|
+
localStorage.setItem("name", "FromStorage");
|
|
260
|
+
mockRows = [];
|
|
261
|
+
mockConfiguredRows = [];
|
|
262
|
+
const { result } = renderProvider();
|
|
263
|
+
expect(result.current.productionName).toBe("FromStorage");
|
|
264
|
+
});
|
|
265
|
+
it("persists reasonForRequest to localStorage on update", () => {
|
|
266
|
+
mockRows = [];
|
|
267
|
+
mockConfiguredRows = [];
|
|
268
|
+
const { result } = renderProvider();
|
|
269
|
+
act(() => result.current.setReasonForRequest("my reason"));
|
|
270
|
+
expect(localStorage.getItem("reasonForRequest")).toBe("my reason");
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
// ---------------------------------------------------------------------------
|
|
274
|
+
// reasonForRequest
|
|
275
|
+
// ---------------------------------------------------------------------------
|
|
276
|
+
describe("RequestProvider — reasonForRequest", () => {
|
|
277
|
+
it("starts with empty reasonForRequest", () => {
|
|
278
|
+
mockRows = [];
|
|
279
|
+
mockConfiguredRows = [];
|
|
280
|
+
const { result } = renderProvider();
|
|
281
|
+
expect(result.current.reasonForRequest).toBe("");
|
|
282
|
+
});
|
|
283
|
+
it("updates reasonForRequest via setReasonForRequest", () => {
|
|
284
|
+
mockRows = [];
|
|
285
|
+
mockConfiguredRows = [];
|
|
286
|
+
const { result } = renderProvider();
|
|
287
|
+
act(() => result.current.setReasonForRequest("I need these for research"));
|
|
288
|
+
expect(result.current.reasonForRequest).toBe("I need these for research");
|
|
289
|
+
});
|
|
290
|
+
it("resets reasonForRequest on clearAll", () => {
|
|
291
|
+
mockRows = [];
|
|
292
|
+
mockConfiguredRows = [];
|
|
293
|
+
const { result } = renderProvider();
|
|
294
|
+
act(() => result.current.setReasonForRequest("some reason"));
|
|
295
|
+
act(() => result.current.clearAll());
|
|
296
|
+
expect(result.current.reasonForRequest).toBe("");
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
// ---------------------------------------------------------------------------
|
|
300
|
+
// useRequest outside provider
|
|
301
|
+
// ---------------------------------------------------------------------------
|
|
302
|
+
describe("useRequest outside provider", () => {
|
|
303
|
+
it("throws when used outside a RequestProvider", () => {
|
|
304
|
+
expect(() => renderHook(() => useRequest())).toThrow("useRequest must be used within a RequestProvider");
|
|
305
|
+
});
|
|
306
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { describe, expect, it, vi } from "vitest";
|
|
3
|
+
import { renderHook } from "@testing-library/react";
|
|
4
|
+
import { RowProvider, useRow } from "../../providers/RowProvider";
|
|
5
|
+
import { createMockRow, mockStrippingLine } from "../testUtils";
|
|
6
|
+
const mockUpdateRow = vi.fn();
|
|
7
|
+
vi.mock("../../providers/RowsProvider", () => ({
|
|
8
|
+
useRows: () => ({
|
|
9
|
+
rows: [],
|
|
10
|
+
configuredRows: [],
|
|
11
|
+
setRows: vi.fn(),
|
|
12
|
+
updateRow: mockUpdateRow,
|
|
13
|
+
removeRow: vi.fn(),
|
|
14
|
+
generateAllFiles: vi.fn(),
|
|
15
|
+
}),
|
|
16
|
+
}));
|
|
17
|
+
function makeWrapper(row) {
|
|
18
|
+
return function Wrapper({ children }) {
|
|
19
|
+
return _jsx(RowProvider, { row: row, children: children });
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// validateBkPath
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
const VALID_PATH = "/LHCb/Collision16/Beam6500GeV-VeloClosed-MagDown/Real Data/Reco16/Stripping28r2/90000000/DIMUON.DST";
|
|
26
|
+
describe("RowProvider — validateBkPath", () => {
|
|
27
|
+
it("returns true for a valid path when no stripping line is set", () => {
|
|
28
|
+
const row = createMockRow({ line: null });
|
|
29
|
+
const { result } = renderHook(() => useRow(), { wrapper: makeWrapper(row) });
|
|
30
|
+
expect(result.current.validateBkPath(VALID_PATH)).toBe(true);
|
|
31
|
+
});
|
|
32
|
+
it("returns true when path matches the line's stream and version", () => {
|
|
33
|
+
const row = createMockRow({ line: mockStrippingLine });
|
|
34
|
+
const { result } = renderHook(() => useRow(), { wrapper: makeWrapper(row) });
|
|
35
|
+
// fileName is "DIMUON.DST" → base is "dimuon", stream is "leptonic" → must match allstreams or stream name
|
|
36
|
+
// Actually DIMUON.DST fileName "DIMUON" doesn't match "leptonic"
|
|
37
|
+
// Let me check... The validation: ["allstreams", streamName].includes(fileName)
|
|
38
|
+
// streamName = "leptonic", fileName = "dimuon" → NOT a match
|
|
39
|
+
// Wait this might return false. Let me re-check the logic:
|
|
40
|
+
// validateBkPathsForLines checks: ["allstreams", streamName].includes(fileName)
|
|
41
|
+
// DIMUON.DST → fileName = "DIMUON.DST", fileName.split(".")[0].toLowerCase() = "dimuon"
|
|
42
|
+
// streamName = line.stream.toLowerCase() = "leptonic"
|
|
43
|
+
// ["allstreams", "leptonic"].includes("dimuon") → false
|
|
44
|
+
// So DIMUON.DST does NOT match Leptonic stream. Use ALLSTREAMS.DST instead.
|
|
45
|
+
const allstreamsPath = "/LHCb/Collision16/Beam6500GeV-VeloClosed-MagDown/Real Data/Reco16/Stripping28r2/90000000/ALLSTREAMS.DST";
|
|
46
|
+
expect(result.current.validateBkPath(allstreamsPath)).toBe(true);
|
|
47
|
+
});
|
|
48
|
+
it("returns false for a path with wrong stripping version", () => {
|
|
49
|
+
const row = createMockRow({ line: mockStrippingLine });
|
|
50
|
+
const { result } = renderHook(() => useRow(), { wrapper: makeWrapper(row) });
|
|
51
|
+
// mockStrippingLine has versions: ["28r2"], wrong version "21"
|
|
52
|
+
const wrongVersionAllstreams = "/LHCb/Collision16/Beam6500GeV-VeloClosed-MagDown/Real Data/Reco16/Stripping21/90000000/ALLSTREAMS.DST";
|
|
53
|
+
expect(result.current.validateBkPath(wrongVersionAllstreams)).toBe(false);
|
|
54
|
+
});
|
|
55
|
+
it("returns false for an invalid path format", () => {
|
|
56
|
+
const row = createMockRow({ line: mockStrippingLine });
|
|
57
|
+
const { result } = renderHook(() => useRow(), { wrapper: makeWrapper(row) });
|
|
58
|
+
expect(result.current.validateBkPath("not-a-valid-path")).toBe(false);
|
|
59
|
+
});
|
|
60
|
+
it("returns true for an ALLSTREAMS path matching the version", () => {
|
|
61
|
+
const row = createMockRow({ line: mockStrippingLine });
|
|
62
|
+
const { result } = renderHook(() => useRow(), { wrapper: makeWrapper(row) });
|
|
63
|
+
const allstreams = "/LHCb/Collision16/Beam6500GeV-VeloClosed-MagDown/Real Data/Reco16/Stripping28r2/90000000/ALLSTREAMS.DST";
|
|
64
|
+
expect(result.current.validateBkPath(allstreams)).toBe(true);
|
|
65
|
+
});
|
|
66
|
+
it("exposes the row from context", () => {
|
|
67
|
+
const row = createMockRow({ id: 42 });
|
|
68
|
+
const { result } = renderHook(() => useRow(), { wrapper: makeWrapper(row) });
|
|
69
|
+
expect(result.current.row.id).toBe(42);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
// updateBkPaths
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
describe("RowProvider — updateBkPaths", () => {
|
|
76
|
+
it("calls updateRow when updateBkPaths is invoked", () => {
|
|
77
|
+
const row = createMockRow({ line: mockStrippingLine });
|
|
78
|
+
const { result } = renderHook(() => useRow(), { wrapper: makeWrapper(row) });
|
|
79
|
+
const allstreams = "/LHCb/Collision16/Beam6500GeV-VeloClosed-MagDown/Real Data/Reco16/Stripping28r2/90000000/ALLSTREAMS.DST";
|
|
80
|
+
result.current.updateBkPaths([allstreams]);
|
|
81
|
+
expect(mockUpdateRow).toHaveBeenCalledOnce();
|
|
82
|
+
});
|
|
83
|
+
it("updateRow callback filters paths using validateBkPath", () => {
|
|
84
|
+
// Make mockUpdateRow actually invoke the callback so the inner lines are covered
|
|
85
|
+
mockUpdateRow.mockImplementation((_id, fn) => {
|
|
86
|
+
const fakeRow = {
|
|
87
|
+
id: 5,
|
|
88
|
+
decay: {},
|
|
89
|
+
dtt: null,
|
|
90
|
+
line: mockStrippingLine,
|
|
91
|
+
paths: [],
|
|
92
|
+
pathOptions: [],
|
|
93
|
+
};
|
|
94
|
+
fn(fakeRow);
|
|
95
|
+
});
|
|
96
|
+
const row = createMockRow({ line: mockStrippingLine });
|
|
97
|
+
const { result } = renderHook(() => useRow(), { wrapper: makeWrapper(row) });
|
|
98
|
+
const allstreams = "/LHCb/Collision16/Beam6500GeV-VeloClosed-MagDown/Real Data/Reco16/Stripping28r2/90000000/ALLSTREAMS.DST";
|
|
99
|
+
result.current.updateBkPaths([allstreams]);
|
|
100
|
+
expect(mockUpdateRow).toHaveBeenCalled();
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
// useRow outside provider
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
describe("useRow outside provider", () => {
|
|
107
|
+
it("throws when used outside a RowProvider", () => {
|
|
108
|
+
expect(() => renderHook(() => useRow())).toThrow("useRow must be used within a RowProvider");
|
|
109
|
+
});
|
|
110
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { act, renderHook } from "@testing-library/react";
|
|
4
|
+
import { RowsProvider, useRows } from "../../providers/RowsProvider";
|
|
5
|
+
import { createMockDtt, createMockRow } from "../testUtils";
|
|
6
|
+
function makeWrapper() {
|
|
7
|
+
return function Wrapper({ children }) {
|
|
8
|
+
return _jsx(RowsProvider, { children: children });
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
function renderProvider() {
|
|
12
|
+
return renderHook(() => useRows(), { wrapper: makeWrapper() });
|
|
13
|
+
}
|
|
14
|
+
describe("RowsProvider", () => {
|
|
15
|
+
it("starts with an empty rows list when localStorage has no rows", () => {
|
|
16
|
+
localStorage.removeItem("rows");
|
|
17
|
+
const { result } = renderProvider();
|
|
18
|
+
expect(result.current.rows).toEqual([]);
|
|
19
|
+
});
|
|
20
|
+
it("hydrates rows from localStorage on mount", () => {
|
|
21
|
+
const row = createMockRow({ id: 99 });
|
|
22
|
+
localStorage.setItem("rows", JSON.stringify([{ ...row, dtt: null }]));
|
|
23
|
+
const { result } = renderProvider();
|
|
24
|
+
expect(result.current.rows[0].id).toBe(99);
|
|
25
|
+
localStorage.removeItem("rows");
|
|
26
|
+
});
|
|
27
|
+
it("reconstructs Dtt from config when stored row has a dtt", () => {
|
|
28
|
+
const row = createMockRow({ id: 200, dtt: createMockDtt("StoredTree") });
|
|
29
|
+
localStorage.setItem("rows", JSON.stringify([row]));
|
|
30
|
+
const { result } = renderProvider();
|
|
31
|
+
expect(result.current.rows[0].dtt).not.toBeNull();
|
|
32
|
+
localStorage.removeItem("rows");
|
|
33
|
+
});
|
|
34
|
+
it("updateRow modifies the matching row", () => {
|
|
35
|
+
localStorage.removeItem("rows");
|
|
36
|
+
const { result } = renderProvider();
|
|
37
|
+
act(() => result.current.setRows([createMockRow({ id: 1 })]));
|
|
38
|
+
act(() => result.current.updateRow(1, (r) => ({ ...r, id: 99 })));
|
|
39
|
+
expect(result.current.rows[0].id).toBe(99);
|
|
40
|
+
});
|
|
41
|
+
it("updateRow does not modify rows with a different id", () => {
|
|
42
|
+
localStorage.removeItem("rows");
|
|
43
|
+
const { result } = renderProvider();
|
|
44
|
+
act(() => result.current.setRows([createMockRow({ id: 1 }), createMockRow({ id: 2 })]));
|
|
45
|
+
act(() => result.current.updateRow(1, (r) => ({ ...r, id: 99 })));
|
|
46
|
+
expect(result.current.rows[1].id).toBe(2);
|
|
47
|
+
});
|
|
48
|
+
it("removeRow filters out the row with the given id", () => {
|
|
49
|
+
localStorage.removeItem("rows");
|
|
50
|
+
const { result } = renderProvider();
|
|
51
|
+
act(() => result.current.setRows([createMockRow({ id: 1 }), createMockRow({ id: 2 })]));
|
|
52
|
+
act(() => result.current.removeRow(1));
|
|
53
|
+
expect(result.current.rows).toHaveLength(1);
|
|
54
|
+
expect(result.current.rows[0].id).toBe(2);
|
|
55
|
+
});
|
|
56
|
+
it("configuredRows contains only rows with a dtt", () => {
|
|
57
|
+
localStorage.removeItem("rows");
|
|
58
|
+
const { result } = renderProvider();
|
|
59
|
+
act(() => result.current.setRows([createMockRow({ id: 1, dtt: null }), createMockRow({ id: 2, dtt: createMockDtt() })]));
|
|
60
|
+
expect(result.current.configuredRows).toHaveLength(1);
|
|
61
|
+
expect(result.current.configuredRows[0].id).toBe(2);
|
|
62
|
+
});
|
|
63
|
+
it("generateAllFiles returns one file per configured row plus info.yaml", () => {
|
|
64
|
+
localStorage.removeItem("rows");
|
|
65
|
+
const { result } = renderProvider();
|
|
66
|
+
act(() => result.current.setRows([
|
|
67
|
+
createMockRow({ id: 1, dtt: createMockDtt("TreeA") }),
|
|
68
|
+
createMockRow({ id: 2, dtt: createMockDtt("TreeB") }),
|
|
69
|
+
]));
|
|
70
|
+
const files = result.current.generateAllFiles();
|
|
71
|
+
// 2 dtt files + 1 info.yaml
|
|
72
|
+
expect(files).toHaveLength(3);
|
|
73
|
+
});
|
|
74
|
+
it("persists rows to localStorage when rows change", () => {
|
|
75
|
+
localStorage.removeItem("rows");
|
|
76
|
+
const { result } = renderProvider();
|
|
77
|
+
act(() => result.current.setRows([createMockRow({ id: 7 })]));
|
|
78
|
+
const stored = JSON.parse(localStorage.getItem("rows") ?? "[]");
|
|
79
|
+
expect(stored[0].id).toBe(7);
|
|
80
|
+
});
|
|
81
|
+
it("throws when useRows is used outside the provider", () => {
|
|
82
|
+
expect(() => renderHook(() => useRows())).toThrow("useRows must be used within a RowsProvider");
|
|
83
|
+
});
|
|
84
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|