qti 2.19.0 → 2.20.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 59b8ff402b8251fba645448e0c6159d73cbfbaa383d7a796c379136ba5b957bc
4
- data.tar.gz: 9b3d848749f95e31dc095c0b3d8b6eddafdc4eb2eed7fc775b5c3d34a696e56e
3
+ metadata.gz: 86c46f8f8cc44f5462d95d93455aefd56aae83e8f6291a492459ba9da425c510
4
+ data.tar.gz: 63429e02991279043a3a0380e720fee6bb27b36132cfcb97837a68ddec0dd0cc
5
5
  SHA512:
6
- metadata.gz: 9487c787ace952ccb0771573bf7293c3797982c411f3c21af35bd57fca53b1a956cf593650aff155af2ea29471e59efa7586bf89a39cf9485396790d62ecc37e
7
- data.tar.gz: c23ecddd40c5dc43e7078b66e569658fa34e25e5511d4999252b9e81cd0bc72ddb098fa64c1f99b6dcf5056ed38b40150b385e6ac6cfad8ca3a77081e9899490
6
+ metadata.gz: 2f628da6521093b39fa707d8305431e5282f4a5c9bae5f386f8e91b59d56be873f3705844b146a220a97e6ca1d8eb7f7b822e22d37c162499b4e418b2aebe5e3
7
+ data.tar.gz: 232035507f0adfe737d0271c9e2d4200330e0921205c4ae2f5800564d755ed82630ad58955615102e6e3c40392f4270fb0a9167e70f769c29e701b96eb6acf40
@@ -1,5 +1,6 @@
1
1
  module Qti
2
2
  module Models
3
+ # rubocop:disable Metrics/ClassLength
3
4
  class AssessmentMeta < Qti::Models::Base
4
5
  def title
5
6
  sanitize_content!(tag_under_quiz('title'))
@@ -9,6 +10,14 @@ module Qti
9
10
  sanitize_content!(tag_under_quiz('description'))
10
11
  end
11
12
 
13
+ def shuffle_questions
14
+ tag_under_quiz('shuffle_questions')
15
+ end
16
+
17
+ def shuffle_questions?
18
+ string_true?(shuffle_questions)
19
+ end
20
+
12
21
  def shuffle_answers
13
22
  tag_under_quiz('shuffle_answers')
14
23
  end
@@ -17,6 +26,10 @@ module Qti
17
26
  string_true?(shuffle_answers)
18
27
  end
19
28
 
29
+ def calculator_type
30
+ tag_under_quiz('calculator_type')
31
+ end
32
+
20
33
  def hide_results
21
34
  tag_under_quiz('hide_results')
22
35
  end
@@ -25,6 +38,14 @@ module Qti
25
38
  tag_under_quiz('scoring_policy')
26
39
  end
27
40
 
41
+ def cooling_period_seconds_raw
42
+ tag_under_quiz('cooling_period_seconds')
43
+ end
44
+
45
+ def cooling_period_seconds
46
+ cooling_period_seconds_raw&.to_i
47
+ end
48
+
28
49
  def quiz_type
29
50
  tag_under_quiz('quiz_type')
30
51
  end
@@ -168,6 +189,14 @@ module Qti
168
189
  tag_under_quiz('hide_correct_answers_at')
169
190
  end
170
191
 
192
+ def allow_clear_mc_selection
193
+ tag_under_quiz('allow_clear_mc_selection')
194
+ end
195
+
196
+ def allow_clear_mc_selection?
197
+ string_true?(allow_clear_mc_selection)
198
+ end
199
+
171
200
  def require_lockdown_browser
172
201
  tag_under_quiz('require_lockdown_browser')
173
202
  end
@@ -196,6 +225,80 @@ module Qti
196
225
  tag_under_quiz('lockdown_browser_monitor_data')
197
226
  end
198
227
 
228
+ def nq_ip_filters
229
+ @doc.xpath('//xmlns:quiz/xmlns:nq_ip_filter').map do |ip_filter|
230
+ [ip_filter.attributes['from']&.value, ip_filter.attributes['to']&.value]
231
+ end
232
+ end
233
+
234
+ def nq_ip_filters_enabled?
235
+ nq_ip_filters.any?
236
+ end
237
+
238
+ def result_view_restricted
239
+ tag_under_quiz('result_view_restricted')
240
+ end
241
+
242
+ def result_view_restricted?
243
+ string_true?(result_view_restricted)
244
+ end
245
+
246
+ def display_items
247
+ tag_under_quiz('display_items')
248
+ end
249
+
250
+ def display_items?
251
+ string_true?(display_items)
252
+ end
253
+
254
+ def display_item_feedback
255
+ tag_under_quiz('display_item_feedback')
256
+ end
257
+
258
+ def display_item_feedback?
259
+ string_true?(display_item_feedback)
260
+ end
261
+
262
+ def display_item_response
263
+ tag_under_quiz('display_item_response')
264
+ end
265
+
266
+ def display_item_response?
267
+ string_true?(display_item_response)
268
+ end
269
+
270
+ def display_points_awarded
271
+ tag_under_quiz('display_points_awarded')
272
+ end
273
+
274
+ def display_points_awarded?
275
+ string_true?(display_points_awarded)
276
+ end
277
+
278
+ def display_points_possible
279
+ tag_under_quiz('display_points_possible')
280
+ end
281
+
282
+ def display_points_possible?
283
+ string_true?(display_points_possible)
284
+ end
285
+
286
+ def display_item_correct_answer
287
+ tag_under_quiz('display_item_correct_answer')
288
+ end
289
+
290
+ def display_item_correct_answer?
291
+ string_true?(display_item_correct_answer)
292
+ end
293
+
294
+ def display_item_response_correctness
295
+ tag_under_quiz('display_item_response_correctness')
296
+ end
297
+
298
+ def display_item_response_correctness?
299
+ string_true?(display_item_response_correctness)
300
+ end
301
+
199
302
  private
200
303
 
201
304
  def tag_under_quiz(tag)
@@ -209,10 +312,12 @@ module Qti
209
312
  value&.casecmp('true')&.zero? || false
210
313
  end
211
314
  end
315
+ # rubocop:enable Metrics/ClassLength
212
316
 
213
317
  module AssessmentMetaBase
214
- delegate :title, :description,
215
- :shuffle_answers?, :scoring_policy, :points_possible,
318
+ delegate :title, :description, :shuffle_questions?,
319
+ :shuffle_answers?, :calculator_type, :scoring_policy,
320
+ :cooling_period_seconds, :points_possible,
216
321
  :hide_results, :quiz_type, :anonymous_submissions?,
217
322
  :could_be_locked?, :allowed_attempts, :one_question_at_a_time?,
218
323
  :cant_go_back?, :available?, :one_time_results?,
@@ -222,7 +327,11 @@ module Qti
222
327
  :lock_at, :unlock_at, :due_at, :require_lockdown_browser?,
223
328
  :require_lockdown_browser_for_results?,
224
329
  :require_lockdown_browser_monitor?, :show_correct_answers?,
