• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

liqd / adhocracy4 / 12278001736

11 Dec 2024 02:04PM UTC coverage: 76.52% (-0.01%) from 76.53%
12278001736

push

github

goapunk
PollDashboard/EditPollManagement.jsx: fix voting options

244 of 800 branches covered (30.5%)

Branch coverage included in aggregate %.

0 of 4 new or added lines in 1 file covered. (0.0%)

1 existing line in 1 file now uncovered.

5984 of 7339 relevant lines covered (81.54%)

1.25 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/adhocracy4/polls/static/PollDashboard/EditPollManagement.jsx
1
import React, { useState, useEffect } from 'react'
2
import django from 'django'
3
import FlipMove from 'react-flip-move'
4
import update from 'immutability-helper'
5

6
import { EditPollQuestion } from './EditPollQuestion'
7
import { EditPollOpenQuestion } from './EditPollOpenQuestion'
8
import EditPollDropdown from './EditPollDropdown'
9

10
import { updateDashboard } from '../../../../adhocracy4/dashboard/assets/dashboard'
11

12
import api from '../../../static/api'
13

14
import Alert from '../../../static/Alert'
15

NEW
16
const translated = {
×
17
  votingOptionsSectionTitle: django.gettext('Voting Options'),
18
  allowUnregisteredUsersLabel: django.gettext('Allow unregistered users to vote'),
19
  allowUnregisteredUsersSR: django.gettext('Enable this option to allow users who are not registered to participate in the voting process.'),
20
  addAndEditSectionTitle: django.gettext('Add and Edit Questions')
21
}
22

23
// | Helper method for local scoped key/identifier
24

25
let maxLocalKey = 0
×
26
const getNextLocalKey = () => {
×
27
  // Get an artificial key for non-committed items.
28
  // The key is prefixed to prevent collisions with real database keys.
29
  return 'local_' + maxLocalKey++
×
30
}
31

