From 2904f993be3cbbb81091f047c90b50846f2aa9f6 Mon Sep 17 00:00:00 2001 From: isaacs <i@izs.me> Date: Mon, 6 Dec 2021 14:38:00 -0800 Subject: [PATCH] Handle invalid iterators If an object has Symbol.iterator method, but it throws when used with Array.from, then it is actually not an Array-like. Detect this situation, and switch to pojo-mode for those types of objects. Fix: https://github.com/tapjs/node-tap/issues/791 --- lib/format.js | 16 +++++++++++++--- tap-snapshots/test/format.js.test.cjs | 4 ++++ test/format.js | 13 +++++++++++++ test/same.js | 2 +- 4 files changed, 31 insertions(+), 4 deletions(-) diff --git a/lib/format.js b/lib/format.js index 8879c76..38df6b4 100644 --- a/lib/format.js +++ b/lib/format.js @@ -1,3 +1,10 @@ +const arrayFrom = obj => { + try { + return Array.from(obj) + } catch (e) { + return null + } +} class Format { constructor (obj, options = {}) { this.options = options @@ -51,9 +58,12 @@ class Format { get objectAsArray () { const value = Array.isArray(this.object) ? this.object - : this.isArray() ? Array.from(this.object) + : this.isArray() ? arrayFrom(this.object) : null + if (value === null) + this.isArray = () => false + Object.defineProperty(this, 'objectAsArray', { value }) return value } @@ -207,7 +217,7 @@ class Format { : this.isSet() ? this.set() : this.isMap() ? this.map() : this.isBuffer() ? this.buffer() - : this.isArray() ? this.array() + : this.isArray() && this.objectAsArray ? this.array() // TODO streams, JSX : this.pojo() @@ -441,7 +451,7 @@ class Format { } } } - return Array.from(own) + return arrayFrom(own) } else return Object.keys(obj || this.object) } diff --git a/tap-snapshots/test/format.js.test.cjs b/tap-snapshots/test/format.js.test.cjs index 3c96dd2..26f2812 100644 --- a/tap-snapshots/test/format.js.test.cjs +++ b/tap-snapshots/test/format.js.test.cjs @@ -3374,6 +3374,10 @@ Null Object { } ` +exports[`test/format.js TAP invalid iterator > must match snapshot 1`] = ` +Object {} +` + exports[`test/format.js TAP locale sorting > must match snapshot 1`] = ` Object { "cat": "meow", diff --git a/test/format.js b/test/format.js index 6b5cf7d..a8f7592 100644 --- a/test/format.js +++ b/test/format.js @@ -256,3 +256,16 @@ t.test('locale sorting', t => { t.matchSnapshot(format(obj, { sort: true })) t.end() }) + +t.test('invalid iterator', t => { + const obj = { [Symbol.iterator] () { return {} } } + t.matchSnapshot(format(obj)) + const f = new Format(obj) + // looks like an array + t.equal(f.isArray(), true) + // until you try to format it + t.equal(f.print(), 'Object {}') + // then it realizes it's actually not + t.equal(f.isArray(), false) + t.end() +}) diff --git a/test/same.js b/test/same.js index a3f3fac..602d2a9 100644 --- a/test/same.js +++ b/test/same.js @@ -79,7 +79,7 @@ t.test('array-likes', t => { a.push(1,2,3) const b = [1, 2, 3] t.ok(same(t, a, b)) - t.notEqual(a.constructor, b.constructor) + t.not(a.constructor, b.constructor) const args = (function () { return arguments })(1,2,3) const o = {[Symbol.iterator]: function*() { for (let i of a) { yield i } } }