From f22dbb7036a4cff3140a55cc7580e91d6538abd8 Mon Sep 17 00:00:00 2001 From: Mel Bourgeois Date: Sat, 24 Feb 2024 21:41:51 -0600 Subject: [PATCH] add test file for types --- tests/test-types.ts | 92 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 tests/test-types.ts diff --git a/tests/test-types.ts b/tests/test-types.ts new file mode 100644 index 00000000..5b119ecc --- /dev/null +++ b/tests/test-types.ts @@ -0,0 +1,92 @@ +/** + * This file serves to validate the public TypeScript API for YJS. + * + * It is not included in `npm run lint` or any other automated type checking, but can be used + * by those working on YJS types to ensure that the public-facing type interface remains valid. + * + * Any lines which are supposed to demonstrate statements that _would_ generate type errors + * should be clearly marked with the type error that is expected to result, to provide a + * negative test case. + */ + +import * as Y from "../dist/src/index"; + +/* + * Typed maps + * + * - Key names are autocompleted in first parameter of `get` and `set`. + * - `MapType` value types are constrained to valid Y.Map contents. + */ +type MyType = { + foo: string; + bar: number | null; + baz?: boolean; +}; + +// Constructor argument keys & values are typechecked, and keys are autocompleted. +// Multiple items for each key and partial initialization are allowed. +const map = new Y.Map([ + ["foo", ""], + ["foo", "even better"], + // ERROR: Type '["baz", number]' is not assignable to type '["foo", string] | ["bar", number | null] | ["baz", boolean | undefined]'. + ["baz", 3], +]); + +// Entries are still allowed to be omitted, so get() still returns | undefined. +const defaultMap = new Y.Map(); + +// `json` has a type of `Partial` +const json = defaultMap.toJSON(); + +// string | undefined +const fooValue = map.get("foo"); +// literal "hi" (string) +const fooSet = map.set("foo", "hi"); +// number | null | undefined +const barValue = map.get("bar"); +// ERROR: Argument of type '"hi"' is not assignable to parameter of type 'number | null'. +const barSet = map.set("bar", "hi"); +// ERROR: Argument of type '"bomb"' is not assignable to parameter of type 'keyof MyType'. +const missingGet = map.get("bomb"); +// Escape hatch: get() +const migrateGet = map.get("extraneousKey"); + +// ERROR: Type '' does not satisfy the constraint 'Record'. +const invalidMap = new Y.Map<{ invalid: () => void }>(); +const invalidMap2 = new Y.Map<{ invalid: Blob }>(); +// Arbitrarily complex valid types are still allowed +type ComplexType = { + n: null; + b: boolean; + s: string; + i: number; + u: Uint8Array; + a: null | boolean | string | number | Uint8Array[]; +}; +const complexValidType = new Y.Map< + ComplexType & { nested: ComplexType & { deeper: ComplexType[] } } +>(); + +/* + * Default behavior + * + * Provides basic typechecking over the range of possible map values. + */ +const untyped = new Y.Map(); + +// MapValue | undefined +const boop = untyped.get("default"); +// Still validates value types: ERROR: Argument of type '() => string' is not assignable to parameter of type 'MapValue'. +const moop = untyped.set("anything", () => "whoops"); + +/* + * `any` maps (bypass typechecking) + */ +const anyMap = new Y.Map(); + +// any +const fooValueAny = anyMap.get("foo"); +// literal "hi" (string) +const fooSetAny = anyMap.set("foo", "hi"); +// Allowed because `any` unlocks cowboy mode +const barSetAny = anyMap.set("bar", () => "hi");