const { test, expect } = require("@playwright/test");

// 手机端（/m/）全页面回归：P5 收官打磨 + HEL-233 系统管理恢复。
// 覆盖：登录、四个入口图标页、行情 12 页、工具 3 页、复盘 5 页、复盘助手、系统管理 5 页，
// 以及日夜两套渲染、空态、错误态、横屏健壮性、深底深字对比度抽查。

const EMPTY_DASHBOARD = {
  meta: {
    trade_date: "2026-07-22",
    requested_date: "2026-07-22",
    source: "tushare",
    realtime: false,
    cached: true,
    market_status: "closed",
    previous_trade_date: "2026-07-21",
  },
  overview: {
    up_count: 2100,
    down_count: 2800,
    limit_up_count: 42,
    limit_down_count: 8,
    broken_count: 17,
    seal_rate: 71.2,
    amount_billion: 12600,
    sentiment_score: 48,
  },
  limits: [],
  broken: [],
  down_limits: [],
  yesterday_limits: [],
  limit_performance: [],
  ladders: [],
  sectors: [],
  sector_rotation: [],
};

const DASHBOARD = {
  ...EMPTY_DASHBOARD,
  limits: [
    { code: "002141", name: "贤丰控股", streak: 4, change: 9.98, price: 12.48, sector: "电子元件", first_time: "09:31", last_time: "10:18", open_times: 1, turnover_rate: 18.42, amount_billion: 12.6, seal_amount_million: 8200, reason: "板块龙头连板打开空间" },
  ],
  broken: [
    { code: "002156", name: "通富微电", change: 9.77, price: 76.64, sector: "半导体", first_time: "10:08:03", open_times: 4, turnover_rate: 17.36, amount_billion: 198.18, reason: "芯片方向冲高回落" },
  ],
  down_limits: [
    { code: "000037", name: "深南电A", change: -10.03, price: 8.97, sector: "电力", turnover_rate: 11.78, amount_billion: 3.68, streak: 2, reason: "连续弱势跌停" },
  ],
  yesterday_limits: [
    { code: "000011", name: "深物业A", prior_streak: 1, current_change: 9.99, outcome: "晋级", current_streak: 2, sector: "房地产开发", reason: "地产政策预期" },
  ],
  limit_performance: [
    { level: 3, label: "昨日3板", count: 4, advanced: 2, advance_rate: 50, positive_rate: 75, average_change: 3.4 },
    { level: 2, label: "昨日2板", count: 9, advanced: 2, advance_rate: 22.2, positive_rate: 44.4, average_change: 0.8 },
  ],
  ladders: [
    { level: 4, label: "4板", count: 1, stocks: [{ code: "002141", name: "贤丰控股", sector: "电子元件", first_time: "09:31", open_times: 0, seal_amount_million: 8200 }] },
    { level: 3, label: "3板", count: 2, stocks: [{ code: "000011", name: "深物业A", sector: "房地产开发", first_time: "09:40", open_times: 1, amount_billion: 3.2 }] },
  ],
};

function authSession(role = "admin", subscribed = true) {
  return {
    authenticated: true,
    csrf_token: "mobile-test-csrf",
    user: {
      id: role === "admin" ? 1 : 2,
      username: role === "admin" ? "admin_user" : "normal_user",
      role,
      membership: { active: role === "admin" || subscribed, subscribed, is_admin: role === "admin" },
    },
  };
}

