@@ -2,18 +2,41 @@ import { fireEvent, render, screen } from "@testing-library/react";
22import { createStore , Provider } from "jotai" ;
33import { afterEach , describe , expect , it , vi } from "vitest" ;
44import { localeAtom } from "../../../../atoms/app-ui" ;
5- import { activeFilePathAtomFamily } from "../../../workspace/atoms" ;
5+ import {
6+ activeFilePathAtomFamily ,
7+ type OpenFile ,
8+ openFilesAtomFamily ,
9+ } from "../../../workspace/atoms" ;
610import { EditorPaneCard } from "./editor-pane-card" ;
711
812const mocks = vi . hoisted ( ( ) => ( {
9- editorState : { marker : "editor-state" } ,
13+ editorState : {
14+ marker : "editor-state" ,
15+ currentFile : undefined as OpenFile | undefined ,
16+ } ,
1017 mockUseCodeEditorActions : vi . fn ( ) ,
1118 mockCodeEditorHost : vi . fn ( ( ) => < div data-testid = "editor-host" > Editor Host</ div > ) ,
1219 mockCodeEditorDesktopHeaderActions : vi . fn ( ( ) => (
13- < div data-testid = "editor-toolbar" > Editor Toolbar</ div >
20+ < div data-testid = "editor-toolbar" role = "toolbar" aria-label = "Editor actions" >
21+ Editor Toolbar
22+ </ div >
1423 ) ) ,
1524} ) ) ;
1625
26+ vi . mock ( "../../../../lib/i18n" , ( ) => ( {
27+ useTranslation : ( ) => ( key : string , params ?: Record < string , string > ) => {
28+ const dictionary : Record < string , string > = {
29+ "action.close" : "Close" ,
30+ "code_editor.unsaved_changes" : "Unsaved changes" ,
31+ "code_editor.close_unsaved_title" : "Discard unsaved changes?" ,
32+ "code_editor.close_unsaved_description" : `${ params ?. name ?? "File" } has unsaved changes.` ,
33+ "code_editor.discard_and_close" : "Discard and Close" ,
34+ "common.cancel" : "Cancel" ,
35+ } ;
36+ return dictionary [ key ] ?? key ;
37+ } ,
38+ } ) ) ;
39+
1740vi . mock ( "../../../code-editor/actions/use-code-editor-actions" , ( ) => ( {
1841 useCodeEditorActions : mocks . mockUseCodeEditorActions ,
1942} ) ) ;
@@ -51,6 +74,10 @@ describe("EditorPaneCard", () => {
5174 expect ( screen . getByText ( "app.tsx" ) ) . toBeInTheDocument ( ) ;
5275 expect ( screen . queryByText ( "src/app.tsx" ) ) . not . toBeInTheDocument ( ) ;
5376 expect ( screen . getByTestId ( "editor-toolbar" ) ) . toBeInTheDocument ( ) ;
77+ expect ( screen . getByTestId ( "editor-toolbar" ) . closest ( ".panel-header" ) ) . toBeTruthy ( ) ;
78+ expect (
79+ screen . queryByText ( "Editor Toolbar" ) ?. closest ( ".editor-pane-card__toolbar-row" )
80+ ) . toBeNull ( ) ;
5481 expect ( screen . getByTestId ( "editor-host" ) ) . toBeInTheDocument ( ) ;
5582 expect ( mocks . mockCodeEditorDesktopHeaderActions ) . toHaveBeenCalledWith (
5683 expect . objectContaining ( {
@@ -75,4 +102,54 @@ describe("EditorPaneCard", () => {
75102 expect ( onSplitPane ) . toHaveBeenNthCalledWith ( 2 , "pane-1" , "vertical" ) ;
76103 expect ( onClosePane ) . toHaveBeenCalledWith ( "pane-1" ) ;
77104 } ) ;
105+
106+ it ( "marks dirty editor pane titles and confirms before closing dirty files" , ( ) => {
107+ const store = createStore ( ) ;
108+ const onClosePane = vi . fn ( ) ;
109+ const onSplitPane = vi . fn ( ) ;
110+
111+ mocks . mockUseCodeEditorActions . mockReturnValue ( mocks . editorState ) ;
112+ store . set ( localeAtom , "en" ) ;
113+ store . set ( activeFilePathAtomFamily ( "ws-123" ) , "src/app.tsx" ) ;
114+ store . set ( openFilesAtomFamily ( "ws-123" ) , {
115+ "src/app.tsx" : {
116+ kind : "text" ,
117+ path : "src/app.tsx" ,
118+ content : "changed" ,
119+ savedContent : "saved" ,
120+ baseHash : "hash-1" ,
121+ isDirty : true ,
122+ } ,
123+ } ) ;
124+
125+ render (
126+ < Provider store = { store } >
127+ < EditorPaneCard
128+ workspaceId = "ws-123"
129+ paneId = "pane-1"
130+ onClosePane = { onClosePane }
131+ onSplitPane = { onSplitPane }
132+ />
133+ </ Provider >
134+ ) ;
135+
136+ const title = screen . getByText ( "app.tsx" ) ;
137+ const titleElement = title . closest ( ".panel-header__title" ) ;
138+ const dirtyMeta = titleElement ?. nextElementSibling ;
139+
140+ expect ( dirtyMeta ) . toHaveClass ( "panel-header__meta" ) ;
141+ expect ( dirtyMeta ?. querySelector ( ".editor-pane-card__dirty-indicator" ) ) . toBeTruthy ( ) ;
142+
143+ fireEvent . click ( screen . getByRole ( "button" , { name : "Close" } ) ) ;
144+ expect ( onClosePane ) . not . toHaveBeenCalled ( ) ;
145+ expect ( screen . getByRole ( "dialog" , { name : "Discard unsaved changes?" } ) ) . toBeInTheDocument ( ) ;
146+
147+ fireEvent . click ( screen . getByRole ( "button" , { name : "Cancel" } ) ) ;
148+ expect ( onClosePane ) . not . toHaveBeenCalled ( ) ;
149+
150+ fireEvent . click ( screen . getByRole ( "button" , { name : "Close" } ) ) ;
151+ fireEvent . click ( screen . getByRole ( "button" , { name : "Discard and Close" } ) ) ;
152+
153+ expect ( onClosePane ) . toHaveBeenCalledWith ( "pane-1" ) ;
154+ } ) ;
78155} ) ;
0 commit comments