225
- :lockdown_browser_monitor_data,
330
+ :allow_clear_mc_selection?, :lockdown_browser_monitor_data,
331
+ :nq_ip_filters, :nq_ip_filters_enabled?,
332
+ :result_view_restricted?, :display_items?, :display_item_feedback?,
333
+ :display_item_response?, :display_points_awarded?, :display_points_possible?,
334
+ :display_item_correct_answer?, :display_item_response_correctness?,
226
335
  to: :@canvas_meta_data, prefix: :canvas, allow_nil: true
227
336
 
228
337
  alias canvas_instructions canvas_description
@@ -17,6 +17,10 @@ module Qti
17
17
  @parent_identifier ||= @node.parent.parent.attributes['ident']&.value
18
18
  end
19
19
 
20
+ def position
21
+ @position ||= @node.attributes['position']&.value
22
+ end
23
+
20
24
  def item_body
21
25
  @item_body ||= begin
22
26
  node = @node.dup
@@ -39,19 +39,33 @@ module Qti
39
39
 
40
40
  def blank_id(stem_item)
41
41
  return stem_item unless canvas_custom_fitb?
42
- canvas_blank_id(stem_item)
42
+ return canvas_blank_id(stem_item) unless new_quizzes_fib?
43
+ nq_blank_id(stem_item)
43
44
  end
44
45
 
45
46
  def blank_value(blank_id)
47
+ return nq_blank_value(blank_id) if new_quizzes_fib?
48
+
46
49
  blank = canvas_fib_responses.find { |response| response[:id] == blank_id }
47
50
  correct_choice = blank[:choices].find { |c| c[:id] == correct_choice_id(blank) }
48
51
  (correct_choice || {})[:item_body] || blank&.dig(:choices, 0, :item_body)
49
52
  end
50
53
 
54
+ # The correct answer value is not necessarily the first one in NQ
55
+ def nq_blank_value(blank_id)
56
+ blank = canvas_fib_responses.find { |response| response[:id] == blank_id }
57
+ correct_choice = blank[:choices].find { |choice| choice[:id] == correct_answer_map[blank_id] }
58
+ correct_choice[:item_body]
59
+ end
60
+
51
61
  def canvas_custom_fitb?
52
62
  @canvas_custom_fitb ||= BaseInteraction.canvas_custom_fitb?(@node)
53
63
  end
54
64
 
65
+ def new_quizzes_fib?
66
+ @new_quizzes_fib ||= BaseInteraction.new_quizzes_fib?(@node)
67
+ end
68
+
55
69
  def canvas_blank_id(stem_item)
56
70
  blank_id = nil
57
71
  node.xpath('.//xmlns:response_lid/xmlns:material').children.map do |response_lid_node|
@@ -62,6 +76,14 @@ module Qti
62
76
  blank_id
63
77
  end
64
78
 
79
+ def nq_blank_id(stem_item)
80
+ blank_id = nil
81
+ node.xpath('.//xmlns:response_lid').map do |resp|
82
+ blank_id = resp[:ident] if resp[:ident] == "response_#{stem_item}"
83
+ end
84
+ blank_id
85
+ end
86
+
65
87
  def canvas_fib_responses
66
88
  @base_canvas_blanks ||= node.xpath('.//xmlns:response_lid').map do |resp|
67
89
  index = 0
@@ -80,8 +102,23 @@ module Qti
80
102
  @canvas_fib_response_ids ||= canvas_fib_responses.map { |b| "[#{b[:id].sub(/^response_/, '')}]" }
81
103
  end
82
104
 
105
+ def correct_answer_map
106
+ @correct_answer_map ||= correct_answers
107
+ end
108
+
83
109
  private
84
110
 
111
+ # Maps response ids to their correct answer's id
112
+ def correct_answers
113
+ correct_answers = {}
114
+
115
+ node.xpath('.//xmlns:varequal').each do |correct_answer|
116
+ correct_answers[correct_answer.attributes['respident']&.value] = correct_answer.text
117
+ end
118
+
119
+ correct_answers
120
+ end
121
+
85
122
  def correct_choice_id(blank)
86
123
  node.xpath('.//xmlns:resprocessing/xmlns:respcondition/xmlns:conditionvar/xmlns:varequal')
87
124
  .find { |varequal| varequal.attribute('respident').value == blank[:id] }&.text
@@ -20,6 +20,15 @@ module Qti
20
20
  question_type(node) == 'fill_in_multiple_blanks_question'
21
21
  end
22
22
 
23
+ def self.new_quizzes_fib?(node)
24
+ return false unless canvas_multiple_fib?(node)
25
+
26
+ first_match = node.at_xpath('.//xmlns:response_label')
27
+ return false if first_match.blank?
28
+
29
+ first_match.attr('scoring_algorithm').present?
30
+ end
31
+
23
32
  def self.question_type(node)
24
33
  path = './/xmlns:qtimetadatafield/xmlns:fieldlabel' \
25
34
  '[text()="question_type"]/../xmlns:fieldentry'
@@ -30,6 +30,10 @@ module Qti
30
30
  @canvas_multiple_fib ||= BaseInteraction.canvas_multiple_fib?(@node)
31
31
  end
32
32
 
33
+ def new_quizzes_fib?
34
+ @new_quizzes_fib ||= BaseInteraction.new_quizzes_fib?(@node)
35
+ end
36
+
33
37
  def stem_items
34
38
  if canvas_multiple_fib?
35
39
  canvas_stem_items(node.at_xpath('.//xmlns:mattext').text)
@@ -76,17 +80,55 @@ module Qti
76
80
  end
77
81
 
78
82
  def scoring_data_structs
79
- answer_nodes.map do |value_node|
83
+ @scoring_data_structs ||= answer_nodes.map do |value_node|
80
84
  ScoringData.new(
81
85
  value_node.content,
82
86
  rcardinality,
83
87
  id: scoring_data_id(value_node),
84
88
  case: scoring_data_case(value_node),
85
- parent_identifier: value_node.parent.parent.attributes['ident']&.value
89
+ parent_identifier: value_node.parent.parent.attributes['ident']&.value,
90
+ scoring_algorithm: scoring_data_algorithm(value_node),
91
+ answer_type: scoring_data_answer_type(value_node),
92
+ scoring_options: scoring_data_options(value_node)
86
93
  )
87
94
  end
88
95
  end
89
96
 
97
+ def wordbank_answer_present?
98
+ return false unless first_wordbank_answer
99
+
100
+ true
101
+ end
102
+
103
+ def wordbank_allow_reuse?
104
+ return unless wordbank_answer_present?
105
+
106
+ wordbank_struct = scoring_data_structs.find { |struct| struct.answer_type == 'wordbank' }
107
+ return false unless wordbank_struct.scoring_options['allow_reuse'] == 'true'
108
+
109
+ true
110
+ end
111
+
112
+ def wordbank_choices
113
+ return unless wordbank_answer_present?
114
+
115
+ choices = []
116
+
117
+ wordbank_answers = all_wordbank_answers.map do |node|
118
+ V1::Models::Choices::FillBlankChoice.new(node, self)
119
+ end
120
+
121
+ wordbank_answers.each do |wordbank_answer|
122
+ choices.push({ id: wordbank_answer.identifier, item_body: wordbank_answer.item_body })
123
+ end
124
+
125
+ choices
126
+ end
127
+
128
+ def correct_answer_map
129
+ @correct_answer_map ||= super
130
+ end
131
+
90
132
  private
