feat(FN-4418): complete Step 4 — collapsible file editor toolbar actions
Fusion-Task-Id: FN-4418 Fusion-Task-Lineage: 088af204-a608-46e1-9ca6-4e021c6d751b
This commit is contained in:
@@ -694,6 +694,13 @@
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.file-editor-toolbar-collapsible {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-xs);
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
.file-editor-line-numbers-button {
|
.file-editor-line-numbers-button {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useState, useCallback, useMemo, useRef, type UIEvent } from "react";
|
import { useState, useCallback, useMemo, useRef, useId, type UIEvent } from "react";
|
||||||
import ReactMarkdown from "react-markdown";
|
import ReactMarkdown from "react-markdown";
|
||||||
import remarkGfm from "remark-gfm";
|
import remarkGfm from "remark-gfm";
|
||||||
import { FileEdit, Eye, ListOrdered, WrapText } from "lucide-react";
|
import { FileEdit, Eye, ListOrdered, WrapText, ChevronDown, ChevronUp } from "lucide-react";
|
||||||
|
|
||||||
interface FileEditorProps {
|
interface FileEditorProps {
|
||||||
content: string;
|
content: string;
|
||||||
@@ -30,13 +30,16 @@ export function FileEditor({
|
|||||||
}: FileEditorProps) {
|
}: FileEditorProps) {
|
||||||
const [showPreview, setShowPreview] = useState(false);
|
const [showPreview, setShowPreview] = useState(false);
|
||||||
const [wordWrap, setWordWrap] = useState(true);
|
const [wordWrap, setWordWrap] = useState(true);
|
||||||
|
const [toolbarActionsExpanded, setToolbarActionsExpanded] = useState(false);
|
||||||
const lineNumbersRef = useRef<HTMLDivElement>(null);
|
const lineNumbersRef = useRef<HTMLDivElement>(null);
|
||||||
const isMarkdown = isMarkdownFile(filePath);
|
const isMarkdown = isMarkdownFile(filePath);
|
||||||
|
const toolbarActionsId = useId();
|
||||||
|
|
||||||
// For markdown files in readOnly mode, default to preview
|
// For markdown files in readOnly mode, default to preview
|
||||||
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 lineCount = useMemo(() => {
|
const lineCount = useMemo(() => {
|
||||||
if (!shouldRenderLineNumbers) {
|
if (!shouldRenderLineNumbers) {
|
||||||
return 0;
|
return 0;
|
||||||
@@ -57,6 +60,10 @@ export function FileEditor({
|
|||||||
setWordWrap((prev) => !prev);
|
setWordWrap((prev) => !prev);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const handleToolbarActionsToggle = useCallback(() => {
|
||||||
|
setToolbarActionsExpanded((prev) => !prev);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const handleTextareaScroll = useCallback((event: UIEvent<HTMLTextAreaElement>) => {
|
const handleTextareaScroll = useCallback((event: UIEvent<HTMLTextAreaElement>) => {
|
||||||
if (!lineNumbersRef.current) {
|
if (!lineNumbersRef.current) {
|
||||||
return;
|
return;
|
||||||
@@ -93,26 +100,42 @@ export function FileEditor({
|
|||||||
</div>
|
</div>
|
||||||
{!readOnly && (
|
{!readOnly && (
|
||||||
<div className="file-editor-toolbar-actions">
|
<div className="file-editor-toolbar-actions">
|
||||||
{shouldShowLineNumbersToggle && (
|
{hasSecondaryActions && (
|
||||||
<button
|
<>
|
||||||
className={`btn btn-sm file-editor-line-numbers-button ${showLineNumbers ? "btn-primary" : ""}`}
|
<button
|
||||||
onClick={onToggleLineNumbers}
|
className="btn btn-sm btn-icon"
|
||||||
aria-label="Toggle line numbers"
|
onClick={handleToolbarActionsToggle}
|
||||||
aria-pressed={showLineNumbers}
|
aria-label="Toggle editor options"
|
||||||
title="Toggle line numbers"
|
title="Toggle editor options"
|
||||||
>
|
aria-expanded={toolbarActionsExpanded}
|
||||||
<ListOrdered size={14} />
|
aria-controls={toolbarActionsId}
|
||||||
<span>Line #</span>
|
>
|
||||||
</button>
|
{toolbarActionsExpanded ? <ChevronUp size={14} /> : <ChevronDown size={14} />}
|
||||||
|
</button>
|
||||||
|
<div className="file-editor-toolbar-collapsible" id={toolbarActionsId} hidden={!toolbarActionsExpanded}>
|
||||||
|
{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>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
<button
|
|
||||||
className={`btn btn-sm ${wordWrap ? "btn-primary" : ""}`}
|
|
||||||
onClick={handleWordWrapToggle}
|
|
||||||
aria-label="Toggle word wrap"
|
|
||||||
title="Toggle word wrap"
|
|
||||||
>
|
|
||||||
<WrapText size={14} />
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -121,26 +144,42 @@ export function FileEditor({
|
|||||||
<div className="file-editor-toolbar">
|
<div className="file-editor-toolbar">
|
||||||
<div className="file-editor-mode-toggle" />
|
<div className="file-editor-mode-toggle" />
|
||||||
<div className="file-editor-toolbar-actions">
|
<div className="file-editor-toolbar-actions">
|
||||||
{shouldShowLineNumbersToggle && (
|
{hasSecondaryActions && (
|
||||||
<button
|
<>
|
||||||
className={`btn btn-sm file-editor-line-numbers-button ${showLineNumbers ? "btn-primary" : ""}`}
|
<button
|
||||||
onClick={onToggleLineNumbers}
|
className="btn btn-sm btn-icon"
|
||||||
aria-label="Toggle line numbers"
|
onClick={handleToolbarActionsToggle}
|
||||||
aria-pressed={showLineNumbers}
|
aria-label="Toggle editor options"
|
||||||
title="Toggle line numbers"
|
title="Toggle editor options"
|
||||||
>
|
aria-expanded={toolbarActionsExpanded}
|
||||||
<ListOrdered size={14} />
|
aria-controls={toolbarActionsId}
|
||||||
<span>Line #</span>
|
>
|
||||||
</button>
|
{toolbarActionsExpanded ? <ChevronUp size={14} /> : <ChevronDown size={14} />}
|
||||||
|
</button>
|
||||||
|
<div className="file-editor-toolbar-collapsible" id={toolbarActionsId} hidden={!toolbarActionsExpanded}>
|
||||||
|
{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>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
<button
|
|
||||||
className={`btn btn-sm ${wordWrap ? "btn-primary" : ""}`}
|
|
||||||
onClick={handleWordWrapToggle}
|
|
||||||
aria-label="Toggle word wrap"
|
|
||||||
title="Toggle word wrap"
|
|
||||||
>
|
|
||||||
<WrapText size={14} />
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -148,9 +187,7 @@ export function FileEditor({
|
|||||||
|
|
||||||
{effectiveShowPreview ? (
|
{effectiveShowPreview ? (
|
||||||
<div className="file-editor-preview markdown-body">
|
<div className="file-editor-preview markdown-body">
|
||||||
<ReactMarkdown remarkPlugins={[remarkGfm]}>
|
<ReactMarkdown remarkPlugins={[remarkGfm]}>{content}</ReactMarkdown>
|
||||||
{content}
|
|
||||||
</ReactMarkdown>
|
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className={`file-editor-textarea-shell ${shouldRenderLineNumbers ? "file-editor-textarea-shell--line-numbers" : ""}`}>
|
<div className={`file-editor-textarea-shell ${shouldRenderLineNumbers ? "file-editor-textarea-shell--line-numbers" : ""}`}>
|
||||||
|
|||||||
@@ -763,6 +763,7 @@ describe("FileBrowserModal", () => {
|
|||||||
|
|
||||||
await clickFileEntry("file1.ts");
|
await clickFileEntry("file1.ts");
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole("button", { name: /toggle editor options/i }));
|
||||||
const toggle = screen.getByRole("button", { name: /toggle line numbers/i });
|
const toggle = screen.getByRole("button", { name: /toggle line numbers/i });
|
||||||
expect(toggle).toHaveAttribute("aria-pressed", "false");
|
expect(toggle).toHaveAttribute("aria-pressed", "false");
|
||||||
|
|
||||||
@@ -786,6 +787,7 @@ describe("FileBrowserModal", () => {
|
|||||||
|
|
||||||
await clickFileEntry("file1.ts");
|
await clickFileEntry("file1.ts");
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole("button", { name: /toggle editor options/i }));
|
||||||
expect(screen.getByRole("button", { name: /toggle line numbers/i })).toHaveAttribute("aria-pressed", "true");
|
expect(screen.getByRole("button", { name: /toggle line numbers/i })).toHaveAttribute("aria-pressed", "true");
|
||||||
|
|
||||||
rerender(
|
rerender(
|
||||||
@@ -799,6 +801,9 @@ describe("FileBrowserModal", () => {
|
|||||||
|
|
||||||
await clickFileEntry("file1.ts");
|
await clickFileEntry("file1.ts");
|
||||||
|
|
||||||
|
if (!screen.queryByRole("button", { name: /toggle line numbers/i })) {
|
||||||
|
fireEvent.click(screen.getByRole("button", { name: /toggle editor options/i }));
|
||||||
|
}
|
||||||
expect(screen.getByRole("button", { name: /toggle line numbers/i })).toHaveAttribute("aria-pressed", "false");
|
expect(screen.getByRole("button", { name: /toggle line numbers/i })).toHaveAttribute("aria-pressed", "false");
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -823,6 +828,7 @@ describe("FileBrowserModal", () => {
|
|||||||
fireEvent.click(screen.getByText("editable.ts"));
|
fireEvent.click(screen.getByText("editable.ts"));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole("button", { name: /toggle editor options/i }));
|
||||||
fireEvent.click(screen.getByRole("button", { name: /toggle line numbers/i }));
|
fireEvent.click(screen.getByRole("button", { name: /toggle line numbers/i }));
|
||||||
expect(document.querySelector(".file-editor-line-numbers")).toBeInTheDocument();
|
expect(document.querySelector(".file-editor-line-numbers")).toBeInTheDocument();
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ import { loadAllAppCss } from "../../test/cssFixture";
|
|||||||
import { FileEditor } from "../FileEditor";
|
import { FileEditor } from "../FileEditor";
|
||||||
|
|
||||||
describe("FileEditor", () => {
|
describe("FileEditor", () => {
|
||||||
|
const expandEditorOptions = () => {
|
||||||
|
fireEvent.click(screen.getByRole("button", { name: /toggle editor options/i }));
|
||||||
|
};
|
||||||
|
|
||||||
it("renders textarea with correct class names", () => {
|
it("renders textarea with correct class names", () => {
|
||||||
render(<FileEditor content="" onChange={vi.fn()} />);
|
render(<FileEditor content="" onChange={vi.fn()} />);
|
||||||
const textarea = screen.getByRole("textbox");
|
const textarea = screen.getByRole("textbox");
|
||||||
@@ -62,35 +66,35 @@ describe("FileEditor", () => {
|
|||||||
it("shows edit/preview toggle for .md files", () => {
|
it("shows edit/preview toggle for .md files", () => {
|
||||||
render(<FileEditor content="# Hello" onChange={vi.fn()} filePath="readme.md" />);
|
render(<FileEditor content="# Hello" onChange={vi.fn()} filePath="readme.md" />);
|
||||||
|
|
||||||
expect(screen.getByRole("button", { name: /edit/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", () => {
|
it("shows edit/preview toggle for .markdown files", () => {
|
||||||
render(<FileEditor content="# Hello" onChange={vi.fn()} filePath="readme.markdown" />);
|
render(<FileEditor content="# Hello" onChange={vi.fn()} filePath="readme.markdown" />);
|
||||||
|
|
||||||
expect(screen.getByRole("button", { name: /edit/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 .mdx files", () => {
|
it("shows edit/preview toggle for .mdx files", () => {
|
||||||
render(<FileEditor content="# Hello" onChange={vi.fn()} filePath="page.mdx" />);
|
render(<FileEditor content="# Hello" onChange={vi.fn()} filePath="page.mdx" />);
|
||||||
|
|
||||||
expect(screen.getByRole("button", { name: /edit/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("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/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("does not show edit/preview toggle when filePath is not provided", () => {
|
||||||
render(<FileEditor content="some content" onChange={vi.fn()} />);
|
render(<FileEditor content="some content" onChange={vi.fn()} />);
|
||||||
|
|
||||||
expect(screen.queryByRole("button", { name: /edit/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();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -123,7 +127,7 @@ describe("FileEditor", () => {
|
|||||||
fireEvent.click(previewButton);
|
fireEvent.click(previewButton);
|
||||||
|
|
||||||
// Then switch back to edit
|
// Then switch back to edit
|
||||||
const editButton = screen.getByRole("button", { name: /edit/i });
|
const editButton = screen.getByRole("button", { name: /edit mode/i });
|
||||||
fireEvent.click(editButton);
|
fireEvent.click(editButton);
|
||||||
|
|
||||||
// Textarea should be visible again
|
// Textarea should be visible again
|
||||||
@@ -146,7 +150,7 @@ describe("FileEditor", () => {
|
|||||||
render(<FileEditor content="# Hello" onChange={vi.fn()} filePath="readme.md" readOnly />);
|
render(<FileEditor content="# Hello" onChange={vi.fn()} filePath="readme.md" readOnly />);
|
||||||
|
|
||||||
// Edit button should not be visible
|
// Edit button should not be visible
|
||||||
expect(screen.queryByRole("button", { name: /edit/i })).not.toBeInTheDocument();
|
expect(screen.queryByRole("button", { name: /edit mode/i })).not.toBeInTheDocument();
|
||||||
// Preview button should still be visible
|
// Preview button should still be visible
|
||||||
expect(screen.getByRole("button", { name: /preview/i })).toBeInTheDocument();
|
expect(screen.getByRole("button", { name: /preview/i })).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
@@ -173,7 +177,7 @@ describe("FileEditor", () => {
|
|||||||
it("edit button is disabled when already in edit mode", () => {
|
it("edit button is disabled when already in edit mode", () => {
|
||||||
render(<FileEditor content="# Hello" onChange={vi.fn()} filePath="readme.md" />);
|
render(<FileEditor content="# Hello" onChange={vi.fn()} filePath="readme.md" />);
|
||||||
|
|
||||||
const editButton = screen.getByRole("button", { name: /edit/i });
|
const editButton = screen.getByRole("button", { name: /edit mode/i });
|
||||||
// Edit button should be disabled in edit mode
|
// Edit button should be disabled in edit mode
|
||||||
expect(editButton).toBeDisabled();
|
expect(editButton).toBeDisabled();
|
||||||
});
|
});
|
||||||
@@ -182,63 +186,69 @@ describe("FileEditor", () => {
|
|||||||
describe("word wrap toggle", () => {
|
describe("word wrap toggle", () => {
|
||||||
it("shows word wrap toggle button for markdown files in edit mode", () => {
|
it("shows word wrap toggle button for markdown files in edit mode", () => {
|
||||||
render(<FileEditor content="# Hello" onChange={vi.fn()} filePath="readme.md" />);
|
render(<FileEditor content="# Hello" onChange={vi.fn()} filePath="readme.md" />);
|
||||||
|
|
||||||
|
expandEditorOptions();
|
||||||
expect(screen.getByRole("button", { name: /toggle word wrap/i })).toBeInTheDocument();
|
expect(screen.getByRole("button", { name: /toggle word wrap/i })).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("shows word wrap toggle button for non-markdown files", () => {
|
it("shows word wrap toggle button 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" />);
|
||||||
|
|
||||||
|
expandEditorOptions();
|
||||||
expect(screen.getByRole("button", { name: /toggle word wrap/i })).toBeInTheDocument();
|
expect(screen.getByRole("button", { name: /toggle word wrap/i })).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not show word wrap toggle button in readOnly mode", () => {
|
it("does not show word wrap toggle button in readOnly mode", () => {
|
||||||
render(<FileEditor content="# Hello" onChange={vi.fn()} filePath="readme.md" readOnly />);
|
render(<FileEditor content="# Hello" onChange={vi.fn()} filePath="readme.md" readOnly />);
|
||||||
|
|
||||||
expect(screen.queryByRole("button", { name: /toggle word wrap/i })).not.toBeInTheDocument();
|
expect(screen.queryByRole("button", { name: /toggle word wrap/i })).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("word wrap is enabled by default", () => {
|
it("word wrap is enabled by default", () => {
|
||||||
render(<FileEditor content="const x = 1;" onChange={vi.fn()} filePath="script.ts" />);
|
render(<FileEditor content="const x = 1;" onChange={vi.fn()} filePath="script.ts" />);
|
||||||
|
|
||||||
const textarea = screen.getByRole("textbox");
|
const textarea = screen.getByRole("textbox");
|
||||||
expect(textarea.classList.contains("file-editor-textarea--wrap")).toBe(true);
|
expect(textarea.classList.contains("file-editor-textarea--wrap")).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("toggle button shows active state when word wrap is enabled", () => {
|
it("toggle button shows active state when word wrap is enabled", () => {
|
||||||
render(<FileEditor content="const x = 1;" onChange={vi.fn()} filePath="script.ts" />);
|
render(<FileEditor content="const x = 1;" onChange={vi.fn()} filePath="script.ts" />);
|
||||||
|
|
||||||
|
expandEditorOptions();
|
||||||
const wrapButton = screen.getByRole("button", { name: /toggle word wrap/i });
|
const wrapButton = screen.getByRole("button", { name: /toggle word wrap/i });
|
||||||
expect(wrapButton.classList.contains("btn-primary")).toBe(true);
|
expect(wrapButton.classList.contains("btn-primary")).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("clicking toggle button disables word wrap", () => {
|
it("clicking toggle button disables word wrap", () => {
|
||||||
render(<FileEditor content="const x = 1;" onChange={vi.fn()} filePath="script.ts" />);
|
render(<FileEditor content="const x = 1;" onChange={vi.fn()} filePath="script.ts" />);
|
||||||
|
|
||||||
|
expandEditorOptions();
|
||||||
const wrapButton = screen.getByRole("button", { name: /toggle word wrap/i });
|
const wrapButton = screen.getByRole("button", { name: /toggle word wrap/i });
|
||||||
fireEvent.click(wrapButton);
|
fireEvent.click(wrapButton);
|
||||||
|
|
||||||
const textarea = screen.getByRole("textbox");
|
const textarea = screen.getByRole("textbox");
|
||||||
expect(textarea.classList.contains("file-editor-textarea--wrap")).toBe(false);
|
expect(textarea.classList.contains("file-editor-textarea--wrap")).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("clicking toggle button again re-enables word wrap", () => {
|
it("clicking toggle button again re-enables word wrap", () => {
|
||||||
render(<FileEditor content="const x = 1;" onChange={vi.fn()} filePath="script.ts" />);
|
render(<FileEditor content="const x = 1;" onChange={vi.fn()} filePath="script.ts" />);
|
||||||
|
|
||||||
|
expandEditorOptions();
|
||||||
const wrapButton = screen.getByRole("button", { name: /toggle word wrap/i });
|
const wrapButton = screen.getByRole("button", { name: /toggle word wrap/i });
|
||||||
fireEvent.click(wrapButton); // turn off
|
fireEvent.click(wrapButton);
|
||||||
fireEvent.click(wrapButton); // turn on
|
fireEvent.click(wrapButton);
|
||||||
|
|
||||||
const textarea = screen.getByRole("textbox");
|
const textarea = screen.getByRole("textbox");
|
||||||
expect(textarea.classList.contains("file-editor-textarea--wrap")).toBe(true);
|
expect(textarea.classList.contains("file-editor-textarea--wrap")).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("toggle button loses active state when word wrap is disabled", () => {
|
it("toggle button loses active state when word wrap is disabled", () => {
|
||||||
render(<FileEditor content="const x = 1;" onChange={vi.fn()} filePath="script.ts" />);
|
render(<FileEditor content="const x = 1;" onChange={vi.fn()} filePath="script.ts" />);
|
||||||
|
|
||||||
|
expandEditorOptions();
|
||||||
const wrapButton = screen.getByRole("button", { name: /toggle word wrap/i });
|
const wrapButton = screen.getByRole("button", { name: /toggle word wrap/i });
|
||||||
fireEvent.click(wrapButton);
|
fireEvent.click(wrapButton);
|
||||||
|
|
||||||
expect(wrapButton.classList.contains("btn-primary")).toBe(false);
|
expect(wrapButton.classList.contains("btn-primary")).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -255,6 +265,7 @@ describe("FileEditor", () => {
|
|||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
expandEditorOptions();
|
||||||
expect(screen.getByRole("button", { name: /toggle line numbers/i })).toHaveAttribute("aria-pressed", "false");
|
expect(screen.getByRole("button", { name: /toggle line numbers/i })).toHaveAttribute("aria-pressed", "false");
|
||||||
expect(screen.getByRole("button", { name: /toggle line numbers/i })).toHaveAttribute("title", "Toggle line numbers");
|
expect(screen.getByRole("button", { name: /toggle line numbers/i })).toHaveAttribute("title", "Toggle line numbers");
|
||||||
});
|
});
|
||||||
@@ -276,6 +287,7 @@ describe("FileEditor", () => {
|
|||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
expandEditorOptions();
|
||||||
fireEvent.click(screen.getByRole("button", { name: /toggle line numbers/i }));
|
fireEvent.click(screen.getByRole("button", { name: /toggle line numbers/i }));
|
||||||
expect(onToggleLineNumbers).toHaveBeenCalledTimes(1);
|
expect(onToggleLineNumbers).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
@@ -324,6 +336,7 @@ describe("FileEditor", () => {
|
|||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
expandEditorOptions();
|
||||||
fireEvent.click(screen.getByRole("button", { name: /preview mode/i }));
|
fireEvent.click(screen.getByRole("button", { name: /preview mode/i }));
|
||||||
expect(document.querySelector(".file-editor-line-numbers")).not.toBeInTheDocument();
|
expect(document.querySelector(".file-editor-line-numbers")).not.toBeInTheDocument();
|
||||||
expect(screen.queryByRole("button", { name: /toggle line numbers/i })).not.toBeInTheDocument();
|
expect(screen.queryByRole("button", { name: /toggle line numbers/i })).not.toBeInTheDocument();
|
||||||
@@ -345,6 +358,46 @@ describe("FileEditor", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("editor toolbar options collapse", () => {
|
||||||
|
it("secondary actions are collapsed by default for markdown and non-markdown files", () => {
|
||||||
|
const { rerender } = render(<FileEditor content="const x = 1;" onChange={vi.fn()} filePath="script.ts" onToggleLineNumbers={vi.fn()} />);
|
||||||
|
|
||||||
|
expect(screen.queryByRole("button", { name: /toggle line numbers/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 word wrap/i })).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("expanding shows line-number and wrap toggles", () => {
|
||||||
|
render(<FileEditor content="const x = 1;" onChange={vi.fn()} filePath="script.ts" onToggleLineNumbers={vi.fn()} />);
|
||||||
|
|
||||||
|
expandEditorOptions();
|
||||||
|
expect(screen.getByRole("button", { name: /toggle line numbers/i })).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole("button", { name: /toggle word wrap/i })).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("collapsing hides the toggles again", () => {
|
||||||
|
render(<FileEditor content="const x = 1;" onChange={vi.fn()} filePath="script.ts" onToggleLineNumbers={vi.fn()} />);
|
||||||
|
|
||||||
|
expandEditorOptions();
|
||||||
|
expandEditorOptions();
|
||||||
|
expect(screen.queryByRole("button", { name: /toggle line numbers/i })).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByRole("button", { name: /toggle word wrap/i })).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("aria-expanded reflects state", () => {
|
||||||
|
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 });
|
||||||
|
expect(optionsButton).toHaveAttribute("aria-expanded", "false");
|
||||||
|
|
||||||
|
fireEvent.click(optionsButton);
|
||||||
|
expect(optionsButton).toHaveAttribute("aria-expanded", "true");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("mobile toolbar CSS", () => {
|
describe("mobile toolbar CSS", () => {
|
||||||
it("keeps file editor toolbar action buttons at a shared mobile touch target size", () => {
|
it("keeps file editor toolbar action buttons at a shared mobile touch target size", () => {
|
||||||
const css = loadAllAppCss();
|
const css = loadAllAppCss();
|
||||||
|
|||||||
Reference in New Issue
Block a user