@tishlang/tish-format 3.6.0 → 3.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/Cargo.toml +1 -0
  2. package/bin/tish-format +0 -0
  3. package/crates/tish/Cargo.toml +4 -1
  4. package/crates/tish/src/cli_help.rs +84 -0
  5. package/crates/tish/src/main.rs +93 -1
  6. package/crates/tish/tests/test_runner.rs +673 -0
  7. package/crates/tish_bytecode/src/compiler.rs +25 -4
  8. package/crates/tish_compile/src/resolve.rs +45 -12
  9. package/crates/tish_core/src/lib.rs +103 -11
  10. package/crates/tish_eval/src/eval.rs +16 -0
  11. package/crates/tish_eval/src/value_convert.rs +10 -0
  12. package/crates/tish_fmt/Cargo.toml +1 -1
  13. package/crates/tish_test/Cargo.toml +47 -0
  14. package/crates/tish_test/src/assert.rs +435 -0
  15. package/crates/tish_test/src/config.rs +85 -0
  16. package/crates/tish_test/src/coverage.rs +237 -0
  17. package/crates/tish_test/src/deep_equal.rs +261 -0
  18. package/crates/tish_test/src/discovery.rs +70 -0
  19. package/crates/tish_test/src/expect.rs +977 -0
  20. package/crates/tish_test/src/instrument.rs +457 -0
  21. package/crates/tish_test/src/isolation.rs +14 -0
  22. package/crates/tish_test/src/lib.rs +22 -0
  23. package/crates/tish_test/src/load.rs +152 -0
  24. package/crates/tish_test/src/mocks.rs +270 -0
  25. package/crates/tish_test/src/module.rs +318 -0
  26. package/crates/tish_test/src/registry.rs +235 -0
  27. package/crates/tish_test/src/report.rs +312 -0
  28. package/crates/tish_test/src/runner.rs +797 -0
  29. package/crates/tish_test/src/snapshots.rs +197 -0
  30. package/crates/tish_vm/src/vm.rs +17 -14
  31. package/package.json +1 -1
  32. package/platform/darwin-arm64/tish-fmt +0 -0
  33. package/platform/darwin-x64/tish-fmt +0 -0
  34. package/platform/linux-arm64/tish-fmt +0 -0
  35. package/platform/linux-x64/tish-fmt +0 -0
  36. package/platform/win32-x64/tish-fmt.exe +0 -0
