From e7796d2a30368aabf3a1db98d5c16374ef6a65a4 Mon Sep 17 00:00:00 2001 From: Splines Date: Mon, 5 Jun 2023 00:16:50 +0200 Subject: [PATCH 01/57] Init Feedback model --- app/models/feedback.rb | 3 +++ config/routes.rb | 3 +++ db/migrate/20230529080510_create_feedbacks.rb | 11 +++++++++++ db/schema.rb | 12 +++++++++++- spec/factories/feedbacks.rb | 7 +++++++ spec/models/feedback_spec.rb | 5 +++++ spec/requests/feedback_spec.rb | 7 +++++++ 7 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 app/models/feedback.rb create mode 100644 db/migrate/20230529080510_create_feedbacks.rb create mode 100644 spec/factories/feedbacks.rb create mode 100644 spec/models/feedback_spec.rb create mode 100644 spec/requests/feedback_spec.rb diff --git a/app/models/feedback.rb b/app/models/feedback.rb new file mode 100644 index 000000000..63affddde --- /dev/null +++ b/app/models/feedback.rb @@ -0,0 +1,3 @@ +class Feedback < ApplicationRecord + belongs_to :user +end diff --git a/config/routes.rb b/config/routes.rb index 46bc8c68d..7b0b11813 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -141,6 +141,9 @@ resources :divisions, except: [:show] + # feedback routes + resources :feedbacks, only: [:create] + # interactions routes get 'interactions/export_interactions', diff --git a/db/migrate/20230529080510_create_feedbacks.rb b/db/migrate/20230529080510_create_feedbacks.rb new file mode 100644 index 000000000..5ddc10ab5 --- /dev/null +++ b/db/migrate/20230529080510_create_feedbacks.rb @@ -0,0 +1,11 @@ +class CreateFeedbacks < ActiveRecord::Migration[7.0] + def change + create_table :feedbacks do |t| + t.text :title + t.text :feedback + t.references :user, null: false, foreign_key: true + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index d9abf5def..96fb50448 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2023_04_27_124337) do +ActiveRecord::Schema[7.0].define(version: 2023_05_29_080510) do # These are extensions that must be enabled in order to support this database enable_extension "pgcrypto" enable_extension "plpgsql" @@ -189,6 +189,15 @@ t.index ["editable_id", "editable_type"], name: "polymorphic_editable_idx" end + create_table "feedbacks", force: :cascade do |t| + t.text "title" + t.text "feedback" + t.bigint "user_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["user_id"], name: "index_feedbacks_on_user_id" + end + create_table "friendly_id_slugs", force: :cascade do |t| t.string "slug", null: false t.integer "sluggable_id", null: false @@ -906,6 +915,7 @@ add_foreign_key "commontator_subscriptions", "commontator_threads", column: "thread_id", on_update: :cascade, on_delete: :cascade add_foreign_key "course_self_joins", "courses" add_foreign_key "divisions", "programs" + add_foreign_key "feedbacks", "users" add_foreign_key "imports", "media" add_foreign_key "items", "media" add_foreign_key "items", "sections" diff --git a/spec/factories/feedbacks.rb b/spec/factories/feedbacks.rb new file mode 100644 index 000000000..f5c995a2d --- /dev/null +++ b/spec/factories/feedbacks.rb @@ -0,0 +1,7 @@ +FactoryBot.define do + factory :feedback do + title { "MyText" } + feedback { "MyText" } + user { nil } + end +end diff --git a/spec/models/feedback_spec.rb b/spec/models/feedback_spec.rb new file mode 100644 index 000000000..ef016398e --- /dev/null +++ b/spec/models/feedback_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe Feedback, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/requests/feedback_spec.rb b/spec/requests/feedback_spec.rb new file mode 100644 index 000000000..fece6185c --- /dev/null +++ b/spec/requests/feedback_spec.rb @@ -0,0 +1,7 @@ +require 'rails_helper' + +RSpec.describe "Feedbacks", type: :request do + describe "GET /index" do + pending "add some examples (or delete) #{__FILE__}" + end +end From 9c6659505ef036c78415dc099edf606fd0ffd668 Mon Sep 17 00:00:00 2001 From: Splines Date: Mon, 5 Jun 2023 00:18:01 +0200 Subject: [PATCH 02/57] Add Feedback modal view and corresponding controller First working version, of course still a lot to improve from here. --- app/assets/javascripts/lectures.coffee | 2 ++ app/assets/stylesheets/feedback.scss | 11 +++++++ app/controllers/feedbacks_controller.rb | 23 ++++++++++++++ app/views/feedbacks/_feedback_button.html.erb | 9 ++++++ app/views/feedbacks/_feedback_form.html.erb | 31 +++++++++++++++++++ app/views/feedbacks/create.js.erb | 4 +++ .../shared/_dropdown_notifications.html.erb | 2 +- app/views/shared/_navbar.html.erb | 15 +++++++++ 8 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 app/assets/stylesheets/feedback.scss create mode 100644 app/controllers/feedbacks_controller.rb create mode 100644 app/views/feedbacks/_feedback_button.html.erb create mode 100644 app/views/feedbacks/_feedback_form.html.erb create mode 100644 app/views/feedbacks/create.js.erb diff --git a/app/assets/javascripts/lectures.coffee b/app/assets/javascripts/lectures.coffee index 7fe308c6e..590fef709 100644 --- a/app/assets/javascripts/lectures.coffee +++ b/app/assets/javascripts/lectures.coffee @@ -214,6 +214,7 @@ $(document).on 'turbolinks:load', -> $('#secondnav').show() $('#lecturesDropdown').appendTo($('#secondnav')) $('#notificationDropdown').appendTo($('#secondnav')) + $('#feedback-btn').appendTo($('#secondnav')) $('#searchField').appendTo($('#secondnav')) $('#second-admin-nav').show() $('#adminDetails').appendTo($('#second-admin-nav')) @@ -236,6 +237,7 @@ $(document).on 'turbolinks:load', -> $('#secondnav').hide() $('#lecturesDropdown').appendTo($('#firstnav')) $('#notificationDropdown').appendTo($('#firstnav')) + $('#feedback-btn').appendTo($('#firstnav')) $('#searchField').appendTo($('#firstnav')) $('#second-admin-nav').hide() $('#teachableDrop').appendTo($('#first-admin-nav')) diff --git a/app/assets/stylesheets/feedback.scss b/app/assets/stylesheets/feedback.scss new file mode 100644 index 000000000..7f14ad18c --- /dev/null +++ b/app/assets/stylesheets/feedback.scss @@ -0,0 +1,11 @@ +#feedback-btn { + color: white; + + &:focus { + box-shadow: none; + } + + &:hover { + color: #ffc107; + } +} \ No newline at end of file diff --git a/app/controllers/feedbacks_controller.rb b/app/controllers/feedbacks_controller.rb new file mode 100644 index 000000000..b44c9202c --- /dev/null +++ b/app/controllers/feedbacks_controller.rb @@ -0,0 +1,23 @@ +class FeedbacksController < ApplicationController + authorize_resource except: [:create] + + def create + feedback = Feedback.new(feedback_params) + feedback.user_id = current_user.id + successfully_saved = feedback.save + flash.now[:status_msg] = if successfully_saved + 'Feedback successfully sent.' + else + 'Something went wrong.' + end + @errors = feedback.errors + # # redirect_to :root, alert: @errors.full_messages.join(', ') + respond_to(&:js) + end + + private + + def feedback_params + params.require(:feedback).permit(:title, :feedback) + end +end diff --git a/app/views/feedbacks/_feedback_button.html.erb b/app/views/feedbacks/_feedback_button.html.erb new file mode 100644 index 000000000..263dceb3e --- /dev/null +++ b/app/views/feedbacks/_feedback_button.html.erb @@ -0,0 +1,9 @@ +<%= stylesheet_link_tag 'feedback' %> + +<%# Feedback button %> + + diff --git a/app/views/feedbacks/_feedback_form.html.erb b/app/views/feedbacks/_feedback_form.html.erb new file mode 100644 index 000000000..5bc93fbee --- /dev/null +++ b/app/views/feedbacks/_feedback_form.html.erb @@ -0,0 +1,31 @@ + + + + + diff --git a/app/views/feedbacks/create.js.erb b/app/views/feedbacks/create.js.erb new file mode 100644 index 000000000..07cf8e696 --- /dev/null +++ b/app/views/feedbacks/create.js.erb @@ -0,0 +1,4 @@ +console.log('do it') +statusMessage = '<%= flash[:status_msg] %>' +console.log(statusMessage) +$('#feedback-status').text(statusMessage) diff --git a/app/views/shared/_dropdown_notifications.html.erb b/app/views/shared/_dropdown_notifications.html.erb index c9a93a229..444d4aedf 100644 --- a/app/views/shared/_dropdown_notifications.html.erb +++ b/app/views/shared/_dropdown_notifications.html.erb @@ -1,7 +1,7 @@ <% relevant_notifications = current_user.notifications .sort_by(&:created_at).reverse %> <% if relevant_notifications.present? %> - + <%= render partial: 'shared/dropdown_notifications'%> + + <%= render partial: 'feedbacks/feedback_button'%> + <%= form_tag(search_index_path, method: 'get', class: 'form-inline mt-2 mt-md-0', @@ -104,4 +118,5 @@ style="display: none;"> + <% end %> From d41365e84312577a2a337f19cfb901fea92f2cb5 Mon Sep 17 00:00:00 2001 From: Splines Date: Mon, 3 Jul 2023 22:45:56 +0200 Subject: [PATCH 03/57] Migrate feedback form to Bootstrap v5 --- app/views/feedbacks/_feedback_button.html.erb | 4 ++-- app/views/feedbacks/_feedback_form.html.erb | 5 +---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/app/views/feedbacks/_feedback_button.html.erb b/app/views/feedbacks/_feedback_button.html.erb index 263dceb3e..aa0273677 100644 --- a/app/views/feedbacks/_feedback_button.html.erb +++ b/app/views/feedbacks/_feedback_button.html.erb @@ -2,8 +2,8 @@ <%# Feedback button %> diff --git a/app/views/feedbacks/_feedback_form.html.erb b/app/views/feedbacks/_feedback_form.html.erb index 5bc93fbee..1dc4ac413 100644 --- a/app/views/feedbacks/_feedback_form.html.erb +++ b/app/views/feedbacks/_feedback_form.html.erb @@ -1,9 +1,6 @@ From 583642226d28728584d37d7cf4954c8dcfa196e5 Mon Sep 17 00:00:00 2001 From: Splines Date: Mon, 3 Jul 2023 23:43:37 +0200 Subject: [PATCH 04/57] Add basic styling to Feedback form --- app/assets/javascripts/feedback.js | 6 +++ app/views/feedbacks/_feedback_form.html.erb | 43 ++++++++++++++------- config/locales/de.yml | 9 +++++ config/locales/en.yml | 9 +++++ 4 files changed, 54 insertions(+), 13 deletions(-) create mode 100644 app/assets/javascripts/feedback.js diff --git a/app/assets/javascripts/feedback.js b/app/assets/javascripts/feedback.js new file mode 100644 index 000000000..33867995e --- /dev/null +++ b/app/assets/javascripts/feedback.js @@ -0,0 +1,6 @@ +$(document).on('turbolinks:load', () => { + $('#submit-form-btn-outside').click(() => { + console.log('click'); + $('#submit-form-btn').click(); + }); +}); diff --git a/app/views/feedbacks/_feedback_form.html.erb b/app/views/feedbacks/_feedback_form.html.erb index 1dc4ac413..4b5e86e1b 100644 --- a/app/views/feedbacks/_feedback_form.html.erb +++ b/app/views/feedbacks/_feedback_form.html.erb @@ -1,3 +1,5 @@ +<%= javascript_include_tag :feedback %> + diff --git a/config/locales/de.yml b/config/locales/de.yml index 35bec56b1..56d6c9075 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -3790,3 +3790,12 @@ de: name: taken: 'Eine Watchlist mit diesem Namen existiert bereits.' + feedback: + title: Titel (optional) + comment: Dein Kommentar + description_html: > +

Wir freuen uns über Dein Feedack zu MaMpf, sei es Lob, Kritik oder + Anregungen, wie wir als Entwickler:innen-Team die Plattform voranbringen + können. Falls Du einen Bug gefunden hast und einen GitHub-Account hast, + dann erstelle am besten direkt auf %{mampf-github} ein Issue. Ansonsten + gerne auch über dieses Formular.

\ No newline at end of file diff --git a/config/locales/en.yml b/config/locales/en.yml index fd3cb8a3f..496b3ee2f 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -3584,3 +3584,12 @@ en: name: taken: 'A watchlist with that name already exists.' + feedback: + title: Title (optional) + comment: Your comment + description_html: > +

We are looking forward to your feedback on MaMpf, be it praise, criticism + or suggestions on how we as a team of developers can advance the platform. + If you have found a bug and have a GitHub account, then create an issue + directly on %{github_mampf}. Otherwise, you are welcome to use this + form.

\ No newline at end of file From 56c47411da152693fe07791067bad082b442aa6c Mon Sep 17 00:00:00 2001 From: Splines Date: Wed, 5 Jul 2023 16:39:30 +0200 Subject: [PATCH 05/57] Add "allow contact via mail" checkbox A new column was added to the Feedbacks schema. Note that we did not create a new migration as this is a PR which should only contain one migration, namely the one for the creation of the whol Feedback table. --- app/controllers/feedbacks_controller.rb | 2 +- app/views/feedbacks/_feedback_form.html.erb | 12 +++++++++++- config/locales/de.yml | 6 +++++- config/locales/en.yml | 6 +++++- db/migrate/20230529080510_create_feedbacks.rb | 1 + 5 files changed, 23 insertions(+), 4 deletions(-) diff --git a/app/controllers/feedbacks_controller.rb b/app/controllers/feedbacks_controller.rb index b44c9202c..0aae3628f 100644 --- a/app/controllers/feedbacks_controller.rb +++ b/app/controllers/feedbacks_controller.rb @@ -18,6 +18,6 @@ def create private def feedback_params - params.require(:feedback).permit(:title, :feedback) + params.require(:feedback).permit(:title, :feedback, :can_contact) end end diff --git a/app/views/feedbacks/_feedback_form.html.erb b/app/views/feedbacks/_feedback_form.html.erb index 4b5e86e1b..c19b15f70 100644 --- a/app/views/feedbacks/_feedback_form.html.erb +++ b/app/views/feedbacks/_feedback_form.html.erb @@ -1,7 +1,8 @@ <%= javascript_include_tag :feedback %> @@ -26,6 +27,15 @@ <%= f.label :feedback, t('feedback.comment') %> + <%# Email contact %> +
+ <%= f.check_box :can_contact, + class: 'form-check-input' %> + <%= f.label :can_contact, + t('feedback.mail_checkbox', user_mail: @current_user.email), + class: 'form-check-label' %> +
+ <%# Submit %> <%# Dummy submit button, the actual submit button is in the modal footer %> <%= f.submit 'Submit', id: 'submit-form-btn', style: 'display: none;'%> diff --git a/config/locales/de.yml b/config/locales/de.yml index 56d6c9075..fb4627545 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -3791,6 +3791,7 @@ de: taken: 'Eine Watchlist mit diesem Namen existiert bereits.' feedback: + modal_title: Feedback geben title: Titel (optional) comment: Dein Kommentar description_html: > @@ -3798,4 +3799,7 @@ de: Anregungen, wie wir als Entwickler:innen-Team die Plattform voranbringen können. Falls Du einen Bug gefunden hast und einen GitHub-Account hast, dann erstelle am besten direkt auf %{mampf-github} ein Issue. Ansonsten - gerne auch über dieses Formular.

\ No newline at end of file + gerne auch über dieses Formular.

+ mail_checkbox: > + Erlaube es uns, Dich per E-Mail (%{user_mail}) zu kontaktieren, falls es + Rückfragen zu Deinem Feedback gibt. \ No newline at end of file diff --git a/config/locales/en.yml b/config/locales/en.yml index 496b3ee2f..debb79c0d 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -3585,6 +3585,7 @@ en: taken: 'A watchlist with that name already exists.' feedback: + modal_title: Submit feedback title: Title (optional) comment: Your comment description_html: > @@ -3592,4 +3593,7 @@ en: or suggestions on how we as a team of developers can advance the platform. If you have found a bug and have a GitHub account, then create an issue directly on %{github_mampf}. Otherwise, you are welcome to use this - form.

\ No newline at end of file + form.

+ mail_checkbox: > + Allow us to contact you via email (%{user_mail}) if there are questions + about your feedback. \ No newline at end of file diff --git a/db/migrate/20230529080510_create_feedbacks.rb b/db/migrate/20230529080510_create_feedbacks.rb index 5ddc10ab5..40401bdb7 100644 --- a/db/migrate/20230529080510_create_feedbacks.rb +++ b/db/migrate/20230529080510_create_feedbacks.rb @@ -3,6 +3,7 @@ def change create_table :feedbacks do |t| t.text :title t.text :feedback + t.boolean :can_contact, :default => false t.references :user, null: false, foreign_key: true t.timestamps From 90d044e700b37c2eee3d2afde1f96c22318b5c2a Mon Sep 17 00:00:00 2001 From: Splines Date: Wed, 5 Jul 2023 16:42:56 +0200 Subject: [PATCH 06/57] Toggle "allow email contact" by default --- app/views/feedbacks/_feedback_form.html.erb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/views/feedbacks/_feedback_form.html.erb b/app/views/feedbacks/_feedback_form.html.erb index c19b15f70..0a8f39c23 100644 --- a/app/views/feedbacks/_feedback_form.html.erb +++ b/app/views/feedbacks/_feedback_form.html.erb @@ -30,6 +30,7 @@ <%# Email contact %>
<%= f.check_box :can_contact, + checked: 'checked', class: 'form-check-input' %> <%= f.label :can_contact, t('feedback.mail_checkbox', user_mail: @current_user.email), From cd057ed4656bbbeb11c7161d2eeedf6d17894aa6 Mon Sep 17 00:00:00 2001 From: Splines Date: Mon, 7 Aug 2023 12:54:01 +0200 Subject: [PATCH 07/57] Improve submit button handler (outsource to function) --- app/assets/javascripts/feedback.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/feedback.js b/app/assets/javascripts/feedback.js index 33867995e..0f3369343 100644 --- a/app/assets/javascripts/feedback.js +++ b/app/assets/javascripts/feedback.js @@ -1,6 +1,7 @@ -$(document).on('turbolinks:load', () => { +$(document).on('turbolinks:load', registerSubmitButtonHandler); + +function registerSubmitButtonHandler() { $('#submit-form-btn-outside').click(() => { - console.log('click'); $('#submit-form-btn').click(); }); -}); +} From 27e49b9192479789ddf39b44f4f3a73a2a8e5dbc Mon Sep 17 00:00:00 2001 From: Splines Date: Mon, 7 Aug 2023 12:58:11 +0200 Subject: [PATCH 08/57] Init feedback mailer Right now just for ourselves, so that we get a plaintext mail with the feedback of a user. Env variables were adjusted accordingly, but need to be set manually in the production environment! --- app/controllers/feedbacks_controller.rb | 7 ++++++- app/mailers/feedback_mailer.rb | 15 +++++++++++++++ .../new_user_feedback_email.text.erb | 13 +++++++++++++ config/initializers/default_setting.rb | 13 +++++++------ docker/development/docker-compose.yml | 3 +++ docker/production/docker.env | 2 ++ docker/run_cypress_tests/docker-compose.local.yml | 1 + docker/run_cypress_tests/docker-compose.yml | 1 + 8 files changed, 48 insertions(+), 7 deletions(-) create mode 100644 app/mailers/feedback_mailer.rb create mode 100644 app/views/feedback_mailer/new_user_feedback_email.text.erb diff --git a/app/controllers/feedbacks_controller.rb b/app/controllers/feedbacks_controller.rb index 0aae3628f..c8349ef91 100644 --- a/app/controllers/feedbacks_controller.rb +++ b/app/controllers/feedbacks_controller.rb @@ -11,7 +11,12 @@ def create 'Something went wrong.' end @errors = feedback.errors - # # redirect_to :root, alert: @errors.full_messages.join(', ') + # redirect_to :root, alert: @errors.full_messages.join(', ') + + if successfully_saved + FeedbackMailer.with(feedback: feedback).new_user_feedback_email.deliver_later + end + respond_to(&:js) end diff --git a/app/mailers/feedback_mailer.rb b/app/mailers/feedback_mailer.rb new file mode 100644 index 000000000..9355cfe12 --- /dev/null +++ b/app/mailers/feedback_mailer.rb @@ -0,0 +1,15 @@ +class FeedbackMailer < ApplicationMailer + default from: DefaultSetting::FEEDBACK_EMAIL + layout false + + # Mail to the MaMpf developers including the new feedback of a user. + def new_user_feedback_email + @feedback = params[:feedback] + reply_to_mail = @feedback.can_contact ? @feedback.user.email : '' + subject = "Feedback: #{@feedback.title}" + mail(to: DefaultSetting::FEEDBACK_EMAIL, + subject: subject, + content_type: 'text/plain', + reply_to: reply_to_mail) + end +end diff --git a/app/views/feedback_mailer/new_user_feedback_email.text.erb b/app/views/feedback_mailer/new_user_feedback_email.text.erb new file mode 100644 index 000000000..822815720 --- /dev/null +++ b/app/views/feedback_mailer/new_user_feedback_email.text.erb @@ -0,0 +1,13 @@ +# Title +<%= @feedback.title %> + +# Feedback +<%= @feedback.feedback %> + + +----- +<% if @feedback.can_contact %> +Reply to this mail to contact the user. +<% else %> +User did not give permission to contact them regarding their feedback, so we cannot reply to this mail. +<% end %> diff --git a/config/initializers/default_setting.rb b/config/initializers/default_setting.rb index ec1aa667f..fa83c1ea7 100644 --- a/config/initializers/default_setting.rb +++ b/config/initializers/default_setting.rb @@ -1,10 +1,11 @@ class DefaultSetting - ERDBEERE_LINK = ENV['ERDBEERE_SERVER'] - MUESLI_LINK = ENV['MUESLI_SERVER'] - PROJECT_EMAIL = ENV['PROJECT_EMAIL'] - PROJECT_NOTIFICATION_EMAIL = ENV['PROJECT_NOTIFICATION_EMAIL'] - BLOG_LINK = ENV['BLOG'] - URL_HOST_SHORT = ENV['URL_HOST_SHORT'] + ERDBEERE_LINK = ENV.fetch('ERDBEERE_SERVER', nil) + MUESLI_LINK = ENV.fetch('MUESLI_SERVER', nil) + PROJECT_EMAIL = ENV.fetch('PROJECT_EMAIL', nil) + FEEDBACK_EMAIL = ENV.fetch('FEEDBACK_EMAIL', nil) + PROJECT_NOTIFICATION_EMAIL = ENV.fetch('PROJECT_NOTIFICATION_EMAIL', nil) + BLOG_LINK = ENV.fetch('BLOG', nil) + URL_HOST_SHORT = ENV.fetch('URL_HOST_SHORT', nil) RESEARCHGATE_LINK = 'https://www.researchgate.net/project/MaMpf-Mathematische-Medienplattform' TOUR_LINK = 'https://mampf.blog/ueber-mampf/' RESOURCES_LINK = 'https://mampf.blog/ressourcen-fur-editorinnen/' diff --git a/docker/development/docker-compose.yml b/docker/development/docker-compose.yml index a5bd44651..922a6f51d 100644 --- a/docker/development/docker-compose.yml +++ b/docker/development/docker-compose.yml @@ -77,6 +77,7 @@ services: ERDBEERE_API: https://erdbeere.mathi.uni-heidelberg.de/api/v1 MUESLI_SERVER: https://muesli.mathi.uni-heidelberg.de PROJECT_EMAIL: project@localhost + FEEDBACK_EMAIL: feedback@localhost PROJECT_NOTIFICATION_EMAIL: project+notification@localhost ERROR_EMAIL: mampf-error@mathi.uni-heidelberg.de INSTANCE_PATH: mampf @@ -93,6 +94,8 @@ services: PROJECT_EMAIL_USERNAME: mampf PROJECT_EMAIL_PASSWORD: mampf PROJECT_EMAIL_MAILBOX: INBOX + FEEDBACK_EMAIL_USERNAME: mampf + FEEDBACK_EMAIL_PASSWORD: mampf BLOG: https://mampf.blog # uncomment DB_SQL_PRESEED_URL and UPLOADS_PRESEED_URL to enable db preseeding # DB_SQL_PRESEED_URL: "https://heibox.uni-heidelberg.de/d/6fb4a9d2e7f54d8b9931/files/?p=%2F20220923120841_mampf.sql&dl=1" diff --git a/docker/production/docker.env b/docker/production/docker.env index 8070eb0d9..d5dc660b8 100644 --- a/docker/production/docker.env +++ b/docker/production/docker.env @@ -20,6 +20,8 @@ IMAPSERVER=mail.mathi.uni-heidelberg.de PROJECT_EMAIL_USERNAME=creativeusername PROJECT_EMAIL_PASSWORD=secretsecret PROJECT_EMAIL_MAILBOX=Other Users/mampf +FEEDBACK_EMAIL_USERNAME=creative-feedback-username +FEEDBACK_EMAIL_PASSWORD=creative-feedback-password # Due to CORS constraints, some urls are proxied to the media server DOWNLOAD_LOCATION=https://mampf.mathi.uni-heidelberg.de/mediaforward diff --git a/docker/run_cypress_tests/docker-compose.local.yml b/docker/run_cypress_tests/docker-compose.local.yml index 7e704f774..83c57390a 100644 --- a/docker/run_cypress_tests/docker-compose.local.yml +++ b/docker/run_cypress_tests/docker-compose.local.yml @@ -54,6 +54,7 @@ services: ERDBEERE_API: https://erdbeere.mathi.uni-heidelberg.de/api/v1 MUESLI_SERVER: https://muesli.mathi.uni-heidelberg.de PROJECT_EMAIL: project@localhost + FEEDBACK_EMAIL: feedback@localhost PROJECT_NOTIFICATION_EMAIL: project+notification@localhost MEDIA_FOLDER: mampf REDIS_URL: redis://redis:6379/1 diff --git a/docker/run_cypress_tests/docker-compose.yml b/docker/run_cypress_tests/docker-compose.yml index 70603e1cd..e4a8d5139 100644 --- a/docker/run_cypress_tests/docker-compose.yml +++ b/docker/run_cypress_tests/docker-compose.yml @@ -58,6 +58,7 @@ services: ERDBEERE_API: https://erdbeere.mathi.uni-heidelberg.de/api/v1 MUESLI_SERVER: https://muesli.mathi.uni-heidelberg.de PROJECT_EMAIL: project@localhost + FEEBACK_EMAIL: feedback@localhost PROJECT_NOTIFICATION_EMAIL: project+notification@localhost MEDIA_FOLDER: mampf REDIS_URL: redis://redis:6379/1 From 58f438924763707f0fe3a1e928b8ac5dbe3cd2f8 Mon Sep 17 00:00:00 2001 From: Splines Date: Mon, 7 Aug 2023 12:58:39 +0200 Subject: [PATCH 09/57] Adjust feedback mail in views --- app/views/feedbacks/_feedback_form.html.erb | 3 ++- app/views/shared/_footer.html.erb | 2 +- config/locales/de.yml | 5 +++-- config/locales/en.yml | 3 ++- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/app/views/feedbacks/_feedback_form.html.erb b/app/views/feedbacks/_feedback_form.html.erb index 0a8f39c23..8a148e029 100644 --- a/app/views/feedbacks/_feedback_form.html.erb +++ b/app/views/feedbacks/_feedback_form.html.erb @@ -9,7 +9,8 @@ diff --git a/config/locales/de.yml b/config/locales/de.yml index 96427deb4..52d50231b 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -3804,6 +3804,8 @@ de: können. Falls Du einen Bug gefunden hast und einen GitHub-Account hast, dann erstelle am besten direkt auf %{github_mampf} ein Issue. Ansonsten gerne auch über dieses Formular oder direkt per %{feedback_mail}.

+ body_too_short_error: > + Dein Feedback ist zu kurz. Bitte gib mindestens %{min_length} Zeichen ein. mail_checkbox: > Erlaube es uns, Dich per E-Mail (%{user_mail}) zu kontaktieren, falls es Rückfragen zu Deinem Feedback gibt. diff --git a/config/locales/en.yml b/config/locales/en.yml index bda06ca32..5f36c66de 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -3598,6 +3598,8 @@ en: If you have found a bug and have a GitHub account, then create an issue directly on %{github_mampf}. Otherwise, you are welcome to use this form or send an %{feedback_mail} directly.

