Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,16 @@ jobs:
node: [22, 24]
runs-on: ${{ matrix.os }}
timeout-minutes: 30
env:
TAPVID_VIDEO_HOME: ${{ runner.temp }}/tapvid test home
PLAYWRIGHT_BROWSERS_PATH: ${{ runner.temp }}/tapvid browsers
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
with:
node-version: ${{ matrix.node }}
- name: Configure isolated runtime paths
shell: bash
run: |
echo "TAPVID_VIDEO_HOME=$RUNNER_TEMP/tapvid test home" >> "$GITHUB_ENV"
echo "PLAYWRIGHT_BROWSERS_PATH=$RUNNER_TEMP/tapvid browsers" >> "$GITHUB_ENV"
- name: Install FFmpeg (macOS)
if: runner.os == 'macOS'
run: brew list ffmpeg >/dev/null 2>&1 || brew install ffmpeg
Expand All @@ -32,4 +34,6 @@ jobs:
- name: Verify reusable installation
run: node skills/remix/scripts/ensure-runtime.mjs
- name: Test including render and standalone installation
run: npm run test:full
# Browser recording tests compare real-time events with captured frames.
# Run test files sequentially so competing renders do not starve the recorder.
run: npm run test:full -- --test-concurrency=1
19 changes: 14 additions & 5 deletions tests/remix/review.test.mjs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import { appendFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, writeFileSync } from 'node:fs';
import { appendFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, symlinkSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { pathToFileURL } from 'node:url';
import { readJson, requireRuntime, writeJson } from '../../skills/remix/scripts/lib/common.mjs';
import { RUNTIME, readJson, requireRuntime, writeJson } from '../../skills/remix/scripts/lib/common.mjs';
import { slotRequestId, speechRequestId } from '../../skills/remix/scripts/lib/media/plan.mjs';
import { loadReplica } from '../../skills/remix/scripts/lib/replica.mjs';
import { assetGroups, fidelity, finalCuts, knownLimits, placeSfx, sfxKinds } from '../../skills/remix/scripts/review.mjs';
Expand Down Expand Up @@ -186,20 +186,29 @@ test("template.html's bridge translates every overlay code project.mjs can emit"
for (const p of parts) assert.equal(L[`part:${p}`].en, p, 'English part names show as written');
});

test('review data carries codes, not Chinese: kinds, modes, units, notes, flags, BGM and asset labels', { skip, timeout: 120_000 }, () => {
test('review data carries codes, not Chinese: kinds, modes, units, notes, flags, BGM and asset labels', { skip, timeout: 120_000 }, (t) => {
// Estimation uses static provider prices, but routing still requires a configured key.
// Keep this offline fixture independent of personal credentials, routes and model caches.
const home = tmp();
t.after(() => rmSync(home, { recursive: true, force: true }));
symlinkSync(RUNTIME, join(home, 'runtime'), process.platform === 'win32' ? 'junction' : 'dir');
const env = { ...process.env, TAPVID_VIDEO_HOME: home, TAPVID_API_KEY: 'offline-review-test', TAPVID_API_BASE: 'http://127.0.0.1:1' };
const clone = makeClone();
const dir = makeReplica(clone, {
edit: (r) => {
r.audio.mode = 'narration';
r.audio.bgm = { brief: '', candidates: [{ path: 'bgm/a.mp3' }], selected: 0, path: null, beats: null };
r.scenes[0].media.cover.provider = 'tapvid';
r.scenes[0].media.cover.model = 'seedance-2.0-fast';
r.scenes[0].narration = 'Meet Nova, the notes app that writes back to you, sorts your week, drafts the replies you owe and reminds you of every promise you made in a meeting.';
},
});
touch(join(dir, 'bgm', 'a.mp3'));
fakeTasks(dir);
touch(join(dir, 'out', 'tapvid.mp4'));
execFileSync(process.execPath, [join(SCRIPTS, 'media.mjs'), 'plan', join(dir, 'replica.json')], { cwd: dir, stdio: 'ignore' });
const d = pageData(node(clone, 'demo'));
execFileSync(process.execPath, [join(SCRIPTS, 'media.mjs'), 'plan', join(dir, 'replica.json')], { cwd: dir, env, stdio: 'ignore' });
const page = execFileSync(process.execPath, [join(SCRIPTS, 'review.mjs'), clone, 'demo', '--no-open'], { env, encoding: 'utf8' }).trim();
const d = pageData(page);
const dict = pageDict();
assert.equal(CJK.test(JSON.stringify(d)), false, 'no Chinese in the review data');
assert.deepEqual(d.preview.bgm[0].title, { index: 1 });
Expand Down
11 changes: 7 additions & 4 deletions tests/remix/standalone.test.mjs
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { cpSync, mkdtempSync, rmSync, mkdirSync, writeFileSync } from 'node:fs';
import { mkdtempSync, rmSync, mkdirSync, writeFileSync } from 'node:fs';
import { cp } from 'node:fs/promises';
import { spawnSync } from 'node:child_process';
import { tmpdir } from 'node:os';
import { join } from 'node:path';

// Catches dependencies on repository-root assets, cwd, or package metadata when a
// user installs only this skill. Spaces and non-ASCII paths must work as well.
test('a standalone remix install replays providers, validates templates, and loads user extensions', (t) => {
test('a standalone remix install replays providers, validates templates, and loads user extensions', async (t) => {
const dir = mkdtempSync(join(tmpdir(), 'tapvid standalone '));
t.after(() => rmSync(dir, { recursive: true, force: true }));
const skill = join(dir, '安装 remix');
const home = join(dir, 'user data');
cpSync(new URL('../../skills/remix/', import.meta.url), skill, { recursive: true });
cpSync(new URL('./fixtures/sample-clone/', import.meta.url), join(dir, 'clone sample'), { recursive: true });
// Node 22's native cpSync path mishandles Unicode destinations on Windows.
// The asynchronous copy keeps the non-ASCII installation path covered there too.
await cp(new URL('../../skills/remix/', import.meta.url), skill, { recursive: true });
await cp(new URL('./fixtures/sample-clone/', import.meta.url), join(dir, 'clone sample'), { recursive: true });
const invoke = (script, ...args) => {
const r = spawnSync(process.execPath, [join(skill, 'scripts', script), ...args], {
cwd: dir, env: { ...process.env, TAPVID_VIDEO_HOME: home }, encoding: 'utf8', timeout: 30000,
Expand Down
Loading