158 lines
5.0 KiB
JavaScript
158 lines
5.0 KiB
JavaScript
'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();
|
|
}
|
|
});
|