diff --git a/__tests__/api.test.js b/__tests__/api.test.js new file mode 100644 index 0000000..e7d1a65 --- /dev/null +++ b/__tests__/api.test.js @@ -0,0 +1,75 @@ +import APIpeline from '../dist/index'; +import 'whatwg-fetch'; +import exampleData from './static/example'; +import server from './server'; + +const API_OPTIONS = { + fetchMethod: window.fetch, + domains: { default: 'http://127.0.0.1:23135' } +}; + +const API_SERVICES = { + testHeaders: { path: 'testHeaders' }, + getExample: { path: 'example.json' }, + postExample: { path: 'postExample', method: 'POST' } +}; + + +describe('>>> Test API calls', () => { + let mockedServer; + beforeAll((done) => { + mockedServer = server.listen(23135, done); + }); + + afterAll((done) => { + mockedServer.close(done); + }); + + it('Returns headers', async () => { + const api = new APIpeline(API_OPTIONS, API_SERVICES); + try { + await expect( + api.get('testHeaders', { headers: { 'Content-Type': 'application/pdf' } }) + ).resolves.toHaveProperty('content-type', 'application/pdf'); + } catch (err) { + throw err; + } + }); + + it('GET/example.json (api.get)', async () => { + const api = new APIpeline(API_OPTIONS, API_SERVICES); + await expect(api.get('getExample')).resolves.toEqual(exampleData); + }); + + it('GET/example.json (api.fetch)', async () => { + const api = new APIpeline(API_OPTIONS, API_SERVICES); + await expect(api.fetch('getExample')).resolves.toEqual(exampleData); + }); + + it('GET/example.json (api.getHeaders)', async () => { + const api = new APIpeline(API_OPTIONS, API_SERVICES); + expect.assertions(2); + try { + const headers = await api.fetchHeaders('getExample'); + expect(headers).toHaveProperty('content-type'); + expect(headers['content-type']).toEqual('application/json; charset=UTF-8'); + } catch (err) { + throw err; + } + }); + + it('POST/postExample (api.post)', async () => { + const api = new APIpeline(API_OPTIONS, API_SERVICES); + try { + await expect(api.post( + 'postExample', + { + fetchOptions: { body: 'key=value' }, + headers: { 'Content-Type': 'application/x-www-form-urlencoded' } + } + )).resolves.toEqual({ key: 'value' }); + } catch (err) { + throw err; + } + }); +}); diff --git a/__tests__/instantiation.test.js b/__tests__/instantiation.test.js new file mode 100644 index 0000000..143522d --- /dev/null +++ b/__tests__/instantiation.test.js @@ -0,0 +1,33 @@ +import APIpeline from '../dist'; + +const API_OPTIONS = { + fetchMethod: require('whatwg-fetch'), + domains: { default: 'http://default.tld' } +}; + +const API_SERVICES = { + example: { path: 'example' } +}; + +describe('>>> Instantiation', () => { + + it('Success with options and services', () => { + new APIpeline(API_OPTIONS, API_SERVICES); + }); + + it('Success without services', () => { + new APIpeline(API_OPTIONS); + }); + + it('Fails when fetchMethod is missing', () => { + expect(() => { + new APIpeline({ ...API_OPTIONS, fetchMethod: undefined }, API_SERVICES); + }).toThrow(/^Your fetch method is undefined/); + }); + + it('Fails when default domain is missing', () => { + expect(() => { + new APIpeline({ ...API_OPTIONS, domains: { staging: 'http://staging.myapi.tld' } }, API_SERVICES); + }).toThrow(/^You didn't set your default domain URL in your options/); + }); +}); diff --git a/__tests__/requirements.test.js b/__tests__/requirements.test.js new file mode 100644 index 0000000..0badd0d --- /dev/null +++ b/__tests__/requirements.test.js @@ -0,0 +1,12 @@ +import APIpeline from '../dist/index'; + +const API_OPTIONS = { + fetchMethod: require('whatwg-fetch'), + domains: { default: 'default' } +}; + +describe('>>> Requirements', () => { + it('Fetch is polyfilled', () => { + new APIpeline(API_OPTIONS); + }); +}); diff --git a/__tests__/server.js b/__tests__/server.js new file mode 100644 index 0000000..8627a3a --- /dev/null +++ b/__tests__/server.js @@ -0,0 +1,19 @@ +const express = require('express'); +const bodyParser = require('body-parser'); + +const app = express(); +const appRouter = express.Router(); +app.use((req, res, next) => { + res.header('Access-Control-Allow-Origin', '*'); + res.header('Access-Control-Allow-Headers', '*'); + res.header('Access-Control-Request-Headers', '*'); + next(); +}); +appRouter.use('/', express.static('__tests__/static')); +app.use(appRouter); +appRouter.use(bodyParser.urlencoded({ extended: true })); + +appRouter.get('/testHeaders', async (req, res) => res.json(req.headers)); +appRouter.post('/postExample', async (req, res) => res.json(req.body)); + +export default app; diff --git a/__tests__/setup.test.js b/__tests__/setup.test.js new file mode 100644 index 0000000..9950d8a --- /dev/null +++ b/__tests__/setup.test.js @@ -0,0 +1,66 @@ +import APIpeline, { DEFAULT_API_OPTIONS, DEFAULT_SERVICE_OPTIONS } from '../dist'; + +const API_OPTIONS = { + fetchMethod: require('whatwg-fetch'), + domains: { default: 'http://default.tld' } +}; + +describe('>>> Setup API options and services', () => { + + it('Merges the default API options for missing keys', () => { + const api = new APIpeline(API_OPTIONS); + expect(api._APIOptions).toStrictEqual({ + ...DEFAULT_API_OPTIONS, + ...API_OPTIONS, + }); + }); + + it('Merges the default services options for each service', () => { + const SERVICES = { default: { path: '/' }, simpleService: { path: 'simpleService', method: 'POST' } }; + const api = new APIpeline(API_OPTIONS, SERVICES); + expect(api._APIServices).toStrictEqual({ + default: { ...DEFAULT_SERVICE_OPTIONS, ...SERVICES.default, }, + simpleService: { ...DEFAULT_SERVICE_OPTIONS, ...SERVICES.simpleService }, + }); + }); + + it('Updates API options after instantiation', () => { + const api = new APIpeline(API_OPTIONS); + const UPDATED_OPTIONS = { printNetworkRequests: true }; + api.setOptions(UPDATED_OPTIONS); + expect(api._APIOptions).toStrictEqual({ + ...DEFAULT_API_OPTIONS, + ...API_OPTIONS, + ...UPDATED_OPTIONS + }); + }); + + it('Sets up services after instantiation without pre-defined services', () => { + const UPDATED_SERVICES = { newService: { path: 'http://myNewService.tld' } }; + const api = new APIpeline(API_OPTIONS); + api.setServices(UPDATED_SERVICES); + expect(api._APIServices).toStrictEqual({ + newService: { + ...UPDATED_SERVICES.newService, + ...DEFAULT_SERVICE_OPTIONS + } + }); + }); + + it('Adds a new service after instantiation with pre-defined services', () => { + const PRE_DEF_SERVICES = { default: { path: 'http://default.tld' } }; + const UPDATED_SERVICES = { newService: 'http://myNewService.tld' }; + const api = new APIpeline(API_OPTIONS, PRE_DEF_SERVICES); + api.setServices(UPDATED_SERVICES); + expect(api._APIServices).toStrictEqual({ + default: { + ...PRE_DEF_SERVICES.default, + ...DEFAULT_SERVICE_OPTIONS + }, + newService: { + ...UPDATED_SERVICES.newService, + ...DEFAULT_SERVICE_OPTIONS + } + }); + }); +}); diff --git a/__tests__/static/example.json b/__tests__/static/example.json new file mode 100644 index 0000000..77ae496 --- /dev/null +++ b/__tests__/static/example.json @@ -0,0 +1,3 @@ +{ + "test": "ok" +} diff --git a/__tests__/unit.test.js b/__tests__/unit.test.js new file mode 100644 index 0000000..21aa83a --- /dev/null +++ b/__tests__/unit.test.js @@ -0,0 +1,17 @@ +import APIpeline, { HTTP_METHODS } from '../dist'; + +const API_OPTIONS = { + fetchMethod: require('whatwg-fetch'), + domains: { default: 'http://default.tld' } +}; + + +describe('>>> Unit tests', () => { + + it('Implements every HTTP methods', () => { + const api = new APIpeline(API_OPTIONS); + HTTP_METHODS.forEach((method) => { + expect(typeof api[method.toLowerCase()]).toBe('function'); + }); + }) +}); diff --git a/package.json b/package.json index 01e303e..f8d4f59 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,13 @@ "types": "./src/index.d.ts", "scripts": { "start": "tsc --watch", - "test": "echo \"Error: no test specified\" && exit 1" + "build": "tsc", + "test": "jest --watch --verbose", + "test:coverage": "jest --collectCoverage" + }, + "jest": { + "testRegex": "(\\/__tests__\\/.*|(\\.|\\/)(test|spec))\\.(test)\\.[jt]sx?$", + "collectCoverageFrom": ["dist/**/*.js"] }, "repository": { "type": "git", @@ -37,7 +43,14 @@ }, "homepage": "https://github.com/Exilz/apipeline#readme", "devDependencies": { - "typescript": "^2.4.2" + "@babel/core": "^7.4.0", + "@babel/preset-env": "^7.4.2", + "@types/jest": "^24.0.11", + "babel-jest": "^24.5.0", + "express": "^4.16.4", + "jest": "^24.5.0", + "typescript": "^2.4.2", + "whatwg-fetch": "^3.0.0" }, "dependencies": { "jssha": "^2.3.1", diff --git a/tsconfig.json b/tsconfig.json index a71bb14..6817270 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { "target": "es5", "baseUrl": "./", - "lib": ["es2015"], + "lib": ["es2015", "dom"], "pretty": true, "jsx": "react-native", "outDir": "./dist", @@ -20,6 +20,7 @@ ], "exclude": [ "node_modules", - "demo" + "demo", + "__tests__" ] -} \ No newline at end of file +}