type-fest/test-d/jsonify.ts
Sindre Sorhus d743202a0a Revert "Revert "Jsonify: Fix handling of tuples (#638)""
This reverts commit 9fdac37ea5e1ea7fcc444a8df95c8eafe88dca2f.
2023-07-16 12:04:52 +02:00

333 lines
10 KiB
TypeScript

/* eslint-disable @typescript-eslint/consistent-type-definitions */
// TODO: Convert the `interface`'s to `type`s.
import {expectAssignable, expectNotAssignable, expectType} from 'tsd';
import type {EmptyObject, Jsonify, JsonValue, NegativeInfinity, PositiveInfinity} from '..';
interface A {
a: number;
}
class B {
a!: number;
}
interface V {
a?: number;
}
interface X {
a: Date;
}
interface Y {
a?: Date;
}
interface Z {
a: number | undefined;
}
interface W {
a?: () => any;
}
declare const a: Jsonify<A>;
declare const b: Jsonify<B>;
declare const v: V; // Not assignable to JsonValue because it is defined as interface
declare const x: X; // Not assignable to JsonValue because it contains Date value
declare const y: Y; // Not assignable to JsonValue because it contains Date value
declare const z: Z; // Not assignable to JsonValue because undefined is not valid Json value
declare const w: W; // Not assignable to JsonValue because a function is not valid Json value
expectAssignable<JsonValue>(null);
expectAssignable<JsonValue>(false);
expectAssignable<JsonValue>(0);
expectAssignable<JsonValue>('');
expectAssignable<JsonValue>([]);
expectAssignable<JsonValue>([] as const);
expectAssignable<JsonValue>({});
expectAssignable<JsonValue>([0]);
expectAssignable<JsonValue>({a: 0});
expectAssignable<JsonValue>(a);
expectAssignable<JsonValue>(b);
expectAssignable<JsonValue>({a: {b: true, c: {}}, d: [{}, 2, 'hi']});
expectAssignable<JsonValue>([{}, {a: 'hi'}, null, 3]);
expectNotAssignable<JsonValue>(new Date());
expectNotAssignable<JsonValue>([new Date()]);
expectNotAssignable<JsonValue>({a: new Date()});
expectNotAssignable<JsonValue>(v);
expectNotAssignable<JsonValue>(x);
expectNotAssignable<JsonValue>(y);
expectNotAssignable<JsonValue>(z);
expectNotAssignable<JsonValue>(w);
expectNotAssignable<JsonValue>(undefined);
expectNotAssignable<JsonValue>(5 as number | undefined);
// TODO: Convert this to a `type`.
interface Geometry {
type: 'Point' | 'Polygon';
coordinates: [number, number];
}
const point: Geometry = {
type: 'Point',
coordinates: [1, 1],
};
expectNotAssignable<JsonValue>(point);
expectAssignable<Jsonify<Geometry>>(point);
// The following const values are examples of values `v` that are not JSON, but are *jsonable* using
// `v.toJSON()` or `JSON.parse(JSON.stringify(v))`
declare const dateToJSON: Jsonify<Date>;
expectAssignable<string>(dateToJSON);
expectAssignable<JsonValue>(dateToJSON);
// The following commented `= JSON.parse(JSON.stringify(x))` is an example of how `parsedStringifiedX` could be created.
// * Note that this would be an unsafe assignment because `JSON.parse()` returns type `any`.
// But by inspection `JSON.stringify(x)` will use `x.a.toJSON()`. So the `JSON.parse()` result can be
// assigned to `Jsonify<X>` if the `@typescript-eslint/no-unsafe-assignment` ESLint rule is ignored
// or an `as Jsonify<X>` is added.
// * This test is not about how `parsedStringifiedX` is created, but about its type, so the `const` value is declared.
declare const parsedStringifiedX: Jsonify<X>; // = JSON.parse(JSON.stringify(x));
expectAssignable<JsonValue>(parsedStringifiedX);
expectAssignable<string>(parsedStringifiedX.a);
class NonJsonWithToJSON {
public fixture = new Map<string, number>([['a', 1], ['b', 2]]);
public toJSON(): {fixture: Array<[string, number]>} {
return {
fixture: [...this.fixture.entries()],
};
}
}
const nonJsonWithToJSON = new NonJsonWithToJSON();
expectNotAssignable<JsonValue>(nonJsonWithToJSON);
expectAssignable<JsonValue>(nonJsonWithToJSON.toJSON());
expectAssignable<Jsonify<NonJsonWithToJSON>>(nonJsonWithToJSON.toJSON());
class NonJsonWithToJSONWrapper {
public inner: NonJsonWithToJSON = nonJsonWithToJSON;
public override = 42;
public toJSON() {
const stringOverride = 'override';
return {
override: stringOverride,
inner: this.inner,
innerDeep: {inner: this.inner},
};
}
}
expectNotAssignable<JsonValue>(new NonJsonWithToJSONWrapper());
type InnerFixture = {fixture: Array<[string, number]>};
expectType<{
override: string;
inner: InnerFixture;
innerDeep: {inner: InnerFixture};
}>({} as Jsonify<NonJsonWithToJSONWrapper>);
class NonJsonWithInvalidToJSON {
public fixture = new Map<string, number>([['a', 1], ['b', 2]]);
// This is intentionally invalid `.toJSON()`.
// It is invalid because the result is not assignable to `JsonValue`.
public toJSON(): {fixture: Map<string, number>} {
return {
fixture: this.fixture,
};
}
}
const nonJsonWithInvalidToJSON = new NonJsonWithInvalidToJSON();
expectNotAssignable<JsonValue>(nonJsonWithInvalidToJSON);
expectNotAssignable<JsonValue>(nonJsonWithInvalidToJSON.toJSON());
// Not jsonable types; these types behave differently when used as plain values, as members of arrays and as values of objects
declare const undefined: undefined;
expectNotAssignable<JsonValue>(undefined);
declare const fn: (_: any) => void;
expectNotAssignable<JsonValue>(fn);
declare const symbol: symbol;
expectNotAssignable<JsonValue>(symbol);
// Plain values fail JSON.stringify()
declare const plainUndefined: Jsonify<typeof undefined>;
expectType<never>(plainUndefined);
declare const plainFn: Jsonify<typeof fn>;
expectType<never>(plainFn);
declare const plainSymbol: Jsonify<typeof symbol>;
expectType<never>(plainSymbol);
// Array members become null
declare const arrayMemberUndefined: Jsonify<Array<typeof undefined>>;
expectType<null[]>(arrayMemberUndefined);
declare const arrayMemberFn: Jsonify<Array<typeof fn>>;
expectType<null[]>(arrayMemberFn);
declare const arrayMemberSymbol: Jsonify<Array<typeof symbol>>;
expectType<null[]>(arrayMemberSymbol);
// When used in object values, these keys are filtered
declare const objectValueUndefined: Jsonify<{keep: string; undefined: typeof undefined}>;
expectType<{keep: string}>(objectValueUndefined);
declare const objectValueFn: Jsonify<{keep: string; fn: typeof fn}>;
expectType<{keep: string}>(objectValueFn);
declare const objectValueSymbol: Jsonify<{keep: string; symbol: typeof symbol}>;
expectType<{keep: string}>(objectValueSymbol);
// Symbol keys are filtered
declare const objectKeySymbol: Jsonify<{[key: typeof symbol]: number; keep: string}>;
expectType<{keep: string}>(objectKeySymbol);
// Number, String and Boolean values are turned into primitive counterparts
declare const number: Number;
expectNotAssignable<JsonValue>(number);
declare const string: String;
expectNotAssignable<JsonValue>(string);
declare const boolean: Boolean;
expectNotAssignable<JsonValue>(boolean);
declare const numberJson: Jsonify<typeof number>;
expectType<number>(numberJson);
declare const stringJson: Jsonify<typeof string>;
expectType<string>(stringJson);
declare const booleanJson: Jsonify<typeof boolean>;
expectType<boolean>(booleanJson);
declare const tupleJson: Jsonify<[string, Date]>;
expectType<[string, string]>(tupleJson);
declare const tupleRestJson: Jsonify<[string, ...Date[]]>;
expectType<[string, ...string[]]>(tupleRestJson);
declare const tupleStringJson: Jsonify<string[] & ['some value']>;
expectType<['some value']>(tupleStringJson);
// BigInt fails JSON.stringify
declare const bigInt: Jsonify<bigint>;
expectType<never>(bigInt);
declare const int8Array: Int8Array;
declare const int8ArrayJson: Jsonify<typeof int8Array>;
expectType<Record<string, number>>(int8ArrayJson);
declare const map: Map<string, number>;
declare const mapJson: Jsonify<typeof map>;
expectType<EmptyObject>(mapJson);
expectAssignable<Jsonify<typeof map>>({});
// Regression test for https://github.com/sindresorhus/type-fest/issues/466
expectNotAssignable<Jsonify<typeof map>>(42);
expectNotAssignable<Jsonify<typeof map>>({foo: 42});
declare const set: Set<string>;
declare const setJson: Jsonify<typeof set>;
expectType<EmptyObject>(setJson);
expectAssignable<Jsonify<typeof set>>({});
// Regression test for https://github.com/sindresorhus/type-fest/issues/466
expectNotAssignable<Jsonify<typeof set>>(42);
expectNotAssignable<Jsonify<typeof set>>({foo: 42});
// Positive and negative Infinity, NaN and null are turned into null
// NOTE: NaN is not detectable in TypeScript, so it is not tested; see https://github.com/sindresorhus/type-fest/issues/406
declare const positiveInfinity: PositiveInfinity;
declare const positiveInfJson: Jsonify<typeof positiveInfinity>;
expectType<null>(positiveInfJson);
declare const negativeInf: NegativeInfinity;
declare const negativeInfJson: Jsonify<typeof negativeInf>;
expectType<null>(negativeInfJson);
// Test that optional type members are not discarded wholesale.
type OptionalPrimitive = {
a?: string;
};
type OptionalTypeUnion = {
a?: string | (() => any);
};
type NonOptionalTypeUnion = {
a: string | undefined;
};
declare const jsonifiedOptionalPrimitive: Jsonify<OptionalPrimitive>;
declare const jsonifiedOptionalTypeUnion: Jsonify<OptionalTypeUnion>;
declare const jsonifiedNonOptionalTypeUnion: Jsonify<NonOptionalTypeUnion>;
expectType<{a?: string}>(jsonifiedOptionalPrimitive);
expectType<{}>(jsonifiedOptionalTypeUnion);
expectType<{a?: string}>(jsonifiedNonOptionalTypeUnion);
// Test for 'Jsonify support for optional object keys, unserializable object values' #424
// See https://github.com/sindresorhus/type-fest/issues/424
type AppData = {
// Should be kept
requiredString: string;
requiredUnion: number | boolean;
// Should be kept and set to optional
optionalString?: string;
optionalUnion?: number | string;
optionalStringUndefined: string | undefined;
optionalUnionUndefined: number | string | undefined;
// Should be omitted
requiredFunction: () => any;
optionalFunction?: () => any;
requiredFunctionUnion: string | (() => any);
optionalFunctionUnion?: string | (() => any);
optionalFunctionUndefined: (() => any) | undefined;
optionalFunctionUnionUndefined: string | (() => any) | undefined;
};
type ExpectedAppDataJson = {
requiredString: string;
requiredUnion: number | boolean;
optionalString?: string;
optionalUnion?: string | number;
optionalStringUndefined?: string;
optionalUnionUndefined?: string | number;
};
declare const response: Jsonify<AppData>;
expectType<ExpectedAppDataJson>(response);
expectType<any>({} as Jsonify<any>);
declare const objectWithAnyProperty: Jsonify<{
a: any;
}>;
expectType<{a: any}>(objectWithAnyProperty);
declare const objectWithAnyProperties: Jsonify<Record<string, any>>;
expectType<Record<string, any>>(objectWithAnyProperties);
/// #629
// declare const readonlyTuple: Jsonify<readonly [1, 2, 3]>;
// expectType<readonly [1, 2, 3]>(readonlyTuple);