Merge branch 'better-still-emoji' into shigusegubu

* better-still-emoji:
  cleanup
  restructure and tests
  fix tags gluing
  fix color of reply row, fix overflow in status-popover
  fix long post fader
  change how "first" line is determined. Allow one mention in the beginning for hellthread style
  cleanup
This commit is contained in:
Henry Jameson 2021-06-12 19:56:44 +03:00
commit 95bc2532df
17 changed files with 503 additions and 131 deletions

View file

@ -40,12 +40,11 @@ const ChatListItem = {
const message = this.chat.lastMessage const message = this.chat.lastMessage
const messageEmojis = message ? message.emojis : [] const messageEmojis = message ? message.emojis : []
const isYou = message && message.account_id === this.currentUser.id const isYou = message && message.account_id === this.currentUser.id
const content = message ? (this.attachmentInfo || message.content_raw) : '' const content = message ? (this.attachmentInfo || message.content) : ''
const messagePreview = isYou ? `<i>${this.$t('chats.you')}</i> ${content}` : content const messagePreview = isYou ? `<i>${this.$t('chats.you')}</i> ${content}` : content
return { return {
summary: '', summary: '',
emojis: messageEmojis, emojis: messageEmojis,
statusnet_html: messagePreview,
raw_html: messagePreview, raw_html: messagePreview,
text: messagePreview, text: messagePreview,
attachments: [] attachments: []

View file

@ -58,8 +58,7 @@ const ChatMessage = {
return { return {
summary: '', summary: '',
emojis: this.message.emojis, emojis: this.message.emojis,
raw_html: this.message.content_raw, raw_html: this.message.content,
statusnet_html: this.message.content,
text: this.message.content, text: this.message.content,
attachments: this.message.attachments attachments: this.message.attachments
} }

View file

@ -1,6 +1,7 @@
import Vue from 'vue' import Vue from 'vue'
import { unescape, flattenDeep } from 'lodash' import { unescape, flattenDeep } from 'lodash'
import { convertHtmlToTree, getTagName, processTextForEmoji, getAttrs } from 'src/services/html_converter/html_tree_converter.service.js' import { getTagName, processTextForEmoji, getAttrs } from 'src/services/html_converter/utility.service.js'
import { convertHtmlToTree } from 'src/services/html_converter/html_tree_converter.service.js'
import { convertHtmlToLines } from 'src/services/html_converter/html_line_converter.service.js' import { convertHtmlToLines } from 'src/services/html_converter/html_line_converter.service.js'
import StillImage from 'src/components/still-image/still-image.vue' import StillImage from 'src/components/still-image/still-image.vue'
import MentionLink from 'src/components/mention_link/mention_link.vue' import MentionLink from 'src/components/mention_link/mention_link.vue'
@ -31,18 +32,12 @@ export default Vue.component('RichContent', {
required: false, required: false,
type: Boolean, type: Boolean,
default: false default: false
},
// Whether to hide last mentions (hellthreads)
hideMentions: {
required: false,
type: Boolean,
default: false
} }
}, },
// NEVER EVER TOUCH DATA INSIDE RENDER // NEVER EVER TOUCH DATA INSIDE RENDER
render (h) { render (h) {
// Pre-process HTML // Pre-process HTML
const { newHtml: html, lastMentions } = preProcessPerLine(this.html, this.greentext, this.hideMentions) const { newHtml: html, lastMentions } = preProcessPerLine(this.html, this.greentext, this.handleLinks)
const firstMentions = [] // Mentions that appear in the beginning of post body const firstMentions = [] // Mentions that appear in the beginning of post body
const lastTags = [] // Tags that appear at the end of post body const lastTags = [] // Tags that appear at the end of post body
const writtenMentions = [] // All mentions that appear in post body const writtenMentions = [] // All mentions that appear in post body
@ -126,7 +121,7 @@ export default Vue.component('RichContent', {
switch (Tag) { switch (Tag) {
case 'span': // replace images with StillImage case 'span': // replace images with StillImage
if (attrs['class'] && attrs['class'].includes('lastMentions')) { if (attrs['class'] && attrs['class'].includes('lastMentions')) {
if (firstMentions.length > 0) { if (firstMentions.length > 1) {
break break
} else { } else {
return '' return ''
@ -141,6 +136,7 @@ export default Vue.component('RichContent', {
if (attrs['class'] && attrs['class'].includes('mention')) { if (attrs['class'] && attrs['class'].includes('mention')) {
return renderMention(attrs, children, encounteredText) return renderMention(attrs, children, encounteredText)
} else if (attrs['class'] && attrs['class'].includes('hashtag')) { } else if (attrs['class'] && attrs['class'].includes('hashtag')) {
encounteredText = true
return item // We'll handle it later return item // We'll handle it later
} else { } else {
attrs.target = '_blank' attrs.target = '_blank'
@ -167,7 +163,7 @@ export default Vue.component('RichContent', {
// Handle text nodes - just add emoji // Handle text nodes - just add emoji
if (typeof item === 'string') { if (typeof item === 'string') {
const emptyText = item.trim() === '' const emptyText = item.trim() === ''
if (emptyText) return encounteredTextReverse ? item : item.trim() if (emptyText) return item
if (!encounteredTextReverse) encounteredTextReverse = true if (!encounteredTextReverse) encounteredTextReverse = true
return item return item
} else if (Array.isArray(item)) { } else if (Array.isArray(item)) {
@ -227,10 +223,12 @@ const getLinkData = (attrs, children, index) => {
* *
* @param {String} html - raw HTML to process * @param {String} html - raw HTML to process
* @param {Boolean} greentext - whether to enable greentexting or not * @param {Boolean} greentext - whether to enable greentexting or not
* @param {Boolean} handleLinks - whether to handle links or not
*/ */
export const preProcessPerLine = (html, greentext) => { export const preProcessPerLine = (html, greentext, handleLinks) => {
const lastMentions = [] const lastMentions = []
let nonEmptyIndex = 0
const newHtml = convertHtmlToLines(html).reverse().map((item, index, array) => { const newHtml = convertHtmlToLines(html).reverse().map((item, index, array) => {
// Going over each line in reverse to detect last mentions, // Going over each line in reverse to detect last mentions,
// keeping non-text stuff as-is // keeping non-text stuff as-is
@ -262,6 +260,7 @@ export const preProcessPerLine = (html, greentext) => {
const tag = getTagName(opener) const tag = getTagName(opener)
// If we have a link we probably have mentions // If we have a link we probably have mentions
if (tag === 'a') { if (tag === 'a') {
if (!handleLinks) return [opener, children, closer]
const attrs = getAttrs(opener) const attrs = getAttrs(opener)
if (attrs['class'] && attrs['class'].includes('mention')) { if (attrs['class'] && attrs['class'].includes('mention')) {
// Got mentions // Got mentions
@ -295,7 +294,7 @@ export const preProcessPerLine = (html, greentext) => {
const result = [...tree].map(process) const result = [...tree].map(process)
// Only check last (first since list is reversed) line // Only check last (first since list is reversed) line
if (hasMentions && !hasLooseText && index === 0) { if (handleLinks && hasMentions && !hasLooseText && nonEmptyIndex++ === 0) {
let mentionIndex = 0 let mentionIndex = 0
const process = (item) => { const process = (item) => {
if (Array.isArray(item)) { if (Array.isArray(item)) {

View file

@ -4,6 +4,7 @@ $status-margin: 0.75em;
.Status { .Status {
min-width: 0; min-width: 0;
white-space: normal;
&:hover { &:hover {
--_still-image-img-visibility: visible; --_still-image-img-visibility: visible;
@ -166,6 +167,7 @@ $status-margin: 0.75em;
line-height: 160%; line-height: 160%;
max-width: 100%; max-width: 100%;
align-items: stretch; align-items: stretch;
z-index: 2;
} }
& .reply-to-popover, & .reply-to-popover,
@ -211,7 +213,9 @@ $status-margin: 0.75em;
padding-right: 0.25em; padding-right: 0.25em;
} }
& .mentions-text,
& .reply-to-text { & .reply-to-text {
color: var(--faint);
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;

View file

@ -247,7 +247,7 @@
flip="horizontal" flip="horizontal"
/> />
<span <span
class="faint-link reply-to-text" class="reply-to-text"
> >
{{ $t('status.reply_to') }} {{ $t('status.reply_to') }}
</span> </span>
@ -281,7 +281,7 @@
@click.prevent="gotoOriginal(status.in_reply_to_status_id)" @click.prevent="gotoOriginal(status.in_reply_to_status_id)"
> >
<span <span
class="faint-link mentions-text" class="mentions-text"
> >
{{ $t('status.mentions') }} {{ $t('status.mentions') }}
</span> </span>

View file

@ -1,5 +1,5 @@
import fileType from 'src/services/file_type/file_type.service' import fileType from 'src/services/file_type/file_type.service'
import RichContent, { getHeadTailLinks } from 'src/components/rich_content/rich_content.jsx' import RichContent from 'src/components/rich_content/rich_content.jsx'
import MentionsLine from 'src/components/mentions_line/mentions_line.vue' import MentionsLine from 'src/components/mentions_line/mentions_line.vue'
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
@ -53,7 +53,7 @@ const StatusContent = {
// Using max-height + overflow: auto for status components resulted in false positives // Using max-height + overflow: auto for status components resulted in false positives
// very often with japanese characters, and it was very annoying. // very often with japanese characters, and it was very annoying.
tallStatus () { tallStatus () {
const lengthScore = this.status.statusnet_html.split(/<p|<br/).length + this.status.text.length / 80 const lengthScore = this.status.raw_html.split(/<p|<br/).length + this.status.text.length / 80
return lengthScore > 20 return lengthScore > 20
}, },
longSubject () { longSubject () {

View file

@ -62,7 +62,7 @@
overflow-y: hidden; overflow-y: hidden;
z-index: 1; z-index: 1;
.text-wrapper { .rich-content-wrapper {
min-height: 0; min-height: 0;
mask: mask:
linear-gradient(to top, white, transparent) bottom/100% 70px no-repeat, linear-gradient(to top, white, transparent) bottom/100% 70px no-repeat,
@ -123,5 +123,4 @@
vertical-align: middle; vertical-align: middle;
object-fit: contain; object-fit: contain;
} }
} }

View file

@ -38,7 +38,10 @@
> >
{{ $t("general.show_more") }} {{ $t("general.show_more") }}
</button> </button>
<span v-if="!hideSubjectStatus && !(singleLine && status.summary_html)"> <span
v-if="!hideSubjectStatus && !(singleLine && status.summary_html)"
class="rich-content-wrapper"
>
<MentionsLine <MentionsLine
v-if="!hideMentions && firstMentions && firstMentions.length > 0" v-if="!hideMentions && firstMentions && firstMentions.length > 0"
:mentions="firstMentions" :mentions="firstMentions"
@ -49,12 +52,11 @@
:html="status.raw_html" :html="status.raw_html"
:emoji="status.emojis" :emoji="status.emojis"
:handle-links="true" :handle-links="true"
:hide-mentions="hideMentions"
:greentext="mergedConfig.greentext" :greentext="mergedConfig.greentext"
@parseReady="setHeadTailLinks" @parseReady="setHeadTailLinks"
/> />
<MentionsLine <MentionsLine
v-if="!hideMentions && lastMentions.length > 0 && firstMentions.length === 0" v-if="!hideMentions && lastMentions.length > 0 && firstMentions.length <= 1"
:mentions="lastMentions" :mentions="lastMentions"
/> />
</span> </span>

View file

@ -267,7 +267,6 @@ export const parseStatus = (data) => {
output.type = data.reblog ? 'retweet' : 'status' output.type = data.reblog ? 'retweet' : 'status'
output.nsfw = data.sensitive output.nsfw = data.sensitive
output.statusnet_html = addEmojis(data.content, data.emojis)
output.raw_html = data.content output.raw_html = data.content
output.emojis = data.emojis output.emojis = data.emojis
@ -329,7 +328,7 @@ export const parseStatus = (data) => {
output.nsfw = data.nsfw output.nsfw = data.nsfw
} }
output.statusnet_html = data.statusnet_html output.raw_html = data.statusnet_html
output.text = data.text output.text = data.text
output.in_reply_to_status_id = data.in_reply_to_status_id output.in_reply_to_status_id = data.in_reply_to_status_id
@ -449,7 +448,7 @@ export const parseChatMessage = (message) => {
output.created_at = new Date(message.created_at) output.created_at = new Date(message.created_at)
output.chat_id = message.chat_id output.chat_id = message.chat_id
output.emojis = message.emojis output.emojis = message.emojis
output.content_raw = message.content output.content = message.content
if (message.content) { if (message.content) {
output.content = addEmojis(message.content, message.emojis) output.content = addEmojis(message.content, message.emojis)
} else { } else {

View file

@ -1,3 +1,5 @@
import { getTagName } from './utility.service.js'
/** /**
* This is a tiny purpose-built HTML parser/processor. This basically detects * This is a tiny purpose-built HTML parser/processor. This basically detects
* any type of visual newline and converts entire HTML into a array structure. * any type of visual newline and converts entire HTML into a array structure.
@ -26,12 +28,6 @@ export const convertHtmlToLines = (html) => {
let textBuffer = '' // Current line content let textBuffer = '' // Current line content
let tagBuffer = null // Current tag buffer, if null = we are not currently reading a tag let tagBuffer = null // Current tag buffer, if null = we are not currently reading a tag
// Extracts tag name from tag, i.e. <span a="b"> => span
const getTagName = (tag) => {
const result = /(?:<\/(\w+)>|<(\w+)\s?[^/]*?\/?>)/gi.exec(tag)
return result && (result[1] || result[2])
}
const flush = () => { // Processes current line buffer, adds it to output buffer and clears line buffer const flush = () => { // Processes current line buffer, adds it to output buffer and clears line buffer
if (textBuffer.trim().length > 0 && !level.some(l => ignoredTags.has(l))) { if (textBuffer.trim().length > 0 && !level.some(l => ignoredTags.has(l))) {
buffer.push({ text: textBuffer }) buffer.push({ text: textBuffer })

View file

@ -1,3 +1,5 @@
import { getTagName } from './utility.service.js'
/** /**
* This is a not-so-tiny purpose-built HTML parser/processor. This parses html * This is a not-so-tiny purpose-built HTML parser/processor. This parses html
* and converts it into a tree structure representing tag openers/closers and * and converts it into a tree structure representing tag openers/closers and
@ -93,54 +95,3 @@ export const convertHtmlToTree = (html) => {
flushText() flushText()
return buffer return buffer
} }
// Extracts tag name from tag, i.e. <span a="b"> => span
export const getTagName = (tag) => {
const result = /(?:<\/(\w+)>|<(\w+)\s?.*?\/?>)/gi.exec(tag)
return result && (result[1] || result[2])
}
export const processTextForEmoji = (text, emojis, processor) => {
const buffer = []
let textBuffer = ''
for (let i = 0; i < text.length; i++) {
const char = text[i]
if (char === ':') {
const next = text.slice(i + 1)
let found = false
for (let emoji of emojis) {
if (next.slice(0, emoji.shortcode.length + 1) === (emoji.shortcode + ':')) {
found = emoji
break
}
}
if (found) {
buffer.push(textBuffer)
textBuffer = ''
buffer.push(processor(found))
i += found.shortcode.length + 1
} else {
textBuffer += char
}
} else {
textBuffer += char
}
}
if (textBuffer) buffer.push(textBuffer)
return buffer
}
export const getAttrs = tag => {
const innertag = tag
.substring(1, tag.length - 1)
.replace(new RegExp('^' + getTagName(tag)), '')
.replace(/\/?$/, '')
.trim()
const attrs = Array.from(innertag.matchAll(/([a-z0-9-]+)(?:=("[^"]+?"|'[^']+?'))?/gi))
.map(([trash, key, value]) => [key, value])
.map(([k, v]) => {
if (!v) return [k, true]
return [k, v.substring(1, v.length - 1)]
})
return Object.fromEntries(attrs)
}

View file

@ -0,0 +1,73 @@
/**
* Extract tag name from tag opener/closer.
*
* @param {String} tag - tag string, i.e. '<a href="...">'
* @return {String} - tagname, i.e. "div"
*/
export const getTagName = (tag) => {
const result = /(?:<\/(\w+)>|<(\w+)\s?.*?\/?>)/gi.exec(tag)
return result && (result[1] || result[2])
}
/**
* Extract attributes from tag opener.
*
* @param {String} tag - tag string, i.e. '<a href="...">'
* @return {Object} - map of attributes key = attribute name, value = attribute value
* attributes without values represented as boolean true
*/
export const getAttrs = tag => {
const innertag = tag
.substring(1, tag.length - 1)
.replace(new RegExp('^' + getTagName(tag)), '')
.replace(/\/?$/, '')
.trim()
const attrs = Array.from(innertag.matchAll(/([a-z0-9-]+)(?:=("[^"]+?"|'[^']+?'))?/gi))
.map(([trash, key, value]) => [key, value])
.map(([k, v]) => {
if (!v) return [k, true]
return [k, v.substring(1, v.length - 1)]
})
return Object.fromEntries(attrs)
}
/**
* Finds shortcodes in text
*
* @param {String} text - original text to find emojis in
* @param {{ url: String, shortcode: Sring }[]} emoji - list of shortcodes to find
* @param {Function} processor - function to call on each encountered emoji,
* function is passed single object containing matching emoji ({ url, shortcode })
* return value will be inserted into resulting array instead of :shortcode:
* @return {Array} resulting array with non-emoji parts of text and whatever {processor}
* returned for emoji
*/
export const processTextForEmoji = (text, emojis, processor) => {
const buffer = []
let textBuffer = ''
for (let i = 0; i < text.length; i++) {
const char = text[i]
if (char === ':') {
const next = text.slice(i + 1)
let found = false
for (let emoji of emojis) {
if (next.slice(0, emoji.shortcode.length + 1) === (emoji.shortcode + ':')) {
found = emoji
break
}
}
if (found) {
buffer.push(textBuffer)
textBuffer = ''
buffer.push(processor(found))
i += found.shortcode.length + 1
} else {
textBuffer += char
}
} else {
textBuffer += char
}
}
if (textBuffer) buffer.push(textBuffer)
return buffer
}

View file

@ -0,0 +1,357 @@
import { shallowMount, createLocalVue } from '@vue/test-utils'
import RichContent from 'src/components/rich_content/rich_content.jsx'
const localVue = createLocalVue()
const makeMention = (who) => `<span class="h-card"><a class="u-url mention" href="https://fake.tld/@${who}">@<span>${who}</span></a></span>`
const stubMention = (who) => `<span class="h-card"><mentionlink-stub url="https://fake.tld/@${who}" content="@<span>${who}</span>"></mentionlink-stub></span>`
const lastMentions = (...data) => `<span class="lastMentions">${data.join('')}</span>`
const p = (...data) => `<p>${data.join('')}</p>`
const compwrap = (...data) => `<span class="RichContent">${data.join('')}</span>`
const removedMentionSpan = '<span class="h-card"></span>'
describe('RichContent', () => {
it('renders simple post without exploding', () => {
const html = p('Hello world!')
const wrapper = shallowMount(RichContent, {
localVue,
propsData: {
handleLinks: true,
greentext: true,
emoji: [],
html
}
})
expect(wrapper.html()).to.eql(compwrap(html))
})
it('removes mentions from the beginning of post', () => {
const html = p(
makeMention('John'),
' how are you doing thoday?'
)
const expected = p(
removedMentionSpan,
'how are you doing thoday?'
)
const wrapper = shallowMount(RichContent, {
localVue,
propsData: {
handleLinks: true,
greentext: true,
emoji: [],
html
}
})
expect(wrapper.html()).to.eql(compwrap(expected))
})
it('removes mentions from the end of the hellpost (<p>)', () => {
const html = [
p('How are you doing today, fine gentlemen?'),
p(
makeMention('John'),
makeMention('Josh'),
makeMention('Jeremy')
)
].join('')
const expected = [
p(
'How are you doing today, fine gentlemen?'
),
// TODO fix this extra line somehow?
p()
].join('')
const wrapper = shallowMount(RichContent, {
localVue,
propsData: {
handleLinks: true,
greentext: true,
emoji: [],
html
}
})
expect(wrapper.html()).to.eql(compwrap(expected))
})
it('removes mentions from the end of the hellpost (<br>)', () => {
const html = [
'How are you doing today, fine gentlemen?',
[
makeMention('John'),
makeMention('Josh'),
makeMention('Jeremy')
].join('')
].join('<br>')
const expected = [
'How are you doing today, fine gentlemen?',
// TODO fix this extra line somehow?
'<br>'
].join('')
const wrapper = shallowMount(RichContent, {
localVue,
propsData: {
handleLinks: true,
greentext: true,
emoji: [],
html
}
})
expect(wrapper.html()).to.eql(compwrap(expected))
})
it('removes mentions from the end of the hellpost (\\n)', () => {
const html = [
'How are you doing today, fine gentlemen?',
[
makeMention('John'),
makeMention('Josh'),
makeMention('Jeremy')
].join('')
].join('\n')
const expected = [
'How are you doing today, fine gentlemen?',
// TODO fix this extra line somehow?
''
].join('\n')
const wrapper = shallowMount(RichContent, {
localVue,
propsData: {
handleLinks: true,
greentext: true,
emoji: [],
html
}
})
expect(wrapper.html()).to.eql(compwrap(expected))
})
it('Does not remove mentions in the middle or at the end of text string', () => {
const html = [
[
makeMention('Jack'),
'let\'s meet up with ',
makeMention('Janet')
].join(''),
[
'cc: ',
makeMention('John'),
makeMention('Josh'),
makeMention('Jeremy')
].join('')
].join('\n')
const expected = [
[
removedMentionSpan,
'let\'s meet up with ',
stubMention('Janet')
].join(''),
[
'cc: ',
stubMention('John'),
stubMention('Josh'),
stubMention('Jeremy')
].join('')
].join('\n')
const wrapper = shallowMount(RichContent, {
localVue,
propsData: {
handleLinks: true,
greentext: true,
emoji: [],
html
}
})
expect(wrapper.html()).to.eql(compwrap(expected))
})
it('removes mentions from the end if there\'s only one first mention', () => {
const html = [
p(
makeMention('Todd'),
'so anyway you are wrong'
),
p(
makeMention('Tom'),
makeMention('Trace'),
makeMention('Theodor')
)
].join('')
const expected = [
p(
removedMentionSpan,
'so anyway you are wrong'
),
// TODO fix this extra line somehow?
p()
].join('')
const wrapper = shallowMount(RichContent, {
localVue,
propsData: {
handleLinks: true,
greentext: true,
emoji: [],
html
}
})
expect(wrapper.html()).to.eql(compwrap(expected))
})
it('does not remove mentions from the end if there\'s more than one first mention', () => {
const html = [
p(
makeMention('Zacharie'),
makeMention('Zinaide'),
'you guys have cool names, and so do these guys: '
),
p(
makeMention('Watson'),
makeMention('Wallace'),
makeMention('Wakamoto')
)
].join('')
const expected = [
p(
removedMentionSpan,
removedMentionSpan,
'you guys have cool names, and so do these guys: '
),
p(
lastMentions(
stubMention('Watson'),
stubMention('Wallace'),
stubMention('Wakamoto')
)
)
].join('')
const wrapper = shallowMount(RichContent, {
localVue,
propsData: {
handleLinks: true,
greentext: true,
emoji: [],
html
}
})
expect(wrapper.html()).to.eql(compwrap(expected))
})
it('Does not touch links if link handling is disabled', () => {
const html = [
[
makeMention('Jack'),
'let\'s meet up with ',
makeMention('Janet')
].join(''),
[
makeMention('John'),
makeMention('Josh'),
makeMention('Jeremy')
].join('')
].join('\n')
const wrapper = shallowMount(RichContent, {
localVue,
propsData: {
handleLinks: false,
greentext: true,
emoji: [],
html
}
})
expect(wrapper.html()).to.eql(compwrap(html))
})
it('Adds greentext and cyantext to the post', () => {
const html = [
'&gt;preordering videogames',
'&gt;any year'
].join('\n')
const expected = [
'<span class="greentext">&gt;preordering videogames</span>',
'<span class="greentext">&gt;any year</span>'
].join('\n')
const wrapper = shallowMount(RichContent, {
localVue,
propsData: {
handleLinks: false,
greentext: true,
emoji: [],
html
}
})
expect(wrapper.html()).to.eql(compwrap(expected))
})
it('Does not add greentext and cyantext if setting is set to false', () => {
const html = [
'&gt;preordering videogames',
'&gt;any year'
].join('\n')
const wrapper = shallowMount(RichContent, {
localVue,
propsData: {
handleLinks: false,
greentext: false,
emoji: [],
html
}
})
expect(wrapper.html()).to.eql(compwrap(html))
})
it('Adds emoji to post', () => {
const html = p('Ebin :DDDD :spurdo:')
const expected = p(
'Ebin :DDDD ',
'<anonymous-stub alt=":spurdo:" src="about:blank" title=":spurdo:" class="emoji img"></anonymous-stub>'
)
const wrapper = shallowMount(RichContent, {
localVue,
propsData: {
handleLinks: false,
greentext: false,
emoji: [{ url: 'about:blank', shortcode: 'spurdo' }],
html
}
})
expect(wrapper.html()).to.eql(compwrap(expected))
})
it('Doesn\'t add nonexistent emoji to post', () => {
const html = p('Lol :lol:')
const wrapper = shallowMount(RichContent, {
localVue,
propsData: {
handleLinks: false,
greentext: false,
emoji: [],
html
}
})
expect(wrapper.html()).to.eql(compwrap(html))
})
})

View file

@ -23,7 +23,6 @@ const makeMockStatusQvitter = (overrides = {}) => {
repeat_num: 0, repeat_num: 0,
repeated: false, repeated: false,
statusnet_conversation_id: '16300488', statusnet_conversation_id: '16300488',
statusnet_html: '<p>haha benis</p>',
summary: null, summary: null,
tags: [], tags: [],
text: 'haha benis', text: 'haha benis',
@ -233,14 +232,6 @@ describe('API Entities normalizer', () => {
expect(parsedRepeat).to.have.deep.property('retweeted_status.id', 'deadbeef') expect(parsedRepeat).to.have.deep.property('retweeted_status.id', 'deadbeef')
}) })
it('adds emojis to post content', () => {
const post = makeMockStatusMasto({ emojis: makeMockEmojiMasto(), content: 'Makes you think :thinking:' })
const parsedPost = parseStatus(post)
expect(parsedPost).to.have.property('statusnet_html').that.contains('<img')
})
it('adds emojis to subject line', () => { it('adds emojis to subject line', () => {
const post = makeMockStatusMasto({ emojis: makeMockEmojiMasto(), spoiler_text: 'CW: 300 IQ :thinking:' }) const post = makeMockStatusMasto({ emojis: makeMockEmojiMasto(), spoiler_text: 'CW: 300 IQ :thinking:' })

View file

@ -2,7 +2,7 @@ import { convertHtmlToLines } from 'src/services/html_converter/html_line_conver
const mapOnlyText = (processor) => (input) => input.text ? processor(input.text) : input const mapOnlyText = (processor) => (input) => input.text ? processor(input.text) : input
describe('TinyPostHTMLProcessor', () => { describe('html_line_converter', () => {
describe('with processor that keeps original line should not make any changes to HTML when', () => { describe('with processor that keeps original line should not make any changes to HTML when', () => {
const processorKeep = (line) => line const processorKeep = (line) => line
it('fed with regular HTML with newlines', () => { it('fed with regular HTML with newlines', () => {

View file

@ -1,6 +1,6 @@
import { convertHtmlToTree, processTextForEmoji, getAttrs } from 'src/services/html_converter/html_tree_converter.service.js' import { convertHtmlToTree } from 'src/services/html_converter/html_tree_converter.service.js'
describe('MiniHtmlConverter', () => { describe('html_tree_converter', () => {
describe('convertHtmlToTree', () => { describe('convertHtmlToTree', () => {
it('converts html into a tree structure', () => { it('converts html into a tree structure', () => {
const input = '1 <p>2</p> <b>3<img src="a">4</b>5' const input = '1 <p>2</p> <b>3<img src="a">4</b>5'
@ -129,38 +129,4 @@ describe('MiniHtmlConverter', () => {
]) ])
}) })
}) })
describe('processTextForEmoji', () => {
it('processes all emoji in text', () => {
const input = 'Hello from finland! :lol: We have best water! :lmao:'
const emojis = [
{ shortcode: 'lol', src: 'LOL' },
{ shortcode: 'lmao', src: 'LMAO' }
]
const processor = ({ shortcode, src }) => ({ shortcode, src })
expect(processTextForEmoji(input, emojis, processor)).to.eql([
'Hello from finland! ',
{ shortcode: 'lol', src: 'LOL' },
' We have best water! ',
{ shortcode: 'lmao', src: 'LMAO' }
])
})
it('leaves text as is', () => {
const input = 'Number one: that\'s terror'
const emojis = []
const processor = ({ shortcode, src }) => ({ shortcode, src })
expect(processTextForEmoji(input, emojis, processor)).to.eql([
'Number one: that\'s terror'
])
})
})
describe('getAttrs', () => {
it('extracts arguments from tag', () => {
const input = '<img src="boop" cool ebin=\'true\'>'
const output = { src: 'boop', cool: true, ebin: 'true' }
expect(getAttrs(input)).to.eql(output)
})
})
}) })

View file

@ -0,0 +1,37 @@
import { processTextForEmoji, getAttrs } from 'src/services/html_converter/utility.service.js'
describe('html_converter utility', () => {
describe('processTextForEmoji', () => {
it('processes all emoji in text', () => {
const input = 'Hello from finland! :lol: We have best water! :lmao:'
const emojis = [
{ shortcode: 'lol', src: 'LOL' },
{ shortcode: 'lmao', src: 'LMAO' }
]
const processor = ({ shortcode, src }) => ({ shortcode, src })
expect(processTextForEmoji(input, emojis, processor)).to.eql([
'Hello from finland! ',
{ shortcode: 'lol', src: 'LOL' },
' We have best water! ',
{ shortcode: 'lmao', src: 'LMAO' }
])
})
it('leaves text as is', () => {
const input = 'Number one: that\'s terror'
const emojis = []
const processor = ({ shortcode, src }) => ({ shortcode, src })
expect(processTextForEmoji(input, emojis, processor)).to.eql([
'Number one: that\'s terror'
])
})
})
describe('getAttrs', () => {
it('extracts arguments from tag', () => {
const input = '<img src="boop" cool ebin=\'true\'>'
const output = { src: 'boop', cool: true, ebin: 'true' }
expect(getAttrs(input)).to.eql(output)
})
})
})