diff --git a/concepts/type-checking/.meta/config.json b/concepts/type-checking/.meta/config.json new file mode 100644 index 0000000000..72261751ff --- /dev/null +++ b/concepts/type-checking/.meta/config.json @@ -0,0 +1,8 @@ +{ + "blurb": "Learn how to check the type of a value or object in JavaScript", + "authors": [ + "quintuple-mallard", + "SleeplessByte" + ], + "contributors": [] +} diff --git a/concepts/type-checking/about.md b/concepts/type-checking/about.md new file mode 100644 index 0000000000..d855cc9dda --- /dev/null +++ b/concepts/type-checking/about.md @@ -0,0 +1,164 @@ +# About + +Knowning what the type of a piece of data is, is often very important for code to run smoothly and without errors. + +Javascript has several ways to check the type of a value or object. + +```exercism/note +Javascript's type checking mechanisms can be somewhat unreliable. + +For better type safety and stronger types, you should probably use TypeScript, a language that builds on JavaScript, but with the type syntax of a static-typed language. +``` + +## The `typeof` operator + +The `typeof` operator returns the type of its operand. +The output is a string matching the name of one of the [primitive data types][primitives], except for `"null"`. +It can also be `"function"` or `"object"`. + +```javascript +typeof undefined; +// => "undefined" + +typeof true; +// => "boolean" + +typeof 42; +// => "number" + +typeof 'Hello, World!'; +// => "string" + +typeof function () { + return 'Hello, World'; +}; +// => "function" + +typeof [1, 2, 3, 4]; +// => "object" + +typeof { city: 'Stockholm', country: 'Sweden' }; +// => "object" +``` + +For [historical reasons][`typeof null` is `"object"`]. + +## The `instanceof` operator + +For checking the type of an object, you can use the `instanceof` operator. +It evaluates into a `boolean` depending on whether the second operand is included in the first operands' [prototype chain][prototype chain]. +To clarify, `instanceof` will return whether the first operand is an instance of second operand or one of its child classes. +`instanceof` only works on objects. + +```javascript +class Beverage { + // ... +} + +// The Coffee class is a child of the Beverage class. +class Coffee extends Beverage { + // ... +} + +const java = new Coffee(); + +java instanceof Coffee; +// => true + +java instanceof Beverage; +// => true +``` + +````exercism/advanced +The `Array` class has a method called `Array.isArray()` that checks if its argument is an array. + +While `instanceof Array` will not work with an array created in a different realm such as an `iframe` in a webpage, `Array.isArray()` will. + +This is because the Array class has a different constructor in each realm, and each `iframe` has its own ream, meaning that the function in the prototype chain will be different, causing `instanceof Array` to fail. +`Array.isArray()` is capable of ignoring this, and should always be used when possible. + +It can also survive false positives where an object isn't actually an `Array`, and merely has `Array` in its prototype chain. + +```javascript +({ __proto__: Array.prototype }) instanceof Array +// => true + +Array.isArray({ __proto__: Array.prototype }) +// => false +``` + +```` + +## The `in` operator + +The `in` operator returns whether the first operand is a property of the second operand. +It does not check that the property has a defined value. +A property set to `undefined` will still be detected by `in`. + +```javascript +class Coffee { + constructor() { + this.temperature = 'hot'; + this.isDarkMatter = undefined; + } + + coolDown() { + this.temperature = 'warm'; + } +} + +const espresso = new Coffee(); + +'temperature' in espresso; +// => true + +'color' in espresso; +// => false + +'isDarkMatter' in espresso; +// => true +``` + +````exercism/note +`in` will return `true` for inherited properties and methods. + +```javascript +"coolDown" in espresso +// => true + +"constructor" in espresso +// => true +``` + +To avoid this, use `Object.hasOwn()` instead +```` + +## The `Object.hasOwn()` function + +The `Object.hasOwn()` method returns whether the specified object _owns the given property_ (it is not inherited or a method). + +```javascript +class Coffee { + constructor() { + this.temperature = 'hot'; + } + + coolDown() { + this.temperature = 'warm'; + } +} +const cappuccino = new Coffee(); + +Object.hasOwn(cappucino, 'temperature'); +// => true + +Object.hasOwn(cappucino, 'constructor'); +// => false + +Object.hasOwn(cappucino, 'coolDown'); +// => false +``` + +[primitives]: https://developer.mozilla.org/en-US/docs/Glossary/Primitive +[typeof null is object]: https://2ality.com/2013/10/typeof-null.html +[prototype chain]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Inheritance_and_the_prototype_chain diff --git a/concepts/type-checking/introduction.md b/concepts/type-checking/introduction.md new file mode 100644 index 0000000000..d855cc9dda --- /dev/null +++ b/concepts/type-checking/introduction.md @@ -0,0 +1,164 @@ +# About + +Knowning what the type of a piece of data is, is often very important for code to run smoothly and without errors. + +Javascript has several ways to check the type of a value or object. + +```exercism/note +Javascript's type checking mechanisms can be somewhat unreliable. + +For better type safety and stronger types, you should probably use TypeScript, a language that builds on JavaScript, but with the type syntax of a static-typed language. +``` + +## The `typeof` operator + +The `typeof` operator returns the type of its operand. +The output is a string matching the name of one of the [primitive data types][primitives], except for `"null"`. +It can also be `"function"` or `"object"`. + +```javascript +typeof undefined; +// => "undefined" + +typeof true; +// => "boolean" + +typeof 42; +// => "number" + +typeof 'Hello, World!'; +// => "string" + +typeof function () { + return 'Hello, World'; +}; +// => "function" + +typeof [1, 2, 3, 4]; +// => "object" + +typeof { city: 'Stockholm', country: 'Sweden' }; +// => "object" +``` + +For [historical reasons][`typeof null` is `"object"`]. + +## The `instanceof` operator + +For checking the type of an object, you can use the `instanceof` operator. +It evaluates into a `boolean` depending on whether the second operand is included in the first operands' [prototype chain][prototype chain]. +To clarify, `instanceof` will return whether the first operand is an instance of second operand or one of its child classes. +`instanceof` only works on objects. + +```javascript +class Beverage { + // ... +} + +// The Coffee class is a child of the Beverage class. +class Coffee extends Beverage { + // ... +} + +const java = new Coffee(); + +java instanceof Coffee; +// => true + +java instanceof Beverage; +// => true +``` + +````exercism/advanced +The `Array` class has a method called `Array.isArray()` that checks if its argument is an array. + +While `instanceof Array` will not work with an array created in a different realm such as an `iframe` in a webpage, `Array.isArray()` will. + +This is because the Array class has a different constructor in each realm, and each `iframe` has its own ream, meaning that the function in the prototype chain will be different, causing `instanceof Array` to fail. +`Array.isArray()` is capable of ignoring this, and should always be used when possible. + +It can also survive false positives where an object isn't actually an `Array`, and merely has `Array` in its prototype chain. + +```javascript +({ __proto__: Array.prototype }) instanceof Array +// => true + +Array.isArray({ __proto__: Array.prototype }) +// => false +``` + +```` + +## The `in` operator + +The `in` operator returns whether the first operand is a property of the second operand. +It does not check that the property has a defined value. +A property set to `undefined` will still be detected by `in`. + +```javascript +class Coffee { + constructor() { + this.temperature = 'hot'; + this.isDarkMatter = undefined; + } + + coolDown() { + this.temperature = 'warm'; + } +} + +const espresso = new Coffee(); + +'temperature' in espresso; +// => true + +'color' in espresso; +// => false + +'isDarkMatter' in espresso; +// => true +``` + +````exercism/note +`in` will return `true` for inherited properties and methods. + +```javascript +"coolDown" in espresso +// => true + +"constructor" in espresso +// => true +``` + +To avoid this, use `Object.hasOwn()` instead +```` + +## The `Object.hasOwn()` function + +The `Object.hasOwn()` method returns whether the specified object _owns the given property_ (it is not inherited or a method). + +```javascript +class Coffee { + constructor() { + this.temperature = 'hot'; + } + + coolDown() { + this.temperature = 'warm'; + } +} +const cappuccino = new Coffee(); + +Object.hasOwn(cappucino, 'temperature'); +// => true + +Object.hasOwn(cappucino, 'constructor'); +// => false + +Object.hasOwn(cappucino, 'coolDown'); +// => false +``` + +[primitives]: https://developer.mozilla.org/en-US/docs/Glossary/Primitive +[typeof null is object]: https://2ality.com/2013/10/typeof-null.html +[prototype chain]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Inheritance_and_the_prototype_chain diff --git a/concepts/type-checking/links.json b/concepts/type-checking/links.json new file mode 100644 index 0000000000..a8e4a31740 --- /dev/null +++ b/concepts/type-checking/links.json @@ -0,0 +1,14 @@ +[ + { + "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof", + "description": "MDN: The typeof operator" + }, + { + "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/instanceof", + "description": "MDN: The instanceof operator" + }, + { + "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty", + "description": "MDN: The object.hasOwnProperty() method" + } +] diff --git a/config.json b/config.json index 25fa27416b..ddc35cb96d 100644 --- a/config.json +++ b/config.json @@ -436,6 +436,22 @@ "objects", "functions" ] + }, + { + "slug": "recycling-robot", + "name": "Recycling Robot", + "uuid": "16114449-52fe-470e-af11-cf9dc3689a93", + "concepts": [ + "type-checking" + ], + "prerequisites": [ + "basics", + "errors", + "objects", + "arrays", + "classes", + "inheritance" + ] } ], "practice": [ @@ -2887,6 +2903,11 @@ "uuid": "ca322d6f-0f7e-4a2d-a058-e98a59cdae93", "slug": "randomness", "name": "Randomness" + }, + { + "uuid": "72e51fbe-db98-492e-b155-8ef21623f741", + "slug": "type-checking", + "name": "Type Checking" } ], "key_features": [ diff --git a/exercises/concept/recycling-robot/.docs/hints.md b/exercises/concept/recycling-robot/.docs/hints.md new file mode 100644 index 0000000000..ebf8921f38 --- /dev/null +++ b/exercises/concept/recycling-robot/.docs/hints.md @@ -0,0 +1,61 @@ +# Hints + +## 1. Check if a value is a boolean + +- You can use `typeof` to find the type of a value. +- `typeof` returns a string. + +## 2. Check if a value is a number. + +- You can use `typeof` to find the type of a value. +- `typeof` returns a string. +- You need to check for `Infinity` and `NaN`. +- `NaN` is never equal to itself, but there is a [built in function][isNaN] to check if a value is NaN. + +## 3. Check if a value is an object + +- You can use `typeof` to find the type of a value. +- `typeof` returns a string. +- You will need to check for `null`. + +## 4. Check if a string is numeric + +- You can use `typeof` to find the type of a value. +- `typeof` returns a string. +- You can iterate over a string to check if all characters are digits. + +## 5. Check if an object is electronic + +- You can use `instanceof` to check if an object is an instance of a class or one of its children. + +## 6. Check if a value is a non empty array + +- You can use `typeof` to find the type of a value. +- `typeof` returns a string. +- You can check the length of an array to find out how many elements it contains. + +## 7. Check if a value is an empty array + +- You can use `typeof` to find the type of a value. +- `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 + +- 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 + +- You can use the `in` operator to check if an object has a property or method. + +## 10. Check if an object has an `id` property + +- To check if an object has a property (not a method), you can use the `Object.hasOwn()` function. + +## 11. Check if an object has a defined `type` property + +- To check if an object has a property (not a method), you can use the `Object.hasOwn()` function. +- You will have to access the `type` property and check if it is defined. + +[isNaN]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/isNaN diff --git a/exercises/concept/recycling-robot/.docs/instructions.md b/exercises/concept/recycling-robot/.docs/instructions.md new file mode 100644 index 0000000000..1ed7f89b40 --- /dev/null +++ b/exercises/concept/recycling-robot/.docs/instructions.md @@ -0,0 +1,183 @@ +## Instructions + +You have been hired by a recycling center. +Due to lack of space, all the products are put on the same conveyor belt, but this has lead to different materials mixing together, making them unusable. +To fix this, you have been tasked with making functions to identify the type of a product. + +### 1. Check if a value is a boolean + +Implement the `isBoolean` function, that checks if a value is a boolean. + +```javascript +isBoolean(true); +// => true + +isBoolean(null); +// => false +``` + +### 2. Check if a value is a number. + +Implement the `isNumber` function, that checks if a value is a _finite_ `number` or `bigint`, ie. not `NaN` or `Infinity`. + +Sometimes, the device for reading IDs fails and reads a non-numeric value as `NaN` (Not a Number) or `Infinity`. +Your function should be able to correctly handle this as well. + +```javascript +isNumber(42); +// => true + +isNumber('Hello, World!'); +// => false + +isNumber(42n); +// => true + +isNumber(NaN); +// => false +``` + +### 3. Check if a value is an object + +Implement the `isObject` function, that should check if the value is an object. +On the conveyor, `null` is nothing and not considered an object. + +```javascript +isObject({ greeting: 'Hello' }); +// => true + +isObject(25n); +// => false +``` + +### 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. + +```javascript +isNumericString(42); +// => false + +isNumericString('42'); +// => true + +isNumericString('Hi!'); +// => false +``` + +### 5. Check if an object is electronic + +Implement the `isElectronic` function, that checks if an object is an instance of the provided `ElectronicDevice` class or one of its child classes. + +```javascript +class Duck { + //... +} + +class WashingMachine extends ElectronicDevice { + //... +} + +isElectronic(new Duck()); +// => false + +isElectronic(new WashingMachine()); +// => false +``` + +### 6. Check if a value is a non empty array + +Implement the `isNonEmptyArray` function, that checks if a value is a non-empty array. + +```javascript +isNonEmptyArray([1, 2, 3]); +// => true + +isNonEmptyArray([]); +// => false +``` + +### 7. Check if a value is an empty array + +Implement the `isEmptyArray` function, that checks if a value is an empty array. + +```javascript +isEmptyArray([1, 2, 3]); +// => false + +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 + +Implement the `hasType` function, that checks whether an object has a `type` property or method. + +```javascript +class Keyboard(){ + type(){ + // ... + } +} +hasType({type:"car",color:"red"}) +// => true + +hasType({color:"green"}) +// => false + +hasType(new Keyboard()) +// => true +``` + +### 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 { + constructor() { + this.number = '42'; + this.id = 'BC269327FE1D9B95'; + } +} +class MyNewClass { + constructor() { + this.number = '42'; + this._id = 'BC269327FE1D9B95'; + } + get id() { + return this._id; + } +} +hasIdProperty(new MyClass()); +// => true + +hasIdProperty(new MyNewClass()); +// => false +``` + +### 11. Check if an object has a defined `type` property + +Implement the `hasDefinedType` function, that checks if an object has a `type` property that is not `undefined`. + +```javascript +hasDefinedType({ type: undefined, color: 'red' }); +// => false + +hasDefinedType({ type: 'car', color: 'green' }); +// => true +``` diff --git a/exercises/concept/recycling-robot/.docs/introduction.md b/exercises/concept/recycling-robot/.docs/introduction.md new file mode 100644 index 0000000000..21f826324b --- /dev/null +++ b/exercises/concept/recycling-robot/.docs/introduction.md @@ -0,0 +1,164 @@ +# Introduction + +Knowning what the type of a piece of data is, is often very important for code to run smoothly and without errors. + +Javascript has several ways to check the type of a value or object. + +```exercism/note +Javascript's type checking mechanisms can be somewhat unreliable. + +For better type safety and stronger types, you should probably use TypeScript, a language that builds on JavaScript, but with the type syntax of a static-typed language. +``` + +## The `typeof` operator + +The `typeof` operator returns the type of its operand. +The output is a string matching the name of one of the [primitive data types][primitives], except for `"null"`. +It can also be `"function"` or `"object"`. + +```javascript +typeof undefined; +// => "undefined" + +typeof true; +// => "boolean" + +typeof 42; +// => "number" + +typeof 'Hello, World!'; +// => "string" + +typeof function () { + return 'Hello, World'; +}; +// => "function" + +typeof [1, 2, 3, 4]; +// => "object" + +typeof { city: 'Stockholm', country: 'Sweden' }; +// => "object" +``` + +For [historical reasons][`typeof null` is `"object"`]. + +## The `instanceof` operator + +For checking the type of an object, you can use the `instanceof` operator. +It evaluates into a `boolean` depending on whether the second operand is included in the first operands' [prototype chain][prototype chain]. +To clarify, `instanceof` will return whether the first operand is an instance of second operand or one of its child classes. +`instanceof` only works on objects. + +```javascript +class Beverage { + // ... +} + +// The Coffee class is a child of the Beverage class. +class Coffee extends Beverage { + // ... +} + +const java = new Coffee(); + +java instanceof Coffee; +// => true + +java instanceof Beverage; +// => true +``` + +````exercism/advanced +The `Array` class has a method called `Array.isArray()` that checks if its argument is an array. + +While `instanceof Array` will not work with an array created in a different realm such as an `iframe` in a webpage, `Array.isArray()` will. + +This is because the Array class has a different constructor in each realm, and each `iframe` has its own ream, meaning that the function in the prototype chain will be different, causing `instanceof Array` to fail. +`Array.isArray()` is capable of ignoring this, and should always be used when possible. + +It can also survive false positives where an object isn't actually an `Array`, and merely has `Array` in its prototype chain. + +```javascript +({ __proto__: Array.prototype }) instanceof Array +// => true + +Array.isArray({ __proto__: Array.prototype }) +// => false +``` + +```` + +## The `in` operator + +The `in` operator returns whether the first operand is a property of the second operand. +It does not check that the property has a defined value. +A property set to `undefined` will still be detected by `in`. + +```javascript +class Coffee { + constructor() { + this.temperature = 'hot'; + this.isDarkMatter = undefined; + } + + coolDown() { + this.temperature = 'warm'; + } +} + +const espresso = new Coffee(); + +'temperature' in espresso; +// => true + +'color' in espresso; +// => false + +'isDarkMatter' in espresso; +// => true +``` + +````exercism/note +`in` will return `true` for inherited properties and methods. + +```javascript +"coolDown" in espresso +// => true + +"constructor" in espresso +// => true +``` + +To avoid this, use `Object.hasOwn()` instead +```` + +## The `Object.hasOwn()` function + +The `Object.hasOwn()` method returns whether the specified object _owns the given property_ (it is not inherited or a method). + +```javascript +class Coffee { + constructor() { + this.temperature = 'hot'; + } + + coolDown() { + this.temperature = 'warm'; + } +} +const cappuccino = new Coffee(); + +Object.hasOwn(cappucino, 'temperature'); +// => true + +Object.hasOwn(cappucino, 'constructor'); +// => false + +Object.hasOwn(cappucino, 'coolDown'); +// => false +``` + +[primitives]: https://developer.mozilla.org/en-US/docs/Glossary/Primitive +[typeof null is object]: https://2ality.com/2013/10/typeof-null.html +[prototype chain]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Inheritance_and_the_prototype_chain diff --git a/exercises/concept/recycling-robot/.gitignore b/exercises/concept/recycling-robot/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/concept/recycling-robot/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/concept/recycling-robot/.meta/config.json b/exercises/concept/recycling-robot/.meta/config.json new file mode 100644 index 0000000000..aec5af47a5 --- /dev/null +++ b/exercises/concept/recycling-robot/.meta/config.json @@ -0,0 +1,21 @@ +{ + "authors": [ + "quintuple-mallard", + "SleeplessByte" + ], + "files": { + "solution": [ + "assembly-line.js" + ], + "test": [ + "assembly-line.spec.js" + ], + "exemplar": [ + ".meta/exemplar.js" + ], + "editor": [ + "lib.js" + ] + }, + "blurb": "Learn about type checking while helping manage an assembly line" +} diff --git a/exercises/concept/recycling-robot/.meta/exemplar.js b/exercises/concept/recycling-robot/.meta/exemplar.js new file mode 100644 index 0000000000..28b8b48beb --- /dev/null +++ b/exercises/concept/recycling-robot/.meta/exemplar.js @@ -0,0 +1,129 @@ +// @ts-check +// +// The line above enables type checking for this file. Various IDEs interpret +// the @ts-check directive. It will give you helpful autocompletion when +// implementing this exercise. + +import { ElectronicDevice } from './lib.js'; + +/** + * Checks if input is a boolean. + * + * @param {unknown} value + * @returns {value is boolean} whether the input is a boolean + */ +export function isBoolean(value) { + return typeof value === 'boolean'; +} + +/** + * Checks if input is a finite number or bigint. + * + * @param {unknown} value + * @returns {value is number | bigint} whether the input is a finite number or bigint + */ +export function isNumber(value) { + return ( + (typeof value === 'number' || typeof value === 'bigint') && + !isNaN(Number(value)) && + value !== Infinity + ); +} + +/** + * Checks if a value is an object. + * + * @param {unknown} value + * @returns {value is object} whether the input is an object. + */ +export function isObject(value) { + return value !== null && typeof value === 'object'; +} + +/** + * Checks if a value is a numeric string. + * + * @param {unknown} 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); + }) + ); +} + +/** + * Checks if an object is an instance of the "ElectronicDevice" class or one of its children. + * + * @param {object} object + * @returns {boolean} whether the object is an instance of the "ElectronicDevice" class or one of its children. + */ +export function isElectronic(object) { + return object instanceof ElectronicDevice; +} + +/** + * Checks if a value is a non empty array. + * + * @param {unknown} value + * @returns {boolean} whether the input is a non empty array. + */ +export function isNonEmptyArray(value) { + return Array.isArray(value) && value.length > 0; +} + +/** + * Checks if a value is an empty array. + * + * @param {unknown} value + * @returns {boolean} whether the input is an empty array. + */ +export function isEmptyArray(value) { + return Array.isArray(value) && value.length === 0; +} + +/** + * Throws an error if an object is missing an "id" property or method. + * + * @param {object} object + * @returns {boolean} undefined if the input has an "id" property, otherwise throws an error. + */ +export function assertHasId(object) { + if ('id' in object) { + return; + } + throw new Error('The "id" property is missing.'); +} + +/** + * Checks if a value has a "type" property or method. + * + * @param {object} object + * @returns {boolean} whether the input has a "type" property. + */ +export function hasType(object) { + return 'type' in object; +} + +/** + * Checks if a value has a "id" property. + * + * @param {object} object + * @returns {boolean} whether the input has a "id" property. + */ +export function hasIdProperty(object) { + return Object.hasOwn(object, 'id'); +} + +/** + * Checks if a value has a defined "type" property. + * + * @param {object} object + * @returns {boolean} whether the input has a defined "type" property. + */ +export function hasDefinedType(object) { + return Object.hasOwn(object, 'type') && object.type !== undefined; +} diff --git a/exercises/concept/recycling-robot/.npmrc b/exercises/concept/recycling-robot/.npmrc new file mode 100644 index 0000000000..d26df800bb --- /dev/null +++ b/exercises/concept/recycling-robot/.npmrc @@ -0,0 +1 @@ +audit=false diff --git a/exercises/concept/recycling-robot/LICENSE b/exercises/concept/recycling-robot/LICENSE new file mode 100644 index 0000000000..90e73be03b --- /dev/null +++ b/exercises/concept/recycling-robot/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Exercism + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/exercises/concept/recycling-robot/assembly-line.js b/exercises/concept/recycling-robot/assembly-line.js new file mode 100644 index 0000000000..65727895a5 --- /dev/null +++ b/exercises/concept/recycling-robot/assembly-line.js @@ -0,0 +1,121 @@ +// @ts-check +// +// The line above enables type checking for this file. Various IDEs interpret +// the @ts-check directive. It will give you helpful autocompletion when +// implementing this exercise. + +import { ElectronicDevice } from './lib.js'; + +/** + * Checks if input is a boolean. + * + * @param {unknown} value + * @returns {boolean} whether the input is a boolean + */ +export function isBoolean(value) { + throw new Error('Remove this line and implement the isBoolean function'); +} + +/** + * Checks if input is a finite number or bigint. + * + * @param {unknown} value + * @returns {boolean} whether the input is a finite number or bigint + */ +export function isNumber(value) { + throw new Error('Remove this line and implement the isNumber function'); +} + +/** + * Checks if a value is an object. + * + * @param {unknown} value + * @returns {boolean} whether the input is an object. + */ +export function isObject(value) { + throw new Error('Remove this line and implement the isObject function'); +} + +/** + * Checks if a value is a numeric string. + * + * @param {unknown} value + * @returns {boolean} whether the input is a numeric string. + */ +export function isNumericString(value) { + throw new Error( + 'Remove this line and implement the isNumericString function', + ); +} + +/** + * Checks if an object is an instance of the `ElectronicDevice` class or one of its children. + * + * @param {object} object + * @returns {boolean} whether the object is an instance of the `ElectronicDevice` class or one of its children. + */ +export function isElectronic(object) { + throw new Error('Remove this line and implement the isElectronic function'); +} + +/** + * Checks if a value is a non empty array. + * + * @param {unknown} value + * @returns {boolean} whether the input is a non empty array. + */ +export function isNonEmptyArray(value) { + throw new Error( + 'Remove this line and implement the isNonEmptyArray function', + ); +} + +/** + * Checks if a value is an empty array. + * + * @param {unknown} value + * @returns {boolean} whether the input is an empty array. + */ +export function isEmptyArray(value) { + throw new Error('Remove this line and implement the isEmptyArray function'); +} + +/** + * Throws an error if an object is missing an "id" property or method. + * + * @param {object} object + * @returns {undefined} undefined if the input has an "id" property or method, otherwise throws an error. + */ +export function assertHasId(object) { + throw new Error('Remove this line and implement the assertHasId function'); +} + +/** + * Checks if a value has a "type" property or method. + * + * @param {object} object + * @returns {boolean} whether the input has a "type" property or method. + */ +export function hasType(object) { + throw new Error('Remove this line and implement the hasType function'); +} + +/** + * Checks if a value has an "id" property. + * + * @param {object} object + * @returns {boolean} whether the input has an "id" property. + */ +export function hasIdProperty(object) { + throw new Error('Remove this line and implement the hasIdProperty function'); +} + +/** + * Checks if a value has a defined "type" property. + * + * @param {object} object + * @returns {boolean} whether the input has a defined "type" property. + */ +export function hasDefinedType(object) { + throw new Error('Remove this line and implement the hasDefinedType function'); +} diff --git a/exercises/concept/recycling-robot/assembly-line.spec.js b/exercises/concept/recycling-robot/assembly-line.spec.js new file mode 100644 index 0000000000..e23b6dc463 --- /dev/null +++ b/exercises/concept/recycling-robot/assembly-line.spec.js @@ -0,0 +1,204 @@ +import { describe, expect, test } from '@jest/globals'; +import { + isBoolean, + isNumber, + isObject, + isNumericString, + isElectronic, + isNonEmptyArray, + isEmptyArray, + assertHasId, + hasType, + hasIdProperty, + hasDefinedType, +} from './assembly-line'; +import { ElectronicDevice } from './lib.js'; + +describe('isBoolean', () => { + test('isBoolean works on booleans', () => { + expect(isBoolean(true)).toBe(true); + expect(isBoolean(false)).toBe(true); + }); + test('isBoolean works on non-booleans', () => { + expect(isBoolean(42)).toBe(false); + expect(isBoolean('Hello, World!')).toBe(false); + expect(isBoolean(null)).toBe(false); + expect(isBoolean('')).toBe(false); + expect(isBoolean(Symbol('1'))).toBe(false); + }); +}); + +describe('isNumber', () => { + test('isNumber works on numbers', () => { + expect(isNumber(42)).toBe(true); + expect(isNumber(92)).toBe(true); + expect(isNumber(43859435.12)).toBe(true); + }); + test('isNumber works on bigints', () => { + expect(isNumber(42n)).toBe(true); + expect(isNumber(92n)).toBe(true); + expect(isNumber(1848958451n)).toBe(true); + }); + test('isNumber works on non-numbers', () => { + expect(isNumber(true)).toBe(false); + expect(isNumber('Hello, World!')).toBe(false); + expect(isNumber(null)).toBe(false); + expect(isNumber('')).toBe(false); + expect(isNumber(Symbol('1'))).toBe(false); + }); + test('isNumber works on NaN and Infinity', () => { + expect(isNumber(NaN)).toBe(false); + expect(isNumber(Infinity)).toBe(false); + }); +}); + +class ClassForTesting { + constructor(number, word) { + this.number = number; + this.word = word; + } + id() {} +} + +describe('isObject', () => { + test('isObject works on objects', () => { + expect(isObject({})).toBe(true); + expect(isObject({ greeting: 'hello' })).toBe(true); + }); + test('isObject works 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); + expect(isObject('Hello, World!')).toBe(false); + expect(isObject(undefined)).toBe(false); + expect(isObject('')).toBe(false); + expect(isObject(Symbol('1'))).toBe(false); + }); + test('isObject works on null', () => { + expect(isObject(null)).toBe(false); + }); +}); + +describe('isNumericString', () => { + test('isNumericString works on numeric strings', () => { + expect(isNumericString('42')).toBe(true); + expect(isNumericString('582')).toBe(true); + }); + test('isNumericString works on non-numeric strings', () => { + 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); + expect(isNumericString(undefined)).toBe(false); + expect(isNumericString([1, 2, 3, 4])).toBe(false); + expect(isNumericString(Symbol('\u0070'))).toBe(false); + }); +}); + +class Oven extends ElectronicDevice {} +class Computer extends ElectronicDevice {} +class PersonalComputer extends Computer {} +class HomeMadePersonalComputer extends PersonalComputer {} + +describe('isElectronic', () => { + test('isElectronic works on instances of ElectronicDevice or its child classes', () => { + expect(isElectronic(new ElectronicDevice())).toBe(true); + expect(isElectronic(new Oven())).toBe(true); + }); + test('isElectronic works on other objects', () => { + expect(isElectronic({ language: 'javascript', typing: 'dynamic' })).toBe( + false, + ); + expect(isElectronic(new ClassForTesting(42, 'ElectronicDevice'))).toBe( + false, + ); + expect(isElectronic([1, 2, 3, 4])).toBe(false); + }); + test('isElectronic works on non-objects', () => { + expect(isElectronic(true)).toBe(false); + expect(isElectronic(1234)).toBe(false); + 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); + }); +}); + +describe('isNonEmptyArray', () => { + test('isNonEmptyArray works on non-empty arrays', () => { + expect(isNonEmptyArray([1, 2, 3])).toBe(true); + expect(isNonEmptyArray(['a', 'b'])).toBe(true); + }); + test('isNonEmptyArray works on 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); + }); +}); + +describe('isEmptyArray', () => { + test('isEmptyArray works on empty arrays', () => { + expect(isEmptyArray([])).toBe(true); + }); + test('isEmptyArray works on non-empty arrays', () => { + expect(isEmptyArray([1, 2, 3])).toBe(false); + }); + test('isEmptyArray works on non-arrays', () => { + expect(isEmptyArray({})).toBe(false); + expect(isEmptyArray('string')).toBe(false); + expect(isEmptyArray(123)).toBe(false); + }); +}); + +class TestAssertHasId { + id() {} +} + +describe('assertHasId', () => { + test("assertHasId throws error if object has no 'id' property or method", () => { + expect(() => assertHasId({})).toThrow(); + }); + 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(); + }); +}); + +class TestHasType { + type() {} +} + +describe('hasType', () => { + test('hasType works correctly', () => { + expect(hasType({ type: 'example' })).toBe(true); + expect(hasType({})).toBe(false); + expect(hasType(new TestHasType())).toBe(true); + }); +}); + +describe('hasIdProperty', () => { + test('hasIdProperty works correctly', () => { + expect(hasIdProperty({ id: 'test' })).toBe(true); + expect(hasIdProperty({})).toBe(false); + expect(hasIdProperty(new ClassForTesting())).toBe(false); + }); +}); + +describe('hasDefinedType', () => { + test('hasDefinedType works correctly', () => { + expect(hasDefinedType({ type: 'example' })).toBe(true); + expect(hasDefinedType({ type: undefined })).toBe(false); + expect(hasDefinedType({})).toBe(false); + }); +}); diff --git a/exercises/concept/recycling-robot/babel.config.js b/exercises/concept/recycling-robot/babel.config.js new file mode 100644 index 0000000000..a638497df1 --- /dev/null +++ b/exercises/concept/recycling-robot/babel.config.js @@ -0,0 +1,4 @@ +module.exports = { + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], +}; diff --git a/exercises/concept/recycling-robot/eslint.config.mjs b/exercises/concept/recycling-robot/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/concept/recycling-robot/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/concept/recycling-robot/jest.config.js b/exercises/concept/recycling-robot/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/concept/recycling-robot/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/concept/recycling-robot/lib.js b/exercises/concept/recycling-robot/lib.js new file mode 100644 index 0000000000..b0c1963e75 --- /dev/null +++ b/exercises/concept/recycling-robot/lib.js @@ -0,0 +1,3 @@ +export class ElectronicDevice { + // This class will be used in the exercise. +} diff --git a/exercises/concept/recycling-robot/package.json b/exercises/concept/recycling-robot/package.json new file mode 100644 index 0000000000..6427dc32e0 --- /dev/null +++ b/exercises/concept/recycling-robot/package.json @@ -0,0 +1,38 @@ +{ + "name": "@exercism/javascript-concept-recycling-robot", + "description": "Exercism concept exercise on type checking", + "author": "Katrina Owen", + "contributors": [ + "Derk-Jan Karrenbeld (https://derk-jan.com)", + "Tejas Bubane (https://tejasbubane.github.io/)" + ], + "private": true, + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/exercism/javascript", + "directory": "exercises/concept/assembly-line" + }, + "devDependencies": { + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.16", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" + }, + "dependencies": {}, + "scripts": { + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" +}