diff --git a/package.json b/package.json index 5bc89e4..d104006 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "DIST_FOLDER": "../bitgesell-wallet-dist" }, "scripts": { - "test": "eslint 'src/*.js'", + "test": "eslint 'src/*.js' test/fetch_query_cleanup_test.js && node test/fetch_query_cleanup_test.js", "local-server": "IP=$npm_package_config_IP PORT=$npm_package_config_PORT node local-server.js", "start": "npm test && npm run local-server", "build": "./build.sh $npm_package_config_DIST_FOLDER", diff --git a/src/api.js b/src/api.js index 5709e1c..d7d4b11 100644 --- a/src/api.js +++ b/src/api.js @@ -28,6 +28,7 @@ const fetchQuery = (url, callback, fetchParams = null, errorFunc = null, callbac } }) .catch((error) => { + if (callbackAlways) callbackAlways(); if (error == 'TypeError: Failed to fetch') error += '

Maybe it is CORS! Check please manual here.'; Swal.fire({ showCloseButton: true, diff --git a/test/fetch_query_cleanup_test.js b/test/fetch_query_cleanup_test.js new file mode 100644 index 0000000..aac98a3 --- /dev/null +++ b/test/fetch_query_cleanup_test.js @@ -0,0 +1,83 @@ +const assert = require('assert'); +const fs = require('fs'); +const path = require('path'); +const vm = require('vm'); + +const source = fs.readFileSync(path.join(__dirname, '..', 'src', 'api.js'), 'utf8'); + +const createContext = (fetchImpl) => { + const swalCalls = []; + const context = { + fetch: fetchImpl, + Swal: { + fire: (params) => { + swalCalls.push(params); + }, + }, + }; + context.global = context; + vm.createContext(context); + vm.runInContext(`${source}\nthis.fetchQuery = fetchQuery;`, context); + context.swalCalls = swalCalls; + return context; +}; + +const flushPromises = () => new Promise((resolve) => setImmediate(resolve)); + +const testRejectedFetchRunsCleanupBeforeAlert = async () => { + const events = []; + // Matches the string branch already used by the wallet's CORS-help path. + // eslint-disable-next-line prefer-promise-reject-errors + const context = createContext(() => Promise.reject('TypeError: Failed to fetch')); + + context.Swal.fire = (params) => { + events.push('alert'); + context.swalCalls.push(params); + }; + + context.fetchQuery( + 'https://node.example/rpc', + () => events.push('success'), + null, + null, + () => events.push('cleanup'), + ); + + await flushPromises(); + await flushPromises(); + + assert.deepStrictEqual(events, [ 'cleanup', 'alert' ]); + assert.strictEqual(context.swalCalls.length, 1); + assert.match(context.swalCalls[0].html, /Maybe it is CORS!/); +}; + +const testErrorResponseRunsCleanupOnce = async () => { + let cleanupCount = 0; + const context = createContext(() => Promise.resolve({ + json: () => Promise.resolve({ error: { message: 'bad tx' } }), + })); + + context.fetchQuery( + 'https://node.example/rpc', + () => assert.fail('success callback should not run for error response'), + null, + () => ({ message: 'mapped error' }), + () => { cleanupCount++; }, + ); + + await flushPromises(); + await flushPromises(); + + assert.strictEqual(cleanupCount, 1); + assert.strictEqual(context.swalCalls.length, 1); + assert.strictEqual(context.swalCalls[0].html, 'mapped error'); +}; + +(async () => { + await testRejectedFetchRunsCleanupBeforeAlert(); + await testErrorResponseRunsCleanupOnce(); + console.log('fetchQuery cleanup tests passed'); +})().catch((error) => { + console.error(error); + process.exit(1); +});