Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 5 additions & 13 deletions generator/plopfile.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ const PAGE_PATH = 'pagePath';

const COMPONENT = 'component';
const COMPONENT_NAME = 'componentName';
const COMPONENT_TYPE = 'componentType';
const API_LIST = 'apiList';

const API = 'api';
Expand Down Expand Up @@ -106,31 +105,31 @@ const pageActions = data => {
const componentActions = data => [
{
type: 'add',
path: `${rootPath}/components/{{${COMPONENT_TYPE}}}/${pascalCasify(COMPONENT_NAME)}/${pascalCasify(COMPONENT_NAME)}.tsx`,
path: `${rootPath}/components/${pascalCasify(COMPONENT_NAME)}/${pascalCasify(COMPONENT_NAME)}.tsx`,
templateFile: 'templates/component/component.hbs',
},
{
type: 'add',
path: `${rootPath}/components/{{${COMPONENT_TYPE}}}/${pascalCasify(COMPONENT_NAME)}/index.ts`,
path: `${rootPath}/components/${pascalCasify(COMPONENT_NAME)}/index.ts`,
templateFile: 'templates/component/index.hbs',
},
{
type: 'add',
path: `${rootPath}/components/{{${COMPONENT_TYPE}}}/${pascalCasify(COMPONENT_NAME)}/styled.ts`,
path: `${rootPath}/components/${pascalCasify(COMPONENT_NAME)}/styled.ts`,
templateFile: 'templates/component/styled.hbs',
...(data[RENDERING_TYPE] === 'SSR(Server-Side-Rendering)' && { skip: () => 'skipped' }),
},
{
type: 'add',
path: `${rootPath}/components/{{${COMPONENT_TYPE}}}/${pascalCasify(COMPONENT_NAME)}/${pascalCasify(COMPONENT_NAME)}.stories.ts`,
path: `${rootPath}/components/${pascalCasify(COMPONENT_NAME)}/${pascalCasify(COMPONENT_NAME)}.stories.ts`,
templateFile: 'templates/component/stories.hbs',
...(!data[TEST_EXIST] && {
skip: () => 'skipped',
}),
},
{
type: 'add',
path: `${rootPath}/components/{{${COMPONENT_TYPE}}}/${pascalCasify(COMPONENT_NAME)}/${pascalCasify(COMPONENT_NAME)}.test.tsx`,
path: `${rootPath}/components/${pascalCasify(COMPONENT_NAME)}/${pascalCasify(COMPONENT_NAME)}.test.tsx`,
templateFile: 'templates/component/test.hbs',
...(!data[TEST_EXIST] && {
skip: () => 'skipped',
Expand Down Expand Up @@ -173,13 +172,6 @@ export default function generator(plop) {
message: 'Asset type',
choices: [PAGE, COMPONENT, API],
},
{
type: 'list',
name: COMPONENT_TYPE,
when: answer => answer[ASSET_TYPE] === COMPONENT,
message: 'Component type',
choices: ['atoms', 'molecules', 'organisms', 'templates'],
},
{
type: 'input',
name: COMPONENT_NAME,
Expand Down
2 changes: 1 addition & 1 deletion generator/templates/component/stories.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { Meta, StoryObj } from '@storybook/react';
import {{pascalCase componentName}} from '.';

const meta = {
title: '{{pascalCase componentType}}/{{pascalCase componentName}}',
title: '{{pascalCase componentName}}',
{{#if (is "CSR(Client-Side-Rendering)" renderingType)}}
component: {{pascalCase componentName}},
{{/if}}
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build",
"test": "jest",
"test:unit": "jest --testPathPattern='src/app/api/.*\\.test\\.tsx?$|src/components/.*\\.test\\.tsx?$'",
"test:integration": "jest --testPathPattern='src/app/(?!api).*\\.test\\.tsx?$'",
"g": "plop --plopfile ./generator/plopfile.mjs",
"e2e": "start-server-and-test dev http://localhost:3000 \"cypress open --e2e\"",
"e2e:headless": "start-server-and-test dev http://localhost:3000 \"cypress run --e2e\""
Expand Down
38 changes: 2 additions & 36 deletions src/app/cars/page.tsx
Original file line number Diff line number Diff line change
@@ -1,41 +1,7 @@
import 'server-only';

import Link from 'next/link';

import { API_HOST } from '~/constants/apiRelated';

export const dynamic = 'force-dynamic';

export interface Car {
createdAt: string;
driverName: string;
driverAvatar: string;
carName: string;
carManufacturer: string;
isAllocation: boolean;
carId: string;
}

async function getCars(): Promise<Car[]> {
// You need to set base url in .env as origin of your url
const res = await fetch(API_HOST + '/api/cars');
await new Promise(resolve => setTimeout(resolve, 4000));
return res.json();
}
import Cars from '~/components/Cars';

export default async function CarsPage(...props: any) {
console.log(props);
const cars = await getCars();

return (
<div className="cars-root-page">
<ul>
{cars.map(v => (
<li key={v.carId}>
<Link href={`/cars/${v.carId}`}>{v.carName}</Link>
</li>
))}
</ul>
</div>
);
return <Cars latency={4000} />;
}
4 changes: 3 additions & 1 deletion src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ export const metadata: Metadata = {
},
description: 'NextJS + AppROuter',
other: {
'naver-site-verification': process.env.NEXT_PUBLIC_NAVER_SITE_VERIFICATION,
...(process.env.NEXT_PUBLIC_NAVER_SITE_VERIFICATION && {
'naver-site-verification': process.env.NEXT_PUBLIC_NAVER_SITE_VERIFICATION,
}),
},
};

Expand Down
18 changes: 0 additions & 18 deletions src/app/page.stories.tsx

This file was deleted.

22 changes: 0 additions & 22 deletions src/app/page.test.tsx

This file was deleted.

38 changes: 34 additions & 4 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,47 @@
'use client';

import { useEffect, useState } from 'react';

import Link from 'next/link';

import Button from '~/components/atoms/Button';
import Button from '~/components/Button';

import { RootPageStyled } from './styled';

export default function Home() {
const [isLoggedIn, setIsLoggedIn] = useState(false);

useEffect(() => {
if (typeof window === 'undefined') return;

const sessionLogin = sessionStorage.getItem('isLoggedIn');

if (sessionLogin !== 'true') return;

setIsLoggedIn(true);
}, []);

return (
<RootPageStyled>
<Link href={'/cars'}>
<Button>Go to cars page</Button>
</Link>
{isLoggedIn ? (
<>
<Link href={'/cars'}>
<Button>Go to cars page</Button>
</Link>
<Button
onClick={() => {
sessionStorage.setItem('isLoggedIn', 'false');
setIsLoggedIn(false);
}}
>
Sign Out
</Button>
</>
) : (
<Link href={'/sign/in'}>
<Button>Sign in needed</Button>
</Link>
)}
</RootPageStyled>
);
}
45 changes: 45 additions & 0 deletions src/app/sign/in/page-ui.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { fireEvent, render, screen } from '@testing-library/react';

import SignInPageUI from './page-ui';

describe('SignInPageUI', () => {
const mockOnSubmit = jest.fn();
const mockSetUsername = jest.fn();
const mockSetPassword = jest.fn();

beforeAll(() => {
render(
<SignInPageUI
onSubmit={mockOnSubmit}
username=""
password=""
setUsername={mockSetUsername}
setPassword={mockSetPassword}
/>,
);
});

it('Should render the SignInPageUI component', () => {
expect(document.querySelector('form')).toBeInTheDocument();
expect(document.querySelector('input[type="username"]')).toBeInTheDocument();
expect(document.querySelector('input[type="password"]')).toBeInTheDocument();
expect(document.querySelector('button')).toBeInTheDocument();
});

it('Should call setUsername when the username input changes', () => {
const usernameInput = screen.getByPlaceholderText('Username');
const passwordInput = screen.getByPlaceholderText('Password');

fireEvent.change(usernameInput, { target: { value: 'testUser' } });
fireEvent.change(passwordInput, { target: { value: 'password123' } });

expect(mockSetUsername).toHaveBeenCalledWith('testUser');
expect(mockSetPassword).toHaveBeenCalledWith('password123');
});

it('Should call onSubmit when the form is submitted', async () => {
fireEvent.submit(document.querySelector('form') as HTMLFormElement);

expect(mockOnSubmit).toHaveBeenCalled();
});
});
43 changes: 43 additions & 0 deletions src/app/sign/in/page-ui.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
'use client';

import { FormEvent } from 'react';

import { SignInpageStyled } from './styled';

interface SignInPageUIProps {
onSubmit: (e: FormEvent<HTMLFormElement>) => void;
username: string;
password: string;
setUsername: (value: string) => void;
setPassword: (value: string) => void;
}

const SignInPageUI = ({
onSubmit,
username,
password,
setUsername,
setPassword,
}: SignInPageUIProps) => {
return (
<SignInpageStyled>
<form onSubmit={onSubmit}>
<input
type="username"
placeholder="Username"
value={username}
onChange={e => setUsername(e.target.value)}
/>
<input
type="password"
placeholder="Password"
value={password}
onChange={e => setPassword(e.target.value)}
/>
<button>Sign In</button>
</form>
</SignInpageStyled>
);
};

export default SignInPageUI;
38 changes: 38 additions & 0 deletions src/app/sign/in/page.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { fireEvent, render, screen } from '@testing-library/react';

import { useRouter } from 'next/navigation';

import SignInpage from './page';

jest.mock('next/navigation', () => ({
useRouter: jest.fn(),
}));

describe('SignInPage', () => {
beforeAll(() => {
render(<SignInpage />);
});

it('Should store user data in sessionStorate and redirect to home on valid sign-in', () => {
const mockPush = jest.fn();
(useRouter as jest.Mock).mockReturnValue({ push: mockPush });

fireEvent.change(screen.getByPlaceholderText('Username'), { target: { value: 'test' } });
fireEvent.change(screen.getByPlaceholderText('Password'), { target: { value: 'test' } });
fireEvent.click(screen.getByRole('button'));

expect(sessionStorage.getItem('isLoggedIn')).toBe('true');

expect(mockPush).toHaveBeenCalledWith('/');
});

it('Should show alert on invalid sign-in', () => {
const mockAlert = jest.spyOn(window, 'alert').mockImplementation();

fireEvent.change(screen.getByPlaceholderText('Username'), { target: { value: '' } });
fireEvent.change(screen.getByPlaceholderText('Password'), { target: { value: '' } });
fireEvent.click(screen.getByRole('button'));

expect(mockAlert).toHaveBeenCalledWith('Please fill in all fields');
});
});
36 changes: 36 additions & 0 deletions src/app/sign/in/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
'use client';

import { FormEvent, useState } from 'react';

import { useRouter } from 'next/navigation';

import SignInPageUI from './page-ui';

const SignInpage = () => {
const router = useRouter();
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');

const onSubmit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();

if (!username || !password) {
return alert('Please fill in all fields');
}

sessionStorage.setItem('isLoggedIn', 'true');
router.push('/');
};

return (
<SignInPageUI
onSubmit={onSubmit}
username={username}
password={password}
setUsername={setUsername}
setPassword={setPassword}
/>
);
};

export default SignInpage;
Loading
Loading