Skip to content

Test Coverage (fetch, updateMany/createMany, addAction(s)) #56

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Aug 19, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions fetch/karma.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ module.exports = function (config) {
browsers: browsers,
files: [
'node_modules/babel-polyfill/dist/polyfill.js',
'node_modules/whatwg-fetch/fetch.js',
'node_modules/js-data/dist/js-data.js',
'fetch/dist/js-data-fetch.js',
'fetch/karma.start.js',
Expand Down
14 changes: 11 additions & 3 deletions fetch/karma.start.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
/* global JSData:true, JSDataHttp:true, sinon:true, chai:true */

before(function () {
var Test = this
Test.TEST_FETCH = true
Test.fail = function (msg) {
if (msg instanceof Error) {
console.log(msg.stack)
Expand All @@ -14,7 +16,14 @@ before(function () {
}
Test.sinon = sinon
Test.JSData = JSData
Test.addAction = JSDataHttp.addAction
Test.addActions = JSDataHttp.addActions
Test.HttpAdapter = JSDataHttp.HttpAdapter

Test.store = new JSData.DataStore()
Test.adapter = new Test.HttpAdapter()
Test.store.registerAdapter('http', Test.adapter, { default: true })

Test.User = new JSData.Mapper({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

o, that shouldn't have been removed.

name: 'user'
})
Expand All @@ -24,14 +33,13 @@ before(function () {
basePath: 'api'
})

Test.User.registerAdapter('http', Test.adapter, { default: true })
Test.Post.registerAdapter('http', Test.adapter, { default: true })
console.log('Testing against js-data ' + JSData.version.full)
})

beforeEach(function () {
var Test = this
Test.adapter = new Test.HttpAdapter()
Test.User.registerAdapter('http', Test.adapter, { default: true })
Test.Post.registerAdapter('http', Test.adapter, { default: true })

Test.p1 = { author: 'John', age: 30, id: 5 }
Test.p2 = { author: 'Sally', age: 31, id: 6 }
Expand Down
3 changes: 1 addition & 2 deletions fetch/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@
"axios",
"rest",
"adapter",
"http",
"fetch"
"http"
],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why?

"dependencies": {
"js-data-adapter": "~0.8.1"
Expand Down
1 change: 1 addition & 0 deletions karma.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ module.exports = function (config) {
browsers: browsers,
files: [
'node_modules/babel-polyfill/dist/polyfill.js',
'node_modules/whatwg-fetch/fetch.js',
'node_modules/js-data/dist/js-data.js',
'dist/js-data-http.js',
'karma.start.js',
Expand Down
15 changes: 12 additions & 3 deletions karma.start.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
/* global JSData:true, JSDataHttp:true, sinon:true, chai:true */

before(function () {
var Test = this
Test.TEST_FETCH = false
Test.fail = function (msg) {
if (msg instanceof Error) {
console.log(msg.stack)
Expand All @@ -14,7 +16,15 @@ before(function () {
}
Test.sinon = sinon
Test.JSData = JSData
Test.addAction = JSDataHttp.addAction
Test.addActions = JSDataHttp.addActions
Test.HttpAdapter = JSDataHttp.HttpAdapter

Test.store = new JSData.DataStore()
Test.adapter = new Test.HttpAdapter()

Test.store.registerAdapter('http', Test.adapter, { default: true })

Test.User = new JSData.Mapper({
name: 'user'
})
Expand All @@ -24,14 +34,13 @@ before(function () {
basePath: 'api'
})

Test.User.registerAdapter('http', Test.adapter, { default: true })
Test.Post.registerAdapter('http', Test.adapter, { default: true })
console.log('Testing against js-data ' + JSData.version.full)
})

beforeEach(function () {
var Test = this
Test.adapter = new Test.HttpAdapter()
Test.User.registerAdapter('http', Test.adapter, { default: true })
Test.Post.registerAdapter('http', Test.adapter, { default: true })

Test.p1 = { author: 'John', age: 30, id: 5 }
Test.p2 = { author: 'Sally', age: 31, id: 6 }
Expand Down
12 changes: 8 additions & 4 deletions node/mocha.start.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,13 @@ before(function () {
}
Test.sinon = require('sinon')
Test.JSData = require('js-data')
Test.addAction = require('./dist/js-data-http-node').addAction
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be good to de-duplicate these 3 files. I would have preferred to do this via. es6 but I haven't gotten both build pipelines to work correctly with that =/

Test.addActions = require('./dist/js-data-http-node').addActions
Test.HttpAdapter = require('./dist/js-data-http-node').HttpAdapter
Test.store = new Test.JSData.DataStore()
Test.adapter = new Test.HttpAdapter()
Test.store.registerAdapter('http', Test.adapter, { default: true })

Test.User = new Test.JSData.Mapper({
name: 'user'
})
Expand All @@ -28,15 +34,13 @@ before(function () {
basePath: 'api'
})

Test.User.registerAdapter('http', Test.adapter, { default: true })
Test.Post.registerAdapter('http', Test.adapter, { default: true })
console.log('Testing against js-data ' + Test.JSData.version.full)
})

beforeEach(function () {
var Test = this
Test.adapter = new Test.HttpAdapter()
Test.User.registerAdapter('http', Test.adapter, { default: true })
Test.Post.registerAdapter('http', Test.adapter, { default: true })

Test.p1 = { author: 'John', age: 30, id: 5 }
Test.p2 = { author: 'Sally', age: 31, id: 6 }
Test.p3 = { author: 'Mike', age: 32, id: 7 }
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@
"phantomjs-prebuilt": "2.1.12",
"rollup-plugin-commonjs": "3.3.1",
"rollup-plugin-replace": "1.1.1",
"uglify-js": "2.7.0"
"uglify-js": "2.7.0",
"whatwg-fetch": "^1.0.0"
}
}
38 changes: 17 additions & 21 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* global fetch:true Headers:true Request:true */
/* global fetch:true Headers:true */

import {utils} from 'js-data'
import axios from '../node_modules/axios/dist/axios'
Expand Down Expand Up @@ -53,7 +53,7 @@ function buildUrl (url, params) {
}

val.forEach(function (v) {
if (window.toString.call(v) === '[object Date]') {
if (toString.call(v) === '[object Date]') {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

window.toString breaks for js-data-http-node

v = v.toISOString()
} else if (utils.isObject(v)) {
v = utils.toJson(v)
Expand Down Expand Up @@ -558,20 +558,19 @@ Adapter.extend({
* @param {Object} config.headers Headers for the request.
* @param {Object} config.params Querystring for the request.
* @param {string} config.url Url for the request.
* @param {Object} [opts] Configuration options.
*/
fetch (config, opts) {
fetch (config) {
const requestConfig = {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pruned opts since it was unused.

method: config.method,
// turn the plain headers object into the Fetch Headers object
headers: new Headers(config.headers)
headers: new Headers(config.headers || {})
}

if (config.data) {
requestConfig.body = utils.toJson(config.data)
}

return fetch(new Request(buildUrl(config.url, config.params), requestConfig))
return fetch(buildUrl(config.url, config.params), requestConfig)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to the docs argument signature for fetch and Request is equivalent https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch, so it shouldn't need to explicitly create a new Request().

.then((response) => {
response.config = {
method: config.method,
Expand Down Expand Up @@ -969,7 +968,7 @@ Adapter.extend({
sum (mapper, field, query, opts) {
query || (query = {})
opts || (opts = {})
if (!utils.utils.isString(field)) {
if (!utils.isString(field)) {
throw new Error('field must be a string!')
}
opts.params = this.getParams(opts)
Expand Down Expand Up @@ -1064,7 +1063,7 @@ Adapter.extend({
*
* // GET /reports/schools/:school_id/teachers
* addAction('getTeacherReports', {
* basePath: 'reports/schools',
* endpoint: 'reports/schools',
* pathname: 'teachers',
* method: 'GET'
* })(store.getMapper('school'))
Expand Down Expand Up @@ -1098,42 +1097,39 @@ export function addAction (name, opts) {
opts.response = opts.response || function (response) { return response }
opts.responseError = opts.responseError || function (err) { return utils.reject(err) }
mapper[name] = function (id, _opts) {
_opts = _opts || {}
if (utils.isObject(id)) {
_opts = id
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we still need these 3 lines. To use your test as an example, getTeacherReportsAsync(1234, { params: { foo: 'bar' } }) should produce async/reports/schools/1234/teachers?foo=bar like you've noted, but getTeacherReportsAsync({ params: { foo: 'bar' } }) should produce async/reports/schools/teachers?foo=bar. Without these 3 lines the second example would not work.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yeah, I was thinking it would just force the getTeacherReportsAsync(null, { params: { foo: 'bar' } }) type of syntax - but that may be less appealing of an interface? Not sure on this one, but if it's possible to avoid type-polymorphism in the arguments I think that would be good.

_opts = _opts || {}
let adapter = this.getAdapter(opts.adapter || this.defaultAdapter || 'http')
let config = {}
utils.fillIn(config, opts)
if (!_opts.hasOwnProperty('endpoint') && config.endpoint) {
_opts.endpoint = config.endpoint
}
utils.fillIn(_opts, opts)
Copy link
Contributor Author

@pik pik Aug 15, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Javascript is pretty dynamic-y but it may be worth considering having a limited fillIn in utils e.g. fillIn(dest, src, [<selected keys>]) so that the attributes merged from one object to another can be explicitly defined.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// at the top of index.js
const ACTION_OPTS = ['foo', 'bar']
// in addAction()
utils.fillIn(_opts, utils.pick(opts, ACTION_OPTS))

let adapter = this.getAdapter(_opts.adapter || this.defaultAdapter || 'http')
const config = {}
config.mapper = this.name
utils.deepMixIn(config, _opts)
config.method = config.method || 'GET'
if (typeof _opts.getEndpoint === 'function') {
config.url = _opts.getEndpoint(this, _opts)
} else {
let args = [
_opts.basePath || this.basePath || adapter.basePath,
adapter.getEndpoint(this, utils.isSorN(id) ? id : null, _opts)
adapter.getEndpoint(this, id, _opts)
]
if (utils.isSorN(id)) {
args.push(id)
}
args.push(opts.pathname || name)
config.url = makePath.apply(null, args)
}
config.method = config.method || 'GET'
config.mapper = this.name
utils.deepMixIn(config, _opts)
return utils.resolve(config)
.then(_opts.request || opts.request)
.then(_opts.request)
.then((config) => adapter.HTTP(config))
.then((data) => {
if (data && data.config) {
data.config.mapper = this.name
}
return data
})
.then(_opts.response || opts.response, _opts.responseError || opts.responseError)
.then(_opts.response, _opts.responseError)
}
return mapper
}
Expand Down
14 changes: 14 additions & 0 deletions test/count.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
describe('sum', function () {
it('should include count=true in query_params', function (done) {
var Test = this

setTimeout(function () {
Test.requests[0].respond(200, { 'Content-Type': 'application/json' }, '{"count": 5}')
}, 5)

Test.adapter.count(Test.Post).then(function (result) {
Test.assert.equal(Test.requests[0].url, 'api/posts?count=true')
done()
})
})
})
16 changes: 15 additions & 1 deletion test/createMany.test.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
describe('createMany', function () {
it('should createMany')
it('should createMany', function (done) {
var Test = this

setTimeout(function () {
Test.requests[0].respond(200, { 'Content-Type': 'application/json' }, '')
}, 5)

var many = [{ author_id: 2, text: 'bar' }, { author_id: 2, text: 'foo' }]
Test.Post.createMany(many).then(function (result) {
Test.assert.equal(Test.requests[0].url, 'api/posts')
Test.assert.equal(Test.requests[0].method, 'POST')
Test.assert.equal(Test.requests[0].requestBody, JSON.stringify(many))
done()
})
})
})
35 changes: 34 additions & 1 deletion test/fetch.test.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,36 @@
describe('fetch', function () {
it('should fetch')
it('should fetch from a URL', function (done) {
var Test = this
// Don't test fetch for Node
try {
fetch // eslint-disable-line
} catch (e) {
return this.skip()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skipping the mocha test because the fetch polyfill for Node is not actually compatible with the browser fetch. We would need to fork the polyfill or have different fetch behaviors depending on node/browser.

}
if (Test.TEST_FETCH) {
Test.xhr = Test.sinon.useFakeXMLHttpRequest()
Test.requests = []
Test.xhr.onCreate = function (xhr) {
Test.requests.push(xhr)
}
}

setTimeout(function () {
Test.requests[0].respond(200, { 'Content-Type': 'application/json' }, '{}')
}, 300)

Test.adapter.fetch({
method: 'get',
params: { active: true },
url: '/api/foos'
}).then(function (response) {
var request = Test.requests[0]
Test.assert.equal(request.method, 'GET')
Test.assert.equal(request.url, '/api/foos?active=true')
if (Test.TEST_FETCH) {
Test.xhr.restore()
}
done()
})
})
})
56 changes: 55 additions & 1 deletion test/static.addAction.test.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,57 @@
describe('static addAction', function () {
it('should addAction')
it('should addAction', function (done) {
var Test = this
var SchoolMapper = Test.store.defineMapper('school', {})

// GET async/reports/schools/:school_id/teachers
Test.addAction('getTeacherReportsAsync', {
basePath: 'async/',
endpoint: 'reports/schools',
pathname: 'teachers',
method: 'GET'
})(SchoolMapper)

setTimeout(function () {
Test.requests[0].respond(200, { 'Content-Type': 'text/plain' }, '')
}, 5)

SchoolMapper.getTeacherReportsAsync(1234).then(function (response) {
Test.assert.equal(1, Test.requests.length)
Test.assert.equal(Test.requests[0].url, 'async/reports/schools/1234/teachers', 'Add action configures basePath, endpoint and pathname')
Test.assert.equal(Test.requests[0].method, 'GET')
done()
})
})

it('addAction action is callable with params instead of id', function (done) {
var Test = this
var adapter = Test.adapter
var store = new Test.JSData.DataStore()
store.registerAdapter('http', adapter, { default: true })
var SchoolMapper = store.defineMapper('school', {})

// GET async/reports/schools/teachers
Test.addAction('getAllTeacherReportsAsync', {
basePath: 'async/',
endpoint: 'reports/schools',
pathname: 'teachers',
method: 'GET'
})(SchoolMapper)

setTimeout(function () {
Test.requests[0].respond(200, { 'Content-Type': 'text/plain' }, '')
}, 5)

// GET async/reports/schools/teachers?<key>=<value>
SchoolMapper.getAllTeacherReportsAsync({
params: {
subject: 'esperanto'
}
}).then(function (response) {
Test.assert.equal(1, Test.requests.length)
Test.assert.equal(Test.requests[0].url, 'async/reports/schools/teachers?subject=esperanto', 'Add action configures basePath, endpoint, pathname, and querystring')
Test.assert.equal(Test.requests[0].method, 'GET')
done()
})
})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's another test:

// GET async/reports/schools/teachers
Test.JSDataHttp.addAction('getAllTeacherReportsAsync', {
  basePath: 'async/',
  endpoint: 'reports/schools',
  pathname: 'teachers',
  method: 'GET'
})(SchoolMapper)

setTimeout(function () {
  Test.requests[0].respond(200, { 'Content-Type': 'text/plain' }, '')
}, 5)

// GET async/reports/schools/teachers?foo=bar
SchoolMapper.getAllTeacherReportsAsync({
  params: {
    foo: 'bar'
  }
}).then(function (response) {
  Test.assert.equal(1, Test.requests.length)
  Test.assert.equal(Test.requests[0].url, 'async/reports/schools/teachers?foo=bar', 'Add action configures basePath, endpoint, pathname, and querystring')
  Test.assert.equal(Test.requests[0].method, 'GET')
  done()
})

})
Loading