From a3178a91b4beb241d9f4e048f56b657cf38056cc Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Tue, 27 May 2025 15:03:26 +0200 Subject: [PATCH] refactor(webapp): store for categories (#8551) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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ß --- backend/src/middleware/categories.spec.ts | 97 ++++++++++++++++ backend/src/middleware/categories.ts | 16 +++ backend/src/middleware/index.ts | 2 + webapp/.env.template | 1 - .../CategoriesSelect/CategoriesSelect.spec.js | 24 +++- .../CategoriesSelect/CategoriesSelect.vue | 17 +-- .../ContributionForm/ContributionForm.spec.js | 9 +- .../ContributionForm/ContributionForm.vue | 3 +- .../FilterMenu/CategoriesFilter.spec.js | 39 +++---- .../FilterMenu/CategoriesFilter.vue | 23 +--- .../components/FilterMenu/CategoriesMenu.vue | 7 +- .../components/FilterMenu/FilterMenu.spec.js | 9 +- .../FilterMenu/FilterMenuComponent.vue | 7 +- .../FilterMenu/FollowingFilter.spec.js | 5 +- .../FilterMenu/OrderByFilter.spec.js | 5 +- webapp/components/Group/GroupForm.spec.js | 15 ++- webapp/components/Group/GroupForm.vue | 3 +- webapp/components/Group/GroupTeaser.vue | 7 +- webapp/components/HeaderMenu/HeaderMenu.vue | 3 +- webapp/components/LoginForm/LoginForm.spec.js | 12 +- webapp/components/LoginForm/LoginForm.vue | 8 +- .../components/PostTeaser/PostTeaser.spec.js | 11 +- webapp/components/PostTeaser/PostTeaser.vue | 7 +- .../SearchResults/SearchResults.spec.js | 4 +- webapp/config/index.js | 1 - webapp/jest.config.js | 2 +- webapp/mixins/getCategoriesMixin.js | 19 ++++ webapp/pages/groups/_id/_slug.spec.js | 107 ++++++------------ webapp/pages/groups/_id/_slug.vue | 11 +- webapp/pages/index.spec.js | 8 +- webapp/pages/index.vue | 4 +- webapp/pages/post/_id/_slug/index.spec.js | 7 +- webapp/pages/post/_id/_slug/index.vue | 4 +- webapp/pages/post/create.spec.js | 12 +- webapp/pages/post/edit/_id.spec.js | 8 +- webapp/store/auth.js | 1 + webapp/store/auth.test.js | 7 +- webapp/store/categories.js | 44 +++++++ webapp/store/categories.spec.js | 105 +++++++++++++++++ 39 files changed, 456 insertions(+), 218 deletions(-) create mode 100644 backend/src/middleware/categories.spec.ts create mode 100644 backend/src/middleware/categories.ts create mode 100644 webapp/mixins/getCategoriesMixin.js create mode 100644 webapp/store/categories.js create mode 100644 webapp/store/categories.spec.js diff --git a/backend/src/middleware/categories.spec.ts b/backend/src/middleware/categories.spec.ts new file mode 100644 index 000000000..3afda82a6 --- /dev/null +++ b/backend/src/middleware/categories.spec.ts @@ -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, + }) + }) + }) +}) diff --git a/backend/src/middleware/categories.ts b/backend/src/middleware/categories.ts new file mode 100644 index 000000000..759a3938f --- /dev/null +++ b/backend/src/middleware/categories.ts @@ -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, + }, +} diff --git a/backend/src/middleware/index.ts b/backend/src/middleware/index.ts index 558b0fdd3..b4824ae0e 100644 --- a/backend/src/middleware/index.ts +++ b/backend/src/middleware/index.ts @@ -7,6 +7,7 @@ import CONFIG from '@config/index' // eslint-disable-next-line import/no-cycle import brandingMiddlewares from './branding/brandingMiddlewares' +import categories from './categories' import chatMiddleware from './chatMiddleware' import excerpt from './excerptMiddleware' import hashtags from './hashtags/hashtagsMiddleware' @@ -46,6 +47,7 @@ const ocelotMiddlewares: MiddlewareOrder[] = [ { order: -80, name: 'includedFields', middleware: includedFields }, { order: -70, name: 'orderBy', middleware: orderBy }, { order: -60, name: 'chatMiddleware', middleware: chatMiddleware }, + { order: -50, name: 'categories', middleware: categories }, ] export default (schema) => { diff --git a/webapp/.env.template b/webapp/.env.template index ee9fd0578..1cd642004 100644 --- a/webapp/.env.template +++ b/webapp/.env.template @@ -5,7 +5,6 @@ GRAPHQL_URI=http://localhost:4000/ MAPBOX_TOKEN="pk.eyJ1IjoiYnVzZmFrdG9yIiwiYSI6ImNraDNiM3JxcDBhaWQydG1uczhpZWtpOW4ifQ.7TNRTO-o9aK1Y6MyW_Nd4g" PUBLIC_REGISTRATION=false INVITE_REGISTRATION=true -CATEGORIES_ACTIVE=false BADGES_ENABLED=true INVITE_LINK_LIMIT=7 NETWORK_NAME="Ocelot.social" diff --git a/webapp/components/CategoriesSelect/CategoriesSelect.spec.js b/webapp/components/CategoriesSelect/CategoriesSelect.spec.js index 82f5e61eb..6d379fbb8 100644 --- a/webapp/components/CategoriesSelect/CategoriesSelect.spec.js +++ b/webapp/components/CategoriesSelect/CategoriesSelect.spec.js @@ -1,6 +1,6 @@ import { mount } from '@vue/test-utils' import CategoriesSelect from './CategoriesSelect' -import Vue from 'vue' +import Vuex from 'vuex' const localVue = global.localVue @@ -12,7 +12,6 @@ describe('CategoriesSelect.vue', () => { let environmentAndNature let consumptionAndSustainablity - const propsData = { model: 'categoryIds' } const categories = [ { id: 'cat9', @@ -35,6 +34,20 @@ describe('CategoriesSelect.vue', () => { 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(() => { provide = { $parentForm: { @@ -48,7 +61,8 @@ describe('CategoriesSelect.vue', () => { describe('shallowMount', () => { const Wrapper = () => { - return mount(CategoriesSelect, { propsData, mocks, localVue, provide }) + const store = new Vuex.Store(storeMocks) + return mount(CategoriesSelect, { propsData, mocks, localVue, provide, store }) } beforeEach(() => { @@ -56,9 +70,7 @@ describe('CategoriesSelect.vue', () => { }) describe('toggleCategory', () => { - beforeEach(async () => { - wrapper.vm.categories = categories - await Vue.nextTick() + beforeEach(() => { democracyAndPolitics = wrapper.findAll('button').at(0) democracyAndPolitics.trigger('click') }) diff --git a/webapp/components/CategoriesSelect/CategoriesSelect.vue b/webapp/components/CategoriesSelect/CategoriesSelect.vue index 91fb7704c..47149bcfd 100644 --- a/webapp/components/CategoriesSelect/CategoriesSelect.vue +++ b/webapp/components/CategoriesSelect/CategoriesSelect.vue @@ -1,7 +1,7 @@