Files
fusion/packages/dashboard/app/components/command-center/__tests__/SystemControlsArea.test.tsx
gsxdsm ed2ffc4885 FN-8346: preserve manual scroll position in live logs
Preserve the sticky-bottom follow invariant across streamed Dev Server and System Controls output.

- Track pinned-bottom state synchronously for rebuild and system-log streams.
- Follow in-place Dev Server transcript mutations only while the reader is pinned.
- Cover manual unsnapping, stream growth, and re-pinning behavior.
- Add a patch changeset for live-log scrolling.

Files changed:
 .changeset/fn-8346-live-tail-scroll-follow.md      |  7 ++
 .../app/components/DevServerLogViewer.tsx          | 43 ++++++++++--
 .../__tests__/DevServerLogViewer.test.tsx          | 47 +++++++++++++
 .../__tests__/SystemControlsArea.test.tsx          | 81 +++++++++++++++++++++-
 .../command-center/areas/SystemControlsArea.tsx    | 44 ++++++++++--
 5 files changed, 208 insertions(+), 14 deletions(-)

Fusion-Task-Id: FN-8346

Fusion-Task-Lineage: c1eeb84d-c0af-473b-99f1-0d8564ac8a24

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-18 18:04:26 -07:00

515 lines
22 KiB
TypeScript

