feat(FN-4047): add responsive compact timestamps and mobile icon-only contr
Restored compact timestamp rendering in AgentDetailView and TaskDetailModal, added responsive timestamp layout for mobile and desktop, and added mobile icon-only stop/run controls to AgentDetailView, with new tests covering both components and a documentation note for the compact timestamp behavior. Fusion-Task-Id: FN-4047
This commit is contained in:
@@ -1221,6 +1221,22 @@ describe("AgentDetailView", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps active header Stop and Run Now buttons accessible by name while using mobile icon-control class", async () => {
|
||||
render(
|
||||
<AgentDetailView
|
||||
agentId="agent-001"
|
||||
onClose={vi.fn()}
|
||||
addToast={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
const stopButton = await screen.findByRole("button", { name: "Stop" });
|
||||
const runNowButton = await screen.findByRole("button", { name: "Run now for Test Agent" });
|
||||
|
||||
expect(stopButton.className).toContain("agent-detail-mobile-icon-control");
|
||||
expect(runNowButton.className).toContain("agent-detail-mobile-icon-control");
|
||||
});
|
||||
|
||||
it("transitions running agent to paused when Stop is clicked", async () => {
|
||||
mockFetchAgent.mockResolvedValue(createMockAgent({ state: "running" }));
|
||||
|
||||
@@ -1312,6 +1328,7 @@ describe("AgentDetailView", () => {
|
||||
expect(stylesContent).toContain("grid-column: 1;");
|
||||
expect(stylesContent).toContain(".agent-detail-header-actions {");
|
||||
expect(stylesContent).toContain("grid-column: 2;");
|
||||
expect(stylesContent).toContain(".agent-detail-controls .agent-detail-mobile-icon-control {");
|
||||
expect(stylesContent).toContain(".agent-detail-mobile-icon-control .agent-detail-control-label {");
|
||||
});
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
import { render, screen, fireEvent, act, waitFor } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import {
|
||||
@@ -162,6 +162,65 @@ describe("TaskDetailModal", () => {
|
||||
expect(timestamps).toBeTruthy();
|
||||
expect(provenance?.compareDocumentPosition(timestamps as Node) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
|
||||
});
|
||||
|
||||
describe("compact timestamp metadata", () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(new Date("2026-05-11T12:00:00.000Z"));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it("renders compact relative timestamps for recent tasks", () => {
|
||||
render(
|
||||
<TaskDetailModal
|
||||
task={makeTask({
|
||||
sourceType: "dashboard_ui",
|
||||
createdAt: "2026-05-09T12:00:00.000Z",
|
||||
updatedAt: "2026-05-11T09:00:00.000Z",
|
||||
})}
|
||||
onClose={noop}
|
||||
onMoveTask={noopMove}
|
||||
onDeleteTask={noopDelete}
|
||||
onMergeTask={noopMerge}
|
||||
onOpenDetail={noopOpenDetail}
|
||||
addToast={noop}
|
||||
/>,
|
||||
);
|
||||
|
||||
const timestamps = screen.getByLabelText("Task timestamps");
|
||||
expect(timestamps).toHaveTextContent("Created 2d ago");
|
||||
expect(timestamps).toHaveTextContent("Updated 3h ago");
|
||||
|
||||
const times = timestamps.querySelectorAll("time");
|
||||
expect(times[0]?.getAttribute("dateTime")).toBe("2026-05-09T12:00:00.000Z");
|
||||
expect(times[1]?.getAttribute("dateTime")).toBe("2026-05-11T09:00:00.000Z");
|
||||
});
|
||||
|
||||
it("renders short calendar date for older timestamps", () => {
|
||||
render(
|
||||
<TaskDetailModal
|
||||
task={makeTask({
|
||||
sourceType: "dashboard_ui",
|
||||
createdAt: "2026-05-01T12:00:00.000Z",
|
||||
updatedAt: "2026-05-02T12:00:00.000Z",
|
||||
})}
|
||||
onClose={noop}
|
||||
onMoveTask={noopMove}
|
||||
onDeleteTask={noopDelete}
|
||||
onMergeTask={noopMerge}
|
||||
onOpenDetail={noopOpenDetail}
|
||||
addToast={noop}
|
||||
/>,
|
||||
);
|
||||
|
||||
const timestamps = screen.getByLabelText("Task timestamps");
|
||||
expect(timestamps).toHaveTextContent("Created May 1");
|
||||
expect(timestamps).toHaveTextContent("Updated May 2");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("renders modal wrapper structure and default close control", () => {
|
||||
|
||||
@@ -27,6 +27,17 @@ describe("TaskDetailModal", () => {
|
||||
expectBaseRule(css, ".detail-meta-inline-controls", "flex-wrap: nowrap;");
|
||||
expect(css).not.toMatch(/@media \(max-width: 640px\)\s*\{[^}]*\.detail-meta-inline-controls\s*\{[^}]*flex-direction:\s*column;/);
|
||||
});
|
||||
|
||||
it("uses grouped timestamp metadata on desktop with mobile stacked overrides", () => {
|
||||
const css = readDashboardStylesSource();
|
||||
|
||||
expectBaseRule(css, ".detail-timestamps", "display: inline-flex;");
|
||||
expectBaseRule(css, ".detail-timestamps", "flex-wrap: wrap;");
|
||||
expectBaseRule(css, ".detail-timestamp-item", "display: inline-flex;");
|
||||
|
||||
expect(css).toMatch(/@media \(max-width: 768px\)\s*\{\s*\.detail-provenance\s*\{[^}]*\}\s*\.detail-timestamps\s*\{[^}]*flex-direction:\s*column;/);
|
||||
expect(css).toMatch(/@media \(max-width: 768px\)\s*\{[\s\S]*?\.detail-timestamp-separator\s*\{[^}]*display:\s*none;/);
|
||||
});
|
||||
it("renders responsive structural classes (modal-lg, overlay, spacer, tabs, detail-body)", () => {
|
||||
const { container } = render(
|
||||
<TaskDetailModal
|
||||
@@ -43,6 +54,8 @@ describe("TaskDetailModal", () => {
|
||||
expect(container.querySelector(".modal-overlay.open")).toBeTruthy();
|
||||
expect(container.querySelector(".modal-actions .modal-actions-spacer")).toBeTruthy();
|
||||
expect(container.querySelector(".detail-body")).toBeTruthy();
|
||||
expect(container.querySelector(".detail-timestamps")).toBeTruthy();
|
||||
expect(container.querySelectorAll(".detail-timestamp-item").length).toBe(2);
|
||||
const tabs = container.querySelectorAll(".detail-tab");
|
||||
expect(tabs.length).toBe(10);
|
||||
expect(tabs[0].classList.contains("detail-tab-active")).toBe(true);
|
||||
|
||||
Reference in New Issue
Block a user