Skip to content

Commit

Permalink
feat: align polls state management with API
Browse files Browse the repository at this point in the history
Signed-off-by: Maksim Sukharev <antreesy.web@gmail.com>
  • Loading branch information
Antreesy committed Oct 11, 2024
1 parent 58c1611 commit 06413e0
Show file tree
Hide file tree
Showing 6 changed files with 251 additions and 85 deletions.
1 change: 1 addition & 0 deletions src/__mocks__/capabilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ export const mockedCapabilities: Capabilities = {
'chat-reference-id',
'mention-permissions',
'edit-messages-note-to-self',
'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

0 comments on commit 06413e0

Please sign in to comment.