merge: 베팅 후보 핵심 능력치 표시를 통합
This commit is contained in:
@@ -126,7 +126,7 @@ 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 } = {}) => {
|
||||
const installFixture = async (page: Page, options: { applicationOpen?: boolean; tournamentType?: number } = {}) => {
|
||||
let joined = false;
|
||||
await page.addInitScript((profile) => {
|
||||
window.localStorage.setItem('sammo-game-token', 'ga_tournament_bracket_playwright');
|
||||
@@ -152,7 +152,7 @@ const installFixture = async (page: Page, options: { applicationOpen?: boolean }
|
||||
state: {
|
||||
stage: options.applicationOpen ? 1 : 0,
|
||||
phase: 0,
|
||||
type: 0,
|
||||
type: options.tournamentType ?? 0,
|
||||
auto: false,
|
||||
openYear: 184,
|
||||
openMonth: 1,
|
||||
@@ -293,16 +293,21 @@ test('desktop bracket connects every real general slot to the next round', async
|
||||
const firstSlot = page.locator('.desktop-bracket-name').first();
|
||||
const oddsContainment = await firstSlot.evaluate((slot) => {
|
||||
const card = slot.getBoundingClientRect();
|
||||
const stat = slot.querySelector<HTMLElement>('.bracket-core-stat')!.getBoundingClientRect();
|
||||
const odds = slot.querySelector<HTMLElement>('.bracket-odds')!.getBoundingClientRect();
|
||||
return {
|
||||
cardTop: card.top,
|
||||
cardBottom: card.bottom,
|
||||
statTop: stat.top,
|
||||
statBottom: stat.bottom,
|
||||
oddsTop: odds.top,
|
||||
oddsBottom: odds.bottom,
|
||||
cardHeight: card.height,
|
||||
};
|
||||
});
|
||||
expect(oddsContainment.cardHeight).toBeGreaterThanOrEqual(82);
|
||||
expect(oddsContainment.statTop).toBeGreaterThanOrEqual(oddsContainment.cardTop);
|
||||
expect(oddsContainment.statBottom).toBeLessThanOrEqual(oddsContainment.cardBottom);
|
||||
expect(oddsContainment.oddsTop).toBeGreaterThanOrEqual(oddsContainment.cardTop);
|
||||
expect(oddsContainment.oddsBottom).toBeLessThanOrEqual(oddsContainment.cardBottom);
|
||||
await expect(firstSlot.locator('.bracket-my-bet')).toHaveText('내 투자 금120');
|
||||
@@ -424,10 +429,20 @@ test('mobile bracket exposes every round through tabs with standard horizontal i
|
||||
const firstMobileSlot = bracket.locator('.mobile-bracket-name').first();
|
||||
const mobileOddsContainment = await firstMobileSlot.evaluate((slot) => {
|
||||
const card = slot.getBoundingClientRect();
|
||||
const stat = slot.querySelector<HTMLElement>('.bracket-core-stat')!.getBoundingClientRect();
|
||||
const odds = slot.querySelector<HTMLElement>('.bracket-odds')!.getBoundingClientRect();
|
||||
return { cardBottom: card.bottom, oddsBottom: odds.bottom, cardHeight: card.height };
|
||||
return {
|
||||
cardTop: card.top,
|
||||
cardBottom: card.bottom,
|
||||
statTop: stat.top,
|
||||
statBottom: stat.bottom,
|
||||
oddsBottom: odds.bottom,
|
||||
cardHeight: card.height,
|
||||
};
|
||||
});
|
||||
expect(mobileOddsContainment.cardHeight).toBeGreaterThanOrEqual(82);
|
||||
expect(mobileOddsContainment.statTop).toBeGreaterThanOrEqual(mobileOddsContainment.cardTop);
|
||||
expect(mobileOddsContainment.statBottom).toBeLessThanOrEqual(mobileOddsContainment.cardBottom);
|
||||
expect(mobileOddsContainment.oddsBottom).toBeLessThanOrEqual(mobileOddsContainment.cardBottom);
|
||||
await expect(firstMobileSlot.locator('.bracket-my-bet')).toHaveText('내 투자 금120');
|
||||
await expect(page.getByRole('tablist', { name: '본선 조 선택' })).toBeVisible();
|
||||
@@ -469,6 +484,7 @@ test('mobile betting rankings use tabs and keep dedicated icons beside general n
|
||||
await page.goto('betting');
|
||||
|
||||
await expect(page.locator('.candidate-card')).toHaveCount(16);
|
||||
await expect(page.locator('.betting-bracket .bracket-core-stat').first()).toHaveText('종합 240');
|
||||
await expect(page.locator('.betting-bracket .bracket-my-bet').first()).toHaveText('내 투자 금120');
|
||||
await expect(page.getByRole('tablist', { name: '토너먼트 랭킹 종목 선택' })).toBeVisible();
|
||||
await expect(page.locator('.ranking-table:visible')).toHaveCount(1);
|
||||
@@ -491,6 +507,17 @@ test('mobile betting rankings use tabs and keep dedicated icons beside general n
|
||||
await persistScreenshot(page, 'tournament-ranking-mobile', testInfo.outputPath('tournament-ranking-mobile.webp'));
|
||||
});
|
||||
|
||||
test('betting bracket shows intelligence for debate tournament candidates', async ({ page }) => {
|
||||
await page.setViewportSize({ width: 390, height: 844 });
|
||||
await installFixture(page, { tournamentType: 3 });
|
||||
await page.goto('betting');
|
||||
|
||||
await expect(page.locator('.betting-bracket .bracket-core-stat').first()).toHaveText('지력 80');
|
||||
await expect(page.locator('.betting-bracket .bracket-odds').first()).toHaveText('배당 28.00');
|
||||
await expect(page.locator('.betting-bracket .bracket-my-bet').first()).toHaveText('내 투자 금120');
|
||||
expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual(390);
|
||||
});
|
||||
|
||||
test('desktop betting presents icon-and-name cards and all four rankings without document overflow', async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { computed, ref } from 'vue';
|
||||
import GeneralIdentity from '../ui/GeneralIdentity.vue';
|
||||
import {
|
||||
buildTournamentBracket,
|
||||
resolveTournamentCoreStat,
|
||||
type TournamentBracketMatch,
|
||||
type TournamentBracketParticipant,
|
||||
} from '../../utils/tournamentBracket';
|
||||
@@ -14,6 +15,7 @@ const props = defineProps<{
|
||||
betTotals?: Record<number, number>;
|
||||
myBetTotals?: Record<number, number>;
|
||||
totalBet: number;
|
||||
tournamentType?: number;
|
||||
showLegend?: boolean;
|
||||
}>();
|
||||
|
||||
@@ -68,6 +70,8 @@ const odds = (id: number | null) => {
|
||||
return (props.totalBet / amount).toFixed(2);
|
||||
};
|
||||
const myBet = (id: number | null) => (id === null ? 0 : (props.myBetTotals?.[id] ?? 0));
|
||||
const coreStat = (slot: (typeof bracket.value.top16.slots)[number]) =>
|
||||
resolveTournamentCoreStat(slot, props.tournamentType ?? 0);
|
||||
const mobilePairs = computed(() => {
|
||||
const column = roundColumns.value[activeMobileRound.value] ?? [];
|
||||
if (activeMobileRound.value === roundColumns.value.length - 1) return column.map((slot) => [slot]);
|
||||
@@ -119,6 +123,9 @@ const mobilePairs = computed(() => {
|
||||
>
|
||||
<GeneralIdentity :name="slot.name" :picture="slot.picture" :image-server="slot.imageServer" />
|
||||
<div v-if="columnIndex === 0" class="bracket-bet-summary">
|
||||
<small v-if="coreStat(slot)" class="bracket-core-stat">
|
||||
{{ coreStat(slot)?.label }} {{ coreStat(slot)?.value }}
|
||||
</small>
|
||||
<small class="bracket-odds">배당 {{ odds(slot.id) }}</small>
|
||||
<small class="bracket-my-bet">내 투자 금{{ myBet(slot.id) }}</small>
|
||||
</div>
|
||||
@@ -152,6 +159,9 @@ const mobilePairs = computed(() => {
|
||||
>
|
||||
<GeneralIdentity :name="slot.name" :picture="slot.picture" :image-server="slot.imageServer" />
|
||||
<div v-if="activeMobileRound === 0" class="bracket-bet-summary">
|
||||
<small v-if="coreStat(slot)" class="bracket-core-stat">
|
||||
{{ coreStat(slot)?.label }} {{ coreStat(slot)?.value }}
|
||||
</small>
|
||||
<small class="bracket-odds">배당 {{ odds(slot.id) }}</small>
|
||||
<small class="bracket-my-bet">내 투자 금{{ myBet(slot.id) }}</small>
|
||||
</div>
|
||||
@@ -236,6 +246,7 @@ const mobilePairs = computed(() => {
|
||||
gap: 4px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.bracket-core-stat,
|
||||
.bracket-odds,
|
||||
.bracket-my-bet {
|
||||
display: block;
|
||||
@@ -244,6 +255,9 @@ const mobilePairs = computed(() => {
|
||||
font-size: 11px;
|
||||
line-height: 12px;
|
||||
}
|
||||
.bracket-core-stat {
|
||||
color: #fff;
|
||||
}
|
||||
.bracket-my-bet {
|
||||
overflow: hidden;
|
||||
color: orange;
|
||||
|
||||
@@ -3,6 +3,9 @@ export interface TournamentBracketParticipant {
|
||||
name: string;
|
||||
picture?: string | null;
|
||||
imageServer?: number | null;
|
||||
leadership?: number;
|
||||
strength?: number;
|
||||
intel?: number;
|
||||
}
|
||||
|
||||
export interface TournamentBracketMatch {
|
||||
@@ -20,6 +23,14 @@ export interface TournamentBracketSlot {
|
||||
picture: string | null;
|
||||
imageServer: number;
|
||||
advanced: boolean;
|
||||
leadership?: number;
|
||||
strength?: number;
|
||||
intel?: number;
|
||||
}
|
||||
|
||||
export interface TournamentCoreStat {
|
||||
label: '종합' | '통솔' | '무력' | '지력';
|
||||
value: number;
|
||||
}
|
||||
|
||||
export interface TournamentBracketRound {
|
||||
@@ -43,6 +54,18 @@ const emptySlot = (): TournamentBracketSlot => ({
|
||||
advanced: false,
|
||||
});
|
||||
|
||||
export const resolveTournamentCoreStat = (
|
||||
participant: Pick<TournamentBracketParticipant, 'leadership' | 'strength' | 'intel'>,
|
||||
tournamentType: number
|
||||
): TournamentCoreStat | null => {
|
||||
const { leadership, strength, intel } = participant;
|
||||
if (leadership === undefined || strength === undefined || intel === undefined) return null;
|
||||
if (tournamentType === 0) return { label: '종합', value: leadership + strength + intel };
|
||||
if (tournamentType === 1) return { label: '통솔', value: leadership };
|
||||
if (tournamentType === 2) return { label: '무력', value: strength };
|
||||
return { label: '지력', value: intel };
|
||||
};
|
||||
|
||||
export const buildTournamentBracket = (
|
||||
participants: TournamentBracketParticipant[],
|
||||
matches: TournamentBracketMatch[],
|
||||
@@ -65,6 +88,9 @@ export const buildTournamentBracket = (
|
||||
picture: participant?.picture ?? null,
|
||||
imageServer: participant?.imageServer ?? 0,
|
||||
advanced: match.winnerId === id,
|
||||
leadership: participant?.leadership,
|
||||
strength: participant?.strength,
|
||||
intel: participant?.intel,
|
||||
};
|
||||
})
|
||||
);
|
||||
@@ -84,6 +110,9 @@ export const buildTournamentBracket = (
|
||||
picture: participantOf(resolvedWinnerId)?.picture ?? null,
|
||||
imageServer: participantOf(resolvedWinnerId)?.imageServer ?? 0,
|
||||
advanced: resolvedWinnerId !== null,
|
||||
leadership: participantOf(resolvedWinnerId)?.leadership,
|
||||
strength: participantOf(resolvedWinnerId)?.strength,
|
||||
intel: participantOf(resolvedWinnerId)?.intel,
|
||||
},
|
||||
final,
|
||||
semi: buildRound(9, 4),
|
||||
|
||||
@@ -137,6 +137,7 @@ const placeBet = async (targetId: number) => {
|
||||
:bet-totals="betTotals"
|
||||
:my-bet-totals="myBetTotals"
|
||||
:total-bet="totalAmount"
|
||||
:tournament-type="snapshot?.state?.type ?? 0"
|
||||
:show-legend="false"
|
||||
/>
|
||||
|
||||
|
||||
@@ -193,6 +193,7 @@ const start = async () => {
|
||||
:bet-totals="betTotals"
|
||||
:my-bet-totals="myBetTotals"
|
||||
:total-bet="totalBet"
|
||||
:tournament-type="snapshot?.state?.type ?? 0"
|
||||
/>
|
||||
|
||||
<section v-if="currentMatch" class="fight bg0">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { describe, it } from 'node:test';
|
||||
|
||||
import { buildTournamentBracket } from '../src/utils/tournamentBracket.ts';
|
||||
import { buildTournamentBracket, resolveTournamentCoreStat } from '../src/utils/tournamentBracket.ts';
|
||||
|
||||
const participants = Array.from({ length: 16 }, (_, index) => ({
|
||||
id: index + 1,
|
||||
@@ -38,6 +38,15 @@ const matches = [
|
||||
];
|
||||
|
||||
void describe('tournament bracket', () => {
|
||||
void it('maps every tournament type to the legacy core stat', () => {
|
||||
const participant = { leadership: 81, strength: 72, intel: 93 };
|
||||
|
||||
assert.deepEqual(resolveTournamentCoreStat(participant, 0), { label: '종합', value: 246 });
|
||||
assert.deepEqual(resolveTournamentCoreStat(participant, 1), { label: '통솔', value: 81 });
|
||||
assert.deepEqual(resolveTournamentCoreStat(participant, 2), { label: '무력', value: 72 });
|
||||
assert.deepEqual(resolveTournamentCoreStat(participant, 3), { label: '지력', value: 93 });
|
||||
});
|
||||
|
||||
void it('keeps every general in the worker roundIndex order and marks the actual winner path', () => {
|
||||
const bracket = buildTournamentBracket(participants, matches, 1);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user