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);
+ }
+}