diff --git a/concepts/callbacks/about.md b/concepts/callbacks/about.md index b5e0cc86c1..4573cdd86b 100644 --- a/concepts/callbacks/about.md +++ b/concepts/callbacks/about.md @@ -1,68 +1,54 @@ # About -[_Callbacks_ describe the pattern][wiki-callbacks] where a function receives a function as an argument to invoke when it arrives at a condition. The condition may be that its work is done, or that an event has occurred, or that some predicate passes. It can be synchronous; it can be asynchronous. +[_Callback_ functions][wiki-callbacks] are functions passed as arguments to other functions. The callback function may then be invoked to trigger a subsequent action. Often, _callbacks_ are used to handle the results of work done, or handle an action when an event occurs. _Callback_ functions can be used in synchronous and asynchronous programming. -This is a useful pattern in JavaScript because it is designed as a single-threaded runtime where only one function call can be executed at a time. During execution, the runtime cannot respond to other events or continue execution until the function has returned. You might have noticed this on websites when they seem to "freeze" or become unresponsive. - -But many API calls (often I/O functions, timers, and event listeners) use an asynchronous mechanism to place the [current function call][mdn-concurrency-stack] on the side until the work is complete. Upon completion, a callback function is placed on the [message queue][mdn-concurrency-queue] so that when the runtime is in between function calls, the messages are then processed by invoking the callback function. - -It is also useful when the `callback` (the argument passed in) may not be defined (created) at the calling site. In other words: it may have been passed down from a different place in the program. - -If the `callback` function _returns_ a boolean or boolean-like value, which is intended to be used (as opposed to a throwaway return value), it's called a predicate. - -## Synchronous Code +```javascript +const sideLength = 5; -A synchronous call is when one function is executed after the other. The order is fixed. +// Caller function takes a callback function +function applySideLength(callback) { + return callback(sideLength); +} -```javascript -function triangleArea(height, base) { - return (height * base) / 2; +// Callback must expect the possible argument from the calling function +function squareArea(side) { + return side * side; } -triangleArea(2, 10); // => 10 -triangleArea(40, 3); // => 60 +applySideLength(areaOfSquare); // => 25 ``` -## Asynchronous Code - -When an asynchronous function is invoked, there is no way to know for certain when it will finish its work. This means there is no value to act on when the function returns to the caller. +You may also write callbacks as a function expression: ```javascript -// This is broken, it may or may not return your value in time to be used -let area = asynchronousTriangleArea(4, 7); -console.log(area); +applySideLength(function squarePerimeter(side) { + return side * 4; +}); ``` -So we can use callbacks to control the order of execution: - -```javascript -function areaCallback(area) { - console.log(area); -} +This is a useful pattern in JavaScript because JavaScript is designed as a single-threaded runtime where only one function call can be executed at a time. During execution, the runtime cannot respond to other events or continue execution until the function has returned. -function asynchronousTriangleArea(height, base, areaCallback) { - areaCallback((height * base) / 2); -} +Many API calls (I/O functions, timers, and event listeners) use an asynchronous mechanism to place the [current function call][mdn-concurrency-stack] on the side until the work is complete. Upon completion, a callback function is placed on the [message queue][mdn-concurrency-queue] so that when the runtime is in between function calls, the messages are then processed by invoking the callback function. -// This outputs the area of the triangle to the console as expected. -asynchronousCallback(4, 7, areaCallback); -``` +It is also useful to use _callback functions_ because they may reference variables in its closure scope which are unavailable to the function where it is later invoked. -## Specific callback forms +## Specific examples of callback functions -### Browser Events +### Event Handlers -_Event handlers_ are a common use case for callbacks in JavaScript. This often takes the form of browser events like `'onload'` or `'onclick'`, where a DOM object's `addEventListener` method then registers a callback to be invoked when a specified event occurs. +_Event handlers_ are a common use-case for callbacks in JavaScript. Browser events like `'onload'` or `'onclick'` are signals which can trigger functions to be invoked. A DOM [[Document Object Model](mdn-dom) object's `addEventListener` method registers a callback function to be invoked when it "hears" that an event has occurred. ```javascript -document.addEventListener('onload' () => alert('The webpage has now been loaded')) +document.addEventListener('onload', function () { + alert('The webpage has now been loaded'); +}); ``` -### Node.js Error Convention +### Node.js Convention -In [Node.js][nodejs], [callbacks][node-callbacks] often follow a [similar convention][node-error-convention] for their arguments: The first argument receives an `Error` object or `null` if no error occurred, and the second and subsequent receive the data that the calling function is designed to send. +In [Node.js][nodejs], [callback functions][node-callbacks] follow a [convention][node-error-convention] to control the flow of a program. They follow this pattern: the first argument of the callback function may receive an `Error` or `null`; The second and subsequent arguments receive the data that the calling function is designed to send. -If an error occurs, the second and subsequent arguments may not be present, so don't depend on them. +If an error occurs, the second and subsequent arguments may not be present, so you may not depend on them to be present. ```javascript function operation(a, b, callback) { @@ -78,7 +64,7 @@ function operation(a, b, callback) { function callback(error, returnedData) { if (error) { - // An error occured, handle it here. + // An error occurred, handle it here. return } @@ -88,11 +74,49 @@ function callback(error, returnedData) { operation(1, 2, callback) ``` -You see this pattern often when dealing with asynchronous functions. +You see this pattern often when dealing with asynchronous functions to assist with control flow. + +### Callbacks in disguise + +Common `Array` functions use callback functions to define their behaviour: + +- `Array.prototype.forEach`: + + - Accepts a callback, which applies the callback to each element of an array. + + ```javascript + [1, 2, 3].forEach(function (element) { + doSomething(element); + }); + // => doSomething() is invoked 3 times, once with each element + ``` + +- `Array.prototype.map` + + - Accepts a callback, which applies the callback to each element of an array using the result to create a new array. + + ```javascript + [1, 2, 3].map(function (element) { + return element + 1; + }); + // => [2, 3, 4] + ``` + +- `Array.prototype.reduce` + + - Accepts a callback, which applies the callback to each element of an array, passing the result forward to the next invocation. + + ```javascript + [1, 2, 3].reduce(function (runningSum, element) { + return runningSum + element; + }, 0); + // => 6 + ``` [mdn-callbacks]: https://developer.mozilla.org/en-US/docs/Glossary/Callback_function [mdn-concurrency-stack]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop#stack [mdn-concurrency-queue]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop#queue +[mdn-dom]: https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model [nodejs]: https://www.nodejs.org [node-callbacks]: https://nodejs.org/en/knowledge/getting-started/control-flow/what-are-callbacks/ [node-error-convention]: https://nodejs.org/en/knowledge/errors/what-are-the-error-conventions/ diff --git a/concepts/callbacks/introduction.md b/concepts/callbacks/introduction.md index 517142f15c..55defec5a2 100644 --- a/concepts/callbacks/introduction.md +++ b/concepts/callbacks/introduction.md @@ -1,35 +1,29 @@ # Introduction -Callbacks are functions that are passed as arguments to another function. This is often done to control the order of execution in an asynchronous context. Writing a callback function is no different from writing a function, but the callback function's arguments must match the signature required by the calling function. +## Callback functions + +Callback functions are functions passed as arguments. This programming pattern creates a sequence of function calls in both synchronous and asynchronous programming. Writing a callback function is no different from writing a function; however, the callback function must match the signature defined by the calling function. ```javascript -const squareLength = 5; +const sideLength = 5; // Caller function takes a callback function -function applyToSquare(callback) { - return callback(squareLength); +function applySideLength(callback) { + return callback(sideLength); } // Callback must expect the possible argument from the calling function -function areaOfSquare(number) { - return number * number; +function squareArea(side) { + return side * side; } -applyToSquare(areaOfSquare); // => 25 +applySideLength(areaOfSquare); // => 25 ``` You may also write callbacks as a function expression: ```javascript -applyToSquare(function squarePerimeter(side) { +applySideLength(function squarePerimeterLength(side) { return side * 4; }); ``` - -Or an anonymous inline arrow function expression: - -```javascript -applyToSquare((side) => side * 4); -``` - -// The argument "(side) => side \* 4" is the callback diff --git a/config.json b/config.json index e9e0720e68..922d815b1f 100644 --- a/config.json +++ b/config.json @@ -111,9 +111,9 @@ "concepts": ["array-analysis"], "prerequisites": [ "arrays", + "arrow-functions", "booleans", "callbacks", - "arrow-functions", "numbers" ], "status": "beta" @@ -133,8 +133,8 @@ "concepts": ["array-loops"], "prerequisites": [ "arrays", - "callbacks", "arrow-functions", + "callbacks", "for-loops", "conditionals" ], @@ -181,7 +181,7 @@ "slug": "fruit-picker", "name": "Fruit Picker", "uuid": "a6348db8-cc2b-4c53-9f43-3c23248d66f0", - "concepts": ["callbacks", "arrow-functions"], + "concepts": ["arrow-functions", "callbacks"], "prerequisites": ["functions", "objects"], "status": "beta" }, @@ -238,11 +238,11 @@ "uuid": "6e156d67-2bd2-4624-956d-ddcc3795bad5", "concepts": ["array-transformations"], "prerequisites": [ + "arrow-functions", "numbers", "arrays", "conditionals", - "callbacks", - "arrow-functions" + "callbacks" ], "status": "beta" }, @@ -500,9 +500,9 @@ "uuid": "9131bdb8-2e0f-4526-b113-8a77712e7216", "practices": [], "prerequisites": [ + "arrow-functions", "strings", "objects", - "arrow-functions", "regular-expressions", "errors" ], diff --git a/exercises/concept/fruit-picker/.docs/instructions.md b/exercises/concept/fruit-picker/.docs/instructions.md index 99a36741ce..9c41e710c7 100644 --- a/exercises/concept/fruit-picker/.docs/instructions.md +++ b/exercises/concept/fruit-picker/.docs/instructions.md @@ -2,21 +2,27 @@ You are creating a new online portal for your patrons to order their fruit fresh from the grocer. The grocer has an API that you can use to see if they have the inventory desired by your customers. You need to create a small library of functions for interacting with the grocer's API. -## 1. Check if the grocer's service is online +## 1. Create a callback to be called when the order is successful -The grocer's application programming interface [API] provides a function to check if their service is online called `checkStatus`. Use the grocer's function to finish the implementation of your `isServiceOnline` function. The `checkStatus` function takes a callback function to receive the status from the API. +Write a callback function called `onSuccess` to be called when the order is successful. It should invoke the imported `notify` function passing a success message to it. -- if the status is `'ONLINE'`, return `true` -- if the status is `'OFFLINE'`, return `false` +```javascript +onSuccess(); +// => `notify` called with `{ message: 'SUCCESS' }` +``` + +## 2. Create a callback to be called when the order fails with an error + +Write a callback function called `onError` to be called when the order encounters an error. It should invoke the imported `notify` function passing an error message to it. ```javascript -isServiceOnline(); -// => true or false +onError(); +// => `notify` called with `{ message: 'ERROR' }` ``` -## 2. See if the grocer has some fruit +## 3. Create a wrapper to wrap the external api function -The grocer's API provides a function to query their inventory called `checkInventory`. It receives two arguments: a _query_, and a _callback_ function. +The grocer's API provides a function to order from their inventory called `order`. It receives three arguments: a _query_, a _callback_ function to be invoked when the order is successful, and a _callback_ function to be invoked when the order encounters an error. You decide to wrap the api function call in a newly defined function `orderFromGrocer` to insulate your codebase from external changes. Your function should forward the arguments (which match the provided api function) to the api function. The query takes the form of an _object_: @@ -27,39 +33,20 @@ const query = { }; ``` -For your `pickFruit` function, you have decided to generalize it and just pass along a callback. So using the arguments `variety` and `quantity` finish the function to call the `checkInventory` API. - -```javascript -function action(err, data) { - // logic -} - -pickFruit('pineapple', 20, action); -// calls the checkInventory function with the query and passes along the `action` callback function -``` - -## 3. Create a callback to buy fruit if the inventory is available - -Finish the `purchaseInventoryIfAvailable` callback function to be used with the grocer's `checkInventory` API function. The API function expects callback functions to accept two arguments, `err` and `isAvailable`. If an error occurs when checking the inventory, a string is returned to `err`. If there is no error, the value is `null`. `isAvailable` is a _boolean_ value, but if there is an error it is _undefined_. - -To finish `purchaseInventoryIfAvailable`, throw a new error if `err` is not null. Otherwise, return `'PURCHASE'` if `isAvailable` is _true_ or `'NOOP'` if _false_. - ```javascript -purchaseInventoryIfAvailable('Server Offline', undefined); -// => Throws new error "Server Offline" - -purchaseInventoryIfAvailable(null, true); -// => 'PURCHASE' - -purchaseInventoryIfAvailable(null, false); -// => 'NOOP' +orderFromGrocer( + { variety: 'pear', quantity: 12 }, + exampleSuccessCallback, + exampleErrorCallback +); +// => `order` was called with the query and the callbacks ``` -## 4. Put it all together +## 4. Create a convenient short function -You notice that you're using `pickFruit` and `purchaseInventoryIfAvailable` so you decide to DRY up your code by extracting the code into a separate function called `pickAndPurchaseFruit`. Reuse `pickFruit` and `purchaseInventoryIfAvailable` to finish this function. +You find that you are calling this function from many different places with the same functions. Seeing an opportunity to refactor your code, you want to create a function where you can supply the variety and quantity to order as arguments. ```javascript -pickAndPurchaseFruit('Red Delicious Apples', 42); -// 'PURCHASE' if available or 'NOOP' if not available +postOrder('peach', 100); +// => order submitted for 100 peaches ``` diff --git a/exercises/concept/fruit-picker/.docs/introduction.md b/exercises/concept/fruit-picker/.docs/introduction.md index 3769497d64..3a88419e35 100644 --- a/exercises/concept/fruit-picker/.docs/introduction.md +++ b/exercises/concept/fruit-picker/.docs/introduction.md @@ -1,8 +1,8 @@ # Introduction -## Callbacks +## Callback functions -Callbacks are functions that are passed as arguments to another function. This is often done to control the order of execution in an asynchronous context. Writing a callback function is no different from writing a function, but the callback function's arguments must match the signature required by the calling function. +Callback functions are functions passed as arguments. This programming pattern creates a sequence of function calls in both synchronous and asynchronous programming. Writing a callback function is no different from writing a function; however, the callback function must match the signature defined by the calling function. ```javascript const squareLength = 5; @@ -27,39 +27,3 @@ applyToSquare(function squarePerimeter(side) { return side * 4; }); ``` - -## Arrow Functions - -Besides function declarations and function expressions, JavaScript also has another very concise syntax for defining a function. -These functions are called _arrow functions_. - -Here is a comparison between a function declaration and an arrow function. - -```javascript -function addUpTwoNumbers(num1, num2) { - return num1 + num2; -} - -// function keyword removed and => added -const addUpTwoNumbers = (num1, num2) => { - return num1 + num2; -}; -``` - -If the function body contains only a return statement, like in the example above, the `{}` and the `return` keyword can be omitted. -If there is only one parameter, the parenthesis `()` can be omitted as well. - - -```javascript -const addUpTwoNumbers = (num1, num2) => num1 + num2; -const square = num => num * num; -``` - - -Arrow functions are often used to define short callback functions directly in the function call. - - -```javascript -applyToSquare(number => number * number); -``` - diff --git a/exercises/concept/fruit-picker/.meta/config.json b/exercises/concept/fruit-picker/.meta/config.json index b001431f38..09dfe3950c 100644 --- a/exercises/concept/fruit-picker/.meta/config.json +++ b/exercises/concept/fruit-picker/.meta/config.json @@ -1,25 +1,13 @@ { - "authors": [ - "neenjaw" - ], - "contributors": [ - "SleeplessByte" - ], + "authors": ["neenjaw"], + "contributors": ["SleeplessByte"], "files": { - "solution": [ - "fruit-picker.js" - ], - "test": [ - "fruit-picker.spec.js" - ], - "exemplar": [ - ".meta/exemplar.js" - ], - "editor": [ - "grocer.js" - ] + "solution": ["fruit-picker.js"], + "test": ["fruit-picker.spec.js"], + "exemplar": [".meta/exemplar.js"], + "editor": ["notifier.js", "grocer.js"] }, - "blurb": "Learn about callbacks picking fruit from the grocer", + "blurb": "Learn about callbacks ordering fruit from the grocer", "custom": { "version.tests.compatibility": "jest-27", "flag.tests.task-per-describe": true, diff --git a/exercises/concept/fruit-picker/.meta/design.md b/exercises/concept/fruit-picker/.meta/design.md index 3630cf7a31..9c16658f4f 100644 --- a/exercises/concept/fruit-picker/.meta/design.md +++ b/exercises/concept/fruit-picker/.meta/design.md @@ -11,7 +11,6 @@ In other words: how _function_ can be passed as an argument to another function, - Function that can pass along a callback function as an argument - How to write a function that can be used as a callback - How to compose functions with callbacks -- Functions using arrow functions ## Out of scope @@ -20,7 +19,6 @@ In other words: how _function_ can be passed as an argument to another function, ## Concepts -- `arrow-functions` - `callbacks` ## Prerequisites diff --git a/exercises/concept/fruit-picker/.meta/exemplar.js b/exercises/concept/fruit-picker/.meta/exemplar.js index 22ca6bf934..dba3e028eb 100644 --- a/exercises/concept/fruit-picker/.meta/exemplar.js +++ b/exercises/concept/fruit-picker/.meta/exemplar.js @@ -1,50 +1,39 @@ /// +// // @ts-check -import { checkStatus, checkInventory } from '../grocer'; +import { notify } from './notifier'; +import { order } from './grocer'; /** - * Returns the service status as a boolean value - * @return {boolean} + * @return void */ -export function isServiceOnline() { - return checkStatus((serviceStatus) => serviceStatus === 'ONLINE'); +export function onSuccess() { + notify({ message: 'SUCCESS' }); } /** - * Pick a fruit using the checkInventory API - * - * @param {string} variety - * @param {number} quantity - * @param {InventoryCallback} callback - * @return {AvailabilityAction} the result from checkInventory + * @return void */ -export function pickFruit(variety, quantity, callback) { - return checkInventory({ variety, quantity }, callback); +export function onError() { + notify({ message: 'ERROR' }); } /** - * This is a callback function to be passed to the checkInventory API - * handles the next step once the inventory is known - * @param {string | null} err - * @param {boolean} isAvailable - * @return {AvailabilityAction} whether the fruit was purchased 'PURCHASE' or 'NOOP' + * @param {GrocerQuery} query + * @param {FruitPickerSuccessCallback} onSuccessCallback + * @param {FruitPickerErrorCallback} onErrorCallback + * @return void */ -export function purchaseInventoryIfAvailable(err, isAvailable) { - if (err) { - throw new Error(err); - } - - return isAvailable ? 'PURCHASE' : 'NOOP'; +export function orderFromGrocer(query, onSuccessCallback, onErrorCallback) { + order(query, onSuccessCallback, onErrorCallback); } /** - * Pick a fruit, and if it is available, purchase it - * * @param {string} variety * @param {number} quantity - * @return {AvailabilityAction} whether the fruit was purchased 'PURCHASE' or 'NOOP' + * @return void */ -export function pickAndPurchaseFruit(variety, quantity) { - return pickFruit(variety, quantity, purchaseInventoryIfAvailable); +export function postOrder(variety, quantity) { + orderFromGrocer({ variety, quantity }, onSuccess, onError); } diff --git a/exercises/concept/fruit-picker/fruit-picker.js b/exercises/concept/fruit-picker/fruit-picker.js index 91d2147d3a..2390e6c336 100644 --- a/exercises/concept/fruit-picker/fruit-picker.js +++ b/exercises/concept/fruit-picker/fruit-picker.js @@ -9,46 +9,38 @@ // // In your own projects, files, and code, you can play with @ts-check as well. -import { checkStatus, checkInventory } from './grocer'; +import { notify } from './notifier'; +import { order } from './grocer'; /** - * Returns the service status as a boolean value - * @return {boolean} + * @return void */ -export function isServiceOnline() { - throw new Error('Implement the isServiceOnline function'); +export function onSuccess() { + // implement the onSuccess callback to call notify with a success message } /** - * Pick a fruit using the checkInventory API - * - * @param {string} variety - * @param {number} quantity - * @param {InventoryCallback} callback - * @return {AvailabilityAction} the result from checkInventory + * @return void */ -export function pickFruit(variety, quantity, callback) { - throw new Error('Implement the pickFruit function'); +export function onError() { + // implement the onError callback to call notify with an error message } /** - * This is a callback function to be passed to the checkInventory API - * handles the next step once the inventory is known - * @param {string | null} err - * @param {boolean | undefined} isAvailable - * @return {AvailabilityAction} whether the fruit was purchased 'PURCHASE' or 'NOOP' + * @param {GrocerQuery} query + * @param {FruitPickerSuccessCallback} onSuccessCallback + * @param {FruitPickerErrorCallback} onErrorCallback + * @return void */ -export function purchaseInventoryIfAvailable(err, isAvailable) { - throw new Error('Implement the purchaseInventoryIfAvailable function'); +export function orderFromGrocer(query, onSuccessCallback, onErrorCallback) { + // implement the orderFromGrocer function to order the query } /** - * Pick a fruit, and if it is available, purchase it - * * @param {string} variety * @param {number} quantity - * @return {AvailabilityAction} whether the fruit was purchased 'PURCHASE' or 'NOOP' + * @return void */ -export function pickAndPurchaseFruit(variety, quantity) { - throw new Error('Implement the pickAndPurchaseFruit function'); +export function postOrder(variety, quantity) { + //implement the postOrder function to create a query and order } diff --git a/exercises/concept/fruit-picker/fruit-picker.spec.js b/exercises/concept/fruit-picker/fruit-picker.spec.js index 21535916ff..f920705511 100644 --- a/exercises/concept/fruit-picker/fruit-picker.spec.js +++ b/exercises/concept/fruit-picker/fruit-picker.spec.js @@ -1,116 +1,57 @@ // @ts-check -import { - purchaseInventoryIfAvailable, - isServiceOnline, - pickAndPurchaseFruit, - pickFruit, -} from './fruit-picker'; -import { - setStatus, - resetStatus, - setResponse, - getLastQuery, - resetQuery, -} from './grocer'; +import { notify } from './notifier'; +import { order } from './grocer'; +import { onError, onSuccess, orderFromGrocer, postOrder } from './fruit-picker'; -describe('service status', () => { - beforeEach(() => { - resetStatus(); - }); +jest.mock('./notifier', () => ({ + notify: jest.fn(), +})); - test('returns the initial status of the service', () => { - expect(isServiceOnline()).toBe(false); - }); +jest.mock('./grocer', () => ({ + order: jest.fn(), +})); - test('returns the status when service is online', () => { - setStatus('ONLINE'); - expect(isServiceOnline()).toBe(true); - }); +afterEach(() => { + jest.resetAllMocks(); }); -describe('inventory service', () => { - beforeEach(() => { - resetQuery(); - }); - - test('uses the query format', () => { - pickFruit('strawberry', 5, () => 'PURCHASE'); - expect(getLastQuery()).toEqual({ - variety: 'strawberry', - quantity: 5, - }); - }); - - test('takes parameters for the query', () => { - pickFruit('blueberry', 20, () => 'PURCHASE'); - expect(getLastQuery()).toEqual({ - variety: 'blueberry', - quantity: 20, - }); - }); - - test('returns synchronously', () => { - expect(pickFruit('melon', 1, () => 'PURCHASE')).toBe('PURCHASE'); - }); - - test('returns the inventory status', () => { - expect(pickFruit('melon', 1, () => 'NOOP')).toBe('NOOP'); +describe('task 1', () => { + test('onSuccess should call notify with a success message', () => { + expect(onSuccess()).toEqual(undefined); + expect(notify).toHaveBeenCalledTimes(1); + expect(notify).toHaveBeenCalledWith({ message: 'SUCCESS' }); }); }); -describe('inventory result callback', () => { - test('throws error if receives inventory error', () => { - expect(() => { - purchaseInventoryIfAvailable('inventory error', undefined); - }).toThrow(); - }); - - test('returns "PURCHASE" when inventory is available', () => { - expect(purchaseInventoryIfAvailable(null, true)).toBe('PURCHASE'); - }); - - test('returns "NOOP" when inventory is unavailable', () => { - expect(purchaseInventoryIfAvailable(null, false)).toBe('NOOP'); +describe('task 2', () => { + test('onError should call notify with an error message', () => { + expect(onError()).toEqual(undefined); + expect(notify).toHaveBeenCalledTimes(1); + expect(notify).toHaveBeenCalledWith({ message: 'ERROR' }); }); }); -describe('putting it together', () => { - beforeEach(() => { - resetQuery(); - }); - - test('uses the query format', () => { - setResponse(null, true); - pickAndPurchaseFruit('jackfruit', 15); - expect(getLastQuery()).toEqual({ - variety: 'jackfruit', - quantity: 15, - }); - }); - - test('takes parameters for the query', () => { - setResponse(null, true); - pickAndPurchaseFruit('raspberry', 30); - expect(getLastQuery()).toEqual({ - variety: 'raspberry', - quantity: 30, - }); - }); - - test('throws error if receives inventory error', () => { - expect(() => { - pickAndPurchaseFruit('honeydew', 3); - }).toThrow(); - }); - - test('returns "NOOP" if quantity not available', () => { - setResponse(null, false); - expect(pickAndPurchaseFruit('apples', 12)).toBe('NOOP'); +describe('task 3', () => { + test('order from grocer passes callback function arguments forward', () => { + const query = { variety: 'apple', quantity: 10 }; + orderFromGrocer(query, onSuccess, onError); + expect(order).toHaveBeenCalledTimes(1); + expect(order).toHaveBeenCalledWith(query, onSuccess, onError); }); +}); - test('returns "PURCHASE" if quantity available', () => { - setResponse(null, true); - expect(pickAndPurchaseFruit('oranges', 22)).toBe('PURCHASE'); +describe('task 4', () => { + test('postOrder composes a request to the grocer using the defined callbacks', () => { + const variety = 'banana'; + const quantity = 5; + postOrder(variety, quantity); + + expect(order).toHaveBeenCalledTimes(1); + expect(order).toHaveBeenCalledWith( + { variety, quantity }, + onSuccess, + onError + ); }); }); diff --git a/exercises/concept/fruit-picker/global.d.ts b/exercises/concept/fruit-picker/global.d.ts index 26535a644e..19087979c6 100644 --- a/exercises/concept/fruit-picker/global.d.ts +++ b/exercises/concept/fruit-picker/global.d.ts @@ -4,18 +4,16 @@ * type information on the fly */ -type Status = 'OFFLINE' | 'ONLINE'; -type AvailabilityAction = 'NOOP' | 'PURCHASE'; - -interface CheckStatus { - callback: StatusCallback; -} +type FruitPickerSuccess = { + message: 'SUCCESS'; +}; -type StatusCallback = (response: Status) => boolean; +type FruitPickerError = { + message: 'ERROR'; +}; -interface CheckInventory { - query: GrocerQuery; - callback: InventoryCallback; +declare module 'notifier' { + function notify(message: FruitPickerSuccess | FruitPickerError): void; } type GrocerQuery = { @@ -23,7 +21,22 @@ type GrocerQuery = { quantity: number; }; -type InventoryCallback = ( - err: string | null, - isAvailable: boolean -) => AvailabilityAction; +interface GrocerOnSuccessCallback { + (quantityOrdered: number): void; +} + +interface GrocerOnErrorCallback { + (errorMessage: string): void; +} + +declare module 'grocer' { + function order( + query: GrocerQuery, + onSuccess: GrocerOnSuccessCallback, + onError: GrocerOnErrorCallback + ): void; +} + +type FruitPickerSuccessCallback = () => SuccessResult; + +type FruitPickerErrorCallback = () => ErrorResult; diff --git a/exercises/concept/fruit-picker/grocer.js b/exercises/concept/fruit-picker/grocer.js index 959dbeaf5f..f167889979 100644 --- a/exercises/concept/fruit-picker/grocer.js +++ b/exercises/concept/fruit-picker/grocer.js @@ -1,70 +1,12 @@ /** - * STORE STATUS API - */ - -let storeStatus = 'OFFLINE'; - -/** - * For testing purposes, set the store status - * @param {string} status - */ -export function setStatus(status) { - storeStatus = status; -} - -/** - * For testing purposes, reset the store status - */ -export function resetStatus() { - storeStatus = 'OFFLINE'; -} - -/** - * Invokes the callback with the store's status to simulate an API call - * @param {StatusCallback} callback - */ -export function checkStatus(callback) { - return callback(storeStatus); -} - -/** - * INVENTORY API - */ - -let lastInventoryQuery = undefined; -let inventoryResponse = undefined; - -/** - * For testing purposes, set the response to return when queried - * @param {any} ...nextResponse - */ -export function setResponse(...nextResponse) { - inventoryResponse = nextResponse; -} - -/** - * For testing purposes, get the last query - * @return {string} - */ -export function getLastQuery() { - return lastInventoryQuery; -} - -/** - * For testing purposes, reset the last query - */ -export function resetQuery() { - lastInventoryQuery = undefined; - inventoryResponse = ['undefined response']; -} - -/** - * Checks the inventory (inventoryResponse) then invokes the callback with the result - * @param {GrocerQuery} query - * @param {InventoryCallback} callback - * @return {AvailabilityAction} return the result of the callback - */ -export function checkInventory(query, callback) { - lastInventoryQuery = query; - return callback.apply(null, inventoryResponse); + * @param {GrocerQuery} query + * @param {GrocerOnSuccessCallback} onSuccess + * @param {GrocerOnErrorCallback} onError + * @return void + */ +export function order(query, onSuccess, onError) { + // This is a mocked function for use with the exercise. + query; + onSuccess; + onError; } diff --git a/exercises/concept/fruit-picker/notifier.js b/exercises/concept/fruit-picker/notifier.js new file mode 100644 index 0000000000..b30eaca2d0 --- /dev/null +++ b/exercises/concept/fruit-picker/notifier.js @@ -0,0 +1,8 @@ +/** + * @param {FruitPickerSuccess | FruitPickerError} message + * @return void + */ +export function notify(message) { + // This is a mocked function for use with the exercise. + message; +}