fix: 빙의 전용 화면과 가오픈 갱신을 보정한다
This commit is contained in:
@@ -2007,7 +2007,11 @@ test('장수 생성에서 등록 전콘을 골라 생성 요청에 전달한다'
|
||||
accessPages: [],
|
||||
createGeneralInputs: [],
|
||||
joinConfig: {
|
||||
rules: { stat: { total: 150, min: 30, max: 70 }, allowCustomName: true },
|
||||
rules: {
|
||||
stat: { total: 150, min: 30, max: 70 },
|
||||
allowDirectCreation: true,
|
||||
allowCustomName: true,
|
||||
},
|
||||
user: {
|
||||
id: 'user-1',
|
||||
displayName: '생성장수',
|
||||
@@ -2056,7 +2060,11 @@ test('활성 전용 아이콘이 없으면 대표 preset을 장수 생성 요청
|
||||
accessPages: [],
|
||||
createGeneralInputs: [],
|
||||
joinConfig: {
|
||||
rules: { stat: { total: 150, min: 30, max: 70 }, allowCustomName: true },
|
||||
rules: {
|
||||
stat: { total: 150, min: 30, max: 70 },
|
||||
allowDirectCreation: true,
|
||||
allowCustomName: true,
|
||||
},
|
||||
user: {
|
||||
id: 'user-1',
|
||||
displayName: '생성장수',
|
||||
|
||||
@@ -41,6 +41,7 @@ const installFixture = async (page: Page, state: FixtureState): Promise<void> =>
|
||||
return response({
|
||||
rules: {
|
||||
stat: { total: 165, min: 15, max: 80, bonusMin: 3, bonusMax: 5 },
|
||||
allowDirectCreation: true,
|
||||
allowCustomName: true,
|
||||
},
|
||||
user: {
|
||||
|
||||
@@ -141,9 +141,16 @@ const installFixture = async (page: Page, state: FixtureState): Promise<void> =>
|
||||
return response({
|
||||
rules: {
|
||||
stat: { total: 165, min: 15, max: 80, bonusMin: 3, bonusMax: 5 },
|
||||
allowDirectCreation: false,
|
||||
allowCustomName: true,
|
||||
},
|
||||
user: { id: 'npc-user', displayName: '빙의사용자', canCreateGeneral: true },
|
||||
user: {
|
||||
id: 'npc-user',
|
||||
displayName: '빙의사용자',
|
||||
canCreateGeneral: true,
|
||||
icons: [],
|
||||
preferredPicture: null,
|
||||
},
|
||||
personalities: [{ key: 'Random', name: '???', info: '' }],
|
||||
warSpecials: [],
|
||||
nations: [],
|
||||
@@ -225,6 +232,83 @@ const installFixture = async (page: Page, state: FixtureState): Promise<void> =>
|
||||
});
|
||||
};
|
||||
|
||||
test('shows only possession and keeps every candidate reachable from the left on mobile', async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
const state: FixtureState = {
|
||||
reservationCalls: 0,
|
||||
reservationInputs: [],
|
||||
rawBodies: [],
|
||||
possessInputs: [],
|
||||
hasGeneral: false,
|
||||
injectTimeout: false,
|
||||
};
|
||||
await installFixture(page, state);
|
||||
await page.setViewportSize({ width: 390, height: 844 });
|
||||
await page.goto('join');
|
||||
|
||||
await expect(page.getByRole('heading', { name: '장수 빙의', exact: true, level: 1 })).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: '장수 생성', exact: true })).toHaveCount(0);
|
||||
await expect(page.locator('.npc-card')).toHaveCount(5);
|
||||
|
||||
const candidateGeometry = await page.locator('.npc-possession-section').evaluate((section) => {
|
||||
const sectionRect = section.getBoundingClientRect();
|
||||
const holder = section.querySelector<HTMLElement>('.npc-card-holder')!;
|
||||
const holderRect = holder.getBoundingClientRect();
|
||||
const cards = [...holder.querySelectorAll<HTMLElement>('.npc-card')];
|
||||
return {
|
||||
viewportWidth: window.innerWidth,
|
||||
documentWidth: document.documentElement.scrollWidth,
|
||||
sectionLeft: sectionRect.left,
|
||||
sectionRight: sectionRect.right,
|
||||
holderLeft: holderRect.left,
|
||||
firstCardLeft: cards[0]!.getBoundingClientRect().left,
|
||||
lastCardRight: cards.at(-1)!.getBoundingClientRect().right,
|
||||
holderClientWidth: holder.clientWidth,
|
||||
holderScrollWidth: holder.scrollWidth,
|
||||
holderScrollLeft: holder.scrollLeft,
|
||||
};
|
||||
});
|
||||
expect(candidateGeometry.viewportWidth).toBe(390);
|
||||
expect(candidateGeometry.documentWidth).toBe(390);
|
||||
expect(candidateGeometry.sectionLeft).toBeGreaterThanOrEqual(0);
|
||||
expect(candidateGeometry.sectionRight).toBeLessThanOrEqual(390);
|
||||
expect(candidateGeometry.firstCardLeft).toBeGreaterThanOrEqual(candidateGeometry.holderLeft);
|
||||
expect(candidateGeometry.lastCardRight).toBeGreaterThan(candidateGeometry.sectionRight);
|
||||
expect(candidateGeometry.holderScrollWidth).toBeGreaterThan(candidateGeometry.holderClientWidth);
|
||||
expect(candidateGeometry.holderScrollLeft).toBe(0);
|
||||
|
||||
const refreshButton = page.locator('#btn-pick-more');
|
||||
await expect(refreshButton).toBeEnabled();
|
||||
const refreshStyle = await refreshButton.evaluate((element) => {
|
||||
const style = getComputedStyle(element);
|
||||
return {
|
||||
backgroundColor: style.backgroundColor,
|
||||
borderBottomWidth: style.borderBottomWidth,
|
||||
opacity: style.opacity,
|
||||
cursor: style.cursor,
|
||||
};
|
||||
});
|
||||
expect(refreshStyle.backgroundColor).not.toBe('rgba(0, 0, 0, 0)');
|
||||
expect(refreshStyle).toMatchObject({ borderBottomWidth: '4px', opacity: '1', cursor: 'pointer' });
|
||||
await page.screenshot({ path: testInfo.outputPath('npc-possession-mobile-active.png'), fullPage: true });
|
||||
|
||||
const dialogs: string[] = [];
|
||||
page.on('dialog', async (dialog) => {
|
||||
dialogs.push(dialog.message());
|
||||
await dialog.dismiss();
|
||||
});
|
||||
await page.locator('.npc-action').last().click();
|
||||
expect(dialogs).toContain('빙의할까요? : 빙의후보5');
|
||||
await expect
|
||||
.poll(() => page.locator('.npc-card-holder').evaluate((holder) => (holder as HTMLElement).scrollLeft))
|
||||
.toBeGreaterThan(0);
|
||||
|
||||
await refreshButton.click();
|
||||
await expect.poll(() => state.reservationCalls).toBe(2);
|
||||
expect(state.reservationInputs.at(-1)).toMatchObject({ refresh: true, keepIds: [] });
|
||||
});
|
||||
|
||||
test('renders Ref-shaped token cards, preserves keep cooldown and retries possession with one ID', async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
@@ -239,6 +323,8 @@ test('renders Ref-shaped token cards, preserves keep cooldown and retries posses
|
||||
await installFixture(page, state);
|
||||
await page.setViewportSize({ width: 1024, height: 900 });
|
||||
await page.goto('join?tab=possess');
|
||||
await expect(page.getByRole('heading', { name: '장수 빙의', exact: true, level: 1 })).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: '장수 생성', exact: true })).toHaveCount(0);
|
||||
await expect(page.getByRole('button', { name: 'NPC 빙의' })).toHaveClass(/active/);
|
||||
|
||||
await expect(page.locator('.npc-card')).toHaveCount(5);
|
||||
|
||||
@@ -53,6 +53,8 @@ const submitting = ref(false);
|
||||
|
||||
const joinConfig = ref<JoinConfig | null>(null);
|
||||
const accountIcons = computed(() => joinConfig.value?.user.icons ?? []);
|
||||
const directCreationEnabled = computed(() => joinConfig.value?.rules.allowDirectCreation === true);
|
||||
const possessionOnly = computed(() => !directCreationEnabled.value && joinConfig.value?.npcPossession.enabled === true);
|
||||
const activeTab = ref<'create' | 'possess'>('create');
|
||||
const contextTab = ref<'invitation' | 'map' | 'generals'>('invitation');
|
||||
const inheritOpen = ref(false);
|
||||
@@ -429,7 +431,11 @@ const loadConfig = async () => {
|
||||
joinConfig.value = config;
|
||||
const storedPossession = readPendingPossess();
|
||||
pendingPossessAction.value = storedPossession?.ownerUserId === config.user.id ? storedPossession : null;
|
||||
if (route.query.tab === 'possess' && config.npcPossession.enabled && config.user.canCreateGeneral) {
|
||||
if (
|
||||
config.npcPossession.enabled &&
|
||||
config.user.canCreateGeneral &&
|
||||
(!config.rules.allowDirectCreation || route.query.tab === 'possess')
|
||||
) {
|
||||
activeTab.value = 'possess';
|
||||
}
|
||||
const pending = readPendingJoin();
|
||||
@@ -658,13 +664,14 @@ onUnmounted(() => {
|
||||
<main class="join-page">
|
||||
<header class="join-header">
|
||||
<div>
|
||||
<h1 class="join-title">장수 생성/빙의</h1>
|
||||
<h1 class="join-title">{{ possessionOnly ? '장수 빙의' : '장수 생성/빙의' }}</h1>
|
||||
<p class="join-subtitle">로그인 완료, 아직 장수가 없는 상태입니다.</p>
|
||||
</div>
|
||||
<div class="join-tabs">
|
||||
<RouterLink class="simulator-link" to="/past-plays">내 지난 플레이</RouterLink>
|
||||
<RouterLink class="simulator-link" to="/battle-simulator">전투 시뮬레이터</RouterLink>
|
||||
<button
|
||||
v-if="directCreationEnabled"
|
||||
:class="{ active: activeTab === 'create' }"
|
||||
:disabled="joinConfig?.user.canCreateGeneral === false"
|
||||
@click="activeTab = 'create'"
|
||||
@@ -1099,7 +1106,7 @@ onUnmounted(() => {
|
||||
<div class="npc-footer">
|
||||
<button
|
||||
id="btn-pick-more"
|
||||
class="ghost"
|
||||
class="legacy-button legacy-button--secondary"
|
||||
type="button"
|
||||
:disabled="npcLoading || npcPickMoreSeconds > 0 || submitting || hasPendingPossession"
|
||||
@click="loadNpcCandidates(true)"
|
||||
@@ -1683,6 +1690,7 @@ onUnmounted(() => {
|
||||
|
||||
.npc-possession-section {
|
||||
width: 1000px;
|
||||
min-width: 0;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
@@ -1807,7 +1815,7 @@ onUnmounted(() => {
|
||||
}
|
||||
|
||||
.npc-general-list-wrap {
|
||||
width: 970px;
|
||||
width: min(100%, 970px);
|
||||
margin: 0 auto 20px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
@@ -1929,5 +1937,23 @@ onUnmounted(() => {
|
||||
.context-general-table {
|
||||
min-width: 520px;
|
||||
}
|
||||
|
||||
.npc-possession-section {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.npc-card-holder {
|
||||
display: flex;
|
||||
max-width: 100%;
|
||||
justify-content: flex-start;
|
||||
overflow-x: auto;
|
||||
padding-bottom: 8px;
|
||||
overscroll-behavior-x: contain;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.npc-card {
|
||||
flex: 0 0 125px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user