Skip to content

Commit fedda70

Browse files
committed
test: cover frontmatter with pbt
1 parent 6acba09 commit fedda70

1 file changed

Lines changed: 106 additions & 0 deletions

File tree

test/unit/shared/utils/parse-basic-frontmatter.spec.ts

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { describe, expect, it } from 'vitest'
2+
import fc from 'fast-check'
23
import { parseBasicFrontmatter } from '../../../../shared/utils/parse-basic-frontmatter'
34

45
describe('parseBasicFrontmatter', () => {
@@ -105,4 +106,109 @@ describe('parseBasicFrontmatter', () => {
105106
tags: ['a', 'b'],
106107
})
107108
})
109+
110+
it.fails('handles string numerics as strings', () => {
111+
const input = "---\nid: '42'\n---"
112+
expect(parseBasicFrontmatter(input)).toEqual({ id: '42' })
113+
})
114+
115+
it.fails('handles numbers using scientific notation', () => {
116+
const input = '---\nprice: 1e+50\n---'
117+
expect(parseBasicFrontmatter(input)).toEqual({ price: 1e50 })
118+
})
119+
120+
it.fails('handles escaped double quote', () => {
121+
const input = '---\nquote: "He said, \\"Hello\\""\n---'
122+
expect(parseBasicFrontmatter(input)).toEqual({ quote: 'He said, "Hello"' })
123+
})
124+
125+
it('strips leading and trailing whitespace', () => {
126+
const input = '---\ntext: Something to say \n---'
127+
expect(parseBasicFrontmatter(input)).toEqual({ text: 'Something to say' })
128+
})
129+
130+
it.fails('handles numeric in arrays', () => {
131+
const input = '---\ntags: [1, 2]\n---'
132+
expect(parseBasicFrontmatter(input)).toEqual({ tags: [1, 2] })
133+
})
134+
135+
it('handles strings with array like content', () => {
136+
const input = '---\ntext: Read the content of this array [1, 2, 3]\n---'
137+
expect(parseBasicFrontmatter(input)).toEqual({
138+
text: 'Read the content of this array [1, 2, 3]',
139+
})
140+
})
141+
142+
it.fails('handles quoted strings with array as content', () => {
143+
const input = '---\nvalue: "[123]"\n---'
144+
expect(parseBasicFrontmatter(input)).toEqual({ value: '[123]' })
145+
})
146+
147+
it.fails('handles string with commas when quoted in arrays', () => {
148+
const input = '---\ntags: ["foo, bar", "baz"]\n---'
149+
expect(parseBasicFrontmatter(input)).toEqual({ tags: ['foo, bar', 'baz'] })
150+
})
151+
152+
it('should parse back every key-value pair from generated frontmatter', () => {
153+
const keyArb = fc.stringMatching(/^[a-zA-Z][a-zA-Z0-9_]*$/)
154+
const singleValueArbs = (inArray: boolean) =>
155+
// for arrays: all values get parsed as strings and there should not be any comma in the value even for quoted strings
156+
[
157+
// For boolean values
158+
fc.boolean().map(b => ({ raw: `${b}`, expected: inArray ? `${b}` : b })),
159+
// For number values
160+
fc
161+
.oneof(
162+
// no support for -0
163+
fc.constant(0),
164+
// no support for scientific notation
165+
// anything <0.000001 will be displayed in scientific notation, and anything >999999999999999900000 too
166+
fc.double({ min: 0.000001, max: 999999999999999900000, noNaN: true }),
167+
fc.double({ min: -999999999999999900000, max: -0.000001, noNaN: true }),
168+
)
169+
.map(n => ({ raw: `${n}`, expected: inArray ? `${n}` : n })),
170+
// For string values
171+
fc.oneof(
172+
fc
173+
.stringMatching(inArray ? /^[^',]*$/ : /^[^']*$/) // single-quoted string
174+
.filter(v => Number.isNaN(Number(v))) // numbers, even quoted ones, get parsed as numbers
175+
.map(v => ({ raw: `'${v}'`, expected: v })),
176+
fc
177+
.stringMatching(inArray ? /^[^",]*$/ : /^[^"]*$/) // double-quoted string
178+
.filter(v => Number.isNaN(Number(v)))
179+
.map(v => ({ raw: `"${v}"`, expected: v })),
180+
fc // need to forbid [, ', " or space as first character
181+
.stringMatching(inArray ? /^[^,]+$/ : /^.+$/) // leading and trailing whitespace are ignored for unquoted strings
182+
.filter(v => Number.isNaN(Number(v)))
183+
.map(v => v.trim())
184+
.map(v => ({ raw: `'${v}'`, expected: v })),
185+
),
186+
]
187+
const valueArb = fc.oneof(
188+
...singleValueArbs(false),
189+
fc
190+
.array(fc.oneof(...singleValueArbs(true)), { minLength: 1 }) // all values get read as strings, no support to empty arrays
191+
.map(arr => ({
192+
raw: `[${arr.map(v => v.raw).join(', ')}]`,
193+
expected: arr.map(v => v.expected),
194+
})),
195+
)
196+
const frontmatterContentArb = fc.dictionary(keyArb, valueArb).map(dict => {
197+
const entries = Object.entries(dict).map(([key, { raw, expected }]) => ({
198+
key,
199+
raw: `${key}: ${raw}`,
200+
expected,
201+
}))
202+
return {
203+
raw: `---\n${entries.map(e => e.raw).join('\n')}\n---\n`,
204+
expected: Object.fromEntries(entries.map(e => [e.key, e.expected])),
205+
}
206+
})
207+
fc.assert(
208+
fc.property(frontmatterContentArb, ({ raw, expected }) => {
209+
expect(parseBasicFrontmatter(raw)).toEqual(expected)
210+
}),
211+
{ numRuns: 10000, endOnFailure: true },
212+
)
213+
})
108214
})

0 commit comments

Comments
 (0)