decidim-forms 0.19.0 → 0.22.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (106) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/app/assets/config/admin/decidim_forms_manifest.js +1 -0
  4. data/app/assets/images/decidim/surveys/icon.svg +1 -19
  5. data/app/assets/javascripts/decidim/forms/admin/collapsible_questions.js.es6 +13 -0
  6. data/app/assets/javascripts/decidim/forms/admin/forms.js.es6 +95 -7
  7. data/app/assets/javascripts/decidim/forms/admin/live_text_update.component.js.es6 +52 -0
  8. data/app/assets/javascripts/decidim/forms/forms.js.es6 +42 -1
  9. data/app/assets/javascripts/decidim/forms/max_choices_alert.component.js.es6 +44 -0
  10. data/app/assets/stylesheets/decidim/forms/forms.scss +39 -0
  11. data/app/cells/decidim/forms/matrix_readonly/show.erb +5 -0
  12. data/app/cells/decidim/forms/matrix_readonly_cell.rb +12 -0
  13. data/app/cells/decidim/forms/question_readonly/show.erb +5 -1
  14. data/app/cells/decidim/forms/question_readonly_cell.rb +5 -0
  15. data/app/cells/decidim/forms/step_navigation/show.erb +35 -0
  16. data/app/cells/decidim/forms/step_navigation_cell.rb +46 -0
  17. data/app/commands/decidim/forms/admin/update_questionnaire.rb +8 -0
  18. data/app/commands/decidim/forms/answer_questionnaire.rb +7 -2
  19. data/app/controllers/decidim/forms/admin/concerns/has_questionnaire.rb +6 -2
  20. data/app/controllers/decidim/forms/concerns/has_questionnaire.rb +58 -2
  21. data/app/forms/decidim/forms/admin/question_form.rb +20 -1
  22. data/app/forms/decidim/forms/admin/question_matrix_row_form.rb +26 -0
  23. data/app/forms/decidim/forms/answer_choice_form.rb +1 -0
  24. data/app/forms/decidim/forms/answer_form.rb +16 -2
  25. data/app/forms/decidim/forms/questionnaire_form.rb +33 -3
  26. data/app/helpers/decidim/forms/admin/application_helper.rb +16 -0
  27. data/app/models/decidim/forms/answer.rb +2 -2
  28. data/app/models/decidim/forms/answer_choice.rb +7 -0
  29. data/app/models/decidim/forms/question.rb +18 -2
  30. data/app/models/decidim/forms/question_matrix_row.rb +12 -0
  31. data/app/models/decidim/forms/questionnaire.rb +2 -1
  32. data/app/queries/decidim/forms/questionnaire_user_answers.rb +1 -1
  33. data/app/types/decidim/forms/answer_option_type.rb +14 -0
  34. data/app/types/decidim/forms/question_type.rb +23 -0
  35. data/app/types/decidim/forms/questionnaire_type.rb +22 -0
  36. data/app/views/decidim/forms/admin/questionnaires/_answer_option_template.html.erb +1 -1
  37. data/app/views/decidim/forms/admin/questionnaires/_form.html.erb +37 -4
  38. data/app/views/decidim/forms/admin/questionnaires/_matrix_row.html.erb +34 -0
  39. data/app/views/decidim/forms/admin/questionnaires/_matrix_row_template.html.erb +7 -0
  40. data/app/views/decidim/forms/admin/questionnaires/_question.html.erb +29 -6
  41. data/app/views/decidim/forms/admin/questionnaires/_separator.html.erb +41 -0
  42. data/app/views/decidim/forms/questionnaires/_answer.html.erb +21 -92
  43. data/app/views/decidim/forms/questionnaires/answers/_long_answer.html.erb +1 -0
  44. data/app/views/decidim/forms/questionnaires/answers/_matrix_multiple.html.erb +43 -0
  45. data/app/views/decidim/forms/questionnaires/answers/_matrix_single.html.erb +43 -0
  46. data/app/views/decidim/forms/questionnaires/answers/_multiple_option.html.erb +23 -0
  47. data/app/views/decidim/forms/questionnaires/answers/_separator.html.erb +1 -0
  48. data/app/views/decidim/forms/questionnaires/answers/_short_answer.html.erb +1 -0
  49. data/app/views/decidim/forms/questionnaires/answers/_single_option.html.erb +30 -0
  50. data/app/views/decidim/forms/questionnaires/answers/_sorting.html.erb +23 -0
  51. data/app/views/decidim/forms/questionnaires/show.html.erb +60 -27
  52. data/config/locales/ar.yml +12 -3
  53. data/config/locales/bg-BG.yml +16 -0
  54. data/config/locales/ca.yml +45 -7
  55. data/config/locales/cs.yml +41 -3
  56. data/config/locales/da-DK.yml +1 -0
  57. data/config/locales/de.yml +41 -3
  58. data/config/locales/el-GR.yml +1 -0
  59. data/config/locales/el.yml +120 -0
  60. data/config/locales/en.yml +41 -3
  61. data/config/locales/es-MX.yml +41 -3
  62. data/config/locales/es-PY.yml +41 -3
  63. data/config/locales/es.yml +41 -3
  64. data/config/locales/et-EE.yml +1 -0
  65. data/config/locales/eu.yml +7 -3
  66. data/config/locales/fi-plain.yml +41 -3
  67. data/config/locales/fi.yml +42 -4
  68. data/config/locales/fr-CA.yml +120 -0
  69. data/config/locales/fr.yml +41 -3
  70. data/config/locales/ga-IE.yml +1 -0
  71. data/config/locales/gl.yml +7 -3
  72. data/config/locales/hr-HR.yml +1 -0
  73. data/config/locales/hu.yml +18 -4
  74. data/config/locales/id-ID.yml +7 -3
  75. data/config/locales/is-IS.yml +1 -0
  76. data/config/locales/it.yml +42 -4
  77. data/config/locales/ja-JP.yml +120 -0
  78. data/config/locales/lt-LT.yml +1 -0
  79. data/config/locales/lv-LV.yml +119 -0
  80. data/config/locales/mt-MT.yml +1 -0
  81. data/config/locales/nl.yml +41 -3
  82. data/config/locales/no.yml +86 -1
  83. data/config/locales/pl.yml +63 -25
  84. data/config/locales/pt-BR.yml +8 -4
  85. data/config/locales/pt.yml +62 -24
  86. data/config/locales/ro-RO.yml +118 -0
  87. data/config/locales/ru.yml +47 -0
  88. data/config/locales/sk-SK.yml +88 -0
  89. data/config/locales/sk.yml +92 -0
  90. data/config/locales/sl.yml +5 -0
  91. data/config/locales/sr-CS.yml +1 -0
  92. data/config/locales/sv.yml +41 -3
  93. data/config/locales/tr-TR.yml +7 -3
  94. data/db/migrate/20190315203056_add_session_token_to_decidim_forms_answers.rb +17 -0
  95. data/db/migrate/20190930094710_add_ip_hash_to_decidim_form_answers.rb +12 -0
  96. data/db/migrate/20200225123810_create_decidim_forms_question_matrix_rows.rb +11 -0
  97. data/db/migrate/20200304152939_add_matrix_row_id_to_decidim_forms_answer_choices.rb +11 -0
  98. data/lib/decidim/api/questionnaire_entity_interface.rb +18 -0
  99. data/lib/decidim/forms.rb +1 -0
  100. data/lib/decidim/forms/api.rb +7 -0
  101. data/lib/decidim/forms/test/factories.rb +55 -11
  102. data/lib/decidim/forms/test/shared_examples/has_questionnaire.rb +320 -43
  103. data/lib/decidim/forms/test/shared_examples/manage_questionnaires.rb +347 -16
  104. data/lib/decidim/forms/user_answers_serializer.rb +23 -4
  105. data/lib/decidim/forms/version.rb +1 -1
  106. metadata +56 -9