91
133
 
92
134
  def rcardinality
@@ -110,6 +152,45 @@ module Qti
110
152
  node.attributes['case']&.value || 'no'
111
153
  end
112
154
 
155
+ def scoring_data_algorithm(node)
156
+ node.attributes['scoring_algorithm']&.value || 'TextInChoices'
157
+ end
158
+
159
+ def scoring_data_answer_type(node)
160
+ node.attributes['answer_type']&.value || 'openEntry'
161
+ end
162
+
163
+ def scoring_data_options(node)
164
+ options = {}
165
+ algorithm = scoring_data_algorithm(node)
166
+ type = scoring_data_answer_type(node)
167
+
168
+ if algorithm == 'TextCloseEnough'
169
+ options['levenshtein_distance'] = levenshtein_distance(node)
170
+ options['ignore_case'] = ignore_case(node)
171
+ end
172
+
173
+ options['allow_reuse'] = node.attributes['allow_reuse']&.value if type == 'wordbank'
174
+
175
+ options
176
+ end
177
+
178
+ def levenshtein_distance(node)
179
+ node.attributes['levenshtein_distance']&.value || '1'
180
+ end
181
+
182
+ def ignore_case(node)
183
+ node.attributes['ignore_case']&.value || 'false'
184
+ end
185
+
186
+ def first_wordbank_answer
187
+ @first_wordbank_answer ||= answer_nodes.find { |node| scoring_data_answer_type(node) == 'wordbank' }
188
+ end
189
+
190
+ def all_wordbank_answers
191
+ @all_wordbank_answers ||= answer_nodes.find_all { |node| scoring_data_answer_type(node) == 'wordbank' }
192
+ end
193
+
113
194
  def qti_stem_item(index, stem_item)
114
195
  if stem_item.xpath('./xmlns:render_fib').present?
115
196
  blank_id = stem_item.attributes['ident'].value
@@ -1,9 +1,11 @@
1
1
  module Qti
2
2
  module V1
3
3
  module Models
4
- ScoringData = Struct.new(:values, :rcardinality, :id, :case, :parent_identifier) do
4
+ ScoringData = Struct.new(:values, :rcardinality, :id, :case, :parent_identifier, :scoring_algorithm,
5
+ :answer_type, :scoring_options) do
5
6
  def initialize(values, rcardinality, options = {})
6
- super(values, rcardinality, options[:id], options[:case], options[:parent_identifier])
7
+ super(values, rcardinality, options[:id], options[:case], options[:parent_identifier],
8
+ options[:scoring_algorithm], options[:answer_type], options[:scoring_options])
7
9
  end
8
10
  end
9
11
  end
data/lib/qti/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Qti
2
- VERSION = '2.19.0'.freeze
2
+ VERSION = '2.20.1'.freeze
3
3
  end
