UNPKG

one

Version:

One is a React framework for web, iOS, and Android apps.

225 lines (191 loc) • 7.72 kB
import path from 'node:path' import { afterEach, describe, expect, it, vi } from 'vitest' import oneBabelPreset, { buildOneBabelPlugins } from './index' const projectRoot = path.resolve(__dirname, '../../') const fakeApi = (cwd: string) => ({ cache: () => {}, cwd: () => cwd, }) describe('one/babel-preset', () => { afterEach(() => { vi.unstubAllEnvs() }) it('returns presets and plugins', () => { const result = oneBabelPreset(fakeApi(projectRoot), { projectRoot }) expect(result).toHaveProperty('plugins') expect(Array.isArray(result.plugins)).toBe(true) // 5 One plugins + import-meta-env-plugin baked in for standalone Metro expect(result.plugins).toHaveLength(6) // @react-native/babel-preset base for TS/Flow syntax + platform transforms expect(result.presets).toHaveLength(2) }) it('orders the plugin chain so server code is removed before router transforms', () => { const { plugins } = oneBabelPreset(fakeApi(projectRoot), { projectRoot, }) const names = (plugins ?? []).map((p) => (Array.isArray(p) ? p[0] : p)) expect(names).toEqual([ '@vxrn/vite-plugin-metro/babel-plugins/import-meta-env-plugin', 'one/babel-plugin-environment-guard', 'one/babel-plugin-remove-server-code', 'babel-plugin-module-resolver', 'one/babel-plugin-one-router-metro', 'one/babel-plugin-inline-one-server-url', ]) }) it('defaults routerRoot to "app"', () => { const { plugins } = oneBabelPreset(fakeApi(projectRoot), { projectRoot, }) const removeServer = (plugins ?? []).find( (p): p is [string, Record<string, unknown>] => Array.isArray(p) && p[0] === 'one/babel-plugin-remove-server-code' ) expect(removeServer?.[1].routerRoot).toBe('app') }) it('threads custom routerRoot through', () => { const { plugins } = oneBabelPreset(fakeApi(projectRoot), { projectRoot, routerRoot: 'src/routes', }) const removeServer = (plugins ?? []).find( (p): p is [string, Record<string, unknown>] => Array.isArray(p) && p[0] === 'one/babel-plugin-remove-server-code' ) expect(removeServer?.[1].routerRoot).toBe('src/routes') }) it('skips the One plugin chain when the Vite Metro caller already injected it', () => { const result = oneBabelPreset( { cache: () => {}, cwd: () => projectRoot, caller: <T>(cb: (caller: unknown) => T): T => cb({ oneViteMetroBabelConfig: true }), }, { projectRoot, } ) expect(result.plugins).toEqual([]) // the one dev path still needs the RN base: the Vite plugin only // injected the One chain, not syntax transforms expect(result.presets).toHaveLength(2) }) it('adds nothing when @vxrn/compiler applies it inside the vite and rolldown pipeline', () => { const result = oneBabelPreset( { cache: () => {}, cwd: () => projectRoot, caller: <T>(cb: (caller: unknown) => T): T => cb({ name: 'vxrn' }), }, { projectRoot } ) expect(result).toEqual({ presets: [], plugins: [] }) }) it('builds the standalone Metro environment with one-way Expo aliases', () => { vi.stubEnv('ONE_PUBLIC_FROM_ONE', 'one') vi.stubEnv('ONE_PUBLIC_CONFLICT', 'one-conflict') vi.stubEnv('EXPO_PUBLIC_CONFLICT', 'expo-conflict') vi.stubEnv('EXPO_PUBLIC_EXPO_ONLY', 'expo-only') vi.stubEnv('ONE_PLATFORM', 'web') vi.stubEnv('EXPO_OS', 'web') const webResult = oneBabelPreset(fakeApi(projectRoot), { projectRoot, }) expect(webResult.plugins?.[0]).toEqual([ '@vxrn/vite-plugin-metro/babel-plugins/import-meta-env-plugin', { env: expect.objectContaining({ ONE_PUBLIC_FROM_ONE: 'one', EXPO_PUBLIC_FROM_ONE: 'one', ONE_PUBLIC_CONFLICT: 'one-conflict', EXPO_PUBLIC_CONFLICT: 'expo-conflict', EXPO_PUBLIC_EXPO_ONLY: 'expo-only', ONE_PLATFORM: 'web', }), }, ]) expect(webResult.plugins?.[0]).not.toHaveProperty('1.env.ONE_PUBLIC_EXPO_ONLY') expect(webResult.plugins?.[0]).not.toHaveProperty('1.env.EXPO_OS') vi.stubEnv('ONE_PLATFORM', 'ios') const iosResult = oneBabelPreset(fakeApi(projectRoot), { projectRoot, }) expect(iosResult.plugins?.[0]).toHaveProperty('1.env.EXPO_OS', 'ios') vi.stubEnv('ONE_PLATFORM', 'android') const androidResult = oneBabelPreset(fakeApi(projectRoot), { projectRoot, }) expect(androidResult.plugins?.[0]).toHaveProperty('1.env.EXPO_OS', 'android') }) }) describe('buildOneBabelPlugins', () => { it('produces the canonical One plugin chain (no env plugin)', () => { const plugins = buildOneBabelPlugins({ projectRoot, relativeRouterRoot: 'app', ignoredRouteFiles: [], linking: undefined, setupFile: undefined, // skip the import-meta-env plugin so the assertion covers the One-only chain includeImportMetaEnv: false, }) expect(plugins).toHaveLength(5) expect(plugins[0]).toBe('one/babel-plugin-environment-guard') expect(plugins[1]).toEqual([ 'one/babel-plugin-remove-server-code', { routerRoot: 'app' }, ]) expect(plugins[2]).toMatchObject(['babel-plugin-module-resolver', { alias: {} }]) const oneRouterMetro = plugins[3] as [string, Record<string, unknown>] expect(oneRouterMetro[0]).toBe('one/babel-plugin-one-router-metro') expect(oneRouterMetro[1]).toMatchObject({ ONE_ROUTER_ROOT_FOLDER_NAME: 'app', ONE_ROUTER_LINKING_CONFIG: undefined, ONE_SETUP_FILE_NATIVE: undefined, }) expect(typeof oneRouterMetro[1].ONE_ROUTER_APP_ROOT_RELATIVE_TO_ENTRY).toBe('string') expect(typeof oneRouterMetro[1].ONE_ROUTER_REQUIRE_CONTEXT_REGEX_STRING).toBe( 'string' ) expect(plugins[4]).toBe('one/babel-plugin-inline-one-server-url') }) it('threads setupFile through as a path relative to the metro entry', () => { const plugins = buildOneBabelPlugins({ projectRoot, relativeRouterRoot: 'app', setupFile: 'src/setup-native.ts', includeImportMetaEnv: false, }) const oneRouterMetro = plugins[3] as [string, Record<string, unknown>] expect(oneRouterMetro[1].ONE_SETUP_FILE_NATIVE).toMatch(/setup-native\.ts$/) }) it('passes the linking config through', () => { const linking = { prefixes: ['myapp://'] } const plugins = buildOneBabelPlugins({ projectRoot, relativeRouterRoot: 'app', linking, includeImportMetaEnv: false, }) const oneRouterMetro = plugins[3] as [string, Record<string, unknown>] expect(oneRouterMetro[1].ONE_ROUTER_LINKING_CONFIG).toBe(linking) }) // both values are inlined by babel as JS literals — `ONE_ROUTER_APP_ROOT_RELATIVE_TO_ENTRY` // becomes the first arg of `require.context()`, `ONE_SETUP_FILE_NATIVE` becomes the literal // in `import "..."`. Native separators on Windows produce platform-conditional AST that // breaks source-maps, snapshot tests, and rolldown/Vite POSIX module-graph keys. it('emits forward-slash-only paths for `require.context` + `import` specifiers', () => { const plugins = buildOneBabelPlugins({ projectRoot, relativeRouterRoot: 'app', setupFile: 'src/setup-native.ts', includeImportMetaEnv: false, }) const oneRouterMetro = plugins[3] as [string, Record<string, unknown>] const appRoot = oneRouterMetro[1].ONE_ROUTER_APP_ROOT_RELATIVE_TO_ENTRY as string const setupNative = oneRouterMetro[1].ONE_SETUP_FILE_NATIVE as string expect(appRoot).not.toContain('\\') expect(setupNative).not.toContain('\\') }) })