1266 lines
38 KiB
JavaScript
1266 lines
38 KiB
JavaScript
import { createTestingPinia } from '@pinia/testing'
|
|
import { snakeCase } from 'lodash'
|
|
import { setActivePinia } from 'pinia'
|
|
|
|
import { useAnnouncementsStore } from 'src/stores/announcements.js'
|
|
import { useBookmarkFoldersStore } from 'src/stores/bookmark_folders.js'
|
|
import { useChatsStore } from 'src/stores/chats.js'
|
|
import { useEmojiStore } from 'src/stores/emoji.js'
|
|
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
|
|
import { useInterfaceStore } from 'src/stores/interface.js'
|
|
import { useListsStore } from 'src/stores/lists.js'
|
|
import { useMergedConfigStore } from 'src/stores/merged_config.js'
|
|
import { useNotificationsStore } from 'src/stores/notifications.js'
|
|
import { useOAuthStore } from 'src/stores/oauth.js'
|
|
import { useStatusesStore } from 'src/stores/statuses.js'
|
|
import { useStreamingStore } from 'src/stores/streaming.js'
|
|
import { useSyncConfigStore } from 'src/stores/sync_config.js'
|
|
import { useTimelinesStore } from 'src/stores/timelines.js'
|
|
import { useUserHighlightStore } from 'src/stores/user_highlight.js'
|
|
import { useUsersStore } from 'src/stores/users.js'
|
|
|
|
import * as PUBLIC_API from 'src/api/public.js'
|
|
import * as USER_API from 'src/api/user.js'
|
|
|
|
const DEFAULT_OPTIONS = (method = 'POST') => ({
|
|
method,
|
|
credentials: 'same-origin',
|
|
headers: {
|
|
Accept: 'application/json',
|
|
'Content-Type': 'application/json',
|
|
},
|
|
})
|
|
|
|
const actionKeys = (action) => {
|
|
const result = {}
|
|
if (action === 'removeUserFromFollowers') {
|
|
result.storeAction = action
|
|
} else {
|
|
result.storeAction = action + 'User'
|
|
}
|
|
|
|
result.apiUrl = `MASTODON_${snakeCase(result.storeAction).toUpperCase()}_URL`
|
|
return result
|
|
}
|
|
|
|
const userId = '1'
|
|
const userScreenName = 'user'
|
|
const userName = 'Guy'
|
|
const userUrl = 'http://localhost/user'
|
|
|
|
const mockMastoAPIUser = ({
|
|
screen_name = userScreenName,
|
|
name = userName,
|
|
url = userUrl,
|
|
id = userId,
|
|
} = {}) => ({
|
|
id,
|
|
acct: screen_name,
|
|
display_name: name,
|
|
fields: [],
|
|
avatar: '',
|
|
url,
|
|
})
|
|
|
|
const mockUser = ({
|
|
screen_name = userScreenName,
|
|
id = userId,
|
|
name = userName,
|
|
url = userUrl,
|
|
} = {}) => ({
|
|
_original: mockMastoAPIUser({
|
|
screen_name,
|
|
id,
|
|
name,
|
|
url,
|
|
}),
|
|
id,
|
|
name,
|
|
screen_name,
|
|
url,
|
|
relationship: undefined,
|
|
})
|
|
|
|
describe('Users store', () => {
|
|
beforeEach(() => {
|
|
setActivePinia(createTestingPinia({ stubActions: false }))
|
|
})
|
|
|
|
describe('addNewUsers', () => {
|
|
describe('users', () => {
|
|
it('adds new users to the set, merging in new information for old users', () => {
|
|
const store = useUsersStore()
|
|
|
|
const modUser = mockUser({ name: 'Dude' })
|
|
|
|
store.addNewUsers({ data: [mockUser()], timestamp: 1 })
|
|
expect(store.users).to.have.length(1)
|
|
expect(store.users).to.have.all.keys(userId)
|
|
|
|
store.addNewUsers({ data: [modUser], timestamp: 2 })
|
|
expect(store.users).to.have.length(1)
|
|
expect(store.users).to.have.all.keys(userId)
|
|
expect(store.users.get(userId).name).to.eql('Dude')
|
|
})
|
|
|
|
it('ignores new users if timestamp is older', () => {
|
|
const store = useUsersStore()
|
|
|
|
const modUser = mockUser({ name: 'Old guy' })
|
|
|
|
store.addNewUsers({ data: [mockUser()], timestamp: 2000 })
|
|
expect(store.users).to.have.length(1)
|
|
expect(store.users).to.have.all.keys(userId)
|
|
expect(store.users.get(userId).name).to.eql('Guy')
|
|
|
|
store.addNewUsers({ data: [modUser], timestamp: 1991 })
|
|
expect(store.users).to.have.length(1)
|
|
expect(store.users).to.have.all.keys(userId)
|
|
expect(store.users.get(userId).name).to.eql('Guy')
|
|
})
|
|
|
|
it('merging array field in new information for old users', () => {
|
|
const store = useUsersStore()
|
|
|
|
const userFields = {
|
|
...mockUser(),
|
|
fields: [{ name: 'Label 1', value: 'Content 1' }],
|
|
}
|
|
const firstModUser = {
|
|
...mockUser(),
|
|
fields: [
|
|
{ name: 'Label 2', value: 'Content 2' },
|
|
{ name: 'Label 3', value: 'Content 3' },
|
|
],
|
|
}
|
|
const secondModUser = {
|
|
...mockUser(),
|
|
fields: [{ name: 'Label 4', value: 'Content 4' }],
|
|
}
|
|
|
|
store.addNewUsers({ data: [userFields], timestamp: 1 })
|
|
const reactive = store.users.get(userId)
|
|
expect(reactive.fields).to.have.length(1)
|
|
expect(reactive.fields[0].name).to.eql('Label 1')
|
|
|
|
store.addNewUsers({ data: [firstModUser], timestamp: 2 })
|
|
expect(reactive.fields).to.have.length(2)
|
|
expect(reactive.fields[0].name).to.eql('Label 2')
|
|
expect(reactive.fields[1].name).to.eql('Label 3')
|
|
|
|
store.addNewUsers({ data: [secondModUser], timestamp: 3 })
|
|
expect(reactive.fields).to.have.length(1)
|
|
expect(reactive.fields[0].name).to.eql('Label 4')
|
|
})
|
|
})
|
|
|
|
describe('relationships', () => {
|
|
it('updates relationship information if present', () => {
|
|
const store = useUsersStore()
|
|
|
|
const modUser = {
|
|
...mockUser(),
|
|
relationship: {
|
|
id: userId,
|
|
following: true,
|
|
},
|
|
}
|
|
|
|
store.addNewUsers({ data: [mockUser()], timestamp: 1 })
|
|
store.addNewUsers({ data: [modUser], timestamp: 2 })
|
|
|
|
expect(store.relationships).to.have.length(1)
|
|
expect(store.relationships).to.have.all.keys(userId)
|
|
expect(store.relationship(userId).following).to.eql(true)
|
|
expect(store.findUser(userId).relationship.following).to.eql(true)
|
|
})
|
|
|
|
it('updates relationship information if present even if user timestamp is older', () => {
|
|
const store = useUsersStore()
|
|
|
|
const modUser = {
|
|
...mockUser({ name: 'Old Dude' }),
|
|
relationship: {
|
|
id: userId,
|
|
following: true,
|
|
},
|
|
}
|
|
|
|
store.addNewUsers({ data: [mockUser()], timestamp: 2000 })
|
|
store.addNewUsers({ data: [modUser], timestamp: 1991 })
|
|
|
|
expect(store.relationships).to.have.length(1)
|
|
expect(store.relationships).to.have.all.keys(userId)
|
|
expect(store.relationship(userId).following).to.eql(true)
|
|
expect(store.findUser(userId).relationship.following).to.eql(true)
|
|
})
|
|
|
|
it("doesn't erase relationship information if new data has it missing", () => {
|
|
const store = useUsersStore()
|
|
|
|
const modUser = mockUser({ name: 'Dude' })
|
|
|
|
store.addNewUsers({
|
|
data: [
|
|
{ ...mockUser(), relationship: { id: userId, following: true } },
|
|
],
|
|
timestamp: 1,
|
|
})
|
|
store.addNewUsers({ data: [modUser], timestamp: 2 })
|
|
|
|
expect(store.relationships).to.have.length(1)
|
|
expect(store.relationships).to.have.all.keys(userId)
|
|
expect(store.relationship(userId).following).to.eql(true)
|
|
expect(store.findUser(userId).relationship.following).to.eql(true)
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('updateUserRelationships', () => {
|
|
it('updates existing user relationship', () => {
|
|
const store = useUsersStore()
|
|
const relationship = { id: userId, following: true }
|
|
|
|
store.addNewUsers({ data: [mockUser()], timestamp: 1 })
|
|
store.updateUserRelationships({ data: relationship, timestamp: 2 })
|
|
|
|
expect(store.relationship(userId)).to.eql({ id: userId, following: true })
|
|
expect(store.findUser(userId).relationship.following).to.eql(true)
|
|
})
|
|
|
|
it('stores relationship for missing user', () => {
|
|
const store = useUsersStore()
|
|
const relationship = { id: userId, following: true }
|
|
|
|
store.updateUserRelationships({ data: relationship, timestamp: 1 })
|
|
|
|
expect(store.relationship(userId)).to.eql({ id: userId, following: true })
|
|
})
|
|
|
|
it('assigns relationship for missing user when it becomes available', () => {
|
|
const store = useUsersStore()
|
|
const relationship = { id: userId, following: true }
|
|
|
|
store.updateUserRelationships({ data: relationship, timestamp: 1 })
|
|
store.addNewUsers({ data: [mockUser()], timestamp: 2 })
|
|
|
|
expect(store.relationship(userId)).to.eql({ id: userId, following: true })
|
|
expect(store.findUser(userId).relationship).to.eql({
|
|
id: userId,
|
|
following: true,
|
|
})
|
|
})
|
|
|
|
it('ignores relationship update if timestamp is older than existing', () => {
|
|
const store = useUsersStore()
|
|
const oldRelationship = { id: userId, following: true }
|
|
const newRelationship = { id: userId, following: false }
|
|
|
|
store.addNewUsers({
|
|
data: [{ ...mockUser(), relationship: newRelationship }],
|
|
timestamp: 2000,
|
|
})
|
|
store.updateUserRelationships({ data: oldRelationship, timestamp: 1991 })
|
|
|
|
expect(store.relationship(userId)).to.eql({
|
|
id: userId,
|
|
following: false,
|
|
})
|
|
expect(store.findUser(userId).relationship.following).to.eql(false)
|
|
})
|
|
})
|
|
|
|
describe('fetchers', () => {
|
|
describe('fetchUserIfMissing', () => {
|
|
it('should fetch requested user, add it to store and return it', async () => {
|
|
vi.stubGlobal(
|
|
'fetch',
|
|
vi.fn().mockResolvedValueOnce(
|
|
new Response(JSON.stringify(mockMastoAPIUser()), {
|
|
headers: { 'Content-Type': 'application/json' },
|
|
}),
|
|
),
|
|
)
|
|
|
|
const expected = mockUser()
|
|
const store = useUsersStore()
|
|
const resultUser = await store.fetchUserIfMissing({ id: '1' })
|
|
|
|
expect(resultUser).to.deep.include(expected)
|
|
expect(store.findUser(userId)).to.deep.include(expected)
|
|
})
|
|
|
|
it('Should re-use existing promise for other fetches', async () => {
|
|
vi.stubGlobal(
|
|
'fetch',
|
|
vi
|
|
.fn()
|
|
.mockResolvedValueOnce(
|
|
new Response(JSON.stringify({ id: '1' }), {
|
|
headers: { 'Content-Type': 'application/json' },
|
|
}),
|
|
)
|
|
// fetch by name yields user id which we request next
|
|
.mockResolvedValueOnce(
|
|
new Response(JSON.stringify(mockMastoAPIUser()), {
|
|
headers: { 'Content-Type': 'application/json' },
|
|
}),
|
|
)
|
|
.mockThrowOnce(new Error("Shouldn't be called more than once")),
|
|
)
|
|
|
|
const expected = mockUser()
|
|
const store = useUsersStore()
|
|
const resultUser1 = await store.fetchUserIfMissing({ name: 'user' })
|
|
const resultUser2 = await store.fetchUserIfMissing({ id: '1' })
|
|
|
|
expect(resultUser1).to.deep.include(expected)
|
|
expect(resultUser2).to.deep.include(expected)
|
|
expect(store.findUser(userId)).to.deep.include(expected)
|
|
})
|
|
|
|
it('Should use cached data if present', async () => {
|
|
vi.stubGlobal(
|
|
'fetch',
|
|
vi.fn().mockThrowOnce(new Error("Shouldn't be called at all")),
|
|
)
|
|
const store = useUsersStore()
|
|
store.addNewUsers({ data: [mockUser()], timestamp: 1 })
|
|
|
|
const resultUser1 = await store.fetchUserIfMissing({ id: '1' })
|
|
const resultUser2 = await store.fetchUserIfMissing({ name: 'user' })
|
|
const expected = mockUser()
|
|
|
|
expect(resultUser1).to.deep.include(expected)
|
|
expect(resultUser2).to.deep.include(expected)
|
|
expect(store.findUser(userId)).to.deep.include(expected)
|
|
})
|
|
|
|
it('Should handle 404 gracefully', async () => {
|
|
vi.stubGlobal(
|
|
'fetch',
|
|
vi.fn().mockResolvedValueOnce(
|
|
new Response(JSON.stringify(mockMastoAPIUser()), {
|
|
status: 404,
|
|
statusText: 'Not Found',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
}),
|
|
),
|
|
)
|
|
const store = useUsersStore()
|
|
|
|
const resultUser = await store.fetchUserIfMissing({ id: '1' })
|
|
|
|
expect(resultUser).to.be.null
|
|
})
|
|
|
|
it('Should throw if no identifier is provided', async () => {
|
|
const store = useUsersStore()
|
|
|
|
await expect(
|
|
async () => await store.fetchUserIfMissing({}),
|
|
).rejects.to.throw(TypeError)
|
|
await expect(
|
|
async () => await store.fetchUserIfMissing(),
|
|
).rejects.to.throw(TypeError)
|
|
})
|
|
})
|
|
|
|
describe('relationships', () => {
|
|
it.each(['Friends', 'Followers'])('fetch%s', async (group) => {
|
|
const mockFetch = vi
|
|
.fn()
|
|
.mockResolvedValueOnce(
|
|
new Response(
|
|
JSON.stringify([
|
|
mockMastoAPIUser({
|
|
screen_name: 'snake',
|
|
name: 'John',
|
|
id: '2',
|
|
}),
|
|
mockMastoAPIUser({
|
|
screen_name: 'zero',
|
|
name: 'David Oh',
|
|
id: '3',
|
|
}),
|
|
]),
|
|
{
|
|
headers: { 'Content-Type': 'application/json' },
|
|
},
|
|
),
|
|
)
|
|
.mockResolvedValueOnce(
|
|
new Response(
|
|
JSON.stringify([
|
|
mockMastoAPIUser({
|
|
screen_name: 'sigint',
|
|
name: 'Mr.Anderson',
|
|
id: '4',
|
|
}),
|
|
mockMastoAPIUser({
|
|
screen_name: 'paramedic',
|
|
name: 'Dr.Clark',
|
|
id: '5',
|
|
}),
|
|
]),
|
|
{
|
|
headers: { 'Content-Type': 'application/json' },
|
|
},
|
|
),
|
|
)
|
|
|
|
vi.stubGlobal('fetch', mockFetch)
|
|
|
|
const store = useUsersStore()
|
|
store.addNewUsers({ timestamp: 1, data: mockUser() })
|
|
|
|
const urlGroup = group === 'Friends' ? 'Following' : group
|
|
const us = store.users.get(userId)
|
|
|
|
await store[`fetch${group}`](userId)
|
|
expect(mockFetch).to.have.been.calledWith(
|
|
PUBLIC_API[`MASTODON_${urlGroup.toUpperCase()}_URL`](userId, {
|
|
limit: 20,
|
|
withRelationships: true,
|
|
}),
|
|
DEFAULT_OPTIONS('GET'),
|
|
)
|
|
expect(
|
|
store.relationshipsLists[group.toLowerCase()].get(us),
|
|
).to.have.length(2)
|
|
|
|
await store[`fetch${group}`](userId)
|
|
expect(mockFetch).to.have.been.calledWith(
|
|
PUBLIC_API[`MASTODON_${urlGroup.toUpperCase()}_URL`](userId, {
|
|
maxId: '3',
|
|
limit: 20,
|
|
withRelationships: true,
|
|
}),
|
|
DEFAULT_OPTIONS('GET'),
|
|
)
|
|
expect(
|
|
store.relationshipsLists[group.toLowerCase()].get(us),
|
|
).to.have.length(4)
|
|
})
|
|
|
|
it.each(['Mutes', 'Blocks'])('fetch%s', async (group) => {
|
|
const mockFetch = vi
|
|
.fn()
|
|
.mockResolvedValueOnce(
|
|
new Response(
|
|
JSON.stringify([
|
|
mockMastoAPIUser({
|
|
screen_name: 'snake',
|
|
name: 'John',
|
|
id: '2',
|
|
}),
|
|
mockMastoAPIUser({
|
|
screen_name: 'zero',
|
|
name: 'David Oh',
|
|
id: '3',
|
|
}),
|
|
]),
|
|
{
|
|
headers: { 'Content-Type': 'application/json' },
|
|
},
|
|
),
|
|
)
|
|
.mockResolvedValueOnce(
|
|
new Response(
|
|
JSON.stringify([
|
|
mockMastoAPIUser({
|
|
screen_name: 'sigint',
|
|
name: 'Mr.Anderson',
|
|
id: '4',
|
|
}),
|
|
mockMastoAPIUser({
|
|
screen_name: 'paramedic',
|
|
name: 'Dr.Clark',
|
|
id: '5',
|
|
}),
|
|
]),
|
|
{
|
|
headers: { 'Content-Type': 'application/json' },
|
|
},
|
|
),
|
|
)
|
|
|
|
vi.stubGlobal('fetch', mockFetch)
|
|
|
|
const store = useUsersStore()
|
|
store.addNewUsers({ timestamp: 1, data: mockUser() })
|
|
|
|
const us = store.users.get(userId)
|
|
|
|
store.currentUser = us
|
|
const ids = group === 'Mutes' ? 'muteIds' : 'blockIds'
|
|
|
|
await store[`fetch${group}`]({ reset: true })
|
|
expect(mockFetch).to.have.been.calledWith(
|
|
USER_API[`MASTODON_USER_${group.toUpperCase()}_URL`]({
|
|
withRelationships: true,
|
|
}),
|
|
DEFAULT_OPTIONS('GET'),
|
|
)
|
|
expect(us[ids]).to.have.length(2)
|
|
|
|
await store[`fetch${group}`]()
|
|
expect(mockFetch).to.have.been.calledWith(
|
|
USER_API[`MASTODON_USER_${group.toUpperCase()}_URL`]({
|
|
withRelationships: true,
|
|
}),
|
|
DEFAULT_OPTIONS('GET'),
|
|
)
|
|
expect(us[ids]).to.have.length(4)
|
|
})
|
|
|
|
it('fetchDomainMutes', async () => {
|
|
const mockFetch = vi.fn().mockResolvedValueOnce(
|
|
new Response(JSON.stringify(['example.com', 'example.org']), {
|
|
headers: { 'Content-Type': 'application/json' },
|
|
}),
|
|
)
|
|
|
|
vi.stubGlobal('fetch', mockFetch)
|
|
|
|
const store = useUsersStore()
|
|
store.addNewUsers({ timestamp: 1, data: mockUser() })
|
|
|
|
const us = store.users.get(userId)
|
|
store.currentUser = us
|
|
|
|
await store.fetchDomainMutes()
|
|
|
|
expect(mockFetch).to.have.been.calledWith(
|
|
USER_API.MASTODON_DOMAIN_BLOCKS_URL,
|
|
DEFAULT_OPTIONS('GET'),
|
|
)
|
|
|
|
expect(us.domainMutes).to.have.eql(
|
|
new Set(['example.com', 'example.org']),
|
|
)
|
|
})
|
|
|
|
it('fetchInLists', async () => {
|
|
const inLists = [
|
|
{ exclusive: false, id: '1', title: 'Operatives' },
|
|
{ exclusive: true, id: '2', title: 'Agents' },
|
|
]
|
|
const mockFetch = vi.fn().mockResolvedValueOnce(
|
|
new Response(JSON.stringify(inLists), {
|
|
headers: { 'Content-Type': 'application/json' },
|
|
}),
|
|
)
|
|
|
|
vi.stubGlobal('fetch', mockFetch)
|
|
|
|
const store = useUsersStore()
|
|
store.addNewUsers({
|
|
timestamp: 1,
|
|
data: [
|
|
mockUser(),
|
|
{ ...mockUser({ name: 'John', screen_name: 'snake', id: '2' }) },
|
|
{ ...mockUser({ name: 'David Oh', screen_name: 'zero', id: '3' }) },
|
|
],
|
|
})
|
|
|
|
const us = store.users.get(userId)
|
|
store.currentUser = us
|
|
|
|
await store.fetchUserInLists('2')
|
|
|
|
expect(mockFetch).to.have.been.calledWith(
|
|
USER_API.MASTODON_USER_IN_LISTS('2'),
|
|
DEFAULT_OPTIONS('GET'),
|
|
)
|
|
|
|
expect(store.users.get('2').inLists).to.have.eql(inLists)
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('misc updates', () => {
|
|
it('updateUserAdminData', () => {
|
|
const store = useUsersStore()
|
|
store.addNewUsers({ data: [mockUser()], timestamp: 1 })
|
|
const adminData = { is_active: true, tags: ['one', 'two'] }
|
|
store.updateUserAdminData(userId, adminData)
|
|
|
|
const userData = store.users.get(userId)
|
|
expect(userData.deactivated).to.eql(false)
|
|
expect(userData.tags).to.eql(new Set(['one', 'two']))
|
|
})
|
|
|
|
it('updateRight', () => {
|
|
const store = useUsersStore()
|
|
store.addNewUsers({ data: [mockUser()], timestamp: 1 })
|
|
store.updateRight(userId, 'right1', true)
|
|
store.updateRight(userId, 'right2', false)
|
|
|
|
const userData = store.users.get(userId)
|
|
expect(userData.rights.right1).to.eql(true)
|
|
expect(userData.rights.right2).to.eql(false)
|
|
})
|
|
|
|
it('clearFollowLists', () => {
|
|
const store = useUsersStore()
|
|
store.addNewUsers({ data: [mockUser()], timestamp: 1 })
|
|
const userData = store.users.get(userId)
|
|
store.relationshipsLists.friends.get(userData).add('2')
|
|
store.relationshipsLists.friends.get(userData).add('3')
|
|
store.relationshipsLists.followers.get(userData).add('4')
|
|
store.relationshipsLists.followers.get(userData).add('5')
|
|
store.clearFollowLists(userId)
|
|
|
|
expect(store.relationshipsLists.friends.get(userData)).to.have.length(0)
|
|
expect(store.relationshipsLists.followers.get(userData)).to.have.length(0)
|
|
})
|
|
})
|
|
|
|
describe('login/logout', () => {
|
|
describe('login', () => {
|
|
it('normal login', async () => {
|
|
vi.stubGlobal(
|
|
'fetch',
|
|
vi.fn().mockResolvedValueOnce(
|
|
new Response(JSON.stringify(mockMastoAPIUser()), {
|
|
headers: { 'Content-Type': 'application/json' },
|
|
}),
|
|
),
|
|
)
|
|
|
|
const spies = [
|
|
// Misc initialization
|
|
vi.spyOn(useSyncConfigStore(), 'initSyncConfig'),
|
|
vi.spyOn(useUserHighlightStore(), 'initUserHighlight'),
|
|
vi.spyOn(useInterfaceStore(), 'applyTheme'),
|
|
vi.spyOn(useInterfaceStore(), 'onLogin'),
|
|
vi.spyOn(useEmojiStore(), 'fetchEmoji'),
|
|
|
|
// Timeline / Notifications
|
|
vi.spyOn(useNotificationsStore(), 'activate'),
|
|
vi.spyOn(useTimelinesStore(), 'activatePersistents'),
|
|
|
|
// Fetchers
|
|
vi.spyOn(useChatsStore(), 'startFetching'),
|
|
vi.spyOn(useListsStore(), 'startFetching'),
|
|
vi.spyOn(useAnnouncementsStore(), 'startFetching'),
|
|
vi.spyOn(useBookmarkFoldersStore(), 'startFetching'),
|
|
vi.spyOn(useStreamingStore(), 'initSocket'),
|
|
]
|
|
|
|
spies.forEach((spy) => {
|
|
spy.mockImplementation(async () => {
|
|
/* no-op */
|
|
})
|
|
})
|
|
|
|
useInstanceCapabilitiesStore().pleromaChatMessagesAvailable = true
|
|
useMergedConfigStore().mergedConfig = { useStreamingApi: true }
|
|
|
|
const store = useUsersStore()
|
|
|
|
// Adding some users to verify they are getting cleaned afterwards
|
|
store.addNewUsers({
|
|
data: [
|
|
mockUser(),
|
|
{ ...mockUser({ name: 'John', screen_name: 'snake' }) },
|
|
{ ...mockUser({ name: 'David Oh', screen_name: 'zero' }) },
|
|
],
|
|
timestamp: 2000,
|
|
})
|
|
|
|
expect(store.loggedIn).to.eql(false)
|
|
await store.loginUser('ACCESS_TOKEN')
|
|
expect(store.loggedIn).to.eql(true)
|
|
|
|
// We should be in the store ourselves
|
|
expect(store.users).to.have.length(1)
|
|
expect(store.usersByName).to.have.length(1)
|
|
expect(store.usersByURL).to.have.length(1)
|
|
expect(store.relationships).to.have.length(0)
|
|
spies.forEach((spy, index) => {
|
|
expect(spy, `Spy ${index} has failed`).to.have.been.called
|
|
})
|
|
})
|
|
|
|
it('bad credentials', async () => {
|
|
vi.stubGlobal(
|
|
'fetch',
|
|
vi.fn().mockResolvedValueOnce(
|
|
new Response(JSON.stringify(mockMastoAPIUser()), {
|
|
status: 403,
|
|
statusText: 'Forbidden',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
}),
|
|
),
|
|
)
|
|
|
|
const spies = [vi.spyOn(useOAuthStore(), 'clearToken')]
|
|
|
|
spies.forEach((spy) => {
|
|
spy.mockImplementation(async () => {
|
|
/* no-op */
|
|
})
|
|
})
|
|
|
|
const store = useUsersStore()
|
|
const exec = async () => {
|
|
await store.loginUser('ACCESS_TOKEN')
|
|
}
|
|
|
|
await expect(exec).rejects.to.throw(Error)
|
|
|
|
expect(store.loggedIn).to.eql(false)
|
|
|
|
spies.forEach((spy, index) => {
|
|
expect(spy, `Spy ${index} has failed`).to.have.been.called
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('logout', () => {
|
|
it('normal logout', async () => {
|
|
const revokeApi = vi
|
|
.fn()
|
|
.mockResolvedValueOnce(
|
|
// Ensure APP
|
|
new Response(JSON.stringify('ok'), {
|
|
headers: { 'Content-Type': 'application/json' },
|
|
}),
|
|
)
|
|
.mockResolvedValueOnce(
|
|
// Revoke Token
|
|
new Response(JSON.stringify('ok'), {
|
|
headers: { 'Content-Type': 'application/json' },
|
|
}),
|
|
)
|
|
|
|
vi.stubGlobal('fetch', revokeApi)
|
|
|
|
const spies = [
|
|
// Misc initialization
|
|
vi.spyOn(useStatusesStore(), 'resetStatuses'),
|
|
vi.spyOn(useInterfaceStore(), 'onLogout'),
|
|
|
|
// Timeline / Notifications
|
|
vi.spyOn(useNotificationsStore(), 'deactivate'),
|
|
vi.spyOn(useTimelinesStore(), 'deactivateAll'),
|
|
|
|
// Fetchers
|
|
vi.spyOn(useChatsStore(), 'resetChats'),
|
|
vi.spyOn(useListsStore(), 'stopFetching'),
|
|
vi.spyOn(useAnnouncementsStore(), 'stopFetching'),
|
|
vi.spyOn(useBookmarkFoldersStore(), 'stopFetching'),
|
|
vi.spyOn(useStreamingStore(), 'stopSocket'),
|
|
]
|
|
|
|
spies.forEach((spy) => {
|
|
spy.mockImplementation(async () => {
|
|
/* no-op */
|
|
})
|
|
})
|
|
|
|
useInstanceCapabilitiesStore().pleromaChatMessagesAvailable = true
|
|
useMergedConfigStore().mergedConfig = { useStreamingApi: true }
|
|
|
|
const store = useUsersStore()
|
|
store.currentUser = mockUser()
|
|
|
|
// Adding some users to verify they are getting cleaned afterwards
|
|
store.addNewUsers({
|
|
data: [
|
|
mockUser(),
|
|
{
|
|
...mockUser({ name: 'John', screen_name: 'snake' }),
|
|
relationship: { id: userId, following: true },
|
|
},
|
|
{ ...mockUser({ name: 'David Oh', screen_name: 'zero' }) },
|
|
],
|
|
timestamp: 2000,
|
|
})
|
|
expect(store.loggedIn).to.eql(true)
|
|
await store.logout()
|
|
expect(store.loggedIn).to.eql(false)
|
|
expect(revokeApi).to.have.been.called
|
|
expect(store.users).to.have.length(0)
|
|
expect(store.usersByName).to.have.length(0)
|
|
expect(store.usersByURL).to.have.length(0)
|
|
expect(store.relationships).to.have.length(0)
|
|
spies.forEach((spy, index) => {
|
|
expect(spy, `Spy ${index} has failed`).to.have.been.called
|
|
})
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('actions', () => {
|
|
describe('follow', () => {
|
|
beforeEach(() => {
|
|
vi.useFakeTimers()
|
|
})
|
|
|
|
afterEach(() => {
|
|
vi.useRealTimers()
|
|
})
|
|
|
|
it('instant follow case', async () => {
|
|
const followApi = vi.fn().mockResolvedValueOnce(
|
|
new Response(
|
|
JSON.stringify({
|
|
id: userId,
|
|
following: true,
|
|
requested: true,
|
|
}),
|
|
{
|
|
headers: { 'Content-Type': 'application/json' },
|
|
},
|
|
),
|
|
)
|
|
|
|
vi.stubGlobal('fetch', followApi)
|
|
const store = useUsersStore()
|
|
|
|
await store.followUser(userId)
|
|
expect(followApi).to.have.been.called
|
|
|
|
expect(store.followPollers).to.have.length(0)
|
|
expect(store.followPollersAttempts).to.have.length(0)
|
|
expect(followApi).to.have.been.calledWith(
|
|
USER_API.MASTODON_FOLLOW_URL(userId),
|
|
{
|
|
body: '{}',
|
|
...DEFAULT_OPTIONS(),
|
|
},
|
|
)
|
|
})
|
|
|
|
it('delayed follow case', async () => {
|
|
const followApi = vi
|
|
.fn()
|
|
.mockResolvedValueOnce(
|
|
// Follow
|
|
new Response(
|
|
JSON.stringify({
|
|
id: userId,
|
|
following: false,
|
|
requested: true,
|
|
}),
|
|
{
|
|
headers: { 'Content-Type': 'application/json' },
|
|
},
|
|
),
|
|
)
|
|
.mockResolvedValueOnce(
|
|
// Check relationship
|
|
new Response(
|
|
JSON.stringify({
|
|
id: userId,
|
|
following: true,
|
|
requested: true,
|
|
}),
|
|
{
|
|
headers: { 'Content-Type': 'application/json' },
|
|
},
|
|
),
|
|
)
|
|
|
|
vi.stubGlobal('fetch', followApi)
|
|
|
|
const store = useUsersStore()
|
|
|
|
await store.followUser(userId)
|
|
expect(followApi).to.have.been.called
|
|
|
|
expect(store.followPollers).to.have.length(1)
|
|
expect(store.followPollersAttempts).to.have.length(1)
|
|
expect(store.followPollersAttempts.get(userId)).to.eql(3)
|
|
|
|
await vi.runAllTimersAsync()
|
|
expect(followApi).to.have.been.called
|
|
expect(store.followPollers).to.have.length(0)
|
|
expect(store.followPollersAttempts).to.have.length(0)
|
|
})
|
|
|
|
it('unresolved follow case', async () => {
|
|
const followApi = vi.fn().mockResolvedValueOnce(
|
|
// Follow
|
|
new Response(
|
|
JSON.stringify({
|
|
id: userId,
|
|
following: false,
|
|
requested: true,
|
|
}),
|
|
{
|
|
headers: { 'Content-Type': 'application/json' },
|
|
},
|
|
),
|
|
)
|
|
|
|
followApi // mockImplementation because we need to re-create Response
|
|
.mockImplementation(
|
|
() =>
|
|
// Check relationship
|
|
new Response(
|
|
JSON.stringify({
|
|
id: userId,
|
|
following: false,
|
|
requested: true,
|
|
}),
|
|
{
|
|
headers: { 'Content-Type': 'application/json' },
|
|
},
|
|
),
|
|
)
|
|
|
|
vi.stubGlobal('fetch', followApi)
|
|
|
|
const store = useUsersStore()
|
|
|
|
await store.followUser(userId)
|
|
expect(store.followPollers).to.have.length(1)
|
|
expect(store.followPollersAttempts).to.have.length(1)
|
|
expect(store.followPollersAttempts.get(userId)).to.eql(3)
|
|
|
|
await vi.runAllTimersAsync()
|
|
expect(store.followPollersAttempts.get(userId)).to.eql(2)
|
|
|
|
await vi.runAllTimersAsync()
|
|
expect(store.followPollersAttempts.get(userId)).to.eql(1)
|
|
|
|
await vi.runAllTimersAsync()
|
|
expect(store.followPollers).to.have.length(0)
|
|
expect(store.followPollersAttempts).to.have.length(0)
|
|
|
|
expect(followApi).to.have.callCount(4) // 1 follow + 3 retries
|
|
})
|
|
})
|
|
|
|
it('unfollow', async () => {
|
|
const mockFetch = vi.fn().mockResolvedValueOnce(
|
|
new Response(
|
|
JSON.stringify({
|
|
id: userId,
|
|
following: false,
|
|
requested: true,
|
|
}),
|
|
{
|
|
headers: { 'Content-Type': 'application/json' },
|
|
},
|
|
),
|
|
)
|
|
|
|
vi.stubGlobal('fetch', mockFetch)
|
|
|
|
const store = useUsersStore()
|
|
await store.unfollowUser(userId)
|
|
|
|
expect(mockFetch).to.have.been.calledWith(
|
|
USER_API.MASTODON_UNFOLLOW_URL(userId),
|
|
DEFAULT_OPTIONS(),
|
|
)
|
|
})
|
|
|
|
it('subscribe', async () => {
|
|
const mockFetch = vi.fn().mockResolvedValueOnce(
|
|
new Response(
|
|
JSON.stringify({
|
|
id: userId,
|
|
following: false,
|
|
requested: true,
|
|
subscribing: true,
|
|
}),
|
|
{
|
|
headers: { 'Content-Type': 'application/json' },
|
|
},
|
|
),
|
|
)
|
|
|
|
vi.stubGlobal('fetch', mockFetch)
|
|
|
|
const store = useUsersStore()
|
|
await store.subscribeUser(userId)
|
|
|
|
expect(mockFetch).to.have.been.calledWith(
|
|
USER_API.MASTODON_FOLLOW_URL(userId),
|
|
{
|
|
body: JSON.stringify({ notify: true }),
|
|
...DEFAULT_OPTIONS(),
|
|
},
|
|
)
|
|
})
|
|
|
|
it('unsubscribe', async () => {
|
|
const mockFetch = vi.fn().mockResolvedValueOnce(
|
|
new Response(
|
|
JSON.stringify({
|
|
id: userId,
|
|
following: false,
|
|
requested: true,
|
|
subscribing: false,
|
|
}),
|
|
{
|
|
headers: { 'Content-Type': 'application/json' },
|
|
},
|
|
),
|
|
)
|
|
|
|
vi.stubGlobal('fetch', mockFetch)
|
|
|
|
const store = useUsersStore()
|
|
await store.unsubscribeUser(userId)
|
|
|
|
expect(mockFetch).to.have.been.calledWith(
|
|
USER_API.MASTODON_FOLLOW_URL(userId),
|
|
{
|
|
body: JSON.stringify({ notify: false }),
|
|
...DEFAULT_OPTIONS(),
|
|
},
|
|
)
|
|
})
|
|
|
|
it('showReblogs', async () => {
|
|
const mockFetch = vi.fn().mockResolvedValueOnce(
|
|
new Response(
|
|
JSON.stringify({
|
|
id: userId,
|
|
following: false,
|
|
requested: true,
|
|
showing_reblogs: true,
|
|
}),
|
|
{
|
|
headers: { 'Content-Type': 'application/json' },
|
|
},
|
|
),
|
|
)
|
|
|
|
vi.stubGlobal('fetch', mockFetch)
|
|
|
|
const store = useUsersStore()
|
|
await store.showReblogs(userId)
|
|
|
|
expect(mockFetch).to.have.been.calledWith(
|
|
USER_API.MASTODON_FOLLOW_URL(userId),
|
|
{
|
|
body: JSON.stringify({ reblogs: true }),
|
|
...DEFAULT_OPTIONS(),
|
|
},
|
|
)
|
|
})
|
|
|
|
it('hideReblogs', async () => {
|
|
const mockFetch = vi.fn().mockResolvedValueOnce(
|
|
new Response(
|
|
JSON.stringify({
|
|
id: userId,
|
|
following: false,
|
|
requested: true,
|
|
showing_reblogs: false,
|
|
}),
|
|
{
|
|
headers: { 'Content-Type': 'application/json' },
|
|
},
|
|
),
|
|
)
|
|
|
|
vi.stubGlobal('fetch', mockFetch)
|
|
|
|
const store = useUsersStore()
|
|
await store.hideReblogs(userId)
|
|
|
|
expect(mockFetch).to.have.been.calledWith(
|
|
USER_API.MASTODON_FOLLOW_URL(userId),
|
|
{
|
|
body: JSON.stringify({ reblogs: false }),
|
|
...DEFAULT_OPTIONS(),
|
|
},
|
|
)
|
|
})
|
|
|
|
it.each([
|
|
'unmute',
|
|
'unblock',
|
|
'removeUserFromFollowers',
|
|
])('%s', async (action) => {
|
|
const mockFetch = vi.fn().mockResolvedValueOnce(
|
|
new Response(JSON.stringify({ id: userId }), {
|
|
headers: { 'Content-Type': 'application/json' },
|
|
}),
|
|
)
|
|
|
|
vi.stubGlobal('fetch', mockFetch)
|
|
|
|
const store = useUsersStore()
|
|
const { storeAction, apiUrl } = actionKeys(action)
|
|
await store[storeAction](userId)
|
|
|
|
expect(mockFetch).to.have.been.calledWith(
|
|
USER_API[apiUrl](userId),
|
|
DEFAULT_OPTIONS(),
|
|
)
|
|
})
|
|
|
|
describe.each(['mute', 'block'])('%s', (action) => {
|
|
it('normal', async () => {
|
|
const mockFetch = vi.fn().mockResolvedValueOnce(
|
|
new Response(JSON.stringify({ id: userId }), {
|
|
headers: { 'Content-Type': 'application/json' },
|
|
}),
|
|
)
|
|
|
|
vi.stubGlobal('fetch', mockFetch)
|
|
|
|
vi.spyOn(useStatusesStore(), 'wipeUserStatuses').mockImplementation(
|
|
async () => {
|
|
/* no-op */
|
|
},
|
|
)
|
|
|
|
vi.spyOn(useTimelinesStore(), 'wipeStatuses').mockImplementation(
|
|
async () => {
|
|
/* no-op */
|
|
},
|
|
)
|
|
|
|
vi.spyOn(useNotificationsStore(), 'wipeStatuses').mockImplementation(
|
|
async () => {
|
|
/* no-op */
|
|
},
|
|
)
|
|
|
|
const store = useUsersStore()
|
|
const { storeAction, apiUrl } = actionKeys(action)
|
|
await store[storeAction](userId)
|
|
|
|
expect(mockFetch).to.have.been.calledWith(USER_API[apiUrl](userId), {
|
|
body: '{}',
|
|
...DEFAULT_OPTIONS(),
|
|
})
|
|
})
|
|
|
|
it('with expiration', async () => {
|
|
const mockFetch = vi.fn().mockResolvedValueOnce(
|
|
new Response(JSON.stringify({ id: userId }), {
|
|
headers: { 'Content-Type': 'application/json' },
|
|
}),
|
|
)
|
|
|
|
vi.stubGlobal('fetch', mockFetch)
|
|
|
|
vi.spyOn(useStatusesStore(), 'wipeUserStatuses').mockImplementation(
|
|
async () => {
|
|
/* no-op */
|
|
},
|
|
)
|
|
|
|
vi.spyOn(useTimelinesStore(), 'wipeStatuses').mockImplementation(
|
|
async () => {
|
|
/* no-op */
|
|
},
|
|
)
|
|
|
|
vi.spyOn(useNotificationsStore(), 'wipeStatuses').mockImplementation(
|
|
async () => {
|
|
/* no-op */
|
|
},
|
|
)
|
|
|
|
const store = useUsersStore()
|
|
const { storeAction, apiUrl } = actionKeys(action)
|
|
await store[storeAction](userId, 20)
|
|
const argument = action === 'mute' ? 'expires_in' : 'duration'
|
|
|
|
expect(mockFetch).to.have.been.calledWith(USER_API[apiUrl](userId), {
|
|
body: `{"${argument}":20}`,
|
|
...DEFAULT_OPTIONS(),
|
|
})
|
|
})
|
|
})
|
|
|
|
it.each(['mute', 'unmute'])('%s domain', async (action) => {
|
|
const mockFetch = vi.fn().mockResolvedValueOnce(
|
|
new Response(JSON.stringify({ id: userId }), {
|
|
headers: { 'Content-Type': 'application/json' },
|
|
}),
|
|
)
|
|
|
|
vi.stubGlobal('fetch', mockFetch)
|
|
|
|
const store = useUsersStore()
|
|
store.currentUser = mockUser()
|
|
store.currentUser.domainMutes = new Set()
|
|
if (action === 'unmute') {
|
|
store.currentUser.domainMutes.add('example.com')
|
|
}
|
|
await store[action + 'Domain']('example.com')
|
|
|
|
expect(mockFetch).to.have.been.calledWith(
|
|
USER_API.MASTODON_DOMAIN_BLOCKS_URL,
|
|
{
|
|
body: JSON.stringify({ domain: 'example.com' }),
|
|
...DEFAULT_OPTIONS(action === 'mute' ? 'POST' : 'DELETE'),
|
|
},
|
|
)
|
|
|
|
if (action === 'mute') {
|
|
expect(store.currentUser.domainMutes).to.include('example.com')
|
|
} else {
|
|
expect(store.currentUser.domainMutes).to.not.include('example.com')
|
|
}
|
|
})
|
|
|
|
it.each([
|
|
'muteDomain',
|
|
'unmuteDomain',
|
|
'muteUser',
|
|
'unmuteUser',
|
|
'blockUser',
|
|
'unblockUser',
|
|
])('%ss', async (action) => {
|
|
const store = useUsersStore()
|
|
store[action] = vi.fn().mockResolvedValue(async () => {
|
|
/* no-op */
|
|
})
|
|
store[action + 's'](['1', '2', '3'])
|
|
expect(store[action]).to.have.been.calledWith('1')
|
|
expect(store[action]).to.have.been.calledWith('2')
|
|
expect(store[action]).to.have.been.calledWith('3')
|
|
})
|
|
})
|
|
|
|
describe('getters', () => {
|
|
it('relationship returns a placeholder if relationship info is missing', () => {
|
|
const store = useUsersStore()
|
|
|
|
expect(store.relationship(userId)).to.eql({ id: userId, loading: true })
|
|
})
|
|
|
|
it('relationship returns a placeholder if relationship info is missing while user is present', () => {
|
|
const store = useUsersStore()
|
|
store.addNewUsers({ data: [mockUser()], timestamp: 1 })
|
|
|
|
expect(store.relationship(userId)).to.eql({ id: userId, loading: true })
|
|
})
|
|
|
|
it('findUser returns user with matching id', () => {
|
|
const store = useUsersStore()
|
|
store.addNewUsers({ data: [mockUser()], timestamp: 1 })
|
|
|
|
expect(store.findUser(userId).id).to.eql(userId)
|
|
})
|
|
|
|
it('findUserByName returns user with matching screen_name', () => {
|
|
const store = useUsersStore()
|
|
store.addNewUsers({ data: [mockUser()], timestamp: 1 })
|
|
|
|
expect(store.findUserByName(mockUser().screen_name).id).to.eql(userId)
|
|
})
|
|
|
|
it('findUserByName returns user with matching url', () => {
|
|
const store = useUsersStore()
|
|
store.addNewUsers({ data: [mockUser()], timestamp: 1 })
|
|
|
|
expect(store.findUserByUrl(mockUser().url).id).to.eql(userId)
|
|
})
|
|
})
|
|
})
|