@thepalaceproject/circulation-admin 1.43.0-post.6 → 1.43.0-post.8
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/BookDetails.test.tsx +133 -0
- package/tests/jest/components/BookDetailsContainer.test.tsx +116 -0
- package/tests/jest/components/ConfigPage.test.tsx +58 -0
- package/tests/jest/components/CustomListPage.test.tsx +57 -0
- package/tests/jest/components/DashboardPage.test.tsx +76 -0
- package/tests/jest/components/DebugAuthentication.test.tsx +1 -5
- 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/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/ManagePatrons.test.tsx +73 -0
- package/tests/jest/components/PatronBlockingRulesEditor.test.tsx +3 -1
- package/tests/jest/components/PatronInfo.test.tsx +54 -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/TroubleshootingTabContainer.test.tsx +68 -0
- package/tests/jest/components/WelcomePage.test.tsx +43 -0
- package/tests/jest/testUtils/formDataShim.test.ts +135 -0
- package/tests/jest/testUtils/formDataShim.ts +112 -0
package/package.json
CHANGED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { screen } from "@testing-library/react";
|
|
3
|
+
|
|
4
|
+
import { renderWithProviders } from "../testUtils/withProviders";
|
|
5
|
+
|
|
6
|
+
// Header and Footer are page chrome with no behavior worth exercising here, and
|
|
7
|
+
// Header pulls in the react-router v3 context that `renderWithProviders` does not
|
|
8
|
+
// supply. Mock them to markers so the test stays about what AccountPage composes.
|
|
9
|
+
jest.mock("../../../src/components/Header", () => ({
|
|
10
|
+
__esModule: true,
|
|
11
|
+
default: () => <div data-testid="header" />,
|
|
12
|
+
}));
|
|
13
|
+
jest.mock("../../../src/components/Footer", () => ({
|
|
14
|
+
__esModule: true,
|
|
15
|
+
default: () => <div data-testid="footer" />,
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
import AccountPage from "../../../src/components/AccountPage";
|
|
19
|
+
|
|
20
|
+
describe("AccountPage", () => {
|
|
21
|
+
it("renders the header and footer chrome", () => {
|
|
22
|
+
renderWithProviders(<AccountPage />);
|
|
23
|
+
|
|
24
|
+
expect(screen.getByTestId("header")).toBeInTheDocument();
|
|
25
|
+
expect(screen.getByTestId("footer")).toBeInTheDocument();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("renders the change-password form", () => {
|
|
29
|
+
renderWithProviders(<AccountPage />);
|
|
30
|
+
|
|
31
|
+
// ChangePasswordForm is connected; its heading and fields appearing proves
|
|
32
|
+
// it received a working store and csrf token from context. EditableInput
|
|
33
|
+
// renders its label and input as siblings with no htmlFor, so there is no
|
|
34
|
+
// accessible label association to query — assert the label text instead.
|
|
35
|
+
expect(
|
|
36
|
+
screen.getByRole("heading", { name: "Change Password" })
|
|
37
|
+
).toBeInTheDocument();
|
|
38
|
+
expect(screen.getByText("New Password")).toBeInTheDocument();
|
|
39
|
+
expect(screen.getByText("Confirm New Password")).toBeInTheDocument();
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { render } from "@testing-library/react";
|
|
3
|
+
|
|
4
|
+
import BookDetails from "../../../src/components/BookDetails";
|
|
5
|
+
import { BookData } from "@thepalaceproject/web-opds-client/lib/interfaces";
|
|
6
|
+
|
|
7
|
+
const book: BookData = {
|
|
8
|
+
id: "urn:librarysimplified.org/terms/id/3M%20ID/crrmnr9",
|
|
9
|
+
url: "http://circulation.librarysimplified.org/works/3M/crrmnr9",
|
|
10
|
+
title: "The Mayan Secrets",
|
|
11
|
+
authors: ["Clive Cussler", "Thomas Perry"],
|
|
12
|
+
contributors: ["contributor 1"],
|
|
13
|
+
summary:
|
|
14
|
+
"<b>Sam and Remi Fargo race for treasure—and survival—in this lightning-paced new adventure from #1<i> New York Times</i> bestselling author Clive Cussler.</b><br /><br />Husband-and-wife team Sam and Remi Fargo are in Mexico when they come upon a remarkable discovery—the mummified remainsof a man clutching an ancient sealed pot. Within the pot is a Mayan book larger than any known before.<br /><br />The book contains astonishing information about the Mayans, their cities, and about mankind itself. The secrets are so powerful that some people would do anything to possess them—as the Fargos are about to find out. Many men and women are going to die for that book.",
|
|
15
|
+
imageUrl: "https://dlotdqc6pnwqb.cloudfront.net/3M/crrmnr9/cover.jpg",
|
|
16
|
+
borrowUrl: "borrow url",
|
|
17
|
+
openAccessLinks: [{ url: "secrets.epub", type: "application/epub+zip" }],
|
|
18
|
+
publisher: "Penguin Publishing Group",
|
|
19
|
+
published: "February 29, 2016",
|
|
20
|
+
categories: ["Children", "10-12", "Fiction", "Adventure", "Fantasy"],
|
|
21
|
+
raw: {
|
|
22
|
+
category: [
|
|
23
|
+
{
|
|
24
|
+
$: {
|
|
25
|
+
scheme: { value: "http://schema.org/audience" },
|
|
26
|
+
label: { value: "Children" },
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
$: {
|
|
31
|
+
scheme: { value: "http://schema.org/typicalAgeRange" },
|
|
32
|
+
label: { value: "10-12" },
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
$: {
|
|
37
|
+
scheme: { value: "http://librarysimplified.org/terms/fiction/" },
|
|
38
|
+
label: { value: "Fiction" },
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
$: {
|
|
43
|
+
scheme: {
|
|
44
|
+
value: "http://librarysimplified.org/terms/genres/Simplified/",
|
|
45
|
+
},
|
|
46
|
+
label: { value: "Adventure" },
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
$: {
|
|
51
|
+
scheme: {
|
|
52
|
+
value: "http://librarysimplified.org/terms/genres/Simplified/",
|
|
53
|
+
},
|
|
54
|
+
label: { value: "Fantasy" },
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
"bibframe:distribution": [
|
|
59
|
+
{
|
|
60
|
+
$: {
|
|
61
|
+
"bibframe:ProviderName": {
|
|
62
|
+
value: "Overdrive",
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
link: [
|
|
68
|
+
{
|
|
69
|
+
$: {
|
|
70
|
+
rel: { value: "issues" },
|
|
71
|
+
href: { value: "http://example.com/report" },
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
$: {
|
|
76
|
+
rel: { value: "http://librarysimplified.org/terms/rel/revoke" },
|
|
77
|
+
href: { value: "http://example.com/revoke" },
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
describe("BookDetails", () => {
|
|
85
|
+
const updateBook = jest.fn();
|
|
86
|
+
|
|
87
|
+
it("shows audience and target age", () => {
|
|
88
|
+
const { container } = render(
|
|
89
|
+
<BookDetails book={book} updateBook={updateBook} />
|
|
90
|
+
);
|
|
91
|
+
const audience = container.querySelector(".audience");
|
|
92
|
+
expect(audience).toBeInTheDocument();
|
|
93
|
+
expect(audience).toHaveTextContent("Audience: Children (age 10-12)");
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("shows categories", () => {
|
|
97
|
+
const { container } = render(
|
|
98
|
+
<BookDetails book={book} updateBook={updateBook} />
|
|
99
|
+
);
|
|
100
|
+
const categories = container.querySelector(".categories");
|
|
101
|
+
expect(categories).toBeInTheDocument();
|
|
102
|
+
expect(categories).toHaveTextContent("Categories: Adventure, Fantasy");
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("doesn't show categories when there aren't any", () => {
|
|
106
|
+
const bookCopy: BookData = {
|
|
107
|
+
...book,
|
|
108
|
+
raw: { category: [], link: [] },
|
|
109
|
+
};
|
|
110
|
+
const { container } = render(
|
|
111
|
+
<BookDetails book={bookCopy} updateBook={updateBook} />
|
|
112
|
+
);
|
|
113
|
+
expect(container.querySelector(".categories")).not.toBeInTheDocument();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("shows distributor", () => {
|
|
117
|
+
const { container } = render(
|
|
118
|
+
<BookDetails book={book} updateBook={updateBook} />
|
|
119
|
+
);
|
|
120
|
+
const distributor = container.querySelector(".distributed-by");
|
|
121
|
+
expect(distributor).toBeInTheDocument();
|
|
122
|
+
expect(distributor).toHaveTextContent("Distributed By: Overdrive");
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("doesn't render any circulation link content", () => {
|
|
126
|
+
const { container } = render(
|
|
127
|
+
<BookDetails book={book} updateBook={updateBook} />
|
|
128
|
+
);
|
|
129
|
+
const circulationLinks = container.querySelector(".circulation-links");
|
|
130
|
+
expect(circulationLinks).toBeInTheDocument();
|
|
131
|
+
expect(circulationLinks).toBeEmptyDOMElement();
|
|
132
|
+
});
|
|
133
|
+
});
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import * as PropTypes from "prop-types";
|
|
3
|
+
import { render, screen } from "@testing-library/react";
|
|
4
|
+
|
|
5
|
+
import buildStore from "../../../src/store";
|
|
6
|
+
import { BookData } from "@thepalaceproject/web-opds-client/lib/interfaces";
|
|
7
|
+
|
|
8
|
+
// BookDetailsTabContainer is a connected TabContainer that mounts every one of its
|
|
9
|
+
// tab panels on render (the metadata editor, classifications, cover, and lists
|
|
10
|
+
// editors), each of which fetches over the network on mount. That machinery is
|
|
11
|
+
// incidental to what BookDetailsContainer composes, so mock it to a marker that
|
|
12
|
+
// echoes the props it receives and renders the book child it is handed.
|
|
13
|
+
jest.mock("../../../src/components/BookDetailsTabContainer", () => ({
|
|
14
|
+
__esModule: true,
|
|
15
|
+
default: (props: any) => (
|
|
16
|
+
<div
|
|
17
|
+
data-testid="book-details-tab-container"
|
|
18
|
+
data-book-url={props.bookUrl}
|
|
19
|
+
data-collection-url={props.collectionUrl}
|
|
20
|
+
data-csrf-token={props.csrfToken}
|
|
21
|
+
data-has-store={String(!!props.store)}
|
|
22
|
+
data-has-library={String(!!props.library)}
|
|
23
|
+
data-has-refresh-catalog={String(!!props.refreshCatalog)}
|
|
24
|
+
>
|
|
25
|
+
{props.children}
|
|
26
|
+
</div>
|
|
27
|
+
),
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
import BookDetailsContainer from "../../../src/components/BookDetailsContainer";
|
|
31
|
+
import BookDetails from "../../../src/components/BookDetails";
|
|
32
|
+
|
|
33
|
+
// BookDetailsContainer reads its configuration from the legacy React context that
|
|
34
|
+
// the OPDS web client supplies in production. `renderWithProviders` does not supply
|
|
35
|
+
// the `library` (nor `tab`) keys it reads, so wrap it in a small legacy context
|
|
36
|
+
// provider that hands it everything it needs.
|
|
37
|
+
class LegacyContextProvider extends React.Component<{ context: any }> {
|
|
38
|
+
static childContextTypes = {
|
|
39
|
+
editorStore: PropTypes.object,
|
|
40
|
+
csrfToken: PropTypes.string,
|
|
41
|
+
tab: PropTypes.string,
|
|
42
|
+
library: PropTypes.func,
|
|
43
|
+
admin: PropTypes.object,
|
|
44
|
+
};
|
|
45
|
+
getChildContext() {
|
|
46
|
+
return this.props.context;
|
|
47
|
+
}
|
|
48
|
+
render() {
|
|
49
|
+
return this.props.children;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const bookData: BookData = {
|
|
54
|
+
id: "book id",
|
|
55
|
+
url: "book url",
|
|
56
|
+
title: "book title",
|
|
57
|
+
raw: { category: [], link: [] },
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
describe("BookDetailsContainer", () => {
|
|
61
|
+
let store;
|
|
62
|
+
let context;
|
|
63
|
+
let refreshCatalog;
|
|
64
|
+
|
|
65
|
+
beforeEach(() => {
|
|
66
|
+
store = buildStore();
|
|
67
|
+
refreshCatalog = jest.fn();
|
|
68
|
+
context = {
|
|
69
|
+
editorStore: store,
|
|
70
|
+
csrfToken: "token",
|
|
71
|
+
library: jest.fn(),
|
|
72
|
+
admin: { isLibraryManager: () => true },
|
|
73
|
+
};
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const renderContainer = () =>
|
|
77
|
+
render(
|
|
78
|
+
<LegacyContextProvider context={context}>
|
|
79
|
+
<BookDetailsContainer
|
|
80
|
+
book={bookData}
|
|
81
|
+
bookUrl="book url"
|
|
82
|
+
collectionUrl="collection url"
|
|
83
|
+
refreshCatalog={refreshCatalog}
|
|
84
|
+
>
|
|
85
|
+
<BookDetails book={bookData} updateBook={jest.fn()} />
|
|
86
|
+
</BookDetailsContainer>
|
|
87
|
+
</LegacyContextProvider>
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
it("renders BookDetails with its child's props", () => {
|
|
91
|
+
renderContainer();
|
|
92
|
+
// The container recreates the real BookDetails from the child element's props,
|
|
93
|
+
// so the book it was handed shows up (its title is rendered as the heading).
|
|
94
|
+
expect(
|
|
95
|
+
screen.getByRole("heading", { name: "book title" })
|
|
96
|
+
).toBeInTheDocument();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("shows a tab container with initial tab", () => {
|
|
100
|
+
renderContainer();
|
|
101
|
+
const tabContainer = screen.getByTestId("book-details-tab-container");
|
|
102
|
+
expect(tabContainer).toBeInTheDocument();
|
|
103
|
+
expect(tabContainer).toHaveAttribute("data-book-url", "book url");
|
|
104
|
+
expect(tabContainer).toHaveAttribute(
|
|
105
|
+
"data-collection-url",
|
|
106
|
+
"collection url"
|
|
107
|
+
);
|
|
108
|
+
expect(tabContainer).toHaveAttribute("data-csrf-token", "token");
|
|
109
|
+
// The library function, redux store, and refreshCatalog callback are forwarded
|
|
110
|
+
// from context/props; they are not serializable to the DOM, so assert they were
|
|
111
|
+
// passed through.
|
|
112
|
+
expect(tabContainer).toHaveAttribute("data-has-store", "true");
|
|
113
|
+
expect(tabContainer).toHaveAttribute("data-has-library", "true");
|
|
114
|
+
expect(tabContainer).toHaveAttribute("data-has-refresh-catalog", "true");
|
|
115
|
+
});
|
|
116
|
+
});
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { screen } from "@testing-library/react";
|
|
3
|
+
|
|
4
|
+
import { renderWithProviders } from "../testUtils/withProviders";
|
|
5
|
+
|
|
6
|
+
// Header and Footer are page chrome; Header pulls in the react-router v3 context
|
|
7
|
+
// that `renderWithProviders` does not supply. Mock them to markers.
|
|
8
|
+
jest.mock("../../../src/components/Header", () => ({
|
|
9
|
+
__esModule: true,
|
|
10
|
+
default: () => <div data-testid="header" />,
|
|
11
|
+
}));
|
|
12
|
+
jest.mock("../../../src/components/Footer", () => ({
|
|
13
|
+
__esModule: true,
|
|
14
|
+
default: () => <div data-testid="footer" />,
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
// ConfigTabContainer mounts the connected configuration sections (Libraries,
|
|
18
|
+
// Collections, Individual Admins, ...) which fetch over the network on mount. That
|
|
19
|
+
// is incidental to what ConfigPage composes — the page's job is to hand the tab
|
|
20
|
+
// container the params and context it needs — so mock it to a marker that echoes
|
|
21
|
+
// the props it received.
|
|
22
|
+
jest.mock("../../../src/components/ConfigTabContainer", () => ({
|
|
23
|
+
__esModule: true,
|
|
24
|
+
default: (props: any) => (
|
|
25
|
+
<div
|
|
26
|
+
data-testid="config-tab-container"
|
|
27
|
+
data-tab={props.tab}
|
|
28
|
+
data-edit-or-create={props.editOrCreate}
|
|
29
|
+
data-identifier={props.identifier}
|
|
30
|
+
data-csrf-token={props.csrfToken}
|
|
31
|
+
data-has-store={String(!!props.store)}
|
|
32
|
+
/>
|
|
33
|
+
),
|
|
34
|
+
}));
|
|
35
|
+
|
|
36
|
+
import ConfigPage from "../../../src/components/ConfigPage";
|
|
37
|
+
|
|
38
|
+
describe("ConfigPage", () => {
|
|
39
|
+
const params = {
|
|
40
|
+
tab: "libraries",
|
|
41
|
+
editOrCreate: "edit",
|
|
42
|
+
identifier: "identifier",
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
it("shows a tab container with tab from params", () => {
|
|
46
|
+
renderWithProviders(<ConfigPage params={params} />, {
|
|
47
|
+
appConfigSettings: { csrfToken: "token" },
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const tabContainer = screen.getByTestId("config-tab-container");
|
|
51
|
+
expect(tabContainer).toHaveAttribute("data-tab", "libraries");
|
|
52
|
+
expect(tabContainer).toHaveAttribute("data-edit-or-create", "edit");
|
|
53
|
+
expect(tabContainer).toHaveAttribute("data-identifier", "identifier");
|
|
54
|
+
expect(tabContainer).toHaveAttribute("data-csrf-token", "token");
|
|
55
|
+
// The redux store is forwarded from context; assert it was passed through.
|
|
56
|
+
expect(tabContainer).toHaveAttribute("data-has-store", "true");
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { screen } from "@testing-library/react";
|
|
3
|
+
|
|
4
|
+
import { renderWithProviders } from "../testUtils/withProviders";
|
|
5
|
+
|
|
6
|
+
// Header and Footer are page chrome; Header pulls in the react-router v3 context
|
|
7
|
+
// that `renderWithProviders` does not supply. Mock them to markers.
|
|
8
|
+
jest.mock("../../../src/components/Header", () => ({
|
|
9
|
+
__esModule: true,
|
|
10
|
+
default: () => <div data-testid="header" />,
|
|
11
|
+
}));
|
|
12
|
+
jest.mock("../../../src/components/Footer", () => ({
|
|
13
|
+
__esModule: true,
|
|
14
|
+
default: () => <div data-testid="footer" />,
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
// CustomLists is a connected component that fetches lists, collections, libraries,
|
|
18
|
+
// and languages over the network on mount. That is incidental to what
|
|
19
|
+
// CustomListPage composes — the page's job is to hand the editor the params and
|
|
20
|
+
// context it needs — so mock it to a marker that echoes the props it received.
|
|
21
|
+
jest.mock("../../../src/components/CustomLists", () => ({
|
|
22
|
+
__esModule: true,
|
|
23
|
+
default: (props: any) => (
|
|
24
|
+
<div
|
|
25
|
+
data-testid="custom-lists"
|
|
26
|
+
data-library={props.library}
|
|
27
|
+
data-edit-or-create={props.editOrCreate}
|
|
28
|
+
data-identifier={props.identifier}
|
|
29
|
+
data-csrf-token={props.csrfToken}
|
|
30
|
+
data-has-store={String(!!props.store)}
|
|
31
|
+
/>
|
|
32
|
+
),
|
|
33
|
+
}));
|
|
34
|
+
|
|
35
|
+
import CustomListPage from "../../../src/components/CustomListPage";
|
|
36
|
+
|
|
37
|
+
describe("CustomListPage", () => {
|
|
38
|
+
const params = {
|
|
39
|
+
library: "library",
|
|
40
|
+
editOrCreate: "edit",
|
|
41
|
+
identifier: "identifier",
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
it("shows custom lists with info from params", () => {
|
|
45
|
+
renderWithProviders(<CustomListPage params={params} />, {
|
|
46
|
+
appConfigSettings: { csrfToken: "token" },
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const customLists = screen.getByTestId("custom-lists");
|
|
50
|
+
expect(customLists).toHaveAttribute("data-library", "library");
|
|
51
|
+
expect(customLists).toHaveAttribute("data-edit-or-create", "edit");
|
|
52
|
+
expect(customLists).toHaveAttribute("data-identifier", "identifier");
|
|
53
|
+
expect(customLists).toHaveAttribute("data-csrf-token", "token");
|
|
54
|
+
// The redux store is forwarded from context; assert it was passed through.
|
|
55
|
+
expect(customLists).toHaveAttribute("data-has-store", "true");
|
|
56
|
+
});
|
|
57
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { screen } from "@testing-library/react";
|
|
3
|
+
|
|
4
|
+
import { renderWithProviders } from "../testUtils/withProviders";
|
|
5
|
+
|
|
6
|
+
// Header and Footer are page chrome; Header pulls in the react-router v3 context
|
|
7
|
+
// that `renderWithProviders` does not supply. Mock them to markers.
|
|
8
|
+
jest.mock("../../../src/components/Header", () => ({
|
|
9
|
+
__esModule: true,
|
|
10
|
+
default: () => <div data-testid="header" />,
|
|
11
|
+
}));
|
|
12
|
+
jest.mock("../../../src/components/Footer", () => ({
|
|
13
|
+
__esModule: true,
|
|
14
|
+
default: () => <div data-testid="footer" />,
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
// Stats fetches statistics via RTK Query on mount; DashboardPage's job is just
|
|
18
|
+
// composition, so mock Stats to a marker that echoes the `library` prop it is
|
|
19
|
+
// handed rather than wiring up fetch mocking.
|
|
20
|
+
jest.mock("../../../src/components/Stats", () => ({
|
|
21
|
+
__esModule: true,
|
|
22
|
+
default: (props: any) => (
|
|
23
|
+
<div data-testid="stats" data-library={props.library} />
|
|
24
|
+
),
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
// CirculationEventsDownload gates its output on a feature flag (off by default) and
|
|
28
|
+
// keeps the `library` prop inside a collapsed form, so it is not DOM-observable when
|
|
29
|
+
// rendered for real. Mock it to a marker that echoes `library` so the prop-flow the
|
|
30
|
+
// original test checked stays observable.
|
|
31
|
+
jest.mock("../../../src/components/CirculationEventsDownload", () => ({
|
|
32
|
+
__esModule: true,
|
|
33
|
+
default: (props: any) => (
|
|
34
|
+
<div
|
|
35
|
+
data-testid="circulation-events-download"
|
|
36
|
+
data-library={props.library}
|
|
37
|
+
/>
|
|
38
|
+
),
|
|
39
|
+
}));
|
|
40
|
+
|
|
41
|
+
import DashboardPage from "../../../src/components/DashboardPage";
|
|
42
|
+
|
|
43
|
+
describe("DashboardPage", () => {
|
|
44
|
+
it("shows Header", () => {
|
|
45
|
+
renderWithProviders(<DashboardPage params={{}} />);
|
|
46
|
+
expect(screen.getByTestId("header")).toBeInTheDocument();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("shows CirculationEventsDownload", () => {
|
|
50
|
+
const { rerender } = renderWithProviders(<DashboardPage params={{}} />);
|
|
51
|
+
|
|
52
|
+
let events = screen.getByTestId("circulation-events-download");
|
|
53
|
+
expect(events).toBeInTheDocument();
|
|
54
|
+
expect(events).not.toHaveAttribute("data-library");
|
|
55
|
+
|
|
56
|
+
rerender(<DashboardPage params={{ library: "NYPL" }} />);
|
|
57
|
+
events = screen.getByTestId("circulation-events-download");
|
|
58
|
+
expect(events).toHaveAttribute("data-library", "NYPL");
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("shows Stats", () => {
|
|
62
|
+
const { rerender } = renderWithProviders(<DashboardPage params={{}} />);
|
|
63
|
+
|
|
64
|
+
let stats = screen.getByTestId("stats");
|
|
65
|
+
expect(stats).not.toHaveAttribute("data-library");
|
|
66
|
+
|
|
67
|
+
rerender(<DashboardPage params={{ library: "NYPL" }} />);
|
|
68
|
+
stats = screen.getByTestId("stats");
|
|
69
|
+
expect(stats).toHaveAttribute("data-library", "NYPL");
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("shows Footer", () => {
|
|
73
|
+
renderWithProviders(<DashboardPage params={{}} />);
|
|
74
|
+
expect(screen.getByTestId("footer")).toBeInTheDocument();
|
|
75
|
+
});
|
|
76
|
+
});
|
|
@@ -212,11 +212,7 @@ describe("DebugAuthentication", () => {
|
|
|
212
212
|
)
|
|
213
213
|
);
|
|
214
214
|
|
|
215
|
-
const {
|
|
216
|
-
getByLabelText,
|
|
217
|
-
queryByLabelText,
|
|
218
|
-
rerender,
|
|
219
|
-
} = renderWithProviders(
|
|
215
|
+
const { getByLabelText, queryByLabelText, rerender } = renderWithProviders(
|
|
220
216
|
<DebugAuthentication library={LIBRARY} csrfToken="test-csrf-token" />,
|
|
221
217
|
{ queryClient }
|
|
222
218
|
);
|
|
@@ -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
|
+
});
|