ams_migration 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE.txt +9 -0
- data/README.md +33 -0
- data/lib/ams_migration/canvas/converter/classic_quiz/calculated.rb +33 -0
- data/lib/ams_migration/canvas/converter/classic_quiz/classic_question.rb +114 -0
- data/lib/ams_migration/canvas/converter/classic_quiz/essay.rb +36 -0
- data/lib/ams_migration/canvas/converter/classic_quiz/file_upload.rb +35 -0
- data/lib/ams_migration/canvas/converter/classic_quiz/fill_in_blank.rb +63 -0
- data/lib/ams_migration/canvas/converter/classic_quiz/fill_in_multiple_blanks.rb +111 -0
- data/lib/ams_migration/canvas/converter/classic_quiz/matching.rb +66 -0
- data/lib/ams_migration/canvas/converter/classic_quiz/multiple_answer.rb +27 -0
- data/lib/ams_migration/canvas/converter/classic_quiz/multiple_choice.rb +57 -0
- data/lib/ams_migration/canvas/converter/classic_quiz/multiple_dropdown.rb +126 -0
- data/lib/ams_migration/canvas/converter/classic_quiz/numerical.rb +89 -0
- data/lib/ams_migration/canvas/converter/classic_quiz/text.rb +28 -0
- data/lib/ams_migration/canvas/converter/classic_quiz/true_false.rb +14 -0
- data/lib/ams_migration/canvas/converter/classic_quiz/unknown.rb +31 -0
- data/lib/ams_migration/canvas/converter/new_quiz/categorization.rb +108 -0
- data/lib/ams_migration/canvas/converter/new_quiz/essay.rb +78 -0
- data/lib/ams_migration/canvas/converter/new_quiz/file_upload.rb +33 -0
- data/lib/ams_migration/canvas/converter/new_quiz/fill_in_blank.rb +180 -0
- data/lib/ams_migration/canvas/converter/new_quiz/formula.rb +33 -0
- data/lib/ams_migration/canvas/converter/new_quiz/hotspot.rb +166 -0
- data/lib/ams_migration/canvas/converter/new_quiz/matching.rb +85 -0
- data/lib/ams_migration/canvas/converter/new_quiz/multiple_answer.rb +28 -0
- data/lib/ams_migration/canvas/converter/new_quiz/multiple_choice.rb +70 -0
- data/lib/ams_migration/canvas/converter/new_quiz/new_question.rb +133 -0
- data/lib/ams_migration/canvas/converter/new_quiz/numerical.rb +117 -0
- data/lib/ams_migration/canvas/converter/new_quiz/ordering.rb +68 -0
- data/lib/ams_migration/canvas/converter/new_quiz/stimulus.rb +35 -0
- data/lib/ams_migration/canvas/converter/new_quiz/true_false.rb +33 -0
- data/lib/ams_migration/canvas/converter/new_quiz/unknown.rb +30 -0
- data/lib/ams_migration/canvas/converter/question_type.rb +30 -0
- data/lib/ams_migration/services/README.md +26 -0
- data/lib/ams_migration/services/data_provider.rb +26 -0
- data/lib/ams_migration/services/learnosity_bank_cleaner.rb +44 -0
- data/lib/ams_migration/services/learnosity_data_api_client.rb +135 -0
- data/lib/ams_migration/services/learnosity_sink.rb +190 -0
- data/lib/ams_migration/services/migration_service.rb +37 -0
- data/lib/ams_migration/services/new_quiz_learnosity_transformer.rb +115 -0
- data/lib/ams_migration/version.rb +3 -0
- data/lib/ams_migration.rb +45 -0
- data/lib/factories/classic_quiz_item_converter_factory.rb +43 -0
- data/lib/factories/new_quiz_item_converter_factory.rb +76 -0
- data/spec/fixtures/sample_data/classic_quiz/calculated.json +24 -0
- data/spec/fixtures/sample_data/classic_quiz/essay.json +24 -0
- data/spec/fixtures/sample_data/classic_quiz/file_upload.json +24 -0
- data/spec/fixtures/sample_data/classic_quiz/fill_in_bank.json +24 -0
- data/spec/fixtures/sample_data/classic_quiz/fill_in_blank.json +53 -0
- data/spec/fixtures/sample_data/classic_quiz/fill_in_multiple_blanks.json +57 -0
- data/spec/fixtures/sample_data/classic_quiz/matching.json +107 -0
- data/spec/fixtures/sample_data/classic_quiz/multiple_answer.json +48 -0
- data/spec/fixtures/sample_data/classic_quiz/multiple_choice.json +56 -0
- data/spec/fixtures/sample_data/classic_quiz/multiple_dropdown.json +73 -0
- data/spec/fixtures/sample_data/classic_quiz/numerical.json +55 -0
- data/spec/fixtures/sample_data/classic_quiz/text.json +24 -0
- data/spec/fixtures/sample_data/classic_quiz/true_false.json +39 -0
- data/spec/fixtures/sample_data/classic_quiz/unknown.json +24 -0
- data/spec/fixtures/sample_data/new_quiz/bank_entry_item.json +5 -0
- data/spec/fixtures/sample_data/new_quiz/bank_item.json +11 -0
- data/spec/fixtures/sample_data/new_quiz/categorization.json +135 -0
- data/spec/fixtures/sample_data/new_quiz/essay.json +40 -0
- data/spec/fixtures/sample_data/new_quiz/file_upload.json +30 -0
- data/spec/fixtures/sample_data/new_quiz/fill_in_blank_association.json +162 -0
- data/spec/fixtures/sample_data/new_quiz/fill_in_blank_dropdown.json +180 -0
- data/spec/fixtures/sample_data/new_quiz/fill_in_blank_math.json +45 -0
- data/spec/fixtures/sample_data/new_quiz/fill_in_blank_mixed.json +188 -0
- data/spec/fixtures/sample_data/new_quiz/fill_in_blank_text.json +140 -0
- data/spec/fixtures/sample_data/new_quiz/formula.json +51 -0
- data/spec/fixtures/sample_data/new_quiz/hotspot_oval.json +38 -0
- data/spec/fixtures/sample_data/new_quiz/hotspot_polygon.json +66 -0
- data/spec/fixtures/sample_data/new_quiz/hotspot_square.json +38 -0
- data/spec/fixtures/sample_data/new_quiz/matching.json +88 -0
- data/spec/fixtures/sample_data/new_quiz/multiple_answer.json +57 -0
- data/spec/fixtures/sample_data/new_quiz/multiple_choice.json +66 -0
- data/spec/fixtures/sample_data/new_quiz/multiple_choice_math.json +55 -0
- data/spec/fixtures/sample_data/new_quiz/numerical.json +64 -0
- data/spec/fixtures/sample_data/new_quiz/ordering.json +60 -0
- data/spec/fixtures/sample_data/new_quiz/stimulus.json +17 -0
- data/spec/fixtures/sample_data/new_quiz/true_false.json +27 -0
- data/spec/fixtures/sample_data/new_quiz/unknown.json +14 -0
- data/spec/lib/ams_migration/services/learnosity_sink_spec.rb +198 -0
- data/spec/lib/canvas/converter/classic_quiz/calculated_spec.rb +26 -0
- data/spec/lib/canvas/converter/classic_quiz/essay_spec.rb +26 -0
- data/spec/lib/canvas/converter/classic_quiz/file_upload_spec.rb +23 -0
- data/spec/lib/canvas/converter/classic_quiz/fill_in_blank_spec.rb +30 -0
- data/spec/lib/canvas/converter/classic_quiz/fill_in_multiple_blanks_spec.rb +33 -0
- data/spec/lib/canvas/converter/classic_quiz/matching_spec.rb +38 -0
- data/spec/lib/canvas/converter/classic_quiz/multiple_answer_spec.rb +33 -0
- data/spec/lib/canvas/converter/classic_quiz/multiple_choice_spec.rb +31 -0
- data/spec/lib/canvas/converter/classic_quiz/multiple_dropdown_spec.rb +39 -0
- data/spec/lib/canvas/converter/classic_quiz/numerical_spec.rb +41 -0
- data/spec/lib/canvas/converter/classic_quiz/text_spec.rb +20 -0
- data/spec/lib/canvas/converter/classic_quiz/true_false_spec.rb +28 -0
- data/spec/lib/canvas/converter/classic_quiz/unknown_spec.rb +25 -0
- data/spec/lib/canvas/converter/common_error.rb +16 -0
- data/spec/lib/canvas/converter/new_quiz/categorization_spec.rb +55 -0
- data/spec/lib/canvas/converter/new_quiz/essay_spec.rb +26 -0
- data/spec/lib/canvas/converter/new_quiz/file_upload_spec.rb +24 -0
- data/spec/lib/canvas/converter/new_quiz/fill_in_blank_spec.rb +111 -0
- data/spec/lib/canvas/converter/new_quiz/formula_spec.rb +23 -0
- data/spec/lib/canvas/converter/new_quiz/hotspot_spec.rb +182 -0
- data/spec/lib/canvas/converter/new_quiz/matching_spec.rb +30 -0
- data/spec/lib/canvas/converter/new_quiz/multiple_answer_spec.rb +34 -0
- data/spec/lib/canvas/converter/new_quiz/multiple_choice_spec.rb +54 -0
- data/spec/lib/canvas/converter/new_quiz/numerical_spec.rb +48 -0
- data/spec/lib/canvas/converter/new_quiz/ordering_spec.rb +25 -0
- data/spec/lib/canvas/converter/new_quiz/stimulus_spec.rb +20 -0
- data/spec/lib/canvas/converter/new_quiz/true_false_spec.rb +24 -0
- data/spec/lib/canvas/converter/new_quiz/unknown_spec.rb +36 -0
- data/spec/lib/factories/classic_quiz_item_converter_factory_spec.rb +183 -0
- data/spec/lib/factories/new_quiz_item_converter_factory_spec.rb +71 -0
- data/spec/lib/services/learnosity_data_api_client_spec.rb +130 -0
- data/spec/lib/services/new_quiz_learnosity_transformer_spec.rb +196 -0
- data/spec/spec_helper.rb +20 -0
- metadata +229 -0
@@ -0,0 +1,198 @@
|
|
1
|
+
# Copyright (C) 2025 - present Instructure, Inc.
|
2
|
+
# This code is made publicly available for educational and reference purposes only.
|
3
|
+
# You may not copy, modify, redistribute, or use this code in any software or service without explicit permission.
|
4
|
+
|
5
|
+
require "spec_helper"
|
6
|
+
require "webmock/rspec"
|
7
|
+
|
8
|
+
describe AmsMigration::Services::LearnositySink do
|
9
|
+
let(:config) do
|
10
|
+
{
|
11
|
+
consumer_key: "test_key",
|
12
|
+
consumer_secret: "test_secret",
|
13
|
+
domain: "test.domain",
|
14
|
+
organization_id: "test_org",
|
15
|
+
replace_images: true,
|
16
|
+
new_image_prefix: "test_prefix"
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
let(:sink) { described_class.new(config) }
|
21
|
+
let(:client) { instance_double(AmsMigration::Services::LearnosityDataApiClient) }
|
22
|
+
|
23
|
+
before do
|
24
|
+
allow(AmsMigration::Services::LearnosityDataApiClient).to receive(:new).and_return(client)
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "#replace_images?" do
|
28
|
+
context "when replace_images is true in config" do
|
29
|
+
let(:config) { super().merge(replace_images: true) }
|
30
|
+
|
31
|
+
it "returns true" do
|
32
|
+
expect(sink.send(:replace_images?)).to be true
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context "when replace_images is false in config" do
|
37
|
+
let(:config) { super().merge(replace_images: false) }
|
38
|
+
|
39
|
+
it "returns false" do
|
40
|
+
expect(sink.send(:replace_images?)).to be false
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context "when replace_images is not specified in config" do
|
45
|
+
let(:config) do
|
46
|
+
super().tap { |c| c.delete(:replace_images) }
|
47
|
+
end
|
48
|
+
|
49
|
+
it "defaults to false" do
|
50
|
+
expect(sink.send(:replace_images?)).to be false
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe "#replace_images_in_questions" do
|
56
|
+
let(:questions) do
|
57
|
+
[
|
58
|
+
{
|
59
|
+
"data" => {
|
60
|
+
"stimulus" => '<p>Here is an image: <img src="http://example.com/image1.jpg"></p>',
|
61
|
+
"options" => [
|
62
|
+
{ "label" => '<img src="http://example.com/image2.png">' }
|
63
|
+
]
|
64
|
+
}
|
65
|
+
}
|
66
|
+
]
|
67
|
+
end
|
68
|
+
|
69
|
+
let(:image_map) do
|
70
|
+
{
|
71
|
+
"http://example.com/image1.jpg" => "https://assets.learnosity.com/test_prefix/image1.jpg",
|
72
|
+
"http://example.com/image2.png" => "https://assets.learnosity.com/test_prefix/image2.png"
|
73
|
+
}
|
74
|
+
end
|
75
|
+
|
76
|
+
it "replaces image URLs in the collection" do
|
77
|
+
expect(sink).to receive(:upload_images).with(["http://example.com/image1.jpg", "http://example.com/image2.png"]).and_return(image_map)
|
78
|
+
|
79
|
+
result = sink.send(:replace_images_in_questions, questions)
|
80
|
+
question = result.first["data"]
|
81
|
+
expect(question["stimulus"]).to include('src="https://assets.learnosity.com/test_prefix/image1.jpg"')
|
82
|
+
expect(question["options"].first["label"]).to include('src="https://assets.learnosity.com/test_prefix/image2.png"')
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe "#find_images_in_json" do
|
87
|
+
it "finds image sources in nested JSON structures" do
|
88
|
+
json = {
|
89
|
+
"stimulus" => '<p><img src="http://example.com/image1.jpg"></p>',
|
90
|
+
"options" => [
|
91
|
+
{ "label" => '<img src="http://example.com/image2.png">' },
|
92
|
+
{ "label" => "No image here" }
|
93
|
+
],
|
94
|
+
"metadata" => {
|
95
|
+
"image" => '<img src="http://example.com/image3.gif">'
|
96
|
+
}
|
97
|
+
}
|
98
|
+
|
99
|
+
result = sink.send(:find_images_in_json, json)
|
100
|
+
|
101
|
+
expect(result).to match_array(%w[http://example.com/image1.jpg http://example.com/image2.png http://example.com/image3.gif])
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
describe "#find_images_in_question" do
|
106
|
+
context "with a regular question" do
|
107
|
+
let(:question) do
|
108
|
+
{
|
109
|
+
"type" => "mcq",
|
110
|
+
"data" => {
|
111
|
+
"stimulus" => '<p><img src="http://example.com/stimulus.jpg"></p>',
|
112
|
+
"options" => [
|
113
|
+
{ "label" => '<img src="http://example.com/option1.png">' }
|
114
|
+
]
|
115
|
+
}
|
116
|
+
}
|
117
|
+
end
|
118
|
+
|
119
|
+
it "finds images in stimulus and options" do
|
120
|
+
result = sink.send(:find_images_in_question, question)
|
121
|
+
expect(result).to match_array(%w[http://example.com/stimulus.jpg http://example.com/option1.png])
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
context "with a hotspot question" do
|
126
|
+
let(:question) do
|
127
|
+
{
|
128
|
+
"type" => "hotspot",
|
129
|
+
"data" => {
|
130
|
+
"type" => "hotspot",
|
131
|
+
"stimulus" => '<p><img src="http://example.com/stimulus.jpg"></p>',
|
132
|
+
"image" => {
|
133
|
+
"source" => "http://example.com/hotspot_background.jpg",
|
134
|
+
"width" => 600,
|
135
|
+
"height" => 400
|
136
|
+
},
|
137
|
+
"options" => [
|
138
|
+
{ "label" => '<img src="http://example.com/option1.png">' }
|
139
|
+
]
|
140
|
+
}
|
141
|
+
}
|
142
|
+
end
|
143
|
+
|
144
|
+
it "includes the hotspot background image" do
|
145
|
+
result = sink.send(:find_images_in_question, question)
|
146
|
+
expect(result).to match_array(%w[http://example.com/stimulus.jpg http://example.com/option1.png http://example.com/hotspot_background.jpg])
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
context "with invalid question data" do
|
151
|
+
it "handles nil question" do
|
152
|
+
expect(sink.send(:find_images_in_question, nil)).to eq([])
|
153
|
+
end
|
154
|
+
|
155
|
+
it "handles question without data" do
|
156
|
+
expect(sink.send(:find_images_in_question, { "type" => "mcq" })).to eq([])
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
describe "#replace_images_in_json" do
|
162
|
+
let(:image_map) do
|
163
|
+
{
|
164
|
+
"http://example.com/old1.jpg" => "https://assets.learnosity.com/new1.jpg",
|
165
|
+
"http://example.com/old2.png" => "https://assets.learnosity.com/new2.png"
|
166
|
+
}
|
167
|
+
end
|
168
|
+
|
169
|
+
it "replaces image URLs in nested JSON structures" do
|
170
|
+
json = {
|
171
|
+
"text" => '<p><img src="http://example.com/old1.jpg"></p>',
|
172
|
+
"nested" => {
|
173
|
+
"content" => '<img src="http://example.com/old2.png">'
|
174
|
+
},
|
175
|
+
"array" => [
|
176
|
+
{ "image" => '<img src="http://example.com/old1.jpg">' }
|
177
|
+
]
|
178
|
+
}
|
179
|
+
|
180
|
+
result = sink.send(:replace_images_in_json, json, image_map)
|
181
|
+
|
182
|
+
expect(result["text"]).to include('src="https://assets.learnosity.com/new1.jpg"')
|
183
|
+
expect(result["nested"]["content"]).to include('src="https://assets.learnosity.com/new2.png"')
|
184
|
+
expect(result["array"].first["image"]).to include('src="https://assets.learnosity.com/new1.jpg"')
|
185
|
+
end
|
186
|
+
|
187
|
+
it "handles non-string values" do
|
188
|
+
json = {
|
189
|
+
"number" => 42,
|
190
|
+
"boolean" => true,
|
191
|
+
"null" => nil
|
192
|
+
}
|
193
|
+
|
194
|
+
result = sink.send(:replace_images_in_json, json, image_map)
|
195
|
+
expect(result).to eq(json)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# Copyright (C) 2025 - present Instructure, Inc.
|
2
|
+
# This code is made publicly available for educational and reference purposes only.
|
3
|
+
# You may not copy, modify, redistribute, or use this code in any software or service without explicit permission.
|
4
|
+
|
5
|
+
require "spec_helper"
|
6
|
+
require_relative "../common_error"
|
7
|
+
|
8
|
+
describe AmsMigration::Canvas::Converter::ClassicQuiz::Calculated do
|
9
|
+
let(:question) { File.read(File.join(SPEC_ROOT, "fixtures", "sample_data", "classic_quiz", "calculated.json")) }
|
10
|
+
|
11
|
+
subject { described_class.new(question) }
|
12
|
+
|
13
|
+
it_behaves_like "handles common errors"
|
14
|
+
|
15
|
+
it "converts to essay item with placeholder text" do
|
16
|
+
conversion = subject.convert
|
17
|
+
expect(conversion[:question]).to eq(
|
18
|
+
{
|
19
|
+
stimulus: "The Calculated item was removed from the quiz because there is no equivalent item type in Mastery Connect. Please check the original quiz for this question.",
|
20
|
+
type: "unsupported_canvas_question_type",
|
21
|
+
validation: { valid_response: { score: 0, value: "" }, scoring_type: "exactMatch" },
|
22
|
+
is_math: true
|
23
|
+
}
|
24
|
+
)
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# Copyright (C) 2025 - present Instructure, Inc.
|
2
|
+
# This code is made publicly available for educational and reference purposes only.
|
3
|
+
# You may not copy, modify, redistribute, or use this code in any software or service without explicit permission.
|
4
|
+
|
5
|
+
require "spec_helper"
|
6
|
+
require_relative "../common_error"
|
7
|
+
|
8
|
+
describe AmsMigration::Canvas::Converter::ClassicQuiz::Essay do
|
9
|
+
let(:question) { File.read(File.join(SPEC_ROOT, "fixtures", "sample_data", "classic_quiz", "essay.json")) }
|
10
|
+
|
11
|
+
let!(:image_src) { '<img src="https://dummyfileurl.com/file.png" alt="file.png" data-api-endpoint="https://dummyfileurl.com" data-api-returntype="File" />' }
|
12
|
+
|
13
|
+
subject { described_class.new(question) }
|
14
|
+
|
15
|
+
it_behaves_like "handles common errors"
|
16
|
+
|
17
|
+
it "converts to essay item" do
|
18
|
+
conversion = subject.convert
|
19
|
+
expect(conversion[:question]).to eq({
|
20
|
+
stimulus: "<p>This is an essay</p> #{image_src}",
|
21
|
+
type: "longtextV2",
|
22
|
+
metadata: { rubric: "Rubric", rubric_points: 1 },
|
23
|
+
is_math: true
|
24
|
+
})
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# Copyright (C) 2025 - present Instructure, Inc.
|
2
|
+
# This code is made publicly available for educational and reference purposes only.
|
3
|
+
# You may not copy, modify, redistribute, or use this code in any software or service without explicit permission.
|
4
|
+
|
5
|
+
require "spec_helper"
|
6
|
+
require_relative "../common_error"
|
7
|
+
|
8
|
+
describe AmsMigration::Canvas::Converter::ClassicQuiz::FileUpload do
|
9
|
+
let(:question) { File.read(File.join(SPEC_ROOT, "fixtures", "sample_data", "classic_quiz", "file_upload.json")) }
|
10
|
+
|
11
|
+
subject { described_class.new(question) }
|
12
|
+
|
13
|
+
it_behaves_like "handles common errors"
|
14
|
+
|
15
|
+
it "converts to essay item with placeholder text" do
|
16
|
+
conversion = subject.convert
|
17
|
+
expect(conversion[:question]).to eq({ stimulus: "The File upload item was removed from the quiz because there is no equivalent item type in Mastery Connect. Please check the original quiz for this question.",
|
18
|
+
type: "unsupported_canvas_question_type",
|
19
|
+
validation: { valid_response: { score: 0, value: "" },
|
20
|
+
scoring_type: "exactMatch" },
|
21
|
+
is_math: true })
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# Copyright (C) 2025 - present Instructure, Inc.
|
2
|
+
# This code is made publicly available for educational and reference purposes only.
|
3
|
+
# You may not copy, modify, redistribute, or use this code in any software or service without explicit permission.
|
4
|
+
|
5
|
+
require "spec_helper"
|
6
|
+
require_relative "../common_error"
|
7
|
+
|
8
|
+
describe AmsMigration::Canvas::Converter::ClassicQuiz::FillInBlank do
|
9
|
+
let(:question) { File.read(File.join(SPEC_ROOT, "fixtures", "sample_data", "classic_quiz", "fill_in_blank.json")) }
|
10
|
+
let!(:image_src) { '<img src="https://dummyfileurl.com/file.png" alt="file.png" data-api-endpoint="https://dummyfileurl.com" data-api-returntype="File" />' }
|
11
|
+
|
12
|
+
subject { described_class.new(question) }
|
13
|
+
|
14
|
+
it_behaves_like "handles common errors"
|
15
|
+
|
16
|
+
it "converts to clozetext item" do
|
17
|
+
conversion = subject.convert
|
18
|
+
expect(conversion[:question]).to eq({
|
19
|
+
template: "<p>This is the fill in the blank</p> #{image_src}<br />{{response}}",
|
20
|
+
type: "clozetext",
|
21
|
+
validation: { valid_response: { score: 1, value: ["answer 1"] },
|
22
|
+
scoring_type: "exactMatch" },
|
23
|
+
is_math: true,
|
24
|
+
metadata: { distractor_rationale_response_level: ["<p>answer comment 1</p>",
|
25
|
+
"<p>answer comment 2</p>",
|
26
|
+
"<p>answer comment 3</p>",
|
27
|
+
"<p>answer comment 4</p>"] }
|
28
|
+
})
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# Copyright (C) 2025 - present Instructure, Inc.
|
2
|
+
# This code is made publicly available for educational and reference purposes only.
|
3
|
+
# You may not copy, modify, redistribute, or use this code in any software or service without explicit permission.
|
4
|
+
|
5
|
+
require "spec_helper"
|
6
|
+
require_relative "../common_error"
|
7
|
+
|
8
|
+
describe AmsMigration::Canvas::Converter::ClassicQuiz::FillInMultipleBlanks do
|
9
|
+
let(:question) do
|
10
|
+
File.read(File.join(SPEC_ROOT, "fixtures", "sample_data", "classic_quiz", "fill_in_multiple_blanks.json"))
|
11
|
+
end
|
12
|
+
let!(:image_src) { '<img src="https://dummyfileurl.com/file.png" alt="file.png" data-api-endpoint="https://dummyfileurl.com" data-api-returntype="File" />' }
|
13
|
+
subject { described_class.new(question) }
|
14
|
+
|
15
|
+
it_behaves_like "handles common errors"
|
16
|
+
|
17
|
+
it "converts to clozetext item" do
|
18
|
+
conversion = subject.convert
|
19
|
+
expect(conversion[:question]).to eq({
|
20
|
+
template: "<p><span>Roses are {{response}}color1, violets are {{response}}color2</span></p> #{image_src}",
|
21
|
+
type: "clozetext",
|
22
|
+
validation: { valid_response: { score: 1, value: %w[red green] },
|
23
|
+
scoring_type: "exactMatch",
|
24
|
+
match_all_possible_responses: true,
|
25
|
+
alt_responses: [{ score: 1, value: %w[blue pink] }] },
|
26
|
+
is_math: true,
|
27
|
+
metadata: { distractor_rationale_response_level: ["<p>red comments</p>",
|
28
|
+
"<p>blue comments</p>",
|
29
|
+
"<p>green comments</p>",
|
30
|
+
"<p>pink comments</p>"] }
|
31
|
+
})
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# Copyright (C) 2025 - present Instructure, Inc.
|
2
|
+
# This code is made publicly available for educational and reference purposes only.
|
3
|
+
# You may not copy, modify, redistribute, or use this code in any software or service without explicit permission.
|
4
|
+
|
5
|
+
require "spec_helper"
|
6
|
+
require_relative "../common_error"
|
7
|
+
|
8
|
+
describe AmsMigration::Canvas::Converter::ClassicQuiz::Matching do
|
9
|
+
let(:question) { File.read(File.join(SPEC_ROOT, "fixtures", "sample_data", "classic_quiz", "matching.json")) }
|
10
|
+
|
11
|
+
let!(:image_src) { '<img src="https://dummyfileurl.com/file.png" alt="file.png" data-api-endpoint="https://dummyfileurl.com" data-api-returntype="File" />' }
|
12
|
+
|
13
|
+
subject { described_class.new(question) }
|
14
|
+
|
15
|
+
it_behaves_like "handles common errors"
|
16
|
+
|
17
|
+
it "converts to association item" do
|
18
|
+
conversion = subject.convert
|
19
|
+
expect(conversion[:question]).to eq({
|
20
|
+
stimulus: "<p>Matching question</p> #{image_src}",
|
21
|
+
possible_responses: ["",
|
22
|
+
"",
|
23
|
+
"R1",
|
24
|
+
"R2",
|
25
|
+
"R3",
|
26
|
+
"d1",
|
27
|
+
"d2",
|
28
|
+
"d3",
|
29
|
+
"d4",
|
30
|
+
"d5"],
|
31
|
+
stimulus_list: ["", "", "L1", "L2", "L3"],
|
32
|
+
type: "association",
|
33
|
+
validation: { valid_response: { score: 1, value: ["", "", "R1", "R2", "R3"] },
|
34
|
+
scoring_type: "exactMatch" },
|
35
|
+
is_math: true
|
36
|
+
})
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# Copyright (C) 2025 - present Instructure, Inc.
|
2
|
+
# This code is made publicly available for educational and reference purposes only.
|
3
|
+
# You may not copy, modify, redistribute, or use this code in any software or service without explicit permission.
|
4
|
+
|
5
|
+
require "spec_helper"
|
6
|
+
require_relative "../common_error"
|
7
|
+
|
8
|
+
describe AmsMigration::Canvas::Converter::ClassicQuiz::MultipleAnswer do
|
9
|
+
let(:question) { File.read(File.join(SPEC_ROOT, "fixtures", "sample_data", "classic_quiz", "multiple_answer.json")) }
|
10
|
+
let!(:image_src) { '<img src="https://dummyfileurl.com/file.png" alt="file.png" data-api-endpoint="https://dummyfileurl.com" data-api-returntype="File" />' }
|
11
|
+
|
12
|
+
subject { described_class.new(question) }
|
13
|
+
|
14
|
+
it_behaves_like "handles common errors"
|
15
|
+
|
16
|
+
it "converts to mcq item" do
|
17
|
+
conversion = subject.convert
|
18
|
+
expect(conversion[:question]).to eq({
|
19
|
+
stimulus: "<p>Multiple answer text</p> #{image_src}",
|
20
|
+
options: [{ label: "<p>Answer 1</p>", value: "6739" },
|
21
|
+
{ label: "<p>Answer 2</p>", value: "1316" },
|
22
|
+
{ label: "Answer 3", value: "6779" }],
|
23
|
+
type: "mcq",
|
24
|
+
validation: {
|
25
|
+
valid_response: { score: 1,
|
26
|
+
value: %w[6739 1316] },
|
27
|
+
scoring_type: "exactMatch"
|
28
|
+
},
|
29
|
+
is_math: true,
|
30
|
+
multiple_responses: true
|
31
|
+
})
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# Copyright (C) 2025 - present Instructure, Inc.
|
2
|
+
# This code is made publicly available for educational and reference purposes only.
|
3
|
+
# You may not copy, modify, redistribute, or use this code in any software or service without explicit permission.
|
4
|
+
|
5
|
+
require "spec_helper"
|
6
|
+
require_relative "../common_error"
|
7
|
+
|
8
|
+
describe AmsMigration::Canvas::Converter::ClassicQuiz::MultipleChoice do
|
9
|
+
let(:question) { File.read(File.join(SPEC_ROOT, "fixtures", "sample_data", "classic_quiz", "multiple_choice.json")) }
|
10
|
+
let!(:image_src) { '<img src="https://dummyfileurl.com/file.png" alt="file.png" data-api-endpoint="https://dummyfileurl.com" data-api-returntype="File" />' }
|
11
|
+
|
12
|
+
subject { described_class.new(question) }
|
13
|
+
|
14
|
+
it_behaves_like "handles common errors"
|
15
|
+
|
16
|
+
it "converts to mcq item" do
|
17
|
+
conversion = subject.convert
|
18
|
+
expect(conversion[:question]).to eq({
|
19
|
+
stimulus: "<p>This is an MC Item</p><p><math xmlns=\"http://www.w3.org/1998/Math/MathML\"> <munderover> <mo>∑<!-- ∑ --></mo> <mrow class=\"MJX-TeXAtom-ORD\"> <mn>23</mn> </mrow> <mrow class=\"MJX-TeXAtom-ORD\"> <mn>23</mn> </mrow> </munderover> <mi>w</mi> <mi>w</mi> <mi>w</mi> <mo>+</mo> <mi>f</mi> <mrow> <mo>(</mo> <mi>d</mi> <mo>)</mo> </mrow></math></p><p> </p> #{image_src}",
|
20
|
+
options: [{ label: "<p>Correct answer</p>", value: "2483" },
|
21
|
+
{ label: "<p>Answer B<strong>more markup</strong></p>",
|
22
|
+
value: "5396" },
|
23
|
+
{ label: "cc", value: "4310" },
|
24
|
+
{ label: "dd", value: "7473" }],
|
25
|
+
type: "mcq",
|
26
|
+
validation: { valid_response: { score: 1, value: ["2483"] },
|
27
|
+
scoring_type: "exactMatch" },
|
28
|
+
is_math: true
|
29
|
+
})
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# Copyright (C) 2025 - present Instructure, Inc.
|
2
|
+
# This code is made publicly available for educational and reference purposes only.
|
3
|
+
# You may not copy, modify, redistribute, or use this code in any software or service without explicit permission.
|
4
|
+
|
5
|
+
require "spec_helper"
|
6
|
+
require_relative "../common_error"
|
7
|
+
|
8
|
+
describe AmsMigration::Canvas::Converter::ClassicQuiz::MultipleDropdown do
|
9
|
+
let(:question) do
|
10
|
+
File.read(File.join(SPEC_ROOT, "fixtures", "sample_data", "classic_quiz", "multiple_dropdown.json"))
|
11
|
+
end
|
12
|
+
let!(:image_src) { '<img src="https://dummyfileurl.com/file.png" alt="file.png" data-api-endpoint="https://dummyfileurl.com" data-api-returntype="File" />' }
|
13
|
+
|
14
|
+
subject { described_class.new(question) }
|
15
|
+
|
16
|
+
it_behaves_like "handles common errors"
|
17
|
+
|
18
|
+
it "converts to clozedropdown item" do
|
19
|
+
conversion = subject.convert
|
20
|
+
expect(conversion[:question]).to eq({
|
21
|
+
template: "<p><span>Roses are {{response}}color1, violets are {{response}}color2</span></p><p> </p> #{image_src}",
|
22
|
+
type: "clozedropdown",
|
23
|
+
possible_responses: [["red", "blue", "green"], ["green", "", "black"]],
|
24
|
+
validation: { valid_response: { score: 1, value: %w[red green] },
|
25
|
+
scoring_type: "exactMatch",
|
26
|
+
match_all_possible_responses: true,
|
27
|
+
alt_responses: [{ score: 1, value: ["blue", ""] },
|
28
|
+
{ score: 1, value: %w[green black] }] },
|
29
|
+
is_math: true,
|
30
|
+
response_container: { pointer: "left" },
|
31
|
+
metadata: { distractor_rationale_response_level: ["<p>answer 1 comments</p>",
|
32
|
+
"<p>answer 2 comments</p>",
|
33
|
+
"<p>answer 3 comments</p>",
|
34
|
+
"",
|
35
|
+
"",
|
36
|
+
""] }
|
37
|
+
})
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# Copyright (C) 2025 - present Instructure, Inc.
|
2
|
+
# This code is made publicly available for educational and reference purposes only.
|
3
|
+
# You may not copy, modify, redistribute, or use this code in any software or service without explicit permission.
|
4
|
+
|
5
|
+
require "spec_helper"
|
6
|
+
require_relative "../common_error"
|
7
|
+
|
8
|
+
describe AmsMigration::Canvas::Converter::ClassicQuiz::Numerical do
|
9
|
+
let(:question) { File.read(File.join(SPEC_ROOT, "fixtures", "sample_data", "classic_quiz", "numerical.json")) }
|
10
|
+
let!(:image_src) { '<img src="https://dummyfileurl.com/file.png" alt="file.png" data-api-endpoint="https://dummyfileurl.com" data-api-returntype="File" />' }
|
11
|
+
|
12
|
+
subject { described_class.new(question) }
|
13
|
+
|
14
|
+
it_behaves_like "handles common errors"
|
15
|
+
|
16
|
+
it "converts to clozeformula item" do
|
17
|
+
conversion = subject.convert
|
18
|
+
expect(conversion[:question]).to eq({
|
19
|
+
stimulus: "<p>Numerical answer stem</p> #{image_src}",
|
20
|
+
template: "{{response}}",
|
21
|
+
response_container: {
|
22
|
+
template: ""
|
23
|
+
},
|
24
|
+
type: "clozeformula",
|
25
|
+
validation: { scoring_type: "exactMatch",
|
26
|
+
valid_response: { score: 1,
|
27
|
+
value: [
|
28
|
+
[{ method: "equivLiteral", value: "20", options: { ignoreOrder: false, inverseResult: false } },
|
29
|
+
{ method: "equivValue",
|
30
|
+
value: '4\\pm2',
|
31
|
+
options: { ignoreOrder: false,
|
32
|
+
inverseResult: false } },
|
33
|
+
{ method: "equivValue",
|
34
|
+
value: "21.0001001",
|
35
|
+
options: { decimalPlaces: 10 } }]
|
36
|
+
] } },
|
37
|
+
is_math: true,
|
38
|
+
response_containers: []
|
39
|
+
})
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# Copyright (C) 2025 - present Instructure, Inc.
|
2
|
+
# This code is made publicly available for educational and reference purposes only.
|
3
|
+
# You may not copy, modify, redistribute, or use this code in any software or service without explicit permission.
|
4
|
+
|
5
|
+
require "spec_helper"
|
6
|
+
require_relative "../common_error"
|
7
|
+
|
8
|
+
describe AmsMigration::Canvas::Converter::ClassicQuiz::Text do
|
9
|
+
let(:question) { File.read(File.join(SPEC_ROOT, "fixtures", "sample_data", "classic_quiz", "text.json")) }
|
10
|
+
let!(:image_src) { '<img src="https://dummyfileurl.com/file.png" alt="file.png" data-api-endpoint="https://dummyfileurl.com" data-api-returntype="File" />' }
|
11
|
+
|
12
|
+
subject { described_class.new(question) }
|
13
|
+
|
14
|
+
it_behaves_like "handles common errors"
|
15
|
+
|
16
|
+
it "converts to a text return value" do
|
17
|
+
conversion = subject.convert
|
18
|
+
expect(conversion[:question]).to eq("<p>Text here</p> #{image_src}")
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# Copyright (C) 2025 - present Instructure, Inc.
|
2
|
+
# This code is made publicly available for educational and reference purposes only.
|
3
|
+
# You may not copy, modify, redistribute, or use this code in any software or service without explicit permission.
|
4
|
+
|
5
|
+
require "spec_helper"
|
6
|
+
require_relative "../common_error"
|
7
|
+
|
8
|
+
describe AmsMigration::Canvas::Converter::ClassicQuiz::TrueFalse do
|
9
|
+
let(:question) { File.read(File.join(SPEC_ROOT, "fixtures", "sample_data", "classic_quiz", "true_false.json")) }
|
10
|
+
let!(:image_src) { '<img src="https://dummyfileurl.com/file.png" alt="file.png" data-api-endpoint="https://dummyfileurl.com" data-api-returntype="File" />' }
|
11
|
+
|
12
|
+
subject { described_class.new(question) }
|
13
|
+
|
14
|
+
it_behaves_like "handles common errors"
|
15
|
+
|
16
|
+
it "converts to mcq item" do
|
17
|
+
conversion = subject.convert
|
18
|
+
expect(conversion[:question]).to eq({
|
19
|
+
stimulus: "<p>This is true/False</p> #{image_src}",
|
20
|
+
options: [{ label: "True", value: "8059" },
|
21
|
+
{ label: "False", value: "957" }],
|
22
|
+
type: "mcq",
|
23
|
+
validation: { valid_response: { score: 1, value: ["957"] },
|
24
|
+
scoring_type: "exactMatch" },
|
25
|
+
is_math: true
|
26
|
+
})
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# Copyright (C) 2025 - present Instructure, Inc.
|
2
|
+
# This code is made publicly available for educational and reference purposes only.
|
3
|
+
# You may not copy, modify, redistribute, or use this code in any software or service without explicit permission.
|
4
|
+
|
5
|
+
require "spec_helper"
|
6
|
+
require_relative "../common_error"
|
7
|
+
|
8
|
+
describe AmsMigration::Canvas::Converter::ClassicQuiz::Unknown do
|
9
|
+
let(:question) do
|
10
|
+
File.read(File.join(SPEC_ROOT, "fixtures", "sample_data", "classic_quiz", "unknown.json"))
|
11
|
+
end
|
12
|
+
|
13
|
+
subject { described_class.new(question) }
|
14
|
+
|
15
|
+
it "returns errors with malformed json" do
|
16
|
+
subject = described_class.new('{"this is": "not valid json" xyz }')
|
17
|
+
conversion = subject.convert
|
18
|
+
expect(conversion[:errors].length).to be >= 1
|
19
|
+
end
|
20
|
+
|
21
|
+
it "returns with question type error" do
|
22
|
+
conversion = subject.convert
|
23
|
+
expect(conversion[:errors]).to include("Unknown question type: Unknown")
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# Copyright (C) 2025 - present Instructure, Inc.
|
2
|
+
# This code is made publicly available for educational and reference purposes only.
|
3
|
+
# You may not copy, modify, redistribute, or use this code in any software or service without explicit permission.
|
4
|
+
|
5
|
+
shared_examples "handles common errors" do
|
6
|
+
it "returns with no errors" do
|
7
|
+
conversion = subject.convert
|
8
|
+
expect(conversion[:errors].length).to eq(0), "Errors: #{conversion[:errors].inspect}"
|
9
|
+
end
|
10
|
+
|
11
|
+
it "returns errors with malformed json" do
|
12
|
+
subject = described_class.new('{"this is": "not valid json" xyz }')
|
13
|
+
conversion = subject.convert
|
14
|
+
expect(conversion[:errors].length).to be >= 1
|
15
|
+
end
|
16
|
+
end
|