chore: setup initial MSW and test scaffolding for search page

This commit is contained in:
Corbin Crutchley
2023-08-14 01:18:27 -07:00
parent 6b008370ac
commit f0a7073bbe
9 changed files with 812 additions and 31 deletions

View File

@@ -3,8 +3,11 @@
// Used for __tests__/testing-library.js
// Learn more: https://github.com/testing-library/jest-dom
require("whatwg-fetch");
require("@testing-library/jest-dom/jest-globals");
global.plausible = null;
global.IntersectionObserver = class IntersectionObserver {
constructor() {}

View File

@@ -1,4 +1,5 @@
const { resolve } = require("path");
require('whatwg-fetch');
// Add any custom config to be passed to Jest
module.exports = {

746
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -37,7 +37,7 @@
"social-previews:dev": "npm-run-all --parallel social-previews:dev:build social-previews:dev:server",
"epub": "tsx --tsconfig tsconfig.script.json build-scripts/generate-epubs.ts",
"tsc": "tsc --noEmit && astro check",
"test": "jest",
"test": "set DEBUG_PRINT_LIMIT=9999999999 && jest",
"prepare": "husky install && playwright install"
},
"devDependencies": {
@@ -51,6 +51,8 @@
"@testing-library/dom": "^9.3.1",
"@testing-library/jest-dom": "^6.0.0",
"@testing-library/preact": "^3.2.3",
"@testing-library/user-event": "^14.4.3",
"@types/hast": "^3.0.0",
"@types/jest": "^29.5.2",
"@types/json5": "^2.2.0",
"@types/node": "^20.5.0",
@@ -85,6 +87,7 @@
"json5": "^2.2.3",
"junk": "^4.0.1",
"lint-staged": "^14.0.0",
"msw": "^1.2.3",
"npm-run-all": "^4.1.5",
"plausible-tracker": "^0.3.8",
"playwright": "^1.37.0",
@@ -114,8 +117,8 @@
"unist-util-replace-all-between": "^0.1.1",
"unist-util-visit": "^5.0.0",
"vercel": "^31.2.3",
"@types/hast": "^3.0.0",
"vite-plugin-svgr": "^3.2.0"
"vite-plugin-svgr": "^3.2.0",
"whatwg-fetch": "^3.6.17"
},
"lint-staged": {
"*.{js,ts,astro}": "eslint --cache --fix",

View File

@@ -16,10 +16,8 @@ export const Picture = ({
}: PictureProps) => {
return (
<picture class={`${className || ""}`}>
{picture.sources.map((attrs) => (
<source {...attrs} />
))}
<img {...(picture.image as any)} {...imgAttrs} alt={alt} />
{picture?.sources.map((attrs) => <source {...attrs} />)}
<img {...((picture?.image as any) ?? {})} {...imgAttrs} alt={alt} />
</picture>
);
};

View File

@@ -23,6 +23,7 @@ export const FilterSectionItem = ({
const props = {
isSelected: selected,
onChange: onChange,
"aria-label": label,
};
const state = useToggleState(props);

View File

@@ -41,6 +41,7 @@ export const SearchTopbar = ({
>
<SearchInput
id="search-bar"
aria-label="Search"
class={style.searchbar}
usedInPreact={true}
value={search}

View File

@@ -0,0 +1,69 @@
import { fireEvent, render, waitFor } from "@testing-library/preact";
import SearchPage, { ServerReturnType } from "./search-page";
import { rest } from "msw";
import { setupServer } from "msw/node";
import { MockPost } from "../../../__mocks__/data/mock-post";
import userEvent from "@testing-library/user-event";
const user = userEvent.setup();
const server = setupServer();
beforeAll(() => server.listen({ onUnhandledRequest: "error" }));
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
function mockFetch(fn: (searchStr: string) => ServerReturnType) {
server.use(
rest.get<ServerReturnType>(
`/api/search`,
async (req, res, ctx) => {
const searchString = req.url.searchParams.get("query");
return res(ctx.json(fn(searchString)));
},
),
);
}
describe("Search page", () => {
test("Should show initial results", () => {
const { getByText } = render(<SearchPage unicornProfilePicMap={[]} />);
expect(getByText("What would you like to find?")).toBeInTheDocument();
});
test("Should show search results filtered by client", async () => {
mockFetch(() => ({
posts: [MockPost],
totalPosts: 1,
totalCollections: 0,
collections: [],
}));
const { getByText, getByLabelText, debug, getByTestId } = render(
<SearchPage unicornProfilePicMap={[]} />,
);
const searchInput = getByLabelText("Search");
await user.type(searchInput, MockPost.title);
await user.type(searchInput, "{enter}");
await waitFor(() => expect(getByText(MockPost.title)).toBeInTheDocument());
});
test.todo("Should show error with 500");
test.todo("Should show 'nothing found'");
test.todo("Remove collections header when none found");
test.todo("Remove posts header when none found");
test.todo("Filter by tag works");
test.todo("Filter by content type");
test.todo("Sort by date works");
test.todo("Filter by author works");
// Changing pages to page 2 shows second page of results
test.todo("Pagination works");
// Search page, sort order, etc
test.todo("Make sure that initial search props are not thrown away");
});

View File

@@ -48,7 +48,7 @@ interface SearchPageProps {
const MAX_POSTS_PER_PAGE = 6;
interface ServerReturnType {
export interface ServerReturnType {
posts: PostInfo[];
totalPosts: number;
collections: ExtendedCollectionInfo[];
@@ -97,6 +97,7 @@ function SearchPageBase({ unicornProfilePicMap }: SearchPageProps) {
return fetch(`/api/search?query=${debouncedSearch}`, {
signal: signal,
method: "GET",
}).then((res) => res.json() as Promise<ServerReturnType>);
},
queryKey: ["search", debouncedSearch],
@@ -353,7 +354,7 @@ function SearchPageBase({ unicornProfilePicMap }: SearchPageProps) {
!isContentLoading &&
showArticles &&
Boolean(data.posts.length) && (
<Fragment>
<div data-testid={"HI"}>
<SubHeader tag="h1" text="Articles" />
<PostCardGrid
listAriaLabel={"List of search result posts"}
@@ -374,7 +375,7 @@ function SearchPageBase({ unicornProfilePicMap }: SearchPageProps) {
return `${window.location.pathname}?${pageParams.toString()}`;
}}
/>
</Fragment>
</div>
)}
</div>
</SearchTag>