feat(sync-help): ConflictModal inline 설명 + 자세히 보기 링크 (onOpenHelp prop)
This commit is contained in:
@@ -5,6 +5,7 @@ import { inboxApi } from '../api.js';
|
|||||||
interface Props {
|
interface Props {
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onResolved: () => void;
|
onResolved: () => void;
|
||||||
|
onOpenHelp?: (anchor: 'main-conflict' | 'auto' | 'silent' | 'setup') => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const overlayStyle: React.CSSProperties = {
|
const overlayStyle: React.CSSProperties = {
|
||||||
@@ -22,7 +23,7 @@ const rowStyle: React.CSSProperties = {
|
|||||||
border: '1px solid #eee', borderRadius: 6, padding: 10, marginTop: 8
|
border: '1px solid #eee', borderRadius: 6, padding: 10, marginTop: 8
|
||||||
};
|
};
|
||||||
|
|
||||||
export function ConflictModal({ onClose, onResolved }: Props): React.ReactElement {
|
export function ConflictModal({ onClose, onResolved, onOpenHelp }: Props): React.ReactElement {
|
||||||
const [conflicts, setConflicts] = useState<SyncConflict[]>([]);
|
const [conflicts, setConflicts] = useState<SyncConflict[]>([]);
|
||||||
const [busy, setBusy] = useState<string | null>(null);
|
const [busy, setBusy] = useState<string | null>(null);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
@@ -74,6 +75,18 @@ export function ConflictModal({ onClose, onResolved }: Props): React.ReactElemen
|
|||||||
<pre style={preStyle()}>{c.remoteText || '(미리보기 없음)'}</pre>
|
<pre style={preStyle()}>{c.remoteText || '(미리보기 없음)'}</pre>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div style={{ marginTop: 8, fontSize: 11, color: '#666', lineHeight: 1.5 }}>
|
||||||
|
<div><b>내 것 사용</b>: 이 기기의 변경을 보존하고 원격의 같은 노트 변경을 폐기.</div>
|
||||||
|
<div><b>원격 사용</b>: 원격의 변경을 가져오고 내 변경을 폐기.</div>
|
||||||
|
{onOpenHelp && (
|
||||||
|
<button
|
||||||
|
onClick={() => onOpenHelp('main-conflict')}
|
||||||
|
style={{ background: 'none', border: 'none', color: '#0a4b80', cursor: 'pointer', fontSize: 11, padding: 0, marginTop: 2, textDecoration: 'underline' }}
|
||||||
|
>
|
||||||
|
자세히 보기 →
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<div style={{ marginTop: 8, display: 'flex', gap: 8, justifyContent: 'flex-end' }}>
|
<div style={{ marginTop: 8, display: 'flex', gap: 8, justifyContent: 'flex-end' }}>
|
||||||
<button
|
<button
|
||||||
onClick={() => { void onChoose(c.path, 'local'); }}
|
onClick={() => { void onChoose(c.path, 'local'); }}
|
||||||
|
|||||||
@@ -58,4 +58,26 @@ describe('ConflictModal', () => {
|
|||||||
expect(onClose).toHaveBeenCalled();
|
expect(onClose).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('각 conflict row 에 local/remote inline 설명 표시', async () => {
|
||||||
|
render(<ConflictModal onClose={() => {}} onResolved={() => {}} onOpenHelp={() => {}} />);
|
||||||
|
await waitFor(() => screen.getByText(/local A/));
|
||||||
|
expect(screen.getAllByText(/이 기기의 변경을 보존/).length).toBeGreaterThanOrEqual(2);
|
||||||
|
expect(screen.getAllByText(/원격의 변경을 가져오고/).length).toBeGreaterThanOrEqual(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('"자세히 보기" 클릭 → onOpenHelp("main-conflict") 호출', async () => {
|
||||||
|
const onOpenHelp = vi.fn();
|
||||||
|
render(<ConflictModal onClose={() => {}} onResolved={() => {}} onOpenHelp={onOpenHelp} />);
|
||||||
|
await waitFor(() => screen.getByText(/local A/));
|
||||||
|
const links = screen.getAllByRole('button', { name: /자세히 보기/ });
|
||||||
|
fireEvent.click(links[0]!);
|
||||||
|
expect(onOpenHelp).toHaveBeenCalledWith('main-conflict');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('onOpenHelp 미제공 → "자세히 보기" 링크 미렌더', async () => {
|
||||||
|
render(<ConflictModal onClose={() => {}} onResolved={() => {}} />);
|
||||||
|
await waitFor(() => screen.getByText(/local A/));
|
||||||
|
expect(screen.queryByRole('button', { name: /자세히 보기/ })).toBeNull();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user