@@ -0,0 +1,86 @@
1
+ <?xml version="1.0" encoding="ISO-8859-1"?>
2
+ <questestinterop xmlns="http://www.imsglobal.org/xsd/ims_qtiasiv1p2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.imsglobal.org/xsd/ims_qtiasiv1p2 http://www.imsglobal.org/xsd/ims_qtiasiv1p2p1.xsd">
3
+ <item ident="a7ca973956582afc57a81192b444632c" title="NQ Fill in Multiple Blanks Answer Types">
4
+ <itemmetadata>
5
+ <qtimetadata>
6
+ <qtimetadatafield>
7
+ <fieldlabel>question_type</fieldlabel>
8
+ <fieldentry>fill_in_multiple_blanks_question</fieldentry>
9
+ </qtimetadatafield>
10
+ <qtimetadatafield>
11
+ <fieldlabel>points_possible</fieldlabel>
12
+ <fieldentry>1.0</fieldentry>
13
+ </qtimetadatafield>
14
+ <qtimetadatafield>
15
+ <fieldlabel>original_answer_ids</fieldlabel>
16
+ <fieldentry>["378646a8-b823-4e5b-beb8-19ca63f1f355", "c9400bfb-78ea-45b6-b5a5-e3311f6d5ed0", "76350749-49ac-4094-966b-c4e1f12d54bc", "cd11d826-906d-4dc4-b78d-d66516eb94ce", "bae0bd4f-2327-4a3e-b29f-199e1e279e91"]</fieldentry>
17
+ </qtimetadatafield>
18
+ <qtimetadatafield>
19
+ <fieldlabel>assessment_question_identifierref</fieldlabel>
20
+ <fieldentry>1c4b07be8a149dfa8f6a263a548cdd84</fieldentry>
21
+ </qtimetadatafield>
22
+ </qtimetadata>
23
+ </itemmetadata>
24
+ <presentation>
25
+ <material>
26
+ <mattext texttype="text/html">&lt;p&gt;Roses are [a20629ed-0c0b-4959-b565-a80c0f199602] and [ab37a945-ebad-4787-a356-66c3c91efcc6].&lt;/p&gt;</mattext>
27
+ </material>
28
+ <response_lid ident="response_a20629ed-0c0b-4959-b565-a80c0f199602">
29
+ <material>
30
+ <mattext>black</mattext>
31
+ </material>
32
+ <render_choice>
33
+ <response_label ident="378646a8-b823-4e5b-beb8-19ca63f1f355" position="1" scoring_algorithm='Equivalence' answer_type='dropdown'>
34
+ <material>
35
+ <mattext texttype="text/plain">black</mattext>
36
+ </material>
37
+ </response_label>
38
+ <response_label ident="c9400bfb-78ea-45b6-b5a5-e3311f6d5ed0" position="2" scoring_algorithm='Equivalence' answer_type='dropdown'>
39
+ <material>
40
+ <mattext texttype="text/plain">violet</mattext>
41
+ </material>
42
+ </response_label>
43
+ <response_label ident="76350749-49ac-4094-966b-c4e1f12d54bc" position="3" scoring_algorithm='Equivalence' answer_type='dropdown'>
44
+ <material>
45
+ <mattext texttype="text/plain">grey</mattext>
46
+ </material>
47
+ </response_label>
48
+ </render_choice>
49
+ </response_lid>
50
+ <response_lid ident="response_ab37a945-ebad-4787-a356-66c3c91efcc6">
51
+ <material>
52
+ <mattext>white</mattext>
53
+ </material>
54
+ <render_choice>
55
+ <response_label ident="cd11d826-906d-4dc4-b78d-d66516eb94ce" scoring_algorithm='TextEquivalence' answer_type='wordbank' allow_reuse='true'>
56
+ <material>
57
+ <mattext texttype="text/plain">brown</mattext>
58
+ </material>
59
+ </response_label>
60
+ <response_label ident="bae0bd4f-2327-4a3e-b29f-199e1e279e91" scoring_algorithm='TextEquivalence' answer_type='wordbank' allow_reuse='true'>
61
+ <material>
62
+ <mattext texttype="text/plain">white</mattext>
63
+ </material>
64
+ </response_label>
65
+ </render_choice>
66
+ </response_lid>
67
+ </presentation>
68
+ <resprocessing>
69
+ <outcomes>
70
+ <decvar maxvalue="100" minvalue="0" varname="SCORE" vartype="Decimal"/>
71
+ </outcomes>
72
+ <respcondition>
73
+ <conditionvar>
74
+ <varequal respident="response_a20629ed-0c0b-4959-b565-a80c0f199602">378646a8-b823-4e5b-beb8-19ca63f1f355</varequal>
75
+ </conditionvar>
76
+ <setvar action="Add" varname="SCORE">50.00</setvar>
77
+ </respcondition>
78
+ <respcondition>
79
+ <conditionvar>
80
+ <varequal respident="response_ab37a945-ebad-4787-a356-66c3c91efcc6">bae0bd4f-2327-4a3e-b29f-199e1e279e91</varequal>
81
+ </conditionvar>
82
+ <setvar action="Add" varname="SCORE">50.00</setvar>
83
+ </respcondition>
84
+ </resprocessing>
85
+ </item>
86
+ </questestinterop>
@@ -0,0 +1,130 @@
1
+ <?xml version="1.0" encoding="ISO-8859-1"?>
2
+ <questestinterop xmlns="http://www.imsglobal.org/xsd/ims_qtiasiv1p2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.imsglobal.org/xsd/ims_qtiasiv1p2 http://www.imsglobal.org/xsd/ims_qtiasiv1p2p1.xsd">
3
+ <item ident="97b0baae152f137d14503e2a2125c4d0" title="NQ Fill in Multiple Blanks Scoring Algorithms">
4
+ <itemmetadata>
5
+ <qtimetadata>
6
+ <qtimetadatafield>
7
+ <fieldlabel>question_type</fieldlabel>
8
+ <fieldentry>fill_in_multiple_blanks_question</fieldentry>
9
+ </qtimetadatafield>
10
+ <qtimetadatafield>
11
+ <fieldlabel>points_possible</fieldlabel>
12
+ <fieldentry>1.0</fieldentry>
13
+ </qtimetadatafield>
14
+ <qtimetadatafield>
15
+ <fieldlabel>original_answer_ids</fieldlabel>
16
+ <fieldentry>["bafd8242-ecdb-4158-a6d3-4ff15e016cb8-0", "d719aee0-6ce0-462c-9c0d-be63ba8d3408-0", "ce47aaf6-c1dd-4db8-9a9f-7f5c131d8a90-0", "41dfd716-8fd9-466a-97fa-33d353e44b42-0", "41dfd716-8fd9-466a-97fa-33d353e44b42-1", "a053119f-6a61-4372-ac79-4b2a7de0232f-0"]</fieldentry>
17
+ </qtimetadatafield>
18
+ <qtimetadatafield>
19
+ <fieldlabel>assessment_question_identifierref</fieldlabel>
20
+ <fieldentry>eaeec2d07f83998ee7b317ffde86dbc0</fieldentry>
21
+ </qtimetadatafield>
22
+ </qtimetadata>
23
+ </itemmetadata>
24
+ <presentation>
25
+ <material>
26
+ <mattext texttype="text/html">&lt;p&gt;Roses are [bafd8242-ecdb-4158-a6d3-4ff15e016cb8], [d719aee0-6ce0-462c-9c0d-be63ba8d3408], [ce47aaf6-c1dd-4db8-9a9f-7f5c131d8a90], [41dfd716-8fd9-466a-97fa-33d353e44b42], and [a053119f-6a61-4372-ac79-4b2a7de0232f].&lt;/p&gt;</mattext>
27
+ </material>
28
+ <response_lid ident="response_bafd8242-ecdb-4158-a6d3-4ff15e016cb8">
29
+ <material>
30
+ <mattext>red</mattext>
31
+ </material>
32
+ <render_choice>
33
+ <response_label ident="bafd8242-ecdb-4158-a6d3-4ff15e016cb8-0" scoring_algorithm='TextContainsAnswer' answer_type='openEntry'>
34
+ <material>
35
+ <mattext texttype="text/plain">red</mattext>
36
+ </material>
37
+ </response_label>
38
+ </render_choice>
39
+ </response_lid>
40
+ <response_lid ident="response_d719aee0-6ce0-462c-9c0d-be63ba8d3408">
41
+ <material>
42
+ <mattext>blue</mattext>
43
+ </material>
44
+ <render_choice>
45
+ <response_label ident="d719aee0-6ce0-462c-9c0d-be63ba8d3408-0" scoring_algorithm='TextCloseEnough' answer_type='openEntry' levenshtein_distance='1' ignore_case='true'>
46
+ <material>
47
+ <mattext texttype="text/plain">blue</mattext>
48
+ </material>
49
+ </response_label>
50
+ </render_choice>
51
+ </response_lid>
52
+ <response_lid ident="response_ce47aaf6-c1dd-4db8-9a9f-7f5c131d8a90">
53
+ <material>
54
+ <mattext>green</mattext>
55
+ </material>
56
+ <render_choice>
57
+ <response_label ident="ce47aaf6-c1dd-4db8-9a9f-7f5c131d8a90-0" scoring_algorithm='TextEquivalence' answer_type='openEntry'>
58
+ <material>
59
+ <mattext texttype="text/plain">green</mattext>
60
+ </material>
61
+ </response_label>
62
+ </render_choice>
63
+ </response_lid>
64
+ <response_lid ident="response_41dfd716-8fd9-466a-97fa-33d353e44b42">
65
+ <material>
66
+ <mattext>yellow</mattext>
67
+ </material>
68
+ <render_choice>
69
+ <response_label ident="41dfd716-8fd9-466a-97fa-33d353e44b42-0" scoring_algorithm='TextInChoices' answer_type='openEntry'>
70
+ <material>
71
+ <mattext texttype="text/plain">yellow</mattext>
72
+ </material>
73
+ </response_label>
74
+ <response_label ident="41dfd716-8fd9-466a-97fa-33d353e44b42-1" scoring_algorithm='TextInChoices' answer_type='openEntry'>
75
+ <material>
76
+ <mattext texttype="text/plain">teal</mattext>
77
+ </material>
78
+ </response_label>
79
+ </render_choice>
80
+ </response_lid>
81
+ <response_lid ident="response_a053119f-6a61-4372-ac79-4b2a7de0232f">
82
+ <material>
83
+ <mattext>^orange$</mattext>
84
+ </material>
85
+ <render_choice>
86
+ <response_label ident="a053119f-6a61-4372-ac79-4b2a7de0232f-0" scoring_algorithm='TextRegex' answer_type='openEntry'>
87
+ <material>
88
+ <mattext texttype="text/plain">^orange$</mattext>
89
+ </material>
90
+ </response_label>
91
+ </render_choice>
92
+ </response_lid>
93
+ </presentation>
94
+ <resprocessing>
95
+ <outcomes>
96
+ <decvar maxvalue="100" minvalue="0" varname="SCORE" vartype="Decimal"/>
97
+ </outcomes>
98
+ <respcondition>
99
+ <conditionvar>
100
+ <varequal respident="response_bafd8242-ecdb-4158-a6d3-4ff15e016cb8">bafd8242-ecdb-4158-a6d3-4ff15e016cb8-0</varequal>
101
+ </conditionvar>
102
+ <setvar action="Add" varname="SCORE">20.00</setvar>
103
+ </respcondition>
104
+ <respcondition>
105
+ <conditionvar>
106
+ <varequal respident="response_d719aee0-6ce0-462c-9c0d-be63ba8d3408">d719aee0-6ce0-462c-9c0d-be63ba8d3408-0</varequal>
107
+ </conditionvar>
108
+ <setvar action="Add" varname="SCORE">20.00</setvar>
109
+ </respcondition>
110
+ <respcondition>
111
+ <conditionvar>
112
+ <varequal respident="response_ce47aaf6-c1dd-4db8-9a9f-7f5c131d8a90">ce47aaf6-c1dd-4db8-9a9f-7f5c131d8a90-0</varequal>
113
+ </conditionvar>
114
+ <setvar action="Add" varname="SCORE">20.00</setvar>
115
+ </respcondition>
116
+ <respcondition>
117
+ <conditionvar>
118
+ <varequal respident="response_41dfd716-8fd9-466a-97fa-33d353e44b42">41dfd716-8fd9-466a-97fa-33d353e44b42-0</varequal>
119
+ </conditionvar>
120
+ <setvar action="Add" varname="SCORE">20.00</setvar>
121
+ </respcondition>
122
+ <respcondition>
123
+ <conditionvar>
124
+ <varequal respident="response_a053119f-6a61-4372-ac79-4b2a7de0232f">a053119f-6a61-4372-ac79-4b2a7de0232f-0</varequal>
125
+ </conditionvar>
126
+ <setvar action="Add" varname="SCORE">20.00</setvar>
127
+ </respcondition>
128
+ </resprocessing>
129
+ </item>
130
+ </questestinterop>
@@ -5,8 +5,11 @@
5
5
  <lock_at>2020-07-10T04:59:59</lock_at>
