> ## Documentation Index
> Fetch the complete documentation index at: https://cindel.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Testing Cindel: Strategies for Unit and Widget Tests

> Use Cindel.openInMemory for fast isolated tests, write CRUD and query assertions, and simulate sync online/offline states with FakeSyncAdapter.

Cindel is designed to be test-friendly. The `Cindel.openInMemory` API gives every test its own temporary database that uses the same generated typed API as your production code, so tests are fast, isolated, and trust-worthy. You don't need a device, an emulator, or any disk setup — just the schemas your test cares about.

## Unit and Package Tests

Open an in-memory database at the start of each test and register only the schemas that test needs:

```dart theme={null}
void main() {
  late CindelDatabase db;

  setUp(() async {
    db = await Cindel.openInMemory(schemas: [TodoSchema]);
  });

  tearDown(() => db.close());

  test('creates and reads a todo', () async {
    final todo = Todo()..title = 'Test';
    await db.writeTxn(() => db.todos.put(todo));

    final found = await db.todos.where().findFirst();
    expect(found?.title, equals('Test'));
  });
}
```

Always call `addTearDown(db.close)` (or close in `tearDown`) so the database is cleaned up even when an expectation fails mid-test. On Web, `openInMemory` uses a unique browser database name for each session, so closing is especially important to avoid state leaking between test runs.

<Tip>
  Give each test its own `openInMemory` instance rather than sharing a single database across tests. Isolated databases make failures easier to diagnose and prevent one test's writes from affecting another.
</Tip>

### What to Test

Cover the operations your application actually relies on:

* **CRUD** — `put`, `get`, `delete`, `putAll`, `getAll`
* **Queries** — `where()` helpers for indexed fields, `filter()` for arbitrary predicates
* **Aggregates** — `count()` for pagination guards and empty-state logic
* **Sorting and pagination** — verify that sort order and page limits produce the expected results

**CRUD example**

```dart theme={null}
test('puts and gets a todo', () async {
  final db = await Cindel.openInMemory(schemas: [TodoSchema]);
  addTearDown(db.close);

  final todo = Todo()..title = 'Write docs';
  await db.writeTxn(() => db.todos.put(todo));

  final saved = await db.todos.get(todo.dbId);
  expect(saved, isNotNull);
  expect(saved!.title, 'Write docs');
});
```

**Query example**

```dart theme={null}
test('filters open todos', () async {
  final db = await Cindel.openInMemory(schemas: [TodoSchema]);
  addTearDown(db.close);

  await db.writeTxn(() => db.todos.putAll([
    Todo()
      ..title = 'Open'
      ..completed = false,
    Todo()
      ..title = 'Done'
      ..completed = true,
  ]));

  final open = await db.todos
      .filter()
      .completedEqualTo(false)
      .findAll();

  expect(open, hasLength(1));
  expect(open.single.title, 'Open');
});
```

**Count example**

```dart theme={null}
final count = await db.todos
    .filter()
    .completedEqualTo(false)
    .count();

expect(count, 1);
```

## Widget Tests

Use `openInMemory` inside `testWidgets` exactly as you would in a unit test. Pass the database to your widget tree and pump normally:

```dart theme={null}
testWidgets('shows todos', (tester) async {
  final db = await Cindel.openInMemory(schemas: [TodoSchema]);
  addTearDown(db.close);

  await db.writeTxn(() => db.todos.put(Todo()..title = 'Write docs'));

  await tester.pumpWidget(
    TodoApp(database: db),
  );

  expect(find.text('Write docs'), findsOneWidget);
});
```

Keep database setup close to the test so each test controls its own data and there is no hidden shared state.

<Note>
  Keep `cindel_flutter_libs` in `dependencies` — not `dev_dependencies` — even when your tests use in-memory databases. This ensures integration tests and platform builds share the same package graph as the app, and that `flutter test` loads the native libraries correctly.
</Note>

```yaml theme={null}
dependencies:
  cindel: ^x.y.z
  cindel_flutter_libs: ^x.y.z
```

## Sync Testing

Test sync behavior with a `FakeSyncAdapter` that lets you control online/offline state deterministically. Toggle `online` before each assertion instead of relying on arbitrary `sleep` calls:

```dart theme={null}
class FakeSyncAdapter implements CindelSyncAdapter {
  bool online = true;

  @override
  Future<CindelPushResult> push(CindelPushRequest request) async {
    if (!online) throw Exception('Offline');
    // ... handle push
  }

  @override
  Future<CindelPullResult> pull(CindelPullRequest request) async {
    if (!online) throw Exception('Offline');
    // ... handle pull
  }
}
```

Wire the fake adapter into a database opened with a real directory (not in-memory, so you can test close/reopen and persistence), and use a short sync interval to keep tests fast:

```dart theme={null}
final adapter = FakeSyncAdapter()..online = false;
final statuses = <CindelSyncStatus>[];

final db = await Cindel.open(
  directory: directory.path,
  schemas: [UserSchema],
  sync: CindelSyncConfig(
    adapter: adapter,
    interval: const Duration(milliseconds: 10),
    onStatusChanged: statuses.add,
  ),
);
```

Useful sync cases to cover:

* A local write is visible immediately, even while offline
* A pending write survives `close` and `reopen`
* A backend correction applies locally after the adapter comes back online
* A delete replicates to another client
* A remote apply does not create a second local pending mutation
* Unsupported operations fail with a clear error

## When to Use a Persistent Temp Directory

Use `openInMemory` for the vast majority of your tests. Reach for a persistent temporary directory (for example, via `path_provider`'s temp dir) only when the test specifically needs:

* **Close/reopen behavior** — verify data survives a database restart
* **File persistence** — check that files appear on disk after a write
* **Backup and restore** — test full archive export/import flows
* **Sync restart behavior** — confirm pending mutations survive a process restart
