接入原生壳分享卡图片导出

新增 file.exportImage 宿主能力契约

分享卡下载在原生壳中优先走宿主图片导出

Expo 壳写入缓存图片并调用系统分享保存

Tauri 壳通过保存对话框写入图片字节

补齐能力漂移检查、测试和架构文档
This commit is contained in:
2026-06-18 01:31:28 +08:00
parent 6843185a6c
commit 910625d5e1
17 changed files with 673 additions and 34 deletions

View File

@@ -22,7 +22,12 @@ vi.mock('expo-clipboard', () => ({
}));
const writtenFiles = vi.hoisted(
() => [] as { uri: string; content: string }[],
() =>
[] as {
uri: string;
content: string;
options?: { encoding?: 'utf8' | 'base64' };
}[],
);
vi.mock('expo-file-system', () => ({
@@ -36,10 +41,11 @@ vi.mock('expo-file-system', () => ({
this.uri = `file:///cache/${fileName}`;
}
write(content: string) {
write(content: string, options?: { encoding?: 'utf8' | 'base64' }) {
writtenFiles.push({
uri: this.uri,
content,
options,
});
}
},
@@ -309,6 +315,7 @@ describe('handleMobileHostBridgeMessage', () => {
{
uri: 'file:///cache/作品-记录-.txt',
content: '暖灯猫街',
options: undefined,
},
]);
expect(Sharing.shareAsync).toHaveBeenCalledWith(
@@ -352,4 +359,61 @@ describe('handleMobileHostBridgeMessage', () => {
expect(writtenFiles).toEqual([]);
expect(Sharing.shareAsync).not.toHaveBeenCalled();
});
test('file.exportImage 写入缓存图片并调起系统分享', async () => {
const response = await send(
request('file.exportImage', {
fileName: ' ../分享:卡?.png ',
base64Data: 'c2hhcmUtY2FyZA==',
mimeType: 'image/png',
}),
);
const okResponse = expectOk(response);
expect(okResponse.result).toEqual({
action: 'saved',
fileName: '分享-卡-.png',
bytes: 10,
});
expect(writtenFiles).toEqual([
{
uri: 'file:///cache/分享-卡-.png',
content: 'c2hhcmUtY2FyZA==',
options: { encoding: 'base64' },
},
]);
expect(Sharing.shareAsync).toHaveBeenCalledWith(
'file:///cache/分享-卡-.png',
{
mimeType: 'image/png',
UTI: 'public.png',
dialogTitle: '分享-卡-.png',
},
);
});
test('file.exportImage 拒绝非图片 MIME 与超限内容', async () => {
const unsupportedMime = await send(
request('file.exportImage', {
fileName: '分享卡.txt',
base64Data: 'c2hhcmUtY2FyZA==',
mimeType: 'text/plain',
}),
);
expect(expectFailed(unsupportedMime).error.code).toBe('invalid_request');
const oversized = await send(
request('file.exportImage', {
fileName: '分享卡.png',
base64Data: `${'A'.repeat(7 * 1024 * 1024)}`,
mimeType: 'image/png',
}),
);
expect(expectFailed(oversized).error.code).toBe('invalid_request');
expect(writtenFiles).toEqual([]);
expect(Sharing.shareAsync).not.toHaveBeenCalled();
});
});