From 3d4c7834e23aa0359e28b5f62e446fb295472f5c Mon Sep 17 00:00:00 2001 From: Kimmo Puputti Date: Tue, 21 Feb 2017 20:57:46 +0200 Subject: [PATCH 1/5] Add sample data module for REPL testing --- src/util/sample.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 src/util/sample.js diff --git a/src/util/sample.js b/src/util/sample.js new file mode 100644 index 00000000..dbe428b3 --- /dev/null +++ b/src/util/sample.js @@ -0,0 +1,13 @@ +import { types } from 'sharetribe-sdk'; + +export const marketplaceId = new types.UUID('16c6a4b8-88ee-429b-835a-6725206cd08c'); +export const userJoeId = new types.UUID('3c073fae-6172-4e75-8b92-f560d58cd47c'); +export const userJaneId = new types.UUID('7b98dd96-74c7-4ddc-9f46-38c0f91c4a19'); +export const listing1Id = new types.UUID('c6ff7190-bdf7-47a0-8a2b-e3136e74334f'); +export const listing2Id = new types.UUID('9009efe1-25ec-4ed5-9413-e80c584ff6bf'); +export const listing3Id = new types.UUID('8918693a-7a58-4d46-8324-f7996ef7579b'); +export const listing4Id = new types.UUID('5e1f2086-522c-46f3-87b4-451c6770c833'); +export const listing5Id = new types.UUID('f5130a7f-4b8b-453b-98e5-78e38ad02c3f'); +export const listing6Id = new types.UUID('927a30a2-3a69-4b0d-9c2e-a41744488703'); +export const image1Id = new types.UUID('4617c584-edfd-49e9-be43-2f1d50fb6e35'); +export const image2Id = new types.UUID('948b1926-67f9-402a-a7c7-f8ff3090c249'); From 37761e97b2a3130bb469481b078b547021edeb53 Mon Sep 17 00:00:00 2001 From: Kimmo Puputti Date: Tue, 21 Feb 2017 20:58:17 +0200 Subject: [PATCH 2/5] Add data module for handling JSONApi responses --- src/util/data.js | 54 ++++++++++++++ src/util/data.test.js | 165 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 219 insertions(+) create mode 100644 src/util/data.js create mode 100644 src/util/data.test.js diff --git a/src/util/data.js b/src/util/data.js new file mode 100644 index 00000000..4e54c809 --- /dev/null +++ b/src/util/data.js @@ -0,0 +1,54 @@ +/** + * Merge the given relationships objects + * + * See: http://jsonapi.org/format/#document-resource-object-relationships + */ +export const mergeRelationships = (rels1, rels2) => { + if (!rels1 && !rels2) { + // Special case to avoid adding an empty relationships object when + // none of the resource objects had any relationships. + return null; + } + return { ...rels1, ...rels2 }; +}; + +/** + * Merge the given resource objects + * + * See: http://jsonapi.org/format/#document-resource-objects + */ +export const mergeResourceObjects = (res1, res2) => { + const { id, type } = res1; + if (res2.id.uuid !== id.uuid || res2.type !== type) { + throw new Error('Cannot merge resource objects with different ids or types'); + } + const attributes = res2.attributes || res1.attributes; + const attrs = attributes ? { attributes } : null; + const relationships = mergeRelationships(res1.relationships, res2.relationships); + const rels = relationships ? { relationships } : null; + return { id, type, ...attrs, ...rels }; +}; + +/** + * Merge the resource objects form the given api response to the + * existing entities. + */ +export const mergeEntities = (oldEntities, apiResponse) => { + const { data, included = [] } = apiResponse; + const objects = (Array.isArray(data) ? data : [data]).concat(included); + + /* eslint-disable no-param-reassign */ + const newEntities = objects.reduce( + (entities, curr) => { + const { id, type } = curr; + entities[type] = entities[type] || {}; + const entity = entities[type][id.uuid]; + entities[type][id.uuid] = entity ? mergeResourceObjects(entity, curr) : curr; + return entities; + }, + oldEntities, + ); + /* eslint-enable no-param-reassign */ + + return newEntities; +}; diff --git a/src/util/data.test.js b/src/util/data.test.js new file mode 100644 index 00000000..3a7669a9 --- /dev/null +++ b/src/util/data.test.js @@ -0,0 +1,165 @@ +import { types } from 'sharetribe-sdk'; +import { mergeRelationships, mergeResourceObjects, mergeEntities } from './data'; + +const { UUID } = types; + +describe('data utils', () => { + describe('mergeRelationships()', () => { + it('handles no rels', () => { + expect(mergeRelationships(undefined, undefined)).toEqual(null); + }); + + it('takes existings rels if no new rels are given', () => { + const listingRels = { + author: { data: { id: new UUID('author-id'), type: 'author' } }, + images: { + data: [ + { id: new UUID('image1'), type: 'image' }, + { id: new UUID('image2'), type: 'image' }, + ], + }, + }; + expect(mergeRelationships(listingRels, undefined)).toEqual(listingRels); + }); + + it('takes new rels if no existing rels are given', () => { + const listingRels = { + author: { data: { id: new UUID('author-id'), type: 'author' } }, + images: { data: { id: new UUID('image1'), type: 'image' } }, + }; + expect(mergeRelationships(undefined, listingRels)).toEqual(listingRels); + }); + + it('merges two nonempty rels', () => { + const authorRel = { data: { id: new UUID('author-id'), type: 'author' } }; + const imagesRel = { + data: [ + { id: new UUID('image1'), type: 'image' }, + { id: new UUID('image2'), type: 'image' }, + ], + }; + const newImagesRel = { data: { id: new UUID('image1'), type: 'image' } }; + const reviewsRel = { + data: [ + { id: new UUID('review1'), type: 'review' }, + { id: new UUID('review2'), type: 'review' }, + ], + }; + + const oldListingRels = { author: authorRel, images: imagesRel }; + const newListingRels = { images: newImagesRel, reviews: reviewsRel }; + expect(mergeRelationships(oldListingRels, newListingRels)).toEqual({ + author: authorRel, + images: newImagesRel, + reviews: reviewsRel, + }); + }); + }); + + describe('mergeResourceObjects()', () => { + it("throws if ids don't match", () => { + const listing1 = { id: new UUID('listing1'), type: 'listing' }; + const listing2 = { id: new UUID('listing2'), type: 'listing' }; + expect(() => mergeResourceObjects(listing1, listing2)).toThrow( + 'Cannot merge resource objects with different ids or types', + ); + }); + + it("throws if types don't match", () => { + const listing1 = { id: new UUID('listing1'), type: 'listing' }; + const author1 = { id: new UUID('author1'), type: 'author' }; + expect(() => mergeResourceObjects(listing1, author1)).toThrow( + 'Cannot merge resource objects with different ids or types', + ); + }); + + it("doesn't add attributes or relationships keys unnecessarily", () => { + const res1 = { id: new UUID('listing1'), type: 'listing' }; + const res2 = { id: new UUID('listing1'), type: 'listing' }; + const merged = mergeResourceObjects(res1, res2); + expect(merged).toEqual(res1); + const keys = Object.keys(merged).sort(); + expect(keys).toEqual(['id', 'type']); + }); + + it('merges new attributes', () => { + const res1 = { + id: new UUID('listing1'), + type: 'listing', + attributes: { title: 'Listing 1 title', description: 'Listing 1 description' }, + }; + const res2 = { + id: new UUID('listing1'), + type: 'listing', + attributes: { title: 'Changed title', description: 'Some description' }, + }; + expect(mergeResourceObjects(res1, res2)).toEqual(res2); + }); + + it("keeps old attributes if new does't have any", () => { + const res1Attributes = { title: 'Listing 1 title', description: 'Listing 1 description' }; + const res2Relationships = { author: { data: { id: new UUID('author1'), type: 'author' } } }; + + const res1 = { id: new UUID('listing1'), type: 'listing', attributes: res1Attributes }; + const res2 = { id: new UUID('listing1'), type: 'listing', relationships: res2Relationships }; + expect(mergeResourceObjects(res1, res2)).toEqual({ + id: new UUID('listing1'), + type: 'listing', + attributes: res1Attributes, + relationships: res2Relationships, + }); + }); + }); + + describe('mergeEntities()', () => { + it('adds a single entity', () => { + const listing1 = { id: new UUID('listing1'), type: 'listing' }; + const response = { data: listing1 }; + const entities = mergeEntities({}, response); + expect(entities).toEqual({ listing: { listing1 } }); + }); + + it('add multiple entities', () => { + const listing1 = { id: new UUID('listing1'), type: 'listing' }; + const listing2 = { id: new UUID('listing2'), type: 'listing' }; + const response = { data: [listing1, listing2] }; + const entities = mergeEntities({}, response); + expect(entities).toEqual({ listing: { listing1, listing2 } }); + }); + + it('handles a more complex merge', () => { + const listing1 = { + id: new UUID('listing1'), + type: 'listing', + attributes: { title: 'Listing 1 title', description: 'Listing 1 description' }, + }; + const listing2 = { + id: new UUID('listing2'), + type: 'listing', + attributes: { title: 'Listing 2 title', description: 'Listing 2 description' }, + }; + const initialResponse = { data: [listing1, listing2] }; + const initialEntities = mergeEntities({}, initialResponse); + expect(initialEntities).toEqual({ listing: { listing1, listing2 } }); + const author1 = { id: new UUID('author1'), type: 'author' }; + const author1WithAttributes = { + id: new UUID('author1'), + type: 'author', + attributes: { name: 'Author 1 name' }, + }; + const listing2Updated = { + id: new UUID('listing2'), + type: 'listing', + attributes: { title: 'New listing 2 title', description: 'new listing 2 description' }, + relationships: { author: { data: author1 } }, + }; + const included = [author1WithAttributes]; + const response = { data: [listing2Updated], included }; + const entities = mergeEntities(initialEntities, response, true); + expect(entities).toEqual({ + listing: { listing1, listing2: listing2Updated }, + author: { author1: author1WithAttributes }, + }); + }); + }); +}); From cec4ea15c773eb06784cc67335fb0e2690229004 Mon Sep 17 00:00:00 2001 From: Kimmo Puputti Date: Tue, 21 Feb 2017 20:59:18 +0200 Subject: [PATCH 3/5] Add duck file for API calls --- src/ducks/index.js | 3 +- src/ducks/sdk.duck.js | 172 ++++++++++++++++++++++++++++++++++++++++++ src/sagas.js | 3 +- 3 files changed, 176 insertions(+), 2 deletions(-) create mode 100644 src/ducks/sdk.duck.js diff --git a/src/ducks/index.js b/src/ducks/index.js index 4d71de3d..89abad1e 100644 --- a/src/ducks/index.js +++ b/src/ducks/index.js @@ -8,5 +8,6 @@ import { reducer as form } from 'redux-form'; import Auth from './Auth.duck'; import FlashNotification from './FlashNotification.duck'; import LocationFilter from './LocationFilter.duck'; +import sdkReducer from './sdk.duck'; -export { form, Auth, FlashNotification, LocationFilter }; +export { form, Auth, FlashNotification, LocationFilter, sdkReducer as data }; diff --git a/src/ducks/sdk.duck.js b/src/ducks/sdk.duck.js new file mode 100644 index 00000000..c81b1a06 --- /dev/null +++ b/src/ducks/sdk.duck.js @@ -0,0 +1,172 @@ +/* eslint-disable no-constant-condition, no-console */ +import { call, put, take, fork } from 'redux-saga/effects'; +import { mergeEntities } from '../util/data'; + +const requestAction = actionType => params => ({ type: actionType, payload: { params } }); + +const successAction = actionType => data => ({ type: actionType, payload: data }); + +const errorAction = actionType => error => ({ type: actionType, payload: error, error: true }); + +// ================ Action types ================ // + +export const SHOW_LISTINGS_REQUEST = 'app/sdk/SHOW_LISTINGS_REQUEST'; +export const SHOW_LISTINGS_SUCCESS = 'app/sdk/SHOW_LISTINGS_SUCCESS'; +export const SHOW_LISTINGS_ERROR = 'app/sdk/SHOW_LISTINGS_ERROR'; + +export const QUERY_LISTINGS_REQUEST = 'app/sdk/QUERY_LISTINGS_REQUEST'; +export const QUERY_LISTINGS_SUCCESS = 'app/sdk/QUERY_LISTINGS_SUCCESS'; +export const QUERY_LISTINGS_ERROR = 'app/sdk/QUERY_LISTINGS_ERROR'; + +export const SEARCH_LISTINGS_REQUEST = 'app/sdk/SEARCH_LISTINGS_REQUEST'; +export const SEARCH_LISTINGS_SUCCESS = 'app/sdk/SEARCH_LISTINGS_SUCCESS'; +export const SEARCH_LISTINGS_ERROR = 'app/sdk/SEARCH_LISTINGS_ERROR'; + +export const SHOW_MARKETPLACE_REQUEST = 'app/sdk/SHOW_MARKETPLACE_REQUEST'; +export const SHOW_MARKETPLACE_SUCCESS = 'app/sdk/SHOW_MARKETPLACE_SUCCESS'; +export const SHOW_MARKETPLACE_ERROR = 'app/sdk/SHOW_MARKETPLACE_ERROR'; + +export const SHOW_USERS_REQUEST = 'app/sdk/SHOW_USERS_REQUEST'; +export const SHOW_USERS_SUCCESS = 'app/sdk/SHOW_USERS_SUCCESS'; +export const SHOW_USERS_ERROR = 'app/sdk/SHOW_USERS_ERROR'; + +// ================ Reducer ================ // + +const initialState = { + // Error instance placeholders for each endpoint + showListingsError: null, + queryListingsError: null, + searchListingsError: null, + showMarketplaceError: null, + showUsersError: null, + // Database of all the fetched entities. + entities: {}, +}; + +const merge = (state, payload) => { + const entities = mergeEntities(state.entities, payload.data); + return { ...state, entities }; +}; + +export default function sdkReducer(state = initialState, action = {}) { + const { type, payload } = action; + switch (type) { + case SHOW_LISTINGS_REQUEST: + return { ...state, showListingsError: null }; + case SHOW_LISTINGS_SUCCESS: + return merge(state, payload); + case SHOW_LISTINGS_ERROR: + console.error(payload); + return { ...state, showListingsError: payload }; + + case QUERY_LISTINGS_REQUEST: + return { ...state, queryListingsError: null }; + case QUERY_LISTINGS_SUCCESS: + return merge(state, payload); + case QUERY_LISTINGS_ERROR: + console.error(payload); + return { ...state, queryListingsError: payload }; + + case SEARCH_LISTINGS_REQUEST: + return { ...state, searchListingsError: null }; + case SEARCH_LISTINGS_SUCCESS: + return merge(state, payload); + case SEARCH_LISTINGS_ERROR: + console.error(payload); + return { ...state, searchListingsError: payload }; + + case SHOW_MARKETPLACE_REQUEST: + return { ...state, showMarketplaceError: null }; + case SHOW_MARKETPLACE_SUCCESS: + return merge(state, payload); + case SHOW_MARKETPLACE_ERROR: + console.error(payload); + return { ...state, showMarketplaceError: payload }; + + case SHOW_USERS_REQUEST: + return { ...state, showUsersError: null }; + case SHOW_USERS_SUCCESS: + return merge(state, payload); + case SHOW_USERS_ERROR: + console.error(payload); + return { ...state, showUsersError: payload }; + + default: + return state; + } +} + +// ================ Action creators ================ // + +export const showListings = requestAction(SHOW_LISTINGS_REQUEST); +export const showListingsSuccess = successAction(SHOW_LISTINGS_SUCCESS); +export const showListingsError = errorAction(SHOW_LISTINGS_ERROR); + +export const queryListings = requestAction(QUERY_LISTINGS_REQUEST); +export const queryListingsSuccess = successAction(QUERY_LISTINGS_SUCCESS); +export const queryListingsError = errorAction(QUERY_LISTINGS_ERROR); + +export const searchListings = requestAction(SEARCH_LISTINGS_REQUEST); +export const searchListingsSuccess = successAction(SEARCH_LISTINGS_SUCCESS); +export const searchListingsError = errorAction(SEARCH_LISTINGS_ERROR); + +export const showMarketplace = requestAction(SHOW_MARKETPLACE_REQUEST); +export const showMarketplaceSuccess = successAction(SHOW_MARKETPLACE_SUCCESS); +export const showMarketplaceError = errorAction(SHOW_MARKETPLACE_ERROR); + +export const showUsers = requestAction(SHOW_USERS_REQUEST); +export const showUsersSuccess = successAction(SHOW_USERS_SUCCESS); +export const showUsersError = errorAction(SHOW_USERS_ERROR); + +// ================ Worker sagas ================ // + +function* callEndpoint(sdkMethod, success, error, action) { + const params = action.payload.params; + try { + const response = yield call(sdkMethod, params); + yield put(success(response)); + } catch (e) { + yield put(error(e)); + } +} + +// ================ Watcher sagas ================ // + +// TODO: Think about structuring this file to avoid repetition without +// too much metaprogramming that makes the code harder to understand. +const endpoints = { + [SHOW_LISTINGS_REQUEST]: { + method: sdk => sdk.listings.show, + success: showListingsSuccess, + error: showListingsError, + }, + [QUERY_LISTINGS_REQUEST]: { + method: sdk => sdk.listings.query, + success: queryListingsSuccess, + error: queryListingsError, + }, + [SEARCH_LISTINGS_REQUEST]: { + method: sdk => sdk.listings.search, + success: searchListingsSuccess, + error: searchListingsError, + }, + [SHOW_MARKETPLACE_REQUEST]: { + method: sdk => sdk.marketplace.show, + success: showMarketplaceSuccess, + error: showMarketplaceError, + }, + [SHOW_USERS_REQUEST]: { + method: sdk => sdk.users.show, + success: showUsersSuccess, + error: showUsersError, + }, +}; + +export function* watchSdk(sdk) { + const requestTypes = Object.keys(endpoints); + while (true) { + const action = yield take(requestTypes); + const { method, success, error } = endpoints[action.type]; + yield fork(callEndpoint, method(sdk), success, error, action); + } +} diff --git a/src/sagas.js b/src/sagas.js index 41af2d2b..7f95eb15 100644 --- a/src/sagas.js +++ b/src/sagas.js @@ -1,8 +1,9 @@ import { watchAuth } from './ducks/Auth.duck'; import { watchLoadListings } from './containers/SearchPage/SearchPage.duck'; +import { watchSdk } from './ducks/sdk.duck'; const createRootSaga = sdk => function* rootSaga() { - yield [watchAuth(sdk), watchLoadListings(sdk)]; + yield [watchAuth(sdk), watchLoadListings(sdk), watchSdk(sdk)]; }; export default createRootSaga; From d02785cf9d3c1eb6993c9ae5b87a6a3f925d2166 Mon Sep 17 00:00:00 2001 From: Kimmo Puputti Date: Tue, 21 Feb 2017 20:59:43 +0200 Subject: [PATCH 4/5] Expose stuff for dev time REPL --- src/index.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/index.js b/src/index.js index 9a1bec46..4ed26359 100644 --- a/src/index.js +++ b/src/index.js @@ -13,12 +13,21 @@ import React from 'react'; import ReactDOM from 'react-dom'; +import { bindActionCreators } from 'redux'; import { createInstance, types } from 'sharetribe-sdk'; import { ClientApp, renderApp } from './app'; import configureStore from './store'; import { matchPathname } from './util/routes'; +import * as sample from './util/sample'; import createRootSaga from './sagas'; import config from './config'; +import { + showListings, + queryListings, + searchListings, + showMarketplace, + showUsers, +} from './ducks/sdk.duck'; import './index.css'; @@ -35,7 +44,11 @@ if (typeof window !== 'undefined') { // Expose stuff for the browser REPL if (config.dev) { - window.app = { config, sdk, sdkTypes: types }; + const actions = bindActionCreators( + { showListings, queryListings, searchListings, showMarketplace, showUsers }, + store.dispatch, + ); + window.app = { config, sdk, sdkTypes: types, actions, store, sample }; } } From 22878eb755ec095670e92bc75dd9fb212d13df1f Mon Sep 17 00:00:00 2001 From: Kimmo Puputti Date: Wed, 22 Feb 2017 11:17:45 +0200 Subject: [PATCH 5/5] Change function and param naming to communicate the purpose better --- src/ducks/sdk.duck.js | 4 ++-- src/util/data.js | 26 +++++++++++++------------- src/util/data.test.js | 34 +++++++++++++++++----------------- 3 files changed, 32 insertions(+), 32 deletions(-) diff --git a/src/ducks/sdk.duck.js b/src/ducks/sdk.duck.js index c81b1a06..eae6302f 100644 --- a/src/ducks/sdk.duck.js +++ b/src/ducks/sdk.duck.js @@ -1,6 +1,6 @@ /* eslint-disable no-constant-condition, no-console */ import { call, put, take, fork } from 'redux-saga/effects'; -import { mergeEntities } from '../util/data'; +import { updatedEntities } from '../util/data'; const requestAction = actionType => params => ({ type: actionType, payload: { params } }); @@ -44,7 +44,7 @@ const initialState = { }; const merge = (state, payload) => { - const entities = mergeEntities(state.entities, payload.data); + const entities = updatedEntities(state.entities, payload.data); return { ...state, entities }; }; diff --git a/src/util/data.js b/src/util/data.js index 4e54c809..4fce3cbc 100644 --- a/src/util/data.js +++ b/src/util/data.js @@ -1,39 +1,39 @@ /** - * Merge the given relationships objects + * Combine the given relationships objects * * See: http://jsonapi.org/format/#document-resource-object-relationships */ -export const mergeRelationships = (rels1, rels2) => { - if (!rels1 && !rels2) { +export const combinedRelationships = (oldRels, newRels) => { + if (!oldRels && !newRels) { // Special case to avoid adding an empty relationships object when // none of the resource objects had any relationships. return null; } - return { ...rels1, ...rels2 }; + return { ...oldRels, ...newRels }; }; /** - * Merge the given resource objects + * Combine the given resource objects * * See: http://jsonapi.org/format/#document-resource-objects */ -export const mergeResourceObjects = (res1, res2) => { - const { id, type } = res1; - if (res2.id.uuid !== id.uuid || res2.type !== type) { +export const combinedResourceObjects = (oldRes, newRes) => { + const { id, type } = oldRes; + if (newRes.id.uuid !== id.uuid || newRes.type !== type) { throw new Error('Cannot merge resource objects with different ids or types'); } - const attributes = res2.attributes || res1.attributes; + const attributes = newRes.attributes || oldRes.attributes; const attrs = attributes ? { attributes } : null; - const relationships = mergeRelationships(res1.relationships, res2.relationships); + const relationships = combinedRelationships(oldRes.relationships, newRes.relationships); const rels = relationships ? { relationships } : null; return { id, type, ...attrs, ...rels }; }; /** - * Merge the resource objects form the given api response to the + * Combine the resource objects form the given api response to the * existing entities. */ -export const mergeEntities = (oldEntities, apiResponse) => { +export const updatedEntities = (oldEntities, apiResponse) => { const { data, included = [] } = apiResponse; const objects = (Array.isArray(data) ? data : [data]).concat(included); @@ -43,7 +43,7 @@ export const mergeEntities = (oldEntities, apiResponse) => { const { id, type } = curr; entities[type] = entities[type] || {}; const entity = entities[type][id.uuid]; - entities[type][id.uuid] = entity ? mergeResourceObjects(entity, curr) : curr; + entities[type][id.uuid] = entity ? combinedResourceObjects(entity, curr) : curr; return entities; }, oldEntities, diff --git a/src/util/data.test.js b/src/util/data.test.js index 3a7669a9..c677c749 100644 --- a/src/util/data.test.js +++ b/src/util/data.test.js @@ -1,12 +1,12 @@ import { types } from 'sharetribe-sdk'; -import { mergeRelationships, mergeResourceObjects, mergeEntities } from './data'; +import { combinedRelationships, combinedResourceObjects, updatedEntities } from './data'; const { UUID } = types; describe('data utils', () => { - describe('mergeRelationships()', () => { + describe('combinedRelationships()', () => { it('handles no rels', () => { - expect(mergeRelationships(undefined, undefined)).toEqual(null); + expect(combinedRelationships(undefined, undefined)).toEqual(null); }); it('takes existings rels if no new rels are given', () => { @@ -19,7 +19,7 @@ describe('data utils', () => { ], }, }; - expect(mergeRelationships(listingRels, undefined)).toEqual(listingRels); + expect(combinedRelationships(listingRels, undefined)).toEqual(listingRels); }); it('takes new rels if no existing rels are given', () => { @@ -27,7 +27,7 @@ describe('data utils', () => { author: { data: { id: new UUID('author-id'), type: 'author' } }, images: { data: { id: new UUID('image1'), type: 'image' } }, }; - expect(mergeRelationships(undefined, listingRels)).toEqual(listingRels); + expect(combinedRelationships(undefined, listingRels)).toEqual(listingRels); }); it('merges two nonempty rels', () => { @@ -48,7 +48,7 @@ describe('data utils', () => { const oldListingRels = { author: authorRel, images: imagesRel }; const newListingRels = { images: newImagesRel, reviews: reviewsRel }; - expect(mergeRelationships(oldListingRels, newListingRels)).toEqual({ + expect(combinedRelationships(oldListingRels, newListingRels)).toEqual({ author: authorRel, images: newImagesRel, reviews: reviewsRel, @@ -56,11 +56,11 @@ describe('data utils', () => { }); }); - describe('mergeResourceObjects()', () => { + describe('combinedResourceObjects()', () => { it("throws if ids don't match", () => { const listing1 = { id: new UUID('listing1'), type: 'listing' }; const listing2 = { id: new UUID('listing2'), type: 'listing' }; - expect(() => mergeResourceObjects(listing1, listing2)).toThrow( + expect(() => combinedResourceObjects(listing1, listing2)).toThrow( 'Cannot merge resource objects with different ids or types', ); }); @@ -68,7 +68,7 @@ describe('data utils', () => { it("throws if types don't match", () => { const listing1 = { id: new UUID('listing1'), type: 'listing' }; const author1 = { id: new UUID('author1'), type: 'author' }; - expect(() => mergeResourceObjects(listing1, author1)).toThrow( + expect(() => combinedResourceObjects(listing1, author1)).toThrow( 'Cannot merge resource objects with different ids or types', ); }); @@ -76,7 +76,7 @@ describe('data utils', () => { it("doesn't add attributes or relationships keys unnecessarily", () => { const res1 = { id: new UUID('listing1'), type: 'listing' }; const res2 = { id: new UUID('listing1'), type: 'listing' }; - const merged = mergeResourceObjects(res1, res2); + const merged = combinedResourceObjects(res1, res2); expect(merged).toEqual(res1); const keys = Object.keys(merged).sort(); expect(keys).toEqual(['id', 'type']); @@ -93,7 +93,7 @@ describe('data utils', () => { type: 'listing', attributes: { title: 'Changed title', description: 'Some description' }, }; - expect(mergeResourceObjects(res1, res2)).toEqual(res2); + expect(combinedResourceObjects(res1, res2)).toEqual(res2); }); it("keeps old attributes if new does't have any", () => { @@ -102,7 +102,7 @@ describe('data utils', () => { const res1 = { id: new UUID('listing1'), type: 'listing', attributes: res1Attributes }; const res2 = { id: new UUID('listing1'), type: 'listing', relationships: res2Relationships }; - expect(mergeResourceObjects(res1, res2)).toEqual({ + expect(combinedResourceObjects(res1, res2)).toEqual({ id: new UUID('listing1'), type: 'listing', attributes: res1Attributes, @@ -111,11 +111,11 @@ describe('data utils', () => { }); }); - describe('mergeEntities()', () => { + describe('updatedEntities()', () => { it('adds a single entity', () => { const listing1 = { id: new UUID('listing1'), type: 'listing' }; const response = { data: listing1 }; - const entities = mergeEntities({}, response); + const entities = updatedEntities({}, response); expect(entities).toEqual({ listing: { listing1 } }); }); @@ -123,7 +123,7 @@ describe('data utils', () => { const listing1 = { id: new UUID('listing1'), type: 'listing' }; const listing2 = { id: new UUID('listing2'), type: 'listing' }; const response = { data: [listing1, listing2] }; - const entities = mergeEntities({}, response); + const entities = updatedEntities({}, response); expect(entities).toEqual({ listing: { listing1, listing2 } }); }); @@ -139,7 +139,7 @@ describe('data utils', () => { attributes: { title: 'Listing 2 title', description: 'Listing 2 description' }, }; const initialResponse = { data: [listing1, listing2] }; - const initialEntities = mergeEntities({}, initialResponse); + const initialEntities = updatedEntities({}, initialResponse); expect(initialEntities).toEqual({ listing: { listing1, listing2 } }); const author1 = { id: new UUID('author1'), type: 'author' }; const author1WithAttributes = { @@ -155,7 +155,7 @@ describe('data utils', () => { }; const included = [author1WithAttributes]; const response = { data: [listing2Updated], included }; - const entities = mergeEntities(initialEntities, response, true); + const entities = updatedEntities(initialEntities, response, true); expect(entities).toEqual({ listing: { listing1, listing2: listing2Updated }, author: { author1: author1WithAttributes },