← back to Allnewsdaily

scripts/short/formats.test.js

61 lines

"use strict";
const test = require('node:test');
const assert = require('node:assert/strict');
const { buildScript } = require('./build-script');
const { editionFormat, validateScript } = require('./formats');
const fixture = date => ({ date, stories: [
  { headline: 'Fixture: The museum opened a new exhibition on Monday.', outlet: 'Fixture Civic Desk' },
  { headline: 'Fixture: The library added evening hours for visitors.', outlet: 'Fixture Library Record' },
  { headline: 'Fixture: The city published its annual transport report.', outlet: 'Fixture City Bulletin' },
  { headline: 'Fixture: The university announced three public science lectures.', outlet: 'Fixture Campus Desk' },
  { headline: 'Fixture: The park reopened its walking path on Tuesday.', outlet: 'Fixture Parks Record' },
] });
test('same edition is identical and consecutive editions cover three formats', () => {
  const ids = new Set(); const intros = new Set(); const outros = new Set();
  for (const date of ['2026-09-10', '2026-09-11', '2026-09-12']) {
    const data = fixture(date); const a = buildScript(data);
    assert.deepEqual(a, buildScript(data)); ids.add(a.format); intros.add(a.intro.text); outros.add(a.outro.text);
    assert.deepEqual(a.beats.map(b => [b.headline, b.outlet]), data.stories.map(b => [b.headline, b.outlet]));
    assert.ok(a.totalSec <= 58); validateScript(a);
  }
  assert.equal(ids.size, 3); assert.equal(intros.size, 3); assert.equal(outros.size, 3);
});
test('dates and missing attribution fail closed', () => {
  for (const d of ['', 'bad', '2026-02-30', '2026-13-01']) assert.throws(() => editionFormat(d));
  assert.throws(() => buildScript({date:'2026-09-10',stories:[]}));
  assert.throws(() => buildScript({date:'2026-09-10',stories:[{headline:'Fixture'}]}));
});
test('overlong facts are rejected without rewriting them', () => {
  const data = fixture('2026-09-10'); data.stories = data.stories.slice(0,3).map(s => ({...s, headline: 'Fact '.repeat(160)}));
  assert.throws(() => buildScript(data), /58 seconds/);
});
test('renderer input rejects tampered duration, format, beat order and zero timing', () => {
  for (const mutate of [s => s.totalSec = 59, s => s.totalSec = 1, s => s.format = 'unknown', s => s.beats[0].n = 4, s => s.intro.estSec = 0]) {
    const s = buildScript(fixture('2026-09-10')); mutate(s); assert.throws(() => validateScript(s));
  }
});
module.exports = { fixture };

test('real ImageMagick fitting preserves short size and shrinks long accepted headlines', () => {
 const fs=require('fs'),os=require('os'),path=require('path');
 const { execFileSync }=require('child_process');
 const { fitCaption }=require('./fit-caption');
 const { FORMATS }=require('./formats');
 const dir=fs.mkdtempSync(path.join(os.tmpdir(),'short-fit-proof-'));
 const file=path.join(dir,'headline.txt');
 const font='/System/Library/Fonts/Supplemental/Arial Bold.ttf';
 const magick='/opt/homebrew/bin/magick';
 const long='Fixture: '+Array(68).fill('museum').join(' ')+'.';
 const data=fixture('2026-09-10'); data.stories=data.stories.slice(0,3); data.stories[0].headline=long;
 assert.ok(buildScript(data).totalSec<=58);
 fs.writeFileSync(file,long);
 for(const layout of FORMATS) {
  const size=fitCaption(magick,font,file,layout.box); assert.ok(size<100);
  const [w,h]=layout.box.split('x').map(Number);
  const measured=Number(execFileSync(magick,['-font',font,'-pointsize',String(size),'-size',`${w}x`,`caption:@${file}`,'-format','%h','info:'],{encoding:'utf8'}));
  assert.ok(measured<=h);
 }
 fs.writeFileSync(file,fixture('2026-09-10').stories[0].headline);
 for(const layout of FORMATS) assert.equal(fitCaption(magick,font,file,layout.box),100);
});