메인 턴 당기기와 미루기를 1턴 기본 분할 버튼으로 변경

This commit is contained in:
2026-09-10 02:50:19 +00:00
parent 3f4e5095b5
commit 3a5d4e7833
3 changed files with 144 additions and 33 deletions
@@ -987,6 +987,20 @@ const install = async (
if (name === 'messages.getContacts') return response({ nation: [] });
if (name === 'board.getAccess') return response({ canMeeting: false, canSecret: false });
if (name === 'tournament.getState') return response({ stage: 0 });
if (name === 'turns.reserved.shiftGeneral') {
const input = (body as Record<string, { generalId: number; amount: number; expectedRevision: number }>)[
String(names.indexOf(name))
];
expect(route.request().method()).toBe('POST');
expect(input.generalId).toBe(generalId);
expect(input.expectedRevision).toBe(generalRevision);
expect(
Number.isInteger(input.amount) && Math.abs(input.amount) >= 1 && Math.abs(input.amount) <= 6
).toBe(true);
requests.push(input);
generalRevision += 1;
return response({ ok: true, revision: generalRevision, turns: generalTurns, autorunLimit: 2403 });
}
if (name === 'turns.reserved.setGeneralBulk') {
requests.push(body);
if (rejectGeneral) return errorResponse(name, '대상 도시를 선택할 수 없습니다.');
@@ -1019,6 +1033,80 @@ const install = async (
return requests;
};
for (const width of [1200, 500]) {
test(`split turn shift applies one turn or the chosen count in both modes at ${width}px`, async ({
page,
}, testInfo) => {
const requests = await install(page);
const shifts = () =>
requests.filter((entry) => typeof entry === 'object' && entry !== null && 'amount' in entry);
await page.setViewportSize({ width, height: 900 });
await page.goto('/');
const editor = page.locator('[data-command-scope="general"]:visible');
const evidence: unknown[] = [];
for (const advanced of [false, true]) {
if (advanced) await editor.getByRole('button', { name: '고급 모드', exact: true }).click();
for (const [index, label] of ['당기기', '미루기'].entries()) {
const direction = index === 0 ? -1 : 1;
const control = editor.locator('.bottom-shift-control').nth(index);
const main = control.getByRole('button', { name: label, exact: true });
const menu = control.locator('details');
const toggle = menu.locator('summary');
const before = shifts().length;
await main.click();
await expect.poll(() => shifts().length).toBe(before + 1);
expect(shifts().at(-1)).toMatchObject({ amount: direction });
await expect(menu).not.toHaveAttribute('open');
await toggle.focus();
await page.keyboard.press('Enter');
await expect(menu).toHaveAttribute('open');
expect(shifts().length).toBe(before + 1);
await expect(menu.locator('.menu-items > button')).toHaveText([
'1턴',
'2턴',
'3턴',
'4턴',
'5턴',
'6턴',
]);
const geometry = await control.evaluate((element) => {
const main = element.querySelector('button')!;
const toggle = element.querySelector('summary')!;
const menu = element.querySelector('.menu-items')!;
return {
main: main.getBoundingClientRect().toJSON(),
toggle: toggle.getBoundingClientRect().toJSON(),
menu: menu.getBoundingClientRect().toJSON(),
style: { radius: getComputedStyle(main).borderRadius, font: getComputedStyle(main).fontFamily },
overflow: document.documentElement.scrollWidth - document.documentElement.clientWidth,
};
});
expect(geometry.main.right).toBeCloseTo(geometry.toggle.left, 1);
expect(geometry.menu.right).toBeLessThanOrEqual(width);
expect(geometry.menu.left).toBeGreaterThanOrEqual(0);
expect(geometry.overflow).toBeLessThanOrEqual(0);
evidence.push({ advanced, label, geometry });
await page.screenshot({
path: testInfo.outputPath(`split-${advanced}-${index}-${width}.png`),
fullPage: true,
});
await menu.getByRole('button', { name: '6턴', exact: true }).click();
await expect.poll(() => shifts().length).toBe(before + 2);
expect(shifts().at(-1)).toMatchObject({ amount: direction * 6 });
await expect(menu).not.toHaveAttribute('open');
await main.click();
await expect.poll(() => shifts().length).toBe(before + 3);
expect(shifts().at(-1)).toMatchObject({ amount: direction });
}
}
await writeFile(testInfo.outputPath('split-evidence.json'), JSON.stringify(evidence, null, 2));
await writeFile(
testInfo.outputPath('split-editor.html'),
await editor.evaluate((element) => element.outerHTML)
);
});
}
test('offers 12 repeat turns and six shift turns for general turns while keeping the chief range', async ({ page }) => {
await install(page);
await page.setViewportSize({ width: 1200, height: 900 });
+4 -2
View File
@@ -4393,10 +4393,12 @@ test('all main Lumen button families share the rounded pressed geometry', async
[
'당기기',
page.locator('[data-main-target="commands"] .bottom-actions').getByRole('button', { name: '당기기' }),
{ radius: '5.25px 0px 0px 5.25px' },
],
[
'미루기',
page.locator('[data-main-target="commands"] .bottom-actions').getByRole('button', { name: '미루기' }),
{ radius: '5.25px 0px 0px 5.25px' },
],
[
'펼치기',
@@ -4457,7 +4459,7 @@ test('all main Lumen button families share the rounded pressed geometry', async
filter: 'none',
});
if (label === '당기기' || label === '미루기') {
expect(base.width, `${label} fills its menu column`).toBeCloseTo(base.parentWidth, 2);
expect(base.width, `${label} leaves room for the split button`).toBeCloseTo(base.parentWidth - 28, 2);
}
await control.focus();
@@ -4702,7 +4704,7 @@ test('mobile main Lumen button families keep the same state geometry without ove
await expect(control).toHaveClass(/legacy-button/u);
await expect(control).toHaveCSS(
'border-radius',
index === 7 ? '0px 5.25px 5.25px 0px' : index === 8 ? '5.25px 0px 0px 5.25px' : '5.25px'
index === 7 ? '0px 5.25px 5.25px 0px' : [4, 5, 8].includes(index) ? '5.25px 0px 0px 5.25px' : '5.25px'
);
await expect(control).toHaveCSS('border-bottom-width', '4px');
}
@@ -701,15 +701,30 @@ const clickOutsideMenu = (event: Event) => {
</div>
<div v-if="!props.compact" class="bottom-actions">
<div v-for="direction in [-1, 1]" :key="direction" class="bottom-shift-control">
<button
class="legacy-button legacy-button--secondary shift-main"
type="button"
:title="direction < 0 ? '1턴 당기기' : '1턴 미루기'"
@click="emit('shift', direction)"
>
{{ direction < 0 ? '당기기' : '미루기' }}
</button>
<details class="legacy-menu bottom-shift-menu">
<summary class="legacy-button legacy-button--secondary" role="button">당기기</summary>
<summary
class="legacy-button legacy-button--secondary"
role="button"
:aria-label="direction < 0 ? '당길 선택' : '미룰 선택'"
>
<span aria-hidden="true"></span>
</summary>
<div class="menu-items">
<button
v-for="amount in props.maxShiftTurn"
:key="amount"
type="button"
@click="
emit('shift', -amount);
emit('shift', direction * amount);
clickOutsideMenu($event);
"
>
@@ -717,22 +732,7 @@ const clickOutsideMenu = (event: Event) => {
</button>
</div>
</details>
<details class="legacy-menu bottom-shift-menu">
<summary class="legacy-button legacy-button--secondary" role="button">미루기</summary>
<div class="menu-items">
<button
v-for="amount in props.maxShiftTurn"
:key="amount"
type="button"
@click="
emit('shift', amount);
clickOutsideMenu($event);
"
>
{{ amount }}
</button>
</div>
</details>
<button class="legacy-button legacy-button--secondary" type="button" @click="expanded = !expanded">
{{ expanded ? '접기' : '펼치기' }}
</button>
@@ -1098,8 +1098,29 @@ const clickOutsideMenu = (event: Event) => {
gap: 4px;
padding-top: 3px;
}
.bottom-shift-control {
display: flex;
min-width: 0;
}
.shift-main {
flex: 1;
min-width: 0;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.bottom-shift-menu {
flex: 0 0 28px;
}
.bottom-shift-menu > summary {
width: 100%;
padding-inline: 0;
border-left: 0;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
.bottom-shift-menu .menu-items {
left: auto;
right: 0;
}
.command-picker {
position: absolute;