de003e862a
Make the web SPA behave correctly inside the Capacitor native shell, where the bundle loads from a local origin: - New lib/origin.ts gatewayOrigin(): absolute URLs resolve to VITE_GATEWAY_URL on native (the finished-game export/share URL in Game.svelte and the Wallet site-root link), falling back to the page origin on web. transport.ts already resolved via VITE_GATEWAY_URL and is left as-is. - Skip the PWA service worker on the native channel (the assets are already local; a worker would risk serving stale content across store updates). - Hide the money-purchase UI in the MVP: new distribution.purchasesHidden() folds VITE_PAYMENTS_DISABLED and the Google Play flag. The Wallet "buy" tab shows a neutral, pointer-free note (wallet.purchasesSoon) for the RuStore MVP and keeps the RuStore stub Google-Play-only. A ?nopay mock force mirrors ?gp for the e2e. - Native env types in vite-env.d.ts. Client-only, contour-safe: no wire/proto/schema change; web/VK/Telegram unchanged. Tests: origin + purchasesHidden unit tests, a ?nopay wallet e2e. svelte-check clean, vitest green, web + native vite build clean.
152 lines
7.2 KiB
TypeScript
152 lines
7.2 KiB
TypeScript
import { expect, test, type Page } from './fixtures';
|
|
|
|
// The Wallet section against the mock transport (no backend). The mock seeds a web/native (direct)
|
|
// account holding a direct (120) + vk (400) chip segment and a small catalog (two chip-priced values
|
|
// and one rouble-priced chip pack — see lib/mock/client.ts), so the compact balance, the split
|
|
// storefront, the exchange-confirm dialog (with its store-compliance warning) and the Google Play
|
|
// stub are all exercisable without a backend.
|
|
|
|
async function loginLobby(page: Page): Promise<void> {
|
|
await page.goto('/');
|
|
await page.getByRole('button', { name: /guest/i }).click();
|
|
await expect(page.getByText('Your turn')).toBeVisible();
|
|
}
|
|
|
|
async function openSettings(page: Page): Promise<void> {
|
|
await page.getByRole('button', { name: /Settings/ }).click(); // lobby ⚙️ tab
|
|
await expect(page.locator('.pane')).toHaveCount(1); // let the slide settle
|
|
}
|
|
|
|
async function openWallet(page: Page): Promise<void> {
|
|
await openSettings(page);
|
|
await page.getByRole('button', { name: 'Wallet', exact: true }).click();
|
|
await expect(page.getByTestId('wallet')).toBeVisible();
|
|
}
|
|
|
|
test('wallet: balance, active benefits and the split storefront render for a durable account', async ({ page }) => {
|
|
await loginLobby(page);
|
|
await openWallet(page);
|
|
|
|
// The compact balance leads with the context's own (direct/"Web") chips, then the linked vk
|
|
// segment behind its logo.
|
|
const balance = page.getByTestId('balance');
|
|
await expect(balance).toContainText('120');
|
|
await expect(balance).toContainText('400');
|
|
await expect(balance.locator('.plogo')).toHaveCount(1); // the one linked (vk) platform
|
|
|
|
// The active benefits (the seeded 5 hints) sit above the store.
|
|
await expect(page.getByRole('heading', { name: 'Active' })).toBeVisible();
|
|
await expect(page.getByTestId('active')).toContainText('Hints: 5');
|
|
await expect(page.getByRole('heading', { name: 'Store' })).toBeVisible();
|
|
|
|
// Default tab — Buy chips: one rouble-priced pack + the public offer, and no values.
|
|
await expect(page.locator('[data-kind="pack"]')).toHaveCount(1);
|
|
await expect(page.locator('[data-kind="pack"]')).toContainText('₽');
|
|
await expect(page.locator('[data-kind="pack"]').getByTestId('buy-pack')).toBeEnabled();
|
|
await expect(page.getByTestId('offer')).toContainText('Public offer');
|
|
await expect(page.locator('[data-kind="value"]')).toHaveCount(0);
|
|
|
|
// Spend chips tab: the two chip-priced values, exchanged (not bought) with the "Exchange" button.
|
|
await page.getByTestId('tab-spend').click();
|
|
await expect(page.locator('[data-kind="value"]')).toHaveCount(2);
|
|
await expect(page.locator('[data-kind="value"]').first().getByTestId('buy')).toHaveText('Exchange');
|
|
});
|
|
|
|
test('wallet: buying a chip pack opens the provider payment page', async ({ page }) => {
|
|
await loginLobby(page);
|
|
await openWallet(page);
|
|
|
|
// The purchase opens the provider's hosted-payment page in a new tab (window.open); capture it.
|
|
await page.evaluate(() => {
|
|
(window as { __opened?: string }).__opened = '';
|
|
window.open = ((u: string) => {
|
|
(window as { __opened?: string }).__opened = u;
|
|
return null;
|
|
}) as typeof window.open;
|
|
});
|
|
await page.locator('[data-kind="pack"]').getByTestId('buy-pack').click();
|
|
await expect
|
|
.poll(() => page.evaluate(() => (window as { __opened?: string }).__opened))
|
|
.toContain('robokassa');
|
|
});
|
|
|
|
test('wallet: the tab sits between Friends and About', async ({ page }) => {
|
|
await loginLobby(page);
|
|
await openSettings(page);
|
|
const labels = await page.locator('.tab .lbl').allInnerTexts();
|
|
const iFriends = labels.indexOf('Friends');
|
|
const iWallet = labels.indexOf('Wallet');
|
|
const iAbout = labels.indexOf('Info');
|
|
expect(iFriends).toBeGreaterThanOrEqual(0);
|
|
expect(iWallet).toBe(iFriends + 1);
|
|
expect(iAbout).toBe(iWallet + 1);
|
|
});
|
|
|
|
test('wallet: a guest has no wallet (nor friends) tab', async ({ page }) => {
|
|
await page.goto('/?guest');
|
|
await page.getByRole('button', { name: /guest/i }).click();
|
|
await expect(page.getByText('Your turn')).toBeVisible();
|
|
await openSettings(page);
|
|
await expect(page.getByRole('button', { name: 'Wallet', exact: true })).toBeHidden();
|
|
await expect(page.getByRole('button', { name: 'Friends', exact: true })).toBeHidden();
|
|
});
|
|
|
|
test('wallet: the Google Play build hides the money purchases behind the RuStore stub', async ({ page }) => {
|
|
await page.goto('/?gp');
|
|
await page.getByRole('button', { name: /guest/i }).click();
|
|
await expect(page.getByText('Your turn')).toBeVisible();
|
|
await openWallet(page);
|
|
|
|
// Buy chips: the chip pack is gone; the stub points at the RuStore build.
|
|
await expect(page.getByTestId('gp-stub')).toBeVisible();
|
|
await expect(page.locator('[data-kind="pack"]')).toHaveCount(0);
|
|
// Spending earned chips still works — the values live in the Spend tab.
|
|
await page.getByTestId('tab-spend').click();
|
|
await expect(page.locator('[data-kind="value"]').first().getByTestId('buy')).toBeVisible();
|
|
});
|
|
|
|
test('wallet: the native MVP hides the money purchases behind a neutral note (no store pointer)', async ({ page }) => {
|
|
await page.goto('/?nopay');
|
|
await page.getByRole('button', { name: /guest/i }).click();
|
|
await expect(page.getByText('Your turn')).toBeVisible();
|
|
await openWallet(page);
|
|
|
|
// Buy chips: the chip pack is gone; a neutral note shows and — unlike the Google Play build — it
|
|
// carries no RuStore (or any store) pointer.
|
|
await expect(page.getByTestId('purchases-hidden')).toBeVisible();
|
|
await expect(page.getByTestId('gp-stub')).toHaveCount(0);
|
|
await expect(page.locator('[data-kind="pack"]')).toHaveCount(0);
|
|
// Spending earned chips still works — the values live in the Spend tab.
|
|
await page.getByTestId('tab-spend').click();
|
|
await expect(page.locator('[data-kind="value"]').first().getByTestId('buy')).toBeVisible();
|
|
});
|
|
|
|
test('wallet: a web spend that would draw store chips confirms with a warning, then completes', async ({ page }) => {
|
|
await loginLobby(page);
|
|
await openWallet(page);
|
|
await page.getByTestId('tab-spend').click();
|
|
|
|
// The 300-chip value drains direct (120) then reaches into vk (180) → a store segment → the confirm
|
|
// dialog carries the cross-platform warning.
|
|
await page.locator('[data-pid="val-noads-30"]').getByTestId('buy').click();
|
|
await expect(page.getByTestId('warn')).toBeVisible();
|
|
await page.getByTestId('spend-confirm').click();
|
|
// The spend applied: the no-ads benefit now shows an end date in the Active section.
|
|
await expect(page.getByTestId('wallet')).toContainText('No ads until');
|
|
});
|
|
|
|
test('wallet: a spend the direct segment covers alone confirms without a warning', async ({ page }) => {
|
|
await loginLobby(page);
|
|
await openWallet(page);
|
|
await page.getByTestId('tab-spend').click();
|
|
|
|
// The 100-chip value is covered by the direct segment (120) alone → the confirm dialog shows, but
|
|
// with no cross-platform warning.
|
|
await page.locator('[data-pid="val-hints-50"]').getByTestId('buy').click();
|
|
await expect(page.getByTestId('spend-body')).toBeVisible();
|
|
await expect(page.getByTestId('warn')).toBeHidden();
|
|
await page.getByTestId('spend-confirm').click();
|
|
// The hints benefit rose from 5 to 55.
|
|
await expect(page.getByTestId('wallet')).toContainText('Hints: 55');
|
|
});
|