use fancy matchers, like Shrimp
This commit is contained in:
parent
3e2e68b31f
commit
614f5c4f91
2
bunfig.toml
Normal file
2
bunfig.toml
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
[test]
|
||||||
|
preload = ["./tests/setup.ts"]
|
||||||
|
|
@ -6,84 +6,84 @@ describe('bitwise operations', () => {
|
||||||
const bytecode = toBytecode([
|
const bytecode = toBytecode([
|
||||||
["PUSH", 5], ["PUSH", 3], ["BIT_AND"], ["HALT"]
|
["PUSH", 5], ["PUSH", 3], ["BIT_AND"], ["HALT"]
|
||||||
])
|
])
|
||||||
expect(await run(bytecode)).toEqual({ type: 'number', value: 1 })
|
await expect(bytecode).toBeNumber(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('BIT_AND with zero', async () => {
|
test('BIT_AND with zero', async () => {
|
||||||
const bytecode = toBytecode([
|
const bytecode = toBytecode([
|
||||||
["PUSH", 5], ["PUSH", 0], ["BIT_AND"], ["HALT"]
|
["PUSH", 5], ["PUSH", 0], ["BIT_AND"], ["HALT"]
|
||||||
])
|
])
|
||||||
expect(await run(bytecode)).toEqual({ type: 'number', value: 0 })
|
await expect(bytecode).toBeNumber(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('BIT_OR', async () => {
|
test('BIT_OR', async () => {
|
||||||
const bytecode = toBytecode([
|
const bytecode = toBytecode([
|
||||||
["PUSH", 5], ["PUSH", 3], ["BIT_OR"], ["HALT"]
|
["PUSH", 5], ["PUSH", 3], ["BIT_OR"], ["HALT"]
|
||||||
])
|
])
|
||||||
expect(await run(bytecode)).toEqual({ type: 'number', value: 7 })
|
await expect(bytecode).toBeNumber(7)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('BIT_OR with zero', async () => {
|
test('BIT_OR with zero', async () => {
|
||||||
const bytecode = toBytecode([
|
const bytecode = toBytecode([
|
||||||
["PUSH", 5], ["PUSH", 0], ["BIT_OR"], ["HALT"]
|
["PUSH", 5], ["PUSH", 0], ["BIT_OR"], ["HALT"]
|
||||||
])
|
])
|
||||||
expect(await run(bytecode)).toEqual({ type: 'number', value: 5 })
|
await expect(bytecode).toBeNumber(5)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('BIT_XOR', async () => {
|
test('BIT_XOR', async () => {
|
||||||
const bytecode = toBytecode([
|
const bytecode = toBytecode([
|
||||||
["PUSH", 5], ["PUSH", 3], ["BIT_XOR"], ["HALT"]
|
["PUSH", 5], ["PUSH", 3], ["BIT_XOR"], ["HALT"]
|
||||||
])
|
])
|
||||||
expect(await run(bytecode)).toEqual({ type: 'number', value: 6 })
|
await expect(bytecode).toBeNumber(6)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('BIT_XOR with itself returns zero', async () => {
|
test('BIT_XOR with itself returns zero', async () => {
|
||||||
const bytecode = toBytecode([
|
const bytecode = toBytecode([
|
||||||
["PUSH", 5], ["PUSH", 5], ["BIT_XOR"], ["HALT"]
|
["PUSH", 5], ["PUSH", 5], ["BIT_XOR"], ["HALT"]
|
||||||
])
|
])
|
||||||
expect(await run(bytecode)).toEqual({ type: 'number', value: 0 })
|
await expect(bytecode).toBeNumber(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('BIT_SHL', async () => {
|
test('BIT_SHL', async () => {
|
||||||
const bytecode = toBytecode([
|
const bytecode = toBytecode([
|
||||||
["PUSH", 5], ["PUSH", 2], ["BIT_SHL"], ["HALT"]
|
["PUSH", 5], ["PUSH", 2], ["BIT_SHL"], ["HALT"]
|
||||||
])
|
])
|
||||||
expect(await run(bytecode)).toEqual({ type: 'number', value: 20 })
|
await expect(bytecode).toBeNumber(20)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('BIT_SHL by zero', async () => {
|
test('BIT_SHL by zero', async () => {
|
||||||
const bytecode = toBytecode([
|
const bytecode = toBytecode([
|
||||||
["PUSH", 5], ["PUSH", 0], ["BIT_SHL"], ["HALT"]
|
["PUSH", 5], ["PUSH", 0], ["BIT_SHL"], ["HALT"]
|
||||||
])
|
])
|
||||||
expect(await run(bytecode)).toEqual({ type: 'number', value: 5 })
|
await expect(bytecode).toBeNumber(5)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('BIT_SHR', async () => {
|
test('BIT_SHR', async () => {
|
||||||
const bytecode = toBytecode([
|
const bytecode = toBytecode([
|
||||||
["PUSH", 20], ["PUSH", 2], ["BIT_SHR"], ["HALT"]
|
["PUSH", 20], ["PUSH", 2], ["BIT_SHR"], ["HALT"]
|
||||||
])
|
])
|
||||||
expect(await run(bytecode)).toEqual({ type: 'number', value: 5 })
|
await expect(bytecode).toBeNumber(5)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('BIT_SHR preserves sign for negative numbers', async () => {
|
test('BIT_SHR preserves sign for negative numbers', async () => {
|
||||||
const bytecode = toBytecode([
|
const bytecode = toBytecode([
|
||||||
["PUSH", -20], ["PUSH", 2], ["BIT_SHR"], ["HALT"]
|
["PUSH", -20], ["PUSH", 2], ["BIT_SHR"], ["HALT"]
|
||||||
])
|
])
|
||||||
expect(await run(bytecode)).toEqual({ type: 'number', value: -5 })
|
await expect(bytecode).toBeNumber(-5)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('BIT_USHR', async () => {
|
test('BIT_USHR', async () => {
|
||||||
const bytecode = toBytecode([
|
const bytecode = toBytecode([
|
||||||
["PUSH", -1], ["PUSH", 1], ["BIT_USHR"], ["HALT"]
|
["PUSH", -1], ["PUSH", 1], ["BIT_USHR"], ["HALT"]
|
||||||
])
|
])
|
||||||
expect(await run(bytecode)).toEqual({ type: 'number', value: 2147483647 })
|
await expect(bytecode).toBeNumber(2147483647)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('BIT_USHR does not preserve sign', async () => {
|
test('BIT_USHR does not preserve sign', async () => {
|
||||||
const bytecode = toBytecode([
|
const bytecode = toBytecode([
|
||||||
["PUSH", -8], ["PUSH", 1], ["BIT_USHR"], ["HALT"]
|
["PUSH", -8], ["PUSH", 1], ["BIT_USHR"], ["HALT"]
|
||||||
])
|
])
|
||||||
expect(await run(bytecode)).toEqual({ type: 'number', value: 2147483644 })
|
await expect(bytecode).toBeNumber(2147483644)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('compound bitwise operations', async () => {
|
test('compound bitwise operations', async () => {
|
||||||
|
|
@ -94,7 +94,7 @@ describe('bitwise operations', () => {
|
||||||
["BIT_OR"], // stack: [5]
|
["BIT_OR"], // stack: [5]
|
||||||
["HALT"]
|
["HALT"]
|
||||||
])
|
])
|
||||||
expect(await run(bytecode)).toEqual({ type: 'number', value: 5 })
|
await expect(bytecode).toBeNumber(5)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('shift with large shift amounts', async () => {
|
test('shift with large shift amounts', async () => {
|
||||||
|
|
@ -102,6 +102,6 @@ describe('bitwise operations', () => {
|
||||||
["PUSH", 1], ["PUSH", 31], ["BIT_SHL"], ["HALT"]
|
["PUSH", 1], ["PUSH", 31], ["BIT_SHL"], ["HALT"]
|
||||||
])
|
])
|
||||||
// 1 << 31 = -2147483648 (most significant bit set)
|
// 1 << 31 = -2147483648 (most significant bit set)
|
||||||
expect(await run(bytecode)).toEqual({ type: 'number', value: -2147483648 })
|
await expect(bytecode).toBeNumber(-2147483648)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ test("PUSH_TRY and POP_TRY - no exception thrown", async () => {
|
||||||
PUSH 999
|
PUSH 999
|
||||||
HALT
|
HALT
|
||||||
`
|
`
|
||||||
expect(await run(toBytecode(str))).toEqual({ type: 'number', value: 52 })
|
await expect(str).toBeNumber(52)
|
||||||
})
|
})
|
||||||
|
|
||||||
test("THROW - catch exception with error value", async () => {
|
test("THROW - catch exception with error value", async () => {
|
||||||
|
|
@ -29,7 +29,7 @@ test("THROW - catch exception with error value", async () => {
|
||||||
.catch:
|
.catch:
|
||||||
HALT
|
HALT
|
||||||
`
|
`
|
||||||
expect(await run(toBytecode(str))).toEqual({ type: 'string', value: 'error occurred' })
|
await expect(str).toBeString('error occurred')
|
||||||
})
|
})
|
||||||
|
|
||||||
test("THROW - uncaught exception throws JS error", async () => {
|
test("THROW - uncaught exception throws JS error", async () => {
|
||||||
|
|
@ -58,7 +58,7 @@ test("THROW - exception with nested try blocks", async () => {
|
||||||
PUSH "outer error"
|
PUSH "outer error"
|
||||||
HALT
|
HALT
|
||||||
`
|
`
|
||||||
expect(await run(toBytecode(str))).toEqual({ type: 'string', value: 'inner error' })
|
await expect(str).toBeString('inner error')
|
||||||
})
|
})
|
||||||
|
|
||||||
test("THROW - exception skips outer handler", async () => {
|
test("THROW - exception skips outer handler", async () => {
|
||||||
|
|
@ -75,7 +75,7 @@ test("THROW - exception skips outer handler", async () => {
|
||||||
.outer_catch:
|
.outer_catch:
|
||||||
HALT
|
HALT
|
||||||
`
|
`
|
||||||
expect(await run(toBytecode(str))).toEqual({ type: 'string', value: 'error message' })
|
await expect(str).toBeString('error message')
|
||||||
})
|
})
|
||||||
|
|
||||||
test("THROW - exception unwinds call stack", async () => {
|
test("THROW - exception unwinds call stack", async () => {
|
||||||
|
|
@ -150,7 +150,7 @@ test("PUSH_FINALLY - finally executes after successful try", async () => {
|
||||||
ADD
|
ADD
|
||||||
HALT
|
HALT
|
||||||
`
|
`
|
||||||
expect(await run(toBytecode(str))).toEqual({ type: 'number', value: 110 })
|
await expect(str).toBeNumber(110)
|
||||||
})
|
})
|
||||||
|
|
||||||
test("PUSH_FINALLY - finally executes after exception", async () => {
|
test("PUSH_FINALLY - finally executes after exception", async () => {
|
||||||
|
|
@ -169,7 +169,7 @@ test("PUSH_FINALLY - finally executes after exception", async () => {
|
||||||
PUSH "finally ran"
|
PUSH "finally ran"
|
||||||
HALT
|
HALT
|
||||||
`
|
`
|
||||||
expect(await run(toBytecode(str))).toEqual({ type: 'string', value: 'finally ran' })
|
await expect(str).toBeString('finally ran')
|
||||||
})
|
})
|
||||||
|
|
||||||
test("PUSH_FINALLY - finally without catch", async () => {
|
test("PUSH_FINALLY - finally without catch", async () => {
|
||||||
|
|
@ -189,7 +189,7 @@ test("PUSH_FINALLY - finally without catch", async () => {
|
||||||
ADD
|
ADD
|
||||||
HALT
|
HALT
|
||||||
`
|
`
|
||||||
expect(await run(toBytecode(str))).toEqual({ type: 'number', value: 52 })
|
await expect(str).toBeNumber(52)
|
||||||
})
|
})
|
||||||
|
|
||||||
test("PUSH_FINALLY - nested try-finally blocks", async () => {
|
test("PUSH_FINALLY - nested try-finally blocks", async () => {
|
||||||
|
|
@ -214,7 +214,7 @@ test("PUSH_FINALLY - nested try-finally blocks", async () => {
|
||||||
ADD
|
ADD
|
||||||
HALT
|
HALT
|
||||||
`
|
`
|
||||||
expect(await run(toBytecode(str))).toEqual({ type: 'number', value: 11 })
|
await expect(str).toBeNumber(11)
|
||||||
})
|
})
|
||||||
|
|
||||||
test("PUSH_FINALLY - error when no handler", async () => {
|
test("PUSH_FINALLY - error when no handler", async () => {
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -159,14 +159,14 @@ describe("RegExp", () => {
|
||||||
PUSH /bar/
|
PUSH /bar/
|
||||||
NEQ
|
NEQ
|
||||||
`
|
`
|
||||||
expect(await run(toBytecode(str))).toEqual({ type: 'boolean', value: true })
|
await expect(str).toBeBoolean(true)
|
||||||
|
|
||||||
const str2 = `
|
const str2 = `
|
||||||
PUSH /test/i
|
PUSH /test/i
|
||||||
PUSH /test/i
|
PUSH /test/i
|
||||||
NEQ
|
NEQ
|
||||||
`
|
`
|
||||||
expect(await run(toBytecode(str2))).toEqual({ type: 'boolean', value: false })
|
await expect(str2).toBeBoolean(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
test("is truthy", async () => {
|
test("is truthy", async () => {
|
||||||
|
|
@ -177,7 +177,7 @@ describe("RegExp", () => {
|
||||||
PUSH 42
|
PUSH 42
|
||||||
.end:
|
.end:
|
||||||
`
|
`
|
||||||
expect(await run(toBytecode(str))).toEqual({ type: 'number', value: 42 })
|
await expect(str).toBeNumber(42)
|
||||||
})
|
})
|
||||||
|
|
||||||
test("NOT returns false (regex is truthy)", async () => {
|
test("NOT returns false (regex is truthy)", async () => {
|
||||||
|
|
@ -185,7 +185,7 @@ describe("RegExp", () => {
|
||||||
PUSH /pattern/
|
PUSH /pattern/
|
||||||
NOT
|
NOT
|
||||||
`
|
`
|
||||||
expect(await run(toBytecode(str))).toEqual({ type: 'boolean', value: false })
|
await expect(str).toBeBoolean(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
test("in arrays", async () => {
|
test("in arrays", async () => {
|
||||||
|
|
@ -301,7 +301,7 @@ describe("RegExp", () => {
|
||||||
PUSH /bar/i
|
PUSH /bar/i
|
||||||
STR_CONCAT #3
|
STR_CONCAT #3
|
||||||
`
|
`
|
||||||
expect(await run(toBytecode(str))).toEqual({ type: 'string', value: '/foo/ and /bar/i' })
|
await expect(str).toBeString('/foo/ and /bar/i')
|
||||||
})
|
})
|
||||||
|
|
||||||
test("DUP with regex", async () => {
|
test("DUP with regex", async () => {
|
||||||
|
|
@ -311,7 +311,7 @@ describe("RegExp", () => {
|
||||||
EQ
|
EQ
|
||||||
`
|
`
|
||||||
// Same regex duplicated should be equal
|
// Same regex duplicated should be equal
|
||||||
expect(await run(toBytecode(str))).toEqual({ type: 'boolean', value: true })
|
await expect(str).toBeBoolean(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
test("empty pattern", async () => {
|
test("empty pattern", async () => {
|
||||||
|
|
@ -365,7 +365,7 @@ describe("RegExp", () => {
|
||||||
PUSH /xyz/
|
PUSH /xyz/
|
||||||
EQ
|
EQ
|
||||||
`
|
`
|
||||||
expect(await run(toBytecode(str1))).toEqual({ type: 'boolean', value: false })
|
await expect(str1).toBeBoolean(false)
|
||||||
|
|
||||||
// Same pattern, different flags
|
// Same pattern, different flags
|
||||||
const str2 = `
|
const str2 = `
|
||||||
|
|
@ -373,7 +373,7 @@ describe("RegExp", () => {
|
||||||
PUSH /test/i
|
PUSH /test/i
|
||||||
EQ
|
EQ
|
||||||
`
|
`
|
||||||
expect(await run(toBytecode(str2))).toEqual({ type: 'boolean', value: false })
|
await expect(str2).toBeBoolean(false)
|
||||||
|
|
||||||
// Different order of flags (should be equal)
|
// Different order of flags (should be equal)
|
||||||
const str3 = `
|
const str3 = `
|
||||||
|
|
@ -381,7 +381,7 @@ describe("RegExp", () => {
|
||||||
PUSH /test/gi
|
PUSH /test/gi
|
||||||
EQ
|
EQ
|
||||||
`
|
`
|
||||||
expect(await run(toBytecode(str3))).toEqual({ type: 'boolean', value: true })
|
await expect(str3).toBeBoolean(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
test("with native functions", async () => {
|
test("with native functions", async () => {
|
||||||
|
|
|
||||||
249
tests/setup.ts
Normal file
249
tests/setup.ts
Normal file
|
|
@ -0,0 +1,249 @@
|
||||||
|
import { expect } from "bun:test"
|
||||||
|
import { toValue, fromValue, type Value, toBytecode, run, type Bytecode } from "#reef"
|
||||||
|
import { isEqual } from "../src/value"
|
||||||
|
|
||||||
|
declare module "bun:test" {
|
||||||
|
interface Matchers<T> {
|
||||||
|
/**
|
||||||
|
* Run bytecode and assert that the result equals a JavaScript value after conversion via toValue()
|
||||||
|
* @example expect(bytecode).toEqualValue(42)
|
||||||
|
* @example expect("PUSH 5\nPUSH 3\nADD").toEqualValue(8)
|
||||||
|
* @example expect([["PUSH", 42]]).toEqualValue(42)
|
||||||
|
*/
|
||||||
|
toEqualValue(expected: any): Promise<void>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run bytecode and assert that the result is null
|
||||||
|
* @example expect(bytecode).toBeNull()
|
||||||
|
* @example expect("PUSH null").toBeNull()
|
||||||
|
*/
|
||||||
|
toBeNull(): Promise<void>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run bytecode and assert that the result is a boolean with the expected value
|
||||||
|
* @example expect(bytecode).toBeBoolean(true)
|
||||||
|
* @example expect("PUSH true").toBeBoolean(true)
|
||||||
|
*/
|
||||||
|
toBeBoolean(expected: boolean): Promise<void>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run bytecode and assert that the result is a number with the expected value
|
||||||
|
* @example expect(bytecode).toBeNumber(42)
|
||||||
|
* @example expect("PUSH 42").toBeNumber(42)
|
||||||
|
*/
|
||||||
|
toBeNumber(expected: number): Promise<void>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run bytecode and assert that the result is a string with the expected value
|
||||||
|
* @example expect(bytecode).toBeString("hello")
|
||||||
|
* @example expect("PUSH \"hello\"").toBeString("hello")
|
||||||
|
*/
|
||||||
|
toBeString(expected: string): Promise<void>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run bytecode and assert that the result is an array with the expected values
|
||||||
|
* @example expect(bytecode).toBeArray([1, 2, 3])
|
||||||
|
*/
|
||||||
|
toBeArray(expected: any[]): Promise<void>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run bytecode and assert that the result is a dict with the expected key-value pairs
|
||||||
|
* @example expect(bytecode).toBeDict({ x: 10, y: 20 })
|
||||||
|
*/
|
||||||
|
toBeDict(expected: Record<string, any>): Promise<void>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run bytecode and assert that the result is a function (Reef or native)
|
||||||
|
* @example expect(bytecode).toBeFunction()
|
||||||
|
*/
|
||||||
|
toBeFunction(): Promise<void>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run bytecode and assert that the result is truthy according to ReefVM semantics
|
||||||
|
* (only null and false are falsy)
|
||||||
|
* @example expect(bytecode).toBeTruthy()
|
||||||
|
*/
|
||||||
|
toBeTruthy(): Promise<void>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run bytecode and assert that the result is falsy according to ReefVM semantics
|
||||||
|
* (only null and false are falsy)
|
||||||
|
* @example expect(bytecode).toBeFalsy()
|
||||||
|
*/
|
||||||
|
toBeFalsy(): Promise<void>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expect.extend({
|
||||||
|
async toEqualValue(received: string | any[] | Bytecode, expected: any) {
|
||||||
|
const bytecode = typeof received === "string" || Array.isArray(received) ? toBytecode(received) : received
|
||||||
|
const result = await run(bytecode)
|
||||||
|
const expectedValue = toValue(expected)
|
||||||
|
const pass = isEqual(result, expectedValue)
|
||||||
|
|
||||||
|
return {
|
||||||
|
pass,
|
||||||
|
message: () =>
|
||||||
|
pass
|
||||||
|
? `Expected value NOT to equal ${formatValue(expectedValue)}, but it did`
|
||||||
|
: `Expected value to equal ${formatValue(expectedValue)}, but received ${formatValue(result)}`,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async toBeNull(received: string | any[] | Bytecode) {
|
||||||
|
const bytecode = typeof received === "string" || Array.isArray(received) ? toBytecode(received) : received
|
||||||
|
const result = await run(bytecode)
|
||||||
|
const pass = result.type === "null"
|
||||||
|
|
||||||
|
return {
|
||||||
|
pass,
|
||||||
|
message: () =>
|
||||||
|
pass
|
||||||
|
? `Expected value NOT to be null, but it was`
|
||||||
|
: `Expected value to be null, but received ${formatValue(result)}`,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async toBeBoolean(received: string | any[] | Bytecode, expected: boolean) {
|
||||||
|
const bytecode = typeof received === "string" || Array.isArray(received) ? toBytecode(received) : received
|
||||||
|
const result = await run(bytecode)
|
||||||
|
const pass = result.type === "boolean" && result.value === expected
|
||||||
|
|
||||||
|
return {
|
||||||
|
pass,
|
||||||
|
message: () =>
|
||||||
|
pass
|
||||||
|
? `Expected value NOT to be boolean ${expected}, but it was`
|
||||||
|
: `Expected value to be boolean ${expected}, but received ${formatValue(result)}`,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async toBeNumber(received: string | any[] | Bytecode, expected: number) {
|
||||||
|
const bytecode = typeof received === "string" || Array.isArray(received) ? toBytecode(received) : received
|
||||||
|
const result = await run(bytecode)
|
||||||
|
const pass = result.type === "number" && result.value === expected
|
||||||
|
|
||||||
|
return {
|
||||||
|
pass,
|
||||||
|
message: () =>
|
||||||
|
pass
|
||||||
|
? `Expected value NOT to be number ${expected}, but it was`
|
||||||
|
: `Expected value to be number ${expected}, but received ${formatValue(result)}`,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async toBeString(received: string | any[] | Bytecode, expected: string) {
|
||||||
|
const bytecode = typeof received === "string" || Array.isArray(received) ? toBytecode(received) : received
|
||||||
|
const result = await run(bytecode)
|
||||||
|
const pass = result.type === "string" && result.value === expected
|
||||||
|
|
||||||
|
return {
|
||||||
|
pass,
|
||||||
|
message: () =>
|
||||||
|
pass
|
||||||
|
? `Expected value NOT to be string "${expected}", but it was`
|
||||||
|
: `Expected value to be string "${expected}", but received ${formatValue(result)}`,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async toBeArray(received: string | any[] | Bytecode, expected: any[]) {
|
||||||
|
const bytecode = typeof received === "string" || Array.isArray(received) ? toBytecode(received) : received
|
||||||
|
const result = await run(bytecode)
|
||||||
|
const expectedValue = toValue(expected)
|
||||||
|
const pass = result.type === "array" && isEqual(result, expectedValue)
|
||||||
|
|
||||||
|
return {
|
||||||
|
pass,
|
||||||
|
message: () =>
|
||||||
|
pass
|
||||||
|
? `Expected value NOT to be array ${formatValue(expectedValue)}, but it was`
|
||||||
|
: `Expected value to be array ${formatValue(expectedValue)}, but received ${formatValue(result)}`,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async toBeDict(received: string | any[] | Bytecode, expected: Record<string, any>) {
|
||||||
|
const bytecode = typeof received === "string" || Array.isArray(received) ? toBytecode(received) : received
|
||||||
|
const result = await run(bytecode)
|
||||||
|
const expectedValue = toValue(expected)
|
||||||
|
const pass = result.type === "dict" && isEqual(result, expectedValue)
|
||||||
|
|
||||||
|
return {
|
||||||
|
pass,
|
||||||
|
message: () =>
|
||||||
|
pass
|
||||||
|
? `Expected value NOT to be dict ${formatValue(expectedValue)}, but it was`
|
||||||
|
: `Expected value to be dict ${formatValue(expectedValue)}, but received ${formatValue(result)}`,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async toBeFunction(received: string | any[] | Bytecode) {
|
||||||
|
const bytecode = typeof received === "string" || Array.isArray(received) ? toBytecode(received) : received
|
||||||
|
const result = await run(bytecode)
|
||||||
|
const pass = result.type === "function" || result.type === "native"
|
||||||
|
|
||||||
|
return {
|
||||||
|
pass,
|
||||||
|
message: () =>
|
||||||
|
pass
|
||||||
|
? `Expected value NOT to be a function, but it was`
|
||||||
|
: `Expected value to be a function, but received ${formatValue(result)}`,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async toBeTruthy(received: string | any[] | Bytecode) {
|
||||||
|
const bytecode = typeof received === "string" || Array.isArray(received) ? toBytecode(received) : received
|
||||||
|
const result = await run(bytecode)
|
||||||
|
// ReefVM semantics: only null and false are falsy
|
||||||
|
const pass = !(result.type === "null" || (result.type === "boolean" && !result.value))
|
||||||
|
|
||||||
|
return {
|
||||||
|
pass,
|
||||||
|
message: () =>
|
||||||
|
pass
|
||||||
|
? `Expected value NOT to be truthy, but it was: ${formatValue(result)}`
|
||||||
|
: `Expected value to be truthy, but received ${formatValue(result)}`,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async toBeFalsy(received: string | any[] | Bytecode) {
|
||||||
|
const bytecode = typeof received === "string" || Array.isArray(received) ? toBytecode(received) : received
|
||||||
|
const result = await run(bytecode)
|
||||||
|
// ReefVM semantics: only null and false are falsy
|
||||||
|
const pass = result.type === "null" || (result.type === "boolean" && !result.value)
|
||||||
|
|
||||||
|
return {
|
||||||
|
pass,
|
||||||
|
message: () =>
|
||||||
|
pass
|
||||||
|
? `Expected value NOT to be falsy, but it was: ${formatValue(result)}`
|
||||||
|
: `Expected value to be falsy, but received ${formatValue(result)}`,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
function formatValue(value: Value): string {
|
||||||
|
switch (value.type) {
|
||||||
|
case "null":
|
||||||
|
return "null"
|
||||||
|
case "boolean":
|
||||||
|
case "number":
|
||||||
|
return String(value.value)
|
||||||
|
case "string":
|
||||||
|
return `"${value.value}"`
|
||||||
|
case "array":
|
||||||
|
return `[${value.value.map(formatValue).join(", ")}]`
|
||||||
|
case "dict": {
|
||||||
|
const entries = Array.from(value.value.entries())
|
||||||
|
.map(([k, v]) => `${k}: ${formatValue(v)}`)
|
||||||
|
.join(", ")
|
||||||
|
return `{${entries}}`
|
||||||
|
}
|
||||||
|
case "regex":
|
||||||
|
return String(value.value)
|
||||||
|
case "function":
|
||||||
|
case "native":
|
||||||
|
return "<function>"
|
||||||
|
default:
|
||||||
|
return String(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user