Skip to content
15 changes: 14 additions & 1 deletion app.js
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,13 @@ function bindTableEvents(renderDraftValidation, updateEditorDraftFromEvent) {
}
event.preventDefault();
renderDraftValidation.flush();

const saveButton = form.querySelector('button[type="submit"]');
if (saveButton && saveButton.getAttribute('aria-disabled') === 'true') {
Comment thread
coderabbitai[bot] marked this conversation as resolved.
showToast(saveButton.title || '현재 사용할 수 없는 작업입니다.');
return;
Comment thread
seonghobae marked this conversation as resolved.
}

saveEditor();
});

Expand Down Expand Up @@ -790,6 +797,7 @@ function renderEditorRow(anchorId) {
panel.className = 'editor-panel';
const form = document.createElement('form');
form.dataset.editorForm = 'true';
form.noValidate = true;
const editorGrid = document.createElement('div');
editorGrid.className = 'editor-grid';

Expand Down Expand Up @@ -1068,7 +1076,12 @@ function renderEditorValidation() {

const saveButton = form.querySelector('button[type="submit"]');
if (saveButton) {
saveButton.disabled = errors.length > 0;
saveButton.disabled = false;
if (errors.length > 0) {
saveButton.setAttribute('aria-disabled', 'true');
Comment thread
seonghobae marked this conversation as resolved.
} else {
saveButton.removeAttribute('aria-disabled');
}
saveButton.title = errors.length > 0 ? '입력값을 올바르게 수정해야 저장할 수 있습니다.' : '저장 (Enter)';
}

Expand Down
36 changes: 36 additions & 0 deletions tests/e2e/editor-validation-feedback.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { test, expect } from '@playwright/test';

test.describe('editor validation feedback', () => {
test.beforeEach(async ({ page }) => {
await page.goto('./');
await page.getByRole('button', { name: '최상위 작업 추가' }).click();
});

test('routes an invalid required-field submit through product validation feedback', async ({ page }) => {
const form = page.locator('form[data-editor-form="true"]');
const toast = page.locator('#toast');

await expect(form).toBeVisible();
await form.evaluate((editorForm) => editorForm.requestSubmit());

await expect(page.locator('#editor-errors')).toContainText('최상위 작업은 단계 값을 입력해야 합니다.');
await expect(toast).toHaveClass(/show/);
await expect(toast).toContainText('입력값을 올바르게 수정해야 저장할 수 있습니다.');
await expect(form).toBeVisible();
});

test('flushes pending validation before an immediate valid submit', async ({ page }) => {
const phaseInput = page.locator('[data-testid="editor-phase"]');

await phaseInput.evaluate((input) => {
input.value = '즉시 제출 단계';
input.dispatchEvent(new Event('input', { bubbles: true }));
input.form.requestSubmit();
});

await expect(page.locator('form[data-editor-form="true"]')).toHaveCount(0);
await expect(
page.locator('tbody tr[data-task-id]').filter({ hasText: '즉시 제출 단계' }),
).toHaveCount(1);
});
});
Loading