tests for Iterator.prototype.join
diff --git a/features.txt b/features.txt
index 065eb25..5ed5a32 100644
--- a/features.txt
+++ b/features.txt
@@ -92,6 +92,10 @@
 # https://github.com/tc39/proposal-joint-iteration
 joint-iteration
 
+# Iterator Join
+# https://github.com/tc39/proposal-iterator-join
+Iterator.prototype.join
+
 ## Standard language features
 #
 # Language features that have been included in a published version of the
diff --git a/test/built-ins/Iterator/prototype/join/closes-on-contents-coercion-exception.js b/test/built-ins/Iterator/prototype/join/closes-on-contents-coercion-exception.js
new file mode 100644
index 0000000..33b8e84
--- /dev/null
+++ b/test/built-ins/Iterator/prototype/join/closes-on-contents-coercion-exception.js
@@ -0,0 +1,36 @@
+// Copyright (C) 2025 Kevin Gibbons. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-iterator.prototype.join
+description: Iterator.prototype.join closes its receiver if coercing a value from the iterator throws.
+features: [Iterator.prototype.join]
+---*/
+
+var throwy = {
+  toString: function () {
+    throw new Test262Error();
+  },
+};
+
+var calledNextCount = 0;
+var calledReturn = false;
+var it = {
+  next: function () {
+    ++calledNextCount;
+    if (calledNextCount > 1) {
+      return { done: true, value: undefined };
+    }
+    return { done: false, value: throwy };
+  },
+  return: function () {
+    calledReturn = true;
+  },
+};
+
+assert.throws(Test262Error, function () {
+  Iterator.prototype.join.call(it);
+});
+
+assert.sameValue(calledNextCount, 1);
+assert(calledReturn);
diff --git a/test/built-ins/Iterator/prototype/join/closes-on-separator-coercion-exception.js b/test/built-ins/Iterator/prototype/join/closes-on-separator-coercion-exception.js
new file mode 100644
index 0000000..d08025d
--- /dev/null
+++ b/test/built-ins/Iterator/prototype/join/closes-on-separator-coercion-exception.js
@@ -0,0 +1,33 @@
+// Copyright (C) 2025 Kevin Gibbons. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-iterator.prototype.join
+description: Iterator.prototype.join closes its receiver if coercing the separator throws.
+features: [Iterator.prototype.join]
+---*/
+
+var throwy = {
+  toString: function () {
+    throw new Test262Error();
+  },
+};
+
+var gotNext = false;
+var calledReturn = false;
+var it = {
+  get next() {
+    // we use a variable instead of simply throwing because throwing is expected in this test
+    gotNext = true;
+  },
+  return: function () {
+    calledReturn = true;
+  },
+};
+
+assert.throws(Test262Error, function () {
+  Iterator.prototype.join.call(it, throwy);
+});
+
+assert.sameValue(gotNext, false);
+assert(calledReturn);
diff --git a/test/built-ins/Iterator/prototype/join/contents-nullish.js b/test/built-ins/Iterator/prototype/join/contents-nullish.js
new file mode 100644
index 0000000..79fbda7
--- /dev/null
+++ b/test/built-ins/Iterator/prototype/join/contents-nullish.js
@@ -0,0 +1,13 @@
+// Copyright (C) 2025 Kevin Gibbons. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-iterator.prototype.join
+description: Iterator.prototype.join formats nullish iterator contents as an empty string.
+features: [Iterator.prototype.join]
+---*/
+
+assert.sameValue(
+  ['one', null, 'two', undefined, 'three'].values().join(),
+  'one,,two,,three'
+);
diff --git a/test/built-ins/Iterator/prototype/join/contents-tostring.js b/test/built-ins/Iterator/prototype/join/contents-tostring.js
new file mode 100644
index 0000000..c721124
--- /dev/null
+++ b/test/built-ins/Iterator/prototype/join/contents-tostring.js
@@ -0,0 +1,22 @@
+// Copyright (C) 2025 Kevin Gibbons. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-iterator.prototype.join
+description: Iterator.prototype.join coerces non-nullish iterator contents to string.
+features: [Iterator.prototype.join]
+---*/
+
+var called = false;
+var coercible = {
+  toString: function () {
+    if (called) {
+      throw new Test262Error('toString should be called exactly once');
+    }
+    called = true;
+    return 'value';
+  },
+};
+
+assert.sameValue([coercible, 0, true].values().join(), 'value,0,true');
+assert(called);
diff --git a/test/built-ins/Iterator/prototype/join/descriptor.js b/test/built-ins/Iterator/prototype/join/descriptor.js
new file mode 100644
index 0000000..48c95f1
--- /dev/null
+++ b/test/built-ins/Iterator/prototype/join/descriptor.js
@@ -0,0 +1,15 @@
+// Copyright (C) 2025 Kevin Gibbons. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-iterator.prototype.join
+description: Iterator.prototype.join has default data property attributes.
+includes: [propertyHelper.js]
+features: [Iterator.prototype.join]
+---*/
+
+verifyProperty(Iterator.prototype, 'join', {
+  enumerable: false,
+  writable: true,
+  configurable: true
+});
diff --git a/test/built-ins/Iterator/prototype/join/does-not-close-on-iterator-error.js b/test/built-ins/Iterator/prototype/join/does-not-close-on-iterator-error.js
new file mode 100644
index 0000000..7f487ac
--- /dev/null
+++ b/test/built-ins/Iterator/prototype/join/does-not-close-on-iterator-error.js
@@ -0,0 +1,24 @@
+// Copyright (C) 2025 Kevin Gibbons. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-iterator.prototype.join
+description: Iterator.prototype.join does not close its receiver if the iterator itself throws.
+features: [Iterator.prototype.join]
+---*/
+
+var gotReturn = false;
+var it = {
+  next: function () {
+    throw new Test262Error();
+  },
+  get return() {
+    gotReturn = true;
+  },
+};
+
+assert.throws(Test262Error, function () {
+  Iterator.prototype.join.call(it);
+});
+
+assert.sameValue(gotReturn, false);
diff --git a/test/built-ins/Iterator/prototype/join/does-not-close-on-iterator-exhaustion.js b/test/built-ins/Iterator/prototype/join/does-not-close-on-iterator-exhaustion.js
new file mode 100644
index 0000000..cafaddd
--- /dev/null
+++ b/test/built-ins/Iterator/prototype/join/does-not-close-on-iterator-exhaustion.js
@@ -0,0 +1,29 @@
+// Copyright (C) 2025 Kevin Gibbons. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-iterator.prototype.join
+description: Iterator.prototype.join does not close its receiver if the iterator is exhausted.
+features: [Iterator.prototype.join]
+---*/
+
+var calledNextCount = 0;
+var gotReturn = false;
+var it = {
+  next: function () {
+    ++calledNextCount;
+    if (calledNextCount > 2) {
+      return { done: true, value: undefined };
+    }
+    return { done: false, value: 'ES' };
+  },
+  get return() {
+    gotReturn = true;
+  },
+};
+
+assert.sameValue(Iterator.prototype.join.call(it), 'ES,ES');
+
+assert.sameValue(calledNextCount, 3);
+
+assert.sameValue(gotReturn, false);
diff --git a/test/built-ins/Iterator/prototype/join/does-not-close-on-iterator-protocol-violation.js b/test/built-ins/Iterator/prototype/join/does-not-close-on-iterator-protocol-violation.js
new file mode 100644
index 0000000..88e1f1b
--- /dev/null
+++ b/test/built-ins/Iterator/prototype/join/does-not-close-on-iterator-protocol-violation.js
@@ -0,0 +1,24 @@
+// Copyright (C) 2025 Kevin Gibbons. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-iterator.prototype.join
+description: Iterator.prototype.join does not close its receiver if the iterator itself throws.
+features: [Iterator.prototype.join]
+---*/
+
+var gotReturn = false;
+var it = {
+  next: function () {
+    return null;
+  },
+  get return() {
+    gotReturn = true;
+  },
+};
+
+assert.throws(TypeError, function () {
+  Iterator.prototype.join.call(it);
+});
+
+assert.sameValue(gotReturn, false);
diff --git a/test/built-ins/Iterator/prototype/join/length.js b/test/built-ins/Iterator/prototype/join/length.js
new file mode 100644
index 0000000..8d32126
--- /dev/null
+++ b/test/built-ins/Iterator/prototype/join/length.js
@@ -0,0 +1,16 @@
+// Copyright (C) 2025 Kevin Gibbons. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-iterator.prototype.join
+description: Iterator.prototype.join.length is 1.
+includes: [propertyHelper.js]
+features: [Iterator.prototype.join]
+---*/
+
+verifyProperty(Iterator.prototype.join, 'length', {
+  value: 1,
+  enumerable: false,
+  writable: false,
+  configurable: true
+});
diff --git a/test/built-ins/Iterator/prototype/join/name.js b/test/built-ins/Iterator/prototype/join/name.js
new file mode 100644
index 0000000..1e94ebd
--- /dev/null
+++ b/test/built-ins/Iterator/prototype/join/name.js
@@ -0,0 +1,16 @@
+// Copyright (C) 2025 Kevin Gibbons. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-iterator.prototype.join
+description: Iterator.prototype.join.name is "join".
+includes: [propertyHelper.js]
+features: [Iterator.prototype.join]
+---*/
+
+verifyProperty(Iterator.prototype.join, 'name', {
+  value: 'join',
+  enumerable: false,
+  writable: false,
+  configurable: true
+});
diff --git a/test/built-ins/Iterator/prototype/join/not-a-constructor.js b/test/built-ins/Iterator/prototype/join/not-a-constructor.js
new file mode 100644
index 0000000..0929c2b
--- /dev/null
+++ b/test/built-ins/Iterator/prototype/join/not-a-constructor.js
@@ -0,0 +1,16 @@
+// Copyright (C) 2025 Kevin Gibbons. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-iterator.prototype.join
+description: Iterator.prototype.join is not a constructor
+includes: [isConstructor.js]
+features: [Iterator.prototype.join, Reflect.construct]
+---*/
+
+assert(!isConstructor(Iterator.prototype.join), "Iterator.prototype.join should not be a constructor");
+
+assert.throws(TypeError, function() {
+  var iterator = [].values();
+  new iterator.join();
+});
diff --git a/test/built-ins/Iterator/prototype/join/receiver-not-object.js b/test/built-ins/Iterator/prototype/join/receiver-not-object.js
new file mode 100644
index 0000000..15b5432
--- /dev/null
+++ b/test/built-ins/Iterator/prototype/join/receiver-not-object.js
@@ -0,0 +1,38 @@
+// Copyright (C) 2025 Kevin Gibbons. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-iterator.prototype.join
+description: Iterator.prototype.join throws if the receiver is not an object.
+features: [Iterator.prototype.join]
+---*/
+
+var it = [].values();
+
+assert.throws(TypeError, function () {
+  it.join.call(undefined);
+});
+
+assert.throws(TypeError, function () {
+  it.join.call(null);
+});
+
+assert.throws(TypeError, function () {
+  it.join.call(false);
+});
+
+assert.throws(TypeError, function () {
+  it.join.call(0);
+});
+
+assert.throws(TypeError, function () {
+  it.join.call(0n);
+});
+
+assert.throws(TypeError, function () {
+  it.join.call("");
+});
+
+assert.throws(TypeError, function () {
+  it.join.call(Symbol());
+});
diff --git a/test/built-ins/Iterator/prototype/join/results-empty-separator.js b/test/built-ins/Iterator/prototype/join/results-empty-separator.js
new file mode 100644
index 0000000..ed1ea2a
--- /dev/null
+++ b/test/built-ins/Iterator/prototype/join/results-empty-separator.js
@@ -0,0 +1,16 @@
+// Copyright (C) 2025 Kevin Gibbons. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-iterator.prototype.join
+description: Iterator.prototype.join allows empty string as separator.
+features: [Iterator.prototype.join]
+---*/
+
+assert.sameValue([].values().join(''), '');
+
+assert.sameValue(['one'].values().join(''), 'one');
+
+assert.sameValue(['one', 'two'].values().join(''), 'onetwo');
+
+assert.sameValue(['one', 'two', 'three'].values().join(''), 'onetwothree');
diff --git a/test/built-ins/Iterator/prototype/join/results-no-separator.js b/test/built-ins/Iterator/prototype/join/results-no-separator.js
new file mode 100644
index 0000000..231bbba
--- /dev/null
+++ b/test/built-ins/Iterator/prototype/join/results-no-separator.js
@@ -0,0 +1,16 @@
+// Copyright (C) 2025 Kevin Gibbons. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-iterator.prototype.join
+description: Iterator.prototype.join joins using a comma if no separator is passed.
+features: [Iterator.prototype.join]
+---*/
+
+assert.sameValue([].values().join(), '');
+
+assert.sameValue(['one'].values().join(), 'one');
+
+assert.sameValue(['one', 'two'].values().join(), 'one,two');
+
+assert.sameValue(['one', 'two', 'three'].values().join(), 'one,two,three');
diff --git a/test/built-ins/Iterator/prototype/join/results-nonempty-separator.js b/test/built-ins/Iterator/prototype/join/results-nonempty-separator.js
new file mode 100644
index 0000000..3ed3424
--- /dev/null
+++ b/test/built-ins/Iterator/prototype/join/results-nonempty-separator.js
@@ -0,0 +1,16 @@
+// Copyright (C) 2025 Kevin Gibbons. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-iterator.prototype.join
+description: Iterator.prototype.join joins using the passed separator.
+features: [Iterator.prototype.join]
+---*/
+
+assert.sameValue([].values().join('&&'), '');
+
+assert.sameValue(['one'].values().join('&&'), 'one');
+
+assert.sameValue(['one', 'two'].values().join('&&'), 'one&&two');
+
+assert.sameValue(['one', 'two', 'three'].values().join('&&'), 'one&&two&&three');
diff --git a/test/built-ins/Iterator/prototype/join/separator-tostring.js b/test/built-ins/Iterator/prototype/join/separator-tostring.js
new file mode 100644
index 0000000..8c750ae
--- /dev/null
+++ b/test/built-ins/Iterator/prototype/join/separator-tostring.js
@@ -0,0 +1,26 @@
+// Copyright (C) 2025 Kevin Gibbons. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-iterator.prototype.join
+description: Iterator.prototype.join coerces the passed separator to a string.
+features: [Iterator.prototype.join]
+---*/
+
+var called = false;
+var coercible = {
+  toString: function () {
+    if (called) {
+      throw new Test262Error('toString should be called exactly once');
+    }
+    called = true;
+    return '&&';
+  },
+};
+
+assert.sameValue(['one', 'two', 'three'].values().join(coercible), 'one&&two&&three');
+assert(called);
+
+assert.sameValue(['one', 'two', 'three'].values().join(undefined), 'one,two,three');
+
+assert.sameValue(['one', 'two', 'three'].values().join(null), 'onenulltwonullthree');