Extract + unit-test the padding-aware cell geometry (Codex nice-to-have)
Pulled the pointer→cell math out of cellAt() into a pure cellFromPoint(rect, x, y, n) in $lib/wordsearch.js (only getBoundingClientRect stays in the component), and covered it with vitest — including the last-column case that was drifting under the old overflowing layout, plus clamping and a scrolled-origin rect. 11 vitest tests now; real-device testing remains the final validator. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
<script>
|
||||
import { getJSON } from '$lib/api.js';
|
||||
import { lineFrom, matchWord } from '$lib/wordsearch.js';
|
||||
import { lineFrom, matchWord, cellFromPoint } from '$lib/wordsearch.js';
|
||||
|
||||
let { size = 'med', onstatus } = $props();
|
||||
|
||||
@@ -86,12 +86,7 @@
|
||||
function summary() { return { game: 'wordsearch', size, date, status, found: foundWords.length, total: words.length, ms: resultMs }; }
|
||||
|
||||
function cellAt(e) {
|
||||
const rect = gridEl.getBoundingClientRect();
|
||||
const pad = 7; // grid padding (6) + border (1)
|
||||
const cw = (rect.width - 2 * pad) / n; // even 1fr columns share the inner width
|
||||
const c = Math.min(n - 1, Math.max(0, Math.floor((e.clientX - rect.left - pad) / cw)));
|
||||
const r = Math.min(n - 1, Math.max(0, Math.floor((e.clientY - rect.top - pad) / cw)));
|
||||
return [r, c];
|
||||
return cellFromPoint(gridEl.getBoundingClientRect(), e.clientX, e.clientY, n);
|
||||
}
|
||||
|
||||
function down(e) {
|
||||
|
||||
@@ -28,6 +28,19 @@ export function lineFrom(start, end, size) {
|
||||
return cells.length ? cells : [start];
|
||||
}
|
||||
|
||||
/**
|
||||
* Map a pointer position to a grid cell. Pure geometry (the DOM rect is passed
|
||||
* in) so it's testable: `rect` is {left, top, width}, cells share the inner width
|
||||
* (grid width minus padding/border) evenly, and the result is clamped to range.
|
||||
* @returns {[number,number]} [row, col]
|
||||
*/
|
||||
export function cellFromPoint(rect, clientX, clientY, n, pad = 7) {
|
||||
const cw = (rect.width - 2 * pad) / n;
|
||||
const c = Math.min(n - 1, Math.max(0, Math.floor((clientX - rect.left - pad) / cw)));
|
||||
const r = Math.min(n - 1, Math.max(0, Math.floor((clientY - rect.top - pad) / cw)));
|
||||
return [r, c];
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the letters along `cells` off `grid` (array of row strings) and return
|
||||
* the matching not-yet-found word (forward or reversed), or null. Re-selecting
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { lineFrom, matchWord } from './wordsearch.js';
|
||||
import { lineFrom, matchWord, cellFromPoint } from './wordsearch.js';
|
||||
|
||||
const colinear = (cells) => {
|
||||
if (cells.length < 2) return true;
|
||||
@@ -30,6 +30,32 @@ describe('lineFrom', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('cellFromPoint', () => {
|
||||
// pad=7; inner = 462-14 = 448; with n=14 → cw = 32px per column.
|
||||
const rect = { left: 0, top: 0, width: 462 };
|
||||
const at = (x, y) => cellFromPoint(rect, x, y, 14);
|
||||
const colCentre = (col) => 7 + col * 32 + 16; // pad + col*cw + half a cell
|
||||
|
||||
it('maps a cell centre to that cell', () => {
|
||||
expect(at(colCentre(0), colCentre(0))).toEqual([0, 0]);
|
||||
expect(at(colCentre(7), colCentre(3))).toEqual([3, 7]);
|
||||
});
|
||||
|
||||
it('maps the LAST column correctly (the overflow regression)', () => {
|
||||
expect(at(colCentre(13), colCentre(13))).toEqual([13, 13]); // not 12, not off-grid
|
||||
});
|
||||
|
||||
it('clamps points outside the grid to the nearest cell', () => {
|
||||
expect(at(-50, -50)).toEqual([0, 0]);
|
||||
expect(at(9999, 9999)).toEqual([13, 13]);
|
||||
});
|
||||
|
||||
it('respects a non-zero rect origin (scrolled page)', () => {
|
||||
const r2 = { left: 100, top: 200, width: 462 };
|
||||
expect(cellFromPoint(r2, 100 + colCentre(5), 200 + colCentre(2), 14)).toEqual([2, 5]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('matchWord', () => {
|
||||
const grid = ['CALM', 'XXXX', 'XXXX', 'XXXX']; // CALM across row 0
|
||||
const words = ['CALM', 'PEACE'];
|
||||
|
||||
Reference in New Issue
Block a user