@@ -0,0 +1,5 @@
1
+ sl:
2
+ activemodel:
3
+ attributes:
4
+ answer:
5
+ body: Odgovor
@@ -0,0 +1 @@
1
+ sr:
@@ -15,6 +15,8 @@ sv:
15
15
  choices:
16
16
  missing: är inte fullständiga
17
17
  too_many: är för många
18
+ questionnaire:
19
+ request_invalid: Ett fel uppstod när begäran skulle hanteras. Försök igen
18
20
  decidim:
19
21
  forms:
20
22
  admin:
@@ -30,27 +32,52 @@ sv:
30
32
  statement: Påstående
31
33
  edit:
32
34
  save: Spara
33
- title: Titel
35
+ title: Redigera frågeformulär
34
36
  form:
35
37
  add_question: Lägg till fråga
38
+ add_separator: Lägg till separator
36
39
  already_answered_warning: Frågeformuläret har redan besvarats av vissa användare så du inte kan ändra dess frågor.
40
+ collapse: Komprimera alla frågor
41
+ expand: Expandera alla frågor
42
+ matrix_row:
43
+ matrix_row: Rad
44
+ remove: Ta bort
45
+ statement: Påstående
37
46
  question:
38
47
  add_answer_option: Lägg till svarsalternativ
48
+ add_matrix_row: Lägg till rad
39
49
  any: Någon
50
+ collapse: Komprimera
40
51
  description: Beskrivning
41
52
  down: Ned
53
+ expand: Expandera
42
54
  question: Fråga
43
55
  remove: Ta bort
44
56
  statement: Påstående
45
57
  up: Upp
58
+ separator:
59
+ down: Ner
60
+ remove: Radera
61
+ separator: Avgränsare
62
+ up: Upp
46
63
  update:
47
64
  invalid: Det gick inte att spara formuläret.
48
65
  success: Formuläret har sparats.
49
66
  errors:
50
67
  answer:
51
68
  body: Kroppen kan inte vara to
69
+ files:
70
+ extension_whitelist: 'Godkända format:'
71
+ images:
72
+ dimensions: "%{width} x %{height} px"
73
+ dimensions_info: 'Denna bild kommer att vara:'
74
+ processors:
75
+ resize_and_pad: Anpassad och vadderad till
76
+ resize_to_fit: Anpassad för att passa
52
77
  question_types:
