unsloth/tests/studio/test_composer_rtl_bidi_attribute.py
Daniel Han 0542dc0725
Studio: IME / multilingual composer regression test + RTL dir="auto" (#5485)
Adds dir="auto" to the main, edit, and compare chat composers so RTL
scripts (Arabic, Hebrew, Persian, Urdu) flow right to left without
forcing the rest of the UI into RTL. Wires a model-free Playwright
smoke (multilingual paste round trip across 31 scripts + a stuck-IME
composition repro for issue #5318 / PR #5327) into the Studio UI CI
job as a third Studio boot, plus a pure-Python static-guard test that
locks down dir="auto" on all three composers and the minimal env
contract for the smoke.
2026-05-17 04:20:46 -07:00

73 lines
2.9 KiB
Python

"""Lock down the RTL bidi auto-detection contract on the chat composers.
The browser's Unicode bidi algorithm only flows Arabic / Hebrew / Persian /
Urdu right-to-left when the textarea carries `dir="auto"`. The three
composer surfaces (main chat, inline edit, compare mode) each need the
attribute, and the IME / i18n Playwright smoke must keep its env contract
minimal (no dead `STUDIO_OLD_PW`).
"""
from __future__ import annotations
import re
from pathlib import Path
REPO = Path(__file__).resolve().parents[2]
THREAD_TSX = REPO / "studio/frontend/src/components/assistant-ui/thread.tsx"
SHARED_TSX = REPO / "studio/frontend/src/features/chat/shared-composer.tsx"
WORKFLOW_YML = REPO / ".github/workflows/studio-ui-smoke.yml"
IME_PY = REPO / "tests/studio/playwright_chat_ime_i18n.py"
def _block_around(src: str, anchor: str, radius: int = 600) -> str:
idx = src.find(anchor)
assert idx != -1, f"anchor {anchor!r} not found"
return src[max(idx - radius, 0) : idx + radius]
def test_main_composer_has_dir_auto():
block = _block_around(THREAD_TSX.read_text(), 'aria-label="Message input"')
assert 'dir="auto"' in block, 'main composer is missing dir="auto"'
def test_edit_composer_has_dir_auto():
block = _block_around(THREAD_TSX.read_text(), "aui-edit-composer-input")
assert 'dir="auto"' in block, 'edit composer is missing dir="auto"'
def test_compare_composer_has_dir_auto():
block = _block_around(SHARED_TSX.read_text(), "Send to both models")
assert 'dir="auto"' in block, 'compare composer is missing dir="auto"'
def test_ime_workflow_step_does_not_set_studio_old_pw():
yml = WORKFLOW_YML.read_text()
drive_idx = yml.find("Drive IME + multilingual paste regression")
assert drive_idx != -1, "IME drive step not found in workflow"
next_step_idx = yml.find("- name:", drive_idx + 1)
drive_block = yml[drive_idx : next_step_idx if next_step_idx != -1 else None]
assert (
"STUDIO_OLD_PW" not in drive_block
), "IME drive step still passes dead STUDIO_OLD_PW env var"
assert "STUDIO_NEW_PW" in drive_block, "IME drive step missing STUDIO_NEW_PW"
def test_ime_pass_password_step_does_not_export_old_pw():
yml = WORKFLOW_YML.read_text()
pass_idx = yml.find("Pass bootstrap pw for IME / i18n test")
assert pass_idx != -1, "IME password setup step not found"
next_step_idx = yml.find("- name:", pass_idx + 1)
pass_block = yml[pass_idx : next_step_idx if next_step_idx != -1 else None]
assert (
"STUDIO_IME_OLD_PW" not in pass_block
), "IME password setup still exports dead STUDIO_IME_OLD_PW"
assert "STUDIO_IME_NEW_PW" in pass_block
def test_ime_playwright_script_does_not_read_studio_old_pw():
src = IME_PY.read_text()
code_only = re.sub(r'""".*?"""', "", src, flags = re.DOTALL)
assert (
"STUDIO_OLD_PW" not in code_only
), "IME Playwright script still references dead STUDIO_OLD_PW env var"
assert 'os.environ["STUDIO_NEW_PW"]' in code_only