mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
Mini App V4 phase 0. The (tg) shell gains the groundwork every later phase builds on: - Dev mock bridge: outside Telegram, a development build falls back to a no-op WebApp object and skips the webapp-auth POST (the regular panel session cookie authorizes API calls), so the cockpit is workable in a plain browser. Production keeps the "Open from Telegram" wall. - Telegram theme adoption: themeParams map onto the shadcn CSS variables scoped to #tg-shell (desktop dashboard untouched), colorScheme drives the dark class, themeChanged re-applies live. Non-hex values are dropped at the trust boundary. - Viewport/swipe correctness: shell height rides Telegram's own --tg-viewport-stable-height (100dvh fallback), vertical swipe-to-close disabled so list scrolling can't dismiss the app. - Native chrome bindings: TgWebAppProvider context plus useMainButton / useBackButton declarative hooks and a null-safe haptics helper — consumers never touch window.Telegram directly.
116 lines
3.8 KiB
TypeScript
116 lines
3.8 KiB
TypeScript
import { describe, it, expect, vi } from "vitest";
|
|
import { applyTelegramTheme, startTelegramThemeSync } from "../theme";
|
|
import type { TelegramWebApp } from "../webapp";
|
|
|
|
function webAppWith(overrides: Partial<TelegramWebApp>): TelegramWebApp {
|
|
return {
|
|
ready: () => undefined,
|
|
expand: () => undefined,
|
|
initData: "",
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe("applyTelegramTheme", () => {
|
|
it("toggles the dark class from colorScheme", () => {
|
|
const el = document.createElement("div");
|
|
applyTelegramTheme(webAppWith({ colorScheme: "dark" }), el);
|
|
expect(el.classList.contains("dark")).toBe(true);
|
|
applyTelegramTheme(webAppWith({ colorScheme: "light" }), el);
|
|
expect(el.classList.contains("dark")).toBe(false);
|
|
});
|
|
|
|
it("maps themeParams onto the panel CSS variables", () => {
|
|
const el = document.createElement("div");
|
|
applyTelegramTheme(
|
|
webAppWith({
|
|
themeParams: {
|
|
bg_color: "#17212b",
|
|
text_color: "#f5f5f5",
|
|
hint_color: "#708499",
|
|
button_color: "#5288c1",
|
|
button_text_color: "#ffffff",
|
|
},
|
|
}),
|
|
el,
|
|
);
|
|
expect(el.style.getPropertyValue("--background")).toBe("#17212b");
|
|
expect(el.style.getPropertyValue("--card")).toBe("#17212b");
|
|
expect(el.style.getPropertyValue("--foreground")).toBe("#f5f5f5");
|
|
expect(el.style.getPropertyValue("--muted-foreground")).toBe("#708499");
|
|
expect(el.style.getPropertyValue("--primary")).toBe("#5288c1");
|
|
expect(el.style.getPropertyValue("--primary-foreground")).toBe("#ffffff");
|
|
});
|
|
|
|
it("prefers the more specific section/secondary keys when present", () => {
|
|
const el = document.createElement("div");
|
|
applyTelegramTheme(
|
|
webAppWith({
|
|
themeParams: {
|
|
bg_color: "#111111",
|
|
secondary_bg_color: "#222222",
|
|
section_bg_color: "#333333",
|
|
},
|
|
}),
|
|
el,
|
|
);
|
|
expect(el.style.getPropertyValue("--background")).toBe("#222222");
|
|
expect(el.style.getPropertyValue("--card")).toBe("#333333");
|
|
});
|
|
|
|
it("drops non-hex values instead of injecting them into style", () => {
|
|
const el = document.createElement("div");
|
|
applyTelegramTheme(
|
|
webAppWith({
|
|
themeParams: {
|
|
bg_color: "url(javascript:alert(1))",
|
|
text_color: "#abc",
|
|
},
|
|
}),
|
|
el,
|
|
);
|
|
expect(el.style.getPropertyValue("--background")).toBe("");
|
|
expect(el.style.getPropertyValue("--foreground")).toBe("");
|
|
});
|
|
});
|
|
|
|
describe("startTelegramThemeSync", () => {
|
|
it("applies immediately, re-applies on themeChanged, and unsubscribes on cleanup", () => {
|
|
const el = document.createElement("div");
|
|
const listeners = new Map<string, () => void>();
|
|
const webApp = webAppWith({
|
|
colorScheme: "light",
|
|
themeParams: { bg_color: "#ffffff" },
|
|
onEvent: vi.fn((event: string, cb: () => void) => {
|
|
listeners.set(event, cb);
|
|
}),
|
|
offEvent: vi.fn((event: string) => {
|
|
listeners.delete(event);
|
|
}),
|
|
});
|
|
|
|
const stop = startTelegramThemeSync(webApp, el);
|
|
expect(el.style.getPropertyValue("--background")).toBe("#ffffff");
|
|
|
|
webApp.colorScheme = "dark";
|
|
webApp.themeParams = { bg_color: "#17212b" };
|
|
listeners.get("themeChanged")?.();
|
|
expect(el.style.getPropertyValue("--background")).toBe("#17212b");
|
|
expect(el.classList.contains("dark")).toBe(true);
|
|
|
|
stop();
|
|
expect(webApp.offEvent).toHaveBeenCalledTimes(1);
|
|
expect(listeners.has("themeChanged")).toBe(false);
|
|
});
|
|
|
|
it("is a one-shot apply with no-op cleanup when the bridge lacks events", () => {
|
|
const el = document.createElement("div");
|
|
const stop = startTelegramThemeSync(
|
|
webAppWith({ themeParams: { bg_color: "#123456" } }),
|
|
el,
|
|
);
|
|
expect(el.style.getPropertyValue("--background")).toBe("#123456");
|
|
expect(stop).not.toThrow();
|
|
});
|
|
});
|