Skip to content

Commit

Permalink
draft (#133)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jacob Bogers authored Feb 21, 2024
1 parent 282fef5 commit af6be1e
Show file tree
Hide file tree
Showing 6 changed files with 109 additions and 20 deletions.
22 changes: 15 additions & 7 deletions i18nextHttpBackend.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return _typeof(key) === "symbol" ? key : String(key); }
function _toPrimitive(input, hint) { if (_typeof(input) !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (_typeof(res) !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : String(i); }
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
var getDefaults = function getDefaults() {
return {
loadPath: '/locales/{{lng}}/{{ns}}.json',
Expand Down Expand Up @@ -227,8 +227,8 @@ function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e;
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return _typeof(key) === "symbol" ? key : String(key); }
function _toPrimitive(input, hint) { if (_typeof(input) !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (_typeof(res) !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : String(i); }
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
var fetchApi;
if (typeof fetch === 'function') {
Expand Down Expand Up @@ -269,7 +269,7 @@ var addQueryString = function addQueryString(url, params) {
}
return url;
};
var fetchIt = function fetchIt(url, fetchOptions, callback) {
var fetchIt = function fetchIt(url, fetchOptions, callback, altFetch) {
var resolver = function resolver(response) {
if (!response.ok) return callback(response.statusText || 'Error', {
status: response.status
Expand All @@ -281,6 +281,13 @@ var fetchIt = function fetchIt(url, fetchOptions, callback) {
});
}).catch(callback);
};
if (altFetch) {
var altResponse = altFetch(url, fetchOptions);
if (altResponse instanceof Promise) {
altResponse.then(resolver).catch(callback);
return;
}
}
if (typeof fetch === 'function') {
fetch(url, fetchOptions).then(resolver).catch(callback);
} else {
Expand All @@ -303,8 +310,9 @@ var requestWithFetch = function requestWithFetch(options, url, payload, callback
body: payload ? options.stringify(payload) : undefined,
headers: headers
}, omitFetchOptions ? {} : reqOptions);
var altFetch = typeof options.alternateFetch === 'function' && options.alternateFetch.length >= 1 ? options.alternateFetch : undefined;
try {
fetchIt(url, fetchOptions, callback);
fetchIt(url, fetchOptions, callback, altFetch);
} catch (e) {
if (!reqOptions || Object.keys(reqOptions).length === 0 || !e.message || e.message.indexOf('not implemented') < 0) {
return callback(e);
Expand All @@ -313,7 +321,7 @@ var requestWithFetch = function requestWithFetch(options, url, payload, callback
Object.keys(reqOptions).forEach(function (opt) {
delete fetchOptions[opt];
});
fetchIt(url, fetchOptions, callback);
fetchIt(url, fetchOptions, callback, altFetch);
omitFetchOptions = true;
} catch (err) {
callback(err);
Expand Down
2 changes: 1 addition & 1 deletion i18nextHttpBackend.min.js

Large diffs are not rendered by default.

14 changes: 14 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,21 @@ type AddPathOption =
| string
| ((lng: string, namespace: string) => string);

type FetchFunction = (input: string, init: RequestInit) => Promise<Response> | void

export interface HttpBackendOptions {
/**
* Use an alternative fetch function that acts like an interecept, (usefull for low level mocks/simulations)
*
* This option is not called if:
*
* 1. There is an custom value set for the "request" property in this options object.
* 2. The backend selected xmlHttpRequest over fetch
*
* If the function is called and it returns anything BUT a promise the fetch or xmlHttpRequest will be subsequentially called
*
*/
alternateFetch?: FetchFunction;
/**
* path where resources get loaded from, or a function
* returning a path:
Expand Down
16 changes: 13 additions & 3 deletions lib/request.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,22 @@ const addQueryString = (url, params) => {
return url
}

const fetchIt = (url, fetchOptions, callback) => {
const fetchIt = (url, fetchOptions, callback, altFetch) => {
const resolver = (response) => {
if (!response.ok) return callback(response.statusText || 'Error', { status: response.status })
response.text().then((data) => {
callback(null, { status: response.status, data })
}).catch(callback)
}
if (altFetch) {
// already checked to have the proper signature
const altResponse = altFetch(url, fetchOptions)
if (altResponse instanceof Promise) {
altResponse.then(resolver).catch(callback)
return
}
// fall through
}
if (typeof fetch === 'function') { // react-native debug mode needs the fetch function to be called directly (no alias)
fetch(url, fetchOptions).then(resolver).catch(callback)
} else {
Expand Down Expand Up @@ -78,8 +87,9 @@ const requestWithFetch = (options, url, payload, callback) => {
headers,
...(omitFetchOptions ? {} : reqOptions)
}
const altFetch = typeof options.alternateFetch === 'function' && options.alternateFetch.length >= 1 ? options.alternateFetch : undefined
try {
fetchIt(url, fetchOptions, callback)
fetchIt(url, fetchOptions, callback, altFetch)
} catch (e) {
if (!reqOptions || Object.keys(reqOptions).length === 0 || !e.message || e.message.indexOf('not implemented') < 0) {
return callback(e)
Expand All @@ -88,7 +98,7 @@ const requestWithFetch = (options, url, payload, callback) => {
Object.keys(reqOptions).forEach((opt) => {
delete fetchOptions[opt]
})
fetchIt(url, fetchOptions, callback)
fetchIt(url, fetchOptions, callback, altFetch)
omitFetchOptions = true
} catch (err) {
callback(err)
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@
"compile": "npm run compile:esm && npm run compile:cjs",
"browser": "browserify --ignore cross-fetch --standalone i18nextHttpBackend cjs/index.js -o i18nextHttpBackend.js && uglifyjs i18nextHttpBackend.js --compress --mangle -o i18nextHttpBackend.min.js",
"build": "npm run compile && npm run browser",
"test:xmlhttpreq": "mocha test -R spec --require test/fixtures/xmlHttpRequest.cjs --exit --experimental-modules",
"test:xmlhttpreq": "mocha test -R spec --require test/fixtures/xmlHttpRequest.cjs --exit --experimental-modules",
"test:fetch": "mocha test -R spec --exit --experimental-modules",
"test": "npm run lint && npm run build && npm run test:fetch && npm run test:xmlhttpreq && npm run test:typescript",
"test:typescript": "tslint --project tsconfig.json && tsd",
Expand Down
73 changes: 65 additions & 8 deletions test/http.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,82 @@ describe(`http backend using ${hasXMLHttpRequest() ? 'XMLHttpRequest' : 'fetch'}

describe('#read', () => {
let backend
const logs = []

before(() => {
before(function () {
logs.splice(0)
backend = new Http(
{
interpolator: i18next.services.interpolator
},
{
loadPath: 'http://localhost:5001/locales/{{lng}}/{{ns}}'
loadPath: 'http://localhost:5001/locales/{{lng}}/{{ns}}',
alternateFetch: (url, requestInit) => {
logs.push([url, requestInit])
// not returning a promise olding actual data makes this a spy
return undefined
}
}
)
})
if (!hasXMLHttpRequest()) {
it('should load data', async () => {
let errO
let dataO
const done = await new Promise((resolve, reject) => {
backend.read('en', 'test', (err, data) => {
// dont check here with "except", if there is an error
// because the test will just "hang" with no further info
errO = err
dataO = data
resolve(true)
setTimeout(() => reject(new Error('timeout')), 1500)
})
})
// evaluate outside callback to get actuall error when something is wrong
expect(errO).to.be(null)
expect(dataO).to.eql({ key: 'passing' })
expect(done).to.be(true)
expect(logs).to.eql([
[
'http://localhost:5001/locales/en/test',
{
method: 'GET',
headers: {
'User-Agent': 'i18next-http-backend (node/v20.8.0; linux x64)'
},
mode: 'cors',
credentials: 'same-origin',
cache: 'default',
body: undefined
}
]
])
})
}

it('should load data', (done) => {
backend.read('en', 'test', (err, data) => {
expect(err).not.to.be.ok()
expect(data).to.eql({ key: 'passing' })
done()
if (hasXMLHttpRequest()) {
it('should load data', async () => {
let errO
let dataO
const done = await new Promise((resolve, reject) => {
backend.read('en', 'test', (err, data) => {
// dont check here with "except", if there is an error
// because the test will just "hang" with no further info
errO = err
dataO = data
resolve(true)
setTimeout(() => reject(new Error('timeout')), 1500)
})
})
// evaluate outside callback to get actuall error when something is wrong
expect(errO).to.be(null)
expect(dataO).to.eql({ key: 'passing' })
expect(done).to.be(true)
// fetch was not used
expect(logs).to.eql([])
})
})
}

it('should throw error on not existing file', (done) => {
backend.read('en', 'notexisting', (err, data) => {
Expand Down

0 comments on commit af6be1e

Please sign in to comment.