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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +9 -0
  3. data/Rakefile +3 -0
  4. data/app/assets/stylesheets/mumuki_laboratory/application/modules/_discussion.scss +1 -2
  5. data/app/controllers/concerns/with_user_discussion_validation.rb +14 -0
  6. data/app/controllers/discussions_controller.rb +1 -14
  7. data/app/controllers/discussions_messages_controller.rb +1 -0
  8. data/app/helpers/discussions_helper.rb +9 -5
  9. data/app/views/discussions/_message.html.erb +16 -9
  10. data/app/views/discussions/show.html.erb +9 -7
  11. data/app/views/layouts/_copyright.html.erb +1 -1
  12. data/app/views/layouts/_discussions.html.erb +4 -4
  13. data/app/views/user_mailer/1st_reminder.html.erb +1 -1
  14. data/app/views/user_mailer/1st_reminder.text.erb +1 -1
  15. data/app/views/user_mailer/2nd_reminder.html.erb +1 -1
  16. data/app/views/user_mailer/2nd_reminder.text.erb +1 -1
  17. data/app/views/user_mailer/3rd_reminder.html.erb +1 -1
  18. data/app/views/user_mailer/3rd_reminder.text.erb +1 -1
  19. data/app/views/user_mailer/no_submissions_reminder.html.erb +1 -1
  20. data/app/views/user_mailer/no_submissions_reminder.text.erb +1 -1
  21. data/lib/mumuki/laboratory/locales/en.yml +3 -2
  22. data/lib/mumuki/laboratory/locales/es.yml +2 -1
  23. data/lib/mumuki/laboratory/locales/pt.yml +3 -2
  24. data/lib/mumuki/laboratory/version.rb +1 -1
  25. data/spec/controllers/discussions_messages_controller_spec.rb +73 -0
  26. data/spec/dummy/db/schema.rb +1 -0
  27. data/spec/features/discussion_flow_spec.rb +190 -0
  28. data/spec/features/menu_bar_spec.rb +88 -7
  29. data/spec/javascripts/bridge-spec.js +5 -0
  30. data/spec/javascripts/csrf-token-spec.js +7 -0
  31. data/spec/javascripts/elipsis-spec.js +25 -0
  32. data/spec/javascripts/results-renderers-spec.js +17 -0
  33. data/spec/javascripts/spec-helper.js +30 -0
  34. data/spec/javascripts/speech-bubble-renderer-spec.js +11 -0
  35. data/spec/javascripts/timeout-spec.js +5 -0
  36. data/spec/javascripts/timer-spec.js +5 -0
  37. data/spec/teaspoon_env.rb +187 -0
  38. metadata +27 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3c7dbf715c210c593e2a509411ba1f39bfa2c061c9b4e45ffd132634abcd6060
4
- data.tar.gz: decfa9f0385134c07cdb7e0fe933c29dcf1c3864741840a7a25573c1c8141c3f
3
+ metadata.gz: fa8619681047d1ea5b8230abc24e7b4b38c63f722d355a3facd956d725b0b3dd
4
+ data.tar.gz: bdefac6e50c9a9cb6950222bc822f8460e095f47a3602ae7ad86b4c3c41ea634
5
5
  SHA512:
6
- metadata.gz: 11813d4b113221ebbc7012a4fa86c78b69cf070f979706d54fe70afa4b3543193f623f8837665c3879d081fca10fad3b3e1dbba66491e6eca338362f268acdb7
7
- data.tar.gz: 472fc73e4c480f8f69797ad5639485ffc4baa1887c28861c4aa59d3b03719ac3df5ebae5e356dc00e8d06a343183793b1d9c89b813c09a35377537a1ad54794e
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
@@ -1,5 +1,6 @@
1
1
  class DiscussionsMessagesController < AjaxController
2
2
  include WithAuthorization
3
+ include WithUserDiscussionValidation
3
4
 
4
5
  before_action :set_discussion!, only: [:create, :destroy]
5
6
  before_action :authorize_user!, only: [:destroy]
@@ -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, organization = Organization.current)
27
- link_to item, path, html_options if organization.forum_enabled?
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, {'data-toggle': 'tooltip', title: (t :moderator), class: 'moderator-star'}) %>
10
+ <%= fa_icon(:star, 'data-toggle': 'tooltip', title: (t :moderator), class: 'moderator-star') %>
11
11
  <% end %>
12
- <% if message.authorized? current_user %>
13
- <span class="actions">
14
- <% if current_user.moderator_here? %>
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
- <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))">
19
- <%= fa_icon('question-circle-o', class: 'fa-xs') %>
20
- </a>
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
- </span>
24
- <% end %>
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
- <div class="discussion-actions">
45
- <% @discussion.reachable_statuses_for(current_user).each do |status| %>
46
- <%= discussion_update_status_button(status) %>
47
- <% end if current_user %>
48
- </div>
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
  &copy; Copyright 2015-<%= DateTime.now.year %>
