qti 0.9.14 → 0.9.15

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.
Files changed (28) hide show
  1. checksums.yaml +4 -4
  2. data/lib/qti.rb +1 -0
  3. data/lib/qti/v1/models/assessment.rb +17 -4
  4. data/lib/qti/v1/models/assessment_item.rb +4 -0
  5. data/lib/qti/v1/models/base.rb +2 -1
  6. data/lib/qti/v1/models/interactions/base_interaction.rb +36 -0
  7. data/lib/qti/v1/models/interactions/choice_interaction.rb +9 -0
  8. data/lib/qti/v1/models/interactions/fill_blank_interaction.rb +1 -1
  9. data/lib/qti/v1/models/interactions/match_interaction.rb +3 -1
  10. data/lib/qti/v1/models/interactions/numeric_interaction.rb +1 -1
  11. data/lib/qti/v1/models/interactions/string_interaction.rb +3 -2
  12. data/lib/qti/v1/models/stimulus_item.rb +33 -0
  13. data/lib/qti/v2/models/assessment_item.rb +4 -0
  14. data/lib/qti/v2/models/interactions/base_interaction.rb +2 -0
  15. data/lib/qti/version.rb +1 -1
  16. data/spec/fixtures/all_canvas_simple_1.2.xml +1676 -0
  17. data/spec/fixtures/feedback_quiz_1.2.xml +246 -0
  18. data/spec/fixtures/items_1.2/matching_feedback.xml +370 -0
  19. data/spec/fixtures/items_1.2/text.no.question.xml +36 -0
  20. data/spec/lib/qti/v1/models/assessment_item_spec.rb +11 -0
  21. data/spec/lib/qti/v1/models/assessment_spec.rb +187 -13
  22. data/spec/lib/qti/v1/models/interactions/base_interaction_spec.rb +56 -0
  23. data/spec/lib/qti/v1/models/interactions/choice_interaction_spec.rb +12 -0
  24. data/spec/lib/qti/v1/models/interactions/match_interaction_spec.rb +67 -23
  25. data/spec/lib/qti/v1/models/stimulus_item_spec.rb +55 -0
  26. metadata +14 -7
  27. data/spec/gemfiles/nokogiri-1.6.gemfile.lock +0 -149
  28. data/spec/gemfiles/nokogiri-1.8.gemfile.lock +0 -149
@@ -0,0 +1,36 @@
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="i1e5e0d6fba7342e63ae43afe9863070d" title="Quiz.text">
4
+ <qtimetadata>
5
+ <qtimetadatafield>
6
+ <fieldlabel>cc_maxattempts</fieldlabel>
7
+ <fieldentry>1</fieldentry>
8
+ </qtimetadatafield>
9
+ </qtimetadata>
10
+ <section ident="root_section">
11
+ <item ident="i5dae6b32741a0962b55c06c35e49ffb8" title="Question1">
12
+ <itemmetadata>
13
+ <qtimetadata>
14
+ <qtimetadatafield>
15
+ <fieldlabel>question_type</fieldlabel>
16
+ <fieldentry>text_only_question</fieldentry>
17
+ </qtimetadatafield>
18
+ <qtimetadatafield>
19
+ <fieldlabel>points_possible</fieldlabel>
20
+ <fieldentry>0</fieldentry>
21
+ </qtimetadatafield>
22
+ <qtimetadatafield>
23
+ <fieldlabel>assessment_question_identifierref</fieldlabel>
24
+ <fieldentry>if47b767d37e06559ff801f2d253307ba</fieldentry>
25
+ </qtimetadatafield>
26
+ </qtimetadata>
27
+ </itemmetadata>
28
+ <presentation>
29
+ <material>
30
+ <mattext texttype="text/html">&lt;div&gt;&lt;p&gt;asfdfas asdvadsfsdav a . aasvdafa&lt;/p&gt;&lt;/div&gt;</mattext>
31
+ </material>
32
+ </presentation>
33
+ </item>
34
+ </section>
35
+ </assessment>
36
+ </questestinterop>
@@ -1,6 +1,17 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Qti::V1::Models::AssessmentItem do
4
+ context 'multiple_fib' do
5
+ let(:file_path) { File.join('spec', 'fixtures', 'items_1.2', 'single_fib.xml') }
6
+ let(:test_object) { Qti::V1::Models::Assessment.from_path!(file_path) }
7
+ let(:assessment_item_refs) { test_object.assessment_items }
8
+ let(:loaded_class) { described_class.new(assessment_item_refs) }
9
+
10
+ it 'has sanitized item_body' do
11
+ expect(loaded_class.item_body).to include '<div'
12
+ end
13
+ end
14
+
4
15
  context 'quiz.xml' do
