From 20c9ab0f65588175e2c9abb40dd57efb6de18594 Mon Sep 17 00:00:00 2001
From: hom3mad3 <8156337+hom3mad3@users.noreply.github.com>
Date: Thu, 5 Dec 2024 12:43:26 +0100
Subject: [PATCH] polls: refactor PollDetail components
---
.../polls/static/PollDetail/CharCounter.jsx | 13 +-
.../polls/static/PollDetail/ChoiceRow.jsx | 97 +++
.../polls/static/PollDetail/PollChoice.jsx | 96 +++
.../static/PollDetail/PollOpenQuestion.jsx | 61 +-
.../polls/static/PollDetail/PollQuestion.jsx | 170 -----
.../polls/static/PollDetail/PollQuestions.jsx | 73 +-
.../static/PollDetail/TextareaWithCounter.jsx | 54 ++
.../static/__tests__/CharCounter.jest.jsx | 2 +-
.../polls/static/__tests__/ChoiceRow.jest.jsx | 97 +++
.../__tests__/EditPollOpenQuestion.jest.jsx | 4 +-
.../__tests__/EditPollQuestion.jest.jsx | 8 +-
.../static/__tests__/PollQuestion.jest.jsx | 130 ----
.../__snapshots__/CharCounter.jest.jsx.snap | 14 +-
.../__snapshots__/PollQuestion.jest.jsx.snap | 649 ------------------
.../__tests__/__testdata__/QUESTION_OBJECT.js | 37 +-
changelog/8462.md | 9 +
16 files changed, 476 insertions(+), 1038 deletions(-)
create mode 100644 adhocracy4/polls/static/PollDetail/ChoiceRow.jsx
create mode 100644 adhocracy4/polls/static/PollDetail/PollChoice.jsx
delete mode 100644 adhocracy4/polls/static/PollDetail/PollQuestion.jsx
create mode 100644 adhocracy4/polls/static/PollDetail/TextareaWithCounter.jsx
create mode 100644 adhocracy4/polls/static/__tests__/ChoiceRow.jest.jsx
delete mode 100644 adhocracy4/polls/static/__tests__/PollQuestion.jest.jsx
delete mode 100644 adhocracy4/polls/static/__tests__/__snapshots__/PollQuestion.jest.jsx.snap
create mode 100644 changelog/8462.md
diff --git a/adhocracy4/polls/static/PollDetail/CharCounter.jsx b/adhocracy4/polls/static/PollDetail/CharCounter.jsx
index b2434f295..e287b2a02 100644
--- a/adhocracy4/polls/static/PollDetail/CharCounter.jsx
+++ b/adhocracy4/polls/static/PollDetail/CharCounter.jsx
@@ -1,9 +1,16 @@
import React from 'react'
+import django from 'django'
-export const CharCounter = (props) => {
- const current = props.value.length
+const translated = {
+ characters: django.gettext('characters')
+}
+
+export const CharCounter = ({ value, max, id }) => {
+ const current = value.length
return (
- {current}/{props.max}
+
+ {current}/{max} {translated.characters}
+
)
}
diff --git a/adhocracy4/polls/static/PollDetail/ChoiceRow.jsx b/adhocracy4/polls/static/PollDetail/ChoiceRow.jsx
new file mode 100644
index 000000000..c3d65582a
--- /dev/null
+++ b/adhocracy4/polls/static/PollDetail/ChoiceRow.jsx
@@ -0,0 +1,97 @@
+import React, { useState, useEffect } from 'react'
+import django from 'django'
+import { TextareaWithCounter } from './TextareaWithCounter'
+
+const translated = {
+ other: django.gettext('other')
+}
+
+const ChoiceInput = ({
+ type,
+ choice,
+ checked,
+ onInputChange,
+ disabled
+}) => (
+ onInputChange(event, choice.is_other_choice)}
+ disabled={disabled}
+ aria-describedby={'textarea-with-counter-' + choice.id}
+ />
+)
+
+// eslint-disable-next-line react/display-name
+export const ChoiceRow = React.memo(({
+ choice,
+ checked,
+ onInputChange,
+ type,
+ disabled,
+ otherChoiceAnswer,
+ onOtherChange,
+ isAuthenticated,
+ isReadOnly,
+ errors
+}) => {
+ const [textareaValue, setTextareaValue] = useState(otherChoiceAnswer)
+ const [showTextarea, setShowTextarea] = useState(false)
+
+ // When the choice is selected or changed, update the textarea visibility
+ useEffect(() => {
+ if (checked && choice.is_other_choice) {
+ setShowTextarea(true)
+ } else {
+ setShowTextarea(false)
+ }
+ }, [checked, choice.is_other_choice])
+
+ const handleChange = (event, isOtherChoice) => {
+ // Update the checkbox/radio button state
+ onInputChange(event, isOtherChoice)
+
+ // If the "Other" option is selected, show the textarea
+ if (isOtherChoice && event.target.checked) {
+ setShowTextarea(true)
+ } else {
+ setShowTextarea(false)
+ }
+ }
+
+ const handleTextareaChange = (event) => {
+ // Preserve the value of the textarea even if options are changed
+ setTextareaValue(event.target.value)
+ onOtherChange(event)
+ }
+
+ return (
+
+ )
+})
diff --git a/adhocracy4/polls/static/PollDetail/PollChoice.jsx b/adhocracy4/polls/static/PollDetail/PollChoice.jsx
new file mode 100644
index 000000000..e79bc3669
--- /dev/null
+++ b/adhocracy4/polls/static/PollDetail/PollChoice.jsx
@@ -0,0 +1,96 @@
+import React, { useEffect, useState } from 'react'
+import django from 'django'
+import { ChoiceRow } from './ChoiceRow'
+
+const translated = {
+ multiple: django.gettext('Multiple answers are possible.')
+}
+
+export const PollChoice = (props) => {
+ const getUserAnswer = () => {
+ const userAnswerId = props.question.other_choice_user_answer
+ const userAnswer = props.question.other_choice_answers.find(oc => oc.vote_id === userAnswerId)
+ return props.question.other_choice_answer
+ ? props.question.other_choice_answer
+ : ((userAnswerId && userAnswer)
+ ? userAnswer.answer
+ : ''
+ )
+ }
+
+ const [userChoices, setUserChoices] = useState([])
+ const [otherChoiceAnswer, setOtherChoiceAnswer] = useState(getUserAnswer())
+ const [errors, setErrors] = useState()
+
+ const multiHelpText = props.question.multiple_choice ?
{translated.multiple}
: null
+ const questionHelpText = props.question.help_text ? {props.question.help_text}
: null
+ const userAllowedVote = props.question.authenticated || props.allowUnregisteredUsers
+
+ useEffect(() => {
+ setUserChoices(props.question.userChoices || [])
+ setErrors(props.errors)
+ }, [props.question.userChoices, props.errors])
+
+ const findOtherChoice = () => {
+ return props.question.choices.find(c => c.is_other_choice)
+ }
+
+ const handleSingleChange = (event, isOther) => {
+ const choiceId = parseInt(event.target.value)
+ setUserChoices([choiceId])
+ props.onSingleChange(props.question.id, choiceId)
+ if (!isOther) {
+ setOtherChoiceAnswer('')
+ props.onOtherChange(props.question.id, '', findOtherChoice())
+ }
+ }
+
+ const handleMultiChange = (event, isOther) => {
+ const choiceId = parseInt(event.target.value)
+ const newChoices = userChoices.includes(choiceId)
+ ? userChoices.filter(id => id !== choiceId)
+ : [...userChoices, choiceId]
+
+ setUserChoices(newChoices)
+ props.onMultiChange(props.question.id, choiceId)
+
+ if (!newChoices.includes(findOtherChoice()?.id)) {
+ setOtherChoiceAnswer('')
+ props.onOtherChange(props.question.id, '', findOtherChoice())
+ }
+ }
+
+ const handleOtherChange = (event) => {
+ const otherAnswer = event.target.value
+ setOtherChoiceAnswer(otherAnswer)
+ props.onOtherChange(props.question.id, otherAnswer)
+ }
+
+ return (
+
+
{props.question.label}
+ {questionHelpText}
+ {multiHelpText}
+
+ {props.question.choices.map((choice) => {
+ const checked = userChoices.indexOf(choice.id) !== -1
+ return (
+
+ )
+ })}
+
+
+ )
+}
diff --git a/adhocracy4/polls/static/PollDetail/PollOpenQuestion.jsx b/adhocracy4/polls/static/PollDetail/PollOpenQuestion.jsx
index dc7d40e73..188a89ba4 100644
--- a/adhocracy4/polls/static/PollDetail/PollOpenQuestion.jsx
+++ b/adhocracy4/polls/static/PollDetail/PollOpenQuestion.jsx
@@ -1,48 +1,47 @@
import React, { useState } from 'react'
-import { CharCounter } from './CharCounter'
-
-export const PollOpenQuestion = (props) => {
- // | Function to define state
+import { TextareaWithCounter } from './TextareaWithCounter'
+export const PollOpenQuestion = ({
+ question,
+ allowUnregisteredUsers,
+ onOpenChange,
+ errors
+}) => {
const getUserOpenAnswer = () => {
- const userAnswerId = props.question.userAnswer
- const userAnswer = props.question.answers.find(oa => oa.id === userAnswerId)
- return props.question.open_answer
- ? props.question.open_answer
- : ((userAnswerId && userAnswer)
- ? userAnswer.answer
- : ''
- )
+ const userAnswerId = question.userAnswer
+ const userAnswer = question.answers.find((oa) => oa.id === userAnswerId)
+ return question.open_answer
+ ? question.open_answer
+ : userAnswerId && userAnswer
+ ? userAnswer.answer
+ : ''
}
const [userAnswer, setUserAnswer] = useState(getUserOpenAnswer())
- const questionHelpText = props.question.help_text ? {props.question.help_text}
: null
- const maxlength = 750
- const userAllowedVote = props.question.authenticated || props.allowUnregisteredUsers
+ const questionHelpText = question.help_text
+ ? (
+ {question.help_text}
+ )
+ : null
+ const userAllowedVote = question.authenticated || allowUnregisteredUsers
const handleOpenChange = (event) => {
setUserAnswer(event.target.value)
- props.onOpenChange(props.question.id, event.target.value)
+ onOpenChange(question.id, event.target.value)
}
return (
-
{props.question.label}
+
{question.label}
{questionHelpText}
-
+
)
}
diff --git a/adhocracy4/polls/static/PollDetail/PollQuestion.jsx b/adhocracy4/polls/static/PollDetail/PollQuestion.jsx
deleted file mode 100644
index c5661860f..000000000
--- a/adhocracy4/polls/static/PollDetail/PollQuestion.jsx
+++ /dev/null
@@ -1,170 +0,0 @@
-import React, { useEffect, useState } from 'react'
-import django from 'django'
-import { CharCounter } from './CharCounter'
-import FormFieldError from '../../../static/FormFieldError'
-
-const translated = {
- multiple: django.gettext('Multiple answers are possible.'),
- other: django.gettext('other')
-}
-
-export const PollQuestion = (props) => {
- // | Function to define state
-
- const getUserAnswer = () => {
- const userAnswerId = props.question.other_choice_user_answer
- const userAnswer = props.question.other_choice_answers.find(oc => oc.vote_id === userAnswerId)
- return props.question.other_choice_answer
- ? props.question.other_choice_answer
- : ((userAnswerId && userAnswer)
- ? userAnswer.answer
- : ''
- )
- }
-
- const [userChoices, setUserChoices] = useState([])
- const [otherChoiceAnswer, setOtherChoiceAnswer] = useState(getUserAnswer())
- const [errors, setErrors] = useState()
- const multiHelpText = props.question.multiple_choice ? {translated.multiple}
: null
- const questionHelpText = props.question.help_text ? {props.question.help_text}
: null
- const maxlength = 250
- const userAllowedVote = props.question.authenticated || props.allowUnregisteredUsers
-
- // eslint-disable-next-line react-hooks/exhaustive-deps
- useEffect(() => {
- setUserChoices(props.question.userChoices || [])
- setErrors(props.errors)
- })
-
- const handleSingleChange = (event, isOther) => {
- const choiceId = parseInt(event.target.value)
- setUserChoices([choiceId])
- props.onSingleChange(props.question.id, choiceId)
- if (!isOther) {
- setOtherChoiceAnswer('')
- props.onOtherChange(props.question.id, '', findOtherChoice())
- }
- }
-
- const handleMultiChange = (event, isOther) => {
- const choiceId = parseInt(event.target.value)
- setUserChoices(prevState => [...prevState, choiceId])
- setOtherChoiceAnswer(otherChoiceAnswer)
- props.onMultiChange(props.question.id, choiceId)
- if (isOther) {
- setOtherChoiceAnswer('')
- } else {
- props.onOtherChange(props.question.id, otherChoiceAnswer, findOtherChoice())
- }
- }
-
- const handleOtherChange = (event) => {
- const otherAnswer = event.target.value
- setOtherChoiceAnswer(otherAnswer)
- props.onOtherChange(props.question.id, otherAnswer)
- }
-
- const findOtherChoice = () => {
- return props.question.choices.find(c => c.is_other_choice)
- }
-
- return (
-
- )
-}
diff --git a/adhocracy4/polls/static/PollDetail/PollQuestions.jsx b/adhocracy4/polls/static/PollDetail/PollQuestions.jsx
index ae3ed4d16..9ec88c944 100644
--- a/adhocracy4/polls/static/PollDetail/PollQuestions.jsx
+++ b/adhocracy4/polls/static/PollDetail/PollQuestions.jsx
@@ -1,7 +1,7 @@
import React from 'react'
import django from 'django'
-import { PollQuestion } from './PollQuestion'
+import { PollChoice } from './PollChoice'
import { PollOpenQuestion } from './PollOpenQuestion'
import PollResults from './PollResults'
@@ -383,7 +383,7 @@ class PollQuestions extends React.Component {
<>
{this.state.showResults
? (
-
+
{this.state.result.map((question, idx) => (
))}
@@ -396,32 +396,35 @@ class PollQuestions extends React.Component {
)
: (
- {this.state.questions.map((question, idx) =>
- question.is_open
- ? (
-
- this.handleVoteOpen(questionId, voteData)}
- />
- )
- : (
-
- this.handleVoteSingle(questionId, voteData)}
- onMultiChange={(questionId, voteData) =>
- this.handleVoteMulti(questionId, voteData)}
- onOtherChange={(questionId, voteAnswer, otherChoice) =>
- this.handleVoteOther(questionId, voteAnswer, otherChoice)}
- errors={this.state.errors}
- />
- )
- )}
+
this.removeAlert()} {...this.state.alert} />
{this.isReadOnly()
@@ -448,14 +451,12 @@ class PollQuestions extends React.Component {
{this.state.allowUnregisteredUsers &&
this.state.questions.length > 0 &&
!this.state.questions[0].authenticated && (
- <>
- this.setState({ captcha: val })}
- apiUrl={this.props.captchaUrl}
- name="id_captcheck"
- refresh={this.state.refreshCaptcha}
- />
- >
+ this.setState({ captcha: val })}
+ apiUrl={this.props.captchaUrl}
+ name="id_captcheck"
+ refresh={this.state.refreshCaptcha}
+ />
)}
{this.buttonVote}
diff --git a/adhocracy4/polls/static/PollDetail/TextareaWithCounter.jsx b/adhocracy4/polls/static/PollDetail/TextareaWithCounter.jsx
new file mode 100644
index 000000000..d6dbdc8f3
--- /dev/null
+++ b/adhocracy4/polls/static/PollDetail/TextareaWithCounter.jsx
@@ -0,0 +1,54 @@
+import React from 'react'
+import django from 'django'
+import { CharCounter } from './CharCounter'
+import FormFieldError from '../../../static/FormFieldError'
+
+const translated = {
+ specify: django.gettext('Please specify:')
+}
+
+export const TextareaWithCounter = ({ value, onChange, disabled, error, id, questionType = 'other' }) => {
+ const handleDynamicHeight = (textarea) => {
+ if (!textarea) return
+ textarea.style.height = 'auto' // Reset height
+ textarea.style.height = textarea.scrollHeight + 'px' // Adjust height based on content
+ }
+
+ const handleTextareaSize = (questionType) => {
+ const rowSize = questionType === 'open' ? 6 : 3
+ const maxLength = questionType === 'open' ? 750 : 250
+
+ return { rowSize, maxLength }
+ }
+ const { rowSize, maxLength } = handleTextareaSize(questionType)
+
+ const handleInputChange = (e) => {
+ onChange(e)
+ handleDynamicHeight(e.target)
+ }
+
+ // Only add aria-invalid when there's an error for the specific id:
+ const ariaInvalid = error && error[id] && error[id].length > 0 ? 'true' : 'false'
+
+ return (
+
+
+
+
+ {error && }
+
+ )
+}
diff --git a/adhocracy4/polls/static/__tests__/CharCounter.jest.jsx b/adhocracy4/polls/static/__tests__/CharCounter.jest.jsx
index 9ecc2f453..ddb2dfc73 100644
--- a/adhocracy4/polls/static/__tests__/CharCounter.jest.jsx
+++ b/adhocracy4/polls/static/__tests__/CharCounter.jest.jsx
@@ -6,6 +6,6 @@ import { render } from '@testing-library/react'
import { CharCounter } from '../PollDetail/CharCounter'
test('
component renders correctly', () => {
- const tree = render()
+ const tree = render()
expect(tree).toMatchSnapshot()
})
diff --git a/adhocracy4/polls/static/__tests__/ChoiceRow.jest.jsx b/adhocracy4/polls/static/__tests__/ChoiceRow.jest.jsx
new file mode 100644
index 000000000..036380e61
--- /dev/null
+++ b/adhocracy4/polls/static/__tests__/ChoiceRow.jest.jsx
@@ -0,0 +1,97 @@
+import React from 'react'
+import { render, screen, fireEvent } from '@testing-library/react'
+import { ChoiceRow } from '../PollDetail/ChoiceRow'
+
+// Mocking the django translation function (gettext)
+jest.mock('django', () => ({
+ gettext: (key) => key // Just return the key as the translation
+}))
+
+// Mock of the TextareaWithCounter component
+jest.mock('../PollDetail/TextareaWithCounter', () => ({
+ TextareaWithCounter: ({ value, onChange }) => (
+
+ )
+}))
+
+describe('ChoiceRow', () => {
+ const mockOnInputChange = jest.fn()
+ const mockOnOtherChange = jest.fn()
+
+ const defaultProps = {
+ choice: {
+ id: 1,
+ label: 'Option 1',
+ is_other_choice: false
+ },
+ checked: false,
+ onInputChange: mockOnInputChange,
+ type: 'radio',
+ disabled: false,
+ otherChoiceAnswer: '',
+ onOtherChange: mockOnOtherChange,
+ isAuthenticated: true,
+ isReadOnly: false,
+ errors: null
+ }
+
+ it('should render the ChoiceRow correctly', () => {
+ render()
+
+ // Check if the label and input are rendered
+ expect(screen.getByText('Option 1')).toBeInTheDocument()
+ expect(screen.getByRole('radio')).toBeInTheDocument()
+ })
+
+ it('should show textarea when "Other" option is selected', () => {
+ const otherChoiceProps = {
+ ...defaultProps,
+ choice: {
+ id: 2,
+ label: 'Other',
+ is_other_choice: true
+ }
+ }
+
+ render()
+
+ // Initially, textarea should not be visible
+ expect(screen.queryByTestId('textarea-with-counter-2')).toBeNull()
+
+ // Simulate selecting the "Other" radio button
+ fireEvent.click(screen.getByRole('radio'))
+
+ // Textarea should now be visible
+ expect(screen.getByTestId('textarea-with-counter-2')).toBeInTheDocument()
+ })
+
+ it('should update textarea value on change', () => {
+ const otherChoiceProps = {
+ ...defaultProps,
+ choice: {
+ id: 2,
+ label: 'Other',
+ is_other_choice: true
+ }
+ }
+
+ render()
+
+ fireEvent.click(screen.getByRole('radio')) // Selecting the "Other" option
+
+ // Get the textarea and simulate typing
+ const textarea = screen.getByTestId('textarea-with-counter-2')
+ fireEvent.change(textarea, { target: { value: 'New answer' } })
+
+ // Check that the textarea value was updated
+ expect(textarea.value).toBe('New answer')
+ expect(mockOnOtherChange).toHaveBeenCalledWith(expect.anything()) // Check if the onOtherChange function was called
+ })
+
+ it('should not show textarea when "Other" option is not selected', () => {
+ render()
+
+ // Check if the textarea is not shown when the "Other" option is not selected
+ expect(screen.queryByTestId('textarea')).toBeNull()
+ })
+})
diff --git a/adhocracy4/polls/static/__tests__/EditPollOpenQuestion.jest.jsx b/adhocracy4/polls/static/__tests__/EditPollOpenQuestion.jest.jsx
index 819375941..87ff2d817 100644
--- a/adhocracy4/polls/static/__tests__/EditPollOpenQuestion.jest.jsx
+++ b/adhocracy4/polls/static/__tests__/EditPollOpenQuestion.jest.jsx
@@ -16,7 +16,7 @@ describe(' with...', () => {
onLabelChange={(label) => onTextChangeFn()}
/>
)
- const questionTextArea = tree.container.querySelector('#id_questions-1-name')
+ const questionTextArea = tree.container.querySelector('#id_questions-6-name')
fireEvent.change(questionTextArea, { target: { value: 'question text' } })
expect(onTextChangeFn).toHaveBeenCalled()
})
@@ -30,7 +30,7 @@ describe(' with...', () => {
onLabelChange={(label) => onTextChangeFn()}
/>
)
- const questionTextArea = tree.container.querySelector('#id_questions-1-name')
+ const questionTextArea = tree.container.querySelector('#id_questions-6-name')
fireEvent.change(questionTextArea, { target: { value: 'question text' } })
expect(onTextChangeFn).toHaveBeenCalled()
})
diff --git a/adhocracy4/polls/static/__tests__/EditPollQuestion.jest.jsx b/adhocracy4/polls/static/__tests__/EditPollQuestion.jest.jsx
index df565e738..317def201 100644
--- a/adhocracy4/polls/static/__tests__/EditPollQuestion.jest.jsx
+++ b/adhocracy4/polls/static/__tests__/EditPollQuestion.jest.jsx
@@ -26,7 +26,7 @@ describe(' with...', () => {
// onAppendChoice={(hasOtherOption) => handleChoice('append', { index, hasOtherOption })}
/>
)
- const questionTextArea = tree.container.querySelector('#id_questions-1-name')
+ const questionTextArea = tree.container.querySelector('#id_questions-6-name')
fireEvent.change(questionTextArea, { target: { value: 'question text' } })
expect(onTextChangeFn).toHaveBeenCalled()
})
@@ -40,7 +40,7 @@ describe(' with...', () => {
onLabelChange={() => onTextChangeFn()}
/>
)
- const questionTextArea = tree.container.querySelector('#id_questions-1-name')
+ const questionTextArea = tree.container.querySelector('#id_questions-6-name')
fireEvent.change(questionTextArea, { target: { value: 'question text' } })
expect(onTextChangeFn).toHaveBeenCalled()
})
@@ -65,7 +65,7 @@ describe(' with...', () => {
onMultipleChoiceChange={() => onMultiChoiceChangeFn()}
/>
)
- const multiChoiceInput = tree.container.querySelector('#id_questions-1-multiple_choice')
+ const multiChoiceInput = tree.container.querySelector('#id_questions-6-multiple_choice')
fireEvent.click(multiChoiceInput)
expect(onMultiChoiceChangeFn).toHaveBeenCalled()
})
@@ -79,7 +79,7 @@ describe(' with...', () => {
onHasOtherChoiceChange={() => onHasOtherChoiceChangeFn()}
/>
)
- const multiChoiceInput = tree.container.querySelector('#id_questions-1-is_other_choice')
+ const multiChoiceInput = tree.container.querySelector('#id_questions-6-is_other_choice')
fireEvent.click(multiChoiceInput)
expect(onHasOtherChoiceChangeFn).toHaveBeenCalled()
})
diff --git a/adhocracy4/polls/static/__tests__/PollQuestion.jest.jsx b/adhocracy4/polls/static/__tests__/PollQuestion.jest.jsx
deleted file mode 100644
index f5bf949c8..000000000
--- a/adhocracy4/polls/static/__tests__/PollQuestion.jest.jsx
+++ /dev/null
@@ -1,130 +0,0 @@
-// tools needed for testing
-import React from 'react'
-import { render, fireEvent } from '@testing-library/react'
-
-// component and related data to be tested
-import { PollQuestion } from '../PollDetail/PollQuestion.jsx'
-import { QUESTION_OBJECT } from './__testdata__/QUESTION_OBJECT'
-
-describe('render with...', () => {
- test('-> single-choice -> non-open-answers', () => {
- const tree = render()
- expect(tree).toMatchSnapshot()
- })
-
- test('-> single-choice -> open-answers', () => {
- const singleOpenQuestion = { ...QUESTION_OBJECT }
- singleOpenQuestion.is_open = true
- const tree = render()
- expect(tree).toMatchSnapshot()
- })
-
- test('-> multiple-choice -> non-open-answers', () => {
- const multiQuestion = { ...QUESTION_OBJECT }
- multiQuestion.multiple_choice = true
- const tree = render()
- expect(tree).toMatchSnapshot()
- })
-
- test('-> multiple-choice -> open-answers', () => {
- const multiOpenQuestion = { ...QUESTION_OBJECT }
- multiOpenQuestion.multiple_choice = true
- multiOpenQuestion.is_open = true
- const tree = render()
- expect(tree).toMatchSnapshot()
- })
-})
-
-describe('calling prop-passed functions...', () => {
- const changedFn = jest.fn()
- const otherChangedFn = jest.fn()
- const multiChangedFn = jest.fn()
-
- test('call onSingleChange', () => {
- const tree = render(
-
- )
- const choiceRadio1 = tree.container.querySelector('#id_choice-1-single')
- expect(choiceRadio1.checked).toBe(false)
- fireEvent.click(choiceRadio1)
- expect(changedFn).toHaveBeenCalled()
- expect(otherChangedFn).toHaveBeenCalled()
- })
-
- test('call onSingleChange -> is_other_choice', () => {
- const singleOpenQuestion = { ...QUESTION_OBJECT }
- singleOpenQuestion.choices[0].is_other_choice = true
- const tree = render(
-
- )
- const choiceRadio = tree.container.querySelector('input[type="radio"][id="id_choice-1-single"]')
- const choiceTextArea = tree.container.querySelector('#id_choice-1-other')
- expect(choiceRadio.checked).toBe(false)
- expect(choiceTextArea.value).toBe('')
- fireEvent.click(choiceRadio)
- fireEvent.change(choiceTextArea, { target: { value: 'something' } })
- expect(otherChangedFn).toHaveBeenCalled()
- expect(otherChangedFn).toHaveBeenCalledWith(1, 'something')
- })
-
- test('call onMultiChange', () => {
- const multiQuestion = { ...QUESTION_OBJECT }
- multiQuestion.multiple_choice = true
- multiQuestion.choices[0].is_other_choice = false
- const tree = render(
-
- )
- const choiceCheckbox1 = tree.container.querySelector('#id_choice-1-multiple')
- expect(choiceCheckbox1.checked).toBe(false)
- fireEvent.click(choiceCheckbox1)
- expect(multiChangedFn).toHaveBeenCalled()
- expect(otherChangedFn).toHaveBeenCalled()
- })
-
- test('call onMultiChange -> is_other_choice', () => {
- const multiOpenQuestion = { ...QUESTION_OBJECT }
- multiOpenQuestion.multiple_choice = true
- multiOpenQuestion.choices[0].is_other_choice = true
- const tree = render(
-
- )
- const choiceCheckbox = tree.container.querySelector('#id_choice-1-multiple')
- const choiceTextArea = tree.container.querySelector('#id_choice-1-other')
- expect(choiceCheckbox.checked).toBe(false)
- expect(choiceTextArea.value).toBe('')
- fireEvent.click(choiceCheckbox)
- fireEvent.change(choiceTextArea, { target: { value: 'something' } })
- expect(otherChangedFn).toHaveBeenCalled()
- expect(otherChangedFn).toHaveBeenCalledWith(1, 'something')
- })
-})
-
-test('initialize with function getUserAnswer -> with user answer', () => {
- const userAnswerQuestion = { ...QUESTION_OBJECT }
- userAnswerQuestion.other_choice_answers = [{ answer: 'antwoord', vote_id: 1 }]
- userAnswerQuestion.other_choice_user_answer = 1
-
- const tree = render(
-
- )
- const choiceTextArea = tree.container.querySelector('#id_choice-1-other')
- expect(choiceTextArea.value).toBe('antwoord')
-})
diff --git a/adhocracy4/polls/static/__tests__/__snapshots__/CharCounter.jest.jsx.snap b/adhocracy4/polls/static/__tests__/__snapshots__/CharCounter.jest.jsx.snap
index a9734061e..9934d2e97 100644
--- a/adhocracy4/polls/static/__tests__/__snapshots__/CharCounter.jest.jsx.snap
+++ b/adhocracy4/polls/static/__tests__/__snapshots__/CharCounter.jest.jsx.snap
@@ -5,18 +5,28 @@ exports[` component renders correctly 1`] = `
"asFragment": [Function],
"baseElement":
-
+
6
/
25
+
+ characters
,
"container":
-
+
6
/
25
+
+ characters
,
"debug": [Function],
diff --git a/adhocracy4/polls/static/__tests__/__snapshots__/PollQuestion.jest.jsx.snap b/adhocracy4/polls/static/__tests__/__snapshots__/PollQuestion.jest.jsx.snap
deleted file mode 100644
index 4f6000acc..000000000
--- a/adhocracy4/polls/static/__tests__/__snapshots__/PollQuestion.jest.jsx.snap
+++ /dev/null
@@ -1,649 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`render with... -> multiple-choice -> non-open-answers 1`] = `
-{
- "asFragment": [Function],
- "baseElement":
-
-
-
- ,
- "container":
-
-
,
- "debug": [Function],
- "findAllByAltText": [Function],
- "findAllByDisplayValue": [Function],
- "findAllByLabelText": [Function],
- "findAllByPlaceholderText": [Function],
- "findAllByRole": [Function],
- "findAllByTestId": [Function],
- "findAllByText": [Function],
- "findAllByTitle": [Function],
- "findByAltText": [Function],
- "findByDisplayValue": [Function],
- "findByLabelText": [Function],
- "findByPlaceholderText": [Function],
- "findByRole": [Function],
- "findByTestId": [Function],
- "findByText": [Function],
- "findByTitle": [Function],
- "getAllByAltText": [Function],
- "getAllByDisplayValue": [Function],
- "getAllByLabelText": [Function],
- "getAllByPlaceholderText": [Function],
- "getAllByRole": [Function],
- "getAllByTestId": [Function],
- "getAllByText": [Function],
- "getAllByTitle": [Function],
- "getByAltText": [Function],
- "getByDisplayValue": [Function],
- "getByLabelText": [Function],
- "getByPlaceholderText": [Function],
- "getByRole": [Function],
- "getByTestId": [Function],
- "getByText": [Function],
- "getByTitle": [Function],
- "queryAllByAltText": [Function],
- "queryAllByDisplayValue": [Function],
- "queryAllByLabelText": [Function],
- "queryAllByPlaceholderText": [Function],
- "queryAllByRole": [Function],
- "queryAllByTestId": [Function],
- "queryAllByText": [Function],
- "queryAllByTitle": [Function],
- "queryByAltText": [Function],
- "queryByDisplayValue": [Function],
- "queryByLabelText": [Function],
- "queryByPlaceholderText": [Function],
- "queryByRole": [Function],
- "queryByTestId": [Function],
- "queryByText": [Function],
- "queryByTitle": [Function],
- "rerender": [Function],
- "unmount": [Function],
-}
-`;
-
-exports[`render with... -> multiple-choice -> open-answers 1`] = `
-{
- "asFragment": [Function],
- "baseElement":
-
-
-
- ,
- "container":
-
-
,
- "debug": [Function],
- "findAllByAltText": [Function],
- "findAllByDisplayValue": [Function],
- "findAllByLabelText": [Function],
- "findAllByPlaceholderText": [Function],
- "findAllByRole": [Function],
- "findAllByTestId": [Function],
- "findAllByText": [Function],
- "findAllByTitle": [Function],
- "findByAltText": [Function],
- "findByDisplayValue": [Function],
- "findByLabelText": [Function],
- "findByPlaceholderText": [Function],
- "findByRole": [Function],
- "findByTestId": [Function],
- "findByText": [Function],
- "findByTitle": [Function],
- "getAllByAltText": [Function],
- "getAllByDisplayValue": [Function],
- "getAllByLabelText": [Function],
- "getAllByPlaceholderText": [Function],
- "getAllByRole": [Function],
- "getAllByTestId": [Function],
- "getAllByText": [Function],
- "getAllByTitle": [Function],
- "getByAltText": [Function],
- "getByDisplayValue": [Function],
- "getByLabelText": [Function],
- "getByPlaceholderText": [Function],
- "getByRole": [Function],
- "getByTestId": [Function],
- "getByText": [Function],
- "getByTitle": [Function],
- "queryAllByAltText": [Function],
- "queryAllByDisplayValue": [Function],
- "queryAllByLabelText": [Function],
- "queryAllByPlaceholderText": [Function],
- "queryAllByRole": [Function],
- "queryAllByTestId": [Function],
- "queryAllByText": [Function],
- "queryAllByTitle": [Function],
- "queryByAltText": [Function],
- "queryByDisplayValue": [Function],
- "queryByLabelText": [Function],
- "queryByPlaceholderText": [Function],
- "queryByRole": [Function],
- "queryByTestId": [Function],
- "queryByText": [Function],
- "queryByTitle": [Function],
- "rerender": [Function],
- "unmount": [Function],
-}
-`;
-
-exports[`render with... -> single-choice -> non-open-answers 1`] = `
-{
- "asFragment": [Function],
- "baseElement":
-
- ,
- "container": ,
- "debug": [Function],
- "findAllByAltText": [Function],
- "findAllByDisplayValue": [Function],
- "findAllByLabelText": [Function],
- "findAllByPlaceholderText": [Function],
- "findAllByRole": [Function],
- "findAllByTestId": [Function],
- "findAllByText": [Function],
- "findAllByTitle": [Function],
- "findByAltText": [Function],
- "findByDisplayValue": [Function],
- "findByLabelText": [Function],
- "findByPlaceholderText": [Function],
- "findByRole": [Function],
- "findByTestId": [Function],
- "findByText": [Function],
- "findByTitle": [Function],
- "getAllByAltText": [Function],
- "getAllByDisplayValue": [Function],
- "getAllByLabelText": [Function],
- "getAllByPlaceholderText": [Function],
- "getAllByRole": [Function],
- "getAllByTestId": [Function],
- "getAllByText": [Function],
- "getAllByTitle": [Function],
- "getByAltText": [Function],
- "getByDisplayValue": [Function],
- "getByLabelText": [Function],
- "getByPlaceholderText": [Function],
- "getByRole": [Function],
- "getByTestId": [Function],
- "getByText": [Function],
- "getByTitle": [Function],
- "queryAllByAltText": [Function],
- "queryAllByDisplayValue": [Function],
- "queryAllByLabelText": [Function],
- "queryAllByPlaceholderText": [Function],
- "queryAllByRole": [Function],
- "queryAllByTestId": [Function],
- "queryAllByText": [Function],
- "queryAllByTitle": [Function],
- "queryByAltText": [Function],
- "queryByDisplayValue": [Function],
- "queryByLabelText": [Function],
- "queryByPlaceholderText": [Function],
- "queryByRole": [Function],
- "queryByTestId": [Function],
- "queryByText": [Function],
- "queryByTitle": [Function],
- "rerender": [Function],
- "unmount": [Function],
-}
-`;
-
-exports[`render with... -> single-choice -> open-answers 1`] = `
-{
- "asFragment": [Function],
- "baseElement":
-
- ,
- "container": ,
- "debug": [Function],
- "findAllByAltText": [Function],
- "findAllByDisplayValue": [Function],
- "findAllByLabelText": [Function],
- "findAllByPlaceholderText": [Function],
- "findAllByRole": [Function],
- "findAllByTestId": [Function],
- "findAllByText": [Function],
- "findAllByTitle": [Function],
- "findByAltText": [Function],
- "findByDisplayValue": [Function],
- "findByLabelText": [Function],
- "findByPlaceholderText": [Function],
- "findByRole": [Function],
- "findByTestId": [Function],
- "findByText": [Function],
- "findByTitle": [Function],
- "getAllByAltText": [Function],
- "getAllByDisplayValue": [Function],
- "getAllByLabelText": [Function],
- "getAllByPlaceholderText": [Function],
- "getAllByRole": [Function],
- "getAllByTestId": [Function],
- "getAllByText": [Function],
- "getAllByTitle": [Function],
- "getByAltText": [Function],
- "getByDisplayValue": [Function],
- "getByLabelText": [Function],
- "getByPlaceholderText": [Function],
- "getByRole": [Function],
- "getByTestId": [Function],
- "getByText": [Function],
- "getByTitle": [Function],
- "queryAllByAltText": [Function],
- "queryAllByDisplayValue": [Function],
- "queryAllByLabelText": [Function],
- "queryAllByPlaceholderText": [Function],
- "queryAllByRole": [Function],
- "queryAllByTestId": [Function],
- "queryAllByText": [Function],
- "queryAllByTitle": [Function],
- "queryByAltText": [Function],
- "queryByDisplayValue": [Function],
- "queryByLabelText": [Function],
- "queryByPlaceholderText": [Function],
- "queryByRole": [Function],
- "queryByTestId": [Function],
- "queryByText": [Function],
- "queryByTitle": [Function],
- "rerender": [Function],
- "unmount": [Function],
-}
-`;
diff --git a/adhocracy4/polls/static/__tests__/__testdata__/QUESTION_OBJECT.js b/adhocracy4/polls/static/__tests__/__testdata__/QUESTION_OBJECT.js
index 3e207c899..74f2cdacd 100644
--- a/adhocracy4/polls/static/__tests__/__testdata__/QUESTION_OBJECT.js
+++ b/adhocracy4/polls/static/__tests__/__testdata__/QUESTION_OBJECT.js
@@ -1,31 +1,48 @@
export const QUESTION_OBJECT = {
- id: 1,
- label: 'My Question',
+ id: 6,
+ label: 'Pick multiple options',
help_text: '',
- multiple_choice: false,
+ multiple_choice: true,
is_open: false,
isReadOnly: false,
authenticated: true,
choices: [
{
id: 1,
- label: 'cool1',
+ label: 'Answer 1',
count: 0,
is_other_choice: false
},
{
id: 2,
- label: 'yeah',
+ label: 'Answer 2',
count: 0,
is_other_choice: false
+ },
+ {
+ id: 3,
+ label: 'Answer 3',
+ count: 0,
+ is_other_choice: false
+ },
+ {
+ id: 4,
+ label: 'other',
+ count: 1,
+ is_other_choice: true
}
],
- userChoices: [],
+ userChoices: [4],
answers: [],
userAnswer: '',
- other_choice_answers: [],
- other_choice_user_answer: '',
- totalVoteCount: 0,
- totalVoteCountMulti: 0,
+ other_choice_answers: [
+ {
+ vote_id: 28,
+ answer: 'question text'
+ }
+ ],
+ other_choice_user_answer: 28,
+ totalVoteCount: 1,
+ totalVoteCountMulti: 1,
totalAnswerCount: 0
}
diff --git a/changelog/8462.md b/changelog/8462.md
new file mode 100644
index 000000000..34beeaed3
--- /dev/null
+++ b/changelog/8462.md
@@ -0,0 +1,9 @@
+### Added
+
+- Added PollDetail/TextareaWithCounter.jsx and PollDetail/ChoiceRow.jsx
+
+### Changed
+- Renamed and refactored PollDetail/PollQuestion.jsx into PollDetail/PollChoice.jsx
+- Refactored PollDetail/CharCounter.jsx
+
+