32
export const EditPollManagement = (props) => {
×
33
  const [questions, setQuestions] = useState([])
×
34
  const [allowUnregisteredUsers, setAllowUnregisteredUsers] = useState(false)
×
35
  const [errors, setErrors] = useState([])
×
36
  const [alert, setAlert] = useState(null)
×
37

38
  useEffect(() => {
×
39
    api.poll.get(props.pollId).done(result => {
×
40
      result.questions.length > 0
×
41
        ? setQuestions(result.questions)
42
        : setQuestions([getNewQuestion()])
43
      setAllowUnregisteredUsers(result.allow_unregistered_users)
×
44
    })
45
    // eslint-disable-next-line react-hooks/exhaustive-deps
46
  }, [])
47

48
  const getNewQuestion = (label = '', helptext = '') => {
×
49
    return {
×
50
      label,
51
      help_text: helptext,
52
      multiple_choice: false,
53
      key: getNextLocalKey(),
54
      is_open: false,
55
      choices: [
56
        getNewChoice(),
57
        getNewChoice()
58
      ],
59
      answers: []
60
    }
61
  }
62

63
  // | Question state related handlers
64

65
  const getNewOpenQuestion = (label = '') => {
×
66
    const newQuestion = getNewQuestion(label)
×
67
    newQuestion.is_open = true
×
68
    newQuestion.choices = []
×
69
    return newQuestion
×
70
  }
71

72
  const handleQuestionLabel = (index, label) => {
×
73
    const diff = {}
×
74
    diff[index] = { $merge: { label } }
×
75
    setQuestions(update(questions, diff))
×
76
  }
77

78
  const handleQuestionHelpText = (index, helptext) => {
×
79
    const diff = {}
×
80
    diff[index] = { $merge: { help_text: helptext } }
×
81
    setQuestions(update(questions, diff))
×
82
  }
83

84
  const handleQuestionMultiChoice = (index, multipleChoice) => {
×
85
    const diff = {}
×
86
    diff[index] = { $merge: { multiple_choice: multipleChoice } }
×
87
    setQuestions(update(questions, diff))
×
88
  }
89

90
  const handleQuestionAppend = (params, index) => {
×
91
    let diff = {}
×
92
    const newQuestion = params && params.isOpen
×
93
      ? getNewOpenQuestion()
94
      : getNewQuestion()
95
    diff = { $push: [newQuestion] }
×
96
    setQuestions(update(questions, diff))
×
97
  }
98

99
  const handleQuestionDelete = (index) => {
×
100
    let diff = {}
×
101
    diff = { $splice: [[index, 1]] }
×
102
    setQuestions(update(questions, diff))
×
103
  }
104

105
  const handleQuestionMoveUp = (index) => {
×
106
    let diff = {}
×
107
    const position = index - 1
×
108
    diff = {
×
109
      $splice: [
110
        [index, 1], // remove from current index
111
        [position, 0, questions[index]] // insert to new index
112
      ]
113
    }
114
    setQuestions(update(questions, diff))
×
115
  }
116

117
  const handleQuestionMoveDown = (index) => {
×
118
    let diff = {}
×
119
    const position = index + 1
×
120
    diff = { $splice: [[index, 1], [position, 0, questions[index]]] }
×
121
    setQuestions(update(questions, diff))
×
122
  }
123

124
  // | Choice state related handlers
125

126
  const getNewChoice = (label = '', isOther = false) => {
×
127
    return {
×
128
      label,
129
      key: isOther ? 'other-choice' : getNextLocalKey(),
×
130
      is_other_choice: isOther
131
    }
132
  }
133

134
  const handleChoiceLabel = (index, choiceIndex, label) => {
×
135
    const diff = {}
×
136
    diff[index] = { choices: {} }
×
137
    diff[index].choices[choiceIndex] = { $merge: { label } }
×
138
    setQuestions(update(questions, diff))
×
139
  }
140

141
  const handleChoiceAppend = (index, hasOtherOption) => {
×
142
    const position = questions[index].choices.length - 1
×
143
    const newChoice = getNewChoice()
×
144
    const diff = {}
×
145
    diff[index] = hasOtherOption
×
146
      ? { choices: { $splice: [[position, 0, newChoice]] } }
147
      : { choices: { $push: [newChoice] } }
148
    setQuestions(update(questions, diff))
×
149
  }
150

151
  const handleChoiceIsOtherChoice = (index, isOtherChoice) => {
×
152
    const diff = {}
×
153
    if (isOtherChoice) {
×
154
      const otherChoice = getNewChoice('other', true)
×
155
      diff[index] = { choices: { $push: [otherChoice] } }
×
156
    } else {
157
      const choiceIndex = questions[index].choices.findIndex(c => c.key === 'other-choice')
×
158
      diff[index] = { choices: { $splice: [[choiceIndex, 1]] } }
×
159
    }
160
    setQuestions(update(questions, diff))
×
161
  }
162

163
  const handleChoiceDelete = (index, choiceIndex) => {
×
164
    const diff = {}
×
165
    diff[index] = { choices: { $splice: [[choiceIndex, 1]] } }
×
166
    setQuestions(update(questions, diff))
×
167
  }
168

169
  // | Poll form and submit logic
170

171
  const removeAlert = () => {
×
172
    setAlert(null)
×
173
  }
174

175
  const handleSubmit = (e) => {
×
176
    e.preventDefault()
×
177

178
    const data = {
×
179
      questions,
180
      allow_unregistered_users: allowUnregisteredUsers
181
    }
182

183
    api.poll.change(data, props.pollId)
×
184
      .done((data) => {
185
        setQuestions(data.questions)
×
186
        setAlert({
×
187
          alertAttribute: 'polite',
188
          type: 'success',
189
          message: django.gettext('The poll has been updated.')
190
        })
191
        setErrors([])
×
192
        if (props.reloadOnSuccess) {
×
193
          updateDashboard()
×
194
        }
195
      })
196
      .fail((xhr, status, err) => {
197
        if (xhr.responseJSON && 'questions' in xhr.responseJSON) {
×
198
          setErrors(xhr.responseJSON.questions)
×
199
        }
200

201
        setAlert({
×
202
          alertAttribute: 'assertive',
203
          type: 'danger',
204
          message: django.gettext(
205
            'The poll could not be updated. Please check the data you entered again.'
206
          )
207
        })
208
      })
209
  }
210

211
  // | JSX render
212

213
  return (
×
214
    <form
215
      onSubmit={(e) => handleSubmit(e)} onChange={() => removeAlert()}
×
216
      className="editpoll__questions"
217
    >
218
      {props.enableUnregisteredUsers &&
×
219
        <section className="editpoll__questions-options">
220
          <h2>{translated.votingOptionsSectionTitle}</h2>
221
          <div className="editpoll__questions-options__form-check">
222
            <input
223
              type="checkbox"
224
              id="allowUnregisteredUsersCheckbox"
NEW
225
              onChange={() => setAllowUnregisteredUsers((state) => !state)}
×
226
              checked={allowUnregisteredUsers}
227
              aria-describedby="votingDescription"
228
            />
229
            <label htmlFor="allowUnregisteredUsersCheckbox">
230
              {translated.allowUnregisteredUsersLabel}
231
            </label>
232
            <p id="votingDescription" className="a4-sr-only">
233
              {translated.allowUnregisteredUsersSR}
234
            </p>
235
          </div>
236
        </section>}
237
      <section>
238
        <h2>{translated.addAndEditSectionTitle}</h2>
239
        <FlipMove easing="cubic-bezier(0.25, 0.5, 0.75, 1)">
240
          {
241
            questions.map((question, index, arr) => {
NEW
242
              const key = question.id || question.key
×
NEW
243
              return question.is_open
×
244
                ? (
245
                  <EditPollOpenQuestion
246
                    id={key}
247
                    question={question}
248
                    onLabelChange={(label) => handleQuestionLabel(index, label)}
×
249
                    onHelptextChange={(helptext) => handleQuestionHelpText(index, helptext)}
×
250
                    onMoveUp={index !== 0 ? () => handleQuestionMoveUp(index) : null}
×
251
                    onMoveDown={index < arr.length - 1 ? () => handleQuestionMoveDown(index) : null}
×
252
                    onDelete={() => handleQuestionDelete(index)}
×
253
                    errors={errors && errors[index] ? errors[index] : {}}
×
254
                  />
255
                  )
256
                : (
257
                  <EditPollQuestion
258
                    id={key}
259
                    question={question}
260
                    onLabelChange={(label) => handleQuestionLabel(index, label)}
×
261
                    onHelptextChange={(helptext) => handleQuestionHelpText(index, helptext)}
×
262
                    onMultipleChoiceChange={(multipleChoice) => handleQuestionMultiChoice(index, multipleChoice)}
×
263
                    onMoveUp={index !== 0 ? () => handleQuestionMoveUp(index) : null}
×
264
                    onMoveDown={index < arr.length - 1 ? () => handleQuestionMoveDown(index) : null}
×
265
                    onDelete={() => handleQuestionDelete(index)}
×
266
                    errors={errors && errors[index] ? errors[index] : {}}
×
267
                    onHasOtherChoiceChange={(isOtherChoice) => handleChoiceIsOtherChoice(index, isOtherChoice)}
×
268
                    onChoiceLabelChange={(choiceIndex, label) => handleChoiceLabel(index, choiceIndex, label)}
×
269
                    onDeleteChoice={(choiceIndex) => handleChoiceDelete(index, choiceIndex)}
×
270
                    onAppendChoice={(hasOtherOption) => handleChoiceAppend(index, hasOtherOption)}
×
271
                  />
272
                  )
273
            })
274
          }
275
        </FlipMove>
276
      </section>
UNCOV
277
      <Alert onClick={() => removeAlert()} {...alert} />
×
278

279
      <div className="editpoll__question-container">
280
        <div className="editpoll__question">
281
          <EditPollDropdown
282
            handleToggleMulti={() => handleQuestionAppend()}
×
283
            handleToggleOpen={() => handleQuestionAppend({ isOpen: true })}
×
284
          />
285
        </div>
286

287
        <div className="editpoll__question-actions">
288
          <button type="submit" className="btn btn--primary">
289
            {django.gettext('Save')}
290
          </button>
291
        </div>
292
      </div>
293
    </form>
294
  )
295
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc