test: add unit test for ChatMessageDisplay

This commit is contained in:
Alexander Daichendt 2025-02-13 14:31:28 +01:00
parent bc95fb10fc
commit 744854fe0d
5 changed files with 1024 additions and 9 deletions

View file

@ -1,13 +1,21 @@
{ {
"name": "react-ts", "name": "simple-chat-client",
"private": true, "private": true,
"version": "0.0.0", "version": "1.0.0",
"type": "module", "type": "module",
"author": {
"email": "git@daichendt.one",
"name": "Alexander Daichendt",
"url": "https://daichendt.one"
},
"packageManager": "pnpm@10.3.0",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "tsc -b && vite build", "build": "tsc -b && vite build",
"lint": "eslint .", "lint": "eslint .",
"preview": "vite preview" "preview": "vite preview",
"test": "vitest",
"coverage": "vitest run --coverage"
}, },
"dependencies": { "dependencies": {
"@tailwindcss/vite": "^4.0.6", "@tailwindcss/vite": "^4.0.6",
@ -18,6 +26,8 @@
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.19.0", "@eslint/js": "^9.19.0",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.2.0",
"@types/react": "^19.0.8", "@types/react": "^19.0.8",
"@types/react-dom": "^19.0.3", "@types/react-dom": "^19.0.3",
"@vitejs/plugin-react": "^4.3.4", "@vitejs/plugin-react": "^4.3.4",
@ -27,6 +37,7 @@
"globals": "^15.14.0", "globals": "^15.14.0",
"typescript": "~5.7.2", "typescript": "~5.7.2",
"typescript-eslint": "^8.22.0", "typescript-eslint": "^8.22.0",
"vite": "^6.1.0" "vite": "^6.1.0",
"vitest": "^3.0.5"
} }
} }

871
client/pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,133 @@
import { render, screen, fireEvent } from "@testing-library/react";
import { describe, it, expect, vi } from "vitest";
import ChatMessageDisplay from "../components/ChatMessage/ChatMessageDisplay";
import { ChatMessage } from "../../../shared";
import "@testing-library/jest-dom/vitest";
describe("ChatMessageDisplay", () => {
const mockMessage: ChatMessage = {
id: "1",
content: "Test message",
author: {
userId: "user1",
name: "Test User",
},
timestamp: Date.now(),
};
const mockHandlers = {
onDelete: vi.fn(),
onEdit: vi.fn(),
};
it("renders message content correctly", () => {
render(
<ChatMessageDisplay
message={mockMessage}
userId="user1"
onDelete={mockHandlers.onDelete}
onEdit={mockHandlers.onEdit}
/>,
);
expect(screen.getByText("Test message")).toBeInTheDocument();
expect(screen.getByText("Test User")).toBeInTheDocument();
});
it("shows edit/delete buttons for own messages", () => {
render(
<ChatMessageDisplay
message={mockMessage}
userId="user1"
onDelete={mockHandlers.onDelete}
onEdit={mockHandlers.onEdit}
/>,
);
expect(screen.getByRole("button", { name: /edit/i })).toBeInTheDocument();
expect(screen.getByRole("button", { name: /delete/i })).toBeInTheDocument();
});
it("does not show edit/delete buttons for other users messages", () => {
render(
<ChatMessageDisplay
message={mockMessage}
userId="user2"
onDelete={mockHandlers.onDelete}
onEdit={mockHandlers.onEdit}
/>,
);
expect(
screen.queryByRole("button", { name: /edit/i }),
).not.toBeInTheDocument();
expect(
screen.queryByRole("button", { name: /delete/i }),
).not.toBeInTheDocument();
});
it("enters edit mode when edit button is clicked", () => {
render(
<ChatMessageDisplay
message={mockMessage}
userId="user1"
onDelete={mockHandlers.onDelete}
onEdit={mockHandlers.onEdit}
/>,
);
fireEvent.click(screen.getByRole("button", { name: /edit/i }));
expect(screen.getByRole("textbox")).toBeInTheDocument();
expect(screen.getByRole("button", { name: /save/i })).toBeInTheDocument();
expect(screen.getByRole("button", { name: /cancel/i })).toBeInTheDocument();
});
it("calls onEdit when saving edited message", () => {
render(
<ChatMessageDisplay
message={mockMessage}
userId="user1"
onDelete={mockHandlers.onDelete}
onEdit={mockHandlers.onEdit}
/>,
);
fireEvent.click(screen.getByRole("button", { name: /edit/i }));
const textarea = screen.getByRole("textbox");
fireEvent.change(textarea, { target: { value: "Edited message" } });
fireEvent.click(screen.getByRole("button", { name: /save/i }));
expect(mockHandlers.onEdit).toHaveBeenCalledWith("1", "Edited message");
});
it("calls onDelete when delete button is clicked", () => {
render(
<ChatMessageDisplay
message={mockMessage}
userId="user1"
onDelete={mockHandlers.onDelete}
onEdit={mockHandlers.onEdit}
/>,
);
fireEvent.click(screen.getByRole("button", { name: /delete/i }));
expect(mockHandlers.onDelete).toHaveBeenCalledWith("1");
});
it("has correct accessibility attributes", () => {
render(
<ChatMessageDisplay
message={mockMessage}
userId="user1"
onDelete={mockHandlers.onDelete}
onEdit={mockHandlers.onEdit}
/>,
);
const article = screen.getByRole("article");
expect(article).toHaveAttribute(
"aria-label",
expect.stringContaining("Message from Test User"),
);
});
});

View file

@ -54,8 +54,3 @@ export const useChatState = () => {
} }
return context; return context;
}; };
export const useCurrentUser = () => {
const { state } = useChatState();
return state.currentUser;
};

View file

@ -1,3 +1,4 @@
/// <reference types="vitest/config" />
import { defineConfig } from "vite"; import { defineConfig } from "vite";
import react from "@vitejs/plugin-react"; import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite"; import tailwindcss from "@tailwindcss/vite";
@ -5,6 +6,10 @@ import tailwindcss from "@tailwindcss/vite";
// https://vite.dev/config/ // https://vite.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [react(), tailwindcss()], plugins: [react(), tailwindcss()],
test: {
globals: true,
environment: "jsdom",
},
server: { server: {
proxy: { proxy: {
"/ws": { "/ws": {