6
6
  <unlock_at>2020-04-16T05:00:00</unlock_at>
7
7
  <due_at>2020-05-23T04:59:59</due_at>
8
+ <shuffle_questions>true</shuffle_questions>
8
9
  <shuffle_answers>true</shuffle_answers>
10
+ <calculator_type>basic</calculator_type>
9
11
  <scoring_policy>keep_average</scoring_policy>
12
+ <cooling_period_seconds>86400</cooling_period_seconds>
10
13
  <hide_results></hide_results>
11
14
  <quiz_type>assignment</quiz_type>
12
15
  <points_possible/>
@@ -28,8 +31,19 @@
28
31
  <available>false</available>
29
32
  <one_time_results>false</one_time_results>
30
33
  <show_correct_answers_last_attempt>false</show_correct_answers_last_attempt>
34
+ <allow_clear_mc_selection>true</allow_clear_mc_selection>
31
35
  <only_visible_to_overrides>false</only_visible_to_overrides>
32
36
  <module_locked>false</module_locked>
37
+ <nq_ip_filter from="255.255.0.0" to="255.255.0.255"/>
38
+ <nq_ip_filter from="255.255.0.255" to="255.255.255.255"/>
39
+ <result_view_restricted>true</result_view_restricted>
40
+ <display_items>true</display_items>
41
+ <display_item_feedback>true</display_item_feedback>
42
+ <display_item_response>true</display_item_response>
43
+ <display_points_awarded>true</display_points_awarded>
44
+ <display_points_possible>true</display_points_possible>
45
+ <display_item_correct_answer>true</display_item_correct_answer>
46
+ <display_item_response_correctness>true</display_item_response_correctness>
33
47
  <assignment identifier="g390e22b33353b658bb23abbce000414f">
34
48
  <title>asd09sdfaxsdflj</title>
35
49
  <due_at>2020-05-23T04:59:59</due_at>
@@ -20,7 +20,10 @@ context 'Canvas Assessment Meta Data' do
20
20
  expect(assessment.canvas_available?).to eq(available)
21
21
  expect(assessment.canvas_one_time_results?).to eq(one_time_results)
22
22
  expect(assessment.canvas_scoring_policy).to eq(scoring_policy)
23
+ expect(assessment.canvas_cooling_period_seconds).to eq(cooling_period_seconds)
24
+ expect(assessment.canvas_shuffle_questions?).to eq(shuffle_questions)
23
25
  expect(assessment.canvas_shuffle_answers?).to eq(shuffle_answers)
26
+ expect(assessment.canvas_calculator_type).to eq(calculator_type)
24
27
  expect(assessment.canvas_lock_at).to eq(lock_at)
25
28
  expect(assessment.canvas_unlock_at).to eq(unlock_at)
26
29
  expect(assessment.canvas_due_at).to eq(due_at)
@@ -30,10 +33,21 @@ context 'Canvas Assessment Meta Data' do
30
33
  expect(assessment.canvas_show_correct_answers?).to eq(show_answers)
31
34
  expect(assessment.canvas_show_correct_answers_at).to eq(show_answers_at)
32
35
  expect(assessment.canvas_hide_correct_answers_at).to eq(hide_answers_at)
36
+ expect(assessment.canvas_allow_clear_mc_selection?).to eq(allow_clear_mc_selection)
33
37
  expect(assessment.canvas_require_lockdown_browser?).to eq(rlb)
34
38
  expect(assessment.canvas_require_lockdown_browser_for_results?).to eq(rlb_results)
35
39
  expect(assessment.canvas_require_lockdown_browser_monitor?).to eq(rlb_monitor)
36
40
  expect(assessment.canvas_lockdown_browser_monitor_data).to eq(lb_monitor_data)
41
+ expect(assessment.canvas_nq_ip_filters_enabled?).to eq(nq_ip_filters_enabled)
42
+ expect(assessment.canvas_nq_ip_filters).to eq(nq_ip_filters)
43
+ expect(assessment.canvas_result_view_restricted?).to eq(result_view_restricted)
44
+ expect(assessment.canvas_display_items?).to eq(display_items)
45
+ expect(assessment.canvas_display_item_response?).to eq(display_item_response)
46
+ expect(assessment.canvas_display_item_feedback?).to eq(display_item_feedback)
47
+ expect(assessment.canvas_display_points_awarded?).to eq(display_points_awarded)
48
+ expect(assessment.canvas_display_points_possible?).to eq(display_points_possible)
49
+ expect(assessment.canvas_display_item_correct_answer?).to eq(display_item_correct_answer)
50
+ expect(assessment.canvas_display_item_response_correctness?).to eq(display_item_response_correctness)
37
51
  end
38
52
  end
39
53
 
@@ -53,7 +67,10 @@ context 'Canvas Assessment Meta Data' do
53
67
  let(:available) { true }
54
68
  let(:one_time_results) { false }
55
69
  let(:scoring_policy) { 'keep_highest' }
70
+ let(:cooling_period_seconds) { nil }
71
+ let(:shuffle_questions) { false }
56
72
  let(:shuffle_answers) { false }
73
+ let(:calculator_type) { nil }
57
74
  let(:lock_at) { nil }
58
75
  let(:unlock_at) { nil }
59
76
  let(:due_at) { nil }
@@ -63,10 +80,21 @@ context 'Canvas Assessment Meta Data' do
63
80
  let(:show_answers) { true }
64
81
  let(:show_answers_at) { nil }
65
82
  let(:hide_answers_at) { nil }
83
+ let(:allow_clear_mc_selection) { false }
66
84
  let(:rlb) { false }
67
85
  let(:rlb_results) { false }
68
86
  let(:rlb_monitor) { false }
