surveyor_gui 0.0.3
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/.gitignore +44 -0
- data/.travis.yml +17 -0
- data/Gemfile +7 -0
- data/LICENSE +21 -0
- data/MIT-LICENSE +20 -0
- data/README.md +159 -0
- data/Rakefile +66 -0
- data/app/controllers/answers_controller.rb +3 -0
- data/app/controllers/application_controller.rb +3 -0
- data/app/controllers/dependencys_controller.rb +286 -0
- data/app/controllers/question_groups_controller.rb +84 -0
- data/app/controllers/questions_controller.rb +187 -0
- data/app/controllers/survey_sections_controller.rb +80 -0
- data/app/controllers/surveyforms_controller.rb +4 -0
- data/app/controllers/surveyor_controller.rb +89 -0
- data/app/controllers/surveyor_gui/reports_controller.rb +339 -0
- data/app/controllers/surveyor_gui/responses_controller.rb +39 -0
- data/app/controllers/surveyor_gui/survey_controller.rb +16 -0
- data/app/facades/report_formatter.rb +44 -0
- data/app/facades/report_preview_wrapper.rb +11 -0
- data/app/facades/report_response_generator.rb +147 -0
- data/app/helpers/application_helper.rb +2 -0
- data/app/helpers/dependency_helper.rb +5 -0
- data/app/helpers/question_helper.rb +22 -0
- data/app/helpers/surveyform_helper.rb +179 -0
- data/app/helpers/surveyor_gui/report_helper.rb +19 -0
- data/app/helpers/surveyor_helper.rb +4 -0
- data/app/inputs/currency_input.rb +5 -0
- data/app/inputs/date_picker_input.rb +7 -0
- data/app/inputs/datetime_picker_input.rb +5 -0
- data/app/inputs/percent_input.rb +5 -0
- data/app/inputs/time_picker_input.rb +5 -0
- data/app/mailers/.gitkeep +0 -0
- data/app/models/.gitkeep +0 -0
- data/app/models/answer.rb +4 -0
- data/app/models/column.rb +3 -0
- data/app/models/dependency.rb +4 -0
- data/app/models/dependency_condition.rb +4 -0
- data/app/models/permitted_params_decorator.rb +80 -0
- data/app/models/question.rb +4 -0
- data/app/models/question_group.rb +4 -0
- data/app/models/question_type.rb +7 -0
- data/app/models/response.rb +4 -0
- data/app/models/response_set.rb +4 -0
- data/app/models/row.rb +3 -0
- data/app/models/survey.rb +4 -0
- data/app/models/survey_section.rb +4 -0
- data/app/models/surveyform.rb +103 -0
- data/app/uploaders/blob_uploader.rb +48 -0
- data/app/views/dependencys/_column.html.erb +1 -0
- data/app/views/dependencys/_dependency.html.erb +22 -0
- data/app/views/dependencys/_dependency_condition_fields.html.erb +48 -0
- data/app/views/dependencys/_form.html.erb +20 -0
- data/app/views/dependencys/blank.html.erb +0 -0
- data/app/views/dependencys/edit.html.erb +1 -0
- data/app/views/dependencys/new.html.erb +1 -0
- data/app/views/layouts/application.html.erb +14 -0
- data/app/views/layouts/surveyor_gui_blank.html.erb +12 -0
- data/app/views/layouts/surveyor_gui_default.html.erb +16 -0
- data/app/views/layouts/surveyor_modified.html.erb +14 -0
- data/app/views/partials/_answer.html.haml +33 -0
- data/app/views/partials/_question.html.haml +33 -0
- data/app/views/partials/_question_group.html.haml +73 -0
- data/app/views/partials/_section.html.haml +13 -0
- data/app/views/question_groups/_form.html.erb +56 -0
- data/app/views/question_groups/_group_inline_field.html.erb +21 -0
- data/app/views/question_groups/_group_inline_fields.html.erb +25 -0
- data/app/views/question_groups/blank.html.erb +0 -0
- data/app/views/question_groups/edit.html.erb +1 -0
- data/app/views/question_groups/new.html.erb +1 -0
- data/app/views/questions/_answer_fields.html.erb +23 -0
- data/app/views/questions/_answer_options.html.erb +28 -0
- data/app/views/questions/_form.html.erb +65 -0
- data/app/views/questions/_grid_dropdown_columns.html.erb +10 -0
- data/app/views/questions/_grid_dropdown_fields.html.erb +42 -0
- data/app/views/questions/_grid_fields.html.erb +9 -0
- data/app/views/questions/_no_picks.html.erb +29 -0
- data/app/views/questions/_pick.html +21 -0
- data/app/views/questions/_picks.html.erb +12 -0
- data/app/views/questions/blank.html.erb +0 -0
- data/app/views/questions/edit.html.erb +1 -0
- data/app/views/questions/new.html.erb +1 -0
- data/app/views/survey_sections/_form.html.erb +13 -0
- data/app/views/survey_sections/blank.html.erb +0 -0
- data/app/views/survey_sections/edit.html.erb +1 -0
- data/app/views/survey_sections/new.html.erb +1 -0
- data/app/views/surveyforms/_form.html.erb +50 -0
- data/app/views/surveyforms/_question_field.html.erb +148 -0
- data/app/views/surveyforms/_question_group.html.erb +116 -0
- data/app/views/surveyforms/_question_group_fields.html.erb +3 -0
- data/app/views/surveyforms/_question_name_and_number.html.erb +6 -0
- data/app/views/surveyforms/_question_section.html.erb +15 -0
- data/app/views/surveyforms/_question_wrapper.html.erb +109 -0
- data/app/views/surveyforms/_survey_section_fields.html.erb +138 -0
- data/app/views/surveyforms/clone_survey.html.erb +13 -0
- data/app/views/surveyforms/edit.html.erb +5 -0
- data/app/views/surveyforms/index.html.erb +40 -0
- data/app/views/surveyforms/new.html.erb +1 -0
- data/app/views/surveyforms/show.html.erb +5 -0
- data/app/views/surveyor_gui/reports/_graphs.html.haml +21 -0
- data/app/views/surveyor_gui/reports/_grid.html.haml +42 -0
- data/app/views/surveyor_gui/reports/_grid_dropdown.html.haml +56 -0
- data/app/views/surveyor_gui/reports/_repeater.html.haml +28 -0
- data/app/views/surveyor_gui/reports/_show_report.html.haml +33 -0
- data/app/views/surveyor_gui/reports/_single_question.html.haml +70 -0
- data/app/views/surveyor_gui/reports/show.html.erb +14 -0
- data/app/views/surveyor_gui/responses/_grid.html.haml +20 -0
- data/app/views/surveyor_gui/responses/_grid_dropdown.html.haml +20 -0
- data/app/views/surveyor_gui/responses/_repeater.html.haml +22 -0
- data/app/views/surveyor_gui/responses/_survey_results.html.haml +40 -0
- data/app/views/surveyor_gui/responses/index.html.haml +24 -0
- data/app/views/surveyor_gui/responses/show.html.haml +42 -0
- data/app/views/surveyor_gui/shared/_grid_comments.html.haml +10 -0
- data/app/views/surveyor_gui/shared/_new_line.html.haml +2 -0
- data/app/views/surveyor_gui/shared/_pick_comments.html.haml +15 -0
- data/app/views/surveyor_gui/shared/_question_number.html.haml +9 -0
- data/app/views/surveyor_gui/shared/_report_data.html.haml +24 -0
- data/app/views/surveyor_gui/shared/_stars_report_data.html.haml +14 -0
- data/config.ru +4 -0
- data/config/environment.rb +0 -0
- data/config/routes.rb +71 -0
- data/db/migrate/20140307204049_add_template_to_surveys.rb +5 -0
- data/db/migrate/20140307235607_add_test_data_to_response_sets.rb +5 -0
- data/db/migrate/20140308171947_add_original_choice_to_answers.rb +5 -0
- data/db/migrate/20140308172118_add_blob_to_responses.rb +5 -0
- data/db/migrate/20140308172417_add_modifiable_to_survey_section.rb +5 -0
- data/db/migrate/20140308174532_add_modifiable_to_question.rb +5 -0
- data/db/migrate/20140308175305_add_dynamically_generate_to_questions.rb +5 -0
- data/db/migrate/20140311032923_add_dummy_blob_to_questions.rb +5 -0
- data/db/migrate/20140311160609_add_dynamic_source_to_questions.rb +5 -0
- data/db/migrate/20140311161714_add_report_code_to_questions.rb +5 -0
- data/db/migrate/20140530181134_add_is_comment_to_questions.rb +5 -0
- data/db/migrate/20140531012006_add_is_comment_to_answers.rb +5 -0
- data/db/migrate/20140531225529_create_rows.rb +9 -0
- data/db/migrate/20140601011151_create_columns.rb +11 -0
- data/db/migrate/20140602030330_add_column_id_to_answers.rb +5 -0
- data/db/migrate/20140603155606_add_column_id_to_responses.rb +5 -0
- data/db/migrate/20140606023527_add_column_id_to_dependency_conditions.rb +5 -0
- data/db/migrate/20140815165307_add_user_id_to_survey.rb +5 -0
- data/lib/assets/.gitkeep +0 -0
- data/lib/assets/images/addicon.png +0 -0
- data/lib/assets/images/datepicker.gif +0 -0
- data/lib/assets/images/delete.gif +0 -0
- data/lib/assets/images/delete.png +0 -0
- data/lib/assets/images/images/border.png +0 -0
- data/lib/assets/images/images/controls.png +0 -0
- data/lib/assets/images/images/loading.gif +0 -0
- data/lib/assets/images/images/loading_background.png +0 -0
- data/lib/assets/images/images/overlay.png +0 -0
- data/lib/assets/images/rails.png +0 -0
- data/lib/assets/images/star.gif +0 -0
- data/lib/assets/javascripts/.gitkeep +0 -0
- data/lib/assets/javascripts/surveyor_gui/jquery.MetaData.js +121 -0
- data/lib/assets/javascripts/surveyor_gui/jquery.blockUI.js +619 -0
- data/lib/assets/javascripts/surveyor_gui/jquery.rating.js +377 -0
- data/lib/assets/javascripts/surveyor_gui/jquery.validate.js +1188 -0
- data/lib/assets/javascripts/surveyor_gui/surveyor_add_ons.js +10 -0
- data/lib/assets/javascripts/surveyor_gui/surveyor_gui.js +1417 -0
- data/lib/assets/stylesheets/.gitkeep +0 -0
- data/lib/assets/stylesheets/surveyor_gui/jquery.rating.css +12 -0
- data/lib/assets/stylesheets/surveyor_gui/surveyor_add_ons.css +3 -0
- data/lib/assets/stylesheets/surveyor_gui/surveyor_gui.sass +650 -0
- data/lib/enumerable_extenders.rb +31 -0
- data/lib/generators/surveyor_gui/install_generator.rb +57 -0
- data/lib/generators/surveyor_gui/templates/app/assets/javascripts/surveyor_add_ons.js +2 -0
- data/lib/generators/surveyor_gui/templates/app/assets/javascripts/surveyor_gui_all.js +24 -0
- data/lib/generators/surveyor_gui/templates/app/assets/stylesheets/surveyor_add_ons.css.sass +1 -0
- data/lib/generators/surveyor_gui/templates/app/assets/stylesheets/surveyor_gui_all.css.sass +8 -0
- data/lib/generators/surveyor_gui/templates/app/helpers/surveyor_helper.rb +4 -0
- data/lib/generators/surveyor_gui/templates/app/models/response_set_user.rb +13 -0
- data/lib/generators/surveyor_gui/templates/app/views/layouts/surveyor_gui_default.html.erb +16 -0
- data/lib/generators/surveyor_gui/templates/config/initializers/simple_form.rb +140 -0
- data/lib/generators/surveyor_gui/templates/config/locales/en.yml +5 -0
- data/lib/surveyor_gui.rb +17 -0
- data/lib/surveyor_gui/engine.rb +30 -0
- data/lib/surveyor_gui/helpers/surveyor_helper_methods.rb +19 -0
- data/lib/surveyor_gui/models/answer_methods.rb +24 -0
- data/lib/surveyor_gui/models/column_methods.rb +43 -0
- data/lib/surveyor_gui/models/dependency_condition_methods.rb +53 -0
- data/lib/surveyor_gui/models/dependency_methods.rb +83 -0
- data/lib/surveyor_gui/models/question_and_group_shared_methods.rb +11 -0
- data/lib/surveyor_gui/models/question_group_methods.rb +55 -0
- data/lib/surveyor_gui/models/question_methods.rb +435 -0
- data/lib/surveyor_gui/models/question_type_methods.rb +493 -0
- data/lib/surveyor_gui/models/response_methods.rb +67 -0
- data/lib/surveyor_gui/models/response_set_methods.rb +54 -0
- data/lib/surveyor_gui/models/row_methods.rb +11 -0
- data/lib/surveyor_gui/models/survey_methods.rb +32 -0
- data/lib/surveyor_gui/models/survey_section_methods.rb +32 -0
- data/lib/surveyor_gui/surveyforms_controller_methods.rb +258 -0
- data/lib/surveyor_gui/version.rb +3 -0
- data/lib/tasks/.gitkeep +0 -0
- data/lib/tasks/surveyor_gui_tasks.rake +4 -0
- data/lib/templates/erb/scaffold/_form.html.erb +13 -0
- data/spec/controllers/surveyforms_controller_spec.rb +361 -0
- data/spec/controllers/surveyor_controller_spec.rb +303 -0
- data/spec/factories.rb +181 -0
- data/spec/features/create_survey_spec.rb +418 -0
- data/spec/features/dependencies_spec.rb +61 -0
- data/spec/features/rearrange_survey_spec.rb +118 -0
- data/spec/features/ui_spec.rb +469 -0
- data/spec/fixtures/REDCapDemoDatabase_DataDictionary.csv +127 -0
- data/spec/fixtures/chinese_survey.rb +14 -0
- data/spec/fixtures/everything.rb +215 -0
- data/spec/fixtures/favorites-ish.rb +22 -0
- data/spec/fixtures/favorites.rb +22 -0
- data/spec/fixtures/feelings.rb +38 -0
- data/spec/fixtures/lifestyle.rb +55 -0
- data/spec/fixtures/numbers.rb +21 -0
- data/spec/fixtures/redcap_new_headers.csv +1 -0
- data/spec/fixtures/redcap_siblings.csv +1 -0
- data/spec/fixtures/redcap_whitespace.csv +1 -0
- data/spec/helpers/formtastic_custom_input_spec.rb +15 -0
- data/spec/helpers/surveyor_helper_spec.rb +116 -0
- data/spec/lib/common_spec.rb +37 -0
- data/spec/lib/parser_spec.rb +393 -0
- data/spec/lib/rake_kitchen_sink.rb +42 -0
- data/spec/lib/redcap_parser_spec.rb +129 -0
- data/spec/lib/unparser_spec.rb +126 -0
- data/spec/models/answer_spec.rb +144 -0
- data/spec/models/dependency_condition_spec.rb +428 -0
- data/spec/models/dependency_spec.rb +90 -0
- data/spec/models/question_group_spec.rb +66 -0
- data/spec/models/question_spec.rb +176 -0
- data/spec/models/response_set_spec.rb +452 -0
- data/spec/models/response_spec.rb +208 -0
- data/spec/models/survey_section_spec.rb +58 -0
- data/spec/models/survey_spec.rb +155 -0
- data/spec/models/surveyor_gui/question_spec.rb +60 -0
- data/spec/models/surveyor_gui/question_type_spec.rb +268 -0
- data/spec/models/validation_condition_spec.rb +98 -0
- data/spec/models/validation_spec.rb +64 -0
- data/spec/spec.opts +4 -0
- data/spec/spec_helper.rb +119 -0
- data/spec/support/scenario_outline_helper.rb +39 -0
- data/spec/support/shared.rb +10 -0
- data/spec/support/surveyforms_creation_helpers.rb +312 -0
- data/spec/support/surveyforms_rearrangement_helpers.rb +170 -0
- data/spec/support/surveyor_api_helpers.rb +15 -0
- data/spec/support/surveyor_ui_helpers.rb +108 -0
- data/spec/support/wait_for_ajax.rb +11 -0
- data/spec/views/questions/edit.html.erb_spec.rb +73 -0
- data/spec/views/surveyforms/edit.html.erb_spec.rb +126 -0
- data/surveyor_gui.gemspec +52 -0
- data/test/dummy/README.rdoc +261 -0
- data/test/dummy/Rakefile +7 -0
- data/test/dummy/app/assets/javascripts/application.js +15 -0
- data/test/dummy/app/assets/stylesheets/application.css +13 -0
- data/test/dummy/app/controllers/application_controller.rb +3 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/mailers/.gitkeep +0 -0
- data/test/dummy/app/models/.gitkeep +0 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/config/application.rb +59 -0
- data/test/dummy/config/boot.rb +10 -0
- data/test/dummy/config/database.yml +25 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +37 -0
- data/test/dummy/config/environments/production.rb +67 -0
- data/test/dummy/config/environments/test.rb +37 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/inflections.rb +15 -0
- data/test/dummy/config/initializers/mime_types.rb +5 -0
- data/test/dummy/config/initializers/secret_token.rb +7 -0
- data/test/dummy/config/initializers/session_store.rb +8 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +5 -0
- data/test/dummy/config/routes.rb +58 -0
- data/test/dummy/lib/assets/.gitkeep +0 -0
- data/test/dummy/log/.gitkeep +0 -0
- data/test/dummy/public/404.html +26 -0
- data/test/dummy/public/422.html +26 -0
- data/test/dummy/public/500.html +25 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/script/rails +6 -0
- data/test/surveyor-gui_test.rb +7 -0
- data/test/test_helper.rb +15 -0
- data/vendor/assets/stylesheets/.gitkeep +0 -0
- data/vendor/assets/stylesheets/custom.sass +5 -0
- data/vendor/plugins/.gitkeep +0 -0
- metadata +664 -0
@@ -0,0 +1,42 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
describe Surveyor::Parser do
|
5
|
+
before do
|
6
|
+
@rake = Rake::Application.new
|
7
|
+
Rake.application = @rake
|
8
|
+
Rake.application.add_import "#{gem_path('surveyor')}/lib/tasks/surveyor_tasks.rake"
|
9
|
+
Rake.application.load_imports
|
10
|
+
Rake::Task.define_task(:environment)
|
11
|
+
end
|
12
|
+
it "should return properly parse the kitchen sink survey" do
|
13
|
+
ENV["FILE"]="surveys/kitchen_sink_survey.rb"
|
14
|
+
@rake["surveyor"].invoke
|
15
|
+
|
16
|
+
Survey.count.should == 1
|
17
|
+
SurveySection.count.should == 2
|
18
|
+
Question.count.should == 51
|
19
|
+
Answer.count.should == 252
|
20
|
+
Dependency.count.should == 8
|
21
|
+
DependencyCondition.count.should == 12
|
22
|
+
QuestionGroup.count.should == 6
|
23
|
+
|
24
|
+
Survey.all.map(&:destroy)
|
25
|
+
end
|
26
|
+
it "should return properly parse a UTF8 survey" do
|
27
|
+
pending "failing - not clear why - await update of surveyor"
|
28
|
+
ENV["FILE"]="../spec/fixtures/chinese_survey.rb"
|
29
|
+
@rake["surveyor"].invoke
|
30
|
+
|
31
|
+
Survey.count.should == 1
|
32
|
+
SurveySection.count.should == 1
|
33
|
+
Question.count.should == 3
|
34
|
+
Answer.count.should == 15
|
35
|
+
Dependency.count.should == 0
|
36
|
+
DependencyCondition.count.should == 0
|
37
|
+
QuestionGroup.count.should == 1
|
38
|
+
|
39
|
+
Survey.all.map(&:destroy)
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe Surveyor::RedcapParser do
|
4
|
+
let(:parser){ Surveyor::RedcapParser.new }
|
5
|
+
it "returns a survey object" do
|
6
|
+
x = %("Variable / Field Name","Form Name","Field Units","Section Header","Field Type","Field Label","Choices OR Calculations","Field Note","Text Validation Type","Text Validation Min","Text Validation Max",Identifier?,"Branching Logic (Show field only if...)","Required Field?"\nstudy_id,demographics,,,text,"Study ID",,,,,,,,)
|
7
|
+
parser.parse(x, "redcaptest").is_a?(Survey).should be_true
|
8
|
+
end
|
9
|
+
context "parses" do
|
10
|
+
it "basic questions from REDCap" do
|
11
|
+
file = "REDCapDemoDatabase_DataDictionary.csv"
|
12
|
+
parser.parse File.read(File.join(Rails.root, '..', 'spec', 'fixtures', file)), file
|
13
|
+
expect(Survey.count).to eq(1)
|
14
|
+
expect(Question.count).to eq(143)
|
15
|
+
expect(Answer.count).to eq(233)
|
16
|
+
expect(Dependency.count).to eq(2)
|
17
|
+
expect(DependencyCondition.count).to eq(3)
|
18
|
+
dependencies = [{rule: "A", question_reference_identifier: "given_birth"},
|
19
|
+
{rule: "A and B", question_reference_identifier: "num_children"}]
|
20
|
+
dependencies.each{|attrs| (expect(Dependency.where(rule: attrs[:rule]).first.question.reference_identifier).to eq(attrs[:question_reference_identifier])) if attrs[:question_reference_identifier] }
|
21
|
+
dependency_conditions_a = [{rule_key: "A", operator: "==", question_reference_identifier: "sex", answer_reference_identifier: "0"}]
|
22
|
+
dependency_conditions_ab = [{rule_key: "A", operator: "==", question_reference_identifier: "sex", answer_reference_identifier: "0"},
|
23
|
+
{rule_key: "B", operator: "==", question_reference_identifier: "given_birth", answer_reference_identifier: "1"}]
|
24
|
+
dependency_conditions_a.each{|attrs| (expect(Dependency.where(rule: "A").first.dependency_conditions.where(rule_key: attrs[:rule_key]).first.answer.reference_identifier).to eq(attrs[:answer_reference_identifier])) if attrs[:answer_reference_identifier] }
|
25
|
+
dependency_conditions_a.each{|attrs| (expect(Dependency.where(rule: "A").first.dependency_conditions.where(rule_key: attrs[:rule_key]).first.question.reference_identifier).to eq(attrs[:question_reference_identifier])) if attrs[:question_reference_identifier] }
|
26
|
+
dependency_conditions_a.each{|attrs| (expect(Dependency.where(rule: "A").first.dependency_conditions.where(rule_key: attrs[:rule_key]).first.operator).to eq(attrs[:operator])) if attrs[:operator] }
|
27
|
+
dependency_conditions_ab.each{|attrs| (expect(Dependency.where(rule: "A and B").first.dependency_conditions.where(rule_key: attrs[:rule_key]).first.answer.reference_identifier).to eq(attrs[:answer_reference_identifier])) if attrs[:answer_reference_identifier] }
|
28
|
+
dependency_conditions_ab.each{|attrs| (expect(Dependency.where(rule: "A and B").first.dependency_conditions.where(rule_key: attrs[:rule_key]).first.question.reference_identifier).to eq(attrs[:question_reference_identifier])) if attrs[:question_reference_identifier] }
|
29
|
+
dependency_conditions_ab.each{|attrs| (expect(Dependency.where(rule: "A and B").first.dependency_conditions.where(rule_key: attrs[:rule_key]).first.operator).to eq(attrs[:operator])) if attrs[:operator] }
|
30
|
+
end
|
31
|
+
it "question level dependencies from REDCap" do
|
32
|
+
file = "redcap_siblings.csv"
|
33
|
+
parser.parse File.read(File.join(Rails.root, '..', 'spec', 'fixtures', file)), file
|
34
|
+
expect(Dependency.count).to eq(1)
|
35
|
+
expect(DependencyCondition.count).to eq(1)
|
36
|
+
dependencies = [{rule: "A", question_reference_identifier: "sib1yob"}]
|
37
|
+
dependencies.each{|attrs| (expect(Dependency.where(rule: attrs[:rule]).first.question.reference_identifier).to eq(attrs[:question_reference_identifier])) if attrs[:question_reference_identifier] }
|
38
|
+
dependency_conditions = [{rule_key: "A", operator: ">", question_reference_identifier: "sibs"}]
|
39
|
+
dependency_conditions.each{|attrs| (expect(Dependency.where(rule: "A").first.dependency_conditions.where(rule_key: attrs[:rule_key]).first.question.reference_identifier).to eq(attrs[:question_reference_identifier])) if attrs[:question_reference_identifier] }
|
40
|
+
dependency_conditions.each{|attrs| (expect(Dependency.where(rule: "A").first.dependency_conditions.where(rule_key: attrs[:rule_key]).first.operator).to eq(attrs[:operator])) if attrs[:operator] }
|
41
|
+
end
|
42
|
+
it "different headers from REDCap" do
|
43
|
+
file = "redcap_new_headers.csv"
|
44
|
+
parser.parse File.read(File.join(Rails.root, '..', 'spec', 'fixtures', file)), file
|
45
|
+
expect(Survey.count).to eq(1)
|
46
|
+
expect(Question.count).to eq(1)
|
47
|
+
expect(Answer.count).to eq(2)
|
48
|
+
end
|
49
|
+
it "different whitespace from REDCap" do
|
50
|
+
file = "redcap_whitespace.csv"
|
51
|
+
parser.parse File.read(File.join(Rails.root, '..', 'spec', 'fixtures', file)), file
|
52
|
+
expect(Survey.count).to eq(1)
|
53
|
+
expect(Question.count).to eq(2)
|
54
|
+
expect(Answer.count).to eq(7)
|
55
|
+
answers = [{reference_identifier: "1", text: "Lexapro"},
|
56
|
+
{reference_identifier: "2", text: "Celexa"},
|
57
|
+
{reference_identifier: "3", text: "Prozac"},
|
58
|
+
{reference_identifier: "4", text: "Paxil"},
|
59
|
+
{reference_identifier: "5", text: "Zoloft"},
|
60
|
+
{reference_identifier: "0", text: "No"},
|
61
|
+
{reference_identifier: "1", text: "Yes"}]
|
62
|
+
answers.each{|attrs| (expect(Answer.where(text: attrs[:text]).first.reference_identifier).to eq(attrs[:reference_identifier]))}
|
63
|
+
end
|
64
|
+
end
|
65
|
+
context "helper methods" do
|
66
|
+
it "requires specific columns" do
|
67
|
+
# with standard fields
|
68
|
+
x = %w(field_units choices_or_calculations text_validation_type variable__field_name form_name section_header field_type field_label field_note text_validation_min text_validation_max identifier branching_logic_show_field_only_if required_field)
|
69
|
+
parser.missing_columns(x).should be_blank
|
70
|
+
# without field_units
|
71
|
+
y = %w(choices_or_calculations text_validation_type variable__field_name form_name section_header field_type field_label field_note text_validation_min text_validation_max identifier branching_logic_show_field_only_if required_field)
|
72
|
+
parser.missing_columns(y).should be_blank
|
73
|
+
# choices_or_calculations => choices_calculations_or_slider_labels
|
74
|
+
z = %w(field_units choices_calculations_or_slider_labels text_validation_type variable__field_name form_name section_header field_type field_label field_note text_validation_min text_validation_max identifier branching_logic_show_field_only_if required_field)
|
75
|
+
parser.missing_columns(z).should be_blank
|
76
|
+
# text_validation_type => text_validation_type_or_show_slider_number
|
77
|
+
a = %w(field_units choices_or_calculations text_validation_type_or_show_slider_number variable__field_name form_name section_header field_type field_label field_note text_validation_min text_validation_max identifier branching_logic_show_field_only_if required_field)
|
78
|
+
parser.missing_columns(a).should be_blank
|
79
|
+
end
|
80
|
+
it "decomposes dependency rules" do
|
81
|
+
# basic
|
82
|
+
Dependency.new.extend(SurveyorRedcapParserDependencyMethods).decompose_rule('[f1_q12]="1"').should == {:rule => "A", :components => ['[f1_q12]="1"']}
|
83
|
+
# spacing
|
84
|
+
Dependency.new.extend(SurveyorRedcapParserDependencyMethods).decompose_rule('[f1_q9] = "1"').should == {:rule => "A", :components => ['[f1_q9] = "1"']}
|
85
|
+
# and
|
86
|
+
Dependency.new.extend(SurveyorRedcapParserDependencyMethods).decompose_rule('[pre_q88]="1" and [pre_q90]="1"').should == {:rule => "A and B", :components => ['[pre_q88]="1"', '[pre_q90]="1"']}
|
87
|
+
# or
|
88
|
+
Dependency.new.extend(SurveyorRedcapParserDependencyMethods).decompose_rule('[second_q111]="1" or [second_q111]="3"').should == {:rule => "A or B", :components => ['[second_q111]="1"', '[second_q111]="3"']}
|
89
|
+
# or and
|
90
|
+
Dependency.new.extend(SurveyorRedcapParserDependencyMethods).decompose_rule('[second_q100]="1" or [second_q100]="3" and [second_q101]="1"').should == {:rule => "A or B and C", :components => ['[second_q100]="1"', '[second_q100]="3"', '[second_q101]="1"']}
|
91
|
+
# and or
|
92
|
+
Dependency.new.extend(SurveyorRedcapParserDependencyMethods).decompose_rule('[second_q4]="1" and [second_q11]="1" or [second_q11]="98"').should == {:rule => "A and B or C", :components => ['[second_q4]="1"', '[second_q11]="1"', '[second_q11]="98"']}
|
93
|
+
# or or or
|
94
|
+
Dependency.new.extend(SurveyorRedcapParserDependencyMethods).decompose_rule('[pre_q74]="1" or [pre_q74]="2" or [pre_q74]="4" or [pre_q74]="5"').should == {:rule => "A or B or C or D", :components => ['[pre_q74]="1"', '[pre_q74]="2"', '[pre_q74]="4"', '[pre_q74]="5"']}
|
95
|
+
# and with different operator
|
96
|
+
Dependency.new.extend(SurveyorRedcapParserDependencyMethods).decompose_rule('[f1_q15] >= 21 and [f1_q28] ="1"').should == {:rule => "A and B", :components => ['[f1_q15] >= 21', '[f1_q28] ="1"']}
|
97
|
+
end
|
98
|
+
it "decomposes nested dependency rules" do
|
99
|
+
# external parenthesis
|
100
|
+
Dependency.new.extend(SurveyorRedcapParserDependencyMethods).decompose_rule('([pre_q74]="1" or [pre_q74]="2" or [pre_q74]="4" or [pre_q74]="5") and [pre_q76]="2"').should == {:rule => "(A or B or C or D) and E", :components => ['[pre_q74]="1"', '[pre_q74]="2"', '[pre_q74]="4"', '[pre_q74]="5"', '[pre_q76]="2"']}
|
101
|
+
# internal parenthesis
|
102
|
+
Dependency.new.extend(SurveyorRedcapParserDependencyMethods).decompose_rule('[f1_q10(4)]="1"').should == {:rule => "A", :components => ['[f1_q10(4)]="1"']}
|
103
|
+
# internal and external parenthesis
|
104
|
+
Dependency.new.extend(SurveyorRedcapParserDependencyMethods).decompose_rule('([f1_q7(11)] = "1" or [initial_52] = "1") and [pre_q76]="2"').should == {:rule => "(A or B) and C", :components => ['[f1_q7(11)] = "1"', '[initial_52] = "1"', '[pre_q76]="2"']}
|
105
|
+
end
|
106
|
+
it "decomposes shortcut dependency rules" do
|
107
|
+
# 'or' on the right of the operator
|
108
|
+
Dependency.new.extend(SurveyorRedcapParserDependencyMethods).decompose_rule('[initial_108] = "1" or "2"').should == {:rule => "A or B", :components => ['[initial_108] = "1"', '[initial_108] = "2"']}
|
109
|
+
# multiple 'or' on the right
|
110
|
+
Dependency.new.extend(SurveyorRedcapParserDependencyMethods).decompose_rule('[initial_52] = "1" or "2" or "3"').should == {:rule => "A or B or C", :components => ['[initial_52] = "1"', '[initial_52] = "2"', '[initial_52] = "3"']}
|
111
|
+
# commas on the right
|
112
|
+
Dependency.new.extend(SurveyorRedcapParserDependencyMethods).decompose_rule('[initial_189] = "1, 2, 3"').should == {:rule => "(A and B and C)", :components => ['[initial_189] = "1"', '[initial_189] = "2"', '[initial_189] = "3"']}
|
113
|
+
# multiple internal parenthesis on the left
|
114
|
+
Dependency.new.extend(SurveyorRedcapParserDependencyMethods).decompose_rule('[initial_119(1)(2)(3)(4)(5)] = "1"').should == {:rule => "(A and B and C and D and E)", :components => ['[initial_119(1)] = "1"', '[initial_119(2)] = "1"', '[initial_119(3)] = "1"', '[initial_119(4)] = "1"', '[initial_119(5)] = "1"']}
|
115
|
+
end
|
116
|
+
it "decomposes components" do
|
117
|
+
Dependency.new.extend(SurveyorRedcapParserDependencyMethods).decompose_component('[initial_52] = "1"').should == {:question_reference => 'initial_52', :operator => '==', :answer_reference => '1'}
|
118
|
+
Dependency.new.extend(SurveyorRedcapParserDependencyMethods).decompose_component('[initial_119(2)] = "1"').should == {:question_reference => 'initial_119', :operator => '==', :answer_reference => '2'}
|
119
|
+
Dependency.new.extend(SurveyorRedcapParserDependencyMethods).decompose_component('[f1_q15] >= 21').should == {:question_reference => 'f1_q15', :operator => '>=', :integer_value => '21'}
|
120
|
+
# basic, blanks
|
121
|
+
Dependency.new.extend(SurveyorRedcapParserDependencyMethods).decompose_component("[f1_q15]=''").should == {:question_reference => 'f1_q15', :operator => '==', :answer_reference => ''}
|
122
|
+
# basic, negatives
|
123
|
+
Dependency.new.extend(SurveyorRedcapParserDependencyMethods).decompose_component("[f1_q15]='-2'").should == {:question_reference => 'f1_q15', :operator => '==', :answer_reference => '-2'}
|
124
|
+
# internal parenthesis
|
125
|
+
Dependency.new.extend(SurveyorRedcapParserDependencyMethods).decompose_component("[hiprep_heat2(97)] = '1'").should == {:question_reference => 'hiprep_heat2', :operator => '==', :answer_reference => '97'}
|
126
|
+
Dependency.new.extend(SurveyorRedcapParserDependencyMethods).decompose_component("[hi_event1_type] <> ''").should == {:question_reference => 'hi_event1_type', :operator => '!=', :answer_reference => ''}
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe Surveyor::Unparser do
|
4
|
+
before(:each) do
|
5
|
+
@survey = Survey.new(:title => "Simple survey", :description => "very simple")
|
6
|
+
@section = @survey.sections.build(:title => "Simple section")
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should unparse a basic survey, section, and question" do
|
10
|
+
q1 = @section.questions.build(:text => "What is your favorite color?", :reference_identifier => 1, :pick => :one)
|
11
|
+
a11 = q1.answers.build(:text => "red", :response_class => "answer", :reference_identifier => 1, :question => q1)
|
12
|
+
a12 = q1.answers.build(:text => "green", :response_class => "answer", :reference_identifier => 2, :question => q1)
|
13
|
+
a13 = q1.answers.build(:text => "blue", :response_class => "answer", :reference_identifier => 3, :question => q1)
|
14
|
+
a14 = q1.answers.build(:text => "Other", :response_class => "string", :reference_identifier => 4, :question => q1)
|
15
|
+
a15 = q1.answers.build(:text => "Omit", :reference_identifier => 5, :question => q1, :is_exclusive => true)
|
16
|
+
q2 = @section.questions.build(:text => "What is your name?", :reference_identifier => 2, :pick => :none)
|
17
|
+
a21 = q2.answers.build(:response_class => "string", :reference_identifier => 1, :question => q2)
|
18
|
+
Surveyor::Unparser.unparse(@survey).should ==
|
19
|
+
<<-dsl
|
20
|
+
survey "Simple survey", :description=>"very simple" do
|
21
|
+
section "Simple section" do
|
22
|
+
|
23
|
+
q_1 "What is your favorite color?", :pick=>"one"
|
24
|
+
a_1 "red"
|
25
|
+
a_2 "green"
|
26
|
+
a_3 "blue"
|
27
|
+
a_4 :other, :string
|
28
|
+
a_5 :omit
|
29
|
+
|
30
|
+
q_2 "What is your name?"
|
31
|
+
a_1 :string
|
32
|
+
end
|
33
|
+
end
|
34
|
+
dsl
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should unparse groups" do
|
38
|
+
q3 = @section.questions.build(:text => "Happy?")
|
39
|
+
a31 = q3.answers.build(:text => "Yes", :question => q3)
|
40
|
+
a32 = q3.answers.build(:text => "Maybe", :question => q3)
|
41
|
+
a33 = q3.answers.build(:text => "No", :question => q3)
|
42
|
+
|
43
|
+
q4 = @section.questions.build(:text => "Energized?")
|
44
|
+
a41 = q4.answers.build(:text => "Yes", :question => q4)
|
45
|
+
a42 = q4.answers.build(:text => "Maybe", :question => q4)
|
46
|
+
a43 = q4.answers.build(:text => "No", :question => q4)
|
47
|
+
|
48
|
+
g1 = q3.build_question_group(:text => "How are you feeling?", :display_type => "grid")
|
49
|
+
q4.question_group = g1
|
50
|
+
g1.questions = [q3, q4]
|
51
|
+
|
52
|
+
q5 = @section.questions.build(:text => "Model")
|
53
|
+
a51 = q5.answers.build(:response_class => "string", :question => q3)
|
54
|
+
|
55
|
+
g2 = q5.build_question_group(:text => "Tell us about the cars you own", :display_type => "repeater")
|
56
|
+
g2.questions = [q5]
|
57
|
+
|
58
|
+
Surveyor::Unparser.unparse(@survey).should ==
|
59
|
+
<<-dsl
|
60
|
+
survey "Simple survey", :description=>"very simple" do
|
61
|
+
section "Simple section" do
|
62
|
+
|
63
|
+
grid "How are you feeling?" do
|
64
|
+
a "Yes"
|
65
|
+
a "Maybe"
|
66
|
+
a "No"
|
67
|
+
q "Happy?"
|
68
|
+
q "Energized?"
|
69
|
+
end
|
70
|
+
|
71
|
+
repeater "Tell us about the cars you own" do
|
72
|
+
q "Model"
|
73
|
+
a :string
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
dsl
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should unparse a basic survey, section, and question" do
|
81
|
+
q6 = @section.questions.build(:text => "What... is your name? (e.g. It is 'Arthur', King of the Britons)", :reference_identifier => "montypython3")
|
82
|
+
a61 = q6.answers.build(:response_class => "string", :reference_identifier => 1, :question => q6)
|
83
|
+
|
84
|
+
q7 = @section.questions.build(:text => "What... is your quest? (e.g. To seek the Holy Grail)", :display_type => "label")
|
85
|
+
d1 = q7.build_dependency(:rule => "A", :question => q7)
|
86
|
+
dc1 = d1.dependency_conditions.build(:dependency => d1, :question => q6, :answer => a61, :operator => "==", :string_value => "It is 'Arthur', King of the Britons", :rule_key => "A")
|
87
|
+
|
88
|
+
q8 = @section.questions.build(:text => "How many pets do you own?")
|
89
|
+
a81 = q8.answers.build(:response_class => "integer", :question => q8)
|
90
|
+
v1 = a81.validations.build(:rule => "A", :answer => a81)
|
91
|
+
vc1 = v1.validation_conditions.build(:operator => ">=", :integer_value => 0, :validation => v1, :rule_key => "A")
|
92
|
+
|
93
|
+
q9 = @section.questions.build(:text => "Pick your favorite date AND time", :custom_renderer => "/partials/custom_question")
|
94
|
+
a91 = q9.answers.build(:response_class => "datetime", :question => q9)
|
95
|
+
|
96
|
+
q10 = @section.questions.build(:text => "What time do you usually take a lunch break?", :reference_identifier => "time_lunch")
|
97
|
+
a101 = q10.answers.build(:response_class => "time", :reference_identifier => 1, :question => q10)
|
98
|
+
|
99
|
+
Surveyor::Unparser.unparse(@survey).should ==
|
100
|
+
<<-dsl
|
101
|
+
survey "Simple survey", :description=>"very simple" do
|
102
|
+
section "Simple section" do
|
103
|
+
|
104
|
+
q_montypython3 "What... is your name? (e.g. It is 'Arthur', King of the Britons)"
|
105
|
+
a_1 :string
|
106
|
+
|
107
|
+
label "What... is your quest? (e.g. To seek the Holy Grail)"
|
108
|
+
dependency :rule=>"A"
|
109
|
+
condition_A :q_montypython3, "==", {:string_value=>"It is 'Arthur', King of the Britons", :answer_reference=>"1"}
|
110
|
+
|
111
|
+
q "How many pets do you own?"
|
112
|
+
a :integer
|
113
|
+
validation :rule=>"A"
|
114
|
+
condition_A ">=", :integer_value=>0
|
115
|
+
|
116
|
+
q "Pick your favorite date AND time", :custom_renderer=>"/partials/custom_question"
|
117
|
+
a :datetime
|
118
|
+
|
119
|
+
q_time_lunch "What time do you usually take a lunch break?"
|
120
|
+
a_1 :time
|
121
|
+
end
|
122
|
+
end
|
123
|
+
dsl
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
@@ -0,0 +1,144 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
3
|
+
|
4
|
+
describe Answer do
|
5
|
+
let(:answer){ FactoryGirl.create(:answer) }
|
6
|
+
|
7
|
+
context "when creating" do
|
8
|
+
it { answer.should be_valid }
|
9
|
+
it "deletes validation when deleted" do
|
10
|
+
v_id = FactoryGirl.create(:validation, :answer => answer).id
|
11
|
+
answer.destroy
|
12
|
+
Validation.find_by_id(v_id).should be_nil
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
context "with mustache text substitution" do
|
17
|
+
require 'mustache'
|
18
|
+
let(:mustache_context){ Class.new(::Mustache){ def site; "Northwestern"; end; def foo; "bar"; end } }
|
19
|
+
it "subsitutes Mustache context variables" do
|
20
|
+
answer.text = "You are in {{site}}"
|
21
|
+
answer.in_context(answer.text, mustache_context).should == "You are in Northwestern"
|
22
|
+
answer.text_for(nil, mustache_context).should == "You are in Northwestern"
|
23
|
+
|
24
|
+
answer.help_text = "{{site}} is your site"
|
25
|
+
answer.in_context(answer.help_text, mustache_context).should == "Northwestern is your site"
|
26
|
+
answer.help_text_for(mustache_context).should == "Northwestern is your site"
|
27
|
+
|
28
|
+
answer.default_value = "{{site}}"
|
29
|
+
answer.in_context(answer.default_value, mustache_context).should == "Northwestern"
|
30
|
+
answer.default_value_for(mustache_context).should == "Northwestern"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
context "with translations" do
|
35
|
+
require 'yaml'
|
36
|
+
let(:survey){ FactoryGirl.create(:survey) }
|
37
|
+
let(:survey_section){ FactoryGirl.create(:survey_section) }
|
38
|
+
let(:survey_translation){
|
39
|
+
FactoryGirl.create(:survey_translation, :locale => :es, :translation => {
|
40
|
+
:questions => {
|
41
|
+
:name => {
|
42
|
+
:answers => {
|
43
|
+
:name => {
|
44
|
+
:help_text => "Mi nombre es..."
|
45
|
+
}
|
46
|
+
}
|
47
|
+
}
|
48
|
+
}
|
49
|
+
}.to_yaml)
|
50
|
+
}
|
51
|
+
let(:question){ FactoryGirl.create(:question, :reference_identifier => "name") }
|
52
|
+
before do
|
53
|
+
answer.reference_identifier = "name"
|
54
|
+
answer.help_text = "My name is..."
|
55
|
+
answer.text = nil
|
56
|
+
answer.question = question
|
57
|
+
question.survey_section = survey_section
|
58
|
+
survey_section.survey = survey
|
59
|
+
survey.translations << survey_translation
|
60
|
+
end
|
61
|
+
it "returns its own translation" do
|
62
|
+
answer.translation(:es)[:help_text].should == "Mi nombre es..."
|
63
|
+
end
|
64
|
+
it "returns translations in views" do
|
65
|
+
answer.help_text_for(nil, :es).should == "Mi nombre es..."
|
66
|
+
end
|
67
|
+
it "returns its own default values" do
|
68
|
+
answer.translation(:de).should == {"text" => nil, "help_text" => "My name is...", "default_value" => nil}
|
69
|
+
end
|
70
|
+
it "returns default values in views" do
|
71
|
+
answer.help_text_for(nil, :de).should == "My name is..."
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
context "handling strings" do
|
76
|
+
it "#split preserves strings" do
|
77
|
+
answer.split(answer.text).should == "My favorite color is clear"
|
78
|
+
end
|
79
|
+
it "#split(:pre) preserves strings" do
|
80
|
+
answer.split(answer.text, :pre).should == "My favorite color is clear"
|
81
|
+
end
|
82
|
+
it "#split(:post) preserves strings" do
|
83
|
+
answer.split(answer.text, :post).should == ""
|
84
|
+
end
|
85
|
+
it "#split splits strings" do
|
86
|
+
answer.text = "before|after|extra"
|
87
|
+
answer.split(answer.text).should == "before|after|extra"
|
88
|
+
end
|
89
|
+
it "#split(:pre) splits strings" do
|
90
|
+
answer.text = "before|after|extra"
|
91
|
+
answer.split(answer.text, :pre).should == "before"
|
92
|
+
end
|
93
|
+
it "#split(:post) splits strings" do
|
94
|
+
answer.text = "before|after|extra"
|
95
|
+
answer.split(answer.text, :post).should == "after|extra"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
context "for views" do
|
100
|
+
it "#text_for with #display_type == image" do
|
101
|
+
answer.text = "rails.png"
|
102
|
+
answer.display_type = :image
|
103
|
+
answer.text_for.should =~ /<img alt="Rails" src="\/(images|assets)\/rails\.png" \/>/
|
104
|
+
end
|
105
|
+
it "#text_for with #display_type == hidden_label" do
|
106
|
+
answer.text = "Red"
|
107
|
+
answer.text_for.should == "Red"
|
108
|
+
answer.display_type = "hidden_label"
|
109
|
+
answer.text_for.should == ""
|
110
|
+
end
|
111
|
+
it "#default_value_for"
|
112
|
+
it "#help_text_for"
|
113
|
+
it "reports DOM ready #css_class from #custom_class" do
|
114
|
+
answer.custom_class = "foo bar"
|
115
|
+
answer.css_class.should == "foo bar"
|
116
|
+
end
|
117
|
+
it "reports DOM ready #css_class from #custom_class and #is_exclusive" do
|
118
|
+
answer.custom_class = "foo bar"
|
119
|
+
answer.is_exclusive = true
|
120
|
+
answer.css_class.should == "exclusive foo bar"
|
121
|
+
end
|
122
|
+
it "#text_for preserves strings" do
|
123
|
+
answer.text_for.should == "My favorite color is clear"
|
124
|
+
end
|
125
|
+
it "#text_for(:pre) preserves strings" do
|
126
|
+
answer.text_for(:pre).should == "My favorite color is clear"
|
127
|
+
end
|
128
|
+
it "#text_for(:post) preserves strings" do
|
129
|
+
answer.text_for(:post).should == ""
|
130
|
+
end
|
131
|
+
it "#text_for splits strings" do
|
132
|
+
answer.text = "before|after|extra"
|
133
|
+
answer.text_for.should == "before|after|extra"
|
134
|
+
end
|
135
|
+
it "#text_for(:pre) splits strings" do
|
136
|
+
answer.text = "before|after|extra"
|
137
|
+
answer.text_for(:pre).should == "before"
|
138
|
+
end
|
139
|
+
it "#text_for(:post) splits strings" do
|
140
|
+
answer.text = "before|after|extra"
|
141
|
+
answer.text_for(:post).should == "after|extra"
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,428 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
|
4
|
+
describe DependencyCondition do
|
5
|
+
it "should have a list of operators" do
|
6
|
+
%w(== != < > <= >=).each do |operator|
|
7
|
+
DependencyCondition.operators.include?(operator).should be_true
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
describe "instance" do
|
12
|
+
before(:each) do
|
13
|
+
@dependency_condition = DependencyCondition.new(
|
14
|
+
:dependency_id => 1, :question_id => 45, :operator => "==",
|
15
|
+
:answer_id => 23, :rule_key => "A")
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should be valid" do
|
19
|
+
@dependency_condition.should be_valid
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should be invalid without a parent dependency_id, question_id" do
|
23
|
+
# this causes issues with building and saving
|
24
|
+
# @dependency_condition.dependency_id = nil
|
25
|
+
# @dependency_condition.should have(1).errors_on(:dependency_id)
|
26
|
+
# @dependency_condition.question_id = nil
|
27
|
+
# @dependency_condition.should have(1).errors_on(:question_id)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should be invalid without an operator" do
|
31
|
+
@dependency_condition.operator = nil
|
32
|
+
@dependency_condition.should have(2).errors_on(:operator)
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should be invalid without a rule_key" do
|
36
|
+
@dependency_condition.should be_valid
|
37
|
+
@dependency_condition.rule_key = nil
|
38
|
+
@dependency_condition.should_not be_valid
|
39
|
+
@dependency_condition.should have(1).errors_on(:rule_key)
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should have unique rule_key within the context of a dependency" do
|
43
|
+
@dependency_condition.should be_valid
|
44
|
+
DependencyCondition.create(
|
45
|
+
:dependency_id => 2, :question_id => 46, :operator => "==",
|
46
|
+
:answer_id => 14, :rule_key => "B")
|
47
|
+
@dependency_condition.rule_key = "B" # rule key uniquness is scoped by dependency_id
|
48
|
+
@dependency_condition.dependency_id = 2
|
49
|
+
@dependency_condition.should_not be_valid
|
50
|
+
@dependency_condition.should have(1).errors_on(:rule_key)
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should have an operator in DependencyCondition.operators" do
|
54
|
+
DependencyCondition.operators.each do |o|
|
55
|
+
@dependency_condition.operator = o
|
56
|
+
@dependency_condition.should have(0).errors_on(:operator)
|
57
|
+
end
|
58
|
+
@dependency_condition.operator = "#"
|
59
|
+
@dependency_condition.should have(1).error_on(:operator)
|
60
|
+
end
|
61
|
+
it "should have a properly formed count operator" do
|
62
|
+
%w(count>1 count<1 count>=1 count<=1 count==1 count!=1).each do |o|
|
63
|
+
@dependency_condition.operator = o
|
64
|
+
@dependency_condition.should have(0).errors_on(:operator)
|
65
|
+
end
|
66
|
+
%w(count> count< count>= count<= count== count!=).each do |o|
|
67
|
+
@dependency_condition.operator = o
|
68
|
+
@dependency_condition.should have(1).errors_on(:operator)
|
69
|
+
end
|
70
|
+
%w(count=1 count><1 count<>1 count!1 count!!1 count=>1 count=<1).each do |o|
|
71
|
+
@dependency_condition.operator = o
|
72
|
+
@dependency_condition.should have(1).errors_on(:operator)
|
73
|
+
end
|
74
|
+
%w(count= count>< count<> count! count!! count=> count=< count> count< count>= count<= count== count!=).each do |o|
|
75
|
+
@dependency_condition.operator = o
|
76
|
+
@dependency_condition.should have(1).errors_on(:operator)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
it "returns true for != with no responses" do
|
82
|
+
question = FactoryGirl.create(:question)
|
83
|
+
dependency_condition = FactoryGirl.create(:dependency_condition, :rule_key => "C", :question => question)
|
84
|
+
rs = FactoryGirl.create(:response_set)
|
85
|
+
dependency_condition.to_hash(rs).should == {:C => false}
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
it "should not assume that Response#as is not nil" do
|
90
|
+
# q_HEIGHT_FT "Portion of height in whole feet (e.g., 5)",
|
91
|
+
# :pick=>:one
|
92
|
+
# a :integer
|
93
|
+
# a_neg_1 "Refused"
|
94
|
+
# a_neg_2 "Don't know"
|
95
|
+
# label "Provided value is outside of the suggested range (4 to 7 feet). This value is admissible, but you may wish to verify."
|
96
|
+
# dependency :rule=>"A or B"
|
97
|
+
# condition_A :q_HEIGHT_FT, "<", {:integer_value => "4"}
|
98
|
+
# condition_B :q_HEIGHT_FT, ">", {:integer_value => "7"}
|
99
|
+
|
100
|
+
answer = FactoryGirl.create(:answer, :response_class => :integer)
|
101
|
+
@dependency_condition = DependencyCondition.new(
|
102
|
+
:dependency => FactoryGirl.create(:dependency),
|
103
|
+
:question => answer.question,
|
104
|
+
:answer => answer,
|
105
|
+
:operator => ">",
|
106
|
+
:integer_value => 4,
|
107
|
+
:rule_key => "A")
|
108
|
+
|
109
|
+
response = FactoryGirl.create(:response, :answer => answer, :question => answer.question)
|
110
|
+
response_set = response.response_set
|
111
|
+
response.integer_value.should == nil
|
112
|
+
|
113
|
+
@dependency_condition.to_hash(response_set).should == {:A => false}
|
114
|
+
end
|
115
|
+
|
116
|
+
describe "evaluate '==' operator" do
|
117
|
+
before(:each) do
|
118
|
+
@a = FactoryGirl.create(:answer, :response_class => "answer")
|
119
|
+
@b = FactoryGirl.create(:answer, :question => @a.question)
|
120
|
+
@r = FactoryGirl.create(:response, :question => @a.question, :answer => @a)
|
121
|
+
@rs = @r.response_set
|
122
|
+
@dc = FactoryGirl.create(:dependency_condition, :question => @a.question, :answer => @a, :operator => "==", :rule_key => "D")
|
123
|
+
@dc.as(:answer).should == @r.as(:answer)
|
124
|
+
end
|
125
|
+
|
126
|
+
it "with checkbox/radio type response" do
|
127
|
+
@dc.to_hash(@rs).should == {:D => true}
|
128
|
+
@dc.answer = @b
|
129
|
+
@dc.to_hash(@rs).should == {:D => false}
|
130
|
+
end
|
131
|
+
|
132
|
+
it "with string value response" do
|
133
|
+
@a.update_attributes(:response_class => "string")
|
134
|
+
@r.update_attributes(:string_value => "hello123")
|
135
|
+
@dc.string_value = "hello123"
|
136
|
+
@dc.to_hash(@rs).should == {:D => true}
|
137
|
+
@r.update_attributes(:string_value => "foo_abc")
|
138
|
+
@dc.to_hash(@rs).should == {:D => false}
|
139
|
+
end
|
140
|
+
|
141
|
+
it "with a text value response" do
|
142
|
+
@a.update_attributes(:response_class => "text")
|
143
|
+
@r.update_attributes(:text_value => "hello this is some text for comparison")
|
144
|
+
@dc.text_value = "hello this is some text for comparison"
|
145
|
+
@dc.to_hash(@rs).should == {:D => true}
|
146
|
+
@r.update_attributes(:text_value => "Not the same text")
|
147
|
+
@dc.to_hash(@rs).should == {:D => false}
|
148
|
+
end
|
149
|
+
|
150
|
+
it "with an integer value response" do
|
151
|
+
@a.update_attributes(:response_class => "integer")
|
152
|
+
@r.update_attributes(:integer_value => 10045)
|
153
|
+
@dc.integer_value = 10045
|
154
|
+
@dc.to_hash(@rs).should == {:D => true}
|
155
|
+
@r.update_attributes(:integer_value => 421)
|
156
|
+
@dc.to_hash(@rs).should == {:D => false}
|
157
|
+
end
|
158
|
+
|
159
|
+
it "with a float value response" do
|
160
|
+
@a.update_attributes(:response_class => "float")
|
161
|
+
@r.update_attributes(:float_value => 121.1)
|
162
|
+
@dc.float_value = 121.1
|
163
|
+
@dc.to_hash(@rs).should == {:D => true}
|
164
|
+
@r.update_attributes(:float_value => 130.123)
|
165
|
+
@dc.to_hash(@rs).should == {:D => false}
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
describe "evaluate '!=' operator" do
|
170
|
+
before(:each) do
|
171
|
+
@a = FactoryGirl.create(:answer)
|
172
|
+
@b = FactoryGirl.create(:answer, :question => @a.question)
|
173
|
+
@r = FactoryGirl.create(:response, :question => @a.question, :answer => @a)
|
174
|
+
@rs = @r.response_set
|
175
|
+
@dc = FactoryGirl.create(:dependency_condition, :question => @a.question, :answer => @a, :operator => "!=", :rule_key => "E")
|
176
|
+
@dc.as(:answer).should == @r.as(:answer)
|
177
|
+
end
|
178
|
+
|
179
|
+
it "with checkbox/radio type response" do
|
180
|
+
@dc.to_hash(@rs).should == {:E => false}
|
181
|
+
@dc.answer_id = @a.id.to_i+1
|
182
|
+
@dc.to_hash(@rs).should == {:E => true}
|
183
|
+
end
|
184
|
+
|
185
|
+
it "with string value response" do
|
186
|
+
@a.update_attributes(:response_class => "string")
|
187
|
+
@r.update_attributes(:string_value => "hello123")
|
188
|
+
@dc.string_value = "hello123"
|
189
|
+
@dc.to_hash(@rs).should == {:E => false}
|
190
|
+
@r.update_attributes(:string_value => "foo_abc")
|
191
|
+
@dc.to_hash(@rs).should == {:E => true}
|
192
|
+
end
|
193
|
+
|
194
|
+
it "with a text value response" do
|
195
|
+
@a.update_attributes(:response_class => "text")
|
196
|
+
@r.update_attributes(:text_value => "hello this is some text for comparison")
|
197
|
+
@dc.text_value = "hello this is some text for comparison"
|
198
|
+
@dc.to_hash(@rs).should == {:E => false}
|
199
|
+
@r.update_attributes(:text_value => "Not the same text")
|
200
|
+
@dc.to_hash(@rs).should == {:E => true}
|
201
|
+
end
|
202
|
+
|
203
|
+
it "with an integer value response" do
|
204
|
+
@a.update_attributes(:response_class => "integer")
|
205
|
+
@r.update_attributes(:integer_value => 10045)
|
206
|
+
@dc.integer_value = 10045
|
207
|
+
@dc.to_hash(@rs).should == {:E => false}
|
208
|
+
@r.update_attributes(:integer_value => 421)
|
209
|
+
@dc.to_hash(@rs).should == {:E => true}
|
210
|
+
end
|
211
|
+
|
212
|
+
it "with a float value response" do
|
213
|
+
@a.update_attributes(:response_class => "float")
|
214
|
+
@r.update_attributes(:float_value => 121.1)
|
215
|
+
@dc.float_value = 121.1
|
216
|
+
@dc.to_hash(@rs).should == {:E => false}
|
217
|
+
@r.update_attributes(:float_value => 130.123)
|
218
|
+
@dc.to_hash(@rs).should == {:E => true}
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
describe "evaluate the '<' operator" do
|
223
|
+
before(:each) do
|
224
|
+
@a = FactoryGirl.create(:answer)
|
225
|
+
@b = FactoryGirl.create(:answer, :question => @a.question)
|
226
|
+
@r = FactoryGirl.create(:response, :question => @a.question, :answer => @a)
|
227
|
+
@rs = @r.response_set
|
228
|
+
@dc = FactoryGirl.create(:dependency_condition, :question => @a.question, :answer => @a, :operator => "<", :rule_key => "F")
|
229
|
+
@dc.as(:answer).should == @r.as(:answer)
|
230
|
+
end
|
231
|
+
|
232
|
+
it "with an integer value response" do
|
233
|
+
@a.update_attributes(:response_class => "integer")
|
234
|
+
@r.update_attributes(:integer_value => 50)
|
235
|
+
@dc.integer_value = 100
|
236
|
+
@dc.to_hash(@rs).should == {:F => true}
|
237
|
+
@r.update_attributes(:integer_value => 421)
|
238
|
+
@dc.to_hash(@rs).should == {:F => false}
|
239
|
+
end
|
240
|
+
|
241
|
+
it "with a float value response" do
|
242
|
+
@a.update_attributes(:response_class => "float")
|
243
|
+
@r.update_attributes(:float_value => 5.1)
|
244
|
+
@dc.float_value = 121.1
|
245
|
+
@dc.to_hash(@rs).should == {:F => true}
|
246
|
+
@r.update_attributes(:float_value => 130.123)
|
247
|
+
@dc.to_hash(@rs).should == {:F => false}
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
describe "evaluate the '<=' operator" do
|
252
|
+
before(:each) do
|
253
|
+
@a = FactoryGirl.create(:answer)
|
254
|
+
@b = FactoryGirl.create(:answer, :question => @a.question)
|
255
|
+
@r = FactoryGirl.create(:response, :question => @a.question, :answer => @a)
|
256
|
+
@rs = @r.response_set
|
257
|
+
@dc = FactoryGirl.create(:dependency_condition, :question => @a.question, :answer => @a, :operator => "<=", :rule_key => "G")
|
258
|
+
@dc.as(:answer).should == @r.as(:answer)
|
259
|
+
end
|
260
|
+
|
261
|
+
it "with an integer value response" do
|
262
|
+
@a.update_attributes(:response_class => "integer")
|
263
|
+
@r.update_attributes(:integer_value => 50)
|
264
|
+
@dc.integer_value = 100
|
265
|
+
@dc.to_hash(@rs).should == {:G => true}
|
266
|
+
@r.update_attributes(:integer_value => 100)
|
267
|
+
@dc.to_hash(@rs).should == {:G => true}
|
268
|
+
@r.update_attributes(:integer_value => 421)
|
269
|
+
@dc.to_hash(@rs).should == {:G => false}
|
270
|
+
end
|
271
|
+
|
272
|
+
it "with a float value response" do
|
273
|
+
@a.update_attributes(:response_class => "float")
|
274
|
+
@r.update_attributes(:float_value => 5.1)
|
275
|
+
@dc.float_value = 121.1
|
276
|
+
@dc.to_hash(@rs).should == {:G => true}
|
277
|
+
@r.update_attributes(:float_value => 121.1)
|
278
|
+
@dc.to_hash(@rs).should == {:G => true}
|
279
|
+
@r.update_attributes(:float_value => 130.123)
|
280
|
+
@dc.to_hash(@rs).should == {:G => false}
|
281
|
+
end
|
282
|
+
|
283
|
+
end
|
284
|
+
|
285
|
+
describe "evaluate the '>' operator" do
|
286
|
+
before(:each) do
|
287
|
+
@a = FactoryGirl.create(:answer)
|
288
|
+
@b = FactoryGirl.create(:answer, :question => @a.question)
|
289
|
+
@r = FactoryGirl.create(:response, :question => @a.question, :answer => @a)
|
290
|
+
@rs = @r.response_set
|
291
|
+
@dc = FactoryGirl.create(:dependency_condition, :question => @a.question, :answer => @a, :operator => ">", :rule_key => "H")
|
292
|
+
@dc.as(:answer).should == @r.as(:answer)
|
293
|
+
end
|
294
|
+
|
295
|
+
it "with an integer value response" do
|
296
|
+
@a.update_attributes(:response_class => "integer")
|
297
|
+
@r.update_attributes(:integer_value => 50)
|
298
|
+
@dc.integer_value = 100
|
299
|
+
@dc.to_hash(@rs).should == {:H => false}
|
300
|
+
@r.update_attributes(:integer_value => 421)
|
301
|
+
@dc.to_hash(@rs).should == {:H => true}
|
302
|
+
end
|
303
|
+
|
304
|
+
it "with a float value response" do
|
305
|
+
@a.update_attributes(:response_class => "float")
|
306
|
+
@r.update_attributes(:float_value => 5.1)
|
307
|
+
@dc.float_value = 121.1
|
308
|
+
@dc.to_hash(@rs).should == {:H => false}
|
309
|
+
@r.update_attributes(:float_value => 130.123)
|
310
|
+
@dc.to_hash(@rs).should == {:H => true}
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
describe "evaluate the '>=' operator" do
|
315
|
+
before(:each) do
|
316
|
+
@a = FactoryGirl.create(:answer)
|
317
|
+
@b = FactoryGirl.create(:answer, :question => @a.question)
|
318
|
+
@r = FactoryGirl.create(:response, :question => @a.question, :answer => @a)
|
319
|
+
@rs = @r.response_set
|
320
|
+
@dc = FactoryGirl.create(:dependency_condition, :question => @a.question, :answer => @a, :operator => ">=", :rule_key => "I")
|
321
|
+
@dc.as(:answer).should == @r.as(:answer)
|
322
|
+
end
|
323
|
+
|
324
|
+
it "with an integer value response" do
|
325
|
+
@a.update_attributes(:response_class => "integer")
|
326
|
+
@r.update_attributes(:integer_value => 50)
|
327
|
+
@dc.integer_value = 100
|
328
|
+
@dc.to_hash(@rs).should == {:I => false}
|
329
|
+
@r.update_attributes(:integer_value => 100)
|
330
|
+
@dc.to_hash(@rs).should == {:I => true}
|
331
|
+
@r.update_attributes(:integer_value => 421)
|
332
|
+
@dc.to_hash(@rs).should == {:I => true}
|
333
|
+
end
|
334
|
+
|
335
|
+
it "with a float value response" do
|
336
|
+
@a.update_attributes(:response_class => "float")
|
337
|
+
@r.update_attributes(:float_value => 5.1)
|
338
|
+
@dc.float_value = 121.1
|
339
|
+
@dc.to_hash(@rs).should == {:I => false}
|
340
|
+
@r.update_attributes(:float_value => 121.1)
|
341
|
+
@dc.to_hash(@rs).should == {:I => true}
|
342
|
+
@r.update_attributes(:float_value => 130.123)
|
343
|
+
@dc.to_hash(@rs).should == {:I => true}
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
describe "evaluating with response_class string" do
|
348
|
+
it "should compare answer ids when the dependency condition string_value is nil" do
|
349
|
+
@a = FactoryGirl.create(:answer, :response_class => "string")
|
350
|
+
@b = FactoryGirl.create(:answer, :question => @a.question)
|
351
|
+
@r = FactoryGirl.create(:response, :question => @a.question, :answer => @a, :string_value => "")
|
352
|
+
@rs = @r.response_set
|
353
|
+
@dc = FactoryGirl.create(:dependency_condition, :question => @a.question, :answer => @a, :operator => "==", :rule_key => "J")
|
354
|
+
@dc.to_hash(@rs).should == {:J => true}
|
355
|
+
end
|
356
|
+
|
357
|
+
it "should compare strings when the dependency condition string_value is not nil, even if it is blank" do
|
358
|
+
@a = FactoryGirl.create(:answer, :response_class => "string")
|
359
|
+
@b = FactoryGirl.create(:answer, :question => @a.question)
|
360
|
+
@r = FactoryGirl.create(:response, :question => @a.question, :answer => @a, :string_value => "foo")
|
361
|
+
@rs = @r.response_set
|
362
|
+
@dc = FactoryGirl.create(:dependency_condition, :question => @a.question, :answer => @a, :operator => "==", :rule_key => "K", :string_value => "foo")
|
363
|
+
@dc.to_hash(@rs).should == {:K => true}
|
364
|
+
|
365
|
+
@r.update_attributes(:string_value => "")
|
366
|
+
@dc.string_value = ""
|
367
|
+
@dc.to_hash(@rs).should == {:K => true}
|
368
|
+
end
|
369
|
+
end
|
370
|
+
|
371
|
+
describe "evaluate 'count' operator" do
|
372
|
+
before(:each) do
|
373
|
+
@q = FactoryGirl.create(:question)
|
374
|
+
@dc = DependencyCondition.new(:operator => "count>2", :rule_key => "M", :question => @q)
|
375
|
+
@as = []
|
376
|
+
3.times do
|
377
|
+
@as << FactoryGirl.create(:answer, :question => @q, :response_class => "answer")
|
378
|
+
end
|
379
|
+
@rs = FactoryGirl.create(:response_set)
|
380
|
+
@as.slice(0,2).each do |a|
|
381
|
+
FactoryGirl.create(:response, :question => @q, :answer => a, :response_set => @rs)
|
382
|
+
end
|
383
|
+
@rs.save
|
384
|
+
end
|
385
|
+
|
386
|
+
it "with operator with >" do
|
387
|
+
@dc.to_hash(@rs).should == {:M => false}
|
388
|
+
FactoryGirl.create(:response, :question => @q, :answer => @as.last, :response_set => @rs)
|
389
|
+
@rs.reload.responses.count.should == 3
|
390
|
+
@dc.to_hash(@rs.reload).should == {:M => true}
|
391
|
+
end
|
392
|
+
|
393
|
+
it "with operator with <" do
|
394
|
+
@dc.operator = "count<2"
|
395
|
+
@dc.to_hash(@rs).should == {:M => false}
|
396
|
+
@dc.operator = "count<3"
|
397
|
+
@dc.to_hash(@rs).should == {:M => true}
|
398
|
+
end
|
399
|
+
|
400
|
+
it "with operator with <=" do
|
401
|
+
@dc.operator = "count<=1"
|
402
|
+
@dc.to_hash(@rs).should == {:M => false}
|
403
|
+
@dc.operator = "count<=2"
|
404
|
+
@dc.to_hash(@rs).should == {:M => true}
|
405
|
+
@dc.operator = "count<=3"
|
406
|
+
@dc.to_hash(@rs).should == {:M => true}
|
407
|
+
end
|
408
|
+
|
409
|
+
it "with operator with >=" do
|
410
|
+
@dc.operator = "count>=1"
|
411
|
+
@dc.to_hash(@rs).should == {:M => true}
|
412
|
+
@dc.operator = "count>=2"
|
413
|
+
@dc.to_hash(@rs).should == {:M => true}
|
414
|
+
@dc.operator = "count>=3"
|
415
|
+
@dc.to_hash(@rs).should == {:M => false}
|
416
|
+
end
|
417
|
+
|
418
|
+
it "with operator with !=" do
|
419
|
+
@dc.operator = "count!=1"
|
420
|
+
@dc.to_hash(@rs).should == {:M => true}
|
421
|
+
@dc.operator = "count!=2"
|
422
|
+
@dc.to_hash(@rs).should == {:M => false}
|
423
|
+
@dc.operator = "count!=3"
|
424
|
+
@dc.to_hash(@rs).should == {:M => true}
|
425
|
+
end
|
426
|
+
end
|
427
|
+
|
428
|
+
end
|