[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6C58B0PQHV5WXMQRP9DEPX1":3,"skill-same:01M6C58B0PQHV5WXMQRP9DEPX1":137},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":23,"publishedAt":43,"linkOnly":44,"markdownContent":45,"markdownBody":46,"frontMatter":47,"refName":48,"commitSha":49,"skillDirectory":50,"forksCount":14,"repositoryHtmlUrl":51,"repositoryHomepageUrl":52,"ownerHtmlUrl":53,"ownerBlogUrl":54,"ownerDescription":55,"updatedAt":56,"install":57,"resources":70},"01M6C58B0PQHV5WXMQRP9DEPX1","testing",null,"Ditto SDK testing strategies in Flutter, JavaScript, Kotlin, or Swift: local-store test helper, tests for DQL statements, MISSING\u002FNULL filters, merge-sensitive writes, soft delete, observer lifecycle, subscription rules, and multi-peer sync tests in one process. Use when writing or reviewing tests for code that uses Ditto, setting up a Ditto test harness, or reproducing a Ditto sync or merge bug.","ditto-examples\u002Fditto-cookbook","ditto-examples","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F192611150?v=4","01M6C57F0Y1W6S5D9RD8GHE3YB","Ditto Examples","01M6C57F52Z11DDMBC0ZJ7FJPG",0,"MIT","https:\u002F\u002Fgithub.com\u002Fditto-examples\u002Fditto-cookbook\u002Fblob\u002Fmain\u002Fplugins\u002Fditto\u002Fskills\u002Ftesting\u002FSKILL.md",[18],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000008","测试与质量","SCENARIO","使用场景",[5,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42],"local","for","swift","test harness","when","testing strategies","tests","missing","bug","strategies","sync","merge","delete","code","filters","writing","up","lifecycle","harness","2026-11-10 10:12:30",false,"---\nname: testing\ndescription: \"Ditto SDK testing strategies in Flutter, JavaScript, Kotlin, or Swift: local-store test helper, tests for DQL statements, MISSING\u002FNULL filters, merge-sensitive writes, soft delete, observer lifecycle, subscription rules, and multi-peer sync tests in one process. Use when writing or reviewing tests for code that uses Ditto, setting up a Ditto test harness, or reproducing a Ditto sync or merge bug.\"\n---\n\n# Ditto Testing Strategies\n\nHow to test code that uses Ditto: a real local store per test for DQL, write shapes, filters, and lifecycle, and several synced peers in one process for merges. Targets Ditto SDK 5.2.0 in every SDK; tests are described as statements and expected results, and the test code is per SDK.\n\n## Before You Apply\n\n- Identify the project's SDK and version: Flutter (`ditto_live` in `pubspec.yaml` \u002F `pubspec.lock`), JavaScript (`@dittolive\u002Fditto` in `package.json`), Kotlin (`com.ditto:ditto-kotlin` in Gradle files), or Swift (`DittoSwift` in `Package.swift`, `Package.resolved`, or the Xcode project). These rules target SDK 5.2.0; for devices that still run 5.1.0, also apply `§ Known Issues in SDK 5.1.0`. **Note (SDK 5.1.0 and 5.2.0)** and **Note (SDK 5.1.0)** mark easy-to-miss behavior seen on those releases, with the safe pattern; **(SDK 5.1+)** \u002F **(SDK 5.2+)** mark features introduced in that release.\n- For API calls, Grep the outline of that SDK's page, `${CLAUDE_SKILL_DIR}\u002F..\u002Fguide\u002Freference\u002Fbest-practices\u002Fplatforms\u002F\u003Csdk>.md` (`flutter`, `javascript`, `kotlin`, `swift`), with `^#{1,3} ` and read only the section you need, not the whole page; sections are named `\u003CTopic> in \u003CSDK>`. API examples: `examples\u002F\u003Csdk>\u002F`, with the same base names in every SDK.\n- `§ \u003CHeading>` cites the full guide: Grep the heading as a fixed string in `best-practices\u002F` and `knowledge\u002F` under `${CLAUDE_SKILL_DIR}\u002F..\u002Fguide\u002Freference\u002F`, then read that section.\n- Report each finding at the severity of its item in `§ Anti-Pattern Checklist` (the checklist decides; Grep a phrase of the item there). The labels below rank the rules, not each item in them.\n\n## Prevents\n\n- DQL statements that fail to parse or plan only at runtime, and hot queries that silently lose their index scan\n- Soft-delete filters that hide documents where the flag is missing or null\n- `DELETE`\u002F`EVICT` statements that complete without removing anything (for example `USE IDS` without a `WHERE` predicate on devices that run SDK 5.1.0)\n- Write shapes that merge badly (whole-document rewrites, arrays instead of maps, `SET` that keeps old map keys) introduced by a refactoring\n- Upserts that rewrite unchanged data\n- Observers and stream subscriptions that are never cancelled\n- Subscription queries rejected at registration (projections, `ORDER BY`\u002F`LIMIT`, `JOIN`, ...)\n- Merge and deletion bugs that only appear when several devices write concurrently\n- Hanging tests from a shared or doubly opened persistence directory\n\n## Workflow\n\n```\nAdding tests to a Ditto app:\n- [ ] 1. Put Ditto behind a repository or service interface; unit-test UI and business logic with a mock\n- [ ] 2. Add the local store helper (local-store-test-helper in examples\u002F\u003Csdk>\u002F), with the app's system parameters and its index setup\n- [ ] 3. Run every app DQL statement through EXPLAIN; assert index scans for hot queries\n- [ ] 4. Test filters with the flag true, false, null, and missing\n- [ ] 5. Test merge-sensitive writes (map keys kept, no-op re-upserts, object replacement)\n- [ ] 6. Test deletion counts with the result's mutated document IDs\n- [ ] 7. Test observer delivery and cleanup; register every app subscription\n- [ ] 8. With an offline license token, add multi-peer tests for concurrent edits and deletions\n- [ ] 9. Keep a few tests on real devices for transports, permissions, and clocks\n```\n\nTest layers (`§ Testing Strategies`):\n\n| Layer | Covers | Ditto instance | License or server |\n|---|---|---|---|\n| Unit tests with a mock | UI and business logic behind your repository interface | None (mock your own interface) | No |\n| Local store tests | Your DQL statements, write shapes, soft-delete filters, observer and subscription lifecycle | Real, small-peers-only, sync never started | No |\n| Multi-peer tests in one process | Concurrent edits and merges, deletion propagation, relay, attachments across peers | Several real instances syncing over TCP on localhost | An offline license token |\n| Tests on real devices | Transports (Bluetooth LE, P2P Wi-Fi, LAN), permissions, Ditto Server, real clocks | Your app on devices | An offline license token, or a Ditto Server test database |\n\n## Rules\n\n### 1. Give every test its own local store, and never start sync in it (CRITICAL)\n\nMany problems in Ditto apps come from the data model and the queries, and are cheap to catch against a **real local store** on one device. Open a small-peers-only instance (the default connection mode) in a fresh temporary persistence directory per test, and close it when the test ends, also when it fails. **Note (SDK 5.2.0):** in Flutter, close the test stores after the last test of the file instead: a process that closed one store per test, each with a subscription or a transaction, stopped at a later `Ditto.open()` (`§ A local-store test helper in Flutter`). The local store works without a license, so such tests need no token and no network and can run in CI.\n\n**✅ DO**:\n- Use one helper (`local-store-test-helper` in `examples\u002F\u003Csdk>\u002F`): temporary directory, cleanup that closes Ditto and deletes the directory, the app's system parameters, and its index setup.\n- Use the same system parameters (for example `DQL_STRICT_MODE`) as the app, through the system parameters of `DittoConfig` (SDK 5.2+; on 5.1, `ALTER SYSTEM` after opening), and the same indexes; system parameters change how statements behave and are not persisted.\n\n**❌ DON'T**:\n- Share one persistence directory between tests or open it twice. **Note (SDK 5.1.0):** in Flutter, a second open of an open directory never completed (5.2.0 fails after about a second).\n- Start sync in local store tests. It is not needed, and in small-peers-only mode it throws until a valid offline license token is set.\n\n`§ Testing Strategies`, `§ A Test Helper for a Local Store`, `§ Initializing Ditto`, `§ One instance per persistence directory`, `§ Applying System Parameters` · Helper: `local-store-test-helper` in `examples\u002F\u003Csdk>\u002F`, [reference\u002Ftest-helpers.md](reference\u002Ftest-helpers.md)\n\n### 2. Run Ditto tests where the SDK runs (CRITICAL)\n\nA real `Ditto` instance needs the SDK's native library, so tests that open Ditto run where the SDK runs; tests that only use a mock of your own interface run anywhere.\n\n| SDK | Where tests that open Ditto run |\n|---|---|\n| Flutter | `integration_test` on a desktop or device target (`flutter test integration_test\u002Forders_test.dart -d macos`) |\n| JavaScript | Node.js (or the browser the app targets) |\n| Kotlin | Android instrumented tests on an emulator or device |\n| Swift | Swift Testing or XCTest on macOS or an iOS simulator |\n\nIn-memory stores (Flutter Web, the JavaScript SDK in browsers) do not support indexes, so skip index assertions there. `§ A Test Helper for a Local Store`, `§ Requirements`, *Testing in \u003CSDK>* on the SDK page\n\n### 3. Test your code, not the SDK (HIGH)\n\n**✅ DO**: Keep Ditto behind a repository or service interface so UI and business logic can be unit-tested with a mock. In Ditto tests, call your app's repository functions (not inline copies of the statements), so a test fails when the app code changes.\n**❌ DON'T**: Write tests that only re-check SDK behavior. Test your own repository functions, data model, and business rules.\n\n`§ Testing Strategies`, `§ A Test Helper for a Local Store`\n\n### 4. Test filters with every flag state, including MISSING and NULL (CRITICAL)\n\nDeletion logic fails silently: a filter that excludes documents without the flag. Insert documents with the flag `true`, `false`, `null`, and missing, and assert exactly which ones the query returns.\n\n```sql\n-- Rows: {\"_id\": \"deleted\", \"isDeleted\": true}, {\"_id\": \"active\", \"isDeleted\": false},\n-- {\"_id\": \"nullFlag\", \"isDeleted\": null}, {\"_id\": \"noFlag\"}\n-- Expected: active, noFlag, nullFlag\nSELECT _id FROM orders WHERE coalesce(isDeleted, false) = false ORDER BY _id\n```\n\n`§ Testing Deletion and Soft Delete`, `§ Soft Delete`, `§ MISSING and NULL` · Example: [reference\u002Flocal-store-tests.md](reference\u002Flocal-store-tests.md#testing-deletion-and-soft-delete), `local-store-tests` in `examples\u002F\u003Csdk>\u002F`\n\n### 5. Assert how many documents a DELETE or EVICT removed (CRITICAL)\n\nA deletion statement can complete without removing anything. Assert the result's mutated document IDs and the remaining rows. This also catches SDK 5.1.0, where `DELETE` or `EVICT` with `USE IDS` and no `WHERE` predicate (no `WHERE` clause, or `WHERE true`) removes nothing (`§ Known Issues in SDK 5.1.0`). Run the same tests on every SDK release that devices in the field use.\n\n**✅ DO**: run `DELETE FROM orders WHERE _id IN :ids` with two IDs, then assert two mutated document IDs.\n**❌ DON'T**: Assume a statement that did not throw removed the documents.\n\nTombstone expiry, husk documents, and \"zombie data\" from devices offline longer than the tombstone TTL involve several peers; cover them in multi-peer tests (rule 10). `§ Testing Deletion and Soft Delete`, `§ DELETE and Tombstones`, `§ Known Issues in SDK 5.1.0`\n\n### 6. Test the write shape of merge-sensitive writes (HIGH)\n\nOne device cannot reproduce a concurrent merge, but it can verify that your code produces the write shape that merges well: field-level updates instead of whole-document rewrites, maps keyed by ID instead of arrays, and upserts that skip unchanged values. Assert the local semantics your code relies on, so a refactoring that switches to a whole-document write or an array is caught.\n\n**✅ DO** assert that:\n- Adding a line item with `INSERT ... ON ID CONFLICT DO UPDATE_LOCAL_DIFF` of a partial document keeps the existing items, and `items` is a map, not an array.\n- Re-upserting unchanged data with `DO UPDATE_LOCAL_DIFF` returns no mutated document IDs.\n- A function that replaces an object (`UNSET` then `SET` in one transaction) removes keys that are not in the new object; a plain `SET` would keep them, because objects merge under the default settings.\n- Statements use the same type declarations (`REGISTER`, `COUNTER`, `ATTACHMENT`) and strict mode setting as the app; mixed declarations produce results that look like data loss.\n\n`§ Testing Merge-Sensitive Writes`, `§ CRDT Types and Merge Behavior`, `§ Arrays and Maps`, `§ Keep type declarations consistent` · Examples: [reference\u002Flocal-store-tests.md](reference\u002Flocal-store-tests.md#testing-merge-sensitive-writes)\n\n### 7. Validate every app statement with EXPLAIN (HIGH)\n\nKeep the app's DQL statements as constants in one place (for example a repository class) and run each through `EXPLAIN` with representative arguments. `EXPLAIN` parses and plans without executing, so the test catches syntax errors and statements that cannot be planned without touching data.\n\n**✅ DO**: For hot queries, create the indexes with the app's startup code and assert that the plan contains `indexScan`:\n\n```sql\n-- Expected: the plan contains \"indexScan\"\n-- Arguments: {\"status\": \"open\"}\nEXPLAIN SELECT * FROM orders WHERE status = :status ORDER BY createdAt DESC\n```\n\nA later change to the query or to strict mode (which disables index scans in 5.1.0 and 5.2.0) then fails the test. During development, `ADVISE` (SDK 5.1+) suggests the indexes these statements need.\n\n`§ Validating DQL in Tests`, `§ EXPLAIN and PROFILE`, `§ Strict Mode`, `§ ADVISE (SDK 5.1+)` · Example: [reference\u002Flocal-store-tests.md](reference\u002Flocal-store-tests.md#validating-dql-in-tests)\n\n### 8. Test observer delivery and cleanup (HIGH)\n\nTest that observers deliver updates, and that your cleanup code releases them: run the screen's cleanup, then assert that the observer reports itself as cancelled (in Flutter, cancel both the stream subscription and the observer).\n\n**✅ DO**: Wait for a specific result with a timeout (for example 5 seconds).\n**❌ DON'T**: Use a fixed delay, or assert on the number of callbacks; it is not guaranteed.\n\nFor UI tests of screens that own observers, keep the observer behind your own interface and inject a stream or fake you control. The differs only accept items produced by Ditto, so code that uses one needs a real store.\n\n`§ Testing Observer Lifecycle`, `§ Observer Lifecycle`, `§ Diffing Results` · Example: [reference\u002Flocal-store-tests.md](reference\u002Flocal-store-tests.md#testing-observer-lifecycle)\n\n### 9. Register every app subscription in a test (HIGH)\n\n`registerSubscription` validates the query when it is called, even before sync starts. It fails for projections, aggregates, `DISTINCT`, `GROUP BY`, `JOIN`, `USE IDS`, and (while `DQL_RESTRICT_SUBSCRIPTIONS` has its default `true`) `ORDER BY` and `LIMIT`. `WHERE` filters are allowed.\n\n**✅ DO**: Register the app's real subscriptions, then cancel them and assert that they are cancelled; this catches invalid subscription queries without a network. Optionally assert that forbidden forms fail to register:\n\n\u003C!-- subscription expect-error -->\n```sql\n-- Expected: registering this subscription fails\nSELECT _id, status FROM orders\n```\n\n`§ Testing Subscription Rules`, `§ Subscription Rules` · Examples: [reference\u002Flocal-store-tests.md](reference\u002Flocal-store-tests.md#testing-subscription-rules)\n\n### 10. Test merges with several peers in one process (HIGH)\n\nConcurrent edits, merges, deletion propagation, multi-hop relay, and attachment fetching across peers need sync between real instances. Most of these tests do not need physical devices: several instances in one test process can sync over TCP on `127.0.0.1`.\n\n- Each peer has its own persistence directory; all peers of a group share one Database ID. Give each group in a test, and each test process that can run at the same time, its own port and Database ID: a second hub listened on a port already in use without an error, and reconnecting clients moved to it and mixed the groups (SDK 5.1.0 and 5.2.0).\n- Disable peer-to-peer transports; peer 0 listens on `127.0.0.1`, the others connect to it (and listen on `127.0.0.1` only).\n- Small-peers-only sync needs an **offline license token**: starting sync fails until `setOfflineOnlyLicenseToken` has set a valid token. Without one, tests are limited to a single local store.\n- **✅ DO**: Make the concurrent writes while the peers are disconnected (stop sync on every peer, write, reconnect), then wait with a timeout until every peer returns the same rows.\n- **❌ DON'T**: Write while the peers are connected and call it a merge test; the writes reach the other peers within milliseconds.\n- A listening peer accepted at most 6 TCP connections by default in testing with SDK 5.1.0 and 5.2.0 (`MESH_CHOOSER_MAX_WLAN_CONNECTIONS`), so keep the peer count at 7 or below. The Flutter multi-peer test file was verified with `ditto_live` 5.1.0 and has not yet been run with 5.2.0. For a relay test, chain the peers: A listens, B listens and connects to A, C connects to B.\n\n`§ Testing on Multiple Devices`, `§ Several peers in one test process`, `§ Transport Configuration` · Helpers and test: `multi-peer-test` in `examples\u002F\u003Csdk>\u002F`, [reference\u002Fmulti-peer-tests.md](reference\u002Fmulti-peer-tests.md)\n\n### 11. Keep what one process cannot simulate on real devices (MEDIUM)\n\nThe in-process setup shares one clock (no clock skew) and exercises only TCP. Bluetooth LE, P2P Wi-Fi, LAN discovery, permissions, app lifecycle, and Bluetooth LE as the only transport need real devices. Two iOS simulators on one Mac can sync over AWDL, which checks the app's transport configuration but not AWDL over the air (`§ Two iOS simulators synced over AWDL alone`).\n\n- **Sync through Ditto Server** needs a Ditto Server test database. Use separate databases (apps) for development, staging, and production, or one per developer; developers who share one can prefix collection names. There is no Ditto Server mock for CI.\n- **Offline scenarios:** to simulate a Ditto Server outage while keeping peer-to-peer sync, let the app authenticate first, then block the Ditto Server host at the network level; blocking it before authentication makes sync fail entirely.\n\nScenarios worth covering: different fields and the same field edited offline on two devices; map (and, for comparison, array) entries added, edited, and removed on two devices; delete racing an update (expect a husk document), also with soft delete; a counter recounted with `RESTART WITH` while another device increments it; a device behind a relay with narrower subscriptions; a soft delete that reaches a device only through a relay; attachments created offline and fetched later.\n\n`§ Testing on Multiple Devices`, `§ Husk documents`, `§ RESTART`, `§ Multi-hop relay`, `§ Availability` · [reference\u002Fmulti-peer-tests.md](reference\u002Fmulti-peer-tests.md#several-peers-in-one-test-process)\n\n### 12. Keep license tokens out of source control (MEDIUM)\n\nPass the offline license token to tests from a CI secret, for example an environment variable or, in Flutter, `--dart-define-from-file=license.json` with an uncommitted `license.json`. Skip multi-peer tests when the token is empty, so local store tests still run in CI without it.\n\n`§ Testing on Multiple Devices`, `§ Several peers in one test process`\n\n## Checklist\n\n- [ ] Ditto is behind a repository interface; UI and business logic tests use a mock\n- [ ] Each Ditto test opens its own temporary persistence directory and closes it when the test ends (Flutter 5.2.0: after the file's last test)\n- [ ] Local store tests never start sync\n- [ ] Tests apply the app's system parameters and indexes\n- [ ] Ditto tests run where the SDK runs (Flutter `integration_test`, Node.js, instrumented tests, Swift Testing or XCTest)\n- [ ] Every app statement passes `EXPLAIN`; hot queries assert `indexScan`\n- [ ] Filters are tested with the flag `true`, `false`, `null`, and missing\n- [ ] Deletions assert the mutated document IDs and the remaining rows\n- [ ] Merge-sensitive writes assert map shape, no-op re-upserts, and object replacement\n- [ ] Observer tests wait with a timeout and assert that cleanup released the observer\n- [ ] Every app subscription is registered and cancelled in a test\n- [ ] Multi-peer tests write while disconnected and wait for convergence\n- [ ] The license token comes from a CI secret; tests skip without it\n- [ ] Transports, permissions, and clock skew are covered on real devices\n\n## More\n\n- Reference: [reference\u002Ftest-helpers.md](reference\u002Ftest-helpers.md) - what the local store helper does, and where tests run\n- Reference: [reference\u002Flocal-store-tests.md](reference\u002Flocal-store-tests.md) - tests for merge-sensitive writes, deletion and soft delete, observers, subscriptions, and `EXPLAIN`, as statements and expected results\n- Reference: [reference\u002Fmulti-peer-tests.md](reference\u002Fmulti-peer-tests.md) - multi-peer helpers, a concurrent-edit test, limits, and scenarios\n- Examples: `examples\u002F\u003Csdk>\u002F` - `local-store-test-helper`, `local-store-tests`, `multi-peer-test`\n- Related skills: `query-sync` (DQL, MISSING vs NULL, subscription rules, observers), `data-modeling` (merge behavior, maps vs arrays, type declarations), `storage-lifecycle` (DELETE, soft delete, EVICT, tombstones), `sdk-setup` (DittoConfig, license token, transports), `audit` (reviewing a Ditto codebase)\n","# Ditto Testing Strategies\n\nHow to test code that uses Ditto: a real local store per test for DQL, write shapes, filters, and lifecycle, and several synced peers in one process for merges. Targets Ditto SDK 5.2.0 in every SDK; tests are described as statements and expected results, and the test code is per SDK.\n\n## Before You Apply\n\n- Identify the project's SDK and version: Flutter (`ditto_live` in `pubspec.yaml` \u002F `pubspec.lock`), JavaScript (`@dittolive\u002Fditto` in `package.json`), Kotlin (`com.ditto:ditto-kotlin` in Gradle files), or Swift (`DittoSwift` in `Package.swift`, `Package.resolved`, or the Xcode project). These rules target SDK 5.2.0; for devices that still run 5.1.0, also apply `§ Known Issues in SDK 5.1.0`. **Note (SDK 5.1.0 and 5.2.0)** and **Note (SDK 5.1.0)** mark easy-to-miss behavior seen on those releases, with the safe pattern; **(SDK 5.1+)** \u002F **(SDK 5.2+)** mark features introduced in that release.\n- For API calls, Grep the outline of that SDK's page, `${CLAUDE_SKILL_DIR}\u002F..\u002Fguide\u002Freference\u002Fbest-practices\u002Fplatforms\u002F\u003Csdk>.md` (`flutter`, `javascript`, `kotlin`, `swift`), with `^#{1,3} ` and read only the section you need, not the whole page; sections are named `\u003CTopic> in \u003CSDK>`. API examples: `examples\u002F\u003Csdk>\u002F`, with the same base names in every SDK.\n- `§ \u003CHeading>` cites the full guide: Grep the heading as a fixed string in `best-practices\u002F` and `knowledge\u002F` under `${CLAUDE_SKILL_DIR}\u002F..\u002Fguide\u002Freference\u002F`, then read that section.\n- Report each finding at the severity of its item in `§ Anti-Pattern Checklist` (the checklist decides; Grep a phrase of the item there). The labels below rank the rules, not each item in them.\n\n## Prevents\n\n- DQL statements that fail to parse or plan only at runtime, and hot queries that silently lose their index scan\n- Soft-delete filters that hide documents where the flag is missing or null\n- `DELETE`\u002F`EVICT` statements that complete without removing anything (for example `USE IDS` without a `WHERE` predicate on devices that run SDK 5.1.0)\n- Write shapes that merge badly (whole-document rewrites, arrays instead of maps, `SET` that keeps old map keys) introduced by a refactoring\n- Upserts that rewrite unchanged data\n- Observers and stream subscriptions that are never cancelled\n- Subscription queries rejected at registration (projections, `ORDER BY`\u002F`LIMIT`, `JOIN`, ...)\n- Merge and deletion bugs that only appear when several devices write concurrently\n- Hanging tests from a shared or doubly opened persistence directory\n\n## Workflow\n\n```\nAdding tests to a Ditto app:\n- [ ] 1. Put Ditto behind a repository or service interface; unit-test UI and business logic with a mock\n- [ ] 2. Add the local store helper (local-store-test-helper in examples\u002F\u003Csdk>\u002F), with the app's system parameters and its index setup\n- [ ] 3. Run every app DQL statement through EXPLAIN; assert index scans for hot queries\n- [ ] 4. Test filters with the flag true, false, null, and missing\n- [ ] 5. Test merge-sensitive writes (map keys kept, no-op re-upserts, object replacement)\n- [ ] 6. Test deletion counts with the result's mutated document IDs\n- [ ] 7. Test observer delivery and cleanup; register every app subscription\n- [ ] 8. With an offline license token, add multi-peer tests for concurrent edits and deletions\n- [ ] 9. Keep a few tests on real devices for transports, permissions, and clocks\n```\n\nTest layers (`§ Testing Strategies`):\n\n| Layer | Covers | Ditto instance | License or server |\n|---|---|---|---|\n| Unit tests with a mock | UI and business logic behind your repository interface | None (mock your own interface) | No |\n| Local store tests | Your DQL statements, write shapes, soft-delete filters, observer and subscription lifecycle | Real, small-peers-only, sync never started | No |\n| Multi-peer tests in one process | Concurrent edits and merges, deletion propagation, relay, attachments across peers | Several real instances syncing over TCP on localhost | An offline license token |\n| Tests on real devices | Transports (Bluetooth LE, P2P Wi-Fi, LAN), permissions, Ditto Server, real clocks | Your app on devices | An offline license token, or a Ditto Server test database |\n\n## Rules\n\n### 1. Give every test its own local store, and never start sync in it (CRITICAL)\n\nMany problems in Ditto apps come from the data model and the queries, and are cheap to catch against a **real local store** on one device. Open a small-peers-only instance (the default connection mode) in a fresh temporary persistence directory per test, and close it when the test ends, also when it fails. **Note (SDK 5.2.0):** in Flutter, close the test stores after the last test of the file instead: a process that closed one store per test, each with a subscription or a transaction, stopped at a later `Ditto.open()` (`§ A local-store test helper in Flutter`). The local store works without a license, so such tests need no token and no network and can run in CI.\n\n**✅ DO**:\n- Use one helper (`local-store-test-helper` in `examples\u002F\u003Csdk>\u002F`): temporary directory, cleanup that closes Ditto and deletes the directory, the app's system parameters, and its index setup.\n- Use the same system parameters (for example `DQL_STRICT_MODE`) as the app, through the system parameters of `DittoConfig` (SDK 5.2+; on 5.1, `ALTER SYSTEM` after opening), and the same indexes; system parameters change how statements behave and are not persisted.\n\n**❌ DON'T**:\n- Share one persistence directory between tests or open it twice. **Note (SDK 5.1.0):** in Flutter, a second open of an open directory never completed (5.2.0 fails after about a second).\n- Start sync in local store tests. It is not needed, and in small-peers-only mode it throws until a valid offline license token is set.\n\n`§ Testing Strategies`, `§ A Test Helper for a Local Store`, `§ Initializing Ditto`, `§ One instance per persistence directory`, `§ Applying System Parameters` · Helper: `local-store-test-helper` in `examples\u002F\u003Csdk>\u002F`, [reference\u002Ftest-helpers.md](reference\u002Ftest-helpers.md)\n\n### 2. Run Ditto tests where the SDK runs (CRITICAL)\n\nA real `Ditto` instance needs the SDK's native library, so tests that open Ditto run where the SDK runs; tests that only use a mock of your own interface run anywhere.\n\n| SDK | Where tests that open Ditto run |\n|---|---|\n| Flutter | `integration_test` on a desktop or device target (`flutter test integration_test\u002Forders_test.dart -d macos`) |\n| JavaScript | Node.js (or the browser the app targets) |\n| Kotlin | Android instrumented tests on an emulator or device |\n| Swift | Swift Testing or XCTest on macOS or an iOS simulator |\n\nIn-memory stores (Flutter Web, the JavaScript SDK in browsers) do not support indexes, so skip index assertions there. `§ A Test Helper for a Local Store`, `§ Requirements`, *Testing in \u003CSDK>* on the SDK page\n\n### 3. Test your code, not the SDK (HIGH)\n\n**✅ DO**: Keep Ditto behind a repository or service interface so UI and business logic can be unit-tested with a mock. In Ditto tests, call your app's repository functions (not inline copies of the statements), so a test fails when the app code changes.\n**❌ DON'T**: Write tests that only re-check SDK behavior. Test your own repository functions, data model, and business rules.\n\n`§ Testing Strategies`, `§ A Test Helper for a Local Store`\n\n### 4. Test filters with every flag state, including MISSING and NULL (CRITICAL)\n\nDeletion logic fails silently: a filter that excludes documents without the flag. Insert documents with the flag `true`, `false`, `null`, and missing, and assert exactly which ones the query returns.\n\n```sql\n-- Rows: {\"_id\": \"deleted\", \"isDeleted\": true}, {\"_id\": \"active\", \"isDeleted\": false},\n-- {\"_id\": \"nullFlag\", \"isDeleted\": null}, {\"_id\": \"noFlag\"}\n-- Expected: active, noFlag, nullFlag\nSELECT _id FROM orders WHERE coalesce(isDeleted, false) = false ORDER BY _id\n```\n\n`§ Testing Deletion and Soft Delete`, `§ Soft Delete`, `§ MISSING and NULL` · Example: [reference\u002Flocal-store-tests.md](reference\u002Flocal-store-tests.md#testing-deletion-and-soft-delete), `local-store-tests` in `examples\u002F\u003Csdk>\u002F`\n\n### 5. Assert how many documents a DELETE or EVICT removed (CRITICAL)\n\nA deletion statement can complete without removing anything. Assert the result's mutated document IDs and the remaining rows. This also catches SDK 5.1.0, where `DELETE` or `EVICT` with `USE IDS` and no `WHERE` predicate (no `WHERE` clause, or `WHERE true`) removes nothing (`§ Known Issues in SDK 5.1.0`). Run the same tests on every SDK release that devices in the field use.\n\n**✅ DO**: run `DELETE FROM orders WHERE _id IN :ids` with two IDs, then assert two mutated document IDs.\n**❌ DON'T**: Assume a statement that did not throw removed the documents.\n\nTombstone expiry, husk documents, and \"zombie data\" from devices offline longer than the tombstone TTL involve several peers; cover them in multi-peer tests (rule 10). `§ Testing Deletion and Soft Delete`, `§ DELETE and Tombstones`, `§ Known Issues in SDK 5.1.0`\n\n### 6. Test the write shape of merge-sensitive writes (HIGH)\n\nOne device cannot reproduce a concurrent merge, but it can verify that your code produces the write shape that merges well: field-level updates instead of whole-document rewrites, maps keyed by ID instead of arrays, and upserts that skip unchanged values. Assert the local semantics your code relies on, so a refactoring that switches to a whole-document write or an array is caught.\n\n**✅ DO** assert that:\n- Adding a line item with `INSERT ... ON ID CONFLICT DO UPDATE_LOCAL_DIFF` of a partial document keeps the existing items, and `items` is a map, not an array.\n- Re-upserting unchanged data with `DO UPDATE_LOCAL_DIFF` returns no mutated document IDs.\n- A function that replaces an object (`UNSET` then `SET` in one transaction) removes keys that are not in the new object; a plain `SET` would keep them, because objects merge under the default settings.\n- Statements use the same type declarations (`REGISTER`, `COUNTER`, `ATTACHMENT`) and strict mode setting as the app; mixed declarations produce results that look like data loss.\n\n`§ Testing Merge-Sensitive Writes`, `§ CRDT Types and Merge Behavior`, `§ Arrays and Maps`, `§ Keep type declarations consistent` · Examples: [reference\u002Flocal-store-tests.md](reference\u002Flocal-store-tests.md#testing-merge-sensitive-writes)\n\n### 7. Validate every app statement with EXPLAIN (HIGH)\n\nKeep the app's DQL statements as constants in one place (for example a repository class) and run each through `EXPLAIN` with representative arguments. `EXPLAIN` parses and plans without executing, so the test catches syntax errors and statements that cannot be planned without touching data.\n\n**✅ DO**: For hot queries, create the indexes with the app's startup code and assert that the plan contains `indexScan`:\n\n```sql\n-- Expected: the plan contains \"indexScan\"\n-- Arguments: {\"status\": \"open\"}\nEXPLAIN SELECT * FROM orders WHERE status = :status ORDER BY createdAt DESC\n```\n\nA later change to the query or to strict mode (which disables index scans in 5.1.0 and 5.2.0) then fails the test. During development, `ADVISE` (SDK 5.1+) suggests the indexes these statements need.\n\n`§ Validating DQL in Tests`, `§ EXPLAIN and PROFILE`, `§ Strict Mode`, `§ ADVISE (SDK 5.1+)` · Example: [reference\u002Flocal-store-tests.md](reference\u002Flocal-store-tests.md#validating-dql-in-tests)\n\n### 8. Test observer delivery and cleanup (HIGH)\n\nTest that observers deliver updates, and that your cleanup code releases them: run the screen's cleanup, then assert that the observer reports itself as cancelled (in Flutter, cancel both the stream subscription and the observer).\n\n**✅ DO**: Wait for a specific result with a timeout (for example 5 seconds).\n**❌ DON'T**: Use a fixed delay, or assert on the number of callbacks; it is not guaranteed.\n\nFor UI tests of screens that own observers, keep the observer behind your own interface and inject a stream or fake you control. The differs only accept items produced by Ditto, so code that uses one needs a real store.\n\n`§ Testing Observer Lifecycle`, `§ Observer Lifecycle`, `§ Diffing Results` · Example: [reference\u002Flocal-store-tests.md](reference\u002Flocal-store-tests.md#testing-observer-lifecycle)\n\n### 9. Register every app subscription in a test (HIGH)\n\n`registerSubscription` validates the query when it is called, even before sync starts. It fails for projections, aggregates, `DISTINCT`, `GROUP BY`, `JOIN`, `USE IDS`, and (while `DQL_RESTRICT_SUBSCRIPTIONS` has its default `true`) `ORDER BY` and `LIMIT`. `WHERE` filters are allowed.\n\n**✅ DO**: Register the app's real subscriptions, then cancel them and assert that they are cancelled; this catches invalid subscription queries without a network. Optionally assert that forbidden forms fail to register:\n\n\u003C!-- subscription expect-error -->\n```sql\n-- Expected: registering this subscription fails\nSELECT _id, status FROM orders\n```\n\n`§ Testing Subscription Rules`, `§ Subscription Rules` · Examples: [reference\u002Flocal-store-tests.md](reference\u002Flocal-store-tests.md#testing-subscription-rules)\n\n### 10. Test merges with several peers in one process (HIGH)\n\nConcurrent edits, merges, deletion propagation, multi-hop relay, and attachment fetching across peers need sync between real instances. Most of these tests do not need physical devices: several instances in one test process can sync over TCP on `127.0.0.1`.\n\n- Each peer has its own persistence directory; all peers of a group share one Database ID. Give each group in a test, and each test process that can run at the same time, its own port and Database ID: a second hub listened on a port already in use without an error, and reconnecting clients moved to it and mixed the groups (SDK 5.1.0 and 5.2.0).\n- Disable peer-to-peer transports; peer 0 listens on `127.0.0.1`, the others connect to it (and listen on `127.0.0.1` only).\n- Small-peers-only sync needs an **offline license token**: starting sync fails until `setOfflineOnlyLicenseToken` has set a valid token. Without one, tests are limited to a single local store.\n- **✅ DO**: Make the concurrent writes while the peers are disconnected (stop sync on every peer, write, reconnect), then wait with a timeout until every peer returns the same rows.\n- **❌ DON'T**: Write while the peers are connected and call it a merge test; the writes reach the other peers within milliseconds.\n- A listening peer accepted at most 6 TCP connections by default in testing with SDK 5.1.0 and 5.2.0 (`MESH_CHOOSER_MAX_WLAN_CONNECTIONS`), so keep the peer count at 7 or below. The Flutter multi-peer test file was verified with `ditto_live` 5.1.0 and has not yet been run with 5.2.0. For a relay test, chain the peers: A listens, B listens and connects to A, C connects to B.\n\n`§ Testing on Multiple Devices`, `§ Several peers in one test process`, `§ Transport Configuration` · Helpers and test: `multi-peer-test` in `examples\u002F\u003Csdk>\u002F`, [reference\u002Fmulti-peer-tests.md](reference\u002Fmulti-peer-tests.md)\n\n### 11. Keep what one process cannot simulate on real devices (MEDIUM)\n\nThe in-process setup shares one clock (no clock skew) and exercises only TCP. Bluetooth LE, P2P Wi-Fi, LAN discovery, permissions, app lifecycle, and Bluetooth LE as the only transport need real devices. Two iOS simulators on one Mac can sync over AWDL, which checks the app's transport configuration but not AWDL over the air (`§ Two iOS simulators synced over AWDL alone`).\n\n- **Sync through Ditto Server** needs a Ditto Server test database. Use separate databases (apps) for development, staging, and production, or one per developer; developers who share one can prefix collection names. There is no Ditto Server mock for CI.\n- **Offline scenarios:** to simulate a Ditto Server outage while keeping peer-to-peer sync, let the app authenticate first, then block the Ditto Server host at the network level; blocking it before authentication makes sync fail entirely.\n\nScenarios worth covering: different fields and the same field edited offline on two devices; map (and, for comparison, array) entries added, edited, and removed on two devices; delete racing an update (expect a husk document), also with soft delete; a counter recounted with `RESTART WITH` while another device increments it; a device behind a relay with narrower subscriptions; a soft delete that reaches a device only through a relay; attachments created offline and fetched later.\n\n`§ Testing on Multiple Devices`, `§ Husk documents`, `§ RESTART`, `§ Multi-hop relay`, `§ Availability` · [reference\u002Fmulti-peer-tests.md](reference\u002Fmulti-peer-tests.md#several-peers-in-one-test-process)\n\n### 12. Keep license tokens out of source control (MEDIUM)\n\nPass the offline license token to tests from a CI secret, for example an environment variable or, in Flutter, `--dart-define-from-file=license.json` with an uncommitted `license.json`. Skip multi-peer tests when the token is empty, so local store tests still run in CI without it.\n\n`§ Testing on Multiple Devices`, `§ Several peers in one test process`\n\n## Checklist\n\n- [ ] Ditto is behind a repository interface; UI and business logic tests use a mock\n- [ ] Each Ditto test opens its own temporary persistence directory and closes it when the test ends (Flutter 5.2.0: after the file's last test)\n- [ ] Local store tests never start sync\n- [ ] Tests apply the app's system parameters and indexes\n- [ ] Ditto tests run where the SDK runs (Flutter `integration_test`, Node.js, instrumented tests, Swift Testing or XCTest)\n- [ ] Every app statement passes `EXPLAIN`; hot queries assert `indexScan`\n- [ ] Filters are tested with the flag `true`, `false`, `null`, and missing\n- [ ] Deletions assert the mutated document IDs and the remaining rows\n- [ ] Merge-sensitive writes assert map shape, no-op re-upserts, and object replacement\n- [ ] Observer tests wait with a timeout and assert that cleanup released the observer\n- [ ] Every app subscription is registered and cancelled in a test\n- [ ] Multi-peer tests write while disconnected and wait for convergence\n- [ ] The license token comes from a CI secret; tests skip without it\n- [ ] Transports, permissions, and clock skew are covered on real devices\n\n## More\n\n- Reference: [reference\u002Ftest-helpers.md](reference\u002Ftest-helpers.md) - what the local store helper does, and where tests run\n- Reference: [reference\u002Flocal-store-tests.md](reference\u002Flocal-store-tests.md) - tests for merge-sensitive writes, deletion and soft delete, observers, subscriptions, and `EXPLAIN`, as statements and expected results\n- Reference: [reference\u002Fmulti-peer-tests.md](reference\u002Fmulti-peer-tests.md) - multi-peer helpers, a concurrent-edit test, limits, and scenarios\n- Examples: `examples\u002F\u003Csdk>\u002F` - `local-store-test-helper`, `local-store-tests`, `multi-peer-test`\n- Related skills: `query-sync` (DQL, MISSING vs NULL, subscription rules, observers), `data-modeling` (merge behavior, maps vs arrays, type declarations), `storage-lifecycle` (DELETE, soft delete, EVICT, tombstones), `sdk-setup` (DittoConfig, license token, transports), `audit` (reviewing a Ditto codebase)\n",{"name":5,"description":7},"main","b67d1a0bc891c9b2a952f8beb0a6dce7d449717c","plugins\u002Fditto\u002Fskills\u002Ftesting","https:\u002F\u002Fgithub.com\u002Fditto-examples\u002Fditto-cookbook","https:\u002F\u002Fdocs.ditto.live\u002Fhome\u002Fintroduction","https:\u002F\u002Fgithub.com\u002Fditto-examples","https:\u002F\u002Fdocs.ditto.live","Example Apps and Information for Ditto","2026-11-13 17:08:48",{"source":8,"skillName":5,"commands":58,"prompt":68,"zipAvailable":69},[59,62,65],{"tool":60,"command":61},"npx","npx skills add ditto-examples\u002Fditto-cookbook --skill testing",{"tool":63,"command":64},"bunx","bunx skills add ditto-examples\u002Fditto-cookbook --skill testing",{"tool":66,"command":67},"pnpm","pnpm dlx skills add ditto-examples\u002Fditto-cookbook --skill testing","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fditto-examples\u002Fditto-cookbook\u002Fblob\u002Fmain\u002Fplugins\u002Fditto\u002Fskills\u002Ftesting\u002FSKILL.md",true,[71,77,81,85,91,95,99,103,107,111,115,119,123,129,133],{"path":72,"resourceType":73,"resourceTypeDesc":74,"sizeBytes":75,"sourceHtmlUrl":76},"examples\u002Fflutter\u002Flocal-store-test-helper.dart","OTHER","其他",2916,"https:\u002F\u002Fgithub.com\u002Fditto-examples\u002Fditto-cookbook\u002Fblob\u002Fmain\u002Fplugins\u002Fditto\u002Fskills\u002Ftesting\u002Fexamples\u002Fflutter\u002Flocal-store-test-helper.dart",{"path":78,"resourceType":73,"resourceTypeDesc":74,"sizeBytes":79,"sourceHtmlUrl":80},"examples\u002Fflutter\u002Flocal-store-tests.dart",10188,"https:\u002F\u002Fgithub.com\u002Fditto-examples\u002Fditto-cookbook\u002Fblob\u002Fmain\u002Fplugins\u002Fditto\u002Fskills\u002Ftesting\u002Fexamples\u002Fflutter\u002Flocal-store-tests.dart",{"path":82,"resourceType":73,"resourceTypeDesc":74,"sizeBytes":83,"sourceHtmlUrl":84},"examples\u002Fflutter\u002Fmulti-peer-test.dart",7242,"https:\u002F\u002Fgithub.com\u002Fditto-examples\u002Fditto-cookbook\u002Fblob\u002Fmain\u002Fplugins\u002Fditto\u002Fskills\u002Ftesting\u002Fexamples\u002Fflutter\u002Fmulti-peer-test.dart",{"path":86,"resourceType":87,"resourceTypeDesc":88,"sizeBytes":89,"sourceHtmlUrl":90},"examples\u002Fjavascript\u002Flocal-store-test-helper.js","SCRIPT","脚本",2217,"https:\u002F\u002Fgithub.com\u002Fditto-examples\u002Fditto-cookbook\u002Fblob\u002Fmain\u002Fplugins\u002Fditto\u002Fskills\u002Ftesting\u002Fexamples\u002Fjavascript\u002Flocal-store-test-helper.js",{"path":92,"resourceType":87,"resourceTypeDesc":88,"sizeBytes":93,"sourceHtmlUrl":94},"examples\u002Fjavascript\u002Flocal-store-tests.js",8093,"https:\u002F\u002Fgithub.com\u002Fditto-examples\u002Fditto-cookbook\u002Fblob\u002Fmain\u002Fplugins\u002Fditto\u002Fskills\u002Ftesting\u002Fexamples\u002Fjavascript\u002Flocal-store-tests.js",{"path":96,"resourceType":87,"resourceTypeDesc":88,"sizeBytes":97,"sourceHtmlUrl":98},"examples\u002Fjavascript\u002Fmulti-peer-test.js",5508,"https:\u002F\u002Fgithub.com\u002Fditto-examples\u002Fditto-cookbook\u002Fblob\u002Fmain\u002Fplugins\u002Fditto\u002Fskills\u002Ftesting\u002Fexamples\u002Fjavascript\u002Fmulti-peer-test.js",{"path":100,"resourceType":73,"resourceTypeDesc":74,"sizeBytes":101,"sourceHtmlUrl":102},"examples\u002Fkotlin\u002Flocal-store-test-helper.kt",3050,"https:\u002F\u002Fgithub.com\u002Fditto-examples\u002Fditto-cookbook\u002Fblob\u002Fmain\u002Fplugins\u002Fditto\u002Fskills\u002Ftesting\u002Fexamples\u002Fkotlin\u002Flocal-store-test-helper.kt",{"path":104,"resourceType":73,"resourceTypeDesc":74,"sizeBytes":105,"sourceHtmlUrl":106},"examples\u002Fkotlin\u002Flocal-store-tests.kt",7992,"https:\u002F\u002Fgithub.com\u002Fditto-examples\u002Fditto-cookbook\u002Fblob\u002Fmain\u002Fplugins\u002Fditto\u002Fskills\u002Ftesting\u002Fexamples\u002Fkotlin\u002Flocal-store-tests.kt",{"path":108,"resourceType":73,"resourceTypeDesc":74,"sizeBytes":109,"sourceHtmlUrl":110},"examples\u002Fkotlin\u002Fmulti-peer-test.kt",6243,"https:\u002F\u002Fgithub.com\u002Fditto-examples\u002Fditto-cookbook\u002Fblob\u002Fmain\u002Fplugins\u002Fditto\u002Fskills\u002Ftesting\u002Fexamples\u002Fkotlin\u002Fmulti-peer-test.kt",{"path":112,"resourceType":73,"resourceTypeDesc":74,"sizeBytes":113,"sourceHtmlUrl":114},"examples\u002Fswift\u002Flocal-store-test-helper.swift",2534,"https:\u002F\u002Fgithub.com\u002Fditto-examples\u002Fditto-cookbook\u002Fblob\u002Fmain\u002Fplugins\u002Fditto\u002Fskills\u002Ftesting\u002Fexamples\u002Fswift\u002Flocal-store-test-helper.swift",{"path":116,"resourceType":73,"resourceTypeDesc":74,"sizeBytes":117,"sourceHtmlUrl":118},"examples\u002Fswift\u002Flocal-store-tests.swift",10005,"https:\u002F\u002Fgithub.com\u002Fditto-examples\u002Fditto-cookbook\u002Fblob\u002Fmain\u002Fplugins\u002Fditto\u002Fskills\u002Ftesting\u002Fexamples\u002Fswift\u002Flocal-store-tests.swift",{"path":120,"resourceType":73,"resourceTypeDesc":74,"sizeBytes":121,"sourceHtmlUrl":122},"examples\u002Fswift\u002Fmulti-peer-test.swift",6747,"https:\u002F\u002Fgithub.com\u002Fditto-examples\u002Fditto-cookbook\u002Fblob\u002Fmain\u002Fplugins\u002Fditto\u002Fskills\u002Ftesting\u002Fexamples\u002Fswift\u002Fmulti-peer-test.swift",{"path":124,"resourceType":125,"resourceTypeDesc":126,"sizeBytes":127,"sourceHtmlUrl":128},"reference\u002Flocal-store-tests.md","REFERENCE","参考文档",9232,"https:\u002F\u002Fgithub.com\u002Fditto-examples\u002Fditto-cookbook\u002Fblob\u002Fmain\u002Fplugins\u002Fditto\u002Fskills\u002Ftesting\u002Freference\u002Flocal-store-tests.md",{"path":130,"resourceType":125,"resourceTypeDesc":126,"sizeBytes":131,"sourceHtmlUrl":132},"reference\u002Fmulti-peer-tests.md",8164,"https:\u002F\u002Fgithub.com\u002Fditto-examples\u002Fditto-cookbook\u002Fblob\u002Fmain\u002Fplugins\u002Fditto\u002Fskills\u002Ftesting\u002Freference\u002Fmulti-peer-tests.md",{"path":134,"resourceType":125,"resourceTypeDesc":126,"sizeBytes":135,"sourceHtmlUrl":136},"reference\u002Ftest-helpers.md",2157,"https:\u002F\u002Fgithub.com\u002Fditto-examples\u002Fditto-cookbook\u002Fblob\u002Fmain\u002Fplugins\u002Fditto\u002Fskills\u002Ftesting\u002Freference\u002Ftest-helpers.md",[138,148,169,178,187,197],{"skillNo":139,"name":140,"nameZh":6,"description":141,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":142,"categories":143,"keywords":144,"publishedAt":43},"01M6C58EAD92WQKNE74549PXEW","transactions-attachments","Ditto SDK transactions and attachments (Flutter, JavaScript, Kotlin, Swift): ditto.store.transaction with tx.execute only, no nesting, short scopes, rollback, close(); newAttachment, ATTACHMENT declarations, lazy fetchAttachment with a stall timeout, immutability, thumbnails, and blob availability. Use when writing or reviewing ditto.store.transaction, newAttachment, or fetchAttachment code, or when displaying photos or files stored in Ditto.","https:\u002F\u002Fgithub.com\u002Fditto-examples\u002Fditto-cookbook\u002Fblob\u002Fmain\u002Fplugins\u002Fditto\u002Fskills\u002Ftransactions-attachments\u002FSKILL.md",[],[145,146,147],"transactions attachments","attachments","transactions",{"skillNo":149,"name":150,"nameZh":6,"description":151,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":152,"categories":153,"keywords":154,"publishedAt":43},"01M6C587HJC1SVAPYEZHJBV1C7","storage-lifecycle","Ditto SDK data removal and local storage management covering DELETE vs soft delete vs EVICT, tombstones and their TTL, husk documents, eviction with complementary subscriptions, retention cleanup, and storage monitoring. Use when writing Ditto DELETE or EVICT statements, soft-delete flags or retention cleanup for Ditto documents, or TOMBSTONE_TTL_HOURS, or when deleted Ditto data came back or evicted documents sync back.","https:\u002F\u002Fgithub.com\u002Fditto-examples\u002Fditto-cookbook\u002Fblob\u002Fmain\u002Fplugins\u002Fditto\u002Fskills\u002Fstorage-lifecycle\u002FSKILL.md",[],[41,155,156,24,157,39,158,159,160,161,162,163,34,164,165,28,166,167,36,168],"storage","storage lifecycle","cleanup","use","documents","monitoring","back","evict","retention","with","subscriptions","ttl","ditto","sdk",{"skillNo":170,"name":171,"nameZh":6,"description":172,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":173,"categories":174,"keywords":175,"publishedAt":43},"01M6C5845MAD3AEBNSN3BT61WH","sdk-setup","Ditto SDK setup, lifecycle, and production security (Flutter, JavaScript, Kotlin, Swift): DittoConfig, the authentication expiration handler, system parameters, sync start and stop, close, transports, presence, keys, permissions, revocation. Use when initializing Ditto, writing Ditto login, startup, shutdown, or background code, configuring Ditto transports, reviewing Ditto security, or when Ditto sync never starts or devices do not connect.","https:\u002F\u002Fgithub.com\u002Fditto-examples\u002Fditto-cookbook\u002Fblob\u002Fmain\u002Fplugins\u002Fditto\u002Fskills\u002Fsdk-setup\u002FSKILL.md",[],[168,176,177],"sdk setup","setup",{"skillNo":179,"name":180,"nameZh":6,"description":181,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":182,"categories":183,"keywords":184,"publishedAt":43},"01M6C580SCSJK0E5Z54AJTV242","query-sync","Ditto SDK DQL reads and writes, subscriptions, and store observers (Flutter, JavaScript, Kotlin, Swift): query parameters, MISSING vs NULL, membership filters, subscription and observer lifecycle, ON ID CONFLICT upserts, JOIN, RETURNING. Use when writing or reviewing ditto.store.execute, ditto.sync.registerSubscription, registerObserver, or DQL in an app that uses Ditto, or when Ditto data does not sync or a Ditto query returns nothing.","https:\u002F\u002Fgithub.com\u002Fditto-examples\u002Fditto-cookbook\u002Fblob\u002Fmain\u002Fplugins\u002Fditto\u002Fskills\u002Fquery-sync\u002FSKILL.md",[],[185,186,34],"query","query sync",{"skillNo":188,"name":189,"nameZh":6,"description":190,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":191,"categories":192,"keywords":193,"publishedAt":43},"01M6C57X4C3TQSWZJC882ECQV2","performance-observability","Ditto SDK store observer performance and diagnostics (Flutter, JavaScript, Kotlin, Swift): observer lifecycle, backpressure (registerObserverV2, signalNext, suspending handlers), narrow UI updates, differs, redundant writes, indexes, ADVISE, EXPLAIN, PROFILE, logging, and system: collections. Use when a Ditto query is slow, a screen showing Ditto data janks, Ditto observers grow memory, Ditto upserts repeat, or when setting up Ditto logging.","https:\u002F\u002Fgithub.com\u002Fditto-examples\u002Fditto-cookbook\u002Fblob\u002Fmain\u002Fplugins\u002Fditto\u002Fskills\u002Fperformance-observability\u002FSKILL.md",[],[194,195,196],"performance observability","performance","observability",{"skillNo":198,"name":199,"nameZh":6,"description":200,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":201,"categories":202,"keywords":203,"publishedAt":43},"01M6C57V1EZKA9B5W1G4F00HAH","guide","Searches the complete Ditto SDK best practices guide (SDK 5.2.0, Flutter, JavaScript, Kotlin, Swift) bundled with this plugin, for answers the focused ditto skills do not cover. Use to look up a Ditto API, DQL keyword or function, system parameter, error message, an SDK's API or behavior, or the reasoning behind a rule, or when a Ditto question spans several topics.","https:\u002F\u002Fgithub.com\u002Fditto-examples\u002Fditto-cookbook\u002Fblob\u002Fmain\u002Fplugins\u002Fditto\u002Fskills\u002Fguide\u002FSKILL.md",[],[199,40,204,205,206,28,207,208,209,167,25,210,211,212,213,164,214,215,158,216],"system","plugin","do","javascript","topics","kotlin","complete","message","flutter","error","api","best practices","question"]