Open
Description
Sometimes it's helpful to test code in real world scenarios, for this a working download function is required. To help others, I want to share my work on this. Create a file like __mocks__/react-native-blob-util.js
:
import fetch from 'cross-fetch';
import fs from 'fs';
import fsPromises from 'fs/promises';
import fsPath from 'path';
import stream from 'stream';
const ensureDir = p => {
if (!fs.existsSync(p)) {
try {
fs.mkdirSync(p);
} catch (error) {
if (!fs.existsSync(tempPath)) {
throw error;
}
}
}
return p;
};
const deleteFolderRecursive = path => {
if (fs.existsSync(path)) {
if (fs.lstatSync(path).isDirectory()) {
fs.readdirSync(path).forEach(file => {
deleteFolderRecursive(fsPath.join(path, file));
});
fs.rmdirSync(path);
} else {
fs.unlinkSync(path);
}
}
};
deleteFolderRecursive(`/tmp/react-native-fs-mocks/${Date.now()}`);
const tempPath = ensureDir('/tmp/react-native-fs-mocks');
const cachePath = ensureDir(`${tempPath}/cache`);
const homePath = ensureDir(`${tempPath}/home`);
export default {
fs: {
exists: fs.existsSync,
unlink: deleteFolderRecursive,
readdir: fsPromises.readdir,
mkdir: fsPromises.mkdir,
appendFile: fsPromises.appendFile,
writeFile: fsPromises.writeFile,
readFile: fsPromises.readFile,
mv: fsPromises.mv,
stat: fsPromises.stat,
dirs: {
DocumentDir: homePath,
CacheDir: cachePath,
DownloadDir: fsPath.join(homePath, 'downloads'),
},
downloadFile: ({fromUrl, toFile, headers}) => ({
promise: (async () => {
const res = await fetch(fromUrl, {headers});
const data = {
statusCode: res.status,
url: res.url || fromUrl,
contentLength: res.headers['content-length'],
...res,
};
await new Promise((resolve, reject) =>
stream.pipeline(res.body, fs.createWriteStream(toFile), err =>
err ? reject(err) : resolve(),
),
);
return data;
})(),
}),
},
config: ({path, overwrite = true}) => {
let resolve;
let reject;
const task = new Promise((a, b) => {
resolve = a;
reject = b;
});
let onProgress;
let cancel = false;
task.fetch = (method, url, headers) => {
fetch(url, {method, headers})
.then(async res => {
const resHeaders = {};
for (const key of res.headers.keys()) {
resHeaders[key] = res.headers.get(key);
}
const info = {
...res,
statusCode: res.status,
status: res.status,
url: res.url || url,
headers: resHeaders,
contentLength: res.headers.get('content-length'),
};
let received = 0;
res.body.on('data', chunk => {
if (cancel) {
throw new Error('cancel');
}
received += chunk.length;
if (onProgress) {
onProgress(received, info.contentLength);
}
});
stream.pipeline(
res.body,
fs.createWriteStream(path, {flags: overwrite ? '' : 'a'}),
error => {
if (error) {
reject(error);
} else {
resolve({info: () => info});
}
},
);
})
.catch(reject);
return task;
};
task.progress = (options, fn) => {
onProgress = fn;
return task;
};
task.cancel = () => {
cancel = true;
};
return task;
},
};