File size: 6,211 Bytes
1210c33
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
import test from 'node:test';
import assert from 'node:assert/strict';
import { compileDraft, makeDraft, makeLayout, parseLayout, parseStrictJSON, toCentralBox, fromCentralBox, resizeLayout, validateDraft, clampRect } from '../layout.mjs';

const draft = () => ({ width: 1536, height: 1024, bbox_coord_space: 'abs', bbox_format: 'xyxy', summary: { caption: 'A ceramic campaign.', meta: ['poster'], canvas: 'single', palette: ['ivory'] }, items: [{ label: 'text', caption: 'Dark-green lettering.', text: '把日常,过成诗。\n"MOSS" 🪴', bbox: [928, 272, 1408, 448], tags: ['title'], palette: ['green'] }] });

test('official landscape example converts to centered yxyx without content loss or mutation', () => {
  const source = draft(), before = structuredClone(source), out = compileDraft(source);
  assert.deepEqual(out.items[0].bbox, [-240, 160, -64, 640]);
  assert.deepEqual(out.summary, source.summary); assert.equal(out.items[0].text, source.items[0].text);
  assert.deepEqual(out.items[0].tags, ['title']); assert.deepEqual(out.items[0].palette, ['green']);
  assert.equal(out.bbox_coord_space, 'abs_centralized'); assert.equal(out.bbox_format, 'yxyx');
  assert.equal('width' in out, false); assert.equal('height' in out, false); assert.deepEqual(source, before);
});
test('portrait, fractions and compiler precision enclose before center subtraction', () => {
  assert.deepEqual(toCentralBox([0, 0, 768, 1344], 768, 1344), [-672, -384, 672, 384]);
  assert.deepEqual(toCentralBox([10.9, 20.1, 100.01, 200.99], 768, 1344), [-652, -374, -471, -283]);
  assert.deepEqual(toCentralBox([1.00000000001, 2, 100, 200], 768, 1344), [-670, -383, -472, -284]);
  assert.deepEqual(toCentralBox([0, 0, 99.99999999999, 199.99999999999], 768, 1344), [-672, -384, -472, -284]);
});
test('draft and compiled round trips retain optional creative fields and literal Unicode', () => {
  const source = draft(), state = parseLayout(JSON.stringify(source));
  assert.deepEqual(makeDraft(state), source);
  assert.deepEqual(makeLayout(parseLayout(JSON.stringify(compileDraft(source)), { width: 1536, height: 1024 })), compileDraft(source));
  assert.equal('id' in makeDraft(state).items[0], false);
  assert.equal(state.items[0].text, source.items[0].text);
});
test('compiled import requires dimensions and honors non-square axis order', () => {
  const source = compileDraft(draft()); assert.throws(() => parseLayout(JSON.stringify(source)), /width/);
  const r = fromCentralBox([-288, -592, 352, 32], 1536, 1024);
  assert.deepEqual(r, { x: 176, y: 224, w: 624, h: 640 });
});
test('invalid numeric geometry, zero area and out-of-canvas boxes are rejected', () => {
  for (const box of [[-1,0,100,100], [0,0,1537,100], [100,0,50,100], [0,0,0,100], [0,0,100], [true,0,100,100], [0,0,NaN,100], [0,0,Infinity,100], [1,0,1.00000000001,100]]) {
    const source = draft(); source.items[0].bbox = box; assert.throws(() => compileDraft(source));
  }
  const source = draft(); source.width = 1535; assert.equal(validateDraft(source).valid, false);
});
test('unknown fields, nested layouts and blank content cannot be silently lost', () => {
  const modifications = [s=>s.other='unknown', s=>s.items[0].children=[], s=>s.items[0].id='editor-id', s=>s.summary.meta=[''], s=>s.summary.caption=' ', s=>s.items[0].text='', s=>s.items=[], s=>s.items[0].label='face', s=>s.summary.canvas='other'];
  for (const modify of modifications) { const source = draft(); modify(source); assert.throws(() => compileDraft(source)); }
  const source = draft(); delete source.items[0].text; assert.throws(() => compileDraft(source), /literal text/);
});
test('strict import rejects duplicate keys including escaped keys and invalid JSON constants', () => {
  for (const value of ['{"x":1,"x":2}', '{"x":1,"\\u0078":2}', '{"a":{"x":1,"x":2}}', '[NaN]', '{"x":Infinity}', '{"x":1e400}', '{"x":01}', '{"x":1,}', 'null trailing', '"unterminated']) assert.throws(() => parseStrictJSON(value));
  assert.deepEqual(parseStrictJSON('{"__proto__":{"polluted":true}}'), JSON.parse('{"__proto__":{"polluted":true}}'));
  assert.equal({}.polluted, undefined);
});
test('width and height retain JSON integer-literal semantics', () => {
  const source = JSON.stringify(draft());
  assert.throws(() => parseLayout(source.replace('"width":1536', '"width":1536.0')), /integer JSON literal/);
  assert.throws(() => parseLayout(source.replace('"height":1024', '"height":1.024e3')), /integer JSON literal/);
  assert.equal(parseLayout(source).width, 1536);
});
test('resolution recommendations produce warnings and are separate from accepted schema', () => {
  const source = draft(); source.width = source.height = 8192;
  const result = validateDraft(source); assert.equal(result.valid, true); assert.equal(result.warnings.length, 1);
});
test('text validation follows Python Unicode whitespace without normalization', () => {
  for (const space of ['\u0085', '\u001c', '\u2007', '\u3000']) {
    const source = draft(); source.summary.caption = space; assert.equal(validateDraft(source).valid, false);
  }
  const source = draft(); source.summary.caption = '\ufeff';
  assert.equal(compileDraft(source).summary.caption, '\ufeff');
});
test('overlap remains permitted and exact text, tags and arrays retain order', () => {
  const source = draft(); source.items.push({ ...structuredClone(source.items[0]), text: 'Second\nline' });
  assert.deepEqual(compileDraft(source).items.map(x=>x.bbox), [[-240,160,-64,640],[-240,160,-64,640]]);
  assert.deepEqual(makeDraft(parseLayout(JSON.stringify(source))), source);
});
test('explicit scaling versus local clamp policy preserves inputs and enforces edit bounds', () => {
  const state = parseLayout(JSON.stringify(draft())), before = structuredClone(state);
  const half = resizeLayout(state, 768, 512);
  assert.equal(half.items[0].x, state.items[0].x/2); assert.equal(half.items[0].h, state.items[0].h/2);
  const clamped = resizeLayout(state, 768, 512, { scale:false });
  assert.equal(clamped.items[0].x+clamped.items[0].w,768); assert.equal(clamped.items[0].y+clamped.items[0].h,448);
  assert.deepEqual(state,before); assert.deepEqual(clampRect({x:-9,y:999,w:50,h:50},1024,1024), {x:0,y:974,w:50,h:50});
});