# Local development

> Build your app in a desktop browser with mockLia(), a pretend Lia that answers every call with a test player. Change its answers, send events, and debug every message. From the Lia developer docs: https://docs.lialabs.org/packages/local-development

Outside the Lia app there's no frame bridge, so every call fails with `not_in_lia`. `mockLia()` puts a pretend Lia in its place: it answers every call the way Lia would, with a test player in test mode, so your whole app runs in a desktop browser or in tests.

## Turn it on

Call it before your first SDK call, and only outside Lia:

```ts
import { init, isLia, mockLia } from '@lia-labs/sdk';

if (!isLia()) mockLia();

const me = await init(); // { player: { name: 'tester' }, mode: 'test', ... }
```

With a framework package it's the same import, from that package. Keep it out of production builds with your bundler's dev flag if you like (`if (import.meta.env.DEV && !isLia())`).

## What it answers

| Call | The mock answers |
| --- | --- |
| `init` | A test player (`tester`), test mode, English, light, a sheet, notifications available |
| `pay` | A paid charge, `ch_mock0001` and up |
| `showAd` and its shortcuts | A watched video (`ad_mock`), or a shown interstitial |
| `confirm`, `showPopup` | Yes, and the first button |
| `requestNotifications`, `inviteFriend` | Allowed, invited |
| `submitScore`, `leaderboard` | Keeps your best score and ranks you first |
| Storage | Kept in memory for the page's life |
| `biometric` | Available Face ID, authenticated |
| Everything else | What Lia answers when it works |

## Change its answers

Pass `init` to change who and how, and `onMethod` to decide any answer yourself. Return a value to answer, throw `{ code }` to fail, or call `next()` for the mock's own answer:

```ts
mockLia({
  init: { language: 'ar', rtl: true, theme: 'dark', startParam: 'room-k3x9' },
  latency: 300, // ms before each answer, to see your loading states
  onMethod: (call, next) => {
    if (call.method === 'pay' && call.params.productId === 'gold-pack') throw { code: 'cancelled' };
    if (call.method === 'showAd') return { watched: false, shown: false, reason: 'no_ad' };
    return next();
  },
});
```

## Send events

The mock can send any of Lia's events, to try how your app reacts:

```ts
const mock = mockLia();
mock.event('theme', { colorScheme: 'dark', theme: { bg_color: '#09090B', text_color: '#F5F5F7' } });
mock.event('soundChanged', { on: false });
mock.event('startParam', { startParam: 'room-a81c' });
mock.event('mainButtonClicked');
mock.event('pause');
```

`mock.calls` lists every call your page made, and `mock.restore()` removes the mock.

## In tests

The same mock makes tests simple (here with Vitest and jsdom):

```ts
import { afterEach, expect, it } from 'vitest';
import { init, mainButton, mockLia, resetForTests, resetStateForTests } from '@lia-labs/sdk';

afterEach(() => {
  resetForTests();
  resetStateForTests();
});

it('shows Buy once the player is in', async () => {
  const mock = mockLia();
  await init();
  mainButton.setParams({ text: 'Buy', isVisible: true });
  await Promise.resolve();
  expect(mock.calls.at(-1)).toMatchObject({ method: 'bottomButton', params: { text: 'Buy', visible: true } });
  mock.restore();
});
```

## See every message

`setDebug(true)` logs every call, answer and event to the console, inside Lia too:

```ts
import { setDebug } from '@lia-labs/sdk';
setDebug(true);
// [lia] call pay { productId: 'extra-lives' }
// [lia] event pause undefined
// [lia] answer pay { id: 7, ok: true, result: { chargeId: 'ch_…', productId: 'extra-lives' } }
```

Pass a second argument to send them to your own logger, which helps inside Lia where there's no web inspector: `setDebug(true, { log: send, warn: send, error: send })`.

## Your server

The mock's session (`lias_mock`) isn't one Lia signed, so `/session/verify` says it isn't valid. Give your server a development path, or try server flows inside Lia as a tester. See [Testing your app](/guides/testing).
