| 1 | package hostrpc |
| 2 | |
| 3 | import ( |
| 4 | "fmt" |
| 5 | "strings" |
| 6 | "testing" |
| 7 | ) |
| 8 | |
| 9 | type tsOther struct { |
| 10 | Name string `json:"name"` |
| 11 | } |
| 12 | |
| 13 | type tsRecord struct { |
| 14 | Label string `json:"label"` |
| 15 | Weird string `json:"content-type"` |
| 16 | Next *tsRecord `json:"next"` |
| 17 | Tags []string `json:"tags,omitempty"` |
| 18 | Owners []*tsOther `json:"owners"` |
| 19 | Extra map[string]any `json:"extra"` |
| 20 | Payload any `json:"payload"` |
| 21 | Nested struct{ On bool } `json:"nested"` |
| 22 | } |
| 23 | |
| 24 | type tsTarget struct{} |
| 25 | |
| 26 | func (*tsTarget) Version() string { return "" } |
| 27 | func (*tsTarget) Reset() {} |
| 28 | func (*tsTarget) Save(r tsRecord, force bool) (tsRecord, error) { return r, nil } |
| 29 | func (*tsTarget) List() ([]tsRecord, error) { return nil, nil } |
| 30 | func (*tsTarget) Others() map[string][]tsOther { return nil } |
| 31 | |
| 32 | func TestWriteTypeScriptEmitsContractDeclarations(t *testing.T) { |
| 33 | c := Build(mustRegistry(t, &tsTarget{}, nil), []string{"agent:event", "runtime:rebuilt"}) |
| 34 | var b strings.Builder |
| 35 | if err := WriteTypeScript(&b, c); err != nil { |
| 36 | t.Fatal(err) |
| 37 | } |
| 38 | out := b.String() |
| 39 | if !strings.HasPrefix(out, "// Code generated by go run . -emit-contract; DO NOT EDIT.\n") { |
| 40 | t.Fatalf("header = %.80q", out) |
| 41 | } |
| 42 | for _, want := range []string{ |
| 43 | fmt.Sprintf("export const DESKTOP_PROTOCOL_VERSION = %d;", ProtocolVersion), |
| 44 | `export const DESKTOP_CONTRACT_DIGEST = "` + c.Digest() + `";`, |
| 45 | "export const DESKTOP_COMMANDS = [\n \"List\",\n \"Others\",\n \"Reset\",\n \"Save\",\n \"Version\",\n] as const;", |
| 46 | "export const DESKTOP_EVENTS = [\n \"agent:event\",\n \"runtime:rebuilt\",\n] as const;", |
| 47 | "export type DesktopCommandName = (typeof DESKTOP_COMMANDS)[number];", |
| 48 | "export interface tsOther {\n name: string;\n}", |
| 49 | "export interface tsRecord {\n", |
| 50 | " label: string;\n", |
| 51 | " \"content-type\": string;\n", |
| 52 | " next?: tsRecord | null;\n", |
| 53 | " tags?: string[];\n", |
| 54 | " owners: (tsOther | null)[];\n", |
| 55 | " extra: Record<string, unknown>;\n", |
| 56 | " payload: unknown;\n", |
| 57 | " nested: tsRecord_Nested;\n", |
| 58 | "export interface tsRecord_Nested {\n On: boolean;\n}", |
| 59 | "export interface GeneratedDesktopCommands {\n", |
| 60 | " List(): Promise<tsRecord[]>;\n", |
| 61 | " Others(): Promise<Record<string, tsOther[]>>;\n", |
| 62 | " Reset(): Promise<void>;\n", |
| 63 | " Save(arg0: tsRecord, arg1: boolean): Promise<tsRecord>;\n", |
| 64 | " Version(): Promise<string>;\n", |
| 65 | } { |
| 66 | if !strings.Contains(out, want) { |
| 67 | t.Errorf("output lacks %q\n%s", want, out) |
| 68 | } |
| 69 | } |
| 70 | } |
| 71 | |
| 72 | func TestTypeScriptNamesDisambiguateCollisions(t *testing.T) { |
| 73 | names := tsTypeNames(map[string]ObjectType{ |
| 74 | "main.Item": {}, |
| 75 | "taskmonitor.Item": {}, |
| 76 | "main.Solo": {}, |
| 77 | "main.Box[int]": {}, |
| 78 | }) |
| 79 | want := map[string]string{ |
| 80 | "main.Item": "main_Item", |
| 81 | "taskmonitor.Item": "taskmonitor_Item", |
| 82 | "main.Solo": "Solo", |
| 83 | "main.Box[int]": "Box_int_", |
| 84 | } |
| 85 | for key, name := range want { |
| 86 | if names[key] != name { |
| 87 | t.Errorf("%s -> %q, want %q", key, names[key], name) |
| 88 | } |
| 89 | } |
| 90 | } |
| 91 | |
| 92 | func TestTypeScriptNamesDoNotShadowGeneratedHelpers(t *testing.T) { |
| 93 | names := tsTypeNames(map[string]ObjectType{ |
| 94 | "transcript.Record": {}, "main.Promise": {}, "main.GeneratedDesktopCommands": {}, |
| 95 | }) |
| 96 | for key, want := range map[string]string{ |
| 97 | "transcript.Record": "transcript_Record", "main.Promise": "main_Promise", |
| 98 | "main.GeneratedDesktopCommands": "main_GeneratedDesktopCommands", |
| 99 | } { |
| 100 | if names[key] != want { |
| 101 | t.Errorf("%s = %s, want %s", key, names[key], want) |
| 102 | } |
| 103 | } |
| 104 | } |
| 105 |