diff --git a/webapp/components/utils/UpdateQuery.js b/webapp/components/utils/UpdateQuery.js new file mode 100644 index 000000000..d601d0ac3 --- /dev/null +++ b/webapp/components/utils/UpdateQuery.js @@ -0,0 +1,17 @@ +import unionBy from 'lodash/unionBy' + +export default function UpdateQuery(component, { $state, pageKey }) { + if (!pageKey) throw new Error('No key given for the graphql query { data } object') + return (previousResult, { fetchMoreResult }) => { + const oldData = (previousResult && previousResult[pageKey]) || [] + const newData = (fetchMoreResult && fetchMoreResult[pageKey]) || [] + if (newData.length < component.pageSize) { + component.hasMore = false + $state.complete() + } + const result = {} + result[pageKey] = unionBy(oldData, newData, item => item.id) + $state.loaded() + return result + } +} diff --git a/webapp/components/utils/UpdateQuery.spec.js b/webapp/components/utils/UpdateQuery.spec.js new file mode 100644 index 000000000..295b256a4 --- /dev/null +++ b/webapp/components/utils/UpdateQuery.spec.js @@ -0,0 +1,86 @@ +import UpdateQuery from './UpdateQuery' + +let $state +let component +let pageKey +let updateQuery +let previousResult +let fetchMoreResult + +beforeEach(() => { + component = { + hasMore: true, + pageSize: 1, + } + + $state = { + complete: jest.fn(), + loaded: jest.fn(), + } + previousResult = { Post: [{ id: 1, foo: 'bar' }] } + fetchMoreResult = { Post: [{ id: 2, foo: 'baz' }] } + updateQuery = () => UpdateQuery(component, { $state, pageKey }) +}) + +describe('UpdateQuery', () => { + it('throws error because no key is given', () => { + expect(() => { + updateQuery()({ Post: [] }, { fetchMoreResult: { Post: [] } }) + }).toThrow(/No key given/) + }) + + describe('with a page key', () => { + beforeEach(() => (pageKey = 'Post')) + + describe('given two arrays of things', () => { + it('merges the arrays', () => { + expect(updateQuery()(previousResult, { fetchMoreResult })).toEqual({ + Post: [ + { id: 1, foo: 'bar' }, + { id: 2, foo: 'baz' }, + ], + }) + }) + + it('does not create duplicates', () => { + fetchMoreResult = { Post: [{ id: 1, foo: 'baz' }] } + expect(updateQuery()(previousResult, { fetchMoreResult })).toEqual({ + Post: [{ id: 1, foo: 'bar' }], + }) + }) + + it('does not call $state.complete()', () => { + expect(updateQuery()(previousResult, { fetchMoreResult })) + expect($state.complete).not.toHaveBeenCalled() + }) + + describe('in case of fewer records than pageSize', () => { + beforeEach(() => (component.pageSize = 10)) + it('calls $state.complete()', () => { + expect(updateQuery()(previousResult, { fetchMoreResult })) + expect($state.complete).toHaveBeenCalled() + }) + + it('changes component.hasMore to `false`', () => { + expect(component.hasMore).toBe(true) + expect(updateQuery()(previousResult, { fetchMoreResult })) + expect(component.hasMore).toBe(false) + }) + }) + }) + + describe('given one array is undefined', () => { + describe('does not crash', () => { + it('neither if the previous data was undefined', () => { + expect(updateQuery()(undefined, { fetchMoreResult })).toEqual({ + Post: [{ id: 2, foo: 'baz' }], + }) + }) + + it('not if the new data is undefined', () => { + expect(updateQuery()(previousResult, {})).toEqual({ Post: [{ id: 1, foo: 'bar' }] }) + }) + }) + }) + }) +}) diff --git a/webapp/pages/index.vue b/webapp/pages/index.vue index 2283a3453..c3f01d548 100644 --- a/webapp/pages/index.vue +++ b/webapp/pages/index.vue @@ -68,6 +68,7 @@ import MasonryGridItem from '~/components/MasonryGrid/MasonryGridItem.vue' import { mapGetters, mapMutations } from 'vuex' import { filterPosts } from '~/graphql/PostQuery.js' import PostMutations from '~/graphql/PostMutations' +import UpdateQuery from '~/components/utils/UpdateQuery' export default { components: { @@ -151,27 +152,7 @@ export default { first: this.pageSize, orderBy: ['pinned_asc', this.orderBy], }, - updateQuery: (previousResult, { fetchMoreResult }) => { - if (!fetchMoreResult || fetchMoreResult.Post.length < this.pageSize) { - this.hasMore = false - $state.complete() - } - - const { Post = [] } = previousResult - const result = { - ...previousResult, - Post: [ - ...Post.filter(prevPost => { - return ( - fetchMoreResult.Post.filter(newPost => newPost.id === prevPost.id).length === 0 - ) - }), - ...fetchMoreResult.Post, - ], - } - $state.loaded() - return result - }, + updateQuery: UpdateQuery(this, { $state, pageKey: 'Post' }), }) }, deletePost(deletedPost) { diff --git a/webapp/pages/profile/_id/_slug.vue b/webapp/pages/profile/_id/_slug.vue index 44ac7f47d..3f7ff068a 100644 --- a/webapp/pages/profile/_id/_slug.vue +++ b/webapp/pages/profile/_id/_slug.vue @@ -283,6 +283,7 @@ import { profilePagePosts } from '~/graphql/PostQuery' import UserQuery from '~/graphql/User' import { Block, Unblock } from '~/graphql/settings/BlockedUsers' import PostMutations from '~/graphql/PostMutations' +import UpdateQuery from '~/components/utils/UpdateQuery' const tabToFilterMapping = ({ tab, id }) => { return { @@ -385,27 +386,7 @@ export default { first: this.pageSize, orderBy: 'createdAt_desc', }, - updateQuery: (previousResult, { fetchMoreResult }) => { - if (!fetchMoreResult || fetchMoreResult.profilePagePosts.length < this.pageSize) { - this.hasMore = false - $state.complete() - } - const { profilePagePosts = [] } = previousResult - const result = { - ...previousResult, - profilePagePosts: [ - ...profilePagePosts.filter(prevPost => { - return ( - fetchMoreResult.profilePagePosts.filter(newPost => newPost.id === prevPost.id) - .length === 0 - ) - }), - ...fetchMoreResult.profilePagePosts, - ], - } - $state.loaded() - return result - }, + updateQuery: UpdateQuery(this, { $state, pageKey: 'profilePagePosts' }), }) }, resetPostList() {