Files
2026-08-28 01:01:12 +02:00

157 lines
6.2 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { guideDisplayName, guideOverlayPosition, isTrackedGuideInputName, syncConnectedAudioGuides, syncConnectedGuides, syncConnectedImageGuides } from '../../web/ui/guide_sync.js';
function stateWithGuides(guides = []) {
return {
timeline: { frame_count: 124, selected_frame: 48 },
guides,
};
}
test('creates the first image guide at native H3 frame 5 by default', () => {
const state = stateWithGuides();
const changed = syncConnectedImageGuides(state, [0], () => 'guide-0');
assert.equal(changed, true);
assert.deepEqual(state.guides, [{
id: 'guide-0', enabled: true, frame: 5, image_slot: 0, audio_slot: null,
}]);
});
test('keeps an existing image guide placement and does not duplicate it', () => {
const state = stateWithGuides([{ id: 'saved', enabled: true, frame: 12, image_slot: 0, audio_slot: null }]);
const changed = syncConnectedImageGuides(state, [0], () => 'unused');
assert.equal(changed, false);
assert.equal(state.guides.length, 1);
assert.equal(state.guides[0].frame, 12);
});
test('removes guides for disconnected image slots and leaves unrelated audio guides', () => {
const state = stateWithGuides([
{ id: 'image', enabled: true, frame: 12, image_slot: 0, audio_slot: null },
{ id: 'audio', enabled: true, frame: 24, image_slot: null, audio_slot: 0 },
]);
const changed = syncConnectedImageGuides(state, [], () => 'unused');
assert.equal(changed, true);
assert.deepEqual(state.guides, [{
id: 'audio', enabled: true, frame: 24, image_slot: null, audio_slot: 0,
}]);
});
test('reconnected slots create a fresh guide at the first free H3 frame', () => {
const state = stateWithGuides([{ id: 'audio', enabled: true, frame: 24, image_slot: null, audio_slot: 0 }]);
const changed = syncConnectedImageGuides(state, [2], () => 'guide-2');
assert.equal(changed, true);
assert.equal(state.guides.at(-1).image_slot, 2);
assert.equal(state.guides.at(-1).frame, 5);
});
test('new guides advance through the native H3 frame grid', () => {
const state = stateWithGuides([{ id: 'saved', enabled: true, frame: 5, image_slot: 0, audio_slot: null }]);
syncConnectedImageGuides(state, [1], () => 'guide-1');
assert.equal(state.guides.at(-1).frame, 22);
});
test('displays guide socket numbers instead of internal ids', () => {
assert.equal(guideDisplayName({ id: 'guide-cb39813f', image_slot: 0, audio_slot: null }), '0');
assert.equal(guideDisplayName({ id: 'guide-ec3ce479', image_slot: 3, audio_slot: null }), '3');
assert.equal(guideDisplayName({ id: 'audio-only', image_slot: null, audio_slot: 2 }), '2');
});
test('keeps multiple guide overlays visible when the filmstrip is condensed', () => {
assert.deepEqual(guideOverlayPosition(3), { normal: '31px', compact: '19px' });
});
test('creates one audio-only guide for a connected audio slot', () => {
const state = stateWithGuides();
const changed = syncConnectedAudioGuides(state, [0], () => 'audio-guide-0');
assert.equal(changed, true);
assert.deepEqual(state.guides, [{
id: 'audio-guide-0', enabled: true, frame: 5, image_slot: null, audio_slot: 0,
}]);
});
test('clears a disconnected audio slot but preserves the visual part of an AV guide', () => {
const state = stateWithGuides([{
id: 'av', enabled: true, frame: 12, image_slot: 0, audio_slot: 0,
}]);
const changed = syncConnectedAudioGuides(state, [], () => 'unused');
assert.equal(changed, true);
assert.deepEqual(state.guides, [{
id: 'av', enabled: true, frame: 12, image_slot: 0, audio_slot: null,
}]);
});
test('removes a disconnected audio-only guide and does not duplicate connected audio slots', () => {
const state = stateWithGuides([
{ id: 'audio0', enabled: true, frame: 12, image_slot: null, audio_slot: 0 },
{ id: 'audio1', enabled: true, frame: 24, image_slot: null, audio_slot: 1 },
]);
const changed = syncConnectedAudioGuides(state, [1], () => 'unused');
assert.equal(changed, true);
assert.equal(state.guides.length, 1);
assert.equal(state.guides[0].audio_slot, 1);
});
test('preview refresh tracking includes audio guide sockets', () => {
assert.equal(isTrackedGuideInputName('guide_audios.guide_audio_0'), true);
assert.equal(isTrackedGuideInputName('guide_images.guide_image_0'), true);
assert.equal(isTrackedGuideInputName('reference_video'), false);
assert.equal(isTrackedGuideInputName('reference_media'), true);
assert.equal(isTrackedGuideInputName('audio_reference'), true);
assert.equal(isTrackedGuideInputName('first_frame'), true);
assert.equal(isTrackedGuideInputName('last_frame'), true);
assert.equal(isTrackedGuideInputName('positive'), false);
});
test('combined media sync creates both newly connected image and audio guides', () => {
const state = stateWithGuides();
const changed = syncConnectedGuides(
state,
[0],
[2],
(slot) => `guide-${slot}`,
(slot) => `audio-guide-${slot}`,
);
assert.equal(changed, true);
assert.deepEqual(state.guides, [
{ id: 'guide-0', enabled: true, frame: 5, image_slot: 0, audio_slot: null },
{ id: 'audio-guide-2', enabled: true, frame: 22, image_slot: null, audio_slot: 2 },
]);
});
test('merges matching image and audio slots into one AV guide', () => {
const state = stateWithGuides();
syncConnectedGuides(state, [0], [0], (slot) => `guide-${slot}`, (slot) => `audio-guide-${slot}`);
assert.deepEqual(state.guides, [{
id: 'guide-0', enabled: true, frame: 5, image_slot: 0, audio_slot: 0,
}]);
});
test('merges matching audio and image slots regardless of connection order', () => {
const state = stateWithGuides();
syncConnectedGuides(state, [], [0], (slot) => `guide-${slot}`, (slot) => `audio-guide-${slot}`);
syncConnectedGuides(state, [0], [0], (slot) => `guide-${slot}`, (slot) => `audio-guide-${slot}`);
assert.deepEqual(state.guides, [{
id: 'audio-guide-0', enabled: true, frame: 5, image_slot: 0, audio_slot: 0,
}]);
});
test('uses the selected frame when H3 snapping is disabled', () => {
const state = stateWithGuides();
syncConnectedGuides(state, [0], [2], (slot) => `guide-${slot}`, (slot) => `audio-guide-${slot}`, 'frame');
assert.deepEqual(state.guides.map((guide) => guide.frame), [48, 48]);
});