Skip to content

Commit

Permalink
Feature/deleteDialog (#301)
Browse files Browse the repository at this point in the history
  • Loading branch information
ttizze authored Sep 19, 2024
2 parents 8a7f8d5 + b592ba9 commit 57ec65d
Show file tree
Hide file tree
Showing 7 changed files with 249 additions and 13 deletions.
175 changes: 175 additions & 0 deletions web/app/routes/$userName+/index.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
import { createRemixStub } from "@remix-run/testing";
import { render, screen, waitFor } from "@testing-library/react";
import { expect, test } from "vitest";
import "@testing-library/jest-dom";
import { userEvent } from "@testing-library/user-event";
import { authenticator } from "~/utils/auth.server";
import { prisma } from "~/utils/prisma";
import UserProfile, { loader, action } from "./index";

vi.mock("~/utils/auth.server", () => ({
authenticator: {
isAuthenticated: vi.fn(),
},
}));

describe("UserProfile", () => {
beforeEach(async () => {
await prisma.user.create({
data: {
userName: "testuser",
displayName: "Test User",
email: "[email protected]",
icon: "https://example.com/icon.jpg",
profile: "This is a test profile",
pages: {
create: [
{
title: "Public Page",
slug: "public-page",
isPublished: true,
content: "This is a test content",
},
{
title: "Private Page",
slug: "private-page",
isPublished: false,
content: "This is a test content2",
},
{
title: "Archived Page",
slug: "archived-page",
isArchived: true,
content: "This is a test content3",
},
],
},
},
include: { pages: true },
});
});

test("loader returns correct data and menu is displayed for authenticated owner", async () => {
// @ts-ignore
vi.mocked(authenticator.isAuthenticated).mockResolvedValue({
id: 1,
userName: "testuser",
});
const RemixStub = createRemixStub([
{
path: "/:userName",
Component: UserProfile,
loader,
},
]);

render(<RemixStub initialEntries={["/testuser"]} />);

expect(await screen.findByText("Test User")).toBeInTheDocument();
expect(
await screen.findByText("This is a test profile"),
).toBeInTheDocument();
expect(await screen.findByText("Public Page")).toBeInTheDocument();
expect(await screen.findByText("Private Page")).toBeInTheDocument();
expect(await screen.queryByText("Archived Page")).not.toBeInTheDocument();
const menuButtons = await screen.findAllByLabelText("More options");
expect(menuButtons.length).toBeGreaterThan(0);

await userEvent.click(menuButtons[0]);

expect(await screen.findByText("Edit")).toBeInTheDocument();
expect(await screen.findByText("Make Private")).toBeInTheDocument();
expect(await screen.findByText("Delete")).toBeInTheDocument();
});

test("loader returns correct data and menu is not displayed for unauthenticated visitor", async () => {
// @ts-ignore
vi.mocked(authenticator.isAuthenticated).mockResolvedValue(null);
const RemixStub = createRemixStub([
{
path: "/:userName",
Component: UserProfile,
loader,
},
]);
render(<RemixStub initialEntries={["/testuser"]} />);

expect(await screen.findByText("Test User")).toBeInTheDocument();
expect(
await screen.findByText("This is a test profile"),
).toBeInTheDocument();
expect(await screen.findByText("Public Page")).toBeInTheDocument();
expect(await screen.queryByText("Private Page")).not.toBeInTheDocument();
expect(await screen.queryByText("Archived Page")).not.toBeInTheDocument();
expect(
await screen.queryByLabelText("More options"),
).not.toBeInTheDocument();
});

test("action handles togglePublish correctly", async () => {
// @ts-ignore
vi.mocked(authenticator.isAuthenticated).mockResolvedValue({
id: 1,
userName: "testuser",
});
const RemixStub = createRemixStub([
{
path: "/:userName",
Component: UserProfile,
loader,
action,
},
]);
render(<RemixStub initialEntries={["/testuser"]} />);

const menuButtons = await screen.findAllByLabelText("More options");
expect(menuButtons.length).toBeGreaterThan(0);

await userEvent.click(menuButtons[0]);

expect(await screen.findByText("Edit")).toBeInTheDocument();
expect(await screen.findByText("Make Private")).toBeInTheDocument();
await userEvent.click(await screen.findByText("Make Private"));

waitFor(() => {
userEvent.click(menuButtons[0]);
expect(screen.findByText("Make Public")).toBeInTheDocument();
});
});

test("action handles archive correctly", async () => {
// @ts-ignore
vi.mocked(authenticator.isAuthenticated).mockResolvedValue({
id: 1,
userName: "testuser",
});
const RemixStub = createRemixStub([
{
path: "/:userName",
Component: UserProfile,
loader,
action,
},
]);
render(<RemixStub initialEntries={["/testuser"]} />);

const menuButtons = await screen.findAllByLabelText("More options");
expect(menuButtons.length).toBeGreaterThan(0);

await userEvent.click(menuButtons[0]);

expect(await screen.findByText("Delete")).toBeInTheDocument();
await userEvent.click(await screen.findByText("Delete"));
expect(
await screen.findByText(
"This action cannot be undone. Are you sure you want to delete this page?",
),
).toBeInTheDocument();

await userEvent.click(await screen.findByText("Delete"));

await waitFor(() => {
expect(screen.queryByText("Test Page")).not.toBeInTheDocument();
});
});
});
55 changes: 47 additions & 8 deletions web/app/routes/$userName+/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { useNavigate } from "@remix-run/react";
import type { MetaFunction } from "@remix-run/react";
import Linkify from "linkify-react";
import { Lock, MoreVertical, Settings } from "lucide-react";
import { BookOpen, Trash } from "lucide-react";
import { useState } from "react";
import { Button } from "~/components/ui/button";
import {
Card,
Expand All @@ -15,6 +17,14 @@ import {
CardHeader,
CardTitle,
} from "~/components/ui/card";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "~/components/ui/dialog";
import {
DropdownMenu,
DropdownMenuContent,
Expand Down Expand Up @@ -84,10 +94,12 @@ export async function action({ request }: ActionFunctionArgs) {
}
}

export default function UserProfile() {
export default function UserPage() {
const navigate = useNavigate();
const { sanitizedUserWithPages, isOwner, pageCreatedAt } =
useLoaderData<typeof loader>();
const [dialogOpen, setDialogOpen] = useState(false);
const [pageToDelete, setPageToDelete] = useState<number | null>(null);

const fetcher = useFetcher();

Expand All @@ -99,15 +111,18 @@ export default function UserProfile() {
};

const handleArchive = (pageId: number) => {
if (
confirm(
"Are you sure you want to delete this page? This action cannot be undone.",
)
) {
fetcher.submit({ intent: "archive", pageId: pageId }, { method: "post" });
setPageToDelete(pageId);
setDialogOpen(true);
};
const confirmArchive = () => {
if (pageToDelete) {
fetcher.submit(
{ intent: "archive", pageId: pageToDelete },
{ method: "post" },
);
}
setDialogOpen(false);
};

return (
<div className="">
<div className="mb-6 rounded-3xl w-full overflow-hidden ">
Expand Down Expand Up @@ -166,6 +181,7 @@ export default function UserProfile() {
<Button
variant="ghost"
className="h-8 w-8 p-0 absolute top-2 right-2"
aria-label="More options"
>
<MoreVertical className="h-4 w-4" />
</Button>
Expand Down Expand Up @@ -213,6 +229,29 @@ export default function UserProfile() {
{isOwner ? "You haven't created any pages yet." : "No pages yet."}
</p>
)}
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle className="flex items-center">
<Trash className="w-4 h-4 mr-2" />
<BookOpen className="w-4 h-4 mr-2" />
</DialogTitle>
<DialogDescription>
This action cannot be undone. Are you sure you want to delete this
page?
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button variant="outline" onClick={() => setDialogOpen(false)}>
Cancel
</Button>
<Button variant="destructive" onClick={confirmArchive}>
<Trash className="w-4 h-4 mr-2" />
Delete
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
);
}
7 changes: 5 additions & 2 deletions web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"start": "NODE_OPTIONS='--import ./instrumentation.server.mjs' remix-serve ./build/server/index.js",
"typecheck": "tsc --noEmit",
"check": "bunx @biomejs/biome check --write .",
"seed": "prisma db seed"
"seed": "prisma db seed",
"test": "vitest"
},
"prisma": {
"seed": "tsx prisma/seed.ts"
Expand Down Expand Up @@ -96,6 +97,7 @@
"@remix-run/dev": "^2.12.0",
"@remix-run/testing": "^2.12.0",
"@testing-library/jest-dom": "^6.5.0",
"@testing-library/react": "^16.0.1",
"@testing-library/user-event": "^14.5.2",
"@types/bcryptjs": "^2.4.6",
"@types/jsdom": "^21.1.7",
Expand All @@ -114,7 +116,8 @@
"vite": "^5.4.5",
"vite-env-only": "^3.0.3",
"vite-tsconfig-paths": "^5.0.1",
"vitest": "^2.1.1"
"vitest": "^2.1.1",
"vitest-environment-vprisma": "^1.3.0"
},
"engines": {
"node": ">=20.0.0"
Expand Down
7 changes: 6 additions & 1 deletion web/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@
],
"compilerOptions": {
"lib": ["DOM", "DOM.Iterable", "ES2022"],
"types": ["@remix-run/node", "vite/client"],
"types": [
"@remix-run/node",
"vite/client",
"vitest/globals",
"vitest-environment-vprisma"
],
"isolatedModules": true,
"esModuleInterop": true,
"jsx": "react-jsx",
Expand Down
4 changes: 3 additions & 1 deletion web/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ export default defineConfig({
},
ignoredRouteFiles: ["**/*"],
routes: async (defineRoutes) => {
return flatRoutes("routes", defineRoutes);
return flatRoutes("routes", defineRoutes, {
ignoredRouteFiles: ["**/*.test.{js,jsx,ts,tsx}"],
});
},
}),
tsconfigPaths(),
Expand Down
8 changes: 7 additions & 1 deletion web/vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,13 @@ import * as VitestConfig from "vitest/config";
export default VitestConfig.defineConfig({
test: {
globals: true,
environment: "jsdom",
environment: "vprisma",
setupFiles: ["vitest-environment-vprisma/setup", "vitest.setup.ts"],
environmentOptions: {
vprisma: {
baseEnv: "jsdom",
},
},
},
resolve: {
alias: {
Expand Down
6 changes: 6 additions & 0 deletions web/vitest.setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { vi } from "vitest";

vi.mock("~/utils/prisma", () => ({
// @ts-ignore
prisma: vPrisma.client,
}));

0 comments on commit 57ec65d

Please sign in to comment.