mirror of
https://github.com/priyanshujain/margin-calendar.git
synced 2026-10-02 11:07:04 +00:00
Keep the hour it is now on the axis
The visible range is drawn from the events in view, and in the evening the events are behind you: the bounds end at six, everything after that is in the trailing strip, and the now line has nowhere to land. The app stops saying where in the day you are, which is most of what it is open for. Same at seven in the morning, from the other end. So when today is one of the columns, the axis takes that one hour in, and everything the widening reached over folds behind it. At half eleven at night the fixture week reads 7am to 8pm as usual, then an "8pm to 11pm" strip, then one 11pm row with the line in it. A row and a strip, not four rows of empty evening. Only the hour itself is opened, so how far the clock has drifted past the last event costs nothing. The pin sits on top of the hysteresis rather than inside it. previous.current still holds what the events and the user asked for, so an hour pinned open tonight cannot accumulate into the bounds the grid remembers tomorrow. One consequence, and it is the only place the axis is allowed to move under you: page to a week that does not contain today and the row goes again, along with the strip it was sitting under. The tick that drives it is an hour long, not a minute, because that is how often the answer changes. It lives in a new useClock alongside the minute tick the now line already had, which moves there out of GridNowLine. Both schedule off the clock rather than off an interval, so a machine that was asleep catches up on the next tick instead of drifting further out every hour. The browser suite has to say what time it is now. The shape of the axis depends on the hour a run happens at, so anything measuring a row height or counting strips pins the clock to the middle of the working day, the same way it already pins the theme and the week start. The three tests that are about the clock ask for half eleven at night, which the fixture leaves empty.
This commit is contained in:
1 parent
3323496a34
commit
67879fd6c3
8 files changed
+223
-32
No files matched your search
@@ -30,6 +30,14 @@ The range expands immediately when it needs to, but only contracts when it would
|
|||||||
hours or more. Without that hysteresis the axis flickers as you page through weeks, and a
|
hours or more. Without that hysteresis the axis flickers as you page through weeks, and a
|
||||||
flickering axis destroys the positional memory that makes a keyboard-driven calendar fast.
|
flickering axis destroys the positional memory that makes a keyboard-driven calendar fast.
|
||||||
|
|
||||||
|
The hour you are in is the one exception to the range being the events' business. At half
|
||||||
|
eleven at night the events stopped hours ago, so the now line has nowhere to land and the app
|
||||||
|
stops telling you where in the day you are, which is most of what a calendar is open for. So
|
||||||
|
when today is on screen the axis takes that hour in, and folds away everything it reached over
|
||||||
|
to get there: the axis grows by a row and a strip, not by an evening. Page to a week that does
|
||||||
|
not contain today and it goes again, which is the one place the axis is allowed to move under
|
||||||
|
you.
|
||||||
|
|
||||||
Interior gaps stay at full scale. A three-hour hole on a Wednesday afternoon is the most
|
Interior gaps stay at full scale. A three-hour hole on a Wednesday afternoon is the most
|
||||||
useful thing on the screen, because it is where work goes, and folding it automatically would
|
useful thing on the screen, because it is where work goes, and folding it automatically would
|
||||||
make a packed day look identical to an open one. But you can fold one deliberately with `z`,
|
make a packed day look identical to an open one. But you can fold one deliberately with `z`,
|
||||||
|
|||||||
@@ -1,30 +1,12 @@
|
|||||||
// The now line, and the wash over the part of today that has already gone. A minute is as fine as
|
// The now line, and the wash over the part of today that has already gone. A minute is as fine as
|
||||||
// either needs to be, and the tick lives here so the rest of the grid never rerenders for it.
|
// either needs to be, and this is the only thing on the tick, so the rest of the grid never
|
||||||
|
// rerenders for it.
|
||||||
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { isFolded, timeToY, type FitLayout } from "../grid/fit";
|
import { isFolded, timeToY, type FitLayout } from "../grid/fit";
|
||||||
import { isSameDay, minutesFromMidnight } from "../time";
|
import { isSameDay, minutesFromMidnight } from "../time";
|
||||||
|
import { useMinuteTick } from "../useClock";
|
||||||
import { vars } from "./GridModel";
|
import { vars } from "./GridModel";
|
||||||
|
|
||||||
const MINUTE = 60_000;
|
|
||||||
|
|
||||||
/** Rounds up to the next wall-clock minute, so the line moves when the clock does. */
|
|
||||||
function useMinuteTick(): number {
|
|
||||||
const [now, setNow] = useState(() => Date.now());
|
|
||||||
useEffect(() => {
|
|
||||||
let timer = 0;
|
|
||||||
const schedule = () => {
|
|
||||||
timer = window.setTimeout(() => {
|
|
||||||
setNow(Date.now());
|
|
||||||
schedule();
|
|
||||||
}, MINUTE - (Date.now() % MINUTE));
|
|
||||||
};
|
|
||||||
schedule();
|
|
||||||
return () => window.clearTimeout(timer);
|
|
||||||
}, []);
|
|
||||||
return now;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface GridNowLineProps {
|
interface GridNowLineProps {
|
||||||
layout: FitLayout;
|
layout: FitLayout;
|
||||||
dayStart: number;
|
dayStart: number;
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ import {
|
|||||||
toOffsetIso,
|
toOffsetIso,
|
||||||
today,
|
today,
|
||||||
} from "../time";
|
} from "../time";
|
||||||
|
import { useHourStart } from "../useClock";
|
||||||
import { PHONE_QUERY, useMediaQuery } from "../useMedia";
|
import { PHONE_QUERY, useMediaQuery } from "../useMedia";
|
||||||
import "../styles/grid.css";
|
import "../styles/grid.css";
|
||||||
import "../styles/folds.css";
|
import "../styles/folds.css";
|
||||||
@@ -237,6 +238,22 @@ export function GridView({ defaultCalendarId }: GridViewProps) {
|
|||||||
|
|
||||||
const timed = useMemo(() => byDay.flat(), [byDay]);
|
const timed = useMemo(() => byDay.flat(), [byDay]);
|
||||||
|
|
||||||
|
// The hour the clock is in, when today is one of the columns.
|
||||||
|
//
|
||||||
|
// The axis is drawn from the events, and at nine in the evening the events are usually behind
|
||||||
|
// you: the bounds end at six, the rest of the day is in the trailing strip, and the now line has
|
||||||
|
// nowhere to land. So the hour you are in is taken into the bounds when it falls outside them.
|
||||||
|
// One hour, and only the hour, so the axis gains a row rather than an evening.
|
||||||
|
//
|
||||||
|
// Keyed on the hour rather than the minute, because that is how often the answer changes. The
|
||||||
|
// line itself moves every minute, on its own tick, in `GridNowLine`.
|
||||||
|
const hourStart = useHourStart();
|
||||||
|
const nowBand = useMemo((): Bounds | null => {
|
||||||
|
if (!days.includes(startOfDay(hourStart))) return null;
|
||||||
|
const hour = new Date(hourStart).getHours();
|
||||||
|
return { start: hour, end: hour + 1 };
|
||||||
|
}, [days, hourStart]);
|
||||||
|
|
||||||
// Hysteresis needs the bounds it adopted last pass, so they live in a ref rather than in state:
|
// Hysteresis needs the bounds it adopted last pass, so they live in a ref rather than in state:
|
||||||
// paging a week must not reflow the axis, and a render loop through state would fight that.
|
// paging a week must not reflow the axis, and a render loop through state would fight that.
|
||||||
const layout = useMemo(() => {
|
const layout = useMemo(() => {
|
||||||
@@ -244,9 +261,23 @@ export function GridView({ defaultCalendarId }: GridViewProps) {
|
|||||||
const bounds = floor
|
const bounds = floor
|
||||||
? { start: Math.min(auto.start, floor.start), end: Math.max(auto.end, floor.end) }
|
? { start: Math.min(auto.start, floor.start), end: Math.max(auto.end, floor.end) }
|
||||||
: auto;
|
: auto;
|
||||||
|
// What the events and the user asked for is what the hysteresis remembers. The clock's hour is
|
||||||
|
// a pin laid over the top of it every hour, so it never accumulates into the axis it pins.
|
||||||
previous.current = bounds;
|
previous.current = bounds;
|
||||||
return computeFit({ bounds, folds, viewportHeight: viewportH });
|
if (!nowBand) return computeFit({ bounds, folds, viewportHeight: viewportH });
|
||||||
}, [timed, folds, floor, viewportH]);
|
|
||||||
|
// Everything the widening reached over folds behind it. At half eleven at night the axis is
|
||||||
|
// one row longer and one strip taller, rather than four rows of empty evening shorter.
|
||||||
|
const reach: Fold[] = [
|
||||||
|
{ start: nowBand.end, end: bounds.start },
|
||||||
|
{ start: bounds.end, end: nowBand.start },
|
||||||
|
];
|
||||||
|
const axis = {
|
||||||
|
start: Math.min(bounds.start, nowBand.start),
|
||||||
|
end: Math.max(bounds.end, nowBand.end),
|
||||||
|
};
|
||||||
|
return computeFit({ bounds: axis, folds: [...folds, ...reach], viewportHeight: viewportH });
|
||||||
|
}, [timed, folds, floor, viewportH, nowBand]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const signature = `${layout.bounds.start} ${layout.bounds.end}`;
|
const signature = `${layout.bounds.start} ${layout.bounds.end}`;
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
// The wall clock, at the two granularities anything on the grid cares about: the minute the now
|
||||||
|
// line moves on, and the hour the fit holds open around it.
|
||||||
|
//
|
||||||
|
// Both schedule off the clock rather than off an interval, so a tab that was asleep and a machine
|
||||||
|
// that woke up catch up on the next tick instead of drifting further out every hour.
|
||||||
|
//
|
||||||
|
// They are separate hooks because they are separate rerenders. The minute belongs to the one
|
||||||
|
// component that draws the line; putting the fit on that tick would solve the whole axis sixty
|
||||||
|
// times an hour to answer a question that changes once.
|
||||||
|
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
const MINUTE = 60_000;
|
||||||
|
|
||||||
|
/** The top of the local hour `ms` falls in. Not `ms - ms % HOUR`: not every zone is on the hour. */
|
||||||
|
function hourStart(ms: number): number {
|
||||||
|
const d = new Date(ms);
|
||||||
|
d.setMinutes(0, 0, 0);
|
||||||
|
return d.getTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The next one. `setMinutes(60)` rolls the date over for us, daylight saving and all. */
|
||||||
|
function nextHourStart(ms: number): number {
|
||||||
|
const d = new Date(ms);
|
||||||
|
d.setMinutes(60, 0, 0);
|
||||||
|
return d.getTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
function useTick<T>(read: (now: number) => T, next: (now: number) => number): T {
|
||||||
|
const [value, setValue] = useState(() => read(Date.now()));
|
||||||
|
useEffect(() => {
|
||||||
|
let timer = 0;
|
||||||
|
const schedule = () => {
|
||||||
|
const now = Date.now();
|
||||||
|
timer = window.setTimeout(
|
||||||
|
() => {
|
||||||
|
setValue(read(Date.now()));
|
||||||
|
schedule();
|
||||||
|
},
|
||||||
|
Math.max(0, next(now) - now),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
schedule();
|
||||||
|
return () => window.clearTimeout(timer);
|
||||||
|
// Both callbacks are module-level functions in every caller, so there is nothing to rebind.
|
||||||
|
}, [read, next]);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const now = (ms: number) => ms;
|
||||||
|
const nextMinute = (ms: number) => ms + MINUTE - (ms % MINUTE);
|
||||||
|
|
||||||
|
/** Now, refreshed on every wall-clock minute. */
|
||||||
|
export function useMinuteTick(): number {
|
||||||
|
return useTick(now, nextMinute);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The top of the hour it is now, refreshed when the clock turns over into the next one.
|
||||||
|
*
|
||||||
|
* Returning the hour rather than the instant is what makes this cheap to depend on: the value is
|
||||||
|
* identical for a whole hour, so a memo keyed on it recomputes once, when the answer changed.
|
||||||
|
*/
|
||||||
|
export function useHourStart(): number {
|
||||||
|
return useTick(hourStart, nextHourStart);
|
||||||
|
}
|
||||||
@@ -23,8 +23,35 @@ export interface OpenOptions {
|
|||||||
view?: "day" | "week" | "agenda";
|
view?: "day" | "week" | "agenda";
|
||||||
/** Extra localStorage entries, written before the app's first script runs. */
|
/** Extra localStorage entries, written before the app's first script runs. */
|
||||||
storage?: Record<string, string>;
|
storage?: Record<string, string>;
|
||||||
|
/** Pins the browser's clock. See `clockAt`. */
|
||||||
|
now?: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A fixed instant on the day the suite is being run, at a given hour of the pinned zone.
|
||||||
|
*
|
||||||
|
* The axis takes in the hour it is now, so the shape of the grid depends on when you run the
|
||||||
|
* suite: at eleven at night it carries a row and a strip that it does not carry at eleven in the
|
||||||
|
* morning. A test that measures the axis has to say which of those it means, the same way it says
|
||||||
|
* which theme and which week start it means. The one test that is *about* the clock asks for an
|
||||||
|
* hour the fixture leaves empty; every other one asks for the middle of the working day.
|
||||||
|
*
|
||||||
|
* Asia/Kolkata is UTC+5:30 the whole year round, so the offset can be written out.
|
||||||
|
*/
|
||||||
|
export function clockAt(hour: number, minute = 0): Date {
|
||||||
|
const day = new Intl.DateTimeFormat("en-CA", {
|
||||||
|
timeZone: "Asia/Kolkata",
|
||||||
|
year: "numeric",
|
||||||
|
month: "2-digit",
|
||||||
|
day: "2-digit",
|
||||||
|
}).format(new Date());
|
||||||
|
const pad = (n: number) => String(n).padStart(2, "0");
|
||||||
|
return new Date(`${day}T${pad(hour)}:${pad(minute)}:00+05:30`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The middle of the working day: the fixture is busy, and the clock pins nothing extra open. */
|
||||||
|
export const MIDDAY = () => clockAt(11, 30);
|
||||||
|
|
||||||
const SEEDED = "__test-seeded";
|
const SEEDED = "__test-seeded";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -56,6 +83,7 @@ export async function openApp(page: Page, options: OpenOptions = {}): Promise<vo
|
|||||||
{ values: seed, flag: SEEDED },
|
{ values: seed, flag: SEEDED },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (options.now) await page.clock.setFixedTime(options.now);
|
||||||
await page.goto("/");
|
await page.goto("/");
|
||||||
if ((options.view ?? "week") === "agenda") await agendaReady(page);
|
if ((options.view ?? "week") === "agenda") await agendaReady(page);
|
||||||
else await gridReady(page);
|
else await gridReady(page);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// ships with, and it is the one nobody looks at.
|
// ships with, and it is the one nobody looks at.
|
||||||
|
|
||||||
import { expect, test } from "@playwright/test";
|
import { expect, test } from "@playwright/test";
|
||||||
import { gridFit, openApp, settle } from "./app";
|
import { MIDDAY, gridFit, openApp, settle } from "./app";
|
||||||
|
|
||||||
const MIN = { width: 880, height: 560 };
|
const MIN = { width: 880, height: 560 };
|
||||||
|
|
||||||
@@ -52,7 +52,9 @@ test("the grid still fits, and still fills the window", async ({ page }) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("no block is painted over by the one after it", async ({ page }) => {
|
test("no block is painted over by the one after it", async ({ page }) => {
|
||||||
await openApp(page);
|
// The row height is the subject here, so the axis has to be the one the events drew: an hour
|
||||||
|
// pinned open for the clock is fewer pixels for every block on it.
|
||||||
|
await openApp(page, { now: MIDDAY() });
|
||||||
|
|
||||||
// A block shorter than the minimum block height is drawn taller than its own time, which is
|
// A block shorter than the minimum block height is drawn taller than its own time, which is
|
||||||
// right: it has to stay hittable. It must not be drawn *under* the next block, though, or the
|
// right: it has to stay hittable. It must not be drawn *under* the next block, though, or the
|
||||||
|
|||||||
+68
-5
@@ -5,7 +5,18 @@
|
|||||||
// are measured rather than asserted about the DOM.
|
// are measured rather than asserted about the DOM.
|
||||||
|
|
||||||
import { expect, test } from "@playwright/test";
|
import { expect, test } from "@playwright/test";
|
||||||
import { axis, blocks, box, gridFit, gridReady, headerDates, openApp, settle } from "./app";
|
import {
|
||||||
|
MIDDAY,
|
||||||
|
axis,
|
||||||
|
blocks,
|
||||||
|
box,
|
||||||
|
clockAt,
|
||||||
|
gridFit,
|
||||||
|
gridReady,
|
||||||
|
headerDates,
|
||||||
|
openApp,
|
||||||
|
settle,
|
||||||
|
} from "./app";
|
||||||
|
|
||||||
test.describe("the grid fits the window", () => {
|
test.describe("the grid fits the window", () => {
|
||||||
test("the body is exactly the height the window left it, with nothing to scroll", async ({
|
test("the body is exactly the height the window left it, with nothing to scroll", async ({
|
||||||
@@ -71,9 +82,13 @@ test.describe("the grid fits the window", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Every test here pins the clock to the middle of the working day. The axis takes in the hour it
|
||||||
|
// is now when the events have left it out, so the shape of a quiet evening is not the shape of a
|
||||||
|
// busy morning, and an axis test that did not say which one it meant would pass or fail on when it
|
||||||
|
// was run. What the clock does to the axis is the last test in the file.
|
||||||
test.describe("the axis holds still", () => {
|
test.describe("the axis holds still", () => {
|
||||||
test("paging a week keeps the same hours on screen", async ({ page }) => {
|
test("paging a week keeps the same hours on screen", async ({ page }) => {
|
||||||
await openApp(page);
|
await openApp(page, { now: MIDDAY() });
|
||||||
const before = (await axis(page)).map((entry) => entry.text);
|
const before = (await axis(page)).map((entry) => entry.text);
|
||||||
const dates = await headerDates(page);
|
const dates = await headerDates(page);
|
||||||
expect(before.length).toBeGreaterThan(6);
|
expect(before.length).toBeGreaterThan(6);
|
||||||
@@ -95,7 +110,7 @@ test.describe("the axis holds still", () => {
|
|||||||
// axis is solved from what the band left over, so paging past them rescales every hour on the
|
// axis is solved from what the band left over, so paging past them rescales every hour on the
|
||||||
// grid. The comment at the top of GridAllDay.tsx says the band never does this.
|
// grid. The comment at the top of GridAllDay.tsx says the band never does this.
|
||||||
test("paging a week does not move the hours on screen either", async ({ page }) => {
|
test("paging a week does not move the hours on screen either", async ({ page }) => {
|
||||||
await openApp(page);
|
await openApp(page, { now: MIDDAY() });
|
||||||
const before = await axis(page);
|
const before = await axis(page);
|
||||||
const band = (await box(page.locator(".grid-allday"))).height;
|
const band = (await box(page.locator(".grid-allday"))).height;
|
||||||
const row = (await gridFit(page)).rowHeight;
|
const row = (await gridFit(page)).rowHeight;
|
||||||
@@ -113,7 +128,7 @@ test.describe("the axis holds still", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("paging back does not reflow them either", async ({ page }) => {
|
test("paging back does not reflow them either", async ({ page }) => {
|
||||||
await openApp(page);
|
await openApp(page, { now: MIDDAY() });
|
||||||
const before = (await axis(page)).map((entry) => entry.text);
|
const before = (await axis(page)).map((entry) => entry.text);
|
||||||
for (let i = 0; i < 4; i++) {
|
for (let i = 0; i < 4; i++) {
|
||||||
await page.keyboard.press("h");
|
await page.keyboard.press("h");
|
||||||
@@ -123,7 +138,7 @@ test.describe("the axis holds still", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("the empty ends of the day are folded into a strip at each end", async ({ page }) => {
|
test("the empty ends of the day are folded into a strip at each end", async ({ page }) => {
|
||||||
await openApp(page);
|
await openApp(page, { now: MIDDAY() });
|
||||||
const strips = page.locator(".grid-strip");
|
const strips = page.locator(".grid-strip");
|
||||||
await expect(strips).toHaveCount(2);
|
await expect(strips).toHaveCount(2);
|
||||||
// Midnight to the first event, and the last event to midnight: both say what they cover.
|
// Midnight to the first event, and the last event to midnight: both say what they cover.
|
||||||
@@ -132,6 +147,54 @@ test.describe("the axis holds still", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test.describe("the hour it is now", () => {
|
||||||
|
// Half eleven at night, which the fixture leaves empty: the axis the events drew stops hours
|
||||||
|
// earlier, so without the pin there is nowhere for the line to be.
|
||||||
|
const LATE = () => clockAt(23, 30);
|
||||||
|
|
||||||
|
test("is on the axis even when the events stopped hours ago", async ({ page }) => {
|
||||||
|
await openApp(page, { now: LATE() });
|
||||||
|
|
||||||
|
expect((await axis(page)).map((entry) => entry.text)).toContain("11pm");
|
||||||
|
await expect(page.locator(".grid-now")).toHaveCount(1);
|
||||||
|
|
||||||
|
// On today's column, and inside the row it belongs to rather than on the strip above it.
|
||||||
|
const line = await box(page.locator(".grid-now"));
|
||||||
|
const row = (await gridFit(page)).rowHeight;
|
||||||
|
const eleven = (await axis(page)).find((entry) => entry.text === "11pm");
|
||||||
|
const canvas = await box(page.locator(".grid-canvas"));
|
||||||
|
expect(line.top - canvas.top).toBeGreaterThan(eleven!.y);
|
||||||
|
expect(line.top - canvas.top).toBeLessThan(eleven!.y + row);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("costs one row, not the whole evening it reached over", async ({ page }) => {
|
||||||
|
await openApp(page, { now: LATE() });
|
||||||
|
const hours = (await axis(page)).map((entry) => entry.text);
|
||||||
|
|
||||||
|
// The hours between the last event and now are not on the axis; they are in a strip.
|
||||||
|
expect(hours).not.toContain("9pm");
|
||||||
|
expect(hours).not.toContain("10pm");
|
||||||
|
await expect(page.locator(".grid-strip", { hasText: /to 11pm/ })).toHaveCount(1);
|
||||||
|
// And the day still fits.
|
||||||
|
const fit = await gridFit(page);
|
||||||
|
expect(fit.canvasHeight).toBeCloseTo(fit.bodyHeight, 0);
|
||||||
|
expect(fit.overflow).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("is gone again on a week that does not contain today", async ({ page }) => {
|
||||||
|
await openApp(page, { now: LATE() });
|
||||||
|
const before = (await axis(page)).map((entry) => entry.text);
|
||||||
|
expect(before).toContain("11pm");
|
||||||
|
|
||||||
|
// A whole week on, today is not one of the columns and neither is the hour it is now.
|
||||||
|
await page.keyboard.press("L");
|
||||||
|
await settle(page);
|
||||||
|
|
||||||
|
expect((await axis(page)).map((entry) => entry.text)).not.toContain("11pm");
|
||||||
|
await expect(page.locator(".grid-now")).toHaveCount(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test.describe("the all-day band", () => {
|
test.describe("the all-day band", () => {
|
||||||
test("a multi-day event spans exactly the days it covers", async ({ page }) => {
|
test("a multi-day event spans exactly the days it covers", async ({ page }) => {
|
||||||
await openApp(page);
|
await openApp(page);
|
||||||
|
|||||||
+13
-2
@@ -4,7 +4,17 @@
|
|||||||
// asking a store what it thinks happened.
|
// asking a store what it thinks happened.
|
||||||
|
|
||||||
import { expect, test } from "@playwright/test";
|
import { expect, test } from "@playwright/test";
|
||||||
import { axis, columnX, gridFit, gridReady, headerDates, hourY, openApp, settle } from "./app";
|
import {
|
||||||
|
MIDDAY,
|
||||||
|
axis,
|
||||||
|
columnX,
|
||||||
|
gridFit,
|
||||||
|
gridReady,
|
||||||
|
headerDates,
|
||||||
|
hourY,
|
||||||
|
openApp,
|
||||||
|
settle,
|
||||||
|
} from "./app";
|
||||||
|
|
||||||
const view = (page: import("@playwright/test").Page) =>
|
const view = (page: import("@playwright/test").Page) =>
|
||||||
page.evaluate(() => document.documentElement.getAttribute("data-view"));
|
page.evaluate(() => document.documentElement.getAttribute("data-view"));
|
||||||
@@ -72,7 +82,8 @@ test.describe("views", () => {
|
|||||||
test("z folds the band under the cursor, and it is still folded after a reload", async ({
|
test("z folds the band under the cursor, and it is still folded after a reload", async ({
|
||||||
page,
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
await openApp(page);
|
// Counted strips, so the clock must not be adding one of its own.
|
||||||
|
await openApp(page, { now: MIDDAY() });
|
||||||
const fit = await gridFit(page);
|
const fit = await gridFit(page);
|
||||||
const evening = await hourY(page, "5pm");
|
const evening = await hourY(page, "5pm");
|
||||||
expect(evening).not.toBeNull();
|
expect(evening).not.toBeNull();
|
||||||
|
|||||||
Reference in new issue
Block a user