The main chunk carried everything the mail view might open: the file preview and its Markdown renderer, the composer and its editor, the contact editor, the filter and share dialogs, and the calendar, contacts and files sidebars. Each is now loaded when first shown. The composer is also fetched when the browser is idle after startup, so the first Compose does not wait on the network. Import the notification helpers statically where they already were: the dynamic imports beside those static ones split nothing.
81 lines
3.1 KiB
TypeScript
81 lines
3.1 KiB
TypeScript
import { act } from "react";
|
|
import { createRoot, type Root } from "react-dom/client";
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { useCompose, type Draft } from "@/store/compose";
|
|
import { ComposerDock } from "../ComposerDock";
|
|
|
|
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
|
|
|
// The question is which composers the dock puts on screen, not what is inside
|
|
// them, so each composer is a bare marker carrying the state it was given.
|
|
vi.mock("../Composer", () => ({
|
|
Composer: ({ draft }: { draft: Draft }) => (
|
|
<div className={`composer ${draft.maximized ? "maximized" : ""} ${draft.minimized ? "minimized" : ""}`} data-key={draft.key} />
|
|
),
|
|
}));
|
|
|
|
/* jsdom has no matchMedia; each test says which side of the 768px breakpoint it stands at. */
|
|
function setWidth(px: number) {
|
|
window.matchMedia = ((q: string) => ({
|
|
matches: /max-width:\s*(\d+)px/.test(q) ? px <= Number(RegExp.$1) : false,
|
|
media: q,
|
|
addEventListener() {},
|
|
removeEventListener() {},
|
|
})) as unknown as typeof window.matchMedia;
|
|
}
|
|
|
|
const draft = (key: string, init: Partial<Draft> = {}) => ({ key, minimized: false, maximized: false, ...init }) as Draft;
|
|
|
|
describe("ComposerDock with a full-screen composer", () => {
|
|
let host: HTMLDivElement;
|
|
let root: Root;
|
|
|
|
beforeEach(() => {
|
|
host = document.createElement("div");
|
|
document.body.appendChild(host);
|
|
root = createRoot(host);
|
|
});
|
|
|
|
afterEach(() => {
|
|
act(() => root.unmount());
|
|
host.remove();
|
|
useCompose.setState({ drafts: [], activeKey: null });
|
|
});
|
|
|
|
// The composer is loaded on demand, so rendering waits for it to arrive.
|
|
const render = async (drafts: Draft[], activeKey: string) => {
|
|
useCompose.setState({ drafts, activeKey });
|
|
await act(async () => {
|
|
root.render(<ComposerDock />);
|
|
await import("../Composer");
|
|
});
|
|
};
|
|
const dock = () => host.querySelector(".composer-dock")!;
|
|
|
|
it("marks the dock so the other composers are hidden behind it", async () => {
|
|
setWidth(1300);
|
|
await render([draft("a"), draft("b", { maximized: true }), draft("c")], "b");
|
|
expect(dock().classList.contains("has-maximized")).toBe(true);
|
|
// Every composer stays mounted: the hiding is the stylesheet's, so nothing being typed elsewhere is lost.
|
|
expect(host.querySelectorAll(".composer").length).toBe(3);
|
|
});
|
|
|
|
it("leaves the dock alone while nobody is full screen", async () => {
|
|
setWidth(1300);
|
|
await render([draft("a"), draft("b")], "b");
|
|
expect(dock().classList.contains("has-maximized")).toBe(false);
|
|
});
|
|
|
|
it("does not count a full-screen composer that has since been minimized", async () => {
|
|
setWidth(1300);
|
|
await render([draft("a"), draft("b", { maximized: true, minimized: true })], "a");
|
|
expect(dock().classList.contains("has-maximized")).toBe(false);
|
|
});
|
|
|
|
it("is not a phone concern: there the active composer is already the only one open", async () => {
|
|
setWidth(400);
|
|
await render([draft("a"), draft("b", { maximized: true })], "b");
|
|
expect(dock().classList.contains("has-maximized")).toBe(false);
|
|
});
|
|
});
|