Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React from "react"
import { SearchInput } from "ol-components"
import type { SearchInputProps, SearchSubmissionEvent } from "ol-components"
import { usePostHog } from "posthog-js/react"

type SearchFieldProps = SearchInputProps & {
onSubmit: (event: SearchSubmissionEvent) => void
setPage: (page: number) => void
}

const { POSTHOG } = APP_SETTINGS

/**
* A wrapper around SearchInput that handles a little application logic like
* - resetting search page to 1 on submission
* - firing tracking events
*/
const SearchField: React.FC<SearchFieldProps> = ({
onSubmit,
setPage,
...others
}) => {
const posthog = usePostHog()
const handleSubmit: SearchInputProps["onSubmit"] = (
event,
{ isEnter } = {},
) => {
onSubmit(event)
setPage(1)
if (POSTHOG?.api_key) {
posthog.capture("search_update", { isEnter: isEnter })
}
Comment on lines +30 to +32
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved the posthog stuff out of ol-components and into the new SearchField component. Since ol-components is generally styling / UI focused, I think this new component is a better place for the posthog call.

}

return <SearchInput onSubmit={handleSubmit} {...others} />
}

export { SearchField }
43 changes: 42 additions & 1 deletion frontends/mit-learn/src/pages/ChannelPage/ChannelSearch.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { screen, within, waitFor, renderTestApp } from "@/test-utils"
import { screen, within, waitFor, renderTestApp, user } from "@/test-utils"
import { setMockResponse, urls, factories, makeRequest } from "api/test-utils"
import type { LearningResourcesSearchResponse } from "api"
import invariant from "tiny-invariant"
Expand Down Expand Up @@ -264,4 +264,45 @@ describe("ChannelSearch", () => {
}
},
)

test("Submitting search text updates URL correctly", async () => {
const resources = factories.learningResources.resources({
count: 10,
}).results
const { channel } = setMockApiResponses({
search: {
count: 1000,
metadata: {
aggregations: {
resource_type: [
{ key: "course", doc_count: 100 },
{ key: "podcast", doc_count: 200 },
{ key: "program", doc_count: 300 },
{ key: "irrelevant", doc_count: 400 },
],
},
suggestions: [],
},
results: resources,
},
})
setMockResponse.get(urls.userMe.get(), {})

const initialSearch = "?q=meow&page=2"
const finalSearch = "?q=woof"

const { location } = renderTestApp({
url: `/c/${channel.channel_type}/${channel.name}${initialSearch}`,
})

const queryInput = await screen.findByRole<HTMLInputElement>("textbox", {
name: "Search for",
})
expect(queryInput.value).toBe("meow")
await user.clear(queryInput)
await user.paste("woof")
expect(location.current.search).toBe(initialSearch)
await user.click(screen.getByRole("button", { name: "Search" }))
expect(location.current.search).toBe(finalSearch)
})
})
8 changes: 5 additions & 3 deletions frontends/mit-learn/src/pages/ChannelPage/ChannelSearch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import type {
} from "@mitodl/course-search-utils"
import { useSearchParams } from "@mitodl/course-search-utils/react-router"
import SearchDisplay from "@/page-components/SearchDisplay/SearchDisplay"
import { Container, SearchInput, styled, VisuallyHidden } from "ol-components"
import { Container, styled, VisuallyHidden } from "ol-components"
import { SearchField } from "@/page-components/SearchField/SearchField"

import { getFacetManifest } from "@/pages/SearchPage/SearchPage"

Expand All @@ -30,7 +31,7 @@ const SearchInputContainer = styled(Container)(({ theme }) => ({
},
}))

