Skip to content
Open
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
37 changes: 37 additions & 0 deletions design-qa.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
**Findings**
- No actionable P0/P1/P2 mismatches remain.
Location: `ExportModal` share/export component.
Evidence: Figma source node `29:143` and the rendered implementation both use a centered 640px modal, two-column desktop body, compact bordered sections, primary blue actions, neutral/success/error status strip treatment, and footer actions aligned to the right.
Impact: The implementation preserves the intended share/export hierarchy and does not block handoff.
Fix: None required.

**Open Questions**
- The Figma source has three export artifact rows: SQL DDL, SVG image, and Mermaid. The implementation adds PlantUML because the current product already exposes PlantUML export. This is treated as an intentional product-capability extension.
- The implementation is localized in Korean while the Figma source is in English. The copy preserves the same intent and hierarchy.
- The close button shows a visible focus ring in the automated screenshots. This is expected accessibility behavior from the capture path and should not be removed.

**Implementation Checklist**
- Source visual truth path: `docs/ui-ux/qa/2026-07-02-figma-share-export-modal.png`.
- Implementation screenshot path: `docs/ui-ux/qa/2026-07-02-implementation-share-export-modal.png`.
- Success-state screenshot path: `docs/ui-ux/qa/2026-07-02-implementation-share-export-success-modal.png`.
- Mobile screenshot path: `docs/ui-ux/qa/2026-07-02-implementation-share-export-mobile.png`.
- Full-view comparison evidence: `docs/ui-ux/qa/2026-07-02-share-export-comparison.png`.
- Viewport: desktop `1440x900`; mobile `390x844 @ 2x`.
- State: default ready state and copied success state in demo mode.
- Focused region comparison evidence: modal-only screenshots were captured because the component-level source and implementation are readable without additional crops.
- Fonts and typography: system UI stack maps acceptably to the app; hierarchy, weight, wrapping, and line height remain readable in desktop and mobile captures.
- Spacing and layout rhythm: modal width, two-column desktop layout, section grouping, status strip, and footer alignment match the source intent. Export row density was compacted during QA.
- Colors and visual tokens: primary blue, slate text, light borders, neutral ready state, green success state, and disabled secondary action match the source intent and app palette.
- Image quality and asset fidelity: no external images or replacement assets are part of this modal; all visible UI is native controls and text.
- Copy and content: Korean implementation copy is coherent and maps to the same source states. PlantUML is the only added product row.
- Responsiveness: mobile capture has no horizontal overflow; the modal collapses to one column and remains scrollable.

**Patches Made Since Previous QA Pass**
- Reduced `exportModal` vertical gaps and padding.
- Reduced share/export section padding and minimum height.
- Tightened export artifact row gaps, padding, and button sizing.

**Follow-up Polish**
- Consider adding a dedicated Figma variant with PlantUML so the source design exactly reflects all current product export types.

final result: passed
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions docs/ui-ux/qa/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# UI/UX QA Evidence

This folder stores visual QA captures used to compare Figma source designs against rendered frontend implementation states.

## 2026-07-02 Share Export Modal

