From 40fc337659bcb05f4247334bf0ddef43366eb998 Mon Sep 17 00:00:00 2001 From: Danielle Maywood Date: Tue, 28 Oct 2025 21:41:02 +0000 Subject: [PATCH] fix(site): fix react state violation in filetree create/update utils (#20483) --- site/src/utils/filetree.test.ts | 34 +++++++++++++++++++++------------ site/src/utils/filetree.ts | 8 +++++--- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/site/src/utils/filetree.test.ts b/site/src/utils/filetree.test.ts index f7e3eb48f3..36e88e2098 100644 --- a/site/src/utils/filetree.test.ts +++ b/site/src/utils/filetree.test.ts @@ -10,18 +10,20 @@ import { } from "./filetree"; test("createFile() set file into the file tree", () => { - let fileTree: FileTree = { + const fileTree: FileTree = { "main.tf": "terraform", images: { "java.Dockerfile": "java dockerfile" }, }; - fileTree = createFile( + const updatedFileTree = createFile( "images/python.Dockerfile", fileTree, "python dockerfile", ); - expect((fileTree.images as FileTree)["python.Dockerfile"]).toEqual( + expect((updatedFileTree.images as FileTree)["python.Dockerfile"]).toEqual( "python dockerfile", ); + // Verify the original FileTree was not modified. + expect((fileTree.images as FileTree)["python.Dockerfile"]).toBeUndefined(); }); test("getFileContent() return the file content from the file tree", () => { @@ -35,50 +37,56 @@ test("getFileContent() return the file content from the file tree", () => { }); test("removeFile() removes a file from a folder", () => { - let fileTree: FileTree = { + const fileTree: FileTree = { "main.tf": "terraform content", images: { "java.Dockerfile": "java dockerfile", "python.Dockerfile": "python Dockerfile", }, }; - fileTree = removeFile("images/python.Dockerfile", fileTree); + const updatedFileTree = removeFile("images/python.Dockerfile", fileTree); const expectedFileTree = { "main.tf": "terraform content", images: { "java.Dockerfile": "java dockerfile", }, }; - expect(expectedFileTree).toEqual(fileTree); + expect(updatedFileTree).toEqual(expectedFileTree); + // Verify the original FileTree was not modified. + expect((fileTree.images as FileTree)["python.Dockerfile"]).toEqual( + "python Dockerfile", + ); }); test("removeFile() removes a file from root", () => { - let fileTree: FileTree = { + const fileTree: FileTree = { "main.tf": "terraform content", images: { "java.Dockerfile": "java dockerfile", "python.Dockerfile": "python Dockerfile", }, }; - fileTree = removeFile("main.tf", fileTree); + const updatedFileTree = removeFile("main.tf", fileTree); const expectedFileTree = { images: { "java.Dockerfile": "java dockerfile", "python.Dockerfile": "python Dockerfile", }, }; - expect(expectedFileTree).toEqual(fileTree); + expect(updatedFileTree).toEqual(expectedFileTree); + // Verify the original FileTree was not modified. + expect(fileTree["main.tf"]).toEqual("terraform content"); }); test("moveFile() moves a file from in file tree", () => { - let fileTree: FileTree = { + const fileTree: FileTree = { "main.tf": "terraform content", images: { "java.Dockerfile": "java dockerfile", "python.Dockerfile": "python Dockerfile", }, }; - fileTree = moveFile( + const updatedFileTree = moveFile( "images/java.Dockerfile", "other/java.Dockerfile", fileTree, @@ -92,7 +100,9 @@ test("moveFile() moves a file from in file tree", () => { "java.Dockerfile": "java dockerfile", }, }; - expect(fileTree).toEqual(expectedFileTree); + expect(updatedFileTree).toEqual(expectedFileTree); + // Verify the original FileTree was not modified. + expect(fileTree["main.tf"]).toEqual("terraform content"); }); test("existsFile() returns if there is or not a file", () => { diff --git a/site/src/utils/filetree.ts b/site/src/utils/filetree.ts index 2f7d8ea845..c67012e21f 100644 --- a/site/src/utils/filetree.ts +++ b/site/src/utils/filetree.ts @@ -20,7 +20,8 @@ export const createFile = ( throw new Error(pathError); } - return set(fileTree, path.split("/"), value); + const updatedFileTree = structuredClone(fileTree); + return set(updatedFileTree, path.split("/"), value); }; export const validatePath = ( @@ -43,7 +44,8 @@ export const updateFile = ( content: FileTree | string, fileTree: FileTree, ): FileTree => { - return set(fileTree, path.split("/"), content); + const updatedFileTree = structuredClone(fileTree); + return set(updatedFileTree, path.split("/"), content); }; export const existsFile = (path: string, fileTree: FileTree) => { @@ -51,7 +53,7 @@ export const existsFile = (path: string, fileTree: FileTree) => { }; export const removeFile = (path: string, fileTree: FileTree) => { - const updatedFileTree = { ...fileTree }; + const updatedFileTree = structuredClone(fileTree); unset(updatedFileTree, path.split("/")); return updatedFileTree; };