diff --git a/packages/fast-check/src/arbitrary/_internals/ArrayArbitrary.ts b/packages/fast-check/src/arbitrary/_internals/ArrayArbitrary.ts index 4fb816ab0fd..1c7da39e024 100644 --- a/packages/fast-check/src/arbitrary/_internals/ArrayArbitrary.ts +++ b/packages/fast-check/src/arbitrary/_internals/ArrayArbitrary.ts @@ -46,7 +46,7 @@ export class ArrayArbitrary extends Arbitrary { // Whenever passing a isEqual to ArrayArbitrary, you also have to filter // it's output just in case produced values are too small (below minLength) readonly setBuilder: CustomSetBuilder> | undefined, - readonly customSlices: T[][], + readonly customSlices: readonly (readonly T[])[], ) { super(); this.lengthArb = integer({ min: minLength, max: maxGeneratedLength }); diff --git a/packages/fast-check/src/arbitrary/_internals/helpers/BuildSlicedGenerator.ts b/packages/fast-check/src/arbitrary/_internals/helpers/BuildSlicedGenerator.ts index 334fda949ba..07e91d249d7 100644 --- a/packages/fast-check/src/arbitrary/_internals/helpers/BuildSlicedGenerator.ts +++ b/packages/fast-check/src/arbitrary/_internals/helpers/BuildSlicedGenerator.ts @@ -17,7 +17,7 @@ import type { SlicedGenerator } from '../interfaces/SlicedGenerator'; export function buildSlicedGenerator( arb: Arbitrary, mrng: Random, - slices: T[][], + slices: readonly (readonly T[])[], biasFactor: number | undefined, ): SlicedGenerator { // We by-pass any slice-based logic if one of: diff --git a/packages/fast-check/src/arbitrary/_internals/helpers/QualifiedObjectConstraints.ts b/packages/fast-check/src/arbitrary/_internals/helpers/QualifiedObjectConstraints.ts index 523da9e342e..8ea3a2574e7 100644 --- a/packages/fast-check/src/arbitrary/_internals/helpers/QualifiedObjectConstraints.ts +++ b/packages/fast-check/src/arbitrary/_internals/helpers/QualifiedObjectConstraints.ts @@ -48,7 +48,7 @@ export interface ObjectConstraints { * @defaultValue {@link boolean}, {@link integer}, {@link double}, {@link string}, null, undefined, Number.NaN, +0, -0, Number.EPSILON, Number.MIN_VALUE, Number.MAX_VALUE, Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER, Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY * @remarks Since 0.0.7 */ - values?: Arbitrary[]; + values?: readonly Arbitrary[]; /** * Also generate boxed versions of values * @defaultValue false @@ -150,13 +150,13 @@ function defaultValues( } /** @internal */ -function boxArbitraries(arbs: Arbitrary[]): Arbitrary[] { +function boxArbitraries(arbs: readonly Arbitrary[]): Arbitrary[] { return arbs.map((arb) => boxedArbitraryBuilder(arb)); } /** @internal */ -function boxArbitrariesIfNeeded(arbs: Arbitrary[], boxEnabled: boolean): Arbitrary[] { - return boxEnabled ? boxArbitraries(arbs).concat(arbs) : arbs; +function boxArbitrariesIfNeeded(arbs: readonly Arbitrary[], boxEnabled: boolean): Arbitrary[] { + return boxEnabled ? boxArbitraries(arbs).concat(arbs) : [...arbs]; } /** diff --git a/packages/fast-check/src/arbitrary/_internals/implementations/SlicedBasedGenerator.ts b/packages/fast-check/src/arbitrary/_internals/implementations/SlicedBasedGenerator.ts index 30bd34c79e5..085a6097e7b 100644 --- a/packages/fast-check/src/arbitrary/_internals/implementations/SlicedBasedGenerator.ts +++ b/packages/fast-check/src/arbitrary/_internals/implementations/SlicedBasedGenerator.ts @@ -15,7 +15,7 @@ export class SlicedBasedGenerator implements SlicedGenerator { constructor( private readonly arb: Arbitrary, private readonly mrng: Random, - private readonly slices: T[][], + private readonly slices: readonly (readonly T[])[], private readonly biasFactor: number, ) {} attemptExact(targetLength: number): void { diff --git a/packages/fast-check/src/arbitrary/array.ts b/packages/fast-check/src/arbitrary/array.ts index 5153810530e..4abb895f3dd 100644 --- a/packages/fast-check/src/arbitrary/array.ts +++ b/packages/fast-check/src/arbitrary/array.ts @@ -63,7 +63,7 @@ export interface ArrayConstraintsInternal extends ArrayConstraints { * Each entry must have at least one element of type T into it. * Each T must be a value acceptable for the arbitrary passed to the array. */ - experimentalCustomSlices?: T[][]; + experimentalCustomSlices?: readonly (readonly T[])[]; } /** diff --git a/packages/fast-check/src/arbitrary/webUrl.ts b/packages/fast-check/src/arbitrary/webUrl.ts index 0bb1dc7686e..8ec1fb335a7 100644 --- a/packages/fast-check/src/arbitrary/webUrl.ts +++ b/packages/fast-check/src/arbitrary/webUrl.ts @@ -23,7 +23,7 @@ export interface WebUrlConstraints { * @defaultValue ['http', 'https'] * @remarks Since 1.14.0 */ - validSchemes?: string[]; + validSchemes?: readonly string[]; /** * Settings for {@link webAuthority} * @defaultValue {} diff --git a/packages/fast-check/test/unit/arbitrary/readonly-constraints.spec.ts b/packages/fast-check/test/unit/arbitrary/readonly-constraints.spec.ts new file mode 100644 index 00000000000..9577d4bcc59 --- /dev/null +++ b/packages/fast-check/test/unit/arbitrary/readonly-constraints.spec.ts @@ -0,0 +1,60 @@ +/** + * Test to verify readonly constraint annotations work correctly + * This demonstrates that users can now pass readonly arrays and objects as constraints + */ +import { describe, it, expect } from 'vitest'; +import { object } from '../../../src/arbitrary/object'; +import { webUrl } from '../../../src/arbitrary/webUrl'; +import { boolean } from '../../../src/arbitrary/boolean'; +import { integer } from '../../../src/arbitrary/integer'; + +describe('Readonly constraint support', () => { + it('should accept readonly arrays for ObjectConstraints.values', () => { + const readonlyValues = [boolean(), integer()] as const; + + // This should compile and work without errors + const objectArb = object({ + values: readonlyValues, + }); + + // Verify it generates values + const value = objectArb.generate({ + nextInt: () => 0, + nextBigInt: () => 0n, + nextDouble: () => 0.5, + }, undefined); + + expect(typeof value.value_).toBe('object'); + }); + + it('should accept readonly string arrays for WebUrlConstraints.validSchemes', () => { + const readonlySchemes = ['http', 'https'] as const; + + // This should compile and work without errors + const urlArb = webUrl({ + validSchemes: readonlySchemes, + }); + + // Verify it generates values + const value = urlArb.generate({ + nextInt: () => 0, + nextBigInt: () => 0n, + nextDouble: () => 0.5, + }, undefined); + + expect(typeof value.value_).toBe('string'); + expect(value.value_).toMatch(/^https?:\/\//); + }); + + it('should maintain backward compatibility with mutable arrays', () => { + // Test that mutable arrays still work + const mutableValues = [boolean(), integer()]; + const mutableSchemes = ['http', 'https']; + + const objectArb = object({ values: mutableValues }); + const urlArb = webUrl({ validSchemes: mutableSchemes }); + + expect(objectArb).toBeDefined(); + expect(urlArb).toBeDefined(); + }); +}); \ No newline at end of file