banner_studio/packages/layout-engine/test/dropflow-wrapper.test.ts
Simeon Schecter 988a47c797 Initial commit: Day 1 + Day 2 of the vertical slice
Day 1 (monorepo + Node layout engine):
- Turborepo + pnpm workspaces with apps/web, apps/render-worker, and
  packages for types, layout-engine, prompts, api-lib.
- @banner-studio/types: BannerSpec contract, every layer kind, ResolvedLayer,
  zod schemas mirroring each interface.
- @banner-studio/layout-engine: Dropflow WASM wrapper, text measurement,
  shrink-to-fit, push_siblings, resolveLayout. Snapshot-tested.

Day 2 (browser parity + AI pipeline):
- Layout engine ./browser subpath: same resolveLayout in the browser via
  Dropflow WASM build. Quarantined wasm-locator import (dropflow 0.5.1
  exports gap).
- Cross-group push_siblings bug fix: deltas now thread through group
  recursion via a shared accumulator; regression test added.
- DEMO_TEMPLATE_300x250 promoted to packages/layout-engine/src/templates/.
- @banner-studio/prompts: versioned extract + generate prompts with
  zod-defined tool schemas (claude-sonnet-4-6, forced tool-use).
- @banner-studio/api-lib: CSV feed loader, extract/generate/route-node/
  assemble agents, orchestrator returning fully-resolved BannerSpec.
  Generate agent retries on character-limit overflow.
- apps/web (Next.js 14 App Router): /api/generate route, /parity diff page,
  promise-singleton browser engine init.
- feeds/demo.csv with five hand-authored rows of varied length.
- SLICE_DEVIATIONS.md documents the five intentional gaps from
  ARCHITECTURE.md with V1 reversal paths.

Verified end-to-end: POST /api/generate against the live Claude API
returns three resolved BannerSpecs and two honestly-skipped rows
(overflow after two attempts). 26 unit + integration tests passing.
2026-05-15 10:25:21 -04:00

45 lines
1.5 KiB
TypeScript

import { beforeAll, describe, expect, it } from 'vitest';
import { measureText } from '../src/index.js';
import { ensureEngineReady } from './_setup.js';
import type { TypographySpec } from '@banner-studio/types';
const typography: TypographySpec = {
font_family: 'Inter',
font_size: 16,
font_weight: 400,
line_height: 1.3,
color: '#000000',
text_align: 'left'
};
describe('dropflow-wrapper / measureText', () => {
beforeAll(async () => {
await ensureEngineReady();
});
it('returns positive width and height for a short single-line string', () => {
const m = measureText({ text: 'Hello world', typography, max_width: 300 });
expect(m.width).toBeGreaterThan(0);
expect(m.height).toBeGreaterThan(0);
expect(m.line_count).toBeGreaterThanOrEqual(1);
});
it('wraps text to multiple lines when max_width is small', () => {
const long =
'The quick brown fox jumps over the lazy dog and keeps on running into the distance.';
const wide = measureText({ text: long, typography, max_width: 500 });
const narrow = measureText({ text: long, typography, max_width: 100 });
expect(narrow.line_count).toBeGreaterThan(wide.line_count);
expect(narrow.height).toBeGreaterThan(wide.height);
});
it('measures larger text as taller', () => {
const small = measureText({ text: 'Test', typography, max_width: 300 });
const big = measureText({
text: 'Test',
typography: { ...typography, font_size: 32 },
max_width: 300
});
expect(big.height).toBeGreaterThan(small.height);
});
});