@forthtilliath/expo-test-kit 0.3.1 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,11 +1,13 @@
1
1
  # @forthtilliath/expo-test-kit
2
2
 
3
- Test helpers for Expo/Drizzle apps: a real in-memory SQLite database (via libsql) to exercise a Drizzle repository's actual SQL, and a lightweight `expo-file-system` fake — both extracted from patterns that were duplicated across test files rather than shared.
3
+ Test helpers for Expo/Drizzle apps: a real in-memory SQLite database (via libsql) to exercise a Drizzle repository's actual SQL, a lightweight `expo-file-system` fake, and a whole-app renderer for Expo Router — all extracted from patterns that were duplicated across test files rather than shared.
4
4
 
5
5
  ## Install
6
6
 
7
7
  ```bash
8
- npm install --save-dev @forthtilliath/expo-test-kit drizzle-orm @libsql/client
8
+ npm install --save-dev @forthtilliath/expo-test-kit
9
+ # only for the sqlite/ helpers:
10
+ npm install --save-dev drizzle-orm @libsql/client
9
11
  ```
10
12
 
11
13
  Or, from within this monorepo, as a workspace dependency:
@@ -18,7 +20,7 @@ Or, from within this monorepo, as a workspace dependency:
18
20
  }
19
21
  ```
20
22
 
21
- `drizzle-orm` and `@libsql/client` are peer dependencies — install them if not already present. Both are dev-only: `expo-test-kit` doesn't touch your app's production `expo-sqlite` driver, it's a separate in-memory database used only while testing.
23
+ `drizzle-orm` and `@libsql/client` are **optional** peer dependencies: only the `sqlite/` modules need them, so an app that only takes `router/createRenderApp` or the file-system fake doesn't install them. Both are dev-only: `expo-test-kit` doesn't touch your app's production `expo-sqlite` driver, it's a separate in-memory database used only while testing.
22
24
 
23
25
  ## Usage
24
26
 
@@ -28,6 +30,7 @@ has an obvious home instead of piling into one flat `src/`:
28
30
 
29
31
  - `sqlite/` — the SQLite/Drizzle/libsql test database and its mocking helper.
30
32
  - `file-system/` — the `expo-file-system` fake.
33
+ - `router/` — whole-app rendering through Expo Router.
31
34
 
32
35
  ```ts
