Skip to content

Commit

Permalink
Merge pull request #343 from nulib/4803-default-ai-tab
Browse files Browse the repository at this point in the history
Move Gen AI toggle tab state to localStorage
  • Loading branch information
adamjarling authored Jul 2, 2024
2 parents 48361d4 + bb9b42a commit 3881cd2
Show file tree
Hide file tree
Showing 19 changed files with 2,784 additions and 1,462 deletions.
9 changes: 4 additions & 5 deletions components/Chat/Chat.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,7 @@ jest.mock("@/hooks/useChatSocket");
}));

describe("Chat component", () => {
it("renders default ai response placeholder text", () => {
mockRouter.setCurrentUrl("/search?ai=true");
it("renders default placeholder text when no search term is present", () => {
render(<Chat />);

const wrapper = screen.getByText(
Expand All @@ -64,7 +63,7 @@ describe("Chat component", () => {
});

it("renders the chat response component when search term is present", () => {
mockRouter.setCurrentUrl("/search?q=tell+me+about+boats&ai=true");
mockRouter.setCurrentUrl("/search?q=tell+me+about+boats");

render(
<SearchProvider>
Expand Down Expand Up @@ -94,7 +93,7 @@ describe("Chat component", () => {
sendMessage: mockMessage,
}));

mockRouter.setCurrentUrl("/search?q=boats&ai=true");
mockRouter.setCurrentUrl("/search?q=boats");

render(
<SearchProvider>
Expand All @@ -112,7 +111,7 @@ describe("Chat component", () => {
});

it("doesn't send a websocket message if the search term is empty", () => {
mockRouter.setCurrentUrl("/search?ai=true");
mockRouter.setCurrentUrl("/search");
render(
<SearchProvider>
<Chat />
Expand Down
22 changes: 2 additions & 20 deletions components/Chat/Chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,24 +68,6 @@ const Chat = ({ totalResults }: { totalResults?: number }) => {
}
}, [message, searchTerm, sourceDocuments, searchDispatch]);

function handleResultsTab() {
if (window.scrollY === 0) {
searchDispatch({ activeTab: "results", type: "updateActiveTab" });
return;
}

window.scrollTo({ behavior: "instant", top: 0 });

const checkScroll = () => {
if (window.scrollY === 0) {
searchDispatch({ activeTab: "results", type: "updateActiveTab" });
window.removeEventListener("scroll", checkScroll);
}
};

window.addEventListener("scroll", checkScroll);
}

function handleNewQuestion() {
const input = document.getElementById("dc-search") as HTMLInputElement;
if (input) {
Expand All @@ -99,7 +81,7 @@ const Chat = ({ totalResults }: { totalResults?: number }) => {
<Container>
<p>
What can I help you find? Try searching for `john cage scrapbooks` or
`famous speeches`
`who played at the Berkeley Folk Music Festival in 1965?`
</p>
</Container>
);
Expand All @@ -116,7 +98,7 @@ const Chat = ({ totalResults }: { totalResults?: number }) => {
<>
<Container>
<StyledResponseActions>
<Button isPrimary isLowercase onClick={handleResultsTab}>
<Button isPrimary isLowercase>
View {pluralize("Result", totalResults || 0)}
</Button>
<Button isLowercase onClick={handleNewQuestion}>
Expand Down
3 changes: 1 addition & 2 deletions components/Facets/Filter/Submit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const FacetsFilterSubmit: React.FC<FacetsFilterSubmitProps> = ({
const {
query: { q },
} = router;
const { ai, urlFacets } = useQueryParams();
const { urlFacets } = useQueryParams();

const {
filterDispatch,
Expand All @@ -34,7 +34,6 @@ const FacetsFilterSubmit: React.FC<FacetsFilterSubmitProps> = ({
...(q && { q }),
...urlFacets,
...userFacetsUnsubmitted,
...(ai && { ai }),
};

router.push({
Expand Down
6 changes: 4 additions & 2 deletions components/Facets/UserFacets/UserFacets.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { SearchProvider } from "@/context/search-context";
import UserFacets from "./UserFacets";

const searchStateDefault: SearchContextStore = {
activeTab: "results",
aggregations: {},
chat: {
answer: "",
Expand All @@ -19,7 +18,6 @@ const searchStateDefault: SearchContextStore = {
};

const searchState: SearchContextStore = {
activeTab: "results",
aggregations: {},
chat: {
answer: "",
Expand Down Expand Up @@ -87,9 +85,11 @@ describe("UserFacet UI component", () => {
);
const userFacets = await screen.findByTestId(`facet-user-component`);
expect(userFacets).toBeInTheDocument();

const toggle = screen.getByTestId(`facet-user-component-popover-toggle`);
expect(toggle).toBeInTheDocument();
expect(toggle).toHaveTextContent("1");

const content = screen.queryByText(`facet-user-component-popover-content`);
expect(content).toBeNull();
});
Expand All @@ -114,8 +114,10 @@ describe("UserFacet UI component", () => {
</FilterProvider>
</SearchProvider>,
);

const userFacets = screen.getByTestId(`facet-user-component`);
expect(userFacets).toBeInTheDocument();

const values = screen.getAllByTestId(`facet-user-value-component`);
expect(values.length).toBe(3);
});
Expand Down
9 changes: 9 additions & 0 deletions components/Header/Header.styled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,14 @@ const User = styled("span", {
},
});

const Logout = styled("button", {
border: "none",
background: "none",
color: "$white",
cursor: "pointer",
paddingLeft: "$gr2",
});

const HeaderStyled = styled("header", {
flexDirection: "column",

Expand Down Expand Up @@ -230,6 +238,7 @@ export type HeaderVariants = VariantProps<typeof HeaderStyled>;

export {
Lockup,
Logout,
Menu,
MenuToggle,
Primary,
Expand Down
19 changes: 9 additions & 10 deletions components/Header/Super.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { IconClear, IconMenu } from "../Shared/SVG/Icons";
import {
Logout,
Menu,
MenuToggle,
Super,
Expand All @@ -14,6 +15,7 @@ import { NavResponsiveOnly } from "@/components/Nav/Nav.styled";
import { NorthwesternWordmark } from "@/components/Shared/SVG/Northwestern";
import React from "react";
import { UserContext } from "@/context/user-context";
import useLocalStorage from "@/hooks/useLocalStorage";

const nav = [
{
Expand All @@ -33,6 +35,7 @@ const nav = [
export default function HeaderSuper() {
const [isLoaded, setIsLoaded] = React.useState(false);
const [isExpanded, setIsExpanded] = React.useState(false);
const [ai, setAI] = useLocalStorage("ai", "false");

React.useEffect(() => {
setIsLoaded(true);
Expand All @@ -41,6 +44,11 @@ export default function HeaderSuper() {
const userAuthContext = React.useContext(UserContext);
const handleMenu = () => setIsExpanded(!isExpanded);

const handleLogout = () => {
if (ai === "true") setAI("false");
window.location.href = `${DCAPI_ENDPOINT}/auth/logout`;
};

return (
<Super>
<Container>
Expand Down Expand Up @@ -68,16 +76,7 @@ export default function HeaderSuper() {
{userAuthContext?.user?.isLoggedIn && (
<>
<User>{userAuthContext.user.name}</User>
<a
href={`${DCAPI_ENDPOINT}/auth/logout`}
style={{
cursor: "pointer",
paddingLeft: "8px",
textDecoration: "underline",
}}
>
Logout
</a>
<Logout onClick={handleLogout}>Logout</Logout>
</>
)}
</Nav>
Expand Down
39 changes: 21 additions & 18 deletions components/Search/GenerativeAIToggle.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@ const defaultUser = {

const withUserProvider = (
Component: React.ReactNode,
user: UserContextType = defaultUser
user: UserContextType = defaultUser,
) => {
return <UserContext.Provider value={user}>{Component}</UserContext.Provider>;
};

const withSearchProvider = (
Component: React.ReactNode,
initialState = defaultSearchState
initialState = defaultSearchState,
) => {
return (
<SearchProvider initialState={initialState}>{Component}</SearchProvider>
Expand All @@ -40,6 +40,9 @@ const withSearchProvider = (
describe("GenerativeAIToggle", () => {
beforeEach(() => {
mockRouter.setCurrentUrl("/");

// Note: localStorage mocked in "jest.setup.js"
localStorage.clear();
});

it("renders the generative AI toggle UI and toggles state for a logged in user", async () => {
Expand All @@ -54,6 +57,7 @@ describe("GenerativeAIToggle", () => {

await user.click(checkbox);
expect(checkbox).toHaveAttribute("data-state", "checked");
expect(localStorage.getItem("ai")).toEqual(JSON.stringify("true"));
});

it("renders the generative AI tooltip", () => {
Expand All @@ -76,54 +80,53 @@ describe("GenerativeAIToggle", () => {
render(
withUserProvider(
withSearchProvider(<GenerativeAIToggle />),
nonLoggedInUser
)
nonLoggedInUser,
),
);

const checkbox = await screen.findByRole("checkbox");
await user.click(checkbox);

const generativeAIDialog = await screen.findByText(
"You must be logged in with a Northwestern NetID to use the Generative AI search feature."
"You must be logged in with a Northwestern NetID to use the Generative AI search feature.",
);

expect(generativeAIDialog).toBeInTheDocument();
});

it("renders a toggled generative ai state when a query param is set and user is logged in", () => {
it("renders a toggled generative ai state when localStorage variable is set and user is logged in", () => {
const activeSearchState = {
...defaultSearchState,
isGenerativeAI: true,
};

mockRouter.setCurrentUrl("/search?ai=true");
localStorage.setItem("ai", JSON.stringify("true"));

mockRouter.setCurrentUrl("/search");
render(
withUserProvider(
withSearchProvider(<GenerativeAIToggle />, activeSearchState)
)
withSearchProvider(<GenerativeAIToggle />, activeSearchState),
),
);

const checkbox = screen.getByRole("checkbox");
expect(checkbox).toHaveAttribute("data-state", "checked");
});

it("sets a query param in the URL when generative AI checkbox is clicked", async () => {
it("updates localStorage ai variable when generative AI checkbox is clicked", async () => {
const user = userEvent.setup();

mockRouter.setCurrentUrl("/");

localStorage.setItem("ai", JSON.stringify("false"));

render(
withUserProvider(
withSearchProvider(<GenerativeAIToggle />, defaultSearchState)
)
withSearchProvider(<GenerativeAIToggle />, defaultSearchState),
),
);

await user.click(screen.getByRole("checkbox"));

expect(mockRouter).toMatchObject({
asPath: "/?ai=true",
pathname: "/",
query: { ai: "true" },
});
expect(localStorage.getItem("ai")).toEqual(JSON.stringify("true"));
});
});
33 changes: 28 additions & 5 deletions components/Search/Search.test.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,37 @@
import { render, screen } from "@/test-utils";

import Search from "./Search";
import { UserContext } from "@/context/user-context";
import { UserContext as UserContextType } from "@/types/context/user";
import mockRouter from "next-router-mock";
import { renderHook } from "@testing-library/react";
import { useRouter } from "next/router";
import userEvent from "@testing-library/user-event";

const mockIsSearchActive = jest.fn();

const defaultUser = {
user: {
email: "[email protected]",
isLoggedIn: true,
isReadingRoom: false,
name: "Ace Frehley",
sub: "xyz123",
},
};

const withUserProvider = (
Component: React.ReactNode,
user: UserContextType = defaultUser,
) => {
return <UserContext.Provider value={user}>{Component}</UserContext.Provider>;
};

describe("Search component", () => {
beforeEach(() => {
localStorage.clear();
});

it("renders the search ui component", () => {
render(<Search isSearchActive={() => ({})} />);
const wrapper = screen.getByTestId("search-ui-component");
Expand Down Expand Up @@ -71,19 +94,19 @@ describe("Search component", () => {
it("renders standard placeholder text for non AI search", () => {
render(<Search isSearchActive={mockIsSearchActive} />);
const input = screen.getByPlaceholderText(
"Search by keyword or phrase, ex: Berkeley Music Festival"
"Search by keyword or phrase, ex: Berkeley Music Festival",
);
expect(input).toBeInTheDocument();
});

it("renders generative AI placeholder text when AI search is active", () => {
mockRouter.setCurrentUrl("/search?ai=true");
localStorage.setItem("ai", JSON.stringify("true"));

render(withUserProvider(<Search isSearchActive={mockIsSearchActive} />));

render(<Search isSearchActive={mockIsSearchActive} />);
const input = screen.getByPlaceholderText(
"What can I show you from our collections?"
"What can I show you from our collections?",
);

expect(input).toBeInTheDocument();
});
});
Loading

0 comments on commit 3881cd2

Please sign in to comment.