Files

118 lines
8.1 KiB
JavaScript

import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import vm from "node:vm";
import test from "node:test";
const grid = vm.createContext({});
vm.runInContext(readFileSync(new URL("../Common/GridLayout.js", import.meta.url), "utf8"), grid);
const cards = [{ id: "clock", w: 2, h: 1 }, { id: "weather", w: 1, h: 1 }, { id: "notifications", w: 3, h: 5 }, { id: "calendar", w: 3, h: 3 }, { id: "media", w: 3, h: 1 }];
const order = cards.map((card, i) => i);
const unit = { w: 1, h: 1 };
const plain = value => JSON.parse(JSON.stringify(value));
test("card spans pack into rows without overlap", () => {
assert.equal(grid.packCells(cards, order, 6).rows, 5);
assert.deepEqual(plain(grid.packCards(cards, order, 6, 600, 0, 100, false).slots[4]), { x: 0, y: 400, w: 300, h: 100, col: 0, row: 4, cols: 3, rows: 1 });
});
test("resize keeps the board within its rows", () => {
assert.deepEqual(plain(grid.fitResize(cards, order, 6, 5, 4, { w: 3, h: 2 }, unit)), { w: 3, h: 1 });
assert.deepEqual(plain(grid.fitResize(cards, order, 6, 5, 4, { w: 1, h: 1 }, unit)), { w: 1, h: 1 });
assert.deepEqual(plain(grid.fitResize(cards, order, 6, 8, 4, { w: 3, h: 2 }, unit)), { w: 3, h: 2 });
});
test("resize on an overflowing board may shrink but not grow the overflow", () => {
const over = cards.concat([{ id: "user", w: 3, h: 2 }]);
const overOrder = over.map((card, i) => i);
assert.equal(grid.packCells(over, overOrder, 6).rows, 7);
assert.deepEqual(plain(grid.fitResize(over, overOrder, 6, 5, 5, { w: 3, h: 1 }, unit)), { w: 3, h: 1 });
assert.deepEqual(plain(grid.fitResize(over, overOrder, 6, 5, 5, { w: 3, h: 3 }, unit)), { w: 3, h: 2 });
});
test("new cards shrink to free space or are rejected", () => {
const partial = [{ id: "clock", w: 6, h: 4 }, { id: "user", w: 2, h: 1 }];
assert.equal(grid.fitNewCard(cards, 6, 5, "user", { w: 3, h: 1 }, unit), null);
assert.deepEqual(plain(grid.fitNewCard(partial, 6, 5, "weather", { w: 1, h: 2 }, unit)), { w: 1, h: 1 });
assert.deepEqual(plain(grid.fitNewCard(partial, 6, 5, "media", { w: 2, h: 1 }, unit)), { w: 2, h: 1 });
assert.equal(grid.fitNewCard(partial, 6, 5, "calendar", { w: 3, h: 3 }, { w: 3, h: 3 }), null);
assert.deepEqual(plain(grid.fitNewCard(partial, 6, 5, "user", { w: 4, h: 3 }, unit)), { w: 4, h: 1 });
});
test("unavailable cards take no space", () => {
const isAvailable = id => id !== "notifications";
assert.equal(grid.packCells(cards, order, 6, isAvailable).cells[2], null);
assert.deepEqual(plain(grid.fitNewCard(cards, 6, 5, "user", { w: 3, h: 3 }, unit, isAvailable)), { w: 3, h: 3 });
});
test("positioned cards keep their cells and push only what they collide with", () => {
const board = [{ id: "a", w: 2, h: 1, col: 0, row: 0 }, { id: "b", w: 2, h: 1, col: 2, row: 0 }, { id: "c", w: 2, h: 1, col: 4, row: 2 }];
const packed = grid.packCells(board, [0, 1, 2], 6);
assert.deepEqual(plain(packed.cells.map(c => [c.col, c.row])), [[0, 0], [2, 0], [4, 2]]);
const grown = board.map((card, i) => i === 0 ? Object.assign({}, card, { w: 3 }) : card);
assert.deepEqual(plain(grid.packCells(grown, [0, 1, 2], 6).cells.map(c => [c.col, c.row])), [[0, 0], [2, 1], [4, 2]]);
assert.deepEqual(plain(grid.packCells(grown, [1, 0, 2], 6).cells.map(c => [c.col, c.row])), [[0, 1], [2, 0], [4, 2]]);
});
test("cards without a cell take the first free one and cells clamp to the columns", () => {
const board = [{ id: "a", w: 2, h: 2, col: 0, row: 0 }, { id: "b", w: 1, h: 1 }, { id: "c", w: 3, h: 1, col: 5, row: 0 }];
assert.deepEqual(plain(grid.packCells(board, [0, 1, 2], 6).cells.map(c => [c.col, c.row])), [[0, 0], [2, 0], [3, 0]]);
assert.deepEqual(plain(grid.packCells([{ id: "a", w: 1, h: 1, col: 0.5, row: 1.5 }], [0], 4, null, 0.5).cells[0]), { col: 0.5, row: 1.5, cols: 1, rows: 1 });
});
test("gravity pulls positioned cards up into holes without jumping over blockers", () => {
const board = [{ id: "a", w: 2, h: 1, col: 0, row: 0 }, { id: "b", w: 2, h: 1, col: 2, row: 0 }, { id: "c", w: 2, h: 1, col: 0, row: 2 }, { id: "d", w: 2, h: 1, col: 2, row: 3 }];
assert.deepEqual(plain(grid.packCells(board, [0, 1, 2, 3], 6).cells.map(c => [c.col, c.row])), [[0, 0], [2, 0], [0, 2], [2, 3]]);
assert.deepEqual(plain(grid.packCells(board, [0, 1, 2, 3], 6, null, 1, true).cells.map(c => [c.col, c.row])), [[0, 0], [2, 0], [0, 1], [2, 1]]);
const blocked = [{ id: "a", w: 2, h: 1, col: 0, row: 1 }, { id: "b", w: 2, h: 1, col: 0, row: 3 }];
assert.deepEqual(plain(grid.packCells(blocked, [1, 0], 6, null, 1, true).cells.map(c => [c.col, c.row])), [[0, 0], [0, 1]]);
});
test("a dragged tile snaps to the cell under its corner", () => {
const layout = grid.packCards(cards, order, 6, 600, 0, 100, false);
assert.deepEqual(plain(grid.cellAt(layout, 240, 160, 2, 1)), { col: 2, row: 2 });
assert.deepEqual(plain(grid.cellAt(layout, 560, -30, 2, 1)), { col: 4, row: 0 });
assert.deepEqual(plain(grid.cellAt(grid.packCards(cards, order, 6, 600, 0, 100, true), 0, 0, 2, 1)), { col: 4, row: 0 });
assert.deepEqual(plain(grid.placedItems([{ id: "x" }, { id: "y" }], [null, { col: 1, row: 2 }])), [{ id: "x" }, { id: "y", col: 1, row: 2 }]);
});
test("panel step follows the pointer and holds on a pinned edge", () => {
const rightEdge = step => Math.min(1000, 500 + step * 36);
assert.equal(grid.nearestStep(rightEdge, 8, 6, 30, rightEdge(8) + 50), 9);
assert.equal(grid.nearestStep(rightEdge, 20, 6, 30, 1400), 20);
});
const dropped = (cells, index, target, columns = 2, step = 1) => {
const items = cells.map((cell, i) => ({ id: String(i), w: cell.cols, h: cell.rows, col: cell.col, row: cell.row }));
const moved = items.map((item, i) => i === index ? Object.assign({}, item, target) : item);
const order = [index].concat(items.map((item, i) => i).filter(i => i !== index));
const pack = trial => grid.packCells(trial, order, columns, null, step, true).cells;
return plain(pack(grid.dropInto(moved, cells, index, target, pack, step)).map(cell => [cell.col, cell.row]));
};
test("a tile dragged down its own stack lands where dropped instead of back above the tile it covers", () => {
const cells = [{ col: 0, row: 0, cols: 1, rows: 1 }, { col: 0, row: 1, cols: 1, rows: 1 }, { col: 0, row: 2, cols: 1, rows: 2 }];
assert.deepEqual(dropped(cells, 0, { col: 0, row: 1 }), [[0, 1], [0, 0], [0, 2]]);
assert.deepEqual(dropped(cells, 0, { col: 0, row: 2 }), [[0, 3], [0, 0], [0, 1]]);
assert.deepEqual(dropped(cells, 2, { col: 0, row: 0 }), [[0, 2], [0, 3], [0, 0]]);
assert.deepEqual(dropped(cells, 1, { col: 0, row: 2 }), [[0, 0], [0, 3], [0, 1]]);
});
test("a half-width tile dragged down beside a blocked stack moves one step per half row", () => {
const cells = [{ col: 0, row: 0, cols: 1, rows: 1 }, { col: 1, row: 0, cols: 0.5, rows: 1 }, { col: 1.5, row: 0, cols: 0.5, rows: 1 }, { col: 0, row: 1, cols: 2, rows: 1 }, { col: 0, row: 2, cols: 2, rows: 1 }];
const rowOf = target => dropped(cells, 1, { col: 1, row: target }, 2, 0.5)[1][1];
assert.deepEqual([1, 1.5, 2, 2.5, 3].map(rowOf), [2, 2, 2, 3, 3]);
});
test("a tile dragged down from another column lands where dropped unless gravity would lift it back", () => {
const cells = [{ col: 0, row: 0, cols: 1, rows: 1 }, { col: 0, row: 1, cols: 1, rows: 1 }, { col: 0, row: 2, cols: 1, rows: 1 }, { col: 1, row: 0, cols: 1, rows: 1 }];
const items = cells.map((cell, i) => ({ id: "abcd"[i], col: cell.col, row: cell.row }));
const drop = (index, target) => {
const moved = items.map((item, i) => i === index ? Object.assign({}, item, target) : item);
const order = [index].concat(items.map((item, i) => i).filter(i => i !== index));
const pack = trial => grid.packCells(trial, order, 2, null, 1, true).cells;
return plain(grid.packCells(grid.dropInto(moved, cells, index, target, pack), order, 2, null, 1, true).cells.map(cell => [cell.col, cell.row]));
};
assert.deepEqual(drop(3, { col: 0, row: 1 }), [[0, 0], [0, 2], [0, 3], [0, 1]]);
assert.deepEqual(drop(0, { col: 0, row: 1 }), [[0, 1], [0, 0], [0, 2], [1, 0]]);
});