diff --git a/client/src/__test__/post/PostCard.test.tsx b/client/src/__test__/post/PostCard.test.tsx new file mode 100644 index 0000000..413bcea --- /dev/null +++ b/client/src/__test__/post/PostCard.test.tsx @@ -0,0 +1,47 @@ +import "@testing-library/jest-dom"; +import { render, screen } from "@testing-library/react"; +import PostCard from "@/components/post/PostCard"; + +const mockData = { + id: "123458", + createdAt: "Mon Nov 06 2023 00:13:07", + nickname: "testNick", + userId: "userID", + userImage: "https://source.unsplash.com/random?wallpapers", + content: + "Lorem ipsum dolor, sit amet consectetur adipisicing elit. Eos ullam aut minus aliquam quis officia, non dolore omnis, magnam totam tenetur ad harum? Mollitia omnis odit atque blanditiis exercitationem! Voluptatum.", + image: ["https://source.unsplash.com/random?wallpapers"], +}; + +describe("버튼 컴포넌트 스펙", () => { + beforeEach(() => render()); + it("@유저아이디 형태의 헤더가 존재하는지 체크", () => { + expect(screen.getByTestId("mui-header")).toHaveTextContent( + `@${mockData.userId}` + ); + }); + it("유저 닉네임이 존재하는지 여부 체크", () => { + expect(screen.getByTestId("mui-header")).toHaveTextContent( + mockData.nickname + ); + }); + it("공유하기 버튼이 존재하는지 여부", () => { + expect(screen.getByTestId("shareBtn")).not.toBeNull(); + }); + it("좋아요 버튼이 존재하는지 여부", () => { + expect(screen.getByTestId("likeBtn")).not.toBeNull(); + }); +}); + +describe("버튼 컴포넌트 조건부렌더링 테스트", () => { + it("포스트에 이미지가 없을경우 이미지가 표시되지 않는지 여부", () => { + render(); + const imgNode = screen.queryByTestId("postImg"); + expect(imgNode).not.toBeInTheDocument(); + }); + it("유저 이미지가 없을경우 이미지대신 1글자의 더미문자 표시여부", () => { + render(); + const imgNode = screen.queryByTestId("avatar"); + expect(imgNode).toHaveTextContent(/./); + }); +}); diff --git a/client/src/app/layout.tsx b/client/src/app/layout.tsx index aa40cf4..8e1f6ea 100644 --- a/client/src/app/layout.tsx +++ b/client/src/app/layout.tsx @@ -2,7 +2,7 @@ import type { Metadata, Viewport } from "next"; import ThemeRegistry from "@/components/theme/ThemeRegistry"; import { nameOfApp, oneLineMessage } from "@/const/brand"; import OverrideCSS from "@/const/overrideCSS"; -import { GlobalStyles } from "@mui/material"; +import { Box, GlobalStyles } from "@mui/material"; import Pretendard from "~/assets/font/Pretendard"; import NavigationBar from "~/components/NavigationBar"; @@ -25,7 +25,14 @@ export default function RootLayout({ - {children} + + {children} + diff --git a/client/src/app/page.tsx b/client/src/app/page.tsx index 5506174..d75255b 100644 --- a/client/src/app/page.tsx +++ b/client/src/app/page.tsx @@ -1,9 +1,10 @@ -import { Button, Paper } from "@mui/material"; +import PostCardList from "@/components/post/PostCardList"; +import { Container } from "@mui/material"; export default function Home() { return ( - - - + + + ); } diff --git a/client/src/components/NavigationBar.tsx b/client/src/components/NavigationBar.tsx index c5c3489..03524fa 100644 --- a/client/src/components/NavigationBar.tsx +++ b/client/src/components/NavigationBar.tsx @@ -1,10 +1,5 @@ -import { - AppBar, - ButtonBase, - ButtonBaseProps, - Toolbar, - Typography, -} from "@mui/material"; +// FIXME "use client"; +import { BottomNavigation, BottomNavigationAction, Paper } from "@mui/material"; import HomeIcon from "~/assets/icons/HomeIcon.svg"; import SearchIcon from "~/assets/icons/SearchIcon.svg"; @@ -12,6 +7,7 @@ import PostIcon from "~/assets/icons/PostIcon.svg"; import BeverageIcon from "~/assets/icons/BeverageIcon.svg"; import MyIcon from "~/assets/icons/MyIcon.svg"; import HOME, { MY_PROFILE, WIKI } from "@/const/clientPath"; +import Link from "next/link"; const NavbarData = [ { @@ -40,44 +36,30 @@ const NavbarData = [ ]; const NavigationBar = () => { + // FIXME const path = usePathname(); return ( - - + + {NavbarData.map((buttonData) => { return ( - - {buttonData.label} - + // FIXME value={buttonData.href} + // FIXME label={buttonData.label} + /> ); })} - - + + ); }; export default NavigationBar; - -interface NavbarIconButtonInterface extends Omit { - children: string; - iconComponent: React.ReactNode; - href?: string; -} - -export const NavbarIconButton = ({ - children, - iconComponent, - ...others -}: NavbarIconButtonInterface) => { - return ( - - {iconComponent} - - {children} - - - ); -}; diff --git a/client/src/components/post/PostCard.tsx b/client/src/components/post/PostCard.tsx new file mode 100644 index 0000000..c5e382b --- /dev/null +++ b/client/src/components/post/PostCard.tsx @@ -0,0 +1,78 @@ +"use client"; +import { PostInterface } from "@/types/post/PostInterface"; +import { + FavoriteOutlined, + MoreVertOutlined, + ShareOutlined, +} from "@mui/icons-material"; +import { + Avatar, + Card, + CardActions, + CardContent, + CardHeader, + CardMedia, + IconButton, + Typography, +} from "@mui/material"; + +const PostCard = ({ + image, + createdAt, + userId, + nickname, + content, + userImage, +}: PostInterface) => { + return ( + + {/* Header */} + + {userImage || userId[0].toUpperCase()} + + } + action={ + + + + } + title={`@${userId}`} + subheader={nickname} + /> + {/* image */} + {image.length !== 0 && ( + + )} + {/* Contents */} + + + {content} + + + {/* CTA */} + + + + + + + + + + ); +}; + +export default PostCard; diff --git a/client/src/components/post/PostCardList.tsx b/client/src/components/post/PostCardList.tsx new file mode 100644 index 0000000..cc07a82 --- /dev/null +++ b/client/src/components/post/PostCardList.tsx @@ -0,0 +1,32 @@ +import { Paper } from "@mui/material"; +import PostCard from "@/components/post/PostCard"; +import { PostInterface } from "@/types/post/PostInterface"; + +const PostCardList = () => { + const dummyPost: PostInterface[] = Array.from(new Array(5)).map((data, i) => { + return { + id: String(i), + createdAt: "Mon Nov 06 2023 00:13:07", + nickname: "testNick", + userId: "userID", + userImage: + i % 2 !== 0 + ? undefined + : "https://source.unsplash.com/random?wallpapers", + content: + "Lorem ipsum dolor, sit amet consectetur adipisicing elit. Eos ullam aut minus aliquam quis officia, non dolore omnis, magnam totam tenetur ad harum? Mollitia omnis odit atque blanditiis exercitationem! Voluptatum.", + image: + i % 2 === 0 ? [] : ["https://source.unsplash.com/random?wallpapers"], + }; + }); + + return ( + <> + {dummyPost.map((post) => ( + + ))} + + ); +}; + +export default PostCardList; diff --git a/client/src/stories/Components/Post/PostCard.stories.tsx b/client/src/stories/Components/Post/PostCard.stories.tsx new file mode 100644 index 0000000..92a1860 --- /dev/null +++ b/client/src/stories/Components/Post/PostCard.stories.tsx @@ -0,0 +1,50 @@ +import PostCard from "@/components/post/PostCard"; +import { Container, Paper } from "@mui/material"; +import { Meta, StoryObj } from "@storybook/react"; + +const mockData = { + id: "123458", + createdAt: "Mon Nov 06 2023 00:13:07", + nickname: "testNick", + userId: "userID", + userImage: "", + content: + "Lorem ipsum dolor, sit amet consectetur adipisicing elit. Eos ullam aut minus aliquam quis officia, non dolore omnis, magnam totam tenetur ad harum? Mollitia omnis odit atque blanditiis exercitationem! Voluptatum.", + image: ["https://source.unsplash.com/random?wallpapers"], +}; + +const meta = { + title: "Components/Post/PostCard", + component: PostCard, + tags: ["autodocs"], + decorators: [ + (Story) => { + return ( + + + + ); + }, + ], +} satisfies Meta; + +export default meta; + +type Story = StoryObj; +export const Default: Story = { + args: { + ...mockData, + }, +}; +export const withoutImage: Story = { + args: { + ...mockData, + image: [], + }, +}; +export const withoutUserImage: Story = { + args: { + ...mockData, + userImage: undefined, + }, +}; diff --git a/client/src/types/post/PostInterface.ts b/client/src/types/post/PostInterface.ts new file mode 100644 index 0000000..ebc6c38 --- /dev/null +++ b/client/src/types/post/PostInterface.ts @@ -0,0 +1,33 @@ +/** + * 서버로부터 응답받은 포스트 간략정보 + */ +export interface PostInterface { + /** + * 글 PK + */ + id: string; + /** + * 글 최초 생성일 + */ + createdAt: string; + /** + * 유저가 설정한 닉네임 + */ + nickname: string; + /** + * 유저의 ID (로그인용 Email 과는 다름) + */ + userId: string; + /** + * 유저 프로필 (Optional, 없을 경우 유저 닉네임 첫글자로 대체) + */ + userImage?: string; + /** + * 포스트 내용 + */ + content: string; + /** + * 이미지 Href 배열 + */ + image: [] | string[]; +}