refactor(portfolio): formatPriceTime util + PriceSessionBadge 추출 + 테스트
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:
@@ -5,26 +5,7 @@ import {
|
|||||||
Tooltip as ChartTooltip,
|
Tooltip as ChartTooltip,
|
||||||
} from 'recharts';
|
} from 'recharts';
|
||||||
import { formatNumber, formatPercent, toNumeric, profitColorClass, numFitClass } from '../stockUtils';
|
import { formatNumber, formatPercent, toNumeric, profitColorClass, numFitClass } from '../stockUtils';
|
||||||
|
import PriceSessionBadge from './portfolio/PriceSessionBadge';
|
||||||
const formatPriceTime = (iso) => {
|
|
||||||
if (!iso) return '';
|
|
||||||
const d = new Date(iso);
|
|
||||||
if (Number.isNaN(d.getTime())) return '';
|
|
||||||
return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const PriceSessionBadge = ({ session, asOf }) => {
|
|
||||||
if (session !== 'NXT_AFTER' && session !== 'NXT_PRE') return null;
|
|
||||||
const isPre = session === 'NXT_PRE';
|
|
||||||
const label = isPre ? 'NXT 프리' : 'NXT';
|
|
||||||
const desc = isPre ? 'NXT 프리마켓 거래가' : 'NXT 야간거래 (15:30~20:00)';
|
|
||||||
const time = formatPriceTime(asOf);
|
|
||||||
return (
|
|
||||||
<span className="pf-nxt-badge" title={time ? `${desc} · ${time}` : desc}>
|
|
||||||
{label}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const PortfolioTab = ({ pf, asset, handleSell, handleSaveSnapshot }) => (
|
const PortfolioTab = ({ pf, asset, handleSell, handleSaveSnapshot }) => (
|
||||||
<>
|
<>
|
||||||
|
|||||||
17
src/pages/stock/components/portfolio/PriceSessionBadge.jsx
Normal file
17
src/pages/stock/components/portfolio/PriceSessionBadge.jsx
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { formatPriceTime } from './portfolioUtils';
|
||||||
|
|
||||||
|
const PriceSessionBadge = ({ session, asOf }) => {
|
||||||
|
if (session !== 'NXT_AFTER' && session !== 'NXT_PRE') return null;
|
||||||
|
const isPre = session === 'NXT_PRE';
|
||||||
|
const label = isPre ? 'NXT 프리' : 'NXT';
|
||||||
|
const desc = isPre ? 'NXT 프리마켓 거래가' : 'NXT 야간거래 (15:30~20:00)';
|
||||||
|
const time = formatPriceTime(asOf);
|
||||||
|
return (
|
||||||
|
<span className="pf-nxt-badge" title={time ? `${desc} · ${time}` : desc}>
|
||||||
|
{label}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PriceSessionBadge;
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import PriceSessionBadge from './PriceSessionBadge.jsx';
|
||||||
|
|
||||||
|
describe('PriceSessionBadge', () => {
|
||||||
|
it('NXT_AFTER → "NXT", NXT_PRE → "NXT 프리"', () => {
|
||||||
|
const { unmount } = render(<PriceSessionBadge session="NXT_AFTER" />);
|
||||||
|
expect(screen.getByText('NXT')).toBeInTheDocument();
|
||||||
|
unmount();
|
||||||
|
render(<PriceSessionBadge session="NXT_PRE" />);
|
||||||
|
expect(screen.getByText('NXT 프리')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
it('그 외 세션 → 렌더 없음', () => {
|
||||||
|
const { container } = render(<PriceSessionBadge session="REGULAR" />);
|
||||||
|
expect(container).toBeEmptyDOMElement();
|
||||||
|
});
|
||||||
|
});
|
||||||
6
src/pages/stock/components/portfolio/portfolioUtils.js
Normal file
6
src/pages/stock/components/portfolio/portfolioUtils.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export const formatPriceTime = (iso) => {
|
||||||
|
if (!iso) return '';
|
||||||
|
const d = new Date(iso);
|
||||||
|
if (Number.isNaN(d.getTime())) return '';
|
||||||
|
return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`;
|
||||||
|
};
|
||||||
14
src/pages/stock/components/portfolio/portfolioUtils.test.js
Normal file
14
src/pages/stock/components/portfolio/portfolioUtils.test.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { formatPriceTime } from './portfolioUtils.js';
|
||||||
|
|
||||||
|
describe('formatPriceTime', () => {
|
||||||
|
it('유효 ISO → HH:MM (제로패드)', () => {
|
||||||
|
expect(formatPriceTime('2026-07-10T09:05:00')).toBe('09:05');
|
||||||
|
expect(formatPriceTime('2026-07-10T18:30:00')).toBe('18:30');
|
||||||
|
});
|
||||||
|
it('falsy/invalid → 빈 문자열', () => {
|
||||||
|
expect(formatPriceTime('')).toBe('');
|
||||||
|
expect(formatPriceTime(null)).toBe('');
|
||||||
|
expect(formatPriceTime('not-a-date')).toBe('');
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user