feat(listings): 실매물 tier/금액 순수 헬퍼 + 테스트
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UHXzpsZQxKG9hQmNRfZjRS
This commit is contained in:
40
src/pages/listings/listingsUtils.js
Normal file
40
src/pages/listings/listingsUtils.js
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
/* 실매물 판정 tier / 금액 포맷 순수 헬퍼 */
|
||||||
|
const C = { green: '#34d399', yellow: '#f59e0b', red: '#f87171', gray: '#94a3b8' };
|
||||||
|
|
||||||
|
export const SAFETY_TIER_META = Object.freeze({
|
||||||
|
'안전': { emoji: '🟢', color: C.green },
|
||||||
|
'주의': { emoji: '🟡', color: C.yellow },
|
||||||
|
'위험': { emoji: '🔴', color: C.red },
|
||||||
|
'보류': { emoji: '⚪', color: C.gray },
|
||||||
|
});
|
||||||
|
|
||||||
|
export const VALUATION_TIER_META = Object.freeze({
|
||||||
|
'저평가': { emoji: '🟢', color: C.green },
|
||||||
|
'시세': { emoji: '🟡', color: C.yellow },
|
||||||
|
'고가': { emoji: '🔴', color: C.red },
|
||||||
|
'보류': { emoji: '⚪', color: C.gray },
|
||||||
|
});
|
||||||
|
|
||||||
|
export const tierMeta = (kind, tier) => {
|
||||||
|
const map = kind === 'valuation' ? VALUATION_TIER_META : SAFETY_TIER_META;
|
||||||
|
if (Object.hasOwn(map, tier)) return { ...map[tier], label: tier };
|
||||||
|
return { emoji: '⚪', color: C.gray, label: tier ?? '' };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const formatMoney = (manwon) => {
|
||||||
|
if (manwon == null || manwon === '') return '-';
|
||||||
|
const n = Number(manwon);
|
||||||
|
if (Number.isNaN(n)) return '-';
|
||||||
|
const eok = Math.floor(n / 10000);
|
||||||
|
const rest = n % 10000;
|
||||||
|
if (eok > 0 && rest > 0) return `${eok}억 ${rest.toLocaleString('ko-KR')}만`;
|
||||||
|
if (eok > 0) return `${eok}억`;
|
||||||
|
return `${n.toLocaleString('ko-KR')}만`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const formatRatio = (ratio) => {
|
||||||
|
if (ratio == null) return '-';
|
||||||
|
const n = Number(ratio);
|
||||||
|
if (Number.isNaN(n)) return '-';
|
||||||
|
return `${(n * 100).toFixed(1)}%`;
|
||||||
|
};
|
||||||
44
src/pages/listings/listingsUtils.test.js
Normal file
44
src/pages/listings/listingsUtils.test.js
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { tierMeta, formatMoney, formatRatio } from './listingsUtils.js';
|
||||||
|
|
||||||
|
describe('tierMeta', () => {
|
||||||
|
it('safety 4종 이모지·색·label', () => {
|
||||||
|
expect(tierMeta('safety', '안전')).toMatchObject({ emoji: '🟢', color: '#34d399', label: '안전' });
|
||||||
|
expect(tierMeta('safety', '주의').emoji).toBe('🟡');
|
||||||
|
expect(tierMeta('safety', '위험').color).toBe('#f87171');
|
||||||
|
expect(tierMeta('safety', '보류').emoji).toBe('⚪');
|
||||||
|
});
|
||||||
|
it('valuation 4종', () => {
|
||||||
|
expect(tierMeta('valuation', '저평가')).toMatchObject({ emoji: '🟢', color: '#34d399', label: '저평가' });
|
||||||
|
expect(tierMeta('valuation', '시세').emoji).toBe('🟡');
|
||||||
|
expect(tierMeta('valuation', '고가').color).toBe('#f87171');
|
||||||
|
});
|
||||||
|
it('미정의 tier는 회색⚪ + 원문 label, undefined는 빈 label', () => {
|
||||||
|
expect(tierMeta('safety', '없는거')).toMatchObject({ emoji: '⚪', color: '#94a3b8', label: '없는거' });
|
||||||
|
expect(tierMeta('valuation', undefined).label).toBe('');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('formatMoney (만원 정밀 표기)', () => {
|
||||||
|
it('만/억 경계', () => {
|
||||||
|
expect(formatMoney(9999)).toBe('9,999만');
|
||||||
|
expect(formatMoney(10000)).toBe('1억');
|
||||||
|
expect(formatMoney(15000)).toBe('1억 5,000만');
|
||||||
|
expect(formatMoney(32000)).toBe('3억 2,000만');
|
||||||
|
});
|
||||||
|
it('null/빈값/NaN → -', () => {
|
||||||
|
expect(formatMoney(null)).toBe('-');
|
||||||
|
expect(formatMoney('')).toBe('-');
|
||||||
|
expect(formatMoney('abc')).toBe('-');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('formatRatio', () => {
|
||||||
|
it('비율 → 퍼센트 1자리', () => {
|
||||||
|
expect(formatRatio(0.812)).toBe('81.2%');
|
||||||
|
expect(formatRatio(1.05)).toBe('105.0%');
|
||||||
|
});
|
||||||
|
it('null → -', () => {
|
||||||
|
expect(formatRatio(null)).toBe('-');
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user