@anywidget/types
Advanced tools
+74
| import { describe, it, expectTypeOf } from "vitest"; | ||
| import type { AnyModel } from "./index.js"; | ||
| declare let model: AnyModel; | ||
| declare let typedModel: AnyModel<{ value: number, name: string }>; | ||
| describe("AnyModel.get", () => { | ||
| it("uses strict types when model is provided", () => { | ||
| expectTypeOf(typedModel.get("value")).toEqualTypeOf<number>(); | ||
| expectTypeOf(typedModel.get("name")).toEqualTypeOf<string>(); | ||
| // @ts-expect-error - foo is not found on the model | ||
| typedModel.get("foo"); | ||
| }); | ||
| it("defers to any when model is unknown", () => { | ||
| expectTypeOf(model.get("foo")).toEqualTypeOf<any>(); | ||
| }); | ||
| }); | ||
| describe("AnyModel.set", () => { | ||
| it("requires strict types when model is provided", () => { | ||
| typedModel.set("value", 42); | ||
| typedModel.set("name", "Ricky Martin"); | ||
| // @ts-expect-error - foo is not found on the model | ||
| typedModel.set("foo", "bar"); | ||
| }); | ||
| it("allows any when model is unknown", () => { | ||
| model.set("foo", "bar"); | ||
| }); | ||
| }); | ||
| describe("AnyModel.on", () => { | ||
| it("infers custom message payload for untyped Model", async () => { | ||
| model.on("msg:custom", (msg, buffers) => { | ||
| expectTypeOf(msg).toEqualTypeOf<any>(); | ||
| expectTypeOf(buffers).toEqualTypeOf<DataView[]>(); | ||
| }); | ||
| }); | ||
| it("infers custom message payload for typed Model", async () => { | ||
| typedModel.on("msg:custom", (msg, buffers) => { | ||
| expectTypeOf(msg).toEqualTypeOf<any>(); | ||
| expectTypeOf(buffers).toEqualTypeOf<DataView[]>(); | ||
| }); | ||
| }); | ||
| it("infers any payload for untyped Model", async () => { | ||
| model.on("change:value", (context, value) => { | ||
| expectTypeOf(context).toEqualTypeOf<unknown>(); | ||
| expectTypeOf(value).toEqualTypeOf<any>(); | ||
| }); | ||
| }); | ||
| it("infers typed payload for typed Model", async () => { | ||
| typedModel.on("change:value", (context, value) => { | ||
| expectTypeOf(context).toEqualTypeOf<unknown>(); | ||
| expectTypeOf(value).toEqualTypeOf<number>(); | ||
| }); | ||
| }); | ||
| it("infers any payload for unknown field of typed Model", async () => { | ||
| typedModel.on("change:foo", (context, value) => { | ||
| expectTypeOf(context).toEqualTypeOf<unknown>(); | ||
| expectTypeOf(value).toEqualTypeOf<any>(); | ||
| }); | ||
| }); | ||
| it("infers any for unknown event", async () => { | ||
| model.on("foo:bar", (...args) => { | ||
| expectTypeOf(args).toEqualTypeOf<any[]>(); | ||
| }); | ||
| }); | ||
| }); |
+6
-0
| # @anywidget/types | ||
| ## 0.1.3 | ||
| ### Patch Changes | ||
| - feat: Infer event payloads from model ([`272782b`](https://github.com/manzt/anywidget/commit/272782bb919355854cf23ccba430c87b7cc28523)) | ||
| ## 0.1.2 | ||
@@ -4,0 +10,0 @@ |
+30
-17
@@ -1,15 +0,6 @@ | ||
| type MaybePromise<T> = T | Promise<T>; | ||
| type Awaitable<T> = T | Promise<T>; | ||
| type ObjectHash = Record<string, any>; | ||
| type CleanupFn = () => MaybePromise<void>; | ||
| type ChangeEventHandler<Payload> = (_: unknown, value: Payload) => void; | ||
| type EventHandler = (...args: any[]) => void; | ||
| /** | ||
| * JavaScript events (used in the methods of the Events interface) | ||
| */ | ||
| interface EventHandler { | ||
| (...args: any[]): void; | ||
| } | ||
| /** | ||
| * Utility type to infer possible event names from a model. | ||
| * | ||
| * Autocomplete works for literal string unions, but adding a union | ||
@@ -21,3 +12,3 @@ * of `string` negates autocomplete entirely. This is a workaround | ||
| */ | ||
| type EventName<T extends PropertyKey> = `change:${T & string}` | 'msg:custom' | (string & {}); | ||
| type LiteralUnion<T, U = string> = T | (U & {}); | ||
@@ -27,6 +18,26 @@ export interface AnyModel<T extends ObjectHash = ObjectHash> { | ||
| set<K extends keyof T>(key: K, value: T[K]): void; | ||
| off<K extends keyof T>(eventName?: EventName<K> | null, callback?: EventHandler | null): void; | ||
| on<K extends keyof T>(eventName: EventName<K>, callback: EventHandler): void; | ||
| off<K extends keyof T>( | ||
| eventName?: LiteralUnion<`change:${K & string}` | "msg:custom"> | null, | ||
| callback?: EventHandler | null, | ||
| ): void; | ||
| on( | ||
| eventName: "msg:custom", | ||
| callback: (msg: any, buffers: DataView[]) => void, | ||
| ): void; | ||
| on<K extends `change:${keyof T & string}`>( | ||
| eventName: K, | ||
| callback: K extends `change:${infer Key}` ? ChangeEventHandler<T[Key]> | ||
| : never, | ||
| ): void; | ||
| on<K extends `change:${string}`>( | ||
| eventName: K, | ||
| callback: ChangeEventHandler<any>, | ||
| ): void; | ||
| on(eventName: string, callback: EventHandler): void; | ||
| save_changes(): void; | ||
| send(content: any, callbacks?: any, buffers?: ArrayBuffer[] | ArrayBufferView[]): void; | ||
| send( | ||
| content: any, | ||
| callbacks?: any, | ||
| buffers?: ArrayBuffer[] | ArrayBufferView[], | ||
| ): void; | ||
| } | ||
@@ -40,3 +51,5 @@ | ||
| export interface Render<T extends ObjectHash = ObjectHash> { | ||
| (context: RenderContext<T>): MaybePromise<void | CleanupFn>; | ||
| (context: RenderContext<T>): Awaitable<void | (() => Awaitable<void>)>; | ||
| } | ||
+2
-2
| { | ||
| "name": "@anywidget/types", | ||
| "type": "module", | ||
| "version": "0.1.2", | ||
| "version": "0.1.3", | ||
| "description": "utility types for anywidget", | ||
@@ -16,4 +16,4 @@ "main": "index.js", | ||
| "scripts": { | ||
| "typecheck": "tsc --noEmit" | ||
| "typecheck": "vitest typecheck --run" | ||
| } | ||
| } |
+0
-1
@@ -35,2 +35,1 @@ # @anywidget/types | ||
| MIT | ||
+8
-1
| { | ||
| "extends": "../../tsconfig.json" | ||
| "extends": "../../tsconfig.json", | ||
| "compilerOptions": { | ||
| "moduleResolution": "node", | ||
| "esModuleInterop": true, | ||
| // TODO: We want to typecheck index.d.ts but not the | ||
| // rest of the node_modules. | ||
| // "skipLibCheck": false | ||
| } | ||
| } |
| import { vi, describe, it, expectTypeOf } from "vitest"; | ||
| import type { ObjectHash, AnyModel } from "./index.js"; | ||
| function createModel<T extends ObjectHash = ObjectHash>(): AnyModel<T> { | ||
| return { | ||
| get: vi.fn(), | ||
| set: vi.fn(), | ||
| on: vi.fn(), | ||
| off: vi.fn(), | ||
| save_changes: vi.fn(), | ||
| send: vi.fn(), | ||
| }; | ||
| } | ||
| describe("AnyModel.get", () => { | ||
| it("uses strict types when model is provided", () => { | ||
| let model = createModel<{ value: number, name: string }>(); | ||
| expectTypeOf(model.get("value")).toEqualTypeOf<number>(); | ||
| expectTypeOf(model.get("name")).toEqualTypeOf<string>(); | ||
| // @ts-expect-error - foo is not found on the model | ||
| model.get("foo"); | ||
| }); | ||
| it("defers to any when model is unknown", () => { | ||
| let model = createModel(); | ||
| expectTypeOf(model.get("foo")).toEqualTypeOf<any>(); | ||
| }); | ||
| }); | ||
| describe("AnyModel.set", () => { | ||
| it("requires strict types when model is provided", () => { | ||
| let model = createModel<{ value: number, name: string }>(); | ||
| model.set("value", 42); | ||
| model.set("name", "Ricky Martin"); | ||
| // @ts-expect-error - foo is not found on the model | ||
| model.set("foo", "bar"); | ||
| }); | ||
| it("allows any when model is unknown", () => { | ||
| let model = createModel(); | ||
| model.set("foo", "bar"); | ||
| }); | ||
| }); |
7382
28.47%120
60%35
-2.78%