接入移动壳文本文件导出能力
Expo 移动壳通过文件系统写入缓存文本并调用系统分享保存面板 补充移动壳导出能力依赖、配置守卫和 HostBridge 单测 更新宿主壳能力协议、方案文档和共享决策记录
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import * as Linking from 'expo-linking';
|
||||
import * as Sharing from 'expo-sharing';
|
||||
import { Share } from 'react-native';
|
||||
import { afterEach, describe, expect, test, vi } from 'vitest';
|
||||
|
||||
@@ -19,6 +20,30 @@ vi.mock('expo-clipboard', () => ({
|
||||
setStringAsync: vi.fn(),
|
||||
}));
|
||||
|
||||
const writtenFiles = vi.hoisted(
|
||||
() => [] as { uri: string; content: string }[],
|
||||
);
|
||||
|
||||
vi.mock('expo-file-system', () => ({
|
||||
Paths: {
|
||||
cache: 'file:///cache/',
|
||||
},
|
||||
File: class MockFile {
|
||||
uri: string;
|
||||
|
||||
constructor(_base: string, fileName: string) {
|
||||
this.uri = `file:///cache/${fileName}`;
|
||||
}
|
||||
|
||||
write(content: string) {
|
||||
writtenFiles.push({
|
||||
uri: this.uri,
|
||||
content,
|
||||
});
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('expo-haptics', () => ({
|
||||
ImpactFeedbackStyle: {
|
||||
Heavy: 'heavy',
|
||||
@@ -32,6 +57,11 @@ vi.mock('expo-linking', () => ({
|
||||
openURL: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('expo-sharing', () => ({
|
||||
isAvailableAsync: vi.fn(async () => true),
|
||||
shareAsync: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('react-native', () => ({
|
||||
Platform: {
|
||||
OS: 'ios',
|
||||
@@ -87,7 +117,11 @@ function expectFailed(response: HostBridgeResponse) {
|
||||
|
||||
afterEach(() => {
|
||||
vi.mocked(Linking.openURL).mockReset();
|
||||
vi.mocked(Sharing.isAvailableAsync).mockReset();
|
||||
vi.mocked(Sharing.isAvailableAsync).mockResolvedValue(true);
|
||||
vi.mocked(Sharing.shareAsync).mockReset();
|
||||
vi.mocked(Share.share).mockReset();
|
||||
writtenFiles.length = 0;
|
||||
resetMobileHostBridgeForTest();
|
||||
});
|
||||
|
||||
@@ -104,6 +138,9 @@ describe('handleMobileHostBridgeMessage', () => {
|
||||
expect(
|
||||
(okResponse.result as { capabilities: string[] }).capabilities,
|
||||
).toContain('navigation.openNativePage');
|
||||
expect(
|
||||
(okResponse.result as { capabilities: string[] }).capabilities,
|
||||
).toContain('file.exportText');
|
||||
expect(
|
||||
(okResponse.result as { capabilities: string[] }).capabilities,
|
||||
).toEqual(
|
||||
@@ -237,7 +274,41 @@ describe('handleMobileHostBridgeMessage', () => {
|
||||
expect(Share.share).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('移动壳未接入真实导出能力时明确返回 unsupported', async () => {
|
||||
test('file.exportText 写入缓存文件并调起系统分享', async () => {
|
||||
const response = await send(
|
||||
request('file.exportText', {
|
||||
fileName: ' ../作品:记录?.txt ',
|
||||
content: '暖灯猫街',
|
||||
mimeType: 'text/markdown',
|
||||
}),
|
||||
);
|
||||
|
||||
const okResponse = expectOk(response);
|
||||
|
||||
expect(okResponse.result).toEqual({
|
||||
action: 'saved',
|
||||
fileName: '作品-记录-.txt',
|
||||
bytes: 12,
|
||||
});
|
||||
expect(writtenFiles).toEqual([
|
||||
{
|
||||
uri: 'file:///cache/作品-记录-.txt',
|
||||
content: '暖灯猫街',
|
||||
},
|
||||
]);
|
||||
expect(Sharing.shareAsync).toHaveBeenCalledWith(
|
||||
'file:///cache/作品-记录-.txt',
|
||||
{
|
||||
mimeType: 'text/markdown',
|
||||
UTI: 'public.plain-text',
|
||||
dialogTitle: '作品-记录-.txt',
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test('file.exportText 在系统分享不可用时明确返回 unsupported capability', async () => {
|
||||
vi.mocked(Sharing.isAvailableAsync).mockResolvedValue(false);
|
||||
|
||||
const response = await send(
|
||||
request('file.exportText', {
|
||||
fileName: '作品记录.txt',
|
||||
@@ -247,6 +318,23 @@ describe('handleMobileHostBridgeMessage', () => {
|
||||
|
||||
const failedResponse = expectFailed(response);
|
||||
|
||||
expect(failedResponse.error.code).toBe('unsupported_method');
|
||||
expect(failedResponse.error.code).toBe('unsupported_capability');
|
||||
expect(writtenFiles).toEqual([]);
|
||||
expect(Sharing.shareAsync).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('file.exportText 拒绝超出上限的文本内容', async () => {
|
||||
const response = await send(
|
||||
request('file.exportText', {
|
||||
fileName: '作品记录.txt',
|
||||
content: 'a'.repeat(5 * 1024 * 1024 + 1),
|
||||
}),
|
||||
);
|
||||
|
||||
const failedResponse = expectFailed(response);
|
||||
|
||||
expect(failedResponse.error.code).toBe('invalid_request');
|
||||
expect(writtenFiles).toEqual([]);
|
||||
expect(Sharing.shareAsync).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user