Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test a few React components #2614

Merged
merged 11 commits into from
Sep 19, 2024
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
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@
"vite": "^5.0.0",
"vite-plugin-html-template": "^1.1.0",
"vite-plugin-svgr": "^4.0.0",
"vitest": "^2.0.0"
"vitest": "^2.0.0",
"vitest-axe": "^1.0.0-pre.3"
}
}
4 changes: 2 additions & 2 deletions public/locales/en-GB/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,8 @@
"video_tile": {
"always_show": "Always show",
"change_fit_contain": "Fit to frame",
"exit_full_screen": "Exit full screen",
"full_screen": "Full screen",
"collapse": "Collapse",
"expand": "Expand",
"mute_for_me": "Mute for me",
"volume": "Volume"
}
Expand Down
30 changes: 30 additions & 0 deletions src/Header.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
Copyright 2024 New Vector Ltd.

SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/

import { expect, test } from "vitest";
import { render, screen } from "@testing-library/react";
import { axe } from "vitest-axe";
import { TooltipProvider } from "@vector-im/compound-web";

import { RoomHeaderInfo } from "./Header";

test("RoomHeaderInfo is accessible", async () => {
const { container } = render(
<TooltipProvider>
<RoomHeaderInfo
id="!a:example.org"
name="Mission Control"
avatarUrl=""
encrypted
participantCount={11}
/>
</TooltipProvider>,
);
expect(await axe(container)).toHaveNoViolations();
// Check that the room name acts as a heading
screen.getByRole("heading", { name: "Mission Control" });
});
7 changes: 6 additions & 1 deletion src/Modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@
styles.drawer,
{ [styles.tabbed]: tabbed },
)}
// Suppress the warning about there being no description; the modal
// has an accessible title
aria-describedby={undefined}

Check warning on line 94 in src/Modal.tsx

View check run for this annotation

Codecov / codecov/patch

src/Modal.tsx#L94

