diff --git a/src/components/global/Footer/index.module.scss b/src/components/global/Footer/index.module.scss
index a34c698..e9cc03e 100644
--- a/src/components/global/Footer/index.module.scss
+++ b/src/components/global/Footer/index.module.scss
@@ -6,16 +6,8 @@
.group {
width: 100%;
height: 113px;
- display: flex;
- flex-direction: column-reverse;
- align-items: center;
- justify-content: center;
- gap: 16px;
@include desktop {
height: 100px;
- flex-direction: row;
- justify-content: space-between;
- gap: 0;
}
}
diff --git a/src/components/global/Footer/index.test.tsx b/src/components/global/Footer/index.test.tsx
new file mode 100644
index 0000000..0389b80
--- /dev/null
+++ b/src/components/global/Footer/index.test.tsx
@@ -0,0 +1,42 @@
+import type { ReactNode } from 'react';
+
+import { render, screen } from '@testing-library/react';
+
+import Footer from './index';
+
+vi.mock('@/components/atoms/Layout', () => ({
+ default: ({ children }: { children: ReactNode }) => (
+
{children}
+ ),
+}));
+
+vi.mock('@/components/molecules/SocialIconLink', () => ({
+ default: ({ type, url }: { type: string; url: string }) => (
+ {type}
+ ),
+}));
+
+describe('Footer', () => {
+ it('renders the footer copyright and social links', () => {
+ render();
+
+ expect(screen.getByRole('contentinfo')).toBeInTheDocument();
+ expect(screen.getByText('All rights reserved ⓒ SIPE')).toBeInTheDocument();
+ expect(screen.getByRole('link', { name: 'INSTAGRAM' })).toHaveAttribute(
+ 'href',
+ 'https://www.instagram.com/sipe_team',
+ );
+ expect(screen.getByRole('link', { name: 'GITHUB' })).toHaveAttribute(
+ 'href',
+ 'https://github.com/sipe-team',
+ );
+ expect(screen.getByRole('link', { name: 'YOUTUBE' })).toHaveAttribute(
+ 'href',
+ 'https://www.youtube.com/@sipe_team',
+ );
+ expect(screen.getByRole('link', { name: 'LINKEDIN' })).toHaveAttribute(
+ 'href',
+ 'https://www.linkedin.com/company/sipe.team',
+ );
+ });
+});
diff --git a/src/components/global/Footer/index.tsx b/src/components/global/Footer/index.tsx
index 541f7e7..6b9b12b 100644
--- a/src/components/global/Footer/index.tsx
+++ b/src/components/global/Footer/index.tsx
@@ -10,7 +10,13 @@ function Footer() {
diff --git a/src/components/global/Navigation/index.module.scss b/src/components/global/Navigation/index.module.scss
index 9014d9d..a485b64 100644
--- a/src/components/global/Navigation/index.module.scss
+++ b/src/components/global/Navigation/index.module.scss
@@ -43,8 +43,6 @@
.menuList {
width: 100%;
- flex-direction: column;
- align-items: center;
padding-bottom: 20px;
@include mobile-and-tablet {
@@ -60,9 +58,6 @@
}
@include desktop {
- flex-direction: row;
- justify-content: center;
- align-items: center;
padding: 0;
}
}
diff --git a/src/components/global/Navigation/index.test.tsx b/src/components/global/Navigation/index.test.tsx
new file mode 100644
index 0000000..187a921
--- /dev/null
+++ b/src/components/global/Navigation/index.test.tsx
@@ -0,0 +1,138 @@
+import type { ReactNode } from 'react';
+
+import { sendGAEvent } from '@next/third-parties/google';
+import { fireEvent, render, screen } from '@testing-library/react';
+
+import Navigation from './index';
+
+vi.mock('next/link', () => ({
+ default: ({ children, href }: { children: ReactNode; href: string }) => (
+ {children}
+ ),
+}));
+
+vi.mock('next/navigation', () => ({
+ usePathname: () => '/about',
+}));
+
+vi.mock('@next/third-parties/google', () => ({
+ sendGAEvent: vi.fn(),
+}));
+
+vi.mock('@/components/atoms/Layout', () => ({
+ default: ({
+ children,
+ className,
+ }: {
+ children: ReactNode;
+ className?: string;
+ }) => {children}
,
+}));
+
+vi.mock('@/components/global/HamburgerButton', () => ({
+ default: ({
+ isOpened,
+ onClick,
+ }: {
+ isOpened: boolean;
+ onClick: () => void;
+ }) => (
+
+ ),
+}));
+
+vi.mock('@/components/molecules/Button', () => ({
+ default: ({
+ active,
+ children,
+ disabled,
+ href,
+ onClick,
+ }: {
+ active?: boolean;
+ children: ReactNode;
+ disabled?: boolean;
+ href?: string;
+ onClick?: () => void;
+ }) => (
+ {
+ event.preventDefault();
+ onClick?.();
+ }}
+ >
+ {children}
+
+ ),
+}));
+
+vi.mock('@/libs/assets/logos', () => ({
+ SipeLogo: () => ,
+}));
+
+vi.mock('@/libs/utils/recruit', () => ({
+ displayApplication: {
+ ongoing: {
+ buttonText: '지원하기',
+ formUrl: 'https://example.com/apply',
+ },
+ },
+ getCurrentStatus: () => 'ongoing',
+}));
+
+describe('Navigation', () => {
+ beforeEach(() => {
+ vi.mocked(sendGAEvent).mockClear();
+ });
+
+ it('renders the current menu, navigation links, and application link', () => {
+ render();
+
+ expect(screen.getByRole('banner')).toBeInTheDocument();
+ expect(screen.getByRole('navigation')).toBeInTheDocument();
+ expect(screen.getByLabelText('사이프 로고').closest('a')).toHaveAttribute(
+ 'href',
+ '/',
+ );
+ expect(screen.getByRole('link', { name: 'About' })).toHaveAttribute(
+ 'aria-current',
+ 'page',
+ );
+ expect(screen.getByRole('link', { name: 'Recruit' })).toHaveAttribute(
+ 'href',
+ '/recruit',
+ );
+ expect(screen.getByRole('link', { name: 'People' })).toHaveAttribute(
+ 'href',
+ '/people',
+ );
+ expect(screen.getByRole('link', { name: 'Activity' })).toHaveAttribute(
+ 'href',
+ '/activity',
+ );
+ expect(screen.getByRole('link', { name: 'Join Us' })).toHaveAttribute(
+ 'href',
+ 'https://example.com/apply',
+ );
+ });
+
+ it('toggles the mobile menu and reports application clicks', () => {
+ render();
+
+ const menuButton = screen.getByRole('button', { name: '메뉴' });
+ expect(menuButton).toHaveAttribute('aria-expanded', 'false');
+
+ fireEvent.click(menuButton);
+ expect(menuButton).toHaveAttribute('aria-expanded', 'true');
+
+ fireEvent.click(screen.getByRole('link', { name: 'Join Us' }));
+ expect(sendGAEvent).toHaveBeenCalledWith('event', 'cilck_join_us_button', {
+ screen_name: 'menu',
+ });
+ });
+});
diff --git a/src/components/global/Navigation/index.tsx b/src/components/global/Navigation/index.tsx
index 7d12444..36e1dc3 100644
--- a/src/components/global/Navigation/index.tsx
+++ b/src/components/global/Navigation/index.tsx
@@ -72,11 +72,14 @@ function Navigation() {
>