redux-api-middleware
Advanced tools
Comparing version 0.5.0 to 0.6.0
214
lib/index.js
@@ -7,19 +7,6 @@ /** | ||
* @exports {Symbol} CALL_API | ||
* @exports {function} isRSAA | ||
* @exports {ReduxMiddleWare} apiMiddleware | ||
*/ | ||
/** | ||
* @typedef {function} ReduxMiddleware | ||
* @param {Object} store | ||
* @returns {ReduxNextHandler} | ||
* | ||
* @typedef {function} ReduxNextHandler | ||
* @param {function} next | ||
* @returns {ReduxActionHandler} | ||
* | ||
* @typedef {function} ReduxActionHandler | ||
* @param {object} action | ||
* @returns undefined | ||
*/ | ||
'use strict'; | ||
@@ -29,198 +16,23 @@ | ||
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; | ||
exports.isRSAA = isRSAA; | ||
exports.apiMiddleware = apiMiddleware; | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } | ||
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } | ||
var _CALL_API = require('./CALL_API'); | ||
function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } | ||
var _CALL_API2 = _interopRequireDefault(_CALL_API); | ||
var _normalizr = require('normalizr'); | ||
var _validateRSAA = require('./validateRSAA'); | ||
var _isomorphicFetch = require('isomorphic-fetch'); | ||
var _validateRSAA2 = _interopRequireDefault(_validateRSAA); | ||
var _isomorphicFetch2 = _interopRequireDefault(_isomorphicFetch); | ||
var _isRSAA = require('./isRSAA'); | ||
var _lodashIsplainobject = require('lodash.isplainobject'); | ||
var _isRSAA2 = _interopRequireDefault(_isRSAA); | ||
var _lodashIsplainobject2 = _interopRequireDefault(_lodashIsplainobject); | ||
var _apiMiddleware = require('./apiMiddleware'); | ||
/** | ||
* Error class for an API response outside the 200 range | ||
* | ||
* @class ApiError | ||
* @access private | ||
* @param {number} status - the status code of the API response | ||
* @param {string} statusText - the status text of the API response | ||
* @param {Object} response - the JSON response of the API server if the 'Content-Type' | ||
* header signals a JSON response, or the raw response object otherwise | ||
*/ | ||
var _apiMiddleware2 = _interopRequireDefault(_apiMiddleware); | ||
var ApiError = (function (_Error) { | ||
_inherits(ApiError, _Error); | ||
function ApiError(status, statusText, response) { | ||
_classCallCheck(this, ApiError); | ||
_Error.call(this); | ||
this.name = 'ApiError'; | ||
this.status = status; | ||
this.statusText = statusText; | ||
this.message = status + ' - ' + statusText; | ||
this.response = response; | ||
} | ||
/** | ||
* Fetches an API response and normalizes the resulting JSON according to schema. | ||
* | ||
* @function callApi | ||
* @access private | ||
* @param {string} endpoint - The URL endpoint for the request | ||
* @param {string} method - The HTTP method for the request | ||
* @param {boolean} [auth=false] - Whether to send authentication credentials or not | ||
* @param {Object} [body] - The body of the request | ||
* @param {Schema} [schema] - The normalizr schema with which to parse the response | ||
* @returns {Promise} | ||
*/ | ||
return ApiError; | ||
})(Error); | ||
function callApi(endpoint, method, headers, body, schema) { | ||
var requestOptions = { method: method, body: body, headers: headers }; | ||
return _isomorphicFetch2['default'](endpoint, requestOptions).then(function (response) { | ||
if (response.ok) { | ||
return Promise.resolve(response); | ||
} else { | ||
return Promise.reject(response); | ||
} | ||
}).then(function (response) { | ||
var contentType = response.headers.get('Content-Type'); | ||
if (contentType && ~contentType.indexOf('json')) { | ||
return response.json().then(function (json) { | ||
if (schema) { | ||
return Promise.resolve(_normalizr.normalize(json, schema)); | ||
} else { | ||
return Promise.resolve(json); | ||
} | ||
}); | ||
} else { | ||
return Promise.resolve(); | ||
} | ||
}, function (response) { | ||
var contentType = response.headers.get('Content-Type'); | ||
if (contentType && ~contentType.indexOf('json')) { | ||
return response.json().then(function (json) { | ||
return Promise.reject(new ApiError(response.status, response.statusText, json)); | ||
}); | ||
} else { | ||
return Promise.reject(new ApiError(response.status, response.statusText, response)); | ||
} | ||
}); | ||
} | ||
/** | ||
* Symbol key that carries API call info interpreted by this Redux middleware. | ||
* | ||
* @constant {Symbol} | ||
* @access public | ||
* @default | ||
*/ | ||
var CALL_API = Symbol('Call API'); | ||
exports.CALL_API = CALL_API; | ||
/** | ||
* Is the given action a Redux Standard API-calling action? | ||
* | ||
* @function isRSAA | ||
* @access public | ||
* @param {Object} action - The action to check against the RSAA definition. | ||
* @returns {boolean} | ||
*/ | ||
function isRSAA(action) { | ||
var validRootKeys = [[CALL_API], 'payload', 'meta']; | ||
var validCallAPIKeys = ['endpoint', 'method', 'types', 'body', 'headers', 'schema', 'bailout']; | ||
var validMethods = ['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS']; | ||
var callAPI = action[CALL_API]; | ||
if (!_lodashIsplainobject2['default'](action) || typeof callAPI === 'undefined') { | ||
return false; | ||
} | ||
var endpoint = callAPI.endpoint; | ||
var method = callAPI.method; | ||
var body = callAPI.body; | ||
var headers = callAPI.headers; | ||
var schema = callAPI.schema; | ||
var types = callAPI.types; | ||
var bailout = callAPI.bailout; | ||
return Object.keys(action).every(function (key) { | ||
return ~validRootKeys.indexOf(key); | ||
}) && _lodashIsplainobject2['default'](callAPI) && Object.keys(callAPI).every(function (key) { | ||
return ~validCallAPIKeys.indexOf(key); | ||
}) && (typeof endpoint === 'string' || typeof endpoint === 'function') && ~validMethods.indexOf(method.toUpperCase()) && (Array.isArray(types) && types.length === 3) && (typeof headers === 'undefined' || _lodashIsplainobject2['default'](headers)) && (typeof schema === 'undefined' || schema instanceof _normalizr.Schema || schema.hasOwnProperty('_itemSchema')) && (typeof bailout === 'undefined' || typeof bailout === 'boolean' || typeof bailout === 'function'); | ||
} | ||
/** | ||
* A Redux middleware that interprets actions with CALL_API info specified. | ||
* Performs the call and promises when such actions are dispatched. | ||
* | ||
* @type {ReduxMiddleware} | ||
* @access public | ||
*/ | ||
function apiMiddleware(_ref) { | ||
var getState = _ref.getState; | ||
return function (next) { | ||
return function (action) { | ||
var callAPI = action[CALL_API]; | ||
if (!isRSAA(action)) { | ||
return next(action); | ||
} | ||
var endpoint = callAPI.endpoint; | ||
var method = callAPI.method; | ||
var body = callAPI.body; | ||
var headers = callAPI.headers; | ||
var schema = callAPI.schema; | ||
var types = callAPI.types; | ||
var bailout = callAPI.bailout; | ||
if (typeof endpoint === 'function') { | ||
endpoint = endpoint(getState()); | ||
} | ||
if (typeof bailout === 'boolean' && bailout || typeof bailout === 'function' && bailout(getState())) { | ||
return Promise.resolve('Bailing out'); | ||
} | ||
function actionWith(data, payload) { | ||
var finalPayload = _extends({}, action.payload, payload); | ||
var finalAction = _extends({}, action, { payload: finalPayload }, data); | ||
delete finalAction[CALL_API]; | ||
return finalAction; | ||
} | ||
var requestType = types[0]; | ||
var successType = types[1]; | ||
var failureType = types[2]; | ||
next(actionWith({ type: requestType })); | ||
return callApi(endpoint, method, headers, body, schema).then(function (response) { | ||
return next(actionWith({ type: successType }, response)); | ||
}, function (error) { | ||
return next(actionWith({ | ||
type: failureType, | ||
payload: error, | ||
error: true | ||
})); | ||
}); | ||
}; | ||
}; | ||
} | ||
exports.CALL_API = _CALL_API2['default']; | ||
exports.validateRSAA = _validateRSAA2['default']; | ||
exports.isRSAA = _isRSAA2['default']; | ||
exports.apiMiddleware = _apiMiddleware2['default']; |
{ | ||
"name": "redux-api-middleware", | ||
"version": "0.5.0", | ||
"version": "0.6.0", | ||
"description": "Redux middleware for calling an API.", | ||
@@ -30,15 +30,15 @@ "main": "lib/index.js", | ||
"isomorphic-fetch": "^2.1.1", | ||
"lodash.isplainobject": "^3.2.0", | ||
"normalizr": "^1.0.0" | ||
"lodash.isplainobject": "^3.2.0" | ||
}, | ||
"devDependencies": { | ||
"babel": "^5.8.21", | ||
"babel-istanbul": "^0.3.17", | ||
"babel": "^5.8.23", | ||
"babel-istanbul": "^0.3.20", | ||
"coveralls": "^2.11.4", | ||
"eslint": "^1.1.0", | ||
"nock": "^2.10.0", | ||
"rimraf": "^2.4.2", | ||
"tap-spec": "^4.0.2", | ||
"eslint": "^1.5.1", | ||
"nock": "^2.13.0", | ||
"normalizr": "^1.0.0", | ||
"rimraf": "^2.4.3", | ||
"tap-spec": "^4.1.0", | ||
"tape": "^4.2.0" | ||
} | ||
} |
@@ -82,3 +82,3 @@ redux-api-middleware | ||
- have a `headers` property, | ||
- have a `schema` property, | ||
- have a `transform` property, | ||
- have a `bailout` property. | ||
@@ -88,3 +88,3 @@ | ||
- include properties other than `endpoint`, `method`, `types`, `body`, `headers`, `schema` and `bailout`. | ||
- include properties other than `endpoint`, `method`, `types`, `body`, `headers`, `transform` and `bailout`. | ||
@@ -97,3 +97,3 @@ ### `[CALL_API].endpoint` | ||
The `[CALL_API].method` property MUST be one of the strings `GET`, `HEAD`, `POST`, `PUT`, `PATCH`, `DELETE` or `OPTIONS (in any mixture of lowercase and uppercase letters). It represents the HTTP method for the API request. | ||
The `[CALL_API].method` property MUST be one of the strings `GET`, `HEAD`, `POST`, `PUT`, `PATCH`, `DELETE` or `OPTIONS` (in any mixture of lowercase and uppercase letters). It represents the HTTP method for the API request. | ||
@@ -112,5 +112,5 @@ ### `[CALL_API].types` | ||
### `[CALL_API].schema` | ||
### `[CALL_API].transform` | ||
The optional `[CALL_API].schema` property MUST be a [`normalizr`](https://www.npmjs.com/package/normalizr) schema, or an `arrayOf` thereof. It specifies with which `normalizr` schema we should process the API response | ||
The optional `[CALL_API].transform` property MUST be a function. It is used to process the JSON response of a successful API request. | ||
@@ -137,3 +137,3 @@ ### `[CALL_API].bailout` | ||
- If the request is successful, an FSA with the `SUCCESS` type is dispatched to the next middleware. | ||
- The `payload` property of this FSA is a merge of the original RSAA's `payload` property and the JSON response from the server. | ||
- The `payload` property of this FSA is a merge of the original RSAA's `payload` property and the JSON response from the server (optionally processed by `[CALL_API].transform`). | ||
- The `meta` property of this FSA is that of the original RSAA. | ||
@@ -157,3 +157,3 @@ - If the request is unsuccessful, an FSA with the `FAILURE` type is dispatched to the next middleware. | ||
import { CALL_API } from 'redux-api-middleware'; | ||
import { Schema } from 'normalizr'; | ||
import { Schema, normalize } from 'normalizr'; | ||
@@ -169,3 +169,3 @@ const userSchema = new Schema({...}); | ||
headers: { credentials: 'same-origin'}, | ||
schema | ||
transform: (response) => normalize(json, schema) | ||
}, | ||
@@ -215,3 +215,3 @@ payload: { somePayload }, | ||
type: 'FETCH_USER.SUCCESS', | ||
payload: { ...somePayload, response }, | ||
payload: { ...somePayload, transformedResponse }, | ||
meta: { someMeta } | ||
@@ -232,7 +232,11 @@ } | ||
Apart from the middleware above, `redux-api-middleware` exposes the following utility function. | ||
Apart from the middleware above (and the `[CALL_API]` Symbol), `redux-api-middleware` exposes the following utility functions. | ||
### validateRSAA(action) | ||
Validates `action` against the RSAA definition, returning an array of validation errors. | ||
### isRSAA(action) | ||
Returns `true` if `action` is RSAA-compliant. | ||
Returns `true` if `action` is RSAA-compliant, and `false` otherwise. Internally, it simply checks the length of the array of validation errors returned by `validateRSAA(action)`. | ||
@@ -251,2 +255,27 @@ ## Installation | ||
## Upgrading from <0.6.0 | ||
In previous versions, `[CALL_API]` had an optional `schema` key that would use a [`normalizr`](https://www.npmjs.com/package/normalizr) schema (or an `arrayOf` thereof) to process the JSON response of a successful API request. This has been now replaced with the optional `transform` key, so you can do any post-processing you like. | ||
Upgrading is easy: just rewrite your RSAA-actions | ||
```js | ||
{ | ||
[CALL_API]: { | ||
... | ||
schema = someSchema, | ||
... | ||
} | ||
} | ||
``` | ||
as follows: | ||
```js | ||
{ | ||
[CALL_API]: { | ||
... | ||
transform = (response) => normalize(response, someSchema), | ||
... | ||
} | ||
``` | ||
(and remember to import `normalizr`). | ||
## License | ||
@@ -253,0 +282,0 @@ |
196
src/index.js
@@ -7,196 +7,12 @@ /** | ||
* @exports {Symbol} CALL_API | ||
* @exports {function} isRSAA | ||
* @exports {ReduxMiddleWare} apiMiddleware | ||
*/ | ||
/** | ||
* @typedef {function} ReduxMiddleware | ||
* @param {Object} store | ||
* @returns {ReduxNextHandler} | ||
* | ||
* @typedef {function} ReduxNextHandler | ||
* @param {function} next | ||
* @returns {ReduxActionHandler} | ||
* | ||
* @typedef {function} ReduxActionHandler | ||
* @param {object} action | ||
* @returns undefined | ||
*/ | ||
import { normalize, Schema } from 'normalizr'; | ||
import fetch from 'isomorphic-fetch'; | ||
import isPlainObject from 'lodash.isplainobject'; | ||
import CALL_API from './CALL_API'; | ||
import validateRSAA from './validateRSAA'; | ||
import isRSAA from './isRSAA'; | ||
import apiMiddleware from './apiMiddleware'; | ||
/** | ||
* Error class for an API response outside the 200 range | ||
* | ||
* @class ApiError | ||
* @access private | ||
* @param {number} status - the status code of the API response | ||
* @param {string} statusText - the status text of the API response | ||
* @param {Object} response - the JSON response of the API server if the 'Content-Type' | ||
* header signals a JSON response, or the raw response object otherwise | ||
*/ | ||
class ApiError extends Error { | ||
constructor(status, statusText, response) { | ||
super(); | ||
this.name = 'ApiError'; | ||
this.status = status; | ||
this.statusText = statusText; | ||
this.message = `${status} - ${statusText}`; | ||
this.response = response; | ||
} | ||
} | ||
/** | ||
* Fetches an API response and normalizes the resulting JSON according to schema. | ||
* | ||
* @function callApi | ||
* @access private | ||
* @param {string} endpoint - The URL endpoint for the request | ||
* @param {string} method - The HTTP method for the request | ||
* @param {boolean} [auth=false] - Whether to send authentication credentials or not | ||
* @param {Object} [body] - The body of the request | ||
* @param {Schema} [schema] - The normalizr schema with which to parse the response | ||
* @returns {Promise} | ||
*/ | ||
function callApi(endpoint, method, headers, body, schema) { | ||
const requestOptions = { method, body, headers } | ||
return fetch(endpoint, requestOptions) | ||
.then((response) => { | ||
if (response.ok) { | ||
return Promise.resolve(response); | ||
} else { | ||
return Promise.reject(response); | ||
} | ||
}) | ||
.then((response) => { | ||
const contentType = response.headers.get('Content-Type'); | ||
if (contentType && ~contentType.indexOf('json')) { | ||
return response.json().then((json) => { | ||
if (schema) { | ||
return Promise.resolve(normalize(json, schema)); | ||
} else { | ||
return Promise.resolve(json); | ||
} | ||
}); | ||
} else { | ||
return Promise.resolve(); | ||
} | ||
}, | ||
(response) => { | ||
const contentType = response.headers.get('Content-Type'); | ||
if (contentType && ~contentType.indexOf('json')) { | ||
return response.json().then((json) => { | ||
return Promise.reject(new ApiError(response.status, response.statusText, json)); | ||
}); | ||
} else { | ||
return Promise.reject(new ApiError(response.status, response.statusText, response)); | ||
} | ||
}); | ||
} | ||
/** | ||
* Symbol key that carries API call info interpreted by this Redux middleware. | ||
* | ||
* @constant {Symbol} | ||
* @access public | ||
* @default | ||
*/ | ||
export const CALL_API = Symbol('Call API'); | ||
/** | ||
* Is the given action a Redux Standard API-calling action? | ||
* | ||
* @function isRSAA | ||
* @access public | ||
* @param {Object} action - The action to check against the RSAA definition. | ||
* @returns {boolean} | ||
*/ | ||
export function isRSAA(action) { | ||
const validRootKeys = [ | ||
[CALL_API], | ||
'payload', | ||
'meta' | ||
]; | ||
const validCallAPIKeys = [ | ||
'endpoint', | ||
'method', | ||
'types', | ||
'body', | ||
'headers', | ||
'schema', | ||
'bailout' | ||
]; | ||
const validMethods = [ | ||
'GET', | ||
'HEAD', | ||
'POST', | ||
'PUT', | ||
'PATCH', | ||
'DELETE', | ||
'OPTIONS' | ||
] | ||
const callAPI = action[CALL_API]; | ||
if (!isPlainObject(action) || typeof callAPI === 'undefined') { | ||
return false; | ||
} | ||
const { endpoint, method, body, headers, schema, types, bailout } = callAPI; | ||
return Object.keys(action).every((key) => ~validRootKeys.indexOf(key)) && | ||
isPlainObject(callAPI) && | ||
Object.keys(callAPI).every((key) => ~validCallAPIKeys.indexOf(key)) && | ||
(typeof endpoint === 'string' || typeof endpoint === 'function') && | ||
~validMethods.indexOf(method.toUpperCase()) && | ||
(Array.isArray(types) && types.length === 3) && | ||
(typeof headers === 'undefined' || isPlainObject(headers)) && | ||
(typeof schema === 'undefined' || schema instanceof Schema || schema.hasOwnProperty('_itemSchema')) && | ||
(typeof bailout === 'undefined' || typeof bailout === 'boolean' || typeof bailout === 'function'); | ||
} | ||
/** | ||
* A Redux middleware that interprets actions with CALL_API info specified. | ||
* Performs the call and promises when such actions are dispatched. | ||
* | ||
* @type {ReduxMiddleware} | ||
* @access public | ||
*/ | ||
export function apiMiddleware({ getState }) { | ||
return (next) => (action) => { | ||
const callAPI = action[CALL_API]; | ||
if (!isRSAA(action)) { | ||
return next(action); | ||
} | ||
let { endpoint } = callAPI; | ||
const { method, body, headers, schema, types, bailout } = callAPI; | ||
if (typeof endpoint === 'function') { | ||
endpoint = endpoint(getState()); | ||
} | ||
if ((typeof bailout === 'boolean' && bailout) || | ||
(typeof bailout === 'function' && bailout(getState()))) { | ||
return Promise.resolve('Bailing out'); | ||
} | ||
function actionWith(data, payload) { | ||
const finalPayload = { ...action.payload, ...payload }; | ||
const finalAction = { ...action, payload: finalPayload, ...data }; | ||
delete finalAction[CALL_API]; | ||
return finalAction; | ||
} | ||
const [requestType, successType, failureType] = types; | ||
next(actionWith({ type: requestType })); | ||
return callApi(endpoint, method, headers, body, schema).then( | ||
(response) => next(actionWith({ type: successType }, response)), | ||
(error) => next(actionWith({ | ||
type: failureType, | ||
payload: error, | ||
error: true | ||
})) | ||
); | ||
}; | ||
} | ||
export { CALL_API, validateRSAA, isRSAA, apiMiddleware }; |
import test from 'tape'; | ||
import { Schema, arrayOf } from 'normalizr'; | ||
import { Schema, normalize, arrayOf } from 'normalizr'; | ||
import nock from 'nock'; | ||
import { CALL_API, apiMiddleware, isRSAA } from '../src'; | ||
import { CALL_API, apiMiddleware, validateRSAA, isRSAA } from '../src'; | ||
test('isRSAA must identify RSAA-compliant actions', function (t) { | ||
t.notOk(isRSAA(''), 'RSAA actions must be plain JavaScript objects'); | ||
t.notOk(isRSAA({}), 'RSAA actions must have an [API_CALL] property'); | ||
t.notOk(isRSAA({ invalidKey: '' }), 'RSAA actions must not have properties other than [API_CALL], payload and meta'); | ||
t.notOk(isRSAA({ | ||
test('validateRSAA/isRSAA must identify RSAA-compliant actions', function (t) { | ||
var action1 = ''; | ||
t.ok( | ||
validateRSAA(action1).length === 1 && | ||
validateRSAA(action1).includes('RSAA must be a plain JavaScript object'), | ||
'RSAA actions must be plain JavaScript objects (validateRSAA)' | ||
); | ||
t.notOk( | ||
isRSAA(action1), | ||
'RSAA actions must be plain JavaScript objects (isRSAA)' | ||
); | ||
var action2 = {}; | ||
t.ok( | ||
validateRSAA(action2).length === 1 && | ||
validateRSAA(action2).includes('Missing [CALL_API] key'), | ||
'RSAA actions must have a [CALL_API] property (validateRSAA)' | ||
); | ||
t.notOk( | ||
isRSAA(action2), | ||
'RSAA actions must have a [CALL_API] property (isRSAA)' | ||
); | ||
var action3 = { | ||
[CALL_API]: {}, | ||
invalidKey: '' | ||
}; | ||
t.ok( | ||
validateRSAA(action3).length === 1 && | ||
validateRSAA(action3).includes('Invalid root key: invalidKey'), | ||
'RSAA actions must not have properties other than [CALL_API], payload and meta (validateRSAA)' | ||
); | ||
t.notOk( | ||
isRSAA(action3), | ||
'RSAA actions must not have properties other than [CALL_API], payload and meta (isRSAA)' | ||
); | ||
var action4 = { | ||
[CALL_API]: '' | ||
}), '[CALL_API] must be a plain JavaScript object'); | ||
t.notOk(isRSAA({ | ||
}; | ||
t.ok( | ||
validateRSAA(action4).length === 1 && | ||
validateRSAA(action4).includes('[CALL_API] property must be a plain JavaScript object'), | ||
'[CALL_API] must be a plain JavaScript object (validateRSAA)' | ||
); | ||
t.notOk( | ||
isRSAA(action4), | ||
'[CALL_API] must be a plain JavaScript object (isRSAA)' | ||
); | ||
var action5 = { | ||
[CALL_API]: { invalidKey: '' } | ||
}), '[CALL_API] must not have properties other than endpoint, method, body, headers, schema, types and bailout'); | ||
t.notOk(isRSAA({ | ||
}; | ||
t.ok( | ||
validateRSAA(action5).length === 1 && | ||
validateRSAA(action5).includes('Invalid [CALL_API] key: invalidKey'), | ||
'[CALL_API] must not have properties other than endpoint, method, body, headers, schema, types and bailout (validateRSAA)' | ||
); | ||
t.notOk( | ||
isRSAA(action5), | ||
'[CALL_API] must not have properties other than endpoint, method, body, headers, schema, types and bailout (isRSAA)' | ||
); | ||
var action6 = { | ||
[CALL_API]: {} | ||
}), '[CALL_API] must have an endpoint property'); | ||
t.notOk(isRSAA({ | ||
}; | ||
t.ok( | ||
validateRSAA(action6).length === 3 && | ||
validateRSAA(action6).includes( | ||
'[CALL_API].endpoint property must be a string or a function', | ||
'[CALL_API].method property must be a string', | ||
'[CALL_API].types property must be an array of length 3' | ||
), | ||
'[CALL_API] must have endpoint, method and types properties (validateRSAA)' | ||
); | ||
t.notOk( | ||
isRSAA(action6), | ||
'[CALL_API] must have endpoint, method and types properties (isRSAA)' | ||
); | ||
var action7 = { | ||
[CALL_API]: { | ||
endpoint: {} | ||
endpoint: {}, | ||
method: 'GET', | ||
types: ['REQUEST', 'SUCCESS', 'FAILURE'] | ||
} | ||
}), '[CALL_API].endpoint must be a string or a function'); | ||
t.notOk(isRSAA({ | ||
}; | ||
t.ok( | ||
validateRSAA(action7).includes('[CALL_API].endpoint property must be a string or a function'), | ||
'[CALL_API].endpoint must be a string or a function (validateRSAA)' | ||
); | ||
t.notOk( | ||
isRSAA(action7), | ||
'[CALL_API].endpoint must be a string or a function (isRSAA)' | ||
); | ||
var action8 = { | ||
[CALL_API]: { | ||
endpoint: '', | ||
method: '' | ||
method: 'InvalidMethod', | ||
types: ['REQUEST', 'SUCCESS', 'FAILURE'] | ||
} | ||
}), '[CALL_API].method must be one of the strings \'GET\', \'HEAD\', \'POST\', \'PUT\', \'PATCH\' \'DELETE\' or \'OPTIONS\''); | ||
t.notOk(isRSAA({ | ||
}; | ||
t.ok( | ||
validateRSAA(action8).includes('Invalid [CALL_API].method: INVALIDMETHOD'), | ||
'[CALL_API].method must be one of the strings \'GET\', \'HEAD\', \'POST\', \'PUT\', \'PATCH\' \'DELETE\' or \'OPTIONS\' (validateRSAA)' | ||
); | ||
t.notOk( | ||
isRSAA(action8), | ||
'[CALL_API].method must be one of the strings \'GET\', \'HEAD\', \'POST\', \'PUT\', \'PATCH\' \'DELETE\' or \'OPTIONS\' (isRSAA)' | ||
); | ||
var action9 = { | ||
[CALL_API]: { | ||
endpoint: '', | ||
method: 'GET', | ||
types: '' | ||
types: {} | ||
} | ||
}), '[CALL_API].types must be an array'); | ||
t.notOk(isRSAA({ | ||
}; | ||
t.ok( | ||
validateRSAA(action9).includes('[CALL_API].types property must be an array of length 3'), | ||
'[CALL_API].types must be an array (validateRSAA)' | ||
); | ||
t.notOk( | ||
isRSAA(action9), | ||
'[CALL_API].types must be an array (isRSAA)' | ||
); | ||
var action10 = { | ||
[CALL_API]: { | ||
endpoint: '', | ||
method: 'GET', | ||
types: ['', ''] | ||
types: ['a', 'b'] | ||
} | ||
}), '[CALL_API].types must have length 3'); | ||
t.notOk(isRSAA({ | ||
}; | ||
t.ok( | ||
validateRSAA(action10).includes('[CALL_API].types property must be an array of length 3'), | ||
'[CALL_API].types must have length 3 (validateRSAA)' | ||
); | ||
t.notOk( | ||
isRSAA(action10), | ||
'[CALL_API].types must have length 3 (isRSAA)' | ||
); | ||
var action11 = { | ||
[CALL_API]: { | ||
@@ -50,7 +156,15 @@ endpoint: '', | ||
types: ['REQUEST', 'SUCCESS', 'FAILURE'], | ||
body: {}, | ||
headers: '' | ||
} | ||
}), '[CALL_API].headers must be a plain JavaScript object'); | ||
t.notOk(isRSAA({ | ||
}; | ||
t.ok( | ||
validateRSAA(action11).includes('[CALL_API].headers property must be undefined, or a plain JavaScript object'), | ||
'[CALL_API].headers must be undefined, or a plain JavaScript object (validateRSAA)' | ||
); | ||
t.notOk( | ||
isRSAA(action11), | ||
'[CALL_API].headers must be undefined, or a plain JavaScript object (isRSAA)' | ||
); | ||
var action12 = { | ||
[CALL_API]: { | ||
@@ -60,8 +174,15 @@ endpoint: '', | ||
types: ['REQUEST', 'SUCCESS', 'FAILURE'], | ||
body: {}, | ||
headers: {}, | ||
schema: '' | ||
transform: '' | ||
} | ||
}), '[CALL_API].schema must be a normalizr schema'); | ||
t.notOk(isRSAA({ | ||
}; | ||
t.ok( | ||
validateRSAA(action12).includes('[CALL_API].transform property must be undefined, or a function'), | ||
'[CALL_API].transform property must be undefined, or a function (validateRSAA)' | ||
); | ||
t.notOk( | ||
isRSAA(action12), | ||
'[CALL_API].transform property must be undefined, or a function (isRSAA)' | ||
); | ||
var action13 = { | ||
[CALL_API]: { | ||
@@ -71,31 +192,64 @@ endpoint: '', | ||
types: ['REQUEST', 'SUCCESS', 'FAILURE'], | ||
body: {}, | ||
headers: {}, | ||
schema: new Schema('key'), | ||
bailout: '' | ||
} | ||
}), '[CALL_API].schema can also be an array of schemas'); | ||
t.notOk(isRSAA({ | ||
}; | ||
t.ok( | ||
validateRSAA(action13).includes('[CALL_API].bailout property must be undefined, a boolean, or a function'), | ||
'[CALL_API].bailout must be undefined, a boolean or a function (validateRSAA)' | ||
); | ||
t.notOk( | ||
isRSAA(action13), | ||
'[CALL_API].bailout must be undefined, a boolean or a function (isRSAA)' | ||
); | ||
var action14 = { | ||
[CALL_API]: { | ||
endpoint: '', | ||
method: 'GET', | ||
types: ['REQUEST', 'SUCCESS', 'FAILURE'], | ||
body: {}, | ||
headers: {}, | ||
schema: arrayOf(new Schema('key')), | ||
bailout: '' | ||
types: ['REQUEST', 'SUCCESS', 'FAILURE'] | ||
} | ||
}), '[CALL_API].bailout must be a boolean or a function'); | ||
t.ok(isRSAA({ | ||
}; | ||
t.notOk( | ||
validateRSAA(action14).length, | ||
'[CALL_API].endpoint may be a string (validateRSAA)' | ||
); | ||
t.ok( | ||
isRSAA(action14), | ||
'[CALL_API].endpoint may be a string (isRSAA)' | ||
); | ||
var action15 = { | ||
[CALL_API]: { | ||
endpoint: (() => ''), | ||
method: 'GET', | ||
types: ['REQUEST', 'SUCCESS', 'FAILURE'] | ||
} | ||
}; | ||
t.notOk( | ||
validateRSAA(action15).length, | ||
'[CALL_API].endpoint may be a function (validateRSAA)' | ||
); | ||
t.ok( | ||
isRSAA(action15), | ||
'[CALL_API].endpoint may be a function (isRSAA)' | ||
); | ||
var action16 = { | ||
[CALL_API]: { | ||
endpoint: '', | ||
method: 'GET', | ||
types: ['REQUEST', 'SUCCESS', 'FAILURE'], | ||
body: {}, | ||
headers: {}, | ||
schema: new Schema('key'), | ||
bailout: false | ||
headers: {} | ||
} | ||
}), 'isRSAA must return true for an RSAA action (1)'); | ||
t.ok(isRSAA({ | ||
}; | ||
t.notOk( | ||
validateRSAA(action16).length, | ||
'[CALL_API].headers may be a plain JavaScript object (validateRSAA)' | ||
); | ||
t.ok( | ||
isRSAA(action16), | ||
'[CALL_API].headers may be a plain JavaScript object (isRSAA)' | ||
); | ||
var action17 = { | ||
[CALL_API]: { | ||
@@ -105,20 +259,48 @@ endpoint: '', | ||
types: ['REQUEST', 'SUCCESS', 'FAILURE'], | ||
body: {}, | ||
headers: {}, | ||
schema: new Schema('key'), | ||
bailout: (() => false) | ||
transform: () => {} | ||
} | ||
}), 'isRSAA must return true for an RSAA action (2)'); | ||
t.ok(isRSAA({ | ||
}; | ||
t.notOk( | ||
validateRSAA(action17).length, | ||
'[CALL_API].transform may be a function (validateRSAA)' | ||
); | ||
t.ok( | ||
isRSAA(action17), | ||
'[CALL_API].transform may be a function (isRSAA)' | ||
); | ||
var action18 = { | ||
[CALL_API]: { | ||
endpoint: (() => ''), | ||
endpoint: '', | ||
method: 'GET', | ||
types: ['REQUEST', 'SUCCESS', 'FAILURE'], | ||
body: {}, | ||
headers: {}, | ||
schema: new Schema('key'), | ||
bailout: false | ||
} | ||
}), 'isRSAA must return true for an RSAA action (3)'); | ||
}; | ||
t.notOk( | ||
validateRSAA(action18).length, | ||
'[CALL_API].bailout may be a boolean (validateRSAA)' | ||
); | ||
t.ok( | ||
isRSAA(action18), | ||
'[CALL_API].bailout may be a boolean (isRSAA)' | ||
); | ||
var action19 = { | ||
[CALL_API]: { | ||
endpoint: '', | ||
method: 'GET', | ||
types: ['REQUEST', 'SUCCESS', 'FAILURE'], | ||
bailout: (() => false) | ||
} | ||
}; | ||
t.notOk( | ||
validateRSAA(action19).length, | ||
'[CALL_API].bailout may be a function (validateRSAA)' | ||
); | ||
t.ok( | ||
isRSAA(action19), | ||
'[CALL_API].bailout may be a function (isRSAA)' | ||
); | ||
t.end(); | ||
@@ -202,3 +384,3 @@ }); | ||
test('apiMiddleware must handle an unsuccessful API request that returns a non-json response', function (t) { | ||
test('apiMiddleware must handle an unsuccessful API request with a non-json response', function (t) { | ||
const api = nock('http://127.0.0.1') | ||
@@ -324,3 +506,3 @@ .get('/api/users/1') | ||
test('apiMiddleware must process a successful API response with a schema when present', function (t) { | ||
test('apiMiddleware must process a successful API response with a transform function when present', function (t) { | ||
const userRecord = { | ||
@@ -360,3 +542,3 @@ id: 1, | ||
types: ['FETCH_USER.REQUEST', 'FETCH_USER.SUCCESS', 'FETCH_USER.FAILURE'], | ||
schema: userSchema | ||
transform: (json) => normalize(json, userSchema) | ||
} | ||
@@ -379,53 +561,2 @@ }; | ||
test('apiMiddleware must process a successful API response with an array of schemas when present', function (t) { | ||
const testList = [ | ||
{ | ||
id: 1, | ||
username: 'Alice', | ||
}, | ||
{ | ||
id: 2, | ||
username: 'Bob' | ||
} | ||
]; | ||
const userSchema = new Schema('users'); | ||
const entities = { | ||
users: { | ||
1: { | ||
id: 1, | ||
username: 'Alice' | ||
}, | ||
2: { | ||
id: 2, | ||
username: 'Bob' | ||
} | ||
} | ||
}; | ||
const api = nock('http://127.0.0.1') | ||
.get('/api/users/1') | ||
.reply(200, testList, {'Content-Type': 'application/json'}); | ||
const anAction = { | ||
[CALL_API]: { | ||
endpoint: 'http://127.0.0.1/api/users/1', | ||
method: 'GET', | ||
types: ['FETCH_USER.REQUEST', 'FETCH_USER.SUCCESS', 'FETCH_USER.FAILURE'], | ||
schema: arrayOf(userSchema) | ||
} | ||
}; | ||
const doGetState = () => {}; | ||
const nextHandler = apiMiddleware({ getState: doGetState }); | ||
const doNext = (action) => { | ||
switch (action.type) { | ||
case 'FETCH_USER.SUCCESS': | ||
t.deepEqual(action.payload.entities, entities, 'success FSA has correct payload property'); | ||
break; | ||
} | ||
}; | ||
const actionHandler = nextHandler(doNext); | ||
t.plan(1); | ||
actionHandler(anAction); | ||
}); | ||
test('apiMiddleware must use an endpoint function when present', function (t) { | ||
@@ -457,3 +588,3 @@ const api = nock('http://127.0.0.1') | ||
test('apiMiddleware must use a bailout function when present', function (t) { | ||
test('apiMiddleware must use a bailout boolean when present', function (t) { | ||
const api = nock('http://127.0.0.1') | ||
@@ -460,0 +591,0 @@ .get('/api/users/1') |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
52865
2
22
1115
278
9
2
- Removednormalizr@^1.0.0
- Removedlodash@3.10.1(transitive)
- Removednormalizr@1.4.1(transitive)