resolve conflict

This commit is contained in:
ogerly 2023-04-25 15:28:43 +02:00
commit bd8ea66922
14 changed files with 317 additions and 47 deletions

View File

@ -70,6 +70,7 @@ export const filterPosts = () => {
id
title
content
eventStart
}
}
`

View File

@ -0,0 +1,230 @@
import { createTestClient } from 'apollo-server-testing'
import Factory, { cleanDatabase } from '../../db/factories'
import { getNeode, getDriver } from '../../db/neo4j'
import createServer from '../../server'
import CONFIG from '../../config'
import { filterPosts, createPostMutation } from '../../graphql/posts'
CONFIG.CATEGORIES_ACTIVE = false
const driver = getDriver()
const neode = getNeode()
let query
let mutate
let authenticatedUser
let user
beforeAll(async () => {
await cleanDatabase()
const { server } = createServer({
context: () => {
return {
driver,
neode,
user: authenticatedUser,
}
},
})
query = createTestClient(server).query
mutate = createTestClient(server).mutate
})
afterAll(async () => {
await cleanDatabase()
driver.close()
})
describe('Filter Posts', () => {
const now = new Date()
beforeAll(async () => {
user = await Factory.build('user', {
id: 'user',
name: 'User',
about: 'I am a user.',
})
authenticatedUser = await user.toJson()
await mutate({
mutation: createPostMutation(),
variables: {
id: 'a1',
title: 'I am an article',
content: 'I am an article written by user.',
},
})
await mutate({
mutation: createPostMutation(),
variables: {
id: 'a2',
title: 'I am anonther article',
content: 'I am another article written by user.',
},
})
await mutate({
mutation: createPostMutation(),
variables: {
id: 'e1',
title: 'Illegaler Kindergeburtstag',
content: 'Elli wird fünf. Wir feiern ihren Geburtstag.',
postType: 'Event',
eventInput: {
eventStart: new Date(now.getFullYear(), now.getMonth() + 1).toISOString(),
eventVenue: 'Garten der Familie Maier',
},
},
})
await mutate({
mutation: createPostMutation(),
variables: {
id: 'e2',
title: 'Räuber-Treffen',
content: 'Planung der nächsten Räuberereien',
postType: 'Event',
eventInput: {
eventStart: new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1).toISOString(),
eventVenue: 'Wirtshaus im Spessart',
},
},
})
})
describe('no filters set', () => {
it('finds all posts', async () => {
const {
data: { Post: result },
} = await query({ query: filterPosts() })
expect(result).toHaveLength(4)
expect(result).toEqual(
expect.arrayContaining([
expect.objectContaining({ id: 'a1' }),
expect.objectContaining({ id: 'a2' }),
expect.objectContaining({ id: 'e1' }),
expect.objectContaining({ id: 'e2' }),
]),
)
})
})
describe('post type filter set to ["Article"]', () => {
it('finds the articles', async () => {
const {
data: { Post: result },
} = await query({ query: filterPosts(), variables: { filter: { postType_in: ['Article'] } } })
expect(result).toHaveLength(2)
expect(result).toEqual(
expect.arrayContaining([
expect.objectContaining({ id: 'a1' }),
expect.objectContaining({ id: 'a2' }),
]),
)
})
})
describe('post type filter set to ["Event"]', () => {
it('finds the articles', async () => {
const {
data: { Post: result },
} = await query({ query: filterPosts(), variables: { filter: { postType_in: ['Event'] } } })
expect(result).toHaveLength(2)
expect(result).toEqual(
expect.arrayContaining([
expect.objectContaining({ id: 'e1' }),
expect.objectContaining({ id: 'e2' }),
]),
)
})
})
describe('post type filter set to ["Article", "Event"]', () => {
it('finds all posts', async () => {
const {
data: { Post: result },
} = await query({
query: filterPosts(),
variables: { filter: { postType_in: ['Article', 'Event'] } },
})
expect(result).toHaveLength(4)
expect(result).toEqual(
expect.arrayContaining([
expect.objectContaining({ id: 'a1' }),
expect.objectContaining({ id: 'a2' }),
expect.objectContaining({ id: 'e1' }),
expect.objectContaining({ id: 'e2' }),
]),
)
})
})
describe('order events by event start descending', () => {
it('finds the events orderd accordingly', async () => {
const {
data: { Post: result },
} = await query({
query: filterPosts(),
variables: { filter: { postType_in: ['Event'] }, orderBy: ['eventStart_desc'] },
})
expect(result).toHaveLength(2)
expect(result).toEqual([
expect.objectContaining({
id: 'e1',
eventStart: new Date(now.getFullYear(), now.getMonth() + 1).toISOString(),
}),
expect.objectContaining({
id: 'e2',
eventStart: new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1).toISOString(),
}),
])
})
})
describe('order events by event start ascending', () => {
it('finds the events orderd accordingly', async () => {
const {
data: { Post: result },
} = await query({
query: filterPosts(),
variables: { filter: { postType_in: ['Event'] }, orderBy: ['eventStart_asc'] },
})
expect(result).toHaveLength(2)
expect(result).toEqual([
expect.objectContaining({
id: 'e2',
eventStart: new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1).toISOString(),
}),
expect.objectContaining({
id: 'e1',
eventStart: new Date(now.getFullYear(), now.getMonth() + 1).toISOString(),
}),
])
})
})
describe('filter events by event start date', () => {
it('finds only events after given date', async () => {
const {
data: { Post: result },
} = await query({
query: filterPosts(),
variables: {
filter: {
postType_in: ['Event'],
eventStart_gte: new Date(
now.getFullYear(),
now.getMonth(),
now.getDate() + 2,
).toISOString(),
},
},
})
expect(result).toHaveLength(1)
expect(result).toEqual([
expect.objectContaining({
id: 'e1',
eventStart: new Date(now.getFullYear(), now.getMonth() + 1).toISOString(),
}),
])
})
})
})

View File

@ -334,6 +334,7 @@ export default {
`
MATCH (user:User {id: $userId}) WHERE user.role = 'admin'
MATCH (post:Post {id: $params.id})
WHERE NOT((post)-[:IN]->(:Group))
MERGE (user)-[pinned:PINNED {createdAt: toString(datetime())}]->(post)
SET post.pinned = true
RETURN post, pinned.createdAt as pinnedAt
@ -346,10 +347,12 @@ export default {
}))
})
const [transactionResult] = await writeTxResultPromise
const { pinnedPost, pinnedAt } = transactionResult
pinnedPostWithNestedAttributes = {
...pinnedPost,
pinnedAt,
if (transactionResult) {
const { pinnedPost, pinnedAt } = transactionResult
pinnedPostWithNestedAttributes = {
...pinnedPost,
pinnedAt,
}
}
} finally {
session.close()

View File

@ -818,11 +818,13 @@ describe('Posts in Groups', () => {
id: 'post-to-public-group',
title: 'A post to a public group',
content: 'I am posting into a public group as a member of the group',
eventStart: null,
},
{
id: 'post-without-group',
title: 'A post without a group',
content: 'I am a user who does not belong to a group yet.',
eventStart: null,
},
]),
},
@ -846,11 +848,13 @@ describe('Posts in Groups', () => {
id: 'post-to-public-group',
title: 'A post to a public group',
content: 'I am posting into a public group as a member of the group',
eventStart: null,
},
{
id: 'post-without-group',
title: 'A post without a group',
content: 'I am a user who does not belong to a group yet.',
eventStart: null,
},
]),
},
@ -874,11 +878,13 @@ describe('Posts in Groups', () => {
id: 'post-to-public-group',
title: 'A post to a public group',
content: 'I am posting into a public group as a member of the group',
eventStart: null,
},
{
id: 'post-without-group',
title: 'A post without a group',
content: 'I am a user who does not belong to a group yet.',
eventStart: null,
},
]),
},
@ -902,11 +908,13 @@ describe('Posts in Groups', () => {
id: 'post-to-public-group',
title: 'A post to a public group',
content: 'I am posting into a public group as a member of the group',
eventStart: null,
},
{
id: 'post-without-group',
title: 'A post without a group',
content: 'I am a user who does not belong to a group yet.',
eventStart: null,
},
]),
},
@ -930,21 +938,25 @@ describe('Posts in Groups', () => {
id: 'post-to-public-group',
title: 'A post to a public group',
content: 'I am posting into a public group as a member of the group',
eventStart: null,
},
{
id: 'post-without-group',
title: 'A post without a group',
content: 'I am a user who does not belong to a group yet.',
eventStart: null,
},
{
id: 'post-to-closed-group',
title: 'A post to a closed group',
content: 'I am posting into a closed group as a member of the group',
eventStart: null,
},
{
id: 'post-to-hidden-group',
title: 'A post to a hidden group',
content: 'I am posting into a hidden group as a member of the group',
eventStart: null,
},
]),
},
@ -1319,16 +1331,19 @@ describe('Posts in Groups', () => {
id: 'post-to-public-group',
title: 'A post to a public group',
content: 'I am posting into a public group as a member of the group',
eventStart: null,
},
{
id: 'post-without-group',
title: 'A post without a group',
content: 'I am a user who does not belong to a group yet.',
eventStart: null,
},
{
id: 'post-to-closed-group',
title: 'A post to a closed group',
content: 'I am posting into a closed group as a member of the group',
eventStart: null,
},
]),
},
@ -1361,21 +1376,25 @@ describe('Posts in Groups', () => {
id: 'post-to-public-group',
title: 'A post to a public group',
content: 'I am posting into a public group as a member of the group',
eventStart: null,
},
{
id: 'post-without-group',
title: 'A post without a group',
content: 'I am a user who does not belong to a group yet.',
eventStart: null,
},
{
id: 'post-to-closed-group',
title: 'A post to a closed group',
content: 'I am posting into a closed group as a member of the group',
eventStart: null,
},
{
id: 'post-to-hidden-group',
title: 'A post to a hidden group',
content: 'I am posting into a hidden group as a member of the group',
eventStart: null,
},
]),
},
@ -1410,16 +1429,19 @@ describe('Posts in Groups', () => {
id: 'post-to-public-group',
title: 'A post to a public group',
content: 'I am posting into a public group as a member of the group',
eventStart: null,
},
{
id: 'post-without-group',
title: 'A post without a group',
content: 'I am a user who does not belong to a group yet.',
eventStart: null,
},
{
id: 'post-to-hidden-group',
title: 'A post to a hidden group',
content: 'I am posting into a hidden group as a member of the group',
eventStart: null,
},
]),
},
@ -1452,11 +1474,13 @@ describe('Posts in Groups', () => {
id: 'post-to-public-group',
title: 'A post to a public group',
content: 'I am posting into a public group as a member of the group',
eventStart: null,
},
{
id: 'post-without-group',
title: 'A post without a group',
content: 'I am a user who does not belong to a group yet.',
eventStart: null,
},
]),
},
@ -1489,21 +1513,25 @@ describe('Posts in Groups', () => {
id: 'post-to-public-group',
title: 'A post to a public group',
content: 'I am posting into a public group as a member of the group',
eventStart: null,
},
{
id: 'post-without-group',
title: 'A post without a group',
content: 'I am a user who does not belong to a group yet.',
eventStart: null,
},
{
id: 'post-to-closed-group',
title: 'A post to a closed group',
content: 'I am posting into a closed group as a member of the group',
eventStart: null,
},
{
id: 'post-to-hidden-group',
title: 'A post to a hidden group',
content: 'I am posting into a hidden group as a member of the group',
eventStart: null,
},
]),
},
@ -1534,21 +1562,25 @@ describe('Posts in Groups', () => {
id: 'post-to-public-group',
title: 'A post to a public group',
content: 'I am posting into a public group as a member of the group',
eventStart: null,
},
{
id: 'post-without-group',
title: 'A post without a group',
content: 'I am a user who does not belong to a group yet.',
eventStart: null,
},
{
id: 'post-to-closed-group',
title: 'A post to a closed group',
content: 'I am posting into a closed group as a member of the group',
eventStart: null,
},
{
id: 'post-to-hidden-group',
title: 'A post to a hidden group',
content: 'I am posting into a hidden group as a member of the group',
eventStart: null,
},
]),
},
@ -1579,21 +1611,25 @@ describe('Posts in Groups', () => {
id: 'post-to-public-group',
title: 'A post to a public group',
content: 'I am posting into a public group as a member of the group',
eventStart: null,
},
{
id: 'post-without-group',
title: 'A post without a group',
content: 'I am a user who does not belong to a group yet.',
eventStart: null,
},
{
id: 'post-to-closed-group',
title: 'A post to a closed group',
content: 'I am posting into a closed group as a member of the group',
eventStart: null,
},
{
id: 'post-to-hidden-group',
title: 'A post to a hidden group',
content: 'I am posting into a hidden group as a member of the group',
eventStart: null,
},
]),
},
@ -1628,21 +1664,25 @@ describe('Posts in Groups', () => {
id: 'post-to-public-group',
title: 'A post to a public group',
content: 'I am posting into a public group as a member of the group',
eventStart: null,
},
{
id: 'post-without-group',
title: 'A post without a group',
content: 'I am a user who does not belong to a group yet.',
eventStart: null,
},
{
id: 'post-to-closed-group',
title: 'A post to a closed group',
content: 'I am posting into a closed group as a member of the group',
eventStart: null,
},
{
id: 'post-to-hidden-group',
title: 'A post to a hidden group',
content: 'I am posting into a hidden group as a member of the group',
eventStart: null,
},
]),
},
@ -1675,21 +1715,25 @@ describe('Posts in Groups', () => {
id: 'post-to-public-group',
title: 'A post to a public group',
content: 'I am posting into a public group as a member of the group',
eventStart: null,
},
{
id: 'post-without-group',
title: 'A post without a group',
content: 'I am a user who does not belong to a group yet.',
eventStart: null,
},
{
id: 'post-to-closed-group',
title: 'A post to a closed group',
content: 'I am posting into a closed group as a member of the group',
eventStart: null,
},
{
id: 'post-to-hidden-group',
title: 'A post to a hidden group',
content: 'I am posting into a hidden group as a member of the group',
eventStart: null,
},
]),
},
@ -1739,11 +1783,13 @@ describe('Posts in Groups', () => {
id: 'post-to-closed-group',
title: 'A post to a closed group',
content: 'I am posting into a closed group as a member of the group',
eventStart: null,
},
{
id: 'post-to-hidden-group',
title: 'A post to a hidden group',
content: 'I am posting into a hidden group as a member of the group',
eventStart: null,
},
]),
},

