Skip to content

Commit

Permalink
chore: init some component tests (#898)
Browse files Browse the repository at this point in the history
Co-authored-by: Mark R. Florkowski <[email protected]>
  • Loading branch information
juliusmarminge and markflorkowski authored Sep 5, 2024
1 parent 7478a92 commit cf6337f
Show file tree
Hide file tree
Showing 4 changed files with 317 additions and 13 deletions.
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@
"@manypkg/cli": "^0.21.3",
"@playwright/test": "1.45.0",
"@prettier/sync": "^0.5.2",
"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.4.8",
"@testing-library/react": "^16.0.0",
"@types/bun": "^1.1.5",
"@types/node": "^20.14.0",
"@uploadthing/eslint-config": "workspace:*",
Expand Down
229 changes: 229 additions & 0 deletions packages/react/test/upload-button.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
// @vitest-environment happy-dom
/// <reference types="@testing-library/jest-dom/vitest" />

import { cleanup, fireEvent, render, waitFor } from "@testing-library/react";
import { http, HttpResponse } from "msw";
import { setupServer } from "msw/node";
import {
afterAll,
afterEach,
beforeAll,
describe,
expect,
it,
vi,
} from "vitest";

import {
createRouteHandler,
createUploadthing,
extractRouterConfig,
} from "uploadthing/server";

import { generateUploadButton } from "../src";

const noop = vi.fn();

const f = createUploadthing();
const testRouter = {
image: f({ image: {} }).onUploadComplete(noop),
audio: f({ audio: {} }).onUploadComplete(noop),
pdf: f({ "application/pdf": {} }).onUploadComplete(noop),
multi: f({ image: { maxFileCount: 4 } }).onUploadComplete(noop),
};
const routeHandler = createRouteHandler({ router: testRouter });
const UploadButton = generateUploadButton<typeof testRouter>();

const utGet = vi.fn<[Request]>();
const utPost = vi.fn<[{ request: Request; body: any }]>();
const server = setupServer(
http.get("/api/uploadthing", ({ request }) => {
utGet(request);
return routeHandler.GET(request);
}),
http.post("/api/uploadthing", async ({ request }) => {
const body = await request.json();
utPost({ request, body });
return HttpResponse.json([
// empty array, we're not testing the upload endpoint here
// we have other tests for that...
]);
}),
);

beforeAll(() => server.listen());
afterEach(() => {
server.resetHandlers();
cleanup();
});
afterAll(() => server.close());

/**
* This is a basic suite of tests for the UploadButton component.
* This is not meant to be a comprehensive test suite, but rather a
* basic check of core functionality.
*
* In the future, the goal is to use these and other tests to ensure
* consistency across the components in alll of the supported libraries
* (React, Solid, Svelte, Vue, etc.). Ideally core test logic should be
* shared as much as possible, so that we don;t have to maintain the test
* implementations in addition to the component implementations.
*
* In #886, we attempted to bring all of the libraries in line with each
* other, and at that time we manually went verified the following:
* - components all accept the same props/have similar APIs
* - styles match across libraries in each component state
* - copy matches across libraries
* - components are reactive
* - selecting files changes the text on the button
* - uploading changes style and text on button
* - paste works
* - abort works
*
* Some other items we did not check but probably should have:
* - functions are triggered at the right times
* - onChange should occur on select, clear, and drop
* - custom styling overrides are available and functioning (and always apply
* to the same parts of the component)
*/

describe("UploadButton - basic", () => {
it("fetches and displays route config", async () => {
const utils = render(<UploadButton endpoint="image" />);
const label = utils.container.querySelector("label");

// Previously, when component was disabled, it would show "Loading..."
// expect(label).toHaveTextContent("Loading...");

// then eventually we load in the data, and we should be in the ready state
await waitFor(() => expect(label).toHaveAttribute("data-state", "ready"));
expect(label).toHaveTextContent("Choose File");

expect(utGet).toHaveBeenCalledOnce();
expect(utils.getByText("Image (4MB)")).toBeInTheDocument();
});

it("picks up route config from global and skips fetch", async () => {
(window as any).__UPLOADTHING = extractRouterConfig(testRouter);

const utils = render(<UploadButton endpoint="image" />);
expect(utils.getByText("Image (4MB)")).toBeInTheDocument();
expect(utGet).not.toHaveBeenCalled();

delete (window as any).__UPLOADTHING;
});

it("requests URLs when a file is selected", async () => {
const utils = render(<UploadButton endpoint="image" />);
const label = utils.container.querySelector("label");
await waitFor(() => expect(label).toHaveAttribute("data-state", "ready"));

fireEvent.change(utils.getByLabelText("Choose File"), {
target: { files: [new File(["foo"], "foo.txt", { type: "text/plain" })] },
});
await waitFor(() => {
expect(utPost).toHaveBeenCalledWith(
expect.objectContaining({
body: {
files: [{ name: "foo.txt", type: "text/plain", size: 3 }],
},
}),
);
});
});

it("manual mode requires extra click", async () => {
const utils = render(
<UploadButton endpoint="image" config={{ mode: "manual" }} />,
);
const label = utils.container.querySelector("label");
await waitFor(() => expect(label).toHaveAttribute("data-state", "ready"));

fireEvent.change(utils.getByLabelText("Choose File"), {
target: { files: [new File([""], "foo.txt")] },
});
expect(label).toHaveTextContent("Upload 1 file");

fireEvent.click(label!);
await waitFor(() => {
expect(utPost).toHaveBeenCalledWith(
expect.objectContaining({
body: {
files: [expect.objectContaining({ name: "foo.txt" })],
},
}),
);
});
});

// https://discord.com/channels/966627436387266600/1102510616326967306/1267098160468197409
it.skip("disabled", async () => {
const utils = render(<UploadButton endpoint="image" disabled />);
const label = utils.container.querySelector("label");
await waitFor(() => expect(label).toHaveTextContent("Choose File"));
expect(label).toHaveAttribute("disabled");
});
});

describe("UploadButton - lifecycle hooks", () => {
it("onBeforeUploadBegin alters the requested files", async () => {
const utils = render(
<UploadButton
endpoint="image"
onBeforeUploadBegin={() => {
return [new File([""], "bar.txt")];
}}
/>,
);
await waitFor(() => {
expect(utils.getByText("Choose File")).toBeInTheDocument();
});

fireEvent.change(utils.getByLabelText("Choose File"), {
target: { files: [new File([""], "foo.txt")] },
});
await waitFor(() => {
expect(utPost).toHaveBeenCalledWith(
expect.objectContaining({
body: {
files: [expect.objectContaining({ name: "bar.txt" })],
},
}),
);
});
});
});

describe("UploadButton - Theming", () => {
it("renders custom styles", async () => {
const utils = render(
<UploadButton
endpoint="image"
appearance={{
button: { backgroundColor: "red" },
}}
/>,
);
await waitFor(() => {
expect(utils.getByText("Choose File")).toHaveStyle({
backgroundColor: "red",
});
});
});
});

describe("UploadButton - Content Customization", () => {
it("renders custom content", async () => {
const utils = render(
<UploadButton
endpoint="image"
content={{
allowedContent: "Allowed content",
}}
/>,
);
await waitFor(() => {
expect(utils.getByText("Allowed content")).toBeInTheDocument();
});
});
});
10 changes: 9 additions & 1 deletion packages/react/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -1 +1,9 @@
export { default } from "../../vitest.config";
import { mergeConfig } from "vitest/config";

import baseConfig from "../../vitest.config";

export default mergeConfig(baseConfig, {
test: {
setupFiles: ["@testing-library/jest-dom/vitest"],
},
});
Loading

0 comments on commit cf6337f

Please sign in to comment.