qti 2.5.0 → 2.7.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 +4 -4
- data/README.md +2 -2
- data/lib/qti/exporter.rb +5 -7
- data/lib/qti/models/assessment_meta.rb +6 -2
- data/lib/qti/models/base.rb +6 -2
- data/lib/qti/models/manifest.rb +3 -2
- data/lib/qti/models/resource.rb +6 -3
- data/lib/qti/sanitizer.rb +1 -1
- data/lib/qti/v1/models/assessment.rb +4 -0
- data/lib/qti/v1/models/assessment_item.rb +2 -2
- data/lib/qti/v1/models/interactions/base_fill_blank_interaction.rb +1 -1
- data/lib/qti/v1/models/interactions.rb +1 -2
- data/lib/qti/v1/models/numerics/exact_match.rb +0 -4
- data/lib/qti/v1/models/numerics/margin_error.rb +0 -4
- data/lib/qti/v1/models/numerics/precision.rb +0 -4
- data/lib/qti/v1/models/numerics/scoring_data.rb +1 -0
- data/lib/qti/v1/models/numerics/within_range.rb +0 -4
- data/lib/qti/v1/models/object_bank.rb +11 -0
- data/lib/qti/v1/models/question_group.rb +8 -0
- data/lib/qti/v2/models/assessment_test.rb +6 -1
- data/lib/qti/v2/models/interactions/match_interaction.rb +1 -1
- data/lib/qti/version.rb +1 -1
- data/lib/qti.rb +4 -3
- data/spec/fixtures/items_1.2/question_group.xml +2 -0
- data/spec/gemfiles/{rails-5.1.gemfile → rails-6.1.gemfile} +1 -1
- data/spec/lib/qti/models/assessment_meta_spec.rb +5 -0
- data/spec/lib/qti/models/base_spec.rb +2 -2
- data/spec/lib/qti/v1/models/assessment_spec.rb +9 -0
- data/spec/lib/qti/v1/models/interactions/base_fill_blank_interaction_spec.rb +25 -0
- data/spec/lib/qti/v1/models/object_bank_spec.rb +37 -13
- data/spec/lib/qti/v1/models/question_group_spec.rb +2 -0
- data/spec/lib/qti/v2/models/assessment_item_spec.rb +2 -2
- data/spec/lib/qti/v2/models/assessment_test_spec.rb +6 -2
- data/spec/lib/qti/v2/models/stimulus_item_spec.rb +4 -4
- data/spec/lib/qti_spec.rb +3 -3
- data/spec/spec_helper.rb +2 -2
- metadata +38 -24
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 01dd841b1a9215aeaddf4e9211f7e2327544067afa5fb2e24a58186f7c035115
|
|
4
|
+
data.tar.gz: b4d688f36fb284da0ba8168463b667eb59bf612b2750ef50650230dcc129e2bb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 301bfa283539d68e2feca734a9aa97046c2604c5eedc8f656e217cdbd90949d3ddf88dcdda39ecbb1c19dde68d0b51c5f582aa9b33c3f79995109e921126dbe2
|
|
7
|
+
data.tar.gz: 880e9ce56fe47276d833ab1b19d1f6b30646cf5a641ddce0f7e6468bb9922b05ccfd5ff140b413f7e8698c532798ade8fa50262a37e93231cfef5af5b3417ae8
|
data/README.md
CHANGED
|
@@ -80,8 +80,8 @@ cached, making additional runs significantly faster.
|
|
|
80
80
|
Individual spec runs can be started like so:
|
|
81
81
|
|
|
82
82
|
```bash
|
|
83
|
-
docker-compose run --rm app
|
|
84
|
-
"BUNDLE_GEMFILE=spec/gemfiles/rails-6.
|
|
83
|
+
docker-compose run --rm app bash -lc \
|
|
84
|
+
"BUNDLE_GEMFILE=spec/gemfiles/rails-6.1.gemfile rvm-exec 2.7 rspec"
|
|
85
85
|
```
|
|
86
86
|
|
|
87
87
|
If you'd like to mount your git checkout within the docker container running
|
data/lib/qti/exporter.rb
CHANGED
|
@@ -7,7 +7,7 @@ module Qti
|
|
|
7
7
|
@assessment_test = assessment_test
|
|
8
8
|
@package_root_path = args[:package_root_path] || '.'
|
|
9
9
|
@exported_file_path =
|
|
10
|
-
File.join(File.expand_path('..', package_root_path), File.basename(export_file_name))
|
|
10
|
+
"#{File.join(File.expand_path('..', package_root_path), File.basename(export_file_name))}.zip"
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def export
|
|
@@ -167,12 +167,10 @@ module Qti
|
|
|
167
167
|
|
|
168
168
|
def add_all_files(zipfile, package_root_path)
|
|
169
169
|
Dir["#{package_root_path}/**/**"].each do |file|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
logger.info("#{file} already exists")
|
|
175
|
-
end
|
|
170
|
+
entry = file.sub("#{package_root_path}/", '')
|
|
171
|
+
zipfile.add(entry, file)
|
|
172
|
+
rescue Zip::EntryExistsError
|
|
173
|
+
logger.info("#{file} already exists")
|
|
176
174
|
end
|
|
177
175
|
end
|
|
178
176
|
end
|
|
@@ -37,10 +37,14 @@ module Qti
|
|
|
37
37
|
points_possible_raw.to_f
|
|
38
38
|
end
|
|
39
39
|
|
|
40
|
-
def
|
|
40
|
+
def show_correct_answers
|
|
41
41
|
tag_under_quiz('show_correct_answers')
|
|
42
42
|
end
|
|
43
43
|
|
|
44
|
+
def show_correct_answers?
|
|
45
|
+
string_true?(show_correct_answers)
|
|
46
|
+
end
|
|
47
|
+
|
|
44
48
|
def anonymous_submissions
|
|
45
49
|
tag_under_quiz('anonymous_submissions')
|
|
46
50
|
end
|
|
@@ -217,7 +221,7 @@ module Qti
|
|
|
217
221
|
:show_correct_answers_at, :hide_correct_answers_at,
|
|
218
222
|
:lock_at, :unlock_at, :due_at, :require_lockdown_browser?,
|
|
219
223
|
:require_lockdown_browser_for_results?,
|
|
220
|
-
:require_lockdown_browser_monitor?,
|
|
224
|
+
:require_lockdown_browser_monitor?, :show_correct_answers?,
|
|
221
225
|
:lockdown_browser_monitor_data,
|
|
222
226
|
to: :@canvas_meta_data, prefix: :canvas, allow_nil: true
|
|
223
227
|
|
data/lib/qti/models/base.rb
CHANGED
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
module Qti
|
|
2
2
|
class ParseError < StandardError; end
|
|
3
|
+
|
|
3
4
|
class SpecificationViolation < StandardError; end
|
|
5
|
+
|
|
4
6
|
class UnsupportedSchema < StandardError; end
|
|
5
7
|
|
|
6
8
|
module Models
|
|
7
9
|
class Base
|
|
8
10
|
attr_reader :doc, :path, :package_root, :resource
|
|
9
11
|
attr_accessor :manifest
|
|
12
|
+
|
|
10
13
|
delegate :metadata, to: :@resource, allow_nil: true
|
|
11
14
|
|
|
12
15
|
def sanitize_content!(html)
|
|
13
16
|
sanitizer.clean(html)
|
|
14
17
|
end
|
|
15
18
|
|
|
16
|
-
def self.from_path!(path, package_root
|
|
19
|
+
def self.from_path!(path, package_root: nil, resource: nil)
|
|
17
20
|
new(path: path, package_root: package_root, resource: resource)
|
|
18
21
|
end
|
|
19
22
|
|
|
@@ -82,7 +85,8 @@ module Qti
|
|
|
82
85
|
def package_root=(package_root)
|
|
83
86
|
@package_root = package_root
|
|
84
87
|
return unless @package_root
|
|
85
|
-
|
|
88
|
+
|
|
89
|
+
@package_root = "#{Pathname.new(@package_root).cleanpath}/"
|
|
86
90
|
end
|
|
87
91
|
|
|
88
92
|
def relative_path
|
data/lib/qti/models/manifest.rb
CHANGED
|
@@ -24,7 +24,8 @@ module Qti
|
|
|
24
24
|
builder = ASSESSMENT_CLASSES[version.split('/').first]
|
|
25
25
|
raise_unsupported unless builder
|
|
26
26
|
rsc = resource_for(identifier, version)
|
|
27
|
-
assessment = builder.from_path!(remap_href_path(asset_resource_for(rsc)), @package_root,
|
|
27
|
+
assessment = builder.from_path!(remap_href_path(asset_resource_for(rsc)), package_root: @package_root,
|
|
28
|
+
resource: rsc)
|
|
28
29
|
assessment.canvas_meta_data(rsc.canvas_metadata)
|
|
29
30
|
assessment
|
|
30
31
|
end
|
|
@@ -46,7 +47,7 @@ module Qti
|
|
|
46
47
|
end
|
|
47
48
|
|
|
48
49
|
def embedded_non_assessment
|
|
49
|
-
Qti::V2::Models::NonAssessmentTest.from_path!(@path, @package_root)
|
|
50
|
+
Qti::V2::Models::NonAssessmentTest.from_path!(@path, package_root: @package_root)
|
|
50
51
|
end
|
|
51
52
|
end
|
|
52
53
|
end
|
data/lib/qti/models/resource.rb
CHANGED
|
@@ -91,7 +91,7 @@ module Qti
|
|
|
91
91
|
Resource.new(resource_node(base_xpath), self)
|
|
92
92
|
end
|
|
93
93
|
|
|
94
|
-
def assessment_identifiers(embedded_as_assessment
|
|
94
|
+
def assessment_identifiers(embedded_as_assessment: true)
|
|
95
95
|
id_list = identifier_list('/assessment')
|
|
96
96
|
return id_list + [EMBEDDED_NON_ASSESSMENT_ID] if embedded_as_assessment && embedded_non_assessment?
|
|
97
97
|
id_list
|
|
@@ -109,13 +109,16 @@ module Qti
|
|
|
109
109
|
doc = load_asset_resource(rsc_path)
|
|
110
110
|
|
|
111
111
|
# There are other types, but all we support right now are object banks...
|
|
112
|
-
|
|
112
|
+
unless doc.search('objectbank').empty?
|
|
113
|
+
Qti::V1::Models::ObjectBank.from_path!(rsc_path, package_root: @package_root,
|
|
114
|
+
resource: rsc)
|
|
115
|
+
end
|
|
113
116
|
end.reject(&:nil?)
|
|
114
117
|
end
|
|
115
118
|
|
|
116
119
|
def objectbanks
|
|
117
120
|
load_associated_content.select do |c|
|
|
118
|
-
c.
|
|
121
|
+
c.instance_of?(Qti::V1::Models::ObjectBank)
|
|
119
122
|
end
|
|
120
123
|
end
|
|
121
124
|
|
data/lib/qti/sanitizer.rb
CHANGED
|
@@ -6,6 +6,10 @@ module Qti
|
|
|
6
6
|
|
|
7
7
|
GROUP_ID = 'xmlns:section/xmlns:selection_ordering'.freeze
|
|
8
8
|
|
|
9
|
+
def identifier
|
|
10
|
+
@identifier ||= xpath_with_single_check('.//xmlns:assessment/@ident')&.content
|
|
11
|
+
end
|
|
12
|
+
|
|
9
13
|
def title
|
|
10
14
|
@title ||= xpath_with_single_check('.//xmlns:assessment/@title')&.content || File.basename(@path, '.xml')
|
|
11
15
|
end
|
|
@@ -3,7 +3,7 @@ module Qti
|
|
|
3
3
|
module Models
|
|
4
4
|
module Interactions
|
|
5
5
|
class BaseFillBlankInteraction < BaseInteraction
|
|
6
|
-
CANVAS_REGEX ||= /(\[
|
|
6
|
+
CANVAS_REGEX ||= /(\[[A-Za-z0-9_\-.]+\])/.freeze
|
|
7
7
|
|
|
8
8
|
def canvas_stem_items(item_prompt)
|
|
9
9
|
item_prompt.split(CANVAS_REGEX).map.with_index do |stem_item, index|
|
|
@@ -56,11 +56,10 @@ module Qti
|
|
|
56
56
|
end
|
|
57
57
|
|
|
58
58
|
def self.get_matches(node, parent, classlist)
|
|
59
|
-
|
|
59
|
+
classlist.each_with_object([]) do |interaction_class, result|
|
|
60
60
|
match = interaction_class.matches(node, parent)
|
|
61
61
|
result << match if match
|
|
62
62
|
end
|
|
63
|
-
matches
|
|
64
63
|
end
|
|
65
64
|
end
|
|
66
65
|
end
|
|
@@ -8,6 +8,17 @@ module Qti
|
|
|
8
8
|
'.//xmlns:qtimetadatafield/xmlns:fieldlabel[text()="bank_title"]/../xmlns:fieldentry'
|
|
9
9
|
)&.content || File.basename(@path, '.xml')
|
|
10
10
|
end
|
|
11
|
+
|
|
12
|
+
def identifier
|
|
13
|
+
@identifier ||= xpath_with_single_check('.//xmlns:objectbank/@ident')&.content
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# tells us whether the bank was an account or course bank
|
|
17
|
+
def bank_type
|
|
18
|
+
@bank_type ||= xpath_with_single_check(
|
|
19
|
+
'.//xmlns:qtimetadatafield/xmlns:fieldlabel[text()="bank_type"]/../xmlns:fieldentry'
|
|
20
|
+
)&.content
|
|
21
|
+
end
|
|
11
22
|
end
|
|
12
23
|
end
|
|
13
24
|
end
|
|
@@ -34,6 +34,14 @@ module Qti
|
|
|
34
34
|
selection&.xpath('xmlns:selection_number')&.text&.to_i
|
|
35
35
|
end
|
|
36
36
|
|
|
37
|
+
def sourcebank_ref
|
|
38
|
+
selection&.xpath('xmlns:sourcebank_ref')&.text
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def sourcebank_export_id
|
|
42
|
+
selection&.xpath('xmlns:sourcebank_export_id')&.text
|
|
43
|
+
end
|
|
44
|
+
|
|
37
45
|
def points_per_item
|
|
38
46
|
selection.xpath('xmlns:selection_extension/xmlns:points_per_item')&.text&.to_f
|
|
39
47
|
end
|
|
@@ -5,6 +5,10 @@ module Qti
|
|
|
5
5
|
include Qti::Models::AssessmentMetaBase
|
|
6
6
|
include Qti::XPathHelpers
|
|
7
7
|
|
|
8
|
+
def identifier
|
|
9
|
+
@identifier ||= xpath_with_single_check('//xmlns:assessmentTest/@identifier')&.content
|
|
10
|
+
end
|
|
11
|
+
|
|
8
12
|
def title
|
|
9
13
|
@title ||= xpath_with_single_check('//xmlns:assessmentTest/@title')&.content || File.basename(@path, '.xml')
|
|
10
14
|
end
|
|
@@ -27,7 +31,8 @@ module Qti
|
|
|
27
31
|
end
|
|
28
32
|
|
|
29
33
|
def create_assessment_item(ref)
|
|
30
|
-
item = Qti::V2::Models::AssessmentItem.from_path!(ref[:path], @package_root,
|
|
34
|
+
item = Qti::V2::Models::AssessmentItem.from_path!(ref[:path], package_root: @package_root,
|
|
35
|
+
resource: ref[:resource])
|
|
31
36
|
item.manifest = manifest
|
|
32
37
|
item
|
|
33
38
|
end
|
data/lib/qti/version.rb
CHANGED
data/lib/qti.rb
CHANGED
|
@@ -5,6 +5,7 @@ require 'active_support/core_ext/hash/except'
|
|
|
5
5
|
require 'active_support/core_ext/module/delegation'
|
|
6
6
|
require 'dry-struct'
|
|
7
7
|
require 'find'
|
|
8
|
+
require 'forwardable'
|
|
8
9
|
require 'mathml2latex'
|
|
9
10
|
require 'nokogiri'
|
|
10
11
|
require 'pathname'
|
|
@@ -14,8 +15,8 @@ require 'zip'
|
|
|
14
15
|
|
|
15
16
|
module Qti
|
|
16
17
|
class Importer
|
|
17
|
-
attr_reader :package_root
|
|
18
|
-
|
|
18
|
+
attr_reader :package_root, :assessment_id
|
|
19
|
+
|
|
19
20
|
delegate :assessment_identifiers, to: :@manifest
|
|
20
21
|
delegate :question_bank_identifiers, to: :@manifest
|
|
21
22
|
|
|
@@ -39,7 +40,7 @@ module Qti
|
|
|
39
40
|
def self.manifest(path)
|
|
40
41
|
mpath = manifest_path(path)
|
|
41
42
|
package_root = File.dirname(mpath)
|
|
42
|
-
manifest = Qti::Models::Manifest.from_path!(mpath, package_root
|
|
43
|
+
manifest = Qti::Models::Manifest.from_path!(mpath, package_root: package_root)
|
|
43
44
|
[mpath, package_root, manifest]
|
|
44
45
|
end
|
|
45
46
|
|
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
<selection_extension>
|
|
16
16
|
<points_per_item>1.0</points_per_item>
|
|
17
17
|
</selection_extension>
|
|
18
|
+
<sourcebank_ref>sourcebank_reference_uuid</sourcebank_ref>
|
|
19
|
+
<sourcebank_export_id>sourcebank_export_id_test</sourcebank_export_id>
|
|
18
20
|
</selection>
|
|
19
21
|
</selection_ordering>
|
|
20
22
|
<item ident="i09d4a5492a38a5b4d1533b8fdbdf7376" title="Question">
|
|
@@ -27,6 +27,7 @@ context 'Canvas Assessment Meta Data' do
|
|
|
27
27
|
expect(assessment.canvas_access_code).to eq(access_code)
|
|
28
28
|
expect(assessment.canvas_ip_filter).to eq(ip_filter)
|
|
29
29
|
expect(assessment.canvas_time_limit).to eq(time_limit)
|
|
30
|
+
expect(assessment.canvas_show_correct_answers?).to eq(show_answers)
|
|
30
31
|
expect(assessment.canvas_show_correct_answers_at).to eq(show_answers_at)
|
|
31
32
|
expect(assessment.canvas_hide_correct_answers_at).to eq(hide_answers_at)
|
|
32
33
|
expect(assessment.canvas_require_lockdown_browser?).to eq(rlb)
|
|
@@ -59,6 +60,7 @@ context 'Canvas Assessment Meta Data' do
|
|
|
59
60
|
let(:access_code) { nil }
|
|
60
61
|
let(:ip_filter) { nil }
|
|
61
62
|
let(:time_limit) { nil }
|
|
63
|
+
let(:show_answers) { true }
|
|
62
64
|
let(:show_answers_at) { nil }
|
|
63
65
|
let(:hide_answers_at) { nil }
|
|
64
66
|
let(:rlb) { false }
|
|
@@ -92,6 +94,7 @@ context 'Canvas Assessment Meta Data' do
|
|
|
92
94
|
let(:access_code) { '98aAlfmxtw#!£$s' }
|
|
93
95
|
let(:ip_filter) { '192.168.217.1/24' }
|
|
94
96
|
let(:time_limit) { 187 }
|
|
97
|
+
let(:show_answers) { true }
|
|
95
98
|
let(:show_answers_at) { '2041-02-17 06:00:00 UTC' }
|
|
96
99
|
let(:hide_answers_at) { '2042-12-26 06:00:00 UTC' }
|
|
97
100
|
let(:rlb) { false }
|
|
@@ -125,6 +128,7 @@ context 'Canvas Assessment Meta Data' do
|
|
|
125
128
|
let(:access_code) { nil }
|
|
126
129
|
let(:ip_filter) { nil }
|
|
127
130
|
let(:time_limit) { nil }
|
|
131
|
+
let(:show_answers) { false }
|
|
128
132
|
let(:show_answers_at) { nil }
|
|
129
133
|
let(:hide_answers_at) { nil }
|
|
130
134
|
let(:rlb) { false }
|
|
@@ -158,6 +162,7 @@ context 'Canvas Assessment Meta Data' do
|
|
|
158
162
|
let(:access_code) { nil }
|
|
159
163
|
let(:ip_filter) { nil }
|
|
160
164
|
let(:time_limit) { nil }
|
|
165
|
+
let(:show_answers) { true }
|
|
161
166
|
let(:show_answers_at) { nil }
|
|
162
167
|
let(:hide_answers_at) { nil }
|
|
163
168
|
let(:rlb) { false }
|
|
@@ -118,7 +118,7 @@ describe Qti::Models::Base do
|
|
|
118
118
|
context 'with explicit package root' do
|
|
119
119
|
let(:package_root) { File.join('spec', 'fixtures', 'test_qti_2.2') }
|
|
120
120
|
let(:item_path) { File.join(package_root, 'true-false', 'true-false.xml') }
|
|
121
|
-
let(:item) { described_class.from_path!(item_path, package_root) }
|
|
121
|
+
let(:item) { described_class.from_path!(item_path, package_root: package_root) }
|
|
122
122
|
|
|
123
123
|
it 'allows safe .. hrefs' do
|
|
124
124
|
expect do
|
|
@@ -135,7 +135,7 @@ describe Qti::Models::Base do
|
|
|
135
135
|
|
|
136
136
|
context 'with nil package root' do
|
|
137
137
|
let(:item_path) { File.join('spec', 'fixtures', 'test_qti_2.2', 'true-false', 'true-false.xml') }
|
|
138
|
-
let(:item) { described_class.from_path!(item_path, nil) }
|
|
138
|
+
let(:item) { described_class.from_path!(item_path, package_root: nil) }
|
|
139
139
|
|
|
140
140
|
it 'rejects .. hrefs' do
|
|
141
141
|
expect do
|
|
@@ -19,6 +19,12 @@ describe Qti::V1::Models::Assessment do
|
|
|
19
19
|
expect(loaded_class.assessment_items.count).to eq(expected_item_count)
|
|
20
20
|
end
|
|
21
21
|
end
|
|
22
|
+
|
|
23
|
+
describe '#identifier' do
|
|
24
|
+
it 'has the identifier' do
|
|
25
|
+
expect(loaded_class.identifier).to eq(expected_identifier)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
22
28
|
end
|
|
23
29
|
|
|
24
30
|
shared_examples_for 'verify quiz items' do
|
|
@@ -36,6 +42,7 @@ describe Qti::V1::Models::Assessment do
|
|
|
36
42
|
let(:path) { File.join(fixtures_path, 'test_qti_1.2', 'quiz.xml') }
|
|
37
43
|
let(:loaded_class) { described_class.from_path!(path) }
|
|
38
44
|
let(:expected_title) { '1.2 Import Quiz' }
|
|
45
|
+
let(:expected_identifier) { 'A1001' }
|
|
39
46
|
let(:expected_item_count) { 5 }
|
|
40
47
|
|
|
41
48
|
include_examples('basic quiz fields')
|
|
@@ -45,6 +52,7 @@ describe Qti::V1::Models::Assessment do
|
|
|
45
52
|
let(:path) { File.join(fixtures_path, 'feedback_quiz_1.2.xml') }
|
|
46
53
|
let(:loaded_class) { described_class.from_path!(path) }
|
|
47
54
|
let(:expected_title) { 'I Can Haz Feedback' }
|
|
55
|
+
let(:expected_identifier) { 'i618e88580f76f70a1ed28804f497df9c' }
|
|
48
56
|
let(:expected_item_count) { 3 }
|
|
49
57
|
let(:expected_item_data) do
|
|
50
58
|
[
|
|
@@ -74,6 +82,7 @@ describe Qti::V1::Models::Assessment do
|
|
|
74
82
|
let(:path) { File.join(fixtures_path, 'all_canvas_simple_1.2.xml') }
|
|
75
83
|
let(:loaded_class) { described_class.from_path!(path) }
|
|
76
84
|
let(:expected_title) { 'Every Canvas Interaction' }
|
|
85
|
+
let(:expected_identifier) { 'ie09bc528e6ecd1cb9ebfafd940c20215' }
|
|
77
86
|
let(:expected_item_count) { 10 }
|
|
78
87
|
let(:expected_item_data) do
|
|
79
88
|
[
|
|
@@ -10,6 +10,31 @@ describe Qti::V1::Models::Interactions::BaseFillBlankInteraction do
|
|
|
10
10
|
end
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
+
context 'canvas_stem_items' do
|
|
14
|
+
let(:file_path) { File.join(fixtures_path, 'canvas_multiple_fib_as_single.xml') }
|
|
15
|
+
let(:simple_prompt) { 'fill in the [blank]' }
|
|
16
|
+
let(:simple_expected) do
|
|
17
|
+
[
|
|
18
|
+
{ id: 'stem_0', position: 1, type: 'text', value: 'fill in the ' },
|
|
19
|
+
{ id: 'stem_1', position: 2, type: 'text', value: '[blank]' }
|
|
20
|
+
]
|
|
21
|
+
end
|
|
22
|
+
let(:embedded_prompt) { '[[embedded] [groups]]' }
|
|
23
|
+
let(:embedded_expected) do
|
|
24
|
+
[
|
|
25
|
+
{ id: 'stem_0', position: 1, type: 'text', value: '[' },
|
|
26
|
+
{ id: 'stem_1', position: 2, type: 'text', value: '[embedded]' },
|
|
27
|
+
{ id: 'stem_2', position: 3, type: 'text', value: ' ' },
|
|
28
|
+
{ id: 'stem_3', position: 4, type: 'text', value: '[groups]' },
|
|
29
|
+
{ id: 'stem_4', position: 5, type: 'text', value: ']' }
|
|
30
|
+
]
|
|
31
|
+
end
|
|
32
|
+
it 'decomposes item prompts' do
|
|
33
|
+
expect(loaded_class.canvas_stem_items(simple_prompt)).to eq(simple_expected)
|
|
34
|
+
expect(loaded_class.canvas_stem_items(embedded_prompt)).to eq(embedded_expected)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
13
38
|
context 'canvas_multiple_dropdowns.xml' do
|
|
14
39
|
let(:file_path) { File.join(fixtures_path, 'canvas_multiple_dropdowns.xml') }
|
|
15
40
|
let(:expected_blanks) do
|
|
@@ -1,4 +1,24 @@
|
|
|
1
1
|
describe Qti::V1::Models::ObjectBank do
|
|
2
|
+
let(:doc) do
|
|
3
|
+
<<~XML
|
|
4
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
5
|
+
<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">
|
|
6
|
+
<objectbank ident="gooblegobble12345">
|
|
7
|
+
<qtimetadata>
|
|
8
|
+
<qtimetadatafield>
|
|
9
|
+
<fieldlabel>not_a_bank_title</fieldlabel>
|
|
10
|
+
<fieldentry>A different metadata entry</fieldentry>
|
|
11
|
+
</qtimetadatafield>
|
|
12
|
+
<qtimetadatafield>
|
|
13
|
+
<fieldlabel>bank_type</fieldlabel>
|
|
14
|
+
<fieldentry>account</fieldentry>
|
|
15
|
+
</qtimetadatafield>
|
|
16
|
+
</qtimetadata>
|
|
17
|
+
</objectbank>
|
|
18
|
+
</questestinterop>
|
|
19
|
+
XML
|
|
20
|
+
end
|
|
21
|
+
|
|
2
22
|
describe 'bank loading' do
|
|
3
23
|
[
|
|
4
24
|
'gf3edf8167be16b3a65a00ca923132b07.xml.qti',
|
|
@@ -19,21 +39,25 @@ describe Qti::V1::Models::ObjectBank do
|
|
|
19
39
|
|
|
20
40
|
describe '#title' do
|
|
21
41
|
it 'missing bank_title defaults to filename' do
|
|
22
|
-
allow(File).to receive(:read).and_return(
|
|
23
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
24
|
-
<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">
|
|
25
|
-
<objectbank ident="gooblegobble12345">
|
|
26
|
-
<qtimetadata>
|
|
27
|
-
<qtimetadatafield>
|
|
28
|
-
<fieldlabel>not_a_bank_title</fieldlabel>
|
|
29
|
-
<fieldentry>A different metadata entry</fieldentry>
|
|
30
|
-
</qtimetadatafield>
|
|
31
|
-
</qtimetadata>
|
|
32
|
-
</objectbank>
|
|
33
|
-
</questestinterop>
|
|
34
|
-
XML
|
|
42
|
+
allow(File).to receive(:read).and_return(doc)
|
|
35
43
|
objectbank = described_class.new path: '/etc/FakeBank007.xml'
|
|
36
44
|
expect(objectbank.title).to eq 'FakeBank007'
|
|
37
45
|
end
|
|
38
46
|
end
|
|
47
|
+
|
|
48
|
+
describe '#identifier' do
|
|
49
|
+
it 'has the identifier attribute' do
|
|
50
|
+
allow(File).to receive(:read).and_return(doc)
|
|
51
|
+
objectbank = described_class.new path: '/etc/FakeBank008.xml'
|
|
52
|
+
expect(objectbank.identifier).to eq 'gooblegobble12345'
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
describe '#bank_type' do
|
|
57
|
+
it 'has the bank_type attribute' do
|
|
58
|
+
allow(File).to receive(:read).and_return(doc)
|
|
59
|
+
objectbank = described_class.new path: '/etc/FakeBank008.xml'
|
|
60
|
+
expect(objectbank.bank_type).to eq 'account'
|
|
61
|
+
end
|
|
62
|
+
end
|
|
39
63
|
end
|
|
@@ -7,6 +7,8 @@ describe Qti::V1::Models::QuestionGroup do
|
|
|
7
7
|
it 'configures a group correctly' do
|
|
8
8
|
expect(loaded_class.title).to eq('Group 1 (1/4)')
|
|
9
9
|
expect(loaded_class.identifier).to eq('i4663897607358cfba8636ed6127b9466')
|
|
10
|
+
expect(loaded_class.sourcebank_ref).to eq('sourcebank_reference_uuid')
|
|
11
|
+
expect(loaded_class.sourcebank_export_id).to eq('sourcebank_export_id_test')
|
|
10
12
|
expect(loaded_class.items.count).to eq(4)
|
|
11
13
|
expect(loaded_class.selection_number).to eq(1)
|
|
12
14
|
expect(loaded_class.points_per_item).to eq(1)
|
|
@@ -84,9 +84,9 @@ describe Qti::V2::Models::AssessmentItem do
|
|
|
84
84
|
|
|
85
85
|
# it 'removes the passage from the item_body' do
|
|
86
86
|
# item = test_object.create_assessment_item(item_ref)
|
|
87
|
-
# rubocop:disable AsciiComments
|
|
87
|
+
# rubocop:disable Style/AsciiComments
|
|
88
88
|
# expect(item.item_body).not_to include '¡El equipo de hockey te necesita!'
|
|
89
|
-
# rubocop:enable AsciiComments
|
|
89
|
+
# rubocop:enable Style/AsciiComments
|
|
90
90
|
# end
|
|
91
91
|
# end
|
|
92
92
|
end
|
|
@@ -15,6 +15,10 @@ describe Qti::V2::Models::AssessmentTest do
|
|
|
15
15
|
expect(loaded_class.title).to eq 'Simple Feedback Test'
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
+
it 'has the identifier' do
|
|
19
|
+
expect(loaded_class.identifier).to eq 'SPECTATUS-GENERATED-TEST'
|
|
20
|
+
end
|
|
21
|
+
|
|
18
22
|
it 'gets dependency file refs' do
|
|
19
23
|
refs = loaded_class.assessment_items
|
|
20
24
|
expect(refs.all? { |ref| File.extname(ref[:path]) == '.xml' })
|
|
@@ -83,9 +87,9 @@ describe Qti::V2::Models::AssessmentTest do
|
|
|
83
87
|
# 'passages', '0cfd5cf7-2c91-4b35-a57a-9f5d1709f68f.html'
|
|
84
88
|
# )
|
|
85
89
|
# stimulus = loaded_class.create_stimulus(stimulus_path)
|
|
86
|
-
# rubocop:disable AsciiComments
|
|
90
|
+
# rubocop:disable Style/AsciiComments
|
|
87
91
|
# expect(stimulus.title).to eq '¡El equipo de hockey te necesita!'
|
|
88
|
-
# rubocop:enable AsciiComments
|
|
92
|
+
# rubocop:enable Style/AsciiComments
|
|
89
93
|
# end
|
|
90
94
|
# end
|
|
91
95
|
end
|
|
@@ -11,17 +11,17 @@
|
|
|
11
11
|
# end
|
|
12
12
|
|
|
13
13
|
# it 'has the title' do
|
|
14
|
-
# rubocop:disable AsciiComments
|
|
14
|
+
# rubocop:disable Style/AsciiComments
|
|
15
15
|
# expect(loaded_class.title).to eq '¡El equipo de hockey te necesita!'
|
|
16
|
-
# rubocop:enable AsciiComments
|
|
16
|
+
# rubocop:enable Style/AsciiComments
|
|
17
17
|
# end
|
|
18
18
|
|
|
19
19
|
# it 'has sanitized item_body' do
|
|
20
20
|
# expect(loaded_class.body).to include '<div'
|
|
21
21
|
# expect(loaded_class.body).to include 'Listen to the audio passage.'
|
|
22
|
-
# rubocop:disable AsciiComments
|
|
22
|
+
# rubocop:disable Style/AsciiComments
|
|
23
23
|
# expect(loaded_class.body).to include '¡'
|
|
24
|
-
# rubocop:enable AsciiComments
|
|
24
|
+
# rubocop:enable Style/AsciiComments
|
|
25
25
|
# end
|
|
26
26
|
|
|
27
27
|
# it 'has the identifier used to identify it in manifest file' do
|
data/spec/lib/qti_spec.rb
CHANGED
|
@@ -40,8 +40,8 @@ describe Qti::Importer do
|
|
|
40
40
|
|
|
41
41
|
it 'sets the path and package root properly' do
|
|
42
42
|
item = importer.create_assessment_item(importer.assessment_item_refs.first)
|
|
43
|
-
expect(item.path).to eq file_path
|
|
44
|
-
expect(item.package_root).to eq file_path
|
|
43
|
+
expect(item.path).to eq "#{file_path}/quiz.xml"
|
|
44
|
+
expect(item.package_root).to eq "#{file_path}/"
|
|
45
45
|
end
|
|
46
46
|
end
|
|
47
47
|
|
|
@@ -64,7 +64,7 @@ describe Qti::Importer do
|
|
|
64
64
|
ref = importer.assessment_item_refs.first
|
|
65
65
|
item = importer.create_assessment_item(ref)
|
|
66
66
|
expect(item.path).to eq ref[:path]
|
|
67
|
-
expect(item.package_root).to eq file_path
|
|
67
|
+
expect(item.package_root).to eq "#{file_path}/"
|
|
68
68
|
expect(item.manifest).not_to be_nil
|
|
69
69
|
end
|
|
70
70
|
end
|
data/spec/spec_helper.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
require 'byebug'
|
|
2
2
|
|
|
3
3
|
# Limit coverage reporting to one build:
|
|
4
|
-
if /^2\.
|
|
4
|
+
if /^2\.7/ =~ RUBY_VERSION && /rails-6\.1/ =~ ENV['BUNDLE_GEMFILE']
|
|
5
5
|
require 'simplecov'
|
|
6
6
|
|
|
7
7
|
SimpleCov.start do
|
|
@@ -16,7 +16,7 @@ end
|
|
|
16
16
|
$LOAD_PATH.unshift File.expand_path('../lib', __dir__)
|
|
17
17
|
require 'qti'
|
|
18
18
|
|
|
19
|
-
Dir['./spec/support/**/*.rb'].each { |f| require f }
|
|
19
|
+
Dir['./spec/support/**/*.rb'].sort.each { |f| require f }
|
|
20
20
|
|
|
21
21
|
RSpec.configure do |config|
|
|
22
22
|
config.expect_with :rspec do |expectations|
|
metadata
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: qti
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.7.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Hannah Bottalla
|
|
8
8
|
- Robinson Rodríguez
|
|
9
|
-
autorequire:
|
|
9
|
+
autorequire:
|
|
10
10
|
bindir: bin
|
|
11
11
|
cert_chain: []
|
|
12
|
-
date: 2021-01
|
|
12
|
+
date: 2021-12-01 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
15
|
name: actionview
|
|
@@ -17,40 +17,40 @@ dependencies:
|
|
|
17
17
|
requirements:
|
|
18
18
|
- - ">="
|
|
19
19
|
- !ruby/object:Gem::Version
|
|
20
|
-
version: 5.
|
|
20
|
+
version: '5.2'
|
|
21
21
|
- - "<"
|
|
22
22
|
- !ruby/object:Gem::Version
|
|
23
|
-
version: '6.
|
|
23
|
+
version: '6.2'
|
|
24
24
|
type: :runtime
|
|
25
25
|
prerelease: false
|
|
26
26
|
version_requirements: !ruby/object:Gem::Requirement
|
|
27
27
|
requirements:
|
|
28
28
|
- - ">="
|
|
29
29
|
- !ruby/object:Gem::Version
|
|
30
|
-
version: 5.
|
|
30
|
+
version: '5.2'
|
|
31
31
|
- - "<"
|
|
32
32
|
- !ruby/object:Gem::Version
|
|
33
|
-
version: '6.
|
|
33
|
+
version: '6.2'
|
|
34
34
|
- !ruby/object:Gem::Dependency
|
|
35
35
|
name: activesupport
|
|
36
36
|
requirement: !ruby/object:Gem::Requirement
|
|
37
37
|
requirements:
|
|
38
38
|
- - ">="
|
|
39
39
|
- !ruby/object:Gem::Version
|
|
40
|
-
version: 5.
|
|
40
|
+
version: '5.2'
|
|
41
41
|
- - "<"
|
|
42
42
|
- !ruby/object:Gem::Version
|
|
43
|
-
version: '6.
|
|
43
|
+
version: '6.2'
|
|
44
44
|
type: :runtime
|
|
45
45
|
prerelease: false
|
|
46
46
|
version_requirements: !ruby/object:Gem::Requirement
|
|
47
47
|
requirements:
|
|
48
48
|
- - ">="
|
|
49
49
|
- !ruby/object:Gem::Version
|
|
50
|
-
version: 5.
|
|
50
|
+
version: '5.2'
|
|
51
51
|
- - "<"
|
|
52
52
|
- !ruby/object:Gem::Version
|
|
53
|
-
version: '6.
|
|
53
|
+
version: '6.2'
|
|
54
54
|
- !ruby/object:Gem::Dependency
|
|
55
55
|
name: dry-struct
|
|
56
56
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -141,14 +141,14 @@ dependencies:
|
|
|
141
141
|
requirements:
|
|
142
142
|
- - "~>"
|
|
143
143
|
- !ruby/object:Gem::Version
|
|
144
|
-
version: '1
|
|
144
|
+
version: '2.1'
|
|
145
145
|
type: :development
|
|
146
146
|
prerelease: false
|
|
147
147
|
version_requirements: !ruby/object:Gem::Requirement
|
|
148
148
|
requirements:
|
|
149
149
|
- - "~>"
|
|
150
150
|
- !ruby/object:Gem::Version
|
|
151
|
-
version: '1
|
|
151
|
+
version: '2.1'
|
|
152
152
|
- !ruby/object:Gem::Dependency
|
|
153
153
|
name: byebug
|
|
154
154
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -183,14 +183,14 @@ dependencies:
|
|
|
183
183
|
requirements:
|
|
184
184
|
- - "~>"
|
|
185
185
|
- !ruby/object:Gem::Version
|
|
186
|
-
version: '
|
|
186
|
+
version: '13.0'
|
|
187
187
|
type: :development
|
|
188
188
|
prerelease: false
|
|
189
189
|
version_requirements: !ruby/object:Gem::Requirement
|
|
190
190
|
requirements:
|
|
191
191
|
- - "~>"
|
|
192
192
|
- !ruby/object:Gem::Version
|
|
193
|
-
version: '
|
|
193
|
+
version: '13.0'
|
|
194
194
|
- !ruby/object:Gem::Dependency
|
|
195
195
|
name: rspec
|
|
196
196
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -225,14 +225,28 @@ dependencies:
|
|
|
225
225
|
requirements:
|
|
226
226
|
- - "~>"
|
|
227
227
|
- !ruby/object:Gem::Version
|
|
228
|
-
version:
|
|
228
|
+
version: 1.8.1
|
|
229
229
|
type: :development
|
|
230
230
|
prerelease: false
|
|
231
231
|
version_requirements: !ruby/object:Gem::Requirement
|
|
232
232
|
requirements:
|
|
233
233
|
- - "~>"
|
|
234
234
|
- !ruby/object:Gem::Version
|
|
235
|
-
version:
|
|
235
|
+
version: 1.8.1
|
|
236
|
+
- !ruby/object:Gem::Dependency
|
|
237
|
+
name: rubocop-rails
|
|
238
|
+
requirement: !ruby/object:Gem::Requirement
|
|
239
|
+
requirements:
|
|
240
|
+
- - "~>"
|
|
241
|
+
- !ruby/object:Gem::Version
|
|
242
|
+
version: 2.9.1
|
|
243
|
+
type: :development
|
|
244
|
+
prerelease: false
|
|
245
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
246
|
+
requirements:
|
|
247
|
+
- - "~>"
|
|
248
|
+
- !ruby/object:Gem::Version
|
|
249
|
+
version: 2.9.1
|
|
236
250
|
- !ruby/object:Gem::Dependency
|
|
237
251
|
name: simplecov
|
|
238
252
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -261,7 +275,7 @@ dependencies:
|
|
|
261
275
|
- - "~>"
|
|
262
276
|
- !ruby/object:Gem::Version
|
|
263
277
|
version: '1.4'
|
|
264
|
-
description:
|
|
278
|
+
description:
|
|
265
279
|
email:
|
|
266
280
|
- hannah@instructure.com
|
|
267
281
|
- rrodriguez-bd@instructure.com
|
|
@@ -613,9 +627,9 @@ files:
|
|
|
613
627
|
- spec/fixtures/with_banks/non_cc_assessments/g9a20cf3af178b54e4792cbe992f65790.xml.qti
|
|
614
628
|
- spec/fixtures/with_banks/non_cc_assessments/gab22de457404cb5cf022078f1e4da75e.xml.qti
|
|
615
629
|
- spec/fixtures/with_banks/non_cc_assessments/gf3edf8167be16b3a65a00ca923132b07.xml.qti
|
|
616
|
-
- spec/gemfiles/rails-5.1.gemfile
|
|
617
630
|
- spec/gemfiles/rails-5.2.gemfile
|
|
618
631
|
- spec/gemfiles/rails-6.0.gemfile
|
|
632
|
+
- spec/gemfiles/rails-6.1.gemfile
|
|
619
633
|
- spec/lib/qti/assessment_item_exporter_spec.rb
|
|
620
634
|
- spec/lib/qti/exporter_spec.rb
|
|
621
635
|
- spec/lib/qti/models/assessment_meta_spec.rb
|
|
@@ -670,7 +684,7 @@ homepage: https://github.com/instructure/qti
|
|
|
670
684
|
licenses:
|
|
671
685
|
- MIT
|
|
672
686
|
metadata: {}
|
|
673
|
-
post_install_message:
|
|
687
|
+
post_install_message:
|
|
674
688
|
rdoc_options: []
|
|
675
689
|
require_paths:
|
|
676
690
|
- lib
|
|
@@ -678,15 +692,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
678
692
|
requirements:
|
|
679
693
|
- - ">="
|
|
680
694
|
- !ruby/object:Gem::Version
|
|
681
|
-
version: '2.
|
|
695
|
+
version: '2.6'
|
|
682
696
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
683
697
|
requirements:
|
|
684
698
|
- - ">="
|
|
685
699
|
- !ruby/object:Gem::Version
|
|
686
700
|
version: '0'
|
|
687
701
|
requirements: []
|
|
688
|
-
rubygems_version: 3.
|
|
689
|
-
signing_key:
|
|
702
|
+
rubygems_version: 3.1.4
|
|
703
|
+
signing_key:
|
|
690
704
|
specification_version: 4
|
|
691
705
|
summary: QTI 1.2 and 2.1 import and export models
|
|
692
706
|
test_files:
|
|
@@ -965,9 +979,9 @@ test_files:
|
|
|
965
979
|
- spec/fixtures/with_banks/non_cc_assessments/g9a20cf3af178b54e4792cbe992f65790.xml.qti
|
|
966
980
|
- spec/fixtures/with_banks/non_cc_assessments/gab22de457404cb5cf022078f1e4da75e.xml.qti
|
|
967
981
|
- spec/fixtures/with_banks/non_cc_assessments/gf3edf8167be16b3a65a00ca923132b07.xml.qti
|
|
968
|
-
- spec/gemfiles/rails-5.1.gemfile
|
|
969
982
|
- spec/gemfiles/rails-5.2.gemfile
|
|
970
983
|
- spec/gemfiles/rails-6.0.gemfile
|
|
984
|
+
- spec/gemfiles/rails-6.1.gemfile
|
|
971
985
|
- spec/lib/qti/assessment_item_exporter_spec.rb
|
|
972
986
|
- spec/lib/qti/exporter_spec.rb
|
|
973
987
|
- spec/lib/qti/models/assessment_meta_spec.rb
|