mirror of
https://github.com/Ocelot-Social-Community/Ocelot-Social.git
synced 2025-12-12 23:35:58 +00:00
feat(webapp): badges UI (#8426)
- New badge UI, including editor. - Adds config to enable/disable badges. --------- Co-authored-by: Sebastian Stein <sebastian@codepassion.de> Co-authored-by: Maximilian Harz <maxharz@gmail.com>
This commit is contained in:
parent
0873fc748c
commit
2fd138697f
76
webapp/components/BadgeSelection.spec.js
Normal file
76
webapp/components/BadgeSelection.spec.js
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import { render, screen, fireEvent } from '@testing-library/vue'
|
||||||
|
import BadgeSelection from './BadgeSelection.vue'
|
||||||
|
|
||||||
|
const localVue = global.localVue
|
||||||
|
|
||||||
|
describe('Badges.vue', () => {
|
||||||
|
const Wrapper = (propsData) => {
|
||||||
|
return render(BadgeSelection, {
|
||||||
|
propsData,
|
||||||
|
localVue,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('without badges', () => {
|
||||||
|
it('renders', () => {
|
||||||
|
const wrapper = Wrapper({ badges: [] })
|
||||||
|
expect(wrapper.container).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('with badges', () => {
|
||||||
|
const badges = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
icon: '/path/to/some/icon',
|
||||||
|
isDefault: false,
|
||||||
|
description: 'Some description',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
icon: '/path/to/another/icon',
|
||||||
|
isDefault: true,
|
||||||
|
description: 'Another description',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
icon: '/path/to/third/icon',
|
||||||
|
isDefault: false,
|
||||||
|
description: 'Third description',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
let wrapper
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
wrapper = Wrapper({ badges })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders', () => {
|
||||||
|
expect(wrapper.container).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('clicking on a badge', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
const badge = screen.getByText(badges[1].description)
|
||||||
|
await fireEvent.click(badge)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('emits badge-selected with badge', async () => {
|
||||||
|
expect(wrapper.emitted()['badge-selected']).toEqual([[badges[1]]])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('clicking twice on a badge', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
const badge = screen.getByText(badges[1].description)
|
||||||
|
await fireEvent.click(badge)
|
||||||
|
await fireEvent.click(badge)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('emits badge-selected with null', async () => {
|
||||||
|
expect(wrapper.emitted()['badge-selected']).toEqual([[badges[1]], [null]])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
102
webapp/components/BadgeSelection.vue
Normal file
102
webapp/components/BadgeSelection.vue
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
<template>
|
||||||
|
<div class="badge-selection">
|
||||||
|
<button
|
||||||
|
v-for="(badge, index) in badges"
|
||||||
|
:key="badge.id"
|
||||||
|
class="badge-selection-item"
|
||||||
|
@click="handleBadgeClick(badge, index)"
|
||||||
|
>
|
||||||
|
<div class="badge-icon">
|
||||||
|
<img :src="badge.icon | proxyApiUrl" :alt="badge.id" />
|
||||||
|
</div>
|
||||||
|
<div class="badge-info">
|
||||||
|
<div class="badge-description">{{ badge.description }}</div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'BadgeSelection',
|
||||||
|
props: {
|
||||||
|
badges: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
selectedIndex: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleBadgeClick(badge, index) {
|
||||||
|
if (this.selectedIndex === index) {
|
||||||
|
this.selectedIndex = null
|
||||||
|
this.$emit('badge-selected', null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.selectedIndex = index
|
||||||
|
this.$emit('badge-selected', badge)
|
||||||
|
},
|
||||||
|
resetSelection() {
|
||||||
|
this.selectedIndex = null
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.badge-selection {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
|
||||||
|
.badge-selection-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 12px 16px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
width: 100%;
|
||||||
|
text-align: left;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-icon {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
margin-right: 16px;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-info {
|
||||||
|
flex-grow: 1;
|
||||||
|
|
||||||
|
.badge-title {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 16px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-description {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -1,29 +1,114 @@
|
|||||||
import { shallowMount } from '@vue/test-utils'
|
import { render, screen, fireEvent } from '@testing-library/vue'
|
||||||
import Badges from './Badges.vue'
|
import Badges from './Badges.vue'
|
||||||
|
|
||||||
|
const localVue = global.localVue
|
||||||
|
|
||||||
describe('Badges.vue', () => {
|
describe('Badges.vue', () => {
|
||||||
let propsData
|
const Wrapper = (propsData) => {
|
||||||
|
return render(Badges, {
|
||||||
|
propsData,
|
||||||
|
localVue,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
beforeEach(() => {
|
describe('without badges', () => {
|
||||||
propsData = {}
|
it('renders in presentation mode', () => {
|
||||||
})
|
const wrapper = Wrapper({ badges: [], selectionMode: false })
|
||||||
|
expect(wrapper.container).toMatchSnapshot()
|
||||||
describe('shallowMount', () => {
|
|
||||||
const Wrapper = () => {
|
|
||||||
return shallowMount(Badges, { propsData })
|
|
||||||
}
|
|
||||||
|
|
||||||
it('has class "hc-badges"', () => {
|
|
||||||
expect(Wrapper().find('.hc-badges').exists()).toBe(true)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('given a badge', () => {
|
it('renders in selection mode', () => {
|
||||||
|
const wrapper = Wrapper({ badges: [], selectionMode: true })
|
||||||
|
expect(wrapper.container).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('with badges', () => {
|
||||||
|
const badges = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
icon: '/path/to/some/icon',
|
||||||
|
isDefault: false,
|
||||||
|
description: 'Some description',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
icon: '/path/to/another/icon',
|
||||||
|
isDefault: true,
|
||||||
|
description: 'Another description',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
icon: '/path/to/third/icon',
|
||||||
|
isDefault: false,
|
||||||
|
description: 'Third description',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
describe('in presentation mode', () => {
|
||||||
|
let wrapper
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
propsData.badges = [{ id: '1', icon: '/path/to/some/icon' }]
|
wrapper = Wrapper({ badges, scale: 1.2, selectionMode: false })
|
||||||
})
|
})
|
||||||
|
|
||||||
it('proxies badge icon, which is just a URL without metadata', () => {
|
it('renders', () => {
|
||||||
expect(Wrapper().find('img[src="/api/path/to/some/icon"]').exists()).toBe(true)
|
expect(wrapper.container).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('clicking on second badge does nothing', async () => {
|
||||||
|
const badge = screen.getByTitle(badges[1].description)
|
||||||
|
await fireEvent.click(badge)
|
||||||
|
expect(wrapper.emitted()).toEqual({})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('in selection mode', () => {
|
||||||
|
let wrapper
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
wrapper = Wrapper({ badges, scale: 1.2, selectionMode: true })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders', () => {
|
||||||
|
expect(wrapper.container).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('clicking on first badge does nothing', async () => {
|
||||||
|
const badge = screen.getByTitle(badges[0].description)
|
||||||
|
await fireEvent.click(badge)
|
||||||
|
expect(wrapper.emitted()).toEqual({})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('clicking on second badge', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
const badge = screen.getByTitle(badges[1].description)
|
||||||
|
await fireEvent.click(badge)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('selects badge', () => {
|
||||||
|
expect(wrapper.container).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('emits badge-selected with index', async () => {
|
||||||
|
expect(wrapper.emitted()['badge-selected']).toEqual([[1]])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('clicking twice on second badge', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
const badge = screen.getByTitle(badges[1].description)
|
||||||
|
await fireEvent.click(badge)
|
||||||
|
await fireEvent.click(badge)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('deselects badge', () => {
|
||||||
|
expect(wrapper.container).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('emits badge-selected with null', async () => {
|
||||||
|
expect(wrapper.emitted()['badge-selected']).toEqual([[1], [null]])
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,69 +1,171 @@
|
|||||||
<template>
|
<template>
|
||||||
<div :class="[badges.length === 2 && 'hc-badges-dual']" class="hc-badges">
|
<div :class="[badges.length === 2 && 'hc-badges-dual']" class="hc-badges">
|
||||||
<div v-for="badge in badges" :key="badge.id" class="hc-badge-container">
|
<component
|
||||||
<img :title="badge.key" :src="badge.icon | proxyApiUrl" class="hc-badge" />
|
:is="selectionMode ? 'button' : 'div'"
|
||||||
</div>
|
class="hc-badge-container"
|
||||||
|
v-for="(badge, index) in badges"
|
||||||
|
:key="index"
|
||||||
|
:class="{ selectable: selectionMode && index > 0, selected: selectedIndex === index }"
|
||||||
|
@click="handleBadgeClick(index)"
|
||||||
|
>
|
||||||
|
<img :title="badge.description" :src="badge.icon | proxyApiUrl" class="hc-badge" />
|
||||||
|
</component>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
|
name: 'Badges',
|
||||||
props: {
|
props: {
|
||||||
badges: {
|
badges: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => [],
|
default: () => [],
|
||||||
},
|
},
|
||||||
|
selectionMode: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
selectedIndex: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleBadgeClick(index) {
|
||||||
|
if (!this.selectionMode || index === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (this.selectedIndex === index) {
|
||||||
|
this.selectedIndex = null
|
||||||
|
this.$emit('badge-selected', null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.selectedIndex = index
|
||||||
|
this.$emit('badge-selected', index)
|
||||||
|
},
|
||||||
|
resetSelection() {
|
||||||
|
this.selectedIndex = null
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@use 'sass:math';
|
|
||||||
.hc-badges {
|
.hc-badges {
|
||||||
text-align: center;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
transform: scale(var(--badges-scale, 1));
|
||||||
|
|
||||||
|
$badge-size-x: 30px;
|
||||||
|
$badge-size-y: 26px;
|
||||||
|
$main-badge-size-x: 60px;
|
||||||
|
$main-badge-size-y: 52px;
|
||||||
|
$gap-x: -6px;
|
||||||
|
$gap-y: 1px;
|
||||||
|
$slot-x: $badge-size-x + $gap-x;
|
||||||
|
$slot-y: $badge-size-y + $gap-y;
|
||||||
|
$offset-y: calc($badge-size-y / 2) - 2 * $gap-x;
|
||||||
|
|
||||||
|
width: $main-badge-size-x + 4 * $badge-size-x + 4 * $gap-x;
|
||||||
|
height: $offset-y + 3 * $badge-size-y + 4 * $gap-y;
|
||||||
|
margin: auto;
|
||||||
|
|
||||||
.hc-badge-container {
|
.hc-badge-container {
|
||||||
display: inline-block;
|
position: absolute;
|
||||||
position: unset;
|
width: $badge-size-x;
|
||||||
overflow: hidden;
|
height: $badge-size-y;
|
||||||
vertical-align: middle;
|
|
||||||
|
clip-path: polygon(25% 0%, 75% 0%, 100% 50%, 75% 100%, 25% 100%, 0% 50%);
|
||||||
|
|
||||||
|
&.selectable {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
transition: transform 0.1s ease-in;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
filter: drop-shadow(0 0 0 $color-primary);
|
||||||
|
img {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.hc-badge {
|
.hc-badge {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
$size: 30px;
|
.hc-badge-container:nth-child(1) {
|
||||||
$offset: $size * -0.2;
|
width: $main-badge-size-x;
|
||||||
|
height: $main-badge-size-y;
|
||||||
&.hc-badges-dual {
|
top: $offset-y + calc($gap-y / 2) - 1px;
|
||||||
padding-top: math.div($size, 2) - 2;
|
left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hc-badge-container {
|
.hc-badge-container:nth-child(1)::before {
|
||||||
width: $size;
|
content: '';
|
||||||
height: 26px;
|
position: absolute;
|
||||||
margin-left: -1px;
|
top: -20px;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 20px;
|
||||||
|
background-image: url('/img/badges/stars.svg');
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: contain;
|
||||||
|
}
|
||||||
|
|
||||||
&:nth-child(3n - 1) {
|
.hc-badge-container:nth-child(2) {
|
||||||
margin-top: -$size - $offset - 3;
|
top: $offset-y + calc(-1 * $gap-y / 2) - 1px;
|
||||||
margin-left: -$size * 0.33 - $offset - 2;
|
left: $main-badge-size-x + $gap-x;
|
||||||
}
|
}
|
||||||
&:nth-child(3n + 0) {
|
|
||||||
margin-top: $size + $offset + 3;
|
.hc-badge-container:nth-child(3) {
|
||||||
margin-left: -$size;
|
top: $offset-y + $slot-y + calc($gap-y / 2) - 1px;
|
||||||
}
|
left: $main-badge-size-x + $gap-x;
|
||||||
&:nth-child(3n + 1) {
|
}
|
||||||
margin-left: -6px;
|
|
||||||
}
|
.hc-badge-container:nth-child(4) {
|
||||||
&:first-child {
|
top: $offset-y + calc(-1 * $badge-size-y / 2) - (2 * $gap-y) - 0.5px;
|
||||||
margin-left: math.div(-$size, 3);
|
left: $main-badge-size-x + $gap-x + $slot-x;
|
||||||
}
|
}
|
||||||
&:last-child {
|
|
||||||
margin-right: math.div(-$size, 3);
|
.hc-badge-container:nth-child(5) {
|
||||||
}
|
top: $offset-y + calc($badge-size-y / 2) - 0.5px;
|
||||||
|
left: $main-badge-size-x + $gap-x + $slot-x;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hc-badge-container:nth-child(6) {
|
||||||
|
top: $offset-y + (1.5 * $badge-size-y) + (2 * $gap-y) - 0.5px;
|
||||||
|
left: $main-badge-size-x + $gap-x + $slot-x;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hc-badge-container:nth-child(7) {
|
||||||
|
top: $offset-y + calc(-1 * $gap-y / 2) - 1px;
|
||||||
|
left: $main-badge-size-x + $gap-x + (2 * $slot-x);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hc-badge-container:nth-child(8) {
|
||||||
|
top: $offset-y + $slot-y + calc($gap-y / 2) - 1px;
|
||||||
|
left: $main-badge-size-x + $gap-x + (2 * $slot-x);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hc-badge-container:nth-child(9) {
|
||||||
|
top: $offset-y + $slot-y - calc($badge-size-y / 2) - $gap-y - 0.5px;
|
||||||
|
left: $main-badge-size-x + $gap-x + (3 * $slot-x);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hc-badge-container:nth-child(10) {
|
||||||
|
top: $offset-y + ($badge-size-y * 1.5) + (2 * $gap-y) - 0.5px;
|
||||||
|
left: $main-badge-size-x + $gap-x + (3 * $slot-x);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
84
webapp/components/__snapshots__/BadgeSelection.spec.js.snap
Normal file
84
webapp/components/__snapshots__/BadgeSelection.spec.js.snap
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`Badges.vue with badges renders 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="badge-selection"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="badge-selection-item"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="badge-icon"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
alt="1"
|
||||||
|
src="/api/path/to/some/icon"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="badge-info"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="badge-description"
|
||||||
|
>
|
||||||
|
Some description
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="badge-selection-item"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="badge-icon"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
alt="2"
|
||||||
|
src="/api/path/to/another/icon"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="badge-info"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="badge-description"
|
||||||
|
>
|
||||||
|
Another description
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="badge-selection-item"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="badge-icon"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
alt="3"
|
||||||
|
src="/api/path/to/third/icon"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="badge-info"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="badge-description"
|
||||||
|
>
|
||||||
|
Third description
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Badges.vue without badges renders 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="badge-selection"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
165
webapp/components/__snapshots__/Badges.spec.js.snap
Normal file
165
webapp/components/__snapshots__/Badges.spec.js.snap
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`Badges.vue with badges in presentation mode renders 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="hc-badges"
|
||||||
|
scale="1.2"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="hc-badge-container"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
class="hc-badge"
|
||||||
|
src="/api/path/to/some/icon"
|
||||||
|
title="Some description"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="hc-badge-container"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
class="hc-badge"
|
||||||
|
src="/api/path/to/another/icon"
|
||||||
|
title="Another description"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="hc-badge-container"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
class="hc-badge"
|
||||||
|
src="/api/path/to/third/icon"
|
||||||
|
title="Third description"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Badges.vue with badges in selection mode clicking on second badge selects badge 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="hc-badges"
|
||||||
|
scale="1.2"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="hc-badge-container"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
class="hc-badge"
|
||||||
|
src="/api/path/to/some/icon"
|
||||||
|
title="Some description"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="hc-badge-container selectable selected"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
class="hc-badge"
|
||||||
|
src="/api/path/to/another/icon"
|
||||||
|
title="Another description"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="hc-badge-container selectable"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
class="hc-badge"
|
||||||
|
src="/api/path/to/third/icon"
|
||||||
|
title="Third description"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Badges.vue with badges in selection mode clicking twice on second badge deselects badge 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="hc-badges"
|
||||||
|
scale="1.2"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="hc-badge-container"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
class="hc-badge"
|
||||||
|
src="/api/path/to/some/icon"
|
||||||
|
title="Some description"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="hc-badge-container selectable"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
class="hc-badge"
|
||||||
|
src="/api/path/to/another/icon"
|
||||||
|
title="Another description"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="hc-badge-container selectable"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
class="hc-badge"
|
||||||
|
src="/api/path/to/third/icon"
|
||||||
|
title="Third description"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Badges.vue with badges in selection mode renders 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="hc-badges"
|
||||||
|
scale="1.2"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="hc-badge-container"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
class="hc-badge"
|
||||||
|
src="/api/path/to/some/icon"
|
||||||
|
title="Some description"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="hc-badge-container selectable"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
class="hc-badge"
|
||||||
|
src="/api/path/to/another/icon"
|
||||||
|
title="Another description"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="hc-badge-container selectable"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
class="hc-badge"
|
||||||
|
src="/api/path/to/third/icon"
|
||||||
|
title="Third description"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Badges.vue without badges renders in presentation mode 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="hc-badges"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Badges.vue without badges renders in selection mode 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="hc-badges"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
@ -35,6 +35,7 @@ const options = {
|
|||||||
COOKIE_EXPIRE_TIME: process.env.COOKIE_EXPIRE_TIME || 730, // Two years by default
|
COOKIE_EXPIRE_TIME: process.env.COOKIE_EXPIRE_TIME || 730, // Two years by default
|
||||||
COOKIE_HTTPS_ONLY: process.env.COOKIE_HTTPS_ONLY || process.env.NODE_ENV === 'production', // ensure true in production if not set explicitly
|
COOKIE_HTTPS_ONLY: process.env.COOKIE_HTTPS_ONLY || process.env.NODE_ENV === 'production', // ensure true in production if not set explicitly
|
||||||
CATEGORIES_ACTIVE: process.env.CATEGORIES_ACTIVE === 'true' || false,
|
CATEGORIES_ACTIVE: process.env.CATEGORIES_ACTIVE === 'true' || false,
|
||||||
|
BADGES_ENABLED: process.env.BADGES_ENABLED === 'true' || false,
|
||||||
}
|
}
|
||||||
|
|
||||||
const CONFIG = {
|
const CONFIG = {
|
||||||
|
|||||||
@ -26,9 +26,15 @@ export const locationFragment = (lang) => gql`
|
|||||||
|
|
||||||
export const badgesFragment = gql`
|
export const badgesFragment = gql`
|
||||||
fragment badges on User {
|
fragment badges on User {
|
||||||
badgeTrophies {
|
badgeTrophiesSelected {
|
||||||
id
|
id
|
||||||
icon
|
icon
|
||||||
|
description
|
||||||
|
}
|
||||||
|
badgeVerification {
|
||||||
|
id
|
||||||
|
icon
|
||||||
|
description
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|||||||
@ -405,6 +405,22 @@ export const currentUserQuery = gql`
|
|||||||
query {
|
query {
|
||||||
currentUser {
|
currentUser {
|
||||||
...user
|
...user
|
||||||
|
badgeTrophiesSelected {
|
||||||
|
id
|
||||||
|
icon
|
||||||
|
description
|
||||||
|
isDefault
|
||||||
|
}
|
||||||
|
badgeTrophiesUnused {
|
||||||
|
id
|
||||||
|
icon
|
||||||
|
description
|
||||||
|
}
|
||||||
|
badgeVerification {
|
||||||
|
id
|
||||||
|
icon
|
||||||
|
description
|
||||||
|
}
|
||||||
email
|
email
|
||||||
role
|
role
|
||||||
about
|
about
|
||||||
@ -466,3 +482,43 @@ export const userDataQuery = (i18n) => {
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const setTrophyBadgeSelected = gql`
|
||||||
|
mutation ($slot: Int!, $badgeId: ID) {
|
||||||
|
setTrophyBadgeSelected(slot: $slot, badgeId: $badgeId) {
|
||||||
|
badgeTrophiesCount
|
||||||
|
badgeTrophiesSelected {
|
||||||
|
id
|
||||||
|
icon
|
||||||
|
description
|
||||||
|
isDefault
|
||||||
|
}
|
||||||
|
badgeTrophiesUnused {
|
||||||
|
id
|
||||||
|
icon
|
||||||
|
description
|
||||||
|
}
|
||||||
|
badgeTrophiesUnusedCount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export const resetTrophyBadgesSelected = gql`
|
||||||
|
mutation {
|
||||||
|
resetTrophyBadgesSelected {
|
||||||
|
badgeTrophiesCount
|
||||||
|
badgeTrophiesSelected {
|
||||||
|
id
|
||||||
|
icon
|
||||||
|
description
|
||||||
|
isDefault
|
||||||
|
}
|
||||||
|
badgeTrophiesUnused {
|
||||||
|
id
|
||||||
|
icon
|
||||||
|
description
|
||||||
|
}
|
||||||
|
badgeTrophiesUnusedCount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|||||||
@ -957,6 +957,16 @@
|
|||||||
"title": "Suchergebnisse"
|
"title": "Suchergebnisse"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
|
"badges": {
|
||||||
|
"click-to-select": "Klicke auf einen freien Platz, um eine Badge hinzufügen.",
|
||||||
|
"click-to-use": "Klicke auf eine Badge, um sie zu platzieren.",
|
||||||
|
"description": "Hier hast du die Möglichkeit zu entscheiden, wie deine bereits erworbenen Badges in deinem Profil gezeigt werden sollen.",
|
||||||
|
"name": "Badges",
|
||||||
|
"no-badges-available": "Im Moment stehen dir keine Badges zur Verfügung, die du hinzufügen könntest.",
|
||||||
|
"remove": "Badge entfernen",
|
||||||
|
"success-update": "Deine Badges wurden erfolgreich gespeichert.",
|
||||||
|
"verification": "Dies ist deine Verifikations-Badge und kann nicht geändert werden."
|
||||||
|
},
|
||||||
"blocked-users": {
|
"blocked-users": {
|
||||||
"block": "Nutzer blockieren",
|
"block": "Nutzer blockieren",
|
||||||
"columns": {
|
"columns": {
|
||||||
|
|||||||
@ -957,6 +957,16 @@
|
|||||||
"title": "Search Results"
|
"title": "Search Results"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
|
"badges": {
|
||||||
|
"click-to-select": "Click on an empty space to add a badge.",
|
||||||
|
"click-to-use": "Click on a badge to use it in the selected slot.",
|
||||||
|
"description": "Here you can choose how to display your earned badges on your profile.",
|
||||||
|
"name": "Badges",
|
||||||
|
"no-badges-available": "You currently don't have any badges available to add.",
|
||||||
|
"remove": "Remove Badge",
|
||||||
|
"success-update": "Your badges have been updated successfully.",
|
||||||
|
"verification": "This is your verification badge and cannot be changed."
|
||||||
|
},
|
||||||
"blocked-users": {
|
"blocked-users": {
|
||||||
"block": "Block user",
|
"block": "Block user",
|
||||||
"columns": {
|
"columns": {
|
||||||
|
|||||||
@ -957,6 +957,16 @@
|
|||||||
"title": null
|
"title": null
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
|
"badges": {
|
||||||
|
"click-to-select": null,
|
||||||
|
"click-to-use": null,
|
||||||
|
"description": null,
|
||||||
|
"name": null,
|
||||||
|
"no-badges-available": null,
|
||||||
|
"remove": null,
|
||||||
|
"success-update": null,
|
||||||
|
"verification": null
|
||||||
|
},
|
||||||
"blocked-users": {
|
"blocked-users": {
|
||||||
"block": "Bloquear usuario",
|
"block": "Bloquear usuario",
|
||||||
"columns": {
|
"columns": {
|
||||||
|
|||||||
@ -957,6 +957,16 @@
|
|||||||
"title": null
|
"title": null
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
|
"badges": {
|
||||||
|
"click-to-select": null,
|
||||||
|
"click-to-use": null,
|
||||||
|
"description": null,
|
||||||
|
"name": null,
|
||||||
|
"no-badges-available": null,
|
||||||
|
"remove": null,
|
||||||
|
"success-update": null,
|
||||||
|
"verification": null
|
||||||
|
},
|
||||||
"blocked-users": {
|
"blocked-users": {
|
||||||
"block": "Bloquer l'utilisateur",
|
"block": "Bloquer l'utilisateur",
|
||||||
"columns": {
|
"columns": {
|
||||||
|
|||||||
@ -957,6 +957,16 @@
|
|||||||
"title": null
|
"title": null
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
|
"badges": {
|
||||||
|
"click-to-select": null,
|
||||||
|
"click-to-use": null,
|
||||||
|
"description": null,
|
||||||
|
"name": null,
|
||||||
|
"no-badges-available": null,
|
||||||
|
"remove": null,
|
||||||
|
"success-update": null,
|
||||||
|
"verification": null
|
||||||
|
},
|
||||||
"blocked-users": {
|
"blocked-users": {
|
||||||
"block": null,
|
"block": null,
|
||||||
"columns": {
|
"columns": {
|
||||||
|
|||||||
@ -957,6 +957,16 @@
|
|||||||
"title": null
|
"title": null
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
|
"badges": {
|
||||||
|
"click-to-select": null,
|
||||||
|
"click-to-use": null,
|
||||||
|
"description": null,
|
||||||
|
"name": null,
|
||||||
|
"no-badges-available": null,
|
||||||
|
"remove": null,
|
||||||
|
"success-update": null,
|
||||||
|
"verification": null
|
||||||
|
},
|
||||||
"blocked-users": {
|
"blocked-users": {
|
||||||
"block": null,
|
"block": null,
|
||||||
"columns": {
|
"columns": {
|
||||||
|
|||||||
@ -957,6 +957,16 @@
|
|||||||
"title": null
|
"title": null
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
|
"badges": {
|
||||||
|
"click-to-select": null,
|
||||||
|
"click-to-use": null,
|
||||||
|
"description": null,
|
||||||
|
"name": null,
|
||||||
|
"no-badges-available": null,
|
||||||
|
"remove": null,
|
||||||
|
"success-update": null,
|
||||||
|
"verification": null
|
||||||
|
},
|
||||||
"blocked-users": {
|
"blocked-users": {
|
||||||
"block": null,
|
"block": null,
|
||||||
"columns": {
|
"columns": {
|
||||||
|
|||||||
@ -957,6 +957,16 @@
|
|||||||
"title": null
|
"title": null
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
|
"badges": {
|
||||||
|
"click-to-select": null,
|
||||||
|
"click-to-use": null,
|
||||||
|
"description": null,
|
||||||
|
"name": null,
|
||||||
|
"no-badges-available": null,
|
||||||
|
"remove": null,
|
||||||
|
"success-update": null,
|
||||||
|
"verification": null
|
||||||
|
},
|
||||||
"blocked-users": {
|
"blocked-users": {
|
||||||
"block": "Bloquear usuário",
|
"block": "Bloquear usuário",
|
||||||
"columns": {
|
"columns": {
|
||||||
|
|||||||
@ -957,6 +957,16 @@
|
|||||||
"title": null
|
"title": null
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
|
"badges": {
|
||||||
|
"click-to-select": null,
|
||||||
|
"click-to-use": null,
|
||||||
|
"description": null,
|
||||||
|
"name": null,
|
||||||
|
"no-badges-available": null,
|
||||||
|
"remove": null,
|
||||||
|
"success-update": null,
|
||||||
|
"verification": null
|
||||||
|
},
|
||||||
"blocked-users": {
|
"blocked-users": {
|
||||||
"block": "Блокировать",
|
"block": "Блокировать",
|
||||||
"columns": {
|
"columns": {
|
||||||
|
|||||||
@ -79,6 +79,7 @@
|
|||||||
"@storybook/addon-actions": "^5.3.21",
|
"@storybook/addon-actions": "^5.3.21",
|
||||||
"@storybook/addon-notes": "^5.3.18",
|
"@storybook/addon-notes": "^5.3.18",
|
||||||
"@storybook/vue": "~7.4.0",
|
"@storybook/vue": "~7.4.0",
|
||||||
|
"@testing-library/jest-dom": "^6.6.3",
|
||||||
"@testing-library/vue": "5",
|
"@testing-library/vue": "5",
|
||||||
"@vue/cli-shared-utils": "~4.3.1",
|
"@vue/cli-shared-utils": "~4.3.1",
|
||||||
"@vue/eslint-config-prettier": "~6.0.0",
|
"@vue/eslint-config-prettier": "~6.0.0",
|
||||||
|
|||||||
427
webapp/pages/__snapshots__/settings.spec.js.snap
Normal file
427
webapp/pages/__snapshots__/settings.spec.js.snap
Normal file
@ -0,0 +1,427 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`settings.vue given badges are disabled renders 1`] = `
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="ds-space"
|
||||||
|
style="margin-top: 16px; margin-bottom: 16px;"
|
||||||
|
>
|
||||||
|
<h1
|
||||||
|
class="ds-heading ds-heading-h1"
|
||||||
|
>
|
||||||
|
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="ds-space"
|
||||||
|
style="margin-top: 32px; margin-bottom: 32px;"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="ds-flex"
|
||||||
|
style="margin-left: -8px; margin-right: -8px;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="menu-container"
|
||||||
|
>
|
||||||
|
<nav
|
||||||
|
class="ds-menu"
|
||||||
|
>
|
||||||
|
<ul
|
||||||
|
class="ds-menu-list"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
class="ds-menu-item ds-menu-item-level-0"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class="ds-menu-item-link"
|
||||||
|
exact="true"
|
||||||
|
href="/settings"
|
||||||
|
>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</a>
|
||||||
|
<!---->
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
class="ds-menu-item ds-menu-item-level-0"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class="ds-menu-item-link"
|
||||||
|
exact="true"
|
||||||
|
href="/settings/my-email-address"
|
||||||
|
>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</a>
|
||||||
|
<!---->
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
class="ds-menu-item ds-menu-item-level-0"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class="ds-menu-item-link"
|
||||||
|
exact="true"
|
||||||
|
href="/settings/security"
|
||||||
|
>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</a>
|
||||||
|
<!---->
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
class="ds-menu-item ds-menu-item-level-0"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class="ds-menu-item-link"
|
||||||
|
exact="true"
|
||||||
|
href="/settings/privacy"
|
||||||
|
>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</a>
|
||||||
|
<!---->
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
class="ds-menu-item ds-menu-item-level-0"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class="ds-menu-item-link"
|
||||||
|
exact="true"
|
||||||
|
href="/settings/my-social-media"
|
||||||
|
>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</a>
|
||||||
|
<!---->
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
class="ds-menu-item ds-menu-item-level-0"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class="ds-menu-item-link"
|
||||||
|
exact="true"
|
||||||
|
href="/settings/muted-users"
|
||||||
|
>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</a>
|
||||||
|
<!---->
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
class="ds-menu-item ds-menu-item-level-0"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class="ds-menu-item-link"
|
||||||
|
exact="true"
|
||||||
|
href="/settings/blocked-users"
|
||||||
|
>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</a>
|
||||||
|
<!---->
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
class="ds-menu-item ds-menu-item-level-0"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class="ds-menu-item-link"
|
||||||
|
exact="true"
|
||||||
|
href="/settings/embeds"
|
||||||
|
>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</a>
|
||||||
|
<!---->
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
class="ds-menu-item ds-menu-item-level-0"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class="ds-menu-item-link"
|
||||||
|
exact="true"
|
||||||
|
href="/settings/notifications"
|
||||||
|
>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</a>
|
||||||
|
<!---->
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
class="ds-menu-item ds-menu-item-level-0"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class="ds-menu-item-link"
|
||||||
|
exact="true"
|
||||||
|
href="/settings/data-download"
|
||||||
|
>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</a>
|
||||||
|
<!---->
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
class="ds-menu-item ds-menu-item-level-0"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class="ds-menu-item-link"
|
||||||
|
exact="true"
|
||||||
|
href="/settings/delete-account"
|
||||||
|
>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</a>
|
||||||
|
<!---->
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="settings-content"
|
||||||
|
id="settings-content"
|
||||||
|
>
|
||||||
|
<transition-stub
|
||||||
|
appear="true"
|
||||||
|
name="slide-up"
|
||||||
|
>
|
||||||
|
<nuxt-child-stub />
|
||||||
|
</transition-stub>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`settings.vue given badges are enabled renders 1`] = `
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="ds-space"
|
||||||
|
style="margin-top: 16px; margin-bottom: 16px;"
|
||||||
|
>
|
||||||
|
<h1
|
||||||
|
class="ds-heading ds-heading-h1"
|
||||||
|
>
|
||||||
|
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="ds-space"
|
||||||
|
style="margin-top: 32px; margin-bottom: 32px;"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="ds-flex"
|
||||||
|
style="margin-left: -8px; margin-right: -8px;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="menu-container"
|
||||||
|
>
|
||||||
|
<nav
|
||||||
|
class="ds-menu"
|
||||||
|
>
|
||||||
|
<ul
|
||||||
|
class="ds-menu-list"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
class="ds-menu-item ds-menu-item-level-0"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class="ds-menu-item-link"
|
||||||
|
exact="true"
|
||||||
|
href="/settings"
|
||||||
|
>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</a>
|
||||||
|
<!---->
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
class="ds-menu-item ds-menu-item-level-0"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class="ds-menu-item-link"
|
||||||
|
exact="true"
|
||||||
|
href="/settings/my-email-address"
|
||||||
|
>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</a>
|
||||||
|
<!---->
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
class="ds-menu-item ds-menu-item-level-0"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class="ds-menu-item-link"
|
||||||
|
exact="true"
|
||||||
|
href="/settings/badges"
|
||||||
|
>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</a>
|
||||||
|
<!---->
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
class="ds-menu-item ds-menu-item-level-0"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class="ds-menu-item-link"
|
||||||
|
exact="true"
|
||||||
|
href="/settings/security"
|
||||||
|
>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</a>
|
||||||
|
<!---->
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
class="ds-menu-item ds-menu-item-level-0"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class="ds-menu-item-link"
|
||||||
|
exact="true"
|
||||||
|
href="/settings/privacy"
|
||||||
|
>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</a>
|
||||||
|
<!---->
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
class="ds-menu-item ds-menu-item-level-0"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class="ds-menu-item-link"
|
||||||
|
exact="true"
|
||||||
|
href="/settings/my-social-media"
|
||||||
|
>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</a>
|
||||||
|
<!---->
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
class="ds-menu-item ds-menu-item-level-0"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class="ds-menu-item-link"
|
||||||
|
exact="true"
|
||||||
|
href="/settings/muted-users"
|
||||||
|
>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</a>
|
||||||
|
<!---->
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
class="ds-menu-item ds-menu-item-level-0"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class="ds-menu-item-link"
|
||||||
|
exact="true"
|
||||||
|
href="/settings/blocked-users"
|
||||||
|
>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</a>
|
||||||
|
<!---->
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
class="ds-menu-item ds-menu-item-level-0"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class="ds-menu-item-link"
|
||||||
|
exact="true"
|
||||||
|
href="/settings/embeds"
|
||||||
|
>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</a>
|
||||||
|
<!---->
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
class="ds-menu-item ds-menu-item-level-0"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class="ds-menu-item-link"
|
||||||
|
exact="true"
|
||||||
|
href="/settings/notifications"
|
||||||
|
>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</a>
|
||||||
|
<!---->
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
class="ds-menu-item ds-menu-item-level-0"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class="ds-menu-item-link"
|
||||||
|
exact="true"
|
||||||
|
href="/settings/data-download"
|
||||||
|
>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</a>
|
||||||
|
<!---->
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
class="ds-menu-item ds-menu-item-level-0"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class="ds-menu-item-link"
|
||||||
|
exact="true"
|
||||||
|
href="/settings/delete-account"
|
||||||
|
>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</a>
|
||||||
|
<!---->
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="settings-content"
|
||||||
|
id="settings-content"
|
||||||
|
>
|
||||||
|
<transition-stub
|
||||||
|
appear="true"
|
||||||
|
name="slide-up"
|
||||||
|
>
|
||||||
|
<nuxt-child-stub />
|
||||||
|
</transition-stub>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
847
webapp/pages/admin/users/__snapshots__/index.spec.js.snap
Normal file
847
webapp/pages/admin/users/__snapshots__/index.spec.js.snap
Normal file
@ -0,0 +1,847 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`Users given badges are disabled renders 1`] = `
|
||||||
|
<div
|
||||||
|
class="admin-users"
|
||||||
|
>
|
||||||
|
<article
|
||||||
|
class="base-card"
|
||||||
|
>
|
||||||
|
<h2
|
||||||
|
class="title"
|
||||||
|
>
|
||||||
|
admin.users.name
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<form
|
||||||
|
autocomplete="off"
|
||||||
|
class="ds-form"
|
||||||
|
novalidate="novalidate"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ds-flex"
|
||||||
|
style="margin-left: -8px; margin-right: -8px;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ds-flex-item"
|
||||||
|
style="flex-basis: 90%; width: 90%; padding-left: 8px; padding-right: 8px; margin-bottom: 16px;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ds-form-item ds-input-size-base"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="ds-input-label"
|
||||||
|
style="display: none;"
|
||||||
|
>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="ds-input-wrap"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ds-input-icon"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-label="icon"
|
||||||
|
class="ds-icon"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
class="ds-icon-svg"
|
||||||
|
viewBox="0 0 32 32"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M19 3c5.511 0 10 4.489 10 10s-4.489 10-10 10a9.923 9.923 0 01-6.313-2.25l-7.969 7.969-1.438-1.438 7.969-7.969a9.919 9.919 0 01-2.25-6.313c0-5.511 4.489-10 10-10zm0 2c-4.43 0-8 3.57-8 8s3.57 8 8 8 8-3.57 8-8-3.57-8-8-8z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
class="ds-input ds-input-has-icon"
|
||||||
|
name="query"
|
||||||
|
placeholder="admin.users.form.placeholder"
|
||||||
|
tabindex="0"
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
<!---->
|
||||||
|
</div>
|
||||||
|
<transition-stub
|
||||||
|
name="ds-input-error"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ds-input-error"
|
||||||
|
style="display: none;"
|
||||||
|
>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</transition-stub>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="ds-flex-item"
|
||||||
|
style="flex-basis: 30px; width: 30px; padding-left: 8px; padding-right: 8px; margin-bottom: 16px;"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="base-button --icon-only --circle --filled"
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="base-icon"
|
||||||
|
>
|
||||||
|
<!---->
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<!---->
|
||||||
|
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<!---->
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article
|
||||||
|
class="base-card"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ds-table-wrap"
|
||||||
|
>
|
||||||
|
<table
|
||||||
|
cellpadding="0"
|
||||||
|
cellspacing="0"
|
||||||
|
class="ds-table ds-table-condensed ds-table-bordered"
|
||||||
|
>
|
||||||
|
<colgroup>
|
||||||
|
<col
|
||||||
|
width=""
|
||||||
|
/>
|
||||||
|
<col
|
||||||
|
width=""
|
||||||
|
/>
|
||||||
|
<col
|
||||||
|
width=""
|
||||||
|
/>
|
||||||
|
<col
|
||||||
|
width=""
|
||||||
|
/>
|
||||||
|
<col
|
||||||
|
width=""
|
||||||
|
/>
|
||||||
|
<col
|
||||||
|
width=""
|
||||||
|
/>
|
||||||
|
<col
|
||||||
|
width=""
|
||||||
|
/>
|
||||||
|
<col
|
||||||
|
width=""
|
||||||
|
/>
|
||||||
|
<col
|
||||||
|
width=""
|
||||||
|
/>
|
||||||
|
</colgroup>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th
|
||||||
|
class="ds-table-head-col"
|
||||||
|
>
|
||||||
|
|
||||||
|
admin.users.table.columns.number
|
||||||
|
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
class="ds-table-head-col"
|
||||||
|
>
|
||||||
|
|
||||||
|
admin.users.table.columns.name
|
||||||
|
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
class="ds-table-head-col"
|
||||||
|
>
|
||||||
|
|
||||||
|
admin.users.table.columns.email
|
||||||
|
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
class="ds-table-head-col"
|
||||||
|
>
|
||||||
|
|
||||||
|
admin.users.table.columns.slug
|
||||||
|
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
class="ds-table-head-col"
|
||||||
|
>
|
||||||
|
|
||||||
|
admin.users.table.columns.createdAt
|
||||||
|
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
class="ds-table-head-col ds-table-head-col-right"
|
||||||
|
>
|
||||||
|
|
||||||
|
🖉
|
||||||
|
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
class="ds-table-head-col ds-table-head-col-right"
|
||||||
|
>
|
||||||
|
|
||||||
|
🗨
|
||||||
|
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
class="ds-table-head-col ds-table-head-col-right"
|
||||||
|
>
|
||||||
|
|
||||||
|
❤
|
||||||
|
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
class="ds-table-head-col ds-table-head-col-right"
|
||||||
|
>
|
||||||
|
|
||||||
|
admin.users.table.columns.role
|
||||||
|
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td
|
||||||
|
class="ds-table-col"
|
||||||
|
>
|
||||||
|
NaN.
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="ds-table-col"
|
||||||
|
>
|
||||||
|
<nuxt-link-stub
|
||||||
|
to="[object Object]"
|
||||||
|
>
|
||||||
|
<b>
|
||||||
|
User
|
||||||
|
</b>
|
||||||
|
</nuxt-link-stub>
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="ds-table-col"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="mailto:user@example.org"
|
||||||
|
>
|
||||||
|
<b>
|
||||||
|
user@example.org
|
||||||
|
</b>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="ds-table-col"
|
||||||
|
>
|
||||||
|
<nuxt-link-stub
|
||||||
|
to="[object Object]"
|
||||||
|
>
|
||||||
|
<b>
|
||||||
|
user
|
||||||
|
</b>
|
||||||
|
</nuxt-link-stub>
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="ds-table-col"
|
||||||
|
>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="ds-table-col ds-table-col-right"
|
||||||
|
>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="ds-table-col ds-table-col-right"
|
||||||
|
>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="ds-table-col ds-table-col-right"
|
||||||
|
>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="ds-table-col ds-table-col-right"
|
||||||
|
>
|
||||||
|
<select />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td
|
||||||
|
class="ds-table-col"
|
||||||
|
>
|
||||||
|
NaN.
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="ds-table-col"
|
||||||
|
>
|
||||||
|
<nuxt-link-stub
|
||||||
|
to="[object Object]"
|
||||||
|
>
|
||||||
|
<b>
|
||||||
|
User
|
||||||
|
</b>
|
||||||
|
</nuxt-link-stub>
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="ds-table-col"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="mailto:user2@example.org"
|
||||||
|
>
|
||||||
|
<b>
|
||||||
|
user2@example.org
|
||||||
|
</b>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="ds-table-col"
|
||||||
|
>
|
||||||
|
<nuxt-link-stub
|
||||||
|
to="[object Object]"
|
||||||
|
>
|
||||||
|
<b>
|
||||||
|
user
|
||||||
|
</b>
|
||||||
|
</nuxt-link-stub>
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="ds-table-col"
|
||||||
|
>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="ds-table-col ds-table-col-right"
|
||||||
|
>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="ds-table-col ds-table-col-right"
|
||||||
|
>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="ds-table-col ds-table-col-right"
|
||||||
|
>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="ds-table-col ds-table-col-right"
|
||||||
|
>
|
||||||
|
<select />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="pagination-buttons"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="previous-button base-button --icon-only --circle"
|
||||||
|
data-test="previous-button"
|
||||||
|
disabled="disabled"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="base-icon"
|
||||||
|
>
|
||||||
|
<!---->
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<!---->
|
||||||
|
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!---->
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="next-button base-button --icon-only --circle"
|
||||||
|
data-test="next-button"
|
||||||
|
disabled="disabled"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="base-icon"
|
||||||
|
>
|
||||||
|
<!---->
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<!---->
|
||||||
|
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!---->
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Users given badges are enabled renders 1`] = `
|
||||||
|
<div
|
||||||
|
class="admin-users"
|
||||||
|
>
|
||||||
|
<article
|
||||||
|
class="base-card"
|
||||||
|
>
|
||||||
|
<h2
|
||||||
|
class="title"
|
||||||
|
>
|
||||||
|
admin.users.name
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<form
|
||||||
|
autocomplete="off"
|
||||||
|
class="ds-form"
|
||||||
|
novalidate="novalidate"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ds-flex"
|
||||||
|
style="margin-left: -8px; margin-right: -8px;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ds-flex-item"
|
||||||
|
style="flex-basis: 90%; width: 90%; padding-left: 8px; padding-right: 8px; margin-bottom: 16px;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ds-form-item ds-input-size-base"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="ds-input-label"
|
||||||
|
style="display: none;"
|
||||||
|
>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="ds-input-wrap"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ds-input-icon"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-label="icon"
|
||||||
|
class="ds-icon"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
class="ds-icon-svg"
|
||||||
|
viewBox="0 0 32 32"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M19 3c5.511 0 10 4.489 10 10s-4.489 10-10 10a9.923 9.923 0 01-6.313-2.25l-7.969 7.969-1.438-1.438 7.969-7.969a9.919 9.919 0 01-2.25-6.313c0-5.511 4.489-10 10-10zm0 2c-4.43 0-8 3.57-8 8s3.57 8 8 8 8-3.57 8-8-3.57-8-8-8z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
class="ds-input ds-input-has-icon"
|
||||||
|
name="query"
|
||||||
|
placeholder="admin.users.form.placeholder"
|
||||||
|
tabindex="0"
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
<!---->
|
||||||
|
</div>
|
||||||
|
<transition-stub
|
||||||
|
name="ds-input-error"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ds-input-error"
|
||||||
|
style="display: none;"
|
||||||
|
>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</transition-stub>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="ds-flex-item"
|
||||||
|
style="flex-basis: 30px; width: 30px; padding-left: 8px; padding-right: 8px; margin-bottom: 16px;"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="base-button --icon-only --circle --filled"
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="base-icon"
|
||||||
|
>
|
||||||
|
<!---->
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<!---->
|
||||||
|
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<!---->
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article
|
||||||
|
class="base-card"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ds-table-wrap"
|
||||||
|
>
|
||||||
|
<table
|
||||||
|
cellpadding="0"
|
||||||
|
cellspacing="0"
|
||||||
|
class="ds-table ds-table-condensed ds-table-bordered"
|
||||||
|
>
|
||||||
|
<colgroup>
|
||||||
|
<col
|
||||||
|
width=""
|
||||||
|
/>
|
||||||
|
<col
|
||||||
|
width=""
|
||||||
|
/>
|
||||||
|
<col
|
||||||
|
width=""
|
||||||
|
/>
|
||||||
|
<col
|
||||||
|
width=""
|
||||||
|
/>
|
||||||
|
<col
|
||||||
|
width=""
|
||||||
|
/>
|
||||||
|
<col
|
||||||
|
width=""
|
||||||
|
/>
|
||||||
|
<col
|
||||||
|
width=""
|
||||||
|
/>
|
||||||
|
<col
|
||||||
|
width=""
|
||||||
|
/>
|
||||||
|
<col
|
||||||
|
width=""
|
||||||
|
/>
|
||||||
|
<col
|
||||||
|
width=""
|
||||||
|
/>
|
||||||
|
</colgroup>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th
|
||||||
|
class="ds-table-head-col"
|
||||||
|
>
|
||||||
|
|
||||||
|
admin.users.table.columns.number
|
||||||
|
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
class="ds-table-head-col"
|
||||||
|
>
|
||||||
|
|
||||||
|
admin.users.table.columns.name
|
||||||
|
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
class="ds-table-head-col"
|
||||||
|
>
|
||||||
|
|
||||||
|
admin.users.table.columns.email
|
||||||
|
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
class="ds-table-head-col"
|
||||||
|
>
|
||||||
|
|
||||||
|
admin.users.table.columns.slug
|
||||||
|
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
class="ds-table-head-col"
|
||||||
|
>
|
||||||
|
|
||||||
|
admin.users.table.columns.createdAt
|
||||||
|
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
class="ds-table-head-col ds-table-head-col-right"
|
||||||
|
>
|
||||||
|
|
||||||
|
🖉
|
||||||
|
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
class="ds-table-head-col ds-table-head-col-right"
|
||||||
|
>
|
||||||
|
|
||||||
|
🗨
|
||||||
|
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
class="ds-table-head-col ds-table-head-col-right"
|
||||||
|
>
|
||||||
|
|
||||||
|
❤
|
||||||
|
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
class="ds-table-head-col ds-table-head-col-right"
|
||||||
|
>
|
||||||
|
|
||||||
|
admin.users.table.columns.role
|
||||||
|
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
class="ds-table-head-col ds-table-head-col-right"
|
||||||
|
>
|
||||||
|
|
||||||
|
admin.users.table.columns.badges
|
||||||
|
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td
|
||||||
|
class="ds-table-col"
|
||||||
|
>
|
||||||
|
NaN.
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="ds-table-col"
|
||||||
|
>
|
||||||
|
<nuxt-link-stub
|
||||||
|
to="[object Object]"
|
||||||
|
>
|
||||||
|
<b>
|
||||||
|
User
|
||||||
|
</b>
|
||||||
|
</nuxt-link-stub>
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="ds-table-col"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="mailto:user@example.org"
|
||||||
|
>
|
||||||
|
<b>
|
||||||
|
user@example.org
|
||||||
|
</b>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="ds-table-col"
|
||||||
|
>
|
||||||
|
<nuxt-link-stub
|
||||||
|
to="[object Object]"
|
||||||
|
>
|
||||||
|
<b>
|
||||||
|
user
|
||||||
|
</b>
|
||||||
|
</nuxt-link-stub>
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="ds-table-col"
|
||||||
|
>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="ds-table-col ds-table-col-right"
|
||||||
|
>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="ds-table-col ds-table-col-right"
|
||||||
|
>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="ds-table-col ds-table-col-right"
|
||||||
|
>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="ds-table-col ds-table-col-right"
|
||||||
|
>
|
||||||
|
<select />
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="ds-table-col ds-table-col-right"
|
||||||
|
>
|
||||||
|
<nuxt-link-stub
|
||||||
|
to="[object Object]"
|
||||||
|
>
|
||||||
|
|
||||||
|
admin.users.table.edit
|
||||||
|
|
||||||
|
</nuxt-link-stub>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td
|
||||||
|
class="ds-table-col"
|
||||||
|
>
|
||||||
|
NaN.
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="ds-table-col"
|
||||||
|
>
|
||||||
|
<nuxt-link-stub
|
||||||
|
to="[object Object]"
|
||||||
|
>
|
||||||
|
<b>
|
||||||
|
User
|
||||||
|
</b>
|
||||||
|
</nuxt-link-stub>
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="ds-table-col"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="mailto:user2@example.org"
|
||||||
|
>
|
||||||
|
<b>
|
||||||
|
user2@example.org
|
||||||
|
</b>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="ds-table-col"
|
||||||
|
>
|
||||||
|
<nuxt-link-stub
|
||||||
|
to="[object Object]"
|
||||||
|
>
|
||||||
|
<b>
|
||||||
|
user
|
||||||
|
</b>
|
||||||
|
</nuxt-link-stub>
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="ds-table-col"
|
||||||
|
>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="ds-table-col ds-table-col-right"
|
||||||
|
>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="ds-table-col ds-table-col-right"
|
||||||
|
>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="ds-table-col ds-table-col-right"
|
||||||
|
>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="ds-table-col ds-table-col-right"
|
||||||
|
>
|
||||||
|
<select />
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="ds-table-col ds-table-col-right"
|
||||||
|
>
|
||||||
|
<nuxt-link-stub
|
||||||
|
to="[object Object]"
|
||||||
|
>
|
||||||
|
|
||||||
|
admin.users.table.edit
|
||||||
|
|
||||||
|
</nuxt-link-stub>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="pagination-buttons"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="previous-button base-button --icon-only --circle"
|
||||||
|
data-test="previous-button"
|
||||||
|
disabled="disabled"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="base-icon"
|
||||||
|
>
|
||||||
|
<!---->
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<!---->
|
||||||
|
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!---->
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="next-button base-button --icon-only --circle"
|
||||||
|
data-test="next-button"
|
||||||
|
disabled="disabled"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="base-icon"
|
||||||
|
>
|
||||||
|
<!---->
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<!---->
|
||||||
|
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!---->
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
@ -10,11 +10,9 @@ const stubs = {
|
|||||||
|
|
||||||
describe('Users', () => {
|
describe('Users', () => {
|
||||||
let wrapper
|
let wrapper
|
||||||
let Wrapper
|
|
||||||
let getters
|
|
||||||
|
|
||||||
const mocks = {
|
const mocks = {
|
||||||
$t: jest.fn(),
|
$t: jest.fn((t) => t),
|
||||||
$apollo: {
|
$apollo: {
|
||||||
loading: false,
|
loading: false,
|
||||||
mutate: jest
|
mutate: jest
|
||||||
@ -38,116 +36,154 @@ describe('Users', () => {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('mount', () => {
|
const getters = {
|
||||||
getters = {
|
'auth/isAdmin': () => true,
|
||||||
'auth/isAdmin': () => true,
|
'auth/user': () => {
|
||||||
'auth/user': () => {
|
return { id: 'admin' }
|
||||||
return { id: 'admin' }
|
},
|
||||||
},
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Wrapper = () => {
|
const Wrapper = () => {
|
||||||
const store = new Vuex.Store({ getters })
|
const store = new Vuex.Store({ getters })
|
||||||
return mount(Users, {
|
return mount(Users, {
|
||||||
mocks,
|
mocks,
|
||||||
localVue,
|
localVue,
|
||||||
store,
|
store,
|
||||||
stubs,
|
stubs,
|
||||||
})
|
data: () => ({
|
||||||
}
|
User: [
|
||||||
|
{
|
||||||
|
id: 'user',
|
||||||
|
email: 'user@example.org',
|
||||||
|
name: 'User',
|
||||||
|
role: 'moderator',
|
||||||
|
slug: 'user',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'user2',
|
||||||
|
email: 'user2@example.org',
|
||||||
|
name: 'User',
|
||||||
|
role: 'moderator',
|
||||||
|
slug: 'user',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('given badges are enabled', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mocks.$env = {
|
||||||
|
BADGES_ENABLED: true,
|
||||||
|
}
|
||||||
|
wrapper = Wrapper()
|
||||||
|
})
|
||||||
|
|
||||||
it('renders', () => {
|
it('renders', () => {
|
||||||
|
expect(wrapper.element).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('given badges are disabled', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mocks.$env = {
|
||||||
|
BADGES_ENABLED: false,
|
||||||
|
}
|
||||||
wrapper = Wrapper()
|
wrapper = Wrapper()
|
||||||
expect(wrapper.element.tagName).toBe('DIV')
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('search', () => {
|
it('renders', () => {
|
||||||
let searchAction
|
expect(wrapper.element).toMatchSnapshot()
|
||||||
beforeEach(() => {
|
})
|
||||||
searchAction = (wrapper, { query }) => {
|
})
|
||||||
wrapper.find('input').setValue(query)
|
|
||||||
wrapper.find('form').trigger('submit')
|
describe('search', () => {
|
||||||
return wrapper
|
let searchAction
|
||||||
}
|
beforeEach(() => {
|
||||||
|
wrapper = Wrapper()
|
||||||
|
searchAction = (wrapper, { query }) => {
|
||||||
|
wrapper.find('input').setValue(query)
|
||||||
|
wrapper.find('form').trigger('submit')
|
||||||
|
return wrapper
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('query looks like an email address', () => {
|
||||||
|
it('searches users for exact email address', async () => {
|
||||||
|
const wrapper = await searchAction(Wrapper(), { query: 'email@example.org' })
|
||||||
|
expect(wrapper.vm.email).toEqual('email@example.org')
|
||||||
|
expect(wrapper.vm.filter).toBe(null)
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('query looks like an email address', () => {
|
it('email address is case-insensitive', async () => {
|
||||||
it('searches users for exact email address', async () => {
|
const wrapper = await searchAction(Wrapper(), { query: 'eMaiL@example.org' })
|
||||||
const wrapper = await searchAction(Wrapper(), { query: 'email@example.org' })
|
expect(wrapper.vm.email).toEqual('email@example.org')
|
||||||
expect(wrapper.vm.email).toEqual('email@example.org')
|
expect(wrapper.vm.filter).toBe(null)
|
||||||
expect(wrapper.vm.filter).toBe(null)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('email address is case-insensitive', async () => {
|
|
||||||
const wrapper = await searchAction(Wrapper(), { query: 'eMaiL@example.org' })
|
|
||||||
expect(wrapper.vm.email).toEqual('email@example.org')
|
|
||||||
expect(wrapper.vm.filter).toBe(null)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('query is just text', () => {
|
|
||||||
it('tries to find matching users by `name`, `slug` or `about`', async () => {
|
|
||||||
const wrapper = await searchAction(await Wrapper(), { query: 'Find me' })
|
|
||||||
const expected = {
|
|
||||||
OR: [
|
|
||||||
{ name_contains: 'Find me' },
|
|
||||||
{ slug_contains: 'Find me' },
|
|
||||||
{ about_contains: 'Find me' },
|
|
||||||
],
|
|
||||||
}
|
|
||||||
expect(wrapper.vm.email).toBe(null)
|
|
||||||
expect(wrapper.vm.filter).toEqual(expected)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('change roles', () => {
|
describe('query is just text', () => {
|
||||||
beforeAll(() => {
|
it('tries to find matching users by `name`, `slug` or `about`', async () => {
|
||||||
wrapper = Wrapper()
|
const wrapper = await searchAction(await Wrapper(), { query: 'Find me' })
|
||||||
wrapper.setData({
|
const expected = {
|
||||||
User: [
|
OR: [
|
||||||
{
|
{ name_contains: 'Find me' },
|
||||||
id: 'admin',
|
{ slug_contains: 'Find me' },
|
||||||
email: 'admin@example.org',
|
{ about_contains: 'Find me' },
|
||||||
name: 'Admin',
|
|
||||||
role: 'admin',
|
|
||||||
slug: 'admin',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'user',
|
|
||||||
email: 'user@example.org',
|
|
||||||
name: 'User',
|
|
||||||
role: 'user',
|
|
||||||
slug: 'user',
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
userRoles: ['user', 'moderator', 'admin'],
|
}
|
||||||
})
|
expect(wrapper.vm.email).toBe(null)
|
||||||
})
|
expect(wrapper.vm.filter).toEqual(expected)
|
||||||
|
|
||||||
it('cannot change own role', () => {
|
|
||||||
const adminRow = wrapper.findAll('tr').at(1)
|
|
||||||
expect(adminRow.find('select').exists()).toBe(false)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('changes the role of another user', () => {
|
|
||||||
const userRow = wrapper.findAll('tr').at(2)
|
|
||||||
userRow.findAll('option').at(1).setSelected()
|
|
||||||
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(
|
|
||||||
expect.objectContaining({
|
|
||||||
variables: {
|
|
||||||
id: 'user',
|
|
||||||
role: 'moderator',
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('toasts a success message after role has changed', () => {
|
|
||||||
const userRow = wrapper.findAll('tr').at(2)
|
|
||||||
userRow.findAll('option').at(1).setSelected()
|
|
||||||
expect(mocks.$toast.success).toHaveBeenCalled()
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('change roles', () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
wrapper = Wrapper()
|
||||||
|
wrapper.setData({
|
||||||
|
User: [
|
||||||
|
{
|
||||||
|
id: 'admin',
|
||||||
|
email: 'admin@example.org',
|
||||||
|
name: 'Admin',
|
||||||
|
role: 'admin',
|
||||||
|
slug: 'admin',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'user',
|
||||||
|
email: 'user@example.org',
|
||||||
|
name: 'User',
|
||||||
|
role: 'user',
|
||||||
|
slug: 'user',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
userRoles: ['user', 'moderator', 'admin'],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('cannot change own role', () => {
|
||||||
|
const adminRow = wrapper.findAll('tr').at(1)
|
||||||
|
expect(adminRow.find('select').exists()).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('changes the role of another user', () => {
|
||||||
|
const userRow = wrapper.findAll('tr').at(2)
|
||||||
|
userRow.findAll('option').at(1).setSelected()
|
||||||
|
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
variables: {
|
||||||
|
id: 'user',
|
||||||
|
role: 'moderator',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('toasts a success message after role has changed', () => {
|
||||||
|
const userRow = wrapper.findAll('tr').at(2)
|
||||||
|
userRow.findAll('option').at(1).setSelected()
|
||||||
|
expect(mocks.$toast.success).toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -120,7 +120,7 @@ export default {
|
|||||||
currentUser: 'auth/user',
|
currentUser: 'auth/user',
|
||||||
}),
|
}),
|
||||||
fields() {
|
fields() {
|
||||||
return {
|
const fields = {
|
||||||
index: this.$t('admin.users.table.columns.number'),
|
index: this.$t('admin.users.table.columns.number'),
|
||||||
name: this.$t('admin.users.table.columns.name'),
|
name: this.$t('admin.users.table.columns.name'),
|
||||||
email: this.$t('admin.users.table.columns.email'),
|
email: this.$t('admin.users.table.columns.email'),
|
||||||
@ -142,11 +142,16 @@ export default {
|
|||||||
label: this.$t('admin.users.table.columns.role'),
|
label: this.$t('admin.users.table.columns.role'),
|
||||||
align: 'right',
|
align: 'right',
|
||||||
},
|
},
|
||||||
badges: {
|
}
|
||||||
|
|
||||||
|
if (this.$env.BADGES_ENABLED) {
|
||||||
|
fields.badges = {
|
||||||
label: this.$t('admin.users.table.columns.badges'),
|
label: this.$t('admin.users.table.columns.badges'),
|
||||||
align: 'right',
|
align: 'right',
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return fields
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
apollo: {
|
apollo: {
|
||||||
|
|||||||
2442
webapp/pages/profile/_id/__snapshots__/_slug.spec.js.snap
Normal file
2442
webapp/pages/profile/_id/__snapshots__/_slug.spec.js.snap
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,22 +1,25 @@
|
|||||||
import { mount } from '@vue/test-utils'
|
import { render } from '@testing-library/vue'
|
||||||
import ProfileSlug from './_slug.vue'
|
import ProfileSlug from './_slug.vue'
|
||||||
|
|
||||||
const localVue = global.localVue
|
const localVue = global.localVue
|
||||||
|
|
||||||
localVue.filter('date', (d) => d)
|
localVue.filter('date', (d) => d)
|
||||||
|
|
||||||
|
// Mock Math.random, used in Dropdown
|
||||||
|
Object.assign(Math, {
|
||||||
|
random: () => 0,
|
||||||
|
})
|
||||||
|
|
||||||
const stubs = {
|
const stubs = {
|
||||||
'client-only': true,
|
'client-only': true,
|
||||||
'v-popover': true,
|
'v-popover': true,
|
||||||
'nuxt-link': true,
|
'nuxt-link': true,
|
||||||
'infinite-loading': true,
|
|
||||||
'follow-list': true,
|
'follow-list': true,
|
||||||
'router-link': true,
|
'router-link': true,
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('ProfileSlug', () => {
|
describe('ProfileSlug', () => {
|
||||||
let wrapper
|
let wrapper
|
||||||
let Wrapper
|
|
||||||
let mocks
|
let mocks
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@ -25,7 +28,7 @@ describe('ProfileSlug', () => {
|
|||||||
id: 'p23',
|
id: 'p23',
|
||||||
name: 'It is a post',
|
name: 'It is a post',
|
||||||
},
|
},
|
||||||
$t: jest.fn(),
|
$t: jest.fn((t) => t),
|
||||||
// If you're mocking router, then don't use VueRouter with localVue: https://vue-test-utils.vuejs.org/guides/using-with-vue-router.html
|
// If you're mocking router, then don't use VueRouter with localVue: https://vue-test-utils.vuejs.org/guides/using-with-vue-router.html
|
||||||
$route: {
|
$route: {
|
||||||
params: {
|
params: {
|
||||||
@ -49,49 +52,144 @@ describe('ProfileSlug', () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('mount', () => {
|
const Wrapper = (badgesEnabled, data) => {
|
||||||
Wrapper = () => {
|
return render(ProfileSlug, {
|
||||||
return mount(ProfileSlug, {
|
localVue,
|
||||||
mocks,
|
stubs,
|
||||||
localVue,
|
data: () => data,
|
||||||
stubs,
|
mocks: {
|
||||||
})
|
...mocks,
|
||||||
}
|
$env: {
|
||||||
|
BADGES_ENABLED: badgesEnabled,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
describe('given an authenticated user', () => {
|
describe('given an authenticated user', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mocks.$filters = {
|
mocks.$filters = {
|
||||||
removeLinks: (c) => c,
|
removeLinks: (c) => c,
|
||||||
truncate: (a) => a,
|
truncate: (a) => a,
|
||||||
}
|
}
|
||||||
mocks.$store = {
|
mocks.$store = {
|
||||||
getters: {
|
getters: {
|
||||||
'auth/isModerator': () => false,
|
'auth/isModerator': () => false,
|
||||||
'auth/user': {
|
'auth/user': {
|
||||||
id: 'u23',
|
id: 'u23',
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
})
|
}
|
||||||
|
})
|
||||||
|
|
||||||
describe('given a user for the profile', () => {
|
describe('given another profile user', () => {
|
||||||
beforeEach(() => {
|
const user = {
|
||||||
wrapper = Wrapper()
|
User: [
|
||||||
wrapper.setData({
|
{
|
||||||
User: [
|
id: 'u3',
|
||||||
|
name: 'Bob the builder',
|
||||||
|
contributionsCount: 6,
|
||||||
|
shoutedCount: 7,
|
||||||
|
commentedCount: 8,
|
||||||
|
badgeVerification: {
|
||||||
|
id: 'bv1',
|
||||||
|
icon: '/path/to/icon-bv1',
|
||||||
|
description: 'verified',
|
||||||
|
isDefault: false,
|
||||||
|
},
|
||||||
|
badgeTrophiesSelected: [
|
||||||
{
|
{
|
||||||
id: 'u3',
|
id: 'bt1',
|
||||||
name: 'Bob the builder',
|
icon: '/path/to/icon-bt1',
|
||||||
contributionsCount: 6,
|
description: 'a trophy',
|
||||||
shoutedCount: 7,
|
isDefault: false,
|
||||||
commentedCount: 8,
|
},
|
||||||
|
{
|
||||||
|
id: 'bt2',
|
||||||
|
icon: '/path/to/icon-bt2',
|
||||||
|
description: 'no trophy',
|
||||||
|
isDefault: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('and badges are enabled', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
wrapper = Wrapper(true, user)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('displays name of the user', () => {
|
it('renders', () => {
|
||||||
expect(wrapper.text()).toContain('Bob the builder')
|
expect(wrapper.container).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('and badges are disabled', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
wrapper = Wrapper(false, user)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders', () => {
|
||||||
|
expect(wrapper.container).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('given the logged in user as profile user', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mocks.$route.params.id = 'u23'
|
||||||
|
})
|
||||||
|
|
||||||
|
const user = {
|
||||||
|
User: [
|
||||||
|
{
|
||||||
|
id: 'u23',
|
||||||
|
name: 'Bob the builder',
|
||||||
|
contributionsCount: 6,
|
||||||
|
shoutedCount: 7,
|
||||||
|
commentedCount: 8,
|
||||||
|
badgeVerification: {
|
||||||
|
id: 'bv1',
|
||||||
|
icon: '/path/to/icon-bv1',
|
||||||
|
description: 'verified',
|
||||||
|
isDefault: false,
|
||||||
|
},
|
||||||
|
badgeTrophiesSelected: [
|
||||||
|
{
|
||||||
|
id: 'bt1',
|
||||||
|
icon: '/path/to/icon-bt1',
|
||||||
|
description: 'a trophy',
|
||||||
|
isDefault: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'bt2',
|
||||||
|
icon: '/path/to/icon-bt2',
|
||||||
|
description: 'no trophy',
|
||||||
|
isDefault: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('and badges are enabled', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
wrapper = Wrapper(true, user)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders', () => {
|
||||||
|
expect(wrapper.container).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('and badges are disabled', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
wrapper = Wrapper(false, user)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders', () => {
|
||||||
|
expect(wrapper.container).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -42,8 +42,11 @@
|
|||||||
{{ $t('profile.memberSince') }} {{ user.createdAt | date('MMMM yyyy') }}
|
{{ $t('profile.memberSince') }} {{ user.createdAt | date('MMMM yyyy') }}
|
||||||
</ds-text>
|
</ds-text>
|
||||||
</ds-space>
|
</ds-space>
|
||||||
<ds-space v-if="user.badgeTrophies && user.badgeTrophies.length" margin="x-small">
|
<ds-space v-if="userBadges && userBadges.length" margin="x-small">
|
||||||
<hc-badges :badges="user.badgeTrophies" />
|
<a v-if="myProfile" href="/settings/badges" class="badge-edit-link">
|
||||||
|
<hc-badges :badges="userBadges" />
|
||||||
|
</a>
|
||||||
|
<hc-badges v-if="!myProfile" :badges="userBadges" />
|
||||||
</ds-space>
|
</ds-space>
|
||||||
<ds-flex>
|
<ds-flex>
|
||||||
<ds-flex-item>
|
<ds-flex-item>
|
||||||
@ -266,6 +269,10 @@ export default {
|
|||||||
user() {
|
user() {
|
||||||
return this.User ? this.User[0] : {}
|
return this.User ? this.User[0] : {}
|
||||||
},
|
},
|
||||||
|
userBadges() {
|
||||||
|
if (!this.$env.BADGES_ENABLED) return null
|
||||||
|
return [this.user.badgeVerification, ...(this.user.badgeTrophiesSelected || [])]
|
||||||
|
},
|
||||||
userName() {
|
userName() {
|
||||||
const { name } = this.user || {}
|
const { name } = this.user || {}
|
||||||
return name || this.$t('profile.userAnonym')
|
return name || this.$t('profile.userAnonym')
|
||||||
@ -456,6 +463,12 @@ export default {
|
|||||||
margin: auto;
|
margin: auto;
|
||||||
margin-top: -60px;
|
margin-top: -60px;
|
||||||
}
|
}
|
||||||
|
.badge-edit-link {
|
||||||
|
transition: all 0.2s ease-out;
|
||||||
|
&:hover {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
}
|
||||||
.page-name-profile-id-slug {
|
.page-name-profile-id-slug {
|
||||||
.ds-flex-item:first-child .content-menu {
|
.ds-flex-item:first-child .content-menu {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { mount } from '@vue/test-utils'
|
import { render } from '@testing-library/vue'
|
||||||
import settings from './settings.vue'
|
import settings from './settings.vue'
|
||||||
|
|
||||||
const localVue = global.localVue
|
const localVue = global.localVue
|
||||||
@ -17,21 +17,37 @@ describe('settings.vue', () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('mount', () => {
|
const Wrapper = () => {
|
||||||
const Wrapper = () => {
|
return render(settings, {
|
||||||
return mount(settings, {
|
mocks,
|
||||||
mocks,
|
localVue,
|
||||||
localVue,
|
stubs,
|
||||||
stubs,
|
})
|
||||||
})
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
describe('given badges are enabled', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
mocks.$env = {
|
||||||
|
BADGES_ENABLED: true,
|
||||||
|
}
|
||||||
wrapper = Wrapper()
|
wrapper = Wrapper()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('renders', () => {
|
it('renders', () => {
|
||||||
expect(wrapper.element.tagName).toBe('DIV')
|
expect(wrapper.container).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('given badges are disabled', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mocks.$env = {
|
||||||
|
BADGES_ENABLED: false,
|
||||||
|
}
|
||||||
|
wrapper = Wrapper()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders', () => {
|
||||||
|
expect(wrapper.container).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -21,7 +21,7 @@
|
|||||||
export default {
|
export default {
|
||||||
computed: {
|
computed: {
|
||||||
routes() {
|
routes() {
|
||||||
return [
|
const routes = [
|
||||||
{
|
{
|
||||||
name: this.$t('settings.data.name'),
|
name: this.$t('settings.data.name'),
|
||||||
path: `/settings`,
|
path: `/settings`,
|
||||||
@ -83,6 +83,15 @@ export default {
|
|||||||
},
|
},
|
||||||
} */
|
} */
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if (this.$env.BADGES_ENABLED) {
|
||||||
|
routes.splice(2, 0, {
|
||||||
|
name: this.$t('settings.badges.name'),
|
||||||
|
path: `/settings/badges`,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return routes
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
429
webapp/pages/settings/__snapshots__/badges.spec.js.snap
Normal file
429
webapp/pages/settings/__snapshots__/badges.spec.js.snap
Normal file
@ -0,0 +1,429 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`badge settings with badges more badges available selecting an empty slot shows list with available badges 1`] = `
|
||||||
|
<div>
|
||||||
|
<article
|
||||||
|
class="base-card"
|
||||||
|
>
|
||||||
|
<h2
|
||||||
|
class="title"
|
||||||
|
>
|
||||||
|
settings.badges.name
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
settings.badges.description
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="ds-space ds-space-centered"
|
||||||
|
style="margin-top: 24px; margin-bottom: 16px;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="presenterContainer"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="hc-badges"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="hc-badge-container"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
class="hc-badge"
|
||||||
|
src="/api/verification/icon"
|
||||||
|
title="Verification description"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="hc-badge-container selectable"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
class="hc-badge"
|
||||||
|
src="/api/path/to/some/icon"
|
||||||
|
title="Some description"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="hc-badge-container selectable selected"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
class="hc-badge"
|
||||||
|
src="/api/path/to/empty/icon"
|
||||||
|
title="Empty"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="hc-badge-container selectable"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
class="hc-badge"
|
||||||
|
src="/api/path/to/third/icon"
|
||||||
|
title="Third description"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!---->
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<strong>
|
||||||
|
|
||||||
|
settings.badges.click-to-use
|
||||||
|
|
||||||
|
</strong>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!---->
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="selection-info"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="badge-selection"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="badge-selection-item"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="badge-icon"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
alt="4"
|
||||||
|
src="/api/path/to/fourth/icon"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="badge-info"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="badge-description"
|
||||||
|
>
|
||||||
|
Fourth description
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="badge-selection-item"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="badge-icon"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
alt="5"
|
||||||
|
src="/api/path/to/fifth/icon"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="badge-info"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="badge-description"
|
||||||
|
>
|
||||||
|
Fifth description
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!---->
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`badge settings with badges no more badges available selecting an empty slot shows no more badges available message 1`] = `
|
||||||
|
<div>
|
||||||
|
<article
|
||||||
|
class="base-card"
|
||||||
|
>
|
||||||
|
<h2
|
||||||
|
class="title"
|
||||||
|
>
|
||||||
|
settings.badges.name
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
settings.badges.description
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="ds-space ds-space-centered"
|
||||||
|
style="margin-top: 24px; margin-bottom: 16px;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="presenterContainer"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="hc-badges"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="hc-badge-container"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
class="hc-badge"
|
||||||
|
src="/api/verification/icon"
|
||||||
|
title="Verification description"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="hc-badge-container selectable"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
class="hc-badge"
|
||||||
|
src="/api/path/to/some/icon"
|
||||||
|
title="Some description"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="hc-badge-container selectable selected"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
class="hc-badge"
|
||||||
|
src="/api/path/to/empty/icon"
|
||||||
|
title="Empty"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="hc-badge-container selectable"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
class="hc-badge"
|
||||||
|
src="/api/path/to/third/icon"
|
||||||
|
title="Third description"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
|
||||||
|
settings.badges.no-badges-available
|
||||||
|
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!---->
|
||||||
|
|
||||||
|
<!---->
|
||||||
|
|
||||||
|
<!---->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!---->
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`badge settings with badges renders 1`] = `
|
||||||
|
<div>
|
||||||
|
<article
|
||||||
|
class="base-card"
|
||||||
|
>
|
||||||
|
<h2
|
||||||
|
class="title"
|
||||||
|
>
|
||||||
|
settings.badges.name
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
settings.badges.description
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="ds-space ds-space-centered"
|
||||||
|
style="margin-top: 24px; margin-bottom: 16px;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="presenterContainer"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="hc-badges"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="hc-badge-container"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
class="hc-badge"
|
||||||
|
src="/api/verification/icon"
|
||||||
|
title="Verification description"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="hc-badge-container selectable"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
class="hc-badge"
|
||||||
|
src="/api/path/to/some/icon"
|
||||||
|
title="Some description"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="hc-badge-container selectable"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
class="hc-badge"
|
||||||
|
src="/api/path/to/empty/icon"
|
||||||
|
title="Empty"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="hc-badge-container selectable"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
class="hc-badge"
|
||||||
|
src="/api/path/to/third/icon"
|
||||||
|
title="Third description"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!---->
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<strong>
|
||||||
|
|
||||||
|
settings.badges.click-to-select
|
||||||
|
|
||||||
|
</strong>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!---->
|
||||||
|
|
||||||
|
<!---->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!---->
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`badge settings with badges selecting a used badge clicking remove badge button with successful server request removes the badge 1`] = `
|
||||||
|
<div>
|
||||||
|
<article
|
||||||
|
class="base-card"
|
||||||
|
>
|
||||||
|
<h2
|
||||||
|
class="title"
|
||||||
|
>
|
||||||
|
settings.badges.name
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
settings.badges.description
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="ds-space"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="presenterContainer"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="hc-badges"
|
||||||
|
style="transform: scale(2);"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="hc-badge-container"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
class="hc-badge"
|
||||||
|
src="/api/verification/icon"
|
||||||
|
title="Verification description"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="hc-badge-container selectable"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
class="hc-badge"
|
||||||
|
src="/api/path/to/some/icon"
|
||||||
|
title="Some description"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="hc-badge-container selectable"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
class="hc-badge"
|
||||||
|
src="/api/path/to/empty/icon"
|
||||||
|
title="Empty"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="hc-badge-container selectable"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
class="hc-badge"
|
||||||
|
src="/api/path/to/third/icon"
|
||||||
|
title="Third description"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!---->
|
||||||
|
|
||||||
|
<!---->
|
||||||
|
|
||||||
|
<!---->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!---->
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`badge settings without badges renders 1`] = `
|
||||||
|
<div>
|
||||||
|
<article
|
||||||
|
class="base-card"
|
||||||
|
>
|
||||||
|
<h2
|
||||||
|
class="title"
|
||||||
|
>
|
||||||
|
settings.badges.name
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
settings.badges.description
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="ds-space ds-space-centered"
|
||||||
|
style="margin-top: 24px; margin-bottom: 16px;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="presenterContainer"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="hc-badges"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="hc-badge-container"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
class="hc-badge"
|
||||||
|
src="/api/verification/icon"
|
||||||
|
title="Verification description"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!---->
|
||||||
|
|
||||||
|
<!---->
|
||||||
|
|
||||||
|
<!---->
|
||||||
|
|
||||||
|
<!---->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!---->
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
302
webapp/pages/settings/badges.spec.js
Normal file
302
webapp/pages/settings/badges.spec.js
Normal file
@ -0,0 +1,302 @@
|
|||||||
|
import { render, screen, fireEvent } from '@testing-library/vue'
|
||||||
|
import '@testing-library/jest-dom'
|
||||||
|
import badges from './badges.vue'
|
||||||
|
|
||||||
|
const localVue = global.localVue
|
||||||
|
|
||||||
|
describe('badge settings', () => {
|
||||||
|
let mocks
|
||||||
|
|
||||||
|
const apolloMutateMock = jest.fn()
|
||||||
|
|
||||||
|
const Wrapper = () => {
|
||||||
|
return render(badges, {
|
||||||
|
localVue,
|
||||||
|
mocks,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mocks = {
|
||||||
|
$t: jest.fn((t) => t),
|
||||||
|
$toast: {
|
||||||
|
success: jest.fn(),
|
||||||
|
error: jest.fn(),
|
||||||
|
},
|
||||||
|
$apollo: {
|
||||||
|
mutate: apolloMutateMock,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('without badges', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mocks.$store = {
|
||||||
|
getters: {
|
||||||
|
'auth/isModerator': () => false,
|
||||||
|
'auth/user': {
|
||||||
|
id: 'u23',
|
||||||
|
badgeVerification: {
|
||||||
|
id: 'bv1',
|
||||||
|
icon: '/verification/icon',
|
||||||
|
description: 'Verification description',
|
||||||
|
isDefault: true,
|
||||||
|
},
|
||||||
|
badgeTrophiesSelected: [],
|
||||||
|
badgeTrophiesUnused: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders', () => {
|
||||||
|
const wrapper = Wrapper()
|
||||||
|
expect(wrapper.container).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('with badges', () => {
|
||||||
|
const badgeTrophiesSelected = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
icon: '/path/to/some/icon',
|
||||||
|
isDefault: false,
|
||||||
|
description: 'Some description',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
icon: '/path/to/empty/icon',
|
||||||
|
isDefault: true,
|
||||||
|
description: 'Empty',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
icon: '/path/to/third/icon',
|
||||||
|
isDefault: false,
|
||||||
|
description: 'Third description',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const badgeTrophiesUnused = [
|
||||||
|
{
|
||||||
|
id: '4',
|
||||||
|
icon: '/path/to/fourth/icon',
|
||||||
|
description: 'Fourth description',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '5',
|
||||||
|
icon: '/path/to/fifth/icon',
|
||||||
|
description: 'Fifth description',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
let wrapper
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mocks.$store = {
|
||||||
|
getters: {
|
||||||
|
'auth/isModerator': () => false,
|
||||||
|
'auth/user': {
|
||||||
|
id: 'u23',
|
||||||
|
badgeVerification: {
|
||||||
|
id: 'bv1',
|
||||||
|
icon: '/verification/icon',
|
||||||
|
description: 'Verification description',
|
||||||
|
isDefault: false,
|
||||||
|
},
|
||||||
|
badgeTrophiesSelected,
|
||||||
|
badgeTrophiesUnused,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
wrapper = Wrapper()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders', () => {
|
||||||
|
expect(wrapper.container).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('selecting a used badge', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
const badge = screen.getByTitle(badgeTrophiesSelected[0].description)
|
||||||
|
await fireEvent.click(badge)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('shows remove badge button', () => {
|
||||||
|
expect(screen.getByText('settings.badges.remove')).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('clicking remove badge button', () => {
|
||||||
|
const clickButton = async () => {
|
||||||
|
const removeButton = screen.getByText('settings.badges.remove')
|
||||||
|
await fireEvent.click(removeButton)
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('with successful server request', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
apolloMutateMock.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
setTrophyBadgeSelected: {
|
||||||
|
id: 'u23',
|
||||||
|
badgeTrophiesSelected: [
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
icon: '/path/to/empty/icon',
|
||||||
|
isDefault: true,
|
||||||
|
description: 'Empty',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
icon: '/path/to/empty/icon',
|
||||||
|
isDefault: true,
|
||||||
|
description: 'Empty',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
icon: '/path/to/third/icon',
|
||||||
|
isDefault: false,
|
||||||
|
description: 'Third description',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
clickButton()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('calls the server', () => {
|
||||||
|
expect(apolloMutateMock).toHaveBeenCalledWith({
|
||||||
|
mutation: expect.anything(),
|
||||||
|
update: expect.anything(),
|
||||||
|
variables: {
|
||||||
|
badgeId: null,
|
||||||
|
slot: 0,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
/* To test this, we would need a better apollo mock */
|
||||||
|
it.skip('removes the badge', async () => {
|
||||||
|
expect(wrapper.container).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('shows a success message', () => {
|
||||||
|
expect(mocks.$toast.success).toHaveBeenCalledWith('settings.badges.success-update')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('with failed server request', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
apolloMutateMock.mockRejectedValue({ message: 'Ouch!' })
|
||||||
|
clickButton()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('shows an error message', () => {
|
||||||
|
expect(mocks.$toast.error).toHaveBeenCalledWith('settings.badges.error-update')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('no more badges available', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
mocks.$store.getters['auth/user'].badgeTrophiesUnused = []
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('selecting an empty slot', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
const emptySlot = screen.getAllByTitle('Empty')[0]
|
||||||
|
await fireEvent.click(emptySlot)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('shows no more badges available message', () => {
|
||||||
|
expect(wrapper.container).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('more badges available', () => {
|
||||||
|
describe('selecting an empty slot', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
const emptySlot = screen.getAllByTitle('Empty')[0]
|
||||||
|
await fireEvent.click(emptySlot)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('shows list with available badges', () => {
|
||||||
|
expect(wrapper.container).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('clicking on an available badge', () => {
|
||||||
|
const clickBadge = async () => {
|
||||||
|
const badge = screen.getByText(badgeTrophiesUnused[0].description)
|
||||||
|
await fireEvent.click(badge)
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('with successful server request', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
apolloMutateMock.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
setTrophyBadgeSelected: {
|
||||||
|
id: 'u23',
|
||||||
|
badgeTrophiesSelected: [
|
||||||
|
{
|
||||||
|
id: '4',
|
||||||
|
icon: '/path/to/fourth/icon',
|
||||||
|
description: 'Fourth description',
|
||||||
|
isDefault: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
icon: '/path/to/empty/icon',
|
||||||
|
isDefault: true,
|
||||||
|
description: 'Empty',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
icon: '/path/to/third/icon',
|
||||||
|
isDefault: false,
|
||||||
|
description: 'Third description',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
clickBadge()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('calls the server', () => {
|
||||||
|
expect(apolloMutateMock).toHaveBeenCalledWith({
|
||||||
|
mutation: expect.anything(),
|
||||||
|
update: expect.anything(),
|
||||||
|
variables: {
|
||||||
|
badgeId: '4',
|
||||||
|
slot: 1,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
/* To test this, we would need a better apollo mock */
|
||||||
|
it.skip('adds the badge', async () => {
|
||||||
|
expect(wrapper.container).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('shows a success message', () => {
|
||||||
|
expect(mocks.$toast.success).toHaveBeenCalledWith('settings.badges.success-update')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('with failed server request', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
apolloMutateMock.mockRejectedValue({ message: 'Ouch!' })
|
||||||
|
clickBadge()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('shows an error message', () => {
|
||||||
|
expect(mocks.$toast.error).toHaveBeenCalledWith('settings.badges.error-update')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
176
webapp/pages/settings/badges.vue
Normal file
176
webapp/pages/settings/badges.vue
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
<template>
|
||||||
|
<base-card>
|
||||||
|
<h2 class="title">{{ $t('settings.badges.name') }}</h2>
|
||||||
|
<p>{{ $t('settings.badges.description') }}</p>
|
||||||
|
<ds-space centered margin-bottom="small" margin-top="base">
|
||||||
|
<div class="presenterContainer">
|
||||||
|
<badges
|
||||||
|
:badges="[currentUser.badgeVerification, ...selectedBadges]"
|
||||||
|
:selection-mode="true"
|
||||||
|
@badge-selected="handleBadgeSlotSelection"
|
||||||
|
ref="badgesComponent"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p v-if="!availableBadges.length && isEmptySlotSelected">
|
||||||
|
{{ $t('settings.badges.no-badges-available') }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div v-if="availableBadges.length > 0">
|
||||||
|
<strong>
|
||||||
|
{{
|
||||||
|
selectedBadgeIndex === null
|
||||||
|
? this.$t('settings.badges.click-to-select')
|
||||||
|
: isEmptySlotSelected
|
||||||
|
? this.$t('settings.badges.click-to-use')
|
||||||
|
: ''
|
||||||
|
}}
|
||||||
|
</strong>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="selectedBadgeIndex !== null && !isEmptySlotSelected" class="badge-actions">
|
||||||
|
<base-button @click="removeBadgeFromSlot" class="remove-button">
|
||||||
|
{{ $t('settings.badges.remove') }}
|
||||||
|
</base-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="availableBadges.length && selectedBadgeIndex !== null && isEmptySlotSelected"
|
||||||
|
class="selection-info"
|
||||||
|
>
|
||||||
|
<badge-selection
|
||||||
|
:badges="availableBadges"
|
||||||
|
@badge-selected="assignBadgeToSlot"
|
||||||
|
ref="badgeSelection"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</ds-space>
|
||||||
|
</base-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapGetters, mapMutations } from 'vuex'
|
||||||
|
import { setTrophyBadgeSelected } from '~/graphql/User'
|
||||||
|
import scrollToContent from './scroll-to-content.js'
|
||||||
|
import Badges from '../../components/Badges.vue'
|
||||||
|
import BadgeSelection from '../../components/BadgeSelection.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: { BadgeSelection, Badges },
|
||||||
|
mixins: [scrollToContent],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
selectedBadgeIndex: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters({
|
||||||
|
currentUser: 'auth/user',
|
||||||
|
}),
|
||||||
|
selectedBadges() {
|
||||||
|
return this.currentUser.badgeTrophiesSelected
|
||||||
|
},
|
||||||
|
availableBadges() {
|
||||||
|
return this.currentUser.badgeTrophiesUnused
|
||||||
|
},
|
||||||
|
isEmptySlotSelected() {
|
||||||
|
return this.selectedBadges[this.selectedBadgeIndex]?.isDefault ?? false
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.userBadges = [...(this.currentUser.badgeTrophiesSelected || [])]
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapMutations({
|
||||||
|
setCurrentUser: 'auth/SET_USER',
|
||||||
|
}),
|
||||||
|
handleBadgeSlotSelection(index) {
|
||||||
|
if (index === 0) {
|
||||||
|
this.$toast.info(this.$t('settings.badges.verification'))
|
||||||
|
this.$refs.badgesComponent.resetSelection()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.selectedBadgeIndex = index === null ? null : index - 1 // The first badge in badges component is the verification badge
|
||||||
|
},
|
||||||
|
async setSlot(badge, slot) {
|
||||||
|
await this.$apollo.mutate({
|
||||||
|
mutation: setTrophyBadgeSelected,
|
||||||
|
variables: {
|
||||||
|
badgeId: badge?.id ?? null,
|
||||||
|
slot,
|
||||||
|
},
|
||||||
|
update: (_, { data: { setTrophyBadgeSelected } }) => {
|
||||||
|
const { badgeTrophiesSelected, badgeTrophiesUnused } = setTrophyBadgeSelected
|
||||||
|
this.setCurrentUser({
|
||||||
|
...this.currentUser,
|
||||||
|
badgeTrophiesSelected,
|
||||||
|
badgeTrophiesUnused,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
async assignBadgeToSlot(badge) {
|
||||||
|
if (!badge || this.selectedBadgeIndex === null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.setSlot(badge, this.selectedBadgeIndex)
|
||||||
|
this.$toast.success(this.$t('settings.badges.success-update'))
|
||||||
|
} catch (error) {
|
||||||
|
this.$toast.error(this.$t('settings.badges.error-update'))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.$refs.badgeSelection && this.$refs.badgeSelection.resetSelection) {
|
||||||
|
this.$refs.badgeSelection.resetSelection()
|
||||||
|
}
|
||||||
|
this.$refs.badgesComponent.resetSelection()
|
||||||
|
this.selectedBadgeIndex = null
|
||||||
|
},
|
||||||
|
async removeBadgeFromSlot() {
|
||||||
|
if (this.selectedBadgeIndex === null) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.setSlot(null, this.selectedBadgeIndex)
|
||||||
|
this.$toast.success(this.$t('settings.badges.success-update'))
|
||||||
|
} catch (error) {
|
||||||
|
this.$toast.error(this.$t('settings.badges.error-update'))
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$refs.badgesComponent.resetSelection()
|
||||||
|
this.selectedBadgeIndex = null
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.presenterContainer {
|
||||||
|
margin-top: 20px;
|
||||||
|
padding-top: 50px;
|
||||||
|
min-height: 220px;
|
||||||
|
--badges-scale: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 400px) {
|
||||||
|
.presenterContainer {
|
||||||
|
--badges-scale: 1.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-actions {
|
||||||
|
margin-top: 20px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.remove-button {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.selection-info {
|
||||||
|
margin-top: 20px;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
11
webapp/static/img/badges/stars.svg
Normal file
11
webapp/static/img/badges/stars.svg
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
<svg width="170mm" height="65mm" version="1.1" viewBox="0 0 170 65" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g transform="translate(-40.481 -87.048)" fill="#f2bd27" stroke-width="1.5118">
|
||||||
|
<path transform="matrix(.30472 .0030284 -.0030284 .30472 .96713 112.56)" d="m461.32 72.567-52.541-26.955-51.991 28.001 9.3998-58.299-42.696-40.794 58.35-9.0756 25.603-53.213 26.663 52.69 58.52 7.9067-41.872 41.64z"/>
|
||||||
|
<path transform="matrix(.21871 .0021736 -.0021736 .21871 75.918 124.75)" d="m461.32 72.567-52.541-26.955-51.991 28.001 9.3998-58.299-42.696-40.794 58.35-9.0756 25.603-53.213 26.663 52.69 58.52 7.9067-41.872 41.64z"/>
|
||||||
|
<path transform="matrix(.21871 .0021736 -.0021736 .21871 -3.5617 124.75)" d="m461.32 72.567-52.541-26.955-51.991 28.001 9.3998-58.299-42.696-40.794 58.35-9.0756 25.603-53.213 26.663 52.69 58.52 7.9067-41.872 41.64z"/>
|
||||||
|
<path transform="matrix(.16908 .0016804 -.0016804 .16908 -13.934 138.16)" d="m461.32 72.567-52.541-26.955-51.991 28.001 9.3998-58.299-42.696-40.794 58.35-9.0756 25.603-53.213 26.663 52.69 58.52 7.9067-41.872 41.64z"/>
|
||||||
|
<path transform="matrix(.16908 .0016804 -.0016804 .16908 126.82 138.16)" d="m461.32 72.567-52.541-26.955-51.991 28.001 9.3998-58.299-42.696-40.794 58.35-9.0756 25.603-53.213 26.663 52.69 58.52 7.9067-41.872 41.64z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.4 KiB |
@ -7,6 +7,11 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf"
|
resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf"
|
||||||
integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==
|
integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==
|
||||||
|
|
||||||
|
"@adobe/css-tools@^4.4.0":
|
||||||
|
version "4.4.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.4.2.tgz#c836b1bd81e6d62cd6cdf3ee4948bcdce8ea79c8"
|
||||||
|
integrity sha512-baYZExFpsdkBNuvGKTKWCwKH57HRZLVtycZS05WTQNVOiXVSeAki3nU35zlRbToeMW8aHlJfyS+1C4BOv27q0A==
|
||||||
|
|
||||||
"@ampproject/remapping@^2.2.0":
|
"@ampproject/remapping@^2.2.0":
|
||||||
version "2.2.1"
|
version "2.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630"
|
resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630"
|
||||||
@ -4253,6 +4258,19 @@
|
|||||||
lz-string "^1.5.0"
|
lz-string "^1.5.0"
|
||||||
pretty-format "^27.0.2"
|
pretty-format "^27.0.2"
|
||||||
|
|
||||||
|
"@testing-library/jest-dom@^6.6.3":
|
||||||
|
version "6.6.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-6.6.3.tgz#26ba906cf928c0f8172e182c6fe214eb4f9f2bd2"
|
||||||
|
integrity sha512-IteBhl4XqYNkM54f4ejhLRJiZNqcSCoXUOG2CPK7qbD322KjQozM4kHQOfkG2oln9b9HTYqs+Sae8vBATubxxA==
|
||||||
|
dependencies:
|
||||||
|
"@adobe/css-tools" "^4.4.0"
|
||||||
|
aria-query "^5.0.0"
|
||||||
|
chalk "^3.0.0"
|
||||||
|
css.escape "^1.5.1"
|
||||||
|
dom-accessibility-api "^0.6.3"
|
||||||
|
lodash "^4.17.21"
|
||||||
|
redent "^3.0.0"
|
||||||
|
|
||||||
"@testing-library/vue@5":
|
"@testing-library/vue@5":
|
||||||
version "5.9.0"
|
version "5.9.0"
|
||||||
resolved "https://registry.yarnpkg.com/@testing-library/vue/-/vue-5.9.0.tgz#d33c52ae89e076808abe622f70dcbccb1b5d080c"
|
resolved "https://registry.yarnpkg.com/@testing-library/vue/-/vue-5.9.0.tgz#d33c52ae89e076808abe622f70dcbccb1b5d080c"
|
||||||
@ -6058,6 +6076,11 @@ aria-query@5.1.3:
|
|||||||
dependencies:
|
dependencies:
|
||||||
deep-equal "^2.0.5"
|
deep-equal "^2.0.5"
|
||||||
|
|
||||||
|
aria-query@^5.0.0:
|
||||||
|
version "5.3.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.3.2.tgz#93f81a43480e33a338f19163a3d10a50c01dcd59"
|
||||||
|
integrity sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==
|
||||||
|
|
||||||
arr-diff@^4.0.0:
|
arr-diff@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520"
|
resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520"
|
||||||
@ -8230,6 +8253,11 @@ css-what@2.1, css-what@^2.1.2:
|
|||||||
resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2"
|
resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2"
|
||||||
integrity sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==
|
integrity sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==
|
||||||
|
|
||||||
|
css.escape@^1.5.1:
|
||||||
|
version "1.5.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb"
|
||||||
|
integrity sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==
|
||||||
|
|
||||||
csscolorparser@~1.0.3:
|
csscolorparser@~1.0.3:
|
||||||
version "1.0.3"
|
version "1.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/csscolorparser/-/csscolorparser-1.0.3.tgz#b34f391eea4da8f3e98231e2ccd8df9c041f171b"
|
resolved "https://registry.yarnpkg.com/csscolorparser/-/csscolorparser-1.0.3.tgz#b34f391eea4da8f3e98231e2ccd8df9c041f171b"
|
||||||
@ -8793,6 +8821,11 @@ dom-accessibility-api@^0.5.9:
|
|||||||
resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz#5a7429e6066eb3664d911e33fb0e45de8eb08453"
|
resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz#5a7429e6066eb3664d911e33fb0e45de8eb08453"
|
||||||
integrity sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==
|
integrity sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==
|
||||||
|
|
||||||
|
dom-accessibility-api@^0.6.3:
|
||||||
|
version "0.6.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz#993e925cc1d73f2c662e7d75dd5a5445259a8fd8"
|
||||||
|
integrity sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==
|
||||||
|
|
||||||
dom-converter@^0.2:
|
dom-converter@^0.2:
|
||||||
version "0.2.0"
|
version "0.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768"
|
resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user