From b87e8b9b4b7d564b40dfb10495c4268fdf6db038 Mon Sep 17 00:00:00 2001 From: Nicolas DUBIEN Date: Tue, 28 Nov 2023 22:19:41 +0100 Subject: [PATCH 1/3] =?UTF-8?q?=20=F0=9F=92=A5=20Include=20invalid=20dates?= =?UTF-8?q?=20by=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 6b9bee03e79..d07fe912850 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 @@ -10,7 +10,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); @@ -22,26 +22,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 ba381817219..a0b16c5f0a6 100644 --- a/packages/fast-check/test/unit/arbitrary/date.spec.ts +++ b/packages/fast-check/test/unit/arbitrary/date.spec.ts @@ -71,7 +71,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 = vi.spyOn(IntegerMock, 'integer'); @@ -115,7 +115,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()); @@ -153,7 +153,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; @@ -203,7 +203,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; @@ -213,7 +219,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 69fb9b3e019..a33db56b627 100644 --- a/website/docs/core-blocks/arbitraries/composites/object.md +++ b/website/docs/core-blocks/arbitraries/composites/object.md @@ -129,11 +129,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 4f58cdc7b87..a7bc554d5bc 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). From 61ce9dad64ea43a274016ad95e85d10879be6f87 Mon Sep 17 00:00:00 2001 From: Nicolas DUBIEN Date: Thu, 9 Jan 2025 08:33:13 +0000 Subject: [PATCH 2/3] fix doc --- website/docs/migration/from-3.x-to-4.x.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/website/docs/migration/from-3.x-to-4.x.md b/website/docs/migration/from-3.x-to-4.x.md index 04b5a5ebff1..5d222d145b6 100644 --- a/website/docs/migration/from-3.x-to-4.x.md +++ b/website/docs/migration/from-3.x-to-4.x.md @@ -21,6 +21,17 @@ Version 4 of fast-check introduces significant changes as part of its major rele To ensure a smoother migration to version 4, we recommend first upgrading to the latest minor release of version 3. Then, review and address the following deprecation notices to align your codebase with supported patterns. +### Changes on `date` + +In version 4, the `date` arbitrary will generate any `Date` instances by default, including Invalid Date. If your code cannot handle invalid dates, you should add the `noInvalidDate: true` constraint to the configuration of your date builder to exclude such values. + +```diff +-fc.date(); ++fc.date({ noInvalidDate: true }); +``` + +Related pull requests: [#5589](https://github.com/dubzzz/fast-check/pull/5589) + ### Changes on `record` In earlier versions, the `record` arbitrary included a flag named `withDeletedKeys`. Starting with version 2.11.0, this flag was deprecated and replaced by a new flag called `requiredKeys`. In version 4.0.0, the deprecated `withDeletedKeys` flag has been removed entirely. From 4001a66acebb95c95d9bd3a134e894c9b8fbf308 Mon Sep 17 00:00:00 2001 From: Nicolas DUBIEN Date: Thu, 9 Jan 2025 09:33:45 +0100 Subject: [PATCH 3/3] Create swift-adults-pay.md --- .changeset/swift-adults-pay.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/swift-adults-pay.md diff --git a/.changeset/swift-adults-pay.md b/.changeset/swift-adults-pay.md new file mode 100644 index 00000000000..05e92856142 --- /dev/null +++ b/.changeset/swift-adults-pay.md @@ -0,0 +1,5 @@ +--- +"fast-check": major +--- + + 💥 Include invalid dates by default