import { readFileSync } from "node:fs";
import { join } from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { act, fireEvent, render, screen, waitFor, within } from "@testing-library/react";
import "@testing-library/jest-dom";
import { CommandCenter } from "../CommandCenter";
import { BUG_URL_MAX_ENCODED } from "../areas/SystemControlsArea";
const apiMock = vi.fn();
const mockFetchSystemInfo = vi.fn();
const mockFetchCurrentSystemRebuild = vi.fn();
const mockFetchSystemStats = vi.fn();
const mockFetchSystemLogs = vi.fn();
const mockFetchNodeSystemStats = vi.fn();
const mockFetchGlobalSettings = vi.fn();
const mockFetchNodes = vi.fn();
const subscribeSseMock = vi.fn(() => () => undefined);
vi.mock("../../../api/legacy", () => ({
fetchCodebaseMetrics: vi.fn().mockResolvedValue({ tokenEstimate: 0, sourceFileCount: 0, sourceByteCount: 0, diskBytes: 0, diskFileCount: 0, method: "local", truncated: false }),
api: (path: string, opts?: RequestInit) => apiMock(path, opts),
withProjectId: (path: string, projectId?: string) =>
projectId ? `${path}${path.includes("?") ? "&" : "?"}projectId=${encodeURIComponent(projectId)}` : path,
fetchOrgTree: vi.fn().mockResolvedValue([]),
fetchExecutorStats: vi.fn().mockResolvedValue({ globalPause: false, enginePaused: false, maxConcurrent: 2 }),
fetchSettings: vi.fn().mockResolvedValue({ maxConcurrent: 2, maxTriageConcurrent: 1, maxWorktrees: 5 }),
fetchConfig: vi.fn().mockResolvedValue({ maxConcurrent: 2, rootDir: "/" }),
updateSettings: vi.fn().mockResolvedValue({}),
createBackup: vi.fn().mockResolvedValue({ ok: true }),
fetchDashboardHealth: vi.fn().mockResolvedValue({ ok: true }),
fetchCurrentSystemRebuild: (...args: unknown[]) => mockFetchCurrentSystemRebuild(...args),
fetchSystemInfo: (...args: unknown[]) => mockFetchSystemInfo(...args),
fetchSystemLogs: (...args: unknown[]) => mockFetchSystemLogs(...args),
reloadAllSystemPlugins: vi.fn().mockResolvedValue({ ok: true }),
requestSystemRestart: vi.fn().mockResolvedValue({ ok: true }),
restartAllSystemAgents: vi.fn().mockResolvedValue({ ok: true }),
restartSystemEngines: vi.fn().mockResolvedValue({ ok: true }),
startSystemRebuild: vi.fn().mockResolvedValue({ id: "job-1", status: "running", kind: "rebuild", scope: "app", lines: [] }),
startFnBinaryLinkLocal: vi.fn().mockResolvedValue({
id: "job-fn-local",
status: "running",
kind: "fn-binary",
scope: "link-local",
lines: [],
}),
startFnBinaryUseGlobal: vi.fn().mockResolvedValue({
id: "job-fn-global",
status: "running",
kind: "fn-binary",
scope: "use-global",
lines: [],
}),
refreshUpdateCheck: vi.fn().mockResolvedValue({
currentVersion: "0.60.0",
latestVersion: "0.60.0",
updateAvailable: false,
}),
}));
vi.mock("../../../api", () => ({
fetchSystemStats: (...args: unknown[]) => mockFetchSystemStats(...args),
fetchNodeSystemStats: (...args: unknown[]) => mockFetchNodeSystemStats(...args),
fetchGlobalSettings: (...args: unknown[]) => mockFetchGlobalSettings(...args),
fetchNodes: (...args: unknown[]) => mockFetchNodes(...args),
killVitestProcesses: vi.fn().mockResolvedValue({ killed: 0, pids: [] }),
updateGlobalSettings: vi.fn().mockResolvedValue({}),
}));
vi.mock("../../../sse-bus", () => ({
subscribeSse: (...args: unknown[]) => subscribeSseMock(...args),
}));
vi.mock("../../../hooks/useAppSettings", () => ({
useAppSettings: () => ({
globalPaused: false,
enginePaused: false,
toggleGlobalPause: vi.fn(),
toggleEnginePause: vi.fn(),
refresh: vi.fn().mockResolvedValue(undefined),
}),
}));
function emptyOverviewResponse(path: string) {
if (path.includes("/command-center/tokens")) {
return { totals: { totalTokens: 0, inputTokens: 0, outputTokens: 0, cachedTokens: 0, cacheWriteTokens: 0, nTasks: 0 }, cost: { usd: null, unavailable: true, stale: false }, groups: [] };
}
if (path.includes("/command-center/tools")) return { totals: { calls: 0, errors: 0 }, groups: [] };
if (path.includes("/command-center/activity")) return { totals: { agentRuns: 0, tasksDone: 0 }, daily: [] };
if (path.includes("/command-center/signals")) return { totals: { open: 0, closed: 0 }, groups: [] };
if (path.includes("/command-center/live")) return { activeTasks: 0, activeAgents: 0, openSignals: 0, tokensPerMinute: 0, tasksByColumn: [] };
return {};
}
function systemInfoFixture(overrides: Record<string, unknown> = {}) {
return {
pid: 12345,
nodeVersion: "v22.0.0",
platform: "darwin",
arch: "arm64",
sourceCheckout: true,
supervised: true,
restartSupported: true,
rebuildSupported: true,
fnBinaryLinkLocalSupported: true,
fnBinaryUseGlobalSupported: true,
engineAvailable: true,
engineRestartSupported: true,
agentRestartSupported: true,
pluginReloadSupported: true,
logsSupported: true,
activeRebuild: null,
...overrides,
};
}
function systemStatsFixture() {
return {
systemStats: {
rss: 1024,
heapUsed: 512,
heapTotal: 1024,
heapLimit: 2048,
external: 0,
arrayBuffers: 0,
cpuPercent: 10,
loadAvg: [0.1, 0.2, 0.3] as [number, number, number],
cpuCount: 8,
systemTotalMem: 4096,
systemFreeMem: 2048,
pid: 12345,
nodeVersion: "v22.0.0",
platform: "darwin/arm64",
},
taskStats: {
total: 0,
byColumn: {},
active: 0,
agents: { idle: 0, active: 0, running: 0, error: 0 },
},
vitestProcessCount: 0,
vitestLastAutoKillAt: null,
};
}
describe("SystemControlsArea layout integration", () => {
const originalClipboard = navigator.clipboard;
const originalExecCommand = document.execCommand;
function mockClipboard(value: unknown) {
Object.defineProperty(navigator, "clipboard", {
configurable: true,
value,
});
}
function mockExecCommand(result: boolean) {
const execCommand = vi.fn().mockReturnValue(result);
Object.defineProperty(document, "execCommand", {
configurable: true,
value: execCommand,
});
return execCommand;
}
async function renderSystemTab(addToast = vi.fn()) {
render(<CommandCenter projectId="proj-1" addToast={addToast} />);
fireEvent.click(screen.getByTestId("command-center-tab-system"));
const diagnosticsCard = await screen.findByTestId("cc-syscontrol-diagnostics");
return { addToast, diagnosticsCard };
}
beforeEach(() => {
vi.clearAllMocks();
subscribeSseMock.mockImplementation(() => () => undefined);
apiMock.mockImplementation((path: string) => Promise.resolve(emptyOverviewResponse(path)));
mockFetchSystemInfo.mockResolvedValue(systemInfoFixture());
mockFetchSystemLogs.mockResolvedValue({
entries: [{ timestamp: "2026-07-12T00:00:00.000Z", level: "info", message: "ready" }],
});
mockFetchCurrentSystemRebuild.mockResolvedValue({ job: null });
mockFetchSystemStats.mockResolvedValue(systemStatsFixture());
mockFetchNodeSystemStats.mockResolvedValue(systemStatsFixture());
mockFetchGlobalSettings.mockResolvedValue({ vitestAutoKillEnabled: true, vitestKillThresholdPct: 90 });
mockFetchNodes.mockResolvedValue([]);
});
afterEach(() => {
Object.defineProperty(navigator, "clipboard", {
configurable: true,
value: originalClipboard,
});
Object.defineProperty(document, "execCommand", {
configurable: true,
value: originalExecCommand,
});
document.body.innerHTML = "";
});
it("wraps System controls, Server logs, and Live system health in the shared gap owner", async () => {
render(<CommandCenter projectId="proj-1" />);
fireEvent.click(screen.getByTestId("command-center-tab-system"));
const systemTab = await screen.findByTestId("cc-system-tab");
const controls = await screen.findByTestId("cc-system-controls");
const logs = await screen.findByTestId("cc-system-logs");
const stats = await screen.findByTestId("cc-area-system");
expect(systemTab).toHaveClass("cc-system-tab");
expect(systemTab).toContainElement(controls);
expect(systemTab).toContainElement(logs);
expect(systemTab).toContainElement(stats);
expect(controls.parentElement).toBe(systemTab);
expect(logs.parentElement).toBe(systemTab);
expect(stats.parentElement).toBe(systemTab);
expect(screen.getByTestId("cc-system-logs-toggle")).toBeInTheDocument();
await waitFor(() => expect(mockFetchSystemInfo).toHaveBeenCalled());
});
it("keeps the System controls refresh button in the scoped inline header", async () => {
render(<CommandCenter projectId="proj-1" />);
fireEvent.click(screen.getByTestId("command-center-tab-system"));
const controls = await screen.findByTestId("cc-system-controls");
const header = controls.querySelector<HTMLElement>(".cc-system-controls-header");
const title = screen.getByRole("heading", { name: "System controls" });
const refresh = screen.getByTestId("cc-system-refresh");
expect(header).toBeInTheDocument();
expect(header).toHaveClass("cc-area-section-header", "cc-system-controls-header");
expect(title.parentElement).toBe(header);
expect(refresh.parentElement).toBe(header);
});
it("copies diagnostics through the execCommand fallback when Clipboard API is unavailable", async () => {
mockClipboard(undefined);
const execCommand = mockExecCommand(true);
const { addToast, diagnosticsCard } = await renderSystemTab();
fireEvent.click(within(diagnosticsCard).getByRole("button", { name: "Copy" }));
await waitFor(() => expect(execCommand).toHaveBeenCalledWith("copy"));
expect(addToast).toHaveBeenCalledWith("Diagnostics copied to clipboard", "success");
expect(addToast).not.toHaveBeenCalledWith(expect.stringContaining("writeText"), "error");
});
it("copies diagnostics through navigator.clipboard.writeText in secure contexts", async () => {
const writeText = vi.fn().mockResolvedValue(undefined);
mockClipboard({ writeText });
const { addToast, diagnosticsCard } = await renderSystemTab();
fireEvent.click(within(diagnosticsCard).getByRole("button", { name: "Copy" }));
await waitFor(() => expect(writeText).toHaveBeenCalledTimes(1));
expect(writeText.mock.calls[0]?.[0]).toContain('"recentLogs"');
expect(addToast).toHaveBeenCalledWith("Diagnostics copied to clipboard", "success");
expect(addToast).not.toHaveBeenCalledWith(expect.stringContaining("writeText"), "error");
});
it("shows a failure toast when diagnostics cannot be copied by either clipboard path", async () => {
mockFetchSystemInfo.mockResolvedValue({ ...systemInfoFixture(), logsSupported: false });
mockClipboard(undefined);
const execCommand = mockExecCommand(false);
const { addToast, diagnosticsCard } = await renderSystemTab();
fireEvent.click(within(diagnosticsCard).getByRole("button", { name: "Copy" }));
await waitFor(() => expect(execCommand).toHaveBeenCalledWith("copy"));
expect(addToast).toHaveBeenCalledWith("Could not copy diagnostics to clipboard", "error");
expect(addToast).not.toHaveBeenCalledWith("Diagnostics copied to clipboard", "success");
expect(addToast).not.toHaveBeenCalledWith(expect.stringContaining("writeText"), "error");
});
it("embeds the full diagnostics bundle in the bug report body when the operator confirms", async () => {
mockFetchSystemLogs.mockResolvedValue({
entries: [{ timestamp: "2026-07-12T00:00:00.000Z", level: "error", message: "boom" }],
});
const confirmSpy = vi.spyOn(window, "confirm").mockReturnValue(true);
const openSpy = vi.spyOn(window, "open").mockReturnValue(null);
await renderSystemTab();
const reportCard = await screen.findByTestId("cc-syscontrol-report-bug");
fireEvent.click(within(reportCard).getByRole("button", { name: "Report" }));
await waitFor(() => expect(openSpy).toHaveBeenCalledTimes(1));
expect(confirmSpy).toHaveBeenCalledTimes(1);
const url = openSpy.mock.calls[0]?.[0] as string;
const body = decodeURIComponent(url.split("?body=")[1] ?? "");
expect(url).toContain("github.com");
expect(url).toContain("/issues/new");
expect(body).toContain("### What happened");
expect(body).toContain("### Environment");
expect(body).toContain("### Diagnostics");
expect(body).toContain("<details><summary>Diagnostics</summary>");
expect(body).toContain('"recentLogs"');
expect(body).toContain("boom");
expect(body).not.toContain("\u2026(truncated)");
expect(url.length).toBeLessThanOrEqual(BUG_URL_MAX_ENCODED);
confirmSpy.mockRestore();
openSpy.mockRestore();
});
it("omits the diagnostics block from the bug report body when the operator declines", async () => {
const confirmSpy = vi.spyOn(window, "confirm").mockReturnValue(false);
const openSpy = vi.spyOn(window, "open").mockReturnValue(null);
await renderSystemTab();
const reportCard = await screen.findByTestId("cc-syscontrol-report-bug");
fireEvent.click(within(reportCard).getByRole("button", { name: "Report" }));
await waitFor(() => expect(openSpy).toHaveBeenCalledTimes(1));
const url = openSpy.mock.calls[0]?.[0] as string;
const body = decodeURIComponent(url.split("?body=")[1] ?? "");
expect(body).toContain("### What happened");
expect(body).toContain("### Environment");
expect(body).not.toContain("### Diagnostics");
confirmSpy.mockRestore();
openSpy.mockRestore();
});
it("neutralizes embedded code fences in log messages so they cannot break out of the diagnostics fence", async () => {
mockFetchSystemLogs.mockResolvedValue({
entries: [{ timestamp: "2026-07-12T00:00:00.000Z", level: "error", message: "```oops``` breakout" }],
});
const confirmSpy = vi.spyOn(window, "confirm").mockReturnValue(true);
const openSpy = vi.spyOn(window, "open").mockReturnValue(null);
await renderSystemTab();
const reportCard = await screen.findByTestId("cc-syscontrol-report-bug");
fireEvent.click(within(reportCard).getByRole("button", { name: "Report" }));
await waitFor(() => expect(openSpy).toHaveBeenCalledTimes(1));
const url = openSpy.mock.calls[0]?.[0] as string;
const body = decodeURIComponent(url.split("?body=")[1] ?? "");
// The fenced JSON block should contain no raw ``` other than the fence delimiters themselves.
const jsonBlockMatch = body.match(/```json\n([\s\S]*?)\n```/);
expect(jsonBlockMatch).not.toBeNull();
expect(jsonBlockMatch?.[1]).not.toContain("```");
expect(jsonBlockMatch?.[1]).toContain("'''oops''' breakout");
confirmSpy.mockRestore();
openSpy.mockRestore();
});
it("truncates an oversized diagnostics bundle against the encoded GitHub URL ceiling", async () => {
mockFetchSystemLogs.mockResolvedValue({
entries: Array.from({ length: 100 }, (_, i) => ({
timestamp: "2026-07-12T00:00:00.000Z",
level: "error" as const,
message: `error-line-${i}-{\"quoted\":\"${"x".repeat(100)}\",\"nested\":{\"value\":\"${"y".repeat(100)}\"}}`,
})),
});
const confirmSpy = vi.spyOn(window, "confirm").mockReturnValue(true);
const openSpy = vi.spyOn(window, "open").mockReturnValue(null);
await renderSystemTab();
const reportCard = await screen.findByTestId("cc-syscontrol-report-bug");
fireEvent.click(within(reportCard).getByRole("button", { name: "Report" }));
await waitFor(() => expect(openSpy).toHaveBeenCalledTimes(1));
const url = openSpy.mock.calls[0]?.[0] as string;
const body = decodeURIComponent(url.split("?body=")[1] ?? "");
expect(url.length).toBeLessThanOrEqual(BUG_URL_MAX_ENCODED);
expect(body).toContain("\u2026(truncated)");
confirmSpy.mockRestore();
openSpy.mockRestore();
});
it("keeps the System controls header row override active on mobile", () => {
const css = readFileSync(join(process.cwd(), "app/components/command-center/areas/SystemControlsArea.css"), "utf8");
expect(css).toMatch(/\.cc-area-section-header\.cc-system-controls-header\s*{[^}]*flex-direction:\s*row;[^}]*justify-content:\s*space-between;/s);
expect(css).toMatch(/@media\s*\(max-width:\s*768px\)\s*{[\s\S]*\.cc-area-section-header\.cc-system-controls-header\s*{[^}]*flex-direction:\s*row;[^}]*justify-content:\s*space-between;/);
});
it("keeps the System tab gap and mobile scroll-owner CSS contracts tokenized", () => {
const css = readFileSync(join(process.cwd(), "app/components/command-center/CommandCenter.css"), "utf8");
expect(css).toMatch(/\.cc-system-tab\s*{[^}]*display:\s*flex;[^}]*flex-direction:\s*column;[^}]*gap:\s*var\(--space-lg\);/s);
expect(css).toMatch(/@media\s*\(max-width:\s*768px\)\s*{[\s\S]*\.cc-tabpanel\s*{[^}]*padding-bottom:\s*calc\(var\(--space-lg\) \+ env\(safe-area-inset-bottom, 0\) \+ var\(--standalone-bottom-gap\)\);/);
expect(css).toMatch(/@media\s*\(max-width:\s*768px\)\s*{[\s\S]*\.cc-system-tab\s*{[^}]*gap:\s*var\(--space-lg\);/);
expect(css).not.toMatch(/\.cc-system-tab\s*{[^}]*overflow-y:\s*auto;/s);
});
/*
FNXC:SystemPanelFnBinary 2026-07-15-09:54:
Source/dev hosts expose build-and-link-local; packaged hosts hide it. Use-global
and check-for-updates stay available, and starting a build job surfaces the
shared log viewer.
*/
it("shows fn binary and update controls for a source checkout and scrolls job output into view", async () => {
const scrollIntoView = vi.fn();
const original = Element.prototype.scrollIntoView;
Element.prototype.scrollIntoView = scrollIntoView;
render(<CommandCenter projectId="proj-1" />);
fireEvent.click(screen.getByTestId("command-center-tab-system"));
const linkLocal = await screen.findByTestId("cc-syscontrol-fn-link-local");
const useGlobal = screen.getByTestId("cc-syscontrol-fn-use-global");
const checkUpdates = screen.getByTestId("cc-syscontrol-check-updates");
expect(linkLocal).toBeInTheDocument();
expect(useGlobal).toBeInTheDocument();
expect(checkUpdates).toBeInTheDocument();
fireEvent.click(within(linkLocal).getByRole("button", { name: "Build & link" }));
await waitFor(() => {
expect(screen.getByTestId("cc-system-rebuild-output")).toBeInTheDocument();
});
await waitFor(() => {
expect(scrollIntoView).toHaveBeenCalled();
});
Element.prototype.scrollIntoView = original;
});
function installScrollGeometry(element: HTMLElement, scrollHeight = 500, clientHeight = 100) {
let scrollTop = 0;
Object.defineProperties(element, {
scrollHeight: { configurable: true, get: () => scrollHeight },
clientHeight: { configurable: true, get: () => clientHeight },
scrollTop: {
configurable: true,
get: () => scrollTop,
set: (value: number) => {
scrollTop = value;
},
},
});
return {
get scrollTop() { return scrollTop; },
set scrollTop(value: number) { scrollTop = value; },
setScrollHeight(value: number) { scrollHeight = value; },
};
}
function getStreamEvents(path: string) {
const call = [...subscribeSseMock.mock.calls].reverse().find(([url]) => url === path);
expect(call).toBeDefined();
return (call?.[1] as { events: Record<string, (event: MessageEvent) => void> }).events;
}
it("keeps manually scrolled rebuild output in place while SSE lines grow", async () => {
render(<CommandCenter projectId="proj-1" />);
fireEvent.click(screen.getByTestId("command-center-tab-system"));
const rebuild = await screen.findByTestId("cc-syscontrol-rebuild-app");
fireEvent.click(within(rebuild).getByRole("button", { name: "Rebuild" }));
const output = (await screen.findByTestId("cc-system-rebuild-output")).querySelector("pre")!;
const geometry = installScrollGeometry(output);
const events = getStreamEvents("/api/system/jobs/job-1/stream");
await act(async () => events.line(new MessageEvent("message", { data: JSON.stringify({ i: 1, stream: "stdout", text: "first" }) })));
geometry.scrollTop = 100;
fireEvent.scroll(output);
geometry.setScrollHeight(600);
await act(async () => events.line(new MessageEvent("message", { data: JSON.stringify({ i: 2, stream: "stdout", text: "second" }) })));
expect(geometry.scrollTop).toBe(100);
geometry.scrollTop = 500;
fireEvent.scroll(output);
geometry.setScrollHeight(700);
await act(async () => events.line(new MessageEvent("message", { data: JSON.stringify({ i: 3, stream: "stdout", text: "third" }) })));
expect(geometry.scrollTop).toBe(700);
});
it("keeps manually scrolled live server logs in place while SSE lines grow", async () => {
render(<CommandCenter projectId="proj-1" />);
fireEvent.click(screen.getByTestId("command-center-tab-system"));
await screen.findByTestId("cc-system-controls");
fireEvent.click(screen.getByTestId("cc-system-logs-toggle"));
const output = await screen.findByText("No log entries yet.");
const container = output.parentElement!;
const geometry = installScrollGeometry(container);
const events = getStreamEvents("/api/system/logs/stream");
await act(async () => events.log(new MessageEvent("message", { data: JSON.stringify({ timestamp: "2026-07-18T00:00:00.000Z", level: "info", message: "first" }) })));
geometry.scrollTop = 100;
fireEvent.scroll(container);
geometry.setScrollHeight(600);
await act(async () => events.log(new MessageEvent("message", { data: JSON.stringify({ timestamp: "2026-07-18T00:00:01.000Z", level: "info", message: "second" }) })));
expect(geometry.scrollTop).toBe(100);
geometry.scrollTop = 500;
fireEvent.scroll(container);
geometry.setScrollHeight(700);
await act(async () => events.log(new MessageEvent("message", { data: JSON.stringify({ timestamp: "2026-07-18T00:00:02.000Z", level: "info", message: "third" }) })));
expect(geometry.scrollTop).toBe(700);
});
it("hides build-and-link-local when the host is not a source checkout", async () => {
mockFetchSystemInfo.mockResolvedValue(
systemInfoFixture({
rebuildSupported: false,
fnBinaryLinkLocalSupported: false,
sourceWorkspaceRoot: undefined,
}),
);
render(<CommandCenter projectId="proj-1" />);
fireEvent.click(screen.getByTestId("command-center-tab-system"));
await screen.findByTestId("cc-system-controls");
expect(screen.queryByTestId("cc-syscontrol-fn-link-local")).not.toBeInTheDocument();
expect(screen.getByTestId("cc-syscontrol-fn-use-global")).toBeInTheDocument();
expect(screen.getByTestId("cc-syscontrol-check-updates")).toBeInTheDocument();
});
});