merge: 최신 main을 외교 권한 복수 임명 수정에 다시 통합한다

This commit is contained in:
2026-08-24 08:42:30 +00:00
16 changed files with 245 additions and 78 deletions
+34
View File
@@ -1990,6 +1990,40 @@ test('장수 생성에서 등록 전콘을 골라 생성 요청에 전달한다'
expect(state.createGeneralInputs?.[0]).toMatchObject({ pic: true, iconId: secondIconId });
});
test('활성 전용 아이콘이 없으면 대표 preset을 장수 생성 요청에 전달하지 않는다', async ({ page }) => {
const state: FixtureState = {
permission: 'member',
myset: 1,
settingMutations: [],
accessPages: [],
createGeneralInputs: [],
joinConfig: {
rules: { stat: { total: 150, min: 30, max: 70 }, allowCustomName: true },
user: {
id: 'user-1',
displayName: '생성장수',
canCreateGeneral: true,
preferredPicture: '장수/유비.jpg',
icons: [],
},
personalities: [{ key: 'Random', name: '???', info: '무작위 성격' }],
nations: [],
selectionPool: { enabled: false },
npcPossession: { enabled: false },
inherit: null,
},
};
await install(page, state);
await page.goto('join');
await expect(page.getByRole('radiogroup', { name: '전용 아이콘 선택' })).toHaveCount(0);
await page.getByRole('button', { name: '장수 생성', exact: true }).last().click();
await expect.poll(() => state.createGeneralInputs?.length ?? 0).toBe(1);
expect(state.createGeneralInputs?.[0]).toMatchObject({ pic: false });
expect(state.createGeneralInputs?.[0]).not.toHaveProperty('iconId');
});
test('내 정보 즉시행동은 timeout 재시도 ID를 유지하고 성공 후 새 ID를 만든다', async ({ page }) => {
const state: FixtureState = {
permission: 'head',
+7 -1
View File
@@ -23,6 +23,10 @@ const history = [
'<span class="war_type war_type_attack">→</span><span class="ev_highlight">강조</span>' +
'<span class="name" onclick="globalThis.__legacyLogXss=3">오염 이름</span></div>',
},
{
id: 3,
text: '이번 수입은 금 <C>158</>입니다.',
},
];
const publicResponse = (operation: string): unknown => {
@@ -59,10 +63,12 @@ for (const viewport of [
await page.goto('public');
const lines = page.locator('.recent-log-line');
await expect(lines).toHaveCount(2);
await expect(lines).toHaveCount(3);
await expect(lines.nth(0).locator('b')).toHaveText('안전 강조');
await expect(lines.nth(1).locator('.small_war_log .war_type_attack')).toHaveText('→');
await expect(lines.nth(1).locator('.ev_highlight')).toHaveText('강조');
await expect(lines.nth(2)).toHaveText('이번 수입은 금 158입니다.');
await expect(lines.nth(2)).not.toContainText('157.5');
await expect(lines.locator('script, img, svg, a, [onerror], [onclick], [style*="url"]')).toHaveCount(0);
await expect(lines.nth(0)).toContainText('<img src=x onerror=');
await expect(lines.nth(1)).toContainText('<span class="name" onclick=');
@@ -429,6 +429,7 @@ test.describe('scenario 903 live selection pool', () => {
expect(created.name).toBe(initialName?.trim());
expect(created.personalCode).toBe('che_안전');
expect(created.specialCode).toMatch(/^che_event_/);
expect(created).toMatchObject({ picture: 'default.jpg', imageServer: 0 });
const createEvent = await db.inputEvent.findFirstOrThrow({
where: { actorUserId: userId, eventType: 'selectPoolCreate' },
orderBy: { sequence: 'desc' },
@@ -508,6 +509,12 @@ test.describe('scenario 903 live selection pool', () => {
await expect
.poll(async () => (await db.general.findUniqueOrThrow({ where: { id: created.id } })).name)
.toBe(targetName);
await expect
.poll(async () => {
const general = await db.general.findUniqueOrThrow({ where: { id: created.id } });
return { picture: general.picture, imageServer: general.imageServer };
})
.toEqual({ picture: 'default.jpg', imageServer: 0 });
const reselectEvent = await db.inputEvent.findFirstOrThrow({
where: { actorUserId: userId, eventType: 'selectPoolReselect' },
orderBy: { sequence: 'desc' },
+2 -1
View File
@@ -64,7 +64,7 @@ const form = ref<JoinForm>({
strength: 0,
intel: 0,
character: 'Random',
pic: true,
pic: false,
iconId: undefined,
inheritBonusStat: [0, 0, 0],
});
@@ -437,6 +437,7 @@ const loadConfig = async () => {
} else {
form.value.name = config.rules.allowCustomName ? config.user.displayName || '' : '무작위';
form.value.iconId = config.user.icons.find((icon) => icon.picture === config.user.preferredPicture)?.id;
form.value.pic = form.value.iconId !== undefined;
applyBalancedStats();
}
} catch (err) {
@@ -429,7 +429,7 @@ onBeforeUnmount(() => {
<th class="legacy-bg1">전콘 선택</th>
<td class="pool-icon-choice">
<label>
<input v-model="selectedIconId" type="radio" value="" /> 선택한 장수
<input v-model="selectedIconId" type="radio" value="" /> 기본 아이
</label>
<label v-for="icon in config.user.icons" :key="icon.id">
<input v-model="selectedIconId" type="radio" :value="icon.id" />