mumuki-laboratory 7.7.2 → 7.7.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 +4 -4
- data/README.md +9 -0
- data/Rakefile +3 -0
- data/app/assets/stylesheets/mumuki_laboratory/application/modules/_discussion.scss +1 -2
- data/app/controllers/concerns/with_user_discussion_validation.rb +14 -0
- data/app/controllers/discussions_controller.rb +1 -14
- data/app/controllers/discussions_messages_controller.rb +1 -0
- data/app/helpers/discussions_helper.rb +9 -5
- data/app/views/discussions/_message.html.erb +16 -9
- data/app/views/discussions/show.html.erb +9 -7
- data/app/views/layouts/_copyright.html.erb +1 -1
- data/app/views/layouts/_discussions.html.erb +4 -4
- data/app/views/user_mailer/1st_reminder.html.erb +1 -1
- data/app/views/user_mailer/1st_reminder.text.erb +1 -1
- data/app/views/user_mailer/2nd_reminder.html.erb +1 -1
- data/app/views/user_mailer/2nd_reminder.text.erb +1 -1
- data/app/views/user_mailer/3rd_reminder.html.erb +1 -1
- data/app/views/user_mailer/3rd_reminder.text.erb +1 -1
- data/app/views/user_mailer/no_submissions_reminder.html.erb +1 -1
- data/app/views/user_mailer/no_submissions_reminder.text.erb +1 -1
- data/lib/mumuki/laboratory/locales/en.yml +3 -2
- data/lib/mumuki/laboratory/locales/es.yml +2 -1
- data/lib/mumuki/laboratory/locales/pt.yml +3 -2
- data/lib/mumuki/laboratory/version.rb +1 -1
- data/spec/controllers/discussions_messages_controller_spec.rb +73 -0
- data/spec/dummy/db/schema.rb +1 -0
- data/spec/features/discussion_flow_spec.rb +190 -0
- data/spec/features/menu_bar_spec.rb +88 -7
- data/spec/javascripts/bridge-spec.js +5 -0
- data/spec/javascripts/csrf-token-spec.js +7 -0
- data/spec/javascripts/elipsis-spec.js +25 -0
- data/spec/javascripts/results-renderers-spec.js +17 -0
- data/spec/javascripts/spec-helper.js +30 -0
- data/spec/javascripts/speech-bubble-renderer-spec.js +11 -0
- data/spec/javascripts/timeout-spec.js +5 -0
- data/spec/javascripts/timer-spec.js +5 -0
- data/spec/teaspoon_env.rb +187 -0
- metadata +27 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fa8619681047d1ea5b8230abc24e7b4b38c63f722d355a3facd956d725b0b3dd
|
|
4
|
+
data.tar.gz: bdefac6e50c9a9cb6950222bc822f8460e095f47a3602ae7ad86b4c3c41ea634
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4a91405f88b73103163b212bbc08b6a00da9f1d109e13630d4713094cf32736621e38de66d007ab739e86bca4f7782a8081faabf50425e4e45dc49c8022ced0d
|
|
7
|
+
data.tar.gz: 9c7a7398eb308154dc585d8d4d3401e27ced3322314bffbbf5a016ef951aeeeb3c109860afb4457bd93f6b5f509b0f36d7b321baa4c4ae26c445e7c3b3275840
|
data/README.md
CHANGED
|
@@ -135,6 +135,15 @@ rails s
|
|
|
135
135
|
bundle exec rspec
|
|
136
136
|
```
|
|
137
137
|
|
|
138
|
+
## Running JS tests
|
|
139
|
+
|
|
140
|
+
> You need first to download [geckodriver](https://github.com/mozilla/geckodriver/releases/download/v0.27.0/geckodriver-v0.27.0-linux64.tar.gz), uncrompress
|
|
141
|
+
> it and add it to your path
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
bundle exec rake teaspoon
|
|
145
|
+
```
|
|
146
|
+
|
|
138
147
|
## JavaScript API Docs
|
|
139
148
|
|
|
140
149
|
In order to be customized by runners, Laboratory exposes the following selectors and methods
|
data/Rakefile
CHANGED
|
@@ -27,6 +27,9 @@ require 'rspec/core/rake_task'
|
|
|
27
27
|
desc "Run all specs in spec directory (excluding plugin specs)"
|
|
28
28
|
RSpec::Core::RakeTask.new(:spec => 'app:db:test:prepare')
|
|
29
29
|
|
|
30
|
+
desc "Run the javascript specs"
|
|
31
|
+
task :teaspoon => "app:teaspoon"
|
|
32
|
+
|
|
30
33
|
task default: :spec
|
|
31
34
|
|
|
32
35
|
|
|
@@ -327,9 +327,9 @@ summary.discussion-summary {
|
|
|
327
327
|
float: right;
|
|
328
328
|
a {
|
|
329
329
|
margin-left: 5px;
|
|
330
|
+
cursor: pointer;
|
|
330
331
|
}
|
|
331
332
|
.discussion-message-approved {
|
|
332
|
-
cursor: pointer;
|
|
333
333
|
text-decoration: none;
|
|
334
334
|
&.selected {
|
|
335
335
|
i {
|
|
@@ -338,7 +338,6 @@ summary.discussion-summary {
|
|
|
338
338
|
}
|
|
339
339
|
}
|
|
340
340
|
.discussion-message-not-actually-a-question {
|
|
341
|
-
cursor: pointer;
|
|
342
341
|
text-decoration: none;
|
|
343
342
|
i {
|
|
344
343
|
position: relative;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module WithUserDiscussionValidation
|
|
2
|
+
extend ActiveSupport::Concern
|
|
3
|
+
|
|
4
|
+
included do
|
|
5
|
+
# discussions are not enabled for all organizations nor all users
|
|
6
|
+
before_action :validate_user_can_discuss!
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
private
|
|
10
|
+
|
|
11
|
+
def validate_user_can_discuss!
|
|
12
|
+
raise Mumuki::Domain::NotFoundError unless current_user&.can_discuss_here?
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
class DiscussionsController < ApplicationController
|
|
2
2
|
include Mumuki::Laboratory::Controllers::Content
|
|
3
|
-
|
|
4
|
-
# discussions are not enabled for all organitions nor all users
|
|
5
|
-
# there is no need to validate forum existance when creating; next validation is stronger
|
|
6
|
-
before_action :validate_forum_enabled!, except: :create
|
|
7
|
-
before_action :validate_can_create_discusssion!, only: :create
|
|
8
|
-
|
|
3
|
+
include WithUserDiscussionValidation
|
|
9
4
|
# users are not allowed to access discussions during exams
|
|
10
5
|
before_action :validate_not_in_exam!
|
|
11
6
|
|
|
@@ -76,14 +71,6 @@ class DiscussionsController < ApplicationController
|
|
|
76
71
|
@filter_params ||= params.permit(Discussion.permitted_query_params)
|
|
77
72
|
end
|
|
78
73
|
|
|
79
|
-
def validate_forum_enabled!
|
|
80
|
-
raise Mumuki::Domain::NotFoundError unless Organization.current.forum_enabled?
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
def validate_can_create_discusssion!
|
|
84
|
-
raise Mumuki::Domain::NotFoundError unless Organization.current.can_create_discussions?(current_user)
|
|
85
|
-
end
|
|
86
|
-
|
|
87
74
|
def validate_not_in_exam!
|
|
88
75
|
raise Mumuki::Domain::BlockedForumError if current_user&.currently_in_exam?
|
|
89
76
|
end
|
|
@@ -23,8 +23,9 @@ module DiscussionsHelper
|
|
|
23
23
|
fixed_fa_icon 'comment', text: text
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
-
def discussions_link(item, path, html_options = nil
|
|
27
|
-
|
|
26
|
+
def discussions_link(item, path, html_options = nil)
|
|
27
|
+
return unless current_user&.can_discuss_here?
|
|
28
|
+
link_to item, path, html_options
|
|
28
29
|
end
|
|
29
30
|
|
|
30
31
|
def item_discussion_path(discussion, params = {})
|
|
@@ -91,8 +92,6 @@ module DiscussionsHelper
|
|
|
91
92
|
end
|
|
92
93
|
|
|
93
94
|
def new_discussion_link(teaser_text, link_text)
|
|
94
|
-
return '' unless Organization.current.can_create_discussions?(current_user)
|
|
95
|
-
|
|
96
95
|
%Q{
|
|
97
96
|
<h4>
|
|
98
97
|
<span>#{t(teaser_text)}</span>
|
|
@@ -113,8 +112,10 @@ module DiscussionsHelper
|
|
|
113
112
|
Mumuki::Domain::Status::Discussion::STATUSES
|
|
114
113
|
end
|
|
115
114
|
|
|
115
|
+
#TODO: this one uses a long method chain in order to take advantage of eager load
|
|
116
|
+
# Delegate it once again when polymorphic association is removed
|
|
116
117
|
def discussions_languages(discussions)
|
|
117
|
-
@languages ||= discussions.map { |it| it.language.name }.uniq
|
|
118
|
+
@languages ||= discussions.map { |it| it.exercise.language.name }.uniq
|
|
118
119
|
end
|
|
119
120
|
|
|
120
121
|
def discussion_status_filter_link(status, discussions)
|
|
@@ -183,4 +184,7 @@ module DiscussionsHelper
|
|
|
183
184
|
discussion_filter_params.except(:page)
|
|
184
185
|
end
|
|
185
186
|
|
|
187
|
+
def should_show_approved_for?(user, message)
|
|
188
|
+
!user&.moderator_here? && message.approved? && !message.from_moderator?
|
|
189
|
+
end
|
|
186
190
|
end
|
|
@@ -7,21 +7,28 @@
|
|
|
7
7
|
<%= t(:time_since, time: time_ago_in_words(message.created_at)) %>
|
|
8
8
|
</span>
|
|
9
9
|
<% if user.moderator_here? %>
|
|
10
|
-
<%= fa_icon(:star,
|
|
10
|
+
<%= fa_icon(:star, 'data-toggle': 'tooltip', title: (t :moderator), class: 'moderator-star') %>
|
|
11
11
|
<% end %>
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
<% if current_user
|
|
12
|
+
<span class="actions">
|
|
13
|
+
<% if message.authorized? current_user %>
|
|
14
|
+
<% if current_user&.moderator_here? %>
|
|
15
15
|
<a class="discussion-message-approved <%= 'selected' if message.approved? %>" onclick="mumuki.Forum.discussionMessageToggleApprove('<%= approve_discussion_message_url(@discussion, message) %>', $(this))">
|
|
16
16
|
<%= fa_icon(:check, class: 'fa-xs') %>
|
|
17
17
|
</a>
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
<% if message.from_initiator? %>
|
|
19
|
+
<a class="discussion-message-not-actually-a-question <%= 'selected' if message.not_actually_a_question? %>" onclick="mumuki.Forum.discussionMessageToggleNotActuallyAQuestion('<%= question_discussion_message_url(@discussion, message) %>', $(this))">
|
|
20
|
+
<%= fa_icon('question-circle-o', class: 'fa-xs') %>
|
|
21
|
+
</a>
|
|
22
|
+
<% end %>
|
|
21
23
|
<% end %>
|
|
22
24
|
<%= link_to fa_icon('trash-o'), discussion_message_path(@discussion, message), method: :delete, data: { confirm: t(:are_you_sure, action: t(:destroy_message)) } %>
|
|
23
|
-
|
|
24
|
-
|
|
25
|
+
<% end %>
|
|
26
|
+
<% if should_show_approved_for?(current_user, message) %>
|
|
27
|
+
<span class="discussion-message-approved selected">
|
|
28
|
+
<%= fa_icon(:check, class: 'fa-xs', 'data-toggle': 'tooltip', title: (t :approved_message)) %>
|
|
29
|
+
</span>
|
|
30
|
+
<% end %>
|
|
31
|
+
</span>
|
|
25
32
|
</div>
|
|
26
33
|
</div>
|
|
27
34
|
<div class="discussion-message-bubble-content">
|
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
</div>
|
|
10
10
|
|
|
11
11
|
<% if @discussion.has_messages? || @discussion.commentable_by?(current_user) %>
|
|
12
|
+
<hr class="message-divider">
|
|
13
|
+
|
|
12
14
|
<div>
|
|
13
15
|
<h3>
|
|
14
16
|
<%= t :messages %>
|
|
@@ -21,8 +23,6 @@
|
|
|
21
23
|
</h3>
|
|
22
24
|
</div>
|
|
23
25
|
|
|
24
|
-
<hr class="message-divider">
|
|
25
|
-
|
|
26
26
|
<% if @discussion.has_messages? %>
|
|
27
27
|
<div class="discussion-messages">
|
|
28
28
|
<% if @discussion.description.present? %>
|
|
@@ -41,8 +41,10 @@
|
|
|
41
41
|
|
|
42
42
|
<% end %>
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
44
|
+
<% if current_user&.moderator_here? %>
|
|
45
|
+
<div class="discussion-actions">
|
|
46
|
+
<% @discussion.reachable_statuses_for(current_user).each do |status| %>
|
|
47
|
+
<%= discussion_update_status_button(status) %>
|
|
48
|
+
<% end %>
|
|
49
|
+
</div>
|
|
50
|
+
<% end %>
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
© Copyright 2015-<%= DateTime.now.year %>
|
|
2
|
-
<a href="http://mumuki.org/" class="mu-org-link"><span class="da da-mumuki-circle"></span> Mumuki
|
|
2
|
+
<a href="http://mumuki.org/" class="mu-org-link"><span class="da da-mumuki-circle"></span> Mumuki</a>
|
|
@@ -42,13 +42,13 @@
|
|
|
42
42
|
<% else %>
|
|
43
43
|
<div class="discussions">
|
|
44
44
|
<% @filtered_discussions.each do |discussion| %>
|
|
45
|
-
<%= link_to
|
|
45
|
+
<%= link_to exercise_discussion_path(discussion.exercise.id, discussion) do %>
|
|
46
46
|
<div class="discussion">
|
|
47
47
|
<div class="discussion-row">
|
|
48
48
|
<%= discussion_messages_icon(discussion) %>
|
|
49
49
|
<% unless @debatable.respond_to? :language %>
|
|
50
50
|
<div class="discussion-language-icon">
|
|
51
|
-
<%= language_icon(discussion.language) %>
|
|
51
|
+
<%= language_icon(discussion.exercise.language) %>
|
|
52
52
|
</div>
|
|
53
53
|
<% end %>
|
|
54
54
|
<div>
|
|
@@ -56,8 +56,8 @@
|
|
|
56
56
|
<span class="discussion-status-icon">
|
|
57
57
|
<%= discussion_status_fa_icon(discussion) %>
|
|
58
58
|
</span>
|
|
59
|
-
<span class="hidden-sm hidden-xs"><%= discussion.exercise.
|
|
60
|
-
<span
|
|
59
|
+
<span class="hidden-sm hidden-xs"><%= discussion.exercise.guide.name %> -</span>
|
|
60
|
+
<span><%= discussion.exercise.name %></span>
|
|
61
61
|
<% if discussion.last_moderator_access_visible_for?(current_user) %>
|
|
62
62
|
<div class="pull-right discussion-moderator-access" >
|
|
63
63
|
<%= profile_picture_for(discussion.last_moderator_access_by, 32) %>
|
|
@@ -313,7 +313,7 @@
|
|
|
313
313
|
|
|
314
314
|
<td valign="top" class="muMailTextContent" style="padding-top:0; padding-right:18px; padding-bottom:9px; padding-left:18px;">
|
|
315
315
|
|
|
316
|
-
© Copyright 2015-<%= DateTime.now.year %> <a href="https://mumuki.org/home/">Mumuki
|
|
316
|
+
© Copyright 2015-<%= DateTime.now.year %> <a href="https://mumuki.org/home/">Mumuki</a><em>.</em><br>
|
|
317
317
|
<br>
|
|
318
318
|
<%= t :stop_emails? %><br>
|
|
319
319
|
<%= link_to t(:cancel_subscription), @organization.url_for("/user/unsubscribe?id=#{@unsubscribe_code}"), target: :_blank %>
|
|
@@ -313,7 +313,7 @@
|
|
|
313
313
|
|
|
314
314
|
<td valign="top" class="muMailTextContent" style="padding-top:0; padding-right:18px; padding-bottom:9px; padding-left:18px;">
|
|
315
315
|
|
|
316
|
-
© Copyright 2015-<%= DateTime.now.year %> <a href="https://mumuki.org/home/">Mumuki
|
|
316
|
+
© Copyright 2015-<%= DateTime.now.year %> <a href="https://mumuki.org/home/">Mumuki</a><em>.</em><br>
|
|
317
317
|
<br>
|
|
318
318
|
<%= t :stop_emails? %><br>
|
|
319
319
|
<%= link_to t(:cancel_subscription), @organization.url_for("/user/unsubscribe?id=#{@unsubscribe_code}"), target: :_blank %>
|
|
@@ -313,7 +313,7 @@
|
|
|
313
313
|
|
|
314
314
|
<td valign="top" class="muMailTextContent" style="padding-top:0; padding-right:18px; padding-bottom:9px; padding-left:18px;">
|
|
315
315
|
|
|
316
|
-
© Copyright 2015-<%= DateTime.now.year %> <a href="https://mumuki.org/home/">Mumuki
|
|
316
|
+
© Copyright 2015-<%= DateTime.now.year %> <a href="https://mumuki.org/home/">Mumuki</a><em>.</em><br>
|
|
317
317
|
<br>
|
|
318
318
|
<%= t :stop_emails? %><br>
|
|
319
319
|
<%= link_to t(:cancel_subscription), @organization.url_for("/user/unsubscribe?id=#{@unsubscribe_code}"), target: :_blank %>
|
|
@@ -313,7 +313,7 @@
|
|
|
313
313
|
|
|
314
314
|
<td valign="top" class="muMailTextContent" style="padding-top:0; padding-right:18px; padding-bottom:9px; padding-left:18px;">
|
|
315
315
|
|
|
316
|
-
© Copyright 2015-<%= DateTime.now.year %> <a href="https://mumuki.org/home/">Mumuki
|
|
316
|
+
© Copyright 2015-<%= DateTime.now.year %> <a href="https://mumuki.org/home/">Mumuki</a><em>.</em><br>
|
|
317
317
|
<br>
|
|
318
318
|
<%= t :stop_emails? %><br>
|
|
319
319
|
<%= link_to t(:cancel_subscription), @organization.url_for("/user/unsubscribe?id=#{@unsubscribe_code}"), target: :_blank %>
|
|
@@ -10,6 +10,7 @@ en:
|
|
|
10
10
|
all: All
|
|
11
11
|
appendix: "Appendix"
|
|
12
12
|
appendix_teaser: Do you want to learn more? <a href="%{link}">Check this chapter's appendix</a>"
|
|
13
|
+
approved_message: Validated by mentor
|
|
13
14
|
are_you_sure: 'Are you sure you want to %{action}?'
|
|
14
15
|
ask_a_question: Ask a question!
|
|
15
16
|
ask_community: Ask community for help
|
|
@@ -134,7 +135,7 @@ en:
|
|
|
134
135
|
messages: Messages
|
|
135
136
|
messages_error: Previous messages are unavailable. Please try again later.
|
|
136
137
|
more_messages: More
|
|
137
|
-
moderator:
|
|
138
|
+
moderator: Mentor
|
|
138
139
|
mumuki_catchphrase: Improve your programming skills
|
|
139
140
|
mumuki_short_description: Mumuki is a simple, open and collaborative platform for sharing and solving programming exercises. It is aimed to help people with learning and teaching programing languages and paradigms
|
|
140
141
|
my_doubts: My doubts
|
|
@@ -156,7 +157,7 @@ en:
|
|
|
156
157
|
no_messages: It seems you don't have any messages yet!
|
|
157
158
|
no_questions: It seems there isn't any question yet.
|
|
158
159
|
no_submissions: It seems like you haven't tried to solve this exercise
|
|
159
|
-
no_useful_result:
|
|
160
|
+
no_useful_result: Didn't find what you were looking for?
|
|
160
161
|
not_found_explanation: 'You may have mistyped the address or the page may have moved.'
|
|
161
162
|
not_in_any_organizations: It seems you aren't in any organizations yet!
|
|
162
163
|
notify_problem_with_exercise: Report a bug
|
|
@@ -11,6 +11,7 @@ es:
|
|
|
11
11
|
and: y
|
|
12
12
|
appendix: "Apéndice"
|
|
13
13
|
appendix_teaser: ¿Querés saber más? <a href="%{link}">Consultá el apéndice de este capítulo</a>
|
|
14
|
+
approved_message: Validado por mentor
|
|
14
15
|
are_you_sure: '¿Estás seguro que querés %{action}?'
|
|
15
16
|
ask_a_question: ¡Hacé una pregunta!
|
|
16
17
|
ask_community: Preguntá a la comunidad
|
|
@@ -146,7 +147,7 @@ es:
|
|
|
146
147
|
minute: minuto
|
|
147
148
|
minutes: minutos
|
|
148
149
|
more_messages: Ver mensajes anteriores
|
|
149
|
-
moderator:
|
|
150
|
+
moderator: Mentor
|
|
150
151
|
mumuki_catchphrase: Aprendé a programar
|
|
151
152
|
mumuki_short_description: Mumuki es la plataforma libre y gratuita para aprender a programar, de la práctica a la teoría y en tu idioma
|
|
152
153
|
my_doubts: Mis consultas
|
|
@@ -11,6 +11,7 @@ pt:
|
|
|
11
11
|
and: e
|
|
12
12
|
appendix: Apêndice
|
|
13
13
|
appendix_teaser: Você quer saber mais? <a href="%{link}"> Consulte o apêndice deste capítulo </a>
|
|
14
|
+
approved_message: Validado pelo mentor
|
|
14
15
|
are_you_sure: 'Tem certeza de que deseja %{action}?'
|
|
15
16
|
ask_a_question: Faça uma pergunta!
|
|
16
17
|
ask_community: Pergunte à comunidade
|
|
@@ -142,7 +143,7 @@ pt:
|
|
|
142
143
|
minute: minuto
|
|
143
144
|
minutes: minutos
|
|
144
145
|
more_messages: Ver as mensagens anteriores
|
|
145
|
-
moderator:
|
|
146
|
+
moderator: Mentor
|
|
146
147
|
mumuki_catchphrase: Aprendi a programar
|
|
147
148
|
mumuki_short_description: Mumuki é a plataforma gratuita e gratuita para aprender a programar, desde a prática até a teoria e no seu idioma
|
|
148
149
|
my_doubts: Minhas duvidas
|
|
@@ -164,7 +165,7 @@ pt:
|
|
|
164
165
|
no_messages: Parece que você ainda não tem uma mensagem!
|
|
165
166
|
no_questions: Parece que ainda não há dúvida.
|
|
166
167
|
no_submissions: Parece que você ainda não tentou resolver este exercício!
|
|
167
|
-
no_useful_result:
|
|
168
|
+
no_useful_result: Não encontrou o que estava buscando?
|
|
168
169
|
not_found: A página que você pesquisou não existe!
|
|
169
170
|
not_found_explanation: Olhe se você escreveu o endereço corretamente. Mas...
|
|
170
171
|
not_in_any_organizations: Parece que você ainda não está em nenhuma organização!
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe DiscussionsMessagesController, organization_workspace: :test do
|
|
4
|
+
let(:student) { create(:user, permissions: {student: 'test/*'}) }
|
|
5
|
+
let(:moderator) { create(:user, permissions: {moderator: 'test/*', student: 'test/*'}) }
|
|
6
|
+
|
|
7
|
+
let(:exercise) { create(:exercise) }
|
|
8
|
+
let(:discussion) { create(:discussion, item: exercise, organization: Organization.current) }
|
|
9
|
+
|
|
10
|
+
before { Organization.current.update! forum_enabled: true }
|
|
11
|
+
|
|
12
|
+
describe 'post' do
|
|
13
|
+
describe 'for student' do
|
|
14
|
+
before { set_current_user! student }
|
|
15
|
+
before { allow_any_instance_of(DiscussionsMessagesController).to receive(:message_params).and_return content: 'Need help' }
|
|
16
|
+
before { post :create, params: {discussion_id: discussion.id} }
|
|
17
|
+
|
|
18
|
+
it { expect(response.status).to eq 302 }
|
|
19
|
+
it { expect(discussion.messages.size).to eq 1 }
|
|
20
|
+
it { expect(discussion.messages.last.content).to eq 'Need help' }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
describe 'for moderator' do
|
|
24
|
+
before { set_current_user! moderator }
|
|
25
|
+
before { allow_any_instance_of(DiscussionsMessagesController).to receive(:message_params).and_return content: 'Do this!' }
|
|
26
|
+
before { post :create, params: {discussion_id: discussion.id} }
|
|
27
|
+
|
|
28
|
+
it { expect(response.status).to eq 302 }
|
|
29
|
+
it { expect(discussion.messages.size).to eq 1 }
|
|
30
|
+
it { expect(discussion.messages.last.content).to eq 'Do this!' }
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
describe 'approve' do
|
|
35
|
+
let(:message) { create(:message, discussion: discussion, sender: student.uid) }
|
|
36
|
+
|
|
37
|
+
describe 'for student' do
|
|
38
|
+
before { set_current_user! student }
|
|
39
|
+
before { post :approve, params: {id: message.id, discussion_id: discussion.id} }
|
|
40
|
+
|
|
41
|
+
it { expect(response.status).to eq 403 }
|
|
42
|
+
it { expect(message.reload.approved).to be false }
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
describe 'for student' do
|
|
46
|
+
before { set_current_user! moderator }
|
|
47
|
+
before { post :approve, params: {id: message.id, discussion_id: discussion.id} }
|
|
48
|
+
|
|
49
|
+
it { expect(response.status).to eq 200 }
|
|
50
|
+
it { expect(message.reload.approved).to be true }
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
describe 'question' do
|
|
55
|
+
let(:message) { create(:message, discussion: discussion, sender: student.uid) }
|
|
56
|
+
|
|
57
|
+
describe 'for student' do
|
|
58
|
+
before { set_current_user! student }
|
|
59
|
+
before { post :question, params: {id: message.id, discussion_id: discussion.id} }
|
|
60
|
+
|
|
61
|
+
it { expect(response.status).to eq 403 }
|
|
62
|
+
it { expect(message.reload.not_actually_a_question).to be false }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
describe 'for student' do
|
|
66
|
+
before { set_current_user! moderator }
|
|
67
|
+
before { post :question, params: {id: message.id, discussion_id: discussion.id} }
|
|
68
|
+
|
|
69
|
+
it { expect(response.status).to eq 200 }
|
|
70
|
+
it { expect(message.reload.not_actually_a_question).to be true }
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
data/spec/dummy/db/schema.rb
CHANGED
|
@@ -385,6 +385,7 @@ ActiveRecord::Schema.define(version: 20200731081757) do
|
|
|
385
385
|
t.string "verified_last_name"
|
|
386
386
|
t.bigint "avatar_id"
|
|
387
387
|
t.datetime "disabled_at"
|
|
388
|
+
t.boolean "trusted_for_forum"
|
|
388
389
|
t.index ["disabled_at"], name: "index_users_on_disabled_at"
|
|
389
390
|
t.index ["last_organization_id"], name: "index_users_on_last_organization_id"
|
|
390
391
|
t.index ["uid"], name: "index_users_on_uid", unique: true
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
feature 'Discussion Flow', organization_workspace: :test do
|
|
4
|
+
|
|
5
|
+
## =============
|
|
6
|
+
## Users
|
|
7
|
+
## =============
|
|
8
|
+
|
|
9
|
+
let(:student) { create(:user, permissions: {student: 'test/*:other/*:empty/*'}) }
|
|
10
|
+
let(:another_student) { create(:user, permissions: {student: 'test/*:other/*:empty/*'}) }
|
|
11
|
+
let(:moderator) { create(:user, permissions: {moderator: 'test/*:other/*:empty/*', student: 'test/*:other/*:empty/*'}) }
|
|
12
|
+
|
|
13
|
+
## =================
|
|
14
|
+
## Organizations
|
|
15
|
+
## =================
|
|
16
|
+
|
|
17
|
+
let(:test_organization) { Organization.locate! 'test' }
|
|
18
|
+
let(:other_organization) { create(:organization, name: 'other') }
|
|
19
|
+
let!(:empty_organization) { create(:organization, name: 'empty') }
|
|
20
|
+
|
|
21
|
+
## ================================
|
|
22
|
+
## Content for test_organization
|
|
23
|
+
## ================================
|
|
24
|
+
|
|
25
|
+
let(:problem_1) { create(:problem) }
|
|
26
|
+
let(:problem_2) { create(:problem) }
|
|
27
|
+
let(:problem_3) { create(:problem) }
|
|
28
|
+
|
|
29
|
+
let!(:chapter) {
|
|
30
|
+
create(:chapter, lessons: [create(:lesson, exercises: [ problem_1, problem_2, problem_3])])
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
## =====================================
|
|
34
|
+
## Discussions for test_organization
|
|
35
|
+
## =====================================
|
|
36
|
+
|
|
37
|
+
let(:problem_2_discussions) { create_list(:discussion, 5, initiator: another_student, item: problem_2, organization: test_organization) }
|
|
38
|
+
let(:problem_3_discussions) { create_list(:discussion, 5, initiator: student, item: problem_2, organization: test_organization) }
|
|
39
|
+
let!(:test_organization_discussions) { problem_2_discussions + problem_3_discussions }
|
|
40
|
+
|
|
41
|
+
## ===================================
|
|
42
|
+
## Content for other_organization
|
|
43
|
+
## ===================================
|
|
44
|
+
|
|
45
|
+
let(:other_problem) { create(:problem) }
|
|
46
|
+
let!(:other_chapter) {
|
|
47
|
+
create(:chapter, book: other_organization.book, lessons: [create(:lesson, exercises: [other_problem])])
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
## =====================================
|
|
51
|
+
## Discussions for other_organization
|
|
52
|
+
## =====================================
|
|
53
|
+
|
|
54
|
+
let(:other_problem_discussions) { create_list(:discussion, 5, initiator: student, item: problem_2, organization: other_organization) }
|
|
55
|
+
let!(:other_organization_discussions) { other_problem_discussions }
|
|
56
|
+
|
|
57
|
+
before { reindex_current_organization! }
|
|
58
|
+
before { reindex_organization! other_organization }
|
|
59
|
+
before { reindex_organization! empty_organization }
|
|
60
|
+
|
|
61
|
+
shared_examples 'no forum access' do
|
|
62
|
+
scenario 'has no forum access' do
|
|
63
|
+
visit current_path
|
|
64
|
+
expect(page).to have_text('You may have mistyped the address or the page may have moved')
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
context 'exercise discussions' do
|
|
69
|
+
let(:current_path) { "/exercises/#{problem_1.id}/discussions" }
|
|
70
|
+
|
|
71
|
+
context 'with no current user' do
|
|
72
|
+
it_behaves_like 'no forum access'
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
context 'with logged user' do
|
|
76
|
+
before { set_current_user! student }
|
|
77
|
+
|
|
78
|
+
context 'but no forum enabled' do
|
|
79
|
+
it_behaves_like 'no forum access'
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
context 'and forum enabled' do
|
|
83
|
+
before { Organization.current.update! forum_enabled: true }
|
|
84
|
+
|
|
85
|
+
scenario 'empty discussion list for problem 1' do
|
|
86
|
+
visit "/exercises/#{problem_1.id}/discussions"
|
|
87
|
+
expect(page).to have_text(problem_1.name)
|
|
88
|
+
expect(page).to have_text('It seems there isn\'t any question yet.')
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
scenario 'discussion list for problem 2' do
|
|
92
|
+
visit "/exercises/#{problem_2.id}/discussions"
|
|
93
|
+
expect(page).to have_text(problem_2.name)
|
|
94
|
+
problem_2_discussions.each do |discussion|
|
|
95
|
+
expect(page).to have_text(discussion.description)
|
|
96
|
+
end
|
|
97
|
+
expect(page).to have_text('Didn\'t find what you were looking for?')
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
context 'book discussions' do
|
|
104
|
+
let(:current_path) { '/discussions' }
|
|
105
|
+
|
|
106
|
+
context 'with no current user' do
|
|
107
|
+
it_behaves_like 'no forum access'
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
context 'with logged user' do
|
|
111
|
+
before { set_current_user! student }
|
|
112
|
+
|
|
113
|
+
context 'but no forum enabled' do
|
|
114
|
+
it_behaves_like 'no forum access'
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
context 'and forum enabled' do
|
|
118
|
+
before { Organization.current.update! forum_enabled: true }
|
|
119
|
+
|
|
120
|
+
context 'in empty organization' do
|
|
121
|
+
before { empty_organization.update! forum_enabled: true }
|
|
122
|
+
before { set_subdomain_host! 'empty' }
|
|
123
|
+
after { set_subdomain_host! 'test' }
|
|
124
|
+
|
|
125
|
+
scenario 'empty discussion list for book without discussions' do
|
|
126
|
+
visit current_path
|
|
127
|
+
expect(page).to have_text(empty_organization.book.name)
|
|
128
|
+
expect(page).to have_text('It seems there isn\'t any question yet.')
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
scenario 'discussion list for book with discussions' do
|
|
134
|
+
visit current_path
|
|
135
|
+
expect(page).to have_text(test_organization.book.name)
|
|
136
|
+
test_organization_discussions.each do |discussion|
|
|
137
|
+
expect(page).to have_text(discussion.description)
|
|
138
|
+
end
|
|
139
|
+
other_organization_discussions.each do |discussion|
|
|
140
|
+
expect(page).not_to have_text(discussion.description)
|
|
141
|
+
end
|
|
142
|
+
expect(page).not_to have_text('Didn\'t find what you were looking for?')
|
|
143
|
+
expect(page).not_to have_text('It seems there isn\'t any question yet.')
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
context 'exercise discussion' do
|
|
150
|
+
let(:current_path) { "/exercises/#{problem_2.id}/discussions/#{problem_2_discussions.first.id}" }
|
|
151
|
+
before { test_organization.switch! }
|
|
152
|
+
|
|
153
|
+
context 'with no current user' do
|
|
154
|
+
it_behaves_like 'no forum access'
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
context 'with logged student' do
|
|
158
|
+
before { set_current_user! student }
|
|
159
|
+
|
|
160
|
+
context 'but no forum enabled' do
|
|
161
|
+
it_behaves_like 'no forum access'
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
context 'and forum enabled' do
|
|
165
|
+
before { Organization.current.update! forum_enabled: true }
|
|
166
|
+
|
|
167
|
+
scenario 'newly created discussion' do
|
|
168
|
+
visit current_path
|
|
169
|
+
expect(page).to have_text(problem_2.name)
|
|
170
|
+
expect(page).to have_text('Open')
|
|
171
|
+
expect(page).to have_text('Messages')
|
|
172
|
+
expect(page).not_to have_xpath("//div[@class='discussion-actions']")
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
context 'for moderator' do
|
|
176
|
+
before { set_current_user! moderator }
|
|
177
|
+
|
|
178
|
+
scenario 'newly created discussion' do
|
|
179
|
+
visit current_path
|
|
180
|
+
expect(page).to have_text(problem_2.name)
|
|
181
|
+
expect(page).to have_text('Open')
|
|
182
|
+
expect(page).to have_text('Messages')
|
|
183
|
+
expect(page).to have_xpath("//div[@class='discussion-actions']")
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
end
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
require 'spec_helper'
|
|
2
2
|
|
|
3
3
|
feature 'menu bar' do
|
|
4
|
-
let(:
|
|
4
|
+
let(:lesson) { create(:lesson, exercises: create_list(:exercise, 3))}
|
|
5
|
+
let(:chapter) { create(:chapter, lessons: [lesson]) }
|
|
5
6
|
let(:book) { create(:book, chapters: [chapter], name: 'private', slug: 'mumuki/mumuki-the-private-book') }
|
|
6
7
|
let(:private_organization) { create(:organization, name: 'private', book: book) }
|
|
7
8
|
|
|
@@ -32,6 +33,8 @@ feature 'menu bar' do
|
|
|
32
33
|
expect(page).not_to have_text('Profile')
|
|
33
34
|
expect(page).not_to have_text('Classroom')
|
|
34
35
|
expect(page).not_to have_text('Bibliotheca')
|
|
36
|
+
expect(page).not_to have_text('Solve other\'s doubts')
|
|
37
|
+
expect(page).not_to have_text('My doubts')
|
|
35
38
|
end
|
|
36
39
|
end
|
|
37
40
|
end
|
|
@@ -52,15 +55,91 @@ feature 'menu bar' do
|
|
|
52
55
|
expect(page).to have_text('Profile')
|
|
53
56
|
expect(page).not_to have_text('Classroom')
|
|
54
57
|
expect(page).not_to have_text('Bibliotheca')
|
|
58
|
+
expect(page).not_to have_text('Solve other\'s doubts')
|
|
59
|
+
expect(page).not_to have_text('My doubts')
|
|
55
60
|
end
|
|
56
61
|
|
|
57
|
-
|
|
58
|
-
|
|
62
|
+
context 'student with no discussions should' do
|
|
63
|
+
scenario 'only see profile if forum is not enabled' do
|
|
64
|
+
set_current_user! student
|
|
59
65
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
66
|
+
visit '/'
|
|
67
|
+
expect(page).to have_text('Profile')
|
|
68
|
+
expect(page).not_to have_text('Classroom')
|
|
69
|
+
expect(page).not_to have_text('Bibliotheca')
|
|
70
|
+
expect(page).not_to have_text('Solve other\'s doubts')
|
|
71
|
+
expect(page).not_to have_text('My doubts')
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
scenario 'see profile and solve_other_doubts links if forum is enabled' do
|
|
75
|
+
set_current_user! student
|
|
76
|
+
private_organization.update! forum_enabled: true
|
|
77
|
+
|
|
78
|
+
visit '/'
|
|
79
|
+
expect(page).to have_text('Profile')
|
|
80
|
+
expect(page).not_to have_text('Classroom')
|
|
81
|
+
expect(page).not_to have_text('Bibliotheca')
|
|
82
|
+
expect(page).to have_text('Solve other\'s doubts')
|
|
83
|
+
expect(page).not_to have_text('My doubts')
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
context 'student with discussions should' do
|
|
88
|
+
let(:discussion) { create(:discussion, item: lesson.exercises.last, initiator: student)}
|
|
89
|
+
|
|
90
|
+
scenario 'only see profile if forum is not enabled' do
|
|
91
|
+
set_current_user! student
|
|
92
|
+
student.subscribe_to! discussion
|
|
93
|
+
|
|
94
|
+
visit '/'
|
|
95
|
+
expect(page).to have_text('Profile')
|
|
96
|
+
expect(page).not_to have_text('Classroom')
|
|
97
|
+
expect(page).not_to have_text('Bibliotheca')
|
|
98
|
+
expect(page).not_to have_text('Solve other\'s doubts')
|
|
99
|
+
expect(page).not_to have_text('My doubts')
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
scenario 'see all discussions links if forum is enabled' do
|
|
103
|
+
set_current_user! student
|
|
104
|
+
private_organization.update! forum_enabled: true
|
|
105
|
+
student.subscribe_to! discussion
|
|
106
|
+
|
|
107
|
+
visit '/'
|
|
108
|
+
expect(page).to have_text('Profile')
|
|
109
|
+
expect(page).not_to have_text('Classroom')
|
|
110
|
+
expect(page).not_to have_text('Bibliotheca')
|
|
111
|
+
expect(page).to have_text('Solve other\'s doubts')
|
|
112
|
+
expect(page).to have_text('My doubts')
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
scenario 'only see profile if forum is enabled in a forum_only_for_trusted organization' do
|
|
116
|
+
set_current_user! student
|
|
117
|
+
student.subscribe_to! discussion
|
|
118
|
+
private_organization.update! forum_enabled: true
|
|
119
|
+
private_organization.update! forum_only_for_trusted: true
|
|
120
|
+
|
|
121
|
+
visit '/'
|
|
122
|
+
expect(page).to have_text('Profile')
|
|
123
|
+
expect(page).not_to have_text('Classroom')
|
|
124
|
+
expect(page).not_to have_text('Bibliotheca')
|
|
125
|
+
expect(page).not_to have_text('Solve other\'s doubts')
|
|
126
|
+
expect(page).not_to have_text('My doubts')
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
scenario 'see all discussions links if forum is enabled in a forum_only_for_trusted organization but it is trusted' do
|
|
130
|
+
student.update! trusted_for_forum: true
|
|
131
|
+
set_current_user! student
|
|
132
|
+
student.subscribe_to! discussion
|
|
133
|
+
private_organization.update! forum_enabled: true
|
|
134
|
+
private_organization.update! forum_only_for_trusted: true
|
|
135
|
+
|
|
136
|
+
visit '/'
|
|
137
|
+
expect(page).to have_text('Profile')
|
|
138
|
+
expect(page).not_to have_text('Classroom')
|
|
139
|
+
expect(page).not_to have_text('Bibliotheca')
|
|
140
|
+
expect(page).to have_text('Solve other\'s doubts')
|
|
141
|
+
expect(page).to have_text('My doubts')
|
|
142
|
+
end
|
|
64
143
|
end
|
|
65
144
|
|
|
66
145
|
scenario 'teacher should see profile and classroom' do
|
|
@@ -71,6 +150,8 @@ feature 'menu bar' do
|
|
|
71
150
|
expect(page).to have_text('Profile')
|
|
72
151
|
expect(page).to have_text('Classroom')
|
|
73
152
|
expect(page).not_to have_text('Bibliotheca')
|
|
153
|
+
expect(page).not_to have_text('Solve other\'s doubts')
|
|
154
|
+
expect(page).not_to have_text('My doubts')
|
|
74
155
|
end
|
|
75
156
|
|
|
76
157
|
scenario 'writer should see profile and bibliotheca' do
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
describe('elipsis', () => {
|
|
2
|
+
it('does nothing when no elipsis', () => {
|
|
3
|
+
expect(mumuki.elipsis('hello')).toEqual('hello');
|
|
4
|
+
})
|
|
5
|
+
|
|
6
|
+
it('replaces student elipsis', () => {
|
|
7
|
+
expect(mumuki.elipsis(`function longitud(unString) {
|
|
8
|
+
/*<elipsis-for-student@*/
|
|
9
|
+
return unString.length;
|
|
10
|
+
/*@elipsis-for-student>*/
|
|
11
|
+
}`)).toEqual(`function longitud(unString) {
|
|
12
|
+
/* ... */
|
|
13
|
+
}`)
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('replaces student hidden', () => {
|
|
17
|
+
expect(mumuki.elipsis(`function longitud(unString) {
|
|
18
|
+
/*<hidden-for-student@*/
|
|
19
|
+
return unString.length;
|
|
20
|
+
/*@hidden-for-student>*/
|
|
21
|
+
}`)).toEqual(`function longitud(unString) {
|
|
22
|
+
/**/
|
|
23
|
+
}`)
|
|
24
|
+
});
|
|
25
|
+
})
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
describe('results renderers', () => {
|
|
2
|
+
it('can compute class for status', () => {
|
|
3
|
+
expect(mumuki.renderers.classForStatus('passed')).toEqual('success');
|
|
4
|
+
})
|
|
5
|
+
|
|
6
|
+
it('can compute icon for status', () => {
|
|
7
|
+
expect(mumuki.renderers.iconForStatus('pending')).toEqual('fa-circle');
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
it('can compute progress list item for status', () => {
|
|
11
|
+
expect(mumuki.renderers.progressListItemClassForStatus('passed_with_warnings')).toEqual('progress-list-item text-center warning ');
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
it('can compute progress list item for status when active', () => {
|
|
15
|
+
expect(mumuki.renderers.progressListItemClassForStatus('failed', true)).toEqual('progress-list-item text-center danger active');
|
|
16
|
+
})
|
|
17
|
+
})
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Teaspoon includes some support files, but you can use anything from your own support path too.
|
|
2
|
+
// require support/jasmine-jquery-1.7.0
|
|
3
|
+
// require support/jasmine-jquery-2.0.0
|
|
4
|
+
// require support/jasmine-jquery-2.1.0
|
|
5
|
+
// require support/sinon
|
|
6
|
+
//
|
|
7
|
+
// PhantomJS (Teaspoons default driver) doesn't have support for Function.prototype.bind, which has caused confusion.
|
|
8
|
+
// Use this polyfill to avoid the confusion.
|
|
9
|
+
//
|
|
10
|
+
// You can require your own javascript files here. By default this will include everything in application, however you
|
|
11
|
+
// may get better load performance if you require the specific files that are being used in the spec that tests them.
|
|
12
|
+
//=require mumuki_laboratory/application
|
|
13
|
+
//
|
|
14
|
+
// Deferring execution
|
|
15
|
+
// If you're using CommonJS, RequireJS or some other asynchronous library you can defer execution. Call
|
|
16
|
+
// Teaspoon.execute() after everything has been loaded. Simple example of a timeout:
|
|
17
|
+
//
|
|
18
|
+
// Teaspoon.defer = true
|
|
19
|
+
// setTimeout(Teaspoon.execute, 1000)
|
|
20
|
+
//
|
|
21
|
+
// Matching files
|
|
22
|
+
// By default Teaspoon will look for files that match _spec.{js,js.coffee,.coffee}. Add a filename_spec.js file in your
|
|
23
|
+
// spec path and it'll be included in the default suite automatically. If you want to customize suites, check out the
|
|
24
|
+
// configuration in teaspoon_env.rb
|
|
25
|
+
//
|
|
26
|
+
// Manifest
|
|
27
|
+
// If you'd rather require your spec files manually (to control order for instance) you can disable the suite matcher in
|
|
28
|
+
// the configuration and use this file as a manifest.
|
|
29
|
+
//
|
|
30
|
+
// For more information: http://github.com/modeset/teaspoon
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
describe('results renderers', () => {
|
|
2
|
+
it('can render result item', () => {
|
|
3
|
+
expect(mumuki.renderers.renderSpeechBubbleResultItem('fix that')).toEqual(`
|
|
4
|
+
<div class="results-item">
|
|
5
|
+
<ul class="results-list">
|
|
6
|
+
<li>fix that</li>
|
|
7
|
+
</ul>
|
|
8
|
+
</div>`);
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
})
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
unless defined?(Rails)
|
|
2
|
+
ENV["RAILS_ROOT"] = File.expand_path("../dummy", __FILE__)
|
|
3
|
+
require File.expand_path("../dummy/config/environment", __FILE__)
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
Teaspoon.configure do |config|
|
|
8
|
+
# Determines where the Teaspoon routes will be mounted. Changing this to "/jasmine" would allow you to browse to
|
|
9
|
+
# `http://localhost:3000/jasmine` to run your tests.
|
|
10
|
+
config.mount_at = "/teaspoon"
|
|
11
|
+
|
|
12
|
+
# Specifies the root where Teaspoon will look for files. If you're testing an engine using a dummy application it can
|
|
13
|
+
# be useful to set this to your engines root (e.g. `Teaspoon::Engine.root`).
|
|
14
|
+
# Note: Defaults to `Rails.root` if nil.
|
|
15
|
+
config.root = Mumuki::Laboratory::Engine.root
|
|
16
|
+
|
|
17
|
+
# Paths that will be appended to the Rails assets paths
|
|
18
|
+
# Note: Relative to `config.root`.
|
|
19
|
+
config.asset_paths = ["spec/javascripts", "spec/javascripts/stylesheets"]
|
|
20
|
+
|
|
21
|
+
# Fixtures are rendered through a controller, which allows using HAML, RABL/JBuilder, etc. Files in these paths will
|
|
22
|
+
# be rendered as fixtures.
|
|
23
|
+
config.fixture_paths = ["spec/javascripts/fixtures"]
|
|
24
|
+
|
|
25
|
+
# SUITES
|
|
26
|
+
#
|
|
27
|
+
# You can modify the default suite configuration and create new suites here. Suites are isolated from one another.
|
|
28
|
+
#
|
|
29
|
+
# When defining a suite you can provide a name and a block. If the name is left blank, :default is assumed. You can
|
|
30
|
+
# omit various directives and the ones defined in the default suite will be used.
|
|
31
|
+
#
|
|
32
|
+
# To run a specific suite
|
|
33
|
+
# - in the browser: http://localhost/teaspoon/[suite_name]
|
|
34
|
+
# - with the rake task: rake teaspoon suite=[suite_name]
|
|
35
|
+
# - with the cli: teaspoon --suite=[suite_name]
|
|
36
|
+
config.suite do |suite|
|
|
37
|
+
# Specify the framework you would like to use. This allows you to select versions, and will do some basic setup for
|
|
38
|
+
# you -- which you can override with the directives below. This should be specified first, as it can override other
|
|
39
|
+
# directives.
|
|
40
|
+
# Note: If no version is specified, the latest is assumed.
|
|
41
|
+
#
|
|
42
|
+
# Versions: 1.3.1, 2.0.3, 2.1.3, 2.2.0, 2.2.1, 2.3.4
|
|
43
|
+
suite.use_framework :jasmine, "2.3.4"
|
|
44
|
+
|
|
45
|
+
# Specify a file matcher as a regular expression and all matching files will be loaded when the suite is run. These
|
|
46
|
+
# files need to be within an asset path. You can add asset paths using the `config.asset_paths`.
|
|
47
|
+
suite.matcher = "{spec/javascripts,app/assets}/**/*-spec.{js,js.coffee,coffee}"
|
|
48
|
+
|
|
49
|
+
# Load additional JS files, but requiring them in your spec helper is the preferred way to do this.
|
|
50
|
+
#suite.javascripts = []
|
|
51
|
+
|
|
52
|
+
# You can include your own stylesheets if you want to change how Teaspoon looks.
|
|
53
|
+
# Note: Spec related CSS can and should be loaded using fixtures.
|
|
54
|
+
#suite.stylesheets = ["teaspoon"]
|
|
55
|
+
|
|
56
|
+
# This suites spec helper, which can require additional support files. This file is loaded before any of your test
|
|
57
|
+
# files are loaded.
|
|
58
|
+
suite.helper = "spec-helper"
|
|
59
|
+
|
|
60
|
+
# Partial to be rendered in the head tag of the runner. You can use the provided ones or define your own by creating
|
|
61
|
+
# a `_boot.html.erb` in your fixtures path, and adjust the config to `"/boot"` for instance.
|
|
62
|
+
#
|
|
63
|
+
# Available: boot, boot_require_js
|
|
64
|
+
suite.boot_partial = "boot"
|
|
65
|
+
|
|
66
|
+
# Partial to be rendered in the body tag of the runner. You can define your own to create a custom body structure.
|
|
67
|
+
suite.body_partial = "body"
|
|
68
|
+
|
|
69
|
+
# Hooks allow you to use `Teaspoon.hook("fixtures")` before, after, or during your spec run. This will make a
|
|
70
|
+
# synchronous Ajax request to the server that will call all of the blocks you've defined for that hook name.
|
|
71
|
+
#suite.hook :fixtures, &proc{}
|
|
72
|
+
|
|
73
|
+
# Determine whether specs loaded into the test harness should be embedded as individual script tags or concatenated
|
|
74
|
+
# into a single file. Similar to Rails' asset `debug: true` and `config.assets.debug = true` options. By default,
|
|
75
|
+
# Teaspoon expands all assets to provide more valuable stack traces that reference individual source files.
|
|
76
|
+
#suite.expand_assets = true
|
|
77
|
+
|
|
78
|
+
# Non-.js file extensions Teaspoon should consider JavaScript files
|
|
79
|
+
#suite.js_extensions = [/(\.js)?.coffee/, /(\.js)?.es6/, ".es6.js"]
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Example suite. Since we're just filtering to files already within the root test/javascripts, these files will also
|
|
83
|
+
# be run in the default suite -- but can be focused into a more specific suite.
|
|
84
|
+
#config.suite :targeted do |suite|
|
|
85
|
+
# suite.matcher = "spec/javascripts/targeted/*_spec.{js,js.coffee,coffee}"
|
|
86
|
+
#end
|
|
87
|
+
|
|
88
|
+
# CONSOLE RUNNER SPECIFIC
|
|
89
|
+
#
|
|
90
|
+
# These configuration directives are applicable only when running via the rake task or command line interface. These
|
|
91
|
+
# directives can be overridden using the command line interface arguments or with ENV variables when using the rake
|
|
92
|
+
# task.
|
|
93
|
+
#
|
|
94
|
+
# Command Line Interface:
|
|
95
|
+
# teaspoon --driver=phantomjs --server-port=31337 --fail-fast=true --format=junit --suite=my_suite /spec/file_spec.js
|
|
96
|
+
#
|
|
97
|
+
# Rake:
|
|
98
|
+
# teaspoon DRIVER=phantomjs SERVER_PORT=31337 FAIL_FAST=true FORMATTERS=junit suite=my_suite
|
|
99
|
+
|
|
100
|
+
# Specify which headless driver to use. Supports PhantomJS, Selenium Webdriver and BrowserStack Webdriver.
|
|
101
|
+
#
|
|
102
|
+
# Available: :phantomjs, :selenium, :browserstack, :capybara_webkit
|
|
103
|
+
# PhantomJS: https://github.com/modeset/teaspoon/wiki/Using-PhantomJS
|
|
104
|
+
# Selenium Webdriver: https://github.com/modeset/teaspoon/wiki/Using-Selenium-WebDriver
|
|
105
|
+
# BrowserStack Webdriver: https://github.com/modeset/teaspoon/wiki/Using-BrowserStack-WebDriver
|
|
106
|
+
# Capybara Webkit: https://github.com/modeset/teaspoon/wiki/Using-Capybara-Webkit
|
|
107
|
+
# config.driver = :capybara_webkit
|
|
108
|
+
config.driver = :selenium
|
|
109
|
+
config.driver_options = {client_driver: :firefox}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
# Specify the timeout for the driver. Specs are expected to complete within this time frame or the run will be
|
|
113
|
+
# considered a failure. This is to avoid issues that can arise where tests stall.
|
|
114
|
+
#config.driver_timeout = 180
|
|
115
|
+
|
|
116
|
+
# Specify a server to use with Rack (e.g. thin, mongrel). If nil is provided Rack::Server is used.
|
|
117
|
+
#config.server = nil
|
|
118
|
+
|
|
119
|
+
# Specify a host to run on a specific host, otherwise Teaspoon will use 127.0.0.1.
|
|
120
|
+
#config.server_host = nil
|
|
121
|
+
|
|
122
|
+
# Specify a port to run on a specific port, otherwise Teaspoon will use a random available port.
|
|
123
|
+
#config.server_port = nil
|
|
124
|
+
|
|
125
|
+
# Timeout for starting the server in seconds. If your server is slow to start you may have to bump this, or you may
|
|
126
|
+
# want to lower this if you know it shouldn't take long to start.
|
|
127
|
+
#config.server_timeout = 20
|
|
128
|
+
|
|
129
|
+
# Force Teaspoon to fail immediately after a failing suite. Can be useful to make Teaspoon fail early if you have
|
|
130
|
+
# several suites, but in environments like CI this may not be desirable.
|
|
131
|
+
#config.fail_fast = true
|
|
132
|
+
|
|
133
|
+
# Specify the formatters to use when outputting the results.
|
|
134
|
+
# Note: Output files can be specified by using `"junit>/path/to/output.xml"`.
|
|
135
|
+
#
|
|
136
|
+
# Available: :dot, :clean, :documentation, :json, :junit, :pride, :rspec_html, :snowday, :swayze_or_oprah, :tap, :tap_y, :teamcity
|
|
137
|
+
config.formatters = [:documentation]
|
|
138
|
+
|
|
139
|
+
# Specify if you want color output from the formatters.
|
|
140
|
+
#config.color = true
|
|
141
|
+
|
|
142
|
+
# Teaspoon pipes all console[log/debug/error] to $stdout. This is useful to catch places where you've forgotten to
|
|
143
|
+
# remove them, but in verbose applications this may not be desirable.
|
|
144
|
+
#config.suppress_log = false
|
|
145
|
+
|
|
146
|
+
# COVERAGE REPORTS / THRESHOLD ASSERTIONS
|
|
147
|
+
#
|
|
148
|
+
# Coverage reports requires Istanbul (https://github.com/gotwarlost/istanbul) to add instrumentation to your code and
|
|
149
|
+
# display coverage statistics.
|
|
150
|
+
#
|
|
151
|
+
# Coverage configurations are similar to suites. You can define several, and use different ones under different
|
|
152
|
+
# conditions.
|
|
153
|
+
#
|
|
154
|
+
# To run with a specific coverage configuration
|
|
155
|
+
# - with the rake task: rake teaspoon USE_COVERAGE=[coverage_name]
|
|
156
|
+
# - with the cli: teaspoon --coverage=[coverage_name]
|
|
157
|
+
|
|
158
|
+
# Specify that you always want a coverage configuration to be used. Otherwise, specify that you want coverage
|
|
159
|
+
# on the CLI.
|
|
160
|
+
# Set this to "true" or the name of your coverage config.
|
|
161
|
+
#config.use_coverage = nil
|
|
162
|
+
|
|
163
|
+
# You can have multiple coverage configs by passing a name to config.coverage.
|
|
164
|
+
# e.g. config.coverage :ci do |coverage|
|
|
165
|
+
# The default coverage config name is :default.
|
|
166
|
+
config.coverage do |coverage|
|
|
167
|
+
# Which coverage reports Istanbul should generate. Correlates directly to what Istanbul supports.
|
|
168
|
+
#
|
|
169
|
+
# Available: text-summary, text, html, lcov, lcovonly, cobertura, teamcity
|
|
170
|
+
#coverage.reports = ["text-summary", "html"]
|
|
171
|
+
|
|
172
|
+
# The path that the coverage should be written to - when there's an artifact to write to disk.
|
|
173
|
+
# Note: Relative to `config.root`.
|
|
174
|
+
#coverage.output_path = "coverage"
|
|
175
|
+
|
|
176
|
+
# Assets to be ignored when generating coverage reports. Accepts an array of filenames or regular expressions. The
|
|
177
|
+
# default excludes assets from vendor, gems and support libraries.
|
|
178
|
+
#coverage.ignore = [%r{/lib/ruby/gems/}, %r{/vendor/assets/}, %r{/support/}, %r{/(.+)_helper.}]
|
|
179
|
+
|
|
180
|
+
# Various thresholds requirements can be defined, and those thresholds will be checked at the end of a run. If any
|
|
181
|
+
# aren't met the run will fail with a message. Thresholds can be defined as a percentage (0-100), or nil.
|
|
182
|
+
#coverage.statements = nil
|
|
183
|
+
#coverage.functions = nil
|
|
184
|
+
#coverage.branches = nil
|
|
185
|
+
#coverage.lines = nil
|
|
186
|
+
end
|
|
187
|
+
end
|
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: 7.7.
|
|
4
|
+
version: 7.7.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Franco Bulgarelli
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2020-08-
|
|
11
|
+
date: 2020-08-07 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|
|
@@ -30,14 +30,14 @@ dependencies:
|
|
|
30
30
|
requirements:
|
|
31
31
|
- - "~>"
|
|
32
32
|
- !ruby/object:Gem::Version
|
|
33
|
-
version: 7.7.
|
|
33
|
+
version: 7.7.2
|
|
34
34
|
type: :runtime
|
|
35
35
|
prerelease: false
|
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
37
|
requirements:
|
|
38
38
|
- - "~>"
|
|
39
39
|
- !ruby/object:Gem::Version
|
|
40
|
-
version: 7.7.
|
|
40
|
+
version: 7.7.2
|
|
41
41
|
- !ruby/object:Gem::Dependency
|
|
42
42
|
name: mumukit-bridge
|
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -424,6 +424,7 @@ files:
|
|
|
424
424
|
- app/controllers/concerns/users_controller_template.rb
|
|
425
425
|
- app/controllers/concerns/with_authorization.rb
|
|
426
426
|
- app/controllers/concerns/with_errors_filter.rb
|
|
427
|
+
- app/controllers/concerns/with_user_discussion_validation.rb
|
|
427
428
|
- app/controllers/concerns/with_user_params.rb
|
|
428
429
|
- app/controllers/discussions_controller.rb
|
|
429
430
|
- app/controllers/discussions_messages_controller.rb
|
|
@@ -632,6 +633,7 @@ files:
|
|
|
632
633
|
- spec/controllers/confirmations_controller_spec.rb
|
|
633
634
|
- spec/controllers/courses_api_controller_spec.rb
|
|
634
635
|
- spec/controllers/discussions_controller_spec.rb
|
|
636
|
+
- spec/controllers/discussions_messages_controller_spec.rb
|
|
635
637
|
- spec/controllers/exercise_solutions_controller_spec.rb
|
|
636
638
|
- spec/controllers/guide_progress_controller_spec.rb
|
|
637
639
|
- spec/controllers/invitations_controller_spec.rb
|
|
@@ -684,6 +686,7 @@ files:
|
|
|
684
686
|
- spec/features/complements_flow_spec.rb
|
|
685
687
|
- spec/features/disable_user_flow_spec.rb
|
|
686
688
|
- spec/features/disabled_organization_flow_spec.rb
|
|
689
|
+
- spec/features/discussion_flow_spec.rb
|
|
687
690
|
- spec/features/dynamic_exam_spec.rb
|
|
688
691
|
- spec/features/exams_flow_spec.rb
|
|
689
692
|
- spec/features/exercise_flow_spec.rb
|
|
@@ -713,10 +716,19 @@ files:
|
|
|
713
716
|
- spec/helpers/test_results_rendering_spec.rb
|
|
714
717
|
- spec/helpers/with_choices_spec.rb
|
|
715
718
|
- spec/helpers/with_navigation_spec.rb
|
|
719
|
+
- spec/javascripts/bridge-spec.js
|
|
720
|
+
- spec/javascripts/csrf-token-spec.js
|
|
721
|
+
- spec/javascripts/elipsis-spec.js
|
|
722
|
+
- spec/javascripts/results-renderers-spec.js
|
|
723
|
+
- spec/javascripts/spec-helper.js
|
|
724
|
+
- spec/javascripts/speech-bubble-renderer-spec.js
|
|
725
|
+
- spec/javascripts/timeout-spec.js
|
|
726
|
+
- spec/javascripts/timer-spec.js
|
|
716
727
|
- spec/login_helper.rb
|
|
717
728
|
- spec/mailers/previews/user_mailer_preview.rb
|
|
718
729
|
- spec/mailers/user_mailer_spec.rb
|
|
719
730
|
- spec/spec_helper.rb
|
|
731
|
+
- spec/teaspoon_env.rb
|
|
720
732
|
- vendor/assets/javascripts/analytics.js
|
|
721
733
|
- vendor/assets/javascripts/codemirror-autorefresh.js
|
|
722
734
|
- vendor/assets/javascripts/codemirror-modes/assembly_x86.js
|
|
@@ -784,6 +796,7 @@ test_files:
|
|
|
784
796
|
- spec/mailers/previews/user_mailer_preview.rb
|
|
785
797
|
- spec/mailers/user_mailer_spec.rb
|
|
786
798
|
- spec/spec_helper.rb
|
|
799
|
+
- spec/teaspoon_env.rb
|
|
787
800
|
- spec/features/guide_reset_spec.rb
|
|
788
801
|
- spec/features/complements_flow_spec.rb
|
|
789
802
|
- spec/features/not_found_public_flow_spec.rb
|
|
@@ -798,6 +811,7 @@ test_files:
|
|
|
798
811
|
- spec/features/home_private_flow_spec.rb
|
|
799
812
|
- spec/features/lessons_flow_spec.rb
|
|
800
813
|
- spec/features/dynamic_exam_spec.rb
|
|
814
|
+
- spec/features/discussion_flow_spec.rb
|
|
801
815
|
- spec/features/not_found_private_flow_spec.rb
|
|
802
816
|
- spec/features/progressive_tips_spec.rb
|
|
803
817
|
- spec/features/exams_flow_spec.rb
|
|
@@ -860,6 +874,14 @@ test_files:
|
|
|
860
874
|
- spec/api_helper.rb
|
|
861
875
|
- spec/capybara_helper.rb
|
|
862
876
|
- spec/evaluation_helper.rb
|
|
877
|
+
- spec/javascripts/bridge-spec.js
|
|
878
|
+
- spec/javascripts/speech-bubble-renderer-spec.js
|
|
879
|
+
- spec/javascripts/timeout-spec.js
|
|
880
|
+
- spec/javascripts/results-renderers-spec.js
|
|
881
|
+
- spec/javascripts/csrf-token-spec.js
|
|
882
|
+
- spec/javascripts/elipsis-spec.js
|
|
883
|
+
- spec/javascripts/timer-spec.js
|
|
884
|
+
- spec/javascripts/spec-helper.js
|
|
863
885
|
- spec/controllers/discussions_controller_spec.rb
|
|
864
886
|
- spec/controllers/chapters_controller_spec.rb
|
|
865
887
|
- spec/controllers/students_api_controller_spec.rb
|
|
@@ -869,6 +891,7 @@ test_files:
|
|
|
869
891
|
- spec/controllers/invitations_controller_spec.rb
|
|
870
892
|
- spec/controllers/users_api_controller_spec.rb
|
|
871
893
|
- spec/controllers/users_controller_spec.rb
|
|
894
|
+
- spec/controllers/discussions_messages_controller_spec.rb
|
|
872
895
|
- spec/controllers/guide_progress_controller_spec.rb
|
|
873
896
|
- spec/controllers/organizations_api_controller_spec.rb
|
|
874
897
|
- spec/controllers/messages_controller_spec.rb
|