add unicorn data to search API response, fix type errors

This commit is contained in:
James Fenn
2023-10-19 01:59:12 -04:00
parent 84d85ceb69
commit aa3ebfbcea
12 changed files with 123 additions and 89 deletions

View File

@@ -23,7 +23,6 @@ export const MockCollection: ExtendedCollectionInfo = {
}, },
locales: ["en"], locales: ["en"],
locale: "en", locale: "en",
posts: [MockPost],
slug: "this-collection-name-here", slug: "this-collection-name-here",
title: "Collection title", title: "Collection title",
description: "This is a short description dunno why this would be this short", description: "This is a short description dunno why this would be this short",

View File

@@ -1,33 +1,25 @@
import { MockUnicorn, MockUnicornTwo } from "./mock-unicorn"; import { MockUnicorn, MockUnicornTwo } from "./mock-unicorn";
import { MockLicense } from "./mock-license"; import { MockLicense } from "./mock-license";
import { ExtendedPostInfo } from "types/index"; import { PostInfo } from "types/index";
export const MockPost: ExtendedPostInfo = { export const MockPost: PostInfo = {
excerpt: "This would be an auto generated excerpt of the post in particular",
title: "Post title", title: "Post title",
published: "10-10-2010T00:00:00.000Z", published: "10-10-2010T00:00:00.000Z",
publishedMeta: "October 10, 2010", publishedMeta: "October 10, 2010",
tags: ["item1"], tags: ["item1"],
description: "This is a short description dunno why this would be this short", description: "This is a short description dunno why this would be this short",
authors: [MockUnicorn.id], authors: [MockUnicorn.id],
authorsMeta: [MockUnicorn],
license: MockLicense.id, license: MockLicense.id,
licenseMeta: MockLicense,
locale: "en", locale: "en",
locales: ["en", "es"], locales: ["en", "es"],
slug: "this-post-name-here", slug: "this-post-name-here",
headingsWithId: [], path: "path",
wordCount: 10000, wordCount: 10000,
contentMeta: "",
Content: {} as never,
suggestedArticles: [] as never,
attached: [], attached: [],
socialImg: "img.png", socialImg: "img.png",
}; };
export const MockMultiAuthorPost: ExtendedPostInfo = { export const MockMultiAuthorPost: PostInfo = {
excerpt:
"This would be a second auto generated excerpt of the post in particular",
title: "Another post title", title: "Another post title",
published: "10-20-2010T00:00:00.000Z", published: "10-20-2010T00:00:00.000Z",
publishedMeta: "October 20, 2010", publishedMeta: "October 20, 2010",
@@ -35,24 +27,17 @@ export const MockMultiAuthorPost: ExtendedPostInfo = {
description: description:
"This is another short description dunno why this would be this short", "This is another short description dunno why this would be this short",
authors: [MockUnicornTwo.id, MockUnicorn.id], authors: [MockUnicornTwo.id, MockUnicorn.id],
authorsMeta: [MockUnicornTwo, MockUnicorn],
license: MockLicense.id, license: MockLicense.id,
licenseMeta: MockLicense,
locale: "en", locale: "en",
locales: ["en", "es"], locales: ["en", "es"],
slug: "this-other-post-name-here", slug: "this-other-post-name-here",
headingsWithId: [], path: "path",
wordCount: 100000, wordCount: 100000,
contentMeta: "",
Content: {} as never,
suggestedArticles: [] as never,
attached: [], attached: [],
socialImg: "img.png", socialImg: "img.png",
}; };
export const MockMuliLanguagePost: ExtendedPostInfo = { export const MockMuliLanguagePost: PostInfo = {
excerpt:
"This would be a second auto generated excerpt of the post in particular",
title: "Another post title", title: "Another post title",
published: "10-20-2010T00:00:00.000Z", published: "10-20-2010T00:00:00.000Z",
publishedMeta: "October 20, 2010", publishedMeta: "October 20, 2010",
@@ -60,24 +45,17 @@ export const MockMuliLanguagePost: ExtendedPostInfo = {
description: description:
"This is another short description dunno why this would be this short", "This is another short description dunno why this would be this short",
authors: [MockUnicornTwo.id, MockUnicorn.id], authors: [MockUnicornTwo.id, MockUnicorn.id],
authorsMeta: [MockUnicornTwo, MockUnicorn],
license: MockLicense.id, license: MockLicense.id,
licenseMeta: MockLicense,
locale: "en", locale: "en",
locales: ["en", "es"], locales: ["en", "es"],
slug: "this-other-post-name-here", slug: "this-other-post-name-here",
headingsWithId: [], path: "path",
wordCount: 100000, wordCount: 100000,
contentMeta: "",
Content: {} as never,
suggestedArticles: [] as never,
attached: [], attached: [],
socialImg: "img.png", socialImg: "img.png",
}; };
export const MockCanonicalPost: ExtendedPostInfo = { export const MockCanonicalPost: PostInfo = {
excerpt:
"This would be a second auto generated excerpt of the post in particular",
title: "Another post title", title: "Another post title",
published: "10-20-2010T00:00:00.000Z", published: "10-20-2010T00:00:00.000Z",
publishedMeta: "October 20, 2010", publishedMeta: "October 20, 2010",
@@ -86,17 +64,12 @@ export const MockCanonicalPost: ExtendedPostInfo = {
description: description:
"This is another short description dunno why this would be this short", "This is another short description dunno why this would be this short",
authors: [MockUnicornTwo.id, MockUnicorn.id], authors: [MockUnicornTwo.id, MockUnicorn.id],
authorsMeta: [MockUnicornTwo, MockUnicorn],
license: MockLicense.id, license: MockLicense.id,
licenseMeta: MockLicense,
locale: "en", locale: "en",
locales: ["en", "es"], locales: ["en", "es"],
slug: "this-other-post-name-here", slug: "this-other-post-name-here",
headingsWithId: [], path: "path",
wordCount: 100000, wordCount: 100000,
contentMeta: "",
Content: {} as never,
suggestedArticles: [] as never,
attached: [], attached: [],
socialImg: "img.png", socialImg: "img.png",
}; };

View File

@@ -1,6 +1,6 @@
import { RolesEnum } from "types/RolesInfo"; import { RolesInfo } from "types/RolesInfo";
export const MockRole: RolesEnum = { export const MockRole: RolesInfo = {
id: "developer", id: "developer",
prettyname: "Developer", prettyname: "Developer",
}; };

View File

@@ -6,10 +6,11 @@ export const MockUnicorn: UnicornInfo = {
firstName: "Joe", firstName: "Joe",
lastName: "Other", lastName: "Other",
id: "joe", id: "joe",
locale: "en",
locales: ["en"],
description: "Exists", description: "Exists",
color: "red", color: "red",
roles: [MockRole.id], roles: [MockRole.id],
rolesMeta: [MockRole],
socials: { socials: {
twitter: "twtrusrname", twitter: "twtrusrname",
github: "ghusrname", github: "ghusrname",
@@ -36,10 +37,11 @@ export const MockUnicornTwo: UnicornInfo = {
firstName: "Diane", firstName: "Diane",
lastName: "", lastName: "",
id: "diane", id: "diane",
locale: "en",
locales: ["en"],
description: "Is a human", description: "Is a human",
color: "blue", color: "blue",
roles: [MockRole.id], roles: [MockRole.id],
rolesMeta: [MockRole],
socials: { socials: {
twitter: "twtrusrname2", twitter: "twtrusrname2",
github: "ghusrname2", github: "ghusrname2",

View File

@@ -2,7 +2,8 @@ import type { VercelRequest, VercelResponse } from "@vercel/node";
import Fuse from "fuse.js"; import Fuse from "fuse.js";
import { createRequire } from "node:module"; import { createRequire } from "node:module";
import type { ExtendedPostInfo } from "types/index"; import { CollectionInfo, UnicornInfo, type PostInfo } from "types/index";
import type { ServerReturnType } from "../src/views/search/types";
const require = createRequire(import.meta.url); const require = createRequire(import.meta.url);
const searchIndex = require("./searchIndex.json"); const searchIndex = require("./searchIndex.json");
@@ -12,7 +13,7 @@ const collectionIndex = Fuse.parseIndex(searchIndex.collectionIndex);
const posts = searchIndex.posts; const posts = searchIndex.posts;
const collections = searchIndex.collections; const collections = searchIndex.collections;
const postFuse = new Fuse<ExtendedPostInfo>( const postFuse = new Fuse<PostInfo>(
posts, posts,
{ {
threshold: 0.3, threshold: 0.3,
@@ -23,7 +24,7 @@ const postFuse = new Fuse<ExtendedPostInfo>(
postIndex, postIndex,
); );
const collectionFuse = new Fuse( const collectionFuse = new Fuse<CollectionInfo>(
collections, collections,
{ {
threshold: 0.3, threshold: 0.3,
@@ -34,35 +35,53 @@ const collectionFuse = new Fuse(
collectionIndex, collectionIndex,
); );
export default async (req: VercelRequest, res: VercelResponse) => { const unicorns: Record<string, UnicornInfo> = searchIndex.unicorns;
function runQuery(req: VercelRequest): ServerReturnType {
// TODO: `pickdeep` only required fields // TODO: `pickdeep` only required fields
const searchStr = req?.query?.query as string; const searchStr = req?.query?.query as string;
if (!searchStr) { if (!searchStr) {
res.send({ return {
unicorns: {},
posts: [], posts: [],
totalPosts: 0, totalPosts: 0,
collections: [], collections: [],
totalCollections: 0, totalCollections: 0,
}); };
return;
} }
if (searchStr === "*") { if (searchStr === "*") {
res.send({ return {
unicorns,
posts, posts,
totalPosts: posts.length, totalPosts: posts.length,
collections, collections,
totalCollections: collections.length, totalCollections: collections.length,
}); };
return;
} }
const searchedPosts = postFuse.search(searchStr).map((item) => item.item); const searchedPosts = postFuse.search(searchStr).map((item) => item.item);
const searchedCollections = collectionFuse const searchedCollections = collectionFuse
.search(searchStr) .search(searchStr)
.map((item) => item.item); .map((item) => item.item);
res.send({
const searchedUnicorns: Record<string, UnicornInfo> = {};
for (const post of searchedPosts) {
for (const id of post.authors) searchedUnicorns[id] = unicorns[id];
}
for (const collection of searchedCollections) {
for (const id of collection.authors) searchedUnicorns[id] = unicorns[id];
}
return {
unicorns: searchedUnicorns,
posts: searchedPosts, posts: searchedPosts,
totalPosts: searchedPosts.length, totalPosts: searchedPosts.length,
collections: searchedCollections, collections: searchedCollections,
totalCollections: searchedCollections.length, totalCollections: searchedCollections.length,
}); };
}
export default async (req: VercelRequest, res: VercelResponse) => {
const response = runQuery(req);
res.send(response);
}; };

View File

@@ -2,7 +2,7 @@ import Fuse from "fuse.js";
import * as fs from "fs"; import * as fs from "fs";
import * as path from "path"; import * as path from "path";
import * as api from "utils/api"; import * as api from "utils/api";
import { PostInfo, CollectionInfo } from "types/index"; import { PostInfo, CollectionInfo, UnicornInfo } from "types/index";
const posts = api.getPostsByLang("en"); const posts = api.getPostsByLang("en");
const collections = api.getCollectionsByLang("en"); const collections = api.getCollectionsByLang("en");
@@ -87,5 +87,18 @@ const createCollectionIndex = () => {
const postIndex = createPostIndex(); const postIndex = createPostIndex();
const collectionIndex = createCollectionIndex(); const collectionIndex = createCollectionIndex();
const json = JSON.stringify({ postIndex, posts, collectionIndex, collections }); const unicorns: Record<string, UnicornInfo> = api
.getUnicornsByLang("en")
.reduce((obj, unicorn) => {
obj[unicorn.id] = unicorn;
return obj;
}, {});
const json = JSON.stringify({
postIndex,
posts,
collectionIndex,
collections,
unicorns,
});
fs.writeFileSync(path.resolve(process.cwd(), "./api/searchIndex.json"), json); fs.writeFileSync(path.resolve(process.cwd(), "./api/searchIndex.json"), json);

View File

@@ -33,10 +33,10 @@ import YourGuide from "src/views/collections/framework-field-guide/segments/your
import CodeBlock from "src/views/collections/framework-field-guide/segments/code-block.astro"; import CodeBlock from "src/views/collections/framework-field-guide/segments/code-block.astro";
import TheRestContainer from "src/views/collections/framework-field-guide/segments/the-rest-container.astro"; import TheRestContainer from "src/views/collections/framework-field-guide/segments/the-rest-container.astro";
import Pricing from "src/views/collections/framework-field-guide/segments/pricing.astro"; import Pricing from "src/views/collections/framework-field-guide/segments/pricing.astro";
import { unicorns } from "utils/data";
import SignUp from "src/views/collections/framework-field-guide/segments/sign-up.astro"; import SignUp from "src/views/collections/framework-field-guide/segments/sign-up.astro";
import WhyLearnAllThree from "src/views/collections/framework-field-guide/segments/why-learn-all-three.astro"; import WhyLearnAllThree from "src/views/collections/framework-field-guide/segments/why-learn-all-three.astro";
import WhatDoIGet from "src/views/collections/framework-field-guide/segments/what-do-i-get.astro"; import WhatDoIGet from "src/views/collections/framework-field-guide/segments/what-do-i-get.astro";
import { getUnicornById } from "utils/api";
--- ---
<Barebones> <Barebones>
@@ -45,7 +45,7 @@ import WhatDoIGet from "src/views/collections/framework-field-guide/segments/wha
pathName={`/collections/framework-field-guide`} pathName={`/collections/framework-field-guide`}
title={"The Framework Field Guide"} title={"The Framework Field Guide"}
description={"A practical and free way to teach Angular, React, and Vue all at once, so you can choose the right tool for the job and learn the underlying concepts in depth."} description={"A practical and free way to teach Angular, React, and Vue all at once, so you can choose the right tool for the job and learn the underlying concepts in depth."}
unicornsData={[unicorns.find((uni) => uni.id === "crutchcorn")]} unicornsData={[getUnicornById("crutchcorn", "en")]}
publishedTime={"2022-12-01T13:45:00.284Z"} publishedTime={"2022-12-01T13:45:00.284Z"}
type={"book"} type={"book"}
shareImage={"/custom-content/collections/framework-field-guide/framework_field_guide_social.png"} shareImage={"/custom-content/collections/framework-field-guide/framework_field_guide_social.png"}

View File

@@ -1,17 +1,26 @@
--- ---
import { PostInfo } from "types/index"; import { Languages, PostInfo } from "types/index";
import { PostCardGrid } from "src/components/post-card/post-card-grid"; import { PostCardGrid } from "src/components/post-card/post-card-grid";
import { getUnicornProfilePicMap } from "utils/get-unicorn-profile-pic-map"; import { getUnicornProfilePicMap } from "utils/get-unicorn-profile-pic-map";
import { Pagination } from "components/pagination/pagination"; import { Pagination } from "components/pagination/pagination";
import { Page } from "astro"; import { Page } from "astro";
import * as api from "utils/api";
export interface PageProps { export interface PageProps {
posts: PostInfo[]; posts: PostInfo[];
page: Pick<Page<any>, "currentPage" | "lastPage">; page: Pick<Page<any>, "currentPage" | "lastPage">;
locale: Languages;
} }
const { posts, page } = Astro.props as PageProps; const { posts, page, locale } = Astro.props as PageProps;
const unicornProfilePicMap = await getUnicornProfilePicMap(); const unicornProfilePicMap = await getUnicornProfilePicMap();
const postAuthors = new Map(
[...new Set(posts.flatMap((p) => p.authors))].map((id) => [
id,
api.getUnicornById(id, locale),
]),
);
--- ---
<h1 class="visually-hidden">Posts</h1> <h1 class="visually-hidden">Posts</h1>
@@ -20,6 +29,7 @@ const unicornProfilePicMap = await getUnicornProfilePicMap();
expanded={true} expanded={true}
aria-label="List of posts" aria-label="List of posts"
postsToDisplay={posts} postsToDisplay={posts}
postAuthors={postAuthors}
unicornProfilePicMap={unicornProfilePicMap} unicornProfilePicMap={unicornProfilePicMap}
/> />

View File

@@ -1,6 +1,6 @@
import { ProfilePictureMap } from "utils/get-unicorn-profile-pic-map"; import { ProfilePictureMap } from "utils/get-unicorn-profile-pic-map";
import { PostInfo } from "types/PostInfo"; import { PostInfo } from "types/PostInfo";
import { ExtendedCollectionInfo } from "types/CollectionInfo"; import { CollectionInfo } from "types/CollectionInfo";
import { useMemo } from "preact/hooks"; import { useMemo } from "preact/hooks";
import { UnicornInfo } from "types/UnicornInfo"; import { UnicornInfo } from "types/UnicornInfo";
import { CSSProperties } from "preact/compat"; import { CSSProperties } from "preact/compat";
@@ -15,7 +15,8 @@ interface FilterDisplayProps {
unicornProfilePicMap: ProfilePictureMap; unicornProfilePicMap: ProfilePictureMap;
posts: PostInfo[]; posts: PostInfo[];
collections: ExtendedCollectionInfo[]; collections: CollectionInfo[];
unicornsMap: Map<string, UnicornInfo>,
selectedTags: string[]; selectedTags: string[];
setSelectedTags: (tags: string[]) => void; setSelectedTags: (tags: string[]) => void;
selectedAuthorIds: string[]; selectedAuthorIds: string[];
@@ -33,6 +34,7 @@ interface FilterDisplayProps {
export const FilterDisplay = ({ export const FilterDisplay = ({
unicornProfilePicMap, unicornProfilePicMap,
collections, collections,
unicornsMap,
posts, posts,
sort, sort,
setSort, setSort,
@@ -80,27 +82,14 @@ export const FilterDisplay = ({
const authors = useMemo(() => { const authors = useMemo(() => {
const postAuthorIdToPostNumMap = new Map<string, number>(); const postAuthorIdToPostNumMap = new Map<string, number>();
const authors: UnicornInfo[] = [];
posts.forEach((post) => { posts.forEach((post) => {
post.authorsMeta.forEach((author) => { post.authors.forEach((author) => {
authors.push(author); const numPosts = postAuthorIdToPostNumMap.get(author) || 0;
postAuthorIdToPostNumMap.set(author, numPosts + 1);
const numPosts = postAuthorIdToPostNumMap.get(author.id) || 0;
postAuthorIdToPostNumMap.set(author.id, numPosts + 1);
}); });
}); });
collections.forEach((collection) => { return Array.from(unicornsMap.values())
collection.authorsMeta.forEach((author) => {
authors.push(author);
});
});
const uniqueAuthors = new Map<string, UnicornInfo>();
authors.forEach((author) => {
uniqueAuthors.set(author.id, author);
});
return Array.from(uniqueAuthors.values())
.sort((a, b) => a.name.localeCompare(b.name)) .sort((a, b) => a.name.localeCompare(b.name))
.map((author) => ({ .map((author) => ({
...author, ...author,

View File

@@ -9,7 +9,8 @@ import {
queryByText, queryByText,
getByTestId, getByTestId,
} from "@testing-library/preact"; } from "@testing-library/preact";
import SearchPage, { ServerReturnType } from "./search-page"; import SearchPage from "./search-page";
import type { ServerReturnType } from "./types";
import { rest } from "msw"; import { rest } from "msw";
import { setupServer } from "msw/node"; import { setupServer } from "msw/node";
import { MockCanonicalPost, MockPost } from "../../../__mocks__/data/mock-post"; import { MockCanonicalPost, MockPost } from "../../../__mocks__/data/mock-post";
@@ -67,6 +68,7 @@ describe("Search page", () => {
test("Should show search results for posts", async () => { test("Should show search results for posts", async () => {
mockFetch(() => ({ mockFetch(() => ({
unicorns: {},
posts: [MockPost], posts: [MockPost],
totalPosts: 1, totalPosts: 1,
totalCollections: 0, totalCollections: 0,
@@ -84,6 +86,7 @@ describe("Search page", () => {
test("Should show search results for collections", async () => { test("Should show search results for collections", async () => {
mockFetch(() => ({ mockFetch(() => ({
unicorns: {},
posts: [], posts: [],
totalPosts: 0, totalPosts: 0,
totalCollections: 1, totalCollections: 1,
@@ -120,6 +123,7 @@ describe("Search page", () => {
test("Should show 'nothing found'", async () => { test("Should show 'nothing found'", async () => {
mockFetch(() => ({ mockFetch(() => ({
unicorns: {},
posts: [], posts: [],
totalPosts: 0, totalPosts: 0,
totalCollections: 0, totalCollections: 0,
@@ -139,6 +143,7 @@ describe("Search page", () => {
test("Remove collections header when none found", async () => { test("Remove collections header when none found", async () => {
mockFetch(() => ({ mockFetch(() => ({
unicorns: {},
posts: [MockPost], posts: [MockPost],
totalPosts: 1, totalPosts: 1,
totalCollections: 0, totalCollections: 0,
@@ -159,6 +164,7 @@ describe("Search page", () => {
test("Remove posts header when none found", async () => { test("Remove posts header when none found", async () => {
mockFetch(() => ({ mockFetch(() => ({
unicorns: {},
posts: [], posts: [],
totalPosts: 0, totalPosts: 0,
totalCollections: 1, totalCollections: 1,
@@ -179,6 +185,7 @@ describe("Search page", () => {
test("Filter by tag works on desktop sidebar", async () => { test("Filter by tag works on desktop sidebar", async () => {
mockFetch(() => ({ mockFetch(() => ({
unicorns: {},
posts: [ posts: [
{ ...MockPost, tags: ["Angular"], title: "One blog post" }, { ...MockPost, tags: ["Angular"], title: "One blog post" },
{ ...MockCanonicalPost, tags: [], title: "Two blog post" }, { ...MockCanonicalPost, tags: [], title: "Two blog post" },
@@ -210,6 +217,7 @@ describe("Search page", () => {
test("Filter by author works on desktop sidebar", async () => { test("Filter by author works on desktop sidebar", async () => {
mockFetch(() => ({ mockFetch(() => ({
unicorns: {},
posts: [ posts: [
{ {
...MockPost, ...MockPost,
@@ -251,6 +259,7 @@ describe("Search page", () => {
test("Filter by content type work on radio group buttons", async () => { test("Filter by content type work on radio group buttons", async () => {
mockFetch(() => ({ mockFetch(() => ({
unicorns: {},
posts: [{ ...MockPost, title: "One blog post" }], posts: [{ ...MockPost, title: "One blog post" }],
totalPosts: 1, totalPosts: 1,
totalCollections: 1, totalCollections: 1,
@@ -293,6 +302,7 @@ describe("Search page", () => {
global.innerWidth = 2000; global.innerWidth = 2000;
mockFetch(() => ({ mockFetch(() => ({
unicorns: {},
posts: [ posts: [
{ {
...MockPost, ...MockPost,
@@ -352,6 +362,7 @@ describe("Search page", () => {
test("Sort by date works on mobile radio group buttons", async () => { test("Sort by date works on mobile radio group buttons", async () => {
global.innerWidth = 500; global.innerWidth = 500;
mockFetch(() => ({ mockFetch(() => ({
unicorns: {},
posts: [ posts: [
{ {
...MockPost, ...MockPost,
@@ -411,6 +422,7 @@ describe("Search page", () => {
test("Pagination - Changing pages to page 2 shows second page of results", async () => { test("Pagination - Changing pages to page 2 shows second page of results", async () => {
// 6 posts per page // 6 posts per page
mockFetch(() => ({ mockFetch(() => ({
unicorns: {},
posts: [ posts: [
{ ...MockPost, slug: `blog-post-1`, title: "One blog post" }, { ...MockPost, slug: `blog-post-1`, title: "One blog post" },
{ ...MockPost, slug: `blog-post-2`, title: "Two blog post" }, { ...MockPost, slug: `blog-post-2`, title: "Two blog post" },
@@ -473,6 +485,7 @@ describe("Search page", () => {
global.innerWidth = 2000; global.innerWidth = 2000;
// 6 posts per page // 6 posts per page
mockFetch(() => ({ mockFetch(() => ({
unicorns: {},
posts: [ posts: [
{ {
...MockPost, ...MockPost,
@@ -603,6 +616,7 @@ describe("Search page", () => {
// Search page, sort order, etc // Search page, sort order, etc
test("Make sure that initial search props are not thrown away", async () => { test("Make sure that initial search props are not thrown away", async () => {
mockFetch(() => ({ mockFetch(() => ({
unicorns: {},
posts: [ posts: [
{ {
...MockPost, ...MockPost,
@@ -762,6 +776,7 @@ describe("Search page", () => {
global.innerWidth = 2000; global.innerWidth = 2000;
mockFetch(() => ({ mockFetch(() => ({
unicorns: {},
posts: [ posts: [
{ {
...MockPost, ...MockPost,
@@ -830,6 +845,7 @@ describe("Search page", () => {
test("Make sure that re-searches reset page to 1 and preserve tags, authors, etc", async () => { test("Make sure that re-searches reset page to 1 and preserve tags, authors, etc", async () => {
mockFetch(() => ({ mockFetch(() => ({
unicorns: {},
posts: [ posts: [
{ {
...MockPost, ...MockPost,
@@ -981,6 +997,7 @@ describe("Search page", () => {
test("Make sure that re-searches to empty string reset page, tags, authors, etc", async () => { test("Make sure that re-searches to empty string reset page, tags, authors, etc", async () => {
mockFetch(() => ({ mockFetch(() => ({
unicorns: {},
posts: [ posts: [
{ {
...MockPost, ...MockPost,
@@ -1132,6 +1149,7 @@ describe("Search page", () => {
test("Back button should show last query", async () => { test("Back button should show last query", async () => {
mockFetch(() => ({ mockFetch(() => ({
unicorns: {},
posts: [], posts: [],
totalPosts: 0, totalPosts: 0,
totalCollections: 0, totalCollections: 0,

View File

@@ -21,7 +21,6 @@ import style from "./search-page.module.scss";
import { PostCardGrid } from "components/post-card/post-card-grid"; import { PostCardGrid } from "components/post-card/post-card-grid";
import { SubHeader } from "components/subheader/subheader"; import { SubHeader } from "components/subheader/subheader";
import { Fragment } from "preact"; import { Fragment } from "preact";
import { ExtendedCollectionInfo } from "types/CollectionInfo";
import { CollectionCard } from "components/collection-card/collection-card"; import { CollectionCard } from "components/collection-card/collection-card";
import { FilterDisplay } from "./components/filter-display"; import { FilterDisplay } from "./components/filter-display";
import { useElementSize } from "../../hooks/use-element-size"; import { useElementSize } from "../../hooks/use-element-size";
@@ -43,6 +42,8 @@ import {
import { debounce } from "utils/debounce"; import { debounce } from "utils/debounce";
import { SortType } from "./components/types"; import { SortType } from "./components/types";
import { SearchResultCount } from "./components/search-result-count"; import { SearchResultCount } from "./components/search-result-count";
import { ServerReturnType } from "./types";
import { CollectionInfo } from "types/CollectionInfo";
const DEFAULT_SORT = "relevance"; const DEFAULT_SORT = "relevance";
const DEFAULT_CONTENT_TO_DISPLAY = "all"; const DEFAULT_CONTENT_TO_DISPLAY = "all";
@@ -53,13 +54,6 @@ interface SearchPageProps {
const MAX_POSTS_PER_PAGE = 6; const MAX_POSTS_PER_PAGE = 6;
export interface ServerReturnType {
posts: PostInfo[];
totalPosts: number;
collections: ExtendedCollectionInfo[];
totalCollections: number;
}
function SearchPageBase({ unicornProfilePicMap }: SearchPageProps) { function SearchPageBase({ unicornProfilePicMap }: SearchPageProps) {
const { urlParams, pushState } = useSearchParams(); const { urlParams, pushState } = useSearchParams();
@@ -141,11 +135,12 @@ function SearchPageBase({ unicornProfilePicMap }: SearchPageProps) {
}, },
queryKey: ["search", debouncedSearch], queryKey: ["search", debouncedSearch],
initialData: { initialData: {
unicorns: {},
posts: [], posts: [],
totalPosts: 0, totalPosts: 0,
collections: [], collections: [],
totalCollections: 0, totalCollections: 0,
} as ServerReturnType, },
refetchOnWindowFocus: false, refetchOnWindowFocus: false,
retry: false, retry: false,
enabled, enabled,
@@ -271,7 +266,7 @@ function SearchPageBase({ unicornProfilePicMap }: SearchPageProps) {
}); });
}, [data, page, sort, selectedUnicorns, selectedTags]); }, [data, page, sort, selectedUnicorns, selectedTags]);
const filteredAndSortedCollections = useMemo(() => { const filteredAndSortedCollections: CollectionInfo[] = useMemo(() => {
const collections = [...data.collections]; const collections = [...data.collections];
if (sort && sort !== "relevance") { if (sort && sort !== "relevance") {
@@ -365,6 +360,7 @@ function SearchPageBase({ unicornProfilePicMap }: SearchPageProps) {
unicornProfilePicMap={unicornProfilePicMap} unicornProfilePicMap={unicornProfilePicMap}
collections={data.collections} collections={data.collections}
posts={data.posts} posts={data.posts}
unicornsMap={new Map(Object.entries(data.unicorns))}
selectedTags={selectedTags} selectedTags={selectedTags}
setSelectedTags={setSelectedTags} setSelectedTags={setSelectedTags}
selectedAuthorIds={selectedUnicorns} selectedAuthorIds={selectedUnicorns}
@@ -488,6 +484,9 @@ function SearchPageBase({ unicornProfilePicMap }: SearchPageProps) {
<CollectionCard <CollectionCard
unicornProfilePicMap={unicornProfilePicMap} unicornProfilePicMap={unicornProfilePicMap}
collection={collection} collection={collection}
authors={collection.authors.map(id => data.unicorns[id])}
// TODO: post count should be sourced from the collection info
posts={[]}
headingTag="h3" headingTag="h3"
/> />
</li> </li>
@@ -509,6 +508,7 @@ function SearchPageBase({ unicornProfilePicMap }: SearchPageProps) {
<PostCardGrid <PostCardGrid
aria-labelledby={"articles-header"} aria-labelledby={"articles-header"}
postsToDisplay={posts} postsToDisplay={posts}
postAuthors={new Map(Object.entries(data.unicorns))}
postHeadingTag="h3" postHeadingTag="h3"
unicornProfilePicMap={unicornProfilePicMap} unicornProfilePicMap={unicornProfilePicMap}
/> />

11
src/views/search/types.ts Normal file
View File

@@ -0,0 +1,11 @@
import { CollectionInfo } from "types/CollectionInfo";
import { PostInfo } from "types/PostInfo";
import { UnicornInfo } from "types/UnicornInfo";
export interface ServerReturnType {
unicorns: Record<string, UnicornInfo>;
posts: PostInfo[];
totalPosts: number;
collections: CollectionInfo[];
totalCollections: number;
}