pleroma-fe/test/unit/specs/stores/follow_requests.spec.js
2026-09-03 21:21:08 +03:00

338 lines
10 KiB
JavaScript

import { createTestingPinia } from '@pinia/testing'
import { setActivePinia } from 'pinia'
import { useFollowRequestsStore } from 'src/stores/follow_requests.js'
import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useNotificationsStore } from 'src/stores/notifications.js'
import { useUsersStore } from 'src/stores/users.js'
import * as USER_API from 'src/api/user.js'
const mockMastoAPIUser = ({
screen_name = 'u1',
name = 'user1',
url = 'http://localhost/u1',
id = 'u1',
} = {}) => ({
id,
acct: screen_name,
display_name: name,
fields: [],
avatar: '',
url,
pleroma: {
emoji_reactions: [],
},
})
describe('Follow Requests store', () => {
beforeEach(() => {
setActivePinia(createTestingPinia({ stubActions: false }))
vi.useFakeTimers()
})
afterEach(() => {
vi.useRealTimers()
vi.resetAllMocks()
})
describe('Getters', () => {
it('followRequestsCount returns total number of follow requests', async () => {
const store = useFollowRequestsStore()
store.requests = new Map([
['1', {}],
['2', {}],
])
expect(store).to.have.property('followRequestsCount', 2)
})
})
describe('Actions', () => {
describe('Fetcher stuff', () => {
it('startFetching should initialize fetcher and fetch some data', async () => {
const store = useFollowRequestsStore()
const mockFetch = vi.fn()
mockFetch.mockResolvedValueOnce(
new Response(JSON.stringify([mockMastoAPIUser()]), {
headers: { 'Content-Type': 'application/json' },
}),
)
mockFetch.mockResolvedValueOnce(
new Response(JSON.stringify([mockMastoAPIUser()]), {
headers: { 'Content-Type': 'application/json' },
}),
)
vi.stubGlobal('fetch', mockFetch)
store.startFetching()
expect(store.fetcher).to.not.be.null
await vi.advanceTimersToNextTimerAsync()
expect(mockFetch).to.have.been.calledOnce
await vi.advanceTimersToNextTimerAsync()
expect(mockFetch).to.have.been.calledTwice
expect(useUsersStore().findUser('u1')).to.not.be.undefined
expect(store.requests.get('u1')).to.not.be.undefined
})
it('stopFetching should stop and remove the fetcher', async () => {
const store = useFollowRequestsStore()
const mockFetch = vi.fn()
mockFetch.mockResolvedValueOnce(
new Response(JSON.stringify([mockMastoAPIUser()]), {
headers: { 'Content-Type': 'application/json' },
}),
)
mockFetch.mockResolvedValueOnce(
new Response(JSON.stringify([mockMastoAPIUser()]), {
headers: { 'Content-Type': 'application/json' },
}),
)
vi.stubGlobal('fetch', mockFetch)
store.startFetching()
expect(store.fetcher).to.not.be.null
store.stopFetching()
expect(store.fetcher).to.be.null
})
})
describe.each(['Approve', 'Deny'])('%s', (intent) => {
const doCall = `do${intent}`
const apiCall = USER_API[`MASTODON_${intent.toUpperCase()}_USER_URL`]
const forkCall = intent.toLowerCase()
const forkProperty = `modalOn${intent}Follow`
const modalProperty = `showing${intent}ConfirmDialog`
const modalCalls = ['show', 'hide'].map(
(vis) => `${vis}${intent}ConfirmDialog`,
)
describe('Dialog calls', () => {
it(`${modalCalls[0]} should show dialog and set tempId`, async () => {
const store = useFollowRequestsStore()
await store[modalCalls[0]]('u13')
expect(store).to.have.property(modalProperty, true)
expect(store).to.have.property('tempId', 'u13')
})
it(`${modalCalls[1]} should hide dialog and clear tempId`, async () => {
const store = useFollowRequestsStore()
await store[modalCalls[1]]()
expect(store).to.have.property(modalProperty, false)
expect(store).to.have.property('tempId', null)
})
})
describe('Fork calls', () => {
it(`Should call ${doCall} if confirmations are disabled (${forkProperty} = false)`, async () => {
const store = useFollowRequestsStore()
const modalSpy = vi.spyOn(store, modalCalls[0]).mockImplementation(() => ({}))
const apiSpy = vi.spyOn(store, doCall).mockImplementation(() => ({}))
useMergedConfigStore().mergedConfig = { [forkProperty]: false }
await store[forkCall]('u23')
expect(modalSpy).to.not.have.been.called
expect(apiSpy).to.have.been.calledOnce
expect(apiSpy).to.have.been.calledWith('u23')
})
it(`Should call ${modalCalls[0]} if confirmations are enabled (${forkProperty} = true)`, async () => {
const store = useFollowRequestsStore()
const modalSpy = vi.spyOn(store, modalCalls[0]).mockImplementation(() => ({}))
const apiSpy = vi.spyOn(store, doCall).mockImplementation(() => ({}))
useMergedConfigStore().mergedConfig = { [forkProperty]: true }
await store[forkCall]('u23')
expect(modalSpy).to.have.been.called
expect(apiSpy).to.not.have.been.calledOnce
})
})
describe('Actual call', () => {
it('Should hide popover', async () => {
const store = useFollowRequestsStore()
const spy = vi.spyOn(store, modalCalls[1])
const mockFetch = vi.fn()
mockFetch.mockResolvedValueOnce(
new Response(JSON.stringify('ok'), {
headers: { 'Content-Type': 'application/json' },
}),
)
vi.stubGlobal('fetch', mockFetch)
await store[doCall]()
expect(spy).to.have.been.calledOnce
})
it('Should call API', async () => {
const store = useFollowRequestsStore()
const mockFetch = vi.fn()
mockFetch.mockResolvedValueOnce(
new Response(JSON.stringify('ok'), {
headers: { 'Content-Type': 'application/json' },
}),
)
vi.stubGlobal('fetch', mockFetch)
await store[doCall]('u99')
expect(mockFetch).to.have.been.calledOnce
expect(mockFetch.mock.calls[0][0]).to.eql(apiCall('u99'))
})
it('Should mark notification as seen', async () => {
const store = useFollowRequestsStore()
const mockFetch = vi.fn()
store.findFollowRequestNotificationId = vi.fn()
store.findFollowRequestNotificationId.mockReturnValue('n91')
const spy = vi.spyOn(useNotificationsStore(), 'markSingleNotificationAsSeen')
mockFetch.mockResolvedValueOnce(
new Response(JSON.stringify('ok'), {
headers: { 'Content-Type': 'application/json' },
}),
)
vi.stubGlobal('fetch', mockFetch)
await store[doCall]('u99')
expect(spy).to.have.been.calledOnce
expect(spy).to.have.been.calledWith('n91')
})
it('Should fallback to tempId if no id is provided', async () => {
const store = useFollowRequestsStore()
const mockFetch = vi.fn()
mockFetch.mockResolvedValueOnce(
new Response(JSON.stringify('ok'), {
headers: { 'Content-Type': 'application/json' },
}),
)
vi.stubGlobal('fetch', mockFetch)
store.tempId = 'u80'
await store[doCall]()
expect(mockFetch).to.have.been.calledOnce
expect(mockFetch.mock.calls[0][0]).to.eql(apiCall('u80'))
})
it('Should remove request from cache', async () => {
const store = useFollowRequestsStore()
const mockFetch = vi.fn()
store.requests.set('u95', { id: 'u95' })
store.requests.set('u96', { id: 'u96' })
store.requests.set('u97', { id: 'u97' })
store.requests.set('u98', { id: 'u98' })
store.requests.set('u99', { id: 'u99' })
mockFetch.mockResolvedValueOnce(
new Response(JSON.stringify('ok'), {
headers: { 'Content-Type': 'application/json' },
}),
)
vi.stubGlobal('fetch', mockFetch)
await store[doCall]('u99')
expect(store.requests).to.have.length(4)
expect(store.requests.get('u99')).to.be.undefined
})
})
})
describe('Utility', () => {
describe('findFollowRequestNotificationId', () => {
it('should search notifications store for relevant notification', () => {
const store = useFollowRequestsStore()
useNotificationsStore().data = [
{
id: 'n4',
from_profile: { id: 'u3' },
type: 'follow_request',
},
{
id: 'n3',
from_profile: { id: 'u2' },
type: 'repeat',
},
{
id: 'n2',
from_profile: { id: 'u1' },
type: 'follow_request',
},
{
id: 'n1',
from_profile: { id: 'u1' },
type: 'favorite',
},
]
const result = store.findFollowRequestNotificationId('u1')
expect(result).to.have.eql('n2')
})
it("shouldn't crash if there is no notification available", () => {
const store = useFollowRequestsStore()
useNotificationsStore().data = [
{
id: 'n4',
from_profile: { id: 'u3' },
type: 'follow_request',
},
{
id: 'n3',
from_profile: { id: 'u2' },
type: 'repeat',
},
{
id: 'n2',
from_profile: { id: 'u1' },
type: 'follow_request',
},
{
id: 'n1',
from_profile: { id: 'u1' },
type: 'favorite',
},
]
const result = store.findFollowRequestNotificationId('u5')
expect(result).to.have.eql(undefined)
})
})
})
})
})