Files
healthchat-web/packages/shared-components/src/timeline/DateSeparatorView/DateSeparatorContextMenuView.test.tsx
T
rbondesson 11030ae68d Refactor DateSeparator using MVVM and move to shared-components (#32482)
* Refactor DateSeparator using MVVM and move to shared-components

* Add a few more stories, tests and screenshots

* Use the shared component and viewmodel in element-web

* Renaming custom content property an updating snapshots

* Fix lint errors and update snapshot after merge

* Change lifecycle handling for DateSeparatoreViewModel in components where manual handling is preferrable over wrapper component.

* Move context menu from viewmodel to shared components - step 1

* Create a jump to date picker component in shared components

* Add tests for coverage and fix layout issues and roving indexes

* Make element-web use the new component

* Simplify context menu and adjusting tests

* The HTMLExport now render shared components and need a I18nContext.Provider

* Updating unit tests for context menu

* Changed to {translate: _t} to let scripts pick up translations

* Fix lint issue and updating screenshots after merge

* Update snaps for element web components

* Renaming MVVM view components with suffix View.

* Fixing problem with input date calendar icon and system dark theme

* Changed the rendering of the menu and added a separate button component

* Handle input control with useRef in onKeyDown

* Updating DateSeparator snapshots on unit tests

* Updating layout after compound Menu got a className property

* Move files to new subfolder after merge

* Updated snapshot after merge

* Updating lock file

* Updates to styling from PR review

* Updates to focus/blur functionality

* Fixed tabbing and export documentation to stories

* Updated snapshots

---------

Co-authored-by: Zack <zazi21@student.bth.se>
2026-03-02 12:18:51 +00:00

183 lines
7.0 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 React from "react";
import { fireEvent, render, screen } from "@test-utils";
import userEvent from "@testing-library/user-event";
import { describe, expect, it, vi } from "vitest";
import { type DateSeparatorViewModel, type DateSeparatorViewSnapshot } from "./DateSeparatorView";
import { DateSeparatorContextMenuView } from "./DateSeparatorContextMenuView";
class TestDateSeparatorViewModel implements DateSeparatorViewModel {
private listeners = new Set<() => void>();
public constructor(private snapshot: DateSeparatorViewSnapshot) {}
public getSnapshot = (): DateSeparatorViewSnapshot => this.snapshot;
public subscribe = (listener: () => void): (() => void) => {
this.listeners.add(listener);
return () => this.listeners.delete(listener);
};
public onLastWeekPicked = vi.fn<() => void>();
public onLastMonthPicked = vi.fn<() => void>();
public onBeginningPicked = vi.fn<() => void>();
public onDatePicked = vi.fn<(dateString: string) => void>();
}
function renderMenu({
open = true,
jumpToEnabled = true,
onOpenChange = vi.fn<(open: boolean) => void>(),
}: {
open?: boolean;
jumpToEnabled?: boolean;
onOpenChange?: (open: boolean) => void;
} = {}): { vm: TestDateSeparatorViewModel; onOpenChange: (open: boolean) => void } {
const vm = new TestDateSeparatorViewModel({
label: "Today",
jumpToEnabled,
jumpFromDate: "2025-01-15",
});
render(
<DateSeparatorContextMenuView
vm={vm}
open={open}
onOpenChange={onOpenChange}
trigger={
<button type="button" data-testid="jump-to-trigger">
Trigger
</button>
}
/>,
);
return { vm, onOpenChange };
}
describe("DateSeparatorContextMenuView", () => {
it("renders menu actions and date picker when open", () => {
renderMenu({ open: true, jumpToEnabled: true });
expect(screen.getByTestId("jump-to-date-last-week")).toBeInTheDocument();
expect(screen.getByTestId("jump-to-date-last-month")).toBeInTheDocument();
expect(screen.getByTestId("jump-to-date-beginning")).toBeInTheDocument();
expect(screen.getByTestId("jump-to-date-picker")).toBeInTheDocument();
});
it("calls onOpenChange for opening and closing transitions", async () => {
const user = userEvent.setup();
const onOpenChange = vi.fn<(open: boolean) => void>();
renderMenu({ open: false, jumpToEnabled: true, onOpenChange });
await user.click(screen.getByTestId("jump-to-trigger"));
expect(onOpenChange).toHaveBeenCalledWith(true);
renderMenu({ open: true, jumpToEnabled: true, onOpenChange });
await user.click(screen.getByTestId("jump-to-date-last-week"));
expect(onOpenChange).toHaveBeenCalledWith(false);
});
it("wires week/month/beginning menu actions to the correct callbacks", async () => {
const user = userEvent.setup();
const { vm } = renderMenu({ open: true, jumpToEnabled: true });
await user.click(screen.getByTestId("jump-to-date-last-week"));
await user.click(screen.getByTestId("jump-to-date-last-month"));
await user.click(screen.getByTestId("jump-to-date-beginning"));
expect(vm.onLastWeekPicked).toHaveBeenCalledTimes(1);
expect(vm.onLastMonthPicked).toHaveBeenCalledTimes(1);
expect(vm.onBeginningPicked).toHaveBeenCalledTimes(1);
});
it("submits date picker and closes the menu", async () => {
const user = userEvent.setup();
const onOpenChange = vi.fn<(open: boolean) => void>();
const { vm } = renderMenu({ open: true, jumpToEnabled: true, onOpenChange });
const dateInput = screen.getByLabelText("Pick a date to jump to");
fireEvent.input(dateInput, { target: { value: "2025-01-10" } });
await user.click(screen.getByRole("button", { name: "Go" }));
expect(vm.onDatePicked).toHaveBeenCalledWith("2025-01-10");
expect(onOpenChange).toHaveBeenCalledWith(false);
});
it("moves focus from date input to submit button on Tab", async () => {
const user = userEvent.setup();
renderMenu({ open: true, jumpToEnabled: true });
const dateInput = screen.getByLabelText("Pick a date to jump to");
const submitButton = screen.getByRole("button", { name: "Go" });
dateInput.focus();
await user.keyboard("{Tab}");
expect(submitButton).toHaveFocus();
});
it("moves focus from submit button back to date input on Shift+Tab", async () => {
const user = userEvent.setup();
renderMenu({ open: true, jumpToEnabled: true });
const dateInput = screen.getByLabelText("Pick a date to jump to");
const submitButton = screen.getByRole("button", { name: "Go" });
submitButton.focus();
await user.keyboard("{Shift>}{Tab}{/Shift}");
expect(dateInput).toHaveFocus();
});
it("closes the menu on Tab from the submit button", async () => {
const user = userEvent.setup();
const onOpenChange = vi.fn<(open: boolean) => void>();
const { vm } = renderMenu({ open: true, jumpToEnabled: true, onOpenChange });
const submitButton = screen.getByRole("button", { name: "Go" });
submitButton.focus();
await user.keyboard("{Tab}");
expect(onOpenChange).toHaveBeenCalledWith(false);
expect(vm.onDatePicked).not.toHaveBeenCalled();
});
it("submits date picker with Enter on the submit button", async () => {
const user = userEvent.setup();
const onOpenChange = vi.fn<(open: boolean) => void>();
const { vm } = renderMenu({ open: true, jumpToEnabled: true, onOpenChange });
const dateInput = screen.getByLabelText("Pick a date to jump to");
fireEvent.input(dateInput, { target: { value: "2025-01-11" } });
const submitButton = screen.getByRole("button", { name: "Go" });
submitButton.focus();
await user.keyboard("{Enter}");
expect(vm.onDatePicked).toHaveBeenCalledWith("2025-01-11");
expect(onOpenChange).toHaveBeenCalledWith(false);
});
it("submits date picker with Space on the submit button", async () => {
const user = userEvent.setup();
const onOpenChange = vi.fn<(open: boolean) => void>();
const { vm } = renderMenu({ open: true, jumpToEnabled: true, onOpenChange });
const dateInput = screen.getByLabelText("Pick a date to jump to");
fireEvent.input(dateInput, { target: { value: "2025-01-12" } });
const submitButton = screen.getByRole("button", { name: "Go" });
submitButton.focus();
await user.keyboard(" ");
expect(vm.onDatePicked).toHaveBeenCalledWith("2025-01-12");
expect(onOpenChange).toHaveBeenCalledWith(false);
});
});