Files
ojnext/docs/superpowers/plans/2026-06-14-submit-formatting-button-state.md

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.