merge: 최신 main을 메인 현재 시각 갱신에 통합한다
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
import { TRPCError } from '@trpc/server';
|
import { TRPCError } from '@trpc/server';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { LogCategory, LogScope } from '@sammo-ts/logic';
|
import { getBillByLevel, LogCategory, LogScope } from '@sammo-ts/logic';
|
||||||
import { asRecord, type RankDataType } from '@sammo-ts/common';
|
import { asRecord, type RankDataType } from '@sammo-ts/common';
|
||||||
|
|
||||||
import type { GameApiContext } from '../../context.js';
|
import type { GameApiContext } from '../../context.js';
|
||||||
@@ -517,6 +517,7 @@ export const getGeneralContext = async (ctx: GameApiContext) => {
|
|||||||
injury: general.injury,
|
injury: general.injury,
|
||||||
experience: general.experience,
|
experience: general.experience,
|
||||||
dedication: general.dedication,
|
dedication: general.dedication,
|
||||||
|
bill: getBillByLevel(dedicationLevel),
|
||||||
age: general.age,
|
age: general.age,
|
||||||
retirementYear,
|
retirementYear,
|
||||||
turnTime: general.turnTime.toISOString(),
|
turnTime: general.turnTime.toISOString(),
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { TRPCError } from '@trpc/server';
|
import { TRPCError } from '@trpc/server';
|
||||||
|
|
||||||
import { asRecord, type RankDataType } from '@sammo-ts/common';
|
import { asRecord, type RankDataType } from '@sammo-ts/common';
|
||||||
import { LogCategory } from '@sammo-ts/logic';
|
import { getBillByLevel, LogCategory } from '@sammo-ts/logic';
|
||||||
|
|
||||||
import { accessAuthedProcedure } from '../../../trpc.js';
|
import { accessAuthedProcedure } from '../../../trpc.js';
|
||||||
import {
|
import {
|
||||||
@@ -201,6 +201,7 @@ export const getBattleCenter = accessAuthedProcedure.query(async ({ ctx }) => {
|
|||||||
},
|
},
|
||||||
experience: general.experience,
|
experience: general.experience,
|
||||||
dedication: general.dedication,
|
dedication: general.dedication,
|
||||||
|
bill: getBillByLevel(dedicationLevel),
|
||||||
injury: general.injury,
|
injury: general.injury,
|
||||||
gold: general.gold,
|
gold: general.gold,
|
||||||
rice: general.rice,
|
rice: general.rice,
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ export const troopRouter = router({
|
|||||||
troopLeaderIds.length === 0
|
troopLeaderIds.length === 0
|
||||||
? []
|
? []
|
||||||
: await ctx.db.generalTurn.findMany({
|
: await ctx.db.generalTurn.findMany({
|
||||||
where: { generalId: { in: troopLeaderIds } },
|
where: { generalId: { in: troopLeaderIds }, turnIdx: { lt: 5 } },
|
||||||
select: { generalId: true, turnIdx: true, actionCode: true },
|
select: { generalId: true, turnIdx: true, actionCode: true },
|
||||||
orderBy: [{ generalId: 'asc' }, { turnIdx: 'asc' }],
|
orderBy: [{ generalId: 'asc' }, { turnIdx: 'asc' }],
|
||||||
});
|
});
|
||||||
@@ -94,7 +94,8 @@ export const troopRouter = router({
|
|||||||
: 30;
|
: 30;
|
||||||
for (const turn of turns) {
|
for (const turn of turns) {
|
||||||
const list = reservedByLeader.get(turn.generalId) ?? [];
|
const list = reservedByLeader.get(turn.generalId) ?? [];
|
||||||
list.push(turn.actionCode);
|
// Ref 부대 편성은 앞쪽 슬롯이 집합인지 여부만 공개하고 다른 명령은 가립니다.
|
||||||
|
list.push(turn.actionCode === 'che_집합' ? '집합' : '-');
|
||||||
reservedByLeader.set(turn.generalId, list);
|
reservedByLeader.set(turn.generalId, list);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -297,6 +297,7 @@ describe('in-game my information ownership', () => {
|
|||||||
statUpgradeLimit: 20,
|
statUpgradeLimit: 20,
|
||||||
dex: [350, 1_375, 3_500, 7_125, 12_650],
|
dex: [350, 1_375, 3_500, 7_125, 12_650],
|
||||||
},
|
},
|
||||||
|
bill: 1_000,
|
||||||
},
|
},
|
||||||
city: {
|
city: {
|
||||||
population: 322_886,
|
population: 322_886,
|
||||||
@@ -433,6 +434,7 @@ describe('in-game my information ownership', () => {
|
|||||||
stats: { attack: 100, defence: 150, speed: 7, avoid: 10, magicCoef: 0, cost: 9, rice: 9 },
|
stats: { attack: 100, defence: 150, speed: 7, avoid: 10, magicCoef: 0, cost: 9, rice: 9 },
|
||||||
},
|
},
|
||||||
progression: { experienceLevel: 4, dedicationLevel: 2, dedicationText: '29품관' },
|
progression: { experienceLevel: 4, dedicationLevel: 2, dedicationText: '29품관' },
|
||||||
|
bill: 800,
|
||||||
itemNames: { horse: '노새(+3)' },
|
itemNames: { horse: '노새(+3)' },
|
||||||
itemInfo: { horse: '통솔 +3' },
|
itemInfo: { horse: '통솔 +3' },
|
||||||
},
|
},
|
||||||
@@ -855,6 +857,7 @@ describe('battle-center general and user permissions', () => {
|
|||||||
statUpgradeLimit: 20,
|
statUpgradeLimit: 20,
|
||||||
dex: [0, 0, 0, 0, 0],
|
dex: [0, 0, 0, 0, 0],
|
||||||
},
|
},
|
||||||
|
bill: 600,
|
||||||
serviceYears: 3,
|
serviceYears: 3,
|
||||||
battleStats: {
|
battleStats: {
|
||||||
kills: 5,
|
kills: 5,
|
||||||
|
|||||||
@@ -77,10 +77,12 @@ const buildContext = (options: {
|
|||||||
auth?: GameSessionTokenPayload | null;
|
auth?: GameSessionTokenPayload | null;
|
||||||
requestId?: string;
|
requestId?: string;
|
||||||
transaction?: ReturnType<typeof vi.fn>;
|
transaction?: ReturnType<typeof vi.fn>;
|
||||||
|
turns?: Array<{ generalId: number; turnIdx: number; actionCode: string }>;
|
||||||
result: Awaited<ReturnType<TurnDaemonTransport['requestCommand']>>;
|
result: Awaited<ReturnType<TurnDaemonTransport['requestCommand']>>;
|
||||||
}) => {
|
}) => {
|
||||||
const me = options.me ?? buildGeneral();
|
const me = options.me ?? buildGeneral();
|
||||||
const requestCommand = vi.fn(async () => options.result);
|
const requestCommand = vi.fn(async () => options.result);
|
||||||
|
const generalTurnFindMany = vi.fn(async () => options.turns ?? []);
|
||||||
const db = {
|
const db = {
|
||||||
...(options.transaction ? { $transaction: options.transaction } : {}),
|
...(options.transaction ? { $transaction: options.transaction } : {}),
|
||||||
general: {
|
general: {
|
||||||
@@ -110,7 +112,7 @@ const buildContext = (options: {
|
|||||||
},
|
},
|
||||||
city: { findMany: vi.fn(async () => [{ id: 1, name: '북평' }]) },
|
city: { findMany: vi.fn(async () => [{ id: 1, name: '북평' }]) },
|
||||||
worldState: { findFirst: vi.fn(async () => ({ config: { const: { upgradeLimit: 20 } } })) },
|
worldState: { findFirst: vi.fn(async () => ({ config: { const: { upgradeLimit: 20 } } })) },
|
||||||
generalTurn: { findMany: vi.fn(async () => []) },
|
generalTurn: { findMany: generalTurnFindMany },
|
||||||
};
|
};
|
||||||
const accessTokenStore = new RedisAccessTokenStore(
|
const accessTokenStore = new RedisAccessTokenStore(
|
||||||
{
|
{
|
||||||
@@ -134,7 +136,7 @@ const buildContext = (options: {
|
|||||||
flushStore: new InMemoryFlushStore(),
|
flushStore: new InMemoryFlushStore(),
|
||||||
gameTokenSecret: 'test-secret',
|
gameTokenSecret: 'test-secret',
|
||||||
};
|
};
|
||||||
return { context, requestCommand };
|
return { context, requestCommand, generalTurnFindMany };
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('troop router permissions and mutations', () => {
|
describe('troop router permissions and mutations', () => {
|
||||||
@@ -175,6 +177,30 @@ describe('troop router permissions and mutations', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('returns only the first five Ref-redacted troop command labels without exposing action codes', async () => {
|
||||||
|
const fixture = buildContext({
|
||||||
|
me: buildGeneral({ troopId: 1 }),
|
||||||
|
turns: [
|
||||||
|
{ generalId: 1, turnIdx: 0, actionCode: 'che_집합' },
|
||||||
|
{ generalId: 1, turnIdx: 1, actionCode: 'che_이동' },
|
||||||
|
{ generalId: 1, turnIdx: 2, actionCode: 'che_징병' },
|
||||||
|
{ generalId: 1, turnIdx: 3, actionCode: '휴식' },
|
||||||
|
{ generalId: 1, turnIdx: 4, actionCode: 'che_화계' },
|
||||||
|
],
|
||||||
|
result: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await appRouter.createCaller(fixture.context).troop.getList();
|
||||||
|
|
||||||
|
expect(result.troops[0]?.reservedCommands).toEqual(['집합', '-', '-', '-', '-']);
|
||||||
|
expect(JSON.stringify(result.troops)).not.toContain('che_');
|
||||||
|
expect(fixture.generalTurnFindMany).toHaveBeenCalledWith({
|
||||||
|
where: { generalId: { in: [1] }, turnIdx: { lt: 5 } },
|
||||||
|
select: { generalId: true, turnIdx: true, actionCode: true },
|
||||||
|
orderBy: [{ generalId: 'asc' }, { turnIdx: 'asc' }],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('creates a troop only for the general owned by the authenticated user', async () => {
|
it('creates a troop only for the general owned by the authenticated user', async () => {
|
||||||
const { context, requestCommand } = buildContext({
|
const { context, requestCommand } = buildContext({
|
||||||
result: { type: 'troopCreate', ok: true, generalId: 1, troopId: 1, troopName: '백마대' },
|
result: { type: 'troopCreate', ok: true, generalId: 1, troopId: 1, troopName: '백마대' },
|
||||||
|
|||||||
@@ -629,6 +629,10 @@ test('nation directory reuses only the public general-directory row on hover and
|
|||||||
await expect(preview.locator('[data-general-card-id="10"]')).toContainText('대담');
|
await expect(preview.locator('[data-general-card-id="10"]')).toContainText('대담');
|
||||||
await expect(preview.locator('[data-directory-tooltip="card-special-domestic-10"]')).toContainText('상재');
|
await expect(preview.locator('[data-directory-tooltip="card-special-domestic-10"]')).toContainText('상재');
|
||||||
await expect(preview.locator('[data-directory-tooltip="card-special-war-10"]')).toContainText('귀모');
|
await expect(preview.locator('[data-directory-tooltip="card-special-war-10"]')).toContainText('귀모');
|
||||||
|
await expect(preview.locator('[data-directory-tooltip="card-special-domestic-10"]')).toHaveCSS(
|
||||||
|
'text-decoration-line',
|
||||||
|
'none'
|
||||||
|
);
|
||||||
await expect(preview).not.toContainText('user-');
|
await expect(preview).not.toContainText('user-');
|
||||||
await expect(preview).not.toContainText('secret');
|
await expect(preview).not.toContainText('secret');
|
||||||
|
|
||||||
|
|||||||
@@ -112,6 +112,7 @@ const myGeneral = (state: FixtureState) => ({
|
|||||||
injury: 0,
|
injury: 0,
|
||||||
experience: 100,
|
experience: 100,
|
||||||
dedication: 200,
|
dedication: 200,
|
||||||
|
bill: 800,
|
||||||
age: 30,
|
age: 30,
|
||||||
turnTime: '2026-01-01 00:10:00',
|
turnTime: '2026-01-01 00:10:00',
|
||||||
recentWar: '2026-01-01 00:00:00',
|
recentWar: '2026-01-01 00:00:00',
|
||||||
@@ -296,6 +297,7 @@ const battleCenter = (state: FixtureState) => ({
|
|||||||
stats: { leadership: 70, strength: 60, intelligence: 50 },
|
stats: { leadership: 70, strength: 60, intelligence: 50 },
|
||||||
experience: 100,
|
experience: 100,
|
||||||
dedication: 200,
|
dedication: 200,
|
||||||
|
bill: 800,
|
||||||
injury: 0,
|
injury: 0,
|
||||||
gold: 1_000,
|
gold: 1_000,
|
||||||
rice: 2_000,
|
rice: 2_000,
|
||||||
@@ -334,6 +336,7 @@ const battleCenter = (state: FixtureState) => ({
|
|||||||
stats: { leadership: 50, strength: 50, intelligence: 50 },
|
stats: { leadership: 50, strength: 50, intelligence: 50 },
|
||||||
experience: 0,
|
experience: 0,
|
||||||
dedication: 0,
|
dedication: 0,
|
||||||
|
bill: 400,
|
||||||
injury: 0,
|
injury: 0,
|
||||||
gold: 500,
|
gold: 500,
|
||||||
rice: 500,
|
rice: 500,
|
||||||
@@ -1245,6 +1248,7 @@ test('내 정보&설정 keeps desktop density and becomes a 390px horizontal-ide
|
|||||||
await expect(page.locator('.general-table')).toContainText('병종보병');
|
await expect(page.locator('.general-table')).toContainText('병종보병');
|
||||||
await expect(page.locator('.general-table')).toContainText('삭턴6 턴');
|
await expect(page.locator('.general-table')).toContainText('삭턴6 턴');
|
||||||
await expect(page.locator('.battle-general-extra')).toContainText('계급29품관');
|
await expect(page.locator('.battle-general-extra')).toContainText('계급29품관');
|
||||||
|
await expect(page.locator('.battle-general-extra')).toContainText('봉급800');
|
||||||
await expect(page.locator('.battle-general-extra')).toContainText('전투8회');
|
await expect(page.locator('.battle-general-extra')).toContainText('전투8회');
|
||||||
await expect(page.locator('.battle-general-extra')).toContainText('계략12');
|
await expect(page.locator('.battle-general-extra')).toContainText('계략12');
|
||||||
await expect(page.locator('.battle-general-extra')).toContainText('사관4년');
|
await expect(page.locator('.battle-general-extra')).toContainText('사관4년');
|
||||||
@@ -1255,7 +1259,7 @@ test('내 정보&설정 keeps desktop density and becomes a 390px horizontal-ide
|
|||||||
await expect(page.locator('.battle-general-extra__recent-value')).toHaveText('01-01 00:00');
|
await expect(page.locator('.battle-general-extra__recent-value')).toHaveText('01-01 00:00');
|
||||||
await expect(page.locator('.legacy-general-details')).toHaveCount(0);
|
await expect(page.locator('.legacy-general-details')).toHaveCount(0);
|
||||||
expect(await readGeneralSummaryRows(page.locator('.battle-general-extra'))).toEqual([
|
expect(await readGeneralSummaryRows(page.locator('.battle-general-extra'))).toEqual([
|
||||||
['명성', '계급', ''],
|
['명성', '계급', '봉급'],
|
||||||
['전투', '계략', '사관'],
|
['전투', '계략', '사관'],
|
||||||
['승률', '승리', '패배'],
|
['승률', '승리', '패배'],
|
||||||
['살상률', '사살', '피살'],
|
['살상률', '사살', '피살'],
|
||||||
@@ -1429,6 +1433,7 @@ test('내 정보 항목과 국가 성향은 HTML 리치 툴팁을 마우스와
|
|||||||
const showWithMouse = async (testId: string, expectedTexts: readonly string[]) => {
|
const showWithMouse = async (testId: string, expectedTexts: readonly string[]) => {
|
||||||
const trigger = page.locator(`[data-rich-tooltip="${testId}"]`);
|
const trigger = page.locator(`[data-rich-tooltip="${testId}"]`);
|
||||||
await expect(trigger).toHaveAttribute('tabindex', '0');
|
await expect(trigger).toHaveAttribute('tabindex', '0');
|
||||||
|
await expect(trigger).toHaveCSS('text-decoration-line', 'none');
|
||||||
await trigger.hover();
|
await trigger.hover();
|
||||||
await expect(visibleTooltip).toHaveCount(1);
|
await expect(visibleTooltip).toHaveCount(1);
|
||||||
await expect(visibleTooltip).toHaveAttribute('role', 'tooltip');
|
await expect(visibleTooltip).toHaveAttribute('role', 'tooltip');
|
||||||
@@ -2039,6 +2044,7 @@ test('감찰부 keeps the selector interaction and shows the permission error pa
|
|||||||
await expect(page.locator('.battle-general-name')).toContainText('간의대부');
|
await expect(page.locator('.battle-general-name')).toContainText('간의대부');
|
||||||
await expect(page.locator('.battle-general-name')).toContainText('건강');
|
await expect(page.locator('.battle-general-name')).toContainText('건강');
|
||||||
await expect(page.locator('.battle-general-extra')).toContainText('계급29품관');
|
await expect(page.locator('.battle-general-extra')).toContainText('계급29품관');
|
||||||
|
await expect(page.locator('.battle-general-extra')).toContainText('봉급800');
|
||||||
await expect(page.locator('.battle-general-card')).toContainText('병종보병');
|
await expect(page.locator('.battle-general-card')).toContainText('병종보병');
|
||||||
await expect(page.locator('.battle-general-card')).not.toContainText('che_');
|
await expect(page.locator('.battle-general-card')).not.toContainText('che_');
|
||||||
await expect(page.locator('.battle-general-card')).toHaveAttribute('data-general-basic-card', '');
|
await expect(page.locator('.battle-general-card')).toHaveAttribute('data-general-basic-card', '');
|
||||||
@@ -2049,7 +2055,7 @@ test('감찰부 keeps the selector interaction and shows the permission error pa
|
|||||||
await expect(page.locator('.battle-general-extra')).toContainText('승률62.50%');
|
await expect(page.locator('.battle-general-extra')).toContainText('승률62.50%');
|
||||||
await expect(page.locator('.battle-general-extra')).toContainText('살상률181.84%');
|
await expect(page.locator('.battle-general-extra')).toContainText('살상률181.84%');
|
||||||
expect(await readGeneralSummaryRows(page.locator('.battle-general-extra'))).toEqual([
|
expect(await readGeneralSummaryRows(page.locator('.battle-general-extra'))).toEqual([
|
||||||
['명성', '계급', ''],
|
['명성', '계급', '봉급'],
|
||||||
['전투', '계략', '사관'],
|
['전투', '계략', '사관'],
|
||||||
['승률', '승리', '패배'],
|
['승률', '승리', '패배'],
|
||||||
['살상률', '사살', '피살'],
|
['살상률', '사살', '피살'],
|
||||||
|
|||||||
@@ -2189,6 +2189,7 @@ test('main cards and command input stay inside their Ref-sized grid slots', asyn
|
|||||||
expect(Math.max(...nationRowHeights) - Math.min(...nationRowHeights)).toBeLessThanOrEqual(0.01);
|
expect(Math.max(...nationRowHeights) - Math.min(...nationRowHeights)).toBeLessThanOrEqual(0.01);
|
||||||
const strategicCell = nationCard.locator('.strategic');
|
const strategicCell = nationCard.locator('.strategic');
|
||||||
const strategicTooltip = strategicCell.getByRole('tooltip');
|
const strategicTooltip = strategicCell.getByRole('tooltip');
|
||||||
|
await expect(strategicCell).toHaveCSS('text-decoration-line', 'none');
|
||||||
await expect(strategicTooltip).toBeHidden();
|
await expect(strategicTooltip).toBeHidden();
|
||||||
await strategicCell.hover();
|
await strategicCell.hover();
|
||||||
await expect(strategicTooltip).toBeVisible();
|
await expect(strategicTooltip).toBeVisible();
|
||||||
|
|||||||
@@ -266,6 +266,7 @@ test('renders Ref-shaped token cards, preserves keep cooldown and retries posses
|
|||||||
});
|
});
|
||||||
const tooltip = page.locator('.npc-tooltip').first();
|
const tooltip = page.locator('.npc-tooltip').first();
|
||||||
const tooltipPopup = tooltip.getByRole('tooltip');
|
const tooltipPopup = tooltip.getByRole('tooltip');
|
||||||
|
await expect(tooltip).toHaveCSS('text-decoration-line', 'none');
|
||||||
await expect(tooltipPopup).toBeHidden();
|
await expect(tooltipPopup).toBeHidden();
|
||||||
await tooltip.hover();
|
await tooltip.hover();
|
||||||
await expect(tooltipPopup).toBeVisible();
|
await expect(tooltipPopup).toBeVisible();
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ const baseTroops = (): TroopFixture[] => [
|
|||||||
name: '백마대',
|
name: '백마대',
|
||||||
nationId: 1,
|
nationId: 1,
|
||||||
turnTime: '2026-07-25T08:20:30.000Z',
|
turnTime: '2026-07-25T08:20:30.000Z',
|
||||||
reservedCommands: ['che_집합', 'che_이동'],
|
reservedCommands: ['집합', '-'],
|
||||||
leader: {
|
leader: {
|
||||||
id: 1,
|
id: 1,
|
||||||
name: '공손찬',
|
name: '공손찬',
|
||||||
@@ -96,7 +96,7 @@ const baseTroops = (): TroopFixture[] => [
|
|||||||
name: '청룡대',
|
name: '청룡대',
|
||||||
nationId: 1,
|
nationId: 1,
|
||||||
turnTime: '2026-07-25T08:30:30.000Z',
|
turnTime: '2026-07-25T08:30:30.000Z',
|
||||||
reservedCommands: ['che_징병'],
|
reservedCommands: ['-'],
|
||||||
leader: {
|
leader: {
|
||||||
id: 2,
|
id: 2,
|
||||||
name: '관우',
|
name: '관우',
|
||||||
@@ -224,6 +224,27 @@ const installApiFixture = async (page: Page, state: FixtureState) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
test('shows only the Ref-safe first-five troop command labels on desktop and mobile', async ({ page }) => {
|
||||||
|
await installApiFixture(page, {
|
||||||
|
me: { id: 1, troopId: 1 },
|
||||||
|
permission: 4,
|
||||||
|
troops: baseTroops(),
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const viewport of [
|
||||||
|
{ width: 1000, height: 800 },
|
||||||
|
{ width: 500, height: 800 },
|
||||||
|
]) {
|
||||||
|
await page.setViewportSize(viewport);
|
||||||
|
await gotoTroop(page);
|
||||||
|
|
||||||
|
const firstTroopCommands = page.locator('.troopReservedCommand').first();
|
||||||
|
await expect(firstTroopCommands).toContainText('1: 집합');
|
||||||
|
await expect(firstTroopCommands).toContainText('2: -');
|
||||||
|
await expect(page.locator('#troopList')).not.toContainText('che_');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
test('renders the legacy desktop grid with matching computed geometry and states', async ({ page }) => {
|
test('renders the legacy desktop grid with matching computed geometry and states', async ({ page }) => {
|
||||||
await installApiFixture(page, {
|
await installApiFixture(page, {
|
||||||
me: { id: 1, troopId: 1 },
|
me: { id: 1, troopId: 1 },
|
||||||
@@ -233,6 +254,9 @@ test('renders the legacy desktop grid with matching computed geometry and states
|
|||||||
await page.setViewportSize({ width: 1000, height: 800 });
|
await page.setViewportSize({ width: 1000, height: 800 });
|
||||||
await gotoTroop(page);
|
await gotoTroop(page);
|
||||||
await expect(page.locator('.troopInfo').filter({ hasText: '백마대' })).toBeVisible();
|
await expect(page.locator('.troopInfo').filter({ hasText: '백마대' })).toBeVisible();
|
||||||
|
await expect(page.locator('.troopReservedCommand').first()).toContainText('1: 집합');
|
||||||
|
await expect(page.locator('.troopReservedCommand').first()).toContainText('2: -');
|
||||||
|
await expect(page.locator('#troopList')).not.toContainText('che_');
|
||||||
|
|
||||||
const geometry = await page
|
const geometry = await page
|
||||||
.locator('.troopItem')
|
.locator('.troopItem')
|
||||||
@@ -319,6 +343,9 @@ test('matches the legacy 500px responsive placement', async ({ page }) => {
|
|||||||
await page.setViewportSize({ width: 500, height: 800 });
|
await page.setViewportSize({ width: 500, height: 800 });
|
||||||
await gotoTroop(page);
|
await gotoTroop(page);
|
||||||
await expect(page.locator('.troopInfo').filter({ hasText: '백마대' })).toBeVisible();
|
await expect(page.locator('.troopInfo').filter({ hasText: '백마대' })).toBeVisible();
|
||||||
|
await expect(page.locator('.troopReservedCommand').first()).toContainText('1: 집합');
|
||||||
|
await expect(page.locator('.troopReservedCommand').first()).toContainText('2: -');
|
||||||
|
await expect(page.locator('#troopList')).not.toContainText('che_');
|
||||||
|
|
||||||
const geometry = await page
|
const geometry = await page
|
||||||
.locator('.troopItem')
|
.locator('.troopItem')
|
||||||
|
|||||||
@@ -29,8 +29,6 @@ defineProps<{
|
|||||||
}
|
}
|
||||||
.directory-tooltip--enabled {
|
.directory-tooltip--enabled {
|
||||||
cursor: help;
|
cursor: help;
|
||||||
text-decoration: underline dotted rgb(150 210 255 / 85%);
|
|
||||||
text-underline-offset: 2px;
|
|
||||||
}
|
}
|
||||||
.directory-tooltip--enabled:focus-visible {
|
.directory-tooltip--enabled:focus-visible {
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export type GeneralBattleSummaryData = {
|
|||||||
available?: boolean;
|
available?: boolean;
|
||||||
experience?: number | null;
|
experience?: number | null;
|
||||||
dedicationText?: string | null;
|
dedicationText?: string | null;
|
||||||
|
bill?: number | null;
|
||||||
warnum?: number | null;
|
warnum?: number | null;
|
||||||
wins?: number | null;
|
wins?: number | null;
|
||||||
losses?: number | null;
|
losses?: number | null;
|
||||||
@@ -62,8 +63,13 @@ const killRate = computed(() => {
|
|||||||
<template v-else>
|
<template v-else>
|
||||||
<span>명성</span><strong>{{ numberText(summary.experience) }}</strong> <span>계급</span
|
<span>명성</span><strong>{{ numberText(summary.experience) }}</strong> <span>계급</span
|
||||||
><strong>{{ summary.dedicationText || '-' }}</strong>
|
><strong>{{ summary.dedicationText || '-' }}</strong>
|
||||||
<span class="battle-general-extra__empty" aria-hidden="true"></span>
|
<template v-if="summary.bill !== undefined">
|
||||||
<strong class="battle-general-extra__empty" aria-hidden="true"></strong>
|
<span>봉급</span><strong>{{ numberText(summary.bill) }}</strong>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<span class="battle-general-extra__empty" aria-hidden="true"></span>
|
||||||
|
<strong class="battle-general-extra__empty" aria-hidden="true"></strong>
|
||||||
|
</template>
|
||||||
<span>전투</span
|
<span>전투</span
|
||||||
><strong>{{ numberText(summary.warnum) }}<template v-if="summary.warnum != null">회</template></strong>
|
><strong>{{ numberText(summary.warnum) }}<template v-if="summary.warnum != null">회</template></strong>
|
||||||
<span>계략</span><strong>{{ numberText(summary.strategies) }}</strong>
|
<span>계략</span><strong>{{ numberText(summary.strategies) }}</strong>
|
||||||
|
|||||||
@@ -284,7 +284,6 @@ const displayChiefName = (chief: NationChief | undefined): string => {
|
|||||||
|
|
||||||
.strategic.has-tooltip {
|
.strategic.has-tooltip {
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
text-decoration: underline dashed red;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.cooldown-tooltip {
|
.cooldown-tooltip {
|
||||||
|
|||||||
@@ -97,8 +97,6 @@ watch(
|
|||||||
|
|
||||||
.rich-tooltip-trigger--enabled {
|
.rich-tooltip-trigger--enabled {
|
||||||
cursor: help;
|
cursor: help;
|
||||||
text-decoration: underline dotted rgb(150 210 255 / 85%);
|
|
||||||
text-underline-offset: 2px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.rich-tooltip-trigger--enabled:focus-visible {
|
.rich-tooltip-trigger--enabled:focus-visible {
|
||||||
|
|||||||
@@ -291,6 +291,7 @@ onMounted(() => {
|
|||||||
available: true,
|
available: true,
|
||||||
experience: selectedGeneral.experience,
|
experience: selectedGeneral.experience,
|
||||||
dedicationText: selectedGeneral.progression.dedicationText,
|
dedicationText: selectedGeneral.progression.dedicationText,
|
||||||
|
bill: selectedGeneral.bill,
|
||||||
warnum: selectedGeneral.warnum,
|
warnum: selectedGeneral.warnum,
|
||||||
wins: selectedGeneral.battleStats.kills,
|
wins: selectedGeneral.battleStats.kills,
|
||||||
losses: selectedGeneral.battleStats.deaths,
|
losses: selectedGeneral.battleStats.deaths,
|
||||||
|
|||||||
@@ -1733,7 +1733,6 @@ onUnmounted(() => {
|
|||||||
.npc-tooltip {
|
.npc-tooltip {
|
||||||
position: relative;
|
position: relative;
|
||||||
cursor: help;
|
cursor: help;
|
||||||
text-decoration: underline dotted;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.npc-tooltip [role='tooltip'] {
|
.npc-tooltip [role='tooltip'] {
|
||||||
|
|||||||
@@ -421,6 +421,7 @@ onMounted(() => {
|
|||||||
available: true,
|
available: true,
|
||||||
experience: data.general.experience,
|
experience: data.general.experience,
|
||||||
dedicationText: data.general.progression?.dedicationText,
|
dedicationText: data.general.progression?.dedicationText,
|
||||||
|
bill: data.general.bill,
|
||||||
warnum: data.general.records.battles,
|
warnum: data.general.records.battles,
|
||||||
wins: data.general.records.wins,
|
wins: data.general.records.wins,
|
||||||
losses: data.general.records.losses,
|
losses: data.general.records.losses,
|
||||||
|
|||||||
@@ -36,7 +36,10 @@ available sort keys and placement. NPC·암행부·세력도시는 Ref의 고정
|
|||||||
`내림차순 → 오름차순 → 해제`를 순환하고, 여러 열의 방향과 우선순위를 scoped
|
`내림차순 → 오름차순 → 해제`를 순환하고, 여러 열의 방향과 우선순위를 scoped
|
||||||
SFC indicator로 표시합니다. 장수 일람의 성격·특기·부상 설명은 많은 행에서
|
SFC indicator로 표시합니다. 장수 일람의 성격·특기·부상 설명은 많은 행에서
|
||||||
eager popup instance를 만들지 않는 `DirectoryTooltip.vue` scoped CSS가 소유하며,
|
eager popup instance를 만들지 않는 `DirectoryTooltip.vue` scoped CSS가 소유하며,
|
||||||
mobile에서는 viewport 가장자리 8px 안의 고정 설명판으로 전환합니다.
|
mobile에서는 viewport 가장자리 8px 안의 고정 설명판으로 전환합니다. 게임 내
|
||||||
|
tooltip trigger는 hover/focus 동작과 `cursor: help`를 유지하되, tooltip이 있는 모든
|
||||||
|
텍스트에 점선 밑줄을 반복하지 않습니다. keyboard `focus-visible` outline은 별도의
|
||||||
|
접근성 상태로 유지합니다.
|
||||||
|
|
||||||
## Button composition
|
## Button composition
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user