5
16
  let(:file_path) { File.join('spec', 'fixtures', 'items_1.2', 'true_false.xml') }
6
17
  let(:test_object) { Qti::V1::Models::Assessment.from_path!(file_path) }
@@ -1,22 +1,174 @@
1
1
  require 'spec_helper'
2
2
 
3
- fixtures_path = File.join('spec', 'fixtures')
4
-
5
3
  describe Qti::V1::Models::Assessment do
6
- let(:path) { File.join(fixtures_path, 'test_qti_1.2', 'quiz.xml') }
7
- let(:loaded_class) { described_class.from_path!(path) }
4
+ describe 'interaction item' do
5
+ let(:fixtures_path) { File.join('spec', 'fixtures') }
6
+ shared_examples_for 'basic quiz fields' do
7
+ it 'loads an AssessmentXML file' do
8
+ expect do
9
+ described_class.from_path!(path)
10
+ end.to_not raise_error
11
+ end
8
12
 
9
- it 'loads an AssessmentXML file' do
10
- expect do
11
- described_class.from_path!(path)
12
- end.to_not raise_error
13
- end
13
+ describe '#title' do
14
+ context '<assessmentTest> has a title property' do
15
+ it 'has the title' do
16
+ expect(loaded_class.title).to eq(expected_title)
17
+ end
18
+ end
19
+
20
+ it 'it has the correct number of items' do
21
+ expect(loaded_class.assessment_items.count).to eq(expected_item_count)
22
+ end
23
+ end
24
+ end
25
+
26
+ shared_examples_for 'verify quiz items' do
27
+ it 'has the correct item data' do
28
+ loaded_class.assessment_items.zip(expected_item_data).each do |node, cmp|
29
+ item = Qti::V1::Models::AssessmentItem.new(node)
30
+ expect(item.interaction_model.class).to eq(cmp[:interaction_class])
31
+ expect(item.title).to eq(cmp[:title])
32
+ expect(item.feedback).to eq(cmp[:feedback])
33
+ end
34
+ end
35
+ end
36
+
37
+ context 'reference qti 1.2 quiz' do
38
+ let(:path) { File.join(fixtures_path, 'test_qti_1.2', 'quiz.xml') }
39
+ let(:loaded_class) { described_class.from_path!(path) }
40
+ let(:expected_title) { '1.2 Import Quiz' }
41
+ let(:expected_item_count) { 5 }
42
+
43
+ include_examples('basic quiz fields')
44
+ end
45
+
46
+ context 'quiz with feedback samples' do
47
+ let(:path) { File.join(fixtures_path, 'feedback_quiz_1.2.xml') }
48
+ let(:loaded_class) { described_class.from_path!(path) }
49
+ let(:expected_title) { 'I Can Haz Feedback' }
50
+ let(:expected_item_count) { 3 }
51
+ let(:expected_item_data) do
52
+ [
53
+ {
54
+ interaction_class: Qti::V1::Models::Interactions::StringInteraction,
55
+ title: 'Question',
56
+ feedback: { neutral: '<p>General Feedback</p>' }
57
+ },
58
+ {
59
+ interaction_class: Qti::V1::Models::Interactions::ChoiceInteraction,
60
+ title: 'Question',
61
+ feedback: { neutral: '<p>Neutral</p>', correct: '<p>Correct</p>', incorrect: '<p>Incorrect</p>' }
62
+ },
63
+ {
64
+ interaction_class: Qti::V1::Models::Interactions::ChoiceInteraction,
65
+ title: 'Question',
66
+ feedback: {}
67
+ }
68
+ ]
69
+ end
14
70
 
