completes #17 - live-coder plus text input type

Squashed commit of the following:

commit d41cbf66eb2a9ee705ab60bb156eed95881fa193
Author: Mark Moser <mark.moser@perficient.com>
Date:   Thu Aug 4 19:58:20 2016 -0500

    live-coder-text validations

commit 866bfeb863516a8656bc26b10f967d0b9b8d8505
Author: Mark Moser <mark.moser@perficient.com>
Date:   Thu Aug 4 11:57:57 2016 -0500

    getting things green again and rebasing develop

commit 28a23200f291e30c690b71e9bcae3e1a69eb3093
Author: Derek Montgomery <montgomerygraphics@gmail.com>
Date:   Thu Aug 4 10:14:23 2016 -0500

    Progress on live coder text field
This commit is contained in:
Mark Moser 2016-08-04 20:01:17 -05:00
parent 635297884d
commit fc784ffcb1
11 changed files with 136 additions and 23 deletions

View File

@ -47,6 +47,7 @@ Metrics/AbcSize:
Metrics/ClassLength: Metrics/ClassLength:
Exclude: Exclude:
- app/controllers/candidate_controller.rb - app/controllers/candidate_controller.rb
- test/**/*
Metrics/LineLength: Metrics/LineLength:
Max: 110 Max: 110

View File

