refactor: lint, frontend tests, vuex store

This fixes the lint errors and failing frontend tests. Also, when the
user chooses another orderBy, the menu gets translated.

The refactoring moves code and complexity into the vuex store where it
can be tested separately.
This commit is contained in:
roschaefer 2019-10-18 16:08:11 +02:00
parent 7330987a69
commit 16d07bd3ba
10 changed files with 197 additions and 99 deletions

View File

@ -67,13 +67,13 @@ export default {
},
computed: {
...mapGetters({
filteredCategoryIds: 'postsFilter/filteredCategoryIds',
filteredCategoryIds: 'posts/filteredCategoryIds',
}),
},
methods: {
...mapMutations({
resetCategories: 'postsFilter/RESET_CATEGORIES',
toggleCategory: 'postsFilter/TOGGLE_CATEGORY',
resetCategories: 'posts/RESET_CATEGORIES',
toggleCategory: 'posts/TOGGLE_CATEGORY',
}),
},
}

View File

@ -50,20 +50,20 @@ describe('FilterPosts.vue', () => {
describe('mount', () => {
mutations = {
'postsFilter/TOGGLE_FILTER_BY_FOLLOWED': jest.fn(),
'postsFilter/RESET_CATEGORIES': jest.fn(),
'postsFilter/TOGGLE_CATEGORY': jest.fn(),
'postsFilter/TOGGLE_EMOTION': jest.fn(),
'posts/TOGGLE_FILTER_BY_FOLLOWED': jest.fn(),
'posts/RESET_CATEGORIES': jest.fn(),
'posts/TOGGLE_CATEGORY': jest.fn(),
'posts/TOGGLE_EMOTION': jest.fn(),
}
getters = {
'postsFilter/isActive': () => false,
'posts/isActive': () => false,
'auth/isModerator': () => false,
'auth/user': () => {
return { id: 'u34' }
},
'postsFilter/filteredCategoryIds': jest.fn(() => []),
'postsFilter/filteredByUsersFollowed': jest.fn(),
'postsFilter/filteredByEmotions': jest.fn(() => []),
'posts/filteredCategoryIds': jest.fn(() => []),
'posts/filteredByUsersFollowed': jest.fn(),
'posts/filteredByEmotions': jest.fn(() => []),
}
const openFilterPosts = () => {
const store = new Vuex.Store({ mutations, getters })
@ -94,18 +94,18 @@ describe('FilterPosts.vue', () => {
const wrapper = openFilterPosts()
environmentAndNatureButton = wrapper.findAll('button').at(2)
environmentAndNatureButton.trigger('click')
expect(mutations['postsFilter/TOGGLE_CATEGORY']).toHaveBeenCalledWith({}, 'cat4')
expect(mutations['posts/TOGGLE_CATEGORY']).toHaveBeenCalledWith({}, 'cat4')
})
it('sets category button attribute `primary` when corresponding category is filtered', () => {
getters['postsFilter/filteredCategoryIds'] = jest.fn(() => ['cat9'])
getters['posts/filteredCategoryIds'] = jest.fn(() => ['cat9'])
const wrapper = openFilterPosts()
democracyAndPoliticsButton = wrapper.findAll('button').at(4)
expect(democracyAndPoliticsButton.attributes().class).toContain('ds-button-primary')
})
it('sets "filter-by-followed-authors-only" button attribute `primary`', () => {
getters['postsFilter/filteredByUsersFollowed'] = jest.fn(() => true)
getters['posts/filteredByUsersFollowed'] = jest.fn(() => true)
const wrapper = openFilterPosts()
expect(
wrapper.find({ name: 'filter-by-followed-authors-only' }).classes('ds-button-primary'),
@ -120,7 +120,7 @@ describe('FilterPosts.vue', () => {
})
it('calls TOGGLE_FILTER_BY_FOLLOWED', () => {
expect(mutations['postsFilter/TOGGLE_FILTER_BY_FOLLOWED']).toHaveBeenCalledWith({}, 'u34')
expect(mutations['posts/TOGGLE_FILTER_BY_FOLLOWED']).toHaveBeenCalledWith({}, 'u34')
})
})
@ -129,11 +129,11 @@ describe('FilterPosts.vue', () => {
const wrapper = openFilterPosts()
happyEmotionButton = wrapper.findAll('button.emotions-buttons').at(1)
happyEmotionButton.trigger('click')
expect(mutations['postsFilter/TOGGLE_EMOTION']).toHaveBeenCalledWith({}, 'happy')
expect(mutations['posts/TOGGLE_EMOTION']).toHaveBeenCalledWith({}, 'happy')
})
it('sets the attribute `src` to colorized image', () => {
getters['postsFilter/filteredByEmotions'] = jest.fn(() => ['happy'])
getters['posts/filteredByEmotions'] = jest.fn(() => ['happy'])
const wrapper = openFilterPosts()
happyEmotionButton = wrapper.findAll('button.emotions-buttons').at(1)
const happyEmotionButtonImage = happyEmotionButton.find('img')

View File

@ -39,7 +39,7 @@ export default {
computed: {
...mapGetters({
currentUser: 'auth/user',
filterActive: 'postsFilter/isActive',
filterActive: 'posts/isActive',
}),
chunk() {
return chunk(this.categories, 2)

View File

@ -68,14 +68,14 @@ export default {
},
computed: {
...mapGetters({
filteredByUsersFollowed: 'postsFilter/filteredByUsersFollowed',
filteredByEmotions: 'postsFilter/filteredByEmotions',
filteredByUsersFollowed: 'posts/filteredByUsersFollowed',
filteredByEmotions: 'posts/filteredByEmotions',
}),
},
methods: {
...mapMutations({
toggleFilteredByFollowed: 'postsFilter/TOGGLE_FILTER_BY_FOLLOWED',
toogleFilteredByEmotions: 'postsFilter/TOGGLE_EMOTION',
toggleFilteredByFollowed: 'posts/TOGGLE_FILTER_BY_FOLLOWED',
toogleFilteredByEmotions: 'posts/TOGGLE_EMOTION',
}),
iconPath(emotion) {
if (this.filteredByEmotions.includes(emotion)) {

View File

@ -50,6 +50,18 @@
}
}
},
"store": {
"posts": {
"orderBy": {
"newest": {
"label": "Neueste"
},
"oldest": {
"label": "Älteste"
}
}
}
},
"maintenance": {
"title": "Human Connection befindet sich in der Wartung",
"explanation": "Zurzeit führen wir einige geplante Wartungsarbeiten durch, bitte versuch es später erneut.",
@ -95,10 +107,6 @@
"code-of-conduct": "Verhaltenscodex",
"back-to-login": "Zurück zur Anmeldung"
},
"sorting": {
"newest": "Neueste",
"oldest": "Älteste"
},
"login": {
"copy": "Wenn Du bereits ein Konto bei Human Connection hast, melde Dich bitte hier an.",
"login": "Einloggen",

View File

@ -51,6 +51,18 @@
}
}
},
"store": {
"posts": {
"orderBy": {
"newest": {
"label": "Newest"
},
"oldest": {
"label": "Oldest"
}
}
}
},
"maintenance": {
"title": "Human Connection is under maintenance",
"explanation": "At the moment we are doing some scheduled maintenance, please try again later.",
@ -96,10 +108,6 @@
"code-of-conduct": "Code of Conduct",
"back-to-login": "Back to login page"
},
"sorting": {
"newest": "Newest",
"oldest": "Oldest"
},
"login": {
"copy": "If you already have a human-connection account, please login.",
"login": "Login",

View File

@ -24,15 +24,37 @@ describe('PostIndex', () => {
let Wrapper
let store
let mocks
let mutations
beforeEach(() => {
mutations = {
'posts/SELECT_ORDER': jest.fn(),
}
store = new Vuex.Store({
getters: {
'postsFilter/postsFilter': () => ({}),
'posts/filter': () => ({}),
'posts/orderOptions': () => () => [
{
key: 'store.posts.orderBy.oldest.label',
label: 'store.posts.orderBy.oldest.label',
icon: 'sort-amount-asc',
value: 'createdAt_asc',
},
{
key: 'store.posts.orderBy.newest.label',
label: 'store.posts.orderBy.newest.label',
icon: 'sort-amount-desc',
value: 'createdAt_desc',
},
],
'posts/selectedOrder': () => () => 'createdAt_desc',
'posts/orderIcon': () => 'sort-amount-desc',
'posts/orderBy': () => 'createdAt_desc',
'auth/user': () => {
return { id: 'u23' }
},
},
mutations,
})
mocks = {
$t: key => key,
@ -103,12 +125,12 @@ describe('PostIndex', () => {
})
})
it('sets the post in the store when there are posts', () => {
it('calls store when using order by menu', () => {
wrapper
.findAll('li')
.at(0)
.trigger('click')
expect(wrapper.vm.sorting).toEqual('createdAt_desc')
expect(mutations['posts/SELECT_ORDER']).toHaveBeenCalledWith({}, 'createdAt_asc')
})
it('updates offset when a user clicks on the load more button', () => {

View File

@ -10,8 +10,7 @@
v-model="selected"
:options="sortingOptions"
size="large"
v-bind:icon-right="sortingIcon"
@input="updateOrder"
:icon-right="sortingIcon"
></ds-select>
</div>
</ds-grid-item>
@ -83,34 +82,29 @@ export default {
offset: 0,
pageSize: 12,
hashtag,
sortingIcon: 'sort-amount-desc',
selected: this.$t('sorting.newest'),
sortingOptions: [
{
label: this.$t('sorting.newest'),
value: 'Newest',
icons: 'sort-amount-desc',
order: 'createdAt_desc',
},
{
label: this.$t('sorting.oldest'),
value: 'Oldest',
icons: 'sort-amount-asc',
order: 'createdAt_asc',
},
],
}
},
mounted() {
if(this.postsOrder.label) {
this.selected = this.postsOrder.label
}
},
computed: {
...mapGetters({
postsFilter: 'postsFilter/postsFilter',
postsOrder: 'postsFilter/postsOrder',
postsFilter: 'posts/filter',
orderOptions: 'posts/orderOptions',
orderBy: 'posts/orderBy',
selectedOrder: 'posts/selectedOrder',
sortingIcon: 'posts/orderIcon',
}),
selected: {
get() {
return this.selectedOrder(this)
},
set({ value }) {
this.offset = 0
this.posts = []
this.selectOrder(value)
},
},
sortingOptions() {
return this.orderOptions(this)
},
finalFilters() {
let filter = this.postsFilter
if (this.hashtag) {
@ -127,15 +121,8 @@ export default {
},
methods: {
...mapMutations({
setPostsOrder: 'postsFilter/UPDATE_ORDER',
selectOrder: 'posts/SELECT_ORDER',
}),
updateOrder(sortingOptions) {
this.offset = 0
this.posts = []
this.sortingIcon = sortingOptions.icons
this.selected = sortingOptions.label
this.setPostsOrder(sortingOptions)
},
clearSearch() {
this.$router.push({ path: '/' })
this.hashtag = null
@ -156,7 +143,7 @@ export default {
offset: this.offset,
filter: this.finalFilters,
first: this.pageSize,
orderBy: this.sorting,
orderBy: this.orderBy,
},
updateQuery: (previousResult, { fetchMoreResult }) => {
if (!fetchMoreResult || fetchMoreResult.Post.length < this.pageSize) {
@ -184,7 +171,7 @@ export default {
return {
filter: this.finalFilters,
first: this.pageSize,
orderBy: this.postsOrder.order,
orderBy: this.orderBy,
offset: 0,
}
},

View File

@ -7,17 +7,25 @@ import clone from 'lodash/clone'
const defaultFilter = {}
const orderOptions = {
createdAt_asc: {
value: 'createdAt_asc',
key: 'store.posts.orderBy.oldest.label',
icon: 'sort-amount-asc',
},
createdAt_desc: {
value: 'createdAt_desc',
key: 'store.posts.orderBy.newest.label',
icon: 'sort-amount-desc',
},
}
export const state = () => {
return {
filter: {
...defaultFilter,
},
postsOrder: {
label: null,
value: 'Newest',
icons: 'sort-amount-desc',
order: 'createdAt_desc',
}
order: orderOptions['createdAt_desc'],
}
}
@ -52,16 +60,16 @@ export const mutations = {
if (isEmpty(get(filter, 'emotions_some.emotion_in'))) delete filter.emotions_some
state.filter = filter
},
UPDATE_ORDER(state, postsOrder) {
state.postsOrder = postsOrder
}
SELECT_ORDER(state, value) {
state.order = orderOptions[value]
},
}
export const getters = {
isActive(state) {
return !isEqual(state.filter, defaultFilter)
},
postsFilter(state) {
filter(state) {
return state.filter
},
filteredCategoryIds(state) {
@ -73,7 +81,23 @@ export const getters = {
filteredByEmotions(state) {
return get(state.filter, 'emotions_some.emotion_in') || []
},
postsOrder(state) {
return state.postsOrder
}
orderOptions: state => ({ $t }) =>
Object.values(orderOptions).map(option => {
return {
...option,
label: $t(option.key),
}
}),
selectedOrder: state => ({ $t }) => {
return {
...state.order,
label: $t(state.order.key),
}
},
orderBy(state) {
return state.order.value
},
orderIcon(state) {
return state.order.icon
},
}

View File

@ -1,7 +1,7 @@
import { getters, mutations } from './postsFilter.js'
import { getters, mutations } from './posts.js'
let state
let testAction
let testMutation
describe('getters', () => {
describe('isActive', () => {
@ -25,10 +25,10 @@ describe('getters', () => {
})
})
describe('postsFilter', () => {
describe('filter', () => {
it('returns filter', () => {
state = { filter: { author: { followedBy_some: { id: 7 } } } }
expect(getters.postsFilter(state)).toEqual({ author: { followedBy_some: { id: 7 } } })
expect(getters.filter(state)).toEqual({ author: { followedBy_some: { id: 7 } } })
})
})
@ -67,14 +67,48 @@ describe('getters', () => {
expect(getters.filteredByEmotions(state)).toEqual([])
})
})
describe('orderByOptions', () => {
it('returns all options regardless of current state', () => {
const $t = jest.fn(t => t)
expect(getters.orderOptions()({ $t })).toEqual([
{
key: 'store.posts.orderBy.oldest.label',
label: 'store.posts.orderBy.oldest.label',
icon: 'sort-amount-asc',
value: 'createdAt_asc',
},
{
key: 'store.posts.orderBy.newest.label',
label: 'store.posts.orderBy.newest.label',
icon: 'sort-amount-desc',
value: 'createdAt_desc',
},
])
})
})
describe('orderBy', () => {
it('returns value for graphql query', () => {
state = {
order: {
key: 'store.posts.orderBy.newest.label',
label: 'store.posts.orderBy.newest.label',
icon: 'sort-amount-desc',
value: 'createdAt_desc',
},
}
expect(getters.orderBy(state)).toEqual('createdAt_desc')
})
})
})
describe('mutations', () => {
describe('RESET_CATEGORIES', () => {
beforeEach(() => {
testAction = categoryId => {
testMutation = categoryId => {
mutations.RESET_CATEGORIES(state, categoryId)
return getters.postsFilter(state)
return getters.filter(state)
}
})
it('resets the categories filter', () => {
@ -84,37 +118,37 @@ describe('mutations', () => {
categories_some: { id_in: [23] },
},
}
expect(testAction(23)).toEqual({ author: { followedBy_some: { id: 7 } } })
expect(testMutation(23)).toEqual({ author: { followedBy_some: { id: 7 } } })
})
})
describe('TOGGLE_CATEGORY', () => {
beforeEach(() => {
testAction = categoryId => {
testMutation = categoryId => {
mutations.TOGGLE_CATEGORY(state, categoryId)
return getters.postsFilter(state)
return getters.filter(state)
}
})
it('creates category filter if empty', () => {
state = { filter: {} }
expect(testAction(23)).toEqual({ categories_some: { id_in: [23] } })
expect(testMutation(23)).toEqual({ categories_some: { id_in: [23] } })
})
it('adds category id not present', () => {
state = { filter: { categories_some: { id_in: [24] } } }
expect(testAction(23)).toEqual({ categories_some: { id_in: [24, 23] } })
expect(testMutation(23)).toEqual({ categories_some: { id_in: [24, 23] } })
})
it('removes category id if present', () => {
state = { filter: { categories_some: { id_in: [23, 24] } } }
const result = testAction(23)
const result = testMutation(23)
expect(result).toEqual({ categories_some: { id_in: [24] } })
})
it('removes category filter if empty', () => {
state = { filter: { categories_some: { id_in: [23] } } }
expect(testAction(23)).toEqual({})
expect(testMutation(23)).toEqual({})
})
it('does not get in the way of other filters', () => {
@ -124,15 +158,15 @@ describe('mutations', () => {
categories_some: { id_in: [23] },
},
}
expect(testAction(23)).toEqual({ author: { followedBy_some: { id: 7 } } })
expect(testMutation(23)).toEqual({ author: { followedBy_some: { id: 7 } } })
})
})
describe('TOGGLE_FILTER_BY_FOLLOWED', () => {
beforeEach(() => {
testAction = userId => {
testMutation = userId => {
mutations.TOGGLE_FILTER_BY_FOLLOWED(state, userId)
return getters.postsFilter(state)
return getters.filter(state)
}
})
@ -142,7 +176,7 @@ describe('mutations', () => {
})
it('attaches the id of the current user to the filter object', () => {
expect(testAction(4711)).toEqual({ author: { followedBy_some: { id: 4711 } } })
expect(testMutation(4711)).toEqual({ author: { followedBy_some: { id: 4711 } } })
})
})
@ -152,8 +186,23 @@ describe('mutations', () => {
})
it('remove the id of the current user from the filter object', () => {
expect(testAction(4711)).toEqual({})
expect(testMutation(4711)).toEqual({})
})
})
})
describe('SELECT_ORDER', () => {
beforeEach(() => {
testMutation = key => {
mutations.SELECT_ORDER(state, key)
return getters.orderBy(state)
}
})
it('switches the currently selected order', () => {
state = {
// does not matter
}
expect(testMutation('createdAt_asc')).toEqual('createdAt_asc')
})
})
})