15
- describe '#title' do
16
- context '<assessmentTest> has a title property' do
17
- it 'has the title' do
18
- expect(loaded_class.title).to eq '1.2 Import Quiz'
71
+ include_examples('basic quiz fields')
72
+ include_examples('verify quiz items')
73
+ end
74
+
75
+ context 'reference qti 1.2 quiz' do
76
+ let(:path) { File.join(fixtures_path, 'all_canvas_simple_1.2.xml') }
77
+ let(:loaded_class) { described_class.from_path!(path) }
78
+ let(:expected_title) { 'Every Canvas Interaction' }
79
+ let(:expected_item_count) { 10 }
80
+ let(:expected_item_data) do
81
+ [
82
+ {
83
+ interaction_class: Qti::V1::Models::Interactions::ChoiceInteraction,
84
+ title: 'Question 1',
85
+ feedback: {
86
+ neutral: '<p>Item Feedback, Neutral</p>',
87
+ correct: '<p>Item Feedback, Correct</p>',
88
+ incorrect: '<p>Item Feedback, Incorrect</p>'
89
+ }
90
+ },
91
+ {
92
+ interaction_class: Qti::V1::Models::Interactions::ChoiceInteraction,
93
+ title: 'Question 2',
94
+ feedback: {
95
+ neutral: '<p>Item Feedback, Neutral</p>',
96
+ correct: '<p>Item Feedback, Correct</p>',
97
+ incorrect: '<p>Item Feedback, Incorrect</p>'
98
+ }
99
+ },
100
+ {
101
+ interaction_class: Qti::V1::Models::Interactions::FillBlankInteraction,
102
+ title: 'Question 3',
103
+ feedback: {
104
+ neutral: '<p>Item Feedback, Neutral</p>',
105
+ correct: '<p>Item Feedback, Correct</p>',
106
+ incorrect: '<p>Item Feedback, Incorrect</p>'
107
+ }
108
+ },
109
+ {
110
+ interaction_class: Qti::V1::Models::Interactions::FillBlankInteraction,
111
+ title: 'Question 4',
112
+ feedback: {
113
+ neutral: '<p>Item Feedback, Neutral</p>',
114
+ correct: '<p>Item Feedback, Correct</p>',
115
+ incorrect: '<p>Item Feedback, Incorrect</p>'
116
+ }
117
+ },
118
+ {
119
+ interaction_class: Qti::V1::Models::Interactions::ChoiceInteraction,
120
+ title: 'Question 5',
121
+ feedback: {
122
+ neutral: '<p>Item Feedback, Neutral</p>',
123
+ correct: '<p>Item Feedback, Correct</p>',
124
+ incorrect: '<p>Item Feedback, Incorrect</p>'
125
+ }
126
+ },
127
+ {
128
+ interaction_class: Qti::V1::Models::Interactions::CanvasMultipleDropdownInteraction,
129
+ title: 'Question 6',
130
+ feedback: {
131
+ neutral: '<p>Item Feedback, Neutral</p>',
132
+ correct: '<p>Item Feedback, Correct</p>',
133
+ incorrect: '<p>Item Feedback, Incorrect</p>'
134
+ }
135
+ },
136
+ {
137
+ interaction_class: Qti::V1::Models::Interactions::MatchInteraction,
138
+ title: 'Question 7',
139
+ feedback: {
140
+ neutral: '<p>Item Feedback, Neutral</p>',
141
+ correct: '<p>Item Feedback, Correct</p>',
142
+ incorrect: '<p>Item Feedback, Incorrect</p>'
143
+ }
144
+ },
145
+ {
146
+ interaction_class: Qti::V1::Models::Interactions::NumericInteraction,
147
+ title: 'Question 8',
148
+ feedback: {
149
+ neutral: '<p>Item Feedback, Neutral</p>',
150
+ correct: '<p>Item Feedback, Correct</p>',
151
+ incorrect: '<p>Item Feedback, Incorrect</p>'
152
+ }
153
+ },
154
+ {
155
+ interaction_class: Qti::V1::Models::Interactions::FormulaInteraction,
156
+ title: 'Question 9',
157
+ feedback: {
158
+ neutral: '<p>Item Feedback, Neutral</p>',
159
+ correct: '<p>Item Feedback, Correct</p>',
160
+ incorrect: '<p>Item Feedback, Incorrect</p>'
161
+ }
162
+ },
163
+ {
164
+ interaction_class: Qti::V1::Models::Interactions::StringInteraction,
165
+ title: 'Question 10',
166
+ feedback: { neutral: '<p>Item Feedback, Neutral</p>' }
167
+ }
168
+ ]
19
169
  end
