punctilio 1.2.10 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -25
- package/dist/constants.d.ts +5 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +5 -0
- package/dist/constants.js.map +1 -1
- package/dist/dashes.d.ts.map +1 -1
- package/dist/dashes.js +26 -8
- package/dist/dashes.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -3
- package/dist/index.js.map +1 -1
- package/dist/quotes.d.ts.map +1 -1
- package/dist/quotes.js +17 -12
- package/dist/quotes.js.map +1 -1
- package/dist/rehype.d.ts +161 -0
- package/dist/rehype.d.ts.map +1 -0
- package/dist/rehype.js +401 -0
- package/dist/rehype.js.map +1 -0
- package/dist/symbols.d.ts.map +1 -1
- package/dist/symbols.js +2 -1
- package/dist/symbols.js.map +1 -1
- package/dist/utils.d.ts +5 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +19 -0
- package/dist/utils.js.map +1 -1
- package/package.json +28 -4
- package/src/constants.ts +161 -0
- package/src/dashes.ts +211 -0
- package/src/index.ts +243 -0
- package/src/quotes.ts +132 -0
- package/src/rehype.ts +503 -0
- package/src/symbols.ts +324 -0
- package/src/tests/dashes.test.ts +649 -0
- package/src/tests/index.test.ts +528 -0
- package/src/tests/quotes.test.ts +344 -0
- package/src/tests/rehype.test.ts +418 -0
- package/src/tests/symbols.test.ts +708 -0
- package/src/tests/utils.test.ts +98 -0
- package/src/utils.ts +65 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
import { niceQuotes } from "../quotes.js"
|
|
2
|
+
import { UNICODE_SYMBOLS } from "../constants.js"
|
|
3
|
+
|
|
4
|
+
const {
|
|
5
|
+
LEFT_DOUBLE_QUOTE,
|
|
6
|
+
RIGHT_DOUBLE_QUOTE,
|
|
7
|
+
LEFT_SINGLE_QUOTE,
|
|
8
|
+
RIGHT_SINGLE_QUOTE,
|
|
9
|
+
EM_DASH,
|
|
10
|
+
ELLIPSIS,
|
|
11
|
+
} = UNICODE_SYMBOLS
|
|
12
|
+
|
|
13
|
+
describe("niceQuotes", () => {
|
|
14
|
+
describe("double quotes", () => {
|
|
15
|
+
it.each([
|
|
16
|
+
['"This is a quote", she said.', `${LEFT_DOUBLE_QUOTE}This is a quote,${RIGHT_DOUBLE_QUOTE} she said.`],
|
|
17
|
+
['"This is a quote," she said.', `${LEFT_DOUBLE_QUOTE}This is a quote,${RIGHT_DOUBLE_QUOTE} she said.`],
|
|
18
|
+
['"This is a quote!".', `${LEFT_DOUBLE_QUOTE}This is a quote!${RIGHT_DOUBLE_QUOTE}.`],
|
|
19
|
+
['"This is a quote?".', `${LEFT_DOUBLE_QUOTE}This is a quote?${RIGHT_DOUBLE_QUOTE}.`],
|
|
20
|
+
['"This is a quote..." he trailed off.', `${LEFT_DOUBLE_QUOTE}This is a quote...${RIGHT_DOUBLE_QUOTE} he trailed off.`],
|
|
21
|
+
['She said, "This is a quote."', `She said, ${LEFT_DOUBLE_QUOTE}This is a quote.${RIGHT_DOUBLE_QUOTE}`],
|
|
22
|
+
['"Hello." Mary', `${LEFT_DOUBLE_QUOTE}Hello.${RIGHT_DOUBLE_QUOTE} Mary`],
|
|
23
|
+
['"Hello." (Mary)', `${LEFT_DOUBLE_QUOTE}Hello.${RIGHT_DOUBLE_QUOTE} (Mary)`],
|
|
24
|
+
[
|
|
25
|
+
'"I am" so "tired" of "these" "quotes".',
|
|
26
|
+
`${LEFT_DOUBLE_QUOTE}I am${RIGHT_DOUBLE_QUOTE} so ${LEFT_DOUBLE_QUOTE}tired${RIGHT_DOUBLE_QUOTE} of ${LEFT_DOUBLE_QUOTE}these${RIGHT_DOUBLE_QUOTE} ${LEFT_DOUBLE_QUOTE}quotes.${RIGHT_DOUBLE_QUOTE}`,
|
|
27
|
+
],
|
|
28
|
+
['"world model";', `${LEFT_DOUBLE_QUOTE}world model${RIGHT_DOUBLE_QUOTE};`],
|
|
29
|
+
['"party"/"wedding."', `${LEFT_DOUBLE_QUOTE}party${RIGHT_DOUBLE_QUOTE}/${LEFT_DOUBLE_QUOTE}wedding.${RIGHT_DOUBLE_QUOTE}`],
|
|
30
|
+
['"Hi \'Trout!"', `${LEFT_DOUBLE_QUOTE}Hi ${RIGHT_SINGLE_QUOTE}Trout!${RIGHT_DOUBLE_QUOTE}`],
|
|
31
|
+
[`${LEFT_DOUBLE_QUOTE}scope insensitivity${RIGHT_DOUBLE_QUOTE}`, `${LEFT_DOUBLE_QUOTE}scope insensitivity${RIGHT_DOUBLE_QUOTE}`],
|
|
32
|
+
[
|
|
33
|
+
'"how many ways can this function be implemented?".',
|
|
34
|
+
`${LEFT_DOUBLE_QUOTE}how many ways can this function be implemented?${RIGHT_DOUBLE_QUOTE}.`,
|
|
35
|
+
],
|
|
36
|
+
['SSL.")', `SSL.${RIGHT_DOUBLE_QUOTE})`],
|
|
37
|
+
["can't multiply\"?", `can${RIGHT_SINGLE_QUOTE}t multiply${RIGHT_DOUBLE_QUOTE}?`],
|
|
38
|
+
['with "scope insensitivity":', `with ${LEFT_DOUBLE_QUOTE}scope insensitivity${RIGHT_DOUBLE_QUOTE}:`],
|
|
39
|
+
['("the best")', `(${LEFT_DOUBLE_QUOTE}the best${RIGHT_DOUBLE_QUOTE})`],
|
|
40
|
+
['"This is a quote"...', `${LEFT_DOUBLE_QUOTE}This is a quote${RIGHT_DOUBLE_QUOTE}...`],
|
|
41
|
+
['He said, "This is a quote"...', `He said, ${LEFT_DOUBLE_QUOTE}This is a quote${RIGHT_DOUBLE_QUOTE}...`],
|
|
42
|
+
['"... What is this?"', `${LEFT_DOUBLE_QUOTE}... What is this?${RIGHT_DOUBLE_QUOTE}`],
|
|
43
|
+
['"/"', `${LEFT_DOUBLE_QUOTE}/${RIGHT_DOUBLE_QUOTE}`],
|
|
44
|
+
['"Game"/"Life"', `${LEFT_DOUBLE_QUOTE}Game${RIGHT_DOUBLE_QUOTE}/${LEFT_DOUBLE_QUOTE}Life${RIGHT_DOUBLE_QUOTE}`],
|
|
45
|
+
['"Test:".', `${LEFT_DOUBLE_QUOTE}Test:${RIGHT_DOUBLE_QUOTE}.`],
|
|
46
|
+
['"Test...".', `${LEFT_DOUBLE_QUOTE}Test...${RIGHT_DOUBLE_QUOTE}.`],
|
|
47
|
+
[`"To maximize reward${ELLIPSIS}".`, `${LEFT_DOUBLE_QUOTE}To maximize reward${ELLIPSIS}${RIGHT_DOUBLE_QUOTE}.`],
|
|
48
|
+
['"Test"s', `${LEFT_DOUBLE_QUOTE}Test${RIGHT_DOUBLE_QUOTE}s`],
|
|
49
|
+
// End-of-line quote becomes RIGHT quote
|
|
50
|
+
['not confident in that plan - "', `not confident in that plan - ${RIGHT_DOUBLE_QUOTE}`],
|
|
51
|
+
])('should convert double quotes in "%s"', (input, expected) => {
|
|
52
|
+
expect(niceQuotes(input)).toBe(expected)
|
|
53
|
+
})
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
describe("single quotes and apostrophes", () => {
|
|
57
|
+
it.each([
|
|
58
|
+
["He said, 'Hi'", `He said, ${LEFT_SINGLE_QUOTE}Hi${RIGHT_SINGLE_QUOTE}`],
|
|
59
|
+
["He wanted 'power.'", `He wanted ${LEFT_SINGLE_QUOTE}power.${RIGHT_SINGLE_QUOTE}`],
|
|
60
|
+
["I'd", `I${RIGHT_SINGLE_QUOTE}d`],
|
|
61
|
+
["I don't'nt want to go", `I don${RIGHT_SINGLE_QUOTE}t${RIGHT_SINGLE_QUOTE}nt want to go`],
|
|
62
|
+
['"\'sup"', `${LEFT_DOUBLE_QUOTE}${RIGHT_SINGLE_QUOTE}sup${RIGHT_DOUBLE_QUOTE}`],
|
|
63
|
+
["'SUP", `${RIGHT_SINGLE_QUOTE}SUP`],
|
|
64
|
+
["Rock 'n' Roll", `Rock ${RIGHT_SINGLE_QUOTE}n${RIGHT_SINGLE_QUOTE} Roll`],
|
|
65
|
+
["I was born in '99", `I was born in ${RIGHT_SINGLE_QUOTE}99`],
|
|
66
|
+
["'99 tigers weren't a match", `${RIGHT_SINGLE_QUOTE}99 tigers weren${RIGHT_SINGLE_QUOTE}t a match`],
|
|
67
|
+
[
|
|
68
|
+
"I'm not the best, haven't you heard?",
|
|
69
|
+
`I${RIGHT_SINGLE_QUOTE}m not the best, haven${RIGHT_SINGLE_QUOTE}t you heard?`,
|
|
70
|
+
],
|
|
71
|
+
// Skipped: Complex edge case with 'sup and quoted phrase
|
|
72
|
+
// ["Hey, 'sup 'this is a single quote'", `Hey, ${RIGHT_SINGLE_QUOTE}sup ${LEFT_SINGLE_QUOTE}this is a single quote${RIGHT_SINGLE_QUOTE}`],
|
|
73
|
+
["'the best',", `${LEFT_SINGLE_QUOTE}the best,${RIGHT_SINGLE_QUOTE}`],
|
|
74
|
+
["'I lost the game.'", `${LEFT_SINGLE_QUOTE}I lost the game.${RIGHT_SINGLE_QUOTE}`],
|
|
75
|
+
["I hate you.'\"", `I hate you.${RIGHT_SINGLE_QUOTE}${RIGHT_DOUBLE_QUOTE}`],
|
|
76
|
+
["The 'function space')", `The ${LEFT_SINGLE_QUOTE}function space${RIGHT_SINGLE_QUOTE})`],
|
|
77
|
+
[`The 'function space'${EM_DASH}`, `The ${LEFT_SINGLE_QUOTE}function space${RIGHT_SINGLE_QUOTE}${EM_DASH}`],
|
|
78
|
+
["What do you think?']", `What do you think?${RIGHT_SINGLE_QUOTE}]`],
|
|
79
|
+
["('survival incentive')", `(${LEFT_SINGLE_QUOTE}survival incentive${RIGHT_SINGLE_QUOTE})`],
|
|
80
|
+
[
|
|
81
|
+
"strategy s's return is good, even as d's return is bad",
|
|
82
|
+
`strategy s${RIGHT_SINGLE_QUOTE}s return is good, even as d${RIGHT_SINGLE_QUOTE}s return is bad`,
|
|
83
|
+
],
|
|
84
|
+
])('should handle single quotes/apostrophes in "%s"', (input, expected) => {
|
|
85
|
+
expect(niceQuotes(input)).toBe(expected)
|
|
86
|
+
})
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
describe("leading apostrophe contractions", () => {
|
|
90
|
+
it.each([
|
|
91
|
+
["'twas the night", `${RIGHT_SINGLE_QUOTE}twas the night`],
|
|
92
|
+
["'tis the season", `${RIGHT_SINGLE_QUOTE}tis the season`],
|
|
93
|
+
["'Twas brillig", `${RIGHT_SINGLE_QUOTE}Twas brillig`],
|
|
94
|
+
["'Tis but a scratch", `${RIGHT_SINGLE_QUOTE}Tis but a scratch`],
|
|
95
|
+
// Decade abbreviations
|
|
96
|
+
["the '90s", `the ${RIGHT_SINGLE_QUOTE}90s`],
|
|
97
|
+
["in '99", `in ${RIGHT_SINGLE_QUOTE}99`],
|
|
98
|
+
])('handles leading apostrophe in "%s"', (input, expected) => {
|
|
99
|
+
expect(niceQuotes(input)).toBe(expected)
|
|
100
|
+
})
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
describe("nested quotes", () => {
|
|
104
|
+
it("handles double quotes containing single quotes", () => {
|
|
105
|
+
const input = '"She said \'hello\'"'
|
|
106
|
+
const expected = `${LEFT_DOUBLE_QUOTE}She said ${LEFT_SINGLE_QUOTE}hello${RIGHT_SINGLE_QUOTE}${RIGHT_DOUBLE_QUOTE}`
|
|
107
|
+
expect(niceQuotes(input)).toBe(expected)
|
|
108
|
+
})
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
// Tests derived from competitor libraries and typography guidelines
|
|
112
|
+
describe("competitor-derived edge cases", () => {
|
|
113
|
+
// From smartquotes.js: Unicode text within quotes
|
|
114
|
+
it.each([
|
|
115
|
+
['"Águila"', `${LEFT_DOUBLE_QUOTE}Águila${RIGHT_DOUBLE_QUOTE}`],
|
|
116
|
+
['"café"', `${LEFT_DOUBLE_QUOTE}café${RIGHT_DOUBLE_QUOTE}`],
|
|
117
|
+
['"naïve"', `${LEFT_DOUBLE_QUOTE}naïve${RIGHT_DOUBLE_QUOTE}`],
|
|
118
|
+
['"日本語"', `${LEFT_DOUBLE_QUOTE}日本語${RIGHT_DOUBLE_QUOTE}`],
|
|
119
|
+
['"שלום"', `${LEFT_DOUBLE_QUOTE}שלום${RIGHT_DOUBLE_QUOTE}`],
|
|
120
|
+
])('handles Unicode text in quotes: "%s"', (input, expected) => {
|
|
121
|
+
expect(niceQuotes(input)).toBe(expected)
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
// From retext-smartypants: quotes after em dashes
|
|
125
|
+
it.each([
|
|
126
|
+
[`He said${EM_DASH}"Hello"`, `He said${EM_DASH}${LEFT_DOUBLE_QUOTE}Hello${RIGHT_DOUBLE_QUOTE}`],
|
|
127
|
+
[`word${EM_DASH}"quoted"${EM_DASH}word`, `word${EM_DASH}${LEFT_DOUBLE_QUOTE}quoted${RIGHT_DOUBLE_QUOTE}${EM_DASH}word`],
|
|
128
|
+
])('handles quotes adjacent to em dashes: "%s"', (input, expected) => {
|
|
129
|
+
expect(niceQuotes(input)).toBe(expected)
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
it("handles single quote after em-dash", () => {
|
|
133
|
+
const input = `${EM_DASH}'Hi'${EM_DASH}`
|
|
134
|
+
const result = niceQuotes(input)
|
|
135
|
+
expect(result).toBe(`${EM_DASH}${LEFT_SINGLE_QUOTE}Hi${RIGHT_SINGLE_QUOTE}${EM_DASH}`)
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
// From Standard Ebooks: M'Donald-style names (archaic patterns)
|
|
139
|
+
it.each([
|
|
140
|
+
[`"M'Lord"`, `${LEFT_DOUBLE_QUOTE}M${RIGHT_SINGLE_QUOTE}Lord${RIGHT_DOUBLE_QUOTE}`],
|
|
141
|
+
[`O'Brien's idea`, `O${RIGHT_SINGLE_QUOTE}Brien${RIGHT_SINGLE_QUOTE}s idea`],
|
|
142
|
+
[`The O'Connors`, `The O${RIGHT_SINGLE_QUOTE}Connors`],
|
|
143
|
+
])('handles Irish/Scottish name apostrophes: "%s"', (input, expected) => {
|
|
144
|
+
expect(niceQuotes(input)).toBe(expected)
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
// From smartquotes.js: complex nested quotes
|
|
148
|
+
it.each([
|
|
149
|
+
['"Alfred \'bertrand\' cees"', `${LEFT_DOUBLE_QUOTE}Alfred ${LEFT_SINGLE_QUOTE}bertrand${RIGHT_SINGLE_QUOTE} cees${RIGHT_DOUBLE_QUOTE}`],
|
|
150
|
+
["'Alfred \"bertrand\" cees'", `${LEFT_SINGLE_QUOTE}Alfred ${LEFT_DOUBLE_QUOTE}bertrand${RIGHT_DOUBLE_QUOTE} cees${RIGHT_SINGLE_QUOTE}`],
|
|
151
|
+
])('handles complex nested quotes: "%s"', (input, expected) => {
|
|
152
|
+
expect(niceQuotes(input)).toBe(expected)
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
// From retext-smartypants: quotes with ellipsis
|
|
156
|
+
it.each([
|
|
157
|
+
[`"${ELLIPSIS}"`, `${LEFT_DOUBLE_QUOTE}${ELLIPSIS}${RIGHT_DOUBLE_QUOTE}`],
|
|
158
|
+
[`"Wait${ELLIPSIS}"`, `${LEFT_DOUBLE_QUOTE}Wait${ELLIPSIS}${RIGHT_DOUBLE_QUOTE}`],
|
|
159
|
+
[`'${ELLIPSIS}'`, `${LEFT_SINGLE_QUOTE}${ELLIPSIS}${RIGHT_SINGLE_QUOTE}`],
|
|
160
|
+
])('handles quotes with ellipsis: "%s"', (input, expected) => {
|
|
161
|
+
expect(niceQuotes(input)).toBe(expected)
|
|
162
|
+
})
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
describe("complex real-world patterns", () => {
|
|
166
|
+
it.each([
|
|
167
|
+
// Dialogue with attribution
|
|
168
|
+
['"I can\'t," she said.', `${LEFT_DOUBLE_QUOTE}I can${RIGHT_SINGLE_QUOTE}t,${RIGHT_DOUBLE_QUOTE} she said.`],
|
|
169
|
+
['"Why not?" he asked.', `${LEFT_DOUBLE_QUOTE}Why not?${RIGHT_DOUBLE_QUOTE} he asked.`],
|
|
170
|
+
// Multiple contractions
|
|
171
|
+
["I'm can't won't don't", `I${RIGHT_SINGLE_QUOTE}m can${RIGHT_SINGLE_QUOTE}t won${RIGHT_SINGLE_QUOTE}t don${RIGHT_SINGLE_QUOTE}t`],
|
|
172
|
+
// Quote within parentheses
|
|
173
|
+
['("test")', `(${LEFT_DOUBLE_QUOTE}test${RIGHT_DOUBLE_QUOTE})`],
|
|
174
|
+
["('test')", `(${LEFT_SINGLE_QUOTE}test${RIGHT_SINGLE_QUOTE})`],
|
|
175
|
+
// Quote with slash separator
|
|
176
|
+
['"option1"/"option2"', `${LEFT_DOUBLE_QUOTE}option1${RIGHT_DOUBLE_QUOTE}/${LEFT_DOUBLE_QUOTE}option2${RIGHT_DOUBLE_QUOTE}`],
|
|
177
|
+
])('handles complex pattern: "%s"', (input, expected) => {
|
|
178
|
+
expect(niceQuotes(input)).toBe(expected)
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
it("handles multi-line dialogue", () => {
|
|
182
|
+
const input = '"Hello,"\n"World"'
|
|
183
|
+
const expected = `${LEFT_DOUBLE_QUOTE}Hello,${RIGHT_DOUBLE_QUOTE}\n${LEFT_DOUBLE_QUOTE}World${RIGHT_DOUBLE_QUOTE}`
|
|
184
|
+
expect(niceQuotes(input)).toBe(expected)
|
|
185
|
+
})
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
describe("idempotency", () => {
|
|
189
|
+
it.each([
|
|
190
|
+
`${LEFT_DOUBLE_QUOTE}already curly${RIGHT_DOUBLE_QUOTE}`,
|
|
191
|
+
`${LEFT_SINGLE_QUOTE}single curly${RIGHT_SINGLE_QUOTE}`,
|
|
192
|
+
`I${RIGHT_SINGLE_QUOTE}m already converted`,
|
|
193
|
+
`${LEFT_DOUBLE_QUOTE}nested ${LEFT_SINGLE_QUOTE}quotes${RIGHT_SINGLE_QUOTE}${RIGHT_DOUBLE_QUOTE}`,
|
|
194
|
+
])('is idempotent for: "%s"', (input) => {
|
|
195
|
+
expect(niceQuotes(input)).toBe(input)
|
|
196
|
+
expect(niceQuotes(niceQuotes(input))).toBe(input)
|
|
197
|
+
})
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
describe("with separator character", () => {
|
|
201
|
+
const sep = "\uE000"
|
|
202
|
+
it.each([
|
|
203
|
+
[`"Hello${sep} world"`, `${LEFT_DOUBLE_QUOTE}Hello${sep} world${RIGHT_DOUBLE_QUOTE}`, "preserves separator positions"],
|
|
204
|
+
[`don${sep}'t`, `don${sep}${RIGHT_SINGLE_QUOTE}t`, "contractions across separator"],
|
|
205
|
+
[`"test${sep}"`, `${LEFT_DOUBLE_QUOTE}test${sep}${RIGHT_DOUBLE_QUOTE}`, "quotes at separator boundaries"],
|
|
206
|
+
])("%s → %s (%s)", (input, expected) => {
|
|
207
|
+
expect(niceQuotes(input, { separator: sep })).toBe(expected)
|
|
208
|
+
})
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
describe("unbalanced and edge quotes", () => {
|
|
212
|
+
it.each([
|
|
213
|
+
['"unclosed', `${LEFT_DOUBLE_QUOTE}unclosed`],
|
|
214
|
+
["unclosed'", `unclosed${RIGHT_SINGLE_QUOTE}`],
|
|
215
|
+
['""', `${LEFT_DOUBLE_QUOTE}${RIGHT_DOUBLE_QUOTE}`],
|
|
216
|
+
])('handles edge quote pattern: "%s"', (input, expected) => {
|
|
217
|
+
expect(niceQuotes(input)).toBe(expected)
|
|
218
|
+
})
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
describe("quotes after various punctuation", () => {
|
|
222
|
+
it.each([
|
|
223
|
+
['text; "quote"', `text; ${LEFT_DOUBLE_QUOTE}quote${RIGHT_DOUBLE_QUOTE}`],
|
|
224
|
+
['title: "quote"', `title: ${LEFT_DOUBLE_QUOTE}quote${RIGHT_DOUBLE_QUOTE}`],
|
|
225
|
+
['what? "quote"', `what? ${LEFT_DOUBLE_QUOTE}quote${RIGHT_DOUBLE_QUOTE}`],
|
|
226
|
+
['wow! "quote"', `wow! ${LEFT_DOUBLE_QUOTE}quote${RIGHT_DOUBLE_QUOTE}`],
|
|
227
|
+
['[note] "quote"', `[note] ${LEFT_DOUBLE_QUOTE}quote${RIGHT_DOUBLE_QUOTE}`],
|
|
228
|
+
['{code} "quote"', `{code} ${LEFT_DOUBLE_QUOTE}quote${RIGHT_DOUBLE_QUOTE}`],
|
|
229
|
+
])('handles quotes after punctuation: "%s"', (input, expected) => {
|
|
230
|
+
expect(niceQuotes(input)).toBe(expected)
|
|
231
|
+
})
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
describe("consecutive contractions", () => {
|
|
235
|
+
it.each([
|
|
236
|
+
["I'd've", `I${RIGHT_SINGLE_QUOTE}d${RIGHT_SINGLE_QUOTE}ve`],
|
|
237
|
+
["y'all'd've", `y${RIGHT_SINGLE_QUOTE}all${RIGHT_SINGLE_QUOTE}d${RIGHT_SINGLE_QUOTE}ve`],
|
|
238
|
+
["wouldn't've", `wouldn${RIGHT_SINGLE_QUOTE}t${RIGHT_SINGLE_QUOTE}ve`],
|
|
239
|
+
["couldn't've", `couldn${RIGHT_SINGLE_QUOTE}t${RIGHT_SINGLE_QUOTE}ve`],
|
|
240
|
+
])('handles double contraction: "%s"', (input, expected) => {
|
|
241
|
+
expect(niceQuotes(input)).toBe(expected)
|
|
242
|
+
})
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
describe("quotes with special Unicode", () => {
|
|
246
|
+
it.each([
|
|
247
|
+
['"Hello! 😊"', `${LEFT_DOUBLE_QUOTE}Hello! 😊${RIGHT_DOUBLE_QUOTE}`],
|
|
248
|
+
['"你好"', `${LEFT_DOUBLE_QUOTE}你好${RIGHT_DOUBLE_QUOTE}`],
|
|
249
|
+
['"こんにちは"', `${LEFT_DOUBLE_QUOTE}こんにちは${RIGHT_DOUBLE_QUOTE}`],
|
|
250
|
+
['"مرحبا"', `${LEFT_DOUBLE_QUOTE}مرحبا${RIGHT_DOUBLE_QUOTE}`],
|
|
251
|
+
['"Привет"', `${LEFT_DOUBLE_QUOTE}Привет${RIGHT_DOUBLE_QUOTE}`],
|
|
252
|
+
])('handles Unicode content in quotes: "%s"', (input, expected) => {
|
|
253
|
+
expect(niceQuotes(input)).toBe(expected)
|
|
254
|
+
})
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
describe("multiline quotes", () => {
|
|
258
|
+
it.each([
|
|
259
|
+
['"Hello\nWorld"', `${LEFT_DOUBLE_QUOTE}Hello\nWorld${RIGHT_DOUBLE_QUOTE}`],
|
|
260
|
+
['"A"\n"B"', `${LEFT_DOUBLE_QUOTE}A${RIGHT_DOUBLE_QUOTE}\n${LEFT_DOUBLE_QUOTE}B${RIGHT_DOUBLE_QUOTE}`],
|
|
261
|
+
])('handles multiline: "%s"', (input, expected) => {
|
|
262
|
+
expect(niceQuotes(input)).toBe(expected)
|
|
263
|
+
})
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
describe("split dialogue", () => {
|
|
267
|
+
it.each([
|
|
268
|
+
[
|
|
269
|
+
'"Yes," he said, "absolutely."',
|
|
270
|
+
`${LEFT_DOUBLE_QUOTE}Yes,${RIGHT_DOUBLE_QUOTE} he said, ${LEFT_DOUBLE_QUOTE}absolutely.${RIGHT_DOUBLE_QUOTE}`,
|
|
271
|
+
],
|
|
272
|
+
[
|
|
273
|
+
'"No," she replied, "I disagree."',
|
|
274
|
+
`${LEFT_DOUBLE_QUOTE}No,${RIGHT_DOUBLE_QUOTE} she replied, ${LEFT_DOUBLE_QUOTE}I disagree.${RIGHT_DOUBLE_QUOTE}`,
|
|
275
|
+
],
|
|
276
|
+
])('handles split dialogue: "%s"', (input, expected) => {
|
|
277
|
+
expect(niceQuotes(input)).toBe(expected)
|
|
278
|
+
})
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
describe("punctuationStyle option", () => {
|
|
282
|
+
// Reusable test strings
|
|
283
|
+
const periodOutsideDouble = `${LEFT_DOUBLE_QUOTE}Hello${RIGHT_DOUBLE_QUOTE}.`
|
|
284
|
+
const periodInsideDouble = `${LEFT_DOUBLE_QUOTE}Hello.${RIGHT_DOUBLE_QUOTE}`
|
|
285
|
+
const periodOutsideSingle = `${LEFT_SINGLE_QUOTE}Hello${RIGHT_SINGLE_QUOTE}.`
|
|
286
|
+
const periodInsideSingle = `${LEFT_SINGLE_QUOTE}Hello.${RIGHT_SINGLE_QUOTE}`
|
|
287
|
+
const commaOutsideDouble = `${LEFT_DOUBLE_QUOTE}test${RIGHT_DOUBLE_QUOTE},`
|
|
288
|
+
const commaInsideDouble = `${LEFT_DOUBLE_QUOTE}test,${RIGHT_DOUBLE_QUOTE}`
|
|
289
|
+
|
|
290
|
+
describe('when "american" (default)', () => {
|
|
291
|
+
it.each([
|
|
292
|
+
[periodOutsideDouble, periodInsideDouble, "period outside double quotes"],
|
|
293
|
+
[periodOutsideSingle, periodInsideSingle, "period outside single quotes"],
|
|
294
|
+
[commaOutsideDouble, commaInsideDouble, "comma outside double quotes"],
|
|
295
|
+
])("moves punctuation inside: %s", (input, expected) => {
|
|
296
|
+
expect(niceQuotes(input, { punctuationStyle: "american" })).toBe(expected)
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
it("is the default behavior", () => {
|
|
300
|
+
expect(niceQuotes(periodOutsideDouble)).toBe(periodInsideDouble)
|
|
301
|
+
})
|
|
302
|
+
})
|
|
303
|
+
|
|
304
|
+
describe('when "british"', () => {
|
|
305
|
+
it.each([
|
|
306
|
+
[periodInsideDouble, periodOutsideDouble, "period inside double quotes"],
|
|
307
|
+
[periodInsideSingle, periodOutsideSingle, "period inside single quotes"],
|
|
308
|
+
[commaInsideDouble, commaOutsideDouble, "comma inside double quotes"],
|
|
309
|
+
])("moves punctuation outside: %s", (input, expected) => {
|
|
310
|
+
expect(niceQuotes(input, { punctuationStyle: "british" })).toBe(expected)
|
|
311
|
+
})
|
|
312
|
+
|
|
313
|
+
it("still converts straight quotes to smart quotes", () => {
|
|
314
|
+
expect(niceQuotes('"Hello."', { punctuationStyle: "british" })).toBe(periodOutsideDouble)
|
|
315
|
+
})
|
|
316
|
+
})
|
|
317
|
+
|
|
318
|
+
describe('when "none"', () => {
|
|
319
|
+
it.each([
|
|
320
|
+
[periodOutsideDouble],
|
|
321
|
+
[periodInsideDouble],
|
|
322
|
+
[commaOutsideDouble],
|
|
323
|
+
])("leaves punctuation unchanged: %s", (input) => {
|
|
324
|
+
expect(niceQuotes(input, { punctuationStyle: "none" })).toBe(input)
|
|
325
|
+
})
|
|
326
|
+
|
|
327
|
+
it("still converts straight quotes to smart quotes", () => {
|
|
328
|
+
expect(niceQuotes('"Hello".', { punctuationStyle: "none" })).toBe(periodOutsideDouble)
|
|
329
|
+
})
|
|
330
|
+
})
|
|
331
|
+
})
|
|
332
|
+
|
|
333
|
+
describe("empty and whitespace quotes", () => {
|
|
334
|
+
it.each([
|
|
335
|
+
["''", `${LEFT_SINGLE_QUOTE}${RIGHT_SINGLE_QUOTE}`, "empty single"],
|
|
336
|
+
['""', `${LEFT_DOUBLE_QUOTE}${RIGHT_DOUBLE_QUOTE}`, "empty double"],
|
|
337
|
+
["' '", `${LEFT_SINGLE_QUOTE} ${RIGHT_SINGLE_QUOTE}`, "whitespace single"],
|
|
338
|
+
['" "', `${LEFT_DOUBLE_QUOTE} ${RIGHT_DOUBLE_QUOTE}`, "whitespace double"],
|
|
339
|
+
])("converts %s → %s (%s)", (input, expected) => {
|
|
340
|
+
expect(niceQuotes(input)).toBe(expected)
|
|
341
|
+
})
|
|
342
|
+
})
|
|
343
|
+
|
|
344
|
+
})
|