Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
167 changes: 167 additions & 0 deletions apps/desktop/src/lib/trpc/routers/settings/font-settings.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import { describe, expect, it } from "bun:test";
import {
setFontSettingsSchema,
transformFontSettings,
} from "./font-settings.utils";

describe("font settings validation", () => {
describe("font size validation (range 10-24)", () => {
it("accepts font size at minimum (10)", () => {
const result = setFontSettingsSchema.safeParse({
terminalFontSize: 10,
});
expect(result.success).toBe(true);
});

it("accepts font size at maximum (24)", () => {
const result = setFontSettingsSchema.safeParse({
editorFontSize: 24,
});
expect(result.success).toBe(true);
});

it("accepts font size in the middle of range", () => {
const result = setFontSettingsSchema.safeParse({
terminalFontSize: 14,
editorFontSize: 16,
});
expect(result.success).toBe(true);
});

it("rejects font size below minimum (< 10)", () => {
const result = setFontSettingsSchema.safeParse({
terminalFontSize: 9,
});
expect(result.success).toBe(false);
});

it("rejects font size of 0", () => {
const result = setFontSettingsSchema.safeParse({
editorFontSize: 0,
});
expect(result.success).toBe(false);
});

it("rejects font size above maximum (> 24)", () => {
const result = setFontSettingsSchema.safeParse({
terminalFontSize: 25,
});
expect(result.success).toBe(false);
});

it("rejects very large font size", () => {
const result = setFontSettingsSchema.safeParse({
editorFontSize: 100,
});
expect(result.success).toBe(false);
});

it("rejects non-integer font sizes", () => {
const result = setFontSettingsSchema.safeParse({
terminalFontSize: 14.5,
});
expect(result.success).toBe(false);
});

it("accepts null font size (reset)", () => {
const result = setFontSettingsSchema.safeParse({
terminalFontSize: null,
editorFontSize: null,
});
expect(result.success).toBe(true);
});
});

describe("font family trimming", () => {
it("trims whitespace from font family", () => {
const input = setFontSettingsSchema.parse({
terminalFontFamily: " JetBrains Mono ",
});
const result = transformFontSettings(input);
expect(result.terminalFontFamily).toBe("JetBrains Mono");
});

it("trims whitespace from editor font family", () => {
const input = setFontSettingsSchema.parse({
editorFontFamily: " Fira Code ",
});
const result = transformFontSettings(input);
expect(result.editorFontFamily).toBe("Fira Code");
});

it("accepts valid font families without modification", () => {
const input = setFontSettingsSchema.parse({
terminalFontFamily: "JetBrains Mono",
editorFontFamily: "Fira Code",
});
const result = transformFontSettings(input);
expect(result.terminalFontFamily).toBe("JetBrains Mono");
expect(result.editorFontFamily).toBe("Fira Code");
});

it("accepts common monospace fonts", () => {
const fonts = [
"JetBrains Mono",
"Fira Code",
"Source Code Pro",
"Cascadia Code",
"IBM Plex Mono",
"Hack",
"Inconsolata",
];

for (const font of fonts) {
const input = setFontSettingsSchema.parse({
terminalFontFamily: font,
});
const result = transformFontSettings(input);
expect(result.terminalFontFamily).toBe(font);
}
});
});

describe("empty string as null (reset)", () => {
it("treats empty string font family as null", () => {
const input = setFontSettingsSchema.parse({
terminalFontFamily: "",
});
const result = transformFontSettings(input);
expect(result.terminalFontFamily).toBeNull();
});

it("treats whitespace-only font family as null", () => {
const input = setFontSettingsSchema.parse({
editorFontFamily: " ",
});
const result = transformFontSettings(input);
expect(result.editorFontFamily).toBeNull();
});

it("treats null font family as null", () => {
const input = setFontSettingsSchema.parse({
terminalFontFamily: null,
});
const result = transformFontSettings(input);
expect(result.terminalFontFamily).toBeNull();
});
});

describe("partial updates", () => {
it("only includes provided fields in the result", () => {
const input = setFontSettingsSchema.parse({
terminalFontFamily: "Fira Code",
});
const result = transformFontSettings(input);
expect(result.terminalFontFamily).toBe("Fira Code");
expect(result).not.toHaveProperty("editorFontFamily");
expect(result).not.toHaveProperty("terminalFontSize");
expect(result).not.toHaveProperty("editorFontSize");
});

it("accepts empty input (no changes)", () => {
const input = setFontSettingsSchema.parse({});
const result = transformFontSettings(input);
expect(Object.keys(result)).toHaveLength(0);
});
});
});
31 changes: 31 additions & 0 deletions apps/desktop/src/lib/trpc/routers/settings/font-settings.utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { z } from "zod";

