From cfedc2f1ee3bef1b83f751c3f3ab5c584362788a Mon Sep 17 00:00:00 2001 From: BHyeonKim Date: Tue, 5 Aug 2025 22:23:51 +0900 Subject: [PATCH 1/5] =?UTF-8?q?test:=20=EC=8B=A4=ED=8C=A8=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/CoinListItem/CoinListItem.test.tsx | 32 +++++++++++++++---- .../CoinListWithSearchBar.test.tsx | 18 +++++++---- 2 files changed, 38 insertions(+), 12 deletions(-) diff --git a/src/features/coin-search-list/ui/CoinListItem/CoinListItem.test.tsx b/src/features/coin-search-list/ui/CoinListItem/CoinListItem.test.tsx index c3aa6ff..bc2a4a5 100644 --- a/src/features/coin-search-list/ui/CoinListItem/CoinListItem.test.tsx +++ b/src/features/coin-search-list/ui/CoinListItem/CoinListItem.test.tsx @@ -1,4 +1,5 @@ import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { createRoutesStub } from 'react-router'; import { describe, expect, it, vi } from 'vitest'; import type { CurrentPriceData } from '~/entities/coin/hooks/useCurrentPrice'; @@ -8,8 +9,10 @@ import CoinListItem from '.'; const props: CoinListItemProps = { name: '๋น„ํŠธ์ฝ”์ธ', ticker: 'BTC', - coinIcon: ๐Ÿช™, to: '/coin/BTC', + currentPrice: 0, + changeRate: 0, + svgIconBase64: '', }; const Stub = createRoutesStub([ @@ -37,20 +40,37 @@ vi.mock('~/entities/coin', async () => { }; }); +const { navigate } = vi.hoisted(() => ({ + navigate: vi.fn(), +})); + +vi.mock('react-router', async () => { + const actual = + await vi.importActual('react-router'); + return { + ...actual, + useNavigate: () => navigate, + createRoutesStub: actual.createRoutesStub, + }; +}); + describe('CoinListItem ์ปดํฌ๋„ŒํŠธ ํ…Œ์ŠคํŠธ', () => { it('ํ™”๋ฉด์— CoinListItem์ด ๋ Œ๋”๋ง ๋œ๋‹ค.', () => { render(); - const coinListItem = screen.getByRole('link'); + const coinListItem = screen.getByRole('button'); expect(coinListItem).toBeInTheDocument(); }); - it('Link์˜ to ์†์„ฑ์œผ๋กœ prop์˜ to๊ฐ€ ์ „๋‹ฌ๋œ๋‹ค.', () => { + it('์‚ฌ์šฉ์ž๊ฐ€ CoinListItem์„ ํด๋ฆญํ•˜๋ฉด navigate๊ฐ€ ํ˜ธ์ถœ๋œ๋‹ค.', async () => { + const user = userEvent.setup(); render(); - const coinListItem = screen.getByRole('link'); - + const coinListItem = screen.getByRole('button'); expect(coinListItem).toBeInTheDocument(); - expect(coinListItem).toHaveAttribute('href', '/coin/BTC'); + + await user.click(coinListItem); + + expect(navigate).toHaveBeenCalledWith('/coin/BTC'); }); }); diff --git a/src/features/coin-search-list/ui/CoinListWithSearchBar/CoinListWithSearchBar.test.tsx b/src/features/coin-search-list/ui/CoinListWithSearchBar/CoinListWithSearchBar.test.tsx index 4db360c..071adc3 100644 --- a/src/features/coin-search-list/ui/CoinListWithSearchBar/CoinListWithSearchBar.test.tsx +++ b/src/features/coin-search-list/ui/CoinListWithSearchBar/CoinListWithSearchBar.test.tsx @@ -25,22 +25,28 @@ vi.mock('@stomp/stompjs', () => { const props: CoinListWithSearchBarProps = { coinList: [ { - coinIcon: ๐Ÿช™, name: '๋น„ํŠธ์ฝ”์ธ', ticker: 'BTC', to: '/coin/BTC', + currentPrice: 0, + changeRate: 0, + svgIconBase64: '', }, { - coinIcon: ๐Ÿช™, name: '์ด๋”๋ฆฌ์›€', ticker: 'ETH', to: '/coin/ETH', + currentPrice: 0, + changeRate: 0, + svgIconBase64: '', }, { - coinIcon: ๐Ÿช™, name: 'ํŠธ๋Ÿผํ”„', ticker: 'TRUMP', to: '/coin/TRUMP', + currentPrice: 0, + changeRate: 0, + svgIconBase64: '', }, ], }; @@ -65,7 +71,7 @@ describe('CoinListWithSearchBar ์ปดํฌ๋„ŒํŠธ ํ…Œ์ŠคํŠธ', () => { ); expect(coinListWithSearchBar).toBeInTheDocument(); - expect(screen.getAllByRole('link')).toHaveLength(3); + expect(screen.getAllByRole('button')).toHaveLength(3); }); it('์‚ฌ์šฉ์ž๊ฐ€ ๊ฒ€์ƒ‰์ฐฝ์— ํ‹ฐ์ปค๋ฅผ ์ž…๋ ฅํ•˜๋ฉด ํ•„ํ„ฐ๋ง๋œ ๋ฆฌ์ŠคํŠธ๊ฐ€ ๋ Œ๋”๋ง ๋œ๋‹ค.', async () => { @@ -81,7 +87,7 @@ describe('CoinListWithSearchBar ์ปดํฌ๋„ŒํŠธ ํ…Œ์ŠคํŠธ', () => { const input = screen.getByRole('textbox'); await user.type(input, 'BTC'); - expect(screen.getAllByRole('link')).toHaveLength(1); - expect(screen.getAllByRole('link')[0]).toHaveTextContent('๋น„ํŠธ์ฝ”์ธ'); + expect(screen.getAllByRole('button')).toHaveLength(1); + expect(screen.getAllByRole('button')[0]).toHaveTextContent('๋น„ํŠธ์ฝ”์ธ'); }); }); From 0ecd7937a8c5d0bbaaa9790887a2044313470f80 Mon Sep 17 00:00:00 2001 From: BHyeonKim Date: Wed, 6 Aug 2025 23:55:28 +0900 Subject: [PATCH 2/5] =?UTF-8?q?refactor:=20stompTestUtils=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/provider/testing/stompTestUtils.tsx | 23 +++++++++++++ src/entities/coin/hooks/hooks.test.tsx | 37 ++++++++------------- 2 files changed, 36 insertions(+), 24 deletions(-) create mode 100644 src/app/provider/testing/stompTestUtils.tsx diff --git a/src/app/provider/testing/stompTestUtils.tsx b/src/app/provider/testing/stompTestUtils.tsx new file mode 100644 index 0000000..f8fd9f5 --- /dev/null +++ b/src/app/provider/testing/stompTestUtils.tsx @@ -0,0 +1,23 @@ +import type { ReactNode } from 'react'; +import { vi } from 'vitest'; +import { StompContext } from '~/app/provider/StompProvider'; + +export const mockClient = { + publish: vi.fn(), + subscribe: vi.fn(() => ({ + unsubscribe: vi.fn(), + id: 'testId', + })), + // biome-ignore lint/suspicious/noExplicitAny: +} as any; + +export const mockStompContextValue = { + client: mockClient, + connected: true, +}; + +export const StompTestWrapper = ({ children }: { children: ReactNode }) => ( + + {children} + +); diff --git a/src/entities/coin/hooks/hooks.test.tsx b/src/entities/coin/hooks/hooks.test.tsx index aa72041..07650c6 100644 --- a/src/entities/coin/hooks/hooks.test.tsx +++ b/src/entities/coin/hooks/hooks.test.tsx @@ -3,6 +3,10 @@ import type { ReactNode } from 'react'; import { beforeEach, describe, expect } from 'vitest'; import { it, vi } from 'vitest'; import { StompContext } from '~/app/provider/StompProvider'; +import { + StompTestWrapper, + mockClient, +} from '~/app/provider/testing/stompTestUtils'; import useCurrentPrice, { type CurrentPriceData } from './useCurrentPrice'; const TICKER_FIRST = 'BTC'; @@ -16,25 +20,6 @@ function generateTopicEndPoint(ticker: string) { return `/topic/prevRate/${ticker}`; } -const mockClient = { - publish: vi.fn(), - subscribe: vi.fn(() => ({ - unsubscribe: vi.fn(), - id: 'testId', - })), -} as any; - -const mockStompContextValue = { - client: mockClient, - connected: true, -}; - -const wrapper = ({ children }: { children: ReactNode }) => ( - - {children} - -); - describe('useCurrentPrice ํ›… ํ…Œ์ŠคํŠธ', () => { beforeEach(() => { vi.clearAllMocks(); @@ -56,7 +41,9 @@ describe('useCurrentPrice ํ›… ํ…Œ์ŠคํŠธ', () => { }); it('ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์—ฐ๊ฒฐ๋˜๋ฉด ์˜ฌ๋ฐ”๋ฅธ destination์œผ๋กœ publishํ•œ๋‹ค', () => { - renderHook(() => useCurrentPrice(TICKER_FIRST), { wrapper }); + renderHook(() => useCurrentPrice(TICKER_FIRST), { + wrapper: StompTestWrapper, + }); expect(mockClient.publish).toHaveBeenCalledWith({ destination: generateDestinationEndPoint(TICKER_FIRST), @@ -65,7 +52,9 @@ describe('useCurrentPrice ํ›… ํ…Œ์ŠคํŠธ', () => { }); it('์˜ฌ๋ฐ”๋ฅธ topic์œผ๋กœ subscribeํ•œ๋‹ค', () => { - renderHook(() => useCurrentPrice(TICKER_FIRST), { wrapper }); + renderHook(() => useCurrentPrice(TICKER_FIRST), { + wrapper: StompTestWrapper, + }); expect(mockClient.subscribe).toHaveBeenCalledWith( generateTopicEndPoint(TICKER_FIRST), @@ -94,7 +83,7 @@ describe('useCurrentPrice ํ›… ํ…Œ์ŠคํŠธ', () => { ); const { result } = renderHook(() => useCurrentPrice(TICKER_FIRST), { - wrapper, + wrapper: StompTestWrapper, }); await waitFor(() => { @@ -104,7 +93,7 @@ describe('useCurrentPrice ํ›… ํ…Œ์ŠคํŠธ', () => { it('ticker๊ฐ€ ๋ณ€๊ฒฝ๋˜๋ฉด ์ƒˆ๋กœ์šด ๊ตฌ๋…์„ ์ƒ์„ฑํ•œ๋‹ค', () => { const { rerender } = renderHook(({ ticker }) => useCurrentPrice(ticker), { - wrapper, + wrapper: StompTestWrapper, initialProps: { ticker: TICKER_FIRST }, }); @@ -121,7 +110,7 @@ describe('useCurrentPrice ํ›… ํ…Œ์ŠคํŠธ', () => { mockClient.subscribe.mockReturnValue({ unsubscribe: mockUnsubscribe }); const { unmount } = renderHook(() => useCurrentPrice(TICKER_FIRST), { - wrapper, + wrapper: StompTestWrapper, }); unmount(); From 3e3db64068342189c9dc6f6d7eb38f283d74820a Mon Sep 17 00:00:00 2001 From: BHyeonKim Date: Thu, 7 Aug 2025 01:27:02 +0900 Subject: [PATCH 3/5] =?UTF-8?q?test:=20order-execution-list=20=ED=9B=85=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/entities/coin/hooks/hooks.test.tsx | 9 +- .../order-execution-list/hooks/hooks.test.tsx | 99 +++++++++++++++++++ 2 files changed, 105 insertions(+), 3 deletions(-) create mode 100644 src/features/order-execution-list/hooks/hooks.test.tsx diff --git a/src/entities/coin/hooks/hooks.test.tsx b/src/entities/coin/hooks/hooks.test.tsx index 07650c6..763c25a 100644 --- a/src/entities/coin/hooks/hooks.test.tsx +++ b/src/entities/coin/hooks/hooks.test.tsx @@ -1,7 +1,7 @@ import { renderHook, waitFor } from '@testing-library/react'; import type { ReactNode } from 'react'; -import { beforeEach, describe, expect } from 'vitest'; -import { it, vi } from 'vitest'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + import { StompContext } from '~/app/provider/StompProvider'; import { StompTestWrapper, @@ -107,7 +107,10 @@ describe('useCurrentPrice ํ›… ํ…Œ์ŠคํŠธ', () => { it('์ปดํฌ๋„ŒํŠธ๊ฐ€ ์–ธ๋งˆ์šดํŠธ๋˜๋ฉด ๊ตฌ๋…์„ ํ•ด์ œํ•œ๋‹ค', () => { const mockUnsubscribe = vi.fn(); - mockClient.subscribe.mockReturnValue({ unsubscribe: mockUnsubscribe }); + mockClient.subscribe.mockReturnValue({ + unsubscribe: mockUnsubscribe, + id: 'testId', + }); const { unmount } = renderHook(() => useCurrentPrice(TICKER_FIRST), { wrapper: StompTestWrapper, diff --git a/src/features/order-execution-list/hooks/hooks.test.tsx b/src/features/order-execution-list/hooks/hooks.test.tsx new file mode 100644 index 0000000..488bc6c --- /dev/null +++ b/src/features/order-execution-list/hooks/hooks.test.tsx @@ -0,0 +1,99 @@ +import { renderHook, waitFor } from '@testing-library/react'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import { StompContext } from '~/app/provider/StompProvider'; +import { + StompTestWrapper, + mockClient, +} from '~/app/provider/testing/stompTestUtils'; +import type { Execution } from '../types/execution.type'; +import useExecutionListData from './useExecutionListData'; + +const TICKER = 'BTC'; + +const MOCK_EXECUTION_ITEM: Execution = { + price: 1000, + size: 1, + timestamp: new Date().toISOString(), + changeRate: 3, + transactionId: '1', + ticker: 'BTC', +}; + +function generateDestinationEndPoint(ticker: string) { + return `/app/subscribe/realTimeTradeRate/${ticker}`; +} + +function generateTopicEndPoint(ticker: string) { + return `/topic/realTimeTradeRate/${ticker}`; +} + +describe('useExecutionListData ํ…Œ์ŠคํŠธ', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์—ฐ๊ฒฐ๋˜์ง€ ์•Š์•˜์„ ๋•Œ๋Š” ๋นˆ ๋ฐฐ์—ด์„ ๋ฆฌํ„ดํ•œ๋‹ค.', () => { + const disconnectedWrapper = ({ + children, + }: { children: React.ReactNode }) => ( + + {children} + + ); + + const { result } = renderHook(() => useExecutionListData(TICKER), { + wrapper: disconnectedWrapper, + }); + + expect(mockClient.publish).not.toHaveBeenCalled(); + expect(mockClient.subscribe).not.toHaveBeenCalled(); + + expect(result.current).toEqual([]); + }); + + it('ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์—ฐ๊ฒฐ๋˜๊ณ  ์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ ๋ฐ์ดํ„ฐ๊ฐ€ ์˜ค๋ฉด ์ฒด๊ฒฐ๋‚ด์—ญ์˜ ๋ฐฐ์—ด์„ ๋ฆฌํ„ดํ•œ๋‹ค.', async () => { + (mockClient.subscribe as any).mockImplementation( + (destination: string, callback: (message: any) => void) => { + setTimeout(() => { + callback({ body: JSON.stringify(MOCK_EXECUTION_ITEM) }); + }, 0); + return { unsubscribe: vi.fn() }; + }, + ); + + const { result } = renderHook(() => useExecutionListData(TICKER), { + wrapper: StompTestWrapper, + }); + + expect(mockClient.publish).toHaveBeenCalledWith({ + destination: generateDestinationEndPoint(TICKER), + body: JSON.stringify({ ticker: TICKER }), + }); + + expect(mockClient.subscribe).toHaveBeenCalledWith( + generateTopicEndPoint(TICKER), + expect.any(Function), + ); + + await waitFor(() => { + expect(result.current).toEqual([MOCK_EXECUTION_ITEM]); + }); + }); + + it('์–ธ๋งˆ์šดํŠธ๊ฐ€ ๋˜๋ฉด ๊ตฌ๋…์„ ํ•ด์ œํ•œ๋‹ค.', () => { + const mockUnsubscribe = vi.fn(); + mockClient.subscribe.mockReturnValue({ + unsubscribe: mockUnsubscribe, + id: 'testId', + }); + + const { unmount } = renderHook(() => useExecutionListData(TICKER), { + wrapper: StompTestWrapper, + }); + + unmount(); + + expect(mockUnsubscribe).toHaveBeenCalled(); + }); +}); From 663339ea18f423cc8fb24278f163b49ffd970200 Mon Sep 17 00:00:00 2001 From: BHyeonKim Date: Mon, 11 Aug 2025 03:11:25 +0900 Subject: [PATCH 4/5] =?UTF-8?q?feat:=20=EB=8D=B0=EB=AA=A8=EC=9A=A9=20?= =?UTF-8?q?=EC=89=98=20=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/backend.sh | 61 ++++++++++++++++++++++++++++++++++++++++++++++ scripts/demo.sh | 21 ++++++++++++++++ 2 files changed, 82 insertions(+) create mode 100755 scripts/backend.sh create mode 100755 scripts/demo.sh diff --git a/scripts/backend.sh b/scripts/backend.sh new file mode 100755 index 0000000..222db88 --- /dev/null +++ b/scripts/backend.sh @@ -0,0 +1,61 @@ +#!/bin/bash + +backend_dir_name="cleanengine-be" +backend_repo_url="https://github.com/CleanEngine/cleanengine-be.git" + + +DEMO_KAKAO_CLIENT_ID=2e063b83bf69bf8e54db000d056539b2 +DEMO_JWT_SECRET=my-super-secret-key-for-jwt-generation-in-investfuture-project +DEMO_MARIADB_ROOT_PASSWORD=1234 +DEMO_MARIADB_DATABASE=if +DEMO_MARIADB_USER=localuser +DEMO_MARIADB_PASSWORD=localpass +DEMO_SPRING_DATASOURCE_URL=jdbc:mariadb://mariadb:3306/if +DEMO_SPRING_DATASOURCE_USERNAME=localuser +DEMO_SPRING_DATASOURCE_PASSWORD=localpass + +function build_backend(){ + + if [ ! -f "local.properties" ]; then + echo "local.properties ํŒŒ์ผ์ด ์—†์Šต๋‹ˆ๋‹ค. ํ•ด๋‹น ํŒŒ์ผ์„ ์ƒ์„ฑํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค." + touch local.properties + echo "KAKAO_CLIENT_ID=${DEMO_KAKAO_CLIENT_ID}" >> local.properties + echo "JWT_SECRET=${DEMO_JWT_SECRET}" >> local.properties + echo "MARIADB_ROOT_PASSWORD=${DEMO_MARIADB_ROOT_PASSWORD}" >> local.properties + echo "MARIADB_DATABASE=${DEMO_MARIADB_DATABASE}" >> local.properties + echo "MARIADB_USER=${DEMO_MARIADB_USER}" >> local.properties + echo "MARIADB_PASSWORD=${DEMO_MARIADB_PASSWORD}" >> local.properties + echo "SPRING_DATASOURCE_URL=${DEMO_SPRING_DATASOURCE_URL}" >> local.properties + echo "SPRING_DATASOURCE_USERNAME=${DEMO_SPRING_DATASOURCE_USERNAME}" >> local.properties + echo "SPRING_DATASOURCE_PASSWORD=${DEMO_SPRING_DATASOURCE_PASSWORD}" >> local.properties + fi + + ./gradlew clean build +} + +function run_docker(){ + if ! docker info > /dev/null 2>&1; then + echo "Docker ์—”์ง„์ด ์‹คํ–‰๋˜์ง€ ์•Š๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. Docker Desktop์„ ์‹œ์ž‘ํ•ด์ฃผ์„ธ์š”." + exit 1 + fi + + docker compose -f docker/docker-compose.yml up -d +} + + +cd "${SCRIPT_DIR}/../.." + +if [ ! -d "${backend_dir_name}" ]; then + echo "๋ฐฑ์—”๋“œ ๋ ˆํฌ์ง€ํ† ๋ฆฌ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ํ•ด๋‹น ๋ ˆํฌ์ง€ํ† ๋ฆฌ๋ฅผ ํด๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค." + git clone "${backend_repo_url}" + cd "${backend_dir_name}" + git checkout main +else + echo "๋ฐฑ์—”๋“œ ๋ ˆํฌ์ง€ํ† ๋ฆฌ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์ตœ์‹  ๋ฒ„์ „์œผ๋กœ ์—…๋ฐ์ดํŠธํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค." + cd "${backend_dir_name}" + git pull origin main + git checkout main +fi + +build_backend +run_docker diff --git a/scripts/demo.sh b/scripts/demo.sh new file mode 100755 index 0000000..d33f317 --- /dev/null +++ b/scripts/demo.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +export SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +chmod +x "${SCRIPT_DIR}/backend.sh" + +"${SCRIPT_DIR}/backend.sh" + +if lsof -i :3000 > /dev/null 2>&1; then + echo "3000๋ฒˆ ํฌํŠธ๊ฐ€ ์‚ฌ์šฉ ์ค‘์ž…๋‹ˆ๋‹ค. ํ•ด๋‹น ํ”„๋กœ์„ธ์Šค๋ฅผ ์ข…๋ฃŒํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ? (y/n)" + read -r answer + if [ "$answer" = "y" ] || [ "$answer" = "Y" ]; then + echo "ํ”„๋กœ์„ธ์Šค๋ฅผ ์ข…๋ฃŒํ•ฉ๋‹ˆ๋‹ค." + kill -9 $(lsof -ti :3000) + else + echo "ํ”„๋กœ์„ธ์Šค ์ข…๋ฃŒ๋ฅผ ์ทจ์†Œํ–ˆ์Šต๋‹ˆ๋‹ค. ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์ข…๋ฃŒํ•ฉ๋‹ˆ๋‹ค." + exit 1 + fi +fi + +yarn start \ No newline at end of file From 9cb43076577a6a25e4c6561c2c044f024b2d9e90 Mon Sep 17 00:00:00 2001 From: BHyeonKim Date: Mon, 11 Aug 2025 03:24:29 +0900 Subject: [PATCH 5/5] =?UTF-8?q?docs:=20readme=EC=97=90=20=EB=A1=9C?= =?UTF-8?q?=EC=BB=AC=20=EC=8B=A4=ED=96=89=EB=B0=A9=EB=B2=95=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/README.md b/README.md index 6fd6cbc..645c255 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,27 @@ **๐ŸŒ ์„œ๋น„์Šค URL:** ~~https://investfuture.my~~ AWS ์ง€์› ์ค‘๋‹จ์œผ๋กœ ๋กœ์ปฌ์‹คํ–‰๋งŒ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. +## ๐Ÿ–ฅ๏ธ ๋กœ์ปฌ ์‹คํ–‰ ๋ฐฉ๋ฒ• + +### ๐Ÿ“‹ Prerequisite +- **Git** +- **Node.js** +- **Yarn** +- **Docker** + +์•„๋ž˜ ๋ช…๋ น์–ด๋ฅผ ํ„ฐ๋ฏธ๋„์— ๋ถ™์—ฌ๋„ฃ๊ธฐ ํ•˜์„ธ์š”. +```bash +git clone https://github.com/CleanEngine/cleanengine-fe.git +cd cleanengine-fe + +chmod +x scripts/demo.sh + +./scripts/demo.sh +``` + +์Šคํฌ๋ฆฝํŠธ๊ฐ€ ์‹คํ–‰๋œ ํ›„ ๋‹ค์Œ์ฃผ์†Œ์— ์ ‘์†ํ•˜์„ธ์š”. +- **ํ”„๋ก ํŠธ์—”๋“œ**: http://localhost:3000 + ## ๐Ÿ“š ๊ฐœ๋ฐœ ํ›„๊ธฐ ๋ฐ ๊ธฐ์ˆ  ๋ธ”๋กœ๊ทธ ํ”„๋กœ์ ํŠธ ๊ฐœ๋ฐœ ๊ณผ์ •์—์„œ ๊ฒช์€ ๊ธฐ์ˆ ์  ๋„์ „๊ณผ ํ•ด๊ฒฐ ๊ณผ์ •์„ ์ •๋ฆฌํ•œ ๋ฌธ์„œ๋“ค์ž…๋‹ˆ๋‹ค: