Skip to content

Commit 0df6b7a

Browse files
authored
Merge pull request Expensify#86632 from twilight2294/issue/85928
Fix: Share sheet flow is busted
2 parents 352ab15 + 537aa14 commit 0df6b7a

2 files changed

Lines changed: 65 additions & 0 deletions

File tree

src/libs/fileDownload/checkFileExists/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ function checkFileExists(path: string | undefined): Promise<boolean> {
2121
decodedPath = path;
2222
}
2323

24+
// RNFS.stat uses NSFileManager.attributesOfItemAtPath: which expects
25+
// a POSIX path, not a file:// URI
26+
if (decodedPath.startsWith('file://')) {
27+
decodedPath = decodedPath.slice(7);
28+
}
29+
2430
// RNFS.stat() returns file info without loading the file content
2531
return RNFS.stat(decodedPath)
2632
.then((fileStat) => {

tests/unit/checkFileExistsTest.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import type RNFS from 'react-native-fs';
2+
import checkFileExists from '@libs/fileDownload/checkFileExists/index';
3+
4+
const mockStat = jest.fn<Promise<RNFS.StatResult>, [string]>();
5+
6+
jest.mock('react-native-fs', () => ({
7+
stat: (...args: [string]) => mockStat(...args),
8+
}));
9+
10+
describe('checkFileExists', () => {
11+
beforeEach(() => {
12+
mockStat.mockReset();
13+
});
14+
15+
it('should return false for undefined path', async () => {
16+
const result = await checkFileExists(undefined);
17+
expect(result).toBe(false);
18+
expect(mockStat).not.toHaveBeenCalled();
19+
});
20+
21+
it('should return false for empty string', async () => {
22+
const result = await checkFileExists('');
23+
expect(result).toBe(false);
24+
expect(mockStat).not.toHaveBeenCalled();
25+
});
26+
27+
it('should call RNFS.stat with a plain POSIX path', async () => {
28+
mockStat.mockResolvedValue({isFile: () => true} as RNFS.StatResult);
29+
const result = await checkFileExists('/var/mobile/Containers/shared_image.png');
30+
expect(result).toBe(true);
31+
expect(mockStat).toHaveBeenCalledWith('/var/mobile/Containers/shared_image.png');
32+
});
33+
34+
it('should strip file:// prefix before calling RNFS.stat', async () => {
35+
mockStat.mockResolvedValue({isFile: () => true} as RNFS.StatResult);
36+
const result = await checkFileExists('file:///var/mobile/Containers/shared_image.png');
37+
expect(result).toBe(true);
38+
expect(mockStat).toHaveBeenCalledWith('/var/mobile/Containers/shared_image.png');
39+
});
40+
41+
it('should decode URI-encoded paths', async () => {
42+
mockStat.mockResolvedValue({isFile: () => true} as RNFS.StatResult);
43+
const result = await checkFileExists('file:///var/mobile/Containers/my%20receipt.png');
44+
expect(result).toBe(true);
45+
expect(mockStat).toHaveBeenCalledWith('/var/mobile/Containers/my receipt.png');
46+
});
47+
48+
it('should return false when RNFS.stat throws', async () => {
49+
mockStat.mockRejectedValue(new Error('File not found'));
50+
const result = await checkFileExists('/nonexistent/path');
51+
expect(result).toBe(false);
52+
});
53+
54+
it('should return false when path is a directory', async () => {
55+
mockStat.mockResolvedValue({isFile: () => false} as RNFS.StatResult);
56+
const result = await checkFileExists('/var/mobile/Containers/');
57+
expect(result).toBe(false);
58+
});
59+
});

0 commit comments

Comments
 (0)