mirror of
https://github.com/stablyai/orca.git
synced 2026-09-24 16:02:41 +00:00
test(mobile): refuse an unlisted native package by the member a mount reads
Both emitted interop helpers short-circuit on `__esModule`, so answering `true` hands the refusing trap back unwrapped to `__importDefault` and `__importStar`. All three import forms now load the importer and throw the named refusal at the first member read, instead of a namespace import silently yielding `undefined` and failing later at the call. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
This commit is contained in:
@@ -54,11 +54,11 @@ describe('the mounted module loader', () => {
|
||||
})
|
||||
|
||||
/**
|
||||
* `__importDefault` reads `__esModule` before any member, so refusing it kills a default import of
|
||||
* an unlisted package at module load — before the recording can show which member was wanted, and
|
||||
* for modules that never touch the package at all.
|
||||
* Both interop helpers short-circuit on `__esModule`, so the trap binds as the module itself in
|
||||
* all three import forms: the module loads, and the refusal lands on the member a recording
|
||||
* actually wanted rather than on every module that merely mentions the package.
|
||||
*/
|
||||
it('defers an unlisted package refusal from module load to the first member read', () => {
|
||||
it('lets a default import of an unlisted package load, and refuses the member it uses', () => {
|
||||
const modules = loaderOver({
|
||||
'uses-default.ts': `
|
||||
import Animated from 'react-native-not-substituted'
|
||||
@@ -66,8 +66,9 @@ describe('the mounted module loader', () => {
|
||||
`
|
||||
})
|
||||
const { read } = modules.load<{ read: () => unknown }>('mobile/src/uses-default.ts')
|
||||
expect(typeof read).toBe('function')
|
||||
expect(() => read()).toThrow(
|
||||
'Unspecified native mounting dependency: react-native-not-substituted'
|
||||
'Unspecified native mounting dependency: react-native-not-substituted.default'
|
||||
)
|
||||
})
|
||||
|
||||
@@ -79,16 +80,16 @@ describe('the mounted module loader', () => {
|
||||
`
|
||||
})
|
||||
const { read } = modules.load<{ read: () => unknown }>('mobile/src/uses-named.ts')
|
||||
expect(() => read()).toThrow('Unspecified native mounting dependency: expo-not-substituted')
|
||||
expect(() => read()).toThrow(
|
||||
'Unspecified native mounting dependency: expo-not-substituted.thing'
|
||||
)
|
||||
})
|
||||
|
||||
/**
|
||||
* The one shape that loses the named refusal: `__importStar` copies own keys into a fresh object,
|
||||
* of which the trap has none, so an unlisted member reads back `undefined` and fails at the call.
|
||||
* Pinned rather than left implicit — it is what lets a screen load a module such as
|
||||
* `platform/haptics.ts`, which imports a device package it only touches on a press.
|
||||
* What lets a screen mount a module such as `platform/haptics.ts`, which imports a device package
|
||||
* it only touches on a press: loading the importer is not itself a use.
|
||||
*/
|
||||
it('lets a namespace import of an unlisted package load, with undefined members', () => {
|
||||
it('lets a namespace import of an unlisted package load, and refuses the member it reads', () => {
|
||||
const modules = loaderOver({
|
||||
'uses-namespace.ts': `
|
||||
import * as Haptics from 'expo-not-substituted'
|
||||
@@ -96,6 +97,9 @@ describe('the mounted module loader', () => {
|
||||
`
|
||||
})
|
||||
const { read } = modules.load<{ read: () => unknown }>('mobile/src/uses-namespace.ts')
|
||||
expect(read()).toBeUndefined()
|
||||
expect(typeof read).toBe('function')
|
||||
expect(() => read()).toThrow(
|
||||
'Unspecified native mounting dependency: expo-not-substituted.selectionAsync'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -55,17 +55,17 @@ export function operationModuleLoader(
|
||||
return new Proxy(
|
||||
{},
|
||||
{
|
||||
// `__esModule` is the module system's interop marker, not a native API. Refusing it kills
|
||||
// a default import inside `__importDefault`, before any member is read; answering
|
||||
// `undefined` defers the refusal to the first real member, the same rule the substitute
|
||||
// traps follow. A namespace import is the one shape that loses it: `__importStar` copies
|
||||
// own keys into a fresh object, of which there are none, so an unlisted member of an
|
||||
// `import * as` reads back `undefined` and fails at the call instead of at the read.
|
||||
// `__esModule` is the module system's interop marker, not a native API. Answering `true`
|
||||
// makes both emitted interop helpers hand this trap straight back — `__importDefault`
|
||||
// returns it instead of wrapping it, `__importStar` returns it instead of copying its
|
||||
// (absent) own keys — so all three import forms load and refuse at the first member read,
|
||||
// naming the member. Substitute partials answer `undefined` instead, because there a real
|
||||
// member object must bind as the module's default.
|
||||
get: (_target, key) => {
|
||||
if (key === '__esModule') {
|
||||
return undefined
|
||||
return true
|
||||
}
|
||||
throw new Error(`Unspecified native mounting dependency: ${name}`)
|
||||
throw new Error(`Unspecified native mounting dependency: ${name}.${String(key)}`)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user