refactor(webapp): store for categories (#8551)

* after authentification, query the categories if active and store them

* get categories from store

* use category store to get categories

* get categories from store

* mock store to have access to categories

* to get rid of the active categories config variable in the frontend, the Category query returns an empty array when categories are not active

* remove CATEGORIES_ACTIVE from .env

* should return string to avoid warnings in console

* replace all env calls for categories active by getter from store

* use categoriesActive getter

* ignore order of returned categories

* mixin to get the category infos from the store, to ensure, that the quey has been called

* fix misspelling

---------

Co-authored-by: Wolfgang Huß <wolle.huss@pjannto.com>
This commit is contained in:
Moriz Wahl 2025-05-27 15:03:26 +02:00 committed by GitHub
parent 5bec51ad5d
commit a3178a91b4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
39 changed files with 456 additions and 218 deletions

View File

@ -0,0 +1,97 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-call */
import { ApolloServer } from 'apollo-server-express'
import { createTestClient } from 'apollo-server-testing'
import gql from 'graphql-tag'
import databaseContext from '@context/database'
import Factory, { cleanDatabase } from '@db/factories'
import CONFIG from '@src/config'
import { categories } from '@src/constants/categories'
import createServer, { getContext } from '@src/server'
const database = databaseContext()
let server: ApolloServer
let query
beforeAll(async () => {
await cleanDatabase()
const authenticatedUser = null
// eslint-disable-next-line @typescript-eslint/require-await
const contextUser = async (_req) => authenticatedUser
const context = getContext({ user: contextUser, database })
server = createServer({ context }).server
const createTestClientResult = createTestClient(server)
query = createTestClientResult.query
for (const category of categories) {
await Factory.build('category', {
id: category.id,
slug: category.slug,
name: category.name,
icon: category.icon,
})
}
})
afterAll(() => {
void server.stop()
void database.driver.close()
database.neode.close()
})
const categoriesQuery = gql`
query {
Category {
id
slug
name
icon
}
}
`
describe('categroeis middleware', () => {
describe('categories are active', () => {
beforeEach(() => {
CONFIG.CATEGORIES_ACTIVE = true
})
it('returns the categories', async () => {
await expect(
query({
query: categoriesQuery,
}),
).resolves.toMatchObject({
data: {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
Category: expect.arrayContaining(categories),
},
errors: undefined,
})
})
})
describe('categories are not active', () => {
beforeEach(() => {
CONFIG.CATEGORIES_ACTIVE = false
})
it('returns an empty array though there are categories in the db', async () => {
await expect(
query({
query: categoriesQuery,
}),
).resolves.toMatchObject({
data: {
Category: [],
},
errors: undefined,
})
})
})
})

View File

@ -0,0 +1,16 @@
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-unsafe-call */
import CONFIG from '@src/config'
const checkCategoriesActive = (resolve, root, args, context, resolveInfo) => {
if (CONFIG.CATEGORIES_ACTIVE) {
return resolve(root, args, context, resolveInfo)
}
return []
}
export default {
Query: {
Category: checkCategoriesActive,
},
}

View File

@ -7,6 +7,7 @@ import CONFIG from '@config/index'
// eslint-disable-next-line import/no-cycle // eslint-disable-next-line import/no-cycle
import brandingMiddlewares from './branding/brandingMiddlewares' import brandingMiddlewares from './branding/brandingMiddlewares'
import categories from './categories'
import chatMiddleware from './chatMiddleware' import chatMiddleware from './chatMiddleware'
import excerpt from './excerptMiddleware' import excerpt from './excerptMiddleware'
import hashtags from './hashtags/hashtagsMiddleware' import hashtags from './hashtags/hashtagsMiddleware'
@ -46,6 +47,7 @@ const ocelotMiddlewares: MiddlewareOrder[] = [
{ order: -80, name: 'includedFields', middleware: includedFields }, { order: -80, name: 'includedFields', middleware: includedFields },
{ order: -70, name: 'orderBy', middleware: orderBy }, { order: -70, name: 'orderBy', middleware: orderBy },
{ order: -60, name: 'chatMiddleware', middleware: chatMiddleware }, { order: -60, name: 'chatMiddleware', middleware: chatMiddleware },
{ order: -50, name: 'categories', middleware: categories },
] ]
export default (schema) => { export default (schema) => {

View File

@ -5,7 +5,6 @@ GRAPHQL_URI=http://localhost:4000/
MAPBOX_TOKEN="pk.eyJ1IjoiYnVzZmFrdG9yIiwiYSI6ImNraDNiM3JxcDBhaWQydG1uczhpZWtpOW4ifQ.7TNRTO-o9aK1Y6MyW_Nd4g" MAPBOX_TOKEN="pk.eyJ1IjoiYnVzZmFrdG9yIiwiYSI6ImNraDNiM3JxcDBhaWQydG1uczhpZWtpOW4ifQ.7TNRTO-o9aK1Y6MyW_Nd4g"
PUBLIC_REGISTRATION=false PUBLIC_REGISTRATION=false
INVITE_REGISTRATION=true INVITE_REGISTRATION=true
CATEGORIES_ACTIVE=false
BADGES_ENABLED=true BADGES_ENABLED=true
INVITE_LINK_LIMIT=7 INVITE_LINK_LIMIT=7
NETWORK_NAME="Ocelot.social" NETWORK_NAME="Ocelot.social"

View File

@ -1,6 +1,6 @@
import { mount } from '@vue/test-utils' import { mount } from '@vue/test-utils'
import CategoriesSelect from './CategoriesSelect' import CategoriesSelect from './CategoriesSelect'
import Vue from 'vue' import Vuex from 'vuex'
const localVue = global.localVue const localVue = global.localVue
@ -12,7 +12,6 @@ describe('CategoriesSelect.vue', () => {
let environmentAndNature let environmentAndNature
let consumptionAndSustainablity let consumptionAndSustainablity
const propsData = { model: 'categoryIds' }
const categories = [ const categories = [
{ {
id: 'cat9', id: 'cat9',
@ -35,6 +34,20 @@ describe('CategoriesSelect.vue', () => {
id: 'cat8', id: 'cat8',
}, },
] ]
const propsData = { model: 'categoryIds' }
const categoriesMock = jest.fn().mockReturnValue(categories)
const storeMocks = {
getters: {
'categories/categories': categoriesMock,
'categories/isInitialized': jest.fn(() => true),
},
actions: {
'categories/init': jest.fn(),
},
}
beforeEach(() => { beforeEach(() => {
provide = { provide = {
$parentForm: { $parentForm: {
@ -48,7 +61,8 @@ describe('CategoriesSelect.vue', () => {
describe('shallowMount', () => { describe('shallowMount', () => {
const Wrapper = () => { const Wrapper = () => {
return mount(CategoriesSelect, { propsData, mocks, localVue, provide }) const store = new Vuex.Store(storeMocks)
return mount(CategoriesSelect, { propsData, mocks, localVue, provide, store })
} }
beforeEach(() => { beforeEach(() => {
@ -56,9 +70,7 @@ describe('CategoriesSelect.vue', () => {
}) })
describe('toggleCategory', () => { describe('toggleCategory', () => {
beforeEach(async () => { beforeEach(() => {
wrapper.vm.categories = categories
await Vue.nextTick()
democracyAndPolitics = wrapper.findAll('button').at(0) democracyAndPolitics = wrapper.findAll('button').at(0)
democracyAndPolitics.trigger('click') democracyAndPolitics.trigger('click')
}) })

View File

@ -1,7 +1,7 @@
<template> <template>
<section class="categories-select"> <section class="categories-select">
<base-button <base-button
v-for="category in categories" v-for="category in sortCategories(categories)"
:key="category.id" :key="category.id"
:data-test="categoryButtonsId(category.id)" :data-test="categoryButtonsId(category.id)"
@click="toggleCategory(category.id)" @click="toggleCategory(category.id)"
@ -20,10 +20,10 @@
</template> </template>
<script> <script>
import CategoryQuery from '~/graphql/CategoryQuery'
import { CATEGORIES_MAX } from '~/constants/categories.js' import { CATEGORIES_MAX } from '~/constants/categories.js'
import xor from 'lodash/xor' import xor from 'lodash/xor'
import SortCategories from '~/mixins/sortCategoriesMixin.js' import SortCategories from '~/mixins/sortCategoriesMixin.js'
import GetCategories from '~/mixins/getCategoriesMixin.js'
export default { export default {
inject: { inject: {
@ -31,14 +31,13 @@ export default {
default: null, default: null,
}, },
}, },
mixins: [SortCategories], mixins: [SortCategories, GetCategories],
props: { props: {
existingCategoryIds: { type: Array, default: () => [] }, existingCategoryIds: { type: Array, default: () => [] },
model: { type: String, required: true }, model: { type: String, required: true },
}, },
data() { data() {
return { return {
categories: null,
selectedMax: CATEGORIES_MAX, selectedMax: CATEGORIES_MAX,
selectedCategoryIds: this.existingCategoryIds, selectedCategoryIds: this.existingCategoryIds,
} }
@ -76,16 +75,6 @@ export default {
return `category-buttons-${categoryId}` return `category-buttons-${categoryId}`
}, },
}, },
apollo: {
Category: {
query() {
return CategoryQuery()
},
result({ data: { Category } }) {
this.categories = this.sortCategories(Category)
},
},
},
} }
</script> </script>

View File

@ -37,7 +37,7 @@ describe('ContributionForm.vue', () => {
const image = { sensitive: false, url: '/uploads/1562010976466-avataaars', aspectRatio: 1 } const image = { sensitive: false, url: '/uploads/1562010976466-avataaars', aspectRatio: 1 }
beforeEach(() => { beforeEach(() => {
mocks = { mocks = {
$t: jest.fn(), $t: jest.fn((t) => t),
$apollo: { $apollo: {
mutate: jest.fn().mockResolvedValueOnce({ mutate: jest.fn().mockResolvedValueOnce({
data: { data: {
@ -62,9 +62,6 @@ describe('ContributionForm.vue', () => {
back: jest.fn(), back: jest.fn(),
push: jest.fn(), push: jest.fn(),
}, },
$env: {
CATEGORIES_ACTIVE: false,
},
} }
propsData = {} propsData = {}
}) })
@ -82,9 +79,13 @@ describe('ContributionForm.vue', () => {
slug: 'you-yourself', slug: 'you-yourself',
} }
}, },
'categories/categoriesActive': jest.fn(() => false),
} }
const store = new Vuex.Store({ const store = new Vuex.Store({
getters, getters,
actions: {
'categories/init': jest.fn(),
},
}) })
const Wrapper = () => { const Wrapper = () => {
return mount(ContributionForm, { return mount(ContributionForm, {

View File

@ -197,8 +197,10 @@ import links from '~/constants/links.js'
import PageParamsLink from '~/components/_new/features/PageParamsLink/PageParamsLink.vue' import PageParamsLink from '~/components/_new/features/PageParamsLink/PageParamsLink.vue'
import DatePicker from 'vue2-datepicker' import DatePicker from 'vue2-datepicker'
import 'vue2-datepicker/scss/index.scss' import 'vue2-datepicker/scss/index.scss'
import GetCategories from '~/mixins/getCategoriesMixin.js'
export default { export default {
mixins: [GetCategories],
components: { components: {
Editor, Editor,
ImageUploader, ImageUploader,
@ -240,7 +242,6 @@ export default {
type: imageType = null, type: imageType = null,
} = image || {} } = image || {}
return { return {
categoriesActive: this.$env.CATEGORIES_ACTIVE,
links, links,
formData: { formData: {
title: title || '', title: title || '',

View File

@ -13,6 +13,24 @@ describe('CategoriesFilter.vue', () => {
} }
const getters = { const getters = {
'posts/filteredCategoryIds': jest.fn(() => []), 'posts/filteredCategoryIds': jest.fn(() => []),
'categories/categories': jest.fn().mockReturnValue([
{ id: 'cat4', name: 'Environment & Nature', icon: 'tree', slug: 'environment-nature' },
{
id: 'cat15',
name: 'Consumption & Sustainability',
icon: 'shopping-cart',
slug: 'consumption-sustainability',
},
{
id: 'cat9',
name: 'Democracy & Politics',
icon: 'university',
slug: 'democracy-politics',
},
]),
}
const actions = {
'categories/init': jest.fn(),
} }
const apolloMutationMock = jest.fn().mockResolvedValue({ const apolloMutationMock = jest.fn().mockResolvedValue({
@ -31,25 +49,8 @@ describe('CategoriesFilter.vue', () => {
} }
const Wrapper = () => { const Wrapper = () => {
const store = new Vuex.Store({ mutations, getters }) const store = new Vuex.Store({ mutations, getters, actions })
const wrapper = mount(CategoriesFilter, { mocks, localVue, store }) const wrapper = mount(CategoriesFilter, { mocks, localVue, store })
wrapper.setData({
categories: [
{ id: 'cat4', name: 'Environment & Nature', icon: 'tree', slug: 'environment-nature' },
{
id: 'cat15',
name: 'Consumption & Sustainability',
icon: 'shopping-cart',
slug: 'consumption-sustainability',
},
{
id: 'cat9',
name: 'Democracy & Politics',
icon: 'university',
slug: 'democracy-politics',
},
],
})
return wrapper return wrapper
} }
@ -75,7 +76,7 @@ describe('CategoriesFilter.vue', () => {
it('calls TOGGLE_CATEGORY when clicked', () => { it('calls TOGGLE_CATEGORY when clicked', () => {
environmentAndNatureButton = wrapper.findAll('.category-filter-list .base-button').at(0) environmentAndNatureButton = wrapper.findAll('.category-filter-list .base-button').at(0)
environmentAndNatureButton.trigger('click') environmentAndNatureButton.trigger('click')
expect(mutations['posts/TOGGLE_CATEGORY']).toHaveBeenCalledWith({}, 'cat4') expect(mutations['posts/TOGGLE_CATEGORY']).toHaveBeenCalledWith({}, 'cat15')
}) })
}) })

View File

@ -15,7 +15,7 @@
<div class="category-filter-list"> <div class="category-filter-list">
<!-- <ds-space margin="small" /> --> <!-- <ds-space margin="small" /> -->
<base-button <base-button
v-for="category in categories" v-for="category in sortCategories(categories)"
:key="category.id" :key="category.id"
@click="saveCategories(category.id)" @click="saveCategories(category.id)"
:filled="filteredCategoryIds.includes(category.id)" :filled="filteredCategoryIds.includes(category.id)"
@ -35,20 +35,15 @@
<script> <script>
import { mapGetters, mapMutations } from 'vuex' import { mapGetters, mapMutations } from 'vuex'
import CategoryQuery from '~/graphql/CategoryQuery.js'
import FilterMenuSection from '~/components/FilterMenu/FilterMenuSection' import FilterMenuSection from '~/components/FilterMenu/FilterMenuSection'
import SortCategories from '~/mixins/sortCategoriesMixin.js' import SortCategories from '~/mixins/sortCategoriesMixin.js'
import GetCategories from '~/mixins/getCategoriesMixin.js'
export default { export default {
components: { components: {
FilterMenuSection, FilterMenuSection,
}, },
mixins: [SortCategories], mixins: [SortCategories, GetCategories],
data() {
return {
categories: [],
}
},
computed: { computed: {
...mapGetters({ ...mapGetters({
filteredCategoryIds: 'posts/filteredCategoryIds', filteredCategoryIds: 'posts/filteredCategoryIds',
@ -68,18 +63,6 @@ export default {
this.$emit('updateCategories', categoryId) this.$emit('updateCategories', categoryId)
}, },
}, },
apollo: {
Category: {
query() {
return CategoryQuery()
},
update({ Category }) {
if (!Category) return []
this.categories = this.sortCategories(Category)
},
fetchPolicy: 'cache-and-network',
},
},
} }
</script> </script>
<style lang="scss"> <style lang="scss">

View File

@ -16,9 +16,11 @@
import Dropdown from '~/components/Dropdown' import Dropdown from '~/components/Dropdown'
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
import CategoriesFilter from './CategoriesFilter' import CategoriesFilter from './CategoriesFilter'
import GetCategories from '~/mixins/getCategoriesMixin.js'
export default { export default {
name: 'CategoriesMenu', name: 'CategoriesMenu',
mixins: [GetCategories],
components: { components: {
Dropdown, Dropdown,
CategoriesFilter, CategoriesFilter,
@ -27,11 +29,6 @@ export default {
placement: { type: String }, placement: { type: String },
offset: { type: [String, Number] }, offset: { type: [String, Number] },
}, },
data() {
return {
categoriesActive: this.$env.CATEGORIES_ACTIVE,
}
},
computed: { computed: {
...mapGetters({ ...mapGetters({
// TODO: implement visibility of active filter later on // TODO: implement visibility of active filter later on

View File

@ -8,15 +8,16 @@ let wrapper
describe('FilterMenu.vue', () => { describe('FilterMenu.vue', () => {
const mocks = { const mocks = {
$t: jest.fn((string) => string), $t: jest.fn((string) => string),
$env: {
CATEGORIES_ACTIVE: true,
},
} }
const getters = { const getters = {
'posts/isActive': () => false, 'posts/isActive': () => false,
'posts/filteredPostTypes': () => [], 'posts/filteredPostTypes': () => [],
'posts/orderBy': () => 'createdAt_desc', 'posts/orderBy': () => 'createdAt_desc',
'categories/categoriesActive': () => false,
}
const actions = {
'categories/init': jest.fn(),
} }
const stubs = { const stubs = {
@ -28,7 +29,7 @@ describe('FilterMenu.vue', () => {
} }
const Wrapper = () => { const Wrapper = () => {
const store = new Vuex.Store({ getters }) const store = new Vuex.Store({ getters, actions })
return mount(FilterMenu, { mocks, localVue, store, stubs }) return mount(FilterMenu, { mocks, localVue, store, stubs })
} }

View File

@ -36,8 +36,10 @@ import OrderByFilter from './OrderByFilter'
import CategoriesFilter from './CategoriesFilter' import CategoriesFilter from './CategoriesFilter'
import LabeledButton from '~/components/_new/generic/LabeledButton/LabeledButton' import LabeledButton from '~/components/_new/generic/LabeledButton/LabeledButton'
import SaveCategories from '~/graphql/SaveCategories.js' import SaveCategories from '~/graphql/SaveCategories.js'
import GetCategories from '~/mixins/getCategoriesMixin.js'
export default { export default {
mixins: [GetCategories],
components: { components: {
EventsByFilter, EventsByFilter,
FollowingFilter, FollowingFilter,
@ -46,11 +48,6 @@ export default {
PostTypeFilter, PostTypeFilter,
LabeledButton, LabeledButton,
}, },
data() {
return {
categoriesActive: this.$env ? this.$env.CATEGORIES_ACTIVE : false,
}
},
computed: { computed: {
...mapGetters({ ...mapGetters({
filteredPostTypes: 'posts/filteredPostTypes', filteredPostTypes: 'posts/filteredPostTypes',

View File

@ -19,13 +19,16 @@ describe('FollowingFilter', () => {
'posts/filteredByUsersFollowed': jest.fn(), 'posts/filteredByUsersFollowed': jest.fn(),
'posts/filteredByPostsInMyGroups': jest.fn(), 'posts/filteredByPostsInMyGroups': jest.fn(),
} }
const actions = {
'categories/init': jest.fn(),
}
const mocks = { const mocks = {
$t: jest.fn((string) => string), $t: jest.fn((string) => string),
} }
const Wrapper = () => { const Wrapper = () => {
const store = new Vuex.Store({ mutations, getters }) const store = new Vuex.Store({ mutations, getters, actions })
const wrapper = mount(FollowingFilter, { mocks, localVue, store }) const wrapper = mount(FollowingFilter, { mocks, localVue, store })
return wrapper return wrapper
} }

View File

@ -15,13 +15,16 @@ describe('OrderByFilter', () => {
'posts/orderedByCreationDate': () => true, 'posts/orderedByCreationDate': () => true,
'posts/orderBy': () => 'createdAt_desc', 'posts/orderBy': () => 'createdAt_desc',
} }
const actions = {
'categories/init': jest.fn(),
}
const mocks = { const mocks = {
$t: jest.fn((string) => string), $t: jest.fn((string) => string),
} }
const Wrapper = () => { const Wrapper = () => {
const store = new Vuex.Store({ mutations, getters }) const store = new Vuex.Store({ mutations, getters, actions })
const wrapper = mount(OrderByFilter, { mocks, localVue, store }) const wrapper = mount(OrderByFilter, { mocks, localVue, store })
return wrapper return wrapper
} }

View File

@ -1,5 +1,6 @@
import { mount } from '@vue/test-utils' import { mount } from '@vue/test-utils'
import GroupForm from './GroupForm.vue' import GroupForm from './GroupForm.vue'
import Vuex from 'vuex'
const localVue = global.localVue const localVue = global.localVue
@ -15,19 +16,27 @@ const propsData = {
describe('GroupForm', () => { describe('GroupForm', () => {
let wrapper let wrapper
let mocks let mocks
let storeMocks
let store
beforeEach(() => { beforeEach(() => {
mocks = { mocks = {
$t: jest.fn(), $t: jest.fn(),
$env: { }
CATEGORIES_ACTIVE: true, storeMocks = {
getters: {
'categories/categoriesActive': () => false,
},
actions: {
'categories/init': jest.fn(),
}, },
} }
store = new Vuex.Store(storeMocks)
}) })
describe('mount', () => { describe('mount', () => {
const Wrapper = () => { const Wrapper = () => {
return mount(GroupForm, { propsData, mocks, localVue, stubs }) return mount(GroupForm, { propsData, mocks, localVue, stubs, store })
} }
beforeEach(() => { beforeEach(() => {

View File

@ -177,11 +177,13 @@ import {
import Editor from '~/components/Editor/Editor' import Editor from '~/components/Editor/Editor'
import ActionRadiusSelect from '~/components/Select/ActionRadiusSelect' import ActionRadiusSelect from '~/components/Select/ActionRadiusSelect'
import { queryLocations } from '~/graphql/location' import { queryLocations } from '~/graphql/location'
import GetCategories from '~/mixins/getCategoriesMixin.js'
let timeout let timeout
export default { export default {
name: 'GroupForm', name: 'GroupForm',
mixins: [GetCategories],
components: { components: {
CategoriesSelect, CategoriesSelect,
Editor, Editor,
@ -203,7 +205,6 @@ export default {
const { name, slug, groupType, about, description, actionRadius, locationName, categories } = const { name, slug, groupType, about, description, actionRadius, locationName, categories } =
this.group this.group
return { return {
categoriesActive: this.$env.CATEGORIES_ACTIVE,
disabled: false, disabled: false,
groupTypeOptions: ['public', 'closed', 'hidden'], groupTypeOptions: ['public', 'closed', 'hidden'],
loadingGeo: false, loadingGeo: false,

View File

@ -79,9 +79,11 @@
<script> <script>
import Category from '~/components/Category' import Category from '~/components/Category'
import GroupContentMenu from '~/components/ContentMenu/GroupContentMenu' import GroupContentMenu from '~/components/ContentMenu/GroupContentMenu'
import GetCategories from '~/mixins/getCategoriesMixin.js'
export default { export default {
name: 'GroupTeaser', name: 'GroupTeaser',
mixins: [GetCategories],
components: { components: {
Category, Category,
GroupContentMenu, GroupContentMenu,
@ -96,11 +98,6 @@ export default {
default: () => {}, default: () => {},
}, },
}, },
data() {
return {
categoriesActive: this.$env.CATEGORIES_ACTIVE,
}
},
computed: { computed: {
descriptionExcerpt() { descriptionExcerpt() {
return this.$filters.removeLinks(this.group.descriptionExcerpt) return this.$filters.removeLinks(this.group.descriptionExcerpt)

View File

@ -293,8 +293,10 @@ import SearchField from '~/components/features/SearchField/SearchField.vue'
import NotificationMenu from '~/components/NotificationMenu/NotificationMenu' import NotificationMenu from '~/components/NotificationMenu/NotificationMenu'
import links from '~/constants/links.js' import links from '~/constants/links.js'
import PageParamsLink from '~/components/_new/features/PageParamsLink/PageParamsLink.vue' import PageParamsLink from '~/components/_new/features/PageParamsLink/PageParamsLink.vue'
import GetCategories from '~/mixins/getCategoriesMixin.js'
export default { export default {
mixins: [GetCategories],
components: { components: {
AvatarMenu, AvatarMenu,
ChatNotificationMenu, ChatNotificationMenu,
@ -327,7 +329,6 @@ export default {
mobileSearchVisible: false, mobileSearchVisible: false,
toggleMobileMenu: false, toggleMobileMenu: false,
inviteRegistration: this.$env.INVITE_REGISTRATION === true, // for 'false' in .env INVITE_REGISTRATION is of type undefined and not(!) boolean false, because of internal handling, inviteRegistration: this.$env.INVITE_REGISTRATION === true, // for 'false' in .env INVITE_REGISTRATION is of type undefined and not(!) boolean false, because of internal handling,
categoriesActive: this.$env.CATEGORIES_ACTIVE,
} }
}, },
computed: { computed: {

View File

@ -15,11 +15,9 @@ const stubs = {
} }
const authUserMock = jest.fn().mockReturnValue({ activeCategories: [] }) const authUserMock = jest.fn().mockReturnValue({ activeCategories: [] })
const apolloQueryMock = jest.fn().mockResolvedValue({ const categoriesMock = jest
data: { .fn()
Category: [{ id: 'cat0' }, { id: 'cat1' }, { id: 'cat2' }, { id: 'cat3' }, { id: 'cat4' }], .mockReturnValue([{ id: 'cat0' }, { id: 'cat1' }, { id: 'cat2' }, { id: 'cat3' }, { id: 'cat4' }])
},
})
describe('LoginForm', () => { describe('LoginForm', () => {
let mocks let mocks
@ -36,6 +34,7 @@ describe('LoginForm', () => {
getters: { getters: {
'auth/pending': () => false, 'auth/pending': () => false,
'auth/user': authUserMock, 'auth/user': authUserMock,
'categories/categories': categoriesMock,
}, },
actions: { actions: {
'auth/login': jest.fn(), 'auth/login': jest.fn(),
@ -52,9 +51,6 @@ describe('LoginForm', () => {
success: jest.fn(), success: jest.fn(),
error: jest.fn(), error: jest.fn(),
}, },
$apollo: {
query: apolloQueryMock,
},
} }
return mount(LoginForm, { mocks, localVue, propsData, store, stubs }) return mount(LoginForm, { mocks, localVue, propsData, store, stubs })
} }

View File

@ -59,7 +59,6 @@ import LocaleSwitch from '~/components/LocaleSwitch/LocaleSwitch'
import Logo from '~/components/Logo/Logo' import Logo from '~/components/Logo/Logo'
import ShowPassword from '../ShowPassword/ShowPassword.vue' import ShowPassword from '../ShowPassword/ShowPassword.vue'
import { mapGetters, mapMutations } from 'vuex' import { mapGetters, mapMutations } from 'vuex'
import CategoryQuery from '~/graphql/CategoryQuery'
export default { export default {
components: { components: {
@ -88,6 +87,7 @@ export default {
}, },
...mapGetters({ ...mapGetters({
currentUser: 'auth/user', currentUser: 'auth/user',
categories: 'categories/categories',
}), }),
}, },
methods: { methods: {
@ -99,13 +99,9 @@ export default {
const { email, password } = this.form const { email, password } = this.form
try { try {
await this.$store.dispatch('auth/login', { email, password }) await this.$store.dispatch('auth/login', { email, password })
const result = await this.$apollo.query({
query: CategoryQuery(),
})
const categories = result.data.Category
if (this.currentUser && this.currentUser.activeCategories) { if (this.currentUser && this.currentUser.activeCategories) {
this.resetCategories() this.resetCategories()
if (this.currentUser.activeCategories.length < categories.length) { if (this.currentUser.activeCategories.length < this.categories.length) {
this.currentUser.activeCategories.forEach((categoryId) => { this.currentUser.activeCategories.forEach((categoryId) => {
this.toggleCategory(categoryId) this.toggleCategory(categoryId)
}) })

View File

@ -12,6 +12,7 @@ describe('PostTeaser', () => {
let mocks let mocks
let propsData let propsData
let getters let getters
let actions
let Wrapper let Wrapper
let wrapper let wrapper
@ -47,21 +48,22 @@ describe('PostTeaser', () => {
data: { DeletePost: { id: 'deleted-post-id' } }, data: { DeletePost: { id: 'deleted-post-id' } },
}), }),
}, },
$env: {
CATEGORIES_ACTIVE: false,
},
} }
getters = { getters = {
'auth/isModerator': () => false, 'auth/isModerator': () => false,
'auth/user': () => { 'auth/user': () => {
return {} return {}
}, },
'categories/categoriesActive': () => false,
}
actions = {
'categories/init': jest.fn(),
} }
}) })
describe('shallowMount', () => { describe('shallowMount', () => {
Wrapper = () => { Wrapper = () => {
store = new Vuex.Store({ getters }) store = new Vuex.Store({ getters, actions })
return shallowMount(PostTeaser, { return shallowMount(PostTeaser, {
store, store,
propsData, propsData,
@ -114,6 +116,7 @@ describe('PostTeaser', () => {
Wrapper = () => { Wrapper = () => {
const store = new Vuex.Store({ const store = new Vuex.Store({
getters, getters,
actions,
}) })
return mount(PostTeaser, { return mount(PostTeaser, {
stubs, stubs,

View File

@ -137,9 +137,11 @@ import UserTeaser from '~/components/UserTeaser/UserTeaser'
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
import PostMutations from '~/graphql/PostMutations' import PostMutations from '~/graphql/PostMutations'
import { postMenuModalsData, deletePostMutation } from '~/components/utils/PostHelpers' import { postMenuModalsData, deletePostMutation } from '~/components/utils/PostHelpers'
import GetCategories from '~/mixins/getCategoriesMixin.js'
export default { export default {
name: 'PostTeaser', name: 'PostTeaser',
mixins: [GetCategories],
components: { components: {
Category, Category,
ContentMenu, ContentMenu,
@ -164,11 +166,6 @@ export default {
default: () => {}, default: () => {},
}, },
}, },
data() {
return {
categoriesActive: this.$env.CATEGORIES_ACTIVE,
}
},
mounted() { mounted() {
const { image } = this.post const { image } = this.post
if (!image) return if (!image) return

View File

@ -26,15 +26,13 @@ describe('SearchResults', () => {
beforeEach(() => { beforeEach(() => {
mocks = { mocks = {
$t: jest.fn(), $t: jest.fn(),
$env: {
CATEGORIES_ACTIVE: false,
},
} }
getters = { getters = {
'auth/user': () => { 'auth/user': () => {
return { id: 'u343', name: 'Matt' } return { id: 'u343', name: 'Matt' }
}, },
'auth/isModerator': () => false, 'auth/isModerator': () => false,
'categories/categoriesActive': () => false,
} }
propsData = { propsData = {
pageSize: 12, pageSize: 12,

View File

@ -34,7 +34,6 @@ const options = {
// Cookies // Cookies
COOKIE_EXPIRE_TIME: process.env.COOKIE_EXPIRE_TIME || 730, // Two years by default COOKIE_EXPIRE_TIME: process.env.COOKIE_EXPIRE_TIME || 730, // Two years by default
COOKIE_HTTPS_ONLY: process.env.COOKIE_HTTPS_ONLY || process.env.NODE_ENV === 'production', // ensure true in production if not set explicitly COOKIE_HTTPS_ONLY: process.env.COOKIE_HTTPS_ONLY || process.env.NODE_ENV === 'production', // ensure true in production if not set explicitly
CATEGORIES_ACTIVE: process.env.CATEGORIES_ACTIVE === 'true' || false,
BADGES_ENABLED: process.env.BADGES_ENABLED === 'true' || false, BADGES_ENABLED: process.env.BADGES_ENABLED === 'true' || false,
INVITE_LINK_LIMIT: process.env.INVITE_LINK_LIMIT || 7, INVITE_LINK_LIMIT: process.env.INVITE_LINK_LIMIT || 7,
NETWORK_NAME: process.env.NETWORK_NAME || 'Ocelot.social', NETWORK_NAME: process.env.NETWORK_NAME || 'Ocelot.social',

View File

@ -17,7 +17,7 @@ module.exports = {
], ],
coverageThreshold: { coverageThreshold: {
global: { global: {
lines: 83, lines: 82,
}, },
}, },
coverageProvider: 'v8', coverageProvider: 'v8',

View File

@ -0,0 +1,19 @@
import { mapGetters, mapActions } from 'vuex'
export default {
computed: {
...mapGetters({
categories: 'categories/categories',
isInitialized: 'categories/isInitialized',
categoriesActive: 'categories/categoriesActive',
}),
},
methods: {
...mapActions({
storeInit: 'categories/init',
}),
},
async created() {
if (!this.storeIsInizialized) await this.storeInit()
},
}

View File

@ -1,5 +1,6 @@
import GroupProfileSlug from './_slug.vue' import GroupProfileSlug from './_slug.vue'
import { render, screen, fireEvent } from '@testing-library/vue' import { render, screen, fireEvent } from '@testing-library/vue'
import Vuex from 'vuex'
const localVue = global.localVue const localVue = global.localVue
@ -32,11 +33,26 @@ describe('GroupProfileSlug', () => {
let bobDerBaumeister let bobDerBaumeister
let huey let huey
const currentUserMock = jest.fn()
const getters = {
'auth/user': currentUserMock,
'auth/isModerator': () => false,
'categories/categoriesActive': () => true,
'categories/categories': () => [{ id: 'cat1' }],
}
const actions = {
'categories/init': jest.fn(),
}
const store = new Vuex.Store({
getters,
actions,
})
beforeEach(() => { beforeEach(() => {
mocks = { mocks = {
$env: {
CATEGORIES_ACTIVE: true,
},
// post: { // post: {
// id: 'p23', // id: 'p23',
// name: 'It is a post', // name: 'It is a post',
@ -213,6 +229,7 @@ describe('GroupProfileSlug', () => {
localVue, localVue,
data, data,
stubs, stubs,
store,
}) })
} }
@ -220,12 +237,7 @@ describe('GroupProfileSlug', () => {
describe('given a current user', () => { describe('given a current user', () => {
describe('as group owner "peter-lustig"', () => { describe('as group owner "peter-lustig"', () => {
beforeEach(() => { beforeEach(() => {
mocks.$store = { currentUserMock.mockReturnValue(peterLustig)
getters: {
'auth/user': peterLustig,
'auth/isModerator': () => false,
},
}
wrapper = Wrapper(() => { wrapper = Wrapper(() => {
return { return {
Group: [ Group: [
@ -265,12 +277,7 @@ describe('GroupProfileSlug', () => {
describe('as usual member "jenny-rostock"', () => { describe('as usual member "jenny-rostock"', () => {
beforeEach(() => { beforeEach(() => {
mocks.$store = { currentUserMock.mockReturnValue(jennyRostock)
getters: {
'auth/user': jennyRostock,
'auth/isModerator': () => false,
},
}
wrapper = Wrapper(() => { wrapper = Wrapper(() => {
return { return {
Group: [ Group: [
@ -291,12 +298,7 @@ describe('GroupProfileSlug', () => {
describe('as pending member "bob-der-baumeister"', () => { describe('as pending member "bob-der-baumeister"', () => {
beforeEach(() => { beforeEach(() => {
mocks.$store = { currentUserMock.mockReturnValue(bobDerBaumeister)
getters: {
'auth/user': bobDerBaumeister,
'auth/isModerator': () => false,
},
}
wrapper = Wrapper(() => { wrapper = Wrapper(() => {
return { return {
Group: [ Group: [
@ -317,12 +319,7 @@ describe('GroupProfileSlug', () => {
describe('as none(!) member "huey"', () => { describe('as none(!) member "huey"', () => {
beforeEach(() => { beforeEach(() => {
mocks.$store = { currentUserMock.mockReturnValue(huey)
getters: {
'auth/user': huey,
'auth/isModerator': () => false,
},
}
wrapper = Wrapper(() => { wrapper = Wrapper(() => {
return { return {
Group: [ Group: [
@ -346,12 +343,7 @@ describe('GroupProfileSlug', () => {
describe('given a current user', () => { describe('given a current user', () => {
describe('as group owner "peter-lustig"', () => { describe('as group owner "peter-lustig"', () => {
beforeEach(() => { beforeEach(() => {
mocks.$store = { currentUserMock.mockReturnValue(peterLustig)
getters: {
'auth/user': peterLustig,
'auth/isModerator': () => false,
},
}
wrapper = Wrapper(() => { wrapper = Wrapper(() => {
return { return {
Group: [ Group: [
@ -372,12 +364,7 @@ describe('GroupProfileSlug', () => {
describe('as usual member "jenny-rostock"', () => { describe('as usual member "jenny-rostock"', () => {
beforeEach(() => { beforeEach(() => {
mocks.$store = { currentUserMock.mockReturnValue(jennyRostock)
getters: {
'auth/user': jennyRostock,
'auth/isModerator': () => false,
},
}
wrapper = Wrapper(() => { wrapper = Wrapper(() => {
return { return {
Group: [ Group: [
@ -421,12 +408,7 @@ describe('GroupProfileSlug', () => {
describe('as pending member "bob-der-baumeister"', () => { describe('as pending member "bob-der-baumeister"', () => {
beforeEach(() => { beforeEach(() => {
mocks.$store = { currentUserMock.mockReturnValue(bobDerBaumeister)
getters: {
'auth/user': bobDerBaumeister,
'auth/isModerator': () => false,
},
}
wrapper = Wrapper(() => { wrapper = Wrapper(() => {
return { return {
Group: [ Group: [
@ -447,12 +429,7 @@ describe('GroupProfileSlug', () => {
describe('as none(!) member "huey"', () => { describe('as none(!) member "huey"', () => {
beforeEach(() => { beforeEach(() => {
mocks.$store = { currentUserMock.mockReturnValue(huey)
getters: {
'auth/user': huey,
'auth/isModerator': () => false,
},
}
wrapper = Wrapper(() => { wrapper = Wrapper(() => {
return { return {
Group: [ Group: [
@ -477,12 +454,7 @@ describe('GroupProfileSlug', () => {
describe('given a current user', () => { describe('given a current user', () => {
describe('as group owner "peter-lustig"', () => { describe('as group owner "peter-lustig"', () => {
beforeEach(() => { beforeEach(() => {
mocks.$store = { currentUserMock.mockReturnValue(peterLustig)
getters: {
'auth/user': peterLustig,
'auth/isModerator': () => false,
},
}
wrapper = Wrapper(() => { wrapper = Wrapper(() => {
return { return {
Group: [ Group: [
@ -503,12 +475,7 @@ describe('GroupProfileSlug', () => {
describe('as usual member "jenny-rostock"', () => { describe('as usual member "jenny-rostock"', () => {
beforeEach(() => { beforeEach(() => {
mocks.$store = { currentUserMock.mockReturnValue(jennyRostock)
getters: {
'auth/user': jennyRostock,
'auth/isModerator': () => false,
},
}
wrapper = Wrapper(() => { wrapper = Wrapper(() => {
return { return {
Group: [ Group: [
@ -529,12 +496,7 @@ describe('GroupProfileSlug', () => {
describe('as pending member "bob-der-baumeister"', () => { describe('as pending member "bob-der-baumeister"', () => {
beforeEach(() => { beforeEach(() => {
mocks.$store = { currentUserMock.mockReturnValue(bobDerBaumeister)
getters: {
'auth/user': bobDerBaumeister,
'auth/isModerator': () => false,
},
}
wrapper = Wrapper(() => { wrapper = Wrapper(() => {
return { return {
Group: [ Group: [
@ -555,12 +517,7 @@ describe('GroupProfileSlug', () => {
describe('as none(!) member "huey"', () => { describe('as none(!) member "huey"', () => {
beforeEach(() => { beforeEach(() => {
mocks.$store = { currentUserMock.mockReturnValue(huey)
getters: {
'auth/user': huey,
'auth/isModerator': () => false,
},
}
wrapper = Wrapper(() => { wrapper = Wrapper(() => {
return { return {
Group: [ Group: [

View File

@ -316,6 +316,8 @@ import PostTeaser from '~/components/PostTeaser/PostTeaser.vue'
import ProfileAvatar from '~/components/_new/generic/ProfileAvatar/ProfileAvatar' import ProfileAvatar from '~/components/_new/generic/ProfileAvatar/ProfileAvatar'
import ProfileList from '~/components/features/ProfileList/ProfileList' import ProfileList from '~/components/features/ProfileList/ProfileList'
import SortCategories from '~/mixins/sortCategoriesMixin.js' import SortCategories from '~/mixins/sortCategoriesMixin.js'
import { mapGetters } from 'vuex'
import GetCategories from '~/mixins/getCategoriesMixin.js'
// import SocialMedia from '~/components/SocialMedia/SocialMedia' // import SocialMedia from '~/components/SocialMedia/SocialMedia'
// import TabNavigation from '~/components/_new/generic/TabNavigation/TabNavigation' // import TabNavigation from '~/components/_new/generic/TabNavigation/TabNavigation'
@ -346,7 +348,7 @@ export default {
// SocialMedia, // SocialMedia,
// TabNavigation, // TabNavigation,
}, },
mixins: [postListActions, SortCategories], mixins: [postListActions, SortCategories, GetCategories],
transition: { transition: {
name: 'slide-up', name: 'slide-up',
mode: 'out-in', mode: 'out-in',
@ -360,7 +362,6 @@ export default {
// const filter = tabToFilterMapping({ tab: 'post', id: this.$route.params.id }) // const filter = tabToFilterMapping({ tab: 'post', id: this.$route.params.id })
const filter = { group: { id: this.$route.params.id } } const filter = { group: { id: this.$route.params.id } }
return { return {
categoriesActive: this.$env.CATEGORIES_ACTIVE,
loadGroupMembers: false, loadGroupMembers: false,
posts: [], posts: [],
hasMore: true, hasMore: true,
@ -378,9 +379,9 @@ export default {
} }
}, },
computed: { computed: {
currentUser() { ...mapGetters({
return this.$store.getters['auth/user'] currentUser: 'auth/user',
}, }),
group() { group() {
return this.Group && this.Group[0] ? this.Group[0] : {} return this.Group && this.Group[0] ? this.Group[0] : {}
}, },

View File

@ -36,8 +36,13 @@ describe('PostIndex', () => {
'auth/user': () => { 'auth/user': () => {
return { id: 'u23' } return { id: 'u23' }
}, },
'categories/categoriesActive': () => true,
'categories/categories': () => ['cat1', 'cat2', 'cat3'],
}, },
mutations, mutations,
actions: {
'categories/init': jest.fn(),
},
}) })
mocks = { mocks = {
$t: (key) => key, $t: (key) => key,
@ -79,9 +84,6 @@ describe('PostIndex', () => {
$route: { $route: {
query: {}, query: {},
}, },
$env: {
CATEGORIES_ACTIVE: true,
},
} }
}) })

View File

@ -155,6 +155,7 @@ import UpdateQuery from '~/components/utils/UpdateQuery'
import FilterMenuComponent from '~/components/FilterMenu/FilterMenuComponent' import FilterMenuComponent from '~/components/FilterMenu/FilterMenuComponent'
import { SHOW_CONTENT_FILTER_MASONRY_GRID } from '~/constants/filter.js' import { SHOW_CONTENT_FILTER_MASONRY_GRID } from '~/constants/filter.js'
import { POST_ADD_BUTTON_POSITION_TOP } from '~/constants/posts.js' import { POST_ADD_BUTTON_POSITION_TOP } from '~/constants/posts.js'
import GetCategories from '~/mixins/getCategoriesMixin.js'
export default { export default {
components: { components: {
@ -167,7 +168,7 @@ export default {
FilterMenuComponent, FilterMenuComponent,
HeaderButton, HeaderButton,
}, },
mixins: [postListActions, mobile()], mixins: [postListActions, mobile(), GetCategories],
data() { data() {
const { hashtag = null } = this.$route.query const { hashtag = null } = this.$route.query
return { return {
@ -184,7 +185,6 @@ export default {
offset: 0, offset: 0,
pageSize: 12, pageSize: 12,
hashtag, hashtag,
categoriesActive: this.$env.CATEGORIES_ACTIVE,
SHOW_CONTENT_FILTER_MASONRY_GRID, SHOW_CONTENT_FILTER_MASONRY_GRID,
POST_ADD_BUTTON_POSITION_TOP, POST_ADD_BUTTON_POSITION_TOP,
} }

View File

@ -42,6 +42,10 @@ describe('PostSlug', () => {
return { id: '1stUser' } return { id: '1stUser' }
}, },
'auth/isModerator': () => false, 'auth/isModerator': () => false,
'categories/categoriesActive': () => false,
},
actions: {
'categories/init': jest.fn(),
}, },
}) })
const propsData = {} const propsData = {}
@ -73,9 +77,6 @@ describe('PostSlug', () => {
query: jest.fn().mockResolvedValue({ data: { PostEmotionsCountByEmotion: {} } }), query: jest.fn().mockResolvedValue({ data: { PostEmotionsCountByEmotion: {} } }),
}, },
$scrollTo: jest.fn(), $scrollTo: jest.fn(),
$env: {
CATEGORIES_ACTIVE: false,
},
} }
stubs = { stubs = {
'client-only': true, 'client-only': true,

View File

@ -182,6 +182,7 @@ import { groupQuery } from '~/graphql/groups'
import PostMutations from '~/graphql/PostMutations' import PostMutations from '~/graphql/PostMutations'
import links from '~/constants/links.js' import links from '~/constants/links.js'
import SortCategories from '~/mixins/sortCategoriesMixin.js' import SortCategories from '~/mixins/sortCategoriesMixin.js'
import GetCategories from '~/mixins/getCategoriesMixin.js'
export default { export default {
name: 'PostSlug', name: 'PostSlug',
@ -203,7 +204,7 @@ export default {
PageParamsLink, PageParamsLink,
UserTeaser, UserTeaser,
}, },
mixins: [SortCategories], mixins: [SortCategories, GetCategories],
head() { head() {
return { return {
title: this.title, title: this.title,
@ -219,7 +220,6 @@ export default {
blurred: false, blurred: false,
blocked: null, blocked: null,
postAuthor: null, postAuthor: null,
categoriesActive: this.$env.CATEGORIES_ACTIVE,
group: null, group: null,
} }
}, },

View File

@ -1,5 +1,6 @@
import { mount } from '@vue/test-utils' import { mount } from '@vue/test-utils'
import create from './create.vue' import create from './create.vue'
import Vuex from 'vuex'
const localVue = global.localVue const localVue = global.localVue
@ -8,9 +9,6 @@ describe('create.vue', () => {
const mocks = { const mocks = {
$t: jest.fn(), $t: jest.fn(),
$env: {
CATEGORIES_ACTIVE: false,
},
$route: { $route: {
query: { query: {
groupId: null, groupId: null,
@ -23,8 +21,14 @@ describe('create.vue', () => {
} }
describe('mount', () => { describe('mount', () => {
const store = new Vuex.Store({
getters: {
'categories/categoriesActive': () => false,
},
})
const Wrapper = () => { const Wrapper = () => {
return mount(create, { mocks, localVue, stubs }) return mount(create, { mocks, localVue, stubs, store })
} }
beforeEach(() => { beforeEach(() => {

View File

@ -39,15 +39,17 @@ describe('post/_id.vue', () => {
}), }),
}, },
}, },
$env: {
CATEGORIES_ACTIVE: false,
},
} }
store = new Vuex.Store({ store = new Vuex.Store({
getters: { getters: {
'auth/user': () => { 'auth/user': () => {
return { id: userId } return { id: userId }
}, },
'categories/categories': jest.fn(() => []),
'categories/categoriesActive': () => false,
},
actions: {
'categories/init': jest.fn(),
}, },
}) })
if (asyncData) { if (asyncData) {

View File

@ -106,6 +106,7 @@ export const actions = {
await this.app.$apolloHelpers.onLogin(login) await this.app.$apolloHelpers.onLogin(login)
commit('SET_TOKEN', login) commit('SET_TOKEN', login)
await dispatch('fetchCurrentUser') await dispatch('fetchCurrentUser')
await dispatch('categories/init', null, { root: true })
if (cookies.get(metadata.COOKIE_NAME) === undefined) { if (cookies.get(metadata.COOKIE_NAME) === undefined) {
throw new Error('no-cookie') throw new Error('no-cookie')
} }

View File

@ -175,8 +175,11 @@ describe('actions', () => {
expect(commit.mock.calls).toEqual(expect.arrayContaining([['SET_TOKEN', token]])) expect(commit.mock.calls).toEqual(expect.arrayContaining([['SET_TOKEN', token]]))
}) })
it('fetches the user', () => { it('fetches the user and initializes categories', () => {
expect(dispatch.mock.calls).toEqual([['fetchCurrentUser']]) expect(dispatch.mock.calls).toEqual([
['fetchCurrentUser'],
['categories/init', null, { root: true }],
])
}) })
it('saves pending flags in order', () => { it('saves pending flags in order', () => {

View File

@ -0,0 +1,44 @@
import CategoryQuery from '~/graphql/CategoryQuery'
export const state = () => {
return {
categories: [],
isInitialized: false,
}
}
export const mutations = {
SET_CATEGORIES(state, categories) {
state.categories = categories || []
},
SET_INIZIALIZED(state) {
state.isInitialized = true
},
}
export const getters = {
categories(state) {
return state.categories
},
categoriesActive(state) {
return !!state.categories.length
},
isInitialized(state) {
return state.isInitialized
},
}
export const actions = {
async init({ commit }) {
try {
const client = this.app.apolloProvider.defaultClient
const {
data: { Category: categories },
} = await client.query({ query: CategoryQuery() })
commit('SET_CATEGORIES', categories)
commit('SET_INIZIALIZED')
} catch (err) {
throw new Error('Could not query categories')
}
},
}

View File

@ -0,0 +1,105 @@
import { state, mutations, getters, actions } from './categories'
import CategoryQuery from '~/graphql/CategoryQuery'
describe('categories store', () => {
describe('initial state', () => {
it('sets no categories and is not inizialized', () => {
expect(state()).toEqual({
categories: [],
isInitialized: false,
})
})
})
describe('getters', () => {
describe('categoriesActive', () => {
it('returns true if there are categories', () => {
const state = { categories: ['cat1', 'cat2'] }
expect(getters.categoriesActive(state)).toBe(true)
})
it('returns false if there are no categories', () => {
const state = { categories: [] }
expect(getters.categoriesActive(state)).toBe(false)
})
})
})
describe('mutations', () => {
let testMutation
describe('SET_CATEGORIES', () => {
beforeEach(() => {
testMutation = (categories) => {
mutations.SET_CATEGORIES(state, categories)
return getters.categories(state)
}
})
it('sets categories to [] if value is undefined', () => {
expect(testMutation(undefined)).toEqual([])
})
it('sets categories correctly', () => {
expect(testMutation(['cat1', 'cat2', 'cat3'])).toEqual(['cat1', 'cat2', 'cat3'])
})
})
describe('SET_INIZIALIZED', () => {
beforeEach(() => {
testMutation = () => {
mutations.SET_INIZIALIZED(state)
return getters.isInitialized(state)
}
})
it('sets isInitialized to true', () => {
expect(testMutation()).toBe(true)
})
})
})
describe('actions', () => {
const queryMock = jest.fn().mockResolvedValue({
data: {
Category: ['cat1', 'cat2', 'cat3'],
},
})
const commit = jest.fn()
let action
beforeEach(() => {
const module = {
app: {
apolloProvider: {
defaultClient: {
query: queryMock,
},
},
},
}
action = actions.init.bind(module)
})
describe('init', () => {
beforeEach(async () => {
await action({ commit })
})
it('calls apollo', () => {
expect(queryMock).toBeCalledWith({
query: CategoryQuery(),
})
})
it('commits SET_CATEGORIES', () => {
expect(commit).toBeCalledWith('SET_CATEGORIES', ['cat1', 'cat2', 'cat3'])
})
it('commits SET_INIZIALIZED', () => {
expect(commit).toBeCalledWith('SET_INIZIALIZED')
})
})
})
})