Files
fusion/packages/dashboard/app/__tests__/mission-planning-modals-mobile.test.ts
gsxdsm 9c5dc07166 fix(FN-1304): reduce mobile button heights from 44px to 36px
- Reduce mobile media query button heights from 44px to 36px across all dashboard components
- Update mobile CSS tests (nav bar, board, modals, mission planning) to expect 36px touch targets
- Add CSS testing patterns to project memory for consistent test guidelines
2026-04-09 04:59:44 -07:00

126 lines
5.0 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { readFileSync } from "fs";
import { resolve } from "path";
const css = readFileSync(resolve(__dirname, "../styles.css"), "utf-8");
function extractMaxWidthMediaBlocks(maxWidthPx: number): string[] {
const marker = `@media (max-width: ${maxWidthPx}px)`;
const blocks: string[] = [];
let fromIndex = 0;
while (fromIndex < css.length) {
const mediaStart = css.indexOf(marker, fromIndex);
if (mediaStart === -1) {
break;
}
const openBrace = css.indexOf("{", mediaStart);
if (openBrace === -1) {
break;
}
let depth = 0;
let closeBrace = -1;
for (let i = openBrace; i < css.length; i += 1) {
const ch = css[i];
if (ch === "{") {
depth += 1;
} else if (ch === "}") {
depth -= 1;
if (depth === 0) {
closeBrace = i;
break;
}
}
}
if (closeBrace === -1) {
throw new Error(`Unclosed @media block starting at index ${mediaStart}`);
}
blocks.push(css.slice(mediaStart, closeBrace + 1));
fromIndex = closeBrace + 1;
}
return blocks;
}
const mobileBlocks = extractMaxWidthMediaBlocks(768);
function findMobileBlockContaining(needle: string): string {
const block = mobileBlocks.find((candidate) => candidate.includes(needle));
expect(block, `Expected a 768px mobile block containing "${needle}"`).toBeTruthy();
return block ?? "";
}
describe("mission + planning modal mobile CSS", () => {
it("MissionManager: mission icon button touch targets are 36px", () => {
const missionBlock = findMobileBlockContaining(".mission-manager-overlay");
expect(missionBlock).toMatch(/\.mission-list__item-actions \.mission-icon-btn,[\s\S]*?\.mission-feature__actions \.mission-icon-btn\s*\{[\s\S]*?min-width:\s*36px;[\s\S]*?min-height:\s*36px;/s);
});
it("MissionManager: body prevents horizontal overflow", () => {
const missionBlock = findMobileBlockContaining(".mission-manager-overlay");
expect(missionBlock).toMatch(/\.mission-manager__body\s*\{[^}]*overflow-x:\s*hidden;/s);
});
it("MissionManager: feature actions wrap", () => {
const missionBlock = findMobileBlockContaining(".mission-manager-overlay");
expect(missionBlock).toMatch(/\.mission-feature__actions\s*\{[^}]*flex-wrap:\s*wrap;/s);
});
it("MissionManager: detail view includes safe-area bottom padding", () => {
const missionBlock = findMobileBlockContaining(".mission-manager-overlay");
expect(missionBlock).toMatch(/\.mission-detail\s*\{[^}]*env\(safe-area-inset-bottom/s);
});
it("SubtaskBreakdown: drag handle is touch-friendly (36px)", () => {
const planningBlock = findMobileBlockContaining(".planning-modal");
expect(planningBlock).toMatch(/\.subtask-drag-handle\s*\{[^}]*min-width:\s*36px;[^}]*min-height:\s*36px;/s);
});
it("SubtaskBreakdown: subtask action icon buttons are 36px", () => {
const planningBlock = findMobileBlockContaining(".planning-modal");
expect(planningBlock).toMatch(/\.subtask-item-actions \.btn-icon\s*\{[^}]*min-width:\s*36px;[^}]*min-height:\s*36px;/s);
});
it("SubtaskBreakdown: dependency chips are touch-friendly", () => {
const planningBlock = findMobileBlockContaining(".planning-modal");
expect(planningBlock).toMatch(/\.planning-dep-chip\s*\{[^}]*min-height:\s*36px;/s);
});
it("PlanningMode: confirm buttons meet touch targets", () => {
const planningBlock = findMobileBlockContaining(".planning-modal");
expect(planningBlock).toMatch(/\.planning-confirm-btn\s*\{[^}]*min-height:\s*36px;/s);
});
it("ModelSelection: combobox dropdown has mobile sizing", () => {
const modelBlock = findMobileBlockContaining(".model-combobox-dropdown");
expect(modelBlock).toMatch(/\.model-combobox-dropdown\s*\{[^}]*max-height:\s*50vh;[^}]*width:\s*min\(360px,\s*calc\(100vw - 32px\)\);/s);
});
it("ModelSelection: combobox search input is 16px on mobile", () => {
const modelBlock = findMobileBlockContaining(".model-combobox-search");
expect(modelBlock).toMatch(/\.model-combobox-search\s*\{[^}]*font-size:\s*16px;/s);
});
it("All planning modals: textareas are 16px to prevent iOS zoom", () => {
const planningBlock = findMobileBlockContaining(".planning-modal");
const hasPlanningTextarea = /\.planning-textarea\s*\{[^}]*font-size:\s*16px;/s.test(planningBlock);
const hasPlanningSummaryForm = /\.planning-summary-form[\s\S]*font-size:\s*16px;/s.test(planningBlock);
expect(hasPlanningTextarea || hasPlanningSummaryForm).toBe(true);
});
it("Planning modal: footer actions stack vertically on mobile", () => {
const planningBlock = findMobileBlockContaining(".planning-modal");
expect(planningBlock).toMatch(/\.planning-actions\s*\{[^}]*flex-direction:\s*column;/s);
});
it("ModelSelection: combobox options meet touch targets", () => {
const modelBlock = findMobileBlockContaining(".model-combobox-option");
expect(modelBlock).toMatch(/\.model-combobox-option\s*\{[^}]*min-height:\s*36px;/s);
});
});