53
78
  long_answer: Långt svar
79
+ matrix_multiple: Matris (flera alternativ)
80
+ matrix_single: Matris (Single alternativ)
54
81
  multiple_option: Flera alternativ
55
82
  short_answer: Kort svar
56
83
  single_option: Ett alternativ
@@ -58,6 +85,7 @@ sv:
58
85
  questionnaires:
59
86
  answer:
60
87
  invalid: Det gick inte att besvara formuläret.
88
+ max_choices_alert: Det finns för många val valda
61
89
  success: Formuläret har besvarats.
62
90
  question:
63
91
  max_choices: 'Högst antal val: %{n}'
@@ -65,7 +93,8 @@ sv:
65
93
  answer_questionnaire:
66
94
  anonymous_user_message: <a href="%{sign_in_link}">Logga in med ditt konto</a> eller <a href="%{sign_up_link}">registrera dig</a> om du vill svara på formuläret.
67
95
  title: Besvara formuläret
68
- are_you_sure: Den här åtgärden kan inte ångras och du kommer inte att kunna redigera dina svar. Är du säker?
96
+ current_step: Steg %{step}
97
+ of_total_steps: av %{total_steps}
69
98
  questionnaire_answered:
70
99
  body: Du har redan besvarat det här formuläret.
71
100
  title: Redan svarat
@@ -75,8 +104,17 @@ sv:
75
104
  questionnaire_for_private_users:
76
105
  body: Formuläret är endast tillgängligt för privata användare
77
106
  title: Formuläret är stängt
78
- submit: Skicka in
79
107
  tos_agreement: Genom att delta godkänner du användarvillkoren
108
+ step_navigation:
109
+ show:
110
+ are_you_sure: Den här åtgärden kan inte ångras och du kommer inte att kunna redigera dina svar. Är du säker?
111
+ back: Tillbaka
112
+ continue: Fortsätt
113
+ submit: Skicka in
80
114
  user_answers_serializer:
81
115
  created_at: Besvarad
82
116
  id: Svars-ID
117
+ ip_hash: Ip-hash
118
+ registered: Registrerad
119
+ unregistered: Ej registrerad
120
+ user_status: Användarstatus
@@ -30,10 +30,12 @@ tr:
30
30
  statement: Beyan
31
31
  edit:
32
32
  save: Kayıt etmek
33
- title: Başlık
34
33
  form:
35
34
  add_question: Soru ekle
36
35
  already_answered_warning: Form, bazı kullanıcılar tarafından zaten yanıtlandı, bu nedenle sorularını değiştiremezsiniz.
36
+ matrix_row:
37
+ remove: Kaldır
38
+ statement: Beyan
37
39
  question:
38
40
  add_answer_option: Cevap seçeneği ekle
39
41
  any: herhangi
@@ -65,7 +67,6 @@ tr:
65
67
  answer_questionnaire:
66
68
  anonymous_user_message: <a href="%{sign_in_link}">Formu yanıtlamak için</a> veya <a href="%{sign_up_link}">hesabınızla giriş yapın</a>.
67
69
  title: Formu cevapla
68
- are_you_sure: Bu işlem geri alınamaz ve cevaplarınızı düzenleyemezsiniz. Emin misiniz?
69
70
  questionnaire_answered:
70
71
  body: Bu formu zaten cevapladınız.
71
72
  title: Zaten cevaplandı
@@ -75,8 +76,11 @@ tr:
75
76
  questionnaire_for_private_users:
76
77
  body: Form yalnızca özel kullanıcılar tarafından kullanılabilir
77
78
  title: Form kapatıldı
78
- submit: Gönder
79
79
  tos_agreement: Katılarak Hizmet Şartlarını kabul etmiş olursunuz.
80
+ step_navigation:
81
+ show:
82
+ are_you_sure: Bu işlem geri alınamaz ve cevaplarınızı düzenleyemezsiniz. Emin misiniz?
83
+ submit: Gönder
80
84
  user_answers_serializer:
81
85
  created_at: Yanıtlandı
