{"_id":"@supa-media/testing","_rev":"2-10ea2f9a6c9db1a5b8c034540fb175f2","name":"@supa-media/testing","dist-tags":{"latest":"1.0.2"},"versions":{"1.0.1":{"name":"@supa-media/testing","version":"1.0.1","keywords":["expo","react-native","testing","routing","web-bundle","metro","native-imports"],"license":"MIT","_id":"@supa-media/testing@1.0.1","maintainers":[{"name":"lilseyi","email":"seyi@supa.media"}],"homepage":"https://github.com/Supa-Media/supa-framework#readme","bugs":{"url":"https://github.com/Supa-Media/supa-framework/issues"},"dist":{"shasum":"8a9edb0d0097f4118f433c5a52958addde5a1d06","tarball":"https://registry.npmjs.org/@supa-media/testing/-/testing-1.0.1.tgz","fileCount":23,"integrity":"sha512-8qINisGjxdaLOp9T6g+/OaPNUwY0y2rnS9dbXIvE6Ai/HT1MhXgk6CI9Rrown8FOyeEFqyKUw+6OX4EPDefPIw==","signatures":[{"sig":"MEYCIQDFldz3DJ9jwUJOfQs41ckCPYsfQJ+gYX45FffwRtrHVgIhAIcLXx8MDB5MkSMXt1M8PMUrnJ1JrkCe5M2O0JKhjjig","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":118299},"main":"dist/index.js","_from":"file:supa-media-testing-1.0.1.tgz","types":"dist/index.d.ts","scripts":{"test":"tsc && node --test \"__tests__/*.test.js\"","build":"tsc"},"_npmUser":{"name":"lilseyi","email":"seyi@supa.media"},"_resolved":"/private/var/folders/h_/rr7ynmfd05q_z73ky7wf2w2w0000gn/T/231ada2a19e5895fbea5b950f742918c/supa-media-testing-1.0.1.tgz","_integrity":"sha512-8qINisGjxdaLOp9T6g+/OaPNUwY0y2rnS9dbXIvE6Ai/HT1MhXgk6CI9Rrown8FOyeEFqyKUw+6OX4EPDefPIw==","repository":{"url":"git+https://github.com/Supa-Media/supa-framework.git","type":"git","directory":"packages/testing"},"_npmVersion":"10.9.4","description":"Reusable test suites that catch common Expo/React Native gotchas — routing conflicts, web bundle safety, React resolution, native import gating","directories":{},"_nodeVersion":"22.22.0","publishConfig":{"registry":"https://registry.npmjs.org"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"~5.9.2","@types/node":"^22.0.0"},"_npmOperationalInternal":{"tmp":"tmp/testing_1.0.1_1788154777637_0.5706592799673456","host":"s3://npm-registry-packages-npm-production"}},"1.0.2":{"_id":"@supa-media/testing@1.0.2","bugs":{"url":"https://github.com/Supa-Media/supa-framework/issues"},"dist":{"shasum":"c07732ffbcaf57118206d1caf73b856c3008ea46","tarball":"https://registry.npmjs.org/@supa-media/testing/-/testing-1.0.2.tgz","fileCount":23,"integrity":"sha512-DXch6VIT01wUuCLSHZ3Op01TTJlGp7dt3r4KAuw8vczkE6z9szHfog0sgVeEicX33gwrY0DJ8Z70391r3egoQw==","signatures":[{"sig":"MEQCICNsUaTk6X3z3E/7lv3THzGV3vj//T+qBsJgOOBOGTm2AiASrWzgkn9FM24s7IOnnC7q1D5Jx0LNszzI2O38xK6ewg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIHSZXeJGzaNaL7QboT/TI9L6wlv1VDiLt6pIOiBhEH/8AiABLijq4e1X2S0PQw+zDpuuw5jCcMwHC2y1utS6pINZlA=="}],"unpackedSize":118409},"main":"dist/index.js","name":"@supa-media/testing","_from":"file:supa-media-testing-1.0.2.tgz","types":"dist/index.d.ts","license":"MIT","scripts":{"test":"tsc && node --test \"__tests__/*.test.js\"","build":"tsc"},"version":"1.0.2","_npmUser":{"name":"lilseyi","email":"seyi@supa.media"},"homepage":"https://github.com/Supa-Media/supa-framework#readme","keywords":["expo","react-native","testing","routing","web-bundle","metro","native-imports"],"_resolved":"/tmp/1074150dfe571a308d359633282f93e4/supa-media-testing-1.0.2.tgz","_integrity":"sha512-DXch6VIT01wUuCLSHZ3Op01TTJlGp7dt3r4KAuw8vczkE6z9szHfog0sgVeEicX33gwrY0DJ8Z70391r3egoQw==","repository":{"url":"git+https://github.com/Supa-Media/supa-framework.git","type":"git","directory":"packages/testing"},"_npmVersion":"10.9.8","description":"Reusable test suites that catch common Expo/React Native gotchas — routing conflicts, web bundle safety, React resolution, native import gating","directories":{},"maintainers":[{"name":"lilseyi","email":"seyi@supa.media"}],"_nodeVersion":"22.23.2","publishConfig":{"registry":"https://registry.npmjs.org"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"~5.9.2","@types/node":"^22.0.0"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/testing_1.0.2_1789139428435_0.5476291662743351"}}},"time":{"created":"2026-08-31T05:39:37.415Z","modified":"2026-09-11T15:10:28.710Z","1.0.1":"2026-08-31T05:39:37.782Z","1.0.2":"2026-09-11T15:10:28.512Z"},"bugs":{"url":"https://github.com/Supa-Media/supa-framework/issues"},"license":"MIT","homepage":"https://github.com/Supa-Media/supa-framework#readme","keywords":["expo","react-native","testing","routing","web-bundle","metro","native-imports"],"repository":{"url":"git+https://github.com/Supa-Media/supa-framework.git","type":"git","directory":"packages/testing"},"description":"Reusable test suites that catch common Expo/React Native gotchas — routing conflicts, web bundle safety, React resolution, native import gating","maintainers":[{"name":"lilseyi","email":"seyi@supa.media"}],"readme":"# @supa-media/testing\n\n**Static checks for Expo/React Native gotchas, packaged as functions you call from\nyour own test runner.** No app boot, no simulator, no test framework of its own —\neach check reads files on disk and either returns a result object or throws a\ndescriptive error, so it drops into Jest, Vitest, or `node --test` unchanged.\n\nFour checks: Expo Router routing conflicts, Metro web-bundle safety, React\nresolution across the native graph, and native import gating.\n\n## Install\n\n```\npnpm add -D @supa-media/testing\n```\n\nNo peer dependencies. Ships compiled CommonJS (`dist/`) with type declarations;\nthere is no subpath export map, so everything comes from the package root.\n\n## Wiring it in\n\nThe `create-supa-app` mobile template registers all four with Jest:\n\n```js\nconst path = require(\"path\");\nconst { createSupaTests, detectRoutingConflicts } = require(\"@supa-media/testing\");\n\nconst appDir = path.join(__dirname, \"..\", \"app\");\nconst tests = createSupaTests({\n  appDir,\n  srcDir: path.join(__dirname, \"..\"),\n  nativeDepsPath: \"native-deps.json\",\n});\n\ndescribe(\"Supa framework guardrails\", () => {\n  test(\"no Expo Router URL conflicts\", () => {\n    const { conflicts } = detectRoutingConflicts(appDir);\n    if (conflicts.length > 0) throw new Error(/* … */);\n  });\n  test(\"web bundle safety\", tests.webBundleSafety);\n  test(\"single React instance resolves\", tests.reactResolution);\n  test(\"native deps classified + no ungated native imports\", tests.nativeImports);\n});\n```\n\nNote what that template does **not** do: it calls `detectRoutingConflicts`\ndirectly rather than `tests.routingConflicts`, because the bundled test function\nalso throws on missing `_layout.tsx` files — a softer heuristic that\nfalse-positives on valid nested stacks. Asserting on `conflicts` alone is the\nnarrower signal.\n\n## Surface\n\nEvery check comes in two forms: a `check*`/`detect*` function returning a result\nobject, and a `test*` function that throws a formatted error instead. The `test*`\nform is the one to hand to `test()`.\n\n| Check | Inspect | Assert |\n| --- | --- | --- |\n| Routing conflicts | `detectRoutingConflicts(appDir)` | `testRoutingConflicts(appDir)` |\n| Web bundle safety | `checkWebBundleSafety(config)` | `testWebBundleSafety(config)` |\n| React resolution | `checkReactResolution(projectRoot, options?)` | `testReactResolution(projectRoot, options?)` |\n| Native imports | `checkNativeImports(config)` | `testNativeImports(config)` |\n\nPlus two aggregators:\n\n- **`createSupaTests(config)`** → `{ routingConflicts, webBundleSafety, reactResolution, nativeImports }`, four zero-argument functions with paths already resolved. Config: `appDir`, `srcDir` (both required), `nativeDepsPath`, `nativeImportAllowlist`, `storesDir`, `providersDir`, `nativeOnlyPatterns`, `reactNativePackages`.\n- **`runAllSupaTests(config)`** → `{ passed: string[], failed: [{ name, error }] }`. Never throws — for a CI script that wants every failure in one pass before exiting.\n\nResult and config types are exported alongside (`RoutingConflictResult`,\n`WebBundleSafetyConfig`, `ReactResolutionResult`, `NativeImportCheckResult`, …).\n\n### What each check looks for\n\n**Routing conflicts** — Expo Router's route groups (`(user)`, `(admin)`) do not\naffect the URL, so `app/(user)/settings/index.tsx` and\n`app/(admin)/settings/index.tsx` both resolve to `/settings`. Reports multiple\nfiles resolving to one URL, and a static route colliding with a dynamic route at\nthe same depth. Single-line `export { default } from …` re-exports are treated as\nintentional duplicates and excluded; a redirect-only file is **not** excluded,\nbecause a `<Redirect>` sharing a URL with a real screen silently shadows it.\nMissing `_layout.tsx` directories are reported separately in `missingLayouts`.\n\n**Web bundle safety** — Metro serves web bundles as plain `<script>`, so\n`import.meta` is undefined and Zustand v5's `import.meta.env.MODE` crashes the\nbundle. Checks that every Zustand store has a `.web.ts` counterpart, that the\ncounterpart does not itself import Zustand, that its exported names cover the\nnative file's, that native-only providers have `.web.tsx` counterparts, and that\nno `.web.*` file uses `import.meta`. Defaults are Supa-shaped and configurable:\n`storesDir: \"stores\"`, `providersDir: \"providers\"`, `additionalWebDirs:\n[\"components\"]`, `nativeOnlyPatterns: [/useConvexConnectionState/]` — that last\none decides which providers count as native-only, so override it for your app.\n\n**React resolution** — resolves `react` from the app directory and from each\nnative package's own directory (`react-native`, `expo-modules-core`,\n`react-native-web` by default, via `reactNativePackages`) and requires them all to\nbe the same **version**; also scans the pnpm virtual store for distinct `react@*`\nentries and checks `react`/`react-dom` agree on major.minor. A native package\nkeyed to a second React re-keys the Expo native-module graph and breaks Fabric\nregistration on the installed binary — native video and animated GIFs render blank\nwhile typecheck, tests, and the JS bundle all pass. Comparing versions rather than\npaths is what makes it correct under both pnpm linkers: under\n`node-linker=hoisted` one React is hard-linked to several paths and there is no\napp-local copy at all.\n\n**Native imports** — reads `native-deps.json` (`{ \"core\": [...], \"gated\": [...] }`),\nreports static imports of `gated` packages with file and line, and reports native\ndependencies in `package.json` that are in neither list.\n\n## Constraints worth knowing\n\n> **⚠️ `checkNativeImports` matches gated specifiers exactly; the\n> `@supa-media/native-safety` CLI also resolves subpaths.** `import … from\n> \"expo-av/build/Video\"` is caught by `check-native-imports`, and is **not**\n> caught here. Where both are available, the CLI is the stricter gate.\n\n> **⚠️ `checkNativeImports` throws if `native-deps.json` is missing** — not\n> returns empty. So `createSupaTests(...).nativeImports` fails hard until the file\n> exists, which is intentional but surprising the first time.\n\n> **⚠️ React resolution reads the real `node_modules`.** Run it after\n> `pnpm install`, never against a stale or partial install, or it reports on a\n> layout that does not exist.\n\n> **⚠️ `testRoutingConflicts` throws on `missingLayouts` as well as conflicts.**\n> If nested stacks in your app legitimately share a parent layout, call\n> `detectRoutingConflicts` and assert on `conflicts` yourself.\n\n`ResolvedReactInfo`, `NativePackageReact`, and `ReactResolutionOptions` are\ndeclared in the react-resolution module but are not among the type re-exports on\nthe package root, even though `ReactResolutionResult` references\n`NativePackageReact`. Import positions and shapes accordingly.\n\n## In CI\n\nThe framework's reusable `ci.yml` runs whatever `mobile-test-command` you pass,\nafter a frozen-lockfile install:\n\n```yaml\n    uses: Supa-Media/supa-framework/.github/workflows/ci.yml@v1\n    with:\n      mobile-test-command: pnpm test\n```\n\nwhich reaches these checks through the template's Jest config —\n`{ testEnvironment: \"node\", testMatch: [\"**/__tests__/**/*.test.js\"] }`. The\n`node` environment matters: these are file-system checks, not component tests, and\njsdom buys nothing.\n\n## Test coverage of this package\n\nHonest state: `node --test` here runs **one** suite, `__tests__/react-resolution.test.js`,\nwith three cases — a healthy hoisted install, a healthy isolated install, and a\ndual-React install where `expo-modules-core` is keyed to a second React. Those\nbuild real `node_modules` fixtures on disk, so they are meaningful, and they exist\nbecause the guard's earlier design failed healthy hoisted installs.\n\nThe routing-conflict, web-bundle-safety, and native-import checks have **no tests\nin this package**. They are regex- and filesystem-heuristic based; read the\nbehaviour above as a description of intent, and verify against your own app before\nyou rely on a green result.\n\n---\n\nPart of the **Supa Media framework** — https://github.com/Supa-Media/supa-framework.\nMIT licensed.\n","readmeFilename":"README.md"}