170
+ include_examples('basic quiz fields')
171
+ include_examples('verify quiz items')
20
172
  end
21
173
 
22
174
  context '<assessmentTest> has no title property' do
@@ -34,4 +186,26 @@ describe Qti::V1::Models::Assessment do
34
186
  end
35
187
  end
36
188
  end
189
+
190
+ describe 'stimulus (text-no-question) item' do
191
+ let(:fixtures_path) { File.join('spec', 'fixtures', 'items_1.2') }
192
+ let(:test_object) { described_class.from_path!(file_path) }
193
+ let(:assessment_item_refs) { test_object.assessment_items }
194
+ let(:assessment_item_ref) { assessment_item_refs.first }
195
+ let(:file_path) { File.join(fixtures_path, 'text.no.question.xml') }
196
+
197
+ it 'methods behave as expected' do
198
+ expect(
199
+ test_object.create_assessment_item(assessment_item_ref)
200
+ ).to eq(nil)
201
+
202
+ expect(
203
+ test_object.stimulus_ref(assessment_item_ref)
204
+ ).to eq(assessment_item_ref)
205
+
206
+ expect(
207
+ test_object.create_stimulus(assessment_item_ref).class
208
+ ).to be(Qti::V1::Models::StimulusItem)
209
+ end
210
+ end
37
211
  end
@@ -18,4 +18,60 @@ describe Qti::V1::Models::Interactions::BaseInteraction do
18
18
  interaction = described_class.new(node, assessment)
19
19
  expect(interaction.rcardinality).to eq 'Single'
20
20
  end
21
+
22
+ let(:fixtures_path) { File.join('spec', 'fixtures', 'items_1.2') }
23
+ let(:test_object) { Qti::V1::Models::Assessment.from_path!(file_path) }
24
+ let(:assessment_item_refs) { test_object.assessment_items }
25
+ let(:loaded_class) { described_class.new(assessment_item_refs.first, test_object) }
26
+
27
+ shared_examples_for 'item_level_feedback' do
28
+ it 'returns the correct item-level feedback' do
29
+ expect(loaded_class.canvas_item_feedback[:neutral]).to eq(general_fb)
30
+ expect(loaded_class.canvas_item_feedback[:correct]).to eq(correct_fb)
31
+ expect(loaded_class.canvas_item_feedback[:incorrect]).to eq(incorrect_fb)
32
+ end
33
+ end
34
+
35
+ shared_examples_for 'answer_feedback' do
36
+ it 'returns the correct answer feedback' do
37
+ expect(loaded_class.answer_feedback).to eq(answer_fb)
38
+ end
39
+ end
40
+
41
+ context 'canvas_multiple_dropdown.xml' do
42
+ let(:file_path) { File.join(fixtures_path, 'canvas_multiple_dropdown.xml') }
43
+ let(:general_fb) do
44
+ "<p>Roses come in many colors, violets probably do too.</p>\n<p>Never the less, " \
45
+ 'the correct answers were <strong>red</strong> and <strong>blue</strong>.</p>'
46
+ end
47
+ let(:correct_fb) { '<p>Completing the poem, is left to you.</p>' }
48
+ let(:incorrect_fb) { '<p>Those aren\'t colors, you meant <strong>red</strong> and <strong>blue</strong>.</p>' }
49
+
50
+ let(:answer_fb) do
51
+ [
52
+ { response_id: 'response_color1', response_value: '6548',
53
+ texttype: 'text/html', feedback: '<p>Yes! <strong>Red</strong>.</p>' },
54
+ { response_id: 'response_color1', response_value: '5550',
55
+ texttype: 'text/html', feedback: "<p>I'm pretty sure you meant <strong>red.</strong></p>" },
56
+ { response_id: 'response_color2', response_value: '6951',
57
+ texttype: 'text/html', feedback: '<p>Yes, <strong>blue</strong>!</p>' },
58
+ { response_id: 'response_color2', response_value: '4500',
59
+ texttype: 'text/html', feedback: '<p>Did you also chose plaid? You wanted to <strong>blue</strong>.</p>' }
60
+ ]
61
+ end
62
+
63
+ include_examples('item_level_feedback')
64
+ include_examples('answer_feedback')
65
+ end
66
+
67
+ context 'canvas_multiple_fib.xml' do
68
+ let(:file_path) { File.join(fixtures_path, 'canvas_multiple_fib.xml') }
69
+ let(:general_fb) { nil }
70
+ let(:correct_fb) { nil }
71
+ let(:incorrect_fb) { nil }
72
+ let(:answer_fb) { nil }
73
+
74
+ include_examples('item_level_feedback')
75
+ include_examples('answer_feedback')
76
+ end
21
77
  end
