Merge remote-tracking branch 'origin/main' into feat/ref-nation-card-20260813
This commit is contained in:
@@ -590,6 +590,8 @@ const persistArtifact = async (page: Page, name: string) => {
|
|||||||
executionStatus: describe('.execution-status'),
|
executionStatus: describe('.execution-status'),
|
||||||
tournamentStatus: describe('.tournament-status'),
|
tournamentStatus: describe('.tournament-status'),
|
||||||
voteStatus: describe('.vote-status'),
|
voteStatus: describe('.vote-status'),
|
||||||
|
autoRefresh: describe('[data-bottom-menu="auto-refresh"]'),
|
||||||
|
manualRefresh: describe('[data-bottom-menu="manual-refresh"]'),
|
||||||
commandMenu: describe('.reserved-command-editor details[open] .menu-items'),
|
commandMenu: describe('.reserved-command-editor details[open] .menu-items'),
|
||||||
commandDividers: [
|
commandDividers: [
|
||||||
...document.querySelectorAll<HTMLElement>('.reserved-command-editor details[open] .menu-divider'),
|
...document.querySelectorAll<HTMLElement>('.reserved-command-editor details[open] .menu-divider'),
|
||||||
@@ -1515,6 +1517,7 @@ test('mobile single document refreshes once and preserves tokens on lobby return
|
|||||||
generalMeCalls: 0,
|
generalMeCalls: 0,
|
||||||
operations: [],
|
operations: [],
|
||||||
};
|
};
|
||||||
|
await installRealtimeHarness(page);
|
||||||
await installFixture(page, state);
|
await installFixture(page, state);
|
||||||
await page.setViewportSize({ width: 500, height: 900 });
|
await page.setViewportSize({ width: 500, height: 900 });
|
||||||
await waitForMain(page);
|
await waitForMain(page);
|
||||||
@@ -1537,9 +1540,65 @@ test('mobile single document refreshes once and preserves tokens on lobby return
|
|||||||
await expect(page.locator(selector)).toBeVisible();
|
await expect(page.locator(selector)).toBeVisible();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const autoRefresh = page.getByRole('button', { name: '자동 갱신 ON' });
|
||||||
|
const manualRefresh = page.getByRole('button', { name: '직접 갱신' });
|
||||||
|
await expect(autoRefresh).toHaveAttribute('aria-pressed', 'true');
|
||||||
|
await expect(autoRefresh.locator('strong')).toHaveCSS('color', 'rgb(158, 240, 184)');
|
||||||
|
await expect(manualRefresh).toHaveAttribute('aria-busy', 'false');
|
||||||
|
await expect
|
||||||
|
.poll(() => page.evaluate(() => (window as unknown as { __hasMainRealtime: () => boolean }).__hasMainRealtime()))
|
||||||
|
.toBe(true);
|
||||||
|
|
||||||
|
const refreshGeometry = await page.locator('.bottom-refresh-controls').evaluate((controls) => {
|
||||||
|
const auto = controls.querySelector<HTMLElement>('[data-bottom-menu="auto-refresh"]');
|
||||||
|
const manual = controls.querySelector<HTMLElement>('[data-bottom-menu="manual-refresh"]');
|
||||||
|
if (!auto || !manual) throw new Error('mobile refresh controls are incomplete');
|
||||||
|
const controlsRect = controls.getBoundingClientRect();
|
||||||
|
const autoRect = auto.getBoundingClientRect();
|
||||||
|
const manualRect = manual.getBoundingClientRect();
|
||||||
|
return {
|
||||||
|
controls: { left: controlsRect.left, right: controlsRect.right, width: controlsRect.width },
|
||||||
|
auto: { left: autoRect.left, right: autoRect.right, width: autoRect.width },
|
||||||
|
manual: { left: manualRect.left, right: manualRect.right, width: manualRect.width },
|
||||||
|
overflow: controls.scrollWidth - controls.clientWidth,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
expect(refreshGeometry.controls.width).toBe(125);
|
||||||
|
expect(refreshGeometry.auto.width).toBe(85);
|
||||||
|
expect(refreshGeometry.manual.width).toBe(40);
|
||||||
|
expect(refreshGeometry.auto.left).toBe(refreshGeometry.controls.left);
|
||||||
|
expect(refreshGeometry.auto.right).toBe(refreshGeometry.manual.left);
|
||||||
|
expect(refreshGeometry.manual.right).toBe(refreshGeometry.controls.right);
|
||||||
|
expect(refreshGeometry.overflow).toBeLessThanOrEqual(0);
|
||||||
|
|
||||||
|
await autoRefresh.focus();
|
||||||
|
await expect(autoRefresh).toBeFocused();
|
||||||
|
await autoRefresh.hover();
|
||||||
|
await expect(autoRefresh).toHaveCSS('filter', 'brightness(1.14)');
|
||||||
|
await autoRefresh.click();
|
||||||
|
const disabledAutoRefresh = page.getByRole('button', { name: '자동 갱신 OFF' });
|
||||||
|
await expect(disabledAutoRefresh).toHaveAttribute('aria-pressed', 'false');
|
||||||
|
await expect(disabledAutoRefresh.locator('strong')).toHaveCSS('color', 'rgb(187, 187, 187)');
|
||||||
|
await expect
|
||||||
|
.poll(() => page.evaluate(() => (window as unknown as { __hasMainRealtime: () => boolean }).__hasMainRealtime()))
|
||||||
|
.toBe(false);
|
||||||
|
await persistArtifact(page, `${basePath.slice(1)}-mobile-auto-refresh-controls-off`);
|
||||||
|
|
||||||
|
state.generalName = '직접갱신된장수';
|
||||||
const callsBeforeRefresh = state.generalMeCalls;
|
const callsBeforeRefresh = state.generalMeCalls;
|
||||||
await page.getByRole('button', { name: '갱 신' }).click();
|
await manualRefresh.click();
|
||||||
await expect.poll(() => state.generalMeCalls).toBeGreaterThan(callsBeforeRefresh);
|
await expect.poll(() => state.generalMeCalls).toBeGreaterThan(callsBeforeRefresh);
|
||||||
|
await expect(page.locator('.general-title')).toContainText('직접갱신된장수');
|
||||||
|
|
||||||
|
const callsBeforeEnable = state.generalMeCalls;
|
||||||
|
await page.getByRole('button', { name: '자동 갱신 OFF' }).click();
|
||||||
|
await expect(page.getByRole('button', { name: '자동 갱신 ON' })).toHaveAttribute('aria-pressed', 'true');
|
||||||
|
await expect.poll(() => state.generalMeCalls).toBeGreaterThan(callsBeforeEnable);
|
||||||
|
await expect
|
||||||
|
.poll(() => page.evaluate(() => (window as unknown as { __hasMainRealtime: () => boolean }).__hasMainRealtime()))
|
||||||
|
.toBe(true);
|
||||||
|
|
||||||
|
await persistArtifact(page, `${basePath.slice(1)}-mobile-auto-refresh-controls`);
|
||||||
|
|
||||||
await page.evaluate(() => {
|
await page.evaluate(() => {
|
||||||
localStorage.setItem('sammo-session-token', 'session_navigation');
|
localStorage.setItem('sammo-session-token', 'session_navigation');
|
||||||
|
|||||||
@@ -18,10 +18,13 @@ const props = defineProps<{
|
|||||||
tournamentStage: number;
|
tournamentStage: number;
|
||||||
nationColor: string;
|
nationColor: string;
|
||||||
npcMode: number;
|
npcMode: number;
|
||||||
|
realtimeEnabled: boolean;
|
||||||
|
refreshing: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
refresh: [];
|
refresh: [];
|
||||||
|
toggleRealtime: [];
|
||||||
lobby: [];
|
lobby: [];
|
||||||
quick: [item: QuickNavigationItem];
|
quick: [item: QuickNavigationItem];
|
||||||
}>();
|
}>();
|
||||||
@@ -189,14 +192,31 @@ const onQuick = (item: QuickNavigationItem) => {
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<div class="bottom-refresh-controls">
|
||||||
class="bottom-trigger refresh-trigger"
|
<button
|
||||||
type="button"
|
class="bottom-trigger auto-refresh-trigger"
|
||||||
data-bottom-menu="refresh"
|
:class="{ active: realtimeEnabled }"
|
||||||
@click="emit('refresh')"
|
type="button"
|
||||||
>
|
data-bottom-menu="auto-refresh"
|
||||||
갱신
|
:aria-pressed="realtimeEnabled"
|
||||||
</button>
|
@click="emit('toggleRealtime')"
|
||||||
|
>
|
||||||
|
<span>자동 갱신</span>
|
||||||
|
<strong>{{ realtimeEnabled ? 'ON' : 'OFF' }}</strong>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="bottom-trigger manual-refresh-trigger"
|
||||||
|
type="button"
|
||||||
|
data-bottom-menu="manual-refresh"
|
||||||
|
aria-label="직접 갱신"
|
||||||
|
title="직접 갱신"
|
||||||
|
:disabled="refreshing"
|
||||||
|
:aria-busy="refreshing"
|
||||||
|
@click="emit('refresh')"
|
||||||
|
>
|
||||||
|
<span aria-hidden="true">↻</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -221,6 +241,17 @@ const onQuick = (item: QuickNavigationItem) => {
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bottom-refresh-controls {
|
||||||
|
display: grid;
|
||||||
|
width: 125px;
|
||||||
|
height: 45px;
|
||||||
|
grid-template-columns: minmax(0, 1fr) 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-refresh-controls > .bottom-trigger {
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.bottom-trigger {
|
.bottom-trigger {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
width: 125px;
|
width: 125px;
|
||||||
@@ -245,6 +276,43 @@ const onQuick = (item: QuickNavigationItem) => {
|
|||||||
background: #212529;
|
background: #212529;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.auto-refresh-trigger {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 2px 1px;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.15;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auto-refresh-trigger strong {
|
||||||
|
color: #bbb;
|
||||||
|
font-size: 11px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auto-refresh-trigger.active {
|
||||||
|
background-color: #164f2c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auto-refresh-trigger.active strong {
|
||||||
|
color: #9ef0b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.manual-refresh-trigger {
|
||||||
|
padding: 0;
|
||||||
|
background: #212529;
|
||||||
|
font-size: 22px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.manual-refresh-trigger:disabled {
|
||||||
|
cursor: wait;
|
||||||
|
filter: grayscale(0.6);
|
||||||
|
opacity: 0.55;
|
||||||
|
}
|
||||||
|
|
||||||
.bottom-trigger:hover,
|
.bottom-trigger:hover,
|
||||||
.bottom-trigger:focus-visible,
|
.bottom-trigger:focus-visible,
|
||||||
.bottom-trigger[aria-expanded='true'] {
|
.bottom-trigger[aria-expanded='true'] {
|
||||||
|
|||||||
@@ -455,7 +455,10 @@ watch(
|
|||||||
:tournament-stage="tournamentStage"
|
:tournament-stage="tournamentStage"
|
||||||
:nation-color="nationColor"
|
:nation-color="nationColor"
|
||||||
:npc-mode="npcMode"
|
:npc-mode="npcMode"
|
||||||
|
:realtime-enabled="realtimeEnabled"
|
||||||
|
:refreshing="refreshing"
|
||||||
@refresh="loadMainData"
|
@refresh="loadMainData"
|
||||||
|
@toggle-realtime="dashboard.setRealtimeEnabled(!realtimeEnabled)"
|
||||||
@lobby="moveLobby"
|
@lobby="moveLobby"
|
||||||
@quick="moveQuick"
|
@quick="moveQuick"
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user