Files
scrabble-game/ui/src/lib/vk.test.ts
T
Ilia Denisov da17c18895
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 55s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m25s
feat(vk): native share/copy, auto theme, friend-code deep link, home-bar safe area
Group B of the VK integration — the contour-verified follow-up to the launch+auth MVP:

- Share/copy inside the VK iframe go through VK Bridge: the friend-code invite shares via
  VKWebAppShare and copies via VKWebAppCopyText, since navigator.share is absent in the desktop
  iframe and navigator.clipboard is blocked there.
- The invite link is a VK Mini App direct link (vk.com/app<id>#f<code>) on VK instead of the
  Telegram link; the app id comes from vk_app_id in the launch params (no build arg needed). The
  recipient's launch routes the deep link from VK's `hash` launch query parameter.
- The app's "auto" theme follows the VK client's light/dark appearance (VKWebAppUpdateConfig),
  which the VK mobile webview's prefers-color-scheme does not track.
- The safe-area CSS vars default to env(safe-area-inset-*), so the VK mobile layout clears the
  home bar (and Capacitor/PWA too); Telegram still overrides them from its SDK.

vk.ts adds vkAppId/vkStartParam/vkShare/vkCopyText/vkOnScheme. Verified: svelte-check, 347 unit
(+ vkAppId/vkStartParam/vkShareLink), build, bundle-gate. The VK-Bridge behaviours need the live
contour (not reproducible headless).
2026-06-29 21:27:36 +02:00

52 lines
1.9 KiB
TypeScript

import { afterEach, describe, expect, it, vi } from 'vitest';
import { insideVK, onVKPath, vkAppId, vkLaunchParams, vkStartParam } from './vk';
describe('vk launch detection', () => {
afterEach(() => vi.unstubAllGlobals());
it('is not inside VK and not on the VK path without a location (node / SSR)', () => {
expect(onVKPath()).toBe(false);
expect(insideVK()).toBe(false);
expect(vkLaunchParams()).toBe('');
});
it('detects the dedicated /vk/ entry path', () => {
vi.stubGlobal('location', { pathname: '/vk/', search: '' });
expect(onVKPath()).toBe(true);
vi.stubGlobal('location', { pathname: '/app/', search: '' });
expect(onVKPath()).toBe(false);
});
it('returns the signed launch query only when a sign is present', () => {
vi.stubGlobal('location', { pathname: '/vk/', search: '?vk_user_id=1&vk_ts=2&sign=abc' });
expect(vkLaunchParams()).toBe('vk_user_id=1&vk_ts=2&sign=abc');
expect(insideVK()).toBe(true);
});
it('treats a URL carrying no sign as not a VK launch', () => {
vi.stubGlobal('location', { pathname: '/vk/', search: '?utm_source=catalog' });
expect(vkLaunchParams()).toBe('');
expect(insideVK()).toBe(false);
});
});
describe('vk launch params', () => {
afterEach(() => vi.unstubAllGlobals());
it('reads the VK app id from the launch query', () => {
vi.stubGlobal('location', { pathname: '/vk/', search: '?vk_app_id=6736218&sign=x' });
expect(vkAppId()).toBe('6736218');
});
it('reads the direct-link deep-link payload from the hash query param', () => {
vi.stubGlobal('location', { pathname: '/vk/', search: '?vk_user_id=1&sign=x&hash=f123456' });
expect(vkStartParam()).toBe('f123456');
});
it('returns empty app id / start param when absent', () => {
vi.stubGlobal('location', { pathname: '/vk/', search: '?vk_user_id=1&sign=x' });
expect(vkAppId()).toBe('');
expect(vkStartParam()).toBe('');
});
});