quby 4.0.3 → 5.0.0.pre1
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 +4 -4
- data/lib/quby/questionnaires.rb +1 -0
- data/lib/quby/questionnaires/api.rb +5 -1
- data/lib/quby/questionnaires/deserializer.rb +433 -0
- data/lib/quby/questionnaires/dsl.rb +10 -15
- data/lib/quby/questionnaires/entities.rb +1 -0
- data/lib/quby/questionnaires/entities/charting/line_chart.rb +23 -0
- data/lib/quby/questionnaires/entities/charting/overview_chart.rb +3 -1
- data/lib/quby/questionnaires/entities/definition.rb +3 -5
- data/lib/quby/questionnaires/entities/fields.rb +0 -15
- data/lib/quby/questionnaires/entities/question.rb +9 -32
- data/lib/quby/questionnaires/entities/questionnaire.rb +4 -15
- data/lib/quby/questionnaires/entities/questions/checkbox_question.rb +0 -24
- data/lib/quby/questionnaires/entities/questions/date_question.rb +0 -8
- data/lib/quby/questionnaires/entities/score_calculation.rb +36 -3
- data/lib/quby/questionnaires/repos.rb +1 -0
- data/lib/quby/questionnaires/repos/bundle_disk_repo.rb +51 -0
- data/lib/quby/version.rb +1 -1
- data/spec/internal/log/test-events.log +166 -0
- data/spec/internal/log/test.log +6170 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-27-14-25-21.063.html +207 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-27-14-25-21.063.png +0 -0
- data/spec/quby/answers/services/answer_validations_spec.rb +8 -8
- data/spec/quby/questionnaires/deserializer/questionnaire_spec.rb +237 -0
- data/spec/quby/questionnaires/dsl_spec.rb +0 -9
- data/spec/quby/questionnaires/entities/fields_spec.rb +3 -3
- data/spec/quby/questionnaires/entities/question_spec.rb +0 -8
- data/spec/quby/questionnaires/entities/questionnaire_spec.rb +2 -26
- data/spec/quby/table_backend/range_tree_spec.rb +7 -0
- data/spec/spec_helper.rb +1 -0
- metadata +25 -46
- data/lib/quby/questionnaires/dsl/base.rb +0 -20
- data/lib/quby/questionnaires/dsl/calls_custom_methods.rb +0 -29
- data/lib/quby/questionnaires/dsl/charting/bar_chart_builder.rb +0 -18
- data/lib/quby/questionnaires/dsl/charting/chart_builder.rb +0 -91
- data/lib/quby/questionnaires/dsl/charting/line_chart_builder.rb +0 -57
- data/lib/quby/questionnaires/dsl/charting/overview_chart_builder.rb +0 -31
- data/lib/quby/questionnaires/dsl/charting/radar_chart_builder.rb +0 -18
- data/lib/quby/questionnaires/dsl/helpers.rb +0 -51
- data/lib/quby/questionnaires/dsl/panel_builder.rb +0 -80
- data/lib/quby/questionnaires/dsl/question_builder.rb +0 -40
- data/lib/quby/questionnaires/dsl/questionnaire_builder.rb +0 -252
- data/lib/quby/questionnaires/dsl/questions/base.rb +0 -179
- data/lib/quby/questionnaires/dsl/questions/checkbox_question_builder.rb +0 -20
- data/lib/quby/questionnaires/dsl/questions/date_question_builder.rb +0 -18
- data/lib/quby/questionnaires/dsl/questions/deprecated_question_builder.rb +0 -18
- data/lib/quby/questionnaires/dsl/questions/float_question_builder.rb +0 -21
- data/lib/quby/questionnaires/dsl/questions/integer_question_builder.rb +0 -21
- data/lib/quby/questionnaires/dsl/questions/radio_question_builder.rb +0 -20
- data/lib/quby/questionnaires/dsl/questions/select_question_builder.rb +0 -18
- data/lib/quby/questionnaires/dsl/questions/string_question_builder.rb +0 -20
- data/lib/quby/questionnaires/dsl/questions/text_question_builder.rb +0 -22
- data/lib/quby/questionnaires/dsl/score_builder.rb +0 -22
- data/lib/quby/questionnaires/dsl/standardized_panel_generators.rb +0 -33
- data/lib/quby/questionnaires/dsl/table_builder.rb +0 -48
- data/lib/quby/questionnaires/services/definition_validator.rb +0 -298
- data/spec/quby/questionnaires/dsl/calls_custom_methods_spec.rb +0 -38
- data/spec/quby/questionnaires/dsl/charting/bar_chart_builder_spec.rb +0 -41
- data/spec/quby/questionnaires/dsl/charting/chart_builder_spec.rb +0 -127
- data/spec/quby/questionnaires/dsl/charting/line_chart_builder_spec.rb +0 -66
- data/spec/quby/questionnaires/dsl/charting/radar_chart_builder_spec.rb +0 -41
- data/spec/quby/questionnaires/dsl/helpers_spec.rb +0 -80
- data/spec/quby/questionnaires/dsl/questionnaire_builder_spec.rb +0 -480
- data/spec/quby/questionnaires/services/definition_validator_spec.rb +0 -793
- data/spec/support/examples_for_chart_builders.rb +0 -59
@@ -1,298 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
require 'nokogumbo'
|
3
|
-
require 'active_model'
|
4
|
-
require 'quby/questionnaires/entities/questionnaire'
|
5
|
-
|
6
|
-
module Quby
|
7
|
-
module Questionnaires
|
8
|
-
module Services
|
9
|
-
class DefinitionValidator < ActiveModel::Validator
|
10
|
-
MAX_KEY_LENGTH = 19
|
11
|
-
KEY_PREFIX = 'v_'
|
12
|
-
|
13
|
-
attr_reader :definition
|
14
|
-
attr_reader :questionnaire
|
15
|
-
|
16
|
-
def validate(definition)
|
17
|
-
questionnaire = DSL.build_from_definition(definition)
|
18
|
-
validate_fields(questionnaire)
|
19
|
-
validate_title(questionnaire)
|
20
|
-
validate_questions(questionnaire)
|
21
|
-
validate_scores(questionnaire)
|
22
|
-
validate_table_edgecases(questionnaire)
|
23
|
-
validate_flags(questionnaire)
|
24
|
-
validate_respondent_types(questionnaire)
|
25
|
-
validate_outcome_tables(questionnaire)
|
26
|
-
validate_markdown_fields(questionnaire) if questionnaire.validate_html
|
27
|
-
validate_raw_content_items(questionnaire) if questionnaire.validate_html
|
28
|
-
# Some compilation errors are Exceptions (pure syntax errors) and some StandardErrors (NameErrors)
|
29
|
-
rescue Exception => exception # rubocop:disable Lint/RescueException
|
30
|
-
definition.errors.add(:sourcecode, {message: "Questionnaire error: #{definition.key}\n" \
|
31
|
-
"#{exception.message}",
|
32
|
-
backtrace: exception.backtrace[0..5].join("<br/>")})
|
33
|
-
end
|
34
|
-
|
35
|
-
def validate_fields(questionnaire)
|
36
|
-
questionnaire.fields.input_keys
|
37
|
-
.find { |k| !k.is_a?(Symbol) }
|
38
|
-
&.tap { |k| fail "Input key #{k} is not a symbol" }
|
39
|
-
questionnaire.fields.answer_keys
|
40
|
-
.find { |k| !k.is_a?(Symbol) }
|
41
|
-
&.tap { |k| fail "Answer key #{k} is not a symbol" }
|
42
|
-
end
|
43
|
-
|
44
|
-
def validate_title(questionnaire)
|
45
|
-
if questionnaire.title.blank?
|
46
|
-
fail "Questionnaire title is missing."
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
def validate_questions(questionnaire)
|
51
|
-
questionnaire.answer_keys.each do |key|
|
52
|
-
validate_key_format(key)
|
53
|
-
end
|
54
|
-
|
55
|
-
questionnaire.question_hash.each_value do |question|
|
56
|
-
validate_question(question)
|
57
|
-
subquestions_cant_have_default_invisible question
|
58
|
-
validate_subquestion_absence_in_select question
|
59
|
-
validate_placeholder_options_nil_values question
|
60
|
-
validate_values_unique question
|
61
|
-
|
62
|
-
validate_question_options(questionnaire, question)
|
63
|
-
validate_presence_of_titles question
|
64
|
-
validate_no_spaces_before_question_nr_in_title question
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
def validate_question(question)
|
69
|
-
unless question.valid?
|
70
|
-
fail "Question #{question.key} is invalid: #{question.errors.full_messages.join(', ')}"
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
def validate_scores(questionnaire)
|
75
|
-
questionnaire.scores.each do |score|
|
76
|
-
validate_score_key_length(score)
|
77
|
-
validate_score_label_present(score)
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
def validate_question_options(questionnaire, question)
|
82
|
-
question.options.each do |option|
|
83
|
-
msg_base = "Question #{option.question.key} option #{option.key}"
|
84
|
-
to_be_hidden_questions_exist_and_not_subquestion?(questionnaire, option, msg_base: msg_base)
|
85
|
-
to_be_shown_questions_exist_and_not_subquestion?(questionnaire, option, msg_base: msg_base)
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
def validate_presence_of_titles(question)
|
90
|
-
return if question.allow_blank_titles
|
91
|
-
if !question.subquestion? && question.title.blank? && question.context_free_title.blank?
|
92
|
-
fail "Question #{question.key} must define either `:title` or `:context_free_title`."
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
def validate_table_edgecases(questionnaire)
|
97
|
-
questionnaire.panels.each do |panel|
|
98
|
-
tables = panel.items.select { |item| item.is_a?(Entities::Table) }
|
99
|
-
tables.each do |table|
|
100
|
-
questions = table.items.select { |item| item.is_a?(Entities::Question) }
|
101
|
-
questions.each { |question| validate_table_question(question) }
|
102
|
-
end
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
def validate_flags(questionnaire)
|
107
|
-
questionnaire.flags.each_value do |flag|
|
108
|
-
validate_flag_shows(questionnaire, flag)
|
109
|
-
validate_flag_hides(questionnaire, flag)
|
110
|
-
validate_flag_depends_on(questionnaire, flag)
|
111
|
-
end
|
112
|
-
end
|
113
|
-
|
114
|
-
def validate_flag_shows(questionnaire, flag)
|
115
|
-
unknown_questions = flag.shows_questions.select { |key| !questionnaire.key_in_use?(key) }
|
116
|
-
return if unknown_questions.blank?
|
117
|
-
|
118
|
-
fail ArgumentError, "Flag '#{key}' has unknown shows_questions keys #{unknown_questions}"
|
119
|
-
end
|
120
|
-
|
121
|
-
def validate_flag_hides(questionnaire, flag)
|
122
|
-
unknown_questions = flag.hides_questions.select { |key| !questionnaire.key_in_use?(key) }
|
123
|
-
return if unknown_questions.blank?
|
124
|
-
|
125
|
-
fail ArgumentError, "Flag '#{key}' has unknown hides_questions keys #{unknown_questions}"
|
126
|
-
end
|
127
|
-
|
128
|
-
def validate_flag_depends_on(questionnaire, flag)
|
129
|
-
return if flag.depends_on.blank? || questionnaire.flags.key?(flag.depends_on)
|
130
|
-
|
131
|
-
fail ArgumentError, "Flag #{flag.key} depends_on nonexistent flag '#{flag.depends_on}'"
|
132
|
-
end
|
133
|
-
|
134
|
-
def validate_respondent_types(questionnaire)
|
135
|
-
valid_respondent_types = Entities::Questionnaire::RESPONDENT_TYPES
|
136
|
-
|
137
|
-
invalid_types = questionnaire.respondent_types - valid_respondent_types
|
138
|
-
|
139
|
-
if invalid_types.present?
|
140
|
-
fail "Invalid respondent types: :#{invalid_types.join(', :')}\n"\
|
141
|
-
"Choose one or more from: :#{valid_respondent_types.join(', :')}"
|
142
|
-
end
|
143
|
-
end
|
144
|
-
|
145
|
-
def validate_outcome_tables(questionnaire)
|
146
|
-
questionnaire.outcome_tables.each do |table|
|
147
|
-
next if table.valid?
|
148
|
-
fail "Outcome table #{table.errors.full_messages}"
|
149
|
-
end
|
150
|
-
end
|
151
|
-
|
152
|
-
def to_be_hidden_questions_exist_and_not_subquestion?(questionnaire, option, msg_base:)
|
153
|
-
return if option.hides_questions.blank?
|
154
|
-
msg_base += " hides_questions"
|
155
|
-
option.hides_questions.each do |key|
|
156
|
-
validate_question_key_exists?(questionnaire, key, msg_base: msg_base)
|
157
|
-
validate_not_subquestion(questionnaire, key, msg_base: msg_base)
|
158
|
-
end
|
159
|
-
end
|
160
|
-
|
161
|
-
def to_be_shown_questions_exist_and_not_subquestion?(questionnaire, option, msg_base:)
|
162
|
-
return if option.shows_questions.blank?
|
163
|
-
msg_base += " shows_questions"
|
164
|
-
option.shows_questions.each do |key|
|
165
|
-
validate_question_key_exists?(questionnaire, key, msg_base: msg_base)
|
166
|
-
validate_not_subquestion(questionnaire, key, msg_base: msg_base)
|
167
|
-
end
|
168
|
-
end
|
169
|
-
|
170
|
-
def subquestions_cant_have_default_invisible(question)
|
171
|
-
if question.subquestion? && question.default_invisible
|
172
|
-
fail "Question #{question.key} is a subquestion with default_invisible."
|
173
|
-
end
|
174
|
-
end
|
175
|
-
|
176
|
-
private
|
177
|
-
|
178
|
-
# Don't write question numbers as " 1. Title", but as "1\\. Title".
|
179
|
-
def validate_no_spaces_before_question_nr_in_title(question)
|
180
|
-
if question.title && question.title.match(/^\s{2,}\d+\\\./)
|
181
|
-
fail "Question with number does not need leading spaces."
|
182
|
-
end
|
183
|
-
end
|
184
|
-
|
185
|
-
def validate_question_key_exists?(questionnaire, key, msg_base:)
|
186
|
-
unless questionnaire.question_hash[key]
|
187
|
-
fail msg_base + " references nonexistent question #{key}"
|
188
|
-
end
|
189
|
-
end
|
190
|
-
|
191
|
-
def validate_not_subquestion(questionnaire, key, msg_base:)
|
192
|
-
if questionnaire.question_hash[key].subquestion?
|
193
|
-
fail msg_base + " references subquestion #{key}"
|
194
|
-
end
|
195
|
-
end
|
196
|
-
|
197
|
-
def validate_key_format(key)
|
198
|
-
if key.to_s.length > MAX_KEY_LENGTH
|
199
|
-
fail "Key '#{key}' should contain at most #{MAX_KEY_LENGTH} characters."
|
200
|
-
end
|
201
|
-
unless key.to_s.start_with?(KEY_PREFIX)
|
202
|
-
fail "Key '#{key}' should start with '#{KEY_PREFIX}'."
|
203
|
-
end
|
204
|
-
end
|
205
|
-
|
206
|
-
def validate_score_key_length(score)
|
207
|
-
if score.key.to_s.length > MAX_KEY_LENGTH
|
208
|
-
fail "Score key `#{score.key}` should contain at most #{MAX_KEY_LENGTH} characters."
|
209
|
-
end
|
210
|
-
end
|
211
|
-
|
212
|
-
def validate_score_label_present(score)
|
213
|
-
fail "Score #{score.key} label must be passed in as an option." unless score.label.present?
|
214
|
-
end
|
215
|
-
|
216
|
-
def validate_subquestion_absence_in_select(question)
|
217
|
-
return unless question.type == :select
|
218
|
-
question.options.each do |option|
|
219
|
-
unless option.questions.empty?
|
220
|
-
fail "Question '#{question.key}' of type ':select' may not include other questions."
|
221
|
-
end
|
222
|
-
end
|
223
|
-
end
|
224
|
-
|
225
|
-
def validate_placeholder_options_nil_values(question)
|
226
|
-
question.options.each do |question_option|
|
227
|
-
if question_option.placeholder && question_option.value.present?
|
228
|
-
fail "#{question.key}:#{question_option.key}: Placeholder options should not have values defined."
|
229
|
-
end
|
230
|
-
end
|
231
|
-
end
|
232
|
-
|
233
|
-
def validate_values_unique(question)
|
234
|
-
return if question.type == :check_box || question.allow_duplicate_option_values
|
235
|
-
|
236
|
-
question.options.each_with_object([]) do |question_option, seen_values|
|
237
|
-
next if question_option.placeholder || question_option.inner_title
|
238
|
-
|
239
|
-
fail "#{question.key}:#{question_option.key}: Has no option value defined." if question_option.value.blank?
|
240
|
-
if seen_values.include?(question_option.value)
|
241
|
-
fail "#{question.key}:#{question_option.key}: " \
|
242
|
-
"Another option with value #{question_option.value} is already defined."
|
243
|
-
end
|
244
|
-
seen_values << question_option.value
|
245
|
-
end
|
246
|
-
end
|
247
|
-
|
248
|
-
def validate_table_question(question)
|
249
|
-
question.subquestions.each do |subquestion|
|
250
|
-
if subquestion.presentation != :next_to_title
|
251
|
-
fail "Question #{question.key} is inside a table, but has a subquestion #{subquestion.key}, " \
|
252
|
-
"which is not allowed."
|
253
|
-
end
|
254
|
-
end
|
255
|
-
end
|
256
|
-
|
257
|
-
def validate_markdown_fields(questionnaire)
|
258
|
-
questionnaire.panels.each do |panel|
|
259
|
-
panel.items.select { |item| item.is_a?(Entities::Text) }.each do |text_item|
|
260
|
-
validate_markdown(text_item.str, text_item.str)
|
261
|
-
end
|
262
|
-
end
|
263
|
-
questionnaire.questions.each do |question|
|
264
|
-
Entities::Question::MARKDOWN_ATTRIBUTES.each do |attr|
|
265
|
-
validate_markdown(question.send(attr), "#{question.key}.#{attr}")
|
266
|
-
question.options.each do |option|
|
267
|
-
Entities::QuestionOption::MARKDOWN_ATTRIBUTES.each do |option_attr|
|
268
|
-
validate_markdown(option.send(option_attr), "#{question.key}:#{option.key}.#{option_attr}")
|
269
|
-
end
|
270
|
-
end
|
271
|
-
end
|
272
|
-
end
|
273
|
-
end
|
274
|
-
|
275
|
-
def validate_raw_content_items(questionnaire)
|
276
|
-
questionnaire.panels.each do |panel|
|
277
|
-
panel.items.each do |item|
|
278
|
-
next if item.raw_content.blank?
|
279
|
-
|
280
|
-
validate_html(item.raw_content)
|
281
|
-
end
|
282
|
-
end
|
283
|
-
end
|
284
|
-
|
285
|
-
def validate_markdown(markdown, key)
|
286
|
-
validate_html(MarkdownParser.new(markdown).to_html, key)
|
287
|
-
end
|
288
|
-
|
289
|
-
def validate_html(html, key = nil)
|
290
|
-
fragment = Nokogiri::HTML5.fragment(html, max_errors: 3)
|
291
|
-
return unless fragment.errors.present?
|
292
|
-
|
293
|
-
fail "#{key || html} contains invalid html: #{fragment.errors.map(&:to_s).join(', ')}."
|
294
|
-
end
|
295
|
-
end
|
296
|
-
end
|
297
|
-
end
|
298
|
-
end
|
@@ -1,38 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'spec_helper'
|
4
|
-
|
5
|
-
module Quby::Questionnaires::DSL
|
6
|
-
describe CallsCustomMethods do
|
7
|
-
|
8
|
-
class TestCustomMethod
|
9
|
-
prepend CallsCustomMethods
|
10
|
-
|
11
|
-
attr_reader :test
|
12
|
-
def initialize(*args)
|
13
|
-
@test = 'attribute'
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
let(:custom_methods) { {custom: -> { 'custom_method' }} }
|
18
|
-
let(:test_object) { TestCustomMethod.new(custom_methods: custom_methods) }
|
19
|
-
|
20
|
-
describe 'prepending' do
|
21
|
-
it 'does not skip the actual initializer' do
|
22
|
-
expect(test_object.test).to eq('attribute')
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
describe '#method_missing' do
|
27
|
-
it 'allows the class to receive messages defined in custom_methods' do
|
28
|
-
expect(test_object.custom).to eq('custom_method')
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
describe '#respond_to_missing?' do
|
33
|
-
it 'also considers custom methods' do
|
34
|
-
expect(test_object.send(:respond_to_missing?, :custom)).to eq(true)
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
@@ -1,41 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'spec_helper'
|
4
|
-
|
5
|
-
module Quby::Questionnaires::DSL
|
6
|
-
describe BarChartBuilder do
|
7
|
-
it_behaves_like ChartBuilder
|
8
|
-
|
9
|
-
let(:questionnaire) { double(key: 'questionnaire_key') }
|
10
|
-
|
11
|
-
it 'makes a bar chart' do
|
12
|
-
expect(dsl { }).to be_an_instance_of(::Quby::Questionnaires::Entities::Charting::BarChart)
|
13
|
-
end
|
14
|
-
|
15
|
-
def dsl(key = :test, options = {}, &block)
|
16
|
-
builder = BarChartBuilder.new(questionnaire, key, options)
|
17
|
-
builder.build(&block)
|
18
|
-
end
|
19
|
-
|
20
|
-
it 'sets y-axis range' do
|
21
|
-
expect(dsl { range 0..40 }.y_range).to eq (0..40)
|
22
|
-
end
|
23
|
-
|
24
|
-
it 'sets y-axis tick interval' do
|
25
|
-
expect(dsl { tick_interval 1 }.tick_interval).to eq 1
|
26
|
-
end
|
27
|
-
|
28
|
-
it 'sets plotlines' do
|
29
|
-
plotlines =
|
30
|
-
dsl do
|
31
|
-
plotline 40, :orange
|
32
|
-
plotline 60, :red
|
33
|
-
end.plotlines
|
34
|
-
|
35
|
-
expect(plotlines).to eq [
|
36
|
-
{value: 40, color: :orange, width: 1, zIndex: 3},
|
37
|
-
{value: 60, color: :red, width: 1, zIndex: 3}
|
38
|
-
]
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
@@ -1,127 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'spec_helper'
|
4
|
-
|
5
|
-
module Quby::Questionnaires::DSL
|
6
|
-
describe ChartBuilder do
|
7
|
-
|
8
|
-
let(:plottable_key) { :some_key }
|
9
|
-
let(:plottable) { Quby::Questionnaires::Entities::ScoreCalculation.new plottable_key, {} }
|
10
|
-
let(:questionnaire) do
|
11
|
-
double(key: 'questionnaire_key').tap do |questionnaire|
|
12
|
-
allow(questionnaire).to receive(:find_plottable).with(plottable_key).and_return plottable
|
13
|
-
end
|
14
|
-
end
|
15
|
-
let(:chart_plottables) { [] }
|
16
|
-
let(:chart_class) { Quby::Questionnaires::Entities::Charting::Chart }
|
17
|
-
before { ChartBuilder.set_chart_class(chart_class) }
|
18
|
-
let(:chart_builder) { ChartBuilder.new questionnaire, 'chart_key' }
|
19
|
-
|
20
|
-
def dsl(key = :test, options = {}, &block)
|
21
|
-
builder = ChartBuilder.new(questionnaire, key, options)
|
22
|
-
builder.build(&block)
|
23
|
-
end
|
24
|
-
|
25
|
-
describe '#plot' do
|
26
|
-
it 'fetches a plottable by key from the questionnaire' do
|
27
|
-
expect(questionnaire).to receive(:find_plottable).with(plottable_key)
|
28
|
-
chart_builder.plot plottable_key
|
29
|
-
end
|
30
|
-
|
31
|
-
it 'creates a plottable with the options merged into the plottable options' do
|
32
|
-
allow(plottable).to receive(:options).and_return({some: 'options', other: 'options'})
|
33
|
-
expect(Quby::Questionnaires::Entities::Charting::Plottable).to receive(:new)
|
34
|
-
.with(plottable.key,
|
35
|
-
some: 'different_options',
|
36
|
-
other: 'options',
|
37
|
-
questionnaire_key: 'questionnaire_key')
|
38
|
-
chart_builder.plot plottable_key, some: 'different_options'
|
39
|
-
end
|
40
|
-
|
41
|
-
it 'sets the label from a question title when no label is present' do
|
42
|
-
plottable = Quby::Questionnaires::Entities::Question.new plottable_key, title: 'some_title'
|
43
|
-
expect(questionnaire).to receive(:find_plottable).with(plottable_key).and_return plottable
|
44
|
-
expect(Quby::Questionnaires::Entities::Charting::Plottable).to receive(:new)
|
45
|
-
.with(plottable_key, questionnaire_key: 'questionnaire_key', label: 'some_title')
|
46
|
-
chart_builder.plot plottable_key
|
47
|
-
end
|
48
|
-
|
49
|
-
it 'does not set the label when it is given in the options' do
|
50
|
-
plottable = Quby::Questionnaires::Entities::Question.new plottable_key, title: 'some_title'
|
51
|
-
expect(questionnaire).to receive(:find_plottable).with(plottable_key).and_return plottable
|
52
|
-
expect(Quby::Questionnaires::Entities::Charting::Plottable).to receive(:new)
|
53
|
-
.with(plottable_key, questionnaire_key: 'questionnaire_key', label: 'some_label')
|
54
|
-
chart_builder.plot plottable_key, label: 'some_label'
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
describe '#y_categories' do
|
59
|
-
it 'assigns y_categories' do
|
60
|
-
categories = %w(Bad Great Best)
|
61
|
-
chart_builder.y_categories categories
|
62
|
-
expect(chart_builder.build { }.y_categories).to eq(categories)
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
describe '#plotband' do
|
67
|
-
it 'can set plotbands' do
|
68
|
-
plotbands =
|
69
|
-
dsl do
|
70
|
-
plotband 2, 5, :yellow
|
71
|
-
plotband 5, 8, :red
|
72
|
-
end.plotbands
|
73
|
-
|
74
|
-
expect(plotbands).to eq [
|
75
|
-
{from: 2, to: 5, color: :yellow},
|
76
|
-
{from: 5, to: 8, color: :red}
|
77
|
-
]
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
describe '#y_range_categories' do
|
82
|
-
it 'sets RangeCategories.new(*parameters).as_range_hash to the chart y_range_categories' do
|
83
|
-
expect(RangeCategories).to receive(:new).with(0, 'Zeer laag', 30).and_return(double(as_range_hash: 'foo'))
|
84
|
-
chart_builder.y_range_categories 0, 'Zeer laag', 30
|
85
|
-
expect(chart_builder.build { }.y_range_categories).to eq('foo')
|
86
|
-
end
|
87
|
-
|
88
|
-
it 'converts the parameters to a range hash' do
|
89
|
-
expected_categories = {(0.0...30.0) => "Zeer laag", (30.0...40.0) => "Laag", (40.0...60.0) => "Gemiddeld",
|
90
|
-
(60.0...70.0) => "Hoog", (70.0..100.0) => "Zeer hoog"}
|
91
|
-
chart_builder.y_range_categories 0, 'Zeer laag',
|
92
|
-
30, 'Laag',
|
93
|
-
40, 'Gemiddeld',
|
94
|
-
60, 'Hoog',
|
95
|
-
70, 'Zeer hoog', 100
|
96
|
-
expect(chart_builder.build { }.y_range_categories).to eq(expected_categories)
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
describe '#range' do
|
101
|
-
it 'assigns y_range' do
|
102
|
-
chart_builder.range 0..5
|
103
|
-
expect(chart_builder.build { }.y_range).to eq(0..5)
|
104
|
-
end
|
105
|
-
end
|
106
|
-
describe '#validate' do
|
107
|
-
describe 'when there is no range defined but y_categories are present' do
|
108
|
-
it 'uses the 0..y_categories.count-1 as a range' do
|
109
|
-
chart_builder.y_categories %w(Bad Great Best)
|
110
|
-
expect(chart_builder.build { }.y_range).to eq(0..2)
|
111
|
-
end
|
112
|
-
end
|
113
|
-
describe 'when range and y_categories are present' do
|
114
|
-
it 'raises if the range does not correspond to the y_categories length' do
|
115
|
-
chart_builder.range 0..3
|
116
|
-
chart_builder.y_categories %w(Bad Great Best)
|
117
|
-
expect { chart_builder.build { } }.to raise_error(ArgumentError)
|
118
|
-
end
|
119
|
-
it 'does not raise if the range matches the y_categories' do
|
120
|
-
chart_builder.range 0..2
|
121
|
-
chart_builder.y_categories %w(Bad Great Best)
|
122
|
-
expect { chart_builder.build { } }.not_to raise_error
|
123
|
-
end
|
124
|
-
end
|
125
|
-
end
|
126
|
-
end
|
127
|
-
end
|