qti 2.5.1 → 2.8.0

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