690503:0135 Update workflow #01
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
// T054: Vitest test for DSLEditor — validates onValidationChange callback and Save button disable logic
|
||||
// ตรวจสอบ: Validate กดแล้ว workflowApi.validateDSL ถูกเรียก; errors → onValidationChange(true); valid → onValidationChange(false)
|
||||
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { DSLEditor } from '../dsl-editor';
|
||||
import { workflowApi } from '@/lib/api/workflows';
|
||||
|
||||
// Mock Monaco editor — ไม่มี DOM environment สำหรับ Monaco
|
||||
vi.mock('@monaco-editor/react', () => ({
|
||||
default: ({ onChange }: { onChange?: (v: string) => void }) => (
|
||||
<textarea
|
||||
data-testid="monaco-editor"
|
||||
onChange={(e) => onChange?.(e.target.value)}
|
||||
/>
|
||||
),
|
||||
}));
|
||||
|
||||
// Mock next-themes
|
||||
vi.mock('next-themes', () => ({
|
||||
useTheme: () => ({ theme: 'light' }),
|
||||
}));
|
||||
|
||||
// Mock workflowApi.validateDSL
|
||||
vi.mock('@/lib/api/workflows', () => ({
|
||||
workflowApi: {
|
||||
validateDSL: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
const mockValidateDSL = vi.mocked(workflowApi.validateDSL);
|
||||
|
||||
describe('DSLEditor (T054)', () => {
|
||||
const onValidationChange = vi.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('calls workflowApi.validateDSL when Validate button is clicked', async () => {
|
||||
mockValidateDSL.mockResolvedValue({ valid: true });
|
||||
|
||||
render(<DSLEditor initialValue="workflow: test" onValidationChange={onValidationChange} />);
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: /validate/i }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockValidateDSL).toHaveBeenCalledWith('workflow: test');
|
||||
});
|
||||
});
|
||||
|
||||
it('calls onValidationChange(true) when validation returns errors', async () => {
|
||||
mockValidateDSL.mockResolvedValue({
|
||||
valid: false,
|
||||
errors: ['DSL must have at least one state'],
|
||||
});
|
||||
|
||||
render(<DSLEditor initialValue="bad: dsl" onValidationChange={onValidationChange} />);
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: /validate/i }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onValidationChange).toHaveBeenCalledWith(true);
|
||||
});
|
||||
|
||||
// แสดง error message ใน UI
|
||||
expect(
|
||||
screen.getByText('DSL must have at least one state')
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('calls onValidationChange(false) when validation returns valid', async () => {
|
||||
mockValidateDSL.mockResolvedValue({ valid: true });
|
||||
|
||||
render(<DSLEditor initialValue="workflow: rfa" onValidationChange={onValidationChange} />);
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: /validate/i }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onValidationChange).toHaveBeenCalledWith(false);
|
||||
});
|
||||
|
||||
// แสดง success message
|
||||
expect(screen.getByText(/valid and ready/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('calls onValidationChange(true) on server error', async () => {
|
||||
mockValidateDSL.mockRejectedValue(new Error('Network error'));
|
||||
|
||||
render(<DSLEditor initialValue="workflow: test" onValidationChange={onValidationChange} />);
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: /validate/i }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onValidationChange).toHaveBeenCalledWith(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('does not call onValidationChange when prop is not provided', async () => {
|
||||
mockValidateDSL.mockResolvedValue({ valid: true });
|
||||
|
||||
// ไม่ส่ง onValidationChange — ต้องไม่ throw
|
||||
render(<DSLEditor initialValue="workflow: test" />);
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: /validate/i }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockValidateDSL).toHaveBeenCalled();
|
||||
});
|
||||
// ไม่ throw error
|
||||
});
|
||||
});
|
||||
@@ -14,9 +14,11 @@ interface DSLEditorProps {
|
||||
initialValue?: string;
|
||||
onChange?: (value: string) => void;
|
||||
readOnly?: boolean;
|
||||
// FR-025: callback เมื่อผล validate เปลี่ยน — parent ใช้ disable Save button
|
||||
onValidationChange?: (hasErrors: boolean) => void;
|
||||
}
|
||||
|
||||
export function DSLEditor({ initialValue = '', onChange, readOnly = false }: DSLEditorProps) {
|
||||
export function DSLEditor({ initialValue = '', onChange, readOnly = false, onValidationChange }: DSLEditorProps) {
|
||||
const [dsl, setDsl] = useState(initialValue);
|
||||
const [validationResult, setValidationResult] = useState<ValidationResult | null>(null);
|
||||
const [isValidating, setIsValidating] = useState(false);
|
||||
@@ -47,9 +49,12 @@ export function DSLEditor({ initialValue = '', onChange, readOnly = false }: DSL
|
||||
try {
|
||||
const result = await workflowApi.validateDSL(dsl);
|
||||
setValidationResult(result);
|
||||
// FR-025: แจ้ง parent ว่ามี validation errors หรือไม่
|
||||
onValidationChange?.(!result.valid);
|
||||
} catch (_error) {
|
||||
// Validation failed - error state shown in UI
|
||||
setValidationResult({ valid: false, errors: ['Validation failed due to server error'] });
|
||||
onValidationChange?.(true);
|
||||
} finally {
|
||||
setIsValidating(false);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user