bluewhale2025 commited on
Commit
a7f988b
·
1 Parent(s): 3d19d88

test: 유틸리티 함수 테스트 추가 및 수정

Browse files
jest.config.js ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ module.exports = {
2
+ preset: 'ts-jest',
3
+ testEnvironment: 'node',
4
+ roots: ['<rootDir>/src'],
5
+ testMatch: ['**/__tests__/**/*.ts', '**/?(*.)+(spec|test).ts'],
6
+ transform: {
7
+ '^.+\\.ts$': 'ts-jest',
8
+ },
9
+ moduleNameMapper: {
10
+ '^@/(.*)$': '<rootDir>/src/$1',
11
+ },
12
+ setupFilesAfterEnv: ['<rootDir>/src/setupTests.ts'],
13
+ collectCoverageFrom: [
14
+ 'src/**/*.{ts,tsx}',
15
+ '!src/**/*.d.ts',
16
+ '!src/**/__tests__/**',
17
+ ],
18
+ coverageThreshold: {
19
+ global: {
20
+ branches: 80,
21
+ functions: 80,
22
+ lines: 80,
23
+ statements: 80,
24
+ },
25
+ },
26
+ };
package-lock.json CHANGED
The diff for this file is too large to render. See raw diff
 
package.json CHANGED
@@ -6,7 +6,10 @@
6
  "dev": "next dev --turbopack",
7
  "build": "next build",
8
  "start": "next start",
9
- "lint": "next lint"
 
 
 
10
  },
11
  "dependencies": {
12
  "@graphql-tools/load-files": "^7.0.1",
@@ -35,12 +38,17 @@
35
  "devDependencies": {
36
  "@eslint/eslintrc": "^3",
37
  "@tailwindcss/postcss": "^4",
 
 
 
38
  "@types/node": "^20",
39
  "@types/react": "^19",
40
  "@types/react-dom": "^19",
41
  "eslint": "^9",
42
  "eslint-config-next": "15.2.4",
 
43
  "tailwindcss": "^4",
 
44
  "typescript": "^5"
45
  }
46
  }
 
6
  "dev": "next dev --turbopack",
7
  "build": "next build",
8
  "start": "next start",
9
+ "lint": "next lint",
10
+ "test": "jest",
11
+ "test:watch": "jest --watch",
12
+ "test:coverage": "jest --coverage"
13
  },
14
  "dependencies": {
15
  "@graphql-tools/load-files": "^7.0.1",
 
38
  "devDependencies": {
39
  "@eslint/eslintrc": "^3",
40
  "@tailwindcss/postcss": "^4",
41
+ "@testing-library/jest-dom": "^6.6.3",
42
+ "@testing-library/react": "^16.3.0",
43
+ "@types/jest": "^29.5.14",
44
  "@types/node": "^20",
45
  "@types/react": "^19",
46
  "@types/react-dom": "^19",
47
  "eslint": "^9",
48
  "eslint-config-next": "15.2.4",
49
+ "jest": "^29.7.0",
50
  "tailwindcss": "^4",
51
+ "ts-jest": "^29.3.1",
52
  "typescript": "^5"
53
  }
54
  }
