diff --git a/configs/eslint-config/docs/check-parent-suspense.md b/configs/eslint-config/docs/check-parent-suspense.md new file mode 100644 index 000000000..a1b87b7fb --- /dev/null +++ b/configs/eslint-config/docs/check-parent-suspense.md @@ -0,0 +1,129 @@ +# @suspensive/check-parent-suspense + +Ensures that components using Suspense-related APIs are wrapped in a Suspense boundary. + +## Rule Details + +This rule checks that components using Suspense-related hooks and components are properly wrapped in a `` component boundary within the same component scope. + +### Suspense-related APIs checked: + +**Hooks:** + +- `useSuspenseQuery` +- `useSuspenseInfiniteQuery` +- `useSuspenseQueries` + +**Components:** + +- `SuspenseQuery` +- `SuspenseInfiniteQuery` +- `SuspenseQueries` + +**Lazy components:** + +- Components created with `lazy()` function + +## Examples + +### ❌ Incorrect + +```tsx +// Hook without Suspense wrapper +function MyComponent() { + const data = useSuspenseQuery(queryOptions) + return
{data}
+} + +// Component without Suspense wrapper +function MyComponent() { + return {(data) =>
{data}
}
+} + +// Lazy component without Suspense wrapper +const LazyComponent = lazy(() => import('./Component')) + +function MyApp() { + return +} +``` + +### ✅ Correct + +```tsx +// Hook with Suspense wrapper +function MyComponent() { + return ( + Loading...}> +
+ {(() => { + const data = useSuspenseQuery(queryOptions) + return
{data}
+ })()} +
+
+ ) +} + +// Component with Suspense wrapper +function MyComponent() { + return ( + Loading...}> + {(data) =>
{data}
}
+
+ ) +} + +// Lazy component with Suspense wrapper +const LazyComponent = lazy(() => import('./Component')) + +function MyApp() { + return ( + Loading...}> + + + ) +} +``` + +## Options + +The rule accepts an options object with the following properties: + +- `suspenseHooks` (array): List of hook names to check. Default: `['useSuspenseQuery', 'useSuspenseInfiniteQuery', 'useSuspenseQueries']` +- `suspenseComponents` (array): List of component names to check. Default: `['SuspenseQuery', 'SuspenseInfiniteQuery', 'SuspenseQueries']` +- `suspenseWrappers` (array): List of valid Suspense wrapper component names. Default: `['Suspense']` + +### Example configuration + +```json +{ + "rules": { + "@suspensive/check-parent-suspense": [ + "error", + { + "suspenseHooks": ["useSuspenseQuery", "useCustomSuspenseHook"], + "suspenseComponents": ["SuspenseQuery", "CustomSuspenseComponent"], + "suspenseWrappers": ["Suspense", "CustomSuspense"] + } + ] + } +} +``` + +## When Not To Use It + +This rule enforces Suspense boundaries within the same component scope. In some patterns, components using Suspense APIs may be rendered within Suspense boundaries defined in parent components. If you're using such patterns and want to allow them, you can disable the rule for specific lines: + +```tsx +const MyComponent = () => { + // eslint-disable-next-line @suspensive/check-parent-suspense + const data = useSuspenseQuery(queryOptions) + return
{data}
+} +``` + +## Related Rules + +- React's built-in Suspense documentation: https://react.dev/reference/react/Suspense +- @tanstack/react-query Suspense documentation diff --git a/configs/eslint-config/index.js b/configs/eslint-config/index.js index 7e663c8f7..b063b02a3 100644 --- a/configs/eslint-config/index.js +++ b/configs/eslint-config/index.js @@ -9,6 +9,7 @@ import cspellConfigs from '@cspell/eslint-plugin/configs' import vitest from '@vitest/eslint-plugin' import jestDom from 'eslint-plugin-jest-dom' import * as mdx from 'eslint-plugin-mdx' +import suspensivePlugin from './plugin.js' const ignores = ['**/.next/**', '**/build/**', '**/coverage/**', '**/dist/**'] @@ -123,17 +124,27 @@ export const suspensiveReactTypeScriptConfig = tseslint.config( JSX: true, }, }, + plugins: { + '@suspensive': suspensivePlugin, + }, rules: { 'react-hooks/react-compiler': 'warn', '@eslint-react/no-use-context': 'off', '@eslint-react/no-forward-ref': 'off', '@eslint-react/no-context-provider': 'off', + '@suspensive/check-parent-suspense': 'error', }, settings: { react: { version: 'detect', }, }, + }, + { + files: ['packages/**/*.{ts,tsx}'], + rules: { + '@suspensive/check-parent-suspense': 'off', + }, } ) diff --git a/configs/eslint-config/package.json b/configs/eslint-config/package.json index d9a0a37b9..f90c1b93c 100644 --- a/configs/eslint-config/package.json +++ b/configs/eslint-config/package.json @@ -23,5 +23,9 @@ "eslint-plugin-prettier": "^5.5.4", "eslint-plugin-react-hooks": "6.0.0-rc.1", "typescript-eslint": "^8.31.0" + }, + "devDependencies": { + "@babel/eslint-parser": "^7.28.4", + "@babel/preset-react": "^7.27.1" } } diff --git a/configs/eslint-config/plugin.js b/configs/eslint-config/plugin.js new file mode 100644 index 000000000..44329e55f --- /dev/null +++ b/configs/eslint-config/plugin.js @@ -0,0 +1,13 @@ +import rules from './rules/index.js' + +export default { + rules, + configs: { + recommended: { + plugins: ['@suspensive'], + rules: { + '@suspensive/check-parent-suspense': 'error', + }, + }, + }, +} diff --git a/configs/eslint-config/rules/check-parent-suspense.js b/configs/eslint-config/rules/check-parent-suspense.js new file mode 100644 index 000000000..8646c77b4 --- /dev/null +++ b/configs/eslint-config/rules/check-parent-suspense.js @@ -0,0 +1,184 @@ +/** + * @fileoverview Rule to check if components using Suspense-related APIs are wrapped in a Suspense boundary + * @author Suspensive Team + */ + +/** @type {import('eslint').Rule.RuleModule} */ +export default { + meta: { + type: 'problem', + docs: { + description: 'ensure components using Suspense-related APIs are wrapped in a Suspense boundary', + category: 'Possible Errors', + recommended: true, + }, + fixable: null, + schema: [ + { + type: 'object', + properties: { + suspenseHooks: { + type: 'array', + items: { type: 'string' }, + default: ['useSuspenseQuery', 'useSuspenseInfiniteQuery', 'useSuspenseQueries'], + }, + suspenseComponents: { + type: 'array', + items: { type: 'string' }, + default: ['SuspenseQuery', 'SuspenseInfiniteQuery', 'SuspenseQueries'], + }, + suspenseWrappers: { + type: 'array', + items: { type: 'string' }, + default: ['Suspense'], + }, + }, + additionalProperties: false, + }, + ], + messages: { + missingSuspenseWrapper: 'Component using "{{name}}" must be wrapped in a Suspense boundary.', + missingLazySuspenseWrapper: 'Lazy component "{{name}}" must be wrapped in a Suspense boundary.', + }, + }, + + create(context) { + const options = context.options[0] || {} + const suspenseHooks = new Set( + options.suspenseHooks || ['useSuspenseQuery', 'useSuspenseInfiniteQuery', 'useSuspenseQueries'] + ) + const suspenseComponents = new Set( + options.suspenseComponents || ['SuspenseQuery', 'SuspenseInfiniteQuery', 'SuspenseQueries'] + ) + const suspenseWrappers = new Set(options.suspenseWrappers || ['Suspense']) + + /** + * Get JSX element name from JSX identifier + */ + function getJSXElementName(nameNode) { + if (nameNode.type === 'JSXIdentifier') { + return nameNode.name + } + if (nameNode.type === 'JSXMemberExpression') { + return `${getJSXElementName(nameNode.object)}.${nameNode.property.name}` + } + return null + } + + /** + * Check if a node is inside a Suspense boundary + */ + function isInsideSuspenseBoundary(node) { + let parent = node.parent + while (parent) { + if (parent.type === 'JSXElement' && parent.openingElement && parent.openingElement.name) { + const elementName = getJSXElementName(parent.openingElement.name) + if (suspenseWrappers.has(elementName)) { + return true + } + } + parent = parent.parent + } + return false + } + + /** + * Check if a call expression is a suspense hook + */ + function isSuspenseHook(node) { + if (node.type !== 'CallExpression') return false + + let name = null + if (node.callee.type === 'Identifier') { + name = node.callee.name + } else if (node.callee.type === 'MemberExpression' && node.callee.property.type === 'Identifier') { + name = node.callee.property.name + } + + return name && suspenseHooks.has(name) + } + + /** + * Check if a JSX element is a lazy component + */ + function isLazyComponent(node) { + if (node.type !== 'JSXElement') return false + + const elementName = getJSXElementName(node.openingElement.name) + if (!elementName) return false + + // Check if the component was created with lazy() + const scope = context.sourceCode.getScope(node) + let currentScope = scope + while (currentScope) { + const variable = currentScope.set.get(elementName) + if (variable && variable.defs.length > 0) { + const def = variable.defs[0] + if (def.node.type === 'VariableDeclarator' && def.node.init) { + return isLazyCall(def.node.init) + } + } + currentScope = currentScope.upper + } + + return false + } + + /** + * Check if a call expression is a lazy() call + */ + function isLazyCall(node) { + if (node.type !== 'CallExpression') return false + + return ( + (node.callee.type === 'Identifier' && node.callee.name === 'lazy') || + (node.callee.type === 'MemberExpression' && + node.callee.property.type === 'Identifier' && + node.callee.property.name === 'lazy') + ) + } + + return { + // Check for suspense hooks + CallExpression: function (node) { + if (isSuspenseHook(node) && !isInsideSuspenseBoundary(node)) { + let name = null + if (node.callee.type === 'Identifier') { + name = node.callee.name + } else if (node.callee.type === 'MemberExpression') { + name = node.callee.property.name + } + + context.report({ + node: node.callee, + messageId: 'missingSuspenseWrapper', + data: { name }, + }) + } + }, + + // Check suspense components and lazy components + JSXElement: function (node) { + const elementName = getJSXElementName(node.openingElement.name) + + // Check if this is a suspense component that needs wrapping + if (elementName && suspenseComponents.has(elementName) && !isInsideSuspenseBoundary(node)) { + context.report({ + node: node.openingElement.name, + messageId: 'missingSuspenseWrapper', + data: { name: elementName }, + }) + } + + // Check if this is a lazy component that needs wrapping + if (isLazyComponent(node) && !isInsideSuspenseBoundary(node)) { + context.report({ + node: node.openingElement.name, + messageId: 'missingLazySuspenseWrapper', + data: { name: elementName }, + }) + } + }, + } + }, +} diff --git a/configs/eslint-config/rules/index.js b/configs/eslint-config/rules/index.js new file mode 100644 index 000000000..98ce79bef --- /dev/null +++ b/configs/eslint-config/rules/index.js @@ -0,0 +1,5 @@ +import checkParentSuspense from './check-parent-suspense.js' + +export default { + 'check-parent-suspense': checkParentSuspense, +} diff --git a/configs/eslint-config/tests/rules/check-parent-suspense.test.js b/configs/eslint-config/tests/rules/check-parent-suspense.test.js new file mode 100644 index 000000000..cc14fc751 --- /dev/null +++ b/configs/eslint-config/tests/rules/check-parent-suspense.test.js @@ -0,0 +1,264 @@ +/** + * @fileoverview Tests for check-parent-suspense rule + * @author Suspensive Team + */ + +import { RuleTester } from 'eslint' +import babelEslintParser from '@babel/eslint-parser' +import rule from '../../rules/check-parent-suspense.js' + +const ruleTester = new RuleTester({ + languageOptions: { + parser: babelEslintParser, + ecmaVersion: 2020, + sourceType: 'module', + parserOptions: { + requireConfigFile: false, + babelOptions: { + presets: ['@babel/preset-react'], + }, + ecmaFeatures: { + jsx: true, + }, + }, + }, +}) + +ruleTester.run('check-parent-suspense', rule, { + valid: [ + // Valid: useSuspenseQuery wrapped in Suspense in same component + { + code: ` + function Component() { + return ( + Loading...}> +
+ {(() => { + const query = useSuspenseQuery() + return
{query.data}
+ })()} +
+
+ ) + } + `, + }, + + // Valid: SuspenseQuery wrapped in Suspense + { + code: ` + function Component() { + return ( + Loading...}> + + {(data) =>
{data}
} +
+
+ ) + } + `, + }, + + // Valid: lazy component wrapped in Suspense + { + code: ` + const LazyComponent = lazy(() => import('./Component')) + + function App() { + return ( + Loading...}> + + + ) + } + `, + }, + + // Valid: nested Suspense boundaries + { + code: ` + function Component() { + return ( + Outer loading...}> +
+ Inner loading...
}> + + {(data) =>
{data}
} +
+
+ +
+ ) + } + `, + }, + + // Valid: non-suspense hooks + { + code: ` + function Component() { + const query = useQuery() + return
{query.data}
+ } + `, + }, + + // Valid: regular components + { + code: ` + function Component() { + return
Hello World
+ } + `, + }, + ], + + invalid: [ + // Invalid: useSuspenseQuery not wrapped in Suspense + { + code: ` + function Component() { + const query = useSuspenseQuery() + return
{query.data}
+ } + `, + errors: [ + { + messageId: 'missingSuspenseWrapper', + data: { name: 'useSuspenseQuery' }, + }, + ], + }, + + // Invalid: useSuspenseInfiniteQuery not wrapped + { + code: ` + function Component() { + const query = useSuspenseInfiniteQuery() + return
{query.data}
+ } + `, + errors: [ + { + messageId: 'missingSuspenseWrapper', + data: { name: 'useSuspenseInfiniteQuery' }, + }, + ], + }, + + // Invalid: useSuspenseQueries not wrapped + { + code: ` + function Component() { + const queries = useSuspenseQueries() + return
{queries[0].data}
+ } + `, + errors: [ + { + messageId: 'missingSuspenseWrapper', + data: { name: 'useSuspenseQueries' }, + }, + ], + }, + + // Invalid: SuspenseQuery not wrapped in Suspense + { + code: ` + function Component() { + return ( + + {(data) =>
{data}
} +
+ ) + } + `, + errors: [ + { + messageId: 'missingSuspenseWrapper', + data: { name: 'SuspenseQuery' }, + }, + ], + }, + + // Invalid: SuspenseInfiniteQuery not wrapped + { + code: ` + function Component() { + return ( + + {(data) =>
{data}
} +
+ ) + } + `, + errors: [ + { + messageId: 'missingSuspenseWrapper', + data: { name: 'SuspenseInfiniteQuery' }, + }, + ], + }, + + // Invalid: lazy component not wrapped in Suspense + { + code: ` + const LazyComponent = lazy(() => import('./Component')) + + function App() { + return + } + `, + errors: [ + { + messageId: 'missingLazySuspenseWrapper', + data: { name: 'LazyComponent' }, + }, + ], + }, + + // Invalid: multiple violations + { + code: ` + function Component() { + const query = useSuspenseQuery() + return ( +
+ + {(data) =>
{data}
} +
+
+ ) + } + `, + errors: [ + { + messageId: 'missingSuspenseWrapper', + data: { name: 'useSuspenseQuery' }, + }, + { + messageId: 'missingSuspenseWrapper', + data: { name: 'SuspenseQuery' }, + }, + ], + }, + + // Invalid: member expression usage + { + code: ` + function Component() { + const query = TanStackQuery.useSuspenseQuery() + return
{query.data}
+ } + `, + errors: [ + { + messageId: 'missingSuspenseWrapper', + data: { name: 'useSuspenseQuery' }, + }, + ], + }, + ], +}) + +console.log('All tests passed!') diff --git a/examples/next-streaming-react-query/src/app/page.tsx b/examples/next-streaming-react-query/src/app/page.tsx index 5f1ab59f3..7ff189690 100644 --- a/examples/next-streaming-react-query/src/app/page.tsx +++ b/examples/next-streaming-react-query/src/app/page.tsx @@ -8,6 +8,7 @@ import type { ReactNode } from 'react' import { query } from '~/query' const Text = ({ ms }: { ms: number }) => { + // eslint-disable-next-line @suspensive/check-parent-suspense const { data } = useSuspenseQuery(query.text(ms)) return

result: {data}

} diff --git a/examples/visualization/src/app/react/zodSearchParams/page.tsx b/examples/visualization/src/app/react/zodSearchParams/page.tsx index 998c93a8f..1337124e4 100644 --- a/examples/visualization/src/app/react/zodSearchParams/page.tsx +++ b/examples/visualization/src/app/react/zodSearchParams/page.tsx @@ -32,6 +32,7 @@ export default ErrorBoundary.with( Suspense.with({ fallback: }, () => { const searchParams = useSearchParams() const { id } = searchParamsSchema.parse(Object.fromEntries(searchParams.entries())) + // eslint-disable-next-line @suspensive/check-parent-suspense const userQuery = useSuspenseQuery({ queryKey: ['users', id] as const, queryFn: ({ queryKey: [, userId] }) => delay(200).then(() => ({ id: userId, name: 'John' })), diff --git a/examples/vite-react-18-suspense-prerender-siblings-problem/src/App.tsx b/examples/vite-react-18-suspense-prerender-siblings-problem/src/App.tsx index 2fbee7ce5..44499a77a 100644 --- a/examples/vite-react-18-suspense-prerender-siblings-problem/src/App.tsx +++ b/examples/vite-react-18-suspense-prerender-siblings-problem/src/App.tsx @@ -41,6 +41,7 @@ const Slow = () => { const Suspend = ({ i }: { i: number }) => { console.log({ Suspend: `before Suspend${i + 1}` }) + // eslint-disable-next-line @suspensive/check-parent-suspense useSuspenseQuery(query.dummy(i)) console.log({ Suspend: `after Suspend${i + 1}` }) return {i} diff --git a/examples/vite-react-18-suspense-prerender-siblings-problem/tsconfig.tsbuildinfo b/examples/vite-react-18-suspense-prerender-siblings-problem/tsconfig.tsbuildinfo index 654de112c..832ae3958 100644 --- a/examples/vite-react-18-suspense-prerender-siblings-problem/tsconfig.tsbuildinfo +++ b/examples/vite-react-18-suspense-prerender-siblings-problem/tsconfig.tsbuildinfo @@ -1 +1 @@ -{"root":["./src/app.tsx","./src/main.tsx","./src/vite-env.d.ts","./vite.config.ts"],"version":"5.8.3"} \ No newline at end of file +{"root":["./src/App.tsx","./src/main.tsx","./src/vite-env.d.ts","./vite.config.ts"],"version":"5.8.3"} \ No newline at end of file diff --git a/packages/react-query-4/src/SuspenseInfiniteQuery.tsx b/packages/react-query-4/src/SuspenseInfiniteQuery.tsx index 6542b1ad5..59fa3d8dc 100644 --- a/packages/react-query-4/src/SuspenseInfiniteQuery.tsx +++ b/packages/react-query-4/src/SuspenseInfiniteQuery.tsx @@ -27,4 +27,8 @@ export const SuspenseInfiniteQuery = < ...options }: UseSuspenseInfiniteQueryOptions & { children: (query: UseSuspenseInfiniteQueryResult) => ReactNode -}) => <>{children(useSuspenseInfiniteQuery(options))} +}) => ( + <> + {children(useSuspenseInfiniteQuery(options))} + +) diff --git a/packages/react-query-4/src/SuspenseQueries.tsx b/packages/react-query-4/src/SuspenseQueries.tsx index 57f9b4def..4c20fa482 100644 --- a/packages/react-query-4/src/SuspenseQueries.tsx +++ b/packages/react-query-4/src/SuspenseQueries.tsx @@ -23,5 +23,9 @@ export function SuspenseQueries({ queries: readonly [...SuspenseQueriesOptions] children: (queries: SuspenseQueriesResults) => ReactNode }) { - return <>{children(useSuspenseQueries({ queries }))} + return ( + <> + {children(useSuspenseQueries({ queries }))} + + ) } diff --git a/packages/react-query-4/src/SuspenseQuery.tsx b/packages/react-query-4/src/SuspenseQuery.tsx index 0b710f9e2..e63ca2b07 100644 --- a/packages/react-query-4/src/SuspenseQuery.tsx +++ b/packages/react-query-4/src/SuspenseQuery.tsx @@ -32,4 +32,8 @@ export const SuspenseQuery = < ...options }: UseSuspenseQueryOptions & { children: (queryResult: UseSuspenseQueryResult) => ReactNode -}) => <>{children(useSuspenseQuery(options))} +}) => ( + <> + {children(useSuspenseQuery(options))} + +) diff --git a/packages/react-query-5/src/SuspenseInfiniteQuery.tsx b/packages/react-query-5/src/SuspenseInfiniteQuery.tsx index 84ad5aecf..286023abf 100644 --- a/packages/react-query-5/src/SuspenseInfiniteQuery.tsx +++ b/packages/react-query-5/src/SuspenseInfiniteQuery.tsx @@ -35,4 +35,8 @@ export const SuspenseInfiniteQuery = < ...options }: UseSuspenseInfiniteQueryOptions & { children: (query: UseSuspenseInfiniteQueryResult) => ReactNode -}) => <>{children(useSuspenseInfiniteQuery(options))} +}) => ( + <> + {children(useSuspenseInfiniteQuery(options))} + +) diff --git a/packages/react-query-5/src/SuspenseQueries.tsx b/packages/react-query-5/src/SuspenseQueries.tsx index 10007df3c..04c70c548 100644 --- a/packages/react-query-5/src/SuspenseQueries.tsx +++ b/packages/react-query-5/src/SuspenseQueries.tsx @@ -25,5 +25,9 @@ export function SuspenseQueries ReactNode combine?: (result: SuspenseQueriesResults) => TCombinedResult }) { - return <>{children(useSuspenseQueries({ queries, combine }))} + return ( + <> + {children(useSuspenseQueries({ queries, combine }))} + + ) } diff --git a/packages/react-query-5/src/SuspenseQuery.tsx b/packages/react-query-5/src/SuspenseQuery.tsx index 842c145f4..4f330f1e5 100644 --- a/packages/react-query-5/src/SuspenseQuery.tsx +++ b/packages/react-query-5/src/SuspenseQuery.tsx @@ -32,4 +32,8 @@ export const SuspenseQuery = < ...options }: UseSuspenseQueryOptions & { children: (queryResult: UseSuspenseQueryResult) => ReactNode -}) => <>{children(useSuspenseQuery(options))} +}) => ( + <> + {children(useSuspenseQuery(options))} + +) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7189a4308..4f07a3020 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -231,6 +231,13 @@ importers: typescript-eslint: specifier: ^8.31.0 version: 8.31.0(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) + devDependencies: + '@babel/eslint-parser': + specifier: ^7.28.4 + version: 7.28.4(@babel/core@7.27.1)(eslint@9.28.0(jiti@2.4.2)) + '@babel/preset-react': + specifier: ^7.27.1 + version: 7.27.1(@babel/core@7.27.1) configs/tsconfig: {} @@ -418,7 +425,7 @@ importers: version: 29.7.0(@types/node@22.14.1)(ts-node@10.9.2(@types/node@22.14.1)(typescript@5.8.3)) jest-expo: specifier: catalog:react19 - version: 52.0.2(@babel/core@7.27.1)(expo@52.0.24(@babel/core@7.27.1)(@babel/preset-env@7.26.0(@babel/core@7.27.1))(@expo/metro-runtime@4.0.0(react-native@0.76.6(@babel/core@7.27.1)(@babel/preset-env@7.26.0(@babel/core@7.27.1))(@types/react@19.1.6)(react@19.1.0)))(graphql@16.9.0)(react-native-webview@13.12.5(react-native@0.76.6(@babel/core@7.27.1)(@babel/preset-env@7.26.0(@babel/core@7.27.1))(@types/react@19.1.6)(react@19.1.0))(react@19.1.0))(react-native@0.76.6(@babel/core@7.27.1)(@babel/preset-env@7.26.0(@babel/core@7.27.1))(@types/react@19.1.6)(react@19.1.0))(react@19.1.0))(jest@29.7.0(@types/node@22.14.1)(ts-node@10.9.2(@types/node@22.14.1)(typescript@5.8.3)))(react-dom@19.1.0(react@19.1.0))(react-native@0.76.6(@babel/core@7.27.1)(@babel/preset-env@7.26.0(@babel/core@7.27.1))(@types/react@19.1.6)(react@19.1.0))(react@19.1.0)(webpack@5.96.1) + version: 52.0.2(@babel/core@7.27.1)(expo@52.0.24(@babel/core@7.27.1)(@babel/preset-env@7.26.0(@babel/core@7.27.1))(@expo/metro-runtime@4.0.0(react-native@0.76.6(@babel/core@7.27.1)(@babel/preset-env@7.26.0(@babel/core@7.27.1))(@types/react@19.1.6)(react@19.1.0)))(graphql@16.9.0)(react-native-webview@13.12.5(react-native@0.76.6(@babel/core@7.27.1)(@babel/preset-env@7.26.0(@babel/core@7.27.1))(@types/react@19.1.6)(react@19.1.0))(react@19.1.0))(react-native@0.76.6(@babel/core@7.27.1)(@babel/preset-env@7.26.0(@babel/core@7.27.1))(@types/react@19.1.6)(react@19.1.0))(react@19.1.0))(jest@29.7.0(@types/node@22.14.1))(react-dom@19.1.0(react@19.1.0))(react-native@0.76.6(@babel/core@7.27.1)(@babel/preset-env@7.26.0(@babel/core@7.27.1))(@types/react@19.1.6)(react@19.1.0))(react@19.1.0)(webpack@5.96.1) react-test-renderer: specifier: 19.0.0 version: 19.0.0(react@19.1.0) @@ -655,7 +662,7 @@ importers: version: 29.7.0(@types/node@22.14.1)(ts-node@10.9.2(@types/node@22.14.1)(typescript@5.8.3)) jest-expo: specifier: catalog:react19 - version: 52.0.2(@babel/core@7.27.1)(expo@52.0.24(@babel/core@7.27.1)(@babel/preset-env@7.26.0(@babel/core@7.27.1))(@expo/metro-runtime@4.0.0(react-native@0.76.6(@babel/core@7.27.1)(@babel/preset-env@7.26.0(@babel/core@7.27.1))(@types/react@19.1.6)(react@19.1.0)))(graphql@16.9.0)(react-native-webview@13.12.5(react-native@0.76.6(@babel/core@7.27.1)(@babel/preset-env@7.26.0(@babel/core@7.27.1))(@types/react@19.1.6)(react@19.1.0))(react@19.1.0))(react-native@0.76.6(@babel/core@7.27.1)(@babel/preset-env@7.26.0(@babel/core@7.27.1))(@types/react@19.1.6)(react@19.1.0))(react@19.1.0))(jest@29.7.0(@types/node@22.14.1)(ts-node@10.9.2(@types/node@22.14.1)(typescript@5.8.3)))(react-dom@19.1.0(react@19.1.0))(react-native@0.76.6(@babel/core@7.27.1)(@babel/preset-env@7.26.0(@babel/core@7.27.1))(@types/react@19.1.6)(react@19.1.0))(react@19.1.0)(webpack@5.96.1) + version: 52.0.2(@babel/core@7.27.1)(expo@52.0.24(@babel/core@7.27.1)(@babel/preset-env@7.26.0(@babel/core@7.27.1))(@expo/metro-runtime@4.0.0(react-native@0.76.6(@babel/core@7.27.1)(@babel/preset-env@7.26.0(@babel/core@7.27.1))(@types/react@19.1.6)(react@19.1.0)))(graphql@16.9.0)(react-native-webview@13.12.5(react-native@0.76.6(@babel/core@7.27.1)(@babel/preset-env@7.26.0(@babel/core@7.27.1))(@types/react@19.1.6)(react@19.1.0))(react@19.1.0))(react-native@0.76.6(@babel/core@7.27.1)(@babel/preset-env@7.26.0(@babel/core@7.27.1))(@types/react@19.1.6)(react@19.1.0))(react@19.1.0))(jest@29.7.0(@types/node@22.14.1))(react-dom@19.1.0(react@19.1.0))(react-native@0.76.6(@babel/core@7.27.1)(@babel/preset-env@7.26.0(@babel/core@7.27.1))(@types/react@19.1.6)(react@19.1.0))(react@19.1.0)(webpack@5.96.1) react: specifier: catalog:react19 version: 19.1.0 @@ -806,6 +813,13 @@ packages: resolution: {integrity: sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ==} engines: {node: '>=6.9.0'} + '@babel/eslint-parser@7.28.4': + resolution: {integrity: sha512-Aa+yDiH87980jR6zvRfFuCR1+dLb00vBydhTL+zI992Rz/wQhSvuxjmOOuJOgO3XmakO6RykRGD2S1mq1AtgHA==} + engines: {node: ^10.13.0 || ^12.13.0 || >=14.0.0} + peerDependencies: + '@babel/core': ^7.11.0 + eslint: ^7.5.0 || ^8.0.0 || ^9.0.0 + '@babel/generator@7.27.1': resolution: {integrity: sha512-UnJfnIpc/+JO0/+KRVQNGU+y5taA5vCbwN8+azkX6beii/ZF+enZJSOKo11ZSzGJjlNfJHfQtmQT8H+9TXPG2w==} engines: {node: '>=6.9.0'} @@ -818,6 +832,10 @@ packages: resolution: {integrity: sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==} engines: {node: '>=6.9.0'} + '@babel/helper-annotate-as-pure@7.27.3': + resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==} + engines: {node: '>=6.9.0'} + '@babel/helper-compilation-targets@7.27.1': resolution: {integrity: sha512-2YaDd/Rd9E598B5+WIc8wJPmWETiiJXFYVE60oX8FDohv7rAUU3CQj+A1MgeEmcsk2+dQuEjIe/GDvig0SqL4g==} engines: {node: '>=6.9.0'} @@ -843,10 +861,6 @@ packages: resolution: {integrity: sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==} engines: {node: '>=6.9.0'} - '@babel/helper-module-imports@7.25.9': - resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==} - engines: {node: '>=6.9.0'} - '@babel/helper-module-imports@7.27.1': resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} engines: {node: '>=6.9.0'} @@ -871,6 +885,10 @@ packages: resolution: {integrity: sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==} engines: {node: '>=6.9.0'} + '@babel/helper-plugin-utils@7.27.1': + resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==} + engines: {node: '>=6.9.0'} + '@babel/helper-remap-async-to-generator@7.25.9': resolution: {integrity: sha512-IZtukuUeBbhgOcaW2s06OXTzVNJR0ybm4W5xC1opWFFJMZbwRj5LCk+ByYH7WdZPZTt8KnFwA8pvjN2yqcPlgw==} engines: {node: '>=6.9.0'} @@ -907,10 +925,6 @@ packages: resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-option@7.25.9': - resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==} - engines: {node: '>=6.9.0'} - '@babel/helper-validator-option@7.27.1': resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} engines: {node: '>=6.9.0'} @@ -1083,6 +1097,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-jsx@7.27.1': + resolution: {integrity: sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-logical-assignment-operators@7.10.4': resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} peerDependencies: @@ -1383,12 +1403,24 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-react-display-name@7.28.0': + resolution: {integrity: sha512-D6Eujc2zMxKjfa4Zxl4GHMsmhKKZ9VpcqIchJLvwTxad9zWIYulwYItBovpDOoNLISpcZSXoDJ5gaGbQUDqViA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-react-jsx-development@7.25.9': resolution: {integrity: sha512-9mj6rm7XVYs4mdLIpbZnHOYdpW42uoiBCTVowg7sP1thUOiANgMb4UtpRivR0pp5iL+ocvUv7X4mZgFRpJEzGw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-react-jsx-development@7.27.1': + resolution: {integrity: sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-react-jsx-self@7.25.9': resolution: {integrity: sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==} engines: {node: '>=6.9.0'} @@ -1407,12 +1439,24 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-react-jsx@7.27.1': + resolution: {integrity: sha512-2KH4LWGSrJIkVf5tSiBFYuXDAoWRq2MMwgivCf+93dd0GQi8RXLjKA/0EvRnVV5G0hrHczsquXuD01L8s6dmBw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-react-pure-annotations@7.25.9': resolution: {integrity: sha512-KQ/Takk3T8Qzj5TppkS1be588lkbTp5uj7w6a0LeQaTMSckU/wK0oJ/pih+T690tkgI5jfmg2TqDJvd41Sj1Cg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-react-pure-annotations@7.27.1': + resolution: {integrity: sha512-JfuinvDOsD9FVMTHpzA/pBLisxpv1aSf+OIV8lgH3MuWrks19R27e6a6DipIg4aX1Zm9Wpb04p8wljfKrVSnPA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-regenerator@7.25.9': resolution: {integrity: sha512-vwDcDNsgMPDGP0nMqzahDWE5/MLcX8sv96+wfX7as7LoF/kr97Bo/7fI00lXY4wUXYfVmwIIyG80fGZ1uvt2qg==} engines: {node: '>=6.9.0'} @@ -1520,6 +1564,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/preset-react@7.27.1': + resolution: {integrity: sha512-oJHWh2gLhU9dW9HHr42q0cI0/iHHXTLGe39qvpAZZzagHy0MzYLCnCVV0symeRvzmjHyVU7mw2K06E6u/JwbhA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/preset-typescript@7.26.0': resolution: {integrity: sha512-NMk1IGZ5I/oHhoXEElcm+xUnL/szL6xflkFZmoEU9xj1qSJXpiS7rsspYo92B4DRCDvZn2erT5LdsCeXAKNCkg==} engines: {node: '>=6.9.0'} @@ -2790,6 +2840,9 @@ packages: next: ^13.0.0 || ^14.0.0 || ^15.0.0 react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + '@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1': + resolution: {integrity: sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==} + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -5816,6 +5869,10 @@ packages: resolution: {integrity: sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + eslint-visitor-keys@2.1.0: + resolution: {integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==} + engines: {node: '>=10'} + eslint-visitor-keys@3.4.3: resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -11065,6 +11122,14 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/eslint-parser@7.28.4(@babel/core@7.27.1)(eslint@9.28.0(jiti@2.4.2))': + dependencies: + '@babel/core': 7.27.1 + '@nicolo-ribaudo/eslint-scope-5-internals': 5.1.1-v1 + eslint: 9.28.0(jiti@2.4.2) + eslint-visitor-keys: 2.1.0 + semver: 6.3.1 + '@babel/generator@7.27.1': dependencies: '@babel/parser': 7.27.1 @@ -11085,6 +11150,10 @@ snapshots: dependencies: '@babel/types': 7.28.2 + '@babel/helper-annotate-as-pure@7.27.3': + dependencies: + '@babel/types': 7.28.2 + '@babel/helper-compilation-targets@7.27.1': dependencies: '@babel/compat-data': 7.27.1 @@ -11131,13 +11200,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/helper-module-imports@7.25.9': - dependencies: - '@babel/traverse': 7.27.1 - '@babel/types': 7.28.2 - transitivePeerDependencies: - - supports-color - '@babel/helper-module-imports@7.27.1': dependencies: '@babel/traverse': 7.27.1 @@ -11148,7 +11210,7 @@ snapshots: '@babel/helper-module-transforms@7.26.0(@babel/core@7.27.1)': dependencies: '@babel/core': 7.27.1 - '@babel/helper-module-imports': 7.25.9 + '@babel/helper-module-imports': 7.27.1 '@babel/helper-validator-identifier': 7.25.9 '@babel/traverse': 7.25.9 transitivePeerDependencies: @@ -11169,6 +11231,8 @@ snapshots: '@babel/helper-plugin-utils@7.25.9': {} + '@babel/helper-plugin-utils@7.27.1': {} + '@babel/helper-remap-async-to-generator@7.25.9(@babel/core@7.27.1)': dependencies: '@babel/core': 7.27.1 @@ -11209,8 +11273,6 @@ snapshots: '@babel/helper-validator-identifier@7.27.1': {} - '@babel/helper-validator-option@7.25.9': {} - '@babel/helper-validator-option@7.27.1': {} '@babel/helper-wrap-function@7.25.9': @@ -11386,6 +11448,11 @@ snapshots: '@babel/core': 7.27.1 '@babel/helper-plugin-utils': 7.25.9 + '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.27.1)': + dependencies: + '@babel/core': 7.27.1 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.27.1)': dependencies: '@babel/core': 7.27.1 @@ -11708,6 +11775,11 @@ snapshots: '@babel/core': 7.27.1 '@babel/helper-plugin-utils': 7.25.9 + '@babel/plugin-transform-react-display-name@7.28.0(@babel/core@7.27.1)': + dependencies: + '@babel/core': 7.27.1 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/plugin-transform-react-jsx-development@7.25.9(@babel/core@7.27.1)': dependencies: '@babel/core': 7.27.1 @@ -11715,6 +11787,13 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/plugin-transform-react-jsx-development@7.27.1(@babel/core@7.27.1)': + dependencies: + '@babel/core': 7.27.1 + '@babel/plugin-transform-react-jsx': 7.27.1(@babel/core@7.27.1) + transitivePeerDependencies: + - supports-color + '@babel/plugin-transform-react-jsx-self@7.25.9(@babel/core@7.27.1)': dependencies: '@babel/core': 7.27.1 @@ -11736,12 +11815,29 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/plugin-transform-react-jsx@7.27.1(@babel/core@7.27.1)': + dependencies: + '@babel/core': 7.27.1 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-module-imports': 7.27.1 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.27.1) + '@babel/types': 7.28.2 + transitivePeerDependencies: + - supports-color + '@babel/plugin-transform-react-pure-annotations@7.25.9(@babel/core@7.27.1)': dependencies: '@babel/core': 7.27.1 '@babel/helper-annotate-as-pure': 7.25.9 '@babel/helper-plugin-utils': 7.25.9 + '@babel/plugin-transform-react-pure-annotations@7.27.1(@babel/core@7.27.1)': + dependencies: + '@babel/core': 7.27.1 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/plugin-transform-regenerator@7.25.9(@babel/core@7.27.1)': dependencies: '@babel/core': 7.27.1 @@ -11912,7 +12008,7 @@ snapshots: dependencies: '@babel/core': 7.27.1 '@babel/helper-plugin-utils': 7.25.9 - '@babel/helper-validator-option': 7.25.9 + '@babel/helper-validator-option': 7.27.1 '@babel/plugin-transform-flow-strip-types': 7.25.9(@babel/core@7.27.1) '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.27.1)': @@ -11934,11 +12030,23 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/preset-react@7.27.1(@babel/core@7.27.1)': + dependencies: + '@babel/core': 7.27.1 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-validator-option': 7.27.1 + '@babel/plugin-transform-react-display-name': 7.28.0(@babel/core@7.27.1) + '@babel/plugin-transform-react-jsx': 7.27.1(@babel/core@7.27.1) + '@babel/plugin-transform-react-jsx-development': 7.27.1(@babel/core@7.27.1) + '@babel/plugin-transform-react-pure-annotations': 7.27.1(@babel/core@7.27.1) + transitivePeerDependencies: + - supports-color + '@babel/preset-typescript@7.26.0(@babel/core@7.27.1)': dependencies: '@babel/core': 7.27.1 '@babel/helper-plugin-utils': 7.25.9 - '@babel/helper-validator-option': 7.25.9 + '@babel/helper-validator-option': 7.27.1 '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.27.1) '@babel/plugin-transform-modules-commonjs': 7.25.9(@babel/core@7.27.1) '@babel/plugin-transform-typescript': 7.25.9(@babel/core@7.27.1) @@ -13710,6 +13818,10 @@ snapshots: react: 19.1.0 third-party-capital: 1.0.20 + '@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1': + dependencies: + eslint-scope: 5.1.1 + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -17255,6 +17367,8 @@ snapshots: esrecurse: 4.3.0 estraverse: 5.3.0 + eslint-visitor-keys@2.1.0: {} + eslint-visitor-keys@3.4.3: {} eslint-visitor-keys@4.2.0: {} @@ -18712,7 +18826,7 @@ snapshots: jest-mock: 29.7.0 jest-util: 29.7.0 - jest-expo@52.0.2(@babel/core@7.27.1)(expo@52.0.24(@babel/core@7.27.1)(@babel/preset-env@7.26.0(@babel/core@7.27.1))(@expo/metro-runtime@4.0.0(react-native@0.76.6(@babel/core@7.27.1)(@babel/preset-env@7.26.0(@babel/core@7.27.1))(@types/react@19.1.6)(react@19.1.0)))(graphql@16.9.0)(react-native-webview@13.12.5(react-native@0.76.6(@babel/core@7.27.1)(@babel/preset-env@7.26.0(@babel/core@7.27.1))(@types/react@19.1.6)(react@19.1.0))(react@19.1.0))(react-native@0.76.6(@babel/core@7.27.1)(@babel/preset-env@7.26.0(@babel/core@7.27.1))(@types/react@19.1.6)(react@19.1.0))(react@19.1.0))(jest@29.7.0(@types/node@22.14.1)(ts-node@10.9.2(@types/node@22.14.1)(typescript@5.8.3)))(react-dom@19.1.0(react@19.1.0))(react-native@0.76.6(@babel/core@7.27.1)(@babel/preset-env@7.26.0(@babel/core@7.27.1))(@types/react@19.1.6)(react@19.1.0))(react@19.1.0)(webpack@5.96.1): + jest-expo@52.0.2(@babel/core@7.27.1)(expo@52.0.24(@babel/core@7.27.1)(@babel/preset-env@7.26.0(@babel/core@7.27.1))(@expo/metro-runtime@4.0.0(react-native@0.76.6(@babel/core@7.27.1)(@babel/preset-env@7.26.0(@babel/core@7.27.1))(@types/react@19.1.6)(react@19.1.0)))(graphql@16.9.0)(react-native-webview@13.12.5(react-native@0.76.6(@babel/core@7.27.1)(@babel/preset-env@7.26.0(@babel/core@7.27.1))(@types/react@19.1.6)(react@19.1.0))(react@19.1.0))(react-native@0.76.6(@babel/core@7.27.1)(@babel/preset-env@7.26.0(@babel/core@7.27.1))(@types/react@19.1.6)(react@19.1.0))(react@19.1.0))(jest@29.7.0(@types/node@22.14.1))(react-dom@19.1.0(react@19.1.0))(react-native@0.76.6(@babel/core@7.27.1)(@babel/preset-env@7.26.0(@babel/core@7.27.1))(@types/react@19.1.6)(react@19.1.0))(react@19.1.0)(webpack@5.96.1): dependencies: '@expo/config': 10.0.7 '@expo/json-file': 9.0.0 @@ -18725,7 +18839,7 @@ snapshots: jest-environment-jsdom: 29.7.0 jest-snapshot: 29.7.0 jest-watch-select-projects: 2.0.0 - jest-watch-typeahead: 2.2.1(jest@29.7.0(@types/node@22.14.1)(ts-node@10.9.2(@types/node@22.14.1)(typescript@5.8.3))) + jest-watch-typeahead: 2.2.1(jest@29.7.0(@types/node@22.14.1)) json5: 2.2.3 lodash: 4.17.21 react-native: 0.76.6(@babel/core@7.27.1)(@babel/preset-env@7.26.0(@babel/core@7.27.1))(@types/react@19.1.6)(react@19.1.0) @@ -18920,7 +19034,7 @@ snapshots: chalk: 3.0.0 prompts: 2.4.2 - jest-watch-typeahead@2.2.1(jest@29.7.0(@types/node@22.14.1)(ts-node@10.9.2(@types/node@22.14.1)(typescript@5.8.3))): + jest-watch-typeahead@2.2.1(jest@29.7.0(@types/node@22.14.1)): dependencies: ansi-escapes: 6.2.1 chalk: 4.1.2