Skip to content

Commit 55e7cc0

Browse files
authored
Add infinite scroll on start page (#96)
1 parent f3213cd commit 55e7cc0

File tree

6 files changed

+93
-12
lines changed

6 files changed

+93
-12
lines changed

src/actions/index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
"use server";
22

33
import { deployEnv } from "@/constants";
4-
import { searchGifs, shareEvent } from "@/api/klipy";
4+
import { searchGifs, shareEvent, trendingGifs } from "@/api/klipy";
55
import type { GifsResult } from "@/types/Gif";
66

77
export const handleSearchGifs = async (
88
searchTerm: string,
99
next?: string,
1010
): Promise<GifsResult> => searchGifs(searchTerm, { next });
1111

12+
export const handleTrendingGifs = async (next?: string): Promise<GifsResult> =>
13+
trendingGifs({ next });
14+
1215
export const handleShared = async (
1316
slug: string,
1417
searchTerm: string,

src/app/page.spec.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { test } from "@/test/browser/fixtures";
44
import { testLayout } from "@/test/browser/shared";
55
import { getTabKey } from "@/test/browser/utils";
66
import { mockedTrendingResponse } from "@/test/api/klipy/mocks/trendingResponse";
7+
import { klipyTrendingHandler } from "@/test/api/klipy";
78

89
test("initial render", async ({ page, baseUrl }) => {
910
await page.goto(baseUrl);
@@ -19,6 +20,61 @@ test("initial render", async ({ page, baseUrl }) => {
1920
await expect(gifs).toHaveCount(gifLimit);
2021
});
2122

23+
test("scrolling to load more gifs", async ({
24+
page,
25+
baseUrl,
26+
requestInterceptor,
27+
}) => {
28+
requestInterceptor.use(
29+
klipyTrendingHandler(200, mockedTrendingResponse, { delay: 200 }),
30+
);
31+
32+
await page.goto(baseUrl);
33+
34+
const gifs = page.getByRole("button", {
35+
name: /Load high quality preview of gif with description/,
36+
});
37+
await expect(gifs).toHaveCount(gifLimit);
38+
39+
await page.evaluate(() => {
40+
window.scrollTo(0, document.body.scrollHeight);
41+
});
42+
43+
await expect(page.getByRole("progressbar")).toBeVisible();
44+
45+
await expect(gifs).toHaveCount(gifLimit * 2);
46+
await expect(page.getByRole("progressbar")).not.toBeVisible();
47+
});
48+
49+
test("handle error on scroll", async ({
50+
page,
51+
baseUrl,
52+
requestInterceptor,
53+
}) => {
54+
requestInterceptor.use(klipyTrendingHandler(200, mockedTrendingResponse));
55+
56+
await page.goto(baseUrl);
57+
58+
const gifs = page.getByRole("button", {
59+
name: /Load high quality preview of gif with description/,
60+
});
61+
await expect(gifs).toHaveCount(gifLimit);
62+
63+
requestInterceptor.use(klipyTrendingHandler(500, undefined, { delay: 200 }));
64+
65+
await page.evaluate(() => {
66+
window.scrollTo(0, document.body.scrollHeight);
67+
});
68+
69+
await expect(page.getByRole("progressbar")).toBeVisible();
70+
await expect(page.getByRole("progressbar")).not.toBeVisible();
71+
72+
await expect(page.getByText("Failed to load more!")).toBeVisible();
73+
await expect(
74+
page.getByRole("button", { name: "Try again", exact: true }),
75+
).toBeVisible();
76+
});
77+
2278
test("selecting a gif and copying the url", async ({
2379
page,
2480
baseUrl,

src/app/page.tsx

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,30 @@
1+
import { handleTrendingGifs } from "@/actions";
12
import { trendingGifs } from "@/api/klipy";
2-
import { GifsGrid } from "@/components/GifsGrid";
33
import { GifPreview } from "@/components/GifPreview";
4+
import { GifsGrid } from "@/components/GifsGrid";
5+
import { MoreGifs } from "@/components/MoreGifs";
46

57
const IndexPage = async () => {
68
const gifsResult = await trendingGifs();
79

810
return (
911
<GifsGrid>
10-
{gifsResult.gifs.map((gif, index) => (
11-
<GifPreview
12-
key={`${gif.id}-${index}`}
13-
gif={gif}
14-
searchTerm="featured"
12+
<GifsGrid>
13+
{gifsResult.gifs.map((gif, index) => (
14+
<GifPreview
15+
key={`${gif.id}-${index}`}
16+
gif={gif}
17+
searchTerm="trending"
18+
/>
19+
))}
20+
</GifsGrid>
21+
{gifsResult.next && (
22+
<MoreGifs
23+
searchTerm="trending"
24+
initialResult={gifsResult}
25+
onMoreGifs={handleTrendingGifs}
1526
/>
16-
))}
27+
)}
1728
</GifsGrid>
1829
);
1930
};

src/app/search/[searchTerm]/page.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { handleSearchGifs } from "@/actions";
12
import { searchGifs } from "@/api/klipy";
23
import { GifPreview } from "@/components/GifPreview";
34
import { GifsGrid } from "@/components/GifsGrid";
@@ -22,7 +23,11 @@ const SearchResultPage = async ({
2223
))}
2324
</GifsGrid>
2425
{gifsResult.next && (
25-
<MoreGifs searchTerm={searchTerm} initialResult={gifsResult} />
26+
<MoreGifs
27+
searchTerm={searchTerm}
28+
initialResult={gifsResult}
29+
onMoreGifs={handleSearchGifs.bind(null, searchTerm)}
30+
/>
2631
)}
2732
</GifsGrid>
2833
);

src/components/MoreGifs.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import {
88
useRef,
99
useState,
1010
} from "react";
11-
import { handleSearchGifs } from "@/actions";
1211
import type { Gif, GifsResult } from "@/types/Gif";
1312
import { useIntersectionObserver } from "@/hooks/useIntersectionObserver";
1413
import { GifPreview } from "@/components/GifPreview";
@@ -18,9 +17,11 @@ import { toast } from "sonner";
1817
export const MoreGifs = ({
1918
searchTerm,
2019
initialResult,
20+
onMoreGifs,
2121
}: {
2222
searchTerm: string;
2323
initialResult: GifsResult;
24+
onMoreGifs: (next?: string) => Promise<GifsResult>;
2425
}) => {
2526
const [next, setNext] = useState<string | undefined>(initialResult.next);
2627
const onLoadMoreRef = useRef<() => void>(null);
@@ -30,7 +31,7 @@ export const MoreGifs = ({
3031
const [gifs, onLoadMore, isPending] = useActionState<Gif[]>(
3132
async (previousState) => {
3233
try {
33-
const newGifsResult = await handleSearchGifs(searchTerm, next);
34+
const newGifsResult = await onMoreGifs(next);
3435

3536
setNext(newGifsResult.next);
3637
return [...previousState, ...newGifsResult.gifs];

src/test/api/klipy/index.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,13 @@ import { klipyBaseUrl } from "@/constants";
88
export const klipyTrendingHandler = (
99
status: number = 200,
1010
response: KlipyGifResponse = mockedTrendingResponse,
11+
options?: { delay: DelayMode | number },
1112
) =>
12-
http.get(`${klipyBaseUrl}/:apiKey/gifs/trending`, () => {
13+
http.get(`${klipyBaseUrl}/:apiKey/gifs/trending`, async () => {
14+
if (options?.delay) {
15+
await delay(options.delay);
16+
}
17+
1318
if (!isOkStatus(status)) return new HttpResponse(null, { status });
1419

1520
return HttpResponse.json(response, { status });

0 commit comments

Comments
 (0)