punctilio 3.7.3 → 3.7.5
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 +28 -28
- package/dist/dashes.d.ts.map +1 -1
- package/dist/dashes.js +2 -1
- package/dist/dashes.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/rehype.d.ts.map +1 -1
- package/dist/rehype.js +5 -20
- package/dist/rehype.js.map +1 -1
- package/dist/symbols.d.ts +1 -1
- package/dist/symbols.d.ts.map +1 -1
- package/dist/symbols.js +13 -6
- package/dist/symbols.js.map +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +2 -0
- package/dist/utils.js.map +1 -1
- package/package.json +5 -4
- package/src/dashes.ts +2 -1
- package/src/index.ts +3 -1
- package/src/rehype.ts +5 -24
- package/src/symbols.ts +12 -6
- package/src/utils.ts +1 -0
- package/tsconfig.json +1 -1
- package/dist/tests/test-helpers.d.ts +0 -33
- package/dist/tests/test-helpers.d.ts.map +0 -1
- package/dist/tests/test-helpers.js +0 -109
- package/dist/tests/test-helpers.js.map +0 -1
- package/src/tests/dashes.test.ts +0 -736
- package/src/tests/index.test.ts +0 -713
- package/src/tests/markdown.test.ts +0 -156
- package/src/tests/nbsp.test.ts +0 -272
- package/src/tests/quotes.test.ts +0 -783
- package/src/tests/rehype.test.ts +0 -701
- package/src/tests/remark.test.ts +0 -200
- package/src/tests/symbols.test.ts +0 -920
- package/src/tests/test-helpers.ts +0 -124
- package/src/tests/utils.test.ts +0 -151
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
import { readFileSync } from "fs"
|
|
2
|
-
import { resolve } from "path"
|
|
3
|
-
import { fileURLToPath } from "url"
|
|
4
|
-
import { transformMarkdown, clearProcessorCache } from "../markdown.js"
|
|
5
|
-
import { transform } from "../index.js"
|
|
6
|
-
import { UNICODE_SYMBOLS } from "../constants.js"
|
|
7
|
-
|
|
8
|
-
const __dirname = fileURLToPath(new URL(".", import.meta.url))
|
|
9
|
-
|
|
10
|
-
const {
|
|
11
|
-
LEFT_DOUBLE_QUOTE: LDQ,
|
|
12
|
-
RIGHT_DOUBLE_QUOTE: RDQ,
|
|
13
|
-
RIGHT_SINGLE_QUOTE: RSQ,
|
|
14
|
-
EM_DASH,
|
|
15
|
-
ELLIPSIS,
|
|
16
|
-
} = UNICODE_SYMBOLS
|
|
17
|
-
|
|
18
|
-
describe("transformMarkdown", () => {
|
|
19
|
-
afterEach(() => {
|
|
20
|
-
clearProcessorCache()
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
it("transforms basic typography", async () => {
|
|
24
|
-
const result = await transformMarkdown('"Hello," she said.', { nbsp: false })
|
|
25
|
-
expect(result.trimEnd()).toEqual(`${LDQ}Hello,${RDQ} she said.`)
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
it("preserves code blocks", async () => {
|
|
29
|
-
const input = '```\n"untouched"\n```'
|
|
30
|
-
const result = await transformMarkdown(input, { nbsp: false })
|
|
31
|
-
expect(result.trimEnd()).toEqual(input)
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
it("handles complex documents", async () => {
|
|
35
|
-
const input = [
|
|
36
|
-
'# "Welcome"',
|
|
37
|
-
"",
|
|
38
|
-
"It's great -- isn't it?",
|
|
39
|
-
"",
|
|
40
|
-
"Wait...",
|
|
41
|
-
].join("\n")
|
|
42
|
-
|
|
43
|
-
const expected = [
|
|
44
|
-
`# ${LDQ}Welcome${RDQ}`,
|
|
45
|
-
"",
|
|
46
|
-
`It${RSQ}s great${EM_DASH}isn${RSQ}t it?`,
|
|
47
|
-
"",
|
|
48
|
-
`Wait${ELLIPSIS}`,
|
|
49
|
-
"", // remark-stringify adds trailing newline
|
|
50
|
-
].join("\n")
|
|
51
|
-
|
|
52
|
-
expect(await transformMarkdown(input, { nbsp: false })).toEqual(expected)
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
it("passes through punctuationStyle option", async () => {
|
|
56
|
-
const american = await transformMarkdown('"Hello."', {
|
|
57
|
-
punctuationStyle: "american",
|
|
58
|
-
nbsp: false,
|
|
59
|
-
})
|
|
60
|
-
const british = await transformMarkdown('"Hello."', {
|
|
61
|
-
punctuationStyle: "british",
|
|
62
|
-
nbsp: false,
|
|
63
|
-
})
|
|
64
|
-
expect(american.trimEnd()).toEqual(`${LDQ}Hello.${RDQ}`)
|
|
65
|
-
expect(british.trimEnd()).toEqual(`${LDQ}Hello${RDQ}.`)
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
it.each([
|
|
69
|
-
["bulletMarker", "- item", { bulletMarker: "+" as const }, "+ item"],
|
|
70
|
-
["emphasisMarker", "*italic*", { emphasisMarker: "_" as const }, "_italic_"],
|
|
71
|
-
["strongMarker", "**bold**", { strongMarker: "_" as const }, "__bold__"],
|
|
72
|
-
["ruleMarker", "---", { ruleMarker: "*" as const }, "***"],
|
|
73
|
-
])("passes through %s option", async (_name, input, options, expected) => {
|
|
74
|
-
expect((await transformMarkdown(input, { ...options, nbsp: false })).trimEnd()).toEqual(expected)
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
it("uses 4-dash thematic break with default ruleMarker to avoid YAML front matter ambiguity", async () => {
|
|
78
|
-
const result = await transformMarkdown("---", { nbsp: false })
|
|
79
|
-
expect(result.trimEnd()).toEqual("----")
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
it("uses default options when none provided", async () => {
|
|
83
|
-
const result = await transformMarkdown('"Hello"')
|
|
84
|
-
expect(result.trimEnd()).toEqual(`${LDQ}Hello${RDQ}`)
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
it("escapes both opening and closing brackets in phrasing content", async () => {
|
|
88
|
-
const result = await transformMarkdown("See references [1][4][5][6] for details.", {
|
|
89
|
-
nbsp: false,
|
|
90
|
-
})
|
|
91
|
-
expect(result.trimEnd()).toEqual("See references \\[1\\]\\[4\\]\\[5\\]\\[6\\] for details.")
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
it.each([
|
|
95
|
-
["plain separators", '| Flag | Default |\n| --- | --- |\n| "a" | b |'],
|
|
96
|
-
["alignment markers", '| Left | Center | Right |\n| :--- | :---: | ---: |\n| "a" | b | c |'],
|
|
97
|
-
["long separators", '| Col |\n| -------------------- |\n| "data" |'],
|
|
98
|
-
])("preserves GFM table separators (%s)", async (_name, input) => {
|
|
99
|
-
const result = await transformMarkdown(input, { nbsp: false })
|
|
100
|
-
expect(result).not.toContain(EM_DASH)
|
|
101
|
-
expect(result).toContain(LDQ)
|
|
102
|
-
})
|
|
103
|
-
|
|
104
|
-
it("reuses cached processor for identical options", async () => {
|
|
105
|
-
const opts = { punctuationStyle: "british" as const, nbsp: false }
|
|
106
|
-
const first = await transformMarkdown('"Hello."', opts)
|
|
107
|
-
const second = await transformMarkdown('"World."', opts)
|
|
108
|
-
expect(first.trimEnd()).toEqual(`${LDQ}Hello${RDQ}.`)
|
|
109
|
-
expect(second.trimEnd()).toEqual(`${LDQ}World${RDQ}.`)
|
|
110
|
-
})
|
|
111
|
-
|
|
112
|
-
it("invalidates cache when options change", async () => {
|
|
113
|
-
const american = await transformMarkdown('"Hello."', { punctuationStyle: "american", nbsp: false })
|
|
114
|
-
const british = await transformMarkdown('"Hello."', { punctuationStyle: "british", nbsp: false })
|
|
115
|
-
expect(american.trimEnd()).toEqual(`${LDQ}Hello.${RDQ}`)
|
|
116
|
-
expect(british.trimEnd()).toEqual(`${LDQ}Hello${RDQ}.`)
|
|
117
|
-
})
|
|
118
|
-
|
|
119
|
-
it("LRU cache retains multiple option sets simultaneously", async () => {
|
|
120
|
-
const american = { punctuationStyle: "american" as const, nbsp: false }
|
|
121
|
-
const british = { punctuationStyle: "british" as const, nbsp: false }
|
|
122
|
-
// Populate both cache entries
|
|
123
|
-
await transformMarkdown('"A."', american)
|
|
124
|
-
await transformMarkdown('"B."', british)
|
|
125
|
-
// Both should still be cached (not evicted)
|
|
126
|
-
const a2 = await transformMarkdown('"C."', american)
|
|
127
|
-
const b2 = await transformMarkdown('"D."', british)
|
|
128
|
-
expect(a2.trimEnd()).toEqual(`${LDQ}C.${RDQ}`)
|
|
129
|
-
expect(b2.trimEnd()).toEqual(`${LDQ}D${RDQ}.`)
|
|
130
|
-
})
|
|
131
|
-
|
|
132
|
-
it("treats undefined options the same as absent options", async () => {
|
|
133
|
-
const withDefault = await transformMarkdown("Dr. Smith")
|
|
134
|
-
const withUndefined = await transformMarkdown("Dr. Smith", { nbsp: undefined })
|
|
135
|
-
expect(withDefault).toContain("\u00A0")
|
|
136
|
-
expect(withUndefined).toContain("\u00A0")
|
|
137
|
-
})
|
|
138
|
-
|
|
139
|
-
it("README.md prose is already typographically correct", () => {
|
|
140
|
-
const readme = readFileSync(resolve(__dirname, "../../README.md"), "utf-8")
|
|
141
|
-
const lines = readme.split("\n")
|
|
142
|
-
let inCodeBlock = false
|
|
143
|
-
|
|
144
|
-
for (const line of lines) {
|
|
145
|
-
if (line.startsWith("```")) { inCodeBlock = !inCodeBlock; continue }
|
|
146
|
-
if (inCodeBlock) continue
|
|
147
|
-
// Skip non-prose: tables, badges, HTML, footnotes, headings, list items (including indented)
|
|
148
|
-
if (/^[|#>\d]|^\s*-\s|^\s*\d+\.\s|^\[[\^!]|<span/.test(line)) continue
|
|
149
|
-
if (line.trim() === "") continue
|
|
150
|
-
|
|
151
|
-
// collapseSpaces: false because Markdown uses double-space for formatting
|
|
152
|
-
const transformed = transform(line, { nbsp: false, collapseSpaces: false, checkIdempotency: false })
|
|
153
|
-
expect(transformed).toBe(line)
|
|
154
|
-
}
|
|
155
|
-
})
|
|
156
|
-
})
|
package/src/tests/nbsp.test.ts
DELETED
|
@@ -1,272 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
nbspAfterShortWords,
|
|
3
|
-
nbspBetweenNumberAndUnit,
|
|
4
|
-
nbspBeforeLastWord,
|
|
5
|
-
nbspAfterReferenceAbbreviations,
|
|
6
|
-
nbspAfterSectionSymbols,
|
|
7
|
-
nbspAfterHonorifics,
|
|
8
|
-
nbspAfterCopyrightSymbols,
|
|
9
|
-
nbspBetweenInitials,
|
|
10
|
-
nbspTransform,
|
|
11
|
-
UNITS,
|
|
12
|
-
HONORIFICS,
|
|
13
|
-
REFERENCE_ABBREVIATIONS,
|
|
14
|
-
type NbspOptions,
|
|
15
|
-
} from "../nbsp.js"
|
|
16
|
-
import { UNICODE_SYMBOLS, DEFAULT_SEPARATOR } from "../constants.js"
|
|
17
|
-
import { assertLinearScaling } from "./test-helpers.js"
|
|
18
|
-
|
|
19
|
-
const { NBSP, COPYRIGHT, REGISTERED, TRADEMARK } = UNICODE_SYMBOLS
|
|
20
|
-
const SEP = DEFAULT_SEPARATOR
|
|
21
|
-
|
|
22
|
-
/** Calls fn with separator option, checking all cases produce correct nbsp. */
|
|
23
|
-
function expectSep(
|
|
24
|
-
fn: (text: string, options?: NbspOptions) => string,
|
|
25
|
-
cases: [string, string][],
|
|
26
|
-
) {
|
|
27
|
-
for (const [input, expected] of cases) {
|
|
28
|
-
expect(fn(input, { separator: SEP })).toBe(expected)
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
describe("nbspAfterShortWords", () => {
|
|
33
|
-
it.each([
|
|
34
|
-
["a cat", `a${NBSP}cat`],
|
|
35
|
-
["I am", `I${NBSP}am`],
|
|
36
|
-
["an apple", `an${NBSP}apple`],
|
|
37
|
-
["to go", `to${NBSP}go`],
|
|
38
|
-
["of the", `of${NBSP}the`],
|
|
39
|
-
["in on", `in${NBSP}on`],
|
|
40
|
-
["is it", `is${NBSP}it`],
|
|
41
|
-
["or if", `or${NBSP}if`],
|
|
42
|
-
["I think", `I${NBSP}think`],
|
|
43
|
-
['"a cat"', `"a${NBSP}cat"`],
|
|
44
|
-
["the cat sat on a mat", `the cat sat on${NBSP}a${NBSP}mat`],
|
|
45
|
-
["Go to the store", `Go${NBSP}to${NBSP}the store`],
|
|
46
|
-
["the cat", "the cat"],
|
|
47
|
-
// Accented Latin short words
|
|
48
|
-
["à chat", `à${NBSP}chat`],
|
|
49
|
-
["où aller", `où${NBSP}aller`],
|
|
50
|
-
["ça va", `ça${NBSP}va`],
|
|
51
|
-
])('"%s" → "%s"', (input, expected) => {
|
|
52
|
-
expect(nbspAfterShortWords(input)).toBe(expected)
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
it("preserves separators at node boundaries", () => {
|
|
56
|
-
expectSep(nbspAfterShortWords, [
|
|
57
|
-
[`a${SEP} cat`, `a${SEP}${NBSP}cat`],
|
|
58
|
-
[`a ${SEP}cat${SEP}`, `a${NBSP}${SEP}cat${SEP}`],
|
|
59
|
-
])
|
|
60
|
-
})
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
describe("nbspBetweenNumberAndUnit", () => {
|
|
64
|
-
it.each(UNITS.map((u) => [`5 ${u}`, `5${NBSP}${u}`]))(
|
|
65
|
-
'"%s" → "%s"',
|
|
66
|
-
(input, expected) => {
|
|
67
|
-
expect(nbspBetweenNumberAndUnit(input)).toBe(expected)
|
|
68
|
-
},
|
|
69
|
-
)
|
|
70
|
-
|
|
71
|
-
it.each([
|
|
72
|
-
["100km", "100km"],
|
|
73
|
-
["5 kms", "5 kms"],
|
|
74
|
-
["chapter 3 above", "chapter 3 above"],
|
|
75
|
-
["item 5 here", "item 5 here"],
|
|
76
|
-
["5 3", "5 3"],
|
|
77
|
-
["page 42 of", "page 42 of"],
|
|
78
|
-
// Excluded units: too ambiguous with common English words
|
|
79
|
-
["found 5 in total", "found 5 in total"],
|
|
80
|
-
["5 in total", "5 in total"],
|
|
81
|
-
["at 5 bar pressure", "at 5 bar pressure"],
|
|
82
|
-
["drew 5 bar charts", "drew 5 bar charts"],
|
|
83
|
-
])('no match: "%s"', (input, expected) => {
|
|
84
|
-
expect(nbspBetweenNumberAndUnit(input)).toBe(expected)
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
it("preserves separators at node boundaries", () => {
|
|
88
|
-
expectSep(nbspBetweenNumberAndUnit, [
|
|
89
|
-
[`5${SEP} kg`, `5${SEP}${NBSP}kg`],
|
|
90
|
-
[`5 ${SEP}kg`, `5${NBSP}${SEP}kg`],
|
|
91
|
-
[`5${SEP} ${SEP}kg`, `5${SEP}${NBSP}${SEP}kg`],
|
|
92
|
-
])
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
it("does not match unit letter that starts a cross-element word", () => {
|
|
96
|
-
// "5 m" + SEP + "ade" simulates <span>5 m</span><span>ade</span> ("made")
|
|
97
|
-
expect(nbspBetweenNumberAndUnit(`5 m${SEP}ade`, { separator: SEP }))
|
|
98
|
-
.toBe(`5 m${SEP}ade`)
|
|
99
|
-
})
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
describe("nbspBeforeLastWord", () => {
|
|
103
|
-
it.each([
|
|
104
|
-
["the end", `the${NBSP}end`],
|
|
105
|
-
["hello world", `hello${NBSP}world`],
|
|
106
|
-
["a b c", `a b${NBSP}c`],
|
|
107
|
-
["hello abcdefghij", `hello${NBSP}abcdefghij`],
|
|
108
|
-
["hello abcdefghijk", "hello abcdefghijk"],
|
|
109
|
-
["hello international", "hello international"],
|
|
110
|
-
["hello", "hello"],
|
|
111
|
-
[" ", " "],
|
|
112
|
-
["the end\n\nNew paragraph", `the${NBSP}end\n\nNew${NBSP}paragraph`],
|
|
113
|
-
["the end\ncontinues", `the end\ncontinues`],
|
|
114
|
-
])('"%s" → "%s"', (input, expected) => {
|
|
115
|
-
expect(nbspBeforeLastWord(input)).toBe(expected)
|
|
116
|
-
})
|
|
117
|
-
|
|
118
|
-
it("preserves separator before end", () => {
|
|
119
|
-
expectSep(nbspBeforeLastWord, [
|
|
120
|
-
[`the end${SEP}`, `the${NBSP}end${SEP}`],
|
|
121
|
-
])
|
|
122
|
-
})
|
|
123
|
-
})
|
|
124
|
-
|
|
125
|
-
describe("nbspAfterReferenceAbbreviations", () => {
|
|
126
|
-
it.each([
|
|
127
|
-
...REFERENCE_ABBREVIATIONS.map((abbr, i) => [`${abbr}. ${i + 1}`, `${abbr}.${NBSP}${i + 1}`]),
|
|
128
|
-
["Fig. caption", "Fig. caption"],
|
|
129
|
-
] as [string, string][])('"%s" → "%s"', (input, expected) => {
|
|
130
|
-
expect(nbspAfterReferenceAbbreviations(input)).toBe(expected)
|
|
131
|
-
})
|
|
132
|
-
|
|
133
|
-
it("preserves separators at node boundaries", () => {
|
|
134
|
-
expectSep(nbspAfterReferenceAbbreviations, [
|
|
135
|
-
[`Fig.${SEP} 1`, `Fig.${SEP}${NBSP}1`],
|
|
136
|
-
[`Fig. ${SEP}1`, `Fig.${NBSP}${SEP}1`],
|
|
137
|
-
])
|
|
138
|
-
})
|
|
139
|
-
})
|
|
140
|
-
|
|
141
|
-
describe("nbspAfterSectionSymbols", () => {
|
|
142
|
-
it.each([
|
|
143
|
-
["§ 5", `§${NBSP}5`],
|
|
144
|
-
["¶ 3", `¶${NBSP}3`],
|
|
145
|
-
["See § 12 for details", `See §${NBSP}12 for details`],
|
|
146
|
-
["§ title", "§ title"],
|
|
147
|
-
])('"%s" → "%s"', (input, expected) => {
|
|
148
|
-
expect(nbspAfterSectionSymbols(input)).toBe(expected)
|
|
149
|
-
})
|
|
150
|
-
|
|
151
|
-
it("preserves separator after symbol", () => {
|
|
152
|
-
expectSep(nbspAfterSectionSymbols, [
|
|
153
|
-
[`§${SEP} 5`, `§${SEP}${NBSP}5`],
|
|
154
|
-
])
|
|
155
|
-
})
|
|
156
|
-
})
|
|
157
|
-
|
|
158
|
-
describe("nbspAfterHonorifics", () => {
|
|
159
|
-
const names = [
|
|
160
|
-
// English
|
|
161
|
-
"Smith", "Jones", "Brown", "Davis", "Wilson", "King", "Patrick", "Martinez", "Judge", "Brown", "Warren", "Lee", "Lee",
|
|
162
|
-
// French
|
|
163
|
-
"Dupont", "Laurent", "Lefebvre",
|
|
164
|
-
// German / Nordic
|
|
165
|
-
"Schmidt", "Weber",
|
|
166
|
-
// Spanish / Portuguese
|
|
167
|
-
"García", "Rodríguez",
|
|
168
|
-
// Italian
|
|
169
|
-
"Rossi", "Bianchi",
|
|
170
|
-
// Dutch
|
|
171
|
-
"Bakker", "Jansen",
|
|
172
|
-
]
|
|
173
|
-
it.each([
|
|
174
|
-
...HONORIFICS.map((h, i) => [`${h}. ${names[i]}`, `${h}.${NBSP}${names[i]}`]),
|
|
175
|
-
["Dr. Élodie", `Dr.${NBSP}Élodie`],
|
|
176
|
-
["Jr. here", "Jr. here"],
|
|
177
|
-
["Dr. said", "Dr. said"],
|
|
178
|
-
] as [string, string][])('"%s" → "%s"', (input, expected) => {
|
|
179
|
-
expect(nbspAfterHonorifics(input)).toBe(expected)
|
|
180
|
-
})
|
|
181
|
-
|
|
182
|
-
it("preserves separators at node boundaries", () => {
|
|
183
|
-
expectSep(nbspAfterHonorifics, [
|
|
184
|
-
[`Dr.${SEP} Smith`, `Dr.${SEP}${NBSP}Smith`],
|
|
185
|
-
[`Dr. ${SEP}Smith`, `Dr.${NBSP}${SEP}Smith`],
|
|
186
|
-
])
|
|
187
|
-
})
|
|
188
|
-
})
|
|
189
|
-
|
|
190
|
-
describe("nbspAfterCopyrightSymbols", () => {
|
|
191
|
-
it.each([
|
|
192
|
-
[`${COPYRIGHT} 2024`, `${COPYRIGHT}${NBSP}2024`],
|
|
193
|
-
[`${REGISTERED} Brand`, `${REGISTERED}${NBSP}Brand`],
|
|
194
|
-
[`${TRADEMARK} Product`, `${TRADEMARK}${NBSP}Product`],
|
|
195
|
-
[`${COPYRIGHT} 2024 Company`, `${COPYRIGHT}${NBSP}2024 Company`],
|
|
196
|
-
[`${COPYRIGHT} company`, `${COPYRIGHT} company`],
|
|
197
|
-
])('"%s" → "%s"', (input, expected) => {
|
|
198
|
-
expect(nbspAfterCopyrightSymbols(input)).toBe(expected)
|
|
199
|
-
})
|
|
200
|
-
|
|
201
|
-
it("preserves separator after symbol", () => {
|
|
202
|
-
expectSep(nbspAfterCopyrightSymbols, [
|
|
203
|
-
[`${COPYRIGHT}${SEP} 2024`, `${COPYRIGHT}${SEP}${NBSP}2024`],
|
|
204
|
-
])
|
|
205
|
-
})
|
|
206
|
-
})
|
|
207
|
-
|
|
208
|
-
describe("nbspBetweenInitials", () => {
|
|
209
|
-
it.each([
|
|
210
|
-
["J. K. Rowling", `J.${NBSP}K.${NBSP}Rowling`],
|
|
211
|
-
["A. B. C.", `A.${NBSP}B.${NBSP}C.`],
|
|
212
|
-
["J. Smith", `J.${NBSP}Smith`],
|
|
213
|
-
["É. Piaf", `É.${NBSP}Piaf`],
|
|
214
|
-
["A. test", "A. test"],
|
|
215
|
-
])('"%s" → "%s"', (input, expected) => {
|
|
216
|
-
expect(nbspBetweenInitials(input)).toBe(expected)
|
|
217
|
-
})
|
|
218
|
-
|
|
219
|
-
it("preserves separator between initials", () => {
|
|
220
|
-
expectSep(nbspBetweenInitials, [
|
|
221
|
-
[`J.${SEP} K. Rowling`, `J.${SEP}${NBSP}K.${NBSP}Rowling`],
|
|
222
|
-
])
|
|
223
|
-
})
|
|
224
|
-
})
|
|
225
|
-
|
|
226
|
-
describe("nbspTransform", () => {
|
|
227
|
-
it("applies all nbsp transformations", () => {
|
|
228
|
-
expect(nbspTransform("Dr. Smith wrote Fig. 1 on p. 42"))
|
|
229
|
-
.toBe(`Dr.${NBSP}Smith wrote Fig.${NBSP}1 on${NBSP}p.${NBSP}42`)
|
|
230
|
-
})
|
|
231
|
-
|
|
232
|
-
it("handles multiple rules on the same text", () => {
|
|
233
|
-
expect(nbspTransform(`${COPYRIGHT} 2024 by J. K. Rowling`))
|
|
234
|
-
.toBe(`${COPYRIGHT}${NBSP}2024 by${NBSP}J.${NBSP}K.${NBSP}Rowling`)
|
|
235
|
-
})
|
|
236
|
-
|
|
237
|
-
it("nbspBeforeLastWord fires on plain text", () => {
|
|
238
|
-
expect(nbspTransform("Hello world")).toBe(`Hello${NBSP}world`)
|
|
239
|
-
})
|
|
240
|
-
|
|
241
|
-
it("passes separator through to all functions", () => {
|
|
242
|
-
expect(nbspTransform(`Dr.${SEP} Smith`, { separator: SEP }))
|
|
243
|
-
.toBe(`Dr.${SEP}${NBSP}Smith`)
|
|
244
|
-
})
|
|
245
|
-
|
|
246
|
-
describe("ordering: specific patterns before generic", () => {
|
|
247
|
-
it("abbreviation claims nbsp before short-word rule", () => {
|
|
248
|
-
const result = nbspTransform("No. 5")
|
|
249
|
-
expect(result).toBe(`No.${NBSP}5`)
|
|
250
|
-
expect(result.split(NBSP).length - 1).toBe(1)
|
|
251
|
-
})
|
|
252
|
-
|
|
253
|
-
it("honorific + initials compose correctly", () => {
|
|
254
|
-
expect(nbspTransform("Dr. J. K. Smith")).toBe(`Dr.${NBSP}J.${NBSP}K.${NBSP}Smith`)
|
|
255
|
-
})
|
|
256
|
-
|
|
257
|
-
it("honorific + unit don't double-apply with short words", () => {
|
|
258
|
-
expect(nbspTransform("St. Anne had 5 kg")).toBe(`St.${NBSP}Anne had 5${NBSP}kg`)
|
|
259
|
-
})
|
|
260
|
-
})
|
|
261
|
-
|
|
262
|
-
describe("stress tests", () => {
|
|
263
|
-
it("scales linearly for short words", () => {
|
|
264
|
-
assertLinearScaling(nbspTransform, (n) => "a b c d e f ".repeat(n))
|
|
265
|
-
})
|
|
266
|
-
|
|
267
|
-
it("early-exits on text with no spaces", () => {
|
|
268
|
-
const result = nbspTransform("nospaces".repeat(1000))
|
|
269
|
-
expect(result).toBe("nospaces".repeat(1000))
|
|
270
|
-
})
|
|
271
|
-
})
|
|
272
|
-
})
|