import { Context, Meta } from "@content-collections/core";
import { cleanup, render, renderHook, screen } from "@testing-library/react";
import { Node, Parent } from "mdast";
import { Pluggable, Transformer } from "unified";
import { beforeEach, describe, expect, it, vitest } from "vitest";
import { Options, compileMDX } from ".";
import {
MDXContent as MDXClientContent,
useMDXComponent as useClientMDXComponent,
} from "./react/client";
import { MDXContent, useMDXComponent } from "./react/server";
type Cache = Context["cache"];
const cache: Cache = (input, fn) => {
return fn(input) as any;
};
const sampleMeta: Meta = {
directory: "post",
extension: ".mdx",
filePath: "post/index.mdx",
fileName: "index",
path: "/post",
};
beforeEach(cleanup);
describe("MDX tests", () => {
async function mdx(content: string, options?: Options) {
const mdx = await compileMDX(
{ cache },
{
content,
_meta: sampleMeta,
},
options,
);
return ;
}
it("should render simple MDX", async () => {
render(await mdx("# Hello World!"));
expect(screen.getByRole("heading")).toHaveTextContent("Hello World!");
});
it("should render MDX with content", async () => {
const content = await mdx(
`import HelloWorld from "./HelloWorld";
`,
{
files: (appender) => {
appender.content(
"./HelloWorld",
"export default function HelloWorld() { return
Hello World!
}",
);
},
},
);
render(content);
expect(screen.getByRole("heading")).toHaveTextContent("Hello World!");
});
it("should render MDX with content from file", async () => {
const content = await mdx(
`import HelloWorld from "./HelloWorld";
`,
{
files: (appender) => {
appender.file("./HelloWorld", "./src/__tests__/HelloWorld.tsx");
},
},
);
render(content);
expect(screen.getByRole("heading")).toHaveTextContent("Hello World!");
});
it("should render MDX with content from directory", async () => {
const content = await mdx(
`import Hello from "./Hello";
import World from "./World";
`,
{
files: (appender) => {
appender.directory("./", "./src/__tests__");
},
},
);
render(content);
expect(screen.getByRole("heading", { name: "Hello" })).toBeInTheDocument();
expect(screen.getByRole("heading", { name: "World" })).toBeInTheDocument();
});
it("should not fail if directory does not exist", async () => {
const content = await mdx("# Hello World", {
files: (appender) => {
appender.directory("./", "./src/non-existing");
},
});
render(content);
expect(screen.getByRole("heading")).toHaveTextContent("Hello World");
});
function isParentNode(tree: Node): tree is Parent {
return "children" in tree;
}
function isMeta(object: any): object is Meta {
return "path" in object;
}
function createAppenderPlugin(): Pluggable {
return (): Transformer => (tree, vFile) => {
let path = "unknown";
if (isMeta(vFile.data._meta)) {
path = vFile.data._meta.path;
}
if (isParentNode(tree)) {
tree.children.push({
type: "heading",
depth: 2,
children: [{ type: "text", value: `hello from ${path}` }],
});
}
};
}
it("should add _meta to the virtual file system", async () => {
const content = await mdx("", {
remarkPlugins: [createAppenderPlugin()],
});
render(content);
expect(screen.getByRole("heading")).toHaveTextContent("hello from /post");
});
it("should use passed components", async () => {
const mdx = await compileMDX(
{ cache },
{
content: "",
_meta: sampleMeta,
},
);
function HelloWorld() {
return Hello World!
;
}
function Content() {
const Component = useMDXComponent(mdx);
return ;
}
render();
expect(screen.getByRole("heading")).toHaveTextContent("Hello World!");
});
});
describe("/react (client)", () => {
async function mdx(content: string, options?: Options) {
return await compileMDX(
{ cache },
{
content,
_meta: {
directory: "post",
extension: ".mdx",
filePath: "post/index.mdx",
fileName: "index",
path: "/post",
},
},
options,
);
}
it("should memoize the component", async () => {
const content = await mdx("# Memoize");
const { result, rerender } = renderHook(() =>
useClientMDXComponent(content),
);
const first = result.current;
rerender();
const second = result.current;
expect(first).toBe(second);
});
it("should render simple MDX", async () => {
const code = await mdx("# Hello World!");
render();
expect(screen.getByRole("heading")).toHaveTextContent("Hello World!");
});
it("should only use required props for caching", async () => {
const cacheFn = vitest.fn();
const doc = {
title: "Hello World",
content: "# hello world",
_meta: sampleMeta,
};
await compileMDX({ cache: cacheFn }, doc);
const call = cacheFn.mock.calls[0];
if (!call) {
throw new Error("cache function was not called");
}
const [input] = call;
expect(Object.keys(input)).toEqual(["content", "_meta"]);
});
});