관리자 페이지네이션 계산 테스트 추가
This commit is contained in:
@@ -1,4 +1,6 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
|
import { buildAdminPaginationItems, clampAdminPage } from '~/lib/admin-pagination.js'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
/** 현재 페이지 */
|
/** 현재 페이지 */
|
||||||
page: {
|
page: {
|
||||||
@@ -23,45 +25,10 @@ const emit = defineEmits(['update:page'])
|
|||||||
const totalPageCount = computed(() => Math.max(1, Number(props.pageCount) || 1))
|
const totalPageCount = computed(() => Math.max(1, Number(props.pageCount) || 1))
|
||||||
|
|
||||||
/** @type {import('vue').ComputedRef<number>} */
|
/** @type {import('vue').ComputedRef<number>} */
|
||||||
const currentPage = computed(() => Math.min(Math.max(Number(props.page) || 1, 1), totalPageCount.value))
|
const currentPage = computed(() => clampAdminPage(props.page, totalPageCount.value))
|
||||||
|
|
||||||
/** @type {import('vue').ComputedRef<Array<number|string>>} */
|
/** @type {import('vue').ComputedRef<Array<number|string>>} */
|
||||||
const paginationItems = computed(() => {
|
const paginationItems = computed(() => buildAdminPaginationItems(currentPage.value, totalPageCount.value))
|
||||||
const total = totalPageCount.value
|
|
||||||
const current = currentPage.value
|
|
||||||
|
|
||||||
if (total <= 7) {
|
|
||||||
return Array.from({ length: total }, (_, index) => index + 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
const pages = new Set([1, total, current - 1, current, current + 1])
|
|
||||||
|
|
||||||
if (current <= 4) {
|
|
||||||
pages.add(2)
|
|
||||||
pages.add(3)
|
|
||||||
pages.add(4)
|
|
||||||
pages.add(5)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (current >= total - 3) {
|
|
||||||
pages.add(total - 4)
|
|
||||||
pages.add(total - 3)
|
|
||||||
pages.add(total - 2)
|
|
||||||
pages.add(total - 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
const orderedPages = [...pages].filter((page) => page >= 1 && page <= total).sort((left, right) => left - right)
|
|
||||||
return orderedPages.reduce((items, page, index) => {
|
|
||||||
const previousPage = orderedPages[index - 1]
|
|
||||||
|
|
||||||
if (previousPage && page - previousPage > 1) {
|
|
||||||
items.push(`ellipsis-${previousPage}-${page}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
items.push(page)
|
|
||||||
return items
|
|
||||||
}, [])
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 페이지 변경을 요청한다.
|
* 페이지 변경을 요청한다.
|
||||||
@@ -69,7 +36,7 @@ const paginationItems = computed(() => {
|
|||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
const updatePage = (page) => {
|
const updatePage = (page) => {
|
||||||
emit('update:page', Math.min(Math.max(Number(page) || 1, 1), totalPageCount.value))
|
emit('update:page', clampAdminPage(page, totalPageCount.value))
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -46,6 +46,7 @@
|
|||||||
| lib/analytics-shared.js | 통계 추적 경로 필터·체류/스크롤 상수(클라이언트·서버 공용) |
|
| lib/analytics-shared.js | 통계 추적 경로 필터·체류/스크롤 상수(클라이언트·서버 공용) |
|
||||||
| lib/analytics.js | 서버 전용 visitor/session hash(`node:crypto`) |
|
| lib/analytics.js | 서버 전용 visitor/session hash(`node:crypto`) |
|
||||||
| lib/analytics-traffic.js | referrer·User-Agent 기반 유입원·디바이스·검색 키워드 축약 분류 |
|
| lib/analytics-traffic.js | referrer·User-Agent 기반 유입원·디바이스·검색 키워드 축약 분류 |
|
||||||
|
| lib/admin-pagination.js | 관리자 번호형 페이지네이션 페이지 보정·ellipsis 항목 계산 |
|
||||||
| lib/csp-report.js | CSP 보고 Content-Type·32KB/20건 제한 상수, payload 검증, 문서 경로·차단 origin·브라우저 계열 최소화 |
|
| lib/csp-report.js | CSP 보고 Content-Type·32KB/20건 제한 상수, payload 검증, 문서 경로·차단 origin·브라우저 계열 최소화 |
|
||||||
| lib/media-catalog.js | 업로드 URL·저장 키 정규화, 미디어 종류·원본/파생 역할 판별, 콘텐츠 미디어 참조 추출, PostgreSQL LIKE 리터럴 이스케이프 |
|
| lib/media-catalog.js | 업로드 URL·저장 키 정규화, 미디어 종류·원본/파생 역할 판별, 콘텐츠 미디어 참조 추출, PostgreSQL LIKE 리터럴 이스케이프 |
|
||||||
| lib/social-links.js | 사이트 설정 SNS 링크 아이콘 프리셋·사용자 SVG 정리·URL 자동 보정·레거시 JSON 문자열 복구·공개 노출 목록 생성 |
|
| lib/social-links.js | 사이트 설정 SNS 링크 아이콘 프리셋·사용자 SVG 정리·URL 자동 보정·레거시 JSON 문자열 복구·공개 노출 목록 생성 |
|
||||||
@@ -96,6 +97,7 @@
|
|||||||
| tests/multipart-file-upload.test.js | multipart 스트리밍·제한 단위 회귀 테스트 |
|
| tests/multipart-file-upload.test.js | multipart 스트리밍·제한 단위 회귀 테스트 |
|
||||||
| tests/csp-report.test.js | CSP URL 최소화·payload 형식 단위 회귀 테스트 |
|
| tests/csp-report.test.js | CSP URL 최소화·payload 형식 단위 회귀 테스트 |
|
||||||
| tests/media-catalog.test.js | 미디어 URL·역할·참조·LIKE 이스케이프 단위 회귀 테스트 |
|
| tests/media-catalog.test.js | 미디어 URL·역할·참조·LIKE 이스케이프 단위 회귀 테스트 |
|
||||||
|
| tests/admin-pagination.test.js | 관리자 페이지네이션 페이지 보정·ellipsis 항목 단위 테스트 |
|
||||||
| tests/playwright/api/core-api.spec.js | 인증·부분 설정 저장·공개 목록·CSP·미디어 reconcile/fallback·세션 무효화 API 통합 10건 |
|
| tests/playwright/api/core-api.spec.js | 인증·부분 설정 저장·공개 목록·CSP·미디어 reconcile/fallback·세션 무효화 API 통합 10건 |
|
||||||
| tests/playwright/e2e/admin-guard-and-settings.spec.js | 설정 카드 독립 저장과 미저장 이탈 취소 브라우저 E2E 2건 |
|
| tests/playwright/e2e/admin-guard-and-settings.spec.js | 설정 카드 독립 저장과 미저장 이탈 취소 브라우저 E2E 2건 |
|
||||||
| tests/playwright/e2e/admin-publish.spec.js | 관리자 로그인·게시물 발행·공개 화면 확인 브라우저 E2E 1건 |
|
| tests/playwright/e2e/admin-publish.spec.js | 관리자 로그인·게시물 발행·공개 화면 확인 브라우저 E2E 1건 |
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
# 업데이트 이력
|
# 업데이트 이력
|
||||||
|
|
||||||
|
## v1.11.10
|
||||||
|
|
||||||
|
- 관리자 페이지네이션 계산 로직을 `lib/admin-pagination.js` 순수 함수로 분리.
|
||||||
|
- 관리자 페이지네이션 페이지 보정·ellipsis 계산 단위 테스트 추가.
|
||||||
|
|
||||||
## v1.11.9
|
## v1.11.9
|
||||||
|
|
||||||
- 관리자 공용 select 입력이 숫자 옵션 값을 보존하도록 보완.
|
- 관리자 공용 select 입력이 숫자 옵션 값을 보존하도록 보완.
|
||||||
|
|||||||
53
lib/admin-pagination.js
Normal file
53
lib/admin-pagination.js
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
/**
|
||||||
|
* 페이지 값을 유효한 범위로 보정한다.
|
||||||
|
* @param {number|string} page - 보정할 페이지
|
||||||
|
* @param {number|string} pageCount - 전체 페이지 수
|
||||||
|
* @returns {number} 보정된 페이지
|
||||||
|
*/
|
||||||
|
export const clampAdminPage = (page, pageCount) => {
|
||||||
|
const totalPageCount = Math.max(1, Number(pageCount) || 1)
|
||||||
|
return Math.min(Math.max(Number(page) || 1, 1), totalPageCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 관리자 번호형 페이지네이션 항목을 생성한다.
|
||||||
|
* @param {number|string} page - 현재 페이지
|
||||||
|
* @param {number|string} pageCount - 전체 페이지 수
|
||||||
|
* @returns {Array<number|string>} 페이지 번호와 ellipsis 항목
|
||||||
|
*/
|
||||||
|
export const buildAdminPaginationItems = (page, pageCount) => {
|
||||||
|
const total = Math.max(1, Number(pageCount) || 1)
|
||||||
|
const current = clampAdminPage(page, total)
|
||||||
|
|
||||||
|
if (total <= 7) {
|
||||||
|
return Array.from({ length: total }, (_, index) => index + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const pages = new Set([1, total, current - 1, current, current + 1])
|
||||||
|
|
||||||
|
if (current <= 4) {
|
||||||
|
pages.add(2)
|
||||||
|
pages.add(3)
|
||||||
|
pages.add(4)
|
||||||
|
pages.add(5)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current >= total - 3) {
|
||||||
|
pages.add(total - 4)
|
||||||
|
pages.add(total - 3)
|
||||||
|
pages.add(total - 2)
|
||||||
|
pages.add(total - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const orderedPages = [...pages].filter((item) => item >= 1 && item <= total).sort((left, right) => left - right)
|
||||||
|
return orderedPages.reduce((items, item, index) => {
|
||||||
|
const previousPage = orderedPages[index - 1]
|
||||||
|
|
||||||
|
if (previousPage && item - previousPage > 1) {
|
||||||
|
items.push(`ellipsis-${previousPage}-${item}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
items.push(item)
|
||||||
|
return items
|
||||||
|
}, [])
|
||||||
|
}
|
||||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "sori.studio",
|
"name": "sori.studio",
|
||||||
"version": "1.11.9",
|
"version": "1.11.10",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "sori.studio",
|
"name": "sori.studio",
|
||||||
"version": "1.11.9",
|
"version": "1.11.10",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nuxtjs/tailwindcss": "^6.14.0",
|
"@nuxtjs/tailwindcss": "^6.14.0",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "sori.studio",
|
"name": "sori.studio",
|
||||||
"version": "1.11.9",
|
"version": "1.11.10",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"imports": {
|
"imports": {
|
||||||
|
|||||||
26
tests/admin-pagination.test.js
Normal file
26
tests/admin-pagination.test.js
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import assert from 'node:assert/strict'
|
||||||
|
import test from 'node:test'
|
||||||
|
import { buildAdminPaginationItems, clampAdminPage } from '../lib/admin-pagination.js'
|
||||||
|
|
||||||
|
test('관리자 페이지 값을 유효 범위로 보정한다', () => {
|
||||||
|
assert.equal(clampAdminPage(0, 10), 1)
|
||||||
|
assert.equal(clampAdminPage(12, 10), 10)
|
||||||
|
assert.equal(clampAdminPage('4', 10), 4)
|
||||||
|
assert.equal(clampAdminPage('abc', 10), 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('전체 페이지가 적으면 모든 페이지 번호를 표시한다', () => {
|
||||||
|
assert.deepEqual(buildAdminPaginationItems(3, 7), [1, 2, 3, 4, 5, 6, 7])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('첫 구간에서는 앞쪽 페이지를 펼치고 끝 페이지를 유지한다', () => {
|
||||||
|
assert.deepEqual(buildAdminPaginationItems(2, 12), [1, 2, 3, 4, 5, 'ellipsis-5-12', 12])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('중간 구간에서는 현재 페이지 주변과 양 끝을 유지한다', () => {
|
||||||
|
assert.deepEqual(buildAdminPaginationItems(6, 12), [1, 'ellipsis-1-5', 5, 6, 7, 'ellipsis-7-12', 12])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('끝 구간에서는 첫 페이지와 뒤쪽 페이지를 펼친다', () => {
|
||||||
|
assert.deepEqual(buildAdminPaginationItems(11, 12), [1, 'ellipsis-1-8', 8, 9, 10, 11, 12])
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user