fix(game-ui): 동적 한국어 조사 처리를 통일한다
세력도시 임명 toast와 접근성 이름, 아이템 파기 확인 문구를 JosaUtil로 조립한다. 받침 유무를 production Chromium 회귀로 고정하고 작업 지침에 병기 조사 금지 규칙을 추가한다.
This commit is contained in:
@@ -340,6 +340,10 @@ docs에서 확인해 주세요. 존재하지 않는 명령을 오래된 report
|
||||
- Vue component는 PascalCase, composable은 `useX`, 변수/함수는 camelCase,
|
||||
type/class는 PascalCase를 사용해 주세요.
|
||||
- 한국어 domain identifier와 설명은 의미가 더 명확할 때 유지해 주세요.
|
||||
- toast·dialog·aria-label·로그처럼 사용자에게 노출하는 동적 한국어 문구에 조사를
|
||||
붙일 때는 `@sammo-ts/common/util/JosaUtil`의 `put()` 또는 `pick()`을 사용해 주세요.
|
||||
`` `${value}을(를)` ``, `` `${value}이(가)` ``, `` `${value}(으)로` `` 같은 병기 표기를
|
||||
실제 제품 문구에 남기지 말고, 받침 있는 값과 없는 값의 최종 표시 문구를 검증해 주세요.
|
||||
- action/command/전투 코드에는 한국 독자가 side effect와 ref 근거를 이해할
|
||||
수 있는 주석을 남기되 코드의 반복 설명은 피해 주세요.
|
||||
- 기능 이관과 무관한 대규모 formatting/refactor를 같은 변경에 섞지 말아 주세요.
|
||||
|
||||
@@ -1781,6 +1781,28 @@ test('내 정보에서 사람 장수의 등록 전콘을 골라 변경한다', a
|
||||
);
|
||||
});
|
||||
|
||||
test('내 정보 아이템 파기 확인은 이름의 받침에 맞는 조사를 쓴다', async ({ page }) => {
|
||||
const state: FixtureState = {
|
||||
permission: 'member',
|
||||
myset: 1,
|
||||
richMyInfo: true,
|
||||
settingMutations: [],
|
||||
accessPages: [],
|
||||
};
|
||||
const prompts: string[] = [];
|
||||
page.on('dialog', async (dialog) => {
|
||||
prompts.push(dialog.message());
|
||||
await dialog.dismiss();
|
||||
});
|
||||
await install(page, state);
|
||||
await page.goto('my-page');
|
||||
|
||||
await page.getByRole('button', { name: '명마', exact: true }).click();
|
||||
await page.getByRole('button', { name: '효경전', exact: true }).click();
|
||||
|
||||
expect(prompts).toEqual(['명마를 버리시겠습니까?', '효경전을 버리시겠습니까?']);
|
||||
});
|
||||
|
||||
test('장수 생성에서 등록 전콘을 골라 생성 요청에 전달한다', async ({ page }) => {
|
||||
const firstIconId = '3f804277-584f-4f44-b39c-9ecf40d1ed31';
|
||||
const secondIconId = 'f6af46a2-809a-481d-b66d-0f7bbb706780';
|
||||
|
||||
@@ -397,7 +397,7 @@ test('암행부 행을 도시별로 나누고 수뇌의 인사부 즉시 임명
|
||||
return { borderTopWidth: style.borderTopWidth, backgroundColor: style.backgroundColor };
|
||||
});
|
||||
expect(disabledStyle).toEqual({ borderTopWidth: '0px', backgroundColor: 'rgba(0, 0, 0, 0)' });
|
||||
const appointButton = page.getByRole('button', { name: '장료을(를) 허창 태수로 임명' });
|
||||
const appointButton = page.getByRole('button', { name: '장료를 허창 태수로 임명' });
|
||||
await appointButton.hover();
|
||||
expect(await appointButton.evaluate((button) => getComputedStyle(button).cursor)).toBe('pointer');
|
||||
await appointButton.focus();
|
||||
@@ -406,6 +406,7 @@ test('암행부 행을 도시별로 나누고 수뇌의 인사부 즉시 임명
|
||||
await page.screenshot({ path: testInfo.outputPath('nation-city-integrated-desktop.png'), fullPage: true });
|
||||
await appointButton.click();
|
||||
await expect.poll(() => state.appointmentInputs).toEqual([{ destGeneralId: 21, destCityId: 1, officerLevel: 4 }]);
|
||||
await expect(page.getByTestId('game-toast')).toContainText('장료를 허창 태수로 임명했습니다.');
|
||||
await expect(page.locator('.city[data-city-id="1"] .officer-4-value')).toHaveText('장료');
|
||||
await expect(page.locator('.city[data-city-id="1"] .officer-4-value')).toHaveClass(/effective-officer/u);
|
||||
await expect(page.locator('.city[data-city-id="1"] tr[data-general-id="21"] .mode-4')).toBeDisabled();
|
||||
@@ -425,7 +426,7 @@ test('수뇌 대상은 재확인하고 일반 장수에게는 임명 버튼을
|
||||
await page.getByRole('button', { name: '암행부 연동' }).click();
|
||||
await page.getByRole('button', { name: '인사부 연동' }).click();
|
||||
|
||||
const chiefButton = page.getByRole('button', { name: '순욱을(를) 허창 태수로 임명' });
|
||||
const chiefButton = page.getByRole('button', { name: '순욱을 허창 태수로 임명' });
|
||||
expect(await chiefButton.evaluate((button) => getComputedStyle(button).color)).toBe('rgb(255, 0, 0)');
|
||||
page.once('dialog', async (dialog) => {
|
||||
expect(dialog.message()).toBe('수뇌입니다. 임명할까요?');
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { JosaUtil } from '@sammo-ts/common/util/JosaUtil';
|
||||
import { computed, onMounted, reactive, ref, watch } from 'vue';
|
||||
import SortableStringList from '../components/ui/SortableStringList';
|
||||
import { trpc } from '../utils/trpc';
|
||||
@@ -365,7 +366,7 @@ const dieOnPrestart = async () => {
|
||||
};
|
||||
|
||||
const dropItem = (item: { key: ItemSlotKey; slotName: string; displayName: string | null; code: string | null }) =>
|
||||
confirmMutation(`${item.displayName ?? item.slotName}을(를) 버리시겠습니까?`, () =>
|
||||
confirmMutation(`${JosaUtil.put(item.displayName ?? item.slotName, '을')} 버리시겠습니까?`, () =>
|
||||
trpc.general.dropItem.mutate({ itemType: item.key })
|
||||
);
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { formatServerDateTime } from '@sammo-ts/common/time/ServerDateTime';
|
||||
import { JosaUtil } from '@sammo-ts/common/util/JosaUtil';
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { formatReservedCommandBrief } from '../components/command/reservedCommandBrief';
|
||||
@@ -59,6 +60,8 @@ const sortOptions = [
|
||||
'규모',
|
||||
].map((label, index) => ({ value: index + 1, label }));
|
||||
const officerLabels: Record<OfficerLevel, string> = { 4: '태수', 3: '군사', 2: '종사' };
|
||||
const appointmentDescription = (city: City, general: SecretGeneral, level: OfficerLevel): string =>
|
||||
`${JosaUtil.put(general.name, '을')} ${city.name} ${JosaUtil.put(officerLabels[level], '으로')} 임명`;
|
||||
const generalsForCity = (cityId: number) => data.value?.generals.filter((general) => general.cityId === cityId) ?? [];
|
||||
const secretGeneralsForCity = (cityId: number) =>
|
||||
secretData.value?.generals.filter((general) => general.cityId === cityId) ?? [];
|
||||
@@ -262,7 +265,7 @@ const appointCityOfficer = async (city: City, general: SecretGeneral, level: Off
|
||||
destCityId: city.id,
|
||||
officerLevel: level,
|
||||
});
|
||||
showSuccessToast(`${general.name}을(를) ${city.name} ${officerLabels[level]}로 임명했습니다.`);
|
||||
showSuccessToast(`${appointmentDescription(city, general, level)}했습니다.`);
|
||||
try {
|
||||
await refreshIntegratedData();
|
||||
} catch (cause) {
|
||||
@@ -652,7 +655,7 @@ onMounted(async () => {
|
||||
:disabled="
|
||||
!canAppoint(city.id, general.id, level) || pendingAppointment !== ''
|
||||
"
|
||||
:aria-label="`${general.name}을(를) ${city.name} ${officerLabels[level]}로 임명`"
|
||||
:aria-label="appointmentDescription(city, general, level)"
|
||||
@click="appointCityOfficer(city, general, level)"
|
||||
>
|
||||
{{ officerLabels[level].slice(0, 1) }}
|
||||
|
||||
Reference in New Issue
Block a user