Skip to content

Commit e90ed16

Browse files
committed
fix: added tests for replaceQuote and made its implementation more robust
1 parent de63238 commit e90ed16

File tree

2 files changed

+47
-11
lines changed

2 files changed

+47
-11
lines changed

spec/smartQuotes.spec.js

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {expect} from 'chai'
2-
import {isDoubleQuote, isSingleQuote, isWhitespace, isSeparatorOrWhitespace, isApostrophe} from '../src/smartQuotes'
2+
import {isDoubleQuote, isSingleQuote, isWhitespace, isSeparatorOrWhitespace, isApostrophe, replaceQuote} from '../src/smartQuotes'
33

44
const allSingleQuotes = ['‘', '’', '‹', '›', '‚', '‘', '›', '‹', `'`, `‘`]
55
const allDoubleQuotes = ['«', '»', '»', '«', '"', '"', '“', '”', '”', '”', '“', '“', '„', '“']
@@ -8,7 +8,7 @@ const nonStringValues = [undefined, null, true, 123, NaN]
88
const whitespaceChars = [' ', '\t', '\n', '\r', '\v', '\f']
99
const separatorValues = ['>', '-', '–—']
1010

11-
describe('Smart Quotes Helper Functions', () => {
11+
describe('Smart Quotes Helper Functions:', () => {
1212
describe('isDoubleQuote', () => {
1313
it('Should return false for non double quote values', () => {
1414
[...charValues, ...separatorValues, ...nonStringValues, ...allSingleQuotes].forEach(value => {
@@ -52,7 +52,7 @@ describe('Smart Quotes Helper Functions', () => {
5252
})
5353

5454
describe('isSeparatorOrWhitespace', () => {
55-
it('should return false for non whitespace / separator characters', () => {
55+
it('should return false for non whitespace/ separator characters', () => {
5656
[...charValues, ...nonStringValues ].forEach(value => {
5757
expect(isSeparatorOrWhitespace(value)).to.equal(false, `Failed for: ${value}`)
5858
})
@@ -80,3 +80,37 @@ describe('Smart Quotes Helper Functions', () => {
8080
})
8181
})
8282

83+
const createRangeWithText = (text) => {
84+
const textNode = document.createTextNode(text)
85+
const range = document.createRange()
86+
range.selectNodeContents(textNode)
87+
return range
88+
}
89+
90+
describe('replaceQuote(): ', () => {
91+
const testString = '123 "you'
92+
const index = testString.indexOf('"')
93+
94+
it('should replace quote at given index', () => {
95+
const range = createRangeWithText(testString)
96+
const replacedTextNode = replaceQuote(range, index, '`')
97+
expect(replacedTextNode.textContent).to.equal('123 `you')
98+
})
99+
100+
it('should return null if range is invalid', () => {
101+
const invalidNodeValue = replaceQuote(undefined, index, '`')
102+
expect(invalidNodeValue).to.equal(null)
103+
})
104+
105+
it('should return null if range is empty', () => {
106+
const range = createRangeWithText('')
107+
const replacedTextNode = replaceQuote(range, 0, '`')
108+
expect(replacedTextNode).to.equal(null)
109+
})
110+
111+
it('should insert quote at the end, if index is out of bounce', () => {
112+
const range = createRangeWithText(testString)
113+
const replacedTextNode = replaceQuote(range, 40, '`')
114+
expect(replacedTextNode.textContent).to.equal(`${testString}${'`'}`)
115+
})
116+
})

src/smartQuotes.js

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ export const shouldApplySmartQuotes = (config, target) => {
44
const {smartQuotes, quotes, singleQuotes} = config
55
return !!smartQuotes && isValidQuotePairConfig(quotes) && isValidQuotePairConfig(singleQuotes) && target.isContentEditable
66
}
7-
// TODO: isDoubleQuote, isSingleQuote, isApostrophe, isWhitespace accept more than one char
7+
88
export const isDoubleQuote = (char) => /^[«»"]$/.test(char)
99
export const isSingleQuote = (char) => /^[']$/.test(char)
1010
export const isApostrophe = (char) => /^[']$/.test(char)
@@ -15,14 +15,16 @@ const shouldBeOpeningQuote = (text, indexCharBefore) => indexCharBefore < 0 || i
1515
const shouldBeClosingQuote = (text, indexCharBefore) => !!text[indexCharBefore] && !isSeparatorOrWhitespace(text[indexCharBefore])
1616
const hasCharAfter = (textArr, indexCharAfter) => !!textArr[indexCharAfter] && !isWhitespace(textArr[indexCharAfter])
1717

18-
const replaceQuote = (range, index, quoteType) => {
19-
const startContainer = range.startContainer
20-
if (!startContainer.nodeValue) {
21-
return
18+
export const replaceQuote = (range, index, quoteType) => {
19+
const startContainer = range?.startContainer
20+
const nodeValue = startContainer?.nodeValue
21+
if (!nodeValue) {
22+
return null
2223
}
23-
const textNode = document.createTextNode(`${startContainer.nodeValue.substring(0, index)}${quoteType}${startContainer.nodeValue.substring(index + 1)}`)
24-
startContainer.replaceWith(textNode)
25-
return textNode
24+
const newText = `${nodeValue.substring(0, index)}${quoteType}${nodeValue.substring(index + 1)}`
25+
const newTextNode = document.createTextNode(newText)
26+
startContainer.replaceWith(newTextNode)
27+
return newTextNode
2628
}
2729

2830
const hasSingleOpeningQuote = (textArr, offset, singleOpeningQuote) => {

0 commit comments

Comments
 (0)