2
- <a href="http://mumuki.org/" class="mu-org-link"><span class="da da-mumuki-circle"></span> Mumuki Project</a>
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 item_discussion_path(discussion) do %>
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.friendly %></span>
60
- <span class="hidden-md hidden-lg"><%= discussion.exercise.name %></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 %>&nbsp;<a href="https://mumuki.org/home/">Mumuki Project</a><em>.</em><br>
316
+ © Copyright 2015-<%= DateTime.now.year %>&nbsp;<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 %>
@@ -7,7 +7,7 @@
7
7
  <%= t :keep_learning %>
8
8
 
9
9
 
10
- © Copyright 2015-<%= DateTime.now.year %> Mumuki Project.
10
+ © Copyright 2015-<%= DateTime.now.year %> Mumuki.
11
11
 
12
12
  <%= t :stop_emails? %>
13
13
  <%= t :cancel_subscription %>
@@ -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 %>&nbsp;<a href="https://mumuki.org/home/">Mumuki Project</a><em>.</em><br>
316
+ © Copyright 2015-<%= DateTime.now.year %>&nbsp;<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 %>
@@ -7,7 +7,7 @@
7
7
  <%= t :keep_learning %>
8
8
 
9
9
 
10
- © Copyright 2015-<%= DateTime.now.year %> Mumuki Project.
10
+ © Copyright 2015-<%= DateTime.now.year %> Mumuki.
11
11
 
12
12
  <%= t :stop_emails? %>
13
13
  <%= t :cancel_subscription %>
@@ -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 %>&nbsp;<a href="https://mumuki.org/home/">Mumuki Project</a><em>.</em><br>
316
+ © Copyright 2015-<%= DateTime.now.year %>&nbsp;<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 %>
@@ -7,7 +7,7 @@
7
7
  <%= t :keep_learning %>
8
8
 
9
9
 
10
- © Copyright 2015-<%= DateTime.now.year %> Mumuki Project.
10
+ © Copyright 2015-<%= DateTime.now.year %> Mumuki.
11
11
 
12
12
  <%= t :stop_emails? %>
13
13
  <%= t :cancel_subscription %>
@@ -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 %>&nbsp;<a href="https://mumuki.org/home/">Mumuki Project</a><em>.</em><br>
316
+ © Copyright 2015-<%= DateTime.now.year %>&nbsp;<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 %>
@@ -7,7 +7,7 @@
7
7
  <%= t :keep_learning %>
8
8
 
9
9
 
10
- © Copyright 2015-<%= DateTime.now.year %> Mumuki Project.
10
+ © Copyright 2015-<%= DateTime.now.year %> Mumuki.
11
11
 
12
12
  <%= t :stop_emails? %>
13
13
  <%= t :cancel_subscription %>
@@ -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: 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: Não encontrou o que estava buscando?
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: Moderador
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: Moderador
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: Didn't find what you were looking for
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!
@@ -1,5 +1,5 @@
1
1
  module Mumuki
2
2
  module Laboratory
3
- VERSION = '7.7.2'
3
+ VERSION = '7.7.3'
4
4
  end
5
5
  end
@@ -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
@@ -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(:chapter) { create(:chapter, lessons: [create(:lesson)]) }
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
- scenario 'student should only see profile' do
58
- set_current_user! visitor
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
- visit '/'
61
- expect(page).to have_text('Profile')
62
- expect(page).not_to have_text('Classroom')
63
- expect(page).not_to have_text('Bibliotheca')
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,5 @@
1
+ describe('bridge', () => {
2
+ it('can create bridge', () => {
3
+ expect(new mumuki.bridge.Laboratory()).not.toBe(null);
4
+ })
5
+ })
@@ -0,0 +1,7 @@
1
+ describe('csrf token', () => {
2
+ it('can create token', () => {
3
+ expect(new mumuki.CsrfToken()).not.toBe(null);
4
+ })
5
+ })
6
+
7
+
@@ -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
+ /*&lt;elipsis-for-student@*/
9
+ return unString.length;
10
+ /*@elipsis-for-student&gt;*/
11
+ }`)).toEqual(`function longitud(unString) {
12
+ /* ... */
13
+ }`)
14
+ });
15
+
16
+ it('replaces student hidden', () => {
17
+ expect(mumuki.elipsis(`function longitud(unString) {
18
+ /*&lt;hidden-for-student@*/
19
+ return unString.length;
20
+ /*@hidden-for-student&gt;*/
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,5 @@
1
+ describe('setTimeout', () => {
2
+ it('can create setTimeout', () => {
3
+ expect(mumuki.setTimeout).not.toBe(null);
4
+ })
5
+ })
@@ -0,0 +1,5 @@
1
+ describe('timer', () => {
2
+ it('can create timer', () => {
3
+ expect(mumuki.startTimer).not.toBe(null);
4
+ })
5
+ })
@@ -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.2
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-01 00:00:00.000000000 Z
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.1
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.1
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