Skip to content
Closed
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
4 changes: 4 additions & 0 deletions .jules/palette.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 2024-07-14 - 동적 파일 크기 μ œν•œ 검증 및 일괄 폼 μœ νš¨μ„± 검사
**Learning:** μ •μ μœΌλ‘œ μ½”λ”©λœ μ—…λ‘œλ“œ μ œν•œ 크기(예: 5 GiB)λŠ” λ°±μ—”λ“œ μ œν•œμ΄ 변경될 λ•Œ ν΄λΌμ΄μ–ΈνŠΈ μΈ‘ 검증과 λΆˆμΌμΉ˜ν•˜κ²Œ 되며, 일괄 μ—…λ‘œλ“œ νΌμ—μ„œ 전체 파일 크기 검증이 μ—†μœΌλ©΄ μ‚¬μš©μžκ°€ μ—…λ‘œλ“œ μ‹€νŒ¨λ₯Ό μ„œλ²„ μΈ‘μ—μ„œλ§Œ ν™•μΈν•˜κ²Œ λ˜μ–΄ λ‚˜μœ UXλ₯Ό μ΄ˆλž˜ν•©λ‹ˆλ‹€.
**Action:** ν΄λΌμ΄μ–ΈνŠΈ μΈ‘ 파일 크기 κ²€μ¦μ—μ„œ λ°±μ—”λ“œμ˜ μ΅œλŒ€ μ œν•œ(`MAX_UPLOAD_BYTES`) μƒμˆ˜λ₯Ό μ‚¬μš©ν•΄ λ™μ μœΌλ‘œ ν¬λ§·νŒ…λœ 인간 μΉœν™”μ μΈ λ‹¨μœ„(예: `formatBinaryBytes(MAX_UPLOAD_BYTES)`)λ₯Ό μ‚¬μš©ν•˜κ³ , 닀쀑 파일 μ—…λ‘œλ“œ 폼에도 각 파일 크기의 합을 κ²€μ¦ν•˜μ—¬ 제좜 μ „ 즉각적인 ν”Όλ“œλ°±μ„ μ œκ³΅ν•˜λ„λ‘ ν•©λ‹ˆλ‹€.

## 2024-07-12 - Intercepting batch form submissions for testing visual loading states
**Learning:** Extending the learning from 2024-06-13, intercepting form submissions using `e.preventDefault()` via `page.evaluate()` is essential for capturing screenshot and video evidence of loading states (e.g., button disabling, spinner appearing) on forms like batch upload where the submission would normally reload the page or download an archive.
**Action:** When testing visual loading states with Playwright, always inject an event listener using `page.evaluate()` to call `e.preventDefault()` on the form's `submit` event to freeze the UI in its loading state for verification.
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,7 @@

