Commit 6e784d60 by David Baumgold

Merge pull request #596 from edx/db/ff-subsection-datetime-bug

Fix Firefox subsection datetime bug
parents 2c78f062 97aaab9c
...@@ -24,7 +24,7 @@ Feature: Create Section ...@@ -24,7 +24,7 @@ Feature: Create Section
Given I have opened a new course in Studio Given I have opened a new course in Studio
And I have added a new section And I have added a new section
When I click the Edit link for the release date When I click the Edit link for the release date
And I save a new section release date And I set the section release date to 12/25/2013
Then the section release date is updated Then the section release date is updated
And I see a "saving" notification And I see a "saving" notification
......
...@@ -35,10 +35,15 @@ def i_click_the_edit_link_for_the_release_date(_step): ...@@ -35,10 +35,15 @@ def i_click_the_edit_link_for_the_release_date(_step):
world.css_click(button_css) world.css_click(button_css)
@step('I save a new section release date$') @step('I set the section release date to ([0-9/-]+)( [0-9:]+)?')
def i_save_a_new_section_release_date(_step): def set_section_release_date(_step, datestring, timestring):
set_date_and_time('input.start-date.date.hasDatepicker', '12/25/2013', if hasattr(timestring, "strip"):
'input.start-time.time.ui-timepicker-input', '00:00') timestring = timestring.strip()
if not timestring:
timestring = "00:00"
set_date_and_time(
'input.start-date.date.hasDatepicker', datestring,
'input.start-time.time.ui-timepicker-input', timestring)
world.browser.click_link_by_text('Save') world.browser.click_link_by_text('Save')
......
...@@ -14,7 +14,7 @@ Feature: Create Subsection ...@@ -14,7 +14,7 @@ Feature: Create Subsection
When I click the New Subsection link When I click the New Subsection link
And I enter a subsection name with a quote and click save And I enter a subsection name with a quote and click save
Then I see my subsection name with a quote on the Courseware page Then I see my subsection name with a quote on the Courseware page
And I click to edit the subsection name And I click on the subsection
Then I see the complete subsection name with a quote in the editor Then I see the complete subsection name with a quote in the editor
Scenario: Assign grading type to a subsection and verify it is still shown after refresh (bug #258) Scenario: Assign grading type to a subsection and verify it is still shown after refresh (bug #258)
...@@ -27,10 +27,13 @@ Feature: Create Subsection ...@@ -27,10 +27,13 @@ Feature: Create Subsection
Scenario: Set a due date in a different year (bug #256) Scenario: Set a due date in a different year (bug #256)
Given I have opened a new subsection in Studio Given I have opened a new subsection in Studio
And I have set a release date and due date in different years And I set the subsection release date to 12/25/2011 03:00
Then I see the correct dates And I set the subsection due date to 01/02/2012 04:00
Then I see the subsection release date is 12/25/2011 03:00
And I see the subsection due date is 01/02/2012 04:00
And I reload the page And I reload the page
Then I see the correct dates Then I see the subsection release date is 12/25/2011 03:00
And I see the subsection due date is 01/02/2012 04:00
Scenario: Delete a subsection Scenario: Delete a subsection
Given I have opened a new course section in Studio Given I have opened a new course section in Studio
...@@ -40,3 +43,16 @@ Feature: Create Subsection ...@@ -40,3 +43,16 @@ Feature: Create Subsection
And I press the "subsection" delete icon And I press the "subsection" delete icon
And I confirm the prompt And I confirm the prompt
Then the subsection does not exist Then the subsection does not exist
Scenario: Sync to Section
Given I have opened a new course section in Studio
And I click the Edit link for the release date
And I set the section release date to 01/02/2103
And I have added a new subsection
And I click on the subsection
And I set the subsection release date to 01/20/2103
And I reload the page
And I click the link to sync release date to section
And I wait for "1" second
And I reload the page
Then I see the subsection release date is 01/02/2103
...@@ -41,8 +41,8 @@ def i_save_subsection_name_with_quote(step): ...@@ -41,8 +41,8 @@ def i_save_subsection_name_with_quote(step):
save_subsection_name('Subsection With "Quote"') save_subsection_name('Subsection With "Quote"')
@step('I click to edit the subsection name$') @step('I click on the subsection$')
def i_click_to_edit_subsection_name(step): def click_on_subsection(step):
world.css_click('span.subsection-name-value') world.css_click('span.subsection-name-value')
...@@ -53,12 +53,28 @@ def i_see_complete_subsection_name_with_quote_in_editor(step): ...@@ -53,12 +53,28 @@ def i_see_complete_subsection_name_with_quote_in_editor(step):
assert_equal(world.css_value(css), 'Subsection With "Quote"') assert_equal(world.css_value(css), 'Subsection With "Quote"')
@step('I have set a release date and due date in different years$') @step('I set the subsection release date to ([0-9/-]+)( [0-9:]+)?')
def test_have_set_dates_in_different_years(step): def set_subsection_release_date(_step, datestring, timestring):
set_date_and_time('input#start_date', '12/25/2011', 'input#start_time', '03:00') if hasattr(timestring, "strip"):
world.css_click('.set-date') timestring = timestring.strip()
# Use a year in the past so that current year will always be different. if not timestring:
set_date_and_time('input#due_date', '01/02/2012', 'input#due_time', '04:00') timestring = "00:00"
set_date_and_time(
'input#start_date', datestring,
'input#start_time', timestring)
@step('I set the subsection due date to ([0-9/-]+)( [0-9:]+)?')
def set_subsection_due_date(_step, datestring, timestring):
if hasattr(timestring, "strip"):
timestring = timestring.strip()
if not timestring:
timestring = "00:00"
if not world.css_visible('input#due_date'):
world.css_click('.due-date-input .set-date')
set_date_and_time(
'input#due_date', datestring,
'input#due_time', timestring)
@step('I mark it as Homework$') @step('I mark it as Homework$')
...@@ -72,6 +88,11 @@ def i_see_it_marked__as_homework(step): ...@@ -72,6 +88,11 @@ def i_see_it_marked__as_homework(step):
assert_equal(world.css_value(".status-label"), 'Homework') assert_equal(world.css_value(".status-label"), 'Homework')
@step('I click the link to sync release date to section')
def click_sync_release_date(step):
world.css_click('.sync-date')
############ ASSERTIONS ################### ############ ASSERTIONS ###################
...@@ -91,16 +112,25 @@ def the_subsection_does_not_exist(step): ...@@ -91,16 +112,25 @@ def the_subsection_does_not_exist(step):
assert world.browser.is_element_not_present_by_css(css) assert world.browser.is_element_not_present_by_css(css)
@step('I see the correct dates$') @step('I see the subsection release date is ([0-9/-]+)( [0-9:]+)?')
def i_see_the_correct_dates(step): def i_see_subsection_release(_step, datestring, timestring):
assert_equal('12/25/2011', get_date('input#start_date')) if hasattr(timestring, "strip"):
assert_equal('03:00', get_date('input#start_time')) timestring = timestring.strip()
assert_equal('01/02/2012', get_date('input#due_date')) assert_equal(datestring, get_date('input#start_date'))
assert_equal('04:00', get_date('input#due_time')) if timestring:
assert_equal(timestring, get_date('input#start_time'))
############ HELPER METHODS ################### @step('I see the subsection due date is ([0-9/-]+)( [0-9:]+)?')
def i_see_subsection_due(_step, datestring, timestring):
if hasattr(timestring, "strip"):
timestring = timestring.strip()
assert_equal(datestring, get_date('input#due_date'))
if timestring:
assert_equal(timestring, get_date('input#due_time'))
############ HELPER METHODS ###################
def get_date(css): def get_date(css):
return world.css_find(css).first.value.strip() return world.css_find(css).first.value.strip()
......
describe "Course Overview", -> describe "Course Overview", ->
beforeEach -> beforeEach ->
appendSetFixtures """ _.each ["/static/js/vendor/date.js", "/static/js/vendor/timepicker/jquery.timepicker.js", "/jsi18n/"], (path) ->
<script src="/static/js/vendor/date.js"></script> appendSetFixtures """
""" <script type="text/javascript" src="#{path}"></script>
"""
appendSetFixtures """
<script type="text/javascript" src="/jsi18n/"></script>
"""
appendSetFixtures """ appendSetFixtures """
<div class="section-published-date"> <div class="section-published-date">
<span class="published-status"> <span class="published-status">
<strong>Will Release:</strong> 06/12/2013 at 04:00 UTC <strong>Will Release:</strong> 06/12/2013 at 04:00 UTC
</span> </span>
<a href="#" class="edit-button" "="" data-date="06/12/2013" data-time="04:00" data-id="i4x://pfogg/42/chapter/d6b47f7b084f49debcaf67fe5436c8e2">Edit</a> <a href="#" class="edit-button" data-date="06/12/2013" data-time="04:00" data-id="i4x://pfogg/42/chapter/d6b47f7b084f49debcaf67fe5436c8e2">Edit</a>
</div> </div>
"""#" """
appendSetFixtures """ appendSetFixtures """
<div class="edit-subsection-publish-settings"> <div class="edit-subsection-publish-settings">
...@@ -38,7 +35,7 @@ describe "Course Overview", -> ...@@ -38,7 +35,7 @@ describe "Course Overview", ->
<a href="#" class="save-button">Save</a><a href="#" class="cancel-button">Cancel</a> <a href="#" class="save-button">Save</a><a href="#" class="cancel-button">Cancel</a>
</div> </div>
</div> </div>
"""#" """
appendSetFixtures """ appendSetFixtures """
<section class="courseware-section branch" data-id="a-location-goes-here"> <section class="courseware-section branch" data-id="a-location-goes-here">
...@@ -46,12 +43,13 @@ describe "Course Overview", -> ...@@ -46,12 +43,13 @@ describe "Course Overview", ->
<a href="#" class="delete-section-button"></a> <a href="#" class="delete-section-button"></a>
</li> </li>
</section> </section>
"""#" """
spyOn(window, 'saveSetSectionScheduleDate').andCallThrough() spyOn(window, 'saveSetSectionScheduleDate').andCallThrough()
# Have to do this here, as it normally gets bound in document.ready() # Have to do this here, as it normally gets bound in document.ready()
$('a.save-button').click(saveSetSectionScheduleDate) $('a.save-button').click(saveSetSectionScheduleDate)
$('a.delete-section-button').click(deleteSection) $('a.delete-section-button').click(deleteSection)
$(".edit-subsection-publish-settings .start-date").datepicker()
@notificationSpy = spyOn(CMS.Views.Notification.Mini.prototype, 'show').andCallThrough() @notificationSpy = spyOn(CMS.Views.Notification.Mini.prototype, 'show').andCallThrough()
window.analytics = jasmine.createSpyObj('analytics', ['track']) window.analytics = jasmine.createSpyObj('analytics', ['track'])
......
...@@ -253,21 +253,20 @@ function syncReleaseDate(e) { ...@@ -253,21 +253,20 @@ function syncReleaseDate(e) {
$("#start_time").val(""); $("#start_time").val("");
} }
function getEdxTimeFromDateTimeVals(date_val, time_val) { function getDatetime(datepickerInput, timepickerInput) {
if (date_val != '') { // given a pair of inputs (datepicker and timepicker), return a JS Date
if (time_val == '') time_val = '00:00'; // object that corresponds to the datetime that they represent. Assume
// UTC timezone, NOT the timezone of the user's browser.
return new Date(date_val + " " + time_val + "Z"); var date = $(datepickerInput).datepicker("getDate");
var time = $(timepickerInput).timepicker("getTime");
if(date && time) {
return new Date(Date.UTC(
date.getFullYear(), date.getMonth(), date.getDate(),
time.getHours(), time.getMinutes()
));
} else {
return null;
} }
else return null;
}
function getEdxTimeFromDateTimeInputs(date_id, time_id) {
var input_date = $('#' + date_id).val();
var input_time = $('#' + time_id).val();
return getEdxTimeFromDateTimeVals(input_date, input_time);
} }
function autosaveInput(e) { function autosaveInput(e) {
...@@ -307,9 +306,17 @@ function saveSubsection() { ...@@ -307,9 +306,17 @@ function saveSubsection() {
metadata[$(el).data("metadata-name")] = el.value; metadata[$(el).data("metadata-name")] = el.value;
} }
// Piece back together the date/time UI elements into one date/time string // get datetimes for start and due, stick into metadata
metadata['start'] = getEdxTimeFromDateTimeInputs('start_date', 'start_time'); _(["start", "due"]).each(function(name) {
metadata['due'] = getEdxTimeFromDateTimeInputs('due_date', 'due_time');
var datetime = getDatetime(
document.getElementById(name+"_date"),
document.getElementById(name+"_time")
);
// if datetime is null, we want to set that in metadata anyway;
// its an indication to the server to clear the datetime in the DB
metadata[name] = datetime;
});
$.ajax({ $.ajax({
url: "/save_item", url: "/save_item",
...@@ -772,21 +779,21 @@ function cancelSetSectionScheduleDate(e) { ...@@ -772,21 +779,21 @@ function cancelSetSectionScheduleDate(e) {
function saveSetSectionScheduleDate(e) { function saveSetSectionScheduleDate(e) {
e.preventDefault(); e.preventDefault();
var input_date = $('.edit-subsection-publish-settings .start-date').val(); var datetime = getDatetime(
var input_time = $('.edit-subsection-publish-settings .start-time').val(); $('.edit-subsection-publish-settings .start-date'),
$('.edit-subsection-publish-settings .start-time')
var start = getEdxTimeFromDateTimeVals(input_date, input_time); );
var id = $modal.attr('data-id'); var id = $modal.attr('data-id');
analytics.track('Edited Section Release Date', { analytics.track('Edited Section Release Date', {
'course': course_location_analytics, 'course': course_location_analytics,
'id': id, 'id': id,
'start': start 'start': datetime
}); });
var saving = new CMS.Views.Notification.Mini({ var saving = new CMS.Views.Notification.Mini({
title: gettext("Saving") + "&hellip;", title: gettext("Saving") + "&hellip;"
}); });
saving.show(); saving.show();
// call into server to commit the new order // call into server to commit the new order
...@@ -798,20 +805,29 @@ function saveSetSectionScheduleDate(e) { ...@@ -798,20 +805,29 @@ function saveSetSectionScheduleDate(e) {
data: JSON.stringify({ data: JSON.stringify({
'id': id, 'id': id,
'metadata': { 'metadata': {
'start': start 'start': datetime
} }
}) })
}).success(function() { }).success(function() {
var pad2 = function(number) {
// pad a number to two places: useful for formatting months, days, hours, etc
// when displaying a date/time
return (number < 10 ? '0' : '') + number;
};
var $thisSection = $('.courseware-section[data-id="' + id + '"]'); var $thisSection = $('.courseware-section[data-id="' + id + '"]');
var html = _.template( var html = _.template(
'<span class="published-status">' + '<span class="published-status">' +
'<strong>' + gettext("Will Release:") + '&nbsp;</strong>' + '<strong>' + gettext("Will Release:") + '&nbsp;</strong>' +
gettext("<%= date %> at <%= time %> UTC") + gettext("{month}/{day}/{year} at {hour}:{minute} UTC") +
'</span>' + '</span>' +
'<a href="#" class="edit-button" data-date="<%= date %>" data-time="<%= time %>" data-id="<%= id %>">' + '<a href="#" class="edit-button" data-date="{month}/{day}/{year}" data-time="{hour}:{minute}" data-id="{id}">' +
gettext("Edit") + gettext("Edit") +
'</a>', '</a>',
{date: input_date, time: input_time, id: id}); {year: datetime.getUTCFullYear(), month: pad2(datetime.getUTCMonth() + 1), day: pad2(datetime.getUTCDate()),
hour: pad2(datetime.getUTCHours()), minute: pad2(datetime.getUTCMinutes()),
id: id},
{interpolate: /\{(.+?)\}/g});
$thisSection.find('.section-published-date').html(html); $thisSection.find('.section-published-date').html(html);
hideModal(); hideModal();
saving.hide(); saving.hide();
......
...@@ -148,7 +148,7 @@ function generateCheckHoverState(selectorsToOpen, selectorsToShove) { ...@@ -148,7 +148,7 @@ function generateCheckHoverState(selectorsToOpen, selectorsToShove) {
} }
}); });
} };
} }
function removeHesitate(event, ui) { function removeHesitate(event, ui) {
......
<%! from django.utils.translation import ugettext as _ %>
<%inherit file="base.html" /> <%inherit file="base.html" />
<%! <%!
import logging import logging
from xmodule.util.date_utils import get_default_time_display, almost_same_datetime from xmodule.util.date_utils import get_default_time_display, almost_same_datetime
from django.utils.translation import ugettext as _
from django.core.urlresolvers import reverse
%> %>
<%! from django.core.urlresolvers import reverse %>
<%block name="title">${_("CMS Subsection")}</%block> <%block name="title">${_("CMS Subsection")}</%block>
<%block name="bodyclass">is-signedin course subsection</%block> <%block name="bodyclass">is-signedin course subsection</%block>
......
<%! from django.utils.translation import ugettext as _ %>
<%inherit file="base.html" /> <%inherit file="base.html" />
<%! <%!
import logging import logging
from xmodule.util import date_utils from xmodule.util import date_utils
from django.utils.translation import ugettext as _
from django.core.urlresolvers import reverse
%> %>
<%! from django.core.urlresolvers import reverse %>
<%block name="title">${_("Course Outline")}</%block> <%block name="title">${_("Course Outline")}</%block>
<%block name="bodyclass">is-signedin course outline</%block> <%block name="bodyclass">is-signedin course outline</%block>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment