Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: align polls state management with API #13516

Merged
merged 1 commit into from
Oct 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/__mocks__/capabilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ export const mockedCapabilities: Capabilities = {
'mention-permissions',
'edit-messages-note-to-self',
'archived-conversations',
'talk-polls-drafts',
],
'features-local': [
'favorites',
Expand Down
1 change: 1 addition & 0 deletions src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,7 @@ export const POLL = {
STATUS: {
OPEN: 0,
CLOSED: 1,
DRAFT: 2,
},

MODE: {
Expand Down
39 changes: 39 additions & 0 deletions src/services/pollService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@ import { generateOcsUrl } from '@nextcloud/router'

import type {
closePollResponse,
createPollDraftResponse,
createPollParams,
createPollResponse,
deletePollDraftResponse,
getPollDraftsResponse,
getPollResponse,
votePollParams,
votePollResponse,
Expand All @@ -31,9 +34,35 @@ const createPoll = async ({ token, question, options, resultMode, maxVotes }: cr
options,
resultMode,
maxVotes,
draft: false,
} as createPollParams)
}

/**
* @param payload The payload
* @param payload.token The conversation token
* @param payload.question The question of the poll
* @param payload.options The options participants can vote for
* @param payload.resultMode Result mode of the poll (0 - always visible | 1 - hidden until the poll is closed)
* @param payload.maxVotes Maximum amount of options a user can vote for (0 - unlimited | 1 - single answer)
*/
const createPollDraft = async ({ token, question, options, resultMode, maxVotes }: createPollPayload): createPollDraftResponse => {
return axios.post(generateOcsUrl('apps/spreed/api/v1/poll/{token}', { token }), {
question,
options,
resultMode,
maxVotes,
draft: true,
} as createPollParams)
}

/**
* @param token The conversation token
*/
const getPollDrafts = async (token: string): getPollDraftsResponse => {
return axios.get(generateOcsUrl('apps/spreed/api/v1/poll/{token}/drafts', { token }))
}

/**
* @param token The conversation token
* @param pollId Id of the poll
Expand All @@ -60,10 +89,20 @@ const submitVote = async (token: string, pollId: string, optionIds: votePollPara
const endPoll = async (token: string, pollId: string): closePollResponse => {
return axios.delete(generateOcsUrl('apps/spreed/api/v1/poll/{token}/{pollId}', { token, pollId }))
}
/**
* @param token The conversation token
* @param pollId Id of the poll draft
*/
const deletePollDraft = async (token: string, pollId: string): deletePollDraftResponse => {
return axios.delete(generateOcsUrl('apps/spreed/api/v1/poll/{token}/{pollId}', { token, pollId }))
}

export {
createPoll,
createPollDraft,
getPollDrafts,
getPollData,
submitVote,
endPoll,
deletePollDraft,
}
227 changes: 145 additions & 82 deletions src/stores/__tests__/polls.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import { setActivePinia, createPinia } from 'pinia'
import { ATTENDEE } from '../../constants.js'
import {
createPoll,
createPollDraft,
getPollData,
getPollDrafts,
submitVote,
endPoll,
} from '../../services/pollService.ts'
Expand All @@ -17,9 +19,12 @@ import { usePollsStore } from '../polls.ts'

jest.mock('../../services/pollService', () => ({
createPoll: jest.fn(),
createPollDraft: jest.fn(),
getPollData: jest.fn(),
getPollDrafts: jest.fn(),
submitVote: jest.fn(),
endPoll: jest.fn(),
deletePollDraft: jest.fn(),
}))

describe('pollsStore', () => {
Expand All @@ -31,18 +36,23 @@ describe('pollsStore', () => {
resultMode: 0,
maxVotes: 1,
}
const pollDraft = {
...pollRequest,
status: 2,
id: 1,
actorType: ATTENDEE.ACTOR_TYPE.USERS,
actorId: 'user',
actorDisplayName: 'User',
}
const poll = {
...pollRequest,
id: 1,
question: 'What is the answer to the universe?',
options: ['42', '24'],
votes: [],
numVoters: 0,
actorType: ATTENDEE.ACTOR_TYPE.USERS,
actorId: 'user',
actorDisplayName: 'User',
status: 0,
resultMode: 0,
maxVotes: 1,
votedSelf: [],
}
const pollWithVote = {
Expand Down Expand Up @@ -90,110 +100,163 @@ describe('pollsStore', () => {
pollsStore = usePollsStore()
})

it('receives a poll from server and adds it to the store', async () => {
// Arrange
const response = generateOCSResponse({ payload: poll })
getPollData.mockResolvedValue(response)
describe('polls management', () => {
it('receives a poll from server and adds it to the store', async () => {
// Arrange
const response = generateOCSResponse({ payload: poll })
getPollData.mockResolvedValue(response)

// Act
await pollsStore.getPollData({ token: TOKEN, pollId: poll.id })
// Act
await pollsStore.getPollData({ token: TOKEN, pollId: poll.id })

// Assert
expect(pollsStore.getPoll(TOKEN, poll.id)).toMatchObject(poll)
})
// Assert
expect(pollsStore.getPoll(TOKEN, poll.id)).toMatchObject(poll)
})

it('debounces a function to get a poll from server', async () => {
// Arrange
jest.useFakeTimers()
const response = generateOCSResponse({ payload: poll })
getPollData.mockResolvedValue(response)
it('debounces a function to get a poll from server', async () => {
// Arrange
jest.useFakeTimers()
const response = generateOCSResponse({ payload: poll })
getPollData.mockResolvedValue(response)

// Act
pollsStore.debounceGetPollData({ token: TOKEN, pollId: poll.id })
jest.advanceTimersByTime(5000)
await flushPromises()
// Act
pollsStore.debounceGetPollData({ token: TOKEN, pollId: poll.id })
jest.advanceTimersByTime(5000)
await flushPromises()

// Assert
expect(pollsStore.debouncedFunctions[TOKEN][poll.id]).toBeDefined()
expect(pollsStore.getPoll(TOKEN, poll.id)).toMatchObject(poll)
})
// Assert
expect(pollsStore.debouncedFunctions[TOKEN][poll.id]).toBeDefined()
expect(pollsStore.getPoll(TOKEN, poll.id)).toMatchObject(poll)
})

it('creates a poll and adds it to the store', async () => {
// Arrange
const response = generateOCSResponse({ payload: poll })
createPoll.mockResolvedValue(response)
it('creates a poll and adds it to the store', async () => {
// Arrange
const response = generateOCSResponse({ payload: poll })
createPoll.mockResolvedValue(response)

// Act
await pollsStore.createPoll({ token: TOKEN, form: pollRequest })
// Act
await pollsStore.createPoll({ token: TOKEN, form: pollRequest })

// Assert
expect(pollsStore.getPoll(TOKEN, poll.id)).toMatchObject(poll)
})
// Assert
expect(pollsStore.getPoll(TOKEN, poll.id)).toMatchObject(poll)
})

it('submits a vote and updates it in the store', async () => {
// Arrange
pollsStore.addPoll({ token: TOKEN, poll })
const response = generateOCSResponse({ payload: pollWithVote })
submitVote.mockResolvedValue(response)
it('submits a vote and updates it in the store', async () => {
// Arrange
pollsStore.addPoll({ token: TOKEN, poll })
const response = generateOCSResponse({ payload: pollWithVote })
submitVote.mockResolvedValue(response)

// Act
await pollsStore.submitVote({ token: TOKEN, pollId: poll.id, optionIds: [0] })
// Act
await pollsStore.submitVote({ token: TOKEN, pollId: poll.id, optionIds: [0] })

// Assert
expect(pollsStore.getPoll(TOKEN, poll.id)).toMatchObject(pollWithVote)
})
// Assert
expect(pollsStore.getPoll(TOKEN, poll.id)).toMatchObject(pollWithVote)
})

it('ends a poll and updates it in the store', async () => {
// Arrange
pollsStore.addPoll({ token: TOKEN, poll: pollWithVote })
const response = generateOCSResponse({ payload: pollWithVoteEnded })
endPoll.mockResolvedValue(response)
it('ends a poll and updates it in the store', async () => {
// Arrange
pollsStore.addPoll({ token: TOKEN, poll: pollWithVote })
const response = generateOCSResponse({ payload: pollWithVoteEnded })
endPoll.mockResolvedValue(response)

// Act
await pollsStore.endPoll({ token: TOKEN, pollId: poll.id })
// Act
await pollsStore.endPoll({ token: TOKEN, pollId: poll.id })

// Assert
expect(pollsStore.getPoll(TOKEN, poll.id)).toMatchObject(pollWithVoteEnded)
// Assert
expect(pollsStore.getPoll(TOKEN, poll.id)).toMatchObject(pollWithVoteEnded)
})
})

it('adds poll toast to the queue from message', async () => {
// Act
pollsStore.addPollToast({ token: TOKEN, message: messageWithPoll })
describe('drafts management', () => {
it('receives drafts from server and adds them to the store', async () => {
// Arrange
const response = generateOCSResponse({ payload: [pollDraft] })
getPollDrafts.mockResolvedValue(response)

// Assert
expect(pollsStore.isNewPoll(poll.id)).toBeTruthy()
})
// Act
await pollsStore.getPollDrafts(TOKEN)

it('sets active poll from the toast', async () => {
// Arrange
pollsStore.addPollToast({ token: TOKEN, message: messageWithPoll })
// Assert
expect(pollsStore.getDrafts(TOKEN)).toMatchObject([pollDraft])
})

// Act
pollsStore.pollToastsQueue[poll.id].options.onClick()
it('receives no drafts from server', async () => {
// Arrange
const response = generateOCSResponse({ payload: [] })
getPollDrafts.mockResolvedValue(response)

// Assert
expect(pollsStore.activePoll).toMatchObject({ token: TOKEN, id: poll.id, name: poll.question })
})
// Act
await pollsStore.getPollDrafts(TOKEN)

// Assert
expect(pollsStore.getDrafts(TOKEN)).toMatchObject([])
})

it('creates a draft and adds it to the store', async () => {
// Arrange
const response = generateOCSResponse({ payload: pollDraft })
createPollDraft.mockResolvedValue(response)

// Act
await pollsStore.createPollDraft({ token: TOKEN, form: pollRequest })

it('removes active poll', async () => {
// Arrange
pollsStore.setActivePoll({ token: TOKEN, pollId: poll.id, name: poll.question })
// Assert
expect(pollsStore.getDrafts(TOKEN, poll.id)).toMatchObject([pollDraft])
})

// Act
pollsStore.removeActivePoll()
it('deletes a draft from the store', async () => {
// Arrange
pollsStore.addPollDraft({ token: TOKEN, draft: pollDraft })

// Assert
expect(pollsStore.activePoll).toEqual(null)
// Act
await pollsStore.deletePollDraft({ token: TOKEN, pollId: pollDraft.id })

// Assert
expect(pollsStore.getDrafts(TOKEN, poll.id)).toMatchObject([])
})
})

it('hides all poll toasts', async () => {
// Arrange
pollsStore.addPollToast({ token: TOKEN, message: messageWithPoll })
describe('poll toasts in call', () => {
it('adds poll toast to the queue from message', async () => {
// Act
pollsStore.addPollToast({ token: TOKEN, message: messageWithPoll })

// Assert
expect(pollsStore.isNewPoll(poll.id)).toBeTruthy()
})

it('sets active poll from the toast', async () => {
// Arrange
pollsStore.addPollToast({ token: TOKEN, message: messageWithPoll })

// Act
pollsStore.pollToastsQueue[poll.id].options.onClick()

// Assert
expect(pollsStore.activePoll).toMatchObject({ token: TOKEN, id: poll.id, name: poll.question })
})

it('removes active poll', async () => {
// Arrange
pollsStore.setActivePoll({ token: TOKEN, pollId: poll.id, name: poll.question })

// Act
pollsStore.removeActivePoll()

// Assert
expect(pollsStore.activePoll).toEqual(null)
})

it('hides all poll toasts', async () => {
// Arrange
pollsStore.addPollToast({ token: TOKEN, message: messageWithPoll })

// Act
pollsStore.hideAllPollToasts()
// Act
pollsStore.hideAllPollToasts()

// Assert
expect(pollsStore.pollToastsQueue).toMatchObject({})
// Assert
expect(pollsStore.pollToastsQueue).toMatchObject({})
})
})
})
Loading
Loading