33
36
  import {
@@ -95,6 +98,35 @@ beforeAll(() => {
95
98
  });
96
99
  ```
97
100
 
101
+ ### `createRenderApp(deps, options?)`
102
+
103
+ Builds a `renderApp(initialUrl)` that renders the whole app — the real screens of `app/`, the real Expo Router navigation — from a URL, and returns `{ getPathname, getSearchParams, navigate }`. It settles the two traps of `renderRouter` under Testing Library v14: fake timers are advanced _before_ awaiting the render (`renderRouter` turns them on, and the first async render never ends otherwise), and a plain object is returned rather than the enriched promise. `navigate` runs inside `act` and waits for the render.
104
+
105
+ Expo Router, its testing library and the test runner are passed in (`deps`), so this package depends on none of them. `options`: `appDir` (`"./app"`), `settleMs` (`100`).
106
+
107
+ ```ts
108
+ // test/renderApp.ts
109
+ import { router } from "expo-router";
110
+ import { act, renderRouter } from "expo-router/testing-library";
111
+ import { createRenderApp } from "@forthtilliath/expo-test-kit/router/createRenderApp";
112
+
113
+ export const renderApp = createRenderApp({
114
+ renderRouter,
115
+ act,
116
+ navigate: (path) => {
117
+ router.navigate(path);
118
+ },
119
+ advanceTimersByTimeAsync: (ms) => jest.advanceTimersByTimeAsync(ms),
120
+ });
121
+
122
+ // in a test
123
+ const app = await renderApp("/games/new");
124
+ await app.navigate("/settings");
125
+ expect(app.getPathname()).toBe("/settings");
126
+ ```
127
+
128
+ For Zustand stores persisted in `AsyncStorage`, see `readPersisted` / `writePersisted` in `@forthtilliath/test-kit`.
129
+
98
130
  ## Scripts
99
131
 
100
132
  ```bash
@@ -0,0 +1,63 @@
1
+ interface RenderedRouter<SearchParams> {
2
+ getPathname: () => string;
3
+ getSearchParams: () => SearchParams;
4
+ }
5
+ export interface RenderAppDeps<SearchParams> {
6
+ /** `renderRouter` from `expo-router/testing-library`. */
7
+ renderRouter: (appDir: string, options: {
8
+ initialUrl: string;
9
+ }) => PromiseLike<RenderedRouter<SearchParams>>;
10
+ /** `act` from `expo-router/testing-library`. */
11
+ act: (callback: () => Promise<void>) => PromiseLike<unknown>;
12
+ /** Navigates like the app does: `(path) => { router.navigate(path); }`. */
13
+ navigate: (path: string) => unknown;
14
+ /** `jest.advanceTimersByTimeAsync` or `vi.advanceTimersByTimeAsync`. */
15
+ advanceTimersByTimeAsync: (ms: number) => PromiseLike<unknown>;
16
+ }
17
+ export interface RenderAppOptions {
18
+ /** The routes folder, relative to the test runner's root. @defaultValue "./app" */
19
+ appDir?: string;
20
+ /** Fake time advanced for the first render to settle. @defaultValue 100 */
21
+ settleMs?: number;
22
+ }
23
+ /**
24
+ * Builds a `renderApp(initialUrl)` that renders the whole app — the real
25
+ * screens of `app/`, the real Expo Router navigation — from a URL.
26
+ *
27
+ * Settles the two traps of `renderRouter` under Testing Library v14:
28
+ * it turns fake timers on, so they must be advanced *before* awaiting the
29
+ * render, or the first async render never ends; and what it returns is an
30
+ * enriched promise, so `renderApp` hands back a plain object instead.
31
+ * `navigate` waits for the render (`testRouter` checks the path right
32
+ * after navigating, without waiting).
33
+ *
34
+ * Expo Router, its testing library and the test runner are passed in, so
35
+ * this package depends on none of them.
36
+ *
37
+ * @example
38
+ * ```ts
39
+ * // test/renderApp.ts
40
+ * import { router } from "expo-router";
41
+ * import { act, renderRouter } from "expo-router/testing-library";
42
+ * import { createRenderApp } from "@forthtilliath/expo-test-kit/router/createRenderApp";
43
+ *
44
+ * export const renderApp = createRenderApp({
45
+ * renderRouter,
46
+ * act,
47
+ * navigate: (path) => { router.navigate(path); },
48
+ * advanceTimersByTimeAsync: (ms) => jest.advanceTimersByTimeAsync(ms),
49
+ * });
50
+ *
51
+ * // in a test
52
+ * const app = await renderApp("/games/new");
53
+ * await app.navigate("/settings");
54
+ * expect(app.getPathname()).toBe("/settings");
55
+ * ```
56
+ */
57
+ export declare function createRenderApp<SearchParams>(deps: RenderAppDeps<SearchParams>, { appDir, settleMs }?: RenderAppOptions): (initialUrl: string) => Promise<{
58
+ getPathname: () => string;
59
+ getSearchParams: () => SearchParams;
60
+ navigate: (path: string) => Promise<void>;
61
+ }>;
62
+ export {};
63
+ //# sourceMappingURL=createRenderApp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createRenderApp.d.ts","sourceRoot":"","sources":["../../src/router/createRenderApp.ts"],"names":[],"mappings":"AAEA,UAAU,cAAc,CAAC,YAAY;IACnC,WAAW,EAAE,MAAM,MAAM,CAAC;IAC1B,eAAe,EAAE,MAAM,YAAY,CAAC;CACrC;AAED,MAAM,WAAW,aAAa,CAAC,YAAY;IACzC,yDAAyD;IACzD,YAAY,EAAE,CACZ,MAAM,EAAE,MAAM,EACd,OAAO,EAAE;QAAE,UAAU,EAAE,MAAM,CAAA;KAAE,KAC5B,WAAW,CAAC,cAAc,CAAC,YAAY,CAAC,CAAC,CAAC;IAC/C,gDAAgD;IAChD,GAAG,EAAE,CAAC,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,KAAK,WAAW,CAAC,OAAO,CAAC,CAAC;IAC7D,2EAA2E;IAC3E,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;IACpC,wEAAwE;IACxE,wBAAwB,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,WAAW,CAAC,OAAO,CAAC,CAAC;CAChE;AAED,MAAM,WAAW,gBAAgB;IAC/B,mFAAmF;IACnF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,wBAAgB,eAAe,CAAC,YAAY,EAC1C,IAAI,EAAE,aAAa,CAAC,YAAY,CAAC,EACjC,EAAE,MAAgB,EAAE,QAAc,EAAE,GAAE,gBAAqB,IAE3B,YAAY,MAAM;;;qBAOvB,MAAM;GASlC"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Builds a `renderApp(initialUrl)` that renders the whole app — the real
3
+ * screens of `app/`, the real Expo Router navigation — from a URL.
4
+ *
5
+ * Settles the two traps of `renderRouter` under Testing Library v14:
6
+ * it turns fake timers on, so they must be advanced *before* awaiting the
7
+ * render, or the first async render never ends; and what it returns is an
8
+ * enriched promise, so `renderApp` hands back a plain object instead.
9
+ * `navigate` waits for the render (`testRouter` checks the path right
10
+ * after navigating, without waiting).
11
+ *
12
+ * Expo Router, its testing library and the test runner are passed in, so
13
+ * this package depends on none of them.
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * // test/renderApp.ts
18
+ * import { router } from "expo-router";
19
+ * import { act, renderRouter } from "expo-router/testing-library";
20
+ * import { createRenderApp } from "@forthtilliath/expo-test-kit/router/createRenderApp";
21
+ *
22
+ * export const renderApp = createRenderApp({
23
+ * renderRouter,
24
+ * act,
25
+ * navigate: (path) => { router.navigate(path); },
26
+ * advanceTimersByTimeAsync: (ms) => jest.advanceTimersByTimeAsync(ms),
27
+ * });
28
+ *
29
+ * // in a test
30
+ * const app = await renderApp("/games/new");
31
+ * await app.navigate("/settings");
32
+ * expect(app.getPathname()).toBe("/settings");
33
+ * ```
34
+ */
35
+ export function createRenderApp(deps, { appDir = "./app", settleMs = 100 } = {}) {
36
+ return async function renderApp(initialUrl) {
37
+ const rendering = deps.renderRouter(appDir, { initialUrl });
38
+ await deps.advanceTimersByTimeAsync(settleMs);
39
+ const app = await rendering;
40
+ return {
41
+ getPathname: () => app.getPathname(),
42
+ getSearchParams: () => app.getSearchParams(),
43
+ navigate: async (path) => {
44
+ // Un callback qui renvoie une promesse : act attend la fin du rendu.
45
+ await deps.act(() => {
46
+ deps.navigate(path);
47
+ return Promise.resolve();
48
+ });
49
+ },
50
+ };
51
+ };
52
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forthtilliath/expo-test-kit",
3
- "version": "0.3.1",
3
+ "version": "0.4.1",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
@@ -23,6 +23,14 @@
23
23
  "@libsql/client": ">=0.14.0",
24
24
  "drizzle-orm": ">=0.44.0"
25
25
  },
26
+ "peerDependenciesMeta": {
27
+ "@libsql/client": {
28
+ "optional": true
29
+ },
30
+ "drizzle-orm": {
31
+ "optional": true
32
+ }
33
+ },
26
34
  "devDependencies": {
27
35
  "@libsql/client": "^0.17.4",
28
36
  "@types/node": "^26.2.0",
@@ -30,7 +38,7 @@
30
38
  "eslint": "10.7.0",
31
39
  "typescript": "^5.9.3",
32
40
  "vitest": "^5.0.2",
33
- "@forthtilliath/eslint-config": "0.8.0",
41
+ "@forthtilliath/eslint-config": "0.9.0",
34
42
  "@forthtilliath/typescript-config": "0.1.1"
35
43
  },
36
44
  "scripts": {