diff --git a/app/assets/javascripts/exercise.ts b/app/assets/javascripts/exercise.ts index daf10d4be8..7f3e37a7da 100644 --- a/app/assets/javascripts/exercise.ts +++ b/app/assets/javascripts/exercise.ts @@ -129,7 +129,7 @@ function initExerciseDescription(): void { initCodeFragments(); } -function initExerciseShow(exerciseId: number, programmingLanguage: string, loggedIn: boolean, editorShown: boolean, courseId: number, _deadline: string, baseSubmissionsUrl: string): void { +function initExerciseShow(exerciseId: number, programmingLanguage: string, loggedIn: boolean, editorShown: boolean, courseId: number, _deadline: string, baseSubmissionsUrl: string, boilerplate: string): void { let editor: AceAjax.Editor; let lastSubmission: string; let lastTimeout: number; @@ -140,6 +140,7 @@ function initExerciseShow(exerciseId: number, programmingLanguage: string, logge initDeadlineTimeout(); enableSubmissionTableLinks(); swapActionButtons(); + initRestoreBoilerplateButton(boilerplate); } // submit source code if button is clicked on editor panel @@ -444,6 +445,20 @@ function initExerciseShow(exerciseId: number, programmingLanguage: string, logge showDeadlineAlerts(); } + function initRestoreBoilerplateButton(boilerplate: string): void { + const restoreWarning = document.getElementById("restore-boilerplate"); + if (!restoreWarning) { + return; + } + + const resetButton = restoreWarning.querySelector("a"); + resetButton.addEventListener("click", () => { + editor.setValue(boilerplate); + editor.focus(); + restoreWarning.hidden = true; + }); + } + init(); } diff --git a/app/assets/stylesheets/models/activities.css.scss b/app/assets/stylesheets/models/activities.css.scss index f0c7fd07f4..da0e9512ae 100644 --- a/app/assets/stylesheets/models/activities.css.scss +++ b/app/assets/stylesheets/models/activities.css.scss @@ -186,6 +186,12 @@ center img { } } +#restore-boilerplate { + a { + cursor: pointer; + } +} + pre { position: relative; diff --git a/app/controllers/activities_controller.rb b/app/controllers/activities_controller.rb index 258255155a..b3a9c93993 100644 --- a/app/controllers/activities_controller.rb +++ b/app/controllers/activities_controller.rb @@ -124,17 +124,20 @@ def show @submissions = @activity.submissions.includes(:annotations) @submissions = @submissions.in_course(@course) if @course.present? && current_user&.member_of?(@course) @submissions = @submissions.of_user(current_user) if current_user - @submissions = policy_scope(@submissions).paginate(page: parse_pagination_param(params[:page])) + @submissions = policy_scope(@submissions) if params[:edit_submission] @edit_submission = Submission.find(params[:edit_submission]) authorize @edit_submission, :edit? + elsif @submissions.any? + @last_submission = @submissions.first end + @submissions = @submissions.paginate(page: parse_pagination_param(params[:page])) if params[:from_solution] @solution = @activity.solutions[params[:from_solution]] authorize @activity, :info? end - @code = @edit_submission.try(:code) || @solution || @activity.boilerplate + @code = @edit_submission.try(:code) || @solution || @last_submission.try(:code) || @activity.boilerplate elsif @activity.content_page? @read_state = if current_user&.member_of?(@course) @activity.activity_read_states.find_by(user: current_user, course: @course) diff --git a/app/views/activities/show.html.erb b/app/views/activities/show.html.erb index a3f629ea56..c5bf88e02d 100644 --- a/app/views/activities/show.html.erb +++ b/app/views/activities/show.html.erb @@ -114,6 +114,14 @@ end %> <%= t('.deadline_passed', deadline: @series.deadline.today? ? @series.deadline.strftime('%R') : @series.deadline.strftime('%F %R')) %> <% end %> + <% if !@edit_submission && !@solution && @last_submission %> +
+ <%= t ".preloaded_info" %> + + <%= t(@activity.boilerplate ? ".preloaded_restore" : ".preloaded_clear") %> + +
+ <% end %>
<%= @code %>
@@ -173,7 +181,8 @@ end %> <%= policy(@activity).submit? || !user_signed_in? %>, <%= @course&.id || "null" %>, <%= raw "\"#{@series&.deadline&.httpdate}\"" || "null" %>, - "<%= submissions_url %>" + "<%= submissions_url %>", + `<%= (@activity.exercise? && raw(@activity.boilerplate&.gsub!('`', '\\\\`')))|| "" %>`, ); }); diff --git a/config/locales/views/activities/en.yml b/config/locales/views/activities/en.yml index 381ba551d4..2d7ec551f3 100644 --- a/config/locales/views/activities/en.yml +++ b/config/locales/views/activities/en.yml @@ -94,6 +94,9 @@ en: back_to_course_actionable: Go back to course mark_as_read: Mark as read read_at: "Marked as read on %{timestamp}" + preloaded_info: "We have preloaded your latest submission into the editor." + preloaded_clear: Clear editor. + preloaded_restore: Restore the boilerplate code. series_activities_add_table: course_added_to_usable: "Adding this exercise will allow this course to use all of the private exercises in this exercise's repository. Are you sure?" edit: diff --git a/config/locales/views/activities/nl.yml b/config/locales/views/activities/nl.yml index 6ac81aa535..4ead22cf5d 100644 --- a/config/locales/views/activities/nl.yml +++ b/config/locales/views/activities/nl.yml @@ -94,6 +94,9 @@ nl: back_to_course_actionable: Ga terug naar de cursus mark_as_read: Markeren als gelezen read_at: "Gelezen op %{timestamp}" + preloaded_info: We hebben jouw laatste oplossing ingeladen in de editor. + preloaded_clear: Maak de editor leeg. + preloaded_restore: Herstel de boilerplate-code. series_activities_add_table: course_added_to_usable: "Deze oefening toevoegen zal deze cursus toegang geven tot alle privΓ© oefeningen in de repository van deze oefening. Ben je zeker?" edit: diff --git a/test/system/activities_test.rb b/test/system/activities_test.rb new file mode 100644 index 0000000000..181eb1675a --- /dev/null +++ b/test/system/activities_test.rb @@ -0,0 +1,60 @@ +require 'capybara/minitest' +require 'application_system_test_case' + +class ActivitiesTest < ApplicationSystemTestCase + include Devise::Test::IntegrationHelpers + # Make the Capybara DSL available in all integration tests + include Capybara::DSL + # Make `assert_*` methods behave like Minitest assertions + include Capybara::Minitest::Assertions + + setup do + @instance = exercises(:python_exercise) + @user = users(:zeus) + sign_in @user + end + + test 'should show exercise' do + visit exercise_path(id: @instance.id) + assert_text @instance.name_en + end + + test 'should show boilerplate in code editor' do + @instance.stubs(:boilerplate).returns('boilerplate') do + visit exercise_path(id: @instance.id) + assert_text 'boilerplate' + end + end + + test 'show latest submission in code editor' do + create(:submission, exercise: @instance, user: @user, status: :correct, code: 'print("hello")') + visit exercise_path(id: @instance.id) + assert_text 'print("hello")' + end + + test 'should show message to clear editor if latest submission is shown' do + create(:submission, exercise: @instance, user: @user, status: :correct, code: 'print("hello")') + visit exercise_path(id: @instance.id) + assert_text 'Clear editor' + end + + test 'should show message to restore boilerplate if latest submission is shown' do + @instance.stubs(:boilerplate).returns('boilerplate') do + create(:submission, exercise: @instance, user: @user, status: :correct, code: 'print("hello")') + visit exercise_path(id: @instance.id) + assert_text 'Restore boilerplate' + end + end + + test 'should not break on complex unicode characters' do + @instance.stubs(:boilerplate).returns('``') do + visit exercise_path(id: @instance.id) + assert_text '``' + + create(:submission, exercise: @instance, user: @user, status: :correct, code: 'print("πŸ˜€")') + visit exercise_path(id: @instance.id) + assert_text 'print("πŸ˜€")' + assert_text 'Restore boilerplate' + end + end +end