@thepalaceproject/circulation-admin 1.43.0-post.2 → 1.43.0-post.4

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/jest.config.js CHANGED
@@ -1,31 +1,63 @@
1
1
  /** @type {import('ts-jest').JestConfigWithTsJest} */
2
- module.exports = {
2
+
3
+ // Options shared by both projects.
4
+ const sharedProjectConfig = {
5
+ preset: "ts-jest",
6
+ testEnvironmentOptions: {
7
+ // Without this, jsdom's default `["browser"]` export condition resolves sinon
8
+ // to its ESM bundle, which ts-jest does not transform. Every legacy suite then
9
+ // dies with `SyntaxError: Unexpected token 'export'`.
10
+ customExportConditions: [""],
11
+ },
12
+ setupFiles: ["<rootDir>/jest.polyfills.js"],
3
13
  moduleNameMapper: {
4
14
  "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$":
5
15
  "<rootDir>/tests/__mocks__/fileMock.js",
6
- "\\.(css|less)$": "<rootDir>/tests/__mocks__/styleMock.js",
16
+ "\\.(css|less|scss|sass)$": "<rootDir>/tests/__mocks__/styleMock.js",
7
17
  "\\.md$": "<rootDir>/tests/__mocks__/fileMock.js",
8
18
  },
9
- preset: "ts-jest",
10
- testEnvironment: "jest-fixed-jsdom",
11
- testEnvironmentOptions: {
12
- customExportConditions: [""],
13
- },
14
- setupFiles: ["./jest.polyfills.js"],
15
- setupFilesAfterEnv: ["./tests/jest/jest-setup.ts"],
16
19
  // `src` must stay in `roots` so that `collectCoverageFrom` can discover source
17
- // files that no jest test imports — otherwise they are silently omitted from
18
- // the report rather than counted as uncovered. `testMatch` (not `--roots`) is
19
- // therefore what keeps jest from picking up the legacy mocha tests that live
20
- // in `src/**/__tests__/`.
21
- roots: ["<rootDir>/src", "<rootDir>/tests/jest"],
22
- testMatch: ["<rootDir>/tests/jest/**/*.test.{js,jsx,ts,tsx}"],
20
+ // files that no test imports — otherwise they are silently omitted from the
21
+ // report rather than counted as uncovered, and the coverage percentage rises
22
+ // while real coverage is unchanged.
23
+ roots: ["<rootDir>/src"],
24
+ };
25
+
26
+ module.exports = {
23
27
  coverageDirectory: "coverage/jest",
24
28
  coverageReporters: ["lcov", "text-summary"],
29
+ // Must live at the top level, not inside `projects`: jest reads
30
+ // `collectCoverageFrom` off the *global* config. Set it only per-project and it
31
+ // is silently undefined globally, so every executed file gets instrumented —
32
+ // including test helpers and everything under `tests/`.
25
33
  collectCoverageFrom: [
26
34
  "src/**/*.{ts,tsx}",
27
35
  "!src/**/__tests__/**",
28
- "!src/testHelper.ts",
29
36
  "!src/**/*.d.ts",
30
37
  ],
38
+ projects: [
39
+ {
40
+ ...sharedProjectConfig,
41
+ displayName: "unit",
42
+ roots: [...sharedProjectConfig.roots, "<rootDir>/tests/jest"],
43
+ testMatch: ["<rootDir>/tests/jest/**/*.test.{js,jsx,ts,tsx}"],
44
+ // `jest-fixed-jsdom` restores Node's fetch/Response globals so MSW can
45
+ // intercept them.
46
+ testEnvironment: "jest-fixed-jsdom",
47
+ setupFilesAfterEnv: ["<rootDir>/tests/jest/jest-setup.ts"],
48
+ },
49
+ {
50
+ // Legacy enzyme + chai + sinon tests, still in `src/**/__tests__/`. They are
51
+ // being rewritten as React Testing Library tests under `tests/jest/`; when the
52
+ // last one is gone, delete this project, its environment, and its setup file.
53
+ //
54
+ // These cannot use `jest-fixed-jsdom`: it swaps in undici's `FormData`, which
55
+ // rejects `new FormData(formElement)` and crashes any test that submits a form.
56
+ ...sharedProjectConfig,
57
+ displayName: "legacy",
58
+ testMatch: ["<rootDir>/src/**/__tests__/*-test.{ts,tsx}"],
59
+ testEnvironment: "<rootDir>/tests/jest/legacyJsdomEnvironment.js",
60
+ setupFilesAfterEnv: ["<rootDir>/tests/jest/legacy-setup.ts"],
61
+ },
62
+ ],
31
63
  };
package/package.json CHANGED
@@ -17,20 +17,14 @@
17
17
  "prepublish": "npm run prod",
18
18
  "lint": "npm run lint:js && sass-lint -c .sass-lint.yml -v -q",
19
19
  "lint:js": "eslint . --max-warnings 0",
20
- "test-js": "tsc && cp -r src/stylesheets lib && cp -r src/images lib && mocha --require lib/testHelper.js lib/__tests__/*.js lib/**/__tests__/*.js lib/**/**/__tests__/*.js",
21
- "test-js-file": "tsc && cp -r src/stylesheets lib && cp -r src/images lib && mocha --require lib/testHelper.js",
22
- "test-js-list": "mocha --require lib/testHelper.js lib/__tests__/*.js lib/**/__tests__/*.js lib/**/**/__tests__/*.js --reporter ./testReporter.js",
23
- "test-ts": "mocha -r ts-node/register --require src/testHelper.ts src/__tests__/*.ts* src/**/__tests__/*.ts* src/**/**/__tests__/*.ts*",
24
- "test-file-ts": "mocha -r ts-node/register --require src/testHelper.ts",
25
- "test": "npm run test-ts && npm run test-jest",
26
- "test-file": "npm run test-file-ts",
20
+ "test": "npm run test-jest",
21
+ "test-file": "npm run test-jest-file",
27
22
  "test-browser": "npm run test-chrome && npm run test-firefox",
28
23
  "test-chrome": "node_modules/.bin/selenium-standalone install && nightwatch -e chrome",
29
24
  "test-firefox": "node_modules/.bin/selenium-standalone install && nightwatch -e firefox",
30
25
  "test-jest": "jest --verbose",
31
26
  "test-jest-file": "jest --verbose",
32
- "coverage": "npm run coverage-ts && npm run coverage-jest",
33
- "coverage-ts": "nyc npm run test-ts",
27
+ "coverage": "npm run coverage-jest",
34
28
  "coverage-jest": "npm run test-jest -- --coverage",
35
29
  "dev": "webpack --watch --progress --config webpack.dev.config",
36
30
  "dev-server": "dotenv -c -- webpack serve --progress --hot --config webpack.dev-server.config",
@@ -81,7 +75,6 @@
81
75
  "@testing-library/react-hooks": "^8.0.1",
82
76
  "@testing-library/user-event": "^14.4.3",
83
77
  "@types/jest": "^29.2.6",
84
- "@types/mocha": "^10.0.1",
85
78
  "@types/node": "^25.9.3",
86
79
  "@types/numeral": "^2.0.5",
87
80
  "@types/prop-types": "^15.7.3",
@@ -93,7 +86,6 @@
93
86
  "@types/redux-mock-store": "^1.0.3",
94
87
  "chai": "4.2.0",
95
88
  "clean-webpack-plugin": "^2.0.1",
96
- "colors-cli": "^1.0.27",
97
89
  "copy-webpack-plugin": "14.0.0",
98
90
  "css-loader": "^7.1.4",
99
91
  "dotenv-cli": "^11.0.0",
@@ -114,18 +106,14 @@
114
106
  "jest": "^29.3.1",
115
107
  "jest-environment-jsdom": "^30.4.1",
116
108
  "jest-fixed-jsdom": "^0.0.11",
117
- "jsdom": "^29.1.1",
118
109
  "json-loader": "^0.5.4",
119
110
  "lint-staged": "^17.0.7",
120
111
  "marked": "^18.0.5",
121
112
  "mini-css-extract-plugin": "1.6.0",
122
- "mocha": "^11.7.6",
123
113
  "msw": "^2.7.3",
124
114
  "nightwatch": "^3.14.0",
125
- "nyc": "^17.1.0",
126
115
  "prettier": "3.8.4",
127
116
  "react-axe": "^3.3.0",
128
- "react-test-renderer": "^16.14.0",
129
117
  "redux-mock-store": "^1.5.4",
130
118
  "resize-observer-polyfill": "^1.5.1",
131
119
  "sass": "^1.64.2",
@@ -137,7 +125,6 @@
137
125
  "terser-webpack-plugin": "^5.3.6",
138
126
  "ts-jest": "^29.2.6",
139
127
  "ts-loader": "^9.5.2",
140
- "ts-node": "^10.9.2",
141
128
  "typedoc": "^0.28.19",
142
129
  "typescript": "^5.7.3",
143
130
  "typescript-eslint": "^8.60.1",
@@ -157,5 +144,5 @@
157
144
  "*.{js,jsx,ts,tsx,css,md}": "prettier --write",
158
145
  "*.{js,css,md}": "prettier --write"
159
146
  },