const StyledSearchInput = styled(SearchInput)({
const StyledSearchField = styled(SearchField)({
width: "624px",
})

Expand Down Expand Up @@ -172,7 +173,7 @@ const ChannelSearch: React.FC<ChannelSearchProps> = ({
<section>
<VisuallyHidden as="h2">Search within {channelTitle}</VisuallyHidden>
<SearchInputContainer>
<StyledSearchInput
<StyledSearchField
value={currentText}
size="large"
onChange={(e) => setCurrentText(e.target.value)}
Expand All @@ -182,6 +183,7 @@ const ChannelSearch: React.FC<ChannelSearchProps> = ({
onClear={() => {
setCurrentTextAndQuery("")
}}
setPage={setPage}
/>
</SearchInputContainer>

Expand Down
37 changes: 18 additions & 19 deletions frontends/mit-learn/src/pages/SearchPage/SearchPage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -201,25 +201,24 @@ describe("SearchPage", () => {
await within(facetsContainer).findByText("Resource Type")
})

test.each([{ withPage: false }, { withPage: true }])(
"Submitting text updates URL",
async ({ withPage }) => {
setMockApiResponses({})
const urlQueryString = withPage ? "?q=meow&page=2" : "?q=meow"
const { location } = renderWithProviders(<SearchPage />, {
url: urlQueryString,
})
const queryInput = await screen.findByRole<HTMLInputElement>("textbox", {
name: "Search for",
})
expect(queryInput.value).toBe("meow")
await user.clear(queryInput)
await user.paste("woof")
expect(location.current.search).toBe(urlQueryString)
await user.click(screen.getByRole("button", { name: "Search" }))
expect(location.current.search).toBe("?q=woof")
},
)
test.each([
{ initialQuery: "?q=meow&page=2", finalQuery: "?q=woof" },
{ initialQuery: "?q=meow", finalQuery: "?q=woof" },
])("Submitting text updates URL", async ({ initialQuery, finalQuery }) => {
setMockApiResponses({})
const { location } = renderWithProviders(<SearchPage />, {
url: initialQuery,
})
const queryInput = await screen.findByRole<HTMLInputElement>("textbox", {
name: "Search for",
})
expect(queryInput.value).toBe("meow")
await user.clear(queryInput)
await user.paste("woof")
expect(location.current.search).toBe(initialQuery)
await user.click(screen.getByRole("button", { name: "Search" }))
expect(location.current.search).toBe(finalQuery)
})

test("unathenticated users do not see admin options", async () => {
setMockApiResponses({
Expand Down
26 changes: 7 additions & 19 deletions frontends/mit-learn/src/pages/SearchPage/SearchPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,8 @@ import {
getDepartmentName,
} from "@mitodl/course-search-utils"
import SearchDisplay from "@/page-components/SearchDisplay/SearchDisplay"
import {
SearchInput,
styled,
Container,
theme,
VisuallyHidden,
} from "ol-components"
import { styled, Container, theme, VisuallyHidden } from "ol-components"
import { SearchField } from "@/page-components/SearchField/SearchField"
import type { LearningResourceOfferor } from "api"
import { useOfferorsList } from "api/hooks/learningResources"
import { capitalize } from "ol-utilities"
Expand Down Expand Up @@ -56,7 +51,7 @@ const SearchFieldContainer = styled(Container)({
justifyContent: "center",
})

const SearchField = styled(SearchInput)(({ theme }) => ({
const StyledSearchField = styled(SearchField)(({ theme }) => ({
[theme.breakpoints.down("sm")]: {
width: "100%",
},
Expand Down Expand Up @@ -216,14 +211,6 @@ const SearchPage: React.FC = () => {
onFacetsChange,
})

const onSearchTermSubmit = useCallback(
(term: string) => {
setCurrentTextAndQuery(term)
setPage(1)
},
[setPage, setCurrentTextAndQuery],
)

const page = +(searchParams.get("page") ?? "1")

return (
Expand All @@ -234,16 +221,17 @@ const SearchPage: React.FC = () => {
</VisuallyHidden>
<Header>
<SearchFieldContainer>
<SearchField
<StyledSearchField
value={currentText}
size="large"
onChange={(e) => setCurrentText(e.target.value)}
onSubmit={(e) => {
onSearchTermSubmit(e.target.value)
setCurrentTextAndQuery(e.target.value)
}}
onClear={() => {
onSearchTermSubmit("")
setCurrentTextAndQuery("")
}}
setPage={setPage}
/>
</SearchFieldContainer>
</Header>
Expand Down
1 change: 0 additions & 1 deletion frontends/ol-components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
"material-ui-popup-state": "^5.1.0",
"ol-test-utilities": "0.0.0",
"ol-utilities": "0.0.0",
"posthog-js": "^1.165.0",
"react": "18.3.1",
"react-router": "^6.22.2",
"react-router-dom": "^6.22.2",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,17 @@ describe("SearchInput", () => {
it("Calls onSubmit when search is clicked", async () => {
const { user, spies } = renderSearchInput({ value: "chemistry" })
await user.click(getSearchButton())
expect(spies.onSubmit).toHaveBeenCalledWith(searchEvent("chemistry"))
expect(spies.onSubmit).toHaveBeenCalledWith(searchEvent("chemistry"), {
isEnter: false,
})
})

it("Calls onSubmit when 'Enter' is pressed", async () => {
const { user, spies } = renderSearchInput({ value: "chemistry" })
await user.type(getSearchInput(), "{enter}")
expect(spies.onSubmit).toHaveBeenCalledWith(searchEvent("chemistry"), {
isEnter: true,
})
})

it("Calls onClear clear is clicked", async () => {
Expand Down
46 changes: 14 additions & 32 deletions frontends/ol-components/src/components/SearchInput/SearchInput.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import React, { useCallback } from "react"
import React from "react"
import { RiSearch2Line, RiCloseLine } from "@remixicon/react"
import { Input, AdornmentButton } from "../Input/Input"
import type { InputProps } from "../Input/Input"
import styled from "@emotion/styled"
import { usePostHog } from "posthog-js/react"

const StyledInput = styled(Input)(({ theme }) => ({
boxShadow: "0px 8px 20px 0px rgba(120, 147, 172, 0.10)",
Expand Down Expand Up @@ -34,7 +33,10 @@ export interface SearchSubmissionEvent {
preventDefault: () => void
}

type SearchSubmitHandler = (event: SearchSubmissionEvent) => void
type SearchSubmitHandler = (
event: SearchSubmissionEvent,
opts?: { isEnter?: boolean },
) => void

interface SearchInputProps {
className?: string
Expand All @@ -54,35 +56,15 @@ const muiInputProps = { "aria-label": "Search for" }

const SearchInput: React.FC<SearchInputProps> = (props) => {
const { onSubmit, value } = props
const posthog = usePostHog()
const { POSTHOG } = APP_SETTINGS
const event = {
target: { value },
preventDefault: () => null,
}

const handleSubmit = useCallback(
(
ev:
| React.SyntheticEvent<HTMLInputElement>
| React.SyntheticEvent<HTMLButtonElement, MouseEvent>,
isEnter: boolean = false,
) => {
const event = {
target: { value },
preventDefault: () => null,
}
if (!(!POSTHOG?.api_key || POSTHOG.api_key.length < 1)) {
posthog.capture("search_update", { isEnter: isEnter })
}
onSubmit(event)
},
[onSubmit, value, posthog, POSTHOG],
)
const onInputKeyDown: React.KeyboardEventHandler<HTMLInputElement> =
useCallback(
(e) => {
if (e.key !== "Enter") return
handleSubmit(e, true)
},
[handleSubmit],
)
const onInputKeyDown: React.KeyboardEventHandler<HTMLInputElement> = (e) => {
if (e.key !== "Enter") return
onSubmit(event, { isEnter: true })
}

return (
<StyledInput
Expand Down Expand Up @@ -114,7 +96,7 @@ const SearchInput: React.FC<SearchInputProps> = (props) => {
<AdornmentButton
aria-label="Search"
className={props.classNameSearch}
onClick={handleSubmit}
onClick={() => onSubmit(event, { isEnter: false })}
>
<RiSearch2Line fontSize="inherit" />
</AdornmentButton>
Expand Down
5 changes: 4 additions & 1 deletion frontends/ol-components/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,10 @@ export * from "./constants/imgConfigs"
export { Input, AdornmentButton } from "./components/Input/Input"
export type { InputProps, AdornmentButtonProps } from "./components/Input/Input"
export { SearchInput } from "./components/SearchInput/SearchInput"
export type { SearchInputProps } from "./components/SearchInput/SearchInput"
export type {
SearchInputProps,
SearchSubmissionEvent,
} from "./components/SearchInput/SearchInput"
export { TextField } from "./components/TextField/TextField"
export {
SimpleSelect,
Expand Down
12 changes: 0 additions & 12 deletions yarn.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading