diff --git a/__test__/pages/search/searchResults.test.tsx b/__test__/pages/search/searchResults.test.tsx
index 13580ad7a..83492f875 100644
--- a/__test__/pages/search/searchResults.test.tsx
+++ b/__test__/pages/search/searchResults.test.tsx
@@ -1,4 +1,5 @@
import React from "react"
+import userEvent from "@testing-library/user-event"
import { render, screen } from "@testing-library/react"
import mockRouter from "next-router-mock"
@@ -11,11 +12,14 @@ jest.mock("next/router", () => jest.requireActual("next-router-mock"))
describe("Search Results page", () => {
describe("More than 50 bibs", () => {
- it("displays many bibs", () => {
- const query = "spaghetti"
+ let query: string
+
+ beforeEach(() => {
+ query = "spaghetti"
mockRouter.push(`/search?q=${query}`)
render()
-
+ })
+ it("displays many bibs", () => {
const displayingText = screen.getByRole("heading", { level: 2 })
expect(displayingText).toHaveTextContent(
`Displaying 1-50 of ${results.results.totalResults} results for keyword "${query}"`
@@ -23,6 +27,25 @@ describe("Search Results page", () => {
const cards = screen.getAllByRole("heading", { level: 3 })
expect(cards).toHaveLength(50)
})
+ it("renders the sort select field and updates the query string in the url on changes", async () => {
+ const sortSelect = screen.getByLabelText("Sort by")
+ expect(sortSelect).toHaveValue("relevance")
+ await userEvent.selectOptions(sortSelect, "Title (A - Z)")
+ expect(sortSelect).toHaveValue("title_asc")
+
+ expect(mockRouter.asPath).toBe(
+ "/?q=spaghetti&sort=title&sort_direction=asc"
+ )
+ })
+ it("returns the user to the first page on sorting changes", async () => {
+ await mockRouter.push(`/search?q=${query}&page=2`)
+ const sortSelect = screen.getByLabelText("Sort by")
+ await userEvent.selectOptions(sortSelect, "Title (Z - A)")
+
+ expect(mockRouter.asPath).toBe(
+ "/?q=spaghetti&sort=title&sort_direction=desc"
+ )
+ })
})
describe("No bibs", () => {
it("displays No results message", () => {
diff --git a/pages/search/index.tsx b/pages/search/index.tsx
index d87514405..f1189d6e3 100644
--- a/pages/search/index.tsx
+++ b/pages/search/index.tsx
@@ -1,5 +1,10 @@
import Head from "next/head"
-import { Heading, SimpleGrid } from "@nypl/design-system-react-components"
+import {
+ Heading,
+ SimpleGrid,
+ Select,
+} from "@nypl/design-system-react-components"
+import type { ChangeEvent } from "react"
import { useRouter } from "next/router"
import { parse } from "qs"
@@ -11,7 +16,10 @@ import { fetchResults } from "../api/search"
import {
mapQueryToSearchParams,
mapElementsToSearchResultsBibs,
+ getQueryString,
+ sortOptions,
} from "../../src/utils/searchUtils"
+import type { SortKey, SortOrder } from "../../src/types/searchTypes"
import { mapWorksToDRBResults } from "../../src/utils/drbUtils"
import { SITE_NAME } from "../../src/config/constants"
import type SearchResultsBib from "../../src/models/SearchResultsBib"
@@ -21,7 +29,7 @@ import type SearchResultsBib from "../../src/models/SearchResultsBib"
* as well as displaying and controlling pagination and search filters.
*/
export default function Search({ results }) {
- const { query } = useRouter()
+ const { push, query } = useRouter()
const { itemListElement: searchResultsElements, totalResults } =
results.results
@@ -37,6 +45,19 @@ export default function Search({ results }) {
// Map DRB Works from response to DRBResult objects
const drbResults = mapWorksToDRBResults(drbWorks)
+ const handleSortChange = async (e: ChangeEvent) => {
+ const selectedSortOption = e.target.value
+ // Extract sort key and order from selected sort option using "_" delineator
+ const [sortBy, order] = selectedSortOption.split("_") as [
+ SortKey,
+ SortOrder | undefined
+ ]
+ // Push the new query values, removing the page number if set.
+ await push(
+ getQueryString({ ...searchParams, sortBy, order, page: undefined })
+ )
+ }
+
return (
<>
@@ -45,13 +66,35 @@ export default function Search({ results }) {
- )
+ <>
+ {totalResults && (
+
+ )}
+ {drbResponse?.totalWorks && (
+
+ )}
+ >
}
>
{totalResults ? (
diff --git a/src/models/Item.ts b/src/models/Item.ts
index 70b3824dd..df25ef229 100644
--- a/src/models/Item.ts
+++ b/src/models/Item.ts
@@ -40,7 +40,7 @@ export default class Item {
this.accessMessage = item.accessMessage?.length
? item.accessMessage[0]?.prefLabel
: ""
- this.callNumber = item.shelfMark.length ? item.shelfMark[0] : null
+ this.callNumber = item.shelfMark?.length ? item.shelfMark[0] : null
this.volume = item.enumerationChronology?.length
? item.enumerationChronology[0]
: null
diff --git a/src/types/searchTypes.ts b/src/types/searchTypes.ts
index e431130e5..a6692b173 100644
--- a/src/types/searchTypes.ts
+++ b/src/types/searchTypes.ts
@@ -33,8 +33,8 @@ export interface Identifiers {
export interface SearchParams {
q?: string
field?: string
- sortBy?: string
- order?: string
+ sortBy?: SortKey
+ order?: SortOrder
filters?: SearchFilters
contributor?: string
title?: string
@@ -43,6 +43,9 @@ export interface SearchParams {
identifiers?: Identifiers
}
+export type SortKey = "relevance" | "title" | "date"
+export type SortOrder = "asc" | "desc"
+
type SearchFormField = { value: string }
export interface SearchResultsResponse {
@@ -86,8 +89,8 @@ export interface SearchQueryParams extends Identifiers {
title?: string
subject?: string
filters?: SearchFilters
- sort?: string
- sort_direction?: string
+ sort?: SortKey
+ sort_direction?: SortOrder
sort_scope?: string
search_scope?: string
page?: number
diff --git a/src/utils/searchUtils.ts b/src/utils/searchUtils.ts
index 2f3bd76ad..e1710c3d0 100644
--- a/src/utils/searchUtils.ts
+++ b/src/utils/searchUtils.ts
@@ -165,6 +165,18 @@ export function mapElementsToSearchResultsBibs(
/* eslint-disable @typescript-eslint/naming-convention */
+/**
+ * sortOptions
+ * The allowed keys for the sort field and their respective labels
+ */
+export const sortOptions: Record = {
+ relevance: "Relevance",
+ title_asc: "Title (A - Z)",
+ title_desc: "Title (Z - A)",
+ date_asc: "Date (Old to New)",
+ date_desc: "Date (New to Old)",
+}
+
/**
* mapQueryToSearchParams
* Maps the SearchQueryParams structure from the request to a SearchParams object, which is expected by fetchResults
diff --git a/src/utils/utilsTests/searchUtils.test.ts b/src/utils/utilsTests/searchUtils.test.ts
index 6e6c59b8a..8b1b99358 100644
--- a/src/utils/utilsTests/searchUtils.test.ts
+++ b/src/utils/utilsTests/searchUtils.test.ts
@@ -30,7 +30,7 @@ describe("searchUtils", () => {
isbn: "456",
search_scope: "contributor",
sort_direction: "asc",
- sort: "spaghetti",
+ sort: "relevance",
})
).toEqual({
identifiers: {
@@ -39,7 +39,7 @@ describe("searchUtils", () => {
},
field: "contributor",
order: "asc",
- sortBy: "spaghetti",
+ sortBy: "relevance",
})
})
})