Refactor mobile to fix dependency resolution and add polyfills
Update Expo and React dependencies to compatible versions. Create a custom Metro config to resolve module conflicts in the workspace and map Jest modules to local node_modules. Add a SharedArrayBuffer polyfill for older runtimes and introduce an adapter for expo-notifications to abstract direct imports.
This commit is contained in:
+1
-1
@@ -1,5 +1,5 @@
|
||||
import { useEffect } from 'react';
|
||||
import * as Notifications from 'expo-notifications';
|
||||
import * as Notifications from './src/services/expoNotifications';
|
||||
import AppNavigator from './src/navigation/AppNavigator';
|
||||
|
||||
export default function App() {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import './src/polyfills/sharedArrayBuffer';
|
||||
import { registerRootComponent } from 'expo';
|
||||
|
||||
import App from './App';
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
module.exports = {
|
||||
preset: 'jest-expo',
|
||||
testMatch: ['**/__tests__/**/*.test.[jt]s?(x)'],
|
||||
moduleNameMapper: {
|
||||
'^react$': '<rootDir>/node_modules/react',
|
||||
'^react-test-renderer$': '<rootDir>/node_modules/react-test-renderer',
|
||||
'^react-native-safe-area-context$': '<rootDir>/node_modules/react-native-safe-area-context',
|
||||
'^react-native-screens$': '<rootDir>/node_modules/react-native-screens',
|
||||
'^@react-native-async-storage/async-storage$':
|
||||
'<rootDir>/node_modules/@react-native-async-storage/async-storage',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { getDefaultConfig } from 'expo/metro-config.js';
|
||||
|
||||
const projectRoot = path.dirname(fileURLToPath(import.meta.url));
|
||||
const workspaceRoot = path.resolve(projectRoot, '../..');
|
||||
|
||||
const config = getDefaultConfig(projectRoot);
|
||||
|
||||
config.resolver.disableHierarchicalLookup = true;
|
||||
config.resolver.nodeModulesPaths = [
|
||||
path.resolve(projectRoot, 'node_modules'),
|
||||
path.resolve(workspaceRoot, 'node_modules'),
|
||||
];
|
||||
config.resolver.extraNodeModules = {
|
||||
react: path.resolve(projectRoot, 'node_modules/react'),
|
||||
'react-test-renderer': path.resolve(projectRoot, 'node_modules/react-test-renderer'),
|
||||
'react-native-safe-area-context': path.resolve(projectRoot, 'node_modules/react-native-safe-area-context'),
|
||||
'react-native-screens': path.resolve(projectRoot, 'node_modules/react-native-screens'),
|
||||
'@react-native-async-storage/async-storage': path.resolve(
|
||||
projectRoot,
|
||||
'node_modules/@react-native-async-storage/async-storage',
|
||||
),
|
||||
'expo-application': path.resolve(projectRoot, 'node_modules/expo-notifications/node_modules/expo-application'),
|
||||
};
|
||||
|
||||
export default config;
|
||||
@@ -13,20 +13,20 @@
|
||||
"test": "jest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-native-async-storage/async-storage": "^3.0.2",
|
||||
"@react-native-async-storage/async-storage": "2.2.0",
|
||||
"@react-navigation/native": "^7.2.4",
|
||||
"@react-navigation/native-stack": "^7.14.14",
|
||||
"@timetoleave/api-client": "*",
|
||||
"@timetoleave/core": "*",
|
||||
"expo": "~54.0.33",
|
||||
"expo-calendar": "^55.0.14",
|
||||
"expo-location": "^55.1.9",
|
||||
"expo-notifications": "^55.0.22",
|
||||
"expo-calendar": "~15.0.8",
|
||||
"expo-location": "~19.0.8",
|
||||
"expo-notifications": "~0.32.17",
|
||||
"expo-status-bar": "~3.0.9",
|
||||
"react": "19.2.4",
|
||||
"react": "19.1.0",
|
||||
"react-native": "0.81.5",
|
||||
"react-native-safe-area-context": "^5.7.0",
|
||||
"react-native-screens": "^4.24.0"
|
||||
"react-native-safe-area-context": "~5.6.0",
|
||||
"react-native-screens": "~4.16.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.39.4",
|
||||
@@ -36,7 +36,7 @@
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"jest": "^29.7.0",
|
||||
"jest-expo": "~54.0.0",
|
||||
"react-test-renderer": "19.2.4",
|
||||
"react-test-renderer": "19.1.0",
|
||||
"ts-jest": "^29.4.9",
|
||||
"typescript": "~5.9.2",
|
||||
"typescript-eslint": "^8.59.3"
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
rescheduleAllNotifications
|
||||
} from '../store/eventStore';
|
||||
import { calculateLeaveByTime } from '../services/notifications';
|
||||
import * as Notifications from 'expo-notifications';
|
||||
import * as Notifications from '../services/expoNotifications';
|
||||
|
||||
// Mock AsyncStorage
|
||||
jest.mock('@react-native-async-storage/async-storage', () => ({
|
||||
@@ -21,8 +21,8 @@ jest.mock('@react-native-async-storage/async-storage', () => ({
|
||||
removeItem: jest.fn(),
|
||||
}));
|
||||
|
||||
// Mock expo-notifications
|
||||
jest.mock('expo-notifications', () => ({
|
||||
// Mock notification adapter
|
||||
jest.mock('../services/expoNotifications', () => ({
|
||||
getAllScheduledNotificationsAsync: jest.fn(),
|
||||
cancelScheduledNotificationAsync: jest.fn(),
|
||||
cancelAllScheduledNotificationsAsync: jest.fn(),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Tests for notification service
|
||||
// Mock expo-notifications before importing
|
||||
jest.mock('expo-notifications', () => ({
|
||||
// Mock notification adapter before importing
|
||||
jest.mock('../services/expoNotifications', () => ({
|
||||
setNotificationHandler: jest.fn(),
|
||||
requestPermissionsAsync: jest.fn().mockResolvedValue({ status: 'granted' }),
|
||||
scheduleNotificationAsync: jest.fn().mockResolvedValue({ identifier: 'mock-id' }),
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
const globalScope = globalThis as Record<string, unknown>;
|
||||
const stringPrototype = String.prototype as typeof String.prototype & {
|
||||
isWellFormed?: () => boolean;
|
||||
toWellFormed?: () => string;
|
||||
};
|
||||
|
||||
const arrayBufferByteLength = Object.getOwnPropertyDescriptor(ArrayBuffer.prototype, 'byteLength')?.get;
|
||||
|
||||
function toWellFormedString(value: string): string {
|
||||
let result = '';
|
||||
|
||||
for (let index = 0; index < value.length; index += 1) {
|
||||
const code = value.charCodeAt(index);
|
||||
|
||||
if (code >= 0xd800 && code <= 0xdbff) {
|
||||
const next = value.charCodeAt(index + 1);
|
||||
if (next >= 0xdc00 && next <= 0xdfff) {
|
||||
result += value[index] + value[index + 1];
|
||||
index += 1;
|
||||
} else {
|
||||
result += '\uFFFD';
|
||||
}
|
||||
} else if (code >= 0xdc00 && code <= 0xdfff) {
|
||||
result += '\uFFFD';
|
||||
} else {
|
||||
result += value[index];
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
if (typeof stringPrototype.toWellFormed !== 'function') {
|
||||
Object.defineProperty(String.prototype, 'toWellFormed', {
|
||||
configurable: true,
|
||||
value() {
|
||||
return toWellFormedString(String(this));
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (typeof stringPrototype.isWellFormed !== 'function') {
|
||||
Object.defineProperty(String.prototype, 'isWellFormed', {
|
||||
configurable: true,
|
||||
value() {
|
||||
const value = String(this);
|
||||
return toWellFormedString(value) === value;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (!Object.getOwnPropertyDescriptor(ArrayBuffer.prototype, 'resizable')) {
|
||||
Object.defineProperty(ArrayBuffer.prototype, 'resizable', {
|
||||
configurable: true,
|
||||
get() {
|
||||
return false;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (typeof globalScope.SharedArrayBuffer === 'undefined') {
|
||||
class SharedArrayBufferPolyfill extends ArrayBuffer {
|
||||
get byteLength() {
|
||||
return arrayBufferByteLength?.call(this) ?? 0;
|
||||
}
|
||||
|
||||
get growable() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Object.defineProperty(SharedArrayBufferPolyfill.prototype, Symbol.toStringTag, {
|
||||
configurable: true,
|
||||
value: 'SharedArrayBuffer',
|
||||
});
|
||||
|
||||
globalScope.SharedArrayBuffer = SharedArrayBufferPolyfill;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
export { requestPermissionsAsync } from 'expo-notifications/build/NotificationPermissions.js';
|
||||
export { setNotificationHandler } from 'expo-notifications/build/NotificationsHandler.js';
|
||||
export { default as getAllScheduledNotificationsAsync } from 'expo-notifications/build/getAllScheduledNotificationsAsync.js';
|
||||
export { default as cancelScheduledNotificationAsync } from 'expo-notifications/build/cancelScheduledNotificationAsync.js';
|
||||
export { default as cancelAllScheduledNotificationsAsync } from 'expo-notifications/build/cancelAllScheduledNotificationsAsync.js';
|
||||
export { default as scheduleNotificationAsync } from 'expo-notifications/build/scheduleNotificationAsync.js';
|
||||
export { SchedulableTriggerInputTypes } from 'expo-notifications/build/Notifications.types.js';
|
||||
export type {
|
||||
NotificationBehavior,
|
||||
NotificationRequest,
|
||||
NotificationRequestInput,
|
||||
} from 'expo-notifications/build/Notifications.types.js';
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as Notifications from 'expo-notifications';
|
||||
import { SchedulableTriggerInputTypes } from 'expo-notifications';
|
||||
import * as Notifications from './expoNotifications';
|
||||
import { SchedulableTriggerInputTypes } from './expoNotifications';
|
||||
import type { Event, Journey, ReminderSettings } from '@timetoleave/core';
|
||||
|
||||
// Register for push notification permissions
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import type { Event, Station, ReminderSettings } from '@timetoleave/core';
|
||||
import * as Notifications from 'expo-notifications';
|
||||
import { SchedulableTriggerInputTypes } from 'expo-notifications';
|
||||
import * as Notifications from '../services/expoNotifications';
|
||||
import { SchedulableTriggerInputTypes } from '../services/expoNotifications';
|
||||
|
||||
// ── Keys ───────────────────────────────
|
||||
|
||||
|
||||
Reference in New Issue
Block a user