From 3937ba87869e3c9ca85ce4f41c9cab52b68eb8e9 Mon Sep 17 00:00:00 2001 From: PhuongLe Date: Sat, 14 Feb 2026 15:45:05 -0500 Subject: [PATCH 01/10] able to synce test result by intercepting fetch request after buddy run test cases --- .../components/panel/editor/EditorPanel.tsx | 41 +++++- .../panel/editor/tab/TestResultTab.tsx | 118 ++++++++++++++++++ .../src/components/panel/editor/tab/index.ts | 1 + extension/src/entrypoints/content.tsx | 6 + extension/src/entrypoints/router.ts | 45 +++++++ .../services/controllers/MessageDispatcher.ts | 38 ++++++ extension/src/store/roomStore.ts | 48 ++++++- extension/src/types/global.ts | 1 + extension/src/types/index.ts | 14 +++ extension/src/types/peers.ts | 11 ++ extension/src/utils/messages.ts | 24 ++++ extension/src/utils/string.ts | 60 ++++++++- 12 files changed, 402 insertions(+), 5 deletions(-) create mode 100644 extension/src/components/panel/editor/tab/TestResultTab.tsx diff --git a/extension/src/components/panel/editor/EditorPanel.tsx b/extension/src/components/panel/editor/EditorPanel.tsx index ab9308cf..ff58c459 100644 --- a/extension/src/components/panel/editor/EditorPanel.tsx +++ b/extension/src/components/panel/editor/EditorPanel.tsx @@ -1,6 +1,10 @@ import { UserDropDownMenu } from "@cb/components/navigator/menu/UserDropDownMenu"; import CreateRoomLoadingPanel from "@cb/components/panel/editor/CreateRoomLoadingPanel"; -import { CodeTab, TestTab } from "@cb/components/panel/editor/tab"; +import { + CodeTab, + TestResultTab, + TestTab, +} from "@cb/components/panel/editor/tab"; import { Tooltip } from "@cb/components/tooltip"; import { SkeletonWrapper } from "@cb/components/ui/SkeletonWrapper"; import { @@ -26,7 +30,8 @@ const EditorPanel = () => { const { selectedPeer, peers } = usePeers(); const { self } = useRoomData(); const roomStatus = useRoomStatus(); - const { selectTest, toggleCodeVisibility } = usePeerActions(); + const { selectTest, selectTestResult, toggleCodeVisibility } = + usePeerActions(); const { getLanguageExtension } = useLeetCodeActions(); const url = self?.url ?? ""; @@ -38,6 +43,16 @@ const EditorPanel = () => { const upperTabConfigs = React.useMemo(() => { const extension = getLanguageExtension(selectedPeer?.questions[url]?.code?.language) ?? ""; + + const activeTestResult = selectedPeer?.questions[url]?.testResults.find( + (testResult) => testResult.selected + ); + + const allTestResult = selectedPeer?.questions[url]?.testResults ?? []; + const generalResult = allTestResult.every( + (r) => r.testResult[0].output === r.testResult[0].expected + ); + return [ { value: "code", @@ -57,8 +72,28 @@ const EditorPanel = () => { /> ), }, + { + value: "testResult", + label: "Test Result", + Icon: FlaskConical, + Content: ( + + ), + }, ]; - }, [selectedPeer, activeTest, selectTest, getLanguageExtension, url]); + }, [ + selectedPeer, + activeTest, + selectTest, + selectTestResult, + getLanguageExtension, + url, + ]); const hideCode = !selectedPeer?.questions[self?.url ?? ""]?.viewable; diff --git a/extension/src/components/panel/editor/tab/TestResultTab.tsx b/extension/src/components/panel/editor/tab/TestResultTab.tsx new file mode 100644 index 00000000..b625d7d5 --- /dev/null +++ b/extension/src/components/panel/editor/tab/TestResultTab.tsx @@ -0,0 +1,118 @@ +import { SkeletonWrapper } from "@cb/components/ui/SkeletonWrapper"; +import { useRoomData } from "@cb/hooks/store"; +import { Identifiable, PeerState, SelectableTestResult } from "@cb/types"; +import React from "react"; + +interface TestResultTabProps { + activePeer: Identifiable | undefined; + activeTestResult: SelectableTestResult | undefined; + selectTestResult: (index: number) => void; + generalResult: boolean; +} + +export const TestResultTab: React.FC = ({ + activePeer, + activeTestResult, + selectTestResult, + generalResult, +}) => { + const { self } = useRoomData(); + return ( + +
+
+
+ {activeTestResult ? ( + <> + {generalResult ? ( + Accepted + ) : ( + Wrong Answer + )} + + ) : null} +
+
+ {(activePeer?.questions[self?.url ?? ""]?.testResults ?? []).map( + (test, idx) => { + const passed = (test.testResult ?? []).every( + (r: any) => r.output === r.expected + ); + const selected = !!test.selected; + return ( +
selectTestResult(idx)}> + {selected ? ( + passed ? ( + + ) : ( + + ) + ) : passed ? ( + + ) : ( + + )} +
+ ); + } + )} +
+
+
+
+
+ {activeTestResult?.testResult.map((assignment, idx) => ( + + {/* Input / Value */} +
+ Input +
+ {Object.keys(assignment.input ?? {}).map( + (variable: string) => ( +
+
+ {variable} = {assignment.input[variable]} +
+
+ ) + )} + + {/* Output */} +
+ Output +
+
+
+ {assignment.output ?? "-"} +
+
+ + {/* Expected */} +
+ Expected +
+
+
+ {assignment.expected ?? "-"} +
+
+
+ )) ?? null} +
+
+
+
+
+ ); +}; diff --git a/extension/src/components/panel/editor/tab/index.ts b/extension/src/components/panel/editor/tab/index.ts index 817b43f3..1e577d9a 100644 --- a/extension/src/components/panel/editor/tab/index.ts +++ b/extension/src/components/panel/editor/tab/index.ts @@ -1,2 +1,3 @@ export { CodeTab } from "@cb/components/panel/editor/tab/CodeTab"; +export { TestResultTab } from "@cb/components/panel/editor/tab/TestResultTab"; export { TestTab } from "@cb/components/panel/editor/tab/TestTab"; diff --git a/extension/src/entrypoints/content.tsx b/extension/src/entrypoints/content.tsx index 32088c6a..d72f1258 100644 --- a/extension/src/entrypoints/content.tsx +++ b/extension/src/entrypoints/content.tsx @@ -11,6 +11,12 @@ export default defineContentScript({ matches: [URLS.ALL_PROBLEMS], runAt: "document_end", async main(ctx) { + const s = document.createElement("script"); + s.src = chrome.runtime.getURL("proxy.js"); + s.type = "text/javascript"; + s.onload = () => s.remove(); + document.documentElement.appendChild(s); + // Initialize controllers on startup getOrCreateControllers(); await injectScript("/router.js", { keepInDom: true }); diff --git a/extension/src/entrypoints/router.ts b/extension/src/entrypoints/router.ts index b93b10bb..7e0535df 100644 --- a/extension/src/entrypoints/router.ts +++ b/extension/src/entrypoints/router.ts @@ -3,6 +3,51 @@ import { defineUnlistedScript } from "wxt/utils/define-unlisted-script"; export default defineUnlistedScript(() => { console.log("Inject router"); + + (function () { + if (window.__LC_FETCH_HOOKED__) return; + window.__LC_FETCH_HOOKED__ = true; + + const origFetch = window.fetch; + + window.fetch = async (...args) => { + const res = await origFetch(...args); + + try { + const regexTestResult = + /^https:\/\/leetcode\.com\/submissions\/detail\/runcode_[^/]+\/check\/$/; + + if (typeof res.url === "string" && regexTestResult.test(res.url)) { + const contentType = res.headers.get("content-type") || ""; + + if (contentType.includes("application/json")) { + const clone = res.clone(); + clone + .json() + .then((data) => { + window.postMessage( + { + source: "LC_HOOK", + url: res.url, + data: data, + type: "LC_HOOK", + }, + "*" + ); + }) + + .catch((e) => { + console.error("Failed to parse JSON from LeetCode response", e); + }); + } + } + } catch (e) { + console.error("Error in fetch hook:", e); + } + return res; + }; + })(); + window.addEventListener("message", (message: MessageEvent) => { if (message.data.action == undefined) { return; diff --git a/extension/src/services/controllers/MessageDispatcher.ts b/extension/src/services/controllers/MessageDispatcher.ts index e3496777..96c2b46f 100644 --- a/extension/src/services/controllers/MessageDispatcher.ts +++ b/extension/src/services/controllers/MessageDispatcher.ts @@ -63,6 +63,7 @@ export class MessageDispatcher { this.unsubscribers.push(this.subscribeToCodeEditor()); this.unsubscribers.push(this.subscribeToTestEditor()); + this.unsubscribers.push(this.subscribeToRunTest()); this.unsubscribers.push(this.subscribeToRtcOpen()); this.unsubscribers.push(this.subscribeToRtcMessage()); this.unsubscribers.push(this.subscribeToRoomChanges()); @@ -162,6 +163,21 @@ export class MessageDispatcher { }; } + private subscribeToRunTest(): () => void { + const messageHandler = async (event: MessageEvent) => { + if (event.data.type === "LC_HOOK") { + const testResult = await this.getTestResultPayload(event.data); + this.emitter.emit("rtc.send.message", { + message: testResult, + }); + } + }; + window.addEventListener("message", messageHandler); + return () => { + window.removeEventListener("message", messageHandler); + }; + } + private subscribeToSubmission() { // todo(nickbar01234): On teardown, we need to revert the changes const sendSuccessSubmission = () => { @@ -267,6 +283,19 @@ export class MessageDispatcher { break; } + case "testResults": { + const { testResults, url } = message; + this.roomStore.getState().actions.peers.update(from, { + questions: { + [url]: { + testResults, + status: QuestionProgressStatus.IN_PROGRESS, + }, + }, + }); + break; + } + case "event": { const { url, event } = message; if (event === EventType.SUBMIT_SUCCESS) { @@ -402,4 +431,13 @@ export class MessageDispatcher { .actions.room.getVariables(getNormalizedUrl(window.location.href)) ); } + + private getTestResultPayload(eventData: MessageEvent) { + return getTestResultsPayload( + this.roomStore + .getState() + .actions.room.getVariables(getNormalizedUrl(window.location.href)), + eventData.data + ); + } } diff --git a/extension/src/store/roomStore.ts b/extension/src/store/roomStore.ts index e7302f3c..553bf2fe 100644 --- a/extension/src/store/roomStore.ts +++ b/extension/src/store/roomStore.ts @@ -18,6 +18,7 @@ import { SelfState, Slug, TestCases, + TestResults, User, } from "@cb/types"; import { ChatMessage, DatabaseService } from "@cb/types/db"; @@ -53,6 +54,7 @@ export enum RoomStatus { interface UpdatePeerQuestionProgress { code?: CodeWithChanges; tests?: TestCases; + testResults?: TestResults; status?: QuestionProgressStatus; } @@ -116,6 +118,7 @@ interface RoomAction { remove: (ids: Id[]) => void; selectPeer: (id: string) => void; selectTest: (idx: number) => void; + selectTestResult: (idx: number) => void; toggleCodeVisibility: () => void; }; self: { @@ -181,10 +184,16 @@ const createRoomStore = (background: BackgroundProxy, appStore: AppStore) => { (acc, curr) => { const [url, data] = curr; const normalizedUrl = getNormalizedUrl(url); - const { code, tests: testsPayload, status } = data; + const { + code, + tests: testsPayload, + testResults: testResultsPayload, + status, + } = data; const questionProgressOrDefault = state.questions[normalizedUrl] ?? { code: undefined, tests: [], + testResults: [], status: QuestionProgressStatus.NOT_STARTED, viewable: false, }; @@ -210,6 +219,25 @@ const createRoomStore = (background: BackgroundProxy, appStore: AppStore) => { questionProgressOrDefault.tests = tests; } + if (testResultsPayload != undefined) { + const testResults = testResultsPayload.map((testResult) => ({ + ...testResult, + selected: false, + })); + const previousSelectedTest = + questionProgressOrDefault.testResults.findIndex( + (testResult) => testResult.selected + ); + const selectedTestIndex = + previousSelectedTest >= testResults.length + ? testResults.length - 1 + : Math.max(previousSelectedTest, 0); + if (testResults[selectedTestIndex]) { + testResults[selectedTestIndex].selected = true; + } + questionProgressOrDefault.testResults = testResults; + } + if (status != undefined) { questionProgressOrDefault.status = status; } @@ -234,11 +262,13 @@ const createRoomStore = (background: BackgroundProxy, appStore: AppStore) => { const setSelfProgressForCurrentUrl = async (question: Question) => { const code = await background.getUserCode({}); const { tests } = getTestsPayload(question.variables); + const { testResults } = getTestResultsPayload(question.variables); useRoom.getState().actions.self.update({ questions: { [question.url]: { code, tests, + testResults, }, }, }); @@ -573,6 +603,22 @@ const createRoomStore = (background: BackgroundProxy, appStore: AppStore) => { })); } }), + selectTestResult: (idx) => + set((state) => { + const active = getSelectedPeer(state.peers); + const progress = + state.peers[active?.id ?? ""].questions[ + getNormalizedUrl(window.location.href) + ]; + if (progress != undefined) { + progress.testResults = progress.testResults.map( + (test, i) => ({ + ...test, + selected: i === idx, + }) + ); + } + }), toggleCodeVisibility: () => set((state) => { const active = getSelectedPeer(state.peers); diff --git a/extension/src/types/global.ts b/extension/src/types/global.ts index a982e3d1..03d65893 100644 --- a/extension/src/types/global.ts +++ b/extension/src/types/global.ts @@ -10,5 +10,6 @@ declare global { push: (url: string) => Promise; }; }; + __LC_FETCH_HOOKED__?: boolean; } } diff --git a/extension/src/types/index.ts b/extension/src/types/index.ts index 7f92ef4e..e4efee17 100644 --- a/extension/src/types/index.ts +++ b/extension/src/types/index.ts @@ -15,14 +15,28 @@ interface Assignment { value: string; } +interface ResultAssignment { + input: Record; + output: string; + expected: string; +} + export interface TestCase { test: Assignment[]; } +export interface TestResult { + testResult: ResultAssignment[]; +} + export type TestCases = TestCase[]; +export type TestResults = TestResult[]; + export interface SelectableTestCase extends TestCase, Selectable {} +export interface SelectableTestResult extends TestResult, Selectable {} + // Refactor post redux export interface LocalStorage { signIn: { diff --git a/extension/src/types/peers.ts b/extension/src/types/peers.ts index 73c78586..cb8ad4d6 100644 --- a/extension/src/types/peers.ts +++ b/extension/src/types/peers.ts @@ -2,8 +2,11 @@ import { Id, QuestionProgressStatus, SelectableTestCase, + SelectableTestResult, TestCase, TestCases, + TestResult, + TestResults, } from "."; import type { ServiceResponse } from "./services"; import { GenericMessage, Selectable } from "./utils"; @@ -24,6 +27,11 @@ interface PeerCodeMessage extends PeerGenericMessage, CodeWithChanges { action: "code"; } +interface PeerTestResultMessage extends PeerGenericMessage { + action: "testResults"; + testResults: TestResults; +} + interface PeerTestMessage extends PeerGenericMessage { action: "tests"; tests: TestCases; @@ -65,6 +73,7 @@ type PeerEventMessage = PeerEventSubmissionMesage | PeerEventAddQuestionMessage; export type PeerMessage = | PeerCodeMessage | PeerTestMessage + | PeerTestResultMessage | PeerEventMessage | RequestProgressMessage | SendProgressMessage; @@ -72,6 +81,7 @@ export type PeerMessage = interface PeerQuestionProgress { code?: CodeWithChanges; tests: SelectableTestCase[]; + testResults: SelectableTestResult[]; status: QuestionProgressStatus; viewable: boolean; } @@ -79,6 +89,7 @@ interface PeerQuestionProgress { interface SelfQuestionProgress { code?: MonacoCode; tests: TestCase[]; + testResults?: TestResult[]; status: QuestionProgressStatus; } diff --git a/extension/src/utils/messages.ts b/extension/src/utils/messages.ts index e4f5ceef..3d0c1ad1 100644 --- a/extension/src/utils/messages.ts +++ b/extension/src/utils/messages.ts @@ -17,6 +17,30 @@ export const getTestsPayload = ( }; }; +export const getTestResultsPayload = ( + variables: Question["variables"] | undefined, + testResults?: any //fix type here? +): ExtractMessage => { + if (!testResults) { + return { + action: "testResults", + testResults: [], + url: getNormalizedUrl(window.location.href), + }; + } + + return { + action: "testResults", + testResults: groupTestResults( + variables, + getTestsPayload(variables).tests, + testResults.code_answer.slice(0, -1), + testResults.expected_code_answer.slice(0, -1) + ), + url: getNormalizedUrl(window.location.href), + }; +}; + export const getCodePayload = async ( changes: Partial ): Promise> => { diff --git a/extension/src/utils/string.ts b/extension/src/utils/string.ts index a2ffdeea..a31d797a 100644 --- a/extension/src/utils/string.ts +++ b/extension/src/utils/string.ts @@ -1,5 +1,5 @@ -import { Question, TestCase } from "@cb/types"; import { Timestamp } from "firebase/firestore"; +import { Question, TestCase, TestCases, TestResult } from "@cb/types"; export const capitalize = (str: string | undefined) => str ? str.charAt(0).toUpperCase() + str.slice(1) : ""; @@ -29,6 +29,64 @@ export const groupTestCases = ( })); }; +export const groupTestResults = ( + variables: Question["variables"] | undefined, + testInputs: TestCases, + testOutputs: string[], + testExpectedOutputs: string[] +): TestResult[] => { + const numCases = testInputs.length; + + if ( + variables == undefined || + testInputs.length !== numCases || + testExpectedOutputs.length !== numCases + ) { + console.error( + "Variables are undefined or tests do not match up", + variables, + testInputs, + testOutputs, + testExpectedOutputs + ); + return []; + } + + const results: TestResult[] = []; + const varCount = variables?.count ?? 0; + + for (let curCaseNo = 0; curCaseNo < numCases; curCaseNo++) { + if (testInputs[curCaseNo].test.length !== varCount) { + console.error( + `Case ${curCaseNo} does not have the correct number of variables`, + variables, + testInputs + ); + return []; + } + + const inputObj: Record = {}; + const currentTestInputs = testInputs[curCaseNo]; + + for (let v = 0; v < varCount; v++) { + const name: string = currentTestInputs.test[v].variable ?? `var${v}`; + inputObj[name] = currentTestInputs.test[v].value; + } + + results.push({ + testResult: [ + { + input: inputObj, + output: testOutputs[curCaseNo] ?? "", + expected: testExpectedOutputs[curCaseNo] ?? "", + }, + ], + }); + } + + return results; +}; + export const safeJsonParse = (content: string): object => { try { return JSON.parse(content); From 4faf25470c0d32fecec7005d92a4c23d0f02e477 Mon Sep 17 00:00:00 2001 From: PhuongLe Date: Sat, 28 Feb 2026 13:02:55 -0500 Subject: [PATCH 02/10] commit to keep progress changed type to reuse test case assignment interface fix for case when input has no variable name --- .../panel/editor/tab/TestResultTab.tsx | 91 +++++++++++-------- extension/src/types/index.ts | 4 +- extension/src/utils/messages.ts | 6 +- extension/src/utils/string.ts | 9 +- 4 files changed, 64 insertions(+), 46 deletions(-) diff --git a/extension/src/components/panel/editor/tab/TestResultTab.tsx b/extension/src/components/panel/editor/tab/TestResultTab.tsx index b625d7d5..9c97100c 100644 --- a/extension/src/components/panel/editor/tab/TestResultTab.tsx +++ b/extension/src/components/panel/editor/tab/TestResultTab.tsx @@ -1,6 +1,12 @@ import { SkeletonWrapper } from "@cb/components/ui/SkeletonWrapper"; import { useRoomData } from "@cb/hooks/store"; -import { Identifiable, PeerState, SelectableTestResult } from "@cb/types"; +import { + Identifiable, + PeerState, + ResultAssignment, + SelectableTestResult, + TestResult, +} from "@cb/types"; import React from "react"; interface TestResultTabProps { @@ -17,6 +23,15 @@ export const TestResultTab: React.FC = ({ generalResult, }) => { const { self } = useRoomData(); + const emptyTestResults: TestResult = { + testResult: [ + { + input: [{ variable: "", value: "" }], + output: "", + expected: "", + }, + ], + }; return (
@@ -34,9 +49,9 @@ export const TestResultTab: React.FC = ({
{(activePeer?.questions[self?.url ?? ""]?.testResults ?? []).map( - (test, idx) => { + (test: SelectableTestResult, idx: number) => { const passed = (test.testResult ?? []).every( - (r: any) => r.output === r.expected + (r: ResultAssignment) => r.output === r.expected ); const selected = !!test.selected; return ( @@ -69,46 +84,46 @@ export const TestResultTab: React.FC = ({
- {activeTestResult?.testResult.map((assignment, idx) => ( - - {/* Input / Value */} -
- Input -
- {Object.keys(assignment.input ?? {}).map( - (variable: string) => ( -
-
- {variable} = {assignment.input[variable]} + {activeTestResult?.testResult.map( + (testResult: ResultAssignment, testIdx: number) => ( + +
+ Input +
+ {testResult.input?.map((input, idx) => ( + +
+
+ {input.variable + ? `${input.variable}=${input.value}` + : input.value} +
-
- ) - )} + + )) ?? null} - {/* Output */} -
- Output -
-
-
- {assignment.output ?? "-"} + {/* Output */} +
+ Output +
+
+
+ {testResult.output ?? "-"} +
-
- {/* Expected */} -
- Expected -
-
-
- {assignment.expected ?? "-"} + {/* Expected */} +
+ Expected
-
- - )) ?? null} +
+
+ {testResult.expected ?? "-"} +
+
+ + ) + ) ?? null}
diff --git a/extension/src/types/index.ts b/extension/src/types/index.ts index e4efee17..ada77170 100644 --- a/extension/src/types/index.ts +++ b/extension/src/types/index.ts @@ -15,8 +15,8 @@ interface Assignment { value: string; } -interface ResultAssignment { - input: Record; +export interface ResultAssignment { + input: Assignment[]; output: string; expected: string; } diff --git a/extension/src/utils/messages.ts b/extension/src/utils/messages.ts index 3d0c1ad1..4168bb5f 100644 --- a/extension/src/utils/messages.ts +++ b/extension/src/utils/messages.ts @@ -19,7 +19,7 @@ export const getTestsPayload = ( export const getTestResultsPayload = ( variables: Question["variables"] | undefined, - testResults?: any //fix type here? + testResults?: any ): ExtractMessage => { if (!testResults) { return { @@ -34,8 +34,8 @@ export const getTestResultsPayload = ( testResults: groupTestResults( variables, getTestsPayload(variables).tests, - testResults.code_answer.slice(0, -1), - testResults.expected_code_answer.slice(0, -1) + testResults.code_answer?.slice(0, -1), + testResults.expected_code_answer?.slice(0, -1) ), url: getNormalizedUrl(window.location.href), }; diff --git a/extension/src/utils/string.ts b/extension/src/utils/string.ts index a31d797a..a0dda3ca 100644 --- a/extension/src/utils/string.ts +++ b/extension/src/utils/string.ts @@ -65,12 +65,15 @@ export const groupTestResults = ( return []; } - const inputObj: Record = {}; + const inputObj: Array = []; const currentTestInputs = testInputs[curCaseNo]; for (let v = 0; v < varCount; v++) { - const name: string = currentTestInputs.test[v].variable ?? `var${v}`; - inputObj[name] = currentTestInputs.test[v].value; + const name: string = currentTestInputs.test[v].variable ?? ""; + inputObj.push({ + variable: name, + value: currentTestInputs.test[v].value, + }); } results.push({ From 17bfa54a49e41cb1a0cf44135c448f5af2630c38 Mon Sep 17 00:00:00 2001 From: PhuongLe Date: Sun, 1 Mar 2026 19:38:55 -0500 Subject: [PATCH 03/10] not breaking non error test result cases but not working for error test result cases yet --- .../panel/editor/tab/TestResultTab.tsx | 14 +- extension/src/constants/index.ts | 6 + extension/src/types/index.ts | 14 ++ extension/src/utils/messages.ts | 16 +- extension/src/utils/string.ts | 233 +++++++++++++++--- 5 files changed, 235 insertions(+), 48 deletions(-) diff --git a/extension/src/components/panel/editor/tab/TestResultTab.tsx b/extension/src/components/panel/editor/tab/TestResultTab.tsx index 9c97100c..655d8eb1 100644 --- a/extension/src/components/panel/editor/tab/TestResultTab.tsx +++ b/extension/src/components/panel/editor/tab/TestResultTab.tsx @@ -5,7 +5,6 @@ import { PeerState, ResultAssignment, SelectableTestResult, - TestResult, } from "@cb/types"; import React from "react"; @@ -23,15 +22,10 @@ export const TestResultTab: React.FC = ({ generalResult, }) => { const { self } = useRoomData(); - const emptyTestResults: TestResult = { - testResult: [ - { - input: [{ variable: "", value: "" }], - output: "", - expected: "", - }, - ], - }; + console.log( + "all test results (TestResultTab): ", + activePeer?.questions[self?.url ?? ""]?.testResults ?? [] + ); return (
diff --git a/extension/src/constants/index.ts b/extension/src/constants/index.ts index 19244691..008a1486 100644 --- a/extension/src/constants/index.ts +++ b/extension/src/constants/index.ts @@ -112,3 +112,9 @@ export const PANEL = { DEFAULT_WIDTH: 350, // px COLLAPSED_WIDTH: 40, // px }; + +export const TEST_RESULT_ERROR = { + "Runtime Error": "runtime_error", + "Compile Error": "compile_error", + "Invalid Testcase": "runtime_error", +}; diff --git a/extension/src/types/index.ts b/extension/src/types/index.ts index ada77170..b8d8378c 100644 --- a/extension/src/types/index.ts +++ b/extension/src/types/index.ts @@ -26,6 +26,20 @@ export interface TestCase { } export interface TestResult { + testResultStatus: + | "Unexamined" + | "Accepted" + | "Unknown Error" + | "Runtime Error" + | "Time Limit Exceeded" + | "Memory Limit Exceeded" + | "Output Limit Exceeded" + | "Compile Error" + | "Invalid Testcase" + | string; + errorMessage?: string; + lastTestCaseRun?: number; + invalidTestCaseIdx?: number; testResult: ResultAssignment[]; } diff --git a/extension/src/utils/messages.ts b/extension/src/utils/messages.ts index 4168bb5f..3cad3892 100644 --- a/extension/src/utils/messages.ts +++ b/extension/src/utils/messages.ts @@ -1,4 +1,4 @@ -import { DOM } from "@cb/constants"; +import { DOM, TEST_RESULT_ERROR } from "@cb/constants"; import background from "@cb/services/background"; import { ExtractMessage, PeerMessage, Question } from "@cb/types"; import monaco from "monaco-editor"; @@ -29,13 +29,25 @@ export const getTestResultsPayload = ( }; } + const statusMsg: string = testResults.invalid_testcase + ? "Invalid Testcase" + : testResults.status_msg; + return { action: "testResults", testResults: groupTestResults( variables, + statusMsg, + testResults.code_answer, + testResults.invalid_testcase_idx, getTestsPayload(variables).tests, testResults.code_answer?.slice(0, -1), - testResults.expected_code_answer?.slice(0, -1) + testResults.expected_code_answer?.slice(0, -1), + statusMsg === "Invalid Testcase" || + statusMsg === "Runtime Error" || + statusMsg === "Compile Error" + ? TEST_RESULT_ERROR[statusMsg as keyof typeof TEST_RESULT_ERROR] + : "" ), url: getNormalizedUrl(window.location.href), }; diff --git a/extension/src/utils/string.ts b/extension/src/utils/string.ts index a0dda3ca..4cc309ae 100644 --- a/extension/src/utils/string.ts +++ b/extension/src/utils/string.ts @@ -29,65 +29,226 @@ export const groupTestCases = ( })); }; +// export const groupTestResults = ( +// variables: Question["variables"] | undefined, +// testResultStatus: string, +// codeAnswer: string[] = [], +// testResultError: string = "", +// invalidTestCaseIdx: number | undefined, +// testInputs: TestCases, +// testOutputs: string[], +// testExpectedOutputs: string[], +// ): TestResults => { +// const numCases = testInputs.length; + +// if ( +// variables == undefined || +// testInputs.length !== numCases || +// testExpectedOutputs.length !== numCases +// ) { +// console.error( +// "Variables are undefined or tests do not match up", +// variables, +// testInputs, +// testOutputs, +// testExpectedOutputs +// ); +// return { +// testResultStatus: testResultStatus, +// testResults: [], +// }; +// } + +// const results: TestResult[] = []; +// const varCount = variables?.count ?? 0; + +// for (let curCaseNo = 0; curCaseNo < numCases; curCaseNo++) { +// if (testInputs[curCaseNo].test.length !== varCount) { +// console.error( +// `Case ${curCaseNo} does not have the correct number of variables`, +// variables, +// testInputs +// ); +// return { +// testResultStatus: testResultStatus, +// testResults: [], +// }; +// } + +// switch (testResultStatus) { +// case "Time Limit Exceeded": +// case "Memory Limit Exceeded": +// case "Output Limit Exceeded": +// let lastTestCaseRunIdx = 0; +// let i = 0; +// while (i < codeAnswer.length && codeAnswer[i] === "0") { +// lastTestCaseRunIdx++; +// i++; +// } +// return { +// testResultStatus: testResultStatus, +// lastTestCaseRun: lastTestCaseRunIdx, +// testResults: [], +// }; +// case "Runtime Error": +// case "Compile Error": +// let lastTestCaseRunIdx = 0; +// let i = 0; +// while (i < codeAnswer.length && codeAnswer[i] === "0") { +// lastTestCaseRunIdx++; +// i++; +// } +// return { +// testResultStatus: testResultStatus, +// errorMessage: testResultError, +// lastTestCaseRun: lastTestCaseRunIdx, +// testResults: [], +// }; +// case "Invalid Testcase": +// return { +// testResultStatus: testResultStatus, +// errorMessage: testResultError, +// invalidTestCaseIdx: invalidTestCaseIdx, +// testResults: [], +// }; +// case "Accepted": +// const inputObj: Array = []; +// const currentTestInputs = testInputs[curCaseNo]; + +// for (let v = 0; v < varCount; v++) { +// const name: string = currentTestInputs.test[v].variable ?? ""; +// inputObj.push({ +// variable: name, +// value: currentTestInputs.test[v].value, +// }); +// } + +// results.push({ +// testResult: [ +// { +// input: inputObj, +// output: testOutputs[curCaseNo] ?? "", +// expected: testExpectedOutputs[curCaseNo] ?? "", +// }, +// ], +// }); + +// return { +// testResultStatus: testResultStatus, +// testResults: results, +// }; + +// default: +// break; +// } +// }; + export const groupTestResults = ( variables: Question["variables"] | undefined, + testResultStatus: string, + codeAnswer: string[] = [], + invalidTestCaseIdx: number | undefined, testInputs: TestCases, testOutputs: string[], - testExpectedOutputs: string[] + testExpectedOutputs: string[], + testResultError: string = "" ): TestResult[] => { const numCases = testInputs.length; + const varCount = variables?.count ?? 0; - if ( - variables == undefined || - testInputs.length !== numCases || - testExpectedOutputs.length !== numCases - ) { + const baseResponse = (overrides: Partial = {}): TestResult => ({ + testResultStatus, + testResult: [], + ...overrides, + }); + + const getLastRunIndex = () => + codeAnswer.findIndex((val) => val !== "0") === -1 + ? codeAnswer.length + : codeAnswer.findIndex((val) => val !== "0"); + + // Validation + if (!variables || testExpectedOutputs.length !== numCases) { console.error( - "Variables are undefined or tests do not match up", + "Variables undefined or test lengths mismatch", variables, testInputs, testOutputs, testExpectedOutputs ); - return []; + return [baseResponse()]; } - const results: TestResult[] = []; - const varCount = variables?.count ?? 0; + // Global error states (no need to iterate test cases) + if ( + [ + "Time Limit Exceeded", + "Memory Limit Exceeded", + "Output Limit Exceeded", + ].includes(testResultStatus) + ) { + return [ + baseResponse({ + lastTestCaseRun: getLastRunIndex(), + }), + ]; + } - for (let curCaseNo = 0; curCaseNo < numCases; curCaseNo++) { - if (testInputs[curCaseNo].test.length !== varCount) { - console.error( - `Case ${curCaseNo} does not have the correct number of variables`, - variables, - testInputs - ); - return []; - } + if (["Runtime Error", "Compile Error"].includes(testResultStatus)) { + return [ + baseResponse({ + errorMessage: testResultError, + lastTestCaseRun: getLastRunIndex(), + }), + ]; + } + + if (testResultStatus === "Invalid Testcase") { + return [ + baseResponse({ + errorMessage: testResultError, + invalidTestCaseIdx, + }), + ]; + } + + // Accepted case + if (testResultStatus === "Accepted") { + const results: TestResult[] = []; + + for (let i = 0; i < numCases; i++) { + const currentTest = testInputs[i]; + + if (currentTest.test.length !== varCount) { + console.error( + `Case ${i} does not match expected variable count`, + variables, + testInputs + ); + return [baseResponse()]; + } - const inputObj: Array = []; - const currentTestInputs = testInputs[curCaseNo]; + const input = currentTest.test.map((t) => ({ + variable: t.variable ?? "", + value: t.value, + })); - for (let v = 0; v < varCount; v++) { - const name: string = currentTestInputs.test[v].variable ?? ""; - inputObj.push({ - variable: name, - value: currentTestInputs.test[v].value, + results.push({ + testResultStatus, + testResult: [ + { + input, + output: testOutputs[i] ?? "", + expected: testExpectedOutputs[i] ?? "", + }, + ], }); } - results.push({ - testResult: [ - { - input: inputObj, - output: testOutputs[curCaseNo] ?? "", - expected: testExpectedOutputs[curCaseNo] ?? "", - }, - ], - }); + return results; } - return results; + return [baseResponse()]; }; export const safeJsonParse = (content: string): object => { From 0b6d129a983d76797e75ce9b3626ccff87391854 Mon Sep 17 00:00:00 2001 From: PhuongLe Date: Mon, 2 Mar 2026 11:38:09 -0500 Subject: [PATCH 04/10] commit to keep progress backend working? frontend not yet for error cases --- .../components/panel/editor/EditorPanel.tsx | 11 +++--- .../panel/editor/tab/TestResultTab.tsx | 8 ++-- extension/src/utils/messages.ts | 4 +- extension/src/utils/string.ts | 39 ++++++++++++------- 4 files changed, 37 insertions(+), 25 deletions(-) diff --git a/extension/src/components/panel/editor/EditorPanel.tsx b/extension/src/components/panel/editor/EditorPanel.tsx index ff58c459..361bb77f 100644 --- a/extension/src/components/panel/editor/EditorPanel.tsx +++ b/extension/src/components/panel/editor/EditorPanel.tsx @@ -48,10 +48,11 @@ const EditorPanel = () => { (testResult) => testResult.selected ); - const allTestResult = selectedPeer?.questions[url]?.testResults ?? []; - const generalResult = allTestResult.every( - (r) => r.testResult[0].output === r.testResult[0].expected - ); + // const allTestResult = selectedPeer?.questions[url]?.testResults ?? []; + // console.log("generalResult", allTestResult); + // const generalResult = allTestResult.every( + // (r) => r.testResult && r.testResult[0].output === r.testResult[0].expected + // ); return [ { @@ -81,7 +82,7 @@ const EditorPanel = () => { activePeer={selectedPeer} activeTestResult={activeTestResult} selectTestResult={selectTestResult} - generalResult={generalResult} + // generalResult={generalResult} /> ), }, diff --git a/extension/src/components/panel/editor/tab/TestResultTab.tsx b/extension/src/components/panel/editor/tab/TestResultTab.tsx index 655d8eb1..20737bde 100644 --- a/extension/src/components/panel/editor/tab/TestResultTab.tsx +++ b/extension/src/components/panel/editor/tab/TestResultTab.tsx @@ -12,14 +12,14 @@ interface TestResultTabProps { activePeer: Identifiable | undefined; activeTestResult: SelectableTestResult | undefined; selectTestResult: (index: number) => void; - generalResult: boolean; + // generalResult: boolean; } export const TestResultTab: React.FC = ({ activePeer, activeTestResult, selectTestResult, - generalResult, + // generalResult, }) => { const { self } = useRoomData(); console.log( @@ -33,11 +33,11 @@ export const TestResultTab: React.FC = ({
{activeTestResult ? ( <> - {generalResult ? ( + {/* {generalResult ? ( Accepted ) : ( Wrong Answer - )} + )} */} ) : null}
diff --git a/extension/src/utils/messages.ts b/extension/src/utils/messages.ts index 3cad3892..541bd7b5 100644 --- a/extension/src/utils/messages.ts +++ b/extension/src/utils/messages.ts @@ -39,14 +39,14 @@ export const getTestResultsPayload = ( variables, statusMsg, testResults.code_answer, - testResults.invalid_testcase_idx, + testResults.case_idx + 1, getTestsPayload(variables).tests, testResults.code_answer?.slice(0, -1), testResults.expected_code_answer?.slice(0, -1), statusMsg === "Invalid Testcase" || statusMsg === "Runtime Error" || statusMsg === "Compile Error" - ? TEST_RESULT_ERROR[statusMsg as keyof typeof TEST_RESULT_ERROR] + ? testResults[TEST_RESULT_ERROR[statusMsg]] : "" ), url: getNormalizedUrl(window.location.href), diff --git a/extension/src/utils/string.ts b/extension/src/utils/string.ts index 4cc309ae..83a6e3ab 100644 --- a/extension/src/utils/string.ts +++ b/extension/src/utils/string.ts @@ -149,13 +149,24 @@ export const groupTestResults = ( codeAnswer: string[] = [], invalidTestCaseIdx: number | undefined, testInputs: TestCases, - testOutputs: string[], - testExpectedOutputs: string[], + testOutputs: string[] = [], + testExpectedOutputs: string[] = [], testResultError: string = "" ): TestResult[] => { const numCases = testInputs.length; const varCount = variables?.count ?? 0; + console.log("grouping test results with: ", { + variables, + testResultStatus, + codeAnswer, + invalidTestCaseIdx, + testInputs, + testOutputs, + testExpectedOutputs, + testResultError, + }); + const baseResponse = (overrides: Partial = {}): TestResult => ({ testResultStatus, testResult: [], @@ -167,17 +178,17 @@ export const groupTestResults = ( ? codeAnswer.length : codeAnswer.findIndex((val) => val !== "0"); - // Validation - if (!variables || testExpectedOutputs.length !== numCases) { - console.error( - "Variables undefined or test lengths mismatch", - variables, - testInputs, - testOutputs, - testExpectedOutputs - ); - return [baseResponse()]; - } + // // Validation + // if (!variables || testExpectedOutputs.length !== numCases) { + // console.error( + // "Variables undefined or test lengths mismatch", + // variables, + // testInputs, + // testOutputs, + // testExpectedOutputs + // ); + // return [baseResponse()]; + // } // Global error states (no need to iterate test cases) if ( @@ -207,7 +218,7 @@ export const groupTestResults = ( return [ baseResponse({ errorMessage: testResultError, - invalidTestCaseIdx, + invalidTestCaseIdx: invalidTestCaseIdx, }), ]; } From 522ce3caaeac3e5d99650505b7a84bceb207bfff Mon Sep 17 00:00:00 2001 From: PhuongLe Date: Mon, 2 Mar 2026 23:52:50 -0500 Subject: [PATCH 05/10] commit to keep progress before starting on frontend, test result from backend compplete --- .../panel/editor/tab/TestResultTab.tsx | 267 ++++++++++++------ extension/src/types/index.ts | 12 +- extension/src/utils/string.ts | 7 +- 3 files changed, 182 insertions(+), 104 deletions(-) diff --git a/extension/src/components/panel/editor/tab/TestResultTab.tsx b/extension/src/components/panel/editor/tab/TestResultTab.tsx index 20737bde..b91ec6d6 100644 --- a/extension/src/components/panel/editor/tab/TestResultTab.tsx +++ b/extension/src/components/panel/editor/tab/TestResultTab.tsx @@ -12,115 +12,198 @@ interface TestResultTabProps { activePeer: Identifiable | undefined; activeTestResult: SelectableTestResult | undefined; selectTestResult: (index: number) => void; - // generalResult: boolean; } +type TestResultStatus = + | "Accepted" + | "Wrong Answer" + | "Time Limit Exceeded" + | "Runtime Error" + | "Compile Error" + | "Memory Limit Exceeded"; + +const STATUS_CONFIG: Record< + TestResultStatus, + { label: string; className: string } +> = { + Accepted: { label: "Accepted", className: "text-green-500" }, + "Wrong Answer": { label: "Wrong Answer", className: "text-red-500" }, + "Time Limit Exceeded": { + label: "Time Limit Exceeded", + className: "text-yellow-500", + }, + "Runtime Error": { label: "Runtime Error", className: "text-red-500" }, + "Compile Error": { label: "Compile Error", className: "text-red-500" }, + "Memory Limit Exceeded": { + label: "Memory Limit Exceeded", + className: "text-yellow-500", + }, +}; + +const StatusBadge: React.FC<{ status: TestResultStatus }> = ({ status }) => { + const config = STATUS_CONFIG[status] ?? { + label: status, + className: "text-label-1 dark:text-dark-label-1", + }; + return {config.label}; +}; + +interface CaseButtonProps { + idx: number; + passed: boolean; + selected: boolean; + onClick: () => void; +} + +const CaseButton: React.FC = ({ + idx, + passed, + selected, + onClick, +}) => { + const baseClasses = + "relative inline-flex items-center whitespace-nowrap rounded-lg px-4 py-1 text-sm font-semibold focus:outline-none"; + const selectedClasses = + "bg-fill-3 dark:bg-dark-fill-3 hover:bg-fill-2 dark:hover:bg-dark-fill-2 hover:text-label-1 dark:hover:text-dark-label-1 text-label-1 dark:text-dark-label-1"; + const unselectedClasses = + "hover:bg-fill-2 dark:hover:bg-dark-fill-2 text-label-2 dark:text-dark-label-2 hover:text-label-1 dark:hover:text-dark-label-1 dark:bg-dark-transparent bg-transparent"; + + return ( +
+ +
+ ); +}; + +const ResultField: React.FC<{ label: string; value: string }> = ({ + label, + value, +}) => ( + <> +
+ {label} +
+
+
+ {value} +
+
+ +); + +const TestResultContent: React.FC<{ + activeTestResult: SelectableTestResult; +}> = ({ activeTestResult }) => { + const status = activeTestResult.testResultStatus as TestResultStatus; + + // For compile errors, show error message instead of test cases + if (status === "Compile Error") { + return ( +
+ +
+ ); + } + + // For runtime errors, show error with partial results if available + if (status === "Runtime Error") { + return ( +
+ + {activeTestResult.testResult?.length > 0 && ( + + )} +
+ ); + } + + // Default: show test case results (Accepted, Wrong Answer, TLE, MLE) + return ( +
+ +
+ ); +}; + +const TestCaseResults: React.FC<{ testResults: ResultAssignment[] }> = ({ + testResults, +}) => ( +
+
+ {testResults.map((testResult, testIdx) => ( + +
+ Input +
+ {Array.isArray(testResult.input) && + testResult.input.map((input, idx) => ( + +
+
+ {input.variable + ? `${input.variable}=${input.value}` + : input.value} +
+
+
+ ))} + + +
+ ))} +
+
+); + export const TestResultTab: React.FC = ({ activePeer, activeTestResult, selectTestResult, - // generalResult, }) => { const { self } = useRoomData(); - console.log( - "all test results (TestResultTab): ", - activePeer?.questions[self?.url ?? ""]?.testResults ?? [] - ); + const testResults = activePeer?.questions[self?.url ?? ""]?.testResults ?? []; + return (
- {activeTestResult ? ( - <> - {/* {generalResult ? ( - Accepted - ) : ( - Wrong Answer - )} */} - - ) : null} -
-
- {(activePeer?.questions[self?.url ?? ""]?.testResults ?? []).map( - (test: SelectableTestResult, idx: number) => { - const passed = (test.testResult ?? []).every( - (r: ResultAssignment) => r.output === r.expected - ); - const selected = !!test.selected; - return ( -
selectTestResult(idx)}> - {selected ? ( - passed ? ( - - ) : ( - - ) - ) : passed ? ( - - ) : ( - - )} -
- ); - } + {activeTestResult && ( + )}
-
-
-
-
- {activeTestResult?.testResult.map( - (testResult: ResultAssignment, testIdx: number) => ( - -
- Input -
- {testResult.input?.map((input, idx) => ( - -
-
- {input.variable - ? `${input.variable}=${input.value}` - : input.value} -
-
-
- )) ?? null} - - {/* Output */} -
- Output -
-
-
- {testResult.output ?? "-"} -
-
- - {/* Expected */} -
- Expected -
-
-
- {testResult.expected ?? "-"} -
-
-
- ) - ) ?? null} -
+
+ {testResults.map((test: SelectableTestResult, idx: number) => { + const passed = (test.testResult ?? []).every( + (r: ResultAssignment) => r.output === r.expected + ); + return ( + selectTestResult(idx)} + /> + ); + })}
+ {activeTestResult && ( + + )}
); diff --git a/extension/src/types/index.ts b/extension/src/types/index.ts index b8d8378c..fd72a579 100644 --- a/extension/src/types/index.ts +++ b/extension/src/types/index.ts @@ -26,17 +26,7 @@ export interface TestCase { } export interface TestResult { - testResultStatus: - | "Unexamined" - | "Accepted" - | "Unknown Error" - | "Runtime Error" - | "Time Limit Exceeded" - | "Memory Limit Exceeded" - | "Output Limit Exceeded" - | "Compile Error" - | "Invalid Testcase" - | string; + testResultStatus: string; errorMessage?: string; lastTestCaseRun?: number; invalidTestCaseIdx?: number; diff --git a/extension/src/utils/string.ts b/extension/src/utils/string.ts index 83a6e3ab..efc18575 100644 --- a/extension/src/utils/string.ts +++ b/extension/src/utils/string.ts @@ -244,8 +244,13 @@ export const groupTestResults = ( value: t.value, })); + //compare output and expected output to determine pass or fail for each test case + //if all matches, keep as "Accepted", if not, change to "Wrong Answer" + const caseResultStatus = + testOutputs[i] === testExpectedOutputs[i] ? "Accepted" : "Wrong Answer"; + results.push({ - testResultStatus, + testResultStatus: caseResultStatus, testResult: [ { input, From d6855b4876f1cef58ca5e7d00812efdc038c593f Mon Sep 17 00:00:00 2001 From: PhuongLe Date: Tue, 3 Mar 2026 00:55:46 -0500 Subject: [PATCH 06/10] frontend in progress --- .../panel/editor/tab/TestResultTab.tsx | 232 ++++++------------ .../editor/tab/test-result/AcceptedResult.tsx | 57 +++++ .../tab/test-result/CompileErrorResult.tsx | 20 ++ .../tab/test-result/InvalidTestCaseResult.tsx | 20 ++ .../test-result/MemoryLimitExceededResult.tsx | 20 ++ .../tab/test-result/RuntimeErrorResult.tsx | 20 ++ .../test-result/TimeLimitExceededResult.tsx | 20 ++ .../tab/test-result/WrongAnswerResult.tsx | 57 +++++ .../panel/editor/tab/test-result/index.ts | 8 + .../panel/editor/tab/test-result/types.ts | 14 ++ extension/src/constants/index.ts | 6 +- extension/src/utils/messages.ts | 4 +- extension/src/utils/string.ts | 2 +- 13 files changed, 317 insertions(+), 163 deletions(-) create mode 100644 extension/src/components/panel/editor/tab/test-result/AcceptedResult.tsx create mode 100644 extension/src/components/panel/editor/tab/test-result/CompileErrorResult.tsx create mode 100644 extension/src/components/panel/editor/tab/test-result/InvalidTestCaseResult.tsx create mode 100644 extension/src/components/panel/editor/tab/test-result/MemoryLimitExceededResult.tsx create mode 100644 extension/src/components/panel/editor/tab/test-result/RuntimeErrorResult.tsx create mode 100644 extension/src/components/panel/editor/tab/test-result/TimeLimitExceededResult.tsx create mode 100644 extension/src/components/panel/editor/tab/test-result/WrongAnswerResult.tsx create mode 100644 extension/src/components/panel/editor/tab/test-result/index.ts create mode 100644 extension/src/components/panel/editor/tab/test-result/types.ts diff --git a/extension/src/components/panel/editor/tab/TestResultTab.tsx b/extension/src/components/panel/editor/tab/TestResultTab.tsx index b91ec6d6..203640cb 100644 --- a/extension/src/components/panel/editor/tab/TestResultTab.tsx +++ b/extension/src/components/panel/editor/tab/TestResultTab.tsx @@ -7,20 +7,16 @@ import { SelectableTestResult, } from "@cb/types"; import React from "react"; - -interface TestResultTabProps { - activePeer: Identifiable | undefined; - activeTestResult: SelectableTestResult | undefined; - selectTestResult: (index: number) => void; -} - -type TestResultStatus = - | "Accepted" - | "Wrong Answer" - | "Time Limit Exceeded" - | "Runtime Error" - | "Compile Error" - | "Memory Limit Exceeded"; +import { + AcceptedResult, + CompileErrorResult, + InvalidTestCaseResult, + MemoryLimitExceededResult, + RuntimeErrorResult, + TestResultStatus, + TimeLimitExceededResult, + WrongAnswerResult, +} from "./test-result"; const STATUS_CONFIG: Record< TestResultStatus, @@ -30,140 +26,48 @@ const STATUS_CONFIG: Record< "Wrong Answer": { label: "Wrong Answer", className: "text-red-500" }, "Time Limit Exceeded": { label: "Time Limit Exceeded", - className: "text-yellow-500", + className: "text-red-500", + }, + "Invalid Test Case": { + label: "Invalid Test Case", + className: "text-red-500", }, "Runtime Error": { label: "Runtime Error", className: "text-red-500" }, "Compile Error": { label: "Compile Error", className: "text-red-500" }, "Memory Limit Exceeded": { label: "Memory Limit Exceeded", - className: "text-yellow-500", + className: "text-red-500", }, }; -const StatusBadge: React.FC<{ status: TestResultStatus }> = ({ status }) => { - const config = STATUS_CONFIG[status] ?? { - label: status, - className: "text-label-1 dark:text-dark-label-1", - }; - return {config.label}; -}; - -interface CaseButtonProps { - idx: number; - passed: boolean; - selected: boolean; - onClick: () => void; -} - -const CaseButton: React.FC = ({ - idx, - passed, - selected, - onClick, -}) => { - const baseClasses = - "relative inline-flex items-center whitespace-nowrap rounded-lg px-4 py-1 text-sm font-semibold focus:outline-none"; - const selectedClasses = - "bg-fill-3 dark:bg-dark-fill-3 hover:bg-fill-2 dark:hover:bg-dark-fill-2 hover:text-label-1 dark:hover:text-dark-label-1 text-label-1 dark:text-dark-label-1"; - const unselectedClasses = - "hover:bg-fill-2 dark:hover:bg-dark-fill-2 text-label-2 dark:text-dark-label-2 hover:text-label-1 dark:hover:text-dark-label-1 dark:bg-dark-transparent bg-transparent"; - - return ( -
- -
- ); -}; - -const ResultField: React.FC<{ label: string; value: string }> = ({ - label, - value, -}) => ( - <> -
- {label} -
-
-
- {value} -
-
- -); - -const TestResultContent: React.FC<{ - activeTestResult: SelectableTestResult; -}> = ({ activeTestResult }) => { +const renderTestResultContent = (activeTestResult: SelectableTestResult) => { const status = activeTestResult.testResultStatus as TestResultStatus; - // For compile errors, show error message instead of test cases - if (status === "Compile Error") { - return ( -
- -
- ); - } - - // For runtime errors, show error with partial results if available - if (status === "Runtime Error") { - return ( -
- - {activeTestResult.testResult?.length > 0 && ( - - )} -
- ); + switch (status) { + case "Accepted": + return ; + case "Wrong Answer": + return ; + case "Compile Error": + return ; + case "Runtime Error": + return ; + case "Time Limit Exceeded": + return ; + case "Memory Limit Exceeded": + return ; + case "Invalid Test Case": + return ; + default: + return ; } - - // Default: show test case results (Accepted, Wrong Answer, TLE, MLE) - return ( -
- -
- ); }; -const TestCaseResults: React.FC<{ testResults: ResultAssignment[] }> = ({ - testResults, -}) => ( -
-
- {testResults.map((testResult, testIdx) => ( - -
- Input -
- {Array.isArray(testResult.input) && - testResult.input.map((input, idx) => ( - -
-
- {input.variable - ? `${input.variable}=${input.value}` - : input.value} -
-
-
- ))} - - -
- ))} -
-
-); +interface TestResultTabProps { + activePeer: Identifiable | undefined; + activeTestResult: SelectableTestResult | undefined; + selectTestResult: (index: number) => void; +} export const TestResultTab: React.FC = ({ activePeer, @@ -173,37 +77,51 @@ export const TestResultTab: React.FC = ({ const { self } = useRoomData(); const testResults = activePeer?.questions[self?.url ?? ""]?.testResults ?? []; + const getStatusBadge = (status: TestResultStatus) => { + const config = STATUS_CONFIG[status] ?? { + label: status, + className: "text-label-1 dark:text-dark-label-1", + }; + return {config.label}; + }; + return (
- {activeTestResult && ( - - )} + {activeTestResult && + getStatusBadge( + activeTestResult.testResultStatus as TestResultStatus + )}
- {testResults.map((test: SelectableTestResult, idx: number) => { - const passed = (test.testResult ?? []).every( - (r: ResultAssignment) => r.output === r.expected - ); - return ( - selectTestResult(idx)} - /> - ); - })} + {(activeTestResult?.testResultStatus === "Accepted" || + activeTestResult?.testResultStatus === "Wrong Answer") && + testResults.map((test: SelectableTestResult, idx: number) => { + const passed = (test.testResult ?? []).every( + (r: ResultAssignment) => r.output === r.expected + ); + const selected = !!test.selected; + const baseClasses = + "relative inline-flex items-center whitespace-nowrap rounded-lg px-4 py-1 text-sm font-semibold focus:outline-none"; + const selectedClasses = + "bg-fill-3 dark:bg-dark-fill-3 hover:bg-fill-2 dark:hover:bg-dark-fill-2 hover:text-label-1 dark:hover:text-dark-label-1 text-label-1 dark:text-dark-label-1"; + const unselectedClasses = + "hover:bg-fill-2 dark:hover:bg-dark-fill-2 text-label-2 dark:text-dark-label-2 hover:text-label-1 dark:hover:text-dark-label-1 dark:bg-dark-transparent bg-transparent"; + return ( +
selectTestResult(idx)}> + +
+ ); + })}
- {activeTestResult && ( - - )} + {activeTestResult && renderTestResultContent(activeTestResult)}
); diff --git a/extension/src/components/panel/editor/tab/test-result/AcceptedResult.tsx b/extension/src/components/panel/editor/tab/test-result/AcceptedResult.tsx new file mode 100644 index 00000000..4838c69d --- /dev/null +++ b/extension/src/components/panel/editor/tab/test-result/AcceptedResult.tsx @@ -0,0 +1,57 @@ +import { ResultAssignment } from "@cb/types"; +import React from "react"; +import { TestResultContentProps } from "./types"; + +export const AcceptedResult: React.FC = ({ + activeTestResult, +}) => { + return ( +
+
+
+ {activeTestResult.testResult?.map( + (testResult: ResultAssignment, testIdx: number) => ( + +
+ Input +
+ {Array.isArray(testResult.input) && + testResult.input.map((input, idx) => ( + +
+
+ {input.variable + ? `${input.variable}=${input.value}` + : input.value} +
+
+
+ ))} + + {/* Output */} +
+ Output +
+
+
+ {testResult.output ?? "-"} +
+
+ + {/* Expected */} +
+ Expected +
+
+
+ {testResult.expected ?? "-"} +
+
+
+ ) + ) ?? null} +
+
+
+ ); +}; diff --git a/extension/src/components/panel/editor/tab/test-result/CompileErrorResult.tsx b/extension/src/components/panel/editor/tab/test-result/CompileErrorResult.tsx new file mode 100644 index 00000000..2538fcb6 --- /dev/null +++ b/extension/src/components/panel/editor/tab/test-result/CompileErrorResult.tsx @@ -0,0 +1,20 @@ +import { TestResult } from "@cb/types"; +import React from "react"; +import { TestResultContentProps } from "./types"; + +export const CompileErrorResult: React.FC = ({ + activeTestResult, +}) => { + return ( +
+
+ Compile Error +
+
+
+ {(activeTestResult as TestResult).errorMessage} +
+
+
+ ); +}; diff --git a/extension/src/components/panel/editor/tab/test-result/InvalidTestCaseResult.tsx b/extension/src/components/panel/editor/tab/test-result/InvalidTestCaseResult.tsx new file mode 100644 index 00000000..06186a67 --- /dev/null +++ b/extension/src/components/panel/editor/tab/test-result/InvalidTestCaseResult.tsx @@ -0,0 +1,20 @@ +import { TestResult } from "@cb/types"; +import React from "react"; +import { TestResultContentProps } from "./types"; + +export const InvalidTestCaseResult: React.FC = ({ + activeTestResult, +}) => { + return ( +
+
+ Invalid Test Case +
+
+
+ {(activeTestResult as TestResult).errorMessage} +
+
+
+ ); +}; diff --git a/extension/src/components/panel/editor/tab/test-result/MemoryLimitExceededResult.tsx b/extension/src/components/panel/editor/tab/test-result/MemoryLimitExceededResult.tsx new file mode 100644 index 00000000..059130a8 --- /dev/null +++ b/extension/src/components/panel/editor/tab/test-result/MemoryLimitExceededResult.tsx @@ -0,0 +1,20 @@ +import { TestResult } from "@cb/types"; +import React from "react"; +import { TestResultContentProps } from "./types"; + +export const MemoryLimitExceededResult: React.FC = ({ + activeTestResult, +}) => { + return ( +
+
+ Memory Limit Exceeded +
+
+
+ {(activeTestResult as TestResult).errorMessage} +
+
+
+ ); +}; diff --git a/extension/src/components/panel/editor/tab/test-result/RuntimeErrorResult.tsx b/extension/src/components/panel/editor/tab/test-result/RuntimeErrorResult.tsx new file mode 100644 index 00000000..bdaa6506 --- /dev/null +++ b/extension/src/components/panel/editor/tab/test-result/RuntimeErrorResult.tsx @@ -0,0 +1,20 @@ +import { TestResult } from "@cb/types"; +import React from "react"; +import { TestResultContentProps } from "./types"; + +export const RuntimeErrorResult: React.FC = ({ + activeTestResult, +}) => { + return ( +
+
+ Runtime Error +
+
+
+ {(activeTestResult as TestResult).errorMessage} +
+
+
+ ); +}; diff --git a/extension/src/components/panel/editor/tab/test-result/TimeLimitExceededResult.tsx b/extension/src/components/panel/editor/tab/test-result/TimeLimitExceededResult.tsx new file mode 100644 index 00000000..23ec1783 --- /dev/null +++ b/extension/src/components/panel/editor/tab/test-result/TimeLimitExceededResult.tsx @@ -0,0 +1,20 @@ +import { TestResult } from "@cb/types"; +import React from "react"; +import { TestResultContentProps } from "./types"; + +export const TimeLimitExceededResult: React.FC = ({ + activeTestResult, +}) => { + return ( +
+
+ Time Limit Exceeded +
+
+
+ {(activeTestResult as TestResult).errorMessage} +
+
+
+ ); +}; diff --git a/extension/src/components/panel/editor/tab/test-result/WrongAnswerResult.tsx b/extension/src/components/panel/editor/tab/test-result/WrongAnswerResult.tsx new file mode 100644 index 00000000..69cc6e9b --- /dev/null +++ b/extension/src/components/panel/editor/tab/test-result/WrongAnswerResult.tsx @@ -0,0 +1,57 @@ +import { ResultAssignment } from "@cb/types"; +import React from "react"; +import { TestResultContentProps } from "./types"; + +export const WrongAnswerResult: React.FC = ({ + activeTestResult, +}) => { + return ( +
+
+
+ {activeTestResult.testResult?.map( + (testResult: ResultAssignment, testIdx: number) => ( + +
+ Input +
+ {Array.isArray(testResult.input) && + testResult.input.map((input, idx) => ( + +
+
+ {input.variable + ? `${input.variable}=${input.value}` + : input.value} +
+
+
+ ))} + + {/* Output */} +
+ Output +
+
+
+ {testResult.output ?? "-"} +
+
+ + {/* Expected */} +
+ Expected +
+
+
+ {testResult.expected ?? "-"} +
+
+
+ ) + ) ?? null} +
+
+
+ ); +}; diff --git a/extension/src/components/panel/editor/tab/test-result/index.ts b/extension/src/components/panel/editor/tab/test-result/index.ts new file mode 100644 index 00000000..aa6894d9 --- /dev/null +++ b/extension/src/components/panel/editor/tab/test-result/index.ts @@ -0,0 +1,8 @@ +export { AcceptedResult } from "./AcceptedResult"; +export { CompileErrorResult } from "./CompileErrorResult"; +export { InvalidTestCaseResult } from "./InvalidTestCaseResult"; +export { MemoryLimitExceededResult } from "./MemoryLimitExceededResult"; +export { RuntimeErrorResult } from "./RuntimeErrorResult"; +export { TimeLimitExceededResult } from "./TimeLimitExceededResult"; +export * from "./types"; +export { WrongAnswerResult } from "./WrongAnswerResult"; diff --git a/extension/src/components/panel/editor/tab/test-result/types.ts b/extension/src/components/panel/editor/tab/test-result/types.ts new file mode 100644 index 00000000..1c3cdabe --- /dev/null +++ b/extension/src/components/panel/editor/tab/test-result/types.ts @@ -0,0 +1,14 @@ +import { SelectableTestResult } from "@cb/types"; + +export type TestResultStatus = + | "Accepted" + | "Wrong Answer" + | "Time Limit Exceeded" + | "Runtime Error" + | "Compile Error" + | "Memory Limit Exceeded" + | "Invalid Test Case"; + +export interface TestResultContentProps { + activeTestResult: SelectableTestResult; +} diff --git a/extension/src/constants/index.ts b/extension/src/constants/index.ts index 008a1486..bdd09996 100644 --- a/extension/src/constants/index.ts +++ b/extension/src/constants/index.ts @@ -114,7 +114,7 @@ export const PANEL = { }; export const TEST_RESULT_ERROR = { - "Runtime Error": "runtime_error", - "Compile Error": "compile_error", - "Invalid Testcase": "runtime_error", + "Runtime Error": "full_runtime_error", + "Compile Error": "full_compile_error", + "Invalid Test Case": "full_runtime_error", }; diff --git a/extension/src/utils/messages.ts b/extension/src/utils/messages.ts index 541bd7b5..0a16f563 100644 --- a/extension/src/utils/messages.ts +++ b/extension/src/utils/messages.ts @@ -30,7 +30,7 @@ export const getTestResultsPayload = ( } const statusMsg: string = testResults.invalid_testcase - ? "Invalid Testcase" + ? "Invalid Test Case" : testResults.status_msg; return { @@ -43,7 +43,7 @@ export const getTestResultsPayload = ( getTestsPayload(variables).tests, testResults.code_answer?.slice(0, -1), testResults.expected_code_answer?.slice(0, -1), - statusMsg === "Invalid Testcase" || + statusMsg === "Invalid Test Case" || statusMsg === "Runtime Error" || statusMsg === "Compile Error" ? testResults[TEST_RESULT_ERROR[statusMsg]] diff --git a/extension/src/utils/string.ts b/extension/src/utils/string.ts index efc18575..4e9199f0 100644 --- a/extension/src/utils/string.ts +++ b/extension/src/utils/string.ts @@ -214,7 +214,7 @@ export const groupTestResults = ( ]; } - if (testResultStatus === "Invalid Testcase") { + if (testResultStatus === "Invalid Test Case") { return [ baseResponse({ errorMessage: testResultError, From eb4050a35951b38fc875f414030c75065b7cd33d Mon Sep 17 00:00:00 2001 From: PhuongLe Date: Sat, 7 Mar 2026 19:23:01 -0500 Subject: [PATCH 07/10] frontend almost complete + fixed backend a little t send the correct object for each error case --- .../tab/test-result/CompileErrorResult.tsx | 6 +- .../tab/test-result/InvalidTestCaseResult.tsx | 8 +- .../test-result/MemoryLimitExceededResult.tsx | 15 +- .../tab/test-result/RuntimeErrorResult.tsx | 24 ++- .../test-result/TimeLimitExceededResult.tsx | 15 +- extension/src/utils/string.ts | 142 ++++++++---------- 6 files changed, 112 insertions(+), 98 deletions(-) diff --git a/extension/src/components/panel/editor/tab/test-result/CompileErrorResult.tsx b/extension/src/components/panel/editor/tab/test-result/CompileErrorResult.tsx index 2538fcb6..62caba0e 100644 --- a/extension/src/components/panel/editor/tab/test-result/CompileErrorResult.tsx +++ b/extension/src/components/panel/editor/tab/test-result/CompileErrorResult.tsx @@ -10,10 +10,8 @@ export const CompileErrorResult: React.FC = ({
Compile Error
-
-
- {(activeTestResult as TestResult).errorMessage} -
+
+ {(activeTestResult as TestResult).errorMessage}
); diff --git a/extension/src/components/panel/editor/tab/test-result/InvalidTestCaseResult.tsx b/extension/src/components/panel/editor/tab/test-result/InvalidTestCaseResult.tsx index 06186a67..4beb7b09 100644 --- a/extension/src/components/panel/editor/tab/test-result/InvalidTestCaseResult.tsx +++ b/extension/src/components/panel/editor/tab/test-result/InvalidTestCaseResult.tsx @@ -8,12 +8,10 @@ export const InvalidTestCaseResult: React.FC = ({ return (
- Invalid Test Case + {(activeTestResult as TestResult).invalidTestCaseIdx}
-
-
- {(activeTestResult as TestResult).errorMessage} -
+
+ {(activeTestResult as TestResult).errorMessage}
); diff --git a/extension/src/components/panel/editor/tab/test-result/MemoryLimitExceededResult.tsx b/extension/src/components/panel/editor/tab/test-result/MemoryLimitExceededResult.tsx index 059130a8..3df3b17e 100644 --- a/extension/src/components/panel/editor/tab/test-result/MemoryLimitExceededResult.tsx +++ b/extension/src/components/panel/editor/tab/test-result/MemoryLimitExceededResult.tsx @@ -1,4 +1,3 @@ -import { TestResult } from "@cb/types"; import React from "react"; import { TestResultContentProps } from "./types"; @@ -8,11 +7,21 @@ export const MemoryLimitExceededResult: React.FC = ({ return (
- Memory Limit Exceeded + Last Executed Input
- {(activeTestResult as TestResult).errorMessage} + {activeTestResult.testResult[ + activeTestResult.lastTestCaseRun + ? activeTestResult.lastTestCaseRun + : 0 + ]?.input.map((input, idx) => ( +
+ {input.variable + ? `${input.variable}=${input.value}` + : input.value} +
+ ))}
diff --git a/extension/src/components/panel/editor/tab/test-result/RuntimeErrorResult.tsx b/extension/src/components/panel/editor/tab/test-result/RuntimeErrorResult.tsx index bdaa6506..ada36228 100644 --- a/extension/src/components/panel/editor/tab/test-result/RuntimeErrorResult.tsx +++ b/extension/src/components/panel/editor/tab/test-result/RuntimeErrorResult.tsx @@ -1,18 +1,36 @@ -import { TestResult } from "@cb/types"; import React from "react"; import { TestResultContentProps } from "./types"; export const RuntimeErrorResult: React.FC = ({ activeTestResult, }) => { + console.log( + activeTestResult.testResult[ + activeTestResult.lastTestCaseRun ? activeTestResult.lastTestCaseRun : 0 + ] + ); + console.log(activeTestResult); return (
+
+ {activeTestResult.errorMessage} +
- Runtime Error + Last Executed Input
- {(activeTestResult as TestResult).errorMessage} + {activeTestResult.testResult[ + activeTestResult.lastTestCaseRun + ? activeTestResult.lastTestCaseRun + : 0 + ]?.input.map((input, idx) => ( +
+ {input.variable + ? `${input.variable}=${input.value}` + : input.value} +
+ ))}
diff --git a/extension/src/components/panel/editor/tab/test-result/TimeLimitExceededResult.tsx b/extension/src/components/panel/editor/tab/test-result/TimeLimitExceededResult.tsx index 23ec1783..97215267 100644 --- a/extension/src/components/panel/editor/tab/test-result/TimeLimitExceededResult.tsx +++ b/extension/src/components/panel/editor/tab/test-result/TimeLimitExceededResult.tsx @@ -1,4 +1,3 @@ -import { TestResult } from "@cb/types"; import React from "react"; import { TestResultContentProps } from "./types"; @@ -8,11 +7,21 @@ export const TimeLimitExceededResult: React.FC = ({ return (
- Time Limit Exceeded + Last Executed Input
- {(activeTestResult as TestResult).errorMessage} + {activeTestResult.testResult[ + activeTestResult.lastTestCaseRun + ? activeTestResult.lastTestCaseRun + : 0 + ]?.input.map((input, idx) => ( +
+ {input.variable + ? `${input.variable}=${input.value}` + : input.value} +
+ ))}
diff --git a/extension/src/utils/string.ts b/extension/src/utils/string.ts index 4e9199f0..f82a2f65 100644 --- a/extension/src/utils/string.ts +++ b/extension/src/utils/string.ts @@ -155,97 +155,81 @@ export const groupTestResults = ( ): TestResult[] => { const numCases = testInputs.length; const varCount = variables?.count ?? 0; - - console.log("grouping test results with: ", { - variables, - testResultStatus, - codeAnswer, - invalidTestCaseIdx, - testInputs, - testOutputs, - testExpectedOutputs, - testResultError, - }); - - const baseResponse = (overrides: Partial = {}): TestResult => ({ - testResultStatus, - testResult: [], - ...overrides, - }); + const results: TestResult[] = []; const getLastRunIndex = () => codeAnswer.findIndex((val) => val !== "0") === -1 ? codeAnswer.length : codeAnswer.findIndex((val) => val !== "0"); - // // Validation - // if (!variables || testExpectedOutputs.length !== numCases) { - // console.error( - // "Variables undefined or test lengths mismatch", - // variables, - // testInputs, - // testOutputs, - // testExpectedOutputs - // ); - // return [baseResponse()]; - // } + const createResponse = ( + input: { variable: string; value: string }[], + index: number, + overrides: Partial = {} + ): TestResult => ({ + testResultStatus, + testResult: [ + { + input, + output: testOutputs[index] ?? "", + expected: testExpectedOutputs[index] ?? "", + }, + ], + ...overrides, + }); - // Global error states (no need to iterate test cases) - if ( - [ - "Time Limit Exceeded", - "Memory Limit Exceeded", - "Output Limit Exceeded", - ].includes(testResultStatus) - ) { - return [ - baseResponse({ - lastTestCaseRun: getLastRunIndex(), - }), - ]; - } + for (let i = 0; i < numCases; i++) { + const currentTest = testInputs[i]; - if (["Runtime Error", "Compile Error"].includes(testResultStatus)) { - return [ - baseResponse({ - errorMessage: testResultError, - lastTestCaseRun: getLastRunIndex(), - }), - ]; - } + if (currentTest.test.length !== varCount) { + console.error( + `Case ${i} does not match expected variable count`, + variables, + testInputs + ); + return [{ testResultStatus, testResult: [] }]; + } - if (testResultStatus === "Invalid Test Case") { - return [ - baseResponse({ - errorMessage: testResultError, - invalidTestCaseIdx: invalidTestCaseIdx, - }), - ]; - } + const input = currentTest.test.map((t) => ({ + variable: t.variable ?? "", + value: t.value, + })); - // Accepted case - if (testResultStatus === "Accepted") { - const results: TestResult[] = []; + // Global error states - return immediately + if ( + [ + "Time Limit Exceeded", + "Memory Limit Exceeded", + "Output Limit Exceeded", + ].includes(testResultStatus) + ) { + return [createResponse(input, i, { lastTestCaseRun: getLastRunIndex() })]; + } - for (let i = 0; i < numCases; i++) { - const currentTest = testInputs[i]; + if (testResultStatus === "Runtime Error") { + return [ + createResponse(input, i, { + errorMessage: testResultError, + lastTestCaseRun: getLastRunIndex(), + }), + ]; + } - if (currentTest.test.length !== varCount) { - console.error( - `Case ${i} does not match expected variable count`, - variables, - testInputs - ); - return [baseResponse()]; - } + if (testResultStatus === "Compile Error") { + return [createResponse(input, i, { errorMessage: testResultError })]; + } - const input = currentTest.test.map((t) => ({ - variable: t.variable ?? "", - value: t.value, - })); + if (testResultStatus === "Invalid Test Case") { + return [ + createResponse(input, i, { + errorMessage: testResultError, + invalidTestCaseIdx, + }), + ]; + } - //compare output and expected output to determine pass or fail for each test case - //if all matches, keep as "Accepted", if not, change to "Wrong Answer" + // Accepted case - accumulate results + if (testResultStatus === "Accepted") { const caseResultStatus = testOutputs[i] === testExpectedOutputs[i] ? "Accepted" : "Wrong Answer"; @@ -260,11 +244,9 @@ export const groupTestResults = ( ], }); } - - return results; } - return [baseResponse()]; + return results.length > 0 ? results : [{ testResultStatus, testResult: [] }]; }; export const safeJsonParse = (content: string): object => { From 5a3e5830cfcc51ba4f45ee6f88c568fdc18f16ab Mon Sep 17 00:00:00 2001 From: PhuongLe Date: Sun, 8 Mar 2026 18:03:16 -0400 Subject: [PATCH 08/10] completed and cleaned up on frontend fix + refactor slightly on backend for grouping test result --- .../panel/editor/tab/TestResultTab.tsx | 11 +- .../editor/tab/test-result/AcceptedResult.tsx | 57 ----- .../tab/test-result/CompileErrorResult.tsx | 18 -- .../test-result/MemoryLimitExceededResult.tsx | 29 --- .../tab/test-result/RuntimeErrorResult.tsx | 38 --- .../test-result/TimeLimitExceededResult.tsx | 29 --- .../CompileErrorResult.tsx | 13 + .../InvalidTestCaseResult.tsx | 8 +- .../MemoryLimitExceededResult.tsx | 13 + .../RuntimeErrorResult.tsx | 14 ++ .../TestResultDisplay.tsx | 35 +++ .../TimeLimitExceededResult.tsx | 13 + .../WrongAnswerResult.tsx | 0 .../index.ts | 4 +- .../testResultComponents/sharedComponents.tsx | 112 +++++++++ .../types.ts | 0 extension/src/utils/string.ts | 234 +++++------------- 17 files changed, 271 insertions(+), 357 deletions(-) delete mode 100644 extension/src/components/panel/editor/tab/test-result/AcceptedResult.tsx delete mode 100644 extension/src/components/panel/editor/tab/test-result/CompileErrorResult.tsx delete mode 100644 extension/src/components/panel/editor/tab/test-result/MemoryLimitExceededResult.tsx delete mode 100644 extension/src/components/panel/editor/tab/test-result/RuntimeErrorResult.tsx delete mode 100644 extension/src/components/panel/editor/tab/test-result/TimeLimitExceededResult.tsx create mode 100644 extension/src/components/panel/editor/tab/testResultComponents/CompileErrorResult.tsx rename extension/src/components/panel/editor/tab/{test-result => testResultComponents}/InvalidTestCaseResult.tsx (55%) create mode 100644 extension/src/components/panel/editor/tab/testResultComponents/MemoryLimitExceededResult.tsx create mode 100644 extension/src/components/panel/editor/tab/testResultComponents/RuntimeErrorResult.tsx create mode 100644 extension/src/components/panel/editor/tab/testResultComponents/TestResultDisplay.tsx create mode 100644 extension/src/components/panel/editor/tab/testResultComponents/TimeLimitExceededResult.tsx rename extension/src/components/panel/editor/tab/{test-result => testResultComponents}/WrongAnswerResult.tsx (100%) rename extension/src/components/panel/editor/tab/{test-result => testResultComponents}/index.ts (76%) create mode 100644 extension/src/components/panel/editor/tab/testResultComponents/sharedComponents.tsx rename extension/src/components/panel/editor/tab/{test-result => testResultComponents}/types.ts (100%) diff --git a/extension/src/components/panel/editor/tab/TestResultTab.tsx b/extension/src/components/panel/editor/tab/TestResultTab.tsx index 203640cb..25966726 100644 --- a/extension/src/components/panel/editor/tab/TestResultTab.tsx +++ b/extension/src/components/panel/editor/tab/TestResultTab.tsx @@ -8,15 +8,14 @@ import { } from "@cb/types"; import React from "react"; import { - AcceptedResult, CompileErrorResult, InvalidTestCaseResult, MemoryLimitExceededResult, RuntimeErrorResult, + TestResultDisplay, TestResultStatus, TimeLimitExceededResult, - WrongAnswerResult, -} from "./test-result"; +} from "./testResultComponents"; const STATUS_CONFIG: Record< TestResultStatus, @@ -45,9 +44,9 @@ const renderTestResultContent = (activeTestResult: SelectableTestResult) => { switch (status) { case "Accepted": - return ; + return ; case "Wrong Answer": - return ; + return ; case "Compile Error": return ; case "Runtime Error": @@ -59,7 +58,7 @@ const renderTestResultContent = (activeTestResult: SelectableTestResult) => { case "Invalid Test Case": return ; default: - return ; + return ; } }; diff --git a/extension/src/components/panel/editor/tab/test-result/AcceptedResult.tsx b/extension/src/components/panel/editor/tab/test-result/AcceptedResult.tsx deleted file mode 100644 index 4838c69d..00000000 --- a/extension/src/components/panel/editor/tab/test-result/AcceptedResult.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import { ResultAssignment } from "@cb/types"; -import React from "react"; -import { TestResultContentProps } from "./types"; - -export const AcceptedResult: React.FC = ({ - activeTestResult, -}) => { - return ( -
-
-
- {activeTestResult.testResult?.map( - (testResult: ResultAssignment, testIdx: number) => ( - -
- Input -
- {Array.isArray(testResult.input) && - testResult.input.map((input, idx) => ( - -
-
- {input.variable - ? `${input.variable}=${input.value}` - : input.value} -
-
-
- ))} - - {/* Output */} -
- Output -
-
-
- {testResult.output ?? "-"} -
-
- - {/* Expected */} -
- Expected -
-
-
- {testResult.expected ?? "-"} -
-
-
- ) - ) ?? null} -
-
-
- ); -}; diff --git a/extension/src/components/panel/editor/tab/test-result/CompileErrorResult.tsx b/extension/src/components/panel/editor/tab/test-result/CompileErrorResult.tsx deleted file mode 100644 index 62caba0e..00000000 --- a/extension/src/components/panel/editor/tab/test-result/CompileErrorResult.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { TestResult } from "@cb/types"; -import React from "react"; -import { TestResultContentProps } from "./types"; - -export const CompileErrorResult: React.FC = ({ - activeTestResult, -}) => { - return ( -
-
- Compile Error -
-
- {(activeTestResult as TestResult).errorMessage} -
-
- ); -}; diff --git a/extension/src/components/panel/editor/tab/test-result/MemoryLimitExceededResult.tsx b/extension/src/components/panel/editor/tab/test-result/MemoryLimitExceededResult.tsx deleted file mode 100644 index 3df3b17e..00000000 --- a/extension/src/components/panel/editor/tab/test-result/MemoryLimitExceededResult.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import React from "react"; -import { TestResultContentProps } from "./types"; - -export const MemoryLimitExceededResult: React.FC = ({ - activeTestResult, -}) => { - return ( -
-
- Last Executed Input -
-
-
- {activeTestResult.testResult[ - activeTestResult.lastTestCaseRun - ? activeTestResult.lastTestCaseRun - : 0 - ]?.input.map((input, idx) => ( -
- {input.variable - ? `${input.variable}=${input.value}` - : input.value} -
- ))} -
-
-
- ); -}; diff --git a/extension/src/components/panel/editor/tab/test-result/RuntimeErrorResult.tsx b/extension/src/components/panel/editor/tab/test-result/RuntimeErrorResult.tsx deleted file mode 100644 index ada36228..00000000 --- a/extension/src/components/panel/editor/tab/test-result/RuntimeErrorResult.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import React from "react"; -import { TestResultContentProps } from "./types"; - -export const RuntimeErrorResult: React.FC = ({ - activeTestResult, -}) => { - console.log( - activeTestResult.testResult[ - activeTestResult.lastTestCaseRun ? activeTestResult.lastTestCaseRun : 0 - ] - ); - console.log(activeTestResult); - return ( -
-
- {activeTestResult.errorMessage} -
-
- Last Executed Input -
-
-
- {activeTestResult.testResult[ - activeTestResult.lastTestCaseRun - ? activeTestResult.lastTestCaseRun - : 0 - ]?.input.map((input, idx) => ( -
- {input.variable - ? `${input.variable}=${input.value}` - : input.value} -
- ))} -
-
-
- ); -}; diff --git a/extension/src/components/panel/editor/tab/test-result/TimeLimitExceededResult.tsx b/extension/src/components/panel/editor/tab/test-result/TimeLimitExceededResult.tsx deleted file mode 100644 index 97215267..00000000 --- a/extension/src/components/panel/editor/tab/test-result/TimeLimitExceededResult.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import React from "react"; -import { TestResultContentProps } from "./types"; - -export const TimeLimitExceededResult: React.FC = ({ - activeTestResult, -}) => { - return ( -
-
- Last Executed Input -
-
-
- {activeTestResult.testResult[ - activeTestResult.lastTestCaseRun - ? activeTestResult.lastTestCaseRun - : 0 - ]?.input.map((input, idx) => ( -
- {input.variable - ? `${input.variable}=${input.value}` - : input.value} -
- ))} -
-
-
- ); -}; diff --git a/extension/src/components/panel/editor/tab/testResultComponents/CompileErrorResult.tsx b/extension/src/components/panel/editor/tab/testResultComponents/CompileErrorResult.tsx new file mode 100644 index 00000000..49c471d1 --- /dev/null +++ b/extension/src/components/panel/editor/tab/testResultComponents/CompileErrorResult.tsx @@ -0,0 +1,13 @@ +import React from "react"; +import { ErrorMessage } from "./sharedComponents"; +import { TestResultContentProps } from "./types"; + +export const CompileErrorResult: React.FC = ({ + activeTestResult, +}) => { + return ( +
+ +
+ ); +}; diff --git a/extension/src/components/panel/editor/tab/test-result/InvalidTestCaseResult.tsx b/extension/src/components/panel/editor/tab/testResultComponents/InvalidTestCaseResult.tsx similarity index 55% rename from extension/src/components/panel/editor/tab/test-result/InvalidTestCaseResult.tsx rename to extension/src/components/panel/editor/tab/testResultComponents/InvalidTestCaseResult.tsx index 4beb7b09..cfe36a4e 100644 --- a/extension/src/components/panel/editor/tab/test-result/InvalidTestCaseResult.tsx +++ b/extension/src/components/panel/editor/tab/testResultComponents/InvalidTestCaseResult.tsx @@ -1,5 +1,5 @@ -import { TestResult } from "@cb/types"; import React from "react"; +import { ErrorMessage } from "./sharedComponents"; import { TestResultContentProps } from "./types"; export const InvalidTestCaseResult: React.FC = ({ @@ -8,11 +8,9 @@ export const InvalidTestCaseResult: React.FC = ({ return (
- {(activeTestResult as TestResult).invalidTestCaseIdx} -
-
- {(activeTestResult as TestResult).errorMessage} + Case {activeTestResult.invalidTestCaseIdx}
+
); }; diff --git a/extension/src/components/panel/editor/tab/testResultComponents/MemoryLimitExceededResult.tsx b/extension/src/components/panel/editor/tab/testResultComponents/MemoryLimitExceededResult.tsx new file mode 100644 index 00000000..7b77c87c --- /dev/null +++ b/extension/src/components/panel/editor/tab/testResultComponents/MemoryLimitExceededResult.tsx @@ -0,0 +1,13 @@ +import React from "react"; +import { LastExecutedInput } from "./sharedComponents"; +import { TestResultContentProps } from "./types"; + +export const MemoryLimitExceededResult: React.FC = ({ + activeTestResult, +}) => { + return ( +
+ +
+ ); +}; diff --git a/extension/src/components/panel/editor/tab/testResultComponents/RuntimeErrorResult.tsx b/extension/src/components/panel/editor/tab/testResultComponents/RuntimeErrorResult.tsx new file mode 100644 index 00000000..1825d316 --- /dev/null +++ b/extension/src/components/panel/editor/tab/testResultComponents/RuntimeErrorResult.tsx @@ -0,0 +1,14 @@ +import React from "react"; +import { ErrorMessage, LastExecutedInput } from "./sharedComponents"; +import { TestResultContentProps } from "./types"; + +export const RuntimeErrorResult: React.FC = ({ + activeTestResult, +}) => { + return ( +
+ + +
+ ); +}; diff --git a/extension/src/components/panel/editor/tab/testResultComponents/TestResultDisplay.tsx b/extension/src/components/panel/editor/tab/testResultComponents/TestResultDisplay.tsx new file mode 100644 index 00000000..8fc134f2 --- /dev/null +++ b/extension/src/components/panel/editor/tab/testResultComponents/TestResultDisplay.tsx @@ -0,0 +1,35 @@ +import { ResultAssignment, SelectableTestResult } from "@cb/types"; +import React from "react"; +import { + ExpectedDisplay, + InputDisplay, + OutputDisplay, +} from "./sharedComponents"; + +interface TestResultDisplayProps { + activeTestResult: SelectableTestResult; +} + +export const TestResultDisplay: React.FC = ({ + activeTestResult, +}) => { + return ( +
+
+
+ {activeTestResult.testResult?.map( + (testResult: ResultAssignment, testIdx: number) => ( + + {Array.isArray(testResult.input) && ( + + )} + + + + ) + ) ?? null} +
+
+
+ ); +}; diff --git a/extension/src/components/panel/editor/tab/testResultComponents/TimeLimitExceededResult.tsx b/extension/src/components/panel/editor/tab/testResultComponents/TimeLimitExceededResult.tsx new file mode 100644 index 00000000..19982569 --- /dev/null +++ b/extension/src/components/panel/editor/tab/testResultComponents/TimeLimitExceededResult.tsx @@ -0,0 +1,13 @@ +import React from "react"; +import { LastExecutedInput } from "./sharedComponents"; +import { TestResultContentProps } from "./types"; + +export const TimeLimitExceededResult: React.FC = ({ + activeTestResult, +}) => { + return ( +
+ +
+ ); +}; diff --git a/extension/src/components/panel/editor/tab/test-result/WrongAnswerResult.tsx b/extension/src/components/panel/editor/tab/testResultComponents/WrongAnswerResult.tsx similarity index 100% rename from extension/src/components/panel/editor/tab/test-result/WrongAnswerResult.tsx rename to extension/src/components/panel/editor/tab/testResultComponents/WrongAnswerResult.tsx diff --git a/extension/src/components/panel/editor/tab/test-result/index.ts b/extension/src/components/panel/editor/tab/testResultComponents/index.ts similarity index 76% rename from extension/src/components/panel/editor/tab/test-result/index.ts rename to extension/src/components/panel/editor/tab/testResultComponents/index.ts index aa6894d9..ed295140 100644 --- a/extension/src/components/panel/editor/tab/test-result/index.ts +++ b/extension/src/components/panel/editor/tab/testResultComponents/index.ts @@ -1,8 +1,8 @@ -export { AcceptedResult } from "./AcceptedResult"; export { CompileErrorResult } from "./CompileErrorResult"; export { InvalidTestCaseResult } from "./InvalidTestCaseResult"; export { MemoryLimitExceededResult } from "./MemoryLimitExceededResult"; export { RuntimeErrorResult } from "./RuntimeErrorResult"; +export * from "./sharedComponents"; +export { TestResultDisplay } from "./TestResultDisplay"; export { TimeLimitExceededResult } from "./TimeLimitExceededResult"; export * from "./types"; -export { WrongAnswerResult } from "./WrongAnswerResult"; diff --git a/extension/src/components/panel/editor/tab/testResultComponents/sharedComponents.tsx b/extension/src/components/panel/editor/tab/testResultComponents/sharedComponents.tsx new file mode 100644 index 00000000..13aba75e --- /dev/null +++ b/extension/src/components/panel/editor/tab/testResultComponents/sharedComponents.tsx @@ -0,0 +1,112 @@ +import { SelectableTestResult } from "@cb/types"; +import React from "react"; + +interface ErrorMessageProps { + message: string | undefined; +} + +export const ErrorMessage: React.FC = ({ message }) => ( +
+ {message} +
+); + +interface InputDisplayProps { + inputs: { variable?: string; value: string }[]; +} + +export const InputDisplay: React.FC = ({ inputs }) => ( + <> +
+ Input +
+ {inputs.map((input, idx) => ( +
+
+ {input.variable ? ( + <> +
{input.variable}=
+ {input.value} + + ) : ( + input.value + )} +
+
+ ))} + +); + +interface OutputDisplayProps { + output: string | undefined; +} + +export const OutputDisplay: React.FC = ({ output }) => ( + <> +
+ Output +
+
+
+ {output ?? "-"} +
+
+ +); + +interface ExpectedDisplayProps { + expected: string | undefined; +} + +export const ExpectedDisplay: React.FC = ({ + expected, +}) => ( + <> +
+ Expected +
+
+
+ {expected ?? "-"} +
+
+ +); + +interface LastExecutedInputProps { + activeTestResult: SelectableTestResult; +} + +export const LastExecutedInput: React.FC = ({ + activeTestResult, +}) => { + const lastIndex = activeTestResult.lastTestCaseRun ?? 0; + const inputs = activeTestResult.testResult[lastIndex]?.input ?? []; + + return ( + <> +
+ Last Executed Input +
+
+
+ {inputs.map((input, idx) => ( +
+ {input.variable ? ( + <> +
{input.variable}=
+ {input.value} + + ) : ( + input.value + )} +
+ ))} +
+
+ + ); +}; diff --git a/extension/src/components/panel/editor/tab/test-result/types.ts b/extension/src/components/panel/editor/tab/testResultComponents/types.ts similarity index 100% rename from extension/src/components/panel/editor/tab/test-result/types.ts rename to extension/src/components/panel/editor/tab/testResultComponents/types.ts diff --git a/extension/src/utils/string.ts b/extension/src/utils/string.ts index f82a2f65..0c2da751 100644 --- a/extension/src/utils/string.ts +++ b/extension/src/utils/string.ts @@ -29,120 +29,6 @@ export const groupTestCases = ( })); }; -// export const groupTestResults = ( -// variables: Question["variables"] | undefined, -// testResultStatus: string, -// codeAnswer: string[] = [], -// testResultError: string = "", -// invalidTestCaseIdx: number | undefined, -// testInputs: TestCases, -// testOutputs: string[], -// testExpectedOutputs: string[], -// ): TestResults => { -// const numCases = testInputs.length; - -// if ( -// variables == undefined || -// testInputs.length !== numCases || -// testExpectedOutputs.length !== numCases -// ) { -// console.error( -// "Variables are undefined or tests do not match up", -// variables, -// testInputs, -// testOutputs, -// testExpectedOutputs -// ); -// return { -// testResultStatus: testResultStatus, -// testResults: [], -// }; -// } - -// const results: TestResult[] = []; -// const varCount = variables?.count ?? 0; - -// for (let curCaseNo = 0; curCaseNo < numCases; curCaseNo++) { -// if (testInputs[curCaseNo].test.length !== varCount) { -// console.error( -// `Case ${curCaseNo} does not have the correct number of variables`, -// variables, -// testInputs -// ); -// return { -// testResultStatus: testResultStatus, -// testResults: [], -// }; -// } - -// switch (testResultStatus) { -// case "Time Limit Exceeded": -// case "Memory Limit Exceeded": -// case "Output Limit Exceeded": -// let lastTestCaseRunIdx = 0; -// let i = 0; -// while (i < codeAnswer.length && codeAnswer[i] === "0") { -// lastTestCaseRunIdx++; -// i++; -// } -// return { -// testResultStatus: testResultStatus, -// lastTestCaseRun: lastTestCaseRunIdx, -// testResults: [], -// }; -// case "Runtime Error": -// case "Compile Error": -// let lastTestCaseRunIdx = 0; -// let i = 0; -// while (i < codeAnswer.length && codeAnswer[i] === "0") { -// lastTestCaseRunIdx++; -// i++; -// } -// return { -// testResultStatus: testResultStatus, -// errorMessage: testResultError, -// lastTestCaseRun: lastTestCaseRunIdx, -// testResults: [], -// }; -// case "Invalid Testcase": -// return { -// testResultStatus: testResultStatus, -// errorMessage: testResultError, -// invalidTestCaseIdx: invalidTestCaseIdx, -// testResults: [], -// }; -// case "Accepted": -// const inputObj: Array = []; -// const currentTestInputs = testInputs[curCaseNo]; - -// for (let v = 0; v < varCount; v++) { -// const name: string = currentTestInputs.test[v].variable ?? ""; -// inputObj.push({ -// variable: name, -// value: currentTestInputs.test[v].value, -// }); -// } - -// results.push({ -// testResult: [ -// { -// input: inputObj, -// output: testOutputs[curCaseNo] ?? "", -// expected: testExpectedOutputs[curCaseNo] ?? "", -// }, -// ], -// }); - -// return { -// testResultStatus: testResultStatus, -// testResults: results, -// }; - -// default: -// break; -// } -// }; - export const groupTestResults = ( variables: Question["variables"] | undefined, testResultStatus: string, @@ -162,22 +48,63 @@ export const groupTestResults = ( ? codeAnswer.length : codeAnswer.findIndex((val) => val !== "0"); - const createResponse = ( - input: { variable: string; value: string }[], - index: number, + const getFirstInput = () => { + if (numCases === 0) return []; + const firstTest = testInputs[0]; + if (firstTest.test.length !== varCount) return []; + return firstTest.test.map((t) => ({ + variable: t.variable ?? "", + value: t.value, + })); + }; + + const createErrorResponse = ( overrides: Partial = {} ): TestResult => ({ testResultStatus, - testResult: [ - { - input, - output: testOutputs[index] ?? "", - expected: testExpectedOutputs[index] ?? "", - }, - ], + testResult: [{ input: getFirstInput(), output: "", expected: "" }], ...overrides, }); + if ( + [ + "Time Limit Exceeded", + "Memory Limit Exceeded", + "Output Limit Exceeded", + ].includes(testResultStatus) + ) { + return [createErrorResponse({ lastTestCaseRun: getLastRunIndex() })]; + } + + if (testResultStatus === "Runtime Error") { + return [ + createErrorResponse({ + errorMessage: testResultError, + lastTestCaseRun: getLastRunIndex(), + }), + ]; + } + + if (testResultStatus === "Compile Error") { + return [createErrorResponse({ errorMessage: testResultError })]; + } + + if (testResultStatus === "Invalid Test Case") { + return [ + createErrorResponse({ + errorMessage: testResultError, + invalidTestCaseIdx, + }), + ]; + } + + // Determine overall status before looping + const allMatch = testOutputs.every( + (output, i) => output === testExpectedOutputs[i] + ); + const overallStatus = allMatch ? "Accepted" : "Wrong Answer"; + + // Only loop for Accepted cases for (let i = 0; i < numCases; i++) { const currentTest = testInputs[i]; @@ -195,55 +122,16 @@ export const groupTestResults = ( value: t.value, })); - // Global error states - return immediately - if ( - [ - "Time Limit Exceeded", - "Memory Limit Exceeded", - "Output Limit Exceeded", - ].includes(testResultStatus) - ) { - return [createResponse(input, i, { lastTestCaseRun: getLastRunIndex() })]; - } - - if (testResultStatus === "Runtime Error") { - return [ - createResponse(input, i, { - errorMessage: testResultError, - lastTestCaseRun: getLastRunIndex(), - }), - ]; - } - - if (testResultStatus === "Compile Error") { - return [createResponse(input, i, { errorMessage: testResultError })]; - } - - if (testResultStatus === "Invalid Test Case") { - return [ - createResponse(input, i, { - errorMessage: testResultError, - invalidTestCaseIdx, - }), - ]; - } - - // Accepted case - accumulate results - if (testResultStatus === "Accepted") { - const caseResultStatus = - testOutputs[i] === testExpectedOutputs[i] ? "Accepted" : "Wrong Answer"; - - results.push({ - testResultStatus: caseResultStatus, - testResult: [ - { - input, - output: testOutputs[i] ?? "", - expected: testExpectedOutputs[i] ?? "", - }, - ], - }); - } + results.push({ + testResultStatus: overallStatus, + testResult: [ + { + input, + output: testOutputs[i] ?? "", + expected: testExpectedOutputs[i] ?? "", + }, + ], + }); } return results.length > 0 ? results : [{ testResultStatus, testResult: [] }]; From 80f2dabab84684628482dd7c1f04aa689637771b Mon Sep 17 00:00:00 2001 From: PhuongLe Date: Sun, 8 Mar 2026 21:48:45 -0400 Subject: [PATCH 09/10] clean up and add comments --- extension/src/components/panel/editor/EditorPanel.tsx | 7 ------- extension/src/entrypoints/router.ts | 1 + extension/src/utils/messages.ts | 2 ++ 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/extension/src/components/panel/editor/EditorPanel.tsx b/extension/src/components/panel/editor/EditorPanel.tsx index 361bb77f..8e014542 100644 --- a/extension/src/components/panel/editor/EditorPanel.tsx +++ b/extension/src/components/panel/editor/EditorPanel.tsx @@ -48,12 +48,6 @@ const EditorPanel = () => { (testResult) => testResult.selected ); - // const allTestResult = selectedPeer?.questions[url]?.testResults ?? []; - // console.log("generalResult", allTestResult); - // const generalResult = allTestResult.every( - // (r) => r.testResult && r.testResult[0].output === r.testResult[0].expected - // ); - return [ { value: "code", @@ -82,7 +76,6 @@ const EditorPanel = () => { activePeer={selectedPeer} activeTestResult={activeTestResult} selectTestResult={selectTestResult} - // generalResult={generalResult} /> ), }, diff --git a/extension/src/entrypoints/router.ts b/extension/src/entrypoints/router.ts index 7e0535df..0f6efbd3 100644 --- a/extension/src/entrypoints/router.ts +++ b/extension/src/entrypoints/router.ts @@ -5,6 +5,7 @@ export default defineUnlistedScript(() => { console.log("Inject router"); (function () { + console.log("Inject proxy"); if (window.__LC_FETCH_HOOKED__) return; window.__LC_FETCH_HOOKED__ = true; diff --git a/extension/src/utils/messages.ts b/extension/src/utils/messages.ts index 0a16f563..b3d1a671 100644 --- a/extension/src/utils/messages.ts +++ b/extension/src/utils/messages.ts @@ -29,6 +29,7 @@ export const getTestResultsPayload = ( }; } + // Because statusMessage of invalid test case is the same as runtime error although they are handled differently const statusMsg: string = testResults.invalid_testcase ? "Invalid Test Case" : testResults.status_msg; @@ -43,6 +44,7 @@ export const getTestResultsPayload = ( getTestsPayload(variables).tests, testResults.code_answer?.slice(0, -1), testResults.expected_code_answer?.slice(0, -1), + // To read the error message for invalid test case, runtime error, and compile error (empty for other test result statuses) statusMsg === "Invalid Test Case" || statusMsg === "Runtime Error" || statusMsg === "Compile Error" From dfac775c643df131868686be706560169bf2de11 Mon Sep 17 00:00:00 2001 From: PhuongLe Date: Wed, 11 Mar 2026 14:28:54 -0400 Subject: [PATCH 10/10] add icon and fixed minor ui --- extension/src/assets/accepted_icon.png | Bin 0 -> 5197 bytes extension/src/assets/wrong_answer_icon.png | Bin 0 -> 5419 bytes .../panel/editor/tab/TestResultTab.tsx | 13 ++++++++++--- .../testResultComponents/sharedComponents.tsx | 2 +- 4 files changed, 11 insertions(+), 4 deletions(-) create mode 100644 extension/src/assets/accepted_icon.png create mode 100644 extension/src/assets/wrong_answer_icon.png diff --git a/extension/src/assets/accepted_icon.png b/extension/src/assets/accepted_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..f67acc70df05984a76be9722bb6d3a9738e1cbfc GIT binary patch literal 5197 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 literal 0 HcmV?d00001 diff --git a/extension/src/assets/wrong_answer_icon.png b/extension/src/assets/wrong_answer_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..795ab1f958228af2bbe3c12ef640166c785d6b30 GIT binary patch literal 5419 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ͲGs{G2eIy*^-X@2?+W^G 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 { case "Invalid Test Case": return ; default: - return ; + return null; } }; @@ -111,9 +113,14 @@ export const TestResultTab: React.FC = ({ return (
selectTestResult(idx)}>
); diff --git a/extension/src/components/panel/editor/tab/testResultComponents/sharedComponents.tsx b/extension/src/components/panel/editor/tab/testResultComponents/sharedComponents.tsx index 13aba75e..f408e761 100644 --- a/extension/src/components/panel/editor/tab/testResultComponents/sharedComponents.tsx +++ b/extension/src/components/panel/editor/tab/testResultComponents/sharedComponents.tsx @@ -6,7 +6,7 @@ interface ErrorMessageProps { } export const ErrorMessage: React.FC = ({ message }) => ( -
+
{message}
);