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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 38 additions & 3 deletions extension/src/components/panel/editor/EditorPanel.tsx
Original file line number Diff line number Diff line change
@@ -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 { useCopyCode } from "@cb/hooks/editor";
Expand All @@ -27,7 +31,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 copyCode = useCopyCode();

Expand All @@ -40,6 +45,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",
Expand All @@ -59,8 +74,28 @@ const EditorPanel = () => {
/>
),
},
{
value: "testResult",
label: "Test Result",
Icon: FlaskConical,
Content: (
<TestResultTab
activePeer={selectedPeer}
activeTestResult={activeTestResult}
selectTestResult={selectTestResult}
generalResult={generalResult}
/>
),
},
];
}, [selectedPeer, activeTest, selectTest, getLanguageExtension, url]);
}, [
selectedPeer,
activeTest,
selectTest,
selectTestResult,
getLanguageExtension,
url,
]);

const hideCode = !selectedPeer?.questions[self?.url ?? ""]?.viewable;

Expand Down
118 changes: 118 additions & 0 deletions extension/src/components/panel/editor/tab/TestResultTab.tsx
Original file line number Diff line number Diff line change
@@ -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<PeerState> | undefined;
activeTestResult: SelectableTestResult | undefined;
selectTestResult: (index: number) => void;
generalResult: boolean;
}

export const TestResultTab: React.FC<TestResultTabProps> = ({
activePeer,
activeTestResult,
selectTestResult,
generalResult,
}) => {
const { self } = useRoomData();
return (
<SkeletonWrapper loading={false} className="relative">
<div className="p-5 flex flex-col space-y-4 h-full w-full overflow-scroll hide-scrollbar">
<div className="flex w-full flex-col items-start justify-between gap-4">
<div className="text-label-1 dark:text-dark-label-1 text-xl">
{activeTestResult ? (
<>
{generalResult ? (
<span className="text-green-500">Accepted</span>
) : (
<span className="text-red-500">Wrong Answer</span>
)}
</>
) : null}
</div>
<div className="hide-scrollbar flex flex-nowrap items-center gap-x-2 gap-y-4 overflow-x-scroll">
{(activePeer?.questions[self?.url ?? ""]?.testResults ?? []).map(
(test, idx) => {
const passed = (test.testResult ?? []).every(
(r: any) => r.output === r.expected
);
const selected = !!test.selected;
return (
<div key={idx} onClick={() => selectTestResult(idx)}>
{selected ? (
passed ? (
<button className="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 relative inline-flex items-center whitespace-nowrap rounded-lg px-4 py-1 text-sm font-semibold focus:outline-none">
V Case {idx + 1}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is V? May be we can use an icon component instead?

</button>
) : (
<button className="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 relative inline-flex items-center whitespace-nowrap rounded-lg px-4 py-1 text-sm font-semibold focus:outline-none">
X Case {idx + 1}
</button>
)
) : passed ? (
<button className="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 relative inline-flex items-center whitespace-nowrap rounded-lg bg-transparent px-4 py-1 text-sm font-semibold focus:outline-none">
V Case {idx + 1}
</button>
) : (
<button className="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 relative inline-flex items-center whitespace-nowrap rounded-lg bg-transparent px-4 py-1 text-sm font-semibold focus:outline-none">
X Case {idx + 1}
</button>
)}
</div>
);
}
)}
</div>
</div>
<div className="space-y-4 pb-12">
<div>
<div className="flex h-full w-full flex-col space-y-2">
{activeTestResult?.testResult.map((assignment, idx) => (
<React.Fragment key={idx}>
{/* Input / Value */}
<div className="text-label-3 dark:text-dark-label-3 text-xs font-medium">
Input
</div>
{Object.keys(assignment.input ?? {}).map(
(variable: string) => (
<div
key={variable}
className="font-menlo bg-fill-3 dark:bg-dark-fill-3 w-full cursor-text rounded-lg border border-transparent px-3 py-[10px]"
>
<div className="font-menlo placeholder:text-label-4 dark:placeholder:text-dark-label-4 sentry-unmask w-full resize-none whitespace-pre-wrap break-words outline-none">
{variable} = {assignment.input[variable]}
</div>
</div>
)
)}

{/* Output */}
<div className="text-label-3 dark:text-dark-label-3 text-xs font-medium">
Output
</div>
<div className="font-menlo bg-fill-3 dark:bg-dark-fill-3 w-full cursor-text rounded-lg border border-transparent px-3 py-[10px]">
<div className="font-menlo placeholder:text-label-4 dark:placeholder:text-dark-label-4 sentry-unmask w-full resize-none whitespace-pre-wrap break-words outline-none">
{assignment.output ?? "-"}
</div>
</div>

{/* Expected */}
<div className="text-label-3 dark:text-dark-label-3 text-xs font-medium">
Expected
</div>
<div className="font-menlo bg-fill-3 dark:bg-dark-fill-3 w-full cursor-text rounded-lg border border-transparent px-3 py-[10px]">
<div className="font-menlo placeholder:text-label-4 dark:placeholder:text-dark-label-4 sentry-unmask w-full resize-none whitespace-pre-wrap break-words outline-none">
{assignment.expected ?? "-"}
</div>
</div>
</React.Fragment>
)) ?? null}
</div>
</div>
</div>
</div>
</SkeletonWrapper>
);
};
1 change: 1 addition & 0 deletions extension/src/components/panel/editor/tab/index.ts
Original file line number Diff line number Diff line change
@@ -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";
1 change: 1 addition & 0 deletions extension/src/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export const DOM = {
IFRAME_CSS_ID: "codebuddy-css",
LEETCODE_ROOT_ID: "#qd-content",
LEETCODE_TEST_ID: ".cm-content",
LEETCODE_RUN_TEST_RESULT: ".cm-content",
LEETCODE_SUBMIT_BUTTON: '[data-e2e-locator="console-submit-button"]',
LEETCODE_SUBMISSION_RESULT: '[data-e2e-locator="submission-result"]',
PROBLEM_ID: ".elfjS",
Expand Down
100 changes: 100 additions & 0 deletions extension/src/services/controllers/MessageDispatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,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());
Expand Down Expand Up @@ -152,6 +153,84 @@ export class MessageDispatcher {
return () => observer.disconnect();
}