async function mockMobileApi(page, options = {}) {
  const auth = options.auth || authSession();
  const dashboard = options.emptyDashboard ? EMPTY_DASHBOARD : DASHBOARD;
  await page.route("**/api/**", async (route) => {
    const url = new URL(route.request().url());
    const path = url.pathname;
    let payload = { ok: true };
    if (options.failDashboard && path === "/api/dashboard") {
      await route.fulfill({ status: 500, contentType: "application/json", body: JSON.stringify({ error: "行情服务暂时不可用" }) });
      return;
    }
    if (path === "/api/auth/me") payload = auth;
    else if (path === "/api/dashboard") payload = dashboard;
    else if (path === "/api/popularity") {
      const hot = { rank: 1, code: "002141", ts_code: "002141.SZ", name: "贤丰控股", change: 2.4, price: 12.48, ths_rank: 1, dc_rank: 2, rank_change: 3, concepts: ["电子元件"], dual_source: true };
      payload = { meta: { trade_date: "2026-07-22" }, combined: [hot], ths: [{ ...hot }], dc: [{ ...hot, rank: 2 }] };
    } else if (path === "/api/sentiment/history") {
      payload = { rows: [{ trade_date: "2026-07-22", score: 48, phase: "修复", direction: "升温", label: "情绪修复", day_change: 4, seal_rate: 71, limit_up_count: 42, broken_count: 17, components: {} }] };
    } else if (path === "/api/rotation/history") {
      payload = { rows: [{ trade_date: "2026-07-22", sectors: [{ name: "人工智能", rank: 1, count: 8, strength: 88 }] }] };
    } else if (path === "/api/rotation/members") {
      payload = { meta: { trade_date: "20260722", sector_name: "人工智能", member_count: 1, quoted_count: 1 }, rows: [{ code: "002141", name: "贤丰控股", change: 4.8, open: 18.21, close: 19.06, amount_billion: 19.6 }] };
    } else if (path === "/api/auction") {
      payload = {
        meta: { trade_date: "2026-07-22", phase: "finalized", available: true },
        summary: { stock_count: 3, focus_count: 1, one_price_count: 1, amount_billion: 2.5 },
        amount_history: [{ trade_date: "2026-07-22", amount_billion: 2.5, stock_count: 2 }],
        themes: { carry: [{ name: "电子元件", status: "强承接", prior_limit_count: 2, leader: "贤丰控股", matched_count: 1, median_change: 4.2, amount_million: 15 }] },
        focus_rows: [{ code: "002141", name: "贤丰控股", sector: "电子元件", change: 4.2, price: 12.48, amount_million: 15, volume_ratio: 1.8, expectation: "超预期", attention_score: 88.5 }],
        rows: [],
        one_price_rows: [],
        watchlist_rows: [],
      };
    } else if (path === "/api/themes") {
      payload = { meta: { trade_date: "2026-07-22" }, summary: { theme_count: 1, up_count: 1, down_count: 0, hot_count: 1 }, items: [{ code: "885728.TI", name: "人工智能", member_count: 8, change: 2.2, turnover_rate: 3.1, hot_rank: 1, has_quote: true }] };
    } else if (path === "/api/themes/detail") {
      payload = { meta: { trade_date: "2026-07-22" }, theme: { code: "885728.TI", name: "人工智能", member_count: 8, change: 2.2, turnover_rate: 3.1 }, summary: { member_count: 8, quoted_count: 8, up_count: 6, down_count: 2 }, members: [{ code: "002141", name: "贤丰控股", change: 2.4, price: 12.48, amount_billion: 3.2 }] };
    } else if (path === "/api/dragon-tiger") {
      payload = { meta: { trade_date: "2026-07-22", status: "ok" }, summary: { trader_count: 1, operation_count: 2, active_stock_count: 1 }, traders: [{ id: "t1", name: "赵老哥", identity_type: "trader", recognized: true, stock_count: 1, operation_count: 2, net_buy_million: 150, operations: [] }], unclassified_seats: [] };
    } else if (path === "/api/dragon-tiger/profiles") {
      payload = { meta: { status: "success" }, summary: { profile_count: 1, described_count: 1, organization_count: 1 }, profiles: [{ id: "p1", name: "赵老哥", description: "聚焦核心。", organizations: ["华泰证券浙江分公司"], organization_count: 1 }] };
    } else if (path === "/api/screener/setup") {
      payload = {
        trade_date: "20260722",
        regime: { id: "repair", label: "修复", confidence: 70, reason: "测试" },
        regimes: [{ id: "repair", label: "修复" }],
        factor_data: { ready: true, date_count: 45 },
        strategies: [{ id: 1, name: "修复确认", description: "保留原有流程", regimes: ["repair"], builtin: true, data_ready: true, missing_data: [], formula: { meta: { library: "smart" }, universe: { exclude_st: true }, filters: [], score: [], limit: 15 } }],
      };
    } else if (path === "/api/screener/tracking") {
      payload = { batches: [], summary: { total: 0, observed: 0, t1_win_rate: null, t5_win_rate: null, average_t5: null } };
    } else if (path === "/api/watchlist") {
      payload = { items: [{ code: "000002", name: "万科A", sector: "房地产开发", color: "red", change: 1.86, return_5d: 8.92, attention_score: 72.4, remark: "" }] };
    } else if (path === "/api/trades") {
      payload = { items: [{ id: 7, trade_date: "20260722", code: "002141", name: "贤丰控股", action: "buy", action_label: "买入", price: 10.2, quantity: 1000, position_pct: 20, pnl_amount: null, pnl_pct: null, emotion: "calm", emotion_label: "平静", tags: [], thesis: "", execution: "" }], summary: { total: 1, realized: 0, win_rate: null, pnl_amount: null, average_position: 20 } };
    } else if (path === "/api/notes") {
      payload = { items: [{ id: 12, code: "002141", stock_name: "贤丰控股", trade_date: "20260722", summary: "缩量修复", content: "等待确认。", plan: "" }] };
    } else if (path === "/api/alerts") {
      payload = { items: [{ id: 11, kind: "manual", available_date: "20260722", title: "复盘开盘强度", content: "", code: "002141", is_read: false, due: true }], unread_count: 1 };
    } else if (path === "/api/assistant/messages") {
      payload = { items: [] };
    } else if (path === "/api/mentors/setup") {
      payload = { trade_date: "20260722", mentors: [{ id: "source-a", name: "原帖老师", tagline: "先看周期。", focus: ["情绪周期"], evidence: { grade: "A" }, private: false }] };
    } else if (path === "/api/mentors/messages") {
      payload = { items: [] };
    } else if (path === "/api/search") {
      payload = { groups: { stocks: [{ id: "002141", code: "002141", name: "贤丰控股", type: "stock", industry: "电子元件" }], sectors: [], themes: [], indices: [] } };
    } else if (path === "/api/auth/accounts") {
      payload = {
        accounts: [{ user_id: auth.user.id, username: auth.user.username, role: auth.user.role, last_used_at: "2026-07-22T09:12:00+08:00" }],
        current_user_id: auth.user.id,
      };
    } else if (path === "/api/account/status") {
      payload = {
        birth_profile_configured: true,
        birth_profile: { birth_datetime: "1990-01-15T08:30", gender: "male" },
        llm_access: {
          daily_limit: 50,
          used_today: 3,
          remaining_calls: 47,
          membership: {
            active: true,
            subscribed: true,
            is_admin: auth.user.role === "admin",
            remaining_days: 20,
            expires_at: "2026-09-18",
            plan: "会员",
          },
        },
      };
    } else if (path === "/api/admin/settings") {
      payload = {
        data: { configured: true, snapshot_dates: 12, background_refresh_enabled: true, ifind: { configured: false } },
        llm: { models: [{ id: "model-1", name: "主模型", base_url: "https://api.openai.com/v1", model: "gpt-4.1", configured: true }], primary_model_id: "model-1", fallback_model_id: "" },
        membership: { member_daily_limit: 50 },
        users: [{ id: 2, username: "normal_user", role: "user", membership_subscribed: true, membership_status: "active", membership_expires_at: "2026-09-18", used_today: 3 }],
      };
    } else if (/^\/api\/stock\/\d+\/preview$/.test(path)) {
      payload = {
        meta: { trade_date: "2026-07-22", intraday_status: "available" },
        stock: { code: "002141", name: "贤丰控股", industry: "电子元件", price: 12.48, change: 2.4 },
        prices: [{ trade_date: "2026-07-21", open: 10, high: 10.5, low: 9.9, close: 10.2, volume: 1000 }, { trade_date: "2026-07-22", open: 10.3, high: 10.9, low: 10.2, close: 12.48, volume: 1200 }],
        intraday: [{ date: "2026-07-22", time: "09:30", open: 10.2, high: 10.24, low: 10.18, close: 10.22, volume: 100, average: 10.22 }],
      };
    }
    await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify(payload) });
  });
}

