Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Date validator addition #208

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,25 @@ Validates the length of a `String` or an `Array`.

**[⬆️ back to top](#validator-api)**

#### `date`

Validates various properties of a date.

👉 [All Options](https://offirgolan.github.io/ember-validators/docs/classes/Date.html#method_validate)

```js
{
propertyName: validateDate({ before: '3000-01-01' }), // must be before 1st Jan. 3000
propertyName: validateDate({ onOrBefore: '3000-01-01' }), // must be not after 1st Jan. 3000
propertyName: validateDate({ after: '3000-01-01' }), // must be after 1st Jan. 3000
propertyName: validateDate({ onOrAfter: '3000-01-01' }), // must be not before 1st Jan. 3000
propertyName: validateDate({ precision: 'year', before: '1950-11-20' }), // must be no later than *1949*
propertyName: validateDate({ format: 'MMM || Do || YYYY' }), // dates must be formatted like "Jan || 1st || 1999"
}
```

**[⬆️ back to top](#validator-api)**

#### `number`

Validates various properties of a number.
Expand Down
16 changes: 16 additions & 0 deletions addon/validators/date.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import buildMessage from 'ember-changeset-validations/utils/validation-errors';
import withDefaults from 'ember-changeset-validations/utils/with-defaults';
import { validate } from 'ember-validators';

const errorFormat = "MMM Do, YYYY";

export default function validateNumber(options = {}) {
options = withDefaults(options, { allowBlank: false, errorFormat: errorFormat});
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... errorFormat: errorFormat }


return (key, value) => {
let result = validate('date', value, options, null, key);
return (result === true) ? true : buildMessage(key, result);
};
}

export {errorFormat};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

export { errorFormat }

7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,12 @@
"ember-cli-blueprint-test-helpers": "^0.19.2",
"ember-cli-dependency-checker": "^3.1.0",
"ember-cli-eslint": "^5.1.0",
"ember-cli-htmlbars-inline-precompile": "^1.0.3",
"ember-cli-inject-live-reload": "^2.0.1",
"ember-cli-moment-shim": "^3.7.1",
"ember-cli-sri": "^2.1.1",
"ember-cli-uglify": "^3.0.0",
"ember-disable-prototype-extensions": "^1.1.3",
"ember-export-application-global": "^2.0.0",
"ember-export-application-global": "^2.0.1",
"ember-load-initializers": "^2.1.0",
"ember-maybe-import-regenerator": "^0.1.6",
"ember-qunit": "^4.5.1",
Expand All @@ -64,7 +64,8 @@
"eslint-plugin-ember": "^7.1.0",
"eslint-plugin-node": "^10.0.0",
"loader.js": "^4.7.0",
"mocha": "^5.0.0"
"mocha": "^5.0.0",
"moment": "^2.24.0"
},
"engines": {
"node": "8.* || >= 10.*"
Expand Down
265 changes: 265 additions & 0 deletions tests/unit/validators/date-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
import moment from 'moment';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can/should avoid moment and use native JS functions or date-fns if last resort.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used it in the unit tests since the dependency was needed anyway so might as well use the closest thing possible to the validator implementation. If ember-validators does remove moment it would indeed be better to work without it in tests.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the extremely simple cases ember-validators uses it for, it definitely should be optional and use the platform if so. This test would still be useful as the API would look like:

validator(key, 'Nov || 3rd || 1998', { dateFn: moment }) or something like that

import validateDate, { errorFormat as errorOutputFormat } from 'ember-changeset-validations/validators/date';
import buildMessage from 'ember-changeset-validations/utils/validation-errors';
import { module, test } from 'qunit';

const inputFormat = "YYYY-MM-DD";

module('Unit | Validator | date', function() {
test('it accepts an `allowBlank` option', function(assert) {
let key = 'birth_date';
let options = { allowBlank: true };
let validator = validateDate(options);

assert.equal(validator(key, null), true, 'null is allowed');
assert.equal(validator(key, undefined), true, 'undefined is allowed');
assert.equal(validator(key, 123), true, 'number value is is allowed'); // valid for momentjs ¯\_(ツ)_/¯
assert.equal(validator(key, '1992-03-30'), true, 'date string is allowed');
assert.equal(validator(key, 'now'), true, '"now" value is is allowed');

assert.equal(validator(key, 'not a date'),
buildMessage(key, { type: 'date', value: 'not a date', context: options }), 'non-date string is not allowed'
);
assert.equal(validator(key, NaN),
buildMessage(key, { type: 'date', value: NaN, context: options }), 'NaN is not allowed'
);
assert.equal(validator(key, {}),
buildMessage(key, { type: 'date', value: {}, context: options }), 'empty object is not allowed'
);
});

test('it accepts valid values', function(assert) {
const key = 'test_date';
const options = {};
const validator = validateDate(options);

// assumes current moment
assert.equal(validator(key, ''), true, 'empty string is allowed');
assert.equal(validator(key, null), true);
assert.equal(validator(key, undefined), true);
assert.equal(validator(key, '1992-03-30'), true, 'date string is allowed');
assert.equal(validator(key, 'now'), true, '"now" value is is allowed');
});

test('it rejectes invalid values', function(assert) {
const key = 'test_date';
const options = {};
const validator = validateDate(options);

assert.equal(validator(key, 'not a date'),
buildMessage(key, { type: 'date', value: 'not a date', context: options }), 'non-date string is not allowed'
);
assert.equal(validator(key, NaN),
buildMessage(key, { type: 'date', value: NaN, context: options }), 'NaN is not allowed'
);
assert.equal(validator(key, {}),
buildMessage(key, { type: 'date', value: {}, context: options }), 'empty object is not allowed'
);
});

test('it accepts a `before` option', function(assert) {
const futureDate = '3000-01-01';
const pastDate = '1900-01-01';

const key = 'test_date';
const options = { before: futureDate };

let validator = validateDate(options);

// Testing with before date in the future
assert.equal(validator(key, moment(futureDate).add(1, 'days').format(inputFormat)),
buildMessage(key, {
type: 'before', value: moment(futureDate).add(1, 'days').format(inputFormat),
context: { before: moment(options.before).format(errorOutputFormat) },
}, 'date after the "before" date is not allowed')
);
validator = validateDate(options);
assert.equal(validator(key, futureDate),
buildMessage(key, {
type: 'before', value: futureDate,
context: { before: moment(options.before).format(errorOutputFormat) },
}, 'date same as the "before" date is not allowed')
);
validator = validateDate(options);
assert.equal(validator(key, moment(futureDate).subtract(1, 'days').format(inputFormat)), true);


// Testing with before date in the past
options.before = pastDate;
validator = validateDate(options);
assert.equal(validator(key, moment(pastDate).add(1, 'days').format(inputFormat)),
buildMessage(key, {
type: 'before', value: moment(pastDate).add(1, 'days').format(inputFormat),
context: { before: moment(options.before).format(errorOutputFormat) },
}, 'date after the "before" date is not allowed')
);
validator = validateDate(options);
assert.equal(validator(key, pastDate),
buildMessage(key, {
type: 'before', value: pastDate,
context: { before: moment(options.before).format(errorOutputFormat) },
}, 'date same as the "before" date is not allowed')
);
validator = validateDate(options);
assert.equal(validator(key, moment(pastDate).subtract(1, 'days').format(inputFormat)), true);
});

test('it accepts an `onOrBefore` option', function(assert) {
const futureDate = '3000-01-01';
const pastDate = '1900-01-01';

const key = 'test_date';
const options = { onOrBefore: futureDate };

let validator = validateDate(options);

// Testing with before date in the future
assert.equal(validator(key, moment(futureDate).add(1, 'days').format(inputFormat)),
buildMessage(key, {
type: 'onOrBefore', value: moment(futureDate).add(1, 'days').format(inputFormat),
context: { onOrBefore: moment(options.onOrBefore).format(errorOutputFormat) },
}, 'date after the "onOrBefore" date is not allowed')
);
validator = validateDate(options);
assert.equal(validator(key, futureDate), true, 'date same as the "onOrBefore" date is allowed');
validator = validateDate(options);
assert.equal(validator(key, moment(futureDate).subtract(1, 'days').format(inputFormat)), true);


// Testing with before date in the past
options.onOrBefore = pastDate;
validator = validateDate(options);
assert.equal(validator(key, moment(pastDate).add(1, 'days').format(inputFormat)),
buildMessage(key, {
type: 'onOrBefore', value: moment(pastDate).add(1, 'days').format(inputFormat),
context: { onOrBefore: moment(options.onOrBefore).format(errorOutputFormat) },
}, 'date after the "onOrBefore" date is not allowed')
);
validator = validateDate(options);
assert.equal(validator(key, pastDate), true, 'date same as the "onOrBefore" date is allowed');
validator = validateDate(options);
assert.equal(validator(key, moment(pastDate).subtract(1, 'days').format(inputFormat)), true);
});

test('it accepts an `after` option', function(assert) {
const futureDate = '3000-01-01';
const pastDate = '1900-01-01';

const key = 'test_date';
const options = { after: futureDate };

let validator = validateDate(options);

// Testing with after date in the future
assert.equal(validator(key, moment(futureDate).subtract(1, 'days').format(inputFormat)),
buildMessage(key, {
type: 'after', value: moment(futureDate).subtract(1, 'days').format(inputFormat),
context: { after: moment(options.after).format(errorOutputFormat) },
}, 'date after the "after" date is not allowed')
);
validator = validateDate(options);
assert.equal(validator(key, futureDate),
buildMessage(key, {
type: 'after', value: futureDate,
context: { after: moment(options.after).format(errorOutputFormat) },
}, 'date same as the "after" date is not allowed')
);
validator = validateDate(options);
assert.equal(validator(key, moment(futureDate).add(1, 'days').format(inputFormat)), true);


// Testing with after date in the past
options.after = pastDate;
validator = validateDate(options);
assert.equal(validator(key, moment(pastDate).subtract(1, 'days').format(inputFormat)),
buildMessage(key, {
type: 'after', value: moment(pastDate).subtract(1, 'days').format(inputFormat),
context: { after: moment(options.after).format(errorOutputFormat) },
}, 'date after the "after" date is not allowed')
);
validator = validateDate(options);
assert.equal(validator(key, pastDate),
buildMessage(key, {
type: 'after', value: pastDate,
context: { after: moment(options.after).format(errorOutputFormat) },
}, 'date same as the "after" date is not allowed')
);
validator = validateDate(options);
assert.equal(validator(key, moment(pastDate).add(1, 'days').format(inputFormat)), true);
});

test('it accepts an `onOrAfter` option', function(assert) {
const futureDate = '3000-01-01';
const pastDate = '1900-01-01';

const key = 'test_date';
const options = { onOrAfter: futureDate };

let validator = validateDate(options);

// Testing with after date in the future
assert.equal(validator(key, moment(futureDate).subtract(1, 'days').format(inputFormat)),
buildMessage(key, {
type: 'onOrAfter', value: moment(futureDate).subtract(1, 'days').format(inputFormat),
context: { onOrAfter: moment(options.onOrAfter).format(errorOutputFormat) },
}, 'date after the "onOrAfter" date is not allowed')
);
validator = validateDate(options);
assert.equal(validator(key, futureDate), true, 'date same as the "onOrAfter" date is allowed');
validator = validateDate(options);
assert.equal(validator(key, moment(futureDate).add(1, 'days').format(inputFormat)), true);


// Testing with after date in the past
options.onOrAfter = pastDate;
validator = validateDate(options);
assert.equal(validator(key, moment(pastDate).subtract(1, 'days').format(inputFormat)),
buildMessage(key, {
type: 'onOrAfter', value: moment(pastDate).subtract(1, 'days').format(inputFormat),
context: { onOrAfter: moment(options.onOrAfter).format(errorOutputFormat) },
}, 'date after the "onOrAfter" date is not allowed')
);
validator = validateDate(options);
assert.equal(validator(key, pastDate), true, 'date same as the "onOrAfter" date is allowed');
validator = validateDate(options);
assert.equal(validator(key, moment(pastDate).add(1, 'days').format(inputFormat)), true);
});

test('it accepts a `precision` option', function(assert) {
const beforeTarget = '1950-11-20';
const key = 'test_date';
const options = { before: beforeTarget, precision: 'year' };
let validator = validateDate(options);

validator = validateDate(options);
assert.equal(validator(key, moment(beforeTarget).subtract(1, 'month').format(inputFormat)),
buildMessage(key, {
type: 'before', value: beforeTarget,
context: { before: moment(options.before).format(errorOutputFormat) },
}, 'date within the same year as the "before" date is not allowed')
);
validator = validateDate(options);
assert.equal(validator(key, moment(beforeTarget).subtract(1, 'year').format(inputFormat)), true);
});

test('it accepts a `format` option', function(assert) {
const key = 'test_date';
const options = {};
let validator = validateDate(options);

options.format = 'YYYY'
validator = validateDate(options);
assert.equal(validator(key, '1-1-1999'),
buildMessage(key, { type: 'wrongDateFormat', value: '1-1-1999', context: options }), 'format should be just year'
);
assert.equal(validator(key, '1999'), true);

options.format = 'MMM || Do || YYYY'
validator = validateDate(options);
assert.equal(validator(key, '11-3-1998'),
buildMessage(key, { type: 'wrongDateFormat', value: '1-1-1999', context: options }), 'format should be "MMM || Do || YYYY"'
);
assert.equal(validator(key, 'Nov || 3rd || 1998'), true);
});

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wow. Phenomenal tests!

});
Loading