From 5007daf6453c7ef95361a749c4a520283bede4d1 Mon Sep 17 00:00:00 2001 From: Emily Arnold Date: Tue, 18 Aug 2026 10:55:48 +0000 Subject: [PATCH 1/6] Add instrumentSessionList to localStorage --- .../InstrumentSessionProvider.tsx | 32 +++++++++++++++++-- .../InstrumentSessionView.test.tsx | 3 +- .../InstrumentSessionView.tsx | 6 ++-- 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/packages/app-shell/src/context/instrumentSession/InstrumentSessionProvider.tsx b/packages/app-shell/src/context/instrumentSession/InstrumentSessionProvider.tsx index e46cd985..05a43d97 100644 --- a/packages/app-shell/src/context/instrumentSession/InstrumentSessionProvider.tsx +++ b/packages/app-shell/src/context/instrumentSession/InstrumentSessionProvider.tsx @@ -2,11 +2,13 @@ import { useState, useEffect, useContext, type ReactNode } from "react"; import { createContext } from "react"; export const ID_STORAGE_KEY = "instrument-session-id"; +export const LIST_STORAGE_KEY = "instrument-session-list"; export type InstrumentSessionContextType = { instrumentSession: string; setInstrumentSession: (session: string) => void; - sessionsList: string[]; + instrumentSessionList: string[]; + setInstrumentSessionList: (list: string[]) => void; }; export const InstrumentSessionContext = createContext< @@ -20,6 +22,31 @@ export const InstrumentSessionProvider = ({ children: ReactNode; sessionsList?: string[]; }) => { + const [instrumentSessionList, setInstrumentSessionList] = useState( + () => { + try { + const rawItem = localStorage.getItem(LIST_STORAGE_KEY); + if (!rawItem) { + return sessionsList; + } + return JSON.parse(rawItem); + } catch (error) { + console.error( + "Failed to load instrument session from localStorage:", + error, + ); + return sessionsList; + } + }, + ); + + useEffect(() => { + localStorage.setItem( + LIST_STORAGE_KEY, + JSON.stringify(instrumentSessionList), + ); + }, [instrumentSessionList]); + const [instrumentSession, setInstrumentSession] = useState(() => { try { const rawItem = localStorage.getItem(ID_STORAGE_KEY); @@ -45,7 +72,8 @@ export const InstrumentSessionProvider = ({ value={{ instrumentSession, setInstrumentSession, - sessionsList, + instrumentSessionList, + setInstrumentSessionList, }} > {children} diff --git a/packages/app-shell/src/context/instrumentSession/InstrumentSessionView.test.tsx b/packages/app-shell/src/context/instrumentSession/InstrumentSessionView.test.tsx index 30a86bff..7dd556a7 100644 --- a/packages/app-shell/src/context/instrumentSession/InstrumentSessionView.test.tsx +++ b/packages/app-shell/src/context/instrumentSession/InstrumentSessionView.test.tsx @@ -19,7 +19,8 @@ vi.mock(import("./InstrumentSessionProvider"), async (importOriginal) => { useInstrumentSession: () => ({ instrumentSession: "cm54321-1", setInstrumentSession: vi.fn(), - sessionsList: ["cm123-4", "cm567-8"], + instrumentSessionList: ["cm123-4", "cm567-8"], + setInstrumentSessionList: vi.fn(), }), }; }); diff --git a/packages/app-shell/src/context/instrumentSession/InstrumentSessionView.tsx b/packages/app-shell/src/context/instrumentSession/InstrumentSessionView.tsx index 6daa17cb..89d1193a 100644 --- a/packages/app-shell/src/context/instrumentSession/InstrumentSessionView.tsx +++ b/packages/app-shell/src/context/instrumentSession/InstrumentSessionView.tsx @@ -9,11 +9,11 @@ import { Science, ExpandMore } from "@mui/icons-material"; import { ListSubheader } from "@mui/material"; export function InstrumentSessionView() { - const { instrumentSession, setInstrumentSession, sessionsList } = + const { instrumentSession, setInstrumentSession, instrumentSessionList } = useInstrumentSession(); const [selectedIndex, setSelectedIndex] = useState(null); - const menuDisabled = sessionsList.length <= 1 ? true : false; + const menuDisabled = instrumentSessionList.length <= 1 ? true : false; const id = "session-input"; const staticButtonId = `${id}-staticButton`; const buttonId = `${id}-button`; @@ -77,7 +77,7 @@ export function InstrumentSessionView() { > Available Sessions - {sessionsList.map((option, index) => ( + {instrumentSessionList.map((option, index) => ( Date: Tue, 18 Aug 2026 16:13:20 +0000 Subject: [PATCH 2/6] Create button which queries for sessions, allow for no sessions and no active session --- apps/i15-1/src/AppProviders.tsx | 24 +++++---- .../ExperimentTable/ULIMSExperimentsTable.tsx | 14 ++--- .../components/getInstrumentSessionButton.tsx | 45 ++++++++++++++++ .../getInstrumentSessionsQuery.generated.ts | 10 ++++ .../src/graphql/getInstrumentSessionsQuery.ts | 15 ++++++ apps/i15-1/src/mocks/handlers.ts | 25 +++++++++ apps/i15-1/src/routes/Dashboard.tsx | 3 ++ .../InstrumentSessionProvider.tsx | 54 ++++++++----------- .../InstrumentSessionView.tsx | 27 ++++++++-- 9 files changed, 162 insertions(+), 55 deletions(-) create mode 100644 apps/i15-1/src/components/getInstrumentSessionButton.tsx create mode 100644 apps/i15-1/src/graphql/getInstrumentSessionsQuery.generated.ts create mode 100644 apps/i15-1/src/graphql/getInstrumentSessionsQuery.ts diff --git a/apps/i15-1/src/AppProviders.tsx b/apps/i15-1/src/AppProviders.tsx index 5c92fc83..352c6870 100644 --- a/apps/i15-1/src/AppProviders.tsx +++ b/apps/i15-1/src/AppProviders.tsx @@ -22,17 +22,19 @@ export function AppProviders({ api, theme, children }: Props) { const config = useLoadPvwsConfig(); return ( - - - - - - {children} - - - - - + + + + + + + {children} + + + + + + ); } diff --git a/apps/i15-1/src/components/ExperimentTable/ULIMSExperimentsTable.tsx b/apps/i15-1/src/components/ExperimentTable/ULIMSExperimentsTable.tsx index 77be34a1..4b3577c9 100644 --- a/apps/i15-1/src/components/ExperimentTable/ULIMSExperimentsTable.tsx +++ b/apps/i15-1/src/components/ExperimentTable/ULIMSExperimentsTable.tsx @@ -19,7 +19,6 @@ import type { import type { ExperimentDefinition, Sample } from "../../../generated/queue"; import { useInstrumentSession, visitTextToVisit } from "@atlas/app-shell"; - export type ExperimentDefinitionData = { q_max: number; frames: number; @@ -76,11 +75,14 @@ export function ExperimentList() { ); } - const visit = visitTextToVisit(instrumentSession); + const visit = visitTextToVisit(instrumentSession ?? "cm0-0"); const { data, loading, error } = useQuery(GET_EXPERIMENTS, { skip: !visit, - variables: { proposal: visit?.proposalNumber ?? 0, session: visit?.number ?? 0 }, + variables: { + proposal: visit?.proposalNumber ?? 0, + session: visit?.number ?? 0, + }, fetchPolicy: "cache-and-network", context: { pathname: location.pathname }, }); @@ -162,9 +164,9 @@ export function ExperimentList() { }, muiToolbarAlertBannerProps: error ? { - color: "error", - children: `Error: ${error.message}`, - } + color: "error", + children: `Error: ${error.message}`, + } : undefined, }); diff --git a/apps/i15-1/src/components/getInstrumentSessionButton.tsx b/apps/i15-1/src/components/getInstrumentSessionButton.tsx new file mode 100644 index 00000000..a46c0106 --- /dev/null +++ b/apps/i15-1/src/components/getInstrumentSessionButton.tsx @@ -0,0 +1,45 @@ +import { useLazyQuery } from "@apollo/client/react"; +import { getInstrumentSessionsQuery } from "../graphql/getInstrumentSessionsQuery.ts"; +import type { TypedDocumentNode } from "@apollo/client"; +import type { + InstrumentSessionQuery, + InstrumentSessionQueryVariables, +} from "../graphql/getInstrumentSessionsQuery.generated.ts"; +import { useInstrumentSession } from "@atlas/app-shell"; +import { Button } from "@mui/material"; + +export const InstrumentSessionButton = () => { + const { setInstrumentSessionList } = useInstrumentSession(); + + const GET_SESSIONS: TypedDocumentNode< + InstrumentSessionQuery, + InstrumentSessionQueryVariables + > = getInstrumentSessionsQuery; + + const [fetchSessions, { loading, error }] = useLazyQuery(GET_SESSIONS); + + const handleButtonClick = async () => { + try { + const { data } = await fetchSessions({ + variables: { instrumentKey: "I15-1" }, + }); + + const edges = data?.instrumentByKey?.instrumentSessions?.edges; + if (edges && edges.length > 0) { + const sessionsList = edges.flatMap((edge) => { + const ref = edge?.node?.instrumentSessionReference; + return ref ? [ref.toLocaleLowerCase()] : []; + }); + setInstrumentSessionList(sessionsList); + } + } catch (err) { + console.error("Failed to fetch sessions:", err); + } + }; + + return ( + + ); +}; diff --git a/apps/i15-1/src/graphql/getInstrumentSessionsQuery.generated.ts b/apps/i15-1/src/graphql/getInstrumentSessionsQuery.generated.ts new file mode 100644 index 00000000..23a7e74b --- /dev/null +++ b/apps/i15-1/src/graphql/getInstrumentSessionsQuery.generated.ts @@ -0,0 +1,10 @@ +/** Internal type. DO NOT USE DIRECTLY. */ +type Exact = { [K in keyof T]: T[K] }; +/** Internal type. DO NOT USE DIRECTLY. */ +export type Incremental = T | { [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never }; +export type InstrumentSessionQueryVariables = Exact<{ + instrumentKey: string; +}>; + + +export type InstrumentSessionQuery = { instrumentByKey: { __typename: 'Instrument', instrumentSessions: { __typename: 'InstrumentSessionConnection', edges: Array<{ __typename: 'InstrumentSessionEdge', node: { __typename: 'InstrumentSession', instrumentSessionReference: string | null } }> } } | null }; diff --git a/apps/i15-1/src/graphql/getInstrumentSessionsQuery.ts b/apps/i15-1/src/graphql/getInstrumentSessionsQuery.ts new file mode 100644 index 00000000..aa401a8c --- /dev/null +++ b/apps/i15-1/src/graphql/getInstrumentSessionsQuery.ts @@ -0,0 +1,15 @@ +import { gql } from "@apollo/client"; + +export const getInstrumentSessionsQuery = gql` + query InstrumentSession($instrumentKey: String!) { + instrumentByKey(key: $instrumentKey) { + instrumentSessions(filterBy: { state: { eq: IN_PROGRESS } }) { + edges { + node { + instrumentSessionReference + } + } + } + } + } +`; diff --git a/apps/i15-1/src/mocks/handlers.ts b/apps/i15-1/src/mocks/handlers.ts index 09f5e5e1..ef9dd04e 100644 --- a/apps/i15-1/src/mocks/handlers.ts +++ b/apps/i15-1/src/mocks/handlers.ts @@ -167,6 +167,27 @@ const fakeContainersForInstrument: { }, }; +const fakeInstrumentSession = { + data: { + instrumentByKey: { + instrumentSessions: { + edges: [ + { + node: { + instrumentSessionReference: "CM44163-4", + }, + }, + { + node: { + instrumentSessionReference: "CM44163-5", + }, + }, + ], + }, + }, + }, +}; + function setWorkerState(new_state: string) { workerStatus.status = new_state; } @@ -753,6 +774,10 @@ export const handlers = [ } } + if (body.operationName === "InstrumentSession") { + return HttpResponse.json(fakeInstrumentSession); + } + return HttpResponse.json(fakeExperiments); }), diff --git a/apps/i15-1/src/routes/Dashboard.tsx b/apps/i15-1/src/routes/Dashboard.tsx index fca281af..62f5d994 100644 --- a/apps/i15-1/src/routes/Dashboard.tsx +++ b/apps/i15-1/src/routes/Dashboard.tsx @@ -4,12 +4,14 @@ import PrecisionManufacturingIcon from "@mui/icons-material/PrecisionManufacturi import QueueIcon from "@mui/icons-material/Queue"; import { useUserAuth } from "../context/userAuth/useUserAuth.ts"; import { User } from "@diamondlightsource/sci-react-ui"; +import { InstrumentSessionButton } from "../components/getInstrumentSessionButton.tsx"; function Dashboard() { const user = useUserAuth(); const handleLogIn = () => window.location.assign("/oauth2/sign_in"); const handleLogOut = () => window.location.assign("/oauth2/sign_out"); + return ( <> @@ -26,6 +28,7 @@ function Dashboard() { : { fedid: user.person } } /> + + + + ); + } if (menuDisabled) { return (
@@ -44,7 +61,7 @@ export function InstrumentSessionView() { color="secondary" variant="outlined" > - {instrumentSession} + {instrumentSession ? instrumentSession : "No Session Selected"}
); @@ -62,7 +79,7 @@ export function InstrumentSessionView() { color="secondary" variant="outlined" > - {instrumentSession} + {instrumentSession ? instrumentSession : "No Session Selected"} Available Sessions - {instrumentSessionList.map((option, index) => ( + {instrumentSessionList?.map((option, index) => ( Date: Tue, 18 Aug 2026 16:28:55 +0000 Subject: [PATCH 3/6] Tidy up linting issues --- apps/i15-1/src/routes/Robot.tsx | 4 ++-- .../context/instrumentSession/InstrumentSessionProvider.tsx | 2 +- packages/blueapi-ui/src/PlanBrowser/PlanParameters.tsx | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/i15-1/src/routes/Robot.tsx b/apps/i15-1/src/routes/Robot.tsx index 36b86d70..02db976c 100644 --- a/apps/i15-1/src/routes/Robot.tsx +++ b/apps/i15-1/src/routes/Robot.tsx @@ -109,12 +109,12 @@ function RobotControl() { diff --git a/packages/app-shell/src/context/instrumentSession/InstrumentSessionProvider.tsx b/packages/app-shell/src/context/instrumentSession/InstrumentSessionProvider.tsx index b2f88e6c..02ad80ca 100644 --- a/packages/app-shell/src/context/instrumentSession/InstrumentSessionProvider.tsx +++ b/packages/app-shell/src/context/instrumentSession/InstrumentSessionProvider.tsx @@ -40,7 +40,7 @@ export const InstrumentSessionProvider = ({ if (!rawItem) { return sessionsList ? sessionsList[0] : null; } - return rawItem; + return JSON.parse(rawItem); } catch (error) { console.error( "Failed to load instrument session from localStorage:", diff --git a/packages/blueapi-ui/src/PlanBrowser/PlanParameters.tsx b/packages/blueapi-ui/src/PlanBrowser/PlanParameters.tsx index 28b02df2..9c5eaa36 100644 --- a/packages/blueapi-ui/src/PlanBrowser/PlanParameters.tsx +++ b/packages/blueapi-ui/src/PlanBrowser/PlanParameters.tsx @@ -69,7 +69,7 @@ export function PlanParameters({ plan }: { plan: Plan }) { From 03d53d70b7baaec7f5436235b5c0ba59d9e03555 Mon Sep 17 00:00:00 2001 From: Emily Arnold Date: Tue, 18 Aug 2026 16:31:14 +0000 Subject: [PATCH 4/6] remove unnecessary variables --- apps/i15-1/src/components/getInstrumentSessionButton.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/i15-1/src/components/getInstrumentSessionButton.tsx b/apps/i15-1/src/components/getInstrumentSessionButton.tsx index a46c0106..c7a36bd6 100644 --- a/apps/i15-1/src/components/getInstrumentSessionButton.tsx +++ b/apps/i15-1/src/components/getInstrumentSessionButton.tsx @@ -16,7 +16,7 @@ export const InstrumentSessionButton = () => { InstrumentSessionQueryVariables > = getInstrumentSessionsQuery; - const [fetchSessions, { loading, error }] = useLazyQuery(GET_SESSIONS); + const [fetchSessions] = useLazyQuery(GET_SESSIONS); const handleButtonClick = async () => { try { From 8f9d2cf2147aa709d80134e8751ee977a5ff14b0 Mon Sep 17 00:00:00 2001 From: Emily Arnold Date: Tue, 18 Aug 2026 17:00:54 +0000 Subject: [PATCH 5/6] Overhaul tests --- apps/i15-1/src/mocks/handlers.ts | 5 - .../InstrumentSessionView.test.tsx | 144 ++++++++++++++---- .../InstrumentSessionView.tsx | 4 + 3 files changed, 117 insertions(+), 36 deletions(-) diff --git a/apps/i15-1/src/mocks/handlers.ts b/apps/i15-1/src/mocks/handlers.ts index ef9dd04e..fcf89a32 100644 --- a/apps/i15-1/src/mocks/handlers.ts +++ b/apps/i15-1/src/mocks/handlers.ts @@ -177,11 +177,6 @@ const fakeInstrumentSession = { instrumentSessionReference: "CM44163-4", }, }, - { - node: { - instrumentSessionReference: "CM44163-5", - }, - }, ], }, }, diff --git a/packages/app-shell/src/context/instrumentSession/InstrumentSessionView.test.tsx b/packages/app-shell/src/context/instrumentSession/InstrumentSessionView.test.tsx index 7dd556a7..e6964eba 100644 --- a/packages/app-shell/src/context/instrumentSession/InstrumentSessionView.test.tsx +++ b/packages/app-shell/src/context/instrumentSession/InstrumentSessionView.test.tsx @@ -1,6 +1,9 @@ import { render, screen, userEvent, within } from "@atlas/vitest-conf"; import { InstrumentSessionView } from "./InstrumentSessionView"; -import { InstrumentSessionProvider } from "./InstrumentSessionProvider"; +import { + InstrumentSessionProvider, + useInstrumentSession, +} from "./InstrumentSessionProvider"; import { describe, it, expect, vi } from "vitest"; function renderComponentWithProvider() { @@ -11,63 +14,142 @@ function renderComponentWithProvider() { ); } +// vi.mock(import("./InstrumentSessionProvider"), async (importOriginal) => { +// const actual = +// await importOriginal(); +// return { +// ...actual, +// useInstrumentSession: () => ({ +// instrumentSession: "cm54321-1", +// setInstrumentSession: vi.fn(), +// instrumentSessionList: ["cm123-4", "cm567-8"], +// setInstrumentSessionList: vi.fn(), +// }), +// }; +// }); + vi.mock(import("./InstrumentSessionProvider"), async (importOriginal) => { const actual = await importOriginal(); return { ...actual, - useInstrumentSession: () => ({ - instrumentSession: "cm54321-1", - setInstrumentSession: vi.fn(), - instrumentSessionList: ["cm123-4", "cm567-8"], - setInstrumentSessionList: vi.fn(), - }), + useInstrumentSession: vi.fn(), }; }); describe("InstrumentSessionView", () => { + const mockSetInstrumentSession = vi.fn(); + const mockSetInstrumentSessionList = vi.fn(); + beforeEach(() => { localStorage.clear(); + vi.clearAllMocks(); }); it("shows only the current instrument session", () => { + vi.mocked(useInstrumentSession).mockReturnValue({ + instrumentSession: "cm54321-1", + setInstrumentSession: vi.fn(), + instrumentSessionList: ["cm123-4", "cm567-8"], + setInstrumentSessionList: vi.fn(), + }); + renderComponentWithProvider(); + expect(screen.getByTestId("session-input-button")).toBeInTheDocument(); + expect(screen.getByTestId("session-input-button")).toHaveTextContent( + "cm54321-1", + ); expect(screen.queryByTestId("session-input-menu")).not.toBeInTheDocument(); }); - it("shows sessions from the provided sessions list when clicked", async () => { + it("shows a static button when only one session is available", () => { + vi.mocked(useInstrumentSession).mockReturnValue({ + instrumentSession: "cm54321-1", + setInstrumentSession: vi.fn(), + instrumentSessionList: ["cm54321-1"], + setInstrumentSessionList: vi.fn(), + }); + renderComponentWithProvider(); - const user = userEvent.setup(); - await user.click(screen.getByTestId("session-input-button")); - expect(screen.getByTestId("session-input-menu")).toBeInTheDocument(); - expect(screen.getByText("cm123-4")).toBeInTheDocument(); - expect(screen.getByText("cm567-8")).toBeInTheDocument(); + expect( + screen.getByTestId("session-input-staticButton"), + ).toBeInTheDocument(); + expect(screen.getByTestId("session-input-staticButton")).toHaveTextContent( + "cm54321-1", + ); + expect(screen.queryByTestId("session-input-menu")).not.toBeInTheDocument(); }); - it("closes menu when a menu item has been clicked", async () => { - renderComponentWithProvider(); + it("shows no session selected when there is no session selected", () => { + vi.mocked(useInstrumentSession).mockReturnValue({ + instrumentSession: null, + setInstrumentSession: vi.fn(), + instrumentSessionList: ["cm123-4", "cm567-8"], + setInstrumentSessionList: vi.fn(), + }); - const user = userEvent.setup(); - await user.click(screen.getByTestId("session-input-button")); - expect(screen.getByTestId("session-input-menu")).toBeInTheDocument(); + renderComponentWithProvider(); - await user.click(screen.getByText("cm123-4")); - expect(screen.queryByTestId("session-input-menu")).not.toBeInTheDocument(); + expect(screen.getByTestId("session-input-button")).toHaveTextContent( + "No Session Selected", + ); }); - it("closes menu when button has been clicked again", async () => { - renderComponentWithProvider(); + // it("shows sessions from the provided sessions list when clicked", async () => { + // vi.mocked(useInstrumentSession).mockReturnValue({ + // instrumentSession: "cm54321-1", + // setInstrumentSession: vi.fn(), + // instrumentSessionList: ["cm123-4", "cm567-8"], + // setInstrumentSessionList: vi.fn(), + // }); - const user = userEvent.setup(); - await user.click(screen.getByTestId("session-input-button")); - expect(screen.getByTestId("session-input-menu")).toBeInTheDocument(); + // renderComponentWithProvider(); - const backdrop = screen.getByRole("presentation").firstChild; - if (backdrop instanceof Element) { - await user.click(backdrop); - } - expect(screen.queryByTestId("session-input-menu")).not.toBeInTheDocument(); - }); + // const user = userEvent.setup(); + // await user.click(screen.getByTestId("session-input-button")); + // expect(screen.getByTestId("session-input-menu")).toBeInTheDocument(); + // expect(screen.getByText("cm123-4")).toBeInTheDocument(); + // expect(screen.getByText("cm567-8")).toBeInTheDocument(); + // }); + + // it("closes menu when a menu item has been clicked", async () => { + // vi.mocked(useInstrumentSession).mockReturnValue({ + // instrumentSession: "cm54321-1", + // setInstrumentSession: vi.fn(), + // instrumentSessionList: ["cm123-4", "cm567-8"], + // setInstrumentSessionList: vi.fn(), + // }); + + // renderComponentWithProvider(); + + // const user = userEvent.setup(); + // await user.click(screen.getByTestId("session-input-button")); + // expect(screen.getByTestId("session-input-menu")).toBeInTheDocument(); + + // await user.click(screen.getByText("cm123-4")); + // expect(screen.queryByTestId("session-input-menu")).not.toBeInTheDocument(); + // }); + + // it("closes menu when button has been clicked again", async () => { + // vi.mocked(useInstrumentSession).mockReturnValue({ + // instrumentSession: "cm54321-1", + // setInstrumentSession: vi.fn(), + // instrumentSessionList: ["cm123-4", "cm567-8"], + // setInstrumentSessionList: vi.fn(), + // }); + + // renderComponentWithProvider(); + + // const user = userEvent.setup(); + // await user.click(screen.getByTestId("session-input-button")); + // expect(screen.getByTestId("session-input-menu")).toBeInTheDocument(); + + // const backdrop = screen.getByRole("presentation").firstChild; + // if (backdrop instanceof Element) { + // await user.click(backdrop); + // } + // expect(screen.queryByTestId("session-input-menu")).not.toBeInTheDocument(); + // }); }); diff --git a/packages/app-shell/src/context/instrumentSession/InstrumentSessionView.tsx b/packages/app-shell/src/context/instrumentSession/InstrumentSessionView.tsx index 0583575b..914134a5 100644 --- a/packages/app-shell/src/context/instrumentSession/InstrumentSessionView.tsx +++ b/packages/app-shell/src/context/instrumentSession/InstrumentSessionView.tsx @@ -36,6 +36,10 @@ export function InstrumentSessionView() { setAnchorEl(null); }; + if (instrumentSessionList && instrumentSessionList.length == 1) { + setInstrumentSession(instrumentSessionList[0]); + } + if (!instrumentSession && !instrumentSessionList) { return (
From fe037656962338581dabe592c24f1d45162f51b9 Mon Sep 17 00:00:00 2001 From: Emily Arnold Date: Wed, 19 Aug 2026 08:28:27 +0000 Subject: [PATCH 6/6] Add tests, logic to instrument session view to handle case where no session is selected --- .../components/getInstrumentSessionButton.tsx | 6 +- .../InstrumentSessionView.test.tsx | 190 +++++++++++------- .../InstrumentSessionView.tsx | 32 ++- 3 files changed, 147 insertions(+), 81 deletions(-) diff --git a/apps/i15-1/src/components/getInstrumentSessionButton.tsx b/apps/i15-1/src/components/getInstrumentSessionButton.tsx index c7a36bd6..7fbdf59a 100644 --- a/apps/i15-1/src/components/getInstrumentSessionButton.tsx +++ b/apps/i15-1/src/components/getInstrumentSessionButton.tsx @@ -9,7 +9,8 @@ import { useInstrumentSession } from "@atlas/app-shell"; import { Button } from "@mui/material"; export const InstrumentSessionButton = () => { - const { setInstrumentSessionList } = useInstrumentSession(); + const { setInstrumentSession, setInstrumentSessionList } = + useInstrumentSession(); const GET_SESSIONS: TypedDocumentNode< InstrumentSessionQuery, @@ -31,6 +32,9 @@ export const InstrumentSessionButton = () => { return ref ? [ref.toLocaleLowerCase()] : []; }); setInstrumentSessionList(sessionsList); + if (sessionsList.length == 1) { + setInstrumentSession(sessionsList[0]); + } } } catch (err) { console.error("Failed to fetch sessions:", err); diff --git a/packages/app-shell/src/context/instrumentSession/InstrumentSessionView.test.tsx b/packages/app-shell/src/context/instrumentSession/InstrumentSessionView.test.tsx index e6964eba..cc2f0e2c 100644 --- a/packages/app-shell/src/context/instrumentSession/InstrumentSessionView.test.tsx +++ b/packages/app-shell/src/context/instrumentSession/InstrumentSessionView.test.tsx @@ -5,6 +5,7 @@ import { useInstrumentSession, } from "./InstrumentSessionProvider"; import { describe, it, expect, vi } from "vitest"; +import { isValidElement } from "react"; function renderComponentWithProvider() { return render( @@ -14,20 +15,6 @@ function renderComponentWithProvider() { ); } -// vi.mock(import("./InstrumentSessionProvider"), async (importOriginal) => { -// const actual = -// await importOriginal(); -// return { -// ...actual, -// useInstrumentSession: () => ({ -// instrumentSession: "cm54321-1", -// setInstrumentSession: vi.fn(), -// instrumentSessionList: ["cm123-4", "cm567-8"], -// setInstrumentSessionList: vi.fn(), -// }), -// }; -// }); - vi.mock(import("./InstrumentSessionProvider"), async (importOriginal) => { const actual = await importOriginal(); @@ -38,9 +25,6 @@ vi.mock(import("./InstrumentSessionProvider"), async (importOriginal) => { }); describe("InstrumentSessionView", () => { - const mockSetInstrumentSession = vi.fn(); - const mockSetInstrumentSessionList = vi.fn(); - beforeEach(() => { localStorage.clear(); vi.clearAllMocks(); @@ -79,7 +63,6 @@ describe("InstrumentSessionView", () => { expect(screen.getByTestId("session-input-staticButton")).toHaveTextContent( "cm54321-1", ); - expect(screen.queryByTestId("session-input-menu")).not.toBeInTheDocument(); }); it("shows no session selected when there is no session selected", () => { @@ -97,59 +80,120 @@ describe("InstrumentSessionView", () => { ); }); - // it("shows sessions from the provided sessions list when clicked", async () => { - // vi.mocked(useInstrumentSession).mockReturnValue({ - // instrumentSession: "cm54321-1", - // setInstrumentSession: vi.fn(), - // instrumentSessionList: ["cm123-4", "cm567-8"], - // setInstrumentSessionList: vi.fn(), - // }); - - // renderComponentWithProvider(); - - // const user = userEvent.setup(); - // await user.click(screen.getByTestId("session-input-button")); - // expect(screen.getByTestId("session-input-menu")).toBeInTheDocument(); - // expect(screen.getByText("cm123-4")).toBeInTheDocument(); - // expect(screen.getByText("cm567-8")).toBeInTheDocument(); - // }); - - // it("closes menu when a menu item has been clicked", async () => { - // vi.mocked(useInstrumentSession).mockReturnValue({ - // instrumentSession: "cm54321-1", - // setInstrumentSession: vi.fn(), - // instrumentSessionList: ["cm123-4", "cm567-8"], - // setInstrumentSessionList: vi.fn(), - // }); - - // renderComponentWithProvider(); - - // const user = userEvent.setup(); - // await user.click(screen.getByTestId("session-input-button")); - // expect(screen.getByTestId("session-input-menu")).toBeInTheDocument(); - - // await user.click(screen.getByText("cm123-4")); - // expect(screen.queryByTestId("session-input-menu")).not.toBeInTheDocument(); - // }); - - // it("closes menu when button has been clicked again", async () => { - // vi.mocked(useInstrumentSession).mockReturnValue({ - // instrumentSession: "cm54321-1", - // setInstrumentSession: vi.fn(), - // instrumentSessionList: ["cm123-4", "cm567-8"], - // setInstrumentSessionList: vi.fn(), - // }); - - // renderComponentWithProvider(); - - // const user = userEvent.setup(); - // await user.click(screen.getByTestId("session-input-button")); - // expect(screen.getByTestId("session-input-menu")).toBeInTheDocument(); - - // const backdrop = screen.getByRole("presentation").firstChild; - // if (backdrop instanceof Element) { - // await user.click(backdrop); - // } - // expect(screen.queryByTestId("session-input-menu")).not.toBeInTheDocument(); - // }); + it("shows sessions from the provided sessions list when clicked", async () => { + vi.mocked(useInstrumentSession).mockReturnValue({ + instrumentSession: "cm54321-1", + setInstrumentSession: vi.fn(), + instrumentSessionList: ["cm123-4", "cm567-8"], + setInstrumentSessionList: vi.fn(), + }); + + renderComponentWithProvider(); + + const user = userEvent.setup(); + await user.click(screen.getByTestId("session-input-button")); + expect(screen.getByTestId("session-input-menu")).toBeInTheDocument(); + expect(screen.getByText("cm123-4")).toBeInTheDocument(); + expect(screen.getByText("cm567-8")).toBeInTheDocument(); + }); + + it("closes menu when a menu item has been clicked", async () => { + vi.mocked(useInstrumentSession).mockReturnValue({ + instrumentSession: "cm54321-1", + setInstrumentSession: vi.fn(), + instrumentSessionList: ["cm123-4", "cm567-8"], + setInstrumentSessionList: vi.fn(), + }); + + renderComponentWithProvider(); + + const user = userEvent.setup(); + await user.click(screen.getByTestId("session-input-button")); + expect(screen.getByTestId("session-input-menu")).toBeInTheDocument(); + + await user.click(screen.getByText("cm123-4")); + expect(screen.queryByTestId("session-input-menu")).not.toBeInTheDocument(); + }); + + it("closes menu when button has been clicked again", async () => { + vi.mocked(useInstrumentSession).mockReturnValue({ + instrumentSession: "cm54321-1", + setInstrumentSession: vi.fn(), + instrumentSessionList: ["cm123-4", "cm567-8"], + setInstrumentSessionList: vi.fn(), + }); + + renderComponentWithProvider(); + + const user = userEvent.setup(); + await user.click(screen.getByTestId("session-input-button")); + expect(screen.getByTestId("session-input-menu")).toBeInTheDocument(); + + const backdrop = screen.getByRole("presentation").firstChild; + if (backdrop instanceof Element) { + await user.click(backdrop); + } + expect(screen.queryByTestId("session-input-menu")).not.toBeInTheDocument(); + }); + + it("shows a tooltip when no sessions list or session is available", async () => { + vi.mocked(useInstrumentSession).mockReturnValue({ + instrumentSession: null, + setInstrumentSession: vi.fn(), + instrumentSessionList: null, + setInstrumentSessionList: vi.fn(), + }); + + renderComponentWithProvider(); + + const user = userEvent.setup(); + await user.hover(screen.getByTestId("session-input-staticButton")); + const tip = await screen.findByRole("tooltip"); + expect(tip).toBeInTheDocument(); + expect(tip).toHaveTextContent( + "Use 'Get Sessions' to fetch available sessions.", + ); + }); + + it("shows assign session when there is only one session available but no active session", () => { + vi.mocked(useInstrumentSession).mockReturnValue({ + instrumentSession: null, + setInstrumentSession: vi.fn(), + instrumentSessionList: ["cm12345-1"], + setInstrumentSessionList: vi.fn(), + }); + + renderComponentWithProvider(); + + const user = userEvent.setup(); + expect( + screen.getByTestId("session-input-staticButton"), + ).toBeInTheDocument(); + expect(screen.getByTestId("session-input-staticButton")).toHaveTextContent( + "Assign Session", + ); + }); + + it("assigns session when there is only one session available but no active session and button is clicked", async () => { + const mockSetInstrumentSession = vi.fn(); + vi.mocked(useInstrumentSession).mockReturnValue({ + instrumentSession: null, + setInstrumentSession: mockSetInstrumentSession, + instrumentSessionList: ["cm12345-1"], + setInstrumentSessionList: vi.fn(), + }); + + renderComponentWithProvider(); + + const user = userEvent.setup(); + expect( + screen.getByTestId("session-input-staticButton"), + ).toBeInTheDocument(); + expect(screen.getByTestId("session-input-staticButton")).toHaveTextContent( + "Assign Session", + ); + + await user.click(screen.getByTestId("session-input-staticButton")); + expect(mockSetInstrumentSession).toHaveBeenCalled(); + }); }); diff --git a/packages/app-shell/src/context/instrumentSession/InstrumentSessionView.tsx b/packages/app-shell/src/context/instrumentSession/InstrumentSessionView.tsx index 914134a5..86445a2c 100644 --- a/packages/app-shell/src/context/instrumentSession/InstrumentSessionView.tsx +++ b/packages/app-shell/src/context/instrumentSession/InstrumentSessionView.tsx @@ -35,10 +35,11 @@ export function InstrumentSessionView() { const handleClose = () => { setAnchorEl(null); }; - - if (instrumentSessionList && instrumentSessionList.length == 1) { - setInstrumentSession(instrumentSessionList[0]); - } + const handleStaticButtonClick = () => { + if (instrumentSessionList) { + setInstrumentSession(instrumentSessionList[0]); + } + }; if (!instrumentSession && !instrumentSessionList) { return ( @@ -55,8 +56,11 @@ export function InstrumentSessionView() {
); - } - if (menuDisabled) { + } else if ( + instrumentSessionList && + instrumentSessionList.length == 1 && + !instrumentSession + ) { return (
+
+ ); + } else if (menuDisabled && instrumentSession) { + return ( +
+
);