69
87
  let(:lb_monitor_data) { nil }
88
+ let(:nq_ip_filters_enabled) { false }
89
+ let(:nq_ip_filters) { [] }
90
+ let(:result_view_restricted) { false }
91
+ let(:display_items) { false }
92
+ let(:display_item_response) { false }
93
+ let(:display_item_feedback) { false }
94
+ let(:display_points_awarded) { false }
95
+ let(:display_points_possible) { false }
96
+ let(:display_item_correct_answer) { false }
97
+ let(:display_item_response_correctness) { false }
70
98
 
71
99
  include_examples('loads canvas meta data')
72
100
  end
@@ -87,7 +115,10 @@ context 'Canvas Assessment Meta Data' do
87
115
  let(:available) { false }
88
116
  let(:one_time_results) { false }
89
117
  let(:scoring_policy) { 'keep_average' }
118
+ let(:shuffle_questions) { true }
119
+ let(:cooling_period_seconds) { 86_400 }
90
120
  let(:shuffle_answers) { true }
121
+ let(:calculator_type) { 'basic' }
91
122
  let(:lock_at) { '2020-07-10T04:59:59' }
92
123
  let(:unlock_at) { '2020-04-16T05:00:00' }
93
124
  let(:due_at) { '2020-05-23T04:59:59' }
@@ -97,10 +128,26 @@ context 'Canvas Assessment Meta Data' do
97
128
  let(:show_answers) { true }
98
129
  let(:show_answers_at) { '2041-02-17 06:00:00 UTC' }
99
130
  let(:hide_answers_at) { '2042-12-26 06:00:00 UTC' }
131
+ let(:allow_clear_mc_selection) { true }
100
132
  let(:rlb) { false }
101
133
  let(:rlb_results) { false }
102
134
  let(:rlb_monitor) { false }
103
135
  let(:lb_monitor_data) { nil }
136
+ let(:nq_ip_filters_enabled) { true }
137
+ let(:nq_ip_filters) do
138
+ [
139
+ ['255.255.0.0', '255.255.0.255'],
140
+ ['255.255.0.255', '255.255.255.255']
141
+ ]
142
+ end
143
+ let(:result_view_restricted) { true }
144
+ let(:display_items) { true }
145
+ let(:display_item_response) { true }
146
+ let(:display_item_feedback) { true }
147
+ let(:display_points_awarded) { true }
148
+ let(:display_points_possible) { true }
149
+ let(:display_item_correct_answer) { true }
150
+ let(:display_item_response_correctness) { true }
104
151
 
105
152
  include_examples('loads canvas meta data')
106
153
  end
@@ -121,7 +168,10 @@ context 'Canvas Assessment Meta Data' do
121
168
  let(:available) { false }
122
169
  let(:one_time_results) { false }
123
170
  let(:scoring_policy) { nil }
171
+ let(:cooling_period_seconds) { nil }
172
+ let(:shuffle_questions) { false }
124
173
  let(:shuffle_answers) { false }
174
+ let(:calculator_type) { nil }
125
175
  let(:lock_at) { nil }
126
176
  let(:unlock_at) { nil }
127
177
  let(:due_at) { nil }
@@ -131,10 +181,21 @@ context 'Canvas Assessment Meta Data' do
131
181
  let(:show_answers) { false }
132
182
  let(:show_answers_at) { nil }
133
183
  let(:hide_answers_at) { nil }
184
+ let(:allow_clear_mc_selection) { false }
134
185
  let(:rlb) { false }
135
186
  let(:rlb_results) { false }
136
187
  let(:rlb_monitor) { false }
137
188
  let(:lb_monitor_data) { nil }
189
+ let(:nq_ip_filters_enabled) { false }
190
+ let(:nq_ip_filters) { [] }
191
+ let(:result_view_restricted) { false }
192
+ let(:display_items) { false }
193
+ let(:display_item_response) { false }
194
+ let(:display_item_feedback) { false }
195
+ let(:display_points_awarded) { false }
196
+ let(:display_points_possible) { false }
197
+ let(:display_item_correct_answer) { false }
198
+ let(:display_item_response_correctness) { false }
138
199
 
139
200
  include_examples('loads canvas meta data')
140
201
  end
@@ -155,7 +216,10 @@ context 'Canvas Assessment Meta Data' do
155
216
  let(:available) { true }
156
217
  let(:one_time_results) { false }
157
218
  let(:scoring_policy) { 'keep_highest' }
219
+ let(:cooling_period_seconds) { nil }
220
+ let(:shuffle_questions) { false }
158
221
  let(:shuffle_answers) { false }
222
+ let(:calculator_type) { nil }
159
223
  let(:lock_at) { nil }
160
224
  let(:unlock_at) { nil }
161
225
  let(:due_at) { nil }
@@ -165,10 +229,21 @@ context 'Canvas Assessment Meta Data' do
165
229
  let(:show_answers) { true }
166
230
  let(:show_answers_at) { nil }
167
231
  let(:hide_answers_at) { nil }
232
+ let(:allow_clear_mc_selection) { false }
168
233
  let(:rlb) { false }
169
234
  let(:rlb_results) { false }
170
235
  let(:rlb_monitor) { false }
171
236
  let(:lb_monitor_data) { nil }
237
+ let(:nq_ip_filters_enabled) { false }
238
+ let(:nq_ip_filters) { [] }
239
+ let(:result_view_restricted) { false }
240
+ let(:display_items) { false }
241
+ let(:display_item_response) { false }
242
+ let(:display_item_feedback) { false }
243
+ let(:display_points_awarded) { false }
244
+ let(:display_points_possible) { false }
245
+ let(:display_item_correct_answer) { false }
246
+ let(:display_item_response_correctness) { false }
172
247
 
173
248
  include_examples('loads canvas meta data')
174
249
  end
@@ -18,4 +18,18 @@ describe Qti::V1::Models::Choices::FillBlankChoice do
18
18
  expect(choices.map(&:item_body).map { |text| text.include? 'correct' }.any?).to eq false
19
19
  end
20
20
  end
21
+
22
+ context 'nq_multiple_fib_answer_types.xml' do
23
+ let(:fixtures_path) { File.join('spec', 'fixtures') }
24
+ let(:file_path) { File.join(fixtures_path, 'items_1.2', 'nq_multiple_fib_answer_types.xml') }
25
+ let(:test_object) { Qti::V1::Models::Assessment.from_path!(file_path) }
26
+ let(:nodes) { test_object.assessment_items }
27
+ let(:dropdown_answer) { nodes.xpath('.//xmlns:response_lid').first }
28
+ let(:dropdown_choices) { dropdown_answer.xpath('.//xmlns:response_label') }
29
+ let(:choices) { dropdown_choices.map { |answer| described_class.new(answer, test_object) } }
30
+
31
+ it 'returns the right positions' do
32
+ expect(choices.map(&:position)).to eq %w[1 2 3]
33
+ end
34
+ end
21
35
  end
@@ -94,4 +94,20 @@ describe Qti::V1::Models::Interactions::BaseInteraction do
94
94
  include_examples('item_level_feedback')
95
95
  include_examples('answer_feedback')
96
96
  end