View File

@ -223,8 +223,7 @@ export default {
},
searchResults: async (_parent, args, context, _resolveInfo) => {
const { query, limit } = args
let userId = null
if (context.user) userId = context.user.id
const userId = context.user?.id || null
const searchType = query.replace(/^([!@#&]?).*$/, '$1')
const searchString = query.replace(/^([!@#&])/, '')

View File

@ -33,7 +33,7 @@ const matchSomeWordsExactly = (str, boost = 2) => {
const matchBeginningOfWords = (str) => {
return str
.split(' ')
.filter((s) => s.length > 3)
.filter((s) => s.length >= 2)
.map((s) => s + '*')
.join(' ')
}

View File

@ -37,7 +37,7 @@ describe('queryString', () => {
describe('globbing for longer words', () => {
it('globs words with more than three characters', () => {
expect(queryString('a couple of words')).toContain('couple* words*')
expect(queryString('a couple of words')).toContain('couple* of* words*')
})
})
})

View File

@ -83,6 +83,8 @@ input _PostFilter {
emotions_every: _PostEMOTEDFilter
group: _GroupFilter
postsInMyGroups: Boolean
postType_in: [PostType]
eventStart_gte: String
}
enum _PostOrdering {
@ -104,6 +106,8 @@ enum _PostOrdering {
language_desc
pinned_asc
pinned_desc
eventStart_asc
eventStart_desc
}

View File

@ -80,7 +80,7 @@ export default {
})
}
if (this.isAdmin) {
if (this.isAdmin && !this.resource.group) {
if (!this.resource.pinnedBy) {
routes.push({
label: this.$t(`post.menu.pin`),

View File

@ -218,7 +218,6 @@ export default {
aspectRatio: imageAspectRatio = null,
type: imageType = null,
} = image || {}
return {
categoriesActive: this.$env.CATEGORIES_ACTIVE,
links,

View File

@ -63,13 +63,6 @@ describe('SearchableInput.vue', () => {
expect(select.element.value).toBe('abcd')
})
it('calls onDelete when the delete key is pressed', () => {
const spy = jest.spyOn(wrapper.vm, 'onDelete')
select.trigger('input')
select.trigger('keyup.delete')
expect(spy).toHaveBeenCalledTimes(1)
})
describe('navigating to resource', () => {
beforeEach(() => {
propsData = { options: searchResults }

View File

@ -1,9 +1,10 @@
<template>
<div class="searchable-input" aria-label="search" role="search">
<ds-select
ref="select"
type="search"
icon="search"
v-model="searchValue"
v-model="value"
:id="id"
label-prop="id"
:icon-right="null"
@ -11,12 +12,11 @@
:loading="loading"
:filter="(item) => item"
:no-options-available="emptyText"
:auto-reset-search="!searchValue"
:auto-reset-search="!value"
:placeholder="$t('search.placeholder')"
@focus.capture.native="onFocus"
@input.native="handleInput"
@input.native="onInput"
@keyup.enter.native="onEnter"
@keyup.delete.native="onDelete"
@keyup.esc.native="clear"
@blur.capture.native="onBlur"
@input.exact="onSelect"
@ -77,23 +77,29 @@ export default {
},
data() {
return {
searchValue: '',
value: '',
unprocessedSearchInput: '',
searchProcess: null,
previousSearchTerm: '',
delay: 300,
}
},
computed: {
emptyText() {
return this.isActive && !this.loading ? this.$t('search.failed') : this.$t('search.hint')
return !this.loading && this.isSearchable()
? this.$t('search.failed')
: this.$t('search.hint')
},
isActive() {
return !isEmpty(this.previousSearchTerm)
return !isEmpty(this.value)
},
},
methods: {
isSearchable() {
return (
!isEmpty(this.value) &&
typeof this.value === 'string' &&
this.value.replace(/\s+/g, '').length >= 3
)
},
isFirstOfType(option) {
return (
this.options.findIndex((o) => o === option) ===
@ -103,49 +109,36 @@ export default {
onFocus(event) {
clearTimeout(this.searchProcess)
},
handleInput(event) {
onInput(event) {
clearTimeout(this.searchProcess)
this.value = event.target ? event.target.value.replace(/\s+/g, ' ').trim() : ''
this.unprocessedSearchInput = this.value
if (isEmpty(this.value) || this.value.replace(/\s+/g, '').length < 3) {
if (!this.isSearchable()) {
this.$emit('clearSearch')
return
}
this.searchProcess = setTimeout(() => {
this.previousSearchTerm = this.value
this.$emit('query', this.value)
}, this.delay)
},
onEnter(event) {
this.$router.push({
path: '/search/search-results',
query: { search: this.unprocessedSearchInput },
query: { search: this.value },
})
this.$emit('clearSearch')
},
onDelete(event) {
clearTimeout(this.searchProcess)
const value = event.target ? event.target.value.trim() : ''
if (isEmpty(value)) {
this.clear()
} else {
this.handleInput(event)
}
this.$refs.select.close()
},
clear() {
this.unprocessedSearchInput = ''
this.previousSearchTerm = ''
this.searchValue = ''
this.value = ''
this.$emit('clearSearch')
clearTimeout(this.searchProcess)
},
onBlur(event) {
this.searchValue = this.previousSearchTerm
clearTimeout(this.searchProcess)
},
onSelect(item) {
this.goToResource(item)
this.$nextTick(() => {
this.searchValue = this.previousSearchTerm
this.value = this.$refs.select.$data.searchString
})
},
getRouteName(item) {

View File

@ -67,7 +67,6 @@ export const postFragment = gql`
}
pinnedAt
pinned
postType
}
`

View File

@ -24,6 +24,7 @@ export default (i18n) => {
query Post($id: ID!) {
Post(id: $id) {
postType
...post
...postCounts
...tagsCategoriesAndPinned
@ -66,6 +67,7 @@ export const filterPosts = (i18n) => {
query Post($filter: _PostFilter, $first: Int, $offset: Int, $orderBy: [_PostOrdering]) {
Post(filter: $filter, first: $first, offset: $offset, orderBy: $orderBy) {
postType
...post
...postCounts
...tagsCategoriesAndPinned
@ -103,6 +105,7 @@ export const profilePagePosts = (i18n) => {
$orderBy: [_PostOrdering]
) {
profilePagePosts(filter: $filter, first: $first, offset: $offset, orderBy: $orderBy) {
postType
...post
...postCounts
...tagsCategoriesAndPinned