diff --git a/backend/src/schema/resolvers/user_management.js b/backend/src/schema/resolvers/user_management.js index beb2cddb3..d88eafdae 100644 --- a/backend/src/schema/resolvers/user_management.js +++ b/backend/src/schema/resolvers/user_management.js @@ -20,16 +20,22 @@ export default { const result = await transaction.run( ` MATCH (user:User {id: $id}) - WITH user, [(user)<-[:OWNED_BY]-(medium:SocialMedia) | properties(medium) ] as media - RETURN user {.*, socialMedia: media } as user + OPTIONAL MATCH (category:Category) WHERE NOT ((user)-[:NOT_INTERESTED_IN]->(category)) + OPTIONAL MATCH (cats:Category) + WITH user, [(user)<-[:OWNED_BY]-(medium:SocialMedia) | properties(medium) ] AS media, category, toString(COUNT(cats)) AS categoryCount + RETURN user {.*, socialMedia: media, activeCategories: collect(category.id) } AS user, categoryCount `, { id: user.id }, ) - log(result) - return result.records.map((record) => record.get('user')) + const [categoryCount] = result.records.map((record) => record.get('categoryCount')) + const [currentUser] = result.records.map((record) => record.get('user')) + // frontend expects empty array when all categories are selected + if (currentUser.activeCategories.length === parseInt(categoryCount)) + currentUser.activeCategories = [] + return currentUser }) try { - const [currentUser] = await currentUserTransactionPromise + const currentUser = await currentUserTransactionPromise return currentUser } finally { session.close() diff --git a/backend/src/schema/resolvers/user_management.spec.js b/backend/src/schema/resolvers/user_management.spec.js index 264cf2752..e7f2f3ed1 100644 --- a/backend/src/schema/resolvers/user_management.spec.js +++ b/backend/src/schema/resolvers/user_management.spec.js @@ -6,6 +6,7 @@ import { createTestClient } from 'apollo-server-testing' import createServer, { context } from '../../server' import encode from '../../jwt/encode' import { getNeode } from '../../db/neo4j' +import { categories } from '../../constants/categories' const neode = getNeode() let query, mutate, variables, req, user @@ -118,6 +119,7 @@ describe('currentUser', () => { } email role + activeCategories } } ` @@ -172,6 +174,52 @@ describe('currentUser', () => { } await respondsWith(expected) }) + + describe('with categories in DB', () => { + beforeEach(async () => { + await Promise.all( + categories.map(async ({ icon, name }, index) => { + await Factory.build('category', { + id: `cat${index + 1}`, + slug: name, + name, + icon, + }) + }), + ) + }) + + it('returns empty array for all categories', async () => { + await respondsWith({ + data: { + currentUser: expect.objectContaining({ activeCategories: [] }), + }, + }) + }) + + describe('with categories saved for current user', () => { + const saveCategorySettings = gql` + mutation ($activeCategories: [String]) { + saveCategorySettings(activeCategories: $activeCategories) + } + ` + beforeEach(async () => { + await mutate({ + mutation: saveCategorySettings, + variables: { activeCategories: ['cat1', 'cat3', 'cat5', 'cat7'] }, + }) + }) + + it('returns only the saved active categories', async () => { + const result = await query({ query: currentUserQuery, variables }) + expect(result.data.currentUser.activeCategories).toHaveLength(4) + expect(result.data.currentUser.activeCategories).toContain('cat1') + expect(result.data.currentUser.activeCategories).toContain('cat3') + expect(result.data.currentUser.activeCategories).toContain('cat5') + expect(result.data.currentUser.activeCategories).toContain('cat7') + }) + }) + }) }) }) }) diff --git a/backend/src/schema/resolvers/users.js b/backend/src/schema/resolvers/users.js index 23a39a2a1..12f00ffb6 100644 --- a/backend/src/schema/resolvers/users.js +++ b/backend/src/schema/resolvers/users.js @@ -286,6 +286,10 @@ export default { { id }, ) }) + + // frontend gives [] when all categories are selected (default) + if (activeCategories.length === 0) return true + const writeTxResultPromise = session.writeTransaction(async (transaction) => { const saveCategorySettingsResponse = await transaction.run( ` diff --git a/backend/src/schema/resolvers/users.spec.js b/backend/src/schema/resolvers/users.spec.js index 7116d2201..d8fce3b29 100644 --- a/backend/src/schema/resolvers/users.spec.js +++ b/backend/src/schema/resolvers/users.spec.js @@ -602,9 +602,7 @@ describe('save category settings', () => { const userQuery = gql` query ($id: ID) { User(id: $id) { - activeCategories { - id - } + activeCategories } } ` @@ -631,11 +629,7 @@ describe('save category settings', () => { data: { User: [ { - activeCategories: expect.arrayContaining([ - { id: 'cat1' }, - { id: 'cat3' }, - { id: 'cat5' }, - ]), + activeCategories: expect.arrayContaining(['cat1', 'cat3', 'cat5']), }, ], }, @@ -678,11 +672,11 @@ describe('save category settings', () => { User: [ { activeCategories: expect.arrayContaining([ - { id: 'cat10' }, - { id: 'cat11' }, - { id: 'cat12' }, - { id: 'cat8' }, - { id: 'cat9' }, + 'cat10', + 'cat11', + 'cat12', + 'cat8', + 'cat9', ]), }, ], diff --git a/backend/src/schema/types/type/User.gql b/backend/src/schema/types/type/User.gql index d2ac79037..c3fcf932b 100644 --- a/backend/src/schema/types/type/User.gql +++ b/backend/src/schema/types/type/User.gql @@ -115,11 +115,11 @@ type User { emotions: [EMOTED] - activeCategories: [Category] @cypher( + activeCategories: [String] @cypher( statement: """ MATCH (category:Category) WHERE NOT ((this)-[:NOT_INTERESTED_IN]->(category)) - RETURN category + RETURN collect(category.id) """ ) } diff --git a/webapp/assets/_new/icons/svgs/save.svg b/webapp/assets/_new/icons/svgs/save.svg new file mode 100644 index 000000000..31c1d8459 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/save.svg @@ -0,0 +1,5 @@ + + diff --git a/webapp/components/FilterMenu/CategoriesFilter.spec.js b/webapp/components/FilterMenu/CategoriesFilter.spec.js index ef0f2ad90..813f7b33c 100644 --- a/webapp/components/FilterMenu/CategoriesFilter.spec.js +++ b/webapp/components/FilterMenu/CategoriesFilter.spec.js @@ -15,8 +15,19 @@ describe('CategoriesFilter.vue', () => { 'posts/filteredCategoryIds': jest.fn(() => []), } + const apolloMutationMock = jest.fn().mockResolvedValue({ + data: { saveCategorySettings: true }, + }) + const mocks = { $t: jest.fn((string) => string), + $apollo: { + mutate: apolloMutationMock, + }, + $toast: { + success: jest.fn(), + error: jest.fn(), + }, } const Wrapper = () => { @@ -76,5 +87,14 @@ describe('CategoriesFilter.vue', () => { expect(mutations['posts/RESET_CATEGORIES']).toHaveBeenCalledTimes(1) }) }) + + describe('save categories', () => { + it('calls the API', async () => { + wrapper = await Wrapper() + const saveButton = wrapper.findAll('.categories-filter .sidebar .base-button').at(1) + saveButton.trigger('click') + expect(apolloMutationMock).toBeCalled() + }) + }) }) }) diff --git a/webapp/components/FilterMenu/CategoriesFilter.vue b/webapp/components/FilterMenu/CategoriesFilter.vue index 47e4bcc10..6ed37d4b3 100644 --- a/webapp/components/FilterMenu/CategoriesFilter.vue +++ b/webapp/components/FilterMenu/CategoriesFilter.vue @@ -7,6 +7,8 @@ icon="check" @click="resetCategories" /> +