Skip to content

Commit

Permalink
포스트페이지-퍼블리싱 (#9)
Browse files Browse the repository at this point in the history
* New : 카드 컴포넌트 구현

* New : 카드 컴포넌트 스펙정의

* Refactor : 메인페이지 (포스트) 레이아웃 구현

* Refactor : 네비게이션 바 컴포넌트 변경
  • Loading branch information
jobkaeHenry authored Nov 5, 2023
1 parent 86e9e29 commit 7bff1ea
Show file tree
Hide file tree
Showing 8 changed files with 273 additions and 43 deletions.
47 changes: 47 additions & 0 deletions client/src/__test__/post/PostCard.test.tsx
Original file line number Diff line number Diff line change
@@ -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(<PostCard {...mockData} />));
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(<PostCard {...{ ...mockData, image: [] }} />);
const imgNode = screen.queryByTestId("postImg");
expect(imgNode).not.toBeInTheDocument();
});
it("유저 이미지가 없을경우 이미지대신 1글자의 더미문자 표시여부", () => {
render(<PostCard {...{ ...mockData, userImage: undefined }} />);
const imgNode = screen.queryByTestId("avatar");
expect(imgNode).toHaveTextContent(/./);
});
});
11 changes: 9 additions & 2 deletions client/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand All @@ -25,7 +25,14 @@ export default function RootLayout({
<body>
<ThemeRegistry options={{ key: "mui" }}>
<GlobalStyles styles={OverrideCSS} />
{children}
<Box
sx={{
maxHeight: "calc(100vh - 56px)",
overflow: "auto",
}}
>
{children}
</Box>
<NavigationBar />
</ThemeRegistry>
</body>
Expand Down
9 changes: 5 additions & 4 deletions client/src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<Paper>
<Button href={'/hi'}>버튼</Button>
</Paper>
<Container sx={{ px: { xs: 0, sm: 4 } }} maxWidth={"lg"}>
<PostCardList />
</Container>
);
}
56 changes: 19 additions & 37 deletions client/src/components/NavigationBar.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
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";
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 = [
{
Expand Down Expand Up @@ -40,44 +36,30 @@ const NavbarData = [
];

const NavigationBar = () => {
// FIXME const path = usePathname();
return (
<AppBar position="fixed" sx={{ top: "auto", bottom: 0 }}>
<Toolbar sx={{ mx: "auto", gap: 4 }}>
<Paper
sx={{ position: "fixed", bottom: 0, left: 0, right: 0 }}
elevation={3}
>
<BottomNavigation
// FIXME value={path}
>
{NavbarData.map((buttonData) => {
return (
<NavbarIconButton
iconComponent={buttonData.iconComponent}
<BottomNavigationAction
icon={buttonData.iconComponent as any}
key={buttonData.label}
component={buttonData.href ? Link : "button"}
href={buttonData.href}
>
{buttonData.label}
</NavbarIconButton>
// FIXME value={buttonData.href}
// FIXME label={buttonData.label}
/>
);
})}
</Toolbar>
</AppBar>
</BottomNavigation>
</Paper>
);
};

export default NavigationBar;

interface NavbarIconButtonInterface extends Omit<ButtonBaseProps, "children"> {
children: string;
iconComponent: React.ReactNode;
href?: string;
}

export const NavbarIconButton = ({
children,
iconComponent,
...others
}: NavbarIconButtonInterface) => {
return (
<ButtonBase sx={{ flexDirection: "column", width: 56 }} {...others}>
{iconComponent}
<Typography variant="label" component="span">
{children}
</Typography>
</ButtonBase>
);
};
78 changes: 78 additions & 0 deletions client/src/components/post/PostCard.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<Card>
{/* Header */}
<CardHeader
data-testid="mui-header"
avatar={
<Avatar
sx={{ bgcolor: "secondary.main" }}
src={userImage}
data-testid="avatar"
>
{userImage || userId[0].toUpperCase()}
</Avatar>
}
action={
<IconButton aria-label="settings">
<MoreVertOutlined />
</IconButton>
}
title={`@${userId}`}
subheader={nickname}
/>
{/* image */}
{image.length !== 0 && (
<CardMedia
data-testid="postImg"
component="img"
height="360"
image={image[0]}
alt={`${userId}의 포스트`}
/>
)}
{/* Contents */}
<CardContent>
<Typography variant="body1" color="text.secondary">
{content}
</Typography>
</CardContent>
{/* CTA */}
<CardActions disableSpacing>
<IconButton aria-label="add to favorites">
<FavoriteOutlined data-testid="likeBtn" />
</IconButton>
<IconButton data-testid="shareBtn" aria-label="share">
<ShareOutlined />
</IconButton>
</CardActions>
</Card>
);
};

export default PostCard;
32 changes: 32 additions & 0 deletions client/src/components/post/PostCardList.tsx
Original file line number Diff line number Diff line change
@@ -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) => (
<PostCard {...post} key={post.id} />
))}
</>
);
};

export default PostCardList;
50 changes: 50 additions & 0 deletions client/src/stories/Components/Post/PostCard.stories.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<Container maxWidth="md" sx={{ mx: "auto" }}>
<Story />
</Container>
);
},
],
} satisfies Meta<typeof PostCard>;

export default meta;

type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
...mockData,
},
};
export const withoutImage: Story = {
args: {
...mockData,
image: [],
},
};
export const withoutUserImage: Story = {
args: {
...mockData,
userImage: undefined,
},
};
33 changes: 33 additions & 0 deletions client/src/types/post/PostInterface.ts
Original file line number Diff line number Diff line change
@@ -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[];
}

0 comments on commit 7bff1ea

Please sign in to comment.