mumuki-laboratory 6.1.5 → 6.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/app/assets/javascripts/application.js +1 -0
- data/app/assets/javascripts/application/codemirror-builder.js +11 -1
- data/app/assets/javascripts/application/codemirror.js +0 -1
- data/app/assets/javascripts/application/discussions.js +15 -2
- data/app/assets/javascripts/application/free-form.js +1 -1
- data/app/assets/javascripts/application/kids.js +3 -3
- data/app/assets/javascripts/application/load-error-svg.js +1 -1
- data/app/controllers/invitations_controller.rb +1 -1
- data/app/helpers/application_helper.rb +0 -22
- data/app/helpers/assets_helper.rb +12 -0
- data/app/helpers/assignment_result_helper.rb +1 -1
- data/app/helpers/assistance_box_helper.rb +9 -0
- data/app/helpers/editor_helper.rb +13 -0
- data/app/helpers/locale_helper.rb +11 -0
- data/app/helpers/menu_bar_helper.rb +13 -0
- data/app/helpers/multiple_file_editor_helper.rb +5 -0
- data/app/helpers/open_graph_helper.rb +12 -0
- data/app/helpers/page_title_helper.rb +11 -0
- data/app/views/exercise_solutions/_kids_results.html.erb +1 -1
- data/app/views/exercises/_read_only.html.erb +3 -1
- data/app/views/invitations/show.html.erb +1 -1
- data/app/views/layouts/_kids.html.erb +1 -1
- data/app/views/layouts/_main.html.erb +5 -20
- data/app/views/layouts/application.html.erb +2 -7
- data/app/views/layouts/exercise_inputs/editors/_multiple_files.html.erb +11 -12
- data/app/views/layouts/exercise_inputs/layouts/_input_kids.html.erb +1 -1
- data/app/views/layouts/exercise_inputs/read_only_editors/_code.html.erb +1 -1
- data/app/views/layouts/exercise_inputs/read_only_editors/_free_form.html.erb +1 -1
- data/app/views/layouts/exercise_inputs/read_only_editors/_multiple_files.html.erb +29 -0
- data/app/views/layouts/mailer.html.erb +1 -1
- data/app/views/layouts/modals/_kids_context.html.erb +1 -1
- data/config/initializers/form_builder.rb +4 -5
- data/lib/events.rb +1 -4
- data/lib/mumuki/laboratory/locales/en.yml +0 -1
- data/lib/mumuki/laboratory/locales/es.yml +0 -1
- data/lib/mumuki/laboratory/locales/pt.yml +0 -1
- data/lib/mumuki/laboratory/version.rb +1 -1
- data/public/character/kids/{yellow_context.svg → kibi_context.svg} +2 -2
- data/public/{amarillo.svg → kibi.svg} +2 -2
- data/public/{anim_amarillo.svg → kibi_animated.svg} +2 -2
- data/public/{amarillo_fracaso.svg → kibi_failure.svg} +2 -2
- data/public/{amarillo_exito.svg → kibi_success.svg} +2 -2
- data/public/{amarillo_exito_bailarin.svg → kibi_success_dancing.svg} +2 -2
- data/spec/capybara_helper.rb +4 -4
- data/spec/helpers/application_helper_spec.rb +0 -16
- data/spec/helpers/page_title_helper_spec.rb +19 -0
- data/spec/spec_helper.rb +2 -1
- data/vendor/assets/javascripts/codemirror-autorefresh.js +46 -0
- metadata +76 -163
- data/spec/factories/api_client_factory.rb +0 -18
- data/spec/factories/assignments_factory.rb +0 -7
- data/spec/factories/book_factory.rb +0 -7
- data/spec/factories/chapter_factory.rb +0 -16
- data/spec/factories/complement_factory.rb +0 -6
- data/spec/factories/course_factory.rb +0 -9
- data/spec/factories/discussion_factory.rb +0 -8
- data/spec/factories/exam_factory.rb +0 -9
- data/spec/factories/exercise_factory.rb +0 -73
- data/spec/factories/guide_factory.rb +0 -30
- data/spec/factories/invitation_factory.rb +0 -7
- data/spec/factories/lesson_factory.rb +0 -7
- data/spec/factories/login_settings_factory.rb +0 -5
- data/spec/factories/message_factory.rb +0 -11
- data/spec/factories/organization_factory.rb +0 -26
- data/spec/factories/topic_factory.rb +0 -9
- data/spec/factories/user_factory.rb +0 -8
- data/spec/models/api_client_spec.rb +0 -6
- data/spec/models/assignment_spec.rb +0 -191
- data/spec/models/book_import_spec.rb +0 -49
- data/spec/models/book_spec.rb +0 -114
- data/spec/models/course_spec.rb +0 -22
- data/spec/models/discussion_spec.rb +0 -174
- data/spec/models/event_generation_spec.rb +0 -189
- data/spec/models/event_publishing_spec.rb +0 -36
- data/spec/models/exam_spec.rb +0 -151
- data/spec/models/exercise_spec.rb +0 -494
- data/spec/models/guide_import_spec.rb +0 -300
- data/spec/models/guide_spec.rb +0 -83
- data/spec/models/interactive_spec.rb +0 -143
- data/spec/models/language_spec.rb +0 -56
- data/spec/models/lesson_spec.rb +0 -90
- data/spec/models/message_spec.rb +0 -87
- data/spec/models/navigation_spec.rb +0 -70
- data/spec/models/organization_spec.rb +0 -124
- data/spec/models/playground_spec.rb +0 -23
- data/spec/models/problem_spec.rb +0 -80
- data/spec/models/query_spec.rb +0 -21
- data/spec/models/question_spec.rb +0 -37
- data/spec/models/reading_spec.rb +0 -10
- data/spec/models/solution_spec.rb +0 -66
- data/spec/models/stats_spec.rb +0 -15
- data/spec/models/string_spec.rb +0 -9
- data/spec/models/topic_import_spec.rb +0 -42
- data/spec/models/usage_spec.rb +0 -77
- data/spec/models/user_changed_spec.rb +0 -35
- data/spec/models/user_spec.rb +0 -276
- data/spec/models/with_expectations_spec.rb +0 -56
@@ -1,36 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
require 'rspec/mocks'
|
3
|
-
|
4
|
-
describe 'events' do
|
5
|
-
let(:exercise) { create(:exercise) }
|
6
|
-
let(:assignment) { create(:assignment, exercise: exercise) }
|
7
|
-
|
8
|
-
before { organization.switch! }
|
9
|
-
before { create(:chapter, lessons: [ create(:lesson, exercises: [ exercise ]) ]) }
|
10
|
-
before { reindex_current_organization! }
|
11
|
-
|
12
|
-
describe '#notify!' do
|
13
|
-
let!(:organization) { create(:organization, name: 'pdep') }
|
14
|
-
before { expect_any_instance_of(Mumukit::Nuntius::NotificationMode::Deaf).to receive(:notify!) }
|
15
|
-
before { organization.switch! }
|
16
|
-
|
17
|
-
it { expect { assignment.notify! }.to_not raise_error }
|
18
|
-
end
|
19
|
-
|
20
|
-
describe 'protect in central book' do
|
21
|
-
let!(:organization) { create(:organization, name: 'central') }
|
22
|
-
before { expect_any_instance_of(Mumukit::Nuntius::NotificationMode::Deaf).to receive(:notify!) }
|
23
|
-
before { organization.switch! }
|
24
|
-
|
25
|
-
it { expect { assignment.notify! }.to_not raise_error }
|
26
|
-
end
|
27
|
-
|
28
|
-
describe 'submit_solution!' do
|
29
|
-
let!(:organization) { create(:organization, name: 'pdep') }
|
30
|
-
before { expect_any_instance_of(Mumukit::Nuntius::NotificationMode::Deaf).to receive(:notify!) }
|
31
|
-
before { organization.switch! }
|
32
|
-
let(:user) { create(:user) }
|
33
|
-
|
34
|
-
it { expect { exercise.submit_solution! user }.to_not raise_error }
|
35
|
-
end
|
36
|
-
end
|
data/spec/models/exam_spec.rb
DELETED
@@ -1,151 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe Exam, organization_workspace: :test do
|
4
|
-
let(:user) { create(:user) }
|
5
|
-
let(:other_user) { create(:user) }
|
6
|
-
|
7
|
-
describe '#upsert' do
|
8
|
-
let(:guide) { create(:guide) }
|
9
|
-
let(:exam_json) { {eid: '1', slug: guide.slug, start_time: 5.minutes.ago, end_time: 10.minutes.since, duration: 150, language: 'haskell', name: 'foo', uids: [], organization: 'test'} }
|
10
|
-
let!(:exam) { Exam.import_from_resource_h! exam_json }
|
11
|
-
context 'when new exam and the guide is the same' do
|
12
|
-
let(:guide2) { create(:guide) }
|
13
|
-
let(:exam_json2) { {eid: '2', slug: guide.slug, start_time: 5.minutes.ago, end_time: 10.minutes.since, duration: 150, language: 'haskell', name: 'foo', uids: [], organization: 'test'} }
|
14
|
-
let!(:exam2) { Exam.import_from_resource_h! exam_json2 }
|
15
|
-
context 'and the organization is the same' do
|
16
|
-
it { expect(Exam.count).to eq 1 }
|
17
|
-
it { expect(Usage.where(item_id: guide.id, parent_item_id: exam.id).count).to eq 0 }
|
18
|
-
it { expect(ExamAuthorization.where(exam_id: exam.id).count).to eq 0 }
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
end
|
23
|
-
|
24
|
-
describe '#validate_accessible_for!' do
|
25
|
-
context 'not enabled' do
|
26
|
-
let(:exam) { create(:exam, start_time: 5.minutes.since, end_time: 10.minutes.since) }
|
27
|
-
|
28
|
-
it { expect(exam.enabled?).to be false }
|
29
|
-
|
30
|
-
context 'not authorized' do
|
31
|
-
it { expect { exam.validate_accessible_for! user }.to raise_error(Mumuki::Domain::ForbiddenError) }
|
32
|
-
end
|
33
|
-
|
34
|
-
context 'authorized' do
|
35
|
-
it { expect(exam.enabled?).to be false }
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
context 'enabled' do
|
40
|
-
let(:exam) { create(:exam, start_time: 5.minutes.ago, end_time: 10.minutes.since) }
|
41
|
-
|
42
|
-
it { expect(exam.enabled?).to be true }
|
43
|
-
|
44
|
-
context 'not authorized' do
|
45
|
-
it { expect { exam.validate_accessible_for! user }.to raise_error(Mumuki::Domain::ForbiddenError) }
|
46
|
-
end
|
47
|
-
|
48
|
-
context 'authorized' do
|
49
|
-
before { exam.authorize! user }
|
50
|
-
|
51
|
-
it { expect { exam.validate_accessible_for! user }.to_not raise_error }
|
52
|
-
it { expect { exam.validate_accessible_for! other_user }.to raise_error(Mumuki::Domain::ForbiddenError) }
|
53
|
-
end
|
54
|
-
|
55
|
-
context 'import_from_json' do
|
56
|
-
let(:user) { create(:user, uid: 'auth0|1') }
|
57
|
-
let(:user2) { create(:user, uid: 'auth0|2') }
|
58
|
-
let(:guide) { create(:guide) }
|
59
|
-
let(:duration) { 150 }
|
60
|
-
let(:exam_json) { {eid: '1', slug: guide.slug, start_time: 5.minutes.ago, end_time: 10.minutes.since, duration: duration, language: 'haskell', name: 'foo', uids: [user.uid], organization: 'test'} }
|
61
|
-
before { Exam.import_from_resource_h! exam_json }
|
62
|
-
|
63
|
-
context 'new exam' do
|
64
|
-
it { expect(Exam.count).to eq 1 }
|
65
|
-
it { expect { Exam.find_by(classroom_id: '1').validate_accessible_for! user }.to_not raise_error }
|
66
|
-
it { expect(guide.usage_in_organization).to be_a Exam }
|
67
|
-
end
|
68
|
-
|
69
|
-
context 'new exam, no duration' do
|
70
|
-
let(:duration) { nil }
|
71
|
-
it { expect(guide.usage_in_organization).to be_a Exam }
|
72
|
-
end
|
73
|
-
|
74
|
-
context 'existing exam' do
|
75
|
-
let(:exam_json2) { {eid: '1', slug: guide.slug, start_time: 5.minutes.ago, end_time: 10.minutes.since, duration: 150, language: 'haskell', name: 'foo', uids: [user2.uid], organization: 'test'} }
|
76
|
-
before { Exam.import_from_resource_h! exam_json2 }
|
77
|
-
|
78
|
-
it { expect(Exam.count).to eq 1 }
|
79
|
-
it { expect { Exam.find_by(classroom_id: '1').validate_accessible_for! user }.to raise_error(Mumuki::Domain::ForbiddenError) }
|
80
|
-
it { expect { Exam.find_by(classroom_id: '1').validate_accessible_for! user2 }.to_not raise_error }
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
context 'real_end_time' do
|
85
|
-
let(:user) { create(:user, uid: 'auth0|1') }
|
86
|
-
let(:guide) { create(:guide) }
|
87
|
-
let(:exam_json) { {eid: '1', slug: guide.slug, start_time: 5.minutes.ago, end_time: 10.minutes.since, duration: duration, language: 'haskell', name: 'foo', uids: [user.uid], organization: 'test'} }
|
88
|
-
let(:exam) { Exam.import_from_resource_h! exam_json }
|
89
|
-
before { exam.start! user }
|
90
|
-
|
91
|
-
context 'with duration' do
|
92
|
-
let(:duration) { 150 }
|
93
|
-
it { expect(exam.real_end_time user).to eq(exam.end_time) }
|
94
|
-
it { expect(exam.started? user).to be_truthy }
|
95
|
-
end
|
96
|
-
|
97
|
-
context 'with short duration' do
|
98
|
-
let(:duration) { 3 }
|
99
|
-
it { expect(exam.real_end_time user).to eq(exam.started_at(user) + 3.minutes) }
|
100
|
-
it { expect(exam.started? user).to be_truthy }
|
101
|
-
end
|
102
|
-
|
103
|
-
context 'no duration' do
|
104
|
-
let(:duration) { nil }
|
105
|
-
it { expect(exam.real_end_time user).to eq(exam.end_time) }
|
106
|
-
it { expect(exam.started? user).to be_truthy }
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
|
-
context 'update exam does not change user started_at' do
|
111
|
-
let(:user) { create(:user, uid: 'auth0|1') }
|
112
|
-
let(:guide) { create(:guide) }
|
113
|
-
let(:exam_json) { {eid: '1', slug: guide.slug, start_time: 5.minutes.ago, end_time: 10.minutes.since, duration: 150, language: 'haskell', name: 'foo', uids: [user.uid], organization: 'test'} }
|
114
|
-
let(:exam) { Exam.import_from_resource_h! exam_json }
|
115
|
-
before { exam.start! user }
|
116
|
-
before { Exam.import_from_resource_h! exam_json.merge(organization: 'test') }
|
117
|
-
|
118
|
-
it { expect(exam.started?(user)).to be true }
|
119
|
-
|
120
|
-
end
|
121
|
-
|
122
|
-
context 'create exam with non existing user' do
|
123
|
-
let(:guide) { create(:guide) }
|
124
|
-
let(:exam_json) { {eid: '1', slug: guide.slug, start_time: 5.minutes.ago, end_time: 10.minutes.since, duration: 150, language: 'haskell', name: 'foo', uids: [user.uid], organization: 'test'} }
|
125
|
-
let(:exam) { Exam.import_from_resource_h! exam_json }
|
126
|
-
|
127
|
-
it { expect { Exam.import_from_resource_h! exam_json.merge(organization: 'test') }.not_to raise_error }
|
128
|
-
|
129
|
-
end
|
130
|
-
|
131
|
-
context 'teacher does not start exams' do
|
132
|
-
let(:teacher) { create(:user, uid: 'auth0|1') }
|
133
|
-
let(:guide) { create(:guide) }
|
134
|
-
let(:exam_json) { {eid: '1', slug: guide.slug, start_time: 5.minutes.ago, end_time: 10.minutes.since, duration: 150, language: 'haskell', name: 'foo', uids: [], organization: 'test'} }
|
135
|
-
let(:exam) { Exam.import_from_resource_h! exam_json }
|
136
|
-
|
137
|
-
context 'exam_authorization do not receive start method' do
|
138
|
-
before { expect(teacher).to receive(:teacher_here?).and_return(true) }
|
139
|
-
before { expect_any_instance_of(ExamAuthorization).to_not receive(:start!) }
|
140
|
-
it { expect { exam.start!(teacher) }.to_not raise_error }
|
141
|
-
|
142
|
-
end
|
143
|
-
|
144
|
-
context 'exam is not started by a teacher' do
|
145
|
-
it { expect(exam.started?(teacher)).to be_falsey }
|
146
|
-
end
|
147
|
-
|
148
|
-
end
|
149
|
-
end
|
150
|
-
end
|
151
|
-
end
|
@@ -1,494 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe Exercise, organization_workspace: :test do
|
4
|
-
let(:exercise) { create(:exercise) }
|
5
|
-
let(:user) { create(:user, first_name: 'Orlo') }
|
6
|
-
|
7
|
-
describe '#choice_values' do
|
8
|
-
context 'when choices are in 5.0 format' do
|
9
|
-
let(:choice_values) { %w(1492 1453 1773) }
|
10
|
-
let(:exercise) { build(:exercise, description: 'when did byzantine empire fall?', choice_values: choice_values) }
|
11
|
-
|
12
|
-
it { expect(exercise.choices).to be_blank }
|
13
|
-
it { expect(exercise[:choice_values]).to eq choice_values }
|
14
|
-
it { expect(exercise.choice_values).to eq choice_values }
|
15
|
-
it { expect(exercise.choice_index_for '1492').to eq 0 }
|
16
|
-
it { expect(exercise.choice_index_for '1773').to eq 2 }
|
17
|
-
end
|
18
|
-
|
19
|
-
context 'when choices are in 6.0 format' do
|
20
|
-
let(:choices) { [{value: '1492', checked: false}, {value: '1453', checked: true}, {value: '1773', checked: false}] }
|
21
|
-
let(:exercise) { build(:exercise, description: 'when did byzantine empire fall?', choices: choices) }
|
22
|
-
|
23
|
-
it { expect(exercise.choices).to eq choices }
|
24
|
-
it { expect(exercise[:choice_values]).to be_blank }
|
25
|
-
it { expect(exercise.choice_values).to eq %w(1492 1453 1773) }
|
26
|
-
it { expect(exercise.choice_index_for '1492').to eq 0 }
|
27
|
-
it { expect(exercise.choice_index_for '1773').to eq 2 }
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
describe '#slug' do
|
32
|
-
let(:guide) { create(:guide, slug: 'foo/bar') }
|
33
|
-
let(:exercise) { create(:exercise, guide: guide, bibliotheca_id: 4) }
|
34
|
-
it { expect(exercise.slug).to eq 'foo/bar/4' }
|
35
|
-
end
|
36
|
-
|
37
|
-
describe '#new_solution' do
|
38
|
-
context 'when there is default content' do
|
39
|
-
let(:exercise) { create(:exercise, default_content: 'foo') }
|
40
|
-
|
41
|
-
it { expect(exercise.new_solution.content).to eq 'foo' }
|
42
|
-
end
|
43
|
-
|
44
|
-
context 'when there is no default content' do
|
45
|
-
let(:exercise) { create(:exercise) }
|
46
|
-
|
47
|
-
it { expect(exercise.new_solution.content).to be_blank }
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
describe '#next_for' do
|
52
|
-
context 'when exercise has no guide' do
|
53
|
-
it { expect(exercise.next_for(user)).to be nil }
|
54
|
-
end
|
55
|
-
context 'when exercise belong to a guide with a single exercise' do
|
56
|
-
let(:exercise_with_guide) { create(:exercise, guide: guide) }
|
57
|
-
let(:guide) { create(:guide) }
|
58
|
-
|
59
|
-
it { expect(exercise_with_guide.next_for(user)).to be nil }
|
60
|
-
end
|
61
|
-
context 'when exercise belongs to a guide with two exercises' do
|
62
|
-
let!(:exercise_with_guide) { create(:exercise, guide: guide, number: 2) }
|
63
|
-
let!(:alternative_exercise) { create(:exercise, guide: guide, number: 3) }
|
64
|
-
let!(:guide) { create(:guide) }
|
65
|
-
|
66
|
-
it { expect(exercise_with_guide.next_for(user)).to eq alternative_exercise }
|
67
|
-
end
|
68
|
-
|
69
|
-
context 'when exercise belongs to a guide with two exercises and alternative exercise has being solved' do
|
70
|
-
let(:exercise_with_guide) { create(:exercise, guide: guide) }
|
71
|
-
let!(:alternative_exercise) { create(:exercise, guide: guide) }
|
72
|
-
let(:guide) { create(:guide) }
|
73
|
-
|
74
|
-
before { alternative_exercise.submit_solution!(user, content: 'foo').passed! }
|
75
|
-
|
76
|
-
it { expect(exercise_with_guide.next_for(user)).to be nil }
|
77
|
-
end
|
78
|
-
|
79
|
-
context 'when exercise belongs to a guide with two exercises and alternative exercise has being submitted but not solved' do
|
80
|
-
let!(:exercise_with_guide) { create(:exercise, guide: guide, number: 2) }
|
81
|
-
let!(:alternative_exercise) { create(:exercise, guide: guide, number: 3) }
|
82
|
-
let(:guide) { create(:guide) }
|
83
|
-
|
84
|
-
before { alternative_exercise.submit_solution!(user, content: 'foo') }
|
85
|
-
|
86
|
-
it { expect(guide.pending_exercises(user)).to_not eq [] }
|
87
|
-
it { expect(guide.next_exercise(user)).to_not be nil }
|
88
|
-
it { expect(guide.pending_exercises(user)).to include(alternative_exercise) }
|
89
|
-
it { expect(exercise_with_guide.next_for(user)).to eq alternative_exercise }
|
90
|
-
it { expect(guide.exercises).to_not eq [] }
|
91
|
-
it { expect(exercise_with_guide.guide).to eq guide }
|
92
|
-
it { expect(guide.pending_exercises(user)).to include(exercise_with_guide) }
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
describe '#extra' do
|
97
|
-
context 'when exercise has no extra code' do
|
98
|
-
it { expect(exercise.extra).to eq '' }
|
99
|
-
end
|
100
|
-
|
101
|
-
context 'when exercise has extra code and has no guide' do
|
102
|
-
let!(:exercise_with_extra) { create(:exercise, extra: 'exercise extra code') }
|
103
|
-
|
104
|
-
it { expect(exercise_with_extra.extra).to eq "exercise extra code\n" }
|
105
|
-
end
|
106
|
-
|
107
|
-
context 'when exercise has extra code and ends with new line and has no guide' do
|
108
|
-
let!(:exercise_with_extra) { create(:exercise, extra: "exercise extra code\n") }
|
109
|
-
|
110
|
-
it { expect(exercise_with_extra.extra).to eq "exercise extra code\n" }
|
111
|
-
end
|
112
|
-
|
113
|
-
context 'when exercise has extra code and belongs to a guide with no extra code' do
|
114
|
-
let!(:exercise_with_extra) { create(:exercise, guide: guide, extra: 'exercise extra code') }
|
115
|
-
let!(:guide) { create(:guide) }
|
116
|
-
|
117
|
-
it { expect(exercise_with_extra.extra).to eq "exercise extra code\n" }
|
118
|
-
end
|
119
|
-
|
120
|
-
context 'when exercise has extra code with trailing whitespaces
|
121
|
-
and belongs to a guide with no extra code' do
|
122
|
-
let!(:exercise_with_extra) { create(:exercise, guide: guide, extra: "\nexercise extra code \n") }
|
123
|
-
let!(:guide) { create(:guide) }
|
124
|
-
|
125
|
-
it { expect(exercise_with_extra.extra).to eq "exercise extra code\n" }
|
126
|
-
end
|
127
|
-
|
128
|
-
context 'when exercise has extra code and belongs to a guide with extra code' do
|
129
|
-
let!(:exercise_with_extra) { create(:exercise, guide: guide, extra: 'exercise extra code') }
|
130
|
-
let!(:guide) { create(:guide, extra: 'guide extra code') }
|
131
|
-
|
132
|
-
it { expect(exercise_with_extra.extra).to eq "guide extra code\nexercise extra code\n" }
|
133
|
-
it { expect(exercise_with_extra[:extra]).to eq 'exercise extra code' }
|
134
|
-
end
|
135
|
-
end
|
136
|
-
|
137
|
-
describe '#extra_preview' do
|
138
|
-
let(:haskell) { create(:haskell) }
|
139
|
-
let(:guide) { create(:guide,
|
140
|
-
extra: 'f x = 1',
|
141
|
-
language: haskell,
|
142
|
-
exercises: [create(:exercise,
|
143
|
-
extra: 'g y = y + 3',
|
144
|
-
language: haskell)]) }
|
145
|
-
let(:exercise) { guide.exercises.first }
|
146
|
-
|
147
|
-
it { expect(exercise.assignment_for(user).extra_preview).to eq "```haskell\nf x = 1\ng y = y + 3\n```" }
|
148
|
-
end
|
149
|
-
|
150
|
-
describe '#submit_solution!' do
|
151
|
-
context 'when user did a submission' do
|
152
|
-
before { exercise.submit_solution!(user) }
|
153
|
-
it { expect(exercise.find_assignment_for(user)).to be_present }
|
154
|
-
end
|
155
|
-
context 'when user did no submission' do
|
156
|
-
it { expect(exercise.find_assignment_for(user)).to be_blank }
|
157
|
-
end
|
158
|
-
end
|
159
|
-
|
160
|
-
describe '#destroy' do
|
161
|
-
context 'when there are no submissions' do
|
162
|
-
it { exercise.destroy! }
|
163
|
-
end
|
164
|
-
|
165
|
-
context 'when there are submissions' do
|
166
|
-
let!(:assignment) { create(:assignment, exercise: exercise) }
|
167
|
-
before { exercise.destroy! }
|
168
|
-
it { expect { Assignment.find(assignment.id) }.to raise_error(ActiveRecord::RecordNotFound) }
|
169
|
-
end
|
170
|
-
|
171
|
-
end
|
172
|
-
|
173
|
-
describe '#previous_solution_for' do
|
174
|
-
context 'when user has a single submission for the exercise' do
|
175
|
-
let!(:assignment) { exercise.submit_solution!(user, content: 'foo') }
|
176
|
-
|
177
|
-
it { expect(assignment.current_content).to eq assignment.solution }
|
178
|
-
end
|
179
|
-
|
180
|
-
context 'when user has no submissions for the exercise' do
|
181
|
-
it { expect(exercise.assignment_for(user).current_content).to eq '' }
|
182
|
-
end
|
183
|
-
|
184
|
-
context 'when using an interpolation' do
|
185
|
-
let!(:chapter) {
|
186
|
-
create(:chapter, lessons: [
|
187
|
-
create(:lesson, exercises: [
|
188
|
-
first_exercise,
|
189
|
-
second_exercise,
|
190
|
-
previous_exercise,
|
191
|
-
exercise
|
192
|
-
])]) }
|
193
|
-
|
194
|
-
let(:first_exercise) { create(:exercise) }
|
195
|
-
let(:second_exercise) { create(:exercise) }
|
196
|
-
let(:previous_exercise) { create(:exercise) }
|
197
|
-
|
198
|
-
before { reindex_current_organization! }
|
199
|
-
|
200
|
-
context 'when interpolation is in default_content' do
|
201
|
-
let(:assignment) { exercise.assignment_for(user) }
|
202
|
-
|
203
|
-
describe 'right previous content' do
|
204
|
-
let(:exercise) { create(:exercise, default_content: interpolation) }
|
205
|
-
|
206
|
-
context 'using previousContent' do
|
207
|
-
let(:interpolation) { '/*...previousContent...*/' }
|
208
|
-
|
209
|
-
context 'has previous submission' do
|
210
|
-
before { previous_exercise.submit_solution!(user, content: 'foobar') }
|
211
|
-
it { expect(assignment.current_content).to eq 'foobar' }
|
212
|
-
end
|
213
|
-
|
214
|
-
context 'does not have previous submission' do
|
215
|
-
it { expect(assignment.current_content).to eq '' }
|
216
|
-
end
|
217
|
-
end
|
218
|
-
context 'using previousSolution' do
|
219
|
-
let(:interpolation) { '/*...previousSolution...*/' }
|
220
|
-
|
221
|
-
context 'has previous submission' do
|
222
|
-
before { previous_exercise.submit_solution!(user, content: 'foobar') }
|
223
|
-
it { expect(assignment.current_content).to eq 'foobar' }
|
224
|
-
end
|
225
|
-
|
226
|
-
context 'does not have previous submission' do
|
227
|
-
it { expect(assignment.current_content).to eq '' }
|
228
|
-
end
|
229
|
-
end
|
230
|
-
end
|
231
|
-
|
232
|
-
describe 'indexed previous content' do
|
233
|
-
context '-2 index' do
|
234
|
-
let(:exercise) { create(:exercise, default_content: '/*...solution[-2]...*/') }
|
235
|
-
|
236
|
-
context 'has previous submission' do
|
237
|
-
before { second_exercise.submit_solution!(user, content: 'foobar') }
|
238
|
-
it { expect(assignment.current_content).to eq 'foobar' }
|
239
|
-
end
|
240
|
-
end
|
241
|
-
context '-1 index' do
|
242
|
-
let(:exercise) { create(:exercise, default_content: '/*...solution[-1]...*/') }
|
243
|
-
|
244
|
-
context 'has previous submission' do
|
245
|
-
before { previous_exercise.submit_solution!(user, content: 'foobar') }
|
246
|
-
it { expect(assignment.current_content).to eq 'foobar' }
|
247
|
-
end
|
248
|
-
|
249
|
-
context 'does not have previous submission' do
|
250
|
-
it { expect(assignment.current_content).to eq '' }
|
251
|
-
end
|
252
|
-
end
|
253
|
-
context '1 index' do
|
254
|
-
let(:exercise) { create(:exercise, default_content: '/*...solution[1]...*/') }
|
255
|
-
|
256
|
-
context 'has previous submission' do
|
257
|
-
before { first_exercise.submit_solution!(user, content: 'foobar') }
|
258
|
-
it { expect(assignment.current_content).to eq 'foobar' }
|
259
|
-
end
|
260
|
-
end
|
261
|
-
context '2 index' do
|
262
|
-
let(:exercise) { create(:exercise, default_content: '/*...solution[2]...*/') }
|
263
|
-
|
264
|
-
context 'has previous submission' do
|
265
|
-
before { second_exercise.submit_solution!(user, content: 'foobar') }
|
266
|
-
it { expect(assignment.current_content).to eq 'foobar' }
|
267
|
-
end
|
268
|
-
end
|
269
|
-
context '3 index' do
|
270
|
-
let(:exercise) { create(:exercise, default_content: '/*...solution[3]...*/') }
|
271
|
-
|
272
|
-
context 'has previous submission' do
|
273
|
-
before { previous_exercise.submit_solution!(user, content: 'foobar') }
|
274
|
-
it { expect(assignment.current_content).to eq 'foobar' }
|
275
|
-
end
|
276
|
-
end
|
277
|
-
end
|
278
|
-
end
|
279
|
-
context 'when interpolation is in test' do
|
280
|
-
let(:assignment) { exercise.assignment_for(user) }
|
281
|
-
|
282
|
-
context 'using user_first_name' do
|
283
|
-
let(:exercise) { create(:exercise, test: "<div>Hola #{interpolation}</div>") }
|
284
|
-
let(:interpolation) { '/*...user_first_name...*/' }
|
285
|
-
|
286
|
-
it { expect(assignment.test).to eq "<div>Hola Orlo</div>" }
|
287
|
-
end
|
288
|
-
|
289
|
-
context 'and test is nil' do
|
290
|
-
let(:exercise) { create(:exercise, test: nil, expectations: [{binding: "program", inspection: 'Uses:foo'}]) }
|
291
|
-
|
292
|
-
it { expect(assignment.test).to eq nil }
|
293
|
-
end
|
294
|
-
end
|
295
|
-
context 'when interpolation is in extra' do
|
296
|
-
let(:assignment) { exercise.assignment_for(user) }
|
297
|
-
|
298
|
-
describe 'right previous content' do
|
299
|
-
let(:exercise) { create(:exercise, extra: interpolation) }
|
300
|
-
|
301
|
-
context 'using previousContent' do
|
302
|
-
let(:interpolation) { '/*...previousContent...*/' }
|
303
|
-
|
304
|
-
context 'has previous submission' do
|
305
|
-
before { previous_exercise.submit_solution!(user, content: 'foobar') }
|
306
|
-
it { expect(assignment.extra).to eq "foobar\n" }
|
307
|
-
end
|
308
|
-
|
309
|
-
context 'does not have previous submission' do
|
310
|
-
it { expect(assignment.extra).to eq "\n" }
|
311
|
-
end
|
312
|
-
end
|
313
|
-
context 'using previousSolution' do
|
314
|
-
let(:interpolation) { '/*...previousSolution...*/' }
|
315
|
-
|
316
|
-
context 'has previous submission' do
|
317
|
-
before { previous_exercise.submit_solution!(user, content: 'foobar') }
|
318
|
-
it { expect(assignment.extra).to eq "foobar\n" }
|
319
|
-
end
|
320
|
-
|
321
|
-
context 'does not have previous submission' do
|
322
|
-
it { expect(assignment.extra).to eq "\n" }
|
323
|
-
end
|
324
|
-
end
|
325
|
-
end
|
326
|
-
|
327
|
-
describe 'indexed previous content' do
|
328
|
-
context '-2 index' do
|
329
|
-
let(:exercise) { create(:exercise, extra: '/*...solution[-2]...*/') }
|
330
|
-
|
331
|
-
context 'has previous submission' do
|
332
|
-
before { second_exercise.submit_solution!(user, content: 'foobar') }
|
333
|
-
it { expect(assignment.extra).to eq "foobar\n" }
|
334
|
-
end
|
335
|
-
end
|
336
|
-
context '-1 index' do
|
337
|
-
let(:exercise) { create(:exercise, extra: '/*...solution[-1]...*/') }
|
338
|
-
|
339
|
-
context 'has previous submission' do
|
340
|
-
before { previous_exercise.submit_solution!(user, content: 'foobar') }
|
341
|
-
it { expect(assignment.extra).to eq "foobar\n" }
|
342
|
-
end
|
343
|
-
|
344
|
-
context 'does not have previous submission' do
|
345
|
-
it { expect(assignment.extra).to eq "\n" }
|
346
|
-
end
|
347
|
-
end
|
348
|
-
context '1 index' do
|
349
|
-
let(:exercise) { create(:exercise, extra: '/*...solution[1]...*/') }
|
350
|
-
|
351
|
-
context 'has previous submission' do
|
352
|
-
before { first_exercise.submit_solution!(user, content: 'foobar') }
|
353
|
-
it { expect(assignment.extra).to eq "foobar\n" }
|
354
|
-
end
|
355
|
-
end
|
356
|
-
context '2 index' do
|
357
|
-
let(:exercise) { create(:exercise, extra: '/*...solution[2]...*/') }
|
358
|
-
|
359
|
-
context 'has previous submission' do
|
360
|
-
before { second_exercise.submit_solution!(user, content: 'foobar') }
|
361
|
-
it { expect(assignment.extra).to eq "foobar\n" }
|
362
|
-
end
|
363
|
-
end
|
364
|
-
context '3 index' do
|
365
|
-
let(:exercise) { create(:exercise, extra: '/*...solution[3]...*/') }
|
366
|
-
|
367
|
-
context 'has previous submission' do
|
368
|
-
before { previous_exercise.submit_solution!(user, content: 'foobar') }
|
369
|
-
it { expect(assignment.extra).to eq "foobar\n" }
|
370
|
-
end
|
371
|
-
end
|
372
|
-
end
|
373
|
-
end
|
374
|
-
end
|
375
|
-
|
376
|
-
context 'when user has multiple submission for the exercise' do
|
377
|
-
before { exercise.submit_solution!(user, content: 'foo') }
|
378
|
-
let!(:assignment) { exercise.submit_solution!(user, content: 'bar') }
|
379
|
-
|
380
|
-
it { expect(assignment.current_content).to eq assignment.solution }
|
381
|
-
end
|
382
|
-
|
383
|
-
context 'when user has no solution and exercise has default content' do
|
384
|
-
let(:exercise) { create(:exercise, default_content: '#write here...') }
|
385
|
-
let(:assignment) { exercise.assignment_for user }
|
386
|
-
|
387
|
-
it { expect(assignment.current_content).to eq '#write here...' }
|
388
|
-
end
|
389
|
-
end
|
390
|
-
|
391
|
-
describe '#guide_done_for?' do
|
392
|
-
|
393
|
-
context 'when it does not belong to a guide' do
|
394
|
-
it { expect(exercise.guide_done_for?(user)).to be false }
|
395
|
-
end
|
396
|
-
|
397
|
-
context 'when it belongs to a guide unfinished' do
|
398
|
-
let!(:guide) { create(:guide) }
|
399
|
-
let!(:exercise_unfinished) { create(:exercise, guide: guide) }
|
400
|
-
let!(:exercise_finished) { create(:exercise, guide: guide) }
|
401
|
-
|
402
|
-
before do
|
403
|
-
exercise_finished.submit_solution!(user, content: 'foo').passed!
|
404
|
-
end
|
405
|
-
|
406
|
-
it { expect(exercise_finished.guide_done_for?(user)).to be false }
|
407
|
-
end
|
408
|
-
|
409
|
-
context 'when it belongs to a guide unfinished' do
|
410
|
-
let!(:guide) { create(:guide) }
|
411
|
-
let!(:exercise_finished) { create(:exercise, guide: guide) }
|
412
|
-
let!(:exercise_finished2) { create(:exercise, guide: guide) }
|
413
|
-
|
414
|
-
before do
|
415
|
-
exercise_finished.submit_solution!(user, content: 'foo').passed!
|
416
|
-
exercise_finished2.submit_solution!(user, content: 'foo').passed!
|
417
|
-
end
|
418
|
-
|
419
|
-
it { expect(exercise_finished.guide_done_for?(user)).to be true }
|
420
|
-
end
|
421
|
-
end
|
422
|
-
|
423
|
-
describe '#language' do
|
424
|
-
let(:guide) { create(:guide) }
|
425
|
-
let(:exercise_with_guide) { create(:exercise, guide: guide, language: guide.language) }
|
426
|
-
let(:other_language) { create(:language) }
|
427
|
-
|
428
|
-
context 'when has no guide' do
|
429
|
-
it { expect(exercise.valid?).to be true }
|
430
|
-
end
|
431
|
-
|
432
|
-
context 'when has guide and is consistent' do
|
433
|
-
it { expect(exercise_with_guide.valid?).to be true }
|
434
|
-
end
|
435
|
-
end
|
436
|
-
|
437
|
-
describe '#friendly_name' do
|
438
|
-
it { expect(Exercise.find(exercise.friendly_name)).to eq exercise }
|
439
|
-
it { expect(Problem.find(exercise.friendly_name)).to eq exercise }
|
440
|
-
end
|
441
|
-
|
442
|
-
describe 'messages_path_for' do
|
443
|
-
let(:haskell) { create(:haskell) }
|
444
|
-
let(:problem) { create(:problem, bibliotheca_id: 32, guide: guide, language: haskell) }
|
445
|
-
let(:guide) { create(:guide, slug: 'mumuki/myguide') }
|
446
|
-
|
447
|
-
context 'user with email uid' do
|
448
|
-
let(:student) { create(:user, uid: 'foo@bar.com') }
|
449
|
-
|
450
|
-
it { expect(problem.messages_path_for(student))
|
451
|
-
.to eq 'api/guides/mumuki/myguide/32/student/foo@bar.com/messages?language=haskell' }
|
452
|
-
it { expect(problem.messages_url_for(student))
|
453
|
-
.to eq 'http://test.classroom-api.localmumuki.io/api/guides/mumuki/myguide/32/student/foo@bar.com/messages?language=haskell' }
|
454
|
-
end
|
455
|
-
|
456
|
-
context 'user with twitter uid' do
|
457
|
-
let(:student) { create(:user, uid: 'twitter|12134342') }
|
458
|
-
|
459
|
-
it { expect(problem.messages_url_for(student))
|
460
|
-
.to eq 'http://test.classroom-api.localmumuki.io/api/guides/mumuki/myguide/32/student/twitter%7C12134342/messages?language=haskell' }
|
461
|
-
end
|
462
|
-
end
|
463
|
-
|
464
|
-
describe '#splitted_description' do
|
465
|
-
let(:exercise) { create(:exercise, description: "**Foo**\n\n> _Bar_") }
|
466
|
-
it { expect(exercise.description_context).to eq "<p><strong>Foo</strong></p>\n" }
|
467
|
-
it { expect(exercise.description_task).to eq "<p><em>Bar</em></p>\n" }
|
468
|
-
end
|
469
|
-
|
470
|
-
describe '#validate!' do
|
471
|
-
context 'non-empty, valid randomizations' do
|
472
|
-
let(:exercise) { build(:exercise,
|
473
|
-
randomizations: {
|
474
|
-
some_word: { type: :one_of, value: %w('some' 'word') },
|
475
|
-
some_number: { type: :range, value: [1, 10] } }) }
|
476
|
-
it { expect { exercise.validate! }.not_to raise_error }
|
477
|
-
end
|
478
|
-
|
479
|
-
context 'empty inspections' do
|
480
|
-
let(:exercise) { build(:exercise, expectations: [{ "binding" => "program", "inspection" => "" }]) }
|
481
|
-
it { expect { exercise.validate! }.to raise_error(/expectations format is invalid/i) }
|
482
|
-
end
|
483
|
-
|
484
|
-
context 'invalid assistance_rules' do
|
485
|
-
let(:exercise) { build(:exercise, assistance_rules: [{ when: 'content_empty', then: ['asd'] }]) }
|
486
|
-
it { expect { exercise.validate! }.to raise_error(/assistance rules format is invalid/i) }
|
487
|
-
end
|
488
|
-
|
489
|
-
context 'invalid randomizations' do
|
490
|
-
let(:exercise) { build(:exercise, randomizations: { type: :range, value: [1] }) }
|
491
|
-
it { expect { exercise.validate! }.to raise_error(/randomizations format is invalid/i) }
|
492
|
-
end
|
493
|
-
end
|
494
|
-
end
|