남은 게임 화면에서 NPC 이름 접두사가 중복되지 않도록 수정
This commit is contained in:
@@ -847,7 +847,8 @@ const install = async (
|
|||||||
clockRunning: boolean;
|
clockRunning: boolean;
|
||||||
clockRecovery: { startsAt: string; endsAt: string } | null;
|
clockRecovery: { startsAt: string; endsAt: string } | null;
|
||||||
turnEngineRunning: boolean;
|
turnEngineRunning: boolean;
|
||||||
}
|
},
|
||||||
|
decoratedNpcChiefs = false
|
||||||
) => {
|
) => {
|
||||||
const requests: unknown[] = [];
|
const requests: unknown[] = [];
|
||||||
const currentGeneralContext = {
|
const currentGeneralContext = {
|
||||||
@@ -982,7 +983,18 @@ const install = async (
|
|||||||
turnTime: '2026-09-10T01:20:00Z',
|
turnTime: '2026-09-10T01:20:00Z',
|
||||||
})),
|
})),
|
||||||
}
|
}
|
||||||
: chiefCenter
|
: decoratedNpcChiefs
|
||||||
|
? {
|
||||||
|
...chiefCenter,
|
||||||
|
chiefs: chiefCenter.chiefs.map((chief, index) => ({
|
||||||
|
...chief,
|
||||||
|
name: ['ⓖ의병', 'ⓤ중립', 'ⓞ특수', 'ⓧ기타', '㉥부대장', 'ⓜ인재', 'ⓝ장수', '사용자'][
|
||||||
|
index
|
||||||
|
]!,
|
||||||
|
npcState: [4, 6, 9, 7, 5, 3, 2, 0][index]!,
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
: chiefCenter
|
||||||
);
|
);
|
||||||
if (name === 'turns.reserved.getGeneral')
|
if (name === 'turns.reserved.getGeneral')
|
||||||
return response({ turns: generalTurns, revision: generalRevision, autorunLimit: 2403 });
|
return response({ turns: generalTurns, revision: generalRevision, autorunLimit: 2403 });
|
||||||
@@ -3554,3 +3566,38 @@ for (const width of [1200, 500]) {
|
|||||||
await expect(clock).toBeDisabled();
|
await expect(clock).toBeDisabled();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test('NPC prefixes stay single in chief command cards', async ({ page }, testInfo) => {
|
||||||
|
await install(page, false, commandTable, 1, undefined, true);
|
||||||
|
for (const width of [1200, 500]) {
|
||||||
|
await page.setViewportSize({ width, height: 900 });
|
||||||
|
await page.goto(gamePath('/chief-center'));
|
||||||
|
const names = page.locator('.chief-card .chief-name, .chief-card .compact-name');
|
||||||
|
// Desktop uses the editor for the current officer; mobile includes that officer in the overview.
|
||||||
|
await expect(names).toHaveText([
|
||||||
|
'ⓖ의병',
|
||||||
|
'ⓤ중립',
|
||||||
|
'ⓞ특수',
|
||||||
|
'ⓧ기타',
|
||||||
|
'㉥부대장',
|
||||||
|
'ⓜ인재',
|
||||||
|
'ⓝ장수',
|
||||||
|
...(width === 500 ? ['사용자'] : []),
|
||||||
|
]);
|
||||||
|
await page.evaluate(() => document.fonts.ready);
|
||||||
|
await testInfo.attach(`npc-prefix-dom-${width}`, {
|
||||||
|
contentType: 'application/json',
|
||||||
|
body: JSON.stringify(
|
||||||
|
await page.locator('.chief-card').evaluateAll((elements) =>
|
||||||
|
elements.map((element) => ({
|
||||||
|
html: element.outerHTML,
|
||||||
|
rect: element.getBoundingClientRect().toJSON(),
|
||||||
|
fontSize: getComputedStyle(element).fontSize,
|
||||||
|
color: getComputedStyle(element).color,
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
),
|
||||||
|
});
|
||||||
|
await page.screenshot({ path: testInfo.outputPath(`npc-chief-prefix-${width}.png`), fullPage: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ const operationNames = (route: Route) =>
|
|||||||
decodeURIComponent(new URL(route.request().url()).pathname.split('/trpc/')[1] ?? '').split(',');
|
decodeURIComponent(new URL(route.request().url()).pathname.split('/trpc/')[1] ?? '').split(',');
|
||||||
|
|
||||||
type NavigationFixture = {
|
type NavigationFixture = {
|
||||||
|
decoratedNpcChiefs?: boolean;
|
||||||
officerLevel: number;
|
officerLevel: number;
|
||||||
permission: number;
|
permission: number;
|
||||||
nationLevel: number;
|
nationLevel: number;
|
||||||
@@ -546,8 +547,16 @@ const generalContext = (state: NavigationFixture) => ({
|
|||||||
techLevel: 0,
|
techLevel: 0,
|
||||||
techLimited: false,
|
techLimited: false,
|
||||||
topChiefs: {
|
topChiefs: {
|
||||||
12: { id: 1, name: '군주', npcState: 0 },
|
12: {
|
||||||
11: { id: 2, name: '참모', npcState: 1 },
|
id: 1,
|
||||||
|
name: state.decoratedNpcChiefs ? 'ⓖ의병군주' : '군주',
|
||||||
|
npcState: state.decoratedNpcChiefs ? 4 : 0,
|
||||||
|
},
|
||||||
|
11: {
|
||||||
|
id: 2,
|
||||||
|
name: state.decoratedNpcChiefs ? 'ⓤ중립참모' : '참모',
|
||||||
|
npcState: state.decoratedNpcChiefs ? 6 : 1,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
impossibleStrategicCommands: [{ name: '수몰', remainingTurns: 2, availableYear: 190, availableMonth: 5 }],
|
impossibleStrategicCommands: [{ name: '수몰', remainingTurns: 2, availableYear: 190, availableMonth: 5 }],
|
||||||
},
|
},
|
||||||
@@ -6836,3 +6845,39 @@ test('main nation and general cards fill their panels without increasing overflo
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('NPC prefixes stay single in the main nation card', async ({ page }, testInfo) => {
|
||||||
|
await installFixture(page, {
|
||||||
|
officerLevel: 5,
|
||||||
|
permission: 2,
|
||||||
|
nationLevel: 3,
|
||||||
|
stage: 0,
|
||||||
|
npcMode: 1,
|
||||||
|
generalMeCalls: 0,
|
||||||
|
operations: [],
|
||||||
|
decoratedNpcChiefs: true,
|
||||||
|
validMapImages: true,
|
||||||
|
});
|
||||||
|
for (const width of [1200, 500]) {
|
||||||
|
await page.setViewportSize({ width, height: 900 });
|
||||||
|
await waitForMain(page);
|
||||||
|
const card = page.locator('[data-nation-basic-card]');
|
||||||
|
await expect(card.getByText('ⓖ의병군주', { exact: true })).toBeVisible();
|
||||||
|
await expect(card.getByText('ⓤ중립참모', { exact: true })).toBeVisible();
|
||||||
|
await page.evaluate(() => document.fonts.ready);
|
||||||
|
await testInfo.attach(`npc-prefix-dom-${width}`, {
|
||||||
|
contentType: 'application/json',
|
||||||
|
body: JSON.stringify(
|
||||||
|
await page.locator('[data-nation-basic-card]').evaluateAll((elements) =>
|
||||||
|
elements.map((element) => ({
|
||||||
|
html: element.outerHTML,
|
||||||
|
rect: element.getBoundingClientRect().toJSON(),
|
||||||
|
fontSize: getComputedStyle(element).fontSize,
|
||||||
|
color: getComputedStyle(element).color,
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
),
|
||||||
|
});
|
||||||
|
await card.screenshot({ path: testInfo.outputPath(`npc-main-prefix-${width}.png`) });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
@@ -160,8 +160,8 @@ const overviewFixture = (state: FixtureState) => ({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 22,
|
id: 22,
|
||||||
name: '조홍',
|
name: 'ⓖ조홍',
|
||||||
npcState: 2,
|
npcState: 4,
|
||||||
officerLevel: 1,
|
officerLevel: 1,
|
||||||
cityId: 2,
|
cityId: 2,
|
||||||
officerCity: 0,
|
officerCity: 0,
|
||||||
@@ -222,7 +222,7 @@ const secretFixture = () => ({
|
|||||||
secretGeneral(21, '장료', 1, {
|
secretGeneral(21, '장료', 1, {
|
||||||
stats: { leadership: 80, strength: 70, intelligence: 50 },
|
stats: { leadership: 80, strength: 70, intelligence: 50 },
|
||||||
}),
|
}),
|
||||||
secretGeneral(22, '조홍', 2, { npcState: 2, reservedCommands: [] }),
|
secretGeneral(22, 'ⓖ조홍', 2, { npcState: 4, reservedCommands: [] }),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -465,3 +465,29 @@ test('암행부 권한 거부는 도시 기밀 행과 인사부 연동을 열지
|
|||||||
await expect(page.getByRole('button', { name: '인사부 연동' })).toHaveCount(0);
|
await expect(page.getByRole('button', { name: '인사부 연동' })).toHaveCount(0);
|
||||||
expect(state.appointmentInputs).toEqual([]);
|
expect(state.appointmentInputs).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('NPC prefixes stay single in city names and linked secret rows', async ({ page }, testInfo) => {
|
||||||
|
await install(page, { role: 'head', appointed: false, appointmentInputs: [] });
|
||||||
|
for (const width of [1200, 500]) {
|
||||||
|
await page.setViewportSize({ width, height: 900 });
|
||||||
|
await page.goto('nation/cities');
|
||||||
|
await expect(page.locator('.general-list').getByText('ⓖ조홍', { exact: true })).toHaveCount(1);
|
||||||
|
await page.getByRole('button', { name: '암행부 연동' }).click();
|
||||||
|
await expect(page.locator('[data-general-id="22"] .secret-name-cell > span').first()).toHaveText('ⓖ조홍');
|
||||||
|
await page.evaluate(() => document.fonts.ready);
|
||||||
|
await testInfo.attach(`npc-prefix-dom-${width}`, {
|
||||||
|
contentType: 'application/json',
|
||||||
|
body: JSON.stringify(
|
||||||
|
await page.locator('.nation-cities-page').evaluateAll((elements) =>
|
||||||
|
elements.map((element) => ({
|
||||||
|
html: element.outerHTML,
|
||||||
|
rect: element.getBoundingClientRect().toJSON(),
|
||||||
|
fontSize: getComputedStyle(element).fontSize,
|
||||||
|
color: getComputedStyle(element).color,
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
),
|
||||||
|
});
|
||||||
|
await page.screenshot({ path: testInfo.outputPath(`npc-city-prefix-${width}.png`), fullPage: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ const emit = defineEmits<{
|
|||||||
const nameColor = computed(() => (props.npcState !== null ? getNpcColor(props.npcState) : undefined));
|
const nameColor = computed(() => (props.npcState !== null ? getNpcColor(props.npcState) : undefined));
|
||||||
const displayName = computed(() => {
|
const displayName = computed(() => {
|
||||||
const name = props.name ?? '-';
|
const name = props.name ?? '-';
|
||||||
return (props.npcState ?? 0) > 0 && !/^[ⓜⓝ]/u.test(name) ? `ⓝ${name}` : name;
|
return (props.npcState ?? 0) > 0 && !/^[ⓜⓝⓖ㉥ⓤⓞⓧ]/u.test(name) ? `ⓝ${name}` : name;
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ const props = defineProps<{
|
|||||||
const number = (value: number): string => value.toLocaleString('ko-KR');
|
const number = (value: number): string => value.toLocaleString('ko-KR');
|
||||||
const displayChiefName = (chief: NationChief | undefined): string => {
|
const displayChiefName = (chief: NationChief | undefined): string => {
|
||||||
if (!chief) return '-';
|
if (!chief) return '-';
|
||||||
return chief.npcState > 0 && !/^[ⓜⓝ㉥]/u.test(chief.name) ? `ⓝ${chief.name}` : chief.name;
|
return chief.npcState > 0 && !/^[ⓜⓝⓖ㉥ⓤⓞⓧ]/u.test(chief.name) ? `ⓝ${chief.name}` : chief.name;
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -69,9 +69,9 @@ const generalsForCity = (cityId: number) =>
|
|||||||
const secretGeneralsForCity = (cityId: number) =>
|
const secretGeneralsForCity = (cityId: number) =>
|
||||||
sortGeneralsByTypeThenName(secretData.value?.generals.filter((general) => general.cityId === cityId) ?? []);
|
sortGeneralsByTypeThenName(secretData.value?.generals.filter((general) => general.cityId === cityId) ?? []);
|
||||||
const displayGeneralName = (general: Result['generals'][number]) =>
|
const displayGeneralName = (general: Result['generals'][number]) =>
|
||||||
general.npcState > 0 && !/^[ⓜⓝ]/u.test(general.name) ? `ⓝ${general.name}` : general.name;
|
general.npcState > 0 && !/^[ⓜⓝⓖ㉥ⓤⓞⓧ]/u.test(general.name) ? `ⓝ${general.name}` : general.name;
|
||||||
const displaySecretGeneralName = (general: SecretGeneral) =>
|
const displaySecretGeneralName = (general: SecretGeneral) =>
|
||||||
general.npcState > 0 && !/^[ⓜⓝ㉥]/u.test(general.name) ? `ⓝ${general.name}` : general.name;
|
general.npcState > 0 && !/^[ⓜⓝⓖ㉥ⓤⓞⓧ]/u.test(general.name) ? `ⓝ${general.name}` : general.name;
|
||||||
const generalCount = (cityId: number) =>
|
const generalCount = (cityId: number) =>
|
||||||
data.value?.generals.filter((general) => general.cityId === cityId).length ?? 0;
|
data.value?.generals.filter((general) => general.cityId === cityId).length ?? 0;
|
||||||
const cities = computed(() => {
|
const cities = computed(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user