Unverified 提交 eb9f9162 authored 作者: Will Chen's avatar Will Chen 提交者: GitHub

Fix refresh to preserve current route (#253) (#2336)

## Summary - When clicking the refresh button in the preview panel, the app now preserves the current route instead of defaulting to the root path (/) - Store the current URL in a ref when refresh is clicked, then use it as the iframe src during reload - Added E2E test to verify route preservation on refresh Fixes #253 ## Test plan - E2E test `refresh preserves current route` verifies the fix: 1. Create an app 2. Navigate to a different route using JavaScript (simulating client-side navigation) 3. Click refresh 4. Verify the address bar still shows the navigated route (not /) - Existing `refresh app` test continues to pass 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- devin-review-badge-begin --> --- <a href="https://app.devin.ai/review/dyad-sh/dyad/pull/2336"> <picture> <source media="(prefers-color-scheme: dark)" srcset="https://static.devin.ai/assets/gh-open-in-devin-review-dark.svg?v=1"> <img src="https://static.devin.ai/assets/gh-open-in-devin-review-light.svg?v=1" alt="Open with Devin"> </picture> </a> <!-- devin-review-badge-end --> <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Ensures the preview iframe keeps the current client-side route on refresh and after HMR remounts. > > - Track route changes (`pushState`/`replaceState`) and persist per-app URL via `previewCurrentUrlAtom`; initialize navigation from preserved URL > - Use `currentIframeUrlRef` and validated same-origin URL as iframe `src` on reload; reset history on app change > - Update `PreviewIframe` navigation history, address bar (`data-testid="preview-address-bar-path"`), and reload logic; avoid unintended src resets on re-render > - Add E2E test `refresh preserves current route` with a multi-page React Router fixture to verify behavior > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit d715ec38b8fb94383eafe3a31b901d407468ab4e. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Refresh in the preview panel now preserves the current route instead of resetting to /. Routes are also restored after HMR remounts; adds an E2E test to confirm this; fixes #253. - **Bug Fixes** - Track route changes (pushState/replaceState) and persist the current URL per app via previewCurrentUrlAtom; on reload, validate same-origin and use it as the iframe src; reset on app change. - Add a data-testid to the address bar and a Playwright test that navigates via a react-router link in a multi-page app, refreshes, waits, and asserts the route remains. <sup>Written for commit d715ec38b8fb94383eafe3a31b901d407468ab4e. Summary will update on new commits.</sup> <!-- End of auto-generated description by cubic. --> --------- Co-authored-by: 's avatarClaude Opus 4.5 <noreply@anthropic.com> Co-authored-by: 's avatarclaude[bot] <41898282+claude[bot]@users.noreply.github.com>
上级 ff07bbc5
Creating a multi-page app with navigation.
<dyad-write path="src/pages/Index.tsx" description="Home page with navigation link">
import { Link } from "react-router-dom";
const Index = () => {
return (
<div className="min-h-screen flex items-center justify-center bg-gray-100">
<div className="text-center">
<h1 className="text-4xl font-bold mb-4">Home Page</h1>
<Link
to="/about"
className="text-blue-500 hover:text-blue-700 underline"
data-testid="nav-to-about"
>
Go to About Page
</Link>
</div>
</div>
);
};
export default Index;
</dyad-write>
<dyad-write path="src/pages/About.tsx" description="About page with navigation link back">
import { Link } from "react-router-dom";
const About = () => {
return (
<div className="min-h-screen flex items-center justify-center bg-gray-100">
<div className="text-center">
<h1 className="text-4xl font-bold mb-4">About Page</h1>
<Link
to="/"
className="text-blue-500 hover:text-blue-700 underline"
data-testid="nav-to-home"
>
Go to Home Page
</Link>
</div>
</div>
);
};
export default About;
</dyad-write>
<dyad-write path="src/App.tsx" description="App with router">
import { Toaster } from "@/components/ui/toaster";
import { Toaster as Sonner } from "@/components/ui/sonner";
import { TooltipProvider } from "@/components/ui/tooltip";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Index from "./pages/Index";
import About from "./pages/About";
const queryClient = new QueryClient();
const App = () => (
<QueryClientProvider client={queryClient}>
<TooltipProvider>
<Toaster />
<Sonner />
<BrowserRouter>
<Routes>
<Route path="/" element={<Index />} />
<Route path="/about" element={<About />} />
</Routes>
</BrowserRouter>
</TooltipProvider>
</QueryClientProvider>
);
export default App;
</dyad-write>
import { testSkipIfWindows } from "./helpers/test_helper";
import { testSkipIfWindows, Timeout } from "./helpers/test_helper";
import { expect } from "@playwright/test";
testSkipIfWindows("refresh app", async ({ po }) => {
await po.setUp({ autoApprove: true });
......@@ -17,3 +18,56 @@ testSkipIfWindows("refresh app", async ({ po }) => {
await po.clickPreviewRefresh();
await po.snapshotPreview();
});
testSkipIfWindows("refresh preserves current route", async ({ po }) => {
await po.setUp({ autoApprove: true });
// Create a multi-page app with react-router navigation
await po.sendPrompt("tc=multi-page");
// Wait for the preview iframe to be visible and loaded
await po.expectPreviewIframeIsVisible();
// Wait for the Home Page content to be visible in the iframe
await expect(
po.getPreviewIframeElement().contentFrame().getByText("Home Page"),
).toBeVisible({ timeout: Timeout.LONG });
// Click on the navigation link to go to /about (realistic user behavior)
await po
.getPreviewIframeElement()
.contentFrame()
.getByText("Go to About Page")
.click();
// Wait for the About Page content to be visible
await expect(
po.getPreviewIframeElement().contentFrame().getByText("About Page"),
).toBeVisible({ timeout: Timeout.MEDIUM });
// Click refresh
await po.clickPreviewRefresh();
// Verify the route is preserved after refresh - About Page should still be visible
await expect(
po.getPreviewIframeElement().contentFrame().getByText("About Page"),
).toBeVisible({ timeout: Timeout.MEDIUM });
// Wait to see if the page stays on About Page (reproducing local issue with HMR)
await po.page.waitForTimeout(5_000);
// Verify it's STILL on About Page after waiting - check that About Page heading is visible
// and the Home Page heading is not (use getByRole to match the heading, not the link text)
await expect(
po
.getPreviewIframeElement()
.contentFrame()
.getByRole("heading", { name: "About Page" }),
).toBeVisible({ timeout: Timeout.MEDIUM });
await expect(
po
.getPreviewIframeElement()
.contentFrame()
.getByRole("heading", { name: "Home Page" }),
).not.toBeVisible();
});
......@@ -24,6 +24,10 @@ export const envVarsAtom = atom<Record<string, string | undefined>>({});
export const previewPanelKeyAtom = atom<number>(0);
// Stores the current preview URL to preserve route across HMR-induced remounts
// Maps appId to the current URL for that app
export const previewCurrentUrlAtom = atom<Record<number, string>>({});
export const previewErrorMessageAtom = atom<
{ message: string; source: "preview-app" | "dyad-app" } | undefined
>(undefined);
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论