97
+
98
+ context 'nq_multiple_fib_scoring_algorithms.xml' do
99
+ let(:file_path) { File.join(fixtures_path, 'nq_multiple_fib_scoring_algorithms.xml') }
100
+
101
+ it 'correctly finds new quizzes QTI' do
102
+ expect(Qti::V1::Models::Interactions::BaseInteraction.new_quizzes_fib?(assessment_item_refs.first)).to eq true
103
+ end
104
+ end
105
+
106
+ context 'canvas_multiple_fib.xml' do
107
+ let(:file_path) { File.join(fixtures_path, 'canvas_multiple_fib.xml') }
108
+
109
+ it 'correctly does not find new quizzes QTI' do
110
+ expect(Qti::V1::Models::Interactions::BaseInteraction.new_quizzes_fib?(assessment_item_refs.first)).to eq false
111
+ end
112
+ end
97
113
  end
@@ -31,6 +31,14 @@ describe Qti::V1::Models::Interactions::FillBlankInteraction do
31
31
  end
32
32
  end
33
33
 
34
+ shared_examples_for 'nq_scoring_data_structs' do
35
+ it 'returns the scoring_data_structs' do
36
+ expect(loaded_class.scoring_data_structs.map(&:scoring_algorithm)).to eq(scoring_data_algorithm)
37
+ expect(loaded_class.scoring_data_structs.map(&:answer_type)).to eq(scoring_data_answer_type)
38
+ expect(loaded_class.scoring_data_structs.map(&:scoring_options)).to eq(scoring_data_options)
39
+ end
40
+ end
41
+
34
42
  shared_examples_for 'stem_items' do
35
43
  it 'returns the stem_items' do
36
44
  expect(loaded_class.stem_items).to eq(expected_stem_items)
@@ -151,6 +159,28 @@ describe Qti::V1::Models::Interactions::FillBlankInteraction do
151
159
  allow(subject).to receive(:answer_nodes).and_return([node])
152
160
  expect(subject.scoring_data_structs.first.case).to eq 'no'
153
161
  end
162
+
163
+ it "returns 'TextInChoices' as scoring algorithm default value" do
164
+ allow(nodexml).to receive(:at_xpath)
165
+ node = double(
166
+ parent: double(parent: double(attributes: 'a')),
167
+ content: 'content',
168
+ attributes: { respident: double(value: 'a') }
169
+ )
170
+ allow(subject).to receive(:answer_nodes).and_return([node])
171
+ expect(subject.scoring_data_structs.first.scoring_algorithm).to eq 'TextInChoices'
172
+ end
173
+
174
+ it "returns 'openEntry' as answer type default value" do
175
+ allow(nodexml).to receive(:at_xpath)
176
+ node = double(
177
+ parent: double(parent: double(attributes: 'a')),
178
+ content: 'content',
179
+ attributes: { respident: double(value: 'a') }
180
+ )
181
+ allow(subject).to receive(:answer_nodes).and_return([node])
182
+ expect(subject.scoring_data_structs.first.answer_type).to eq 'openEntry'
183
+ end
154
184
  end
155
185
 
156
186
  context 'fib_str.xml' do
@@ -192,22 +222,152 @@ describe Qti::V1::Models::Interactions::FillBlankInteraction do
192
222
  let(:scoring_data_case) { %w[no] }
193
223
  let(:answer_count) { 1 }
194
224
  let(:expected_blanks) { [{ id: '3537' }] }
195
- let(:expected_stem_items) do
196
- [
197
- { id: 'stem_0', position: 1, type: 'text', value: '<div><p>Bird, bird, bird, bird is the ' },
198
- { id: 'stem_1', position: 2, type: 'blank', blank_id: 'response_blank1', blank_name: 'word' },
199
- { id: 'stem_2', position: 3, type: 'text', value: '</p></div>' }
200
- ]
201
- end
202
225
 
203
226
  include_examples 'shuffled?'
204
227
  include_examples 'answers'
205
228
  include_examples 'scoring_data_structs'
206
229
  include_examples 'blanks'
207
- include_examples 'stem_items'
208
230
 
209
231
  it 'returns false for #single_fill_in_blank?' do
210
232
  expect(loaded_class.single_fill_in_blank?).to eq false
211
233
  end
212
234
  end
