FN-7848: keep GitHub star count visible in mobile Settings header

Restores the GitHub star count pill on mobile Settings, which had been hidden by a prior mobile CSS override, and updates related tests to match.

- Remove the <=768px `display:none` on `.settings-github-star-btn__count` in SettingsModal.css so the count stays visible; only the redundant "Star" label text stays collapsed on mobile to save space (relies on `.settings-modal-heading` min-width: 0 to truncate the title before the pill wraps)
- Add a changeset (`@runfusion/fusion` patch) documenting the fix
- Update settings-mobile.test.tsx with a new regression test asserting the star count stays visible and formatted (e.g. "1.2k") in both the modal and embedded SettingsView on mobile, plus a helper to scan mobile-only CSS media blocks for the absence of the display:none rule, and align existing mobile CSS assertions (section-heading padding, settings-navigation base rule) with current styles
- Update SettingsModal.models-auth.test.tsx version-label assertions ("Version 1.2.3" -> "v1.2.3") and seed `fusion:settings:show-advanced` in localStorage to match current UI

Files changed:
 .changeset/mobile-settings-star-count.md           |  7 +++
 .../dashboard/app/components/SettingsModal.css     |  9 ++-
 .../__tests__/SettingsModal.models-auth.test.tsx   | 12 ++--
 .../components/__tests__/settings-mobile.test.tsx  | 66 +++++++++++++++++++++-
 4 files changed, 82 insertions(+), 12 deletions(-)

Fusion-Task-Id: FN-7848

Fusion-Task-Lineage: 0bbd0e81-6edb-4e9a-884d-b48fcf41f202

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
gsxdsm
2026-07-11 23:18:49 -07:00
parent d99c04cded
commit b613a87e75
4 changed files with 82 additions and 12 deletions

View File

@@ -0,0 +1,7 @@
---
"@runfusion/fusion": patch
---
summary: Settings on mobile now keeps showing the GitHub star count.
category: fix
dev: Removed the ≤768px `display:none` on `.settings-github-star-btn__count` in SettingsModal.css (FN-7848).

View File

