diff --git a/exercises/concept/recycling-robot/.docs/hints.md b/exercises/concept/recycling-robot/.docs/hints.md index ebf8921f38..58e1f325dc 100644 --- a/exercises/concept/recycling-robot/.docs/hints.md +++ b/exercises/concept/recycling-robot/.docs/hints.md @@ -40,14 +40,14 @@ - `typeof` returns a string. - You can check the length of an array to find out how many elements it contains. -## 8. Throw an error if an object does not have the `id` property or method +## 8. Check if an object has a `type` property or method - You can use the `in` operator to check if an object has a property or method. -- If the `id` property or method is missing, your function should throw an `Error`. -## 9. Check if an object has a `type` property or method +## 9. Throw an error if an object does not have the `id` property or method - You can use the `in` operator to check if an object has a property or method. +- If the `id` property or method is missing, your function should throw an `Error`. ## 10. Check if an object has an `id` property diff --git a/exercises/concept/recycling-robot/.docs/instructions.md b/exercises/concept/recycling-robot/.docs/instructions.md index 1ed7f89b40..c6ddbd2f63 100644 --- a/exercises/concept/recycling-robot/.docs/instructions.md +++ b/exercises/concept/recycling-robot/.docs/instructions.md @@ -52,7 +52,8 @@ isObject(25n); ### 4. Check if a string is numeric -Implement the `isNumericString` function, that should check if the value is a string that only consists of digits. +Implement the `isNumericString` function, that should check if the value is a string that only consists of digits or a minus followed by digits indicating a negative number. +Only integers should be considered, decimals are not considered numeric for this check of the recycling robot. ```javascript isNumericString(42); @@ -109,21 +110,7 @@ isEmptyArray([]); // => true ``` -### 8. Throw an error if an object does not have an `id` property or method - -Implement the `assertHasId` function, that will throw an `Error` if an object is missing the `id` property. - -If an object does have the `id` property, it should not return anything. - -```javascript -assertHasId({ id: 42, color: 'red' }); -// => undefined - -assertHasId({ color: 'green' }); -// Error: "Object is missing the 'id' property" -``` - -### 9. Check if an object has a `type` property or method +### 8. Check if an object has a `type` property or method Implement the `hasType` function, that checks whether an object has a `type` property or method. @@ -133,40 +120,62 @@ class Keyboard(){ // ... } } -hasType({type:"car",color:"red"}) +hasType({ type:"car", color:"red" }) // => true -hasType({color:"green"}) +hasType({ color:"green" }) // => false hasType(new Keyboard()) // => true ``` +### 9. Throw an error if an object does not have an `id` property or method + +Implement the `assertHasId` function, that will throw an `Error` if an object is missing the `id` property. + +If an object does have the `id` property, it should not return anything. + +```javascript +assertHasId({ id: 42, color: 'red' }); +// => undefined + +assertHasId({ color: 'green' }); +// Error: "Object is missing the 'id' property" +``` + ### 10. Check if an object has an `id` property Implement the `hasIdProperty` function, that checks whether an object has an `id` property. ```javascript -class MyClass { +class SimpleData { constructor() { this.number = '42'; this.id = 'BC269327FE1D9B95'; } } -class MyNewClass { + +class StealingData extends SimpleData {} + +class MethodData { constructor() { this.number = '42'; this._id = 'BC269327FE1D9B95'; } + get id() { return this._id; } } -hasIdProperty(new MyClass()); + +hasIdProperty(new SimpleData()); // => true -hasIdProperty(new MyNewClass()); +hasIdProperty(new MethodData()); +// => false + +hasIdProperty(new StealingData()); // => false ``` diff --git a/exercises/concept/recycling-robot/.meta/exemplar.js b/exercises/concept/recycling-robot/.meta/exemplar.js index 28b8b48beb..e2c4bf84f1 100644 --- a/exercises/concept/recycling-robot/.meta/exemplar.js +++ b/exercises/concept/recycling-robot/.meta/exemplar.js @@ -24,9 +24,7 @@ export function isBoolean(value) { */ export function isNumber(value) { return ( - (typeof value === 'number' || typeof value === 'bigint') && - !isNaN(Number(value)) && - value !== Infinity + (typeof value === 'number' && isFinite(value)) || typeof value === 'bigint' ); } @@ -47,12 +45,7 @@ export function isObject(value) { * @returns {boolean} whether the input is a numeric string. */ export function isNumericString(value) { - return ( - typeof value === 'string' && - value.split('').every((char) => { - return /[0-9]/.test(char); - }) - ); + return typeof value === 'string' && /^-?\d+$/.test(value); } /** @@ -86,26 +79,27 @@ export function isEmptyArray(value) { } /** - * Throws an error if an object is missing an "id" property or method. + * Checks if a value has a "type" property or method. * * @param {object} object - * @returns {boolean} undefined if the input has an "id" property, otherwise throws an error. + * @returns {boolean} whether the input has a "type" property. */ -export function assertHasId(object) { - if ('id' in object) { - return; - } - throw new Error('The "id" property is missing.'); +export function hasType(object) { + return 'type' in object; } /** - * Checks if a value has a "type" property or method. + * Throws an error if an object is missing an "id" property or method. * * @param {object} object - * @returns {boolean} whether the input has a "type" property. + * @returns {never|void} undefined if the input has an "id" property, otherwise throws an error. */ -export function hasType(object) { - return 'type' in object; +export function assertHasId(object) { + if ('id' in object) { + return; + } + + throw new Error('The "id" property is missing.'); } /** diff --git a/exercises/concept/recycling-robot/assembly-line.js b/exercises/concept/recycling-robot/assembly-line.js index 65727895a5..bdd32f2d47 100644 --- a/exercises/concept/recycling-robot/assembly-line.js +++ b/exercises/concept/recycling-robot/assembly-line.js @@ -81,23 +81,23 @@ export function isEmptyArray(value) { } /** - * Throws an error if an object is missing an "id" property or method. + * Checks if a value has a "type" property or method. * * @param {object} object - * @returns {undefined} undefined if the input has an "id" property or method, otherwise throws an error. + * @returns {boolean} whether the input has a "type" property or method. */ -export function assertHasId(object) { - throw new Error('Remove this line and implement the assertHasId function'); +export function hasType(object) { + throw new Error('Remove this line and implement the hasType function'); } /** - * Checks if a value has a "type" property or method. + * Throws an error if an object is missing an "id" property or method. * * @param {object} object - * @returns {boolean} whether the input has a "type" property or method. + * @returns {never|void} undefined if the input has an "id" property or method, otherwise throws an error. */ -export function hasType(object) { - throw new Error('Remove this line and implement the hasType function'); +export function assertHasId(object) { + throw new Error('Remove this line and implement the assertHasId function'); } /** diff --git a/exercises/concept/recycling-robot/assembly-line.spec.js b/exercises/concept/recycling-robot/assembly-line.spec.js index e23b6dc463..d338348dff 100644 --- a/exercises/concept/recycling-robot/assembly-line.spec.js +++ b/exercises/concept/recycling-robot/assembly-line.spec.js @@ -15,40 +15,109 @@ import { import { ElectronicDevice } from './lib.js'; describe('isBoolean', () => { - test('isBoolean works on booleans', () => { + test('returns true for true', () => { expect(isBoolean(true)).toBe(true); + }); + + test('returns true for false', () => { expect(isBoolean(false)).toBe(true); }); - test('isBoolean works on non-booleans', () => { + + test('returns false for numbers', () => { expect(isBoolean(42)).toBe(false); + expect(isBoolean(42n)).toBe(false); + expect(isBoolean(0)).toBe(false); + }); + + test('returns false for strings', () => { expect(isBoolean('Hello, World!')).toBe(false); - expect(isBoolean(null)).toBe(false); + expect(isBoolean('42')).toBe(false); + expect(isBoolean('true')).toBe(false); expect(isBoolean('')).toBe(false); + }); + + test('returns false for null', () => { + expect(isBoolean(null)).toBe(false); + }); + + test('returns false for undefined', () => { + expect(isBoolean(undefined)).toBe(false); + }); + + test('returns false for symbols', () => { expect(isBoolean(Symbol('1'))).toBe(false); + expect(isBoolean(Symbol('true'))).toBe(false); + }); + + test('returns false for objects', () => { + expect(isBoolean({})).toBe(false); + expect(isBoolean({ true: false })).toBe(false); + }); + + test('returns false for arrays', () => { + expect(isBoolean([])).toBe(false); + expect(isBoolean([true, false])).toBe(false); }); }); describe('isNumber', () => { - test('isNumber works on numbers', () => { + test('returns true for numbers', () => { expect(isNumber(42)).toBe(true); - expect(isNumber(92)).toBe(true); - expect(isNumber(43859435.12)).toBe(true); + expect(isNumber(0)).toBe(true); + expect(isNumber(43_859_435.12)).toBe(true); + expect(isNumber(Number.MAX_SAFE_INTEGER)).toBe(true); + expect(isNumber(Number.MAX_VALUE)).toBe(true); + expect(isNumber(Number.MIN_SAFE_INTEGER)).toBe(true); + expect(isNumber(Number.MIN_VALUE)).toBe(true); }); - test('isNumber works on bigints', () => { + + test('returns true for bigints', () => { expect(isNumber(42n)).toBe(true); + expect(isNumber(0n)).toBe(true); expect(isNumber(92n)).toBe(true); - expect(isNumber(1848958451n)).toBe(true); + expect(isNumber(1_848_958_451n)).toBe(true); + expect(isNumber(9_007_199_254_740_991n)).toBe(true); + expect(isNumber(9_999_999_999_999_999n)).toBe(true); }); - test('isNumber works on non-numbers', () => { - expect(isNumber(true)).toBe(false); + + test('returns false for non-finite numbers such as NaN', () => { + expect(isNumber(NaN)).toBe(false); + expect(isNumber(Infinity)).toBe(false); + }); + + test('returns false for strings', () => { expect(isNumber('Hello, World!')).toBe(false); - expect(isNumber(null)).toBe(false); + expect(isNumber('42')).toBe(false); + expect(isNumber('true')).toBe(false); expect(isNumber('')).toBe(false); + }); + + test('returns false for null', () => { + expect(isNumber(null)).toBe(false); + }); + + test('returns false for undefined', () => { + expect(isNumber(undefined)).toBe(false); + }); + + test('returns false for symbols', () => { expect(isNumber(Symbol('1'))).toBe(false); + expect(isNumber(Symbol('true'))).toBe(false); }); - test('isNumber works on NaN and Infinity', () => { - expect(isNumber(NaN)).toBe(false); - expect(isNumber(Infinity)).toBe(false); + + test('returns false for objects', () => { + expect(isNumber({})).toBe(false); + expect(isNumber({ true: false })).toBe(false); + }); + + test('returns false for arrays', () => { + expect(isNumber([])).toBe(false); + expect(isNumber([1])).toBe(false); + }); + + test('returns false for booleans', () => { + expect(isNumber(true)).toBe(false); + expect(isNumber(false)).toBe(false); }); }); @@ -57,47 +126,115 @@ class ClassForTesting { this.number = number; this.word = word; } + id() {} } describe('isObject', () => { - test('isObject works on objects', () => { + test('returns true on object literals', () => { expect(isObject({})).toBe(true); expect(isObject({ greeting: 'hello' })).toBe(true); }); - test('isObject works on class instances', () => { + + test('returns true on class instances', () => { expect(isObject(new ClassForTesting(5, 'Hello'))).toBe(true); expect(isObject(new ClassForTesting(58, 'null'))).toBe(true); expect(isObject(new ClassForTesting(1488, 'World!'))).toBe(true); }); - test('isObject works on non-Objects', () => { - expect(isObject(true)).toBe(false); + + test('returns true for arrays which are objects', () => { + expect(isObject([])).toBe(true); + expect(isObject([{}])).toBe(true); + }); + + test('returns false on functions', () => { + expect(isObject(isObject)).toBe(false); + expect(isObject(() => {})).toBe(false); + expect(isObject(() => ({}))).toBe(false); + }); + + test('returns false for strings', () => { expect(isObject('Hello, World!')).toBe(false); - expect(isObject(undefined)).toBe(false); + expect(isObject('{}')).toBe(false); + expect(isObject('42')).toBe(false); + expect(isObject('true')).toBe(false); expect(isObject('')).toBe(false); - expect(isObject(Symbol('1'))).toBe(false); }); - test('isObject works on null', () => { + + test('returns false for null', () => { expect(isObject(null)).toBe(false); }); + + test('returns false for undefined', () => { + expect(isObject(undefined)).toBe(false); + }); + + test('returns false for symbols', () => { + expect(isObject(Symbol('1'))).toBe(false); + expect(isObject(Symbol('true'))).toBe(false); + }); + test('returns false for booleans', () => { + expect(isObject(true)).toBe(false); + expect(isObject(false)).toBe(false); + }); }); describe('isNumericString', () => { - test('isNumericString works on numeric strings', () => { + test('returns true on single-digit strings', () => { + expect(isNumericString('1')).toBe(true); + expect(isNumericString('0')).toBe(true); + expect(isNumericString('9')).toBe(true); + }); + + test('returns true on negative single-digit strings', () => { + expect(isNumericString('-1')).toBe(true); + expect(isNumericString('-0')).toBe(true); + expect(isNumericString('-9')).toBe(true); + }); + + test('returns true on multi-digit strings', () => { + expect(isNumericString('12')).toBe(true); + expect(isNumericString('00')).toBe(true); expect(isNumericString('42')).toBe(true); - expect(isNumericString('582')).toBe(true); + expect(isNumericString('-582')).toBe(true); }); - test('isNumericString works on non-numeric strings', () => { + + test('returns false on non-numeric strings', () => { + expect(isNumericString('')).toBe(false); + expect(isNumericString('-')).toBe(false); + expect(isNumericString('--')).toBe(false); + expect(isNumericString('--32')).toBe(false); expect(isNumericString('Hello, World!')).toBe(false); expect(isNumericString('')).toBe(false); expect(isNumericString('NaN')).toBe(false); }); - test('isNumericString works on non-strings', () => { - expect(isNumericString(true)).toBe(false); - expect(isNumericString(1234)).toBe(false); + + test('returns false for bigint strings', () => { + expect(isNumericString('12n')).toBe(false); + expect(isNumericString('-582n')).toBe(false); + }); + + test('returns false for null', () => { + expect(isNumericString(null)).toBe(false); + }); + + test('returns false for undefined', () => { expect(isNumericString(undefined)).toBe(false); - expect(isNumericString([1, 2, 3, 4])).toBe(false); - expect(isNumericString(Symbol('\u0070'))).toBe(false); + }); + + test('returns false for symbols', () => { + expect(isNumericString(Symbol('1'))).toBe(false); + expect(isNumericString(Symbol('true'))).toBe(false); + }); + + test('returns false for arrays', () => { + expect(isNumericString([])).toBe(false); + expect(isNumericString(['42'])).toBe(false); + }); + + test('returns false for booleans', () => { + expect(isNumericString(true)).toBe(false); + expect(isNumericString(false)).toBe(false); }); }); @@ -107,11 +244,26 @@ class PersonalComputer extends Computer {} class HomeMadePersonalComputer extends PersonalComputer {} describe('isElectronic', () => { - test('isElectronic works on instances of ElectronicDevice or its child classes', () => { + test('returns true on ElectronicDevices', () => { expect(isElectronic(new ElectronicDevice())).toBe(true); + }); + + test('returns true on sub-classes of ElectronicDevice', () => { expect(isElectronic(new Oven())).toBe(true); + expect(isElectronic(new PersonalComputer())).toBe(true); + expect(isElectronic(new HomeMadePersonalComputer())).toBe(true); + }); + + test('returns false on electronic devices not created using the constructor', () => { + expect(isElectronic(Object.create(ElectronicDevice.prototype))).toBe(false); + expect(isElectronic({ __proto__: ElectronicDevice.prototype })).toBe(false); + + const fakeDevice = {}; + Object.setPrototypeOf(fakeDevice, ElectronicDevice.prototype); + expect(isElectronic(fakeDevice)).toBe(false); }); - test('isElectronic works on other objects', () => { + + test('returns false on non-electronic device objects', () => { expect(isElectronic({ language: 'javascript', typing: 'dynamic' })).toBe( false, ); @@ -120,85 +272,249 @@ describe('isElectronic', () => { ); expect(isElectronic([1, 2, 3, 4])).toBe(false); }); - test('isElectronic works on non-objects', () => { - expect(isElectronic(true)).toBe(false); - expect(isElectronic(1234)).toBe(false); + + test('returns false for strings', () => { + expect(isElectronic('12n')).toBe(false); + expect(isElectronic('ElectronicDevice')).toBe(false); + }); + + test('returns false for null', () => { + expect(isElectronic(null)).toBe(false); + }); + + test('returns false for undefined', () => { expect(isElectronic(undefined)).toBe(false); - expect(isElectronic('Hello!')).toBe(false); - expect(isElectronic(Symbol('\u0070'))).toBe(false); }); - test('a really long prototype chain', () => { - expect(isElectronic(new HomeMadePersonalComputer())).toBe(true); + + test('returns false for symbols', () => { + expect(isElectronic(Symbol('1'))).toBe(false); + expect(isElectronic(Symbol('true'))).toBe(false); + }); + + test('returns false for arrays', () => { + expect(isElectronic([])).toBe(false); + expect(isElectronic(['42'])).toBe(false); + }); + + test('returns false for booleans', () => { + expect(isElectronic(true)).toBe(false); + expect(isElectronic(false)).toBe(false); }); }); describe('isNonEmptyArray', () => { - test('isNonEmptyArray works on non-empty arrays', () => { + test('returns true for non-empty arrays', () => { expect(isNonEmptyArray([1, 2, 3])).toBe(true); expect(isNonEmptyArray(['a', 'b'])).toBe(true); + + // The prototype of Array is also an array, but in Node it's considered empty + // expect(isNonEmptyArray(Array.prototype)).toBe(true); }); - test('isNonEmptyArray works on empty arrays', () => { + + test('returns false for empty arrays', () => { expect(isNonEmptyArray([])).toBe(false); }); - test('isNonEmptyArray works on non-arrays', () => { - expect(isNonEmptyArray({})).toBe(false); - expect(isNonEmptyArray('string')).toBe(false); - expect(isNonEmptyArray(123)).toBe(false); + + test('returns false for fake non-empty arrays', () => { + expect(isNonEmptyArray({ __proto__: Array.prototype, length: 1 })).toBe( + false, + ); + + const fakeArray = { length: 1 }; + Object.setPrototypeOf(fakeArray, Array.prototype); + expect(isNonEmptyArray(fakeArray)).toBe(false); + }); + + test('returns false for strings', () => { + expect(isNonEmptyArray('12n')).toBe(false); + expect(isNonEmptyArray('[1]')).toBe(false); + }); + + test('returns false for null', () => { + expect(isNonEmptyArray(null)).toBe(false); + }); + + test('returns false for undefined', () => { + expect(isNonEmptyArray(undefined)).toBe(false); + }); + + test('returns false for symbols', () => { + expect(isNonEmptyArray(Symbol('1'))).toBe(false); + expect(isNonEmptyArray(Symbol('[1]'))).toBe(false); + }); + + test('returns false for booleans', () => { + expect(isNonEmptyArray(true)).toBe(false); + expect(isNonEmptyArray(false)).toBe(false); }); }); describe('isEmptyArray', () => { - test('isEmptyArray works on empty arrays', () => { + test('returns true for empty arrays', () => { expect(isEmptyArray([])).toBe(true); }); - test('isEmptyArray works on non-empty arrays', () => { + + test('returns false for non-empty arrays', () => { expect(isEmptyArray([1, 2, 3])).toBe(false); + expect(isEmptyArray(['a', 'b'])).toBe(false); + + // The prototype of Array is also an array, but in Node it's considered empty + // expect(isEmptyArray(Array.prototype)).toBe(false); + }); + + test('returns false on fake empty arrays', () => { + expect(isEmptyArray({ __proto__: Array.prototype, length: 0 })).toBe(false); + expect(isEmptyArray(Object.create(Array.prototype))).toBe(false); + + const fakeArray = {}; + Object.setPrototypeOf(fakeArray, Array.prototype); + expect(isNonEmptyArray(fakeArray)).toBe(false); + }); + + test('returns false for strings', () => { + expect(isEmptyArray('12n')).toBe(false); + expect(isEmptyArray('[]')).toBe(false); }); - test('isEmptyArray works on non-arrays', () => { - expect(isEmptyArray({})).toBe(false); - expect(isEmptyArray('string')).toBe(false); - expect(isEmptyArray(123)).toBe(false); + + test('returns false for null', () => { + expect(isEmptyArray(null)).toBe(false); + }); + + test('returns false for undefined', () => { + expect(isEmptyArray(undefined)).toBe(false); + }); + + test('returns false for symbols', () => { + expect(isEmptyArray(Symbol('1'))).toBe(false); + expect(isEmptyArray(Symbol('[]'))).toBe(false); + }); + + test('returns false for booleans', () => { + expect(isEmptyArray(true)).toBe(false); + expect(isEmptyArray(false)).toBe(false); }); }); -class TestAssertHasId { - id() {} +class MagicInspector { + type() { + return 'sleight of hand'; + } } -describe('assertHasId', () => { - test("assertHasId throws error if object has no 'id' property or method", () => { - expect(() => assertHasId({})).toThrow(); +class MagicRevealer extends MagicInspector { + spill() { + throw new Error('A true magician never reveals their secrets'); + } +} + +describe('hasType', () => { + test('returns true if the type property exists', () => { + expect(hasType({ type: 'car', color: 'red' })).toBe(true); }); - test("assertHasId does not throw error if object has 'id' property or method", () => { - expect(() => assertHasId({ id: 1 })).not.toThrow(); - expect(() => assertHasId(new TestAssertHasId())).not.toThrow(); + + test('returns true if the type method exists', () => { + expect(hasType(new MagicInspector())).toBe(true); + }); + + test('returns true if the type method is inherited', () => { + expect(hasType(new MagicRevealer())).toBe(true); + }); + + test('returns false if neither the type property, nor the method exists', () => { + expect(hasType({ color: 'green' })).toBe(false); }); }); -class TestHasType { - type() {} +class IdGenerator { + id() { + return Math.random() * 42; + } } -describe('hasType', () => { - test('hasType works correctly', () => { - expect(hasType({ type: 'example' })).toBe(true); - expect(hasType({})).toBe(false); - expect(hasType(new TestHasType())).toBe(true); +class MagicIdGenerator extends IdGenerator { + magic() { + return '🔮'; + } +} + +describe('assertHasId', () => { + test('returns nothing if the id property is present', () => { + expect(() => assertHasId({ id: 1 })).not.toThrow(); + expect(() => assertHasId({ id: 42, color: 'red' })).not.toThrow(); + + const oven = new Oven(); + oven.id = 42; + expect(() => assertHasId(oven)).not.toThrow(); + + // Even when there is no ID set + expect(() => assertHasId({ id: null })).not.toThrow(); + }); + + test('returns nothing if the id method is present', () => { + expect(() => assertHasId(new IdGenerator())).not.toThrow(); + }); + + test('returns nothing if the id method is inherited', () => { + expect(() => assertHasId(new MagicIdGenerator())).not.toThrow(); + }); + + test("throws error if object has no 'id' property or method", () => { + expect(() => assertHasId({})).toThrow(Error); + expect(() => assertHasId({ color: 'green' })).toThrow(Error); }); }); +class SimpleData { + constructor() { + this.number = '42'; + this.id = 'BC269327FE1D9B95'; + } +} + +class StealingData extends SimpleData {} + +class MethodData { + constructor() { + this.number = '42'; + this._id = 'BC269327FE1D9B95'; + } + + get id() { + return this._id; + } +} + describe('hasIdProperty', () => { - test('hasIdProperty works correctly', () => { + test('returns true if it has the id property', () => { expect(hasIdProperty({ id: 'test' })).toBe(true); - expect(hasIdProperty({})).toBe(false); - expect(hasIdProperty(new ClassForTesting())).toBe(false); + expect(hasIdProperty(new SimpleData())).toBe(true); + }); + + test('returns false if it does not have the id property', () => { + expect(hasIdProperty(new MethodData())).toBe(false); + expect(hasIdProperty({ color: 'green' })).toBe(false); + }); + + test('returns true if the id property was set in the constructor in the prototype chain', () => { + expect(hasIdProperty(new StealingData())).toBe(true); }); }); describe('hasDefinedType', () => { - test('hasDefinedType works correctly', () => { - expect(hasDefinedType({ type: 'example' })).toBe(true); - expect(hasDefinedType({ type: undefined })).toBe(false); - expect(hasDefinedType({})).toBe(false); + test('returns true if the type property is defined and set', () => { + expect(hasDefinedType({ type: 'car', color: 'green' })).toBe(true); + }); + + test('returns true if the type property is defined and set to an empty value', () => { + expect(hasDefinedType({ type: null, color: 'blue' })).toBe(true); + }); + + test('returns false if the type property is defined but not set', () => { + expect(hasDefinedType({ type: undefined, color: 'red' })).toBe(false); + }); + + test('returns false if the type property is missing', () => { + expect(hasDefinedType({ color: 'white' })).toBe(false); + expect(hasDefinedType(new MagicInspector())).toBe(false); }); }); diff --git a/exercises/concept/recycling-robot/lib.js b/exercises/concept/recycling-robot/lib.js index b0c1963e75..55ce7cadea 100644 --- a/exercises/concept/recycling-robot/lib.js +++ b/exercises/concept/recycling-robot/lib.js @@ -1,3 +1,18 @@ +const certification = Symbol('Certification'); + export class ElectronicDevice { // This class will be used in the exercise. + + static [Symbol.hasInstance](instance) { + return instance && instance.__certification === certification; + } + + constructor() { + Object.defineProperty(this, '__certification', { + enumerable: false, + writable: false, + configurable: false, + value: certification, + }); + } }