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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/lib/qti/exporter.rb +5 -7
  4. data/lib/qti/models/base.rb +6 -2
  5. data/lib/qti/models/manifest.rb +3 -2
  6. data/lib/qti/models/resource.rb +6 -3
  7. data/lib/qti/sanitizer.rb +14 -1
  8. data/lib/qti/v1/models/assessment.rb +4 -0
  9. data/lib/qti/v1/models/assessment_item.rb +2 -2
  10. data/lib/qti/v1/models/interactions/base_fill_blank_interaction.rb +1 -1
  11. data/lib/qti/v1/models/interactions.rb +1 -2
  12. data/lib/qti/v1/models/numerics/exact_match.rb +0 -4
  13. data/lib/qti/v1/models/numerics/margin_error.rb +0 -4
  14. data/lib/qti/v1/models/numerics/precision.rb +0 -4
  15. data/lib/qti/v1/models/numerics/scoring_data.rb +1 -0
  16. data/lib/qti/v1/models/numerics/within_range.rb +0 -4
  17. data/lib/qti/v1/models/object_bank.rb +11 -0
  18. data/lib/qti/v1/models/question_group.rb +8 -0
  19. data/lib/qti/v2/models/assessment_test.rb +6 -1
  20. data/lib/qti/v2/models/interactions/match_interaction.rb +1 -1
  21. data/lib/qti/version.rb +1 -1
  22. data/lib/qti.rb +13 -3
  23. data/spec/fixtures/items_1.2/question_group.xml +2 -0
  24. data/spec/fixtures/items_1.2/true_false.xml +1 -1
  25. data/spec/gemfiles/{rails-5.1.gemfile → rails-6.1.gemfile} +1 -1
  26. data/spec/lib/qti/models/base_spec.rb +2 -2
  27. data/spec/lib/qti/v1/models/assessment_item_spec.rb +12 -0
  28. data/spec/lib/qti/v1/models/assessment_spec.rb +9 -0
  29. data/spec/lib/qti/v1/models/interactions/base_fill_blank_interaction_spec.rb +25 -0
  30. data/spec/lib/qti/v1/models/object_bank_spec.rb +37 -13
  31. data/spec/lib/qti/v1/models/question_group_spec.rb +2 -0
  32. data/spec/lib/qti/v2/models/assessment_item_spec.rb +2 -2
  33. data/spec/lib/qti/v2/models/assessment_test_spec.rb +6 -2
  34. data/spec/lib/qti/v2/models/stimulus_item_spec.rb +4 -4
  35. data/spec/lib/qti_spec.rb +3 -3
  36. data/spec/spec_helper.rb +6 -2
  37. metadata +33 -19
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 80bd134de64054ac80ae1f3293bfc25cac5f1f0801f82767134759fafa1e50fa
4
- data.tar.gz: '008ddea00fb490d5f58266ded09d4ce64bb8578a7ea6c2d48017f6084a094d3e'
3
+ metadata.gz: e121df522dfdc1e21c2944bfa04334fdb4226c8a288356ff4ad1ef84e7354a86
4
+ data.tar.gz: 01cea5632a13762bc2929b4f61cafbc47893c38fda88c1d14a5d51a5eb1b73aa
5
5
  SHA512:
6
- metadata.gz: 0be90cee407e5119935bc98e9ba32c5a6202ac678fdd6d19e344f5a7133253cfb76b806e43d1362861d1766f54fb381fba3d2c0f8ea73040c38e17fabac16d07
7
- data.tar.gz: ef3d1d4d84c6b1d9546b39d2326b2be9887ea4659a36c898b6f8120bce6d39e75b79007b8c9510c014ff60f5d0256f73d75318cc0e8d93d6de8f9a4cb44fb8f4
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 /bin/bash -l -c \
84
- "BUNDLE_GEMFILE=spec/gemfiles/rails-6.0.gemfile rvm-exec 2.5 rspec"
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)) + '.zip'
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
- begin
171
- entry = file.sub(package_root_path + '/', '')
172
- zipfile.add(entry, file)
173
- rescue Zip::EntryExistsError
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
@@ -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 = nil, resource = nil)
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
- @package_root = Pathname.new(@package_root).cleanpath.to_s + '/'
88
+
89
+ @package_root = "#{Pathname.new(@package_root).cleanpath}/"
86
90
  end
87
91
 
88
92
  def relative_path
