fix: 자율 행동 상태를 행 툴팁으로 표시

This commit is contained in:
2026-08-17 14:18:38 +00:00
parent e8e062514c
commit 9c2b8d78d0
2 changed files with 106 additions and 33 deletions
+49 -16
View File
@@ -1300,18 +1300,36 @@ test('keeps the entered command visible and reports a server validation error',
await expect(page.getByTestId('command-argument-form').locator('select')).toHaveValue('2'); await expect(page.getByTestId('command-argument-form').locator('select')).toHaveValue('2');
}); });
test('keeps Ref command briefs and autonomous-action state after a turn mutation', async ({ page }) => { test('keeps Ref command briefs and autonomous-action state after a turn mutation', async ({ page, context }) => {
await install(page); await install(page);
await page.setViewportSize({ width: 1200, height: 900 }); await page.setViewportSize({ width: 1200, height: 900 });
await page.goto('/'); await page.goto('/');
const editor = page.locator('[data-command-scope="general"]'); const editor = page.locator('[data-command-scope="general"]');
const status = editor.locator('[data-command-autorun-status]');
const firstRow = editor.locator('.action-column > div').first(); const firstRow = editor.locator('.action-column > div').first();
await expect(status).toHaveText(/ : 200 3 · \d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/u); await expect(editor.locator('[data-command-autorun-status]')).toHaveCount(0);
await expect(firstRow).toContainText('휴식(자율 행동)'); await expect(firstRow).toContainText('휴식(자율 행동)');
await expect(firstRow).toHaveAttribute(
'data-autorun-tooltip',
/ : 200 3 · \d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/u
);
expect(await firstRow.evaluate((element) => getComputedStyle(element).color)).toBe('rgb(170, 255, 255)'); expect(await firstRow.evaluate((element) => getComputedStyle(element).color)).toBe('rgb(170, 255, 255)');
const tooltipStyle = () =>
firstRow.evaluate((element) => {
const style = getComputedStyle(element, '::after');
return { content: style.content, opacity: style.opacity, visibility: style.visibility };
});
expect((await tooltipStyle()).visibility).toBe('hidden');
const heightBeforeHover = await editor.evaluate((element) => element.getBoundingClientRect().height);
await firstRow.hover();
await expect.poll(async () => (await tooltipStyle()).visibility).toBe('visible');
expect((await tooltipStyle()).content).toContain('자율 행동: 200年 3月');
expect(await editor.evaluate((element) => element.getBoundingClientRect().height)).toBe(heightBeforeHover);
await page.screenshot({ path: test.info().outputPath('command-brief-autorun-hover-desktop-1200.png'), fullPage: true });
await page.mouse.move(0, 0);
await expect.poll(async () => (await tooltipStyle()).visibility).toBe('hidden');
await page.getByRole('button', { name: '1턴 명령 입력', exact: true }).click(); await page.getByRole('button', { name: '1턴 명령 입력', exact: true }).click();
const picker = page.getByTestId('command-picker'); const picker = page.getByTestId('command-picker');
await picker.getByRole('button', { name: '군사', exact: true }).click(); await picker.getByRole('button', { name: '군사', exact: true }).click();
@@ -1320,31 +1338,46 @@ test('keeps Ref command briefs and autonomous-action state after a turn mutation
await picker.getByRole('button', { name: '입력', exact: true }).click(); await picker.getByRole('button', { name: '입력', exact: true }).click();
await expect(firstRow).toHaveText('【단양】으로 출병'); await expect(firstRow).toHaveText('【단양】으로 출병');
await expect(firstRow).toHaveAttribute('title', / /u); await expect(firstRow).toHaveAttribute('data-autorun-tooltip', / : 200 3 · .*/u);
expect(await firstRow.evaluate((element) => getComputedStyle(element).color)).toBe('rgb(170, 255, 255)'); expect(await firstRow.evaluate((element) => getComputedStyle(element).color)).toBe('rgb(170, 255, 255)');
await expect(status).toHaveText(/ : 200 3 · .*/u); await firstRow.focus();
await expect.poll(async () => (await tooltipStyle()).visibility).toBe('visible');
await expect(firstRow).toBeFocused();
const desktopGeometry = await editor.evaluate((element) => { const desktopGeometry = await editor.evaluate((element) => {
const statusElement = element.querySelector<HTMLElement>('[data-command-autorun-status]'); const layout = element.querySelector<HTMLElement>('.editor-layout');
const controlPad = element.querySelector<HTMLElement>('.control-pad');
const row = element.querySelector<HTMLElement>('.action-column > div'); const row = element.querySelector<HTMLElement>('.action-column > div');
if (!statusElement || !row) throw new Error('autonomous command geometry is missing'); if (!layout || !controlPad || !row) throw new Error('autonomous command geometry is missing');
return { return {
horizontalOverflow: element.scrollWidth - element.clientWidth, horizontalOverflow: element.scrollWidth - element.clientWidth,
statusWidth: statusElement.getBoundingClientRect().width, controlPadOffset: controlPad.getBoundingClientRect().top - layout.getBoundingClientRect().top,
editorWidth: element.getBoundingClientRect().width,
rowHeight: row.getBoundingClientRect().height, rowHeight: row.getBoundingClientRect().height,
}; };
}); });
expect(desktopGeometry.horizontalOverflow).toBeLessThanOrEqual(0); expect(desktopGeometry.horizontalOverflow).toBeLessThanOrEqual(0);
expect(desktopGeometry.statusWidth).toBeLessThanOrEqual(desktopGeometry.editorWidth); expect(desktopGeometry.controlPadOffset).toBe(0);
expect(desktopGeometry.rowHeight).toBeGreaterThanOrEqual(20); expect(desktopGeometry.rowHeight).toBeGreaterThanOrEqual(20);
await page.screenshot({ path: test.info().outputPath('command-brief-autorun-desktop-1200.png'), fullPage: true }); await page.screenshot({ path: test.info().outputPath('command-brief-autorun-focus-desktop-1200.png'), fullPage: true });
await page.setViewportSize({ width: 500, height: 900 }); const mobilePage = await context.newPage();
await expect(firstRow).toHaveText('【단양】으로 출병'); await install(mobilePage);
await expect(status).toBeVisible(); await mobilePage.setViewportSize({ width: 500, height: 900 });
expect(await editor.evaluate((element) => element.scrollWidth - element.clientWidth)).toBeLessThanOrEqual(0); await mobilePage.goto('/');
await page.screenshot({ path: test.info().outputPath('command-brief-autorun-mobile-500.png'), fullPage: true }); const mobileEditor = mobilePage.locator('[data-command-scope="general"]');
const mobileFirstRow = mobileEditor.locator('.action-column > div').first();
await expect(mobileFirstRow).toContainText('휴식(자율 행동)');
await expect(mobileFirstRow).toHaveAttribute('data-autorun-tooltip', / : 200 3 · .*/u);
await mobileFirstRow.focus();
await expect
.poll(() => mobileFirstRow.evaluate((element) => getComputedStyle(element, '::after').visibility))
.toBe('visible');
expect(await mobileEditor.evaluate((element) => element.scrollWidth - element.clientWidth)).toBeLessThanOrEqual(0);
await mobilePage.screenshot({
path: test.info().outputPath('command-brief-autorun-focus-mobile-500.png'),
fullPage: true,
});
await mobilePage.close();
}); });
test('uses drag selection, clipboard paste, and a stored template in advanced mode', async ({ page }) => { test('uses drag selection, clipboard paste, and a stored template in advanced mode', async ({ page }) => {
@@ -348,10 +348,6 @@ const clickOutsideMenu = (event: Event) => {
<span>{{ props.title }} :</span><strong>{{ props.name ?? '-' }}</strong> <span>{{ props.title }} :</span><strong>{{ props.name ?? '-' }}</strong>
</header> </header>
<div v-if="props.autonomousUntil" class="autorun-status" data-command-autorun-status role="status">
자율 행동: {{ props.autonomousUntil }}
</div>
<div class="editor-layout"> <div class="editor-layout">
<aside class="control-pad"> <aside class="control-pad">
<div v-if="props.mobile && props.compact" class="mobile-identity legacy-bg1"> <div v-if="props.mobile && props.compact" class="mobile-identity legacy-bg1">
@@ -640,10 +636,17 @@ const clickOutsideMenu = (event: Event) => {
<div <div
v-for="row in displayRows" v-for="row in displayRows"
:key="row.index" :key="row.index"
:title=" :title="row.autonomous ? undefined : rowLabel(row)"
row.autonomous :data-autorun-tooltip="
? `${rowLabel(row)} · 자율 행동${props.autonomousUntil ? ` (${props.autonomousUntil})` : ''}` row.autonomous && props.autonomousUntil
: rowLabel(row) ? `자율 행동: ${props.autonomousUntil}`
: undefined
"
:tabindex="row.autonomous && props.autonomousUntil ? 0 : undefined"
:aria-label="
row.autonomous && props.autonomousUntil
? `${rowLabel(row)}. 자율 행동: ${props.autonomousUntil}`
: undefined
" "
:class="{ autonomous: row.autonomous }" :class="{ autonomous: row.autonomous }"
> >
@@ -887,15 +890,6 @@ const clickOutsideMenu = (event: Event) => {
display: grid; display: grid;
grid-template-columns: 75px 40px minmax(0, 1fr) 38px; grid-template-columns: 75px 40px minmax(0, 1fr) 38px;
} }
.autorun-status {
padding: 3px 6px;
border-bottom: 1px solid #2d6574;
background: #102c35;
color: #aaffff;
font-size: 0.78rem;
line-height: 1.35;
text-align: center;
}
.queue-grid.advanced { .queue-grid.advanced {
grid-template-columns: 34px 75px 40px minmax(0, 1fr); grid-template-columns: 34px 75px 40px minmax(0, 1fr);
} }
@@ -965,6 +959,52 @@ const clickOutsideMenu = (event: Event) => {
.action-column > div.autonomous { .action-column > div.autonomous {
color: #aaffff; color: #aaffff;
} }
.action-column > div.autonomous[data-autorun-tooltip] {
position: relative;
overflow: visible;
outline-offset: -2px;
}
.action-column > div.autonomous[data-autorun-tooltip] > span {
min-width: 0;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
}
.action-column > div.autonomous[data-autorun-tooltip]::after {
content: attr(data-autorun-tooltip);
position: absolute;
z-index: 30;
right: 0;
bottom: calc(100% + 3px);
box-sizing: border-box;
width: min(200px, calc(100vw - 16px));
padding: 5px 7px;
border: 1px solid #2d6574;
border-radius: 3px;
background: #102c35;
box-shadow: 0 3px 8px #000;
color: #aaffff;
font-size: 0.78rem;
font-weight: 400;
line-height: 1.35;
text-align: left;
white-space: normal;
opacity: 0;
visibility: hidden;
pointer-events: none;
transition:
opacity 80ms ease-out,
visibility 80ms ease-out;
}
.action-column > div.autonomous[data-autorun-tooltip]:hover,
.action-column > div.autonomous[data-autorun-tooltip]:focus {
z-index: 25;
}
.action-column > div.autonomous[data-autorun-tooltip]:hover::after,
.action-column > div.autonomous[data-autorun-tooltip]:focus::after {
opacity: 1;
visibility: visible;
}
.action-column small { .action-column small {
font-size: 0.72em; font-size: 0.72em;
line-height: 1; line-height: 1;