@@ -20,22 +20,32 @@ describe Qti::V1::Models::Interactions::ChoiceInteraction do
20
20
  end
21
21
  end
22
22
 
23
+ shared_examples_for 'meta_type' do
24
+ it 'returns interaction type from canvas meta data' do
25
+ expect(loaded_class.meta_type).to eq meta_type
26
+ end
27
+ end
28
+
23
29
  context 'multiple_choice.xml' do
24
30
  let(:file_path) { File.join(fixtures_path, 'multiple_choice.xml') }
25
31
  let(:shuffle_value) { false }
26
32
  let(:answer_choices_count) { 5 }
33
+ let(:meta_type) { nil }
27
34
 
28
35
  include_examples 'shuffled?'
29
36
  include_examples 'answers'
37
+ include_examples 'meta_type'
30
38
  end
31
39
 
32
40
  context 'true_false.xml' do
33
41
  let(:file_path) { File.join(fixtures_path, 'true_false.xml') }
34
42
  let(:shuffle_value) { true }
35
43
  let(:answer_choices_count) { 2 }
44
+ let(:meta_type) { nil }
36
45
 
37
46
  include_examples 'shuffled?'
38
47
  include_examples 'answers'
48
+ include_examples 'meta_type'
39
49
  end
40
50
 
41
51
  context 'multiple_answer.xml' do
@@ -49,8 +59,10 @@ describe Qti::V1::Models::Interactions::ChoiceInteraction do
49
59
  let(:file_path) { File.join(fixtures_path, 'multiple_answer_canvas.xml') }
50
60
  let(:shuffle_value) { false }
51
61
  let(:answer_choices_count) { 4 }
62
+ let(:meta_type) { 'multiple_answers_question' }
52
63
 
53
64
  include_examples 'shuffled?'
65
+ include_examples 'meta_type'
54
66
  end
55
67
 
56
68
  context 'multiple respconditions with empty setvars' do
@@ -1,44 +1,88 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Qti::V1::Models::Interactions::MatchInteraction do
4
- let(:file) { File.join('spec', 'fixtures', 'items_1.2', 'matching.xml') }
4
+ let(:path) { File.join('spec', 'fixtures', 'items_1.2') }
5
5
  let(:assessment) { Qti::V1::Models::Assessment.from_path!(file) }
6
6
  let(:item) { assessment.assessment_items.first }
7
- subject { described_class.new(item, assessment) }
7
+ let(:subject) { described_class.new(item, assessment) }
8
8
 
