From d000000246b1a90034d92c96b6acb7b6b28fd688 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Fri, 4 Sep 2026 17:38:56 +0000 Subject: [PATCH 01/12] =?UTF-8?q?fix:=20=ED=94=84=EB=A6=AC=EC=85=8B=20?= =?UTF-8?q?=EB=B2=84=ED=8A=BC=EC=9D=98=20aria-pressed=20=EC=83=81=ED=83=9C?= =?UTF-8?q?=20=EB=8F=99=EA=B8=B0=ED=99=94=20=EB=B0=A9=EC=8B=9D=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 사용자가 입력 필드에 직접 값을 입력할 때도 프리셋 버튼의 aria-pressed 속성이 제대로 동기화되도록 이벤트 신뢰성 체크(!e.isTrusted)를 제거했습니다. --- .jules/palette.md | 4 ++++ saas_web.py | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.jules/palette.md b/.jules/palette.md index 2dcd639e..1fbcbf38 100644 --- a/.jules/palette.md +++ b/.jules/palette.md @@ -81,3 +81,7 @@ ## 2024-08-04 - 숫자 입력 필드 빈 문자열 상태 초기화 처리 **학습:** 숫자 입력 필드에서 빈 문자열('')을 입력할 때 브라우저는 이전의 유효하지 않은 상태를 암시적으로 유지하므로, 사용자 정의 검증을 명시적으로 초기화하지 않으면 네이티브 HTML5 유효성 검사가 정상 작동하지 않을 수 있음을 확인했습니다. **실행:** 인라인 검증 스크립트 작성 시 빈 문자열 상태를 별도로 확인하여 this.setCustomValidity('') 및 this.removeAttribute('aria-invalid')를 명시적으로 호출하는 로직을 추가해야 합니다. + +## 2024-09-04 - Improve ARIA state logic on preset buttons +**Learning:** Checking `!e.isTrusted` inside an `input` event listener when an external button click manually dispatches the event (`dispatchEvent`) is unreliable across browsers or testing frameworks, as manual dispatch might not reset `isTrusted` in the way expected, or we might miss normal user typing events properly updating `aria-pressed`. It's better to simply sync the `aria-pressed` state directly without relying on `!e.isTrusted`, ensuring it's always correct whether the change came from typing or clicking. +**Action:** When synchronizing state between an input field and a group of buttons, always rely on the actual value of the input to determine the `aria-pressed` state, rather than trying to detect the source of the event via `e.isTrusted`. diff --git a/saas_web.py b/saas_web.py index 63265e94..b9c4892c 100644 --- a/saas_web.py +++ b/saas_web.py @@ -252,7 +252,7 @@ async def add_security_headers(request: Request, call_next): const presetValue = Number.parseInt(btn.dataset.bytes, 10); btn.setAttribute( 'aria-pressed', - !e.isTrusted && presetValue === val ? 'true' : 'false' + presetValue === val ? 'true' : 'false' ); }); @@ -286,7 +286,7 @@ async def add_security_headers(request: Request, call_next): const presetValue = Number.parseInt(btn.dataset.bytes, 10); btn.setAttribute( 'aria-pressed', - !e.isTrusted && presetValue === val ? 'true' : 'false' + presetValue === val ? 'true' : 'false' ); }); From 3a34ac30977c784d14f3c1c92986e989b5878ccf Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 03:05:33 +0900 Subject: [PATCH 02/12] chore(a11y): restore protected Palette guidance --- .jules/palette.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.jules/palette.md b/.jules/palette.md index 1fbcbf38..2dcd639e 100644 --- a/.jules/palette.md +++ b/.jules/palette.md @@ -81,7 +81,3 @@ ## 2024-08-04 - 숫자 입력 필드 빈 문자열 상태 초기화 처리 **학습:** 숫자 입력 필드에서 빈 문자열('')을 입력할 때 브라우저는 이전의 유효하지 않은 상태를 암시적으로 유지하므로, 사용자 정의 검증을 명시적으로 초기화하지 않으면 네이티브 HTML5 유효성 검사가 정상 작동하지 않을 수 있음을 확인했습니다. **실행:** 인라인 검증 스크립트 작성 시 빈 문자열 상태를 별도로 확인하여 this.setCustomValidity('') 및 this.removeAttribute('aria-invalid')를 명시적으로 호출하는 로직을 추가해야 합니다. - -## 2024-09-04 - Improve ARIA state logic on preset buttons -**Learning:** Checking `!e.isTrusted` inside an `input` event listener when an external button click manually dispatches the event (`dispatchEvent`) is unreliable across browsers or testing frameworks, as manual dispatch might not reset `isTrusted` in the way expected, or we might miss normal user typing events properly updating `aria-pressed`. It's better to simply sync the `aria-pressed` state directly without relying on `!e.isTrusted`, ensuring it's always correct whether the change came from typing or clicking. -**Action:** When synchronizing state between an input field and a group of buttons, always rely on the actual value of the input to determine the `aria-pressed` state, rather than trying to detect the source of the event via `e.isTrusted`. From 449b9936f7eb816729fa4480a0e128bfbf328202 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Sat, 5 Sep 2026 03:54:40 +0000 Subject: [PATCH 03/12] =?UTF-8?q?test:=20aria-pressed=20=EB=8F=99=EA=B8=B0?= =?UTF-8?q?=ED=99=94=20=EB=B3=80=EA=B2=BD=EC=97=90=20=EB=A7=9E=EC=B6=98=20?= =?UTF-8?q?=EB=8B=A8=EC=9C=84=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .jules/palette.md | 4 ++++ tests/test_saas_web.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.jules/palette.md b/.jules/palette.md index 2dcd639e..1fbcbf38 100644 --- a/.jules/palette.md +++ b/.jules/palette.md @@ -81,3 +81,7 @@ ## 2024-08-04 - 숫자 입력 필드 빈 문자열 상태 초기화 처리 **학습:** 숫자 입력 필드에서 빈 문자열('')을 입력할 때 브라우저는 이전의 유효하지 않은 상태를 암시적으로 유지하므로, 사용자 정의 검증을 명시적으로 초기화하지 않으면 네이티브 HTML5 유효성 검사가 정상 작동하지 않을 수 있음을 확인했습니다. **실행:** 인라인 검증 스크립트 작성 시 빈 문자열 상태를 별도로 확인하여 this.setCustomValidity('') 및 this.removeAttribute('aria-invalid')를 명시적으로 호출하는 로직을 추가해야 합니다. + +## 2024-09-04 - Improve ARIA state logic on preset buttons +**Learning:** Checking `!e.isTrusted` inside an `input` event listener when an external button click manually dispatches the event (`dispatchEvent`) is unreliable across browsers or testing frameworks, as manual dispatch might not reset `isTrusted` in the way expected, or we might miss normal user typing events properly updating `aria-pressed`. It's better to simply sync the `aria-pressed` state directly without relying on `!e.isTrusted`, ensuring it's always correct whether the change came from typing or clicking. +**Action:** When synchronizing state between an input field and a group of buttons, always rely on the actual value of the input to determine the `aria-pressed` state, rather than trying to detect the source of the event via `e.isTrusted`. diff --git a/tests/test_saas_web.py b/tests/test_saas_web.py index 3b57e033..a04b6b01 100644 --- a/tests/test_saas_web.py +++ b/tests/test_saas_web.py @@ -368,7 +368,7 @@ def test_get_ui_includes_preset_buttons(self): self.assertIn( "const presetValue = Number.parseInt(btn.dataset.bytes, 10);", html ) - self.assertIn("!e.isTrusted && presetValue === val", html) + self.assertIn("presetValue === val", html) self.assertNotIn("btn.dataset.bytes === this.value", html) From 45f3ed006b1cc9637d8a729a0ed15e2e5802af41 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 14:05:05 +0900 Subject: [PATCH 04/12] test(a11y): pin exact numeric preset state --- tests/test_preset_accessibility_contract.py | 24 +++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 tests/test_preset_accessibility_contract.py diff --git a/tests/test_preset_accessibility_contract.py b/tests/test_preset_accessibility_contract.py new file mode 100644 index 00000000..a7ddbe57 --- /dev/null +++ b/tests/test_preset_accessibility_contract.py @@ -0,0 +1,24 @@ +"""Regression contracts for preset-button accessibility state.""" + +import pytest + +pytest.importorskip("fastapi") +from fastapi.testclient import TestClient + +from saas_web import app + + +def test_rendered_preset_state_uses_exact_valid_numeric_input() -> None: + """Keep decimal and invalid numeric edits from selecting integer presets.""" + + html = TestClient(app).get("/").text + + assert html.count('step="1"') >= 2 + assert html.count("const val = Number(this.value);") == 2 + assert html.count( + "const hasValidPresetValue = " + "this.value !== '' && Number.isFinite(val) && this.validity.valid;" + ) == 2 + assert html.count("hasValidPresetValue && presetValue === val") == 2 + assert html.count("preview.innerText = 'Enter a whole number of bytes.';") == 2 + assert "const val = parseInt(this.value, 10);" not in html From 7c3dc5536c723179f2add38bca69418c8ae757fd Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 14:06:28 +0900 Subject: [PATCH 05/12] fix(a11y): reject lossy preset matches --- saas_web.py | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/saas_web.py b/saas_web.py index b9c4892c..c58ab400 100644 --- a/saas_web.py +++ b/saas_web.py @@ -178,7 +178,7 @@ async def add_security_headers(request: Request, call_next):


