From d85f49fb155b6be3364ff92e9fe1b0cadd896f93 Mon Sep 17 00:00:00 2001 From: senderfm Date: Wed, 19 Jun 2019 17:45:52 +0200 Subject: [PATCH 01/31] Fix: long comments can be folded up and down --- webapp/components/Comment.vue | 26 ++++++++++++++++++++++++-- webapp/graphql/PostCommentsQuery.js | 1 + webapp/locales/de.json | 4 ++++ webapp/locales/en.json | 5 +++++ 4 files changed, 34 insertions(+), 2 deletions(-) diff --git a/webapp/components/Comment.vue b/webapp/components/Comment.vue index 67bdb92a8..d265b7b70 100644 --- a/webapp/components/Comment.vue +++ b/webapp/components/Comment.vue @@ -23,10 +23,27 @@ -
- +
+
+ + {{ $t('comment.show.more') }} + +
+
+ + {{ $t('comment.show.less') }} +
+ diff --git a/webapp/components/ContributionForm/index.vue b/webapp/components/ContributionForm/index.vue index c925a6dca..670960df5 100644 --- a/webapp/components/ContributionForm/index.vue +++ b/webapp/components/ContributionForm/index.vue @@ -7,6 +7,7 @@ + @@ -50,10 +51,12 @@ import HcEditor from '~/components/Editor' import orderBy from 'lodash/orderBy' import locales from '~/locales' import PostMutations from '~/graphql/PostMutations.js' +import HcCategoriesSelect from '~/components/CategoriesSelect/CategoriesSelect' export default { components: { HcEditor, + HcCategoriesSelect, }, props: { contribution: { type: Object, default: () => {} }, @@ -65,6 +68,7 @@ export default { content: '', language: null, languageOptions: [], + categories: null, }, formSchema: { title: { required: true, min: 3, max: 64 }, @@ -106,22 +110,23 @@ export default { }, methods: { submit() { + const { title, content, language, categories } = this.form this.loading = true this.$apollo .mutate({ mutation: this.id ? PostMutations().UpdatePost : PostMutations().CreatePost, variables: { id: this.id, - title: this.form.title, - content: this.form.content, - language: this.form.language ? this.form.language.value : this.$i18n.locale(), + title, + content, + language: language ? language.value : this.$i18n.locale(), + categories, }, }) .then(res => { this.loading = false this.$toast.success(this.$t('contribution.success')) this.disabled = true - const result = res.data[this.id ? 'UpdatePost' : 'CreatePost'] this.$router.push({ @@ -144,6 +149,9 @@ export default { this.form.languageOptions.push({ label: locale.name, value: locale.code }) }) }, + updateCategories(ids) { + this.form.categories = ids + }, }, apollo: { User: { diff --git a/webapp/graphql/PostMutations.js b/webapp/graphql/PostMutations.js index ad629bee7..4f195eb99 100644 --- a/webapp/graphql/PostMutations.js +++ b/webapp/graphql/PostMutations.js @@ -3,14 +3,17 @@ import gql from 'graphql-tag' export default () => { return { CreatePost: gql` - mutation($title: String!, $content: String!, $language: String) { - CreatePost(title: $title, content: $content, language: $language) { + mutation($title: String!, $content: String!, $language: String, $categories: [ID]) { + CreatePost(title: $title, content: $content, language: $language, categories: $categories) { id title slug content contentExcerpt language + categories { + name + } } } `, diff --git a/webapp/locales/en.json b/webapp/locales/en.json index 2442dc202..8f09c5fe1 100644 --- a/webapp/locales/en.json +++ b/webapp/locales/en.json @@ -331,6 +331,9 @@ "filterFollow": "Filter contributions from users I follow", "filterALL": "View all contributions", "success": "Saved!", - "languageSelectLabel": "Language" + "languageSelectLabel": "Language", + "categories": { + "infoSelectedNoOfMaxCategories": "{chosen} of {max} categories selected" + } } } From bb40e9d4326ed09ca519d747486faf9aea0b897a Mon Sep 17 00:00:00 2001 From: Matt Rider Date: Wed, 26 Jun 2019 18:21:12 -0300 Subject: [PATCH 11/31] Update component tests for categories --- .../ContributionForm/ContributionForm.spec.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/webapp/components/ContributionForm/ContributionForm.spec.js b/webapp/components/ContributionForm/ContributionForm.spec.js index 6856a64b2..0991a3ea2 100644 --- a/webapp/components/ContributionForm/ContributionForm.spec.js +++ b/webapp/components/ContributionForm/ContributionForm.spec.js @@ -3,6 +3,7 @@ import ContributionForm from './index.vue' import Styleguide from '@human-connection/styleguide' import Vuex from 'vuex' import PostMutations from '~/graphql/PostMutations.js' +import CategoriesSelect from '~/components/CategoriesSelect/CategoriesSelect' const localVue = createLocalVue() @@ -100,7 +101,13 @@ describe('ContributionForm.vue', () => { beforeEach(async () => { expectedParams = { mutation: PostMutations().CreatePost, - variables: { title: postTitle, content: postContent, language: 'en', id: null }, + variables: { + title: postTitle, + content: postContent, + language: 'en', + id: null, + categories: null, + }, } postTitleInput = wrapper.find('.ds-input') postTitleInput.setValue(postTitle) @@ -124,6 +131,14 @@ describe('ContributionForm.vue', () => { expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expect.objectContaining(expectedParams)) }) + it('supports adding categories', async () => { + const categories = ['cat12', 'cat15', 'cat37'] + expectedParams.variables.categories = categories + wrapper.find(CategoriesSelect).vm.$emit('updateCategories', categories) + await wrapper.find('form').trigger('submit') + expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expect.objectContaining(expectedParams)) + }) + it("pushes the user to the post's page", async () => { expect(mocks.$router.push).toHaveBeenCalledTimes(1) }) @@ -200,6 +215,7 @@ describe('ContributionForm.vue', () => { content: postContent, language: propsData.contribution.language, id: propsData.contribution.id, + categories: null, }, } postTitleInput = wrapper.find('.ds-input') From 351f258fab91df7c54ebff64624e03facd32fe5b Mon Sep 17 00:00:00 2001 From: Matt Rider Date: Thu, 27 Jun 2019 16:57:55 -0300 Subject: [PATCH 12/31] Refactor resolver, write component tests - use UNWIND instead of iterating over categories(cypher magic) - do not return nested categories on post creation as it's expensive and we don't use - refactor backend test - component tests for CategoriesSelect --- backend/src/schema/resolvers/posts.js | 48 ++------ backend/src/schema/resolvers/posts.spec.js | 34 +++++- .../CategoriesSelect/CategoriesSelect.spec.js | 108 ++++++++++++++++++ 3 files changed, 148 insertions(+), 42 deletions(-) create mode 100644 webapp/components/CategoriesSelect/CategoriesSelect.spec.js diff --git a/backend/src/schema/resolvers/posts.js b/backend/src/schema/resolvers/posts.js index d585bcfc0..a9617d255 100644 --- a/backend/src/schema/resolvers/posts.js +++ b/backend/src/schema/resolvers/posts.js @@ -10,8 +10,9 @@ export default { CreatePost: async (object, params, context, resolveInfo) => { const { categories } = params + let post params = await fileUpload(params, { file: 'imageUpload', url: 'image' }) - let post = await neo4jgraphql(object, params, context, resolveInfo, false) + post = await neo4jgraphql(object, params, context, resolveInfo, false) const session = context.driver.session() await session.run( @@ -24,42 +25,17 @@ export default { }, ) if (categories && categories.length) { - let postsCategoriesArray = [] - await Promise.all( - categories.map(async categoryId => { - let postsCategoriesTransaction = await session.run( - `MATCH (category:Category { id: $categoryId}), (post:Post {id: $postId}) - MERGE (post)-[:CATEGORIZED]->(category) - RETURN category - `, - { - categoryId, - postId: post.id, - }, - ) - postsCategoriesArray.push(postsCategoriesTransaction) - }), + await session.run( + `MATCH (post:Post {id: $postId}) + UNWIND $categories AS categoryId + MATCH (category:Category {id: categoryId}) + MERGE (post)-[:CATEGORIZED]->(category) + RETURN category`, + { + categories, + postId: post.id, + }, ) - let categoryArray = [] - postsCategoriesArray.map(categoryRecord => { - let [category] = categoryRecord.records.map(record => { - return { - category: record.get('category'), - } - }) - categoryArray.push(category) - }) - let categoriesPropertiesArray = [] - categoryArray.map(node => { - let { category } = node - let categories = { ...category.properties } - categoriesPropertiesArray.push(categories) - }) - - post = { - ...post, - categories: categoriesPropertiesArray, - } } session.close() return post diff --git a/backend/src/schema/resolvers/posts.spec.js b/backend/src/schema/resolvers/posts.spec.js index 56b2c8b95..06a342c1c 100644 --- a/backend/src/schema/resolvers/posts.spec.js +++ b/backend/src/schema/resolvers/posts.spec.js @@ -120,25 +120,47 @@ describe('CreatePost', () => { const createPostWithCategoriesMutation = ` mutation($title: String!, $content: String!, $categories: [ID]) { CreatePost(title: $title, content: $content, categories: $categories) { + id categories { id } } } ` - const postCategories = ['cat9', 'cat4', 'cat15'] const creatPostWithCategoriesVariables = { title: postTitle, content: postContent, - categories: postCategories, + categories: ['cat9', 'cat4', 'cat15'], } + const postQueryWithCategories = ` + query($id: ID) { + Post(id: $id) { + categories { + id + } + } + } + ` const expected = { - CreatePost: { - categories: [{ id: 'cat9' }, { id: 'cat4' }, { id: 'cat15' }], - }, + Post: [ + { + categories: [ + { id: expect.any(String) }, + { id: expect.any(String) }, + { id: expect.any(String) }, + ], + }, + ], + } + const postWithCategories = await client.request( + createPostWithCategoriesMutation, + creatPostWithCategoriesVariables, + ) + const postQueryWithCategoriesVariables = { + id: postWithCategories.CreatePost.id, } await expect( - client.request(createPostWithCategoriesMutation, creatPostWithCategoriesVariables), + client.request(postQueryWithCategories, postQueryWithCategoriesVariables), ).resolves.toEqual(expect.objectContaining(expected)) }) }) diff --git a/webapp/components/CategoriesSelect/CategoriesSelect.spec.js b/webapp/components/CategoriesSelect/CategoriesSelect.spec.js new file mode 100644 index 000000000..199dacb74 --- /dev/null +++ b/webapp/components/CategoriesSelect/CategoriesSelect.spec.js @@ -0,0 +1,108 @@ +import { mount, createLocalVue } from '@vue/test-utils' +import CategoriesSelect from './CategoriesSelect' +import Styleguide from '@human-connection/styleguide' + +const localVue = createLocalVue() +localVue.use(Styleguide) + +describe('CategoriesSelect.vue', () => { + let wrapper + let mocks + let democracyAndPolitics + let environmentAndNature + let consumptionAndSustainablity + + const categories = [ + { + id: 'cat9', + name: 'Democracy & Politics', + icon: 'university', + }, + { + id: 'cat4', + name: 'Environment & Nature', + icon: 'tree', + }, + { + id: 'cat15', + name: 'Consumption & Sustainability', + icon: 'shopping-cart', + }, + { + name: 'Cooperation & Development', + icon: 'users', + id: 'cat8', + }, + ] + beforeEach(() => { + mocks = { + $t: jest.fn(), + } + }) + + describe('shallowMount', () => { + const Wrapper = () => { + return mount(CategoriesSelect, { mocks, localVue }) + } + + beforeEach(() => { + wrapper = Wrapper() + }) + + describe('toggleCategory', () => { + beforeEach(() => { + wrapper.vm.categories = categories + democracyAndPolitics = wrapper.findAll('button').at(0) + democracyAndPolitics.trigger('click') + }) + + it('adds categories to selectedCategoryIds when clicked', () => { + expect(wrapper.vm.selectedCategoryIds).toEqual([categories[0].id]) + }) + + it('emits an updateCategories event when the selectedCategoryIds changes', () => { + expect(wrapper.emitted().updateCategories[0][0]).toEqual([categories[0].id]) + }) + + it('removes categories when clicked a second time', () => { + democracyAndPolitics.trigger('click') + expect(wrapper.vm.selectedCategoryIds).toEqual([]) + }) + + it('changes the selectedCount when selectedCategoryIds is updated', () => { + expect(wrapper.vm.selectedCount).toEqual(1) + democracyAndPolitics.trigger('click') + expect(wrapper.vm.selectedCount).toEqual(0) + }) + + it('sets a category to active when it has been selected', () => { + expect(wrapper.vm.isActive(categories[0].id)).toEqual(true) + }) + + describe('maximum', () => { + beforeEach(() => { + environmentAndNature = wrapper.findAll('button').at(1) + consumptionAndSustainablity = wrapper.findAll('button').at(2) + environmentAndNature.trigger('click') + consumptionAndSustainablity.trigger('click') + }) + + it('allows three categories to be selected', () => { + expect(wrapper.vm.selectedCategoryIds).toEqual([ + categories[0].id, + categories[1].id, + categories[2].id, + ]) + }) + + it('sets reachedMaximum to true after three', () => { + expect(wrapper.vm.reachedMaximum).toEqual(true) + }) + + it('sets other categories to disabled after three', () => { + expect(wrapper.vm.isDisabled(categories[3].id)).toEqual(true) + }) + }) + }) + }) +}) From 1b2396645fe677533bf7b6e249278e94addcbb66 Mon Sep 17 00:00:00 2001 From: senderfm Date: Fri, 28 Jun 2019 09:01:47 +0200 Subject: [PATCH 13/31] main menu changed to flex menu and responsively adapted, search edited --- webapp/layouts/default.vue | 229 ++++++++++++++++++++++--------------- 1 file changed, 135 insertions(+), 94 deletions(-) diff --git a/webapp/layouts/default.vue b/webapp/layouts/default.vue index 162bae0fb..c5ed6e4b2 100644 --- a/webapp/layouts/default.vue +++ b/webapp/layouts/default.vue @@ -2,87 +2,97 @@