async function openMobile(page) {
  await page.setViewportSize({ width: 390, height: 844 });
  await page.goto("/m/");
  await expect(page.locator("#m-boot-splash")).toBeHidden();
  await expect(page.locator("#m-tabbar")).toBeVisible();
}

async function setMobileTheme(page, theme) {
  const wanted = theme === "night" ? "dark" : "light";
  await page.evaluate((value) => window.MobileTheme.applyTheme(value), wanted);
  await expect(page.locator("#m-app")).toHaveAttribute("data-theme", wanted);
}

async function navigateToFeature(page, key) {
  await page.evaluate((k) => { window.MobileRouter.navigate("#/feature/" + k); }, key);
  await expect(page.locator(`#m-view .m-page[data-page="${key}"]`)).toBeVisible();
}

function measureOverflow(page) {
  return page.evaluate(() => document.documentElement.scrollWidth - window.innerWidth);
}

// 深底深字 / 隐形文字抽查：文本元素的前景色若与其实际背景色完全一致即为不可见文字。
function findInvisibleText(page) {
  return page.evaluate(() => {
    const issues = [];
    const seen = new Set();
    const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);
    let node;
    while ((node = walker.nextNode())) {
      const text = node.textContent.trim();
      if (!text || text.length < 1) continue;
      const el = node.parentElement;
      if (!el || seen.has(el)) continue;
      seen.add(el);
      const style = getComputedStyle(el);
      if (style.display === "none" || style.visibility === "hidden" || style.opacity === "0") continue;
      let bg = "transparent";
      let cur = el;
      while (cur && (bg === "transparent" || bg === "rgba(0, 0, 0, 0)")) {
        bg = getComputedStyle(cur).backgroundColor;
        cur = cur.parentElement;
      }
      if (style.color === bg) {
        issues.push({ tag: el.tagName, cls: String(el.className).slice(0, 40), text: text.slice(0, 24), color: style.color });
      }
    }
    return issues;
  });
}

