qti 2.18.1 → 2.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/qti/v1/models/assessment.rb +12 -2
- data/lib/qti/v1/models/bank_entry_item.rb +27 -0
- data/lib/qti/v1/models/choices/fill_blank_choice.rb +4 -0
- data/lib/qti/v1/models/interactions/base_fill_blank_interaction.rb +38 -1
- data/lib/qti/v1/models/interactions/base_interaction.rb +9 -0
- data/lib/qti/v1/models/interactions/fill_blank_interaction.rb +83 -2
- data/lib/qti/v1/models/scoring_data.rb +4 -2
- data/lib/qti/v2/models/assessment_test.rb +4 -0
- data/lib/qti/version.rb +1 -1
- data/lib/qti.rb +5 -0
- data/spec/fixtures/items_1.2/bank_entry_item.xml +14 -0
- data/spec/fixtures/items_1.2/nq_multiple_fib_answer_types.xml +86 -0
- data/spec/fixtures/items_1.2/nq_multiple_fib_scoring_algorithms.xml +130 -0
- data/spec/fixtures/test_qti_1.2_bank_entry_item/imsmanifest.xml +33 -0
- data/spec/fixtures/test_qti_1.2_bank_entry_item/quiz.xml +14 -0
- data/spec/lib/qti/v1/models/assessment_spec.rb +18 -0
- data/spec/lib/qti/v1/models/bank_entry_item_spec.rb +12 -0
- data/spec/lib/qti/v1/models/choices/fill_blank_choice_spec.rb +14 -0
- data/spec/lib/qti/v1/models/interactions/base_interaction_spec.rb +16 -0
- data/spec/lib/qti/v1/models/interactions/fill_blank_interaction_spec.rb +168 -8
- data/spec/lib/qti/v2/models/assessment_test_spec.rb +9 -0
- data/spec/lib/qti_spec.rb +20 -0
- metadata +15 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1c38dab5972627b0bf1cd428bbde63a1363365dab50bf9726f6b8a35aa605a2a
|
|
4
|
+
data.tar.gz: b00506a2ada125d8853f42fa7b5d328b68985b9821be063e5371a03f6333ef90
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a4ac85f4bc7890cf07b860b9ddeb8874298dd18fc04136c3d5432e4e0513ec07fb15971657f08ef120ffa8040c1361d3d48b4a45457cb261415a8e1ce9b49358
|
|
7
|
+
data.tar.gz: b2445a01bdeb590a23f2f89a050215b4b19290a436d2d49ea14d1d52a2c6be2b9c486e85095d68153ca5ace4c894d3a1524276d0189ccaae4024fffe2a1c5dad
|
|
@@ -15,11 +15,12 @@ module Qti
|
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
def assessment_items
|
|
18
|
-
@doc.xpath("//*[self::#{GROUP_ID} or self::xmlns:item[not(ancestor::#{GROUP_ID})]
|
|
18
|
+
@doc.xpath("//*[self::#{GROUP_ID} or self::xmlns:item[not(ancestor::#{GROUP_ID})]" \
|
|
19
|
+
' or self::xmlns:bankentry_item]')
|
|
19
20
|
end
|
|
20
21
|
|
|
21
22
|
def create_assessment_item(assessment_item)
|
|
22
|
-
return nil if sub_section?(assessment_item)
|
|
23
|
+
return nil if sub_section?(assessment_item) || bank_entry_item?(assessment_item)
|
|
23
24
|
item = Qti::V1::Models::AssessmentItem.new(assessment_item, @package_root, self)
|
|
24
25
|
item.manifest = manifest
|
|
25
26
|
item
|
|
@@ -39,6 +40,11 @@ module Qti
|
|
|
39
40
|
Qti::V1::Models::QuestionGroup.new(group_ref)
|
|
40
41
|
end
|
|
41
42
|
|
|
43
|
+
def create_bank_entry_item(bank_entry_item_ref)
|
|
44
|
+
return nil unless bank_entry_item?(bank_entry_item_ref)
|
|
45
|
+
Qti::V1::Models::BankEntryItem.new(bank_entry_item_ref)
|
|
46
|
+
end
|
|
47
|
+
|
|
42
48
|
private
|
|
43
49
|
|
|
44
50
|
def sub_section?(ref_node)
|
|
@@ -57,6 +63,10 @@ module Qti
|
|
|
57
63
|
def question_group?(ref_node)
|
|
58
64
|
ref_node.xpath("self::#{GROUP_ID}").present?
|
|
59
65
|
end
|
|
66
|
+
|
|
67
|
+
def bank_entry_item?(ref_node)
|
|
68
|
+
ref_node.xpath('self::xmlns:bankentry_item').present?
|
|
69
|
+
end
|
|
60
70
|
end
|
|
61
71
|
end
|
|
62
72
|
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module Qti
|
|
2
|
+
module V1
|
|
3
|
+
module Models
|
|
4
|
+
class BankEntryItem < Qti::V1::Models::Base
|
|
5
|
+
attr_reader :doc
|
|
6
|
+
|
|
7
|
+
def initialize(item, package_root = nil)
|
|
8
|
+
@doc = item
|
|
9
|
+
@path = item.document.url
|
|
10
|
+
self.package_root = package_root
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def sourcebank_ref
|
|
14
|
+
@sourcebank_ref ||= @doc.attribute('sourcebank_ref').value
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def item_ref
|
|
18
|
+
@item_ref ||= @doc.attribute('item_ref').value
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def points_possible
|
|
22
|
+
@points_possible ||= @doc.attribute('points_possible')&.value || 1
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -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
|
|
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
|
|
@@ -44,6 +44,10 @@ module Qti
|
|
|
44
44
|
def create_stimulus(stimulus_ref)
|
|
45
45
|
Qti::V2::Models::StimulusItem.new(path: stimulus_ref, package_root: @package_root, html: true)
|
|
46
46
|
end
|
|
47
|
+
|
|
48
|
+
def create_bank_entry_item(_bank_entry_item_ref = nil)
|
|
49
|
+
nil # we don't support this in QTI V2 yet
|
|
50
|
+
end
|
|
47
51
|
end
|
|
48
52
|
end
|
|
49
53
|
end
|
data/lib/qti/version.rb
CHANGED
data/lib/qti.rb
CHANGED
|
@@ -68,6 +68,10 @@ module Qti
|
|
|
68
68
|
def create_question_group(question_group_ref)
|
|
69
69
|
@import.create_question_group(question_group_ref)
|
|
70
70
|
end
|
|
71
|
+
|
|
72
|
+
def create_bank_entry_item(bank_entry_item_ref)
|
|
73
|
+
@import.create_bank_entry_item(bank_entry_item_ref)
|
|
74
|
+
end
|
|
71
75
|
end
|
|
72
76
|
|
|
73
77
|
def self.configuration
|
|
@@ -117,6 +121,7 @@ require 'qti/v1/models/object_bank'
|
|
|
117
121
|
require 'qti/v1/models/scoring_data'
|
|
118
122
|
require 'qti/v1/models/stimulus_item'
|
|
119
123
|
require 'qti/v1/models/question_group'
|
|
124
|
+
require 'qti/v1/models/bank_entry_item'
|
|
120
125
|
|
|
121
126
|
require 'qti/v1/models/interactions/base_interaction'
|
|
122
127
|
require 'qti/v1/models/interactions/choice_interaction'
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
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
|
+
<assessment ident="i1211c711ada74a559ee1b6c122358695" title="Question Groups">
|
|
4
|
+
<qtimetadata>
|
|
5
|
+
<qtimetadatafield>
|
|
6
|
+
<fieldlabel>cc_maxattempts</fieldlabel>
|
|
7
|
+
<fieldentry>1</fieldentry>
|
|
8
|
+
</qtimetadatafield>
|
|
9
|
+
</qtimetadata>
|
|
10
|
+
<section ident="root_section">
|
|
11
|
+
<bankentry_item sourcebank_ref="gafe7177102bee6759845b002c5de397c" item_ref="ge0c6b84f2afc834ec575718bbc1451ff" points_possible="2.0"/>
|
|
12
|
+
</section>
|
|
13
|
+
</assessment>
|
|
14
|
+
</questestinterop>
|
|
@@ -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"><p>Roses are [a20629ed-0c0b-4959-b565-a80c0f199602] and [ab37a945-ebad-4787-a356-66c3c91efcc6].</p></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"><p>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].</p></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>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<?xml version="1.0"?>
|
|
2
|
+
<manifest identifier="MANIFEST1" xmlns="http://www.imsglobal.org/xsd/imscp_v1p1"
|
|
3
|
+
xmlns:imsmd="http://www.imsglobal.org/xsd/imsmd_v1p2"
|
|
4
|
+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
5
|
+
xsi:schemaLocation="http://www.imsglobal.org/xsd/imscp_v1p1 imscp_v1p1.xsd http://www.imsglobal.org/xsd/imsmd_v1p2 imsmd_v1p2p2.xsd">
|
|
6
|
+
<metadata>
|
|
7
|
+
<schema>IMS Content</schema>
|
|
8
|
+
<schemaversion>1.1.3</schemaversion>
|
|
9
|
+
<imsmd:lom>
|
|
10
|
+
<imsmd:general>
|
|
11
|
+
<imsmd:identifier>
|
|
12
|
+
<imsmd:langstring xml:lang="en-US">52BF3E44F087427782489C7DC5CAB5FD</imsmd:langstring>
|
|
13
|
+
</imsmd:identifier>
|
|
14
|
+
<imsmd:title>
|
|
15
|
+
<imsmd:langstring xml:lang="en-US">Quiz (Respondus IMS QTI export)</imsmd:langstring>
|
|
16
|
+
</imsmd:title>
|
|
17
|
+
</imsmd:general>
|
|
18
|
+
</imsmd:lom>
|
|
19
|
+
</metadata>
|
|
20
|
+
<organizations default="EXAM1">
|
|
21
|
+
<organization identifier="EXAM1" structure="hierarchical">
|
|
22
|
+
<title>default</title>
|
|
23
|
+
<item identifier="ITEM1" identifierref="RESOURCE1">
|
|
24
|
+
<title>Exam 1</title>
|
|
25
|
+
</item>
|
|
26
|
+
</organization>
|
|
27
|
+
</organizations>
|
|
28
|
+
<resources>
|
|
29
|
+
<resource identifier="RESOURCE1" type="imsqti_xmlv1p2" href="quiz.xml">
|
|
30
|
+
<file href="quiz.xml" />
|
|
31
|
+
</resource>
|
|
32
|
+
</resources>
|
|
33
|
+
</manifest>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
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
|
+
<assessment ident="i1211c711ada74a559ee1b6c122358695" title="Question Groups">
|
|
4
|
+
<qtimetadata>
|
|
5
|
+
<qtimetadatafield>
|
|
6
|
+
<fieldlabel>cc_maxattempts</fieldlabel>
|
|
7
|
+
<fieldentry>1</fieldentry>
|
|
8
|
+
</qtimetadatafield>
|
|
9
|
+
</qtimetadata>
|
|
10
|
+
<section ident="root_section">
|
|
11
|
+
<bankentry_item sourcebank_ref="gafe7177102bee6759845b002c5de397c" item_ref="ge0c6b84f2afc834ec575718bbc1451ff"/>
|
|
12
|
+
</section>
|
|
13
|
+
</assessment>
|
|
14
|
+
</questestinterop>
|
|
@@ -228,4 +228,22 @@ describe Qti::V1::Models::Assessment do
|
|
|
228
228
|
).to eq(nil)
|
|
229
229
|
end
|
|
230
230
|
end
|
|
231
|
+
|
|
232
|
+
describe 'bank entry items' do
|
|
233
|
+
let(:fixtures_path) { File.join('spec', 'fixtures', 'items_1.2') }
|
|
234
|
+
let(:test_object) { described_class.from_path!(file_path) }
|
|
235
|
+
let(:assessment_item_refs) { test_object.assessment_items }
|
|
236
|
+
let(:assessment_item_ref) { assessment_item_refs.first }
|
|
237
|
+
let(:file_path) { File.join(fixtures_path, 'bank_entry_item.xml') }
|
|
238
|
+
|
|
239
|
+
it 'methods behave as expected' do
|
|
240
|
+
expect(
|
|
241
|
+
test_object.create_assessment_item(assessment_item_ref)
|
|
242
|
+
).to eq(nil)
|
|
243
|
+
|
|
244
|
+
expect(
|
|
245
|
+
test_object.create_bank_entry_item(assessment_item_ref).class
|
|
246
|
+
).to be(Qti::V1::Models::BankEntryItem)
|
|
247
|
+
end
|
|
248
|
+
end
|
|
231
249
|
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
describe Qti::V1::Models::BankEntryItem do
|
|
2
|
+
let(:file_path) { File.join('spec', 'fixtures', 'items_1.2', 'bank_entry_item.xml') }
|
|
3
|
+
let(:test_object) { Qti::V1::Models::Assessment.from_path!(file_path) }
|
|
4
|
+
let(:assessment_item_refs) { test_object.assessment_items }
|
|
5
|
+
let(:loaded_class) { described_class.new(assessment_item_refs.first) }
|
|
6
|
+
|
|
7
|
+
it 'configures a bank entry item correctly' do
|
|
8
|
+
expect(loaded_class.sourcebank_ref).to eq('gafe7177102bee6759845b002c5de397c')
|
|
9
|
+
expect(loaded_class.item_ref).to eq('ge0c6b84f2afc834ec575718bbc1451ff')
|
|
10
|
+
expect(loaded_class.points_possible).to eq('2.0')
|
|
11
|
+
end
|
|
12
|
+
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
|
|
@@ -77,6 +77,15 @@ describe Qti::V2::Models::AssessmentTest do
|
|
|
77
77
|
end
|
|
78
78
|
end
|
|
79
79
|
|
|
80
|
+
describe '#create_bank_entry_item' do
|
|
81
|
+
let(:path) { File.join(fixtures_path, 'test_qti_2.2', 'assessment.xml') }
|
|
82
|
+
let(:loaded_class) { described_class.from_path!(path) }
|
|
83
|
+
|
|
84
|
+
it 'returns nil' do
|
|
85
|
+
expect(loaded_class.create_bank_entry_item).to be_nil
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
80
89
|
# describe '#create_stimulus' do
|
|
81
90
|
# let(:path) { File.join(fixtures_path, 'test_qti_2.2', 'assessment.xml') }
|
|
82
91
|
# let(:loaded_class) { described_class.from_path!(path) }
|
data/spec/lib/qti_spec.rb
CHANGED
|
@@ -45,6 +45,26 @@ describe Qti::Importer do
|
|
|
45
45
|
end
|
|
46
46
|
end
|
|
47
47
|
|
|
48
|
+
describe '#create_bank_entry_item' do
|
|
49
|
+
context 'when given a bankentry_item object' do
|
|
50
|
+
let(:file_path) { File.join(fixtures_path, 'test_qti_1.2_bank_entry_item') }
|
|
51
|
+
|
|
52
|
+
it 'creates a bankentry_item instance' do
|
|
53
|
+
bank_entry_item = importer.create_bank_entry_item(importer.assessment_item_refs.first)
|
|
54
|
+
expect(bank_entry_item).to be_a(Qti::V1::Models::BankEntryItem)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
context 'when given an object other than a bankentry_item' do
|
|
59
|
+
let(:file_path) { File.join(fixtures_path, 'test_qti_1.2') }
|
|
60
|
+
|
|
61
|
+
it 'returns nil' do
|
|
62
|
+
bank_entry_item = importer.create_bank_entry_item(importer.assessment_item_refs.first)
|
|
63
|
+
expect(bank_entry_item).to be_nil
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
48
68
|
context 'canvas generated' do
|
|
49
69
|
let(:file_path) { File.join(fixtures_path, 'test_qti_1.2_canvas') }
|
|
50
70
|
|
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.
|
|
4
|
+
version: 2.20.0
|
|
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-
|
|
15
|
+
date: 2023-08-03 00:00:00.000000000 Z
|
|
16
16
|
dependencies:
|
|
17
17
|
- !ruby/object:Gem::Dependency
|
|
18
18
|
name: actionview
|
|
@@ -325,6 +325,7 @@ files:
|
|
|
325
325
|
- lib/qti/sanitizer.rb
|
|
326
326
|
- lib/qti/v1/models/assessment.rb
|
|
327
327
|
- lib/qti/v1/models/assessment_item.rb
|
|
328
|
+
- lib/qti/v1/models/bank_entry_item.rb
|
|
328
329
|
- lib/qti/v1/models/base.rb
|
|
329
330
|
- lib/qti/v1/models/choices/fill_blank_choice.rb
|
|
330
331
|
- lib/qti/v1/models/choices/logical_identifier_choice.rb
|
|
@@ -392,6 +393,7 @@ files:
|
|
|
392
393
|
- spec/fixtures/imsqti_2.2_package/images/sign.png
|
|
393
394
|
- spec/fixtures/imsqti_2.2_package/imsmanifest.xml
|
|
394
395
|
- spec/fixtures/interaction_checks_1.2.xml
|
|
396
|
+
- spec/fixtures/items_1.2/bank_entry_item.xml
|
|
395
397
|
- spec/fixtures/items_1.2/canvas_multiple_dropdown.xml
|
|
396
398
|
- spec/fixtures/items_1.2/canvas_multiple_dropdowns.xml
|
|
397
399
|
- spec/fixtures/items_1.2/canvas_multiple_dropdowns_correct_choice_is_not_the_first.xml
|
|
@@ -415,6 +417,8 @@ files:
|
|
|
415
417
|
- spec/fixtures/items_1.2/multiple_answer.xml
|
|
416
418
|
- spec/fixtures/items_1.2/multiple_answer_canvas.xml
|
|
417
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
|
|
418
422
|
- spec/fixtures/items_1.2/numeric_exact_match.xml
|
|
419
423
|
- spec/fixtures/items_1.2/numeric_margin_error.xml
|
|
420
424
|
- spec/fixtures/items_1.2/numeric_margin_error_percent.xml
|
|
@@ -558,6 +562,8 @@ files:
|
|
|
558
562
|
- spec/fixtures/test_qti_1.2/image.png
|
|
559
563
|
- spec/fixtures/test_qti_1.2/imsmanifest.xml
|
|
560
564
|
- spec/fixtures/test_qti_1.2/quiz.xml
|
|
565
|
+
- spec/fixtures/test_qti_1.2_bank_entry_item/imsmanifest.xml
|
|
566
|
+
- spec/fixtures/test_qti_1.2_bank_entry_item/quiz.xml
|
|
561
567
|
- spec/fixtures/test_qti_1.2_canvas/i00f37ac2af026e5a607e4a5d9a70b1d5/assessment_meta.xml
|
|
562
568
|
- spec/fixtures/test_qti_1.2_canvas/i00f37ac2af026e5a607e4a5d9a70b1d5/i00f37ac2af026e5a607e4a5d9a70b1d5.xml
|
|
563
569
|
- spec/fixtures/test_qti_1.2_canvas/imsmanifest.xml
|
|
@@ -675,6 +681,7 @@ files:
|
|
|
675
681
|
- spec/lib/qti/sanitizer_spec.rb
|
|
676
682
|
- spec/lib/qti/v1/models/assessment_item_spec.rb
|
|
677
683
|
- spec/lib/qti/v1/models/assessment_spec.rb
|
|
684
|
+
- spec/lib/qti/v1/models/bank_entry_item_spec.rb
|
|
678
685
|
- spec/lib/qti/v1/models/base_spec.rb
|
|
679
686
|
- spec/lib/qti/v1/models/choices/fill_blank_choice_spec.rb
|
|
680
687
|
- spec/lib/qti/v1/models/choices/logical_identifier_choice_spec.rb
|
|
@@ -760,6 +767,7 @@ test_files:
|
|
|
760
767
|
- spec/fixtures/imsqti_2.2_package/images/sign.png
|
|
761
768
|
- spec/fixtures/imsqti_2.2_package/imsmanifest.xml
|
|
762
769
|
- spec/fixtures/interaction_checks_1.2.xml
|
|
770
|
+
- spec/fixtures/items_1.2/bank_entry_item.xml
|
|
763
771
|
- spec/fixtures/items_1.2/canvas_multiple_dropdown.xml
|
|
764
772
|
- spec/fixtures/items_1.2/canvas_multiple_dropdowns.xml
|
|
765
773
|
- spec/fixtures/items_1.2/canvas_multiple_dropdowns_correct_choice_is_not_the_first.xml
|
|
@@ -783,6 +791,8 @@ test_files:
|
|
|
783
791
|
- spec/fixtures/items_1.2/multiple_answer.xml
|
|
784
792
|
- spec/fixtures/items_1.2/multiple_answer_canvas.xml
|
|
785
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
|
|
786
796
|
- spec/fixtures/items_1.2/numeric_exact_match.xml
|
|
787
797
|
- spec/fixtures/items_1.2/numeric_margin_error.xml
|
|
788
798
|
- spec/fixtures/items_1.2/numeric_margin_error_percent.xml
|
|
@@ -926,6 +936,8 @@ test_files:
|
|
|
926
936
|
- spec/fixtures/test_qti_1.2/image.png
|
|
927
937
|
- spec/fixtures/test_qti_1.2/imsmanifest.xml
|
|
928
938
|
- spec/fixtures/test_qti_1.2/quiz.xml
|
|
939
|
+
- spec/fixtures/test_qti_1.2_bank_entry_item/imsmanifest.xml
|
|
940
|
+
- spec/fixtures/test_qti_1.2_bank_entry_item/quiz.xml
|
|
929
941
|
- spec/fixtures/test_qti_1.2_canvas/i00f37ac2af026e5a607e4a5d9a70b1d5/assessment_meta.xml
|
|
930
942
|
- spec/fixtures/test_qti_1.2_canvas/i00f37ac2af026e5a607e4a5d9a70b1d5/i00f37ac2af026e5a607e4a5d9a70b1d5.xml
|
|
931
943
|
- spec/fixtures/test_qti_1.2_canvas/imsmanifest.xml
|
|
@@ -1043,6 +1055,7 @@ test_files:
|
|
|
1043
1055
|
- spec/lib/qti/sanitizer_spec.rb
|
|
1044
1056
|
- spec/lib/qti/v1/models/assessment_item_spec.rb
|
|
1045
1057
|
- spec/lib/qti/v1/models/assessment_spec.rb
|
|
1058
|
+
- spec/lib/qti/v1/models/bank_entry_item_spec.rb
|
|
1046
1059
|
- spec/lib/qti/v1/models/base_spec.rb
|
|
1047
1060
|
- spec/lib/qti/v1/models/choices/fill_blank_choice_spec.rb
|
|
1048
1061
|
- spec/lib/qti/v1/models/choices/logical_identifier_choice_spec.rb
|