Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ const meta: Meta<typeof TemplateVersionEditor> = {
activePath: "main.tf",
template: MockTemplate,
templateVersion: MockTemplateVersion,
defaultFileTree: MockTemplateVersionFileTree,
fileTree: MockTemplateVersionFileTree,
onFileTreeChange: action("onFileTreeChange"),
onPublish: action("onPublish"),
onConfirmPublish: action("onConfirmPublish"),
onCancelPublish: action("onCancelPublish"),
Expand Down
17 changes: 10 additions & 7 deletions site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ type Tab = "logs" | "resources" | undefined; // Undefined is to hide the tab
interface TemplateVersionEditorProps {
template: Template;
templateVersion: TemplateVersion;
defaultFileTree: FileTree;
fileTree: FileTree;
onFileTreeChange: (updater: (fileTree: FileTree) => FileTree) => void;
buildLogs?: ProvisionerJobLog[];
resources?: WorkspaceResource[];
isBuilding: boolean;
Expand Down Expand Up @@ -108,7 +109,8 @@ export const TemplateVersionEditor: FC<TemplateVersionEditorProps> = ({
canPublish,
template,
templateVersion,
defaultFileTree,
fileTree,
onFileTreeChange,
onPreview,
onPublish,
onConfirmPublish,
Expand All @@ -133,7 +135,6 @@ export const TemplateVersionEditor: FC<TemplateVersionEditorProps> = ({
const navigate = useNavigate();
const getLink = useLinks();
const [selectedTab, setSelectedTab] = useState<Tab>(defaultTab);
const [fileTree, setFileTree] = useState(defaultFileTree);
const [createFileOpen, setCreateFileOpen] = useState(false);
const [deleteFileOpen, setDeleteFileOpen] = useState<string>();
const [renameFileOpen, setRenameFileOpen] = useState<string>();
Expand Down Expand Up @@ -351,7 +352,9 @@ export const TemplateVersionEditor: FC<TemplateVersionEditorProps> = ({
}}
checkExists={(path) => existsFile(path, fileTree)}
onConfirm={(path) => {
setFileTree((fileTree) => createFile(path, fileTree, ""));
onFileTreeChange((fileTree) =>
createFile(path, fileTree, ""),
);
onActivePathChange(path);
setCreateFileOpen(false);
setDirty(true);
Expand All @@ -362,7 +365,7 @@ export const TemplateVersionEditor: FC<TemplateVersionEditorProps> = ({
if (!deleteFileOpen) {
throw new Error("delete file must be set");
}
setFileTree((fileTree) =>
onFileTreeChange((fileTree) =>
removeFile(deleteFileOpen, fileTree),
);
setDeleteFileOpen(undefined);
Expand All @@ -387,7 +390,7 @@ export const TemplateVersionEditor: FC<TemplateVersionEditorProps> = ({
if (!renameFileOpen) {
return;
}
setFileTree((fileTree) =>
onFileTreeChange((fileTree) =>
moveFile(renameFileOpen, newPath, fileTree),
);
onActivePathChange(newPath);
Expand Down Expand Up @@ -433,7 +436,7 @@ export const TemplateVersionEditor: FC<TemplateVersionEditorProps> = ({
if (!activePath) {
return;
}
setFileTree((fileTree) =>
onFileTreeChange((fileTree) =>
updateFile(activePath, value, fileTree),
);
setDirty(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,78 @@ test("Preserves the currently open file path when building a template version",
expect(router.state.location.search).toBe("?path=myfile.tf");
});

test("Creating a new file opens it in the editor", async () => {
const user = userEvent.setup();
const { router } = renderWithAuth(<TemplateVersionEditorPage />, {
route: `/templates/${MockTemplate.name}/versions/${MockTemplateVersion.name}/edit`,
path: "/templates/:template/versions/:version/edit",
});

// Wait for the default entrypoint file to load.
const editor = await screen.findByTestId("monaco-editor");
await waitFor(() => {
expect(editor).not.toHaveValue("");
});

const createButton = await screen.findByRole("button", {
name: "Create File",
});
await user.click(createButton);

const dialog = await screen.findByTestId("dialog");
const pathField = within(dialog).getByLabelText("File Path");
await user.type(pathField, "newfile.tf");
await user.click(within(dialog).getByRole("button", { name: "Create" }));

// The new (empty) file should be opened in the editor and the URL path
// query parameter should reflect the new file.
await waitFor(() => {
expect(screen.getByTestId("monaco-editor")).toHaveValue("");
});
expect(router.state.location.search).toBe("?path=newfile.tf");
});

test("Renaming a file does not throw and opens the new path", async () => {
const user = userEvent.setup();
const { router } = renderWithAuth(<TemplateVersionEditorPage />, {
route: `/templates/${MockTemplate.name}/versions/${MockTemplateVersion.name}/edit`,
path: "/templates/:template/versions/:version/edit",
});

// Wait for the default entrypoint file to load and capture its content so
// we can confirm the same content is shown after renaming.
const editor = await screen.findByTestId("monaco-editor");
await waitFor(() => {
expect(editor).not.toHaveValue("");
});
if (!(editor instanceof HTMLTextAreaElement)) {
throw new Error("editor is not a textarea");
}
const originalContent = editor.value;

// Open the file actions menu for the active file and click Rename.
const fileActions = await screen.findByRole("button", {
name: "File actions",
});
await user.click(fileActions);
await user.click(await screen.findByRole("menuitem", { name: /rename/i }));

const dialog = await screen.findByTestId("dialog");
const pathField = within(dialog).getByLabelText("File Path");
await user.clear(pathField);
await user.type(pathField, "renamed.tf");
await user.click(within(dialog).getByRole("button", { name: "Rename" }));

// The renamed file should still be open with its original content and
// the URL path query parameter should reflect the new name. Previously
// this path threw "File is not a text file" because the parent's stale
// file tree fell back to the old entrypoint name.
await waitFor(() => {
expect(screen.getByTestId("monaco-editor")).toHaveValue(originalContent);
});
expect(router.state.location.search).toBe("?path=renamed.tf");
});

describe.each([
{
testName: "Do not ask when template version has no errors",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ const TemplateVersionEditorPage: FC = () => {
const logs = useWatchVersionLogs(activeTemplateVersion, {
onDone: activeTemplateVersionQuery.refetch,
});
const { fileTree, tarFile } = useFileTree(activeTemplateVersion);
const { fileTree, setFileTree, tarFile } = useFileTree(activeTemplateVersion);
const {
missingVariables,
setIsMissingVariablesDialogOpen,
Expand Down Expand Up @@ -138,7 +138,10 @@ const TemplateVersionEditorPage: FC = () => {
onActivePathChange={onActivePathChange}
template={templateQuery.data}
templateVersion={activeTemplateVersion}
defaultFileTree={fileTree}
fileTree={fileTree}
onFileTreeChange={(updater) => {
setFileTree((current) => (current ? updater(current) : current));
}}
onPreview={async (newFileTree) => {
if (!tarFile) {
return;
Expand Down Expand Up @@ -244,13 +247,8 @@ const useFileTree = (templateVersion: TemplateVersion | undefined) => {
...file(templateVersion?.job.file_id ?? ""),
enabled: templateVersion !== undefined,
});
const [state, setState] = useState<{
fileTree?: FileTree;
tarFile?: TarReader;
}>({
fileTree: undefined,
tarFile: undefined,
});
const [fileTree, setFileTree] = useState<FileTree | undefined>(undefined);
const [tarFile, setTarFile] = useState<TarReader | undefined>(undefined);

useEffect(() => {
let stale = false;
Expand All @@ -262,8 +260,8 @@ const useFileTree = (templateVersion: TemplateVersion | undefined) => {
if (stale) {
return;
}
const fileTree = createTemplateVersionFileTree(tarFile);
setState({ fileTree, tarFile });
setFileTree(createTemplateVersionFileTree(tarFile));
setTarFile(tarFile);
} catch (error) {
console.error(error);
toast.error("Error on initializing the editor.", {
Expand All @@ -281,7 +279,7 @@ const useFileTree = (templateVersion: TemplateVersion | undefined) => {
};
}, [fileQuery.data]);

return state;
return { fileTree, setFileTree, tarFile };
};

const useMissingVariables = (templateVersion: TemplateVersion | undefined) => {
Expand Down
Loading