From 7f29d334f26229753e68d20a5aaab33c39de9f06 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Sun, 1 Mar 2026 12:21:21 +0900 Subject: feat(frontend): show submission history on play screen Replace the placeholder submission status section with a full submission history table using the existing getGamePlaySubmissions API. Extract shared DataTable, DataTableCell, and formatUnixTimestamp from RankingTable into a reusable Gaming/DataTable component. Co-Authored-By: Claude Opus 4.6 --- .../GolfPlayApps/GolfPlayAppGaming.test.tsx | 107 +++++++++++++++++++++ .../components/GolfPlayApps/GolfPlayAppGaming.tsx | 62 +++++++----- 2 files changed, 143 insertions(+), 26 deletions(-) create mode 100644 frontend/app/components/GolfPlayApps/GolfPlayAppGaming.test.tsx (limited to 'frontend/app/components/GolfPlayApps') diff --git a/frontend/app/components/GolfPlayApps/GolfPlayAppGaming.test.tsx b/frontend/app/components/GolfPlayApps/GolfPlayAppGaming.test.tsx new file mode 100644 index 0000000..2d51d66 --- /dev/null +++ b/frontend/app/components/GolfPlayApps/GolfPlayAppGaming.test.tsx @@ -0,0 +1,107 @@ +/** + * @vitest-environment jsdom + */ +import { cleanup, render, screen } from "@testing-library/react"; +import { createStore, Provider } from "jotai"; +import { afterEach, describe, expect, test } from "vitest"; +import { + setCurrentTimestampAtom, + setDurationSecondsAtom, + setGameStartedAtAtom, + setLatestGameStateAtom, +} from "../../states/play"; +import GolfPlayAppGaming from "./GolfPlayAppGaming"; + +afterEach(() => { + cleanup(); +}); + +function createTestStore() { + const store = createStore(); + const now = Math.floor(Date.now() / 1000); + store.set(setCurrentTimestampAtom); + store.set(setDurationSecondsAtom, 600); + store.set(setGameStartedAtAtom, now - 60); + store.set(setLatestGameStateAtom, { + status: "none", + code: "", + score: null, + best_score_submitted_at: null, + }); + return store; +} + +const defaultProps = { + gameDisplayName: "Test Game", + playerProfile: { + id: 1, + displayName: "Test Player", + iconPath: null, + }, + problemTitle: "Test Problem", + problemDescription: "Description", + problemLanguage: "php" as const, + sampleCode: " {}, + onCodeSubmit: () => {}, + isFinished: false, +}; + +describe("GolfPlayAppGaming submission history", () => { + test("shows placeholder row when no submissions", () => { + const store = createTestStore(); + render( + + + , + ); + expect(screen.getByText("提出待ち")).toBeDefined(); + const dashes = screen.getAllByText("-"); + expect(dashes.length).toBe(3); + }); + + test("renders submission rows with status and code size", () => { + const store = createTestStore(); + const submissions = [ + { + submission_id: 1, + game_id: 1, + status: "success" as const, + code: " + + , + ); + expect(screen.getByText("成功")).toBeDefined(); + expect(screen.getByText("テスト失敗")).toBeDefined(); + expect(screen.getByText("7")).toBeDefined(); + expect(screen.getByText("10")).toBeDefined(); + }); + + test("renders table headers", () => { + const store = createTestStore(); + render( + + + , + ); + expect(screen.getByText("ステータス")).toBeDefined(); + expect(screen.getByText("スコア")).toBeDefined(); + expect(screen.getByText("提出時刻")).toBeDefined(); + expect(screen.getByText("コード")).toBeDefined(); + }); +}); diff --git a/frontend/app/components/GolfPlayApps/GolfPlayAppGaming.tsx b/frontend/app/components/GolfPlayApps/GolfPlayAppGaming.tsx index fa9a2b4..e590df0 100644 --- a/frontend/app/components/GolfPlayApps/GolfPlayAppGaming.tsx +++ b/frontend/app/components/GolfPlayApps/GolfPlayAppGaming.tsx @@ -1,6 +1,7 @@ import { useAtomValue } from "jotai"; import React, { useRef, useState } from "react"; import { Link } from "wouter"; +import type { components } from "../../api/schema"; import { calcCodeSize, gamingLeftTimeSecondsAtom, @@ -10,6 +11,11 @@ import { import type { PlayerProfile } from "../../types/PlayerProfile"; import type { SupportedLanguage } from "../../types/SupportedLanguage"; import BorderedContainer from "../BorderedContainer"; +import CodePopover from "../Gaming/CodePopover"; +import DataTable, { + DataTableCell, + formatUnixTimestamp, +} from "../Gaming/DataTable"; import LeftTime from "../Gaming/LeftTime"; import ProblemColumn from "../Gaming/ProblemColumn"; import SubmitButton from "../SubmitButton"; @@ -18,6 +24,8 @@ import ThreeColumnLayout from "../ThreeColumnLayout"; import TitledColumn from "../TitledColumn"; import UserIcon from "../UserIcon"; +type Submission = components["schemas"]["Submission"]; + type Props = { gameDisplayName: string; playerProfile: PlayerProfile; @@ -29,6 +37,7 @@ type Props = { onCodeChange: (code: string) => void; onCodeSubmit: (code: string) => void; isFinished: boolean; + submissions: Submission[]; }; export default function GolfPlayAppGaming({ @@ -42,6 +51,7 @@ export default function GolfPlayAppGaming({ onCodeChange, onCodeSubmit, isFinished, + submissions, }: Props) { const leftTimeSeconds = useAtomValue(gamingLeftTimeSecondsAtom); const score = useAtomValue(scoreAtom); @@ -124,33 +134,33 @@ export default function GolfPlayAppGaming({ -
- - - - + + {submissions.length === 0 ? ( + + + + + - + - + - + + ) : ( + submissions.map((s) => ( + + + + + {s.code_size} + + {formatUnixTimestamp(s.created_at)} + + + + - - - {[status].map((status) => ( - - - - ))} - -
- ステータス -
- -
-
-

- NOTE: - 過去の提出結果を閲覧する機能は現在実装中です。それまでは提出コードをお手元に保管しておいてください。 -

+ )) + )} +
-- cgit v1.3.1