From 9bd0d7bac34e8f346dba3b0f422d65658e72d966 Mon Sep 17 00:00:00 2001 From: Nicolas DUBIEN Date: Tue, 28 Nov 2023 22:19:41 +0100 Subject: [PATCH] =?UTF-8?q?=20=F0=9F=92=A5=20Include=20invalid=20dates=20b?= =?UTF-8?q?y=20default=20(#4490)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **💥 Breaking change** In v3, `fc.date` was not producing "Invalid Date" values by default. One has to specify `noInvalidDate: false` to get some. With this PR we now default the flag to false when not specified: in other words, the new default will be to produce invalid dates except asked explicitly not to. After that change, if you want not to produce "Invalid Date", you'll have to use: ```js // in v3, it was only producing valid dates fc.date() // equivalent in v4 fc.date({noInvalidDate: true}) //--- // in v3, including invalid dates required users to pass an extra constraint to the arbitrary fc.date({noInvalidDate: false}) // equivalent in v4 fc.date() ``` You can also prepare yourself to v4 by already toggling: `noInvalidDate: false` on your `fc.date`. --- packages/fast-check/src/arbitrary/date.ts | 4 +-- .../_internals/mappers/TimeToDate.spec.ts | 32 ++++++++----------- .../test/unit/arbitrary/date.spec.ts | 20 +++++++++--- .../arbitraries/composites/object.md | 4 +-- .../arbitraries/primitives/date.md | 15 +++++++-- 5 files changed, 45 insertions(+), 30 deletions(-) diff --git a/packages/fast-check/src/arbitrary/date.ts b/packages/fast-check/src/arbitrary/date.ts index adf8dae2d3d..94d94bc9c78 100644 --- a/packages/fast-check/src/arbitrary/date.ts +++ b/packages/fast-check/src/arbitrary/date.ts @@ -30,7 +30,7 @@ export interface DateConstraints { max?: Date; /** * When set to true, no more "Invalid Date" can be generated. - * @defaultValue true + * @defaultValue false * @remarks Since 3.13.0 */ noInvalidDate?: boolean; @@ -48,7 +48,7 @@ export function date(constraints: DateConstraints = {}): Arbitrary { // Date min and max in ECMAScript specification : https://stackoverflow.com/a/11526569/3707828 const intMin = constraints.min !== undefined ? safeGetTime(constraints.min) : -8640000000000000; const intMax = constraints.max !== undefined ? safeGetTime(constraints.max) : 8640000000000000; - const noInvalidDate = constraints.noInvalidDate === undefined || constraints.noInvalidDate; + const noInvalidDate = constraints.noInvalidDate; if (safeNumberIsNaN(intMin)) throw new Error('fc.date min must be valid instance of Date'); if (safeNumberIsNaN(intMax)) throw new Error('fc.date max must be valid instance of Date'); if (intMin > intMax) throw new Error('fc.date max must be greater or equal to min'); diff --git a/packages/fast-check/test/unit/arbitrary/_internals/mappers/TimeToDate.spec.ts b/packages/fast-check/test/unit/arbitrary/_internals/mappers/TimeToDate.spec.ts index 079fb9bd243..176a8b1bd9c 100644 --- a/packages/fast-check/test/unit/arbitrary/_internals/mappers/TimeToDate.spec.ts +++ b/packages/fast-check/test/unit/arbitrary/_internals/mappers/TimeToDate.spec.ts @@ -9,7 +9,7 @@ import { describe('timeToDateUnmapper', () => { it('should be able to revert any mapped date correctly even invalid ones', () => { fc.assert( - fc.property(fc.date({ noInvalidDate: false }), (d) => { + fc.property(fc.date(), (d) => { // Arrange / Act const rev = timeToDateUnmapper(d); const revRev = timeToDateMapper(rev); @@ -21,26 +21,22 @@ describe('timeToDateUnmapper', () => { }); }); -describe('timeToDateUnmapperWithNane', () => { +describe('timeToDateUnmapperWithNaN', () => { it('should be able to revert any mapped date correctly even invalid once', () => { fc.assert( - fc.property( - fc.date({ noInvalidDate: false }), - fc.integer({ min: -8640000000000000, max: 8640000000000001 }), - (d, nanValue) => { - // Arrange / Act - const rev = timeToDateUnmapperWithNaN(nanValue)(d); - const revRev = timeToDateMapperWithNaN(nanValue)(rev); + fc.property(fc.date(), fc.integer({ min: -8640000000000000, max: 8640000000000001 }), (d, nanValue) => { + // Arrange / Act + const rev = timeToDateUnmapperWithNaN(nanValue)(d); + const revRev = timeToDateMapperWithNaN(nanValue)(rev); - // Assert - if (d.getTime() === nanValue) { - expect(rev).toBe(nanValue); - expect(revRev.getTime()).toEqual(Number.NaN); - } else { - expect(revRev.getTime()).toEqual(d.getTime()); - } - }, - ), + // Assert + if (d.getTime() === nanValue) { + expect(rev).toBe(nanValue); + expect(revRev.getTime()).toEqual(Number.NaN); + } else { + expect(revRev.getTime()).toEqual(d.getTime()); + } + }), ); }); }); diff --git a/packages/fast-check/test/unit/arbitrary/date.spec.ts b/packages/fast-check/test/unit/arbitrary/date.spec.ts index 3a0af7a3ea9..9b209ff5420 100644 --- a/packages/fast-check/test/unit/arbitrary/date.spec.ts +++ b/packages/fast-check/test/unit/arbitrary/date.spec.ts @@ -74,7 +74,7 @@ describe('date', () => { fc.assert( fc.property(constraintsArb(), (constraints) => { // Arrange - const withInvalidDates = constraints.noInvalidDate === false; + const withInvalidDates = !constraints.noInvalidDate; const { instance, map } = fakeArbitrary(); const { instance: mappedInstance } = fakeArbitrary(); const integer = jest.spyOn(IntegerMock, 'integer'); @@ -118,7 +118,7 @@ describe('date', () => { const d = mapper(rangeMin! + (mod % (rangeMax! - rangeMin! + 1))) as Date; // Assert - if (constraints.noInvalidDate !== false || !Number.isNaN(d.getTime())) { + if (constraints.noInvalidDate || !Number.isNaN(d.getTime())) { expect(d.getTime()).not.toBe(Number.NaN); if (constraints.min) expect(d.getTime()).toBeGreaterThanOrEqual(constraints.min.getTime()); if (constraints.max) expect(d.getTime()).toBeLessThanOrEqual(constraints.max.getTime()); @@ -156,7 +156,7 @@ describe('date (integration)', () => { const extraParameters: fc.Arbitrary = constraintsArb(); const isCorrect = (d: Date, extra: Extra) => { - if (extra.noInvalidDate || extra.noInvalidDate === undefined) { + if (extra.noInvalidDate) { expect(d.getTime()).not.toBe(Number.NaN); } else if (Number.isNaN(d.getTime())) { return; @@ -206,7 +206,13 @@ describe('date (integration)', () => { function constraintsArb() { return fc - .tuple(fc.date(), fc.date(), fc.boolean(), fc.boolean(), fc.option(fc.boolean(), { nil: undefined })) + .tuple( + fc.date({ noInvalidDate: true }), + fc.date({ noInvalidDate: true }), + fc.boolean(), + fc.boolean(), + fc.option(fc.boolean(), { nil: undefined }), + ) .map(([d1, d2, withMin, withMax, noInvalidDate]) => { const min = d1 < d2 ? d1 : d2; const max = d1 < d2 ? d2 : d1; @@ -216,7 +222,11 @@ function constraintsArb() { function invalidRangeConstraintsArb() { return fc - .tuple(fc.date(), fc.date(), fc.option(fc.boolean(), { nil: undefined })) + .tuple( + fc.date({ noInvalidDate: true }), + fc.date({ noInvalidDate: true }), + fc.option(fc.boolean(), { nil: undefined }), + ) .filter(([d1, d2]) => +d1 !== +d2) .map(([d1, d2, noInvalidDate]) => { const min = d1 < d2 ? d1 : d2; diff --git a/website/docs/core-blocks/arbitraries/composites/object.md b/website/docs/core-blocks/arbitraries/composites/object.md index f3dde1c83c0..43bc6d9a941 100644 --- a/website/docs/core-blocks/arbitraries/composites/object.md +++ b/website/docs/core-blocks/arbitraries/composites/object.md @@ -128,11 +128,11 @@ fc.record( ); // Note: All keys except 'id' will be optional values. id has been marked as required. // Examples of generated values: -// • {"id":"46045be9-0009-4000-8000-0008ffffffed","name":"Karen","age":11,"birthday":new Date("2100-12-31T23:59:59.996Z")} +// • {"id":"46045be9-0009-4000-8000-0008ffffffed","name":"Karen","age":11,"birthday":new Date("2100-12-31T23:59:59.997Z")} // • {"id":"fffffffe-0015-4000-95a0-f8e9ffffffe7","name":"Karen","birthday":new Date("1970-01-01T00:00:00.018Z")} // • {"id":"e2b066ec-000b-4000-bfff-ffe7ccb1828d","name":"Karen","age":17} // • {"id":"43b7d8e5-d043-42ef-8000-001a00000005","age":16,"birthday":new Date("2004-10-16T22:01:09.416Z")} -// • {"id":"00000007-2008-452e-8000-00133ed36be7","name":"Karen","age":6,"birthday":new Date("2100-12-31T23:59:59.981Z")} +// • {"id":"00000007-2008-452e-8000-00133ed36be7","name":"Karen","age":6,"birthday":new Date("2100-12-31T23:59:59.982Z")} // • … fc.record( diff --git a/website/docs/core-blocks/arbitraries/primitives/date.md b/website/docs/core-blocks/arbitraries/primitives/date.md index a6c576214ab..60dacdd460e 100644 --- a/website/docs/core-blocks/arbitraries/primitives/date.md +++ b/website/docs/core-blocks/arbitraries/primitives/date.md @@ -21,7 +21,7 @@ Generate any possible dates in the specified range. Both the lower bound and upp - `min?` — default: `new Date(-8640000000000000)` — _lower bound of the range (included)_ - `max?` — default: `new Date(8640000000000000)` — _upper bound of the range (included)_ -- `noInvalidDate?` — default: `true` — _when `true` the Date "Invalid Date" will never be defined_ +- `noInvalidDate?` — default: `false` — _when `true` the Date "Invalid Date" will never be defined_ **Usages:** @@ -41,7 +41,7 @@ fc.date({ min: new Date('2000-01-01T00:00:00.000Z') }); // • new Date("2000-01-01T00:00:00.039Z") // • new Date("2000-01-01T00:00:00.047Z") // • new Date("2000-01-01T00:00:00.003Z") -// • new Date("+275760-09-12T23:59:59.981Z") +// • new Date("+275760-09-12T23:59:59.982Z") // • … fc.date({ max: new Date('2000-01-01T00:00:00.000Z') }); @@ -58,9 +58,18 @@ fc.date({ min: new Date('2000-01-01T00:00:00.000Z'), max: new Date('2000-12-31T2 // • new Date("2000-05-15T03:02:40.263Z") // • new Date("2000-10-22T03:00:45.936Z") // • new Date("2000-02-25T19:00:10.679Z") -// • new Date("2000-12-31T23:59:59.996Z") +// • new Date("2000-12-31T23:59:59.997Z") // • new Date("2000-01-04T14:12:03.484Z") // • … + +fc.date({ noInvalidDate: true }); +// Examples of generated values: +// • new Date("-043663-07-08T11:17:34.486Z") +// • new Date("-169183-12-11T00:28:46.358Z") +// • new Date("1969-12-31T23:59:59.988Z") +// • new Date("1969-12-31T23:59:59.984Z") +// • new Date("-271821-04-20T00:00:00.033Z") +// • … ``` Resources: [API reference](https://fast-check.dev/api-reference/functions/date.html).