feat: 모바일 메인 패널 순서 설정 추가
This commit is contained in:
@@ -1231,7 +1231,9 @@ test('내 정보&설정의 지난 플레이는 기본 탐색과 분리되어 오
|
||||
const actions = element.querySelector<HTMLElement>('.title-actions')!.getBoundingClientRect();
|
||||
const navigation = element.querySelector<HTMLElement>('.navigation-actions')!.getBoundingClientRect();
|
||||
const back = element.querySelector<HTMLAnchorElement>('.navigation-actions a')!.getBoundingClientRect();
|
||||
const refresh = element.querySelector<HTMLButtonElement>('.navigation-actions button')!.getBoundingClientRect();
|
||||
const refresh = element
|
||||
.querySelector<HTMLButtonElement>('.navigation-actions button')!
|
||||
.getBoundingClientRect();
|
||||
const past = element.querySelector<HTMLAnchorElement>('.past-plays-link')!.getBoundingClientRect();
|
||||
const pastStyle = getComputedStyle(element.querySelector<HTMLAnchorElement>('.past-plays-link')!);
|
||||
return {
|
||||
@@ -1269,6 +1271,77 @@ test('내 정보&설정의 지난 플레이는 기본 탐색과 분리되어 오
|
||||
}
|
||||
});
|
||||
|
||||
test('내 정보&설정에서 모바일 메인 패널을 드래그하거나 버튼으로 재정렬하고 Ref 순서로 복원한다', async ({ page }) => {
|
||||
const state: FixtureState = { permission: 'head', myset: 3, settingMutations: [], accessPages: [] };
|
||||
await install(page, state);
|
||||
await page.setViewportSize({ width: 390, height: 844 });
|
||||
await page.goto('my-page');
|
||||
|
||||
await page.getByRole('button', { name: '순서 바꾸기', exact: true }).click();
|
||||
const dialog = page.getByRole('dialog', { name: '모바일 레이아웃 순서 바꾸기' });
|
||||
await expect(dialog).toBeVisible();
|
||||
const readOrder = () =>
|
||||
dialog
|
||||
.locator('[data-mobile-layout-id]')
|
||||
.evaluateAll((elements) => elements.map((element) => element.getAttribute('data-mobile-layout-id')));
|
||||
const defaultOrder = [
|
||||
'commands',
|
||||
'nation-menu',
|
||||
'nation',
|
||||
'general',
|
||||
'city',
|
||||
'map',
|
||||
'records',
|
||||
'global-menu',
|
||||
'messages',
|
||||
];
|
||||
await expect.poll(readOrder).toEqual(defaultOrder);
|
||||
|
||||
await dialog
|
||||
.locator('[data-mobile-layout-id="messages"]')
|
||||
.dragTo(dialog.locator('[data-mobile-layout-id="commands"]'));
|
||||
await expect
|
||||
.poll(readOrder)
|
||||
.toEqual(['messages', 'commands', 'nation-menu', 'nation', 'general', 'city', 'map', 'records', 'global-menu']);
|
||||
await dialog.getByRole('button', { name: '지도 위로' }).click();
|
||||
await expect
|
||||
.poll(readOrder)
|
||||
.toEqual(['messages', 'commands', 'nation-menu', 'nation', 'general', 'map', 'city', 'records', 'global-menu']);
|
||||
|
||||
const dialogGeometry = await dialog.evaluate((element) => {
|
||||
const rect = element.getBoundingClientRect();
|
||||
const firstItem = element.querySelector<HTMLElement>('[data-mobile-layout-id]')?.getBoundingClientRect();
|
||||
const moveButton = element.querySelector<HTMLButtonElement>('[aria-label$="아래로"]')?.getBoundingClientRect();
|
||||
return {
|
||||
rect: rect.toJSON(),
|
||||
firstItem: firstItem?.toJSON() ?? null,
|
||||
moveButton: moveButton?.toJSON() ?? null,
|
||||
overflowX: getComputedStyle(element).overflowX,
|
||||
documentWidth: document.documentElement.scrollWidth,
|
||||
};
|
||||
});
|
||||
expect(dialogGeometry.rect.left).toBeGreaterThanOrEqual(0);
|
||||
expect(dialogGeometry.rect.right).toBeLessThanOrEqual(390);
|
||||
expect(dialogGeometry.firstItem?.height).toBeGreaterThanOrEqual(44);
|
||||
expect(dialogGeometry.moveButton?.width).toBeGreaterThanOrEqual(36);
|
||||
expect(dialogGeometry.documentWidth).toBe(390);
|
||||
await persistParityArtifact(page, 'core-my-page-mobile-layout-order-dialog', dialogGeometry);
|
||||
|
||||
await dialog.getByRole('button', { name: '적용', exact: true }).click();
|
||||
await expect(dialog).toBeHidden();
|
||||
await expect
|
||||
.poll(() => page.evaluate(() => JSON.parse(localStorage.getItem('sam.mobileMainPanelOrder.v1') ?? '[]')))
|
||||
.toEqual(['messages', 'commands', 'nation-menu', 'nation', 'general', 'map', 'city', 'records', 'global-menu']);
|
||||
|
||||
await page.getByRole('button', { name: '순서 바꾸기', exact: true }).click();
|
||||
await dialog.getByRole('button', { name: 'Ref 초깃값' }).click();
|
||||
await expect.poll(readOrder).toEqual(defaultOrder);
|
||||
await dialog.getByRole('button', { name: '적용', exact: true }).click();
|
||||
await expect
|
||||
.poll(() => page.evaluate(() => JSON.parse(localStorage.getItem('sam.mobileMainPanelOrder.v1') ?? '[]')))
|
||||
.toEqual(defaultOrder);
|
||||
});
|
||||
|
||||
for (const [label, failure] of [
|
||||
['daemon timeout', 'TIMEOUT'],
|
||||
['engine transaction 오류', 'INTERNAL_SERVER_ERROR'],
|
||||
|
||||
@@ -95,11 +95,10 @@ type DashboardBundleInput = {
|
||||
const operationInput = (route: Route, index: number): DashboardBundleInput => {
|
||||
const request = route.request();
|
||||
const queryInput = new URL(request.url()).searchParams.get('input');
|
||||
const parsed = (request.postData()
|
||||
? request.postDataJSON()
|
||||
: queryInput
|
||||
? JSON.parse(queryInput)
|
||||
: {}) as Record<string, unknown>;
|
||||
const parsed = (request.postData() ? request.postDataJSON() : queryInput ? JSON.parse(queryInput) : {}) as Record<
|
||||
string,
|
||||
unknown
|
||||
>;
|
||||
const entry = (parsed[String(index)] ?? parsed) as { json?: DashboardBundleInput };
|
||||
return entry.json ?? (entry as DashboardBundleInput);
|
||||
};
|
||||
@@ -253,32 +252,32 @@ const commandTableFixture = (large: boolean, blockedCount = 0, refCategories = f
|
||||
general: draftCommands
|
||||
? draftCommandGroups
|
||||
: refCategories
|
||||
? refCommandCategoryFixture
|
||||
: large
|
||||
? ['내정', '군사', '계략'].map((category, categoryIndex) => ({
|
||||
category,
|
||||
values: Array.from({ length: 16 }, (_, localIndex) => {
|
||||
const index = categoryIndex * 16 + localIndex;
|
||||
return {
|
||||
key: `command-${index}`,
|
||||
name: index === 0 ? '주민 선정과 장기 도시 개발' : `명령 ${index}`,
|
||||
reqArg: index % 2 === 0,
|
||||
possible: index >= blockedCount,
|
||||
status: index >= blockedCount ? 'available' : 'blocked',
|
||||
inputFields: [
|
||||
{
|
||||
key: 'amount',
|
||||
label: '수량',
|
||||
kind: 'number',
|
||||
required: true,
|
||||
min: 1,
|
||||
max: 10_000,
|
||||
},
|
||||
],
|
||||
};
|
||||
}),
|
||||
}))
|
||||
: [],
|
||||
? refCommandCategoryFixture
|
||||
: large
|
||||
? ['내정', '군사', '계략'].map((category, categoryIndex) => ({
|
||||
category,
|
||||
values: Array.from({ length: 16 }, (_, localIndex) => {
|
||||
const index = categoryIndex * 16 + localIndex;
|
||||
return {
|
||||
key: `command-${index}`,
|
||||
name: index === 0 ? '주민 선정과 장기 도시 개발' : `명령 ${index}`,
|
||||
reqArg: index % 2 === 0,
|
||||
possible: index >= blockedCount,
|
||||
status: index >= blockedCount ? 'available' : 'blocked',
|
||||
inputFields: [
|
||||
{
|
||||
key: 'amount',
|
||||
label: '수량',
|
||||
kind: 'number',
|
||||
required: true,
|
||||
min: 1,
|
||||
max: 10_000,
|
||||
},
|
||||
],
|
||||
};
|
||||
}),
|
||||
}))
|
||||
: [],
|
||||
nation: [],
|
||||
inputOptions: {
|
||||
cities: Array.from({ length: 20 }, (_, index) => ({ value: index + 1, label: `도시 ${index + 1}` })),
|
||||
@@ -500,7 +499,7 @@ const installFixture = async (page: Page, state: NavigationFixture) => {
|
||||
? response({ id: 'user-7', username: 'menu-user', displayName: '메뉴 사용자' })
|
||||
: operation === 'navigation.get'
|
||||
? response(runtimeNavigation)
|
||||
: response({ ok: true })
|
||||
: response({ ok: true })
|
||||
);
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
@@ -1558,10 +1557,7 @@ test('message targets keep reply behavior and use nation-color contrast in label
|
||||
|
||||
await page.setViewportSize({ width: 500, height: 900 });
|
||||
const mobilePanel = page.locator('.mobile-message-panel');
|
||||
await expect(mobilePanel.locator('.msg-plate[data-id="101"] .msg-target')).toHaveCSS(
|
||||
'color',
|
||||
'rgb(255, 255, 255)'
|
||||
);
|
||||
await expect(mobilePanel.locator('.msg-plate[data-id="101"] .msg-target')).toHaveCSS('color', 'rgb(255, 255, 255)');
|
||||
await expect(mobilePanel.locator('.msg-plate[data-id="103"] .msg-target')).toHaveCSS('color', 'rgb(0, 0, 0)');
|
||||
await expect(mobilePanel.locator('#mailbox_list optgroup[label="밝은국"]')).toHaveCSS('color', 'rgb(0, 0, 0)');
|
||||
await persistArtifact(page, `${basePath.slice(1)}-message-nation-contrast-mobile-500`);
|
||||
@@ -2217,6 +2213,49 @@ test('the 939/940 boundary switches to the Ref-style 500px single document', asy
|
||||
]) {
|
||||
await expect(page.locator(selector)).toBeVisible();
|
||||
}
|
||||
const readMobilePanelOrder = () =>
|
||||
page
|
||||
.locator('.layout-mobile > [data-mobile-panel-id]')
|
||||
.evaluateAll((elements) => elements.map((element) => element.getAttribute('data-mobile-panel-id')));
|
||||
await expect
|
||||
.poll(readMobilePanelOrder)
|
||||
.toEqual(['commands', 'nation-menu', 'nation', 'general', 'city', 'map', 'records', 'global-menu', 'messages']);
|
||||
await page.evaluate(() => {
|
||||
localStorage.setItem(
|
||||
'sam.mobileMainPanelOrder.v1',
|
||||
JSON.stringify([
|
||||
'messages',
|
||||
'map',
|
||||
'commands',
|
||||
'nation-menu',
|
||||
'nation',
|
||||
'general',
|
||||
'city',
|
||||
'records',
|
||||
'global-menu',
|
||||
])
|
||||
);
|
||||
document.dispatchEvent(new CustomEvent('sam-mobile-main-panel-order-changed'));
|
||||
});
|
||||
await expect
|
||||
.poll(readMobilePanelOrder)
|
||||
.toEqual(['messages', 'map', 'commands', 'nation-menu', 'nation', 'general', 'city', 'records', 'global-menu']);
|
||||
const customPanelGeometry = await page.locator('.layout-mobile > [data-mobile-panel-id]').evaluateAll((elements) =>
|
||||
elements.map((element) => {
|
||||
const rect = element.getBoundingClientRect();
|
||||
return {
|
||||
id: element.getAttribute('data-mobile-panel-id'),
|
||||
top: rect.top,
|
||||
bottom: rect.bottom,
|
||||
left: rect.left,
|
||||
right: rect.right,
|
||||
};
|
||||
})
|
||||
);
|
||||
expect(customPanelGeometry.every(({ left, right }) => left >= 0 && right <= 500)).toBe(true);
|
||||
expect(
|
||||
customPanelGeometry.every((panel, index) => index === 0 || panel.top >= customPanelGeometry[index - 1]!.bottom)
|
||||
).toBe(true);
|
||||
await persistArtifact(page, `${basePath.slice(1)}-mobile-500`);
|
||||
});
|
||||
|
||||
@@ -3040,9 +3079,9 @@ for (const viewport of [
|
||||
await refreshActivityAndCommands();
|
||||
await expect(picker.getByLabel('장비 종류', { exact: true })).toHaveValue('weapon');
|
||||
await expect(picker.getByLabel('장비', { exact: true })).toHaveValue('청룡언월도');
|
||||
await expect.poll(() => page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual(
|
||||
viewport.width
|
||||
);
|
||||
await expect
|
||||
.poll(() => page.evaluate(() => document.documentElement.scrollWidth))
|
||||
.toBeLessThanOrEqual(viewport.width);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user