Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions e2e/create-pages.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,16 +153,89 @@ for (const mode of ['DEV', 'PRD'] as const) {
// https://github.com/wakujs/waku/issues/1255
test('long suspense', async ({ page }) => {
await page.goto(`http://localhost:${port}/long-suspense/1`);
await expect(page.getByTestId('long-suspense-component')).toHaveCount(2);
await expect(
page.getByRole('heading', { name: 'Long Suspense Page 1' }),
).toBeVisible();
await page.click("a[href='/long-suspense/2']");
await page.waitForFunction(
() => {
const pendingElement = document.querySelector(
'[data-testid="long-suspense-pending"]',
);
const heading = document.querySelector(
'[data-testid="long-suspense-component"] h3',
);
return (
pendingElement?.textContent === 'Pending...' &&
heading?.textContent === 'Long Suspense Page 1'
);
},
undefined,
{ timeout: 1000 },
);
await expect(page.getByTestId('long-suspense')).toHaveCount(0);
await expect(
page.getByRole('heading', { name: 'Long Suspense Page 2' }),
).toBeVisible();
await page.click("a[href='/long-suspense/3']");
await expect(
page.getByRole('heading', { name: 'Long Suspense Page 2' }),
).not.toBeVisible();
await expect(page.getByTestId('long-suspense')).toHaveText('Loading...');
await expect(page.getByTestId('long-suspense-pending')).toHaveCount(0);
await expect(
page.getByRole('heading', { name: 'Long Suspense Page 3' }),
).toBeVisible();
await page.click("a[href='/long-suspense/2']");
await page.waitForFunction(
() => {
const pendingElement = document.querySelector(
'[data-testid="long-suspense-pending"]',
);
const heading = document.querySelector(
'[data-testid="long-suspense-component"] h3',
);
return (
pendingElement?.textContent === 'Pending...' &&
heading?.textContent === 'Long Suspense Page 3'
);
},
undefined,
{ timeout: 1000 },
);
await expect(page.getByTestId('long-suspense')).toHaveCount(0);
await expect(
page.getByRole('heading', { name: 'Long Suspense Page 2' }),
).toBeVisible();
});

// https://github.com/wakujs/waku/issues/1437
test('static long suspense', async ({ page }) => {
await page.goto(`http://localhost:${port}/static-long-suspense/4`);
// no loading state for static
await expect(page.getByTestId('long-suspense')).toHaveCount(0);
await expect(page.getByTestId('long-suspense-component')).toHaveCount(2);
await expect(
page.getByRole('heading', { name: 'Long Suspense Page 4' }),
).toBeVisible();
await page.click("a[href='/static-long-suspense/5']");
// It flashes very briefly
// await expect(page.getByTestId('long-suspense-pending')).toHaveCount(1);
await expect(page.getByTestId('long-suspense')).toHaveCount(0);
await expect(
page.getByRole('heading', { name: 'Long Suspense Page 5' }),
).toBeVisible();
await page.click("a[href='/static-long-suspense/6']");
// It flashes very briefly
// await expect(page.getByTestId('long-suspense-pending')).toHaveCount(0);
// No loading state with static
await expect(page.getByTestId('long-suspense')).toHaveCount(0);
await expect(
page.getByRole('heading', { name: 'Long Suspense Page 6' }),
).toBeVisible();
});

test('api hi', async () => {
const res = await fetch(`http://localhost:${port}/api/hi`);
expect(res.status).toBe(200);
Expand Down
59 changes: 51 additions & 8 deletions e2e/fixtures/create-pages/src/components/LongSuspenseLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,65 @@ import { Suspense } from 'react';
import type { ReactNode } from 'react';
import { Link } from 'waku';

const SlowComponent = async () => {
await new Promise((resolve) => setTimeout(resolve, 1000));
return <div>Slow Component</div>;
export const SlowComponent = async ({ children }: { children?: ReactNode }) => {
await new Promise((resolve) => setTimeout(resolve, 500));
return (
<div data-testid="long-suspense-component">
{children || 'Slow Component'}
</div>
);
};

const LongSuspenseLayout = ({ children }: { children: ReactNode }) => {
export const StaticLongSuspenseLayout = ({
children,
}: {
children: ReactNode;
}) => {
return (
<div>
<h2>Long Suspense Layout</h2>
<Link to="/long-suspense/2">Click Me</Link>
<h2>Static Long Suspense Layout</h2>
<div>
<Link
to="/static-long-suspense/5"
unstable_pending={
<div data-testid="long-suspense-pending">Pending...</div>
}
>
Click Me
</Link>
</div>
<div>
<Link to="/static-long-suspense/6">Click Me Too</Link>
</div>
<Suspense fallback={<div data-testid="long-suspense">Loading...</div>}>
<SlowComponent />
{children}
</Suspense>
{children}
</div>
);
};

export default LongSuspenseLayout;
export const LongSuspenseLayout = ({ children }: { children: ReactNode }) => {
return (
<div>
<h2>Long Suspense Layout</h2>
<div>
<Link
to="/long-suspense/2"
unstable_pending={
<div data-testid="long-suspense-pending">Pending...</div>
}
>
Click Me
</Link>
</div>
<div>
<Link to="/long-suspense/3">Click Me Too</Link>
</div>
<Suspense fallback={<div data-testid="long-suspense">Loading...</div>}>
<SlowComponent />
{children}
</Suspense>
</div>
);
};
70 changes: 64 additions & 6 deletions e2e/fixtures/create-pages/src/entries.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ import NestedBazPage from './components/NestedBazPage.js';
import NestedLayout from './components/NestedLayout.js';
import { DeeplyNestedLayout } from './components/DeeplyNestedLayout.js';
import ErrorPage from './components/ErrorPage.js';
import LongSuspenseLayout from './components/LongSuspenseLayout.js';
import {
SlowComponent,
StaticLongSuspenseLayout,
LongSuspenseLayout,
} from './components/LongSuspenseLayout.js';
import { readFile } from 'node:fs/promises';
import StaticPagePart from './components/StaticPagePart.js';
import DynamicPagePart from './components/DynamicPagePart.js';
Expand Down Expand Up @@ -93,21 +97,75 @@ const pages: ReturnType<typeof createPages> = createPages(
}),

createLayout({
render: 'dynamic',
render: 'static',
path: '/long-suspense',
component: LongSuspenseLayout,
}),

createPage({
render: 'static',
render: 'dynamic',
path: '/long-suspense/1',
component: () => <h3>Long Suspense Page 1</h3>,
component: () => (
<SlowComponent>
<h3>Long Suspense Page 1</h3>
</SlowComponent>
),
}),

createPage({
render: 'static',
render: 'dynamic',
path: '/long-suspense/2',
component: () => <h3>Long Suspense Page 2</h3>,
component: () => (
<SlowComponent>
<h3>Long Suspense Page 2</h3>
</SlowComponent>
),
}),

createPage({
render: 'dynamic',
path: '/long-suspense/3',
component: () => (
<SlowComponent>
<h3>Long Suspense Page 3</h3>
</SlowComponent>
),
}),

createLayout({
render: 'static',
path: '/static-long-suspense',
component: StaticLongSuspenseLayout,
}),

createPage({
render: 'static',
path: '/static-long-suspense/4',
component: () => (
<SlowComponent>
<h3>Long Suspense Page 4</h3>
</SlowComponent>
),
}),

createPage({
render: 'static',
path: '/static-long-suspense/5',
component: () => (
<SlowComponent>
<h3>Long Suspense Page 5</h3>
</SlowComponent>
),
}),

createPage({
render: 'static',
path: '/static-long-suspense/6',
component: () => (
<SlowComponent>
<h3>Long Suspense Page 6</h3>
</SlowComponent>
),
}),

createPage({
Expand Down
24 changes: 16 additions & 8 deletions packages/waku/src/router/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ type ChangeRoute = (
options: {
shouldScroll: boolean;
skipRefetch?: boolean;
unstable_startTransition?: ((fn: TransitionFunction) => void) | undefined;
},
) => Promise<void>;

Expand Down Expand Up @@ -339,7 +340,10 @@ export function Link({
'',
url,
);
await changeRoute(route, { shouldScroll: scroll ?? newPath });
await changeRoute(route, {
shouldScroll: scroll ?? newPath,
unstable_startTransition: startTransitionFn,
});
});
}
};
Expand Down Expand Up @@ -689,6 +693,8 @@ const InnerRouter = ({ initialRoute }: { initialRoute: RouteProps }) => {
const changeRoute: ChangeRoute = useCallback(
async (route, options) => {
executeListeners('start', route);
const startTransitionFn =
options.unstable_startTransition || ((fn: TransitionFunction) => fn());
refetching.current = [];
setErr(null);
const { skipRefetch } = options || {};
Expand All @@ -703,13 +709,15 @@ const InnerRouter = ({ initialRoute }: { initialRoute: RouteProps }) => {
throw e;
}
}
if (options.shouldScroll) {
handleScroll();
}
setRoute(route);
refetching.current[0]?.();
refetching.current = null;
executeListeners('complete', route);
startTransitionFn(() => {
if (options.shouldScroll) {
handleScroll();
}
setRoute(route);
refetching.current![0]?.();
refetching.current = null;
executeListeners('complete', route);
});
},
[executeListeners, refetch],
);
Expand Down
Loading