diff --git a/__tests__/components/drops/view/part/DropPartMarkdown.test.tsx b/__tests__/components/drops/view/part/DropPartMarkdown.test.tsx index 3d7f221a00..aaf4c09eae 100644 --- a/__tests__/components/drops/view/part/DropPartMarkdown.test.tsx +++ b/__tests__/components/drops/view/part/DropPartMarkdown.test.tsx @@ -1,15 +1,55 @@ import { render, screen } from "@testing-library/react"; -import DropPartMarkdown from "../../../../../components/drops/view/part/DropPartMarkdown"; +import type { ReactElement } from "react"; -jest.mock("../../../../../hooks/isMobileScreen", () => () => false); -jest.mock("../../../../../contexts/EmojiContext", () => ({ - useEmoji: () => ({ emojiMap: [] }), -})); -jest.mock("react-tweet", () => ({ - Tweet: ({ id }: any) =>
tweet:{id}
, -})); +type DropPartMarkdownType = typeof import("../../../../../components/drops/view/part/DropPartMarkdown").default; describe("DropPartMarkdown", () => { + let DropPartMarkdown: DropPartMarkdownType; + let dynamicSpy: jest.Mock; + + const loadComponent = async (): Promise => { + jest.resetModules(); + dynamicSpy = jest.fn(); + + jest.doMock("../../../../../hooks/isMobileScreen", () => () => false); + jest.doMock("../../../../../contexts/EmojiContext", () => ({ + useEmoji: () => ({ + emojiMap: [], + findNativeEmoji: () => undefined, + }), + })); + jest.doMock("react-tweet", () => ({ + Tweet: ({ id }: any) =>
tweet:{id}
, + })); + jest.doMock("next/dynamic", () => ({ + __esModule: true, + default: (loader: any, options: any) => { + dynamicSpy(loader, options); + const LoadingComponent = options?.loading; + const DynamicComponent = (props: any): ReactElement | null => + LoadingComponent ? : null; + + (DynamicComponent as any).__loader = loader; + (DynamicComponent as any).__options = options; + + return DynamicComponent; + }, + })); + + DropPartMarkdown = ( + await import("../../../../../components/drops/view/part/DropPartMarkdown") + ).default; + }; + + beforeEach(async () => { + await loadComponent(); + }); + + afterEach(() => { + jest.resetModules(); + delete process.env.BASE_ENDPOINT; + }); + it("renders gif embeds", () => { const content = "Check this ![gif](https://media.tenor.com/test.gif)"; render( @@ -57,4 +97,27 @@ describe("DropPartMarkdown", () => { expect(a).not.toHaveAttribute("target"); expect(a).toHaveAttribute("href", "/page"); }); + + it("lazy loads tweet embeds with a loading skeleton", async () => { + const content = "Check this [tweet](https://twitter.com/user/status/1234567890)"; + + render( + + ); + + expect(screen.getByTestId("tweet-embed-loading")).toBeInTheDocument(); + + expect(dynamicSpy).toHaveBeenCalledTimes(1); + const [loader, options] = dynamicSpy.mock.calls[0]; + expect(options?.ssr).toBe(false); + + const TweetComponent = await loader(); + const { getByText } = render(); + expect(getByText("tweet:abc123")).toBeInTheDocument(); + }); }); diff --git a/components/drops/view/part/DropPartMarkdown.tsx b/components/drops/view/part/DropPartMarkdown.tsx index 0289b22202..2a4a22ab69 100644 --- a/components/drops/view/part/DropPartMarkdown.tsx +++ b/components/drops/view/part/DropPartMarkdown.tsx @@ -14,17 +14,15 @@ import Markdown, { ExtraProps } from "react-markdown"; import rehypeExternalLinks from "rehype-external-links"; import rehypeSanitize from "rehype-sanitize"; import remarkGfm from "remark-gfm"; +import dynamic from "next/dynamic"; import { getRandomObjectId } from "../../../../helpers/AllowlistToolHelpers"; - -// Component definitions moved outside for better performance -const BreakComponent = () =>
; import DropListItemContentPart, { DropListItemContentPartProps, } from "../item/content/DropListItemContentPart"; import { ApiDropMentionedUser } from "../../../../generated/models/ApiDropMentionedUser"; import { ApiDropReferencedNFT } from "../../../../generated/models/ApiDropReferencedNFT"; -import { Tweet } from "react-tweet"; +import type { TweetProps } from "react-tweet"; import DropPartMarkdownImage from "./DropPartMarkdownImage"; import WaveDropQuoteWithDropId from "../../../waves/drops/WaveDropQuoteWithDropId"; @@ -42,6 +40,25 @@ import WaveItemChat from "../../../waves/list/WaveItemChat"; import DropItemChat from "../../../waves/drops/DropItemChat"; import ChatItemHrefButtons from "../../../waves/ChatItemHrefButtons"; +const BreakComponent = () =>
; + +const TweetEmbedSkeleton = () => ( +
+ Loading tweet… +
+); + +const TweetEmbed = dynamic( + () => import("react-tweet").then((mod) => mod.Tweet), + { + ssr: false, + loading: TweetEmbedSkeleton, + } +); + export interface DropPartMarkdownProps { readonly mentionedUsers: Array; readonly referencedNfts: Array; @@ -322,7 +339,7 @@ function DropPartMarkdown({ const renderTweetEmbed = (result: { href: string; tweetId: string }) => (
- +