test(05-01): add unit tests for Y-flip coordinate conversion formula

- Created src/lib/pdf/__tests__/prepare-document.test.ts with 10 test cases
- Tests verify Y-axis flip: screenY=0 → pdfY≈792, screenY=792 → pdfY≈0
- Tests verify scale-invariance at both 1:1 and 50% zoom ratios
- Installed jest, ts-jest, @types/jest and added jest config to package.json
- All 10 tests pass

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Chandler Copeland
2026-03-19 23:55:27 -06:00
parent c81e8ea838
commit 34ed0baa43
3 changed files with 4552 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -30,10 +30,15 @@
"react-pdf": "^10.4.1",
"zod": "^4.3.6"
},
"jest": {
"preset": "ts-jest",
"testEnvironment": "node"
},
"devDependencies": {
"@tailwindcss/postcss": "^4",
"@types/adm-zip": "^0.5.8",
"@types/bcryptjs": "^2.4.6",
"@types/jest": "^30.0.0",
"@types/node": "^20",
"@types/nodemailer": "^7.0.11",
"@types/react": "^19",
@@ -42,8 +47,10 @@
"drizzle-kit": "^0.31.10",
"eslint": "^9",
"eslint-config-next": "16.2.0",
"jest": "^29.7.0",
"playwright": "^1.58.2",
"tailwindcss": "^4",
"ts-jest": "^29.4.6",
"tsx": "^4.21.0",
"typescript": "^5"
}

View File

@@ -0,0 +1,74 @@
/**
* Unit tests for the Y-flip coordinate conversion formula used in FieldPlacer.tsx.
*
* PDF user space: origin at bottom-left, Y increases upward.
* DOM/screen space: origin at top-left, Y increases downward.
* Formula must flip the Y axis.
*
* US Letter: 612 × 792 pts at 72 DPI.
*/
// Pure conversion functions extracted from the FieldPlacer formula.
// These MUST match what is implemented in FieldPlacer.tsx exactly.
function screenToPdfY(screenY: number, renderedH: number, originalHeight: number): number {
return ((renderedH - screenY) / renderedH) * originalHeight;
}
function screenToPdfX(screenX: number, renderedW: number, originalWidth: number): number {
return (screenX / renderedW) * originalWidth;
}
const US_LETTER_W = 612; // pts
const US_LETTER_H = 792; // pts
describe('Y-flip coordinate conversion (US Letter 612×792)', () => {
describe('at 1:1 scale (rendered = original dimensions)', () => {
const rW = US_LETTER_W;
const rH = US_LETTER_H;
test('screenY=0 (visual top) produces pdfY≈792 (PDF top)', () => {
expect(screenToPdfY(0, rH, US_LETTER_H)).toBeCloseTo(792, 1);
});
test('screenY=792 (visual bottom) produces pdfY≈0 (PDF bottom)', () => {
expect(screenToPdfY(792, rH, US_LETTER_H)).toBeCloseTo(0, 1);
});
test('screenY=396 (visual center) produces pdfY≈396 (PDF center)', () => {
expect(screenToPdfY(396, rH, US_LETTER_H)).toBeCloseTo(396, 1);
});
test('screenX=0 produces pdfX=0', () => {
expect(screenToPdfX(0, rW, US_LETTER_W)).toBeCloseTo(0, 1);
});
test('screenX=612 (full width) produces pdfX≈612', () => {
expect(screenToPdfX(612, rW, US_LETTER_W)).toBeCloseTo(612, 1);
});
test('screenX=306 (horizontal center) produces pdfX≈306', () => {
expect(screenToPdfX(306, rW, US_LETTER_W)).toBeCloseTo(306, 1);
});
});
describe('at 50% zoom (rendered = half original dimensions)', () => {
const rW = US_LETTER_W / 2; // 306
const rH = US_LETTER_H / 2; // 396
test('screenY=0 at 50% zoom still produces pdfY≈792 (scale-invariant)', () => {
expect(screenToPdfY(0, rH, US_LETTER_H)).toBeCloseTo(792, 1);
});
test('screenY=396 (visual bottom at 50% zoom) produces pdfY≈0', () => {
expect(screenToPdfY(396, rH, US_LETTER_H)).toBeCloseTo(0, 1);
});
test('screenY=198 (visual center at 50% zoom) produces pdfY≈396', () => {
expect(screenToPdfY(198, rH, US_LETTER_H)).toBeCloseTo(396, 1);
});
test('screenX=153 (quarter width at 50% zoom) produces pdfX≈306 (half PDF width)', () => {
expect(screenToPdfX(153, rW, US_LETTER_W)).toBeCloseTo(306, 1);
});
});
});