83d732d60e
* Refactor className? to component property in EncryptionEventView * Refactor extraClassNames to default react className as component property for DecryptionFailureBodyView * Refactor className to component property for MessageTimestampView * Refactor className and children to component properties for ReactionsRowButton * Refactor className to component property for DisambiguatedProfile * Refactor className to a component property in DateSeparatorView * Fix for lint errors and EncryptionEventView unsupported icon color * EncryptionEventView fix for icon color css specificity/order
239 lines
8.2 KiB
TypeScript
239 lines
8.2 KiB
TypeScript
/*
|
|
* Copyright 2026 Element Creations Ltd.
|
|
*
|
|
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
|
* Please see LICENSE files in the repository root for full details.
|
|
*/
|
|
|
|
import { render, screen, fireEvent } from "@test-utils";
|
|
import { composeStories } from "@storybook/react-vite";
|
|
import React from "react";
|
|
import userEvent from "@testing-library/user-event";
|
|
import { describe, it, vi, afterEach, expect } from "vitest";
|
|
|
|
import * as stories from "./MessageTimestampView.stories.tsx";
|
|
import {
|
|
MessageTimestampView,
|
|
type MessageTimestampViewActions,
|
|
type MessageTimestampViewSnapshot,
|
|
} from "./MessageTimestampView";
|
|
import { MockViewModel } from "../../viewmodel/MockViewModel.ts";
|
|
import { I18nContext } from "../../utils/i18nContext.ts";
|
|
import { I18nApi } from "../../index.ts";
|
|
|
|
const { Default, HasHref } = composeStories(stories);
|
|
|
|
const renderWithI18n = (ui: React.ReactElement): ReturnType<typeof render> =>
|
|
render(ui, {
|
|
wrapper: ({ children }) => <I18nContext.Provider value={new I18nApi()}>{children}</I18nContext.Provider>,
|
|
});
|
|
|
|
describe("MessageTimestampView", () => {
|
|
afterEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
it("renders the message timestamp in default state", async () => {
|
|
const { container } = render(<Default />);
|
|
expect(container).toMatchSnapshot();
|
|
});
|
|
|
|
it("applies custom className to the timestamp element", async () => {
|
|
const vm = new MockViewModel<MessageTimestampViewSnapshot>({
|
|
ts: "04:58",
|
|
tsSentAt: "Thu, 17 Nov 2022, 4:58:32 pm",
|
|
});
|
|
|
|
renderWithI18n(<MessageTimestampView vm={vm} className="extra_class_1 extra_class_2" />);
|
|
|
|
const target = screen.getByText("04:58");
|
|
expect(target).toHaveClass("extra_class_1", "extra_class_2");
|
|
});
|
|
|
|
it("renders the message timestamp with href", async () => {
|
|
const { container } = render(<HasHref />);
|
|
expect(container).toMatchSnapshot();
|
|
});
|
|
|
|
const onClick = vi.fn((event: React.MouseEvent<HTMLElement>) => event.preventDefault());
|
|
const onContextMenu = vi.fn((event: React.MouseEvent<HTMLElement>) => event.preventDefault());
|
|
|
|
class MessageTimestampViewModel
|
|
extends MockViewModel<MessageTimestampViewSnapshot>
|
|
implements MessageTimestampViewActions
|
|
{
|
|
public onClick = onClick;
|
|
public onContextMenu = onContextMenu;
|
|
}
|
|
|
|
it("should attach vm methods with href", async () => {
|
|
const vm = new MessageTimestampViewModel({
|
|
ts: "04:58",
|
|
tsSentAt: "Thu, 17 Nov 2022, 4:58:32 pm",
|
|
href: "~",
|
|
});
|
|
|
|
renderWithI18n(<MessageTimestampView vm={vm} />);
|
|
|
|
const target = screen.getByRole("link");
|
|
|
|
fireEvent.click(target);
|
|
expect(onClick).toHaveBeenCalled();
|
|
|
|
fireEvent.contextMenu(target);
|
|
expect(onContextMenu).toHaveBeenCalled();
|
|
});
|
|
|
|
it("should attach vm methods without href", async () => {
|
|
const user = userEvent.setup();
|
|
const vm = new MessageTimestampViewModel({
|
|
ts: "04:58",
|
|
tsSentAt: "Thu, 17 Nov 2022, 4:58:32 pm",
|
|
});
|
|
|
|
renderWithI18n(<MessageTimestampView vm={vm} />);
|
|
|
|
const target = screen.getByRole("link", { hidden: true });
|
|
|
|
await user.click(target);
|
|
expect(onClick).toHaveBeenCalled();
|
|
|
|
await user.pointer({ target, keys: "[MouseRight]" });
|
|
expect(onContextMenu).toHaveBeenCalled();
|
|
});
|
|
|
|
it("should show full date & time on hover", async () => {
|
|
const user = userEvent.setup();
|
|
const vm = new MessageTimestampViewModel({
|
|
ts: "08:09",
|
|
tsSentAt: "Fri, Dec 17, 2021, 08:09:00",
|
|
});
|
|
|
|
renderWithI18n(<MessageTimestampView vm={vm} />);
|
|
|
|
await user.hover(screen.getByRole("link"));
|
|
expect((await screen.findByRole("tooltip")).textContent).toMatchInlineSnapshot(`"Fri, Dec 17, 2021, 08:09:00"`);
|
|
});
|
|
|
|
it("should show sent & received time on hover if passed", async () => {
|
|
const user = userEvent.setup();
|
|
const vm = new MessageTimestampViewModel({
|
|
ts: "08:09",
|
|
tsSentAt: "Fri, Dec 17, 2021, 08:09:00",
|
|
tsReceivedAt: "Received at: Sat, Dec 18, 2021, 08:09:00",
|
|
});
|
|
|
|
renderWithI18n(<MessageTimestampView vm={vm} />);
|
|
|
|
await user.hover(screen.getByRole("link"));
|
|
expect((await screen.findByRole("tooltip")).textContent).toMatchInlineSnapshot(
|
|
`"Sent at: Fri, Dec 17, 2021, 08:09:00Received at: Received at: Sat, Dec 18, 2021, 08:09:00"`,
|
|
);
|
|
});
|
|
|
|
it("handles keyboard activation on span when click handler is set", async () => {
|
|
const vm = new MessageTimestampViewModel({
|
|
ts: "12:34",
|
|
tsSentAt: "Mon, Jan 1, 2024, 12:34:00",
|
|
});
|
|
|
|
renderWithI18n(<MessageTimestampView vm={vm} />);
|
|
|
|
const target = screen.getByRole("link");
|
|
fireEvent.keyDown(target, { key: "Enter" });
|
|
fireEvent.keyDown(target, { key: " " });
|
|
|
|
expect(onClick).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
it("ignores other keys when click handler is set", async () => {
|
|
const vm = new MessageTimestampViewModel({
|
|
ts: "13:14",
|
|
tsSentAt: "Tue, Jun 6, 2023, 13:14:00",
|
|
});
|
|
|
|
renderWithI18n(<MessageTimestampView vm={vm} />);
|
|
|
|
const target = screen.getByRole("link");
|
|
fireEvent.keyDown(target, { key: "Escape" });
|
|
|
|
expect(onClick).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("ignores keyboard activation when no click handler is provided", async () => {
|
|
const vm = new MessageTimestampViewModelNoActions({
|
|
ts: "15:16",
|
|
tsSentAt: "Wed, Jul 7, 2021, 15:16:00",
|
|
});
|
|
|
|
renderWithI18n(<MessageTimestampView vm={vm} />);
|
|
|
|
const target = screen.getByText("15:16");
|
|
fireEvent.keyDown(target, { key: "Enter" });
|
|
|
|
expect(onClick).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("does not wrap tooltip labels when received timestamp is empty", async () => {
|
|
const user = userEvent.setup();
|
|
const vm = new MessageTimestampViewModel({
|
|
ts: "09:10",
|
|
tsSentAt: "Tue, Feb 2, 2021, 09:10:00",
|
|
tsReceivedAt: "",
|
|
});
|
|
|
|
renderWithI18n(<MessageTimestampView vm={vm} />);
|
|
|
|
await user.hover(screen.getByRole("link"));
|
|
expect((await screen.findByRole("tooltip")).textContent).toMatchInlineSnapshot(`"Tue, Feb 2, 2021, 09:10:00"`);
|
|
});
|
|
|
|
class MessageTimestampViewModelNoActions extends MockViewModel<MessageTimestampViewSnapshot> {}
|
|
|
|
it("renders without tooltip when inhibited and no click handler is provided", async () => {
|
|
const vm = new MessageTimestampViewModelNoActions({
|
|
ts: "07:08",
|
|
tsSentAt: "Wed, Mar 3, 2021, 07:08:00",
|
|
inhibitTooltip: true,
|
|
});
|
|
|
|
renderWithI18n(<MessageTimestampView vm={vm} />);
|
|
|
|
const target = screen.getByText("07:08");
|
|
expect(target).not.toHaveAttribute("role");
|
|
expect(target).not.toHaveAttribute("tabindex");
|
|
expect(screen.queryByRole("tooltip")).toBeNull();
|
|
});
|
|
|
|
it("keeps link semantics when inhibited but click handler exists", async () => {
|
|
const vm = new MessageTimestampViewModel({
|
|
ts: "11:12",
|
|
tsSentAt: "Thu, Apr 4, 2024, 11:12:00",
|
|
inhibitTooltip: true,
|
|
});
|
|
|
|
renderWithI18n(<MessageTimestampView vm={vm} />);
|
|
|
|
const target = screen.getByRole("link");
|
|
expect(target).toHaveAttribute("tabindex", "0");
|
|
expect(screen.queryByRole("tooltip")).toBeNull();
|
|
});
|
|
|
|
it("exposes focusable span when tooltip is enabled without click handler", async () => {
|
|
const user = userEvent.setup();
|
|
const vm = new MessageTimestampViewModelNoActions({
|
|
ts: "03:04",
|
|
tsSentAt: "Fri, May 5, 2023, 03:04:00",
|
|
});
|
|
|
|
renderWithI18n(<MessageTimestampView vm={vm} />);
|
|
|
|
const target = screen.getByText("03:04");
|
|
expect(target).toHaveAttribute("tabindex", "0");
|
|
expect(target).not.toHaveAttribute("role");
|
|
|
|
await user.hover(target);
|
|
expect((await screen.findByRole("tooltip")).textContent).toMatchInlineSnapshot(`"Fri, May 5, 2023, 03:04:00"`);
|
|
});
|
|
});
|