From c685401ab2739db4fc71dac9efcd13617e4ee9e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristj=C3=A1n=20Oddsson?= Date: Fri, 20 Oct 2023 09:39:30 +0200 Subject: [PATCH 1/4] inline `type-detect` as a simple function --- README.md | 1 - lib/chai/core/assertions.js | 4 ++-- lib/chai/utils/expectTypes.js | 2 +- lib/chai/utils/getOperator.js | 3 +-- lib/chai/utils/index.js | 2 +- lib/chai/utils/type-detect.js | 18 ++++++++++++++++++ package-lock.json | 3 +-- package.json | 3 +-- 8 files changed, 25 insertions(+), 11 deletions(-) create mode 100644 lib/chai/utils/type-detect.js diff --git a/README.md b/README.md index d9145e20..ed1ee7bd 100644 --- a/README.md +++ b/README.md @@ -145,7 +145,6 @@ Chai offers a robust Plugin architecture for extending Chai's assertions and int - [chaijs / chai-docs](https://github.com/chaijs/chai-docs): The chaijs.com website source code. - [chaijs / assertion-error](https://github.com/chaijs/assertion-error): Custom `Error` constructor thrown upon an assertion failing. - [chaijs / deep-eql](https://github.com/chaijs/deep-eql): Improved deep equality testing for Node.js and the browser. -- [chaijs / type-detect](https://github.com/chaijs/type-detect): Improved typeof detection for Node.js and the browser. - [chaijs / check-error](https://github.com/chaijs/check-error): Error comparison and information related utility for Node.js and the browser. - [chaijs / loupe](https://github.com/chaijs/loupe): Inspect utility for Node.js and browsers. - [chaijs / pathval](https://github.com/chaijs/pathval): Object value retrieval given a string path. diff --git a/lib/chai/core/assertions.js b/lib/chai/core/assertions.js index 2820327e..76b7641b 100644 --- a/lib/chai/core/assertions.js +++ b/lib/chai/core/assertions.js @@ -243,8 +243,8 @@ Assertion.addProperty('all', function () { * ### .a(type[, msg]) * * Asserts that the target's type is equal to the given string `type`. Types - * are case insensitive. See the `type-detect` project page for info on the - * type detection algorithm: https://github.com/chaijs/type-detect. + * are case insensitive. See the utility file `./type-detect.js` for info on the + * type detection algorithm. * * expect('foo').to.be.a('string'); * expect({a: 1}).to.be.an('object'); diff --git a/lib/chai/utils/expectTypes.js b/lib/chai/utils/expectTypes.js index 4b2b54df..f0a206f4 100644 --- a/lib/chai/utils/expectTypes.js +++ b/lib/chai/utils/expectTypes.js @@ -20,7 +20,7 @@ import AssertionError from 'assertion-error'; import {flag} from './flag.js'; -import {default as type} from 'type-detect'; +import {type} from './type-detect.js'; export function expectTypes(obj, types) { var flagMsg = flag(obj, 'message'); diff --git a/lib/chai/utils/getOperator.js b/lib/chai/utils/getOperator.js index 50bd1bf9..29a4f239 100644 --- a/lib/chai/utils/getOperator.js +++ b/lib/chai/utils/getOperator.js @@ -1,6 +1,5 @@ import {flag} from './flag.js'; -import type from 'type-detect'; - +import {type} from './type-detect.js'; function isObjectType(obj) { var objectType = type(obj); diff --git a/lib/chai/utils/index.js b/lib/chai/utils/index.js index 08c0b0e4..4b9eba8b 100644 --- a/lib/chai/utils/index.js +++ b/lib/chai/utils/index.js @@ -20,7 +20,7 @@ export {test} from './test.js'; * type utility */ -export {default as type} from 'type-detect'; +export {type} from './type-detect.js'; /*! * expectTypes utility diff --git a/lib/chai/utils/type-detect.js b/lib/chai/utils/type-detect.js new file mode 100644 index 00000000..b0aab2d4 --- /dev/null +++ b/lib/chai/utils/type-detect.js @@ -0,0 +1,18 @@ +const typesToLowerCase = [ + "Number", + "String", + "Boolean", + "Null", + "Undefined", + "Function", + "Symbol", +]; + +export function type(obj) { + const type = Object.prototype.toString.call(obj).slice(8, -1); + + if (typesToLowerCase.includes(type)) { + return type.toLowerCase(); + } + return type; +} diff --git a/package-lock.json b/package-lock.json index 51b15ff9..f42f4b79 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,8 +13,7 @@ "check-error": "^2.0.0", "deep-eql": "^5.0.0", "loupe": "^2.3.1", - "pathval": "^2.0.0", - "type-detect": "^4.0.5" + "pathval": "^2.0.0" }, "devDependencies": { "bump-cli": "^1.1.3", diff --git a/package.json b/package.json index 6194314a..a3031045 100644 --- a/package.json +++ b/package.json @@ -47,8 +47,7 @@ "check-error": "^2.0.0", "deep-eql": "^5.0.0", "loupe": "^2.3.1", - "pathval": "^2.0.0", - "type-detect": "^4.0.5" + "pathval": "^2.0.0" }, "devDependencies": { "bump-cli": "^1.1.3", From b044dff137301c338b7cefb31d3024b4a5113466 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristj=C3=A1n=20Oddsson?= Date: Fri, 20 Oct 2023 12:17:48 +0200 Subject: [PATCH 2/4] Add type-detec tests --- karma.conf.cjs | 1 + test/type-detect/deno-test.ts | 7 + test/type-detect/dom.js | 337 +++++++++++++++++++++++ test/type-detect/index.js | 277 +++++++++++++++++++ test/type-detect/new-ecmascript-types.js | 141 ++++++++++ test/type-detect/node.js | 23 ++ test/type-detect/tostringtag-extras.js | 25 ++ 7 files changed, 811 insertions(+) create mode 100644 test/type-detect/deno-test.ts create mode 100644 test/type-detect/dom.js create mode 100644 test/type-detect/index.js create mode 100644 test/type-detect/new-ecmascript-types.js create mode 100644 test/type-detect/node.js create mode 100644 test/type-detect/tostringtag-extras.js diff --git a/karma.conf.cjs b/karma.conf.cjs index edbd5a97..a7a525bc 100644 --- a/karma.conf.cjs +++ b/karma.conf.cjs @@ -5,6 +5,7 @@ module.exports = function(config) { { pattern: 'chai.js', type: 'module', included: false, served: true } , { pattern: 'test/bootstrap/index.js', type: 'module'} , { pattern: 'test/*.js', type: 'module' } + , { pattern: 'test/type-detect/*.js', type: 'module' } ] , reporters: [ 'progress' ] , colors: true diff --git a/test/type-detect/deno-test.ts b/test/type-detect/deno-test.ts new file mode 100644 index 00000000..b2666d05 --- /dev/null +++ b/test/type-detect/deno-test.ts @@ -0,0 +1,7 @@ +/* global Deno:readonly */ +// @ts-nocheck +import { assertEquals } from 'https://deno.land/std/testing/asserts.ts'; +import typeDetect from '../index.ts'; +Deno.test('type detect works', () => { + assertEquals(typeDetect('hello'), 'string'); +}); diff --git a/test/type-detect/dom.js b/test/type-detect/dom.js new file mode 100644 index 00000000..36c4a96e --- /dev/null +++ b/test/type-detect/dom.js @@ -0,0 +1,337 @@ +function assert (expr, msg) { + if (!expr) { + throw new Error(msg || 'Assertion Failed'); + } +} + +const type = chai.util.type + +function describeIf(condition) { + return condition ? describe : describe.skip; +} +function itIf(condition) { + return condition ? it : it.skip; +} +describeIf(typeof window !== 'undefined' && typeof window.document !== 'undefined')('DOM Specific', () => { + + it('window', () => { + assert(type(window) === 'Window'); + }); + + it('document', () => { + assert(type(document) === 'HTMLDocument'); + }); + + it('domparser', () => { + assert(type(new DOMParser()) === 'DOMParser'); + }); + + it('history', () => { + assert(type(window.history) === 'History'); + }); + + it('location', () => { + assert(type(window.location) === 'Location'); + }); + + it('attr', () => { + const div = document.createElement('div'); + div.setAttribute('id', 'foo'); + assert(type(div.getAttributeNode('id')) === 'Attr'); + }); + + describe('Events', () => { + + it('event', () => { + assert(type(document.createEvent('Event')) === 'Event'); + }); + + itIf(typeof HashChangeEvent !== 'undefined')('HashChangeEvent', () => { + assert(type(new HashChangeEvent('')) === 'HashChangeEvent'); + }); + + }); + + describe('Navigator', () => { + + it('navigator', () => { + assert(type(window.navigator) === 'Navigator'); + }); + + itIf(typeof navigator !== 'undefined' && 'geolocation' in navigator)('geolocation', () => { + assert(type(navigator.geolocation) === 'Geolocation'); + }); + + itIf(typeof navigator !== 'undefined' && 'connection' in navigator)('networkinformation', () => { + assert(type(navigator.connection) === 'NetworkInformation'); + }); + + itIf(typeof navigator !== 'undefined' && 'mediaDevices' in navigator)('mediadevices', () => { + assert(type(navigator.mediaDevices) === 'MediaDevices'); + }); + + itIf(typeof navigator !== 'undefined' && 'mimeTypes' in navigator)('mimetypearray', () => { + assert(type(navigator.mimeTypes) === 'MimeTypeArray'); + }); + + itIf(typeof navigator !== 'undefined' && 'nfc' in navigator)('nfc', () => { + assert(type(navigator.nfc) === 'NFC'); + }); + + itIf(typeof navigator !== 'undefined' && 'permissions' in navigator)('permissions', () => { + assert(type(navigator.permissions) === 'Permissions'); + }); + + itIf(typeof navigator !== 'undefined' && 'plugins' in navigator)('pluginarray', () => { + assert(type(navigator.plugins) === 'PluginArray'); + }); + + itIf(typeof navigator !== 'undefined' && 'plugins' in navigator && navigator.plugins.length)('plugin', () => { + assert(type(navigator.plugins[0]) === 'Plugin'); + }); + + itIf(typeof navigator !== 'undefined' && 'presentation' in navigator)('presentation', () => { + assert(type(navigator.presentation) === 'Presentation'); + }); + + itIf(typeof navigator !== 'undefined' && 'serviceworker' in navigator)('serviceworkercontainer', () => { + assert(type(navigator.serviceworker) === 'ServiceWorkerContainer'); + }); + + itIf(typeof navigator !== 'undefined' && 'services' in navigator)('serviceportcollection', () => { + assert(type(navigator.services) === 'ServicePortCollection'); + }); + + itIf(typeof navigator !== 'undefined' && 'storage' in navigator)('storagemanager', () => { + assert(type(navigator.storage) === 'StorageManager'); + }); + + itIf(typeof navigator !== 'undefined' && 'storageQuota' in navigator)('storagequota', () => { + assert(type(navigator.storageQuota) === 'StorageQuota'); + }); + + itIf(typeof navigator !== 'undefined' && 'usb' in navigator)('usb', () => { + assert(type(navigator.usb) === 'USB'); + }); + + }); + + describe('(HTMLElements)', () => { + + it('HTMLAreaElement', () => { + assert(type(document.createElement('Area')) === 'HTMLAreaElement'); + }); + + it('HTMLBRElement', () => { + assert(type(document.createElement('BR')) === 'HTMLBRElement'); + }); + + it('HTMLBaseElement', () => { + assert(type(document.createElement('Base')) === 'HTMLBaseElement'); + }); + + it('HTMLBodyElement', () => { + assert(type(document.createElement('Body')) === 'HTMLBodyElement'); + }); + + it('HTMLButtonElement', () => { + assert(type(document.createElement('Button')) === 'HTMLButtonElement'); + }); + + it('HTMLCanvasElement', () => { + assert(type(document.createElement('Canvas')) === 'HTMLCanvasElement'); + }); + + it('HTMLDListElement', () => { + assert(type(document.createElement('DL')) === 'HTMLDListElement'); + }); + + // not yet supported in Safari + itIf(typeof HTMLDataListElement === 'function')('HTMLDataListElement', () => { + assert(type(document.createElement('DataList')) === 'HTMLDataListElement'); + }); + + it('HTMLDivElement', () => { + assert(type(document.createElement('Div')) === 'HTMLDivElement'); + }); + + it('HTMLFieldSetElement', () => { + assert(type(document.createElement('FieldSet')) === 'HTMLFieldSetElement'); + }); + + it('HTMLFormElement', () => { + assert(type(document.createElement('Form')) === 'HTMLFormElement'); + }); + + it('HTMLFrameSetElement', () => { + assert(type(document.createElement('FrameSet')) === 'HTMLFrameSetElement'); + }); + + it('HTMLHRElement', () => { + assert(type(document.createElement('HR')) === 'HTMLHRElement'); + }); + + it('HTMLHeadElement', () => { + assert(type(document.createElement('Head')) === 'HTMLHeadElement'); + }); + + it('HTMLHeadingElement', () => { + assert(type(document.createElement('H1')) === 'HTMLHeadingElement'); + assert(type(document.createElement('H2')) === 'HTMLHeadingElement'); + assert(type(document.createElement('H3')) === 'HTMLHeadingElement'); + assert(type(document.createElement('H4')) === 'HTMLHeadingElement'); + assert(type(document.createElement('H5')) === 'HTMLHeadingElement'); + assert(type(document.createElement('H6')) === 'HTMLHeadingElement'); + }); + + it('HTMLHtmlElement', () => { + assert(type(document.createElement('Html')) === 'HTMLHtmlElement'); + }); + + it('HTMLIFrameElement', () => { + assert(type(document.createElement('IFrame')) === 'HTMLIFrameElement'); + }); + + it('HTMLImageElement', () => { + assert(type(document.createElement('Img')) === 'HTMLImageElement'); + }); + + it('HTMLInputElement', () => { + assert(type(document.createElement('Input')) === 'HTMLInputElement'); + }); + + it('HTMLLIElement', () => { + assert(type(document.createElement('LI')) === 'HTMLLIElement'); + }); + + it('HTMLLabelElement', () => { + assert(type(document.createElement('Label')) === 'HTMLLabelElement'); + }); + + it('HTMLLegendElement', () => { + assert(type(document.createElement('Legend')) === 'HTMLLegendElement'); + }); + + it('HTMLLinkElement', () => { + assert(type(document.createElement('Link')) === 'HTMLLinkElement'); + }); + + it('HTMLMapElement', () => { + assert(type(document.createElement('Map')) === 'HTMLMapElement'); + }); + + it('HTMLMetaElement', () => { + assert(type(document.createElement('Meta')) === 'HTMLMetaElement'); + }); + + itIf(typeof HTMLMeterElement !== 'undefined')('HTMLMeterElement', () => { + assert(type(document.createElement('Meter')) === 'HTMLMeterElement'); + }); + + it('HTMLModElement', () => { + assert(type(document.createElement('Del')) === 'HTMLModElement'); + }); + + it('HTMLOListElement', () => { + assert(type(document.createElement('OL')) === 'HTMLOListElement'); + }); + + it('HTMLOptGroupElement', () => { + assert(type(document.createElement('OptGroup')) === 'HTMLOptGroupElement'); + }); + + it('HTMLOptionElement', () => { + assert(type(document.createElement('Option')) === 'HTMLOptionElement'); + }); + + itIf(typeof HTMLOutputElement !== 'undefined')('HTMLOutputElement', () => { + assert(type(document.createElement('Output')) === 'HTMLOutputElement'); + }); + + it('HTMLParagraphElement', () => { + assert(type(document.createElement('P')) === 'HTMLParagraphElement'); + }); + + it('HTMLParamElement', () => { + assert(type(document.createElement('Param')) === 'HTMLParamElement'); + }); + + it('HTMLPreElement', () => { + assert(type(document.createElement('Pre')) === 'HTMLPreElement'); + }); + + itIf(typeof HTMLProgressElement !== 'undefined')('HTMLProgressElement', () => { + assert(type(document.createElement('Progress')) === 'HTMLProgressElement'); + }); + + it('HTMLQuoteElement', () => { + assert(type(document.createElement('BlockQuote')) === 'HTMLQuoteElement'); + assert(type(document.createElement('Q')) === 'HTMLQuoteElement'); + }); + + it('HTMLScriptElement', () => { + assert(type(document.createElement('Script')) === 'HTMLScriptElement'); + }); + + it('HTMLSelectElement', () => { + assert(type(document.createElement('Select')) === 'HTMLSelectElement'); + }); + + it('HTMLSpanElement', () => { + assert(type(document.createElement('Span')) === 'HTMLSpanElement'); + }); + + it('HTMLStyleElement', () => { + assert(type(document.createElement('Style')) === 'HTMLStyleElement'); + }); + + it('HTMLTableCaptionElement', () => { + assert(type(document.createElement('Caption')) === 'HTMLTableCaptionElement'); + }); + + it('HTMLTableCellElement', () => { + assert(type(document.createElement('TD')) === 'HTMLTableCellElement'); + }); + + it('HTMLTableHeaderCellElement', () => { + assert(type(document.createElement('TH')) === 'HTMLTableCellElement'); + }); + + it('HTMLTableColElement', () => { + assert(type(document.createElement('Col')) === 'HTMLTableColElement'); + assert(type(document.createElement('ColGroup')) === 'HTMLTableColElement'); + }); + + it('HTMLTableElement', () => { + assert(type(document.createElement('Table')) === 'HTMLTableElement'); + }); + + it('HTMLTableRowElement', () => { + assert(type(document.createElement('TR')) === 'HTMLTableRowElement'); + }); + + it('HTMLTableSectionElement', () => { + assert(type(document.createElement('THead')) === 'HTMLTableSectionElement'); + assert(type(document.createElement('TBody')) === 'HTMLTableSectionElement'); + assert(type(document.createElement('TFoot')) === 'HTMLTableSectionElement'); + }); + + it('HTMLTextAreaElement', () => { + assert(type(document.createElement('TextArea')) === 'HTMLTextAreaElement'); + }); + + it('HTMLTitleElement', () => { + assert(type(document.createElement('Title')) === 'HTMLTitleElement'); + }); + + it('HTMLUListElement', () => { + assert(type(document.createElement('UL')) === 'HTMLUListElement'); + }); + + it('HTMLUnknownElement', () => { + assert(type(document.createElement('foobarbaz')) === 'HTMLUnknownElement'); + }); + + }); + +}); diff --git a/test/type-detect/index.js b/test/type-detect/index.js new file mode 100644 index 00000000..07896b43 --- /dev/null +++ b/test/type-detect/index.js @@ -0,0 +1,277 @@ +function assert (expr, msg) { + if (!expr) { + throw new Error(msg || 'Assertion Failed'); + } +} + +const type = chai.util.type + +describe('Generic', () => { + + it('array', () => { + assert(type([]) === 'Array'); + assert(type(new Array()) === 'Array'); + }); + + it('regexp', () => { + assert(type(/a-z/gi) === 'RegExp'); + assert(type(new RegExp('a-z')) === 'RegExp'); + }); + + it('function', () => { + assert(type(() => {}) === 'Function'); + }); + + it('arguments', function () { + assert(type(arguments) === 'Arguments'); + }); + + it('date', () => { + assert(type(new Date()) === 'Date'); + }); + + it('number', () => { + assert(type(1) === 'Number'); + assert(type(1.234) === 'Number'); + assert(type(-1) === 'Number'); + assert(type(-1.234) === 'Number'); + assert(type(Infinity) === 'Number'); + assert(type(NaN) === 'Number'); + }); + + it('number objects', () => { + assert(type(new Number(2)) === 'Number'); + }); + + it('string', () => { + assert(type('hello world') === 'String'); + }); + + it('string objects', () => { + assert(type(new String('hello')) === 'String'); + }); + + it('null', () => { + assert(type(null) === 'null'); + assert(type(undefined) !== 'null'); + }); + + it('undefined', () => { + assert(type(undefined) === 'undefined'); + assert(type(null) !== 'undefined'); + }); + + it('object', () => { + function Noop() {} + assert(type({}) === 'Object'); + assert(type(Noop) !== 'Object'); + assert(type(new Noop()) === 'Object'); + assert(type(new Object()) === 'Object'); + assert(type(Object.create(null)) === 'Object'); + assert(type(Object.create(Object.prototype)) === 'Object'); + }); + + // See: https://github.com/chaijs/type-detect/pull/25 + it('object with .undefined property getter', () => { + const foo = {}; + Object.defineProperty(foo, 'undefined', { + get() { + throw Error('Should never happen'); + }, + }); + assert(type(foo) === 'Object'); + }); + + it('boolean', () => { + assert(type(true) === 'Boolean'); + assert(type(false) === 'Boolean'); + assert(type(!0) === 'Boolean'); + }); + + it('boolean object', () => { + assert(type(new Boolean()) === 'Boolean'); + }); + + it('error', () => { + assert(type(new Error()) === 'Error'); + assert(type(new TypeError()) === 'Error'); + assert(type(new EvalError()) === 'Error'); + assert(type(new RangeError()) === 'Error'); + assert(type(new ReferenceError()) === 'Error'); + assert(type(new SyntaxError()) === 'Error'); + assert(type(new TypeError()) === 'Error'); + assert(type(new URIError()) === 'Error'); + }); + + it('Math', () => { + assert(type(Math) === 'Math'); + }); + + it('JSON', () => { + assert(type(JSON) === 'JSON'); + }); + + describe('Stubbed ES2015 Types', () => { + const originalObjectToString = Object.prototype.toString; + function stubObjectToStringOnce(staticValue) { + Object.prototype.toString = function () { // eslint-disable-line no-extend-native + Object.prototype.toString = originalObjectToString; // eslint-disable-line no-extend-native + return staticValue; + }; + } + function Thing() {} + + it('map', () => { + stubObjectToStringOnce('[object Map]'); + assert(type(new Thing()) === 'Map'); + }); + + it('weakmap', () => { + stubObjectToStringOnce('[object WeakMap]'); + assert(type(new Thing()) === 'WeakMap'); + }); + + it('set', () => { + stubObjectToStringOnce('[object Set]'); + assert(type(new Thing()) === 'Set'); + }); + + it('weakset', () => { + stubObjectToStringOnce('[object WeakSet]'); + assert(type(new Thing()) === 'WeakSet'); + }); + + it('symbol', () => { + stubObjectToStringOnce('[object Symbol]'); + assert(type(new Thing()) === 'Symbol'); + }); + + it('promise', () => { + stubObjectToStringOnce('[object Promise]'); + assert(type(new Thing()) === 'Promise'); + }); + + it('int8array', () => { + stubObjectToStringOnce('[object Int8Array]'); + assert(type(new Thing()) === 'Int8Array'); + }); + + it('uint8array', () => { + stubObjectToStringOnce('[object Uint8Array]'); + assert(type(new Thing()) === 'Uint8Array'); + }); + + it('uint8clampedarray', () => { + stubObjectToStringOnce('[object Uint8ClampedArray]'); + assert(type(new Thing()) === 'Uint8ClampedArray'); + }); + + it('int16array', () => { + stubObjectToStringOnce('[object Int16Array]'); + assert(type(new Thing()) === 'Int16Array'); + }); + + it('uint16array', () => { + stubObjectToStringOnce('[object Uint16Array]'); + assert(type(new Thing()) === 'Uint16Array'); + }); + + it('int32array', () => { + stubObjectToStringOnce('[object Int32Array]'); + assert(type(new Thing()) === 'Int32Array'); + }); + + it('uint32array', () => { + stubObjectToStringOnce('[object Uint32Array]'); + assert(type(new Thing()) === 'Uint32Array'); + }); + + it('float32array', () => { + stubObjectToStringOnce('[object Float32Array]'); + assert(type(new Thing()) === 'Float32Array'); + }); + + it('float64array', () => { + stubObjectToStringOnce('[object Float64Array]'); + assert(type(new Thing()) === 'Float64Array'); + }); + + it('dataview', () => { + stubObjectToStringOnce('[object DataView]'); + assert(type(new Thing()) === 'DataView'); + }); + + it('arraybuffer', () => { + stubObjectToStringOnce('[object ArrayBuffer]'); + assert(type(new Thing()) === 'ArrayBuffer'); + }); + + it('generatorfunction', () => { + stubObjectToStringOnce('[object GeneratorFunction]'); + assert(type(new Thing()) === 'GeneratorFunction'); + }); + + it('generator', () => { + stubObjectToStringOnce('[object Generator]'); + assert(type(new Thing()) === 'Generator'); + }); + + it('string iterator', () => { + stubObjectToStringOnce('[object String Iterator]'); + assert(type(new Thing()) === 'String Iterator'); + }); + + it('array iterator', () => { + stubObjectToStringOnce('[object Array Iterator]'); + assert(type(new Thing()) === 'Array Iterator'); + }); + + it('map iterator', () => { + stubObjectToStringOnce('[object Map Iterator]'); + assert(type(new Thing()) === 'Map Iterator'); + }); + + it('set iterator', () => { + stubObjectToStringOnce('[object Set Iterator]'); + assert(type(new Thing()) === 'Set Iterator'); + }); + + }); + + describe('@@toStringTag Sham', () => { + const originalObjectToString = Object.prototype.toString; + before(() => { + const globalObject = typeof self === 'object' ? self : global; + globalObject.Symbol = globalObject.Symbol || {}; + if (!Symbol.toStringTag) { + Symbol.toStringTag = '__@@toStringTag__'; + } + const test = {}; + test[Symbol.toStringTag] = function () { + return 'foo'; + }; + if (Object.prototype.toString(test) !== '[object foo]') { + Object.prototype.toString = function () { // eslint-disable-line no-extend-native + if (typeof this === 'object' && typeof this[Symbol.toStringTag] === 'function') { + return `[object ${ this[Symbol.toStringTag]() }]`; + } + return originalObjectToString.call(this); + }; + } + }); + + after(() => { + Object.prototype.toString = originalObjectToString; // eslint-disable-line no-extend-native + }); + + it('plain object', () => { + const obj = {}; + obj[Symbol.toStringTag] = function () { + return 'Foo'; + }; + assert(type(obj) === 'Foo', 'type(obj) === "Foo"'); + }); + + }); + +}); diff --git a/test/type-detect/new-ecmascript-types.js b/test/type-detect/new-ecmascript-types.js new file mode 100644 index 00000000..977ac0eb --- /dev/null +++ b/test/type-detect/new-ecmascript-types.js @@ -0,0 +1,141 @@ +function assert (expr, msg) { + if (!expr) { + throw new Error(msg || 'Assertion Failed'); + } +} + +const type = chai.util.type + +const symbolExists = typeof Symbol === 'function'; +const setExists = typeof Set === 'function'; +const mapExists = typeof Map === 'function'; +let supportArrows = false; +let supportGenerators = false; +try { + eval('function * foo () {}; foo'); // eslint-disable-line no-eval + supportGenerators = true; +} catch (error) { + supportGenerators = false; +} +try { + eval('() => {}'); // eslint-disable-line no-eval + supportArrows = true; +} catch (error) { + supportArrows = false; +} +function itIf(condition) { + return condition ? it : it.skip; +} + +describe('ES2015 Specific', () => { + itIf(symbolExists && typeof String.prototype[Symbol.iterator] === 'function')('string iterator', () => { + assert(type(''[Symbol.iterator]()) === 'String Iterator'); + }); + + itIf(symbolExists && typeof Array.prototype[Symbol.iterator] === 'function')('array iterator', () => { + assert(type([][Symbol.iterator]()) === 'Array Iterator'); + }); + + itIf(typeof Array.prototype.entries === 'function')('array iterator (entries)', () => { + assert(type([].entries()) === 'Array Iterator'); + }); + + itIf(mapExists)('map', () => { + assert(type(new Map()) === 'Map'); + }); + + itIf(symbolExists && mapExists && typeof Map.prototype[Symbol.iterator] === 'function')('map iterator', () => { + assert(type(new Map()[Symbol.iterator]()) === 'Map Iterator'); + }); + + itIf(mapExists && typeof Map.prototype.entries === 'function')('map iterator (entries)', () => { + assert(type(new Map().entries()) === 'Map Iterator'); + }); + + itIf(typeof WeakMap === 'function')('weakmap', () => { + assert(type(new WeakMap()) === 'WeakMap'); + }); + + itIf(setExists)('set', () => { + assert(type(new Set()) === 'Set'); + }); + + itIf(symbolExists && setExists && typeof Set.prototype[Symbol.iterator] === 'function')('set iterator', () => { + assert(type(new Set()[Symbol.iterator]()) === 'Set Iterator'); + }); + + itIf(setExists && typeof Set.prototype.entries === 'function')('set iterator', () => { + assert(type(new Set().entries()) === 'Set Iterator'); + }); + + itIf(typeof WeakSet === 'function')('weakset', () => { + assert(type(new WeakSet()) === 'WeakSet'); + }); + + itIf(typeof Symbol === 'function')('symbol', () => { + assert(type(Symbol('foo')) === 'Symbol'); + }); + + itIf(typeof Promise === 'function')('promise', () => { + function noop() {} + assert(type(new Promise(noop)) === 'Promise'); + }); + + itIf(typeof Int8Array === 'function')('int8array', () => { + assert(type(new Int8Array()) === 'Int8Array'); + }); + + itIf(typeof Uint8Array === 'function')('uint8array', () => { + assert(type(new Uint8Array()) === 'Uint8Array'); + }); + + itIf(typeof Uint8ClampedArray === 'function')('uint8clampedarray', () => { + assert(type(new Uint8ClampedArray()) === 'Uint8ClampedArray'); + }); + + itIf(typeof Int16Array === 'function')('int16array', () => { + assert(type(new Int16Array()) === 'Int16Array'); + }); + + itIf(typeof Uint16Array === 'function')('uint16array', () => { + assert(type(new Uint16Array()) === 'Uint16Array'); + }); + + itIf(typeof Int32Array === 'function')('int32array', () => { + assert(type(new Int32Array()) === 'Int32Array'); + }); + + itIf(typeof Uint32Array === 'function')('uint32array', () => { + assert(type(new Uint32Array()) === 'Uint32Array'); + }); + + itIf(typeof Float32Array === 'function')('float32array', () => { + assert(type(new Float32Array()) === 'Float32Array'); + }); + + itIf(typeof Float64Array === 'function')('float64array', () => { + assert(type(new Float64Array()) === 'Float64Array'); + }); + + itIf(typeof DataView === 'function')('dataview', () => { + const arrayBuffer = new ArrayBuffer(1); + assert(type(new DataView(arrayBuffer)) === 'DataView'); + }); + + itIf(typeof ArrayBuffer === 'function')('arraybuffer', () => { + assert(type(new ArrayBuffer(1)) === 'ArrayBuffer'); + }); + + itIf(supportArrows)('arrow function', () => { + assert(type(eval('() => {}')) === 'Function'); // eslint-disable-line no-eval + }); + + itIf(supportGenerators)('generator function', () => { + assert(type(eval('function * foo () {}; foo')) === 'GeneratorFunction'); // eslint-disable-line no-eval + }); + + itIf(supportGenerators)('generator', () => { + assert(type(eval('(function * foo () {}())')) === 'Generator'); // eslint-disable-line no-eval + }); + +}); diff --git a/test/type-detect/node.js b/test/type-detect/node.js new file mode 100644 index 00000000..c01c3557 --- /dev/null +++ b/test/type-detect/node.js @@ -0,0 +1,23 @@ +function assert (expr, msg) { + if (!expr) { + throw new Error(msg || 'Assertion Failed'); + } +} + +const type = chai.util.type + +const isNode = typeof process !== 'undefined' && typeof process.release === 'object' && process.release.name; +function describeIf(condition) { + return condition ? describe : describe.skip; +} +describeIf(isNode)('Node Specific', () => { + + it('global', () => { + assert(type(global) === 'global'); + }); + + it('process', () => { + assert(type(process) === 'process'); + }); + +}); diff --git a/test/type-detect/tostringtag-extras.js b/test/type-detect/tostringtag-extras.js new file mode 100644 index 00000000..ccef8dae --- /dev/null +++ b/test/type-detect/tostringtag-extras.js @@ -0,0 +1,25 @@ +function assert (expr, msg) { + if (!expr) { + throw new Error(msg || 'Assertion Failed'); + } +} + +const type = chai.util.type + +const symbolExists = typeof Symbol === 'function'; +const symbolToStringTagExists = symbolExists && typeof Symbol.toStringTag !== 'undefined'; +function describeIf(condition) { + return condition ? describe : describe.skip; +} + +describeIf(symbolToStringTagExists)('toStringTag extras', () => { + + it('supports toStringTag on arrays', () => { + assert(type([]) === 'Array'); + const arr = []; + arr[Symbol.toStringTag] = 'foo'; + assert(type(arr) === 'foo', 'type(arr) === "foo"'); + }); + + +}); From f80e6bff1761161c828e709d6d7820aa006931e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristj=C3=A1n=20Oddsson?= Date: Fri, 20 Oct 2023 12:18:34 +0200 Subject: [PATCH 3/4] update type-detect function --- lib/chai/utils/getOperator.js | 2 +- lib/chai/utils/type-detect.js | 24 +++++++++++------------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/lib/chai/utils/getOperator.js b/lib/chai/utils/getOperator.js index 29a4f239..a2afa505 100644 --- a/lib/chai/utils/getOperator.js +++ b/lib/chai/utils/getOperator.js @@ -3,7 +3,7 @@ import {type} from './type-detect.js'; function isObjectType(obj) { var objectType = type(obj); - var objectTypes = ['Array', 'Object', 'function']; + var objectTypes = ['Array', 'Object', 'Function']; return objectTypes.indexOf(objectType) !== -1; } diff --git a/lib/chai/utils/type-detect.js b/lib/chai/utils/type-detect.js index b0aab2d4..5a86a6ab 100644 --- a/lib/chai/utils/type-detect.js +++ b/lib/chai/utils/type-detect.js @@ -1,18 +1,16 @@ -const typesToLowerCase = [ - "Number", - "String", - "Boolean", - "Null", - "Undefined", - "Function", - "Symbol", -]; - export function type(obj) { - const type = Object.prototype.toString.call(obj).slice(8, -1); + if (typeof obj === 'undefined') { + return 'undefined'; + } + + if (obj === null) { + return 'null'; + } - if (typesToLowerCase.includes(type)) { - return type.toLowerCase(); + const stringTag = obj[Symbol.toStringTag]; + if (typeof stringTag === 'string') { + return stringTag; } + const type = Object.prototype.toString.call(obj).slice(8, -1); return type; } From 557dbef3da82069abc65b0f33392d5c590e45652 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristj=C3=A1n=20Oddsson?= Date: Fri, 20 Oct 2023 12:19:22 +0200 Subject: [PATCH 4/4] update existing tests --- test/assert.js | 18 +++++++++--------- test/bootstrap/index.js | 2 +- test/expect.js | 12 ++++++------ test/should.js | 10 +++++----- 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/test/assert.js b/test/assert.js index 78b669cb..0f24b40e 100644 --- a/test/assert.js +++ b/test/assert.js @@ -171,11 +171,11 @@ describe('assert', function () { err(function(){ assert.instanceOf(new Foo(), 1, 'blah'); - }, "blah: The instanceof assertion needs a constructor but number was given."); + }, "blah: The instanceof assertion needs a constructor but Number was given."); err(function(){ assert.instanceOf(new Foo(), 'batman'); - }, "The instanceof assertion needs a constructor but string was given."); + }, "The instanceof assertion needs a constructor but String was given."); err(function(){ assert.instanceOf(new Foo(), {}); @@ -183,7 +183,7 @@ describe('assert', function () { err(function(){ assert.instanceOf(new Foo(), true); - }, "The instanceof assertion needs a constructor but boolean was given."); + }, "The instanceof assertion needs a constructor but Boolean was given."); err(function(){ assert.instanceOf(new Foo(), null); @@ -198,12 +198,12 @@ describe('assert', function () { var t = new Thing(); Thing.prototype = 1337; assert.instanceOf(t, Thing); - }, 'The instanceof assertion needs a constructor but function was given.', true); + }, 'The instanceof assertion needs a constructor but Function was given.', true); if (typeof Symbol !== 'undefined' && typeof Symbol.hasInstance !== 'undefined') { err(function(){ assert.instanceOf(new Foo(), Symbol()); - }, "The instanceof assertion needs a constructor but symbol was given."); + }, "The instanceof assertion needs a constructor but Symbol was given."); err(function() { var FakeConstructor = {}; @@ -233,11 +233,11 @@ describe('assert', function () { err(function(){ assert.notInstanceOf(new Foo(), 1, 'blah'); - }, "blah: The instanceof assertion needs a constructor but number was given."); + }, "blah: The instanceof assertion needs a constructor but Number was given."); err(function(){ assert.notInstanceOf(new Foo(), 'batman'); - }, "The instanceof assertion needs a constructor but string was given."); + }, "The instanceof assertion needs a constructor but String was given."); err(function(){ assert.notInstanceOf(new Foo(), {}); @@ -245,7 +245,7 @@ describe('assert', function () { err(function(){ assert.notInstanceOf(new Foo(), true); - }, "The instanceof assertion needs a constructor but boolean was given."); + }, "The instanceof assertion needs a constructor but Boolean was given."); err(function(){ assert.notInstanceOf(new Foo(), null); @@ -258,7 +258,7 @@ describe('assert', function () { if (typeof Symbol !== 'undefined' && typeof Symbol.hasInstance !== 'undefined') { err(function(){ assert.notInstanceOf(new Foo(), Symbol()); - }, "The instanceof assertion needs a constructor but symbol was given."); + }, "The instanceof assertion needs a constructor but Symbol was given."); err(function() { var FakeConstructor = {}; diff --git a/test/bootstrap/index.js b/test/bootstrap/index.js index 4338b7ef..a59c5bad 100644 --- a/test/bootstrap/index.js +++ b/test/bootstrap/index.js @@ -30,7 +30,7 @@ if (typeof Error.captureStackTrace !== 'undefined') { */ globalThis.err = function globalErr (fn, val, skipStackTest) { - if (chai.util.type(fn) !== 'function') + if (chai.util.type(fn) !== 'Function') throw new chai.AssertionError('Invalid fn'); try { diff --git a/test/expect.js b/test/expect.js index 0ce003a8..9a018be0 100644 --- a/test/expect.js +++ b/test/expect.js @@ -403,15 +403,15 @@ describe('expect', function () { err(function(){ expect(new Foo()).to.an.instanceof(1, 'blah'); - }, "blah: The instanceof assertion needs a constructor but number was given."); + }, "blah: The instanceof assertion needs a constructor but Number was given."); err(function(){ expect(new Foo(), 'blah').to.an.instanceof(1); - }, "blah: The instanceof assertion needs a constructor but number was given."); + }, "blah: The instanceof assertion needs a constructor but Number was given."); err(function(){ expect(new Foo()).to.an.instanceof('batman'); - }, "The instanceof assertion needs a constructor but string was given."); + }, "The instanceof assertion needs a constructor but String was given."); err(function(){ expect(new Foo()).to.an.instanceof({}); @@ -419,7 +419,7 @@ describe('expect', function () { err(function(){ expect(new Foo()).to.an.instanceof(true); - }, "The instanceof assertion needs a constructor but boolean was given."); + }, "The instanceof assertion needs a constructor but Boolean was given."); err(function(){ expect(new Foo()).to.an.instanceof(null); @@ -434,12 +434,12 @@ describe('expect', function () { var t = new Thing(); Thing.prototype = 1337; expect(t).to.an.instanceof(Thing); - }, 'The instanceof assertion needs a constructor but function was given.', true) + }, 'The instanceof assertion needs a constructor but Function was given.', true) if (typeof Symbol !== 'undefined' && typeof Symbol.hasInstance !== 'undefined') { err(function(){ expect(new Foo()).to.an.instanceof(Symbol()); - }, "The instanceof assertion needs a constructor but symbol was given."); + }, "The instanceof assertion needs a constructor but Symbol was given."); err(function() { var FakeConstructor = {}; diff --git a/test/should.js b/test/should.js index f8367eee..7ade2c85 100644 --- a/test/should.js +++ b/test/should.js @@ -468,11 +468,11 @@ describe('should', function() { err(function(){ new Foo().should.be.an.instanceof(1, 'blah'); - }, "blah: The instanceof assertion needs a constructor but number was given."); + }, "blah: The instanceof assertion needs a constructor but Number was given."); err(function(){ new Foo().should.be.an.instanceof('batman'); - }, "The instanceof assertion needs a constructor but string was given."); + }, "The instanceof assertion needs a constructor but String was given."); err(function(){ new Foo().should.be.an.instanceof({}); @@ -480,7 +480,7 @@ describe('should', function() { err(function(){ new Foo().should.be.an.instanceof(true); - }, "The instanceof assertion needs a constructor but boolean was given."); + }, "The instanceof assertion needs a constructor but Boolean was given."); err(function(){ new Foo().should.be.an.instanceof(null); @@ -495,12 +495,12 @@ describe('should', function() { var t = new Thing(); Thing.prototype = 1337; t.should.be.an.instanceof(Thing); - }, 'The instanceof assertion needs a constructor but function was given.', true); + }, 'The instanceof assertion needs a constructor but Function was given.', true); if (typeof Symbol !== 'undefined' && typeof Symbol.hasInstance !== 'undefined') { err(function(){ new Foo().should.be.an.instanceof(Symbol()); - }, "The instanceof assertion needs a constructor but symbol was given."); + }, "The instanceof assertion needs a constructor but Symbol was given."); err(function() { var FakeConstructor = {};