fix: probe MapLibre v11 via package exports, not legacy NativeModules name#1499
Merged
Conversation
Test Results✅ All tests passed
Coverage
⏱️ Duration: 82.9s |
… mock Two test suites still leaned on the old v10 pattern of mutating `NativeModules.MLRNModule` to force the native-availability probe to return false. The v11 probe consults the `@maplibre/maplibre-react-native` package's JS export surface instead, so the old hack is a no-op — the dispatcher happy path ran and the assertions mismatched: - `__tests__/screens/MapScreenDispatcher.test.tsx` asserted that MapUnavailableCard rendered, but the probe saw `Map` on the jest.setup mock and returned true, so the dispatcher mounted `MapScreenNative` behind Suspense instead (ActivityIndicator). Fix: mock `@/utils/isMapNativeAvailable` directly. The card's accessibility label is `heading`, which is `"Map unavailable in Expo Go"` when the reason matches the Expo-Go fingerprint (`Cannot find module`) — so the test now pins both the probe result and the diagnostic reason to reach that heading deterministically. - `__tests__/hooks/useMapTileCache.test.ts` mocked MapLibre with only `OfflineManager`. `useMapTileCache` calls `isMapNativeAvailable()` first, which requires a `Map` export to return true — so the probe short-circuited to false and the hook never touched OfflineManager, breaking every assertion. Fix: add `Map` + `NetworkManager` to the test's local mock so the probe treats the module as linked. Core OfflineManager assertions unchanged. Full suite: 3380 tests pass, tsc clean.
…ors per-test doMock
Two cases in the probe test suite passed in isolation and under the
plain `npm test` harness but failed under the CI command
`npx jest --coverage --ci --verbose --json`:
● returns false when MapLibre package is missing (Expo Go)
● returns false when MapLibre package loads but Map export is absent
Both expected `freshProbe()` to return false; both saw true. The probe
was finding a `Map` export on the module even though each test had a
`jest.doMock` that either threw or omitted `Map`.
Root cause: the file used a top-level hoisted `jest.mock` for
`@maplibre/maplibre-react-native` as the "happy path" factory, plus
`beforeEach(() => { jest.resetModules(); __resetMapNativeProbeForTests(); })`
and `jest.doMock(...)` inside each test. Outside of --coverage this
pattern worked — the test-body `doMock` won. Under --coverage (plus the
global jest.setup.js mock that also registers a `Map` export) the
hoisted setup factory kept resolving first, so the test-body `doMock`
never reached the probe.
Fix: wrap each test body in `jest.isolateModules(() => { … })`.
`isolateModules` creates a scoped module registry AND re-resolves mock
factories for that scope, so the per-test `jest.doMock` is guaranteed
to be the one `require()` sees inside the block. The top-level hoisted
`jest.mock` is removed since each test now declares its own factory
explicitly, and the `beforeEach` reset plumbing is no longer needed —
isolateModules handles both.
Verified under the exact CI command:
npx jest --coverage --ci --verbose --json --outputFile=test-results.json
→ 3380 passed / 460 suites.
This was referenced Apr 17, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Root cause
After the SDK 54 migration (PR #1495) the app launches fine on TestFlight but the Map tab renders the "Map unavailable in Expo Go" fallback instead of the actual map. The TestFlight build is NOT Expo Go — the card copy is misleading, but the underlying issue is real.
The probe in
isMapNativeAvailable.tsopened with a cheap presence check:Under MapLibre v10 on the legacy bridge,
MLRNModuleexisted as a legacy bridge module and this check worked. Under MapLibre v11 + React Native New Architecture, the native module registers viaTurboModuleRegistryasMLRNNetworkModule(see v11 source).NativeModules.MLRNModuleis permanently null because that name never gets registered at all.Net effect: the probe short-circuits to
falseon every call on v11 builds, regardless of whether MapLibre is actually wired up. The map screen always falls back toMapUnavailableCard.Fix
Drop the legacy-bridge presence check. Instead, the probe now proves MapLibre is linked by:
require()-ing the package (catches the Expo Go case — require throws with "Cannot find module")Mapnamed export (catches v10 residue — v10 exportedMapView, v11 exportsMap)NetworkManager.setConnected(true)as a cheap native-touch, but swallowing any throw since this method is Android-only in v11 and will legitimately no-op on iOSThis matches MapLibre v11's actual JS surface rather than assumptions inherited from v10.
Better diagnostics
The previous
MapUnavailableCardhard-coded "Map unavailable in Expo Go" and gated the diagnosticreasonbehind__DEV__. Neither fit TestFlight where the build is NOT Expo Go and__DEV__is false.The card now:
reasonin production when the failure is unexpected — so testers can report what they see instead of us guessingTest changes
__tests__/unit/isMapNativeAvailable.test.tsrewritten to mutate the jest-mocked MapLibre package (viajest.doMock+jest.resetModules()) instead ofNativeModules.MLRNModule. New cases cover:Mapexport present)Mapexport)jest.setup.jshad a manualNativeModules.MLRNModule = {}hack to force map-rendering tests through the old probe's happy path. Removed — the new probe ignoresNativeModulesentirely and relies on the MapLibre jest mock (already present in setup) which exportsMap, so tests flow through the happy path naturally.Verification
After merge:
The map tab should now render the actual map. If something unexpected fails, the card will now tell us what instead of lying about Expo Go.
Files changed
app/src/utils/isMapNativeAvailable.ts— core fixapp/src/components/map/MapUnavailableCard.tsx— UX + diagnosticsapp/__tests__/unit/isMapNativeAvailable.test.ts— rewrittenapp/jest.setup.js— removed stale NativeModules hack