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 {
55 clampCodeFontSize,
66 clampInterfaceFontSize,
77 clampPromptFontSize,
8 DEFAULT_CODE_FONT_STACK,
9 DEFAULT_SANS_FONT_STACK,
10 appearanceFontStack,
118 cssFontFamilies,
129 resolveDefaultFamilyLabel,
1310 resolveTerminalFontPreference,
......@@ -58,18 +55,6 @@ describe("resolveDefaultFamilyLabel", () => {
5855 });
5956});
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
7358describe("resolveTerminalFontPreference", () => {
7459 it("inherits the code font in simple mode", () => {
7560 expect(
apps/web/src/appearanceFonts.ts-6
......@@ -71,12 +71,6 @@ export function cssFontFamilies(input: string): string | null {
7171 return families.length > 0 ? families.join(", ") : null;
7272}
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
8074export interface AppearanceFontPreferences {
8175 readonly sans: string;
8276 readonly code: string;
apps/web/src/components/Sidebar.logic.test.ts-39
......@@ -20,7 +20,6 @@ import {
2020 isTrailingDoubleClick,
2121 orderItemsByPreferredIds,
2222 resolveProjectStatusIndicator,
23 resolveSidebarStageBadgeLabel,
2423 resolveThreadRowClassName,
2524 resolveSidebarThreadStatus,
2625 resolveThreadStatusPill,
......@@ -236,44 +235,6 @@ describe("buildMultiSelectThreadContextMenuItems", () => {
236235 });
237236});
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
277238function makeLatestTurn(overrides?: {
278239 completedAt?: string | null;
279240 startedAt?: string | null;
apps/web/src/components/Sidebar.logic.ts-8
......@@ -13,7 +13,6 @@ import type { SidebarThreadSummary, Thread } from "../types";
1313import type { ThreadRouteTarget } from "../threadRoutes";
1414import { cn } from "../lib/utils";
1515import { isLatestTurnSettled } from "../session-logic";
16import { resolveServerBackedAppStageLabel } from "../branding.logic";
1716
1817export const THREAD_SELECTION_SAFE_SELECTOR = "[data-thread-item], [data-thread-selection-safe]";
1918export const THREAD_JUMP_HINT_SHOW_DELAY_MS = 200;
......@@ -170,13 +169,6 @@ export interface ThreadJumpHintVisibilityController {
170169 dispose: () => void;
171170}
172171
173export function resolveSidebarStageBadgeLabel(input: {
174 primaryServerVersion: string | null | undefined;
175 fallbackStageLabel: string;
176}): string {
177 return resolveServerBackedAppStageLabel(input);
178}
179
180172export function createThreadJumpHintVisibilityController(input: {
181173 delayMs: number;
182174 onVisibilityChange: (visible: boolean) => void;
apps/web/src/pendingUserInput.test.ts-24
......@@ -4,7 +4,6 @@ import {
44 buildPendingUserInputAnswers,
55 countAnsweredPendingUserInputQuestions,
66 derivePendingUserInputProgress,
7 findFirstUnansweredPendingUserInputQuestionIndex,
87 resolvePendingUserInputAnswer,
98 setPendingUserInputCustomAnswer,
109 togglePendingUserInputOptionSelection,
......@@ -182,29 +181,6 @@ describe("pending user input question progress", () => {
182181 ).toBe(1);
183182 });
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
208184 it("derives the active question and advancement state", () => {
209185 expect(
210186 derivePendingUserInputProgress(
apps/web/src/pendingUserInput.ts-11
......@@ -128,17 +128,6 @@ export function countAnsweredPendingUserInputQuestions(
128128 }, 0);
129129}
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
142131export function derivePendingUserInputProgress(
143132 questions: ReadonlyArray<UserInputQuestion>,
144133 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 @@
11import { useCallback, useMemo } from "react";
22import * 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
88const ProviderUpdateDismissalsSchema = Schema.Struct({
99 keys: Schema.Array(Schema.String),
1010});
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
6512export function useDismissedProviderUpdateNotificationKeys() {
6613 const [dismissals, setDismissals] = useLocalStorage(
6714 PROVIDER_UPDATE_DISMISSALS_STORAGE_KEY,
apps/web/src/threadSync.test.ts+1-8
......@@ -1,6 +1,6 @@
11import { describe, expect, it } from "vite-plus/test";
22
3import { resolveThreadSyncPhase, threadSyncLabel } from "./threadSync";
3import { resolveThreadSyncPhase } from "./threadSync";
44
55describe("resolveThreadSyncPhase", () => {
66 it("loads when only shell data is available", () => {
......@@ -40,10 +40,3 @@ describe("resolveThreadSyncPhase", () => {
4040 ).toBeNull();
4141 });
4242});
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 {
66 formatExpiresInLabel,
77 formatRelativeTime,
88 formatRelativeTimeLabel,
9 formatRelativeTimeUntil,
10 formatRelativeTimeUntilLabel,
119 formatShortTimestamp,
12 formatTimestamp,
1310 getRelativeTimeState,
1411 getTimestampFormatOptions,
1512 resolveTimestampLocale,
......@@ -76,33 +73,6 @@ describe("resolveTimestampLocale", () => {
7673 });
7774});
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
10676describe("formatExpiresInLabel", () => {
10777 beforeEach(() => {
10878 vi.useFakeTimers();
......@@ -196,10 +166,6 @@ describe("formatDayAwareTimestamp", () => {
196166});
197167
198168describe("invalid timestamp inputs", () => {
199 it("returns an empty timestamp instead of throwing", () => {
200 expect(formatTimestamp("not-a-date", "12-hour")).toBe("");
201 });
202
203169 it("returns an empty short timestamp instead of throwing", () => {
204170 expect(formatShortTimestamp("not-a-date", "12-hour")).toBe("");
205171 });
......@@ -218,11 +184,6 @@ describe("invalid timestamp inputs", () => {
218184 expect(formatElapsedDurationLabel("not-a-date")).toBe("");
219185 });
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
226187 it("returns an empty expires-in label instead of a NaN label", () => {
227188 expect(formatExpiresInLabel("not-a-date")).toBe("");
228189 });
apps/web/src/timestampFormat.ts-31
......@@ -78,12 +78,6 @@ export function parseTimestampDate(isoDate: string): Date | null {
7878 return Number.isNaN(date.getTime()) ? null : date;
7979}
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
8781// Deliberately not the host locale: the tooltip's ordinal suffix and
8882// day-before-month order below are English, so a localized month alone would
8983// read "4th Juni 2026". Localizing the whole label is a separate change.
......@@ -228,31 +222,6 @@ export function formatElapsedDurationLabel(isoDate: string, nowMs: number = Date
228222 return `${days}d`;
229223}
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
256225/**
257226 * Countdown for a future instant (e.g. link expiry): "Expires in 4m 12s", with second precision under one hour.
258227 * 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);
88
99import { APP_VERSION } from "./branding";
1010import {
11 appendVersionMismatchHint,
1211 buildVersionMismatchDismissalKey,
1312 dismissVersionMismatch,
1413 isVersionMismatchDismissed,
......@@ -139,14 +138,6 @@ describe("versionSkew", () => {
139138 ).toBe(false);
140139 });
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
150141 it("reads desktop-managed update capabilities from config descriptors", () => {
151142 expect(
152143 resolveServerSelfUpdateCapability({
apps/web/src/versionSkew.ts-14
......@@ -159,17 +159,3 @@ export function dismissVersionMismatch(dismissalKey: string | null | undefined):
159159 keys: [...document.keys, dismissalKey],
160160 });
161161}
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}