157 lines
4.3 KiB
TypeScript
157 lines
4.3 KiB
TypeScript
/**
|
|
* T2.3 — Screenshot pipeline unit tests (with mocked nut.js and sharp)
|
|
*/
|
|
|
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
|
|
const mockGrabRegion = vi.fn();
|
|
const mockRegion = vi.fn().mockImplementation((left: number, top: number, width: number, height: number) => ({
|
|
left, top, width, height,
|
|
}));
|
|
|
|
const mockResize = vi.fn();
|
|
const mockJpeg = vi.fn();
|
|
const mockToBuffer = vi.fn().mockResolvedValue(Buffer.from('fake-jpeg-data'));
|
|
|
|
// Build a chainable mock that supports .clone()
|
|
function makeChain() {
|
|
const chain: any = {
|
|
resize: mockResize,
|
|
jpeg: mockJpeg,
|
|
toBuffer: mockToBuffer,
|
|
clone: vi.fn(() => makeChain()),
|
|
};
|
|
mockResize.mockReturnValue(chain);
|
|
mockJpeg.mockReturnValue(chain);
|
|
return chain;
|
|
}
|
|
|
|
// Mock sharp
|
|
vi.mock('sharp', () => {
|
|
return {
|
|
default: vi.fn(() => makeChain()),
|
|
};
|
|
});
|
|
|
|
// Mock display module
|
|
vi.mock('../../src/desktop/display.js', () => ({
|
|
getDisplay: vi.fn(),
|
|
}));
|
|
|
|
// Mock nut.js — must handle dynamic import()
|
|
vi.mock('@nut-tree-fork/nut-js', () => ({
|
|
screen: { grabRegion: mockGrabRegion },
|
|
Region: mockRegion,
|
|
}));
|
|
|
|
import { captureScreenshot } from '../../src/desktop/screenshot.js';
|
|
import { getDisplay } from '../../src/desktop/display.js';
|
|
import sharp from 'sharp';
|
|
|
|
const mockGetDisplay = vi.mocked(getDisplay);
|
|
const mockSharp = vi.mocked(sharp);
|
|
|
|
function setupDisplay(width: number, height: number, scaleFactor: number = 1) {
|
|
mockGetDisplay.mockResolvedValue({
|
|
index: 0,
|
|
label: 'Test',
|
|
width,
|
|
height,
|
|
scaleFactor,
|
|
isPrimary: true,
|
|
origin: { x: 0, y: 0 },
|
|
});
|
|
}
|
|
|
|
function setupCapture(physicalWidth: number, physicalHeight: number) {
|
|
mockGrabRegion.mockResolvedValue({
|
|
width: physicalWidth,
|
|
height: physicalHeight,
|
|
data: Buffer.alloc(physicalWidth * physicalHeight * 4),
|
|
});
|
|
}
|
|
|
|
describe('captureScreenshot', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
// Re-set defaults after clearAllMocks
|
|
mockToBuffer.mockResolvedValue(Buffer.from('fake-jpeg-data'));
|
|
});
|
|
|
|
it('1080p display: no resize needed', async () => {
|
|
setupDisplay(1920, 1080, 1);
|
|
setupCapture(1920, 1080);
|
|
|
|
const result = await captureScreenshot(0, 75);
|
|
|
|
expect(result.physicalWidth).toBe(1920);
|
|
expect(result.physicalHeight).toBe(1080);
|
|
expect(result.logicalWidth).toBe(1920);
|
|
expect(result.logicalHeight).toBe(1080);
|
|
expect(result.imageWidth).toBe(1920);
|
|
expect(result.imageHeight).toBe(1080);
|
|
expect(result.mimeType).toBe('image/jpeg');
|
|
expect(result.displayIndex).toBe(0);
|
|
|
|
expect(mockResize).toHaveBeenCalledWith(1920, 1080, { kernel: 'lanczos3' });
|
|
});
|
|
|
|
it('Retina: 2880x1800 physical, scaleFactor=2 → 1440x900 (no further resize)', async () => {
|
|
setupDisplay(1440, 900, 2);
|
|
setupCapture(2880, 1800);
|
|
|
|
const result = await captureScreenshot(0, 75);
|
|
|
|
expect(result.physicalWidth).toBe(2880);
|
|
expect(result.physicalHeight).toBe(1800);
|
|
expect(result.logicalWidth).toBe(1440);
|
|
expect(result.logicalHeight).toBe(900);
|
|
expect(result.imageWidth).toBe(1440);
|
|
expect(result.imageHeight).toBe(900);
|
|
expect(result.scaleFactor).toBe(2);
|
|
});
|
|
|
|
it('4K: 3840x2160 physical, scaleFactor=1 → resize to 1920x1080', async () => {
|
|
setupDisplay(3840, 2160, 1);
|
|
setupCapture(3840, 2160);
|
|
|
|
const result = await captureScreenshot(0, 75);
|
|
|
|
expect(result.physicalWidth).toBe(3840);
|
|
expect(result.logicalWidth).toBe(3840);
|
|
expect(result.imageWidth).toBe(1920);
|
|
expect(result.imageHeight).toBe(1080);
|
|
});
|
|
|
|
it('2K: 2560x1440 physical, scaleFactor=1 → resize to 1920x1080', async () => {
|
|
setupDisplay(2560, 1440, 1);
|
|
setupCapture(2560, 1440);
|
|
|
|
const result = await captureScreenshot(0, 75);
|
|
|
|
expect(result.logicalWidth).toBe(2560);
|
|
expect(result.imageWidth).toBe(1920);
|
|
expect(result.imageHeight).toBe(1080);
|
|
});
|
|
|
|
it('returns valid base64 string', async () => {
|
|
setupDisplay(1920, 1080, 1);
|
|
setupCapture(1920, 1080);
|
|
|
|
const result = await captureScreenshot(0, 75);
|
|
|
|
expect(typeof result.image).toBe('string');
|
|
expect(result.image.length).toBeGreaterThan(0);
|
|
expect(() => Buffer.from(result.image, 'base64')).not.toThrow();
|
|
});
|
|
|
|
it('passes jpeg quality to sharp', async () => {
|
|
setupDisplay(1920, 1080, 1);
|
|
setupCapture(1920, 1080);
|
|
|
|
await captureScreenshot(0, 50);
|
|
|
|
expect(mockJpeg).toHaveBeenCalledWith({ quality: 50 });
|
|
});
|
|
});
|