qti 0.9.14 → 0.9.15

Sign up to get free protection for your applications and to get access to all the features.
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