From db26be7ccb4736c4f43ed75f6464eee7f13a0b4f Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 19 Mar 2026 13:23:29 +0000 Subject: [PATCH 1/3] Add testing guide and skill for TanStack DB Adds a comprehensive testing docs page covering mock collection creation, framework-specific testing patterns (React, Vue, Svelte, Solid, Angular), mutation testing, and virtual prop handling. Also adds a db-core/testing skill for AI agent assistance with testing questions. https://claude.ai/code/session_01EsJdvoefdRVjyFXLazdn73 --- docs/config.json | 4 + docs/guides/testing.md | 587 ++++++++++++++++++++ packages/db/skills/db-core/SKILL.md | 2 + packages/db/skills/db-core/testing/SKILL.md | 361 ++++++++++++ 4 files changed, 954 insertions(+) create mode 100644 docs/guides/testing.md create mode 100644 packages/db/skills/db-core/testing/SKILL.md diff --git a/docs/config.json b/docs/config.json index 09ca7c62b..d5a79e587 100644 --- a/docs/config.json +++ b/docs/config.json @@ -45,6 +45,10 @@ { "label": "Creating Collection Options Creators", "to": "guides/collection-options-creator" + }, + { + "label": "Testing", + "to": "guides/testing" } ] }, diff --git a/docs/guides/testing.md b/docs/guides/testing.md new file mode 100644 index 000000000..ea151008a --- /dev/null +++ b/docs/guides/testing.md @@ -0,0 +1,587 @@ +--- +title: Testing +id: testing +--- + +# Testing + +TanStack DB is designed to be easy to test. Collections can be created with mock sync configurations, and every framework adapter works with standard testing tools for that ecosystem. + +This guide covers how to set up your test environment, create mock collections, and test components that use TanStack DB across all supported frameworks. + +## Test Setup + +TanStack DB uses [Vitest](https://vitest.dev/) as its testing framework. Install the necessary dependencies for your framework: + +```bash +# Core +npm install -D vitest @testing-library/jest-dom + +# React +npm install -D @testing-library/react + +# Vue +# (no additional testing library needed — use Vue's built-in nextTick) + +# Solid +npm install -D @solidjs/testing-library + +# Svelte +# (no additional testing library needed — use Svelte's built-in flushSync) + +# Angular +# (uses Angular's built-in TestBed) +``` + +### Vitest Configuration + +A basic Vitest configuration for testing TanStack DB: + +```ts +// vitest.config.ts +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + globals: true, + environment: 'jsdom', // needed for DOM-based tests + setupFiles: ['./tests/test-setup.ts'], + }, +}) +``` + +### Test Setup File + +For React projects, create a setup file that configures the React ACT environment and automatic cleanup: + +```ts +// tests/test-setup.ts +import '@testing-library/jest-dom/vitest' +import { cleanup } from '@testing-library/react' +import { afterEach } from 'vitest' + +declare global { + var IS_REACT_ACT_ENVIRONMENT: boolean +} + +global.IS_REACT_ACT_ENVIRONMENT = true +afterEach(() => cleanup()) +``` + +For non-React projects, a minimal setup is sufficient: + +```ts +// tests/test-setup.ts +import '@testing-library/jest-dom/vitest' +``` + +## Creating Mock Collections + +The key to testing with TanStack DB is creating collections with controlled sync behavior. You don't need a real backend — create collections with inline sync functions that provide initial data immediately. + +### Basic Pattern + +Create a collection that synchronously loads initial data: + +```ts +import { createCollection } from '@tanstack/db' + +function createTestCollection(config: { + id: string + initialData: Array + getKey: (item: T) => string | number +}) { + let begin: () => void + let write: (op: { type: string; value: T }) => void + let commit: () => void + + return createCollection({ + id: config.id, + getKey: config.getKey, + sync: { + sync: (params) => { + begin = params.begin + write = params.write + commit = params.commit + + // Load initial data immediately + begin() + config.initialData.forEach((item) => { + write({ type: 'insert', value: item }) + }) + commit() + params.markReady() + }, + }, + startSync: true, + }) +} +``` + +### Using the Built-in Test Utility + +TanStack DB's own test suite uses a `mockSyncCollectionOptions` helper that also exposes `begin`, `write`, and `commit` for simulating live sync updates after initial load. You can adopt this pattern in your own tests: + +```ts +import { createCollection } from '@tanstack/db' + +type Person = { + id: string + name: string + age: number +} + +const collection = createCollection({ + id: 'test-persons', + getKey: (person) => person.id, + sync: { + sync: ({ begin, write, commit, markReady }) => { + begin() + write({ type: 'insert', value: { id: '1', name: 'Alice', age: 30 } }) + write({ type: 'insert', value: { id: '2', name: 'Bob', age: 25 } }) + commit() + markReady() + }, + }, + startSync: true, +}) +``` + +### Simulating Live Updates + +To test how your components react to data changes, keep references to the sync functions and call them later: + +```ts +let begin: () => void +let write: (op: { type: string; value: any }) => void +let commit: () => void + +const collection = createCollection({ + id: 'test-persons', + getKey: (person) => person.id, + sync: { + sync: (params) => { + begin = params.begin + write = params.write + commit = params.commit + + // Load initial data + begin() + write({ type: 'insert', value: { id: '1', name: 'Alice', age: 30 } }) + commit() + params.markReady() + }, + }, + startSync: true, +}) + +// Later in your test, simulate a new record arriving from the server: +begin() +write({ type: 'insert', value: { id: '3', name: 'Charlie', age: 28 } }) +commit() +``` + +## Testing by Framework + +### React + +Use `renderHook` and `waitFor` from `@testing-library/react`: + +```tsx +import { describe, expect, it } from 'vitest' +import { renderHook, waitFor } from '@testing-library/react' +import { createCollection, gt } from '@tanstack/db' +import { useLiveQuery } from '@tanstack/react-db' + +type Person = { id: string; name: string; age: number } + +describe('useLiveQuery', () => { + it('filters data reactively', async () => { + const collection = createCollection({ + id: 'persons', + getKey: (p) => p.id, + sync: { + sync: ({ begin, write, commit, markReady }) => { + begin() + write({ type: 'insert', value: { id: '1', name: 'Alice', age: 30 } }) + write({ type: 'insert', value: { id: '2', name: 'Bob', age: 25 } }) + write({ type: 'insert', value: { id: '3', name: 'Charlie', age: 35 } }) + commit() + markReady() + }, + }, + startSync: true, + }) + + const { result } = renderHook(() => + useLiveQuery((q) => + q + .from({ persons: collection }) + .where(({ persons }) => gt(persons.age, 28)) + .select(({ persons }) => ({ + id: persons.id, + name: persons.name, + })), + ), + ) + + await waitFor(() => { + expect(result.current.data).toHaveLength(2) + }) + + expect(result.current.data).toEqual( + expect.arrayContaining([ + expect.objectContaining({ name: 'Alice' }), + expect.objectContaining({ name: 'Charlie' }), + ]), + ) + }) +}) +``` + +### Vue + +Use Vue's `nextTick` for reactivity updates: + +```ts +import { describe, expect, it } from 'vitest' +import { nextTick } from 'vue' +import { createCollection, gt } from '@tanstack/db' +import { useLiveQuery } from '@tanstack/vue-db' + +describe('useLiveQuery', () => { + it('returns reactive data', async () => { + const collection = createCollection({ + id: 'persons', + getKey: (p) => p.id, + sync: { + sync: ({ begin, write, commit, markReady }) => { + begin() + write({ type: 'insert', value: { id: '1', name: 'Alice', age: 30 } }) + write({ type: 'insert', value: { id: '2', name: 'Bob', age: 25 } }) + commit() + markReady() + }, + }, + startSync: true, + }) + + const { data } = useLiveQuery((q) => + q + .from({ persons: collection }) + .where(({ persons }) => gt(persons.age, 28)) + .select(({ persons }) => ({ + id: persons.id, + name: persons.name, + })), + ) + + await nextTick() + // Additional delay for collection updates + await new Promise((resolve) => setTimeout(resolve, 50)) + + expect(data.value).toHaveLength(1) + expect(data.value[0]).toMatchObject({ name: 'Alice' }) + }) +}) +``` + +### Svelte + +Use Svelte 5's `$effect.root` and `flushSync`: + +```ts +import { describe, expect, it, afterEach } from 'vitest' +import { flushSync } from 'svelte' +import { createCollection, gt } from '@tanstack/db' +import { useLiveQuery } from '@tanstack/svelte-db' + +describe('useLiveQuery', () => { + let cleanup: (() => void) | null = null + afterEach(() => cleanup?.()) + + it('works with Svelte runes', () => { + const collection = createCollection({ + id: 'persons', + getKey: (p) => p.id, + sync: { + sync: ({ begin, write, commit, markReady }) => { + begin() + write({ type: 'insert', value: { id: '1', name: 'Alice', age: 30 } }) + write({ type: 'insert', value: { id: '2', name: 'Bob', age: 25 } }) + commit() + markReady() + }, + }, + startSync: true, + }) + + cleanup = $effect.root(() => { + const query = useLiveQuery((q) => + q + .from({ persons: collection }) + .where(({ persons }) => gt(persons.age, 28)), + ) + + flushSync() + + expect(query.data).toHaveLength(1) + expect(query.data[0]).toMatchObject({ name: 'Alice' }) + }) + }) +}) +``` + +### Solid + +Use `renderHook` from `@solidjs/testing-library`: + +```tsx +import { describe, expect, it } from 'vitest' +import { renderHook, waitFor } from '@solidjs/testing-library' +import { createCollection, gt } from '@tanstack/db' +import { useLiveQuery } from '@tanstack/solid-db' + +describe('useLiveQuery', () => { + it('returns reactive accessors', async () => { + const collection = createCollection({ + id: 'persons', + getKey: (p) => p.id, + sync: { + sync: ({ begin, write, commit, markReady }) => { + begin() + write({ type: 'insert', value: { id: '1', name: 'Alice', age: 30 } }) + write({ type: 'insert', value: { id: '2', name: 'Bob', age: 25 } }) + commit() + markReady() + }, + }, + startSync: true, + }) + + const rendered = renderHook(() => + useLiveQuery((q) => + q + .from({ persons: collection }) + .where(({ persons }) => gt(persons.age, 28)), + ), + ) + + await waitFor(() => { + expect(rendered.result.state.size).toBe(1) + }) + + expect(rendered.result()).toHaveLength(1) + }) +}) +``` + +### Angular + +Use Angular's `TestBed.runInInjectionContext`: + +```ts +import { TestBed } from '@angular/core/testing' +import { describe, expect, it } from 'vitest' +import { createCollection, gt } from '@tanstack/db' +import { injectLiveQuery } from '@tanstack/angular-db' + +describe('injectLiveQuery', () => { + it('returns signals', async () => { + const collection = createCollection({ + id: 'persons', + getKey: (p) => p.id, + sync: { + sync: ({ begin, write, commit, markReady }) => { + begin() + write({ type: 'insert', value: { id: '1', name: 'Alice', age: 30 } }) + write({ type: 'insert', value: { id: '2', name: 'Bob', age: 25 } }) + commit() + markReady() + }, + }, + startSync: true, + }) + + TestBed.runInInjectionContext(() => { + const query = injectLiveQuery((q) => + q + .from({ persons: collection }) + .where(({ persons }) => gt(persons.age, 28)), + ) + + // Wait for Angular effects and collection updates + await new Promise((resolve) => setTimeout(resolve, 50)) + + expect(query.data()).toHaveLength(1) + }) + }) +}) +``` + +## Testing Mutations + +Test optimistic mutations by verifying the collection state changes immediately: + +```ts +import { describe, expect, it } from 'vitest' +import { createCollection } from '@tanstack/db' + +describe('mutations', () => { + it('applies optimistic inserts', async () => { + const collection = createCollection({ + id: 'persons', + getKey: (p) => p.id, + sync: { + sync: ({ begin, commit, markReady }) => { + begin() + commit() + markReady() + }, + }, + startSync: true, + onInsert: async () => { + // Mock handler — in tests, resolve immediately + }, + }) + + await collection.stateWhenReady() + + collection.insert({ id: '1', name: 'Alice', age: 30 }) + + expect(collection.has('1')).toBe(true) + expect(collection.get('1')).toMatchObject({ name: 'Alice' }) + }) + + it('applies optimistic updates', async () => { + const collection = createCollection({ + id: 'persons', + getKey: (p) => p.id, + sync: { + sync: ({ begin, write, commit, markReady }) => { + begin() + write({ type: 'insert', value: { id: '1', name: 'Alice', age: 30 } }) + commit() + markReady() + }, + }, + startSync: true, + onUpdate: async () => {}, + }) + + await collection.stateWhenReady() + + collection.update('1', (draft) => { + draft.name = 'Alice Updated' + }) + + expect(collection.get('1')).toMatchObject({ name: 'Alice Updated' }) + }) + + it('applies optimistic deletes', async () => { + const collection = createCollection({ + id: 'persons', + getKey: (p) => p.id, + sync: { + sync: ({ begin, write, commit, markReady }) => { + begin() + write({ type: 'insert', value: { id: '1', name: 'Alice', age: 30 } }) + commit() + markReady() + }, + }, + startSync: true, + onDelete: async () => {}, + }) + + await collection.stateWhenReady() + + collection.delete('1') + + expect(collection.has('1')).toBe(false) + }) +}) +``` + +## Testing with createOptimisticAction + +```ts +import { createCollection, createOptimisticAction } from '@tanstack/db' + +const collection = createCollection<{ id: string; liked: boolean }>({ + id: 'posts', + getKey: (p) => p.id, + sync: { + sync: ({ begin, write, commit, markReady }) => { + begin() + write({ type: 'insert', value: { id: '1', liked: false } }) + commit() + markReady() + }, + }, + startSync: true, +}) + +const likePost = createOptimisticAction({ + onMutate: (postId) => { + collection.update(postId, (draft) => { + draft.liked = true + }) + }, + mutationFn: async () => { + // Mock API call + }, +}) + +// In your test: +likePost('1') +expect(collection.get('1')).toMatchObject({ liked: true }) +``` + +## Stripping Virtual Props + +TanStack DB adds virtual properties (`$synced`, `$origin`, `$key`, `$collectionId`) to collection items. When asserting on specific object shapes, you may want to strip these: + +```ts +function stripVirtualProps>(value: T) { + const { + $synced: _synced, + $origin: _origin, + $key: _key, + $collectionId: _collectionId, + ...rest + } = value as Record + return rest as T +} + +// Usage in tests +const item = collection.get('1')! +expect(stripVirtualProps(item)).toEqual({ + id: '1', + name: 'Alice', + age: 30, +}) +``` + +Alternatively, use `toMatchObject` which ignores extra properties: + +```ts +expect(collection.get('1')).toMatchObject({ + id: '1', + name: 'Alice', + age: 30, +}) +``` + +## Tips + +- **Use `stateWhenReady()`** to wait for a collection to finish its initial sync before asserting: `await collection.stateWhenReady()` +- **Use `toMatchObject`** instead of `toEqual` to avoid needing to match virtual properties +- **Each test should create its own collection** with a unique `id` to avoid state leakage between tests +- **For async tests**, each framework has its own reactivity flush mechanism: + - React: `waitFor` from `@testing-library/react` + - Vue: `nextTick()` + small delay + - Svelte: `flushSync()` from `svelte` + - Solid: `waitFor` from `@solidjs/testing-library` + - Angular: `setTimeout` + `TestBed.runInInjectionContext` +- **Collection-level tests don't need a framework** — you can test `createCollection`, mutations, and live queries directly without any UI framework diff --git a/packages/db/skills/db-core/SKILL.md b/packages/db/skills/db-core/SKILL.md index 5cdc46502..a9505dfda 100644 --- a/packages/db/skills/db-core/SKILL.md +++ b/packages/db/skills/db-core/SKILL.md @@ -34,6 +34,7 @@ hooks. In framework projects, import from the framework package directly. | Insert, update, delete with optimistic UI | db-core/mutations-optimistic/SKILL.md | | Build a custom sync adapter | db-core/custom-adapter/SKILL.md | | Preload collections in route loaders | meta-framework/SKILL.md | +| Test collections and components | db-core/testing/SKILL.md | | Add offline transaction queueing | offline/SKILL.md (in @tanstack/offline-transactions) | For framework-specific hooks: @@ -54,6 +55,7 @@ For framework-specific hooks: - Using React hooks? → react-db - Preloading in route loaders (Start, Next, Remix)? → meta-framework - Building an adapter for a new backend? → db-core/custom-adapter +- Testing collections or components? → db-core/testing - Need offline transaction persistence? → offline ## Version diff --git a/packages/db/skills/db-core/testing/SKILL.md b/packages/db/skills/db-core/testing/SKILL.md new file mode 100644 index 000000000..15b9c7636 --- /dev/null +++ b/packages/db/skills/db-core/testing/SKILL.md @@ -0,0 +1,361 @@ +--- +name: db-core/testing +description: > + Testing TanStack DB collections and components. Creating mock collections + with controlled sync behavior. Framework-specific testing patterns for + React (renderHook, waitFor), Vue (nextTick), Svelte ($effect.root, flushSync), + Solid (renderHook), and Angular (TestBed). Testing mutations (insert, update, + delete), createOptimisticAction, and live queries. Stripping virtual props. + Vitest setup and configuration. +type: sub-skill +library: db +library_version: '0.5.30' +sources: + - 'TanStack/db:docs/guides/testing.md' + - 'TanStack/db:packages/db/tests/utils.ts' + - 'TanStack/db:packages/react-db/tests/useLiveQuery.test.tsx' + - 'TanStack/db:packages/vue-db/tests/useLiveQuery.test.ts' + - 'TanStack/db:packages/svelte-db/tests/useLiveQuery.svelte.test.ts' + - 'TanStack/db:packages/solid-db/tests/useLiveQuery.test.tsx' + - 'TanStack/db:packages/angular-db/tests/inject-live-query.test.ts' +--- + +This skill builds on db-core. Read it first for the overall mental model. + +# TanStack DB — Testing + +## Quick Decision Tree + +- Testing collection logic without a framework? → [Core Collection Tests](#core-collection-tests) +- Testing a React component with useLiveQuery? → [React](#react) +- Testing a Vue component with useLiveQuery? → [Vue](#vue) +- Testing a Svelte component with useLiveQuery? → [Svelte](#svelte) +- Testing a Solid component with useLiveQuery? → [Solid](#solid) +- Testing an Angular component with injectLiveQuery? → [Angular](#angular) +- Testing mutations or optimistic actions? → [Testing Mutations](#testing-mutations) + +## Setup + +TanStack DB uses **Vitest** with **jsdom** for all tests. + +```ts +// vitest.config.ts +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + globals: true, + environment: 'jsdom', + setupFiles: ['./tests/test-setup.ts'], + }, +}) +``` + +React setup file (other frameworks need only `import '@testing-library/jest-dom/vitest'`): + +```ts +// tests/test-setup.ts +import '@testing-library/jest-dom/vitest' +import { cleanup } from '@testing-library/react' +import { afterEach } from 'vitest' + +declare global { + var IS_REACT_ACT_ENVIRONMENT: boolean +} +global.IS_REACT_ACT_ENVIRONMENT = true +afterEach(() => cleanup()) +``` + +## Creating Mock Collections + +The core pattern: create a collection with an inline `sync` function that loads data synchronously. + +```ts +import { createCollection } from '@tanstack/db' + +type Person = { id: string; name: string; age: number } + +const collection = createCollection({ + id: 'test-persons', + getKey: (p) => p.id, + sync: { + sync: ({ begin, write, commit, markReady }) => { + begin() + write({ type: 'insert', value: { id: '1', name: 'Alice', age: 30 } }) + write({ type: 'insert', value: { id: '2', name: 'Bob', age: 25 } }) + commit() + markReady() + }, + }, + startSync: true, +}) +``` + +**Key points:** +- Call `begin()` before writing, `commit()` after, and `markReady()` once initial load is done. +- Each test should use a unique collection `id` to avoid state leakage. +- Keep references to `begin`/`write`/`commit` to simulate live updates later in the test. + +## Core Collection Tests + +You can test collection logic without any UI framework: + +```ts +const collection = createCollection({ + id: 'persons', + getKey: (p) => p.id, + sync: { + sync: ({ begin, write, commit, markReady }) => { + begin() + write({ type: 'insert', value: { id: '1', name: 'Alice', age: 30 } }) + commit() + markReady() + }, + }, + startSync: true, + onUpdate: async () => {}, +}) + +await collection.stateWhenReady() + +// Test mutation +collection.update('1', (draft) => { draft.name = 'Alice Updated' }) +expect(collection.get('1')).toMatchObject({ name: 'Alice Updated' }) + +// Test size +expect(collection.size).toBe(1) +``` + +## React + +Use `renderHook` and `waitFor` from `@testing-library/react`. + +```tsx +import { renderHook, waitFor } from '@testing-library/react' +import { createCollection, gt } from '@tanstack/db' +import { useLiveQuery } from '@tanstack/react-db' + +const { result } = renderHook(() => + useLiveQuery((q) => + q + .from({ persons: collection }) + .where(({ persons }) => gt(persons.age, 28)) + .select(({ persons }) => ({ + id: persons.id, + name: persons.name, + })), + ), +) + +await waitFor(() => { + expect(result.current.data).toHaveLength(2) +}) +``` + +**Access pattern:** `result.current.data`, `result.current.state`, `result.current.status` + +## Vue + +Use Vue's `nextTick()` + a small delay for collection update propagation. + +```ts +import { nextTick } from 'vue' +import { useLiveQuery } from '@tanstack/vue-db' + +const { data, state } = useLiveQuery((q) => + q.from({ persons: collection }) + .where(({ persons }) => gt(persons.age, 28)), +) + +await nextTick() +await new Promise((resolve) => setTimeout(resolve, 50)) + +expect(data.value).toHaveLength(1) +``` + +**Access pattern:** `data.value`, `state.value` (Vue refs) + +## Svelte + +Use `$effect.root` and `flushSync` from Svelte 5. + +```ts +import { flushSync } from 'svelte' +import { useLiveQuery } from '@tanstack/svelte-db' + +let cleanup: (() => void) | null = null +afterEach(() => cleanup?.()) + +cleanup = $effect.root(() => { + const query = useLiveQuery((q) => + q.from({ persons: collection }) + .where(({ persons }) => gt(persons.age, 28)), + ) + + flushSync() + + expect(query.data).toHaveLength(1) +}) +``` + +**Access pattern:** `query.data`, `query.state` (direct access, reactive via runes) + +**Note:** When destructuring, wrap in `$derived` to maintain reactivity: +```ts +const { data } = $derived(query) // preserves reactivity +``` + +## Solid + +Use `renderHook` and `waitFor` from `@solidjs/testing-library`. + +```tsx +import { renderHook, waitFor } from '@solidjs/testing-library' +import { useLiveQuery } from '@tanstack/solid-db' + +const rendered = renderHook(() => + useLiveQuery((q) => + q.from({ persons: collection }) + .where(({ persons }) => gt(persons.age, 28)), + ), +) + +await waitFor(() => { + expect(rendered.result.state.size).toBe(1) +}) + +// result is an accessor (function call) +expect(rendered.result()).toHaveLength(1) +``` + +**Access pattern:** `rendered.result()` (accessor function), `rendered.result.state` + +## Angular + +Use `TestBed.runInInjectionContext` to provide the injection context. + +```ts +import { TestBed } from '@angular/core/testing' +import { injectLiveQuery } from '@tanstack/angular-db' + +TestBed.runInInjectionContext(() => { + const query = injectLiveQuery((q) => + q.from({ persons: collection }) + .where(({ persons }) => gt(persons.age, 28)), + ) + + await new Promise((resolve) => setTimeout(resolve, 50)) + + expect(query.data()).toHaveLength(1) +}) +``` + +**Access pattern:** `query.data()`, `query.state()` (Angular signals) + +## Testing Mutations + +Test optimistic inserts, updates, and deletes directly on collections: + +```ts +// Provide mutation handlers so the collection accepts mutations +const collection = createCollection({ + id: 'test', + getKey: (p) => p.id, + sync: { + sync: ({ begin, commit, markReady }) => { + begin(); commit(); markReady() + }, + }, + startSync: true, + onInsert: async () => {}, + onUpdate: async () => {}, + onDelete: async () => {}, +}) + +await collection.stateWhenReady() + +// Insert +collection.insert({ id: '1', name: 'Alice', age: 30 }) +expect(collection.has('1')).toBe(true) + +// Update (Immer-style draft) +collection.update('1', (draft) => { draft.age = 31 }) +expect(collection.get('1')).toMatchObject({ age: 31 }) + +// Delete +collection.delete('1') +expect(collection.has('1')).toBe(false) +``` + +### Testing createOptimisticAction + +```ts +import { createOptimisticAction } from '@tanstack/db' + +const likePost = createOptimisticAction({ + onMutate: (postId) => { + collection.update(postId, (draft) => { draft.liked = true }) + }, + mutationFn: async () => { /* mock */ }, +}) + +likePost('1') +expect(collection.get('1')).toMatchObject({ liked: true }) +``` + +## Stripping Virtual Props + +TanStack DB adds `$synced`, `$origin`, `$key`, `$collectionId` to items. Two approaches: + +1. **Use `toMatchObject`** (recommended) — ignores extra properties automatically. +2. **Strip manually** if you need exact equality: + +```ts +function stripVirtualProps>(value: T) { + const { $synced, $origin, $key, $collectionId, ...rest } = value as any + return rest as T +} + +expect(stripVirtualProps(item)).toEqual({ id: '1', name: 'Alice', age: 30 }) +``` + +## Common Mistakes + +| Mistake | Fix | +|---------|-----| +| Not calling `markReady()` in sync | Collection stays in `loading` state; queries never resolve | +| Reusing collection `id` across tests | State leaks between tests; use unique IDs | +| Not waiting for reactivity flush | Use framework-appropriate wait mechanism | +| Using `toEqual` on collection items | Use `toMatchObject` to ignore virtual props | +| Forgetting mutation handlers | Provide `onInsert`/`onUpdate`/`onDelete` (even empty async functions) | + +## Test Suite Overview + +TanStack DB ships with 166+ test files covering: + +| Area | Files | Focus | +|------|-------|-------| +| Collection Core | ~15 | CRUD, lifecycle, events, errors, schemas, indexes | +| Query System | ~30 | Builder, compiler, optimizer, operators, joins | +| IVM Operators | ~27 | filter, join, orderBy, groupBy, topK, etc. | +| Framework Hooks | 5 | React, Vue, Svelte, Solid, Angular | +| Persistence | ~20 | SQLite adapters (Node, Browser, Electron, React Native) | +| Offline Sync | 8 | Transaction queuing, failover, leader election | +| Integrations | ~6 | Electric, PowerSync, RxDB, TrailBase, Query | + +Run the full suite with: + +```bash +pnpm test +``` + +Or test a specific package: + +```bash +pnpm --filter @tanstack/db test +pnpm --filter @tanstack/react-db test +``` + +## Version + +Targets @tanstack/db v0.5.30. From 82e4f47f1b98397f6a04fafd14d24d959ad951c0 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 19 Mar 2026 13:38:04 +0000 Subject: [PATCH 2/3] Improve testing skill to follow Claude Code skills best practices - Add `user-invocable: false` since this is background knowledge, not a command - Expand description with trigger phrases ("when writing tests", "debugging test failures") - Extract test inventory into references/test-inventory.md supporting file (keeps SKILL.md focused, per "under 500 lines" guidance) - Add cross-references from SKILL.md to the reference file https://claude.ai/code/session_01EsJdvoefdRVjyFXLazdn73 --- packages/db/skills/db-core/testing/SKILL.md | 33 +-- .../testing/references/test-inventory.md | 279 ++++++++++++++++++ 2 files changed, 292 insertions(+), 20 deletions(-) create mode 100644 packages/db/skills/db-core/testing/references/test-inventory.md diff --git a/packages/db/skills/db-core/testing/SKILL.md b/packages/db/skills/db-core/testing/SKILL.md index 15b9c7636..2163a50e9 100644 --- a/packages/db/skills/db-core/testing/SKILL.md +++ b/packages/db/skills/db-core/testing/SKILL.md @@ -6,7 +6,9 @@ description: > React (renderHook, waitFor), Vue (nextTick), Svelte ($effect.root, flushSync), Solid (renderHook), and Angular (TestBed). Testing mutations (insert, update, delete), createOptimisticAction, and live queries. Stripping virtual props. - Vitest setup and configuration. + Vitest setup and configuration. Use when writing tests, setting up test + infrastructure, or debugging test failures for code that uses TanStack DB. +user-invocable: false type: sub-skill library: db library_version: '0.5.30' @@ -22,6 +24,9 @@ sources: This skill builds on db-core. Read it first for the overall mental model. +For the full test inventory with file counts and coverage areas, +see [references/test-inventory.md](references/test-inventory.md). + # TanStack DB — Testing ## Quick Decision Tree @@ -33,6 +38,7 @@ This skill builds on db-core. Read it first for the overall mental model. - Testing a Solid component with useLiveQuery? → [Solid](#solid) - Testing an Angular component with injectLiveQuery? → [Angular](#angular) - Testing mutations or optimistic actions? → [Testing Mutations](#testing-mutations) +- Want to see what the built-in test suite covers? → [references/test-inventory.md](references/test-inventory.md) ## Setup @@ -329,33 +335,20 @@ expect(stripVirtualProps(item)).toEqual({ id: '1', name: 'Alice', age: 30 }) | Using `toEqual` on collection items | Use `toMatchObject` to ignore virtual props | | Forgetting mutation handlers | Provide `onInsert`/`onUpdate`/`onDelete` (even empty async functions) | -## Test Suite Overview - -TanStack DB ships with 166+ test files covering: - -| Area | Files | Focus | -|------|-------|-------| -| Collection Core | ~15 | CRUD, lifecycle, events, errors, schemas, indexes | -| Query System | ~30 | Builder, compiler, optimizer, operators, joins | -| IVM Operators | ~27 | filter, join, orderBy, groupBy, topK, etc. | -| Framework Hooks | 5 | React, Vue, Svelte, Solid, Angular | -| Persistence | ~20 | SQLite adapters (Node, Browser, Electron, React Native) | -| Offline Sync | 8 | Transaction queuing, failover, leader election | -| Integrations | ~6 | Electric, PowerSync, RxDB, TrailBase, Query | - -Run the full suite with: +## Running the Built-in Tests ```bash +# Full suite pnpm test -``` - -Or test a specific package: -```bash +# Specific package pnpm --filter @tanstack/db test pnpm --filter @tanstack/react-db test ``` +For a detailed inventory of all 166+ built-in test files grouped by package and area, +see [references/test-inventory.md](references/test-inventory.md). + ## Version Targets @tanstack/db v0.5.30. diff --git a/packages/db/skills/db-core/testing/references/test-inventory.md b/packages/db/skills/db-core/testing/references/test-inventory.md new file mode 100644 index 000000000..f54fad428 --- /dev/null +++ b/packages/db/skills/db-core/testing/references/test-inventory.md @@ -0,0 +1,279 @@ +# TanStack DB — Built-in Test Inventory + +This is a supporting reference for the `db-core/testing` skill. It lists every +test file in the TanStack DB monorepo grouped by package and area. + +## Summary + +| Area | Files | Focus | +|------|-------|-------| +| Collection Core | ~15 | CRUD, lifecycle, events, errors, schemas, indexes | +| Query System | ~30 | Builder, compiler, optimizer, operators, joins | +| IVM Operators | ~27 | filter, join, orderBy, groupBy, topK, etc. | +| Framework Hooks | 5 | React, Vue, Svelte, Solid, Angular | +| Persistence | ~20 | SQLite adapters (Node, Browser, Electron, React Native) | +| Offline Sync | 8 | Transaction queuing, failover, leader election | +| Integrations | ~6 | Electric, PowerSync, RxDB, TrailBase, Query | + +**Total: 166+ test files** + +--- + +## Core Database (`@tanstack/db`) + +Location: `packages/db/tests/` + +### Collection tests + +| File | What it tests | +|------|---------------| +| `collection.test.ts` | Creation, CRUD, bulk ops, transactions, truncate, error handling (~1700 lines) | +| `collection-events.test.ts` | Status change, subscriber count, index lifecycle events, `on`/`once`/`off`/`waitFor` | +| `collection-schema.test.ts` | Zod/ArkType validation, transforms, defaults, type inference | +| `collection-getters.test.ts` | `state`, `size`, `has`, `keys`, `values`, `entries`, `get`, `stateWhenReady`, `toArray` | +| `collection-errors.test.ts` | Cleanup errors, stack preservation, state transition validation | +| `collection-indexes.test.ts` | Index creation/removal, signature stability, canonicalization, partial indexes | +| `collection-auto-index.test.ts` | Auto-indexing behavior, query optimization with auto-indexes | +| `collection-subscribe-changes.test.ts` | Change subscription, event aggregation, batching | +| `collection-subscription.test.ts` | Subscription mechanics | +| `collection-lifecycle.test.ts` | Lifecycle states, startup, shutdown, error recovery | +| `collection-change-events.test.ts` | Change event handling, propagation | +| `collection-truncate.test.ts` | Truncate operations with sync transactions | +| `collection-subscriber-duplicate-inserts.test.ts` | Duplicate insert handling in subscriber scenarios | +| `collection.test-d.ts` | TypeScript type definition tests | + +### Query tests + +| File | What it tests | +|------|---------------| +| `query/basic.test.ts` | Basic query construction, SELECT, WHERE, FROM | +| `query/builder/buildQuery.test.ts` | Query builder construction | +| `query/builder/join.test.ts` | JOIN operations | +| `query/builder/from.test.ts` | FROM clause | +| `query/builder/where.test.ts` | WHERE clause | +| `query/builder/order-by.test.ts` | ORDER BY | +| `query/builder/group-by.test.ts` | GROUP BY | +| `query/builder/select.test.ts` | SELECT clause | +| `query/builder/ref-proxy.test.ts` | Reference proxy patterns | +| `query/builder/functional-variants.test.ts` | Functional query variants | +| `query/builder/functions.test.ts` | Query functions | +| `query/compiler/basic.test.ts` | Query compilation | +| `query/compiler/evaluators.test.ts` | Expression evaluators | +| `query/compiler/group-by.test.ts` | GROUP BY compilation | +| `query/compiler/select.test.ts` | SELECT compilation | +| `query/compiler/subqueries.test.ts` | Subquery compilation | +| `query/compiler/subquery-caching.test.ts` | Subquery caching optimization | +| `query/composables.test.ts` | Reusable query composition | +| `query/distinct.test.ts` | DISTINCT operations | +| `query/expression-helpers.test.ts` | Expression helper functions | +| `query/functional-variants.test.ts` | Functional query variants | +| `query/group-by.test.ts` | GROUP BY queries | +| `query/indexes.test.ts` | Query index usage | +| `query/join.test.ts` | JOIN operations | +| `query/join-subquery.test.ts` | Subquery JOINs | +| `query/live-query-collection.test.ts` | Live query collections | +| `query/load-subset-subquery.test.ts` | Subset loading in subqueries | +| `query/optimistic-delete-with-limit.test.ts` | Optimistic delete with LIMIT | +| `query/optimizer.test.ts` | Query optimization | +| `query/optional-fields-runtime.test.ts` | Optional field handling | +| `query/order-by.test.ts` | ORDER BY execution | +| `query/predicate-utils.test.ts` | Predicate utility functions | +| `query/query-once.test.ts` | One-shot query execution | +| `query/query-while-syncing.test.ts` | Querying during sync | +| `query/scheduler.test.ts` | Query scheduling | +| `query/select.test.ts` | SELECT execution | +| `query/select-spread.test.ts` | SELECT with spread | +| `query/subquery.test.ts` | Subquery handling | +| `query/subset-dedupe.test.ts` | Subset deduplication | +| `query/validate-aliases.test.ts` | Alias validation | +| `query/where.test.ts` | WHERE execution | + +### Infrastructure tests + +| File | What it tests | +|------|---------------| +| `apply-mutations.test.ts` | Mutation application logic | +| `btree-index-undefined-values.test.ts` | BTree index with undefined values | +| `cleanup-queue.test.ts` | Cleanup queue management | +| `cursor.test.ts` | Cursor operations | +| `cursor.property.test.ts` | Property-based cursor tests | +| `comparison.property.test.ts` | Property-based comparison tests | +| `deferred.test.ts` | Deferred execution | +| `deterministic-ordering.test.ts` | Deterministic ordering guarantees | +| `effect.test.ts` | Side-effect handling | +| `errors.test.ts` | Error handling | +| `local-only.test.ts` | Local-only operations | +| `local-storage.test.ts` | LocalStorage operations | +| `optimistic-action.test.ts` | Optimistic action patterns | +| `paced-mutations.test.ts` | Paced mutation operations | +| `proxy.test.ts` | Proxy object patterns | +| `SortedMap.test.ts` | SortedMap data structure | +| `transaction-types.test.ts` | Transaction type checking | +| `transactions.test.ts` | Transaction operations | +| `utils.property.test.ts` | Property-based utility tests | +| `utils.test.ts` | Utility functions | +| `utility-exposure.test.ts` | Public utility exposure validation | +| `integration/uint8array-id-comparison.test.ts` | UInt8Array ID comparison | + +--- + +## IVM (`@tanstack/db-ivm`) + +Location: `packages/db-ivm/tests/` + +### Core + +| File | What it tests | +|------|---------------| +| `graph.test.ts` | DifferenceStreamReader/Writer, queue, multi-reader | +| `multiset.test.ts` | MultiSet data structure | +| `indexes.test.ts` | Index structures | +| `hash.property.test.ts` | Property-based hash testing | +| `utils.test.ts` | Utility functions | + +### Operators (27 files in `operators/`) + +`concat`, `consolidate`, `count`, `debug`, `distinct`, `filter`, `filterBy`, +`groupBy`, `groupedOrderByWithFractionalIndex`, `groupedTopKWithFractionalIndex`, +`join`, `join-types`, `keying`, `keying-types`, `map`, `negate`, `orderBy`, +`orderByWithFractionalIndex`, `orderByWithIndex`, `output`, `pipe`, `reduce`, +`topK`, `topKWithFractionalIndex`, `topKWithIndex` + +--- + +## Framework Hooks + +| Package | File | What it tests | +|---------|------|---------------| +| `@tanstack/react-db` | `useLiveQuery.test.tsx` | React hook with renderHook/waitFor | +| `@tanstack/react-db` | `useLiveInfiniteQuery.test.tsx` | Infinite pagination | +| `@tanstack/react-db` | `useLiveSuspenseQuery.test.tsx` | Suspense integration | +| `@tanstack/react-db` | `useLiveQueryEffect.test.tsx` | useEffect-based queries | +| `@tanstack/react-db` | `usePacedMutations.test.tsx` | Paced mutations hook | +| `@tanstack/vue-db` | `useLiveQuery.test.ts` | Vue 3 composition API | +| `@tanstack/svelte-db` | `useLiveQuery.svelte.test.ts` | Svelte 5 runes | +| `@tanstack/solid-db` | `useLiveQuery.test.tsx` | Solid.js reactive primitives | +| `@tanstack/angular-db` | `inject-live-query.test.ts` | Angular inject() pattern | + +--- + +## Persistence + +### SQLite Core (`@tanstack/db-sqlite-persisted-collection-core`) + +| File | What it tests | +|------|---------------| +| `sqlite-core-adapter.test.ts` | SQLite adapter operations | +| `sqlite-core-adapter-cli-runtime.test.ts` | CLI runtime compatibility | +| `persisted.test.ts` | Persistence adapter interface, recording adapter | + +### Node SQLite (`@tanstack/db-node-sqlite-persisted-collection`) + +| File | What it tests | +|------|---------------| +| `node-driver.test.ts` | Node.js SQLite driver | +| `node-persistence.test.ts` | Node.js persistence layer | +| `node-sqlite-core-adapter-contract.test.ts` | Core adapter contract compliance | +| `node-persisted-collection.e2e.test.ts` | End-to-end node persistence | + +### Browser WA-SQLite (`@tanstack/db-browser-wa-sqlite-persisted-collection`) + +| File | What it tests | +|------|---------------| +| `wa-sqlite-driver.test.ts` | WebAssembly SQLite driver | +| `browser-persistence.test.ts` | Browser persistence | +| `browser-single-tab.test.ts` | Single-tab scenarios | +| `browser-coordinator.test.ts` | Browser coordinator | +| `opfs-database.test.ts` | OPFS database | +| `browser-single-tab-persisted-collection.e2e.test.ts` | End-to-end browser tests | + +### Electron SQLite (`@tanstack/db-electron-sqlite-persisted-collection`) + +| File | What it tests | +|------|---------------| +| `electron-ipc.test.ts` | Electron IPC communication | +| `electron-sqlite-core-adapter-contract.test.ts` | Core adapter contract | +| `electron-runtime-bridge.e2e.test.ts` | Runtime bridge E2E | +| `electron-persisted-collection.e2e.test.ts` | Electron persistence E2E | + +### React Native SQLite (`@tanstack/db-react-native-sqlite-persisted-collection`) + +| File | What it tests | +|------|---------------| +| `op-sqlite-driver.test.ts` | Op SQLite driver | +| `react-native-persistence.test.ts` | React Native persistence | +| `expo-sqlite-core-adapter-contract.test.ts` | Expo SQLite adapter | +| `react-native-sqlite-core-adapter-contract.test.ts` | React Native SQLite adapter | +| `mobile-runtime-persistence-contract.test.ts` | Mobile runtime contract | +| `expo-persisted-collection.e2e.test.ts` | Expo persistence E2E | +| `react-native-persisted-collection.e2e.test.ts` | React Native persistence E2E | + +--- + +## Integration Packages + +### Electric SQL (`@tanstack/electric-db-collection`) + +| File | What it tests | +|------|---------------| +| `electric.test.ts` | Electric integration, ShapeStream mocking, change subscription | +| `electric-live-query.test.ts` | Live queries with Electric | +| `tags.test.ts` | Tag handling | +| `pg-serializer.test.ts` | PostgreSQL serialization | +| `pg-serializer.property.test.ts` | Property-based serializer tests | +| `sql-compiler.test.ts` | SQL compilation | +| `electric.e2e.test.ts` | End-to-end Electric | + +### PowerSync (`@tanstack/powersync-db-collection`) + +| File | What it tests | +|------|---------------| +| `powersync.test.ts` | PowerSync integration | +| `collection-schema.test.ts` | PowerSync schema | +| `schema.test.ts` | Schema definitions | +| `load-hooks.test.ts` | Load hook callbacks | +| `on-demand-sync.test.ts` | On-demand synchronization | +| `sqlite-compiler.test.ts` | SQLite compilation | + +### Others + +| Package | File | What it tests | +|---------|------|---------------| +| `@tanstack/query-db-collection` | `query.test.ts` | Query collection operations | +| `@tanstack/query-db-collection` | `query.e2e.test.ts` | Query collection E2E | +| `@tanstack/rxdb-db-collection` | `rxdb.test.ts` | RxDB integration | +| `@tanstack/trailbase-db-collection` | `trailbase.test.ts` | TrailBase integration | +| `@tanstack/trailbase-db-collection` | `trailbase.e2e.test.ts` | TrailBase E2E | + +--- + +## Offline Transactions (`@tanstack/offline-transactions`) + +Location: `packages/offline-transactions/tests/` + +| File | What it tests | +|------|---------------| +| `OfflineExecutor.test.ts` | Executor creation, offline transactions, outbox | +| `KeyScheduler.test.ts` | Key-based scheduling | +| `OnlineDetector.test.ts` | Browser online detection | +| `ReactNativeOnlineDetector.test.ts` | React Native online detection | +| `TransactionSerializer.test.ts` | Transaction serialization | +| `storage-failure.test.ts` | Storage failure scenarios | +| `leader-failover.test.ts` | Multi-tab leader election, failover | +| `offline-e2e.test.ts` | End-to-end offline scenarios | + +--- + +## Testing Patterns Used Across the Suite + +| Pattern | Where | Example | +|---------|-------|---------| +| `mockSyncCollectionOptions()` | All core + framework tests | Creates controlled sync with initial data | +| `stripVirtualProps()` | Core tests | Removes `$synced`, `$origin`, `$key`, `$collectionId` | +| `createIndexUsageTracker()` | Index/query optimizer tests | Monitors index method calls | +| `flushPromises()` | Async tests | `new Promise(resolve => setTimeout(resolve, 0))` | +| `withExpectedRejection()` | Error tests | Suppresses expected unhandled rejections | +| `vi.fn()` / `vi.mock()` | Integration tests | Mocks for ShapeStream, APIs, etc. | +| `.property.test.ts` files | Core + IVM | Property-based testing (fast-check) | +| `.e2e.test.ts` files | Persistence + integrations | Full integration across subsystems | +| `.test-d.ts` files | Core | TypeScript type definition tests | From ea5a58a5116db8f55c2b9ad8d37f09c95b569c77 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Thu, 19 Mar 2026 13:56:27 +0000 Subject: [PATCH 3/3] ci: apply automated fixes --- packages/db/skills/db-core/testing/SKILL.md | 48 ++- .../testing/references/test-inventory.md | 376 +++++++++--------- 2 files changed, 216 insertions(+), 208 deletions(-) diff --git a/packages/db/skills/db-core/testing/SKILL.md b/packages/db/skills/db-core/testing/SKILL.md index 2163a50e9..20b5ee454 100644 --- a/packages/db/skills/db-core/testing/SKILL.md +++ b/packages/db/skills/db-core/testing/SKILL.md @@ -98,6 +98,7 @@ const collection = createCollection({ ``` **Key points:** + - Call `begin()` before writing, `commit()` after, and `markReady()` once initial load is done. - Each test should use a unique collection `id` to avoid state leakage. - Keep references to `begin`/`write`/`commit` to simulate live updates later in the test. @@ -125,7 +126,9 @@ const collection = createCollection({ await collection.stateWhenReady() // Test mutation -collection.update('1', (draft) => { draft.name = 'Alice Updated' }) +collection.update('1', (draft) => { + draft.name = 'Alice Updated' +}) expect(collection.get('1')).toMatchObject({ name: 'Alice Updated' }) // Test size @@ -169,8 +172,7 @@ import { nextTick } from 'vue' import { useLiveQuery } from '@tanstack/vue-db' const { data, state } = useLiveQuery((q) => - q.from({ persons: collection }) - .where(({ persons }) => gt(persons.age, 28)), + q.from({ persons: collection }).where(({ persons }) => gt(persons.age, 28)), ) await nextTick() @@ -194,8 +196,7 @@ afterEach(() => cleanup?.()) cleanup = $effect.root(() => { const query = useLiveQuery((q) => - q.from({ persons: collection }) - .where(({ persons }) => gt(persons.age, 28)), + q.from({ persons: collection }).where(({ persons }) => gt(persons.age, 28)), ) flushSync() @@ -207,6 +208,7 @@ cleanup = $effect.root(() => { **Access pattern:** `query.data`, `query.state` (direct access, reactive via runes) **Note:** When destructuring, wrap in `$derived` to maintain reactivity: + ```ts const { data } = $derived(query) // preserves reactivity ``` @@ -221,8 +223,7 @@ import { useLiveQuery } from '@tanstack/solid-db' const rendered = renderHook(() => useLiveQuery((q) => - q.from({ persons: collection }) - .where(({ persons }) => gt(persons.age, 28)), + q.from({ persons: collection }).where(({ persons }) => gt(persons.age, 28)), ), ) @@ -246,8 +247,7 @@ import { injectLiveQuery } from '@tanstack/angular-db' TestBed.runInInjectionContext(() => { const query = injectLiveQuery((q) => - q.from({ persons: collection }) - .where(({ persons }) => gt(persons.age, 28)), + q.from({ persons: collection }).where(({ persons }) => gt(persons.age, 28)), ) await new Promise((resolve) => setTimeout(resolve, 50)) @@ -269,7 +269,9 @@ const collection = createCollection({ getKey: (p) => p.id, sync: { sync: ({ begin, commit, markReady }) => { - begin(); commit(); markReady() + begin() + commit() + markReady() }, }, startSync: true, @@ -285,7 +287,9 @@ collection.insert({ id: '1', name: 'Alice', age: 30 }) expect(collection.has('1')).toBe(true) // Update (Immer-style draft) -collection.update('1', (draft) => { draft.age = 31 }) +collection.update('1', (draft) => { + draft.age = 31 +}) expect(collection.get('1')).toMatchObject({ age: 31 }) // Delete @@ -300,9 +304,13 @@ import { createOptimisticAction } from '@tanstack/db' const likePost = createOptimisticAction({ onMutate: (postId) => { - collection.update(postId, (draft) => { draft.liked = true }) + collection.update(postId, (draft) => { + draft.liked = true + }) + }, + mutationFn: async () => { + /* mock */ }, - mutationFn: async () => { /* mock */ }, }) likePost('1') @@ -327,13 +335,13 @@ expect(stripVirtualProps(item)).toEqual({ id: '1', name: 'Alice', age: 30 }) ## Common Mistakes -| Mistake | Fix | -|---------|-----| -| Not calling `markReady()` in sync | Collection stays in `loading` state; queries never resolve | -| Reusing collection `id` across tests | State leaks between tests; use unique IDs | -| Not waiting for reactivity flush | Use framework-appropriate wait mechanism | -| Using `toEqual` on collection items | Use `toMatchObject` to ignore virtual props | -| Forgetting mutation handlers | Provide `onInsert`/`onUpdate`/`onDelete` (even empty async functions) | +| Mistake | Fix | +| ------------------------------------ | --------------------------------------------------------------------- | +| Not calling `markReady()` in sync | Collection stays in `loading` state; queries never resolve | +| Reusing collection `id` across tests | State leaks between tests; use unique IDs | +| Not waiting for reactivity flush | Use framework-appropriate wait mechanism | +| Using `toEqual` on collection items | Use `toMatchObject` to ignore virtual props | +| Forgetting mutation handlers | Provide `onInsert`/`onUpdate`/`onDelete` (even empty async functions) | ## Running the Built-in Tests diff --git a/packages/db/skills/db-core/testing/references/test-inventory.md b/packages/db/skills/db-core/testing/references/test-inventory.md index f54fad428..5f7de9b34 100644 --- a/packages/db/skills/db-core/testing/references/test-inventory.md +++ b/packages/db/skills/db-core/testing/references/test-inventory.md @@ -5,15 +5,15 @@ test file in the TanStack DB monorepo grouped by package and area. ## Summary -| Area | Files | Focus | -|------|-------|-------| -| Collection Core | ~15 | CRUD, lifecycle, events, errors, schemas, indexes | -| Query System | ~30 | Builder, compiler, optimizer, operators, joins | -| IVM Operators | ~27 | filter, join, orderBy, groupBy, topK, etc. | -| Framework Hooks | 5 | React, Vue, Svelte, Solid, Angular | -| Persistence | ~20 | SQLite adapters (Node, Browser, Electron, React Native) | -| Offline Sync | 8 | Transaction queuing, failover, leader election | -| Integrations | ~6 | Electric, PowerSync, RxDB, TrailBase, Query | +| Area | Files | Focus | +| --------------- | ----- | ------------------------------------------------------- | +| Collection Core | ~15 | CRUD, lifecycle, events, errors, schemas, indexes | +| Query System | ~30 | Builder, compiler, optimizer, operators, joins | +| IVM Operators | ~27 | filter, join, orderBy, groupBy, topK, etc. | +| Framework Hooks | 5 | React, Vue, Svelte, Solid, Angular | +| Persistence | ~20 | SQLite adapters (Node, Browser, Electron, React Native) | +| Offline Sync | 8 | Transaction queuing, failover, leader election | +| Integrations | ~6 | Electric, PowerSync, RxDB, TrailBase, Query | **Total: 166+ test files** @@ -25,95 +25,95 @@ Location: `packages/db/tests/` ### Collection tests -| File | What it tests | -|------|---------------| -| `collection.test.ts` | Creation, CRUD, bulk ops, transactions, truncate, error handling (~1700 lines) | -| `collection-events.test.ts` | Status change, subscriber count, index lifecycle events, `on`/`once`/`off`/`waitFor` | -| `collection-schema.test.ts` | Zod/ArkType validation, transforms, defaults, type inference | -| `collection-getters.test.ts` | `state`, `size`, `has`, `keys`, `values`, `entries`, `get`, `stateWhenReady`, `toArray` | -| `collection-errors.test.ts` | Cleanup errors, stack preservation, state transition validation | -| `collection-indexes.test.ts` | Index creation/removal, signature stability, canonicalization, partial indexes | -| `collection-auto-index.test.ts` | Auto-indexing behavior, query optimization with auto-indexes | -| `collection-subscribe-changes.test.ts` | Change subscription, event aggregation, batching | -| `collection-subscription.test.ts` | Subscription mechanics | -| `collection-lifecycle.test.ts` | Lifecycle states, startup, shutdown, error recovery | -| `collection-change-events.test.ts` | Change event handling, propagation | -| `collection-truncate.test.ts` | Truncate operations with sync transactions | -| `collection-subscriber-duplicate-inserts.test.ts` | Duplicate insert handling in subscriber scenarios | -| `collection.test-d.ts` | TypeScript type definition tests | +| File | What it tests | +| ------------------------------------------------- | --------------------------------------------------------------------------------------- | +| `collection.test.ts` | Creation, CRUD, bulk ops, transactions, truncate, error handling (~1700 lines) | +| `collection-events.test.ts` | Status change, subscriber count, index lifecycle events, `on`/`once`/`off`/`waitFor` | +| `collection-schema.test.ts` | Zod/ArkType validation, transforms, defaults, type inference | +| `collection-getters.test.ts` | `state`, `size`, `has`, `keys`, `values`, `entries`, `get`, `stateWhenReady`, `toArray` | +| `collection-errors.test.ts` | Cleanup errors, stack preservation, state transition validation | +| `collection-indexes.test.ts` | Index creation/removal, signature stability, canonicalization, partial indexes | +| `collection-auto-index.test.ts` | Auto-indexing behavior, query optimization with auto-indexes | +| `collection-subscribe-changes.test.ts` | Change subscription, event aggregation, batching | +| `collection-subscription.test.ts` | Subscription mechanics | +| `collection-lifecycle.test.ts` | Lifecycle states, startup, shutdown, error recovery | +| `collection-change-events.test.ts` | Change event handling, propagation | +| `collection-truncate.test.ts` | Truncate operations with sync transactions | +| `collection-subscriber-duplicate-inserts.test.ts` | Duplicate insert handling in subscriber scenarios | +| `collection.test-d.ts` | TypeScript type definition tests | ### Query tests -| File | What it tests | -|------|---------------| -| `query/basic.test.ts` | Basic query construction, SELECT, WHERE, FROM | -| `query/builder/buildQuery.test.ts` | Query builder construction | -| `query/builder/join.test.ts` | JOIN operations | -| `query/builder/from.test.ts` | FROM clause | -| `query/builder/where.test.ts` | WHERE clause | -| `query/builder/order-by.test.ts` | ORDER BY | -| `query/builder/group-by.test.ts` | GROUP BY | -| `query/builder/select.test.ts` | SELECT clause | -| `query/builder/ref-proxy.test.ts` | Reference proxy patterns | -| `query/builder/functional-variants.test.ts` | Functional query variants | -| `query/builder/functions.test.ts` | Query functions | -| `query/compiler/basic.test.ts` | Query compilation | -| `query/compiler/evaluators.test.ts` | Expression evaluators | -| `query/compiler/group-by.test.ts` | GROUP BY compilation | -| `query/compiler/select.test.ts` | SELECT compilation | -| `query/compiler/subqueries.test.ts` | Subquery compilation | -| `query/compiler/subquery-caching.test.ts` | Subquery caching optimization | -| `query/composables.test.ts` | Reusable query composition | -| `query/distinct.test.ts` | DISTINCT operations | -| `query/expression-helpers.test.ts` | Expression helper functions | -| `query/functional-variants.test.ts` | Functional query variants | -| `query/group-by.test.ts` | GROUP BY queries | -| `query/indexes.test.ts` | Query index usage | -| `query/join.test.ts` | JOIN operations | -| `query/join-subquery.test.ts` | Subquery JOINs | -| `query/live-query-collection.test.ts` | Live query collections | -| `query/load-subset-subquery.test.ts` | Subset loading in subqueries | -| `query/optimistic-delete-with-limit.test.ts` | Optimistic delete with LIMIT | -| `query/optimizer.test.ts` | Query optimization | -| `query/optional-fields-runtime.test.ts` | Optional field handling | -| `query/order-by.test.ts` | ORDER BY execution | -| `query/predicate-utils.test.ts` | Predicate utility functions | -| `query/query-once.test.ts` | One-shot query execution | -| `query/query-while-syncing.test.ts` | Querying during sync | -| `query/scheduler.test.ts` | Query scheduling | -| `query/select.test.ts` | SELECT execution | -| `query/select-spread.test.ts` | SELECT with spread | -| `query/subquery.test.ts` | Subquery handling | -| `query/subset-dedupe.test.ts` | Subset deduplication | -| `query/validate-aliases.test.ts` | Alias validation | -| `query/where.test.ts` | WHERE execution | +| File | What it tests | +| -------------------------------------------- | --------------------------------------------- | +| `query/basic.test.ts` | Basic query construction, SELECT, WHERE, FROM | +| `query/builder/buildQuery.test.ts` | Query builder construction | +| `query/builder/join.test.ts` | JOIN operations | +| `query/builder/from.test.ts` | FROM clause | +| `query/builder/where.test.ts` | WHERE clause | +| `query/builder/order-by.test.ts` | ORDER BY | +| `query/builder/group-by.test.ts` | GROUP BY | +| `query/builder/select.test.ts` | SELECT clause | +| `query/builder/ref-proxy.test.ts` | Reference proxy patterns | +| `query/builder/functional-variants.test.ts` | Functional query variants | +| `query/builder/functions.test.ts` | Query functions | +| `query/compiler/basic.test.ts` | Query compilation | +| `query/compiler/evaluators.test.ts` | Expression evaluators | +| `query/compiler/group-by.test.ts` | GROUP BY compilation | +| `query/compiler/select.test.ts` | SELECT compilation | +| `query/compiler/subqueries.test.ts` | Subquery compilation | +| `query/compiler/subquery-caching.test.ts` | Subquery caching optimization | +| `query/composables.test.ts` | Reusable query composition | +| `query/distinct.test.ts` | DISTINCT operations | +| `query/expression-helpers.test.ts` | Expression helper functions | +| `query/functional-variants.test.ts` | Functional query variants | +| `query/group-by.test.ts` | GROUP BY queries | +| `query/indexes.test.ts` | Query index usage | +| `query/join.test.ts` | JOIN operations | +| `query/join-subquery.test.ts` | Subquery JOINs | +| `query/live-query-collection.test.ts` | Live query collections | +| `query/load-subset-subquery.test.ts` | Subset loading in subqueries | +| `query/optimistic-delete-with-limit.test.ts` | Optimistic delete with LIMIT | +| `query/optimizer.test.ts` | Query optimization | +| `query/optional-fields-runtime.test.ts` | Optional field handling | +| `query/order-by.test.ts` | ORDER BY execution | +| `query/predicate-utils.test.ts` | Predicate utility functions | +| `query/query-once.test.ts` | One-shot query execution | +| `query/query-while-syncing.test.ts` | Querying during sync | +| `query/scheduler.test.ts` | Query scheduling | +| `query/select.test.ts` | SELECT execution | +| `query/select-spread.test.ts` | SELECT with spread | +| `query/subquery.test.ts` | Subquery handling | +| `query/subset-dedupe.test.ts` | Subset deduplication | +| `query/validate-aliases.test.ts` | Alias validation | +| `query/where.test.ts` | WHERE execution | ### Infrastructure tests -| File | What it tests | -|------|---------------| -| `apply-mutations.test.ts` | Mutation application logic | -| `btree-index-undefined-values.test.ts` | BTree index with undefined values | -| `cleanup-queue.test.ts` | Cleanup queue management | -| `cursor.test.ts` | Cursor operations | -| `cursor.property.test.ts` | Property-based cursor tests | -| `comparison.property.test.ts` | Property-based comparison tests | -| `deferred.test.ts` | Deferred execution | -| `deterministic-ordering.test.ts` | Deterministic ordering guarantees | -| `effect.test.ts` | Side-effect handling | -| `errors.test.ts` | Error handling | -| `local-only.test.ts` | Local-only operations | -| `local-storage.test.ts` | LocalStorage operations | -| `optimistic-action.test.ts` | Optimistic action patterns | -| `paced-mutations.test.ts` | Paced mutation operations | -| `proxy.test.ts` | Proxy object patterns | -| `SortedMap.test.ts` | SortedMap data structure | -| `transaction-types.test.ts` | Transaction type checking | -| `transactions.test.ts` | Transaction operations | -| `utils.property.test.ts` | Property-based utility tests | -| `utils.test.ts` | Utility functions | -| `utility-exposure.test.ts` | Public utility exposure validation | -| `integration/uint8array-id-comparison.test.ts` | UInt8Array ID comparison | +| File | What it tests | +| ---------------------------------------------- | ---------------------------------- | +| `apply-mutations.test.ts` | Mutation application logic | +| `btree-index-undefined-values.test.ts` | BTree index with undefined values | +| `cleanup-queue.test.ts` | Cleanup queue management | +| `cursor.test.ts` | Cursor operations | +| `cursor.property.test.ts` | Property-based cursor tests | +| `comparison.property.test.ts` | Property-based comparison tests | +| `deferred.test.ts` | Deferred execution | +| `deterministic-ordering.test.ts` | Deterministic ordering guarantees | +| `effect.test.ts` | Side-effect handling | +| `errors.test.ts` | Error handling | +| `local-only.test.ts` | Local-only operations | +| `local-storage.test.ts` | LocalStorage operations | +| `optimistic-action.test.ts` | Optimistic action patterns | +| `paced-mutations.test.ts` | Paced mutation operations | +| `proxy.test.ts` | Proxy object patterns | +| `SortedMap.test.ts` | SortedMap data structure | +| `transaction-types.test.ts` | Transaction type checking | +| `transactions.test.ts` | Transaction operations | +| `utils.property.test.ts` | Property-based utility tests | +| `utils.test.ts` | Utility functions | +| `utility-exposure.test.ts` | Public utility exposure validation | +| `integration/uint8array-id-comparison.test.ts` | UInt8Array ID comparison | --- @@ -123,13 +123,13 @@ Location: `packages/db-ivm/tests/` ### Core -| File | What it tests | -|------|---------------| -| `graph.test.ts` | DifferenceStreamReader/Writer, queue, multi-reader | -| `multiset.test.ts` | MultiSet data structure | -| `indexes.test.ts` | Index structures | -| `hash.property.test.ts` | Property-based hash testing | -| `utils.test.ts` | Utility functions | +| File | What it tests | +| ----------------------- | -------------------------------------------------- | +| `graph.test.ts` | DifferenceStreamReader/Writer, queue, multi-reader | +| `multiset.test.ts` | MultiSet data structure | +| `indexes.test.ts` | Index structures | +| `hash.property.test.ts` | Property-based hash testing | +| `utils.test.ts` | Utility functions | ### Operators (27 files in `operators/`) @@ -143,17 +143,17 @@ Location: `packages/db-ivm/tests/` ## Framework Hooks -| Package | File | What it tests | -|---------|------|---------------| -| `@tanstack/react-db` | `useLiveQuery.test.tsx` | React hook with renderHook/waitFor | -| `@tanstack/react-db` | `useLiveInfiniteQuery.test.tsx` | Infinite pagination | -| `@tanstack/react-db` | `useLiveSuspenseQuery.test.tsx` | Suspense integration | -| `@tanstack/react-db` | `useLiveQueryEffect.test.tsx` | useEffect-based queries | -| `@tanstack/react-db` | `usePacedMutations.test.tsx` | Paced mutations hook | -| `@tanstack/vue-db` | `useLiveQuery.test.ts` | Vue 3 composition API | -| `@tanstack/svelte-db` | `useLiveQuery.svelte.test.ts` | Svelte 5 runes | -| `@tanstack/solid-db` | `useLiveQuery.test.tsx` | Solid.js reactive primitives | -| `@tanstack/angular-db` | `inject-live-query.test.ts` | Angular inject() pattern | +| Package | File | What it tests | +| ---------------------- | ------------------------------- | ---------------------------------- | +| `@tanstack/react-db` | `useLiveQuery.test.tsx` | React hook with renderHook/waitFor | +| `@tanstack/react-db` | `useLiveInfiniteQuery.test.tsx` | Infinite pagination | +| `@tanstack/react-db` | `useLiveSuspenseQuery.test.tsx` | Suspense integration | +| `@tanstack/react-db` | `useLiveQueryEffect.test.tsx` | useEffect-based queries | +| `@tanstack/react-db` | `usePacedMutations.test.tsx` | Paced mutations hook | +| `@tanstack/vue-db` | `useLiveQuery.test.ts` | Vue 3 composition API | +| `@tanstack/svelte-db` | `useLiveQuery.svelte.test.ts` | Svelte 5 runes | +| `@tanstack/solid-db` | `useLiveQuery.test.tsx` | Solid.js reactive primitives | +| `@tanstack/angular-db` | `inject-live-query.test.ts` | Angular inject() pattern | --- @@ -161,52 +161,52 @@ Location: `packages/db-ivm/tests/` ### SQLite Core (`@tanstack/db-sqlite-persisted-collection-core`) -| File | What it tests | -|------|---------------| -| `sqlite-core-adapter.test.ts` | SQLite adapter operations | -| `sqlite-core-adapter-cli-runtime.test.ts` | CLI runtime compatibility | -| `persisted.test.ts` | Persistence adapter interface, recording adapter | +| File | What it tests | +| ----------------------------------------- | ------------------------------------------------ | +| `sqlite-core-adapter.test.ts` | SQLite adapter operations | +| `sqlite-core-adapter-cli-runtime.test.ts` | CLI runtime compatibility | +| `persisted.test.ts` | Persistence adapter interface, recording adapter | ### Node SQLite (`@tanstack/db-node-sqlite-persisted-collection`) -| File | What it tests | -|------|---------------| -| `node-driver.test.ts` | Node.js SQLite driver | -| `node-persistence.test.ts` | Node.js persistence layer | +| File | What it tests | +| ------------------------------------------- | -------------------------------- | +| `node-driver.test.ts` | Node.js SQLite driver | +| `node-persistence.test.ts` | Node.js persistence layer | | `node-sqlite-core-adapter-contract.test.ts` | Core adapter contract compliance | -| `node-persisted-collection.e2e.test.ts` | End-to-end node persistence | +| `node-persisted-collection.e2e.test.ts` | End-to-end node persistence | ### Browser WA-SQLite (`@tanstack/db-browser-wa-sqlite-persisted-collection`) -| File | What it tests | -|------|---------------| -| `wa-sqlite-driver.test.ts` | WebAssembly SQLite driver | -| `browser-persistence.test.ts` | Browser persistence | -| `browser-single-tab.test.ts` | Single-tab scenarios | -| `browser-coordinator.test.ts` | Browser coordinator | -| `opfs-database.test.ts` | OPFS database | -| `browser-single-tab-persisted-collection.e2e.test.ts` | End-to-end browser tests | +| File | What it tests | +| ----------------------------------------------------- | ------------------------- | +| `wa-sqlite-driver.test.ts` | WebAssembly SQLite driver | +| `browser-persistence.test.ts` | Browser persistence | +| `browser-single-tab.test.ts` | Single-tab scenarios | +| `browser-coordinator.test.ts` | Browser coordinator | +| `opfs-database.test.ts` | OPFS database | +| `browser-single-tab-persisted-collection.e2e.test.ts` | End-to-end browser tests | ### Electron SQLite (`@tanstack/db-electron-sqlite-persisted-collection`) -| File | What it tests | -|------|---------------| -| `electron-ipc.test.ts` | Electron IPC communication | -| `electron-sqlite-core-adapter-contract.test.ts` | Core adapter contract | -| `electron-runtime-bridge.e2e.test.ts` | Runtime bridge E2E | -| `electron-persisted-collection.e2e.test.ts` | Electron persistence E2E | +| File | What it tests | +| ----------------------------------------------- | -------------------------- | +| `electron-ipc.test.ts` | Electron IPC communication | +| `electron-sqlite-core-adapter-contract.test.ts` | Core adapter contract | +| `electron-runtime-bridge.e2e.test.ts` | Runtime bridge E2E | +| `electron-persisted-collection.e2e.test.ts` | Electron persistence E2E | ### React Native SQLite (`@tanstack/db-react-native-sqlite-persisted-collection`) -| File | What it tests | -|------|---------------| -| `op-sqlite-driver.test.ts` | Op SQLite driver | -| `react-native-persistence.test.ts` | React Native persistence | -| `expo-sqlite-core-adapter-contract.test.ts` | Expo SQLite adapter | -| `react-native-sqlite-core-adapter-contract.test.ts` | React Native SQLite adapter | -| `mobile-runtime-persistence-contract.test.ts` | Mobile runtime contract | -| `expo-persisted-collection.e2e.test.ts` | Expo persistence E2E | -| `react-native-persisted-collection.e2e.test.ts` | React Native persistence E2E | +| File | What it tests | +| --------------------------------------------------- | ---------------------------- | +| `op-sqlite-driver.test.ts` | Op SQLite driver | +| `react-native-persistence.test.ts` | React Native persistence | +| `expo-sqlite-core-adapter-contract.test.ts` | Expo SQLite adapter | +| `react-native-sqlite-core-adapter-contract.test.ts` | React Native SQLite adapter | +| `mobile-runtime-persistence-contract.test.ts` | Mobile runtime contract | +| `expo-persisted-collection.e2e.test.ts` | Expo persistence E2E | +| `react-native-persisted-collection.e2e.test.ts` | React Native persistence E2E | --- @@ -214,36 +214,36 @@ Location: `packages/db-ivm/tests/` ### Electric SQL (`@tanstack/electric-db-collection`) -| File | What it tests | -|------|---------------| -| `electric.test.ts` | Electric integration, ShapeStream mocking, change subscription | -| `electric-live-query.test.ts` | Live queries with Electric | -| `tags.test.ts` | Tag handling | -| `pg-serializer.test.ts` | PostgreSQL serialization | -| `pg-serializer.property.test.ts` | Property-based serializer tests | -| `sql-compiler.test.ts` | SQL compilation | -| `electric.e2e.test.ts` | End-to-end Electric | +| File | What it tests | +| -------------------------------- | -------------------------------------------------------------- | +| `electric.test.ts` | Electric integration, ShapeStream mocking, change subscription | +| `electric-live-query.test.ts` | Live queries with Electric | +| `tags.test.ts` | Tag handling | +| `pg-serializer.test.ts` | PostgreSQL serialization | +| `pg-serializer.property.test.ts` | Property-based serializer tests | +| `sql-compiler.test.ts` | SQL compilation | +| `electric.e2e.test.ts` | End-to-end Electric | ### PowerSync (`@tanstack/powersync-db-collection`) -| File | What it tests | -|------|---------------| -| `powersync.test.ts` | PowerSync integration | -| `collection-schema.test.ts` | PowerSync schema | -| `schema.test.ts` | Schema definitions | -| `load-hooks.test.ts` | Load hook callbacks | -| `on-demand-sync.test.ts` | On-demand synchronization | -| `sqlite-compiler.test.ts` | SQLite compilation | +| File | What it tests | +| --------------------------- | ------------------------- | +| `powersync.test.ts` | PowerSync integration | +| `collection-schema.test.ts` | PowerSync schema | +| `schema.test.ts` | Schema definitions | +| `load-hooks.test.ts` | Load hook callbacks | +| `on-demand-sync.test.ts` | On-demand synchronization | +| `sqlite-compiler.test.ts` | SQLite compilation | ### Others -| Package | File | What it tests | -|---------|------|---------------| -| `@tanstack/query-db-collection` | `query.test.ts` | Query collection operations | -| `@tanstack/query-db-collection` | `query.e2e.test.ts` | Query collection E2E | -| `@tanstack/rxdb-db-collection` | `rxdb.test.ts` | RxDB integration | -| `@tanstack/trailbase-db-collection` | `trailbase.test.ts` | TrailBase integration | -| `@tanstack/trailbase-db-collection` | `trailbase.e2e.test.ts` | TrailBase E2E | +| Package | File | What it tests | +| ----------------------------------- | ----------------------- | --------------------------- | +| `@tanstack/query-db-collection` | `query.test.ts` | Query collection operations | +| `@tanstack/query-db-collection` | `query.e2e.test.ts` | Query collection E2E | +| `@tanstack/rxdb-db-collection` | `rxdb.test.ts` | RxDB integration | +| `@tanstack/trailbase-db-collection` | `trailbase.test.ts` | TrailBase integration | +| `@tanstack/trailbase-db-collection` | `trailbase.e2e.test.ts` | TrailBase E2E | --- @@ -251,29 +251,29 @@ Location: `packages/db-ivm/tests/` Location: `packages/offline-transactions/tests/` -| File | What it tests | -|------|---------------| -| `OfflineExecutor.test.ts` | Executor creation, offline transactions, outbox | -| `KeyScheduler.test.ts` | Key-based scheduling | -| `OnlineDetector.test.ts` | Browser online detection | -| `ReactNativeOnlineDetector.test.ts` | React Native online detection | -| `TransactionSerializer.test.ts` | Transaction serialization | -| `storage-failure.test.ts` | Storage failure scenarios | -| `leader-failover.test.ts` | Multi-tab leader election, failover | -| `offline-e2e.test.ts` | End-to-end offline scenarios | +| File | What it tests | +| ----------------------------------- | ----------------------------------------------- | +| `OfflineExecutor.test.ts` | Executor creation, offline transactions, outbox | +| `KeyScheduler.test.ts` | Key-based scheduling | +| `OnlineDetector.test.ts` | Browser online detection | +| `ReactNativeOnlineDetector.test.ts` | React Native online detection | +| `TransactionSerializer.test.ts` | Transaction serialization | +| `storage-failure.test.ts` | Storage failure scenarios | +| `leader-failover.test.ts` | Multi-tab leader election, failover | +| `offline-e2e.test.ts` | End-to-end offline scenarios | --- ## Testing Patterns Used Across the Suite -| Pattern | Where | Example | -|---------|-------|---------| -| `mockSyncCollectionOptions()` | All core + framework tests | Creates controlled sync with initial data | -| `stripVirtualProps()` | Core tests | Removes `$synced`, `$origin`, `$key`, `$collectionId` | -| `createIndexUsageTracker()` | Index/query optimizer tests | Monitors index method calls | -| `flushPromises()` | Async tests | `new Promise(resolve => setTimeout(resolve, 0))` | -| `withExpectedRejection()` | Error tests | Suppresses expected unhandled rejections | -| `vi.fn()` / `vi.mock()` | Integration tests | Mocks for ShapeStream, APIs, etc. | -| `.property.test.ts` files | Core + IVM | Property-based testing (fast-check) | -| `.e2e.test.ts` files | Persistence + integrations | Full integration across subsystems | -| `.test-d.ts` files | Core | TypeScript type definition tests | +| Pattern | Where | Example | +| ----------------------------- | --------------------------- | ----------------------------------------------------- | +| `mockSyncCollectionOptions()` | All core + framework tests | Creates controlled sync with initial data | +| `stripVirtualProps()` | Core tests | Removes `$synced`, `$origin`, `$key`, `$collectionId` | +| `createIndexUsageTracker()` | Index/query optimizer tests | Monitors index method calls | +| `flushPromises()` | Async tests | `new Promise(resolve => setTimeout(resolve, 0))` | +| `withExpectedRejection()` | Error tests | Suppresses expected unhandled rejections | +| `vi.fn()` / `vi.mock()` | Integration tests | Mocks for ShapeStream, APIs, etc. | +| `.property.test.ts` files | Core + IVM | Property-based testing (fast-check) | +| `.e2e.test.ts` files | Persistence + integrations | Full integration across subsystems | +| `.test-d.ts` files | Core | TypeScript type definition tests |