FN-7031: preview binary media files in dock Files viewer
Render browser-native previews for right-dock file selections while preserving text editing behavior. - Add image, video, audio, and PDF preview rendering through the workspace download URL. - Keep previewable and known binary files out of the text editor, with a read-only fallback for unsupported binaries. - Cover compact and two-pane dock preview behavior with tests, docs, and a patch changeset. Files changed: .changeset/fn-7031-dock-files-binary-preview.md | 7 + docs/dashboard-guide.md | 2 +- .../dashboard/app/components/DockFilesView.css | 45 ++++++ .../dashboard/app/components/DockFilesView.tsx | 85 ++++++++++- .../components/__tests__/DockFilesView.test.tsx | 170 ++++++++++++++++++++- 5 files changed, 303 insertions(+), 6 deletions(-) Fusion-Task-Id: FN-7031 Fusion-Task-Lineage: a52bdd57-bb3e-494e-85a8-81326a1548e2
This commit is contained in:
7
.changeset/fn-7031-dock-files-binary-preview.md
Normal file
7
.changeset/fn-7031-dock-files-binary-preview.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Preview image, video, audio, and PDF files natively in the right-dock Files viewer.
|
||||
category: fix
|
||||
dev: Reuses the shared file-preview classification and download route in DockFilesView.
|
||||
@@ -52,7 +52,7 @@ The **Right Dock Panel** experiment is enabled by default. To disable it, open *
|
||||
|
||||
When enabled on desktop or tablet project screens, the right dock is a persistent far-right tools sidebar in the project content row. Use the in-dock collapse control to switch between the full tool panel and the compact far-right rail; the selected tool, expanded/collapsed state, width, and expanded modal size persist across reloads.
|
||||
|
||||
The dock toolbar has built-in inline tool panels for **Activity**, **Activity Log**, **Git Manager**, **Files**, and project tool launchers such as **Import from GitHub** / **Import Tasks** workflow entry points and **Automation** actions when available. **Activity**, **Activity Log**, **Git Manager**, and **Files** render in embedded mode inside the dock instead of opening fixed popup overlays; **Files** opens by default and is the fallback when browser storage points at a removed dock key. Inline dock views have an expand button that opens the same view in a resizable modal for more room. Plugin overflow views may add additional right-dock tool tabs, except plugin destinations that explicitly belong in the left sidebar.
|
||||
The dock toolbar has built-in inline tool panels for **Activity**, **Activity Log**, **Git Manager**, **Files**, and project tool launchers such as **Import from GitHub** / **Import Tasks** workflow entry points and **Automation** actions when available. **Activity**, **Activity Log**, **Git Manager**, and **Files** render in embedded mode inside the dock instead of opening fixed popup overlays; **Files** opens by default and is the fallback when browser storage points at a removed dock key. Inline dock views have an expand button that opens the same view in a resizable modal for more room. The right-dock **Files** viewer and its expanded pop-out match the Files modal for browser-previewable file types: image, video/movie, audio, and PDF selections render as native browser previews, while editable text files keep the editor and save flow. Plugin overflow views may add additional right-dock tool tabs, except plugin destinations that explicitly belong in the left sidebar.
|
||||
|
||||
Use the desktop/tablet right dock this way:
|
||||
|
||||
|
||||
@@ -109,6 +109,39 @@ Hidden until a file is selected; when selected it overlays the tree as the singl
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
/*
|
||||
FNXC:RightDockFiles 2026-06-25-00:00:
|
||||
The dock Files viewer shares FileBrowserModal's native preview classes but needs dock-scoped flex sizing so image/video/audio/PDF previews fit both the compact single-panel stack and the two-pane pop-out without overflowing or showing the CodeMirror shell.
|
||||
*/
|
||||
.dock-files-preview {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.dock-files-preview__media {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.dock-files-preview .file-browser-preview-media--image,
|
||||
.dock-files-preview .file-browser-preview-media--video {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.dock-files-preview .file-browser-preview-media--audio {
|
||||
width: min(100%, calc(var(--space-xl) * 18));
|
||||
}
|
||||
|
||||
.dock-files-preview .file-browser-preview-media--pdf {
|
||||
width: 100%;
|
||||
min-height: 0;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.dock-files-viewer__status {
|
||||
padding: var(--space-md);
|
||||
font-size: var(--font-size-xs);
|
||||
@@ -203,3 +236,15 @@ DETERMINISTIC two-pane layout for the RightDockExpandModal pop-out. Driven by th
|
||||
.dock-files-view--two-pane .dock-files-view__viewer .dock-files-viewer__back {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.dock-files-preview {
|
||||
padding: var(--space-md);
|
||||
}
|
||||
|
||||
.dock-files-preview .file-browser-preview-media--image,
|
||||
.dock-files-preview .file-browser-preview-media--video,
|
||||
.dock-files-preview .file-browser-preview-media--pdf {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ArrowLeft, Maximize2, Save } from "lucide-react";
|
||||
import type { PluginDashboardViewContext } from "../plugins/types";
|
||||
import { downloadFileUrl } from "../api";
|
||||
import { useWorkspaceFileBrowser } from "../hooks/useWorkspaceFileBrowser";
|
||||
import { useWorkspaceFileEditor } from "../hooks/useWorkspaceFileEditor";
|
||||
import { getScopedItem, removeScopedItem, scopedKey, setScopedItem } from "../utils/projectStorage";
|
||||
import { getFilePreviewKind, IMAGE_PREVIEW_EXTENSIONS, VIDEO_PREVIEW_EXTENSIONS, AUDIO_PREVIEW_EXTENSIONS, PDF_PREVIEW_EXTENSIONS } from "../utils/file-preview-kind";
|
||||
import { FileBrowser } from "./FileBrowser";
|
||||
import { FileEditor } from "./FileEditor";
|
||||
import "./DockFilesView.css";
|
||||
@@ -28,6 +30,30 @@ Share the current-file path through scoped localStorage (`kb-dashboard-dock-file
|
||||
*/
|
||||
export const DOCK_FILES_CURRENT_KEY = "kb-dashboard-dock-files-current";
|
||||
|
||||
const BINARY_EXTENSIONS = new Set([
|
||||
...IMAGE_PREVIEW_EXTENSIONS,
|
||||
...VIDEO_PREVIEW_EXTENSIONS,
|
||||
...AUDIO_PREVIEW_EXTENSIONS,
|
||||
...PDF_PREVIEW_EXTENSIONS,
|
||||
".exe", ".dll", ".so", ".dylib",
|
||||
".zip", ".tar", ".gz", ".bz2", ".xz", ".7z", ".rar",
|
||||
".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx",
|
||||
".avi", ".mkv", ".flv",
|
||||
".woff", ".woff2", ".ttf", ".otf", ".eot",
|
||||
".wasm", ".bin",
|
||||
]);
|
||||
|
||||
function isBinaryFile(filename?: string | null): boolean {
|
||||
if (!filename) {
|
||||
return false;
|
||||
}
|
||||
const dotIndex = filename.lastIndexOf(".");
|
||||
if (dotIndex < 0) {
|
||||
return false;
|
||||
}
|
||||
return BINARY_EXTENSIONS.has(filename.slice(dotIndex).toLowerCase());
|
||||
}
|
||||
|
||||
/*
|
||||
FNXC:RightDockFiles 2026-06-22-00:00:
|
||||
The right-dock Files tool opens a clicked file INLINE inside the dock as a read-only viewer instead of immediately launching the resizable/movable FileBrowserModal.
|
||||
@@ -78,9 +104,22 @@ export function DockFilesView({ projectId, openFile, layout = "auto" }: DockFile
|
||||
return () => window.removeEventListener("storage", onStorage);
|
||||
}, [projectId]);
|
||||
|
||||
const selectedPreviewKind = useMemo(() => getFilePreviewKind(selectedFile), [selectedFile]);
|
||||
const isPreviewOnlyFile = selectedPreviewKind !== null;
|
||||
const isReadOnlyBinaryFile = Boolean(selectedFile && !isPreviewOnlyFile && isBinaryFile(selectedFile));
|
||||
const previewUrl = useMemo(() => {
|
||||
if (!selectedFile || !selectedPreviewKind) {
|
||||
return null;
|
||||
}
|
||||
return downloadFileUrl("project", selectedFile, projectId);
|
||||
}, [projectId, selectedFile, selectedPreviewKind]);
|
||||
|
||||
/*
|
||||
FNXC:RightDockFiles 2026-06-22-16:28:
|
||||
The right-sidebar file viewer must be the same editor surface as the modal/mobile file browser: real workspace editor state, visible toolbar options, Preview/Edit for markdown, Line #, and Wrap. Use the shared editor hook instead of the old read-only content fetch so edits can be saved and the toolbar is not a reduced sidebar-only variant.
|
||||
|
||||
FNXC:RightDockFiles 2026-06-25-00:00:
|
||||
Known image/video/audio/PDF selections render through browser-native previews loaded from the workspace-safe download route. Keep those preview-only files out of CodeMirror so binary bytes are never fetched as editor text; editable text keeps the existing FileEditor + Save path, while known non-preview binary extensions stay read-only without loading bytes as editor content.
|
||||
*/
|
||||
const {
|
||||
content,
|
||||
@@ -90,7 +129,7 @@ export function DockFilesView({ projectId, openFile, layout = "auto" }: DockFile
|
||||
error: contentError,
|
||||
save,
|
||||
hasChanges,
|
||||
} = useWorkspaceFileEditor("project", selectedFile, Boolean(selectedFile), projectId);
|
||||
} = useWorkspaceFileEditor("project", selectedFile, Boolean(selectedFile) && !isPreviewOnlyFile && !isReadOnlyBinaryFile, projectId);
|
||||
|
||||
const handleBack = useCallback(() => selectFile(null), [selectFile]);
|
||||
const handlePopOut = useCallback(() => {
|
||||
@@ -99,6 +138,9 @@ export function DockFilesView({ projectId, openFile, layout = "auto" }: DockFile
|
||||
const handleToggleLineNumbers = useCallback(() => setShowLineNumbers((current) => !current), []);
|
||||
|
||||
const fileName = selectedFile ? selectedFile.split("/").pop() || selectedFile : "";
|
||||
const selectedPreviewTitle = selectedFile
|
||||
? t("fileBrowser.previewTitle", "Preview for {{file}}", { file: selectedFile })
|
||||
: "";
|
||||
|
||||
// FNXC:Files 2026-06-22-00:00:
|
||||
// `data-selected` on the root lets the container query distinguish "no file selected" (narrow: viewer pane hidden so only the tree shows) from "file selected" (narrow: viewer pane covers the stack). When wide both panes are always visible regardless of this flag.
|
||||
@@ -155,7 +197,7 @@ export function DockFilesView({ projectId, openFile, layout = "auto" }: DockFile
|
||||
>
|
||||
<Maximize2 size={14} />
|
||||
</button>
|
||||
{selectedFile ? (
|
||||
{selectedFile && !isPreviewOnlyFile && !isReadOnlyBinaryFile ? (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-sm btn-primary dock-files-viewer__save"
|
||||
@@ -173,6 +215,43 @@ export function DockFilesView({ projectId, openFile, layout = "auto" }: DockFile
|
||||
<div className="dock-files-viewer__status dock-files-viewer__empty" data-testid="right-dock-files-empty">
|
||||
{t("fileViewer.selectAFile", "Select a file")}
|
||||
</div>
|
||||
) : previewUrl && selectedPreviewKind ? (
|
||||
<div className={`file-browser-preview file-browser-preview--${selectedPreviewKind} dock-files-preview`}>
|
||||
{selectedPreviewKind === "image" ? (
|
||||
<img
|
||||
src={previewUrl}
|
||||
alt={selectedFile}
|
||||
className="file-browser-preview-media file-browser-preview-media--image dock-files-preview__media"
|
||||
/>
|
||||
) : null}
|
||||
{selectedPreviewKind === "video" ? (
|
||||
<video
|
||||
src={previewUrl}
|
||||
controls
|
||||
aria-label={selectedPreviewTitle}
|
||||
className="file-browser-preview-media file-browser-preview-media--video dock-files-preview__media"
|
||||
/>
|
||||
) : null}
|
||||
{selectedPreviewKind === "audio" ? (
|
||||
<audio
|
||||
src={previewUrl}
|
||||
controls
|
||||
aria-label={selectedPreviewTitle}
|
||||
className="file-browser-preview-media file-browser-preview-media--audio dock-files-preview__media"
|
||||
/>
|
||||
) : null}
|
||||
{selectedPreviewKind === "pdf" ? (
|
||||
<iframe
|
||||
src={previewUrl}
|
||||
title={selectedPreviewTitle}
|
||||
className="file-browser-preview-media file-browser-preview-media--pdf dock-files-preview__media"
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
) : isReadOnlyBinaryFile ? (
|
||||
<div className="dock-files-viewer__status" data-testid="right-dock-files-binary-read-only">
|
||||
{t("fileBrowser.binaryReadOnly", "Binary file — read only")}
|
||||
</div>
|
||||
) : contentLoading ? (
|
||||
<div className="dock-files-viewer__status">{t("common.loading", "Loading...")}</div>
|
||||
) : contentError ? (
|
||||
|
||||
@@ -12,11 +12,24 @@ Proves the current-file path is shared between the dock instance and the popped-
|
||||
*/
|
||||
|
||||
vi.mock("react-i18next", () => ({
|
||||
useTranslation: () => ({ t: (_key: string, fallback?: string) => fallback ?? _key }),
|
||||
useTranslation: () => ({
|
||||
t: (_key: string, fallback?: string, options?: Record<string, string>) => {
|
||||
const value = fallback ?? _key;
|
||||
return options?.file ? value.replace("{{file}}", options.file) : value;
|
||||
},
|
||||
}),
|
||||
}));
|
||||
|
||||
const entries: FileNode[] = [
|
||||
let entries: FileNode[] = [];
|
||||
|
||||
const defaultEntries: FileNode[] = [
|
||||
{ name: "readme.md", type: "file", size: 10, mtime: "2026-01-15T10:30:00Z" },
|
||||
{ name: "changed.txt", type: "file", size: 12, mtime: "2026-01-15T10:30:00Z" },
|
||||
{ name: "assets/Logo.PNG", type: "file", size: 1024, mtime: "2026-01-15T10:30:00Z" },
|
||||
{ name: "media/demo.mp4", type: "file", size: 2048, mtime: "2026-01-15T10:30:00Z" },
|
||||
{ name: "sounds/theme.MP3", type: "file", size: 2048, mtime: "2026-01-15T10:30:00Z" },
|
||||
{ name: "docs/manual.PDF", type: "file", size: 4096, mtime: "2026-01-15T10:30:00Z" },
|
||||
{ name: "build/output.bin", type: "file", size: 8192, mtime: "2026-01-15T10:30:00Z" },
|
||||
];
|
||||
|
||||
const dockFilesCss = readFileSync(resolve(__dirname, "../DockFilesView.css"), "utf8");
|
||||
@@ -34,9 +47,42 @@ vi.mock("../../hooks/useWorkspaceFileBrowser", () => ({
|
||||
|
||||
const mockFetchContent = vi.fn(() => Promise.resolve({ content: "# hi" }));
|
||||
const mockSaveContent = vi.fn(() => Promise.resolve({ mtime: "2026-01-15T10:31:00Z" }));
|
||||
const mockDownloadFileUrl = vi.fn((workspace: string, filePath: string, projectId?: string) => {
|
||||
const params = new URLSearchParams({ workspace });
|
||||
if (projectId) params.set("projectId", projectId);
|
||||
return `/api/files/${encodeURIComponent(filePath)}?${params.toString()}`;
|
||||
});
|
||||
vi.mock("../../api", () => ({
|
||||
fetchWorkspaceFileContent: (...args: unknown[]) => mockFetchContent(...(args as [])),
|
||||
saveWorkspaceFileContent: (...args: unknown[]) => mockSaveContent(...(args as [])),
|
||||
downloadFileUrl: (...args: unknown[]) => mockDownloadFileUrl(...(args as [string, string, string | undefined])),
|
||||
}));
|
||||
|
||||
const capturedEditorHookCalls: Array<{
|
||||
workspace: string;
|
||||
filePath: string | null;
|
||||
enabled: boolean;
|
||||
projectId?: string;
|
||||
}> = [];
|
||||
const mockSetContent = vi.fn();
|
||||
const mockSave = vi.fn(() => Promise.resolve());
|
||||
|
||||
vi.mock("../../hooks/useWorkspaceFileEditor", () => ({
|
||||
useWorkspaceFileEditor: (workspace: string, filePath: string | null, enabled: boolean, projectId?: string) => {
|
||||
capturedEditorHookCalls.push({ workspace, filePath, enabled, projectId });
|
||||
const hasChanges = filePath === "changed.txt";
|
||||
return {
|
||||
content: filePath ? `content for ${filePath}` : "",
|
||||
setContent: mockSetContent,
|
||||
originalContent: hasChanges ? "original" : filePath ? `content for ${filePath}` : "",
|
||||
loading: false,
|
||||
saving: false,
|
||||
error: null,
|
||||
save: mockSave,
|
||||
hasChanges,
|
||||
mtime: null,
|
||||
};
|
||||
},
|
||||
}));
|
||||
|
||||
const capturedFileEditorProps: Array<{
|
||||
@@ -82,9 +128,14 @@ const KEY = scopedKey("kb-dashboard-dock-files-current", PROJECT_ID);
|
||||
describe("DockFilesView shared current-file state", () => {
|
||||
beforeEach(() => {
|
||||
window.localStorage.clear();
|
||||
entries = [...defaultEntries];
|
||||
mockFetchContent.mockClear();
|
||||
mockSaveContent.mockClear();
|
||||
mockDownloadFileUrl.mockClear();
|
||||
mockSetContent.mockClear();
|
||||
mockSave.mockClear();
|
||||
capturedFileEditorProps.length = 0;
|
||||
capturedEditorHookCalls.length = 0;
|
||||
});
|
||||
afterEach(() => cleanup());
|
||||
|
||||
@@ -165,4 +216,119 @@ describe("DockFilesView shared current-file state", () => {
|
||||
expect(latest?.onToggleLineNumbers).toEqual(expect.any(Function));
|
||||
expect(screen.getByTestId("right-dock-files-save")).toBeDisabled();
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
layout: "auto" as const,
|
||||
file: "assets/Logo.PNG",
|
||||
selector: "img.file-browser-preview-media--image",
|
||||
attr: "src",
|
||||
expectedEnabled: false,
|
||||
},
|
||||
{
|
||||
layout: "auto" as const,
|
||||
file: "media/demo.mp4",
|
||||
selector: "video.file-browser-preview-media--video",
|
||||
attr: "src",
|
||||
expectedEnabled: false,
|
||||
},
|
||||
{
|
||||
layout: "auto" as const,
|
||||
file: "sounds/theme.MP3",
|
||||
selector: "audio.file-browser-preview-media--audio",
|
||||
attr: "src",
|
||||
expectedEnabled: false,
|
||||
},
|
||||
{
|
||||
layout: "auto" as const,
|
||||
file: "docs/manual.PDF",
|
||||
selector: "iframe.file-browser-preview-media--pdf",
|
||||
attr: "src",
|
||||
expectedEnabled: false,
|
||||
},
|
||||
{
|
||||
layout: "two-pane" as const,
|
||||
file: "assets/Logo.PNG",
|
||||
selector: "img.file-browser-preview-media--image",
|
||||
attr: "src",
|
||||
expectedEnabled: false,
|
||||
},
|
||||
{
|
||||
layout: "two-pane" as const,
|
||||
file: "media/demo.mp4",
|
||||
selector: "video.file-browser-preview-media--video",
|
||||
attr: "src",
|
||||
expectedEnabled: false,
|
||||
},
|
||||
{
|
||||
layout: "two-pane" as const,
|
||||
file: "sounds/theme.MP3",
|
||||
selector: "audio.file-browser-preview-media--audio",
|
||||
attr: "src",
|
||||
expectedEnabled: false,
|
||||
},
|
||||
{
|
||||
layout: "two-pane" as const,
|
||||
file: "docs/manual.PDF",
|
||||
selector: "iframe.file-browser-preview-media--pdf",
|
||||
attr: "src",
|
||||
expectedEnabled: false,
|
||||
},
|
||||
])("renders $file as a native preview in $layout layout without loading the editor", async ({ layout, file, selector, attr }) => {
|
||||
/*
|
||||
FNXC:RightDockFiles 2026-06-25-00:00:
|
||||
Symptom verification: browser-previewable files in both compact and pop-out dock layouts must use native preview elements backed by downloadFileUrl, not the FileEditor/CodeMirror binary-text path.
|
||||
*/
|
||||
render(<DockFilesView projectId={PROJECT_ID} layout={layout} />);
|
||||
fireEvent.click(screen.getByText(file));
|
||||
|
||||
await waitFor(() => expect(document.querySelector(selector)).toBeInTheDocument());
|
||||
const preview = document.querySelector(selector);
|
||||
expect(preview).toHaveAttribute(attr, `/api/files/${encodeURIComponent(file)}?workspace=project&projectId=${PROJECT_ID}`);
|
||||
if (selector.startsWith("video") || selector.startsWith("audio")) {
|
||||
expect(preview).toHaveAttribute("controls");
|
||||
expect(preview).toHaveAttribute("aria-label", `Preview for ${file}`);
|
||||
}
|
||||
if (selector.startsWith("iframe")) {
|
||||
expect(preview).toHaveAttribute("title", `Preview for ${file}`);
|
||||
}
|
||||
expect(screen.queryByTestId("mock-file-editor")).toBeNull();
|
||||
expect(screen.queryByTestId("right-dock-files-save")).toBeNull();
|
||||
expect(capturedEditorHookCalls.at(-1)).toMatchObject({ workspace: "project", filePath: file, enabled: false, projectId: PROJECT_ID });
|
||||
expect(mockDownloadFileUrl).toHaveBeenLastCalledWith("project", file, PROJECT_ID);
|
||||
});
|
||||
|
||||
it("keeps text files editable with Save when changes exist", async () => {
|
||||
render(<DockFilesView projectId={PROJECT_ID} layout="auto" />);
|
||||
fireEvent.click(screen.getByText("changed.txt"));
|
||||
|
||||
await waitFor(() => expect(screen.getByTestId("mock-file-editor")).toHaveAttribute("data-file-path", "changed.txt"));
|
||||
expect(screen.getByTestId("right-dock-files-save")).toBeEnabled();
|
||||
expect(document.querySelector(".file-browser-preview")).not.toBeInTheDocument();
|
||||
expect(capturedEditorHookCalls.at(-1)).toMatchObject({ workspace: "project", filePath: "changed.txt", enabled: true, projectId: PROJECT_ID });
|
||||
});
|
||||
|
||||
it("keeps known non-preview binary files read-only without rendering garbage editor content", () => {
|
||||
render(<DockFilesView projectId={PROJECT_ID} layout="two-pane" />);
|
||||
fireEvent.click(screen.getByText("build/output.bin"));
|
||||
|
||||
expect(screen.getByTestId("right-dock-files-binary-read-only")).toHaveTextContent("Binary file — read only");
|
||||
expect(screen.queryByTestId("mock-file-editor")).toBeNull();
|
||||
expect(screen.queryByTestId("right-dock-files-save")).toBeNull();
|
||||
expect(document.querySelector(".file-browser-preview")).not.toBeInTheDocument();
|
||||
expect(capturedEditorHookCalls.at(-1)).toMatchObject({ workspace: "project", filePath: "build/output.bin", enabled: false, projectId: PROJECT_ID });
|
||||
});
|
||||
|
||||
it("clears stale preview state when switching from a preview file back to text", async () => {
|
||||
render(<DockFilesView projectId={PROJECT_ID} layout="auto" />);
|
||||
fireEvent.click(screen.getByText("assets/Logo.PNG"));
|
||||
await waitFor(() => expect(document.querySelector("img.file-browser-preview-media--image")).toBeInTheDocument());
|
||||
|
||||
fireEvent.click(screen.getByText("readme.md"));
|
||||
await waitFor(() => expect(screen.getByTestId("mock-file-editor")).toHaveAttribute("data-file-path", "readme.md"));
|
||||
|
||||
expect(document.querySelector("img.file-browser-preview-media--image")).not.toBeInTheDocument();
|
||||
expect(screen.getByTestId("right-dock-files-save")).toBeDisabled();
|
||||
expect(capturedEditorHookCalls.at(-1)).toMatchObject({ workspace: "project", filePath: "readme.md", enabled: true, projectId: PROJECT_ID });
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user