fix: 세력일람에 군주 현재 위치를 복원
영토가 없는 세력도 전체 도시 사전에서 군주의 위치를 찾아 Ref와 같은 현재 위치를 표시한다. API와 Chromium 회귀 테스트를 추가한다.
This commit is contained in:
@@ -134,6 +134,7 @@ export const getNationDirectory = authedProcedure.query(async ({ ctx }) => {
|
|||||||
generalsByNation.set(general.nationId, list);
|
generalsByNation.set(general.nationId, list);
|
||||||
}
|
}
|
||||||
const citiesByNation = new Map<number, typeof cities>();
|
const citiesByNation = new Map<number, typeof cities>();
|
||||||
|
const cityNameById = new Map(cities.map((city) => [city.id, city.name] as const));
|
||||||
for (const city of cities) {
|
for (const city of cities) {
|
||||||
const list = citiesByNation.get(city.nationId) ?? [];
|
const list = citiesByNation.get(city.nationId) ?? [];
|
||||||
list.push(city);
|
list.push(city);
|
||||||
@@ -153,6 +154,7 @@ export const getNationDirectory = authedProcedure.query(async ({ ctx }) => {
|
|||||||
: null,
|
: null,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
const ruler = officers.find((officer) => officer.officerLevel === 12)?.general;
|
||||||
const secretPermissions = nationGenerals.map((general) => ({
|
const secretPermissions = nationGenerals.map((general) => ({
|
||||||
general,
|
general,
|
||||||
permission: resolveSecretPermission(
|
permission: resolveSecretPermission(
|
||||||
@@ -178,6 +180,7 @@ export const getNationDirectory = authedProcedure.query(async ({ ctx }) => {
|
|||||||
},
|
},
|
||||||
power: readMetaNumber(nation.meta, 'power'),
|
power: readMetaNumber(nation.meta, 'power'),
|
||||||
capitalCityId: nation.capitalCityId ?? 0,
|
capitalCityId: nation.capitalCityId ?? 0,
|
||||||
|
rulerCityName: ruler ? (cityNameById.get(ruler.cityId) ?? null) : null,
|
||||||
generalCount: readMetaNumber(nation.meta, 'gennum', nationGenerals.length),
|
generalCount: readMetaNumber(nation.meta, 'gennum', nationGenerals.length),
|
||||||
cityCount: nationCities.length,
|
cityCount: nationCities.length,
|
||||||
officers,
|
officers,
|
||||||
|
|||||||
@@ -183,6 +183,11 @@ const createContext = (
|
|||||||
auth?: GameSessionTokenPayload | null;
|
auth?: GameSessionTokenPayload | null;
|
||||||
me?: GeneralRow | null;
|
me?: GeneralRow | null;
|
||||||
isUnited?: number;
|
isUnited?: number;
|
||||||
|
directory?: {
|
||||||
|
nations: typeof nationRows;
|
||||||
|
generals: typeof globalGenerals;
|
||||||
|
cities: Array<{ id: number; name: string; nationId: number }>;
|
||||||
|
};
|
||||||
} = {}
|
} = {}
|
||||||
): { context: GameApiContext; requestCommand: ReturnType<typeof vi.fn> } => {
|
): { context: GameApiContext; requestCommand: ReturnType<typeof vi.fn> } => {
|
||||||
const token = options.auth === undefined ? auth() : options.auth;
|
const token = options.auth === undefined ? auth() : options.auth;
|
||||||
@@ -192,17 +197,20 @@ const createContext = (
|
|||||||
const db = {
|
const db = {
|
||||||
general: {
|
general: {
|
||||||
findFirst: vi.fn(async () => me),
|
findFirst: vi.fn(async () => me),
|
||||||
findMany: vi.fn(async () => globalGenerals),
|
findMany: vi.fn(async () => options.directory?.generals ?? globalGenerals),
|
||||||
},
|
},
|
||||||
nation: {
|
nation: {
|
||||||
findMany: vi.fn(async () => nationRows),
|
findMany: vi.fn(async () => options.directory?.nations ?? nationRows),
|
||||||
},
|
},
|
||||||
city: {
|
city: {
|
||||||
findMany: vi.fn(async () => [
|
findMany: vi.fn(
|
||||||
{ id: 1, name: '허창', nationId: 1 },
|
async () =>
|
||||||
{ id: 2, name: '성도', nationId: 2 },
|
options.directory?.cities ?? [
|
||||||
{ id: 3, name: '낙양', nationId: 0 },
|
{ id: 1, name: '허창', nationId: 1 },
|
||||||
]),
|
{ id: 2, name: '성도', nationId: 2 },
|
||||||
|
{ id: 3, name: '낙양', nationId: 0 },
|
||||||
|
]
|
||||||
|
),
|
||||||
},
|
},
|
||||||
generalAccessLog: {
|
generalAccessLog: {
|
||||||
findMany: vi.fn(async () => [
|
findMany: vi.fn(async () => [
|
||||||
@@ -307,6 +315,43 @@ describe('legacy global nation/general directories', () => {
|
|||||||
expect(result[2]).toMatchObject({ name: '재 야', generalCount: 1, cityCount: 1 });
|
expect(result[2]).toMatchObject({ name: '재 야', generalCount: 1, cityCount: 1 });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('projects a level-zero nation location from its ruler city even when that city belongs to another nation', async () => {
|
||||||
|
const roamingNation = {
|
||||||
|
...nationRows[0]!,
|
||||||
|
id: 3,
|
||||||
|
name: '방랑군',
|
||||||
|
capitalCityId: 0,
|
||||||
|
chiefGeneralId: 40,
|
||||||
|
level: 0,
|
||||||
|
meta: { power: 500 },
|
||||||
|
};
|
||||||
|
const roamingRuler = directoryGeneral({
|
||||||
|
id: 40,
|
||||||
|
name: '방랑군주',
|
||||||
|
nationId: 3,
|
||||||
|
cityId: 2,
|
||||||
|
officerLevel: 12,
|
||||||
|
});
|
||||||
|
const result = await appRouter
|
||||||
|
.createCaller(
|
||||||
|
createContext({
|
||||||
|
directory: {
|
||||||
|
nations: [roamingNation],
|
||||||
|
generals: [roamingRuler],
|
||||||
|
cities: [{ id: 2, name: '성도', nationId: 2 }],
|
||||||
|
},
|
||||||
|
}).context
|
||||||
|
)
|
||||||
|
.world.getNationDirectory();
|
||||||
|
|
||||||
|
expect(result[0]).toMatchObject({
|
||||||
|
id: 3,
|
||||||
|
level: 0,
|
||||||
|
cityCount: 0,
|
||||||
|
rulerCityName: '성도',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('implements all legacy sort boundaries and never exposes user ids or raw metadata', async () => {
|
it('implements all legacy sort boundaries and never exposes user ids or raw metadata', async () => {
|
||||||
const caller = appRouter.createCaller(createContext().context);
|
const caller = appRouter.createCaller(createContext().context);
|
||||||
const defaultResult = await caller.world.getGeneralDirectory();
|
const defaultResult = await caller.world.getGeneralDirectory();
|
||||||
|
|||||||
@@ -200,7 +200,8 @@ const install = async (
|
|||||||
mode: 'general' | 'no-general' | 'error-after-load' = 'general',
|
mode: 'general' | 'no-general' | 'error-after-load' = 'general',
|
||||||
accessPages: string[] = [],
|
accessPages: string[] = [],
|
||||||
requestedOperations: string[] = [],
|
requestedOperations: string[] = [],
|
||||||
directoryRows = generals
|
directoryRows = generals,
|
||||||
|
nationDirectoryRows = nationDirectory
|
||||||
) => {
|
) => {
|
||||||
let generalDirectoryCalls = 0;
|
let generalDirectoryCalls = 0;
|
||||||
await page.addInitScript((profile) => {
|
await page.addInitScript((profile) => {
|
||||||
@@ -249,7 +250,7 @@ const install = async (
|
|||||||
return response({ myGeneral: mode === 'no-general' ? null : { id: 1, name: '조회자' } });
|
return response({ myGeneral: mode === 'no-general' ? null : { id: 1, name: '조회자' } });
|
||||||
}
|
}
|
||||||
if (operation === 'join.getConfig') return response({});
|
if (operation === 'join.getConfig') return response({});
|
||||||
if (operation === 'world.getNationDirectory') return response(nationDirectory);
|
if (operation === 'world.getNationDirectory') return response(nationDirectoryRows);
|
||||||
if (operation === 'public.recordAccess') {
|
if (operation === 'public.recordAccess') {
|
||||||
const payload = requestBody?.[String(operationIndex)];
|
const payload = requestBody?.[String(operationIndex)];
|
||||||
const pageName = payload?.json?.page ?? payload?.page;
|
const pageName = payload?.json?.page ?? payload?.page;
|
||||||
@@ -322,6 +323,31 @@ const installAccessBoundary = async (page: Page, accessPages: string[]) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
test('a level-zero nation shows the ruler current city even without territory there', async ({ page }) => {
|
||||||
|
const roamingNation = {
|
||||||
|
...nationDirectory[0]!,
|
||||||
|
id: 3,
|
||||||
|
name: '방랑군',
|
||||||
|
level: 0,
|
||||||
|
capitalCityId: 0,
|
||||||
|
cityCount: 0,
|
||||||
|
officers: Array.from({ length: 8 }, (_, index) => ({
|
||||||
|
officerLevel: 12 - index,
|
||||||
|
general: index === 0 ? { id: 40, name: '방랑군주', npcState: 0, cityId: 2 } : null,
|
||||||
|
})),
|
||||||
|
rulerCityName: '성도',
|
||||||
|
cities: [],
|
||||||
|
generals: [{ id: 40, name: '방랑군주', npcState: 0 }],
|
||||||
|
};
|
||||||
|
await install(page, 'general', [], [], generals, [roamingNation]);
|
||||||
|
|
||||||
|
await page.goto('nation-list');
|
||||||
|
|
||||||
|
const table = page.locator('[data-nation-id="3"]');
|
||||||
|
await expect(table).toContainText('현재 위치 : 성도');
|
||||||
|
await expect(table.locator('.roaming-city')).toHaveCSS('color', 'rgb(255, 255, 0)');
|
||||||
|
});
|
||||||
|
|
||||||
test('PYA-scale general directory mounts only the active responsive layout and defers portraits', async (
|
test('PYA-scale general directory mounts only the active responsive layout and defers portraits', async (
|
||||||
{ page },
|
{ page },
|
||||||
testInfo
|
testInfo
|
||||||
|
|||||||
@@ -51,10 +51,6 @@ const displayAmbassadorName = (nation: Nation, name: string) => {
|
|||||||
return general ? displayGeneralName(general) : name;
|
return general ? displayGeneralName(general) : name;
|
||||||
};
|
};
|
||||||
|
|
||||||
const roamingCityName = (nation: Nation): string => {
|
|
||||||
const chief = officerName(nation, 12);
|
|
||||||
return nation.cities.find((city) => city.id === chief?.cityId)?.name ?? '-';
|
|
||||||
};
|
|
||||||
const closeWindow = () => window.close();
|
const closeWindow = () => window.close();
|
||||||
const officerLevelAt = (row: number, column: number, columns: number) => 13 - ((row - 1) * columns + column);
|
const officerLevelAt = (row: number, column: number, columns: number) => 13 - ((row - 1) * columns + column);
|
||||||
|
|
||||||
@@ -275,7 +271,8 @@ onBeforeUnmount(() => {
|
|||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
<template v-else
|
<template v-else
|
||||||
>현재 위치 : <span class="roaming-city">{{ roamingCityName(nation) }}</span></template
|
>현재 위치 :
|
||||||
|
<span class="roaming-city">{{ nation.rulerCityName ?? '-' }}</span></template
|
||||||
>
|
>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
Reference in New Issue
Block a user