blob: d3c2a32284ad71b09e0221348041f0006be5b8b7 [file] [log] [blame]
Sean McCulloughb29f8912025-04-20 15:39:11 -07001import { test, expect } from "@sand4rt/experimental-ct-web";
2import { SketchContainerStatus } from "./sketch-container-status";
Sean McCullough86b56862025-04-18 13:04:03 -07003import { State } from "../types";
4
Sean McCulloughb29f8912025-04-20 15:39:11 -07005// Mock complete state for testing
6const mockCompleteState: State = {
Philip Zeyligerd03318d2025-05-08 13:09:12 -07007 state_version: 2,
Sean McCulloughb29f8912025-04-20 15:39:11 -07008 hostname: "test-host",
9 working_dir: "/test/dir",
10 initial_commit: "abcdef1234567890",
11 message_count: 42,
12 os: "linux",
13 title: "Test Session",
14 total_usage: {
15 input_tokens: 1000,
16 output_tokens: 2000,
17 cache_read_input_tokens: 300,
18 cache_creation_input_tokens: 400,
19 total_cost_usd: 0.25,
Sean McCulloughd9f13372025-04-21 15:08:49 -070020 start_time: "",
21 messages: 0,
22 tool_uses: {},
Sean McCulloughb29f8912025-04-20 15:39:11 -070023 },
Philip Zeyliger99a9a022025-04-27 15:15:25 +000024 outstanding_llm_calls: 0,
25 outstanding_tool_calls: [],
Philip Zeyligerc72fff52025-04-29 20:17:54 +000026 session_id: "test-session-id",
27 ssh_available: false,
Philip Zeyliger2c4db092025-04-28 16:57:50 -070028 in_container: true,
29 first_message_index: 0,
Sean McCulloughb29f8912025-04-20 15:39:11 -070030};
31
32test("render props", async ({ mount }) => {
33 const component = await mount(SketchContainerStatus, {
34 props: {
35 state: mockCompleteState,
36 },
37 });
38 await expect(component.locator("#hostname")).toContainText(
39 mockCompleteState.hostname,
40 );
41 // Check that all expected elements exist
42 await expect(component.locator("#workingDir")).toContainText(
43 mockCompleteState.working_dir,
44 );
45 await expect(component.locator("#initialCommit")).toContainText(
46 mockCompleteState.initial_commit.substring(0, 8),
47 );
48
Autoformatter5c70bfe2025-04-25 21:28:00 +000049 await expect(component.locator("#messageCount")).toContainText(
Sean McCulloughb29f8912025-04-20 15:39:11 -070050 mockCompleteState.message_count + "",
51 );
Josh Bleecher Snyder35889972025-04-24 20:48:16 +000052 const expectedTotalInputTokens =
53 mockCompleteState.total_usage.input_tokens +
54 mockCompleteState.total_usage.cache_read_input_tokens +
55 mockCompleteState.total_usage.cache_creation_input_tokens;
Sean McCulloughb29f8912025-04-20 15:39:11 -070056 await expect(component.locator("#inputTokens")).toContainText(
Josh Bleecher Snydera0801ad2025-04-25 19:34:53 +000057 expectedTotalInputTokens.toLocaleString(),
Sean McCulloughb29f8912025-04-20 15:39:11 -070058 );
59 await expect(component.locator("#outputTokens")).toContainText(
Josh Bleecher Snydera0801ad2025-04-25 19:34:53 +000060 mockCompleteState.total_usage.output_tokens.toLocaleString(),
Sean McCulloughb29f8912025-04-20 15:39:11 -070061 );
Sean McCulloughb29f8912025-04-20 15:39:11 -070062 await expect(component.locator("#totalCost")).toContainText(
63 "$" + mockCompleteState.total_usage.total_cost_usd.toFixed(2),
64 );
65});
66
67test("renders with undefined state", async ({ mount }) => {
68 const component = await mount(SketchContainerStatus, {});
69
70 // Elements should exist but be empty
71 await expect(component.locator("#hostname")).toContainText("");
72 await expect(component.locator("#workingDir")).toContainText("");
73 await expect(component.locator("#initialCommit")).toContainText("");
74 await expect(component.locator("#messageCount")).toContainText("");
Josh Bleecher Snyder35889972025-04-24 20:48:16 +000075 await expect(component.locator("#inputTokens")).toContainText("0");
Sean McCulloughb29f8912025-04-20 15:39:11 -070076 await expect(component.locator("#outputTokens")).toContainText("");
77 await expect(component.locator("#totalCost")).toContainText("$0.00");
78});
79
80test("renders with partial state data", async ({ mount }) => {
81 const partialState: Partial<State> = {
Philip Zeyligerd03318d2025-05-08 13:09:12 -070082 state_version: 2,
Sean McCulloughb29f8912025-04-20 15:39:11 -070083 hostname: "partial-host",
84 message_count: 10,
Sean McCullough86b56862025-04-18 13:04:03 -070085 os: "linux",
Sean McCulloughb29f8912025-04-20 15:39:11 -070086 title: "Partial Test",
Philip Zeyligerc72fff52025-04-29 20:17:54 +000087 session_id: "partial-session",
88 ssh_available: false,
Sean McCullough86b56862025-04-18 13:04:03 -070089 total_usage: {
Sean McCulloughb29f8912025-04-20 15:39:11 -070090 input_tokens: 500,
Sean McCulloughd9f13372025-04-21 15:08:49 -070091 start_time: "",
92 messages: 0,
93 output_tokens: 0,
94 cache_read_input_tokens: 0,
95 cache_creation_input_tokens: 0,
96 total_cost_usd: 0,
97 tool_uses: {},
Sean McCullough71941bd2025-04-18 13:31:48 -070098 },
Sean McCullough86b56862025-04-18 13:04:03 -070099 };
100
Sean McCulloughb29f8912025-04-20 15:39:11 -0700101 const component = await mount(SketchContainerStatus, {
102 props: {
103 state: partialState as State,
104 },
Sean McCullough86b56862025-04-18 13:04:03 -0700105 });
106
Sean McCulloughb29f8912025-04-20 15:39:11 -0700107 // Check that elements with data are properly populated
108 await expect(component.locator("#hostname")).toContainText("partial-host");
109 await expect(component.locator("#messageCount")).toContainText("10");
Josh Bleecher Snyder35889972025-04-24 20:48:16 +0000110
111 const expectedTotalInputTokens =
112 partialState.total_usage.input_tokens +
113 partialState.total_usage.cache_read_input_tokens +
114 partialState.total_usage.cache_creation_input_tokens;
115 await expect(component.locator("#inputTokens")).toContainText(
Josh Bleecher Snydera0801ad2025-04-25 19:34:53 +0000116 expectedTotalInputTokens.toLocaleString(),
Josh Bleecher Snyder35889972025-04-24 20:48:16 +0000117 );
Sean McCullough86b56862025-04-18 13:04:03 -0700118
Sean McCulloughb29f8912025-04-20 15:39:11 -0700119 // Check that elements without data are empty
120 await expect(component.locator("#workingDir")).toContainText("");
121 await expect(component.locator("#initialCommit")).toContainText("");
Josh Bleecher Snyder35889972025-04-24 20:48:16 +0000122 await expect(component.locator("#outputTokens")).toContainText("0");
Sean McCulloughb29f8912025-04-20 15:39:11 -0700123 await expect(component.locator("#totalCost")).toContainText("$0.00");
124});
Sean McCullough86b56862025-04-18 13:04:03 -0700125
Sean McCulloughb29f8912025-04-20 15:39:11 -0700126test("handles cost formatting correctly", async ({ mount }) => {
127 // Test with different cost values
128 const testCases = [
129 { cost: 0, expected: "$0.00" },
130 { cost: 0.1, expected: "$0.10" },
131 { cost: 1.234, expected: "$1.23" },
132 { cost: 10.009, expected: "$10.01" },
133 ];
134
135 for (const testCase of testCases) {
136 const stateWithCost = {
137 ...mockCompleteState,
Sean McCullough86b56862025-04-18 13:04:03 -0700138 total_usage: {
Sean McCulloughb29f8912025-04-20 15:39:11 -0700139 ...mockCompleteState.total_usage,
140 total_cost_usd: testCase.cost,
Sean McCullough71941bd2025-04-18 13:31:48 -0700141 },
Sean McCullough86b56862025-04-18 13:04:03 -0700142 };
143
Sean McCulloughb29f8912025-04-20 15:39:11 -0700144 const component = await mount(SketchContainerStatus, {
145 props: {
146 state: stateWithCost,
147 },
148 });
149 await expect(component.locator("#totalCost")).toContainText(
150 testCase.expected,
151 );
152 await component.unmount();
153 }
154});
Sean McCullough86b56862025-04-18 13:04:03 -0700155
Sean McCulloughb29f8912025-04-20 15:39:11 -0700156test("truncates commit hash to 8 characters", async ({ mount }) => {
157 const stateWithLongCommit = {
158 ...mockCompleteState,
159 initial_commit: "1234567890abcdef1234567890abcdef12345678",
160 };
Sean McCullough71941bd2025-04-18 13:31:48 -0700161
Sean McCulloughb29f8912025-04-20 15:39:11 -0700162 const component = await mount(SketchContainerStatus, {
163 props: {
164 state: stateWithLongCommit,
165 },
Sean McCullough86b56862025-04-18 13:04:03 -0700166 });
167
Sean McCulloughb29f8912025-04-20 15:39:11 -0700168 await expect(component.locator("#initialCommit")).toContainText("12345678");
169});
Sean McCullough86b56862025-04-18 13:04:03 -0700170
Sean McCulloughb29f8912025-04-20 15:39:11 -0700171test("has correct link elements", async ({ mount }) => {
172 const component = await mount(SketchContainerStatus, {
173 props: {
174 state: mockCompleteState,
175 },
Sean McCullough86b56862025-04-18 13:04:03 -0700176 });
177
Sean McCulloughb29f8912025-04-20 15:39:11 -0700178 // Check for logs link
179 const logsLink = component.locator("a").filter({ hasText: "Logs" });
180 await expect(logsLink).toHaveAttribute("href", "logs");
Sean McCullough86b56862025-04-18 13:04:03 -0700181
Sean McCulloughb29f8912025-04-20 15:39:11 -0700182 // Check for download link
183 const downloadLink = component.locator("a").filter({ hasText: "Download" });
184 await expect(downloadLink).toHaveAttribute("href", "download");
Sean McCullough86b56862025-04-18 13:04:03 -0700185});