merge: 토너먼트 단계별 노출을 main에 통합한다
This commit is contained in:
@@ -126,7 +126,15 @@ const persistScreenshot = async (page: Page, name: string, fallbackPath: string)
|
||||
await page.screenshot({ path: resolve(responsiveArtifactDir, `${name}.webp`), fullPage: true });
|
||||
};
|
||||
|
||||
const installFixture = async (page: Page, options: { applicationOpen?: boolean; tournamentType?: number } = {}) => {
|
||||
const installFixture = async (
|
||||
page: Page,
|
||||
options: {
|
||||
applicationOpen?: boolean;
|
||||
tournamentType?: number;
|
||||
tournamentStage?: number;
|
||||
joinedGroupId?: number;
|
||||
} = {}
|
||||
) => {
|
||||
let joined = false;
|
||||
await page.addInitScript((profile) => {
|
||||
window.localStorage.setItem('sammo-game-token', 'ga_tournament_bracket_playwright');
|
||||
@@ -148,9 +156,11 @@ const installFixture = async (page: Page, options: { applicationOpen?: boolean;
|
||||
if (operation === 'general.me') return response({ general: { id: 1, name: names[0] } });
|
||||
if (operation === 'tournament.getAdminStatus') return response({ ok: false });
|
||||
if (operation === 'tournament.getSnapshot') {
|
||||
const tournamentStage = options.tournamentStage ?? (options.applicationOpen ? 1 : 0);
|
||||
const joinedGroupId = options.joinedGroupId ?? 0;
|
||||
return response({
|
||||
state: {
|
||||
stage: options.applicationOpen ? 1 : 0,
|
||||
stage: tournamentStage,
|
||||
phase: 0,
|
||||
type: options.tournamentType ?? 0,
|
||||
auto: false,
|
||||
@@ -158,7 +168,7 @@ const installFixture = async (page: Page, options: { applicationOpen?: boolean;
|
||||
openMonth: 1,
|
||||
termSeconds: 60,
|
||||
nextAt: '2026-08-02T00:00:00.000Z',
|
||||
winnerId: 1,
|
||||
winnerId: tournamentStage === 0 ? 1 : undefined,
|
||||
},
|
||||
participants:
|
||||
options.applicationOpen && !joined
|
||||
@@ -167,8 +177,10 @@ const installFixture = async (page: Page, options: { applicationOpen?: boolean;
|
||||
? [
|
||||
{
|
||||
...participants[0],
|
||||
groupId: 0,
|
||||
groupId: joinedGroupId,
|
||||
groupNo: 0,
|
||||
preliminaryGroupId: joinedGroupId,
|
||||
preliminaryGroupNo: 0,
|
||||
win: 0,
|
||||
draw: 0,
|
||||
lose: 0,
|
||||
@@ -322,20 +334,36 @@ test('desktop bracket connects every real general slot to the next round', async
|
||||
await persistScreenshot(page, 'tournament-desktop', testInfo.outputPath('tournament-bracket-desktop.webp'));
|
||||
});
|
||||
|
||||
test('join refresh shows the assigned preliminary group immediately with accessible controls', async ({ page }) => {
|
||||
test('join refresh shows the assigned preliminary group immediately with accessible controls', async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
await page.setViewportSize({ width: 390, height: 844 });
|
||||
await installFixture(page, { applicationOpen: true });
|
||||
await installFixture(page, { applicationOpen: true, joinedGroupId: 5 });
|
||||
await page.goto('tournament');
|
||||
|
||||
const refresh = page.getByRole('button', { name: '갱신' });
|
||||
const join = page.getByRole('button', { name: '참가' });
|
||||
const close = page.getByRole('button', { name: '창 닫기' }).first();
|
||||
await expect(join).toBeEnabled();
|
||||
await expect(page.getByText('조별 예선 순위')).toBeVisible();
|
||||
await expect(page.getByText('조별 본선 순위')).toHaveCount(0);
|
||||
await expect(page.getByLabel('토너먼트 대진표')).toHaveCount(0);
|
||||
await join.click();
|
||||
|
||||
await expect(page.getByRole('status')).toHaveText('참가 신청이 반영되었습니다.');
|
||||
await expect(page.getByRole('status')).toHaveText('참가 신청이 반영되었습니다. 六조에 배정되었습니다.');
|
||||
await expect(join).toBeDisabled();
|
||||
await expect(page.locator('.preliminary-grid .general-identity', { hasText: names[0] })).toBeVisible();
|
||||
const preliminaryTabs = page.getByRole('tablist', { name: '예선 조 선택' });
|
||||
await expect(preliminaryTabs.getByRole('tab').nth(5)).toHaveAttribute('aria-selected', 'true');
|
||||
const assignedGroup = page.locator('[data-preliminary-group="5"]');
|
||||
await expect(assignedGroup.locator('.general-identity', { hasText: names[0] })).toBeVisible();
|
||||
const assignedGroupBounds = await assignedGroup.boundingBox();
|
||||
expect(assignedGroupBounds?.y).toBeLessThan(844);
|
||||
expect((assignedGroupBounds?.y ?? 0) + (assignedGroupBounds?.height ?? 0)).toBeGreaterThan(0);
|
||||
await persistScreenshot(
|
||||
page,
|
||||
'tournament-joined-group-mobile',
|
||||
testInfo.outputPath('tournament-joined-group.webp')
|
||||
);
|
||||
|
||||
for (const control of [refresh, join, close]) {
|
||||
const box = await control.boundingBox();
|
||||
@@ -349,6 +377,41 @@ test('join refresh shows the assigned preliminary group immediately with accessi
|
||||
expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual(390);
|
||||
});
|
||||
|
||||
test('desktop join scrolls the assigned preliminary group into view without future sections', async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
await page.setViewportSize({ width: 1365, height: 900 });
|
||||
await installFixture(page, { applicationOpen: true, joinedGroupId: 7 });
|
||||
await page.goto('tournament');
|
||||
|
||||
await expect(page.getByText('조별 본선 순위')).toHaveCount(0);
|
||||
await expect(page.getByLabel('토너먼트 대진표')).toHaveCount(0);
|
||||
await page.getByRole('button', { name: '참가' }).click();
|
||||
|
||||
await expect(page.getByRole('status')).toHaveText('참가 신청이 반영되었습니다. 八조에 배정되었습니다.');
|
||||
const assignedGroup = page.locator('[data-preliminary-group="7"]');
|
||||
await expect(assignedGroup.locator('.general-identity', { hasText: names[0] })).toBeVisible();
|
||||
const bounds = await assignedGroup.boundingBox();
|
||||
expect(bounds?.y).toBeLessThan(900);
|
||||
expect((bounds?.y ?? 0) + (bounds?.height ?? 0)).toBeGreaterThan(0);
|
||||
await persistScreenshot(
|
||||
page,
|
||||
'tournament-joined-group-desktop',
|
||||
testInfo.outputPath('tournament-joined-group.webp')
|
||||
);
|
||||
});
|
||||
|
||||
test('final group section appears before the later knockout section', async ({ page }, testInfo) => {
|
||||
await page.setViewportSize({ width: 390, height: 844 });
|
||||
await installFixture(page, { tournamentStage: 3 });
|
||||
await page.goto('tournament');
|
||||
|
||||
await expect(page.getByText('조별 예선 순위')).toBeVisible();
|
||||
await expect(page.getByText('조별 본선 순위')).toBeVisible();
|
||||
await expect(page.getByLabel('토너먼트 대진표')).toHaveCount(0);
|
||||
await persistScreenshot(page, 'tournament-final-stage-mobile', testInfo.outputPath('tournament-final-stage.webp'));
|
||||
});
|
||||
|
||||
test('mobile bracket exposes every round through tabs with standard horizontal identities', async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
|
||||
@@ -13,3 +13,18 @@ export const tournamentStageNames = [
|
||||
] as const;
|
||||
|
||||
export const resolveTournamentStageName = (stage: number): string => tournamentStageNames[stage] ?? '상태 확인 중';
|
||||
|
||||
export interface TournamentSectionVisibility {
|
||||
preliminary: boolean;
|
||||
final: boolean;
|
||||
knockout: boolean;
|
||||
}
|
||||
|
||||
export const resolveTournamentSectionVisibility = (stage: number, winnerId?: number): TournamentSectionVisibility => {
|
||||
const completed = stage === 0 && winnerId !== undefined;
|
||||
return {
|
||||
preliminary: stage >= 1 || completed,
|
||||
final: stage >= 3 || completed,
|
||||
knockout: stage >= 5 || completed,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { formatServerDateTime } from '@sammo-ts/common/time/ServerDateTime';
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import { computed, nextTick, onMounted, ref } from 'vue';
|
||||
import TournamentBracket from '../components/tournament/TournamentBracket.vue';
|
||||
import TournamentPageHeader from '../components/tournament/TournamentPageHeader.vue';
|
||||
import GeneralIdentity from '../components/ui/GeneralIdentity.vue';
|
||||
import { trpc } from '../utils/trpc';
|
||||
import { resolveTournamentStageName } from '../utils/tournamentStatus';
|
||||
import { resolveTournamentSectionVisibility, resolveTournamentStageName } from '../utils/tournamentStatus';
|
||||
|
||||
type Snapshot = Awaited<ReturnType<typeof trpc.tournament.getSnapshot.query>>;
|
||||
|
||||
@@ -18,6 +18,7 @@ const actionMessage = ref<string | null>(null);
|
||||
const adminEnabled = ref(false);
|
||||
const activeFinalGroup = ref(0);
|
||||
const activePreliminaryGroup = ref(0);
|
||||
const tournamentContainer = ref<HTMLElement | null>(null);
|
||||
|
||||
const typeNames = ['전력전', '통솔전', '일기토', '설전'];
|
||||
const typeStatNames = ['종합', '통솔', '무력', '지력'];
|
||||
@@ -63,6 +64,12 @@ const myBetTotals = computed(() => betting.value?.myTotals as Record<number, num
|
||||
const isParticipant = computed(() =>
|
||||
(snapshot.value?.participants ?? []).some((participant) => participant.id === myGeneralId.value)
|
||||
);
|
||||
const sectionVisibility = computed(() =>
|
||||
resolveTournamentSectionVisibility(snapshot.value?.state?.stage ?? 0, snapshot.value?.state?.winnerId)
|
||||
);
|
||||
const preliminaryGroupIdOf = (participant: Snapshot['participants'][number]): number | undefined =>
|
||||
participant.preliminaryGroupId ??
|
||||
(participant.groupId !== undefined && participant.groupId < 8 ? participant.groupId : undefined);
|
||||
const groups = computed(() =>
|
||||
Array.from({ length: 8 }, (_, index) =>
|
||||
(snapshot.value?.participants ?? [])
|
||||
@@ -74,10 +81,7 @@ const preliminaryGroups = computed(() =>
|
||||
Array.from({ length: 8 }, (_, index) =>
|
||||
(snapshot.value?.participants ?? [])
|
||||
.filter((participant) => {
|
||||
const groupId =
|
||||
participant.preliminaryGroupId ??
|
||||
(participant.groupId !== undefined && participant.groupId < 8 ? participant.groupId : undefined);
|
||||
return groupId === index;
|
||||
return preliminaryGroupIdOf(participant) === index;
|
||||
})
|
||||
.map((participant) => ({
|
||||
...participant,
|
||||
@@ -113,15 +117,38 @@ const currentMatch = computed(() => {
|
||||
return matchesAt(state.stage).find((match) => !match.winnerId) ?? matchesAt(state.stage)[state.phase] ?? null;
|
||||
});
|
||||
|
||||
const revealMyPreliminaryGroup = async (): Promise<number | undefined> => {
|
||||
const participant = (snapshot.value?.participants ?? []).find((entry) => entry.id === myGeneralId.value);
|
||||
if (!participant) return undefined;
|
||||
|
||||
const groupId = preliminaryGroupIdOf(participant);
|
||||
if (groupId === undefined || groupId < 0 || groupId >= groupNames.length) return undefined;
|
||||
|
||||
activePreliminaryGroup.value = groupId;
|
||||
await nextTick();
|
||||
tournamentContainer.value
|
||||
?.querySelector<HTMLElement>(`[data-preliminary-group="${groupId}"]`)
|
||||
?.scrollIntoView({ block: 'center' });
|
||||
return groupId;
|
||||
};
|
||||
|
||||
const join = async () => {
|
||||
actionMessage.value = null;
|
||||
let joined = false;
|
||||
try {
|
||||
await trpc.tournament.join.mutate();
|
||||
actionMessage.value = '참가 신청이 반영되었습니다.';
|
||||
joined = true;
|
||||
} catch (value) {
|
||||
actionMessage.value = errorText(value);
|
||||
} finally {
|
||||
await load();
|
||||
if (joined) {
|
||||
const groupId = await revealMyPreliminaryGroup();
|
||||
actionMessage.value =
|
||||
groupId === undefined
|
||||
? '참가 신청이 반영되었습니다.'
|
||||
: `참가 신청이 반영되었습니다. ${groupNames[groupId]}조에 배정되었습니다.`;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -159,7 +186,7 @@ const start = async () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main id="tournament-container" class="legacy-page">
|
||||
<main id="tournament-container" ref="tournamentContainer" class="legacy-page">
|
||||
<TournamentPageHeader class="bg0" active-page="tournament" title="삼모전 토너먼트" />
|
||||
|
||||
<section class="toolbar bg0">
|
||||
@@ -183,145 +210,152 @@ const start = async () => {
|
||||
({{ resolveTournamentStageName(snapshot?.state?.stage ?? 0) }}, 개막시간 {{ openingTime }}, 경기당
|
||||
{{ snapshot?.state?.termSeconds ?? '-' }}초)
|
||||
</section>
|
||||
<section class="section-title bg2">16강 승자전</section>
|
||||
<template v-if="sectionVisibility.knockout">
|
||||
<section class="section-title bg2">16강 승자전</section>
|
||||
|
||||
<TournamentBracket
|
||||
class="bg0"
|
||||
:participants="snapshot?.participants ?? []"
|
||||
:matches="snapshot?.matches ?? []"
|
||||
:winner-id="snapshot?.state?.winnerId"
|
||||
:bet-totals="betTotals"
|
||||
:my-bet-totals="myBetTotals"
|
||||
:total-bet="totalBet"
|
||||
:tournament-type="snapshot?.state?.type ?? 0"
|
||||
/>
|
||||
<TournamentBracket
|
||||
class="bg0"
|
||||
:participants="snapshot?.participants ?? []"
|
||||
:matches="snapshot?.matches ?? []"
|
||||
:winner-id="snapshot?.state?.winnerId"
|
||||
:bet-totals="betTotals"
|
||||
:my-bet-totals="myBetTotals"
|
||||
:total-bet="totalBet"
|
||||
:tournament-type="snapshot?.state?.type ?? 0"
|
||||
/>
|
||||
|
||||
<section v-if="currentMatch" class="fight bg0">
|
||||
<h2>{{ nameOf(currentMatch.attackerId) }} vs {{ nameOf(currentMatch.defenderId) }}</h2>
|
||||
<p v-for="(line, index) in currentMatch.log ?? []" :key="index">{{ line }}</p>
|
||||
</section>
|
||||
<section v-if="currentMatch" class="fight bg0">
|
||||
<h2>{{ nameOf(currentMatch.attackerId) }} vs {{ nameOf(currentMatch.defenderId) }}</h2>
|
||||
<p v-for="(line, index) in currentMatch.log ?? []" :key="index">{{ line }}</p>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<section class="section-title groups-title bg2">조별 본선 순위</section>
|
||||
<div class="group-tabs bg0" role="tablist" aria-label="본선 조 선택">
|
||||
<button
|
||||
v-for="(groupName, groupIndex) in groupNames"
|
||||
:key="`final-tab-${groupName}`"
|
||||
type="button"
|
||||
role="tab"
|
||||
:aria-selected="activeFinalGroup === groupIndex"
|
||||
:class="{ active: activeFinalGroup === groupIndex }"
|
||||
@click="activeFinalGroup = groupIndex"
|
||||
>
|
||||
{{ groupName }}조
|
||||
</button>
|
||||
</div>
|
||||
<section class="group-grid bg0">
|
||||
<table
|
||||
v-for="(group, groupIndex) in groups"
|
||||
:key="groupIndex"
|
||||
:class="{ 'mobile-active': activeFinalGroup === groupIndex }"
|
||||
>
|
||||
<caption>
|
||||
{{
|
||||
groupNames[groupIndex]
|
||||
}}조
|
||||
</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>순</th>
|
||||
<th>장수</th>
|
||||
<th>{{ typeStatNames[snapshot?.state?.type ?? 0] }}</th>
|
||||
<th>경</th>
|
||||
<th>승</th>
|
||||
<th>무</th>
|
||||
<th>패</th>
|
||||
<th>점</th>
|
||||
<th>득</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="rowIndex in 4" :key="rowIndex">
|
||||
<td>{{ rowIndex }}</td>
|
||||
<td class="general-cell">
|
||||
<GeneralIdentity
|
||||
v-if="group[rowIndex - 1]"
|
||||
:name="group[rowIndex - 1]!.name"
|
||||
:picture="group[rowIndex - 1]!.picture"
|
||||
:image-server="group[rowIndex - 1]!.imageServer"
|
||||
/>
|
||||
</td>
|
||||
<td>{{ statOf(group[rowIndex - 1]) }}</td>
|
||||
<td>{{ gamesOf(group[rowIndex - 1]) }}</td>
|
||||
<td>{{ group[rowIndex - 1]?.win ?? '' }}</td>
|
||||
<td>{{ group[rowIndex - 1]?.draw ?? '' }}</td>
|
||||
<td>{{ group[rowIndex - 1]?.lose ?? '' }}</td>
|
||||
<td>{{ pointsOf(group[rowIndex - 1]) }}</td>
|
||||
<td>{{ group[rowIndex - 1]?.gl ?? '' }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
<template v-if="sectionVisibility.final">
|
||||
<section class="section-title groups-title bg2">조별 본선 순위</section>
|
||||
<div class="group-tabs bg0" role="tablist" aria-label="본선 조 선택">
|
||||
<button
|
||||
v-for="(groupName, groupIndex) in groupNames"
|
||||
:key="`final-tab-${groupName}`"
|
||||
type="button"
|
||||
role="tab"
|
||||
:aria-selected="activeFinalGroup === groupIndex"
|
||||
:class="{ active: activeFinalGroup === groupIndex }"
|
||||
@click="activeFinalGroup = groupIndex"
|
||||
>
|
||||
{{ groupName }}조
|
||||
</button>
|
||||
</div>
|
||||
<section class="group-grid bg0">
|
||||
<table
|
||||
v-for="(group, groupIndex) in groups"
|
||||
:key="groupIndex"
|
||||
:class="{ 'mobile-active': activeFinalGroup === groupIndex }"
|
||||
>
|
||||
<caption>
|
||||
{{
|
||||
groupNames[groupIndex]
|
||||
}}조
|
||||
</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>순</th>
|
||||
<th>장수</th>
|
||||
<th>{{ typeStatNames[snapshot?.state?.type ?? 0] }}</th>
|
||||
<th>경</th>
|
||||
<th>승</th>
|
||||
<th>무</th>
|
||||
<th>패</th>
|
||||
<th>점</th>
|
||||
<th>득</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="rowIndex in 4" :key="rowIndex">
|
||||
<td>{{ rowIndex }}</td>
|
||||
<td class="general-cell">
|
||||
<GeneralIdentity
|
||||
v-if="group[rowIndex - 1]"
|
||||
:name="group[rowIndex - 1]!.name"
|
||||
:picture="group[rowIndex - 1]!.picture"
|
||||
:image-server="group[rowIndex - 1]!.imageServer"
|
||||
/>
|
||||
</td>
|
||||
<td>{{ statOf(group[rowIndex - 1]) }}</td>
|
||||
<td>{{ gamesOf(group[rowIndex - 1]) }}</td>
|
||||
<td>{{ group[rowIndex - 1]?.win ?? '' }}</td>
|
||||
<td>{{ group[rowIndex - 1]?.draw ?? '' }}</td>
|
||||
<td>{{ group[rowIndex - 1]?.lose ?? '' }}</td>
|
||||
<td>{{ pointsOf(group[rowIndex - 1]) }}</td>
|
||||
<td>{{ group[rowIndex - 1]?.gl ?? '' }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<section class="section-title groups-title bg2">조별 예선 순위</section>
|
||||
<div class="group-tabs bg0" role="tablist" aria-label="예선 조 선택">
|
||||
<button
|
||||
v-for="(groupName, groupIndex) in groupNames"
|
||||
:key="`preliminary-tab-${groupName}`"
|
||||
type="button"
|
||||
role="tab"
|
||||
:aria-selected="activePreliminaryGroup === groupIndex"
|
||||
:class="{ active: activePreliminaryGroup === groupIndex }"
|
||||
@click="activePreliminaryGroup = groupIndex"
|
||||
>
|
||||
{{ groupName }}조
|
||||
</button>
|
||||
</div>
|
||||
<section class="group-grid preliminary-grid bg0">
|
||||
<table
|
||||
v-for="(group, groupIndex) in preliminaryGroups"
|
||||
:key="`preliminary-${groupIndex}`"
|
||||
:class="{ 'mobile-active': activePreliminaryGroup === groupIndex }"
|
||||
>
|
||||
<caption>
|
||||
{{
|
||||
groupNames[groupIndex]
|
||||
}}조
|
||||
</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>순</th>
|
||||
<th>장수</th>
|
||||
<th>{{ typeStatNames[snapshot?.state?.type ?? 0] }}</th>
|
||||
<th>경</th>
|
||||
<th>승</th>
|
||||
<th>무</th>
|
||||
<th>패</th>
|
||||
<th>점</th>
|
||||
<th>득</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="rowIndex in 8" :key="rowIndex">
|
||||
<td>{{ rowIndex }}</td>
|
||||
<td class="general-cell">
|
||||
<GeneralIdentity
|
||||
v-if="group[rowIndex - 1]"
|
||||
:name="group[rowIndex - 1]!.name"
|
||||
:picture="group[rowIndex - 1]!.picture"
|
||||
:image-server="group[rowIndex - 1]!.imageServer"
|
||||
/>
|
||||
</td>
|
||||
<td>{{ statOf(group[rowIndex - 1]) }}</td>
|
||||
<td>{{ gamesOf(group[rowIndex - 1]) }}</td>
|
||||
<td>{{ group[rowIndex - 1]?.win ?? '' }}</td>
|
||||
<td>{{ group[rowIndex - 1]?.draw ?? '' }}</td>
|
||||
<td>{{ group[rowIndex - 1]?.lose ?? '' }}</td>
|
||||
<td>{{ pointsOf(group[rowIndex - 1]) }}</td>
|
||||
<td>{{ group[rowIndex - 1]?.gl ?? '' }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
<template v-if="sectionVisibility.preliminary">
|
||||
<section class="section-title groups-title bg2">조별 예선 순위</section>
|
||||
<div class="group-tabs bg0" role="tablist" aria-label="예선 조 선택">
|
||||
<button
|
||||
v-for="(groupName, groupIndex) in groupNames"
|
||||
:key="`preliminary-tab-${groupName}`"
|
||||
type="button"
|
||||
role="tab"
|
||||
:aria-selected="activePreliminaryGroup === groupIndex"
|
||||
:class="{ active: activePreliminaryGroup === groupIndex }"
|
||||
@click="activePreliminaryGroup = groupIndex"
|
||||
>
|
||||
{{ groupName }}조
|
||||
</button>
|
||||
</div>
|
||||
<section class="group-grid preliminary-grid bg0">
|
||||
<table
|
||||
v-for="(group, groupIndex) in preliminaryGroups"
|
||||
:key="`preliminary-${groupIndex}`"
|
||||
:data-preliminary-group="groupIndex"
|
||||
:class="{ 'mobile-active': activePreliminaryGroup === groupIndex }"
|
||||
>
|
||||
<caption>
|
||||
{{
|
||||
groupNames[groupIndex]
|
||||
}}조
|
||||
</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>순</th>
|
||||
<th>장수</th>
|
||||
<th>{{ typeStatNames[snapshot?.state?.type ?? 0] }}</th>
|
||||
<th>경</th>
|
||||
<th>승</th>
|
||||
<th>무</th>
|
||||
<th>패</th>
|
||||
<th>점</th>
|
||||
<th>득</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="rowIndex in 8" :key="rowIndex">
|
||||
<td>{{ rowIndex }}</td>
|
||||
<td class="general-cell">
|
||||
<GeneralIdentity
|
||||
v-if="group[rowIndex - 1]"
|
||||
:name="group[rowIndex - 1]!.name"
|
||||
:picture="group[rowIndex - 1]!.picture"
|
||||
:image-server="group[rowIndex - 1]!.imageServer"
|
||||
/>
|
||||
</td>
|
||||
<td>{{ statOf(group[rowIndex - 1]) }}</td>
|
||||
<td>{{ gamesOf(group[rowIndex - 1]) }}</td>
|
||||
<td>{{ group[rowIndex - 1]?.win ?? '' }}</td>
|
||||
<td>{{ group[rowIndex - 1]?.draw ?? '' }}</td>
|
||||
<td>{{ group[rowIndex - 1]?.lose ?? '' }}</td>
|
||||
<td>{{ pointsOf(group[rowIndex - 1]) }}</td>
|
||||
<td>{{ group[rowIndex - 1]?.gl ?? '' }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<div class="legacy-bracket-table-signature" hidden>
|
||||
<table v-for="(rowCount, tableIndex) in [11, 11, 11, 10]" :key="tableIndex">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { describe, it } from 'node:test';
|
||||
import { resolveTournamentStageName } from '../src/utils/tournamentStatus.ts';
|
||||
import { resolveTournamentSectionVisibility, resolveTournamentStageName } from '../src/utils/tournamentStatus.ts';
|
||||
|
||||
void describe('tournament status labels', () => {
|
||||
void it('describes inactive and active tournament stages', () => {
|
||||
@@ -14,4 +14,32 @@ void describe('tournament status labels', () => {
|
||||
assert.equal(resolveTournamentStageName(11), '상태 확인 중');
|
||||
assert.equal(resolveTournamentStageName(-1), '상태 확인 중');
|
||||
});
|
||||
|
||||
void it('reveals tournament sections only after their stage begins and retains completed results', () => {
|
||||
assert.deepEqual(resolveTournamentSectionVisibility(0), {
|
||||
preliminary: false,
|
||||
final: false,
|
||||
knockout: false,
|
||||
});
|
||||
assert.deepEqual(resolveTournamentSectionVisibility(1), {
|
||||
preliminary: true,
|
||||
final: false,
|
||||
knockout: false,
|
||||
});
|
||||
assert.deepEqual(resolveTournamentSectionVisibility(3), {
|
||||
preliminary: true,
|
||||
final: true,
|
||||
knockout: false,
|
||||
});
|
||||
assert.deepEqual(resolveTournamentSectionVisibility(5), {
|
||||
preliminary: true,
|
||||
final: true,
|
||||
knockout: true,
|
||||
});
|
||||
assert.deepEqual(resolveTournamentSectionVisibility(0, 7), {
|
||||
preliminary: true,
|
||||
final: true,
|
||||
knockout: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user