Files
SlabUploader/frontend/src/components.test.ts
T
Ty 49c22c9385 feat(greenfield): rebuild SlabUploader M1-M3 on clean foundation, all gates green
Clean re-implementation of the full product (not a port of the old M3 tree):
- P0 foundation: 13-table schema + per-call sqlite factory (thread-safe),
  FastAPI app (health, JSON logging + secret redaction, CORS, startup guard).
- P1 M1 Capture: mask/sheetKeep, geometry/rect (min-area rect), measure,
  png crop/inventory/overlay, EXIF decodeOrientedBitmap.
- P2 M2 Photos: stateful capture session (batch, pick-cal, apply mask/crop
  to batch) + photos-ready lineup.
- P3 M3 Create: real backend (crypto, woo client, taxonomy sync, pricing,
  inference Call1/Call2, review, publish, settings, slabs, photos) + real FE
  review/publish/settings screens consuming the API (no hardcoded Woo ids).
- P4 gate exit: check_requirements + check_coverage both exit 0
  (137/155 IDs, 0 gaps); backend 130/130, frontend 150/150, build green.

Cite: docs/REQUIREMENTS.md (155 rows), docs/MILESTONES.md M1-M3.
2026-09-25 06:57:29 -07:00

82 lines
3.1 KiB
TypeScript

import { test } from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
import { compile, preprocess } from 'svelte/compiler';
import sp from 'svelte-preprocess';
import { draft } from './lib/types';
import { computeSqft, computeBdft, sampleWidths } from './lib/measure';
import { buildTitle as buildCopyTitle } from './review/copy';
const here = dirname(fileURLToPath(import.meta.url));
const tsPreprocessor = { script: sp.typescript().script };
async function compileComponent(rel: string) {
const code = readFileSync(join(here, rel), 'utf8');
const processed = await preprocess(code, [tsPreprocessor], { filename: rel });
return compile(processed.code, { filename: rel, generate: 'client' });
}
// Each fresh component must compile clean (no warnings) — this catches Svelte
// template errors that tsc cannot see (e.g. binding to a reactive statement).
for (const rel of [
'./App.svelte',
'./capture/PhotoImport.svelte',
'./measure/MeasureInputs.svelte',
'./review/ReviewForm.svelte',
'./publish/PublishPanel.svelte',
'./settings/SettingsPanel.svelte'
]) {
test(`compiles ${rel}`, async () => {
const { warnings } = await compileComponent(rel);
assert.deepStrictEqual(warnings, [], warnings.map((w) => w.code + ': ' + w.message).join('; '));
});
}
test('auto title derives from the primary wood-category leaf (live edge vs dimensional)', () => {
const facts = { lengthIn: 48, widthAvgIn: 18, thicknessIn: 2, bdft: 12 };
assert.strictEqual(
buildCopyTitle(facts, 'Figured', 'Black Walnut', { slug: 'live-edge-wood', name: 'Live edge wood' }),
'Figured Black Walnut Live Edge Slab',
);
assert.strictEqual(
buildCopyTitle(facts, 'Plain', 'Red Oak', { slug: 'dimensional-wood', name: 'Dimensional wood' }),
'Plain Red Oak Dimensional',
);
});
test('a fresh draft reaches a ready, publishable state deterministically', () => {
// The full local workflow: capture -> measure -> review -> ready.
const slab = draft({
lengthIn: 96,
thicknessIn: 1.5,
sqft: computeSqft(921600, 100),
bdft: computeBdft(0.64, 1.5),
widthAvgIn: sampleWidths(96, [18, 20, 22]).avg,
sku: 'SLAB-DEMO-001',
speciesId: 101,
woodCategoryIds: [201],
edgeTypeTermId: 301,
figureTermIds: [402],
gradeTermId: 501,
thicknessTermId: 601,
moistureTermId: 701,
price: 11.52,
title: buildCopyTitle(
{ lengthIn: 96, widthAvgIn: 20, thicknessIn: 1.5, bdft: 0.96 },
'Figured',
'Black Walnut',
{ slug: 'live-edge-wood', name: 'Live edge wood' },
)
});
// Same readiness gate ReviewForm.checkReady() enforces.
const ready =
!!slab.sku && slab.sku.length >= 3 && slab.speciesId != null && slab.woodCategoryIds.length > 0 &&
slab.edgeTypeTermId != null && slab.figureTermIds.length > 0 && slab.gradeTermId != null &&
slab.thicknessTermId != null && slab.moistureTermId != null && slab.price > 0 && slab.title.length > 0;
assert.ok(ready);
assert.strictEqual(slab.bdft, 0.96);
assert.strictEqual(slab.price, Math.round(slab.bdft * 12 * 100) / 100);
});