private subscribeToRunTest(): () => void {
let cleanup = () => {};
waitForElement('[data-e2e-locator="console-run-button"]')
.then(() => {
const clickHandler = async () => {
try {
const resultEl = await waitForElement(DOM.LEETCODE_RUN_TEST_RESULT);
await new Promise<void>((resolve, reject) => {
const observer = new MutationObserver(() => {
const hasContent =
(resultEl.textContent ?? "").trim().length > 0 ||
resultEl.children.length > 0;
if (hasContent) {
observer.disconnect();
clearTimeout(timeout);
resolve();
}
});

observer.observe(resultEl, {
childList: true,
subtree: true,
characterData: true,
});

const timeout = setTimeout(() => {
observer.disconnect();
reject(new Error("Timed out waiting for test result mutation"));
}, DOM.TIMEOUT);
}).catch((err) => {
console.warn("Timed out waiting for test result mutation", err);
});

const testResult = await this.getTestResultPayload();

this.roomStore.getState().actions.self.update({
questions: {
[testResult.url]: {
testResults: testResult.testResults,
},
},
});

console.log(
"Emitting test result -- message dispatcher",
testResult
);
this.emitter.emit("rtc.send.message", {
message: testResult,
});
} catch (e) {
console.error("Error handling run button click", e);
}
};

const delegatedHandler = (e: MouseEvent) => {
const el = e.target as Element | null;
if (el?.closest('[data-e2e-locator="console-run-button"]')) {
clickHandler();
}
};

document.addEventListener("click", delegatedHandler);

cleanup = () => {
document.removeEventListener("click", delegatedHandler);
};
})
.catch(() =>
console.error(
"Unable to find Run button",
'[data-e2e-locator="console-run-button"]'
)
);

return () => cleanup();
}

private subscribeToSubmission() {
// todo(nickbar01234): On teardown, we need to revert the changes
const sendSuccessSubmission = () => {
Expand Down Expand Up @@ -246,6 +325,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) {
Expand Down Expand Up @@ -378,4 +470,12 @@ export class MessageDispatcher {
.actions.room.getVariables(getNormalizedUrl(window.location.href))
);
}

private getTestResultPayload() {
return getTestResultsPayload(
this.roomStore
.getState()
.actions.room.getVariables(getNormalizedUrl(window.location.href))
);
}
}
Loading
Loading