88 lines
5.2 KiB
JavaScript
88 lines
5.2 KiB
JavaScript
'use strict';
|
|
const { test } = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs'), path = require('node:path'), vm = require('node:vm');
|
|
// Synthetic JPEG marker bytes only; no real photograph or service is involved.
|
|
const jpeg = '/9j/2Q==', dataURL = 'data:image/jpeg;base64,' + jpeg;
|
|
function setup(result, options = {}) {
|
|
const writes = [], draws = [], calls = [];
|
|
const platform = {
|
|
getImageInfo(options) { options.success({ width: 2, height: 1 }); },
|
|
createCanvasContext(id) {
|
|
assert.equal(id, 'compress-canvas');
|
|
const context = { clearRect() {}, drawImage(src) { draws.push(src); }, draw(reserve, complete) { complete(); },
|
|
toDataURL(value) {
|
|
calls.push(value);
|
|
if (options.nativePromiseFailure) { value.fail({ errorMessage: '原生 JPEG 导出失败' }); return Promise.reject(Error('原生 JPEG 导出失败')); }
|
|
if (options.promiseOnly) return Promise.resolve(result);
|
|
value.success(result); if (options.dualPromise) return Promise.resolve(result);
|
|
} };
|
|
if (options.fileExport) context.toTempFilePath = value => { calls.push(value); value.success({ apFilePath: 'PRIVATE_TEMP_JPEG_PATH' }); };
|
|
return context;
|
|
},
|
|
getFileSystemManager: () => ({ readFile(value) { if (options.readFailure) value.fail({ errorMessage: '读取失败' }); else value.success({ data: result }); } }),
|
|
canvasPutImageData(options) { writes.push(options); options.success(); }
|
|
};
|
|
const module = { exports: {} };
|
|
vm.runInNewContext(fs.readFileSync(path.resolve(__dirname, '../utils/image.js'), 'utf8'), {
|
|
module, my: platform, Promise, setTimeout, clearTimeout, ArrayBuffer, Uint8Array, Uint8ClampedArray
|
|
});
|
|
const page = { alive: true, setData(value, complete) { complete(); } };
|
|
return { image: module.exports, page, writes, draws, calls };
|
|
}
|
|
|
|
test('legacy/native Canvas file export validates actual JPEG bytes rather than trusting a requested file type', async () => {
|
|
const fixture = setup(jpeg, { fileExport: true });
|
|
assert.equal(await fixture.image.compressToBase64('native-takePhoto-path', fixture.page), dataURL);
|
|
assert.deepEqual(fixture.draws, ['native-takePhoto-path']); assert.equal(fixture.calls[0].fileType, 'jpg');
|
|
for (const value of ['iVBORw0KGgo=', '/9j/invalid', 'data:image/png;base64,' + jpeg]) {
|
|
const bad = setup(value, { fileExport: true });
|
|
await assert.rejects(bad.image.compressToBase64('photo', bad.page), /JPEG 转换失败/);
|
|
}
|
|
const empty = setup('', { fileExport: true }); await assert.rejects(empty.image.compressToBase64('photo', empty.page), /内容为空/);
|
|
const readFailure = setup(jpeg, { fileExport: true, readFailure: true }); await assert.rejects(readFailure.image.compressToBase64('photo', readFailure.page), /读取失败/);
|
|
});
|
|
function exportImage(fixture, kind) {
|
|
if (kind === 'compression') return fixture.image.compressToBase64('synthetic-photo', fixture.page);
|
|
return fixture.image.captureFrameToBase64({ width: 2, height: 1, pixelFormat: 'RGBA',
|
|
capturedImage: new Uint8Array([10, 20, 30, 255, 40, 50, 60, 255]).buffer }, fixture.page);
|
|
}
|
|
for (const kind of ['compression', 'raw AR frame']) {
|
|
test(kind + ' handles native legacy Canvas Promise and callback exports without duplicate completion or unhandled rejection', async () => {
|
|
for (const options of [{ promiseOnly: true }, { dualPromise: true }]) {
|
|
const fixture = setup(dataURL, options); assert.equal(await exportImage(fixture, kind), dataURL); assert.equal(fixture.calls.length, 1);
|
|
}
|
|
const failure = setup(null, { nativePromiseFailure: true });
|
|
await assert.rejects(exportImage(failure, kind), /原生 JPEG 导出失败/);
|
|
await new Promise(resolve => setImmediate(resolve));
|
|
});
|
|
test(kind + ' accepts Taobao string and documented native wrapper shapes', async () => {
|
|
const results = [dataURL, jpeg, { dataURL }, { dataUrl: dataURL }, { base64: dataURL }, { base64: jpeg }];
|
|
for (const result of results) {
|
|
const fixture = setup(result);
|
|
assert.equal(await exportImage(fixture, kind), dataURL);
|
|
assert.equal(fixture.calls.length, 1);
|
|
assert.equal(fixture.calls[0].fileType, 'jpg');
|
|
assert.equal(fixture.calls[0].quality, .88);
|
|
if (kind === 'compression') assert.deepEqual(fixture.draws, ['synthetic-photo']);
|
|
else {
|
|
assert.equal(fixture.writes.length, 1);
|
|
assert.deepEqual(Array.from(fixture.writes[0].data), [10, 20, 30, 255, 40, 50, 60, 255]);
|
|
}
|
|
}
|
|
});
|
|
test(kind + ' rejects empty, malformed, unknown and non-JPEG export responses', async () => {
|
|
const invalid = [null, undefined, '', {}, { data: dataURL }, { dataURL: jpeg }, { dataUrl: jpeg },
|
|
{ base64: '' }, 'data:image/jpeg;base64,', { dataURL: 'data:image/jpeg;base64,' },
|
|
'data:image/png;base64,/9j/2Q==', { base64: 'data:image/png;base64,/9j/2Q==' },
|
|
'iVBORw0KGgo=', { base64: 'iVBORw0KGgo=' },
|
|
'data:image/jpeg;base64,iVBORw0KGgo=', '/9j/frame', '/9j/2Q=', '/9j/2Q== ', '/9j/2_==',
|
|
{ dataURL: 'https://example.invalid/photo.jpg' }];
|
|
for (const result of invalid) {
|
|
const fixture = setup(result);
|
|
await assert.rejects(exportImage(fixture, kind), /JPEG 转换失败/, JSON.stringify(result));
|
|
assert.equal(fixture.calls.length, 1);
|
|
}
|
|
});
|
|
}
|