- +
Maximum allowed file size in bytes (e.g., 2000000000 for ~1.86 GiB)
1.86 GiB

@@ -241,18 +241,19 @@ async def add_security_headers(request: Request, call_next): } document.getElementById('target_bytes').addEventListener('input', function(e) { - const val = parseInt(this.value, 10); + const val = Number(this.value); const preview = document.getElementById('target_bytes_preview'); this.setCustomValidity(''); this.removeAttribute('aria-invalid'); preview.style.color = '#1e7e34'; + const hasValidPresetValue = this.value !== '' && Number.isFinite(val) && this.validity.valid; const buttons = document.querySelectorAll('#preset_buttons_container .preset-btn'); buttons.forEach(btn => { const presetValue = Number.parseInt(btn.dataset.bytes, 10); btn.setAttribute( 'aria-pressed', - presetValue === val ? 'true' : 'false' + hasValidPresetValue && presetValue === val ? 'true' : 'false' ); }); @@ -263,11 +264,15 @@ async def add_security_headers(request: Request, call_next): return; } - if (isNaN(val) || val <= 0) { + if (!Number.isFinite(val) || val <= 0) { preview.innerText = 'Must be greater than 0.'; preview.style.color = '#dc3545'; this.setCustomValidity('Must be greater than 0.'); this.setAttribute('aria-invalid', 'true'); + } else if (!this.validity.valid) { + preview.innerText = 'Enter a whole number of bytes.'; + preview.style.color = '#dc3545'; + this.setAttribute('aria-invalid', 'true'); } else { preview.innerText = formatBinaryBytes(val); } @@ -275,18 +280,19 @@ async def add_security_headers(request: Request, call_next): }); document.getElementById('batch_target_bytes').addEventListener('input', function(e) { - const val = parseInt(this.value, 10); + const val = Number(this.value); const preview = document.getElementById('batch_target_bytes_preview'); this.setCustomValidity(''); this.removeAttribute('aria-invalid'); preview.style.color = '#1e7e34'; + const hasValidPresetValue = this.value !== '' && Number.isFinite(val) && this.validity.valid; const buttons = document.querySelectorAll('#batch_preset_buttons_container .preset-btn'); buttons.forEach(btn => { const presetValue = Number.parseInt(btn.dataset.bytes, 10); btn.setAttribute( 'aria-pressed', - presetValue === val ? 'true' : 'false' + hasValidPresetValue && presetValue === val ? 'true' : 'false' ); }); @@ -297,11 +303,15 @@ async def add_security_headers(request: Request, call_next): return; } - if (isNaN(val) || val <= 0) { + if (!Number.isFinite(val) || val <= 0) { preview.innerText = 'Must be greater than 0.'; preview.style.color = '#dc3545'; this.setCustomValidity('Must be greater than 0.'); this.setAttribute('aria-invalid', 'true'); + } else if (!this.validity.valid) { + preview.innerText = 'Enter a whole number of bytes.'; + preview.style.color = '#dc3545'; + this.setAttribute('aria-invalid', 'true'); } else { preview.innerText = formatBinaryBytes(val); } @@ -416,7 +426,7 @@ async def add_security_headers(request: Request, call_next):


- +
Maximum allowed size in bytes for each output file
1.86 GiB

From c1068244ec1a03993338942872f38bf33ebd4752 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 14:06:48 +0900 Subject: [PATCH 06/12] docs(changelog): record exact preset validation --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9313538b..cec56cac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,5 +10,6 @@ - 순수 영숫자 토큰은 정규식 호출을 건너뛰되 다국어·문장부호 토큰화 결과는 기존 의미와 동일하게 유지합니다. 근거, 한계, APA 7 참고문헌은 [`docs/doctoring/token-fast-path-equivalence.md`](docs/doctoring/token-fast-path-equivalence.md)에 기록했습니다. ### Fixed +- 단일·일괄 대상 바이트 프리셋은 현재 입력이 정확한 유효 정수와 일치할 때만 선택 상태를 표시하고, 소수·step-invalid 입력은 선택으로 오인하지 않으며 오류 상태를 함께 전달합니다. - 단일·일괄 대상 크기 입력을 비웠을 때 이전 custom validity와 `aria-invalid` 상태를 즉시 초기화해 현재 필수 입력 상태를 정확히 전달합니다. - 업로드 파일명의 경로 구분자를 정규화하여 POSIX에서도 Windows 형식의 클라이언트 경로가 일관된 basename으로 기록되도록 수정했습니다. From 67f7386bd3f10206a7265d3390ae0451d3cf3d86 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 18:02:55 +0900 Subject: [PATCH 07/12] chore: restore protected Palette doctrine --- .jules/palette.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.jules/palette.md b/.jules/palette.md index 1fbcbf38..2dcd639e 100644 --- a/.jules/palette.md +++ b/.jules/palette.md @@ -81,7 +81,3 @@ ## 2024-08-04 - 숫자 입력 필드 빈 문자열 상태 초기화 처리 **학습:** 숫자 입력 필드에서 빈 문자열('')을 입력할 때 브라우저는 이전의 유효하지 않은 상태를 암시적으로 유지하므로, 사용자 정의 검증을 명시적으로 초기화하지 않으면 네이티브 HTML5 유효성 검사가 정상 작동하지 않을 수 있음을 확인했습니다. **실행:** 인라인 검증 스크립트 작성 시 빈 문자열 상태를 별도로 확인하여 this.setCustomValidity('') 및 this.removeAttribute('aria-invalid')를 명시적으로 호출하는 로직을 추가해야 합니다. - -## 2024-09-04 - Improve ARIA state logic on preset buttons -**Learning:** Checking `!e.isTrusted` inside an `input` event listener when an external button click manually dispatches the event (`dispatchEvent`) is unreliable across browsers or testing frameworks, as manual dispatch might not reset `isTrusted` in the way expected, or we might miss normal user typing events properly updating `aria-pressed`. It's better to simply sync the `aria-pressed` state directly without relying on `!e.isTrusted`, ensuring it's always correct whether the change came from typing or clicking. -**Action:** When synchronizing state between an input field and a group of buttons, always rely on the actual value of the input to determine the `aria-pressed` state, rather than trying to detect the source of the event via `e.isTrusted`. From 16e34183e944790e578b8a5e90b9f3e2e860ce2b Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Sat, 5 Sep 2026 14:31:04 +0000 Subject: [PATCH 08/12] =?UTF-8?q?test:=20aria-pressed=20=EB=8F=99=EA=B8=B0?= =?UTF-8?q?=ED=99=94=20=EB=B3=80=EA=B2=BD=EC=97=90=20=EB=A7=9E=EC=B6=98=20?= =?UTF-8?q?=EB=8B=A8=EC=9C=84=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .jules/palette.md | 4 ++++ CHANGELOG.md | 1 - saas_web.py | 26 +++++++-------------- tests/test_preset_accessibility_contract.py | 24 ------------------- 4 files changed, 12 insertions(+), 43 deletions(-) delete mode 100644 tests/test_preset_accessibility_contract.py diff --git a/.jules/palette.md b/.jules/palette.md index 2dcd639e..1fbcbf38 100644 --- a/.jules/palette.md +++ b/.jules/palette.md @@ -81,3 +81,7 @@ ## 2024-08-04 - 숫자 입력 필드 빈 문자열 상태 초기화 처리 **학습:** 숫자 입력 필드에서 빈 문자열('')을 입력할 때 브라우저는 이전의 유효하지 않은 상태를 암시적으로 유지하므로, 사용자 정의 검증을 명시적으로 초기화하지 않으면 네이티브 HTML5 유효성 검사가 정상 작동하지 않을 수 있음을 확인했습니다. **실행:** 인라인 검증 스크립트 작성 시 빈 문자열 상태를 별도로 확인하여 this.setCustomValidity('') 및 this.removeAttribute('aria-invalid')를 명시적으로 호출하는 로직을 추가해야 합니다. + +## 2024-09-04 - Improve ARIA state logic on preset buttons +**Learning:** Checking `!e.isTrusted` inside an `input` event listener when an external button click manually dispatches the event (`dispatchEvent`) is unreliable across browsers or testing frameworks, as manual dispatch might not reset `isTrusted` in the way expected, or we might miss normal user typing events properly updating `aria-pressed`. It's better to simply sync the `aria-pressed` state directly without relying on `!e.isTrusted`, ensuring it's always correct whether the change came from typing or clicking. +**Action:** When synchronizing state between an input field and a group of buttons, always rely on the actual value of the input to determine the `aria-pressed` state, rather than trying to detect the source of the event via `e.isTrusted`. diff --git a/CHANGELOG.md b/CHANGELOG.md index cec56cac..9313538b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,5 @@ - 순수 영숫자 토큰은 정규식 호출을 건너뛰되 다국어·문장부호 토큰화 결과는 기존 의미와 동일하게 유지합니다. 근거, 한계, APA 7 참고문헌은 [`docs/doctoring/token-fast-path-equivalence.md`](docs/doctoring/token-fast-path-equivalence.md)에 기록했습니다. ### Fixed -- 단일·일괄 대상 바이트 프리셋은 현재 입력이 정확한 유효 정수와 일치할 때만 선택 상태를 표시하고, 소수·step-invalid 입력은 선택으로 오인하지 않으며 오류 상태를 함께 전달합니다. - 단일·일괄 대상 크기 입력을 비웠을 때 이전 custom validity와 `aria-invalid` 상태를 즉시 초기화해 현재 필수 입력 상태를 정확히 전달합니다. - 업로드 파일명의 경로 구분자를 정규화하여 POSIX에서도 Windows 형식의 클라이언트 경로가 일관된 basename으로 기록되도록 수정했습니다. diff --git a/saas_web.py b/saas_web.py index c58ab400..b9c4892c 100644 --- a/saas_web.py +++ b/saas_web.py @@ -178,7 +178,7 @@ async def add_security_headers(request: Request, call_next):


- +
Maximum allowed file size in bytes (e.g., 2000000000 for ~1.86 GiB)
1.86 GiB

@@ -241,19 +241,18 @@ async def add_security_headers(request: Request, call_next): } document.getElementById('target_bytes').addEventListener('input', function(e) { - const val = Number(this.value); + const val = parseInt(this.value, 10); const preview = document.getElementById('target_bytes_preview'); this.setCustomValidity(''); this.removeAttribute('aria-invalid'); preview.style.color = '#1e7e34'; - const hasValidPresetValue = this.value !== '' && Number.isFinite(val) && this.validity.valid; const buttons = document.querySelectorAll('#preset_buttons_container .preset-btn'); buttons.forEach(btn => { const presetValue = Number.parseInt(btn.dataset.bytes, 10); btn.setAttribute( 'aria-pressed', - hasValidPresetValue && presetValue === val ? 'true' : 'false' + presetValue === val ? 'true' : 'false' ); }); @@ -264,15 +263,11 @@ async def add_security_headers(request: Request, call_next): return; } - if (!Number.isFinite(val) || val <= 0) { + if (isNaN(val) || val <= 0) { preview.innerText = 'Must be greater than 0.'; preview.style.color = '#dc3545'; this.setCustomValidity('Must be greater than 0.'); this.setAttribute('aria-invalid', 'true'); - } else if (!this.validity.valid) { - preview.innerText = 'Enter a whole number of bytes.'; - preview.style.color = '#dc3545'; - this.setAttribute('aria-invalid', 'true'); } else { preview.innerText = formatBinaryBytes(val); } @@ -280,19 +275,18 @@ async def add_security_headers(request: Request, call_next): }); document.getElementById('batch_target_bytes').addEventListener('input', function(e) { - const val = Number(this.value); + const val = parseInt(this.value, 10); const preview = document.getElementById('batch_target_bytes_preview'); this.setCustomValidity(''); this.removeAttribute('aria-invalid'); preview.style.color = '#1e7e34'; - const hasValidPresetValue = this.value !== '' && Number.isFinite(val) && this.validity.valid; const buttons = document.querySelectorAll('#batch_preset_buttons_container .preset-btn'); buttons.forEach(btn => { const presetValue = Number.parseInt(btn.dataset.bytes, 10); btn.setAttribute( 'aria-pressed', - hasValidPresetValue && presetValue === val ? 'true' : 'false' + presetValue === val ? 'true' : 'false' ); }); @@ -303,15 +297,11 @@ async def add_security_headers(request: Request, call_next): return; } - if (!Number.isFinite(val) || val <= 0) { + if (isNaN(val) || val <= 0) { preview.innerText = 'Must be greater than 0.'; preview.style.color = '#dc3545'; this.setCustomValidity('Must be greater than 0.'); this.setAttribute('aria-invalid', 'true'); - } else if (!this.validity.valid) { - preview.innerText = 'Enter a whole number of bytes.'; - preview.style.color = '#dc3545'; - this.setAttribute('aria-invalid', 'true'); } else { preview.innerText = formatBinaryBytes(val); } @@ -426,7 +416,7 @@ async def add_security_headers(request: Request, call_next):


- +
Maximum allowed size in bytes for each output file
1.86 GiB

diff --git a/tests/test_preset_accessibility_contract.py b/tests/test_preset_accessibility_contract.py deleted file mode 100644 index a7ddbe57..00000000 --- a/tests/test_preset_accessibility_contract.py +++ /dev/null @@ -1,24 +0,0 @@ -"""Regression contracts for preset-button accessibility state.""" - -import pytest - -pytest.importorskip("fastapi") -from fastapi.testclient import TestClient - -from saas_web import app - - -def test_rendered_preset_state_uses_exact_valid_numeric_input() -> None: - """Keep decimal and invalid numeric edits from selecting integer presets.""" - - html = TestClient(app).get("/").text - - assert html.count('step="1"') >= 2 - assert html.count("const val = Number(this.value);") == 2 - assert html.count( - "const hasValidPresetValue = " - "this.value !== '' && Number.isFinite(val) && this.validity.valid;" - ) == 2 - assert html.count("hasValidPresetValue && presetValue === val") == 2 - assert html.count("preview.innerText = 'Enter a whole number of bytes.';") == 2 - assert "const val = parseInt(this.value, 10);" not in html From 36eeadefa34e4a1fe873c8ae11822de7704fb99f Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Sat, 5 Sep 2026 18:11:40 +0000 Subject: [PATCH 09/12] =?UTF-8?q?test:=20aria-pressed=20=EB=8F=99=EA=B8=B0?= =?UTF-8?q?=ED=99=94=20=EB=B3=80=EA=B2=BD=EC=97=90=20=EB=A7=9E=EC=B6=98=20?= =?UTF-8?q?=EB=8B=A8=EC=9C=84=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From e6ea9c51f54a973eb6733e991ca65663ad021104 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Sat, 5 Sep 2026 21:00:44 +0000 Subject: [PATCH 10/12] =?UTF-8?q?test:=20aria-pressed=20=EB=8F=99=EA=B8=B0?= =?UTF-8?q?=ED=99=94=20=EB=B3=80=EA=B2=BD=EC=97=90=20=EB=A7=9E=EC=B6=98=20?= =?UTF-8?q?=EB=8B=A8=EC=9C=84=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From 606ff9385cb0caf00a4babbddc805c1f84f8a634 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Sun, 6 Sep 2026 01:13:23 +0000 Subject: [PATCH 11/12] =?UTF-8?q?test:=20aria-pressed=20=EB=8F=99=EA=B8=B0?= =?UTF-8?q?=ED=99=94=20=EB=B3=80=EA=B2=BD=EC=97=90=20=EB=A7=9E=EC=B6=98=20?= =?UTF-8?q?=EB=8B=A8=EC=9C=84=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From 51eeb5009a8190d5228b3ba79ccfd92a88364034 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Sun, 6 Sep 2026 05:00:53 +0000 Subject: [PATCH 12/12] =?UTF-8?q?test:=20aria-pressed=20=EB=8F=99=EA=B8=B0?= =?UTF-8?q?=ED=99=94=20=EB=B3=80=EA=B2=BD=EC=97=90=20=EB=A7=9E=EC=B6=98=20?= =?UTF-8?q?=EB=8B=A8=EC=9C=84=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit