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

💥 Include invalid dates by default #5589

Merged
merged 3 commits into from
Jan 9, 2025
Merged
Show file tree
Hide file tree
Changes from all 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
5 changes: 5 additions & 0 deletions .changeset/swift-adults-pay.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"fast-check": major
---

💥 Include invalid dates by default
4 changes: 2 additions & 2 deletions packages/fast-check/src/arbitrary/date.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -48,7 +48,7 @@ export function date(constraints: DateConstraints = {}): Arbitrary<Date> {
// 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');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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());
}
}),
);
});
});
20 changes: 15 additions & 5 deletions packages/fast-check/test/unit/arbitrary/date.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<number>();
const { instance: mappedInstance } = fakeArbitrary<Date>();
const integer = vi.spyOn(IntegerMock, 'integer');
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -153,7 +153,7 @@ describe('date (integration)', () => {
const extraParameters: fc.Arbitrary<Extra> = 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;
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down
4 changes: 2 additions & 2 deletions website/docs/core-blocks/arbitraries/composites/object.md
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
15 changes: 12 additions & 3 deletions website/docs/core-blocks/arbitraries/primitives/date.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:**

Expand All @@ -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') });
Expand All @@ -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).
Expand Down
11 changes: 11 additions & 0 deletions website/docs/migration/from-3.x-to-4.x.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Loading