Set up E2E test framework for Android using WebdriverIO, Appium, and UiAutomator2. Add testID props to key components (AuthButton, BaseButton, ChatBubble, CustomTextInput, ProposedEventCard) and apply testIDs to login screen, chat screen, tab bar, and settings. Include initial tests for app launch detection and login flow validation. Update CLAUDE.md with E2E docs.
253 lines
6.6 KiB
TypeScript
253 lines
6.6 KiB
TypeScript
import { ActivityIndicator, Text, View } from "react-native";
|
|
import BaseBackground from "../../components/BaseBackground";
|
|
import BaseButton, { BaseButtonProps } from "../../components/BaseButton";
|
|
import { useThemeStore } from "../../stores/ThemeStore";
|
|
import { AuthService } from "../../services/AuthService";
|
|
import { router } from "expo-router";
|
|
import { Ionicons } from "@expo/vector-icons";
|
|
import { SimpleHeader } from "../../components/Header";
|
|
import { THEMES } from "../../Themes";
|
|
import CustomTextInput from "../../components/CustomTextInput";
|
|
import { useCallback, useRef, useState } from "react";
|
|
import { CaldavConfigService } from "../../services/CaldavConfigService";
|
|
import { useCaldavConfigStore } from "../../stores";
|
|
|
|
const handleLogout = async () => {
|
|
await AuthService.logout();
|
|
router.replace("/login");
|
|
};
|
|
|
|
const SettingsButton = (props: BaseButtonProps) => {
|
|
return (
|
|
<BaseButton
|
|
testID={props.testID}
|
|
onPress={props.onPress}
|
|
solid={props.solid}
|
|
className={"w-11/12"}
|
|
>
|
|
{props.children}
|
|
</BaseButton>
|
|
);
|
|
};
|
|
|
|
type CaldavTextInputProps = {
|
|
title: string;
|
|
value: string;
|
|
onValueChange: (text: string) => void;
|
|
secureTextEntry?: boolean;
|
|
};
|
|
|
|
const CaldavTextInput = ({
|
|
title,
|
|
value,
|
|
onValueChange,
|
|
secureTextEntry,
|
|
}: CaldavTextInputProps) => {
|
|
const { theme } = useThemeStore();
|
|
return (
|
|
<View className="flex flex-row items-center py-1">
|
|
<Text className="ml-4 w-24" style={{ color: theme.textPrimary }}>
|
|
{title}:
|
|
</Text>
|
|
<CustomTextInput
|
|
className="flex-1 mr-4 px-3 py-2"
|
|
text={value}
|
|
onValueChange={onValueChange}
|
|
secureTextEntry={secureTextEntry}
|
|
/>
|
|
</View>
|
|
);
|
|
};
|
|
|
|
type Feedback = { text: string; isError: boolean; loading: boolean };
|
|
|
|
const FeedbackRow = ({ feedback }: { feedback: Feedback | null }) => {
|
|
const { theme } = useThemeStore();
|
|
if (!feedback) return null;
|
|
return (
|
|
<View className="flex flex-row items-center justify-center mt-2 mx-4 gap-2">
|
|
{feedback.loading && (
|
|
<ActivityIndicator size="small" color={theme.textMuted} />
|
|
)}
|
|
<Text
|
|
style={{
|
|
color: feedback.loading
|
|
? theme.textMuted
|
|
: feedback.isError
|
|
? theme.rejectButton
|
|
: theme.confirmButton,
|
|
}}
|
|
>
|
|
{feedback.text}
|
|
</Text>
|
|
</View>
|
|
);
|
|
};
|
|
|
|
const CaldavSettings = () => {
|
|
const { theme } = useThemeStore();
|
|
const { config, setConfig } = useCaldavConfigStore();
|
|
|
|
const [serverUrl, setServerUrl] = useState(config?.serverUrl ?? "");
|
|
const [username, setUsername] = useState(config?.username ?? "");
|
|
const [password, setPassword] = useState(config?.password ?? "");
|
|
const [saveFeedback, setSaveFeedback] = useState<Feedback | null>(null);
|
|
const [syncFeedback, setSyncFeedback] = useState<Feedback | null>(null);
|
|
const saveTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
const syncTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
|
|
const showFeedback = useCallback(
|
|
(
|
|
setter: typeof setSaveFeedback,
|
|
timer: typeof saveTimer,
|
|
text: string,
|
|
isError: boolean,
|
|
loading = false,
|
|
) => {
|
|
if (timer.current) clearTimeout(timer.current);
|
|
setter({ text, isError, loading });
|
|
if (!loading) {
|
|
timer.current = setTimeout(() => setter(null), 3000);
|
|
}
|
|
},
|
|
[],
|
|
);
|
|
|
|
const saveConfig = async () => {
|
|
showFeedback(
|
|
setSaveFeedback,
|
|
saveTimer,
|
|
"Speichere Konfiguration...",
|
|
false,
|
|
true,
|
|
);
|
|
try {
|
|
const saved = await CaldavConfigService.saveConfig(
|
|
serverUrl,
|
|
username,
|
|
password,
|
|
);
|
|
setConfig(saved);
|
|
showFeedback(
|
|
setSaveFeedback,
|
|
saveTimer,
|
|
"Konfiguration wurde gespeichert",
|
|
false,
|
|
);
|
|
} catch {
|
|
showFeedback(
|
|
setSaveFeedback,
|
|
saveTimer,
|
|
"Fehler beim Speichern der Konfiguration",
|
|
true,
|
|
);
|
|
}
|
|
};
|
|
|
|
const sync = async () => {
|
|
showFeedback(setSyncFeedback, syncTimer, "Synchronisiere...", false, true);
|
|
try {
|
|
await CaldavConfigService.sync();
|
|
showFeedback(
|
|
setSyncFeedback,
|
|
syncTimer,
|
|
"Synchronisierung erfolgreich",
|
|
false,
|
|
);
|
|
} catch {
|
|
showFeedback(
|
|
setSyncFeedback,
|
|
syncTimer,
|
|
"Fehler beim Synchronisieren",
|
|
true,
|
|
);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<View>
|
|
<Text
|
|
className="text-center text-2xl"
|
|
style={{ color: theme.textPrimary }}
|
|
>
|
|
Caldav Config
|
|
</Text>
|
|
</View>
|
|
<View>
|
|
<View className="pb-1">
|
|
<CaldavTextInput
|
|
title="url"
|
|
value={serverUrl}
|
|
onValueChange={setServerUrl}
|
|
/>
|
|
<CaldavTextInput
|
|
title="username"
|
|
value={username}
|
|
onValueChange={setUsername}
|
|
/>
|
|
<CaldavTextInput
|
|
title="password"
|
|
value={password}
|
|
onValueChange={setPassword}
|
|
secureTextEntry
|
|
/>
|
|
</View>
|
|
<View className="flex flex-row">
|
|
<BaseButton className="mx-4 w-1/5" solid={true} onPress={saveConfig}>
|
|
Save
|
|
</BaseButton>
|
|
<BaseButton className="w-1/5" solid={true} onPress={sync}>
|
|
Sync
|
|
</BaseButton>
|
|
</View>
|
|
<FeedbackRow feedback={saveFeedback} />
|
|
<FeedbackRow feedback={syncFeedback} />
|
|
</View>
|
|
</>
|
|
);
|
|
};
|
|
|
|
const Settings = () => {
|
|
const { theme, setTheme } = useThemeStore();
|
|
|
|
return (
|
|
<BaseBackground>
|
|
<SimpleHeader text="Settings" />
|
|
<View className="flex items-center mt-4">
|
|
<SettingsButton testID="settings-logout-button" onPress={handleLogout} solid={true}>
|
|
<Ionicons name="log-out-outline" size={24} color={theme.primeFg} />{" "}
|
|
Logout
|
|
</SettingsButton>
|
|
<View>
|
|
<Text
|
|
className="text-center text-2xl"
|
|
style={{ color: theme.textPrimary }}
|
|
>
|
|
Select Theme
|
|
</Text>
|
|
</View>
|
|
<SettingsButton
|
|
solid={theme == THEMES.defaultLight}
|
|
onPress={() => {
|
|
setTheme("defaultLight");
|
|
}}
|
|
>
|
|
Default Light
|
|
</SettingsButton>
|
|
<SettingsButton
|
|
solid={theme == THEMES.defaultDark}
|
|
onPress={() => {
|
|
setTheme("defaultDark");
|
|
}}
|
|
>
|
|
Default Dark
|
|
</SettingsButton>
|
|
</View>
|
|
<CaldavSettings />
|
|
</BaseBackground>
|
|
);
|
|
};
|
|
|
|
export default Settings;
|