- `2026-07-02-figma-share-export-modal.png`: Figma source node `29:143`.
- `2026-07-02-implementation-share-export-modal.png`: rendered default state at `1440x900`.
- `2026-07-02-implementation-share-export-success-modal.png`: rendered copied-link success state at `1440x900`.
- `2026-07-02-implementation-share-export-mobile.png`: rendered mobile state at `390x844 @ 2x`.
- `2026-07-02-share-export-comparison.png`: combined comparison evidence.
25 changes: 15 additions & 10 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1425,36 +1425,38 @@ export default function App() {
</button>
<button
type="button"
onClick={onDownloadSvg}
onClick={onOpenExport}
disabled={nodes.length === 0}
title={
nodes.length === 0 ? "내보낼 테이블이 없습니다" : "SVG 내보내기"
nodes.length === 0
? "내보낼 테이블이 없습니다"
: "SVG/PlantUML/Mermaid 내보내기 모달 열기"
}
aria-label="SVG 그림 내보내기"
aria-label="이미지/텍스트 내보내기 모달 열기"
>
IMG
</button>
<button
type="button"
onClick={onDownloadUml}
onClick={onOpenExport}
disabled={nodes.length === 0}
title={
nodes.length === 0 ? "내보낼 테이블이 없습니다" : "UML 내보내기"
nodes.length === 0 ? "내보낼 테이블이 없습니다" : "SVG/PlantUML/Mermaid 내보내기 모달 열기"
}
aria-label="PlantUML 내보내기"
aria-label="이미지/텍스트 내보내기 모달 열기"
>
UML
</button>
<button
type="button"
onClick={onDownloadMermaid}
onClick={onOpenExport}
disabled={nodes.length === 0}
title={
nodes.length === 0
? "내보낼 테이블이 없습니다"
: "Mermaid 내보내기"
: "SVG/PlantUML/Mermaid 내보내기 모달 열기"
}
aria-label="Mermaid 내보내기"
aria-label="이미지/텍스트 내보내기 모달 열기"
>
{"{}"}
</button>
Expand Down Expand Up @@ -1518,16 +1520,19 @@ export default function App() {

<ExportModal
isOpen={isExportModalOpen}
exportDdlText={exportDdlText}
isCopied={isCopied}
hasDdlExport={nodes.length > 0}
hasDiagramExport={nodes.length > 0}
shareLinkUrl={shareLinkUrl}
isCreatingShareLink={isCreatingShareLink}
isShareLinkCopied={isShareLinkCopied}
shareLinkError={shareLinkError}
canCreateShareLink={Boolean(selectedProjectId)}
onCloseExport={onCloseExport}
onCopyExportDdl={onCopyExportDdl}
onDownloadSvg={onDownloadSvg}
onDownloadUml={onDownloadUml}
onDownloadMermaid={onDownloadMermaid}
onCreateShareLink={onCreateShareLink}
onCopyShareLink={onCopyShareLink}
/>
Expand Down
62 changes: 54 additions & 8 deletions frontend/src/components/modals/ExportModal.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,19 @@ import { ExportModal } from './ExportModal';

const baseProps = {
isOpen: true,
exportDdlText: 'create table users (id integer primary key);',
isCopied: false,
hasDdlExport: true,
hasDiagramExport: true,
shareLinkUrl: '',
isCreatingShareLink: false,
isShareLinkCopied: false,
shareLinkError: null,
canCreateShareLink: true,
onCloseExport: vi.fn(),
onCopyExportDdl: vi.fn(),
onDownloadSvg: vi.fn(),
onDownloadUml: vi.fn(),
onDownloadMermaid: vi.fn(),
onCreateShareLink: vi.fn(),
onCopyShareLink: vi.fn(),
};
Expand All @@ -25,7 +28,7 @@ afterEach(() => {
});

describe('ExportModal', () => {
it('creates a project share link and exposes the current DDL', () => {
it('separates project share links from export artifacts', () => {
const onCreateShareLink = vi.fn();
render(
<ExportModal
Expand All @@ -37,7 +40,12 @@ describe('ExportModal', () => {
fireEvent.click(screen.getByRole('button', { name: '링크 만들기' }));

expect(onCreateShareLink).toHaveBeenCalledOnce();
expect(screen.getByLabelText('DDL Export')).toHaveValue(baseProps.exportDdlText);
expect(screen.getByRole('heading', { name: '공유 링크' })).toBeInTheDocument();
expect(screen.getByRole('heading', { name: '내보내기 산출물' })).toBeInTheDocument();
expect(screen.getByText('SQL DDL')).toBeInTheDocument();
expect(screen.getByText('SVG 이미지')).toBeInTheDocument();
expect(screen.getByText('PlantUML')).toBeInTheDocument();
expect(screen.getByText('Mermaid')).toBeInTheDocument();
});

it('copies an already generated share link', () => {
Expand All @@ -58,6 +66,33 @@ describe('ExportModal', () => {
expect(onCopyShareLink).toHaveBeenCalledOnce();
});

it('runs each export artifact action from the modal', () => {
const onCopyExportDdl = vi.fn();
const onDownloadSvg = vi.fn();
const onDownloadUml = vi.fn();
const onDownloadMermaid = vi.fn();

render(
<ExportModal
{...baseProps}
onCopyExportDdl={onCopyExportDdl}
onDownloadSvg={onDownloadSvg}
onDownloadUml={onDownloadUml}
onDownloadMermaid={onDownloadMermaid}
/>,
);

fireEvent.click(screen.getByRole('button', { name: 'SQL DDL 복사' }));
fireEvent.click(screen.getByRole('button', { name: 'SVG 이미지 내보내기' }));
fireEvent.click(screen.getByRole('button', { name: 'PlantUML 내보내기' }));
fireEvent.click(screen.getByRole('button', { name: 'Mermaid 내보내기' }));

expect(onCopyExportDdl).toHaveBeenCalledOnce();
expect(onDownloadSvg).toHaveBeenCalledOnce();
expect(onDownloadUml).toHaveBeenCalledOnce();
expect(onDownloadMermaid).toHaveBeenCalledOnce();
});

it('shows share link copy or creation errors', () => {
render(
<ExportModal
Expand All @@ -69,17 +104,28 @@ describe('ExportModal', () => {
expect(screen.getByRole('alert')).toHaveTextContent('공유 링크 복사에 실패했습니다.');
});

it('explains when DDL cannot be generated yet', () => {
it('explains when exports cannot be generated yet', () => {
render(
<ExportModal
{...baseProps}
exportDdlText=""
hasDdlExport={false}
hasDiagramExport={false}
/>,
);

expect(screen.queryByLabelText('DDL Export')).not.toBeInTheDocument();
expect(screen.getByText('DDL을 만들려면 먼저 스냅샷을 생성하거나 테이블을 추가하세요.')).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'DDL 복사' })).toBeDisabled();
expect(screen.getAllByText('먼저 테이블을 추가하세요')).toHaveLength(4);
expect(screen.getByRole('button', { name: 'SQL DDL 복사' })).toBeDisabled();
expect(screen.getByRole('button', { name: 'SVG 이미지 내보내기' })).toBeDisabled();
expect(screen.getByRole('button', { name: 'PlantUML 내보내기' })).toBeDisabled();
expect(screen.getByRole('button', { name: 'Mermaid 내보내기' })).toBeDisabled();
});

it('exposes access-control guidance for disabled button', () => {
render(<ExportModal {...baseProps} canCreateShareLink={false} />);

expect(screen.getByText('접근 권한은 프로젝트 설정에서 별도로 관리합니다.')).toBeInTheDocument();
const accessManagementButton = screen.getByRole('button', { name: '접근 관리' });
expect(accessManagementButton).toBeDisabled();
expect(accessManagementButton).toHaveAttribute('aria-describedby', 'share-export-access-hint');
});
});
Loading