mirror of
https://github.com/jakejarvis/sofa.git
synced 2026-08-29 05:05:38 -04:00
chore: upgrade Expo SDK 55 → 57 and React Native 0.83 → 0.86
This commit is contained in:
@@ -69,7 +69,7 @@
|
||||
"expo-build-properties",
|
||||
{
|
||||
"ios": {
|
||||
"deploymentTarget": "16.2"
|
||||
"deploymentTarget": "16.4"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
+48
-50
@@ -17,75 +17,73 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@better-auth/expo": "catalog:",
|
||||
"@expo/metro-runtime": "55.0.6",
|
||||
"@expo/ui": "55.0.5",
|
||||
"@formatjs/intl-datetimeformat": "7.3.1",
|
||||
"@formatjs/intl-getcanonicallocales": "3.2.2",
|
||||
"@formatjs/intl-locale": "5.3.1",
|
||||
"@formatjs/intl-numberformat": "9.3.1",
|
||||
"@formatjs/intl-pluralrules": "6.3.1",
|
||||
"@formatjs/intl-relativetimeformat": "12.3.1",
|
||||
"@expo/metro-runtime": "~57.0.3",
|
||||
"@expo/ui": "~57.0.3",
|
||||
"@formatjs/intl-datetimeformat": "7.4.9",
|
||||
"@formatjs/intl-getcanonicallocales": "3.2.10",
|
||||
"@formatjs/intl-locale": "5.3.9",
|
||||
"@formatjs/intl-numberformat": "9.3.11",
|
||||
"@formatjs/intl-pluralrules": "6.3.10",
|
||||
"@formatjs/intl-relativetimeformat": "12.3.10",
|
||||
"@lingui/core": "catalog:",
|
||||
"@lingui/react": "catalog:",
|
||||
"@orpc/client": "catalog:",
|
||||
"@orpc/contract": "catalog:",
|
||||
"@orpc/tanstack-query": "catalog:",
|
||||
"@react-native-menu/menu": "2.0.0",
|
||||
"@react-navigation/elements": "2.9.11",
|
||||
"@react-navigation/native": "7.1.34",
|
||||
"@sentry/react-native": "8.6.0",
|
||||
"@sentry/react-native": "~7.11.0",
|
||||
"@shopify/flash-list": "2.0.2",
|
||||
"@sofa/api": "workspace:*",
|
||||
"@sofa/i18n": "workspace:*",
|
||||
"@tabler/icons-react-native": "3.40.0",
|
||||
"@tanstack/query-async-storage-persister": "5.95.2",
|
||||
"@tabler/icons-react-native": "3.44.0",
|
||||
"@tanstack/query-async-storage-persister": "5.101.2",
|
||||
"@tanstack/react-form": "catalog:",
|
||||
"@tanstack/react-query": "catalog:",
|
||||
"@tanstack/react-query-persist-client": "5.95.2",
|
||||
"@tanstack/react-query-persist-client": "5.101.2",
|
||||
"better-auth": "catalog:",
|
||||
"burnt": "0.13.0",
|
||||
"expo": "55.0.8",
|
||||
"expo-application": "55.0.10",
|
||||
"expo-build-properties": "55.0.10",
|
||||
"expo-clipboard": "55.0.9",
|
||||
"expo-constants": "55.0.9",
|
||||
"expo-dev-client": "55.0.18",
|
||||
"expo-device": "55.0.10",
|
||||
"expo-font": "55.0.4",
|
||||
"expo-glass-effect": "55.0.8",
|
||||
"expo-haptics": "55.0.9",
|
||||
"expo-image": "55.0.6",
|
||||
"expo-image-picker": "55.0.13",
|
||||
"expo-linear-gradient": "55.0.9",
|
||||
"expo-linking": "55.0.8",
|
||||
"expo-localization": "55.0.9",
|
||||
"expo-network": "55.0.9",
|
||||
"expo-router": "55.0.7",
|
||||
"expo-secure-store": "55.0.9",
|
||||
"expo-splash-screen": "55.0.12",
|
||||
"expo-status-bar": "55.0.4",
|
||||
"expo-system-ui": "55.0.10",
|
||||
"expo-updates": "55.0.15",
|
||||
"expo-web-browser": "55.0.10",
|
||||
"expo-widgets": "55.0.7",
|
||||
"posthog-react-native": "4.37.6",
|
||||
"expo": "^57.0.0",
|
||||
"expo-application": "~57.0.0",
|
||||
"expo-build-properties": "~57.0.3",
|
||||
"expo-clipboard": "~57.0.0",
|
||||
"expo-constants": "~57.0.3",
|
||||
"expo-dev-client": "~57.0.5",
|
||||
"expo-device": "~57.0.0",
|
||||
"expo-font": "~57.0.0",
|
||||
"expo-glass-effect": "~57.0.0",
|
||||
"expo-haptics": "~57.0.0",
|
||||
"expo-image": "~57.0.0",
|
||||
"expo-image-picker": "~57.0.2",
|
||||
"expo-linear-gradient": "~57.0.0",
|
||||
"expo-linking": "~57.0.1",
|
||||
"expo-localization": "~57.0.0",
|
||||
"expo-network": "~57.0.0",
|
||||
"expo-router": "~57.0.3",
|
||||
"expo-secure-store": "~57.0.0",
|
||||
"expo-splash-screen": "~57.0.2",
|
||||
"expo-status-bar": "~57.0.0",
|
||||
"expo-system-ui": "~57.0.0",
|
||||
"expo-updates": "~57.0.6",
|
||||
"expo-web-browser": "~57.0.0",
|
||||
"expo-widgets": "~57.0.2",
|
||||
"posthog-react-native": "4.54.4",
|
||||
"react": "catalog:",
|
||||
"react-dom": "catalog:",
|
||||
"react-native": "0.83.2",
|
||||
"react-native-gesture-handler": "2.30.0",
|
||||
"react-native": "0.86.0",
|
||||
"react-native-gesture-handler": "~2.32.0",
|
||||
"react-native-ios-context-menu": "3.2.0",
|
||||
"react-native-ios-utilities": "5.2.0",
|
||||
"react-native-keyboard-controller": "1.20.7",
|
||||
"react-native-mmkv": "4.2.0",
|
||||
"react-native-nitro-modules": "0.35.0",
|
||||
"react-native-reanimated": "4.2.1",
|
||||
"react-native-safe-area-context": "5.6.2",
|
||||
"react-native-screens": "4.23.0",
|
||||
"react-native-svg": "15.15.3",
|
||||
"react-native-worklets": "0.7.2",
|
||||
"react-native-keyboard-controller": "1.21.9",
|
||||
"react-native-mmkv": "4.3.2",
|
||||
"react-native-nitro-modules": "0.36.1",
|
||||
"react-native-reanimated": "4.5.0",
|
||||
"react-native-safe-area-context": "~5.7.0",
|
||||
"react-native-screens": "4.25.2",
|
||||
"react-native-svg": "15.15.4",
|
||||
"react-native-worklets": "0.10.0",
|
||||
"tailwind-merge": "catalog:",
|
||||
"tailwindcss": "catalog:",
|
||||
"uniwind": "1.6.1",
|
||||
"uniwind": "1.10.0",
|
||||
"zeego": "3.0.6",
|
||||
"zod": "catalog:"
|
||||
},
|
||||
|
||||
@@ -27,6 +27,20 @@ function LibraryHeaderRight() {
|
||||
);
|
||||
}
|
||||
|
||||
function renderLibraryHeaderRight() {
|
||||
return <LibraryHeaderRight />;
|
||||
}
|
||||
|
||||
function getLibraryHeaderRightItems() {
|
||||
return [
|
||||
{
|
||||
type: "custom" as const,
|
||||
element: <LibraryHeaderRight />,
|
||||
hidesSharedBackground: true,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export default function LibraryLayout() {
|
||||
const { t } = useLingui();
|
||||
const contentStyle = useResolveClassNames("bg-background");
|
||||
@@ -40,13 +54,7 @@ export default function LibraryLayout() {
|
||||
<Stack
|
||||
screenOptions={{
|
||||
contentStyle,
|
||||
unstable_headerRightItems: () => [
|
||||
{
|
||||
type: "custom" as const,
|
||||
element: <LibraryHeaderRight />,
|
||||
hidesSharedBackground: true,
|
||||
},
|
||||
],
|
||||
unstable_headerRightItems: getLibraryHeaderRightItems,
|
||||
}}
|
||||
>
|
||||
<Stack.Screen name="index">
|
||||
@@ -76,7 +84,7 @@ export default function LibraryLayout() {
|
||||
screenOptions={{
|
||||
contentStyle,
|
||||
headerTitleAlign: "left",
|
||||
headerRight: () => <LibraryHeaderRight />,
|
||||
headerRight: renderLibraryHeaderRight,
|
||||
}}
|
||||
>
|
||||
<Stack.Screen name="index">
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import "@/lib/intl-polyfills";
|
||||
import "@/global.css";
|
||||
import { I18nProvider } from "@lingui/react";
|
||||
import { ThemeProvider } from "@react-navigation/native";
|
||||
import { QueryClientProvider } from "@tanstack/react-query";
|
||||
import {
|
||||
persistQueryClientRestore,
|
||||
persistQueryClientSubscribe,
|
||||
} from "@tanstack/react-query-persist-client";
|
||||
import { Stack, useGlobalSearchParams, usePathname } from "expo-router";
|
||||
import { ThemeProvider } from "expo-router/react-navigation";
|
||||
import * as SplashScreen from "expo-splash-screen";
|
||||
import { StatusBar } from "expo-status-bar";
|
||||
import { PostHogErrorBoundary, PostHogProvider } from "posthog-react-native";
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Trans, useLingui } from "@lingui/react/macro";
|
||||
import { useHeaderHeight } from "@react-navigation/elements";
|
||||
import { FlashList } from "@shopify/flash-list";
|
||||
import {
|
||||
IconAlertTriangle,
|
||||
@@ -10,6 +9,7 @@ import {
|
||||
} from "@tabler/icons-react-native";
|
||||
import { useInfiniteQuery } from "@tanstack/react-query";
|
||||
import { useLocalSearchParams, useRouter } from "expo-router";
|
||||
import { useHeaderHeight } from "expo-router/react-navigation";
|
||||
import { useCallback, useEffect, useMemo } from "react";
|
||||
import { Pressable, useWindowDimensions, View } from "react-native";
|
||||
import Animated, { FadeIn, FadeInDown } from "react-native-reanimated";
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Trans, useLingui } from "@lingui/react/macro";
|
||||
import { useHeaderHeight } from "@react-navigation/elements";
|
||||
import { FlashList } from "@shopify/flash-list";
|
||||
import {
|
||||
IconAlertTriangle,
|
||||
@@ -18,6 +17,7 @@ import { useQuery } from "@tanstack/react-query";
|
||||
import { GlassView, isLiquidGlassAvailable } from "expo-glass-effect";
|
||||
import { LinearGradient } from "expo-linear-gradient";
|
||||
import { Link, useLocalSearchParams, useRouter } from "expo-router";
|
||||
import { useHeaderHeight } from "expo-router/react-navigation";
|
||||
import * as WebBrowser from "expo-web-browser";
|
||||
import { useCallback, useEffect, useMemo } from "react";
|
||||
import { Pressable, ScrollView, StyleSheet, View } from "react-native";
|
||||
@@ -57,10 +57,10 @@ const titleGenresContentStyle = { paddingHorizontal: 16 };
|
||||
const titleAvailabilityContentStyle = { gap: 8, paddingHorizontal: 16 };
|
||||
const titleDetailStyles = StyleSheet.create({
|
||||
heroFill: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
...StyleSheet.absoluteFill,
|
||||
},
|
||||
heroBaseOverlay: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
...StyleSheet.absoluteFill,
|
||||
backgroundColor: "rgba(0,0,0,0.1)",
|
||||
},
|
||||
heroGradient: {
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { StyleProp, ViewStyle } from "react-native";
|
||||
import { ScrollView } from "react-native";
|
||||
import { KeyboardAvoidingView } from "react-native-keyboard-controller";
|
||||
import Animated, { FadeIn } from "react-native-reanimated";
|
||||
import type { AnimatedStyle } from "react-native-reanimated";
|
||||
|
||||
import { SofaLogo } from "@/components/ui/sofa-logo";
|
||||
import { Text } from "@/components/ui/text";
|
||||
@@ -10,7 +11,7 @@ import { Text } from "@/components/ui/text";
|
||||
interface AuthScreenProps {
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
logoStyle?: StyleProp<ViewStyle>;
|
||||
logoStyle?: StyleProp<AnimatedStyle<ViewStyle>>;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ export function NativeTabBar() {
|
||||
<NativeTabs.Trigger.Label>{t`Settings`}</NativeTabs.Trigger.Label>
|
||||
<NativeTabs.Trigger.Icon sf="gear" md="settings" />
|
||||
</NativeTabs.Trigger>
|
||||
<NativeTabs.Trigger name="(search)" role="search" disableTransparentOnScrollEdge>
|
||||
<NativeTabs.Trigger name="(search)" disableTransparentOnScrollEdge>
|
||||
<NativeTabs.Trigger.Label>{t`Search`}</NativeTabs.Trigger.Label>
|
||||
<NativeTabs.Trigger.Icon sf="magnifyingglass" md="search" />
|
||||
</NativeTabs.Trigger>
|
||||
|
||||
@@ -4,6 +4,20 @@ import { useCSSVariable, useResolveClassNames } from "uniwind";
|
||||
|
||||
import { HeaderAvatar } from "@/components/header-avatar";
|
||||
|
||||
function renderHeaderAvatar() {
|
||||
return <HeaderAvatar />;
|
||||
}
|
||||
|
||||
function getHeaderRightItems() {
|
||||
return [
|
||||
{
|
||||
type: "custom" as const,
|
||||
element: <HeaderAvatar />,
|
||||
hidesSharedBackground: true,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function TabStack({ title, children }: { title?: string; children?: ReactNode }) {
|
||||
const contentStyle = useResolveClassNames("bg-background");
|
||||
const tintColor = useCSSVariable("--color-primary") as string;
|
||||
@@ -16,13 +30,7 @@ export function TabStack({ title, children }: { title?: string; children?: React
|
||||
<Stack
|
||||
screenOptions={{
|
||||
contentStyle,
|
||||
unstable_headerRightItems: () => [
|
||||
{
|
||||
type: "custom" as const,
|
||||
element: <HeaderAvatar />,
|
||||
hidesSharedBackground: true,
|
||||
},
|
||||
],
|
||||
unstable_headerRightItems: getHeaderRightItems,
|
||||
}}
|
||||
>
|
||||
{title ? (
|
||||
@@ -55,7 +63,7 @@ export function TabStack({ title, children }: { title?: string; children?: React
|
||||
screenOptions={{
|
||||
contentStyle,
|
||||
headerTitleAlign: "left",
|
||||
headerRight: () => <HeaderAvatar />,
|
||||
headerRight: renderHeaderAvatar,
|
||||
}}
|
||||
>
|
||||
{title ? (
|
||||
|
||||
@@ -51,18 +51,18 @@ export function SeasonAccordion({
|
||||
const [visibleCount, setVisibleCount] = useState(INITIAL_BATCH);
|
||||
|
||||
useEffect(() => {
|
||||
if (expanded) {
|
||||
setVisibleCount(INITIAL_BATCH);
|
||||
if (episodes.length > INITIAL_BATCH) {
|
||||
const task = InteractionManager.runAfterInteractions(() => {
|
||||
setVisibleCount(episodes.length);
|
||||
});
|
||||
return () => task.cancel();
|
||||
}
|
||||
if (expanded && episodes.length > INITIAL_BATCH) {
|
||||
const task = InteractionManager.runAfterInteractions(() => {
|
||||
setVisibleCount(episodes.length);
|
||||
});
|
||||
return () => task.cancel();
|
||||
}
|
||||
}, [expanded, episodes.length]);
|
||||
|
||||
const toggleExpanded = useCallback(() => setExpanded((v) => !v), []);
|
||||
const toggleExpanded = useCallback(() => {
|
||||
if (!expanded) setVisibleCount(INITIAL_BATCH);
|
||||
setExpanded((v) => !v);
|
||||
}, [expanded]);
|
||||
|
||||
useEffect(() => {
|
||||
chevronRotation.set(
|
||||
|
||||
+14
-14
@@ -5,33 +5,33 @@ import { createMMKV } from "react-native-mmkv";
|
||||
export const globalStorage = createMMKV();
|
||||
|
||||
// Server+user scoped storage — switches when scope changes
|
||||
let _scopedStore: ReturnType<typeof createMMKV> | null = null;
|
||||
let _currentScopeKey: string | null = null;
|
||||
let scopedStore: ReturnType<typeof createMMKV> | null = null;
|
||||
let currentScopeKey: string | null = null;
|
||||
|
||||
const scopeChangeListeners: Array<() => void> = [];
|
||||
|
||||
export function scopedStorage() {
|
||||
if (!_scopedStore) throw new Error("Scoped storage not initialized");
|
||||
return _scopedStore;
|
||||
if (!scopedStore) throw new Error("Scoped storage not initialized");
|
||||
return scopedStore;
|
||||
}
|
||||
|
||||
export function hasScopedStorage(): boolean {
|
||||
return _scopedStore !== null;
|
||||
return scopedStore !== null;
|
||||
}
|
||||
|
||||
export function getScopeKey(): string | null {
|
||||
return _currentScopeKey;
|
||||
return currentScopeKey;
|
||||
}
|
||||
|
||||
export function setStorageScope(instanceId: string, userId: string) {
|
||||
_currentScopeKey = `${instanceId}_${userId}`;
|
||||
_scopedStore = createMMKV({ id: _currentScopeKey });
|
||||
currentScopeKey = `${instanceId}_${userId}`;
|
||||
scopedStore = createMMKV({ id: currentScopeKey });
|
||||
for (const listener of scopeChangeListeners) listener();
|
||||
}
|
||||
|
||||
export function clearStorageScope() {
|
||||
_currentScopeKey = null;
|
||||
_scopedStore = null;
|
||||
currentScopeKey = null;
|
||||
scopedStore = null;
|
||||
for (const listener of scopeChangeListeners) listener();
|
||||
}
|
||||
|
||||
@@ -43,11 +43,11 @@ export function onStorageScopeChange(callback: () => void): () => void {
|
||||
};
|
||||
}
|
||||
|
||||
// Query persister — reads/writes through _scopedStore via closure
|
||||
// Query persister — reads/writes through scopedStore via closure
|
||||
const scopedMmkvStorage = {
|
||||
getItem: (key: string) => _scopedStore?.getString(key) ?? null,
|
||||
setItem: (key: string, value: string) => _scopedStore?.set(key, value),
|
||||
removeItem: (key: string) => void _scopedStore?.remove(key),
|
||||
getItem: (key: string) => scopedStore?.getString(key) ?? null,
|
||||
setItem: (key: string, value: string) => scopedStore?.set(key, value),
|
||||
removeItem: (key: string) => void scopedStore?.remove(key),
|
||||
};
|
||||
|
||||
export const QUERY_CACHE_KEY = "REACT_QUERY_OFFLINE_CACHE";
|
||||
|
||||
@@ -380,7 +380,7 @@ function getCachedSession(): CachedSessionData | null {
|
||||
}
|
||||
}
|
||||
|
||||
let _cachedSessionSeeded = false;
|
||||
let cachedSessionSeeded = false;
|
||||
|
||||
/**
|
||||
* Seed the Better Auth session atom from SecureStore before React renders.
|
||||
@@ -389,7 +389,7 @@ let _cachedSessionSeeded = false;
|
||||
export function initSession(): void {
|
||||
const cached = getCachedSession();
|
||||
if (cached) {
|
||||
_cachedSessionSeeded = true;
|
||||
cachedSessionSeeded = true;
|
||||
const sessionAtom = authClient.$store.atoms.session;
|
||||
sessionAtom.set({
|
||||
data: cached,
|
||||
@@ -402,11 +402,11 @@ export function initSession(): void {
|
||||
}
|
||||
|
||||
export function wasCachedSessionSeeded(): boolean {
|
||||
return _cachedSessionSeeded;
|
||||
return cachedSessionSeeded;
|
||||
}
|
||||
|
||||
export function clearCachedSessionSeeded(): void {
|
||||
_cachedSessionSeeded = false;
|
||||
cachedSessionSeeded = false;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -419,15 +419,15 @@ export function clearCachedSessionSeeded(): void {
|
||||
|
||||
// Signals that the next session-loss redirect should go to the server-url
|
||||
// screen instead of login (set by the "Change Server" flow in settings).
|
||||
let _serverChangeRequested = false;
|
||||
let serverChangeRequested = false;
|
||||
|
||||
export function requestServerChange(): void {
|
||||
_serverChangeRequested = true;
|
||||
serverChangeRequested = true;
|
||||
}
|
||||
|
||||
export function consumeServerChangeRequest(): boolean {
|
||||
const was = _serverChangeRequested;
|
||||
_serverChangeRequested = false;
|
||||
const was = serverChangeRequested;
|
||||
serverChangeRequested = false;
|
||||
return was;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { DarkTheme } from "@react-navigation/native";
|
||||
import { DarkTheme } from "expo-router/react-navigation";
|
||||
|
||||
export const sofaTheme = {
|
||||
...DarkTheme,
|
||||
|
||||
@@ -1,23 +1,33 @@
|
||||
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
||||
|
||||
const CACHED_WIDGET_IMAGE_RE = /^file:\/\/\/group\/cw_/;
|
||||
type LibraryResult = { items: Array<Record<string, unknown>> };
|
||||
type SnapshotProps = Record<string, unknown>;
|
||||
type TimelineEntry = { props?: Record<string, unknown> };
|
||||
|
||||
const platform = { OS: "ios" };
|
||||
const resolveAssetSource = vi.fn(() => ({ uri: "file:///tmp/sofa-icon.png" }));
|
||||
const continueWatching = vi.fn();
|
||||
const upcoming = vi.fn();
|
||||
const downloadWidgetImage = vi.fn(async (_url: string, key: string) => `file:///group/${key}`);
|
||||
const copyBundledAsset = vi.fn(async (_assetUri: string, key: string) => `file:///group/${key}`);
|
||||
const pruneWidgetImages = vi.fn(async () => undefined);
|
||||
const getWidgetIconAsset = vi.fn(() => "widget-icon-asset");
|
||||
const resolveAssetSource = vi.fn<() => { uri: string }>(() => ({
|
||||
uri: "file:///tmp/sofa-icon.png",
|
||||
}));
|
||||
const continueWatching = vi.fn<() => Promise<LibraryResult>>();
|
||||
const upcoming = vi.fn<() => Promise<LibraryResult>>();
|
||||
const downloadWidgetImage = vi.fn<(url: string, key: string) => Promise<string>>(
|
||||
async (_url: string, key: string) => `file:///group/${key}`,
|
||||
);
|
||||
const copyBundledAsset = vi.fn<(assetUri: string, key: string) => Promise<string>>(
|
||||
async (_assetUri: string, key: string) => `file:///group/${key}`,
|
||||
);
|
||||
const pruneWidgetImages = vi.fn<() => Promise<void>>(async () => undefined);
|
||||
const getWidgetIconAsset = vi.fn<() => string>(() => "widget-icon-asset");
|
||||
|
||||
const continueWatchingWidget = {
|
||||
updateSnapshot: vi.fn(),
|
||||
updateTimeline: vi.fn(),
|
||||
updateSnapshot: vi.fn<(props: SnapshotProps) => void>(),
|
||||
updateTimeline: vi.fn<(entries: TimelineEntry[]) => void>(),
|
||||
};
|
||||
|
||||
const upcomingWidget = {
|
||||
updateSnapshot: vi.fn(),
|
||||
updateTimeline: vi.fn(),
|
||||
updateSnapshot: vi.fn<(props: SnapshotProps) => void>(),
|
||||
updateTimeline: vi.fn<(entries: TimelineEntry[]) => void>(),
|
||||
};
|
||||
|
||||
vi.mock("react-native", () => ({
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
"extends": "expo/tsconfig.base",
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user