feat(FN-4560): complete Step 4 — unify toolbar disclosure and sizing
Fusion-Task-Id: FN-4560 Fusion-Task-Lineage: 90f0af13-2ec7-4ea5-9ebc-af3941465975
This commit is contained in:
@@ -397,9 +397,9 @@
|
|||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-editor-toolbar-actions .btn {
|
.file-editor-toolbar-button {
|
||||||
min-height: var(--mobile-nav-height);
|
min-height: calc(var(--space-lg) + var(--space-sm));
|
||||||
min-width: var(--mobile-nav-height);
|
min-width: calc(var(--space-lg) + var(--space-sm));
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-browser-modal-header .modal-close {
|
.file-browser-modal-header .modal-close {
|
||||||
@@ -688,8 +688,8 @@
|
|||||||
.file-editor-toolbar {
|
.file-editor-toolbar {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: flex-end;
|
||||||
gap: var(--space-md);
|
gap: var(--space-xs);
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
padding: var(--space-sm) var(--space-lg);
|
padding: var(--space-sm) var(--space-lg);
|
||||||
border-bottom: calc(var(--space-xs) * 0.25) solid var(--border);
|
border-bottom: calc(var(--space-xs) * 0.25) solid var(--border);
|
||||||
@@ -697,6 +697,11 @@
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.file-editor-toolbar--expanded {
|
||||||
|
padding-top: var(--space-xs);
|
||||||
|
padding-bottom: var(--space-xs);
|
||||||
|
}
|
||||||
|
|
||||||
.file-editor-toolbar-actions {
|
.file-editor-toolbar-actions {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -704,33 +709,20 @@
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-editor-toolbar-collapsible {
|
.file-editor-toolbar-button {
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--space-xs);
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-editor-toolbar-collapsible[hidden] {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-editor-line-numbers-button {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--space-xs);
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-editor-mode-toggle {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--space-xs);
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-editor-mode-toggle .btn {
|
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
gap: calc(var(--space-sm) - var(--space-xs) * 0.5);
|
gap: calc(var(--space-sm) - var(--space-xs) * 0.5);
|
||||||
|
min-height: calc(var(--space-lg) + var(--space-sm));
|
||||||
|
padding: calc(var(--space-xs) + var(--space-xs) * 0.25) var(--space-sm);
|
||||||
|
font-size: calc(var(--space-sm) + var(--space-xs));
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-editor-toolbar-button.btn-icon {
|
||||||
|
width: calc(var(--space-lg) + var(--space-sm));
|
||||||
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* File Editor Preview */
|
/* File Editor Preview */
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ export function FileEditor({
|
|||||||
const effectiveShowPreview = isMarkdown && (readOnly ? true : showPreview);
|
const effectiveShowPreview = isMarkdown && (readOnly ? true : showPreview);
|
||||||
const shouldRenderLineNumbers = showLineNumbers && !readOnly && !effectiveShowPreview;
|
const shouldRenderLineNumbers = showLineNumbers && !readOnly && !effectiveShowPreview;
|
||||||
const shouldShowLineNumbersToggle = Boolean(onToggleLineNumbers) && canToggleLineNumbers && !readOnly && !effectiveShowPreview;
|
const shouldShowLineNumbersToggle = Boolean(onToggleLineNumbers) && canToggleLineNumbers && !readOnly && !effectiveShowPreview;
|
||||||
const hasSecondaryActions = shouldShowLineNumbersToggle || !readOnly;
|
const hasToolbarActions = isMarkdown || !readOnly || shouldShowLineNumbersToggle;
|
||||||
const languageExtension = useMemo(() => resolveCodeMirrorLanguage(filePath), [filePath]);
|
const languageExtension = useMemo(() => resolveCodeMirrorLanguage(filePath), [filePath]);
|
||||||
|
|
||||||
const handleEditClick = useCallback(() => setShowPreview(false), []);
|
const handleEditClick = useCallback(() => setShowPreview(false), []);
|
||||||
@@ -139,43 +139,38 @@ export function FileEditor({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="file-editor-container">
|
<div className="file-editor-container">
|
||||||
{(isMarkdown || !readOnly) ? (
|
{hasToolbarActions ? (
|
||||||
<div className={`file-editor-toolbar ${toolbarActionsExpanded ? "file-editor-toolbar--expanded" : ""}`}>
|
<div className={`file-editor-toolbar ${toolbarActionsExpanded ? "file-editor-toolbar--expanded" : ""}`}>
|
||||||
<div className="file-editor-toolbar-actions">
|
<button className="btn btn-sm btn-icon file-editor-toolbar-button" onClick={handleToolbarActionsToggle} aria-label="Toggle editor options" title="Toggle editor options" aria-expanded={toolbarActionsExpanded} aria-controls={toolbarActionsId}>
|
||||||
|
{toolbarActionsExpanded ? <ChevronUp size={14} /> : <ChevronDown size={14} />}
|
||||||
|
</button>
|
||||||
|
<div className="file-editor-toolbar-actions" id={toolbarActionsId} hidden={!toolbarActionsExpanded}>
|
||||||
{isMarkdown ? (
|
{isMarkdown ? (
|
||||||
<div className="file-editor-mode-toggle">
|
<>
|
||||||
{!readOnly && (
|
{!readOnly && (
|
||||||
<button className={`btn btn-sm ${!effectiveShowPreview ? "btn-primary" : ""}`} onClick={handleEditClick} disabled={!effectiveShowPreview} aria-label="Edit mode">
|
<button className={`btn btn-sm file-editor-toolbar-button ${!effectiveShowPreview ? "btn-primary" : ""}`} onClick={handleEditClick} disabled={!effectiveShowPreview} aria-label="Edit mode">
|
||||||
<FileEdit size={14} />
|
<FileEdit size={14} />
|
||||||
Edit
|
Edit
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
<button className={`btn btn-sm ${effectiveShowPreview ? "btn-primary" : ""}`} onClick={handlePreviewClick} disabled={effectiveShowPreview} aria-label="Preview mode">
|
<button className={`btn btn-sm file-editor-toolbar-button ${effectiveShowPreview ? "btn-primary" : ""}`} onClick={handlePreviewClick} disabled={effectiveShowPreview} aria-label="Preview mode">
|
||||||
<Eye size={14} />
|
<Eye size={14} />
|
||||||
Preview
|
Preview
|
||||||
</button>
|
</button>
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
{!readOnly && hasSecondaryActions ? (
|
|
||||||
<>
|
|
||||||
{toolbarActionsExpanded ? (
|
|
||||||
<div className="file-editor-toolbar-collapsible" id={toolbarActionsId}>
|
|
||||||
{shouldShowLineNumbersToggle && (
|
|
||||||
<button className={`btn btn-sm file-editor-line-numbers-button ${showLineNumbers ? "btn-primary" : ""}`} onClick={onToggleLineNumbers} aria-label="Toggle line numbers" aria-pressed={showLineNumbers} title="Toggle line numbers">
|
|
||||||
<ListOrdered size={14} />
|
|
||||||
<span>Line #</span>
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
<button className={`btn btn-sm ${wordWrap ? "btn-primary" : ""}`} onClick={handleWordWrapToggle} aria-label="Toggle word wrap" title="Toggle word wrap">
|
|
||||||
<WrapText size={14} />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
<button className="btn btn-sm btn-icon" onClick={handleToolbarActionsToggle} aria-label="Toggle editor options" title="Toggle editor options" aria-expanded={toolbarActionsExpanded} aria-controls={toolbarActionsId}>
|
|
||||||
{toolbarActionsExpanded ? <ChevronUp size={14} /> : <ChevronDown size={14} />}
|
|
||||||
</button>
|
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
|
{shouldShowLineNumbersToggle && (
|
||||||
|
<button className={`btn btn-sm file-editor-toolbar-button ${showLineNumbers ? "btn-primary" : ""}`} onClick={onToggleLineNumbers} aria-label="Toggle line numbers" aria-pressed={showLineNumbers} title="Toggle line numbers">
|
||||||
|
<ListOrdered size={14} />
|
||||||
|
<span>Line #</span>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{!readOnly && (
|
||||||
|
<button className={`btn btn-sm file-editor-toolbar-button ${wordWrap ? "btn-primary" : ""}`} onClick={handleWordWrapToggle} aria-label="Toggle word wrap" title="Toggle word wrap">
|
||||||
|
<WrapText size={14} />
|
||||||
|
<span>Wrap</span>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -18,7 +18,10 @@ describe("FileEditor", () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const expandEditorOptions = () => {
|
const expandEditorOptions = () => {
|
||||||
fireEvent.click(screen.getByRole("button", { name: /toggle editor options/i }));
|
const toggle = screen.getByRole("button", { name: /toggle editor options/i });
|
||||||
|
if (toggle.getAttribute("aria-expanded") !== "true") {
|
||||||
|
fireEvent.click(toggle);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const highlightedTokenSelector = ".cm-line span[class]";
|
const highlightedTokenSelector = ".cm-line span[class]";
|
||||||
@@ -54,12 +57,12 @@ describe("FileEditor", () => {
|
|||||||
it("markdown preview toggle still works", () => {
|
it("markdown preview toggle still works", () => {
|
||||||
document.documentElement.dataset.theme = "dark";
|
document.documentElement.dataset.theme = "dark";
|
||||||
render(<FileEditor content="# Hello" onChange={vi.fn()} filePath="readme.md" />);
|
render(<FileEditor content="# Hello" onChange={vi.fn()} filePath="readme.md" />);
|
||||||
|
expandEditorOptions();
|
||||||
fireEvent.click(screen.getByRole("button", { name: /preview mode/i }));
|
fireEvent.click(screen.getByRole("button", { name: /preview mode/i }));
|
||||||
expect(document.querySelector(".file-editor-preview")).toBeInTheDocument();
|
expect(document.querySelector(".file-editor-preview")).toBeInTheDocument();
|
||||||
fireEvent.click(screen.getByRole("button", { name: /edit mode/i }));
|
fireEvent.click(screen.getByRole("button", { name: /edit mode/i }));
|
||||||
expect(document.querySelector(".cm-editor")).toBeInTheDocument();
|
expect(document.querySelector(".cm-editor")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("line-number toggle still flips state and gutter visibility", () => {
|
it("line-number toggle still flips state and gutter visibility", () => {
|
||||||
document.documentElement.dataset.theme = "dark";
|
document.documentElement.dataset.theme = "dark";
|
||||||
const onToggle = vi.fn();
|
const onToggle = vi.fn();
|
||||||
@@ -135,113 +138,45 @@ describe("FileEditor", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("markdown preview", () => {
|
describe("markdown preview", () => {
|
||||||
it("shows edit/preview toggle for .md files", () => {
|
it("shows edit/preview toggle for markdown extensions when expanded", () => {
|
||||||
render(<FileEditor content="# Hello" onChange={vi.fn()} filePath="readme.md" />);
|
const { rerender } = render(<FileEditor content="# Hello" onChange={vi.fn()} filePath="readme.md" />);
|
||||||
|
expandEditorOptions();
|
||||||
expect(screen.getByRole("button", { name: /edit mode/i })).toBeInTheDocument();
|
expect(screen.getByRole("button", { name: /edit mode/i })).toBeInTheDocument();
|
||||||
expect(screen.getByRole("button", { name: /preview/i })).toBeInTheDocument();
|
expect(screen.getByRole("button", { name: /preview/i })).toBeInTheDocument();
|
||||||
});
|
|
||||||
|
|
||||||
it("shows edit/preview toggle for .markdown files", () => {
|
rerender(<FileEditor content="# Hello" onChange={vi.fn()} filePath="readme.markdown" />);
|
||||||
render(<FileEditor content="# Hello" onChange={vi.fn()} filePath="readme.markdown" />);
|
expandEditorOptions();
|
||||||
|
|
||||||
expect(screen.getByRole("button", { name: /edit mode/i })).toBeInTheDocument();
|
expect(screen.getByRole("button", { name: /edit mode/i })).toBeInTheDocument();
|
||||||
expect(screen.getByRole("button", { name: /preview/i })).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("shows edit/preview toggle for .mdx files", () => {
|
rerender(<FileEditor content="# Hello" onChange={vi.fn()} filePath="page.mdx" />);
|
||||||
render(<FileEditor content="# Hello" onChange={vi.fn()} filePath="page.mdx" />);
|
expandEditorOptions();
|
||||||
|
|
||||||
expect(screen.getByRole("button", { name: /edit mode/i })).toBeInTheDocument();
|
|
||||||
expect(screen.getByRole("button", { name: /preview/i })).toBeInTheDocument();
|
expect(screen.getByRole("button", { name: /preview/i })).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not show edit/preview toggle for non-markdown files", () => {
|
it("does not show edit/preview toggle for non-markdown files", () => {
|
||||||
render(<FileEditor content="const x = 1;" onChange={vi.fn()} filePath="script.ts" />);
|
render(<FileEditor content="const x = 1;" onChange={vi.fn()} filePath="script.ts" />);
|
||||||
|
|
||||||
expect(screen.queryByRole("button", { name: /edit mode/i })).not.toBeInTheDocument();
|
expect(screen.queryByRole("button", { name: /edit mode/i })).not.toBeInTheDocument();
|
||||||
expect(screen.queryByRole("button", { name: /preview/i })).not.toBeInTheDocument();
|
expect(screen.queryByRole("button", { name: /preview/i })).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not show edit/preview toggle when filePath is not provided", () => {
|
it("switches preview and edit when expanded", () => {
|
||||||
render(<FileEditor content="some content" onChange={vi.fn()} />);
|
|
||||||
|
|
||||||
expect(screen.queryByRole("button", { name: /edit mode/i })).not.toBeInTheDocument();
|
|
||||||
expect(screen.queryByRole("button", { name: /preview/i })).not.toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("defaults to edit mode for markdown files", () => {
|
|
||||||
render(<FileEditor content="# Hello World" onChange={vi.fn()} filePath="readme.md" />);
|
render(<FileEditor content="# Hello World" onChange={vi.fn()} filePath="readme.md" />);
|
||||||
expect(document.querySelector(".cm-editor")).toBeInTheDocument();
|
expandEditorOptions();
|
||||||
});
|
|
||||||
|
|
||||||
it("switches to preview mode when preview button is clicked", () => {
|
|
||||||
render(<FileEditor content="# Hello World" onChange={vi.fn()} filePath="readme.md" />);
|
|
||||||
|
|
||||||
const previewButton = screen.getByRole("button", { name: /preview/i });
|
const previewButton = screen.getByRole("button", { name: /preview/i });
|
||||||
fireEvent.click(previewButton);
|
fireEvent.click(previewButton);
|
||||||
|
|
||||||
expect(document.querySelector(".cm-editor")).not.toBeInTheDocument();
|
|
||||||
expect(document.querySelector(".file-editor-preview")).toBeInTheDocument();
|
expect(document.querySelector(".file-editor-preview")).toBeInTheDocument();
|
||||||
});
|
|
||||||
|
|
||||||
it("switches back to edit mode when edit button is clicked", () => {
|
|
||||||
render(<FileEditor content="# Hello World" onChange={vi.fn()} filePath="readme.md" />);
|
|
||||||
|
|
||||||
const previewButton = screen.getByRole("button", { name: /preview/i });
|
|
||||||
fireEvent.click(previewButton);
|
|
||||||
|
|
||||||
const editButton = screen.getByRole("button", { name: /edit mode/i });
|
const editButton = screen.getByRole("button", { name: /edit mode/i });
|
||||||
fireEvent.click(editButton);
|
fireEvent.click(editButton);
|
||||||
|
|
||||||
expect(document.querySelector(".cm-editor")).toBeInTheDocument();
|
expect(document.querySelector(".cm-editor")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders markdown content in preview mode", () => {
|
it("readOnly markdown shows preview action when expanded", () => {
|
||||||
render(<FileEditor content="# Hello World" onChange={vi.fn()} filePath="readme.md" />);
|
|
||||||
|
|
||||||
// Switch to preview
|
|
||||||
const previewButton = screen.getByRole("button", { name: /preview/i });
|
|
||||||
fireEvent.click(previewButton);
|
|
||||||
|
|
||||||
// Check that the markdown is rendered (heading should be present)
|
|
||||||
expect(document.querySelector(".file-editor-preview")).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("hides edit button in readOnly mode for markdown files", () => {
|
|
||||||
render(<FileEditor content="# Hello" onChange={vi.fn()} filePath="readme.md" readOnly />);
|
render(<FileEditor content="# Hello" onChange={vi.fn()} filePath="readme.md" readOnly />);
|
||||||
|
expandEditorOptions();
|
||||||
// Edit button should not be visible
|
|
||||||
expect(screen.queryByRole("button", { name: /edit mode/i })).not.toBeInTheDocument();
|
expect(screen.queryByRole("button", { name: /edit mode/i })).not.toBeInTheDocument();
|
||||||
// Preview button should still be visible
|
|
||||||
expect(screen.getByRole("button", { name: /preview/i })).toBeInTheDocument();
|
expect(screen.getByRole("button", { name: /preview/i })).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("defaults to preview mode in readOnly mode for markdown files", () => {
|
|
||||||
render(<FileEditor content="# Hello World" onChange={vi.fn()} filePath="readme.md" readOnly />);
|
|
||||||
|
|
||||||
expect(document.querySelector(".cm-editor")).not.toBeInTheDocument();
|
|
||||||
expect(document.querySelector(".file-editor-preview")).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("preview button is disabled when already in preview mode", () => {
|
|
||||||
render(<FileEditor content="# Hello" onChange={vi.fn()} filePath="readme.md" />);
|
|
||||||
|
|
||||||
// Switch to preview
|
|
||||||
const previewButton = screen.getByRole("button", { name: /preview/i });
|
|
||||||
fireEvent.click(previewButton);
|
|
||||||
|
|
||||||
// Preview button should now be disabled
|
|
||||||
expect(previewButton).toBeDisabled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("edit button is disabled when already in edit mode", () => {
|
|
||||||
render(<FileEditor content="# Hello" onChange={vi.fn()} filePath="readme.md" />);
|
|
||||||
|
|
||||||
const editButton = screen.getByRole("button", { name: /edit mode/i });
|
|
||||||
// Edit button should be disabled in edit mode
|
|
||||||
expect(editButton).toBeDisabled();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("word wrap toggle", () => {
|
describe("word wrap toggle", () => {
|
||||||
@@ -414,101 +349,107 @@ describe("FileEditor", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("editor toolbar options collapse", () => {
|
describe("editor toolbar options collapse", () => {
|
||||||
it("secondary actions are collapsed by default for markdown and non-markdown files", () => {
|
it("hides edit, preview, line numbers, and wrap while collapsed", () => {
|
||||||
const { rerender } = render(<FileEditor content="const x = 1;" onChange={vi.fn()} filePath="script.ts" onToggleLineNumbers={vi.fn()} />);
|
render(<FileEditor content="# Hello" onChange={vi.fn()} filePath="readme.md" onToggleLineNumbers={vi.fn()} />);
|
||||||
|
expect(screen.queryByRole("button", { name: /edit mode/i })).not.toBeInTheDocument();
|
||||||
expect(screen.queryByRole("button", { name: /toggle line numbers/i })).not.toBeInTheDocument();
|
expect(screen.queryByRole("button", { name: /preview mode/i })).not.toBeInTheDocument();
|
||||||
expect(screen.queryByRole("button", { name: /toggle word wrap/i })).not.toBeInTheDocument();
|
|
||||||
|
|
||||||
rerender(<FileEditor content="# Hello" onChange={vi.fn()} filePath="readme.md" onToggleLineNumbers={vi.fn()} />);
|
|
||||||
expect(screen.queryByRole("button", { name: /toggle line numbers/i })).not.toBeInTheDocument();
|
expect(screen.queryByRole("button", { name: /toggle line numbers/i })).not.toBeInTheDocument();
|
||||||
expect(screen.queryByRole("button", { name: /toggle word wrap/i })).not.toBeInTheDocument();
|
expect(screen.queryByRole("button", { name: /toggle word wrap/i })).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("expanding shows line-number and wrap toggles", () => {
|
it("expanding shows all actions in one toolbar actions row", () => {
|
||||||
render(<FileEditor content="const x = 1;" onChange={vi.fn()} filePath="script.ts" onToggleLineNumbers={vi.fn()} />);
|
render(<FileEditor content="# Hello" onChange={vi.fn()} filePath="readme.md" onToggleLineNumbers={vi.fn()} />);
|
||||||
|
|
||||||
expandEditorOptions();
|
expandEditorOptions();
|
||||||
expect(screen.getByRole("button", { name: /toggle line numbers/i })).toBeInTheDocument();
|
|
||||||
expect(screen.getByRole("button", { name: /toggle word wrap/i })).toBeInTheDocument();
|
const editButton = screen.getByRole("button", { name: /edit mode/i });
|
||||||
|
const actionsRow = editButton.closest(".file-editor-toolbar-actions");
|
||||||
|
expect(actionsRow).toBeTruthy();
|
||||||
|
expect(actionsRow).toContainElement(screen.getByRole("button", { name: /preview mode/i }));
|
||||||
|
expect(actionsRow).toContainElement(screen.getByRole("button", { name: /toggle line numbers/i }));
|
||||||
|
expect(actionsRow).toContainElement(screen.getByRole("button", { name: /toggle word wrap/i }));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("collapsing hides the toggles again", () => {
|
it.each([/edit mode/i, /preview mode/i, /toggle line numbers/i, /toggle word wrap/i])(
|
||||||
render(<FileEditor content="const x = 1;" onChange={vi.fn()} filePath="script.ts" onToggleLineNumbers={vi.fn()} />);
|
"expanded action button %s uses compact toolbar class signature",
|
||||||
|
(name) => {
|
||||||
expandEditorOptions();
|
render(<FileEditor content="# Hello" onChange={vi.fn()} filePath="readme.md" onToggleLineNumbers={vi.fn()} />);
|
||||||
expandEditorOptions();
|
expandEditorOptions();
|
||||||
expect(screen.queryByRole("button", { name: /toggle line numbers/i })).not.toBeInTheDocument();
|
const button = screen.getByRole("button", { name });
|
||||||
expect(screen.queryByRole("button", { name: /toggle word wrap/i })).not.toBeInTheDocument();
|
expect(button.className).toContain("btn");
|
||||||
});
|
expect(button.className).toContain("btn-sm");
|
||||||
|
expect(button.className).toContain("file-editor-toolbar-button");
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
it("aria-expanded reflects state", () => {
|
it("aria-expanded reflects state", () => {
|
||||||
render(<FileEditor content="const x = 1;" onChange={vi.fn()} filePath="script.ts" onToggleLineNumbers={vi.fn()} />);
|
render(<FileEditor content="const x = 1;" onChange={vi.fn()} filePath="script.ts" onToggleLineNumbers={vi.fn()} />);
|
||||||
|
|
||||||
const optionsButton = screen.getByRole("button", { name: /toggle editor options/i });
|
const optionsButton = screen.getByRole("button", { name: /toggle editor options/i });
|
||||||
expect(optionsButton).toHaveAttribute("aria-expanded", "false");
|
expect(optionsButton).toHaveAttribute("aria-expanded", "false");
|
||||||
|
|
||||||
fireEvent.click(optionsButton);
|
fireEvent.click(optionsButton);
|
||||||
expect(optionsButton).toHaveAttribute("aria-expanded", "true");
|
expect(optionsButton).toHaveAttribute("aria-expanded", "true");
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("FN-4480: collapsible region has computed display:none when collapsed", () => {
|
describe("toolbar sizing CSS", () => {
|
||||||
|
it("keeps equal height and font-size for all toolbar buttons on desktop and mobile", () => {
|
||||||
const css = loadAllAppCss();
|
const css = loadAllAppCss();
|
||||||
const style = document.createElement("style");
|
const style = document.createElement("style");
|
||||||
style.textContent = css;
|
style.textContent = css;
|
||||||
document.head.appendChild(style);
|
document.head.appendChild(style);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
render(<FileEditor content="x" onChange={vi.fn()} filePath="script.ts" onToggleLineNumbers={vi.fn()} />);
|
render(<FileEditor content="# Hello" onChange={vi.fn()} filePath="readme.md" onToggleLineNumbers={vi.fn()} />);
|
||||||
|
expandEditorOptions();
|
||||||
|
|
||||||
const optionsButton = screen.getByRole("button", { name: /toggle editor options/i });
|
const buttons = [
|
||||||
const controlsId = optionsButton.getAttribute("aria-controls");
|
screen.getByRole("button", { name: /edit mode/i }),
|
||||||
expect(controlsId).toBeTruthy();
|
screen.getByRole("button", { name: /preview mode/i }),
|
||||||
|
screen.getByRole("button", { name: /toggle line numbers/i }),
|
||||||
|
screen.getByRole("button", { name: /toggle word wrap/i }),
|
||||||
|
];
|
||||||
|
|
||||||
const collapsibleRegion = document.getElementById(controlsId as string);
|
const desktopStyles = buttons.map((button) => getComputedStyle(button));
|
||||||
expect(collapsibleRegion).toBeTruthy();
|
expect(new Set(desktopStyles.map((styleDecl) => styleDecl.height)).size).toBe(1);
|
||||||
|
expect(new Set(desktopStyles.map((styleDecl) => styleDecl.fontSize)).size).toBe(1);
|
||||||
|
|
||||||
expect(getComputedStyle(collapsibleRegion as HTMLElement).display).toBe("none");
|
window.matchMedia = vi.fn().mockImplementation((query: string) => ({
|
||||||
|
matches: query.includes("max-width: 768px"),
|
||||||
|
media: query,
|
||||||
|
onchange: null,
|
||||||
|
addListener: vi.fn(),
|
||||||
|
removeListener: vi.fn(),
|
||||||
|
addEventListener: vi.fn(),
|
||||||
|
removeEventListener: vi.fn(),
|
||||||
|
dispatchEvent: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
fireEvent.click(optionsButton);
|
const mobileStyles = buttons.map((button) => getComputedStyle(button));
|
||||||
expect(getComputedStyle(collapsibleRegion as HTMLElement).display).toBe("inline-flex");
|
expect(new Set(mobileStyles.map((styleDecl) => styleDecl.height)).size).toBe(1);
|
||||||
|
expect(new Set(mobileStyles.map((styleDecl) => styleDecl.fontSize)).size).toBe(1);
|
||||||
|
} finally {
|
||||||
|
style.remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
fireEvent.click(optionsButton);
|
it("reduces toolbar vertical padding when expanded", () => {
|
||||||
expect(getComputedStyle(collapsibleRegion as HTMLElement).display).toBe("none");
|
const css = loadAllAppCss();
|
||||||
|
const style = document.createElement("style");
|
||||||
|
style.textContent = css;
|
||||||
|
document.head.appendChild(style);
|
||||||
|
|
||||||
|
try {
|
||||||
|
render(<FileEditor content="# Hello" onChange={vi.fn()} filePath="readme.md" onToggleLineNumbers={vi.fn()} />);
|
||||||
|
const toolbar = document.querySelector(".file-editor-toolbar") as HTMLElement;
|
||||||
|
const collapsed = getComputedStyle(toolbar);
|
||||||
|
|
||||||
|
expandEditorOptions();
|
||||||
|
const expanded = getComputedStyle(toolbar);
|
||||||
|
|
||||||
|
expect(expanded.paddingTop).not.toBe(collapsed.paddingTop);
|
||||||
|
expect(expanded.paddingBottom).not.toBe(collapsed.paddingBottom);
|
||||||
} finally {
|
} finally {
|
||||||
style.remove();
|
style.remove();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("mobile toolbar CSS", () => {
|
|
||||||
it("keeps file editor toolbar action buttons at a shared mobile touch target size", () => {
|
|
||||||
const css = loadAllAppCss();
|
|
||||||
const selectorIndex = css.indexOf(".file-editor-toolbar-actions .btn");
|
|
||||||
|
|
||||||
expect(selectorIndex).toBeGreaterThanOrEqual(0);
|
|
||||||
|
|
||||||
const mediaIndex = css.lastIndexOf("@media (max-width: 768px)", selectorIndex);
|
|
||||||
expect(mediaIndex).toBeGreaterThanOrEqual(0);
|
|
||||||
|
|
||||||
const openBraceIndex = css.indexOf("{", mediaIndex);
|
|
||||||
let depth = 1;
|
|
||||||
let cursor = openBraceIndex + 1;
|
|
||||||
|
|
||||||
while (cursor < css.length && depth > 0) {
|
|
||||||
if (css[cursor] === "{") {
|
|
||||||
depth += 1;
|
|
||||||
} else if (css[cursor] === "}") {
|
|
||||||
depth -= 1;
|
|
||||||
}
|
|
||||||
cursor += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
const mobileCss = css.slice(openBraceIndex + 1, cursor - 1);
|
|
||||||
|
|
||||||
expect(mobileCss).toMatch(
|
|
||||||
/\.file-editor-toolbar-actions\s+\.btn\s*\{[^}]*min-height:\s*var\(--mobile-nav-height\);[^}]*min-width:\s*var\(--mobile-nav-height\);[^}]*\}/,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user