File size: 3,797 Bytes
f778c12
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
// Model list table tests cover terminal table rendering for model list output.
import { describe, expect, it, vi } from "vitest";
import { visibleWidth } from "../../../packages/terminal-core/src/ansi.js";
import { printModelTable } from "./list.table.js";
import type { ModelRow } from "./list.types.js";

describe("printModelTable", () => {
  it("prints an empty model list as valid structured JSON", () => {
    const runtime = { log: vi.fn(), error: vi.fn() };

    printModelTable([], runtime as never, { json: true });

    expect(runtime.log).toHaveBeenCalledTimes(1);
    expect(JSON.parse(runtime.log.mock.calls[0]![0] as string)).toEqual({
      count: 0,
      models: [],
    });
  });

  it("keeps an empty plain model list silent", () => {
    const runtime = { log: vi.fn(), error: vi.fn() };

    printModelTable([], runtime as never, { plain: true });

    expect(runtime.log).not.toHaveBeenCalled();
  });

  it("writes populated plain model rows directly to stdout", () => {
    const runtime = {
      log: vi.fn(),
      error: vi.fn(),
      exit: vi.fn(),
      writeStdout: vi.fn(),
      writeJson: vi.fn(),
    };
    const rows: ModelRow[] = [
      {
        key: "anthropic/claude-sonnet-4-6",
        name: "Claude Sonnet",
        input: "text",
        contextWindow: 200_000,
        local: false,
        available: true,
        tags: [],
      },
    ];

    printModelTable(rows, runtime, { plain: true });

    expect(runtime.writeStdout).toHaveBeenCalledExactlyOnceWith("anthropic/claude-sonnet-4-6");
    expect(runtime.log).not.toHaveBeenCalled();
  });

  it("prints context caps with sanitized tags in their original order", () => {
    const runtime = { log: vi.fn(), error: vi.fn() };
    const tags = [
      "\u001b[31mdefault\u001b[0m",
      "fallback#2",
      "img-fallback#1",
      "alias:a\tb",
      "unknown",
      "default",
    ];
    const rows: ModelRow[] = [
      {
        key: "openai/gpt-5.5",
        name: "GPT-5.5",
        input: "text+image",
        contextWindow: 400_000,
        contextTokens: 272_000,
        local: false,
        available: true,
        tags,
      },
    ];

    printModelTable(rows, runtime as never);

    // Decimal windows render in decimal K: 272000 -> "272k", 400000 -> "400k".
    expect(runtime.log.mock.calls).toEqual([
      ["Model                                      Input      Ctx         Local Auth  Tags"],
      [
        "openai/gpt-5.5                             text+image 272k/400k   no    yes   default,fallback#2,img-fallback#1,alias:a\\tb,unknown,default",
      ],
    ]);
    expect(rows[0]?.tags).toEqual([
      "\u001b[31mdefault\u001b[0m",
      "fallback#2",
      "img-fallback#1",
      "alias:a\tb",
      "unknown",
      "default",
    ]);
  });

  it("keeps fixed-width rows aligned when model keys contain wide graphemes", () => {
    const runtime = { log: vi.fn(), error: vi.fn() };
    const wideKey = `${"a".repeat(41)}表`;
    const rows: ModelRow[] = [
      {
        key: wideKey,
        name: wideKey,
        input: "text",
        contextWindow: 128_000,
        contextTokens: undefined,
        local: false,
        available: true,
        tags: [],
      },
    ];

    printModelTable(rows, runtime as never);

    const [header, row] = runtime.log.mock.calls.map(([line]) => line);
    expect(typeof header).toBe("string");
    expect(typeof row).toBe("string");
    const headerInputIndex = (header as string).indexOf("Input");
    const rowInputIndex = (row as string).indexOf("text");
    expect(headerInputIndex).toBeGreaterThan(0);
    expect(rowInputIndex).toBeGreaterThan(0);
    expect(visibleWidth((row as string).slice(0, rowInputIndex))).toBe(
      visibleWidth((header as string).slice(0, headerInputIndex)),
    );
  });
});