@ -93,7 +93,7 @@ function indentSelection(e){
} }
function loadLiveCoders(){ function loadLiveCoders(){
$.each($('.answer-sec.live_code-type'), function(index, elem){ $.each($('.answer-sec.live_code-type, .answer-sec.live_code_text-type'), function(index, elem){
var qid = $(elem).data('qid'); var qid = $(elem).data('qid');
$(elem).find("[data-id='live-coder-answer']").load("/live-coder-entry/" + qid, function(){ $(elem).find("[data-id='live-coder-answer']").load("/live-coder-entry/" + qid, function(){
$(elem).find('.js-error').addClass('hidden'); $(elem).find('.js-error').addClass('hidden');
@ -112,7 +112,7 @@ $(function(){
// wait a half second before updating results // wait a half second before updating results
// restart the timer if they resume typing // restart the timer if they resume typing
$('html').on('keyup', '.code-input textarea', function(){ $('html').on('keyup', '.code-input textarea', function(){
var elem = $(this).closest('.answer-sec.live_code-type'); var elem = $(this).closest('.answer-sec.live_code-type, .answer-sec.live_code_text-type');
if (timer) { clearTimeout(timer); } if (timer) { clearTimeout(timer); }
timer = setTimeout(updateResults(elem), 500); timer = setTimeout(updateResults(elem), 500);
}); });

View File

@ -173,7 +173,7 @@ $('.answer-sec')
.on('click', '.button-save', saveClickHandler); .on('click', '.button-save', saveClickHandler);
// Dynamically load in coders // Dynamically load in coders
$.each($('.answer-sec.live-coder-type'), function(index, elem){ $.each($('.answer-sec.live_code-type, .answer-sec.live_code_text-type'), function(index, elem){
var qid = $(elem).data('qid'); var qid = $(elem).data('qid');
$(elem).find("[data-id='live-coder-answer']").load("/live-coder-entry/" + qid, function(){ $(elem).find("[data-id='live-coder-answer']").load("/live-coder-entry/" + qid, function(){
$(elem).find('.js-error').addClass('hidden'); $(elem).find('.js-error').addClass('hidden');

View File

@ -42,7 +42,7 @@ class CandidateController < ApplicationController
prep_question params[:question_id] prep_question params[:question_id]
prep_instance_answer @question prep_instance_answer @question
prep_answer params[:question_id] prep_answer params[:question_id]
render layout: false render @question.input_type, layout: false
end end
def summary def summary
@ -95,8 +95,13 @@ class CandidateController < ApplicationController
def answer_params def answer_params
params.require(:answer).permit( params.require(:answer).permit(
:question_id, :answer_id, :question_id,
:radio, :text, checkbox: [], live_code: [:later, :html, :css, :js] :answer_id,
:radio,
:text,
checkbox: [],
live_code: [:later, :html, :css, :js],
live_code_text: [:later, :html, :css, :js, :text]
) )
end end
@ -148,4 +153,11 @@ class CandidateController < ApplicationController
submitted: params.key?(:submit)) submitted: params.key?(:submit))
route_answer route_answer
end end
def process_live_code_text
@answer.update(answer: answer_params[:live_code_text].to_h,
saved: params.key?(:save),
submitted: params.key?(:submit))
route_answer
end
end end

View File

@ -14,7 +14,7 @@ class AnswerFormatValidator < ActiveModel::EachValidator
end end
if clean_val.length > 1000 if clean_val.length > 1000
record.errors[attribute] << (options[:message] || "The character limit for a textarea answer is 1000") record.errors[attribute] << (options[:message] || "The character limit for this answer is 1000.")
end end
end end
@ -33,11 +33,40 @@ class AnswerFormatValidator < ActiveModel::EachValidator
def live_code record, attribute, value def live_code record, attribute, value
return unless value.nil? || value.values.join.blank? return unless value.nil? || value.values.join.blank?
msg = if value.present? && value.keys.count == 1 record.errors[attribute] << (options[:message] || live_code_error_message(value))
"Please check that you will come back to complete the code example." end
else
def live_code_text record, attribute, value
return if value.present? && live_code_text_value_check(value)
record.errors[attribute] << (options[:message] || live_code_text_error_message(value))
end
#################################
def live_code_text_error_message value
return 'You must write code in one of the above textareas to progress.' if value.nil?
code_present = value.values_at(:html, :css, :js).join.present?
reason_present = value[:text].present?
later_present = value.keys.count == 1
return 'Please check that you will come back to complete the code example.' if later_present
return 'You must write code in one of the above textareas to progress.' unless code_present
return 'You must provide an answer in the reason field.' unless reason_present
end
def live_code_error_message value
if value.present? && value.keys.count == 1
return "Please check that you will come back to complete the code example."
end
"You must write code in one of the above textareas to progress." "You must write code in one of the above textareas to progress."
end end
record.errors[attribute] << (options[:message] || msg)
def live_code_text_value_check value
later_present = value[:later].present? && value.keys.count == 1
code_present = value.values_at(:html, :css, :js).join.present?
reason_present = value[:text].present?
later_present || (code_present && reason_present)
end end
end end

View File

@ -0,0 +1 @@
<%= render partial: "candidate/live_code", locals: {question: question, form: form} %>

View File

@ -0,0 +1,21 @@
<% answer = @answer.answer || {} %>
<label for="answer_live_code_text">Reasoning</label>
<%= text_area_tag 'answer[live_code_text][text]', (answer['text']) %>
<div class="code-input">
<label for="answer_live_code_html">HTML</label>
<%= text_area_tag 'answer[live_code_text][html]', (answer['html']), { 'data-id' => 'code-html', class: 'code-answer code-html' } %>
</div>
<div class="code-input">
<label for="answer_live_code_css">CSS</label>
<%= text_area_tag 'answer[live_code_text][css]', (answer['css']), { 'data-id' => 'code-css', class: 'code-answer code-css' } %>
</div>
<div class="code-input">
<label for="answer_live_code_js">JS</label>
<%= text_area_tag 'answer[live_code_text][js]', (answer['js']), { 'data-id' => 'code-js', class: 'code-answer code-js' } %>
</div>
<div class="results" data-id="results"></div>

View File

@ -39,7 +39,7 @@ dawn2:
dawn3: dawn3:
candidate: dawn candidate: dawn
question: fed3 question: fed3
answer: {html: "dawn3 <h1>I'm a little tealpot</h1>", css: 'h1 {color: teal;}', js: ''} answer: {html: "<h1>I'm a little tealpot</h1>", css: 'h1 {color: teal;}', js: '', text: 'I did this because reasons.'}
saved: 0 saved: 0
submitted: true submitted: true
created_at: <%= DateTime.now() - 38.hours - 50.minutes %> created_at: <%= DateTime.now() - 38.hours - 50.minutes %>
@ -75,7 +75,7 @@ dawn6:
dawn7: dawn7:
candidate: dawn candidate: dawn
question: fed7 question: fed7
answer: {html: 'dawn7 <p>This means <strong>jQuery</strong> needs to be available in live-coder!</p>', css: "strong {font-size: 1.6em;}\n.green {color: green;}", js: '$("strong").addClass("green");'} answer: {html: '<p>This means <strong>jQuery</strong> needs to be available in live-coder!</p>', css: "strong {font-size: 1.6em;}\n.green {color: green;}", js: '$("strong").addClass("green");'}
saved: 0 saved: 0
submitted: true submitted: true
created_at: <%= DateTime.now() - 38.hours - 34.minutes %> created_at: <%= DateTime.now() - 38.hours - 34.minutes %>
@ -129,7 +129,7 @@ peggy2:
peggy3: peggy3:
candidate: peggy candidate: peggy
question: fed3 question: fed3
answer: {html: "peggy3 <h1>I'm a little tealpot</h1>", css: 'h1 {color: teal;}', js: ''} answer: {html: "<h1>I'm a little tealpot</h1>", css: 'h1 {color: teal;}', js: '', text: 'I like turtles.'}
saved: 0 saved: 0
submitted: true submitted: true
created_at: <%= DateTime.now() - 38.hours - 50.minutes %> created_at: <%= DateTime.now() - 38.hours - 50.minutes %>
@ -165,7 +165,7 @@ peggy6:
peggy7: peggy7:
candidate: peggy candidate: peggy
question: fed7 question: fed7
answer: {html: 'peggy7 <p>This means <strong>jQuery</strong> needs to be available in live-coder!</p>', css: "strong {font-size: 1.6em;}\n.green {color: green;}", js: '$("strong").addClass("green");'} answer: {html: '<p>This means <strong>jQuery</strong> needs to be available in live-coder!</p>', css: "strong {font-size: 1.6em;}\n.green {color: green;}", js: '$("strong").addClass("green");'}
saved: 0 saved: 0
submitted: true submitted: true
created_at: <%= DateTime.now() - 38.hours - 34.minutes %> created_at: <%= DateTime.now() - 38.hours - 34.minutes %>
@ -220,7 +220,7 @@ richard2:
richard3: richard3:
candidate: richard candidate: richard
question: fed3 question: fed3
answer: {html: 'richard3 <h1>Salmon</h1>', css: 'h1 {color: salmon;}', js: ''} answer: {html: '<h1>Salmon</h1>', css: 'h1 {color: salmon;}', js: '', text: 'Gotta lotta GOOD things on sale, strangah.'}
saved: 0 saved: 0
submitted: true submitted: true
created_at: <%= DateTime.now() - 36.hours - 26.minutes %> created_at: <%= DateTime.now() - 36.hours - 26.minutes %>
@ -256,7 +256,7 @@ richard6:
richard7: richard7:
candidate: richard candidate: richard
question: fed7 question: fed7
answer: {html: 'richard7 <p>This means <strong>jQuery</strong> needs to be available in live-coder!</p>', css: "strong {font-size: 1.6em;}\n.green {color: green;}", js: '$("strong").addClass("green");'} answer: {html: '<p>This means <strong>jQuery</strong> needs to be available in live-coder!</p>', css: "strong {font-size: 1.6em;}\n.green {color: green;}", js: '$("strong").addClass("green");'}
saved: 0 saved: 0
submitted: true submitted: true
created_at: <%= DateTime.now() - 36.hours - 34.minutes %> created_at: <%= DateTime.now() - 36.hours - 34.minutes %>

View File

@ -22,7 +22,7 @@ fed3:
quiz: fed quiz: fed
question: How would you create a widget that would fit in a 250px wide area as well as a 400px wide area? question: How would you create a widget that would fit in a 250px wide area as well as a 400px wide area?
category: CSS category: CSS
input_type: live_code input_type: live_code_text
input_options: input_options:
sort: 2 sort: 2
active: true active: true

View File

@ -77,7 +77,7 @@ class AnswerFormatValidatorTest < ActiveSupport::TestCase
obj.answer = SecureRandom.urlsafe_base64(1001) obj.answer = SecureRandom.urlsafe_base64(1001)
refute obj.valid? refute obj.valid?
assert_match(/char.*limit.*1000$/, obj.errors.messages[:answer][0]) assert_match(/char.*limit.*1000.$/, obj.errors.messages[:answer][0])
end end
# input_type CHECK BOX # input_type CHECK BOX
@ -141,7 +141,7 @@ class AnswerFormatValidatorTest < ActiveSupport::TestCase
test "live_code should FAIL without checking finish later" do test "live_code should FAIL without checking finish later" do
obj = AnswerValidatable.new('live_code') obj = AnswerValidatable.new('live_code')
obj.answer = { "later" => "" } obj.answer = { later: "" }
refute obj.valid? refute obj.valid?
assert_match(/come back/, obj.errors.messages[:answer][0]) assert_match(/come back/, obj.errors.messages[:answer][0])
@ -149,9 +149,58 @@ class AnswerFormatValidatorTest < ActiveSupport::TestCase
test "live_code should FAIL without values" do test "live_code should FAIL without values" do
obj = AnswerValidatable.new('live_code') obj = AnswerValidatable.new('live_code')
obj.answer = { "later" => "", "html" => "", "css" => "", "js" => "" } obj.answer = { later: "", html: "", css: "", js: "" }
refute obj.valid? refute obj.valid?
assert_match(/write.*code/, obj.errors.messages[:answer][0]) assert_match(/write.*code/, obj.errors.messages[:answer][0])
end end
# input_type LIVE CODER TEXT
test "live_code_text should PASS with populated hash" do
obj = AnswerValidatable.new('live_code_text')
obj.answer = { html: 'this is html', css: '', js: '', text: 'some reasons' }
assert obj.valid?
assert obj.errors.messages.empty?
end
test "live_code_text should PASS with finish later" do
obj = AnswerValidatable.new('live_code_text')
obj.answer = { later: "true" }
assert obj.valid?
assert obj.errors.messages.empty?
end
test "live_code_text should FAIL with nil" do
obj = AnswerValidatable.new('live_code_text')
obj.answer = nil
refute obj.valid?
assert_match(/write.*code/, obj.errors.messages[:answer][0])
end
test "live_code_text should FAIL without checking finish later" do
obj = AnswerValidatable.new('live_code_text')
obj.answer = { later: "" }
refute obj.valid?
assert_match(/come back/, obj.errors.messages[:answer][0])
end
test "live_code_text should FAIL without values" do
obj = AnswerValidatable.new('live_code_text')
obj.answer = { later: "", html: "", css: "", js: "", text: "" }
refute obj.valid?
assert_match(/write.*code/, obj.errors.messages[:answer][0])
end
test "live_code_text should FAIL without text" do
obj = AnswerValidatable.new('live_code_text')
obj.answer = { later: "", html: "some html", css: "", js: "", text: "" }
refute obj.valid?
assert_match(/provide.*reason/, obj.errors.messages[:answer][0])
end
end end