160
- "version": "1.43.0-post.2"
147
+ "version": "1.43.0-post.4"
161
148
  }
@@ -0,0 +1,245 @@
1
+ import adapter from "../../src/editorAdapter";
2
+ import {
3
+ OPDSEntry,
4
+ Contributor,
5
+ Series,
6
+ Category,
7
+ Summary,
8
+ } from "opds-feed-parser";
9
+
10
+ describe("editorAdapter", () => {
11
+ it("adapts valid OPDS entry", () => {
12
+ const entry = new OPDSEntry({
13
+ id: "id",
14
+ updated: "updated",
15
+ title: "title",
16
+ authors: [new Contributor({ name: "name", uri: "uri", role: "role" })],
17
+ subtitle: "subtitle",
18
+ contributors: [
19
+ new Contributor({ name: "name2", uri: "uri2", role: "role2" }),
20
+ ],
21
+ series: new Series({ name: "series", position: 2 }),
22
+ categories: [
23
+ new Category({ term: "term", scheme: "scheme", label: "label" }),
24
+ new Category({
25
+ term: "13-16",
26
+ scheme: "http://schema.org/typicalAgeRange",
27
+ label: "age",
28
+ }),
29
+ new Category({
30
+ term: "ya",
31
+ scheme: "http://schema.org/audience",
32
+ label: "ya",
33
+ }),
34
+ new Category({
35
+ term: "fiction",
36
+ scheme: "http://librarysimplified.org/terms/fiction/",
37
+ label: "Fiction",
38
+ }),
39
+ ],
40
+ identifiers: [],
41
+ links: [
42
+ {
43
+ href: "hide",
44
+ rel: "http://librarysimplified.org/terms/rel/hide",
45
+ type: "type",
46
+ title: "title",
47
+ role: "role",
48
+ },
49
+ {
50
+ href: "restore",
51
+ rel: "http://librarysimplified.org/terms/rel/restore",
52
+ type: "type",
53
+ title: "title",
54
+ role: "role",
55
+ },
56
+ {
57
+ href: "refresh",
58
+ rel: "http://librarysimplified.org/terms/rel/refresh",
59
+ type: "type",
60
+ title: "title",
61
+ role: "role",
62
+ },
63
+ {
64
+ href: "edit",
65
+ rel: "edit",
66
+ type: "type",
67
+ title: "title",
68
+ role: "role",
69
+ },
70
+ {
71
+ href: "issues",
72
+ rel: "issues",
73
+ type: "type",
74
+ title: "title",
75
+ role: "role",
76
+ },
77
+ {
78
+ href: "change-cover",
79
+ rel: "http://librarysimplified.org/terms/rel/change_cover",
80
+ type: "type",
81
+ title: "title",
82
+ role: "role",
83
+ },
84
+ {
85
+ href: "cover",
86
+ rel: "http://opds-spec.org/image",
87
+ type: "image/png",
88
+ title: "title",
89
+ role: "role",
90
+ },
91
+ ],
92
+ issued: "issued",
93
+ language: "language",
94
+ rights: "rights",
95
+ publisher: "publisher",
96
+ published: "published",
97
+ summary: new Summary({ content: "content", link: "link" }),
98
+ unparsed: {
99
+ "schema:alternativeHeadline": [{ _: "subtitle" }],
100
+ $: {
101
+ "schema:additionalType": { value: "medium" },
102
+ },
103
+ "bib:publisherImprint": [{ _: "imprint" }],
104
+ "schema:Rating": [
105
+ {
106
+ $: {
107
+ "schema:ratingValue": { value: "0.3" },
108
+ "schema:additionalType": {
109
+ value: "http://librarysimplified.org/terms/rel/quality",
110
+ },
111
+ },
112
+ },
113
+ {
114
+ $: {
115
+ "schema:ratingValue": { value: "4" },
116
+ "schema:additionalType": {
117
+ value: "http://schema.org/ratingValue",
118
+ },
119
+ },
120
+ },
121
+ ],
122
+ },
123
+ });
124
+
125
+ const adapted = adapter(entry);
126
+ expect(adapted.title).toBe("title");
127
+ expect(adapted.authors).toStrictEqual([
128
+ { name: "name", uri: "uri", role: "aut" },
129
+ ]);
130
+ // The adapter flattens `Contributor` instances into plain objects, so assert
131
+ // the shape it produces rather than comparing against `entry.contributors`.
132
+ expect(adapted.contributors).toStrictEqual([
133
+ { name: "name2", uri: "uri2", role: "role2" },
134
+ ]);
135
+ expect(adapted.subtitle).toBe("subtitle");
136
+ expect(adapted.summary).toBe("content");
137
+ expect(adapted.audience).toBe("ya");
138
+ expect(adapted.targetAgeRange).toStrictEqual(["13", "16"]);
139
+ expect(adapted.fiction).toBe(true);
140
+ expect(adapted.categories).toStrictEqual(["label", "age", "ya", "Fiction"]);
141
+ expect(adapted.hideLink).toStrictEqual(entry.links[0]);
142
+ expect(adapted.restoreLink).toStrictEqual(entry.links[1]);
143
+ expect(adapted.refreshLink).toStrictEqual(entry.links[2]);
144
+ expect(adapted.editLink).toStrictEqual(entry.links[3]);
145
+ expect(adapted.issuesLink).toStrictEqual(entry.links[4]);
146
+ expect(adapted.changeCoverLink).toStrictEqual(entry.links[5]);
147
+ expect(adapted.coverUrl).toBe(entry.links[6].href);
148
+ expect(adapted.series).toBe("series");
149
+ expect(adapted.seriesPosition).toBe(2);
150
+ expect(adapted.medium).toBe("medium");
151
+ expect(adapted.language).toBe("language");
152
+ expect(adapted.publisher).toBe("publisher");
153
+ expect(adapted.imprint).toBe("imprint");
154
+ expect(adapted.issued).toBe("issued");
155
+ expect(adapted.rating).toBe("4");
156
+ });
157
+
158
+ it("adapts visibility status from categories", () => {
159
+ const entry = new OPDSEntry({
160
+ id: "id",
161
+ updated: "updated",
162
+ title: "title",
163
+ subtitle: "subtitle",
164
+ authors: [],
165
+ contributors: [],
166
+ series: new Series({ name: "series", position: 2 }),
167
+ categories: [
168
+ new Category({ term: "term", scheme: "scheme", label: "label" }),
169
+ new Category({
170
+ term: "manually-suppressed",
171
+ scheme: "http://palaceproject.io/terms/visibility-status",
172
+ label: "Manually Suppressed",
173
+ }),
174
+ ],
175
+ identifiers: [],
176
+ links: [],
177
+ issued: "issued",
178
+ language: "language",
179
+ rights: "rights",
180
+ publisher: "publisher",
181
+ published: "published",
182
+ summary: new Summary({ content: "content", link: "link" }),
183
+ unparsed: {},
184
+ });
185
+
186
+ const adapted = adapter(entry);
187
+ expect(adapted.visibilityStatus).toBe("manually-suppressed");
188
+ });
189
+
190
+ it("adapts policy-filtered visibility status", () => {
191
+ const entry = new OPDSEntry({
192
+ id: "id",
193
+ updated: "updated",
194
+ title: "title",
195
+ subtitle: "subtitle",
196
+ authors: [],
197
+ contributors: [],
198
+ series: new Series({ name: "series", position: 2 }),
199
+ categories: [
200
+ new Category({
201
+ term: "policy-filtered",
202
+ scheme: "http://palaceproject.io/terms/visibility-status",
203
+ label: "Policy Filtered",
204
+ }),
205
+ ],
206
+ identifiers: [],
207
+ links: [],
208
+ issued: "issued",
209
+ language: "language",
210
+ rights: "rights",
211
+ publisher: "publisher",
212
+ published: "published",
213
+ summary: new Summary({ content: "content", link: "link" }),
214
+ unparsed: {},
215
+ });
216
+
217
+ const adapted = adapter(entry);
218
+ expect(adapted.visibilityStatus).toBe("policy-filtered");
219
+ });
220
+
221
+ it("doesn't crash when expected data is missing", () => {
222
+ const entry = new OPDSEntry({
223
+ id: "id",
224
+ updated: "updated",
225
+ title: "title",
226
+ subtitle: "subtitle",
227
+ authors: [],
228
+ contributors: [],
229
+ series: new Series({ name: "series", position: 2 }),
230
+ categories: [],
231
+ identifiers: [],
232
+ links: [],
233
+ issued: "issued",
234
+ language: "language",
235
+ rights: "rights",
236
+ publisher: "publisher",
237
+ published: "published",
238
+ summary: new Summary({ content: "content", link: "link" }),
239
+ unparsed: {},
240
+ });
241
+
242
+ const adapted = adapter(entry);
243
+ expect(adapted.title).toBe("title");
244
+ });
245
+ });
@@ -0,0 +1,13 @@
1
+ import { configure } from "enzyme";
2
+ import * as Adapter from "enzyme-adapter-react-16";
3
+
4
+ // Setup for the legacy enzyme + chai + sinon tests under `src/**/__tests__/`.
5
+ // Replaces what `src/testHelper.ts` did for mocha. Delete alongside the last
6
+ // enzyme test.
7
+
8
+ configure({ adapter: new Adapter() });
9
+
10
+ // Unhandled promise rejections are tolerated for these tests — see the comment in
11
+ // `legacyJsdomEnvironment.js`. It cannot be done from here: jest hands test code a
12
+ // *copy* of `process`, so a listener registered in this file never reaches the real
13
+ // one that Node checks before throwing.
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+
3
+ const JSDOMEnvironment = require("jest-environment-jsdom").default;
4
+
5
+ const warnUnhandledRejection = (reason) => {
6
+ console.warn("Unhandled promise rejection in a legacy test:", reason);
7
+ };
8
+
9
+ /**
10
+ * jsdom environment for the legacy enzyme tests. Delete it with the last one.
11
+ *
12
+ * Two things it restores that `src/testHelper.ts` used to provide under mocha:
13
+ *
14
+ * 1. `global.jsdom`. A handful of tests call `global.jsdom.reconfigure({ url })` to
15
+ * change the page origin mid-test. `history.replaceState` is not a substitute —
16
+ * they navigate across origins (`http://example.com`), which it cannot do.
17
+ *
18
+ * 2. Tolerance for unhandled promise rejections. Several suites stub a `fetch` that
19
+ * rejects, and nothing awaits the result; the rejection surfaces after the test
20
+ * has already passed. Mocha shrugged those off because its runner keeps an
21
+ * `unhandledRejection` listener installed, and Node only throws when there are
22
+ * none. Under jest they kill the worker and take the whole suite with them.
23
+ *
24
+ * Re-arming has to happen on the `teardown` event, not in the constructor.
25
+ * `jest-circus/globalErrorHandlers` reads `process.listeners()` off the sandbox's
26
+ * *copied* `process` (always empty) but calls `removeAllListeners()` on the real
27
+ * one. So any listener registered earlier is dropped at `setup` and "restored"
28
+ * from an empty list, leaving zero listeners during the post-run window where
29
+ * these rejections actually land.
30
+ *
31
+ * Scoping this to the legacy environment keeps the modern `tests/jest/` suite
32
+ * strict: jest clears listeners at each file's `setup`, and nothing re-arms one
33
+ * for a project that does not use this environment.
34
+ */
35
+ module.exports = class LegacyJSDOMEnvironment extends JSDOMEnvironment {
36
+ constructor(config, context) {
37
+ super(config, context);
38
+ this.global.jsdom = this.dom;
39
+ }
40
+
41
+ handleTestEvent(event) {
42
+ if (
43
+ event.name === "teardown" &&
44
+ process.listenerCount("unhandledRejection") === 0
45
+ ) {
46
+ process.on("unhandledRejection", warnUnhandledRejection);
47
+ }
48
+ }
49
+ };
@@ -0,0 +1,206 @@
1
+ import Admin from "../../../src/models/Admin";
2
+
3
+ describe("Admin", () => {
4
+ const systemAdmin = new Admin([{ role: "system" }]);
5
+ const sitewideLibraryManager = new Admin([{ role: "manager-all" }]);
6
+ const sitewideLibrarian = new Admin([{ role: "librarian-all" }]);
7
+ const libraryManagerA = new Admin([{ role: "manager", library: "a" }]);
8
+ const librarianA = new Admin([{ role: "librarian", library: "a" }]);
9
+ const libraryManagerB = new Admin([{ role: "manager", library: "b" }]);
10
+ const librarianB = new Admin([{ role: "librarian", library: "b" }]);
11
+ const libraryManagerASitewideLibrarian = new Admin([
12
+ { role: "librarian-all" },
13
+ { role: "manager", library: "a" },
14
+ ]);
15
+ const libraryManagerALibrarianB = new Admin([
16
+ { role: "manager", library: "a" },
17
+ { role: "librarian", library: "b" },
18
+ ]);
19
+
20
+ it("stores email", () => {
21
+ expect(new Admin([], "email").email).toBe("email");
22
+ expect(new Admin([]).email).toBeFalsy();
23
+ });
24
+
25
+ it("identifies system admins", () => {
26
+ expect(systemAdmin.isSystemAdmin()).toBeTruthy();
27
+ expect(sitewideLibraryManager.isSystemAdmin()).toBeFalsy();
28
+ expect(sitewideLibrarian.isSystemAdmin()).toBeFalsy();
29
+ expect(libraryManagerA.isSystemAdmin()).toBeFalsy();
30
+ expect(librarianA.isSystemAdmin()).toBeFalsy();
31
+ expect(libraryManagerB.isSystemAdmin()).toBeFalsy();
32
+ expect(librarianB.isSystemAdmin()).toBeFalsy();
33
+ expect(libraryManagerASitewideLibrarian.isSystemAdmin()).toBeFalsy();
34
+ expect(libraryManagerALibrarianB.isSystemAdmin()).toBeFalsy();
35
+ });
36
+
37
+ it("identifies sitewide library managers", () => {
38
+ expect(systemAdmin.isSitewideLibraryManager()).toBeTruthy();
39
+ expect(sitewideLibraryManager.isSitewideLibraryManager()).toBeTruthy();
40
+ expect(sitewideLibrarian.isSitewideLibraryManager()).toBeFalsy();
41
+ expect(libraryManagerA.isSitewideLibraryManager()).toBeFalsy();
42
+ expect(librarianA.isSitewideLibraryManager()).toBeFalsy();
43
+ expect(libraryManagerB.isSitewideLibraryManager()).toBeFalsy();
44
+ expect(librarianB.isSitewideLibraryManager()).toBeFalsy();
45
+ expect(
46
+ libraryManagerASitewideLibrarian.isSitewideLibraryManager()
47
+ ).toBeFalsy();
48
+ expect(libraryManagerALibrarianB.isSitewideLibraryManager()).toBeFalsy();
49
+ });
50
+
51
+ it("identifies sitewide librarians", () => {
52
+ expect(systemAdmin.isSitewideLibrarian()).toBeTruthy();
53
+ expect(sitewideLibraryManager.isSitewideLibrarian()).toBeTruthy();
54
+ expect(sitewideLibrarian.isSitewideLibrarian()).toBeTruthy();
55
+ expect(libraryManagerA.isSitewideLibrarian()).toBeFalsy();
56
+ expect(librarianA.isSitewideLibrarian()).toBeFalsy();
57
+ expect(libraryManagerB.isSitewideLibrarian()).toBeFalsy();
58
+ expect(librarianB.isSitewideLibrarian()).toBeFalsy();
59
+ expect(libraryManagerASitewideLibrarian.isSitewideLibrarian()).toBeTruthy();
60
+ expect(libraryManagerALibrarianB.isSitewideLibrarian()).toBeFalsy();
61
+ });
62
+
63
+ it("identifies library managers", () => {
64
+ expect(systemAdmin.isLibraryManager("a")).toBeTruthy();
65
+ expect(sitewideLibraryManager.isLibraryManager("a")).toBeTruthy();
66
+ expect(sitewideLibrarian.isLibraryManager("a")).toBeFalsy();
67
+ expect(libraryManagerA.isLibraryManager("a")).toBeTruthy();
68
+ expect(librarianA.isLibraryManager("a")).toBeFalsy();
69
+ expect(libraryManagerB.isLibraryManager("a")).toBeFalsy();
70
+ expect(librarianB.isLibraryManager("a")).toBeFalsy();
71
+ expect(libraryManagerASitewideLibrarian.isLibraryManager("a")).toBeTruthy();
72
+ expect(libraryManagerALibrarianB.isLibraryManager("a")).toBeTruthy();
73
+
74
+ expect(systemAdmin.isLibraryManager("b")).toBeTruthy();
75
+ expect(sitewideLibraryManager.isLibraryManager("b")).toBeTruthy();
76
+ expect(sitewideLibrarian.isLibraryManager("b")).toBeFalsy();
77
+ expect(libraryManagerA.isLibraryManager("b")).toBeFalsy();
78
+ expect(librarianA.isLibraryManager("b")).toBeFalsy();
79
+ expect(libraryManagerB.isLibraryManager("b")).toBeTruthy();
80
+ expect(librarianB.isLibraryManager("b")).toBeFalsy();
81
+ expect(libraryManagerASitewideLibrarian.isLibraryManager("b")).toBeFalsy();
82
+ expect(libraryManagerALibrarianB.isLibraryManager("b")).toBeFalsy();
83
+ });
84
+
85
+ it("identifies librarians", () => {
86
+ expect(systemAdmin.isLibrarian("a")).toBeTruthy();
87
+ expect(sitewideLibraryManager.isLibrarian("a")).toBeTruthy();
88
+ expect(sitewideLibrarian.isLibrarian("a")).toBeTruthy();
89
+ expect(libraryManagerA.isLibrarian("a")).toBeTruthy();
90
+ expect(librarianA.isLibrarian("a")).toBeTruthy();
91
+ expect(libraryManagerB.isLibrarian("a")).toBeFalsy();
92
+ expect(librarianB.isLibrarian("a")).toBeFalsy();
93
+ expect(libraryManagerASitewideLibrarian.isLibrarian("a")).toBeTruthy();
94
+ expect(libraryManagerALibrarianB.isLibrarian("a")).toBeTruthy();
95
+
96
+ expect(systemAdmin.isLibrarian("b")).toBeTruthy();
97
+ expect(sitewideLibraryManager.isLibrarian("b")).toBeTruthy();
98
+ expect(sitewideLibrarian.isLibrarian("b")).toBeTruthy();
99
+ expect(libraryManagerA.isLibrarian("b")).toBeFalsy();
100
+ expect(librarianA.isLibrarian("b")).toBeFalsy();
101
+ expect(libraryManagerB.isLibrarian("b")).toBeTruthy();
102
+ expect(librarianB.isLibrarian("b")).toBeTruthy();
103
+ expect(libraryManagerASitewideLibrarian.isLibrarian("b")).toBeTruthy();
104
+ expect(libraryManagerALibrarianB.isLibrarian("b")).toBeTruthy();
105
+ });
106
+
107
+ it("identifies all library managers", () => {
108
+ expect(systemAdmin.isLibraryManagerOfSomeLibrary()).toBeTruthy();
109
+ expect(sitewideLibraryManager.isLibraryManagerOfSomeLibrary()).toBeTruthy();
110
+ expect(sitewideLibrarian.isLibraryManagerOfSomeLibrary()).toBeFalsy();
111
+ expect(libraryManagerA.isLibraryManagerOfSomeLibrary()).toBeTruthy();
112
+ expect(librarianA.isLibraryManagerOfSomeLibrary()).toBeFalsy();
113
+ expect(libraryManagerB.isLibraryManagerOfSomeLibrary()).toBeTruthy();
114
+ expect(librarianB.isLibraryManagerOfSomeLibrary()).toBeFalsy();
115
+ expect(
116
+ libraryManagerASitewideLibrarian.isLibraryManagerOfSomeLibrary()
117
+ ).toBeTruthy();
118
+ expect(
119
+ libraryManagerALibrarianB.isLibraryManagerOfSomeLibrary()
120
+ ).toBeTruthy();
121
+ });
122
+
123
+ it("checks for specific roles", () => {
124
+ expect(systemAdmin.hasRole("system")).toBeTruthy();
125
+ expect(systemAdmin.hasRole("manager-all")).toBeTruthy();
126
+ expect(systemAdmin.hasRole("librarian-all")).toBeTruthy();
127
+ expect(systemAdmin.hasRole("manager", "a")).toBeTruthy();
128
+ expect(systemAdmin.hasRole("librarian", "a")).toBeTruthy();
129
+ expect(systemAdmin.hasRole("manager", "b")).toBeTruthy();
130
+ expect(systemAdmin.hasRole("librarian", "b")).toBeTruthy();
131
+
132
+ expect(sitewideLibraryManager.hasRole("system")).toBeFalsy();
133
+ expect(sitewideLibraryManager.hasRole("manager-all")).toBeTruthy();
134
+ expect(sitewideLibraryManager.hasRole("librarian-all")).toBeTruthy();
135
+ expect(sitewideLibraryManager.hasRole("manager", "a")).toBeTruthy();
136
+ expect(sitewideLibraryManager.hasRole("librarian", "a")).toBeTruthy();
137
+ expect(sitewideLibraryManager.hasRole("manager", "b")).toBeTruthy();
138
+ expect(sitewideLibraryManager.hasRole("librarian", "b")).toBeTruthy();
139
+
140
+ expect(sitewideLibrarian.hasRole("system")).toBeFalsy();
141
+ expect(sitewideLibrarian.hasRole("manager-all")).toBeFalsy();
142
+ expect(sitewideLibrarian.hasRole("librarian-all")).toBeTruthy();
143
+ expect(sitewideLibrarian.hasRole("manager", "a")).toBeFalsy();
144
+ expect(sitewideLibrarian.hasRole("librarian", "a")).toBeTruthy();
145
+ expect(sitewideLibrarian.hasRole("manager", "b")).toBeFalsy();
146
+ expect(sitewideLibrarian.hasRole("librarian", "b")).toBeTruthy();
147
+
148
+ expect(libraryManagerA.hasRole("system")).toBeFalsy();
149
+ expect(libraryManagerA.hasRole("manager-all")).toBeFalsy();
150
+ expect(libraryManagerA.hasRole("librarian-all")).toBeFalsy();
151
+ expect(libraryManagerA.hasRole("manager", "a")).toBeTruthy();
152
+ expect(libraryManagerA.hasRole("librarian", "a")).toBeTruthy();
153
+ expect(libraryManagerA.hasRole("manager", "b")).toBeFalsy();
154
+ expect(libraryManagerA.hasRole("librarian", "b")).toBeFalsy();
155
+
156
+ expect(librarianA.hasRole("system")).toBeFalsy();
157
+ expect(librarianA.hasRole("manager-all")).toBeFalsy();
158
+ expect(librarianA.hasRole("librarian-all")).toBeFalsy();
159
+ expect(librarianA.hasRole("manager", "a")).toBeFalsy();
160
+ expect(librarianA.hasRole("librarian", "a")).toBeTruthy();
161
+ expect(librarianA.hasRole("manager", "b")).toBeFalsy();
162
+ expect(librarianA.hasRole("librarian", "b")).toBeFalsy();
163
+
164
+ expect(libraryManagerB.hasRole("system")).toBeFalsy();
165
+ expect(libraryManagerB.hasRole("manager-all")).toBeFalsy();
166
+ expect(libraryManagerB.hasRole("librarian-all")).toBeFalsy();
167
+ expect(libraryManagerB.hasRole("manager", "a")).toBeFalsy();
168
+ expect(libraryManagerB.hasRole("librarian", "a")).toBeFalsy();
169
+ expect(libraryManagerB.hasRole("manager", "b")).toBeTruthy();
170
+ expect(libraryManagerB.hasRole("librarian", "b")).toBeTruthy();
171
+
172
+ expect(librarianB.hasRole("system")).toBeFalsy();
173
+ expect(librarianB.hasRole("manager-all")).toBeFalsy();
174
+ expect(librarianB.hasRole("librarian-all")).toBeFalsy();
175
+ expect(librarianB.hasRole("manager", "a")).toBeFalsy();
176
+ expect(librarianB.hasRole("librarian", "a")).toBeFalsy();
177
+ expect(librarianB.hasRole("manager", "b")).toBeFalsy();
178
+ expect(librarianB.hasRole("librarian", "b")).toBeTruthy();
179
+
180
+ expect(libraryManagerASitewideLibrarian.hasRole("system")).toBeFalsy();
181
+ expect(libraryManagerASitewideLibrarian.hasRole("manager-all")).toBeFalsy();
182
+ expect(
183
+ libraryManagerASitewideLibrarian.hasRole("librarian-all")
184
+ ).toBeTruthy();
185
+ expect(
186
+ libraryManagerASitewideLibrarian.hasRole("manager", "a")
187
+ ).toBeTruthy();
188
+ expect(
189
+ libraryManagerASitewideLibrarian.hasRole("librarian", "a")
190
+ ).toBeTruthy();
191
+ expect(
192
+ libraryManagerASitewideLibrarian.hasRole("manager", "b")
193
+ ).toBeFalsy();
194
+ expect(
195
+ libraryManagerASitewideLibrarian.hasRole("librarian", "b")
196
+ ).toBeTruthy();
197
+
198
+ expect(libraryManagerALibrarianB.hasRole("system")).toBeFalsy();
199
+ expect(libraryManagerALibrarianB.hasRole("manager-all")).toBeFalsy();
200
+ expect(libraryManagerALibrarianB.hasRole("librarian-all")).toBeFalsy();
201
+ expect(libraryManagerALibrarianB.hasRole("manager", "a")).toBeTruthy();
202
+ expect(libraryManagerALibrarianB.hasRole("librarian", "a")).toBeTruthy();
203
+ expect(libraryManagerALibrarianB.hasRole("manager", "b")).toBeFalsy();
204
+ expect(libraryManagerALibrarianB.hasRole("librarian", "b")).toBeTruthy();
205
+ });
206
+ });