feat(FN-786): move Settings to last position in overflow menu
- Reorder Header overflow menu items so Settings always appears last - Update Header.tsx to position SettingsMenuItem as the final menu entry - Add regression tests in Header.test.tsx verifying Settings is last in all overflow states - Add ordering tests in tablet-header-controls.test.tsx for tablet layout
This commit is contained in:
@@ -370,4 +370,40 @@ describe("tablet header controls", () => {
|
|||||||
expect(screen.getByTestId("project-selector-trigger")).toBeDefined();
|
expect(screen.getByTestId("project-selector-trigger")).toBeDefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ── Settings is the last overflow menu item ────────────────────
|
||||||
|
|
||||||
|
describe("overflow menu ordering on tablet", () => {
|
||||||
|
it("Settings is the last item in the tablet overflow menu when all optional items are present", () => {
|
||||||
|
const { container } = renderTabletHeader({
|
||||||
|
onOpenUsage: noop,
|
||||||
|
onOpenActivityLog: noop,
|
||||||
|
onOpenWorkflowSteps: noop,
|
||||||
|
onOpenMissions: noop,
|
||||||
|
onOpenFiles: noop,
|
||||||
|
onOpenGitManager: noop,
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByTitle("More header actions"));
|
||||||
|
|
||||||
|
// Get all menu items inside the overflow menu
|
||||||
|
const menu = container.querySelector(".mobile-overflow-menu")!;
|
||||||
|
const menuItems = Array.from(menu.querySelectorAll<HTMLButtonElement>("button.mobile-overflow-item"));
|
||||||
|
|
||||||
|
// The last menu item should be Settings
|
||||||
|
const lastItem = menuItems[menuItems.length - 1];
|
||||||
|
expect(lastItem.textContent).toBe("Settings");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Settings is the last item in the tablet overflow menu when optional items are absent", () => {
|
||||||
|
renderTabletHeader();
|
||||||
|
fireEvent.click(screen.getByTitle("More header actions"));
|
||||||
|
|
||||||
|
const menu = screen.getByRole("menu");
|
||||||
|
const menuItems = Array.from(menu.querySelectorAll<HTMLButtonElement>("button[role='menuitem']"));
|
||||||
|
|
||||||
|
const lastItem = menuItems[menuItems.length - 1];
|
||||||
|
expect(lastItem.textContent).toBe("Settings");
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -544,4 +544,82 @@ describe("Header", () => {
|
|||||||
// The "tasks" element no longer exists - it was removed
|
// The "tasks" element no longer exists - it was removed
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("action ordering", () => {
|
||||||
|
it("Settings is the last inline user-facing action on desktop (before pause/stop)", () => {
|
||||||
|
const { container } = renderHeader({
|
||||||
|
onOpenUsage: noop,
|
||||||
|
onOpenActivityLog: noop,
|
||||||
|
onOpenWorkflowSteps: noop,
|
||||||
|
onOpenMissions: noop,
|
||||||
|
onOpenFiles: noop,
|
||||||
|
onOpenGitManager: noop,
|
||||||
|
onOpenScripts: noop,
|
||||||
|
onRunScript: noop,
|
||||||
|
}, "desktop");
|
||||||
|
|
||||||
|
// Get all inline btn-icon buttons inside header-actions
|
||||||
|
const headerActions = container.querySelector(".header-actions")!;
|
||||||
|
const inlineButtons = Array.from(headerActions.querySelectorAll<HTMLButtonElement>(":scope > button.btn-icon"));
|
||||||
|
|
||||||
|
// Find the Settings button index and the Pause/Stop button indices
|
||||||
|
const settingsIdx = inlineButtons.findIndex((btn) => btn.title === "Settings");
|
||||||
|
const pauseIdx = inlineButtons.findIndex((btn) => btn.title === "Pause scheduling" || btn.title === "Resume scheduling");
|
||||||
|
const stopIdx = inlineButtons.findIndex((btn) => btn.title === "Stop AI engine" || btn.title === "Start AI engine");
|
||||||
|
|
||||||
|
// Settings must exist
|
||||||
|
expect(settingsIdx).toBeGreaterThanOrEqual(0);
|
||||||
|
|
||||||
|
// Settings must come before pause/stop (engine controls come after Settings)
|
||||||
|
if (pauseIdx >= 0) {
|
||||||
|
expect(settingsIdx).toBeLessThan(pauseIdx);
|
||||||
|
}
|
||||||
|
if (stopIdx >= 0) {
|
||||||
|
expect(settingsIdx).toBeLessThan(stopIdx);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Settings must be the last button before pause/stop — no other user-facing btn-icon after it
|
||||||
|
const buttonsAfterSettings = inlineButtons.slice(settingsIdx + 1);
|
||||||
|
const userFacingAfterSettings = buttonsAfterSettings.filter(
|
||||||
|
(btn) => btn.title !== "Pause scheduling" &&
|
||||||
|
btn.title !== "Resume scheduling" &&
|
||||||
|
btn.title !== "Stop AI engine" &&
|
||||||
|
btn.title !== "Start AI engine"
|
||||||
|
);
|
||||||
|
expect(userFacingAfterSettings).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Settings is the last item in the mobile overflow menu", () => {
|
||||||
|
const { container } = renderHeader({
|
||||||
|
onOpenUsage: noop,
|
||||||
|
onOpenActivityLog: noop,
|
||||||
|
onOpenWorkflowSteps: noop,
|
||||||
|
onOpenMissions: noop,
|
||||||
|
onOpenFiles: noop,
|
||||||
|
onOpenGitManager: noop,
|
||||||
|
}, "mobile");
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByTitle("More header actions"));
|
||||||
|
|
||||||
|
// Get all menu items inside the overflow menu
|
||||||
|
const menu = container.querySelector(".mobile-overflow-menu")!;
|
||||||
|
const menuItems = Array.from(menu.querySelectorAll<HTMLButtonElement>("button.mobile-overflow-item"));
|
||||||
|
|
||||||
|
// The last menu item should be Settings
|
||||||
|
const lastItem = menuItems[menuItems.length - 1];
|
||||||
|
expect(lastItem.textContent).toBe("Settings");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Settings is the last item in the mobile overflow menu even when optional items are absent", () => {
|
||||||
|
renderHeader({}, "mobile");
|
||||||
|
fireEvent.click(screen.getByTitle("More header actions"));
|
||||||
|
|
||||||
|
// Get the overflow menu items
|
||||||
|
const menu = screen.getByRole("menu");
|
||||||
|
const menuItems = Array.from(menu.querySelectorAll<HTMLButtonElement>("button[role='menuitem']"));
|
||||||
|
|
||||||
|
const lastItem = menuItems[menuItems.length - 1];
|
||||||
|
expect(lastItem.textContent).toBe("Settings");
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -564,14 +564,6 @@ export function Header({
|
|||||||
<Clock size={16} />
|
<Clock size={16} />
|
||||||
<span>Scheduled Tasks</span>
|
<span>Scheduled Tasks</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
|
||||||
className="mobile-overflow-item"
|
|
||||||
onClick={() => handleOverflowAction(onOpenSettings)}
|
|
||||||
role="menuitem"
|
|
||||||
>
|
|
||||||
<Settings size={16} />
|
|
||||||
<span>Settings</span>
|
|
||||||
</button>
|
|
||||||
{/* Activity Log - in overflow on mobile */}
|
{/* Activity Log - in overflow on mobile */}
|
||||||
{onOpenActivityLog && (
|
{onOpenActivityLog && (
|
||||||
<button
|
<button
|
||||||
@@ -620,6 +612,15 @@ export function Header({
|
|||||||
<span>Missions</span>
|
<span>Missions</span>
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
{/* Settings - always last in overflow menu */}
|
||||||
|
<button
|
||||||
|
className="mobile-overflow-item"
|
||||||
|
onClick={() => handleOverflowAction(onOpenSettings)}
|
||||||
|
role="menuitem"
|
||||||
|
>
|
||||||
|
<Settings size={16} />
|
||||||
|
<span>Settings</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user