diff --git a/tests/Starling.Js.Test262.Tests/Test262Runner.cs b/tests/Starling.Js.Test262.Tests/Test262Runner.cs index 80e64161..78b48798 100644 --- a/tests/Starling.Js.Test262.Tests/Test262Runner.cs +++ b/tests/Starling.Js.Test262.Tests/Test262Runner.cs @@ -366,11 +366,23 @@ private static bool IsParseLevel(Exception ex) => /// Read the name property of a thrown error value (e.g. /// "TypeError"); null when the throw isn't an error-shaped object. - private static string? ErrorName(JsValue value) + internal static string? ErrorName(JsValue value) { if (!value.IsObject) return null; - var n = value.AsObject.Get("name"); - return n.IsString ? n.AsString : null; + var obj = value.AsObject; + // Built-in errors expose their type via `name` (TypeError.prototype.name + // etc.). The harness's Test262Error and other plain error classes set no + // `name` at all, so fall back to the constructor's name — which is what + // the official test262 runner matches a negative `type` against. + var n = obj.Get("name"); + if (n.IsString) return n.AsString; + var ctor = obj.Get("constructor"); + if (ctor.IsObject) + { + var cn = ctor.AsObject.Get("name"); + if (cn.IsString) return cn.AsString; + } + return null; } private static string ExtractMessage(Exception ex) => diff --git a/tests/Starling.Js.Test262.Tests/Test262RunnerErrorNameTests.cs b/tests/Starling.Js.Test262.Tests/Test262RunnerErrorNameTests.cs new file mode 100644 index 00000000..02d2c292 --- /dev/null +++ b/tests/Starling.Js.Test262.Tests/Test262RunnerErrorNameTests.cs @@ -0,0 +1,51 @@ +using Starling.Js.Bytecode; +using Starling.Js.Parse; +using Starling.Js.Runtime; + +namespace Starling.Js.Test262.Tests; + +/// +/// Pins how the runner classifies a thrown error against a negative test's +/// type. The harness's Test262Error is a plain function with no +/// name property, so a negative type: Test262Error can only be +/// matched by the thrown value's constructor name. +/// +[TestClass] +public class Test262RunnerErrorNameTests +{ + [TestMethod] + public void Test262Error_instance_resolves_by_constructor_name() + { + var value = Eval("function Test262Error(m){ this.message = m || ''; } new Test262Error('boom');"); + Assert.AreEqual("Test262Error", Test262Runner.ErrorName(value)); + } + + [TestMethod] + public void Builtin_error_resolves_by_name_property() + { + var value = Eval("new TypeError('x');"); + Assert.AreEqual("TypeError", Test262Runner.ErrorName(value)); + } + + [TestMethod] + public void Name_property_wins_over_constructor_name() + { + // `name` is present, so it is used even though the constructor (Object) + // has a different name. + var value = Eval("({ name: 'Custom' });"); + Assert.AreEqual("Custom", Test262Runner.ErrorName(value)); + } + + [TestMethod] + public void Thrown_primitive_has_no_error_name() + { + var value = Eval("'not an error';"); + Assert.IsNull(Test262Runner.ErrorName(value)); + } + + private static JsValue Eval(string source) + { + var chunk = JsCompiler.CompileForEval(new JsParser(source).ParseProgram()); + return new JsVm(new JsRuntime()).Run(chunk); + } +}