From 6ec6c5b2d923f3fcf169e5e4427b0bba8de01a13 Mon Sep 17 00:00:00 2001 From: Kwon Seo Jin <97675977+B0XERCAT@users.noreply.github.com> Date: Sun, 6 Oct 2024 09:27:27 +0900 Subject: [PATCH] feat(fe): implement test in code editor (#2093) * feat(fe): add test button, implement poll request * feat(fe): implement testcase panel with dummy data * chore(fe): edit table row style * feat(fe): store test result * chore(fe): delete console.log * feat(fe): show wrong testcase numbers, adjust table style * feat(fe): implement testcase tabs * feat(fe): edit tab style * feat(fe): implement test result detail * feat(fe): implement delete tab button * fix(fe): fix testcase tab style * refactor(fe): componentize TestcaseTab TestSummary TestResultDetail * feat(fe): add output in testcase result * chore(fe): specify type statement Co-authored-by: Eunbi Kang * chore(fe): execute prettier * chore(fe): add padding bottom to panel, add ellipsis to long input output * chore(fe): add pre wrap for labeled field * chore(fe): simplify long styles using truncate --------- Co-authored-by: Jaehyeon Kim Co-authored-by: Eunbi Kang --- apps/frontend/components/EditorHeader.tsx | 98 +++++++- apps/frontend/components/EditorLayout.tsx | 8 +- .../components/EditorResizablePanel.tsx | 61 ++++- .../EditorResizablePanelWithContext.tsx | 31 +++ apps/frontend/components/TestcasePanel.tsx | 236 ++++++++++++++++++ apps/frontend/components/TestcaseTable.tsx | 78 ++++++ apps/frontend/stores/editor.ts | 31 ++- apps/frontend/types/type.ts | 13 + 8 files changed, 532 insertions(+), 24 deletions(-) create mode 100644 apps/frontend/components/EditorResizablePanelWithContext.tsx create mode 100644 apps/frontend/components/TestcasePanel.tsx create mode 100644 apps/frontend/components/TestcaseTable.tsx diff --git a/apps/frontend/components/EditorHeader.tsx b/apps/frontend/components/EditorHeader.tsx index 46a0061a07..697c85bb2f 100644 --- a/apps/frontend/components/EditorHeader.tsx +++ b/apps/frontend/components/EditorHeader.tsx @@ -25,6 +25,7 @@ import { fetcherWithAuth } from '@/lib/utils' import submitIcon from '@/public/submit.svg' import useAuthModalStore from '@/stores/authModal' import { + TestResultsContext, useLanguageStore, getKey, setItem, @@ -35,7 +36,8 @@ import type { Language, ProblemDetail, Submission, - Template + Template, + TestResult } from '@/types/type' import JSConfetti from 'js-confetti' import { Save } from 'lucide-react' @@ -44,7 +46,7 @@ import Image from 'next/image' import { useRouter } from 'next/navigation' import { useContext, useEffect, useRef, useState } from 'react' import { BsTrash3 } from 'react-icons/bs' -//import { IoPlayCircleOutline } from 'react-icons/io5' +import { IoPlayCircleOutline } from 'react-icons/io5' import { useInterval } from 'react-use' import { toast } from 'sonner' import { useStore } from 'zustand' @@ -61,9 +63,12 @@ export default function Editor({ templateString }: ProblemEditorProps) { const { language, setLanguage } = useLanguageStore() - const store = useContext(CodeContext) - if (!store) throw new Error('CodeContext is not provided') - const { code, setCode } = useStore(store) + const codeStore = useContext(CodeContext) + if (!codeStore) throw new Error('CodeContext is not provided') + const { code, setCode } = useStore(codeStore) + const testResultStore = useContext(TestResultsContext) + if (!testResultStore) throw new Error('TestResultsContext is not provided') + const { setTestResults } = useStore(testResultStore) const [loading, setLoading] = useState(false) const [submissionId, setSubmissionId] = useState(null) const [templateCode, setTemplateCode] = useState(null) @@ -165,6 +170,82 @@ export default function Editor({ } } + const submitTest = async () => { + if (code === '') { + toast.error('Please write code before test') + return + } + setLoading(true) + const res = await fetcherWithAuth.post('submission/test', { + json: { + language, + code: [ + { + id: 1, + text: code, + locked: false + } + ] + }, + searchParams: { + problemId: problem.id + }, + next: { + revalidate: 0 + } + }) + if (res.ok) { + pollTestResult() + } else { + setLoading(false) + if (res.status === 401) { + showSignIn() + toast.error('Log in first to test your code') + } else toast.error('Please try again later.') + } + } + + const pollTestResult = async () => { + let attempts = 0 + const maxAttempts = 10 + const pollingInterval = 2000 + + const poll = async () => { + const res = await fetcherWithAuth.get('submission/test', { + next: { + revalidate: 0 + } + }) + + if (res.ok) { + const resultArray: TestResult[] = await res.json() + + setTestResults(resultArray) + + const allJudged = resultArray.every( + (submission: TestResult) => submission.result !== 'Judging' + ) + + if (!allJudged) { + if (attempts < maxAttempts) { + attempts += 1 + setTimeout(poll, pollingInterval) + } else { + setLoading(false) + toast.error('Judging took too long. Please try again later.') + } + } else { + setLoading(false) + } + } else { + setLoading(false) + toast.error('Please try again later.') + } + } + + poll() + } + const saveCode = async () => { const session = await auth() if (!session) { @@ -242,16 +323,15 @@ export default function Editor({ Save - {/* TODO: Add Test function - - */}