@@ -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, rsc)
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
@@ -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 = true)
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
- Qti::V1::Models::ObjectBank.from_path!(rsc_path, @package_root, rsc) unless doc.search('objectbank').empty?
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.class == Qti::V1::Models::ObjectBank
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
@@ -2,8 +2,8 @@ module Qti
2
2
  module V1
3
3
  module Models
4
4
  class AssessmentItem < Qti::V1::Models::Base
5
- attr_reader :doc
6
- attr_reader :resource
5
+ attr_reader :doc, :resource
6
+
7
7
  delegate :metadata, to: :@resource
8
8
 
9
9
  def initialize(item, package_root = nil, resource = nil)
@@ -3,7 +3,7 @@ module Qti
3
3
  module Models
4
4
  module Interactions
5
5
  class BaseFillBlankInteraction < BaseInteraction
6
- CANVAS_REGEX ||= /(\[.+?\])/.freeze
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
- matches = classlist.each_with_object([]) do |interaction_class, result|
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
@@ -3,10 +3,6 @@ module Qti
3
3
  module Models
4
4
  module Numerics
5
5
  class ExactMatch < ScoringBase
6
- def initialize(scoring_node)
7
- super(scoring_node)
8
- end
9
-
10
6
  def scoring_data
11
7
  return unless valid?
12
8
  Struct.new(
@@ -3,10 +3,6 @@ module Qti
3
3
  module Models
4
4
  module Numerics
5
5
  class MarginError < ScoringBase
6
- def initialize(scoring_node)
7
- super(scoring_node)
8
- end
9
-
10
6
  def scoring_data
11
7
  return unless valid?
12
8
  Struct.new(
@@ -5,10 +5,6 @@ module Qti
5
5
  class Precision < ScoringBase
6
6
  include ActionView::Helpers::NumberHelper
7
7
 
8
- def initialize(scoring_node)
9
- super(scoring_node)
10
- end
11
-
12
8
  def scoring_data
13
9
  return unless equal_node && gt_node && lte_node
14
10
  Struct.new(
@@ -4,6 +4,7 @@ module Qti
4
4
  module Numerics
5
5
  class ScoringData
6
6
  class UnsupportedNumreicType < StandardError; end
7
+
7
8
  def initialize(node)
8
9
  @scoring_node = ScoringNode.new(node)
9
10
  end
@@ -3,10 +3,6 @@ module Qti
3
3
  module Models
4
4
  module Numerics
5
5
  class WithinRange < ScoringBase
6
- def initialize(scoring_node)
7
- super(scoring_node)
8
- end
9
-
10
6
  def scoring_data
11
7
  return unless valid?
12
8
  Struct.new(
@@ -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, ref[:resource])
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
@@ -6,7 +6,7 @@ module Qti
6
6
  module Models
7
7
  module Interactions
8
8
  class MatchInteraction < BaseInteraction
9
- extend Forwardable
9
+ extend ::Forwardable
10
10
 
11
11
  attr_reader :implementation
12
12
 
data/lib/qti/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Qti
2
- VERSION = '2.5.1'.freeze
2
+ VERSION = '2.8.0'.freeze
3
3
  end
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
- attr_reader :assessment_id
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 = 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">
@@ -2,4 +2,4 @@ source 'https://rubygems.org'
2
2
 
3
3
  gemspec path: '../../'
4
4
 
5
- gem 'rails', '~> 5.1.4'
5
+ gem 'rails', '~> 6.1.0'
@@ -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(<<~XML)
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 + '/quiz.xml'
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\.6/ =~ RUBY_VERSION && /rails-6\.0/ =~ ENV['BUNDLE_GEMFILE']
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
- Dir['./spec/support/**/*.rb'].each { |f| require f }
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.5.1
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: 2021-05-25 00:00:00.000000000 Z
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.1.7
20
+ version: '5.2'
21
21
  - - "<"
22
22
  - !ruby/object:Gem::Version
23
- version: '6.1'
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.1.7
30
+ version: '5.2'
31
31
  - - "<"
32
32
  - !ruby/object:Gem::Version
33
- version: '6.1'
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.1.7
40
+ version: '5.2'
41
41
  - - "<"
42
42
  - !ruby/object:Gem::Version
43
- version: '6.1'
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.1.7
50
+ version: '5.2'
51
51
  - - "<"
52
52
  - !ruby/object:Gem::Version
53
- version: '6.1'
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.17'
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.17'
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: '12.3'
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: '12.3'
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: 0.74.0
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: 0.74.0
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.4'
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