work on navigation component workflow.

This commit is contained in:
Jordan Hewitt 2025-02-22 18:28:10 -08:00
parent 6673663883
commit eb7599bfe8
8 changed files with 116 additions and 32 deletions

38
__mocks__/api.ts Normal file
View File

@ -0,0 +1,38 @@
// __mocks__/api.ts
import { language_matrix, language_matrix_entry } from "@/app/i18n/api";
// Import the actual API module to extend its functionality
const origApi = jest.requireActual('@/app/i18n/api.ts');
class LanguageServer {
constructor(...args: any[]) { }
fetchLanguages(): language_matrix {
return {
"en" : { code: "en", name: "English", targets: ['fr', 'es'] },
"fr" : { code: "fr", name: "French", targets: ["en", "es"] },
"es": { code: "es", name: "Spanish", targets: ['fr', 'en'] },
}
}
}
class Translator {
constructor(...args : any []) {}
translate(message : string, target : string) {
return message;
}
}
class CachedTranslator extends Translator{
}
module.exports = {
...origApi,
LanguageServer,
Translator,
CachedTranslator,
// Mock the specific functions you want to override
fetchData: jest.fn(() => Promise.resolve({ data: 'mocked data' })),
// Add more mock implementations as needed
};

View File

@ -83,6 +83,12 @@ export class Translator {
console.log(data)
return data.translatedText
}
static async getDefault(defaultTarget: string | undefined = undefined) {
const settings = await Settings.getDefault();
const source = await settings.getHostLanguage();
return new Translator(source, defaultTarget, await LanguageServer.getDefault())
}
}
export class CachedTranslator extends Translator {

View File

@ -4,6 +4,7 @@ import { Image, Text, View, StyleSheet, Button, Pressable } from "react-native";
import { LanguageServer, Translator, language_matrix_entry } from "./i18n/api";
import { Conversation } from "./lib/conversation";
import { LanguageSelection } from "@/components/LanguageSelection";
import { Link } from 'expo-router';
function LogoTitle() {
return (

View File

@ -67,6 +67,7 @@ const ConversationThread = ({ route } : {route?: Route<"Conversation", {conversa
return cachedTranslator ? (
<View style={{ flex: 1, flexDirection: "column" }}>
<Text>Conversation Thread</Text>
<ScrollView
style={{
borderColor: "black",

View File

@ -1,4 +1,4 @@
import { CachedTranslator, Translator, language_matrix, language_matrix_entry } from "@/app/i18n/api";
import { CachedTranslator, LanguageServer, Translator, language_matrix, language_matrix_entry } from "@/app/i18n/api";
import { LIBRETRANSLATE_BASE_URL } from "@/constants/api";
import { useEffect, useState } from "react";
import ISpeakButton from "./ui/ISpeakButton";
@ -7,17 +7,19 @@ import { ScrollView, StyleSheet, Text, View } from "react-native";
import { SafeAreaProvider, SafeAreaView } from "react-native-safe-area-context";
import { Conversation, Speaker } from "@/app/lib/conversation";
import { NavigationProp, ParamListBase } from "@react-navigation/native";
import { Link } from "expo-router";
export function LanguageSelection(props: {
navigation?: NavigationProp<ParamListBase>
translator?: Translator
onLangSelected? : (lang : language_matrix_entry) => any
onLangSelected?: (lang: language_matrix_entry) => any
}) {
const [languages, setLanguages] = useState<language_matrix | undefined>();
const [languagesLoaded, setLanguagesLoaded] = useState<boolean>(false);
const translator = props.translator || new CachedTranslator("en")
const languageServer = new LanguageServer(LIBRETRANSLATE_BASE_URL);
const translator = props.translator || new CachedTranslator("en", undefined, languageServer);
function onLangSelected(language: language_matrix_entry) {
props.onLangSelected && props.onLangSelected(language)
@ -28,7 +30,7 @@ export function LanguageSelection(props: {
const fetchData = async () => {
try {
// Replace with your actual async data fetching logic
const languages = await translator.fetchLanguages();
const languages = await languageServer.fetchLanguages();
setLanguages(languages);
setLanguagesLoaded(true);
} catch (error) {
@ -41,20 +43,25 @@ export function LanguageSelection(props: {
}, []);
return (
<ScrollView >
<SafeAreaProvider >
<SafeAreaView>
{(languages && languagesLoaded) ? Object.entries(languages).filter((l) => (LANG_FLAGS as any)[l[0]] !== undefined).map(
([lang, lang_entry]) => {
return (
<ISpeakButton language={lang_entry} key={lang_entry.code} onLangSelected={onLangSelected} />
);
<View>
<Link href={"/settings"}>
<Text>Settings</Text>
</Link>
<ScrollView >
<SafeAreaProvider >
<SafeAreaView>
{(languages && languagesLoaded) ? Object.entries(languages).filter((l) => (LANG_FLAGS as any)[l[0]] !== undefined).map(
([lang, lang_entry]) => {
return (
<ISpeakButton language={lang_entry} key={lang_entry.code} onLangSelected={onLangSelected} />
);
}
) : <Text>Waiting...</Text>
}
) : <Text>Waiting...</Text>
}
</SafeAreaView>
</SafeAreaProvider>
</ScrollView>
</SafeAreaView>
</SafeAreaProvider>
</ScrollView>
</View>
)
}

View File

@ -2,16 +2,38 @@ import * as React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import SettingsComponent from '@/components/Settings';
import { LanguageSelection } from '@/components/LanguageSelection';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { createNativeStackNavigator, NativeStackNavigationProp } from '@react-navigation/native-stack';
import {
useNavigation,
} from '@react-navigation/native'
import ConversationThread from '@/components/ConversationThread';
import { language_matrix_entry, Translator } from '@/app/i18n/api';
import { useRouter } from 'expo-router';
import { Conversation } from '@/app/lib/conversation';
import { Settings } from '@/app/lib/settings';
import { RootStackParamList } from '@/navigation.types';
const Stack = createNativeStackNavigator();
export default function TTNavStack() {
async function onLangSelected(lang: language_matrix_entry) {
const nav = useNavigation<NativeStackNavigationProp<RootStackParamList, 'ConversationThread'>>();
const settings = await Settings.getDefault();
const hostLanguage = await settings.getHostLanguage();
const conversation = new Conversation(
(await Translator.getDefault(lang.code)),
{ id: "host", language: hostLanguage },
{ "id": "guest", language: lang.code, }
)
nav.navigate("Conversation", { conversation, })
}
return (
<NavigationContainer>
<Stack.Navigator initialRouteName='LanguageSelection'>
<Stack.Screen name="LanguageSelection" component={LanguageSelection} />
<Stack.Screen name="LanguageSelection" component={({ ...props }) => <LanguageSelection {...props} onLangSelected={onLangSelected} />} />
<Stack.Screen name="ConversationThread" component={ConversationThread} />
<Stack.Screen name="Settings" component={SettingsComponent} />
</Stack.Navigator>

View File

@ -1,17 +1,11 @@
import {dirname, resolve} from 'path'
import React from 'react';
import { fireEvent, render, screen } from '@testing-library/react-native';
import { NavigationContainer } from '@react-navigation/native';
import { act, fireEvent, render, screen } from '@testing-library/react-native';
import { createStackNavigator } from '@react-navigation/stack';
import TTNavStack from '../TTNavStack';
// Mock the expo-file-system/next module
jest.mock('expo-file-system/next', () => ({
File: jest.fn(),
Paths: {
join: jest.fn(),
},
}));
jest.mock("@/app/i18n/api", () => require("../../__mocks__/api.ts"));
import TTNavStack from '../TTNavStack';
const Stack = createStackNavigator();
@ -23,15 +17,19 @@ describe('Navigation', () => {
it('Navigates to ConversationThread on language selection', async () => {
render(<TTNavStack />);
const languageSelectionText = await screen.findByText("Language Selection");
fireEvent.press(languageSelectionText);
const languageSelectionText = await screen.findByText(/I Speak French\./i);
act(() => {
fireEvent.press(languageSelectionText);
})
expect(await screen.findByText("Conversation Thread")).toBeOnTheScreen();
});
it('Navigates to Settings on settings selection', async () => {
render(<TTNavStack />);
const settingsButton = await screen.findByText("Settings");
fireEvent.press(settingsButton);
act(() => {
fireEvent.press(settingsButton)
})
expect(await screen.findByText("Settings")).toBeOnTheScreen();
});
});

11
navigation.types.ts Normal file
View File

@ -0,0 +1,11 @@
// navigation.types.ts
import { ParamListBase } from '@react-navigation/native';
import { Conversation } from '@/app/lib/conversation';
export type RootStackParamList = {
LanguageSelection: undefined;
ConversationThread: undefined;
Settings: undefined;
Conversation: { conversation: Conversation };
};