mirror of
https://github.com/scottlamb/moonfire-nvr.git
synced 2025-03-13 21:12:55 -04:00
In the upgrade I managed to dust off some tests that I'd been skipping for quite a while. It turns out one of them was pointing out a real problem: in the network error case, we didn't display the error to the user properly. It's really sad this reaches our code as a `TypeError`, but it is what it is.
133 lines
4.7 KiB
TypeScript
133 lines
4.7 KiB
TypeScript
// This file is part of Moonfire NVR, a security camera network video recorder.
|
|
// Copyright (C) 2021 The Moonfire NVR Authors; see AUTHORS and LICENSE.txt.
|
|
// SPDX-License-Identifier: GPL-v3.0-or-later WITH GPL-3.0-linking-exception
|
|
|
|
import {
|
|
screen,
|
|
waitFor,
|
|
waitForElementToBeRemoved,
|
|
} from "@testing-library/react";
|
|
import userEvent from "@testing-library/user-event";
|
|
import { delay, http, HttpResponse } from "msw";
|
|
import { setupServer } from "msw/node";
|
|
import Login from "./Login";
|
|
import { renderWithCtx } from "./testutil";
|
|
import {
|
|
beforeAll,
|
|
afterEach,
|
|
afterAll,
|
|
test,
|
|
vi,
|
|
expect,
|
|
beforeEach,
|
|
} from "vitest";
|
|
|
|
// Set up a fake API backend.
|
|
const server = setupServer(
|
|
http.post<any, Record<string, string>>("/api/login", async ({ request }) => {
|
|
const body = await request.json();
|
|
const { username, password } = body!;
|
|
console.log(
|
|
"/api/login post username=" + username + " password=" + password
|
|
);
|
|
if (username === "slamb" && password === "hunter2") {
|
|
return new HttpResponse(null, { status: 204 });
|
|
} else if (username === "delay") {
|
|
await delay("infinite");
|
|
return new HttpResponse(null);
|
|
} else if (username === "server-error") {
|
|
return HttpResponse.text("server error", { status: 503 });
|
|
} else if (username === "network-error") {
|
|
return HttpResponse.error();
|
|
} else {
|
|
return HttpResponse.text("bad credentials", { status: 401 });
|
|
}
|
|
})
|
|
);
|
|
beforeAll(() => server.listen({ onUnhandledRequest: "error" }));
|
|
beforeEach(() => {
|
|
// Using fake timers allows tests to jump forward to when a snackbar goes away, without incurring
|
|
// extra real delay. msw only appears to work when `shouldAdvanceTime` is set though.
|
|
vi.useFakeTimers({
|
|
shouldAdvanceTime: true,
|
|
});
|
|
});
|
|
afterEach(() => {
|
|
vi.runOnlyPendingTimers();
|
|
vi.useRealTimers();
|
|
server.resetHandlers();
|
|
});
|
|
afterAll(() => server.close());
|
|
|
|
test("success", async () => {
|
|
const user = userEvent.setup();
|
|
const handleClose = vi.fn().mockName("handleClose");
|
|
const onSuccess = vi.fn().mockName("handleOpen");
|
|
renderWithCtx(
|
|
<Login open={true} onSuccess={onSuccess} handleClose={handleClose} />
|
|
);
|
|
await user.type(screen.getByLabelText(/Username/), "slamb");
|
|
await user.type(screen.getByLabelText(/Password/), "hunter2{enter}");
|
|
await waitFor(() => expect(onSuccess).toHaveBeenCalledTimes(1));
|
|
});
|
|
|
|
test("close while pending", async () => {
|
|
const handleClose = vi.fn().mockName("handleClose");
|
|
const onSuccess = vi.fn().mockName("handleOpen");
|
|
const { rerender } = renderWithCtx(
|
|
<Login open={true} onSuccess={onSuccess} handleClose={handleClose} />
|
|
);
|
|
userEvent.type(screen.getByLabelText(/Username/), "delay");
|
|
userEvent.type(screen.getByLabelText(/Password/), "hunter2{enter}");
|
|
expect(screen.getByRole("button", { name: /Log in/ })).toBeInTheDocument();
|
|
rerender(
|
|
<Login open={false} onSuccess={onSuccess} handleClose={handleClose} />
|
|
);
|
|
await waitFor(() =>
|
|
expect(
|
|
screen.queryByRole("button", { name: /Log in/ })
|
|
).not.toBeInTheDocument()
|
|
);
|
|
});
|
|
|
|
test("bad credentials", async () => {
|
|
const handleClose = vi.fn().mockName("handleClose");
|
|
const onSuccess = vi.fn().mockName("handleOpen");
|
|
renderWithCtx(
|
|
<Login open={true} onSuccess={onSuccess} handleClose={handleClose} />
|
|
);
|
|
await userEvent.type(screen.getByLabelText(/Username/), "slamb");
|
|
await userEvent.type(screen.getByLabelText(/Password/), "wrong{enter}");
|
|
await screen.findByText(/bad credentials/);
|
|
expect(onSuccess).toHaveBeenCalledTimes(0);
|
|
});
|
|
|
|
test("server error", async () => {
|
|
const handleClose = vi.fn().mockName("handleClose");
|
|
const onSuccess = vi.fn().mockName("handleOpen");
|
|
renderWithCtx(
|
|
<Login open={true} onSuccess={onSuccess} handleClose={handleClose} />
|
|
);
|
|
await userEvent.type(screen.getByLabelText(/Username/), "server-error");
|
|
await userEvent.type(screen.getByLabelText(/Password/), "asdf{enter}");
|
|
await screen.findByText(/server error/);
|
|
vi.runOnlyPendingTimers();
|
|
await waitForElementToBeRemoved(() => screen.queryByText(/server error/));
|
|
expect(onSuccess).toHaveBeenCalledTimes(0);
|
|
});
|
|
|
|
test("network error", async () => {
|
|
const handleClose = vi.fn().mockName("handleClose");
|
|
const onSuccess = vi.fn().mockName("handleOpen");
|
|
renderWithCtx(
|
|
<Login open={true} onSuccess={onSuccess} handleClose={handleClose} />
|
|
);
|
|
await userEvent.type(screen.getByLabelText(/Username/), "network-error");
|
|
await userEvent.type(screen.getByLabelText(/Password/), "asdf{enter}");
|
|
|
|
// This is the text chosen by msw:
|
|
// https://github.com/mswjs/interceptors/blob/122a6533ce57d551dc3b59b3bb43a39026989b70/src/interceptors/fetch/index.ts#L187
|
|
await screen.findByText(/Failed to fetch/);
|
|
expect(onSuccess).toHaveBeenCalledTimes(0);
|
|
});
|