fix: add tests for polls, improve a11y of poll form (#1239)
This commit is contained in:
parent
b45868bbfd
commit
37d3cac7d2
|
@ -1,6 +1,6 @@
|
||||||
<div class={computedClass} aria-busy={loading} >
|
<div class={computedClass} aria-busy={loading} >
|
||||||
{#if voted || expired }
|
{#if voted || expired }
|
||||||
<ul class="options" aria-label="Poll results">
|
<ul aria-label="Poll results">
|
||||||
{#each options as option}
|
{#each options as option}
|
||||||
<li class="option">
|
<li class="option">
|
||||||
<div class="option-text">
|
<div class="option-text">
|
||||||
|
@ -14,19 +14,21 @@
|
||||||
</ul>
|
</ul>
|
||||||
{:else}
|
{:else}
|
||||||
<form class="poll-form" aria-label="Vote on poll" on:submit="onSubmit(event)" ref:form>
|
<form class="poll-form" aria-label="Vote on poll" on:submit="onSubmit(event)" ref:form>
|
||||||
{#each options as option, i}
|
<ul aria-label="Poll choices">
|
||||||
<div class="poll-form-option">
|
{#each options as option, i}
|
||||||
<input type="{multiple ? 'checkbox' : 'radio'}"
|
<li class="poll-form-option">
|
||||||
id="poll-choice-{uuid}-{i}"
|
<input type="{multiple ? 'checkbox' : 'radio'}"
|
||||||
name="poll-choice-{uuid}"
|
id="poll-choice-{uuid}-{i}"
|
||||||
value="{i}"
|
name="poll-choice-{uuid}"
|
||||||
on:change="onChange()"
|
value="{i}"
|
||||||
>
|
on:change="onChange()"
|
||||||
<label for="poll-choice-{uuid}-{i}">
|
>
|
||||||
{option.title}
|
<label for="poll-choice-{uuid}-{i}">
|
||||||
</label>
|
{option.title}
|
||||||
</div>
|
</label>
|
||||||
{/each}
|
</li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
<button disabled={formDisabled} type="submit">Vote</button>
|
<button disabled={formDisabled} type="submit">Vote</button>
|
||||||
</form>
|
</form>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -71,13 +73,18 @@
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
ul.options {
|
ul {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
li.option {
|
li {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option {
|
||||||
margin: 0 0 10px 0;
|
margin: 0 0 10px 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -86,10 +93,6 @@
|
||||||
stroke-width: 10px;
|
stroke-width: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
li.option:last-child {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.option-text {
|
.option-text {
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
|
|
|
@ -9,6 +9,8 @@ import { followAccount, unfollowAccount } from '../src/routes/_api/follow'
|
||||||
import { updateCredentials } from '../src/routes/_api/updateCredentials'
|
import { updateCredentials } from '../src/routes/_api/updateCredentials'
|
||||||
import { reblogStatus } from '../src/routes/_api/reblog'
|
import { reblogStatus } from '../src/routes/_api/reblog'
|
||||||
import { submitMedia } from './submitMedia'
|
import { submitMedia } from './submitMedia'
|
||||||
|
import { voteOnPoll } from '../src/routes/_api/polls'
|
||||||
|
import { POLL_EXPIRY_DEFAULT } from '../src/routes/_static/polls'
|
||||||
|
|
||||||
global.fetch = fetch
|
global.fetch = fetch
|
||||||
global.File = FileApi.File
|
global.File = FileApi.File
|
||||||
|
@ -68,3 +70,15 @@ export async function unfollowAs (username, userToFollow) {
|
||||||
export async function updateUserDisplayNameAs (username, displayName) {
|
export async function updateUserDisplayNameAs (username, displayName) {
|
||||||
return updateCredentials(instanceName, users[username].accessToken, { display_name: displayName })
|
return updateCredentials(instanceName, users[username].accessToken, { display_name: displayName })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function createPollAs (username, content, options, multiple) {
|
||||||
|
return postStatus(instanceName, users[username].accessToken, content, null, null, false, null, 'public', {
|
||||||
|
options,
|
||||||
|
multiple,
|
||||||
|
expires_in: POLL_EXPIRY_DEFAULT
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function voteOnPollAs (username, pollId, choices) {
|
||||||
|
return voteOnPoll(instanceName, users[username].accessToken, pollId, choices.map(_ => _.toString()))
|
||||||
|
}
|
||||||
|
|
94
tests/spec/126-polls.js
Normal file
94
tests/spec/126-polls.js
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
import {
|
||||||
|
getNthStatusContent,
|
||||||
|
getNthStatusPollOption,
|
||||||
|
getNthStatusPollVoteButton,
|
||||||
|
getNthStatusPollForm,
|
||||||
|
getNthStatusPollResult,
|
||||||
|
sleep,
|
||||||
|
getNthStatusPollRefreshButton,
|
||||||
|
getNthStatusPollVoteCount,
|
||||||
|
getNthStatusRelativeDate, getUrl, goBack
|
||||||
|
} from '../utils'
|
||||||
|
import { loginAsFoobar } from '../roles'
|
||||||
|
import { createPollAs, voteOnPollAs } from '../serverActions'
|
||||||
|
|
||||||
|
fixture`126-polls.js`
|
||||||
|
.page`http://localhost:4002`
|
||||||
|
|
||||||
|
test('Can vote on polls', async t => {
|
||||||
|
await loginAsFoobar(t)
|
||||||
|
await createPollAs('admin', 'vote on my cool poll', ['yes', 'no'], false)
|
||||||
|
await t
|
||||||
|
.expect(getNthStatusContent(1).innerText).contains('vote on my cool poll')
|
||||||
|
.expect(getNthStatusPollVoteCount(1).innerText).eql('0 votes')
|
||||||
|
.click(getNthStatusPollOption(1, 2))
|
||||||
|
.click(getNthStatusPollVoteButton(1))
|
||||||
|
.expect(getNthStatusPollForm(1).exists).notOk({ timeout: 20000 })
|
||||||
|
.expect(getNthStatusPollResult(1, 1).innerText).eql('0% yes')
|
||||||
|
.expect(getNthStatusPollResult(1, 2).innerText).eql('100% no')
|
||||||
|
.expect(getNthStatusPollVoteCount(1).innerText).eql('1 vote')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Can vote on multiple-choice polls', async t => {
|
||||||
|
await loginAsFoobar(t)
|
||||||
|
await createPollAs('admin', 'vote on my other poll', ['yes', 'no', 'maybe'], true)
|
||||||
|
await t
|
||||||
|
.expect(getNthStatusContent(1).innerText).contains('vote on my other poll')
|
||||||
|
.click(getNthStatusPollOption(1, 1))
|
||||||
|
.click(getNthStatusPollOption(1, 3))
|
||||||
|
.click(getNthStatusPollVoteButton(1))
|
||||||
|
.expect(getNthStatusPollForm(1).exists).notOk({ timeout: 20000 })
|
||||||
|
.expect(getNthStatusPollResult(1, 1).innerText).eql('50% yes')
|
||||||
|
.expect(getNthStatusPollResult(1, 2).innerText).eql('0% no')
|
||||||
|
.expect(getNthStatusPollResult(1, 3).innerText).eql('50% maybe')
|
||||||
|
.expect(getNthStatusPollVoteCount(1).innerText).eql('2 votes')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Can update poll results', async t => {
|
||||||
|
const { poll } = await createPollAs('admin', 'vote on this poll', ['yes', 'no', 'maybe'], false)
|
||||||
|
const { id: pollId } = poll
|
||||||
|
await voteOnPollAs('baz', pollId, [1])
|
||||||
|
await voteOnPollAs('ExternalLinks', pollId, [1])
|
||||||
|
await voteOnPollAs('foobar', pollId, [2])
|
||||||
|
await sleep(1000)
|
||||||
|
await loginAsFoobar(t)
|
||||||
|
await t
|
||||||
|
.expect(getNthStatusContent(1).innerText).contains('vote on this poll')
|
||||||
|
.expect(getNthStatusPollForm(1).exists).notOk()
|
||||||
|
.expect(getNthStatusPollResult(1, 1).innerText).eql('0% yes')
|
||||||
|
.expect(getNthStatusPollResult(1, 2).innerText).eql('67% no')
|
||||||
|
.expect(getNthStatusPollResult(1, 3).innerText).eql('33% maybe')
|
||||||
|
.expect(getNthStatusPollVoteCount(1).innerText).eql('3 votes')
|
||||||
|
await sleep(1000)
|
||||||
|
await voteOnPollAs('quux', pollId, [0])
|
||||||
|
await sleep(1000)
|
||||||
|
await t
|
||||||
|
.click(getNthStatusPollRefreshButton(1))
|
||||||
|
.expect(getNthStatusPollResult(1, 1).innerText).eql('25% yes', { timeout: 20000 })
|
||||||
|
.expect(getNthStatusPollResult(1, 2).innerText).eql('50% no')
|
||||||
|
.expect(getNthStatusPollResult(1, 3).innerText).eql('25% maybe')
|
||||||
|
.expect(getNthStatusPollVoteCount(1).innerText).eql('4 votes')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Poll results refresh everywhere', async t => {
|
||||||
|
await loginAsFoobar(t)
|
||||||
|
await createPollAs('admin', 'another poll', ['yes', 'no'], false)
|
||||||
|
await t
|
||||||
|
.expect(getNthStatusContent(1).innerText).contains('another poll')
|
||||||
|
.click(getNthStatusRelativeDate(1))
|
||||||
|
.expect(getUrl()).contains('/statuses')
|
||||||
|
.expect(getNthStatusContent(1).innerText).contains('another poll')
|
||||||
|
.click(getNthStatusPollOption(1, 1))
|
||||||
|
.click(getNthStatusPollVoteButton(1))
|
||||||
|
.expect(getNthStatusPollForm(1).exists).notOk({ timeout: 20000 })
|
||||||
|
.expect(getNthStatusPollResult(1, 1).innerText).eql('100% yes')
|
||||||
|
.expect(getNthStatusPollResult(1, 2).innerText).eql('0% no')
|
||||||
|
.expect(getNthStatusPollVoteCount(1).innerText).eql('1 vote')
|
||||||
|
await goBack()
|
||||||
|
await t
|
||||||
|
.expect(getUrl()).eql('http://localhost:4002/')
|
||||||
|
.expect(getNthStatusPollForm(1).exists).notOk({ timeout: 20000 })
|
||||||
|
.expect(getNthStatusPollResult(1, 1).innerText).eql('100% yes')
|
||||||
|
.expect(getNthStatusPollResult(1, 2).innerText).eql('0% no')
|
||||||
|
.expect(getNthStatusPollVoteCount(1).innerText).eql('1 vote')
|
||||||
|
})
|
67
tests/spec/127-compose-polls.js
Normal file
67
tests/spec/127-compose-polls.js
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
import {
|
||||||
|
getNthStatusContent,
|
||||||
|
getNthStatusPollForm,
|
||||||
|
getNthStatusPollResult,
|
||||||
|
getNthStatusPollVoteCount,
|
||||||
|
pollButton,
|
||||||
|
getComposePollNthInput,
|
||||||
|
composePoll,
|
||||||
|
composePollMultipleChoice,
|
||||||
|
composePollExpiry, composePollAddButton, getComposePollRemoveNthButton, postStatusButton, composeInput
|
||||||
|
} from '../utils'
|
||||||
|
import { loginAsFoobar } from '../roles'
|
||||||
|
import { POLL_EXPIRY_DEFAULT } from '../../src/routes/_static/polls'
|
||||||
|
|
||||||
|
fixture`127-compose-polls.js`
|
||||||
|
.page`http://localhost:4002`
|
||||||
|
|
||||||
|
test('Can add and remove poll', async t => {
|
||||||
|
await loginAsFoobar(t)
|
||||||
|
await t
|
||||||
|
.expect(composePoll.exists).notOk()
|
||||||
|
.expect(pollButton.getAttribute('aria-label')).eql('Add poll')
|
||||||
|
.click(pollButton)
|
||||||
|
.expect(composePoll.exists).ok()
|
||||||
|
.expect(getComposePollNthInput(1).value).eql('')
|
||||||
|
.expect(getComposePollNthInput(2).value).eql('')
|
||||||
|
.expect(getComposePollNthInput(3).exists).notOk()
|
||||||
|
.expect(getComposePollNthInput(4).exists).notOk()
|
||||||
|
.expect(composePollMultipleChoice.checked).notOk()
|
||||||
|
.expect(composePollExpiry.value).eql(POLL_EXPIRY_DEFAULT.toString())
|
||||||
|
.expect(pollButton.getAttribute('aria-label')).eql('Remove poll')
|
||||||
|
.click(pollButton)
|
||||||
|
.expect(composePoll.exists).notOk()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Can add and remove poll options', async t => {
|
||||||
|
await loginAsFoobar(t)
|
||||||
|
await t
|
||||||
|
.expect(composePoll.exists).notOk()
|
||||||
|
.expect(pollButton.getAttribute('aria-label')).eql('Add poll')
|
||||||
|
.click(pollButton)
|
||||||
|
.expect(composePoll.exists).ok()
|
||||||
|
.typeText(getComposePollNthInput(1), 'first', { paste: true })
|
||||||
|
.typeText(getComposePollNthInput(2), 'second', { paste: true })
|
||||||
|
.click(composePollAddButton)
|
||||||
|
.typeText(getComposePollNthInput(3), 'third', { paste: true })
|
||||||
|
.expect(getComposePollNthInput(1).value).eql('first')
|
||||||
|
.expect(getComposePollNthInput(2).value).eql('second')
|
||||||
|
.expect(getComposePollNthInput(3).value).eql('third')
|
||||||
|
.expect(getComposePollNthInput(4).exists).notOk()
|
||||||
|
.click(getComposePollRemoveNthButton(2))
|
||||||
|
.expect(getComposePollNthInput(1).value).eql('first')
|
||||||
|
.expect(getComposePollNthInput(2).value).eql('third')
|
||||||
|
.expect(getComposePollNthInput(3).exists).notOk()
|
||||||
|
.expect(getComposePollNthInput(4).exists).notOk()
|
||||||
|
.click(composePollAddButton)
|
||||||
|
.typeText(getComposePollNthInput(3), 'fourth', { paste: true })
|
||||||
|
.typeText(composeInput, 'Vote on my poll!!!', { paste: true })
|
||||||
|
.click(postStatusButton)
|
||||||
|
.expect(getNthStatusContent(1).innerText).contains('Vote on my poll!!!')
|
||||||
|
.expect(getNthStatusPollForm(1).exists).notOk()
|
||||||
|
.expect(getNthStatusPollResult(1, 1).innerText).eql('0% first')
|
||||||
|
.expect(getNthStatusPollResult(1, 2).innerText).eql('0% third')
|
||||||
|
.expect(getNthStatusPollResult(1, 3).innerText).eql('0% fourth')
|
||||||
|
.expect(getNthStatusPollResult(1, 4).exists).notOk()
|
||||||
|
.expect(getNthStatusPollVoteCount(1).innerText).eql('0 votes')
|
||||||
|
})
|
|
@ -22,6 +22,7 @@ export const composeButton = $('.compose-box-button')
|
||||||
export const composeLengthIndicator = $('.compose-box-length')
|
export const composeLengthIndicator = $('.compose-box-length')
|
||||||
export const emojiButton = $('.compose-box-toolbar button:first-child')
|
export const emojiButton = $('.compose-box-toolbar button:first-child')
|
||||||
export const mediaButton = $('.compose-box-toolbar button:nth-child(2)')
|
export const mediaButton = $('.compose-box-toolbar button:nth-child(2)')
|
||||||
|
export const pollButton = $('.compose-box-toolbar button:nth-child(3)')
|
||||||
export const postPrivacyButton = $('.compose-box-toolbar button:nth-child(4)')
|
export const postPrivacyButton = $('.compose-box-toolbar button:nth-child(4)')
|
||||||
export const contentWarningButton = $('.compose-box-toolbar button:nth-child(5)')
|
export const contentWarningButton = $('.compose-box-toolbar button:nth-child(5)')
|
||||||
export const emailInput = $('input#user_email')
|
export const emailInput = $('input#user_email')
|
||||||
|
@ -58,6 +59,11 @@ export const composeModalContentWarningInput = $('.modal-dialog .content-warning
|
||||||
export const composeModalEmojiButton = $('.modal-dialog .compose-box-toolbar button:nth-child(1)')
|
export const composeModalEmojiButton = $('.modal-dialog .compose-box-toolbar button:nth-child(1)')
|
||||||
export const composeModalPostPrivacyButton = $('.modal-dialog .compose-box-toolbar button:nth-child(4)')
|
export const composeModalPostPrivacyButton = $('.modal-dialog .compose-box-toolbar button:nth-child(4)')
|
||||||
|
|
||||||
|
export const composePoll = $('.compose-poll')
|
||||||
|
export const composePollMultipleChoice = $('.compose-poll input[type="checkbox"]')
|
||||||
|
export const composePollExpiry = $('.compose-poll select')
|
||||||
|
export const composePollAddButton = $('.compose-poll button:last-of-type')
|
||||||
|
|
||||||
export const postPrivacyDialogButtonUnlisted = $('[aria-label="Post privacy dialog"] li:nth-child(2) button')
|
export const postPrivacyDialogButtonUnlisted = $('[aria-label="Post privacy dialog"] li:nth-child(2) button')
|
||||||
|
|
||||||
export const accountProfileFilterStatuses = $('.account-profile-filters li:nth-child(1)')
|
export const accountProfileFilterStatuses = $('.account-profile-filters li:nth-child(1)')
|
||||||
|
@ -220,6 +226,38 @@ export function getNthPostPrivacyButton (n) {
|
||||||
return $(`${getNthStatusSelector(n)} .compose-box-toolbar button:nth-child(4)`)
|
return $(`${getNthStatusSelector(n)} .compose-box-toolbar button:nth-child(4)`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getNthStatusPollOption (n, i) {
|
||||||
|
return $(`${getNthStatusSelector(n)} .poll li:nth-child(${i}) input`)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getNthStatusPollVoteButton (n) {
|
||||||
|
return $(`${getNthStatusSelector(n)} .poll button`)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getNthStatusPollForm (n) {
|
||||||
|
return $(`${getNthStatusSelector(n)} .poll form`)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getNthStatusPollResult (n, i) {
|
||||||
|
return $(`${getNthStatusSelector(n)} .poll li:nth-child(${i})`)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getNthStatusPollRefreshButton (n) {
|
||||||
|
return $(`${getNthStatusSelector(n)} button.poll-stat`)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getNthStatusPollVoteCount (n) {
|
||||||
|
return $(`${getNthStatusSelector(n)} .poll .poll-stat:nth-child(1) .poll-stat-text`)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getComposePollNthInput (n) {
|
||||||
|
return $(`.compose-poll input[type="text"]:nth-of-type(${n})`)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getComposePollRemoveNthButton (n) {
|
||||||
|
return $(`.compose-poll button:nth-of-type(${n})`)
|
||||||
|
}
|
||||||
|
|
||||||
export function getNthAutosuggestionResult (n) {
|
export function getNthAutosuggestionResult (n) {
|
||||||
return $(`.compose-autosuggest-list-item:nth-child(${n})`)
|
return $(`.compose-autosuggest-list-item:nth-child(${n})`)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue