decidim-forms 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +65 -0
- data/Rakefile +3 -0
- data/app/assets/config/admin/decidim_forms_manifest.js +1 -0
- data/app/assets/config/decidim_forms_manifest.js +1 -0
- data/app/assets/images/decidim/surveys/icon.svg +19 -0
- data/app/assets/javascripts/decidim/forms/admin/auto_buttons_by_min_items.component.js.es6 +25 -0
- data/app/assets/javascripts/decidim/forms/admin/auto_select_options_by_total_items.component.js.es6 +23 -0
- data/app/assets/javascripts/decidim/forms/admin/forms.js.es6 +188 -0
- data/app/assets/javascripts/decidim/forms/autosortable_checkboxes.component.js.es6 +65 -0
- data/app/assets/javascripts/decidim/forms/forms.js.es6 +20 -0
- data/app/assets/javascripts/decidim/forms/option_attached_inputs.component.js.es6 +32 -0
- data/app/commands/decidim/forms/admin/update_questionnaire.rb +86 -0
- data/app/commands/decidim/forms/answer_questionnaire.rb +54 -0
- data/app/controllers/decidim/forms/admin/concerns/has_questionnaire.rb +95 -0
- data/app/controllers/decidim/forms/concerns/has_questionnaire.rb +84 -0
- data/app/forms/decidim/forms/admin/answer_option_form.rb +23 -0
- data/app/forms/decidim/forms/admin/question_form.rb +35 -0
- data/app/forms/decidim/forms/admin/questionnaire_form.rb +27 -0
- data/app/forms/decidim/forms/answer_choice_form.rb +15 -0
- data/app/forms/decidim/forms/answer_form.rb +69 -0
- data/app/forms/decidim/forms/questionnaire_form.rb +22 -0
- data/app/helpers/decidim/forms/admin/application_helper.rb +19 -0
- data/app/models/concerns/decidim/forms/has_questionnaire.rb +20 -0
- data/app/models/decidim/forms/answer.rb +45 -0
- data/app/models/decidim/forms/answer_choice.rb +15 -0
- data/app/models/decidim/forms/answer_option.rb +11 -0
- data/app/models/decidim/forms/application_record.rb +10 -0
- data/app/models/decidim/forms/question.rb +36 -0
- data/app/models/decidim/forms/questionnaire.rb +23 -0
- data/app/queries/decidim/forms/questionnaire_user_answers.rb +28 -0
- data/app/views/decidim/forms/admin/questionnaires/_answer_option.html.erb +44 -0
- data/app/views/decidim/forms/admin/questionnaires/_form.html.erb +51 -0
- data/app/views/decidim/forms/admin/questionnaires/_question.html.erb +115 -0
- data/app/views/decidim/forms/admin/questionnaires/edit.html.erb +7 -0
- data/app/views/decidim/forms/questionnaires/_answer.html.erb +94 -0
- data/app/views/decidim/forms/questionnaires/show.html.erb +84 -0
- data/config/locales/ca.yml +79 -0
- data/config/locales/de.yml +79 -0
- data/config/locales/en.yml +79 -0
- data/config/locales/es-PY.yml +79 -0
- data/config/locales/es.yml +79 -0
- data/config/locales/eu.yml +79 -0
- data/config/locales/fi-pl.yml +79 -0
- data/config/locales/fi.yml +79 -0
- data/config/locales/fr.yml +79 -0
- data/config/locales/gl.yml +79 -0
- data/config/locales/hu.yml +79 -0
- data/config/locales/id-ID.yml +1 -0
- data/config/locales/it.yml +79 -0
- data/config/locales/nl.yml +79 -0
- data/config/locales/pl.yml +79 -0
- data/config/locales/pt-BR.yml +79 -0
- data/config/locales/pt.yml +79 -0
- data/config/locales/ru.yml +1 -0
- data/config/locales/sv.yml +79 -0
- data/config/locales/tr-TR.yml +1 -0
- data/config/locales/uk.yml +1 -0
- data/db/migrate/20170511092231_create_decidim_forms_questionnaires.rb +15 -0
- data/db/migrate/20170515090916_create_decidim_forms_questions.rb +17 -0
- data/db/migrate/20170515144119_create_decidim_forms_answers.rb +15 -0
- data/db/migrate/20180405015012_create_decidim_forms_answer_options.rb +11 -0
- data/db/migrate/20180405015147_create_decidim_forms_answer_choices.rb +13 -0
- data/lib/decidim/forms.rb +13 -0
- data/lib/decidim/forms/admin.rb +9 -0
- data/lib/decidim/forms/admin_engine.rb +21 -0
- data/lib/decidim/forms/data_portability_user_answers_serializer.rb +37 -0
- data/lib/decidim/forms/engine.rb +14 -0
- data/lib/decidim/forms/test.rb +4 -0
- data/lib/decidim/forms/test/factories.rb +55 -0
- data/lib/decidim/forms/test/shared_examples/has_questionnaire.rb +524 -0
- data/lib/decidim/forms/test/shared_examples/manage_questionnaires.rb +626 -0
- data/lib/decidim/forms/user_answers_serializer.rb +29 -0
- data/lib/decidim/forms/version.rb +10 -0
- metadata +165 -0
@@ -0,0 +1 @@
|
|
1
|
+
ru:
|
@@ -0,0 +1,79 @@
|
|
1
|
+
sv:
|
2
|
+
activemodel:
|
3
|
+
attributes:
|
4
|
+
answer:
|
5
|
+
body: Svar
|
6
|
+
question:
|
7
|
+
max_choices: Max antal val
|
8
|
+
question_type: Typ
|
9
|
+
questionnaire_question:
|
10
|
+
mandatory: Obligatorisk
|
11
|
+
errors:
|
12
|
+
models:
|
13
|
+
answer:
|
14
|
+
attributes:
|
15
|
+
choices:
|
16
|
+
missing: är inte fullständiga
|
17
|
+
too_many: är för många
|
18
|
+
decidim:
|
19
|
+
forms:
|
20
|
+
admin:
|
21
|
+
models:
|
22
|
+
components:
|
23
|
+
description: Beskrivning
|
24
|
+
tos: Användarvillkor
|
25
|
+
questionnaires:
|
26
|
+
answer_option:
|
27
|
+
answer_option: Svaralternativ
|
28
|
+
free_text: Fri text
|
29
|
+
remove: Ta bort
|
30
|
+
statement: Påstående
|
31
|
+
edit:
|
32
|
+
save: Spara
|
33
|
+
title: Titel
|
34
|
+
form:
|
35
|
+
add_question: Lägg till fråga
|
36
|
+
already_answered_warning: Frågeformuläret är redan besvarat av vissa användare så att du inte kan ändra sina frågor.
|
37
|
+
question:
|
38
|
+
add_answer_option: Lägg till svaralternativ
|
39
|
+
any: Vilken som helst
|
40
|
+
description: Beskrivning
|
41
|
+
down: Ner
|
42
|
+
question: Fråga
|
43
|
+
remove: Ta bort
|
44
|
+
statement: Påstående
|
45
|
+
up: Upp
|
46
|
+
update:
|
47
|
+
invalid: Det har varit fel när du sparat frågeformuläret.
|
48
|
+
success: Frågeformulär sparades framgångsrikt.
|
49
|
+
errors:
|
50
|
+
answer:
|
51
|
+
body: Kroppen kan inte vara tomt
|
52
|
+
question_types:
|
53
|
+
long_answer: Långt svar
|
54
|
+
multiple_option: Flera alternativ
|
55
|
+
short_answer: Kort svar
|
56
|
+
single_option: Enkelt alternativ
|
57
|
+
sorting: Sortering
|
58
|
+
questionnaires:
|
59
|
+
answer:
|
60
|
+
invalid: Det har varit fel när du besvarade frågeformuläret.
|
61
|
+
success: Frågeformuläret besvarades med framgång.
|
62
|
+
question:
|
63
|
+
max_choices: 'Max val: %{n}'
|
64
|
+
show:
|
65
|
+
answer_questionnaire:
|
66
|
+
anonymous_user_message: <a href="%{sign_in_link}">Logga in med ditt konto</a> eller <a href="%{sign_up_link}">registrera dig</a> att svara på frågeformuläret.
|
67
|
+
title: Besvara frågeformulä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?
|
69
|
+
questionnaire_answered:
|
70
|
+
body: Du har redan besvarat det här frågeformuläret.
|
71
|
+
title: Redan svarat
|
72
|
+
questionnaire_closed:
|
73
|
+
body: Frågeformuläret är stängt och kan inte besvaras.
|
74
|
+
title: Frågeformuläret stängt
|
75
|
+
questionnaire_for_private_users:
|
76
|
+
body: Frågeformuläret är endast tillgängligt för privata användare
|
77
|
+
title: Frågeformuläret stängt
|
78
|
+
submit: Lämna
|
79
|
+
tos_agreement: Genom att acceptera accepterar du användarvillkoren
|
@@ -0,0 +1 @@
|
|
1
|
+
tr:
|
@@ -0,0 +1 @@
|
|
1
|
+
uk:
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class CreateDecidimFormsQuestionnaires < ActiveRecord::Migration[5.0]
|
4
|
+
def change
|
5
|
+
create_table :decidim_forms_questionnaires do |t|
|
6
|
+
t.jsonb :title
|
7
|
+
t.jsonb :description
|
8
|
+
t.jsonb :tos
|
9
|
+
t.references :questionnaire_for, polymorphic: true, index: { name: "index_decidim_forms_questionnaires_questionnaire_for" }
|
10
|
+
t.datetime :published_at
|
11
|
+
|
12
|
+
t.timestamps
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class CreateDecidimFormsQuestions < ActiveRecord::Migration[5.0]
|
4
|
+
def change
|
5
|
+
create_table :decidim_forms_questions do |t|
|
6
|
+
t.references :decidim_questionnaire, index: true
|
7
|
+
t.integer :position, index: true
|
8
|
+
t.string :question_type
|
9
|
+
t.boolean :mandatory
|
10
|
+
t.jsonb :body
|
11
|
+
t.jsonb :description
|
12
|
+
t.integer :max_choices
|
13
|
+
|
14
|
+
t.timestamps
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class CreateDecidimFormsAnswers < ActiveRecord::Migration[5.0]
|
4
|
+
def change
|
5
|
+
create_table :decidim_forms_answers do |t|
|
6
|
+
t.jsonb :body, default: []
|
7
|
+
t.references :decidim_user, index: true
|
8
|
+
t.references :decidim_questionnaire, index: true
|
9
|
+
t.references :decidim_question, index: { name: "index_decidim_forms_answers_question_id" }
|
10
|
+
t.text :body
|
11
|
+
|
12
|
+
t.timestamps
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class CreateDecidimFormsAnswerOptions < ActiveRecord::Migration[5.1]
|
4
|
+
def change
|
5
|
+
create_table :decidim_forms_answer_options do |t|
|
6
|
+
t.references :decidim_question, index: { name: "index_decidim_forms_answer_options_question_id" }
|
7
|
+
t.jsonb :body
|
8
|
+
t.boolean :free_text
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class CreateDecidimFormsAnswerChoices < ActiveRecord::Migration[5.1]
|
4
|
+
def change
|
5
|
+
create_table :decidim_forms_answer_choices do |t|
|
6
|
+
t.references :decidim_answer, index: { name: "index_decidim_forms_answer_choices_answer_id" }
|
7
|
+
t.references :decidim_answer_option, index: { name: "index_decidim_forms_answer_choices_answer_option_id" }
|
8
|
+
t.integer :position
|
9
|
+
t.jsonb :body
|
10
|
+
t.text :custom_body
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "decidim/forms/admin"
|
4
|
+
require "decidim/forms/engine"
|
5
|
+
require "decidim/forms/admin_engine"
|
6
|
+
|
7
|
+
module Decidim
|
8
|
+
# This namespace holds the logic of the `Forms`.
|
9
|
+
module Forms
|
10
|
+
autoload :UserAnswersSerializer, "decidim/forms/user_answers_serializer"
|
11
|
+
autoload :DataPortabilityUserAnswersSerializer, "decidim/forms/data_portability_user_answers_serializer"
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Forms
|
5
|
+
# This is the engine that runs on the public interface of `Forms`.
|
6
|
+
class AdminEngine < ::Rails::Engine
|
7
|
+
isolate_namespace Decidim::Forms::Admin
|
8
|
+
|
9
|
+
paths["db/migrate"] = nil
|
10
|
+
paths["lib/tasks"] = nil
|
11
|
+
|
12
|
+
initializer "decidim_forms.admin_assets" do |app|
|
13
|
+
app.config.assets.precompile += %w(admin/decidim_forms_manifest.js)
|
14
|
+
end
|
15
|
+
|
16
|
+
def load_seed
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Forms
|
5
|
+
class DataPortabilityUserAnswersSerializer < Decidim::Exporters::Serializer
|
6
|
+
include Decidim::TranslationsHelper
|
7
|
+
# Serializes an user answer for data portability
|
8
|
+
def serialize
|
9
|
+
{
|
10
|
+
id: resource.id,
|
11
|
+
user: {
|
12
|
+
name: resource.user.name,
|
13
|
+
email: resource.user.email
|
14
|
+
},
|
15
|
+
questionnaire: {
|
16
|
+
id: resource.question.questionnaire.id,
|
17
|
+
title: translated_attribute(resource.question.questionnaire.title),
|
18
|
+
description: translated_attribute(resource.question.questionnaire.description),
|
19
|
+
tos: translated_attribute(resource.question.questionnaire.tos)
|
20
|
+
},
|
21
|
+
question: {
|
22
|
+
id: resource.question.id,
|
23
|
+
body: translated_attribute(resource.question.body),
|
24
|
+
description: translated_attribute(resource.question.description)
|
25
|
+
},
|
26
|
+
answer: normalize_body(resource)
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def normalize_body(resource)
|
33
|
+
resource.body || resource.choices.pluck(:body)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Forms
|
5
|
+
# This is the engine that runs on the public interface of `decidim-forms`.
|
6
|
+
class Engine < ::Rails::Engine
|
7
|
+
isolate_namespace Decidim::Forms
|
8
|
+
|
9
|
+
initializer "decidim_forms.assets" do |app|
|
10
|
+
app.config.assets.precompile += %w(decidim_forms_manifest.js)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "decidim/core/test/factories"
|
4
|
+
require "decidim/participatory_processes/test/factories"
|
5
|
+
|
6
|
+
FactoryBot.define do
|
7
|
+
factory :questionnaire, class: Decidim::Forms::Questionnaire do
|
8
|
+
title { generate_localized_title }
|
9
|
+
description do
|
10
|
+
Decidim::Faker::Localized.wrapped("<p>", "</p>") do
|
11
|
+
generate_localized_title
|
12
|
+
end
|
13
|
+
end
|
14
|
+
tos { generate_localized_title }
|
15
|
+
questionnaire_for { build(:participatory_process) }
|
16
|
+
end
|
17
|
+
|
18
|
+
factory :questionnaire_question, class: Decidim::Forms::Question do
|
19
|
+
transient do
|
20
|
+
answer_options { [] }
|
21
|
+
end
|
22
|
+
|
23
|
+
body { generate_localized_title }
|
24
|
+
mandatory { false }
|
25
|
+
position { 0 }
|
26
|
+
question_type { Decidim::Forms::Question::TYPES.first }
|
27
|
+
questionnaire
|
28
|
+
|
29
|
+
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
|
+
)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
factory :answer, class: Decidim::Forms::Answer do
|
40
|
+
body { "hola" }
|
41
|
+
questionnaire
|
42
|
+
question { create(:questionnaire_question, questionnaire: questionnaire) }
|
43
|
+
user { create(:user, organization: questionnaire.questionnaire_for.organization) }
|
44
|
+
end
|
45
|
+
|
46
|
+
factory :answer_option, class: Decidim::Forms::AnswerOption do
|
47
|
+
question { create(:questionnaire_question) }
|
48
|
+
body { generate_localized_title }
|
49
|
+
end
|
50
|
+
|
51
|
+
factory :answer_choice, class: Decidim::Forms::AnswerChoice do
|
52
|
+
answer
|
53
|
+
answer_option { create(:answer_option, question: answer.question) }
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,524 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "spec_helper"
|
4
|
+
|
5
|
+
shared_examples_for "has questionnaire" do
|
6
|
+
context "when the user is not logged in" do
|
7
|
+
it "does not allow answering the questionnaire" do
|
8
|
+
visit questionnaire_public_path
|
9
|
+
|
10
|
+
expect(page).to have_i18n_content(questionnaire.title, upcase: true)
|
11
|
+
expect(page).to have_i18n_content(questionnaire.description)
|
12
|
+
|
13
|
+
expect(page).to have_no_i18n_content(question.body)
|
14
|
+
|
15
|
+
expect(page).to have_content("Sign in with your account or sign up to answer the questionnaire.")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context "when the user is logged in" do
|
20
|
+
before do
|
21
|
+
login_as user, scope: :user
|
22
|
+
end
|
23
|
+
|
24
|
+
it "allows answering the questionnaire" do
|
25
|
+
visit questionnaire_public_path
|
26
|
+
|
27
|
+
expect(page).to have_i18n_content(questionnaire.title, upcase: true)
|
28
|
+
expect(page).to have_i18n_content(questionnaire.description)
|
29
|
+
|
30
|
+
fill_in question.body["en"], with: "My first answer"
|
31
|
+
|
32
|
+
check "questionnaire_tos_agreement"
|
33
|
+
|
34
|
+
accept_confirm { click_button "Submit" }
|
35
|
+
|
36
|
+
within ".success.flash" do
|
37
|
+
expect(page).to have_content("successfully")
|
38
|
+
end
|
39
|
+
|
40
|
+
visit questionnaire_public_path
|
41
|
+
|
42
|
+
expect(page).to have_content("You have already answered this questionnaire.")
|
43
|
+
expect(page).to have_no_i18n_content(question.body)
|
44
|
+
end
|
45
|
+
|
46
|
+
context "when the questionnaire has already been answered by someone else" do
|
47
|
+
let!(:question) do
|
48
|
+
create(
|
49
|
+
:questionnaire_question,
|
50
|
+
questionnaire: questionnaire,
|
51
|
+
question_type: "single_option",
|
52
|
+
position: 0,
|
53
|
+
answer_options: [
|
54
|
+
{ "body" => Decidim::Faker::Localized.sentence },
|
55
|
+
{ "body" => Decidim::Faker::Localized.sentence }
|
56
|
+
]
|
57
|
+
)
|
58
|
+
end
|
59
|
+
|
60
|
+
before do
|
61
|
+
answer = create(:answer, id: 1, questionnaire: questionnaire, question: question)
|
62
|
+
|
63
|
+
answer.choices.create!(
|
64
|
+
answer_option: Decidim::Forms::AnswerOption.first,
|
65
|
+
body: "Lalalilo"
|
66
|
+
)
|
67
|
+
end
|
68
|
+
|
69
|
+
it "does not leak defaults from other answers" do
|
70
|
+
visit questionnaire_public_path
|
71
|
+
|
72
|
+
expect(page).to have_no_selector("input[type=radio]:checked")
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
shared_examples_for "a correctly ordered questionnaire" do
|
77
|
+
it "displays the questions ordered by position starting with one" do
|
78
|
+
form_fields = all(".answer-questionnaire .row")
|
79
|
+
|
80
|
+
expect(form_fields[0]).to have_i18n_content(question.body).and have_content("1. ")
|
81
|
+
expect(form_fields[1]).to have_i18n_content(other_question.body).and have_content("2. ")
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
context "and submitting a fresh form" do
|
86
|
+
let!(:other_question) { create(:questionnaire_question, questionnaire: questionnaire, position: 1) }
|
87
|
+
|
88
|
+
before do
|
89
|
+
visit questionnaire_public_path
|
90
|
+
end
|
91
|
+
|
92
|
+
it_behaves_like "a correctly ordered questionnaire"
|
93
|
+
end
|
94
|
+
|
95
|
+
context "and rendering a form after errors" do
|
96
|
+
let!(:other_question) { create(:questionnaire_question, questionnaire: questionnaire, position: 1) }
|
97
|
+
|
98
|
+
before do
|
99
|
+
visit questionnaire_public_path
|
100
|
+
accept_confirm { click_button "Submit" }
|
101
|
+
end
|
102
|
+
|
103
|
+
it_behaves_like "a correctly ordered questionnaire"
|
104
|
+
end
|
105
|
+
|
106
|
+
shared_context "when a non multiple choice question is mandatory" do
|
107
|
+
let!(:question) do
|
108
|
+
create(
|
109
|
+
:questionnaire_question,
|
110
|
+
questionnaire: questionnaire,
|
111
|
+
question_type: "short_answer",
|
112
|
+
position: 0,
|
113
|
+
mandatory: true
|
114
|
+
)
|
115
|
+
end
|
116
|
+
|
117
|
+
before do
|
118
|
+
visit questionnaire_public_path
|
119
|
+
|
120
|
+
check "questionnaire_tos_agreement"
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
describe "leaving a blank question (without js)", driver: :rack_test do
|
125
|
+
include_context "when a non multiple choice question is mandatory"
|
126
|
+
|
127
|
+
before do
|
128
|
+
click_button "Submit"
|
129
|
+
end
|
130
|
+
|
131
|
+
it "submits the form and shows errors" do
|
132
|
+
within ".alert.flash" do
|
133
|
+
expect(page).to have_content("error")
|
134
|
+
end
|
135
|
+
|
136
|
+
expect(page).to have_content("can't be blank")
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
describe "leaving a blank question (with js)" do
|
141
|
+
include_context "when a non multiple choice question is mandatory"
|
142
|
+
|
143
|
+
before do
|
144
|
+
accept_confirm { click_button "Submit" }
|
145
|
+
end
|
146
|
+
|
147
|
+
it "shows errors without submitting the form" do
|
148
|
+
expect(page).to have_no_selector ".alert.flash"
|
149
|
+
|
150
|
+
expect(page).to have_content("can't be blank")
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
describe "leaving a blank multiple choice question" do
|
155
|
+
let!(:question) do
|
156
|
+
create(
|
157
|
+
:questionnaire_question,
|
158
|
+
questionnaire: questionnaire,
|
159
|
+
question_type: "single_option",
|
160
|
+
position: 0,
|
161
|
+
mandatory: true,
|
162
|
+
answer_options: [
|
163
|
+
{ "body" => Decidim::Faker::Localized.sentence },
|
164
|
+
{ "body" => Decidim::Faker::Localized.sentence }
|
165
|
+
]
|
166
|
+
)
|
167
|
+
end
|
168
|
+
|
169
|
+
before do
|
170
|
+
visit questionnaire_public_path
|
171
|
+
|
172
|
+
check "questionnaire_tos_agreement"
|
173
|
+
|
174
|
+
accept_confirm { click_button "Submit" }
|
175
|
+
end
|
176
|
+
|
177
|
+
it "submits the form and shows errors" do
|
178
|
+
within ".alert.flash" do
|
179
|
+
expect(page).to have_content("error")
|
180
|
+
end
|
181
|
+
|
182
|
+
expect(page).to have_content("can't be blank")
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
context "when a question has a rich text description" do
|
187
|
+
let!(:question) { create(:questionnaire_question, questionnaire: questionnaire, position: 0, description: "<b>This question is important</b>") }
|
188
|
+
|
189
|
+
it "properly interprets HTML descriptions" do
|
190
|
+
visit questionnaire_public_path
|
191
|
+
|
192
|
+
expect(page).to have_selector("b", text: "This question is important")
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
describe "free text options" do
|
197
|
+
let(:answer_option_bodies) { Array.new(3) { Decidim::Faker::Localized.sentence } }
|
198
|
+
|
199
|
+
let!(:question) do
|
200
|
+
create(
|
201
|
+
:questionnaire_question,
|
202
|
+
questionnaire: questionnaire,
|
203
|
+
question_type: question_type,
|
204
|
+
answer_options: [
|
205
|
+
{ "body" => answer_option_bodies[0] },
|
206
|
+
{ "body" => answer_option_bodies[1] },
|
207
|
+
{ "body" => answer_option_bodies[2], "free_text" => true }
|
208
|
+
]
|
209
|
+
)
|
210
|
+
end
|
211
|
+
|
212
|
+
let!(:other_question) do
|
213
|
+
create(
|
214
|
+
:questionnaire_question,
|
215
|
+
questionnaire: questionnaire,
|
216
|
+
question_type: "multiple_option",
|
217
|
+
max_choices: 2,
|
218
|
+
answer_options: [
|
219
|
+
{ "body" => Decidim::Faker::Localized.sentence },
|
220
|
+
{ "body" => Decidim::Faker::Localized.sentence },
|
221
|
+
{ "body" => Decidim::Faker::Localized.sentence }
|
222
|
+
]
|
223
|
+
)
|
224
|
+
end
|
225
|
+
|
226
|
+
before do
|
227
|
+
visit questionnaire_public_path
|
228
|
+
end
|
229
|
+
|
230
|
+
context "when question is single_option type" do
|
231
|
+
let(:question_type) { "single_option" }
|
232
|
+
|
233
|
+
it "renders them as radio buttons with attached text fields disabled by default" do
|
234
|
+
expect(page).to have_selector(".radio-button-collection input[type=radio]", count: 3)
|
235
|
+
|
236
|
+
expect(page).to have_field("questionnaire_answers_0_choices_2_custom_body", disabled: true, count: 1)
|
237
|
+
|
238
|
+
choose answer_option_bodies[2]["en"]
|
239
|
+
|
240
|
+
expect(page).to have_field("questionnaire_answers_0_choices_2_custom_body", disabled: false, count: 1)
|
241
|
+
end
|
242
|
+
|
243
|
+
it "saves the free text in a separate field if submission correct" do
|
244
|
+
choose answer_option_bodies[2]["en"]
|
245
|
+
fill_in "questionnaire_answers_0_choices_2_custom_body", with: "Cacatua"
|
246
|
+
|
247
|
+
check "questionnaire_tos_agreement"
|
248
|
+
accept_confirm { click_button "Submit" }
|
249
|
+
|
250
|
+
within ".success.flash" do
|
251
|
+
expect(page).to have_content("successfully")
|
252
|
+
end
|
253
|
+
|
254
|
+
expect(Decidim::Forms::Answer.first.choices.first.custom_body).to eq("Cacatua")
|
255
|
+
end
|
256
|
+
|
257
|
+
it "preserves the previous custom body if submission not correct" do
|
258
|
+
check other_question.answer_options.first.body["en"]
|
259
|
+
check other_question.answer_options.second.body["en"]
|
260
|
+
check other_question.answer_options.third.body["en"]
|
261
|
+
|
262
|
+
choose answer_option_bodies[2]["en"]
|
263
|
+
fill_in "questionnaire_answers_0_choices_2_custom_body", with: "Cacatua"
|
264
|
+
|
265
|
+
check "questionnaire_tos_agreement"
|
266
|
+
accept_confirm { click_button "Submit" }
|
267
|
+
|
268
|
+
within ".alert.flash" do
|
269
|
+
expect(page).to have_content("There's been errors when answering")
|
270
|
+
end
|
271
|
+
|
272
|
+
expect(page).to have_field("questionnaire_answers_0_choices_2_custom_body", with: "Cacatua")
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
context "when question is multiple_option type" do
|
277
|
+
let(:question_type) { "multiple_option" }
|
278
|
+
|
279
|
+
it "renders them as check boxes with attached text fields disabled by default" do
|
280
|
+
expect(page.first(".check-box-collection")).to have_selector("input[type=checkbox]", count: 3)
|
281
|
+
|
282
|
+
expect(page).to have_field("questionnaire_answers_0_choices_2_custom_body", disabled: true, count: 1)
|
283
|
+
|
284
|
+
check answer_option_bodies[2]["en"]
|
285
|
+
|
286
|
+
expect(page).to have_field("questionnaire_answers_0_choices_2_custom_body", disabled: false, count: 1)
|
287
|
+
end
|
288
|
+
|
289
|
+
it "saves the free text in a separate field if submission correct" do
|
290
|
+
check answer_option_bodies[2]["en"]
|
291
|
+
fill_in "questionnaire_answers_0_choices_2_custom_body", with: "Cacatua"
|
292
|
+
|
293
|
+
check "questionnaire_tos_agreement"
|
294
|
+
accept_confirm { click_button "Submit" }
|
295
|
+
|
296
|
+
within ".success.flash" do
|
297
|
+
expect(page).to have_content("successfully")
|
298
|
+
end
|
299
|
+
|
300
|
+
expect(Decidim::Forms::Answer.first.choices.first.custom_body).to eq("Cacatua")
|
301
|
+
end
|
302
|
+
|
303
|
+
it "preserves the previous custom body if submission not correct" do
|
304
|
+
check "questionnaire_answers_1_choices_0_body"
|
305
|
+
check "questionnaire_answers_1_choices_1_body"
|
306
|
+
check "questionnaire_answers_1_choices_2_body"
|
307
|
+
|
308
|
+
check answer_option_bodies[2]["en"]
|
309
|
+
fill_in "questionnaire_answers_0_choices_2_custom_body", with: "Cacatua"
|
310
|
+
|
311
|
+
check "questionnaire_tos_agreement"
|
312
|
+
accept_confirm { click_button "Submit" }
|
313
|
+
|
314
|
+
within ".alert.flash" do
|
315
|
+
expect(page).to have_content("There's been errors when answering")
|
316
|
+
end
|
317
|
+
|
318
|
+
expect(page).to have_field("questionnaire_answers_0_choices_2_custom_body", with: "Cacatua")
|
319
|
+
end
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
context "when question type is long answer" do
|
324
|
+
let!(:question) { create(:questionnaire_question, questionnaire: questionnaire, question_type: "long_answer") }
|
325
|
+
|
326
|
+
it "renders the answer as a textarea" do
|
327
|
+
visit questionnaire_public_path
|
328
|
+
|
329
|
+
expect(page).to have_selector("textarea#questionnaire_answers_0")
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
context "when question type is short answer" do
|
334
|
+
let!(:question) { create(:questionnaire_question, questionnaire: questionnaire, question_type: "short_answer") }
|
335
|
+
|
336
|
+
it "renders the answer as a text field" do
|
337
|
+
visit questionnaire_public_path
|
338
|
+
|
339
|
+
expect(page).to have_selector("input[type=text]#questionnaire_answers_0")
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
343
|
+
context "when question type is single option" do
|
344
|
+
let(:answer_options) { Array.new(2) { { "body" => Decidim::Faker::Localized.sentence } } }
|
345
|
+
let!(:question) { create(:questionnaire_question, questionnaire: questionnaire, question_type: "single_option", answer_options: answer_options) }
|
346
|
+
|
347
|
+
it "renders answers as a collection of radio buttons" do
|
348
|
+
visit questionnaire_public_path
|
349
|
+
|
350
|
+
expect(page).to have_selector(".radio-button-collection input[type=radio]", count: 2)
|
351
|
+
|
352
|
+
choose answer_options[0]["body"][:en]
|
353
|
+
|
354
|
+
check "questionnaire_tos_agreement"
|
355
|
+
|
356
|
+
accept_confirm { click_button "Submit" }
|
357
|
+
|
358
|
+
within ".success.flash" do
|
359
|
+
expect(page).to have_content("successfully")
|
360
|
+
end
|
361
|
+
|
362
|
+
visit questionnaire_public_path
|
363
|
+
|
364
|
+
expect(page).to have_content("You have already answered this questionnaire.")
|
365
|
+
expect(page).to have_no_i18n_content(question.body)
|
366
|
+
end
|
367
|
+
end
|
368
|
+
|
369
|
+
context "when question type is multiple option" do
|
370
|
+
let(:answer_options) { Array.new(3) { { "body" => Decidim::Faker::Localized.sentence } } }
|
371
|
+
let!(:question) { create(:questionnaire_question, questionnaire: questionnaire, question_type: "multiple_option", answer_options: answer_options) }
|
372
|
+
|
373
|
+
it "renders answers as a collection of radio buttons" do
|
374
|
+
visit questionnaire_public_path
|
375
|
+
|
376
|
+
expect(page).to have_selector(".check-box-collection input[type=checkbox]", count: 3)
|
377
|
+
|
378
|
+
expect(page).to have_no_content("Max choices:")
|
379
|
+
|
380
|
+
check answer_options[0]["body"][:en]
|
381
|
+
check answer_options[1]["body"][:en]
|
382
|
+
|
383
|
+
check "questionnaire_tos_agreement"
|
384
|
+
|
385
|
+
accept_confirm { click_button "Submit" }
|
386
|
+
|
387
|
+
within ".success.flash" do
|
388
|
+
expect(page).to have_content("successfully")
|
389
|
+
end
|
390
|
+
|
391
|
+
visit questionnaire_public_path
|
392
|
+
|
393
|
+
expect(page).to have_content("You have already answered this questionnaire.")
|
394
|
+
expect(page).to have_no_i18n_content(question.body)
|
395
|
+
end
|
396
|
+
|
397
|
+
it "respects the max number of choices" do
|
398
|
+
question.update!(max_choices: 2)
|
399
|
+
|
400
|
+
visit questionnaire_public_path
|
401
|
+
|
402
|
+
expect(page).to have_content("Max choices: 2")
|
403
|
+
|
404
|
+
check answer_options[0]["body"][:en]
|
405
|
+
check answer_options[1]["body"][:en]
|
406
|
+
check answer_options[2]["body"][:en]
|
407
|
+
|
408
|
+
check "questionnaire_tos_agreement"
|
409
|
+
|
410
|
+
accept_confirm { click_button "Submit" }
|
411
|
+
|
412
|
+
within ".alert.flash" do
|
413
|
+
expect(page).to have_content("There's been errors")
|
414
|
+
end
|
415
|
+
|
416
|
+
expect(page).to have_content("are too many")
|
417
|
+
|
418
|
+
uncheck answer_options[2]["body"][:en]
|
419
|
+
|
420
|
+
accept_confirm { click_button "Submit" }
|
421
|
+
|
422
|
+
within ".success.flash" do
|
423
|
+
expect(page).to have_content("successfully")
|
424
|
+
end
|
425
|
+
end
|
426
|
+
end
|
427
|
+
|
428
|
+
context "when question type is multiple option" do
|
429
|
+
let(:answer_options) { Array.new(2) { { "body" => Decidim::Faker::Localized.sentence } } }
|
430
|
+
let!(:question) { create(:questionnaire_question, questionnaire: questionnaire, question_type: "multiple_option", answer_options: answer_options) }
|
431
|
+
|
432
|
+
it "renders the question answers as a collection of radio buttons" do
|
433
|
+
visit questionnaire_public_path
|
434
|
+
|
435
|
+
expect(page).to have_selector(".check-box-collection input[type=checkbox]", count: 2)
|
436
|
+
|
437
|
+
check answer_options[0]["body"][:en]
|
438
|
+
check answer_options[1]["body"][:en]
|
439
|
+
|
440
|
+
check "questionnaire_tos_agreement"
|
441
|
+
|
442
|
+
accept_confirm { click_button "Submit" }
|
443
|
+
|
444
|
+
within ".success.flash" do
|
445
|
+
expect(page).to have_content("successfully")
|
446
|
+
end
|
447
|
+
|
448
|
+
visit questionnaire_public_path
|
449
|
+
|
450
|
+
expect(page).to have_content("You have already answered this questionnaire.")
|
451
|
+
expect(page).to have_no_i18n_content(question.body)
|
452
|
+
end
|
453
|
+
end
|
454
|
+
|
455
|
+
context "when question type is sorting" do
|
456
|
+
let!(:question) do
|
457
|
+
create(
|
458
|
+
:questionnaire_question,
|
459
|
+
questionnaire: questionnaire,
|
460
|
+
question_type: "sorting",
|
461
|
+
answer_options: [
|
462
|
+
{ "body" => "idiotas" },
|
463
|
+
{ "body" => "trates" },
|
464
|
+
{ "body" => "No" },
|
465
|
+
{ "body" => "por" },
|
466
|
+
{ "body" => "nos" }
|
467
|
+
]
|
468
|
+
)
|
469
|
+
end
|
470
|
+
|
471
|
+
it "renders the question answers as a collection of check boxes sortable on click" do
|
472
|
+
visit questionnaire_public_path
|
473
|
+
|
474
|
+
expect(page).to have_selector(".sortable-check-box-collection input[type=checkbox]", count: 5)
|
475
|
+
|
476
|
+
expect(page).to have_content("idiotas\ntrates\nNo\npor\nnos")
|
477
|
+
|
478
|
+
check "No"
|
479
|
+
check "nos"
|
480
|
+
check "trates"
|
481
|
+
check "por"
|
482
|
+
check "idiotas"
|
483
|
+
|
484
|
+
expect(page).to have_content("1. No\n2. nos\n3. trates\n4. por\n5. idiotas")
|
485
|
+
end
|
486
|
+
|
487
|
+
it "properly saves valid sortings" do
|
488
|
+
visit questionnaire_public_path
|
489
|
+
|
490
|
+
check "No"
|
491
|
+
check "nos"
|
492
|
+
check "trates"
|
493
|
+
check "por"
|
494
|
+
check "idiotas"
|
495
|
+
|
496
|
+
check "questionnaire_tos_agreement"
|
497
|
+
|
498
|
+
accept_confirm { click_button "Submit" }
|
499
|
+
|
500
|
+
within ".success.flash" do
|
501
|
+
expect(page).to have_content("successfully")
|
502
|
+
end
|
503
|
+
|
504
|
+
expect(Decidim::Forms::Answer.first.choices.pluck(:position, :body)).to eq(
|
505
|
+
[[0, "No"], [1, "nos"], [2, "trates"], [3, "por"], [4, "idiotas"]]
|
506
|
+
)
|
507
|
+
end
|
508
|
+
|
509
|
+
it "displays errors on incomplete sortings" do
|
510
|
+
visit questionnaire_public_path
|
511
|
+
|
512
|
+
check "No"
|
513
|
+
|
514
|
+
accept_confirm { click_button "Submit" }
|
515
|
+
|
516
|
+
within ".alert.flash" do
|
517
|
+
expect(page).to have_content("error")
|
518
|
+
end
|
519
|
+
|
520
|
+
expect(page).to have_content("are not complete")
|
521
|
+
end
|
522
|
+
end
|
523
|
+
end
|
524
|
+
end
|