Added line #L94 was not covered by tests
robintown marked this conversation as resolved.
Show resolved Hide resolved
{...rest}
>
<div className={styles.content}>
Expand All @@ -111,7 +114,9 @@
<DialogOverlay
className={classNames(overlayStyles.bg, overlayStyles.animate)}
/>
<DialogContent asChild {...rest}>
{/* Suppress the warning about there being no description; the modal
has an accessible title */}
<DialogContent asChild aria-describedby={undefined} {...rest}>
<Glass
className={classNames(
className,
Expand Down
14 changes: 3 additions & 11 deletions src/Toast.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,12 @@ Please see LICENSE in the repository root for full details.
*/

import { describe, expect, test, vi } from "vitest";
import { render, configure } from "@testing-library/react";
import { render } from "@testing-library/react";
import userEvent from "@testing-library/user-event";

import { Toast } from "../src/Toast";
import { withFakeTimers } from "./utils/test";

configure({
defaultHidden: true,
});

// Test Explanation:
// This test the toast. We need to use { document: window.document } because the toast listens
// for user input on `window`.
describe("Toast", () => {
test("renders", () => {
const { queryByRole } = render(
Expand All @@ -36,7 +29,7 @@ describe("Toast", () => {
});

test("dismisses when Esc is pressed", async () => {
const user = userEvent.setup({ document: window.document });
const user = userEvent.setup();
const onDismiss = vi.fn();
render(
<Toast open={true} onDismiss={onDismiss}>
Expand All @@ -50,15 +43,14 @@ describe("Toast", () => {
test("dismisses when background is clicked", async () => {
const user = userEvent.setup();
const onDismiss = vi.fn();
const { getByRole, unmount } = render(
const { getByRole } = render(
<Toast open={true} onDismiss={onDismiss}>
Hello world!
</Toast>,
);
const background = getByRole("dialog").previousSibling! as Element;
await user.click(background);
expect(onDismiss).toHaveBeenCalled();
unmount();
});

test("dismisses itself after the specified timeout", () => {
Expand Down
29 changes: 29 additions & 0 deletions src/input/StarRating.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
Copyright 2024 New Vector Ltd.

SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/

import { test, expect, vi } from "vitest";
import { render, screen } from "@testing-library/react";
import { axe } from "vitest-axe";
import userEvent from "@testing-library/user-event";

import { StarRatingInput } from "./StarRatingInput";

test("StarRatingInput is accessible", async () => {
const user = userEvent.setup();
const onChange = vi.fn();
const { container } = render(
<StarRatingInput starCount={5} onChange={onChange} />,
);
expect(await axe(container)).toHaveNoViolations();
// Change the rating to 4 stars
await user.click(
(
await screen.findAllByRole("radio", { name: "star_rating_input_label" })
)[3],
);
expect(onChange).toBeCalledWith(4);
});
1 change: 0 additions & 1 deletion src/room/EncryptionLock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ export const EncryptionLock: FC<Props> = ({ encrypted }) => {
height={16}
className={styles.lock}
data-encrypted={encrypted}
aria-hidden
/>
</Tooltip>
);
Expand Down
35 changes: 35 additions & 0 deletions src/room/InviteModal.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
Copyright 2024 New Vector Ltd.

SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/

import { render, screen } from "@testing-library/react";
import { expect, test, vi } from "vitest";
import { Room } from "matrix-js-sdk/src/matrix";
import { axe } from "vitest-axe";
import { BrowserRouter } from "react-router-dom";
import userEvent from "@testing-library/user-event";

import { InviteModal } from "./InviteModal";

// Used by copy-to-clipboard
window.prompt = (): null => null;

test("InviteModal is accessible", async () => {
const user = userEvent.setup();
const room = {
roomId: "!a:example.org",
name: "Mission Control",
} as unknown as Room;
const onDismiss = vi.fn();
const { container } = render(
<InviteModal room={room} open={true} onDismiss={onDismiss} />,
{ wrapper: BrowserRouter },
);

expect(await axe(container)).toHaveNoViolations();
await user.click(screen.getByRole("button", { name: "action.copy_link" }));
expect(onDismiss).toBeCalled();
});
61 changes: 13 additions & 48 deletions src/state/MediaViewModel.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,52 +5,17 @@ SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/

import { RoomMember } from "matrix-js-sdk/src/matrix";
import { expect, test, vi } from "vitest";
import { LocalParticipant, RemoteParticipant } from "livekit-client";

import {
LocalUserMediaViewModel,
RemoteUserMediaViewModel,
} from "./MediaViewModel";
import { withTestScheduler } from "../utils/test";
withLocalMedia,
withRemoteMedia,
withTestScheduler,
} from "../utils/test";

function withLocal(continuation: (vm: LocalUserMediaViewModel) => void): void {
const member = {} as unknown as RoomMember;
const vm = new LocalUserMediaViewModel(
"a",
member,
{} as unknown as LocalParticipant,
true,
);
try {
continuation(vm);
} finally {
vm.destroy();
}
}

function withRemote(
participant: Partial<RemoteParticipant>,
continuation: (vm: RemoteUserMediaViewModel) => void,
): void {
const member = {} as unknown as RoomMember;
const vm = new RemoteUserMediaViewModel(
"a",
member,
{ setVolume() {}, ...participant } as RemoteParticipant,
true,
);
try {
continuation(vm);
} finally {
vm.destroy();
}
}

test("set a participant's volume", () => {
test("set a participant's volume", async () => {
const setVolumeSpy = vi.fn();
withRemote({ setVolume: setVolumeSpy }, (vm) =>
await withRemoteMedia({}, { setVolume: setVolumeSpy }, (vm) =>
withTestScheduler(({ expectObservable, schedule }) => {
schedule("-a|", {
a() {
Expand All @@ -63,9 +28,9 @@ test("set a participant's volume", () => {
);
});

test("mute and unmute a participant", () => {
test("mute and unmute a participant", async () => {
const setVolumeSpy = vi.fn();
withRemote({ setVolume: setVolumeSpy }, (vm) =>
await withRemoteMedia({}, { setVolume: setVolumeSpy }, (vm) =>
withTestScheduler(({ expectObservable, schedule }) => {
schedule("-abc|", {
a() {
Expand All @@ -90,8 +55,8 @@ test("mute and unmute a participant", () => {
);
});

test("toggle fit/contain for a participant's video", () => {
withRemote({}, (vm) =>
test("toggle fit/contain for a participant's video", async () => {
await withRemoteMedia({}, {}, (vm) =>
withTestScheduler(({ expectObservable, schedule }) => {
schedule("-ab|", {
a: () => vm.toggleFitContain(),
Expand All @@ -106,15 +71,15 @@ test("toggle fit/contain for a participant's video", () => {
);
});

test("local media remembers whether it should always be shown", () => {
withLocal((vm) =>
test("local media remembers whether it should always be shown", async () => {
await withLocalMedia({}, (vm) =>
withTestScheduler(({ expectObservable, schedule }) => {
schedule("-a|", { a: () => vm.setAlwaysShow(false) });
expectObservable(vm.alwaysShow).toBe("ab", { a: true, b: false });
}),
);
// Next local media should start out *not* always shown
withLocal((vm) =>
await withLocalMedia({}, (vm) =>
withTestScheduler(({ expectObservable, schedule }) => {
schedule("-a|", { a: () => vm.setAlwaysShow(true) });
expectObservable(vm.alwaysShow).toBe("ab", { a: false, b: true });
Expand Down
43 changes: 43 additions & 0 deletions src/tile/GridTile.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
Copyright 2024 New Vector Ltd.

SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/

import { RemoteTrackPublication } from "livekit-client";
import { test, expect } from "vitest";
import { render, screen } from "@testing-library/react";
import { axe } from "vitest-axe";

import { GridTile } from "./GridTile";
import { withRemoteMedia } from "../utils/test";

test("GridTile is accessible", async () => {
await withRemoteMedia(
{
rawDisplayName: "Alice",
getMxcAvatarUrl: () => "mxc://adfsg",
},
{
setVolume() {},
getTrackPublication: () =>
({}) as Partial<RemoteTrackPublication> as RemoteTrackPublication,
},
async (vm) => {
const { container } = render(
<GridTile
vm={vm}
onOpenProfile={() => {}}
targetWidth={300}
targetHeight={200}
showVideo
showSpeakingIndicators
/>,
);
expect(await axe(container)).toHaveNoViolations();
// Name should be visible
screen.getByText("Alice");
},
);
});
1 change: 0 additions & 1 deletion src/tile/MediaView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,6 @@ export const MediaView = forwardRef<HTMLDivElement, Props>(
width={20}
height={20}
className={styles.errorIcon}
aria-hidden
/>
</Tooltip>
)}
Expand Down
Loading