punctilio 3.7.3 → 3.7.4
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/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 +12 -5
- 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/rehype.ts +5 -24
- package/src/symbols.ts +11 -5
- 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
package/src/tests/dashes.test.ts
DELETED
|
@@ -1,736 +0,0 @@
|
|
|
1
|
-
import { hyphenReplace, enDashNumberRange, enDashDateRange, minusReplace, numberRangeDisallowedPrefixes } from "../dashes.js"
|
|
2
|
-
import { DEFAULT_SEPARATOR, UNICODE_SYMBOLS } from "../constants.js"
|
|
3
|
-
import { assertLinearScaling } from "./test-helpers.js"
|
|
4
|
-
|
|
5
|
-
const {
|
|
6
|
-
LEFT_DOUBLE_QUOTE,
|
|
7
|
-
RIGHT_DOUBLE_QUOTE,
|
|
8
|
-
LEFT_SINGLE_QUOTE,
|
|
9
|
-
RIGHT_SINGLE_QUOTE,
|
|
10
|
-
EM_DASH,
|
|
11
|
-
EN_DASH,
|
|
12
|
-
MINUS,
|
|
13
|
-
ELLIPSIS,
|
|
14
|
-
} = UNICODE_SYMBOLS
|
|
15
|
-
|
|
16
|
-
describe("hyphenReplace", () => {
|
|
17
|
-
describe("em dashes from surrounded hyphens", () => {
|
|
18
|
-
it.each([
|
|
19
|
-
["This is a - hyphen.", `This is a${EM_DASH}hyphen.`],
|
|
20
|
-
// Parenthetical dash before a digit (not math subtraction)
|
|
21
|
-
["Safari) - 9 combinations", `Safari)${EM_DASH}9 combinations`],
|
|
22
|
-
[`This is an ${EM_DASH} em dash.`, `This is an${EM_DASH}em dash.`],
|
|
23
|
-
[`word ${EM_DASH} word`, `word${EM_DASH}word`],
|
|
24
|
-
["word ---", `word${EM_DASH}`],
|
|
25
|
-
[`word${EM_DASH} word`, `word${EM_DASH}word`],
|
|
26
|
-
[`word ${EM_DASH}word`, `word${EM_DASH}word`],
|
|
27
|
-
['"I love dogs." - Me', `"I love dogs."${EM_DASH}Me`],
|
|
28
|
-
["- Me", `${EM_DASH} Me`],
|
|
29
|
-
["-- Me", `${EM_DASH} Me`],
|
|
30
|
-
["Hi-- what do you think?", `Hi${EM_DASH}what do you think?`],
|
|
31
|
-
[
|
|
32
|
-
`${EM_DASH}such behaviors still have to be retrodicted`,
|
|
33
|
-
`${EM_DASH}such behaviors still have to be retrodicted`,
|
|
34
|
-
],
|
|
35
|
-
[`emphasis" ${EM_DASH}`, `emphasis"${EM_DASH}`],
|
|
36
|
-
["- Intro text\n then more", `${EM_DASH} Intro text\n then more`],
|
|
37
|
-
[
|
|
38
|
-
`reward${ELLIPSIS} ${EM_DASH} [Model-based RL, Desires, Brains, Wireheading](https://www.alignmentforum.org/posts/K5ikTdaNymfWXQHFb/model-based-rl-desires-brains-wireheading#Self_aware_desires_1__wireheading)`,
|
|
39
|
-
`reward${ELLIPSIS}${EM_DASH}[Model-based RL, Desires, Brains, Wireheading](https://www.alignmentforum.org/posts/K5ikTdaNymfWXQHFb/model-based-rl-desires-brains-wireheading#Self_aware_desires_1__wireheading)`,
|
|
40
|
-
],
|
|
41
|
-
["a browser- or OS-specific fashion", "a browser- or OS-specific fashion"],
|
|
42
|
-
// Suspended/hanging hyphens: prefix shared with a preceding compound
|
|
43
|
-
["Yes-men and -women", "Yes-men and -women"],
|
|
44
|
-
["the over- and -supply of widgets", "the over- and -supply of widgets"],
|
|
45
|
-
["since--as you know", `since${EM_DASH}as you know`],
|
|
46
|
-
// Arrow patterns should be preserved (not converted to em-dashes)
|
|
47
|
-
["word -> arrow", "word -> arrow"],
|
|
48
|
-
["word --> arrow", "word --> arrow"],
|
|
49
|
-
["-> start", "-> start"],
|
|
50
|
-
["--> start", "--> start"],
|
|
51
|
-
])('should replace hyphens in "%s"', (input, expected) => {
|
|
52
|
-
expect(hyphenReplace(input)).toBe(expected)
|
|
53
|
-
})
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
describe("multiple dashes within words", () => {
|
|
57
|
-
it.each([
|
|
58
|
-
["Since--as you know", `Since${EM_DASH}as you know`, "double dashes"],
|
|
59
|
-
["word---another", `word${EM_DASH}another`, "triple dashes"],
|
|
60
|
-
])("%s → %s (%s)", (input, expected) => {
|
|
61
|
-
expect(hyphenReplace(input)).toBe(expected)
|
|
62
|
-
})
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
describe("dashes at start of line", () => {
|
|
66
|
-
it.each([
|
|
67
|
-
["- Author", `${EM_DASH} Author`],
|
|
68
|
-
["--- Author attribution", `${EM_DASH} Author attribution`],
|
|
69
|
-
["Quote\n- Author", `Quote\n${EM_DASH} Author`],
|
|
70
|
-
])('handles "%s"', (input, expected) => {
|
|
71
|
-
expect(hyphenReplace(input)).toBe(expected)
|
|
72
|
-
})
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
describe("spaces around em dashes", () => {
|
|
76
|
-
it.each([
|
|
77
|
-
[`word ${EM_DASH} another`, `word${EM_DASH}another`],
|
|
78
|
-
[`word${EM_DASH} another`, `word${EM_DASH}another`],
|
|
79
|
-
[`word ${EM_DASH}another`, `word${EM_DASH}another`],
|
|
80
|
-
])('removes spaces in "%s"', (input, expected) => {
|
|
81
|
-
expect(hyphenReplace(input)).toBe(expected)
|
|
82
|
-
})
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
describe("quote-to-quote em dash (Chicago: no spaces)", () => {
|
|
86
|
-
it.each([
|
|
87
|
-
// Chicago style: no spaces around em-dashes, even between quotes
|
|
88
|
-
`"Hello."${EM_DASH}"World"`,
|
|
89
|
-
`'Hi.'${EM_DASH}'There'`,
|
|
90
|
-
`${LEFT_DOUBLE_QUOTE}Hello.${RIGHT_DOUBLE_QUOTE}${EM_DASH}${LEFT_DOUBLE_QUOTE}World${RIGHT_DOUBLE_QUOTE}`,
|
|
91
|
-
`${LEFT_SINGLE_QUOTE}Hi.${RIGHT_SINGLE_QUOTE}${EM_DASH}${LEFT_SINGLE_QUOTE}There${RIGHT_SINGLE_QUOTE}`,
|
|
92
|
-
])('preserves unspaced em-dash in "%s"', (input) => {
|
|
93
|
-
expect(hyphenReplace(input)).toBe(input)
|
|
94
|
-
})
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
describe("em dashes at start of line", () => {
|
|
98
|
-
it.each([
|
|
99
|
-
[`${EM_DASH}Start of line`, `${EM_DASH} Start of line`],
|
|
100
|
-
[`Line 1\n${EM_DASH}Line 2`, `Line 1\n${EM_DASH} Line 2`],
|
|
101
|
-
[`${EM_DASH} Already correct`, `${EM_DASH} Already correct`],
|
|
102
|
-
])('handles "%s"', (input, expected) => {
|
|
103
|
-
expect(hyphenReplace(input)).toBe(expected)
|
|
104
|
-
})
|
|
105
|
-
})
|
|
106
|
-
|
|
107
|
-
describe("number ranges to en dashes", () => {
|
|
108
|
-
it.each([
|
|
109
|
-
["Pages 1-5", `Pages 1${EN_DASH}5`],
|
|
110
|
-
["5--10", `5${EN_DASH}10`],
|
|
111
|
-
["2000-2020", `2000${EN_DASH}2020`],
|
|
112
|
-
["2018-2021. Then 1-3", `2018${EN_DASH}2021. Then 1${EN_DASH}3`],
|
|
113
|
-
["p.10-15", `p.10${EN_DASH}15`],
|
|
114
|
-
])('converts "%s"', (input, expected) => {
|
|
115
|
-
expect(hyphenReplace(input)).toBe(expected)
|
|
116
|
-
})
|
|
117
|
-
})
|
|
118
|
-
|
|
119
|
-
describe("month ranges to en dashes", () => {
|
|
120
|
-
it.each([
|
|
121
|
-
["Jan-Mar", `Jan${EN_DASH}Mar`],
|
|
122
|
-
["Mar - Nov", `Mar${EN_DASH}Nov`],
|
|
123
|
-
["January - March", `January${EN_DASH}March`],
|
|
124
|
-
["October 2012 - December 2014", `October 2012${EN_DASH}December 2014`],
|
|
125
|
-
["Jan 2000 - Feb", `Jan 2000${EN_DASH}Feb`],
|
|
126
|
-
["March - April 2025", `March${EN_DASH}April 2025`],
|
|
127
|
-
])('converts "%s"', (input, expected) => {
|
|
128
|
-
expect(hyphenReplace(input)).toBe(expected)
|
|
129
|
-
})
|
|
130
|
-
})
|
|
131
|
-
|
|
132
|
-
describe("tilde preservation", () => {
|
|
133
|
-
it.each([
|
|
134
|
-
["word ~ word", "word ~ word"],
|
|
135
|
-
["word ~~ word", "word ~~ word"],
|
|
136
|
-
["the ~5% range", "the ~5% range"],
|
|
137
|
-
["~approximately", "~approximately"],
|
|
138
|
-
])('preserves tilde in "%s"', (input) => {
|
|
139
|
-
expect(hyphenReplace(input)).toBe(input)
|
|
140
|
-
})
|
|
141
|
-
})
|
|
142
|
-
|
|
143
|
-
describe("with separator character", () => {
|
|
144
|
-
const sep = "\uE000"
|
|
145
|
-
it.each([
|
|
146
|
-
[`word${sep} - ${sep}another`, `word${sep}${EM_DASH}${sep}another`, "em dash context"],
|
|
147
|
-
[`pages 1${sep}-${sep}5`, `pages 1${sep}${EN_DASH}${sep}5`, "number ranges"],
|
|
148
|
-
// American em-dash (Chicago style) consumes surrounding spaces, even across separator boundaries
|
|
149
|
-
[`text ${EM_DASH} ${sep} more text`, `text${EM_DASH}${sep}more text`, "consumes space after separator for em-dash"],
|
|
150
|
-
[`text - ${sep} more`, `text${EM_DASH}${sep}more`, "consumes space after separator with hyphen"],
|
|
151
|
-
// Separator-only before dash (no space between word and separator): e.g., link text followed by dash
|
|
152
|
-
[`word${sep}${EN_DASH} rest`, `word${sep}${EM_DASH}rest`, "en-dash after separator without preceding space"],
|
|
153
|
-
[`word${sep}- rest`, `word${sep}${EM_DASH}rest`, "hyphen after separator without preceding space"],
|
|
154
|
-
// Spaced hyphen with separator after: "word -<sep> another" is a parenthetical dash, not suspended
|
|
155
|
-
[`word -${sep} another`, `word${EM_DASH}${sep}another`, "hyphen before separator-space is parenthetical"],
|
|
156
|
-
// Suspended hyphen across separator boundary: hyphen attached to word through separator
|
|
157
|
-
[`and -${sep}women`, `and -${sep}women`, "suspended hyphen across separator preserved"],
|
|
158
|
-
])("%s → %s (%s)", (input, expected) => {
|
|
159
|
-
expect(hyphenReplace(input, { separator: sep })).toBe(expected)
|
|
160
|
-
})
|
|
161
|
-
|
|
162
|
-
it.each([
|
|
163
|
-
// British en-dash (spaced) preserves trailing space after separator
|
|
164
|
-
[`text - ${sep} more`, `text ${EN_DASH} ${sep} more`, "preserves space after separator for en-dash"],
|
|
165
|
-
[`word${sep}${EN_DASH} rest`, `word${sep} ${EN_DASH} rest`, "en-dash after separator british style"],
|
|
166
|
-
])("%s → %s (%s)", (input, expected) => {
|
|
167
|
-
expect(hyphenReplace(input, { separator: sep, dashStyle: "british" })).toBe(expected)
|
|
168
|
-
})
|
|
169
|
-
})
|
|
170
|
-
})
|
|
171
|
-
|
|
172
|
-
describe("enDashNumberRange", () => {
|
|
173
|
-
it.each([
|
|
174
|
-
["1-2", `1${EN_DASH}2`],
|
|
175
|
-
["10-20", `10${EN_DASH}20`],
|
|
176
|
-
["100-200", `100${EN_DASH}200`],
|
|
177
|
-
["1000-2000", `1000${EN_DASH}2000`],
|
|
178
|
-
["1,000-2,000", `1,000${EN_DASH}2,000`],
|
|
179
|
-
["1.000-2.000", `1.000${EN_DASH}2.000`],
|
|
180
|
-
["1-2 and 3-4", `1${EN_DASH}2 and 3${EN_DASH}4`],
|
|
181
|
-
["from 5-10 to 15-20", `from 5${EN_DASH}10 to 15${EN_DASH}20`],
|
|
182
|
-
["1-2-3", "1-2-3"], // Multi-segment patterns are not ranges
|
|
183
|
-
["a-b", "a-b"], // Don't replace non-numeric ranges
|
|
184
|
-
["1a-2b", "1a-2b"], // Don't replace if not purely numeric
|
|
185
|
-
["a1-2b", "a1-2b"], // Don't replace if not purely numeric
|
|
186
|
-
["p. 206-207)", `p. 206${EN_DASH}207)`],
|
|
187
|
-
["Qwen1.5-1.8", "Qwen1.5-1.8"], // Don't replace if there's a decimal
|
|
188
|
-
["$100-$200", `$100${EN_DASH}$200`],
|
|
189
|
-
["$1.50-$3.50", `$1.50${EN_DASH}$3.50`],
|
|
190
|
-
["$1-3", `$1${EN_DASH}3`],
|
|
191
|
-
["5--10", `5${EN_DASH}10`], // Double dash as range separator
|
|
192
|
-
["5---10", `5${EN_DASH}10`], // Triple dash as range separator
|
|
193
|
-
["$100--$200", `$100${EN_DASH}$200`], // Double dash with currency
|
|
194
|
-
["1 - 2", "1 - 2"], // Spaced ranges should not change
|
|
195
|
-
// Multiplier suffixes
|
|
196
|
-
["1-10x", `1${EN_DASH}10x`], // lowercase x for multiplier
|
|
197
|
-
["1-10K", `1${EN_DASH}10K`], // uppercase K for kilo
|
|
198
|
-
["1-10M", `1${EN_DASH}10M`], // uppercase M for million
|
|
199
|
-
["1-10B", `1${EN_DASH}10B`], // uppercase B for billion
|
|
200
|
-
["1-10T", `1${EN_DASH}10T`], // uppercase T for trillion
|
|
201
|
-
["1-10X", "1-10X"], // uppercase X should NOT match (only lowercase)
|
|
202
|
-
["1-10k", "1-10k"], // lowercase k should NOT match (only uppercase)
|
|
203
|
-
])('should convert "%s" to "%s"', (input, expected) => {
|
|
204
|
-
expect(enDashNumberRange(input)).toBe(expected)
|
|
205
|
-
})
|
|
206
|
-
|
|
207
|
-
describe("disallowed prefix characters", () => {
|
|
208
|
-
it.each(numberRangeDisallowedPrefixes)(
|
|
209
|
-
'should not convert number ranges preceded by "%s"',
|
|
210
|
-
(prefix) => {
|
|
211
|
-
const input = `foo${prefix}2-7B`
|
|
212
|
-
expect(enDashNumberRange(input)).toBe(input)
|
|
213
|
-
}
|
|
214
|
-
)
|
|
215
|
-
|
|
216
|
-
it.each([
|
|
217
|
-
// Model names with version numbers should NOT be converted
|
|
218
|
-
["Llama-2-7B-chat", "Llama-2-7B-chat"],
|
|
219
|
-
["Llama-2-7B", "Llama-2-7B"],
|
|
220
|
-
["Llama-3-8B-Instruct", "Llama-3-8B-Instruct"],
|
|
221
|
-
["ReLU-2-4", "ReLU-2-4"],
|
|
222
|
-
])('should not convert "%s"', (input, expected) => {
|
|
223
|
-
expect(enDashNumberRange(input)).toBe(expected)
|
|
224
|
-
})
|
|
225
|
-
})
|
|
226
|
-
|
|
227
|
-
describe("marker robustness", () => {
|
|
228
|
-
// Tests that separators don't create false word boundaries
|
|
229
|
-
const sep = DEFAULT_SEPARATOR
|
|
230
|
-
|
|
231
|
-
it.each([
|
|
232
|
-
// [description, input, expected]
|
|
233
|
-
["false leading boundary", `x${sep}1-10`, `x${sep}1-10`], // "x1-10" - should NOT convert
|
|
234
|
-
["multiplier suffix with separator", `1-10${sep}x`, `1${EN_DASH}10${sep}x`], // range of multipliers - should convert
|
|
235
|
-
["false trailing boundary (non-suffix)", `1-10${sep}y`, `1-10${sep}y`], // "y" is not a valid suffix
|
|
236
|
-
["valid boundaries with space", `pages ${sep}1-10${sep} total`, `pages ${sep}1${EN_DASH}10${sep} total`], // should convert
|
|
237
|
-
])("handles %s", (_desc, input, expected) => {
|
|
238
|
-
expect(enDashNumberRange(input, { separator: sep })).toBe(expected)
|
|
239
|
-
})
|
|
240
|
-
})
|
|
241
|
-
})
|
|
242
|
-
|
|
243
|
-
describe("enDashDateRange", () => {
|
|
244
|
-
describe("american style (default, unspaced)", () => {
|
|
245
|
-
it.each([
|
|
246
|
-
["January-March", `January${EN_DASH}March`],
|
|
247
|
-
["Jan-Mar", `Jan${EN_DASH}Mar`],
|
|
248
|
-
["February-April 2024", `February${EN_DASH}April 2024`],
|
|
249
|
-
["May-June", `May${EN_DASH}June`],
|
|
250
|
-
["Sep-Nov", `Sep${EN_DASH}Nov`],
|
|
251
|
-
["December-January", `December${EN_DASH}January`],
|
|
252
|
-
["October 2012 - December 2014", `October 2012${EN_DASH}December 2014`],
|
|
253
|
-
["Oct 2012 - Dec 2014", `Oct 2012${EN_DASH}Dec 2014`],
|
|
254
|
-
["January 2020 - March 2020", `January 2020${EN_DASH}March 2020`],
|
|
255
|
-
["May 2019-June 2020", `May 2019${EN_DASH}June 2020`],
|
|
256
|
-
["Jan 2000 - Feb", `Jan 2000${EN_DASH}Feb`],
|
|
257
|
-
["March - April 2025", `March${EN_DASH}April 2025`],
|
|
258
|
-
])('should convert "%s" to "%s"', (input, expected) => {
|
|
259
|
-
expect(enDashDateRange(input)).toBe(expected)
|
|
260
|
-
})
|
|
261
|
-
})
|
|
262
|
-
|
|
263
|
-
describe("british style (spaced)", () => {
|
|
264
|
-
it.each([
|
|
265
|
-
["January-March", `January ${EN_DASH} March`],
|
|
266
|
-
["Jan-Mar", `Jan ${EN_DASH} Mar`],
|
|
267
|
-
["October 2012 - December 2014", `October 2012 ${EN_DASH} December 2014`],
|
|
268
|
-
["Oct 2012 - Dec 2014", `Oct 2012 ${EN_DASH} Dec 2014`],
|
|
269
|
-
["May 2019-June 2020", `May 2019 ${EN_DASH} June 2020`],
|
|
270
|
-
])('should convert "%s" to "%s"', (input, expected) => {
|
|
271
|
-
expect(enDashDateRange(input, { dashStyle: "british" })).toBe(expected)
|
|
272
|
-
})
|
|
273
|
-
})
|
|
274
|
-
|
|
275
|
-
describe("none style (skip entirely)", () => {
|
|
276
|
-
it.each([
|
|
277
|
-
["January-March"],
|
|
278
|
-
["January - March"],
|
|
279
|
-
["October 2012 - December 2014"],
|
|
280
|
-
["Oct 2012-Dec 2014"],
|
|
281
|
-
])("skips all date range transforms: %s", (input) => {
|
|
282
|
-
expect(enDashDateRange(input, { dashStyle: "none" })).toBe(input)
|
|
283
|
-
})
|
|
284
|
-
})
|
|
285
|
-
|
|
286
|
-
it("should not convert non-month words", () => {
|
|
287
|
-
expect(enDashDateRange("hello-world")).toBe("hello-world")
|
|
288
|
-
expect(enDashDateRange("Mon-Fri")).toBe("Mon-Fri") // Days, not months
|
|
289
|
-
})
|
|
290
|
-
|
|
291
|
-
describe("marker robustness", () => {
|
|
292
|
-
// Tests that separators don't create false word boundaries
|
|
293
|
-
const sep = DEFAULT_SEPARATOR
|
|
294
|
-
|
|
295
|
-
it.each([
|
|
296
|
-
// [description, input, expected]
|
|
297
|
-
["false leading boundary", `x${sep}January-March`, `x${sep}January-March`], // "xJanuary-March" - should NOT convert
|
|
298
|
-
["false trailing boundary", `January-March${sep}x`, `January-March${sep}x`], // "January-Marchx" - should NOT convert
|
|
299
|
-
["valid boundaries with space", `from ${sep}January-March${sep} period`, `from ${sep}January${EN_DASH}March${sep} period`], // should convert
|
|
300
|
-
])("handles %s", (_desc, input, expected) => {
|
|
301
|
-
expect(enDashDateRange(input, { separator: sep })).toBe(expected)
|
|
302
|
-
})
|
|
303
|
-
})
|
|
304
|
-
})
|
|
305
|
-
|
|
306
|
-
describe("minusReplace", () => {
|
|
307
|
-
it.each([
|
|
308
|
-
["-5", `${MINUS}5`],
|
|
309
|
-
["-5.5", `${MINUS}5.5`],
|
|
310
|
-
["(-5)", `(${MINUS}5)`],
|
|
311
|
-
["The value is -10", `The value is ${MINUS}10`],
|
|
312
|
-
[" -3", ` ${MINUS}3`],
|
|
313
|
-
['"-5"', `"${MINUS}5"`],
|
|
314
|
-
// Spaced math subtraction (digit before)
|
|
315
|
-
["5 - 3", `5 ${MINUS} 3`],
|
|
316
|
-
["10 - 5 = 5", `10 ${MINUS} 5 = 5`],
|
|
317
|
-
["(100 - 50)", `(100 ${MINUS} 50)`],
|
|
318
|
-
])('should convert "%s" to use minus sign', (input, expected) => {
|
|
319
|
-
expect(minusReplace(input)).toBe(expected)
|
|
320
|
-
})
|
|
321
|
-
|
|
322
|
-
it("should not convert hyphens in other contexts", () => {
|
|
323
|
-
expect(minusReplace("well-known")).toBe("well-known")
|
|
324
|
-
expect(minusReplace("re-read")).toBe("re-read")
|
|
325
|
-
})
|
|
326
|
-
|
|
327
|
-
it("should not convert spaced hyphens without digit before (parenthetical)", () => {
|
|
328
|
-
// These should be left for em-dash conversion, not minus
|
|
329
|
-
expect(minusReplace("Safari) - 9 combinations")).toBe("Safari) - 9 combinations")
|
|
330
|
-
expect(minusReplace("word - 5")).toBe("word - 5")
|
|
331
|
-
})
|
|
332
|
-
|
|
333
|
-
it("should handle subtraction of negative numbers", () => {
|
|
334
|
-
expect(minusReplace("5 - -3")).toBe(`5 ${MINUS} ${MINUS}3`)
|
|
335
|
-
})
|
|
336
|
-
})
|
|
337
|
-
|
|
338
|
-
describe("enDashNumberRange preserves", () => {
|
|
339
|
-
it.each([
|
|
340
|
-
"555-123-4567", "+1-555-123-4567", "(555)-123-4567", // phones
|
|
341
|
-
"978-3-16-148410-0", "0-13-468599-1", // ISBNs
|
|
342
|
-
"2024-01-15", "2024-01", "1999-12", // ISO dates
|
|
343
|
-
"192-168-1-1", "12-34-5678", // IPs
|
|
344
|
-
])('"%s"', (input) => {
|
|
345
|
-
expect(enDashNumberRange(input)).toBe(input)
|
|
346
|
-
})
|
|
347
|
-
})
|
|
348
|
-
|
|
349
|
-
describe("negative number ranges", () => {
|
|
350
|
-
it.each([
|
|
351
|
-
["-5--2", `${MINUS}5${EN_DASH}${MINUS}2`],
|
|
352
|
-
["-5-5", `${MINUS}5${EN_DASH}5`],
|
|
353
|
-
["-5-3-7", `${MINUS}5-3-7`], // multi-segment preserved
|
|
354
|
-
])('converts "%s" to "%s"', (input, expected) => {
|
|
355
|
-
expect(enDashNumberRange(minusReplace(input))).toBe(expected)
|
|
356
|
-
})
|
|
357
|
-
})
|
|
358
|
-
|
|
359
|
-
// Tests derived from competitor libraries and typography guidelines
|
|
360
|
-
describe("competitor-derived edge cases", () => {
|
|
361
|
-
// From retext-smartypants: oldschool dash handling
|
|
362
|
-
describe("double/triple dash patterns", () => {
|
|
363
|
-
it.each([
|
|
364
|
-
// From retext-smartypants oldschool mode
|
|
365
|
-
["word--word", `word${EM_DASH}word`],
|
|
366
|
-
["word---word", `word${EM_DASH}word`],
|
|
367
|
-
["start-- end", `start${EM_DASH}end`],
|
|
368
|
-
])('converts "%s"', (input, expected) => {
|
|
369
|
-
expect(hyphenReplace(input)).toBe(expected)
|
|
370
|
-
})
|
|
371
|
-
|
|
372
|
-
it("handles double dash after quote", () => {
|
|
373
|
-
const input = '"Hello"-- she said'
|
|
374
|
-
const result = hyphenReplace(input)
|
|
375
|
-
expect(result).toBe(`"Hello"${EM_DASH}she said`)
|
|
376
|
-
})
|
|
377
|
-
})
|
|
378
|
-
|
|
379
|
-
// Phone numbers and ISBNs - documented via enDashNumberRange tests above
|
|
380
|
-
// These multi-segment patterns are correctly preserved
|
|
381
|
-
describe("multi-segment pattern preservation", () => {
|
|
382
|
-
it.each([
|
|
383
|
-
// Already tested in enDashNumberRange preserves section
|
|
384
|
-
"555-123-4567", // phone
|
|
385
|
-
"978-3-16-148410-0", // ISBN
|
|
386
|
-
"2024-01-15", // ISO date
|
|
387
|
-
])('preserves multi-segment pattern: "%s"', (input) => {
|
|
388
|
-
expect(enDashNumberRange(input)).toBe(input)
|
|
389
|
-
})
|
|
390
|
-
})
|
|
391
|
-
|
|
392
|
-
// From tipograph: dash after quoted text
|
|
393
|
-
describe("dashes with quotes", () => {
|
|
394
|
-
it.each([
|
|
395
|
-
// Em dash after closing quote with space
|
|
396
|
-
[`${RIGHT_DOUBLE_QUOTE} - author`, `${RIGHT_DOUBLE_QUOTE}${EM_DASH}author`],
|
|
397
|
-
[`${RIGHT_SINGLE_QUOTE} -- source`, `${RIGHT_SINGLE_QUOTE}${EM_DASH}source`],
|
|
398
|
-
])('handles spaced dashes after quotes: "%s"', (input, expected) => {
|
|
399
|
-
expect(hyphenReplace(input)).toBe(expected)
|
|
400
|
-
})
|
|
401
|
-
|
|
402
|
-
it("handles unspaced double dash between quotes", () => {
|
|
403
|
-
const input = '"first"--"second"'
|
|
404
|
-
const result = hyphenReplace(input)
|
|
405
|
-
expect(result).toBe(`"first"${EM_DASH}"second"`)
|
|
406
|
-
})
|
|
407
|
-
})
|
|
408
|
-
})
|
|
409
|
-
|
|
410
|
-
describe("complex real-world patterns", () => {
|
|
411
|
-
describe("year ranges", () => {
|
|
412
|
-
it.each([
|
|
413
|
-
["2020-2024", `2020${EN_DASH}2024`],
|
|
414
|
-
["the 1990-1999 decade", `the 1990${EN_DASH}1999 decade`],
|
|
415
|
-
["(2010-2015)", `(2010${EN_DASH}2015)`],
|
|
416
|
-
])('handles year range: "%s"', (input, expected) => {
|
|
417
|
-
expect(hyphenReplace(input)).toBe(expected)
|
|
418
|
-
})
|
|
419
|
-
})
|
|
420
|
-
|
|
421
|
-
describe("score ranges", () => {
|
|
422
|
-
it.each([
|
|
423
|
-
["won 3-2", `won 3${EN_DASH}2`],
|
|
424
|
-
["final score 21-17", `final score 21${EN_DASH}17`],
|
|
425
|
-
["led 14-7", `led 14${EN_DASH}7`],
|
|
426
|
-
])('handles score: "%s"', (input, expected) => {
|
|
427
|
-
expect(hyphenReplace(input)).toBe(expected)
|
|
428
|
-
})
|
|
429
|
-
})
|
|
430
|
-
|
|
431
|
-
describe("price ranges", () => {
|
|
432
|
-
it.each([
|
|
433
|
-
// Dollar sign is supported
|
|
434
|
-
["$10-$20", `$10${EN_DASH}$20`],
|
|
435
|
-
])('handles USD price range: "%s"', (input, expected) => {
|
|
436
|
-
expect(hyphenReplace(input)).toBe(expected)
|
|
437
|
-
})
|
|
438
|
-
|
|
439
|
-
it.each([
|
|
440
|
-
["€5-€10", `€5${EN_DASH}€10`],
|
|
441
|
-
["£100-£200", `£100${EN_DASH}£200`],
|
|
442
|
-
["¥1000-¥2000", `¥1000${EN_DASH}¥2000`],
|
|
443
|
-
])('handles non-USD currency range: "%s"', (input, expected) => {
|
|
444
|
-
expect(hyphenReplace(input)).toBe(expected)
|
|
445
|
-
})
|
|
446
|
-
})
|
|
447
|
-
|
|
448
|
-
describe("mixed content", () => {
|
|
449
|
-
it("handles multiple em dash transformations", () => {
|
|
450
|
-
const input = 'The meeting -- scheduled for today -- covers important topics.'
|
|
451
|
-
const expected = `The meeting${EM_DASH}scheduled for today${EM_DASH}covers important topics.`
|
|
452
|
-
expect(hyphenReplace(input)).toBe(expected)
|
|
453
|
-
})
|
|
454
|
-
|
|
455
|
-
it("handles page ranges", () => {
|
|
456
|
-
const input = 'See pages 10-20 for details.'
|
|
457
|
-
const expected = `See pages 10${EN_DASH}20 for details.`
|
|
458
|
-
expect(hyphenReplace(input)).toBe(expected)
|
|
459
|
-
})
|
|
460
|
-
|
|
461
|
-
it("handles em dash before URL-like text", () => {
|
|
462
|
-
const input = 'Visit -- https://example.com'
|
|
463
|
-
const expected = `Visit${EM_DASH}https://example.com`
|
|
464
|
-
expect(hyphenReplace(input)).toBe(expected)
|
|
465
|
-
})
|
|
466
|
-
|
|
467
|
-
it.each([
|
|
468
|
-
["2-3pm", `2${EN_DASH}3pm`],
|
|
469
|
-
["9-10am", `9${EN_DASH}10am`],
|
|
470
|
-
["2-3PM", `2${EN_DASH}3PM`],
|
|
471
|
-
["9-10AM", `9${EN_DASH}10AM`],
|
|
472
|
-
])('handles time range: "%s"', (input, expected) => {
|
|
473
|
-
expect(hyphenReplace(input)).toBe(expected)
|
|
474
|
-
})
|
|
475
|
-
})
|
|
476
|
-
})
|
|
477
|
-
|
|
478
|
-
describe("idempotency", () => {
|
|
479
|
-
it.each([
|
|
480
|
-
`word${EM_DASH}word`,
|
|
481
|
-
`1${EN_DASH}5`,
|
|
482
|
-
`${MINUS}10`,
|
|
483
|
-
`January${EN_DASH}March`,
|
|
484
|
-
])('is idempotent for: "%s"', (input) => {
|
|
485
|
-
expect(hyphenReplace(input)).toBe(input)
|
|
486
|
-
expect(hyphenReplace(hyphenReplace(input))).toBe(input)
|
|
487
|
-
})
|
|
488
|
-
})
|
|
489
|
-
|
|
490
|
-
describe("dashStyle option", () => {
|
|
491
|
-
describe('when "american" (default)', () => {
|
|
492
|
-
it.each([
|
|
493
|
-
["word - word", `word${EM_DASH}word`],
|
|
494
|
-
["word -- word", `word${EM_DASH}word`],
|
|
495
|
-
["since--as you know", `since${EM_DASH}as you know`],
|
|
496
|
-
])("uses unspaced em dash: %s", (input, expected) => {
|
|
497
|
-
expect(hyphenReplace(input, { dashStyle: "american" })).toBe(expected)
|
|
498
|
-
})
|
|
499
|
-
|
|
500
|
-
it("is the default behavior", () => {
|
|
501
|
-
expect(hyphenReplace("word - word")).toBe(`word${EM_DASH}word`)
|
|
502
|
-
})
|
|
503
|
-
})
|
|
504
|
-
|
|
505
|
-
describe('when "british" (Oxford style)', () => {
|
|
506
|
-
it.each([
|
|
507
|
-
["word - word", `word ${EN_DASH} word`],
|
|
508
|
-
["word -- word", `word ${EN_DASH} word`],
|
|
509
|
-
["since--as you know", `since ${EN_DASH} as you know`],
|
|
510
|
-
])("uses spaced en dash: %s", (input, expected) => {
|
|
511
|
-
expect(hyphenReplace(input, { dashStyle: "british" })).toBe(expected)
|
|
512
|
-
})
|
|
513
|
-
|
|
514
|
-
it("still converts number ranges to en dashes", () => {
|
|
515
|
-
expect(hyphenReplace("pages 1-5", { dashStyle: "british" })).toBe(`pages 1${EN_DASH}5`)
|
|
516
|
-
})
|
|
517
|
-
|
|
518
|
-
it("converts existing em-dashes to spaced en-dashes", () => {
|
|
519
|
-
// Oxford style uses spaced en-dashes, so em-dashes get converted
|
|
520
|
-
expect(hyphenReplace(`word ${EM_DASH} word`, { dashStyle: "british" })).toBe(`word ${EN_DASH} word`)
|
|
521
|
-
})
|
|
522
|
-
|
|
523
|
-
it("converts date ranges with spaced en dash", () => {
|
|
524
|
-
expect(hyphenReplace("January-March", { dashStyle: "british" })).toBe(`January ${EN_DASH} March`)
|
|
525
|
-
})
|
|
526
|
-
})
|
|
527
|
-
|
|
528
|
-
describe('when "none"', () => {
|
|
529
|
-
it.each([
|
|
530
|
-
["word - word"],
|
|
531
|
-
["word -- word"],
|
|
532
|
-
["pages 1-5"],
|
|
533
|
-
["-5"],
|
|
534
|
-
["January-March"],
|
|
535
|
-
])("skips all dash transforms: %s", (input) => {
|
|
536
|
-
expect(hyphenReplace(input, { dashStyle: "none" })).toBe(input)
|
|
537
|
-
})
|
|
538
|
-
})
|
|
539
|
-
|
|
540
|
-
describe("style conversion consistency", () => {
|
|
541
|
-
const testCases = [
|
|
542
|
-
"word - word",
|
|
543
|
-
"word -- word",
|
|
544
|
-
"since--as you know",
|
|
545
|
-
"Hello. -- Author",
|
|
546
|
-
`word${EM_DASH}word`,
|
|
547
|
-
`"Quote."${EM_DASH}Author`,
|
|
548
|
-
]
|
|
549
|
-
|
|
550
|
-
it.each(testCases)(
|
|
551
|
-
"American → British equals direct British: %s",
|
|
552
|
-
(input) => {
|
|
553
|
-
const directBritish = hyphenReplace(input, { dashStyle: "british" })
|
|
554
|
-
const viaAmerican = hyphenReplace(
|
|
555
|
-
hyphenReplace(input, { dashStyle: "american" }),
|
|
556
|
-
{ dashStyle: "british" }
|
|
557
|
-
)
|
|
558
|
-
expect(viaAmerican).toBe(directBritish)
|
|
559
|
-
}
|
|
560
|
-
)
|
|
561
|
-
|
|
562
|
-
it.each(testCases)(
|
|
563
|
-
"British → American equals direct American: %s",
|
|
564
|
-
(input) => {
|
|
565
|
-
const directAmerican = hyphenReplace(input, { dashStyle: "american" })
|
|
566
|
-
const viaBritish = hyphenReplace(
|
|
567
|
-
hyphenReplace(input, { dashStyle: "british" }),
|
|
568
|
-
{ dashStyle: "american" }
|
|
569
|
-
)
|
|
570
|
-
expect(viaBritish).toBe(directAmerican)
|
|
571
|
-
}
|
|
572
|
-
)
|
|
573
|
-
})
|
|
574
|
-
})
|
|
575
|
-
|
|
576
|
-
describe("technical patterns preservation", () => {
|
|
577
|
-
it.each([
|
|
578
|
-
["https://example-site.com", "https://example-site.com"],
|
|
579
|
-
["http://sub-domain.example.com/path-to-file", "http://sub-domain.example.com/path-to-file"],
|
|
580
|
-
["user-name@example.com", "user-name@example.com"],
|
|
581
|
-
["550e8400-e29b-41d4-a716-446655440000", "550e8400-e29b-41d4-a716-446655440000"],
|
|
582
|
-
["commit 1a2b3c4d-5e6f", "commit 1a2b3c4d-5e6f"],
|
|
583
|
-
])('preserves technical pattern: "%s"', (input, expected) => {
|
|
584
|
-
expect(hyphenReplace(input)).toBe(expected)
|
|
585
|
-
})
|
|
586
|
-
})
|
|
587
|
-
|
|
588
|
-
describe("scientific notation", () => {
|
|
589
|
-
it.each([
|
|
590
|
-
["1e-10", "1e-10"],
|
|
591
|
-
["5.5e-3", "5.5e-3"],
|
|
592
|
-
["1E-5", "1E-5"],
|
|
593
|
-
["3.14e+10", "3.14e+10"],
|
|
594
|
-
])('preserves scientific notation: "%s"', (input, expected) => {
|
|
595
|
-
expect(hyphenReplace(input)).toBe(expected)
|
|
596
|
-
})
|
|
597
|
-
})
|
|
598
|
-
|
|
599
|
-
describe("version numbers", () => {
|
|
600
|
-
it.each([
|
|
601
|
-
["v1.0.0-beta", "v1.0.0-beta"],
|
|
602
|
-
["1.0.0-rc.1", "1.0.0-rc.1"],
|
|
603
|
-
["2.0.0-alpha.1", "2.0.0-alpha.1"],
|
|
604
|
-
["1.0.0-beta.1-hotfix", "1.0.0-beta.1-hotfix"],
|
|
605
|
-
])('preserves version number: "%s"', (input, expected) => {
|
|
606
|
-
expect(hyphenReplace(input)).toBe(expected)
|
|
607
|
-
})
|
|
608
|
-
})
|
|
609
|
-
|
|
610
|
-
describe("edge number ranges", () => {
|
|
611
|
-
it.each([
|
|
612
|
-
["1000000-2000000", `1000000${EN_DASH}2000000`],
|
|
613
|
-
["1.5-2.5", `1.5${EN_DASH}2.5`],
|
|
614
|
-
["pp. 100-200", `pp. 100${EN_DASH}200`],
|
|
615
|
-
["I-V", "I-V"],
|
|
616
|
-
["i-v", "i-v"],
|
|
617
|
-
["Chapter I-III", "Chapter I-III"],
|
|
618
|
-
])('handles number range edge case: "%s"', (input, expected) => {
|
|
619
|
-
expect(hyphenReplace(input)).toBe(expected)
|
|
620
|
-
})
|
|
621
|
-
})
|
|
622
|
-
|
|
623
|
-
describe("social media patterns", () => {
|
|
624
|
-
it.each([
|
|
625
|
-
["#my-hashtag", "#my-hashtag"],
|
|
626
|
-
["@user-name", "@user-name"],
|
|
627
|
-
])('preserves social pattern: "%s"', (input, expected) => {
|
|
628
|
-
expect(hyphenReplace(input)).toBe(expected)
|
|
629
|
-
})
|
|
630
|
-
})
|
|
631
|
-
|
|
632
|
-
describe("mixed dash types", () => {
|
|
633
|
-
it.each([
|
|
634
|
-
[`pages 1${EN_DASH}5`, `pages 1${EN_DASH}5`],
|
|
635
|
-
[`word${EM_DASH}word`, `word${EM_DASH}word`],
|
|
636
|
-
[`pages 1-5 and word${EM_DASH}word`, `pages 1${EN_DASH}5 and word${EM_DASH}word`],
|
|
637
|
-
])('handles mixed dashes: "%s"', (input, expected) => {
|
|
638
|
-
expect(hyphenReplace(input)).toBe(expected)
|
|
639
|
-
})
|
|
640
|
-
})
|
|
641
|
-
|
|
642
|
-
describe("negative temperatures", () => {
|
|
643
|
-
it.each([
|
|
644
|
-
["-5 to -10", `${MINUS}5 to ${MINUS}10`],
|
|
645
|
-
["High: 5, Low: -10", `High: 5, Low: ${MINUS}10`],
|
|
646
|
-
])('handles negative temperatures: "%s"', (input, expected) => {
|
|
647
|
-
expect(hyphenReplace(input)).toBe(expected)
|
|
648
|
-
})
|
|
649
|
-
})
|
|
650
|
-
|
|
651
|
-
describe("phone number preservation", () => {
|
|
652
|
-
it.each([
|
|
653
|
-
// Preserved phone patterns
|
|
654
|
-
["555-123-4567", "555-123-4567", "full phone with area code"],
|
|
655
|
-
["(555) 123-4567", "(555) 123-4567", "area code in parens"],
|
|
656
|
-
["1-800", "1-800", "toll-free prefix 800"],
|
|
657
|
-
["1-888", "1-888", "toll-free prefix 888"],
|
|
658
|
-
["1-877", "1-877", "toll-free prefix 877"],
|
|
659
|
-
["1-866", "1-866", "toll-free prefix 866"],
|
|
660
|
-
["1-855", "1-855", "toll-free prefix 855"],
|
|
661
|
-
["1-844", "1-844", "toll-free prefix 844"],
|
|
662
|
-
["1-833", "1-833", "toll-free prefix 833"],
|
|
663
|
-
["Call 1-800-...", "Call 1-800-...", "truncated toll-free"],
|
|
664
|
-
["1-800-555-1234", "1-800-555-1234", "full toll-free"],
|
|
665
|
-
["1-888-555-1234", "1-888-555-1234", "full toll-free 888"],
|
|
666
|
-
["+1-800-555-1234", "+1-800-555-1234", "international US"],
|
|
667
|
-
["+44-20-7946-0958", "+44-20-7946-0958", "international UK"],
|
|
668
|
-
["+1 (800) 555-1234", "+1 (800) 555-1234", "international with parens"],
|
|
669
|
-
// International short patterns (country code + city code, no further segments)
|
|
670
|
-
["+44-20", "+44-20", "UK country + city code"],
|
|
671
|
-
["+49-30", "+49-30", "German country + city code"],
|
|
672
|
-
["+33-1", "+33-1", "French country + city code"],
|
|
673
|
-
["+61-2", "+61-2", "Australian country + city code"],
|
|
674
|
-
["+91-22", "+91-22", "Indian country + city code"],
|
|
675
|
-
// Phone-shaped 3+4 bare digits preserve; thousands-grouped form converts
|
|
676
|
-
["555-1234", "555-1234", "standalone 3+4 looks phone, preserve"],
|
|
677
|
-
["555-1,234", `555${EN_DASH}1,234`, "3+4 with thousands comma converts"],
|
|
678
|
-
["555-1.234", `555${EN_DASH}1.234`, "3+4 with thousands period converts"],
|
|
679
|
-
["1-5", `1${EN_DASH}5`, "simple range"],
|
|
680
|
-
["1-50", `1${EN_DASH}50`, "range to two digits"],
|
|
681
|
-
["1-99", `1${EN_DASH}99`, "range to 99"],
|
|
682
|
-
["1-100", `1${EN_DASH}100`, "range to 100 (not a toll-free prefix)"],
|
|
683
|
-
["1-200", `1${EN_DASH}200`, "range to 200 (not a toll-free prefix)"],
|
|
684
|
-
["1-500", `1${EN_DASH}500`, "range to 500 (not a toll-free prefix)"],
|
|
685
|
-
["1-999", `1${EN_DASH}999`, "range to 999 (not a toll-free prefix)"],
|
|
686
|
-
["2-800", `2${EN_DASH}800`, "non-US country code pattern"],
|
|
687
|
-
])("%s → %s (%s)", (input, expected) => {
|
|
688
|
-
expect(hyphenReplace(input)).toBe(expected)
|
|
689
|
-
})
|
|
690
|
-
})
|
|
691
|
-
|
|
692
|
-
describe("minusReplace regex-special separators", () => {
|
|
693
|
-
const regexSpecialChars = [".", "*", "+", "?", "^", "$", "[", "]", "\\", "|", "(", ")"]
|
|
694
|
-
|
|
695
|
-
it.each(regexSpecialChars)("handles '%s' as separator", (sep) => {
|
|
696
|
-
expect(() => minusReplace("-5", { separator: sep })).not.toThrow()
|
|
697
|
-
})
|
|
698
|
-
})
|
|
699
|
-
|
|
700
|
-
const sep = DEFAULT_SEPARATOR
|
|
701
|
-
|
|
702
|
-
describe("minusReplace separator boundary", () => {
|
|
703
|
-
it.each([
|
|
704
|
-
["genuine negative at element start", `${sep}-5${sep}`, `${sep}${MINUS}5${sep}`],
|
|
705
|
-
["negative in parens across boundary", `(${sep}-5${sep})`, `(${sep}${MINUS}5${sep})`],
|
|
706
|
-
])("%s", (_desc, input, expected) => {
|
|
707
|
-
expect(minusReplace(input, { separator: sep })).toBe(expected)
|
|
708
|
-
})
|
|
709
|
-
|
|
710
|
-
it.each([
|
|
711
|
-
["no false negative after digit+sep", `2${sep}-3`],
|
|
712
|
-
["no false negative after letter+sep", `GPT${sep}-3`],
|
|
713
|
-
["multi-segment 1-2-3 with seps", `1-${sep}2${sep}-3`],
|
|
714
|
-
])("%s", (_desc, input) => {
|
|
715
|
-
expect(minusReplace(input, { separator: sep })).toBe(input)
|
|
716
|
-
})
|
|
717
|
-
})
|
|
718
|
-
|
|
719
|
-
describe("hyphenReplace preserves multi-segment numbers across separators", () => {
|
|
720
|
-
it.each([
|
|
721
|
-
["1-2-3 across elements", `1-${sep}2${sep}-3`],
|
|
722
|
-
["model name across elements", `${sep}GPT${sep}-3`],
|
|
723
|
-
])("%s", (_desc, input) => {
|
|
724
|
-
expect(hyphenReplace(input, { separator: sep })).toBe(input)
|
|
725
|
-
})
|
|
726
|
-
})
|
|
727
|
-
|
|
728
|
-
describe("dash stress tests", () => {
|
|
729
|
-
it("scales linearly for parenthetical dashes", () => {
|
|
730
|
-
assertLinearScaling(hyphenReplace, (n) => "word - word ".repeat(n))
|
|
731
|
-
})
|
|
732
|
-
|
|
733
|
-
it("scales linearly for number ranges", () => {
|
|
734
|
-
assertLinearScaling(hyphenReplace, (n) => "pages 1-2 ".repeat(n))
|
|
735
|
-
})
|
|
736
|
-
})
|