82
86
  id: Cevap kimliği
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddSessionTokenToDecidimFormsAnswers < ActiveRecord::Migration[5.2]
4
+ class Answer < ApplicationRecord
5
+ self.table_name = :decidim_forms_answers
6
+ end
7
+
8
+ def change
9
+ add_column :decidim_forms_answers, :session_token, :string, null: false, default: ""
10
+ add_index :decidim_forms_answers, :session_token
11
+
12
+ Answer.find_each do |answer|
13
+ answer.session_token = Digest::MD5.hexdigest("#{answer.decidim_user_id}-#{Rails.application.secrets.secret_key_base}")
14
+ answer.save!
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddIpHashToDecidimFormAnswers < ActiveRecord::Migration[5.2]
4
+ class Answer < ApplicationRecord
5
+ self.table_name = :decidim_forms_answers
6
+ end
7
+
8
+ def change
9
+ add_column :decidim_forms_answers, :ip_hash, :string
10
+ add_index :decidim_forms_answers, :ip_hash
11
+ end
12
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateDecidimFormsQuestionMatrixRows < ActiveRecord::Migration[5.2]
4
+ def change
5
+ create_table :decidim_forms_question_matrix_rows do |t|
6
+ t.references :decidim_question, index: { name: "index_decidim_forms_question_matrix_questionnaire_id" }
7
+ t.integer :position, index: true
8
+ t.jsonb :body
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddMatrixRowIdToDecidimFormsAnswerChoices < ActiveRecord::Migration[5.2]
4
+ class AnswerChoice < ApplicationRecord
5
+ self.table_name = :decidim_forms_answer_choices
6
+ end
7
+
8
+ def change
9
+ add_column :decidim_forms_answer_choices, :decidim_question_matrix_row_id, :integer
10
+ end
11
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Forms
5
+ # This interface should be implemented by any Type that can be linked to a questionnaire
6
+ # The only requirement is to have an ID and the Type name be the class.name + Type
7
+ QuestionnaireEntityInterface = GraphQL::InterfaceType.define do
8
+ name "QuestionnaireEntityInterface"
9
+ description "An interface that can be used in objects with questionnaires"
10
+
11
+ field :id, !types.ID, "ID of this entity"
12
+
13
+ resolve_type ->(obj, _ctx) {
14
+ "#{obj.class.name}Type".constantize
15
+ }
16
+ end
17
+ end
18
+ end
@@ -3,6 +3,7 @@
3
3
  require "decidim/forms/admin"
4
4
  require "decidim/forms/engine"
5
5
  require "decidim/forms/admin_engine"
6
+ require "decidim/forms/api"
6
7
 
7
8
  module Decidim
8
9
  # This namespace holds the logic of the `Forms`.
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Forms
5
+ autoload :QuestionnaireEntityInterface, "decidim/api/questionnaire_entity_interface"
6
+ end
7
+ end
@@ -4,7 +4,7 @@ require "decidim/core/test/factories"
4
4
  require "decidim/participatory_processes/test/factories"
5
5
 
6
6
  FactoryBot.define do
7
- factory :questionnaire, class: Decidim::Forms::Questionnaire do
7
+ factory :questionnaire, class: "Decidim::Forms::Questionnaire" do
8
8
  title { generate_localized_title }
9
9
  description do
10
10
  Decidim::Faker::Localized.wrapped("<p>", "</p>") do
@@ -13,43 +13,87 @@ FactoryBot.define do
13
13
  end
14
14
  tos { generate_localized_title }
15
15
  questionnaire_for { build(:participatory_process) }
16
+
17
+ trait :with_questions do
18
+ questions do
19
+ qs = %w(short_answer long_answer).collect do |text_question_type|
20
+ build(:questionnaire_question, question_type: text_question_type)
21
+ end
22
+ qs << build(:questionnaire_question, :with_answer_options, question_type: :single_option)
23
+ qs
24
+ end
25
+ end
16
26
  end
17
27
 
18
- factory :questionnaire_question, class: Decidim::Forms::Question do
28
+ factory :questionnaire_question, class: "Decidim::Forms::Question" do
19
29
  transient do
20
- answer_options { [] }
30
+ options { [] }
31
+ rows { [] }
21
32
  end
22
33
 
23
34
  body { generate_localized_title }
35
+ description do
36
+ Decidim::Faker::Localized.wrapped("<p>", "</p>") do
37
+ generate_localized_title
38
+ end
39
+ end
24
40
  mandatory { false }
25
41
  position { 0 }
26
42
  question_type { Decidim::Forms::Question::TYPES.first }
27
43
  questionnaire
28
44
 
29
45
  before(:create) do |question, evaluator|
30
- evaluator.answer_options.each do |answer_option|
31
- question.answer_options.build(
32
- body: answer_option["body"],
33
- free_text: answer_option["free_text"]
34
- )
46
+ if question.answer_options.empty?
47
+ evaluator.options.each do |option|
48
+ question.answer_options.build(
49
+ body: option["body"],
50
+ free_text: option["free_text"]
51
+ )
52
+ end
53
+ end
54
+
55
+ if question.matrix_rows.empty?
56
+ evaluator.rows.each do |row|
57
+ question.matrix_rows.build(
58
+ body: row["body"]
59
+ )
60
+ end
35
61
  end
36
62
  end
63
+
64
+ trait :with_answer_options do
65
+ answer_options do
66
+ Array.new(3).collect { build(:answer_option) }
67
+ end
68
+ end
69
+
70
+ trait :separator do
71
+ question_type { :separator }
72
+ end
37
73
  end
38
74
 
39
- factory :answer, class: Decidim::Forms::Answer do
75
+ factory :answer, class: "Decidim::Forms::Answer" do
40
76
  body { "hola" }
41
77
  questionnaire
42
78
  question { create(:questionnaire_question, questionnaire: questionnaire) }
43
79
  user { create(:user, organization: questionnaire.questionnaire_for.organization) }
44
80
  end
45
81
 
46
- factory :answer_option, class: Decidim::Forms::AnswerOption do
82
+ factory :answer_option, class: "Decidim::Forms::AnswerOption" do
47
83
  question { create(:questionnaire_question) }
48
84
  body { generate_localized_title }
85
+ free_text { false }
49
86
  end
50
87
 
51
- factory :answer_choice, class: Decidim::Forms::AnswerChoice do
88
+ factory :answer_choice, class: "Decidim::Forms::AnswerChoice" do
52
89
  answer
53
90
  answer_option { create(:answer_option, question: answer.question) }
91
+ matrix_row { create(:question_matrix_row, question: answer.question) }
92
+ end
93
+
94
+ factory :question_matrix_row, class: "Decidim::Forms::QuestionMatrixRow" do
95
+ question { create(:questionnaire_question) }
96
+ body { generate_localized_title }
97
+ position { 0 }
54
98
  end
55
99
  end
@@ -35,7 +35,9 @@ shared_examples_for "has questionnaire" do
35
35
 
36
36
  check "questionnaire_tos_agreement"
37
37
 
38
- accept_confirm { click_button "Submit" }
38
+ accept_confirm do
39
+ click_button "Submit"
40
+ end
39
41
 
40
42
  within ".success.flash" do
41
43
  expect(page).to have_content("successfully")
@@ -47,6 +49,73 @@ shared_examples_for "has questionnaire" do
47
49
  expect(page).to have_no_i18n_content(question.body)
48
50
  end
49
51
 
52
+ context "with multiple steps" do
53
+ let!(:separator) { create(:questionnaire_question, questionnaire: questionnaire, position: 1, question_type: :separator) }
54
+ let!(:question2) { create(:questionnaire_question, questionnaire: questionnaire, position: 2) }
55
+
56
+ before do
57
+ visit questionnaire_public_path
58
+ end
59
+
60
+ it "allows answering the first questionnaire" do
61
+ expect(page).to have_content("STEP 1 OF 2")
62
+
63
+ within ".answer-questionnaire__submit" do
64
+ expect(page).to have_no_content("Back")
65
+ end
66
+
67
+ answer_first_questionnaire
68
+
69
+ expect(page).to have_no_selector(".success.flash")
70
+ end
71
+
72
+ it "allows revisiting previously-answered questionnaires with my answers" do
73
+ answer_first_questionnaire
74
+
75
+ click_link "Back"
76
+
77
+ expect(page).to have_content("STEP 1 OF 2")
78
+ expect(page).to have_field("questionnaire_answers_0", with: "My first answer")
79
+ end
80
+
81
+ it "finishes the submission when answering the last questionnaire" do
82
+ answer_first_questionnaire
83
+
84
+ check "questionnaire_tos_agreement"
85
+ accept_confirm { click_button "Submit" }
86
+
87
+ within ".success.flash" do
88
+ expect(page).to have_content("successfully")
89
+ end
90
+
91
+ visit questionnaire_public_path
92
+
93
+ expect(page).to have_content("You have already answered this form.")
94
+ end
95
+
96
+ def answer_first_questionnaire
97
+ expect(page).to have_no_selector("#questionnaire_tos_agreement")
98
+
99
+ fill_in question.body["en"], with: "My first answer"
100
+ within ".answer-questionnaire__submit" do
101
+ click_link "Continue"
102
+ end
103
+ expect(page).to have_content("STEP 2 OF 2")
104
+ end
105
+ end
106
+
107
+ it "requires confirmation when exiting mid-answering" do
108
+ visit questionnaire_public_path
109
+
110
+ fill_in question.body["en"], with: "My first answer"
111
+
112
+ dismiss_page_unload do
113
+ page.find(".logo-wrapper a").click
114
+ end
115
+
116
+ expect(page).to have_current_path questionnaire_public_path
117
+ end
118
+
50
119
  context "when the questionnaire has already been answered by someone else" do
51
120
  let!(:question) do
52
121
  create(
@@ -54,7 +123,7 @@ shared_examples_for "has questionnaire" do
54
123
  questionnaire: questionnaire,
55
124
  question_type: "single_option",
56
125
  position: 0,
57
- answer_options: [
126
+ options: [
58
127
  { "body" => Decidim::Faker::Localized.sentence },
59
128
  { "body" => Decidim::Faker::Localized.sentence }
60
129
  ]
@@ -163,7 +232,7 @@ shared_examples_for "has questionnaire" do
163
232
  question_type: "single_option",
164
233
  position: 0,
165
234
  mandatory: true,
166
- answer_options: [
235
+ options: [
167
236
  { "body" => Decidim::Faker::Localized.sentence },
168
237
  { "body" => Decidim::Faker::Localized.sentence }
169
238
  ]
@@ -205,7 +274,8 @@ shared_examples_for "has questionnaire" do
205
274
  :questionnaire_question,
206
275
  questionnaire: questionnaire,
207
276
  question_type: question_type,
208
- answer_options: [
277
+ position: 1,
278
+ options: [
209
279
  { "body" => answer_option_bodies[0] },
210
280
  { "body" => answer_option_bodies[1] },
211
281
  { "body" => answer_option_bodies[2], "free_text" => true }
@@ -219,7 +289,8 @@ shared_examples_for "has questionnaire" do
219
289
  questionnaire: questionnaire,
220
290
  question_type: "multiple_option",
221
291
  max_choices: 2,
222
- answer_options: [
292
+ position: 2,
293
+ options: [
223
294
  { "body" => Decidim::Faker::Localized.sentence },
224
295
  { "body" => Decidim::Faker::Localized.sentence },
225
296
  { "body" => Decidim::Faker::Localized.sentence }
@@ -283,16 +354,16 @@ shared_examples_for "has questionnaire" do
283
354
  it "renders them as check boxes with attached text fields disabled by default" do
284
355
  expect(page.first(".check-box-collection")).to have_selector("input[type=checkbox]", count: 3)
285
356
 
286
- expect(page).to have_field("questionnaire_answers_0_choices_2_custom_body", disabled: true, count: 1)
357
+ expect(page).to have_field("questionnaire_responses_0_choices_2_custom_body", disabled: true, count: 1)
287
358
 
288
359
  check answer_option_bodies[2]["en"]
289
360
 
290
- expect(page).to have_field("questionnaire_answers_0_choices_2_custom_body", disabled: false, count: 1)
361
+ expect(page).to have_field("questionnaire_responses_0_choices_2_custom_body", disabled: false, count: 1)
291
362
  end
292
363
 
293
364
  it "saves the free text in a separate field if submission correct" do
294
365
  check answer_option_bodies[2]["en"]
295
- fill_in "questionnaire_answers_0_choices_2_custom_body", with: "Cacatua"
366
+ fill_in "questionnaire_responses_0_choices_2_custom_body", with: "Cacatua"
296
367
 
297
368
  check "questionnaire_tos_agreement"
298
369
  accept_confirm { click_button "Submit" }
@@ -305,12 +376,12 @@ shared_examples_for "has questionnaire" do
305
376
  end
306
377
 
307
378
  it "preserves the previous custom body if submission not correct" do
308
- check "questionnaire_answers_1_choices_0_body"
309
- check "questionnaire_answers_1_choices_1_body"
310
- check "questionnaire_answers_1_choices_2_body"
379
+ check "questionnaire_responses_1_choices_0_body"
380
+ check "questionnaire_responses_1_choices_1_body"
381
+ check "questionnaire_responses_1_choices_2_body"
311
382
 
312
383
  check answer_option_bodies[2]["en"]
313
- fill_in "questionnaire_answers_0_choices_2_custom_body", with: "Cacatua"
384
+ fill_in "questionnaire_responses_0_choices_2_custom_body", with: "Cacatua"
314
385
 
315
386
  check "questionnaire_tos_agreement"
316
387
  accept_confirm { click_button "Submit" }
@@ -319,7 +390,7 @@ shared_examples_for "has questionnaire" do
319
390
  expect(page).to have_content("There was a problem answering")
320
391
  end
321
392
 
322
- expect(page).to have_field("questionnaire_answers_0_choices_2_custom_body", with: "Cacatua")
393
+ expect(page).to have_field("questionnaire_responses_0_choices_2_custom_body", with: "Cacatua")
323
394
  end
324
395
  end
325
396
  end
@@ -346,7 +417,7 @@ shared_examples_for "has questionnaire" do
346
417
 
347
418
  context "when question type is single option" do
348
419
  let(:answer_options) { Array.new(2) { { "body" => Decidim::Faker::Localized.sentence } } }
349
- let!(:question) { create(:questionnaire_question, questionnaire: questionnaire, question_type: "single_option", answer_options: answer_options) }
420
+ let!(:question) { create(:questionnaire_question, questionnaire: questionnaire, question_type: "single_option", options: answer_options) }
350
421
 
351
422
  it "renders answers as a collection of radio buttons" do
352
423
  visit questionnaire_public_path
@@ -372,7 +443,7 @@ shared_examples_for "has questionnaire" do
372
443
 
373
444
  context "when question type is multiple option" do
374
445
  let(:answer_options) { Array.new(3) { { "body" => Decidim::Faker::Localized.sentence } } }
375
- let!(:question) { create(:questionnaire_question, questionnaire: questionnaire, question_type: "multiple_option", answer_options: answer_options) }
446
+ let!(:question) { create(:questionnaire_question, questionnaire: questionnaire, question_type: "multiple_option", options: answer_options) }
376
447
 
377
448
  it "renders answers as a collection of radio buttons" do
378
449
  visit questionnaire_public_path
@@ -409,6 +480,8 @@ shared_examples_for "has questionnaire" do
409
480
  check answer_options[1]["body"][:en]
410
481
  check answer_options[2]["body"][:en]
411
482
 
483
+ expect(page).to have_content("too many choices")
484
+
412
485
  check "questionnaire_tos_agreement"
413
486
 
414
487
  accept_confirm { click_button "Submit" }
@@ -429,40 +502,13 @@ shared_examples_for "has questionnaire" do
429
502
  end
430
503
  end
431
504
 
432
- context "when question type is multiple option" do
433
- let(:answer_options) { Array.new(2) { { "body" => Decidim::Faker::Localized.sentence } } }
434
- let!(:question) { create(:questionnaire_question, questionnaire: questionnaire, question_type: "multiple_option", answer_options: answer_options) }
435
-
436
- it "renders the question answers as a collection of radio buttons" do
437
- visit questionnaire_public_path
438
-
439
- expect(page).to have_selector(".check-box-collection input[type=checkbox]", count: 2)
440
-
441
- check answer_options[0]["body"][:en]
442
- check answer_options[1]["body"][:en]
443
-
444
- check "questionnaire_tos_agreement"
445
-
446
- accept_confirm { click_button "Submit" }
447
-
448
- within ".success.flash" do
449
- expect(page).to have_content("successfully")
450
- end
451
-
452
- visit questionnaire_public_path
453
-
454
- expect(page).to have_content("You have already answered this form.")
455
- expect(page).to have_no_i18n_content(question.body)
456
- end
457
- end
458
-
459
505
  context "when question type is sorting" do
460
506
  let!(:question) do
461
507
  create(
462
508
  :questionnaire_question,
463
509
  questionnaire: questionnaire,
464
510
  question_type: "sorting",
465
- answer_options: [
511
+ options: [
466
512
  { "body" => "idiotas" },
467
513
  { "body" => "trates" },
468
514
  { "body" => "No" },
@@ -524,5 +570,236 @@ shared_examples_for "has questionnaire" do
524
570
  expect(page).to have_content("are not complete")
525
571
  end
526
572
  end
573
+
574
+ context "when question type is matrix_single" do
575
+ let(:matrix_rows) { Array.new(2) { { "body" => Decidim::Faker::Localized.sentence } } }
576
+ let(:answer_options) { Array.new(2) { { "body" => Decidim::Faker::Localized.sentence } } }
577
+ let(:mandatory) { false }
578
+
579
+ let!(:question) do
580
+ create(
581
+ :questionnaire_question,
582
+ questionnaire: questionnaire,
583
+ question_type: "matrix_single",
584
+ rows: matrix_rows,
585
+ options: answer_options,
586
+ mandatory: mandatory
587
+ )
588
+ end
589
+
590
+ it "renders the question answers as a collection of radio buttons" do
591
+ visit questionnaire_public_path
592
+
593
+ expect(page).to have_selector(".radio-button-collection input[type=radio]", count: 4)
594
+
595
+ expect(page).to have_content(matrix_rows.map { |row| row["body"]["en"] }.join("\n"))
596
+ expect(page).to have_content(answer_options.map { |option| option["body"]["en"] }.join(" "))
597
+
598
+ radio_buttons = page.all(".radio-button-collection input[type=radio]")
599
+
600
+ choose radio_buttons.first[:id]
601
+ choose radio_buttons.last[:id]
602
+
603
+ check "questionnaire_tos_agreement"
604
+
605
+ accept_confirm { click_button "Submit" }
606
+
607
+ within ".success.flash" do
608
+ expect(page).to have_content("successfully")
609
+ end
610
+
611
+ visit questionnaire_public_path
612
+
613
+ expect(page).to have_content("You have already answered this form.")
614
+ expect(page).to have_no_i18n_content(question.body)
615
+
616
+ first_choice, last_choice = Decidim::Forms::Answer.last.choices.pluck(:decidim_answer_option_id, :decidim_question_matrix_row_id)
617
+
618
+ expect(first_choice).to eq([question.answer_options.first.id, question.matrix_rows.first.id])
619
+ expect(last_choice).to eq([question.answer_options.last.id, question.matrix_rows.last.id])
620
+ end
621
+
622
+ it "preserves the chosen answers if submission not correct" do
623
+ visit questionnaire_public_path
624
+
625
+ radio_buttons = page.all(".radio-button-collection input[type=radio]")
626
+ choose radio_buttons[1][:id]
627
+
628
+ accept_confirm { click_button "Submit" }
629
+
630
+ within ".alert.flash" do
631
+ expect(page).to have_content("There was a problem answering")
632
+ end
633
+
634
+ radio_buttons = page.all(".radio-button-collection input[type=radio]")
635
+ expect(radio_buttons.map { |b| b[:checked] }).to eq([nil, "true", nil, nil])
636
+ end
637
+
638
+ context "when the question is mandatory and the answer is not complete" do
639
+ let!(:mandatory) { true }
640
+
641
+ it "shows an error if the question is mandatory and the answer is not complete" do
642
+ visit questionnaire_public_path
643
+
644
+ radio_buttons = page.all(".radio-button-collection input[type=radio]")
645
+ choose radio_buttons[0][:id]
646
+
647
+ check "questionnaire_tos_agreement"
648
+ accept_confirm { click_button "Submit" }
649
+
650
+ within ".alert.flash" do
651
+ expect(page).to have_content("There was a problem answering")
652
+ end
653
+
654
+ expect(page).to have_content("Choices are not complete")
655
+ end
656
+ end
657
+ end
658
+
659
+ context "when question type is matrix_multiple" do
660
+ let(:matrix_rows) { Array.new(2) { { "body" => Decidim::Faker::Localized.sentence } } }
661
+ let(:answer_options) { Array.new(3) { { "body" => Decidim::Faker::Localized.sentence } } }
662
+ let(:max_choices) { nil }
663
+ let(:mandatory) { false }
664
+
665
+ let!(:question) do
666
+ create(
667
+ :questionnaire_question,
668
+ questionnaire: questionnaire,
669
+ question_type: "matrix_multiple",
670
+ rows: matrix_rows,
671
+ options: answer_options,
672
+ max_choices: max_choices,
673
+ mandatory: mandatory
674
+ )
675
+ end
676
+
677
+ it "renders the question answers as a collection of check boxes" do
678
+ visit questionnaire_public_path
679
+
680
+ expect(page).to have_selector(".check-box-collection input[type=checkbox]", count: 6)
681
+
682
+ expect(page).to have_content(matrix_rows.map { |row| row["body"]["en"] }.join("\n"))
683
+ expect(page).to have_content(answer_options.map { |option| option["body"]["en"] }.join(" "))
684
+
685
+ checkboxes = page.all(".check-box-collection input[type=checkbox]")
686
+
687
+ check checkboxes[0][:id]
688
+ check checkboxes[1][:id]
689
+ check checkboxes[3][:id]
690
+
691
+ check "questionnaire_tos_agreement"
692
+
693
+ accept_confirm { click_button "Submit" }
694
+
695
+ within ".success.flash" do
696
+ expect(page).to have_content("successfully")
697
+ end
698
+
699
+ visit questionnaire_public_path
700
+
701
+ expect(page).to have_content("You have already answered this form.")
702
+ expect(page).to have_no_i18n_content(question.body)
703
+
704
+ first_choice, second_choice, third_choice = Decidim::Forms::Answer.last.choices.pluck(:decidim_answer_option_id, :decidim_question_matrix_row_id)
705
+
706
+ expect(first_choice).to eq([question.answer_options.first.id, question.matrix_rows.first.id])
707
+ expect(second_choice).to eq([question.answer_options.second.id, question.matrix_rows.first.id])
708
+ expect(third_choice).to eq([question.answer_options.first.id, question.matrix_rows.last.id])
709
+ end
710
+
711
+ context "when the question hax max_choices defined" do
712
+ let!(:max_choices) { 2 }
713
+
714
+ it "respects the max number of choices" do
715
+ visit questionnaire_public_path
716
+
717
+ expect(page).to have_content("Max choices: 2")
718
+
719
+ checkboxes = page.all(".check-box-collection input[type=checkbox]")
720
+
721
+ check checkboxes[0][:id]
722
+ check checkboxes[1][:id]
723
+ check checkboxes[2][:id]
724
+
725
+ expect(page).to have_content("too many choices")
726
+
727
+ check checkboxes[3][:id]
728
+ check checkboxes[4][:id]
729
+
730
+ expect(page).to have_content("too many choices")
731
+
732
+ check checkboxes[5][:id]
733
+
734
+ uncheck checkboxes[0][:id]
735
+
736
+ expect(page).to have_content("too many choices")
737
+
738
+ check "questionnaire_tos_agreement"
739
+
740
+ accept_confirm { click_button "Submit" }
741
+
742
+ within ".alert.flash" do
743
+ expect(page).to have_content("There was a problem answering")
744
+ end
745
+
746
+ expect(page).to have_content("are too many")
747
+
748
+ checkboxes = page.all(".check-box-collection input[type=checkbox]")
749
+
750
+ uncheck checkboxes[5][:id]
751
+
752
+ accept_confirm { click_button "Submit" }
753
+
754
+ within ".success.flash" do
755
+ expect(page).to have_content("successfully")
756
+ end
757
+ end
758
+ end
759
+
760
+ context "when the submission is not correct" do
761
+ let!(:max_choices) { 2 }
762
+
763
+ it "preserves the chosen answers" do
764
+ visit questionnaire_public_path
765
+
766
+ checkboxes = page.all(".check-box-collection input[type=checkbox]")
767
+ check checkboxes[0][:id]
768
+ check checkboxes[1][:id]
769
+ check checkboxes[2][:id]
770
+ check checkboxes[5][:id]
771
+
772
+ check "questionnaire_tos_agreement"
773
+ accept_confirm { click_button "Submit" }
774
+
775
+ within ".alert.flash" do
776
+ expect(page).to have_content("There was a problem answering")
777
+ end
778
+
779
+ checkboxes = page.all(".check-box-collection input[type=checkbox]")
780
+ expect(checkboxes.map { |c| c[:checked] }).to eq(["true", "true", "true", nil, nil, "true"])
781
+ end
782
+ end
783
+
784
+ context "when the question is mandatory and the answer is not complete" do
785
+ let!(:mandatory) { true }
786
+
787
+ it "shows an error" do
788
+ visit questionnaire_public_path
789
+
790
+ checkboxes = page.all(".check-box-collection input[type=checkbox]")
791
+ check checkboxes[0][:id]
792
+
793
+ check "questionnaire_tos_agreement"
794
+ accept_confirm { click_button "Submit" }
795
+
796
+ within ".alert.flash" do
797
+ expect(page).to have_content("There was a problem answering")
798
+ end
799
+
800
+ expect(page).to have_content("Choices are not complete")
801
+ end
802
+ end
803
+ end
527
804
  end
528
805
  end