Skip to content

Commit

Permalink
fix(NcRichContenteditable): fix IME support
Browse files Browse the repository at this point in the history
Signed-off-by: Grigorii K. Shartsev <me@shgk.me>
  • Loading branch information
ShGKme committed Jul 11, 2023
1 parent 3614945 commit 60c8790
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 5 deletions.
9 changes: 9 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@
*/

const ignorePatterns = [
'ansi-regex',
'bail',
'char-regex',
'comma-separated-tokens',
'decode-named-character-reference',
'escape-string-regexp',
Expand All @@ -33,6 +35,9 @@ const ignorePatterns = [
'rehype-*',
'remark-*',
'space-separated-tokens',
'string-length',
'strip-ansi',
'tributejs',
'trim-lines',
'trough',
'unified',
Expand Down Expand Up @@ -62,6 +67,10 @@ module.exports = {
'/node_modules/(?!(' + ignorePatterns.join('|') + '))',
],

moduleNameMapper: {
'\\.(css|scss)$': 'jest-transform-stub',
},

snapshotSerializers: [
'<rootDir>/node_modules/jest-serializer-vue',
],
Expand Down
18 changes: 13 additions & 5 deletions src/components/NcRichContenteditable/NcRichContenteditable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,8 @@ export default {
role="textbox"
v-on="listeners"
@input="onInput"
@compositionstart="isComposing = true"
@compositionend="isComposing = false"
@keydown.delete="onDelete"
@keydown.enter.exact="onEnter"
@keydown.ctrl.enter.exact.stop.prevent="onCtrlEnter"
Expand Down Expand Up @@ -364,6 +366,9 @@ export default {
// serves no other purpose than to check whether the
// content is empty or not
localValue: this.value,
// Is in text composition session in IME
isComposing: false,
}
},
Expand Down Expand Up @@ -672,17 +677,20 @@ export default {
event.preventDefault()
}
},
/**
* Enter key pressed. Submits if not multiline
*
* @param {Event} event the keydown event
*/
onEnter(event) {
// Prevent submitting if autocompletion menu
// is opened or length is over maxlength
if (this.multiline || this.isOverMaxlength
|| this.autocompleteTribute.isActive || this.emojiTribute.isActive || this.linkTribute.isActive) {
// Prevent submitting if multiline
// or length is over maxlength
// or autocompletion menu is opened
// or in a text composition session with IME
if (this.multiline
|| this.isOverMaxlength
|| this.autocompleteTribute.isActive || this.emojiTribute.isActive || this.linkTribute.isActive
|| this.isComposing) {
return
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { mount } from '@vue/test-utils'
import NcRichContenteditable from '../../../../src/components/NcRichContenteditable/NcRichContenteditable.vue'
import Tribute from 'tributejs/dist/tribute.esm.js'

// FIXME: find a way to use Tribute in JSDOM or test with e2e
jest.mock('tributejs/dist/tribute.esm.js')
Tribute.mockImplementation(() => ({
attach: jest.fn(),
detach: jest.fn(),
}))

/**
* Mount NcRichContentEditable
*
* @param {object} options mount options
* @param {object} options.propsData mount options.propsData
* @param {object} options.listeners mount options.listeners
* @param {object} options.attrs mount options.attrs
* @return {object}
*/
function mountNcRichContenteditable({ propsData, listeners, attrs } = {}) {
let currentValue = propsData?.value

const wrapper = mount(NcRichContenteditable, {
propsData: {
value: currentValue,
...propsData,
},
listeners: {
'update:value': ($event) => {
currentValue = $event
wrapper.setProps({ value: $event })
},
...listeners,
},
attrs: {
...attrs,
},
attachTo: document.body,
})

const getCurrentValue = () => currentValue

const inputValue = async (newValue) => {
wrapper.element.innerHTML += newValue
await wrapper.trigger('input')
}

return {
wrapper,
getCurrentValue,
inputValue,
}
}

describe('NcRichContenteditable', () => {
it('should update value during input', async () => {
const { wrapper, inputValue } = mountNcRichContenteditable()
const TEST_TEXT = 'Test Text'
await inputValue('Test Text')
expect(wrapper.emitted('update:value')).toBeDefined()
expect(wrapper.emitted('update:value').at(-1)[0]).toBe(TEST_TEXT)
})

it('should not emit "submit" during input', async () => {
const { wrapper, inputValue } = mountNcRichContenteditable()
await inputValue('Test Text')
expect(wrapper.emitted('submit')).not.toBeDefined()
})

it('should emit "paste" on past', async () => {
const { wrapper } = mountNcRichContenteditable()
await wrapper.trigger('paste', { clipboardData: { getData: () => 'PASTED_TEXT', files: [], items: {} } })
expect(wrapper.emitted('paste')).toBeDefined()
expect(wrapper.emitted('paste')).toHaveLength(1)
})

it('should emit "submit" on Enter', async () => {
const { wrapper, inputValue } = mountNcRichContenteditable()

await inputValue('Test Text')

await wrapper.trigger('keydown', { keyCode: 13 }) // Enter

expect(wrapper.emitted('submit')).toBeDefined()
expect(wrapper.emitted('submit')).toHaveLength(1)
})

it('should not emit "submit" on Enter during composition session', async () => {
const { wrapper, inputValue } = mountNcRichContenteditable()

await wrapper.trigger('compositionstart')
await inputValue('猫')
await wrapper.trigger('keydown', { keyCode: 13 }) // Enter
await wrapper.trigger('compositionend')
await inputValue(' - means "Cat"')
await wrapper.trigger('keydown', { keyCode: 13 }) // Enter

expect(wrapper.emitted('submit')).toBeDefined()
expect(wrapper.emitted('submit')).toHaveLength(1)
})

it('should proxy component events listeners to native event handlers', async () => {
const handlers = {
focus: jest.fn(),
paste: jest.fn(),
blur: jest.fn(),
}
const { wrapper } = mountNcRichContenteditable({
listeners: handlers,
})

await wrapper.trigger('focus')
await wrapper.trigger('paste', { clipboardData: { getData: () => 'PASTED_TEXT', files: [], items: {} } })
await wrapper.trigger('blur')

expect(handlers.focus).toHaveBeenCalledTimes(1)
expect(handlers.paste).toHaveBeenCalledTimes(1)
expect(handlers.blur).toHaveBeenCalledTimes(1)
})
})

0 comments on commit 60c8790

Please sign in to comment.