fix ui view
This commit is contained in:
1 parent
863ca89485
commit
83f4d7c728
9 files changed
+182
-17
No files matched your search
@@ -0,0 +1,103 @@
|
||||
import pytest
|
||||
from django.urls import reverse
|
||||
from playwright.sync_api import expect
|
||||
|
||||
from apps.accounts.models import TicketMagicLink, User
|
||||
from apps.core.models import SiteBranding
|
||||
from apps.customers.models import CustomerProfile
|
||||
from apps.messaging.models import Message
|
||||
from apps.staff.models import StaffProfile
|
||||
from apps.tickets.models import TicketCategory
|
||||
from apps.tickets.services import create_ticket
|
||||
from tests.e2e.pages.auth import AuthenticationPage
|
||||
from tests.e2e.pages.tickets import TicketConversationPage
|
||||
|
||||
|
||||
pytestmark = [pytest.mark.e2e, pytest.mark.django_db(transaction=True)]
|
||||
PASSWORD = "E2e-safe-password-2026"
|
||||
|
||||
|
||||
def assert_workspace_fits_visual_viewport(page):
|
||||
metrics = page.evaluate(
|
||||
"""() => {
|
||||
const box = document.querySelector('.ticket-workspace').getBoundingClientRect();
|
||||
const viewportHeight = window.visualViewport?.height || window.innerHeight;
|
||||
return {
|
||||
top: box.top,
|
||||
bottom: box.bottom,
|
||||
viewportHeight,
|
||||
pageWidth: document.documentElement.scrollWidth,
|
||||
viewportWidth: document.documentElement.clientWidth,
|
||||
};
|
||||
}"""
|
||||
)
|
||||
assert metrics["top"] >= -1
|
||||
assert metrics["bottom"] <= metrics["viewportHeight"] + 1
|
||||
assert metrics["pageWidth"] <= metrics["viewportWidth"] + 1
|
||||
|
||||
|
||||
def test_magic_link_mobile_viewport_keeps_composer_messages_and_credit_visible(live_server, browser_page):
|
||||
branding = SiteBranding.load()
|
||||
branding.developer_credit_text = "امضای مقدس توسعهدهنده E2E"
|
||||
branding.save()
|
||||
category = TicketCategory.objects.create(title="موبایل لینک امن", slug="magic-mobile-viewport")
|
||||
customer = User.objects.create_user(username="magic-mobile", mobile="09120009101", account_type=User.AccountType.CUSTOMER)
|
||||
CustomerProfile.objects.create(user=customer, first_name="مشتری", last_name="لینک امن")
|
||||
staff = User.objects.create_user(username="magic-mobile-staff", mobile="09120009102", account_type=User.AccountType.STAFF)
|
||||
StaffProfile.objects.create(user=staff, display_name="کارشناس لینک امن")
|
||||
ticket = create_ticket(customer=customer, category=category, subject="بررسی موبایل لینک امن", body="پیام آغازین")
|
||||
for index in range(12):
|
||||
Message.objects.create(ticket=ticket, author=staff, body=f"پاسخ کارشناس شماره {index + 1}")
|
||||
token = TicketMagicLink.issue(ticket)
|
||||
|
||||
with browser_page() as page:
|
||||
page.set_viewport_size({"width": 390, "height": 844})
|
||||
page.goto(f"{live_server.url}{reverse('accounts:magic', args=[token])}", wait_until="domcontentloaded")
|
||||
conversation = TicketConversationPage(page, live_server.url)
|
||||
credit = page.locator(".ticket-detail-credit")
|
||||
expect(credit).to_be_visible()
|
||||
expect(credit).to_contain_text("امضای مقدس توسعهدهنده E2E")
|
||||
assert_workspace_fits_visual_viewport(page)
|
||||
conversation.expect_latest_message_above_composer()
|
||||
|
||||
conversation.open_composer()
|
||||
page.set_viewport_size({"width": 390, "height": 620})
|
||||
expect(page.locator('form[data-ticket-composer] textarea')).to_be_focused()
|
||||
expect(credit).to_be_visible()
|
||||
assert_workspace_fits_visual_viewport(page)
|
||||
conversation.expect_latest_message_above_composer()
|
||||
|
||||
page.locator("[data-composer-close]").click()
|
||||
expect(page.locator("[data-composer-shell].is-collapsed")).to_be_visible()
|
||||
expect(credit).to_be_visible()
|
||||
assert_workspace_fits_visual_viewport(page)
|
||||
|
||||
|
||||
def test_staff_ticket_mobile_viewport_survives_open_close_and_rotation(live_server, browser_page):
|
||||
category = TicketCategory.objects.create(title="موبایل کارشناس", slug="staff-mobile-viewport")
|
||||
customer = User.objects.create_user(username="staff-mobile-customer", mobile="09120009103", account_type=User.AccountType.CUSTOMER)
|
||||
CustomerProfile.objects.create(user=customer, first_name="مشتری", last_name="پنل کارشناس")
|
||||
staff = User.objects.create_user(username="staff-mobile-user", mobile="09120009104", password=PASSWORD, account_type=User.AccountType.STAFF)
|
||||
StaffProfile.objects.create(user=staff, display_name="کارشناس موبایل")
|
||||
ticket = create_ticket(customer=customer, category=category, subject="بررسی موبایل پنل کارشناس", selected_expert=staff, body="شرح درخواست مشتری")
|
||||
for index in range(12):
|
||||
Message.objects.create(ticket=ticket, author=customer, body=f"پیام مشتری شماره {index + 1}")
|
||||
|
||||
with browser_page() as page:
|
||||
page.set_viewport_size({"width": 390, "height": 844})
|
||||
AuthenticationPage(page, live_server.url).login_staff(staff, PASSWORD)
|
||||
conversation = TicketConversationPage(page, live_server.url)
|
||||
conversation.goto(reverse("staff_tickets:detail", args=[ticket.public_id]))
|
||||
expect(page.get_by_role("button", name="ثبت اقدام جدید در تیکت")).to_be_visible()
|
||||
assert_workspace_fits_visual_viewport(page)
|
||||
conversation.expect_latest_message_above_composer()
|
||||
|
||||
conversation.open_composer()
|
||||
page.set_viewport_size({"width": 844, "height": 390})
|
||||
assert_workspace_fits_visual_viewport(page)
|
||||
conversation.expect_latest_message_above_composer()
|
||||
expect(page.locator("[data-composer-close]")).to_be_visible()
|
||||
|
||||
page.locator("[data-composer-close]").click()
|
||||
expect(page.locator("[data-composer-shell].is-collapsed")).to_be_visible()
|
||||
assert_workspace_fits_visual_viewport(page)
|
||||
@@ -136,9 +136,17 @@ def test_e2e_subject_stays_empty_for_manual_and_search_routing_until_user_types(
|
||||
subject_input = page.locator("[data-ticket-subject]")
|
||||
page.locator(".ticket-manual-routing summary").click()
|
||||
expect(form.unit).to_be_visible()
|
||||
unit_box = form.unit.bounding_box()
|
||||
category_box = form.category.bounding_box()
|
||||
assert unit_box and category_box
|
||||
boxes = page.locator("[data-routing-selectors]").evaluate(
|
||||
"""root => {
|
||||
const unit = root.querySelector('[data-ticket-unit]').getBoundingClientRect();
|
||||
const category = root.querySelector('[data-ticket-category]').getBoundingClientRect();
|
||||
return {
|
||||
unit: {x: unit.x, y: unit.y, width: unit.width},
|
||||
category: {x: category.x, y: category.y, width: category.width},
|
||||
};
|
||||
}"""
|
||||
)
|
||||
unit_box, category_box = boxes["unit"], boxes["category"]
|
||||
assert abs(unit_box["y"] - category_box["y"]) < 2
|
||||
assert unit_box["x"] + unit_box["width"] <= category_box["x"] or category_box["x"] + category_box["width"] <= unit_box["x"]
|
||||
expect(subject_input).to_have_value("")
|
||||
|
||||
+19
-1
@@ -442,10 +442,27 @@ def test_ticket_polling_javascript_keeps_background_sync_and_recovers_reliably()
|
||||
source_css=Path("static/src/tailwind.css").read_text(encoding="utf-8")
|
||||
assert ".staff-ticket-workspace .composer.is-collapsed{position:fixed" not in source_css
|
||||
assert ".staff-ticket-workspace .composer:not(.is-collapsed){position:fixed" not in source_css
|
||||
assert "max-height:min(45dvh,28rem)" in source_css
|
||||
assert "max-height:min(45svh,max(7rem,calc(var(--visual-viewport-height,100svh) - 18rem)),28rem)" in source_css
|
||||
for menu_feature in ("data-message-menu","data-message-menu-close","closeMessageMenu","event.key==='Escape'","!menu.contains(event.target)"):
|
||||
assert menu_feature in script or menu_feature in Path("templates/tickets/_messages.html").read_text(encoding="utf-8")
|
||||
|
||||
def test_ticket_workspace_uses_mobile_viewport_and_keyboard_safe_layout_contract():
|
||||
base=Path("templates/base.html").read_text(encoding="utf-8")
|
||||
source_css=Path("static/src/tailwind.css").read_text(encoding="utf-8")
|
||||
ui_script=Path("static/js/ui.js").read_text(encoding="utf-8")
|
||||
conversation_script=Path("static/js/conversation.js").read_text(encoding="utf-8")
|
||||
magic_template=Path("templates/tickets/customer_detail.html").read_text(encoding="utf-8")
|
||||
assert "interactive-widget=resizes-content" in base
|
||||
for feature in ("100svh","--visual-viewport-height","overscroll-behavior:none","display-mode:standalone"):
|
||||
assert feature in source_css
|
||||
assert "min-height:32rem" not in source_css
|
||||
assert "max-height:min(45svh,max(7rem,calc(var(--visual-viewport-height,100svh) - 18rem)),28rem)" in source_css
|
||||
for feature in ("window.visualViewport","--visual-viewport-height","appviewportchange","orientationchange"):
|
||||
assert feature in ui_script
|
||||
assert "layoutObserver?.observe(composer)" in conversation_script
|
||||
assert "focus({preventScroll:true})" in conversation_script
|
||||
assert '<footer class="ticket-detail-credit">{{ developer_credit }}</footer>' in magic_template
|
||||
|
||||
def test_frontend_runtime_libraries_are_local_and_versioned():
|
||||
template=Path("templates/base.html").read_text(encoding="utf-8")
|
||||
assert "unpkg.com" not in template
|
||||
@@ -527,6 +544,7 @@ def test_pwa_prompt_is_mobile_only_and_uses_square_app_icon(client):
|
||||
script=Path("static/js/pwa.js").read_text(encoding="utf-8")
|
||||
manifest=client.get(reverse("web_manifest")).json()
|
||||
assert "mobileDevice" in script and "banner&&mobileDevice" in script
|
||||
assert "hasWorkerController=Boolean(navigator.serviceWorker.controller)" in script
|
||||
assert all(icon["src"].endswith("pwa-icon.png") for icon in manifest["icons"])
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user