플레이 감사 장수 이름 검색과 번호 역순 조회 추가
This commit is contained in:
@@ -154,6 +154,8 @@ export const playAuditRouter = router({
|
||||
zAuditPage.extend({
|
||||
cityId: z.number().int().nonnegative().optional(),
|
||||
population: z.enum(['human', 'npc', 'troopNpc']).optional(),
|
||||
name: z.string().trim().max(64).optional(),
|
||||
order: z.enum(['asc', 'desc']).default('asc'),
|
||||
})
|
||||
)
|
||||
.query(({ ctx, input }) =>
|
||||
@@ -168,6 +170,14 @@ export const playAuditRouter = router({
|
||||
? 5
|
||||
: undefined;
|
||||
const filter = { nationId: input.nationId, cityId: input.cityId, npcState };
|
||||
// LIKE wildcard도 이름의 문자로 취급한다. 과거 검색은 선택한 표본 안에서만 수행한다.
|
||||
const name = input.name?.replace(/[\\%_]/g, '\\$&');
|
||||
const idRange =
|
||||
input.cursor === undefined
|
||||
? undefined
|
||||
: input.order === 'desc'
|
||||
? { lt: input.cursor }
|
||||
: { gt: input.cursor };
|
||||
if (input.at) {
|
||||
const sample = await findAuditMonth(tx, world, input.at);
|
||||
const rows = sample
|
||||
@@ -175,9 +185,10 @@ export const playAuditRouter = router({
|
||||
where: {
|
||||
sampleId: sample.id,
|
||||
...filter,
|
||||
generalId: input.cursor === undefined ? undefined : { gt: input.cursor },
|
||||
generalId: idRange,
|
||||
data: name ? { path: ['name'], string_contains: name } : undefined,
|
||||
},
|
||||
orderBy: { generalId: 'asc' },
|
||||
orderBy: { generalId: input.order },
|
||||
take: input.limit + 1,
|
||||
select: { data: true },
|
||||
})
|
||||
@@ -194,8 +205,8 @@ export const playAuditRouter = router({
|
||||
};
|
||||
}
|
||||
const rows = await tx.general.findMany({
|
||||
where: { ...filter, id: input.cursor === undefined ? undefined : { gt: input.cursor } },
|
||||
orderBy: { id: 'asc' },
|
||||
where: { ...filter, id: idRange, name: name ? { contains: name } : undefined },
|
||||
orderBy: { id: input.order },
|
||||
take: input.limit + 1,
|
||||
select: generalSelect,
|
||||
});
|
||||
|
||||
@@ -2628,6 +2628,43 @@ integration('game API security over HTTP transport', () => {
|
||||
result: { data: { collected: true, items: [{ name: '과거이름' }] } },
|
||||
});
|
||||
expect(JSON.stringify(history.body)).not.toContain('must-not-expose');
|
||||
for (const name of ['과거', ' 과거 ']) {
|
||||
expect((await get('generals', admin, { at: { year: 190, month: 1 }, name })).body).toMatchObject({
|
||||
result: { data: { items: [{ name: '과거이름' }] } },
|
||||
});
|
||||
}
|
||||
for (const name of ['없는이름', '%', '_', '\\']) {
|
||||
expect((await get('generals', admin, { at: { year: 190, month: 1 }, name })).body).toMatchObject({
|
||||
result: { data: { items: [] } },
|
||||
});
|
||||
}
|
||||
const currentName = (await db.general.findUniqueOrThrow({ where: { id: generalId } })).name;
|
||||
expect((await get('generals', admin, { name: currentName })).body).toMatchObject({
|
||||
result: { data: { items: expect.arrayContaining([expect.objectContaining({ id: generalId })]) } },
|
||||
});
|
||||
expect((await get('generals', admin, { name: '과거이름' })).body).toMatchObject({
|
||||
result: { data: { items: [] } },
|
||||
});
|
||||
expect((await get('generals', admin, { name: '%' })).body).toMatchObject({
|
||||
result: { data: { items: [] } },
|
||||
});
|
||||
const descendingIds = await db.general.findMany({ orderBy: { id: 'desc' }, select: { id: true } });
|
||||
expect((await get('generals', admin, { order: 'desc', limit: 1 })).body).toMatchObject({
|
||||
result: { data: { nextCursor: descendingIds[0]!.id, items: [descendingIds[0]] } },
|
||||
});
|
||||
expect(
|
||||
(await get('generals', admin, { order: 'desc', limit: 1, cursor: descendingIds[0]!.id })).body
|
||||
).toMatchObject({
|
||||
result: { data: { items: [descendingIds[1]] } },
|
||||
});
|
||||
expect(
|
||||
(await get('generals', admin, { order: 'desc', at: { year: 190, month: 1 }, cursor: generalId })).body
|
||||
).toMatchObject({
|
||||
result: { data: { items: [] } },
|
||||
});
|
||||
expect((await get('generals', admin, { name: '가'.repeat(65) })).status).toBe(400);
|
||||
expect((await get('generals', admin, { order: 'gold' })).status).toBe(400);
|
||||
|
||||
expect((await get('generals', admin, { at: { year: 190, month: 2 } })).body).toMatchObject({
|
||||
result: { data: { collected: false, items: [] } },
|
||||
});
|
||||
|
||||
@@ -982,3 +982,36 @@ for (const [kind, label, value] of [
|
||||
await expect(page.getByLabel('외교 전후 값', { exact: true })).toContainText('미관측 / 없음');
|
||||
});
|
||||
}
|
||||
|
||||
test('general search is explicit and persists across pagination and reload', async ({ page }) => {
|
||||
await page.setViewportSize({ width: 390, height: 844 });
|
||||
const requests = await install(page);
|
||||
await page.goto(gamePath('/play-audit?tab=generals&at=month&year=190&month=6'));
|
||||
await expect(page.getByRole('rowheader', { name: /감사장수/ })).toBeVisible();
|
||||
const count = requests.filter((r) => r.operation === 'playAudit.generals').length;
|
||||
await page.getByLabel('장수 이름', { exact: true }).fill('감사');
|
||||
await page.getByLabel('장수 번호 정렬', { exact: true }).selectOption('desc');
|
||||
expect(requests.filter((r) => r.operation === 'playAudit.generals')).toHaveLength(count);
|
||||
await page.getByRole('button', { name: '조회', exact: true }).click();
|
||||
await expect(page).toHaveURL(/name=/);
|
||||
await expect
|
||||
.poll(() => requests.filter((r) => r.operation === 'playAudit.generals').at(-1)?.input)
|
||||
.toMatchObject({
|
||||
name: '감사',
|
||||
order: 'desc',
|
||||
at: { year: 190, month: 6, kind: 'MONTH_END' },
|
||||
});
|
||||
await page.getByRole('button', { name: '다음 50개 불러오기' }).click();
|
||||
await expect
|
||||
.poll(() => requests.filter((r) => r.operation === 'playAudit.generals').at(-1)?.input)
|
||||
.toMatchObject({
|
||||
name: '감사',
|
||||
order: 'desc',
|
||||
cursor: 1,
|
||||
});
|
||||
await page.reload();
|
||||
await expect(page.getByLabel('장수 이름', { exact: true })).toHaveValue('감사');
|
||||
await expect(page.getByLabel('장수 번호 정렬', { exact: true })).toHaveValue('desc');
|
||||
await expect(page.getByRole('rowheader', { name: /감사장수/ })).toBeVisible();
|
||||
await capture(page, 'mobile-general-search');
|
||||
});
|
||||
|
||||
@@ -63,6 +63,8 @@ const appliedDiplomacy = computed(() => ({
|
||||
const nationId = ref('');
|
||||
const cityId = ref('');
|
||||
const population = ref('');
|
||||
const generalName = ref('');
|
||||
const generalOrder = ref<'asc' | 'desc'>('asc');
|
||||
const moment = ref('current');
|
||||
const year = ref(0);
|
||||
const month = ref(1);
|
||||
@@ -139,6 +141,8 @@ const readQuery = () => {
|
||||
otherNationId.value = route.query.otherNation ? String(numeric(route.query.otherNation, 0)) : '';
|
||||
nationId.value = route.query.nation ? String(numeric(route.query.nation, 0)) : '';
|
||||
cityId.value = route.query.city ? String(numeric(route.query.city, 0)) : '';
|
||||
generalName.value = typeof route.query.name === 'string' ? route.query.name : '';
|
||||
generalOrder.value = route.query.order === 'desc' ? 'desc' : 'asc';
|
||||
population.value = ['human', 'npc', 'troopNpc'].includes(String(route.query.population))
|
||||
? String(route.query.population)
|
||||
: '';
|
||||
@@ -171,6 +175,8 @@ const load = async (append = false) => {
|
||||
const response = await trpc.playAudit.generals.query({
|
||||
...filter,
|
||||
cityId: cityId.value === '' ? undefined : Number(cityId.value),
|
||||
name: generalName.value.trim() || undefined,
|
||||
order: generalOrder.value,
|
||||
population:
|
||||
population.value === 'human' || population.value === 'npc' || population.value === 'troopNpc'
|
||||
? population.value
|
||||
@@ -241,6 +247,8 @@ const apply = async () => {
|
||||
otherNation: tab.value === 'diplomacy' ? otherNationId.value || undefined : undefined,
|
||||
city: cityId.value || undefined,
|
||||
population: population.value || undefined,
|
||||
name: tab.value === 'generals' ? generalName.value.trim() || undefined : undefined,
|
||||
order: tab.value === 'generals' ? generalOrder.value : undefined,
|
||||
at: moment.value,
|
||||
year: String(year.value),
|
||||
month: String(month.value),
|
||||
@@ -269,6 +277,8 @@ const showCityGenerals = async (id: number) => {
|
||||
cityId.value = String(id);
|
||||
nationId.value = '';
|
||||
population.value = '';
|
||||
generalName.value = '';
|
||||
generalOrder.value = 'asc';
|
||||
await apply();
|
||||
};
|
||||
const moreNations = async () => {
|
||||
@@ -468,6 +478,19 @@ onMounted(async () => {
|
||||
</select></label
|
||||
>
|
||||
<template v-if="tab === 'generals'">
|
||||
<label
|
||||
>장수 이름<input v-model="generalName" maxlength="64" placeholder="이름 부분 검색"
|
||||
/></label>
|
||||
<label
|
||||
>장수 번호 정렬<select
|
||||
class="legacy-sort-select"
|
||||
v-model="generalOrder"
|
||||
aria-label="장수 번호 정렬"
|
||||
>
|
||||
<option value="asc">오름차순</option>
|
||||
<option value="desc">내림차순</option>
|
||||
</select></label
|
||||
>
|
||||
<label>도시 번호<input v-model="cityId" type="number" min="0" placeholder="모든 도시" /></label>
|
||||
<label
|
||||
>장수 분류<select class="legacy-sort-select" v-model="population">
|
||||
|
||||
Reference in New Issue
Block a user