fix: 세력도시 기본 목록을 지역과 규모순으로 정렬한다
This commit is contained in:
1 parent
da79013226
commit
f16d33e34c
2 files changed
+103
-7
No files matched your search
@@ -1,4 +1,7 @@
|
||||
import { expect, test, type Page, type Route } from '@playwright/test';
|
||||
import { writeFile } from 'node:fs/promises';
|
||||
|
||||
import type { trpc } from '../src/utils/trpc.js';
|
||||
|
||||
import { gameProfile, gameTrpcRoute } from './gameTestPaths.js';
|
||||
import { expectLumenButtonStates } from './lumenButton.js';
|
||||
@@ -306,7 +309,11 @@ const personnelFixture = (state: FixtureState) => {
|
||||
};
|
||||
};
|
||||
|
||||
const install = async (page: Page, state: FixtureState): Promise<void> => {
|
||||
const install = async (
|
||||
page: Page,
|
||||
state: FixtureState,
|
||||
overviewCities?: Awaited<ReturnType<typeof trpc.nation.getCityOverview.query>>['cities']
|
||||
): Promise<void> => {
|
||||
await page.addInitScript((profile) => {
|
||||
localStorage.setItem('sammo-game-token', 'ga_city_office');
|
||||
localStorage.setItem('sammo-game-profile', profile);
|
||||
@@ -316,7 +323,10 @@ const install = async (page: Page, state: FixtureState): Promise<void> => {
|
||||
if (operation === 'auth.status') return response({ ok: true });
|
||||
if (operation === 'lobby.info') return response({ myGeneral: { id: 20, name: '순욱' } });
|
||||
if (operation === 'join.getConfig') return response({});
|
||||
if (operation === 'nation.getCityOverview') return response(overviewFixture(state));
|
||||
if (operation === 'nation.getCityOverview') {
|
||||
const overview = overviewFixture(state);
|
||||
return response({ ...overview, cities: overviewCities ?? overview.cities });
|
||||
}
|
||||
if (operation === 'nation.getSecretGeneralList') {
|
||||
return state.secretForbidden
|
||||
? errorResponse(
|
||||
@@ -491,3 +501,88 @@ test('NPC prefixes stay single in city names and linked secret rows', async ({ p
|
||||
await page.screenshot({ path: testInfo.outputPath(`npc-city-prefix-${width}.png`), fullPage: true });
|
||||
}
|
||||
});
|
||||
|
||||
for (const width of [1200, 500]) {
|
||||
test(`세력도시 기본 정렬은 지역별로 묶고 같은 지역 안에서 규모순으로 표시한다 (${width}px)`, async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
const state: FixtureState = { role: 'head', appointed: false, appointmentInputs: [] };
|
||||
const baseCity = overviewFixture(state).cities[0]!;
|
||||
// ID·시세·규모순이 지역 우선 정렬과 서로 다르게 되는 입력이다.
|
||||
const overviewCities = [
|
||||
{ id: 10, name: '허창', region: 2, level: 8, trade: 60 },
|
||||
{ id: 20, name: '상당', region: 1, level: 5, trade: 130 },
|
||||
{ id: 30, name: '진류', region: 2, level: 5, trade: 110 },
|
||||
{ id: 40, name: '업', region: 1, level: 8, trade: 80 },
|
||||
{ id: 50, name: '북평', region: 1, level: 8, trade: 90 },
|
||||
].map((city) => ({ ...baseCity, ...city }));
|
||||
await install(page, state, overviewCities);
|
||||
await page.setViewportSize({ width, height: 900 });
|
||||
await page.goto('nation/cities');
|
||||
const tables = page.locator('.city');
|
||||
await expect(tables).toHaveCount(5);
|
||||
await page.evaluate(() => document.fonts.ready);
|
||||
const cityOrder = () => tables.evaluateAll((elements) => elements.map((el) => Number(el.dataset.cityId)));
|
||||
const regionStarts = () =>
|
||||
page
|
||||
.locator('.city.region-break')
|
||||
.evaluateAll((elements) => elements.map((el) => Number(el.dataset.cityId)));
|
||||
const expectedOrder = [40, 50, 20, 10, 30];
|
||||
const select = page.locator('#nation-city-sort');
|
||||
const submit = page.getByRole('button', { name: '정렬하기' });
|
||||
|
||||
await page.screenshot({ path: testInfo.outputPath('default-region-order.png'), fullPage: true });
|
||||
await writeFile(
|
||||
testInfo.outputPath('default-region-order.html'),
|
||||
await page.locator('main').evaluate((el) => el.outerHTML)
|
||||
);
|
||||
const geometry = await tables.evaluateAll((elements) =>
|
||||
elements.map((el) => {
|
||||
const rect = el.getBoundingClientRect();
|
||||
const style = getComputedStyle(el);
|
||||
return {
|
||||
id: Number(el.dataset.cityId),
|
||||
title: el.querySelector('.city-title')?.textContent?.trim(),
|
||||
x: rect.x,
|
||||
y: rect.y,
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
marginTop: style.marginTop,
|
||||
fontSize: style.fontSize,
|
||||
borderCollapse: style.borderCollapse,
|
||||
};
|
||||
})
|
||||
);
|
||||
await writeFile(
|
||||
testInfo.outputPath('default-region-order.json'),
|
||||
JSON.stringify({ url: page.url(), width, geometry }, null, 2)
|
||||
);
|
||||
await expect(select).toHaveValue('1');
|
||||
expect(await cityOrder()).toEqual(expectedOrder);
|
||||
expect(await regionStarts()).toEqual([40, 10]);
|
||||
expect(geometry.map((city) => city.marginTop)).toEqual(['18px', '0px', '0px', '18px', '0px']);
|
||||
await expect(tables.locator('.city-title')).toContainText(['하북', '하북', '하북', '중원', '중원']);
|
||||
|
||||
// 선택만으로는 적용하지 않고 submit 뒤에만 기존 명시적 정렬을 적용한다.
|
||||
await select.selectOption('12');
|
||||
expect(await cityOrder()).toEqual(expectedOrder);
|
||||
await submit.click();
|
||||
expect(await cityOrder()).toEqual([40, 50, 10, 20, 30]);
|
||||
expect(await regionStarts()).toEqual([]);
|
||||
await page.getByRole('button', { name: '시세 기준 정렬' }).first().click();
|
||||
await expect(select).toHaveValue('10');
|
||||
expect(await cityOrder()).toEqual([20, 30, 50, 40, 10]);
|
||||
expect(await regionStarts()).toEqual([]);
|
||||
for (const sort of ['11', '1']) {
|
||||
await select.selectOption(sort);
|
||||
await submit.click();
|
||||
expect(await cityOrder()).toEqual(expectedOrder);
|
||||
expect(await regionStarts()).toEqual([40, 10]);
|
||||
}
|
||||
await page.reload();
|
||||
await expect(select).toHaveValue('1');
|
||||
await expect(tables).toHaveCount(5);
|
||||
expect(await cityOrder()).toEqual(expectedOrder);
|
||||
expect(state.appointmentInputs).toEqual([]);
|
||||
});
|
||||
}
|
||||
@@ -34,8 +34,8 @@ const integrationError = ref('');
|
||||
const secretLoading = ref(false);
|
||||
const personnelLoading = ref(false);
|
||||
const pendingAppointment = ref('');
|
||||
const sort = ref<Sort>(10);
|
||||
const selectedSort = ref<Sort>(10);
|
||||
const sort = ref<Sort>(1);
|
||||
const selectedSort = ref<Sort>(1);
|
||||
const extraSort = ref<
|
||||
| 'name'
|
||||
| 'populationRate'
|
||||
@@ -95,7 +95,7 @@ const cities = computed(() => {
|
||||
}
|
||||
return values.sort((a, b) => {
|
||||
const key = sort.value;
|
||||
if (key === 1) return a.id - b.id;
|
||||
if (key === 1 || key === 11) return a.region - b.region || b.level - a.level;
|
||||
if (key === 2) return b.population - a.population;
|
||||
if (key === 3) return b.population / b.populationMax - a.population / a.populationMax;
|
||||
if (key === 4) return b.trust - a.trust;
|
||||
@@ -105,7 +105,6 @@ const cities = computed(() => {
|
||||
if (key === 8) return b.defence - a.defence;
|
||||
if (key === 9) return b.wall - a.wall;
|
||||
if (key === 10) return (b.trade ?? -1) - (a.trade ?? -1);
|
||||
if (key === 11) return a.region - b.region || b.level - a.level;
|
||||
return b.level - a.level || a.region - b.region;
|
||||
});
|
||||
});
|
||||
@@ -148,7 +147,9 @@ const developmentClass = (
|
||||
return ratio > 0.8 ? 'development-high' : ratio > 0.4 ? 'development-mid' : 'development-low';
|
||||
};
|
||||
const isRegionBreak = (city: City, index: number) =>
|
||||
sort.value === 10 && extraSort.value === null && (index === 0 || cities.value[index - 1]?.region !== city.region);
|
||||
(sort.value === 1 || sort.value === 11) &&
|
||||
extraSort.value === null &&
|
||||
(index === 0 || cities.value[index - 1]?.region !== city.region);
|
||||
const officer = (city: City, level: 2 | 3 | 4) => city.officers[level]?.name ?? '-';
|
||||
const officerIsStationed = (city: City, level: OfficerLevel): boolean =>
|
||||
secretData.value !== null && city.officers[level]?.cityId === city.id;
|
||||
|
||||
Reference in new issue
Block a user