test: 完善项目单元测试,提高覆盖率
This commit is contained in:
parent
1954d27eff
commit
267128e1bc
|
@ -10,7 +10,7 @@ import Editor from '../../../editor/index'
|
|||
/**
|
||||
* 生成 Tooltip 的显示隐藏函数
|
||||
*/
|
||||
function createShowHideFn(editor: Editor) {
|
||||
export function createShowHideFn(editor: Editor) {
|
||||
let tooltip: Tooltip | null
|
||||
|
||||
/**
|
||||
|
@ -65,6 +65,7 @@ function createShowHideFn(editor: Editor) {
|
|||
* @param e
|
||||
* @param editor
|
||||
*/
|
||||
/* istanbul ignore next */
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
function preEnterListener(e: KeyboardEvent, editor: Editor) {
|
||||
// 获取当前标签元素
|
||||
|
|
|
@ -62,7 +62,7 @@ function showDargBox($textContainerElem: DomElement, $drag: DomElement, $img: Do
|
|||
/**
|
||||
* 生成图片拖拽框的 显示/隐藏 函数
|
||||
*/
|
||||
function createShowHideFn(editor: Editor) {
|
||||
export function createShowHideFn(editor: Editor) {
|
||||
const $textContainerElem = editor.$textContainerElem
|
||||
let $imgTarget: DomElement
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import Editor from '../../../editor/index'
|
|||
/**
|
||||
* 生成 Tooltip 的显示隐藏函数
|
||||
*/
|
||||
function createShowHideFn(editor: Editor) {
|
||||
export function createShowHideFn(editor: Editor) {
|
||||
let tooltip: Tooltip | null
|
||||
const t = (text: string, prefix: string = ''): string => {
|
||||
return editor.i18next.t(prefix + text)
|
||||
|
|
|
@ -141,6 +141,13 @@ class UploadImg {
|
|||
config.customAlert(`${t('图片验证未通过')}: \n` + errInfos.join('\n'), 'warning')
|
||||
return
|
||||
}
|
||||
|
||||
// 如果过滤后文件列表为空直接返回
|
||||
if (resultFiles.length === 0) {
|
||||
config.customAlert(t('传入的文件不合法'), 'warning')
|
||||
return
|
||||
}
|
||||
|
||||
if (resultFiles.length > maxLength) {
|
||||
config.customAlert(t('一次最多上传') + maxLength + t('张图片'), 'warning')
|
||||
return
|
||||
|
|
|
@ -44,7 +44,7 @@ function getChildrenJSON($elem: DomElement): NodeListType {
|
|||
elemResult.tag = curElem.nodeName.toLowerCase()
|
||||
// attr
|
||||
const attrData = []
|
||||
const attrList = curElem.attributes || []
|
||||
const attrList = curElem.attributes
|
||||
const attrListLength = attrList.length || 0
|
||||
for (let i = 0; i < attrListLength; i++) {
|
||||
const attr = attrList[i]
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
export const MOCK_FIEFOX_USER_AGENT =
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:82.0) Gecko/20100101 Firefox/82.0'
|
|
@ -0,0 +1,6 @@
|
|||
import { MOCK_FIEFOX_USER_AGENT } from './constants'
|
||||
|
||||
// 修改userAgent,模拟firefox
|
||||
Object.defineProperty(navigator, 'userAgent', {
|
||||
value: MOCK_FIEFOX_USER_AGENT,
|
||||
})
|
|
@ -0,0 +1,28 @@
|
|||
import { DomElement } from '../../src/utils/dom-core'
|
||||
|
||||
const EventType = {
|
||||
Event: Event,
|
||||
KeyBoardEvent: KeyboardEvent,
|
||||
MouseEvent: MouseEvent,
|
||||
// jest dom 没有ClipboardEvent,使用Event替代
|
||||
ClipboardEvent: Event,
|
||||
}
|
||||
|
||||
type EventTypeKey = keyof typeof EventType
|
||||
|
||||
export default function mockEventTrigger(
|
||||
$el: DomElement,
|
||||
type: string,
|
||||
eventType: EventTypeKey = 'Event',
|
||||
option?: any
|
||||
) {
|
||||
const EventConstruct = EventType[eventType]
|
||||
|
||||
const event = new EventConstruct(type, {
|
||||
view: window,
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
...option,
|
||||
})
|
||||
$el.elems[0].dispatchEvent(event)
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
export default function mockFile(option: any) {
|
||||
const name = option.name ?? 'mock.txt'
|
||||
const size = option.size ?? 1024
|
||||
const mimeType = option.mimeType || 'plain/txt'
|
||||
|
||||
function range(count: number) {
|
||||
let output = ''
|
||||
for (var i = 0; i < count; i++) {
|
||||
output += 'a'
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
const blob = new Blob([range(size)], { type: mimeType })
|
||||
|
||||
return new File([blob], name)
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
const xhrMockClass = (config: any) => ({
|
||||
open: jest.fn(),
|
||||
send: jest.fn(),
|
||||
setRequestHeader: jest.fn(),
|
||||
ontimeout: jest.fn(),
|
||||
upload: jest.fn(),
|
||||
onreadystatechange: jest.fn(),
|
||||
status: config.status,
|
||||
readyState: 4,
|
||||
responseText: config.res,
|
||||
})
|
||||
|
||||
export default xhrMockClass
|
|
@ -1,3 +1,8 @@
|
|||
/**
|
||||
* @description history compile
|
||||
* @author luochao
|
||||
*/
|
||||
|
||||
import createEditor from '../../../helpers/create-editor'
|
||||
import Editor from '../../../../src/editor'
|
||||
import compile, {
|
||||
|
@ -7,6 +12,7 @@ import compile, {
|
|||
compliePosition,
|
||||
} from '../../../../src/editor/history/data/node/compile'
|
||||
import { Compile } from '../../../../src/editor/history/data/type'
|
||||
import { UA } from '../../../../src/utils/util'
|
||||
|
||||
let editor: Editor
|
||||
|
||||
|
@ -27,11 +33,19 @@ function generateCompileData(mutationList: MutationRecord[]) {
|
|||
return mockData
|
||||
}
|
||||
|
||||
const originalValue = UA.isFirefox
|
||||
|
||||
describe('Editor history compile', () => {
|
||||
beforeEach(() => {
|
||||
editor = createEditor(document, 'div1')
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
Object.defineProperty(UA, 'isFirefox', {
|
||||
value: originalValue,
|
||||
})
|
||||
})
|
||||
|
||||
test('可以将MutationRecord生成Compile数据', done => {
|
||||
expect.assertions(3)
|
||||
|
||||
|
@ -49,4 +63,28 @@ describe('Editor history compile', () => {
|
|||
|
||||
editor.txt.html('<span>123</span>')
|
||||
})
|
||||
|
||||
test('如果在firefox中,如果有删除节点的mutaion,需要一些特殊处理', done => {
|
||||
expect.assertions(2)
|
||||
|
||||
Object.defineProperty(UA, 'isFirefox', {
|
||||
value: true,
|
||||
})
|
||||
|
||||
const observer = new MutationObserver((mutationList: MutationRecord[]) => {
|
||||
const compileData = compile(mutationList)
|
||||
expect(compileData instanceof Array).toBeTruthy()
|
||||
expect(compileData.length).toBe(4)
|
||||
done()
|
||||
})
|
||||
|
||||
const $textEl = editor.$textElem.elems[0]
|
||||
observer.observe($textEl, { attributes: true, childList: true, subtree: true })
|
||||
|
||||
// 添加多的dom变化情况,可以使得compile statement 执行到达率高
|
||||
editor.txt.html('<span id="test">123<i>456</i></span>')
|
||||
editor.txt.html('<h1>标题</h1><span id="test">123<i>456</i></span>')
|
||||
editor.txt.html('<span>123</span>')
|
||||
editor.txt.html('<span></span>')
|
||||
})
|
||||
})
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
/**
|
||||
* @description Editor history data html cache
|
||||
* @author luochao
|
||||
*/
|
||||
import createEditor from '../../../helpers/create-editor'
|
||||
import Editor from '../../../../src/editor'
|
||||
import HtmlCache from '../../../../src/editor/history/data/html'
|
||||
|
||||
let editor: Editor
|
||||
let htmlCache: HtmlCache
|
||||
describe('Editor history html cache', () => {
|
||||
beforeEach(() => {
|
||||
editor = createEditor(document, 'div1', '', {
|
||||
compatibleMode: () => true,
|
||||
})
|
||||
|
||||
htmlCache = new HtmlCache(editor)
|
||||
|
||||
htmlCache.observe()
|
||||
})
|
||||
|
||||
test('可以使用 HtmlCache 实现编辑器内容撤回', () => {
|
||||
const testHtml1 = '<span>123</span>'
|
||||
const testHtml2 = '<h1>456</h1>'
|
||||
|
||||
editor.txt.html(testHtml1)
|
||||
|
||||
expect(editor.$textElem.elems[0]).toContainHTML(testHtml1)
|
||||
|
||||
htmlCache.save()
|
||||
|
||||
editor.txt.html(testHtml2)
|
||||
|
||||
expect(editor.$textElem.elems[0]).toContainHTML(testHtml2)
|
||||
|
||||
htmlCache.save()
|
||||
|
||||
htmlCache.revoke()
|
||||
|
||||
expect(editor.$textElem.elems[0]).toContainHTML(testHtml1)
|
||||
})
|
||||
|
||||
test('可以使用 HtmlCache 撤回编辑器内容,撤回后还可以恢复', () => {
|
||||
const testHtml1 = '<span>123</span>'
|
||||
const testHtml2 = '<h1>456</h1>'
|
||||
|
||||
editor.txt.html(testHtml1)
|
||||
|
||||
htmlCache.save()
|
||||
|
||||
expect(editor.$textElem.elems[0]).toContainHTML(testHtml1)
|
||||
|
||||
editor.txt.html(testHtml2)
|
||||
|
||||
htmlCache.save()
|
||||
|
||||
const revokeRes = htmlCache.revoke()
|
||||
|
||||
expect(revokeRes).toBeTruthy()
|
||||
expect(editor.$textElem.elems[0]).not.toContainHTML(testHtml2)
|
||||
|
||||
const restoreRes = htmlCache.restore()
|
||||
expect(restoreRes).toBeTruthy()
|
||||
expect(editor.$textElem.elems[0]).toContainHTML(testHtml2)
|
||||
})
|
||||
|
||||
test('没有内容撤回时,调用revoke返回false', () => {
|
||||
const testHtml1 = '<span>123</span>'
|
||||
|
||||
editor.txt.html(testHtml1)
|
||||
|
||||
const res = htmlCache.revoke()
|
||||
expect(res).toBeFalsy()
|
||||
})
|
||||
|
||||
test('没有内容恢复时,调用restore返回false', () => {
|
||||
const testHtml1 = '<span>123</span>'
|
||||
|
||||
editor.txt.html(testHtml1)
|
||||
|
||||
const res = htmlCache.restore()
|
||||
expect(res).toBeFalsy()
|
||||
})
|
||||
})
|
|
@ -1,3 +1,8 @@
|
|||
/**
|
||||
* @description history decompile
|
||||
* @author luochao
|
||||
*/
|
||||
|
||||
import createEditor from '../../../helpers/create-editor'
|
||||
import Editor from '../../../../src/editor'
|
||||
import compile from '../../../../src/editor/history/data/node/compile'
|
||||
|
@ -10,7 +15,7 @@ describe('Editor history decompile', () => {
|
|||
editor = createEditor(document, 'div1')
|
||||
})
|
||||
|
||||
test('可以通过revoke方法撤销编辑器设置的内容', done => {
|
||||
test('可以通过revoke方法撤销编辑器设置的html', done => {
|
||||
expect.assertions(3)
|
||||
|
||||
const observer = new MutationObserver((mutationList: MutationRecord[]) => {
|
||||
|
@ -33,6 +38,58 @@ describe('Editor history decompile', () => {
|
|||
editor.txt.html('<span>123</span>')
|
||||
})
|
||||
|
||||
test('可以通过revoke方法撤销编辑器设置的属性', done => {
|
||||
expect.assertions(3)
|
||||
|
||||
const observer = new MutationObserver((mutationList: MutationRecord[]) => {
|
||||
const compileData = compile(mutationList)
|
||||
|
||||
expect(compileData instanceof Array).toBeTruthy()
|
||||
expect(compileData.length).toBe(1)
|
||||
|
||||
observer.disconnect()
|
||||
|
||||
revoke(compileData)
|
||||
|
||||
expect(editor.$textElem.html()).toEqual('<span>123</span>')
|
||||
done()
|
||||
})
|
||||
|
||||
const $textEl = editor.$textElem.elems[0]
|
||||
|
||||
editor.txt.html('<span>123</span>')
|
||||
|
||||
observer.observe($textEl, { attributes: true, childList: true, subtree: true })
|
||||
|
||||
editor.txt.html('<span id="123">123</span>')
|
||||
})
|
||||
|
||||
test('可以通过revoke方法撤销编辑器设置的文本', done => {
|
||||
expect.assertions(3)
|
||||
|
||||
const observer = new MutationObserver((mutationList: MutationRecord[]) => {
|
||||
const compileData = compile(mutationList)
|
||||
|
||||
expect(compileData instanceof Array).toBeTruthy()
|
||||
expect(compileData.length).toBe(1)
|
||||
|
||||
observer.disconnect()
|
||||
|
||||
revoke(compileData)
|
||||
|
||||
expect(editor.$textElem.html()).toEqual('<span></span>')
|
||||
done()
|
||||
})
|
||||
|
||||
const $textEl = editor.$textElem.elems[0]
|
||||
|
||||
editor.txt.html('<span></span>')
|
||||
|
||||
observer.observe($textEl, { attributes: true, childList: true, subtree: true })
|
||||
|
||||
editor.txt.html('<span>123</span>')
|
||||
})
|
||||
|
||||
test('可以通过restore方法恢复撤销的内容', done => {
|
||||
expect.assertions(2)
|
||||
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
/**
|
||||
* @description Editor catalog scroll to head
|
||||
* @author luochao
|
||||
*/
|
||||
|
||||
import Editor from '../../../../src/editor'
|
||||
import scrollToHead from '../../../../src/editor/init-fns/scroll-to-head'
|
||||
import { TCatalog } from '../../../../src/config/events'
|
||||
import $ from 'jquery'
|
||||
|
||||
const catalogHtml = `<h1>标题一</h1>
|
||||
<p>
|
||||
正文balabala
|
||||
</p>
|
||||
<h2>标题二</h2>
|
||||
<p>
|
||||
正文balabala
|
||||
</p>
|
||||
<h3>标题三</h3>
|
||||
<p>
|
||||
正文balabala
|
||||
</p>
|
||||
<h4>标题四</h4>
|
||||
<p>
|
||||
正文balabala
|
||||
</p>
|
||||
<h2>标题五</h2>
|
||||
<p>
|
||||
正文balabala
|
||||
</p>
|
||||
<h3>标题六</h3>
|
||||
<p>
|
||||
正文balabala
|
||||
</p>
|
||||
<h3>标题七</h3>
|
||||
<p>
|
||||
正文balabala
|
||||
</p>`
|
||||
|
||||
let editor: Editor
|
||||
let testId = ''
|
||||
describe('Editor catalog', () => {
|
||||
beforeEach(() => {
|
||||
const toolbar = document.createElement('div')
|
||||
toolbar.id = 'toolbar'
|
||||
toolbar.innerHTML = catalogHtml
|
||||
|
||||
document.body.appendChild(toolbar)
|
||||
|
||||
const catalogContainer = document.createElement('div')
|
||||
catalogContainer.id = 'catalogContainer'
|
||||
document.body.appendChild(catalogContainer)
|
||||
|
||||
editor = new Editor('#toolbar')
|
||||
|
||||
editor.config.onCatalogChange = function (arr: TCatalog[]) {
|
||||
const lastItem = arr[arr.length - 1]
|
||||
const box = document.getElementById('catalogContainer')
|
||||
|
||||
if (box == null) return
|
||||
|
||||
const a = document.createElement('a')
|
||||
a.href = 'javascript:void(0)'
|
||||
a.innerText = lastItem.text
|
||||
testId = lastItem.id
|
||||
a.id = lastItem.id
|
||||
box.appendChild(a)
|
||||
}
|
||||
|
||||
editor.create()
|
||||
})
|
||||
|
||||
test('能滚动到指定的锚点', done => {
|
||||
expect.assertions(1)
|
||||
|
||||
const a = $(`${testId}`)
|
||||
|
||||
expect(a).not.toBeNull()
|
||||
|
||||
try {
|
||||
scrollToHead(editor, testId)
|
||||
} catch (err) {
|
||||
done()
|
||||
}
|
||||
})
|
||||
})
|
|
@ -0,0 +1,225 @@
|
|||
/**
|
||||
* @description 上传post方法
|
||||
* @author luochao
|
||||
*/
|
||||
|
||||
import post from '../../../src/editor/upload/upload-core'
|
||||
import mockXHR from '../../helpers/mock-xhr'
|
||||
|
||||
const API_URL = 'http://localhost:8881/api/upload-img'
|
||||
|
||||
const origilaXHR = window.XMLHttpRequest
|
||||
const deaultResponse = { status: 200, res: JSON.stringify({ data: ['url1'], errno: 0 }) }
|
||||
|
||||
const mockXMLHttpRequest = (resonse: any = deaultResponse) => {
|
||||
const mockObject = jest.fn().mockImplementation(() => mockXHR(resonse))
|
||||
|
||||
// @ts-ignore
|
||||
window.XMLHttpRequest = mockObject
|
||||
}
|
||||
|
||||
const createFormData = () => {
|
||||
const data = new FormData()
|
||||
data.append('name', 'name')
|
||||
data.append('filename', 'filename')
|
||||
return data
|
||||
}
|
||||
|
||||
describe('Editor upload core post', () => {
|
||||
afterAll(() => {
|
||||
window.XMLHttpRequest = origilaXHR
|
||||
})
|
||||
|
||||
test('能够发送简单的post请求,处理返回的json字符串', done => {
|
||||
mockXMLHttpRequest()
|
||||
|
||||
expect.assertions(2)
|
||||
|
||||
const data = createFormData()
|
||||
|
||||
const xhr = post(API_URL, {
|
||||
formData: data,
|
||||
onSuccess: (xhr: XMLHttpRequest, res: any) => {
|
||||
expect(res.data).toEqual(['url1'])
|
||||
expect(res.errno).toBe(0)
|
||||
done()
|
||||
},
|
||||
})
|
||||
// @ts-ignore
|
||||
xhr.onreadystatechange()
|
||||
})
|
||||
|
||||
test('能够发送简单的post请求,处理返回的json对象', done => {
|
||||
mockXMLHttpRequest()
|
||||
|
||||
expect.assertions(2)
|
||||
|
||||
const data = createFormData()
|
||||
|
||||
const xhr = post(API_URL, {
|
||||
formData: data,
|
||||
onSuccess: (xhr: XMLHttpRequest, res: any) => {
|
||||
expect(res.data).toEqual(['url1'])
|
||||
expect(res.errno).toBe(0)
|
||||
done()
|
||||
},
|
||||
})
|
||||
// @ts-ignore
|
||||
xhr.onreadystatechange()
|
||||
})
|
||||
|
||||
test('发送请求失败后会有错误回调', done => {
|
||||
mockXMLHttpRequest({ status: 500, res: JSON.stringify({ data: 'error', errno: 1 }) })
|
||||
|
||||
expect.assertions(2)
|
||||
|
||||
const data = createFormData()
|
||||
|
||||
const successFn = jest.fn()
|
||||
const errorFn = jest.fn()
|
||||
|
||||
const xhr = post(API_URL, {
|
||||
formData: data,
|
||||
onSuccess: successFn,
|
||||
onError: errorFn,
|
||||
})
|
||||
|
||||
// @ts-ignore
|
||||
xhr.onreadystatechange()
|
||||
|
||||
setTimeout(() => {
|
||||
expect(successFn).not.toBeCalled()
|
||||
expect(errorFn).toBeCalled()
|
||||
done()
|
||||
}, 1000)
|
||||
})
|
||||
|
||||
test('发送请求能够监听进度变化', done => {
|
||||
mockXMLHttpRequest()
|
||||
|
||||
expect.assertions(2)
|
||||
|
||||
const data = createFormData()
|
||||
|
||||
const successFn = jest.fn()
|
||||
const progressFn = jest.fn()
|
||||
|
||||
const xhr = post(API_URL, {
|
||||
formData: data,
|
||||
onSuccess: successFn,
|
||||
onProgress: progressFn,
|
||||
})
|
||||
|
||||
// @ts-ignore
|
||||
xhr.upload.onprogress({ loaded: 50, total: 100 })
|
||||
|
||||
expect(progressFn).toBeCalled()
|
||||
|
||||
// @ts-ignore
|
||||
xhr.onreadystatechange()
|
||||
|
||||
setTimeout(() => {
|
||||
expect(successFn).toBeCalled()
|
||||
done()
|
||||
}, 1000)
|
||||
})
|
||||
|
||||
test('发送请求能够自定义请求头', done => {
|
||||
mockXMLHttpRequest()
|
||||
|
||||
expect.assertions(1)
|
||||
|
||||
const data = createFormData()
|
||||
|
||||
const successFn = jest.fn()
|
||||
|
||||
const xhr = post(API_URL, {
|
||||
formData: data,
|
||||
onSuccess: successFn,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
|
||||
// @ts-ignore
|
||||
xhr.onreadystatechange()
|
||||
|
||||
setTimeout(() => {
|
||||
expect(successFn).toBeCalled()
|
||||
done()
|
||||
}, 1000)
|
||||
})
|
||||
|
||||
test('发送请求可以配置超时时间,并配置超时回调', done => {
|
||||
mockXMLHttpRequest()
|
||||
|
||||
expect.assertions(2)
|
||||
|
||||
const data = createFormData()
|
||||
|
||||
const successFn = jest.fn()
|
||||
const timeoutFn = jest.fn()
|
||||
|
||||
const xhr = post(API_URL, {
|
||||
formData: data,
|
||||
onSuccess: successFn,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
timeout: 1000,
|
||||
onTimeout: timeoutFn,
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
// @ts-ignore
|
||||
xhr.ontimeout()
|
||||
|
||||
expect(timeoutFn).toBeCalled()
|
||||
expect(successFn).not.toBeCalled()
|
||||
|
||||
done()
|
||||
}, 1000)
|
||||
})
|
||||
|
||||
test('发送请求前可以添加beforeSend检验', () => {
|
||||
mockXMLHttpRequest()
|
||||
|
||||
const data = createFormData()
|
||||
|
||||
const successFn = jest.fn()
|
||||
|
||||
const msg = post(API_URL, {
|
||||
formData: data,
|
||||
onSuccess: successFn,
|
||||
beforeSend: () => ({ prevent: true, msg: '阻止发送请求' }),
|
||||
})
|
||||
|
||||
expect(msg).toBe('阻止发送请求')
|
||||
})
|
||||
|
||||
test('发送请求成功返回的结果如果不是json字符串,会通过fail回调处理', done => {
|
||||
mockXMLHttpRequest({ status: 200, res: '{test: 123}' })
|
||||
|
||||
expect.assertions(2)
|
||||
|
||||
const data = createFormData()
|
||||
|
||||
const successFn = jest.fn()
|
||||
const failFn = jest.fn()
|
||||
|
||||
const xhr = post(API_URL, {
|
||||
formData: data,
|
||||
onSuccess: successFn,
|
||||
onFail: failFn,
|
||||
})
|
||||
|
||||
// @ts-ignore
|
||||
xhr.onreadystatechange()
|
||||
|
||||
setTimeout(() => {
|
||||
expect(successFn).not.toBeCalled()
|
||||
expect(failFn).toBeCalled()
|
||||
done()
|
||||
}, 1000)
|
||||
})
|
||||
})
|
|
@ -0,0 +1,68 @@
|
|||
/**
|
||||
* @description Editor upload progress
|
||||
* @author luochao
|
||||
*/
|
||||
|
||||
import Progress from '../../../src/editor/upload/progress'
|
||||
import Editor from '../../../src/editor'
|
||||
import createEditor from '../../helpers/create-editor'
|
||||
import $ from 'jquery'
|
||||
|
||||
let editor: Editor
|
||||
|
||||
const progressClassName = '.w-e-progress'
|
||||
|
||||
let id = 1
|
||||
describe('Editor upload progress', () => {
|
||||
beforeEach(() => {
|
||||
editor = createEditor(document, `div${id++}`)
|
||||
})
|
||||
|
||||
test('在编辑器中展示 progress bar', () => {
|
||||
const progress = new Progress(editor)
|
||||
|
||||
progress.show(0.5)
|
||||
|
||||
const progressBar = $(`#div${id - 1}`).find(progressClassName)
|
||||
expect(progressBar.length).toBe(1)
|
||||
expect(progressBar.get(0)).toHaveStyle('width:50%')
|
||||
expect(editor.$textContainerElem.elems[0]).toContainHTML(progressBar.get(0).innerHTML)
|
||||
})
|
||||
|
||||
test('多次调用不会重复在编辑器中展示 progress bar', () => {
|
||||
const progress = new Progress(editor)
|
||||
|
||||
progress.show(0.5)
|
||||
progress.show(0.7)
|
||||
|
||||
const progressBar = $(`#div${id - 1}`).find(progressClassName)
|
||||
expect(progressBar.length).toBe(1)
|
||||
})
|
||||
|
||||
test('在编辑器中展示 progress bar,500ms后自动消失', done => {
|
||||
expect.assertions(2)
|
||||
|
||||
const progress = new Progress(editor)
|
||||
|
||||
progress.show(0.5)
|
||||
|
||||
const progressBar = $(`#div${id - 1}`).find(progressClassName)
|
||||
expect(progressBar.length).toBe(1)
|
||||
|
||||
setTimeout(() => {
|
||||
const progressBar = $(progressClassName)
|
||||
expect(progressBar.length).toBe(0)
|
||||
done()
|
||||
}, 500)
|
||||
})
|
||||
|
||||
test('如果设置的进度超过1进度长度样式将失效', () => {
|
||||
const progress = new Progress(editor)
|
||||
|
||||
progress.show(1.1)
|
||||
|
||||
const progressBar = $(progressClassName)
|
||||
expect(progressBar.length).toBe(1)
|
||||
expect(progressBar.get(0)).not.toHaveStyle('width:110%')
|
||||
})
|
||||
})
|
|
@ -0,0 +1,51 @@
|
|||
/**
|
||||
* @description code tooltip-bind-event
|
||||
* @author luochao
|
||||
*/
|
||||
|
||||
import Editor from '../../../src/editor'
|
||||
import $ from '../../../src/utils/dom-core'
|
||||
|
||||
import createEditor from '../../helpers/create-editor'
|
||||
import { createShowHideFn } from '../../../src/menus/code/bind-event/tooltip-event'
|
||||
|
||||
let editor: Editor
|
||||
let id = 1
|
||||
|
||||
describe('code bind event', () => {
|
||||
beforeEach(() => {
|
||||
editor = createEditor(document, `div${id++}`)
|
||||
})
|
||||
|
||||
test('调用 createShowHideFn 返回显示和隐藏code tooltip函数', () => {
|
||||
const fnObj = createShowHideFn(editor)
|
||||
|
||||
expect(fnObj.showCodeTooltip).toBeInstanceOf(Function)
|
||||
expect(fnObj.hideCodeTooltip).toBeInstanceOf(Function)
|
||||
})
|
||||
|
||||
test('调用 showCodeTooltip 方法展示code tooltip', () => {
|
||||
const fnObj = createShowHideFn(editor)
|
||||
const codeDom = $('<div></div>')
|
||||
|
||||
fnObj.showCodeTooltip(codeDom)
|
||||
|
||||
const tooltip = $(`#div${id - 1} .w-e-tooltip`)
|
||||
expect(tooltip.elems[0]).not.toBeNull()
|
||||
})
|
||||
|
||||
test('调用 hideCodeTooltip 方法隐藏code tooltip', () => {
|
||||
const fnObj = createShowHideFn(editor)
|
||||
const codeDom = $('<div></div>')
|
||||
|
||||
fnObj.showCodeTooltip(codeDom)
|
||||
|
||||
const tooltip1 = $(`#div${id - 1} .w-e-tooltip`)
|
||||
expect(tooltip1.elems[0]).not.toBeNull()
|
||||
|
||||
fnObj.hideCodeTooltip()
|
||||
|
||||
const tooltip2 = $(`#div${id - 1} .w-e-tooltip`)
|
||||
expect(tooltip2.elems[0]).toBeUndefined()
|
||||
})
|
||||
})
|
|
@ -0,0 +1,83 @@
|
|||
/**
|
||||
* @description droplist menu test
|
||||
* @author luochao
|
||||
*/
|
||||
|
||||
import DropListMenu from '../../../src/menus/menu-constructors/DropListMenu'
|
||||
import DropList from '../../../src/menus/menu-constructors/DropList'
|
||||
import createEditor from '../../helpers/create-editor'
|
||||
import $ from '../../../src/utils/dom-core'
|
||||
import dispatchEvent from '../../helpers/mock-dispatch-event'
|
||||
|
||||
let editor: ReturnType<typeof createEditor>
|
||||
let droplistMenu: DropListMenu
|
||||
let id = 1
|
||||
let menuEl: ReturnType<typeof $>
|
||||
|
||||
describe('dropList menu', () => {
|
||||
beforeEach(() => {
|
||||
editor = createEditor(document, `div${id++}`, '', {
|
||||
lang: 'en',
|
||||
})
|
||||
|
||||
const mockClickFn = jest.fn((value: string) => value)
|
||||
menuEl = $(`<div id="menu${id++}"></div>`)
|
||||
const conf = {
|
||||
title: '设置标题',
|
||||
type: 'list',
|
||||
width: 100,
|
||||
clickHandler: mockClickFn,
|
||||
list: [
|
||||
{
|
||||
value: 'test123',
|
||||
$elem: $('<span><i>test123</i></span>'),
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
droplistMenu = new DropListMenu(menuEl, editor, conf)
|
||||
})
|
||||
|
||||
test('初始化基本的下拉菜单', () => {
|
||||
expect(droplistMenu.dropList instanceof DropList).toBeTruthy()
|
||||
})
|
||||
|
||||
test('初始化基本的下拉菜单,模拟菜单mouseenter事件会展开下来菜单', done => {
|
||||
expect.assertions(2)
|
||||
|
||||
dispatchEvent(menuEl, 'mouseenter', 'MouseEvent')
|
||||
|
||||
setTimeout(() => {
|
||||
expect(droplistMenu.dropList.isShow).toBeTruthy()
|
||||
expect(menuEl.elems[0]).toHaveStyle(`z-index:${editor.zIndex.get('menu')}`)
|
||||
done()
|
||||
}, 300)
|
||||
})
|
||||
|
||||
test('初始化基本的下拉菜单,模拟菜单mouseleave事件会隐藏菜单', done => {
|
||||
expect.assertions(3)
|
||||
|
||||
dispatchEvent(menuEl, 'mouseenter', 'MouseEvent')
|
||||
|
||||
setTimeout(() => {
|
||||
expect(droplistMenu.dropList.isShow).toBeTruthy()
|
||||
|
||||
dispatchEvent(menuEl, 'mouseleave', 'MouseEvent')
|
||||
|
||||
setTimeout(() => {
|
||||
expect(droplistMenu.dropList.isShow).toBeFalsy()
|
||||
expect(menuEl.elems[0]).toHaveStyle(`z-index:auto`)
|
||||
done()
|
||||
}, 20)
|
||||
}, 300)
|
||||
})
|
||||
|
||||
test('初始化基本的下拉菜单,模拟菜单mouseenter事件如果编辑器当前的range为空,则直接返回', () => {
|
||||
const mockGetRage = jest.spyOn(editor.selection, 'getRange')
|
||||
mockGetRage.mockImplementation(() => null)
|
||||
|
||||
dispatchEvent(menuEl, 'mouseenter', 'MouseEvent')
|
||||
|
||||
expect(droplistMenu.dropList.isShow).toBeFalsy()
|
||||
})
|
||||
})
|
|
@ -0,0 +1,10 @@
|
|||
/**
|
||||
* @description Img menu bind-event drag-size
|
||||
* @author luochao
|
||||
*/
|
||||
|
||||
describe('Img menu bind-eveent drag-size', () => {
|
||||
test('绑定drag-size事件', () => {
|
||||
expect(true).toBeTruthy()
|
||||
})
|
||||
})
|
|
@ -0,0 +1,72 @@
|
|||
/**
|
||||
* @description Img menu bind-event drop-img
|
||||
* @author luochao
|
||||
*/
|
||||
import createEditor from '../../../helpers/create-editor'
|
||||
import UploadImg from '../../../../src/menus/img/upload-img'
|
||||
import bindDropImg from '../../../../src/menus/img/bind-event/drop-img'
|
||||
import mockFile from '../../../helpers/mock-file'
|
||||
|
||||
const mockUploadImg = jest.fn()
|
||||
|
||||
jest.mock('../../../../src/menus/img/upload-img', () => {
|
||||
return jest.fn().mockImplementation(() => {
|
||||
return { uploadImg: mockUploadImg }
|
||||
})
|
||||
})
|
||||
|
||||
describe('Img menu bind-event drop-img', () => {
|
||||
beforeEach(() => {
|
||||
// @ts-ignore
|
||||
UploadImg.mockClear()
|
||||
mockUploadImg.mockClear()
|
||||
})
|
||||
|
||||
test('调用 dropImg 方法绑定drop-img事件', () => {
|
||||
const editor = createEditor(document, 'div1')
|
||||
|
||||
bindDropImg(editor)
|
||||
|
||||
expect(editor.txt.eventHooks.dropEvents.length).toBeGreaterThanOrEqual(1)
|
||||
})
|
||||
|
||||
test('调用 dropImg 方法绑定drop-img事件后,执行dropEvents里面的方法会触发uploadImg调用', () => {
|
||||
const editor = createEditor(document, 'div2')
|
||||
|
||||
bindDropImg(editor)
|
||||
|
||||
const dropEvents = editor.txt.eventHooks.dropEvents
|
||||
|
||||
expect(dropEvents.length).toBeGreaterThanOrEqual(1)
|
||||
|
||||
const files = [mockFile({ name: 'test.png', size: 200, mimeType: 'image/png' })]
|
||||
const mockDropEvent = { dataTransfer: { files } }
|
||||
dropEvents.forEach(fn => {
|
||||
// @ts-ignore
|
||||
fn(mockDropEvent)
|
||||
})
|
||||
|
||||
expect(UploadImg).toHaveBeenCalled()
|
||||
expect(mockUploadImg).toBeCalledWith(files)
|
||||
})
|
||||
|
||||
test('调用 dropImg 方法绑定drop-img事件后,如果dropEvent触发的事件参数的文件为空,则不触发上传', () => {
|
||||
const editor = createEditor(document, 'div3')
|
||||
|
||||
bindDropImg(editor)
|
||||
|
||||
const dropEvents = editor.txt.eventHooks.dropEvents
|
||||
|
||||
expect(dropEvents.length).toBeGreaterThanOrEqual(1)
|
||||
|
||||
const files: any[] = []
|
||||
const mockDropEvent = { dataTransfer: { files } }
|
||||
dropEvents.forEach(fn => {
|
||||
// @ts-ignore
|
||||
fn(mockDropEvent)
|
||||
})
|
||||
|
||||
expect(UploadImg).not.toBeCalled()
|
||||
expect(mockUploadImg).not.toBeCalled()
|
||||
})
|
||||
})
|
|
@ -4,12 +4,12 @@
|
|||
*/
|
||||
|
||||
import $ from 'jquery'
|
||||
import Editor from '../../../src/editor'
|
||||
import createEditor from '../../helpers/create-editor'
|
||||
import mockCmdFn from '../../helpers/command-mock'
|
||||
import ImgMenu from '../../../src/menus/img/index'
|
||||
import { getMenuInstance } from '../../helpers/menus'
|
||||
import Panel from '../../../src/menus/menu-constructors/Panel'
|
||||
import Editor from '../../../../src/editor'
|
||||
import createEditor from '../../../helpers/create-editor'
|
||||
import mockCmdFn from '../../../helpers/command-mock'
|
||||
import ImgMenu from '../../../../src/menus/img/index'
|
||||
import { getMenuInstance } from '../../../helpers/menus'
|
||||
import Panel from '../../../../src/menus/menu-constructors/Panel'
|
||||
|
||||
let editor: Editor
|
||||
let imgMenu: ImgMenu
|
|
@ -0,0 +1,137 @@
|
|||
/**
|
||||
* @description Img menu paste-img
|
||||
* @author luochao
|
||||
*/
|
||||
import createEditor from '../../../helpers/create-editor'
|
||||
import bindPasteImgEvent from '../../../../src/menus/img/bind-event/paste-img'
|
||||
import UploadImg from '../../../../src/menus/img/upload-img'
|
||||
import mockFile from '../../../helpers/mock-file'
|
||||
import mockCmdFn from '../../../helpers/command-mock'
|
||||
import * as pasteEvents from '../../../../src/text/paste/paste-event'
|
||||
|
||||
const mockFiles = [mockFile({ name: 'test.png', size: 200, mimeType: 'image/png' })]
|
||||
const mockUploadImg = jest.fn()
|
||||
|
||||
jest.mock('../../../../src/menus/img/upload-img', () => {
|
||||
return jest.fn().mockImplementation(() => {
|
||||
return { uploadImg: mockUploadImg }
|
||||
})
|
||||
})
|
||||
|
||||
describe('Img menu paste-img', () => {
|
||||
beforeEach(() => {
|
||||
// @ts-ignore
|
||||
UploadImg.mockClear()
|
||||
mockUploadImg.mockClear()
|
||||
})
|
||||
|
||||
test('调用 bindPasteImgEvent 方法给编辑器绑定paste事件', () => {
|
||||
const editor = createEditor(document, 'div1')
|
||||
|
||||
bindPasteImgEvent(editor)
|
||||
|
||||
expect(editor.txt.eventHooks.pasteEvents.length).toBeGreaterThanOrEqual(1)
|
||||
})
|
||||
|
||||
test('调用 bindPasteImgEvent 方法给编辑器绑定paste事件后,执行pasteEvent里面的函数会触发上传', () => {
|
||||
mockCmdFn(document)
|
||||
|
||||
const mock = jest.spyOn(pasteEvents, 'getPasteImgs')
|
||||
|
||||
mock.mockReturnValue(mockFiles)
|
||||
|
||||
const editor = createEditor(document, 'div1')
|
||||
|
||||
bindPasteImgEvent(editor)
|
||||
|
||||
const mockGetData = jest.fn().mockImplementation(() => '')
|
||||
|
||||
const eventHooks = editor.txt.eventHooks.pasteEvents
|
||||
const mockEvent = { clipboardData: { getData: mockGetData, items: mockFiles } }
|
||||
|
||||
eventHooks.forEach(fn => {
|
||||
// @ts-ignore
|
||||
fn(mockEvent)
|
||||
})
|
||||
|
||||
expect(UploadImg).toBeCalled()
|
||||
expect(mockUploadImg).toBeCalledWith(mockFiles)
|
||||
})
|
||||
|
||||
test('调用 bindPasteImgEvent 方法给编辑器绑定paste事件后,执行pasteEvent里面的函数会如果粘贴板没有图片文件,则不会触发上传逻辑', () => {
|
||||
mockCmdFn(document)
|
||||
|
||||
const mock = jest.spyOn(pasteEvents, 'getPasteImgs')
|
||||
|
||||
mock.mockReturnValue([])
|
||||
|
||||
const editor = createEditor(document, 'div1')
|
||||
|
||||
bindPasteImgEvent(editor)
|
||||
|
||||
const mockGetData = jest.fn().mockImplementation(() => '')
|
||||
|
||||
const eventHooks = editor.txt.eventHooks.pasteEvents
|
||||
const mockEvent = { clipboardData: { getData: mockGetData, items: mockFiles } }
|
||||
|
||||
eventHooks.forEach(fn => {
|
||||
// @ts-ignore
|
||||
fn(mockEvent)
|
||||
})
|
||||
|
||||
expect(UploadImg).not.toBeCalled()
|
||||
})
|
||||
|
||||
test('调用 bindPasteImgEvent 方法给编辑器绑定paste事件后,执行pasteEvent里面的函数如果粘贴的内容有HTML会直接返回', () => {
|
||||
mockCmdFn(document)
|
||||
|
||||
const mock = jest.spyOn(pasteEvents, 'getPasteImgs')
|
||||
|
||||
mock.mockReturnValue(mockFiles)
|
||||
|
||||
const editor = createEditor(document, 'div1')
|
||||
|
||||
bindPasteImgEvent(editor)
|
||||
|
||||
const mockGetData = jest.fn().mockImplementation(() => '<span></span>')
|
||||
|
||||
const eventHooks = editor.txt.eventHooks.pasteEvents
|
||||
const mockEvent = { clipboardData: { getData: mockGetData, items: mockFiles } }
|
||||
|
||||
eventHooks.forEach(fn => {
|
||||
// @ts-ignore
|
||||
fn(mockEvent)
|
||||
})
|
||||
|
||||
expect(UploadImg).not.toBeCalled()
|
||||
})
|
||||
|
||||
test('调用 bindPasteImgEvent 方法给编辑器绑定paste事件后,执行pasteEvent里面的函数如果粘贴的内容有Text会直接返回', () => {
|
||||
mockCmdFn(document)
|
||||
|
||||
const mock = jest.spyOn(pasteEvents, 'getPasteImgs')
|
||||
|
||||
mock.mockReturnValue(mockFiles)
|
||||
|
||||
const editor = createEditor(document, 'div1')
|
||||
|
||||
bindPasteImgEvent(editor)
|
||||
|
||||
const mockGetData = jest.fn().mockImplementation((type: string) => {
|
||||
if (type === 'text' || type === 'text/plain') {
|
||||
return '123'
|
||||
}
|
||||
return ''
|
||||
})
|
||||
|
||||
const eventHooks = editor.txt.eventHooks.pasteEvents
|
||||
const mockEvent = { clipboardData: { getData: mockGetData, items: mockFiles } }
|
||||
|
||||
eventHooks.forEach(fn => {
|
||||
// @ts-ignore
|
||||
fn(mockEvent)
|
||||
})
|
||||
|
||||
expect(UploadImg).not.toBeCalled()
|
||||
})
|
||||
})
|
|
@ -0,0 +1,60 @@
|
|||
/**
|
||||
* @description Img menu tooltip-event
|
||||
* @author luochao
|
||||
*/
|
||||
import createEditor from '../../../helpers/create-editor'
|
||||
import $ from '../../../../src/utils/dom-core'
|
||||
import bindTooltipEvent, * as tooltipEvent from '../../../../src/menus/img/bind-event/tooltip-event'
|
||||
|
||||
describe('Img menu tooltip-event', () => {
|
||||
test('绑定 tooltip-event 事件', () => {
|
||||
const editor = createEditor(document, 'div1')
|
||||
|
||||
bindTooltipEvent(editor)
|
||||
|
||||
expect(editor.txt.eventHooks.imgClickEvents.length).toBeGreaterThanOrEqual(1)
|
||||
})
|
||||
|
||||
test('调用 createShowHideFn 函数返回显示和隐藏tooltip方法', () => {
|
||||
const editor = createEditor(document, 'div2')
|
||||
const fns = tooltipEvent.createShowHideFn(editor)
|
||||
|
||||
expect(fns.showImgTooltip instanceof Function).toBeTruthy()
|
||||
expect(fns.hideImgTooltip instanceof Function).toBeTruthy()
|
||||
})
|
||||
|
||||
test('绑定 tooltip-event 事件,执行图片点击事件会展示tooltip', () => {
|
||||
const editor = createEditor(document, 'div4')
|
||||
|
||||
bindTooltipEvent(editor)
|
||||
|
||||
const imgClickEvents = editor.txt.eventHooks.imgClickEvents
|
||||
|
||||
imgClickEvents.forEach(fn => {
|
||||
fn($(editor.$textElem))
|
||||
})
|
||||
|
||||
expect($('.w-e-tooltip').elems[0]).not.toBeUndefined()
|
||||
expect($('.w-e-tooltip').elems[0].childNodes.length).toBe(5)
|
||||
})
|
||||
|
||||
test('绑定 tooltip-event 事件,执行图片之外的其它点击事件会隐藏tooltip', () => {
|
||||
const editor = createEditor(document, 'div5')
|
||||
|
||||
bindTooltipEvent(editor)
|
||||
|
||||
const imgClickEvents = editor.txt.eventHooks.imgClickEvents
|
||||
const clickEvents = editor.txt.eventHooks.clickEvents
|
||||
|
||||
imgClickEvents.forEach(fn => {
|
||||
fn($('<div></div>'))
|
||||
})
|
||||
|
||||
clickEvents.forEach(fn => {
|
||||
// @ts-ignore
|
||||
fn()
|
||||
})
|
||||
|
||||
expect($('#div5 .w-e-tooltip').elems[0]).toBeUndefined()
|
||||
})
|
||||
})
|
|
@ -0,0 +1,537 @@
|
|||
/**
|
||||
* @description upload-img test
|
||||
* @author luochao
|
||||
*/
|
||||
import createEditor from '../../../helpers/create-editor'
|
||||
import mockCmdFn from '../../../helpers/command-mock'
|
||||
import mockFile from '../../../helpers/mock-file'
|
||||
import mockXHR from '../../../helpers/mock-xhr'
|
||||
import Editor from '../../../../src/editor'
|
||||
import UploadImg from '../../../../src/menus/img/upload-img'
|
||||
|
||||
let editor: Editor
|
||||
let id = 1
|
||||
|
||||
const imgUrl = 'http://www.wangeditor.com/imgs/logo.jpeg'
|
||||
const errorUrl = 'logo123.jpeg'
|
||||
const uploadImgServer = 'http://localhost:8881/api/upload-img'
|
||||
|
||||
const defaultRes = {
|
||||
status: 200,
|
||||
res: JSON.stringify({ data: ['url1'], errno: 0 }),
|
||||
}
|
||||
|
||||
const mockXHRHttpRequest = (res: any = defaultRes) => {
|
||||
const mockXHRObject = mockXHR(res)
|
||||
|
||||
const mockObject = jest.fn().mockImplementation(() => mockXHRObject)
|
||||
|
||||
// @ts-ignore
|
||||
window.XMLHttpRequest = mockObject
|
||||
|
||||
return mockXHRObject
|
||||
}
|
||||
|
||||
const createUploadImgInstance = (config: any) => {
|
||||
const editor = createEditor(document, `div${id++}`, '', config)
|
||||
const uploadImg = new UploadImg(editor)
|
||||
return uploadImg
|
||||
}
|
||||
|
||||
const mockSupportCommand = () => {
|
||||
mockCmdFn(document)
|
||||
document.queryCommandSupported = jest.fn(() => true)
|
||||
}
|
||||
|
||||
const deaultFiles = [{ name: 'test.png', size: 512, mimeType: 'image/png' }]
|
||||
const createMockFilse = (fileList: any[] = deaultFiles) => {
|
||||
const files = fileList.map(file => mockFile(file))
|
||||
return files.filter(Boolean)
|
||||
}
|
||||
|
||||
describe('upload img', () => {
|
||||
// mock img onload and onerror event
|
||||
beforeAll(() => {
|
||||
// Mocking Image.prototype.src to call the onload or onerror
|
||||
// callbacks depending on the src passed to it
|
||||
Object.defineProperty(global.Image.prototype, 'src', {
|
||||
// Define the property setter
|
||||
set(src) {
|
||||
if (src === errorUrl) {
|
||||
// Call with setTimeout to simulate async loading
|
||||
setTimeout(() => this.onerror(new Error('mocked error')))
|
||||
} else if (src === imgUrl) {
|
||||
setTimeout(() => this.onload())
|
||||
}
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
editor = createEditor(document, `div${id++}`)
|
||||
})
|
||||
|
||||
test('能够初始化基本的UploadImg类', () => {
|
||||
const uploadImg = new UploadImg(editor)
|
||||
|
||||
expect(uploadImg.insertImg instanceof Function).toBeTruthy()
|
||||
expect(uploadImg.uploadImg instanceof Function).toBeTruthy()
|
||||
})
|
||||
|
||||
test('调用 insertImg 可以网编辑器里插入图片', () => {
|
||||
const uploadImg = new UploadImg(editor)
|
||||
|
||||
mockSupportCommand()
|
||||
|
||||
uploadImg.insertImg(imgUrl)
|
||||
|
||||
expect(document.execCommand).toBeCalledWith(
|
||||
'insertHTML',
|
||||
false,
|
||||
`<img src="${imgUrl}" style="max-width:100%;"/>`
|
||||
)
|
||||
})
|
||||
|
||||
test('调用 insertImg 可以网编辑器里插入图片,可以监听插入图片回调', () => {
|
||||
const callback = jest.fn()
|
||||
|
||||
const uploadImg = createUploadImgInstance({
|
||||
linkImgCallback: callback,
|
||||
})
|
||||
|
||||
mockSupportCommand()
|
||||
|
||||
uploadImg.insertImg(imgUrl)
|
||||
|
||||
expect(document.execCommand).toBeCalledWith(
|
||||
'insertHTML',
|
||||
false,
|
||||
`<img src="${imgUrl}" style="max-width:100%;"/>`
|
||||
)
|
||||
expect(callback).toBeCalledWith(imgUrl)
|
||||
})
|
||||
|
||||
test('调用 insertImg 可以网编辑器里插入图片,插入图片加载失败可以通过customAlert配置错误提示', done => {
|
||||
expect.assertions(1)
|
||||
|
||||
const alertFn = jest.fn()
|
||||
|
||||
const uploadImg = createUploadImgInstance({ customAlert: alertFn })
|
||||
|
||||
mockSupportCommand()
|
||||
|
||||
uploadImg.insertImg(errorUrl)
|
||||
|
||||
setTimeout(() => {
|
||||
expect(alertFn).toBeCalledWith(
|
||||
'插入图片错误',
|
||||
'error',
|
||||
`wangEditor: 插入图片错误,图片链接 "${errorUrl}",下载链接失败`
|
||||
)
|
||||
done()
|
||||
}, 1000)
|
||||
})
|
||||
|
||||
test('调用 uploadImg 上传图片', done => {
|
||||
expect.assertions(1)
|
||||
|
||||
const jestFn = jest.fn()
|
||||
|
||||
const upload = createUploadImgInstance({
|
||||
uploadImgServer,
|
||||
uploadImgHooks: {
|
||||
success: jestFn,
|
||||
},
|
||||
})
|
||||
|
||||
const files = createMockFilse()
|
||||
const mockXHRObject = mockXHRHttpRequest()
|
||||
|
||||
upload.uploadImg(files)
|
||||
|
||||
mockXHRObject.onreadystatechange()
|
||||
|
||||
setTimeout(() => {
|
||||
expect(jestFn).toBeCalled()
|
||||
done()
|
||||
}, 1000)
|
||||
})
|
||||
|
||||
test('调用 uploadImg 上传图片,如果传入的文件为空直接返回', () => {
|
||||
const upload = new UploadImg(editor)
|
||||
|
||||
const res = upload.uploadImg([])
|
||||
|
||||
expect(res).toBeUndefined()
|
||||
})
|
||||
|
||||
test('调用 uploadImg 上传图片,如果没有配置customUploadImg, 则必须配置 uploadImgServer 或者 uploadImgShowBase64', () => {
|
||||
const upload = new UploadImg(editor)
|
||||
const files = createMockFilse()
|
||||
|
||||
const res = upload.uploadImg(files)
|
||||
|
||||
expect(res).toBeUndefined()
|
||||
})
|
||||
|
||||
test('调用 uploadImg 上传图片,如果文件没有名字或者size为,则会被过滤掉', () => {
|
||||
const fn = jest.fn()
|
||||
|
||||
const upload = createUploadImgInstance({
|
||||
uploadImgServer,
|
||||
customAlert: fn,
|
||||
})
|
||||
|
||||
const files = createMockFilse([{ name: '', size: 0, mimeType: 'image/png' }])
|
||||
|
||||
const res = upload.uploadImg(files)
|
||||
|
||||
expect(res).toBeUndefined()
|
||||
expect(fn).toBeCalledWith('传入的文件不合法', 'warning')
|
||||
})
|
||||
|
||||
test('调用 uploadImg 上传图片,如果文件非图片,则返回并提示错误信息', () => {
|
||||
const fn = jest.fn()
|
||||
|
||||
const upload = createUploadImgInstance({
|
||||
uploadImgServer,
|
||||
customAlert: fn,
|
||||
})
|
||||
|
||||
const files = createMockFilse([{ name: 'test.txt', size: 200, mimeType: 'text/plain' }])
|
||||
|
||||
const res = upload.uploadImg(files)
|
||||
|
||||
expect(res).toBeUndefined()
|
||||
expect(fn).toBeCalledWith('图片验证未通过: \n【test.txt】不是图片', 'warning')
|
||||
})
|
||||
|
||||
test('调用 uploadImg 上传图片,如果文件体积大小超过配置的大小,则返回并提示错误信息', () => {
|
||||
const fn = jest.fn()
|
||||
|
||||
const upload = createUploadImgInstance({
|
||||
uploadImgServer,
|
||||
uploadImgMaxSize: 5 * 1024 * 1024,
|
||||
customAlert: fn,
|
||||
})
|
||||
|
||||
const files = createMockFilse([
|
||||
{ name: 'test.png', size: 6 * 1024 * 1024, mimeType: 'image/png' },
|
||||
])
|
||||
|
||||
const res = upload.uploadImg(files)
|
||||
|
||||
expect(res).toBeUndefined()
|
||||
expect(fn).toBeCalledWith(`图片验证未通过: \n【test.png】大于 5M`, 'warning')
|
||||
})
|
||||
|
||||
test('调用 uploadImg 上传图片,如果文件个数超过配置的的大小,则返回并提示错误信息', () => {
|
||||
const fn = jest.fn()
|
||||
|
||||
const upload = createUploadImgInstance({
|
||||
uploadImgServer,
|
||||
uploadImgMaxLength: 2,
|
||||
customAlert: fn,
|
||||
})
|
||||
|
||||
const files = createMockFilse([
|
||||
{ name: 'test1.png', size: 2048, mimeType: 'image/png' },
|
||||
{ name: 'test2.png', size: 2048, mimeType: 'image/png' },
|
||||
{ name: 'test3.png', size: 2048, mimeType: 'image/png' },
|
||||
])
|
||||
|
||||
const res = upload.uploadImg(files)
|
||||
|
||||
expect(res).toBeUndefined()
|
||||
expect(fn).toBeCalledWith('一次最多上传2张图片', 'warning')
|
||||
})
|
||||
|
||||
test('调用 uploadImg 上传图片,如果配置了 customUploadImg 选项,则调用customUploadImg上传', () => {
|
||||
const fn = jest.fn()
|
||||
|
||||
const upload = createUploadImgInstance({
|
||||
uploadImgServer,
|
||||
customUploadImg: fn,
|
||||
})
|
||||
|
||||
const files = createMockFilse()
|
||||
|
||||
const res = upload.uploadImg(files)
|
||||
|
||||
expect(res).toBeUndefined()
|
||||
expect(fn).toBeCalled()
|
||||
})
|
||||
|
||||
test('调用 uploadImg 上传图片,如果可以配置uploadImgParamsWithUrl添加query参数', done => {
|
||||
expect.assertions(1)
|
||||
|
||||
const fn = jest.fn()
|
||||
|
||||
const upload = createUploadImgInstance({
|
||||
uploadImgServer,
|
||||
uploadImgParams: {
|
||||
a: 'a',
|
||||
b: 'b',
|
||||
},
|
||||
uploadImgParamsWithUrl: true,
|
||||
uploadImgHooks: {
|
||||
success: fn,
|
||||
},
|
||||
})
|
||||
|
||||
const files = createMockFilse()
|
||||
|
||||
const mockXHRObject = mockXHRHttpRequest()
|
||||
|
||||
upload.uploadImg(files)
|
||||
|
||||
mockXHRObject.onreadystatechange()
|
||||
|
||||
setTimeout(() => {
|
||||
expect(fn).toBeCalled()
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
test('调用 uploadImg 上传图片,uploadImgServer支持hash参数拼接', done => {
|
||||
expect.assertions(1)
|
||||
|
||||
const fn = jest.fn()
|
||||
|
||||
const upload = createUploadImgInstance({
|
||||
uploadImgServer,
|
||||
uploadImgParams: {
|
||||
a: 'a',
|
||||
b: 'b',
|
||||
},
|
||||
uploadImgParamsWithUrl: true,
|
||||
uploadImgHooks: {
|
||||
success: fn,
|
||||
},
|
||||
})
|
||||
|
||||
const files = createMockFilse([
|
||||
{ name: 'test1.png', size: 2048, mimeType: 'image/png' },
|
||||
{ name: 'test2.png', size: 2048, mimeType: 'image/png' },
|
||||
])
|
||||
|
||||
const mockXHRObject = mockXHRHttpRequest()
|
||||
|
||||
upload.uploadImg(files)
|
||||
|
||||
mockXHRObject.onreadystatechange()
|
||||
|
||||
setTimeout(() => {
|
||||
expect(fn).toBeCalled()
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
test('调用 uploadImg 上传图片失败,会有错误提示,并支持配置onError hook', done => {
|
||||
expect.assertions(2)
|
||||
|
||||
const fn = jest.fn()
|
||||
const alertFn = jest.fn()
|
||||
|
||||
const upload = createUploadImgInstance({
|
||||
uploadImgServer,
|
||||
uploadImgHooks: {
|
||||
error: fn,
|
||||
},
|
||||
customAlert: alertFn,
|
||||
})
|
||||
const files = createMockFilse()
|
||||
|
||||
const mockXHRObject = mockXHRHttpRequest({ status: 500 })
|
||||
|
||||
upload.uploadImg(files)
|
||||
|
||||
mockXHRObject.onreadystatechange()
|
||||
|
||||
setTimeout(() => {
|
||||
expect(fn).toBeCalled()
|
||||
expect(alertFn).toBeCalledWith(
|
||||
'上传图片错误',
|
||||
'error',
|
||||
'上传图片错误,服务器返回状态: 500'
|
||||
)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
test('调用 uploadImg 上传图片成功后数据返回不正常,会有错误提示,并支持配置onFail hook', done => {
|
||||
expect.assertions(2)
|
||||
|
||||
const fn = jest.fn()
|
||||
const alertFn = jest.fn()
|
||||
|
||||
const upload = createUploadImgInstance({
|
||||
uploadImgServer,
|
||||
uploadImgHooks: {
|
||||
fail: fn,
|
||||
},
|
||||
customAlert: alertFn,
|
||||
})
|
||||
const files = createMockFilse()
|
||||
|
||||
const mockXHRObject = mockXHRHttpRequest({
|
||||
status: 200,
|
||||
res: '{test: 123}',
|
||||
})
|
||||
|
||||
upload.uploadImg(files)
|
||||
|
||||
mockXHRObject.onreadystatechange()
|
||||
|
||||
setTimeout(() => {
|
||||
expect(fn).toBeCalled()
|
||||
expect(alertFn).toBeCalledWith(
|
||||
'上传图片失败',
|
||||
'error',
|
||||
'上传图片返回结果错误,返回结果: {test: 123}'
|
||||
)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
test('调用 uploadImg 上传图片成功后,支持自定义插入图片函数', done => {
|
||||
expect.assertions(1)
|
||||
|
||||
const insertFn = jest.fn()
|
||||
|
||||
const upload = createUploadImgInstance({
|
||||
uploadImgServer,
|
||||
uploadImgHooks: {
|
||||
customInsert: insertFn,
|
||||
},
|
||||
})
|
||||
|
||||
const files = createMockFilse()
|
||||
|
||||
const mockXHRObject = mockXHRHttpRequest()
|
||||
|
||||
upload.uploadImg(files)
|
||||
|
||||
mockXHRObject.onreadystatechange()
|
||||
|
||||
setTimeout(() => {
|
||||
expect(insertFn).toBeCalled()
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
test('调用 uploadImg 上传被阻止,会有错误提示', done => {
|
||||
expect.assertions(2)
|
||||
|
||||
const beforFn = jest.fn(() => ({ prevent: true, msg: '阻止发送请求' }))
|
||||
const alertFn = jest.fn()
|
||||
|
||||
const upload = createUploadImgInstance({
|
||||
uploadImgServer,
|
||||
uploadImgHooks: {
|
||||
before: beforFn,
|
||||
},
|
||||
customAlert: alertFn,
|
||||
})
|
||||
|
||||
const files = createMockFilse()
|
||||
|
||||
const mockXHRObject = mockXHRHttpRequest()
|
||||
|
||||
upload.uploadImg(files)
|
||||
|
||||
mockXHRObject.onreadystatechange()
|
||||
|
||||
setTimeout(() => {
|
||||
expect(beforFn).toBeCalled()
|
||||
expect(alertFn).toBeCalledWith('阻止发送请求', 'error')
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
test('调用 uploadImg 上传返回的错误码不符合条件会有错误提示,并触发fail回调', done => {
|
||||
expect.assertions(2)
|
||||
|
||||
const failFn = jest.fn()
|
||||
const alertFn = jest.fn()
|
||||
|
||||
const upload = createUploadImgInstance({
|
||||
uploadImgServer,
|
||||
uploadImgHooks: {
|
||||
fail: failFn,
|
||||
},
|
||||
customAlert: alertFn,
|
||||
})
|
||||
|
||||
const files = createMockFilse()
|
||||
|
||||
const mockXHRObject = mockXHRHttpRequest({
|
||||
status: 200,
|
||||
res: { test: 123, errno: -1 },
|
||||
})
|
||||
|
||||
upload.uploadImg(files)
|
||||
|
||||
mockXHRObject.onreadystatechange()
|
||||
|
||||
setTimeout(() => {
|
||||
expect(failFn).toBeCalled()
|
||||
expect(alertFn).toBeCalledWith(
|
||||
'上传图片失败',
|
||||
'error',
|
||||
'上传图片返回结果错误,返回结果 errno=-1'
|
||||
)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
test('调用 uploadImg 上传,如果配置 uploadImgShowBase64 参数,则直接插入base64到编辑器', () => {
|
||||
const callback = jest.fn()
|
||||
const upload = createUploadImgInstance({
|
||||
uploadImgShowBase64: true,
|
||||
linkImgCallback: callback,
|
||||
})
|
||||
const files = createMockFilse()
|
||||
|
||||
const mockFn = jest.fn()
|
||||
|
||||
// @ts-ignore
|
||||
jest.spyOn(global, 'FileReader').mockImplementation(() => {
|
||||
return {
|
||||
readAsDataURL: mockFn,
|
||||
}
|
||||
})
|
||||
|
||||
upload.uploadImg(files)
|
||||
|
||||
expect(mockFn).toBeCalled()
|
||||
})
|
||||
|
||||
test('调用 uploadImg 上传超时会触发超时回调', done => {
|
||||
expect.assertions(2)
|
||||
|
||||
const timeoutFn = jest.fn()
|
||||
const alertFn = jest.fn()
|
||||
|
||||
const upload = createUploadImgInstance({
|
||||
uploadImgServer,
|
||||
uploadImgHooks: {
|
||||
timeout: timeoutFn,
|
||||
},
|
||||
customAlert: alertFn,
|
||||
})
|
||||
|
||||
const files = createMockFilse()
|
||||
const mockXHRObject = mockXHRHttpRequest()
|
||||
|
||||
upload.uploadImg(files)
|
||||
|
||||
mockXHRObject.ontimeout()
|
||||
|
||||
setTimeout(() => {
|
||||
expect(timeoutFn).toBeCalled()
|
||||
expect(alertFn).toBeCalledWith('上传图片超时', 'error')
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
|
@ -8,24 +8,147 @@ import createEditor from '../../helpers/create-editor'
|
|||
import mockCmdFn from '../../helpers/command-mock'
|
||||
import lineHeight from '../../../src/menus/lineHeight/index'
|
||||
import { getMenuInstance } from '../../helpers/menus'
|
||||
import { UA } from '../../../src/utils/util'
|
||||
|
||||
let editor: Editor
|
||||
let lineHeightMenu: lineHeight
|
||||
let id = 1
|
||||
|
||||
test('lineHeight 菜单:dropList', () => {
|
||||
editor = createEditor(document, 'div1') // 赋值给全局变量
|
||||
lineHeightMenu = getMenuInstance(editor, lineHeight) as lineHeight // 赋值给全局变量
|
||||
expect(lineHeightMenu.dropList).not.toBeNull()
|
||||
lineHeightMenu.dropList.show()
|
||||
expect(lineHeightMenu.dropList.isShow).toBe(true)
|
||||
lineHeightMenu.dropList.hide()
|
||||
expect(lineHeightMenu.dropList.isShow).toBe(false)
|
||||
})
|
||||
describe('LineHeight menu', () => {
|
||||
beforeEach(() => {
|
||||
editor = createEditor(document, `div${id++}`)
|
||||
lineHeightMenu = getMenuInstance(editor, lineHeight) as lineHeight
|
||||
})
|
||||
|
||||
test('lineHeight 菜单:增加行高', () => {
|
||||
mockCmdFn(document)
|
||||
const cmdVal = '2'
|
||||
lineHeightMenu.command(cmdVal)
|
||||
// 此处触发 editor.cmd.do('insertHTML', xx),可以被 jest 成功执行,具体参考 mockCmdFn 的描述
|
||||
expect(editor.$textElem.html().indexOf('<p style="line-height:2;"><br></p>')).toBeGreaterThan(0)
|
||||
test('lineHeight 菜单:dropList', () => {
|
||||
expect(lineHeightMenu.dropList).not.toBeNull()
|
||||
lineHeightMenu.dropList.show()
|
||||
expect(lineHeightMenu.dropList.isShow).toBe(true)
|
||||
lineHeightMenu.dropList.hide()
|
||||
expect(lineHeightMenu.dropList.isShow).toBe(false)
|
||||
})
|
||||
|
||||
test('lineHeight 菜单:增加行高', () => {
|
||||
mockCmdFn(document)
|
||||
const cmdVal = '2'
|
||||
lineHeightMenu.command(cmdVal)
|
||||
// 此处触发 editor.cmd.do('insertHTML', xx),可以被 jest 成功执行,具体参考 mockCmdFn 的描述
|
||||
expect(editor.$textElem.elems[0]).toContainHTML('<p style="line-height:2;"><br></p>')
|
||||
})
|
||||
|
||||
test('lineHeight 菜单:选择多行增加行高', () => {
|
||||
mockCmdFn(document)
|
||||
|
||||
editor.txt.html('<p>123</p><p>234</p>')
|
||||
|
||||
const [startNode, endNode] = Array.from(editor.$textElem.elems[0].childNodes)
|
||||
lineHeightMenu.setRange(startNode, endNode)
|
||||
|
||||
const cmdVal = '2'
|
||||
lineHeightMenu.command(cmdVal)
|
||||
// 此处触发 editor.cmd.do('insertHTML', xx),可以被 jest 成功执行,具体参考 mockCmdFn 的描述
|
||||
expect(
|
||||
editor.$textElem.elems[0].innerHTML.indexOf('<p style="line-height:2;">123</p>')
|
||||
).toBeGreaterThanOrEqual(0)
|
||||
})
|
||||
|
||||
test('lineHeight 菜单:选择多行增加行高, 如果是IE浏览器,直接返回', () => {
|
||||
mockCmdFn(document)
|
||||
|
||||
const mockIE = jest.spyOn(UA, 'isIE')
|
||||
mockIE.mockReturnValueOnce(true)
|
||||
|
||||
editor.txt.html('<p>123</p><p>234</p>')
|
||||
|
||||
const [startNode, endNode] = Array.from(editor.$textElem.elems[0].childNodes)
|
||||
lineHeightMenu.setRange(startNode, endNode)
|
||||
|
||||
const cmdVal = '2'
|
||||
lineHeightMenu.command(cmdVal)
|
||||
// 此处触发 editor.cmd.do('insertHTML', xx),可以被 jest 成功执行,具体参考 mockCmdFn 的描述
|
||||
expect(editor.$textElem.elems[0].innerHTML.indexOf('<p>123</p>')).toBeGreaterThanOrEqual(0)
|
||||
})
|
||||
|
||||
test('lineHeight 菜单:选择多行增加行高, 设置非段落的P标签开头全选', () => {
|
||||
mockCmdFn(document)
|
||||
|
||||
editor.txt.html('<p>234<span>123</span></p><div>345</div>')
|
||||
|
||||
const [startNode, endNode] = Array.from(editor.$textElem.elems[0].childNodes)
|
||||
lineHeightMenu.setRange(startNode, endNode)
|
||||
|
||||
const cmdVal = '2'
|
||||
lineHeightMenu.command(cmdVal)
|
||||
// 此处触发 editor.cmd.do('insertHTML', xx),可以被 jest 成功执行,具体参考 mockCmdFn 的描述
|
||||
expect(
|
||||
editor.$textElem.elems[0].innerHTML.indexOf(
|
||||
'<p style="line-height:2;">234<span>123</span></p>'
|
||||
)
|
||||
).toBeGreaterThanOrEqual(0)
|
||||
})
|
||||
|
||||
test('lineHeight 菜单:选择多行增加行高, 设置无P标签的全选,设置无效', () => {
|
||||
mockCmdFn(document)
|
||||
|
||||
editor.txt.html('<div>345</div><div>234<span>123</span></div>')
|
||||
|
||||
const [startNode, endNode] = Array.from(editor.$textElem.elems[0].childNodes)
|
||||
lineHeightMenu.setRange(startNode, endNode)
|
||||
|
||||
const cmdVal = '2'
|
||||
lineHeightMenu.command(cmdVal)
|
||||
// 此处触发 editor.cmd.do('insertHTML', xx),可以被 jest 成功执行,具体参考 mockCmdFn 的描述
|
||||
expect(
|
||||
editor.$textElem.elems[0].innerHTML.indexOf(
|
||||
'<div>345</div><div>234<span>123</span></div>'
|
||||
)
|
||||
).toBeGreaterThanOrEqual(0)
|
||||
})
|
||||
|
||||
test('lineHeight 菜单:增加行高, 如果不传value值,则设为默认行高,并且不设置 line-height 样式', () => {
|
||||
mockCmdFn(document)
|
||||
|
||||
editor.txt.html('<p style="color:red;">123</p>')
|
||||
|
||||
const [startNode] = Array.from(editor.$textElem.elems[0].childNodes)
|
||||
lineHeightMenu.setRange(startNode, startNode)
|
||||
|
||||
lineHeightMenu.command('')
|
||||
// 此处触发 editor.cmd.do('insertHTML', xx),可以被 jest 成功执行,具体参考 mockCmdFn 的描述
|
||||
expect(
|
||||
editor.$textElem.elems[0].innerHTML.indexOf('<p style="color:red;">123</p>')
|
||||
).toBeGreaterThanOrEqual(0)
|
||||
})
|
||||
|
||||
test('lineHeight 菜单:增加行高, 如果选区的元素有style样式,则会在样式上叠加 line-height 样式', () => {
|
||||
mockCmdFn(document)
|
||||
|
||||
editor.txt.html('<p style="color:red;">123</p>')
|
||||
|
||||
const [startNode] = Array.from(editor.$textElem.elems[0].childNodes)
|
||||
lineHeightMenu.setRange(startNode, startNode)
|
||||
|
||||
lineHeightMenu.command('2')
|
||||
// 此处触发 editor.cmd.do('insertHTML', xx),可以被 jest 成功执行,具体参考 mockCmdFn 的描述
|
||||
expect(
|
||||
editor.$textElem.elems[0].innerHTML.indexOf(
|
||||
'<p style="color:red;line-height:2;">123</p>'
|
||||
)
|
||||
).toBeGreaterThanOrEqual(0)
|
||||
})
|
||||
|
||||
test('lineHeight 菜单:增加行高, 如果选区的元素为blockquote元素,则不会叠加 line-height 样式', () => {
|
||||
mockCmdFn(document)
|
||||
|
||||
editor.txt.html('<blockquote>123</blockquote>')
|
||||
|
||||
const [startNode] = Array.from(editor.$textElem.elems[0].childNodes)
|
||||
lineHeightMenu.setRange(startNode, startNode)
|
||||
|
||||
lineHeightMenu.command('2')
|
||||
// 此处触发 editor.cmd.do('insertHTML', xx),可以被 jest 成功执行,具体参考 mockCmdFn 的描述
|
||||
expect(
|
||||
editor.$textElem.elems[0].innerHTML.indexOf('<blockquote>123</blockquote>')
|
||||
).toBeGreaterThanOrEqual(0)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -5,25 +5,75 @@
|
|||
import createEditor from '../../helpers/create-editor'
|
||||
import Editor from '../../../src/editor'
|
||||
import createQuote from '../../../src/menus/quote/create-quote-node'
|
||||
import QuoteMenu from '../../../src/menus/quote'
|
||||
import { getMenuInstance } from '../../helpers/menus'
|
||||
import $, { DomElement } from '../../../src/utils/dom-core'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
let editor: Editor
|
||||
let id = 1
|
||||
let quoteMenu: QuoteMenu
|
||||
describe('Editor quote menu', () => {
|
||||
beforeEach(() => {
|
||||
editor = createEditor(document, `div${id++}`)
|
||||
quoteMenu = getMenuInstance(editor, QuoteMenu)
|
||||
})
|
||||
test('创建编辑器会初始化 quote 菜单', () => {
|
||||
expect(editor.txt.eventHooks.enterDownEvents.length).toBeGreaterThanOrEqual(1)
|
||||
expect(quoteMenu).not.toBeNull()
|
||||
})
|
||||
|
||||
test('单行引用', () => {
|
||||
editor = createEditor(document, 'div1') // 赋值给全局变量
|
||||
const $childElem: DomElement[] = [$(`<p>123</p>`)]
|
||||
const $quote = createQuote($childElem)
|
||||
const p = $(`<p></p>`)
|
||||
p.append($quote)
|
||||
expect(p.html()).toEqual(`<blockquote><p>123</p></blockquote>`)
|
||||
})
|
||||
test('给编辑器添加单行引用', () => {
|
||||
editor.selection.createEmptyRange()
|
||||
quoteMenu.clickHandler()
|
||||
|
||||
test('多行引用', () => {
|
||||
editor = createEditor(document, 'div1') // 赋值给全局变量
|
||||
const $childElem: DomElement[] = [$(`<p>123</p>`), $(`<p>456</p>`)]
|
||||
const $quote = createQuote($childElem)
|
||||
const p = $(`<p></p>`)
|
||||
p.append($quote)
|
||||
expect(p.html()).toEqual(`<blockquote><p>123</p><p>456</p></blockquote>`)
|
||||
expect((editor.txt.html() as string).indexOf('blockquote')).toBeGreaterThanOrEqual(0)
|
||||
})
|
||||
|
||||
test('取消编辑器添加单行引用', () => {
|
||||
editor.selection.createEmptyRange()
|
||||
quoteMenu.clickHandler()
|
||||
expect((editor.txt.html() as string).indexOf('blockquote')).toBeGreaterThanOrEqual(0)
|
||||
quoteMenu.clickHandler()
|
||||
expect((editor.txt.html() as string).indexOf('blockquote')).toEqual(-1)
|
||||
})
|
||||
|
||||
test('执行 enterDownEvents 里面的函数会触发 quoteEnter 函数执行', () => {
|
||||
const $childElem: DomElement[] = [$(`<p>123</p>`)]
|
||||
const $quote = createQuote($childElem)
|
||||
|
||||
editor.$textElem.append($quote)
|
||||
// @ts-ignore
|
||||
editor.selection.createRangeByElem($quote)
|
||||
|
||||
const mockSelectionGetSelectionContainerElem = jest.spyOn(
|
||||
editor.selection,
|
||||
'getSelectionContainerElem'
|
||||
)
|
||||
const mockGetSelectionRangeTopNodes = jest.spyOn(
|
||||
editor.selection,
|
||||
'getSelectionRangeTopNodes'
|
||||
)
|
||||
|
||||
mockSelectionGetSelectionContainerElem.mockImplementation(() => {
|
||||
return $quote
|
||||
})
|
||||
mockGetSelectionRangeTopNodes.mockImplementation(() => {
|
||||
return [$quote]
|
||||
})
|
||||
|
||||
const event = new KeyboardEvent('keydown')
|
||||
|
||||
editor.txt.eventHooks.enterDownEvents.forEach(fn => {
|
||||
fn(event)
|
||||
})
|
||||
})
|
||||
|
||||
test('可以使用 createQuote 创建多行引用', () => {
|
||||
const $childElem: DomElement[] = [$(`<p>123</p>`), $(`<p>456</p>`)]
|
||||
const $quote = createQuote($childElem)
|
||||
const p = $(`<p></p>`)
|
||||
p.append($quote)
|
||||
expect(p.html()).toEqual(`<blockquote><p>123</p><p>456</p></blockquote>`)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
/**
|
||||
* @description split-line menu
|
||||
* @author luochao
|
||||
*/
|
||||
import createEditor from '../../helpers/create-editor'
|
||||
import mockCommand from '../../helpers/command-mock'
|
||||
import SplitLine from '../../../src/menus/split-line'
|
||||
import { getMenuInstance } from '../../helpers/menus'
|
||||
import $ from '../../../src/utils/dom-core'
|
||||
|
||||
let editor: ReturnType<typeof createEditor>
|
||||
let splitLineMenu: SplitLine
|
||||
let id = 1
|
||||
|
||||
describe('split-line menu', () => {
|
||||
beforeEach(() => {
|
||||
editor = createEditor(document, `div${id++}`)
|
||||
splitLineMenu = getMenuInstance(editor, SplitLine)
|
||||
})
|
||||
|
||||
test('初始化编辑器默认会创建 split-line 菜单', () => {
|
||||
expect(splitLineMenu).not.toBeNull()
|
||||
})
|
||||
|
||||
test('点击分割线菜单会创建分割线', () => {
|
||||
mockCommand(document)
|
||||
|
||||
splitLineMenu.clickHandler()
|
||||
|
||||
expect((editor.txt.html() as string).indexOf('<hr>')).toBeGreaterThanOrEqual(0)
|
||||
})
|
||||
|
||||
test('执行 splitLineEvents 里面的钩子函数会展示 tooltip 菜单,点击其它地方会隐藏tooptip', () => {
|
||||
mockCommand(document)
|
||||
|
||||
editor.txt.eventHooks.splitLineEvents.forEach(fn => {
|
||||
fn(splitLineMenu.$elem)
|
||||
})
|
||||
|
||||
const tooltip1 = $('.w-e-tooltip')
|
||||
|
||||
expect(tooltip1.elems[0]).not.toBeUndefined()
|
||||
|
||||
editor.$textElem.elems[0].click()
|
||||
|
||||
const tooltip2 = $('.w-e-tooltip')
|
||||
expect(tooltip2.elems[0]).toBeUndefined()
|
||||
})
|
||||
|
||||
test('执行 splitLineEvents 里面的钩子函数会展示 tooltip 菜单,点击tooltip删除按钮会移除分隔线', () => {
|
||||
mockCommand(document)
|
||||
|
||||
splitLineMenu.clickHandler()
|
||||
|
||||
expect((editor.txt.html() as string).indexOf('<hr>')).toBeGreaterThanOrEqual(0)
|
||||
|
||||
editor.txt.eventHooks.splitLineEvents.forEach(fn => {
|
||||
fn(splitLineMenu.$elem)
|
||||
})
|
||||
|
||||
const tooltip = $('.w-e-tooltip')
|
||||
expect(tooltip.elems[0]).not.toBeUndefined()
|
||||
|
||||
const deleteSpan = tooltip.find('.w-e-tooltip-item-wrapper span')
|
||||
deleteSpan.elems[0].click()
|
||||
|
||||
expect(document.execCommand).toBeCalledWith('delete', false, undefined)
|
||||
})
|
||||
})
|
|
@ -0,0 +1,72 @@
|
|||
/**
|
||||
* @description editor.text event-hooks del-to-keep-p test
|
||||
* @author luochao
|
||||
*/
|
||||
import delToKeepP from '../../../src/text/event-hooks/del-to-keep-p'
|
||||
import createEditor from '../../helpers/create-editor'
|
||||
|
||||
describe('editor.text event-hooks tab-to-space test', () => {
|
||||
test('能绑定分别绑定 一个处理 up 和 down 的函数', () => {
|
||||
const upFns: Function[] = []
|
||||
const downFns: Function[] = []
|
||||
const editor = createEditor(document, 'div1')
|
||||
|
||||
delToKeepP(editor, upFns, downFns)
|
||||
|
||||
expect(upFns.length).toBe(1)
|
||||
expect(downFns.length).toBe(1)
|
||||
})
|
||||
|
||||
test('当编辑器内容为空时,执行 up 函数,则会插入 <p><br></p> 内容', () => {
|
||||
const upFns: Function[] = []
|
||||
const downFns: Function[] = []
|
||||
const editor = createEditor(document, 'div2')
|
||||
|
||||
delToKeepP(editor, upFns, downFns)
|
||||
|
||||
editor.txt.html(' ')
|
||||
|
||||
upFns.forEach(fn => {
|
||||
fn()
|
||||
})
|
||||
|
||||
expect(editor.$textElem.elems[0].innerHTML).toEqual('<p><br></p>')
|
||||
})
|
||||
|
||||
test('当编辑器内容只有 <br> 时,执行 up 函数,则会插入 <p><br></p> 内容', () => {
|
||||
const upFns: Function[] = []
|
||||
const downFns: Function[] = []
|
||||
const editor = createEditor(document, 'div3')
|
||||
|
||||
delToKeepP(editor, upFns, downFns)
|
||||
|
||||
editor.txt.html('<br>')
|
||||
|
||||
upFns.forEach(fn => {
|
||||
fn()
|
||||
})
|
||||
|
||||
expect(editor.$textElem.elems[0].innerHTML).toEqual(' <p><br></p>')
|
||||
})
|
||||
|
||||
test('当编辑器内容清空到只剩下 <p><br></p> 内容时,则不允许再删除', () => {
|
||||
const upFns: Function[] = []
|
||||
const downFns: Function[] = []
|
||||
const editor = createEditor(document, 'div4')
|
||||
|
||||
delToKeepP(editor, upFns, downFns)
|
||||
|
||||
editor.txt.html('<p><br></p>')
|
||||
|
||||
const e = new KeyboardEvent('mousedown')
|
||||
const mockPreventDefault = jest.fn()
|
||||
jest.spyOn(e, 'preventDefault').mockImplementation(mockPreventDefault)
|
||||
|
||||
downFns.forEach(fn => {
|
||||
fn(e)
|
||||
})
|
||||
|
||||
expect(editor.$textElem.elems[0].innerHTML).toEqual('<p><br></p>')
|
||||
expect(mockPreventDefault).toBeCalled()
|
||||
})
|
||||
})
|
|
@ -0,0 +1,178 @@
|
|||
/**
|
||||
* @description editor.text event-hooks del-to-keep-p test
|
||||
* @author luochao
|
||||
*/
|
||||
import enterToCreateP from '../../../src/text/event-hooks/enter-to-create-p'
|
||||
import $ from '../../../src/utils/dom-core'
|
||||
import createEditor from '../../helpers/create-editor'
|
||||
import commandMock from '../../helpers/command-mock'
|
||||
|
||||
type Editor = ReturnType<typeof createEditor>
|
||||
|
||||
let editor: Editor
|
||||
let id = 1
|
||||
|
||||
const mockGetSelectionContainerElem = (editor: Editor, tagString: string, isChild = true) => {
|
||||
const container = $(tagString)
|
||||
jest.spyOn(editor.selection, 'getSelectionContainerElem').mockImplementation(() =>
|
||||
isChild ? container.children()! : container
|
||||
)
|
||||
}
|
||||
|
||||
describe('editor.text event-hooks tab-to-space test', () => {
|
||||
beforeEach(() => {
|
||||
editor = createEditor(document, `div${id++}`)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
test('能绑定分别绑定 一个处理 up 和 down 的函数', () => {
|
||||
const upFns: Function[] = []
|
||||
const downFns: Function[] = []
|
||||
|
||||
enterToCreateP(editor, upFns, downFns)
|
||||
|
||||
expect(upFns.length).toBe(1)
|
||||
expect(downFns.length).toBe(1)
|
||||
})
|
||||
|
||||
test('当编辑器选区内容父元素为 <code><br></code> ,则移除内容, 插入 <p><br></p>', () => {
|
||||
const upFns: Function[] = []
|
||||
const downFns: Function[] = []
|
||||
|
||||
enterToCreateP(editor, upFns, downFns)
|
||||
|
||||
editor.txt.html(' ')
|
||||
|
||||
mockGetSelectionContainerElem(editor, '<code><br></code>', false)
|
||||
|
||||
upFns.forEach(fn => {
|
||||
fn()
|
||||
})
|
||||
|
||||
expect(editor.$textElem.elems[0].innerHTML).toEqual('<p><br></p>')
|
||||
})
|
||||
|
||||
test('当编辑器选区内容的父元素不是 $textElm,则不处理', () => {
|
||||
const upFns: Function[] = []
|
||||
const downFns: Function[] = []
|
||||
|
||||
enterToCreateP(editor, upFns, downFns)
|
||||
|
||||
editor.txt.html('<p>0</p>')
|
||||
|
||||
const container = $('<p>123</p>')
|
||||
|
||||
jest.spyOn(editor.selection, 'getSelectionContainerElem').mockImplementation(
|
||||
() => container
|
||||
)
|
||||
|
||||
upFns.forEach(fn => {
|
||||
fn()
|
||||
})
|
||||
|
||||
expect(editor.$textElem.elems[0].innerHTML).toEqual('<p>0</p>')
|
||||
})
|
||||
|
||||
test('当编辑器选区内容是P标签,则不处理', () => {
|
||||
const upFns: Function[] = []
|
||||
const downFns: Function[] = []
|
||||
|
||||
enterToCreateP(editor, upFns, downFns)
|
||||
|
||||
editor.txt.html('<p>0</p>')
|
||||
|
||||
const container = $('<p>123</p>')
|
||||
|
||||
editor.$textElem.append(container)
|
||||
|
||||
jest.spyOn(editor.selection, 'getSelectionContainerElem').mockImplementation(
|
||||
() => container
|
||||
)
|
||||
|
||||
upFns.forEach(fn => {
|
||||
fn()
|
||||
})
|
||||
|
||||
expect(editor.$textElem.elems[0].innerHTML).toEqual('<p>0</p><p>123</p>')
|
||||
})
|
||||
|
||||
test('当编辑器选区内容是非 P 标签并且含有 text 内容,则不处理', () => {
|
||||
const upFns: Function[] = []
|
||||
const downFns: Function[] = []
|
||||
|
||||
enterToCreateP(editor, upFns, downFns)
|
||||
|
||||
editor.txt.html('<div>123</div>')
|
||||
|
||||
const container = $('<div>123</div>')
|
||||
|
||||
editor.$textElem.append(container)
|
||||
|
||||
jest.spyOn(editor.selection, 'getSelectionContainerElem').mockImplementation(
|
||||
() => container
|
||||
)
|
||||
|
||||
upFns.forEach(fn => {
|
||||
fn()
|
||||
})
|
||||
|
||||
expect(editor.$textElem.elems[0].innerHTML).toEqual('<div>123</div><div>123</div>')
|
||||
})
|
||||
|
||||
test('当编辑器选区内容为非P标签,且没有文本内容,插入 <p><br></p>', () => {
|
||||
const upFns: Function[] = []
|
||||
const downFns: Function[] = []
|
||||
|
||||
enterToCreateP(editor, upFns, downFns)
|
||||
|
||||
editor.txt.html('<div></div>')
|
||||
|
||||
const container = $('<div></div>')
|
||||
|
||||
editor.$textElem.append(container)
|
||||
|
||||
jest.spyOn(editor.selection, 'getSelectionContainerElem').mockImplementation(
|
||||
() => container
|
||||
)
|
||||
|
||||
upFns.forEach(fn => {
|
||||
fn()
|
||||
})
|
||||
|
||||
expect(editor.$textElem.elems[0].innerHTML.indexOf('<p><br></p>')).toBeGreaterThanOrEqual(0)
|
||||
})
|
||||
|
||||
test('当编辑器选区内容 $textElm,执行enter down,插入 <p><br></p>', () => {
|
||||
commandMock(document)
|
||||
|
||||
const upFns: Function[] = []
|
||||
const downFns: Function[] = []
|
||||
|
||||
enterToCreateP(editor, upFns, downFns)
|
||||
|
||||
editor.txt.html('')
|
||||
|
||||
const container = $('<div></div>')
|
||||
|
||||
editor.$textElem.append(container)
|
||||
|
||||
jest.spyOn(editor.selection, 'getSelectionContainerElem').mockImplementation(
|
||||
() => editor.$textElem
|
||||
)
|
||||
|
||||
const mockPreventDefault = jest.fn()
|
||||
const event = new KeyboardEvent('mousedown')
|
||||
jest.spyOn(event, 'preventDefault').mockImplementation(mockPreventDefault)
|
||||
jest.spyOn(document, 'queryCommandSupported').mockImplementation(() => true)
|
||||
|
||||
downFns.forEach(fn => {
|
||||
fn(event)
|
||||
})
|
||||
|
||||
expect(mockPreventDefault).toBeCalled()
|
||||
expect(document.execCommand).toBeCalledWith('insertHTML', false, '<p><br></p>')
|
||||
})
|
||||
})
|
|
@ -0,0 +1,52 @@
|
|||
/**
|
||||
* @description editor.text getHtmlByNodeList test
|
||||
* @author luochao
|
||||
*/
|
||||
import getHtmlByNodeList from '../../../src/text/getHtmlByNodeList'
|
||||
import getChildrenJSON, { NodeListType } from '../../../src/text/getChildrenJSON'
|
||||
import $ from '../../../src/utils/dom-core'
|
||||
describe('txt utils geChildrenJSON', () => {
|
||||
test('能将元素的所有子元素包括属性还原成json数据', () => {
|
||||
const nodeList: NodeListType = [
|
||||
'123',
|
||||
'',
|
||||
{
|
||||
tag: 'div',
|
||||
attrs: [],
|
||||
children: [
|
||||
{
|
||||
tag: 'span',
|
||||
attrs: [
|
||||
{
|
||||
name: 'id',
|
||||
value: 'child',
|
||||
},
|
||||
],
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
tag: 'p',
|
||||
attrs: [
|
||||
{
|
||||
name: 'id',
|
||||
value: 'node2',
|
||||
},
|
||||
],
|
||||
children: [],
|
||||
},
|
||||
]
|
||||
const parent = document.createElement('div')
|
||||
const html = getHtmlByNodeList(nodeList, parent)
|
||||
const json = getChildrenJSON(html)
|
||||
expect(json).toEqual(nodeList.filter(Boolean))
|
||||
})
|
||||
|
||||
test('如果元素不存在或者没有子元素,则返回空数组', () => {
|
||||
const json1 = getChildrenJSON($('.div1'))
|
||||
const json2 = getChildrenJSON($('<div></div>'))
|
||||
expect(json1.length).toEqual(0)
|
||||
expect(json2.length).toEqual(0)
|
||||
})
|
||||
})
|
|
@ -0,0 +1,48 @@
|
|||
/**
|
||||
* @description editor.text getHtmlByNodeList test
|
||||
* @author luochao
|
||||
*/
|
||||
import getHtmlByNodeList from '../../../src/text/getHtmlByNodeList'
|
||||
import { NodeListType } from '../../../src/text/getChildrenJSON'
|
||||
describe('txt utils getHtmlByNodeList', () => {
|
||||
test('能将 nodeList 全部聚合成在一个 container 元素中, 并支持子元素嵌套', () => {
|
||||
const nodeList: NodeListType = [
|
||||
'123',
|
||||
{
|
||||
tag: 'div',
|
||||
attrs: [
|
||||
{
|
||||
name: 'id',
|
||||
value: 'node1',
|
||||
},
|
||||
],
|
||||
children: [
|
||||
{
|
||||
tag: 'span',
|
||||
attrs: [
|
||||
{
|
||||
name: 'id',
|
||||
value: 'child',
|
||||
},
|
||||
],
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
tag: 'p',
|
||||
attrs: [
|
||||
{
|
||||
name: 'id',
|
||||
value: 'node2',
|
||||
},
|
||||
],
|
||||
children: [],
|
||||
},
|
||||
]
|
||||
const html = getHtmlByNodeList(nodeList)
|
||||
expect(html.elems[0].innerHTML).toBe(
|
||||
'123<div id="node1"><span id="child"></span></div><p id="node2"></p>'
|
||||
)
|
||||
})
|
||||
})
|
|
@ -0,0 +1,58 @@
|
|||
/**
|
||||
* @description text utils getPasteImgs test
|
||||
* @author luochao
|
||||
*/
|
||||
import { getPasteImgs } from '../../../src/text/paste/paste-event'
|
||||
import mockFile from '../../helpers/mock-file'
|
||||
|
||||
window.ClipboardEvent = jest.fn().mockImplementation(() => {
|
||||
return {
|
||||
clipboardData: {
|
||||
getData: jest.fn(),
|
||||
items: [
|
||||
{
|
||||
type: 'image/png',
|
||||
getAsFile: jest.fn(() =>
|
||||
mockFile({ name: '1.png', size: 1024, mimeType: 'image/png' })
|
||||
),
|
||||
},
|
||||
{
|
||||
type: 'image/png',
|
||||
getAsFile: jest.fn(() =>
|
||||
mockFile({ name: '2.png', size: 1024, mimeType: 'image/png' })
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
// @ts-ignore
|
||||
window.clipboardData = {
|
||||
getData: jest.fn(),
|
||||
}
|
||||
|
||||
describe('text utils getPasteImgs test', () => {
|
||||
test('能从 clipboradEvent 获取到图片文件', () => {
|
||||
const clipboradEvent = new ClipboardEvent('')
|
||||
|
||||
const mockGetData = jest.fn(() => '')
|
||||
// @ts-ignore
|
||||
jest.spyOn(clipboradEvent.clipboardData, 'getData').mockImplementation(mockGetData)
|
||||
|
||||
const results = getPasteImgs(clipboradEvent)
|
||||
expect(results.length).toBe(2)
|
||||
})
|
||||
|
||||
test('能从 clipboradEvent 有text内容,直接返回空数组', () => {
|
||||
const clipboradEvent = new ClipboardEvent('')
|
||||
const mockGetData = jest.fn(() => 'test123')
|
||||
// @ts-ignore
|
||||
jest.spyOn(clipboradEvent.clipboardData, 'getData').mockImplementation(mockGetData)
|
||||
|
||||
const result = getPasteImgs(clipboradEvent)
|
||||
|
||||
expect(result.length).toBe(0)
|
||||
expect(mockGetData).toBeCalledWith('text/plain')
|
||||
})
|
||||
})
|
|
@ -0,0 +1,555 @@
|
|||
/**
|
||||
* @description 编辑区域,入口文件测试
|
||||
* @author luochao
|
||||
*/
|
||||
import createEditor from '../../helpers/create-editor'
|
||||
import $ from '../../../src/utils/dom-core'
|
||||
import dispatchEvent from '../../helpers/mock-dispatch-event'
|
||||
import { UA } from '../../../src/utils/util'
|
||||
|
||||
let editor: ReturnType<typeof createEditor>
|
||||
let id = 1
|
||||
|
||||
const nodeList = [
|
||||
{
|
||||
tag: 'div',
|
||||
attrs: [],
|
||||
children: [
|
||||
{
|
||||
tag: 'span',
|
||||
attrs: [
|
||||
{
|
||||
name: 'id',
|
||||
value: 'child',
|
||||
},
|
||||
],
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
tag: 'p',
|
||||
attrs: [
|
||||
{
|
||||
name: 'id',
|
||||
value: 'node2',
|
||||
},
|
||||
],
|
||||
children: [],
|
||||
},
|
||||
]
|
||||
|
||||
const nodeListHtml = '<div><span id="child"></span></div><p id="node2"></p>'
|
||||
|
||||
describe('Editor Text test', () => {
|
||||
beforeEach(() => {
|
||||
editor = createEditor(document, `div${id++}`)
|
||||
})
|
||||
|
||||
test('编辑器初始化,也会初始化 Text', () => {
|
||||
expect(editor.txt).not.toBeUndefined()
|
||||
expect(editor.txt.eventHooks).not.toBeUndefined()
|
||||
})
|
||||
|
||||
test('编辑器初始化,会绑定一系列事件', () => {
|
||||
const eventHooks = editor.txt.eventHooks
|
||||
Object.keys(eventHooks).forEach(key => {
|
||||
// @ts-ignore
|
||||
expect(eventHooks[key].length).toBeGreaterThanOrEqual(0)
|
||||
})
|
||||
})
|
||||
|
||||
test('编辑器初始化后,调用 txt togglePlaceholder 如果 editor txt 没有 html 内容则会展示 placeholder', () => {
|
||||
editor.txt.togglePlaceholder()
|
||||
expect(editor.$textContainerElem.find('.placeholder').elems[0]).not.toBeUndefined()
|
||||
expect(editor.$textContainerElem.find('.placeholder').elems[0]).toHaveStyle('display:block')
|
||||
})
|
||||
|
||||
test('编辑器初始化后,调用 txt togglePlaceholder 如果 editor txt 有 html 内容则不展示 placeholder', () => {
|
||||
editor.txt.html('<p>123</p>')
|
||||
|
||||
editor.txt.togglePlaceholder()
|
||||
|
||||
expect(editor.$textContainerElem.find('.placeholder').elems[0]).toHaveStyle('display:none')
|
||||
})
|
||||
|
||||
test('编辑器初始化后,调用 txt clear 方法,清空编辑内容,只留下 p><br></p>', () => {
|
||||
editor.txt.html('<p>123</p>')
|
||||
|
||||
editor.txt.clear()
|
||||
|
||||
expect(editor.txt.html()).toBe('')
|
||||
expect(editor.$textElem.elems[0].innerHTML).toBe('<p><br></p>')
|
||||
})
|
||||
|
||||
test('编辑器初始化后,调用 txt setJSON 方法将 JSON 内容设置成 html', () => {
|
||||
editor.txt.setJSON(nodeList)
|
||||
|
||||
expect(editor.txt.html()).toBe(nodeListHtml)
|
||||
})
|
||||
|
||||
test('编辑器初始化后,调用 txt getJSON 方法将 html 内容还原成JSON', () => {
|
||||
editor.txt.html(nodeListHtml)
|
||||
|
||||
const res = editor.txt.getJSON()
|
||||
|
||||
expect(res).toEqual(nodeList)
|
||||
})
|
||||
|
||||
test('编辑器初始化后,调用 txt text 方法 能获取 html text', () => {
|
||||
editor.txt.html('<p>12345</p>')
|
||||
|
||||
expect(editor.txt.text()).toEqual('12345')
|
||||
})
|
||||
|
||||
test('编辑器初始化后,调用 txt text 方法 能设置 text', () => {
|
||||
editor.txt.text('12345')
|
||||
|
||||
expect(editor.txt.html()).toEqual('<p>12345</p>')
|
||||
})
|
||||
|
||||
test('编辑器初始化后,调用 txt append 方法 能追加 html', () => {
|
||||
editor.txt.append('12345<span>1234</span>')
|
||||
|
||||
expect(editor.txt.html()).toEqual('<p><br></p><p>12345<span>1234</span></p>')
|
||||
})
|
||||
|
||||
test('编辑器初始化后,编辑器区域会绑定 keyup 事件,触发保存range和激活菜单函数', () => {
|
||||
const saveRangeFn = jest.fn()
|
||||
const changeActiveFn = jest.fn()
|
||||
jest.spyOn(editor.selection, 'saveRange').mockImplementation(saveRangeFn)
|
||||
jest.spyOn(editor.menus, 'changeActive').mockImplementation(changeActiveFn)
|
||||
|
||||
dispatchEvent(editor.$textElem, 'keyup', 'KeyBoardEvent')
|
||||
expect(saveRangeFn).toBeCalled()
|
||||
expect(changeActiveFn).toBeCalled()
|
||||
})
|
||||
|
||||
test('编辑器初始化后,编辑器区域会绑定 mouseup mousedown 事件,对range进行处理,如果range不存在,不处理', () => {
|
||||
const saveRangeFn = jest.fn()
|
||||
const getRangeFn = jest.fn(() => null)
|
||||
jest.spyOn(editor.selection, 'saveRange').mockImplementation(saveRangeFn)
|
||||
jest.spyOn(editor.selection, 'getRange').mockImplementation(getRangeFn)
|
||||
|
||||
dispatchEvent(editor.$textElem, 'mousedown', 'MouseEvent')
|
||||
dispatchEvent(editor.$textElem, 'mouseup', 'MouseEvent')
|
||||
|
||||
expect(saveRangeFn).not.toBeCalled()
|
||||
})
|
||||
|
||||
test('编辑器初始化后,编辑器区域会绑定 mouseup mousedown 事件,对存在的range进行处理', () => {
|
||||
const saveRangeFn = jest.fn()
|
||||
|
||||
const getRangeFn = jest.fn(() => ({
|
||||
startOffest: 10,
|
||||
endOffset: 14,
|
||||
endContainer: $('<p>12345</p>').elems[0],
|
||||
setStart: jest.fn(),
|
||||
}))
|
||||
|
||||
jest.spyOn(editor.selection, 'saveRange').mockImplementation(saveRangeFn)
|
||||
// @ts-ignore
|
||||
jest.spyOn(editor.selection, 'getRange').mockImplementation(getRangeFn)
|
||||
|
||||
dispatchEvent(editor.$textElem, 'mousedown', 'MouseEvent')
|
||||
dispatchEvent(editor.$textElem, 'mouseup', 'MouseEvent')
|
||||
|
||||
expect(saveRangeFn).toBeCalled()
|
||||
})
|
||||
|
||||
test('编辑器初始化后,编辑器区域会绑定 click 事件,触发执行eventsHook clickEvent的函数执行', () => {
|
||||
const mockClickFn = jest.fn()
|
||||
|
||||
Object.defineProperty(editor.txt.eventHooks, 'clickEvents', {
|
||||
value: [mockClickFn, mockClickFn],
|
||||
})
|
||||
|
||||
dispatchEvent(editor.$textElem, 'click')
|
||||
|
||||
expect(mockClickFn.mock.calls.length).toEqual(2)
|
||||
})
|
||||
|
||||
test('编辑器初始化后,编辑器区域会绑定 enter键 keyup 事件,触发执行eventsHook enterUpEvents的函数执行', () => {
|
||||
const mockClickFn = jest.fn()
|
||||
|
||||
Object.defineProperty(editor.txt.eventHooks, 'enterUpEvents', {
|
||||
value: [mockClickFn, mockClickFn],
|
||||
})
|
||||
|
||||
dispatchEvent(editor.$textElem, 'keyup', 'KeyBoardEvent', {
|
||||
keyCode: 13,
|
||||
})
|
||||
|
||||
// 模拟不是enter键的情况
|
||||
dispatchEvent(editor.$textElem, 'keyup', 'KeyBoardEvent', {
|
||||
keyCode: 0,
|
||||
})
|
||||
|
||||
expect(mockClickFn.mock.calls.length).toEqual(2)
|
||||
})
|
||||
|
||||
test('编辑器初始化后,编辑器区域会绑定 keyup 事件,触发执行eventsHook keyupEvents的函数执行', () => {
|
||||
const mockClickFn = jest.fn()
|
||||
|
||||
Object.defineProperty(editor.txt.eventHooks, 'keyupEvents', {
|
||||
value: [mockClickFn, mockClickFn],
|
||||
})
|
||||
|
||||
dispatchEvent(editor.$textElem, 'keyup', 'KeyBoardEvent')
|
||||
|
||||
expect(mockClickFn.mock.calls.length).toEqual(2)
|
||||
})
|
||||
|
||||
test('编辑器初始化后,编辑器区域会绑定 delete键 keyup 事件,触发执行eventsHook deleteUpEvents的函数执行', () => {
|
||||
const mockClickFn = jest.fn()
|
||||
|
||||
Object.defineProperty(editor.txt.eventHooks, 'deleteUpEvents', {
|
||||
value: [mockClickFn, mockClickFn],
|
||||
})
|
||||
|
||||
dispatchEvent(editor.$textElem, 'keyup', 'KeyBoardEvent', {
|
||||
keyCode: 8,
|
||||
})
|
||||
|
||||
// 模拟不是delete键的情况
|
||||
dispatchEvent(editor.$textElem, 'keyup', 'KeyBoardEvent', {
|
||||
keyCode: 0,
|
||||
})
|
||||
|
||||
expect(mockClickFn.mock.calls.length).toEqual(2)
|
||||
})
|
||||
|
||||
test('编辑器初始化后,编辑器区域会绑定 delete键 keydown 事件,触发执行eventsHook deleteDownEvents的函数执行', () => {
|
||||
const mockClickFn = jest.fn()
|
||||
|
||||
Object.defineProperty(editor.txt.eventHooks, 'deleteDownEvents', {
|
||||
value: [mockClickFn, mockClickFn],
|
||||
})
|
||||
|
||||
dispatchEvent(editor.$textElem, 'keydown', 'KeyBoardEvent', {
|
||||
keyCode: 8,
|
||||
})
|
||||
|
||||
// 模拟不是delete键的情况
|
||||
dispatchEvent(editor.$textElem, 'keydown', 'KeyBoardEvent', {
|
||||
keyCode: 0,
|
||||
})
|
||||
|
||||
expect(mockClickFn.mock.calls.length).toEqual(2)
|
||||
})
|
||||
|
||||
test('编辑器初始化后,编辑器区域会绑定 paste 事件,触发执行eventsHook pasteEvents的函数执行', () => {
|
||||
const mockClickFn = jest.fn()
|
||||
|
||||
Object.defineProperty(editor.txt.eventHooks, 'pasteEvents', {
|
||||
value: [mockClickFn, mockClickFn],
|
||||
})
|
||||
|
||||
// 模拟IE
|
||||
jest.spyOn(UA, 'isIE')
|
||||
.mockImplementationOnce(() => true)
|
||||
.mockImplementationOnce(() => false)
|
||||
|
||||
dispatchEvent(editor.$textElem, 'paste', 'ClipboardEvent')
|
||||
|
||||
expect(mockClickFn.mock.calls.length).toEqual(0)
|
||||
|
||||
dispatchEvent(editor.$textElem, 'paste', 'ClipboardEvent')
|
||||
|
||||
expect(mockClickFn.mock.calls.length).toEqual(2)
|
||||
})
|
||||
|
||||
test('编辑器初始化后,编辑器区域会绑定 撤销和取消 快捷键,触发执行历史撤销和重做的函数执行', () => {
|
||||
const restoreFn = jest.fn()
|
||||
const revokeFn = jest.fn()
|
||||
|
||||
jest.spyOn(editor.history, 'restore').mockImplementation(restoreFn)
|
||||
jest.spyOn(editor.history, 'revoke').mockImplementation(revokeFn)
|
||||
|
||||
Object.defineProperty(editor, 'isFocus', {
|
||||
value: true,
|
||||
})
|
||||
|
||||
// 重做事件
|
||||
dispatchEvent(editor.$textElem, 'keydown', 'KeyBoardEvent', {
|
||||
keyCode: 90,
|
||||
shiftKey: true,
|
||||
ctrlKey: true,
|
||||
})
|
||||
|
||||
expect(restoreFn).toBeCalled()
|
||||
|
||||
// 撤回事件
|
||||
dispatchEvent(editor.$textElem, 'keydown', 'KeyBoardEvent', {
|
||||
keyCode: 90,
|
||||
shiftKey: false,
|
||||
ctrlKey: true,
|
||||
})
|
||||
|
||||
expect(revokeFn).toBeCalled()
|
||||
})
|
||||
|
||||
test('编辑器初始化后,编辑器区域会绑定 tab键 keyup 事件,触发执行eventsHook tabUpEvents的函数执行', () => {
|
||||
const mockClickFn = jest.fn()
|
||||
|
||||
Object.defineProperty(editor.txt.eventHooks, 'tabUpEvents', {
|
||||
value: [mockClickFn, mockClickFn],
|
||||
})
|
||||
|
||||
// 模拟不是tab键的情况
|
||||
dispatchEvent(editor.$textElem, 'keyup', 'KeyBoardEvent', {
|
||||
keyCode: 0,
|
||||
})
|
||||
|
||||
expect(mockClickFn.mock.calls.length).toEqual(0)
|
||||
|
||||
dispatchEvent(editor.$textElem, 'keyup', 'KeyBoardEvent', {
|
||||
keyCode: 9,
|
||||
})
|
||||
|
||||
expect(mockClickFn.mock.calls.length).toEqual(2)
|
||||
})
|
||||
|
||||
test('编辑器初始化后,编辑器区域会绑定 tab键 keydown 事件,触发执行eventsHook tabDownEvents的函数执行', () => {
|
||||
const mockClickFn = jest.fn()
|
||||
|
||||
Object.defineProperty(editor.txt.eventHooks, 'tabDownEvents', {
|
||||
value: [mockClickFn, mockClickFn],
|
||||
})
|
||||
|
||||
// 模拟不是tab键的情况
|
||||
dispatchEvent(editor.$textElem, 'keydown', 'KeyBoardEvent', {
|
||||
keyCode: 0,
|
||||
})
|
||||
|
||||
expect(mockClickFn.mock.calls.length).toEqual(0)
|
||||
|
||||
dispatchEvent(editor.$textElem, 'keydown', 'KeyBoardEvent', {
|
||||
keyCode: 9,
|
||||
})
|
||||
|
||||
expect(mockClickFn.mock.calls.length).toEqual(2)
|
||||
})
|
||||
|
||||
// todo 没法模拟
|
||||
test('编辑器初始化后,编辑器区域会绑定 scroll 事件,触发执行eventsHook textScrollEvents的函数执行', () => {
|
||||
const mockClickFn = jest.fn()
|
||||
|
||||
Object.defineProperty(editor.txt.eventHooks, 'textScrollEvents', {
|
||||
value: [mockClickFn, mockClickFn],
|
||||
})
|
||||
|
||||
dispatchEvent(editor.$textElem, 'scroll', 'Event')
|
||||
|
||||
expect(mockClickFn.mock.calls.length).toEqual(0)
|
||||
})
|
||||
|
||||
// todo 没法模拟
|
||||
test('编辑器初始化后,编辑器区域会禁用 dcument dragleave、drop、dragenter、dragover 事件', () => {
|
||||
const preventDefaultFn = jest.fn()
|
||||
|
||||
dispatchEvent(editor.$textElem, 'dragleave', 'MouseEvent', {
|
||||
preventDefault: preventDefaultFn,
|
||||
})
|
||||
|
||||
dispatchEvent(editor.$textElem, 'drop', 'MouseEvent', {
|
||||
preventDefault: preventDefaultFn,
|
||||
})
|
||||
|
||||
dispatchEvent(editor.$textElem, 'dragenter', 'MouseEvent', {
|
||||
preventDefault: preventDefaultFn,
|
||||
})
|
||||
|
||||
dispatchEvent(editor.$textElem, 'dragover', 'MouseEvent', {
|
||||
preventDefault: preventDefaultFn,
|
||||
})
|
||||
|
||||
expect(preventDefaultFn.mock.calls.length).toEqual(0)
|
||||
})
|
||||
|
||||
test('编辑器初始化后,编辑器区域 监听 链接点击事件, 触发执行eventsHook linkClickEvents的函数执行', () => {
|
||||
const mockClickFn = jest.fn()
|
||||
|
||||
Object.defineProperty(editor.txt.eventHooks, 'linkClickEvents', {
|
||||
value: [mockClickFn, mockClickFn],
|
||||
})
|
||||
|
||||
const a = $('<a href="http://www.wangeditor.com">wangeditor</a>')
|
||||
|
||||
editor.$textElem.append(a)
|
||||
|
||||
dispatchEvent(a, 'click', 'Event', {
|
||||
target: a.elems[0],
|
||||
})
|
||||
|
||||
expect(mockClickFn.mock.calls.length).toEqual(2)
|
||||
// 模拟事件代理的情况
|
||||
const target = $('<li></li>')
|
||||
const link = $('<a href="http://www.wangeditor.com">wangeditor</a>').append(target)
|
||||
|
||||
editor.$textElem.append(link)
|
||||
|
||||
dispatchEvent(target, 'click', 'Event', {
|
||||
target,
|
||||
})
|
||||
|
||||
expect(mockClickFn.mock.calls.length).toEqual(4)
|
||||
})
|
||||
|
||||
test('编辑器初始化后,编辑器区域 监听 img点击事件, 触发执行eventsHook imgClickEvents的函数执行', () => {
|
||||
const mockClickFn = jest.fn()
|
||||
|
||||
Object.defineProperty(editor.txt.eventHooks, 'imgClickEvents', {
|
||||
value: [mockClickFn, mockClickFn],
|
||||
})
|
||||
|
||||
const img = $('<img src="http://www.wangeditor.com/imgs/ali-pay.jpeg" />')
|
||||
|
||||
editor.$textElem.append(img)
|
||||
|
||||
dispatchEvent(img, 'click', 'Event', {
|
||||
target: img.elems[0],
|
||||
})
|
||||
|
||||
expect(mockClickFn.mock.calls.length).toEqual(2)
|
||||
|
||||
// 模拟表情点击的情况,不执行图片钩子函数
|
||||
const emotiomImg = $(
|
||||
'<img class="eleImg" src="http://www.wangeditor.com/imgs/ali-pay.jpeg" />'
|
||||
)
|
||||
|
||||
editor.$textElem.append(emotiomImg)
|
||||
|
||||
dispatchEvent(emotiomImg, 'click', 'Event', {
|
||||
target: emotiomImg.elems[0],
|
||||
})
|
||||
|
||||
expect(mockClickFn.mock.calls.length).toEqual(2)
|
||||
})
|
||||
|
||||
test('编辑器初始化后,编辑器区域 监听 code区域点击事件, 触发执行eventsHook codeClickEvents的函数执行', () => {
|
||||
const mockClickFn = jest.fn()
|
||||
|
||||
Object.defineProperty(editor.txt.eventHooks, 'codeClickEvents', {
|
||||
value: [mockClickFn, mockClickFn],
|
||||
})
|
||||
|
||||
const code = $('<pre>123</pre>')
|
||||
|
||||
editor.$textElem.append(code)
|
||||
|
||||
dispatchEvent(code, 'click', 'Event', {
|
||||
target: code.elems[0],
|
||||
})
|
||||
|
||||
expect(mockClickFn.mock.calls.length).toEqual(2)
|
||||
|
||||
// 模拟点击pre里面的元素
|
||||
const codeWrapper = $('<pre>123</pre>')
|
||||
const target = $('<span>123</span>')
|
||||
codeWrapper.append(target)
|
||||
|
||||
editor.$textElem.append(codeWrapper)
|
||||
|
||||
dispatchEvent(target, 'click', 'Event', {
|
||||
target: target.elems[0],
|
||||
})
|
||||
|
||||
editor.txt.html('')
|
||||
|
||||
// 模拟不是点击pre区域情况
|
||||
dispatchEvent(editor.$textElem, 'click', 'Event', {
|
||||
target: editor.$textElem.elems[0],
|
||||
})
|
||||
|
||||
expect(mockClickFn.mock.calls.length).toEqual(4)
|
||||
})
|
||||
|
||||
test('编辑器初始化后,编辑器区域 监听 hr标签点击事件, 触发执行eventsHook splitLineEvents的函数执行', () => {
|
||||
const mockClickFn = jest.fn()
|
||||
|
||||
Object.defineProperty(editor.txt.eventHooks, 'splitLineEvents', {
|
||||
value: [mockClickFn, mockClickFn],
|
||||
})
|
||||
|
||||
const hr = $('<hr />')
|
||||
|
||||
editor.$textElem.append(hr)
|
||||
|
||||
dispatchEvent(hr, 'click', 'Event', {
|
||||
target: hr.elems[0],
|
||||
})
|
||||
|
||||
expect(mockClickFn.mock.calls.length).toEqual(2)
|
||||
|
||||
// 模拟点击不是hr情况
|
||||
const target = $('<span>123</span>')
|
||||
|
||||
editor.$textElem.append(target)
|
||||
|
||||
dispatchEvent(target, 'click', 'Event', {
|
||||
target: target.elems[0],
|
||||
})
|
||||
|
||||
expect(mockClickFn.mock.calls.length).toEqual(2)
|
||||
})
|
||||
|
||||
test('编辑器初始化后,编辑区域容器添加监听点击事件, 点击的元素是图片拖拽调整大小的 bar, 触发执行eventsHook imgDragBarMouseDownEvents的函数执行', () => {
|
||||
const mockClickFn = jest.fn()
|
||||
|
||||
Object.defineProperty(editor.txt.eventHooks, 'imgDragBarMouseDownEvents', {
|
||||
value: [mockClickFn, mockClickFn],
|
||||
})
|
||||
|
||||
const target = $('<div class="w-e-img-drag-rb"></div>')
|
||||
|
||||
editor.$textContainerElem.append(target)
|
||||
|
||||
dispatchEvent(target, 'mousedown', 'KeyBoardEvent')
|
||||
|
||||
expect(mockClickFn.mock.calls.length).toEqual(2)
|
||||
})
|
||||
|
||||
test('编辑器初始化后,编辑器区域监听表格区域点击事件, 触发执行eventsHook tableClickEvents的函数执行', () => {
|
||||
const mockClickFn = jest.fn()
|
||||
|
||||
Object.defineProperty(editor.txt.eventHooks, 'tableClickEvents', {
|
||||
value: [mockClickFn, mockClickFn],
|
||||
})
|
||||
|
||||
const table = $('<table><tr><td>123</td></tr></table>')
|
||||
|
||||
editor.$textElem.append(table)
|
||||
|
||||
dispatchEvent($(table.childNodes()), 'click')
|
||||
|
||||
expect(mockClickFn.mock.calls.length).toEqual(2)
|
||||
|
||||
// 模拟点击非表格区域
|
||||
const target = $('<span>123</span>')
|
||||
editor.$textElem.append(target)
|
||||
|
||||
dispatchEvent(target, 'click', 'Event')
|
||||
|
||||
expect(mockClickFn.mock.calls.length).toEqual(2)
|
||||
})
|
||||
|
||||
test('编辑器初始化后,编辑器区域监听 ednter keydown 事件, 触发执行eventsHook enterDownEvents的函数执行', () => {
|
||||
const mockClickFn = jest.fn()
|
||||
|
||||
Object.defineProperty(editor.txt.eventHooks, 'enterDownEvents', {
|
||||
value: [mockClickFn, mockClickFn],
|
||||
})
|
||||
|
||||
dispatchEvent(editor.$textElem, 'keydown', 'KeyBoardEvent', {
|
||||
keyCode: 13,
|
||||
})
|
||||
|
||||
// 模拟非enter键按下
|
||||
dispatchEvent(editor.$textElem, 'keydown', 'KeyBoardEvent', {
|
||||
keyCode: 0,
|
||||
})
|
||||
|
||||
expect(mockClickFn.mock.calls.length).toEqual(2)
|
||||
})
|
||||
})
|
|
@ -0,0 +1,204 @@
|
|||
/**
|
||||
* @description text utils paste-text-html test
|
||||
* @author luochao
|
||||
*/
|
||||
import pasteTextHtml from '../../../src/text/event-hooks/paste-text-html'
|
||||
import createEditor from '../../helpers/create-editor'
|
||||
import * as pasteEvents from '../../../src/text/paste/paste-event'
|
||||
import $ from '../../../src/utils/dom-core'
|
||||
import mockCommand from '../../helpers/command-mock'
|
||||
|
||||
describe('text utils getPasteImgs test', () => {
|
||||
test('执行函数会绑定一个 pasteEvents handler', () => {
|
||||
const editor = createEditor(document, 'div1')
|
||||
const pasteEvents: Function[] = []
|
||||
pasteTextHtml(editor, pasteEvents)
|
||||
|
||||
expect(pasteEvents.length).toBeGreaterThanOrEqual(1)
|
||||
})
|
||||
|
||||
test('如果当前选区所在元素不存在,执行 pasteEvents 的函数直接返回', () => {
|
||||
const editor = createEditor(document, 'div2')
|
||||
const pasteEventList: Function[] = []
|
||||
|
||||
pasteTextHtml(editor, pasteEventList)
|
||||
|
||||
jest.spyOn(pasteEvents, 'getPasteText').mockImplementation(() => '123')
|
||||
jest.spyOn(pasteEvents, 'getPasteHtml').mockImplementation(() => '<p>123</p>')
|
||||
jest.spyOn(editor.selection, 'getSelectionContainerElem').mockImplementation(
|
||||
() => undefined
|
||||
)
|
||||
|
||||
pasteEventList.forEach(fn => {
|
||||
const res = fn(new Event(''))
|
||||
expect(res).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
test('如果当前选区所在元素为CODE, 则执行用户配置的 pasteTextHandle 函数', () => {
|
||||
mockCommand(document)
|
||||
|
||||
jest.spyOn(document, 'queryCommandSupported').mockImplementation(() => true)
|
||||
|
||||
const mockPasteTextHandle = jest.fn(() => 'mock123<br>')
|
||||
const editor = createEditor(document, 'div3', '', {
|
||||
pasteTextHandle: mockPasteTextHandle,
|
||||
})
|
||||
|
||||
const pasteEventList: Function[] = []
|
||||
|
||||
pasteTextHtml(editor, pasteEventList)
|
||||
|
||||
jest.spyOn(pasteEvents, 'getPasteText').mockImplementation(() => '1234255\n')
|
||||
jest.spyOn(pasteEvents, 'getPasteHtml').mockImplementation(() => '<p>1234</p>')
|
||||
jest.spyOn(editor.selection, 'getSelectionContainerElem').mockImplementation(() =>
|
||||
$('<code></code>')
|
||||
)
|
||||
|
||||
pasteEventList.forEach(fn => {
|
||||
fn(new Event(''))
|
||||
})
|
||||
|
||||
expect(mockPasteTextHandle).toBeCalledWith('1234255<br>')
|
||||
expect(document.execCommand).toBeCalledWith('insertHTML', false, 'mock123\n')
|
||||
})
|
||||
|
||||
test('如果复制的文本内容是 url,则插入链接', () => {
|
||||
mockCommand(document)
|
||||
|
||||
jest.spyOn(document, 'queryCommandSupported').mockImplementation(() => true)
|
||||
|
||||
const editor = createEditor(document, 'div4')
|
||||
|
||||
const pasteEventList: Function[] = []
|
||||
|
||||
pasteTextHtml(editor, pasteEventList)
|
||||
|
||||
const pasteText = 'http://www.wangeditor.com'
|
||||
|
||||
jest.spyOn(pasteEvents, 'getPasteText').mockImplementation(() => pasteText)
|
||||
jest.spyOn(pasteEvents, 'getPasteHtml').mockImplementation(() => '<p>1234</p>')
|
||||
jest.spyOn(editor.selection, 'getSelectionContainerElem').mockImplementation(() =>
|
||||
$('<p></p>')
|
||||
)
|
||||
|
||||
pasteEventList.forEach(fn => {
|
||||
fn(new Event(''))
|
||||
})
|
||||
|
||||
expect(document.execCommand).toBeCalledWith(
|
||||
'insertHTML',
|
||||
false,
|
||||
`<a href="${pasteText}" target="_blank">${pasteText}</a>`
|
||||
)
|
||||
})
|
||||
|
||||
test('如果复制的内容没有 html 内容,直接返回', () => {
|
||||
mockCommand(document)
|
||||
|
||||
jest.spyOn(document, 'queryCommandSupported').mockImplementation(() => true)
|
||||
|
||||
const editor = createEditor(document, 'div4')
|
||||
|
||||
const pasteEventList: Function[] = []
|
||||
|
||||
pasteTextHtml(editor, pasteEventList)
|
||||
|
||||
jest.spyOn(pasteEvents, 'getPasteText').mockImplementation(() => '123')
|
||||
jest.spyOn(pasteEvents, 'getPasteHtml').mockImplementation(() => '')
|
||||
jest.spyOn(editor.selection, 'getSelectionContainerElem').mockImplementation(() =>
|
||||
$('<p></p>')
|
||||
)
|
||||
|
||||
pasteEventList.forEach(fn => {
|
||||
const res = fn(new Event(''))
|
||||
expect(res).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
test('如果复制的内容没有 html 内容,直接返回', () => {
|
||||
mockCommand(document)
|
||||
|
||||
jest.spyOn(document, 'queryCommandSupported').mockImplementation(() => true)
|
||||
|
||||
const editor = createEditor(document, 'div4')
|
||||
|
||||
const pasteEventList: Function[] = []
|
||||
|
||||
pasteTextHtml(editor, pasteEventList)
|
||||
|
||||
jest.spyOn(pasteEvents, 'getPasteText').mockImplementation(() => '123')
|
||||
jest.spyOn(pasteEvents, 'getPasteHtml').mockImplementation(() => '')
|
||||
jest.spyOn(editor.selection, 'getSelectionContainerElem').mockImplementation(() =>
|
||||
$('<p></p>')
|
||||
)
|
||||
|
||||
pasteEventList.forEach(fn => {
|
||||
const res = fn(new Event(''))
|
||||
expect(res).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
test('如果复制内容有 html, 则执行用户配置的 pasteTextHandle 函数,并且会将非 p 标签的元素替换为 p 标签', () => {
|
||||
mockCommand(document)
|
||||
|
||||
jest.spyOn(document, 'queryCommandSupported').mockImplementation(() => true)
|
||||
|
||||
const mockPasteTextHandle = jest.fn(() => '<div>123</div><p></p>')
|
||||
const editor = createEditor(document, 'div3', '', {
|
||||
pasteTextHandle: mockPasteTextHandle,
|
||||
})
|
||||
|
||||
const pasteEventList: Function[] = []
|
||||
|
||||
pasteTextHtml(editor, pasteEventList)
|
||||
|
||||
jest.spyOn(pasteEvents, 'getPasteText').mockImplementation(() => '1234255\n')
|
||||
jest.spyOn(pasteEvents, 'getPasteHtml').mockImplementation(() => '<div>1234</div>')
|
||||
jest.spyOn(editor.selection, 'getSelectionContainerElem').mockImplementation(() =>
|
||||
$('<p></p>')
|
||||
)
|
||||
|
||||
pasteEventList.forEach(fn => {
|
||||
fn(new Event(''))
|
||||
})
|
||||
|
||||
expect(mockPasteTextHandle).toBeCalledWith('<div>1234</div>')
|
||||
expect(document.execCommand).toBeCalledWith('insertHTML', false, '<p>123</p><p><br></p>')
|
||||
})
|
||||
|
||||
test('如果复制内容有 html, 第一次插入 html 报错会使用 pasteText 再执行一次', () => {
|
||||
mockCommand(document)
|
||||
|
||||
jest.spyOn(document, 'queryCommandSupported').mockImplementation(() => true)
|
||||
jest.spyOn(document, 'execCommand')
|
||||
.mockImplementationOnce(() => {
|
||||
throw new Error('error')
|
||||
})
|
||||
.mockImplementationOnce(jest.fn())
|
||||
|
||||
const mockPasteTextHandle = jest.fn(() => '<div>123</div><p></p>')
|
||||
|
||||
const editor = createEditor(document, 'div3', '', {
|
||||
pasteTextHandle: mockPasteTextHandle,
|
||||
})
|
||||
|
||||
const pasteEventList: Function[] = []
|
||||
|
||||
pasteTextHtml(editor, pasteEventList)
|
||||
|
||||
jest.spyOn(pasteEvents, 'getPasteText').mockImplementation(() => '<div>12345</div>')
|
||||
jest.spyOn(pasteEvents, 'getPasteHtml').mockImplementation(() => '<div>1234</div>')
|
||||
jest.spyOn(editor.selection, 'getSelectionContainerElem').mockImplementation(() =>
|
||||
$('<p></p>')
|
||||
)
|
||||
|
||||
pasteEventList.forEach(fn => {
|
||||
fn(new Event(''))
|
||||
})
|
||||
|
||||
expect(mockPasteTextHandle).toBeCalledWith('<div>1234</div>')
|
||||
expect(mockPasteTextHandle).toBeCalledWith('<div>12345</div>')
|
||||
expect(document.execCommand).toBeCalledWith('insertHTML', false, '<p>123</p><p><br></p>')
|
||||
})
|
||||
})
|
|
@ -0,0 +1,159 @@
|
|||
/**
|
||||
* @description editor.text event-hooks tab-to-space test
|
||||
* @author luochao
|
||||
*/
|
||||
import tabHandler from '../../../src/text/event-hooks/tab-to-space'
|
||||
import $ from '../../../src/utils/dom-core'
|
||||
import createEditor from '../../helpers/create-editor'
|
||||
import mockCommand from '../../helpers/command-mock'
|
||||
|
||||
describe('editor.text event-hooks tab-to-space test', () => {
|
||||
test('能绑定一个处理 tab 的函数', () => {
|
||||
const fn: Function[] = []
|
||||
const editor = createEditor(document, 'div1')
|
||||
|
||||
tabHandler(editor, fn)
|
||||
|
||||
expect(fn.length).toBe(1)
|
||||
})
|
||||
|
||||
test('能绑定一个处理 tab 的函数,如果不支持 insertHTML 指令,则不执行后续的插入操作', () => {
|
||||
mockCommand(document)
|
||||
|
||||
const fn: Function[] = []
|
||||
const editor = createEditor(document, 'div1')
|
||||
|
||||
jest.spyOn(editor.cmd, 'queryCommandSupported').mockImplementation(() => false)
|
||||
|
||||
tabHandler(editor, fn)
|
||||
|
||||
fn.forEach(fn => {
|
||||
fn()
|
||||
})
|
||||
|
||||
expect(document.execCommand).not.toBeCalled()
|
||||
})
|
||||
|
||||
test('能绑定一个处理 tab 的函数,如果没有选区内容,则不执行后续的插入操作', () => {
|
||||
mockCommand(document)
|
||||
|
||||
const fn: Function[] = []
|
||||
const editor = createEditor(document, 'div1')
|
||||
|
||||
jest.spyOn(editor.cmd, 'queryCommandSupported').mockImplementation(() => true)
|
||||
jest.spyOn(editor.selection, 'getSelectionContainerElem').mockImplementation(
|
||||
() => undefined
|
||||
)
|
||||
|
||||
tabHandler(editor, fn)
|
||||
|
||||
fn.forEach(fn => {
|
||||
fn()
|
||||
})
|
||||
|
||||
expect(document.execCommand).not.toBeCalled()
|
||||
})
|
||||
|
||||
test('能绑定一个处理 tab 的函数,如果有选区内容,并且是正常的HTML元素,则插入空格', () => {
|
||||
mockCommand(document)
|
||||
|
||||
const fn: Function[] = []
|
||||
const editor = createEditor(document, 'div1')
|
||||
|
||||
jest.spyOn(editor.cmd, 'queryCommandSupported').mockImplementation(() => true)
|
||||
const container = $('<p><br></p>')
|
||||
container.append($('<p>123</p>'))
|
||||
jest.spyOn(editor.selection, 'getSelectionContainerElem').mockImplementation(
|
||||
() => container
|
||||
)
|
||||
|
||||
tabHandler(editor, fn)
|
||||
|
||||
fn.forEach(fn => {
|
||||
fn()
|
||||
})
|
||||
|
||||
expect(document.execCommand).toBeCalledWith('insertHTML', false, ' ')
|
||||
})
|
||||
|
||||
describe('当选区内容父元素为code,pre,hljs或者选区元素为code的情况,则插入特殊的空格', () => {
|
||||
mockCommand(document)
|
||||
|
||||
let editor: ReturnType<typeof createEditor>
|
||||
let id = 1
|
||||
let fn: Function[] = []
|
||||
|
||||
beforeEach(() => {
|
||||
editor = createEditor(document, `div${id++}`)
|
||||
|
||||
tabHandler(editor, fn)
|
||||
|
||||
jest.spyOn(editor.cmd, 'queryCommandSupported').mockImplementation(() => true)
|
||||
})
|
||||
|
||||
// mock getSelectionContainerElem return value
|
||||
const mockGetSelectionContainerElem = (tagString: string, isChild = true) => {
|
||||
const container = $(tagString)
|
||||
container.append($('<p>123</p>'))
|
||||
jest.spyOn(editor.selection, 'getSelectionContainerElem').mockImplementation(() =>
|
||||
isChild ? container.children()! : container
|
||||
)
|
||||
}
|
||||
|
||||
test('选区元素是 CODE', () => {
|
||||
mockGetSelectionContainerElem('<code></code>', false)
|
||||
|
||||
fn.forEach(fn => {
|
||||
fn()
|
||||
})
|
||||
|
||||
expect(document.execCommand).toBeCalledWith(
|
||||
'insertHTML',
|
||||
false,
|
||||
editor.config.languageTab
|
||||
)
|
||||
})
|
||||
|
||||
test('选区元素父元素是 CODE', () => {
|
||||
mockGetSelectionContainerElem('<code></code>')
|
||||
|
||||
fn.forEach(fn => {
|
||||
fn()
|
||||
})
|
||||
|
||||
expect(document.execCommand).toBeCalledWith(
|
||||
'insertHTML',
|
||||
false,
|
||||
editor.config.languageTab
|
||||
)
|
||||
})
|
||||
|
||||
test('选区元素父元素是 PRE', () => {
|
||||
mockGetSelectionContainerElem('<pre></pre>')
|
||||
|
||||
fn.forEach(fn => {
|
||||
fn()
|
||||
})
|
||||
|
||||
expect(document.execCommand).toBeCalledWith(
|
||||
'insertHTML',
|
||||
false,
|
||||
editor.config.languageTab
|
||||
)
|
||||
})
|
||||
|
||||
test('选区元素父元素是 hljs', () => {
|
||||
mockGetSelectionContainerElem('<hljs></hljs>')
|
||||
|
||||
fn.forEach(fn => {
|
||||
fn()
|
||||
})
|
||||
|
||||
expect(document.execCommand).toBeCalledWith(
|
||||
'insertHTML',
|
||||
false,
|
||||
editor.config.languageTab
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
Loading…
Reference in New Issue