+ body_too_short_error: > + Your feedback is too short. Please enter at least %{min_length} characters. mail_checkbox: > Allow us to contact you via email (%{user_mail}) if there are questions about your feedback. From ace2b08d49b01a7a341a8b91609e33388404632d Mon Sep 17 00:00:00 2001 From: Splines Date: Sun, 27 Aug 2023 17:10:24 +0200 Subject: [PATCH 22/57] Default `can_contact` to false in backend --- db/schema.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/schema.rb b/db/schema.rb index 390c14812..5fa61eaac 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -192,10 +192,10 @@ create_table "feedbacks", force: :cascade do |t| t.text "title" t.text "feedback" + t.boolean "can_contact", default: false t.bigint "user_id", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.boolean "can_contact" t.index ["user_id"], name: "index_feedbacks_on_user_id" end From 5cd1af25820f0eea73a98a99073634ef77daa75a Mon Sep 17 00:00:00 2001 From: Splines Date: Sun, 27 Aug 2023 17:35:43 +0200 Subject: [PATCH 23/57] Update bootstrap to v5.3.1 command used: bundle update bootstrap bundle update bootstrap --conservative did not work, as docker containers did not start again due to dependency errors --- Gemfile.lock | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 848f9a66e..92c9c871f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,6 +1,6 @@ GIT remote: https://github.com/rails/sprockets-rails - revision: 73e7351abff3506f6dca6b2da8abedfd5c7c0d77 + revision: 065cbe83989c44019eca7161782ed4fdb6473517 branch: master specs: sprockets-rails (3.4.2) @@ -142,9 +142,9 @@ GEM bindex (0.8.1) bootsnap (1.16.0) msgpack (~> 1.2) - bootstrap (5.2.1) + bootstrap (5.3.1) autoprefixer-rails (>= 9.1.0) - popper_js (>= 2.11.6, < 3) + popper_js (>= 2.11.8, < 3) sassc-rails (>= 2.0.0) bootstrap_form (5.1.0) actionpack (>= 5.2) @@ -278,7 +278,7 @@ GEM http-accept (1.7.0) http-cookie (1.0.5) domain_name (~> 0.5) - i18n (1.12.0) + i18n (1.14.1) concurrent-ruby (~> 1.0) image_processing (1.12.2) mini_magick (>= 4.9.5, < 5) @@ -329,9 +329,9 @@ GEM listen (3.0.8) rb-fsevent (~> 0.9, >= 0.9.4) rb-inotify (~> 0.9, >= 0.9.7) - loofah (2.19.1) + loofah (2.21.3) crass (~> 1.0.2) - nokogiri (>= 1.5.9) + nokogiri (>= 1.12.0) mail (2.8.1) mini_mime (>= 0.1.1) net-imap @@ -344,7 +344,7 @@ GEM mime-types-data (3.2023.0218.1) mini_magick (4.12.0) mini_mime (1.1.2) - minitest (5.18.0) + minitest (5.19.0) msgpack (1.7.0) multi_json (1.15.0) multipart-post (2.3.0) @@ -360,7 +360,7 @@ GEM net-protocol netrc (0.11.0) nio4r (2.5.8) - nokogiri (1.14.3-x86_64-linux) + nokogiri (1.15.4-x86_64-linux) racc (~> 1.4) onebox (2.2.19) addressable (~> 2.8.0) @@ -383,7 +383,7 @@ GEM ttfunk pg (1.4.6) pgreset (0.3) - popper_js (2.11.6) + popper_js (2.11.8) pr_geohash (1.0.0) premailer (1.21.0) addressable @@ -404,8 +404,8 @@ GEM pundit (2.3.0) activesupport (>= 3.0.0) raabro (1.4.0) - racc (1.6.2) - rack (2.2.7) + racc (1.7.1) + rack (2.2.8) rack-proxy (0.7.6) rack rack-test (2.1.0) @@ -424,16 +424,18 @@ GEM activesupport (= 7.0.4.3) bundler (>= 1.15.0) railties (= 7.0.4.3) - rails-dom-testing (2.0.3) - activesupport (>= 4.2.0) + rails-dom-testing (2.2.0) + activesupport (>= 5.0.0) + minitest nokogiri (>= 1.6) rails-erd (1.7.2) activerecord (>= 4.2) activesupport (>= 4.2) choice (~> 0.2.0) ruby-graphviz (~> 1.2) - rails-html-sanitizer (1.5.0) - loofah (~> 2.19, >= 2.19.1) + rails-html-sanitizer (1.6.0) + loofah (~> 2.21) + nokogiri (~> 1.14) rails-i18n (7.0.6) i18n (>= 0.7, < 2) railties (>= 6.0.0, < 8) @@ -587,7 +589,7 @@ GEM tins (~> 1.0) terser (1.1.14) execjs (>= 0.3.0, < 3) - thor (1.2.1) + thor (1.2.2) thredded (1.1.0) active_record_union (>= 1.3.0) autoprefixer-rails @@ -609,7 +611,7 @@ GEM sassc-rails (>= 2.0.0) sprockets-es6 timeago_js (>= 3.0.2.2) - tilt (2.1.0) + tilt (2.2.0) timeago_js (3.0.2.2) timeout (0.3.2) tins (1.32.1) @@ -648,7 +650,7 @@ GEM websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) will_paginate (3.3.1) - zeitwerk (2.6.7) + zeitwerk (2.6.11) PLATFORMS x86_64-linux @@ -734,7 +736,7 @@ DEPENDENCIES sunspot_rails! sunspot_solr terser - thredded! + thredded thredded-markdown_katex! trix-rails turbolinks (~> 5) From 4ed07571445c20f66434c3275def8ee65118f3af Mon Sep 17 00:00:00 2001 From: Splines Date: Sun, 27 Aug 2023 17:49:12 +0200 Subject: [PATCH 24/57] Revert "Update bootstrap to v5.3.1" in favor of PR #537 This reverts commit 5cd1af25820f0eea73a98a99073634ef77daa75a. --- Gemfile.lock | 40 +++++++++++++++++++--------------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 92c9c871f..848f9a66e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,6 +1,6 @@ GIT remote: https://github.com/rails/sprockets-rails - revision: 065cbe83989c44019eca7161782ed4fdb6473517 + revision: 73e7351abff3506f6dca6b2da8abedfd5c7c0d77 branch: master specs: sprockets-rails (3.4.2) @@ -142,9 +142,9 @@ GEM bindex (0.8.1) bootsnap (1.16.0) msgpack (~> 1.2) - bootstrap (5.3.1) + bootstrap (5.2.1) autoprefixer-rails (>= 9.1.0) - popper_js (>= 2.11.8, < 3) + popper_js (>= 2.11.6, < 3) sassc-rails (>= 2.0.0) bootstrap_form (5.1.0) actionpack (>= 5.2) @@ -278,7 +278,7 @@ GEM http-accept (1.7.0) http-cookie (1.0.5) domain_name (~> 0.5) - i18n (1.14.1) + i18n (1.12.0) concurrent-ruby (~> 1.0) image_processing (1.12.2) mini_magick (>= 4.9.5, < 5) @@ -329,9 +329,9 @@ GEM listen (3.0.8) rb-fsevent (~> 0.9, >= 0.9.4) rb-inotify (~> 0.9, >= 0.9.7) - loofah (2.21.3) + loofah (2.19.1) crass (~> 1.0.2) - nokogiri (>= 1.12.0) + nokogiri (>= 1.5.9) mail (2.8.1) mini_mime (>= 0.1.1) net-imap @@ -344,7 +344,7 @@ GEM mime-types-data (3.2023.0218.1) mini_magick (4.12.0) mini_mime (1.1.2) - minitest (5.19.0) + minitest (5.18.0) msgpack (1.7.0) multi_json (1.15.0) multipart-post (2.3.0) @@ -360,7 +360,7 @@ GEM net-protocol netrc (0.11.0) nio4r (2.5.8) - nokogiri (1.15.4-x86_64-linux) + nokogiri (1.14.3-x86_64-linux) racc (~> 1.4) onebox (2.2.19) addressable (~> 2.8.0) @@ -383,7 +383,7 @@ GEM ttfunk pg (1.4.6) pgreset (0.3) - popper_js (2.11.8) + popper_js (2.11.6) pr_geohash (1.0.0) premailer (1.21.0) addressable @@ -404,8 +404,8 @@ GEM pundit (2.3.0) activesupport (>= 3.0.0) raabro (1.4.0) - racc (1.7.1) - rack (2.2.8) + racc (1.6.2) + rack (2.2.7) rack-proxy (0.7.6) rack rack-test (2.1.0) @@ -424,18 +424,16 @@ GEM activesupport (= 7.0.4.3) bundler (>= 1.15.0) railties (= 7.0.4.3) - rails-dom-testing (2.2.0) - activesupport (>= 5.0.0) - minitest + rails-dom-testing (2.0.3) + activesupport (>= 4.2.0) nokogiri (>= 1.6) rails-erd (1.7.2) activerecord (>= 4.2) activesupport (>= 4.2) choice (~> 0.2.0) ruby-graphviz (~> 1.2) - rails-html-sanitizer (1.6.0) - loofah (~> 2.21) - nokogiri (~> 1.14) + rails-html-sanitizer (1.5.0) + loofah (~> 2.19, >= 2.19.1) rails-i18n (7.0.6) i18n (>= 0.7, < 2) railties (>= 6.0.0, < 8) @@ -589,7 +587,7 @@ GEM tins (~> 1.0) terser (1.1.14) execjs (>= 0.3.0, < 3) - thor (1.2.2) + thor (1.2.1) thredded (1.1.0) active_record_union (>= 1.3.0) autoprefixer-rails @@ -611,7 +609,7 @@ GEM sassc-rails (>= 2.0.0) sprockets-es6 timeago_js (>= 3.0.2.2) - tilt (2.2.0) + tilt (2.1.0) timeago_js (3.0.2.2) timeout (0.3.2) tins (1.32.1) @@ -650,7 +648,7 @@ GEM websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) will_paginate (3.3.1) - zeitwerk (2.6.11) + zeitwerk (2.6.7) PLATFORMS x86_64-linux @@ -736,7 +734,7 @@ DEPENDENCIES sunspot_rails! sunspot_solr terser - thredded + thredded! thredded-markdown_katex! trix-rails turbolinks (~> 5) From b34806166e612a27b64fcbc36ece7807cd4fbffb Mon Sep 17 00:00:00 2001 From: Splines Date: Mon, 28 Aug 2023 16:38:07 +0200 Subject: [PATCH 25/57] Submit form via Ctrl + Enter when modal is opened --- app/assets/javascripts/feedback.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/feedback.js b/app/assets/javascripts/feedback.js index 3b63b194a..0a5179c1d 100644 --- a/app/assets/javascripts/feedback.js +++ b/app/assets/javascripts/feedback.js @@ -18,9 +18,19 @@ function registerToasts() { } function registerSubmitButtonHandler() { + const submitButton = $('#submit-form-btn'); + + // Invoke the hidden submit button inside the actual Rails form $('#submit-form-btn-outside').click(() => { - // Invoke the hidden submit button inside the actual Rails form - $('#submit-form-btn').click(); + submitButton.click(); + }); + + // Submit form by pressing Ctrl + Enter + document.addEventListener('keydown', (event) => { + const isModalOpen = $('#submit-feedback').is(':visible'); + if (isModalOpen && event.ctrlKey && event.key == "Enter") { + submitButton.click(); + } }); } From 696a395cce1bb24d1018e84479a85cc0a66d77ba Mon Sep 17 00:00:00 2001 From: Splines Date: Mon, 28 Aug 2023 16:40:26 +0200 Subject: [PATCH 26/57] Remove default nil value from ENV.fetch() --- config/initializers/default_setting.rb | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/config/initializers/default_setting.rb b/config/initializers/default_setting.rb index fa83c1ea7..730516485 100644 --- a/config/initializers/default_setting.rb +++ b/config/initializers/default_setting.rb @@ -1,11 +1,11 @@ class DefaultSetting - ERDBEERE_LINK = ENV.fetch('ERDBEERE_SERVER', nil) - MUESLI_LINK = ENV.fetch('MUESLI_SERVER', nil) - PROJECT_EMAIL = ENV.fetch('PROJECT_EMAIL', nil) - FEEDBACK_EMAIL = ENV.fetch('FEEDBACK_EMAIL', nil) - PROJECT_NOTIFICATION_EMAIL = ENV.fetch('PROJECT_NOTIFICATION_EMAIL', nil) - BLOG_LINK = ENV.fetch('BLOG', nil) - URL_HOST_SHORT = ENV.fetch('URL_HOST_SHORT', nil) + ERDBEERE_LINK = ENV.fetch('ERDBEERE_SERVER') + MUESLI_LINK = ENV.fetch('MUESLI_SERVER') + PROJECT_EMAIL = ENV.fetch('PROJECT_EMAIL') + FEEDBACK_EMAIL = ENV.fetch('FEEDBACK_EMAIL') + PROJECT_NOTIFICATION_EMAIL = ENV.fetch('PROJECT_NOTIFICATION_EMAIL') + BLOG_LINK = ENV.fetch('BLOG') + URL_HOST_SHORT = ENV.fetch('URL_HOST_SHORT') RESEARCHGATE_LINK = 'https://www.researchgate.net/project/MaMpf-Mathematische-Medienplattform' TOUR_LINK = 'https://mampf.blog/ueber-mampf/' RESOURCES_LINK = 'https://mampf.blog/ressourcen-fur-editorinnen/' From 1261706a10f9405b4b6524b8b46481dc3d469c9a Mon Sep 17 00:00:00 2001 From: Splines Date: Tue, 17 Oct 2023 20:53:43 +0200 Subject: [PATCH 27/57] Revert "Remove default nil value from ENV.fetch()" This reverts commit 696a395cce1bb24d1018e84479a85cc0a66d77ba. --- config/initializers/default_setting.rb | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/config/initializers/default_setting.rb b/config/initializers/default_setting.rb index 730516485..fa83c1ea7 100644 --- a/config/initializers/default_setting.rb +++ b/config/initializers/default_setting.rb @@ -1,11 +1,11 @@ class DefaultSetting - ERDBEERE_LINK = ENV.fetch('ERDBEERE_SERVER') - MUESLI_LINK = ENV.fetch('MUESLI_SERVER') - PROJECT_EMAIL = ENV.fetch('PROJECT_EMAIL') - FEEDBACK_EMAIL = ENV.fetch('FEEDBACK_EMAIL') - PROJECT_NOTIFICATION_EMAIL = ENV.fetch('PROJECT_NOTIFICATION_EMAIL') - BLOG_LINK = ENV.fetch('BLOG') - URL_HOST_SHORT = ENV.fetch('URL_HOST_SHORT') + ERDBEERE_LINK = ENV.fetch('ERDBEERE_SERVER', nil) + MUESLI_LINK = ENV.fetch('MUESLI_SERVER', nil) + PROJECT_EMAIL = ENV.fetch('PROJECT_EMAIL', nil) + FEEDBACK_EMAIL = ENV.fetch('FEEDBACK_EMAIL', nil) + PROJECT_NOTIFICATION_EMAIL = ENV.fetch('PROJECT_NOTIFICATION_EMAIL', nil) + BLOG_LINK = ENV.fetch('BLOG', nil) + URL_HOST_SHORT = ENV.fetch('URL_HOST_SHORT', nil) RESEARCHGATE_LINK = 'https://www.researchgate.net/project/MaMpf-Mathematische-Medienplattform' TOUR_LINK = 'https://mampf.blog/ueber-mampf/' RESOURCES_LINK = 'https://mampf.blog/ressourcen-fur-editorinnen/' From dab8b3bd38c032ab24757edad1260ec9c4310c7e Mon Sep 17 00:00:00 2001 From: Splines Date: Tue, 17 Oct 2023 21:01:38 +0200 Subject: [PATCH 28/57] Rename button to 'Send' (not 'Save') --- app/views/feedbacks/_feedback_form.html.erb | 2 +- config/locales/de.yml | 1 + config/locales/en.yml | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/views/feedbacks/_feedback_form.html.erb b/app/views/feedbacks/_feedback_form.html.erb index 02560e907..761f07cfb 100644 --- a/app/views/feedbacks/_feedback_form.html.erb +++ b/app/views/feedbacks/_feedback_form.html.erb @@ -57,6 +57,6 @@ <%# Submit %>
diff --git a/config/locales/de.yml b/config/locales/de.yml index 52d50231b..c17bb6ad5 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -3415,6 +3415,7 @@ de: refresh_token: 'Code erneuern' invite: 'Einladen' send: 'Versenden' + send_variant: 'Absenden' download: 'Herunterladen' move: 'Verschieben' accept: 'Akzeptieren' diff --git a/config/locales/en.yml b/config/locales/en.yml index 5f36c66de..bd6357209 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -3236,6 +3236,7 @@ en: refresh_token: 'Refresh Code' invite: 'Invite' send: 'Send' + send_variant: 'Send' download: 'Download' move: 'Move' accept: 'Accept' From 6e8c7690279f2d59fc95b333e7be47c6d336915e Mon Sep 17 00:00:00 2001 From: Splines Date: Sun, 22 Oct 2023 17:05:11 +0200 Subject: [PATCH 29/57] Check if should register feedback event handlers --- app/assets/javascripts/feedback.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/feedback.js b/app/assets/javascripts/feedback.js index 0a5179c1d..9ce685d9c 100644 --- a/app/assets/javascripts/feedback.js +++ b/app/assets/javascripts/feedback.js @@ -1,15 +1,24 @@ $(document).on('turbolinks:load', () => { + if (!shouldRegisterFeedback()) { + return; + } registerToasts(); registerSubmitButtonHandler(); registerFeedbackBodyValidator(); }); +SUBMIT_FEEDBACK_ID = '#submit-feedback'; + TOAST_OPTIONS = { animation: true, autohide: true, delay: 6000 // autohide after ... milliseconds }; +function shouldRegisterFeedback() { + return $(SUBMIT_FEEDBACK_ID).length > 0; +} + function registerToasts() { const toastElements = document.querySelectorAll('.toast'); const toastList = [...toastElements].map(toast => { @@ -27,7 +36,7 @@ function registerSubmitButtonHandler() { // Submit form by pressing Ctrl + Enter document.addEventListener('keydown', (event) => { - const isModalOpen = $('#submit-feedback').is(':visible'); + const isModalOpen = $(SUBMIT_FEEDBACK_ID).is(':visible'); if (isModalOpen && event.ctrlKey && event.key == "Enter") { submitButton.click(); } From 7f60853bb1e42253da64abe2cb1c90cf2473c435 Mon Sep 17 00:00:00 2001 From: Splines Date: Sun, 22 Oct 2023 17:08:31 +0200 Subject: [PATCH 30/57] Make feedback button ID more specific --- app/assets/javascripts/feedback.js | 4 ++-- app/views/feedbacks/_feedback_form.html.erb | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/feedback.js b/app/assets/javascripts/feedback.js index 9ce685d9c..1430e54ac 100644 --- a/app/assets/javascripts/feedback.js +++ b/app/assets/javascripts/feedback.js @@ -27,10 +27,10 @@ function registerToasts() { } function registerSubmitButtonHandler() { - const submitButton = $('#submit-form-btn'); + const submitButton = $('#submit-feedback-form-btn'); // Invoke the hidden submit button inside the actual Rails form - $('#submit-form-btn-outside').click(() => { + $('#submit-feedback-form-btn-outside').click(() => { submitButton.click(); }); diff --git a/app/views/feedbacks/_feedback_form.html.erb b/app/views/feedbacks/_feedback_form.html.erb index 761f07cfb..d7d7edf68 100644 --- a/app/views/feedbacks/_feedback_form.html.erb +++ b/app/views/feedbacks/_feedback_form.html.erb @@ -45,7 +45,7 @@ <%# Submit %> <%# Dummy submit button, the actual submit button is in the modal footer %> - <%= f.submit 'Submit', id: 'submit-form-btn', style: 'display: none;' %> + <%= f.submit 'Submit', id: 'submit-feedback-form-btn', style: 'display: none;' %> <% end %> @@ -56,7 +56,7 @@ <%# Submit %> - From 26f9ae8bbff0a68fca10bfe77f1ff43cdf5294e4 Mon Sep 17 00:00:00 2001 From: Splines Date: Sun, 22 Oct 2023 17:39:39 +0200 Subject: [PATCH 31/57] Fix line wrapping (code style) --- app/models/feedback.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/models/feedback.rb b/app/models/feedback.rb index 6c40f3981..2cc0a0c6b 100644 --- a/app/models/feedback.rb +++ b/app/models/feedback.rb @@ -4,6 +4,7 @@ class Feedback < ApplicationRecord BODY_MIN_LENGTH = 10 BODY_MAX_LENGTH = 10_000 - validates :feedback, length: { minimum: BODY_MIN_LENGTH, maximum: BODY_MAX_LENGTH }, + validates :feedback, length: { minimum: BODY_MIN_LENGTH, + maximum: BODY_MAX_LENGTH }, allow_blank: false end From 2b02a48e4e22941ed706ad3d89e4d3a4466244e3 Mon Sep 17 00:00:00 2001 From: Splines Date: Sun, 22 Oct 2023 17:40:22 +0200 Subject: [PATCH 32/57] Use delete on cascade to be able to delete a user even if he/she has sent some feedback --- db/migrate/20230529080510_create_feedbacks.rb | 4 ++-- db/schema.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/db/migrate/20230529080510_create_feedbacks.rb b/db/migrate/20230529080510_create_feedbacks.rb index 40401bdb7..47e08beff 100644 --- a/db/migrate/20230529080510_create_feedbacks.rb +++ b/db/migrate/20230529080510_create_feedbacks.rb @@ -3,8 +3,8 @@ def change create_table :feedbacks do |t| t.text :title t.text :feedback - t.boolean :can_contact, :default => false - t.references :user, null: false, foreign_key: true + t.boolean :can_contact, default: false + t.references :user, null: false, foreign_key: { on_delete: :cascade } t.timestamps end diff --git a/db/schema.rb b/db/schema.rb index 5fa61eaac..c82786ab1 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -916,7 +916,7 @@ add_foreign_key "commontator_subscriptions", "commontator_threads", column: "thread_id", on_update: :cascade, on_delete: :cascade add_foreign_key "course_self_joins", "courses" add_foreign_key "divisions", "programs" - add_foreign_key "feedbacks", "users" + add_foreign_key "feedbacks", "users", on_delete: :cascade add_foreign_key "imports", "media" add_foreign_key "items", "media" add_foreign_key "items", "sections" From 3a215fc2e5d5a6ebf013b6e298d9f24db7bbc40f Mon Sep 17 00:00:00 2001 From: Splines Date: Mon, 30 Oct 2023 17:37:38 +0100 Subject: [PATCH 33/57] Move Send button before Cancel button --- app/views/feedbacks/_feedback_form.html.erb | 22 ++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/app/views/feedbacks/_feedback_form.html.erb b/app/views/feedbacks/_feedback_form.html.erb index d7d7edf68..18694dbe4 100644 --- a/app/views/feedbacks/_feedback_form.html.erb +++ b/app/views/feedbacks/_feedback_form.html.erb @@ -13,13 +13,13 @@ feedback_mail: mail_to(DefaultSetting::FEEDBACK_EMAIL, t('basics.mail_noun'))) %> <%= form_with model: Feedback.new, remote: true do |f| %> - <%# Title %> +
<%= f.text_field :title, class: 'form-control', placeholder: '_' %> <%= f.label :title, t('feedback.title') %>
- <%# Comment %> +
<%= f.text_area :feedback, class: 'form-control', @@ -33,7 +33,7 @@ <%= f.label :feedback, t('feedback.comment') %>
- <%# Email contact %> +
<%= f.check_box :can_contact, checked: 'checked', @@ -43,20 +43,20 @@ class: 'form-check-label' %>
- <%# Submit %> - <%# Dummy submit button, the actual submit button is in the modal footer %> + + <%= f.submit 'Submit', id: 'submit-feedback-form-btn', style: 'display: none;' %> <% end %> From 07ba509b828c85524a45fdee8e4d6c68a08141eb Mon Sep 17 00:00:00 2001 From: Splines Date: Mon, 30 Oct 2023 17:49:23 +0100 Subject: [PATCH 34/57] Replace "on delete cascade" with "dependent destroy" --- app/models/user.rb | 3 +++ db/migrate/20230529080510_create_feedbacks.rb | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/models/user.rb b/app/models/user.rb index d90b05bb8..912636a33 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -61,6 +61,9 @@ class User < ApplicationRecord # a user has a watchlist with watchlist_entries has_many :watchlists, dependent: :destroy + + has_many :feedbacks, dependent: :destroy + include ScreenshotUploader[:image] # if a homepage is given it should at leat be a valid address diff --git a/db/migrate/20230529080510_create_feedbacks.rb b/db/migrate/20230529080510_create_feedbacks.rb index 47e08beff..177ff9311 100644 --- a/db/migrate/20230529080510_create_feedbacks.rb +++ b/db/migrate/20230529080510_create_feedbacks.rb @@ -4,7 +4,7 @@ def change t.text :title t.text :feedback t.boolean :can_contact, default: false - t.references :user, null: false, foreign_key: { on_delete: :cascade } + t.references :user, null: false, foreign_key: true t.timestamps end From 888470784c7c8bcb4a8176a4a85c55d98f2b7abb Mon Sep 17 00:00:00 2001 From: Splines Date: Wed, 20 Dec 2023 00:59:03 +0100 Subject: [PATCH 35/57] Add cypress rules to ESLint & ignore some patterns --- .eslintrc.js | 26 ++++++++++++++++++++++---- package.json | 4 +--- yarn.lock | 14 ++++++++++++++ 3 files changed, 37 insertions(+), 7 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 8912b06dc..313e0a66c 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -19,6 +19,15 @@ const customizedStylistic = stylistic.configs.customize({ "brace-style": "1tbs", }); +const cypressRules = { + "cypress/no-assigning-return-values": "error", + "cypress/no-unnecessary-waiting": "error", + "cypress/assertion-before-screenshot": "warn", + "cypress/no-force": "warn", + "cypress/no-async-tests": "error", + "cypress/no-pause": "error", +}; + module.exports = { root: true, parserOptions: { @@ -26,18 +35,27 @@ module.exports = { sourceType: "module", }, env: { - node: true, - browser: true, - jquery: true, + "node": true, + "browser": true, + "jquery": true, + "cypress/globals": true, }, extends: [ "eslint:recommended", // Allow linting of ERB files, see https://github.com/Splines/eslint-plugin-erb "plugin:erb/recommended", ], - plugins: ["@stylistic", "erb"], + plugins: ["@stylistic", "erb", "cypress"], rules: { ...customizedStylistic.rules, "no-unused-vars": "warn", + ...cypressRules, }, + ignorePatterns: [ + "node_modules/", + "tmp/", + "public/packs/", + "public/packs-test/", + "public/uploads/", + ], }; diff --git a/package.json b/package.json index 37d734e0d..0ffb3fcf9 100644 --- a/package.json +++ b/package.json @@ -17,12 +17,10 @@ "webpack-cli": "^4.10.0", "webpack-dev-server": "^4.15.1" }, - "scripts": { - "lint": "eslint ." - }, "devDependencies": { "@stylistic/eslint-plugin": "^1.5.0", "eslint": "^8.55.0", + "eslint-plugin-cypress": "^2.15.1", "eslint-plugin-erb": "^1.1.0" } } diff --git a/yarn.lock b/yarn.lock index 9ef709e2c..a32b4e906 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3242,6 +3242,13 @@ escape-string-regexp@^4.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== +eslint-plugin-cypress@^2.15.1: + version "2.15.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-cypress/-/eslint-plugin-cypress-2.15.1.tgz#336afa7e8e27451afaf65aa359c9509e0a4f3a7b" + integrity sha512-eLHLWP5Q+I4j2AWepYq0PgFEei9/s5LvjuSqWrxurkg1YZ8ltxdvMNmdSf0drnsNo57CTgYY/NIHHLRSWejR7w== + dependencies: + globals "^13.20.0" + eslint-plugin-erb@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/eslint-plugin-erb/-/eslint-plugin-erb-1.1.0.tgz#ac0ac8d5b5e75602c0e41016ff525748cc66d95e" @@ -3902,6 +3909,13 @@ globals@^13.19.0: dependencies: type-fest "^0.20.2" +globals@^13.20.0: + version "13.24.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171" + integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ== + dependencies: + type-fest "^0.20.2" + globalthis@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.3.tgz#5852882a52b80dc301b0660273e1ed082f0b6ccf" From d1593f13c610fc92f77002ddfc6321d1e3bad3f3 Mon Sep 17 00:00:00 2001 From: Splines Date: Wed, 20 Dec 2023 01:00:48 +0100 Subject: [PATCH 36/57] Allow usage of tempusDominus global variable --- app/assets/javascripts/datetimepicker.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/assets/javascripts/datetimepicker.js b/app/assets/javascripts/datetimepicker.js index 7730f99a1..4ffc82762 100644 --- a/app/assets/javascripts/datetimepicker.js +++ b/app/assets/javascripts/datetimepicker.js @@ -1,3 +1,5 @@ +/* global tempusDominus */ + // Initialize on page load (when js file is dynamically loaded) $(document).ready(startInitialization); From 144135e000e345d76984280055b4197367246524 Mon Sep 17 00:00:00 2001 From: Splines Date: Wed, 20 Dec 2023 01:11:01 +0100 Subject: [PATCH 37/57] Ignore JS files with Sprocket syntax --- .eslintrc.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/.eslintrc.js b/.eslintrc.js index 313e0a66c..f2bed0376 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -21,13 +21,22 @@ const customizedStylistic = stylistic.configs.customize({ const cypressRules = { "cypress/no-assigning-return-values": "error", - "cypress/no-unnecessary-waiting": "error", + "cypress/no-unnecessary-waiting": "warn", "cypress/assertion-before-screenshot": "warn", "cypress/no-force": "warn", "cypress/no-async-tests": "error", "cypress/no-pause": "error", }; +const ignoreFilesWithSprocketRequireSyntax = [ + "app/assets/javascripts/application.js", + "app/assets/config/manifest.js", + "app/assets/javascripts/thredded_timeago.js", + "app/assets/javascripts/edit_clicker_assets.js", + "app/assets/javascripts/show_clicker_assets.js", + "app/assets/javascripts/geogebra_assets.js", +]; + module.exports = { root: true, parserOptions: { @@ -53,9 +62,11 @@ module.exports = { }, ignorePatterns: [ "node_modules/", + "pdfcomprezzor/", "tmp/", "public/packs/", "public/packs-test/", "public/uploads/", + ...ignoreFilesWithSprocketRequireSyntax, ], }; From 19342f7c98bc4c3abb44d16e4843e1b40ca40de3 Mon Sep 17 00:00:00 2001 From: Splines Date: Wed, 3 Jan 2024 14:45:53 +0100 Subject: [PATCH 38/57] Further improve rules, e.g. allow common globals --- .eslintrc.js | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index f2bed0376..115d0fea5 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -12,7 +12,6 @@ const stylistic = require("@stylistic/eslint-plugin"); const customizedStylistic = stylistic.configs.customize({ "indent": 2, - "quotes": "double", "jsx": false, "quote-props": "always", "semi": "always", @@ -21,7 +20,7 @@ const customizedStylistic = stylistic.configs.customize({ const cypressRules = { "cypress/no-assigning-return-values": "error", - "cypress/no-unnecessary-waiting": "warn", + "cypress/no-unnecessary-waiting": "off", // TODO: fix this issue "cypress/assertion-before-screenshot": "warn", "cypress/no-force": "warn", "cypress/no-async-tests": "error", @@ -31,12 +30,25 @@ const cypressRules = { const ignoreFilesWithSprocketRequireSyntax = [ "app/assets/javascripts/application.js", "app/assets/config/manifest.js", - "app/assets/javascripts/thredded_timeago.js", "app/assets/javascripts/edit_clicker_assets.js", "app/assets/javascripts/show_clicker_assets.js", "app/assets/javascripts/geogebra_assets.js", + "vendor/assets/javascripts/thredded_timeago.js", ]; +const customGlobals = { + TomSelect: "readable", + bootstrap: "readable", + + // Rails globals + Routes: "readable", + App: "readable", + ActionCable: "readable", + + // Common global methods + initBootstrapPopovers: "readable", +}; + module.exports = { root: true, parserOptions: { @@ -54,11 +66,14 @@ module.exports = { // Allow linting of ERB files, see https://github.com/Splines/eslint-plugin-erb "plugin:erb/recommended", ], + globals: customGlobals, plugins: ["@stylistic", "erb", "cypress"], rules: { ...customizedStylistic.rules, "no-unused-vars": "warn", ...cypressRules, + // see https://github.com/eslint-stylistic/eslint-stylistic/issues/254 + "@stylistic/quotes": ["error", "double", { avoidEscape: true }], }, ignorePatterns: [ "node_modules/", From 70dbfed655db6da9abc563d2d9c4fc40ec6d26b5 Mon Sep 17 00:00:00 2001 From: Splines Date: Wed, 3 Jan 2024 14:50:30 +0100 Subject: [PATCH 39/57] Ignore sprocket syntax in cable.js --- app/assets/javascripts/cable.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/assets/javascripts/cable.js b/app/assets/javascripts/cable.js index 739aa5f02..9ccc7fd7b 100644 --- a/app/assets/javascripts/cable.js +++ b/app/assets/javascripts/cable.js @@ -1,9 +1,12 @@ // Action Cable provides the framework to deal with WebSockets in Rails. // You can generate new channels where WebSocket features live using the `rails generate channel` command. // +// disable eslint +/* eslint-disable */ //= require action_cable //= require_self //= require_tree ./channels +/* eslint-enable */ (function() { this.App || (this.App = {}); From 9b9c0829e63982fb13e8286f1edfe08ef0ad5947 Mon Sep 17 00:00:00 2001 From: Splines Date: Wed, 3 Jan 2024 14:51:33 +0100 Subject: [PATCH 40/57] Autofix all `.js` and `.js.erb` files Command used: `yarn run eslint --fix .` Still 47 problems (27 errors, 20 warnings) after this. --- .../javascripts/_selectize_turbolinks_fix.js | 151 ++-- .../bootstrap_modal_turbolinks_fix.js | 26 +- app/assets/javascripts/bootstrap_popovers.js | 16 +- app/assets/javascripts/cable.js | 3 +- app/assets/javascripts/datetimepicker.js | 193 ++--- app/assets/javascripts/footer_modal.js | 6 +- app/javascript/packs/application.js | 63 +- app/views/assignments/new.js.erb | 20 +- app/views/commontator/comments/cancel.js.erb | 8 +- app/views/commontator/comments/create.js.erb | 12 +- app/views/commontator/comments/edit.js.erb | 2 +- app/views/commontator/comments/new.js.erb | 6 +- app/views/commontator/comments/show.js.erb | 10 +- .../threads/_hide_show_links.js.erb | 12 +- app/views/commontator/threads/_show.js.erb | 1 - app/views/submissions/select_tutorial.js.erb | 18 +- app/views/watchlist_entries/create.js.erb | 9 +- app/views/watchlists/create.js.erb | 33 +- app/views/watchlists/update.js.erb | 11 +- babel.config.js | 82 +- config/webpack/development.js | 8 +- config/webpack/environment.js | 13 +- config/webpack/loaders/coffee.js | 6 +- config/webpack/loaders/css.js | 10 +- config/webpack/loaders/scss.js | 10 +- config/webpack/production.js | 6 +- config/webpack/test.js | 6 +- postcss.config.js | 16 +- spec/cypress.config.js | 22 +- spec/cypress/e2e/admin_spec.cy.js | 107 ++- spec/cypress/e2e/courses_spec.cy.js | 311 ++++--- spec/cypress/e2e/media_spec.cy.js | 799 +++++++++--------- spec/cypress/e2e/search_spec.cy.js | 61 +- spec/cypress/e2e/submissions_spec.cy.js | 296 ++++--- spec/cypress/e2e/thredded_spec.cy.js | 168 ++-- spec/cypress/e2e/tutorials_spec.cy.js | 613 +++++++------- spec/cypress/e2e/watchlists_spec.cy.js | 409 +++++---- spec/cypress/support/e2e.js | 4 +- spec/cypress/support/index.js | 4 +- spec/cypress/support/on-rails.js | 34 +- 40 files changed, 1792 insertions(+), 1793 deletions(-) diff --git a/app/assets/javascripts/_selectize_turbolinks_fix.js b/app/assets/javascripts/_selectize_turbolinks_fix.js index eaef1d817..9e5b9b4e8 100644 --- a/app/assets/javascripts/_selectize_turbolinks_fix.js +++ b/app/assets/javascripts/_selectize_turbolinks_fix.js @@ -5,130 +5,137 @@ // transfer knowledge about selected items from selectize to html options var resetSelectized; -resetSelectized = function(index, select) { +resetSelectized = function (index, select) { var i, len, selectedValue, val; selectedValue = select.tomselect.getValue(); select.tomselect.destroy(); - $(select).find('option').attr('selected', null); - if ($(select).prop('multiple')) { + $(select).find("option").attr("selected", null); + if ($(select).prop("multiple")) { for (i = 0, len = selectedValue.length; i < len; i++) { val = selectedValue[i]; - if (val !== '') { - $(select).find("option[value='" + val + "']").attr('selected', true); + if (val !== "") { + $(select).find("option[value='" + val + "']").attr("selected", true); } } - } else { - if (selectedValue !== '') { - $(select).find("option[value='" + selectedValue + "']").attr('selected', true); + } + else { + if (selectedValue !== "") { + $(select).find("option[value='" + selectedValue + "']").attr("selected", true); } } }; -this.fillOptionsByAjax = function($selectizedSelection) { - $selectizedSelection.each(function() { +this.fillOptionsByAjax = function ($selectizedSelection) { + $selectizedSelection.each(function () { var courseId, existing_values, fill_path, loaded, locale, model_select, plugins, send_data, parent; - if (this.dataset.drag === 'true') { - plugins = ['remove_button', 'drag_drop']; - } else { - plugins = ['remove_button']; + if (this.dataset.drag === "true") { + plugins = ["remove_button", "drag_drop"]; + } + else { + plugins = ["remove_button"]; } - if (this.dataset.ajax === 'true' && this.dataset.filled === 'false') { + if (this.dataset.ajax === "true" && this.dataset.filled === "false") { model_select = this; courseId = 0; placeholder = this.dataset.placeholder; no_result_msg = this.dataset.noResults; - existing_values = Array.apply(null, model_select.options).map(function(o) { + existing_values = Array.apply(null, model_select.options).map(function (o) { return o.value; }); send_data = false; loaded = false; parent = this.dataset.modal === undefined ? document.body : null; - if (this.dataset.model === 'tag') { + if (this.dataset.model === "tag") { locale = this.dataset.locale; fill_path = Routes.fill_tag_select_path({ - locale: locale + locale: locale, }); send_data = true; - } else if (this.dataset.model === 'user') { + } + else if (this.dataset.model === "user") { fill_path = Routes.fill_user_select_path(); send_data = true; - } else if (this.dataset.model === 'user_generic') { + } + else if (this.dataset.model === "user_generic") { fill_path = Routes.list_generic_users_path(); - } else if (this.dataset.model === 'teachable') { + } + else if (this.dataset.model === "teachable") { fill_path = Routes.fill_teachable_select_path(); - } else if (this.dataset.model === 'medium') { + } + else if (this.dataset.model === "medium") { fill_path = Routes.fill_media_select_path(); - } else if (this.dataset.model === 'course_tag') { + } + else if (this.dataset.model === "course_tag") { courseId = this.dataset.course; fill_path = Routes.fill_course_tags_path(); } - (function() { - class MinimumLengthSelect extends TomSelect{ - - refreshOptions(triggerDropdown=true){ + (function () { + class MinimumLengthSelect extends TomSelect { + refreshOptions(triggerDropdown = true) { var query = this.inputValue(); - if( query.length < 2){ + if (query.length < 2) { this.close(false); return; } super.refreshOptions(triggerDropdown); } - } new MinimumLengthSelect("#" + model_select.id, { - plugins: plugins, - valueField: 'value', - labelField: 'name', - searchField: 'name', - maxOptions: null, - placeholder: placeholder, - closeAfterSelect: true, - load: function(query, callback) { - var url; - if (send_data || !loaded) { - url = fill_path + "?course_id=" + courseId + "&q=" + encodeURIComponent(query); - fetch(url).then(function(response) { - return response.json(); - }).then(function(json) { - loaded = true; - return callback(json.map(function(item) { - return { - name: item.text, - value: item.value - }; - })); - })["catch"](function() { - callback(); - }); - } - callback(); - }, - render: { - option: function(data, escape) { - return '
' + '' + escape(data.name) + '' + '
'; + plugins: plugins, + valueField: "value", + labelField: "name", + searchField: "name", + maxOptions: null, + placeholder: placeholder, + closeAfterSelect: true, + load: function (query, callback) { + var url; + if (send_data || !loaded) { + url = fill_path + "?course_id=" + courseId + "&q=" + encodeURIComponent(query); + fetch(url).then(function (response) { + return response.json(); + }).then(function (json) { + loaded = true; + return callback(json.map(function (item) { + return { + name: item.text, + value: item.value, + }; + })); + })["catch"](function () { + callback(); + }); + } + callback(); }, - item: function(item, escape) { - return '
' + escape(item.name) + '
'; + render: { + option: function (data, escape) { + return "
" + '' + escape(data.name) + "" + "
"; + }, + item: function (item, escape) { + return '
' + escape(item.name) + "
"; + }, + no_results: function (data, escape) { + return '
' + escape(no_result_msg) + "
"; + }, }, - no_results: function(data, escape) { - return '
'+ escape(no_result_msg) + '
'; - } - } - }); - })();} else { + }); + })(); + } + else { return new TomSelect("#" + this.id, { plugins: plugins, - maxOptions: null + maxOptions: null, }); } }); }; -$(document).on('turbolinks:before-cache', function() { - $('.tomselected').each(resetSelectized); +$(document).on("turbolinks:before-cache", function () { + $(".tomselected").each(resetSelectized); }); -$(document).on('turbolinks:load', function() { - fillOptionsByAjax($('.selectize')); +$(document).on("turbolinks:load", function () { + fillOptionsByAjax($(".selectize")); }); diff --git a/app/assets/javascripts/bootstrap_modal_turbolinks_fix.js b/app/assets/javascripts/bootstrap_modal_turbolinks_fix.js index d050707d7..15f5d432d 100644 --- a/app/assets/javascripts/bootstrap_modal_turbolinks_fix.js +++ b/app/assets/javascripts/bootstrap_modal_turbolinks_fix.js @@ -1,16 +1,16 @@ -$(document).on('turbolinks:load', function () { - // show all active modals - $('.activeModal').modal('show'); - // remove active status (this needs to be reestablished before caching) - $('.activeModal').removeClass('activeModal'); +$(document).on("turbolinks:load", function () { + // show all active modals + $(".activeModal").modal("show"); + // remove active status (this needs to be reestablished before caching) + $(".activeModal").removeClass("activeModal"); }); -$(document).on('turbolinks:before-cache', function () { - // if some modal is open - if ($('body').hasClass('modal-open')) { - $('.modal.show').addClass('activeModal'); - $('.modal.show').modal('hide'); - // remove the greyed out background - $('.modal-backdrop').remove(); - } +$(document).on("turbolinks:before-cache", function () { + // if some modal is open + if ($("body").hasClass("modal-open")) { + $(".modal.show").addClass("activeModal"); + $(".modal.show").modal("hide"); + // remove the greyed out background + $(".modal-backdrop").remove(); + } }); diff --git a/app/assets/javascripts/bootstrap_popovers.js b/app/assets/javascripts/bootstrap_popovers.js index 8bc3d7cd9..16cfe7d5c 100644 --- a/app/assets/javascripts/bootstrap_popovers.js +++ b/app/assets/javascripts/bootstrap_popovers.js @@ -1,18 +1,18 @@ -$(document).on('turbolinks:load', function () { - initBootstrapPopovers(); +$(document).on("turbolinks:load", function () { + initBootstrapPopovers(); }); /** * Initializes all Bootstrap popovers on the page. - * + * * This function might be used for the first initialization of popovers as well * as for reinitialization on page changes. * * See: https://getbootstrap.com/docs/5.3/components/popovers/#enable-popovers */ function initBootstrapPopovers() { - const popoverHtmlElements = document.querySelectorAll('[data-bs-toggle="popover"]'); - for (const element of popoverHtmlElements) { - new bootstrap.Popover(element); - } -} \ No newline at end of file + const popoverHtmlElements = document.querySelectorAll('[data-bs-toggle="popover"]'); + for (const element of popoverHtmlElements) { + new bootstrap.Popover(element); + } +} diff --git a/app/assets/javascripts/cable.js b/app/assets/javascripts/cable.js index 9ccc7fd7b..170e304a6 100644 --- a/app/assets/javascripts/cable.js +++ b/app/assets/javascripts/cable.js @@ -8,9 +8,8 @@ //= require_tree ./channels /* eslint-enable */ -(function() { +(function () { this.App || (this.App = {}); App.cable = ActionCable.createConsumer(); - }).call(this); diff --git a/app/assets/javascripts/datetimepicker.js b/app/assets/javascripts/datetimepicker.js index 4ffc82762..893da4156 100644 --- a/app/assets/javascripts/datetimepicker.js +++ b/app/assets/javascripts/datetimepicker.js @@ -4,125 +4,126 @@ $(document).ready(startInitialization); // On page change (e.g. go back and forth in browser) -$(document).on('turbolinks:before-cache', () => { - // Remove stale datetimepickers - $('.tempus-dominus-widget').remove(); +$(document).on("turbolinks:before-cache", () => { + // Remove stale datetimepickers + $(".tempus-dominus-widget").remove(); }); function startInitialization() { - const pickerElements = $('.td-picker'); - if (pickerElements.length == 0) { - console.error('No datetimepicker element found on page, although requested.'); - return; - } + const pickerElements = $(".td-picker"); + if (pickerElements.length == 0) { + console.error("No datetimepicker element found on page, although requested."); + return; + } - pickerElements.each((i, element) => { - element = $(element); - const datetimePicker = initDatetimePicker(element); - registerErrorHandlers(datetimePicker, element); - registerFocusHandlers(datetimePicker, element); - }); + pickerElements.each((i, element) => { + element = $(element); + const datetimePicker = initDatetimePicker(element); + registerErrorHandlers(datetimePicker, element); + registerFocusHandlers(datetimePicker, element); + }); } function getDateTimePickerIcons() { - // At the moment: continue to use FontAwesome 5 icons - // see https://getdatepicker.com/6/plugins/fa5.html - // see https://github.com/Eonasdan/tempus-dominus/blob/master/dist/plugins/fa-five.js - return { - type: 'icons', - time: 'fas fa-clock', - date: 'fas fa-calendar', - up: 'fas fa-arrow-up', - down: 'fas fa-arrow-down', - previous: 'fas fa-chevron-left', - next: 'fas fa-chevron-right', - today: 'fas fa-calendar-check', - clear: 'fas fa-trash', - close: 'fas fa-times', - } + // At the moment: continue to use FontAwesome 5 icons + // see https://getdatepicker.com/6/plugins/fa5.html + // see https://github.com/Eonasdan/tempus-dominus/blob/master/dist/plugins/fa-five.js + return { + type: "icons", + time: "fas fa-clock", + date: "fas fa-calendar", + up: "fas fa-arrow-up", + down: "fas fa-arrow-down", + previous: "fas fa-chevron-left", + next: "fas fa-chevron-right", + today: "fas fa-calendar-check", + clear: "fas fa-trash", + close: "fas fa-times", + }; } function initDatetimePicker(element) { - // see https://getdatepicker.com - return new tempusDominus.TempusDominus( - element.get(0), - { - display: { - sideBySide: true, // clock to the right of the calendar - icons: getDateTimePickerIcons(), - }, - localization: { - startOfTheWeek: 1, - // choose format to be compliant with backend time format - format: 'yyyy-MM-dd HH:mm', - hourCycle: 'h23', - } - } - ); + // see https://getdatepicker.com + return new tempusDominus.TempusDominus( + element.get(0), + { + display: { + sideBySide: true, // clock to the right of the calendar + icons: getDateTimePickerIcons(), + }, + localization: { + startOfTheWeek: 1, + // choose format to be compliant with backend time format + format: "yyyy-MM-dd HH:mm", + hourCycle: "h23", + }, + }, + ); } function registerErrorHandlers(datetimePicker, element) { - // Catch Tempus Dominus error when user types in invalid date - // this is rather hacky at the moment, see this discussion: - // https://github.com/Eonasdan/tempus-dominus/discussions/2656 - datetimePicker.dates.oldParseInput = datetimePicker.dates.parseInput; - datetimePicker.dates.parseInput = (input) => { - try { - return datetimePicker.dates.oldParseInput(input); - } catch (err) { - const errorMsg = element.find('.td-error').data('td-invalid-date'); - element.find('.td-error').text(errorMsg).show(); - datetimePicker.dates.clear(); - } - }; + // Catch Tempus Dominus error when user types in invalid date + // this is rather hacky at the moment, see this discussion: + // https://github.com/Eonasdan/tempus-dominus/discussions/2656 + datetimePicker.dates.oldParseInput = datetimePicker.dates.parseInput; + datetimePicker.dates.parseInput = (input) => { + try { + return datetimePicker.dates.oldParseInput(input); + } + catch (err) { + const errorMsg = element.find(".td-error").data("td-invalid-date"); + element.find(".td-error").text(errorMsg).show(); + datetimePicker.dates.clear(); + } + }; - datetimePicker.subscribe(tempusDominus.Namespace.events.change, (e) => { - // see https://getdatepicker.com/6/namespace/events.html#change + datetimePicker.subscribe(tempusDominus.Namespace.events.change, (e) => { + // see https://getdatepicker.com/6/namespace/events.html#change - // Clear error message - if (e.isValid && !e.isClear) { - element.find('.td-error').empty(); - } + // Clear error message + if (e.isValid && !e.isClear) { + element.find(".td-error").empty(); + } - // If date was selected, close datetimepicker. - // However: leave the datetimepicker open if user only changed time - if (e.oldDate && e.date && !hasUserChangedDate(e.oldDate, e.date)) { - datetimePicker.hide(); - } - }); + // If date was selected, close datetimepicker. + // However: leave the datetimepicker open if user only changed time + if (e.oldDate && e.date && !hasUserChangedDate(e.oldDate, e.date)) { + datetimePicker.hide(); + } + }); } function hasUserChangedDate(oldDate, newDate) { - return oldDate.getHours() != newDate.getHours() - || oldDate.getMinutes() != newDate.getMinutes(); + return oldDate.getHours() != newDate.getHours() + || oldDate.getMinutes() != newDate.getMinutes(); } function registerFocusHandlers(datetimePicker, element) { - // Show datetimepicker when user clicks in text field next to button - // or when input field receives focus - var isButtonInvokingFocus = false; + // Show datetimepicker when user clicks in text field next to button + // or when input field receives focus + var isButtonInvokingFocus = false; - element.find('.td-input').on('click focusin', (e) => { - try { - if (!isButtonInvokingFocus) { - datetimePicker.show(); - } - } - finally { - isButtonInvokingFocus = false; - } - }); + element.find(".td-input").on("click focusin", (e) => { + try { + if (!isButtonInvokingFocus) { + datetimePicker.show(); + } + } + finally { + isButtonInvokingFocus = false; + } + }); - element.find('.td-picker-button').on('click', () => { - isButtonInvokingFocus = true; - element.find('.td-input').focus(); - }); + element.find(".td-picker-button").on("click", () => { + isButtonInvokingFocus = true; + element.find(".td-input").focus(); + }); - // Hide datetimepicker when input field loses focus - element.find('.td-input').blur((e) => { - if (!e.relatedTarget) { - return; - } - datetimePicker.hide(); - }); + // Hide datetimepicker when input field loses focus + element.find(".td-input").blur((e) => { + if (!e.relatedTarget) { + return; + } + datetimePicker.hide(); + }); } diff --git a/app/assets/javascripts/footer_modal.js b/app/assets/javascripts/footer_modal.js index 28d02804f..523aa3785 100644 --- a/app/assets/javascripts/footer_modal.js +++ b/app/assets/javascripts/footer_modal.js @@ -1,5 +1,5 @@ document.addEventListener("turbolinks:load", () => { - if (window.location.hash == "#sponsors") { - $('#sponsors').modal('show'); - } + if (window.location.hash == "#sponsors") { + $("#sponsors").modal("show"); + } }); diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js index 5c9da133f..9eeaf5b3a 100644 --- a/app/javascript/packs/application.js +++ b/app/javascript/packs/application.js @@ -7,7 +7,6 @@ // To reference this file, add <%= javascript_pack_tag 'application' %> to the appropriate // layout file, like app/views/layouts/application.html.erb - // Uncomment to copy all static images under ../images to the output folder and reference // them with the image_pack_tag helper in views (e.g <%= image_pack_tag 'rails.png' %>) // or the `imagePath` JavaScript helper below. @@ -16,40 +15,40 @@ // const imagePath = (name) => images(name, true) import { - WidgetInstance + WidgetInstance, } from "friendly-challenge"; -var friendlyChallengeWidgetInstance = WidgetInstance +var friendlyChallengeWidgetInstance = WidgetInstance; document.addEventListener("turbolinks:load", function () { - var doneCallback, element, options, widget; + var doneCallback, element, options, widget; - doneCallback = function (solution) { - console.log(solution); - document.querySelector("#register-user").disabled = false; + doneCallback = function (solution) { + console.log(solution); + document.querySelector("#register-user").disabled = false; + }; + const errorCallback = (err) => { + console.log("There was an error when trying to solve the Captcha."); + console.log(err); + }; + element = document.querySelector("#captcha-widget"); + if (element != null) { + options = { + doneCallback: doneCallback, + errorCallback, + puzzleEndpoint: $("#captcha-widget").data("captcha-url"), + startMode: "auto", + language: $("#captcha-widget").data("lang"), }; - const errorCallback = (err) => { - console.log('There was an error when trying to solve the Captcha.'); - console.log(err); - } - element = document.querySelector('#captcha-widget'); - if (element != null) { - options = { - doneCallback: doneCallback, - errorCallback, - puzzleEndpoint: $('#captcha-widget').data("captcha-url"), - startMode: "auto", - language: $('#captcha-widget').data("lang") - }; - console.log(options) - widget = new WidgetInstance(element, options); - //DO not uncomment, evil - // widget.reset(); - } + console.log(options); + widget = new WidgetInstance(element, options); + // DO not uncomment, evil + // widget.reset(); + } - // Init Masonry grid system - // see https://getbootstrap.com/docs/5.0/examples/masonry/ - // and official documentation: https://masonry.desandro.com/ - $('.masonry-grid').masonry({ - percentPosition: true - }); -}) \ No newline at end of file + // Init Masonry grid system + // see https://getbootstrap.com/docs/5.0/examples/masonry/ + // and official documentation: https://masonry.desandro.com/ + $(".masonry-grid").masonry({ + percentPosition: true, + }); +}); diff --git a/app/views/assignments/new.js.erb b/app/views/assignments/new.js.erb index c7872b491..a0daee451 100644 --- a/app/views/assignments/new.js.erb +++ b/app/views/assignments/new.js.erb @@ -1,18 +1,18 @@ -$('#newAssignmentButton').hide(); +$("#newAssignmentButton").hide(); -$('#assignmentListHeader').show() - .after('<%= j render partial: "assignments/form", locals: { assignment: @assignment } %>'); +$("#assignmentListHeader").show() + .after("<%= j render partial: "assignments/form", locals: { assignment: @assignment } %>"); -new TomSelect('#assignment_medium_id_', { +new TomSelect("#assignment_medium_id_", { sortField: { - field: 'text', - direction: 'asc' + field: "text", + direction: "asc", }, render: { - no_results: function(data, escape) { + no_results: function (data, escape) { return '
<%= t("basics.no_results") %>
'; - } - } + }, + }, }); -$('#assignment_medium_id_').val(null).trigger('change'); +$("#assignment_medium_id_").val(null).trigger("change"); diff --git a/app/views/commontator/comments/cancel.js.erb b/app/views/commontator/comments/cancel.js.erb index d9f34dcc6..71688f1d6 100644 --- a/app/views/commontator/comments/cancel.js.erb +++ b/app/views/commontator/comments/cancel.js.erb @@ -1,15 +1,15 @@ <% if @comment.nil? || @comment.new_record? %> - <% +<% id = @comment.nil? || @comment.parent.nil? ? "commontator-thread-#{@commontator_thread.id}-new-comment" : "commontator-comment-#{@comment.parent.id}-reply" %> - $("#<%= id %>").hide(); +$("#<%= id %>").hide(); - $("#<%= id %>-link").fadeIn(); +$("#<%= id %>-link").fadeIn(); <% else %> - $("#commontator-comment-<%= @comment.id %>-body").html("<%= escape_javascript( +$("#commontator-comment-<%= @comment.id %>-body").html("<%= escape_javascript( render partial: 'body', locals: { comment: @comment } ) %>"); <% end %> diff --git a/app/views/commontator/comments/create.js.erb b/app/views/commontator/comments/create.js.erb index 4754a6fef..3d9cbb2e1 100644 --- a/app/views/commontator/comments/create.js.erb +++ b/app/views/commontator/comments/create.js.erb @@ -18,11 +18,11 @@ %> <% if @commontator_new_comment.nil? %> - $("#<%= id %>").hide(); +$("#<%= id %>").hide(); - $("#<%= id %>-link").fadeIn(); +$("#<%= id %>-link").fadeIn(); <% else %> - $("#<%= id %>").html("<%= escape_javascript( +$("#<%= id %>").html("<%= escape_javascript( render partial: 'form', locals: { comment: @commontator_new_comment, thread: @commontator_thread } @@ -30,12 +30,12 @@ <% end %> <% if @update_icon %> -$('#commentsIcon').addClass('new-comment'); +$("#commentsIcon").addClass("new-comment"); <% end %> var commontatorComment = $("#commontator-comment-<%= @comment.id %>").hide().fadeIn(); -$('html, body').animate( - { scrollTop: commontatorComment.offset().top - window.innerHeight/2 }, 'fast' +$("html, body").animate( + { scrollTop: commontatorComment.offset().top - window.innerHeight / 2 }, "fast", ); <%= javascript_proc %> diff --git a/app/views/commontator/comments/edit.js.erb b/app/views/commontator/comments/edit.js.erb index 4bb3f28e0..0714bd3e3 100644 --- a/app/views/commontator/comments/edit.js.erb +++ b/app/views/commontator/comments/edit.js.erb @@ -2,6 +2,6 @@ $("#commontator-comment-<%= @comment.id %>-body").html("<%= escape_javascript( render partial: 'form', locals: { comment: @comment } ) %>"); -$('#commontator-comment-<%= @comment.id %>-edit-body').focus(); +$("#commontator-comment-<%= @comment.id %>-edit-body").focus(); <%= javascript_proc %> diff --git a/app/views/commontator/comments/new.js.erb b/app/views/commontator/comments/new.js.erb index 0ec489036..38ad2afbe 100644 --- a/app/views/commontator/comments/new.js.erb +++ b/app/views/commontator/comments/new.js.erb @@ -6,11 +6,11 @@ var commontatorForm = $("#<%= id %>").html("<%= escape_javascript( render partial: 'form', locals: { comment: @comment, thread: @commontator_thread } ) %>").hide().fadeIn(); -$('html, body').animate({ scrollTop: commontatorForm.offset().top - window.innerHeight/2 }, 'fast'); +$("html, body").animate({ scrollTop: commontatorForm.offset().top - window.innerHeight / 2 }, "fast"); -initBootstrapPopovers() +initBootstrapPopovers(); $("#<%= id %>-link").hide(); -$('#<%= id %>-body').focus(); +$("#<%= id %>-body").focus(); <%= javascript_proc %> diff --git a/app/views/commontator/comments/show.js.erb b/app/views/commontator/comments/show.js.erb index 0274f80c9..ca0dc8332 100644 --- a/app/views/commontator/comments/show.js.erb +++ b/app/views/commontator/comments/show.js.erb @@ -1,8 +1,8 @@ var commontatorOldCommentIds = $("#commontator-comment-<%= @comment.id -%>-children").children().map(function() { - return '#' + $(this).attr('id'); -}).toArray().join(','); +%>-children").children().map(function () { + return "#" + $(this).attr("id"); +}).toArray().join(","); <%= render partial: 'show', locals: { @@ -17,8 +17,8 @@ var commontatorOldCommentIds = $("#commontator-comment-<%= var commontatorNewComments = $("#commontator-comment-<%= @comment.id %>-children").children().not(commontatorOldCommentIds).hide().fadeIn(); -$('html, body').animate( - { scrollTop: commontatorNewComments.offset().top - window.innerHeight/2 }, 'fast' +$("html, body").animate( + { scrollTop: commontatorNewComments.offset().top - window.innerHeight / 2 }, "fast", ); <%= javascript_proc %> diff --git a/app/views/commontator/threads/_hide_show_links.js.erb b/app/views/commontator/threads/_hide_show_links.js.erb index 2d5dd862d..8b8f405d4 100644 --- a/app/views/commontator/threads/_hide_show_links.js.erb +++ b/app/views/commontator/threads/_hide_show_links.js.erb @@ -3,19 +3,19 @@ thread %> -$("#commontator-thread-<%= thread.id %>-hide-link").click(function() { +$("#commontator-thread-<%= thread.id %>-hide-link").click(function () { $("#commontator-thread-<%= thread.id %>-content").hide(); var commontatorLink = $("#commontator-thread-<%= thread.id %>-show").fadeIn(); - $('html, body').animate( - { scrollTop: commontatorLink.offset().top - window.innerHeight/2 }, 'fast' + $("html, body").animate( + { scrollTop: commontatorLink.offset().top - window.innerHeight / 2 }, "fast", ); }); -$("#commontator-thread-<%= thread.id %>-show-link").click(function() { +$("#commontator-thread-<%= thread.id %>-show-link").click(function () { var commontatorThread = $("#commontator-thread-<%= thread.id %>-content").fadeIn(); - $('html, body').animate( - { scrollTop: commontatorThread.offset().top - window.innerHeight/2 }, 'fast' + $("html, body").animate( + { scrollTop: commontatorThread.offset().top - window.innerHeight / 2 }, "fast", ); $("#commontator-thread-<%= thread.id %>-show").hide(); diff --git a/app/views/commontator/threads/_show.js.erb b/app/views/commontator/threads/_show.js.erb index 8c94ea3d7..777c19d0c 100644 --- a/app/views/commontator/threads/_show.js.erb +++ b/app/views/commontator/threads/_show.js.erb @@ -6,7 +6,6 @@ show_all %> - $("#commontator-thread-<%= thread.id %>").html("<%= escape_javascript( render partial: 'commontator/threads/show', formats: [ :html ], locals: { user: user, thread: thread, page: page, show_all: show_all diff --git a/app/views/submissions/select_tutorial.js.erb b/app/views/submissions/select_tutorial.js.erb index c4ddc36c8..0d17f9b67 100644 --- a/app/views/submissions/select_tutorial.js.erb +++ b/app/views/submissions/select_tutorial.js.erb @@ -1,17 +1,17 @@ $('.submission-actions[data-id="<%= @submission.id %>"]').empty() - .append('<%= j render partial: "submissions/select_tutorial", + .append("<%= j render partial: "submissions/select_tutorial", locals: { submission: @submission, lecture: @lecture, - tutorial: @tutorial } %>'); + tutorial: @tutorial } %>"); -new TomSelect('#submission_tutorial_id-<%= @submission.id %>', { +new TomSelect("#submission_tutorial_id-<%= @submission.id %>", { sortField: { - field: 'text', - direction: 'asc' + field: "text", + direction: "asc", }, render: { - no_results: function(data, escape) { + no_results: function (data, escape) { return '
<%= t("basics.no_results") %>
'; - } - } -}); \ No newline at end of file + }, + }, +}); diff --git a/app/views/watchlist_entries/create.js.erb b/app/views/watchlist_entries/create.js.erb index e3cdb6d1f..49d835f99 100644 --- a/app/views/watchlist_entries/create.js.erb +++ b/app/views/watchlist_entries/create.js.erb @@ -1,5 +1,6 @@ -if(<%= @success%>) { +if (<%= @success%>) { location.reload(true); -} else { - $('#watchlistSelectForm').html("<%= j render partial: 'watchlists/select_form', locals: { watchlist_entry: @watchlist_entry, watchlist: @watchlist, medium: @medium } %>") -} \ No newline at end of file +} +else { + $("#watchlistSelectForm").html("<%= j render partial: 'watchlists/select_form', locals: { watchlist_entry: @watchlist_entry, watchlist: @watchlist, medium: @medium } %>"); +} diff --git a/app/views/watchlists/create.js.erb b/app/views/watchlists/create.js.erb index 98d45f075..c50cd99c8 100644 --- a/app/views/watchlists/create.js.erb +++ b/app/views/watchlists/create.js.erb @@ -1,27 +1,28 @@ -if(<%= @success %>) { - if(<%= @medium.present? %>) { +if (<%= @success %>) { + if (<%= @medium.present? %>) { + $("#collapseNewWatchlist").collapse("hide"); - $('#collapseNewWatchlist').collapse('hide'); - - $('#watchlist-select-form').html("<%= j render partial: 'watchlists/select_form', + $("#watchlist-select-form").html("<%= j render partial: 'watchlists/select_form', locals: { watchlist_entry: @watchlist_entry, watchlist: @watchlist, medium: @medium } %>"); - $('#collapseNewWatchlist').html("<%= j render partial: 'watchlists/new_form', + $("#collapseNewWatchlist").html("<%= j render partial: 'watchlists/new_form', locals: { watchlist_entry: @watchlist_entry, watchlist: @watchlist, medium: @medium } %>"); - - $('#watchlistEntrySubmitButton').attr("disabled", false); - $('#watchlistEntrySubmitButton').removeAttr("data-confirm"); - $('#watchlistSelectForm').append("<%= t('watchlist.creation_success')%>"); - setTimeout(function() { - $('#watchlistModalBody').find('.bg-success').remove(); + $("#watchlistEntrySubmitButton").attr("disabled", false); + $("#watchlistEntrySubmitButton").removeAttr("data-confirm"); + + $("#watchlistSelectForm").append("<%= t('watchlist.creation_success')%>"); + setTimeout(function () { + $("#watchlistModalBody").find(".bg-success").remove(); }, 2000); - } else { + } + else { location.reload(true); } -} else { - $('#newWatchlistForm').html("<%= j render partial: 'watchlists/new_form', locals: { watchlist_entry: @watchlist_entry, watchlist: @watchlist, medium: @medium } %>") -} \ No newline at end of file +} +else { + $("#newWatchlistForm").html("<%= j render partial: 'watchlists/new_form', locals: { watchlist_entry: @watchlist_entry, watchlist: @watchlist, medium: @medium } %>"); +} diff --git a/app/views/watchlists/update.js.erb b/app/views/watchlists/update.js.erb index ea7390b1d..c97268851 100644 --- a/app/views/watchlists/update.js.erb +++ b/app/views/watchlists/update.js.erb @@ -1,5 +1,6 @@ -if(<%= @success %>) { - location.reload(true) -} else { - $('#changeWatchlistForm').html("<%= j render partial: 'watchlists/change_form', locals: { watchlist: @watchlist } %>") -} \ No newline at end of file +if (<%= @success %>) { + location.reload(true); +} +else { + $("#changeWatchlistForm").html("<%= j render partial: 'watchlists/change_form', locals: { watchlist: @watchlist } %>"); +} diff --git a/babel.config.js b/babel.config.js index 4df194934..fc95d2831 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,70 +1,70 @@ -module.exports = function(api) { - var validEnv = ['development', 'test', 'production'] - var currentEnv = api.env() - var isDevelopmentEnv = api.env('development') - var isProductionEnv = api.env('production') - var isTestEnv = api.env('test') +module.exports = function (api) { + var validEnv = ["development", "test", "production"]; + var currentEnv = api.env(); + var isDevelopmentEnv = api.env("development"); + var isProductionEnv = api.env("production"); + var isTestEnv = api.env("test"); if (!validEnv.includes(currentEnv)) { throw new Error( - 'Please specify a valid `NODE_ENV` or ' + - '`BABEL_ENV` environment variables. Valid values are "development", ' + - '"test", and "production". Instead, received: ' + - JSON.stringify(currentEnv) + - '.' - ) + "Please specify a valid `NODE_ENV` or " + + '`BABEL_ENV` environment variables. Valid values are "development", ' + + '"test", and "production". Instead, received: ' + + JSON.stringify(currentEnv) + + ".", + ); } return { presets: [ isTestEnv && [ - '@babel/preset-env', + "@babel/preset-env", { targets: { - node: 'current' - } - } + node: "current", + }, + }, ], (isProductionEnv || isDevelopmentEnv) && [ - '@babel/preset-env', + "@babel/preset-env", { forceAllTransforms: true, - useBuiltIns: 'entry', + useBuiltIns: "entry", corejs: 3, modules: false, - exclude: ['transform-typeof-symbol'] - } - ] + exclude: ["transform-typeof-symbol"], + }, + ], ].filter(Boolean), plugins: [ - 'babel-plugin-macros', - '@babel/plugin-syntax-dynamic-import', - isTestEnv && 'babel-plugin-dynamic-import-node', - '@babel/plugin-transform-destructuring', + "babel-plugin-macros", + "@babel/plugin-syntax-dynamic-import", + isTestEnv && "babel-plugin-dynamic-import-node", + "@babel/plugin-transform-destructuring", [ - '@babel/plugin-proposal-class-properties', + "@babel/plugin-proposal-class-properties", { - loose: true - } + loose: true, + }, ], [ - '@babel/plugin-proposal-object-rest-spread', + "@babel/plugin-proposal-object-rest-spread", { - useBuiltIns: true - } + useBuiltIns: true, + }, ], [ - '@babel/plugin-transform-runtime', + "@babel/plugin-transform-runtime", { - helpers: false - } + helpers: false, + }, ], [ - '@babel/plugin-transform-regenerator', + "@babel/plugin-transform-regenerator", { - async: false - } - ] - ].filter(Boolean) - } -} + async: false, + }, + ], + ].filter(Boolean), + }; +}; diff --git a/config/webpack/development.js b/config/webpack/development.js index e097071fb..0a408cc8f 100644 --- a/config/webpack/development.js +++ b/config/webpack/development.js @@ -1,6 +1,6 @@ -process.env.NODE_ENV = process.env.NODE_ENV || 'development' +process.env.NODE_ENV = process.env.NODE_ENV || "development"; -const environment = require('./environment') +const environment = require("./environment"); const config = environment.toWebpackConfig(); -config.output.filename = "js/[name]-[hash].js" -module.exports = config +config.output.filename = "js/[name]-[hash].js"; +module.exports = config; diff --git a/config/webpack/environment.js b/config/webpack/environment.js index 6289f57d7..8e88f17c1 100644 --- a/config/webpack/environment.js +++ b/config/webpack/environment.js @@ -1,10 +1,9 @@ -const { environment } = require('@rails/webpacker') -const coffee = require('./loaders/coffee') -const css = require('./loaders/css') +const { environment } = require("@rails/webpacker"); +const coffee = require("./loaders/coffee"); +const css = require("./loaders/css"); +environment.loaders.prepend("coffee", coffee); -environment.loaders.prepend('coffee', coffee) +environment.loaders.prepend("css", css); -environment.loaders.prepend('css', css) - -module.exports = environment +module.exports = environment; diff --git a/config/webpack/loaders/coffee.js b/config/webpack/loaders/coffee.js index 4666716dc..1cfb3e04b 100644 --- a/config/webpack/loaders/coffee.js +++ b/config/webpack/loaders/coffee.js @@ -1,6 +1,6 @@ module.exports = { test: /\.coffee(\.erb)?$/, use: [{ - loader: 'coffee-loader' - }] -} + loader: "coffee-loader", + }], +}; diff --git a/config/webpack/loaders/css.js b/config/webpack/loaders/css.js index 3a49e838a..e64f63151 100644 --- a/config/webpack/loaders/css.js +++ b/config/webpack/loaders/css.js @@ -1,6 +1,6 @@ module.exports = { - test: /\.css(\.erb)?$/, - use: [{ - loader: 'css-loader' - }] - } \ No newline at end of file + test: /\.css(\.erb)?$/, + use: [{ + loader: "css-loader", + }], +}; diff --git a/config/webpack/loaders/scss.js b/config/webpack/loaders/scss.js index ab3a4d506..cb6b844a7 100644 --- a/config/webpack/loaders/scss.js +++ b/config/webpack/loaders/scss.js @@ -1,6 +1,6 @@ module.exports = { - test: /\.sass(\.erb)?$/, - use: [{ - loader: 'sass-loader' - }] - } \ No newline at end of file + test: /\.sass(\.erb)?$/, + use: [{ + loader: "sass-loader", + }], +}; diff --git a/config/webpack/production.js b/config/webpack/production.js index be0f53aac..73d924d83 100644 --- a/config/webpack/production.js +++ b/config/webpack/production.js @@ -1,5 +1,5 @@ -process.env.NODE_ENV = process.env.NODE_ENV || 'production' +process.env.NODE_ENV = process.env.NODE_ENV || "production"; -const environment = require('./environment') +const environment = require("./environment"); -module.exports = environment.toWebpackConfig() +module.exports = environment.toWebpackConfig(); diff --git a/config/webpack/test.js b/config/webpack/test.js index c5edff94a..7f3342f96 100644 --- a/config/webpack/test.js +++ b/config/webpack/test.js @@ -1,5 +1,5 @@ -process.env.NODE_ENV = process.env.NODE_ENV || 'development' +process.env.NODE_ENV = process.env.NODE_ENV || "development"; -const environment = require('./environment') +const environment = require("./environment"); -module.exports = environment.toWebpackConfig() +module.exports = environment.toWebpackConfig(); diff --git a/postcss.config.js b/postcss.config.js index aa5998a80..37626ee68 100644 --- a/postcss.config.js +++ b/postcss.config.js @@ -1,12 +1,12 @@ module.exports = { plugins: [ - require('postcss-import'), - require('postcss-flexbugs-fixes'), - require('postcss-preset-env')({ + require("postcss-import"), + require("postcss-flexbugs-fixes"), + require("postcss-preset-env")({ autoprefixer: { - flexbox: 'no-2009' + flexbox: "no-2009", }, - stage: 3 - }) - ] -} + stage: 3, + }), + ], +}; diff --git a/spec/cypress.config.js b/spec/cypress.config.js index 3b2ff73f9..e289330b7 100644 --- a/spec/cypress.config.js +++ b/spec/cypress.config.js @@ -1,13 +1,13 @@ -const { defineConfig } = require('cypress') +const { defineConfig } = require("cypress"); module.exports = defineConfig({ - e2e:{ - "baseUrl": "http://localhost:3000", - "defaultCommandTimeout": 10000, - "projectId": "v45wg9", - "retries": { - "runMode": 2, - "openMode": 0 - } - } -}) \ No newline at end of file + e2e: { + baseUrl: "http://localhost:3000", + defaultCommandTimeout: 10000, + projectId: "v45wg9", + retries: { + runMode: 2, + openMode: 0, + }, + }, +}); diff --git a/spec/cypress/e2e/admin_spec.cy.js b/spec/cypress/e2e/admin_spec.cy.js index 42c442607..8730b8257 100644 --- a/spec/cypress/e2e/admin_spec.cy.js +++ b/spec/cypress/e2e/admin_spec.cy.js @@ -1,63 +1,62 @@ describe("Authentication", function () { - beforeEach(() => { - cy.app("clean"); + beforeEach(() => { + cy.app("clean"); + }); + describe("admin", () => { + it("can login", () => { + // call a scenario in app_commands/scenarios + cy.appScenario("admin"); + cy.visit("/users/sign_in"); + cy.get('input[type="email"]').type("administrator@mampf.edu"); + cy.get('input[type="password"]').type("test123456"); + cy.get('input[type="submit"]').click(); + cy.url().should("contain", "main/start"); + cy.contains("Veranstaltungen").should("exist"); }); - describe("admin", () => { - it("can login", () => { - //call a scenario in app_commands/scenarios - cy.appScenario("admin"); - cy.visit("/users/sign_in"); - cy.get('input[type="email"]').type("administrator@mampf.edu"); - cy.get('input[type="password"]').type("test123456"); - cy.get('input[type="submit"]').click(); - cy.url().should("contain", "main/start"); - cy.contains("Veranstaltungen").should("exist"); - }); - it("can set profile image", () => { - cy.appScenario("admin"); - cy.visit("/users/sign_in"); - cy.get('input[type="email"]').type("administrator@mampf.edu"); - cy.get('input[type="password"]').type("test123456"); - cy.get('input[type="submit"]').click(); - cy.visit(`/administration/profile`); - cy.contains("Profile Image").should("exist"); - const yourFixturePath = 'cypress/fixtures/files/image.png'; - cy.get('#upload-image').selectFile(yourFixturePath,{force:true}); - cy.wait(100); - cy.contains("Upload").click(); - cy.wait(100); - cy.contains("Speichern").click(); - cy.contains("image.png").should("exist"); - }); + it("can set profile image", () => { + cy.appScenario("admin"); + cy.visit("/users/sign_in"); + cy.get('input[type="email"]').type("administrator@mampf.edu"); + cy.get('input[type="password"]').type("test123456"); + cy.get('input[type="submit"]').click(); + cy.visit("/administration/profile"); + cy.contains("Profile Image").should("exist"); + const yourFixturePath = "cypress/fixtures/files/image.png"; + cy.get("#upload-image").selectFile(yourFixturePath, { force: true }); + cy.wait(100); + cy.contains("Upload").click(); + cy.wait(100); + cy.contains("Speichern").click(); + cy.contains("image.png").should("exist"); }); + }); }); describe("Clicker Admin", function () { - beforeEach(() => { - cy.app("clean"); - cy.appScenario("admin"); - cy.visit("/users/sign_in"); - cy.get('input[type="email"]').type("administrator@mampf.edu"); - cy.get('input[type="password"]').type("test123456"); - cy.get('input[type="submit"]').click(); - }); - - it("can create clicker", () => { - cy.visit("/administration"); - cy.get('a[title="Clicker anlegen"]').click(); - cy.get('input[name="clicker[title]"]').type("ErsterClicker"); - cy.get("div#new-clicker-area").contains("Speichern").click(); - cy.contains("ErsterClicker").should("exist"); - }); + beforeEach(() => { + cy.app("clean"); + cy.appScenario("admin"); + cy.visit("/users/sign_in"); + cy.get('input[type="email"]').type("administrator@mampf.edu"); + cy.get('input[type="password"]').type("test123456"); + cy.get('input[type="submit"]').click(); + }); - it("can show clicker qr", () => { - cy.appFactories([ - ['create', 'clicker', 'with_editor'] - ]).then((clickers) => { - cy.visit(`/clickers/${clickers[0].id}/edit`); - cy.contains("QR-Code zeigen").click(); - cy.get("li#clickerQRCode").should("exist"); - }); + it("can create clicker", () => { + cy.visit("/administration"); + cy.get('a[title="Clicker anlegen"]').click(); + cy.get('input[name="clicker[title]"]').type("ErsterClicker"); + cy.get("div#new-clicker-area").contains("Speichern").click(); + cy.contains("ErsterClicker").should("exist"); + }); + it("can show clicker qr", () => { + cy.appFactories([ + ["create", "clicker", "with_editor"], + ]).then((clickers) => { + cy.visit(`/clickers/${clickers[0].id}/edit`); + cy.contains("QR-Code zeigen").click(); + cy.get("li#clickerQRCode").should("exist"); }); -}); \ No newline at end of file + }); +}); diff --git a/spec/cypress/e2e/courses_spec.cy.js b/spec/cypress/e2e/courses_spec.cy.js index 7ac0166fa..f8e9cb3e3 100644 --- a/spec/cypress/e2e/courses_spec.cy.js +++ b/spec/cypress/e2e/courses_spec.cy.js @@ -1,164 +1,163 @@ describe("Courses", function () { + beforeEach(() => { + cy.app("clean"); + cy.appScenario("setup"); + }); + describe("admin user", () => { beforeEach(() => { - cy.app("clean"); - cy.appScenario("setup"); + cy.appScenario("admin"); + cy.visit("/users/sign_in"); + cy.get('input[type="email"]').type("administrator@mampf.edu"); + cy.get('input[type="password"]').type("test123456"); + cy.get('input[type="submit"]').click(); }); - describe("admin user", () => { - beforeEach(() => { - cy.appScenario("admin"); - cy.visit("/users/sign_in"); - cy.get('input[type="email"]').type("administrator@mampf.edu"); - cy.get('input[type="password"]').type("test123456"); - cy.get('input[type="submit"]').click(); - }); - it("can add tag to course", () => { - cy.appFactories([ - ['create', 'course'] - ]).then((records) => { - cy.visit('/courses/1/edit'); - cy.get('#new-tag-button').click(); - cy.get('#tag_notions_attributes_0_title').type('Geometrie'); - cy.wait(100); - cy.get('#tag_notions_attributes_1_title').type('Geometry'); - cy.get('.col-12 > .btn-primary').click(); - cy.wait(100); - cy.contains('Geometrie'); - }) - }); - it("can set editor in course", () => { - cy.appFactories([ - ['create', 'course'] - ]).then((records) => { - cy.visit('/courses/1/edit'); - cy.get('#course_editor_ids-ts-control').click(); - cy.get('#course_editor_ids-ts-control').type('ad'); - cy.contains('administrator@mampf.edu').click(); - cy.get('.btn-primary').click(); - cy.contains("Admin"); - }) - }); - it("can create module", () => { - cy.visit('/administration'); - cy.get('i[title="Modul anlegen"]').click(); - cy.get('input[name="course[title]"]').type("Lineare Algebra I"); - cy.get('input[name="course[short_title]"]').type("LA I"); - cy.get('input[type="submit"]').click(); - //cy.visit('/administration'); - cy.contains("Lineare Algebra I").should("exist"); - }); - it("can set course image", ()=>{ - cy.appFactories([ - ['create', 'course'], - ['create', 'term'], - ['create', 'lecture',{'term_id':1, 'course_id':1}] - ]).then((records)=>{ - cy.visit(`/courses/${records[0].id}/edit`); - cy.contains("Bild").should("exist"); - cy.get("#image_heading").contains("Ein-/Ausklappen").click(); - const yourFixturePath = 'cypress/fixtures/files/image.png'; - cy.get('#upload-image').selectFile(yourFixturePath,{force: true}); - cy.contains("Upload").click(); - cy.wait(100); - cy.contains("Speichern").click(); - cy.get("#image_heading").contains("Ein-/Ausklappen").click(); - cy.contains("image.png").should("exist"); - }); - }); - it("can create lecture", () => { - cy.appFactories([ - ['create', 'course'], - ['create', 'term'], - ['create','editable_user_join',{ - 'editable_id': 1, - 'editable_type':'Course', - 'user_id':1 - }] - ]).then((records) => { - cy.visit('/administration'); - cy.get('a[title="Veranstaltung anlegen"]').click(); - - cy.get("#lecture_course_id-ts-control").type(records[0].title).type("{enter}"); - cy.get("div#new-lecture-area").contains("Speichern").click(); - cy.contains(records[0].title).should("exist"); - cy.contains(`${records[1].season} ${records[1].year}`).should("exist"); - }); - }); + it("can add tag to course", () => { + cy.appFactories([ + ["create", "course"], + ]).then((records) => { + cy.visit("/courses/1/edit"); + cy.get("#new-tag-button").click(); + cy.get("#tag_notions_attributes_0_title").type("Geometrie"); + cy.wait(100); + cy.get("#tag_notions_attributes_1_title").type("Geometry"); + cy.get(".col-12 > .btn-primary").click(); + cy.wait(100); + cy.contains("Geometrie"); + }); + }); + it("can set editor in course", () => { + cy.appFactories([ + ["create", "course"], + ]).then((records) => { + cy.visit("/courses/1/edit"); + cy.get("#course_editor_ids-ts-control").click(); + cy.get("#course_editor_ids-ts-control").type("ad"); + cy.contains("administrator@mampf.edu").click(); + cy.get(".btn-primary").click(); + cy.contains("Admin"); + }); + }); + it("can create module", () => { + cy.visit("/administration"); + cy.get('i[title="Modul anlegen"]').click(); + cy.get('input[name="course[title]"]').type("Lineare Algebra I"); + cy.get('input[name="course[short_title]"]').type("LA I"); + cy.get('input[type="submit"]').click(); + // cy.visit('/administration'); + cy.contains("Lineare Algebra I").should("exist"); + }); + it("can set course image", () => { + cy.appFactories([ + ["create", "course"], + ["create", "term"], + ["create", "lecture", { term_id: 1, course_id: 1 }], + ]).then((records) => { + cy.visit(`/courses/${records[0].id}/edit`); + cy.contains("Bild").should("exist"); + cy.get("#image_heading").contains("Ein-/Ausklappen").click(); + const yourFixturePath = "cypress/fixtures/files/image.png"; + cy.get("#upload-image").selectFile(yourFixturePath, { force: true }); + cy.contains("Upload").click(); + cy.wait(100); + cy.contains("Speichern").click(); + cy.get("#image_heading").contains("Ein-/Ausklappen").click(); + cy.contains("image.png").should("exist"); + }); + }); + it("can create lecture", () => { + cy.appFactories([ + ["create", "course"], + ["create", "term"], + ["create", "editable_user_join", { + editable_id: 1, + editable_type: "Course", + user_id: 1, + }], + ]).then((records) => { + cy.visit("/administration"); + cy.get('a[title="Veranstaltung anlegen"]').click(); + cy.get("#lecture_course_id-ts-control").type(records[0].title).type("{enter}"); + cy.get("div#new-lecture-area").contains("Speichern").click(); + cy.contains(records[0].title).should("exist"); + cy.contains(`${records[1].season} ${records[1].year}`).should("exist"); + }); + }); + }); + describe("teacher", () => { + beforeEach(() => { + cy.appScenario("non_admin"); + cy.visit("/users/sign_in"); + cy.get('input[type="email"]').type("max@mampf.edu"); + cy.get('input[type="password"]').type("test123456"); + cy.get('input[type="submit"]').click(); + }); + it("can subscribe to unpublished on page", () => { + cy.appFactories([ + ["create", "lecture", { + teacher_id: 1, + }], + ]).then((courses) => { + cy.visit(`/lectures/${courses[0].id}`); + cy.contains("Achtung").should("exist"); + cy.contains("Veranstaltung abonnieren").click(); + cy.contains("Vorlesungsinhalt").should("exist"); + }); }); - describe("teacher", () => { - beforeEach(() => { - cy.appScenario("non_admin"); - cy.visit("/users/sign_in"); - cy.get('input[type="email"]').type("max@mampf.edu"); - cy.get('input[type="password"]').type("test123456"); - cy.get('input[type="submit"]').click(); - }); - it("can subscribe to unpublished on page", () => { - cy.appFactories([ - ["create", "lecture", { - "teacher_id": 1 - }] - ]).then((courses) => { - cy.visit(`/lectures/${courses[0].id}`); - cy.contains("Achtung").should("exist"); - cy.contains("Veranstaltung abonnieren").click(); - cy.contains("Vorlesungsinhalt").should("exist"); - }); - }); + }); + describe("simple user", () => { + beforeEach(() => { + cy.appScenario("non_admin"); + cy.visit("/users/sign_in"); + cy.get('input[type="email"]').type("max@mampf.edu"); + cy.get('input[type="password"]').type("test123456"); + cy.get('input[type="submit"]').click(); }); - describe("simple user", () => { - beforeEach(() => { - cy.appScenario("non_admin"); - cy.visit("/users/sign_in"); - cy.get('input[type="email"]').type("max@mampf.edu"); - cy.get('input[type="password"]').type("test123456"); - cy.get('input[type="submit"]').click(); - }); - it("can subscribe", () => { - //call a scenario in app_commands/scenarios + it("can subscribe", () => { + // call a scenario in app_commands/scenarios - cy.appFactories([ - ['create_list', 'lecture', 6, 'released_for_all'] - ]).then((records) => { - cy.visit("/main/start"); - //cy.get('input[name="search[fulltext]"]').type(records[0][0].title) - cy.contains("Veranstaltungssuche").click(); - cy.contains("Suche").click(); - cy.get('[title="abonnieren"]').first().click(); - cy.get('[title="abbestellen"]').should("exist"); - }); - }); - it("can subscribe on page", () => { - cy.appFactories([ - ["create", "lecture", "released_for_all"] - ]).then((courses) => { - cy.visit(`/lectures/${courses[0].id}`); - cy.contains("Achtung").should("exist"); - cy.contains("Veranstaltung abonnieren").click(); - cy.contains("Vorlesungsinhalt").should("exist"); - }); - }); - it("is blocked to subscribe on page", () => { - cy.appFactories([ - ["create", "lecture", { - "released": "locked", - "passphrase": "passphrase" - }] - ]).then((courses) => { - cy.visit(`/lectures/${courses[0].id}`); - cy.contains("Achtung").should("exist"); - cy.contains("Veranstaltung abonnieren").click(); - cy.contains("Vorlesungsinhalt").should("not.exist"); - cy.contains("Achtung").should("exist"); - }); - }); - it("can not subscribe on page to unpublished", () => { - cy.appFactories([ - ["create", "lecture"] - ]).then((courses) => { - cy.visit(`/lectures/${courses[0].id}`); - cy.contains("Du bist nicht berechtigt").should("exist"); - }); - }); + cy.appFactories([ + ["create_list", "lecture", 6, "released_for_all"], + ]).then((records) => { + cy.visit("/main/start"); + // cy.get('input[name="search[fulltext]"]').type(records[0][0].title) + cy.contains("Veranstaltungssuche").click(); + cy.contains("Suche").click(); + cy.get('[title="abonnieren"]').first().click(); + cy.get('[title="abbestellen"]').should("exist"); + }); + }); + it("can subscribe on page", () => { + cy.appFactories([ + ["create", "lecture", "released_for_all"], + ]).then((courses) => { + cy.visit(`/lectures/${courses[0].id}`); + cy.contains("Achtung").should("exist"); + cy.contains("Veranstaltung abonnieren").click(); + cy.contains("Vorlesungsinhalt").should("exist"); + }); + }); + it("is blocked to subscribe on page", () => { + cy.appFactories([ + ["create", "lecture", { + released: "locked", + passphrase: "passphrase", + }], + ]).then((courses) => { + cy.visit(`/lectures/${courses[0].id}`); + cy.contains("Achtung").should("exist"); + cy.contains("Veranstaltung abonnieren").click(); + cy.contains("Vorlesungsinhalt").should("not.exist"); + cy.contains("Achtung").should("exist"); + }); + }); + it("can not subscribe on page to unpublished", () => { + cy.appFactories([ + ["create", "lecture"], + ]).then((courses) => { + cy.visit(`/lectures/${courses[0].id}`); + cy.contains("Du bist nicht berechtigt").should("exist"); + }); }); -}); \ No newline at end of file + }); +}); diff --git a/spec/cypress/e2e/media_spec.cy.js b/spec/cypress/e2e/media_spec.cy.js index 0173b59d8..f199f69fd 100644 --- a/spec/cypress/e2e/media_spec.cy.js +++ b/spec/cypress/e2e/media_spec.cy.js @@ -1,410 +1,409 @@ describe("Media", () => { + beforeEach(() => { + cy.app("clean"); + }); + describe("Simple User", () => { + beforeEach(() => { + cy.app("clean"); + cy.appScenario("non_admin"); + cy.visit("/users/sign_in"); + cy.get('input[type="email"]').type("max@mampf.edu"); + cy.get('input[type="password"]').type("test123456"); + cy.get('input[type="submit"]').click(); + }); + it("can view media", () => { + cy.appFactories([ + [ + "create", "lesson_medium", "with_manuscript", "released", + ], + ["create", "lecture_user_join", { + user_id: 1, + lecture_id: 1, + }]]).then((records) => { + console.log(records); + cy.visit(`/media/${records[0].id}`); + cy.contains(records[0].description).should("exist"); + }); + }); + it("can comment media", () => { + cy.appFactories([ + + [ + "create", "lesson_medium", "with_manuscript", "released", + ], + ["create", "lecture_user_join", { + user_id: 1, + lecture_id: 1, + }]]).then((records) => { + console.log(records); + cy.visit(`/media/${records[0].id}`); + cy.contains(records[0].description).should("exist"); + cy.contains("Neuer Kommentar").click(); + cy.get('textarea[name="comment[body]"]').type("Dies ist ein super Test Kommentar"); + cy.contains("Kommentar speichern").click(); + cy.contains("Test Kommentar").should("exist"); + }); + }); + }); + describe("Admin", () => { beforeEach(() => { - cy.app("clean"); + cy.app("clean"); + cy.appScenario("admin"); + cy.visit("/users/sign_in"); + cy.get('input[type="email"]').type("administrator@mampf.edu"); + cy.get('input[type="password"]').type("test123456"); + cy.get('input[type="submit"]').click(); }); - describe("Simple User",()=>{ - beforeEach(() => { - cy.app("clean"); - cy.appScenario("non_admin"); - cy.visit("/users/sign_in"); - cy.get('input[type="email"]').type("max@mampf.edu"); - cy.get('input[type="password"]').type("test123456"); - cy.get('input[type="submit"]').click(); - }); - it("can view media",()=>{ - cy.appFactories([ - - [ - "create","lesson_medium", "with_manuscript","released" - ], - ["create", "lecture_user_join", { - user_id: 1, - lecture_id: 1 - }]]).then((records)=>{ - console.log(records); - cy.visit(`/media/${records[0].id}`); - cy.contains(records[0].description).should("exist"); - }); - }); - it("can comment media",()=>{ - cy.appFactories([ - - [ - "create","lesson_medium", "with_manuscript","released" - ], - ["create", "lecture_user_join", { - user_id: 1, - lecture_id: 1 - }]]).then((records)=>{ - console.log(records); - cy.visit(`/media/${records[0].id}`); - cy.contains(records[0].description).should("exist"); - cy.contains("Neuer Kommentar").click(); - cy.get('textarea[name="comment[body]"]').type("Dies ist ein super Test Kommentar"); - cy.contains("Kommentar speichern").click(); - cy.contains("Test Kommentar").should("exist"); - }); - }) - }) - describe("Admin", () => { - beforeEach(() => { - cy.app("clean"); - cy.appScenario("admin"); - cy.visit("/users/sign_in"); - cy.get('input[type="email"]').type("administrator@mampf.edu"); - cy.get('input[type="password"]').type("test123456"); - cy.get('input[type="submit"]').click(); - }); - it("can create medium & release it scheduled", () => { - cy.appFactories([ - ["create", - "lecture", { - "teacher_id": 1, - "released": "all" - } - ] - ]).then((lectures) => { - cy.visit(`lectures/${lectures[0].id}/edit`); - cy.contains("Medium anlegen").should("exist"); - cy.contains("Medium anlegen").click(); - cy.get('input[name="medium[description]"]').type("Media 1"); - cy.contains("Speichern und bearbeiten").click(); - cy.contains("Media 1").should("exist"); - cy.contains("Veröffentlichen").click(); - cy.wait(100); - cy.contains("zum folgenden Zeitpunkt").click(); - - var date = new Date(); - date.setDate(date.getDate() + 7); - console.log(date); - cy.wait(100); - cy.get('input[name="medium[release_date]"]').click().clear().type(date.toLocaleString("de").replace(",","")); - cy.wait(100); - cy.contains("Ich bestätige hiermit, dass durch die Veröffentlichung des Mediums auf der MaMpf-Plattform keine Rechte Dritter verletzt werden.").click(); - cy.get("#publishMediumModal").contains("Speichern").click(); - cy.wait(100); - cy.contains("Dieses Medium wird planmäßig").should("exist"); - }); - }); - it("can create medium & release it.", () => { - cy.appFactories([ - ["create", - "lecture", { - "teacher_id": 1, - "released": "all" - } - ], - ["create", - "lecture_user_join", { - "user_id": 1, - "lecture_id": 1 - } - ] - ]).then((lectures) => { - cy.visit(`lectures/${lectures[0].id}/edit`); - cy.contains("Medium anlegen").should("exist"); - cy.contains("Medium anlegen").click(); - cy.get('input[name="medium[description]"]').type("Media 1"); - cy.wait(100); - cy.contains("Speichern und bearbeiten").click(); - cy.contains("Media 1").should("exist"); - cy.contains("Veröffentlichen").click(); - cy.contains("Ich bestätige hiermit, dass durch die Veröffentlichung des Mediums auf der MaMpf-Plattform keine Rechte Dritter verletzt werden.").click(); - cy.get("#publishMediumModal").contains("Speichern").click(); - cy.wait(100); - cy.visit(`lectures/${lectures[0].id}/food?project=kaviar`); - cy.contains("Media 1").should("exist"); - }); - }); - it("can create medium & release it scheduled with submission", () => { - cy.appFactories([ - ["create", - "lecture", { - "teacher_id": 1, - "released": "all" - } - ] - ]).then((lectures) => { - cy.visit(`lectures/${lectures[0].id}/edit`); - cy.contains("Medium anlegen").should("exist"); - cy.contains("Medium anlegen").click(); - cy.wait(1000); - cy.get('input[name="medium[description]"]').type("Media 1"); - cy.wait(100); - cy.get('select[name="medium[sort]"]').select("Übung"); - cy.contains("Speichern und bearbeiten").click(); - cy.contains("Media 1").should("exist"); - cy.contains("Veröffentlichen").click(); - cy.wait(100); - cy.contains("zum folgenden Zeitpunkt").click(); - cy.contains("Hausaufgabe zu diesem Medium anlegen").click(); + it("can create medium & release it scheduled", () => { + cy.appFactories([ + ["create", + "lecture", { + teacher_id: 1, + released: "all", + }, + ], + ]).then((lectures) => { + cy.visit(`lectures/${lectures[0].id}/edit`); + cy.contains("Medium anlegen").should("exist"); + cy.contains("Medium anlegen").click(); + cy.get('input[name="medium[description]"]').type("Media 1"); + cy.contains("Speichern und bearbeiten").click(); + cy.contains("Media 1").should("exist"); + cy.contains("Veröffentlichen").click(); + cy.wait(100); + cy.contains("zum folgenden Zeitpunkt").click(); - var date = new Date(); - date.setDate(date.getDate() + 7); - console.log(date); - cy.get('input[name="medium[release_date]"]').click().clear().type(date.toLocaleString("de")).type('{enter}'); - date.setDate(date.getDate() + 8); - cy.get('input[name="medium[assignment_deadline]"]').click().clear().type(date.toLocaleString("de")); - cy.contains("Ich bestätige hiermit, dass durch die Veröffentlichung des Mediums auf der MaMpf-Plattform keine Rechte Dritter verletzt werden.").click(); - cy.get("#publishMediumModal").contains("Speichern").click(); - cy.contains("Dieses Medium wird planmäßig").should("exist"); - }); - }); + var date = new Date(); + date.setDate(date.getDate() + 7); + console.log(date); + cy.wait(100); + cy.get('input[name="medium[release_date]"]').click().clear().type(date.toLocaleString("de").replace(",", "")); + cy.wait(100); + cy.contains("Ich bestätige hiermit, dass durch die Veröffentlichung des Mediums auf der MaMpf-Plattform keine Rechte Dritter verletzt werden.").click(); + cy.get("#publishMediumModal").contains("Speichern").click(); + cy.wait(100); + cy.contains("Dieses Medium wird planmäßig").should("exist"); + }); + }); + it("can create medium & release it.", () => { + cy.appFactories([ + ["create", + "lecture", { + teacher_id: 1, + released: "all", + }, + ], + ["create", + "lecture_user_join", { + user_id: 1, + lecture_id: 1, + }, + ], + ]).then((lectures) => { + cy.visit(`lectures/${lectures[0].id}/edit`); + cy.contains("Medium anlegen").should("exist"); + cy.contains("Medium anlegen").click(); + cy.get('input[name="medium[description]"]').type("Media 1"); + cy.wait(100); + cy.contains("Speichern und bearbeiten").click(); + cy.contains("Media 1").should("exist"); + cy.contains("Veröffentlichen").click(); + cy.contains("Ich bestätige hiermit, dass durch die Veröffentlichung des Mediums auf der MaMpf-Plattform keine Rechte Dritter verletzt werden.").click(); + cy.get("#publishMediumModal").contains("Speichern").click(); + cy.wait(100); + cy.visit(`lectures/${lectures[0].id}/food?project=kaviar`); + cy.contains("Media 1").should("exist"); + }); + }); + it("can create medium & release it scheduled with submission", () => { + cy.appFactories([ + ["create", + "lecture", { + teacher_id: 1, + released: "all", + }, + ], + ]).then((lectures) => { + cy.visit(`lectures/${lectures[0].id}/edit`); + cy.contains("Medium anlegen").should("exist"); + cy.contains("Medium anlegen").click(); + cy.wait(1000); + cy.get('input[name="medium[description]"]').type("Media 1"); + cy.wait(100); + cy.get('select[name="medium[sort]"]').select("Übung"); + cy.contains("Speichern und bearbeiten").click(); + cy.contains("Media 1").should("exist"); + cy.contains("Veröffentlichen").click(); + cy.wait(100); + cy.contains("zum folgenden Zeitpunkt").click(); + cy.contains("Hausaufgabe zu diesem Medium anlegen").click(); + + var date = new Date(); + date.setDate(date.getDate() + 7); + console.log(date); + cy.get('input[name="medium[release_date]"]').click().clear().type(date.toLocaleString("de")).type("{enter}"); + date.setDate(date.getDate() + 8); + cy.get('input[name="medium[assignment_deadline]"]').click().clear().type(date.toLocaleString("de")); + cy.contains("Ich bestätige hiermit, dass durch die Veröffentlichung des Mediums auf der MaMpf-Plattform keine Rechte Dritter verletzt werden.").click(); + cy.get("#publishMediumModal").contains("Speichern").click(); + cy.contains("Dieses Medium wird planmäßig").should("exist"); + }); + }); + }); + describe("Non-Admin Teacher", () => { + beforeEach(() => { + cy.app("clean"); + cy.appScenario("teacher"); + cy.visit("/users/sign_in"); + cy.get('input[type="email"]').type("teacher@mampf.edu"); + cy.get('input[type="password"]').type("test123456"); + cy.get('input[type="submit"]').click(); }); - describe("Non-Admin Teacher", () => { - beforeEach(() => { - cy.app("clean"); - cy.appScenario("teacher"); - cy.visit("/users/sign_in"); - cy.get('input[type="email"]').type("teacher@mampf.edu"); - cy.get('input[type="password"]').type("test123456"); - cy.get('input[type="submit"]').click(); - }); - it("can create medium & release it scheduled", () => { - cy.appFactories([ - ["create", - "lecture", { - "teacher_id": 1, - "released": "all" - } - ] - ]).then((lectures) => { - cy.visit(`lectures/${lectures[0].id}/edit`); - cy.contains("Medium anlegen").should("exist"); - cy.contains("Medium anlegen").click(); - cy.get('input[name="medium[description]"]').type("Media 1"); - cy.contains("Speichern und bearbeiten").click(); - cy.contains("Media 1").should("exist"); - cy.contains("Veröffentlichen").click(); - cy.wait(100); - cy.contains("zum folgenden Zeitpunkt").click(); - - var date = new Date(); - date.setDate(date.getDate() + 7); - console.log(date); - cy.wait(100); - cy.get('input[name="medium[release_date]"]').click().clear().type(date.toLocaleString("de").replace(",","")); - cy.wait(100); - cy.contains("Ich bestätige hiermit, dass durch die Veröffentlichung des Mediums auf der MaMpf-Plattform keine Rechte Dritter verletzt werden.").click(); - cy.get("#publishMediumModal").contains("Speichern").click(); - cy.wait(100); - cy.contains("Dieses Medium wird planmäßig").should("exist"); - }); - }); - it("can create medium & release it.", () => { - cy.appFactories([ - ["create", - "lecture", { - "teacher_id": 1, - "released": "all" - } - ], - ["create", - "lecture_user_join", { - "user_id": 1, - "lecture_id": 1 - } - ] - ]).then((lectures) => { - cy.visit(`lectures/${lectures[0].id}/edit`); - cy.contains("Medium anlegen").should("exist"); - cy.contains("Medium anlegen").click(); - cy.get('input[name="medium[description]"]').type("Media 1"); - cy.wait(100); - cy.contains("Speichern und bearbeiten").click(); - cy.contains("Media 1").should("exist"); - cy.contains("Veröffentlichen").click(); - cy.contains("Ich bestätige hiermit, dass durch die Veröffentlichung des Mediums auf der MaMpf-Plattform keine Rechte Dritter verletzt werden.").click(); - cy.get("#publishMediumModal").contains("Speichern").click(); - cy.wait(100); - cy.visit(`lectures/${lectures[0].id}/food?project=kaviar`); - cy.contains("Media 1").should("exist"); - }); - }); - it("can create medium & release it scheduled with submission", () => { - cy.appFactories([ - ["create", - "lecture", { - "teacher_id": 1, - "released": "all" - } - ] - ]).then((lectures) => { - cy.visit(`lectures/${lectures[0].id}/edit`); - cy.contains("Medium anlegen").should("exist"); - cy.contains("Medium anlegen").click(); - cy.get('input[name="medium[description]"]').type("Media 1"); - cy.wait(100); - cy.get('select[name="medium[sort]"]').select("Übung"); - cy.contains("Speichern und bearbeiten").click(); - cy.contains("Media 1").should("exist"); - cy.contains("Veröffentlichen").click(); - cy.wait(100); - cy.contains("Hausaufgabe zu diesem Medium anlegen").click(); + it("can create medium & release it scheduled", () => { + cy.appFactories([ + ["create", + "lecture", { + teacher_id: 1, + released: "all", + }, + ], + ]).then((lectures) => { + cy.visit(`lectures/${lectures[0].id}/edit`); + cy.contains("Medium anlegen").should("exist"); + cy.contains("Medium anlegen").click(); + cy.get('input[name="medium[description]"]').type("Media 1"); + cy.contains("Speichern und bearbeiten").click(); + cy.contains("Media 1").should("exist"); + cy.contains("Veröffentlichen").click(); + cy.wait(100); + cy.contains("zum folgenden Zeitpunkt").click(); - var date = new Date(); - date.setDate(date.getDate() + 7); - cy.wait(100); - console.log(date); - cy.get('input[name="medium[release_date]"]').click().clear().type(date.toLocaleString("de")).type('{enter}'); - date.setDate(date.getDate() + 8); - cy.get('input[name="medium[assignment_deadline]"]').click().clear().type(date.toLocaleString("de")); - cy.get('select[name="medium[assignment_deletion_date]"]').should("exist"); - cy.contains("Ich bestätige hiermit, dass durch die Veröffentlichung des Mediums auf der MaMpf-Plattform keine Rechte Dritter verletzt werden.").click(); - cy.get("#publishMediumModal").contains("Speichern").click(); - cy.contains("Dieses Medium wird planmäßig").should("exist"); - }); - }); - it("can create medium & release it with submission", () => { - cy.appFactories([ - ["create", - "lecture", { - "teacher_id": 1, - "released": "all" - } - ] - ]).then((lectures) => { - cy.visit(`lectures/${lectures[0].id}/edit`); - cy.contains("Medium anlegen").should("exist"); - cy.contains("Medium anlegen").click(); - cy.get('input[name="medium[description]"]').type("Media 1"); - cy.wait(100); - cy.get('select[name="medium[sort]"]').select("Übung"); - cy.contains("Speichern und bearbeiten").click(); - cy.contains("Media 1").should("exist"); - cy.contains("Veröffentlichen").click(); - cy.wait(100); - cy.contains("Hausaufgabe zu diesem Medium anlegen").click(); - var date = new Date(); - date.setDate(date.getDate() + 8); - cy.wait(100); - cy.get('input[name="medium[assignment_deadline]"]').click().clear().type(date.toLocaleString("de")); - cy.get('select[name="medium[assignment_deletion_date]"]').should("exist"); - cy.contains("Ich bestätige hiermit, dass durch die Veröffentlichung des Mediums auf der MaMpf-Plattform keine Rechte Dritter verletzt werden.").click(); - cy.get("#publishMediumModal").contains("Speichern").click(); - cy.contains("Dieses Medium wird planmäßig").should("not.exist"); - cy.wait(500); - cy.contains("zur Veranstaltung").click(); - cy.get('#assignments_heading').click(); - cy.contains("Media 1").should("exist"); - }); - }); + var date = new Date(); + date.setDate(date.getDate() + 7); + console.log(date); + cy.wait(100); + cy.get('input[name="medium[release_date]"]').click().clear().type(date.toLocaleString("de").replace(",", "")); + cy.wait(100); + cy.contains("Ich bestätige hiermit, dass durch die Veröffentlichung des Mediums auf der MaMpf-Plattform keine Rechte Dritter verletzt werden.").click(); + cy.get("#publishMediumModal").contains("Speichern").click(); + cy.wait(100); + cy.contains("Dieses Medium wird planmäßig").should("exist"); + }); + }); + it("can create medium & release it.", () => { + cy.appFactories([ + ["create", + "lecture", { + teacher_id: 1, + released: "all", + }, + ], + ["create", + "lecture_user_join", { + user_id: 1, + lecture_id: 1, + }, + ], + ]).then((lectures) => { + cy.visit(`lectures/${lectures[0].id}/edit`); + cy.contains("Medium anlegen").should("exist"); + cy.contains("Medium anlegen").click(); + cy.get('input[name="medium[description]"]').type("Media 1"); + cy.wait(100); + cy.contains("Speichern und bearbeiten").click(); + cy.contains("Media 1").should("exist"); + cy.contains("Veröffentlichen").click(); + cy.contains("Ich bestätige hiermit, dass durch die Veröffentlichung des Mediums auf der MaMpf-Plattform keine Rechte Dritter verletzt werden.").click(); + cy.get("#publishMediumModal").contains("Speichern").click(); + cy.wait(100); + cy.visit(`lectures/${lectures[0].id}/food?project=kaviar`); + cy.contains("Media 1").should("exist"); + }); + }); + it("can create medium & release it scheduled with submission", () => { + cy.appFactories([ + ["create", + "lecture", { + teacher_id: 1, + released: "all", + }, + ], + ]).then((lectures) => { + cy.visit(`lectures/${lectures[0].id}/edit`); + cy.contains("Medium anlegen").should("exist"); + cy.contains("Medium anlegen").click(); + cy.get('input[name="medium[description]"]').type("Media 1"); + cy.wait(100); + cy.get('select[name="medium[sort]"]').select("Übung"); + cy.contains("Speichern und bearbeiten").click(); + cy.contains("Media 1").should("exist"); + cy.contains("Veröffentlichen").click(); + cy.wait(100); + cy.contains("Hausaufgabe zu diesem Medium anlegen").click(); + + var date = new Date(); + date.setDate(date.getDate() + 7); + cy.wait(100); + console.log(date); + cy.get('input[name="medium[release_date]"]').click().clear().type(date.toLocaleString("de")).type("{enter}"); + date.setDate(date.getDate() + 8); + cy.get('input[name="medium[assignment_deadline]"]').click().clear().type(date.toLocaleString("de")); + cy.get('select[name="medium[assignment_deletion_date]"]').should("exist"); + cy.contains("Ich bestätige hiermit, dass durch die Veröffentlichung des Mediums auf der MaMpf-Plattform keine Rechte Dritter verletzt werden.").click(); + cy.get("#publishMediumModal").contains("Speichern").click(); + cy.contains("Dieses Medium wird planmäßig").should("exist"); + }); + }); + it("can create medium & release it with submission", () => { + cy.appFactories([ + ["create", + "lecture", { + teacher_id: 1, + released: "all", + }, + ], + ]).then((lectures) => { + cy.visit(`lectures/${lectures[0].id}/edit`); + cy.contains("Medium anlegen").should("exist"); + cy.contains("Medium anlegen").click(); + cy.get('input[name="medium[description]"]').type("Media 1"); + cy.wait(100); + cy.get('select[name="medium[sort]"]').select("Übung"); + cy.contains("Speichern und bearbeiten").click(); + cy.contains("Media 1").should("exist"); + cy.contains("Veröffentlichen").click(); + cy.wait(100); + cy.contains("Hausaufgabe zu diesem Medium anlegen").click(); + var date = new Date(); + date.setDate(date.getDate() + 8); + cy.wait(100); + cy.get('input[name="medium[assignment_deadline]"]').click().clear().type(date.toLocaleString("de")); + cy.get('select[name="medium[assignment_deletion_date]"]').should("exist"); + cy.contains("Ich bestätige hiermit, dass durch die Veröffentlichung des Mediums auf der MaMpf-Plattform keine Rechte Dritter verletzt werden.").click(); + cy.get("#publishMediumModal").contains("Speichern").click(); + cy.contains("Dieses Medium wird planmäßig").should("not.exist"); + cy.wait(500); + cy.contains("zur Veranstaltung").click(); + cy.get("#assignments_heading").click(); + cy.contains("Media 1").should("exist"); + }); + }); + }); + describe("Editor", () => { + beforeEach(() => { + cy.app("clean"); + cy.appScenario("editor"); + cy.visit("/users/sign_in"); + cy.get('input[type="email"]').type("editor@mampf.edu"); + cy.get('input[type="password"]').type("test123456"); + cy.get('input[type="submit"]').click(); + }); + it("can create medium & release it scheduled", () => { + cy.appFactories([ + ["create", + "lecture", { + editor_ids: [1], + released: "all", + }, + ], + ["create", + "lecture_user_join", { + user_id: 1, + lecture_id: 1, + }, + ], + ]).then((lectures) => { + cy.visit(`lectures/${lectures[0].id}/edit`); + cy.contains("Medium anlegen").should("exist"); + cy.contains("Medium anlegen").click(); + cy.get('input[name="medium[description]"]').type("Media 1"); + cy.contains("Speichern und bearbeiten").click(); + cy.contains("Media 1").should("exist"); + cy.contains("Veröffentlichen").click(); + cy.wait(100); + cy.contains("zum folgenden Zeitpunkt").click(); + + var date = new Date(); + date.setDate(date.getDate() + 7); + console.log(date); + cy.wait(100); + cy.get('input[name="medium[release_date]"]').click().clear().type(date.toLocaleString("de").replace(",", "")); + cy.wait(100); + cy.contains("Ich bestätige hiermit, dass durch die Veröffentlichung des Mediums auf der MaMpf-Plattform keine Rechte Dritter verletzt werden.").click(); + cy.get("#publishMediumModal").contains("Speichern").click(); + cy.wait(100); + cy.contains("Dieses Medium wird planmäßig").should("exist"); + }); + }); + it("can create medium & release it.", () => { + cy.appFactories([ + ["create", + "lecture", { + editor_ids: [1], + released: "all", + }, + ], + ["create", + "lecture_user_join", { + user_id: 1, + lecture_id: 1, + }, + ], + ]).then((lectures) => { + cy.visit(`lectures/${lectures[0].id}/edit`); + cy.contains("Medium anlegen").should("exist"); + cy.contains("Medium anlegen").click(); + cy.get('input[name="medium[description]"]').type("Media 1"); + cy.wait(100); + cy.contains("Speichern und bearbeiten").click(); + cy.contains("Media 1").should("exist"); + cy.contains("Veröffentlichen").click(); + cy.contains("Ich bestätige hiermit, dass durch die Veröffentlichung des Mediums auf der MaMpf-Plattform keine Rechte Dritter verletzt werden.").click(); + cy.get("#publishMediumModal").contains("Speichern").click(); + cy.wait(100); + cy.visit(`lectures/${lectures[0].id}/food?project=kaviar`); + cy.contains("Media 1").should("exist"); + }); }); - describe("Editor", () => { - beforeEach(() => { - cy.app("clean"); - cy.appScenario("editor"); - cy.visit("/users/sign_in"); - cy.get('input[type="email"]').type("editor@mampf.edu"); - cy.get('input[type="password"]').type("test123456"); - cy.get('input[type="submit"]').click(); - }); - it("can create medium & release it scheduled", () => { - cy.appFactories([ - ["create", - "lecture", { - "editor_ids": [1], - "released": "all" - } - ], - ["create", - "lecture_user_join", { - user_id: 1, - lecture_id: 1 - } - ] - ]).then((lectures) => { - cy.visit(`lectures/${lectures[0].id}/edit`); - cy.contains("Medium anlegen").should("exist"); - cy.contains("Medium anlegen").click(); - cy.get('input[name="medium[description]"]').type("Media 1"); - cy.contains("Speichern und bearbeiten").click(); - cy.contains("Media 1").should("exist"); - cy.contains("Veröffentlichen").click(); - cy.wait(100); - cy.contains("zum folgenden Zeitpunkt").click(); - - var date = new Date(); - date.setDate(date.getDate() + 7); - console.log(date); - cy.wait(100); - cy.get('input[name="medium[release_date]"]').click().clear().type(date.toLocaleString("de").replace(",","")); - cy.wait(100); - cy.contains("Ich bestätige hiermit, dass durch die Veröffentlichung des Mediums auf der MaMpf-Plattform keine Rechte Dritter verletzt werden.").click(); - cy.get("#publishMediumModal").contains("Speichern").click(); - cy.wait(100); - cy.contains("Dieses Medium wird planmäßig").should("exist"); - }); - }); - it("can create medium & release it.", () => { - cy.appFactories([ - ["create", - "lecture", { - "editor_ids": [1], - "released": "all" - } - ], - ["create", - "lecture_user_join", { - user_id: 1, - lecture_id: 1 - } - ] - ]).then((lectures) => { - cy.visit(`lectures/${lectures[0].id}/edit`); - cy.contains("Medium anlegen").should("exist"); - cy.contains("Medium anlegen").click(); - cy.get('input[name="medium[description]"]').type("Media 1"); - cy.wait(100); - cy.contains("Speichern und bearbeiten").click(); - cy.contains("Media 1").should("exist"); - cy.contains("Veröffentlichen").click(); - cy.contains("Ich bestätige hiermit, dass durch die Veröffentlichung des Mediums auf der MaMpf-Plattform keine Rechte Dritter verletzt werden.").click(); - cy.get("#publishMediumModal").contains("Speichern").click(); - cy.wait(100); - cy.visit(`lectures/${lectures[0].id}/food?project=kaviar`); - cy.contains("Media 1").should("exist"); - }); - }); - it("can create medium & release it scheduled with submission", () => { - cy.appFactories([ - ["create", - "lecture", { - "editor_ids": [1], - "released": "all" - } - ], - ["create", - "lecture_user_join", { - user_id: 1, - lecture_id: 1 - } - ] - ]).then((lectures) => { - cy.visit(`lectures/${lectures[0].id}/edit`); - cy.contains("Medium anlegen").should("exist"); - cy.contains("Medium anlegen").click(); - cy.get('input[name="medium[description]"]').type("Media 1"); - cy.wait(100); - cy.get('select[name="medium[sort]"]').select("Übung"); - cy.contains("Speichern und bearbeiten").click(); - cy.contains("Media 1").should("exist"); - cy.contains("Veröffentlichen").click(); - cy.wait(100); - cy.contains("zum folgenden Zeitpunkt").click(); - cy.contains("Hausaufgabe zu diesem Medium anlegen").click(); + it("can create medium & release it scheduled with submission", () => { + cy.appFactories([ + ["create", + "lecture", { + editor_ids: [1], + released: "all", + }, + ], + ["create", + "lecture_user_join", { + user_id: 1, + lecture_id: 1, + }, + ], + ]).then((lectures) => { + cy.visit(`lectures/${lectures[0].id}/edit`); + cy.contains("Medium anlegen").should("exist"); + cy.contains("Medium anlegen").click(); + cy.get('input[name="medium[description]"]').type("Media 1"); + cy.wait(100); + cy.get('select[name="medium[sort]"]').select("Übung"); + cy.contains("Speichern und bearbeiten").click(); + cy.contains("Media 1").should("exist"); + cy.contains("Veröffentlichen").click(); + cy.wait(100); + cy.contains("zum folgenden Zeitpunkt").click(); + cy.contains("Hausaufgabe zu diesem Medium anlegen").click(); - var date = new Date(); - date.setDate(date.getDate() + 7); - console.log(date); - cy.get('input[name="medium[release_date]"]').click().clear().type(date.toLocaleString("de")).type('{enter}');; - date.setDate(date.getDate() + 8); - cy.get('input[name="medium[assignment_deadline]"]').click().clear().type(date.toLocaleString("de")); - cy.contains("Ich bestätige hiermit, dass durch die Veröffentlichung des Mediums auf der MaMpf-Plattform keine Rechte Dritter verletzt werden.").click(); - cy.get("#publishMediumModal").contains("Speichern").click(); - cy.contains("Dieses Medium wird planmäßig").should("exist"); - }); - }); + var date = new Date(); + date.setDate(date.getDate() + 7); + console.log(date); + cy.get('input[name="medium[release_date]"]').click().clear().type(date.toLocaleString("de")).type("{enter}"); + date.setDate(date.getDate() + 8); + cy.get('input[name="medium[assignment_deadline]"]').click().clear().type(date.toLocaleString("de")); + cy.contains("Ich bestätige hiermit, dass durch die Veröffentlichung des Mediums auf der MaMpf-Plattform keine Rechte Dritter verletzt werden.").click(); + cy.get("#publishMediumModal").contains("Speichern").click(); + cy.contains("Dieses Medium wird planmäßig").should("exist"); + }); }); -}); \ No newline at end of file + }); +}); diff --git a/spec/cypress/e2e/search_spec.cy.js b/spec/cypress/e2e/search_spec.cy.js index 2ab57da10..e530b5316 100644 --- a/spec/cypress/e2e/search_spec.cy.js +++ b/spec/cypress/e2e/search_spec.cy.js @@ -1,19 +1,18 @@ describe("Media", () => { - - beforeEach(() => { + beforeEach(() => { + cy.app("clean"); + }); + describe("Simple User", () => { + describe("Media search", () => { + beforeEach(() => { cy.app("clean"); - }); - describe("Simple User",()=>{ - describe("Media search",()=>{ - beforeEach(() => { - cy.app("clean"); - cy.appScenario("non_admin"); - cy.visit("/users/sign_in"); - cy.get('input[type="email"]').type("max@mampf.edu"); - cy.get('input[type="password"]').type("test123456"); - cy.get('input[type="submit"]').click(); - }); - /*it("can search released media",()=>{ + cy.appScenario("non_admin"); + cy.visit("/users/sign_in"); + cy.get('input[type="email"]').type("max@mampf.edu"); + cy.get('input[type="password"]').type("test123456"); + cy.get('input[type="submit"]').click(); + }); + /* it("can search released media",()=>{ cy.appFactories([ ["create","lesson_medium", "with_manuscript","released"], ["create","lesson_medium", "with_manuscript"] @@ -22,20 +21,20 @@ describe("Media", () => { cy.get('#collapseMediaSearch > .card-body > form > .row > .col-12 > .btn').click(); cy.get('#media-search-results').get('.col-12 > .card').should('have.length',1); }); - });*/ - it("can filter for tags",()=>{ - cy.appFactories([ - ["create","lesson_medium", "with_manuscript","released","with_tags"], - ["create","lesson_medium", "with_manuscript","released"] - ]).then((records)=>{ - cy.get('#mediaSearchLink').click(); - cy.get('#media_fulltext').type(records[0].description); - cy.wait(1000); - cy.get('#collapseMediaSearch > .card-body > form > .row > .col-12 > .btn').click(); - cy.get('#media-search-results').get('.col-12 > .card').should('have.length',1); - cy.get('#media-search-results').get('.col-12 > .card').contains(records[0].description); - }); - }); - }); - }); -}); \ No newline at end of file + }); */ + it("can filter for tags", () => { + cy.appFactories([ + ["create", "lesson_medium", "with_manuscript", "released", "with_tags"], + ["create", "lesson_medium", "with_manuscript", "released"], + ]).then((records) => { + cy.get("#mediaSearchLink").click(); + cy.get("#media_fulltext").type(records[0].description); + cy.wait(1000); + cy.get("#collapseMediaSearch > .card-body > form > .row > .col-12 > .btn").click(); + cy.get("#media-search-results").get(".col-12 > .card").should("have.length", 1); + cy.get("#media-search-results").get(".col-12 > .card").contains(records[0].description); + }); + }); + }); + }); +}); diff --git a/spec/cypress/e2e/submissions_spec.cy.js b/spec/cypress/e2e/submissions_spec.cy.js index e1f7f5fa1..a59dab957 100644 --- a/spec/cypress/e2e/submissions_spec.cy.js +++ b/spec/cypress/e2e/submissions_spec.cy.js @@ -1,155 +1,153 @@ describe("Submissions", () => { - + beforeEach(() => { + cy.app("clean"); + }); + describe("Administration", () => { beforeEach(() => { - cy.app("clean"); + cy.app("clean"); + cy.appScenario("admin"); + cy.visit("/users/sign_in"); + cy.get('input[type="email"]').type("administrator@mampf.edu"); + cy.get('input[type="password"]').type("test123456"); + cy.get('input[type="submit"]').click(); }); - describe("Administration", () => { - beforeEach(() => { - cy.app("clean"); - cy.appScenario("admin"); - cy.visit("/users/sign_in"); - cy.get('input[type="email"]').type("administrator@mampf.edu"); - cy.get('input[type="password"]').type("test123456"); - cy.get('input[type="submit"]').click(); - }); - it("can create tutorial", () => { - cy.appFactories([ - ["create", - "lecture", { - "teacher_id": 1 - } - ], - ["create", "user", "auto_confirmed"] - ]).then((lectures) => { - cy.visit(`lectures/${lectures[0].id}/edit`); - cy.contains("Tutorien").should("exist"); - cy.contains("Tutorien").click(); - cy.contains("Neues Tutorium anlegen").click(); - cy.get('input[name="tutorial[title]"]').type("Tutorium A"); - cy.get('#tutorial_tutor_ids_-ts-control').type(lectures[1].name); - cy.contains(lectures[1].name).click(); - cy.get("#exercises_collapse").contains("Speichern").click(); - }); - }) - it("can create assignment", () => { - cy.appFactories([ - ["create", - "lecture", { - "teacher_id": 1 - } - ], - [ - "create", "tutorial", { - "lecture_id": 1 - } - ] - ]).then((tutorials) => { - console.log(tutorials[1]); - cy.visit(`/lectures/${tutorials[1].lecture_id}/edit`); - cy.contains("Hausaufgaben").should("exist"); - cy.contains("Hausaufgaben").click(); - cy.contains("Neue Hausaufgabe anlegen").click(); - cy.get('input[name="assignment[title]"]').type("Assignment A"); - cy.get('input[name="assignment[deadline]"]').type((new Date()).toLocaleTimeString("de")); - cy.get("#assignments_collapse").contains("Speichern").click(); - cy.contains("Assignment A").should("exist"); - }); - }); + it("can create tutorial", () => { + cy.appFactories([ + ["create", + "lecture", { + teacher_id: 1, + }, + ], + ["create", "user", "auto_confirmed"], + ]).then((lectures) => { + cy.visit(`lectures/${lectures[0].id}/edit`); + cy.contains("Tutorien").should("exist"); + cy.contains("Tutorien").click(); + cy.contains("Neues Tutorium anlegen").click(); + cy.get('input[name="tutorial[title]"]').type("Tutorium A"); + cy.get("#tutorial_tutor_ids_-ts-control").type(lectures[1].name); + cy.contains(lectures[1].name).click(); + cy.get("#exercises_collapse").contains("Speichern").click(); + }); }); - describe("User", () => { - beforeEach(() => { - cy.app("clean"); - cy.appScenario("non_admin"); - cy.visit("/users/sign_in"); - cy.get('input[type="email"]').type("max@mampf.edu"); - cy.get('input[type="password"]').type("test123456"); - cy.get('input[type="submit"]').click(); - - cy.appFactories([ - ["create", "lecture", "released_for_all"], - ["create", "lecture_user_join", { - user_id: 1, - lecture_id: 1 - }] - ]).then((lectures) => {}); - }); - it("can create submission", () => { - cy.appFactories([ - [ - "create", - "tutorial", "with_tutors", { - lecture_id: 1 - } - ], - [ - "create", "assignment", { - lecture_id: 1 - } - ] - ]).then((assignments) => { - cy.visit(`lectures/${assignments[0].lecture_id}/submissions`); - cy.contains("Anlegen").click(); - const yourFixturePath = 'cypress/fixtures/files/manuscript.pdf'; - cy.get('#upload-userManuscript').selectFile(yourFixturePath,{force: true}); - cy.get('input[type="checkbox"]').check(); - cy.contains("Hochladen").click(); - cy.get(".submissionFooter").contains("Speichern").click(); - cy.contains("Du").should("exist"); - }); - }); - it("can process multiple files", () => { - cy.appFactories([ - [ - "create", - "tutorial", "with_tutors", { - lecture_id: 1 - } - ], - [ - "create", "assignment", { - lecture_id: 1 - } - ] - ]).then((assignments) => { - cy.visit(`lectures/${assignments[0].lecture_id}/submissions`); - cy.contains("Anlegen").click(); - const yourFixturePath = 'cypress/fixtures/files/manuscript.pdf'; - cy.get('#upload-userManuscript').selectFile([yourFixturePath,yourFixturePath,yourFixturePath],{force: true}); - cy.get('#userManuscript-merge-btn').should("exist"); - cy.get('#userManuscript-merge-btn').click(); - cy.get('#multiple-files-selected').should("have.attr", "style", "display: none;"); - cy.get('input[type="checkbox"]').check(); - cy.get('#userManuscript-uploadButton-call').click(); - cy.get('#userManuscript-uploadButton-call').contains("Erfolgreich hochgeladen") - }); - }) - it("can join submission", () => { - cy.appFactories([ - [ - "create", - "tutorial", "with_tutors", { - lecture_id: 1 - } - ], - [ - "create", "assignment", { - lecture_id: 1 - } - ], - ["create", "submission", "with_users", "with_manuscript", { - assignment_id: 1, - tutorial_id: 1 - }] - ]).then((assignments) => { - cy.visit(`lectures/${assignments[0].lecture_id}/submissions`); - cy.contains("Beitreten").click(); - cy.contains("Code").should("exist"); - console.log(assignments[2]); - cy.get('input[name="join[code]"]').type(assignments[2].token); - cy.contains("Beitreten").click(); - cy.contains("Du").should("exist"); - }); - }); + it("can create assignment", () => { + cy.appFactories([ + ["create", + "lecture", { + teacher_id: 1, + }, + ], + [ + "create", "tutorial", { + lecture_id: 1, + }, + ], + ]).then((tutorials) => { + console.log(tutorials[1]); + cy.visit(`/lectures/${tutorials[1].lecture_id}/edit`); + cy.contains("Hausaufgaben").should("exist"); + cy.contains("Hausaufgaben").click(); + cy.contains("Neue Hausaufgabe anlegen").click(); + cy.get('input[name="assignment[title]"]').type("Assignment A"); + cy.get('input[name="assignment[deadline]"]').type((new Date()).toLocaleTimeString("de")); + cy.get("#assignments_collapse").contains("Speichern").click(); + cy.contains("Assignment A").should("exist"); + }); }); + }); + describe("User", () => { + beforeEach(() => { + cy.app("clean"); + cy.appScenario("non_admin"); + cy.visit("/users/sign_in"); + cy.get('input[type="email"]').type("max@mampf.edu"); + cy.get('input[type="password"]').type("test123456"); + cy.get('input[type="submit"]').click(); -}); \ No newline at end of file + cy.appFactories([ + ["create", "lecture", "released_for_all"], + ["create", "lecture_user_join", { + user_id: 1, + lecture_id: 1, + }], + ]).then((lectures) => {}); + }); + it("can create submission", () => { + cy.appFactories([ + [ + "create", + "tutorial", "with_tutors", { + lecture_id: 1, + }, + ], + [ + "create", "assignment", { + lecture_id: 1, + }, + ], + ]).then((assignments) => { + cy.visit(`lectures/${assignments[0].lecture_id}/submissions`); + cy.contains("Anlegen").click(); + const yourFixturePath = "cypress/fixtures/files/manuscript.pdf"; + cy.get("#upload-userManuscript").selectFile(yourFixturePath, { force: true }); + cy.get('input[type="checkbox"]').check(); + cy.contains("Hochladen").click(); + cy.get(".submissionFooter").contains("Speichern").click(); + cy.contains("Du").should("exist"); + }); + }); + it("can process multiple files", () => { + cy.appFactories([ + [ + "create", + "tutorial", "with_tutors", { + lecture_id: 1, + }, + ], + [ + "create", "assignment", { + lecture_id: 1, + }, + ], + ]).then((assignments) => { + cy.visit(`lectures/${assignments[0].lecture_id}/submissions`); + cy.contains("Anlegen").click(); + const yourFixturePath = "cypress/fixtures/files/manuscript.pdf"; + cy.get("#upload-userManuscript").selectFile([yourFixturePath, yourFixturePath, yourFixturePath], { force: true }); + cy.get("#userManuscript-merge-btn").should("exist"); + cy.get("#userManuscript-merge-btn").click(); + cy.get("#multiple-files-selected").should("have.attr", "style", "display: none;"); + cy.get('input[type="checkbox"]').check(); + cy.get("#userManuscript-uploadButton-call").click(); + cy.get("#userManuscript-uploadButton-call").contains("Erfolgreich hochgeladen"); + }); + }); + it("can join submission", () => { + cy.appFactories([ + [ + "create", + "tutorial", "with_tutors", { + lecture_id: 1, + }, + ], + [ + "create", "assignment", { + lecture_id: 1, + }, + ], + ["create", "submission", "with_users", "with_manuscript", { + assignment_id: 1, + tutorial_id: 1, + }], + ]).then((assignments) => { + cy.visit(`lectures/${assignments[0].lecture_id}/submissions`); + cy.contains("Beitreten").click(); + cy.contains("Code").should("exist"); + console.log(assignments[2]); + cy.get('input[name="join[code]"]').type(assignments[2].token); + cy.contains("Beitreten").click(); + cy.contains("Du").should("exist"); + }); + }); + }); +}); diff --git a/spec/cypress/e2e/thredded_spec.cy.js b/spec/cypress/e2e/thredded_spec.cy.js index 192f04d06..21d1ba512 100644 --- a/spec/cypress/e2e/thredded_spec.cy.js +++ b/spec/cypress/e2e/thredded_spec.cy.js @@ -1,88 +1,88 @@ -describe('Thredded', function() { +describe("Thredded", function () { + beforeEach(() => { + cy.app("clean"); + }); + describe("Administration", () => { beforeEach(() => { - cy.app("clean"); + cy.app("clean"); + cy.appScenario("admin"); + cy.visit("/users/sign_in"); + cy.get('input[type="email"]').type("administrator@mampf.edu"); + cy.get('input[type="password"]').type("test123456"); + cy.get('input[type="submit"]').click(); + }); + it("can access managment", () => { + cy.appFactories([ + ["create", "course"], + ["create", + "lecture", "released_for_all", { + teacher_id: 1, + course_id: 1, + }, + ], ["create", "lecture_user_join", { + user_id: 1, + lecture_id: 1, + }], + ]).then((records) => { + cy.visit(`/lectures/${records[1].id}/edit`); + cy.contains("Forum").click(); + cy.contains("Forum anlegen").click(); + cy.contains("Forum").click(); + cy.contains("Forum löschen").should("exist"); + cy.visit("/forum"); + cy.wait(100); + cy.contains(records[0].title).click(); + cy.get('input[name="topic[title]').click().type("Test"); + cy.get('textarea[name="topic[content]').click().type("Test"); + cy.contains("Erstelle eine neue Diskussion").click(); + cy.visit("/forum"); + cy.contains("Verwaltung").click(); + cy.contains("Ausstehend").should("exist"); + }); + }); + it("can create forum", () => { + cy.appFactories([ + ["create", "course"], + ["create", + "lecture", "released_for_all", { + teacher_id: 1, + course_id: 1, + }, + ], ["create", "lecture_user_join", { + user_id: 1, + lecture_id: 1, + }], + ]).then((records) => { + cy.visit(`/lectures/${records[1].id}/edit`); + cy.contains("Forum").click(); + cy.contains("Forum anlegen").click(); + cy.contains("Forum").click(); + cy.contains("Forum löschen").should("exist"); + cy.visit("/forum"); + console.log(records[0]); + cy.wait(100); + cy.contains(records[0].title).should("exist"); + }); }); - describe("Administration", () => { - beforeEach(() => { - cy.app("clean"); - cy.appScenario("admin"); - cy.visit("/users/sign_in"); - cy.get('input[type="email"]').type("administrator@mampf.edu"); - cy.get('input[type="password"]').type("test123456"); - cy.get('input[type="submit"]').click(); - }); - it("can access managment",()=>{ - cy.appFactories([ - ["create","course"], - ["create", - "lecture","released_for_all", { - "teacher_id": 1, - "course_id":1 - } - ],["create", "lecture_user_join", { - user_id: 1, - lecture_id: 1 - }] - ]).then((records)=>{ - cy.visit(`/lectures/${records[1].id}/edit`); - cy.contains("Forum").click(); - cy.contains("Forum anlegen").click(); - cy.contains("Forum").click(); - cy.contains("Forum löschen").should("exist"); - cy.visit(`/forum`); - cy.wait(100); - cy.contains(records[0].title).click(); - cy.get('input[name="topic[title]').click().type("Test"); - cy.get('textarea[name="topic[content]').click().type("Test"); - cy.contains("Erstelle eine neue Diskussion").click(); - cy.visit('/forum'); - cy.contains("Verwaltung").click(); - cy.contains("Ausstehend").should("exist"); - }); - }); - it('can create forum',()=>{ - cy.appFactories([ - ["create","course"], - ["create", - "lecture","released_for_all", { - "teacher_id": 1, - "course_id":1 - } - ],["create", "lecture_user_join", { - user_id: 1, - lecture_id: 1 - }] - ]).then((records)=>{ - cy.visit(`/lectures/${records[1].id}/edit`); - cy.contains("Forum").click(); - cy.contains("Forum anlegen").click(); - cy.contains("Forum").click(); - cy.contains("Forum löschen").should("exist"); - cy.visit(`/forum`); - console.log(records[0]) - cy.wait(100); - cy.contains(records[0].title).should("exist"); - }); - }); - it('can delete forum',()=>{ - cy.appFactories([ - ["create","course"], - ["create", - "lecture","released_for_all", "with_forum", { - "teacher_id": 1, - "course_id":1 - } - ],["create", "lecture_user_join", { - user_id: 1, - lecture_id: 1 - }] - ]).then((records)=>{ - cy.visit(`/lectures/${records[1].id}/edit`); - cy.contains("Forum").click(); - cy.contains("Forum löschen").click(); - cy.contains("Forum").click(); - cy.contains("Forum anlegen").should("exist"); - }); - }); + it("can delete forum", () => { + cy.appFactories([ + ["create", "course"], + ["create", + "lecture", "released_for_all", "with_forum", { + teacher_id: 1, + course_id: 1, + }, + ], ["create", "lecture_user_join", { + user_id: 1, + lecture_id: 1, + }], + ]).then((records) => { + cy.visit(`/lectures/${records[1].id}/edit`); + cy.contains("Forum").click(); + cy.contains("Forum löschen").click(); + cy.contains("Forum").click(); + cy.contains("Forum anlegen").should("exist"); + }); }); }); +}); diff --git a/spec/cypress/e2e/tutorials_spec.cy.js b/spec/cypress/e2e/tutorials_spec.cy.js index da2213d1b..3bf2a13dd 100644 --- a/spec/cypress/e2e/tutorials_spec.cy.js +++ b/spec/cypress/e2e/tutorials_spec.cy.js @@ -1,312 +1,311 @@ describe("Tutorials", () => { - + beforeEach(() => { + cy.app("clean"); + }); + describe("Teacher", () => { beforeEach(() => { - cy.app("clean"); + cy.app("clean"); + cy.appScenario("teacher"); + cy.visit("/users/sign_in"); + cy.get('input[type="email"]').type("teacher@mampf.edu"); + cy.get('input[type="password"]').type("test123456"); + cy.get('input[type="submit"]').click(); }); - describe("Teacher", () => { - beforeEach(() => { - cy.app("clean"); - cy.appScenario("teacher"); - cy.visit("/users/sign_in"); - cy.get('input[type="email"]').type("teacher@mampf.edu"); - cy.get('input[type="password"]').type("test123456"); - cy.get('input[type="submit"]').click(); - }); - it("can view tutorials", () => { - cy.appFactories([ - ["create", - "lecture", { - teacher_id: 1 - } - ], - ["create", - "lecture_user_join", { - user_id: 1, - lecture_id: 1 - } - ], - ["create", - "tutorial", "with_tutors", { - lecture_id: 1 - } - ], - ["create", - "assignment", { - lecture_id: 1 - } - ], - ["create", "submission", "with_users", "with_manuscript", "with_correction", { - assignment_id: 1, - tutorial_id: 1 - }] - ]).then((lectures) => { - cy.visit(`lectures/${lectures[0].id}`); - cy.contains("Tutorien").should("exist"); - cy.contains("Tutorien").click(); - cy.get('main > .dropdown > .btn').contains("Tutorien").should("exist"); - cy.get('main > .dropdown > .btn').contains("Tutorien").click(); - cy.get('main > .dropdown > .dropdown-menu > .dropdown-item').contains(lectures[2].title).click(); - cy.reload(); - cy.contains("Übersicht").should("exist"); - }); - }); - it("can view tutorials if tutor", () => { - cy.appFactories([ - ["create", - "lecture", { - teacher_id: 1 - } - ], - ["create", - "lecture_user_join", { - user_id: 1, - lecture_id: 1 - } - ], - ["create", - "tutorial", { - lecture_id: 1, - tutor_ids: [1] - } - ], - ["create", - "tutorial", "with_tutors", { - lecture_id: 1 - } - ], - ["create", - "assignment", { - lecture_id: 1 - } - ], - ["create", "submission", "with_users", "with_manuscript", "with_correction", { - assignment_id: 1, - tutorial_id: 1 - }] - ]).then((lectures) => { - cy.visit(`lectures/${lectures[0].id}`); - cy.contains("Tutorien").should("exist"); - cy.contains("Tutorien").click(); - cy.get('main').contains("Übersicht").should("exist"); - cy.get('main').contains("Übersicht").click(); - cy.get('main > .dropdown > .btn').contains("Tutorien").click(); - cy.contains('Eigene Tutorien').should('exist'); - cy.contains('Sonstige Tutorien').should('exist'); - cy.get('.dropdown-menu').contains(lectures[2].title).should("exist"); - cy.get('.dropdown-menu').contains(lectures[3].title).should("exist"); - }); - }); - it("can create correction if tutor", () => { - cy.appFactories([ - ["create", - "lecture", { - teacher_id: 1 - } - ], - ["create", - "lecture_user_join", { - user_id: 1, - lecture_id: 1 - } - ], - ["create", - "tutorial", { - lecture_id: 1, - tutor_ids: [1] - } - ], - ["create", - "assignment", "inactive", { - lecture_id: 1 - } - ], - ["create", - "assignment", { - lecture_id: 1 - } - ], - ["create", "submission", "with_users", "with_manuscript", { - assignment_id: 1, - tutorial_id: 1 - }], - ["create", "submission", "with_users", "with_manuscript", { - assignment_id: 2, - tutorial_id: 1 - }] - ]).then((lectures) => { - cy.visit(`lectures/${lectures[0].id}`); - cy.contains("Tutorien").click(); - cy.contains("Achtung").should("exist"); - cy.contains(lectures[4].title).click(); - cy.contains(lectures[3].title).click(); - cy.contains("Akzeptieren").click(); - cy.reload(); - cy.get(".correction-column").contains("Hochladen").click(); - const yourFixturePath = 'cypress/fixtures/files/manuscript.pdf'; - cy.get(".correction-column").contains("Datei").click(); - cy.get(`#upload-correction-${lectures[5].id}`).selectFile(yourFixturePath,{force:true}); - cy.contains("Upload").click(); - cy.get('.correction-upload > .mt-2 > .col-12 > .btn-primary').contains("Speichern").click(); - cy.reload(); - cy.get('.correction-action-area > [data-turbolinks="false"]').should("exist"); - }); - }); - it("can move submission if tutor", () => { - cy.appFactories([ - ["create", - "lecture", { - teacher_id: 1 - } - ], - ["create", - "lecture_user_join", { - user_id: 1, - lecture_id: 1 - } - ], - ["create", - "tutorial", { - lecture_id: 1, - tutor_ids: [1] - } - ], - ["create", - "tutorial", { - lecture_id: 1 - } - ], - ["create", - "assignment", { - lecture_id: 1 - } - ], - ["create", "submission", "with_users", "with_manuscript", { - assignment_id: 1, - tutorial_id: 1 - }], - ["create", "submission", "with_users", "with_manuscript", { - assignment_id: 1, - tutorial_id: 2 - }] - ]).then((lectures) => { - cy.visit(`lectures/${lectures[0].id}`); - cy.contains("Tutorien").click(); - cy.contains("Verschieben").click(); - cy.get('.ts-control').type(lectures[3].title); - cy.get('.ts-control').type('{enter}'); - cy.get('.submission-actions > form > .mt-2 > .col-12 > .btn-primary').contains("Speichern").should("exist"); - cy.get('.submission-actions > form > .mt-2 > .col-12 > .btn-primary').contains("Speichern").click(); - cy.get('.submission-actions > form > .mt-2 > .col-12 > .btn-primary').contains("Speichern").click(); - cy.reload(); - cy.contains("Zu dieser Hausaufgabe liegen in diesem Tutorium keine Abgaben vor.").should("exist"); - }); - }); + it("can view tutorials", () => { + cy.appFactories([ + ["create", + "lecture", { + teacher_id: 1, + }, + ], + ["create", + "lecture_user_join", { + user_id: 1, + lecture_id: 1, + }, + ], + ["create", + "tutorial", "with_tutors", { + lecture_id: 1, + }, + ], + ["create", + "assignment", { + lecture_id: 1, + }, + ], + ["create", "submission", "with_users", "with_manuscript", "with_correction", { + assignment_id: 1, + tutorial_id: 1, + }], + ]).then((lectures) => { + cy.visit(`lectures/${lectures[0].id}`); + cy.contains("Tutorien").should("exist"); + cy.contains("Tutorien").click(); + cy.get("main > .dropdown > .btn").contains("Tutorien").should("exist"); + cy.get("main > .dropdown > .btn").contains("Tutorien").click(); + cy.get("main > .dropdown > .dropdown-menu > .dropdown-item").contains(lectures[2].title).click(); + cy.reload(); + cy.contains("Übersicht").should("exist"); + }); }); - describe("Tutor", () => { - beforeEach(() => { - cy.app("clean"); - cy.appScenario("non_admin"); - cy.visit("/users/sign_in"); - cy.get('input[type="email"]').type("max@mampf.edu"); - cy.get('input[type="password"]').type("test123456"); - cy.get('input[type="submit"]').click(); - }); - it("can upload correction if assignment inactive", () => { - cy.appFactories([ - ["create", - "lecture" - ], - ["create", - "lecture_user_join", { - user_id: 1, - lecture_id: 1 - } - ], - ["create", - "tutorial", { - lecture_id: 1, - tutor_ids: [1] - } - ], - ["create", - "assignment", { - lecture_id: 1 - } - ], - ["create", - "assignment", "inactive", { - lecture_id: 1 - } - ], - ["create", "submission", "with_users", "with_manuscript", { - assignment_id: 1, - tutorial_id: 1 - }], - ["create", "submission", "with_users", "with_manuscript", { - assignment_id: 2, - tutorial_id: 1 - }] - ]).then((lectures) => { - cy.visit(`lectures/${lectures[0].id}`); - cy.contains("Tutorien").click(); - cy.contains("Tutorien").should("exist"); - cy.contains("Achtung").should("exist"); - cy.contains(lectures[3].title).click(); - cy.contains(lectures[4].title).click(); - cy.contains("Akzeptieren").click(); - cy.reload(); - cy.get(".correction-column").contains("Hochladen").click(); - const yourFixturePath = 'cypress/fixtures/files/manuscript.pdf'; - cy.get(`#upload-correction-${lectures[6].id}`).selectFile(yourFixturePath,{force:true}); - cy.contains("Upload").click(); - cy.get('.correction-upload > .mt-2 > .col-12 > .btn-primary').contains("Speichern").click(); - cy.reload(); - cy.get('.correction-action-area > [data-turbolinks="false"]').should("exist"); - }); - }); - it("can move submission", () => { - cy.appFactories([ - ["create", - "lecture" - ], - ["create", - "lecture_user_join", { - user_id: 1, - lecture_id: 1 - } - ], - ["create", - "tutorial", { - lecture_id: 1, - tutor_ids: [1] - } - ], - ["create", - "tutorial", { - lecture_id: 1 - } - ], - ["create", - "assignment", { - lecture_id: 1 - } - ], - ["create", "submission", "with_users", "with_manuscript", { - assignment_id: 1, - tutorial_id: 1 - }] - ]).then((lectures) => { - cy.visit(`lectures/${lectures[0].id}`); - cy.contains("Tutorien").click(); - cy.contains("Verschieben").click(); - cy.get('.ts-control').type(lectures[3].title); - cy.get('.ts-control').type('{enter}'); - cy.get('.submission-actions > form > .mt-2 > .col-12 > .btn-primary').contains("Speichern").should("exist"); - cy.get('.submission-actions > form > .mt-2 > .col-12 > .btn-primary').contains("Speichern").click(); - cy.get('.submission-actions > form > .mt-2 > .col-12 > .btn-primary').contains("Speichern").click(); - cy.reload(); - cy.contains("Zu dieser Hausaufgabe liegen in diesem Tutorium keine Abgaben vor.").should("exist"); - }); - }); + it("can view tutorials if tutor", () => { + cy.appFactories([ + ["create", + "lecture", { + teacher_id: 1, + }, + ], + ["create", + "lecture_user_join", { + user_id: 1, + lecture_id: 1, + }, + ], + ["create", + "tutorial", { + lecture_id: 1, + tutor_ids: [1], + }, + ], + ["create", + "tutorial", "with_tutors", { + lecture_id: 1, + }, + ], + ["create", + "assignment", { + lecture_id: 1, + }, + ], + ["create", "submission", "with_users", "with_manuscript", "with_correction", { + assignment_id: 1, + tutorial_id: 1, + }], + ]).then((lectures) => { + cy.visit(`lectures/${lectures[0].id}`); + cy.contains("Tutorien").should("exist"); + cy.contains("Tutorien").click(); + cy.get("main").contains("Übersicht").should("exist"); + cy.get("main").contains("Übersicht").click(); + cy.get("main > .dropdown > .btn").contains("Tutorien").click(); + cy.contains("Eigene Tutorien").should("exist"); + cy.contains("Sonstige Tutorien").should("exist"); + cy.get(".dropdown-menu").contains(lectures[2].title).should("exist"); + cy.get(".dropdown-menu").contains(lectures[3].title).should("exist"); + }); }); -}); \ No newline at end of file + it("can create correction if tutor", () => { + cy.appFactories([ + ["create", + "lecture", { + teacher_id: 1, + }, + ], + ["create", + "lecture_user_join", { + user_id: 1, + lecture_id: 1, + }, + ], + ["create", + "tutorial", { + lecture_id: 1, + tutor_ids: [1], + }, + ], + ["create", + "assignment", "inactive", { + lecture_id: 1, + }, + ], + ["create", + "assignment", { + lecture_id: 1, + }, + ], + ["create", "submission", "with_users", "with_manuscript", { + assignment_id: 1, + tutorial_id: 1, + }], + ["create", "submission", "with_users", "with_manuscript", { + assignment_id: 2, + tutorial_id: 1, + }], + ]).then((lectures) => { + cy.visit(`lectures/${lectures[0].id}`); + cy.contains("Tutorien").click(); + cy.contains("Achtung").should("exist"); + cy.contains(lectures[4].title).click(); + cy.contains(lectures[3].title).click(); + cy.contains("Akzeptieren").click(); + cy.reload(); + cy.get(".correction-column").contains("Hochladen").click(); + const yourFixturePath = "cypress/fixtures/files/manuscript.pdf"; + cy.get(".correction-column").contains("Datei").click(); + cy.get(`#upload-correction-${lectures[5].id}`).selectFile(yourFixturePath, { force: true }); + cy.contains("Upload").click(); + cy.get(".correction-upload > .mt-2 > .col-12 > .btn-primary").contains("Speichern").click(); + cy.reload(); + cy.get('.correction-action-area > [data-turbolinks="false"]').should("exist"); + }); + }); + it("can move submission if tutor", () => { + cy.appFactories([ + ["create", + "lecture", { + teacher_id: 1, + }, + ], + ["create", + "lecture_user_join", { + user_id: 1, + lecture_id: 1, + }, + ], + ["create", + "tutorial", { + lecture_id: 1, + tutor_ids: [1], + }, + ], + ["create", + "tutorial", { + lecture_id: 1, + }, + ], + ["create", + "assignment", { + lecture_id: 1, + }, + ], + ["create", "submission", "with_users", "with_manuscript", { + assignment_id: 1, + tutorial_id: 1, + }], + ["create", "submission", "with_users", "with_manuscript", { + assignment_id: 1, + tutorial_id: 2, + }], + ]).then((lectures) => { + cy.visit(`lectures/${lectures[0].id}`); + cy.contains("Tutorien").click(); + cy.contains("Verschieben").click(); + cy.get(".ts-control").type(lectures[3].title); + cy.get(".ts-control").type("{enter}"); + cy.get(".submission-actions > form > .mt-2 > .col-12 > .btn-primary").contains("Speichern").should("exist"); + cy.get(".submission-actions > form > .mt-2 > .col-12 > .btn-primary").contains("Speichern").click(); + cy.get(".submission-actions > form > .mt-2 > .col-12 > .btn-primary").contains("Speichern").click(); + cy.reload(); + cy.contains("Zu dieser Hausaufgabe liegen in diesem Tutorium keine Abgaben vor.").should("exist"); + }); + }); + }); + describe("Tutor", () => { + beforeEach(() => { + cy.app("clean"); + cy.appScenario("non_admin"); + cy.visit("/users/sign_in"); + cy.get('input[type="email"]').type("max@mampf.edu"); + cy.get('input[type="password"]').type("test123456"); + cy.get('input[type="submit"]').click(); + }); + it("can upload correction if assignment inactive", () => { + cy.appFactories([ + ["create", + "lecture", + ], + ["create", + "lecture_user_join", { + user_id: 1, + lecture_id: 1, + }, + ], + ["create", + "tutorial", { + lecture_id: 1, + tutor_ids: [1], + }, + ], + ["create", + "assignment", { + lecture_id: 1, + }, + ], + ["create", + "assignment", "inactive", { + lecture_id: 1, + }, + ], + ["create", "submission", "with_users", "with_manuscript", { + assignment_id: 1, + tutorial_id: 1, + }], + ["create", "submission", "with_users", "with_manuscript", { + assignment_id: 2, + tutorial_id: 1, + }], + ]).then((lectures) => { + cy.visit(`lectures/${lectures[0].id}`); + cy.contains("Tutorien").click(); + cy.contains("Tutorien").should("exist"); + cy.contains("Achtung").should("exist"); + cy.contains(lectures[3].title).click(); + cy.contains(lectures[4].title).click(); + cy.contains("Akzeptieren").click(); + cy.reload(); + cy.get(".correction-column").contains("Hochladen").click(); + const yourFixturePath = "cypress/fixtures/files/manuscript.pdf"; + cy.get(`#upload-correction-${lectures[6].id}`).selectFile(yourFixturePath, { force: true }); + cy.contains("Upload").click(); + cy.get(".correction-upload > .mt-2 > .col-12 > .btn-primary").contains("Speichern").click(); + cy.reload(); + cy.get('.correction-action-area > [data-turbolinks="false"]').should("exist"); + }); + }); + it("can move submission", () => { + cy.appFactories([ + ["create", + "lecture", + ], + ["create", + "lecture_user_join", { + user_id: 1, + lecture_id: 1, + }, + ], + ["create", + "tutorial", { + lecture_id: 1, + tutor_ids: [1], + }, + ], + ["create", + "tutorial", { + lecture_id: 1, + }, + ], + ["create", + "assignment", { + lecture_id: 1, + }, + ], + ["create", "submission", "with_users", "with_manuscript", { + assignment_id: 1, + tutorial_id: 1, + }], + ]).then((lectures) => { + cy.visit(`lectures/${lectures[0].id}`); + cy.contains("Tutorien").click(); + cy.contains("Verschieben").click(); + cy.get(".ts-control").type(lectures[3].title); + cy.get(".ts-control").type("{enter}"); + cy.get(".submission-actions > form > .mt-2 > .col-12 > .btn-primary").contains("Speichern").should("exist"); + cy.get(".submission-actions > form > .mt-2 > .col-12 > .btn-primary").contains("Speichern").click(); + cy.get(".submission-actions > form > .mt-2 > .col-12 > .btn-primary").contains("Speichern").click(); + cy.reload(); + cy.contains("Zu dieser Hausaufgabe liegen in diesem Tutorium keine Abgaben vor.").should("exist"); + }); + }); + }); +}); diff --git a/spec/cypress/e2e/watchlists_spec.cy.js b/spec/cypress/e2e/watchlists_spec.cy.js index f9d773671..cd12c277f 100644 --- a/spec/cypress/e2e/watchlists_spec.cy.js +++ b/spec/cypress/e2e/watchlists_spec.cy.js @@ -1,209 +1,208 @@ describe("Watchlists", () => { - + beforeEach(() => { + cy.app("clean"); + }); + describe("User", () => { beforeEach(() => { - cy.app("clean"); + cy.app("clean"); + cy.appScenario("non_admin"); + cy.visit("/users/sign_in"); + cy.get('input[type="email"]').type("max@mampf.edu"); + cy.get('input[type="password"]').type("test123456"); + cy.get('input[type="submit"]').click(); }); - describe("User", () => { - beforeEach(() => { - cy.app("clean"); - cy.appScenario("non_admin"); - cy.visit("/users/sign_in"); - cy.get('input[type="email"]').type("max@mampf.edu"); - cy.get('input[type="password"]').type("test123456"); - cy.get('input[type="submit"]').click(); - }); - // it("can create and add to watchlist in lecture", () => { - // cy.appFactories([ - // ["create", "lecture_medium", "with_manuscript", "released"], - // ["create", - // "lecture_user_join", { - // user_id: 1, - // lecture_id: 1 - // } - // ] - // ]).then((data) => { - // cy.visit(`lectures/${data[0].id}`); - // cy.get('.nav > :nth-child(6) > .nav-link').click(); - // cy.get('div.text-light > .fa-list').click(); - // cy.get('#openNewWatchlistForm').click(); - // cy.get('#watchlistNameField').type('Lernliste'); - // cy.get(100); - // cy.get('#createWatchlistBtn').click(); - // cy.get('#watchlistEntrySubmitButton').click(); - // cy.wait(100); - // cy.get('div.text-light > .fa-list').click(); - // cy.get('#watchlistEntrySubmitButton').click(); - // cy.get('.invalid-feedback').should('exist'); - // cy.wait(200); - // cy.get('.close > span').click(); - // cy.wait(100); - // cy.get('#watchlistsIcon').click(); - // cy.get('#card-title').should('exist'); - // }); - // }); - it("can change watchlist", () => { - cy.appFactories([ - ["create", "watchlist", { - user_id: 1 - }] - ]).then((data) => { - cy.visit(`watchlists/1`); - cy.get('#changeWatchlistBtn').click(); - cy.get('#watchlistNameField').should('have.value', `${data[0].name}`); - cy.get('#watchlistNameField').clear(); - cy.wait(500); - cy.get('#watchlistNameField').type('Lernliste'); - cy.get('#watchlistDescriptionField').type('Dies ist eine Lernliste.'); - cy.get('#confirmChangeWatchlistButton').click(); - cy.wait(100); - cy.get('#watchlistButton').contains('Lernliste'); - cy.get('#descriptionButton').click(); - cy.get('.card').contains('Dies ist eine Lernliste.') - }); - }) - it("can create new watchlist in watchlist view", () => { - cy.appFactories([ - ]).then((data) => { - cy.get('#watchlistsIcon').click(); - cy.get('#openNewWatchlistForm').click(); - cy.get('#watchlistNameField').type('Lernliste'); - cy.get('#newWatchlistButton').click(); - cy.wait(100); - cy.get('#watchlistButton').contains('Lernliste'); - }); - }); - it("can use bookmark", () => { - cy.appFactories([ - ["create", "watchlist", { - user_id: 1 - }], - ["create", "watchlist_entry", "with_medium", { - watchlist_id: 1 - }], - ["create", - "lecture_user_join", { - user_id: 1, - lecture_id: 1 - } - ] - ]).then((data) => { - cy.visit('lectures/1'); - cy.get('#watchlistsIcon').click(); - cy.get('#watchlistButton').contains(`${data[0].name}`).should('exist'); - }); - }); - it("can change visibility of watchlist", () => { - cy.appFactories([ - ["create", "watchlist", { - user_id: 1 - }] - ]).then((data) => { - cy.visit(`watchlists/1`); - cy.get('#watchlistVisiblityCheck').should('not.be.checked'); - cy.get('#watchlistVisiblityCheck').click(); - cy.reload(); - cy.get('#watchlistVisiblityCheck').should('be.checked'); - }); - }); - it("can view public watchlist of other user", () => { - cy.appFactories([ - ["create", "watchlist", "with_user", { - public: true - }] - ]).then((data) => { - cy.visit(`watchlists/1`); - cy.get('#watchlistButton').should('exist'); - }); - }); - it("can not view private watchlist of other user", () => { - cy.appFactories([ - ["create", "watchlist", "with_user", { - public: false - }] - ]).then((data) => { - cy.visit(`watchlists/1`); - cy.get(':nth-child(3) > .row > .col-12 > :nth-child(2)').contains('Du bist nicht berechtigt').should('exist'); - }); - }); - it("can filter watchlist_entries", () => { - cy.appFactories([ - ["create", "watchlist", { - user_id: 1 - }], - ["create_list", "watchlist_entry", 5, "with_medium", { - watchlist_id: 1 - }] - ]).then((data) => { - cy.get('#watchlistsIcon').click(); - cy.get('#reverseButton').click(); - cy.wait(100); - cy.get('#perPageButton').click(); - cy.get('[href="/watchlists/1?page=1&per=3&reverse=true"]').click(); - cy.wait(100); - cy.get('#allButton').click(); - cy.get('#watchlistButton').should('exist'); - cy.get('.active > .page-link').should('not.exist'); - }); - }); - it("can drag and drop watchlist_entry", () => { - cy.appFactories([ - ["create", "watchlist", { - user_id: 1 - }], - ["create", "watchlist_entry", "with_medium", { - watchlist_id: 1 - }], - ["create", "watchlist_entry", "with_medium", { - watchlist_id: 1 - }], - ["create", "watchlist_entry", "with_medium", { - watchlist_id: 1 - }] - ]).then((data) => { - cy.get('#watchlistsIcon').click(); - cy.get(':nth-child(1) > .card > .card-header').trigger('mousedown', { which: 1 }); - cy.wait(100); - cy.get(':nth-child(3) > .card > .card-header').trigger('mousemove', 0, 0); - cy.wait(100); - cy.get(':nth-child(1) > .card > .card-header').trigger('mouseup'); - cy.reload(); - cy.wait(100); - cy.get(':nth-child(1) > .card > .card-header > :nth-child(1) > #card-title').contains(`${data[2].medium_id}`).should('exist'); - }); - }); - it("can delete watchlist_entry from watchlist", () => { - cy.appFactories([ - ["create", "watchlist", { - user_id: 1 - }], - ["create", "watchlist_entry", "with_medium", { - watchlist_id: 1 - }] - ]).then((data) => { - cy.get('#watchlistsIcon').click(); - cy.get('div.text-light > .fas').click(); - cy.get('.alert-secondary').should('exist'); - cy.get('#watchlistButton').contains(`${data[0].name}`).should('exist'); - }); - }); - it("can delete watchlist", () => { - cy.appFactories([ - ["create", "lecture_medium", "released"], - ["create", "watchlist", { - user_id: 1 - }], - ["create", "watchlist", { - user_id: 1 - }] - ]).then((data) => { - cy.get('#watchlistsIcon').click(); - cy.contains(`${data[1].name}`).click(); - cy.contains(`${data[2].name}`).click(); - cy.wait(100); - cy.get('#deleteWatchlistBtn').click(); - cy.contains(`${data[1].name}`).should('exist'); - cy.get('.alert-secondary').should('exist'); - }); - }); + // it("can create and add to watchlist in lecture", () => { + // cy.appFactories([ + // ["create", "lecture_medium", "with_manuscript", "released"], + // ["create", + // "lecture_user_join", { + // user_id: 1, + // lecture_id: 1 + // } + // ] + // ]).then((data) => { + // cy.visit(`lectures/${data[0].id}`); + // cy.get('.nav > :nth-child(6) > .nav-link').click(); + // cy.get('div.text-light > .fa-list').click(); + // cy.get('#openNewWatchlistForm').click(); + // cy.get('#watchlistNameField').type('Lernliste'); + // cy.get(100); + // cy.get('#createWatchlistBtn').click(); + // cy.get('#watchlistEntrySubmitButton').click(); + // cy.wait(100); + // cy.get('div.text-light > .fa-list').click(); + // cy.get('#watchlistEntrySubmitButton').click(); + // cy.get('.invalid-feedback').should('exist'); + // cy.wait(200); + // cy.get('.close > span').click(); + // cy.wait(100); + // cy.get('#watchlistsIcon').click(); + // cy.get('#card-title').should('exist'); + // }); + // }); + it("can change watchlist", () => { + cy.appFactories([ + ["create", "watchlist", { + user_id: 1, + }], + ]).then((data) => { + cy.visit("watchlists/1"); + cy.get("#changeWatchlistBtn").click(); + cy.get("#watchlistNameField").should("have.value", `${data[0].name}`); + cy.get("#watchlistNameField").clear(); + cy.wait(500); + cy.get("#watchlistNameField").type("Lernliste"); + cy.get("#watchlistDescriptionField").type("Dies ist eine Lernliste."); + cy.get("#confirmChangeWatchlistButton").click(); + cy.wait(100); + cy.get("#watchlistButton").contains("Lernliste"); + cy.get("#descriptionButton").click(); + cy.get(".card").contains("Dies ist eine Lernliste."); + }); }); -}); \ No newline at end of file + it("can create new watchlist in watchlist view", () => { + cy.appFactories([ + ]).then((data) => { + cy.get("#watchlistsIcon").click(); + cy.get("#openNewWatchlistForm").click(); + cy.get("#watchlistNameField").type("Lernliste"); + cy.get("#newWatchlistButton").click(); + cy.wait(100); + cy.get("#watchlistButton").contains("Lernliste"); + }); + }); + it("can use bookmark", () => { + cy.appFactories([ + ["create", "watchlist", { + user_id: 1, + }], + ["create", "watchlist_entry", "with_medium", { + watchlist_id: 1, + }], + ["create", + "lecture_user_join", { + user_id: 1, + lecture_id: 1, + }, + ], + ]).then((data) => { + cy.visit("lectures/1"); + cy.get("#watchlistsIcon").click(); + cy.get("#watchlistButton").contains(`${data[0].name}`).should("exist"); + }); + }); + it("can change visibility of watchlist", () => { + cy.appFactories([ + ["create", "watchlist", { + user_id: 1, + }], + ]).then((data) => { + cy.visit("watchlists/1"); + cy.get("#watchlistVisiblityCheck").should("not.be.checked"); + cy.get("#watchlistVisiblityCheck").click(); + cy.reload(); + cy.get("#watchlistVisiblityCheck").should("be.checked"); + }); + }); + it("can view public watchlist of other user", () => { + cy.appFactories([ + ["create", "watchlist", "with_user", { + public: true, + }], + ]).then((data) => { + cy.visit("watchlists/1"); + cy.get("#watchlistButton").should("exist"); + }); + }); + it("can not view private watchlist of other user", () => { + cy.appFactories([ + ["create", "watchlist", "with_user", { + public: false, + }], + ]).then((data) => { + cy.visit("watchlists/1"); + cy.get(":nth-child(3) > .row > .col-12 > :nth-child(2)").contains("Du bist nicht berechtigt").should("exist"); + }); + }); + it("can filter watchlist_entries", () => { + cy.appFactories([ + ["create", "watchlist", { + user_id: 1, + }], + ["create_list", "watchlist_entry", 5, "with_medium", { + watchlist_id: 1, + }], + ]).then((data) => { + cy.get("#watchlistsIcon").click(); + cy.get("#reverseButton").click(); + cy.wait(100); + cy.get("#perPageButton").click(); + cy.get('[href="/watchlists/1?page=1&per=3&reverse=true"]').click(); + cy.wait(100); + cy.get("#allButton").click(); + cy.get("#watchlistButton").should("exist"); + cy.get(".active > .page-link").should("not.exist"); + }); + }); + it("can drag and drop watchlist_entry", () => { + cy.appFactories([ + ["create", "watchlist", { + user_id: 1, + }], + ["create", "watchlist_entry", "with_medium", { + watchlist_id: 1, + }], + ["create", "watchlist_entry", "with_medium", { + watchlist_id: 1, + }], + ["create", "watchlist_entry", "with_medium", { + watchlist_id: 1, + }], + ]).then((data) => { + cy.get("#watchlistsIcon").click(); + cy.get(":nth-child(1) > .card > .card-header").trigger("mousedown", { which: 1 }); + cy.wait(100); + cy.get(":nth-child(3) > .card > .card-header").trigger("mousemove", 0, 0); + cy.wait(100); + cy.get(":nth-child(1) > .card > .card-header").trigger("mouseup"); + cy.reload(); + cy.wait(100); + cy.get(":nth-child(1) > .card > .card-header > :nth-child(1) > #card-title").contains(`${data[2].medium_id}`).should("exist"); + }); + }); + it("can delete watchlist_entry from watchlist", () => { + cy.appFactories([ + ["create", "watchlist", { + user_id: 1, + }], + ["create", "watchlist_entry", "with_medium", { + watchlist_id: 1, + }], + ]).then((data) => { + cy.get("#watchlistsIcon").click(); + cy.get("div.text-light > .fas").click(); + cy.get(".alert-secondary").should("exist"); + cy.get("#watchlistButton").contains(`${data[0].name}`).should("exist"); + }); + }); + it("can delete watchlist", () => { + cy.appFactories([ + ["create", "lecture_medium", "released"], + ["create", "watchlist", { + user_id: 1, + }], + ["create", "watchlist", { + user_id: 1, + }], + ]).then((data) => { + cy.get("#watchlistsIcon").click(); + cy.contains(`${data[1].name}`).click(); + cy.contains(`${data[2].name}`).click(); + cy.wait(100); + cy.get("#deleteWatchlistBtn").click(); + cy.contains(`${data[1].name}`).should("exist"); + cy.get(".alert-secondary").should("exist"); + }); + }); + }); +}); diff --git a/spec/cypress/support/e2e.js b/spec/cypress/support/e2e.js index 3f5a4b829..06682f943 100644 --- a/spec/cypress/support/e2e.js +++ b/spec/cypress/support/e2e.js @@ -1,2 +1,2 @@ -import './commands' -import './on-rails' \ No newline at end of file +import "./commands"; +import "./on-rails"; diff --git a/spec/cypress/support/index.js b/spec/cypress/support/index.js index d19205882..4253b2efb 100644 --- a/spec/cypress/support/index.js +++ b/spec/cypress/support/index.js @@ -14,8 +14,8 @@ // *********************************************************** // Import commands.js using ES2015 syntax: -import './commands'; -import './on-rails'; +import "./commands"; +import "./on-rails"; // Alternatively you can use CommonJS syntax: // require('./commands') diff --git a/spec/cypress/support/on-rails.js b/spec/cypress/support/on-rails.js index 337ae52da..cda668f24 100644 --- a/spec/cypress/support/on-rails.js +++ b/spec/cypress/support/on-rails.js @@ -1,37 +1,37 @@ // CypressOnRails: dont remove these command -Cypress.Commands.add('appCommands', function (body) { +Cypress.Commands.add("appCommands", function (body) { cy.log("APP: " + JSON.stringify(body)); return cy.request({ - method: 'POST', + method: "POST", url: "/__cypress__/command", body: JSON.stringify(body), log: true, - failOnStatusCode: true + failOnStatusCode: true, }).then((response) => { return response.body; }); }); -Cypress.Commands.add('app', function (name, command_options) { - return cy.appCommands({name: name, options: command_options}).then((body) => { +Cypress.Commands.add("app", function (name, command_options) { + return cy.appCommands({ name: name, options: command_options }).then((body) => { return body[0]; }); }); -Cypress.Commands.add('appScenario', function (name, options = {}) { - return cy.app('scenarios/' + name, options); +Cypress.Commands.add("appScenario", function (name, options = {}) { + return cy.app("scenarios/" + name, options); }); -Cypress.Commands.add('appEval', function (code) { - return cy.app('eval', code); +Cypress.Commands.add("appEval", function (code) { + return cy.app("eval", code); }); -Cypress.Commands.add('appFactories', function (options) { - return cy.app('factory_bot', options); +Cypress.Commands.add("appFactories", function (options) { + return cy.app("factory_bot", options); }); -Cypress.Commands.add('appFixtures', function (options) { - cy.app('activerecord_fixtures', options); +Cypress.Commands.add("appFixtures", function (options) { + cy.app("activerecord_fixtures", options); }); Cypress.Commands.add("dragTo", { prevSubject: "element" }, (subject, targetEl) => { @@ -46,13 +46,13 @@ Cypress.Commands.add("dragTo", { prevSubject: "element" }, (subject, targetEl) = // }); // comment this out if you do not want to attempt to log additional info on test fail -Cypress.on('fail', (err, runnable) => { +Cypress.on("fail", (err, runnable) => { // allow app to generate additional logging data Cypress.$.ajax({ - url: '/__cypress__/command', - data: JSON.stringify({name: 'log_fail', options: {error_message: err.message, runnable_full_title: runnable.fullTitle() }}), + url: "/__cypress__/command", + data: JSON.stringify({ name: "log_fail", options: { error_message: err.message, runnable_full_title: runnable.fullTitle() } }), async: false, - method: 'POST' + method: "POST", }); throw err; From cedd0c429e6485437606ca00d8aed7222c0720a0 Mon Sep 17 00:00:00 2001 From: Splines Date: Wed, 3 Jan 2024 14:53:26 +0100 Subject: [PATCH 41/57] Fix variables in turbolink fix --- .../javascripts/_selectize_turbolinks_fix.js | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/app/assets/javascripts/_selectize_turbolinks_fix.js b/app/assets/javascripts/_selectize_turbolinks_fix.js index 9e5b9b4e8..dfe418cf2 100644 --- a/app/assets/javascripts/_selectize_turbolinks_fix.js +++ b/app/assets/javascripts/_selectize_turbolinks_fix.js @@ -25,9 +25,16 @@ resetSelectized = function (index, select) { } }; -this.fillOptionsByAjax = function ($selectizedSelection) { +function fillOptionsByAjax($selectizedSelection) { + // TODO: this function definitely needs some refactoring $selectizedSelection.each(function () { - var courseId, existing_values, fill_path, loaded, locale, model_select, plugins, send_data, parent; + let plugins = []; + let send_data = false; + let fill_path = ""; + let courseId = 0; + let loaded = false; + let locale = null; + if (this.dataset.drag === "true") { plugins = ["remove_button", "drag_drop"]; } @@ -35,16 +42,12 @@ this.fillOptionsByAjax = function ($selectizedSelection) { plugins = ["remove_button"]; } if (this.dataset.ajax === "true" && this.dataset.filled === "false") { - model_select = this; + const model_select = this; courseId = 0; - placeholder = this.dataset.placeholder; - no_result_msg = this.dataset.noResults; - existing_values = Array.apply(null, model_select.options).map(function (o) { - return o.value; - }); + const placeholder = this.dataset.placeholder; + const no_result_msg = this.dataset.noResults; send_data = false; loaded = false; - parent = this.dataset.modal === undefined ? document.body : null; if (this.dataset.model === "tag") { locale = this.dataset.locale; fill_path = Routes.fill_tag_select_path({ @@ -130,7 +133,7 @@ this.fillOptionsByAjax = function ($selectizedSelection) { }); } }); -}; +} $(document).on("turbolinks:before-cache", function () { $(".tomselected").each(resetSelectized); From f8366aba1b1bbddb36d74b47c280a548b48fd8a7 Mon Sep 17 00:00:00 2001 From: Splines Date: Wed, 3 Jan 2024 15:07:07 +0100 Subject: [PATCH 42/57] Prepend unused variables with "_" --- .eslintrc.js | 2 +- app/assets/javascripts/datetimepicker.js | 2 +- app/views/assignments/new.js.erb | 2 +- app/views/submissions/select_tutorial.js.erb | 2 +- spec/cypress/e2e/courses_spec.cy.js | 6 +++--- spec/cypress/e2e/submissions_spec.cy.js | 2 +- spec/cypress/e2e/watchlists_spec.cy.js | 10 +++++----- spec/cypress/plugins/index.js | 2 +- 8 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 115d0fea5..da7dbdaeb 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -70,7 +70,7 @@ module.exports = { plugins: ["@stylistic", "erb", "cypress"], rules: { ...customizedStylistic.rules, - "no-unused-vars": "warn", + "no-unused-vars": ["warn", { argsIgnorePattern: "^_" }], ...cypressRules, // see https://github.com/eslint-stylistic/eslint-stylistic/issues/254 "@stylistic/quotes": ["error", "double", { avoidEscape: true }], diff --git a/app/assets/javascripts/datetimepicker.js b/app/assets/javascripts/datetimepicker.js index 893da4156..9d6376d70 100644 --- a/app/assets/javascripts/datetimepicker.js +++ b/app/assets/javascripts/datetimepicker.js @@ -103,7 +103,7 @@ function registerFocusHandlers(datetimePicker, element) { // or when input field receives focus var isButtonInvokingFocus = false; - element.find(".td-input").on("click focusin", (e) => { + element.find(".td-input").on("click focusin", (_e) => { try { if (!isButtonInvokingFocus) { datetimePicker.show(); diff --git a/app/views/assignments/new.js.erb b/app/views/assignments/new.js.erb index a0daee451..3f1853678 100644 --- a/app/views/assignments/new.js.erb +++ b/app/views/assignments/new.js.erb @@ -9,7 +9,7 @@ new TomSelect("#assignment_medium_id_", { direction: "asc", }, render: { - no_results: function (data, escape) { + no_results: function (_data, _escape) { return '
<%= t("basics.no_results") %>
'; }, }, diff --git a/app/views/submissions/select_tutorial.js.erb b/app/views/submissions/select_tutorial.js.erb index 0d17f9b67..ad613dc6f 100644 --- a/app/views/submissions/select_tutorial.js.erb +++ b/app/views/submissions/select_tutorial.js.erb @@ -10,7 +10,7 @@ new TomSelect("#submission_tutorial_id-<%= @submission.id %>", { direction: "asc", }, render: { - no_results: function (data, escape) { + no_results: function (_data, _escape) { return '
<%= t("basics.no_results") %>
'; }, }, diff --git a/spec/cypress/e2e/courses_spec.cy.js b/spec/cypress/e2e/courses_spec.cy.js index f8e9cb3e3..833a7bb92 100644 --- a/spec/cypress/e2e/courses_spec.cy.js +++ b/spec/cypress/e2e/courses_spec.cy.js @@ -14,7 +14,7 @@ describe("Courses", function () { it("can add tag to course", () => { cy.appFactories([ ["create", "course"], - ]).then((records) => { + ]).then((_records) => { cy.visit("/courses/1/edit"); cy.get("#new-tag-button").click(); cy.get("#tag_notions_attributes_0_title").type("Geometrie"); @@ -28,7 +28,7 @@ describe("Courses", function () { it("can set editor in course", () => { cy.appFactories([ ["create", "course"], - ]).then((records) => { + ]).then((_records) => { cy.visit("/courses/1/edit"); cy.get("#course_editor_ids-ts-control").click(); cy.get("#course_editor_ids-ts-control").type("ad"); @@ -118,7 +118,7 @@ describe("Courses", function () { cy.appFactories([ ["create_list", "lecture", 6, "released_for_all"], - ]).then((records) => { + ]).then((_records) => { cy.visit("/main/start"); // cy.get('input[name="search[fulltext]"]').type(records[0][0].title) cy.contains("Veranstaltungssuche").click(); diff --git a/spec/cypress/e2e/submissions_spec.cy.js b/spec/cypress/e2e/submissions_spec.cy.js index a59dab957..adcd0b5f6 100644 --- a/spec/cypress/e2e/submissions_spec.cy.js +++ b/spec/cypress/e2e/submissions_spec.cy.js @@ -70,7 +70,7 @@ describe("Submissions", () => { user_id: 1, lecture_id: 1, }], - ]).then((lectures) => {}); + ]).then((_lectures) => {}); }); it("can create submission", () => { cy.appFactories([ diff --git a/spec/cypress/e2e/watchlists_spec.cy.js b/spec/cypress/e2e/watchlists_spec.cy.js index cd12c277f..2513d3787 100644 --- a/spec/cypress/e2e/watchlists_spec.cy.js +++ b/spec/cypress/e2e/watchlists_spec.cy.js @@ -62,7 +62,7 @@ describe("Watchlists", () => { }); it("can create new watchlist in watchlist view", () => { cy.appFactories([ - ]).then((data) => { + ]).then((_data) => { cy.get("#watchlistsIcon").click(); cy.get("#openNewWatchlistForm").click(); cy.get("#watchlistNameField").type("Lernliste"); @@ -96,7 +96,7 @@ describe("Watchlists", () => { ["create", "watchlist", { user_id: 1, }], - ]).then((data) => { + ]).then((_data) => { cy.visit("watchlists/1"); cy.get("#watchlistVisiblityCheck").should("not.be.checked"); cy.get("#watchlistVisiblityCheck").click(); @@ -109,7 +109,7 @@ describe("Watchlists", () => { ["create", "watchlist", "with_user", { public: true, }], - ]).then((data) => { + ]).then((_data) => { cy.visit("watchlists/1"); cy.get("#watchlistButton").should("exist"); }); @@ -119,7 +119,7 @@ describe("Watchlists", () => { ["create", "watchlist", "with_user", { public: false, }], - ]).then((data) => { + ]).then((_data) => { cy.visit("watchlists/1"); cy.get(":nth-child(3) > .row > .col-12 > :nth-child(2)").contains("Du bist nicht berechtigt").should("exist"); }); @@ -132,7 +132,7 @@ describe("Watchlists", () => { ["create_list", "watchlist_entry", 5, "with_medium", { watchlist_id: 1, }], - ]).then((data) => { + ]).then((_data) => { cy.get("#watchlistsIcon").click(); cy.get("#reverseButton").click(); cy.wait(100); diff --git a/spec/cypress/plugins/index.js b/spec/cypress/plugins/index.js index 8dd144a6c..3596c1897 100644 --- a/spec/cypress/plugins/index.js +++ b/spec/cypress/plugins/index.js @@ -15,7 +15,7 @@ /** * @type {Cypress.PluginConfig} */ -module.exports = (on, config) => { +module.exports = (_on, _config) => { // `on` is used to hook into various events Cypress emits // `config` is the resolved Cypress config }; From bc87b65e475849aa48480164f56ecd8eaba35384 Mon Sep 17 00:00:00 2001 From: Splines Date: Wed, 3 Jan 2024 15:08:33 +0100 Subject: [PATCH 43/57] Get rid of unused widget variable --- app/javascript/packs/application.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js index 9eeaf5b3a..0586df987 100644 --- a/app/javascript/packs/application.js +++ b/app/javascript/packs/application.js @@ -17,10 +17,9 @@ import { WidgetInstance, } from "friendly-challenge"; -var friendlyChallengeWidgetInstance = WidgetInstance; document.addEventListener("turbolinks:load", function () { - var doneCallback, element, options, widget; + var doneCallback, element, options; doneCallback = function (solution) { console.log(solution); @@ -40,7 +39,7 @@ document.addEventListener("turbolinks:load", function () { language: $("#captcha-widget").data("lang"), }; console.log(options); - widget = new WidgetInstance(element, options); + new WidgetInstance(element, options); // DO not uncomment, evil // widget.reset(); } From a8fc9726fb7611edb37aac3ef677decde8752ba4 Mon Sep 17 00:00:00 2001 From: Splines Date: Wed, 3 Jan 2024 15:11:07 +0100 Subject: [PATCH 44/57] Fix specs comment tab alignment --- spec/cypress/e2e/search_spec.cy.js | 22 +++++----- spec/cypress/e2e/watchlists_spec.cy.js | 58 +++++++++++++------------- 2 files changed, 40 insertions(+), 40 deletions(-) diff --git a/spec/cypress/e2e/search_spec.cy.js b/spec/cypress/e2e/search_spec.cy.js index e530b5316..c443b3c79 100644 --- a/spec/cypress/e2e/search_spec.cy.js +++ b/spec/cypress/e2e/search_spec.cy.js @@ -12,20 +12,20 @@ describe("Media", () => { cy.get('input[type="password"]').type("test123456"); cy.get('input[type="submit"]').click(); }); - /* it("can search released media",()=>{ - cy.appFactories([ - ["create","lesson_medium", "with_manuscript","released"], - ["create","lesson_medium", "with_manuscript"] - ]).then((records)=>{ - cy.get('#mediaSearchLink').click(); - cy.get('#collapseMediaSearch > .card-body > form > .row > .col-12 > .btn').click(); - cy.get('#media-search-results').get('.col-12 > .card').should('have.length',1); - }); - }); */ + /* it("can search released media", () => { + cy.appFactories([ + ["create", "lesson_medium", "with_manuscript", "released"], + ["create", "lesson_medium", "with_manuscript"], + ]).then((_records) => { + cy.get("#mediaSearchLink").click(); + cy.get("#collapseMediaSearch > .card-body > form > .row > .col-12 > .btn").click(); + cy.get("#media-search-results").get(".col-12 > .card").should("have.length", 1); + }); + }); */ it("can filter for tags", () => { cy.appFactories([ ["create", "lesson_medium", "with_manuscript", "released", "with_tags"], - ["create", "lesson_medium", "with_manuscript", "released"], + ["create", "lesson_medium", "with_manuscript", "released"], ]).then((records) => { cy.get("#mediaSearchLink").click(); cy.get("#media_fulltext").type(records[0].description); diff --git a/spec/cypress/e2e/watchlists_spec.cy.js b/spec/cypress/e2e/watchlists_spec.cy.js index 2513d3787..4f6cedaa0 100644 --- a/spec/cypress/e2e/watchlists_spec.cy.js +++ b/spec/cypress/e2e/watchlists_spec.cy.js @@ -11,35 +11,35 @@ describe("Watchlists", () => { cy.get('input[type="password"]').type("test123456"); cy.get('input[type="submit"]').click(); }); - // it("can create and add to watchlist in lecture", () => { - // cy.appFactories([ - // ["create", "lecture_medium", "with_manuscript", "released"], - // ["create", - // "lecture_user_join", { - // user_id: 1, - // lecture_id: 1 - // } - // ] - // ]).then((data) => { - // cy.visit(`lectures/${data[0].id}`); - // cy.get('.nav > :nth-child(6) > .nav-link').click(); - // cy.get('div.text-light > .fa-list').click(); - // cy.get('#openNewWatchlistForm').click(); - // cy.get('#watchlistNameField').type('Lernliste'); - // cy.get(100); - // cy.get('#createWatchlistBtn').click(); - // cy.get('#watchlistEntrySubmitButton').click(); - // cy.wait(100); - // cy.get('div.text-light > .fa-list').click(); - // cy.get('#watchlistEntrySubmitButton').click(); - // cy.get('.invalid-feedback').should('exist'); - // cy.wait(200); - // cy.get('.close > span').click(); - // cy.wait(100); - // cy.get('#watchlistsIcon').click(); - // cy.get('#card-title').should('exist'); - // }); - // }); + /* it("can create and add to watchlist in lecture", () => { + cy.appFactories([ + ["create", "lecture_medium", "with_manuscript", "released"], + ["create", + "lecture_user_join", { + user_id: 1, + lecture_id: 1, + }, + ], + ]).then((data) => { + cy.visit(`lectures/${data[0].id}`); + cy.get(".nav > :nth-child(6) > .nav-link").click(); + cy.get("div.text-light > .fa-list").click(); + cy.get("#openNewWatchlistForm").click(); + cy.get("#watchlistNameField").type("Lernliste"); + cy.get(100); + cy.get("#createWatchlistBtn").click(); + cy.get("#watchlistEntrySubmitButton").click(); + cy.wait(100); + cy.get("div.text-light > .fa-list").click(); + cy.get("#watchlistEntrySubmitButton").click(); + cy.get(".invalid-feedback").should("exist"); + cy.wait(200); + cy.get(".close > span").click(); + cy.wait(100); + cy.get("#watchlistsIcon").click(); + cy.get("#card-title").should("exist"); + }); + }); */ it("can change watchlist", () => { cy.appFactories([ ["create", "watchlist", { From f636d0fe322b28f02e5d427608cc05f7593cb0a6 Mon Sep 17 00:00:00 2001 From: Splines <37160523+Splines@users.noreply.github.com> Date: Thu, 11 Jan 2024 19:05:36 +0100 Subject: [PATCH 45/57] Warn about too long GitHub commit messages (#586) --- .vscode/settings.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.vscode/settings.json b/.vscode/settings.json index 21983ff1e..24b00c4b5 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -79,6 +79,12 @@ } ], ////////////////////////////////////// + // Git + ////////////////////////////////////// + "git.inputValidation": "warn", + "git.inputValidationSubjectLength": 50, + "git.inputValidationLength": 72, + ////////////////////////////////////// // Spell Checker ////////////////////////////////////// "cSpell.words": [ From eb9484a8106d15683c5028b55acb0d6798329c29 Mon Sep 17 00:00:00 2001 From: Splines <37160523+Splines@users.noreply.github.com> Date: Tue, 16 Jan 2024 19:15:51 +0100 Subject: [PATCH 46/57] Fix comment status (#585) * Reapply first fix for Reader/Media See discussion on #574 for further details. Previous PR for this was #576, closed in favor of this one as this directly branches off the new "dev" branch. * Correctly show latest post (might be current_user's comment) * Fix update of unread comments logic in comments controller * Fix update icon logic and latest post comment * Simplify latest comment logic * Improve code comments * Further improve comments * Fix wording in comment * Fix construction of media array & use `.blank?` instead of `.empty?` --- .../commontator/comments_controller.rb | 51 +++++++++++++------ app/controllers/main_controller.rb | 2 +- app/controllers/readers_controller.rb | 2 +- app/models/user.rb | 38 +++++++++++--- app/views/main/comments.html.erb | 4 +- 5 files changed, 70 insertions(+), 27 deletions(-) diff --git a/app/controllers/commontator/comments_controller.rb b/app/controllers/commontator/comments_controller.rb index 31058b3da..331919345 100644 --- a/app/controllers/commontator/comments_controller.rb +++ b/app/controllers/commontator/comments_controller.rb @@ -71,6 +71,7 @@ def create Commontator::Subscription.comment_created(@comment) # The next line constitutes a customization of the original controller update_unread_status + activate_unread_comments_icon_if_necessary @commontator_page = @commontator_thread.new_comment_page( @comment.parent_id, @commontator_show_all @@ -194,9 +195,14 @@ def subscribe_mentioned end end - # This method ensures that the unread_comments flag is updated - # for users affected by the creation of a newly created comment - # It constitues a customization + # Updates the unread_comments flag for users subscribed to the current thread. + # This method should only be called when a new comment was created. + # + # The originator of the comment does not get the flag set since that user + # already knows about the comment; that user has just created it after all. + # + # (This is a customization of the original controller provided + # by the commontator gem.) def update_unread_status medium = @commontator_thread.commontable return unless medium.released.in?(["all", "users", "subscribers"]) @@ -205,22 +211,35 @@ def update_unread_status relevant_users.where.not(id: current_user.id) .where(unread_comments: false) .update(unread_comments: true) - - # make sure that the thread associated to this comment is marked as read - # by the comment creator (unless some other user posted a comment in it - # that has not yet been read) - @reader = Reader.find_or_create_by(user: current_user, - thread: @commontator_thread) - if unseen_comments? - @update_icon = true - return - end - @reader.touch end - def unseen_comments? + # Might activate the flag used in the view to indicate unread comments. + # This method should only be called when a new comment was created. + # The flag is activated if the current user has not seen all comments + # in the thread in which the new comment was created. + # + # The flag might only be activated, not deactivated since the checks + # performed here are not sufficient to determine whether a user has + # any unread comments (including those in possibly other threads). + # + # This method was introduced for one specific edge case: + # When the current user A has just created a new comment in a thread, + # but in the meantime, another user B has created a comment in the same + # thread. User A will not be informed immediately about the new comment + # by B since we don't have websockets implemented. Instead, A will be + # informed by a visual indicator as soon as A has posted their own comment. + # + # (This is a customization of the original controller provided + # by the commontator gem.) + def activate_unread_comments_icon_if_necessary + reader = Reader.find_by(user: current_user, thread: @commontator_thread) + @update_icon = true if unseen_comments_in_current_thread?(reader) + end + + def unseen_comments_in_current_thread?(reader = nil) @commontator_thread.comments.any? do |c| - c.creator != current_user && c.created_at > @reader.updated_at + not_marked_as_read_in_reader = reader.nil? || c.created_at > reader.updated_at + c.creator != current_user && not_marked_as_read_in_reader end end end diff --git a/app/controllers/main_controller.rb b/app/controllers/main_controller.rb index 8fa45237c..c7e8d469b 100644 --- a/app/controllers/main_controller.rb +++ b/app/controllers/main_controller.rb @@ -26,7 +26,7 @@ def sponsors end def comments - @media_comments = current_user.media_latest_comments + @media_comments = current_user.subscribed_media_with_latest_comments_not_by_creator @media_comments.select! do |m| (Reader.find_by(user: current_user, thread: m[:thread]) &.updated_at || 1000.years.ago) < m[:latest_comment].created_at && diff --git a/app/controllers/readers_controller.rb b/app/controllers/readers_controller.rb index f276f2989..931b93a45 100644 --- a/app/controllers/readers_controller.rb +++ b/app/controllers/readers_controller.rb @@ -9,7 +9,7 @@ def update @reader = Reader.find_or_create_by(user: current_user, thread: @thread) @reader.touch - @anything_left = current_user.media_latest_comments.any? do |m| + @anything_left = current_user.subscribed_media_with_latest_comments_not_by_creator.any? do |m| (Reader.find_by(user: current_user, thread: m[:thread]) &.updated_at || 1000.years.ago) < m[:latest_comment].created_at end diff --git a/app/models/user.rb b/app/models/user.rb index 7745f2260..40feb6f60 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -549,14 +549,38 @@ def subscribed_commentable_media_with_comments .select { |m| m.commontator_thread.comments.any? } end - def media_latest_comments - media = subscribed_commentable_media_with_comments - .map do |m| - { medium: m, - thread: m.commontator_thread, - latest_comment: m.commontator_thread - .comments.max_by(&:created_at) } + # Returns the media that the user has subscribed to and that have been + # commented on by somebody else (not by the current user). The order is + # given by the time of the latest comment by somebody else. + # + # Media that have not been commented on by somebody else than the current user, + # are not returned (!). + # + # For each medium, the following information is stored: + # - the medium itself + # - the thread of the medium + # - the latest comment by somebody else than the current user + # - the latest comment by any user (which might include the current user) + def subscribed_media_with_latest_comments_not_by_creator + media = [] + + subscribed_commentable_media_with_comments.each do |m| + thread = m.commontator_thread + comments = thread.comments + next if comments.blank? + + comments_not_by_creator = comments.reject { |c| c.creator == self } + next if comments_not_by_creator.blank? + + latest_comment = comments_not_by_creator.max_by(&:created_at) + latest_comment_by_any_user = comments.max_by(&:created_at) + + media << { medium: m, + thread: thread, + latest_comment: latest_comment, + latest_comment_by_any_user: latest_comment_by_any_user } end + media.sort_by { |x| x[:latest_comment].created_at }.reverse end diff --git a/app/views/main/comments.html.erb b/app/views/main/comments.html.erb index 13e122bad..22f6e5ad4 100644 --- a/app/views/main/comments.html.erb +++ b/app/views/main/comments.html.erb @@ -68,8 +68,8 @@
<%= t('comments.who_and_when', - when: time_ago_in_words(m[:latest_comment].created_at), - who: m[:latest_comment].creator.name) %> + when: time_ago_in_words(m[:latest_comment_by_any_user].created_at), + who: m[:latest_comment_by_any_user].creator.name) %>
From 475fc0483821e89df8f2c1f73772c11b71920893 Mon Sep 17 00:00:00 2001 From: Splines <37160523+Splines@users.noreply.github.com> Date: Wed, 17 Jan 2024 23:21:59 +0100 Subject: [PATCH 47/57] Migrate `unread_comments` flag (fix inconsistencies) (#587) * Add dummy migration * Implement migration for unread comment flag * Remove unnecessary comment * Declare migration as not idempotent * Use array.length instead of counting * Throw error to prevent revert of migration * Fix severe flaws in unread comments migration * Simplify Reader retrieval * Use the more explicit `.nil?` method * Update migration date * Fix annoying bug: don't use `.select!` but `.select` * Polish migration e.g. update comment, more suitable name for the method etc. * Rename method according to #585 --- ...000_fix_unread_comments_inconsistencies.rb | 56 +++++++++++++++++++ db/schema.rb | 2 +- 2 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20240116180000_fix_unread_comments_inconsistencies.rb diff --git a/db/migrate/20240116180000_fix_unread_comments_inconsistencies.rb b/db/migrate/20240116180000_fix_unread_comments_inconsistencies.rb new file mode 100644 index 000000000..3392da3dc --- /dev/null +++ b/db/migrate/20240116180000_fix_unread_comments_inconsistencies.rb @@ -0,0 +1,56 @@ +# Fixes the unread_comments flag for all users. Unintended behavior was +# introduced in pull request #515. Migration introduced in #587. +# Behavior fixed in #585. +# +# This migration is generally *not* idempotent since users might have interacted +# with the website since the migration was run and thus they will probably have +# different unread comments flags as the ones at the time of the migration. +# +# This migration is not reversible as we don't store the previous state of +# the unread_comments flag. +class FixUnreadCommentsInconsistencies < ActiveRecord::Migration[7.0] + def up + num_fixed_users = 0 + + User.find_each do |user| + had_user_unread_comments = user.unread_comments # boolean + has_user_unread_comments = user_unread_comments?(user) + + has_flag_changed = (had_user_unread_comments != has_user_unread_comments) + user.update(unread_comments: has_user_unread_comments) if has_flag_changed + num_fixed_users += 1 if has_flag_changed + end + + Rails.logger.debug { "Ran through #{User.count} users (unread comments flag)" } + Rails.logger.debug { "Fixed #{num_fixed_users} users (unread comments flag)" } + end + + # Checks and returns whether the user has unread comments. + def user_unread_comments?(user) + # Check for unread comments -- directly via Reader + readers = Reader.where(user: user) + readers.each do |reader| + thread = Commontator::Thread.find_by(id: reader.thread_id) + next if thread.nil? + + latest_thread_comment_by_any_user = thread.comments.max_by(&:created_at) + next if latest_thread_comment_by_any_user.blank? + + latest_thread_comment_time = latest_thread_comment_by_any_user.created_at + has_user_unread_comments = reader.updated_at < latest_thread_comment_time + + return true if has_user_unread_comments + end + + # User might still have unread comments but no related Reader objects + # -> Check for unread comments -- via Media + unseen_media = user.subscribed_media_with_latest_comments_not_by_creator.select do |m| + m[:medium].visible_for_user?(user) + end + unseen_media.present? + end + + def down + raise ActiveRecord::IrreversibleMigration + end +end diff --git a/db/schema.rb b/db/schema.rb index 923aed6fb..0eb8c6842 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2023_11_01_100015) do +ActiveRecord::Schema[7.0].define(version: 2024_01_16_180000) do # These are extensions that must be enabled in order to support this database enable_extension "pgcrypto" enable_extension "plpgsql" From 92d48d4d03f1393fb017356384afee0b8cdb6ace Mon Sep 17 00:00:00 2001 From: Splines <37160523+Splines@users.noreply.github.com> Date: Fri, 19 Jan 2024 16:06:36 +0100 Subject: [PATCH 48/57] Use `warn` log level for migration (#588) --- .../20240116180000_fix_unread_comments_inconsistencies.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/db/migrate/20240116180000_fix_unread_comments_inconsistencies.rb b/db/migrate/20240116180000_fix_unread_comments_inconsistencies.rb index 3392da3dc..74ef88755 100644 --- a/db/migrate/20240116180000_fix_unread_comments_inconsistencies.rb +++ b/db/migrate/20240116180000_fix_unread_comments_inconsistencies.rb @@ -21,8 +21,8 @@ def up num_fixed_users += 1 if has_flag_changed end - Rails.logger.debug { "Ran through #{User.count} users (unread comments flag)" } - Rails.logger.debug { "Fixed #{num_fixed_users} users (unread comments flag)" } + Rails.logger.warn { "Ran through #{User.count} users (unread comments flag)" } + Rails.logger.warn { "Fixed #{num_fixed_users} users (unread comments flag)" } end # Checks and returns whether the user has unread comments. From ca14be21b20de125052d8fbffd7d147a3b126d99 Mon Sep 17 00:00:00 2001 From: Splines Date: Thu, 25 Jan 2024 20:08:09 +0100 Subject: [PATCH 49/57] Fix linting in feedback.js --- app/assets/javascripts/feedback.js | 83 +++++++++++++++--------------- 1 file changed, 42 insertions(+), 41 deletions(-) diff --git a/app/assets/javascripts/feedback.js b/app/assets/javascripts/feedback.js index 1430e54ac..57d243602 100644 --- a/app/assets/javascripts/feedback.js +++ b/app/assets/javascripts/feedback.js @@ -1,57 +1,58 @@ -$(document).on('turbolinks:load', () => { - if (!shouldRegisterFeedback()) { - return; - } - registerToasts(); - registerSubmitButtonHandler(); - registerFeedbackBodyValidator(); +$(document).on("turbolinks:load", () => { + if (!shouldRegisterFeedback()) { + return; + } + registerToasts(); + registerSubmitButtonHandler(); + registerFeedbackBodyValidator(); }); -SUBMIT_FEEDBACK_ID = '#submit-feedback'; +const SUBMIT_FEEDBACK_ID = "#submit-feedback"; -TOAST_OPTIONS = { - animation: true, - autohide: true, - delay: 6000 // autohide after ... milliseconds +const TOAST_OPTIONS = { + animation: true, + autohide: true, + delay: 6000, // autohide after ... milliseconds }; function shouldRegisterFeedback() { - return $(SUBMIT_FEEDBACK_ID).length > 0; + return $(SUBMIT_FEEDBACK_ID).length > 0; } function registerToasts() { - const toastElements = document.querySelectorAll('.toast'); - const toastList = [...toastElements].map(toast => { - new bootstrap.Toast(toast, TOAST_OPTIONS); - }); + const toastElements = document.querySelectorAll(".toast"); + [...toastElements].map((toast) => { + new bootstrap.Toast(toast, TOAST_OPTIONS); + }); } function registerSubmitButtonHandler() { - const submitButton = $('#submit-feedback-form-btn'); - - // Invoke the hidden submit button inside the actual Rails form - $('#submit-feedback-form-btn-outside').click(() => { - submitButton.click(); - }); - - // Submit form by pressing Ctrl + Enter - document.addEventListener('keydown', (event) => { - const isModalOpen = $(SUBMIT_FEEDBACK_ID).is(':visible'); - if (isModalOpen && event.ctrlKey && event.key == "Enter") { - submitButton.click(); - } - }); + const submitButton = $("#submit-feedback-form-btn"); + + // Invoke the hidden submit button inside the actual Rails form + $("#submit-feedback-form-btn-outside").click(() => { + submitButton.click(); + }); + + // Submit form by pressing Ctrl + Enter + document.addEventListener("keydown", (event) => { + const isModalOpen = $(SUBMIT_FEEDBACK_ID).is(":visible"); + if (isModalOpen && event.ctrlKey && event.key == "Enter") { + submitButton.click(); + } + }); } function registerFeedbackBodyValidator() { - const feedbackBody = document.getElementById('feedback_feedback'); - feedbackBody.addEventListener('input', () => { - if (feedbackBody.validity.tooShort) { - const tooShortMessage = feedbackBody.dataset.tooShortMessage; - feedbackBody.setCustomValidity(tooShortMessage); - } else { - // render input valid, so that form will submit - feedbackBody.setCustomValidity(''); - } - }); + const feedbackBody = document.getElementById("feedback_feedback"); + feedbackBody.addEventListener("input", () => { + if (feedbackBody.validity.tooShort) { + const tooShortMessage = feedbackBody.dataset.tooShortMessage; + feedbackBody.setCustomValidity(tooShortMessage); + } + else { + // render input valid, so that form will submit + feedbackBody.setCustomValidity(""); + } + }); } From aaf9d37ef2e2e46a3bcfde3e9afa1b7e7b4c4109 Mon Sep 17 00:00:00 2001 From: Splines Date: Thu, 25 Jan 2024 20:09:46 +0100 Subject: [PATCH 50/57] Fix RuboCop errors --- app/mailers/feedback_mailer.rb | 4 ++-- db/migrate/20230529080510_create_feedbacks.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/mailers/feedback_mailer.rb b/app/mailers/feedback_mailer.rb index 9355cfe12..152b1378b 100644 --- a/app/mailers/feedback_mailer.rb +++ b/app/mailers/feedback_mailer.rb @@ -5,11 +5,11 @@ class FeedbackMailer < ApplicationMailer # Mail to the MaMpf developers including the new feedback of a user. def new_user_feedback_email @feedback = params[:feedback] - reply_to_mail = @feedback.can_contact ? @feedback.user.email : '' + reply_to_mail = @feedback.can_contact ? @feedback.user.email : "" subject = "Feedback: #{@feedback.title}" mail(to: DefaultSetting::FEEDBACK_EMAIL, subject: subject, - content_type: 'text/plain', + content_type: "text/plain", reply_to: reply_to_mail) end end diff --git a/db/migrate/20230529080510_create_feedbacks.rb b/db/migrate/20230529080510_create_feedbacks.rb index 177ff9311..a36effc6d 100644 --- a/db/migrate/20230529080510_create_feedbacks.rb +++ b/db/migrate/20230529080510_create_feedbacks.rb @@ -3,7 +3,7 @@ def change create_table :feedbacks do |t| t.text :title t.text :feedback - t.boolean :can_contact, default: false + t.boolean :can_contact, default: false, null: false t.references :user, null: false, foreign_key: true t.timestamps From db8173e4227c15b085dcef6f8a394c2daa03e5da Mon Sep 17 00:00:00 2001 From: Splines Date: Thu, 25 Jan 2024 20:10:26 +0100 Subject: [PATCH 51/57] Fix remaining ESLint errors --- app/views/feedbacks/create.js.erb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/views/feedbacks/create.js.erb b/app/views/feedbacks/create.js.erb index 69b7e5747..1538b24bf 100644 --- a/app/views/feedbacks/create.js.erb +++ b/app/views/feedbacks/create.js.erb @@ -1,7 +1,7 @@ <% if @feedback_success %> - $('#submit-feedback').modal('hide'); - $('#submit-feedback').find('form').trigger('reset'); // clear form - $('#toast-successfully-sent').toast('show'); +$("#submit-feedback").modal("hide"); +$("#submit-feedback").find("form").trigger("reset"); // clear form +$("#toast-successfully-sent").toast("show"); <% else %> - $('#toast-could-not-send').toast('show'); +$("#toast-could-not-send").toast("show"); <% end %> From be140b5b24b6dbddb528f8d9da13a2d35d3c38eb Mon Sep 17 00:00:00 2001 From: Splines Date: Thu, 25 Jan 2024 20:32:40 +0100 Subject: [PATCH 52/57] Update timestamp of feedback migration --- ...eate_feedbacks.rb => 20240125180000_create_feedbacks.rb} | 0 db/schema.rb | 6 +++--- 2 files changed, 3 insertions(+), 3 deletions(-) rename db/migrate/{20230529080510_create_feedbacks.rb => 20240125180000_create_feedbacks.rb} (100%) diff --git a/db/migrate/20230529080510_create_feedbacks.rb b/db/migrate/20240125180000_create_feedbacks.rb similarity index 100% rename from db/migrate/20230529080510_create_feedbacks.rb rename to db/migrate/20240125180000_create_feedbacks.rb diff --git a/db/schema.rb b/db/schema.rb index 0f5168ee6..893ebcec9 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2024_01_16_180000) do +ActiveRecord::Schema[7.0].define(version: 2024_01_25_180000) do # These are extensions that must be enabled in order to support this database enable_extension "pgcrypto" enable_extension "plpgsql" @@ -192,7 +192,7 @@ create_table "feedbacks", force: :cascade do |t| t.text "title" t.text "feedback" - t.boolean "can_contact", default: false + t.boolean "can_contact", default: false, null: false t.bigint "user_id", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false @@ -921,7 +921,7 @@ add_foreign_key "commontator_subscriptions", "commontator_threads", column: "thread_id", on_update: :cascade, on_delete: :cascade add_foreign_key "course_self_joins", "courses" add_foreign_key "divisions", "programs" - add_foreign_key "feedbacks", "users", on_delete: :cascade + add_foreign_key "feedbacks", "users" add_foreign_key "imports", "media" add_foreign_key "items", "media" add_foreign_key "items", "sections" From 5374cb27918bafe92dae3445a7f0e3f3807182bd Mon Sep 17 00:00:00 2001 From: Splines Date: Thu, 25 Jan 2024 20:46:54 +0100 Subject: [PATCH 53/57] Add missing Feedback email to prod docker.env --- docker/production/docker.env | 1 + 1 file changed, 1 insertion(+) diff --git a/docker/production/docker.env b/docker/production/docker.env index d5dc660b8..b9588f0eb 100644 --- a/docker/production/docker.env +++ b/docker/production/docker.env @@ -20,6 +20,7 @@ IMAPSERVER=mail.mathi.uni-heidelberg.de PROJECT_EMAIL_USERNAME=creativeusername PROJECT_EMAIL_PASSWORD=secretsecret PROJECT_EMAIL_MAILBOX=Other Users/mampf +FEEDBACK_EMAIL=mampf-feedback-mail FEEDBACK_EMAIL_USERNAME=creative-feedback-username FEEDBACK_EMAIL_PASSWORD=creative-feedback-password From da0b409b882e7ee72fa7517f1365d1d12775e144 Mon Sep 17 00:00:00 2001 From: Splines Date: Tue, 19 Mar 2024 12:31:22 +0100 Subject: [PATCH 54/57] Remove unnecessary Feedback env variables --- docker/development/docker-compose.yml | 2 -- docker/production/docker.env | 2 -- 2 files changed, 4 deletions(-) diff --git a/docker/development/docker-compose.yml b/docker/development/docker-compose.yml index e462a17f9..212d2fd6f 100644 --- a/docker/development/docker-compose.yml +++ b/docker/development/docker-compose.yml @@ -113,8 +113,6 @@ services: PROJECT_EMAIL_USERNAME: mampf PROJECT_EMAIL_PASSWORD: mampf PROJECT_EMAIL_MAILBOX: INBOX - FEEDBACK_EMAIL_USERNAME: mampf - FEEDBACK_EMAIL_PASSWORD: mampf BLOG: https://mampf.blog # uncomment DB_SQL_PRESEED_URL and UPLOADS_PRESEED_URL to enable db preseeding # DB_SQL_PRESEED_URL: "https://heibox.uni-heidelberg.de/d/6fb4a9d2e7f54d8b9931/files/?p=%2F20220923120841_mampf.sql&dl=1" diff --git a/docker/production/docker.env b/docker/production/docker.env index a214c5523..886040d36 100644 --- a/docker/production/docker.env +++ b/docker/production/docker.env @@ -25,8 +25,6 @@ PROJECT_EMAIL_MAILBOX="Other Users/mampf" MAMPF_EMAIL_USERNAME=secret MAMPF_EMAIL_PASSWORD=secret FEEDBACK_EMAIL=mampf-feedback-mail -FEEDBACK_EMAIL_USERNAME=secret -FEEDBACK_EMAIL_PASSWORD=secret # Due to CORS constraints, some urls are proxied to the media server DOWNLOAD_LOCATION=https://mampf.mathi.uni-heidelberg.de/mediaforward From ebf95cb2e5741091a910e210385a95427c15acfe Mon Sep 17 00:00:00 2001 From: Splines Date: Tue, 19 Mar 2024 18:14:05 +0100 Subject: [PATCH 55/57] Add validation message for empty body --- app/assets/javascripts/feedback.js | 40 ++++++++++++++------- app/views/feedbacks/_feedback_form.html.erb | 3 +- config/locales/de.yml | 2 ++ config/locales/en.yml | 2 ++ 4 files changed, 34 insertions(+), 13 deletions(-) diff --git a/app/assets/javascripts/feedback.js b/app/assets/javascripts/feedback.js index 57d243602..4cf7ab8f0 100644 --- a/app/assets/javascripts/feedback.js +++ b/app/assets/javascripts/feedback.js @@ -27,18 +27,16 @@ function registerToasts() { } function registerSubmitButtonHandler() { - const submitButton = $("#submit-feedback-form-btn"); - // Invoke the hidden submit button inside the actual Rails form $("#submit-feedback-form-btn-outside").click(() => { - submitButton.click(); + submitFeedback(); }); // Submit form by pressing Ctrl + Enter document.addEventListener("keydown", (event) => { const isModalOpen = $(SUBMIT_FEEDBACK_ID).is(":visible"); if (isModalOpen && event.ctrlKey && event.key == "Enter") { - submitButton.click(); + submitFeedback(); } }); } @@ -46,13 +44,31 @@ function registerSubmitButtonHandler() { function registerFeedbackBodyValidator() { const feedbackBody = document.getElementById("feedback_feedback"); feedbackBody.addEventListener("input", () => { - if (feedbackBody.validity.tooShort) { - const tooShortMessage = feedbackBody.dataset.tooShortMessage; - feedbackBody.setCustomValidity(tooShortMessage); - } - else { - // render input valid, so that form will submit - feedbackBody.setCustomValidity(""); - } + validateFeedback(); }); } + +function validateFeedback() { + const feedbackBody = document.getElementById("feedback_feedback"); + const validityState = feedbackBody.validity; + if (validityState.tooShort) { + const tooShortMessage = feedbackBody.dataset.tooShortMessage; + feedbackBody.setCustomValidity(tooShortMessage); + } + else if (validityState.valueMissing) { + const valueMissingMessage = feedbackBody.dataset.valueMissingMessage; + feedbackBody.setCustomValidity(valueMissingMessage); + } + else { + // render input valid, so that form will submit + feedbackBody.setCustomValidity(""); + } + + feedbackBody.reportValidity(); +} + +function submitFeedback() { + const submitButton = $("#submit-feedback-form-btn"); + validateFeedback(); + submitButton.click(); +} diff --git a/app/views/feedbacks/_feedback_form.html.erb b/app/views/feedbacks/_feedback_form.html.erb index 18694dbe4..2e0833e3e 100644 --- a/app/views/feedbacks/_feedback_form.html.erb +++ b/app/views/feedbacks/_feedback_form.html.erb @@ -29,7 +29,8 @@ minlength: Feedback::BODY_MIN_LENGTH, maxlength: Feedback::BODY_MAX_LENGTH, 'data-too-short-message': t('feedback.body_too_short_error', - min_length: Feedback::BODY_MIN_LENGTH) %> + min_length: Feedback::BODY_MIN_LENGTH), + 'data-value-missing-message': t('feedback.body_missing_error') %> <%= f.label :feedback, t('feedback.comment') %>
diff --git a/config/locales/de.yml b/config/locales/de.yml index d5d8d2632..13ae67031 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -3813,6 +3813,8 @@ de: gerne auch über dieses Formular oder direkt per %{feedback_mail}.

body_too_short_error: > Dein Feedback ist zu kurz. Bitte gib mindestens %{min_length} Zeichen ein. + body_missing_error: > + Bitte gib hier Dein Feedback ein. mail_checkbox: > Erlaube es uns, Dich per E-Mail (%{user_mail}) zu kontaktieren, falls es Rückfragen zu Deinem Feedback gibt. diff --git a/config/locales/en.yml b/config/locales/en.yml index 50b428b19..0f738f912 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -3606,6 +3606,8 @@ en: form or send an %{feedback_mail} directly.

body_too_short_error: > Your feedback is too short. Please enter at least %{min_length} characters. + body_missing_error: > + Please enter your feedback here. mail_checkbox: > Allow us to contact you via email (%{user_mail}) if there are questions about your feedback. From d5c2e0ce2d04fd6b2920329ea12dac0c6f981eeb Mon Sep 17 00:00:00 2001 From: Splines Date: Tue, 19 Mar 2024 18:14:52 +0100 Subject: [PATCH 56/57] Change `const` to `var` to avoid "redefined" errors --- app/assets/javascripts/feedback.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/feedback.js b/app/assets/javascripts/feedback.js index 4cf7ab8f0..7d9ab3772 100644 --- a/app/assets/javascripts/feedback.js +++ b/app/assets/javascripts/feedback.js @@ -7,9 +7,9 @@ $(document).on("turbolinks:load", () => { registerFeedbackBodyValidator(); }); -const SUBMIT_FEEDBACK_ID = "#submit-feedback"; +var SUBMIT_FEEDBACK_ID = "#submit-feedback"; -const TOAST_OPTIONS = { +var TOAST_OPTIONS = { animation: true, autohide: true, delay: 6000, // autohide after ... milliseconds From a36cf52292ea4a14471266c53c45a99ff3b155fb Mon Sep 17 00:00:00 2001 From: Splines Date: Tue, 19 Mar 2024 18:16:13 +0100 Subject: [PATCH 57/57] Update timestamp of feedback migration (again) --- ...0_create_feedbacks.rb => 20240319130000_create_feedbacks.rb} | 0 db/schema.rb | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename db/migrate/{20240125180000_create_feedbacks.rb => 20240319130000_create_feedbacks.rb} (100%) diff --git a/db/migrate/20240125180000_create_feedbacks.rb b/db/migrate/20240319130000_create_feedbacks.rb similarity index 100% rename from db/migrate/20240125180000_create_feedbacks.rb rename to db/migrate/20240319130000_create_feedbacks.rb diff --git a/db/schema.rb b/db/schema.rb index 423121de5..d17bf4c82 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2024_02_15_100000) do +ActiveRecord::Schema[7.0].define(version: 2024_03_19_130000) do # These are extensions that must be enabled in order to support this database enable_extension "pgcrypto" enable_extension "plpgsql"