mirror of
https://github.com/Ocelot-Social-Community/Ocelot-Social.git
synced 2025-12-13 07:46:06 +00:00
Merge pull request #2160 from Human-Connection/2119_Create_Post_consistent_form_input_validation
🍰 2119-Fix Contribution consistent form input validation
This commit is contained in:
commit
c775884c59
@ -5,9 +5,12 @@ import {
|
|||||||
} from "cypress-cucumber-preprocessor/steps";
|
} from "cypress-cucumber-preprocessor/steps";
|
||||||
import helpers from "../../support/helpers";
|
import helpers from "../../support/helpers";
|
||||||
import { VERSION } from '../../constants/terms-and-conditions-version.js'
|
import { VERSION } from '../../constants/terms-and-conditions-version.js'
|
||||||
|
import locales from '../../../webapp/locales'
|
||||||
|
import orderBy from 'lodash/orderBy'
|
||||||
|
|
||||||
/* global cy */
|
/* global cy */
|
||||||
|
|
||||||
|
const languages = orderBy(locales, 'name')
|
||||||
let lastPost = {};
|
let lastPost = {};
|
||||||
|
|
||||||
let loginCredentials = {
|
let loginCredentials = {
|
||||||
@ -245,6 +248,12 @@ Then("I select a category", () => {
|
|||||||
.click();
|
.click();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
When("I choose {string} as the language for the post", (languageCode) => {
|
||||||
|
cy.get('.ds-flex-item > .ds-form-item .ds-select ')
|
||||||
|
.click().get('.ds-select-option')
|
||||||
|
.eq(languages.findIndex(l => l.code === languageCode)).click()
|
||||||
|
})
|
||||||
|
|
||||||
Then("the post shows up on the landing page at position {int}", index => {
|
Then("the post shows up on the landing page at position {int}", index => {
|
||||||
cy.openPage("landing");
|
cy.openPage("landing");
|
||||||
const selector = `.post-card:nth-child(${index}) > .ds-card-content`;
|
const selector = `.post-card:nth-child(${index}) > .ds-card-content`;
|
||||||
|
|||||||
@ -20,6 +20,7 @@ Feature: Notification for a mention
|
|||||||
"""
|
"""
|
||||||
And mention "@matt-rider" in the text
|
And mention "@matt-rider" in the text
|
||||||
And I select a category
|
And I select a category
|
||||||
|
And I choose "en" as the language for the post
|
||||||
And I click on "Save"
|
And I click on "Save"
|
||||||
When I log out
|
When I log out
|
||||||
And I log in with the following credentials:
|
And I log in with the following credentials:
|
||||||
|
|||||||
@ -17,7 +17,8 @@ Feature: Create a post
|
|||||||
Human Connection is a free and open-source social network
|
Human Connection is a free and open-source social network
|
||||||
for active citizenship.
|
for active citizenship.
|
||||||
"""
|
"""
|
||||||
Then I select a category
|
And I select a category
|
||||||
|
And I choose "en" as the language for the post
|
||||||
And I click on "Save"
|
And I click on "Save"
|
||||||
Then I get redirected to ".../my-first-post"
|
Then I get redirected to ".../my-first-post"
|
||||||
And the post was saved successfully
|
And the post was saved successfully
|
||||||
|
|||||||
@ -8,10 +8,12 @@ localVue.use(Styleguide)
|
|||||||
describe('CategoriesSelect.vue', () => {
|
describe('CategoriesSelect.vue', () => {
|
||||||
let wrapper
|
let wrapper
|
||||||
let mocks
|
let mocks
|
||||||
|
let provide
|
||||||
let democracyAndPolitics
|
let democracyAndPolitics
|
||||||
let environmentAndNature
|
let environmentAndNature
|
||||||
let consumptionAndSustainablity
|
let consumptionAndSustainablity
|
||||||
|
|
||||||
|
const propsData = { model: 'categoryIds' }
|
||||||
const categories = [
|
const categories = [
|
||||||
{
|
{
|
||||||
id: 'cat9',
|
id: 'cat9',
|
||||||
@ -35,6 +37,11 @@ describe('CategoriesSelect.vue', () => {
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
provide = {
|
||||||
|
$parentForm: {
|
||||||
|
update: jest.fn(),
|
||||||
|
},
|
||||||
|
}
|
||||||
mocks = {
|
mocks = {
|
||||||
$t: jest.fn(),
|
$t: jest.fn(),
|
||||||
}
|
}
|
||||||
@ -42,7 +49,7 @@ describe('CategoriesSelect.vue', () => {
|
|||||||
|
|
||||||
describe('shallowMount', () => {
|
describe('shallowMount', () => {
|
||||||
const Wrapper = () => {
|
const Wrapper = () => {
|
||||||
return mount(CategoriesSelect, { mocks, localVue })
|
return mount(CategoriesSelect, { propsData, mocks, localVue, provide })
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@ -60,8 +67,8 @@ describe('CategoriesSelect.vue', () => {
|
|||||||
expect(wrapper.vm.selectedCategoryIds).toEqual([categories[0].id])
|
expect(wrapper.vm.selectedCategoryIds).toEqual([categories[0].id])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('emits an updateCategories event when the selectedCategoryIds changes', () => {
|
it('calls $parent.update with selected category ids', () => {
|
||||||
expect(wrapper.emitted().updateCategories[0][0]).toEqual([categories[0].id])
|
expect(provide.$parentForm.update).toHaveBeenCalledWith('categoryIds', ['cat9'])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('removes categories when clicked a second time', () => {
|
it('removes categories when clicked a second time', () => {
|
||||||
|
|||||||
@ -5,6 +5,7 @@
|
|||||||
<ds-flex-item>
|
<ds-flex-item>
|
||||||
<ds-button
|
<ds-button
|
||||||
size="small"
|
size="small"
|
||||||
|
:data-test="categoryButtonsId(category.id)"
|
||||||
@click.prevent="toggleCategory(category.id)"
|
@click.prevent="toggleCategory(category.id)"
|
||||||
:primary="isActive(category.id)"
|
:primary="isActive(category.id)"
|
||||||
:disabled="isDisabled(category.id)"
|
:disabled="isDisabled(category.id)"
|
||||||
@ -28,16 +29,23 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import CategoryQuery from '~/graphql/CategoryQuery'
|
import CategoryQuery from '~/graphql/CategoryQuery'
|
||||||
|
import xor from 'lodash/xor'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
inject: {
|
||||||
|
$parentForm: {
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
props: {
|
props: {
|
||||||
existingCategoryIds: { type: Array, default: () => [] },
|
existingCategoryIds: { type: Array, default: () => [] },
|
||||||
|
model: { type: String, required: true },
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
categories: null,
|
categories: null,
|
||||||
selectedMax: 3,
|
selectedMax: 3,
|
||||||
selectedCategoryIds: [],
|
selectedCategoryIds: this.existingCategoryIds,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -48,39 +56,22 @@ export default {
|
|||||||
return this.selectedCount >= this.selectedMax
|
return this.selectedCount >= this.selectedMax
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
|
||||||
selectedCategoryIds(categoryIds) {
|
|
||||||
this.$emit('updateCategories', categoryIds)
|
|
||||||
},
|
|
||||||
existingCategoryIds: {
|
|
||||||
immediate: true,
|
|
||||||
handler: function(existingCategoryIds) {
|
|
||||||
if (!existingCategoryIds || !existingCategoryIds.length) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.selectedCategoryIds = existingCategoryIds
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
toggleCategory(id) {
|
toggleCategory(id) {
|
||||||
const index = this.selectedCategoryIds.indexOf(id)
|
this.selectedCategoryIds = xor(this.selectedCategoryIds, [id])
|
||||||
if (index > -1) {
|
if (this.$parentForm) {
|
||||||
this.selectedCategoryIds.splice(index, 1)
|
this.$parentForm.update(this.model, this.selectedCategoryIds)
|
||||||
} else {
|
|
||||||
this.selectedCategoryIds.push(id)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
isActive(id) {
|
isActive(id) {
|
||||||
const index = this.selectedCategoryIds.indexOf(id)
|
return this.selectedCategoryIds.includes(id)
|
||||||
if (index > -1) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
},
|
},
|
||||||
isDisabled(id) {
|
isDisabled(id) {
|
||||||
return !!(this.reachedMaximum && !this.isActive(id))
|
return !!(this.reachedMaximum && !this.isActive(id))
|
||||||
},
|
},
|
||||||
|
categoryButtonsId(categoryId) {
|
||||||
|
return `category-buttons-${categoryId}`
|
||||||
|
},
|
||||||
},
|
},
|
||||||
apollo: {
|
apollo: {
|
||||||
Category: {
|
Category: {
|
||||||
|
|||||||
@ -20,15 +20,45 @@ config.stubs['client-only'] = '<span><slot /></span>'
|
|||||||
config.stubs['nuxt-link'] = '<span><slot /></span>'
|
config.stubs['nuxt-link'] = '<span><slot /></span>'
|
||||||
config.stubs['v-popover'] = '<span><slot /></span>'
|
config.stubs['v-popover'] = '<span><slot /></span>'
|
||||||
|
|
||||||
|
const categories = [
|
||||||
|
{
|
||||||
|
id: 'cat3',
|
||||||
|
slug: 'health-wellbeing',
|
||||||
|
icon: 'medkit',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'cat12',
|
||||||
|
slug: 'it-internet-data-privacy',
|
||||||
|
icon: 'mouse-pointer',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'cat9',
|
||||||
|
slug: 'democracy-politics',
|
||||||
|
icon: 'university',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'cat15',
|
||||||
|
slug: 'consumption-sustainability',
|
||||||
|
icon: 'shopping-cart',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'cat4',
|
||||||
|
slug: 'environment-nature',
|
||||||
|
icon: 'tree',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
describe('ContributionForm.vue', () => {
|
describe('ContributionForm.vue', () => {
|
||||||
let wrapper
|
let wrapper,
|
||||||
let postTitleInput
|
postTitleInput,
|
||||||
let expectedParams
|
expectedParams,
|
||||||
let deutschOption
|
cancelBtn,
|
||||||
let cancelBtn
|
mocks,
|
||||||
let mocks
|
propsData,
|
||||||
let propsData
|
categoryIds,
|
||||||
let categoryIds
|
englishLanguage,
|
||||||
|
deutschLanguage,
|
||||||
|
dataPrivacyButton
|
||||||
const postTitle = 'this is a title for a post'
|
const postTitle = 'this is a title for a post'
|
||||||
const postTitleTooShort = 'xx'
|
const postTitleTooShort = 'xx'
|
||||||
let postTitleTooLong = ''
|
let postTitleTooLong = ''
|
||||||
@ -36,11 +66,6 @@ describe('ContributionForm.vue', () => {
|
|||||||
postTitleTooLong += 'x'
|
postTitleTooLong += 'x'
|
||||||
}
|
}
|
||||||
const postContent = 'this is a post'
|
const postContent = 'this is a post'
|
||||||
const postContentTooShort = 'xx'
|
|
||||||
let postContentTooLong = ''
|
|
||||||
for (let i = 0; i < 2001; i++) {
|
|
||||||
postContentTooLong += 'x'
|
|
||||||
}
|
|
||||||
const imageUpload = {
|
const imageUpload = {
|
||||||
file: {
|
file: {
|
||||||
filename: 'avataar.svg',
|
filename: 'avataar.svg',
|
||||||
@ -109,90 +134,59 @@ describe('ContributionForm.vue', () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
wrapper = Wrapper()
|
wrapper = Wrapper()
|
||||||
wrapper.setData({
|
|
||||||
form: {
|
|
||||||
languageOptions: [
|
|
||||||
{
|
|
||||||
label: 'Deutsch',
|
|
||||||
value: 'de',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('CreatePost', () => {
|
describe('CreatePost', () => {
|
||||||
describe('language placeholder', () => {
|
|
||||||
it("displays the name that corresponds with the user's location code", () => {
|
|
||||||
expect(wrapper.find('.ds-select-placeholder').text()).toEqual('English')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('invalid form submission', () => {
|
describe('invalid form submission', () => {
|
||||||
it('title and content should not be empty ', async () => {
|
beforeEach(async () => {
|
||||||
wrapper.find('.submit-button-for-test').trigger('click')
|
wrapper.find(CategoriesSelect).setData({ categories })
|
||||||
expect(mocks.$apollo.mutate).not.toHaveBeenCalled()
|
postTitleInput = wrapper.find('.ds-input')
|
||||||
|
postTitleInput.setValue(postTitle)
|
||||||
|
await wrapper.vm.updateEditorContent(postContent)
|
||||||
|
englishLanguage = wrapper.findAll('li').filter(language => language.text() === 'English')
|
||||||
|
englishLanguage.trigger('click')
|
||||||
|
dataPrivacyButton = await wrapper
|
||||||
|
.find(CategoriesSelect)
|
||||||
|
.find('[data-test="category-buttons-cat12"]')
|
||||||
|
dataPrivacyButton.trigger('click')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('title should not be empty', async () => {
|
it('title should not be empty', async () => {
|
||||||
await wrapper.vm.updateEditorContent(postContent)
|
postTitleInput.setValue('')
|
||||||
wrapper.find('.submit-button-for-test').trigger('click')
|
wrapper.find('form').trigger('submit')
|
||||||
expect(mocks.$apollo.mutate).not.toHaveBeenCalled()
|
expect(mocks.$apollo.mutate).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('title should not be too long', async () => {
|
it('title should not be too long', async () => {
|
||||||
postTitleInput = wrapper.find('.ds-input')
|
|
||||||
postTitleInput.setValue(postTitleTooLong)
|
postTitleInput.setValue(postTitleTooLong)
|
||||||
await wrapper.vm.updateEditorContent(postContent)
|
wrapper.find('form').trigger('submit')
|
||||||
wrapper.find('.submit-button-for-test').trigger('click')
|
|
||||||
expect(mocks.$apollo.mutate).not.toHaveBeenCalled()
|
expect(mocks.$apollo.mutate).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('title should not be too short', async () => {
|
it('title should not be too short', async () => {
|
||||||
postTitleInput = wrapper.find('.ds-input')
|
|
||||||
postTitleInput.setValue(postTitleTooShort)
|
postTitleInput.setValue(postTitleTooShort)
|
||||||
await wrapper.vm.updateEditorContent(postContent)
|
wrapper.find('form').trigger('submit')
|
||||||
wrapper.find('.submit-button-for-test').trigger('click')
|
|
||||||
expect(mocks.$apollo.mutate).not.toHaveBeenCalled()
|
expect(mocks.$apollo.mutate).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('content should not be empty', async () => {
|
it('content should not be empty', async () => {
|
||||||
postTitleInput = wrapper.find('.ds-input')
|
await wrapper.vm.updateEditorContent('')
|
||||||
postTitleInput.setValue(postTitle)
|
await wrapper.find('form').trigger('submit')
|
||||||
await wrapper.find('.submit-button-for-test').trigger('click')
|
|
||||||
expect(mocks.$apollo.mutate).not.toHaveBeenCalled()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('content should not be too short', async () => {
|
|
||||||
postTitleInput = wrapper.find('.ds-input')
|
|
||||||
postTitleInput.setValue(postTitle)
|
|
||||||
await wrapper.vm.updateEditorContent(postContentTooShort)
|
|
||||||
wrapper.find('.submit-button-for-test').trigger('click')
|
|
||||||
expect(mocks.$apollo.mutate).not.toHaveBeenCalled()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('content should not be too long', async () => {
|
|
||||||
postTitleInput = wrapper.find('.ds-input')
|
|
||||||
postTitleInput.setValue(postTitle)
|
|
||||||
await wrapper.vm.updateEditorContent(postContentTooLong)
|
|
||||||
wrapper.find('.submit-button-for-test').trigger('click')
|
|
||||||
expect(mocks.$apollo.mutate).not.toHaveBeenCalled()
|
expect(mocks.$apollo.mutate).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should have at least one category', async () => {
|
it('should have at least one category', async () => {
|
||||||
postTitleInput = wrapper.find('.ds-input')
|
dataPrivacyButton = await wrapper
|
||||||
postTitleInput.setValue(postTitle)
|
.find(CategoriesSelect)
|
||||||
await wrapper.vm.updateEditorContent(postContent)
|
.find('[data-test="category-buttons-cat12"]')
|
||||||
wrapper.find('.submit-button-for-test').trigger('click')
|
dataPrivacyButton.trigger('click')
|
||||||
|
wrapper.find('form').trigger('submit')
|
||||||
expect(mocks.$apollo.mutate).not.toHaveBeenCalled()
|
expect(mocks.$apollo.mutate).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should have not have more than three categories', async () => {
|
it('should have not have more than three categories', async () => {
|
||||||
postTitleInput = wrapper.find('.ds-input')
|
|
||||||
postTitleInput.setValue(postTitle)
|
|
||||||
await wrapper.vm.updateEditorContent(postContent)
|
|
||||||
wrapper.vm.form.categoryIds = ['cat4', 'cat9', 'cat15', 'cat27']
|
wrapper.vm.form.categoryIds = ['cat4', 'cat9', 'cat15', 'cat27']
|
||||||
wrapper.find('.submit-button-for-test').trigger('click')
|
wrapper.find('form').trigger('submit')
|
||||||
expect(mocks.$apollo.mutate).not.toHaveBeenCalled()
|
expect(mocks.$apollo.mutate).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -214,43 +208,51 @@ describe('ContributionForm.vue', () => {
|
|||||||
postTitleInput = wrapper.find('.ds-input')
|
postTitleInput = wrapper.find('.ds-input')
|
||||||
postTitleInput.setValue(postTitle)
|
postTitleInput.setValue(postTitle)
|
||||||
await wrapper.vm.updateEditorContent(postContent)
|
await wrapper.vm.updateEditorContent(postContent)
|
||||||
categoryIds = ['cat12']
|
wrapper.find(CategoriesSelect).setData({ categories })
|
||||||
wrapper.find(CategoriesSelect).vm.$emit('updateCategories', categoryIds)
|
englishLanguage = wrapper.findAll('li').filter(language => language.text() === 'English')
|
||||||
|
englishLanguage.trigger('click')
|
||||||
|
dataPrivacyButton = await wrapper
|
||||||
|
.find(CategoriesSelect)
|
||||||
|
.find('[data-test="category-buttons-cat12"]')
|
||||||
|
dataPrivacyButton.trigger('click')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('creates a post with valid title, content, and at least one category', async () => {
|
it('creates a post with valid title, content, and at least one category', async () => {
|
||||||
await wrapper.find('.submit-button-for-test').trigger('click')
|
await wrapper.find('form').trigger('submit')
|
||||||
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expect.objectContaining(expectedParams))
|
|
||||||
})
|
|
||||||
|
|
||||||
it("sends a fallback language based on a user's locale", () => {
|
|
||||||
wrapper.find('.submit-button-for-test').trigger('click')
|
|
||||||
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expect.objectContaining(expectedParams))
|
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expect.objectContaining(expectedParams))
|
||||||
})
|
})
|
||||||
|
|
||||||
it('supports changing the language', async () => {
|
it('supports changing the language', async () => {
|
||||||
expectedParams.variables.language = 'de'
|
expectedParams.variables.language = 'de'
|
||||||
deutschOption = wrapper.findAll('li').at(0)
|
deutschLanguage = wrapper.findAll('li').filter(language => language.text() === 'Deutsch')
|
||||||
deutschOption.trigger('click')
|
deutschLanguage.trigger('click')
|
||||||
wrapper.find('.submit-button-for-test').trigger('click')
|
wrapper.find('form').trigger('submit')
|
||||||
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expect.objectContaining(expectedParams))
|
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expect.objectContaining(expectedParams))
|
||||||
})
|
})
|
||||||
|
|
||||||
it('supports adding a teaser image', async () => {
|
it('supports adding a teaser image', async () => {
|
||||||
expectedParams.variables.imageUpload = imageUpload
|
expectedParams.variables.imageUpload = imageUpload
|
||||||
wrapper.find(TeaserImage).vm.$emit('addTeaserImage', imageUpload)
|
wrapper.find(TeaserImage).vm.$emit('addTeaserImage', imageUpload)
|
||||||
await wrapper.find('.submit-button-for-test').trigger('click')
|
await wrapper.find('form').trigger('submit')
|
||||||
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expect.objectContaining(expectedParams))
|
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expect.objectContaining(expectedParams))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('content is valid with just a link', async () => {
|
||||||
|
await wrapper.vm.updateEditorContent(
|
||||||
|
'<a href="https://www.youtube.com/watch?v=smoEelV6FUk" target="_blank"></a>',
|
||||||
|
)
|
||||||
|
wrapper.find('form').trigger('submit')
|
||||||
|
expect(mocks.$apollo.mutate).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
|
||||||
it("pushes the user to the post's page", async () => {
|
it("pushes the user to the post's page", async () => {
|
||||||
wrapper.find('.submit-button-for-test').trigger('click')
|
wrapper.find('form').trigger('submit')
|
||||||
await mocks.$apollo.mutate
|
await mocks.$apollo.mutate
|
||||||
expect(mocks.$router.push).toHaveBeenCalledTimes(1)
|
expect(mocks.$router.push).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('shows a success toaster', async () => {
|
it('shows a success toaster', async () => {
|
||||||
wrapper.find('.submit-button-for-test').trigger('click')
|
wrapper.find('form').trigger('submit')
|
||||||
await mocks.$apollo.mutate
|
await mocks.$apollo.mutate
|
||||||
expect(mocks.$toast.success).toHaveBeenCalledTimes(1)
|
expect(mocks.$toast.success).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
@ -275,11 +277,17 @@ describe('ContributionForm.vue', () => {
|
|||||||
postTitleInput.setValue(postTitle)
|
postTitleInput.setValue(postTitle)
|
||||||
await wrapper.vm.updateEditorContent(postContent)
|
await wrapper.vm.updateEditorContent(postContent)
|
||||||
categoryIds = ['cat12']
|
categoryIds = ['cat12']
|
||||||
wrapper.find(CategoriesSelect).vm.$emit('updateCategories', categoryIds)
|
wrapper.find(CategoriesSelect).setData({ categories })
|
||||||
|
englishLanguage = wrapper.findAll('li').filter(language => language.text() === 'English')
|
||||||
|
englishLanguage.trigger('click')
|
||||||
|
dataPrivacyButton = await wrapper
|
||||||
|
.find(CategoriesSelect)
|
||||||
|
.find('[data-test="category-buttons-cat12"]')
|
||||||
|
dataPrivacyButton.trigger('click')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('shows an error toaster when apollo mutation rejects', async () => {
|
it('shows an error toaster when apollo mutation rejects', async () => {
|
||||||
await wrapper.find('.submit-button-for-test').trigger('click')
|
await wrapper.find('form').trigger('submit')
|
||||||
await mocks.$apollo.mutate
|
await mocks.$apollo.mutate
|
||||||
await expect(mocks.$toast.error).toHaveBeenCalledWith('Not Authorised!')
|
await expect(mocks.$toast.error).toHaveBeenCalledWith('Not Authorised!')
|
||||||
})
|
})
|
||||||
@ -341,11 +349,11 @@ describe('ContributionForm.vue', () => {
|
|||||||
expectedParams = {
|
expectedParams = {
|
||||||
mutation: PostMutations().UpdatePost,
|
mutation: PostMutations().UpdatePost,
|
||||||
variables: {
|
variables: {
|
||||||
title: postTitle,
|
title: propsData.contribution.title,
|
||||||
content: postContent,
|
content: propsData.contribution.content,
|
||||||
language: propsData.contribution.language,
|
language: propsData.contribution.language,
|
||||||
id: propsData.contribution.id,
|
id: propsData.contribution.id,
|
||||||
categoryIds,
|
categoryIds: ['cat12'],
|
||||||
image,
|
image,
|
||||||
imageUpload: null,
|
imageUpload: null,
|
||||||
},
|
},
|
||||||
@ -353,22 +361,20 @@ describe('ContributionForm.vue', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('calls the UpdatePost apollo mutation', async () => {
|
it('calls the UpdatePost apollo mutation', async () => {
|
||||||
postTitleInput = wrapper.find('.ds-input')
|
expectedParams.variables.content = postContent
|
||||||
postTitleInput.setValue(postTitle)
|
|
||||||
wrapper.vm.updateEditorContent(postContent)
|
wrapper.vm.updateEditorContent(postContent)
|
||||||
wrapper.find(CategoriesSelect).vm.$emit('updateCategories', categoryIds)
|
await wrapper.find('form').trigger('submit')
|
||||||
wrapper.find('.submit-button-for-test').trigger('click')
|
|
||||||
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expect.objectContaining(expectedParams))
|
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expect.objectContaining(expectedParams))
|
||||||
})
|
})
|
||||||
|
|
||||||
it('supports updating categories', async () => {
|
it('supports updating categories', async () => {
|
||||||
const categoryIds = ['cat3', 'cat51', 'cat37']
|
expectedParams.variables.categoryIds.push('cat3')
|
||||||
postTitleInput = wrapper.find('.ds-input')
|
wrapper.find(CategoriesSelect).setData({ categories })
|
||||||
postTitleInput.setValue(postTitle)
|
const healthWellbeingButton = await wrapper
|
||||||
wrapper.vm.updateEditorContent(postContent)
|
.find(CategoriesSelect)
|
||||||
expectedParams.variables.categoryIds = categoryIds
|
.find('[data-test="category-buttons-cat3"]')
|
||||||
wrapper.find(CategoriesSelect).vm.$emit('updateCategories', categoryIds)
|
healthWellbeingButton.trigger('click')
|
||||||
await wrapper.find('.submit-button-for-test').trigger('click')
|
await wrapper.find('form').trigger('submit')
|
||||||
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expect.objectContaining(expectedParams))
|
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expect.objectContaining(expectedParams))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,5 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<ds-form ref="contributionForm" v-model="form" :schema="formSchema">
|
<ds-form
|
||||||
|
class="contribution-form"
|
||||||
|
ref="contributionForm"
|
||||||
|
v-model="form"
|
||||||
|
:schema="formSchema"
|
||||||
|
@submit="submit"
|
||||||
|
>
|
||||||
<template slot-scope="{ errors }">
|
<template slot-scope="{ errors }">
|
||||||
<hc-teaser-image :contribution="contribution" @addTeaserImage="addTeaserImage">
|
<hc-teaser-image :contribution="contribution" @addTeaserImage="addTeaserImage">
|
||||||
<img
|
<img
|
||||||
@ -21,7 +27,13 @@
|
|||||||
name="title"
|
name="title"
|
||||||
autofocus
|
autofocus
|
||||||
/>
|
/>
|
||||||
<small class="smallTag">{{ form.title.length }}/{{ formSchema.title.max }}</small>
|
<ds-text align="right">
|
||||||
|
<ds-chip v-if="errors && errors.title" color="danger" size="base">
|
||||||
|
{{ form.title.length }}/{{ formSchema.title.max }}
|
||||||
|
<ds-icon name="warning"></ds-icon>
|
||||||
|
</ds-chip>
|
||||||
|
<ds-chip v-else size="base">{{ form.title.length }}/{{ formSchema.title.max }}</ds-chip>
|
||||||
|
</ds-text>
|
||||||
<client-only>
|
<client-only>
|
||||||
<hc-editor
|
<hc-editor
|
||||||
:users="users"
|
:users="users"
|
||||||
@ -29,27 +41,43 @@
|
|||||||
:hashtags="hashtags"
|
:hashtags="hashtags"
|
||||||
@input="updateEditorContent"
|
@input="updateEditorContent"
|
||||||
/>
|
/>
|
||||||
<small class="smallTag">{{ form.contentLength }}</small>
|
<ds-text align="right">
|
||||||
|
<ds-chip v-if="errors && errors.content" color="danger" size="base">
|
||||||
|
{{ contentLength }}
|
||||||
|
<ds-icon name="warning"></ds-icon>
|
||||||
|
</ds-chip>
|
||||||
|
<ds-chip v-else size="base">
|
||||||
|
{{ contentLength }}
|
||||||
|
</ds-chip>
|
||||||
|
</ds-text>
|
||||||
</client-only>
|
</client-only>
|
||||||
<ds-space margin-bottom="small" />
|
<ds-space margin-bottom="small" />
|
||||||
<hc-categories-select
|
<hc-categories-select model="categoryIds" :existingCategoryIds="form.categoryIds" />
|
||||||
model="categoryIds"
|
<ds-text align="right">
|
||||||
@updateCategories="updateCategories"
|
<ds-chip v-if="errors && errors.categoryIds" color="danger" size="base">
|
||||||
:existingCategoryIds="form.categoryIds"
|
{{ form.categoryIds.length }} / 3
|
||||||
/>
|
<ds-icon name="warning"></ds-icon>
|
||||||
|
</ds-chip>
|
||||||
|
<ds-chip v-else size="base">{{ form.categoryIds.length }} / 3</ds-chip>
|
||||||
|
</ds-text>
|
||||||
<ds-flex class="contribution-form-footer">
|
<ds-flex class="contribution-form-footer">
|
||||||
<ds-flex-item :width="{ base: '10%', sm: '10%', md: '10%', lg: '15%' }" />
|
<ds-flex-item :width="{ lg: '50%', md: '50%', sm: '100%' }" />
|
||||||
<ds-flex-item :width="{ base: '80%', sm: '30%', md: '30%', lg: '20%' }">
|
<ds-flex-item>
|
||||||
<ds-space margin-bottom="small" />
|
<ds-space margin-bottom="small" />
|
||||||
<ds-select
|
<ds-select
|
||||||
model="language"
|
model="language"
|
||||||
:options="form.languageOptions"
|
:options="languageOptions"
|
||||||
icon="globe"
|
icon="globe"
|
||||||
:placeholder="locale"
|
:placeholder="$t('contribution.languageSelectText')"
|
||||||
:label="$t('contribution.languageSelectLabel')"
|
:label="$t('contribution.languageSelectLabel')"
|
||||||
/>
|
/>
|
||||||
</ds-flex-item>
|
</ds-flex-item>
|
||||||
</ds-flex>
|
</ds-flex>
|
||||||
|
<ds-text align="right">
|
||||||
|
<ds-chip v-if="errors && errors.language" size="base" color="danger">
|
||||||
|
<ds-icon name="warning"></ds-icon>
|
||||||
|
</ds-chip>
|
||||||
|
</ds-text>
|
||||||
<ds-space />
|
<ds-space />
|
||||||
<div slot="footer" style="text-align: right">
|
<div slot="footer" style="text-align: right">
|
||||||
<ds-button
|
<ds-button
|
||||||
@ -60,15 +88,7 @@
|
|||||||
>
|
>
|
||||||
{{ $t('actions.cancel') }}
|
{{ $t('actions.cancel') }}
|
||||||
</ds-button>
|
</ds-button>
|
||||||
<ds-button
|
<ds-button type="submit" icon="check" :loading="loading" :disabled="errors" primary>
|
||||||
class="submit-button-for-test"
|
|
||||||
type="submit"
|
|
||||||
icon="check"
|
|
||||||
:loading="loading"
|
|
||||||
:disabled="failsValidations || errors"
|
|
||||||
primary
|
|
||||||
@click.prevent="submit"
|
|
||||||
>
|
|
||||||
{{ $t('actions.save') }}
|
{{ $t('actions.save') }}
|
||||||
</ds-button>
|
</ds-button>
|
||||||
</div>
|
</div>
|
||||||
@ -100,73 +120,78 @@ export default {
|
|||||||
contribution: { type: Object, default: () => {} },
|
contribution: { type: Object, default: () => {} },
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
|
const languageOptions = orderBy(locales, 'name').map(locale => {
|
||||||
|
return { label: locale.name, value: locale.code }
|
||||||
|
})
|
||||||
|
|
||||||
|
const formDefaults = {
|
||||||
|
title: '',
|
||||||
|
content: '',
|
||||||
|
teaserImage: null,
|
||||||
|
image: null,
|
||||||
|
language: null,
|
||||||
|
categoryIds: [],
|
||||||
|
}
|
||||||
|
let id = null
|
||||||
|
let slug = null
|
||||||
|
const form = { ...formDefaults }
|
||||||
|
if (this.contribution && this.contribution.id) {
|
||||||
|
id = this.contribution.id
|
||||||
|
slug = this.contribution.slug
|
||||||
|
form.title = this.contribution.title
|
||||||
|
form.content = this.contribution.content
|
||||||
|
form.image = this.contribution.image
|
||||||
|
form.language =
|
||||||
|
this.contribution && this.contribution.language
|
||||||
|
? languageOptions.find(o => this.contribution.language === o.value)
|
||||||
|
: null
|
||||||
|
form.categoryIds = this.categoryIds(this.contribution.categories)
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
form: {
|
form,
|
||||||
title: '',
|
|
||||||
content: '',
|
|
||||||
contentLength: 0,
|
|
||||||
teaserImage: null,
|
|
||||||
image: null,
|
|
||||||
language: null,
|
|
||||||
languageOptions: [],
|
|
||||||
categoryIds: [],
|
|
||||||
},
|
|
||||||
formSchema: {
|
formSchema: {
|
||||||
title: { required: true, min: 3, max: 100 },
|
title: { required: true, min: 3, max: 100 },
|
||||||
content: [{ required: true }],
|
content: { required: true },
|
||||||
|
categoryIds: {
|
||||||
|
type: 'array',
|
||||||
|
required: true,
|
||||||
|
validator: (rule, value) => {
|
||||||
|
const errors = []
|
||||||
|
if (!(value && value.length >= 1 && value.length <= 3)) {
|
||||||
|
errors.push(new Error(this.$t('common.validations.categories')))
|
||||||
|
}
|
||||||
|
return errors
|
||||||
|
},
|
||||||
|
},
|
||||||
|
language: { required: true },
|
||||||
},
|
},
|
||||||
id: null,
|
languageOptions,
|
||||||
|
id,
|
||||||
|
slug,
|
||||||
loading: false,
|
loading: false,
|
||||||
slug: null,
|
|
||||||
users: [],
|
users: [],
|
||||||
contentMin: 3,
|
contentMin: 3,
|
||||||
failsValidations: true,
|
|
||||||
hashtags: [],
|
hashtags: [],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
|
||||||
contribution: {
|
|
||||||
immediate: true,
|
|
||||||
handler: function(contribution) {
|
|
||||||
if (!contribution || !contribution.id) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.id = contribution.id
|
|
||||||
this.slug = contribution.slug
|
|
||||||
this.form.title = contribution.title
|
|
||||||
this.form.content = contribution.content
|
|
||||||
this.form.image = contribution.image
|
|
||||||
this.form.categoryIds = this.categoryIds(contribution.categories)
|
|
||||||
this.manageContent(this.form.content)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
computed: {
|
computed: {
|
||||||
locale() {
|
contentLength() {
|
||||||
const locale =
|
return this.$filters.removeHtml(this.form.content).length
|
||||||
this.contribution && this.contribution.language
|
|
||||||
? locales.find(loc => this.contribution.language === loc.code)
|
|
||||||
: locales.find(loc => this.$i18n.locale() === loc.code)
|
|
||||||
return locale.name
|
|
||||||
},
|
},
|
||||||
...mapGetters({
|
...mapGetters({
|
||||||
currentUser: 'auth/user',
|
currentUser: 'auth/user',
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
mounted() {
|
|
||||||
this.availableLocales()
|
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
submit() {
|
submit() {
|
||||||
const { title, content, image, teaserImage, categoryIds } = this.form
|
const {
|
||||||
let language
|
language: { value: language },
|
||||||
if (this.form.language) {
|
title,
|
||||||
language = this.form.language.value
|
content,
|
||||||
} else if (this.contribution && this.contribution.language) {
|
image,
|
||||||
language = this.contribution.language
|
teaserImage,
|
||||||
} else {
|
categoryIds,
|
||||||
language = this.$i18n.locale()
|
} = this.form
|
||||||
}
|
|
||||||
this.loading = true
|
this.loading = true
|
||||||
this.$apollo
|
this.$apollo
|
||||||
.mutate({
|
.mutate({
|
||||||
@ -185,7 +210,6 @@ export default {
|
|||||||
this.loading = false
|
this.loading = false
|
||||||
this.$toast.success(this.$t('contribution.success'))
|
this.$toast.success(this.$t('contribution.success'))
|
||||||
const result = data[this.id ? 'UpdatePost' : 'CreatePost']
|
const result = data[this.id ? 'UpdatePost' : 'CreatePost']
|
||||||
this.failedValidations = false
|
|
||||||
|
|
||||||
this.$router.push({
|
this.$router.push({
|
||||||
name: 'post-id-slug',
|
name: 'post-id-slug',
|
||||||
@ -195,45 +219,16 @@ export default {
|
|||||||
.catch(err => {
|
.catch(err => {
|
||||||
this.$toast.error(err.message)
|
this.$toast.error(err.message)
|
||||||
this.loading = false
|
this.loading = false
|
||||||
this.failedValidations = true
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
updateEditorContent(value) {
|
updateEditorContent(value) {
|
||||||
// TODO: Do smth????? what is happening
|
|
||||||
this.$refs.contributionForm.update('content', value)
|
this.$refs.contributionForm.update('content', value)
|
||||||
this.manageContent(value)
|
|
||||||
},
|
|
||||||
manageContent(content) {
|
|
||||||
// filter HTML out of content value
|
|
||||||
const str = content.replace(/<\/?[^>]+(>|$)/gm, '')
|
|
||||||
// Set counter length of text
|
|
||||||
this.form.contentLength = str.length
|
|
||||||
this.validatePost()
|
|
||||||
},
|
|
||||||
availableLocales() {
|
|
||||||
orderBy(locales, 'name').map(locale => {
|
|
||||||
this.form.languageOptions.push({ label: locale.name, value: locale.code })
|
|
||||||
})
|
|
||||||
},
|
|
||||||
updateCategories(ids) {
|
|
||||||
this.form.categoryIds = ids
|
|
||||||
this.validatePost()
|
|
||||||
},
|
},
|
||||||
addTeaserImage(file) {
|
addTeaserImage(file) {
|
||||||
this.form.teaserImage = file
|
this.form.teaserImage = file
|
||||||
},
|
},
|
||||||
categoryIds(categories) {
|
categoryIds(categories) {
|
||||||
const categoryIds = []
|
return categories.map(c => c.id)
|
||||||
categories.map(categoryId => {
|
|
||||||
categoryIds.push(categoryId.id)
|
|
||||||
})
|
|
||||||
return categoryIds
|
|
||||||
},
|
|
||||||
validatePost() {
|
|
||||||
const passesContentValidations = this.form.contentLength >= this.contentMin
|
|
||||||
const passesCategoryValidations =
|
|
||||||
this.form.categoryIds.length > 0 && this.form.categoryIds.length <= 3
|
|
||||||
this.failsValidations = !(passesContentValidations && passesCategoryValidations)
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
apollo: {
|
apollo: {
|
||||||
@ -288,4 +283,10 @@ export default {
|
|||||||
padding-right: 0;
|
padding-right: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.contribution-form {
|
||||||
|
.ds-chip {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -463,7 +463,8 @@
|
|||||||
"reportContent": "Melden",
|
"reportContent": "Melden",
|
||||||
"validations": {
|
"validations": {
|
||||||
"email": "muss eine gültige E-Mail Adresse sein",
|
"email": "muss eine gültige E-Mail Adresse sein",
|
||||||
"url": "muss eine gültige URL sein"
|
"url": "muss eine gültige URL sein",
|
||||||
|
"categories": "es müssen eine bis drei Kategorien ausgewählt werden"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
@ -608,7 +609,8 @@
|
|||||||
"filterFollow": "Beiträge filtern von Usern denen ich folge",
|
"filterFollow": "Beiträge filtern von Usern denen ich folge",
|
||||||
"filterALL": "Alle Beiträge anzeigen",
|
"filterALL": "Alle Beiträge anzeigen",
|
||||||
"success": "Gespeichert!",
|
"success": "Gespeichert!",
|
||||||
"languageSelectLabel": "Sprache",
|
"languageSelectLabel": "Sprache deines Beitrags",
|
||||||
|
"languageSelectText": "Sprache wählen",
|
||||||
"categories": {
|
"categories": {
|
||||||
"infoSelectedNoOfMaxCategories": "{chosen} von {max} Kategorien ausgewählt"
|
"infoSelectedNoOfMaxCategories": "{chosen} von {max} Kategorien ausgewählt"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -464,7 +464,8 @@
|
|||||||
"reportContent": "Report",
|
"reportContent": "Report",
|
||||||
"validations": {
|
"validations": {
|
||||||
"email": "must be a valid e-mail address",
|
"email": "must be a valid e-mail address",
|
||||||
"url": "must be a valid URL"
|
"url": "must be a valid URL",
|
||||||
|
"categories": "at least one and at most three categories must be selected"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
@ -609,7 +610,8 @@
|
|||||||
"filterFollow": "Filter contributions from users I follow",
|
"filterFollow": "Filter contributions from users I follow",
|
||||||
"filterALL": "View all contributions",
|
"filterALL": "View all contributions",
|
||||||
"success": "Saved!",
|
"success": "Saved!",
|
||||||
"languageSelectLabel": "Language",
|
"languageSelectLabel": "Language of your contribution",
|
||||||
|
"languageSelectText": "Select Language",
|
||||||
"categories": {
|
"categories": {
|
||||||
"infoSelectedNoOfMaxCategories": "{chosen} of {max} categories selected"
|
"infoSelectedNoOfMaxCategories": "{chosen} of {max} categories selected"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -10,41 +10,37 @@
|
|||||||
<script>
|
<script>
|
||||||
import HcContributionForm from '~/components/ContributionForm/ContributionForm'
|
import HcContributionForm from '~/components/ContributionForm/ContributionForm'
|
||||||
import PostQuery from '~/graphql/PostQuery'
|
import PostQuery from '~/graphql/PostQuery'
|
||||||
|
import { mapGetters } from 'vuex'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
HcContributionForm,
|
HcContributionForm,
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
user() {
|
...mapGetters({
|
||||||
return this.$store.getters['auth/user']
|
user: 'auth/user',
|
||||||
},
|
}),
|
||||||
author() {
|
|
||||||
return this.contribution ? this.contribution.author : {}
|
|
||||||
},
|
|
||||||
contribution() {
|
|
||||||
return this.Post ? this.Post[0] : {}
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
watch: {
|
async asyncData(context) {
|
||||||
contribution() {
|
const {
|
||||||
if (this.author.id !== this.user.id) {
|
app,
|
||||||
throw new Error(`You can't edit that!`)
|
store,
|
||||||
}
|
error,
|
||||||
},
|
params: { id },
|
||||||
},
|
} = context
|
||||||
apollo: {
|
const client = app.apolloProvider.defaultClient
|
||||||
Post: {
|
const {
|
||||||
query() {
|
data: {
|
||||||
return PostQuery(this.$i18n)
|
Post: [contribution],
|
||||||
},
|
},
|
||||||
variables() {
|
} = await client.query({
|
||||||
return {
|
query: PostQuery(app.$i18n),
|
||||||
id: this.$route.params.id,
|
variables: { id },
|
||||||
}
|
})
|
||||||
},
|
if (contribution.author.id !== store.getters['auth/user'].id) {
|
||||||
fetchPolicy: 'cache-and-network',
|
error({ statusCode: 403, message: "You can't edit that!" })
|
||||||
},
|
}
|
||||||
|
return { contribution }
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user