Files
sori.studio/tests/markdown-slash-commands.test.js

246 lines
7.7 KiB
JavaScript

import assert from 'node:assert/strict'
import test from 'node:test'
import {
addSlashSuppressedLine,
clampSlashHighlightedIndex,
createSlashCommandApplyPlan,
getMediaPickerKindFromSlashAction,
getNextSlashHighlightedIndex,
getSlashCommandFocusTarget,
getSlashCommandTextareaSelection,
getSlashMenuKeyboardAction,
insertMarkdownLinesAtSlashTarget,
isMediaSlashAction,
parseSlashInput,
removeSlashSuppressedLine,
resolveSlashCommand,
resolveSlashCommandFromInputValue,
shouldCaptureSlashMenuKeyboardAction,
shouldIgnorePendingMediaSlashCommit
} from '../lib/markdown-slash-commands.js'
test('슬래시 입력은 단일 줄 명령만 파싱한다', () => {
assert.deepEqual(parseSlashInput('/image'), {
query: 'image',
raw: '/image'
})
assert.equal(parseSlashInput(' /image'), null)
assert.equal(parseSlashInput('/image\n다음 줄'), null)
})
test('정확한 슬래시 명령과 미디어 액션을 판별한다', () => {
assert.equal(resolveSlashCommand('image')?.action, 'media-image')
assert.equal(resolveSlashCommand('img')?.id, 'image')
assert.equal(resolveSlashCommandFromInputValue('/image')?.id, 'image')
assert.equal(resolveSlashCommandFromInputValue('/ㄴㅇ'), null)
assert.equal(resolveSlashCommandFromInputValue(' /image'), null)
assert.equal(isMediaSlashAction('media-image'), true)
assert.equal(isMediaSlashAction('lines'), false)
assert.equal(getMediaPickerKindFromSlashAction('media-image'), 'image')
assert.equal(getMediaPickerKindFromSlashAction('media-gallery'), 'gallery')
assert.equal(getMediaPickerKindFromSlashAction('lines'), '')
})
test('슬래시 명령 적용 계획은 미디어와 줄 삽입을 구분한다', () => {
assert.deepEqual(createSlashCommandApplyPlan({
command: resolveSlashCommand('image'),
sourceLine: 2
}), {
kind: 'media',
line: 2,
mediaKind: 'image'
})
const headingPlan = createSlashCommandApplyPlan({
command: resolveSlashCommand('h2'),
sourceLine: 4
})
assert.deepEqual(headingPlan, {
kind: 'lines',
line: 4,
lines: ['## ']
})
assert.notEqual(headingPlan.lines, resolveSlashCommand('h2')?.lines)
})
test('슬래시 명령 적용 계획은 적용할 수 없는 입력을 무시한다', () => {
assert.deepEqual(createSlashCommandApplyPlan({
command: null,
sourceLine: 1
}), {
kind: 'none'
})
assert.deepEqual(createSlashCommandApplyPlan({
command: resolveSlashCommand('image'),
sourceLine: null
}), {
kind: 'none'
})
assert.deepEqual(createSlashCommandApplyPlan({
command: {
id: 'broken',
label: '깨진 명령',
description: '',
keywords: [],
action: 'unknown'
},
sourceLine: 1
}), {
kind: 'none'
})
})
test('미디어 슬래시 명령 지연 commit은 같은 줄 slash 원문일 때만 무시한다', () => {
assert.equal(shouldIgnorePendingMediaSlashCommit({
pendingInsertLine: 3,
startLine: 3,
replacementLines: ['/image']
}), true)
assert.equal(shouldIgnorePendingMediaSlashCommit({
pendingInsertLine: 3,
startLine: 4,
replacementLines: ['/image']
}), false)
assert.equal(shouldIgnorePendingMediaSlashCommit({
pendingInsertLine: 3,
startLine: 3,
replacementLines: ['본문']
}), false)
})
test('슬래시 제외 줄 Set은 기존 값을 보존한 새 Set으로 갱신한다', () => {
const source = new Set([1, 2])
const added = addSlashSuppressedLine(source, 3)
const removed = removeSlashSuppressedLine(added, 2)
assert.deepEqual([...source], [1, 2])
assert.deepEqual([...added], [1, 2, 3])
assert.deepEqual([...removed], [1, 3])
assert.notEqual(added, source)
assert.notEqual(removed, added)
})
test('슬래시 제외 줄 Set은 유효하지 않은 줄 번호를 무시한다', () => {
assert.deepEqual([...addSlashSuppressedLine(new Set([1]), Number.NaN)], [1])
assert.deepEqual([...removeSlashSuppressedLine(new Set([1]), Number.NaN)], [1])
})
test('슬래시 메뉴 하이라이트 인덱스는 명령 목록 범위 안으로 보정한다', () => {
assert.equal(clampSlashHighlightedIndex(3, 5), 3)
assert.equal(clampSlashHighlightedIndex(7, 5), 4)
assert.equal(clampSlashHighlightedIndex(-2, 5), 0)
assert.equal(clampSlashHighlightedIndex(2, 0), 0)
assert.equal(clampSlashHighlightedIndex(Number.NaN, Number.NaN), 0)
})
test('슬래시 메뉴 하이라이트 인덱스는 방향키로 순환한다', () => {
assert.equal(getNextSlashHighlightedIndex({ currentIndex: 1, commandCount: 3, key: 'ArrowDown' }), 2)
assert.equal(getNextSlashHighlightedIndex({ currentIndex: 2, commandCount: 3, key: 'ArrowDown' }), 0)
assert.equal(getNextSlashHighlightedIndex({ currentIndex: 0, commandCount: 3, key: 'ArrowUp' }), 2)
assert.equal(getNextSlashHighlightedIndex({ currentIndex: 8, commandCount: 3, key: 'ArrowUp' }), 1)
assert.equal(getNextSlashHighlightedIndex({ currentIndex: 1, commandCount: 0, key: 'ArrowDown' }), 0)
assert.equal(getNextSlashHighlightedIndex({ currentIndex: 1, commandCount: 3, key: 'Enter' }), 1)
})
test('슬래시 메뉴 키보드 입력은 메뉴 동작으로 분류한다', () => {
assert.equal(getSlashMenuKeyboardAction('ArrowDown'), 'navigate')
assert.equal(getSlashMenuKeyboardAction('ArrowUp'), 'navigate')
assert.equal(getSlashMenuKeyboardAction('Enter'), 'apply')
assert.equal(getSlashMenuKeyboardAction('Escape'), 'cancel')
assert.equal(getSlashMenuKeyboardAction('Tab'), '')
})
test('슬래시 메뉴 키보드 입력은 적용 가능한 명령이 있을 때만 가로챈다', () => {
assert.equal(shouldCaptureSlashMenuKeyboardAction({
action: 'navigate',
commandCount: 2
}), true)
assert.equal(shouldCaptureSlashMenuKeyboardAction({
action: 'apply',
commandCount: 1
}), true)
assert.equal(shouldCaptureSlashMenuKeyboardAction({
action: 'navigate',
commandCount: 0
}), false)
assert.equal(shouldCaptureSlashMenuKeyboardAction({
action: 'apply',
commandCount: 0
}), false)
assert.equal(shouldCaptureSlashMenuKeyboardAction({
action: 'cancel',
commandCount: 0
}), true)
assert.equal(shouldCaptureSlashMenuKeyboardAction({
action: '',
commandCount: 1
}), false)
})
test('슬래시 명령 줄에 미디어를 삽입할 때 원문 줄을 교체한다', () => {
const markdown = [
'첫 줄',
'/image',
'마지막 줄'
].join('\n')
assert.equal(insertMarkdownLinesAtSlashTarget(markdown, 1, ['![](/uploads/a.png)']), [
'첫 줄',
'![](/uploads/a.png)',
'마지막 줄'
].join('\n'))
})
test('일반 줄에는 지정 위치 앞에 마크다운 줄을 삽입한다', () => {
assert.equal(insertMarkdownLinesAtSlashTarget('첫 줄\n마지막 줄', 1, ['중간 줄']), '첫 줄\n중간 줄\n마지막 줄')
})
test('슬래시 명령 포커스 대상은 단일 줄과 블록 삽입을 구분한다', () => {
assert.deepEqual(getSlashCommandFocusTarget(2, ['## ']), {
line: 2,
offset: 3
})
assert.deepEqual(getSlashCommandFocusTarget(2, ['```', '', '```']), {
line: 3,
offset: 0
})
})
test('슬래시 명령 textarea 선택 위치는 줄 시작과 포커스 오프셋을 합산한다', () => {
const markdown = [
'첫 줄',
'둘째 줄',
'## ',
'마지막 줄'
].join('\n')
assert.deepEqual(getSlashCommandTextareaSelection({
markdown,
startLine: 2,
lines: ['## ']
}), {
start: 12,
end: 12
})
})
test('슬래시 명령 textarea 선택 위치는 여러 줄 블록의 내부 빈 줄을 가리킨다', () => {
const markdown = [
'첫 줄',
'```',
'',
'```',
'마지막 줄'
].join('\n')
assert.deepEqual(getSlashCommandTextareaSelection({
markdown,
startLine: 1,
lines: ['```', '', '```']
}), {
start: 8,
end: 8
})
})