From a331b69f74d4c28ca17a4572a4a827641938b640 Mon Sep 17 00:00:00 2001 From: nrodriguezcuellar Date: Thu, 5 Jun 2025 22:34:35 +0200 Subject: [PATCH 1/7] integrate with capacitor config to disable type checking of native folders --- .gitignore | 3 +++ lint-staged.config.cjs | 2 +- src/module.ts | 10 +++++++ src/parts/typescript.ts | 58 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 src/parts/typescript.ts diff --git a/.gitignore b/.gitignore index ec1e1531..873126ff 100644 --- a/.gitignore +++ b/.gitignore @@ -44,6 +44,9 @@ coverage *.iml .idea +# Zed +.zed + # OSX .DS_Store .AppleDouble diff --git a/lint-staged.config.cjs b/lint-staged.config.cjs index e74b7787..218dd6e7 100644 --- a/lint-staged.config.cjs +++ b/lint-staged.config.cjs @@ -1,3 +1,3 @@ module.exports = { - '*.{js,ts,mjs,cjs,json}': ['pnpm lint:eslint'], + '*.{js,ts,mjs,cjs,json}': ['pnpm lint'], } diff --git a/src/module.ts b/src/module.ts index f644efe5..9fd7adbe 100644 --- a/src/module.ts +++ b/src/module.ts @@ -20,6 +20,7 @@ import { useCSSSetup } from './parts/css' import { setupIcons } from './parts/icons' import { setupMeta } from './parts/meta' import { setupRouter } from './parts/router' +import { useTypescrip } from './parts/typescript' export interface ModuleOptions { integrations?: { @@ -138,6 +139,15 @@ export default defineNuxtModule({ nuxt.options.typescript.hoist ||= [] nuxt.options.typescript.hoist.push('@ionic/vue') + const { excludeNativeFolders, detectIfCapacatorIsEnabled, parseCapacitorConfig } + = useTypescrip() + // add the `android` and `ios` folders to the TypeScript config exclude list if capacitor is enabled + // this is to prevent TypeScript from trying to resolve the Capacitor native code + if (await detectIfCapacatorIsEnabled()) { + const { androidPath, iosPath } = await parseCapacitorConfig() + excludeNativeFolders(androidPath, iosPath) + } + // Add auto-imported components IonicBuiltInComponents.map(name => addComponent({ diff --git a/src/parts/typescript.ts b/src/parts/typescript.ts new file mode 100644 index 00000000..2bf2524c --- /dev/null +++ b/src/parts/typescript.ts @@ -0,0 +1,58 @@ +import type { CapacitorConfig } from '@capacitor/cli' +import { findPath, useNuxt } from '@nuxt/kit' +import { join } from 'pathe' + +export const useTypescrip = () => { + const nuxt = useNuxt() + + /** Find the path to capacitor configuration file (if it exists) */ + const findCapacitorConfig = async () => { + const path = await findPath( + ['capacitor.config.ts', 'capacitor.config.json'], + { + extensions: ['ts', 'json'], + virtual: false, + }, + 'file', + ) + + return path + } + + const detectIfCapacatorIsEnabled = async () => { + const paths = await findCapacitorConfig() + return !!paths + } + + const parseCapacitorConfig = async () => { + const path = await findCapacitorConfig() + if (!path) { + return { + androidPath: null, + iosPath: null, + } + } + + const capacitorConfig = (await import(path)) as CapacitorConfig + + return { + androidPath: capacitorConfig.android?.path || null, + iosPath: capacitorConfig.ios?.path || null, + } + } + + const excludeNativeFolders = (androidPath: string | null, iosPath: string | null) => { + nuxt.options.typescript.tsConfig ||= {} + nuxt.options.typescript.tsConfig.exclude ||= [] + nuxt.options.typescript.tsConfig.exclude.push( + join('../', androidPath ?? '/android'), + join('../', iosPath ?? '/ios'), + ) + } + + return { + excludeNativeFolders, + detectIfCapacatorIsEnabled, + parseCapacitorConfig, + } +} From b631d6419cc8934d3c8854fd0d458cff8643e558 Mon Sep 17 00:00:00 2001 From: nrodriguezcuellar Date: Thu, 5 Jun 2025 22:44:32 +0200 Subject: [PATCH 2/7] rename to better describe actual logic --- playground/capacitor.config.ts | 1 - src/module.ts | 13 ++++++++----- src/parts/{typescript.ts => capacitor.ts} | 13 ++++--------- 3 files changed, 12 insertions(+), 15 deletions(-) rename src/parts/{typescript.ts => capacitor.ts} (80%) diff --git a/playground/capacitor.config.ts b/playground/capacitor.config.ts index a95c5588..669063ae 100644 --- a/playground/capacitor.config.ts +++ b/playground/capacitor.config.ts @@ -4,7 +4,6 @@ const config: CapacitorConfig = { appId: 'io.ionic.starter', appName: 'nuxt-ionic-playground', webDir: 'dist', - bundledWebRuntime: false, } export default config diff --git a/src/module.ts b/src/module.ts index 9fd7adbe..bf4c3def 100644 --- a/src/module.ts +++ b/src/module.ts @@ -20,7 +20,7 @@ import { useCSSSetup } from './parts/css' import { setupIcons } from './parts/icons' import { setupMeta } from './parts/meta' import { setupRouter } from './parts/router' -import { useTypescrip } from './parts/typescript' +import { useCapacitor } from './parts/capacitor' export interface ModuleOptions { integrations?: { @@ -139,12 +139,15 @@ export default defineNuxtModule({ nuxt.options.typescript.hoist ||= [] nuxt.options.typescript.hoist.push('@ionic/vue') - const { excludeNativeFolders, detectIfCapacatorIsEnabled, parseCapacitorConfig } - = useTypescrip() + // add capacitor integration + const { excludeNativeFolders, findCapacitorConfig, parseCapacitorConfig } + = useCapacitor() + // add the `android` and `ios` folders to the TypeScript config exclude list if capacitor is enabled // this is to prevent TypeScript from trying to resolve the Capacitor native code - if (await detectIfCapacatorIsEnabled()) { - const { androidPath, iosPath } = await parseCapacitorConfig() + const capacitorConfigPath = await findCapacitorConfig() + if (capacitorConfigPath) { + const { androidPath, iosPath } = await parseCapacitorConfig(capacitorConfigPath) excludeNativeFolders(androidPath, iosPath) } diff --git a/src/parts/typescript.ts b/src/parts/capacitor.ts similarity index 80% rename from src/parts/typescript.ts rename to src/parts/capacitor.ts index 2bf2524c..843729f7 100644 --- a/src/parts/typescript.ts +++ b/src/parts/capacitor.ts @@ -2,7 +2,7 @@ import type { CapacitorConfig } from '@capacitor/cli' import { findPath, useNuxt } from '@nuxt/kit' import { join } from 'pathe' -export const useTypescrip = () => { +export const useCapacitor = () => { const nuxt = useNuxt() /** Find the path to capacitor configuration file (if it exists) */ @@ -19,13 +19,7 @@ export const useTypescrip = () => { return path } - const detectIfCapacatorIsEnabled = async () => { - const paths = await findCapacitorConfig() - return !!paths - } - - const parseCapacitorConfig = async () => { - const path = await findCapacitorConfig() + const parseCapacitorConfig = async (path: string | null) => { if (!path) { return { androidPath: null, @@ -41,6 +35,7 @@ export const useTypescrip = () => { } } + /** Exclude native folder paths from type checking by excluding them in tsconfig */ const excludeNativeFolders = (androidPath: string | null, iosPath: string | null) => { nuxt.options.typescript.tsConfig ||= {} nuxt.options.typescript.tsConfig.exclude ||= [] @@ -52,7 +47,7 @@ export const useTypescrip = () => { return { excludeNativeFolders, - detectIfCapacatorIsEnabled, + findCapacitorConfig, parseCapacitorConfig, } } From 8e71d1eaa5fad08b379effd27a9b5ee2da374a6c Mon Sep 17 00:00:00 2001 From: nrodriguezcuellar Date: Mon, 9 Jun 2025 17:21:58 +0200 Subject: [PATCH 3/7] add tests --- src/parts/capacitor.ts | 5 +- test/unit/capacitor.spec.ts | 131 ++++++++++++++++++++++++++++++++++++ 2 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 test/unit/capacitor.spec.ts diff --git a/src/parts/capacitor.ts b/src/parts/capacitor.ts index 843729f7..df6b280e 100644 --- a/src/parts/capacitor.ts +++ b/src/parts/capacitor.ts @@ -19,7 +19,10 @@ export const useCapacitor = () => { return path } - const parseCapacitorConfig = async (path: string | null) => { + const parseCapacitorConfig = async (path: string | null): Promise<{ + androidPath: string | null + iosPath: string | null + }> => { if (!path) { return { androidPath: null, diff --git a/test/unit/capacitor.spec.ts b/test/unit/capacitor.spec.ts new file mode 100644 index 00000000..5d1da42f --- /dev/null +++ b/test/unit/capacitor.spec.ts @@ -0,0 +1,131 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest' +import { useNuxt, findPath } from '@nuxt/kit' +import { useCapacitor } from '../../src/parts/capacitor' + +// Mock @nuxt/kit +vi.mock('@nuxt/kit', () => ({ + findPath: vi.fn(), + useNuxt: vi.fn(), +})) + +describe('useCapacitor', () => { + const mockNuxt = { + options: { + typescript: { + tsConfig: { + exclude: [], + }, + }, + }, + } + + beforeEach(() => { + vi.clearAllMocks() + vi.mocked(useNuxt).mockReturnValue(mockNuxt as any) + mockNuxt.options.typescript.tsConfig.exclude = [] + }) + + describe('findCapacitorConfig', () => { + it('should find capacitor.config.ts', async () => { + const mockPath = '/project/capacitor.config.ts' + vi.mocked(findPath).mockResolvedValue(mockPath) + + const { findCapacitorConfig } = useCapacitor() + const result = await findCapacitorConfig() + + expect(result).toBe(mockPath) + }) + + it('should return null when no config found', async () => { + vi.mocked(findPath).mockResolvedValue(null) + + const { findCapacitorConfig } = useCapacitor() + const result = await findCapacitorConfig() + + expect(result).toBeNull() + }) + }) + + describe('parseCapacitorConfig', () => { + it('should return null paths when no config path provided', async () => { + const { parseCapacitorConfig } = useCapacitor() + const result = await parseCapacitorConfig(null) + + expect(result).toEqual({ + androidPath: null, + iosPath: null, + }) + }) + + it('should parse capacitor config with custom paths', async () => { + const configPath = './capacitor.config.ts' + const mockConfig = { + android: { path: 'custom-android' }, + ios: { path: 'custom-ios' }, + } + + vi.doMock(configPath, () => ({ + default: mockConfig, + ...mockConfig, + })) + + const { parseCapacitorConfig } = useCapacitor() + const result = await parseCapacitorConfig(configPath) + + expect(result).toEqual({ + androidPath: 'custom-android', + iosPath: 'custom-ios', + }) + }) + + it('should handle config without android/ios paths', async () => { + const configPath = './capacitor.config.ts' + const mockConfig = { + android: undefined, + ios: undefined, + } + + vi.doMock(configPath, () => ({ + default: mockConfig, + ...mockConfig, + })) + + const { parseCapacitorConfig } = useCapacitor() + const result = await parseCapacitorConfig(configPath) + + expect(result).toEqual({ + androidPath: null, + iosPath: null, + }) + }) + }) + + describe('excludeNativeFolders', () => { + it('should add native folders to typescript exclude', () => { + const { excludeNativeFolders } = useCapacitor() + excludeNativeFolders('android', 'ios') + + expect(mockNuxt.options.typescript.tsConfig.exclude).toContain('../android') + expect(mockNuxt.options.typescript.tsConfig.exclude).toContain('../ios') + }) + + it('should handle null paths with defaults', () => { + const { excludeNativeFolders } = useCapacitor() + excludeNativeFolders(null, null) + + expect(mockNuxt.options.typescript.tsConfig.exclude).toContain('../android') + expect(mockNuxt.options.typescript.tsConfig.exclude).toContain('../ios') + }) + + it('should initialize tsConfig if not present', () => { + // @ts-expect-error should not be undefined + mockNuxt.options.typescript.tsConfig = undefined + + const { excludeNativeFolders } = useCapacitor() + excludeNativeFolders('android', 'ios') + + expect(mockNuxt.options.typescript.tsConfig).toBeDefined() + expect(mockNuxt.options.typescript.tsConfig.exclude).toContain('../android') + }) + }) +}) From c74d24c9de2094d8850d1f7d2d100c5243975da9 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Wed, 30 Jul 2025 12:54:01 +0100 Subject: [PATCH 4/7] refactor: rename to `setupCapacitor` --- src/module.ts | 4 ++-- src/parts/capacitor.ts | 2 +- test/unit/capacitor.spec.ts | 18 +++++++++--------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/module.ts b/src/module.ts index c7038ee3..f6d21754 100644 --- a/src/module.ts +++ b/src/module.ts @@ -20,7 +20,7 @@ import { useCSSSetup } from './parts/css' import { setupIcons } from './parts/icons' import { setupMeta } from './parts/meta' import { setupRouter } from './parts/router' -import { useCapacitor } from './parts/capacitor' +import { setupCapacitor } from './parts/capacitor' export interface ModuleOptions { integrations?: { @@ -141,7 +141,7 @@ export default defineNuxtModule({ // add capacitor integration const { excludeNativeFolders, findCapacitorConfig, parseCapacitorConfig } - = useCapacitor() + = setupCapacitor() // add the `android` and `ios` folders to the TypeScript config exclude list if capacitor is enabled // this is to prevent TypeScript from trying to resolve the Capacitor native code diff --git a/src/parts/capacitor.ts b/src/parts/capacitor.ts index df6b280e..42866b45 100644 --- a/src/parts/capacitor.ts +++ b/src/parts/capacitor.ts @@ -2,7 +2,7 @@ import type { CapacitorConfig } from '@capacitor/cli' import { findPath, useNuxt } from '@nuxt/kit' import { join } from 'pathe' -export const useCapacitor = () => { +export const setupCapacitor = () => { const nuxt = useNuxt() /** Find the path to capacitor configuration file (if it exists) */ diff --git a/test/unit/capacitor.spec.ts b/test/unit/capacitor.spec.ts index 5d1da42f..b7865c7a 100644 --- a/test/unit/capacitor.spec.ts +++ b/test/unit/capacitor.spec.ts @@ -1,6 +1,6 @@ import { describe, it, expect, vi, beforeEach } from 'vitest' import { useNuxt, findPath } from '@nuxt/kit' -import { useCapacitor } from '../../src/parts/capacitor' +import { setupCapacitor } from '../../src/parts/capacitor' // Mock @nuxt/kit vi.mock('@nuxt/kit', () => ({ @@ -30,7 +30,7 @@ describe('useCapacitor', () => { const mockPath = '/project/capacitor.config.ts' vi.mocked(findPath).mockResolvedValue(mockPath) - const { findCapacitorConfig } = useCapacitor() + const { findCapacitorConfig } = setupCapacitor() const result = await findCapacitorConfig() expect(result).toBe(mockPath) @@ -39,7 +39,7 @@ describe('useCapacitor', () => { it('should return null when no config found', async () => { vi.mocked(findPath).mockResolvedValue(null) - const { findCapacitorConfig } = useCapacitor() + const { findCapacitorConfig } = setupCapacitor() const result = await findCapacitorConfig() expect(result).toBeNull() @@ -48,7 +48,7 @@ describe('useCapacitor', () => { describe('parseCapacitorConfig', () => { it('should return null paths when no config path provided', async () => { - const { parseCapacitorConfig } = useCapacitor() + const { parseCapacitorConfig } = setupCapacitor() const result = await parseCapacitorConfig(null) expect(result).toEqual({ @@ -69,7 +69,7 @@ describe('useCapacitor', () => { ...mockConfig, })) - const { parseCapacitorConfig } = useCapacitor() + const { parseCapacitorConfig } = setupCapacitor() const result = await parseCapacitorConfig(configPath) expect(result).toEqual({ @@ -90,7 +90,7 @@ describe('useCapacitor', () => { ...mockConfig, })) - const { parseCapacitorConfig } = useCapacitor() + const { parseCapacitorConfig } = setupCapacitor() const result = await parseCapacitorConfig(configPath) expect(result).toEqual({ @@ -102,7 +102,7 @@ describe('useCapacitor', () => { describe('excludeNativeFolders', () => { it('should add native folders to typescript exclude', () => { - const { excludeNativeFolders } = useCapacitor() + const { excludeNativeFolders } = setupCapacitor() excludeNativeFolders('android', 'ios') expect(mockNuxt.options.typescript.tsConfig.exclude).toContain('../android') @@ -110,7 +110,7 @@ describe('useCapacitor', () => { }) it('should handle null paths with defaults', () => { - const { excludeNativeFolders } = useCapacitor() + const { excludeNativeFolders } = setupCapacitor() excludeNativeFolders(null, null) expect(mockNuxt.options.typescript.tsConfig.exclude).toContain('../android') @@ -121,7 +121,7 @@ describe('useCapacitor', () => { // @ts-expect-error should not be undefined mockNuxt.options.typescript.tsConfig = undefined - const { excludeNativeFolders } = useCapacitor() + const { excludeNativeFolders } = setupCapacitor() excludeNativeFolders('android', 'ios') expect(mockNuxt.options.typescript.tsConfig).toBeDefined() From eff83f0403cce67a0e6883695bad84f6cf63ea43 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Wed, 30 Jul 2025 12:55:38 +0100 Subject: [PATCH 5/7] chore: small tweaks --- src/parts/capacitor.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/parts/capacitor.ts b/src/parts/capacitor.ts index 42866b45..c69e97c7 100644 --- a/src/parts/capacitor.ts +++ b/src/parts/capacitor.ts @@ -8,7 +8,7 @@ export const setupCapacitor = () => { /** Find the path to capacitor configuration file (if it exists) */ const findCapacitorConfig = async () => { const path = await findPath( - ['capacitor.config.ts', 'capacitor.config.json'], + 'capacitor.config', { extensions: ['ts', 'json'], virtual: false, @@ -43,8 +43,8 @@ export const setupCapacitor = () => { nuxt.options.typescript.tsConfig ||= {} nuxt.options.typescript.tsConfig.exclude ||= [] nuxt.options.typescript.tsConfig.exclude.push( - join('../', androidPath ?? '/android'), - join('../', iosPath ?? '/ios'), + join('..', androidPath ?? 'android'), + join('..', iosPath ?? 'ios'), ) } From 6fee11037f8bf184cb77598c82865c31c0844a6f Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Wed, 30 Jul 2025 13:03:49 +0100 Subject: [PATCH 6/7] fix: support new nuxt contexts --- src/module.ts | 3 +-- src/parts/capacitor.ts | 22 +++++++++++++++++----- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/module.ts b/src/module.ts index f6d21754..fe5f1983 100644 --- a/src/module.ts +++ b/src/module.ts @@ -140,8 +140,7 @@ export default defineNuxtModule({ nuxt.options.typescript.hoist.push('@ionic/vue') // add capacitor integration - const { excludeNativeFolders, findCapacitorConfig, parseCapacitorConfig } - = setupCapacitor() + const { excludeNativeFolders, findCapacitorConfig, parseCapacitorConfig } = setupCapacitor() // add the `android` and `ios` folders to the TypeScript config exclude list if capacitor is enabled // this is to prevent TypeScript from trying to resolve the Capacitor native code diff --git a/src/parts/capacitor.ts b/src/parts/capacitor.ts index c69e97c7..fe80345f 100644 --- a/src/parts/capacitor.ts +++ b/src/parts/capacitor.ts @@ -40,11 +40,23 @@ export const setupCapacitor = () => { /** Exclude native folder paths from type checking by excluding them in tsconfig */ const excludeNativeFolders = (androidPath: string | null, iosPath: string | null) => { - nuxt.options.typescript.tsConfig ||= {} - nuxt.options.typescript.tsConfig.exclude ||= [] - nuxt.options.typescript.tsConfig.exclude.push( - join('..', androidPath ?? 'android'), - join('..', iosPath ?? 'ios'), + nuxt.hook('prepare:types', (ctx) => { + const paths = [ + join('..', androidPath ?? 'android'), + join('..', iosPath ?? 'ios'), + ] + + for (const key of ['tsConfig', 'nodeTsConfig', 'sharedTsConfig'] as const) { + if (ctx[key]) { + ctx[key].exclude ||= [] + ctx[key].exclude.push(...paths) + } + } + }) + + nuxt.options.ignore.push( + join(androidPath ?? 'android'), + join(iosPath ?? 'ios'), ) } From d1263d237ae6362a27f1a693a624f26055d8507d Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Wed, 30 Jul 2025 13:09:03 +0100 Subject: [PATCH 7/7] test: update tests --- test/unit/capacitor.spec.ts | 84 ++++++++++++++++++++++++++++++------- 1 file changed, 68 insertions(+), 16 deletions(-) diff --git a/test/unit/capacitor.spec.ts b/test/unit/capacitor.spec.ts index b7865c7a..3d2a4324 100644 --- a/test/unit/capacitor.spec.ts +++ b/test/unit/capacitor.spec.ts @@ -10,19 +10,16 @@ vi.mock('@nuxt/kit', () => ({ describe('useCapacitor', () => { const mockNuxt = { + hook: vi.fn(), options: { - typescript: { - tsConfig: { - exclude: [], - }, - }, + ignore: [], }, } beforeEach(() => { vi.clearAllMocks() vi.mocked(useNuxt).mockReturnValue(mockNuxt as any) - mockNuxt.options.typescript.tsConfig.exclude = [] + mockNuxt.options.ignore = [] }) describe('findCapacitorConfig', () => { @@ -101,31 +98,86 @@ describe('useCapacitor', () => { }) describe('excludeNativeFolders', () => { - it('should add native folders to typescript exclude', () => { + it('should register prepare:types hook and add native folders to ignore', () => { const { excludeNativeFolders } = setupCapacitor() excludeNativeFolders('android', 'ios') - expect(mockNuxt.options.typescript.tsConfig.exclude).toContain('../android') - expect(mockNuxt.options.typescript.tsConfig.exclude).toContain('../ios') + expect(mockNuxt.hook).toHaveBeenCalledWith('prepare:types', expect.any(Function)) + expect(mockNuxt.options.ignore).toContain('android') + expect(mockNuxt.options.ignore).toContain('ios') }) it('should handle null paths with defaults', () => { const { excludeNativeFolders } = setupCapacitor() excludeNativeFolders(null, null) - expect(mockNuxt.options.typescript.tsConfig.exclude).toContain('../android') - expect(mockNuxt.options.typescript.tsConfig.exclude).toContain('../ios') + expect(mockNuxt.hook).toHaveBeenCalledWith('prepare:types', expect.any(Function)) + expect(mockNuxt.options.ignore).toContain('android') + expect(mockNuxt.options.ignore).toContain('ios') }) - it('should initialize tsConfig if not present', () => { - // @ts-expect-error should not be undefined - mockNuxt.options.typescript.tsConfig = undefined + it('should modify typescript configs in prepare:types hook', () => { + const { excludeNativeFolders } = setupCapacitor() + excludeNativeFolders('custom-android', 'custom-ios') + + // Get the hook callback that was registered + const hookCallback = mockNuxt.hook.mock.calls.find(call => call[0] === 'prepare:types')?.[1] + expect(hookCallback).toBeDefined() + + // Mock typescript context + const mockCtx = { + tsConfig: { exclude: [] }, + nodeTsConfig: { exclude: [] }, + sharedTsConfig: { exclude: [] }, + } + + // Call the hook callback + hookCallback(mockCtx) + // Verify all configs were updated + expect(mockCtx.tsConfig.exclude).toContain('../custom-android') + expect(mockCtx.tsConfig.exclude).toContain('../custom-ios') + expect(mockCtx.nodeTsConfig.exclude).toContain('../custom-android') + expect(mockCtx.nodeTsConfig.exclude).toContain('../custom-ios') + expect(mockCtx.sharedTsConfig.exclude).toContain('../custom-android') + expect(mockCtx.sharedTsConfig.exclude).toContain('../custom-ios') + }) + + it('should initialize exclude arrays if not present in typescript configs', () => { const { excludeNativeFolders } = setupCapacitor() excludeNativeFolders('android', 'ios') - expect(mockNuxt.options.typescript.tsConfig).toBeDefined() - expect(mockNuxt.options.typescript.tsConfig.exclude).toContain('../android') + const hookCallback = mockNuxt.hook.mock.calls.find(call => call[0] === 'prepare:types')?.[1] + + // Mock context without exclude arrays + const mockCtx = { + tsConfig: {} as any, + nodeTsConfig: {} as any, + sharedTsConfig: {} as any, + } + + hookCallback(mockCtx) + + expect(mockCtx.tsConfig.exclude).toEqual(['../android', '../ios']) + expect(mockCtx.nodeTsConfig.exclude).toEqual(['../android', '../ios']) + expect(mockCtx.sharedTsConfig.exclude).toEqual(['../android', '../ios']) + }) + + it('should handle missing typescript configs gracefully', () => { + const { excludeNativeFolders } = setupCapacitor() + excludeNativeFolders('android', 'ios') + + const hookCallback = mockNuxt.hook.mock.calls.find(call => call[0] === 'prepare:types')?.[1] + + // Mock context with only some configs present + const mockCtx = { + tsConfig: { exclude: [] }, + // nodeTsConfig and sharedTsConfig are undefined + } + + expect(() => hookCallback(mockCtx)).not.toThrow() + expect(mockCtx.tsConfig.exclude).toContain('../android') + expect(mockCtx.tsConfig.exclude).toContain('../ios') }) }) })