import { code, List, Output, refkey, render, StatementList, } from "@alloy-js/core"; import { describe, expect, it } from "vitest"; import { InterfaceMember, ObjectExpression } from "../src/components/index.js"; import { MemberExpression } from "../src/components/MemberExpression.jsx"; import { VarDeclaration } from "../src/components/VarDeclaration.jsx"; import type { ParameterDescriptor } from "../src/index.js"; import { ClassDeclaration, ClassField, FunctionDeclaration, InterfaceDeclaration, ObjectProperty, SourceFile, } from "../src/index.js"; import { TestFile } from "./utils.js"; it("renders basic member expression with dot notation", () => { expect( , ).toRenderTo(` obj.property `); }); it("renders basic member expression with numeric id", () => { expect( , ).toRenderTo(` arr[0]["foo-bar"] `); }); it("renders basic member expression with index", () => { expect( , ).toRenderTo(` arr[0]["foo-bar"] `); }); it("renders basic member expression with numeric children", () => { expect( 0 , ).toRenderTo(` arr[0]["foo-bar"] `); }); it("renders basic member expression with string children", () => { expect( "0" , ).toRenderTo(` arr["0"]["foo-bar"] `); }); it("renders basic member expression with an expression index", () => { const xRefkey = refkey(); expect( {xRefkey} + 1 , ).toRenderTo(` const x = 1; arr[x + 1]["foo-bar"]; `); }); it("renders basic member expression with an expression id", () => { expect( "foo" + 1 , ).toRenderTo(` arr["foo" + 1]["foo-bar"]; `); }); it("renders member expression with bracket notation for invalid identifiers", () => { expect( , ).toRenderTo(` obj["property-name"] `); }); it("renders member expressions with quotes", () => { expect( , ).toRenderTo(` obj["property-\\"name\\""] `); }); it("handles nullish chaining", () => { expect( , ).toRenderTo(` obj?.property `); }); it("supports multiple levels of nesting", () => { expect( , ).toRenderTo(` a.b.c.d `); }); it("ignores non-part children", () => { expect( , ).toRenderTo(` obj.property `); }); it("ignores allows parts to define children", () => { expect( well hello , ).toRenderTo(` well.hello `); }); it("flattens nested member expressions", () => { expect( , ).toRenderTo(` outer.inner.prop.last `); }); it("handles a mix of dot and bracket notation", () => { expect( , ).toRenderTo(` obj.normalProp["special-prop"]["123"] `); }); it("handles nullish chaining at multiple levels", () => { expect( , ).toRenderTo(` a.b?.c.d?.e `); }); it("throws an error when providing conflicting part props", () => { expect(() => render( , { insertFinalNewLine: false }, ), ).toThrowError( `Only one of args, id can be used for a MemberExpression part at a time`, ); }); it("takes children for the id part", () => { expect( child1 child2 child1 child2 child3 "foo" + 1 , ).toRenderTo(` child1["child2"] child1["child2"]()["child3"]?.()["foo" + 1]() `); }); describe("with refkeys", () => { it("handles symbols correctly", () => { const rk1 = refkey(); const rk2 = refkey(); expect( , ).toRenderTo(` const test1 = 1; const test1_2 = 2; test1.test1_2; `); }); it("handles nullish symbols correctly", () => { // Note that this is deliberately producing invalid references (it's just easiest to test with VarDeclaration). const rk1 = refkey(); const rk2 = refkey(); expect( , ).toRenderTo(` const test1 = 1; const test1_2 = 2; test1?.test1_2; `); }); it("handles optional parameters correctly", () => { const fooRef = refkey(); const modelRef = refkey(); const parameters: ParameterDescriptor[] = [ { name: "foo", optional: true, refkey: fooRef, type: modelRef }, ]; const messageRef = refkey(); const template = ( } /> <>console.log({messageRef}) ); expect({template}).toRenderTo(` interface Model { bar: string } function fooFunction(foo?: Model) { const message = foo?.bar; console.log(message); } `); }); it("handles nullish parameters correctly", () => { const fooRef = refkey(); const modelRef = refkey(); const parameters: ParameterDescriptor[] = [ { name: "foo", nullish: true, refkey: fooRef, type: <>{modelRef} | null, }, ]; const messageRef = refkey(); const template = ( } /> <>console.log({messageRef}) ); expect({template}).toRenderTo(` interface Model { bar: string } function fooFunction(foo: Model | null) { const message = foo?.bar; console.log(message); } `); }); it("handles nullish class member correctly", () => { const classRefkey = refkey(); const interfaceRefkey = refkey(); const interfaceMemberRefkey = refkey(); const classMemberRefkey = refkey(); const instanceRefkey = refkey(); expect( , ).toRenderTo(` interface Bar { prop1: string } class Foo { test1: Bar } const inst = new Foo(); inst.test1?.prop1 `); }); it("handles late resolved refkeys correctly", () => { const rk1 = refkey(); const rk2 = refkey(); expect( , ).toRenderTo(` test1.test1_2; const test1 = 1; const test1_2 = 2; `); }); it("creates a full reference to the first refkey", () => { const rk1 = refkey(); expect( {rk1} , ).toRenderTo({ "index.ts": ` import { importMe } from "./source.js"; importMe.prop.foo; importMe.prop.foo; `, "source.ts": expect.anything(), }); }); }); describe("with function calls", () => { it("handles simple function calls correctly", () => { expect( , ).toRenderTo(` myFunction(1, 2) `); }); it("handles nullish function calls correctly", () => { expect( , ).toRenderTo(` method1?.(1, 2)?.()?.method2?.().prop `); }); it("handles function calls returning nullish correctly", () => { expect( , ).toRenderTo(` myFunction(1, 2)?.prop `); }); it("handles function calls returning nullish correctly", () => { expect( , ).toRenderTo(` myFunction(1, 2)?.prop `); }); }); describe("formatting", () => { describe("simple chains", () => { it("just dots", () => { expect( , ).toRenderTo( ` four.four .four.four .four.four `, { printWidth: 12 }, ); }); it("bracket breaks", () => { expect( , ).toRenderTo( ` obj[ "property-name" ].prop `, { printWidth: 12 }, ); }); }); describe("call chains", () => { it("handles single calls", () => { expect( ]} /> , ).toRenderTo( ` z.object({ x: 1, }) `, { printWidth: 12 }, ); }); it("handles single calls with multiple parameters", () => { expect( , , ]} /> , ).toRenderTo( ` z.object( { x: 1, }, { y: 2, } ) `, { printWidth: 12 }, ); }); it("handles multiple calls", () => { expect( ]} /> , ).toRenderTo( ` z .object({ x: 1, }) .partial() `, { printWidth: 12 }, ); }); it("renders multiple calls on the same line when there are no breaks and they fit", () => { expect( , ).toRenderTo(` z.object().partial().optional() `); }); it("handles multiple calls with id parts", () => { expect( ]} /> , ).toRenderTo(` z.z1 ?.object({ x: 1, }) .foo.partial() `); }); it("handles the first part being a call", () => { expect( ]} /> , ).toRenderTo(` z() .z1?.object({ x: 1, }) .foo.partial() `); }); }); }); describe("with await", () => { it("renders member expression with await", () => { expect( , ).toRenderTo(` (await foo.bar()).baz `); }); it("renders member expression with await at the end", () => { expect( , ).toRenderTo(` await foo.bar() `); }); it("renders member expression with await in the middle of property access", () => { expect( , ).toRenderTo(` (await foo.bar).baz `); }); it("renders member expression with multiple awaits", () => { expect( , ).toRenderTo(` (await (await foo.bar).baz).qux `); }); it("renders member expression with await and call chain (disables formatting)", () => { expect( , ).toRenderTo(` (await foo.bar().baz()).qux() `); }); });