@thepalaceproject/circulation-admin 1.43.0-post.7 → 1.43.0-post.9
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/package.json +1 -1
- package/tests/jest/components/AccountPage.test.tsx +41 -0
- package/tests/jest/components/AdvancedSearchBooleanFilter.test.tsx +231 -0
- package/tests/jest/components/AdvancedSearchFilter.test.tsx +142 -0
- package/tests/jest/components/AdvancedSearchFilterInput.test.tsx +212 -0
- package/tests/jest/components/AdvancedSearchFilterViewer.test.tsx +52 -0
- package/tests/jest/components/AdvancedSearchValueFilter.test.tsx +123 -0
- package/tests/jest/components/BookDetails.test.tsx +133 -0
- package/tests/jest/components/BookDetailsContainer.test.tsx +116 -0
- package/tests/jest/components/CatalogServices.test.tsx +63 -0
- package/tests/jest/components/CirculationEventsDownloadForm.test.tsx +41 -0
- package/tests/jest/components/ConfigPage.test.tsx +58 -0
- package/tests/jest/components/CustomListPage.test.tsx +57 -0
- package/tests/jest/components/CustomListSearch.test.tsx +275 -0
- package/tests/jest/components/CustomListsSidebar.test.tsx +251 -0
- package/tests/jest/components/DashboardPage.test.tsx +76 -0
- package/tests/jest/components/DebugAuthentication.test.tsx +1 -5
- package/tests/jest/components/DiagnosticsTabContainer.test.tsx +147 -0
- package/tests/jest/components/EditableConfigList.test.tsx +8 -2
- package/tests/jest/components/EntryPointsContainer.test.tsx +97 -0
- package/tests/jest/components/EntryPointsTabs.test.tsx +88 -0
- package/tests/jest/components/ErrorMessage.test.tsx +97 -0
- package/tests/jest/components/Footer.test.tsx +141 -0
- package/tests/jest/components/InventoryReportRequestModal.test.tsx +16 -42
- package/tests/jest/components/LaneEditor.test.tsx +2 -6
- package/tests/jest/components/LanePage.test.tsx +68 -0
- package/tests/jest/components/LanguageField.test.tsx +40 -0
- package/tests/jest/components/LoadButton.test.tsx +49 -0
- package/tests/jest/components/ManagePatrons.test.tsx +73 -0
- package/tests/jest/components/ManagePatronsForm.test.tsx +180 -0
- package/tests/jest/components/ManagePatronsTabContainer.test.tsx +143 -0
- package/tests/jest/components/MetadataServices.test.tsx +59 -0
- package/tests/jest/components/PatronBlockingRulesEditor.test.tsx +3 -1
- package/tests/jest/components/PatronInfo.test.tsx +54 -0
- package/tests/jest/components/SelfTestResult.test.tsx +164 -0
- package/tests/jest/components/SelfTestsCategory.test.tsx +200 -0
- package/tests/jest/components/ServiceWithRegistrationsEditForm.test.tsx +110 -0
- package/tests/jest/components/SetupPage.test.tsx +41 -0
- package/tests/jest/components/SingleStatListItem.test.tsx +62 -0
- package/tests/jest/components/Stats.test.tsx +4 -8
- package/tests/jest/components/TabContainer.test.tsx +68 -0
- package/tests/jest/components/Timestamp.test.tsx +74 -0
- package/tests/jest/components/TroubleshootingTabContainer.test.tsx +68 -0
- package/tests/jest/components/WelcomePage.test.tsx +43 -0
- package/tests/jest/components/WithEditButton.test.tsx +55 -0
- package/tests/jest/components/WithRemoveButton.test.tsx +95 -0
- package/tests/jest/index.test.tsx +50 -0
- package/tests/jest/components/AdvancedSearchBuilder.test.tsx +0 -38
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { render, screen } from "@testing-library/react";
|
|
3
|
+
import userEvent from "@testing-library/user-event";
|
|
4
|
+
|
|
5
|
+
import buildStore from "../../../src/store";
|
|
6
|
+
|
|
7
|
+
// The active tab renders DiagnosticsServiceType, which recurses into the service
|
|
8
|
+
// tab machinery. The legacy test only asserted the `type`/`services` props it
|
|
9
|
+
// received, so mock it to a marker that echoes those.
|
|
10
|
+
jest.mock("../../../src/components/DiagnosticsServiceType", () => ({
|
|
11
|
+
__esModule: true,
|
|
12
|
+
default: (props: any) => (
|
|
13
|
+
<div
|
|
14
|
+
data-testid="service-type"
|
|
15
|
+
data-type={props.type}
|
|
16
|
+
data-has-services={String(props.services !== undefined)}
|
|
17
|
+
/>
|
|
18
|
+
),
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
import { DiagnosticsTabContainer } from "../../../src/components/DiagnosticsTabContainer";
|
|
22
|
+
|
|
23
|
+
describe("DiagnosticsTabContainer", () => {
|
|
24
|
+
let goToTab: jest.Mock;
|
|
25
|
+
let fetchDiagnostics: jest.Mock;
|
|
26
|
+
|
|
27
|
+
const ts1 = {
|
|
28
|
+
service: "test_service",
|
|
29
|
+
id: "1",
|
|
30
|
+
start: "start_time_string",
|
|
31
|
+
duration: "0",
|
|
32
|
+
collection_name: "collection1",
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const diagnostics = {
|
|
36
|
+
monitor: [{ test_service_1: [{ collection1: [ts1] }] }],
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
beforeEach(() => {
|
|
40
|
+
goToTab = jest.fn();
|
|
41
|
+
fetchDiagnostics = jest.fn();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const element = (overrides = {}) => {
|
|
45
|
+
const props = {
|
|
46
|
+
goToTab,
|
|
47
|
+
tab: "coverage_provider",
|
|
48
|
+
store: buildStore(),
|
|
49
|
+
fetchDiagnostics,
|
|
50
|
+
diagnostics: { coverage_provider: [] },
|
|
51
|
+
isLoaded: true,
|
|
52
|
+
...overrides,
|
|
53
|
+
};
|
|
54
|
+
return <DiagnosticsTabContainer {...props} />;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const renderContainer = (overrides = {}) => render(element(overrides));
|
|
58
|
+
|
|
59
|
+
describe("rendering", () => {
|
|
60
|
+
it("renders a tab container", () => {
|
|
61
|
+
const { container } = renderContainer();
|
|
62
|
+
expect(container.firstChild).toHaveClass("tab-container");
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("renders tabs and defaults to showing the Coverage Providers tab", () => {
|
|
66
|
+
const { container } = renderContainer();
|
|
67
|
+
const tabs = container.querySelectorAll("ul.nav-tabs li");
|
|
68
|
+
expect(tabs).toHaveLength(4);
|
|
69
|
+
|
|
70
|
+
expect(tabs[0]).toHaveTextContent("Coverage Providers");
|
|
71
|
+
expect(tabs[0]).toHaveClass("active");
|
|
72
|
+
|
|
73
|
+
expect(tabs[1]).toHaveTextContent("Monitors");
|
|
74
|
+
expect(tabs[1]).not.toHaveClass("active");
|
|
75
|
+
|
|
76
|
+
expect(tabs[2]).toHaveTextContent("Scripts");
|
|
77
|
+
expect(tabs[2]).not.toHaveClass("active");
|
|
78
|
+
|
|
79
|
+
expect(tabs[3]).toHaveTextContent("Other");
|
|
80
|
+
expect(tabs[3]).not.toHaveClass("active");
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("renders tab content", () => {
|
|
84
|
+
renderContainer({ diagnostics });
|
|
85
|
+
const serviceTypes = screen.getAllByTestId("service-type");
|
|
86
|
+
expect(serviceTypes).toHaveLength(4);
|
|
87
|
+
|
|
88
|
+
expect(serviceTypes[0]).toHaveAttribute("data-type", "coverage_provider");
|
|
89
|
+
expect(serviceTypes[0]).toHaveAttribute("data-has-services", "false");
|
|
90
|
+
|
|
91
|
+
expect(serviceTypes[1]).toHaveAttribute("data-type", "monitor");
|
|
92
|
+
expect(serviceTypes[1]).toHaveAttribute("data-has-services", "true");
|
|
93
|
+
|
|
94
|
+
expect(serviceTypes[2]).toHaveAttribute("data-type", "script");
|
|
95
|
+
expect(serviceTypes[2]).toHaveAttribute("data-has-services", "false");
|
|
96
|
+
|
|
97
|
+
expect(serviceTypes[3]).toHaveAttribute("data-type", "other");
|
|
98
|
+
expect(serviceTypes[3]).toHaveAttribute("data-has-services", "false");
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
describe("behavior", () => {
|
|
103
|
+
it("calls fetchDiagnostics on mount", () => {
|
|
104
|
+
renderContainer();
|
|
105
|
+
expect(fetchDiagnostics).toHaveBeenCalled();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("shows the loading indicator", () => {
|
|
109
|
+
const { container, rerender } = renderContainer();
|
|
110
|
+
expect(container.querySelectorAll(".loading")).toHaveLength(0);
|
|
111
|
+
rerender(element({ isLoaded: false }));
|
|
112
|
+
expect(container.querySelectorAll(".loading")).toHaveLength(4);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("shows an error message", () => {
|
|
116
|
+
const { container, rerender } = renderContainer();
|
|
117
|
+
expect(container.querySelectorAll(".alert-danger")).toHaveLength(0);
|
|
118
|
+
rerender(
|
|
119
|
+
element({
|
|
120
|
+
fetchError: { status: 401, response: "error fetching diagnostics" },
|
|
121
|
+
})
|
|
122
|
+
);
|
|
123
|
+
expect(container.querySelectorAll(".alert-danger")).toHaveLength(4);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("calls goToTab when a tab is clicked", async () => {
|
|
127
|
+
const user = userEvent.setup();
|
|
128
|
+
renderContainer({ diagnostics });
|
|
129
|
+
await user.click(screen.getByRole("link", { name: "Monitors" }));
|
|
130
|
+
expect(goToTab).toHaveBeenCalledTimes(1);
|
|
131
|
+
expect(goToTab).toHaveBeenCalledWith("monitor");
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("switches tabs when the tab prop changes", () => {
|
|
135
|
+
const { container, rerender } = renderContainer();
|
|
136
|
+
let tabs = container.querySelectorAll("ul.nav-tabs li");
|
|
137
|
+
expect(tabs[0]).toHaveClass("active");
|
|
138
|
+
expect(tabs[1]).not.toHaveClass("active");
|
|
139
|
+
|
|
140
|
+
rerender(element({ tab: "monitor" }));
|
|
141
|
+
|
|
142
|
+
tabs = container.querySelectorAll("ul.nav-tabs li");
|
|
143
|
+
expect(tabs[0]).not.toHaveClass("active");
|
|
144
|
+
expect(tabs[1]).toHaveClass("active");
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
});
|
|
@@ -383,7 +383,11 @@ describe("EditableConfigList - library association disclosure", () => {
|
|
|
383
383
|
it("falls back to short_name when allLibraries is absent from the data", () => {
|
|
384
384
|
const { container } = renderWithContext(
|
|
385
385
|
<TestServiceList
|
|
386
|
-
data={{
|
|
386
|
+
data={{
|
|
387
|
+
services: [
|
|
388
|
+
{ id: 1, name: "Service A", libraries: [{ short_name: "nypl" }] },
|
|
389
|
+
],
|
|
390
|
+
}}
|
|
387
391
|
fetchData={jest.fn()}
|
|
388
392
|
editItem={jest.fn().mockResolvedValue(undefined)}
|
|
389
393
|
deleteItem={jest.fn().mockResolvedValue(undefined)}
|
|
@@ -393,7 +397,9 @@ describe("EditableConfigList - library association disclosure", () => {
|
|
|
393
397
|
config
|
|
394
398
|
);
|
|
395
399
|
fireEvent.click(container.querySelector(".association-toggle"));
|
|
396
|
-
expect(container.querySelector(".associated-items li").textContent).toBe(
|
|
400
|
+
expect(container.querySelector(".associated-items li").textContent).toBe(
|
|
401
|
+
"nypl"
|
|
402
|
+
);
|
|
397
403
|
});
|
|
398
404
|
});
|
|
399
405
|
});
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { screen } from "@testing-library/react";
|
|
3
|
+
|
|
4
|
+
import { renderWithProviders } from "../testUtils/withProviders";
|
|
5
|
+
|
|
6
|
+
// The wrapped `Collection` (from web-opds-client) is incidental here — the
|
|
7
|
+
// container's job is to pull the "Formats" facets out and hand them to
|
|
8
|
+
// `EntryPointsTabs`. Mock Collection to a marker so we can render the real
|
|
9
|
+
// EntryPointsTabs and assert the tabs it produces.
|
|
10
|
+
jest.mock(
|
|
11
|
+
"@thepalaceproject/web-opds-client/lib/components/Collection",
|
|
12
|
+
() => ({
|
|
13
|
+
__esModule: true,
|
|
14
|
+
default: () => <div data-testid="collection" />,
|
|
15
|
+
})
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
// EntryPointsTabs renders CatalogLink -> React Router `Link`, which needs router
|
|
19
|
+
// context renderWithProviders does not supply. Mock `Link` to a marker.
|
|
20
|
+
jest.mock("react-router", () => ({
|
|
21
|
+
...jest.requireActual("react-router"),
|
|
22
|
+
Link: (props) => (
|
|
23
|
+
<div data-testid="Link" data-to={props.to}>
|
|
24
|
+
{props.children}
|
|
25
|
+
</div>
|
|
26
|
+
),
|
|
27
|
+
}));
|
|
28
|
+
|
|
29
|
+
import EntryPointsContainer from "../../../src/components/EntryPointsContainer";
|
|
30
|
+
|
|
31
|
+
// Stands in for the OPDS Collection child whose `collection` prop the container
|
|
32
|
+
// reads. It is never rendered by the container (only its props are inspected).
|
|
33
|
+
class CollectionChild extends React.Component<{ collection: unknown }> {
|
|
34
|
+
render() {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
describe("EntryPointsContainer", () => {
|
|
40
|
+
it("should not render entry-point tabs if no facets were passed", () => {
|
|
41
|
+
const collection = {
|
|
42
|
+
facetGroups: [],
|
|
43
|
+
lanes: [],
|
|
44
|
+
books: [],
|
|
45
|
+
navigationLinks: [],
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const { container } = renderWithProviders(
|
|
49
|
+
<EntryPointsContainer>
|
|
50
|
+
<CollectionChild collection={collection} />
|
|
51
|
+
</EntryPointsContainer>
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
expect(
|
|
55
|
+
container.querySelector(".entry-points-tab-container")
|
|
56
|
+
).toBeInTheDocument();
|
|
57
|
+
// EntryPointsTabs renders nothing when there are no format facets.
|
|
58
|
+
expect(container.querySelector(".nav-tabs")).toBeNull();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("shows a tab container with facets when the label is 'Formats'", () => {
|
|
62
|
+
const facetGroups = [
|
|
63
|
+
{
|
|
64
|
+
facets: [],
|
|
65
|
+
label: "Some other facets",
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
facets: [
|
|
69
|
+
{
|
|
70
|
+
label: "Ebooks",
|
|
71
|
+
href: "http://circulation.librarysimplified.org/groups/?entrypoint=Book",
|
|
72
|
+
active: false,
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
label: "Audiobooks",
|
|
76
|
+
href: "http://circulation.librarysimplified.org/groups/?entrypoint=Audio",
|
|
77
|
+
active: false,
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
label: "Formats",
|
|
81
|
+
},
|
|
82
|
+
];
|
|
83
|
+
const collection = { facetGroups };
|
|
84
|
+
|
|
85
|
+
const { container } = renderWithProviders(
|
|
86
|
+
<EntryPointsContainer>
|
|
87
|
+
<CollectionChild collection={collection} />
|
|
88
|
+
</EntryPointsContainer>
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
const list = container.querySelector("ul.nav-tabs");
|
|
92
|
+
expect(list).toBeInTheDocument();
|
|
93
|
+
expect(list.querySelectorAll("li")).toHaveLength(2);
|
|
94
|
+
expect(screen.getByText("Ebooks")).toBeInTheDocument();
|
|
95
|
+
expect(screen.getByText("Audiobooks")).toBeInTheDocument();
|
|
96
|
+
});
|
|
97
|
+
});
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { screen } from "@testing-library/react";
|
|
3
|
+
import { FacetData } from "@thepalaceproject/web-opds-client/lib/interfaces";
|
|
4
|
+
|
|
5
|
+
import { renderWithProviders } from "../testUtils/withProviders";
|
|
6
|
+
|
|
7
|
+
// Each tab is a CatalogLink, which renders a React Router `Link`. Router `Link`
|
|
8
|
+
// needs router context that `renderWithProviders` does not supply, so mock it to
|
|
9
|
+
// a marker that exposes its navigation target and children (matching Lane.test.tsx).
|
|
10
|
+
jest.mock("react-router", () => ({
|
|
11
|
+
...jest.requireActual("react-router"),
|
|
12
|
+
Link: (props) => (
|
|
13
|
+
<div data-testid="Link" data-to={props.to}>
|
|
14
|
+
{props.children}
|
|
15
|
+
</div>
|
|
16
|
+
),
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
import { EntryPointsTabs } from "../../../src/components/EntryPointsTabs";
|
|
20
|
+
|
|
21
|
+
const twoFacets: FacetData[] = [
|
|
22
|
+
{
|
|
23
|
+
label: "Ebooks",
|
|
24
|
+
href: "http://circulation.librarysimplified.org/groups/?entrypoint=Book",
|
|
25
|
+
active: true,
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
label: "Audiobooks",
|
|
29
|
+
href: "http://circulation.librarysimplified.org/groups/?entrypoint=Audio",
|
|
30
|
+
active: false,
|
|
31
|
+
},
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
describe("EntryPointsTabs", () => {
|
|
35
|
+
it("should not generate any tabs", () => {
|
|
36
|
+
const { container } = renderWithProviders(<EntryPointsTabs />);
|
|
37
|
+
|
|
38
|
+
expect(container.querySelector(".nav-tabs")).toBeNull();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("should generate two tabs with two entry points", () => {
|
|
42
|
+
const { container } = renderWithProviders(
|
|
43
|
+
<EntryPointsTabs facets={twoFacets} />
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
const list = container.querySelector("ul.nav-tabs");
|
|
47
|
+
expect(list).toBeInTheDocument();
|
|
48
|
+
|
|
49
|
+
const items = list.querySelectorAll("li");
|
|
50
|
+
expect(items).toHaveLength(2);
|
|
51
|
+
expect(items[0]).toHaveClass("active");
|
|
52
|
+
expect(items[1]).not.toHaveClass("active");
|
|
53
|
+
|
|
54
|
+
expect(screen.getByText("Ebooks")).toBeInTheDocument();
|
|
55
|
+
expect(screen.getByText("Audiobooks")).toBeInTheDocument();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("uses router to navigate when a tab is clicked", () => {
|
|
59
|
+
renderWithProviders(<EntryPointsTabs facets={twoFacets} />);
|
|
60
|
+
|
|
61
|
+
const links = screen.getAllByTestId("Link");
|
|
62
|
+
expect(links).toHaveLength(2);
|
|
63
|
+
expect(links[0]).toHaveTextContent("Ebooks");
|
|
64
|
+
expect(links[1]).toHaveTextContent("Audiobooks");
|
|
65
|
+
// Each tab links somewhere for the router to navigate to.
|
|
66
|
+
expect(links[0]).toHaveAttribute("data-to");
|
|
67
|
+
expect(links[1]).toHaveAttribute("data-to");
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("should generate one tab with an active entry point", () => {
|
|
71
|
+
const oneFacet: FacetData[] = [
|
|
72
|
+
{
|
|
73
|
+
label: "Audiobooks",
|
|
74
|
+
href: "http://circulation.librarysimplified.org/groups/?entrypoint=Audio",
|
|
75
|
+
active: true,
|
|
76
|
+
},
|
|
77
|
+
];
|
|
78
|
+
const { container } = renderWithProviders(
|
|
79
|
+
<EntryPointsTabs facets={oneFacet} />
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
const items = container.querySelectorAll("ul.nav-tabs li");
|
|
83
|
+
expect(items).toHaveLength(1);
|
|
84
|
+
expect(items[0]).toHaveClass("active");
|
|
85
|
+
|
|
86
|
+
expect(screen.getByText("Audiobooks")).toBeInTheDocument();
|
|
87
|
+
});
|
|
88
|
+
});
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { render, screen } from "@testing-library/react";
|
|
3
|
+
import userEvent from "@testing-library/user-event";
|
|
4
|
+
|
|
5
|
+
import ErrorMessage from "../../../src/components/ErrorMessage";
|
|
6
|
+
|
|
7
|
+
describe("ErrorMessage", () => {
|
|
8
|
+
it("shows logged out message for 401 error", () => {
|
|
9
|
+
render(<ErrorMessage error={{ status: 401, response: "", url: "" }} />);
|
|
10
|
+
|
|
11
|
+
expect(screen.getByRole("alert")).toHaveTextContent("logged out");
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("shows detail for problem detail", () => {
|
|
15
|
+
render(
|
|
16
|
+
<ErrorMessage
|
|
17
|
+
error={{
|
|
18
|
+
status: 500,
|
|
19
|
+
response: JSON.stringify({ status: 500, detail: "detail" }),
|
|
20
|
+
url: "",
|
|
21
|
+
}}
|
|
22
|
+
/>
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
expect(screen.getByRole("alert")).toHaveTextContent("detail");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("shows response for non-json response", () => {
|
|
29
|
+
render(
|
|
30
|
+
<ErrorMessage error={{ status: 500, response: "response", url: "" }} />
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
expect(screen.getByRole("alert")).toHaveTextContent("response");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("parses non-JSON problem detail string", () => {
|
|
37
|
+
// prettier-ignore
|
|
38
|
+
const response = "Remote service returned a problem detail document: {\"status\": 502, \"detail\": problem text, \"title\": TITLE}";
|
|
39
|
+
render(<ErrorMessage error={{ status: 500, response, url: "" }} />);
|
|
40
|
+
|
|
41
|
+
expect(screen.getByRole("alert")).toHaveTextContent(
|
|
42
|
+
"Remote service returned a problem detail document with status 502: problem text"
|
|
43
|
+
);
|
|
44
|
+
expect(screen.getByText("TITLE")).toBeInTheDocument();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("can handle missing detail property in non-JSON problem detail string", () => {
|
|
48
|
+
// prettier-ignore
|
|
49
|
+
const response = "Remote service returned a problem detail document: {\"status\": 502, \"title\": TITLE}";
|
|
50
|
+
render(<ErrorMessage error={{ status: 500, response, url: "" }} />);
|
|
51
|
+
|
|
52
|
+
expect(screen.getByRole("alert")).toHaveTextContent(
|
|
53
|
+
"Remote service returned a problem detail document with status 502:"
|
|
54
|
+
);
|
|
55
|
+
expect(screen.getByText("TITLE")).toBeInTheDocument();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("can handle missing status property in non-JSON problem detail string", () => {
|
|
59
|
+
// prettier-ignore
|
|
60
|
+
const response = "Remote service returned a problem detail document: {\"detail\": problem text, \"title\": TITLE}";
|
|
61
|
+
render(<ErrorMessage error={{ status: 500, response, url: "" }} />);
|
|
62
|
+
|
|
63
|
+
expect(screen.getByRole("alert")).toHaveTextContent(
|
|
64
|
+
"Remote service returned a problem detail document: problem text"
|
|
65
|
+
);
|
|
66
|
+
expect(screen.getByText("TITLE")).toBeInTheDocument();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("can handle missing title property in non-JSON problem detail string", () => {
|
|
70
|
+
// prettier-ignore
|
|
71
|
+
const response = "Remote service returned a problem detail document: {\"status\": 502, \"detail\": problem text}";
|
|
72
|
+
render(<ErrorMessage error={{ status: 500, response, url: "" }} />);
|
|
73
|
+
|
|
74
|
+
expect(screen.getByRole("alert")).toHaveTextContent(
|
|
75
|
+
"Remote service returned a problem detail document with status 502:"
|
|
76
|
+
);
|
|
77
|
+
expect(screen.getByText("Error")).toBeInTheDocument();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("shows a try again button that fires the callback when clicked", async () => {
|
|
81
|
+
const user = userEvent.setup();
|
|
82
|
+
const tryAgain = jest.fn();
|
|
83
|
+
render(
|
|
84
|
+
<ErrorMessage
|
|
85
|
+
error={{ status: 500, response: "response", url: "" }}
|
|
86
|
+
tryAgain={tryAgain}
|
|
87
|
+
/>
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const tryAgainButton = screen.getByRole("button", { name: "Try again" });
|
|
91
|
+
expect(tryAgainButton).toBeInTheDocument();
|
|
92
|
+
|
|
93
|
+
await user.click(tryAgainButton);
|
|
94
|
+
|
|
95
|
+
expect(tryAgain).toHaveBeenCalledTimes(1);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { renderWithProviders } from "../testUtils/withProviders";
|
|
3
|
+
import Footer, {
|
|
4
|
+
makeFooterSupportContactText,
|
|
5
|
+
} from "../../../src/components/Footer";
|
|
6
|
+
import {
|
|
7
|
+
DEFAULT_SUPPORT_CONTACT_TEXT,
|
|
8
|
+
SupportContactLink,
|
|
9
|
+
} from "../../../src/context/appContext";
|
|
10
|
+
|
|
11
|
+
const expectedTosText = "Terms of Service Text";
|
|
12
|
+
const expectedTosHref = "terms_of_service";
|
|
13
|
+
const testSupportContactEmail = "helpdesk@example.com";
|
|
14
|
+
const expectedSupportContact: SupportContactLink = {
|
|
15
|
+
href: `mailto:${testSupportContactEmail}?subject=Support+request`,
|
|
16
|
+
text: `Email ${testSupportContactEmail}.`,
|
|
17
|
+
};
|
|
18
|
+
const expectedSupportContactText = makeFooterSupportContactText(
|
|
19
|
+
expectedSupportContact.text
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
// Builds the app config the ContextProvider transforms into terms-of-service and
|
|
23
|
+
// support-contact context, mirroring the legacy test's `getFooterProviders`.
|
|
24
|
+
const footerConfig = ({ hasSupportContactUrl = false } = {}) => ({
|
|
25
|
+
tos_link_text: expectedTosText,
|
|
26
|
+
tos_link_href: expectedTosHref,
|
|
27
|
+
// Include support contact URL only if we ask for it.
|
|
28
|
+
support_contact_url: hasSupportContactUrl
|
|
29
|
+
? "https://example.com/link-from-deprecated-setting"
|
|
30
|
+
: undefined,
|
|
31
|
+
supportContactUrl: hasSupportContactUrl
|
|
32
|
+
? expectedSupportContact.href
|
|
33
|
+
: undefined,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe("Footer", () => {
|
|
37
|
+
it("accepts an optional className prop", () => {
|
|
38
|
+
const { container } = renderWithProviders(
|
|
39
|
+
<Footer className="customClass" />,
|
|
40
|
+
{ appConfigSettings: footerConfig() }
|
|
41
|
+
);
|
|
42
|
+
expect(container.querySelector("footer")).toHaveClass("customClass");
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("displays a terms of service link", () => {
|
|
46
|
+
const { container } = renderWithProviders(<Footer />, {
|
|
47
|
+
appConfigSettings: footerConfig(),
|
|
48
|
+
});
|
|
49
|
+
const links = container.querySelectorAll("a");
|
|
50
|
+
expect(links).toHaveLength(1);
|
|
51
|
+
|
|
52
|
+
expect(links[0]).toHaveTextContent(expectedTosText);
|
|
53
|
+
expect(links[0]).toHaveAttribute("href", expectedTosHref);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe("support contact link", () => {
|
|
57
|
+
it("displays a support contact link, when URL present", () => {
|
|
58
|
+
const { container } = renderWithProviders(<Footer />, {
|
|
59
|
+
appConfigSettings: footerConfig({ hasSupportContactUrl: true }),
|
|
60
|
+
});
|
|
61
|
+
const links = container.querySelectorAll("a");
|
|
62
|
+
expect(links).toHaveLength(2);
|
|
63
|
+
|
|
64
|
+
// The second link should be the support contact link.
|
|
65
|
+
expect(links[1]).toHaveAttribute("href", expectedSupportContact.href);
|
|
66
|
+
expect(links[1]).toHaveTextContent(expectedSupportContactText);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("omits contact link, when no support URL provided", () => {
|
|
70
|
+
const { container } = renderWithProviders(<Footer />, {
|
|
71
|
+
appConfigSettings: footerConfig({ hasSupportContactUrl: false }),
|
|
72
|
+
});
|
|
73
|
+
// There should only be one link....
|
|
74
|
+
const links = container.querySelectorAll("a");
|
|
75
|
+
expect(links).toHaveLength(1);
|
|
76
|
+
|
|
77
|
+
// ... and it should be the TOS link, not a support contact link.
|
|
78
|
+
expect(links[0]).not.toHaveAttribute("href", expectedSupportContact.href);
|
|
79
|
+
expect(links[0]).toHaveAttribute("href", expectedTosHref);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("displays a default label for an https url", () => {
|
|
83
|
+
const { container } = renderWithProviders(<Footer />, {
|
|
84
|
+
appConfigSettings: { supportContactUrl: "https://example.com/support" },
|
|
85
|
+
});
|
|
86
|
+
const links = container.querySelectorAll("a");
|
|
87
|
+
expect(links).toHaveLength(2);
|
|
88
|
+
expect(links[1]).toHaveAttribute("href", "https://example.com/support");
|
|
89
|
+
expect(links[1]).toHaveTextContent(
|
|
90
|
+
makeFooterSupportContactText(DEFAULT_SUPPORT_CONTACT_TEXT)
|
|
91
|
+
);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("displays a custom label for an https url", () => {
|
|
95
|
+
const { container } = renderWithProviders(<Footer />, {
|
|
96
|
+
appConfigSettings: {
|
|
97
|
+
supportContactUrl: "https://example.com/support",
|
|
98
|
+
supportContactText: "Custom Support Text",
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
const links = container.querySelectorAll("a");
|
|
102
|
+
expect(links).toHaveLength(2);
|
|
103
|
+
expect(links[1]).toHaveAttribute("href", "https://example.com/support");
|
|
104
|
+
expect(links[1]).toHaveTextContent(
|
|
105
|
+
makeFooterSupportContactText("Custom Support Text")
|
|
106
|
+
);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("uses the deprecated support_contact_url if supportContactUrl is missing", () => {
|
|
110
|
+
const { container } = renderWithProviders(<Footer />, {
|
|
111
|
+
appConfigSettings: {
|
|
112
|
+
support_contact_url: "https://example.com/deprecated-support",
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
const links = container.querySelectorAll("a");
|
|
116
|
+
expect(links).toHaveLength(2);
|
|
117
|
+
expect(links[1]).toHaveAttribute(
|
|
118
|
+
"href",
|
|
119
|
+
"https://example.com/deprecated-support"
|
|
120
|
+
);
|
|
121
|
+
expect(links[1]).toHaveTextContent(
|
|
122
|
+
makeFooterSupportContactText(DEFAULT_SUPPORT_CONTACT_TEXT)
|
|
123
|
+
);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("displays a custom label for a mailto url", () => {
|
|
127
|
+
const { container } = renderWithProviders(<Footer />, {
|
|
128
|
+
appConfigSettings: {
|
|
129
|
+
supportContactUrl: "mailto:custom@example.com",
|
|
130
|
+
supportContactText: "Email Us Now",
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
const links = container.querySelectorAll("a");
|
|
134
|
+
expect(links).toHaveLength(2);
|
|
135
|
+
expect(links[1]).toHaveAttribute("href", "mailto:custom@example.com");
|
|
136
|
+
expect(links[1]).toHaveTextContent(
|
|
137
|
+
makeFooterSupportContactText("Email Us Now")
|
|
138
|
+
);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
});
|