mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
feat(tg): P0 — dev mock bridge + Telegram-native foundations
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.
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user