mirror of
https://github.com/Ocelot-Social-Community/Ocelot-Social.git
synced 2025-12-13 07:46:06 +00:00
Store Hashtags of Post content in database and write a lot of tests
Co-Authored-By: mattwr18 <mattwr18@gmail.com>
This commit is contained in:
parent
f7196e8663
commit
a4cf2d3ee8
@ -21,16 +21,25 @@ const notify = async (postId, idsOfMentionedUsers, context) => {
|
||||
|
||||
const updateHashtagsOfPost = async (postId, hashtags, context) => {
|
||||
const session = context.driver.session()
|
||||
const cypher = `
|
||||
MATCH (p:Post { id: $postId })-[oldRelations:TAGGED]->(oldTags:Tag)
|
||||
DELETE oldRelations
|
||||
WITH p
|
||||
// We need two Cypher statements, because the 'MATCH' in the 'cypherDeletePreviousRelations' statement
|
||||
// functions as an 'if'. In case there is no previous relation, the rest of the commands are omitted
|
||||
// and no new Hashtags and relations will be created.
|
||||
const cypherDeletePreviousRelations = `
|
||||
MATCH (p:Post { id: $postId })-[previousRelations:TAGGED]->(t:Tag)
|
||||
DELETE previousRelations
|
||||
RETURN p, t
|
||||
`
|
||||
const cypherCreateNewTagsAndRelations = `
|
||||
MATCH (p:Post { id: $postId})
|
||||
UNWIND $hashtags AS tagName
|
||||
MERGE (t:Tag { id: tagName, name: tagName, disabled: false, deleted: false })
|
||||
MERGE (p)-[:TAGGED]->(t)
|
||||
RETURN p, t
|
||||
`
|
||||
await session.run(cypher, {
|
||||
await session.run(cypherDeletePreviousRelations, {
|
||||
postId,
|
||||
})
|
||||
await session.run(cypherCreateNewTagsAndRelations, {
|
||||
postId,
|
||||
hashtags,
|
||||
})
|
||||
@ -38,18 +47,14 @@ const updateHashtagsOfPost = async (postId, hashtags, context) => {
|
||||
}
|
||||
|
||||
const handleContentData = async (resolve, root, args, context, resolveInfo) => {
|
||||
console.log('args.content: ', args.content)
|
||||
// extract user ids before xss-middleware removes classes via the following "resolve" call
|
||||
const idsOfMentionedUsers = extractMentionedUsers(args.content)
|
||||
console.log('idsOfMentionedUsers: ', idsOfMentionedUsers)
|
||||
// extract tag (hashtag) ids before xss-middleware removes classes via the following "resolve" call
|
||||
const hashtags = extractHashtags(args.content)
|
||||
console.log('hashtags: ', hashtags)
|
||||
|
||||
// removes classes from the content
|
||||
const post = await resolve(root, args, context, resolveInfo)
|
||||
|
||||
console.log('post.id: ', post.id)
|
||||
await notify(post.id, idsOfMentionedUsers, context)
|
||||
await updateHashtagsOfPost(post.id, hashtags, context)
|
||||
|
||||
@ -61,4 +66,4 @@ export default {
|
||||
CreatePost: handleContentData,
|
||||
UpdatePost: handleContentData,
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,11 @@
|
||||
import { GraphQLClient } from 'graphql-request'
|
||||
import { host, login } from '../../jest/helpers'
|
||||
import {
|
||||
GraphQLClient
|
||||
} from 'graphql-request'
|
||||
import gql from 'graphql-tag'
|
||||
import {
|
||||
host,
|
||||
login
|
||||
} from '../../jest/helpers'
|
||||
import Factory from '../../seed/factories'
|
||||
|
||||
const factory = Factory()
|
||||
@ -20,22 +26,29 @@ afterEach(async () => {
|
||||
})
|
||||
|
||||
describe('currentUser { notifications }', () => {
|
||||
const query = `query($read: Boolean) {
|
||||
currentUser {
|
||||
notifications(read: $read, orderBy: createdAt_desc) {
|
||||
read
|
||||
post {
|
||||
content
|
||||
}
|
||||
}
|
||||
const query = gql `
|
||||
query($read: Boolean) {
|
||||
currentUser {
|
||||
notifications(read: $read, orderBy: createdAt_desc) {
|
||||
read
|
||||
post {
|
||||
content
|
||||
}
|
||||
}`
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
describe('authenticated', () => {
|
||||
let headers
|
||||
beforeEach(async () => {
|
||||
headers = await login({ email: 'test@example.org', password: '1234' })
|
||||
client = new GraphQLClient(host, { headers })
|
||||
headers = await login({
|
||||
email: 'test@example.org',
|
||||
password: '1234',
|
||||
})
|
||||
client = new GraphQLClient(host, {
|
||||
headers,
|
||||
})
|
||||
})
|
||||
|
||||
describe('given another user', () => {
|
||||
@ -60,17 +73,24 @@ describe('currentUser { notifications }', () => {
|
||||
'Hey <a class="mention" href="/profile/you/al-capone">@al-capone</a> how do you do?'
|
||||
|
||||
beforeEach(async () => {
|
||||
const createPostMutation = `
|
||||
mutation($title: String!, $content: String!) {
|
||||
CreatePost(title: $title, content: $content) {
|
||||
id
|
||||
title
|
||||
content
|
||||
const createPostMutation = gql `
|
||||
mutation($title: String!, $content: String!) {
|
||||
CreatePost(title: $title, content: $content) {
|
||||
id
|
||||
title
|
||||
content
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
authorClient = new GraphQLClient(host, { headers: authorHeaders })
|
||||
const { CreatePost } = await authorClient.request(createPostMutation, { title, content })
|
||||
authorClient = new GraphQLClient(host, {
|
||||
headers: authorHeaders,
|
||||
})
|
||||
const {
|
||||
CreatePost
|
||||
} = await authorClient.request(createPostMutation, {
|
||||
title,
|
||||
content,
|
||||
})
|
||||
post = CreatePost
|
||||
})
|
||||
|
||||
@ -79,10 +99,19 @@ describe('currentUser { notifications }', () => {
|
||||
'Hey <a href="/profile/you/al-capone" target="_blank">@al-capone</a> how do you do?'
|
||||
const expected = {
|
||||
currentUser: {
|
||||
notifications: [{ read: false, post: { content: expectedContent } }],
|
||||
notifications: [{
|
||||
read: false,
|
||||
post: {
|
||||
content: expectedContent,
|
||||
},
|
||||
}, ],
|
||||
},
|
||||
}
|
||||
await expect(client.request(query, { read: false })).resolves.toEqual(expected)
|
||||
await expect(
|
||||
client.request(query, {
|
||||
read: false,
|
||||
}),
|
||||
).resolves.toEqual(expected)
|
||||
})
|
||||
|
||||
describe('who mentions me again', () => {
|
||||
@ -93,16 +122,21 @@ describe('currentUser { notifications }', () => {
|
||||
// during development and thought: A feature not a bug! This way we
|
||||
// can encode a re-mentioning of users when you edit your post or
|
||||
// comment.
|
||||
const createPostMutation = `
|
||||
mutation($id: ID!, $content: String!) {
|
||||
UpdatePost(id: $id, content: $content) {
|
||||
title
|
||||
content
|
||||
const createPostMutation = gql `
|
||||
mutation($id: ID!, $content: String!) {
|
||||
UpdatePost(id: $id, content: $content) {
|
||||
title
|
||||
content
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
authorClient = new GraphQLClient(host, { headers: authorHeaders })
|
||||
await authorClient.request(createPostMutation, { id: post.id, content: updatedContent })
|
||||
authorClient = new GraphQLClient(host, {
|
||||
headers: authorHeaders,
|
||||
})
|
||||
await authorClient.request(createPostMutation, {
|
||||
id: post.id,
|
||||
content: updatedContent,
|
||||
})
|
||||
})
|
||||
|
||||
it('creates exactly one more notification', async () => {
|
||||
@ -110,16 +144,135 @@ describe('currentUser { notifications }', () => {
|
||||
'Hey <a href="/profile/you/al-capone" target="_blank">@al-capone</a> how do you do? One more mention to <a href="/profile/you" target="_blank">@al-capone</a>'
|
||||
const expected = {
|
||||
currentUser: {
|
||||
notifications: [
|
||||
{ read: false, post: { content: expectedContent } },
|
||||
{ read: false, post: { content: expectedContent } },
|
||||
notifications: [{
|
||||
read: false,
|
||||
post: {
|
||||
content: expectedContent,
|
||||
},
|
||||
},
|
||||
{
|
||||
read: false,
|
||||
post: {
|
||||
content: expectedContent,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
await expect(client.request(query, { read: false })).resolves.toEqual(expected)
|
||||
await expect(
|
||||
client.request(query, {
|
||||
read: false,
|
||||
}),
|
||||
).resolves.toEqual(expected)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('Hashtags', () => {
|
||||
const postId = 'p135'
|
||||
const postTitle = 'Two Hashtags'
|
||||
const postContent =
|
||||
'<p>Hey Dude, <a class="hashtag" href="/search/hashtag/Democracy">#Democracy</a> should work equal for everybody!? That seems to be the only way to have equal <a class="hashtag" href="/search/hashtag/Liberty">#Liberty</a> for everyone.</p>'
|
||||
const postWithHastagsQuery = gql `
|
||||
query($id: ID) {
|
||||
Post(id: $id) {
|
||||
tags {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
const postWithHastagsVariables = {
|
||||
id: postId,
|
||||
}
|
||||
const createPostMutation = gql `
|
||||
mutation($postId: ID, $postTitle: String!, $postContent: String!) {
|
||||
CreatePost(id: $postId, title: $postTitle, content: $postContent) {
|
||||
id
|
||||
title
|
||||
content
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
describe('authenticated', () => {
|
||||
let headers
|
||||
beforeEach(async () => {
|
||||
headers = await login({
|
||||
email: 'test@example.org',
|
||||
password: '1234',
|
||||
})
|
||||
client = new GraphQLClient(host, {
|
||||
headers,
|
||||
})
|
||||
})
|
||||
|
||||
describe('create a Post with Hashtags', () => {
|
||||
beforeEach(async () => {
|
||||
await client.request(createPostMutation, {
|
||||
postId,
|
||||
postTitle,
|
||||
postContent,
|
||||
})
|
||||
})
|
||||
|
||||
it('both Hashtags are created', async () => {
|
||||
const expected = [{
|
||||
name: 'Democracy',
|
||||
},
|
||||
{
|
||||
name: 'Liberty',
|
||||
},
|
||||
]
|
||||
await expect(
|
||||
client.request(postWithHastagsQuery, postWithHastagsVariables),
|
||||
).resolves.toEqual({
|
||||
Post: [{
|
||||
tags: expect.arrayContaining(expected),
|
||||
}, ],
|
||||
})
|
||||
})
|
||||
|
||||
describe('afterwards update the Post by removing a Hashtag, leaving a Hashtag and add a Hashtag', () => {
|
||||
// The already existing Hashtag has no class at this point.
|
||||
const updatedPostContent =
|
||||
'<p>Hey Dude, <a class="hashtag" href="/search/hashtag/Elections">#Elections</a> should work equal for everybody!? That seems to be the only way to have equal <a href="/search/hashtag/Liberty">#Liberty</a> for everyone.</p>'
|
||||
const updatePostMutation = gql `
|
||||
mutation($postId: ID!, $postTitle: String, $updatedPostContent: String) {
|
||||
UpdatePost(id: $postId, title: $postTitle, content: $updatedPostContent) {
|
||||
id
|
||||
title
|
||||
content
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
it('only one previous Hashtag and the new Hashtag exists', async () => {
|
||||
await client.request(updatePostMutation, {
|
||||
postId,
|
||||
postTitle,
|
||||
updatedPostContent,
|
||||
})
|
||||
|
||||
const expected = [{
|
||||
name: 'Elections',
|
||||
},
|
||||
{
|
||||
name: 'Liberty',
|
||||
},
|
||||
]
|
||||
await expect(
|
||||
client.request(postWithHastagsQuery, postWithHastagsVariables),
|
||||
).resolves.toEqual({
|
||||
Post: [{
|
||||
tags: expect.arrayContaining(expected),
|
||||
}, ],
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -1,21 +1,22 @@
|
||||
import cheerio from 'cheerio'
|
||||
const ID_REGEX = /\/search\/hashtag\/([\w\-.!~*'"(),]+)/g
|
||||
|
||||
export default function(content) {
|
||||
export default function (content) {
|
||||
if (!content) return []
|
||||
const $ = cheerio.load(content)
|
||||
const urls = $('.hashtag')
|
||||
// We can not search for class '.hashtag', because the classes are removed at the 'xss' middleware.
|
||||
// But we have to know, which Hashtags are removed from the content es well, so we search for the 'a' html-tag.
|
||||
const urls = $('a')
|
||||
.map((_, el) => {
|
||||
return $(el).attr('href')
|
||||
})
|
||||
.get()
|
||||
const hashtags = []
|
||||
urls.forEach(url => {
|
||||
console.log('url: ', url)
|
||||
let match
|
||||
while ((match = ID_REGEX.exec(url)) != null) {
|
||||
hashtags.push(match[1])
|
||||
}
|
||||
})
|
||||
return hashtags
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,51 @@
|
||||
import extractHashtags from './extractHashtags'
|
||||
|
||||
describe('extractHashtags', () => {
|
||||
describe('content undefined', () => {
|
||||
it('returns empty array', () => {
|
||||
expect(extractHashtags()).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
describe('searches through links', () => {
|
||||
it('finds links with and without ".hashtag" class and extracts Hashtag names', () => {
|
||||
const content =
|
||||
'<p><a class="hashtag" href="/search/hashtag/Elections">#Elections</a><a href="/search/hashtag/Democracy">#Democracy</a></p>'
|
||||
expect(extractHashtags(content)).toEqual(['Elections', 'Democracy'])
|
||||
})
|
||||
|
||||
it('ignores mentions', () => {
|
||||
const content =
|
||||
'<p>Something inspirational about <a href="/profile/u2" target="_blank">@bob-der-baumeister</a> and <a href="/profile/u3/jenny-rostock" class="mention" target="_blank">@jenny-rostock</a>.</p>'
|
||||
expect(extractHashtags(content)).toEqual([])
|
||||
})
|
||||
|
||||
describe('handles links', () => {
|
||||
it('with domains', () => {
|
||||
const content =
|
||||
'<p><a class="hashtag" href="http://localhost:3000/search/hashtag/Elections">#Elections</a><a href="http://localhost:3000/search/hashtag/Democracy">#Democracy</a></p>'
|
||||
expect(extractHashtags(content)).toEqual(['Elections', 'Democracy'])
|
||||
})
|
||||
|
||||
it('special characters', () => {
|
||||
const content =
|
||||
'<p>Something inspirational about <a href="/search/hashtag/u!*(),2" class="hashtag" target="_blank">#u!*(),2</a> and <a href="/search/hashtag/u.~-3" target="_blank">#u.~-3</a>.</p>'
|
||||
expect(extractHashtags(content)).toEqual(['u!*(),2', 'u.~-3'])
|
||||
})
|
||||
})
|
||||
|
||||
describe('does not crash if', () => {
|
||||
it('`href` contains no Hashtag name', () => {
|
||||
const content =
|
||||
'<p>Something inspirational about <a href="/search/hashtag/" target="_blank">#Democracy</a> and <a href="/search/hashtag" target="_blank">#liberty</a>.</p>'
|
||||
expect(extractHashtags(content)).toEqual([])
|
||||
})
|
||||
|
||||
it('`href` is empty or invalid', () => {
|
||||
const content =
|
||||
'<p>Something inspirational about <a href="" class="hashtag" target="_blank">@bob-der-baumeister</a> and <a href="not-a-url" target="_blank">@jenny-rostock</a>.</p>'
|
||||
expect(extractHashtags(content)).toEqual([])
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -1,7 +1,7 @@
|
||||
import cheerio from 'cheerio'
|
||||
const ID_REGEX = /\/profile\/([\w\-.!~*'"(),]+)/g
|
||||
|
||||
export default function(content) {
|
||||
export default function (content) {
|
||||
if (!content) return []
|
||||
const $ = cheerio.load(content)
|
||||
const urls = $('.mention')
|
||||
@ -11,11 +11,10 @@ export default function(content) {
|
||||
.get()
|
||||
const ids = []
|
||||
urls.forEach(url => {
|
||||
console.log('url: ', url)
|
||||
let match
|
||||
while ((match = ID_REGEX.exec(url)) != null) {
|
||||
ids.push(match[1])
|
||||
}
|
||||
})
|
||||
return ids
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,9 @@
|
||||
import extractIds from './extractMentionedUsers'
|
||||
import extractMentionedUsers from './extractMentionedUsers'
|
||||
|
||||
describe('extractIds', () => {
|
||||
describe('extractMentionedUsers', () => {
|
||||
describe('content undefined', () => {
|
||||
it('returns empty array', () => {
|
||||
expect(extractIds()).toEqual([])
|
||||
expect(extractMentionedUsers()).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
@ -11,33 +11,33 @@ describe('extractIds', () => {
|
||||
it('ignores links without .mention class', () => {
|
||||
const content =
|
||||
'<p>Something inspirational about <a href="/profile/u2" target="_blank">@bob-der-baumeister</a> and <a href="/profile/u3" target="_blank">@jenny-rostock</a>.</p>'
|
||||
expect(extractIds(content)).toEqual([])
|
||||
expect(extractMentionedUsers(content)).toEqual([])
|
||||
})
|
||||
|
||||
describe('given a link with .mention class', () => {
|
||||
it('extracts ids', () => {
|
||||
const content =
|
||||
'<p>Something inspirational about <a href="/profile/u2" class="mention" target="_blank">@bob-der-baumeister</a> and <a href="/profile/u3/jenny-rostock" class="mention" target="_blank">@jenny-rostock</a>.</p>'
|
||||
expect(extractIds(content)).toEqual(['u2', 'u3'])
|
||||
expect(extractMentionedUsers(content)).toEqual(['u2', 'u3'])
|
||||
})
|
||||
|
||||
describe('handles links', () => {
|
||||
it('with slug and id', () => {
|
||||
const content =
|
||||
'<p>Something inspirational about <a href="/profile/u2/bob-der-baumeister" class="mention" target="_blank">@bob-der-baumeister</a> and <a href="/profile/u3/jenny-rostock/" class="mention" target="_blank">@jenny-rostock</a>.</p>'
|
||||
expect(extractIds(content)).toEqual(['u2', 'u3'])
|
||||
expect(extractMentionedUsers(content)).toEqual(['u2', 'u3'])
|
||||
})
|
||||
|
||||
it('with domains', () => {
|
||||
const content =
|
||||
'<p>Something inspirational about <a href="http://localhost:3000/profile/u2/bob-der-baumeister" class="mention" target="_blank">@bob-der-baumeister</a> and <a href="http://localhost:3000//profile/u3/jenny-rostock/" class="mention" target="_blank">@jenny-rostock</a>.</p>'
|
||||
expect(extractIds(content)).toEqual(['u2', 'u3'])
|
||||
expect(extractMentionedUsers(content)).toEqual(['u2', 'u3'])
|
||||
})
|
||||
|
||||
it('special characters', () => {
|
||||
const content =
|
||||
'<p>Something inspirational about <a href="http://localhost:3000/profile/u!*(),2/bob-der-baumeister" class="mention" target="_blank">@bob-der-baumeister</a> and <a href="http://localhost:3000//profile/u.~-3/jenny-rostock/" class="mention" target="_blank">@jenny-rostock</a>.</p>'
|
||||
expect(extractIds(content)).toEqual(['u!*(),2', 'u.~-3'])
|
||||
expect(extractMentionedUsers(content)).toEqual(['u!*(),2', 'u.~-3'])
|
||||
})
|
||||
})
|
||||
|
||||
@ -45,15 +45,15 @@ describe('extractIds', () => {
|
||||
it('`href` contains no user id', () => {
|
||||
const content =
|
||||
'<p>Something inspirational about <a href="/profile" class="mention" target="_blank">@bob-der-baumeister</a> and <a href="/profile/" class="mention" target="_blank">@jenny-rostock</a>.</p>'
|
||||
expect(extractIds(content)).toEqual([])
|
||||
expect(extractMentionedUsers(content)).toEqual([])
|
||||
})
|
||||
|
||||
it('`href` is empty or invalid', () => {
|
||||
const content =
|
||||
'<p>Something inspirational about <a href="" class="mention" target="_blank">@bob-der-baumeister</a> and <a href="not-a-url" class="mention" target="_blank">@jenny-rostock</a>.</p>'
|
||||
expect(extractIds(content)).toEqual([])
|
||||
expect(extractMentionedUsers(content)).toEqual([])
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -28,16 +28,16 @@
|
||||
<ds-space />
|
||||
<div slot="footer" style="text-align: right">
|
||||
<ds-button
|
||||
class="cancel-button"
|
||||
:disabled="loading || disabled"
|
||||
ghost
|
||||
class="cancel-button"
|
||||
@click="$router.back()"
|
||||
@click.prevent="$router.back()"
|
||||
>
|
||||
{{ $t('actions.cancel') }}
|
||||
</ds-button>
|
||||
<ds-button
|
||||
icon="check"
|
||||
type="submit"
|
||||
icon="check"
|
||||
:loading="loading"
|
||||
:disabled="disabled || errors"
|
||||
primary
|
||||
@ -52,7 +52,7 @@
|
||||
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import HcEditor from '~/components/Editor'
|
||||
import HcEditor from '~/components/Editor/Editor'
|
||||
import orderBy from 'lodash/orderBy'
|
||||
import locales from '~/locales'
|
||||
import PostMutations from '~/graphql/PostMutations.js'
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { mount, createLocalVue } from '@vue/test-utils'
|
||||
import Editor from './'
|
||||
import Editor from './Editor'
|
||||
import Vuex from 'vuex'
|
||||
|
||||
import Styleguide from '@human-connection/styleguide'
|
||||
@ -36,7 +36,9 @@ describe('Editor.vue', () => {
|
||||
propsData,
|
||||
localVue,
|
||||
sync: false,
|
||||
stubs: { transition: false },
|
||||
stubs: {
|
||||
transition: false,
|
||||
},
|
||||
store,
|
||||
}))
|
||||
}
|
||||
@ -24,7 +24,7 @@
|
||||
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import HcEditor from '~/components/Editor'
|
||||
import HcEditor from '~/components/Editor/Editor'
|
||||
import PostCommentsQuery from '~/graphql/PostCommentsQuery.js'
|
||||
import CommentMutations from '~/graphql/CommentMutations.js'
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user