From e9a23b46954be110a9f0552c1df53258e9c2f172 Mon Sep 17 00:00:00 2001 From: Raphael Catolino Date: Sat, 22 Dec 2012 15:46:19 +0100 Subject: [PATCH] Bug 769359 - Adds step attribute and stepUp/stepDown method support for . r=mounir --- .../html/content/src/nsHTMLInputElement.cpp | 142 ++++++--- content/html/content/src/nsHTMLInputElement.h | 24 ++ .../test/forms/test_step_attribute.html | 128 +++++++- .../test/forms/test_stepup_stepdown.html | 294 +++++++++++++----- 4 files changed, 472 insertions(+), 116 deletions(-) diff --git a/content/html/content/src/nsHTMLInputElement.cpp b/content/html/content/src/nsHTMLInputElement.cpp index 1b5a6ee1db22..d783ab4cd43b 100644 --- a/content/html/content/src/nsHTMLInputElement.cpp +++ b/content/html/content/src/nsHTMLInputElement.cpp @@ -88,6 +88,7 @@ #include "mozilla/LookAndFeel.h" #include "mozilla/Util.h" // DebugOnly #include "mozilla/Preferences.h" +#include "mozilla/MathAlgorithms.h" #include "nsIIDNService.h" @@ -176,6 +177,8 @@ static const nsAttrValue::EnumTable kInputInputmodeTable[] = { // Default inputmode value is "auto". static const nsAttrValue::EnumTable* kInputDefaultInputmode = &kInputInputmodeTable[0]; +const double nsHTMLInputElement::kStepScaleFactorDate = 86400000; +const double nsHTMLInputElement::kStepScaleFactorNumber = 1; const double nsHTMLInputElement::kDefaultStepBase = 0; const double nsHTMLInputElement::kStepAny = 0; @@ -1201,43 +1204,54 @@ void nsHTMLInputElement::SetValue(double aValue) { nsAutoString value; + ConvertNumberToString(aValue, value); + SetValue(value); +} + +bool +nsHTMLInputElement::ConvertNumberToString(double aValue, + nsAString& aResultString) const +{ + MOZ_ASSERT(mType == NS_FORM_INPUT_DATE || mType == NS_FORM_INPUT_NUMBER, + "ConvertNumberToString is only implemented for type='{number,date}'"); + + aResultString.Truncate(); + switch (mType) { case NS_FORM_INPUT_NUMBER: - value.AppendFloat(aValue); - break; + aResultString.AppendFloat(aValue); + return true; case NS_FORM_INPUT_DATE: - { - value.Truncate(); - JSContext* ctx = nsContentUtils::GetContextFromDocument(OwnerDoc()); - if (!ctx) { - break; - } + { + JSContext* ctx = nsContentUtils::GetContextFromDocument(OwnerDoc()); + if (!ctx) { + return false; + } - JSObject* date = JS_NewDateObjectMsec(ctx, aValue); - if (!date) { - break; - } + // The specs require |aValue| to be truncated. + aValue = floor(aValue); - jsval year, month, day; - if(!JS::Call(ctx, date, "getUTCFullYear", 0, nullptr, &year)) { - break; - } + JSObject* date = JS_NewDateObjectMsec(ctx, aValue); + if (!date) { + return false; + } - if(!JS::Call(ctx, date, "getUTCMonth", 0, nullptr, &month)) { - break; - } + jsval year, month, day; + if (!JS::Call(ctx, date, "getUTCFullYear", 0, nullptr, &year) || + !JS::Call(ctx, date, "getUTCMonth", 0, nullptr, &month) || + !JS::Call(ctx, date, "getUTCDate", 0, nullptr, &day)) { + return false; + } - if(!JS::Call(ctx, date, "getUTCDate", 0, nullptr, &day)) { - break; - } + aResultString.AppendPrintf("%04.0f-%02.0f-%02.0f", year.toNumber(), + month.toNumber() + 1, day.toNumber()); - value.AppendPrintf("%04.0f-%02.0f-%02.0f", year.toNumber(), - month.toNumber() + 1, day.toNumber()); - } - break; + return true; + } + default: + MOZ_NOT_REACHED(); + return false; } - - SetValue(value); } NS_IMETHODIMP @@ -1415,6 +1429,21 @@ nsHTMLInputElement::ApplyStep(int32_t aStep) value += aStep * step; + // For date inputs, the value can hold a string that is not a day. We do not + // want to round it, as it might result in a step mismatch. Instead we want to + // clamp to the next valid value. + if (mType == NS_FORM_INPUT_DATE && + NS_floorModulo(value - GetStepBase(), GetStepScaleFactor()) != 0) { + double validStep = EuclidLCM(static_cast(step), + static_cast(GetStepScaleFactor())); + if (aStep > 0) { + value -= NS_floorModulo(value - GetStepBase(), validStep); + value += validStep; + } else if (aStep < 0) { + value -= NS_floorModulo(value - GetStepBase(), validStep); + } + } + // When stepUp() is called and the value is below min, we should clamp on // min unless stepUp() moves us higher than min. if (GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW) && aStep > 0 && @@ -4255,11 +4284,10 @@ nsHTMLInputElement::DoesMinMaxApply() const double nsHTMLInputElement::GetStep() const { - NS_ASSERTION(mType == NS_FORM_INPUT_NUMBER, - "We can't be there if type!=number!"); + MOZ_ASSERT(mType == NS_FORM_INPUT_NUMBER || mType == NS_FORM_INPUT_DATE, + "We can't be there if type!=number or date!"); - // NOTE: should be defaultStep * defaultStepScaleFactor, - // which is 1 for type=number. + // NOTE: should be defaultStep, which is 1 for type=number and date. double step = 1; if (HasAttr(kNameSpaceID_None, nsGkAtoms::step)) { @@ -4272,17 +4300,16 @@ nsHTMLInputElement::GetStep() const } nsresult ec; - // NOTE: should be multiplied by defaultStepScaleFactor, - // which is 1 for type=number. step = stepStr.ToDouble(&ec); if (NS_FAILED(ec) || step <= 0) { - // NOTE: we should use defaultStep * defaultStepScaleFactor, - // which is 1 for type=number. + // NOTE: we should use defaultStep, which is 1 for type=number and date. step = 1; } } - return step; + // TODO: This multiplication can lead to inexact results, we should use a + // type that supports a better precision than double. Bug 783607. + return step * GetStepScaleFactor(); } // nsIConstraintValidation @@ -4473,6 +4500,15 @@ nsHTMLInputElement::HasStepMismatch() const return false; } + if (mType == NS_FORM_INPUT_DATE) { + // The multiplication by the stepScaleFactor for date can easily lead + // to precision loss, since in most use cases this value should be + // an integer (millisecond precision), we can get rid of the precision + // loss by rounding step. This will however lead to erroneous results + // when step was intented to have a precision superior to a millisecond. + step = NS_round(step); + } + // Value has to be an integral multiple of step. return NS_floorModulo(value - GetStepBase(), step) != 0; } @@ -4748,6 +4784,16 @@ nsHTMLInputElement::GetValidationMessage(nsAString& aValidationMessage, double step = GetStep(); MOZ_ASSERT(step != kStepAny); + // In case this is a date and the step is not an integer, we don't want to + // display the dates corresponding to the truncated timestamps of valueLow + // and valueHigh because they might suffer from a step mismatch as well. + // Instead we want the timestamps to correspond to a rounded day. That is, + // we want a multiple of the step scale factor (1 day) as well as of step. + if (mType == NS_FORM_INPUT_DATE) { + step = EuclidLCM(static_cast(step), + static_cast(GetStepScaleFactor())); + } + double stepBase = GetStepBase(); double valueLow = value - NS_floorModulo(value - stepBase, step); @@ -4757,8 +4803,8 @@ nsHTMLInputElement::GetValidationMessage(nsAString& aValidationMessage, if (MOZ_DOUBLE_IS_NaN(max) || valueHigh <= max) { nsAutoString valueLowStr, valueHighStr; - valueLowStr.AppendFloat(valueLow); - valueHighStr.AppendFloat(valueHigh); + ConvertNumberToString(valueLow, valueLowStr); + ConvertNumberToString(valueHigh, valueHighStr); const PRUnichar* params[] = { valueLowStr.get(), valueHighStr.get() }; rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES, @@ -4766,7 +4812,7 @@ nsHTMLInputElement::GetValidationMessage(nsAString& aValidationMessage, params, message); } else { nsAutoString valueLowStr; - valueLowStr.AppendFloat(valueLow); + ConvertNumberToString(valueLow, valueLowStr); const PRUnichar* params[] = { valueLowStr.get() }; rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES, @@ -5183,6 +5229,22 @@ nsHTMLInputElement::GetFilterFromAccept() return filter; } +double +nsHTMLInputElement::GetStepScaleFactor() const +{ + MOZ_ASSERT(DoesStepApply()); + + switch (mType) { + case NS_FORM_INPUT_DATE: + return kStepScaleFactorDate; + case NS_FORM_INPUT_NUMBER: + return kStepScaleFactorNumber; + default: + MOZ_NOT_REACHED(); + return MOZ_DOUBLE_NaN(); + } +} + void nsHTMLInputElement::UpdateValidityUIBits(bool aIsFocused) { diff --git a/content/html/content/src/nsHTMLInputElement.h b/content/html/content/src/nsHTMLInputElement.h index 60db0fed763f..372cdecfa89c 100644 --- a/content/html/content/src/nsHTMLInputElement.h +++ b/content/html/content/src/nsHTMLInputElement.h @@ -571,6 +571,19 @@ protected: */ bool ConvertStringToNumber(nsAString& aValue, double& aResultValue) const; + /** + * Convert a double to a string in a type specific way, ie convert a timestamp + * to a date string if type=date or append the number string representing the + * value if type=number. + * + * @param aValue the double to be converted + * @param aResultString [out] the string representing the double + * @return whether the function succeded, it will fail if the current input's + * type is not supported or the number can't be converted to a string + * as expected by the type. + */ + bool ConvertNumberToString(double aValue, nsAString& aResultString) const; + /** * Parse a date string of the form yyyy-mm-dd * @param the string to be parsed. @@ -619,6 +632,13 @@ protected: */ double GetMaxAsDouble() const; + /** + * Get the step scale value for the current type. + * See: + * http://www.whatwg.org/specs/web-apps/current-work/multipage/common-input-element-attributes.html#concept-input-step-scale + */ + double GetStepScaleFactor() const; + /** * Returns the current step value. * Returns kStepAny if the current step is "any" string. @@ -688,6 +708,10 @@ protected: */ nsString mFocusedValue; + // Step scale factor values, for input types that have one. + static const double kStepScaleFactorDate; + static const double kStepScaleFactorNumber; + // Default step base value when a type do not have specific one. static const double kDefaultStepBase; // Float alue returned by GetStep() when the step attribute is set to 'any'. diff --git a/content/html/content/test/forms/test_step_attribute.html b/content/html/content/test/forms/test_step_attribute.html index 751494b5bf54..b8c5e16e784c 100644 --- a/content/html/content/test/forms/test_step_attribute.html +++ b/content/html/content/test/forms/test_step_attribute.html @@ -28,7 +28,7 @@ var types = [ [ 'email', false ], [ 'password', false ], [ 'datetime', true, true ], - [ 'date', true, true ], + [ 'date', true ], [ 'month', true, true ], [ 'week', true, true ], [ 'time', true, true ], @@ -115,6 +115,132 @@ for (var data of types) { checkValidity(input, true, apply); file.remove(false); + } else if (input.type == 'date') { + // For date, the step is calulated on the timestamp since 1970-01-01 + // which mean that for all dates prior to the epoch, this timestamp is < 0 + // and the behavior might differ, therefore we have to test for these cases. + + // When step is 1 every date is valid + input.value = '2012-07-05'; + checkValidity(input, true, apply); + + input.step = 'foo'; + input.value = '1970-01-01'; + checkValidity(input, true, apply); + + input.step = '-1'; + input.value = '1969-12-12'; + checkValidity(input, true, apply); + + input.removeAttribute('step'); + input.value = '1500-01-01'; + checkValidity(input, true, apply); + + input.step = 'any'; + checkValidity(input, true, apply); + + input.step = 'aNy'; + checkValidity(input, true, apply); + + input.step = 'AnY'; + checkValidity(input, true, apply); + + input.step = 'ANY'; + checkValidity(input, true, apply); + + // When min is set to a valid date, there is a step base. + input.min = '2008-02-28'; + input.step = '2'; + input.value = '2008-03-01'; + checkValidity(input, true, apply); + + input.value = '2008-02-29'; + checkValidity(input, false, apply, { low: "2008-02-28", high: "2008-03-01" }); + + input.min = '2008-02-27'; + input.value = '2008-02-28'; + checkValidity(input, false, apply, { low: "2008-02-27", high: "2008-02-29" }); + + input.min = '2009-02-27'; + input.value = '2009-02-28'; + checkValidity(input, false, apply, { low: "2009-02-27", high: "2009-03-01" }); + + input.min = '2009-02-01'; + input.step = '1.1'; + input.value = '2009-02-02'; + checkValidity(input, false, apply, { low: "2009-02-01", high: "2009-02-12" }); + + // Without any step attribute the date is valid + input.removeAttribute('step'); + checkValidity(input, true, apply); + + input.min = '1950-01-01'; + input.step = '366'; + input.value = '1951-01-01'; + checkValidity(input, false, apply, { low: "1950-01-01", high: "1951-01-02" }); + + input.min = '1951-01-01'; + input.step = '365'; + input.value = '1952-01-01'; + checkValidity(input, true, apply); + + input.step = '0.9'; + input.value = '1951-01-02'; + checkValidity(input, false, apply, { low: "1951-01-01", high: "1951-01-10" }); + + input.value = '1951-01-10' + checkValidity(input, true, apply); + + input.step = '0.5'; + input.value = '1951-01-02'; + checkValidity(input, true, apply); + + input.step = '1.5'; + input.value = '1951-01-03'; + checkValidity(input, false, apply, { low: "1951-01-01", high: "1951-01-04" }); + + input.value = '1951-01-08'; + checkValidity(input, false, apply, { low: "1951-01-07", high: "1951-01-10" }); + + input.step = '3000'; + input.min= '1968-01-01'; + input.value = '1968-05-12'; + checkValidity(input, false, apply, { low: "1968-01-01", high: "1976-03-19" }); + + input.value = '1971-01-01'; + checkValidity(input, false, apply, { low: "1968-01-01", high: "1976-03-19" }); + + input.value = '1991-01-01'; + checkValidity(input, false, apply, { low: "1984-06-05", high: "1992-08-22" }); + + input.value = '1984-06-05'; + checkValidity(input, true, apply); + + input.value = '1992-08-22'; + checkValidity(input, true, apply); + + input.step = '1.1'; + input.min = '1991-01-01'; + input.value = '1991-01-01'; + checkValidity(input, true, apply); + + input.value = '1991-01-02'; + checkValidity(input, false, apply, { low: "1991-01-01", high: "1991-01-12" }); + + input.value = '1991-01-12'; + checkValidity(input, true, apply); + + input.step = '1.1'; + input.min = '1969-12-20'; + input.value = '1969-12-20'; + checkValidity(input, true, apply); + + input.value = '1969-12-21'; + checkValidity(input, false, apply, { low: "1969-12-20", high: "1969-12-31" }); + + input.value = '1969-12-31'; + checkValidity(input, true, apply); + } else { // When step=0, the allowed step is 1. input.value = '1.2'; diff --git a/content/html/content/test/forms/test_stepup_stepdown.html b/content/html/content/test/forms/test_stepup_stepdown.html index ef264369e0fc..4bc797426c0e 100644 --- a/content/html/content/test/forms/test_stepup_stepdown.html +++ b/content/html/content/test/forms/test_stepup_stepdown.html @@ -47,6 +47,7 @@ function checkAvailability() ["reset", false], ["button", false], ["number", true], + ["date", true], // The next types have not been implemented but will fallback to "text" // which has the same value. ["color", false], @@ -55,7 +56,6 @@ function checkAvailability() var todoList = [ ["datetime", true], - ["date", true], ["month", true], ["week", true], ["time", true], @@ -107,12 +107,13 @@ function checkAvailability() } } -function checkStepDownForNumber() +function checkStepDown() { - // This testData is very similar to the one in checkStepUpForNumber - // with some changes relative to stepDown. + // This testData is very similar to the one in checkStepUp with some changes + // relative to stepDown. var testData = [ /* Initial value | step | min | max | stepDown arg | final value | exception */ + { type: 'number', data: [ // Regular case. [ '1', null, null, null, null, '0', false ], // Argument testing. @@ -178,54 +179,124 @@ function checkStepDownForNumber() [ '0', 'aNy', null, null, 1, null, true ], // With @value = step base. [ '1', '2', null, null, null, '-1', false ], + ]}, + { type: 'date', data: [ + // Regular case. + [ '2012-07-09', null, null, null, null, '2012-07-08', false ], + // Argument testing. + [ '2012-07-09', null, null, null, 1, '2012-07-08', false ], + [ '2012-07-09', null, null, null, 5, '2012-07-04', false ], + [ '2012-07-09', null, null, null, -1, '2012-07-10', false ], + [ '2012-07-09', null, null, null, 0, '2012-07-09', false ], + // Month/Year wrapping. + [ '2012-08-01', null, null, null, 1, '2012-07-31', false ], + [ '1969-01-02', null, null, null, 4, '1968-12-29', false ], + [ '1969-01-01', null, null, null, -365, '1970-01-01', false ], + [ '2012-02-29', null, null, null, -1, '2012-03-01', false ], + // Float values are rounded to integer (1.1 -> 1). + [ '2012-01-02', null, null, null, 1.1, '2012-01-01', false ], + [ '2012-01-02', null, null, null, 1.9, '2012-01-01', false ], + // With step values. + [ '2012-01-03', '0.5', null, null, null, '2012-01-02', false ], + [ '2012-01-02', '0.5', null, null, null, '2012-01-01', false ], + [ '2012-01-01', '2', null, null, null, '2011-12-30', false ], + [ '2012-01-02', '0.25',null, null, 4, '2012-01-01', false ], + [ '2012-01-15', '1.1', '2012-01-01', null, 1, '2012-01-12', false ], + [ '2012-01-12', '1.1', '2012-01-01', null, 2, '2012-01-01', false ], + [ '2012-01-23', '1.1', '2012-01-01', null, 10, '2012-01-12', false ], + [ '2012-01-23', '1.1', '2012-01-01', null, 11, '2012-01-01', false ], + [ '1968-01-12', '1.1', '1968-01-01', null, 8, '1968-01-01', false ], + // step = 0 isn't allowed (-> step = 1). + [ '2012-01-02', '0', null, null, null, '2012-01-01', false ], + // step < 0 isn't allowed (-> step = 1). + [ '2012-01-02', '-1', null, null, null, '2012-01-01', false ], + // step = NaN isn't allowed (-> step = 1). + [ '2012-01-02', 'foo', null, null, null, '2012-01-01', false ], + // Min values testing. + [ '2012-01-03', '1', 'foo', null, 2, '2012-01-01', false ], + [ '2012-01-02', '1', '2012-01-01', null, null, '2012-01-01', false ], + [ '2012-01-01', '1', '2012-01-01', null, null, '2012-01-01', false ], + [ '2012-01-01', '1', '2012-01-10', null, 1, '2012-01-01', false ], + [ '2012-01-05', '3', '2012-01-01', null, null, '2012-01-04', false ], + [ '1969-01-01', '5', '1969-01-01', '1969-01-02', null, '1969-01-01', false ], + // Max values testing. + [ '2012-01-02', '1', null, 'foo', null, '2012-01-01', false ], + [ '2012-01-02', null, null, '2012-01-05', null, '2012-01-01', false ], + [ '2012-01-03', null, null, '2012-01-03', null, '2012-01-02', false ], + [ '2012-01-07', null, null, '2012-01-04', 4, '2012-01-03', false ], + [ '2012-01-07', '2', null, '2012-01-04', 3, '2012-01-01', false ], + // Step mismatch. + [ '2012-01-04', '2', '2012-01-01', null, null, '2012-01-03', false ], + [ '2012-01-06', '2', '2012-01-01', null, 2, '2012-01-03', false ], + [ '2012-01-05', '2', '2012-01-04', '2012-01-08', null, '2012-01-04', false ], + [ '1970-01-04', '2', null, null, null, '1970-01-03', false ], + [ '1970-01-09', '3', null, null, null, '1970-01-07', false ], + // Clamping. + [ '2012-05-01', null, null, '2012-01-05', null, '2012-01-05', false ], + [ '1970-01-05', '2', '1970-01-02', '1970-01-05', null, '1970-01-04', false ], + [ '1970-01-01', '5', '1970-01-02', '1970-01-09', 10, '1970-01-01', false ], + [ '1970-01-07', '5', '1969-12-27', '1970-01-06', 2, '1970-01-01', false ], + [ '1970-03-08', '3', '1970-02-01', '1970-02-07', 15, '1970-02-01', false ], + [ '1970-01-10', '3', '1970-01-01', '1970-01-06', 2, '1970-01-04', false ], + // value = "" (NaN). + [ '', null, null, null, null, '', false ], + // With step = 'any'. + [ '2012-01-01', 'any', null, null, 1, null, true ], + [ '2012-01-01', 'ANY', null, null, 1, null, true ], + [ '2012-01-01', 'AnY', null, null, 1, null, true ], + [ '2012-01-01', 'aNy', null, null, 1, null, true ], + ]}, ]; - for (var data of testData) { - var element = document.createElement("input"); - element.type = 'number'; + for (var test of testData) { + for (var data of test.data) { + var element = document.createElement("input"); + element.type = test.type; - if (data[0] != null) { - element.setAttribute('value', data[0]); - } - - if (data[1] != null) { - element.step = data[1]; - } - - if (data[2] != null) { - element.min = data[2]; - } - - if (data[3] != null) { - element.max = data[3]; - } - - var exceptionCaught = false; - try { - if (data[4] != null) { - element.stepDown(data[4]); - } else { - element.stepDown(); + if (data[0] != null) { + element.setAttribute('value', data[0]); } - is(element.value, data[5], "The value should be " + data[5]); - } catch (e) { - exceptionCaught = true; - is(element.value, data[0], e.name + "The value should not have changed"); - is(e.name, 'InvalidStateError', - "It should be a InvalidStateError exception."); - } finally { - is(exceptionCaught, data[6], "exception status should be " + data[6]); + if (data[1] != null) { + element.step = data[1]; + } + + if (data[2] != null) { + element.min = data[2]; + } + + if (data[3] != null) { + element.max = data[3]; + } + + var exceptionCaught = false; + try { + if (data[4] != null) { + element.stepDown(data[4]); + } else { + element.stepDown(); + } + + is(element.value, data[5], "The value should be " + data[5]); + } catch (e) { + exceptionCaught = true; + is(element.value, data[0], e.name + "The value should not have changed"); + is(e.name, 'InvalidStateError', + "It should be a InvalidStateError exception."); + } finally { + is(exceptionCaught, data[6], "exception status should be " + data[6]); + } } } } -function checkStepUpForNumber() +function checkStepUp() { - // This testData is very similar to the one in checkStepDownForNumber - // with some changes relative to stepUp. + // This testData is very similar to the one in checkStepDown with some changes + // relative to stepUp. var testData = [ /* Initial value | step | min | max | stepUp arg | final value | exception */ + { type: 'number', data: [ // Regular case. [ '1', null, null, null, null, '2', false ], // Argument testing. @@ -288,44 +359,116 @@ function checkStepUpForNumber() [ '0', 'aNy', null, null, 1, null, true ], // With @value = step base. [ '1', '2', null, null, null, '3', false ], + ]}, + { type: 'date', data: [ + // Regular case. + [ '2012-07-09', null, null, null, null, '2012-07-10', false ], + // Argument testing. + [ '2012-07-09', null, null, null, 1, '2012-07-10', false ], + [ '2012-07-09', null, null, null, 9, '2012-07-18', false ], + [ '2012-07-09', null, null, null, -1, '2012-07-08', false ], + [ '2012-07-09', null, null, null, 0, '2012-07-09', false ], + // Month/Year wrapping. + [ '2012-07-31', null, null, null, 1, '2012-08-01', false ], + [ '1968-12-29', null, null, null, 4, '1969-01-02', false ], + [ '1970-01-01', null, null, null, -365, '1969-01-01', false ], + [ '2012-03-01', null, null, null, -1, '2012-02-29', false ], + // Float values are rounded to integer (1.1 -> 1). + [ '2012-01-01', null, null, null, 1.1, '2012-01-02', false ], + [ '2012-01-01', null, null, null, 1.9, '2012-01-02', false ], + // With step values. + [ '2012-01-01', '0.5', null, null, null, '2012-01-02', false ], + [ '2012-01-01', '0.5', null, null, null, '2012-01-02', false ], + [ '2012-01-01', '2', null, null, null, '2012-01-03', false ], + [ '2012-01-01', '0.25', null, null, 4, '2012-01-02', false ], + [ '2012-01-01', '1.1', '2012-01-01', null, 1, '2012-01-12', false ], + [ '2012-01-01', '1.1', '2012-01-01', null, 2, '2012-01-12', false ], + [ '2012-01-01', '1.1', '2012-01-01', null, 10, '2012-01-12', false ], + [ '2012-01-01', '1.1', '2012-01-01', null, 11, '2012-01-23', false ], + [ '1968-01-01', '1.1', '1968-01-01', null, 8, '1968-01-12', false ], + // step = 0 isn't allowed (-> step = 1). + [ '2012-01-01', '0', null, null, null, '2012-01-02', false ], + // step < 0 isn't allowed (-> step = 1). + [ '2012-01-01', '-1', null, null, null, '2012-01-02', false ], + // step = NaN isn't allowed (-> step = 1). + [ '2012-01-01', 'foo', null, null, null, '2012-01-02', false ], + // Min values testing. + [ '2012-01-01', '1', 'foo', null, null, '2012-01-02', false ], + [ '2012-01-01', null, '2011-12-01', null, null, '2012-01-02', false ], + [ '2012-01-01', null, '2012-01-02', null, null, '2012-01-02', false ], + [ '2012-01-01', null, '2012-01-01', null, null, '2012-01-02', false ], + [ '2012-01-01', null, '2012-01-04', null, 4, '2012-01-05', false ], + [ '2012-01-01', '2', '2012-01-04', null, 3, '2012-01-06', false ], + // Max values testing. + [ '2012-01-01', '1', null, 'foo', 2, '2012-01-03', false ], + [ '2012-01-01', '1', null, '2012-01-10', 1, '2012-01-02', false ], + [ '2012-01-02', null, null, '2012-01-01', null, '2012-01-02', false ], + [ '2012-01-02', null, null, '2012-01-02', null, '2012-01-02', false ], + [ '2012-01-02', null, null, '2012-01-02', null, '2012-01-02', false ], + [ '1969-01-02', '5', '1969-01-01', '1969-01-02', null, '1969-01-02', false ], + // Step mismatch. + [ '2012-01-02', '2', '2012-01-01', null, null, '2012-01-03', false ], + [ '2012-01-02', '2', '2012-01-01', null, 2, '2012-01-05', false ], + [ '2012-01-05', '2', '2012-01-01', '2012-01-06', null, '2012-01-05', false ], + [ '1970-01-02', '2', null, null, null, '1970-01-03', false ], + [ '1970-01-05', '3', null, null, null, '1970-01-07', false ], + [ '1970-01-03', '3', null, null, null, '1970-01-04', false ], + [ '1970-01-03', '3', '1970-01-02', null, null, '1970-01-05', false ], + // Clamping. + [ '2012-01-01', null, '2012-01-31', null, null, '2012-01-31', false ], + [ '1970-01-02', '2', '1970-01-01', '1970-01-04', null, '1970-01-03', false ], + [ '1970-01-01', '5', '1970-01-02', '1970-01-09', 10, '1970-01-07', false ], + [ '1969-12-28', '5', '1969-12-29', '1970-01-06', 3, '1970-01-03', false ], + [ '1970-01-01', '3', '1970-02-01', '1970-02-07', 15, '1970-02-07', false ], + [ '1970-01-01', '3', '1970-01-01', '1970-01-06', 2, '1970-01-04', false ], + // value = "" (NaN). + [ '', null, null, null, null, '', false ], + // With step = 'any'. + [ '2012-01-01', 'any', null, null, 1, null, true ], + [ '2012-01-01', 'ANY', null, null, 1, null, true ], + [ '2012-01-01', 'AnY', null, null, 1, null, true ], + [ '2012-01-01', 'aNy', null, null, 1, null, true ], + ]}, ]; - for (var data of testData) { - var element = document.createElement("input"); - element.type = 'number'; + for (var test of testData) { + for (var data of test.data) { + var element = document.createElement("input"); + element.type = test.type; - if (data[0] != null) { - element.setAttribute('value', data[0]); - } - - if (data[1] != null) { - element.step = data[1]; - } - - if (data[2] != null) { - element.min = data[2]; - } - - if (data[3] != null) { - element.max = data[3]; - } - - var exceptionCaught = false; - try { - if (data[4] != null) { - element.stepUp(data[4]); - } else { - element.stepUp(); + if (data[0] != null) { + element.setAttribute('value', data[0]); } - is(element.value, data[5], "The value should be " + data[5]); - } catch (e) { - exceptionCaught = true; - is(element.value, data[0], e.name + "The value should not have changed"); - is(e.name, 'InvalidStateError', - "It should be a InvalidStateError exception."); - } finally { - is(exceptionCaught, data[6], "exception status should be " + data[6]); + if (data[1] != null) { + element.step = data[1]; + } + + if (data[2] != null) { + element.min = data[2]; + } + + if (data[3] != null) { + element.max = data[3]; + } + + var exceptionCaught = false; + try { + if (data[4] != null) { + element.stepUp(data[4]); + } else { + element.stepUp(); + } + + is(element.value, data[5], "The value should be " + data[5]); + } catch (e) { + exceptionCaught = true; + is(element.value, data[0], e.name + "The value should not have changed"); + is(e.name, 'InvalidStateError', + "It should be a InvalidStateError exception."); + } finally { + is(exceptionCaught, data[6], "exception status should be " + data[6]); + } } } } @@ -335,8 +478,9 @@ SpecialPowers.pushPrefEnv({'set': [["dom.experimental_forms", true]]}, function( checkPresence(); checkAvailability(); -checkStepDownForNumber(); -checkStepUpForNumber(); + +checkStepDown(); +checkStepUp(); SimpleTest.finish(); });