6.4 KiB
Submit Formatting Button State Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Show 格式化中 on the submit button during automatic formatting, then show 正在提交 continuously while the submission request is pending.
Architecture: Extract the button presentation rules into a small pure TypeScript function so the state priority can be tested without adding a frontend test framework. Keep formatter and submission-request flags local to SubmitCode.vue, with finally blocks ensuring both flags clear on every outcome.
Tech Stack: Vue 3 Composition API, TypeScript, Node.js built-in test runner, Rsbuild
Task 1: Define and test submit button presentation rules
Files:
-
Create:
tests/submitButtonState.test.ts -
Create:
src/oj/problem/components/submitButtonState.ts -
Step 1: Write the failing test
Create tests/submitButtonState.test.ts:
import assert from "node:assert/strict"
import test from "node:test"
import { getSubmitButtonState } from "../src/oj/problem/components/submitButtonState.ts"
const idleInput = {
isAuthed: true,
hasCode: true,
isFormatting: false,
isSubmitting: false,
isJudging: false,
isCooldown: false,
}
test("shows a disabled loading state while formatting", () => {
assert.deepEqual(
getSubmitButtonState({ ...idleInput, isFormatting: true }),
{
disabled: true,
label: "格式化中",
icon: "eos-icons:loading",
},
)
})
test("shows submitting immediately after formatting", () => {
assert.deepEqual(
getSubmitButtonState({ ...idleInput, isSubmitting: true }),
{
disabled: true,
label: "正在提交",
icon: "eos-icons:loading",
},
)
})
test("preserves existing login, judging, cooldown, and idle states", () => {
assert.deepEqual(
getSubmitButtonState({ ...idleInput, isAuthed: false }),
{
disabled: true,
label: "请先登录",
icon: "ph:play-fill",
},
)
assert.deepEqual(getSubmitButtonState({ ...idleInput, isJudging: true }), {
disabled: true,
label: "正在评分",
icon: "eos-icons:loading",
})
assert.deepEqual(getSubmitButtonState({ ...idleInput, isCooldown: true }), {
disabled: true,
label: "正在冷却",
icon: "ph:lightbulb-fill",
})
assert.deepEqual(getSubmitButtonState(idleInput), {
disabled: false,
label: "提交代码",
icon: "ph:play-fill",
})
})
- Step 2: Run the test to verify it fails
Run:
node --test tests/submitButtonState.test.ts
Expected: FAIL with ERR_MODULE_NOT_FOUND for submitButtonState.ts.
- Step 3: Implement the pure state function
Create src/oj/problem/components/submitButtonState.ts:
export interface SubmitButtonStateInput {
isAuthed: boolean
hasCode: boolean
isFormatting: boolean
isSubmitting: boolean
isJudging: boolean
isCooldown: boolean
}
export interface SubmitButtonState {
disabled: boolean
label: string
icon: string
}
export function getSubmitButtonState({
isAuthed,
hasCode,
isFormatting,
isSubmitting,
isJudging,
isCooldown,
}: SubmitButtonStateInput): SubmitButtonState {
const disabled =
!isAuthed ||
!hasCode ||
isFormatting ||
isSubmitting ||
isJudging ||
isCooldown
let label = "提交代码"
if (!isAuthed) {
label = "请先登录"
} else if (isFormatting) {
label = "格式化中"
} else if (isSubmitting) {
label = "正在提交"
} else if (isJudging) {
label = "正在评分"
} else if (isCooldown) {
label = "正在冷却"
}
const icon =
isFormatting || isSubmitting || isJudging
? "eos-icons:loading"
: isCooldown
? "ph:lightbulb-fill"
: "ph:play-fill"
return { disabled, label, icon }
}
- Step 4: Run the test to verify it passes
Run:
node --test tests/submitButtonState.test.ts
Expected: 3 tests pass.
Task 2: Connect formatting and submission request lifecycle to the button
Files:
-
Modify:
src/oj/problem/components/SubmitCode.vue -
Step 1: Add local request states and computed presentation
Import getSubmitButtonState, add isFormatting and isSubmittingRequest refs, and replace the three existing button computed properties with:
const buttonState = computed(() =>
getSubmitButtonState({
isAuthed: userStore.isAuthed,
hasCode: codeStore.code.value.trim() !== "",
isFormatting: isFormatting.value,
isSubmitting: isSubmittingRequest.value || submitting.value,
isJudging: judging.value || pending.value,
isCooldown: isCooldown.value,
}),
)
Use buttonState.disabled, buttonState.icon, and buttonState.label in the template.
- Step 2: Guard and track the formatting request
At the start of submit, return when buttonState.value.disabled is true. Around formatCode, set isFormatting.value = true before the request and clear it in finally:
isFormatting.value = true
try {
const res = await formatCode({
code: codeStore.code.value,
language: formatLang,
})
codeStore.setCode(res.data.code)
} catch (e: any) {
if (e?.error === "format-error") {
message.warning(`代码格式化失败:${e.data},请检查代码后重试`)
return
}
} finally {
isFormatting.value = false
}
- Step 3: Track the submission API request
Set isSubmittingRequest.value = true immediately before submitCode, keep the existing success flow inside the try, and clear the request state in finally:
isSubmittingRequest.value = true
try {
const res = await submitCode(data)
console.log(`[Submit] 代码已提交: ID=${res.data.submission_id}`)
startCooldown()
startMonitoring(res.data.submission_id)
showResult.value = true
} finally {
isSubmittingRequest.value = false
}
- Step 4: Run focused tests
Run:
node --test tests/submitButtonState.test.ts
Expected: 3 tests pass.
- Step 5: Run the production build
Run:
npm run build
Expected: Rsbuild exits with status 0.
- Step 6: Check the final diff
Run:
git diff --check
git diff -- src/oj/problem/components/SubmitCode.vue src/oj/problem/components/submitButtonState.ts tests/submitButtonState.test.ts
Expected: no whitespace errors; diff is limited to the button state feature and its test.