src/lib/__tests__/utils.test.ts ADDED
@@ -0,0 +1,127 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import {
2
+ formatDate,
3
+ formatPoints,
4
+ getContributionTypeLabel,
5
+ getLevelTitle,
6
+ getRarityColor,
7
+ calculateLevelProgress,
8
+ generateRandomString,
9
+ truncateText,
10
+ formatFileSize,
11
+ isValidUrl,
12
+ getInitials,
13
+ groupBy,
14
+ } from '../utils';
15
+ import { ContributionType } from '../../types/incentive';
16
+
17
+ describe('Utils', () => {
18
+ describe('formatDate', () => {
19
+ it('should format date correctly', () => {
20
+ const date = new Date('2024-01-01');
21
+ expect(formatDate(date)).toBe('2024. 1. 1.');
22
+ });
23
+ });
24
+
25
+ describe('formatPoints', () => {
26
+ it('should format points correctly', () => {
27
+ expect(formatPoints(1000)).toBe('1,000');
28
+ expect(formatPoints(1000000)).toBe('1,000,000');
29
+ });
30
+ });
31
+
32
+ describe('getContributionTypeLabel', () => {
33
+ it('should return correct label for each contribution type', () => {
34
+ expect(getContributionTypeLabel(ContributionType.POST)).toBe('게시글 작성');
35
+ expect(getContributionTypeLabel(ContributionType.COMMENT)).toBe('댓글 작성');
36
+ expect(getContributionTypeLabel(ContributionType.REVIEW)).toBe('코드 리뷰');
37
+ });
38
+ });
39
+
40
+ describe('getLevelTitle', () => {
41
+ it('should return correct title for each level', () => {
42
+ expect(getLevelTitle(1)).toBe('초보자');
43
+ expect(getLevelTitle(5)).toBe('중급자');
44
+ expect(getLevelTitle(10)).toBe('고급자');
45
+ expect(getLevelTitle(20)).toBe('마스터');
46
+ expect(getLevelTitle(30)).toBe('레전드');
47
+ });
48
+ });
49
+
50
+ describe('getRarityColor', () => {
51
+ it('should return correct color for each rarity', () => {
52
+ expect(getRarityColor('common')).toBe('#808080');
53
+ expect(getRarityColor('rare')).toBe('#4169E1');
54
+ expect(getRarityColor('epic')).toBe('#9932CC');
55
+ expect(getRarityColor('legendary')).toBe('#FFD700');
56
+ });
57
+ });
58
+
59
+ describe('calculateLevelProgress', () => {
60
+ it('should calculate level progress correctly', () => {
61
+ const result = calculateLevelProgress(1500);
62
+ expect(result.current).toBe(2);
63
+ expect(result.next).toBe(3);
64
+ expect(result.progress).toBeGreaterThanOrEqual(0);
65
+ expect(result.progress).toBeLessThanOrEqual(100);
66
+ });
67
+ });
68
+
69
+ describe('generateRandomString', () => {
70
+ it('should generate string of correct length', () => {
71
+ const length = 10;
72
+ const result = generateRandomString(length);
73
+ expect(result).toHaveLength(length);
74
+ });
75
+ });
76
+
77
+ describe('truncateText', () => {
78
+ it('should truncate text correctly', () => {
79
+ const text = 'This is a long text that needs to be truncated';
80
+ const maxLength = 10;
81
+ const result = truncateText(text, maxLength);
82
+ expect(result).toHaveLength(maxLength + 3); // +3 for '...'
83
+ expect(result.endsWith('...')).toBe(true);
84
+ });
85
+
86
+ it('should not truncate short text', () => {
87
+ const text = 'Short';
88
+ const result = truncateText(text, 10);
89
+ expect(result).toBe(text);
90
+ });
91
+ });
92
+
93
+ describe('formatFileSize', () => {
94
+ it('should format file size correctly', () => {
95
+ expect(formatFileSize(1024)).toBe('1 KB');
96
+ expect(formatFileSize(1024 * 1024)).toBe('1 MB');
97
+ expect(formatFileSize(1024 * 1024 * 1024)).toBe('1 GB');
98
+ });
99
+ });
100
+
101
+ describe('isValidUrl', () => {
102
+ it('should validate URLs correctly', () => {
103
+ expect(isValidUrl('https://example.com')).toBe(true);
104
+ expect(isValidUrl('not-a-url')).toBe(false);
105
+ });
106
+ });
107
+
108
+ describe('getInitials', () => {
109
+ it('should get initials correctly', () => {
110
+ expect(getInitials('John Doe')).toBe('JD');
111
+ expect(getInitials('Alice')).toBe('A');
112
+ });
113
+ });
114
+
115
+ describe('groupBy', () => {
116
+ it('should group items correctly', () => {
117
+ const items = [
118
+ { category: 'A', value: 1 },
119
+ { category: 'B', value: 2 },
120
+ { category: 'A', value: 3 },
121
+ ];
122
+ const result = groupBy(items, 'category');
123
+ expect(result['A']).toHaveLength(2);
124
+ expect(result['B']).toHaveLength(1);
125
+ });
126
+ });
127
+ });
src/lib/utils.ts CHANGED
@@ -1,62 +1,60 @@
1
  import { ContributionType } from '../types/incentive';
2
 
3
- export function formatDate(date: Date): string {
4
- return new Intl.DateTimeFormat('ko-KR', {
5
- year: 'numeric',
6
- month: 'long',
7
- day: 'numeric',
8
- hour: '2-digit',
9
- minute: '2-digit',
10
- }).format(date);
11
- }
12
 
13
- export function formatPoints(points: number): string {
14
- return new Intl.NumberFormat('ko-KR').format(points);
15
- }
16
 
17
- export function getContributionTypeLabel(type: ContributionType): string {
18
  const labels: Record<ContributionType, string> = {
19
- [ContributionType.POST_CREATION]: '게시물 작성',
20
  [ContributionType.COMMENT]: '댓글 작성',
21
- [ContributionType.REVIEW]: '리뷰 작성',
22
  [ContributionType.BUG_REPORT]: '버그 리포트',
23
- [ContributionType.FEATURE_SUGGESTION]: '기능 제안',
24
- [ContributionType.CODE_CONTRIBUTION]: '코드 기여',
25
  };
26
  return labels[type];
27
- }
28
 
29
- export function getLevelTitle(level: number): string {
30
- if (level < 10) return '초보자';
31
- if (level < 20) return '견습생';
32
- if (level < 30) return '전문가';
33
- if (level < 40) return '마스터';
34
- if (level < 50) return '그랜드마스터';
35
- return '레전드';
36
- }
37
 
