1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
|
--- @class test.assert
local M = {}
local FORMAT_DEPTH = 100
--- @param value any
--- @return string
local function fmt(value)
if type(value) == 'string' then
return string.format('%q', value)
end
local ok, inspected = pcall(vim.inspect, value, { depth = FORMAT_DEPTH })
if ok then
return inspected
end
return tostring(value)
end
--- @param condition boolean
--- @param value any
--- @param context any
--- @param message string
local function assert_value(condition, value, context, message)
if not condition then
error((context ~= nil and tostring(context) .. ': ' or '') .. message, 0)
end
return value
end
--- @param expected any
--- @param actual any
--- @param comparator string
--- @return string
local function comparison_message(expected, actual, comparator)
return ('Expected values to be %s.\nExpected:\n%s\nActual:\n%s'):format(
comparator,
fmt(expected),
fmt(actual)
)
end
--- @param expected any
--- @param actual any
--- @param context? any
--- @return any
function M.eq(expected, actual, context)
return assert_value(
vim.deep_equal(expected, actual),
actual,
context,
comparison_message(expected, actual, 'equal')
)
end
--- Checks the supplied fields recursively, ignoring other record fields.
--- Lists must have the same length and order; their records may have extra fields.
--- Does not check the non-existence of a field.
---
--- Examples:
--- ```lua
--- local result = { id = 1, opts = { enabled = false, priority = 10 } }
--- eq_partial({ opts = { enabled = false } }, result) -- Passes.
--- eq(nil, result.missing) -- Check that a field is absent.
---
--- eq_partial({ { id = 1 } }, { { id = 1, name = 'test' } }) -- Passes.
--- eq_partial({ { id = 1 } }, { { id = 1 }, { id = 2 } }) -- Fails: extra list item.
--- ```
---
--- @param expected any
--- @param actual any
--- @param context? any
--- @return any
function M.eq_partial(expected, actual, context)
local seen = {} --- @type table<table, table<table, boolean>>
local prefix = context ~= nil and tostring(context) .. ': ' or ''
--- @param want any
--- @param got any
--- @param path string
local function compare(want, got, path)
if type(want) ~= 'table' or type(got) ~= 'table' or next(want) == nil then
M.eq(want, got, prefix .. path)
return
end
if vim.islist(want) then
assert_value(vim.islist(got), got, prefix .. path, 'Expected a list.\nActual:\n' .. fmt(got))
M.eq(#want, #got, prefix .. path .. ' (length)')
end
if seen[want] and seen[want][got] then
return
end
seen[want] = seen[want] or {}
seen[want][got] = true
for key, value in pairs(want) do
local field = type(key) == 'string' and key:match('^[%a_][%w_]*$') and '.' .. key
or '[' .. fmt(key) .. ']'
compare(value, got[key], path .. field)
end
end
compare(expected, actual, 'actual')
return actual
end
--- @param expected any
--- @param actual any
--- @param context? any
--- @return any
function M.neq(expected, actual, context)
return assert_value(
not vim.deep_equal(expected, actual),
actual,
context,
('Expected values to differ.\nValue:\n%s'):format(fmt(actual))
)
end
return setmetatable(M, {
--- @param condition any
--- @param message? string
--- @param level? integer
__call = function(_, condition, message, level, ...)
if condition then
return condition, message, level, ...
end
error(message or 'assertion failed!', (type(level) == 'number' and level or 1) + 1)
end,
})
|