Merge pull request #5597 from Ocelot-Social-Community/5540-Refactor-Design-Of-Category-Filter-In-Filter-Menu

refactor: 🍰 Refactor Design Of Category Filter In Filter Menu
This commit is contained in:
Wolfgang Huß 2022-10-28 15:37:42 +02:00 committed by GitHub
commit 9a17ac12f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 290 additions and 237 deletions

View File

@ -269,7 +269,7 @@ jobs:
report_name: Coverage Webapp
type: lcov
result_path: ./coverage/lcov.info
min_coverage: 64
min_coverage: 63
token: ${{ github.token }}
##############################################################################

View File

@ -59,20 +59,22 @@ describe('CategoriesFilter.vue', () => {
describe('mount', () => {
it('starts with all categories button active', () => {
const allCategoriesButton = wrapper.find('.categories-filter .sidebar .base-button')
const allCategoriesButton = wrapper.find('.categories-filter .item-all-topics .base-button')
expect(allCategoriesButton.attributes().class).toContain('--filled')
})
it('sets category button attribute `filled` when corresponding category is filtered', async () => {
getters['posts/filteredCategoryIds'] = jest.fn(() => ['cat9'])
wrapper = await Wrapper()
democracyAndPoliticsButton = wrapper.findAll('.categories-filter .item .base-button').at(2)
democracyAndPoliticsButton = wrapper.find('.categories-filter .item-save-topics .base-button')
expect(democracyAndPoliticsButton.attributes().class).toContain('--filled')
})
describe('click on an "catetories-buttons" button', () => {
it('calls TOGGLE_CATEGORY when clicked', () => {
environmentAndNatureButton = wrapper.findAll('.categories-filter .item .base-button').at(0)
environmentAndNatureButton = wrapper
.findAll('.categories-filter .item-category .base-button')
.at(0)
environmentAndNatureButton.trigger('click')
expect(mutations['posts/TOGGLE_CATEGORY']).toHaveBeenCalledWith({}, 'cat4')
})
@ -82,7 +84,7 @@ describe('CategoriesFilter.vue', () => {
it('when all button is clicked', async () => {
getters['posts/filteredCategoryIds'] = jest.fn(() => ['cat9'])
wrapper = await Wrapper()
const allCategoriesButton = wrapper.find('.categories-filter .sidebar .base-button')
const allCategoriesButton = wrapper.find('.categories-filter .item-all-topics .base-button')
allCategoriesButton.trigger('click')
expect(mutations['posts/RESET_CATEGORIES']).toHaveBeenCalledTimes(1)
})
@ -91,7 +93,7 @@ describe('CategoriesFilter.vue', () => {
describe('save categories', () => {
it('calls the API', async () => {
wrapper = await Wrapper()
const saveButton = wrapper.findAll('.categories-filter .sidebar .base-button').at(1)
const saveButton = wrapper.find('.categories-filter .item-save-topics .base-button')
saveButton.trigger('click')
expect(apolloMutationMock).toBeCalled()
})

View File

@ -1,17 +1,23 @@
<template>
<filter-menu-section :title="$t('filter-menu.categories')" class="categories-filter">
<template #sidebar>
<labeled-button
:filled="!filteredCategoryIds.length"
:label="$t('filter-menu.all')"
icon="check"
@click="resetCategories"
/>
<template #filter-topics>
<li class="item item-all-topics">
<labeled-button
:filled="!filteredCategoryIds.length"
:label="$t('filter-menu.all')"
icon="check"
@click="resetCategories"
/>
</li>
<li class="item item-save-topics">
<labeled-button filled :label="$t('actions.save')" icon="save" @click="saveCategories" />
</li>
<hr />
<labeled-button filled :label="$t('actions.save')" icon="save" @click="saveCategories" />
<ds-space margin="base" />
</template>
<template #filter-list>
<li v-for="category in categories" :key="category.id" class="item">
<li v-for="category in categories" :key="category.id" class="item item-category">
<labeled-button
:icon="category.icon"
:filled="filteredCategoryIds.includes(category.id)"
@ -39,6 +45,9 @@ export default {
FilterMenuSection,
LabeledButton,
},
props: {
showMobileMenu: { type: Boolean, default: false },
},
data() {
return {
categories: [],

View File

@ -1,64 +1,79 @@
import { mount } from '@vue/test-utils'
import Vuex from 'vuex'
// import Vuex from 'vuex'
import EmotionsFilter from './EmotionsFilter'
const localVue = global.localVue
let wrapper, happyEmotionButton
describe('EmotionsFilter', () => {
const mutations = {
'posts/TOGGLE_EMOTION': jest.fn(),
'posts/RESET_EMOTIONS': jest.fn(),
}
const getters = {
'posts/filteredByEmotions': jest.fn(() => []),
}
const mocks = {
$t: jest.fn((string) => string),
}
// let wrapper, happyEmotionButton
describe('mount', () => {
let wrapper
const Wrapper = () => {
const store = new Vuex.Store({ mutations, getters })
return mount(EmotionsFilter, { mocks, localVue, store })
return mount(EmotionsFilter, { localVue })
}
beforeEach(() => {
wrapper = Wrapper()
})
describe('mount', () => {
it('starts with all emotions button active', () => {
const allEmotionsButton = wrapper.find('.emotions-filter .sidebar .base-button')
expect(allEmotionsButton.attributes().class).toContain('--filled')
})
describe('click on an "emotion-button" button', () => {
it('calls TOGGLE_EMOTION when clicked', () => {
const wrapper = Wrapper()
happyEmotionButton = wrapper.findAll('.emotion-button > .base-button').at(1)
happyEmotionButton.trigger('click')
expect(mutations['posts/TOGGLE_EMOTION']).toHaveBeenCalledWith({}, 'happy')
})
it('sets the attribute `src` to colorized image', () => {
getters['posts/filteredByEmotions'] = jest.fn(() => ['happy'])
const wrapper = Wrapper()
happyEmotionButton = wrapper.findAll('.emotion-button > .base-button').at(1)
const happyEmotionButtonImage = happyEmotionButton.find('img')
expect(happyEmotionButtonImage.attributes().src).toEqual('/img/svg/emoji/happy_color.svg')
})
})
describe('clears filter', () => {
it('when all button is clicked', async () => {
getters['posts/filteredByEmotions'] = jest.fn(() => ['happy'])
wrapper = await Wrapper()
const allEmotionsButton = wrapper.find('.emotions-filter .sidebar .base-button')
allEmotionsButton.trigger('click')
expect(mutations['posts/RESET_EMOTIONS']).toHaveBeenCalledTimes(1)
})
})
it('renders button DIV', () => {
expect(wrapper.find('div').exists()).toBe(true)
})
})
// describe('EmotionsFilter', () => {
// const mutations = {
// 'posts/TOGGLE_EMOTION': jest.fn(),
// 'posts/RESET_EMOTIONS': jest.fn(),
// }
// const getters = {
// 'posts/filteredByEmotions': jest.fn(() => []),
// }
// const mocks = {
// $t: jest.fn((string) => string),
// }
// const Wrapper = () => {
// const store = new Vuex.Store({ mutations, getters })
// return mount(EmotionsFilter, { mocks, localVue, store })
// }
// beforeEach(() => {
// wrapper = Wrapper()
// })
// describe('mount', () => {
// it('starts with all emotions button active', () => {
// const allEmotionsButton = wrapper.find('.emotions-filter .sidebar .base-button')
// expect(allEmotionsButton.attributes().class).toContain('--filled')
// })
// describe('click on an "emotion-button" button', () => {
// it('calls TOGGLE_EMOTION when clicked', () => {
// const wrapper = Wrapper()
// happyEmotionButton = wrapper.findAll('.emotion-button > .base-button').at(1)
// happyEmotionButton.trigger('click')
// expect(mutations['posts/TOGGLE_EMOTION']).toHaveBeenCalledWith({}, 'happy')
// })
// it('sets the attribute `src` to colorized image', () => {
// getters['posts/filteredByEmotions'] = jest.fn(() => ['happy'])
// const wrapper = Wrapper()
// happyEmotionButton = wrapper.findAll('.emotion-button > .base-button').at(1)
// const happyEmotionButtonImage = happyEmotionButton.find('img')
// expect(happyEmotionButtonImage.attributes().src).toEqual('/img/svg/emoji/happy_color.svg')
// })
// })
// describe('clears filter', () => {
// it('when all button is clicked', async () => {
// getters['posts/filteredByEmotions'] = jest.fn(() => ['happy'])
// wrapper = await Wrapper()
// const allEmotionsButton = wrapper.find('.emotions-filter .sidebar .base-button')
// allEmotionsButton.trigger('click')
// expect(mutations['posts/RESET_EMOTIONS']).toHaveBeenCalledTimes(1)
// })
// })
// })
// })

View File

@ -1,5 +1,6 @@
<template>
<filter-menu-section :title="$t('filter-menu.emotions')" class="emotions-filter">
<div>
<!-- <filter-menu-section :title="$t('filter-menu.emotions')" class="emotions-filter">
<template #sidebar>
<labeled-button
:filled="!filteredByEmotions.length"
@ -17,43 +18,44 @@
/>
</li>
</template>
</filter-menu-section>
</filter-menu-section> -->
</div>
</template>
<script>
import { mapGetters, mapMutations } from 'vuex'
import EmotionButton from '~/components/EmotionButton/EmotionButton'
import FilterMenuSection from '~/components/FilterMenu/FilterMenuSection'
import LabeledButton from '~/components/_new/generic/LabeledButton/LabeledButton'
// import { mapGetters, mapMutations } from 'vuex'
// import EmotionButton from '~/components/EmotionButton/EmotionButton'
// import FilterMenuSection from '~/components/FilterMenu/FilterMenuSection'
// import LabeledButton from '~/components/_new/generic/LabeledButton/LabeledButton'
export default {
components: {
EmotionButton,
FilterMenuSection,
LabeledButton,
},
data() {
return {
emotionsArray: ['funny', 'happy', 'surprised', 'cry', 'angry'],
}
},
computed: {
...mapGetters({
filteredByEmotions: 'posts/filteredByEmotions',
currentUser: 'auth/user',
}),
},
methods: {
...mapMutations({
resetEmotions: 'posts/RESET_EMOTIONS',
toogleFilteredByEmotions: 'posts/TOGGLE_EMOTION',
}),
iconPath(emotion) {
if (this.filteredByEmotions.includes(emotion)) {
return `/img/svg/emoji/${emotion}_color.svg`
}
return `/img/svg/emoji/${emotion}.svg`
},
},
}
// export default {
// components: {
// EmotionButton,
// FilterMenuSection,
// LabeledButton,
// },
// data() {
// return {
// emotionsArray: ['funny', 'happy', 'surprised', 'cry', 'angry'],
// }
// },
// computed: {
// ...mapGetters({
// filteredByEmotions: 'posts/filteredByEmotions',
// currentUser: 'auth/user',
// }),
// },
// methods: {
// ...mapMutations({
// resetEmotions: 'posts/RESET_EMOTIONS',
// toogleFilteredByEmotions: 'posts/TOGGLE_EMOTION',
// }),
// iconPath(emotion) {
// if (this.filteredByEmotions.includes(emotion)) {
// return `/img/svg/emoji/${emotion}_color.svg`
// }
// return `/img/svg/emoji/${emotion}.svg`
// },
// },
// }
</script>

View File

@ -14,7 +14,7 @@
<div class="filter-menu-options">
<h2 class="title">{{ $t('filter-menu.filter-by') }}</h2>
<following-filter />
<categories-filter v-if="categoriesActive" />
<categories-filter v-if="categoriesActive" :showMobileMenu="showMobileMenu" />
</div>
<div class="filter-menu-options">
<h2 class="title">{{ $t('filter-menu.order-by') }}</h2>
@ -46,6 +46,7 @@ export default {
props: {
placement: { type: String },
offset: { type: [String, Number] },
showMobileMenu: { type: Boolean, default: false },
},
computed: {
...mapGetters({

View File

@ -1,10 +1,17 @@
<template>
<section class="filter-menu-section">
<h3 v-if="title" class="title">{{ title }}</h3>
<aside class="sidebar">
<ul class="filter-list">
<slot name="filter-follower" />
</ul>
<ul class="filter-list">
<slot name="filter-topics" />
</ul>
<!-- <aside class="sidebar">
<slot name="sidebar" />
</aside>
<div v-if="divider" class="divider" />
</aside> -->
<!-- <div v-if="divider" class="divider" /> -->
<ul class="filter-list">
<slot name="filter-list" />
</ul>
@ -14,10 +21,10 @@
<script>
export default {
props: {
divider: {
type: Boolean,
default: true,
},
// divider: {
// type: Boolean,
// default: true,
// },
title: {
type: String,
},
@ -37,42 +44,35 @@ export default {
font-size: $font-size-base;
}
> .sidebar {
display: flex;
flex-wrap: wrap;
flex-basis: 80%;
flex-grow: 1;
max-width: $size-width-filter-sidebar;
}
// > .sidebar {
// display: flex;
// flex-wrap: wrap;
// flex-basis: 80%;
// flex-grow: 1;
// max-width: $size-width-filter-sidebar;
// }
> .divider {
border-left: $border-size-base solid $border-color-soft;
margin: $space-small;
margin-left: 0;
}
// > .divider {
// // border-left: $border-size-base solid $border-color-soft;
// margin: $space-small;
// margin-left: 0;
// border-top: $border-size-base solid $border-color-soft;
// }
> .filter-list {
display: flex;
flex-wrap: wrap;
flex-basis: 80%;
flex-basis: 100%;
flex-grow: 1;
> .item {
width: 50%;
width: 30%;
padding: 0 $space-x-small;
margin-bottom: $space-small;
text-align: center;
@media only screen and (max-width: 800px) {
width: 50%;
}
@media only screen and (max-width: 630px) {
width: 40%;
}
@media only screen and (max-width: 440px) {
width: 30%;
@media only screen and (min-width: 800px) {
width: 20%;
}
}
}
@ -84,14 +84,14 @@ export default {
text-align: center;
}
> .sidebar {
max-width: none;
}
// > .sidebar {
// max-width: none;
// }
> .divider {
border-top: $border-size-base solid $border-color-soft;
margin: $space-small;
}
// > .divider {
// border-top: $border-size-base solid $border-color-soft;
// margin: $space-small;
// }
}
}
</style>

View File

@ -35,12 +35,16 @@ describe('FollowingFilter', () => {
it('sets "filter-by-followed" button attribute `filled`', () => {
getters['posts/filteredByUsersFollowed'] = jest.fn(() => true)
const wrapper = Wrapper()
expect(wrapper.find('.following-filter .sidebar .base-button').classes('--filled')).toBe(true)
expect(
wrapper
.find('.following-filter .filter-list .follower-item .base-button')
.classes('--filled'),
).toBe(true)
})
describe('click "filter-by-followed" button', () => {
it('calls TOGGLE_FILTER_BY_FOLLOWED', () => {
wrapper.find('.following-filter .sidebar .base-button').trigger('click')
wrapper.find('.following-filter .filter-list .follower-item .base-button').trigger('click')
expect(mutations['posts/TOGGLE_FILTER_BY_FOLLOWED']).toHaveBeenCalledWith({}, 'u34')
})
})

View File

@ -1,13 +1,15 @@
<template>
<filter-menu-section :divider="false" class="following-filter">
<template #sidebar>
<labeled-button
icon="user-plus"
:label="$t('filter-menu.following')"
:filled="filteredByUsersFollowed"
:title="$t('contribution.filterFollow')"
@click="toggleFilteredByFollowed(currentUser.id)"
/>
<template #filter-follower>
<li class="item follower-item">
<labeled-button
icon="user-plus"
:label="$t('filter-menu.following')"
:filled="filteredByUsersFollowed"
:title="$t('contribution.filterFollow')"
@click="toggleFilteredByFollowed(currentUser.id)"
/>
</li>
</template>
</filter-menu-section>
</template>

View File

@ -1,70 +1,85 @@
import { mount } from '@vue/test-utils'
import Vuex from 'vuex'
import locales from '~/locales'
import orderBy from 'lodash/orderBy'
// import Vuex from 'vuex'
// import locales from '~/locales'
// import orderBy from 'lodash/orderBy'
import LanguagesFilter from './LanguagesFilter'
const localVue = global.localVue
let wrapper, englishButton, spanishButton
// let wrapper, englishButton, spanishButton
const languages = orderBy(locales, 'name')
describe('LanguagesFilter.vue', () => {
const mutations = {
'posts/TOGGLE_LANGUAGE': jest.fn(),
'posts/RESET_LANGUAGES': jest.fn(),
}
const getters = {
'posts/filteredLanguageCodes': jest.fn(() => []),
}
const mocks = {
$t: jest.fn((string) => string),
}
// const languages = orderBy(locales, 'name')
describe('mount', () => {
let wrapper
const Wrapper = () => {
const store = new Vuex.Store({ mutations, getters })
return mount(LanguagesFilter, { mocks, localVue, store })
return mount(LanguagesFilter, { localVue })
}
beforeEach(() => {
wrapper = Wrapper()
})
describe('mount', () => {
it('starts with all categories button active', () => {
const allLanguagesButton = wrapper.find('.languages-filter .sidebar .base-button')
expect(allLanguagesButton.attributes().class).toContain('--filled')
})
it('sets language button attribute `filled` when corresponding language is filtered', () => {
getters['posts/filteredLanguageCodes'] = jest.fn(() => ['es'])
const wrapper = Wrapper()
spanishButton = wrapper
.findAll('.languages-filter .item .base-button')
.at(languages.findIndex((l) => l.code === 'es'))
expect(spanishButton.attributes().class).toContain('--filled')
})
describe('click on an "language-button" button', () => {
it('calls TOGGLE_LANGUAGE when clicked', () => {
const wrapper = Wrapper()
englishButton = wrapper
.findAll('.languages-filter .item .base-button')
.at(languages.findIndex((l) => l.code === 'en'))
englishButton.trigger('click')
expect(mutations['posts/TOGGLE_LANGUAGE']).toHaveBeenCalledWith({}, 'en')
})
})
describe('clears filter', () => {
it('when all button is clicked', async () => {
getters['posts/filteredLanguageCodes'] = jest.fn(() => ['en'])
wrapper = await Wrapper()
const allLanguagesButton = wrapper.find('.languages-filter .sidebar .base-button')
allLanguagesButton.trigger('click')
expect(mutations['posts/RESET_LANGUAGES']).toHaveBeenCalledTimes(1)
})
})
it('renders button DIV', () => {
expect(wrapper.find('div').exists()).toBe(true)
})
})
// describe('LanguagesFilter.vue', () => {
// const mutations = {
// 'posts/TOGGLE_LANGUAGE': jest.fn(),
// 'posts/RESET_LANGUAGES': jest.fn(),
// }
// const getters = {
// 'posts/filteredLanguageCodes': jest.fn(() => []),
// }
// const mocks = {
// $t: jest.fn((string) => string),
// }
// const Wrapper = () => {
// const store = new Vuex.Store({ mutations, getters })
// return mount(LanguagesFilter, { mocks, localVue, store })
// }
// beforeEach(() => {
// wrapper = Wrapper()
// })
// describe('mount', () => {
// it('starts with all categories button active', () => {
// const allLanguagesButton = wrapper.find('.languages-filter .sidebar .base-button')
// expect(allLanguagesButton.attributes().class).toContain('--filled')
// })
// it('sets language button attribute `filled` when corresponding language is filtered', () => {
// getters['posts/filteredLanguageCodes'] = jest.fn(() => ['es'])
// const wrapper = Wrapper()
// spanishButton = wrapper
// .findAll('.languages-filter .item .base-button')
// .at(languages.findIndex((l) => l.code === 'es'))
// expect(spanishButton.attributes().class).toContain('--filled')
// })
// describe('click on an "language-button" button', () => {
// it('calls TOGGLE_LANGUAGE when clicked', () => {
// const wrapper = Wrapper()
// englishButton = wrapper
// .findAll('.languages-filter .item .base-button')
// .at(languages.findIndex((l) => l.code === 'en'))
// englishButton.trigger('click')
// expect(mutations['posts/TOGGLE_LANGUAGE']).toHaveBeenCalledWith({}, 'en')
// })
// })
// describe('clears filter', () => {
// it('when all button is clicked', async () => {
// getters['posts/filteredLanguageCodes'] = jest.fn(() => ['en'])
// wrapper = await Wrapper()
// const allLanguagesButton = wrapper.find('.languages-filter .sidebar .base-button')
// allLanguagesButton.trigger('click')
// expect(mutations['posts/RESET_LANGUAGES']).toHaveBeenCalledTimes(1)
// })
// })
// })
// })

View File

@ -1,7 +1,9 @@
<template>
<filter-menu-section :title="$t('filter-menu.languages')" class="languages-filter">
<template #sidebar>
<div>
<!-- <filter-menu-section :title="$t('filter-menu.languages')" class="languages-filter">
<template>
<labeled-button
class="filter-languages"
:filled="!filteredLanguageCodes.length"
:label="$t('filter-menu.all')"
icon="check"
@ -19,36 +21,37 @@
</base-button>
</li>
</template>
</filter-menu-section>
</filter-menu-section> -->
</div>
</template>
<script>
import { mapGetters, mapMutations } from 'vuex'
import orderBy from 'lodash/orderBy'
import locales from '~/locales'
import FilterMenuSection from '~/components/FilterMenu/FilterMenuSection'
import LabeledButton from '~/components/_new/generic/LabeledButton/LabeledButton'
// import { mapGetters, mapMutations } from 'vuex'
// import orderBy from 'lodash/orderBy'
// import locales from '~/locales'
// import FilterMenuSection from '~/components/FilterMenu/FilterMenuSection'
// import LabeledButton from '~/components/_new/generic/LabeledButton/LabeledButton'
export default {
components: {
FilterMenuSection,
LabeledButton,
},
computed: {
...mapGetters({
filteredLanguageCodes: 'posts/filteredLanguageCodes',
}),
},
methods: {
...mapMutations({
resetLanguages: 'posts/RESET_LANGUAGES',
toggleLanguage: 'posts/TOGGLE_LANGUAGE',
}),
},
data() {
return {
locales: orderBy(locales, 'name'),
}
},
}
// export default {
// components: {
// FilterMenuSection,
// LabeledButton,
// },
// computed: {
// ...mapGetters({
// filteredLanguageCodes: 'posts/filteredLanguageCodes',
// }),
// },
// methods: {
// ...mapMutations({
// resetLanguages: 'posts/RESET_LANGUAGES',
// toggleLanguage: 'posts/TOGGLE_LANGUAGE',
// }),
// },
// data() {
// return {
// locales: orderBy(locales, 'name'),
// }
// },
// }
</script>

View File

@ -92,7 +92,7 @@
</ds-flex>
<!-- mobile header menu -->
<div v-else>
<div v-else class="mobil-header-box">
<!-- logo, hamburger-->
<ds-flex>
<ds-flex-item :width="{ base: LOGOS.LOGO_HEADER_WIDTH }" style="margin-right: 20px">
@ -139,7 +139,7 @@
style="flex-grow: 0; flex-basis: auto; padding: 20px 0"
>
<client-only>
<filter-menu v-show="showFilterMenuDropdown" />
<filter-menu v-show="showFilterMenuDropdown" :showMobileMenu="showMobileMenu" />
</client-only>
</ds-flex-item>
</ds-flex>

View File

@ -3,7 +3,7 @@ import gql from 'graphql-tag'
export default () => {
return gql`
query {
Category {
Category(orderBy: slug_asc) {
id
slug
icon