235
+
236
+ context 'new quizzes multiple fill in the blank questions with all possible scoring algorithms' do
237
+ let(:file_path) { File.join(fixtures_path, 'nq_multiple_fib_scoring_algorithms.xml') }
238
+ let(:scoring_data_ids) do
239
+ %w[bafd8242-ecdb-4158-a6d3-4ff15e016cb8-0
240
+ d719aee0-6ce0-462c-9c0d-be63ba8d3408-0
241
+ ce47aaf6-c1dd-4db8-9a9f-7f5c131d8a90-0
242
+ 41dfd716-8fd9-466a-97fa-33d353e44b42-0
243
+ 41dfd716-8fd9-466a-97fa-33d353e44b42-1
244
+ a053119f-6a61-4372-ac79-4b2a7de0232f-0]
245
+ end
246
+ let(:scoring_data_values) { %w[red blue green yellow teal ^orange$] }
247
+ let(:scoring_data_case) { %w[no no no no no no] }
248
+ let(:scoring_data_algorithm) do
249
+ %w[TextContainsAnswer TextCloseEnough TextEquivalence TextInChoices TextInChoices TextRegex]
250
+ end
251
+ let(:scoring_data_answer_type) { %w[openEntry openEntry openEntry openEntry openEntry openEntry] }
252
+ let(:scoring_data_options) { [{}, { 'ignore_case' => 'true', 'levenshtein_distance' => '1' }, {}, {}, {}, {}] }
253
+ let(:correct_answer_map) do
254
+ { 'response_bafd8242-ecdb-4158-a6d3-4ff15e016cb8' => 'bafd8242-ecdb-4158-a6d3-4ff15e016cb8-0',
255
+ 'response_d719aee0-6ce0-462c-9c0d-be63ba8d3408' => 'd719aee0-6ce0-462c-9c0d-be63ba8d3408-0',
256
+ 'response_ce47aaf6-c1dd-4db8-9a9f-7f5c131d8a90' => 'ce47aaf6-c1dd-4db8-9a9f-7f5c131d8a90-0',
257
+ 'response_41dfd716-8fd9-466a-97fa-33d353e44b42' => '41dfd716-8fd9-466a-97fa-33d353e44b42-0',
258
+ 'response_a053119f-6a61-4372-ac79-4b2a7de0232f' => 'a053119f-6a61-4372-ac79-4b2a7de0232f-0' }
259
+ end
260
+ let(:expected_stem_items) do
261
+ [{ id: 'stem_0', position: 1, type: 'text', value: '<p>Roses are ' },
262
+ { blank_id: 'response_bafd8242-ecdb-4158-a6d3-4ff15e016cb8',
263
+ blank_name: 'red',
264
+ id: 'stem_1',
265
+ position: 2,
266
+ type: 'blank' },
267
+ { id: 'stem_2', position: 3, type: 'text', value: ', ' },
268
+ { blank_id: 'response_d719aee0-6ce0-462c-9c0d-be63ba8d3408',
269
+ blank_name: 'blue',
270
+ id: 'stem_3',
271
+ position: 4,
272
+ type: 'blank' },
273
+ { id: 'stem_4', position: 5, type: 'text', value: ', ' },
274
+ { blank_id: 'response_ce47aaf6-c1dd-4db8-9a9f-7f5c131d8a90',
275
+ blank_name: 'green',
276
+ id: 'stem_5',
277
+ position: 6,
278
+ type: 'blank' },
279
+ { id: 'stem_6', position: 7, type: 'text', value: ', ' },
280
+ { blank_id: 'response_41dfd716-8fd9-466a-97fa-33d353e44b42',
281
+ blank_name: 'yellow',
282
+ id: 'stem_7',
283
+ position: 8,
284
+ type: 'blank' },
285
+ { id: 'stem_8', position: 9, type: 'text', value: ', and ' },
286
+ { blank_id: 'response_a053119f-6a61-4372-ac79-4b2a7de0232f',
287
+ blank_name: '^orange$',
288
+ id: 'stem_9',
289
+ position: 10,
290
+ type: 'blank' },
291
+ { id: 'stem_10', position: 11, type: 'text', value: '.</p>' }]
292
+ end
293
+
294
+ include_examples 'scoring_data_structs'
295
+ include_examples 'nq_scoring_data_structs'
296
+ include_examples 'stem_items'
297
+
298
+ it 'returns false for #wordbank_answer_present?' do
299
+ expect(loaded_class.wordbank_answer_present?).to eq false
300
+ end
301
+
302
+ it 'returns nil for #wordbank_allow_reuse?' do
303
+ expect(loaded_class.wordbank_allow_reuse?).to eq nil
304
+ end
305
+
306
+ it 'returns nil for #wordbank_choices' do
307
+ expect(loaded_class.wordbank_choices).to eq nil
308
+ end
309
+
310
+ it 'returns the answer map for #correct_answer_map' do
311
+ expect(loaded_class.correct_answer_map).to eq(correct_answer_map)
312
+ end
313
+ end
314
+
315
+ context 'new quizzes multiple fill in the blank questions with all possible answer types' do
316
+ let(:file_path) { File.join(fixtures_path, 'nq_multiple_fib_answer_types.xml') }
317
+ let(:scoring_data_ids) do
318
+ %w[378646a8-b823-4e5b-beb8-19ca63f1f355
319
+ c9400bfb-78ea-45b6-b5a5-e3311f6d5ed0
320
+ 76350749-49ac-4094-966b-c4e1f12d54bc
321
+ cd11d826-906d-4dc4-b78d-d66516eb94ce
322
+ bae0bd4f-2327-4a3e-b29f-199e1e279e91]
323
+ end
324
+ let(:scoring_data_values) { %w[black violet grey brown white] }
325
+ let(:scoring_data_case) { %w[no no no no no] }
326
+ let(:scoring_data_algorithm) { %w[Equivalence Equivalence Equivalence TextEquivalence TextEquivalence] }
327
+ let(:scoring_data_answer_type) { %w[dropdown dropdown dropdown wordbank wordbank] }
328
+ let(:scoring_data_options) { [{}, {}, {}, { 'allow_reuse' => 'true' }, { 'allow_reuse' => 'true' }] }
329
+ let(:wordbank_choices) do
330
+ [{ id: 'cd11d826-906d-4dc4-b78d-d66516eb94ce', item_body: 'brown' },
331
+ { id: 'bae0bd4f-2327-4a3e-b29f-199e1e279e91', item_body: 'white' }]
332
+ end
333
+ let(:correct_answer_map) do
334
+ { 'response_a20629ed-0c0b-4959-b565-a80c0f199602' => '378646a8-b823-4e5b-beb8-19ca63f1f355',
335
+ 'response_ab37a945-ebad-4787-a356-66c3c91efcc6' => 'bae0bd4f-2327-4a3e-b29f-199e1e279e91' }
336
+ end
337
+ let(:expected_stem_items) do
338
+ [{ id: 'stem_0', position: 1, type: 'text', value: '<p>Roses are ' },
339
+ { blank_id: 'response_a20629ed-0c0b-4959-b565-a80c0f199602',
340
+ blank_name: 'black',
341
+ id: 'stem_1',
342
+ position: 2,
343
+ type: 'blank' },
344
+ { id: 'stem_2', position: 3, type: 'text', value: ' and ' },
345
+ { blank_id: 'response_ab37a945-ebad-4787-a356-66c3c91efcc6',
346
+ blank_name: 'white',
347
+ id: 'stem_3',
348
+ position: 4,
349
+ type: 'blank' },
350
+ { id: 'stem_4', position: 5, type: 'text', value: '.</p>' }]
351
+ end
352
+
353
+ include_examples 'scoring_data_structs'
354
+ include_examples 'nq_scoring_data_structs'
355
+ include_examples 'stem_items'
356
+
357
+ it 'returns true for #wordbank_answer_present?' do
358
+ expect(loaded_class.wordbank_answer_present?).to eq true
359
+ end
360
+
361
+ it 'returns true for #wordbank_allow_reuse?' do
362
+ expect(loaded_class.wordbank_allow_reuse?).to eq true
363
+ end
364
+
365
+ it 'returns the correct choices for #wordbank_choices' do
366
+ expect(loaded_class.wordbank_choices).to eq(wordbank_choices)
367
+ end
368
+
369
+ it 'returns the answer map for #correct_answer_map' do
370
+ expect(loaded_class.correct_answer_map).to eq(correct_answer_map)
371
+ end
372
+ end
213
373
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: qti
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.19.0
4
+ version: 2.20.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adrian Diaz
@@ -12,7 +12,7 @@ authors:
12
12
  autorequire:
13
13
  bindir: bin
14
14
  cert_chain: []
15
- date: 2023-08-01 00:00:00.000000000 Z
15
+ date: 2023-08-11 00:00:00.000000000 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  name: actionview
@@ -417,6 +417,8 @@ files:
417
417
  - spec/fixtures/items_1.2/multiple_answer.xml
418
418
  - spec/fixtures/items_1.2/multiple_answer_canvas.xml
419
419
  - spec/fixtures/items_1.2/multiple_choice.xml
420
+ - spec/fixtures/items_1.2/nq_multiple_fib_answer_types.xml
421
+ - spec/fixtures/items_1.2/nq_multiple_fib_scoring_algorithms.xml
420
422
  - spec/fixtures/items_1.2/numeric_exact_match.xml
421
423
  - spec/fixtures/items_1.2/numeric_margin_error.xml
422
424
  - spec/fixtures/items_1.2/numeric_margin_error_percent.xml
@@ -789,6 +791,8 @@ test_files:
789
791
  - spec/fixtures/items_1.2/multiple_answer.xml
790
792
  - spec/fixtures/items_1.2/multiple_answer_canvas.xml
791
793
  - spec/fixtures/items_1.2/multiple_choice.xml
794
+ - spec/fixtures/items_1.2/nq_multiple_fib_answer_types.xml
795
+ - spec/fixtures/items_1.2/nq_multiple_fib_scoring_algorithms.xml
792
796
  - spec/fixtures/items_1.2/numeric_exact_match.xml
793
797
  - spec/fixtures/items_1.2/numeric_margin_error.xml
794
798
  - spec/fixtures/items_1.2/numeric_margin_error_percent.xml