__tests__/files-crop.test.js
Rama main · réplica de lectura del repositorio original.
/**
* upload_file must CONFIRM the crop/adjust dialog that sites (YouTube's banner + photo) pop after a
* file is chosen — otherwise the image never commits and the agent stalls hunting for a button.
*/
import { describe, it, expect, beforeEach, afterAll } from 'vitest';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { EventEmitter } from 'node:events';
const { captureFileChoosers } = require('../src/fileChooser');
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'gb-fc-'));
process.env.PROFILE_DIR = dir;
const fa = await import('../src/fileAssets.js');
const filesMod = await import('../src/tools/files.js');
const files = filesMod.default || filesMod;
beforeEach(() => fs.rmSync(dir, { recursive: true, force: true }));
afterAll(() => fs.rmSync(dir, { recursive: true, force: true }));
function ctx(page) {
const steps = [], said = [];
return { steps, said, page: () => page, settle: async () => {}, step: (k, t) => steps.push([k, t]), observe: (t) => said.push(t), resetClickLoop: () => {}, session: () => ({}) };
}
describe('upload_file crop-confirm', () => {
it.each([false, true])('handles a pending native file chooser when setFiles fails=%s', async (fails) => {
const id = fa.put({ mime: 'image/png', kind: 'post', name: 'reference.png', bytes: Buffer.from([1, 2, 3]) });
const page = new EventEmitter();
page.$$ = async () => [];
const context = new EventEmitter();
context.pages = () => [page];
captureFileChoosers(context);
let received;
page.emit('filechooser', { setFiles: async (file) => {
if (fails) throw new Error('picker closed');
received = file;
} });
const c = ctx(page);
await files.upload_file(c, { assetId: id });
if (fails) {
expect(c.said.join(' ')).toMatch(/Could not set the file.*picker closed/);
expect(c.steps).toEqual([]);
} else {
expect(received.name).toBe('reference.png');
expect(received.buffer).toEqual(Buffer.from([1, 2, 3]));
expect(c.steps[0][0]).toBe('upload');
}
});
it.each(['image', 'post'])('uses a stored image labelled %s as the reference when no asset id is given', async (kind) => {
fa.put({ mime: 'image/png', kind, name: 'reference.png', bytes: Buffer.from([1, 2, 3, 4]) });
fa.put({ mime: 'video/mp4', kind: 'video', name: 'old-clip.mp4', bytes: Buffer.from([5, 6, 7, 8]) });
let received;
const input = { setInputFiles: async (f) => { received = f; } };
const page = { $$: async (sel) => String(sel).includes('input[type=file]') ? [input] : [] };
await files.upload_file(ctx(page), { kind: 'image' });
expect(received.name).toBe('reference.png');
expect(received.buffer).toEqual(Buffer.from([1, 2, 3, 4]));
});
it('reports a missing reference without substituting a video', async () => {
fa.put({ mime: 'video/mp4', kind: 'video', bytes: Buffer.from([5, 6, 7, 8]) });
const c = ctx({ $$: async () => { throw new Error('must not upload'); } });
await files.upload_file(c, { kind: 'image' });
expect(c.said.join(' ')).toMatch(/No stored file of kind "image"/);
expect(c.steps).toEqual([]);
});
it('does not replace a missing explicit asset with a different image', async () => {
fa.put({ mime: 'image/png', kind: 'image', bytes: Buffer.from([1, 2]) });
const c = ctx({});
await files.upload_file(c, { assetId: 'missing', kind: 'image' });
expect(c.said.join(' ')).toMatch(/No stored file with id "missing"/);
expect(c.steps).toEqual([]);
});
it('clicks the crop dialog confirm ("Gereed") after setting the file, not Cancel', async () => {
const id = fa.put({ mime: 'image/png', kind: 'cover', name: 'b.png', bytes: Buffer.from([1, 2, 3, 4]) });
const clicks = [];
const input = { setInputFiles: async () => {} };
const gereed = { innerText: async () => 'Gereed', isVisible: async () => true, click: async () => { clicks.push('gereed'); } };
const cancel = { innerText: async () => 'Annuleren', isVisible: async () => true, click: async () => { clicks.push('cancel'); } };
let btnCall = 0;
const page = { $$: async (sel) => {
if (String(sel).includes('input[type=file]')) return [input];
btnCall += 1; return btnCall === 1 ? [cancel, gereed] : []; // one confirmable dialog, then none
} };
const c = ctx(page);
await files.upload_file(c, { assetId: id });
expect(clicks).toContain('gereed');
expect(clicks).not.toContain('cancel');
expect(c.said.join(' ')).toMatch(/confirmed/i);
});
it('does not fabricate a confirm when no crop dialog is present', async () => {
const id = fa.put({ mime: 'image/png', kind: 'profile', name: 'p.png', bytes: Buffer.from([9, 9]) });
const input = { setInputFiles: async () => {} };
const page = { $$: async (sel) => (String(sel).includes('input[type=file]') ? [input] : []) };
const c = ctx(page);
await files.upload_file(c, { assetId: id });
expect(c.said.join(' ')).toMatch(/No crop dialog needed/i);
});
});