| @@ -0,0 +1,196 @@ |
| | 1 | # LiveStore Effect Testing Patterns Worth Porting |
| | 2 | |
| | 3 | LiveStore is useful as a reference repo for Effect-heavy test ergonomics, not |
| | 4 | as a wholesale tooling model. Executor already has the stricter Effect lint |
| | 5 | posture and a simpler Bun/Turbo toolchain. The part worth copying is the small |
| | 6 | test harness shape that makes scoped Effect tests easier to write and easier to |
| | 7 | debug when they hang. |
| | 8 | |
| | 9 | Reference checkout: |
| | 10 | |
| | 11 | ```txt |
| | 12 | .reference/livestore |
| | 13 | ``` |
| | 14 | |
| | 15 | ## What to Port |
| | 16 | |
| | 17 | ### Effect test context helper |
| | 18 | |
| | 19 | LiveStore's `packages/@livestore/utils-dev/src/node-vitest/Vitest.ts` wraps |
| | 20 | `@effect/vitest` with: |
| | 21 | |
| | 22 | - `withTestCtx(test)` for per-test Effect setup. |
| | 23 | - `makeWithTestCtx(...)` for reusable layer/timeout config. |
| | 24 | - A default timeout, longer in CI. |
| | 25 | - `Effect.logWarnIfTakesLongerThan` before the timeout trips. |
| | 26 | - `Effect.timeout(...)`. |
| | 27 | - `Effect.provide(...)` for per-test layers. |
| | 28 | - `Effect.scoped` so finalizers and spans close predictably. |
| | 29 | - Optional tracing/log layer wiring. |
| | 30 | |
| | 31 | Executor should port the shape, not the exact implementation. LiveStore is on |
| | 32 | Effect 3, while Executor is on Effect 4 beta. The local helper should be tiny |
| | 33 | and typed against the current `@effect/vitest` and `effect` APIs. |
| | 34 | |
| | 35 | Likely home: |
| | 36 | |
| | 37 | ```txt |
| | 38 | packages/core/sdk/src/testing.ts |
| | 39 | ``` |
| | 40 | |
| | 41 | If the helper starts pulling in cloud-specific, Node-server, or plugin-specific |
| | 42 | dependencies, stop and keep those helpers package-local instead. |
| | 43 | |
| | 44 | ### Timeout diagnostics for slow Effect tests |
| | 45 | |
| | 46 | Several Executor tests already have manual sleeps, `Promise.race`, test-level |
| | 47 | timeouts, or long-running fixtures. A `withTestCtx`-style helper would make |
| | 48 | failures more readable by logging before timeout rather than only surfacing a |
| | 49 | late Vitest timeout. |
| | 50 | |
| | 51 | Good initial targets: |
| | 52 | |
| | 53 | - `apps/cloud/src/mcp-miniflare.e2e.node.test.ts` |
| | 54 | - `apps/cloud/src/mcp-session.e2e.node.test.ts` |
| | 55 | - `packages/plugins/openapi/src/sdk/oauth-refresh.test.ts` |
| | 56 | - `packages/plugins/mcp/src/sdk/connection-pool.test.ts` |
| | 57 | - `packages/core/execution/src/tool-invoker.test.ts` |
| | 58 | |
| | 59 | The first slice should convert only one painful test cluster. If it makes the |
| | 60 | test body clearer and failure output better, then expand. |
| | 61 | |
| | 62 | ### Scoped fixture/runtime pattern |
| | 63 | |
| | 64 | LiveStore's `tests/sync-provider/src/sync-provider.test.ts` builds a shared |
| | 65 | `ManagedRuntime` in `beforeAll`, disposes it in `afterAll`, then creates |
| | 66 | per-test providers with isolated ids. That pattern is a good fit for expensive |
| | 67 | Executor integration fixtures: |
| | 68 | |
| | 69 | - local HTTP protocol servers, |
| | 70 | - MCP servers, |
| | 71 | - OAuth mock servers, |
| | 72 | - Miniflare environments, |
| | 73 | - database-backed cloud services, |
| | 74 | - plugin SDK tests that need realistic server behavior. |
| | 75 | |
| | 76 | Executor already uses `layer(TestLayer)(...)` in some OpenAPI and cloud tests. |
| | 77 | Keep that where it works. Use the LiveStore runtime pattern only where the |
| | 78 | fixture is expensive enough that rebuilding it per test is wasteful or flaky. |
| | 79 | |
| | 80 | ### Property-test wrapper later |
| | 81 | |
| | 82 | LiveStore's `asProp` wrapper normalizes FastCheck options and makes shrinking |
| | 83 | progress clearer. Do not port it preemptively. |
| | 84 | |
| | 85 | It becomes useful if Executor adds property tests for: |
| | 86 | |
| | 87 | - OpenAPI parameter encoding, |
| | 88 | - form/multipart request bodies, |
| | 89 | - schema round-trips, |
| | 90 | - scope ordering and shadowing, |
| | 91 | - tool/source dedupe, |
| | 92 | - storage adapter conformance. |
| | 93 | |
| | 94 | Until then, direct `@effect/vitest` property tests are enough. |
| | 95 | |
| | 96 | ### Possibly scoped React perf lint |
| | 97 | |
| | 98 | LiveStore enables `react-perf` oxlint rules for JSX props: |
| | 99 | |
| | 100 | - no new functions as props, |
| | 101 | - no new objects as props, |
| | 102 | - no JSX as props, |
| | 103 | - no new arrays as props. |
| | 104 | |
| | 105 | This might be useful in `packages/react`, but it should start as a scoped |
| | 106 | experiment. Do not enable it repo-wide. The risk is turning useful UI work into |
| | 107 | memoization churn. |
| | 108 | |
| | 109 | ## What Not to Port |
| | 110 | |
| | 111 | ### Biome |
| | 112 | |
| | 113 | LiveStore uses Biome for formatting/import organization plus oxlint for linting. |
| | 114 | Executor already uses `oxfmt` and oxlint: |
| | 115 | |
| | 116 | ```txt |
| | 117 | bun run format |
| | 118 | bun run format:check |
| | 119 | bun run lint |
| | 120 | ``` |
| | 121 | |
| | 122 | Adding Biome would split formatter ownership without solving an Executor |
| | 123 | problem. |
| | 124 | |
| | 125 | ### devenv / genie / effect-utils repo generation |
| | 126 | |
| | 127 | LiveStore's config generation and `devenv` task setup are substantial |
| | 128 | infrastructure. Executor's Bun/Turbo setup is smaller and easier to reason |
| | 129 | about. Do not port that unless there is a concrete repo-management problem that |
| | 130 | cannot be solved locally. |
| | 131 | |
| | 132 | ### LiveStore's lint rules wholesale |
| | 133 | |
| | 134 | Executor's lint posture is already stronger and more domain-specific. Executor |
| | 135 | currently bans or guides: |
| | 136 | |
| | 137 | - raw Vitest imports, |
| | 138 | - conditional tests, |
| | 139 | - double casts, |
| | 140 | - cross-package relative imports, |
| | 141 | - missing effect-atom reactivity keys, |
| | 142 | - Effect escape hatches, |
| | 143 | - unsupported Effect APIs, |
| | 144 | - `new Error`, |
| | 145 | - `try`/`catch` and raw `throw`, |
| | 146 | - `Promise.catch` / `Promise.reject`, |
| | 147 | - raw fetch outside approved boundaries, |
| | 148 | - manual tagged-error checks, |
| | 149 | - duplicated schema/value-derived types. |
| | 150 | |
| | 151 | LiveStore disables several things Executor intentionally cares about, including |
| | 152 | some broad TypeScript strictness. Their rule set should stay reference-only. |
| | 153 | |
| | 154 | ### An Effect barrel module |
| | 155 | |
| | 156 | LiveStore centralizes Effect exports through `@livestore/utils/effect`. |
| | 157 | Executor mostly imports from `effect` directly. Do not introduce a broad |
| | 158 | `@executor-js/.../effect` barrel just for symmetry. It would add another import |
| | 159 | surface without a concrete need. |
| | 160 | |
| | 161 | ## First Implementation Slice |
| | 162 | |
| | 163 | Add a tiny test helper, then use it in one test cluster. |
| | 164 | |
| | 165 | Possible local API: |
| | 166 | |
| | 167 | ```ts |
| | 168 | export const withTestCtx = |
| | 169 | (test: TestContext, options?: TestContextOptions) => |
| | 170 | <A, E, R>(effect: Effect.Effect<A, E, R>) => |
| | 171 | effect.pipe( |
| | 172 | Effect.logWarnIfTakesLongerThan({ |
| | 173 | duration: "...", |
| | 174 | label: "...", |
| | 175 | }), |
| | 176 | Effect.timeout("..."), |
| | 177 | Effect.provide(options?.layer ?? Layer.empty), |
| | 178 | Effect.scoped, |
| | 179 | ); |
| | 180 | ``` |
| | 181 | |
| | 182 | Keep the helper small: |
| | 183 | |
| | 184 | - no tracing dependency in the first pass, |
| | 185 | - no generic framework package, |
| | 186 | - no large fixture registry, |
| | 187 | - no migration of every test. |
| | 188 | |
| | 189 | Validation should be narrow: |
| | 190 | |
| | 191 | ```txt |
| | 192 | vitest run <converted test file> --testNamePattern "<converted test>" |
| | 193 | ``` |
| | 194 | |
| | 195 | If the first conversion produces clearer test bodies and better failure output, |
| | 196 | then expand it to the other long-running Effect integration tests. |