a37a1ae363
The server-rendered pages worked on desktop but were near-unusable on a phone: no viewport meta tag (mobile browsers laid out in a ~980px canvas and zoomed out, rendering everything tiny) and a fixed `max-width: 900px` desktop body with zero responsive breakpoints. Fix is CSS-only and purely additive: - Add `<meta name="viewport" content="width=device-width, initial-scale=1">` to all four page templates (each owns its own <head> — askama uses import/include here, no shared base layout, so the tag lands in 4 places). - Append a single `@media (max-width: 640px)` block to the three desktop-width pages (my_cases, case_recordings, case_page): case rows and action bars reflow to a single column, the audio player goes full-width, and primary touch targets (delete/reopen/play) get a 44px minimum. login.html already has a 360px column layout, so it needs the viewport tag only. Desktop layout is preserved by construction: a `max-width: 640px` query never matches at >=900px, so the desktop render is byte-identical. The diff adds lines only — no existing rule is touched. Breakpoint chosen at 640px (well below the 900px body width) so all portrait phones, including the ~390px target, fall under it while tablets keep the desktop layout. New integration test server/tests/responsive_test.rs pins the two textual acceptance criteria that survive without a headless browser: every page ships the viewport meta, and the three desktop-width pages ship an @media breakpoint. The visual criteria (operable at 390px, 44px touch targets) are CSS-driven and verified by eye — deliberately not covered by a browser-automation dependency. Verified: cargo test -p doctate-server --test responsive_test (4/4 green); web/login/case_page regression suites green; diff is additive. closes #10
117 lines
3.7 KiB
Rust
117 lines
3.7 KiB
Rust
//! Responsive WebUI regression pins for issue #10.
|
|
//!
|
|
//! These guard the two *textual* acceptance criteria that survive
|
|
//! without a headless browser: every page ships a `width=device-width`
|
|
//! viewport meta tag, and the three desktop-width pages (cases list,
|
|
//! case page, recordings) carry at least one `@media` breakpoint so the
|
|
//! layout can reflow on narrow viewports. The visual criteria (operable
|
|
//! at 390px, 44px touch targets) are verified by eye; these tests stop a
|
|
//! future template rewrite from silently dropping the viewport tag or
|
|
//! the breakpoints.
|
|
|
|
mod common;
|
|
|
|
use axum::body::Body;
|
|
use axum::http::{Request, StatusCode};
|
|
use tower::util::ServiceExt;
|
|
|
|
use common::{TestConfig, body_string, get_with_cookie, paths, seed_case, test_user};
|
|
|
|
const VIEWPORT: &str = "width=device-width";
|
|
|
|
#[tokio::test]
|
|
async fn login_page_has_viewport_meta() {
|
|
let cfg = TestConfig::new()
|
|
.with_label("resp-login")
|
|
.with_user(test_user("dr_a"))
|
|
.build();
|
|
let app = doctate_server::create_router(cfg);
|
|
|
|
let resp = app
|
|
.oneshot(
|
|
Request::builder()
|
|
.uri(paths::LOGIN)
|
|
.body(Body::empty())
|
|
.unwrap(),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(resp.status(), StatusCode::OK);
|
|
let body = body_string(resp).await;
|
|
assert!(body.contains(VIEWPORT), "login page missing viewport meta");
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn cases_list_has_viewport_and_media_query() {
|
|
let cfg = TestConfig::new()
|
|
.with_label("resp-cases")
|
|
.with_user(test_user("dr_a"))
|
|
.build();
|
|
let app = doctate_server::create_router(cfg);
|
|
let cookie = common::login(&app, "dr_a", "s").await;
|
|
|
|
let resp = app
|
|
.oneshot(get_with_cookie(paths::CASES, &cookie))
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(resp.status(), StatusCode::OK);
|
|
let body = body_string(resp).await;
|
|
assert!(body.contains(VIEWPORT), "cases list missing viewport meta");
|
|
assert!(
|
|
body.contains("@media"),
|
|
"cases list missing @media breakpoint"
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn case_page_has_viewport_and_media_query() {
|
|
let cfg = TestConfig::new()
|
|
.with_label("resp-cp")
|
|
.with_user(test_user("dr_a"))
|
|
.with_llm("http://unused")
|
|
.build();
|
|
let case_id = "22222222-2222-2222-2222-222222222222";
|
|
seed_case(&cfg.data_path, "dr_a", case_id);
|
|
let app = doctate_server::create_router(cfg);
|
|
let cookie = common::login(&app, "dr_a", "s").await;
|
|
|
|
let resp = app
|
|
.oneshot(get_with_cookie(paths::case_detail(case_id), &cookie))
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(resp.status(), StatusCode::OK);
|
|
let body = body_string(resp).await;
|
|
assert!(body.contains(VIEWPORT), "case page missing viewport meta");
|
|
assert!(
|
|
body.contains("@media"),
|
|
"case page missing @media breakpoint"
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn case_recordings_has_viewport_and_media_query() {
|
|
let cfg = TestConfig::new()
|
|
.with_label("resp-rec")
|
|
.with_user(test_user("dr_a"))
|
|
.build();
|
|
let case_id = "33333333-3333-3333-3333-333333333333";
|
|
seed_case(&cfg.data_path, "dr_a", case_id);
|
|
let app = doctate_server::create_router(cfg);
|
|
let cookie = common::login(&app, "dr_a", "s").await;
|
|
|
|
let resp = app
|
|
.oneshot(get_with_cookie(paths::case_recordings(case_id), &cookie))
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(resp.status(), StatusCode::OK);
|
|
let body = body_string(resp).await;
|
|
assert!(
|
|
body.contains(VIEWPORT),
|
|
"recordings page missing viewport meta"
|
|
);
|
|
assert!(
|
|
body.contains("@media"),
|
|
"recordings page missing @media breakpoint"
|
|
);
|
|
}
|