Merge remote-tracking branch 'origin/develop' into migrate/vuex-to-pinia

This commit is contained in:
Henry Jameson 2025-01-30 18:08:05 +02:00
commit 58e18d48df
489 changed files with 31167 additions and 9871 deletions

View file

@ -0,0 +1,276 @@
import Gallery from 'src/components/gallery/gallery.vue'
describe('Gallery', () => {
let local
it('attachments is falsey', () => {
local = { attachments: false }
expect(Gallery.computed.rows.call(local)).to.eql([])
local = { attachments: null }
expect(Gallery.computed.rows.call(local)).to.eql([])
local = { attachments: undefined }
expect(Gallery.computed.rows.call(local)).to.eql([])
})
it('no attachments', () => {
local = { attachments: [] }
expect(Gallery.computed.rows.call(local)).to.eql([])
})
it('one audio attachment', () => {
local = {
attachments: [
{ mimetype: 'audio/mpeg' }
]
}
expect(Gallery.computed.rows.call(local)).to.eql([
{ audio: true, items: [{ mimetype: 'audio/mpeg' }] }
])
})
it('one image attachment', () => {
local = {
attachments: [
{ mimetype: 'image/png' }
]
}
expect(Gallery.computed.rows.call(local)).to.eql([
{ items: [{ mimetype: 'image/png' }] }
])
})
it('one audio attachment and one image attachment', () => {
local = {
attachments: [
{ mimetype: 'audio/mpeg' },
{ mimetype: 'image/png' }
]
}
expect(Gallery.computed.rows.call(local)).to.eql([
{ audio: true, items: [{ mimetype: 'audio/mpeg' }] },
{ items: [{ mimetype: 'image/png' }] }
])
})
it('has "size" key set to "hide"', () => {
let local
local = {
attachments: [
{ mimetype: 'audio/mpeg' }
],
size: 'hide'
}
expect(Gallery.computed.rows.call(local)).to.eql([
{ minimal: true, items: [{ mimetype: 'audio/mpeg' }] }
])
local = {
attachments: [
{ mimetype: 'image/jpg' },
{ mimetype: 'image/png' },
{ mimetype: 'image/jpg' },
{ mimetype: 'audio/mpeg' },
{ mimetype: 'image/png' },
{ mimetype: 'audio/mpeg' },
{ mimetype: 'image/jpg' },
{ mimetype: 'image/png' },
{ mimetype: 'image/jpg' }
],
size: 'hide'
}
// When defining `size: hide`, the `items` aren't
// grouped and `audio` isn't set
expect(Gallery.computed.rows.call(local)).to.eql([
{ minimal: true, items: [{ mimetype: 'image/jpg' }] },
{ minimal: true, items: [{ mimetype: 'image/png' }] },
{ minimal: true, items: [{ mimetype: 'image/jpg' }] },
{ minimal: true, items: [{ mimetype: 'audio/mpeg' }] },
{ minimal: true, items: [{ mimetype: 'image/png' }] },
{ minimal: true, items: [{ mimetype: 'audio/mpeg' }] },
{ minimal: true, items: [{ mimetype: 'image/jpg' }] },
{ minimal: true, items: [{ mimetype: 'image/png' }] },
{ minimal: true, items: [{ mimetype: 'image/jpg' }] }
])
})
// types other than image or audio should be `minimal`
it('non-image/audio', () => {
let local
local = {
attachments: [
{ mimetype: 'plain/text' }
]
}
expect(Gallery.computed.rows.call(local)).to.eql([
{ minimal: true, items: [{ mimetype: 'plain/text' }] }
])
// No grouping of non-image/audio items
local = {
attachments: [
{ mimetype: 'plain/text' },
{ mimetype: 'plain/text' },
{ mimetype: 'plain/text' }
]
}
expect(Gallery.computed.rows.call(local)).to.eql([
{ minimal: true, items: [{ mimetype: 'plain/text' }] },
{ minimal: true, items: [{ mimetype: 'plain/text' }] },
{ minimal: true, items: [{ mimetype: 'plain/text' }] }
])
local = {
attachments: [
{ mimetype: 'image/png' },
{ mimetype: 'plain/text' },
{ mimetype: 'image/jpg' },
{ mimetype: 'audio/mpeg' }
]
}
// NOTE / TODO: When defining `size: hide`, the `items` aren't
// grouped and `audio` isn't set
expect(Gallery.computed.rows.call(local)).to.eql([
{ items: [{ mimetype: 'image/png' }] },
{ minimal: true, items: [{ mimetype: 'plain/text' }] },
{ items: [{ mimetype: 'image/jpg' }] },
{ audio: true, items: [{ mimetype: 'audio/mpeg' }] }
])
})
it('mixed attachments', () => {
local = {
attachments: [
{ mimetype: 'audio/mpeg' },
{ mimetype: 'image/png' },
{ mimetype: 'audio/mpeg' },
{ mimetype: 'image/jpg' },
{ mimetype: 'image/png' },
{ mimetype: 'image/jpg' },
{ mimetype: 'image/jpg' }
]
}
expect(Gallery.computed.rows.call(local)).to.eql([
{ audio: true, items: [{ mimetype: 'audio/mpeg' }] },
{ items: [{ mimetype: 'image/png' }] },
{ audio: true, items: [{ mimetype: 'audio/mpeg' }] },
{ items: [{ mimetype: 'image/jpg' }, { mimetype: 'image/png' }, { mimetype: 'image/jpg' }, { mimetype: 'image/jpg' }] }
])
local = {
attachments: [
{ mimetype: 'image/jpg' },
{ mimetype: 'image/png' },
{ mimetype: 'image/jpg' },
{ mimetype: 'image/jpg' },
{ mimetype: 'audio/mpeg' },
{ mimetype: 'image/png' },
{ mimetype: 'audio/mpeg' }
]
}
expect(Gallery.computed.rows.call(local)).to.eql([
{ items: [{ mimetype: 'image/jpg' }, { mimetype: 'image/png' }, { mimetype: 'image/jpg' }] },
{ items: [{ mimetype: 'image/jpg' }] },
{ audio: true, items: [{ mimetype: 'audio/mpeg' }] },
{ items: [{ mimetype: 'image/png' }] },
{ audio: true, items: [{ mimetype: 'audio/mpeg' }] }
])
local = {
attachments: [
{ mimetype: 'image/jpg' },
{ mimetype: 'image/png' },
{ mimetype: 'image/jpg' },
{ mimetype: 'image/jpg' },
{ mimetype: 'image/png' },
{ mimetype: 'image/png' },
{ mimetype: 'image/jpg' }
]
}
// Group by three-per-row, unless there's one dangling, then stick it on the end of the last row
// https://git.pleroma.social/pleroma/pleroma-fe/-/merge_requests/1785#note_98514
expect(Gallery.computed.rows.call(local)).to.eql([
{ items: [{ mimetype: 'image/jpg' }, { mimetype: 'image/png' }, { mimetype: 'image/jpg' }] },
{ items: [{ mimetype: 'image/jpg' }, { mimetype: 'image/png' }, { mimetype: 'image/png' }, { mimetype: 'image/jpg' }] }
])
local = {
attachments: [
{ mimetype: 'image/jpg' },
{ mimetype: 'image/png' },
{ mimetype: 'image/jpg' },
{ mimetype: 'image/jpg' },
{ mimetype: 'image/png' },
{ mimetype: 'image/png' },
{ mimetype: 'image/jpg' },
{ mimetype: 'image/png' }
]
}
expect(Gallery.computed.rows.call(local)).to.eql([
{ items: [{ mimetype: 'image/jpg' }, { mimetype: 'image/png' }, { mimetype: 'image/jpg' }] },
{ items: [{ mimetype: 'image/jpg' }, { mimetype: 'image/png' }, { mimetype: 'image/png' }] },
{ items: [{ mimetype: 'image/jpg' }, { mimetype: 'image/png' }] }
])
})
it('does not do grouping when grid is set', () => {
const attachments = [
{ mimetype: 'audio/mpeg' },
{ mimetype: 'image/png' },
{ mimetype: 'audio/mpeg' },
{ mimetype: 'image/jpg' },
{ mimetype: 'image/png' },
{ mimetype: 'image/jpg' },
{ mimetype: 'image/jpg' }
]
local = { grid: true, attachments }
expect(Gallery.computed.rows.call(local)).to.eql([
{ grid: true, items: attachments }
])
})
it('limit is set', () => {
const attachments = [
{ mimetype: 'audio/mpeg' },
{ mimetype: 'image/png' },
{ mimetype: 'image/jpg' },
{ mimetype: 'audio/mpeg' },
{ mimetype: 'image/jpg' }
]
let local
local = { attachments, limit: 2 }
expect(Gallery.computed.rows.call(local)).to.eql([
{ audio: true, items: [{ mimetype: 'audio/mpeg' }] },
{ items: [{ mimetype: 'image/png' }] }
])
local = { attachments, limit: 3 }
expect(Gallery.computed.rows.call(local)).to.eql([
{ audio: true, items: [{ mimetype: 'audio/mpeg' }] },
{ items: [{ mimetype: 'image/png' }, { mimetype: 'image/jpg' }] }
])
local = { attachments, limit: 4 }
expect(Gallery.computed.rows.call(local)).to.eql([
{ audio: true, items: [{ mimetype: 'audio/mpeg' }] },
{ items: [{ mimetype: 'image/png' }, { mimetype: 'image/jpg' }] },
{ audio: true, items: [{ mimetype: 'audio/mpeg' }] }
])
})
})

