'use strict'; const test = require('node:test'); const assert = require('node:assert/strict'); const CaptureService = require('../../app/capture'); function makeService() { const store = { addStep() { throw new Error('not used in this test'); }, }; const settings = { get(key) { if (key === 'capture.mode') return 'fullscreen'; if (key === 'capture.delayMs') return 0; return null; }, }; return new CaptureService({ store, settings, getWindow: () => null, notify: () => {}, }); } test('click-triggered session capture uses the low-latency hide pause', async () => { const service = makeService(); service.session = { guideId: 'guide-1', paused: false, count: 0, intervalSec: 0 }; let seenOptions = null; service.shoot = async (options) => { seenOptions = options; return { ok: true, step: { stepId: 'step-1' } }; }; const result = await service.sessionCapture('click'); assert.equal(result.ok, true); assert.equal(service.session.count, 1); assert.deepEqual(seenOptions, { guideId: 'guide-1', mode: 'fullscreen', delayMs: 0, hideWindowDelayMs: 25, refocus: false, clickPos: null, }); }); test('every click capture takes a fresh shot — pre-grabbed frames are never reused', async () => { const service = makeService(); service.session = { guideId: 'guide-2', paused: false, count: 0, intervalSec: 0 }; let shots = 0; service.captureCurrentFrame = async (mode) => { shots += 1; return { mode, png: Buffer.from(`frame-${shots}`), size: { width: 100, height: 100 }, display: { bounds: { x: 0, y: 0, width: 100, height: 100 } }, cursor: { x: 50, y: 50 }, capturedAt: Date.now(), }; }; const captured = []; service.store.addStep = (guideId, fields, png) => { captured.push(png.toString()); return { stepId: `step-${captured.length}` }; }; service.notify = () => {}; await service.sessionCapture('click', { x: 10, y: 10 }); await service.sessionCapture('click', { x: 20, y: 20 }); service.togglePause(true); service.togglePause(false); await service.sessionCapture('click', { x: 30, y: 30 }); assert.deepEqual(captured, ['frame-1', 'frame-2', 'frame-3'], 'each click must capture a brand-new frame, including after pause/resume'); }); test('click capture marks the click-time cursor position', async () => { const service = makeService(); service.settings.get = (key) => { if (key === 'capture.mode') return 'fullscreen'; if (key === 'capture.delayMs') return 0; if (key === 'capture.clickMarker') return true; if (key === 'capture.clickMarkerColor') return '#E5484D'; if (key === 'editor.focusedViewDefaultForNewSteps') return false; return null; }; service.session = { guideId: 'guide-4', paused: false, count: 0, intervalSec: 0 }; // No capture cache, so sessionCapture falls back to a fresh shoot(). service.captureCurrentFrame = async () => ({ mode: 'fullscreen', png: Buffer.from('live-png'), size: { width: 100, height: 100 }, display: { bounds: { x: 0, y: 0, width: 100, height: 100 } }, // Grab-time cursor, well outside the display — must not be used. cursor: { x: -1, y: -1 }, capturedAt: Date.now(), }); let added = null; service.store.addStep = (guideId, fields, png, size) => { added = { guideId, fields, png, size }; return { stepId: 'step-4', ...fields }; }; service.notify = () => {}; const result = await service.sessionCapture('click', { x: 50, y: 50 }); assert.equal(result.ok, true); assert.equal(added.fields.annotations.length, 1); assert.equal(added.fields.annotations[0].type, 'oval'); }); test('a new session starts paused and does not hide the window until "Start recording" is pressed', async () => { const service = makeService(); const win = { destroyed: false, visible: true, minimized: false, hidden: 0, shown: 0, isDestroyed() { return this.destroyed; }, isVisible() { return this.visible; }, isMinimized() { return this.minimized; }, hide() { this.visible = false; this.hidden += 1; }, show() { this.visible = true; this.shown += 1; }, showInactive() { this.visible = true; this.shown += 1; }, focus() {}, getTitle() { return 'StepForge'; }, getBounds() { return { x: 0, y: 0, width: 800, height: 600 }; }, }; service.getWindow = () => win; service.clickCaptureAvailable = () => true; try { service.startSession('guide-5'); assert.equal(service.session.paused, true, 'sessions start paused'); assert.equal(service.state().paused, true); assert.equal(win.hidden, 0, 'window must stay visible until recording starts'); // User clicks "Start recording" (the resume action). service.togglePause(false); assert.equal(service.session.paused, false); assert.equal(win.hidden, 0, 'hide is deferred briefly so the user sees it happen'); await new Promise((r) => setTimeout(r, 450)); assert.equal(win.hidden, 1, 'window hides once recording actually starts'); } finally { service.finishSession(); } });