Skip to content

Instantly share code, notes, and snippets.

@holtwick
Created March 19, 2022 06:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save holtwick/ec4219363808a0d066ef02fd6c405077 to your computer and use it in GitHub Desktop.
Save holtwick/ec4219363808a0d066ef02fd6c405077 to your computer and use it in GitHub Desktop.
A runtime for in browser test execution
import { format } from "pretty-format"
import { reactive } from "vue"
import { deepEqual, isPromise, Logger, SerialQueue } from "zeed"
const log = Logger("jest")
let context: any = {}
let performedAssertions = 0
let expectAssertions = -1
interface Test {
title: string
group?: string
state?: "running" | "passed" | "failed"
actual?: string
expected?: string
}
export const tests = reactive<{
fails: number
tests: Test[]
}>({
fails: 0,
tests: [],
})
if (import.meta.hot) {
log("meta hot")
import.meta.hot.on("vite:beforeUpdate", () => {
log("*** hmr")
tests.fails = 0
tests.tests = []
})
}
let queue = new SerialQueue()
export async function describe(title: string, fn: any) {
queue.enqueue(async () => {
context = {
its: [],
}
let testGroup = { title } as Test
tests.tests.push(testGroup)
try {
let describeResult = fn.call(context)
if (isPromise(describeResult)) {
await describeResult
}
} catch (err) {
tests.fails += 1
log.warn("Exception:", err)
}
log(`${title}... ${context.its.length} tests`)
if (context.beforeAll) {
context.beforeAll()
}
for (let it of context.its) {
log(`... ${it.title}`)
let test = reactive({
title: it.title,
group: title,
state: "running",
} as Test)
tests.tests.push(test)
context.test = test
try {
performedAssertions = 0
expectAssertions = -1
var waitForDone
var done = (value: any) => log.error("Did not set up done() correctly")
if (it.fn.length > 0) {
// Implements: it('', done => {})
waitForDone = new Promise((r) => (done = r))
}
let resultIt = it.fn.call(null, done)
if (waitForDone) {
await waitForDone
}
if (isPromise(resultIt)) {
await resultIt
}
if (expectAssertions >= 0 && performedAssertions !== expectAssertions) {
test.state = "failed"
log.warn(
`Expected ${expectAssertions} assertions, got ${performedAssertions}. ${title}/${it.title}`
)
}
} catch (err) {
tests.fails += 1
test.state = "failed"
log.warn("Exception:", err)
}
log(`... ${it.title} -> DONE`)
// await sleep(1)
}
if (context.afterAll) {
context.afterAll()
}
if (tests.fails > 0) {
// alert(`${tests.fails} tests did fail in ${title}`)
} else {
log.info(`All ${context.its.length} tests of ${title} passed!`)
}
})
}
export function beforeAll(fn: any) {
context.beforeAll = fn
}
export function afterAll(fn: any) {
context.afterAll = fn
}
export function it(title: string, fn: any) {
context.its.push({
title,
fn,
})
}
function formatPretty(s: any) {
return format(s).trim()
}
function expect(actual: any) {
function test(ok: boolean, expected: any) {
if (ok) {
// log("OK - Passed Test")
performedAssertions += 1
context.test.state = "passed"
} else {
tests.fails += 1
context.test.state = "failed"
context.test.actual =
typeof actual === "string" ? actual : formatPretty(actual)
context.test.expected =
typeof expected === "string" ? expected : formatPretty(expected)
let msg = `Fail: got ${context.test.actual} expected ${context.test.expected}`
log.warn(msg)
console.trace(msg)
}
}
let matchers = {
toBe: (expected: any) => expected === actual,
toEqual: (expected: any) => deepEqual(expected, actual), // formatPretty(expected) === formatPretty(actual),
toBeNull: () => actual == null,
toBeTruthy: () => actual == true,
toBeFalsy: () => actual == false,
toBeGreaterThan: (expected: number) => expected < actual,
toBeLessThan: (expected: number) => expected > actual,
toContain: (expected: any) => actual.includes(expected),
toHaveLength: (expected: any) => actual.length === expected,
toMatchInlineSnapshot: (expected: string) => {
let actualPretty = formatPretty(actual)
let extectedPretty = expected
let lines = expected.split(/\n/)
if (lines.length > 1) {
let padding = lines[1].length - lines[1].trimStart().length
extectedPretty = lines
.map((l) => l.substring(padding))
.join("\n")
.trim()
}
let cmpActualPretty = actualPretty
.trim()
.split("\n")
.map((l) => l.trim())
.join("\n")
let cmpExpectedPretty = extectedPretty
.trim()
.split("\n")
.map((l) => l.trim())
.join("\n")
return cmpActualPretty === cmpExpectedPretty
},
}
let obj: any = {
not: {},
}
for (const [key, value] of Object.entries(matchers)) {
obj[key] = (expected: any) => {
test(value(expected), expected)
}
obj.not[key] = (expected: any) => {
test(!value(expected), expected)
}
}
return obj
}
expect.assertions = (count: number) => (expectAssertions = count)
// @ts-ignore
Object.assign(window, {
expect,
it,
test: it,
beforeAll,
afterAll,
describe,
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment