Skip to content

Commit

Permalink
✨ Add option to generate unicode values in object (#5010)
Browse files Browse the repository at this point in the history
**Description**

Recent bugs found in Vitest around badly formed snapshot names would
have benefit from having a built-in way to switch on unicode generation
on `object` and `anything`.

The proposed PR adds a new flag onto them. It allows users to force
generation of unicode strings as keys and values of the instances
produced by these two helpers. The flag is called `withUnicodeString`.

<!-- Please provide a short description and potentially linked issues
hustifying the need for this PR -->

<!-- * Your PR is fixing a bug or regression? Check for existing issues
related to this bug and link them -->
<!-- * Your PR is adding a new feature? Make sure there is a related
issue or discussion attached to it -->

<!-- You can provide any additional context to help into understanding
what's this PR is attempting to solve: reproduction of a bug, code
snippets... -->

**Checklist** — _Don't delete this checklist and make sure you do the
following before opening the PR_

- [x] The name of my PR follows [gitmoji](https://gitmoji.dev/)
specification
- [x] My PR references one of several related issues (if any)
- [x] New features or breaking changes must come with an associated
Issue or Discussion
- [x] My PR does not add any new dependency without an associated Issue
or Discussion
- [x] My PR includes bumps details, please run `yarn bump` and flag the
impacts properly
- [x] My PR adds relevant tests and they would have failed without my PR
(when applicable)

<!-- More about contributing at
https://github.com/dubzzz/fast-check/blob/main/CONTRIBUTING.md -->

**Advanced**

<!-- How to fill the advanced section is detailed below! -->

- [x] Category: ✨ Introduce new features
- [x] Impacts: N.A.

<!-- [Category] Please use one of the categories below, it will help us
into better understanding the urgency of the PR -->
<!-- * ✨ Introduce new features -->
<!-- * 📝 Add or update documentation -->
<!-- * ✅ Add or update tests -->
<!-- * 🐛 Fix a bug -->
<!-- * 🏷️ Add or update types -->
<!-- * ⚡️ Improve performance -->
<!-- * _Other(s):_ ... -->

<!-- [Impacts] Please provide a comma separated list of the potential
impacts that might be introduced by this change -->
<!-- * Generated values: Can your change impact any of the existing
generators in terms of generated values, if so which ones? when? -->
<!-- * Shrink values: Can your change impact any of the existing
generators in terms of shrink values, if so which ones? when? -->
<!-- * Performance: Can it require some typings changes on user side?
Please give more details -->
<!-- * Typings: Is there a potential performance impact? In which cases?
-->
  • Loading branch information
dubzzz authored May 22, 2024
1 parent 6a2677c commit 50394c8
Show file tree
Hide file tree
Showing 6 changed files with 59 additions and 19 deletions.
8 changes: 8 additions & 0 deletions .yarn/versions/62e8ed33.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
releases:
fast-check: minor

declined:
- "@fast-check/ava"
- "@fast-check/jest"
- "@fast-check/vitest"
- "@fast-check/worker"
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { Arbitrary } from '../../../check/arbitrary/definition/Arbitrary';
import { boolean } from '../../boolean';
import { constant } from '../../constant';
import { double } from '../../double';
import { fullUnicodeString } from '../../fullUnicodeString';
import { maxSafeInteger } from '../../maxSafeInteger';
import { oneof } from '../../oneof';
import { string } from '../../string';
Expand Down Expand Up @@ -103,6 +104,13 @@ export interface ObjectConstraints {
* @remarks Since 2.13.0
*/
withSparseArray?: boolean;
/**
* Replace the arbitrary of strings defaulted for key and values by one able to generate unicode strings with non-ascii characters.
* If you override key and/or values constraint, this flag will not apply to your override.
* @defaultValue false
* @remarks Since 3.19.0
*/
withUnicodeString?: boolean;
}

/** @internal */
Expand All @@ -113,18 +121,24 @@ type ObjectConstraintsOptionalValues = 'depthSize' | 'maxDepth' | 'maxKeys' | 's
* @internal
*/
export type QualifiedObjectConstraints = Required<
Omit<ObjectConstraints, 'withBoxedValues' | ObjectConstraintsOptionalValues>
Omit<ObjectConstraints, 'withBoxedValues' | 'withUnicodeString' | ObjectConstraintsOptionalValues>
> &
Pick<ObjectConstraints, ObjectConstraintsOptionalValues>;

/** @internal */
function defaultValues(constraints: { size: SizeForArbitrary }): Arbitrary<unknown>[] {
type DefaultValuesConstraints = { size: SizeForArbitrary };

/** @internal */
function defaultValues(
constraints: DefaultValuesConstraints,
stringArbitrary: (constraints: DefaultValuesConstraints) => Arbitrary<string>,
): Arbitrary<unknown>[] {
return [
boolean(),
maxSafeInteger(),
double(),
string(constraints),
oneof(string(constraints), constant(null), constant(undefined)),
stringArbitrary(constraints),
oneof(stringArbitrary(constraints), constant(null), constant(undefined)),
];
}

Expand All @@ -146,11 +160,12 @@ export function toQualifiedObjectConstraints(settings: ObjectConstraints = {}):
function orDefault<T>(optionalValue: T | undefined, defaultValue: T): T {
return optionalValue !== undefined ? optionalValue : defaultValue;
}
const stringArbitrary = settings.withUnicodeString ? fullUnicodeString : string;
const valueConstraints = { size: settings.size };
return {
key: orDefault(settings.key, string(valueConstraints)),
key: orDefault(settings.key, stringArbitrary(valueConstraints)),
values: boxArbitrariesIfNeeded(
orDefault(settings.values, defaultValues(valueConstraints)),
orDefault(settings.values, defaultValues(valueConstraints, stringArbitrary)),
orDefault(settings.withBoxedValues, false),
),
depthSize: settings.depthSize,
Expand Down
1 change: 1 addition & 0 deletions packages/fast-check/test/e2e/GenerateAllValues.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ describe(`Generate all values (seed: ${seed})`, () => {
withDate: true,
withTypedArray: true,
withSparseArray: true,
withUnicodeString: true,
...(typeof BigInt !== 'undefined' ? { withBigInt: true } : {}),
});
while (++numTries <= 10000) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, it, expect } from 'vitest';
import fc from 'fast-check';
import fc, { stringify } from 'fast-check';

import { anyArbitraryBuilder } from '../../../../../src/arbitrary/_internals/builders/AnyArbitraryBuilder';
import type { ObjectConstraints } from '../../../../../src/arbitrary/_internals/helpers/QualifiedObjectConstraints';
Expand Down Expand Up @@ -102,6 +102,7 @@ describe('anyArbitraryBuilder (integration)', () => {
withSet: fc.boolean(),
withSparseArray: fc.boolean(),
withTypedArray: fc.boolean(),
withUnicodeString: fc.boolean(),
},
{ requiredKeys: [] },
)
Expand Down Expand Up @@ -148,6 +149,9 @@ describe('anyArbitraryBuilder (integration)', () => {
if (!extra.withTypedArray) {
expect(isTypedArray(v)).toBe(false);
}
if (!extra.withUnicodeString) {
expect(stringify(v)).toSatisfy(doesNotIncludeAnySurrogateCharacter);
}
// No check for !extra.withObjectString as nothing prevent normal string builders to build such strings
// In the coming major releases withObjectString might even disappear
};
Expand All @@ -174,6 +178,11 @@ describe('anyArbitraryBuilder (integration)', () => {

// Helpers

function doesNotIncludeAnySurrogateCharacter(s: string): boolean {
// No character is a part of a surrogate pair
return s.split('').every((c) => c < '\uD800' || c > '\uDFFF');
}

function isBigInt(v: unknown): boolean {
return typeof v === 'bigint';
}
Expand Down
3 changes: 2 additions & 1 deletion packages/fast-check/test/unit/utils/stringify.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class CustomTagThrowingToString {
}
}

const anythingEnableAll = {
const anythingEnableAll: fc.ObjectConstraints = {
withBoxedValues: true,
withMap: true,
withSet: true,
Expand All @@ -46,6 +46,7 @@ const anythingEnableAll = {
withDate: true,
withTypedArray: true,
withSparseArray: true,
withUnicodeString: true,
...(typeof BigInt !== 'undefined' ? { withBigInt: true } : {}),
};

Expand Down
Loading

0 comments on commit 50394c8

Please sign in to comment.