Skip to content

Commit

Permalink
feat: support run cases (#6)
Browse files Browse the repository at this point in the history
* feat: support run test cases

* fix: local cache lost option
  • Loading branch information
ZLY201 authored Oct 22, 2023
1 parent 9e4e09c commit 4bfe8db
Show file tree
Hide file tree
Showing 13 changed files with 236 additions and 74 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/compressed-size.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,6 @@ jobs:
- name: Compressed size
uses: preactjs/compressed-size-action@v2
with:
pattern: dist
pattern: dist/**/*
compression: none
build-script: build
2 changes: 1 addition & 1 deletion config/i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,6 @@
},
"record_tip": {
"en": "In order to save memory, we only save the latest 8 records. You can choose to manually delete some useless data to break this limit.",
"zh-cn": "为了节省内存,我们仅保存最新的 8 条记录,您可以选择手动删除一些数据来突破这个限制"
"zh-cn": "为了节省内存,我们仅保存最新的 8 条记录,您可以在达到限制后手动删除一些数据"
}
}
2 changes: 1 addition & 1 deletion src/modules/Editor/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ function Editor() {
const [{ setting, currentProblem }] = useContext(Context);

function onChange(filename: ProblemFiles, content: string) {
if (!raw) return;
if (!raw || filename !== ProblemFiles.template) return;
localCache.setProblemCache(currentProblem.key, {
lastUpdated: content,
});
Expand Down
6 changes: 3 additions & 3 deletions src/modules/Editor/monaco-editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
} from '@src/utils/problems';
import { Setting } from '@src/utils/setting';
import emitter from '@src/utils/emit';
import { revalidateModel } from './revalidate-model';
import { validateMonacoModel } from '@src/utils/validate-monaco-model';

self.MonacoEnvironment = {
getWorker: function () {
Expand Down Expand Up @@ -49,7 +49,7 @@ export interface MonacoEditorProps {
height?: number | string;
namespace?: string;
raw: ProblemRaw;
selectedFilename: ProblemFiles;
selectedFilename: Exclude<ProblemFiles, ProblemFiles.test>;
onChange?: (filename: ProblemFiles, content: string) => void;
setting: Setting;
}
Expand Down Expand Up @@ -149,7 +149,7 @@ const MonacoEditor = decorateWithAutoResize(
for (const filename of Object.keys(this.props.raw)) {
this.models[filename].updateOptions(this.props.setting);
if (!filename.includes('node_modules')) {
revalidateModel(this.models[filename]);
validateMonacoModel(this.models[filename]);
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/modules/Question/Description.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ const Description = function () {
[currentProblem, state],
);

const updateDescription = useCallback(
const updateData = useCallback(
debounce(async function (problem: Problem) {
const desc = await getProblemDocs(problem, ProblemDocs.description);
setDesc(desc);
Expand All @@ -54,7 +54,7 @@ const Description = function () {
useEffect(
function () {
setLoading(true);
updateDescription(currentProblem);
updateData(currentProblem);
},
[currentProblem],
);
Expand Down
30 changes: 20 additions & 10 deletions src/modules/Results/index.module.less
Original file line number Diff line number Diff line change
Expand Up @@ -99,19 +99,29 @@
color: rgb(var(--red-6));
}
.result-error-info {
width: 100%;
height: auto;
padding: 8px 16px;
box-sizing: border-box;
border-radius: 8px;
font-size: 16px;
color: rgb(var(--red-6));
background-color: rgba(var(--red-6), 0.12);
.result-error-item {
padding: 4px 0;
}
}
}
.result-error-info {
width: 100%;
height: auto;
padding: 8px 16px;
box-sizing: border-box;
border-radius: 8px;
font-size: 16px;
color: rgb(var(--red-6));
background-color: rgba(var(--red-6), 0.12);
.result-error-item {
height: auto;
padding: 4px 0;
}
}
.result-pass {
width: 100%;
height: auto;
font-size: 24px;
color: rgb(var(--green-6));
}
}
.footer {
width: 100%;
Expand Down
211 changes: 166 additions & 45 deletions src/modules/Results/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,30 @@ import { CustomTabs } from '@src/components/CustomTabs';
import localCache, { PROBLEM_STATUS } from '@src/utils/local-cache';
import emitter from '@src/utils/emit';
import Context from '@src/utils/context';
import { NULL_CASE, Problem } from '@src/utils/problems';
import {
getProblemTestRaw,
NULL_CASE,
Problem,
ProblemFiles,
ProblemTestReplaceVal,
} from '@src/utils/problems';
import i18nJson from '@config/i18n.json';
import { validateMonacoModel } from '@src/utils/validate-monaco-model';
import styles from './index.module.less';

const enum MainTab {
cases = 'cases',
result = 'result',
}

function formatErrorFromMarkers(markers: editor.IMarker[]) {
return markers.map(function (maker) {
return `${maker.resource.path}:${maker.startLineNumber}:${
maker.startColumn
}: error: ${maker.code ? `TS${maker.code}: ` : ''}${maker.message}`;
});
}

const Results = function () {
const [{ currentProblem, setting }] = useContext(Context);
const [loading, setLoading] = useState(true);
Expand All @@ -24,14 +39,95 @@ const Results = function () {
const { key, cases: originCases = [] } = currentProblem;
const noCases = useMemo(() => originCases.length === 0, [originCases]);
const [cases, setCases] = useState(noCases ? [NULL_CASE] : originCases);
const [testRaw, setTestRaw] = useState<string | undefined>(undefined);
const [model, setModel] = useState<editor.ITextModel | undefined>(undefined);
const [casesErrors, setCasesErrors] = useState<string[][]>([]);

const updateData = useCallback(
debounce(async function (problem: Problem) {
const raw = await getProblemTestRaw(problem);
setTestRaw(raw);
setStatus([]);
setCasesErrors([]);
setCases(problem.cases || [NULL_CASE]);
setActiveMainTab(MainTab.cases);
setLoading(false);
}, 500),
[],
);

useEffect(
function () {
setLoading(true);
updateData(currentProblem);
},
[currentProblem],
);

function onSubmit() {
const markers = editor.getModelMarkers({});
const errors = markers.map(function (maker) {
return `${maker.resource.path}:${maker.startLineNumber}:${
maker.startColumn
}: error: ${maker.code ? `TS${maker.code}: ` : ''}${maker.message}`;
useEffect(
function () {
if (testRaw === undefined) {
setModel(undefined);
} else {
const uri = Uri.file(`${currentProblem.key}/${ProblemFiles.test}`);
const m =
editor.getModel(uri) || editor.createModel(testRaw, undefined, uri);
setModel(m);
}
return function () {
model?.dispose();
};
},
[testRaw],
);

async function run() {
setLoading(true);
setActiveMainTab(MainTab.result);
setStatus([]);
await new Promise(resolve => setTimeout(resolve, 500));
const templateUri = Uri.file(
`${currentProblem.key}/${ProblemFiles.template}`,
);
const markers = editor.getModelMarkers({
resource: templateUri,
});
const errors = formatErrorFromMarkers(markers);
if (errors.length > 0) {
setStatus(errors);
} else {
const e = [];
if (model && testRaw) {
for (const { source, target } of cases) {
const content = testRaw
.replace(ProblemTestReplaceVal.source, source)
.replace(ProblemTestReplaceVal.target, target);
model.setValue(content);
await validateMonacoModel(model);
const markers = editor.getModelMarkers({
resource: Uri.file(`${currentProblem.key}/${ProblemFiles.test}`),
});
const errors = formatErrorFromMarkers(markers);
e.push(errors);
}
setCasesErrors(e);
}
}
setLoading(false);
}

async function onSubmit() {
setLoading(true);
await new Promise(resolve => setTimeout(resolve, 500));
const markers = [
...editor.getModelMarkers({
resource: Uri.file(`${currentProblem.key}/${ProblemFiles.template}`),
}),
...editor.getModelMarkers({
resource: Uri.file(`${currentProblem.key}/${ProblemFiles.check}`),
}),
];
const errors = formatErrorFromMarkers(markers);
setStatus(errors.length > 0 ? errors : 'Accept!');
const status =
errors.length > 0 ? PROBLEM_STATUS.unAccepted : PROBLEM_STATUS.accepted;
Expand All @@ -48,23 +144,71 @@ const Results = function () {
});
emitter.emit('submit-code');
setActiveMainTab(MainTab.result);
setLoading(false);
}

const updateCases = useCallback(
debounce(function (cases: Problem['cases']) {
setCases(cases || [NULL_CASE]);
setLoading(false);
}, 500),
[],
);

useEffect(
const resultContent = useMemo(
function () {
setLoading(true);
setStatus([]);
updateCases(currentProblem.cases);
if (typeof status === 'string') {
return <div className={styles['result-accept']}>Accepted!</div>;
} else if (Array.isArray(status) && status.length > 0) {
return (
<div className={styles['result-errors']}>
<div className={styles['result-error-title']}>
Compilation Error
</div>
<div className={styles['result-error-info']}>
{status.map(function (error) {
return (
<div key={error} className={styles['result-error-item']}>
{error}
</div>
);
})}
</div>
</div>
);
} else if (casesErrors.length > 0) {
return (
<CustomTabs className={styles['case-tabs']}>
{cases.map(function (_, index) {
const result = casesErrors[index];
return (
<CustomTabs.TabPane
key={index}
title={`${i18nJson['case'][setting.language]} ${index + 1}`}
>
{result.length > 0 && (
<div className={styles['result-error-info']}>
{result.map(function (error) {
return (
<div
key={error}
className={styles['result-error-item']}
>
{error}
</div>
);
})}
</div>
)}
{result.length === 0 && (
<div className={styles['result-pass']}>Pass!</div>
)}
</CustomTabs.TabPane>
);
})}
</CustomTabs>
);
} else {
return (
<div className={styles['result-empty']}>
{i18nJson['please_run_or_submit_first'][setting.language]}
</div>
);
}
},
[currentProblem],
[cases, casesErrors, status],
);

return (
Expand Down Expand Up @@ -114,34 +258,11 @@ const Results = function () {
key={MainTab.result}
title={i18nJson[MainTab.result][setting.language]}
>
{Array.isArray(status) && status.length === 0 && (
<div className={styles['result-empty']}>
{i18nJson['please_run_or_submit_first'][setting.language]}
</div>
)}
{typeof status === 'string' && (
<div className={styles['result-accept']}>{'Accepted!'}</div>
)}
{Array.isArray(status) && status.length > 0 && (
<div className={styles['result-errors']}>
<div className={styles['result-error-title']}>
Compilation Error
</div>
<div className={styles['result-error-info']}>
{status.map(function (error) {
return (
<div key={error} className={styles['result-error-item']}>
{error}
</div>
);
})}
</div>
</div>
)}
{resultContent}
</CustomTabs.TabPane>
</CustomTabs>
<div className={styles.footer}>
<Button type={'primary'} className={styles.btn}>
<Button type={'primary'} className={styles.btn} onClick={run}>
{i18nJson['run_code'][setting.language]}
</Button>
<Button
Expand Down
3 changes: 2 additions & 1 deletion src/problems/difficulties/transpose-matrix/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import check from './check?url';
import template from './template?url';
import test from './test?url';
import * as description from './docs/description';
import * as tutorial from './docs/tutorial';

export { check, template, tutorial, description };
export { check, test, template, tutorial, description };
3 changes: 3 additions & 0 deletions src/problems/difficulties/transpose-matrix/test
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { TransposeMatrix } from './template';

export const _: TransposeMatrix<TEST_SOURCE_STRING> = TEST_TARGET_STRING;
2 changes: 1 addition & 1 deletion src/types/problems.d.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
declare module '@src/problems' {
import type { ProblemDocs } from '@src/utils/problems';
import type { Setting } from '@src/utils/setting';
declare type FilePrefixes = ['check', 'template'];
declare type FilePrefixes = ['check', 'template', 'test'];
const problemsUrl: {
[subjectKey: string]: {
[key: string]: {
Expand Down
Loading

0 comments on commit 4bfe8db

Please sign in to comment.