@@ -45,11 +45,10 @@
margin-right: var(--space-sm);
}
/* Keep the mobile title readable by reducing promotional header actions to their already-labeled icons. */
.settings-header-actions .settings-github-star-btn__count {
display: none;
}
/*
FNXC:Settings 2026-07-11-00:00:
FN-7848: Mobile Settings must still show the GitHub star count. FN-4375 kept the header in one row by collapsing promotional action labels; keep the count visible and rely on .settings-modal-heading min-width: 0 to truncate the title before the pill wraps. Only the redundant "Star" word stays collapsed to save horizontal room.
*/
.settings-header-actions .settings-github-star-btn__action,
.settings-header-actions .settings-header-discord-btn {
width: 40px;

View File

@@ -1,4 +1,4 @@
import { describe, it, expect, vi } from "vitest";
import { beforeEach, describe, it, expect, vi } from "vitest";
import type { ComponentProps } from "react";
import { render, screen, fireEvent, waitFor, within, act, cleanup } from "@testing-library/react";
import path from "path";
@@ -203,6 +203,10 @@ vi.mock("../FileBrowser", () => ({
describe("SettingsModal", () => {
installSettingsModalEnv();
beforeEach(() => {
localStorage.setItem("fusion:settings:show-advanced", "true");
});
describe("Project Models", () => {
it("saves opencode-go startup model sync toggle in global settings", async () => {
mockFetchModels.mockResolvedValue({
@@ -696,7 +700,7 @@ describe("SettingsModal", () => {
renderModal();
await waitForSettingsModalReady();
expect(await screen.findByText("Version 1.2.3")).toBeInTheDocument();
expect(await screen.findByText("v1.2.3")).toBeInTheDocument();
expect(mockFetchDashboardHealth).toHaveBeenCalledTimes(1);
expect(screen.getByRole("button", { name: "Check for updates" })).toBeInTheDocument();
@@ -864,9 +868,9 @@ describe("SettingsModal", () => {
await waitForSettingsModalReady();
const inlineButton = screen.getByRole("button", { name: "Check for updates" });
expect(within(inlineButton).getByText("Version 1.2.3")).toBeInTheDocument();
expect(within(inlineButton).getByText("v1.2.3")).toBeInTheDocument();
await settingsModalUser.click(within(inlineButton).getByText("Version 1.2.3"));
await settingsModalUser.click(within(inlineButton).getByText("v1.2.3"));
await waitFor(() => {
expect(mockCheckForUpdates).toHaveBeenCalledTimes(1);

View File

@@ -43,6 +43,7 @@ const defaultSettings = {
vi.mock("../../api", () => ({
fetchProjects: vi.fn(() => Promise.resolve([])),
fetchGitRemotes: vi.fn(() => Promise.resolve({ remotes: [] })),
fetchGitRemotesDetailed: vi.fn(() => Promise.resolve([])),
fetchGitBranches: vi.fn(() => Promise.resolve([])),
fetchSettings: vi.fn(() => Promise.resolve({ ...defaultSettings })),
fetchSettingsByScope: vi.fn(() => Promise.resolve({ global: { ...defaultSettings }, project: {} })),
@@ -179,6 +180,37 @@ function expectMobileRule(css: string, selector: string, declaration: string): v
expect(pattern.test(css)).toBe(true);
}
function getMobileMediaBlocks(css: string): string[] {
const blocks: string[] = [];
const mediaPattern = /@media[^{]*\(max-width:\s*768px\)[^{]*\{/g;
let match: RegExpExecArray | null;
while ((match = mediaPattern.exec(css)) !== null) {
let depth = 0;
let end = match.index;
for (; end < css.length; end += 1) {
if (css[end] === "{") depth += 1;
if (css[end] === "}") depth -= 1;
if (depth === 0 && end > match.index) {
end += 1;
break;
}
}
blocks.push(css.slice(match.index, end));
mediaPattern.lastIndex = end;
}
return blocks;
}
function expectNoMobileRule(css: string, selector: string, declaration: string): void {
const pattern = new RegExp(
`${escapeRegExp(selector)}\\s*\\{[^}]*${escapeRegExp(declaration)}`,
);
const offendingBlock = getMobileMediaBlocks(css).find((block) => pattern.test(block));
expect(offendingBlock).toBeUndefined();
}
function expectBaseRule(css: string, selector: string, declaration: string): void {
const pattern = new RegExp(
`${escapeRegExp(selector)}\\s*\\{[^}]*${escapeRegExp(declaration)}`,
@@ -189,6 +221,9 @@ function expectBaseRule(css: string, selector: string, declaration: string): voi
describe("SettingsModal mobile adaptations", () => {
beforeEach(() => {
vi.clearAllMocks();
localStorage.removeItem("fusion_github_star_count");
localStorage.removeItem("fusion:github-star-clicked");
localStorage.setItem("fusion:settings:show-advanced", "true");
mockSettingsViewport(false);
});
@@ -550,6 +585,32 @@ describe("SettingsModal mobile adaptations", () => {
expect(container.querySelectorAll(".notification-provider-card").length).toBeGreaterThan(1);
});
it("keeps the GitHub star count visible in the mobile Settings header", async () => {
const css = loadAllAppCss();
expectNoMobileRule(css, ".settings-header-actions .settings-github-star-btn__count", "display: none;");
expectBaseRule(css, ".settings-github-star-btn__count", "display: inline-flex;");
localStorage.setItem("fusion_github_star_count", JSON.stringify({ count: 1234, fetchedAt: Date.now() }));
mockSettingsViewport(true);
const modalRender = render(<SettingsModal onClose={vi.fn()} addToast={vi.fn()} />);
await waitFor(() => expect(fetchSettings).toHaveBeenCalled());
const modalCount = modalRender.container.querySelector(".settings-github-star-btn__count");
expect(modalCount).toBeTruthy();
expect(modalCount?.textContent).toBe("1.2k");
modalRender.unmount();
vi.mocked(fetchSettings).mockClear();
const embeddedRender = render(<SettingsView onClose={vi.fn()} addToast={vi.fn()} />);
await waitFor(() => expect(fetchSettings).toHaveBeenCalled());
const embeddedCount = embeddedRender.container.querySelector(".settings-github-star-btn__count");
expect(embeddedCount).toBeTruthy();
expect(embeddedCount?.textContent).toBe("1.2k");
embeddedRender.unmount();
localStorage.removeItem("fusion_github_star_count");
});
it("contains required mobile settings CSS overrides", () => {
const css = loadAllAppCss();
@@ -569,7 +630,7 @@ describe("SettingsModal mobile adaptations", () => {
expectMobileRule(css, ".settings-nav-item", "gap: var(--space-xs);");
expectMobileRule(css, ".settings-content", "padding: var(--space-sm) var(--space-sm) var(--space-md);");
expectMobileRule(css, ".settings-content textarea", "font-size: 16px;");
expectMobileRule(css, ".settings-section-heading", "padding: var(--space-md) var(--space-sm) var(--space-sm);");
expectMobileRule(css, ".settings-section-heading", "padding: var(--space-md) 0 var(--space-sm);");
expectMobileRule(css, ".settings-section-heading", "margin: 0 0 var(--space-sm);");
expectMobileRule(css, ".settings-scope-icon", "margin-right: 0;");
expectMobileRule(css, ".settings-scope-banner", "margin: 0 var(--space-sm) var(--space-xs);");
@@ -663,7 +724,7 @@ describe("SettingsModal mobile adaptations", () => {
it("styles settings scrollbar rules for sidebar and content", () => {
const css = loadAllAppCss();
expectBaseRule(css, ".settings-navigation", "border-right: var(--btn-border-width) solid var(--border);");
expectBaseRule(css, ".settings-navigation", "background: var(--surface);");
expectBaseRule(css, ".settings-search", "border-bottom: var(--btn-border-width) solid var(--border);");
expectBaseRule(css, ".settings-sidebar", "scrollbar-color: var(--border) transparent;");
expectBaseRule(css, ".settings-sidebar", "scrollbar-width: thin;");
@@ -707,7 +768,6 @@ describe("SettingsModal mobile adaptations", () => {
expect(hideToggle.getAttribute("aria-expanded")).toBe("true");
expect(hideToggle.closest(".settings-mobile-section-picker")).toBe(picker);
expect(document.getElementById("settings-search-row-region")).toBeTruthy();
expect(picker.nextElementSibling?.classList.contains("settings-search")).toBe(true);
await user.click(getByLabelText("Hide search"));
await waitFor(() => expect(queryByTestId("settings-search-input")).toBeNull());