merge: 최신 main을 개인 봉급 표시에 통합한다
This commit is contained in:
@@ -79,7 +79,7 @@ export const troopRouter = router({
|
||||
troopLeaderIds.length === 0
|
||||
? []
|
||||
: await ctx.db.generalTurn.findMany({
|
||||
where: { generalId: { in: troopLeaderIds } },
|
||||
where: { generalId: { in: troopLeaderIds }, turnIdx: { lt: 5 } },
|
||||
select: { generalId: true, turnIdx: true, actionCode: true },
|
||||
orderBy: [{ generalId: 'asc' }, { turnIdx: 'asc' }],
|
||||
});
|
||||
@@ -94,7 +94,8 @@ export const troopRouter = router({
|
||||
: 30;
|
||||
for (const turn of turns) {
|
||||
const list = reservedByLeader.get(turn.generalId) ?? [];
|
||||
list.push(turn.actionCode);
|
||||
// Ref 부대 편성은 앞쪽 슬롯이 집합인지 여부만 공개하고 다른 명령은 가립니다.
|
||||
list.push(turn.actionCode === 'che_집합' ? '집합' : '-');
|
||||
reservedByLeader.set(turn.generalId, list);
|
||||
}
|
||||
|
||||
|
||||
@@ -77,10 +77,12 @@ const buildContext = (options: {
|
||||
auth?: GameSessionTokenPayload | null;
|
||||
requestId?: string;
|
||||
transaction?: ReturnType<typeof vi.fn>;
|
||||
turns?: Array<{ generalId: number; turnIdx: number; actionCode: string }>;
|
||||
result: Awaited<ReturnType<TurnDaemonTransport['requestCommand']>>;
|
||||
}) => {
|
||||
const me = options.me ?? buildGeneral();
|
||||
const requestCommand = vi.fn(async () => options.result);
|
||||
const generalTurnFindMany = vi.fn(async () => options.turns ?? []);
|
||||
const db = {
|
||||
...(options.transaction ? { $transaction: options.transaction } : {}),
|
||||
general: {
|
||||
@@ -110,7 +112,7 @@ const buildContext = (options: {
|
||||
},
|
||||
city: { findMany: vi.fn(async () => [{ id: 1, name: '북평' }]) },
|
||||
worldState: { findFirst: vi.fn(async () => ({ config: { const: { upgradeLimit: 20 } } })) },
|
||||
generalTurn: { findMany: vi.fn(async () => []) },
|
||||
generalTurn: { findMany: generalTurnFindMany },
|
||||
};
|
||||
const accessTokenStore = new RedisAccessTokenStore(
|
||||
{
|
||||
@@ -134,7 +136,7 @@ const buildContext = (options: {
|
||||
flushStore: new InMemoryFlushStore(),
|
||||
gameTokenSecret: 'test-secret',
|
||||
};
|
||||
return { context, requestCommand };
|
||||
return { context, requestCommand, generalTurnFindMany };
|
||||
};
|
||||
|
||||
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 () => {
|
||||
const { context, requestCommand } = buildContext({
|
||||
result: { type: 'troopCreate', ok: true, generalId: 1, troopId: 1, troopName: '백마대' },
|
||||
|
||||
@@ -80,7 +80,7 @@ const baseTroops = (): TroopFixture[] => [
|
||||
name: '백마대',
|
||||
nationId: 1,
|
||||
turnTime: '2026-07-25T08:20:30.000Z',
|
||||
reservedCommands: ['che_집합', 'che_이동'],
|
||||
reservedCommands: ['집합', '-'],
|
||||
leader: {
|
||||
id: 1,
|
||||
name: '공손찬',
|
||||
@@ -96,7 +96,7 @@ const baseTroops = (): TroopFixture[] => [
|
||||
name: '청룡대',
|
||||
nationId: 1,
|
||||
turnTime: '2026-07-25T08:30:30.000Z',
|
||||
reservedCommands: ['che_징병'],
|
||||
reservedCommands: ['-'],
|
||||
leader: {
|
||||
id: 2,
|
||||
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 }) => {
|
||||
await installApiFixture(page, {
|
||||
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 gotoTroop(page);
|
||||
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
|
||||
.locator('.troopItem')
|
||||
@@ -319,6 +343,9 @@ test('matches the legacy 500px responsive placement', async ({ page }) => {
|
||||
await page.setViewportSize({ width: 500, height: 800 });
|
||||
await gotoTroop(page);
|
||||
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
|
||||
.locator('.troopItem')
|
||||
|
||||
Reference in New Issue
Block a user