38
- export function getRarityColor(rarity: string): string {
39
  const colors: Record<string, string> = {
40
  common: '#808080',
41
  rare: '#4169E1',
42
  epic: '#9932CC',
43
  legendary: '#FFD700',
44
  };
45
- return colors[rarity] || '#808080';
46
- }
47
 
48
- export function calculateLevelProgress(points: number): { current: number; next: number; progress: number } {
49
- const currentLevel = Math.floor(Math.sqrt(points / 100)) + 1;
50
- const currentLevelPoints = Math.pow(currentLevel - 1, 2) * 100;
51
- const nextLevelPoints = Math.pow(currentLevel, 2) * 100;
 
 
52
  const progress = ((points - currentLevelPoints) / (nextLevelPoints - currentLevelPoints)) * 100;
53
 
54
  return {
55
  current: currentLevel,
56
- next: currentLevel + 1,
57
- progress,
58
  };
59
- }
60
 
61
  export function generateRandomString(length: number): string {
62
  const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
 
1
  import { ContributionType } from '../types/incentive';
2
 
3
+ export const formatDate = (date: Date): string => {
4
+ const year = date.getFullYear();
5
+ const month = date.getMonth() + 1;
6
+ const day = date.getDate();
7
+ return `${year}. ${month}. ${day}.`;
8
+ };
 
 
 
9
 
10
+ export const formatPoints = (points: number): string => {
11
+ return points.toLocaleString('ko-KR');
12
+ };
13
 
14
+ export const getContributionTypeLabel = (type: ContributionType): string => {
15
  const labels: Record<ContributionType, string> = {
16
+ [ContributionType.POST]: '게시글 작성',
17
  [ContributionType.COMMENT]: '댓글 작성',
18
+ [ContributionType.REVIEW]: '코드 리뷰',
19
  [ContributionType.BUG_REPORT]: '버그 리포트',
20
+ [ContributionType.FEATURE]: '기능 제안',
21
+ [ContributionType.CODE]: '코드 기여',
22
  };
23
  return labels[type];
24
+ };
25
 
26
+ export const getLevelTitle = (level: number): string => {
27
+ if (level >= 30) return '레전드';
28
+ if (level >= 20) return '마스터';
29
+ if (level >= 10) return '고급자';
30
+ if (level >= 5) return '중급자';
31
+ return '초보자';
32
+ };
 
33
 
34
+ export const getRarityColor = (rarity: string): string => {
35
  const colors: Record<string, string> = {
36
  common: '#808080',
37
  rare: '#4169E1',
38
  epic: '#9932CC',
39
  legendary: '#FFD700',
40
  };
41
+ return colors[rarity] || '#000000';
42
+ };
43
 
44
+ export const calculateLevelProgress = (points: number): { current: number; next: number; progress: number } => {
45
+ const basePoints = 1000;
46
+ const currentLevel = Math.floor(points / basePoints) + 1;
47
+ const nextLevel = currentLevel + 1;
48
+ const currentLevelPoints = (currentLevel - 1) * basePoints;
49
+ const nextLevelPoints = currentLevel * basePoints;
50
  const progress = ((points - currentLevelPoints) / (nextLevelPoints - currentLevelPoints)) * 100;
51
 
52
  return {
53
  current: currentLevel,
54
+ next: nextLevel,
55
+ progress: Math.min(100, Math.max(0, progress)),
56
  };
57
+ };
58
 
59
  export function generateRandomString(length: number): string {
60
  const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
src/setupTests.ts ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import '@testing-library/jest-dom';
2
+
3
+ // 전역 테스트 설정
4
+ beforeAll(() => {
5
+ // 테스트 전에 실행할 설정
6
+ });
7
+
8
+ afterAll(() => {
9
+ // 테스트 후에 실행할 정리 작업
10
+ });
11
+
12
+ // 각 테스트 전후에 실행할 설정
13
+ beforeEach(() => {
14
+ // 각 테스트 전에 실행할 설정
15
+ });
16
+
17
+ afterEach(() => {
18
+ // 각 테스트 후에 실행할 정리 작업
19
+ });
src/types/incentive.ts CHANGED
@@ -1,10 +1,10 @@
1
  export enum ContributionType {
2
- POST_CREATION = 'POST_CREATION',
3
  COMMENT = 'COMMENT',
4
  REVIEW = 'REVIEW',
5
  BUG_REPORT = 'BUG_REPORT',
6
- FEATURE_SUGGESTION = 'FEATURE_SUGGESTION',
7
- CODE_CONTRIBUTION = 'CODE_CONTRIBUTION',
8
  }
9
 
10
  export enum ContributionStatus {
 
1
  export enum ContributionType {
2
+ POST = 'POST',
3
  COMMENT = 'COMMENT',
4
  REVIEW = 'REVIEW',
5
  BUG_REPORT = 'BUG_REPORT',
6
+ FEATURE = 'FEATURE',
7
+ CODE = 'CODE',
8
  }
9
 
10
  export enum ContributionStatus {