const MARKET_PAGES = [
  ["market/sentiment", "情绪周期"],
  ["market/limit-up", "涨停池"],
  ["market/broken", "炸板池"],
  ["market/limit-down", "跌停池"],
  ["market/yesterday", "昨日涨停"],
  ["market/performance", "涨停表现"],
  ["market/ladder", "市场天梯"],
  ["market/rotation", "主题轮动"],
  ["market/auction", "竞价"],
  ["market/themes", "题材库"],
  ["market/popularity", "人气榜"],
  ["market/dragon", "龙虎榜"],
];

const TOOL_PAGES = [
  ["tools/screener", "智能选股"],
  ["tools/tracking", "策略跟踪"],
  ["tools/mentor", "问师"],
];

const REVIEW_PAGES = [
  ["review/watchlist", "自选股"],
  ["review/trades", "交易日志"],
  ["review/daily", "每日复盘"],
  ["review/notes", "个股笔记"],
  ["review/alerts", "提醒中心"],
];

const SYSTEM_PAGES = [
  ["system/profile", "账号资料"],
  ["system/password", "修改密码"],
  ["system/membership", "会员状态"],
  ["system/admin", "系统设置"],
  ["system/members", "会员管理"],
];

const PLACEHOLDER_COPY = "该功能页将在后续批次实现";

test("mobile login renders before authentication", async ({ page }) => {
  await page.setViewportSize({ width: 390, height: 844 });
  await page.route("**/api/**", async (route) => {
    const url = new URL(route.request().url());
    if (url.pathname === "/api/auth/me") {
      await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ authenticated: false }) });
      return;
    }
    await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ ok: true }) });
  });
  await page.goto("/m/");
  await expect(page.locator("#m-auth-form")).toBeVisible();
  await expect(page.locator("#m-auth-username")).toBeVisible();
  await expect(page.locator("#m-tabbar")).toBeHidden();
});

test("four hub pages render their icon grids", async ({ page }) => {
  await mockMobileApi(page);
  await openMobile(page);
  for (const hub of ["market", "tools", "review"]) {
    await page.evaluate((h) => { window.MobileRouter.navigate("#/hub/" + h); }, hub);
    await expect(page.locator(".m-hub-grid")).toBeVisible();
    await expect(page.locator(".m-hub-grid .m-grid-item").first()).toBeVisible();
    expect(await measureOverflow(page)).toBeLessThanOrEqual(1);
  }
  await page.evaluate(() => { window.MobileRouter.navigate("#/hub/system"); });
  await expect(page.locator("[data-system-page='home']")).toBeVisible();
  await expect(page.locator(".m-sys-row").first()).toBeVisible();
  expect(await measureOverflow(page)).toBeLessThanOrEqual(1);
});

for (const theme of ["day", "night"]) {
  test(`market 12 pages render without overflow or invisible text (${theme})`, async ({ page }) => {
    await mockMobileApi(page);
    await openMobile(page);
    await setMobileTheme(page, theme);
    for (const [key, label] of MARKET_PAGES) {
      await navigateToFeature(page, key);
      await expect(page.locator("#m-title")).toHaveText(label);
      await expect(page.locator("#m-view .m-page")).toBeVisible();
      await page.waitForTimeout(120);
      expect(await measureOverflow(page), `${key} overflows in ${theme}`).toBeLessThanOrEqual(1);
      const invisible = await findInvisibleText(page);
      expect(invisible, `${key} invisible text in ${theme}: ${JSON.stringify(invisible)}`).toEqual([]);
    }
  });
}

