Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions rtl-spec/components/sidebar-file-tree.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,34 @@ describe('SidebarFileTree component', () => {
expect(editorMosaic.files.get('tester.js')).toBe(EditorPresence.Pending);
});

it('fails to create new editors (file name inside subdir)', async () => {
const user = userEvent.setup();
const { container } = render(<SidebarFileTree appState={store} />);
const EDITOR_NEW_NAME = 'subdir/tester.js';

expect(editorMosaic.files.get(EDITOR_NEW_NAME)).toBe(undefined);

// Click the "Add New File" button (by icon)
const addButton = container.querySelector(
'button .bp3-icon-add',
)?.parentElement;
await user.click(addButton!);

// Type the filename and press Enter
const input = container.querySelector(
'#new-file-input',
) as HTMLInputElement;
await user.type(input, `${EDITOR_NEW_NAME}{Enter}`);

// Wait for error dialog to be called
await waitFor(() => {
console.log('store.showErrorDialog: ', store.showErrorDialog);
expect(store.showErrorDialog).toHaveBeenCalledWith(
`Invalid filename "${EDITOR_NEW_NAME}": inside subdir not supported yet`,
);
});
});

it('can delete editors', async () => {
const user = userEvent.setup();
const { container } = render(<SidebarFileTree appState={store} />);
Expand Down Expand Up @@ -237,6 +265,36 @@ describe('SidebarFileTree component', () => {
expect(editorMosaic.files.get(EDITOR_NAME)).toBe(EditorPresence.Pending);
});

it('fails if trying to rename an editor to an unsupported name (file inside subdir)', async () => {
const user = userEvent.setup();
const EDITOR_NAME = 'index.html';
const EDITOR_NEW_NAME = 'msg/data.json';

store.showInputDialog = vi.fn().mockResolvedValueOnce(EDITOR_NEW_NAME);
store.showErrorDialog = vi.fn().mockResolvedValueOnce(true);

const { container } = render(<SidebarFileTree appState={store} />);

// Right-click on index.html to open context menu
const fileLabel = Array.from(container.querySelectorAll('.pointer')).find(
(el) => el.textContent === EDITOR_NAME,
) as HTMLElement;
fireEvent.contextMenu(fileLabel);

// Click the "Rename" menu item
const renameItem = await screen.findByText('Rename');
await user.click(renameItem);

// Wait for error dialog to be called
await waitFor(() => {
expect(store.showErrorDialog).toHaveBeenCalledWith(
`Invalid filename "${EDITOR_NEW_NAME}": inside subdir not supported yet`,
);
});

expect(editorMosaic.files.get(EDITOR_NAME)).toBe(EditorPresence.Pending);
});

it('fails if trying to rename an editor to an existing name', async () => {
const user = userEvent.setup();
const EXISTED_NAME = 'styles.css';
Expand Down
24 changes: 22 additions & 2 deletions src/renderer/components/sidebar-file-tree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export const SidebarFileTree = observer(
}

public render() {
const { editorMosaic } = this.props.appState;
const { editorMosaic, showErrorDialog } = this.props.appState;
const { files, focusedFile } = editorMosaic;

const fileList: TreeNodeInfo[] = Array.from(files)
Expand Down Expand Up @@ -108,10 +108,23 @@ export const SidebarFileTree = observer(
<input
className={classNames(Classes.INPUT, Classes.FILL, Classes.SMALL)}
style={{ width: `100%`, padding: 0 }}
onKeyDown={(e) => {
onKeyDown={async (e) => {
if (e.key === 'Escape') {
e.currentTarget.blur();
} else if (e.key === 'Enter') {
const filename = e.currentTarget.value.trim();
if (filename.includes('/') || filename.includes('\\')) {
await showErrorDialog(
`Invalid filename "${filename}": inside subdir not supported yet`,
);
return;
}
if (!isSupportedFile(filename)) {
await showErrorDialog(
`Invalid filename "${filename}": Must be a file ending in .cjs, .js, .mjs, .html, .css, or .json`,
);
return;
}
this.createEditor(e.currentTarget.value as EditorId);
e.currentTarget.blur();
}
Expand Down Expand Up @@ -189,6 +202,13 @@ export const SidebarFileTree = observer(

if (!id) return;

if (id.includes('/') || id.includes('\\')) {
await appState.showErrorDialog(
`Invalid filename "${id}": inside subdir not supported yet`,
);
return;
}

if (
id.endsWith('.json') &&
[PACKAGE_NAME, 'package-lock.json'].includes(id)
Expand Down
6 changes: 4 additions & 2 deletions src/renderer/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1023,14 +1023,16 @@ export class AppState {
});
}

public async showErrorDialog(label: string | JSX.Element): Promise<void> {
public showErrorDialog = async (
label: string | JSX.Element,
): Promise<void> => {
await this.showGenericDialog({
label,
ok: 'Close',
type: GenericDialogType.warning,
wantsInput: false,
});
}
};

/**
* Ensure that any buffered console output is
Expand Down