export const setFontSettingsSchema = z.object({
terminalFontFamily: z.string().max(500).nullable().optional(),
terminalFontSize: z.number().int().min(10).max(24).nullable().optional(),
editorFontFamily: z.string().max(500).nullable().optional(),
editorFontSize: z.number().int().min(10).max(24).nullable().optional(),
});

export type SetFontSettingsInput = z.infer<typeof setFontSettingsSchema>;

export function transformFontSettings(
input: SetFontSettingsInput,
): Record<string, string | number | null> {
const set: Record<string, string | number | null> = {};

if (input.terminalFontFamily !== undefined) {
set.terminalFontFamily = input.terminalFontFamily?.trim() || null;
}
if (input.terminalFontSize !== undefined) {
set.terminalFontSize = input.terminalFontSize;
}
if (input.editorFontFamily !== undefined) {
set.editorFontFamily = input.editorFontFamily?.trim() || null;
}
if (input.editorFontSize !== undefined) {
set.editorFontSize = input.editorFontSize;
}

return set;
}
35 changes: 35 additions & 0 deletions apps/desktop/src/lib/trpc/routers/settings/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ import { DEFAULT_RINGTONE_ID, RINGTONES } from "shared/ringtones";
import { z } from "zod";
import { publicProcedure, router } from "../..";
import { getGitAuthorName, getGitHubUsername } from "../workspaces/utils/git";
import {
setFontSettingsSchema,
transformFontSettings,
} from "./font-settings.utils";

const VALID_RINGTONE_IDS = RINGTONES.map((r) => r.id);

Expand Down Expand Up @@ -569,6 +573,37 @@ export const createSettingsRouter = () => {
return { success: true };
}),

getFontSettings: publicProcedure.query(() => {
const row = getSettings();
return {
terminalFontFamily: row.terminalFontFamily ?? null,
terminalFontSize: row.terminalFontSize ?? null,
editorFontFamily: row.editorFontFamily ?? null,
editorFontSize: row.editorFontSize ?? null,
};
}),

setFontSettings: publicProcedure
.input(setFontSettingsSchema)
.mutation(({ input }) => {
const set = transformFontSettings(input);

if (Object.keys(set).length === 0) {
return { success: true };
}

localDb
.insert(settings)
.values({ id: 1, ...set })
.onConflictDoUpdate({
target: settings.id,
set,
})
.run();

return { success: true };
}),

// TODO: remove telemetry procedures once telemetry_enabled column is dropped
getTelemetryEnabled: publicProcedure.query(() => {
return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import htmlWorker from "monaco-editor/esm/vs/language/html/html.worker?worker";
import jsonWorker from "monaco-editor/esm/vs/language/json/json.worker?worker";
import tsWorker from "monaco-editor/esm/vs/language/typescript/ts.worker?worker";
import type React from "react";
import { createContext, useContext, useEffect, useState } from "react";
import { createContext, useContext, useEffect, useMemo, useState } from "react";
import { electronTrpc } from "renderer/lib/electron-trpc";
import { useMonacoTheme } from "renderer/stores/theme";

self.MonacoEnvironment = {
Expand Down Expand Up @@ -127,6 +128,31 @@ export const MONACO_EDITOR_OPTIONS = {
},
};

export function useMonacoEditorOptions() {
const { data: fontSettings } = electronTrpc.settings.getFontSettings.useQuery(
undefined,
{
staleTime: 30_000,
},
);

return useMemo(() => {
if (!fontSettings) return MONACO_EDITOR_OPTIONS;
const fontSize =
fontSettings.editorFontSize ?? MONACO_EDITOR_OPTIONS.fontSize;
return {
...MONACO_EDITOR_OPTIONS,
...(fontSettings.editorFontFamily && {
fontFamily: fontSettings.editorFontFamily,
}),
...(fontSettings.editorFontSize != null && {
fontSize,
lineHeight: Math.round(fontSize * 1.5),
}),
};
}, [fontSettings]);
}

export function registerSaveAction(
editor: monaco.editor.IStandaloneCodeEditor,
onSave: () => void,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ export {
monaco,
registerSaveAction,
SUPERSET_THEME,
useMonacoEditorOptions,
useMonacoReady,
} from "./MonacoProvider";
Loading