test("tools pages render screener, tracking and mentor", async ({ page }) => {
  await mockMobileApi(page);
  await openMobile(page);
  for (const [key, label] of TOOL_PAGES) {
    await navigateToFeature(page, key);
    await expect(page.locator("#m-title")).toHaveText(label);
    await page.waitForTimeout(120);
    expect(await measureOverflow(page)).toBeLessThanOrEqual(1);
  }
});

test("review five pages render watchlist, trades, daily, notes and alerts", async ({ page }) => {
  await mockMobileApi(page);
  await openMobile(page);
  for (const [key, label] of REVIEW_PAGES) {
    await navigateToFeature(page, key);
    await expect(page.locator("#m-title")).toHaveText(label);
    await page.waitForTimeout(120);
    expect(await measureOverflow(page)).toBeLessThanOrEqual(1);
  }
});

test("system management pages render real content instead of placeholders", async ({ page }) => {
  await mockMobileApi(page);
  await openMobile(page);
  for (const [key, label] of SYSTEM_PAGES) {
    await navigateToFeature(page, key);
    await expect(page.locator("#m-title")).toHaveText(label);
    await expect(page.locator(".m-placeholder")).toHaveCount(0);
    await expect(page.locator("#m-view")).not.toContainText(PLACEHOLDER_COPY);
    await expect(page.locator("[data-system-page], [data-system-admin-panel]").first()).toBeVisible();
    expect(await measureOverflow(page)).toBeLessThanOrEqual(1);
  }
  await navigateToFeature(page, "system/profile");
  await expect(page.locator("#m-sys-birth-date")).toBeVisible();
  await expect(page.locator("[data-system-save-birth]")).toBeVisible();
  await navigateToFeature(page, "system/password");
  await expect(page.locator("#m-sys-password-current")).toBeVisible();
  await navigateToFeature(page, "system/membership");
  await expect(page.locator(".m-sys-grid")).toBeVisible();
  await navigateToFeature(page, "system/admin");
  await expect(page.locator("#m-sys-token")).toBeVisible();
  await navigateToFeature(page, "system/members");
  await expect(page.locator("#m-sys-member-limit")).toBeVisible();
});

test("system home groups entries and keeps admin-only items gated", async ({ page }) => {
  await mockMobileApi(page);
  await openMobile(page);
  await page.evaluate(() => { window.MobileRouter.navigate("#/hub/system"); });
  await expect(page.locator("[data-system-page='home']")).toBeVisible();
  await expect(page.locator("#m-view")).toContainText("账号");
  await expect(page.locator("#m-view")).toContainText("偏好");
  await expect(page.locator("#m-view")).toContainText("管理员专区");
  await expect(page.locator("[data-theme-toggle]")).toBeVisible();
  await expect(page.locator("[data-system-switch]")).toBeVisible();
  expect(await measureOverflow(page)).toBeLessThanOrEqual(1);
});

test("system settings tabs, model editor, delete confirm and theme toggle work", async ({ page }) => {
  await mockMobileApi(page);
  await openMobile(page);
  await navigateToFeature(page, "system/admin");
  await expect(page.locator("[data-system-admin-panel='market']")).toBeVisible();
  await page.locator("[data-system-admin-tab='models']").click();
  await expect(page.locator("[data-system-admin-panel='models']")).toBeVisible();
  await page.locator("[data-system-edit-model]").first().click();
  await expect(page.locator(".m-sheet-root.is-open")).toBeVisible();
  await expect(page.locator(".m-sheet-head h2")).toHaveText("编辑模型");
  await page.locator("[data-sheet-close]").click();
  await page.locator("[data-system-admin-tab='market']").click();
  await expect(page.locator("#m-sys-token")).toBeVisible();

  await navigateToFeature(page, "system/profile");
  await page.locator("[data-system-delete-birth]").click();
  await expect(page.locator(".m-dialog")).toBeVisible();
  await expect(page.locator(".m-dialog")).toContainText("删除命理资料");
  await page.locator("[data-sheet-close]").click();

  await page.evaluate(() => { window.MobileRouter.navigate("#/hub/system"); });
  await expect(page.locator("[data-theme-toggle]")).toBeVisible();
  const before = await page.locator("#m-app").getAttribute("data-theme");
  await page.locator("[data-theme-toggle]").click();
  await expect.poll(async () => page.locator("#m-app").getAttribute("data-theme")).not.toBe(before);

  await navigateToFeature(page, "system/members");
  await page.locator("[data-system-open-member]").click();
  await expect(page.locator(".m-sheet-root.is-open")).toBeVisible();
  await expect(page.locator(".m-sheet-head h2")).toContainText("管理会员");
});

