authorgravatar for 269035359+t3-code[bot]@users.noreply.github.comt3-code[bot] <269035359+t3-code[bot]@users.noreply.github.com> 2026-08-30 05:31:02+00:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-08-30 05:31:02+00:00
log2daff8c25adf701fddd062ae93b94cc57d420ec2
tree19827171d8e6b927957872545afe44880480732c
parent8dcb96314c976899e4df6951fb9af03131c2a46f
signature Signed by PGP key B5690EEEBB952194

test(web): remove tests for unreachable helpers (#8738)

Co-authored-by: t3-code[bot] <269035359+t3-code[bot]@users.noreply.github.com>

13 files changed, 3 insertions(+), 282 deletions(-)

apps/web/src/appearanceFonts.test.ts-15
...@@ -5,9 +5,6 @@ import {...@@ -5,9 +5,6 @@ import {
5 clampCodeFontSize,5 clampCodeFontSize,
6 clampInterfaceFontSize,6 clampInterfaceFontSize,
7 clampPromptFontSize,7 clampPromptFontSize,
8 DEFAULT_CODE_FONT_STACK,
9 DEFAULT_SANS_FONT_STACK,
10 appearanceFontStack,
11 cssFontFamilies,8 cssFontFamilies,
12 resolveDefaultFamilyLabel,9 resolveDefaultFamilyLabel,
13 resolveTerminalFontPreference,10 resolveTerminalFontPreference,
...@@ -58,18 +55,6 @@ describe("resolveDefaultFamilyLabel", () => {...@@ -58,18 +55,6 @@ describe("resolveDefaultFamilyLabel", () => {
58 });55 });
59});56});
6057
61describe("appearanceFontStack", () => {
62 it("prepends the custom family to the default stack", () => {
63 expect(appearanceFontStack("Fira Code", DEFAULT_CODE_FONT_STACK)).toBe(
64 `"Fira Code", ${DEFAULT_CODE_FONT_STACK}`,
65 );
66 });
67
68 it("falls back to the default stack when unset", () => {
69 expect(appearanceFontStack("", DEFAULT_SANS_FONT_STACK)).toBe(DEFAULT_SANS_FONT_STACK);
70 });
71});
72
73describe("resolveTerminalFontPreference", () => {58describe("resolveTerminalFontPreference", () => {
74 it("inherits the code font in simple mode", () => {59 it("inherits the code font in simple mode", () => {
75 expect(60 expect(
apps/web/src/appearanceFonts.ts-6
...@@ -71,12 +71,6 @@ export function cssFontFamilies(input: string): string | null {...@@ -71,12 +71,6 @@ export function cssFontFamilies(input: string): string | null {
71 return families.length > 0 ? families.join(", ") : null;71 return families.length > 0 ? families.join(", ") : null;
72}72}
7373
74/** The full stack a preference resolves to: custom families before the default. */
75export function appearanceFontStack(custom: string, defaultStack: string): string {
76 const families = cssFontFamilies(custom);
77 return families === null ? defaultStack : `${families}, ${defaultStack}`;
78}
79
80export interface AppearanceFontPreferences {74export interface AppearanceFontPreferences {
81 readonly sans: string;75 readonly sans: string;
82 readonly code: string;76 readonly code: string;
apps/web/src/components/Sidebar.logic.test.ts-39
...@@ -20,7 +20,6 @@ import {...@@ -20,7 +20,6 @@ import {
20 isTrailingDoubleClick,20 isTrailingDoubleClick,
21 orderItemsByPreferredIds,21 orderItemsByPreferredIds,
22 resolveProjectStatusIndicator,22 resolveProjectStatusIndicator,
23 resolveSidebarStageBadgeLabel,
24 resolveThreadRowClassName,23 resolveThreadRowClassName,
25 resolveSidebarThreadStatus,24 resolveSidebarThreadStatus,
26 resolveThreadStatusPill,25 resolveThreadStatusPill,
...@@ -236,44 +235,6 @@ describe("buildMultiSelectThreadContextMenuItems", () => {...@@ -236,44 +235,6 @@ describe("buildMultiSelectThreadContextMenuItems", () => {
236 });235 });
237});236});
238237
239describe("resolveSidebarStageBadgeLabel", () => {
240 it("returns Nightly for nightly primary server versions", () => {
241 expect(
242 resolveSidebarStageBadgeLabel({
243 primaryServerVersion: "0.0.28-nightly.20260616.12",
244 fallbackStageLabel: "Alpha",
245 }),
246 ).toBe("Nightly");
247 });
248
249 it("returns the fallback label for stable primary server versions", () => {
250 expect(
251 resolveSidebarStageBadgeLabel({
252 primaryServerVersion: "0.0.27",
253 fallbackStageLabel: "Alpha",
254 }),
255 ).toBe("Alpha");
256 });
257
258 it("returns the fallback label when the primary server version is missing", () => {
259 expect(
260 resolveSidebarStageBadgeLabel({
261 primaryServerVersion: null,
262 fallbackStageLabel: "Dev",
263 }),
264 ).toBe("Dev");
265 });
266
267 it("returns the fallback label for malformed nightly prerelease versions", () => {
268 expect(
269 resolveSidebarStageBadgeLabel({
270 primaryServerVersion: "0.0.28-nightly.20260616",
271 fallbackStageLabel: "Alpha",
272 }),
273 ).toBe("Alpha");
274 });
275});
276
277function makeLatestTurn(overrides?: {238function makeLatestTurn(overrides?: {
278 completedAt?: string | null;239 completedAt?: string | null;
279 startedAt?: string | null;240 startedAt?: string | null;
apps/web/src/components/Sidebar.logic.ts-8
...@@ -13,7 +13,6 @@ import type { SidebarThreadSummary, Thread } from "../types";...@@ -13,7 +13,6 @@ import type { SidebarThreadSummary, Thread } from "../types";
13import type { ThreadRouteTarget } from "../threadRoutes";13import type { ThreadRouteTarget } from "../threadRoutes";
14import { cn } from "../lib/utils";14import { cn } from "../lib/utils";
15import { isLatestTurnSettled } from "../session-logic";15import { isLatestTurnSettled } from "../session-logic";
16import { resolveServerBackedAppStageLabel } from "../branding.logic";
1716
18export const THREAD_SELECTION_SAFE_SELECTOR = "[data-thread-item], [data-thread-selection-safe]";17export const THREAD_SELECTION_SAFE_SELECTOR = "[data-thread-item], [data-thread-selection-safe]";
19export const THREAD_JUMP_HINT_SHOW_DELAY_MS = 200;18export const THREAD_JUMP_HINT_SHOW_DELAY_MS = 200;
...@@ -170,13 +169,6 @@ export interface ThreadJumpHintVisibilityController {...@@ -170,13 +169,6 @@ export interface ThreadJumpHintVisibilityController {
170 dispose: () => void;169 dispose: () => void;
171}170}
172171
173export function resolveSidebarStageBadgeLabel(input: {
174 primaryServerVersion: string | null | undefined;
175 fallbackStageLabel: string;
176}): string {
177 return resolveServerBackedAppStageLabel(input);
178}
179
180export function createThreadJumpHintVisibilityController(input: {172export function createThreadJumpHintVisibilityController(input: {
181 delayMs: number;173 delayMs: number;
182 onVisibilityChange: (visible: boolean) => void;174 onVisibilityChange: (visible: boolean) => void;
apps/web/src/pendingUserInput.test.ts-24
...@@ -4,7 +4,6 @@ import {...@@ -4,7 +4,6 @@ import {
4 buildPendingUserInputAnswers,4 buildPendingUserInputAnswers,
5 countAnsweredPendingUserInputQuestions,5 countAnsweredPendingUserInputQuestions,
6 derivePendingUserInputProgress,6 derivePendingUserInputProgress,
7 findFirstUnansweredPendingUserInputQuestionIndex,
8 resolvePendingUserInputAnswer,7 resolvePendingUserInputAnswer,
9 setPendingUserInputCustomAnswer,8 setPendingUserInputCustomAnswer,
10 togglePendingUserInputOptionSelection,9 togglePendingUserInputOptionSelection,
...@@ -182,29 +181,6 @@ describe("pending user input question progress", () => {...@@ -182,29 +181,6 @@ describe("pending user input question progress", () => {
182 ).toBe(1);181 ).toBe(1);
183 });182 });
184183
185 it("finds the first unanswered question", () => {
186 expect(
187 findFirstUnansweredPendingUserInputQuestionIndex(questions, {
188 scope: {
189 selectedOptionLabels: ["Orchestration-first"],
190 },
191 }),
192 ).toBe(1);
193 });
194
195 it("returns the last question index when all answers are complete", () => {
196 expect(
197 findFirstUnansweredPendingUserInputQuestionIndex(questions, {
198 scope: {
199 selectedOptionLabels: ["Orchestration-first"],
200 },
201 compat: {
202 customAnswer: "Keep it for one release window",
203 },
204 }),
205 ).toBe(1);
206 });
207
208 it("derives the active question and advancement state", () => {184 it("derives the active question and advancement state", () => {
209 expect(185 expect(
210 derivePendingUserInputProgress(186 derivePendingUserInputProgress(
apps/web/src/pendingUserInput.ts-11
...@@ -128,17 +128,6 @@ export function countAnsweredPendingUserInputQuestions(...@@ -128,17 +128,6 @@ export function countAnsweredPendingUserInputQuestions(
128 }, 0);128 }, 0);
129}129}
130130
131export function findFirstUnansweredPendingUserInputQuestionIndex(
132 questions: ReadonlyArray<UserInputQuestion>,
133 draftAnswers: Record<string, PendingUserInputDraftAnswer>,
134): number {
135 const unansweredIndex = questions.findIndex(
136 (question) => !resolvePendingUserInputAnswer(question, draftAnswers[question.id]),
137 );
138
139 return unansweredIndex === -1 ? Math.max(questions.length - 1, 0) : unansweredIndex;
140}
141
142export function derivePendingUserInputProgress(131export function derivePendingUserInputProgress(
143 questions: ReadonlyArray<UserInputQuestion>,132 questions: ReadonlyArray<UserInputQuestion>,
144 draftAnswers: Record<string, PendingUserInputDraftAnswer>,133 draftAnswers: Record<string, PendingUserInputDraftAnswer>,
apps/web/src/providerUpdateDismissal.test.ts deleted-23
...@@ -1,23 +0,0 @@
1import { beforeEach, describe, expect, it } from "vite-plus/test";
2
3import { removeLocalStorageItem } from "./hooks/useLocalStorage";
4import {
5 dismissProviderUpdateNotification,
6 isProviderUpdateNotificationDismissed,
7 PROVIDER_UPDATE_DISMISSALS_STORAGE_KEY,
8} from "./providerUpdateDismissal";
9
10describe("providerUpdateDismissal", () => {
11 beforeEach(() => {
12 removeLocalStorageItem(PROVIDER_UPDATE_DISMISSALS_STORAGE_KEY);
13 });
14
15 it("persists provider update dismissals by notification key", () => {
16 expect(isProviderUpdateNotificationDismissed("opencode:1.14.33")).toBe(false);
17
18 dismissProviderUpdateNotification("opencode:1.14.33");
19
20 expect(isProviderUpdateNotificationDismissed("opencode:1.14.33")).toBe(true);
21 expect(isProviderUpdateNotificationDismissed("opencode:1.14.34")).toBe(false);
22 });
23});
apps/web/src/providerUpdateDismissal.ts+2-55
...@@ -1,67 +1,14 @@...@@ -1,67 +1,14 @@
1import { useCallback, useMemo } from "react";1import { useCallback, useMemo } from "react";
2import * as Schema from "effect/Schema";2import * as Schema from "effect/Schema";
33
4import { getLocalStorageItem, setLocalStorageItem, useLocalStorage } from "./hooks/useLocalStorage";4import { useLocalStorage } from "./hooks/useLocalStorage";
55
6export const PROVIDER_UPDATE_DISMISSALS_STORAGE_KEY = "t3code:provider-update-dismissals:v1";6const PROVIDER_UPDATE_DISMISSALS_STORAGE_KEY = "t3code:provider-update-dismissals:v1";
77
8const ProviderUpdateDismissalsSchema = Schema.Struct({8const ProviderUpdateDismissalsSchema = Schema.Struct({
9 keys: Schema.Array(Schema.String),9 keys: Schema.Array(Schema.String),
10});10});
1111
12type ProviderUpdateDismissals = typeof ProviderUpdateDismissalsSchema.Type;
13
14function readProviderUpdateDismissals(): ProviderUpdateDismissals {
15 try {
16 return (
17 getLocalStorageItem(
18 PROVIDER_UPDATE_DISMISSALS_STORAGE_KEY,
19 ProviderUpdateDismissalsSchema,
20 ) ?? {
21 keys: [],
22 }
23 );
24 } catch (error) {
25 console.error("Could not read provider-update dismissals.", error);
26 return { keys: [] };
27 }
28}
29
30function writeProviderUpdateDismissals(document: ProviderUpdateDismissals): void {
31 try {
32 setLocalStorageItem(
33 PROVIDER_UPDATE_DISMISSALS_STORAGE_KEY,
34 document,
35 ProviderUpdateDismissalsSchema,
36 );
37 } catch (error) {
38 console.error("Could not persist provider-update dismissals.", error);
39 }
40}
41
42export function isProviderUpdateNotificationDismissed(
43 dismissalKey: string | null | undefined,
44): boolean {
45 if (!dismissalKey) {
46 return false;
47 }
48 return readProviderUpdateDismissals().keys.includes(dismissalKey);
49}
50
51export function dismissProviderUpdateNotification(dismissalKey: string | null | undefined): void {
52 const trimmedKey = dismissalKey?.trim();
53 if (!trimmedKey) {
54 return;
55 }
56 const document = readProviderUpdateDismissals();
57 if (document.keys.includes(trimmedKey)) {
58 return;
59 }
60 writeProviderUpdateDismissals({
61 keys: [...document.keys, trimmedKey],
62 });
63}
64
65export function useDismissedProviderUpdateNotificationKeys() {12export function useDismissedProviderUpdateNotificationKeys() {
66 const [dismissals, setDismissals] = useLocalStorage(13 const [dismissals, setDismissals] = useLocalStorage(
67 PROVIDER_UPDATE_DISMISSALS_STORAGE_KEY,14 PROVIDER_UPDATE_DISMISSALS_STORAGE_KEY,
apps/web/src/threadSync.test.ts+1-8
...@@ -1,6 +1,6 @@...@@ -1,6 +1,6 @@
1import { describe, expect, it } from "vite-plus/test";1import { describe, expect, it } from "vite-plus/test";
22
3import { resolveThreadSyncPhase, threadSyncLabel } from "./threadSync";3import { resolveThreadSyncPhase } from "./threadSync";
44
5describe("resolveThreadSyncPhase", () => {5describe("resolveThreadSyncPhase", () => {
6 it("loads when only shell data is available", () => {6 it("loads when only shell data is available", () => {
...@@ -40,10 +40,3 @@ describe("resolveThreadSyncPhase", () => {...@@ -40,10 +40,3 @@ describe("resolveThreadSyncPhase", () => {
40 ).toBeNull();40 ).toBeNull();
41 });41 });
42});42});
43
44describe("threadSyncLabel", () => {
45 it("uses the same loading and syncing language as mobile", () => {
46 expect(threadSyncLabel("loading")).toBe("Loading messages...");
47 expect(threadSyncLabel("syncing")).toBe("Syncing messages...");
48 });
49});
apps/web/src/timestampFormat.test.ts-39
...@@ -6,10 +6,7 @@ import {...@@ -6,10 +6,7 @@ import {
6 formatExpiresInLabel,6 formatExpiresInLabel,
7 formatRelativeTime,7 formatRelativeTime,
8 formatRelativeTimeLabel,8 formatRelativeTimeLabel,
9 formatRelativeTimeUntil,
10 formatRelativeTimeUntilLabel,
11 formatShortTimestamp,9 formatShortTimestamp,
12 formatTimestamp,
13 getRelativeTimeState,10 getRelativeTimeState,
14 getTimestampFormatOptions,11 getTimestampFormatOptions,
15 resolveTimestampLocale,12 resolveTimestampLocale,
...@@ -76,33 +73,6 @@ describe("resolveTimestampLocale", () => {...@@ -76,33 +73,6 @@ describe("resolveTimestampLocale", () => {
76 });73 });
77});74});
7875
79describe("formatRelativeTimeUntilLabel", () => {
80 beforeEach(() => {
81 vi.useFakeTimers();
82 vi.setSystemTime(new Date("2026-04-07T12:00:00.000Z"));
83 });
84
85 afterEach(() => {
86 vi.useRealTimers();
87 });
88
89 it("returns Expired when the instant is in the past", () => {
90 expect(formatRelativeTimeUntilLabel("2026-04-07T11:59:00.000Z")).toBe("Expired");
91 });
92
93 it("formats seconds remaining", () => {
94 expect(formatRelativeTimeUntilLabel("2026-04-07T12:00:45.000Z")).toBe("45s left");
95 });
96
97 it("formats minutes remaining", () => {
98 expect(formatRelativeTimeUntilLabel("2026-04-07T12:15:00.000Z")).toBe("15m left");
99 });
100
101 it("formats hours remaining", () => {
102 expect(formatRelativeTimeUntilLabel("2026-04-07T18:00:00.000Z")).toBe("6h left");
103 });
104});
105
106describe("formatExpiresInLabel", () => {76describe("formatExpiresInLabel", () => {
107 beforeEach(() => {77 beforeEach(() => {
108 vi.useFakeTimers();78 vi.useFakeTimers();
...@@ -196,10 +166,6 @@ describe("formatDayAwareTimestamp", () => {...@@ -196,10 +166,6 @@ describe("formatDayAwareTimestamp", () => {
196});166});
197167
198describe("invalid timestamp inputs", () => {168describe("invalid timestamp inputs", () => {
199 it("returns an empty timestamp instead of throwing", () => {
200 expect(formatTimestamp("not-a-date", "12-hour")).toBe("");
201 });
202
203 it("returns an empty short timestamp instead of throwing", () => {169 it("returns an empty short timestamp instead of throwing", () => {
204 expect(formatShortTimestamp("not-a-date", "12-hour")).toBe("");170 expect(formatShortTimestamp("not-a-date", "12-hour")).toBe("");
205 });171 });
...@@ -218,11 +184,6 @@ describe("invalid timestamp inputs", () => {...@@ -218,11 +184,6 @@ describe("invalid timestamp inputs", () => {
218 expect(formatElapsedDurationLabel("not-a-date")).toBe("");184 expect(formatElapsedDurationLabel("not-a-date")).toBe("");
219 });185 });
220186
221 it("returns an empty relative time until label instead of a NaN label", () => {
222 expect(formatRelativeTimeUntil("not-a-date")).toBeNull();
223 expect(formatRelativeTimeUntilLabel("not-a-date")).toBe("");
224 });
225
226 it("returns an empty expires-in label instead of a NaN label", () => {187 it("returns an empty expires-in label instead of a NaN label", () => {
227 expect(formatExpiresInLabel("not-a-date")).toBe("");188 expect(formatExpiresInLabel("not-a-date")).toBe("");
228 });189 });
apps/web/src/timestampFormat.ts-31
...@@ -78,12 +78,6 @@ export function parseTimestampDate(isoDate: string): Date | null {...@@ -78,12 +78,6 @@ export function parseTimestampDate(isoDate: string): Date | null {
78 return Number.isNaN(date.getTime()) ? null : date;78 return Number.isNaN(date.getTime()) ? null : date;
79}79}
8080
81export function formatTimestamp(isoDate: string, timestampFormat: TimestampFormat): string {
82 const date = parseTimestampDate(isoDate);
83 if (!date) return "";
84 return getTimestampFormatter(timestampFormat, true).format(date);
85}
86
87// Deliberately not the host locale: the tooltip's ordinal suffix and81// Deliberately not the host locale: the tooltip's ordinal suffix and
88// day-before-month order below are English, so a localized month alone would82// day-before-month order below are English, so a localized month alone would
89// read "4th Juni 2026". Localizing the whole label is a separate change.83// read "4th Juni 2026". Localizing the whole label is a separate change.
...@@ -228,31 +222,6 @@ export function formatElapsedDurationLabel(isoDate: string, nowMs: number = Date...@@ -228,31 +222,6 @@ export function formatElapsedDurationLabel(isoDate: string, nowMs: number = Date
228 return `${days}d`;222 return `${days}d`;
229}223}
230224
231/**
232 * Relative time until an ISO instant (e.g. expiry). Mirrors {@link formatRelativeTime} but for future times.
233 */
234export function formatRelativeTimeUntil(isoDate: string): RelativeTimeParts | null {
235 const date = parseTimestampDate(isoDate);
236 if (!date) return null;
237 const diffMs = date.getTime() - Date.now();
238 if (diffMs <= 0) return { value: "Expired", suffix: null };
239 const seconds = Math.floor(diffMs / 1000);
240 if (seconds < 5) return { value: "Soon", suffix: null };
241 if (seconds < 60) return { value: `${seconds}s`, suffix: "left" };
242 const minutes = Math.floor(seconds / 60);
243 if (minutes < 60) return { value: `${minutes}m`, suffix: "left" };
244 const hours = Math.floor(minutes / 60);
245 if (hours < 24) return { value: `${hours}h`, suffix: "left" };
246 const days = Math.floor(hours / 24);
247 return { value: `${days}d`, suffix: "left" };
248}
249
250export function formatRelativeTimeUntilLabel(isoDate: string): string {
251 const relative = formatRelativeTimeUntil(isoDate);
252 if (!relative) return "";
253 return relative.suffix ? `${relative.value} ${relative.suffix}` : relative.value;
254}
255
256/**225/**
257 * Countdown for a future instant (e.g. link expiry): "Expires in 4m 12s", with second precision under one hour.226 * Countdown for a future instant (e.g. link expiry): "Expires in 4m 12s", with second precision under one hour.
258 * Pass `nowMs` when a parent tick drives re-renders so the diff matches that snapshot.227 * Pass `nowMs` when a parent tick drives re-renders so the diff matches that snapshot.
apps/web/src/versionSkew.test.ts-9
...@@ -8,7 +8,6 @@ vi.mock("./branding", () => branding);...@@ -8,7 +8,6 @@ vi.mock("./branding", () => branding);
88
9import { APP_VERSION } from "./branding";9import { APP_VERSION } from "./branding";
10import {10import {
11 appendVersionMismatchHint,
12 buildVersionMismatchDismissalKey,11 buildVersionMismatchDismissalKey,
13 dismissVersionMismatch,12 dismissVersionMismatch,
14 isVersionMismatchDismissed,13 isVersionMismatchDismissed,
...@@ -139,14 +138,6 @@ describe("versionSkew", () => {...@@ -139,14 +138,6 @@ describe("versionSkew", () => {
139 ).toBe(false);138 ).toBe(false);
140 });139 });
141140
142 it("appends a hint to connection errors when the server is behind", () => {
143 const mismatch = resolveVersionMismatch("0.0.33");
144
145 expect(appendVersionMismatchHint("Socket closed.", mismatch)).toBe(
146 `Socket closed. Hint: ${MISMATCH_HINT}`,
147 );
148 });
149
150 it("reads desktop-managed update capabilities from config descriptors", () => {141 it("reads desktop-managed update capabilities from config descriptors", () => {
151 expect(142 expect(
152 resolveServerSelfUpdateCapability({143 resolveServerSelfUpdateCapability({
apps/web/src/versionSkew.ts-14
...@@ -159,17 +159,3 @@ export function dismissVersionMismatch(dismissalKey: string | null | undefined):...@@ -159,17 +159,3 @@ export function dismissVersionMismatch(dismissalKey: string | null | undefined):
159 keys: [...document.keys, dismissalKey],159 keys: [...document.keys, dismissalKey],
160 });160 });
161}161}
162
163export function appendVersionMismatchHint(
164 message: string | null | undefined,
165 mismatch: VersionMismatch | null | undefined,
166): string | null {
167 const normalizedMessage = normalizeVersion(message);
168 if (!normalizedMessage) {
169 return mismatch?.hint ?? null;
170 }
171 if (!mismatch) {
172 return normalizedMessage;
173 }
174 return `${normalizedMessage} Hint: ${mismatch.hint}`;
175}