qti 2 conformance
test plan: * qti migration regressions Change-Id: I53587880a591d9ea70e7039d4216770cb1772880 Reviewed-on: https://gerrit.instructure.com/34407 Reviewed-by: Jeremy Stanley <jeremy@instructure.com> Tested-by: Jenkins <jenkins@instructure.com> QA-Review: Clare Strong <clare@instructure.com> Product-Review: James Williams <jamesw@instructure.com>
This commit is contained in:
parent
463407ee65
commit
12ec269bba
|
@ -124,6 +124,9 @@ module Importers
|
|||
migration.add_imported_item(bank) if migration
|
||||
self.prep_for_import(hash, context, migration)
|
||||
|
||||
missing_links = hash.delete(:missing_links) || {}
|
||||
import_warnings = hash.delete(:import_warnings)
|
||||
|
||||
if id = hash['assessment_question_id']
|
||||
AssessmentQuestion.where(id: id).update_all(name: hash[:question_name], question_data: hash.to_yaml,
|
||||
workflow_state: 'active', created_at: Time.now.utc, updated_at: Time.now.utc,
|
||||
|
@ -137,21 +140,21 @@ module Importers
|
|||
AssessmentQuestion.primary_key, nil, AssessmentQuestion.sequence_name)
|
||||
hash['assessment_question_id'] = id
|
||||
end
|
||||
|
||||
if migration
|
||||
hash[:missing_links].each do |field, missing_links|
|
||||
missing_links.each do |field, links|
|
||||
migration.add_missing_content_links(:class => self.to_s,
|
||||
:id => hash['assessment_question_id'], :field => field, :missing_links => missing_links,
|
||||
:id => hash['assessment_question_id'], :field => field, :missing_links => links,
|
||||
:url => "/#{context.class.to_s.underscore.pluralize}/#{context.id}/question_banks/#{bank.id}#question_#{hash['assessment_question_id']}_question_text")
|
||||
end
|
||||
if hash[:import_warnings]
|
||||
hash[:import_warnings].each do |warning|
|
||||
if import_warnings
|
||||
import_warnings.each do |warning|
|
||||
migration.add_warning(warning, {
|
||||
:fix_issue_html_url => "/#{context.class.to_s.underscore.pluralize}/#{context.id}/question_banks/#{bank.id}#question_#{hash['assessment_question_id']}_question_text"
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
hash.delete(:missing_links)
|
||||
hash
|
||||
end
|
||||
|
||||
|
|
|
@ -44,6 +44,7 @@ module Importers
|
|||
begin
|
||||
Importers::QuizImporter.import_from_migration(assessment, migration.context, migration, question_data, nil, allow_update)
|
||||
rescue
|
||||
debugger
|
||||
migration.add_import_warning(t('#migration.quiz_type', "Quiz"), assessment[:title], $!)
|
||||
end
|
||||
end
|
||||
|
@ -116,9 +117,7 @@ module Importers
|
|||
if question_data
|
||||
hash[:questions] ||= []
|
||||
|
||||
if question_data[:qq_data] || question_data[:aq_data]
|
||||
existing_questions = item.quiz_questions.active.where("migration_id IS NOT NULL").select([:id, :migration_id]).index_by(&:migration_id)
|
||||
end
|
||||
existing_questions = item.quiz_questions.active.where("migration_id IS NOT NULL").select([:id, :migration_id]).index_by(&:migration_id)
|
||||
|
||||
if question_data[:qq_data]
|
||||
question_data[:qq_data].values.each do |q|
|
||||
|
@ -158,10 +157,17 @@ module Importers
|
|||
when "question_group"
|
||||
Importers::QuizGroupImporter.import_from_migration(question, context, item, question_data, i + 1, migration)
|
||||
when "text_only_question"
|
||||
qq = item.quiz_questions.new
|
||||
qq.question_data = question
|
||||
qq.position = i + 1
|
||||
qq.save!
|
||||
if question['migration_id']
|
||||
existing_question = existing_questions[question['migration_id']]
|
||||
question['quiz_question_id'] = existing_question.id if existing_question
|
||||
question['position'] = i + 1
|
||||
Importers::QuizQuestionImporter.import_from_migration(question, context, migration, item)
|
||||
else
|
||||
qq = item.quiz_questions.new
|
||||
qq.question_data = question
|
||||
qq.position = i + 1
|
||||
qq.save!
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -54,7 +54,7 @@ module Qti
|
|||
def self.convert_assessments(manifest_path, opts={})
|
||||
assessments = []
|
||||
doc = Nokogiri::XML(open(manifest_path))
|
||||
doc.css('manifest resources resource[type=imsqti_assessment_xmlv2p1]').each do |item|
|
||||
doc.css('manifest resources resource[type=imsqti_assessment_xmlv2p1], manifest resources resource[type=imsqti_test_xmlv2p1]').each do |item|
|
||||
a = AssessmentTestConverter.new(item, File.dirname(manifest_path), opts).create_instructure_quiz
|
||||
assessments << a if a
|
||||
end
|
||||
|
|
|
@ -58,26 +58,51 @@ class AssessmentItemConverter
|
|||
end
|
||||
end
|
||||
|
||||
EXCLUDED_QUESTION_TEXT_CLASSES = ["RESPONSE_BLOCK", "RIGHT_MATCH_BLOCK"]
|
||||
|
||||
def create_instructure_question
|
||||
begin
|
||||
create_doc
|
||||
@question[:question_name] = @title || get_node_att(@doc, 'assessmentItem', 'title')
|
||||
# The colons are replaced with dashes in the conversion from QTI 1.2
|
||||
@question[:migration_id] = get_node_att(@doc, 'assessmentItem', 'identifier')
|
||||
@question[:migration_id] = @question[:migration_id].gsub(/:/, '-') if @question[:migration_id]
|
||||
@question[:migration_id] = @question[:migration_id].gsub(/:/, '-').gsub('identifier=', '') if @question[:migration_id]
|
||||
|
||||
if @flavor == Qti::Flavors::D2L
|
||||
# In D2L-generated QTI the assessments reference the items by the label instead of the identifier
|
||||
# also, the identifier is not always unique, so we use the label as the migration id
|
||||
@question[:migration_id] = get_node_att(@doc, 'assessmentItem', 'label')
|
||||
end
|
||||
if @doc.at_css('itemBody div.html')
|
||||
@question[:question_text] = ''
|
||||
@doc.css('itemBody > div.html').each_with_index do |text, i|
|
||||
@question[:question_text] += "\n<br/>\n" if i > 0
|
||||
@question[:question_text] += sanitize_html_string(text.text)
|
||||
|
||||
if @type == 'text_entry_interaction'
|
||||
@doc.css('textEntryInteraction').each do |node|
|
||||
node.inner_html = "[#{node['responseIdentifier']}]"
|
||||
end
|
||||
end
|
||||
|
||||
parse_instructure_metadata
|
||||
|
||||
selectors = ['itemBody > div', 'itemBody > p']
|
||||
type = @opts[:custom_type] || @migration_type || @type
|
||||
unless ['fill_in_multiple_blanks_question', 'canvas_matching', 'matching_question',
|
||||
'multiple_dropdowns_question', 'respondus_matching'].include?(type)
|
||||
selectors << 'itemBody > choiceInteraction > prompt'
|
||||
end
|
||||
|
||||
text_nodes = @doc.css(selectors.join(','))
|
||||
text_nodes = text_nodes.reject{|node| node.inner_html.strip.empty? ||
|
||||
EXCLUDED_QUESTION_TEXT_CLASSES.any?{|c| c.casecmp(node['class'].to_s) == 0}}
|
||||
|
||||
if text_nodes.length > 0
|
||||
@question[:question_text] = ''
|
||||
text_nodes.each_with_index do |node, i|
|
||||
@question[:question_text] += "\n<br/>\n" if i > 0
|
||||
if ['html', 'text'].include?(node['class'])
|
||||
@question[:question_text] += sanitize_html_string(node.text)
|
||||
else
|
||||
@question[:question_text] += sanitize_html!(node)
|
||||
end
|
||||
end
|
||||
elsif text = @doc.at_css('itemBody > div.text')
|
||||
@question[:question_text] = sanitize_html!(text)
|
||||
elsif text = @doc.at_css('itemBody div:first-child') || @doc.at_css('itemBody p:first-child') || @doc.at_css('itemBody div') || @doc.at_css('itemBody p')
|
||||
@question[:question_text] = sanitize_html!(text)
|
||||
elsif @doc.at_css('itemBody')
|
||||
|
@ -85,7 +110,6 @@ class AssessmentItemConverter
|
|||
@question[:question_text] = sanitize_html_string(text.text)
|
||||
end
|
||||
end
|
||||
parse_instructure_metadata
|
||||
|
||||
if @migration_type and UNSUPPORTED_TYPES.member?(@migration_type)
|
||||
@question[:question_type] = @migration_type
|
||||
|
@ -109,6 +133,7 @@ class AssessmentItemConverter
|
|||
/matching/i => 'matching_question',
|
||||
'textInformation' => 'text_only_question',
|
||||
'trueFalse' => 'true_false_question',
|
||||
'multiple_dropdowns' => 'multiple_dropdowns_question'
|
||||
}
|
||||
|
||||
def parse_instructure_metadata
|
||||
|
@ -163,17 +188,13 @@ class AssessmentItemConverter
|
|||
end
|
||||
|
||||
def unique_local_id
|
||||
@@ids ||= {}
|
||||
id = rand(10000)
|
||||
while @@ids[id]
|
||||
id = rand(10000)
|
||||
end
|
||||
@@ids[id] = true
|
||||
id
|
||||
@@idx ||= 0
|
||||
@@idx += 1
|
||||
"qti_item_migration_id_#{@identifier}_#{@@idx}"
|
||||
end
|
||||
|
||||
def reset_local_ids
|
||||
@@ids = {}
|
||||
@@idx = 0
|
||||
end
|
||||
|
||||
def get_feedback
|
||||
|
@ -348,7 +369,7 @@ class AssessmentItemConverter
|
|||
guesser = QuestionTypeEducatedGuesser.new(opts)
|
||||
opts[:interaction_type], opts[:custom_type] = guesser.educatedly_guess_type
|
||||
end
|
||||
|
||||
|
||||
case opts[:interaction_type]
|
||||
when /choiceinteraction|multiple_choice_question|multiple_answers_question|true_false_question|stupid_likert_scale_question/i
|
||||
if opts[:custom_type] and opts[:custom_type] == "matching"
|
||||
|
@ -372,6 +393,8 @@ class AssessmentItemConverter
|
|||
q = OrderInteraction.new(opts)
|
||||
when /fill_in_multiple_blanks_question|multiple_dropdowns_question/i
|
||||
q = FillInTheBlank.new(opts)
|
||||
when /textentryinteraction/i
|
||||
q = FillInTheBlank.new(opts)
|
||||
when nil
|
||||
q = AssessmentItemConverter.new(opts)
|
||||
else
|
||||
|
|
|
@ -89,7 +89,7 @@ class AssessmentTestConverter
|
|||
if limit = doc.at_css('timeLimits')
|
||||
@quiz[:time_limit] = AssessmentTestConverter.parse_time_limit(limit['maxTime'])
|
||||
end
|
||||
if part = doc.at_css('testPart[identifier=BaseTestPart]')
|
||||
if part = doc.at_css('testPart[identifier=BaseTestPart]') || doc.at_css('testPart')
|
||||
if control = part.at_css('itemSessionControl')
|
||||
if max = control['maxAttempts']
|
||||
max = -1 if max =~ /unlimited/i
|
||||
|
@ -153,14 +153,14 @@ class AssessmentTestConverter
|
|||
if val = get_bool_val(section, 'sourcebank_is_external')
|
||||
group[:question_bank_is_external] = val
|
||||
end
|
||||
group[:migration_id] = section['identifier'] && section['identifier'] != "" ? section['identifier'] : rand(100_000)
|
||||
group[:migration_id] = section['identifier'] && section['identifier'] != "" ? section['identifier'] : unique_local_id
|
||||
questions_list = group[:questions]
|
||||
end
|
||||
end
|
||||
if section['visible'] and section['visible'] =~ /true/i
|
||||
if title = section['title']
|
||||
#Create an empty question with a title in it
|
||||
@quiz[:questions] << {:question_type => 'text_only_question', :question_text => title, :migration_id => rand(100_000)}
|
||||
@quiz[:questions] << {:question_type => 'text_only_question', :question_text => title, :migration_id => unique_local_id}
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -222,5 +222,10 @@ class AssessmentTestConverter
|
|||
weight
|
||||
end
|
||||
|
||||
def unique_local_id
|
||||
@@idx ||= 0
|
||||
@@idx += 1
|
||||
"qti_test_migration_id_#{@identifier}_#{@@idx}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -13,6 +13,8 @@ class Converter < Canvas::Migration::Migrator
|
|||
QTI_2_NAMESPACES = %w[
|
||||
http://www.imsglobal.org/xsd/imsqti_v2p0
|
||||
http://www.imsglobal.org/xsd/imsqti_v2p1
|
||||
http://www.imsglobal.org/xsd/qti/qtiv2p0
|
||||
http://www.imsglobal.org/xsd/qti/qtiv2p1
|
||||
]
|
||||
IMS_MD = "http://www.imsglobal.org/xsd/imsmd_v1p2"
|
||||
QTI_2_OUTPUT_PATH = "qti_2_1"
|
||||
|
@ -63,7 +65,11 @@ class Converter < Canvas::Migration::Migrator
|
|||
def self.is_qti_2(manifest_path)
|
||||
if File.exists?(manifest_path)
|
||||
xml = Nokogiri::XML(File.open(manifest_path))
|
||||
return xml.namespaces.values.any? { |v| QTI_2_NAMESPACES.include?(v) }
|
||||
if xml.namespaces.values.any? { |v| QTI_2_NAMESPACES.include?(v) }
|
||||
return true
|
||||
elsif (xml.at_css('metadata schema') ? xml.at_css('metadata schema').text : '') =~ /QTIv2\./i
|
||||
return true
|
||||
end
|
||||
end
|
||||
false
|
||||
end
|
||||
|
|
|
@ -16,6 +16,8 @@ class FillInTheBlank < AssessmentItemConverter
|
|||
process_angel
|
||||
elsif @type == 'fillinmultiple'
|
||||
process_respondus
|
||||
elsif @type == 'text_entry_interaction'
|
||||
process_text_entry
|
||||
elsif @doc.at_css('itemBody extendedTextInteraction')
|
||||
process_d2l
|
||||
else
|
||||
|
@ -130,5 +132,19 @@ class FillInTheBlank < AssessmentItemConverter
|
|||
end
|
||||
end
|
||||
|
||||
def process_text_entry
|
||||
@doc.css('responseDeclaration').each do |res_node|
|
||||
res_id = res_node['identifier']
|
||||
res_node.css('correctResponse value').each do |correct_id|
|
||||
answer = {}
|
||||
answer[:id] = unique_local_id
|
||||
answer[:weight] = DEFAULT_CORRECT_WEIGHT
|
||||
answer[:text] = correct_id.text
|
||||
answer[:blank_id] = res_id
|
||||
@question[:answers] << answer
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
|
@ -18,9 +18,15 @@ class QuestionTypeEducatedGuesser < AssessmentItemConverter
|
|||
if @doc.at_css('associateInteraction')
|
||||
return ['associateinteraction', 'matching']
|
||||
end
|
||||
if@doc.at_css('extendedTextInteraction')
|
||||
if @doc.at_css('extendedTextInteraction')
|
||||
return ['extendedTextInteraction', nil]
|
||||
end
|
||||
if @doc.at_css('textEntryInteraction')
|
||||
return ['textEntryInteraction', 'text_entry_interaction']
|
||||
end
|
||||
if @doc.at_css('matchInteraction')
|
||||
return ['matchInteraction', 'matching']
|
||||
end
|
||||
rescue => e
|
||||
message = "There was an error educatedly guessing the type for an assessment question"
|
||||
@question[:qti_error] = "#{message} - #{e.to_s}"
|
||||
|
|
BIN
vendor/plugins/qti_exporter/spec_canvas/fixtures/qti2_conformance/VE_IP_01.zip
vendored
Executable file
BIN
vendor/plugins/qti_exporter/spec_canvas/fixtures/qti2_conformance/VE_IP_01.zip
vendored
Executable file
Binary file not shown.
BIN
vendor/plugins/qti_exporter/spec_canvas/fixtures/qti2_conformance/VE_IP_02.zip
vendored
Executable file
BIN
vendor/plugins/qti_exporter/spec_canvas/fixtures/qti2_conformance/VE_IP_02.zip
vendored
Executable file
Binary file not shown.
BIN
vendor/plugins/qti_exporter/spec_canvas/fixtures/qti2_conformance/VE_IP_03.zip
vendored
Executable file
BIN
vendor/plugins/qti_exporter/spec_canvas/fixtures/qti2_conformance/VE_IP_03.zip
vendored
Executable file
Binary file not shown.
BIN
vendor/plugins/qti_exporter/spec_canvas/fixtures/qti2_conformance/VE_IP_04.zip
vendored
Executable file
BIN
vendor/plugins/qti_exporter/spec_canvas/fixtures/qti2_conformance/VE_IP_04.zip
vendored
Executable file
Binary file not shown.
BIN
vendor/plugins/qti_exporter/spec_canvas/fixtures/qti2_conformance/VE_IP_05.zip
vendored
Executable file
BIN
vendor/plugins/qti_exporter/spec_canvas/fixtures/qti2_conformance/VE_IP_05.zip
vendored
Executable file
Binary file not shown.
BIN
vendor/plugins/qti_exporter/spec_canvas/fixtures/qti2_conformance/VE_IP_06.zip
vendored
Executable file
BIN
vendor/plugins/qti_exporter/spec_canvas/fixtures/qti2_conformance/VE_IP_06.zip
vendored
Executable file
Binary file not shown.
BIN
vendor/plugins/qti_exporter/spec_canvas/fixtures/qti2_conformance/VE_IP_07.zip
vendored
Executable file
BIN
vendor/plugins/qti_exporter/spec_canvas/fixtures/qti2_conformance/VE_IP_07.zip
vendored
Executable file
Binary file not shown.
BIN
vendor/plugins/qti_exporter/spec_canvas/fixtures/qti2_conformance/VE_IP_11.zip
vendored
Executable file
BIN
vendor/plugins/qti_exporter/spec_canvas/fixtures/qti2_conformance/VE_IP_11.zip
vendored
Executable file
Binary file not shown.
BIN
vendor/plugins/qti_exporter/spec_canvas/fixtures/qti2_conformance/VE_TP_01.zip
vendored
Executable file
BIN
vendor/plugins/qti_exporter/spec_canvas/fixtures/qti2_conformance/VE_TP_01.zip
vendored
Executable file
Binary file not shown.
BIN
vendor/plugins/qti_exporter/spec_canvas/fixtures/qti2_conformance/VE_TP_02.zip
vendored
Executable file
BIN
vendor/plugins/qti_exporter/spec_canvas/fixtures/qti2_conformance/VE_TP_02.zip
vendored
Executable file
Binary file not shown.
BIN
vendor/plugins/qti_exporter/spec_canvas/fixtures/qti2_conformance/VE_TP_03.zip
vendored
Executable file
BIN
vendor/plugins/qti_exporter/spec_canvas/fixtures/qti2_conformance/VE_TP_03.zip
vendored
Executable file
Binary file not shown.
BIN
vendor/plugins/qti_exporter/spec_canvas/fixtures/qti2_conformance/VE_TP_04.zip
vendored
Executable file
BIN
vendor/plugins/qti_exporter/spec_canvas/fixtures/qti2_conformance/VE_TP_04.zip
vendored
Executable file
Binary file not shown.
BIN
vendor/plugins/qti_exporter/spec_canvas/fixtures/qti2_conformance/VE_TP_05.zip
vendored
Executable file
BIN
vendor/plugins/qti_exporter/spec_canvas/fixtures/qti2_conformance/VE_TP_05.zip
vendored
Executable file
Binary file not shown.
BIN
vendor/plugins/qti_exporter/spec_canvas/fixtures/qti2_conformance/VE_TP_06.zip
vendored
Executable file
BIN
vendor/plugins/qti_exporter/spec_canvas/fixtures/qti2_conformance/VE_TP_06.zip
vendored
Executable file
Binary file not shown.
|
@ -167,10 +167,10 @@ module CanvasExpected
|
|||
:question_name=>"Oi!",
|
||||
:points_possible=>10.3,
|
||||
:migration_id=>"if87ef626591c52375b6a4f16cdab8bd0",
|
||||
:question_text=>"Ole"}
|
||||
:question_text=>"Ole\n<br/>\n<a>Test Page</a>\n<br/>\nWhy would you link to a wiki page from a quiz question? That doesn't seem right."}
|
||||
|
||||
TRUE_FALSE = {:points_possible=>10,
|
||||
:question_text=>"Generating QTI is \n<strong>super</strong> awesome!",
|
||||
:question_text=>"Generating QTI is \n<strong>super</strong> awesome!\n<br/>\noh, and &amp;",
|
||||
:answers=>
|
||||
[{:weight=>0, :migration_id=>"RESPONSE_5309", :text=>"True"},
|
||||
{:comments=>"You're an idiot.", :weight=>100, :migration_id=>"RESPONSE_239",:text=>"False"}],
|
||||
|
|
287
vendor/plugins/qti_exporter/spec_canvas/lib/qti/qti_2_1_conformance_spec.rb
vendored
Normal file
287
vendor/plugins/qti_exporter/spec_canvas/lib/qti/qti_2_1_conformance_spec.rb
vendored
Normal file
|
@ -0,0 +1,287 @@
|
|||
require File.expand_path(File.dirname(__FILE__) + '/../../qti_helper')
|
||||
if Qti.migration_executable
|
||||
|
||||
# QTI conformancec packages from http://www.imsglobal.org/developers/apipalliance/conformance/QTIconformanceresources.cfm
|
||||
describe "QTI 2.1 zip" do
|
||||
def import_fixture(filename)
|
||||
archive_file_path = File.join(BASE_FIXTURE_DIR, 'qti2_conformance', filename)
|
||||
unzipped_file_path = File.join(File.dirname(archive_file_path), "qti_#{File.basename(archive_file_path, '.zip')}", 'oi')
|
||||
@export_folder = File.join(File.dirname(archive_file_path), "qti_#{filename}".gsub('.zip', ''))
|
||||
@course = Course.create!(:name => filename)
|
||||
@migration = ContentMigration.create(:context => @course)
|
||||
|
||||
@converter = Qti::Converter.new(:export_archive_path => archive_file_path, :base_download_dir => unzipped_file_path, :content_migration => @migration)
|
||||
@converter.export
|
||||
@course_data = @converter.course.with_indifferent_access
|
||||
@course_data['all_files_export'] ||= {}
|
||||
@course_data['all_files_export']['file_path'] = @course_data['all_files_zip']
|
||||
|
||||
@migration.migration_settings[:migration_ids_to_import] = {:copy => {}}
|
||||
@migration.migration_settings[:files_import_root_path] = @course_data[:files_import_root_path]
|
||||
Importers::CourseContentImporter.import_content(@course, @course_data, nil, @migration)
|
||||
|
||||
@migration.migration_issues.should be_empty
|
||||
end
|
||||
|
||||
after :each do
|
||||
if @export_folder && File.exists?(@export_folder)
|
||||
FileUtils::rm_rf(@export_folder)
|
||||
end
|
||||
end
|
||||
|
||||
it "should import VE_IP_01" do
|
||||
import_fixture('VE_IP_01.zip')
|
||||
@course.quizzes.count.should == 0
|
||||
@course.assessment_questions.count.should == 1
|
||||
q = @course.assessment_questions.first
|
||||
q.name.should == "QTI v2.1 Entry Profile Single T/F Item Test Instance"
|
||||
|
||||
q.question_data['question_text'].should include("Answer the following question.")
|
||||
q.question_data['question_text'].should include("Sigmund Freud and Carl Jung both belong to the psychoanalytic school of psychology.")
|
||||
|
||||
q.question_data['question_type'].should == 'multiple_choice_question'
|
||||
q.question_data['answers'].count.should == 2
|
||||
answers = q.question_data['answers'].sort_by{|h| h['migration_id']}
|
||||
answers.map{|a| a['text']}.sort.should == ['False', 'True']
|
||||
answers.map{|a| a['weight']}.sort.should == [0, 100]
|
||||
end
|
||||
|
||||
it "should import VE_IP_02" do
|
||||
import_fixture('VE_IP_02.zip')
|
||||
|
||||
@course.quizzes.count.should == 0
|
||||
@course.assessment_questions.count.should == 1
|
||||
q = @course.assessment_questions.first
|
||||
q.attachments.count.should == 1
|
||||
att = q.attachments.first
|
||||
|
||||
q.name.should == "QTI v2.1 Entry Profile Single MC/SR Item Test Instance"
|
||||
|
||||
["<img id=\"figure1\" height=\"165\" width=\"250\" src=\"/assessment_questions/#{q.id}/files/#{att.id}/download?verifier=#{att.uuid}\" alt=\"Figure showing Rectangle ABCD divided into 12 equal boxes. 4 of the boxes are shaded.\">",
|
||||
"<span id=\"labelA\">A</span>", "<span id=\"labelB\">B</span>", "<span id=\"labelC\">C</span>", "<span id=\"labelD\">D</span>",
|
||||
"In the figure above, what fraction of the rectangle <em>ABCD</em> is", "shaded?"
|
||||
].each do |text|
|
||||
q.question_data['question_text'].should include(text)
|
||||
end
|
||||
|
||||
q.question_data['question_type'].should == 'multiple_choice_question'
|
||||
answers = q.question_data['answers'].sort_by{|h| h['migration_id']}
|
||||
answers.count.should == 5
|
||||
answers.map{|h| h['weight']}.should == [0, 0, 0, 100, 0]
|
||||
end
|
||||
|
||||
it "should import VE_IP_03" do
|
||||
import_fixture('VE_IP_03.zip')
|
||||
@course.quizzes.count.should == 0
|
||||
@course.assessment_questions.count.should == 1
|
||||
q = @course.assessment_questions.first
|
||||
|
||||
q.name.should == "QTI v2.1 Entry Profile Single MC/MR Item Test Instance"
|
||||
q.question_data['question_text'].split("\n").map(&:strip).should == [
|
||||
"<span id=\"a\">Ms. Smith's class contains 24 students. </span>",
|
||||
"<span id=\"b\">Each student voted for his or her favorite color. </span>",
|
||||
"<span id=\"c\">The result of the class vote is shown </span>",
|
||||
"<span id=\"z\">in the table below.</span>", "<br>",
|
||||
"Indicate which of the following statements are accurate."
|
||||
]
|
||||
|
||||
|
||||
q.question_data['question_type'].should == 'multiple_answers_question'
|
||||
answers = q.question_data['answers'].sort_by{|h| h['migration_id']}
|
||||
answers.count.should == 5
|
||||
answers.map{|h| h['weight']}.should == [100, 100, 0, 100, 0]
|
||||
answers.map{|h| h['text']}.should == [
|
||||
"The majority of students voted for Red.",
|
||||
"Twice as many students voted for Red a voted for Blue.",
|
||||
"Two percent of students voted for Yellow.",
|
||||
"Red received more votes than any other color.",
|
||||
"Twenty-five percent of students voted for Green."
|
||||
]
|
||||
end
|
||||
|
||||
it "should import VE_IP_04" do
|
||||
import_fixture('VE_IP_04.zip')
|
||||
@course.quizzes.count.should == 0
|
||||
@course.assessment_questions.count.should == 1
|
||||
q = @course.assessment_questions.first
|
||||
|
||||
q.name.should == "QTI v2.1 Entry Profile Single FIB Item Test Instance"
|
||||
q.question_data['question_text'].should include("Canada and the United States share 4 out of the 5 Great Lakes in central North America.")
|
||||
q.question_data['question_text'].should include("Which lake is entirely within the boundaries of the United States?")
|
||||
q.question_data['question_text'].should include("Type your answer here: [RESPONSE]")
|
||||
|
||||
q.question_data['question_type'].should == 'fill_in_multiple_blanks_question'
|
||||
q.question_data['answers'].count.should == 1
|
||||
answer = q.question_data['answers'].first
|
||||
answer['weight'].should == 100
|
||||
answer['text'].should == 'Lake Michigan'
|
||||
answer['blank_id'].should == 'RESPONSE'
|
||||
end
|
||||
|
||||
it "should import VE_IP_05" do
|
||||
import_fixture('VE_IP_05.zip')
|
||||
@course.quizzes.count.should == 0
|
||||
@course.assessment_questions.count.should == 1
|
||||
q = @course.assessment_questions.first
|
||||
|
||||
q.name.should == "QTI v2.1 Entry Profile Single Essay Item Test Instance"
|
||||
|
||||
q.attachments.count.should == 3
|
||||
q.attachments.each do |att|
|
||||
q.question_data['question_text'].should include("src=\"/assessment_questions/#{q.id}/files/#{att.id}/download?verifier=#{att.uuid}\"")
|
||||
end
|
||||
|
||||
q.question_data['question_type'].should == 'essay_question'
|
||||
q.question_data['answers'].count.should == 0
|
||||
end
|
||||
|
||||
it "should import VE_IP_06" do
|
||||
pending('hotspot questions')
|
||||
import_fixture('VE_IP_06.zip')
|
||||
end
|
||||
|
||||
it "should import VE_IP_07" do
|
||||
import_fixture('VE_IP_07.zip')
|
||||
@course.quizzes.count.should == 0
|
||||
@course.assessment_questions.count.should == 1
|
||||
q = @course.assessment_questions.first
|
||||
|
||||
q.name.should == "QTI v2.1 Core Profile Single Pattern Match Item Test Instance"
|
||||
q.question_data['question_text'].split("\n").map(&:strip).select{|s| s.length > 0}.should == [
|
||||
"Match the following characters to the Shakespeare play they appeared in:",
|
||||
"Capulet", "Demetrius", "Lysander", "Prospero",
|
||||
"A Midsummer-Night's Dream", "Romeo and Juliet", "The Tempest"
|
||||
]
|
||||
|
||||
q.question_data['question_type'].should == 'matching_question'
|
||||
answers = q.question_data['answers'].sort_by{|h| h['text']}
|
||||
answers.count.should == 4
|
||||
matches = q.question_data['matches']
|
||||
matches.count.should == 3
|
||||
|
||||
answers.map{|h| h['text']}.should == ["Capulet", "Demetrius", "Lysander", "Prospero"]
|
||||
answers.map{|h| h['right']}.should == [
|
||||
"Romeo and Juliet",
|
||||
"A Midsummer-Night's Dream",
|
||||
"A Midsummer-Night's Dream",
|
||||
"The Tempest"
|
||||
]
|
||||
|
||||
answers.each do |h|
|
||||
match = matches.detect{|m| m['match_id'] == h['match_id']}
|
||||
match['text'].should == h['right']
|
||||
end
|
||||
end
|
||||
|
||||
it "should import VE_IP_11" do
|
||||
import_fixture('VE_IP_11.zip')
|
||||
@course.assessment_questions.count.should == 5
|
||||
@course.assessment_questions.map{|q| q.question_data['question_type']}.sort.should == [
|
||||
"essay_question",
|
||||
"fill_in_multiple_blanks_question",
|
||||
"multiple_answers_question",
|
||||
"multiple_choice_question",
|
||||
"multiple_choice_question"
|
||||
]
|
||||
end
|
||||
|
||||
it "should import VE_TP_01" do
|
||||
import_fixture('VE_TP_01.zip')
|
||||
@course.assessment_questions.count.should == 1
|
||||
@course.quizzes.count.should == 1
|
||||
quiz = @course.quizzes.first
|
||||
quiz.quiz_questions.count.should == 2
|
||||
|
||||
header = quiz.quiz_questions.detect{|q| q.position == 1}
|
||||
header.question_data['question_type'].should == 'text_only_question'
|
||||
header.question_data['question_text'].should == "QTI v2.1 Entry Profile Single Section Instance"
|
||||
|
||||
question = quiz.quiz_questions.detect{|q| q.position == 2}
|
||||
question.question_data['question_type'].should == 'multiple_choice_question'
|
||||
question.assessment_question_id.should == @course.assessment_questions.first.id
|
||||
end
|
||||
|
||||
it "should import VE_TP_02" do
|
||||
import_fixture('VE_TP_02.zip')
|
||||
@course.assessment_questions.count.should == 1
|
||||
@course.quizzes.count.should == 1
|
||||
quiz = @course.quizzes.first
|
||||
quiz.quiz_questions.count.should == 2
|
||||
|
||||
header = quiz.quiz_questions.detect{|q| q.position == 1}
|
||||
header.question_data['question_type'].should == 'text_only_question'
|
||||
header.question_data['question_text'].should == "QTI v2.1 Entry Profile Single Section Instance"
|
||||
|
||||
question = quiz.quiz_questions.detect{|q| q.position == 2}
|
||||
question.question_data['question_type'].should == 'multiple_choice_question'
|
||||
question.assessment_question_id.should == @course.assessment_questions.first.id
|
||||
end
|
||||
|
||||
it "should import VE_TP_03" do
|
||||
import_fixture('VE_TP_03.zip')
|
||||
@course.assessment_questions.count.should == 1
|
||||
@course.quizzes.count.should == 1
|
||||
quiz = @course.quizzes.first
|
||||
quiz.quiz_questions.count.should == 2
|
||||
|
||||
header = quiz.quiz_questions.detect{|q| q.position == 1}
|
||||
header.question_data['question_type'].should == 'text_only_question'
|
||||
header.question_data['question_text'].should == "QTI v2.1 Entry Profile Single Section Instance"
|
||||
|
||||
question = quiz.quiz_questions.detect{|q| q.position == 2}
|
||||
question.question_data['question_type'].should == 'multiple_answers_question'
|
||||
question.assessment_question_id.should == @course.assessment_questions.first.id
|
||||
end
|
||||
|
||||
it "should import VE_TP_04" do
|
||||
import_fixture('VE_TP_04.zip')
|
||||
@course.assessment_questions.count.should == 1
|
||||
@course.quizzes.count.should == 1
|
||||
quiz = @course.quizzes.first
|
||||
quiz.quiz_questions.count.should == 2
|
||||
|
||||
header = quiz.quiz_questions.detect{|q| q.position == 1}
|
||||
header.question_data['question_type'].should == 'text_only_question'
|
||||
header.question_data['question_text'].should == "QTI v2.1 Entry Profile Single Section Instance"
|
||||
|
||||
question = quiz.quiz_questions.detect{|q| q.position == 2}
|
||||
question.question_data['question_type'].should == 'fill_in_multiple_blanks_question'
|
||||
question.assessment_question_id.should == @course.assessment_questions.first.id
|
||||
end
|
||||
|
||||
it "should import VE_TP_05" do
|
||||
import_fixture('VE_TP_05.zip')
|
||||
@course.assessment_questions.count.should == 1
|
||||
@course.quizzes.count.should == 1
|
||||
quiz = @course.quizzes.first
|
||||
quiz.quiz_questions.count.should == 2
|
||||
|
||||
header = quiz.quiz_questions.detect{|q| q.position == 1}
|
||||
header.question_data['question_type'].should == 'text_only_question'
|
||||
header.question_data['question_text'].should == "QTI v2.1 Entry Profile Single Section Instance"
|
||||
|
||||
question = quiz.quiz_questions.detect{|q| q.position == 2}
|
||||
question.question_data['question_type'].should == 'essay_question'
|
||||
question.assessment_question_id.should == @course.assessment_questions.first.id
|
||||
end
|
||||
|
||||
it "should import VE_TP_06" do
|
||||
import_fixture('VE_TP_06.zip')
|
||||
@course.assessment_questions.count.should == 5
|
||||
@course.quizzes.count.should == 1
|
||||
quiz = @course.quizzes.first
|
||||
quiz.quiz_questions.count.should == 6
|
||||
|
||||
header = quiz.quiz_questions.detect{|q| q.position == 1}
|
||||
header.question_data['question_text'].should == "QTI v2.1 Entry Profile Single Section Instance with Multiple Items"
|
||||
|
||||
questions = quiz.quiz_questions.sort_by(&:position)
|
||||
questions.map{|q| q.question_data['question_type']}.should == [
|
||||
"text_only_question", "multiple_choice_question", "multiple_choice_question",
|
||||
"multiple_answers_question", "fill_in_multiple_blanks_question", "essay_question"
|
||||
]
|
||||
questions.select{|q| q.position > 1}.map(&:assessment_question_id).sort.should == @course.assessment_questions.map(&:id).sort
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -5,7 +5,7 @@ describe "Converting QTI items" do
|
|||
file_path = File.join(BASE_FIXTURE_DIR, 'qti')
|
||||
question = get_question_hash(file_path, 'zero_point_mc')
|
||||
|
||||
question[:question_text].should == 'MC - multiple correct with multiple selection. C and D are correct'
|
||||
question[:question_text].should == "<div class=\"text\"></div>\n<br/>\nMC - multiple correct with multiple selection. C and D are correct"
|
||||
end
|
||||
|
||||
it "should sanitize InstructureMetadata" do
|
||||
|
|
Loading…
Reference in New Issue