punctilio 2.3.0 → 3.0.1
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 +1 -2
- package/dist/dashes.js +4 -4
- package/dist/dashes.js.map +1 -1
- package/dist/index.d.ts +1 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -16
- package/dist/index.js.map +1 -1
- package/dist/quotes.d.ts +11 -11
- package/dist/quotes.d.ts.map +1 -1
- package/dist/quotes.js +21 -7
- package/dist/quotes.js.map +1 -1
- package/dist/rehype.d.ts.map +1 -1
- package/dist/rehype.js +9 -4
- package/dist/rehype.js.map +1 -1
- package/dist/symbols.d.ts.map +1 -1
- package/dist/symbols.js +17 -22
- package/dist/symbols.js.map +1 -1
- package/package.json +1 -1
- package/src/dashes.ts +4 -4
- package/src/index.ts +5 -28
- package/src/quotes.ts +22 -18
- package/src/rehype.ts +8 -4
- package/src/symbols.ts +28 -22
- package/src/tests/dashes.test.ts +11 -0
- package/src/tests/index.test.ts +32 -18
- package/src/tests/nbsp.test.ts +9 -9
- package/src/tests/quotes.test.ts +46 -49
- package/src/tests/rehype.test.ts +9 -8
- package/src/tests/remark.test.ts +1 -1
- package/src/tests/symbols.test.ts +16 -6
package/src/index.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* @packageDocumentation
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
export { niceQuotes, type QuoteOptions, type PunctuationStyle } from "./quotes.js"
|
|
11
|
+
export { niceQuotes, classifyApostrophes, type QuoteOptions, type PunctuationStyle } from "./quotes.js"
|
|
12
12
|
import type { PunctuationStyle } from "./quotes.js"
|
|
13
13
|
export {
|
|
14
14
|
hyphenReplace,
|
|
@@ -155,17 +155,6 @@ export interface TransformOptions {
|
|
|
155
155
|
*/
|
|
156
156
|
checkIdempotency?: boolean
|
|
157
157
|
|
|
158
|
-
/**
|
|
159
|
-
* Use modifier letter apostrophe (U+02BC) instead of right single quote
|
|
160
|
-
* (U+2019) for contractions and possessives. This distinguishes apostrophes
|
|
161
|
-
* from closing quotes in most cases, though some ambiguous patterns (e.g.,
|
|
162
|
-
* `dogs'` vs a closing single quote) cannot be resolved without semantic
|
|
163
|
-
* understanding. May also break downstream regex patterns that expect
|
|
164
|
-
* standard quote characters like /don\u2019t/ or /O\u2019Brien/.
|
|
165
|
-
*
|
|
166
|
-
* Default: false
|
|
167
|
-
*/
|
|
168
|
-
useModifierLetterApostrophe?: boolean
|
|
169
158
|
}
|
|
170
159
|
|
|
171
160
|
import { niceQuotes } from "./quotes.js"
|
|
@@ -224,7 +213,6 @@ const defaultOpts: Required<Omit<TransformOptions, "separator">> = {
|
|
|
224
213
|
checkIdempotency: true,
|
|
225
214
|
punctuationStyle: "american",
|
|
226
215
|
dashStyle: "american",
|
|
227
|
-
useModifierLetterApostrophe: false,
|
|
228
216
|
}
|
|
229
217
|
|
|
230
218
|
export function transform(text: string, options: TransformOptions = {}): string {
|
|
@@ -241,29 +229,24 @@ export function transform(text: string, options: TransformOptions = {}): string
|
|
|
241
229
|
}
|
|
242
230
|
|
|
243
231
|
const original = text
|
|
244
|
-
const { symbols, fractions, degrees, superscript, ligatures, nbsp, collapseSpaces, checkIdempotency,
|
|
232
|
+
const { symbols, fractions, degrees, superscript, ligatures, nbsp, collapseSpaces, checkIdempotency, ...separatorOpts } = { ...defaultOpts, ...options }
|
|
245
233
|
|
|
246
234
|
text = hyphenReplace(text, separatorOpts)
|
|
247
235
|
if (separatorOpts.punctuationStyle !== "none") {
|
|
248
236
|
text = primeMarks(text, separatorOpts)
|
|
249
237
|
}
|
|
250
|
-
|
|
251
|
-
// because NBSP's PUNCTUATION_OR_QUOTE class includes RSQ but not MLA.
|
|
252
|
-
// Without this, apostrophes in "don't" would act as word boundaries
|
|
253
|
-
// during NBSP insertion. The final MLA→RSQ conversion at line 286
|
|
254
|
-
// runs after all transforms complete.
|
|
255
|
-
text = niceQuotes(text, { ...separatorOpts, useModifierLetterApostrophe: true })
|
|
238
|
+
text = niceQuotes(text, separatorOpts)
|
|
256
239
|
|
|
257
240
|
if (symbols) {
|
|
258
241
|
text = symbolTransform(text, separatorOpts)
|
|
259
242
|
}
|
|
260
243
|
|
|
261
244
|
if (fractions) {
|
|
262
|
-
text = fractionsTransform(text)
|
|
245
|
+
text = fractionsTransform(text, separatorOpts)
|
|
263
246
|
}
|
|
264
247
|
|
|
265
248
|
if (degrees) {
|
|
266
|
-
text = degreesTransform(text)
|
|
249
|
+
text = degreesTransform(text, separatorOpts)
|
|
267
250
|
}
|
|
268
251
|
|
|
269
252
|
if (superscript) {
|
|
@@ -284,12 +267,6 @@ export function transform(text: string, options: TransformOptions = {}): string
|
|
|
284
267
|
|
|
285
268
|
assertSeparatorCountPreserved(original, text, separator, "transform")
|
|
286
269
|
|
|
287
|
-
// Convert MLA→RSQ for regex-friendly output, after all transforms
|
|
288
|
-
// (especially NBSP which relies on MLA being letter-like).
|
|
289
|
-
if (!useModifierLetterApostrophe) {
|
|
290
|
-
text = text.replaceAll(UNICODE_SYMBOLS.MODIFIER_LETTER_APOSTROPHE, UNICODE_SYMBOLS.RIGHT_SINGLE_QUOTE)
|
|
291
|
-
}
|
|
292
|
-
|
|
293
270
|
if (checkIdempotency) {
|
|
294
271
|
const secondPass = transform(text, { ...options, checkIdempotency: false })
|
|
295
272
|
/* istanbul ignore if -- defensive check that should never trigger */
|
package/src/quotes.ts
CHANGED
|
@@ -22,17 +22,6 @@ export interface QuoteOptions {
|
|
|
22
22
|
separator?: string
|
|
23
23
|
/** "american" (inside), "british" (outside), "none". Default: "american" */
|
|
24
24
|
punctuationStyle?: PunctuationStyle
|
|
25
|
-
/**
|
|
26
|
-
* Use modifier letter apostrophe (U+02BC) instead of right single quote
|
|
27
|
-
* (U+2019) for contractions and possessives. This distinguishes apostrophes
|
|
28
|
-
* from closing quotes in most cases, though some ambiguous patterns (e.g.,
|
|
29
|
-
* `dogs'` vs a closing single quote) cannot be resolved without semantic
|
|
30
|
-
* understanding. May also break downstream regex patterns that expect
|
|
31
|
-
* standard quote characters like /don\u2019t/ or /O\u2019Brien/.
|
|
32
|
-
*
|
|
33
|
-
* Default: false
|
|
34
|
-
*/
|
|
35
|
-
useModifierLetterApostrophe?: boolean
|
|
36
25
|
}
|
|
37
26
|
|
|
38
27
|
/** Convert straight single quotes to curly quotes and apostrophes */
|
|
@@ -177,8 +166,8 @@ function applyPunctuationStyle(text: string, sep: string, style: PunctuationStyl
|
|
|
177
166
|
return text
|
|
178
167
|
}
|
|
179
168
|
|
|
180
|
-
/**
|
|
181
|
-
|
|
169
|
+
/** Shared quote-processing pipeline. Returns text with MLA for apostrophes. */
|
|
170
|
+
function processQuotes(text: string, options: QuoteOptions): string {
|
|
182
171
|
const sep = options.separator ?? DEFAULT_SEPARATOR
|
|
183
172
|
const punctuationStyle = options.punctuationStyle ?? "american"
|
|
184
173
|
if (punctuationStyle === "none") return text
|
|
@@ -187,12 +176,27 @@ export function niceQuotes(text: string, options: QuoteOptions = {}): string {
|
|
|
187
176
|
text = convertDoubleQuotes(text, sep)
|
|
188
177
|
text = applyPunctuationStyle(text, sep, punctuationStyle)
|
|
189
178
|
|
|
179
|
+
return text
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** Convert straight quotes to smart quotes. */
|
|
183
|
+
export function niceQuotes(text: string, options: QuoteOptions = {}): string {
|
|
190
184
|
// MLA is used internally so applyPunctuationStyle can distinguish
|
|
191
185
|
// apostrophes (MLA, don't move) from closing quotes (RSQ, do move).
|
|
192
|
-
//
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
}
|
|
186
|
+
// Always convert back to RSQ for standard output per Unicode.
|
|
187
|
+
return processQuotes(text, options).replaceAll(MODIFIER_LETTER_APOSTROPHE, RIGHT_SINGLE_QUOTE)
|
|
188
|
+
}
|
|
196
189
|
|
|
197
|
-
|
|
190
|
+
/**
|
|
191
|
+
* Classify apostrophes vs. closing single quotes.
|
|
192
|
+
*
|
|
193
|
+
* Returns the text with smart quotes applied, where apostrophes are
|
|
194
|
+
* U+02BC (MODIFIER LETTER APOSTROPHE) and closing single quotes are
|
|
195
|
+
* U+2019 (RIGHT SINGLE QUOTATION MARK).
|
|
196
|
+
*
|
|
197
|
+
* For display output, use {@link niceQuotes} or `transform()` instead,
|
|
198
|
+
* which use U+2019 for both per the Unicode Standard.
|
|
199
|
+
*/
|
|
200
|
+
export function classifyApostrophes(text: string, options: QuoteOptions = {}): string {
|
|
201
|
+
return processQuotes(text, options)
|
|
198
202
|
}
|
package/src/rehype.ts
CHANGED
|
@@ -183,15 +183,19 @@ export function getFirstTextNode(
|
|
|
183
183
|
export function assertSmartQuotesMatch(input: string): void {
|
|
184
184
|
if (!input) return
|
|
185
185
|
|
|
186
|
-
const
|
|
186
|
+
const openers = new Set(["\u201C"]) // left double quote opens
|
|
187
|
+
const closerToOpener: Record<string, string> = { "\u201D": "\u201C" } // right closes left
|
|
187
188
|
const stack: string[] = []
|
|
188
189
|
|
|
189
190
|
for (const char of input) {
|
|
190
|
-
if (char
|
|
191
|
-
|
|
191
|
+
if (openers.has(char)) {
|
|
192
|
+
stack.push(char)
|
|
193
|
+
} else if (char in closerToOpener) {
|
|
194
|
+
if (stack.length > 0 && stack[stack.length - 1] === closerToOpener[char]) {
|
|
192
195
|
stack.pop()
|
|
193
196
|
} else {
|
|
194
|
-
|
|
197
|
+
// Unmatched closer
|
|
198
|
+
throw new Error(`Mismatched quotes in ${input}`)
|
|
195
199
|
}
|
|
196
200
|
}
|
|
197
201
|
}
|
package/src/symbols.ts
CHANGED
|
@@ -116,32 +116,38 @@ export function mathSymbols(text: string): string {
|
|
|
116
116
|
return text
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
-
/**
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
119
|
+
/**
|
|
120
|
+
* Context-aware replacement for legal symbols like (c), (r), (tm).
|
|
121
|
+
* Extracts surrounding text and delegates the convert/skip decision to a predicate.
|
|
122
|
+
*/
|
|
123
|
+
function contextAwareLegalReplace(
|
|
124
|
+
text: string,
|
|
125
|
+
pattern: RegExp,
|
|
126
|
+
replacement: string,
|
|
127
|
+
shouldConvert: (before: string, after: string) => boolean,
|
|
128
|
+
): string {
|
|
129
|
+
return text.replace(pattern, (match: string, offset: number, str: string) => {
|
|
130
|
+
const before = str.slice(Math.max(0, offset - 25), offset)
|
|
131
|
+
const after = str.slice(offset + match.length, offset + match.length + 25)
|
|
132
|
+
return shouldConvert(before, after) ? replacement : match
|
|
133
|
+
})
|
|
134
|
+
}
|
|
124
135
|
|
|
125
136
|
/** Convert (c), (r), (tm) to ©, ®, ™. */
|
|
126
137
|
export function legalSymbols(text: string): string {
|
|
127
|
-
// (c)
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
return match
|
|
140
|
-
})
|
|
138
|
+
// (c) → © only with positive copyright evidence (year or "copyright" keyword)
|
|
139
|
+
text = contextAwareLegalReplace(text, /\(c\)/gi, COPYRIGHT, (before, after) =>
|
|
140
|
+
/^\s*(?:19|20)\d{2}\b/.test(after) || /\bcopyright\s*$/i.test(before)
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
// (r) → ® unless in enumeration "(q), (r)" or legal citation "(r)(1)" context
|
|
144
|
+
text = contextAwareLegalReplace(text, /\(r\)/gi, REGISTERED, (before, after) =>
|
|
145
|
+
!/\([a-z]\)[,;]\s*$/i.test(before) && !/^\(\d/.test(after)
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
// (tm) → ™ unconditionally
|
|
149
|
+
text = text.replace(/\(tm\)/gi, TRADEMARK)
|
|
141
150
|
|
|
142
|
-
for (const [pattern, replacement] of LEGAL_SYMBOL_MAP) {
|
|
143
|
-
text = text.replace(pattern, replacement)
|
|
144
|
-
}
|
|
145
151
|
return text
|
|
146
152
|
}
|
|
147
153
|
|
package/src/tests/dashes.test.ts
CHANGED
|
@@ -124,6 +124,17 @@ describe("hyphenReplace", () => {
|
|
|
124
124
|
})
|
|
125
125
|
})
|
|
126
126
|
|
|
127
|
+
describe("tilde preservation", () => {
|
|
128
|
+
it.each([
|
|
129
|
+
["word ~ word", "word ~ word"],
|
|
130
|
+
["word ~~ word", "word ~~ word"],
|
|
131
|
+
["the ~5% range", "the ~5% range"],
|
|
132
|
+
["~approximately", "~approximately"],
|
|
133
|
+
])('preserves tilde in "%s"', (input) => {
|
|
134
|
+
expect(hyphenReplace(input)).toBe(input)
|
|
135
|
+
})
|
|
136
|
+
})
|
|
137
|
+
|
|
127
138
|
describe("with separator character", () => {
|
|
128
139
|
const sep = "\uE000"
|
|
129
140
|
it.each([
|
package/src/tests/index.test.ts
CHANGED
|
@@ -11,6 +11,9 @@ const {
|
|
|
11
11
|
ELLIPSIS,
|
|
12
12
|
MULTIPLICATION,
|
|
13
13
|
NOT_EQUAL,
|
|
14
|
+
PLUS_MINUS,
|
|
15
|
+
LESS_EQUAL,
|
|
16
|
+
GREATER_EQUAL,
|
|
14
17
|
COPYRIGHT,
|
|
15
18
|
NBSP,
|
|
16
19
|
PRIME,
|
|
@@ -18,6 +21,7 @@ const {
|
|
|
18
21
|
TRADEMARK,
|
|
19
22
|
REGISTERED,
|
|
20
23
|
DEGREE,
|
|
24
|
+
MINUS,
|
|
21
25
|
FRACTION_1_2,
|
|
22
26
|
SUPERSCRIPT_ST,
|
|
23
27
|
} = UNICODE_SYMBOLS
|
|
@@ -183,11 +187,11 @@ describe("transform", () => {
|
|
|
183
187
|
// Date ranges - all competitors fail
|
|
184
188
|
["January-March", `January${EN_DASH}March`],
|
|
185
189
|
// Minus signs - most competitors fail
|
|
186
|
-
["-5",
|
|
187
|
-
["(-5)", `(
|
|
190
|
+
["-5", `${MINUS}5`],
|
|
191
|
+
["(-5)", `(${MINUS}5)`],
|
|
188
192
|
// Prime marks - smartypants/typograf fail
|
|
189
|
-
['5\'10"', `5
|
|
190
|
-
['He is 6\'2" tall', `He is 6
|
|
193
|
+
['5\'10"', `5${PRIME}10${DOUBLE_PRIME}`],
|
|
194
|
+
['He is 6\'2" tall', `He is 6${PRIME}2${DOUBLE_PRIME} tall`],
|
|
191
195
|
])('correctly transforms: "%s"', (input, expected) => {
|
|
192
196
|
expect(transform(input, { symbols: true, degrees: true, nbsp: false })).toBe(expected)
|
|
193
197
|
})
|
|
@@ -229,7 +233,7 @@ describe("transform", () => {
|
|
|
229
233
|
|
|
230
234
|
it("handles technical documentation", () => {
|
|
231
235
|
const input = 'The API returns x != y when a <= b and c >= d. Error tolerance: +-5%.'
|
|
232
|
-
const expected = `The API returns x ${NOT_EQUAL} y when a
|
|
236
|
+
const expected = `The API returns x ${NOT_EQUAL} y when a ${LESS_EQUAL} b and c ${GREATER_EQUAL} d. Error tolerance: ${PLUS_MINUS}5%.`
|
|
233
237
|
expect(transform(input, { nbsp: false })).toBe(expected)
|
|
234
238
|
})
|
|
235
239
|
|
|
@@ -263,11 +267,11 @@ describe("transform", () => {
|
|
|
263
267
|
it.each([
|
|
264
268
|
[
|
|
265
269
|
'"Wait..." she said - "it\'s pages 1-5."',
|
|
266
|
-
`${LEFT_DOUBLE_QUOTE}Wait
|
|
270
|
+
`${LEFT_DOUBLE_QUOTE}Wait${ELLIPSIS}${RIGHT_DOUBLE_QUOTE} she said${EM_DASH}${LEFT_DOUBLE_QUOTE}it${RIGHT_SINGLE_QUOTE}s pages 1${EN_DASH}5.${RIGHT_DOUBLE_QUOTE}`,
|
|
267
271
|
],
|
|
268
272
|
[
|
|
269
273
|
"(c) 2024 - Room is 10x12, set to 72 F",
|
|
270
|
-
`${COPYRIGHT} 2024${EM_DASH}Room is 10
|
|
274
|
+
`${COPYRIGHT} 2024${EM_DASH}Room is 10${MULTIPLICATION}12, set to 72 ${DEGREE}F`,
|
|
271
275
|
],
|
|
272
276
|
])('handles complex transform: "%s"', (input, expected) => {
|
|
273
277
|
expect(transform(input, { degrees: true, nbsp: false })).toBe(expected)
|
|
@@ -313,7 +317,7 @@ describe("transform", () => {
|
|
|
313
317
|
const count = 50
|
|
314
318
|
const input = "pages 1-5 ".repeat(count)
|
|
315
319
|
const result = transform(input, { nbsp: false })
|
|
316
|
-
const enDashCount = (result.match(
|
|
320
|
+
const enDashCount = (result.match(new RegExp(EN_DASH, "g")) || []).length
|
|
317
321
|
expect(enDashCount).toBe(count)
|
|
318
322
|
})
|
|
319
323
|
|
|
@@ -321,7 +325,7 @@ describe("transform", () => {
|
|
|
321
325
|
const count = 50
|
|
322
326
|
const input = "Wait... ".repeat(count)
|
|
323
327
|
const result = transform(input, { nbsp: false })
|
|
324
|
-
const ellipsisCount = (result.match(
|
|
328
|
+
const ellipsisCount = (result.match(new RegExp(ELLIPSIS, "g")) || []).length
|
|
325
329
|
expect(ellipsisCount).toBe(count)
|
|
326
330
|
})
|
|
327
331
|
})
|
|
@@ -373,7 +377,7 @@ describe("transform", () => {
|
|
|
373
377
|
|
|
374
378
|
it.each([
|
|
375
379
|
[`word${sep} - ${sep}word`, `word${sep}${EM_DASH}${sep}word`],
|
|
376
|
-
[`1${sep}-${sep}5`, `1${sep}
|
|
380
|
+
[`1${sep}-${sep}5`, `1${sep}${EN_DASH}${sep}5`],
|
|
377
381
|
])('handles separator in dashes', (input, expected) => {
|
|
378
382
|
expect(transform(input, { separator: sep, nbsp: false })).toBe(expected)
|
|
379
383
|
})
|
|
@@ -390,6 +394,16 @@ describe("transform", () => {
|
|
|
390
394
|
const input = `Text with ${sep} in it`
|
|
391
395
|
expect(() => transform(input, { separator: sep, nbsp: false })).not.toThrow()
|
|
392
396
|
})
|
|
397
|
+
|
|
398
|
+
it("handles separator in fractions", () => {
|
|
399
|
+
const input = `1${sep}/2`
|
|
400
|
+
expect(transform(input, { separator: sep, fractions: true, nbsp: false })).toBe(`${sep}${FRACTION_1_2}`)
|
|
401
|
+
})
|
|
402
|
+
|
|
403
|
+
it("handles separator in degrees", () => {
|
|
404
|
+
const input = `72${sep} F`
|
|
405
|
+
expect(transform(input, { separator: sep, degrees: true, nbsp: false })).toBe(`72${sep} ${DEGREE}F`)
|
|
406
|
+
})
|
|
393
407
|
})
|
|
394
408
|
|
|
395
409
|
describe("additional idempotency", () => {
|
|
@@ -407,10 +421,10 @@ describe("transform", () => {
|
|
|
407
421
|
it.each([
|
|
408
422
|
`${LEFT_DOUBLE_QUOTE}Hello${RIGHT_DOUBLE_QUOTE}`,
|
|
409
423
|
`word${EM_DASH}word`,
|
|
410
|
-
`1
|
|
411
|
-
`Wait
|
|
412
|
-
`5
|
|
413
|
-
`20${NBSP}
|
|
424
|
+
`1${EN_DASH}5`,
|
|
425
|
+
`Wait${ELLIPSIS}`,
|
|
426
|
+
`5${MULTIPLICATION}5`,
|
|
427
|
+
`20${NBSP}${DEGREE}C`,
|
|
414
428
|
`${COPYRIGHT}${NBSP}2024`,
|
|
415
429
|
])('stable for: "%s"', (input) => {
|
|
416
430
|
expect(transform(input)).toBe(input)
|
|
@@ -506,12 +520,12 @@ describe("transform", () => {
|
|
|
506
520
|
it.each([
|
|
507
521
|
['"start', `${LEFT_DOUBLE_QUOTE}start`],
|
|
508
522
|
["'start", `${RIGHT_SINGLE_QUOTE}start`],
|
|
509
|
-
["-5 start",
|
|
510
|
-
["...start",
|
|
523
|
+
["-5 start", `${MINUS}5 start`],
|
|
524
|
+
["...start", `${ELLIPSIS} start`],
|
|
511
525
|
['end"', `end${RIGHT_DOUBLE_QUOTE}`],
|
|
512
526
|
["end'", `end${RIGHT_SINGLE_QUOTE}`],
|
|
513
|
-
["end...", `end
|
|
514
|
-
["5x", `5
|
|
527
|
+
["end...", `end${ELLIPSIS}`],
|
|
528
|
+
["5x", `5${MULTIPLICATION}`],
|
|
515
529
|
])('handles boundary: "%s"', (input, expected) => {
|
|
516
530
|
expect(transform(input, { nbsp: false })).toBe(expected)
|
|
517
531
|
})
|
package/src/tests/nbsp.test.ts
CHANGED
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
} from "../nbsp.js"
|
|
16
16
|
import { UNICODE_SYMBOLS, DEFAULT_SEPARATOR } from "../constants.js"
|
|
17
17
|
|
|
18
|
-
const NBSP = UNICODE_SYMBOLS
|
|
18
|
+
const { NBSP, COPYRIGHT, REGISTERED, TRADEMARK } = UNICODE_SYMBOLS
|
|
19
19
|
const SEP = DEFAULT_SEPARATOR
|
|
20
20
|
|
|
21
21
|
/** Calls fn with separator option, checking all cases produce correct nbsp. */
|
|
@@ -183,18 +183,18 @@ describe("nbspAfterHonorifics", () => {
|
|
|
183
183
|
|
|
184
184
|
describe("nbspAfterCopyrightSymbols", () => {
|
|
185
185
|
it.each([
|
|
186
|
-
[
|
|
187
|
-
[
|
|
188
|
-
[
|
|
189
|
-
[
|
|
190
|
-
[
|
|
186
|
+
[`${COPYRIGHT} 2024`, `${COPYRIGHT}${NBSP}2024`],
|
|
187
|
+
[`${REGISTERED} Brand`, `${REGISTERED}${NBSP}Brand`],
|
|
188
|
+
[`${TRADEMARK} Product`, `${TRADEMARK}${NBSP}Product`],
|
|
189
|
+
[`${COPYRIGHT} 2024 Company`, `${COPYRIGHT}${NBSP}2024 Company`],
|
|
190
|
+
[`${COPYRIGHT} company`, `${COPYRIGHT} company`],
|
|
191
191
|
])('"%s" → "%s"', (input, expected) => {
|
|
192
192
|
expect(nbspAfterCopyrightSymbols(input)).toBe(expected)
|
|
193
193
|
})
|
|
194
194
|
|
|
195
195
|
it("preserves separator after symbol", () => {
|
|
196
196
|
expectSep(nbspAfterCopyrightSymbols, [
|
|
197
|
-
[
|
|
197
|
+
[`${COPYRIGHT}${SEP} 2024`, `${COPYRIGHT}${SEP}${NBSP}2024`],
|
|
198
198
|
])
|
|
199
199
|
})
|
|
200
200
|
})
|
|
@@ -224,8 +224,8 @@ describe("nbspTransform", () => {
|
|
|
224
224
|
})
|
|
225
225
|
|
|
226
226
|
it("handles multiple rules on the same text", () => {
|
|
227
|
-
expect(nbspTransform(
|
|
228
|
-
.toBe(
|
|
227
|
+
expect(nbspTransform(`${COPYRIGHT} 2024 by J. K. Rowling`))
|
|
228
|
+
.toBe(`${COPYRIGHT}${NBSP}2024 by${NBSP}J.${NBSP}K.${NBSP}Rowling`)
|
|
229
229
|
})
|
|
230
230
|
|
|
231
231
|
it("nbspBeforeLastWord fires on plain text", () => {
|