The date field would not open at all on some days: on a Friday "Friday" and "In a week" resolve to the same day, and the option list was keyed by the day it writes, so Svelte tore it down with each_key_duplicate. On a desktop this took the whole view with it - the same bug, not two. Duplicate offers are dropped (the more specific name wins) and the list is keyed by position, so no caller can tear it down that way again. Panels are placed against the visual viewport rather than the window, which a phone's keyboard shrinks without resizing anything: they scroll inside what is left, re-place as the keyboard animates in, and come back into view when the keyboard covers the very row they hang off - visible beats staying next to the anchor. The quick look centres the same way. Its actions are thumb-sized where the pointer is coarse, with the keycaps hidden - there is no keyboard to press them on - and it gains "no date" and a close button, since a phone has no Escape. Both menus gain a plain "Edit" beside "Edit in Tasks"; it was only ever reachable by pressing e.
98 lines
3.4 KiB
TypeScript
98 lines
3.4 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { onScreen, place, roomFor } from "../src/lib/place";
|
|
import type { Box } from "../src/lib/place";
|
|
|
|
const VIEW = { width: 1280, height: 800 };
|
|
const PANEL = { width: 320, height: 200 };
|
|
const row = (over: Partial<Box> = {}): Box => ({
|
|
top: 300,
|
|
left: 400,
|
|
width: 500,
|
|
height: 40,
|
|
...over,
|
|
});
|
|
|
|
describe("onScreen", () => {
|
|
it("skips the copy that is hidden and has no box", () => {
|
|
const hidden = row({ top: 0, left: 0, width: 0, height: 0 });
|
|
const shown = row();
|
|
expect(onScreen([hidden, shown])).toBe(shown);
|
|
});
|
|
|
|
it("takes the first one when several are on screen", () => {
|
|
const first = row();
|
|
expect(onScreen([first, row({ top: 500 })])).toBe(first);
|
|
});
|
|
|
|
it("says nothing when every copy is hidden", () => {
|
|
expect(onScreen([row({ width: 0, height: 0 })])).toBeNull();
|
|
expect(onScreen([])).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe("place", () => {
|
|
it("sits just under the anchor", () => {
|
|
expect(place(row(), PANEL, VIEW)).toEqual({ top: 346, left: 400 });
|
|
});
|
|
|
|
it("flips above when there is no room below", () => {
|
|
expect(place(row({ top: 700 }), PANEL, VIEW)).toEqual({ top: 494, left: 400 });
|
|
});
|
|
|
|
it("stays inside the window when there is room neither way", () => {
|
|
const tall = { width: 320, height: 780 };
|
|
const spot = place(row({ top: 400 }), tall, VIEW);
|
|
expect(spot.top).toBeGreaterThanOrEqual(10);
|
|
expect(spot.top + tall.height).toBeLessThanOrEqual(VIEW.height);
|
|
});
|
|
|
|
it("pulls back from the right edge", () => {
|
|
expect(place(row({ left: 1200 }), PANEL, VIEW).left).toBe(950);
|
|
});
|
|
|
|
it("never goes off the left edge", () => {
|
|
expect(place(row({ left: -40 }), PANEL, VIEW).left).toBe(10);
|
|
});
|
|
|
|
it("centres rather than hiding in the corner with nothing to anchor to", () => {
|
|
expect(place(null, PANEL, VIEW)).toEqual({ top: 300, left: 480 });
|
|
});
|
|
});
|
|
|
|
// A phone's keyboard shrinks the visual viewport and leaves the window alone.
|
|
describe("place against a keyboard", () => {
|
|
const SHRUNK = { width: 390, height: 400, top: 0, left: 0 };
|
|
|
|
it("keeps the panel above the keyboard", () => {
|
|
const spot = place(row({ top: 380, left: 10, width: 370 }), PANEL, SHRUNK);
|
|
expect(spot.top + PANEL.height).toBeLessThanOrEqual(SHRUNK.height);
|
|
});
|
|
|
|
it("measures from where the visible area starts", () => {
|
|
const scrolled = { width: 390, height: 400, top: 120, left: 0 };
|
|
const spot = place(null, PANEL, scrolled);
|
|
expect(spot.top).toBeGreaterThanOrEqual(scrolled.top);
|
|
expect(spot.top + PANEL.height).toBeLessThanOrEqual(
|
|
scrolled.top + scrolled.height
|
|
);
|
|
});
|
|
|
|
it("comes back into view when the anchor itself is under the keyboard", () => {
|
|
// The row the panel hangs off is at 650, the keyboard leaves 380: neither
|
|
// above nor below the anchor is reachable, so the visible area wins.
|
|
const spot = place(row({ top: 650, height: 40 }), PANEL, SHRUNK);
|
|
expect(spot.top).toBeGreaterThanOrEqual(10);
|
|
expect(spot.top + PANEL.height).toBeLessThanOrEqual(SHRUNK.height);
|
|
});
|
|
});
|
|
|
|
describe("roomFor", () => {
|
|
it("leaves the panel less height than the visible area", () => {
|
|
expect(roomFor({ width: 390, height: 400 })).toBeLessThan(400);
|
|
});
|
|
|
|
it("never collapses to nothing when the keyboard takes almost everything", () => {
|
|
expect(roomFor({ width: 390, height: 60 })).toBeGreaterThan(100);
|
|
});
|
|
});
|