View file

@ -539,7 +539,6 @@ describe('RichContent', () => {
`,
props: ['handleLinks', 'attentions', 'vhtml']
}
console.log(1)
const ptest = (handleLinks, vhtml) => {
const t0 = performance.now()
@ -562,11 +561,11 @@ describe('RichContent', () => {
return `Mount: ${t1 - t0}ms, destroy: ${t2 - t1}ms, avg ${(t1 - t0) / amount}ms - ${(t2 - t1) / amount}ms per item`
}
console.log(`${amount} items with links handling:`)
console.log(ptest(true))
console.log(`${amount} items without links handling:`)
console.log(ptest(false))
console.log(`${amount} items plain v-html:`)
console.log(ptest(false, true))
console.debug(`${amount} items with links handling:`)
console.debug(ptest(true))
console.debug(`${amount} items without links handling:`)
console.debug(ptest(false))
console.debug(`${amount} items plain v-html:`)
console.debug(ptest(false, true))
})
})

View file

@ -74,7 +74,7 @@ describe('The serverSideStorage module', () => {
})
})
it('should reset local timestamp to remote if contents are the same', () => {
it.only('should reset local timestamp to remote if contents are the same', () => {
const state = {
...cloneDeep(defaultState),
cache: null
@ -176,33 +176,33 @@ describe('The serverSideStorage module', () => {
})
describe('_getRecentData', () => {
it('should handle nulls correctly', () => {
expect(_getRecentData(null, null)).to.eql({ recent: null, stale: null, needUpload: true })
expect(_getRecentData(null, null, true)).to.eql({ recent: null, stale: null, needUpload: true })
})
it('doesn\'t choke on invalid data', () => {
expect(_getRecentData({ a: 1 }, { b: 2 })).to.eql({ recent: null, stale: null, needUpload: true })
expect(_getRecentData({ a: 1 }, { b: 2 }, true)).to.eql({ recent: null, stale: null, needUpload: true })
})
it('should prefer the valid non-null correctly, needUpload works properly', () => {
const nonNull = { _version: VERSION, _timestamp: 1 }
expect(_getRecentData(nonNull, null)).to.eql({ recent: nonNull, stale: null, needUpload: true })
expect(_getRecentData(null, nonNull)).to.eql({ recent: nonNull, stale: null, needUpload: false })
expect(_getRecentData(nonNull, null, true)).to.eql({ recent: nonNull, stale: null, needUpload: true })
expect(_getRecentData(null, nonNull, true)).to.eql({ recent: nonNull, stale: null, needUpload: false })
})
it('should prefer the one with higher timestamp', () => {
const a = { _version: VERSION, _timestamp: 1 }
const b = { _version: VERSION, _timestamp: 2 }
expect(_getRecentData(a, b)).to.eql({ recent: b, stale: a, needUpload: false })
expect(_getRecentData(b, a)).to.eql({ recent: b, stale: a, needUpload: false })
expect(_getRecentData(a, b, true)).to.eql({ recent: b, stale: a, needUpload: false })
expect(_getRecentData(b, a, true)).to.eql({ recent: b, stale: a, needUpload: false })
})
it('case where both are same', () => {
const a = { _version: VERSION, _timestamp: 3 }
const b = { _version: VERSION, _timestamp: 3 }
expect(_getRecentData(a, b)).to.eql({ recent: b, stale: a, needUpload: false })
expect(_getRecentData(b, a)).to.eql({ recent: b, stale: a, needUpload: false })
expect(_getRecentData(a, b, true)).to.eql({ recent: b, stale: a, needUpload: false })
expect(_getRecentData(b, a, true)).to.eql({ recent: b, stale: a, needUpload: false })
})
})

View file

@ -77,24 +77,6 @@ describe('Statuses module', () => {
expect(state.timelines.public.newStatusCount).to.equal(0)
})
it('removes statuses by tag on deletion', () => {
const state = defaultState()
const status = makeMockStatus({ id: '1' })
const otherStatus = makeMockStatus({ id: '3' })
status.uri = 'xxx'
const deletion = makeMockStatus({ id: '2', type: 'deletion' })
deletion.text = 'Dolus deleted notice {{tag:gs.smuglo.li,2016-11-18:noticeId=1038007:objectType=note}}.'
deletion.uri = 'xxx'
mutations.addNewStatuses(state, { statuses: [status, otherStatus], showImmediately: true, timeline: 'public' })
mutations.addNewStatuses(state, { statuses: [deletion], showImmediately: true, timeline: 'public' })
expect(state.allStatuses).to.eql([otherStatus])
expect(state.timelines.public.statuses).to.eql([otherStatus])
expect(state.timelines.public.visibleStatuses).to.eql([otherStatus])
expect(state.timelines.public.maxId).to.eql('3')
})
it('does not update the maxId when the noIdUpdate flag is set', () => {
const state = defaultState()
const status = makeMockStatus({ id: '1' })
@ -315,62 +297,4 @@ describe('Statuses module', () => {
expect(state.timelines.user.userId).to.eql(123)
})
})
describe('notifications', () => {
it('removes a notification when the notice gets removed', () => {
const user = { id: '1' }
const state = defaultState()
const status = makeMockStatus({ id: '1' })
const otherStatus = makeMockStatus({ id: '3' })
const mentionedStatus = makeMockStatus({ id: '2' })
mentionedStatus.attentions = [user]
mentionedStatus.uri = 'xxx'
otherStatus.attentions = [user]
const deletion = makeMockStatus({ id: '4', type: 'deletion' })
deletion.text = 'Dolus deleted notice {{tag:gs.smuglo.li,2016-11-18:noticeId=1038007:objectType=note}}.'
deletion.uri = 'xxx'
const newNotificationSideEffects = () => {}
mutations.addNewStatuses(state, { statuses: [status, otherStatus], user })
mutations.addNewNotifications(
state,
{
notifications: [{
from_profile: { id: '2' },
id: '998',
type: 'mention',
status: otherStatus,
action: otherStatus,
seen: false
}],
newNotificationSideEffects
})
expect(state.notifications.data.length).to.eql(1)
mutations.addNewNotifications(
state,
{
notifications: [{
from_profile: { id: '2' },
id: '999',
type: 'mention',
status: mentionedStatus,
action: mentionedStatus,
seen: false
}],
newNotificationSideEffects
})
mutations.addNewStatuses(state, { statuses: [mentionedStatus], user })
expect(state.allStatuses.length).to.eql(3)
expect(state.notifications.data.length).to.eql(2)
expect(state.notifications.data[1].status).to.eql(mentionedStatus)
expect(state.notifications.data[1].action).to.eql(mentionedStatus)
expect(state.notifications.data[1].type).to.eql('mention')
mutations.addNewStatuses(state, { statuses: [deletion], user })
expect(state.allStatuses.length).to.eql(2)
expect(state.notifications.data.length).to.eql(1)
})
})
})

View file

@ -78,5 +78,11 @@ describe('MatcherService', () => {
expect(MatcherService.extractTagFromUrl(url)).to.eql(false)
})
it('should return tag name from non-ascii tags', () => {
const url = encodeURI('https://website.com/tag/喵喵喵')
expect(MatcherService.extractTagFromUrl(url)).to.eql('喵喵喵')
})
})
})

View file

@ -5,28 +5,28 @@ describe('NotificationUtils', () => {
it('should return sorted notifications with configured types', () => {
const store = {
state: {
statuses: {
notifications: {
data: [
{
id: 1,
action: { id: '1' },
type: 'like'
},
{
id: 2,
action: { id: '2' },
type: 'mention'
},
{
id: 3,
action: { id: '3' },
type: 'repeat'
}
]
}
},
config: {
notifications: {
data: [
{
id: 1,
action: { id: '1' },
type: 'like'
},
{
id: 2,
action: { id: '2' },
type: 'mention'
},
{
id: 3,
action: { id: '3' },
type: 'repeat'
}
]
}
},
getters: {
mergedConfig: {
notificationVisibility: {
likes: true,
repeats: true,
@ -55,23 +55,23 @@ describe('NotificationUtils', () => {
it('should return only notifications not marked as seen', () => {
const store = {
state: {
statuses: {
notifications: {
data: [
{
action: { id: '1' },
type: 'like',
seen: false
},
{
action: { id: '2' },
type: 'mention',
seen: true
}
]
}
},
config: {
notifications: {
data: [
{
action: { id: '1' },
type: 'like',
seen: false
},
{
action: { id: '2' },
type: 'mention',
seen: true
}
]
}
},
getters: {
mergedConfig: {
notificationVisibility: {
likes: true,
repeats: true,

View file

@ -0,0 +1,40 @@
import { deserialize } from 'src/services/theme_data/iss_deserializer.js'
import { serialize } from 'src/services/theme_data/iss_serializer.js'
const componentsContext = require.context('src', true, /\.style.js(on)?$/)
describe('ISS (de)serialization', () => {
componentsContext.keys().forEach(key => {
const component = componentsContext(key).default
it(`(De)serialization of component ${component.name} works`, () => {
const normalized = component.defaultRules.map(x => ({ component: component.name, ...x }))
const serialized = serialize(normalized)
const deserialized = deserialize(serialized)
// for some reason comparing objects directly fails the assert
expect(JSON.stringify(deserialized, null, 2)).to.equal(JSON.stringify(normalized, null, 2))
})
})
/*
// Debug snippet
const onlyComponent = componentsContext('./components/panel_header.style.js').default
it.only(`(De)serialization of component ${onlyComponent.name} works`, () => {
const normalized = onlyComponent.defaultRules.map(x => ({ component: onlyComponent.name, ...x }))
console.debug('BEGIN INPUT ================')
console.debug(normalized)
console.debug('END INPUT ==================')
const serialized = serialize(normalized)
console.debug('BEGIN SERIAL ===============')
console.debug(serialized)
console.debug('END SERIAL =================')
const deserialized = deserialize(serialized)
console.debug('BEGIN DESERIALIZED =========')
console.debug(serialized)
console.debug('END DESERIALIZED ===========')
// for some reason comparing objects directly fails the assert
expect(JSON.stringify(deserialized, null, 2)).to.equal(JSON.stringify(normalized, null, 2))
})
/* */
})

View file

@ -0,0 +1,150 @@
// import { topoSort } from 'src/services/theme_data/theme_data.service.js'
import {
getAllPossibleCombinations
} from 'src/services/theme_data/iss_utils.js'
import {
init
} from 'src/services/theme_data/theme_data_3.service.js'
import {
basePaletteKeys
} from 'src/services/theme_data/theme2_to_theme3.js'
describe('Theme Data 3', () => {
describe('getAllPossibleCombinations', () => {
it('test simple 3 values case', () => {
const out = getAllPossibleCombinations([1, 2, 3]).map(x => x.sort((a, b) => a - b))
expect(out).to.eql([
[1], [2], [3],
[1, 2], [1, 3], [2, 3],
[1, 2, 3]
])
})
it('test simple 4 values case', () => {
const out = getAllPossibleCombinations([1, 2, 3, 4]).map(x => x.sort((a, b) => a - b))
expect(out).to.eql([
[1], [2], [3], [4],
[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4],
[1, 2, 3], [1, 2, 4], [1, 3, 4], [2, 3, 4],
[1, 2, 3, 4]
])
})
it('test massive 5 values case, using strings', () => {
const out = getAllPossibleCombinations(['a', 'b', 'c', 'd', 'e']).map(x => x.sort((a, b) => a - b))
expect(out).to.eql([
// 1
['a'], ['b'], ['c'], ['d'], ['e'],
// 2
['a', 'b'], ['a', 'c'], ['a', 'd'], ['a', 'e'],
['b', 'c'], ['b', 'd'], ['b', 'e'],
['c', 'd'], ['c', 'e'],
['d', 'e'],
// 3
['a', 'b', 'c'], ['a', 'b', 'd'], ['a', 'b', 'e'],
['a', 'c', 'd'], ['a', 'c', 'e'],
['a', 'd', 'e'],
['b', 'c', 'd'], ['b', 'c', 'e'],
['b', 'd', 'e'],
['c', 'd', 'e'],
// 4
['a', 'b', 'c', 'd'], ['a', 'b', 'c', 'e'],
['a', 'b', 'd', 'e'],
['a', 'c', 'd', 'e'],
['b', 'c', 'd', 'e'],
// 5
['a', 'b', 'c', 'd', 'e']
])
})
})
describe('init', function () {
this.timeout(5000)
it('Test initialization without anything', () => {
const out = init({ inputRuleset: [], ultimateBackgroundColor: '#DEADAF' })
expect(out).to.have.property('eager')
expect(out).to.have.property('lazy')
expect(out).to.have.property('staticVars')
expect(out.lazy).to.be.an('array')
expect(out.lazy).to.have.lengthOf.above(1)
expect(out.eager).to.be.an('array')
expect(out.eager).to.have.lengthOf.above(1)
expect(out.staticVars).to.be.an('object')
// check backwards compat/generic stuff
basePaletteKeys.forEach(key => {
expect(out.staticVars).to.have.property(key)
})
})
it('Test initialization with a basic palette', () => {
const out = init({
inputRuleset: [{
component: 'Root',
directives: {
'--bg': 'color | #008080',
'--fg': 'color | #00C0A0'
}
}],
ultimateBackgroundColor: '#DEADAF'
})
expect(out.staticVars).to.have.property('bg').equal('#008080')
expect(out.staticVars).to.have.property('fg').equal('#00C0A0')
const panelRule = out.eager.filter(x => {
if (x.component !== 'Panel') return false
return true
})[0]
expect(panelRule).to.have.nested.deep.property('dynamicVars.stacked', { r: 0, g: 128, b: 128 })
})
it('Test initialization with opacity', () => {
const out = init({
inputRuleset: [{
component: 'Root',
directives: {
'--bg': 'color | #008080'
}
}, {
component: 'Panel',
directives: {
opacity: 0.5
}
}],
ultimateBackgroundColor: '#DEADAF'
})
expect(out.staticVars).to.have.property('bg').equal('#008080')
const panelRule = out.eager.filter(x => {
if (x.component !== 'Panel') return false
return true
})[0]
expect(panelRule).to.have.nested.deep.property('dynamicVars.background', { r: 0, g: 128, b: 128, a: 0.5 })
expect(panelRule).to.have.nested.deep.property('dynamicVars.stacked')
// Somewhat incorrect since we don't do gamma correction
// real expectancy should be this:
/*
expect(panelRule).to.have.nested.deep.property('dynamicVars.stacked.r').that.is.closeTo(147.0, 0.01)
expect(panelRule).to.have.nested.deep.property('dynamicVars.stacked.g').that.is.closeTo(143.2, 0.01)
expect(panelRule).to.have.nested.deep.property('dynamicVars.stacked.b').that.is.closeTo(144.0, 0.01)
*/
expect(panelRule).to.have.nested.deep.property('dynamicVars.stacked.r').that.is.closeTo(88.8, 0.01)
expect(panelRule).to.have.nested.deep.property('dynamicVars.stacked.g').that.is.closeTo(133.2, 0.01)
expect(panelRule).to.have.nested.deep.property('dynamicVars.stacked.b').that.is.closeTo(134, 0.01)
})
})
})

View file

@ -1,11 +0,0 @@
import { extractCommit } from 'src/services/version/version.service.js'
describe('extractCommit', () => {
it('return short commit hash following "-g" characters', () => {
expect(extractCommit('1.0.0-45-g5e7aeebc')).to.eql('5e7aeebc')
})
it('return short commit hash without branch name', () => {
expect(extractCommit('1.0.0-45-g5e7aeebc-branch')).to.eql('5e7aeebc')
})
})