Hi there !
vitest-bdd
A testing solution based on vitest that plays nice with ReScript.
I was finally annoyed at not having source maps and a simple testing solution for ReScript.
This plugin was initially created to support Gherkins tests with step definitions in ReScript or TypeScript but I thought that adding support for unit tests was a nice addition.
And then… I added source maps ! to the test files by retro-pedaling the compiled sources.
Usage
pnpm add -D vitest-bdd
import { vitestBdd } from "vitest-bdd";
import { defineConfig } from "vitest/config";
export default defineConfig({
plugins: [ vitestBdd() ],
test: {
pool: "threads",
include: ["**/*.feature", "**/*.md", "**/*.spec.ts", "**/*_test.res"],
},
});
Make sure to include the *.res
file, not the compiled source.
If you want to keep compiling out of source, you can add your own resolver function for the compiled source in the options:
export type VitestBddOptions = { // defaults
debug?: boolean; // false
markdownExtensions?: string[]; // [".md", ".mdx", ".markdown"]
gherkinExtensions?: string[]; // [".feature"]
rescriptExtensions?: string[]; // [".res"]
stepsResolver?: (path: string) => string | null;
resCompiledResolver?: (path: string) => string | null;
};
More information and a working test app on github (please star the project if you like it
).
Feedback and experiences welcome !
PS: You can use “vitest” without “vite” (I use it in my react-native expo project)
4 Likes
Oh awesome! I have a couple projects I want to add unit tests to, so this is perfect timing.
Awesome !
Let me know how it goes.
Maybe also explore the Gherkins way. I found them super useful for “behavior” testing (like a complicated, multi-stage input). I use a mix of unit and feature tests and am super happy because they all end up in the same test infrastructure.
Example:
# test/TextEntry.feature
Feature: TextEntry
Scenario: enter a single int
Given template "I am [int] years old"
When I type 4
And I type 9
And I am done
Then I should receive "I am 49 years old"
Scenario: enter fractions
Given template "x = \frac{[int]}{[int]}"
When I type 4
And I type 9
And I am done
And I type 7
And I type 4
And I am done
Then I should receive "x = \frac{49}{74}"
Scenario: editing state
Given template "x = \frac{[int]}{[int]}"
When I type 4
Then display should be "x = \frac{{\color{pink}4}}{{\color{violet}?}}"
open VitestBdd
given("template {string}", ({step}, str: string) => {
let entry = TextEntry.make(str)
step("I type {number}", (nb: int) => {
entry.append(nb->Int.toString)
})
step("I am done", () => {
entry.next()
})
step("I should receive {string}", (str: string) => {
expect(entry.text).toEqual(str)
})
step("display should be {string}", (str: string) => {
expect(entry.display).toEqual(str)
})
})
Cool VS Code extensions for vitest and gherkins:
-
Vitest extension (for proper test integration)
-
Cucumber extension (for nice colors)
1 Like
Wow this is awesome! How did you support source maps? Could you give some pointers?
The full sources of the “compiler” is here.
The basic idea is that I parse each lines of the original rescript code and the compiled code looking for corresponding tests. It is done in a way that multiple tests can have the same string.
const TEST_RE = /(describe|it|test|only|skip|todo)\(("([^"]*)")/;
And then I interploate corresponding line numbers between the two files:
const map = new SourceMapGenerator({ file: path });
// rescript line, compiled line
function sm(rline: number, cline: number) {
map.addMapping({
source: path,
generated: { line: cline, column: 0 },
original: { line: rline, column: 0 },
});
}
let ri = 1;
let ci = 1;
for (const [rline, cline] of smap) {
let dr = rline - ri;
let dc = cline - ci;
if (dr > dc) {
for (let i = 1; i <= dr; i++) {
sm(ri + i, Math.ceil(ci + (i * dc) / dr));
}
} else {
for (let i = 1; i <= dc; i++) {
sm(Math.ceil(ri + (i * dr) / dc), ci + i);
}
}
ri = rline;
ci = cline;
}
The smap
array is built by first parsing rescript and then matching the lines in the compiled source.
At the end, I send the compiled JS sources with the source maps to vitest.
2 Likes