Skip to content
Merged

Dev #162

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
2 changes: 1 addition & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
| 2 | 📚 **단어장 정리 도구** | 글자순 정렬, 특정 단어 추출 등 단어장 관리 |
| 3 | 🗄️ **오픈 DB 단어** | 단어 다운로드 및 검색 |
| 4 | 😈 **빌런 단어장 게시** | 빌런 단어장 공유 및 열람 |
| 5 | ⌨️ **타자 연습** | 끄투 단어로 타자 연습 |
| 5 | ⌨️ **타자 연습** | *(예정)* 끄투 단어로 타자 연습 |
| 6 | 🔍 **끄코 정보 조회** | 프로필, 랭킹 등 유저 정보 조회 |
| 7 | ⚔️ **루트전 엔진** | *(예정)* 루트전 최적 플레이 보조 엔진 |

Expand Down
43 changes: 40 additions & 3 deletions scripts/release.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,13 @@ async function run() {

// Repo URL이 있으면 비교 링크 생성
if (config.repoUrl) {
// 릴리즈 모드일 때는 이전 태그와 현재 버전 비교 필요 (구현 생략 - 단순화)
// PR 모드일 때는 현재 버전(old) .. 다음 버전(new)
const prevVersionTag = isReleaseMode ? '...' : `v${currentVersion}`;
// PR 모드일 때는 현재 버전(old) .. 다음 버전(new)
// 릴리즈 모드일 때는 GitHub latest release 태그(old) .. 현재 버전(new)
let prevVersionTag = `v${currentVersion}`;
if (isReleaseMode) {
prevVersionTag = await getPreviousReleaseTag();
}

header = `# [${versionHeader}](${config.repoUrl}/compare/${prevVersionTag}...${versionHeader}) - ${date}`;
}

Expand Down Expand Up @@ -241,4 +245,37 @@ async function createGitHubRelease(version, body, dryRun) {
console.log(`🎉 GitHub Release published: ${release.html_url}`);
}

async function getPreviousReleaseTag() {
const token = process.env.GITHUB_TOKEN;

// GitHub API 우선 시도 (latest release)
if (token) {
try {
const octokit = getOctokit(token);
const { data: latestRelease } = await octokit.rest.repos.getLatestRelease({
owner: context.repo.owner,
repo: context.repo.repo
});

if (latestRelease?.tag_name) {
return latestRelease.tag_name;
}
} catch (error) {
console.warn(`⚠️ Failed to fetch latest release tag from GitHub API: ${error.message}`);
}
}

// 폴백: 로컬 최신 git 태그
try {
const tags = await git.tags();
if (tags.latest) {
return tags.latest;
}
} catch (error) {
console.warn(`⚠️ Failed to fetch latest local git tag: ${error.message}`);
}

throw new Error('Unable to resolve previous version tag from GitHub latest release or local git tags.');
}

run();
14 changes: 7 additions & 7 deletions src/__tests__/kkuko/shared/components/TryRenderImg.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ describe('TryRenderImg', () => {
render(<TryRenderImg {...defaultProps} />);
const img = screen.getByTestId('next-image');
expect(img).toBeInTheDocument();
expect(img).toHaveAttribute('src', defaultProps.url);
expect(img).toHaveAttribute('src', expect.stringContaining(defaultProps.url));
expect(img).toHaveAttribute('alt', defaultProps.alt);
});

Expand All @@ -61,13 +61,13 @@ describe('TryRenderImg', () => {

// Expect src to have changed
// url + ?r=1&ts=...
const expectedSrc1 = `${defaultProps.url}?r=1&ts=1234567890`;
expect(img).toHaveAttribute('src', expectedSrc1);
const expectedSrc1 = `r=1&ts=1234567890`;
expect(img).toHaveAttribute('src', expect.stringContaining(expectedSrc1));

// Second error -> retry 2
fireEvent.error(img);
const expectedSrc2 = `${defaultProps.url}?r=2&ts=1234567890`;
expect(img).toHaveAttribute('src', expectedSrc2);
const expectedSrc2 = `r=2&ts=1234567890`;
expect(img).toHaveAttribute('src', expect.stringContaining(expectedSrc2));
});

it('should show placeholder and call onFailure after maxRetries exceeded', () => {
Expand Down Expand Up @@ -112,14 +112,14 @@ describe('TryRenderImg', () => {

// Trigger one error to change state
fireEvent.error(img);
expect(img).toHaveAttribute('src', expect.stringContaining('?r=1'));
expect(img).toHaveAttribute('src', expect.stringContaining('r=1'));

// Change URL
const newUrl = 'https://example.com/new.png';
rerender(<TryRenderImg {...defaultProps} url={newUrl} />);

const newImg = screen.getByTestId('next-image');
expect(newImg).toHaveAttribute('src', newUrl);
expect(newImg).toHaveAttribute('src', expect.stringContaining(newUrl));
// Should not have query params yet
expect(newImg).not.toHaveAttribute('src', expect.stringContaining('?r='));
});
Expand Down
102 changes: 102 additions & 0 deletions src/__tests__/manager-tool/extract/ContainX.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import WordExtractorApp from "@/src/app/manager-tool/extract/containx/ContainX";
import { getOutsideHelpModal } from "@/test/utils/dom";

jest.mock("@/app/manager-tool/extract/components/FileContentDisplay", () => {
return ({ onFileUpload, fileContent, resultData, resultTitle }: any) => (
<div data-testid="file-content-display">
<div>File Content: {fileContent || "No content"}</div>
<div>Result Title: {resultTitle}</div>
<div>Result Count: {resultData?.length || 0}</div>
<button onClick={() => onFileUpload?.("test\ncontent\ndata\ntest")}>Mock File Upload</button>
<div data-testid="result-word">{fileContent}</div>
</div>
);
});

describe("ContainX", () => {
beforeEach(() => {
jest.clearAllMocks();
});

it("초기 렌더링이 정상적으로 되는지 확인", () => {
render(<WordExtractorApp />);

expect(screen.getByText("X가 포함된 단어 추출")).toBeInTheDocument();
expect(screen.getAllByText("설정")).toHaveLength(2);
expect(screen.getAllByText("실행")).toHaveLength(2);
});

it("포함글자 입력이 정상적으로 동작하는지 확인", async () => {
const user = userEvent.setup();
render(<WordExtractorApp />);

const wordIncludeInput = getOutsideHelpModal(() =>
screen.getAllByPlaceholderText("포함글자를 입력하세요"),
);
expect(wordIncludeInput).toBeDefined();

await user.type(wordIncludeInput!, "te");
expect(wordIncludeInput).toHaveValue("te");
});

it("파일 내용이 없을 때 단어 추출 버튼이 비활성화되는지 확인", () => {
render(<WordExtractorApp />);

const extractButton = getOutsideHelpModal(() =>
screen.getAllByText("단어 추출"),
);
expect(extractButton).toBeDisabled();
});

it("파일 업로드 후 포함 단어 추출이 정상적으로 동작하는지 확인", async () => {
const user = userEvent.setup();
render(<WordExtractorApp />);

const mockUploadButton = screen.getByText("Mock File Upload");
await user.click(mockUploadButton);

const wordIncludeInput = getOutsideHelpModal(() =>
screen.getAllByPlaceholderText("포함글자를 입력하세요"),
);
await user.type(wordIncludeInput, "te");

const extractButton = getOutsideHelpModal(() =>
screen.getAllByText("단어 추출"),
);
await user.click(extractButton);

await waitFor(() => {
const resultDisplay = screen.getByTestId("file-content-display");
expect(resultDisplay).toHaveTextContent("Result Count: 2");
});
});

it("단어 추출 결과에 따라 다운로드 버튼이 활성화되는지 확인", async () => {
const user = userEvent.setup();
render(<WordExtractorApp />);

const downloadButton = getOutsideHelpModal(() =>
screen.getAllByText("결과 다운로드"),
);
expect(downloadButton).toBeDisabled();

const mockUploadButton = screen.getByText("Mock File Upload");
await user.click(mockUploadButton);

const wordIncludeInput = getOutsideHelpModal(() =>
screen.getAllByPlaceholderText("포함글자를 입력하세요"),
);
await user.type(wordIncludeInput, "te");

const extractButton = getOutsideHelpModal(() =>
screen.getAllByText("단어 추출"),
);
await user.click(extractButton);

await waitFor(() => {
expect(downloadButton).not.toBeDisabled();
});
});
});
11 changes: 7 additions & 4 deletions src/app/admin/request-docs/RequestDocsHome.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Pagination, PaginationContent, PaginationItem, PaginationLink, Paginati
import { SCM } from '@/src/app/lib/supabaseClient';
import { PostgrestError } from '@supabase/supabase-js';
import ErrorModal from '@/src/app/components/ErrModal';
import Link from 'next/link';

type DocsWaitRequest = {id: number, req_at: string, docs_name: string, req_by: string | null, initial_consonant: boolean, req_byId: string | null}

Expand Down Expand Up @@ -122,10 +123,12 @@ export default function DocsWaitManager({initialData}: {initialData?: DocsWaitRe
<div className="min-h-screen bg-gradient-to-b from-gray-100 to-gray-200 dark:from-gray-900 dark:to-gray-800">
<div className="container mx-auto py-8">
<div className="mb-4 flex">
<Button variant="outline">
<ArrowLeft className="mr-2 h-4 w-4" />
관리자 대시보드로 이동
</Button>
<Link href={'/admin'} className="mb-4 flex">
<Button variant="outline">
<ArrowLeft className="mr-2 h-4 w-4" />
관리자 대시보드로 이동
</Button>
</Link>
</div>

<Card className="w-full bg-white dark:bg-gray-800 border border-transparent dark:border-gray-700 shadow-sm">
Expand Down
2 changes: 1 addition & 1 deletion src/app/admin/request-words/AdminWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export default function AdminHomeWrapper(){
return;
}

updateLoadingState(30, "단어 삭제/추가 요청 단아 가져오는 중...");
updateLoadingState(30, "단어 삭제/추가 요청 단어 가져오는 중...");
const {data: waitWordsData, error: waitWordsError} = await SCM.get().allWaitWords();
if (waitWordsError){
MakeError(waitWordsError);
Expand Down
24 changes: 17 additions & 7 deletions src/app/manager-tool/extract/ExtractHome.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
Type,
Hash,
ArrowRight,
Search,
RotateCcw,
Merge,
FileText,
Expand Down Expand Up @@ -37,15 +38,24 @@ const ExtractHome: React.FC = () => {
},
{
id: 3,
name: "X가 포함된 단어 추출",
description: "지정한 글자나 문자열이 포함된 단어를 추출합니다.",
link: "/containx",
icon: Search,
color: "from-cyan-500 to-sky-500",
bgColor: "group-hover:bg-cyan-50 dark:group-hover:bg-cyan-950/20"
},
{
id: 4,
name: "X로 끝나는 단어 추출",
description: "특정 접미사나 끝글자로 마무리되는 단어들을 추출합니다.",
description: "특정 접미사로 마무리되는 단어들을 추출합니다.",
link: "/endx",
icon: Type,
color: "from-purple-500 to-violet-500",
bgColor: "group-hover:bg-purple-50 dark:group-hover:bg-purple-950/20"
},
{
id: 4,
id: 5,
name: "돌림단어 추출",
description: "회문이나 대칭 구조를 가진 특별한 단어들을 추출합니다.",
link: "/loop",
Expand All @@ -54,7 +64,7 @@ const ExtractHome: React.FC = () => {
bgColor: "group-hover:bg-orange-50 dark:group-hover:bg-orange-950/20"
},
{
id: 5,
id: 6,
name: "파일 합성",
description: "여러 텍스트 파일을 하나로 병합시킵니다.",
link: "/merge",
Expand All @@ -63,7 +73,7 @@ const ExtractHome: React.FC = () => {
bgColor: "group-hover:bg-teal-50 dark:group-hover:bg-teal-950/20"
},
{
id: 6,
id: 7,
name: "한국어 미션단어 추출 - A",
description: "한국어 텍스트에서 미션 조건에 맞는 단어들을 추출합니다.",
link: "/korean-mission",
Expand All @@ -72,7 +82,7 @@ const ExtractHome: React.FC = () => {
bgColor: "group-hover:bg-pink-50 dark:group-hover:bg-pink-950/20"
},
{
id: 7,
id: 8,
name: "영어 미션단어 추출",
description: "영어 텍스트에서 미션 조건을 만족하는 단어들을 추출합니다.",
link: "/english-mission",
Expand All @@ -81,7 +91,7 @@ const ExtractHome: React.FC = () => {
bgColor: "group-hover:bg-indigo-50 dark:group-hover:bg-indigo-950/20"
},
{
id: 8,
id: 9,
name: "한국어 미션단어 추출 - B",
description: "1티어 미션단어만을 선별하여 단어를 추출합니다.",
link: "/korean-mission-b",
Expand All @@ -90,7 +100,7 @@ const ExtractHome: React.FC = () => {
bgColor: "group-hover:bg-yellow-50 dark:group-hover:bg-yellow-950/20"
},
{
id: 9,
id: 10,
name: "기능 제안하기",
description: "새로운 기능이 필요하시다면 언제든 요청해주세요!",
icon: HelpCircle,
Expand Down
Loading
Loading