Skip to content
Open
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
7 changes: 5 additions & 2 deletions apps/i15-1/src/AppProviders.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { Api } from "@atlas/blueapi";
import { ThemeProvider } from "@diamondlightsource/sci-react-ui";
import type { Theme } from "@mui/material";
import type { ReactNode } from "react";
import { InstrumentSessionProvider } from "./context/instrumentSession/InstrumentSessionProvider";
import { InstrumentSessionProvider } from "@atlas/app-shell";
import { Provider as ReduxProvider } from "react-redux";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { BlueapiProvider } from "@atlas/blueapi-query";
Expand All @@ -22,7 +22,10 @@ export function AppProviders({ api, theme, children }: Props) {
const config = useLoadPvwsConfig();
return (
<ThemeProvider theme={theme}>
<InstrumentSessionProvider>
<InstrumentSessionProvider
defaultSessionId="cm44163-3"
sessionsList={["cm44163-3", "cm44163-4"]}
>
<ReduxProvider store={store(config)}>
<QueryClientProvider client={new QueryClient()}>
<UserAuthProvider>
Expand Down

This file was deleted.

This file was deleted.

12 changes: 0 additions & 12 deletions apps/i15-1/src/context/instrumentSession/useInstrumentSession.ts

This file was deleted.

10 changes: 0 additions & 10 deletions apps/i15-1/src/routes/Dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { Container, Typography, Button, Stack } from "@mui/material";
import { Link } from "react-router-dom";
import PrecisionManufacturingIcon from "@mui/icons-material/PrecisionManufacturing";
import QueueIcon from "@mui/icons-material/Queue";
import InstrumentSessionView from "../components/InstrumentSessionSelection/InstrumentSessionView.tsx";
import { useUserAuth } from "../context/userAuth/useUserAuth.ts";
import { User } from "@diamondlightsource/sci-react-ui";

Expand All @@ -28,15 +27,6 @@ function Dashboard() {
}
colour="white"
/>
<InstrumentSessionView
sessionsList={[
"cm12345-1",
"cm12345-2",
"cm12345-3",
"cm12345-4",
"cm12345-5",
]}
/>
<Stack direction={"row"} spacing={5}>
<Button
component={Link}
Expand Down
2 changes: 1 addition & 1 deletion apps/i15-1/src/routes/Robot.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useInstrumentSession } from "../context/instrumentSession/useInstrumentSession";
import { useInstrumentSession } from "@atlas/app-shell";
import { Box, Typography, Stack, useTheme } from "@mui/material";
import { useState } from "react";
import { NumberInput } from "../components/NumberInput";
Expand Down
8 changes: 8 additions & 0 deletions packages/app-shell/src/Layout.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@ import type { RouterProps } from "./Router";
import { Layout } from "./Layout";
import { createMemoryRouter, RouterProvider } from "react-router-dom";

// mock instrument session view which is out of scope of this test
export function InstrumentSessionView() {
return <button>cm12345-1</button>;
}
vi.mock("./context/instrumentSession/InstrumentSessionView", () => ({
InstrumentSessionView: InstrumentSessionView,
}));

describe("Layout", () => {
it("shows title, nav section titles, and main content", () => {
const props: RouterProps = {
Expand Down
8 changes: 8 additions & 0 deletions packages/app-shell/src/TopBar.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ vi.mock("@diamondlightsource/sci-react-ui", async () => {
};
});

// mock instrument session view which is out of scope of this test
export function InstrumentSessionView() {
return <button>cm12345-1</button>;
}
vi.mock("./context/instrumentSession/InstrumentSessionView", () => ({
InstrumentSessionView: InstrumentSessionView,
}));

describe("TopBar", () => {
const barProps = {
title: "Test application",
Expand Down
14 changes: 14 additions & 0 deletions packages/app-shell/src/TopBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import { ColourSchemeButton, Logo } from "@diamondlightsource/sci-react-ui";
import { Divider } from "@mui/material";
import type { Theme } from "@mui/material/styles";

import { InstrumentSessionView } from "./context/instrumentSession/InstrumentSessionView";

type Props = {
title: string;
open: boolean;
Expand Down Expand Up @@ -62,11 +64,23 @@ export function TopBar({ title, open, setOpen }: Props) {
sx={{
ml: 1.5,
mt: 1.25,
mr: 1.25,
}}
>
{title}
</Typography>

<Divider orientation="vertical" variant="middle" flexItem />

<Box
sx={{
ml: 1.5,
mt: 1.25,
}}
>
<InstrumentSessionView />
</Box>

<Box sx={{ ml: "auto" }}>
<ColourSchemeButton />
</Box>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { useState, useEffect, useContext, type ReactNode } from "react";
import { createContext } from "react";

export const ID_STORAGE_KEY = "instrument-session-id";

export type InstrumentSessionContextType = {
instrumentSession: string;
setInstrumentSession: (session: string) => void;
sessionsList: string[];
};

export const InstrumentSessionContext = createContext<
InstrumentSessionContextType | undefined
>(undefined);

export const InstrumentSessionProvider = ({
children,
defaultSessionId = "cm12345-1",
sessionsList = ["cm12345-2", "cm12345-2"],
}: {
children: ReactNode;
defaultSessionId?: string;
sessionsList?: string[];
}) => {
const [instrumentSession, setInstrumentSession] = useState<string>(() => {
return localStorage.getItem(ID_STORAGE_KEY) ?? defaultSessionId;
});

useEffect(() => {
localStorage.setItem(ID_STORAGE_KEY, instrumentSession);
}, [instrumentSession]);

return (
<InstrumentSessionContext.Provider
value={{
instrumentSession,
setInstrumentSession,
sessionsList,
}}
>
{children}
</InstrumentSessionContext.Provider>
);
};

export const useInstrumentSession = () => {
const context = useContext(InstrumentSessionContext);
if (!context) {
throw new Error(
"useInstrumentSession must be used within InstrumentSessionProvider",
);
}
return context;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { render, screen, userEvent, within } from "@atlas/vitest-conf";
import { InstrumentSessionView } from "./InstrumentSessionView";
import { InstrumentSessionProvider } from "./InstrumentSessionProvider";
import { describe, it, expect, vi } from "vitest";

function renderComponentWithProvider() {
return render(
<InstrumentSessionProvider>
<InstrumentSessionView />
</InstrumentSessionProvider>,
);
}

vi.mock(import("./InstrumentSessionProvider"), async (importOriginal) => {
const actual =
await importOriginal<typeof import("./InstrumentSessionProvider")>();
return {
...actual,
useInstrumentSession: () => ({
instrumentSession: "cm54321-1",
setInstrumentSession: vi.fn(),
sessionsList: ["cm123-4", "cm567-8"],
}),
};
});

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

it("shows only the current instrument session", () => {
renderComponentWithProvider();
expect(screen.getByTestId("session-input-button")).toBeInTheDocument();
expect(screen.queryByTestId("session-input-menu")).not.toBeInTheDocument();
});

it("shows sessions from the provided sessions list when clicked", async () => {
renderComponentWithProvider();

const user = userEvent.setup();
await user.click(screen.getByTestId("session-input-button"));
expect(screen.getByTestId("visit-field")).toBeInTheDocument();
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 () => {
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 () => {
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("lets you input a valid session", async () => {
const onSubmit = vi.fn();
renderComponentWithProvider();

const user = userEvent.setup();
await user.click(screen.getByTestId("session-input-button"));

const visitInput = screen.getByTestId("session-input-menu");
const visitTextBox = within(visitInput).getByRole("textbox");
await user.click(visitTextBox);
await user.clear(visitTextBox);
await user.type(visitTextBox, "cm1-1");
await user.keyboard("{Enter}");
});
});
Loading
Loading