feat(frontend): enable problem-scoped Python and Java submissions
This commit is contained in:
@@ -1,7 +1,8 @@
|
|||||||
// No source is stored here. Fingerprints bind an immutable request and owner to a UUID.
|
// No source is stored here. Fingerprints bind an immutable request and owner to a UUID.
|
||||||
|
import type { components } from './generated/schema'
|
||||||
export type SubmissionInput = {
|
export type SubmissionInput = {
|
||||||
problem_version_id: string
|
problem_version_id: string
|
||||||
language: 'c17' | 'cpp20'
|
language: components['schemas']['kf_oj__contracts__judging_v2__Language']
|
||||||
source: string
|
source: string
|
||||||
}
|
}
|
||||||
type Intent = { fingerprint: string; key: string; submissionId?: string }
|
type Intent = { fingerprint: string; key: string; submissionId?: string }
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import 'monaco-editor/features/linesOperations/register'
|
|||||||
import 'monaco-editor/features/wordOperations/register'
|
import 'monaco-editor/features/wordOperations/register'
|
||||||
import 'monaco-editor/languages/definitions/cpp/register'
|
import 'monaco-editor/languages/definitions/cpp/register'
|
||||||
import 'monaco-editor/languages/definitions/python/register'
|
import 'monaco-editor/languages/definitions/python/register'
|
||||||
|
import 'monaco-editor/languages/definitions/java/register'
|
||||||
import 'monaco-editor/languages/definitions/rust/register'
|
import 'monaco-editor/languages/definitions/rust/register'
|
||||||
import EditorWorker from 'monaco-editor/editor/editor.worker?worker'
|
import EditorWorker from 'monaco-editor/editor/editor.worker?worker'
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import type { components } from '../api/generated/schema'
|
import type { components } from '../api/generated/schema'
|
||||||
|
import { preparingStatus, type WireLanguage } from '../composables/judgingLanguage'
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
state: components['schemas']['SubmissionSummary']['state']
|
state: components['schemas']['SubmissionSummary']['state']
|
||||||
verdict: components['schemas']['SubmissionSummary']['verdict']
|
verdict: components['schemas']['SubmissionSummary']['verdict']
|
||||||
|
language?: WireLanguage
|
||||||
}>()
|
}>()
|
||||||
const grades: Record<string, string> = {
|
const grades: Record<string, string> = {
|
||||||
accepted: 'AC',
|
accepted: 'AC',
|
||||||
@@ -27,7 +29,11 @@ const states: Record<string, string> = {
|
|||||||
finished: '评测完成',
|
finished: '评测完成',
|
||||||
}
|
}
|
||||||
const label = computed(() =>
|
const label = computed(() =>
|
||||||
props.verdict ? (grades[props.verdict] ?? props.verdict) : states[props.state],
|
props.verdict
|
||||||
|
? (grades[props.verdict] ?? props.verdict)
|
||||||
|
: props.state === 'compiling'
|
||||||
|
? preparingStatus(props.language)
|
||||||
|
: states[props.state],
|
||||||
)
|
)
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import type { components } from '../api/generated/schema'
|
||||||
|
import type { Language } from './useCodeDraft'
|
||||||
|
|
||||||
|
export type WireLanguage = components['schemas']['SubmissionSummary']['language']
|
||||||
|
type ProblemCapability = Pick<
|
||||||
|
components['schemas']['ProblemDetail'],
|
||||||
|
'languages' | 'language_configurations' | 'run_limits'
|
||||||
|
>
|
||||||
|
const wire: Record<Language, WireLanguage | null> = {
|
||||||
|
c: 'c17',
|
||||||
|
cpp: 'cpp20',
|
||||||
|
python: 'python313',
|
||||||
|
java: 'java21',
|
||||||
|
rust: null,
|
||||||
|
}
|
||||||
|
|
||||||
|
export function languageCapability(problem: ProblemCapability | null, language: Language) {
|
||||||
|
const id = wire[language]
|
||||||
|
if (!problem || !id || !problem.languages.includes(id)) return null
|
||||||
|
const configuration = problem.language_configurations.find((c) => c.language === id)
|
||||||
|
if (configuration) {
|
||||||
|
const validEntry =
|
||||||
|
id === 'python313'
|
||||||
|
? configuration.preparation_kind === 'syntax_check' &&
|
||||||
|
configuration.source_name === 'main.py' &&
|
||||||
|
configuration.entrypoint === 'main.py'
|
||||||
|
: id === 'java21'
|
||||||
|
? configuration.preparation_kind === 'compile' &&
|
||||||
|
configuration.source_name === 'Main.java' &&
|
||||||
|
configuration.entrypoint === 'Main'
|
||||||
|
: configuration.preparation_kind === 'compile'
|
||||||
|
if (!validEntry || !configuration.runtime_version) return null
|
||||||
|
return { wire: id, configuration, runLimits: configuration.run_limits }
|
||||||
|
}
|
||||||
|
// Only legacy C/C++ versions expose one shared budget. A managed language
|
||||||
|
// without its server-provided configuration must not inherit that budget.
|
||||||
|
if (
|
||||||
|
(id === 'c17' || id === 'cpp20') &&
|
||||||
|
problem.language_configurations.length === 0 &&
|
||||||
|
problem.run_limits
|
||||||
|
)
|
||||||
|
return { wire: id, configuration: null, runLimits: problem.run_limits }
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
export function preparationName(language?: WireLanguage) {
|
||||||
|
return language === 'python313' ? '语法检查' : '编译'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function preparingStatus(language?: WireLanguage) {
|
||||||
|
return language === 'python313' ? '正在检查语法' : '正在编译'
|
||||||
|
}
|
||||||
@@ -1,15 +1,17 @@
|
|||||||
import { computed, onScopeDispose, ref, watch } from 'vue'
|
import { computed, onScopeDispose, ref, watch } from 'vue'
|
||||||
export type Language = 'c' | 'cpp' | 'python' | 'rust'
|
export type Language = 'c' | 'cpp' | 'python' | 'java' | 'rust'
|
||||||
export const languageLabels: Record<Language, string> = {
|
export const languageLabels: Record<Language, string> = {
|
||||||
c: 'C17',
|
c: 'C17',
|
||||||
cpp: 'C++20',
|
cpp: 'C++20',
|
||||||
python: 'Python',
|
python: 'Python 3.13',
|
||||||
|
java: 'Java 21',
|
||||||
rust: 'Rust',
|
rust: 'Rust',
|
||||||
}
|
}
|
||||||
export const codeTemplates: Record<Language, string> = {
|
export const codeTemplates: Record<Language, string> = {
|
||||||
c: '#include <stdio.h>\n\nint main(void) {\n return 0;\n}\n',
|
c: '#include <stdio.h>\n\nint main(void) {\n return 0;\n}\n',
|
||||||
cpp: '#include <iostream>\nusing namespace std;\n\nint main() {\n // 在这里编写你的解法\n return 0;\n}\n',
|
cpp: '#include <iostream>\nusing namespace std;\n\nint main() {\n // 在这里编写你的解法\n return 0;\n}\n',
|
||||||
python: '# 在这里编写你的解法\n\n',
|
python: '# 在这里编写你的解法\n\n',
|
||||||
|
java: 'public class Main {\n public static void main(String[] args) throws Exception {\n // 在这里编写你的解法\n }\n}\n',
|
||||||
rust: 'use std::io::{self, Read};\n\nfn main() {\n let mut input = String::new();\n io::stdin().read_to_string(&mut input).unwrap();\n // 在这里编写你的解法\n}\n',
|
rust: 'use std::io::{self, Read};\n\nfn main() {\n let mut input = String::new();\n io::stdin().read_to_string(&mut input).unwrap();\n // 在这里编写你的解法\n}\n',
|
||||||
}
|
}
|
||||||
export function useCodeDraft(problemId: string) {
|
export function useCodeDraft(problemId: string) {
|
||||||
@@ -32,11 +34,11 @@ export function useCodeDraft(problemId: string) {
|
|||||||
typeof saved.drafts === 'object'
|
typeof saved.drafts === 'object'
|
||||||
) {
|
) {
|
||||||
const storedDrafts = saved.drafts as Record<string, unknown>
|
const storedDrafts = saved.drafts as Record<string, unknown>
|
||||||
for (const lang of ['c', 'cpp', 'python', 'rust'] as const) {
|
for (const lang of Object.keys(codeTemplates) as Language[]) {
|
||||||
const value = storedDrafts[lang]
|
const value = storedDrafts[lang]
|
||||||
if (typeof value === 'string') drafts.value[lang] = value
|
if (typeof value === 'string') drafts.value[lang] = value
|
||||||
}
|
}
|
||||||
if ('language' in saved && ['c', 'cpp', 'python', 'rust'].includes(String(saved.language)))
|
if ('language' in saved && Object.hasOwn(codeTemplates, String(saved.language)))
|
||||||
language.value = saved.language as Language
|
language.value = saved.language as Language
|
||||||
state.value = 'saved'
|
state.value = 'saved'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { submissionIntent, type SubmissionInput } from '../api/submissionIntent'
|
|||||||
import type { components } from '../api/generated/schema'
|
import type { components } from '../api/generated/schema'
|
||||||
import { useSession } from '../composables/useSession'
|
import { useSession } from '../composables/useSession'
|
||||||
import { useCodeDraft, codeTemplates, languageLabels } from '../composables/useCodeDraft'
|
import { useCodeDraft, codeTemplates, languageLabels } from '../composables/useCodeDraft'
|
||||||
|
import { languageCapability, preparationName } from '../composables/judgingLanguage'
|
||||||
import CodeEditor from '../components/CodeEditor.vue'
|
import CodeEditor from '../components/CodeEditor.vue'
|
||||||
import EmptyState from '../components/EmptyState.vue'
|
import EmptyState from '../components/EmptyState.vue'
|
||||||
import UiIcon from '../components/UiIcon.vue'
|
import UiIcon from '../components/UiIcon.vue'
|
||||||
@@ -31,17 +32,10 @@ const workspace = ref<HTMLDivElement>()
|
|||||||
const request = new AbortController()
|
const request = new AbortController()
|
||||||
let alive = true
|
let alive = true
|
||||||
const intents = new Map<string, ReturnType<typeof submissionIntent>>()
|
const intents = new Map<string, ReturnType<typeof submissionIntent>>()
|
||||||
const wireLanguage = computed(() =>
|
const capability = computed(() => languageCapability(problem.value, language.value))
|
||||||
language.value === 'c' ? 'c17' : language.value === 'cpp' ? 'cpp20' : null,
|
const wireLanguage = computed(() => capability.value?.wire ?? null)
|
||||||
)
|
const supported = computed(() => capability.value !== null)
|
||||||
const supported = computed(
|
const runLimits = computed(() => capability.value?.runLimits)
|
||||||
() => wireLanguage.value !== null && !!problem.value?.languages.includes(wireLanguage.value),
|
|
||||||
)
|
|
||||||
const runLimits = computed(
|
|
||||||
() =>
|
|
||||||
problem.value?.language_configurations?.find((c) => c.language === wireLanguage.value)
|
|
||||||
?.run_limits ?? problem.value?.run_limits,
|
|
||||||
)
|
|
||||||
const draftText = computed(
|
const draftText = computed(
|
||||||
() =>
|
() =>
|
||||||
({
|
({
|
||||||
@@ -102,7 +96,11 @@ function downloadCode() {
|
|||||||
const link = document.createElement('a')
|
const link = document.createElement('a')
|
||||||
link.href = url
|
link.href = url
|
||||||
link.download =
|
link.download =
|
||||||
'solution-' + id + '.' + { c: 'c', cpp: 'cpp', python: 'py', rust: 'rs' }[language.value]
|
language.value === 'java'
|
||||||
|
? 'Main.java'
|
||||||
|
: language.value === 'python'
|
||||||
|
? 'main.py'
|
||||||
|
: 'solution-' + id + '.' + { c: 'c', cpp: 'cpp', rust: 'rs' }[language.value]
|
||||||
link.click()
|
link.click()
|
||||||
setTimeout(() => URL.revokeObjectURL(url), 1000)
|
setTimeout(() => URL.revokeObjectURL(url), 1000)
|
||||||
}
|
}
|
||||||
@@ -289,7 +287,21 @@ onBeforeUnmount(() => {
|
|||||||
</div>
|
</div>
|
||||||
<div class="editor-testing">
|
<div class="editor-testing">
|
||||||
<p v-if="!supported" class="notice-strip">
|
<p v-if="!supported" class="notice-strip">
|
||||||
当前语言暂不支持提交,草稿仍会保存。可切换到题目支持的 C17 / C++20。
|
当前题目版本不支持此语言,草稿仍会保存。请选择题目公布的可用语言。
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
v-else-if="capability?.configuration"
|
||||||
|
class="notice-strip"
|
||||||
|
data-testid="language-capability"
|
||||||
|
>
|
||||||
|
{{ languageLabels[language] }} · {{ capability.configuration.runtime_version }} ·
|
||||||
|
{{ preparationName(capability.wire) }}后逐点运行。
|
||||||
|
<template v-if="capability.wire === 'java21'">
|
||||||
|
默认包 Main.java,入口为 public static void main(String[])。
|
||||||
|
</template>
|
||||||
|
<template v-else-if="capability.wire === 'python313'">
|
||||||
|
单文件 main.py,仅标准库。
|
||||||
|
</template>
|
||||||
</p>
|
</p>
|
||||||
<p v-if="submitError" role="alert">
|
<p v-if="submitError" role="alert">
|
||||||
{{ submitError }}
|
{{ submitError }}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import type { components } from '../api/generated/schema'
|
|||||||
import PageHeading from '../components/PageHeading.vue'
|
import PageHeading from '../components/PageHeading.vue'
|
||||||
import PanelCard from '../components/PanelCard.vue'
|
import PanelCard from '../components/PanelCard.vue'
|
||||||
import JudgementStatus from '../components/JudgementStatus.vue'
|
import JudgementStatus from '../components/JudgementStatus.vue'
|
||||||
|
import { preparationName } from '../composables/judgingLanguage'
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const id = String(route.params.id)
|
const id = String(route.params.id)
|
||||||
const submission = ref<components['schemas']['SubmissionDetail'] | null>(null)
|
const submission = ref<components['schemas']['SubmissionDetail'] | null>(null)
|
||||||
@@ -120,7 +121,11 @@ onBeforeUnmount(() => {
|
|||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<PageHeading :title="'提交 ' + id" description="个人提交的持久化评测状态"
|
<PageHeading :title="'提交 ' + id" description="个人提交的持久化评测状态"
|
||||||
><JudgementStatus v-if="submission" :state="submission.state" :verdict="submission.verdict"
|
><JudgementStatus
|
||||||
|
v-if="submission"
|
||||||
|
:state="submission.state"
|
||||||
|
:verdict="submission.verdict"
|
||||||
|
:language="submission.language"
|
||||||
/></PageHeading>
|
/></PageHeading>
|
||||||
<p v-if="error" role="alert">
|
<p v-if="error" role="alert">
|
||||||
{{ error }}
|
{{ error }}
|
||||||
@@ -169,13 +174,17 @@ onBeforeUnmount(() => {
|
|||||||
<PanelCard title="评测详情" icon="submissions">
|
<PanelCard title="评测详情" icon="submissions">
|
||||||
<p v-if="submission.failure_code" role="alert">系统原因:{{ submission.failure_code }}</p>
|
<p v-if="submission.failure_code" role="alert">系统原因:{{ submission.failure_code }}</p>
|
||||||
<template v-if="submission.result">
|
<template v-if="submission.result">
|
||||||
<p>编译状态:{{ submission.result.compilation_status }}</p>
|
<p>
|
||||||
|
{{ preparationName(submission.language) }}状态:{{ submission.result.compilation_status }}
|
||||||
|
</p>
|
||||||
<pre
|
<pre
|
||||||
v-if="submission.result.diagnostics"
|
v-if="submission.result.diagnostics"
|
||||||
class="sample-block"
|
class="sample-block"
|
||||||
data-testid="compiler-diagnostics"
|
data-testid="compiler-diagnostics"
|
||||||
>{{ submission.result.diagnostics }}</pre>
|
>{{ submission.result.diagnostics }}</pre>
|
||||||
<p v-if="submission.result.diagnostics_truncated">编译诊断已截断。</p>
|
<p v-if="submission.result.diagnostics_truncated">
|
||||||
|
{{ preparationName(submission.language) }}诊断已截断。
|
||||||
|
</p>
|
||||||
<p v-if="!submission.result.cleanup_complete" role="alert">
|
<p v-if="!submission.result.cleanup_complete" role="alert">
|
||||||
资源清理未确认,服务保留该任务并隔离容量。
|
资源清理未确认,服务保留该任务并隔离容量。
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -91,7 +91,10 @@ onBeforeUnmount(() => {
|
|||||||
label="状态"
|
label="状态"
|
||||||
min-width="130"
|
min-width="130"
|
||||||
><template #default="{ row }"
|
><template #default="{ row }"
|
||||||
><JudgementStatus :state="row.state" :verdict="row.verdict" /></template></el-table-column
|
><JudgementStatus
|
||||||
|
:state="row.state"
|
||||||
|
:verdict="row.verdict"
|
||||||
|
:language="row.language" /></template></el-table-column
|
||||||
><el-table-column prop="created_at" label="提交时间" min-width="180" />
|
><el-table-column prop="created_at" label="提交时间" min-width="180" />
|
||||||
</el-table>
|
</el-table>
|
||||||
<div class="pagination-row">
|
<div class="pagination-row">
|
||||||
|
|||||||
@@ -0,0 +1,105 @@
|
|||||||
|
import assert from 'node:assert/strict'
|
||||||
|
import { test } from 'node:test'
|
||||||
|
import type { components } from '../src/api/generated/schema'
|
||||||
|
import {
|
||||||
|
languageCapability,
|
||||||
|
preparationName,
|
||||||
|
preparingStatus,
|
||||||
|
} from '../src/composables/judgingLanguage.ts'
|
||||||
|
|
||||||
|
const limits: components['schemas']['RunLimits'] = {
|
||||||
|
cpu_time_us: 1_000_000,
|
||||||
|
wall_time_us: 3_000_000,
|
||||||
|
memory_bytes: 67_108_864,
|
||||||
|
file_bytes: 16_777_216,
|
||||||
|
open_files: 64,
|
||||||
|
processes: 8,
|
||||||
|
stack_bytes: 8_388_608,
|
||||||
|
stderr_bytes: 1_048_576,
|
||||||
|
stdout_bytes: 1_048_576,
|
||||||
|
workdir_bytes: 67_108_864,
|
||||||
|
}
|
||||||
|
const python: components['schemas']['PublicLanguageConfiguration'] = {
|
||||||
|
language: 'python313',
|
||||||
|
runtime_version: '3.13.16',
|
||||||
|
source_name: 'main.py',
|
||||||
|
entrypoint: 'main.py',
|
||||||
|
preparation_kind: 'syntax_check',
|
||||||
|
compile_limits: limits,
|
||||||
|
run_limits: { ...limits, cpu_time_us: 2_000_000 },
|
||||||
|
job_wall_time_us: 12_000_000,
|
||||||
|
}
|
||||||
|
const java: components['schemas']['PublicLanguageConfiguration'] = {
|
||||||
|
language: 'java21',
|
||||||
|
runtime_version: '21.0.12.1+1-LTS',
|
||||||
|
source_name: 'Main.java',
|
||||||
|
entrypoint: 'Main',
|
||||||
|
preparation_kind: 'compile',
|
||||||
|
compile_limits: limits,
|
||||||
|
run_limits: { ...limits, memory_bytes: 134_217_728 },
|
||||||
|
job_wall_time_us: 12_000_000,
|
||||||
|
}
|
||||||
|
type Problem = Parameters<typeof languageCapability>[0]
|
||||||
|
|
||||||
|
test('server capability controls managed submission and the selected version/budget; legacy C stays supported', () => {
|
||||||
|
const legacy: Problem = {
|
||||||
|
languages: ['c17', 'cpp20'],
|
||||||
|
language_configurations: [],
|
||||||
|
run_limits: limits,
|
||||||
|
}
|
||||||
|
assert.equal(languageCapability(legacy, 'c')?.wire, 'c17')
|
||||||
|
assert.equal(languageCapability(legacy, 'cpp')?.runLimits, limits)
|
||||||
|
assert.equal(languageCapability(legacy, 'python'), null)
|
||||||
|
assert.equal(languageCapability(legacy, 'java'), null)
|
||||||
|
assert.equal(languageCapability(legacy, 'rust'), null)
|
||||||
|
const current: Problem = {
|
||||||
|
languages: ['python313', 'java21'],
|
||||||
|
language_configurations: [python, java],
|
||||||
|
run_limits: null,
|
||||||
|
}
|
||||||
|
assert.equal(
|
||||||
|
languageCapability(current, 'python')?.configuration?.runtime_version,
|
||||||
|
python.runtime_version,
|
||||||
|
)
|
||||||
|
assert.equal(languageCapability(current, 'python')?.runLimits.cpu_time_us, 2_000_000)
|
||||||
|
assert.equal(languageCapability(current, 'java')?.runLimits.memory_bytes, 134_217_728)
|
||||||
|
assert.equal(languageCapability(current, 'java')?.configuration?.entrypoint, 'Main')
|
||||||
|
assert.equal(languageCapability(current, 'c'), null)
|
||||||
|
assert.equal(languageCapability(null, 'python'), null)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('managed language with absent or invalid fixed entry capability cannot inherit legacy budget', () => {
|
||||||
|
const missing: Problem = {
|
||||||
|
languages: ['python313', 'java21'],
|
||||||
|
language_configurations: [],
|
||||||
|
run_limits: limits,
|
||||||
|
}
|
||||||
|
assert.equal(languageCapability(missing, 'python'), null)
|
||||||
|
assert.equal(languageCapability(missing, 'java'), null)
|
||||||
|
for (const invalid of [
|
||||||
|
{ ...python, preparation_kind: 'compile' as const },
|
||||||
|
{ ...python, source_name: 'main.pyc' },
|
||||||
|
{ ...java, entrypoint: 'Other' },
|
||||||
|
{ ...java, source_name: 'package/Main.java' },
|
||||||
|
{ ...java, runtime_version: '' },
|
||||||
|
]) {
|
||||||
|
const problem: Problem = {
|
||||||
|
languages: [invalid.language],
|
||||||
|
language_configurations: [invalid],
|
||||||
|
run_limits: limits,
|
||||||
|
}
|
||||||
|
assert.equal(
|
||||||
|
languageCapability(problem, invalid.language === 'python313' ? 'python' : 'java'),
|
||||||
|
null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Python preparation is displayed as syntax checking while Java and old C/C++ compile', () => {
|
||||||
|
assert.equal(preparingStatus('python313'), '正在检查语法')
|
||||||
|
assert.equal(preparationName('python313'), '语法检查')
|
||||||
|
for (const language of ['c17', 'cpp20', 'java21'] as const) {
|
||||||
|
assert.equal(preparingStatus(language), '正在编译')
|
||||||
|
assert.equal(preparationName(language), '编译')
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -22,13 +22,45 @@ const flush = async () => {
|
|||||||
await new Promise<void>((resolve) => setImmediate(resolve))
|
await new Promise<void>((resolve) => setImmediate(resolve))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test('Python and Java retries preserve UUID and language/content changes create a new intent', async () => {
|
||||||
|
const storage = memory()
|
||||||
|
const keys: string[] = []
|
||||||
|
for (const language of ['python313', 'java21'] as const) {
|
||||||
|
const managed = { ...input, language }
|
||||||
|
await assert.rejects(
|
||||||
|
submissionIntent(storage, 'owner').submit(managed, async (key) => {
|
||||||
|
keys.push(key)
|
||||||
|
throw new Error('uncertain delivery')
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
const failedKey = keys.at(-1)
|
||||||
|
await submissionIntent(storage, 'owner').submit(managed, async (key) => {
|
||||||
|
assert.equal(key, failedKey)
|
||||||
|
return 'saved-' + language
|
||||||
|
})
|
||||||
|
}
|
||||||
|
assert.notEqual(keys[0], keys[1])
|
||||||
|
await submissionIntent(storage, 'owner').submit(
|
||||||
|
{ ...input, language: 'java21', source: 'changed Java' },
|
||||||
|
async (key) => {
|
||||||
|
assert.notEqual(key, keys[1])
|
||||||
|
return 'changed-id'
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
test('timeout and reload reuse persisted key; double click has one flight; content and owner change key', async () => {
|
test('timeout and reload reuse persisted key; double click has one flight; content and owner change key', async () => {
|
||||||
const storage = memory()
|
const storage = memory()
|
||||||
const keys: string[] = []
|
const keys: string[] = []
|
||||||
const intent = submissionIntent(storage, 'owner-a')
|
const intent = submissionIntent(storage, 'owner-a')
|
||||||
let respond: (id: string) => void = () => {}
|
let respond: (id: string) => void = () => {}
|
||||||
|
let sent: () => void = () => {}
|
||||||
|
const sending = new Promise<void>((resolve) => {
|
||||||
|
sent = resolve
|
||||||
|
})
|
||||||
const send = async (key: string) => {
|
const send = async (key: string) => {
|
||||||
keys.push(key)
|
keys.push(key)
|
||||||
|
sent()
|
||||||
return new Promise<string>((resolve) => {
|
return new Promise<string>((resolve) => {
|
||||||
respond = resolve
|
respond = resolve
|
||||||
})
|
})
|
||||||
@@ -36,7 +68,7 @@ test('timeout and reload reuse persisted key; double click has one flight; conte
|
|||||||
const a = intent.submit(input, send)
|
const a = intent.submit(input, send)
|
||||||
const b = intent.submit(input, send)
|
const b = intent.submit(input, send)
|
||||||
assert.equal(a, b)
|
assert.equal(a, b)
|
||||||
await flush()
|
await sending
|
||||||
assert.equal(keys.length, 1)
|
assert.equal(keys.length, 1)
|
||||||
respond('real-id')
|
respond('real-id')
|
||||||
assert.equal(await a, 'real-id')
|
assert.equal(await a, 'real-id')
|
||||||
|
|||||||
@@ -142,6 +142,7 @@ test('pagehide preserves the selected language and keeps problem drafts separate
|
|||||||
c: codeTemplates.c,
|
c: codeTemplates.c,
|
||||||
cpp: '// C++ draft',
|
cpp: '// C++ draft',
|
||||||
python: '# Python draft',
|
python: '# Python draft',
|
||||||
|
java: codeTemplates.java,
|
||||||
rust: codeTemplates.rust,
|
rust: codeTemplates.rust,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -190,3 +191,44 @@ test('old Python/Rust drafts survive adding C17; C and C++ retain separate conte
|
|||||||
assert.equal(env.read()?.drafts.python, '# original Python')
|
assert.equal(env.read()?.drafts.python, '# original Python')
|
||||||
assert.equal(env.read()?.drafts.rust, '// original Rust')
|
assert.equal(env.read()?.drafts.rust, '// original Rust')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('old drafts acquire Java without replacing their contents; four languages survive reload and version changes', (t) => {
|
||||||
|
const env = browser(t)
|
||||||
|
env.storage.items.set(
|
||||||
|
'kf-oj:code-draft:version-a',
|
||||||
|
JSON.stringify({
|
||||||
|
version: 1,
|
||||||
|
language: 'python',
|
||||||
|
drafts: { python: '# old Python', cpp: '// old C++', rust: '// old Rust' },
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
const draft = env.open('version-a')
|
||||||
|
assert.equal(draft.code.value, '# old Python')
|
||||||
|
draft.language.value = 'java'
|
||||||
|
assert.equal(draft.code.value, codeTemplates.java)
|
||||||
|
draft.code.value = '// Java draft'
|
||||||
|
draft.language.value = 'c'
|
||||||
|
draft.code.value = '// C draft'
|
||||||
|
draft.language.value = 'java'
|
||||||
|
env.page.dispatchEvent(new Event('pagehide'))
|
||||||
|
draft.scope.stop()
|
||||||
|
const restored = env.open('version-a')
|
||||||
|
assert.equal(restored.language.value, 'java')
|
||||||
|
assert.equal(restored.code.value, '// Java draft')
|
||||||
|
for (const [language, contents] of [
|
||||||
|
['c', '// C draft'],
|
||||||
|
['cpp', '// old C++'],
|
||||||
|
['python', '# old Python'],
|
||||||
|
['rust', '// old Rust'],
|
||||||
|
] as const) {
|
||||||
|
restored.language.value = language
|
||||||
|
assert.equal(restored.code.value, contents)
|
||||||
|
}
|
||||||
|
const other = env.open('version-b')
|
||||||
|
other.language.value = 'java'
|
||||||
|
assert.equal(other.code.value, codeTemplates.java)
|
||||||
|
other.code.value = '// other version Java'
|
||||||
|
other.save()
|
||||||
|
assert.equal(env.read('version-a')?.drafts.java, '// Java draft')
|
||||||
|
assert.equal(env.read('version-b')?.drafts.java, '// other version Java')
|
||||||
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user