Files
inkwell/frontend/src/notes/masonry.test.ts
T
bvandeusenandClaude Opus 5.5 0f787c6363
Android / Build, or is the channel already serving this? (push) Successful in 2s
CI & Build / Python lint (push) Successful in 2s
CI & Build / Build now, or wait for Android? (push) Successful in 2s
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 2s
CI & Build / Web typecheck and unit tests (push) Successful in 8s
CI & Build / integration (push) Successful in 1m37s
Desktop (Tauri) / Web tests, clippy, Rust tests and rustfmt (push) Successful in 1m58s
CI & Build / Python tests (push) Successful in 13s
Android / Core and FFI clippy and tests (push) Successful in 40s
CI & Build / Build & push image (push) Skipped
Android / Kotlin + Rust (APK) (push) Successful in 7m38s
Android / Build the server image (push) Successful in 1s
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 2m47s
Desktop (Tauri) / Tauri desktop (Linux) (push) Failing after 3m43s
Desktop (Tauri) / Update manifest (push) Skipped
The board places each card in the shortest column, instead of leaving it to CSS columns
CSS multi-column decides placement by balancing column heights, so the same
notes could land differently at the same width: an edit left two cards alone in
the fourth column with the middle two empty, and a refresh left the fourth empty
beside a stack of three in the first.

Cards now go, in board order, into the shortest column, leftmost on a tie, so the
same notes at the same width always land the same way and every column fills
before any stacks. This changes the reading order to match Android: the second
note sits top-right rather than under the first.

useMasonry measures the cards and watches them for height changes. Placement is a
pure function, tested in notes/masonry.test.ts. Cards are absolutely placed, so
TransitionGroup's move animation carries on working unchanged.

Issue #6709.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 09:56:58 -04:00

39 lines
1.5 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { placeCards } from "./masonry";
const heights = (entries: Record<string, number>) => new Map(Object.entries(entries));
describe("placing cards on the board", () => {
it("uses every column before stacking", () => {
const { slots } = placeCards(["a", "b", "c", "d"], heights({ a: 100, b: 100, c: 100, d: 100 }), 4, 16);
expect([...slots.values()].map((s) => s.column)).toEqual([0, 1, 2, 3]);
});
it("puts the next card under the shortest column, with the gap above it", () => {
const { slots } = placeCards(["a", "b", "c"], heights({ a: 300, b: 50, c: 80 }), 2, 16);
expect(slots.get("c")).toEqual({ column: 1, top: 66 });
});
it("takes the leftmost column on a tie", () => {
const { slots } = placeCards(["a", "b", "c"], heights({ a: 100, b: 100, c: 10 }), 2, 16);
expect(slots.get("c")).toEqual({ column: 0, top: 116 });
});
it("reports the tallest column as the board's height", () => {
const { height } = placeCards(["a", "b", "c"], heights({ a: 300, b: 50, c: 80 }), 2, 16);
expect(height).toBe(316);
});
it("counts an unmeasured card as zero tall", () => {
const { slots } = placeCards(["a", "b"], heights({ b: 100 }), 1, 16);
expect(slots.get("b")).toEqual({ column: 0, top: 16 });
});
it("is an empty board with no cards", () => {
const { slots, height } = placeCards([], new Map(), 4, 16);
expect(slots.size).toBe(0);
expect(height).toBe(0);
});
});