diff --git a/src/app/pages/blog/search-results/search-results.tsx b/src/app/pages/blog/search-results/search-results.tsx
index 31fb560f8..6f66ef730 100644
--- a/src/app/pages/blog/search-results/search-results.tsx
+++ b/src/app/pages/blog/search-results/search-results.tsx
@@ -7,7 +7,7 @@ import ArticleSummary, {
import usePaginatorContext, {
PaginatorContextProvider
} from '~/components/paginator/paginator-context';
-import {PaginatorControls} from '~/components/paginator/search-results/paginator.js';
+import {PaginatorControls} from '~/components/paginator/search-results/paginator';
import NoResults from './no-results';
import useAllArticles from './use-all-articles';
import './search-results.scss';
diff --git a/src/app/pages/errata-form/form/ErrorSourceSelector.js b/src/app/pages/errata-form/form/ErrorSourceSelector.js
index 17aa279a5..68fd6b231 100644
--- a/src/app/pages/errata-form/form/ErrorSourceSelector.js
+++ b/src/app/pages/errata-form/form/ErrorSourceSelector.js
@@ -1,6 +1,6 @@
import React, {useState, useRef, useMemo} from 'react';
import useErrataFormContext from '../errata-form-context';
-import managedInvalidMessage from './InvalidMessage.js';
+import managedInvalidMessage from './InvalidMessage';
import getFields from '~/models/errata-fields';
const sourceNames = {
diff --git a/src/app/pages/errata-form/form/ErrorTypeSelector.js b/src/app/pages/errata-form/form/ErrorTypeSelector.js
index 67b2cc8f7..6efcc195b 100644
--- a/src/app/pages/errata-form/form/ErrorTypeSelector.js
+++ b/src/app/pages/errata-form/form/ErrorTypeSelector.js
@@ -1,5 +1,5 @@
import React, {useState, useRef} from 'react';
-import managedInvalidMessage from './InvalidMessage.js';
+import managedInvalidMessage from './InvalidMessage';
function OtherErrorInput() {
const inputRef = useRef();
diff --git a/src/app/pages/errata-form/form/form.js b/src/app/pages/errata-form/form/form.js
index bf62aad8b..049b645d0 100644
--- a/src/app/pages/errata-form/form/form.js
+++ b/src/app/pages/errata-form/form/form.js
@@ -5,7 +5,7 @@ import ErrorTypeSelector from './ErrorTypeSelector';
import ErrorSourceSelector from './ErrorSourceSelector';
import ErrorLocationSelector from './ErrorLocationSelector/ErrorLocationSelector';
import FileUploader from './FileUploader';
-import managedInvalidMessage from './InvalidMessage.js';
+import managedInvalidMessage from './InvalidMessage';
import $ from '~/helpers/$';
import Dialog from '~/components/dialog/dialog';
import {useNavigate} from 'react-router-dom';
diff --git a/src/app/pages/renewal-form/renewal-form.tsx b/src/app/pages/renewal-form/renewal-form.tsx
index bf127440a..2a48eefca 100644
--- a/src/app/pages/renewal-form/renewal-form.tsx
+++ b/src/app/pages/renewal-form/renewal-form.tsx
@@ -5,7 +5,7 @@ import useUserContext from '~/contexts/user';
import useAdoptions, {Adoption} from '~/models/renewals';
import YearSelector from '~/components/year-selector/year-selector';
import TrackingParameters from '~/components/tracking-parameters/tracking-parameters';
-import {WindowWithSettings} from '~helpers/window-settings';
+import {WindowWithSettings} from '~/helpers/window-settings';
// -- We'll be trying to do this for the next release.
// import _adoptionsPromise from './salesforce-data';
import BookTagsMultiselect, {
diff --git a/src/styles/components/bucket.scss b/src/styles/components/bucket.scss
deleted file mode 100644
index 885aa9f46..000000000
--- a/src/styles/components/bucket.scss
+++ /dev/null
@@ -1,3 +0,0 @@
-.quote-bucket .image {
- background-repeat: no-repeat;
-}
diff --git a/src/styles/main.scss b/src/styles/main.scss
index ab3ec5ae1..c7bee738f 100755
--- a/src/styles/main.scss
+++ b/src/styles/main.scss
@@ -358,7 +358,6 @@ a:not([href]) {
}
@import 'components/animations';
-@import 'components/bucket';
@import 'components/buttons';
@import 'components/filter-buttons';
@import 'components/forms';
diff --git a/test/src/components/paginator.test.js b/test/src/components/paginator.test.js
deleted file mode 100644
index 880312d11..000000000
--- a/test/src/components/paginator.test.js
+++ /dev/null
@@ -1,28 +0,0 @@
-import React from 'react';
-import {render, screen} from '@testing-library/preact';
-import userEvent from '@testing-library/user-event';
-import {PaginatorContextProvider} from '~/components/paginator/paginator-context';
-import {PaginatorControls} from '~/components/paginator/search-results/paginator.js';
-import {test, expect} from '@jest/globals';
-
-function activePage() {
- const activeButton = screen.getByRole('button', {current: 'page'});
-
- return activeButton.textContent;
-}
-
-test('operates by button clicks', async () => {
- const user = userEvent.setup();
-
- render(
-
-
-
- );
- await user.click(screen.getByText('Next'));
- expect(activePage()).toBe('3');
- await user.click(screen.getByText('4'));
- expect(activePage()).toBe('4');
- await user.click(screen.getByText('Previous'));
- expect(activePage()).toBe('3');
-});
diff --git a/test/src/components/paginator.test.tsx b/test/src/components/paginator.test.tsx
new file mode 100644
index 000000000..8bf336ab7
--- /dev/null
+++ b/test/src/components/paginator.test.tsx
@@ -0,0 +1,45 @@
+import React from 'react';
+import {render, screen} from '@testing-library/preact';
+import userEvent from '@testing-library/user-event';
+import {PaginatorContextProvider} from '~/components/paginator/paginator-context';
+import {PaginatorControls} from '~/components/paginator/search-results/paginator';
+import SimplePaginator from '~/components/paginator/simple-paginator';
+
+function activePage() {
+ const activeButton = screen.getByRole('button', {current: 'page'});
+
+ return activeButton.textContent;
+}
+
+describe('paginator', () => {
+ const user = userEvent.setup();
+
+ it('operates by button clicks', async () => {
+ render(
+
+
+
+ );
+ await user.click(screen.getByText('Next'));
+ expect(activePage()).toBe('3');
+ await user.click(screen.getByText('4'));
+ expect(activePage()).toBe('4');
+ await user.click(screen.getByText('Previous'));
+ expect(activePage()).toBe('3');
+ });
+ it('does ellipses when beyond page 4', async () => {
+ const setPage = jest.fn();
+
+ render(
+
+ );
+
+ screen.getByText('…', {exact: false});
+ await user.click(screen.getByRole('link', {name: '10'}));
+ expect(setPage).toHaveBeenCalledWith(10);
+ });
+});
diff --git a/test/src/components/role-selector.test.tsx b/test/src/components/role-selector.test.tsx
new file mode 100644
index 000000000..0a8ed850a
--- /dev/null
+++ b/test/src/components/role-selector.test.tsx
@@ -0,0 +1,26 @@
+import React from 'react';
+import {render, screen} from '@testing-library/preact';
+import RoleSelector from '~/components/role-selector/role-selector';
+import {MemoryRouter} from 'react-router-dom';
+import {LanguageContextProvider} from '~/contexts/language';
+
+// @ts-expect-error does not exist on
+const {routerFuture} = global;
+
+describe('role-selector', () => {
+ it('renders student content', async () => {
+ render(
+
+
+
+ Student stuff
+ Instructor stuff
+
+
+
+ );
+ expect(
+ (await screen.findByRole('heading', {level: 1})).textContent
+ ).toBe('Student stuff');
+ });
+});
diff --git a/test/src/components/salesforce-form.test.tsx b/test/src/components/salesforce-form.test.tsx
new file mode 100644
index 000000000..c147c2959
--- /dev/null
+++ b/test/src/components/salesforce-form.test.tsx
@@ -0,0 +1,43 @@
+import React from 'react';
+import {fireEvent, render, screen} from '@testing-library/preact';
+import SalesforceForm from '~/components/salesforce-form/salesforce-form';
+import * as SFC from '~/contexts/salesforce';
+import userEvent from '@testing-library/user-event';
+
+const {SalesforceContextProvider} = SFC;
+const afterSubmit = jest.fn();
+
+function Component() {
+ return (
+
+
+
+ );
+}
+
+describe('salesforce-form', () => {
+ it('shows loading until oid is populated', () => {
+ render(
);
+ screen.getByText('Loading...');
+ });
+ it('shows form, submits', async () => {
+ jest.spyOn(SFC, 'default').mockReturnValue({
+ debug: true,
+ oid: 'something'
+ } as any); // eslint-disable-line @typescript-eslint/no-explicit-any
+ render(
+
+
+
+ );
+ // Can't actually submit in jest
+ screen.getByRole('form').onsubmit = (e) => {
+ e.preventDefault();
+ };
+ await userEvent.click(screen.getByRole('button'));
+ const iframe = document.querySelector('iframe') as HTMLIFrameElement;
+
+ fireEvent.load(iframe);
+ expect(afterSubmit).toHaveBeenCalled();
+ });
+});
diff --git a/test/src/pages/blog/article.test.tsx b/test/src/pages/blog/article.test.tsx
index 2f2efb5fa..ac48694f9 100644
--- a/test/src/pages/blog/article.test.tsx
+++ b/test/src/pages/blog/article.test.tsx
@@ -33,9 +33,9 @@ describe('blog/article', () => {
expect(onload).toHaveBeenCalled();
screen.getByText('2 min read');
- expect(document.body.querySelector('.quote-bucket .quote .attribution')).toBeTruthy();
+ expect(document.body.querySelector('.quote .attribution')).toBeTruthy();
// Bottom-aligned stuff comes at the end
- expect(document.body.querySelector('.quote-bucket ~ figure.bottom')).toBeTruthy();
+ expect(document.body.querySelector('.quote ~ figure.bottom')).toBeTruthy();
// CTA should be there
screen.getByRole('link', {name: 'click me'});
});
diff --git a/test/src/pages/errata/errata-summary.test.js b/test/src/pages/errata/errata-summary.test.js
deleted file mode 100644
index 133e2780e..000000000
--- a/test/src/pages/errata/errata-summary.test.js
+++ /dev/null
@@ -1,44 +0,0 @@
-import React from 'react';
-import {render, screen} from '@testing-library/preact';
-import {within} from '@testing-library/dom';
-import userEvent from '@testing-library/user-event';
-import ErrataSummaryLoader from '~/pages/errata-summary/errata-summary';
-import {MemoryRouter} from 'react-router-dom';
-import {test, expect} from '@jest/globals';
-
-const searchStr = '/errata/?book=Anatomy%20and%20Physiology';
-
-window.history.pushState('', '', searchStr);
-
-// This is complicated by the fact that there are two versions that
-// display at once, but one is hidden depending on screen resolution
-// which testing knows nothing about.
-// The desktop version is the last table; there are multiple tables
-// (one for each row) in the mobile version
-async function getTableRows() {
- const tables = await screen.findAllByRole('table');
-
- return within(tables.pop()).getAllByRole('row');
-}
-
-test('shows all items in table', async () => {
- render(
-
-
-
- );
- expect(await getTableRows()).toHaveLength(54);
-});
-test('filters', async () => {
- render(
-
-
-
- );
- const filters = await screen.findByRole('radiogroup');
- const user = userEvent.setup({delay: null});
- const reviewButton = within(filters).queryByText('In Review');
-
- await user.click(reviewButton);
- expect(await getTableRows()).toHaveLength(19);
-});
diff --git a/test/src/pages/errata/errata-summary.test.tsx b/test/src/pages/errata/errata-summary.test.tsx
new file mode 100644
index 000000000..43a3c9ff9
--- /dev/null
+++ b/test/src/pages/errata/errata-summary.test.tsx
@@ -0,0 +1,59 @@
+import React from 'react';
+import {render, screen, fireEvent} from '@testing-library/preact';
+import {within} from '@testing-library/dom';
+import userEvent from '@testing-library/user-event';
+import ErrataSummaryLoader from '~/pages/errata-summary/errata-summary';
+import {MemoryRouter} from 'react-router-dom';
+
+// @ts-expect-error does not exist on
+const {routerFuture} = global;
+
+const searchStr = '/errata/?book=Anatomy%20and%20Physiology';
+
+window.history.pushState('', '', searchStr);
+
+// This is complicated by the fact that there are two versions that
+// display at once, but one is hidden depending on screen resolution
+// which testing knows nothing about.
+// The desktop version is the last table; there are multiple tables
+// (one for each row) in the mobile version
+async function getTableRows() {
+ const tables = await screen.findAllByRole('table');
+
+ return within(tables.pop() as HTMLElement).getAllByRole('row');
+}
+
+describe('errata-summary', () => {
+ test('shows all items in table', async () => {
+ render(
+
+
+
+ );
+ expect(await getTableRows()).toHaveLength(54);
+ });
+ test('filters', async () => {
+ render(
+
+
+
+ );
+ const user = userEvent.setup({delay: null});
+ const reviewButton = await screen.findByRole('radio', {
+ name: 'In Review'
+ });
+
+ await user.click(reviewButton);
+ expect(await getTableRows()).toHaveLength(19);
+ const allButton = screen.getByRole('radio', {name: 'View All'});
+
+ expect(screen.getAllByRole('table')).toHaveLength(19);
+ // Ignores anything but space or enter
+ fireEvent.keyDown(allButton, {key: 'a'});
+ expect(screen.getAllByRole('table')).toHaveLength(19);
+
+ // Acts on Enter
+ fireEvent.keyDown(allButton, {key: 'Enter'});
+ expect(screen.getAllByRole('table')).toHaveLength(54);
+ });
+});
diff --git a/test/src/pages/webinars/explore-page.test.tsx b/test/src/pages/webinars/explore-page.test.tsx
index fa4ed962c..623a938ed 100644
--- a/test/src/pages/webinars/explore-page.test.tsx
+++ b/test/src/pages/webinars/explore-page.test.tsx
@@ -1,15 +1,19 @@
import React from 'react';
import {describe, it} from '@jest/globals';
import {render, screen} from '@testing-library/preact';
+import userEvent from '@testing-library/user-event';
import {MemoryRouter, Routes, Route} from 'react-router-dom';
import ExplorePage from '~/pages/webinars/explore-page/explore-page';
import useWebinarContext from '~/pages/webinars/webinar-context';
import {pastWebinar} from '../../data/webinars';
import type {Webinar} from '~/pages/webinars/types';
+// @ts-expect-error does not exist on
+const {routerFuture} = global;
+
function Component({path}: {path: string}) {
return (
-
+
jest.fn());
+jest.mock('~/helpers/use-document-head', () => jest.fn());
const webinars: Webinar[] = [0, 1, 2, 3, 4]
.map(() => JSON.parse(JSON.stringify(pastWebinar)))
@@ -48,6 +53,8 @@ webinars[4].subjects.push({
});
describe('webinars explore page', () => {
+ const user = userEvent.setup();
+
(useWebinarContext as jest.Mock).mockImplementation(() => {
return {
latestWebinars: webinars,
@@ -93,7 +100,7 @@ describe('webinars explore page', () => {
2
);
});
- it('paginates if there are more than 9', () => {
+ it('paginates if there are more than 9', async () => {
(useWebinarContext as jest.Mock).mockImplementation(() => {
return {
latestWebinars: webinars.concat(Array(15).fill(webinars[3])),
@@ -105,6 +112,14 @@ describe('webinars explore page', () => {
expect(screen.getAllByText('Showing 1-9 of 16 webinars')).toHaveLength(
1
);
+ await user.click(screen.getByRole('button', {name: 'next'}));
+ expect(screen.getAllByText('Showing 10-16 of 16 webinars')).toHaveLength(
+ 1
+ );
+ await user.click(screen.getByRole('button', {name: 'previous'}));
+ expect(screen.getAllByText('Showing 1-9 of 16 webinars')).toHaveLength(
+ 1
+ );
});
it('shows no results with invalid explore type', () => {
render();
diff --git a/tsconfig.json b/tsconfig.json
index c71c65cb1..9ccaaaad3 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -10,7 +10,7 @@
"esModuleInterop": true,
"moduleResolution": "bundler",
"paths": {
- "~*": ["./src/app/*"]
+ "~/*": ["./src/app/*"]
}
},
"include": [