test("password mismatch shows inline error instead of a silent submit", async ({ page }) => {
  await mockMobileApi(page);
  await openMobile(page);
  await navigateToFeature(page, "system/password");
  await page.locator("#m-sys-password-current").fill("OldPass12");
  await page.locator("#m-sys-password-new").fill("NewPass123");
  await page.locator("#m-sys-password-confirm").fill("OtherPass123");
  await page.locator("[data-system-save-password]").click();
  await expect(page.locator("[data-field-error='confirm']")).toBeVisible();
  await expect(page.locator("[data-field-error='confirm']")).toContainText("两次输入的密码不一致");
});

test("empty birth profile save shows a validation toast", async ({ page }) => {
  await mockMobileApi(page);
  await openMobile(page);
  await navigateToFeature(page, "system/profile");
  await expect(page.locator("[data-system-save-birth]")).toBeVisible();
  await page.locator("#m-sys-birth-date").fill("");
  await page.locator("#m-sys-birth-time").fill("");
  await page.locator("[data-system-save-birth]").click();
  await expect(page.locator("#m-toast.is-visible")).toBeVisible();
  await expect(page.locator("#m-toast")).toContainText("请填写完整出生日期和时间");
});

test("non-admin cannot open system admin pages as placeholders", async ({ page }) => {
  await mockMobileApi(page, { auth: authSession("user", true) });
  await openMobile(page);
  await page.evaluate(() => { window.MobileRouter.navigate("#/hub/system"); });
  await expect(page.locator("[data-system-page='home']")).toBeVisible();
  await expect(page.locator('[data-route="#/feature/system/admin"]')).toHaveCount(0);
  await expect(page.locator('[data-route="#/feature/system/members"]')).toHaveCount(0);
  await expect(page.locator("[data-system-switch]")).toBeVisible();
  await navigateToFeature(page, "system/admin");
  await expect(page.locator("#m-view")).not.toContainText(PLACEHOLDER_COPY);
  await expect(page.locator("[data-system-page='forbidden']")).toBeVisible();
});

test("assistant chat renders with presets and input", async ({ page }) => {
  await mockMobileApi(page);
  await openMobile(page);
  await page.evaluate(() => { window.MobileRouter.navigate("#/assistant/chat"); });
  await expect(page.locator("#m-title")).toHaveText("复盘助手");
  await expect(page.locator("#m-chat-input")).toBeVisible();
  await expect(page.locator(".m-chat-presets")).toBeVisible();
  expect(await measureOverflow(page)).toBeLessThanOrEqual(1);
});

test("empty dashboard shows empty state instead of a bare table", async ({ page }) => {
  await mockMobileApi(page, { emptyDashboard: true });
  await openMobile(page);
  await navigateToFeature(page, "market/limit-up");
  await expect(page.locator(".m-state")).toBeVisible();
  await expect(page.locator(".m-state p")).toContainText("暂无相关数据");
});

test("dashboard failure shows error state with retry", async ({ page }) => {
  await mockMobileApi(page, { failDashboard: true });
  await openMobile(page);
  await navigateToFeature(page, "market/limit-up");
  await expect(page.locator(".m-state--error")).toBeVisible();
  await expect(page.locator(".m-btn-retry")).toBeVisible();
});

test("landscape keeps pages usable and returns to portrait", async ({ page }) => {
  await mockMobileApi(page);
  await page.setViewportSize({ width: 390, height: 844 });
  await page.goto("/m/");
  await expect(page.locator("#m-tabbar")).toBeVisible();

  await page.setViewportSize({ width: 844, height: 390 });
  await page.evaluate(() => { window.MobileRouter.navigate("#/feature/market/sentiment"); });
  await page.waitForTimeout(150);
  await expect(page.locator("#m-title")).toHaveText("情绪周期");
  expect(await measureOverflow(page)).toBeLessThanOrEqual(1);
  await expect(page.locator("#m-tabbar")).toBeVisible();

  await page.evaluate(() => { window.MobileRouter.navigate("#/feature/market/limit-up"); });
  await page.waitForTimeout(150);
  await expect(page.locator(".m-table")).toBeVisible();
  expect(await measureOverflow(page)).toBeLessThanOrEqual(1);

  await page.setViewportSize({ width: 390, height: 844 });
  await expect(page.locator("#m-tabbar")).toBeVisible();
  expect(await measureOverflow(page)).toBeLessThanOrEqual(1);
});