## [Unreleased]
### Added
- 단일 및 닀쀑 파일 μ—…λ‘œλ“œ νΌμ—μ„œ 파일 크기 초과 였λ₯˜ μ‹œ λ°±μ—”λ“œ μ œν•œ 값에 맞좰 동적인 μ‚¬μ΄μ¦ˆ ν•œλ„λ₯Ό ν‘œκΈ°ν•˜λ„λ‘ κ°œμ„ 
- 닀쀑 파일 μ—…λ‘œλ“œ(일괄 μ—…λ‘œλ“œ) μ‹œ 총 ν•©μ‚° 크기가 μ œν•œ(MAX_UPLOAD_BYTES)을 μ΄ˆκ³Όν•˜λŠ”μ§€ ν΄λΌμ΄μ–ΈνŠΈμ—μ„œ λ¨Όμ € κ²€μ¦ν•˜μ—¬ μ¦‰μ‹œ ν”Όλ“œλ°±μ„ 주도둝 κ°œμ„ 
- 닀쀑 파일 μ—…λ‘œλ“œ 선택 μ‹œ 즉각적인 파일 개수 ν”Όλ“œλ°± 및 μ œν•œ 초과 κ²½κ³  λ©”μ‹œμ§€ μΆ”κ°€
- 일괄 μ—…λ‘œλ“œ 폼에 λŒ€μƒ λ°”μ΄νŠΈ 프리셋 λ²„νŠΌκ³Ό 총 파일 크기 미리보기λ₯Ό μΆ”κ°€ν•˜μ—¬ μ‚¬μš©μ„±μ„ κ°œμ„ ν–ˆμŠ΅λ‹ˆλ‹€.
23 changes: 21 additions & 2 deletions saas_web.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,9 +230,19 @@ async def add_security_headers(request: Request, call_next):
}
const text = formatBinaryBytes(file.size);
if (file.size > MAX_UPLOAD_BYTES) {
input.setCustomValidity('File exceeds 5 GiB limit.');
const limitStr = formatBinaryBytes(MAX_UPLOAD_BYTES);
input.setCustomValidity('File exceeds ' + limitStr + ' limit.');
input.setAttribute('aria-invalid', 'true');
preview.innerText = 'Selected file size: ' + text + ' (exceeds 5 GiB limit)';
preview.innerText = 'Selected file size: ' + text + ' (exceeds ' + limitStr + ' limit)';
preview.style.color = '#dc3545';
return;
}

if (totalSize > MAX_UPLOAD_BYTES) {
const limitStr = formatBinaryBytes(MAX_UPLOAD_BYTES);
input.setCustomValidity('Total combined file size exceeds ' + limitStr + ' limit.');
input.setAttribute('aria-invalid', 'true');
preview.innerText = 'Selected ' + files.length + ' files (' + formatBinaryBytes(totalSize) + ', exceeds ' + limitStr + ' limit)';
preview.style.color = '#dc3545';
return;
}
Expand Down Expand Up @@ -325,6 +335,15 @@ async def add_security_headers(request: Request, call_next):
preview.style.color = '#dc3545';
return;
}

if (totalSize > MAX_UPLOAD_BYTES) {
const limitStr = formatBinaryBytes(MAX_UPLOAD_BYTES);
input.setCustomValidity('Total combined file size exceeds ' + limitStr + ' limit.');
input.setAttribute('aria-invalid', 'true');
preview.innerText = 'Selected ' + files.length + ' files (' + formatBinaryBytes(totalSize) + ', exceeds ' + limitStr + ' limit)';
preview.style.color = '#dc3545';
return;
}
preview.innerText = 'Selected ' + files.length + ' file(s) (' + formatBinaryBytes(totalSize) + ')';
}

Expand Down
4 changes: 3 additions & 1 deletion tests/test_saas_web.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ def test_get_ui_includes_binary_file_size_validation(self):

self.assertIn("const MAX_UPLOAD_BYTES = 5 * 1024 * 1024 * 1024;", html)
self.assertIn("['B', 'KiB', 'MiB', 'GiB']", html)
self.assertIn("File exceeds 5 GiB limit.", html)
self.assertIn("const limitStr = formatBinaryBytes(MAX_UPLOAD_BYTES);", html)
self.assertIn("input.setCustomValidity('File exceeds ' + limitStr + ' limit.');", html)
self.assertIn("preview.style.color = '#0f6674';", html)
self.assertIn('onchange="updateFileSizePreview(this)"', html)

Expand Down Expand Up @@ -594,6 +595,7 @@ def test_get_ui_includes_batch_upload_form(self):
self.assertIn('onchange="updateBatchFilePreview(this)"', html)
self.assertIn('id="batch_files_preview"', html)
self.assertIn('function updateBatchFilePreview(input)', html)
self.assertIn("input.setCustomValidity('Total combined file size exceeds ' + limitStr + ' limit.');", html)


@unittest.skipUnless(_HAS_FASTAPI, "fastapi not installed (optional integration dependency)")
Expand Down
Loading