Skip to content
This repository was archived by the owner on Apr 15, 2025. It is now read-only.

Commit 50c003e

Browse files
committed
Pulled business logic from CacheableImage component and placed it in FileSystem.js for better code organization. Added additional tests for new FileSystem.js logic.
1 parent c81cd53 commit 50c003e

File tree

4 files changed

+141
-29
lines changed

4 files changed

+141
-29
lines changed

lib/FileSystem.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,45 @@ export class FileSystem {
164164

165165
}
166166

167+
/**
168+
*
169+
* Convenience method used to get the associated local file path of a web image that has been written to disk.
170+
* If the local file does not exist yet, the remote file is downloaded to local disk then the local filepath is returned.
171+
*
172+
* @param url {String} - url of file to download.
173+
* @param permanent {Boolean} - True persists the file locally indefinitely, false caches the file temporarily (until file is removed during cache pruning).
174+
* @returns {Promise} promise that resolves to the local file path of downloaded url file.
175+
*/
176+
async getLocalFilePathFromUrl(url, permanent) {
177+
178+
let filePath = null;
179+
180+
let fileName = this.getFileNameFromUrl(url);
181+
182+
let permanentFileExists = this.exists('permanent/' + fileName);
183+
let cacheFileExists = this.exists('cache/' + fileName);
184+
185+
let exists = await Promise.all([permanentFileExists, cacheFileExists]);
186+
187+
if (exists[0]) {
188+
189+
filePath = this.baseFilePath + 'permanent/' + fileName;
190+
191+
} else if (exists[1]) {
192+
193+
filePath = this.baseFilePath + 'cache/' + fileName;
194+
195+
} else {
196+
197+
let result = await this.fetchFile(url, permanent);
198+
filePath = result.path;
199+
200+
}
201+
202+
return filePath;
203+
204+
}
205+
167206
/**
168207
*
169208
* Used to download files to local filesystem.

lib/imageCacheHoc.js

Lines changed: 6 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -83,32 +83,17 @@ export default function imageCacheHoc(Image, options = {}) {
8383
// Async calls to local FS or network should occur here.
8484
// See: https://reactjs.org/docs/react-component.html#componentdidmount
8585
componentDidMount() {
86-
let fileName = this.fileSystem.getFileNameFromUrl(traverse(this.props).get(['source', 'uri']));
8786

8887
// Add a cache lock to file with this name (prevents concurrent <CacheableImage> components from pruning a file with this name from cache).
88+
let fileName = this.fileSystem.getFileNameFromUrl(traverse(this.props).get(['source', 'uri']));
8989
FileSystem.lockCacheFile(fileName, this.componentId);
9090

91-
let permanentFileExists = this.fileSystem.exists('permanent/' + fileName);
92-
let cacheFileExists = this.fileSystem.exists('cache/' + fileName);
93-
94-
return Promise.all([permanentFileExists, cacheFileExists])
95-
.then( result => {
96-
97-
if (result[0]) {
98-
this.setState({ localFilePath: this.fileSystem.baseFilePath + 'permanent/' + fileName });
99-
} else if (result[1]) {
100-
this.setState({ localFilePath: this.fileSystem.baseFilePath + 'cache/' + fileName });
101-
} else {
102-
103-
let permanent = this.props.permanent ? true : false;
104-
105-
this.fileSystem.fetchFile(this.props.source.uri, permanent)
106-
.then( filePath => {
107-
this.setState({ localFilePath: filePath });
108-
});
109-
110-
}
91+
// Check local fs for file, fallback to network and write file to disk if local file not found.
92+
let permanent = this.props.permanent ? true : false;
11193

94+
this.fileSystem.getLocalFilePathFromUrl(traverse(this.props).get(['source', 'uri']), permanent)
95+
.then( localFilePath => {
96+
this.setState({ localFilePath });
11297
});
11398

11499
}

tests/FileSystem.test.js

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ describe('lib/FileSystem', function() {
7474

7575
it('#exists mocked as true.', () => {
7676

77+
const RNFetchBlob = require('react-native-fetch-blob');
78+
RNFetchBlob.fs.exists
79+
.mockReturnValue(true);
80+
7781
const fileSystem = FileSystemFactory();
7882

7983
fileSystem.exists('abitrary-file.jpg').should.be.true();
@@ -98,6 +102,64 @@ describe('lib/FileSystem', function() {
98102

99103
});
100104

105+
it('#getLocalFilePathFromUrl should return local filepath if it exists on local fs in permanent dir.', () => {
106+
107+
const RNFetchBlob = require('react-native-fetch-blob');
108+
RNFetchBlob.fs.exists
109+
.mockReturnValueOnce(true) // mock exist in local permanent dir
110+
.mockReturnValue(true);
111+
112+
const fileSystem = FileSystemFactory();
113+
114+
return fileSystem.getLocalFilePathFromUrl('https://img.wennermedia.com/5333a62d-07db-432a-92e2-198cafa38a14-326adb1a-d8ed-4a5d-b37e-5c88883e1989.png')
115+
.then( localFilePath => {
116+
localFilePath.should.equal(mockData.basePath + '/react-native-image-cache-hoc/permanent/cd7d2199cd8e088cdfd9c99fc6359666adc36289.png');
117+
});
118+
119+
});
120+
121+
it('#getLocalFilePathFromUrl should return local filepath if it exists on local fs in cache dir.', () => {
122+
123+
const RNFetchBlob = require('react-native-fetch-blob');
124+
RNFetchBlob.fs.exists
125+
.mockReturnValueOnce(false) // mock not exist in local permanent dir
126+
.mockReturnValueOnce(true) // mock exist in local cache dir
127+
.mockReturnValue(true);
128+
129+
const fileSystem = FileSystemFactory();
130+
131+
return fileSystem.getLocalFilePathFromUrl('https://img.wennermedia.com/5333a62d-07db-432a-92e2-198cafa38a14-326adb1a-d8ed-4a5d-b37e-5c88883e1989.png')
132+
.then( localFilePath => {
133+
localFilePath.should.equal(mockData.basePath + '/react-native-image-cache-hoc/cache/cd7d2199cd8e088cdfd9c99fc6359666adc36289.png');
134+
});
135+
136+
});
137+
138+
it('#getLocalFilePathFromUrl should download file and write to disk (default to cache dir) if it does not exist on local fs.', () => {
139+
140+
const RNFetchBlob = require('react-native-fetch-blob');
141+
RNFetchBlob.fs.exists
142+
.mockReturnValueOnce(false) // mock not exist in local permanent dir
143+
.mockReturnValueOnce(false) // mock not exist in local cache dir
144+
.mockReturnValueOnce(false) // mock does not exist to get past clobber
145+
.mockReturnValue(true);
146+
147+
RNFetchBlob.fetch
148+
.mockReturnValue({
149+
path: () => {
150+
return '/this/is/path/to/file.jpg';
151+
}
152+
});
153+
154+
const fileSystem = FileSystemFactory();
155+
156+
return fileSystem.getLocalFilePathFromUrl('https://img.wennermedia.com/5333a62d-07db-432a-92e2-198cafa38a14-326adb1a-d8ed-4a5d-b37e-5c88883e1989.png')
157+
.then( localFilePath => {
158+
localFilePath.should.equal('/this/is/path/to/file.jpg');
159+
});
160+
161+
});
162+
101163
it('#fetchFile should validate path.', () => {
102164

103165
const fileSystem = FileSystemFactory();
@@ -119,6 +181,14 @@ describe('lib/FileSystem', function() {
119181

120182
const fileSystem = FileSystemFactory();
121183

184+
const RNFetchBlob = require('react-native-fetch-blob');
185+
RNFetchBlob.fetch
186+
.mockReturnValue({
187+
path: () => {
188+
return '/this/is/path/to/file.jpg';
189+
}
190+
});
191+
122192
// fileSystem.exists() is mocked to always return true, so error should always be thrown unless clobber is set to true.
123193
return fileSystem.fetchFile('https://img.wennermedia.com/5333a62d-07db-432a-92e2-198cafa38a14-326adb1a-d8ed-4a5d-b37e-5c88883e1989.png')
124194
.then(() => {
@@ -134,6 +204,14 @@ describe('lib/FileSystem', function() {
134204

135205
const fileSystem = FileSystemFactory();
136206

207+
const RNFetchBlob = require('react-native-fetch-blob');
208+
RNFetchBlob.fetch
209+
.mockReturnValue({
210+
path: () => {
211+
return '/this/is/path/to/file.jpg';
212+
}
213+
});
214+
137215
let pruneCacheHit = false;
138216

139217
// Mock fileSystem.pruneCache() to determine if it is called correctly.
@@ -153,6 +231,14 @@ describe('lib/FileSystem', function() {
153231

154232
const fileSystem = FileSystemFactory();
155233

234+
const RNFetchBlob = require('react-native-fetch-blob');
235+
RNFetchBlob.fetch
236+
.mockReturnValue({
237+
path: () => {
238+
return '/this/is/path/to/file.jpg';
239+
}
240+
});
241+
156242
let pruneCacheHit = false;
157243

158244
// Mock fileSystem.pruneCache() to determine if it is called correctly.
@@ -172,6 +258,14 @@ describe('lib/FileSystem', function() {
172258

173259
const fileSystem = FileSystemFactory();
174260

261+
const RNFetchBlob = require('react-native-fetch-blob');
262+
RNFetchBlob.fetch
263+
.mockReturnValue({
264+
path: () => {
265+
return '/this/is/path/to/file.jpg';
266+
}
267+
});
268+
175269
// Mock fileSystem.pruneCache().
176270
fileSystem.pruneCache = () => {};
177271

tests/config.js

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ jest.mock('react-native-fetch-blob', () => {
3030
CacheDir: mockData.basePath,
3131
DocumentDir: mockData.basePath
3232
},
33-
exists: () => { return true; },
33+
exists: jest.fn(),
3434
lstat: () => {
3535

3636
let lstat = [
@@ -52,13 +52,7 @@ jest.mock('react-native-fetch-blob', () => {
5252
return mockRNFetchBlob; // Must return reference to self to support method chaining.
5353
};
5454

55-
mockRNFetchBlob.fetch = () => {
56-
return {
57-
path: () => {
58-
return '/this/is/path/to/file.jpg';
59-
}
60-
};
61-
};
55+
mockRNFetchBlob.fetch = jest.fn();
6256

6357
return mockRNFetchBlob;
6458

0 commit comments

Comments
 (0)