Implement support for some basic embed options for work sheets via LTI
This commit also fixes an issue with the flash messages being positioned too high and displayed for too long
This commit is contained in:
@ -11,7 +11,7 @@ window.CodeOcean = {
|
||||
var ANIMATION_DURATION = 500;
|
||||
|
||||
$.isController = function(name) {
|
||||
return $('.container[data-controller="' + name + '"]').isPresent();
|
||||
return $('div[data-controller="' + name + '"]').isPresent();
|
||||
};
|
||||
|
||||
$.fn.isPresent = function() {
|
||||
|
@ -110,6 +110,10 @@ configureEditors: function () {
|
||||
|
||||
// The event ready.jstree is fired too early and thus doesn't work.
|
||||
selectFileInJsTree: function(filetree, file_id) {
|
||||
if (!filetree.is(':visible'))
|
||||
// The left sidebar is not shown and thus the filetree is not rendered.
|
||||
return;
|
||||
|
||||
if (!filetree.hasClass('jstree-loading')) {
|
||||
filetree.jstree("deselect_all");
|
||||
filetree.jstree().select_node(file_id);
|
||||
@ -224,6 +228,11 @@ configureEditors: function () {
|
||||
// remove last (empty) that is there by default line
|
||||
document.removeLines(document.getLength() - 1, document.getLength() - 1);
|
||||
editor.setReadOnly($(element).data('read-only') !== undefined);
|
||||
if (editor.getReadOnly()) {
|
||||
editor.setHighlightActiveLine(false);
|
||||
editor.setHighlightGutterLine(false);
|
||||
editor.renderer.$cursorLayer.element.style.opacity = 0;
|
||||
}
|
||||
editor.setShowPrintMargin(false);
|
||||
editor.setTheme(this.THEME);
|
||||
|
||||
@ -458,7 +467,7 @@ configureEditors: function () {
|
||||
|
||||
var editor = this.editor_for_file.get(file);
|
||||
editor.gotoLine(line, 0);
|
||||
|
||||
event.preventDefault();
|
||||
},
|
||||
|
||||
augmentStacktraceInOutput: function () {
|
||||
@ -722,6 +731,7 @@ configureEditors: function () {
|
||||
this.initPrompt();
|
||||
this.renderScore();
|
||||
this.showFirstFile();
|
||||
this.resizeAceEditors();
|
||||
|
||||
$(window).on("beforeunload", this.unloadAutoSave.bind(this));
|
||||
// create autosave when the editor is opened the first time
|
||||
|
@ -27,8 +27,11 @@ CodeOceanEditorEvaluation = {
|
||||
printScoringResult: function (result, index) {
|
||||
$('#results').show();
|
||||
var card = $('#dummies').children().first().clone();
|
||||
if (card.isPresent()) {
|
||||
// the card won't be present if @embed_options[::hide_test_results] == true
|
||||
this.populateCard(card, result, index);
|
||||
$('#results ul').first().append(card);
|
||||
}
|
||||
},
|
||||
|
||||
printScoringResults: function (response) {
|
||||
@ -141,14 +144,19 @@ CodeOceanEditorEvaluation = {
|
||||
},
|
||||
|
||||
printOutput: function (output, colorize, index) {
|
||||
if (output.stderr === undefined && output.stdout === undefined) {
|
||||
// Prevent empty element with no text at all
|
||||
return;
|
||||
}
|
||||
|
||||
var element = this.findOrCreateOutputElement(index);
|
||||
if (!colorize) {
|
||||
if (output.stdout != undefined && output.stdout != '') {
|
||||
if (output.stdout !== undefined && output.stdout !== '') {
|
||||
//element.append(output.stdout)
|
||||
element.text(element.text() + output.stdout)
|
||||
}
|
||||
|
||||
if (output.stderr != undefined && output.stderr != '') {
|
||||
if (output.stderr !== undefined && output.stderr !== '') {
|
||||
//element.append('StdErr: ' + output.stderr);
|
||||
element.text('StdErr: ' + element.text() + output.stderr);
|
||||
}
|
||||
|
@ -172,8 +172,9 @@ CodeOceanEditorRequestForComments = {
|
||||
this.createSubmission($('#requestComments'), null, createRequestForComments.bind(this));
|
||||
|
||||
$('#comment-modal').modal('hide');
|
||||
$('#question').val('');
|
||||
// we disabled the button to prevent that the user spams RFCs, but decided against this now.
|
||||
//var button = $('#requestComments');
|
||||
//button.prop('disabled', true);
|
||||
},
|
||||
}
|
||||
};
|
||||
|
@ -10,6 +10,7 @@ CodeOceanEditorSubmissions = {
|
||||
*/
|
||||
createSubmission: function (initiator, filter, callback) {
|
||||
this.showSpinner(initiator);
|
||||
var url = $(initiator).data('url') || $('#editor').data('submissions-url');
|
||||
var jqxhr = this.ajax({
|
||||
data: {
|
||||
submission: {
|
||||
@ -21,7 +22,7 @@ CodeOceanEditorSubmissions = {
|
||||
},
|
||||
dataType: 'json',
|
||||
method: 'POST',
|
||||
url: $(initiator).data('url') || $('#editor').data('submissions-url')
|
||||
url: url + '.json'
|
||||
});
|
||||
jqxhr.always(this.hideSpinner.bind(this));
|
||||
jqxhr.done(this.createSubmissionCallback.bind(this));
|
||||
|
@ -5,7 +5,7 @@ class ApplicationController < ActionController::Base
|
||||
MEMBER_ACTIONS = [:destroy, :edit, :show, :update]
|
||||
|
||||
after_action :verify_authorized, except: [:help, :welcome]
|
||||
before_action :set_locale, :allow_iframe_requests
|
||||
before_action :set_locale, :allow_iframe_requests, :load_embed_options
|
||||
protect_from_forgery(with: :exception, prepend: true)
|
||||
rescue_from Pundit::NotAuthorizedError, with: :render_not_authorized
|
||||
|
||||
@ -38,4 +38,14 @@ class ApplicationController < ActionController::Base
|
||||
def allow_iframe_requests
|
||||
response.headers.delete('X-Frame-Options')
|
||||
end
|
||||
|
||||
def load_embed_options
|
||||
if session[:embed_options].present? && session[:embed_options].is_a?(Hash)
|
||||
@embed_options = session[:embed_options].symbolize_keys
|
||||
else
|
||||
@embed_options = {}
|
||||
end
|
||||
@embed_options
|
||||
end
|
||||
private :load_embed_options
|
||||
end
|
||||
|
@ -22,6 +22,7 @@ module Lti
|
||||
if (exercise_id.nil?)
|
||||
session.delete(:consumer_id)
|
||||
session.delete(:external_user_id)
|
||||
session.delete(:embed_options)
|
||||
else
|
||||
LtiParameter.where(consumers_id: consumer_id,
|
||||
external_users_id: user_id,
|
||||
@ -133,6 +134,26 @@ module Lti
|
||||
end
|
||||
private :set_current_user
|
||||
|
||||
def set_embedding_options
|
||||
@embed_options = {}
|
||||
[:hide_navbar,
|
||||
:hide_exercise_description,
|
||||
:disable_run,
|
||||
:disable_score,
|
||||
:disable_rfc,
|
||||
:disable_interventions,
|
||||
:hide_sidebar,
|
||||
:read_only,
|
||||
:hide_test_results,
|
||||
:disable_hints].each do |option|
|
||||
value = params["custom_embed_options_#{option}".to_sym] == 'true'
|
||||
# Optimize storage and save only those that are true, the session cookie is limited to 4KB
|
||||
@embed_options[option] = value if value.present?
|
||||
end
|
||||
session[:embed_options] = @embed_options
|
||||
end
|
||||
private :set_embedding_options
|
||||
|
||||
def store_lti_session_data(options = {})
|
||||
lti_parameters = LtiParameter.find_or_create_by(consumers_id: options[:consumer].id,
|
||||
external_users_id: @current_user.id,
|
||||
|
@ -55,6 +55,11 @@ module SubmissionScoring
|
||||
}
|
||||
end
|
||||
end
|
||||
if @embed_options.present? && @embed_options[:hide_test_results] && outputs.present?
|
||||
outputs.each do |output|
|
||||
output.except!(:error_messages, :count, :failed, :filename, :message, :passed, :stderr, :stdout)
|
||||
end
|
||||
end
|
||||
outputs
|
||||
end
|
||||
end
|
||||
|
@ -188,7 +188,15 @@ class ExercisesController < ApplicationController
|
||||
user_got_intervention_in_exercise = UserExerciseIntervention.where(user: current_user, exercise: @exercise).size >= max_intervention_count_per_exercise
|
||||
user_got_enough_interventions = count_interventions_today >= max_intervention_count_per_day or user_got_intervention_in_exercise
|
||||
|
||||
unless @embed_options[:disable_interventions]
|
||||
@show_rfc_interventions = (not user_solved_exercise and not user_got_enough_interventions).to_s
|
||||
@show_break_interventions = false
|
||||
else
|
||||
@show_rfc_interventions = false
|
||||
@show_break_interventions = false
|
||||
end
|
||||
|
||||
@hide_rfc_button = @embed_options[:disable_rfc]
|
||||
|
||||
|
||||
@search = Search.new
|
||||
|
@ -8,7 +8,7 @@ class FlowrController < ApplicationController
|
||||
.order('testruns.created_at DESC').first
|
||||
|
||||
# Return if no submission was found
|
||||
if submission.blank?
|
||||
if submission.blank? || @embed_options[:disable_hints] || @embed_options[:hide_test_results]
|
||||
skip_authorization
|
||||
render json: [], status: :ok
|
||||
return
|
||||
|
@ -96,6 +96,10 @@ class RequestForCommentsController < ApplicationController
|
||||
# POST /request_for_comments
|
||||
# POST /request_for_comments.json
|
||||
def create
|
||||
# Consider all requests as JSON
|
||||
request.format = 'json'
|
||||
raise Pundit::NotAuthorizedError if @embed_options[:disable_rfc]
|
||||
|
||||
@request_for_comment = RequestForComment.new(request_for_comment_params)
|
||||
respond_to do |format|
|
||||
if @request_for_comment.save
|
||||
|
@ -1,7 +1,7 @@
|
||||
class SessionsController < ApplicationController
|
||||
include Lti
|
||||
|
||||
[:require_oauth_parameters, :require_valid_consumer_key, :require_valid_oauth_signature, :require_unique_oauth_nonce, :set_current_user, :require_valid_exercise_token].each do |method_name|
|
||||
[:require_oauth_parameters, :require_valid_consumer_key, :require_valid_oauth_signature, :require_unique_oauth_nonce, :set_current_user, :require_valid_exercise_token, :set_embedding_options].each do |method_name|
|
||||
before_action(method_name, only: :create_through_lti)
|
||||
end
|
||||
|
||||
|
@ -129,6 +129,11 @@ class SubmissionsController < ApplicationController
|
||||
# end
|
||||
|
||||
hijack do |tubesock|
|
||||
if @embed_options[:disable_run]
|
||||
kill_socket(tubesock)
|
||||
return
|
||||
end
|
||||
|
||||
# probably add:
|
||||
# ensure
|
||||
# #guarantee that the thread is releasing the DB connection after it is done
|
||||
@ -291,6 +296,11 @@ class SubmissionsController < ApplicationController
|
||||
|
||||
def score
|
||||
hijack do |tubesock|
|
||||
if @embed_options[:disable_score]
|
||||
kill_socket(tubesock)
|
||||
return
|
||||
end
|
||||
|
||||
Thread.new { EventMachine.run } unless EventMachine.reactor_running? && EventMachine.reactor_thread.alive?
|
||||
# tubesock is the socket to the client
|
||||
|
||||
@ -308,6 +318,7 @@ class SubmissionsController < ApplicationController
|
||||
end
|
||||
|
||||
def send_hints(tubesock, errors)
|
||||
return if @embed_options[:disable_hints]
|
||||
errors = errors.to_a.uniq { |e| e.hint}
|
||||
errors.each do | error |
|
||||
tubesock.send_data JSON.dump({cmd: 'hint', hint: error.hint, description: error.error_template.description})
|
||||
|
@ -1,4 +1,5 @@
|
||||
- if current_user.try(:internal_user?)
|
||||
.container
|
||||
ul.breadcrumb
|
||||
- if model = Kernel.const_get(controller_path.classify) rescue nil
|
||||
- object = model.find_by(id: params[:id])
|
||||
|
@ -1,4 +1,4 @@
|
||||
#flash-container
|
||||
#flash-container.container
|
||||
#flash.container.fixed_error_messages data-message-failure=t('shared.message_failure')
|
||||
- %w[alert danger info notice success warning].each do |severity|
|
||||
div.alert.flash class="alert-#{{'alert' => 'warning', 'notice' => 'success'}.fetch(severity, severity)} alert-dismissible fade show"
|
||||
|
@ -5,26 +5,29 @@
|
||||
- show_rfc_interventions = @show_rfc_interventions || "false"
|
||||
- hide_rfc_button = @hide_rfc_button || false
|
||||
#editor.row data-exercise-id=@exercise.id data-message-depleted=t('exercises.editor.depleted') data-message-timeout=t('exercises.editor.timeout', permitted_execution_time: @exercise.execution_environment.permitted_execution_time) data-submissions-url=submissions_path data-user-id=@current_user.id data-user-external-id=external_user_external_id data-working-times-url=working_times_exercise_path(@exercise) data-intervention-save-url=intervention_exercise_path(@exercise) data-rfc-interventions=show_rfc_interventions data-break-interventions=show_break_interventions data-course_token=@course_token data-search-save-url=search_exercise_path(@exercise)
|
||||
- unless @embed_options[:hide_sidebar]
|
||||
div id="sidebar" class=(@exercise.hide_file_tree ? 'sidebar-col-collapsed' : 'sidebar-col') = render('editor_file_tree', exercise: @exercise, files: @files)
|
||||
div.editor-col.col.p-0 id='frames'
|
||||
#editor-buttons.btn-group.enforce-bottom-margin
|
||||
= render('editor_button', disabled: true, icon: 'fa fa-ban', id: 'dummy', label: t('exercises.editor.dummy'))
|
||||
= render('editor_button', icon: 'fa fa-desktop', id: 'render', label: t('exercises.editor.render'))
|
||||
= render('editor_button', data: {:'data-message-failure' => t('exercises.editor.run_failure'), :'data-message-network' => t('exercises.editor.network'), :'data-message-success' => t('exercises.editor.run_success'), :'data-placement' => 'top', :'data-toggle' => 'tooltip', :'data-container' => 'body'}, icon: 'fa fa-play', id: 'run', label: t('exercises.editor.run'), title: t('shared.tooltips.shortcut', shortcut: 'ALT + r'))
|
||||
= render('editor_button', data: {:'data-placement' => 'top', :'data-toggle' => 'tooltip', :'data-container' => 'body'}, icon: 'fa fa-stop', id: 'stop', label: t('exercises.editor.stop'), title: t('shared.tooltips.shortcut', shortcut: 'ALT + r'))
|
||||
= render('editor_button', data: {:'data-placement' => 'top', :'data-toggle' => 'tooltip', :'data-container' => 'body'}, icon: 'fa fa-rocket', id: 'test', label: t('exercises.editor.test'), title: t('shared.tooltips.shortcut', shortcut: 'ALT + t'))
|
||||
= render('editor_button', data: {:'data-placement' => 'top', :'data-toggle' => 'tooltip', :'data-container' => 'body'}, icon: 'fa fa-trophy', id: 'assess', label: t('exercises.editor.score'), title: t('shared.tooltips.shortcut', shortcut: 'ALT + s'))
|
||||
= render('editor_button', icon: 'fa fa-desktop', id: 'render', label: t('exercises.editor.render')) unless @embed_options[:hide_run_button]
|
||||
= render('editor_button', data: {:'data-message-failure' => t('exercises.editor.run_failure'), :'data-message-network' => t('exercises.editor.network'), :'data-message-success' => t('exercises.editor.run_success'), :'data-placement' => 'top', :'data-toggle' => 'tooltip', :'data-container' => 'body'}, icon: 'fa fa-play', id: 'run', label: t('exercises.editor.run'), title: t('shared.tooltips.shortcut', shortcut: 'ALT + r')) unless @embed_options[:disable_run]
|
||||
= render('editor_button', data: {:'data-placement' => 'top', :'data-toggle' => 'tooltip', :'data-container' => 'body'}, icon: 'fa fa-stop', id: 'stop', label: t('exercises.editor.stop'), title: t('shared.tooltips.shortcut', shortcut: 'ALT + r')) unless @embed_options[:disable_run]
|
||||
= render('editor_button', data: {:'data-placement' => 'top', :'data-toggle' => 'tooltip', :'data-container' => 'body'}, icon: 'fa fa-rocket', id: 'test', label: t('exercises.editor.test'), title: t('shared.tooltips.shortcut', shortcut: 'ALT + t')) unless @embed_options[:disable_run]
|
||||
= render('editor_button', data: {:'data-placement' => 'top', :'data-toggle' => 'tooltip', :'data-container' => 'body'}, icon: 'fa fa-trophy', id: 'assess', label: t('exercises.editor.score'), title: t('shared.tooltips.shortcut', shortcut: 'ALT + s')) unless @embed_options[:disable_score]
|
||||
// todo: check this
|
||||
- if not hide_rfc_button
|
||||
- unless hide_rfc_button
|
||||
= render('editor_button', icon: 'fa fa-comment', id: 'requestComments', label: t('exercises.editor.requestComments'), title: t('exercises.editor.requestCommentsTooltip'))
|
||||
- @files.each do |file|
|
||||
- file.read_only = true if @embed_options[:read_only]
|
||||
= render('editor_frame', exercise: exercise, file: file)
|
||||
#autosave-label
|
||||
= t('exercises.editor.lastsaved')
|
||||
span
|
||||
button style="display:none" id="autosave"
|
||||
- unless @embed_options[:disable_run] && @embed_options[:disable_score]
|
||||
div id='output_sidebar' class='output-col-collapsed' = render('exercises/editor_output', external_user_id: external_user_id, consumer_id: consumer_id )
|
||||
|
||||
|
||||
= render('shared/modal', id: 'comment-modal', title: t('exercises.implement.comment.request'), template: 'exercises/_request_comment_dialogcontent')
|
||||
= render('shared/modal', id: 'break-intervention-modal', title: t('exercises.implement.break_intervention.title'), template: 'interventions/_break_intervention_modal')
|
||||
= render('shared/modal', id: 'comment-modal', title: t('exercises.implement.comment.request'), template: 'exercises/_request_comment_dialogcontent') unless @embed_options[:disable_rfc]
|
||||
= render('shared/modal', id: 'break-intervention-modal', title: t('exercises.implement.break_intervention.title'), template: 'interventions/_break_intervention_modal') unless @embed_options[:disable_interventions]
|
||||
|
@ -10,6 +10,7 @@ div.h-100 id='output_sidebar_uncollapsed' class='d-none col-sm-12 enforce-bottom
|
||||
#results
|
||||
h2 = t('exercises.implement.results')
|
||||
p.test-count == t('exercises.implement.test_count', count: 0)
|
||||
- unless @embed_options[:hide_test_results]
|
||||
ul.list-unstyled
|
||||
ul#dummies.d-none.list-unstyled
|
||||
li.card.mt-2
|
||||
@ -44,12 +45,13 @@ div.h-100 id='output_sidebar_uncollapsed' class='d-none col-sm-12 enforce-bottom
|
||||
input#prompt-input.form-control type='text'
|
||||
span.input-group-btn
|
||||
button#prompt-submit.btn.btn-primary type="button" = t('exercises.editor.send')
|
||||
- unless @embed_options[:disable_hints]
|
||||
#error-hints
|
||||
.heading = t('exercises.implement.error_hints.heading')
|
||||
ul.body
|
||||
#output.mt-2
|
||||
pre = t('exercises.implement.no_output_yet')
|
||||
- if CodeOcean::Config.new(:code_ocean).read[:flowr][:enabled]
|
||||
- if CodeOcean::Config.new(:code_ocean).read[:flowr][:enabled] && !@embed_options[:disable_hints] && !@embed_options[:hide_test_results]
|
||||
#flowrHint.card.text-white.bg-info data-url=CodeOcean::Config.new(:code_ocean).read[:flowr][:url] role='tab'
|
||||
.card-header = t('exercises.implement.flowr.heading')
|
||||
.card-body.text-dark.bg-white
|
||||
|
@ -1,5 +1,6 @@
|
||||
.row
|
||||
#editor-column.col-md-12
|
||||
- unless @embed_options[:hide_exercise_description]
|
||||
.exercise.clearfix
|
||||
div
|
||||
span.badge.badge-pill.badge-primary.float-right.score
|
||||
|
@ -13,6 +13,7 @@ html lang='en'
|
||||
= yield(:head)
|
||||
= csrf_meta_tags
|
||||
body
|
||||
- unless @embed_options[:hide_navbar]
|
||||
nav.navbar.navbar-dark.bg-dark.navbar-expand-md.mb-4.py-1 role='navigation'
|
||||
.container
|
||||
.navbar-brand
|
||||
@ -26,9 +27,9 @@ html lang='en'
|
||||
= render('locale_selector', cached: true)
|
||||
li.nav-item.mr-3 = link_to(t('shared.help.link'), '#modal-help', data: {toggle: 'modal'}, class: 'nav-link')
|
||||
= render('session')
|
||||
.container data-controller=controller_name
|
||||
div data-controller=controller_name
|
||||
= render('flash')
|
||||
= render('breadcrumbs') if current_user.try(:internal_user?)
|
||||
= render('breadcrumbs') if current_user.try(:internal_user?) && !@embed_options[:hide_navbar]
|
||||
- if (controller_name == "exercises" && action_name == "implement")
|
||||
.container-fluid
|
||||
= yield
|
||||
|
@ -1,8 +1,8 @@
|
||||
json.extract! @submission, :id, :files
|
||||
json.download_url download_submission_path(@submission)
|
||||
json.score_url score_submission_path(@submission)
|
||||
json.stop_url stop_submission_path(@submission)
|
||||
json.download_file_url download_file_submission_path(@submission, 'a.').gsub(/a\.$/, '{filename}')
|
||||
json.render_url render_submission_path(@submission, 'a.').gsub(/a\.$/, '{filename}')
|
||||
json.run_url run_submission_path(@submission, 'a.').gsub(/a\.$/, '{filename}')
|
||||
json.test_url test_submission_path(@submission, 'a.').gsub(/a\.$/, '{filename}')
|
||||
json.download_url download_submission_path(@submission, format: :json)
|
||||
json.score_url score_submission_path(@submission, format: :json)
|
||||
json.stop_url stop_submission_path(@submission, format: :json)
|
||||
json.download_file_url download_file_submission_path(@submission, 'a.', format: :json).gsub(/a\.\.json$/, '{filename}.json')
|
||||
json.render_url render_submission_path(@submission, 'a.', format: :json).gsub(/a\.\.json$/, '{filename}.json')
|
||||
json.run_url run_submission_path(@submission, 'a.', format: :json).gsub(/a\.\.json$/, '{filename}.json')
|
||||
json.test_url test_submission_path(@submission, 'a.', format: :json).gsub(/a\.\.json$/, '{filename}.json')
|
||||
|
@ -1,5 +1,5 @@
|
||||
$( document ).on('turbolinks:load', function() {
|
||||
var DURATION = 10000;
|
||||
var DURATION = 5000;
|
||||
var SEVERITIES = ['danger', 'info', 'success', 'warning'];
|
||||
|
||||
var buildFlash = function(options) {
|
||||
|
@ -1,6 +1,5 @@
|
||||
#flash-container {
|
||||
position: relative;
|
||||
top: -21px;
|
||||
}
|
||||
|
||||
.flash {
|
||||
|
@ -19,6 +19,7 @@ describe Lti do
|
||||
it 'clears the session' do
|
||||
expect(controller.session).to receive(:delete).with(:consumer_id)
|
||||
expect(controller.session).to receive(:delete).with(:external_user_id)
|
||||
expect(controller.session).to receive(:delete).with(:embed_options)
|
||||
controller.send(:clear_lti_session_data)
|
||||
end
|
||||
end
|
||||
|
@ -186,7 +186,7 @@ describe SubmissionsController do
|
||||
end
|
||||
|
||||
it 'ends with a placeholder' do
|
||||
expect(url).to end_with(Submission::FILENAME_URL_PLACEHOLDER)
|
||||
expect(url).to end_with(Submission::FILENAME_URL_PLACEHOLDER + '.json')
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -196,7 +196,7 @@ describe SubmissionsController do
|
||||
let(:url) { JSON.parse(response.body).with_indifferent_access.fetch("#{action}_url") }
|
||||
|
||||
it "corresponds to the #{action} path" do
|
||||
expect(url).to eq(Rails.application.routes.url_helpers.send(:"#{action}_submission_path", submission))
|
||||
expect(url).to eq(Rails.application.routes.url_helpers.send(:"#{action}_submission_path", submission, format: :json))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -10,6 +10,7 @@ describe 'exercises/implement.html.slim' do
|
||||
assign(:exercise, exercise)
|
||||
assign(:files, files)
|
||||
assign(:paths, [])
|
||||
assign(:embed_options, {})
|
||||
render
|
||||
end
|
||||
|
||||
|
Reference in New Issue
Block a user