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(
,
).toRenderTo({
"test1.ts": `
export default function asdf() {}
`,
"test2.ts": `
import asdf from "./test1.js";
const v = asdf;
`,
});
});
it("works with named imports", () => {
expect(
,
).toRenderTo({
"test1.ts": `
export function test() {}
`,
"test2.ts": `
import { test } from "./test1.js";
const v = test;
`,
});
});
it("sort named imports", () => {
expect(
,
).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(
,
).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(
,
).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(
,
).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(
,
).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(
,
).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(
,
).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(
,
// 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(
,
).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(
,
).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(
,
).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(
,
).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(
,
).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(
,
).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(
,
).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(
,
).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(
,
).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(
,
).toRenderTo({
"test.ts": `
import Big, {
alphaItem,
bravoItem,
charlieItem,
deltaItem,
} from "testLib";
Big;
alphaItem;
bravoItem;
charlieItem;
deltaItem;
`,
});
});
});