mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-07-26 11:49:16 +00:00
feat: add syntax highlighting to .env editor tab (#1459)
Apply Monaco built-in ini language mode to the .env editor tab, matching the syntax highlighting already used by the FileViewer, diff previews, and Git source diffs for env content. - EditorView.tsx: change env tab language from plaintext to ini - Add EditorView.test.tsx: 3 cases asserting ini/yaml/files tabs - Docs: update editor.mdx to reflect env tab has highlighting
This commit is contained in:
@@ -100,7 +100,7 @@ The editor card has three tabs:
|
||||
| Tab | Purpose |
|
||||
|-----|---------|
|
||||
| `compose.yaml` | YAML editor with syntax highlighting. Always available. |
|
||||
| `.env` | Plaintext editor for the discovered env file. Disabled when the stack has no env file. |
|
||||
| `.env` | Environment file editor with syntax highlighting. Disabled when the stack has no env file. |
|
||||
| **Files** | Browseable directory tree for the stack folder. See the dedicated [Stack File Explorer](/features/stack-file-explorer) page for permissions, upload caps, and protected-file behavior. |
|
||||
|
||||
Above the editor area, the toolbar carries:
|
||||
|
||||
@@ -524,7 +524,7 @@ export function EditorView(props: EditorViewProps) {
|
||||
<Suspense fallback={<div className="w-full h-full" aria-busy="true" />}>
|
||||
<Editor
|
||||
height="100%"
|
||||
language={activeTab === 'compose' ? 'yaml' : 'plaintext'}
|
||||
language={activeTab === 'compose' ? 'yaml' : 'ini'}
|
||||
theme={isDarkMode ? 'vs-dark' : 'vs'}
|
||||
value={activeTab === 'compose' ? safeContent : safeEnvContent}
|
||||
onMount={(editor) => { monacoEditorRef.current = editor; }}
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
import { describe, it, expect, vi, afterEach } from 'vitest';
|
||||
import { render } from '@testing-library/react';
|
||||
import type { ReactNode } from 'react';
|
||||
import { EditorView } from '../EditorView';
|
||||
import type { EditorViewProps } from '../EditorView';
|
||||
|
||||
// Capture Monaco language/value props to assert env tab gets 'ini', compose gets 'yaml'.
|
||||
let lastLanguage: string | undefined;
|
||||
let lastValue: string | undefined;
|
||||
vi.mock('@/lib/monacoLoader', () => ({
|
||||
Editor: ({ language, value }: { language?: string; value?: string }) => {
|
||||
lastLanguage = language;
|
||||
lastValue = value;
|
||||
return <div data-testid="monaco-editor" />;
|
||||
},
|
||||
}));
|
||||
|
||||
// Stub heavy children; this test only asserts the Monaco language prop.
|
||||
vi.mock('../editor-view-blocks', () => ({
|
||||
StackIdentityHeader: () => <div>identity-header</div>,
|
||||
ContainersHealth: () => <div>health-pane</div>,
|
||||
StackLogsSection: () => <div>logs-pane</div>,
|
||||
}));
|
||||
vi.mock('../../StackAnatomyPanel', () => ({
|
||||
default: () => <div>anatomy-pane</div>,
|
||||
}));
|
||||
vi.mock('../StackOperationBanner', () => ({ StackOperationBanner: () => null }));
|
||||
vi.mock('../../ErrorBoundary', () => ({ default: ({ children }: { children: ReactNode }) => <>{children}</> }));
|
||||
vi.mock('@/hooks/use-is-mobile', () => ({ useIsMobile: () => false }));
|
||||
|
||||
function makeProps(over: Partial<EditorViewProps> = {}): EditorViewProps {
|
||||
return {
|
||||
stackName: 'web',
|
||||
isDarkMode: false,
|
||||
activeNode: null,
|
||||
containers: [],
|
||||
containerStats: {},
|
||||
containerStatsError: null,
|
||||
content: '',
|
||||
envContent: '',
|
||||
envExists: false,
|
||||
envFiles: [],
|
||||
selectedEnvFile: '',
|
||||
isFileLoading: false,
|
||||
backupInfo: { exists: false, timestamp: null },
|
||||
gitSourcePendingMap: {},
|
||||
notifications: [],
|
||||
copiedDigest: null,
|
||||
loadingAction: null,
|
||||
stackMisconfigScanning: false,
|
||||
activeTab: 'compose',
|
||||
isEditing: false,
|
||||
editingCompose: false,
|
||||
logsMode: 'structured',
|
||||
can: () => true,
|
||||
isAdmin: false,
|
||||
trivy: { available: false },
|
||||
copiedDigestTimerRef: { current: null },
|
||||
deployStack: vi.fn(),
|
||||
restartStack: vi.fn(),
|
||||
stopStack: vi.fn(),
|
||||
updateStack: vi.fn(),
|
||||
rollbackStack: vi.fn(),
|
||||
scanStackConfig: vi.fn(),
|
||||
enterEditMode: vi.fn(),
|
||||
requestSave: vi.fn(),
|
||||
requestSaveAndDeploy: vi.fn(),
|
||||
discardChanges: vi.fn(),
|
||||
setContent: vi.fn(),
|
||||
setEnvContent: vi.fn(),
|
||||
changeEnvFile: vi.fn(),
|
||||
openLogViewer: vi.fn(),
|
||||
openBashModal: vi.fn(),
|
||||
serviceAction: vi.fn(),
|
||||
setActiveTab: vi.fn(),
|
||||
setLogsMode: vi.fn(),
|
||||
setEditingCompose: vi.fn(),
|
||||
setGitSourceOpen: vi.fn(),
|
||||
setCopiedDigest: vi.fn(),
|
||||
requestDeleteStack: vi.fn(),
|
||||
onRefreshState: vi.fn(),
|
||||
onDismissRecovery: vi.fn(),
|
||||
panelStartedAt: null,
|
||||
onMobileBack: vi.fn(),
|
||||
onCloseEditor: vi.fn(),
|
||||
hasUnsavedChanges: () => false,
|
||||
...over,
|
||||
};
|
||||
}
|
||||
|
||||
describe('EditorView Monaco language prop', () => {
|
||||
afterEach(() => {
|
||||
lastLanguage = undefined;
|
||||
lastValue = undefined;
|
||||
});
|
||||
|
||||
it('passes language="ini" when the env tab is active', () => {
|
||||
render(
|
||||
<EditorView
|
||||
{...makeProps({
|
||||
editingCompose: true,
|
||||
activeTab: 'env',
|
||||
envExists: true,
|
||||
envContent: 'KEY=val\n# a comment',
|
||||
})}
|
||||
/>,
|
||||
);
|
||||
expect(lastLanguage).toBe('ini');
|
||||
expect(lastValue).toBe('KEY=val\n# a comment');
|
||||
});
|
||||
|
||||
it('passes language="yaml" when the compose tab is active', () => {
|
||||
render(
|
||||
<EditorView
|
||||
{...makeProps({
|
||||
editingCompose: true,
|
||||
activeTab: 'compose',
|
||||
content: 'services:\n web:\n image: nginx',
|
||||
})}
|
||||
/>,
|
||||
);
|
||||
expect(lastLanguage).toBe('yaml');
|
||||
expect(lastValue).toBe('services:\n web:\n image: nginx');
|
||||
});
|
||||
|
||||
it('does not mount Monaco when the files tab is active', () => {
|
||||
render(
|
||||
<EditorView
|
||||
{...makeProps({
|
||||
editingCompose: true,
|
||||
activeTab: 'files',
|
||||
})}
|
||||
/>,
|
||||
);
|
||||
// StackFileExplorer replaces Monaco in the files tab path.
|
||||
expect(lastLanguage).toBeUndefined();
|
||||
expect(lastValue).toBeUndefined();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user