Skip to content

Commit 53da60d

Browse files
committed
test: add property based tests via fast-check
1 parent b914c04 commit 53da60d

File tree

4 files changed

+132
-0
lines changed

4 files changed

+132
-0
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@
104104
"@vitest/coverage-v8": "4.0.18",
105105
"@vue/test-utils": "2.4.6",
106106
"axe-core": "4.11.1",
107+
"fast-check": "4.5.3",
107108
"fast-npm-meta": "1.0.0",
108109
"knip": "5.82.1",
109110
"lint-staged": "16.2.7",

pnpm-lock.yaml

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/unit/server/utils/docs/text.spec.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { describe, expect, it } from 'vitest'
2+
import * as fc from 'fast-check'
23
import {
34
escapeHtml,
45
parseJsDocLinks,
@@ -40,6 +41,15 @@ describe('stripAnsi', () => {
4041
const input = `object is ReactElement${ESC}[0m${ESC}[38;5;12m<${ESC}[0mP${ESC}[38;5;12m>${ESC}[0m`
4142
expect(stripAnsi(input)).toBe('object is ReactElement<P>')
4243
})
44+
45+
it('should strip everything in one pass', () => {
46+
fc.assert(
47+
fc.property(fc.string(), input => {
48+
const stripped = stripAnsi(input)
49+
expect(stripAnsi(stripped)).toBe(stripped)
50+
}),
51+
)
52+
})
4353
})
4454

4555
describe('escapeHtml', () => {
@@ -124,6 +134,65 @@ describe('parseJsDocLinks', () => {
124134
const result = parseJsDocLinks('{@link http://example.com}', emptyLookup)
125135
expect(result).toContain('href="http://example.com"')
126136
})
137+
138+
it('should convert external URLs using {@link url} to links', () => {
139+
fc.assert(
140+
fc.property(fc.webUrl(), url => {
141+
const result = parseJsDocLinks(`{@link ${url}}`, emptyLookup)
142+
expect(result).toContain(`href="${url.replaceAll('"', '\\"')}"`)
143+
expect(result).toContain('target="_blank"')
144+
expect(result).toContain('rel="noreferrer"')
145+
expect(result).toContain(escapeHtml(url))
146+
}),
147+
)
148+
})
149+
150+
it('should convert external URLs using {@link url text} to links', () => {
151+
fc.assert(
152+
fc.property(fc.webUrl(), fc.stringMatching(/^[^}\s][^}]+[^}\s]$/), (url, text) => {
153+
const result = parseJsDocLinks(`{@link ${url} ${text}}`, emptyLookup)
154+
expect(result).toContain(`href="${url.replaceAll('"', '\\"')}"`)
155+
expect(result).toContain('target="_blank"')
156+
expect(result).toContain('rel="noreferrer"')
157+
expect(result).toContain(escapeHtml(text))
158+
}),
159+
)
160+
})
161+
162+
it('should be able to treat correctly several external URLs at the middle of a text', () => {
163+
const surrounding = fc.stringMatching(/^[^{]*$/)
164+
const link = fc.record({
165+
url: fc.webUrl(),
166+
label: fc.option(fc.stringMatching(/^[^}\s][^}]+[^}\s]$/)),
167+
before: surrounding,
168+
after: surrounding,
169+
})
170+
fc.assert(
171+
fc.property(fc.array(link, { minLength: 1 }), content => {
172+
let docString = ''
173+
const expectedUrls = []
174+
for (const chunk of content) {
175+
if (chunk.before.length !== 0 || docString.length !== 0) {
176+
docString += `${chunk.before} `
177+
}
178+
if (chunk.label === null) {
179+
docString += `{@link ${chunk.url}}`
180+
expectedUrls.push(chunk.url)
181+
} else {
182+
docString += `{@link ${chunk.url} ${chunk.label}}`
183+
expectedUrls.push(chunk.url)
184+
}
185+
if (chunk.after.length !== 0) {
186+
docString += ` ${chunk.after}`
187+
}
188+
}
189+
const result = parseJsDocLinks(docString, emptyLookup)
190+
for (const url of expectedUrls) {
191+
expect(result).toContain(`href="${url.replaceAll('"', '\\"')}"`)
192+
}
193+
}),
194+
)
195+
})
127196
})
128197

129198
describe('renderMarkdown', () => {

test/unit/shared/utils/async.spec.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { describe, expect, it, vi } from 'vitest'
2+
import * as fc from 'fast-check'
23
import { mapWithConcurrency } from '../../../../shared/utils/async'
34

45
describe('mapWithConcurrency', () => {
@@ -92,4 +93,49 @@ describe('mapWithConcurrency', () => {
9293
// Should only have 3 concurrent since we only have 3 items
9394
expect(maxConcurrent).toBe(3)
9495
})
96+
97+
it('waits for all tasks to succeed and return them in order whatever their count and the concurrency', async () => {
98+
await fc.assert(
99+
fc.asyncProperty(
100+
fc.array(fc.anything()),
101+
fc.integer({ min: 1 }),
102+
fc.scheduler(),
103+
async (items, concurrency, s) => {
104+
const fn = s.scheduleFunction(async item => item)
105+
const results = await s.waitFor(mapWithConcurrency(items, fn, concurrency))
106+
expect(results).toEqual(items)
107+
},
108+
),
109+
)
110+
})
111+
112+
it('not run more than concurrency tasks in parallel', async () => {
113+
await fc.assert(
114+
fc.asyncProperty(
115+
fc.array(fc.anything()), // TODO, support failing tasks too
116+
fc.integer({ min: 1 }),
117+
fc.scheduler(),
118+
async (items, concurrency, s) => {
119+
let tooManyRunningTasksEncountered = false
120+
let currentlyRunning = 0
121+
const fn = async (item: (typeof items)[number]) => {
122+
currentlyRunning++
123+
if (currentlyRunning > concurrency) {
124+
tooManyRunningTasksEncountered = true
125+
}
126+
const task = s.schedule(Promise.resolve(item))
127+
task.then(
128+
// not a finally as we want to handle failing tasks too in the future
129+
() => currentlyRunning--,
130+
() => currentlyRunning--,
131+
)
132+
return task
133+
}
134+
await s.waitFor(mapWithConcurrency(items, fn, concurrency))
135+
expect(tooManyRunningTasksEncountered).toBe(false)
136+
},
137+
),
138+
{ endOnFailure: true },
139+
)
140+
})
95141
})

0 commit comments

Comments
 (0)