bd0482c376
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Successful in 49s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 59s
Extend the openLink routing from the dictionary lookup to every external
link shown inside the Mini App, so none triggers the WebView's 'open this
link?' confirmation. A shared onExternalLinkClick handler resolves the anchor
via closest() (so it also works delegated on {@html} content), backed by a
pure routeExternalLinkInTelegram decision: only inside Telegram, only an
external http(s) target=_blank link, excluding same-origin/in-app and t.me
links (t.me keeps openTelegramLink). Applied to the word-check lookup, the
About rules link, the Feedback operator-reply links, and the feature-gated
announcement banner.
Outside Telegram every anchor keeps its native target=_blank.
158 lines
5.5 KiB
TypeScript
158 lines
5.5 KiB
TypeScript
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
import {
|
|
insideTelegram,
|
|
routeExternalLinkInTelegram,
|
|
telegramClosingConfirmation,
|
|
telegramLaunch,
|
|
telegramOpenExternalLink,
|
|
telegramRequestFullscreen,
|
|
} from './telegram';
|
|
|
|
function stubWebApp(initData: string, startParam?: string) {
|
|
vi.stubGlobal('window', {
|
|
Telegram: {
|
|
WebApp: {
|
|
initData,
|
|
initDataUnsafe: startParam ? { start_param: startParam } : {},
|
|
themeParams: { bg_color: '#101418' },
|
|
ready: () => {},
|
|
expand: () => {},
|
|
},
|
|
},
|
|
});
|
|
}
|
|
|
|
describe('telegram launch detection', () => {
|
|
afterEach(() => vi.unstubAllGlobals());
|
|
|
|
it('is not inside Telegram without a window', () => {
|
|
expect(insideTelegram()).toBe(false);
|
|
});
|
|
|
|
it('is inside Telegram only with non-empty initData', () => {
|
|
stubWebApp('');
|
|
expect(insideTelegram()).toBe(false);
|
|
stubWebApp('query_id=abc');
|
|
expect(insideTelegram()).toBe(true);
|
|
});
|
|
|
|
it('telegramLaunch returns initData, start param and theme', () => {
|
|
stubWebApp('query_id=abc', 'g123');
|
|
const launch = telegramLaunch();
|
|
expect(launch.initData).toBe('query_id=abc');
|
|
expect(launch.startParam).toBe('g123');
|
|
expect(launch.theme?.bg_color).toBe('#101418');
|
|
});
|
|
});
|
|
|
|
// stubClient stands up a fake WebApp on the given platform with spies for the mobile-gated
|
|
// chrome toggles, so the platform gate can be asserted without a real Telegram client.
|
|
function stubClient(platform?: string) {
|
|
const enable = vi.fn();
|
|
const disable = vi.fn();
|
|
const requestFullscreen = vi.fn();
|
|
vi.stubGlobal('window', {
|
|
Telegram: {
|
|
WebApp: { platform, enableClosingConfirmation: enable, disableClosingConfirmation: disable, requestFullscreen },
|
|
},
|
|
});
|
|
return { enable, disable, requestFullscreen };
|
|
}
|
|
|
|
const mobilePlatforms = ['ios', 'android', 'android_x'];
|
|
const desktopPlatforms = ['tdesktop', 'macos', 'web', undefined];
|
|
|
|
describe('telegramClosingConfirmation', () => {
|
|
afterEach(() => vi.unstubAllGlobals());
|
|
|
|
it('arms the close guard on mobile clients', () => {
|
|
for (const p of mobilePlatforms) {
|
|
const { enable } = stubClient(p);
|
|
telegramClosingConfirmation(true);
|
|
expect(enable, `platform=${p}`).toHaveBeenCalledOnce();
|
|
}
|
|
});
|
|
|
|
it('skips the close guard on desktop clients (the dialog there is just noise)', () => {
|
|
for (const p of desktopPlatforms) {
|
|
const { enable } = stubClient(p);
|
|
telegramClosingConfirmation(true);
|
|
expect(enable, `platform=${p}`).not.toHaveBeenCalled();
|
|
}
|
|
});
|
|
|
|
it('always lifts the guard on leave, regardless of platform', () => {
|
|
const { disable } = stubClient('tdesktop');
|
|
telegramClosingConfirmation(false);
|
|
expect(disable).toHaveBeenCalledOnce();
|
|
});
|
|
});
|
|
|
|
describe('telegramRequestFullscreen', () => {
|
|
afterEach(() => vi.unstubAllGlobals());
|
|
|
|
it('goes immersive fullscreen on mobile clients', () => {
|
|
for (const p of mobilePlatforms) {
|
|
const { requestFullscreen } = stubClient(p);
|
|
telegramRequestFullscreen();
|
|
expect(requestFullscreen, `platform=${p}`).toHaveBeenCalledOnce();
|
|
}
|
|
});
|
|
|
|
it('leaves desktop clients as a standard window (the bot full-size setting fills it)', () => {
|
|
for (const p of desktopPlatforms) {
|
|
const { requestFullscreen } = stubClient(p);
|
|
telegramRequestFullscreen();
|
|
expect(requestFullscreen, `platform=${p}`).not.toHaveBeenCalled();
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('telegramOpenExternalLink', () => {
|
|
afterEach(() => vi.unstubAllGlobals());
|
|
|
|
it('routes an external URL through the SDK openLink inside Telegram', () => {
|
|
const openLink = vi.fn();
|
|
vi.stubGlobal('window', { Telegram: { WebApp: { openLink } } });
|
|
expect(telegramOpenExternalLink('https://gramota.ru/poisk?query=кот')).toBe(true);
|
|
expect(openLink).toHaveBeenCalledWith('https://gramota.ru/poisk?query=кот');
|
|
});
|
|
|
|
it('returns false without the SDK so the caller falls back to the anchor', () => {
|
|
expect(telegramOpenExternalLink('https://x.io')).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('routeExternalLinkInTelegram', () => {
|
|
afterEach(() => vi.unstubAllGlobals());
|
|
|
|
function stubInside(openLink = vi.fn(), origin = 'https://app.example') {
|
|
vi.stubGlobal('window', { Telegram: { WebApp: { initData: 'query_id=abc', openLink } } });
|
|
vi.stubGlobal('location', { href: origin + '/', origin });
|
|
return openLink;
|
|
}
|
|
|
|
it('routes an external http(s) _blank link through openLink inside Telegram', () => {
|
|
const openLink = stubInside();
|
|
expect(routeExternalLinkInTelegram({ href: 'https://gramota.ru/poisk?query=кот', target: '_blank' })).toBe(true);
|
|
expect(openLink).toHaveBeenCalledWith('https://gramota.ru/poisk?query=кот');
|
|
});
|
|
|
|
it('leaves a t.me link to the native handler (openTelegramLink owns those)', () => {
|
|
const openLink = stubInside();
|
|
expect(routeExternalLinkInTelegram({ href: 'https://t.me/some_bot', target: '_blank' })).toBe(false);
|
|
expect(openLink).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('leaves same-origin and non-_blank links to in-app navigation', () => {
|
|
const openLink = stubInside(vi.fn(), 'https://app.example');
|
|
expect(routeExternalLinkInTelegram({ href: 'https://app.example/lobby', target: '_blank' })).toBe(false);
|
|
expect(routeExternalLinkInTelegram({ href: 'https://gramota.ru', target: '' })).toBe(false);
|
|
expect(openLink).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('does nothing outside Telegram (the anchor opens natively)', () => {
|
|
expect(routeExternalLinkInTelegram({ href: 'https://x.io', target: '_blank' })).toBe(false);
|
|
});
|
|
});
|