@@ -0,0 +1,270 @@
1
+ //! `mock` / `spyOn` with call tracking.
2
+
3
+ use tishlang_core::{value_call, Arc, ObjectMap, Value, VmRef};
4
+
5
+ use std::cell::RefCell;
6
+ use std::sync::atomic::{AtomicUsize, Ordering};
7
+
8
+ thread_local! {
9
+ static MOCKS: RefCell<Vec<MockHandle>> = const { RefCell::new(Vec::new()) };
10
+ }
11
+
12
+ struct MockHandle {
13
+ meta: Value,
14
+ restore: Option<(Value, String, Value)>,
15
+ }
16
+
17
+ static MOCK_SEQ: AtomicUsize = AtomicUsize::new(1);
18
+
19
+ fn next_mock_id() -> usize {
20
+ MOCK_SEQ.fetch_add(1, Ordering::SeqCst)
21
+ }
22
+
23
+ fn empty_calls_array() -> Value {
24
+ Value::Array(VmRef::new(Vec::new()))
25
+ }
26
+
27
+ fn bump_calls(meta: &Value, args: &[Value]) {
28
+ let Value::Object(o) = meta else {
29
+ return;
30
+ };
31
+ let mut b = o.borrow_mut();
32
+ if !b.strings.contains_key("calls") {
33
+ b.strings.insert(Arc::from("calls"), empty_calls_array());
34
+ }
35
+ if let Some(Value::Array(arr)) = b.strings.get("calls") {
36
+ arr.borrow_mut()
37
+ .push(Value::Array(VmRef::new(args.to_vec())));
38
+ }
39
+ let count = match b.strings.get("calls") {
40
+ Some(Value::Array(a)) => a.borrow().len() as f64,
41
+ _ => 0.0,
42
+ };
43
+ b.strings
44
+ .insert(Arc::from("callCount"), Value::Number(count));
45
+ }
46
+
47
+ fn make_mock_meta(impl_fn: Value) -> Value {
48
+ let id = next_mock_id();
49
+ let mut m = ObjectMap::default();
50
+ m.insert(Arc::from("mockId"), Value::Number(id as f64));
51
+ m.insert(Arc::from("calls"), empty_calls_array());
52
+ m.insert(Arc::from("callCount"), Value::Number(0.0));
53
+ m.insert(Arc::from("implementation"), impl_fn.clone());
54
+ let meta = Value::object(m);
55
+ if let Value::Object(o) = &meta {
56
+ let o_clear = o.clone();
57
+ let o_reset = o.clone();
58
+ let impl_reset = impl_fn;
59
+ o.borrow_mut().strings.insert(
60
+ Arc::from("mockClear"),
61
+ Value::native(move |_args: &[Value]| {
62
+ let mut b = o_clear.borrow_mut();
63
+ b.strings.insert(Arc::from("calls"), empty_calls_array());
64
+ b.strings.insert(Arc::from("callCount"), Value::Number(0.0));
65
+ Value::Null
66
+ }),
67
+ );
68
+ o.borrow_mut().strings.insert(
69
+ Arc::from("mockReset"),
70
+ Value::native(move |_args: &[Value]| {
71
+ let mut b = o_reset.borrow_mut();
72
+ b.strings.insert(Arc::from("calls"), empty_calls_array());
73
+ b.strings.insert(Arc::from("callCount"), Value::Number(0.0));
74
+ b.strings
75
+ .insert(Arc::from("implementation"), impl_reset.clone());
76
+ Value::Null
77
+ }),
78
+ );
79
+ }
80
+ meta
81
+ }
82
+
83
+ fn wrap_mock_fn(meta: Value) -> Value {
84
+ let meta_call = meta.clone();
85
+ Value::native(move |args: &[Value]| {
86
+ bump_calls(&meta_call, args);
87
+ let impl_fn = match &meta_call {
88
+ Value::Object(o) => o
89
+ .borrow()
90
+ .strings
91
+ .get("implementation")
92
+ .cloned()
93
+ .unwrap_or(Value::Null),
94
+ _ => Value::Null,
95
+ };
96
+ value_call(&impl_fn, args)
97
+ })
98
+ }
99
+
100
+ /// Set `implementation` on a mock's metadata object.
101
+ fn set_implementation(meta: &Value, f: Value) {
102
+ if let Value::Object(o) = meta {
103
+ o.borrow_mut()
104
+ .strings
105
+ .insert(Arc::from("implementation"), f);
106
+ }
107
+ }
108
+
109
+ /// Jest/Bun put `mockClear` / `mockImplementation` / `mockReturnValue` on the mock function
110
+ /// itself, not on `.mock`. Attach both so either spelling works.
111
+ fn attach_mock_controls(outer: &mut ObjectMap, meta: &Value) {
112
+ if let Value::Object(o) = meta {
113
+ for name in ["mockClear", "mockReset"] {
114
+ let existing = o.borrow().strings.get(name).cloned();
115
+ if let Some(f) = existing {
116
+ outer.insert(Arc::from(name), f);
117
+ }
118
+ }
119
+ }
120
+ let m_impl = meta.clone();
121
+ outer.insert(
122
+ Arc::from("mockImplementation"),
123
+ Value::native(move |args: &[Value]| {
124
+ set_implementation(&m_impl, args.first().cloned().unwrap_or(Value::Null));
125
+ Value::Null
126
+ }),
127
+ );
128
+ let m_once = meta.clone();
129
+ outer.insert(
130
+ Arc::from("mockImplementationOnce"),
131
+ Value::native(move |args: &[Value]| {
132
+ set_implementation(&m_once, args.first().cloned().unwrap_or(Value::Null));
133
+ Value::Null
134
+ }),
135
+ );
136
+ let m_ret = meta.clone();
137
+ outer.insert(
138
+ Arc::from("mockReturnValue"),
139
+ Value::native(move |args: &[Value]| {
140
+ let v = args.first().cloned().unwrap_or(Value::Null);
141
+ set_implementation(&m_ret, Value::native(move |_a: &[Value]| v.clone()));
142
+ Value::Null
143
+ }),
144
+ );
145
+ let m_res = meta.clone();
146
+ outer.insert(
147
+ Arc::from("mockResolvedValue"),
148
+ Value::native(move |args: &[Value]| {
149
+ let v = args.first().cloned().unwrap_or(Value::Null);
150
+ // No promise construction here: a resolved value is what `await` yields anyway.
151
+ set_implementation(&m_res, Value::native(move |_a: &[Value]| v.clone()));
152
+ Value::Null
153
+ }),
154
+ );
155
+ }
156
+
157
+ /// `mock(fn?)` — callable mock object with `.mock.calls` / `.mockClear`.
158
+ pub fn mock(args: &[Value]) -> Value {
159
+ let impl_fn = args
160
+ .first()
161
+ .cloned()
162
+ .unwrap_or_else(|| Value::native(|_a: &[Value]| Value::Null));
163
+ let meta = make_mock_meta(impl_fn);
164
+ let call = wrap_mock_fn(meta.clone());
165
+ let mut outer = ObjectMap::default();
166
+ outer.insert(Arc::from("__call"), call);
167
+ outer.insert(Arc::from("mock"), meta.clone());
168
+ attach_mock_controls(&mut outer, &meta);
169
+ let obj = Value::object(outer);
170
+ MOCKS.with(|m| {
171
+ m.borrow_mut().push(MockHandle {
172
+ meta,
173
+ restore: None,
174
+ });
175
+ });
176
+ obj
177
+ }
178
+
179
+ /// `spyOn(obj, methodName)` — wraps a method; restore via `restoreAllMocks`.
180
+ pub fn spy_on(args: &[Value]) -> Value {
181
+ let target = args.first().cloned().unwrap_or(Value::Null);
182
+ let prop = args
183
+ .get(1)
184
+ .map(|v| v.to_display_string())
185
+ .unwrap_or_default();
186
+ let original = match &target {
187
+ Value::Object(o) => o
188
+ .borrow()
189
+ .strings
190
+ .get(prop.as_str())
191
+ .cloned()
192
+ .unwrap_or(Value::Null),
193
+ _ => Value::Null,
194
+ };
195
+ let meta = make_mock_meta(original.clone());
196
+ let call = wrap_mock_fn(meta.clone());
197
+ let mut outer = ObjectMap::default();
198
+ outer.insert(Arc::from("__call"), call);
199
+ outer.insert(Arc::from("mock"), meta.clone());
200
+ attach_mock_controls(&mut outer, &meta);
201
+ let spy = Value::object(outer);
202
+ if !matches!(target, Value::Object(_)) {
203
+ // Silently returning an unattached spy made the whole test a no-op.
204
+ tishlang_core::set_pending_throw(crate::assert::assertion_error(
205
+ format!(
206
+ "spyOn(target, {prop:?}): target is not an object ({})",
207
+ target.to_display_string()
208
+ ),
209
+ Some(target.clone()),
210
+ None,
211
+ "spyOn",
212
+ true,
213
+ ));
214
+ return Value::Null;
215
+ }
216
+ if let Value::Object(o) = &target {
217
+ o.borrow_mut()
218
+ .strings
219
+ .insert(Arc::from(prop.as_str()), spy.clone());
220
+ }
221
+ MOCKS.with(|m| {
222
+ m.borrow_mut().push(MockHandle {
223
+ meta,
224
+ restore: Some((target, prop, original)),
225
+ });
226
+ });
227
+ spy
228
+ }
229
+
230
+ pub fn clear_all_mocks(_args: &[Value]) -> Value {
231
+ MOCKS.with(|m| {
232
+ for h in m.borrow().iter() {
233
+ if let Value::Object(o) = &h.meta {
234
+ let mut b = o.borrow_mut();
235
+ b.strings.insert(Arc::from("calls"), empty_calls_array());
236
+ b.strings.insert(Arc::from("callCount"), Value::Number(0.0));
237
+ }
238
+ }
239
+ });
240
+ Value::Null
241
+ }
242
+
243
+ pub fn restore_all_mocks(_args: &[Value]) -> Value {
244
+ MOCKS.with(|m| {
245
+ let mut list = m.borrow_mut();
246
+ // Restore newest-first: spying twice on one method must land back on the ORIGINAL,
247
+ // not on the first spy.
248
+ for h in list.drain(..).rev() {
249
+ if let Some((Value::Object(o), prop, original)) = h.restore {
250
+ o.borrow_mut()
251
+ .strings
252
+ .insert(Arc::from(prop.as_str()), original);
253
+ }
254
+ }
255
+ });
256
+ Value::Null
257
+ }
258
+
259
+ /// Drop all mock/spy handles between test files (restore spies first).
260
+ pub fn reset_mock_registry() {
261
+ let _ = restore_all_mocks(&[]);
262
+ }
263
+
264
+ pub fn mock_native(args: &[Value]) -> Value {
265
+ mock(args)
266
+ }
267
+
268
+ pub fn spy_on_native(args: &[Value]) -> Value {
269
+ spy_on(args)
270
+ }
@@ -0,0 +1,318 @@
1
+ //! `tish:test` and `tish:assert` module export maps.
2
+
3
+ use tishlang_core::{value_call, Arc, ObjectMap, Value};
4
+
5
+ use crate::assert::assert_object;
6
+ use crate::expect::expect_object;
7
+ use crate::mocks::{clear_all_mocks, mock, restore_all_mocks, spy_on};
8
+ use crate::registry::{add_hook, add_test, push_describe, TestMode};
9
+
10
+ fn name_and_fn(args: &[Value]) -> (String, Value) {
11
+ match args.len() {
12
+ 0 => (String::new(), Value::Null),
13
+ 1 => {
14
+ if matches!(args[0], Value::Function(_) | Value::Object(_)) {
15
+ ("<anonymous>".into(), args[0].clone())
16
+ } else {
17
+ (args[0].to_display_string(), Value::Null)
18
+ }
19
+ }
20
+ _ => {
21
+ let name = args[0].to_display_string();
22
+ let body = args.last().cloned().unwrap_or(Value::Null);
23
+ (name, body)
24
+ }
25
+ }
26
+ }
27
+
28
+ fn opts_object(args: &[Value]) -> Option<Value> {
29
+ if args.len() >= 3 {
30
+ if let Value::Object(_) = &args[1] {
31
+ return Some(args[1].clone());
32
+ }
33
+ }
34
+ None
35
+ }
36
+
37
+ fn timeout_from_args(args: &[Value]) -> Option<u64> {
38
+ let v = opts_object(args)?;
39
+ let Value::Object(o) = v else {
40
+ return None;
41
+ };
42
+ let n = {
43
+ let b = o.borrow();
44
+ b.strings
45
+ .get("timeout")
46
+ .and_then(|v| v.as_number())
47
+ .filter(|n| n.is_finite() && *n >= 0.0)
48
+ }?;
49
+ Some(n as u64)
50
+ }
51
+
52
+ fn tags_from_args(args: &[Value]) -> Vec<String> {
53
+ let Some(Value::Object(o)) = opts_object(args) else {
54
+ return Vec::new();
55
+ };
56
+ let b = o.borrow();
57
+ match b.strings.get("tags") {
58
+ Some(Value::Array(a)) => a.borrow().iter().map(|v| v.to_display_string()).collect(),
59
+ Some(Value::String(s)) => vec![s.to_string()],
60
+ _ => Vec::new(),
61
+ }
62
+ }
63
+
64
+ fn is_truthy(v: &Value) -> bool {
65
+ match v {
66
+ Value::Null => false,
67
+ Value::Bool(b) => *b,
68
+ Value::Number(n) => *n != 0.0 && !n.is_nan(),
69
+ Value::String(s) => !s.is_empty(),
70
+ _ => true,
71
+ }
72
+ }
73
+
74
+ fn row_args(row: &Value) -> Vec<Value> {
75
+ match row {
76
+ Value::Array(a) => a.borrow().clone(),
77
+ Value::NumberArray(a) => a.borrow().to_values(),
78
+ other => vec![other.clone()],
79
+ }
80
+ }
81
+
82
+ fn format_each_name(template: &str, row: &Value, index: usize) -> String {
83
+ if !template.contains('%') {
84
+ return format!("{template} [{index}]");
85
+ }
86
+ let args = row_args(row);
87
+ let mut out = String::new();
88
+ let mut chars = template.chars().peekable();
89
+ let mut ai = 0usize;
90
+ while let Some(c) = chars.next() {
91
+ if c == '%' {
92
+ match chars.peek().copied() {
93
+ Some('#') => {
94
+ chars.next();
95
+ out.push_str(&index.to_string());
96
+ }
97
+ Some('s') | Some('i') | Some('d') | Some('p') | Some('j') => {
98
+ chars.next();
99
+ let v = args.get(ai).cloned().unwrap_or(Value::Null);
100
+ ai += 1;
101
+ out.push_str(&v.to_display_string());
102
+ }
103
+ Some('%') => {
104
+ chars.next();
105
+ out.push('%');
106
+ }
107
+ _ => out.push('%'),
108
+ }
109
+ } else {
110
+ out.push(c);
111
+ }
112
+ }
113
+ out
114
+ }
115
+
116
+ fn make_test_call(mode: TestMode) -> Value {
117
+ Value::native(move |args: &[Value]| {
118
+ let (name, body) = name_and_fn(args);
119
+ let timeout = timeout_from_args(args);
120
+ let tags = tags_from_args(args);
121
+ // Parallel `test.concurrent` is deferred (single-threaded VM); `{ concurrent: true }`
122
+ // is accepted and ignored so Bun/Jest suites port without edits.
123
+ add_test(&name, mode, body, timeout, tags);
124
+ Value::Null
125
+ })
126
+ }
127
+
128
+ fn make_each_call(mode: TestMode) -> Value {
129
+ Value::native(move |args: &[Value]| {
130
+ let table = args.first().cloned().unwrap_or(Value::Null);
131
+ Value::native(move |inner: &[Value]| {
132
+ let (name_tmpl, body) = name_and_fn(inner);
133
+ let timeout = timeout_from_args(inner);
134
+ let rows: Vec<Value> = match &table {
135
+ Value::Array(a) => a.borrow().clone(),
136
+ Value::NumberArray(a) => a.borrow().to_values(),
137
+ other => vec![other.clone()],
138
+ };
139
+ for (i, row) in rows.into_iter().enumerate() {
140
+ let name = format_each_name(&name_tmpl, &row, i);
141
+ let body = body.clone();
142
+ let row_for_call = row;
143
+ let wrapped = Value::native(move |_a: &[Value]| {
144
+ let call_args = row_args(&row_for_call);
145
+ value_call(&body, &call_args)
146
+ });
147
+ add_test(&name, mode, wrapped, timeout, Vec::new());
148
+ }
149
+ Value::Null
150
+ })
151
+ })
152
+ }
153
+
154
+ fn attach_each(target: &mut ObjectMap, mode: TestMode) {
155
+ target.insert(Arc::from("each"), make_each_call(mode));
156
+ }
157
+
158
+ fn test_object() -> Value {
159
+ let mut m = ObjectMap::default();
160
+ m.insert(Arc::from("__call"), make_test_call(TestMode::Normal));
161
+ attach_each(&mut m, TestMode::Normal);
162
+ m.insert(Arc::from("skipIf"), {
163
+ Value::native(|args: &[Value]| {
164
+ let skip = args.first().map(is_truthy).unwrap_or(false);
165
+ make_test_call(if skip {
166
+ TestMode::Skip
167
+ } else {
168
+ TestMode::Normal
169
+ })
170
+ })
171
+ });
172
+ m.insert(Arc::from("skip"), {
173
+ let mut o = ObjectMap::default();
174
+ o.insert(Arc::from("__call"), make_test_call(TestMode::Skip));
175
+ attach_each(&mut o, TestMode::Skip);
176
+ Value::object(o)
177
+ });
178
+ m.insert(Arc::from("only"), {
179
+ let mut o = ObjectMap::default();
180
+ o.insert(Arc::from("__call"), make_test_call(TestMode::Only));
181
+ attach_each(&mut o, TestMode::Only);
182
+ Value::object(o)
183
+ });
184
+ m.insert(Arc::from("todo"), {
185
+ let mut o = ObjectMap::default();
186
+ o.insert(
187
+ Arc::from("__call"),
188
+ Value::native(|args: &[Value]| {
189
+ let name = args
190
+ .first()
191
+ .map(|v| v.to_display_string())
192
+ .unwrap_or_else(|| "<todo>".into());
193
+ add_test(&name, TestMode::Todo, Value::Null, None, Vec::new());
194
+ Value::Null
195
+ }),
196
+ );
197
+ Value::object(o)
198
+ });
199
+ m.insert(Arc::from("failing"), {
200
+ let mut o = ObjectMap::default();
201
+ o.insert(Arc::from("__call"), make_test_call(TestMode::Failing));
202
+ attach_each(&mut o, TestMode::Failing);
203
+ Value::object(o)
204
+ });
205
+ Value::object(m)
206
+ }
207
+
208
+ fn make_describe_call(mode: TestMode) -> Value {
209
+ Value::native(move |args: &[Value]| {
210
+ let name = args
211
+ .first()
212
+ .map(|v| v.to_display_string())
213
+ .unwrap_or_default();
214
+ let body = args.get(1).cloned().unwrap_or(Value::Null);
215
+ push_describe(&name, mode, &body);
216
+ Value::Null
217
+ })
218
+ }
219
+
220
+ fn describe_object() -> Value {
221
+ let mut m = ObjectMap::default();
222
+ m.insert(Arc::from("__call"), make_describe_call(TestMode::Normal));
223
+ m.insert(Arc::from("skip"), {
224
+ let mut o = ObjectMap::default();
225
+ o.insert(Arc::from("__call"), make_describe_call(TestMode::Skip));
226
+ Value::object(o)
227
+ });
228
+ m.insert(Arc::from("only"), {
229
+ let mut o = ObjectMap::default();
230
+ o.insert(Arc::from("__call"), make_describe_call(TestMode::Only));
231
+ Value::object(o)
232
+ });
233
+ // `describe.each` — table-driven suites (same table shape as `test.each`).
234
+ m.insert(
235
+ Arc::from("each"),
236
+ Value::native(move |args: &[Value]| {
237
+ let table = args.first().cloned().unwrap_or(Value::Null);
238
+ Value::native(move |inner: &[Value]| {
239
+ let name_tmpl = inner
240
+ .first()
241
+ .map(|v| v.to_display_string())
242
+ .unwrap_or_default();
243
+ let body = inner.get(1).cloned().unwrap_or(Value::Null);
244
+ let rows: Vec<Value> = match &table {
245
+ Value::Array(a) => a.borrow().clone(),
246
+ Value::NumberArray(a) => a.borrow().to_values(),
247
+ other => vec![other.clone()],
248
+ };
249
+ for (i, row) in rows.into_iter().enumerate() {
250
+ let name = format_each_name(&name_tmpl, &row, i);
251
+ let body = body.clone();
252
+ let row_for_call = row;
253
+ let wrapped = Value::native(move |_a: &[Value]| {
254
+ let call_args = row_args(&row_for_call);
255
+ value_call(&body, &call_args)
256
+ });
257
+ push_describe(&name, TestMode::Normal, &wrapped);
258
+ }
259
+ Value::Null
260
+ })
261
+ }),
262
+ );
263
+ Value::object(m)
264
+ }
265
+
266
+ fn hook(kind: &'static str) -> Value {
267
+ Value::native(move |args: &[Value]| {
268
+ let body = args.first().cloned().unwrap_or(Value::Null);
269
+ add_hook(kind, body);
270
+ Value::Null
271
+ })
272
+ }
273
+
274
+ /// Named exports for `import { … } from "tish:test"`.
275
+ pub fn test_module() -> ObjectMap {
276
+ let test = test_object();
277
+ let describe = describe_object();
278
+ let expect = expect_object();
279
+
280
+ let mut m = ObjectMap::default();
281
+ m.insert(Arc::from("describe"), describe.clone());
282
+ m.insert(Arc::from("suite"), describe);
283
+ m.insert(Arc::from("test"), test.clone());
284
+ m.insert(Arc::from("it"), test);
285
+ m.insert(Arc::from("expect"), expect);
286
+ m.insert(Arc::from("beforeAll"), hook("beforeAll"));
287
+ m.insert(Arc::from("afterAll"), hook("afterAll"));
288
+ m.insert(Arc::from("beforeEach"), hook("beforeEach"));
289
+ m.insert(Arc::from("afterEach"), hook("afterEach"));
290
+ m.insert(Arc::from("before"), hook("before"));
291
+ m.insert(Arc::from("after"), hook("after"));
292
+ m.insert(Arc::from("mock"), Value::native(mock));
293
+ m.insert(Arc::from("spyOn"), Value::native(spy_on));
294
+ m.insert(Arc::from("clearAllMocks"), Value::native(clear_all_mocks));
295
+ m.insert(
296
+ Arc::from("restoreAllMocks"),
297
+ Value::native(restore_all_mocks),
298
+ );
299
+ let default_test = m.get("test").cloned().unwrap_or(Value::Null);
300
+ m.insert(Arc::from("default"), default_test);
301
+ m
302
+ }
303
+
304
+ /// Named + default exports for `tish:assert` / `node:assert`.
305
+ pub fn assert_module() -> ObjectMap {
306
+ let assert = assert_object();
307
+ let mut m = ObjectMap::default();
308
+ if let Value::Object(o) = &assert {
309
+ for (k, v) in o.borrow().strings.iter() {
310
+ if k.as_ref() != "__call" {
311
+ m.insert(Arc::clone(k), v.clone());
312
+ }
313
+ }
314
+ }
315
+ m.insert(Arc::from("assert"), assert.clone());
316
+ m.insert(Arc::from("default"), assert);
317
+ m
318
+ }