Harden Heute route and German voice onboarding E2E

Rebased onto main with Expo SDK 57 typecheck fixes. Voice options expose
stable test ids; Shell keeps /app/heute across bootstrap and redirects
legacy /app/heute bot URLs. Heute coach E2E asserts URL after reload.

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
This commit is contained in:
Cursor Agent 2026-10-06 17:01:08 +00:00
parent 057e039e83
commit 83ec966a67
No known key found for this signature in database
4 changed files with 18 additions and 11 deletions

View File

@ -49,12 +49,10 @@ test("German voice step lists German voices after choosing Deutsch", async ({ pa
await expect(page.getByText(/Loading voices|Stimmen werden geladen/i)).toHaveCount(0, {
timeout: 30_000,
});
const germanVoice = page.getByRole("button", { name: /Eva|Bernd/i });
await expect(germanVoice.first()).toBeVisible({ timeout: 30_000 });
await expect(page.getByRole("button", { name: "Alloy", exact: true })).toHaveCount(0);
await expect(
page.getByRole("button", { name: /Eva|Bernd/i, pressed: true }).first(),
).toBeVisible();
await expect(page.getByTestId("onboarding-voice-df_eva")).toBeVisible({ timeout: 30_000 });
await expect(page.getByTestId("onboarding-voice-dm_bernd")).toBeVisible();
await expect(page.getByTestId("onboarding-voice-af_alloy")).toHaveCount(0);
await expect(page.getByTestId("onboarding-voice-df_eva")).toHaveAttribute("aria-pressed", "true");
});
test("main conversation does not show a group subtitle for one Oo", async ({ page }) => {

View File

@ -84,8 +84,7 @@ test("Heute shows the coach's brief, habits and goals, checks habits off and set
await expect(page.getByTestId(`heute-habit-streak-${habit.id}`)).toHaveText("3 days");
await captureScreenshot(page, testInfo, "heute-coach");
await page.reload();
await page.goto("/app/heute", { waitUntil: "domcontentloaded" });
await page.reload({ waitUntil: "domcontentloaded" });
await expect(page).toHaveURL(/\/app\/heute$/);
await expect(page.getByTestId("heute-brief")).toBeVisible();
await expect(page.getByTestId(`heute-habit-${habit.id}`).getByRole("checkbox")).toBeChecked();

View File

@ -92,6 +92,7 @@ export function OnboardingVoiceStep({
<button
key={voice.id}
type="button"
data-testid={`onboarding-voice-${voice.id}`}
onClick={() => onSelectedVoiceIdChange(voice.id)}
aria-pressed={selectedVoiceId === voice.id}
className={`rounded-xl border-2 px-3 py-3 text-left text-sm font-medium transition-all ${

View File

@ -273,6 +273,11 @@ export function ShellPage() {
const navigate = useNavigate();
const [searchParams, setSearchParams] = useSearchParams();
const onHeuteRoute = isHeuteAppPath(location.pathname);
useEffect(() => {
if (botId === "heute") {
navigate(HEUTE_APP_PATH, { replace: true });
}
}, [botId, navigate]);
const [activeNavPillar, setActiveNavPillar] = useState<NavPillar>(() => {
if (isHeuteAppPath(location.pathname)) return "heute";
if (groupId) return "projekte";
@ -620,8 +625,12 @@ export function ShellPage() {
const recordingSkill = activeTaughtSkills.find((skill) => skill.status === "recording") ?? null;
const routeBotId = useRef<string | undefined>(botId);
routeBotId.current = botId;
const routePathname = useRef(location.pathname);
routePathname.current = location.pathname;
const routeOnHeute = useRef(onHeuteRoute);
routeOnHeute.current = onHeuteRoute;
const holdingHeuteRoute = () =>
routeOnHeute.current || isHeuteAppPath(routePathname.current);
const routeGroupId = useRef<string | undefined>(groupId);
routeGroupId.current = groupId;
const activeBotId = useRef<string | undefined>(inGroup ? undefined : active?.id);
@ -756,7 +765,7 @@ export function ShellPage() {
return;
}
if (!currentBotId || !list.some((bot) => bot.id === currentBotId)) {
if (routeOnHeute.current) return;
if (holdingHeuteRoute()) return;
navigate(firstThreadRoute(list, groupList, inProject), { replace: true });
}
} finally {
@ -1004,11 +1013,11 @@ export function ShellPage() {
return;
}
if (inProject && !botId) {
if (routeOnHeute.current) return;
if (holdingHeuteRoute()) return;
navigate(firstThreadRoute(bootstrap.bots, groupList, true), { replace: true });
return;
}
if (routeOnHeute.current) return;
if (holdingHeuteRoute()) return;
const selectedBotId = bootstrap.thread?.botId ?? bootstrap.bots[0]?.id;
if (selectedBotId && selectedBotId !== botId) {
navigate(`/app/${selectedBotId}`, { replace: true });