1use crate::host::{
5 call_method, invoke, is_callable, reject_promise_val, resolve_promise_val, subscribe_native,
6 take_exc_or_error, with_host, JsObj, PromiseState,
7};
8use fusevm::Value;
9
10pub const METHODS: &[&str] = &[
11 "ok",
12 "equal",
13 "notEqual",
14 "strictEqual",
15 "notStrictEqual",
16 "deepEqual",
17 "notDeepEqual",
18 "deepStrictEqual",
19 "notDeepStrictEqual",
20 "throws",
21 "doesNotThrow",
22 "fail",
23 "match",
24 "doesNotMatch",
25 "ifError",
26 "partialDeepStrictEqual",
27 "rejects",
28 "doesNotReject",
29];
30
31pub fn call(method: &str, args: &[Value]) -> Option<Result<Value, String>> {
32 let a = || args.first().cloned().unwrap_or(Value::Undef);
33 let b = || args.get(1).cloned().unwrap_or(Value::Undef);
34 Some(match method {
35 "ok" => assert_ok(args),
36 "equal" => check(loose_eq(&a(), &b()), args, 2, "==", &a(), &b()),
37 "notEqual" => check(!loose_eq(&a(), &b()), args, 2, "!=", &a(), &b()),
38 "strictEqual" => check(strict(&a(), &b()), args, 2, "===", &a(), &b()),
39 "notStrictEqual" => check(!strict(&a(), &b()), args, 2, "!==", &a(), &b()),
40 "deepEqual" => check(
41 deep_equal(&a(), &b(), false),
42 args,
43 2,
44 "deepEqual",
45 &a(),
46 &b(),
47 ),
48 "notDeepEqual" => check(
49 !deep_equal(&a(), &b(), false),
50 args,
51 2,
52 "notDeepEqual",
53 &a(),
54 &b(),
55 ),
56 "deepStrictEqual" => check(
57 deep_equal(&a(), &b(), true),
58 args,
59 2,
60 "deepStrictEqual",
61 &a(),
62 &b(),
63 ),
64 "notDeepStrictEqual" => check(
65 !deep_equal(&a(), &b(), true),
66 args,
67 2,
68 "notDeepStrictEqual",
69 &a(),
70 &b(),
71 ),
72 "throws" => throws(args, true),
73 "doesNotThrow" => throws(args, false),
74 "fail" => Err(throw_assertion(
77 &message(args, 0).unwrap_or_else(|| "Failed".to_string()),
78 message(args, 0).is_none(),
79 "fail",
80 Value::Undef,
81 Value::Undef,
82 )),
83 "match" => assert_match(args, true),
84 "doesNotMatch" => assert_match(args, false),
85 "ifError" => if_error(&a()),
86 "partialDeepStrictEqual" => partial(&a(), &b(), args),
87 "rejects" => Ok(rejects_impl(args, true)),
88 "doesNotReject" => Ok(rejects_impl(args, false)),
89 _ => return None,
90 })
91}
92
93pub fn strict_call(method: &str, args: &[Value]) -> Option<Result<Value, String>> {
96 let mapped = match method {
97 "equal" => "strictEqual",
98 "notEqual" => "notStrictEqual",
99 "deepEqual" => "deepStrictEqual",
100 "notDeepEqual" => "notDeepStrictEqual",
101 other => other,
102 };
103 call(mapped, args)
104}
105
106fn assert_match(args: &[Value], want_match: bool) -> Result<Value, String> {
109 let s = args.first().cloned().unwrap_or(Value::Undef);
110 let re = args.get(1).cloned().unwrap_or(Value::Undef);
111 if !is_regexp(&re) {
112 return Err(crate::host::coded_error(
114 "TypeError",
115 "ERR_INVALID_ARG_TYPE",
116 &format!(
117 "The \"regexp\" argument must be an instance of RegExp. Received {}",
118 crate::stdlib::received_desc(&re)
119 ),
120 ));
121 }
122 let is_string = with_host(|h| h.type_of(&s) == "string");
125 if is_string && regexp_matches(&re, &s)? == want_match {
126 return Ok(Value::Undef);
127 }
128 let operator = if want_match { "match" } else { "doesNotMatch" };
129 let custom = message(args, 2);
130 let generated = custom.is_none();
131 let msg = custom.unwrap_or_else(|| {
132 with_host(|h| {
133 if !is_string {
134 return format!(
135 "The \"string\" argument must be of type string. Received type {} ({})",
136 h.type_of(&s),
137 h.inspect(&s)
138 );
139 }
140 let verb = if want_match {
141 "The input did not match the regular expression"
142 } else {
143 "The input was expected to not match the regular expression"
144 };
145 format!("{verb} {}. Input:\n\n{}\n", h.inspect(&re), h.inspect(&s))
146 })
147 });
148 Err(throw_assertion(&msg, generated, operator, s, re))
149}
150
151fn is_regexp(v: &Value) -> bool {
152 with_host(|h| matches!(h.get(v), Some(JsObj::RegExp(_))))
153}
154
155fn regexp_matches(re: &Value, s: &Value) -> Result<bool, String> {
157 let m = call_method(re, "exec", vec![s.clone()])?;
158 Ok(!with_host(|h| h.is_null(&m)) && !matches!(m, Value::Undef))
159}
160
161fn if_error(v: &Value) -> Result<Value, String> {
163 if with_host(|h| h.is_nullish(v)) {
164 return Ok(Value::Undef);
165 }
166 let desc = with_host(|h| match h.get(v) {
167 Some(JsObj::Object(p)) => p
168 .get("message")
169 .map(|m| h.str_of(m))
170 .unwrap_or_else(|| h.inspect(v)),
171 _ => h.inspect(v),
172 });
173 Err(assertion_error(&format!(
174 "ifError got unwanted exception: {desc}"
175 )))
176}
177
178fn partial(actual: &Value, expected: &Value, args: &[Value]) -> Result<Value, String> {
182 if partial_deep(actual, expected) {
183 return Ok(Value::Undef);
184 }
185 if let Some(m) = message(args, 2) {
186 return Err(assertion_error(&m));
187 }
188 let (sa, sb) = with_host(|h| (h.inspect(actual), h.inspect(expected)));
189 Err(assertion_error(&format!(
190 "Expected values to be strictly deep-equal (partial):\n{sb} should be a subset of {sa}"
191 )))
192}
193
194fn partial_deep(actual: &Value, expected: &Value) -> bool {
195 let ekind = with_host(|h| h.get(expected).map(kind));
196 match ekind {
197 Some(Kind::Object) => {
198 if !matches!(with_host(|h| h.get(actual).map(kind)), Some(Kind::Object)) {
199 return false;
200 }
201 let (ea, ee) = with_host(|h| (object_of(h, actual), object_of(h, expected)));
202 ee.iter().all(|(k, ve)| {
203 ea.iter()
204 .find(|(k2, _)| k2 == k)
205 .is_some_and(|(_, va)| partial_deep(va, ve))
206 })
207 }
208 Some(Kind::Array) => {
209 if !matches!(with_host(|h| h.get(actual).map(kind)), Some(Kind::Array)) {
210 return false;
211 }
212 let (ia, ie) = with_host(|h| (array_of(h, actual), array_of(h, expected)));
213 ie.len() <= ia.len() && ie.iter().zip(ia.iter()).all(|(e, a)| partial_deep(a, e))
214 }
215 _ => strict(actual, expected),
216 }
217}
218
219fn rejects_impl(args: &[Value], want_reject: bool) -> Value {
224 let input = args.first().cloned().unwrap_or(Value::Undef);
225 let rest: Vec<Value> = args.get(1..).unwrap_or(&[]).to_vec();
226 let result = with_host(|h| h.new_promise());
227 let rid = with_host(|h| h.promise_id(&result).unwrap());
228 let fail = |e: String| {
229 let ev = take_exc_or_error(&e);
230 reject_promise_val(rid, ev);
231 };
232 let operand = if with_host(|h| is_callable(h, &input)) {
236 match invoke(&input, Vec::new(), None) {
237 Ok(v) if is_promise_like(&v) => crate::host::promise_of(&v),
238 Ok(v) => {
239 fail(crate::host::coded_error(
240 "TypeError",
241 "ERR_INVALID_RETURN_VALUE",
242 &format!(
243 "Expected instance of Promise to be returned from the \"promiseFn\" function but got {}.",
244 crate::stdlib::received_desc(&v)
245 ),
246 ));
247 return result;
248 }
249 Err(e) => {
250 fail(e);
251 return result;
252 }
253 }
254 } else if is_promise_like(&input) {
255 crate::host::promise_of(&input)
256 } else {
257 fail(crate::host::coded_error(
258 "TypeError",
259 "ERR_INVALID_ARG_TYPE",
260 &format!(
261 "The \"promiseFn\" argument must be of type function or an instance of Promise. Received {}",
262 crate::stdlib::received_desc(&input)
263 ),
264 ));
265 return result;
266 };
267 let oid = with_host(|h| h.promise_id(&operand).unwrap());
268 subscribe_native(
269 oid,
270 Box::new(move |state, val| {
271 let actual = (state == PromiseState::Rejected).then_some(val);
272 let checked = if want_reject {
273 expects_error("rejects", "rejection", actual, &rest)
274 } else {
275 expects_no_error("doesNotReject", "rejection", actual, &rest)
276 };
277 match checked {
278 Ok(()) => resolve_promise_val(rid, Value::Undef),
279 Err(e) => {
280 let ev = take_exc_or_error(&e);
281 reject_promise_val(rid, ev);
282 }
283 }
284 Ok(())
285 }),
286 );
287 result
288}
289
290pub fn construct_assertion_error(args: &[Value]) -> Value {
294 let opts = args.first().cloned().unwrap_or(Value::Undef);
295 let (message, actual, expected, operator) = with_host(|h| match h.get(&opts) {
296 Some(JsObj::Object(p)) => (
297 p.get("message").map(|v| h.str_of(v)),
298 p.get("actual").cloned(),
299 p.get("expected").cloned(),
300 p.get("operator").map(|v| h.str_of(v)),
301 ),
302 _ => (None, None, None, None),
303 });
304 let generated = message.is_none();
305 let msg = message.unwrap_or_else(|| {
306 let (sa, se) = with_host(|h| {
307 (
308 actual.as_ref().map(|v| h.inspect(v)).unwrap_or_default(),
309 expected.as_ref().map(|v| h.inspect(v)).unwrap_or_default(),
310 )
311 });
312 let op = operator.clone().unwrap_or_else(|| "==".to_string());
313 format!("{sa} {op} {se}")
314 });
315 assertion_error_object(
316 &msg,
317 generated,
318 operator.as_deref(),
319 actual.unwrap_or(Value::Undef),
320 expected.unwrap_or(Value::Undef),
321 )
322}
323
324const DIFF_MODE: &str = "simple";
329
330fn assertion_error_object(
340 msg: &str,
341 generated: bool,
342 operator: Option<&str>,
343 actual: Value,
344 expected: Value,
345) -> Value {
346 let stack = format!("AssertionError [ERR_ASSERTION]: {msg}\n at <anonymous>");
347 let op_val = match operator {
348 Some(o) => with_host(|h| h.new_str(o)),
349 None => Value::Undef,
350 };
351 let name_v = with_host(|h| h.new_str("AssertionError"));
352 let msg_v = with_host(|h| h.new_str(msg));
353 let code_v = with_host(|h| h.new_str("ERR_ASSERTION"));
354 let stack_v = with_host(|h| h.new_str(stack));
355 let diff_v = with_host(|h| h.new_str(DIFF_MODE));
356 let mut props: indexmap::IndexMap<String, Value> = indexmap::IndexMap::new();
357 props.insert("generatedMessage".into(), Value::Bool(generated));
359 props.insert("code".into(), code_v);
360 props.insert("actual".into(), actual);
361 props.insert("expected".into(), expected);
362 props.insert("operator".into(), op_val);
363 props.insert("diff".into(), diff_v);
364 props.insert("name".into(), name_v);
365 props.insert("message".into(), msg_v);
366 props.insert("stack".into(), stack_v);
367 let obj = with_host(|h| h.new_object(props));
368 with_host(|h| {
369 for k in ["name", "message", "stack"] {
370 h.hide_prop(&obj, k);
371 }
372 h.ensure_error_protos();
373 if let Some(p) = crate::host::error_proto_of(h, "AssertionError") {
377 h.set_proto(&obj, p);
378 }
379 });
380 obj
381}
382
383fn throw_assertion(
390 msg: &str,
391 generated: bool,
392 operator: &str,
393 actual: Value,
394 expected: Value,
395) -> String {
396 let err = assertion_error_object(msg, generated, Some(operator), actual, expected);
397 with_host(|h| h.exc = Some(err));
398 assertion_error(msg)
399}
400
401pub fn assert_ok(args: &[Value]) -> Result<Value, String> {
403 let v = args.first().cloned().unwrap_or(Value::Undef);
404 if with_host(|h| h.truthy(&v)) {
405 return Ok(Value::Undef);
406 }
407 let custom = message(args, 1);
408 let msg = custom.clone().unwrap_or_else(||
409 "The expression evaluated to a falsy value:".to_string());
412 Err(throw_assertion(
416 &msg,
417 custom.is_none(),
418 "==",
419 v,
420 Value::Bool(true),
421 ))
422}
423
424fn check(
425 pass: bool,
426 args: &[Value],
427 msg_idx: usize,
428 op: &str,
429 a: &Value,
430 b: &Value,
431) -> Result<Value, String> {
432 if pass {
433 return Ok(Value::Undef);
434 }
435 let custom = message(args, msg_idx);
436 let diff_operator = match op {
442 "===" => Some("strictEqual"),
443 "deepStrictEqual" => Some("deepStrictEqual"),
444 "partialDeepStrictEqual" => Some("partialDeepStrictEqual"),
445 _ => None,
446 };
447 if let Some(diff_op) = diff_operator {
448 let msg = super::assert_diff::create_err_diff(a, b, diff_op, custom.as_deref());
449 let operator = if op == "===" { "strictEqual" } else { op };
450 return Err(throw_assertion(
451 &msg,
452 custom.is_none(),
453 operator,
454 a.clone(),
455 b.clone(),
456 ));
457 }
458 let (sa, sb) = (
463 super::assert_diff::inspect_operand(a),
464 super::assert_diff::inspect_operand(b),
465 );
466 let msg = match op {
471 "==" | "!=" => format!("{sa} {op} {sb}"),
472 "===" => format!("Expected values to be strictly equal:\n\n{sa} !== {sb}\n"),
473 "!==" => format!("Expected \"actual\" to be strictly unequal to: {sa}"),
474 "deepEqual" => format!(
475 "Expected values to be loosely deep-equal:\n\n{sa}\n\nshould loosely \
476 deep-equal\n\n{sb}"
477 ),
478 "notDeepEqual" => {
479 format!("Expected \"actual\" not to be loosely deep-equal to:\n\n{sa}")
480 }
481 "deepStrictEqual" => {
486 format!("Expected values to be strictly deep-equal:\n\n{sa} !== {sb}\n")
487 }
488 "notDeepStrictEqual" => {
489 format!("Expected \"actual\" not to be strictly deep-equal to:\n\n{sa}\n")
490 }
491 _ => format!("{sa} {op} {sb}"),
492 };
493 let operator = match op {
498 "===" => "strictEqual",
499 "!==" => "notStrictEqual",
500 other => other,
501 };
502 Err(throw_assertion(
503 &custom.clone().unwrap_or(msg),
504 custom.is_none(),
505 operator,
506 a.clone(),
507 b.clone(),
508 ))
509}
510
511fn throws(args: &[Value], want_throw: bool) -> Result<Value, String> {
516 let f = args.first().cloned().unwrap_or(Value::Undef);
517 if !with_host(|h| is_callable(h, &f)) {
518 return Err(crate::host::coded_error(
519 "TypeError",
520 "ERR_INVALID_ARG_TYPE",
521 &format!(
522 "The \"fn\" argument must be of type function. Received {}",
523 crate::stdlib::received_desc(&f)
524 ),
525 ));
526 }
527 let caught = match invoke(&f, Vec::new(), None) {
530 Ok(_) => None,
531 Err(e) => Some(crate::host::take_exc_or_error(&e)),
532 };
533 let rest = args.get(1..).unwrap_or(&[]);
534 if want_throw {
535 expects_error("throws", "exception", caught, rest)
536 } else {
537 expects_no_error("doesNotThrow", "exception", caught, rest)
538 }
539 .map(|_| Value::Undef)
540}
541
542fn expects_error(
545 operator: &str,
546 kind: &str,
547 actual: Option<Value>,
548 rest: &[Value],
549) -> Result<(), String> {
550 let mut error = rest.first().cloned().unwrap_or(Value::Undef);
551 let mut msg = message(rest, 1);
552 let type_of = |v: &Value| with_host(|h| h.type_of(v));
553 if type_of(&error) == "string" {
554 if rest.len() >= 2 {
555 return Err(invalid_error_arg(&error));
556 }
557 let text = with_host(|h| h.str_of(&error));
558 if let Some(a) = &actual {
559 if is_object(a) {
560 let m = crate::builtins::get_property(a, "message")?;
561 if with_host(|h| h.type_of(&m) == "string" && h.str_of(&m) == text) {
562 return Err(ambiguous(&format!(
563 "The error message \"{text}\" is identical to the message."
564 )));
565 }
566 } else if with_host(|h| h.type_of(a) == "string" && h.str_of(a) == text) {
567 return Err(ambiguous(&format!(
568 "The error \"{text}\" is identical to the message."
569 )));
570 }
571 }
572 msg = Some(text);
573 error = Value::Undef;
574 } else if !with_host(|h| h.is_nullish(&error))
575 && !matches!(type_of(&error), "object" | "function")
576 {
577 return Err(invalid_error_arg(&error));
578 }
579 let Some(actual) = actual else {
580 let name = if with_host(|h| h.is_nullish(&error)) {
581 None
582 } else {
583 let n = crate::builtins::get_property(&error, "name")?;
584 with_host(|h| h.truthy(&n).then(|| h.str_of(&n)))
585 };
586 let mut details = name.map(|n| format!(" ({n})")).unwrap_or_default();
587 details.push_str(&msg.map(|m| format!(": {m}")).unwrap_or_else(|| ".".into()));
588 return Err(throw_assertion(
589 &format!("Missing expected {kind}{details}"),
590 false,
591 operator,
592 Value::Undef,
593 error,
594 ));
595 };
596 if !with_host(|h| h.truthy(&error)) {
597 return Ok(());
598 }
599 expected_exception(operator, actual, error, msg)
600}
601
602fn invalid_error_arg(error: &Value) -> String {
603 crate::host::coded_error(
604 "TypeError",
605 "ERR_INVALID_ARG_TYPE",
606 &format!(
607 "The \"error\" argument must be of type function or an instance of Error, RegExp, or Object. Received {}",
608 crate::stdlib::received_desc(error)
609 ),
610 )
611}
612
613fn ambiguous(detail: &str) -> String {
614 crate::host::coded_error(
615 "TypeError",
616 "ERR_AMBIGUOUS_ARGUMENT",
617 &format!("The \"error/message\" argument is ambiguous. {detail}"),
618 )
619}
620
621fn is_object(v: &Value) -> bool {
622 with_host(|h| h.type_of(v) == "object" && !h.is_null(v))
623}
624
625fn is_error(v: &Value) -> bool {
627 is_object(v) && with_host(|h| crate::builtins::object_tag(h, v) == "[object Error]")
628}
629
630fn expected_exception(
633 operator: &str,
634 actual: Value,
635 expected: Value,
636 msg: Option<String>,
637) -> Result<(), String> {
638 let generated = msg.is_none();
639 let fail = |text: String| -> Result<(), String> {
640 Err(throw_assertion(
641 &text,
642 generated,
643 operator,
644 actual.clone(),
645 expected.clone(),
646 ))
647 };
648 if with_host(|h| h.type_of(&expected)) != "function" {
649 if is_regexp(&expected) {
650 let s = crate::host::to_string_value(&actual)?;
651 if regexp_matches(&expected, &s)? {
652 return Ok(());
653 }
654 return fail(msg.unwrap_or_else(|| {
655 with_host(|h| {
656 format!(
657 "The input did not match the regular expression {}. Input:\n\n{}\n",
658 h.inspect(&expected),
659 h.inspect(&s)
660 )
661 })
662 }));
663 }
664 if !is_object(&actual) {
665 let text = msg.unwrap_or_else(|| {
666 super::assert_diff::create_err_diff(&actual, &expected, "deepStrictEqual", None)
667 });
668 return fail(text);
669 }
670 let keys_v = crate::builtins::call_builtin_function("Object.keys", vec![expected.clone()])?;
674 let mut keys: Vec<Value> = with_host(|h| match h.get(&keys_v) {
675 Some(JsObj::Array(items)) => items.clone(),
676 _ => Vec::new(),
677 });
678 if is_error(&expected) {
679 keys.extend(with_host(|h| [h.new_str("name"), h.new_str("message")]));
680 } else if keys.is_empty() {
681 let shown = with_host(|h| h.inspect(&expected));
682 return Err(crate::host::coded_error(
683 "TypeError",
684 "ERR_INVALID_ARG_VALUE",
685 &format!("The argument 'error' may not be an empty object. Received {shown}"),
686 ));
687 }
688 for key in &keys {
689 let k = with_host(|h| h.str_of(key));
690 let a = crate::builtins::get_property(&actual, &k)?;
691 let e = crate::builtins::get_property(&expected, &k)?;
692 if with_host(|h| h.type_of(&a)) == "string" && is_regexp(&e) && regexp_matches(&e, &a)?
693 {
694 continue;
695 }
696 let present = crate::builtins::has_property(&actual, &k)?;
697 if present && deep_equal(&a, &e, true) {
698 continue;
699 }
700 let text = match &msg {
701 Some(m) => m.clone(),
702 None => {
703 let keys_arr = with_host(|h| h.new_array(keys.clone()));
704 let ca = comparison(&actual, &keys_arr, &Value::Undef)?;
705 let cb = comparison(&expected, &keys_arr, &actual)?;
706 super::assert_diff::create_err_diff(&ca, &cb, "deepStrictEqual", None)
707 }
708 };
709 return fail(text);
710 }
711 return Ok(());
712 }
713 let proto = crate::builtins::get_property(&expected, "prototype")?;
715 if !matches!(proto, Value::Undef) && crate::host::instance_of(&actual, &expected)? {
716 return Ok(());
717 }
718 if is_error_subclass(&expected) {
719 let text = match msg {
720 Some(m) => m,
721 None => {
722 let name = with_host(|h| h.callable_name(&expected));
723 let mut m =
724 format!("The error is expected to be an instance of \"{name}\". Received ");
725 if is_error(&actual) {
726 let ctor = crate::builtins::get_property(&actual, "constructor")?;
727 let ctor_name = if with_host(|h| h.truthy(&ctor)) {
728 crate::builtins::get_property(&ctor, "name")?
729 } else {
730 Value::Undef
731 };
732 let actual_name = if with_host(|h| h.truthy(&ctor_name)) {
733 with_host(|h| h.str_of(&ctor_name))
734 } else {
735 let n = crate::builtins::get_property(&actual, "name")?;
736 with_host(|h| h.str_of(&n))
737 };
738 if actual_name == name {
739 m.push_str("an error with identical name but a different prototype.");
740 } else {
741 m.push_str(&format!("\"{actual_name}\""));
742 }
743 let am = crate::builtins::get_property(&actual, "message")?;
744 if with_host(|h| h.truthy(&am)) {
745 m.push_str(&format!(
746 "\n\nError message:\n\n{}",
747 with_host(|h| h.str_of(&am))
748 ));
749 }
750 } else {
751 m.push_str(&format!("\"{}\"", with_host(|h| h.inspect(&actual))));
752 }
753 m
754 }
755 };
756 return fail(text);
757 }
758 let this = with_host(|h| h.new_object(indexmap::IndexMap::new()));
760 let res = invoke(&expected, vec![actual.clone()], Some(this))?;
761 if matches!(res, Value::Bool(true)) {
762 return Ok(());
763 }
764 let text = match msg {
765 Some(m) => m,
766 None => {
767 let name = with_host(|h| h.callable_name(&expected));
768 let named = if name.is_empty() {
769 String::new()
770 } else {
771 format!("\"{name}\" ")
772 };
773 let mut m = format!(
774 "The {named}validation function is expected to return \"true\". Received {}",
775 with_host(|h| h.inspect(&res))
776 );
777 if is_error(&actual) {
778 let s = crate::host::to_string_value(&actual)?;
779 m.push_str(&format!(
780 "\n\nCaught error:\n\n{}",
781 with_host(|h| h.str_of(&s))
782 ));
783 }
784 m
785 }
786 };
787 fail(text)
788}
789
790fn expects_no_error(
793 operator: &str,
794 kind: &str,
795 actual: Option<Value>,
796 rest: &[Value],
797) -> Result<(), String> {
798 let Some(actual) = actual else {
799 return Ok(());
800 };
801 let mut error = rest.first().cloned().unwrap_or(Value::Undef);
802 let mut msg = message(rest, 1);
803 if with_host(|h| h.type_of(&error)) == "string" {
804 msg = Some(with_host(|h| h.str_of(&error)));
805 error = Value::Undef;
806 }
807 if !with_host(|h| h.truthy(&error)) || has_matching_error(&actual, &error)? {
808 let details = msg.map(|m| format!(": {m}")).unwrap_or_else(|| ".".into());
809 let actual_message = if is_object(&actual) {
810 let m = crate::builtins::get_property(&actual, "message")?;
811 with_host(|h| h.str_of(&m))
812 } else {
813 "undefined".to_string()
814 };
815 return Err(throw_assertion(
816 &format!("Got unwanted {kind}{details}\nActual message: \"{actual_message}\""),
817 false,
818 operator,
819 actual,
820 error,
821 ));
822 }
823 Err(with_host(|h| {
824 h.exc = Some(actual.clone());
825 crate::builtins::error_string(h, &actual)
826 }))
827}
828
829fn has_matching_error(actual: &Value, expected: &Value) -> Result<bool, String> {
831 if with_host(|h| h.type_of(expected)) != "function" {
832 if is_regexp(expected) {
833 let s = crate::host::to_string_value(actual)?;
834 return regexp_matches(expected, &s);
835 }
836 return Err(crate::host::coded_error(
837 "TypeError",
838 "ERR_INVALID_ARG_TYPE",
839 &format!(
840 "The \"expected\" argument must be of type function or an instance of RegExp. Received {}",
841 crate::stdlib::received_desc(expected)
842 ),
843 ));
844 }
845 let proto = crate::builtins::get_property(expected, "prototype")?;
846 if !matches!(proto, Value::Undef) && crate::host::instance_of(actual, expected)? {
847 return Ok(true);
848 }
849 if is_error_subclass(expected) {
850 return Ok(false);
851 }
852 let this = with_host(|h| h.new_object(indexmap::IndexMap::new()));
853 let res = invoke(expected, vec![actual.clone()], Some(this))?;
854 Ok(matches!(res, Value::Bool(true)))
855}
856
857fn is_error_subclass(expected: &Value) -> bool {
860 let mut cur = crate::builtins::prototype_of(expected);
861 for _ in 0..100_000 {
862 if with_host(|h| h.is_null(&cur) || matches!(cur, Value::Undef)) {
863 return false;
864 }
865 if with_host(|h| matches!(h.get(&cur), Some(JsObj::Builtin(n)) if n == "Error")) {
866 return true;
867 }
868 cur = crate::builtins::prototype_of(&cur);
869 }
870 false
871}
872
873fn comparison(obj: &Value, keys: &Value, actual: &Value) -> Result<Value, String> {
878 thread_local! {
879 static FACTORY: std::cell::RefCell<Option<Value>> = const { std::cell::RefCell::new(None) };
880 }
881 let factory = match FACTORY.with(|f| f.borrow().clone()) {
882 Some(f) => f,
883 None => {
884 let f = crate::eval_in_global_scope(
885 "(() => { class Comparison { constructor(obj, keys, actual) { for (const key of keys) { if (key in obj) { if (actual !== undefined && typeof actual[key] === 'string' && Object.prototype.toString.call(obj[key]) === '[object RegExp]' && obj[key].exec(actual[key]) !== null) { this[key] = actual[key]; } else { this[key] = obj[key]; } } } } } return (obj, keys, actual) => new Comparison(obj, keys, actual); })()",
886 )?;
887 FACTORY.with(|c| *c.borrow_mut() = Some(f.clone()));
888 f
889 }
890 };
891 invoke(
892 &factory,
893 vec![obj.clone(), keys.clone(), actual.clone()],
894 None,
895 )
896}
897
898fn message(args: &[Value], idx: usize) -> Option<String> {
899 match args.get(idx) {
900 Some(Value::Undef) | None => None,
901 Some(v) => Some(with_host(|h| h.str_of(v))),
902 }
903}
904
905fn assertion_error(msg: &str) -> String {
912 crate::host::coded_error("AssertionError", "ERR_ASSERTION", msg)
913}
914
915fn strict(a: &Value, b: &Value) -> bool {
921 crate::builtins::same_value(a, b)
922}
923
924fn loose_eq(a: &Value, b: &Value) -> bool {
925 if strict(a, b) {
926 return true;
927 }
928 with_host(|h| {
929 let (na, nb) = (h.to_number(a), h.to_number(b));
930 if !na.is_nan() && !nb.is_nan() && (na == nb) {
931 return true;
932 }
933 h.str_of(a) == h.str_of(b)
934 })
935}
936
937pub fn deep_equal(a: &Value, b: &Value, strict_mode: bool) -> bool {
939 deep_equal_seen(a, b, strict_mode, &mut Vec::new())
940}
941
942fn deep_equal_seen(
950 a: &Value,
951 b: &Value,
952 strict_mode: bool,
953 seen: &mut Vec<(Value, Value)>,
954) -> bool {
955 if seen.iter().any(|(x, y)| x == a && y == b) {
956 return true;
957 }
958 if strict_mode {
963 let both_objects = with_host(|h| h.get(a).is_some() && h.get(b).is_some());
964 if both_objects
965 && with_host(|h| {
966 h.proto_of(a) != h.proto_of(b) || h.has_null_proto(a) != h.has_null_proto(b)
971 })
972 {
973 return false;
974 }
975 }
976 let kinds = with_host(|h| {
977 let av = h.get(a).map(kind);
978 let bv = h.get(b).map(kind);
979 (av, bv)
980 });
981 seen.push((a.clone(), b.clone()));
982 let result = deep_equal_body(a, b, strict_mode, seen, kinds);
983 seen.pop();
984 result
985}
986
987fn deep_equal_body(
988 a: &Value,
989 b: &Value,
990 strict_mode: bool,
991 seen: &mut Vec<(Value, Value)>,
992 kinds: (Option<Kind>, Option<Kind>),
993) -> bool {
994 match kinds {
995 (Some(Kind::Array), Some(Kind::Array)) => {
996 let (ia, ib) = with_host(|h| (array_of(h, a), array_of(h, b)));
997 ia.len() == ib.len()
998 && ia
999 .iter()
1000 .zip(ib.iter())
1001 .all(|(x, y)| deep_equal_seen(x, y, strict_mode, seen))
1002 }
1003 (Some(Kind::Object), Some(Kind::Object)) => {
1004 let (ea, eb) = with_host(|h| (object_of(h, a), object_of(h, b)));
1005 if ea.len() != eb.len() {
1006 return false;
1007 }
1008 let props_match = ea.iter().all(|(k, va)| {
1009 eb.iter()
1010 .find(|(k2, _)| k2 == k)
1011 .is_some_and(|(_, vb)| deep_equal_seen(va, vb, strict_mode, seen))
1012 });
1013 if !props_match {
1014 return false;
1015 }
1016 let (ia, ib) = with_host(|h| (internals_of(h, a), internals_of(h, b)));
1026 if ia.len() != ib.len() {
1027 return false;
1028 }
1029 ia.iter().all(|(k, va)| {
1030 ib.iter()
1031 .find(|(k2, _)| k2 == k)
1032 .is_some_and(|(_, vb)| deep_equal_seen(va, vb, strict_mode, seen))
1033 })
1034 }
1035 (Some(Kind::Map), Some(Kind::Map)) => {
1040 let (ea, eb) = with_host(|h| (map_entries_of(h, a), map_entries_of(h, b)));
1041 unordered_match(&ea, &eb, seen, |(ka, va), (kb, vb), seen| {
1042 deep_equal_seen(ka, kb, strict_mode, seen)
1043 && deep_equal_seen(va, vb, strict_mode, seen)
1044 })
1045 }
1046 (Some(Kind::Set), Some(Kind::Set)) => {
1047 let (ea, eb) = with_host(|h| (set_members_of(h, a), set_members_of(h, b)));
1048 unordered_match(&ea, &eb, seen, |x, y, seen| {
1049 deep_equal_seen(x, y, strict_mode, seen)
1050 })
1051 }
1052 (Some(Kind::RegExp), Some(Kind::RegExp)) => {
1055 with_host(|h| regexp_key(h, a) == regexp_key(h, b))
1056 }
1057 _ => {
1063 if strict_mode {
1064 strict(a, b)
1065 } else {
1066 loose_eq(a, b)
1067 }
1068 }
1069 }
1070}
1071
1072fn unordered_match<T>(
1077 ea: &[T],
1078 eb: &[T],
1079 seen: &mut Vec<(Value, Value)>,
1080 eq: impl Fn(&T, &T, &mut Vec<(Value, Value)>) -> bool,
1081) -> bool {
1082 if ea.len() != eb.len() {
1083 return false;
1084 }
1085 let mut claimed = vec![false; eb.len()];
1086 'outer: for x in ea {
1087 for (i, y) in eb.iter().enumerate() {
1088 if !claimed[i] && eq(x, y, seen) {
1089 claimed[i] = true;
1090 continue 'outer;
1091 }
1092 }
1093 return false;
1094 }
1095 true
1096}
1097
1098enum Kind {
1099 Array,
1100 Object,
1101 Map,
1102 Set,
1103 RegExp,
1104 Other,
1106}
1107fn kind(o: &JsObj) -> Kind {
1108 match o {
1109 JsObj::Array(_) => Kind::Array,
1110 JsObj::Object(_) => Kind::Object,
1111 JsObj::Map { weak: false, .. } => Kind::Map,
1114 JsObj::Set { weak: false, .. } => Kind::Set,
1115 JsObj::RegExp(_) => Kind::RegExp,
1116 _ => Kind::Other,
1117 }
1118}
1119fn map_entries_of(h: &crate::host::JsHost, v: &Value) -> Vec<(Value, Value)> {
1121 match h.get(v) {
1122 Some(JsObj::Map { entries, .. }) => entries.values().cloned().collect(),
1123 _ => Vec::new(),
1124 }
1125}
1126fn set_members_of(h: &crate::host::JsHost, v: &Value) -> Vec<Value> {
1128 match h.get(v) {
1129 Some(JsObj::Set { entries, .. }) => entries.values().cloned().collect(),
1130 _ => Vec::new(),
1131 }
1132}
1133fn regexp_key(h: &crate::host::JsHost, v: &Value) -> Option<(String, String)> {
1135 match h.get(v) {
1136 Some(JsObj::RegExp(r)) => Some((r.source.clone(), r.flags.clone())),
1137 _ => None,
1138 }
1139}
1140fn internals_of(h: &crate::host::JsHost, v: &Value) -> Vec<(String, Value)> {
1145 match h.get(v) {
1146 Some(JsObj::Object(p)) => p
1147 .iter()
1148 .filter(|(k, _)| k.starts_with("@@"))
1149 .map(|(k, v)| (k.clone(), v.clone()))
1150 .collect(),
1151 _ => Vec::new(),
1152 }
1153}
1154fn array_of(h: &crate::host::JsHost, v: &Value) -> Vec<Value> {
1155 match h.get(v) {
1156 Some(JsObj::Array(items)) => items.clone(),
1157 _ => Vec::new(),
1158 }
1159}
1160fn object_of(h: &crate::host::JsHost, v: &Value) -> Vec<(String, Value)> {
1161 match h.get(v) {
1162 Some(JsObj::Object(p)) => p
1163 .iter()
1164 .filter(|(k, _)| !k.starts_with("@@") && !k.starts_with('#'))
1165 .map(|(k, v)| (k.clone(), v.clone()))
1166 .collect(),
1167 _ => Vec::new(),
1168 }
1169}
1170
1171fn is_promise_like(v: &Value) -> bool {
1174 if with_host(|h| h.promise_id(v)).is_some() {
1175 return true;
1176 }
1177 if !is_object(v) {
1178 return false;
1179 }
1180 ["then", "catch"].iter().all(|k| {
1181 crate::builtins::get_property(v, k)
1182 .map(|f| with_host(|h| is_callable(h, &f)))
1183 .unwrap_or(false)
1184 })
1185}