import { List, Output, SourceDirectory, StatementList, refkey, } from "@alloy-js/core"; import { describe, expect, it } from "vitest"; import * as ts from "../src/components/index.js"; import { Reference } from "../src/components/Reference.js"; import { TypeRefContext } from "../src/context/type-ref-context.js"; import { createPackage } from "../src/create-package.js"; import { tsNameConflictResolver } from "../src/name-conflict-resolver.js"; it("works with default imports", () => { expect( const v = ; , ).toRenderTo({ "test1.ts": ` export default function asdf() {} `, "test2.ts": ` import asdf from "./test1.js"; const v = asdf; `, }); }); it("works with named imports", () => { expect( const v = ; , ).toRenderTo({ "test1.ts": ` export function test() {} `, "test2.ts": ` import { test } from "./test1.js"; const v = test; `, }); }); it("sort named imports", () => { expect( <> <> <> const b = ; <> const a = ; , ).toRenderTo({ "test2.ts": ` import { a, B } from "./test1.js"; const b = B; const a = a; `, "test1.ts": ` export class B {} export function a() {} `, }); }); it("sort statements by import paths", () => { expect( <> const b = ; <> const a = ; , ).toRenderTo({ "test2.ts": ` import A from "./a.js"; import B from "./b.js"; const b = B; const a = A; `, "a.ts": "export default class A {}", "b.ts": "export default class B {}", }); }); it("works with default and named imports", () => { expect( const v1 = ; const v2 = ; , ).toRenderTo({ "test1.ts": ` export default function test1() {} export function test2() {} `, "test2.ts": ` import test1, { test2 } from "./test1.js"; const v1 = test1; const v2 = test2; `, }); }); it("works with default and named imports and name conflicts", () => { expect(

<> const v1 = <> const v1_1 = <> const v2 = <> const v3 = <> const v4 =
, ).toRenderTo({ "test1.ts": ` export default function test1() {} export function test2() {} `, "test2.ts": ` export default function test1() {} export function test2() {} `, "test3.ts": ` import test1_1, { test2 as test2_1 } from "./test1.js"; import test1_2, { test2 as test2_2 } from "./test2.js"; const v1 = test1_1; const v1_1 = test2_1; const v2 = test1_2; const v3 = test1_2; const v4 = test2_2; `, }); }); it("works with importing the same name many times from different files with the default name conflict resolver", () => { const rk1 = refkey(); const rk1i = refkey(); const rk2 = refkey(); const rk2i = refkey(); const rk3 = refkey(); const rk3i = refkey(); expect( "hi" "hi" "hi" , ).toRenderTo({ "test-import.ts": ` import { conflict, type MyInterface } from "./test1.js"; import { conflict as conflict_2, type MyInterface as MyInterface_2, } from "./test2.js"; import { conflict as conflict_3, type MyInterface as MyInterface_3, } from "./test3.js"; const one: MyInterface = conflict; const two: MyInterface_2 = conflict_2; const three: MyInterface_3 = conflict_3; `, "test1.ts": `export const conflict = "hi"export interface MyInterface {}`, "test2.ts": `const conflict = "hi"export interface MyInterface {}`, "test3.ts": `const conflict = "hi"export interface MyInterface {}`, }); }); it("works with default and named imports and name conflicts and references in nested scopes", () => { expect( <> const v1 = <> const v1_1 = <> const v2 = <> const v3 = <> const v4 = , ).toRenderTo({ "test1.ts": ` export default function test1() {} export function test2() {} `, "test2.ts": ` export default function test1() {} export function test2() {} `, "test3.ts": ` import test1_1, { test2 as test2_1 } from "./test1.js"; import test1_2, { test2 as test2_2 } from "./test2.js"; const v1 = test1_1; const v1_1 = test2_1; function foo() { const v2 = test1_2; const v3 = test1_2; const v4 = test2_2; } `, }); }); it("works with imports from different directories", () => { expect( const v = ; const v = ; , ).toRenderTo({ "src/test1.ts": ` import { test2 } from "../test2.js"; export function test() {} const v = test2; `, "test2.ts": ` import { test } from "./src/test1.js"; const v = test; export function test2() {} `, }); }); it("handles conflicts with local declarations", () => { expect( const v = ; const v = ; , // cspell:ignore stest2 ).toRenderTo({ "src/test1.ts": ` export function test() {} const v = ; `, "test2.ts": ` import { test as test_1 } from "./src/test1.js"; const v = test_1; export function test() {} `, }); }); describe("type imports", () => { function mkTestFile(name: string) { const TypeA = refkey("TypeA"); const TypeB = refkey("TypeB"); const ClassA = refkey("ClassA"); return { component: ( ), TypeA, TypeB, ClassA, }; } it("adds type keyword only to type imports", () => { const { component, TypeA, ClassA } = mkTestFile("test1.ts"); expect( {component} <> type A = <> class B extends {"{}"} , ).toRenderTo({ "test1.ts": expect.anything(), "test2.ts": ` import { ClassA, type TypeA } from "./test1.js"; type A = TypeA class B extends ClassA {} `, }); }); it("add type keyword for whole import if all are types", () => { const { component, TypeA, TypeB } = mkTestFile("test1.ts"); expect( {component} <> type A = <> type B = , ).toRenderTo({ "test1.ts": expect.anything(), "test2.ts": ` import type { TypeA, TypeB } from "./test1.js"; type A = TypeA type B = TypeB `, }); }); it("reference same type multiple times", () => { const { component, TypeA } = mkTestFile("test1.ts"); expect( {component} <> type A = <> type B = , ).toRenderTo({ "test1.ts": expect.anything(), "test2.ts": ` import type { TypeA } from "./test1.js"; type A = TypeA type B = TypeA `, }); }); it("same reference used as both type and non type doesn't include type", () => { const { component, ClassA } = mkTestFile("test1.ts"); expect( {component} <> type A = <> class B extends {"{}"} , ).toRenderTo({ "test1.ts": expect.anything(), "test2.ts": ` import { ClassA } from "./test1.js"; type A = ClassA class B extends ClassA {} `, }); }); it("value reference from another file doesn't affect a type only reference", () => { const { component, ClassA } = mkTestFile("test1.ts"); expect( {component} type A = class B extends {"{}"} , ).toRenderTo({ "test1.ts": expect.anything(), "test2.ts": ` import type { ClassA } from "./test1.js"; type A = ClassA `, "test3.ts": ` import { ClassA } from "./test1.js"; class B extends ClassA {} `, }); }); it("infer if a type reference from the typescript context", () => { const { component, TypeA } = mkTestFile("test1.ts"); expect( {component} <> type A = <> type B = , ).toRenderTo({ "test1.ts": expect.anything(), "test2.ts": ` import type { TypeA } from "./test1.js"; type A = TypeA type B = TypeA `, }); }); it("reference from a package", () => { const pkg1 = createPackage({ name: "test", version: "^1.0.0", descriptor: { ".": { named: ["Foo"], }, }, }); expect( type A = , ).toRenderTo({ "test2.ts": ` import type { Foo } from "test"; type A = Foo `, }); }); }); describe("import formatting", () => { it("breaks long imports across lines", () => { const lib = createPackage({ name: "testLib", version: "1.0.0", descriptor: { ".": { named: [ "alphaItem", "bravoItem", "charlieItem", "deltaItem", "echoItem", "foxtrotItem", ], }, }, }); expect( {lib.alphaItem}; {lib.bravoItem}; {lib.charlieItem}; {lib.deltaItem}; {lib.echoItem}; {lib.foxtrotItem}; , ).toRenderTo({ "test.ts": ` import { alphaItem, bravoItem, charlieItem, deltaItem, echoItem, foxtrotItem, } from "testLib"; alphaItem; bravoItem; charlieItem; deltaItem; echoItem; foxtrotItem; `, }); }); it("keeps short imports on one line", () => { const lib = createPackage({ name: "testLib", version: "1.0.0", descriptor: { ".": { named: ["a", "b"] }, }, }); expect( {lib.a}; {lib.b}; , ).toRenderTo({ "test.ts": ` import { a, b } from "testLib"; a; b; `, }); }); it("breaks long default + named imports", () => { const lib = createPackage({ name: "testLib", version: "1.0.0", descriptor: { ".": { default: "Big", named: ["alphaItem", "bravoItem", "charlieItem", "deltaItem"], }, }, }); expect( {lib.default}; {lib.alphaItem}; {lib.bravoItem}; {lib.charlieItem}; {lib.deltaItem}; , ).toRenderTo({ "test.ts": ` import Big, { alphaItem, bravoItem, charlieItem, deltaItem, } from "testLib"; Big; alphaItem; bravoItem; charlieItem; deltaItem; `, }); }); });