diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 476e583..19969f0 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -14,7 +14,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
- node-version: 22
+ node-version: 24
- name: install dependencies
run: npm ci
- name: build spec
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index d7cd942..d91c183 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -15,7 +15,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
- node-version: 20
+ node-version: 24
- run: >
npm ci &&
npm run build &&
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index ef47e21..28cd987 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -10,7 +10,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
- node-version: 22
+ node-version: 24
- name: install dependencies
run: npm ci
- name: build
diff --git a/README.md b/README.md
index 6f24152..fcf7750 100644
--- a/README.md
+++ b/README.md
@@ -39,11 +39,14 @@ stronger than choosing SameValueZero to match `Array.prototype.includes`.
`Array.prototype.includes` has a second parameter that starts the search from
the given index instead of the beginning of the Array. This makes sense for the
Array API because the alternative (slicing first) would first allocate another
-Array and then perform a copy from that index. But Iterators have `drop` which
-is a constant time/space operation, so there's no need to include this
-parameter. That being said, it *could* be included to mirror the Array API, but
-I don't think it's worth it. Additionally, if it was included, it would have to
-reject negative values, which would be an unnecessarily surprising difference.
+Array and then perform a copy from that index. Iterators have `drop`, so it's
+unnecessary to include this parameter, but to avoid confusion for somebody who
+is already familiar with the Array method, we choose to include it.
+Unfortunately, there is still a difference in the interfaces because the
+Iterator method cannot accept negative offsets, which would be a surprising
+difference, but one that makes sense in context. Alternatively, we could throw
+if a second argument is ever provided, but that would be a highly unusual
+behaviour among JavaScript built-ins.
## chosen solution
diff --git a/spec.emu b/spec.emu
index 45be258..77efffe 100644
--- a/spec.emu
+++ b/spec.emu
@@ -10,14 +10,24 @@ copyright: false
- Iterator.prototype.includes ( _searchElement_ )
+ Iterator.prototype.includes ( _searchElement_ [ , _skippedElements_ ] )
1. Let _O_ be the *this* value.
1. If _O_ is not an Object, throw a *TypeError* exception.
+ 1. If _skippedElements_ is *undefined*, then
+ 1. Let _toSkip_ be 0.
+ 1. Else,
+ 1. If _skippedElements_ is not one of *+∞𝔽*, *-∞𝔽*, or an integral Number, throw a *TypeError* exception.
+ 1. Let _toSkip_ be the extended mathematical value of _skippedElements_.
+ 1. If _toSkip_ < 0, throw a *RangeError* exception.
+ 1. Let _skipped_ be 0.
1. Let _iterated_ be ? GetIteratorDirect(_O_).
1. Repeat,
1. Let _value_ be ? IteratorStepValue(_iterated_).
1. If _value_ is ~done~, return *false*.
- 1. If SameValueZero(_value_, _searchElement_) is *true*, return ? IteratorClose(_iterated_, NormalCompletion(*true*)).
+ 1. If _skipped_ < _toSkip_, then
+ 1. Set _skipped_ to _skipped_ + 1.
+ 1. Else,
+ 1. If SameValueZero(_value_, _searchElement_) is *true*, return ? IteratorClose(_iterated_, NormalCompletion(*true*)).
diff --git a/src/index.ts b/src/index.ts
index 66fb5df..4a7e4d2 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,6 +1,21 @@
-function includes(this: IterableIterator, searchElement: T): boolean {
+function includes(this: IterableIterator, searchElement: T, skippedElements = undefined): boolean {
+ let toSkip = 0;
+ if (skippedElements !== undefined) {
+ if (!(skippedElements === 2e308 || skippedElements === -2e308 || typeof skippedElements === 'number' && Math.trunc(skippedElements) === skippedElements)) {
+ throw new TypeError;
+ }
+ toSkip = skippedElements as number;
+ }
+ if (toSkip < 0) {
+ throw new RangeError;
+ }
+ let skipped = 0;
for (let e of this) {
- if ([e].includes(searchElement)) return true;
+ if (skipped < toSkip) {
+ ++skipped;
+ } else if ([e].includes(searchElement)) {
+ return true;
+ }
}
return false;
}
diff --git a/test/index.mjs b/test/index.mjs
index 8d2a6a2..733f654 100644
--- a/test/index.mjs
+++ b/test/index.mjs
@@ -75,6 +75,104 @@ test('closes iterator', async t => {
assert.equal(closed, true);
});
+test('skipped elements', async t => {
+ await test('negative integral', async t => {
+ assert.throws(() => {
+ [].values().includes(0, -1);
+ }, RangeError);
+ });
+
+ await test('negative non-integral', async t => {
+ assert.throws(() => {
+ [].values().includes(0, -0.1);
+ }, TypeError);
+ });
+
+ await test('negative infinity', async t => {
+ assert.throws(() => {
+ [].values().includes(0, -2e308);
+ }, RangeError);
+ });
+
+ await test('zero', async t => {
+ assert.equal([4, 5, 6, 7].values().includes(8, 0), false);
+ assert.equal([4, 5, 6, 7].values().includes(7, 0), true);
+ assert.equal([4, 5, 6, 7].values().includes(6, 0), true);
+ assert.equal([4, 5, 6, 7].values().includes(5, 0), true);
+ assert.equal([4, 5, 6, 7].values().includes(4, 0), true);
+ assert.equal([4, 5, 6, 7].values().includes(3, 0), false);
+
+ assert.equal([4, 5, 6, 7].values().includes(8, -0), false);
+ assert.equal([4, 5, 6, 7].values().includes(7, -0), true);
+ assert.equal([4, 5, 6, 7].values().includes(6, -0), true);
+ assert.equal([4, 5, 6, 7].values().includes(5, -0), true);
+ assert.equal([4, 5, 6, 7].values().includes(4, -0), true);
+ assert.equal([4, 5, 6, 7].values().includes(3, -0), false);
+ });
+
+ await test('positive integral', async t => {
+ assert.equal([4, 5, 6, 7].values().includes(4, 1), false);
+ assert.equal([4, 5, 6, 7].values().includes(4, 2), false);
+ assert.equal([4, 5, 6, 7].values().includes(4, 3), false);
+ assert.equal([4, 5, 6, 7].values().includes(4, 4), false);
+ assert.equal([4, 5, 6, 7].values().includes(4, 5), false);
+
+ assert.equal([4, 5, 6, 7].values().includes(5, 1), true);
+ assert.equal([4, 5, 6, 7].values().includes(5, 2), false);
+ assert.equal([4, 5, 6, 7].values().includes(5, 3), false);
+ assert.equal([4, 5, 6, 7].values().includes(5, 4), false);
+ assert.equal([4, 5, 6, 7].values().includes(5, 5), false);
+
+ assert.equal([4, 5, 6, 7].values().includes(6, 1), true);
+ assert.equal([4, 5, 6, 7].values().includes(6, 2), true);
+ assert.equal([4, 5, 6, 7].values().includes(6, 3), false);
+ assert.equal([4, 5, 6, 7].values().includes(6, 4), false);
+ assert.equal([4, 5, 6, 7].values().includes(6, 5), false);
+
+ assert.equal([4, 5, 6, 7].values().includes(7, 1), true);
+ assert.equal([4, 5, 6, 7].values().includes(7, 2), true);
+ assert.equal([4, 5, 6, 7].values().includes(7, 3), true);
+ assert.equal([4, 5, 6, 7].values().includes(7, 4), false);
+ assert.equal([4, 5, 6, 7].values().includes(7, 5), false);
+ });
+
+ await test('positive non-integral', async t => {
+ assert.throws(() => {
+ [].values().includes(0, 0.1);
+ }, TypeError);
+ });
+
+ await test('positive infinity', async t => {
+ let closed = false;
+ let i = 0;
+ let iter = {
+ __proto__: Iterator.prototype,
+ next() {
+ ++i;
+ if (i < 1000) {
+ return { value: i, done: false };
+ } else {
+ closed = true;
+ return { value: undefined, done: true };
+ }
+ },
+ return() {
+ closed = true;
+ return { value: undefined, done: true };
+ },
+ };
+
+ assert.equal(iter.includes(1, Infinity), false);
+ assert.equal(closed, true);
+ });
+
+ await test('non-numeric', async t => {
+ assert.throws(() => {
+ [].values().includes(0, { valueOf() { return 0; } });
+ }, TypeError);
+ });
+});
+
test('name', async t => {
assert.equal(Iterator.prototype.includes.name, 'includes');
});