mumuki-laboratory 5.6.3 → 5.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/assets/javascripts/application/bridge.js +1 -1
- data/app/assets/javascripts/application/codemirror-builder.js +33 -10
- data/app/assets/javascripts/application/discussions.js +12 -0
- data/app/assets/stylesheets/application/modules/_discussion.scss +19 -0
- data/app/controllers/application_controller.rb +1 -0
- data/app/controllers/exercises_controller.rb +1 -0
- data/app/helpers/application_helper.rb +4 -0
- data/app/helpers/discussions_helper.rb +1 -2
- data/app/helpers/progress_helper.rb +10 -9
- data/app/models/assignment.rb +9 -1
- data/app/models/concerns/submittable/solvable.rb +1 -5
- data/app/models/concerns/with_assignments.rb +5 -1
- data/app/models/discussion.rb +4 -2
- data/app/models/submission/submission.rb +1 -0
- data/app/views/discussions/_description_message.html.erb +17 -0
- data/app/views/discussions/_message.html.erb +1 -1
- data/app/views/discussions/_new_message.html.erb +3 -1
- data/app/views/discussions/show.html.erb +3 -0
- data/app/views/exercise_solutions/_results_button.html.erb +7 -5
- data/app/views/exercises/show.html.erb +2 -2
- data/app/views/layouts/_main.html.erb +63 -0
- data/app/views/layouts/application.html.erb +50 -105
- data/app/views/layouts/embedded.html.erb +1 -0
- data/app/views/layouts/exercise_inputs/editors/_multiple_files.html.erb +1 -1
- data/lib/mumuki/laboratory.rb +1 -0
- data/lib/mumuki/laboratory/controllers.rb +1 -0
- data/lib/mumuki/laboratory/controllers/embedded_mode.rb +45 -0
- data/lib/mumuki/laboratory/extensions/string.rb +4 -0
- data/lib/mumuki/laboratory/file.rb +18 -0
- data/lib/mumuki/laboratory/locales/en.yml +5 -1
- data/lib/mumuki/laboratory/locales/es.yml +10 -0
- data/lib/mumuki/laboratory/locales/pt.yml +4 -0
- data/lib/mumuki/laboratory/status/discussion/opened.rb +6 -2
- data/lib/mumuki/laboratory/status/discussion/pending_review.rb +1 -1
- data/lib/mumuki/laboratory/status/discussion/solved.rb +1 -1
- data/lib/mumuki/laboratory/version.rb +1 -1
- data/spec/controllers/exercise_solutions_controller_spec.rb +12 -3
- data/spec/features/exercise_flow_spec.rb +21 -0
- data/spec/models/discussion_spec.rb +12 -4
- data/spec/models/exercise_spec.rb +15 -1
- metadata +9 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: eebf90572caa6242083ad55e7af966691ffadd00c9ea6cf4b9ccd5ce3ef89f22
|
4
|
+
data.tar.gz: a7a348cc2799dbbf19ab01be6f9b4edace17979ce784f124ef695db12a6ba1c2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a02f3ef36c6f80aa9b88b508b579e4d9479c4c43d04c8c120928394ca59a222192a31a7d8ec816d4ac6ccc18fd6e13d36913e819b959b10bbf26875ea95e9ebc
|
7
|
+
data.tar.gz: bc5ae60058112ffac03761ee34567349a3b34348f3a2f90c334b607fa97bd84e1e82af08886c828f607ac010d1be50eec4f0504fa83bc9ca77ce4506d5aded95
|
@@ -23,7 +23,7 @@ var mumuki = mumuki || {};
|
|
23
23
|
var token = new mumuki.CsrfToken();
|
24
24
|
var request = token.newRequest({
|
25
25
|
type: 'POST',
|
26
|
-
url: window.location.origin + window.location.pathname + '/solutions',
|
26
|
+
url: window.location.origin + window.location.pathname + '/solutions' + window.location.search,
|
27
27
|
data: solution
|
28
28
|
});
|
29
29
|
|
@@ -13,18 +13,28 @@ var mumuki = mumuki || {};
|
|
13
13
|
$('.btn-submit').click();
|
14
14
|
}
|
15
15
|
|
16
|
+
function submitMessage() {
|
17
|
+
$('.discussion-new-message-button').click();
|
18
|
+
}
|
19
|
+
|
20
|
+
var codeMirrorDefaults = {
|
21
|
+
autofocus: false,
|
22
|
+
tabSize: 2,
|
23
|
+
cursorHeight: 1,
|
24
|
+
matchBrackets: true,
|
25
|
+
lineWiseCopyCut: true,
|
26
|
+
autoCloseBrackets: true,
|
27
|
+
showCursorWhenSelecting: true,
|
28
|
+
lineWrapping: true
|
29
|
+
};
|
30
|
+
|
16
31
|
CodeMirrorBuilder.prototype = {
|
32
|
+
createEditor: function (customOptions) {
|
33
|
+
return CodeMirror.fromTextArea(this.textarea, Object.assign({}, codeMirrorDefaults, customOptions));
|
34
|
+
},
|
17
35
|
setupEditor: function () {
|
18
|
-
this.editor =
|
19
|
-
autofocus: false,
|
20
|
-
tabSize: 2,
|
36
|
+
this.editor = this.createEditor({
|
21
37
|
lineNumbers: true,
|
22
|
-
lineWrapping: true,
|
23
|
-
cursorHeight: 1,
|
24
|
-
matchBrackets: true,
|
25
|
-
lineWiseCopyCut: true,
|
26
|
-
autoCloseBrackets: true,
|
27
|
-
showCursorWhenSelecting: true,
|
28
38
|
extraKeys: {
|
29
39
|
'Ctrl-Space': 'autocomplete',
|
30
40
|
'Cmd-Enter': submit,
|
@@ -38,12 +48,25 @@ var mumuki = mumuki || {};
|
|
38
48
|
}
|
39
49
|
});
|
40
50
|
},
|
51
|
+
setupSimpleEditor: function () {
|
52
|
+
this.editor = this.createEditor({
|
53
|
+
mode: 'text',
|
54
|
+
extraKeys: {
|
55
|
+
'Cmd-Enter': submitMessage,
|
56
|
+
'Ctrl-Enter': submitMessage,
|
57
|
+
'Tab': function (cm) {
|
58
|
+
mumuki.editor.indentWithSpaces(cm)
|
59
|
+
}
|
60
|
+
}
|
61
|
+
});
|
62
|
+
},
|
41
63
|
setupLanguage: function () {
|
42
64
|
var language = this.$textarea.data('editor-language');
|
43
65
|
if (language === 'dynamic') {
|
44
66
|
mumuki.page.dynamicEditors.push(this.editor);
|
45
67
|
} else {
|
46
|
-
|
68
|
+
this.editor.setOption('mode', language);
|
69
|
+
this.editor.refresh();
|
47
70
|
}
|
48
71
|
},
|
49
72
|
setupOptions: function (minLines) {
|
@@ -14,6 +14,18 @@ mumuki.load(function () {
|
|
14
14
|
var $subscriptionSpans = $('.discussion-subscription > span');
|
15
15
|
var $upvoteSpans = $('.discussion-upvote > span');
|
16
16
|
|
17
|
+
function createNewMessageEditor() {
|
18
|
+
var $textarea = $("#new-discussion-message");
|
19
|
+
var textarea = $textarea[0];
|
20
|
+
if(!textarea) return;
|
21
|
+
var builder = new mumuki.editor.CodeMirrorBuilder(textarea);
|
22
|
+
builder.setupSimpleEditor();
|
23
|
+
builder.setupOptions($textarea.data('lines'));
|
24
|
+
builder.build();
|
25
|
+
}
|
26
|
+
|
27
|
+
createNewMessageEditor();
|
28
|
+
|
17
29
|
var Forum = {
|
18
30
|
toggleButton: function (spans) {
|
19
31
|
spans.toggleClass('hidden');
|
@@ -242,12 +242,31 @@ $moderator-star-color: #dd9900;
|
|
242
242
|
|
243
243
|
.discussion-message-content {
|
244
244
|
padding: 15px;
|
245
|
+
p {
|
246
|
+
margin: 0px;
|
247
|
+
white-space: pre-wrap;
|
248
|
+
}
|
245
249
|
}
|
246
250
|
|
247
251
|
.discussion-new-message-content {
|
248
252
|
resize: none;
|
253
|
+
min-height: 150px;
|
249
254
|
border-radius: 0;
|
250
255
|
border-color: transparent;
|
256
|
+
overflow: hidden;
|
257
|
+
text-overflow: ellipsis;
|
258
|
+
white-space: nowrap;
|
259
|
+
.CodeMirror {
|
260
|
+
border: unset;
|
261
|
+
color: $brand-primary;
|
262
|
+
font-family: $font-family-sans-serif;
|
263
|
+
padding: 15px;
|
264
|
+
}
|
265
|
+
.CodeMirror-wrap,
|
266
|
+
.CodeMirror-scroll {
|
267
|
+
height: auto !important;
|
268
|
+
min-height: 100px !important;
|
269
|
+
}
|
251
270
|
&:focus {
|
252
271
|
border-color: white;
|
253
272
|
}
|
@@ -7,6 +7,7 @@ class ApplicationController < ActionController::Base
|
|
7
7
|
include Mumuki::Laboratory::Controllers::Authorization
|
8
8
|
include Mumuki::Laboratory::Controllers::Notifications
|
9
9
|
include Mumuki::Laboratory::Controllers::DynamicErrors
|
10
|
+
include Mumuki::Laboratory::Controllers::EmbeddedMode
|
10
11
|
|
11
12
|
before_action :set_current_organization!
|
12
13
|
before_action :set_locale!
|
@@ -15,6 +15,10 @@ module ApplicationHelper
|
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
18
|
+
def profile_picture
|
19
|
+
image_tag(@current_user.image_url, height: 40, class: 'img-circle', onError: "this.onerror = null; this.src = '#{image_url('user_shape.png')}'")
|
20
|
+
end
|
21
|
+
|
18
22
|
def paginate(object, options={})
|
19
23
|
"<div class=\"text-center\">#{super(object, {theme: 'twitter-bootstrap-3'}.merge(options))}</div>".html_safe
|
20
24
|
end
|
@@ -1,18 +1,19 @@
|
|
1
1
|
module ProgressHelper
|
2
2
|
def lesson_practice_key_for(stats)
|
3
|
-
|
4
|
-
:continue_lesson
|
5
|
-
else
|
6
|
-
:start_lesson
|
7
|
-
end
|
3
|
+
key_or_default stats, :started?, :continue_lesson, :start_lesson
|
8
4
|
end
|
9
5
|
|
10
|
-
#FIXME refactorme: similar methods
|
11
6
|
def book_practice_key_for(student)
|
12
|
-
|
13
|
-
|
7
|
+
key_or_default student, :last_exercise, :continue_practicing, :start_practicing
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def key_or_default(object, property, key, default_key)
|
13
|
+
if object.try(property)
|
14
|
+
key
|
14
15
|
else
|
15
|
-
|
16
|
+
default_key
|
16
17
|
end
|
17
18
|
end
|
18
19
|
end
|
data/app/models/assignment.rb
CHANGED
@@ -60,6 +60,10 @@ class Assignment < ApplicationRecord
|
|
60
60
|
end
|
61
61
|
end
|
62
62
|
|
63
|
+
def test
|
64
|
+
exercise.test_for submitter
|
65
|
+
end
|
66
|
+
|
63
67
|
def extra
|
64
68
|
exercise.extra_for submitter
|
65
69
|
end
|
@@ -90,11 +94,15 @@ class Assignment < ApplicationRecord
|
|
90
94
|
update! result: message, submission_status: :errored
|
91
95
|
end
|
92
96
|
|
93
|
-
%w(query try
|
97
|
+
%w(query try).each do |key|
|
94
98
|
name = "run_#{key}!"
|
95
99
|
define_method(name) { |params| exercise.send name, params.merge(extra: extra) }
|
96
100
|
end
|
97
101
|
|
102
|
+
def run_tests!(params)
|
103
|
+
exercise.run_tests! params.merge(extra: extra, test: test)
|
104
|
+
end
|
105
|
+
|
98
106
|
def as_platform_json
|
99
107
|
navigable_parent = exercise.navigable_parent
|
100
108
|
as_json(except: [:exercise_id, :submission_id, :id, :submitter_id, :solution, :created_at, :updated_at, :submission_status],
|
@@ -15,7 +15,7 @@ module WithAssignments
|
|
15
15
|
.directives_sections
|
16
16
|
.split_sections(assignment_for(user)&.solution || default_content_for(user))
|
17
17
|
.except('content')
|
18
|
-
.map { |name, content|
|
18
|
+
.map { |name, content| Mumuki::Laboratory::File.new name, content }
|
19
19
|
end
|
20
20
|
|
21
21
|
def interpolate_for(user, field)
|
@@ -30,6 +30,10 @@ module WithAssignments
|
|
30
30
|
interpolate_for user, extra
|
31
31
|
end
|
32
32
|
|
33
|
+
def test_for(user)
|
34
|
+
test && interpolate_for(user, test)
|
35
|
+
end
|
36
|
+
|
33
37
|
def replace_content_reference(user, interpolee)
|
34
38
|
case interpolee
|
35
39
|
when /previousContent|previousSolution/
|
data/app/models/discussion.rb
CHANGED
@@ -13,6 +13,8 @@ class Discussion < ApplicationRecord
|
|
13
13
|
before_save :capitalize_title
|
14
14
|
validates_presence_of :title
|
15
15
|
|
16
|
+
markdown_on :description
|
17
|
+
|
16
18
|
sortable :created_at, :upvotes_count, default: :created_at_desc
|
17
19
|
filterable :status, :language
|
18
20
|
pageable
|
@@ -43,7 +45,7 @@ class Discussion < ApplicationRecord
|
|
43
45
|
end
|
44
46
|
|
45
47
|
def commentable_by?(user)
|
46
|
-
opened? && user.present?
|
48
|
+
user&.moderator? || (opened? && user.present?)
|
47
49
|
end
|
48
50
|
|
49
51
|
def subscribable?
|
@@ -110,7 +112,7 @@ class Discussion < ApplicationRecord
|
|
110
112
|
end
|
111
113
|
|
112
114
|
def has_messages?
|
113
|
-
messages.exists?
|
115
|
+
messages.exists? || description.present?
|
114
116
|
end
|
115
117
|
|
116
118
|
def responses_count
|
@@ -0,0 +1,17 @@
|
|
1
|
+
<%= render layout: 'discussions/message_container', locals: {user: discussion.initiator} do %>
|
2
|
+
<div class="discussion-message-bubble">
|
3
|
+
<div class="discussion-message-bubble-header">
|
4
|
+
<div class="discussion-message-bubble-title">
|
5
|
+
<%= discussion.initiator.name %>
|
6
|
+
<span class="message-date">
|
7
|
+
<%= t(:time_since, time: time_ago_in_words(discussion.created_at)) %>
|
8
|
+
</span>
|
9
|
+
</div>
|
10
|
+
</div>
|
11
|
+
<div class="discussion-message-bubble-content">
|
12
|
+
<div class="discussion-message-content">
|
13
|
+
<%= discussion.description_html %>
|
14
|
+
</div>
|
15
|
+
</div>
|
16
|
+
</div>
|
17
|
+
<% end %>
|
@@ -9,7 +9,9 @@
|
|
9
9
|
<div class="discussion-message-bubble-content">
|
10
10
|
<div class="container-fluid">
|
11
11
|
<div class="row">
|
12
|
-
<div
|
12
|
+
<div class="discussion-new-message-content">
|
13
|
+
<%= f.editor :content, '', { id: 'new-discussion-message', class: 'form-control', placeholder: t(:message) } %>
|
14
|
+
</div>
|
13
15
|
</div>
|
14
16
|
</div>
|
15
17
|
</div>
|
@@ -56,6 +56,9 @@
|
|
56
56
|
|
57
57
|
<% if @discussion.has_messages? %>
|
58
58
|
<div class="discussion-messages">
|
59
|
+
<% if @discussion.description.present? %>
|
60
|
+
<%= render partial: 'discussions/description_message', locals: {discussion: @discussion} %>
|
61
|
+
<% end %>
|
59
62
|
<% @discussion.messages.each do |message| %>
|
60
63
|
<%= render partial: 'discussions/message', locals: {user: message.sender_user, message: message} %>
|
61
64
|
<% end %>
|
@@ -1,7 +1,9 @@
|
|
1
|
-
|
2
|
-
<div class="
|
3
|
-
<div class="
|
4
|
-
|
1
|
+
<% if standalone_mode? %>
|
2
|
+
<div class="row">
|
3
|
+
<div class="col-md-12">
|
4
|
+
<div class="actions">
|
5
|
+
<%= next_exercise_button(@exercise) %>
|
6
|
+
</div>
|
5
7
|
</div>
|
6
8
|
</div>
|
7
|
-
|
9
|
+
<% end %>
|
@@ -1,7 +1,7 @@
|
|
1
1
|
<%= render_runner_assets @exercise.language, :layout %>
|
2
2
|
|
3
3
|
<%= content_for :breadcrumbs do %>
|
4
|
-
|
4
|
+
<%= breadcrumbs @exercise %>
|
5
5
|
<% end %>
|
6
6
|
|
7
7
|
<%= render partial: 'layouts/authoring', locals: {guide: @guide} %>
|
@@ -28,7 +28,7 @@
|
|
28
28
|
<% end %>
|
29
29
|
|
30
30
|
<div>
|
31
|
-
<% if @stats %>
|
31
|
+
<% if @stats && standalone_mode? %>
|
32
32
|
<%= render partial: 'layouts/progress_bar', locals: {actual: @exercise, guide: @exercise.guide, stats: @stats} %>
|
33
33
|
<% end %>
|
34
34
|
</div>
|
@@ -0,0 +1,63 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html lang="<%= Organization.current.locale %>">
|
3
|
+
<head>
|
4
|
+
<title><%= page_title subject %></title>
|
5
|
+
<% unless input_kids? %>
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
7
|
+
<% end %>
|
8
|
+
|
9
|
+
<link rel="icon" type="image/x-icon" href="<%= Organization.current.favicon_url %>" data-turbolinks-track="reload">
|
10
|
+
<link rel="stylesheet" type="text/css" href="<%= theme_stylesheet_url %>" data-turbolinks-track="reload">
|
11
|
+
|
12
|
+
<meta property="og:site_name" content="<%= Organization.current.site_name %>" />
|
13
|
+
<meta property="og:title" content="<%= page_title subject %>"/>
|
14
|
+
<meta property="og:description" content="<%= Organization.current.central? ? t(:mumuki_short_description) : Organization.current.description %>"/>
|
15
|
+
<meta property="og:type" content="website"/>
|
16
|
+
<meta property="og:image" content="<%= Organization.current.open_graph_image_url %>"/>
|
17
|
+
<meta property="og:url" content="<%= request.original_url %>"/>
|
18
|
+
|
19
|
+
<meta name="turbolinks-cache-control" content="no-cache">
|
20
|
+
|
21
|
+
<meta name="description" content="<%= t :mumuki_short_description %>"/>
|
22
|
+
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
|
23
|
+
<%= csrf_meta_tags %>
|
24
|
+
<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
|
25
|
+
<script type="text/javascript" src="<%= extension_javascript_url %>" defer data-turbolinks-track="reload"></script>
|
26
|
+
<script type="text/javascript">
|
27
|
+
window.mumukiLocale = <%= raw Organization.current.locale_json %>;
|
28
|
+
mumuki.locale = '<%= Organization.current.locale %>';
|
29
|
+
moment.locale('<%= Organization.current.locale %>');
|
30
|
+
</script>
|
31
|
+
<%= login_form.header_html %>
|
32
|
+
</head>
|
33
|
+
<body>
|
34
|
+
<% if should_choose_organization? %>
|
35
|
+
<%= render partial: 'layouts/organization_chooser' %>
|
36
|
+
<% end %>
|
37
|
+
|
38
|
+
<%= yield :navbar %>
|
39
|
+
|
40
|
+
<div class="<%= exercise_container_type %><%= @kids ? '-fluid' : '' %>" id="wrap">
|
41
|
+
<div id="toast-container" class="toast-bottom-left">
|
42
|
+
<% if notice %>
|
43
|
+
<div class="alert toast toast-success" role="alert">
|
44
|
+
<%= notice.html_safe %>
|
45
|
+
<button type="button" class="close" data-dismiss="alert">×</button>
|
46
|
+
</div>
|
47
|
+
<% elsif alert %>
|
48
|
+
<div class="alert toast toast-error" role="alert">
|
49
|
+
<%= alert.html_safe %>
|
50
|
+
<button type="button" class="close" data-dismiss="alert">×</button>
|
51
|
+
</div>
|
52
|
+
<% end %>
|
53
|
+
</div>
|
54
|
+
|
55
|
+
<%= yield %>
|
56
|
+
</div>
|
57
|
+
|
58
|
+
<%= yield :no_container %>
|
59
|
+
|
60
|
+
<%= yield :footer %>
|
61
|
+
|
62
|
+
</body>
|
63
|
+
</html>
|
@@ -1,59 +1,23 @@
|
|
1
|
-
|
2
|
-
<
|
3
|
-
<
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
<meta property="og:description" content="<%= Organization.current.central? ? t(:mumuki_short_description) : Organization.current.description %>"/>
|
15
|
-
<meta property="og:type" content="website"/>
|
16
|
-
<meta property="og:image" content="<%= Organization.current.open_graph_image_url %>"/>
|
17
|
-
<meta property="og:url" content="<%= request.original_url %>"/>
|
18
|
-
|
19
|
-
<meta name="turbolinks-cache-control" content="no-cache">
|
20
|
-
|
21
|
-
<meta name="description" content="<%= t :mumuki_short_description %>"/>
|
22
|
-
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
|
23
|
-
<%= csrf_meta_tags %>
|
24
|
-
<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
|
25
|
-
<script type="text/javascript" src="<%= extension_javascript_url %>" defer data-turbolinks-track="reload"></script>
|
26
|
-
<script type="text/javascript">
|
27
|
-
window.mumukiLocale = <%= raw Organization.current.locale_json %>;
|
28
|
-
mumuki.locale = '<%= Organization.current.locale %>';
|
29
|
-
moment.locale('<%= Organization.current.locale %>');
|
30
|
-
</script>
|
31
|
-
<%= login_form.header_html %>
|
32
|
-
</head>
|
33
|
-
<body>
|
34
|
-
<% if should_choose_organization? %>
|
35
|
-
<%= render partial: 'layouts/organization_chooser' %>
|
36
|
-
<% end %>
|
37
|
-
|
38
|
-
<div class="<%= exercise_container_type %>">
|
39
|
-
<nav class="mu-navbar">
|
40
|
-
<div class="mu-navbar-breadcrumb hidden-xs">
|
41
|
-
<ul class="mu-breadcrumb-list">
|
42
|
-
<%= yield :breadcrumbs %>
|
43
|
-
</ul>
|
44
|
-
</div>
|
45
|
-
<div class="dropdown hamburguer-breadcrumb hidden-sm hidden-md hidden-lg">
|
46
|
-
<% if content_for? :breadcrumbs %>
|
47
|
-
<span class="hamburguer" id="profileDropdown" data-toggle="dropdown">
|
48
|
-
<i class="fa fa-bars"></i>
|
49
|
-
</span>
|
1
|
+
<%= content_for :navbar do %>
|
2
|
+
<div class="<%= exercise_container_type %>">
|
3
|
+
<nav class="mu-navbar">
|
4
|
+
<div class="mu-navbar-breadcrumb hidden-xs">
|
5
|
+
<ul class="mu-breadcrumb-list">
|
6
|
+
<%= yield :breadcrumbs %>
|
7
|
+
</ul>
|
8
|
+
</div>
|
9
|
+
<div class="dropdown hamburguer-breadcrumb hidden-sm hidden-md hidden-lg">
|
10
|
+
<% if content_for? :breadcrumbs %>
|
11
|
+
<span class="hamburguer" id="profileDropdown" data-toggle="dropdown">
|
12
|
+
<i class="fa fa-bars"></i>
|
13
|
+
</span>
|
50
14
|
<ul class="dropdown-menu dropdown-menu-left" aria-labelledby="profileDropdown">
|
51
15
|
<%= yield :breadcrumbs %>
|
52
16
|
</ul>
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
17
|
+
<% end %>
|
18
|
+
</div>
|
19
|
+
<div class="mu-navbar-avatar">
|
20
|
+
<% if current_user? %>
|
57
21
|
<div class="dropdown">
|
58
22
|
<span>
|
59
23
|
<a class="notifications-box <%= has_notifications? ? '' : 'notifications-box-empty' %>" href=<%= "#{user_notifications_path}" %>>
|
@@ -64,7 +28,7 @@
|
|
64
28
|
</div>
|
65
29
|
<div class="dropdown">
|
66
30
|
<span id="profileDropdown" data-toggle="dropdown" aria-label="<%= t(:user) %>" role="menu" tabindex="0">
|
67
|
-
<%=
|
31
|
+
<%= profile_picture %>
|
68
32
|
</span>
|
69
33
|
<ul class="dropdown-menu dropdown-menu-right" aria-labelledby="profileDropdown">
|
70
34
|
<li>
|
@@ -77,64 +41,45 @@
|
|
77
41
|
<li><%= link_to(t(:sign_out), logout_path(origin: url_for), role: 'menuitem') %></li>
|
78
42
|
</ul>
|
79
43
|
</div>
|
80
|
-
|
44
|
+
<% else %>
|
81
45
|
<%= login_button %>
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
</div>
|
86
|
-
|
87
|
-
<div class="<%= exercise_container_type %><%= @kids ? '-fluid' : '' %>" id="wrap">
|
88
|
-
<div id="toast-container" class="toast-bottom-left">
|
89
|
-
<% if notice %>
|
90
|
-
<div class="alert toast toast-success" role="alert">
|
91
|
-
<%= notice.html_safe %>
|
92
|
-
<button type="button" class="close" data-dismiss="alert">×</button>
|
93
|
-
</div>
|
94
|
-
<% elsif alert %>
|
95
|
-
<div class="alert toast toast-error" role="alert">
|
96
|
-
<%= alert.html_safe %>
|
97
|
-
<button type="button" class="close" data-dismiss="alert">×</button>
|
98
|
-
</div>
|
99
|
-
<% end %>
|
46
|
+
<% end %>
|
47
|
+
</div>
|
48
|
+
</nav>
|
100
49
|
</div>
|
50
|
+
<% end %>
|
101
51
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
<%= yield :no_container %>
|
107
|
-
|
108
|
-
<footer class="footer">
|
109
|
-
<div class="<%= exercise_container_type %>">
|
110
|
-
<hr>
|
52
|
+
<% content_for :footer do %>
|
53
|
+
<footer class="footer">
|
54
|
+
<div class="<%= exercise_container_type %>">
|
55
|
+
<hr>
|
111
56
|
|
112
|
-
|
113
|
-
|
114
|
-
|
57
|
+
<div class="row">
|
58
|
+
<div class="col-md-12">
|
59
|
+
<%= yield :authoring %>
|
60
|
+
</div>
|
115
61
|
</div>
|
116
|
-
</div>
|
117
62
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
63
|
+
<div id="footer-copyright" class="row">
|
64
|
+
<div class="col-md-4 text-left">
|
65
|
+
<p>© Copyright 2015-<%= DateTime.now.year %>
|
66
|
+
<a href="http://mumuki.org/" class="mu-org-link"><span class="da da-mumuki-circle"></span> Mumuki Project</a>
|
67
|
+
</p>
|
68
|
+
</div>
|
124
69
|
|
125
|
-
|
126
|
-
|
127
|
-
|
70
|
+
<div class="col-md-4 text-center">
|
71
|
+
<%= login_form.footer_html %>
|
72
|
+
</div>
|
128
73
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
74
|
+
<div id="footer-social" class="col-md-4 text-right" lang="en">
|
75
|
+
<a class="fa fa-facebook social-icon" aria-label="Facebook" href="https://www.facebook.com/MumukiProject" target="_blank"></a>
|
76
|
+
<a class="fa fa-twitter social-icon" aria-label="Twitter" href="https://twitter.com/MumukiProject" target="_blank"></a>
|
77
|
+
<a class="fa fa-github social-icon" aria-label="Github" href="https://github.com/mumuki" target="_blank"></a>
|
78
|
+
<a class="fa fa-linkedin social-icon" aria-label="LinkedIn" href="https://www.linkedin.com/company/mumuki-project" target="_blank"></a>
|
79
|
+
</div>
|
134
80
|
</div>
|
135
|
-
|
136
81
|
</div>
|
137
|
-
</
|
138
|
-
|
139
|
-
|
140
|
-
|
82
|
+
</footer>
|
83
|
+
<% end %>
|
84
|
+
|
85
|
+
<%= render partial: 'layouts/main' %>
|
@@ -0,0 +1 @@
|
|
1
|
+
<%= render partial: 'layouts/main' %>
|
@@ -13,7 +13,7 @@
|
|
13
13
|
<div class="tab-content">
|
14
14
|
<% @files.each_with_index do |file, index| %>
|
15
15
|
<div role="tabpanel" class="tab-pane mu-input-panel <%= 'fade in active' if index == 0 %>" id="editor-file-<%= index %>">
|
16
|
-
<%= form.editor "content[#{file.name}]",
|
16
|
+
<%= form.editor "content[#{file.name}]", file.highlight_mode,
|
17
17
|
placeholder: t(:editor_placeholder),
|
18
18
|
class: 'form-control editor',
|
19
19
|
value: file.content,
|
data/lib/mumuki/laboratory.rb
CHANGED
@@ -5,6 +5,7 @@ require_relative './controllers/authorization'
|
|
5
5
|
require_relative './controllers/current_organization'
|
6
6
|
require_relative './controllers/dynamic_errors'
|
7
7
|
require_relative './controllers/notifications'
|
8
|
+
require_relative './controllers/embedded_mode'
|
8
9
|
require_relative './controllers/nested_in_exercise'
|
9
10
|
require_relative './controllers/results_rendering'
|
10
11
|
require_relative './controllers/exercise_seed'
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# This mixin provides support for implementing the
|
2
|
+
# embedded mode - as opposed to the standalone mode -in which
|
3
|
+
# the mumuki pages
|
4
|
+
#
|
5
|
+
# * are displayed used a simplified layout, called `embedded.html.erb`
|
6
|
+
# * are served using `X-Frame-Options` that allow them to be used within
|
7
|
+
# an iframe
|
8
|
+
#
|
9
|
+
# Not all organizations can be emedded - only those that have the `embeddable?`
|
10
|
+
# setting set.
|
11
|
+
#
|
12
|
+
# This mixin provides two sets of methods:
|
13
|
+
#
|
14
|
+
# * `embedded_mode?` / `standalone_mode?`, which are helpers aimed to be used by views
|
15
|
+
# and change very specific rendering details in one or the other mode
|
16
|
+
# * `enable_embedded_rendering`, which is designed to be called from main-views controller-methods that
|
17
|
+
# actually support embedded mode.
|
18
|
+
module Mumuki::Laboratory::Controllers::EmbeddedMode
|
19
|
+
extend ActiveSupport::Concern
|
20
|
+
|
21
|
+
included do
|
22
|
+
helper_method :embedded_mode?,
|
23
|
+
:standalone_mode?
|
24
|
+
end
|
25
|
+
|
26
|
+
def embedded_mode?
|
27
|
+
@embedded_mode ||= params[:embed] == 'true' && Organization.current.embeddable?
|
28
|
+
end
|
29
|
+
|
30
|
+
def standalone_mode?
|
31
|
+
!embedded_mode?
|
32
|
+
end
|
33
|
+
|
34
|
+
def enable_embedded_rendering
|
35
|
+
return unless embedded_mode?
|
36
|
+
allow_parent_iframe!
|
37
|
+
render layout: 'embedded'
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def allow_parent_iframe!
|
43
|
+
response.set_header 'X-Frame-Options', 'ALLOWALL'
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Mumuki::Laboratory
|
2
|
+
class File
|
3
|
+
attr_reader :name, :content
|
4
|
+
|
5
|
+
def initialize(name, content)
|
6
|
+
@name = name
|
7
|
+
@content = content
|
8
|
+
end
|
9
|
+
|
10
|
+
def highlight_mode
|
11
|
+
Language.find_by(extension: extension)&.highlight_mode || extension
|
12
|
+
end
|
13
|
+
|
14
|
+
def extension
|
15
|
+
name.get_file_extension
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -27,6 +27,7 @@ en:
|
|
27
27
|
classroom: Classroom
|
28
28
|
clear_console: Clear console
|
29
29
|
closed: Closed
|
30
|
+
closed_count: '%{count} closed'
|
30
31
|
created_at_asc: Oldest
|
31
32
|
created_at_desc: Newest
|
32
33
|
comment: Comment
|
@@ -138,6 +139,7 @@ en:
|
|
138
139
|
notify_problem_with_exercise: Report a bug
|
139
140
|
office: Office
|
140
141
|
opened: Open
|
142
|
+
opened_count: '%{count} opened'
|
141
143
|
output: Output
|
142
144
|
overview: Overview
|
143
145
|
passed: Everything is in order! Your solution passed all our tests!
|
@@ -145,7 +147,8 @@ en:
|
|
145
147
|
pending: pending
|
146
148
|
pending_messages: You have unread messages
|
147
149
|
pending_messages_explanation: You can't send new solutions until you read pending messages
|
148
|
-
|
150
|
+
pending_review: Pending review
|
151
|
+
pending_review_count: '%{count} for review'
|
149
152
|
permissions: Permissions
|
150
153
|
please_fill_profile_data: Please complete your profile data to continue!
|
151
154
|
please_validate: 'Please validate your data before continue:'
|
@@ -172,6 +175,7 @@ en:
|
|
172
175
|
sign_up_date: User since
|
173
176
|
solution: Solution
|
174
177
|
solved: Solved
|
178
|
+
solved_count: '%{count} solved'
|
175
179
|
solve_doubts: Solve other's doubts
|
176
180
|
solve_your_doubts: Solve your doubts
|
177
181
|
solve_your_doubts_teaser: Do you have any doubts?
|
@@ -28,6 +28,9 @@ es:
|
|
28
28
|
classroom: Aula
|
29
29
|
clear_console: Reiniciar consola
|
30
30
|
closed: Cerrada
|
31
|
+
closed_count:
|
32
|
+
one: 1 cerrada
|
33
|
+
other: '%{count} cerradas'
|
31
34
|
created_at_asc: Antiguas
|
32
35
|
created_at_desc: Nuevas
|
33
36
|
comment: Comentar
|
@@ -151,6 +154,9 @@ es:
|
|
151
154
|
notify_problem_with_exercise: Reportá un bug
|
152
155
|
office: Secretaría
|
153
156
|
opened: Abierta
|
157
|
+
opened_count:
|
158
|
+
one: '1 abierta'
|
159
|
+
other: '%{count} abiertas'
|
154
160
|
organizations: Organizaciones
|
155
161
|
output: Salida
|
156
162
|
overview: Vista general
|
@@ -158,6 +164,7 @@ es:
|
|
158
164
|
passed_with_warnings: Tu solución funcionó, pero hay cosas que mejorar
|
159
165
|
pending: pendiente
|
160
166
|
pending_review: En revisión
|
167
|
+
pending_review_count: '%{count} en revisión'
|
161
168
|
pending_messages: Tenés mensajes sin leer
|
162
169
|
pending_messages_explanation: "¡Tu docente te dejó un mensaje! Revisalo antes de seguir enviando soluciones."
|
163
170
|
permissions: Permisos
|
@@ -188,6 +195,9 @@ es:
|
|
188
195
|
sign_up_date: Usuario desde
|
189
196
|
solution: Solución
|
190
197
|
solved: Resuelta
|
198
|
+
solved_count:
|
199
|
+
one: 1 resuelta
|
200
|
+
other: '%{count} resueltas'
|
191
201
|
solved_exercises: Ejercicios Resueltos
|
192
202
|
solve_doubts: Resolvé consultas
|
193
203
|
solve_your_doubts: Consultá tus dudas
|
@@ -27,6 +27,7 @@ pt:
|
|
27
27
|
classroom: Sala de aula
|
28
28
|
clear_console: Reiniciar o console
|
29
29
|
closed: Fechado
|
30
|
+
closed_count: '%{count} fechado'
|
30
31
|
comment: Comentar
|
31
32
|
confirm: Confirme
|
32
33
|
confirm_restart: Você está prestes a apagar o seu progresso neste guia. Você quer continuar?
|
@@ -148,6 +149,7 @@ pt:
|
|
148
149
|
notify_problem_with_exercise: Relatar um erro
|
149
150
|
office: Secretariado
|
150
151
|
opened: Aberto
|
152
|
+
opened_count: '%{count} aberto'
|
151
153
|
organizations: Organizações
|
152
154
|
output: Sair
|
153
155
|
overview: Visão global
|
@@ -157,6 +159,7 @@ pt:
|
|
157
159
|
pending_messages: Você tem mensagens não lidas
|
158
160
|
pending_messages_explanation: Seu professor deixou uma mensagem para você! Verifique antes de continuar a enviar soluções.
|
159
161
|
pending_review: Sendo revisto
|
162
|
+
pending_review_count: '%{count} sendo revisto'
|
160
163
|
permissions: Permissões
|
161
164
|
please_fill_profile_data: Preencha suas informações pessoais para continuar!
|
162
165
|
please_validate: Você está prestes a entrar no curso. Para oferecer uma experiência melhor, valide que seus dados abaixo sejam reais e corretos.
|
@@ -185,6 +188,7 @@ pt:
|
|
185
188
|
sign_up_date: Usuário de
|
186
189
|
solution: Solução
|
187
190
|
solved: Resolvido
|
191
|
+
solved_count: '%{count} resolvido'
|
188
192
|
solved_exercises: Exercícios resolvidos
|
189
193
|
solve_doubts: Resolvi dúvidas
|
190
194
|
solve_your_doubts: Consulte suas dúvidas
|
@@ -13,8 +13,12 @@ module Mumuki::Laboratory::Status::Discussion::Opened
|
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
16
|
-
def self.reachable_statuses_for_moderator(
|
17
|
-
|
16
|
+
def self.reachable_statuses_for_moderator(discussion)
|
17
|
+
if discussion.has_responses?
|
18
|
+
[Mumuki::Laboratory::Status::Discussion::Closed, Mumuki::Laboratory::Status::Discussion::Solved]
|
19
|
+
else
|
20
|
+
[Mumuki::Laboratory::Status::Discussion::Closed]
|
21
|
+
end
|
18
22
|
end
|
19
23
|
|
20
24
|
def self.iconize
|
@@ -6,7 +6,7 @@ module Mumuki::Laboratory::Status::Discussion::PendingReview
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def self.reachable_statuses_for_moderator(*)
|
9
|
-
[Mumuki::Laboratory::Status::Discussion::Closed, Mumuki::Laboratory::Status::Discussion::Solved]
|
9
|
+
[Mumuki::Laboratory::Status::Discussion::Opened, Mumuki::Laboratory::Status::Discussion::Closed, Mumuki::Laboratory::Status::Discussion::Solved]
|
10
10
|
end
|
11
11
|
|
12
12
|
def self.iconize
|
@@ -6,7 +6,7 @@ module Mumuki::Laboratory::Status::Discussion::Solved
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def self.reachable_statuses_for_moderator(*)
|
9
|
-
[Mumuki::Laboratory::Status::Discussion::Closed]
|
9
|
+
[Mumuki::Laboratory::Status::Discussion::Opened, Mumuki::Laboratory::Status::Discussion::Closed]
|
10
10
|
end
|
11
11
|
|
12
12
|
def self.iconize
|
@@ -20,10 +20,19 @@ describe ExerciseSolutionsController, organization_workspace: :test do
|
|
20
20
|
end
|
21
21
|
|
22
22
|
context 'when multifile content is sent' do
|
23
|
-
before {
|
23
|
+
before { create(:language, extension: 'js', highlight_mode: 'javascript') }
|
24
|
+
before { post :create, params: { exercise_id: problem.id, solution: { content: {
|
25
|
+
'a_file.css' => 'a css content',
|
26
|
+
'a_file.js' => 'a js content'
|
27
|
+
} } } }
|
28
|
+
let(:files) { problem.files_for(user) }
|
24
29
|
|
25
30
|
it { expect(response.status).to eq 200 }
|
26
|
-
it { expect(Assignment.last.solution).to eq("/*<a_file#*/a content/*#a_file>*/\n/*<content#*//*...a_file...*//*#content>*/") }
|
27
|
-
it { expect(
|
31
|
+
it { expect(Assignment.last.solution).to eq("/*<a_file.css#*/a css content/*#a_file.css>*/\n/*<a_file.js#*/a js content/*#a_file.js>*/\n/*<content#*//*...a_file.css...*/\n/*...a_file.js...*//*#content>*/") }
|
32
|
+
it { expect(files.count).to eq 2 }
|
33
|
+
it { expect(files[0]).to have_attributes(name: 'a_file.css', content: 'a css content') }
|
34
|
+
it { expect(files[0].highlight_mode).to eq 'css' }
|
35
|
+
it { expect(files[1]).to have_attributes(name: 'a_file.js', content: 'a js content') }
|
36
|
+
it { expect(files[1].highlight_mode).to eq 'javascript' }
|
28
37
|
end
|
29
38
|
end
|
@@ -91,6 +91,27 @@ feature 'Exercise Flow', organization_workspace: :test do
|
|
91
91
|
expect(page).to have_text('Description of Succ1')
|
92
92
|
end
|
93
93
|
|
94
|
+
describe 'embedded mode' do
|
95
|
+
scenario 'visit exercise by id, standalone mode' do
|
96
|
+
visit "/exercises/#{problem_1.id}"
|
97
|
+
expect(page).to have_text('Functional Programming 1')
|
98
|
+
expect(page).to have_text('Profile')
|
99
|
+
end
|
100
|
+
scenario 'visit exercise by id, embedded mode in non embeddable organization' do
|
101
|
+
visit "/exercises/#{problem_1.id}?embed=true"
|
102
|
+
expect(page).to have_text('Functional Programming 1')
|
103
|
+
expect(page).to have_text('Profile')
|
104
|
+
end
|
105
|
+
scenario 'visit exercise by id, embedded mode in embeddable organization' do
|
106
|
+
Organization.current.tap { |it| it.embeddable = true }.save!
|
107
|
+
|
108
|
+
visit "/exercises/#{problem_1.id}?embed=true"
|
109
|
+
expect(page).to_not have_text('Functional Programming 1')
|
110
|
+
expect(page).to_not have_text('Profile')
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
|
94
115
|
scenario 'visit exercise by id, editor right layout' do
|
95
116
|
visit "/exercises/#{problem_1.id}"
|
96
117
|
|
@@ -18,8 +18,10 @@ describe Discussion, organization_workspace: :test do
|
|
18
18
|
it { expect(initiator.subscribed_to? discussion).to be true }
|
19
19
|
it { expect(discussion.status).to eq :opened }
|
20
20
|
it { expect(discussion.reachable_statuses_for initiator).to eq [:closed] }
|
21
|
-
it { expect(discussion.reachable_statuses_for moderator).to eq [:closed
|
21
|
+
it { expect(discussion.reachable_statuses_for moderator).to eq [:closed] }
|
22
22
|
it { expect(discussion.reachable_statuses_for student).to eq [] }
|
23
|
+
it { expect(discussion.commentable_by? student).to be true }
|
24
|
+
it { expect(discussion.commentable_by? moderator).to be true }
|
23
25
|
|
24
26
|
describe 'initiator sends a message' do
|
25
27
|
before { discussion.submit_message!({content: 'I forgot to say this'}, initiator) }
|
@@ -28,7 +30,7 @@ describe Discussion, organization_workspace: :test do
|
|
28
30
|
it { expect(discussion.messages.first.content).to eq 'I forgot to say this' }
|
29
31
|
it { expect(initiator.unread_discussions).to eq [] }
|
30
32
|
it { expect(discussion.reachable_statuses_for initiator).to eq [:closed] }
|
31
|
-
it { expect(discussion.reachable_statuses_for moderator).to eq [:closed
|
33
|
+
it { expect(discussion.reachable_statuses_for moderator).to eq [:closed] }
|
32
34
|
it { expect(discussion.reachable_statuses_for student).to eq [] }
|
33
35
|
|
34
36
|
describe 'and closes the discussion' do
|
@@ -38,6 +40,8 @@ describe Discussion, organization_workspace: :test do
|
|
38
40
|
it { expect(discussion.reachable_statuses_for initiator).to eq [] }
|
39
41
|
it { expect(discussion.reachable_statuses_for moderator).to eq [:opened, :solved] }
|
40
42
|
it { expect(discussion.reachable_statuses_for student).to eq [] }
|
43
|
+
it { expect(discussion.commentable_by? student).to be false }
|
44
|
+
it { expect(discussion.commentable_by? moderator).to be true }
|
41
45
|
end
|
42
46
|
end
|
43
47
|
|
@@ -56,8 +60,10 @@ describe Discussion, organization_workspace: :test do
|
|
56
60
|
|
57
61
|
it { expect(discussion.status).to eq :pending_review }
|
58
62
|
it { expect(discussion.reachable_statuses_for initiator).to eq [] }
|
59
|
-
it { expect(discussion.reachable_statuses_for moderator).to eq [:closed, :solved] }
|
63
|
+
it { expect(discussion.reachable_statuses_for moderator).to eq [:opened, :closed, :solved] }
|
60
64
|
it { expect(discussion.reachable_statuses_for student).to eq [] }
|
65
|
+
it { expect(discussion.commentable_by? student).to be false }
|
66
|
+
it { expect(discussion.commentable_by? moderator).to be true }
|
61
67
|
end
|
62
68
|
|
63
69
|
describe 'initiator tries to solve it' do
|
@@ -69,8 +75,10 @@ describe Discussion, organization_workspace: :test do
|
|
69
75
|
|
70
76
|
it { expect(discussion.status).to eq :solved }
|
71
77
|
it { expect(discussion.reachable_statuses_for initiator).to eq [] }
|
72
|
-
it { expect(discussion.reachable_statuses_for moderator).to eq [:closed] }
|
78
|
+
it { expect(discussion.reachable_statuses_for moderator).to eq [:opened, :closed] }
|
73
79
|
it { expect(discussion.reachable_statuses_for student).to eq [] }
|
80
|
+
it { expect(discussion.commentable_by? student).to be false }
|
81
|
+
it { expect(discussion.commentable_by? moderator).to be true }
|
74
82
|
end
|
75
83
|
end
|
76
84
|
end
|
@@ -2,7 +2,7 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe Exercise, organization_workspace: :test do
|
4
4
|
let(:exercise) { create(:exercise) }
|
5
|
-
let(:user) { create(:user) }
|
5
|
+
let(:user) { create(:user, first_name: 'Orlo') }
|
6
6
|
|
7
7
|
describe '#slug' do
|
8
8
|
let(:guide) { create(:guide, slug: 'foo/bar') }
|
@@ -274,6 +274,20 @@ describe Exercise, organization_workspace: :test do
|
|
274
274
|
end
|
275
275
|
end
|
276
276
|
end
|
277
|
+
context 'when interpolation is in test' do
|
278
|
+
context 'using user_first_name' do
|
279
|
+
let(:exercise) { create(:exercise, test: "<div>Hola #{interpolation}</div>") }
|
280
|
+
let(:interpolation) { '/*...user_first_name...*/' }
|
281
|
+
|
282
|
+
it { expect(exercise.test_for(user)).to eq "<div>Hola Orlo</div>" }
|
283
|
+
end
|
284
|
+
|
285
|
+
context 'and test is nil' do
|
286
|
+
let(:exercise) { create(:exercise, test: nil, expectations: [{}]) }
|
287
|
+
|
288
|
+
it { expect(exercise.test_for(user)).to eq nil }
|
289
|
+
end
|
290
|
+
end
|
277
291
|
context 'when interpolation is in extra' do
|
278
292
|
describe 'right previous content' do
|
279
293
|
let(:exercise) { create(:exercise, extra: interpolation) }
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mumuki-laboratory
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 5.
|
4
|
+
version: 5.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Franco Bulgarelli
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-08-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -114,14 +114,14 @@ dependencies:
|
|
114
114
|
requirements:
|
115
115
|
- - "~>"
|
116
116
|
- !ruby/object:Gem::Version
|
117
|
-
version: '2.
|
117
|
+
version: '2.4'
|
118
118
|
type: :runtime
|
119
119
|
prerelease: false
|
120
120
|
version_requirements: !ruby/object:Gem::Requirement
|
121
121
|
requirements:
|
122
122
|
- - "~>"
|
123
123
|
- !ruby/object:Gem::Version
|
124
|
-
version: '2.
|
124
|
+
version: '2.4'
|
125
125
|
- !ruby/object:Gem::Dependency
|
126
126
|
name: mumukit-login
|
127
127
|
requirement: !ruby/object:Gem::Requirement
|
@@ -460,6 +460,7 @@ files:
|
|
460
460
|
- app/views/book_discussions/index.html.erb
|
461
461
|
- app/views/chapters/show.html.erb
|
462
462
|
- app/views/complements/show.html.erb
|
463
|
+
- app/views/discussions/_description_message.html.erb
|
463
464
|
- app/views/discussions/_message.html.erb
|
464
465
|
- app/views/discussions/_message_container.html.erb
|
465
466
|
- app/views/discussions/_new_message.html.erb
|
@@ -488,6 +489,7 @@ files:
|
|
488
489
|
- app/views/layouts/_error.html.erb
|
489
490
|
- app/views/layouts/_guide.html.erb
|
490
491
|
- app/views/layouts/_kids.html.erb
|
492
|
+
- app/views/layouts/_main.html.erb
|
491
493
|
- app/views/layouts/_messages.html.erb
|
492
494
|
- app/views/layouts/_organization_chooser.html.erb
|
493
495
|
- app/views/layouts/_organizations_listing.html.erb
|
@@ -500,6 +502,7 @@ files:
|
|
500
502
|
- app/views/layouts/_test_results.html.erb
|
501
503
|
- app/views/layouts/_timer.html.erb
|
502
504
|
- app/views/layouts/application.html.erb
|
505
|
+
- app/views/layouts/embedded.html.erb
|
503
506
|
- app/views/layouts/exercise_inputs/editors/_code.html.erb
|
504
507
|
- app/views/layouts/exercise_inputs/editors/_custom.html.erb
|
505
508
|
- app/views/layouts/exercise_inputs/editors/_hidden.html.erb
|
@@ -796,6 +799,7 @@ files:
|
|
796
799
|
- lib/mumuki/laboratory/controllers/content.rb
|
797
800
|
- lib/mumuki/laboratory/controllers/current_organization.rb
|
798
801
|
- lib/mumuki/laboratory/controllers/dynamic_errors.rb
|
802
|
+
- lib/mumuki/laboratory/controllers/embedded_mode.rb
|
799
803
|
- lib/mumuki/laboratory/controllers/exercise_seed.rb
|
800
804
|
- lib/mumuki/laboratory/controllers/nested_in_exercise.rb
|
801
805
|
- lib/mumuki/laboratory/controllers/notifications.rb
|
@@ -813,6 +817,7 @@ files:
|
|
813
817
|
- lib/mumuki/laboratory/extensions/array.rb
|
814
818
|
- lib/mumuki/laboratory/extensions/request.rb
|
815
819
|
- lib/mumuki/laboratory/extensions/string.rb
|
820
|
+
- lib/mumuki/laboratory/file.rb
|
816
821
|
- lib/mumuki/laboratory/locales/activerecord.en.yml
|
817
822
|
- lib/mumuki/laboratory/locales/activerecord.es.yml
|
818
823
|
- lib/mumuki/laboratory/locales/datetime.es.yml
|