Skip to content

Commit

Permalink
fix(core): fix type issues and add tests for datetime input (#5558)
Browse files Browse the repository at this point in the history
  • Loading branch information
binoy14 authored Jan 24, 2024
1 parent 97faf08 commit dec2fb7
Show file tree
Hide file tree
Showing 4 changed files with 161 additions and 66 deletions.
4 changes: 3 additions & 1 deletion packages/@sanity/util/src/legacyDateFormat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ export type ParseResult = {isValid: boolean; date?: Date; error?: string} & (
// todo: find a way to get rid of moment there.
// note: the format comes form peoples schemas, so we need to deprecate it for a while and
// find a way to tell people that they need to change it
export function format(input: Date, format: string) {
export function format(input: Date, format: string, useUTC = false) {
if (useUTC) return moment.utc(input).format(format)

return moment(input).format(format)
}

Expand Down
17 changes: 11 additions & 6 deletions packages/sanity/src/core/validation/validators/dateValidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,17 @@ interface DateTimeOptions {
timeFormat?: string
}

const getFormattedDate = (type = '', value: string | number | Date, options?: DateTimeOptions) => {
const getFormattedDate = (type = '', value: Date, options?: DateTimeOptions) => {
const dateFormat = options?.dateFormat || legacyDateFormat.DEFAULT_DATE_FORMAT
const timeFormat = options?.timeFormat || legacyDateFormat.DEFAULT_TIME_FORMAT

// adding the time information in the date only case causes timezone information to be kept
// instead of it being assumed to be UTC. This was a problem because midnight UTC is the previous
// day in many other timezones resulting in the date displayed to be the previous day.
return legacyDateFormat.format(
new Date(type === 'date' ? `${value}T00:00:00` : value),
value,
type === 'date' ? dateFormat : `${dateFormat} ${timeFormat}`,
type === 'date',
)
}

Expand Down Expand Up @@ -59,11 +60,13 @@ export const dateValidators: Validators = {

min: (minDate, value, message, {type, i18n}) => {
const dateVal = parseDate(value)
const minDateVal = parseDate(minDate, true)

if (!dateVal) {
return true // `type()` should catch parse errors
}

if (!value || dateVal >= parseDate(minDate, true)) {
if (!value || dateVal >= minDateVal) {
return true
}

Expand All @@ -81,19 +84,21 @@ export const dateValidators: Validators = {
// validator is available as `providedMinDate`. This because the formatted date is likely
// what the developer wants to present to the user
i18n.t('validation:date.minimum', {
minDate: getFormattedDate(type.name, minDate, dateTimeOptions),
minDate: getFormattedDate(type.name, minDateVal, dateTimeOptions),
providedMinDate: minDate,
})
)
},

max: (maxDate, value, message, {type, i18n}) => {
const dateVal = parseDate(value)
const maxDateVal = parseDate(maxDate, true)

if (!dateVal) {
return true // `type()` should catch parse errors
}

if (!value || dateVal <= parseDate(maxDate, true)) {
if (!value || dateVal <= maxDateVal) {
return true
}

Expand All @@ -111,7 +116,7 @@ export const dateValidators: Validators = {
// validator is available as `providedMaxDate`. This because the formatted date is likely
// what the developer wants to present to the user
i18n.t('validation:date.maximum', {
maxDate: getFormattedDate(type.name, maxDate, dateTimeOptions),
maxDate: getFormattedDate(type.name, maxDateVal, dateTimeOptions),
providedMaxDate: maxDate,
})
)
Expand Down
80 changes: 58 additions & 22 deletions packages/sanity/test/validation/__snapshots__/dates.test.ts.snap
Original file line number Diff line number Diff line change
@@ -1,23 +1,45 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`date max length constraint: Must be at or before 1`] = `
exports[`date with custom format max length constraint: Must be at or before 1`] = `
Array [
Object {
"item": Object {
"message": "Must be at or before 2024-01-01",
"message": "Must be at or before 01-01-2024",
},
"level": "error",
"message": "Must be at or before 2024-01-01",
"message": "Must be at or before 01-01-2024",
"path": Array [],
},
]
`;

exports[`date max length constraint: max length: valid 1`] = `Array []`;
exports[`date with custom format min length constraint: Must be at or after 1`] = `
Array [
Object {
"item": Object {
"message": "Must be at or after 01-01-2024",
},
"level": "error",
"message": "Must be at or after 01-01-2024",
"path": Array [],
},
]
`;

exports[`date max length constraint: max length: valid 2`] = `Array []`;
exports[`date with default format max length constraint: Must be at or before 1`] = `
Array [
Object {
"item": Object {
"message": "Must be at or before 2024-01-01",
},
"level": "error",
"message": "Must be at or before 2024-01-01",
"path": Array [],
},
]
`;

exports[`date min length constraint: Must be at or after 1`] = `
exports[`date with default format min length constraint: Must be at or after 1`] = `
Array [
Object {
"item": Object {
Expand All @@ -30,40 +52,54 @@ Array [
]
`;

exports[`date min length constraint: min length: valid 1`] = `Array []`;

exports[`date min length constraint: min length: valid 2`] = `Array []`;

exports[`date with custom format max length constraint: Must be at or before 1`] = `
exports[`datetime with custom format max length constraint: Must be at or before 1`] = `
Array [
Object {
"item": Object {
"message": "Must be at or before 01-01-2024",
"message": "Must be at or before 1st. January 2024 09:31",
},
"level": "error",
"message": "Must be at or before 01-01-2024",
"message": "Must be at or before 1st. January 2024 09:31",
"path": Array [],
},
]
`;

exports[`date with custom format max length constraint: max length: valid 1`] = `Array []`;

exports[`date with custom format max length constraint: max length: valid 2`] = `Array []`;

exports[`date with custom format min length constraint: Must be at or after 1`] = `
exports[`datetime with custom format min length constraint: Must be at or after 1`] = `
Array [
Object {
"item": Object {
"message": "Must be at or after 01-01-2024",
"message": "Must be at or after 1st. January 2024 09:31",
},
"level": "error",
"message": "Must be at or after 01-01-2024",
"message": "Must be at or after 1st. January 2024 09:31",
"path": Array [],
},
]
`;

exports[`date with custom format min length constraint: min length: valid 1`] = `Array []`;
exports[`datetime with default format max length constraint: Must be at or before 1`] = `
Array [
Object {
"item": Object {
"message": "Must be at or before 2024-01-01 09:31",
},
"level": "error",
"message": "Must be at or before 2024-01-01 09:31",
"path": Array [],
},
]
`;

exports[`date with custom format min length constraint: min length: valid 2`] = `Array []`;
exports[`datetime with default format min length constraint: Must be at or after 1`] = `
Array [
Object {
"item": Object {
"message": "Must be at or after 2024-01-01 09:31",
},
"level": "error",
"message": "Must be at or after 2024-01-01 09:31",
"path": Array [],
},
]
`;
126 changes: 89 additions & 37 deletions packages/sanity/test/validation/dates.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,49 +2,101 @@ import {getFallbackLocaleSource} from '../../src/core/i18n/fallback'
import {Rule} from '../../src/core/validation'

describe('date', () => {
const context: any = {client: {}, i18n: getFallbackLocaleSource(), type: {name: 'date'}}

test('min length constraint', async () => {
const rule = Rule.dateTime().min('2024-01-01')
await expect(rule.validate('2023-12-31', context)).resolves.toMatchSnapshot(
'Must be at or after',
)
await expect(rule.validate('2024-01-02', context)).resolves.toMatchSnapshot('min length: valid')
await expect(rule.validate('2024-01-01', context)).resolves.toMatchSnapshot('min length: valid')
describe('with default format', () => {
const context: any = {client: {}, i18n: getFallbackLocaleSource(), type: {name: 'date'}}

test('min length constraint', async () => {
const rule = Rule.dateTime().min(Date.parse('2024-01-01'))
await expect(rule.validate('2023-12-31', context)).resolves.toMatchSnapshot(
'Must be at or after',
)
await expect(rule.validate('2024-01-02', context)).resolves.toHaveLength(0)
await expect(rule.validate('2024-01-01', context)).resolves.toHaveLength(0)
})

test('max length constraint', async () => {
const rule = Rule.dateTime().max(Date.parse('2024-01-01'))
await expect(rule.validate('2024-01-02', context)).resolves.toMatchSnapshot(
'Must be at or before',
)
await expect(rule.validate('2023-12-31', context)).resolves.toHaveLength(0)
await expect(rule.validate('2024-01-01', context)).resolves.toHaveLength(0)
})
})

test('max length constraint', async () => {
const rule = Rule.dateTime().max('2024-01-01')
await expect(rule.validate('2024-01-02', context)).resolves.toMatchSnapshot(
'Must be at or before',
)
await expect(rule.validate('2023-12-31', context)).resolves.toMatchSnapshot('max length: valid')
await expect(rule.validate('2024-01-01', context)).resolves.toMatchSnapshot('max length: valid')
describe('with custom format', () => {
const context: any = {
client: {},
i18n: getFallbackLocaleSource(),
type: {name: 'date', options: {dateFormat: 'MM-DD-YYYY'}},
}

test('min length constraint', async () => {
const rule = Rule.dateTime().min(Date.parse('2024-01-01'))
await expect(rule.validate('2023-12-31', context)).resolves.toMatchSnapshot(
'Must be at or after',
)
await expect(rule.validate('2024-01-02', context)).resolves.toHaveLength(0)
await expect(rule.validate('2024-01-01', context)).resolves.toHaveLength(0)
})

test('max length constraint', async () => {
const rule = Rule.dateTime().max(Date.parse('2024-01-01'))
await expect(rule.validate('2024-01-02', context)).resolves.toMatchSnapshot(
'Must be at or before',
)
await expect(rule.validate('2023-12-31', context)).resolves.toHaveLength(0)
await expect(rule.validate('2024-01-01', context)).resolves.toHaveLength(0)
})
})
})

describe('date with custom format', () => {
const context: any = {
client: {},
i18n: getFallbackLocaleSource(),
type: {name: 'date', options: {dateFormat: 'MM-DD-YYYY'}},
}

test('min length constraint', async () => {
const rule = Rule.dateTime().min('2024-01-01')
await expect(rule.validate('2023-12-31', context)).resolves.toMatchSnapshot(
'Must be at or after',
)
await expect(rule.validate('2024-01-02', context)).resolves.toMatchSnapshot('min length: valid')
await expect(rule.validate('2024-01-01', context)).resolves.toMatchSnapshot('min length: valid')
describe('datetime', () => {
describe('with default format', () => {
const context: any = {client: {}, i18n: getFallbackLocaleSource(), type: {name: 'datetime'}}

test('min length constraint', async () => {
const rule = Rule.dateTime().min(Date.parse('2024-01-01T17:31:00.000Z'))
await expect(rule.validate('2023-12-31T17:31:00.000Z', context)).resolves.toMatchSnapshot(
'Must be at or after',
)
await expect(rule.validate('2024-01-02T17:31:00.000Z', context)).resolves.toHaveLength(0)
await expect(rule.validate('2024-01-01T17:31:00.000Z', context)).resolves.toHaveLength(0)
})

test('max length constraint', async () => {
const rule = Rule.dateTime().max(Date.parse('2024-01-01T17:31:00.000Z'))
await expect(rule.validate('2024-01-02T17:31:00.000Z', context)).resolves.toMatchSnapshot(
'Must be at or before',
)
await expect(rule.validate('2023-12-23T17:31:00.000Z', context)).resolves.toHaveLength(0)
await expect(rule.validate('2024-01-01T17:31:00.000Z', context)).resolves.toHaveLength(0)
})
})

test('max length constraint', async () => {
const rule = Rule.dateTime().max('2024-01-01')
await expect(rule.validate('2024-01-02', context)).resolves.toMatchSnapshot(
'Must be at or before',
)
await expect(rule.validate('2023-12-31', context)).resolves.toMatchSnapshot('max length: valid')
await expect(rule.validate('2024-01-01', context)).resolves.toMatchSnapshot('max length: valid')
describe('with custom format', () => {
const context: any = {
client: {},
i18n: getFallbackLocaleSource(),
type: {name: 'datetime', options: {dateFormat: 'Do. MMMM YYYY'}},
}

test('min length constraint', async () => {
const rule = Rule.dateTime().min(Date.parse('2024-01-01T17:31:00.000Z'))
await expect(rule.validate('2023-12-31T17:31:00.000Z', context)).resolves.toMatchSnapshot(
'Must be at or after',
)
await expect(rule.validate('2024-01-02T17:31:00.000Z', context)).resolves.toHaveLength(0)
await expect(rule.validate('2024-01-01T17:31:00.000Z', context)).resolves.toHaveLength(0)
})

test('max length constraint', async () => {
const rule = Rule.dateTime().max(Date.parse('2024-01-01T17:31:00.000Z'))
await expect(rule.validate('2024-01-02T17:31:00.000Z', context)).resolves.toMatchSnapshot(
'Must be at or before',
)
await expect(rule.validate('2023-12-31T17:31:00.000Z', context)).resolves.toHaveLength(0)
await expect(rule.validate('2024-01-01T17:31:00.000Z', context)).resolves.toHaveLength(0)
})
})
})

0 comments on commit dec2fb7

Please sign in to comment.