mirror of
https://github.com/priyanshujain/margin-calendar.git
synced 2026-10-02 11:07:04 +00:00
fix bug on hour fold update
This commit is contained in:
1 parent
f7d3505ab2
commit
af8e66e84d
13 files changed
+243
-65
No files matched your search
@@ -0,0 +1,16 @@
|
||||
## Project Guidelines
|
||||
|
||||
- Do not call the task done until it is fully complete and tested.
|
||||
- Do not dismiss bug as a pre-existing" issue even if it was present before your change. It does not matter, it's still your responsibility to fix it. When you see a bug, fix it. Don't ignore it.
|
||||
|
||||
## Coding Guidelines
|
||||
|
||||
- Keep code simple and easy to read.
|
||||
- Avoid excessive comments. Only comment when absolutely necessary. Code should be readable and not require comments to understand it.
|
||||
|
||||
## Git Commit Rules
|
||||
|
||||
- Do not make branches, commit in main only
|
||||
- Commit message is one plain lowercase line. No type prefix, no scope, no body.
|
||||
- Never use `git add .` or `git add -A`. Always stage specific files by name.
|
||||
- Don't batch multiple unrelated changes into one commit.
|
||||
+9
-2
@@ -23,8 +23,7 @@ always fits exactly.
|
||||
The visible range is computed from the events in the span you are looking at: floor to the
|
||||
hour before your earliest event, ceil to the hour after your latest, clamped to a minimum of
|
||||
eight hours so a quiet week does not render four enormous rows. Empty bands at the top and
|
||||
bottom fold into thin strips labelled with their range and a count of anything hiding inside.
|
||||
Click or press `z` to unfold one.
|
||||
bottom fold into thin strips labelled with their range. Click or press `z` to unfold one.
|
||||
|
||||
The range expands immediately when it needs to, but only contracts when it would shrink by two
|
||||
hours or more. Without that hysteresis the axis flickers as you page through weeks, and a
|
||||
@@ -44,6 +43,14 @@ make a packed day look identical to an open one. But you can fold one deliberate
|
||||
and it stays folded across navigation until you unfold it. The fold is per range, remembered
|
||||
in local state, not derived from the data.
|
||||
|
||||
A fold only ever hides empty hours. The range is what is remembered; what the strip hides is
|
||||
that range minus whatever the events on screen cover. Create an event at four inside a fold of
|
||||
two to five, by any route, and the strip shrinks to two to four with the event at full scale
|
||||
under it. An event in the middle of a fold splits it into a strip either side. Page to a week
|
||||
where the hour is empty again and the whole range folds back. Expanding a strip takes exactly
|
||||
that strip out of its fold: another strip the same fold was showing stays, and the hours an
|
||||
event was covering are not kept to come back later.
|
||||
|
||||
One consequence worth stating plainly: within the unfolded region the scale is strictly
|
||||
linear, so a block twice as tall is an event twice as long, always. That property is why the
|
||||
grid is worth having at all, and it is the thing automatic gap-folding would have cost.
|
||||
|
||||
Generated
+1
-1
@@ -2276,7 +2276,7 @@ checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
|
||||
|
||||
[[package]]
|
||||
name = "margin-calendar"
|
||||
version = "0.0.1"
|
||||
version = "0.0.2"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"block2",
|
||||
|
||||
@@ -39,15 +39,13 @@ export const GridDay = memo(function GridDay({
|
||||
{packed.map((p) => {
|
||||
const { startMin, endMin } = dayMinutes(p.event, dayStart);
|
||||
const top = timeToY(layout, startMin);
|
||||
const raw = timeToY(layout, endMin) - top;
|
||||
// Both ends inside the same strip: it is hiding in there, and the strip carries the count.
|
||||
if (raw < 1) return null;
|
||||
const height = Math.max(timeToY(layout, endMin) - top, MIN_BLOCK_H);
|
||||
return (
|
||||
<GridEvent
|
||||
key={p.event.id}
|
||||
item={p.event}
|
||||
top={top}
|
||||
height={Math.max(raw, MIN_BLOCK_H)}
|
||||
height={height}
|
||||
left={p.left}
|
||||
width={p.width}
|
||||
selected={sameKey(selected, keyOf(p.event.instance))}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { Instance } from "../ipc";
|
||||
import { bandAt, busyHours, type Placed } from "./GridModel";
|
||||
|
||||
const at = (hour: number, minute = 0) => new Date(2026, 0, 5, hour, minute).getTime();
|
||||
|
||||
/** Only the times matter to the hour scan, so the instance behind them is left blank. */
|
||||
const placed = (from: [number, number], to: [number, number]): Placed => ({
|
||||
instance: {} as Instance,
|
||||
id: `${from[0]}:${from[1]}`,
|
||||
startMs: at(...from),
|
||||
endMs: at(...to),
|
||||
});
|
||||
|
||||
describe("busyHours", () => {
|
||||
it("marks every whole hour an event touches", () => {
|
||||
const busy = busyHours([placed([14, 30], [16, 0]), placed([16, 0], [16, 15])]);
|
||||
const hours = busy.flatMap((b, h) => (b ? [h] : []));
|
||||
expect(hours).toEqual([14, 15, 16]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("bandAt", () => {
|
||||
const items = [placed([9, 0], [9, 30]), placed([18, 0], [18, 30])];
|
||||
const bounds = { start: 7, end: 20 };
|
||||
|
||||
it("is the whole empty run around the hour, stopped by the events and the bounds", () => {
|
||||
expect(bandAt(items, bounds, 12)).toEqual({ start: 10, end: 18 });
|
||||
expect(bandAt(items, bounds, 19)).toEqual({ start: 19, end: 20 });
|
||||
});
|
||||
|
||||
it("offers nothing on a busy hour, since a fold there would come straight back", () => {
|
||||
expect(bandAt(items, bounds, 9)).toBeNull();
|
||||
});
|
||||
});
|
||||
Binary file not shown.
@@ -9,14 +9,13 @@ import { create } from "zustand";
|
||||
import {
|
||||
DEFAULT_BOUNDS,
|
||||
addFold,
|
||||
foldAt,
|
||||
normalizeFolds,
|
||||
unfoldStrip,
|
||||
type Bounds,
|
||||
type FitLayout,
|
||||
type Fold,
|
||||
} from "../grid/fit";
|
||||
import { loadFolds, saveFolds } from "../grid/folds";
|
||||
import { bandAt, type Placed } from "./GridModel";
|
||||
import { bandAt, busyHours, type Placed } from "./GridModel";
|
||||
|
||||
export type DragMode = "create" | "move" | "resize-start" | "resize-end";
|
||||
|
||||
@@ -82,9 +81,7 @@ export const useGrid = create<GridState>((set, get) => ({
|
||||
set({ folds });
|
||||
},
|
||||
unfold: (range) => {
|
||||
const folds = saveFolds(
|
||||
normalizeFolds(get().folds).filter((f) => f.end <= range.start || f.start >= range.end),
|
||||
);
|
||||
const folds = saveFolds(unfoldStrip(get().folds, busyHours(get().items), range));
|
||||
// An out-of-bounds band has no fold to remove, so widening the floor is what holds it open.
|
||||
const base = get().floor ?? get().layout?.bounds ?? DEFAULT_BOUNDS;
|
||||
set({
|
||||
@@ -93,7 +90,7 @@ export const useGrid = create<GridState>((set, get) => ({
|
||||
});
|
||||
},
|
||||
toggleFold: (hour) => {
|
||||
const { layout, hoverHour, items, folds } = get();
|
||||
const { layout, hoverHour, items } = get();
|
||||
if (!layout) return;
|
||||
const at = hour ?? hoverHour;
|
||||
if (at === null || at === undefined) return;
|
||||
@@ -103,11 +100,8 @@ export const useGrid = create<GridState>((set, get) => ({
|
||||
get().unfold({ start: segment.start / 60, end: segment.end / 60 });
|
||||
return;
|
||||
}
|
||||
if (foldAt(folds, h)) {
|
||||
get().unfold({ start: h, end: h + 1 });
|
||||
return;
|
||||
}
|
||||
get().fold(bandAt(items, layout.bounds, h));
|
||||
const band = bandAt(items, layout.bounds, h);
|
||||
if (band) get().fold(band);
|
||||
},
|
||||
unfoldAll: () => {
|
||||
saveFolds([]);
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
// A folded band. The empty hours outside the bounds fold themselves; an interior band only folds
|
||||
// because you asked for it, with `z` or with the chip that hangs off an empty run. Either way the
|
||||
// strip says what it covers, how much is hiding in there, and which way it goes: the whole band is
|
||||
// the button, and the mark next to its range says Expand.
|
||||
// strip says what it covers and which way it goes: the whole band is the button, and the mark next
|
||||
// to its range says Expand. It never has anything to count: an hour with an event in it is not
|
||||
// folded, whatever fold it sits inside.
|
||||
|
||||
import { memo } from "react";
|
||||
import type { Segment } from "../grid/fit";
|
||||
@@ -11,11 +12,10 @@ import { Icon } from "./Icon";
|
||||
|
||||
interface GridStripProps {
|
||||
segment: Segment;
|
||||
hidden: number;
|
||||
onUnfold: (range: { start: number; end: number }) => void;
|
||||
}
|
||||
|
||||
export const GridStrip = memo(function GridStrip({ segment, hidden, onUnfold }: GridStripProps) {
|
||||
export const GridStrip = memo(function GridStrip({ segment, onUnfold }: GridStripProps) {
|
||||
const range = { start: segment.start / 60, end: segment.end / 60 };
|
||||
const label = `Expand ${rangeLabel(range)}`;
|
||||
return (
|
||||
@@ -34,7 +34,6 @@ export const GridStrip = memo(function GridStrip({ segment, hidden, onUnfold }:
|
||||
<span className="grid-fold-verb">Expand</span>
|
||||
</span>
|
||||
<span className="grid-strip-rule" />
|
||||
{hidden > 0 && <span className="grid-strip-count">{hidden}</span>}
|
||||
</button>
|
||||
);
|
||||
});
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
computeBounds,
|
||||
computeFit,
|
||||
timeToY,
|
||||
trimFolds,
|
||||
yToTime,
|
||||
type Bounds,
|
||||
type Fold,
|
||||
@@ -51,6 +52,7 @@ import { GridGaps } from "./GridGaps";
|
||||
import { GridGhost } from "./GridGhost";
|
||||
import {
|
||||
MIN_EVENT_MINUTES,
|
||||
busyHours,
|
||||
clampMinutes,
|
||||
dayMinutes,
|
||||
hourLabel,
|
||||
@@ -264,7 +266,10 @@ export function GridView({ defaultCalendarId }: GridViewProps) {
|
||||
// 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;
|
||||
if (!nowBand) return computeFit({ bounds, folds, viewportHeight: viewportH });
|
||||
// A fold hides empty hours. Any hour an event on screen covers is given back at full scale,
|
||||
// however it got there: typed into the palette, moved in the editor, or synced in from Google.
|
||||
const shown = trimFolds(folds, busyHours(timed));
|
||||
if (!nowBand) return computeFit({ bounds, folds: shown, viewportHeight: 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.
|
||||
@@ -276,7 +281,7 @@ export function GridView({ defaultCalendarId }: GridViewProps) {
|
||||
start: Math.min(bounds.start, nowBand.start),
|
||||
end: Math.max(bounds.end, nowBand.end),
|
||||
};
|
||||
return computeFit({ bounds: axis, folds: [...folds, ...reach], viewportHeight: viewportH });
|
||||
return computeFit({ bounds: axis, folds: [...shown, ...reach], viewportHeight: viewportH });
|
||||
}, [timed, folds, floor, viewportH, nowBand]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -320,16 +325,6 @@ export function GridView({ defaultCalendarId }: GridViewProps) {
|
||||
return out;
|
||||
}, [layout]);
|
||||
|
||||
/** What each strip is hiding: events with nowhere to show, not events it merely clips. */
|
||||
const hiddenIn = useCallback(
|
||||
(start: number, end: number) =>
|
||||
timed.filter((item) => {
|
||||
const { startMin, endMin } = dayMinutes(item, startOfDay(item.startMs));
|
||||
return startMin >= start && endMin <= end;
|
||||
}).length,
|
||||
[timed],
|
||||
);
|
||||
|
||||
const unfold = useGrid((s) => s.unfold);
|
||||
const onUnfold = useCallback((range: Fold) => unfold(range), [unfold]);
|
||||
|
||||
@@ -781,12 +776,7 @@ export function GridView({ defaultCalendarId }: GridViewProps) {
|
||||
|
||||
{layout.segments.map((segment) =>
|
||||
segment.kind === "strip" ? (
|
||||
<GridStrip
|
||||
key={`s${segment.start}`}
|
||||
segment={segment}
|
||||
hidden={hiddenIn(segment.start, segment.end)}
|
||||
onUnfold={onUnfold}
|
||||
/>
|
||||
<GridStrip key={`s${segment.start}`} segment={segment} onUnfold={onUnfold} />
|
||||
) : null,
|
||||
)}
|
||||
|
||||
|
||||
+58
-9
@@ -8,12 +8,12 @@ import {
|
||||
computeBounds,
|
||||
computeFit,
|
||||
eventSpan,
|
||||
foldAt,
|
||||
isFolded,
|
||||
normalizeFolds,
|
||||
rawBounds,
|
||||
removeFoldAt,
|
||||
timeToY,
|
||||
trimFolds,
|
||||
unfoldStrip,
|
||||
widen,
|
||||
yToTime,
|
||||
type FitEvent,
|
||||
@@ -168,13 +168,62 @@ describe("folds", () => {
|
||||
).toEqual([]);
|
||||
});
|
||||
|
||||
it("adds and removes by the hour under the cursor", () => {
|
||||
const folds = addFold([{ start: 9, end: 11 }], { start: 14, end: 16 });
|
||||
expect(folds).toHaveLength(2);
|
||||
expect(foldAt(folds, 15)).toEqual({ start: 14, end: 16 });
|
||||
expect(foldAt(folds, 16)).toBeNull();
|
||||
expect(removeFoldAt(folds, 15)).toEqual([{ start: 9, end: 11 }]);
|
||||
expect(removeFoldAt(folds, 13)).toEqual(folds);
|
||||
it("adds a band and keeps the set normalized", () => {
|
||||
expect(addFold([{ start: 9, end: 11 }], { start: 14, end: 16 })).toEqual([
|
||||
{ start: 9, end: 11 },
|
||||
{ start: 14, end: 16 },
|
||||
]);
|
||||
expect(addFold([{ start: 9, end: 11 }], { start: 11, end: 12 })).toEqual([{ start: 9, end: 12 }]);
|
||||
});
|
||||
});
|
||||
|
||||
/** Hours with something in them, the way `busyHours` reports them. */
|
||||
const busyAt = (...hours: number[]): boolean[] => {
|
||||
const busy = new Array<boolean>(24).fill(false);
|
||||
for (const h of hours) busy[h] = true;
|
||||
return busy;
|
||||
};
|
||||
|
||||
describe("trimFolds", () => {
|
||||
it("gives back the hour an event landed in and keeps the rest folded", () => {
|
||||
expect(trimFolds([{ start: 14, end: 17 }], busyAt(16))).toEqual([{ start: 14, end: 16 }]);
|
||||
expect(trimFolds([{ start: 14, end: 17 }], busyAt(14))).toEqual([{ start: 15, end: 17 }]);
|
||||
});
|
||||
|
||||
it("splits a fold around an event in the middle of it", () => {
|
||||
expect(trimFolds([{ start: 14, end: 18 }], busyAt(16))).toEqual([
|
||||
{ start: 14, end: 16 },
|
||||
{ start: 17, end: 18 },
|
||||
]);
|
||||
});
|
||||
|
||||
it("drops a fold with nothing empty left in it, and leaves an empty one alone", () => {
|
||||
expect(trimFolds([{ start: 14, end: 16 }], busyAt(14, 15))).toEqual([]);
|
||||
expect(trimFolds([{ start: 14, end: 16 }], busyAt(13, 16))).toEqual([{ start: 14, end: 16 }]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("unfoldStrip", () => {
|
||||
const folds = [
|
||||
{ start: 9, end: 11 },
|
||||
{ start: 14, end: 18 },
|
||||
];
|
||||
|
||||
it("takes the strip out and leaves the other folds alone", () => {
|
||||
expect(unfoldStrip(folds, busyAt(), { start: 14, end: 18 })).toEqual([{ start: 9, end: 11 }]);
|
||||
expect(unfoldStrip(folds, busyAt(), { start: 0, end: 8 })).toEqual(folds);
|
||||
});
|
||||
|
||||
it("keeps the other strip of a split fold and drops what the event was covering", () => {
|
||||
expect(unfoldStrip(folds, busyAt(16), { start: 14, end: 16 })).toEqual([
|
||||
{ start: 9, end: 11 },
|
||||
{ start: 17, end: 18 },
|
||||
]);
|
||||
});
|
||||
|
||||
it("takes a fold merged into an end strip with it", () => {
|
||||
expect(unfoldStrip([{ start: 6, end: 10 }], busyAt(), { start: 0, end: 10 })).toEqual([]);
|
||||
expect(unfoldStrip([{ start: 18, end: 22 }], busyAt(), { start: 18, end: 24 })).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
+35
-5
@@ -203,13 +203,43 @@ export function addFold(folds: readonly Fold[], range: Fold): Fold[] {
|
||||
return normalizeFolds([...folds, range]);
|
||||
}
|
||||
|
||||
/** Unfolds whatever band covers `hour`. Nothing there is not an error. */
|
||||
export function removeFoldAt(folds: readonly Fold[], hour: number): Fold[] {
|
||||
return normalizeFolds(folds).filter((f) => hour < f.start || hour >= f.end);
|
||||
/**
|
||||
* The folds with the busy hours taken out of them. A fold hides empty time and nothing else, so
|
||||
* an event landing in a folded hour gives that hour back at once, and a fold with an event in
|
||||
* the middle of it shows as two strips. The range itself is kept as it was: page to a span where
|
||||
* the hour is empty again and it folds back.
|
||||
*/
|
||||
export function trimFolds(folds: readonly Fold[], busy: readonly boolean[]): Fold[] {
|
||||
const out: Fold[] = [];
|
||||
for (const f of normalizeFolds(folds)) {
|
||||
let start = f.start;
|
||||
for (let h = f.start; h < f.end; h++) {
|
||||
if (!busy[h]) continue;
|
||||
if (h > start) out.push({ start, end: h });
|
||||
start = h + 1;
|
||||
}
|
||||
if (f.end > start) out.push({ start, end: f.end });
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
export function foldAt(folds: readonly Fold[], hour: number): Fold | null {
|
||||
return normalizeFolds(folds).find((f) => hour >= f.start && hour < f.end) ?? null;
|
||||
const overlaps = (a: HourRange, b: HourRange): boolean => a.start < b.end && a.end > b.start;
|
||||
|
||||
/**
|
||||
* Takes an expanded strip out of the folds. The fold it came from keeps the other strips it was
|
||||
* showing and nothing else: the hours an event was covering go too, so expanding a strip never
|
||||
* leaves a piece of fold behind that could come back on its own once the event moves.
|
||||
*/
|
||||
export function unfoldStrip(folds: readonly Fold[], busy: readonly boolean[], strip: Fold): Fold[] {
|
||||
const out: Fold[] = [];
|
||||
for (const f of normalizeFolds(folds)) {
|
||||
if (!overlaps(f, strip)) {
|
||||
out.push(f);
|
||||
continue;
|
||||
}
|
||||
for (const piece of trimFolds([f], busy)) if (!overlaps(piece, strip)) out.push(piece);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -418,15 +418,6 @@
|
||||
background: var(--fold-line);
|
||||
}
|
||||
|
||||
.grid-strip-count {
|
||||
flex: none;
|
||||
padding: 1px 6px;
|
||||
border-radius: var(--r-sm);
|
||||
background: var(--accent-wash);
|
||||
color: var(--ink-soft);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
/* Columns
|
||||
------------------------------------------------------------------------------------------- */
|
||||
|
||||
|
||||
@@ -11,9 +11,11 @@ import {
|
||||
blocks,
|
||||
box,
|
||||
clockAt,
|
||||
columnX,
|
||||
gridFit,
|
||||
gridReady,
|
||||
headerDates,
|
||||
hourY,
|
||||
openApp,
|
||||
settle,
|
||||
} from "./app";
|
||||
@@ -147,6 +149,73 @@ test.describe("the axis holds still", () => {
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("folds", () => {
|
||||
test("an event created inside a fold takes its hour back and leaves the rest folded", async ({
|
||||
page,
|
||||
}) => {
|
||||
await openApp(page, { now: MIDDAY() });
|
||||
// The fixture's last event ends at half six, so the evening is the one empty run long enough
|
||||
// to fold and then land an event in. Hold it open first, then fold it by hand.
|
||||
await page.locator(".grid-strip", { hasText: /to 12am/ }).click();
|
||||
await settle(page);
|
||||
const nine = await hourY(page, "9pm");
|
||||
expect(nine).not.toBeNull();
|
||||
const fit = await gridFit(page);
|
||||
await page.mouse.move(await columnX(page, 3), nine! + fit.rowHeight / 2);
|
||||
await page.keyboard.press("z");
|
||||
await settle(page);
|
||||
await expect(page.locator(".grid-strip", { hasText: "7pm to 12am" })).toHaveCount(1);
|
||||
|
||||
await page.keyboard.press("ControlOrMeta+k");
|
||||
await page.keyboard.type("Yoga 11pm");
|
||||
await page.keyboard.press("Enter");
|
||||
await settle(page);
|
||||
|
||||
// The hour the event landed in is back on the axis with the event in it at full scale, and
|
||||
// the empty hours before it are still folded.
|
||||
const yoga = page.locator(".grid-event", { hasText: "Yoga" });
|
||||
await expect(yoga).toBeVisible();
|
||||
expect((await box(yoga)).height).toBeGreaterThan(fit.rowHeight / 2);
|
||||
const hours = (await axis(page)).map((entry) => entry.text);
|
||||
expect(hours).toContain("11pm");
|
||||
expect(hours).not.toContain("8pm");
|
||||
await expect(page.locator(".grid-strip", { hasText: "7pm to 11pm" })).toHaveCount(1);
|
||||
await expect(page.locator(".grid-strip", { hasText: "7pm to 12am" })).toHaveCount(0);
|
||||
});
|
||||
|
||||
test("a remembered fold over a busy hour shows that hour, and only that hour", async ({
|
||||
page,
|
||||
}) => {
|
||||
// 4pm has a quarter hour block on every workday; 5pm has nothing on any day.
|
||||
await openApp(page, {
|
||||
now: MIDDAY(),
|
||||
storage: { "margincal-folds": '[{"start":16,"end":18}]' },
|
||||
});
|
||||
const hours = (await axis(page)).map((entry) => entry.text);
|
||||
expect(hours).toContain("4pm");
|
||||
expect(hours).not.toContain("5pm");
|
||||
await expect(page.locator(".grid-strip", { hasText: "5pm to 6pm" })).toHaveCount(1);
|
||||
await expect(page.locator(".grid-strip", { hasText: "4pm to 6pm" })).toHaveCount(0);
|
||||
});
|
||||
|
||||
test("expanding one strip of a split fold leaves the other where it was", async ({ page }) => {
|
||||
// 6pm has a block on every day, so a fold from 5pm to 9pm shows as a strip either side of it,
|
||||
// the later one merged into the strip at the end of the day.
|
||||
await openApp(page, {
|
||||
now: MIDDAY(),
|
||||
storage: { "margincal-folds": '[{"start":17,"end":21}]' },
|
||||
});
|
||||
const five = page.locator(".grid-strip", { hasText: "5pm to 6pm" });
|
||||
await expect(five).toHaveCount(1);
|
||||
await expect(page.locator(".grid-strip", { hasText: "7pm to 12am" })).toHaveCount(1);
|
||||
|
||||
await five.click();
|
||||
await settle(page);
|
||||
expect((await axis(page)).map((entry) => entry.text)).toContain("5pm");
|
||||
await expect(page.locator(".grid-strip", { hasText: "7pm to 12am" })).toHaveCount(1);
|
||||
});
|
||||
});
|
||||
|
||||
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.
|
||||
|
||||
Reference in new issue
Block a user