9
- it 'returns shuffle setting' do
10
- expect(subject.shuffled?).to eq false
11
- end
9
+ shared_examples_for 'common features' do
10
+ describe '.matches' do
11
+ it 'matches the item in file' do
12
+ expect(described_class.matches(item, assessment)).to be_truthy
13
+ end
14
+ end
12
15
 
13
- it 'returns the answers' do
14
- expect(subject.answers.map(&:item_body)).to eq(
15
- ['Magnify up to about 400 times. Sometimes requires colored staining of cells.',
16
- "Uses a beam of electrons. Can provide details of cells' internal structure.",
17
- 'A distractor answer.']
18
- )
16
+ it 'returns shuffle setting' do
17
+ expect(subject.shuffled?).to eq false
18
+ end
19
19
  end
20
20
 
21
- describe '.matches' do
22
- it 'matches the item in file' do
23
- expect(described_class.matches(item, assessment)).to be_truthy
21
+ shared_examples_for 'questions and answers' do
22
+ it 'returns the answers' do
23
+ expect(subject.answers.map(&:item_body)).to eq(expected_answers)
24
+ end
25
+
26
+ it 'returns the questions' do
27
+ expect(subject.questions).to eq(expected_questions)
24
28
  end
25
29
  end
26
30
 
27
- describe '#scoring_data_structs' do
31
+ shared_examples_for '#scoring_data_structs' do
28
32
  it 'grabs scoring data value for matching questions' do
29
- expect(subject.scoring_data_structs.first.values).to eq(
33
+ expect(subject.scoring_data_structs.first.values).to eq(expected_scoring_data)
34
+ end
35
+ end
36
+
37
+ context 'matching.xml' do
38
+ let(:file) { File.join(path, 'matching.xml') }
39
+ let(:expected_answers) do
40
+ [
41
+ 'Magnify up to about 400 times. Sometimes requires colored staining of cells.',
42
+ "Uses a beam of electrons. Can provide details of cells' internal structure.",
43
+ 'A distractor answer.'
44
+ ]
45
+ end
46
+ let(:expected_questions) do
47
+ [
48
+ { id: 'question_1', itemBody: 'Light Microscope' },
49
+ { id: 'question_2', itemBody: 'Electron Microscopes' }
50
+ ]
51
+ end
52
+ let(:expected_scoring_data) do
53
+ {
30
54
  'question_1' => 'Magnify up to about 400 times. Sometimes requires colored staining of cells.',
31
55
  'question_2' => "Uses a beam of electrons. Can provide details of cells' internal structure."
32
- )
56
+ }
33
57
  end
58
+
59
+ include_examples('common features')
60
+ include_examples('questions and answers')
61
+ include_examples('#scoring_data_structs')
34
62
  end
35
63
 
36
- describe '#questions' do
37
- it 'returns the questions' do
38
- expect(subject.questions).to eq(
39
- [{ id: 'question_1', itemBody: 'Light Microscope' },
40
- { id: 'question_2', itemBody: 'Electron Microscopes' }]
41
- )
64
+ context 'matching_feedback.xml' do
65
+ let(:file) { File.join(path, 'matching_feedback.xml') }
66
+ let(:expected_answers) { '1,2,,C,D,E,F,3,4,5,6'.split(',') }
67
+ let(:expected_questions) do
68
+ [
69
+ { id: 'response_6831', itemBody: 'A' },
70
+ { id: 'response_6259', itemBody: 'B' },
71
+ { id: 'response_743', itemBody: '' },
72
+ { id: 'response_1943', itemBody: '' }
73
+ ]
74
+ end
75
+ let(:expected_scoring_data) do
76
+ {
77
+ 'response_6831' => '1',
78
+ 'response_6259' => '2',
79
+ 'response_743' => '',
80
+ 'response_1943' => ''
81
+ }
42
82
  end
83
+
84
+ include_examples('common features')
85
+ include_examples('questions and answers')
86
+ include_examples('#scoring_data_structs')
43
87
  end
44
88
  end