From 5cc10ef7b75b576bac662c938ec1bf4c81b2e604 Mon Sep 17 00:00:00 2001 From: Oliver Eyton-Williams Date: Fri, 28 Jun 2024 19:50:02 +0200 Subject: [PATCH] fix(client): only use contents from savedChallenges (#55364) --- client/src/redux/prop-types.ts | 2 + .../classic/saved-challenges.test.ts | 132 ++++++++++++++++++ .../Challenges/classic/saved-challenges.ts | 28 ++++ .../src/templates/Challenges/classic/show.tsx | 5 +- 4 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 client/src/templates/Challenges/classic/saved-challenges.test.ts create mode 100644 client/src/templates/Challenges/classic/saved-challenges.ts diff --git a/client/src/redux/prop-types.ts b/client/src/redux/prop-types.ts index 48a5be7f4f5..d7b3a256247 100644 --- a/client/src/redux/prop-types.ts +++ b/client/src/redux/prop-types.ts @@ -357,6 +357,8 @@ export type SavedChallenge = { challengeFiles: SavedChallengeFiles; }; +// TODO: remove unused properties and stop returning them from api? (e.g. +// history, ext, name) export type SavedChallengeFile = { fileKey: string; ext: Ext; diff --git a/client/src/templates/Challenges/classic/saved-challenges.test.ts b/client/src/templates/Challenges/classic/saved-challenges.test.ts new file mode 100644 index 00000000000..96ec5bb2fce --- /dev/null +++ b/client/src/templates/Challenges/classic/saved-challenges.test.ts @@ -0,0 +1,132 @@ +import type { + ChallengeFile, + SavedChallengeFile +} from '../../../redux/prop-types'; +import { mergeChallengeFiles } from './saved-challenges'; + +const jsChallenge = { + id: '1', + contents: 'js contents', + fileKey: 'jsFileKey', + name: 'name', + ext: 'js' as const, + head: 'head', + tail: 'tail', + history: [], + seed: 'original js contents' +}; + +const cssChallenge = { + id: '2', + contents: 'css contents', + fileKey: 'cssFileKey', + name: 'name', + ext: 'css' as const, + head: 'head', + tail: 'tail', + history: [], + seed: 'original css contents' +}; + +const htmlChallenge = { + id: '3', + contents: 'html contents', + fileKey: 'htmlFileKey', + name: 'name', + ext: 'html' as const, + head: 'head', + tail: 'tail', + history: [], + seed: 'original html contents' +}; + +const savedJsChallenge: SavedChallengeFile = { + contents: 'saved js contents', + fileKey: 'jsFileKey', + name: 'name', + ext: 'js' as const +}; + +const savedCssChallenge: SavedChallengeFile = { + contents: 'saved css contents', + fileKey: 'cssFileKey', + name: 'name', + ext: 'css' as const +}; + +const savedHtmlChallenge: SavedChallengeFile = { + contents: 'saved html contents', + fileKey: 'htmlFileKey', + name: 'name', + ext: 'html' as const +}; + +describe('mergeChallengeFiles', () => { + it('should return files if savedChallengeFiles is undefined', () => { + const files: ChallengeFile[] = [htmlChallenge]; + const savedChallengeFiles = undefined; + + const result = mergeChallengeFiles(files, savedChallengeFiles); + + expect(result).toEqual(files); + }); + + it('should return an empty array if files is undefined', () => { + const files = undefined; + const savedChallengeFiles = [savedJsChallenge]; + + const result = mergeChallengeFiles(files, savedChallengeFiles); + + expect(result).toEqual([]); + }); + + it('should return files if savedChallengeFiles has a different length', () => { + const files: ChallengeFile[] = [cssChallenge]; + const savedChallengeFiles: SavedChallengeFile[] = [ + savedCssChallenge, + savedJsChallenge + ]; + + const result = mergeChallengeFiles(files, savedChallengeFiles); + + expect(result).toEqual(files); + }); + + it('should return files if the fileKey properties do not match', () => { + const files: ChallengeFile[] = [jsChallenge, cssChallenge]; + const savedChallengeFiles: SavedChallengeFile[] = [ + savedHtmlChallenge, + savedCssChallenge + ]; + + const result = mergeChallengeFiles(files, savedChallengeFiles); + + expect(result).toEqual(files); + }); + + it('should use the contents from the saved file', () => { + const files: ChallengeFile[] = [cssChallenge, htmlChallenge, jsChallenge]; + const savedChallengeFiles = [ + savedJsChallenge, + savedCssChallenge, + savedHtmlChallenge + ]; + + const result = mergeChallengeFiles(files, savedChallengeFiles); + + expect(result).toEqual([ + { + ...cssChallenge, + contents: savedCssChallenge.contents + }, + { + ...htmlChallenge, + contents: savedHtmlChallenge.contents + }, + { + ...jsChallenge, + contents: savedJsChallenge.contents + } + ]); + }); +}); diff --git a/client/src/templates/Challenges/classic/saved-challenges.ts b/client/src/templates/Challenges/classic/saved-challenges.ts new file mode 100644 index 00000000000..ed8c03d3f54 --- /dev/null +++ b/client/src/templates/Challenges/classic/saved-challenges.ts @@ -0,0 +1,28 @@ +import { ChallengeFile, SavedChallengeFile } from '../../../redux/prop-types'; + +export function mergeChallengeFiles( + files?: ChallengeFile[] | null, + savedFiles?: SavedChallengeFile[] | null +): ChallengeFile[] { + if (!files) return []; + if (!savedFiles) return files; + if (files.length !== savedFiles.length) return files; + + const sortedChallengeFiles = files.sort((a, b) => + a.fileKey.localeCompare(b.fileKey) + ); + const sortedSavedChallengeFiles = savedFiles.sort((a, b) => + a.fileKey.localeCompare(b.fileKey) + ); + + const fileKeysMatch = sortedChallengeFiles.every( + (file, index) => file.fileKey === sortedSavedChallengeFiles[index].fileKey + ); + + if (!fileKeysMatch) return files; + + return sortedChallengeFiles.map((file, index) => ({ + ...file, + contents: sortedSavedChallengeFiles[index].contents + })); +} diff --git a/client/src/templates/Challenges/classic/show.tsx b/client/src/templates/Challenges/classic/show.tsx index ded0b5f070a..7466e82a2be 100644 --- a/client/src/templates/Challenges/classic/show.tsx +++ b/client/src/templates/Challenges/classic/show.tsx @@ -62,6 +62,7 @@ import { XtermTerminal } from './xterm'; import MultifileEditor from './multifile-editor'; import DesktopLayout from './desktop-layout'; import MobileLayout from './mobile-layout'; +import { mergeChallengeFiles } from './saved-challenges'; import './classic.css'; import '../components/test-frame.css'; @@ -351,7 +352,9 @@ function ShowClassic({ return challenge.id === challengeMeta.id; }); - createFiles(savedChallenge?.challengeFiles || challengeFiles || []); + createFiles( + mergeChallengeFiles(challengeFiles, savedChallenge?.challengeFiles) + ); initTests(tests); if (showProjectPreview) openModal('projectPreview');