qti 2.5.1 → 2.8.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/README.md +2 -2
- data/lib/qti/exporter.rb +5 -7
- 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 +14 -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 +13 -3
- data/spec/fixtures/items_1.2/question_group.xml +2 -0
- data/spec/fixtures/items_1.2/true_false.xml +1 -1
- data/spec/gemfiles/{rails-5.1.gemfile → rails-6.1.gemfile} +1 -1
- data/spec/lib/qti/models/base_spec.rb +2 -2
- data/spec/lib/qti/v1/models/assessment_item_spec.rb +12 -0
- 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 +6 -2
- metadata +33 -19
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e121df522dfdc1e21c2944bfa04334fdb4226c8a288356ff4ad1ef84e7354a86
|
|
4
|
+
data.tar.gz: 01cea5632a13762bc2929b4f61cafbc47893c38fda88c1d14a5d51a5eb1b73aa
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 43eb6eee2a5e6073750023429875e267565ab4ff2a23a6bcf292231ccf95f5a09da886208d12ad1fc4db7c339616801e908b0c0a4e3aee0c72f4e651065eaec3
|
|
7
|
+
data.tar.gz: cfea73f7c7d3a86f41de834205c634a5f0789ea57519eb180491a8411c55e292c4648673ca76d5aa1b04653ee7e704de50a422b3399780f3f428301a897d2968
|
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
|
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
|
@@ -37,6 +37,18 @@ module Qti
|
|
|
37
37
|
|
|
38
38
|
private
|
|
39
39
|
|
|
40
|
+
def convert_canvas_math_images(env)
|
|
41
|
+
node = env[:node]
|
|
42
|
+
node_name = env[:node_name]
|
|
43
|
+
latex = node['data-equation-content']
|
|
44
|
+
|
|
45
|
+
return if env[:is_whitelisted] || !env[:node].element?
|
|
46
|
+
return unless node_name == 'img'
|
|
47
|
+
return unless latex
|
|
48
|
+
|
|
49
|
+
node.replace("\\(#{latex}\\)")
|
|
50
|
+
end
|
|
51
|
+
|
|
40
52
|
def object_tag_transformer
|
|
41
53
|
lambda do |env|
|
|
42
54
|
return unless env[:node_name] == 'object'
|
|
@@ -73,6 +85,7 @@ module Qti
|
|
|
73
85
|
transformers << src_transformers
|
|
74
86
|
transformers << object_tag_transformer if import_objects
|
|
75
87
|
transformers << remap_unknown_tags_transformer
|
|
88
|
+
transformers << method(:convert_canvas_math_images) if Qti.configuration.extract_latex_from_image_tags
|
|
76
89
|
Sanitize::Config::RELAXED.merge transformers: transformers
|
|
77
90
|
end
|
|
78
91
|
|
|
@@ -93,7 +106,7 @@ module Qti
|
|
|
93
106
|
path = remap_href_path(node[:data])
|
|
94
107
|
if path
|
|
95
108
|
case node[:type]
|
|
96
|
-
when %r{^image
|
|
109
|
+
when %r{^image/}
|
|
97
110
|
return replace_with_image(node, node[:data])
|
|
98
111
|
when 'text/html'
|
|
99
112
|
return replace_with_html(node, path)
|
|
@@ -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
|
@@ -4,7 +4,9 @@ require 'active_support/core_ext/string'
|
|
|
4
4
|
require 'active_support/core_ext/hash/except'
|
|
5
5
|
require 'active_support/core_ext/module/delegation'
|
|
6
6
|
require 'dry-struct'
|
|
7
|
+
require 'ostruct'
|
|
7
8
|
require 'find'
|
|
9
|
+
require 'forwardable'
|
|
8
10
|
require 'mathml2latex'
|
|
9
11
|
require 'nokogiri'
|
|
10
12
|
require 'pathname'
|
|
@@ -14,8 +16,8 @@ require 'zip'
|
|
|
14
16
|
|
|
15
17
|
module Qti
|
|
16
18
|
class Importer
|
|
17
|
-
attr_reader :package_root
|
|
18
|
-
|
|
19
|
+
attr_reader :package_root, :assessment_id
|
|
20
|
+
|
|
19
21
|
delegate :assessment_identifiers, to: :@manifest
|
|
20
22
|
delegate :question_bank_identifiers, to: :@manifest
|
|
21
23
|
|
|
@@ -39,7 +41,7 @@ module Qti
|
|
|
39
41
|
def self.manifest(path)
|
|
40
42
|
mpath = manifest_path(path)
|
|
41
43
|
package_root = File.dirname(mpath)
|
|
42
|
-
manifest = Qti::Models::Manifest.from_path!(mpath, package_root
|
|
44
|
+
manifest = Qti::Models::Manifest.from_path!(mpath, package_root: package_root)
|
|
43
45
|
[mpath, package_root, manifest]
|
|
44
46
|
end
|
|
45
47
|
|
|
@@ -67,6 +69,14 @@ module Qti
|
|
|
67
69
|
@import.create_question_group(question_group_ref)
|
|
68
70
|
end
|
|
69
71
|
end
|
|
72
|
+
|
|
73
|
+
def self.configuration
|
|
74
|
+
@configuration ||= OpenStruct.new
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def self.configure
|
|
78
|
+
yield(configuration)
|
|
79
|
+
end
|
|
70
80
|
end
|
|
71
81
|
|
|
72
82
|
# The load order of all of these is important.
|
|
@@ -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">
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<item title="Grading - specific - 3 pt score" ident="QUE_1003">
|
|
6
6
|
<presentation>
|
|
7
7
|
<material>
|
|
8
|
-
<mattext texttype="text/html"><![CDATA[If I get a 3, I must have done something wrong. <img align="bottom" alt="image.png" src="org0/images/image.png" border="0"/>]]></mattext>
|
|
8
|
+
<mattext texttype="text/html"><![CDATA[If I get a 3, I must have done something wrong. <img data-equation-content="sample equation" script="alert('bad')" align="bottom" alt="image.png" src="org0/images/image.png" border="0"/> <img script="alert('bad')" align="bottom" alt="image.png" src="org0/images/image.png" border="0"/>]]></mattext>
|
|
9
9
|
</material>
|
|
10
10
|
<response_lid ident="QUE_1004_RL" rcardinality="Single" rtiming="No">
|
|
11
11
|
<render_choice shuffle="Yes">
|
|
@@ -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
|
|
@@ -29,6 +29,18 @@ describe Qti::V1::Models::AssessmentItem do
|
|
|
29
29
|
it 'has sanitized item_body' do
|
|
30
30
|
expect(loaded_class.item_body).to include '<img'
|
|
31
31
|
expect(loaded_class.item_body).to include 'If I get a 3, I must have done something wrong.'
|
|
32
|
+
expect(loaded_class.item_body).not_to include 'script="alert(\'bad\')"'
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it 'transforms canvas math content when conversion is enabled' do
|
|
36
|
+
expect(loaded_class.item_body).to include '\(sample equation\)'
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it 'does not transform math content when conversion is Disabled' do
|
|
40
|
+
Qti.configure do |config|
|
|
41
|
+
config.extract_latex_from_image_tags = false
|
|
42
|
+
end
|
|
43
|
+
expect(loaded_class.item_body).not_to include '"sample equation"'
|
|
32
44
|
end
|
|
33
45
|
|
|
34
46
|
describe '#points_possible' 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,11 @@ end
|
|
|
16
16
|
$LOAD_PATH.unshift File.expand_path('../lib', __dir__)
|
|
17
17
|
require 'qti'
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
Qti.configure do |config|
|
|
20
|
+
config.extract_latex_from_image_tags = true
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
Dir['./spec/support/**/*.rb'].sort.each { |f| require f }
|
|
20
24
|
|
|
21
25
|
RSpec.configure do |config|
|
|
22
26
|
config.expect_with :rspec do |expectations|
|
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.8.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Hannah Bottalla
|
|
@@ -9,7 +9,7 @@ authors:
|
|
|
9
9
|
autorequire:
|
|
10
10
|
bindir: bin
|
|
11
11
|
cert_chain: []
|
|
12
|
-
date:
|
|
12
|
+
date: 2022-01-18 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
|
|
@@ -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
|
|
@@ -678,7 +692,7 @@ 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
|
- - ">="
|
|
@@ -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
|