217 lines
6.1 KiB
JavaScript
217 lines
6.1 KiB
JavaScript
import assert from 'node:assert/strict'
|
|
import test from 'node:test'
|
|
import { computed, effectScope, nextTick, ref } from 'vue'
|
|
import { useAdminMarkdownSlashMenuPosition } from '../composables/useAdminMarkdownSlashMenuPosition.js'
|
|
|
|
/**
|
|
* 테스트용 브라우저 window 객체를 설치한다.
|
|
* @returns {{ added: string[], removed: string[], restore: () => void }} 이벤트 기록과 복구 함수
|
|
*/
|
|
const installWindowStub = () => {
|
|
const previousWindow = globalThis.window
|
|
const added = []
|
|
const removed = []
|
|
|
|
globalThis.window = {
|
|
innerHeight: 800,
|
|
innerWidth: 1200,
|
|
addEventListener: (eventName) => {
|
|
added.push(eventName)
|
|
},
|
|
removeEventListener: (eventName) => {
|
|
removed.push(eventName)
|
|
}
|
|
}
|
|
|
|
return {
|
|
added,
|
|
removed,
|
|
restore: () => {
|
|
if (previousWindow === undefined) {
|
|
delete globalThis.window
|
|
return
|
|
}
|
|
|
|
globalThis.window = previousWindow
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 슬래시 메뉴 위치 composable 테스트 상태를 생성한다.
|
|
* @param {Object} [overrides] - 상태 재정의
|
|
* @returns {Object} 테스트 상태
|
|
*/
|
|
const createSlashMenuPositionHarness = (overrides = {}) => {
|
|
const activeMode = ref(overrides.activeMode ?? 'preview')
|
|
const markdownValue = ref(overrides.markdownValue ?? '/image')
|
|
const liveSlashSourceLine = ref(overrides.sourceLine ?? 0)
|
|
const liveSlashQuery = ref(overrides.query ?? 'image')
|
|
const liveSlashHighlightedIndex = ref(overrides.highlightedIndex ?? 0)
|
|
const liveSlashMenuPosition = ref({ top: 0, left: 0 })
|
|
const liveSlashMenuRef = ref(overrides.menuRef ?? null)
|
|
const liveSlashVisible = computed(() => liveSlashSourceLine.value !== null)
|
|
const visibleLiveSlashCommands = ref(overrides.commands ?? [{ id: 'image' }, { id: 'gallery' }])
|
|
const resetCalls = []
|
|
const clampCalls = []
|
|
const previewSurfaceEvents = []
|
|
const textareaEvents = []
|
|
const previewSurface = {
|
|
addEventListener: (eventName) => previewSurfaceEvents.push(`add:${eventName}`),
|
|
removeEventListener: (eventName) => previewSurfaceEvents.push(`remove:${eventName}`)
|
|
}
|
|
const textarea = {
|
|
scrollTop: 0,
|
|
scrollLeft: 0,
|
|
addEventListener: (eventName) => textareaEvents.push(`add:${eventName}`),
|
|
removeEventListener: (eventName) => textareaEvents.push(`remove:${eventName}`),
|
|
getBoundingClientRect: () => ({ top: 20, left: 30 })
|
|
}
|
|
|
|
return {
|
|
options: {
|
|
activeMode,
|
|
markdownValue,
|
|
liveSlashSourceLine,
|
|
liveSlashQuery,
|
|
liveSlashHighlightedIndex,
|
|
liveSlashMenuPosition,
|
|
liveSlashMenuRef,
|
|
liveSlashVisible,
|
|
visibleLiveSlashCommands,
|
|
resetLiveSlashHighlightedIndex: () => resetCalls.push('reset'),
|
|
clampLiveSlashHighlightedIndex: (commandCount) => clampCalls.push(commandCount),
|
|
getPreviewSourceLineElement: () => ({
|
|
getBoundingClientRect: () => ({ top: 100, bottom: 124, left: 48 })
|
|
}),
|
|
getPreviewSurfaceElement: () => previewSurface,
|
|
getWriteTextareaElement: () => textarea,
|
|
isClient: true
|
|
},
|
|
activeMode,
|
|
liveSlashSourceLine,
|
|
liveSlashQuery,
|
|
liveSlashHighlightedIndex,
|
|
liveSlashMenuPosition,
|
|
liveSlashMenuRef,
|
|
visibleLiveSlashCommands,
|
|
resetCalls,
|
|
clampCalls,
|
|
previewSurfaceEvents,
|
|
textareaEvents
|
|
}
|
|
}
|
|
|
|
test('관리자 Markdown 슬래시 메뉴 위치는 미리보기 줄 기준으로 갱신한다', async () => {
|
|
const windowStub = installWindowStub()
|
|
const scope = effectScope()
|
|
const harness = createSlashMenuPositionHarness({
|
|
menuRef: {
|
|
offsetHeight: 160,
|
|
offsetWidth: 240
|
|
}
|
|
})
|
|
|
|
try {
|
|
scope.run(() => {
|
|
const position = useAdminMarkdownSlashMenuPosition(harness.options)
|
|
position.updateLiveSlashMenuPosition()
|
|
})
|
|
|
|
await nextTick()
|
|
await nextTick()
|
|
|
|
assert.deepEqual(harness.liveSlashMenuPosition.value, {
|
|
top: 128,
|
|
left: 48
|
|
})
|
|
} finally {
|
|
scope.stop()
|
|
windowStub.restore()
|
|
}
|
|
})
|
|
|
|
test('관리자 Markdown 슬래시 메뉴 위치는 노출 상태에 맞춰 스크롤 리스너를 정리한다', async () => {
|
|
const windowStub = installWindowStub()
|
|
const scope = effectScope()
|
|
const harness = createSlashMenuPositionHarness()
|
|
|
|
try {
|
|
scope.run(() => {
|
|
useAdminMarkdownSlashMenuPosition(harness.options)
|
|
})
|
|
|
|
harness.liveSlashSourceLine.value = null
|
|
await nextTick()
|
|
harness.liveSlashSourceLine.value = 0
|
|
await nextTick()
|
|
harness.liveSlashSourceLine.value = null
|
|
await nextTick()
|
|
|
|
assert.deepEqual(windowStub.added, ['scroll', 'resize'])
|
|
assert.deepEqual(windowStub.removed.slice(-2), ['scroll', 'resize'])
|
|
assert.deepEqual(harness.previewSurfaceEvents, ['add:scroll', 'remove:scroll'])
|
|
assert.deepEqual(harness.textareaEvents, ['add:scroll', 'remove:scroll'])
|
|
} finally {
|
|
scope.stop()
|
|
windowStub.restore()
|
|
}
|
|
})
|
|
|
|
test('관리자 Markdown 슬래시 메뉴 하이라이트는 메뉴 안으로 스크롤된다', async () => {
|
|
const windowStub = installWindowStub()
|
|
const scope = effectScope()
|
|
const menu = {
|
|
offsetHeight: 160,
|
|
offsetWidth: 240,
|
|
scrollTop: 0,
|
|
clientHeight: 80,
|
|
querySelector: () => ({
|
|
offsetTop: 120,
|
|
offsetHeight: 32
|
|
})
|
|
}
|
|
const harness = createSlashMenuPositionHarness({
|
|
menuRef: {
|
|
getRootElement: () => menu
|
|
}
|
|
})
|
|
|
|
try {
|
|
scope.run(() => {
|
|
useAdminMarkdownSlashMenuPosition(harness.options)
|
|
})
|
|
|
|
harness.liveSlashHighlightedIndex.value = 1
|
|
await nextTick()
|
|
await nextTick()
|
|
|
|
assert.equal(menu.scrollTop, 72)
|
|
} finally {
|
|
scope.stop()
|
|
windowStub.restore()
|
|
}
|
|
})
|
|
|
|
test('관리자 Markdown 슬래시 메뉴 검색어와 명령 수 변경은 하이라이트를 보정한다', async () => {
|
|
const windowStub = installWindowStub()
|
|
const scope = effectScope()
|
|
const harness = createSlashMenuPositionHarness()
|
|
|
|
try {
|
|
scope.run(() => {
|
|
useAdminMarkdownSlashMenuPosition(harness.options)
|
|
})
|
|
|
|
harness.liveSlashQuery.value = 'callout'
|
|
harness.visibleLiveSlashCommands.value = [{ id: 'callout' }]
|
|
await nextTick()
|
|
|
|
assert.deepEqual(harness.resetCalls, ['reset'])
|
|
assert.deepEqual(harness.clampCalls, [1])
|
|
} finally {
|
|
scope.stop()
|
|
windowStub.restore()
|
|
}
|
|
})
|