feat: port scenario action effects
This commit is contained in:
@@ -22,6 +22,7 @@ const persistParityArtifact = async (page: Page, name: string, geometry: unknown
|
||||
type FixtureState = {
|
||||
permission: 'head' | 'member';
|
||||
myset: number;
|
||||
scenarioEffect?: string | null;
|
||||
settingMutations: Array<Record<string, unknown>>;
|
||||
accessPages: string[];
|
||||
};
|
||||
@@ -154,7 +155,11 @@ const install = async (page: Page, state: FixtureState) => {
|
||||
currentYear: 185,
|
||||
currentMonth: 1,
|
||||
tickSeconds: 600,
|
||||
config: { npcMode: 0, const: { availableInstantAction: {} } },
|
||||
config: {
|
||||
npcMode: 0,
|
||||
const: { availableInstantAction: {} },
|
||||
environment: { scenarioEffect: state.scenarioEffect ?? null },
|
||||
},
|
||||
meta: {
|
||||
turntime: '2026-01-01T00:00:00.000Z',
|
||||
opentime: '2025-12-01T00:00:00.000Z',
|
||||
@@ -266,11 +271,20 @@ test('접속량정보 keeps the legacy public 1016px chart geometry', async ({ p
|
||||
test('내 정보&설정 keeps the legacy 1000px/500px geometry and saves in place', async ({ page }) => {
|
||||
const state: FixtureState = { permission: 'head', myset: 3, settingMutations: [], accessPages: [] };
|
||||
await install(page, state);
|
||||
await page.setViewportSize({ width: 1200, height: 900 });
|
||||
await page.setViewportSize({ width: 1000, height: 900 });
|
||||
await page.goto('my-page');
|
||||
await expect(page.locator('.title-row')).toContainText('내 정 보');
|
||||
await expect(page.locator('#set_my_setting')).toBeVisible();
|
||||
await expect.poll(() => state.accessPages).toContain('my-page');
|
||||
const noDefenceOption = page.locator('option[value="999"]');
|
||||
await expect(noDefenceOption).toHaveText('× [훈련 -3,사기 -6]');
|
||||
await expect(page.locator('#defence_train option')).toHaveText([
|
||||
'☆(훈사90)',
|
||||
'◎(훈사80)',
|
||||
'○(훈사60)',
|
||||
'△(훈사40)',
|
||||
'× [훈련 -3,사기 -6]',
|
||||
]);
|
||||
|
||||
const desktop = await page.locator('#container').evaluate((element) => {
|
||||
const rect = element.getBoundingClientRect();
|
||||
@@ -311,14 +325,62 @@ test('내 정보&설정 keeps the legacy 1000px/500px geometry and saves in plac
|
||||
expect(desktop.sectionBackgroundImage).toContain('back_green.jpg');
|
||||
await persistParityArtifact(page, 'core-my-page-desktop', desktop);
|
||||
|
||||
await page
|
||||
.locator('select')
|
||||
.filter({ has: page.locator('option[value="999"]') })
|
||||
.selectOption('999');
|
||||
const defenceSelect = page.locator('select').filter({ has: page.locator('option[value="999"]') });
|
||||
await defenceSelect.selectOption('999');
|
||||
const noEffectState = await defenceSelect.evaluate((element) => {
|
||||
const rect = element.getBoundingClientRect();
|
||||
const style = getComputedStyle(element);
|
||||
return {
|
||||
scenarioEffect: null,
|
||||
optionText: element.querySelector<HTMLOptionElement>('option[value="999"]')?.textContent,
|
||||
selectedText: element.querySelector<HTMLOptionElement>('option:checked')?.textContent,
|
||||
rect: { x: rect.x, y: rect.y, width: rect.width, height: rect.height },
|
||||
fontSize: style.fontSize,
|
||||
lineHeight: style.lineHeight,
|
||||
color: style.color,
|
||||
backgroundColor: style.backgroundColor,
|
||||
};
|
||||
});
|
||||
expect(noEffectState.rect.width).toBe(134);
|
||||
expect(noEffectState.rect.height).toBe(20);
|
||||
await persistParityArtifact(page, 'core-my-page-no-effect-999', noEffectState);
|
||||
await page.locator('#set_my_setting').click();
|
||||
await expect.poll(() => state.settingMutations.length).toBe(1);
|
||||
expect(state.settingMutations[0]).not.toHaveProperty('generalId');
|
||||
|
||||
for (const [effectIndex, scenarioEffect] of [
|
||||
'event_UnlimitedDefenceThresholdChange',
|
||||
'event_StrongAttacker',
|
||||
'event_MoreEffect',
|
||||
].entries()) {
|
||||
state.scenarioEffect = scenarioEffect;
|
||||
state.myset = 1;
|
||||
await page.reload();
|
||||
await expect(noDefenceOption).toHaveText('×');
|
||||
await defenceSelect.selectOption('999');
|
||||
const effectState = await defenceSelect.evaluate((element) => {
|
||||
const rect = element.getBoundingClientRect();
|
||||
const style = getComputedStyle(element);
|
||||
return {
|
||||
optionText: element.querySelector<HTMLOptionElement>('option[value="999"]')?.textContent,
|
||||
selectedText: element.querySelector<HTMLOptionElement>('option:checked')?.textContent,
|
||||
rect: { x: rect.x, y: rect.y, width: rect.width, height: rect.height },
|
||||
fontSize: style.fontSize,
|
||||
lineHeight: style.lineHeight,
|
||||
color: style.color,
|
||||
backgroundColor: style.backgroundColor,
|
||||
};
|
||||
});
|
||||
expect(effectState.optionText).toBe('×');
|
||||
expect(effectState.selectedText).toBe('×');
|
||||
expect(effectState.rect.width).toBe(86);
|
||||
expect(effectState.rect.height).toBe(20);
|
||||
await persistParityArtifact(page, `core-my-page-${scenarioEffect}`, effectState);
|
||||
await page.locator('#set_my_setting').click();
|
||||
await expect.poll(() => state.settingMutations.length).toBe(effectIndex + 2);
|
||||
expect(state.settingMutations.at(-1)).not.toHaveProperty('generalId');
|
||||
}
|
||||
|
||||
await page.setViewportSize({ width: 500, height: 900 });
|
||||
await page.reload();
|
||||
const mobile = await page.locator('#container').evaluate((element) => {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { computed, onMounted, reactive, ref, watch } from 'vue';
|
||||
import { trpc } from '../utils/trpc';
|
||||
import { formatLog } from '../utils/formatLog';
|
||||
import { isDefenceTrainPenaltyWaivedByScenarioEffect } from '@sammo-ts/logic';
|
||||
|
||||
const SCREEN_MODE_KEY = 'sam.screenMode';
|
||||
const CUSTOM_CSS_KEY = 'sam_customCSS';
|
||||
@@ -95,6 +96,13 @@ const statusLine = computed(() =>
|
||||
|
||||
const canSave = computed(() => (data.value?.settings.myset ?? 1) > 0);
|
||||
const penalties = computed(() => Object.entries(data.value?.penalties ?? {}));
|
||||
const noDefencePenaltyWaived = computed(() => {
|
||||
const environment = asRecord(world.value?.config.environment);
|
||||
return isDefenceTrainPenaltyWaivedByScenarioEffect(
|
||||
typeof environment.scenarioEffect === 'string' ? environment.scenarioEffect : null
|
||||
);
|
||||
});
|
||||
const noDefenceLabel = computed(() => (noDefencePenaltyWaived.value ? '×' : '× [훈련 -3,사기 -6]'));
|
||||
const items = computed<Array<{ key: ItemSlotKey; name: string; code: string | null }>>(() => [
|
||||
{ key: 'horse', name: '말', code: data.value?.general.items.horse ?? null },
|
||||
{ key: 'weapon', name: '무기', code: data.value?.general.items.weapon ?? null },
|
||||
@@ -319,12 +327,16 @@ onMounted(() => {
|
||||
|
||||
<label class="setting-line">
|
||||
수비 【
|
||||
<select v-model.number="form.defence_train">
|
||||
<option :value="90">수비 함(훈사90)</option>
|
||||
<option :value="80">수비 함(훈사80)</option>
|
||||
<option :value="60">수비 함(훈사60)</option>
|
||||
<option :value="40">수비 함(훈사40)</option>
|
||||
<option :value="999">수비 안함 [훈련 -3, 사기 -6]</option>
|
||||
<select
|
||||
id="defence_train"
|
||||
v-model.number="form.defence_train"
|
||||
:class="{ 'penalty-waived': noDefencePenaltyWaived }"
|
||||
>
|
||||
<option :value="90">☆(훈사90)</option>
|
||||
<option :value="80">◎(훈사80)</option>
|
||||
<option :value="60">○(훈사60)</option>
|
||||
<option :value="40">△(훈사40)</option>
|
||||
<option :value="999">{{ noDefenceLabel }}</option>
|
||||
</select>
|
||||
】
|
||||
</label>
|
||||
@@ -584,6 +596,12 @@ dt {
|
||||
display: block;
|
||||
margin-top: 5px;
|
||||
}
|
||||
#defence_train {
|
||||
width: 134px;
|
||||
}
|
||||
#defence_train.penalty-waived {
|
||||
width: 86px;
|
||||
}
|
||||
.hint {
|
||||
margin: 0 0 13px;
|
||||
color: orange;
|
||||
|
||||
Reference in New Issue
Block a user