From f93bf99c0e2935124fe81aa8a5bc41bc9048b72f Mon Sep 17 00:00:00 2001 From: Dannyel Cardoso da Fonseca Date: Sun, 28 Apr 2024 14:22:34 +0200 Subject: [PATCH 1/9] Add convert temperature exercise --- exercises/convertTemperature/index.html | 18 +++++++++++ exercises/convertTemperature/src/init.js | 38 ++++++++++++++++++++++++ exercises/convertTemperature/styles.css | 3 ++ 3 files changed, 59 insertions(+) create mode 100644 exercises/convertTemperature/index.html create mode 100644 exercises/convertTemperature/src/init.js create mode 100644 exercises/convertTemperature/styles.css diff --git a/exercises/convertTemperature/index.html b/exercises/convertTemperature/index.html new file mode 100644 index 00000000..6295a3bc --- /dev/null +++ b/exercises/convertTemperature/index.html @@ -0,0 +1,18 @@ + + + + + Convert Temperatures + + + + +
+ + + +
+ + + + diff --git a/exercises/convertTemperature/src/init.js b/exercises/convertTemperature/src/init.js new file mode 100644 index 00000000..118b1351 --- /dev/null +++ b/exercises/convertTemperature/src/init.js @@ -0,0 +1,38 @@ +debugger; // once when the program is initialized + +document.getElementById('temperatures').addEventListener('change', (event) => { + debugger; // each time the user changes the 'temperatures' input + + const convertedTemperaturesContainer = document.getElementById( + 'convertedTemperatures', + ); + + // Erase previous content + convertedTemperaturesContainer.innerHTML = ''; + + // Get the text input + const fahrenheitTextList = event.target.value; + + // Validade it + if (fahrenheitTextList) { + if (!/^[0-9\s]*$/.test(fahrenheitTextList)) { + window.alert( + `The '${fahrenheitTextList}' contains values different of integer numbers`, + ); + } + + // Sanitize it + const fahrenheitList = fahrenheitTextList.trim().split(/\s+/); + + fahrenheitList.forEach((fahrenheit) => { + // Do the math + const celsius = ((fahrenheit - 32) * 5) / 9; + + // Render the result + const liString = `
  • ${celsius.toFixed(2)}
  • `; + + convertedTemperaturesContainer.innerHTML = + convertedTemperaturesContainer.innerHTML + liString; + }); + } +}); diff --git a/exercises/convertTemperature/styles.css b/exercises/convertTemperature/styles.css new file mode 100644 index 00000000..a8ac6631 --- /dev/null +++ b/exercises/convertTemperature/styles.css @@ -0,0 +1,3 @@ +.number-item { + font-family: fantasy; +} From 9fd1bcd064c8ad7866902570c394012022807667 Mon Sep 17 00:00:00 2001 From: Dannyel Cardoso da Fonseca Date: Wed, 1 May 2024 20:10:26 +0200 Subject: [PATCH 2/9] Add @types/jest --- package-lock.json | 21 +++++++++++++++++++++ package.json | 5 +++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6d02f551..d8e1810d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "MIT", "devDependencies": { "@babel/plugin-transform-modules-commonjs": "^7.22.5", + "@types/jest": "^29.5.12", "jest": "^29.6.1", "jest-environment-jsdom": "^29.6.1", "prettier": "^2.3.2" @@ -1038,6 +1039,16 @@ "@types/istanbul-lib-report": "*" } }, + "node_modules/@types/jest": { + "version": "29.5.12", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.12.tgz", + "integrity": "sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==", + "dev": true, + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, "node_modules/@types/jsdom": { "version": "20.0.1", "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.1.tgz", @@ -4792,6 +4803,16 @@ "@types/istanbul-lib-report": "*" } }, + "@types/jest": { + "version": "29.5.12", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.12.tgz", + "integrity": "sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==", + "dev": true, + "requires": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, "@types/jsdom": { "version": "20.0.1", "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.1.tgz", diff --git a/package.json b/package.json index 4ae947a5..1f575409 100644 --- a/package.json +++ b/package.json @@ -17,9 +17,10 @@ }, "homepage": "https://github.com/HackYourFutureBelgium/separation-of-concerns#readme", "devDependencies": { - "prettier": "^2.3.2", "@babel/plugin-transform-modules-commonjs": "^7.22.5", + "@types/jest": "^29.5.12", "jest": "^29.6.1", - "jest-environment-jsdom": "^29.6.1" + "jest-environment-jsdom": "^29.6.1", + "prettier": "^2.3.2" } } From 14ef6a49b0c36313bdebd1922d0855f4ac3f6ff3 Mon Sep 17 00:00:00 2001 From: Dannyel Cardoso da Fonseca Date: Thu, 2 May 2024 09:37:36 +0200 Subject: [PATCH 3/9] Refactor to use constant data --- .../convertTemperature/src/data/constants.js | 3 + exercises/convertTemperature/src/init.js | 80 ++++++++++--------- 2 files changed, 46 insertions(+), 37 deletions(-) create mode 100644 exercises/convertTemperature/src/data/constants.js diff --git a/exercises/convertTemperature/src/data/constants.js b/exercises/convertTemperature/src/data/constants.js new file mode 100644 index 00000000..7afd129c --- /dev/null +++ b/exercises/convertTemperature/src/data/constants.js @@ -0,0 +1,3 @@ +export const SPACE_REGEX = /\s+/; +export const TEMPERATURES_INPUT = 'temperatures'; +export const CONVERT_TEMPERATURE_CONTAINER = 'convertedTemperatures'; diff --git a/exercises/convertTemperature/src/init.js b/exercises/convertTemperature/src/init.js index 118b1351..522768e0 100644 --- a/exercises/convertTemperature/src/init.js +++ b/exercises/convertTemperature/src/init.js @@ -1,38 +1,44 @@ -debugger; // once when the program is initialized - -document.getElementById('temperatures').addEventListener('change', (event) => { - debugger; // each time the user changes the 'temperatures' input - - const convertedTemperaturesContainer = document.getElementById( - 'convertedTemperatures', - ); - - // Erase previous content - convertedTemperaturesContainer.innerHTML = ''; - - // Get the text input - const fahrenheitTextList = event.target.value; - - // Validade it - if (fahrenheitTextList) { - if (!/^[0-9\s]*$/.test(fahrenheitTextList)) { - window.alert( - `The '${fahrenheitTextList}' contains values different of integer numbers`, - ); +import { + SPACE_REGEX, + TEMPERATURES_INPUT, + CONVERT_TEMPERATURE_CONTAINER, +} from './data/constants.js'; + +document + .getElementById(TEMPERATURES_INPUT) + .addEventListener('change', (event) => { + //debugger; // each time the user changes the 'temperatures' input + + const convertedTemperaturesContainer = document.getElementById( + CONVERT_TEMPERATURE_CONTAINER, + ); + + // Erase previous content + convertedTemperaturesContainer.innerHTML = ''; + + // Get the text input + const fahrenheitTextList = event.target.value; + + // Validade it + if (fahrenheitTextList) { + if (!/^[0-9\s]*$/.test(fahrenheitTextList)) { + window.alert( + `The '${fahrenheitTextList}' contains values different of integer numbers`, + ); + return; + } + + // Sanitize it + const fahrenheitList = fahrenheitTextList.trim().split(SPACE_REGEX); + fahrenheitList.forEach((fahrenheit) => { + // Do the math + const celsius = ((fahrenheit - 32) * 5) / 9; + + // Render the result + const liString = `
  • ${celsius.toFixed(2)}
  • `; + + convertedTemperaturesContainer.innerHTML = + convertedTemperaturesContainer.innerHTML + liString; + }); } - - // Sanitize it - const fahrenheitList = fahrenheitTextList.trim().split(/\s+/); - - fahrenheitList.forEach((fahrenheit) => { - // Do the math - const celsius = ((fahrenheit - 32) * 5) / 9; - - // Render the result - const liString = `
  • ${celsius.toFixed(2)}
  • `; - - convertedTemperaturesContainer.innerHTML = - convertedTemperaturesContainer.innerHTML + liString; - }); - } -}); + }); From c8d43f6a761f39794d7bb8b40d1596cf238290e1 Mon Sep 17 00:00:00 2001 From: Dannyel Cardoso da Fonseca Date: Thu, 2 May 2024 16:07:43 +0200 Subject: [PATCH 4/9] Refactor to use replacePlaceholders utils --- .../convertTemperature/src/data/constants.js | 2 ++ exercises/convertTemperature/src/init.js | 4 +++- .../convertTemperature/src/utils/strings.js | 16 +++++++++++++ .../src/utils/strings.test.js | 24 +++++++++++++++++++ 4 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 exercises/convertTemperature/src/utils/strings.js create mode 100644 exercises/convertTemperature/src/utils/strings.test.js diff --git a/exercises/convertTemperature/src/data/constants.js b/exercises/convertTemperature/src/data/constants.js index 7afd129c..b06b430c 100644 --- a/exercises/convertTemperature/src/data/constants.js +++ b/exercises/convertTemperature/src/data/constants.js @@ -1,3 +1,5 @@ export const SPACE_REGEX = /\s+/; export const TEMPERATURES_INPUT = 'temperatures'; export const CONVERT_TEMPERATURE_CONTAINER = 'convertedTemperatures'; +export const MESSAGE_ERROR_NOT_INTEGER = + "The '{0}' contains values different of integer numbers"; diff --git a/exercises/convertTemperature/src/init.js b/exercises/convertTemperature/src/init.js index 522768e0..0e435ffa 100644 --- a/exercises/convertTemperature/src/init.js +++ b/exercises/convertTemperature/src/init.js @@ -2,7 +2,9 @@ import { SPACE_REGEX, TEMPERATURES_INPUT, CONVERT_TEMPERATURE_CONTAINER, + MESSAGE_ERROR_NOT_INTEGER, } from './data/constants.js'; +import { replacePlaceholders } from './utils/strings.js'; document .getElementById(TEMPERATURES_INPUT) @@ -23,7 +25,7 @@ document if (fahrenheitTextList) { if (!/^[0-9\s]*$/.test(fahrenheitTextList)) { window.alert( - `The '${fahrenheitTextList}' contains values different of integer numbers`, + replacePlaceholders(MESSAGE_ERROR_NOT_INTEGER, fahrenheitTextList), ); return; } diff --git a/exercises/convertTemperature/src/utils/strings.js b/exercises/convertTemperature/src/utils/strings.js new file mode 100644 index 00000000..151d35ff --- /dev/null +++ b/exercises/convertTemperature/src/utils/strings.js @@ -0,0 +1,16 @@ +/** + * Returns a new string with all placeholders ({0}, {1}, {n}) replaced. + * Reference: https://medium.com/@onlinemsr/javascript-string-format-the-best-3-ways-to-do-it-c6a12b4b94ed + * + * @param {string} template - The string with placeholders. + * @param {string[]} args - The replacements. + * @returns {string} - The string with all placeholders ({0}, {1}, {n}) replaced. + */ +export const replacePlaceholders = (template, ...args) => { + if (template) { + return template.replace(/{([0-9]+)}/g, function (match, index) { + return typeof args[index] === 'undefined' ? match : args[index]; + }); + } + return template; +}; diff --git a/exercises/convertTemperature/src/utils/strings.test.js b/exercises/convertTemperature/src/utils/strings.test.js new file mode 100644 index 00000000..06231241 --- /dev/null +++ b/exercises/convertTemperature/src/utils/strings.test.js @@ -0,0 +1,24 @@ +import { replacePlaceholders } from './strings'; + +describe('replacePlaceholders', () => { + describe.each` + text | replacements | expected + ${null} | ${[]} | ${null} + ${null} | ${['text']} | ${null} + ${''} | ${[]} | ${''} + ${''} | ${['text']} | ${''} + ${'text'} | ${['text']} | ${'text'} + ${'{0} text {0}'} | ${['zero']} | ${'zero text zero'} + ${'{0} text {1}'} | ${['zero', 'one']} | ${'zero text one'} + ${'{0} text {1}'} | ${['zero', 'one']} | ${'zero text one'} + ${'{0} text {1}'} | ${[]} | ${'{0} text {1}'} + ${'{0} text {1}'} | ${['zero']} | ${'zero text {1}'} + `( + 'text $text with replacements $replacements', + ({ text, replacements, expected }) => { + it(`returns ${expected}`, () => { + expect(replacePlaceholders(text, ...replacements)).toBe(expected); + }); + }, + ); +}); From 970ec95063f7674a812b3e296c5a007d3ee50772 Mon Sep 17 00:00:00 2001 From: Dannyel Cardoso da Fonseca Date: Thu, 2 May 2024 19:03:59 +0200 Subject: [PATCH 5/9] Refactor listener and handlers --- .../convertTemperature/src/data/constants.js | 10 +++- .../handlers/convert-temperatures-handler.js | 43 +++++++++++++++++ .../convert-temperatures-handler.test.js | 34 +++++++++++++ exercises/convertTemperature/src/init.js | 48 ++----------------- .../listeners/temperatures-input-listener.js | 7 +++ .../temperatures-input-listener.test.js | 31 ++++++++++++ 6 files changed, 127 insertions(+), 46 deletions(-) create mode 100644 exercises/convertTemperature/src/handlers/convert-temperatures-handler.js create mode 100644 exercises/convertTemperature/src/handlers/convert-temperatures-handler.test.js create mode 100644 exercises/convertTemperature/src/listeners/temperatures-input-listener.js create mode 100644 exercises/convertTemperature/src/listeners/temperatures-input-listener.test.js diff --git a/exercises/convertTemperature/src/data/constants.js b/exercises/convertTemperature/src/data/constants.js index b06b430c..4d530615 100644 --- a/exercises/convertTemperature/src/data/constants.js +++ b/exercises/convertTemperature/src/data/constants.js @@ -1,5 +1,13 @@ -export const SPACE_REGEX = /\s+/; +// Events +export const ON_CHANGE = 'change'; + +// Selectors export const TEMPERATURES_INPUT = 'temperatures'; export const CONVERT_TEMPERATURE_CONTAINER = 'convertedTemperatures'; + +// Regex +export const SPACE_REGEX = /\s+/; + +// Messages export const MESSAGE_ERROR_NOT_INTEGER = "The '{0}' contains values different of integer numbers"; diff --git a/exercises/convertTemperature/src/handlers/convert-temperatures-handler.js b/exercises/convertTemperature/src/handlers/convert-temperatures-handler.js new file mode 100644 index 00000000..7818d2d7 --- /dev/null +++ b/exercises/convertTemperature/src/handlers/convert-temperatures-handler.js @@ -0,0 +1,43 @@ +import { + SPACE_REGEX, + CONVERT_TEMPERATURE_CONTAINER, + MESSAGE_ERROR_NOT_INTEGER, +} from '../data/constants.js'; +import { replacePlaceholders } from '../utils/strings.js'; + +export const convertTemperaturesHandler = (event) => { + //debugger; // each time the user changes the 'temperatures' input + + const convertedTemperaturesContainer = document.getElementById( + CONVERT_TEMPERATURE_CONTAINER, + ); + + // Erase previous content + convertedTemperaturesContainer.innerHTML = ''; + + // Get the text input + const fahrenheitTextList = event.target.value; + + // Validade it + if (fahrenheitTextList) { + if (!/^[0-9\s]*$/.test(fahrenheitTextList)) { + window.alert( + replacePlaceholders(MESSAGE_ERROR_NOT_INTEGER, fahrenheitTextList), + ); + return; + } + + // Sanitize it + const fahrenheitList = fahrenheitTextList.trim().split(SPACE_REGEX); + fahrenheitList.forEach((fahrenheit) => { + // Do the math + const celsius = ((fahrenheit - 32) * 5) / 9; + + // Render the result + const liString = `
  • ${celsius.toFixed(2)}
  • `; + + convertedTemperaturesContainer.innerHTML = + convertedTemperaturesContainer.innerHTML + liString; + }); + } +}; diff --git a/exercises/convertTemperature/src/handlers/convert-temperatures-handler.test.js b/exercises/convertTemperature/src/handlers/convert-temperatures-handler.test.js new file mode 100644 index 00000000..359af893 --- /dev/null +++ b/exercises/convertTemperature/src/handlers/convert-temperatures-handler.test.js @@ -0,0 +1,34 @@ +/** + * @jest-environment jsdom + */ + +import { + CONVERT_TEMPERATURE_CONTAINER, + TEMPERATURES_INPUT, +} from '../data/constants'; +import { convertTemperaturesHandler } from './convert-temperatures-handler'; + +describe('convertTemperaturesHandler', () => { + beforeEach(() => { + document.body.innerHTML = ` +
    + +
    +
    + `; + }); + + it('should convert the input temperatures', () => { + // GIVEN + const event = { target: { value: '0 32 64' } }; + // WHEN + convertTemperaturesHandler(event); + // THEN + const innerHTML = document.getElementById( + CONVERT_TEMPERATURE_CONTAINER, + ).innerHTML; + expect(innerHTML).toContain('>-17.78<'); + expect(innerHTML).toContain('>0.00<'); + expect(innerHTML).toContain('>17.78<'); + }); +}); diff --git a/exercises/convertTemperature/src/init.js b/exercises/convertTemperature/src/init.js index 0e435ffa..9ff9e758 100644 --- a/exercises/convertTemperature/src/init.js +++ b/exercises/convertTemperature/src/init.js @@ -1,46 +1,4 @@ -import { - SPACE_REGEX, - TEMPERATURES_INPUT, - CONVERT_TEMPERATURE_CONTAINER, - MESSAGE_ERROR_NOT_INTEGER, -} from './data/constants.js'; -import { replacePlaceholders } from './utils/strings.js'; +import { convertTemperaturesHandler } from './handlers/convert-temperatures-handler.js'; +import { addTemperaturesChangeListener } from './listeners/temperatures-input-listener.js'; -document - .getElementById(TEMPERATURES_INPUT) - .addEventListener('change', (event) => { - //debugger; // each time the user changes the 'temperatures' input - - const convertedTemperaturesContainer = document.getElementById( - CONVERT_TEMPERATURE_CONTAINER, - ); - - // Erase previous content - convertedTemperaturesContainer.innerHTML = ''; - - // Get the text input - const fahrenheitTextList = event.target.value; - - // Validade it - if (fahrenheitTextList) { - if (!/^[0-9\s]*$/.test(fahrenheitTextList)) { - window.alert( - replacePlaceholders(MESSAGE_ERROR_NOT_INTEGER, fahrenheitTextList), - ); - return; - } - - // Sanitize it - const fahrenheitList = fahrenheitTextList.trim().split(SPACE_REGEX); - fahrenheitList.forEach((fahrenheit) => { - // Do the math - const celsius = ((fahrenheit - 32) * 5) / 9; - - // Render the result - const liString = `
  • ${celsius.toFixed(2)}
  • `; - - convertedTemperaturesContainer.innerHTML = - convertedTemperaturesContainer.innerHTML + liString; - }); - } - }); +addTemperaturesChangeListener(convertTemperaturesHandler); diff --git a/exercises/convertTemperature/src/listeners/temperatures-input-listener.js b/exercises/convertTemperature/src/listeners/temperatures-input-listener.js new file mode 100644 index 00000000..d57b6d68 --- /dev/null +++ b/exercises/convertTemperature/src/listeners/temperatures-input-listener.js @@ -0,0 +1,7 @@ +import { ON_CHANGE, TEMPERATURES_INPUT } from '../data/constants.js'; + +export const addTemperaturesChangeListener = (handler) => { + document + .getElementById(TEMPERATURES_INPUT) + ?.addEventListener(ON_CHANGE, handler); +}; diff --git a/exercises/convertTemperature/src/listeners/temperatures-input-listener.test.js b/exercises/convertTemperature/src/listeners/temperatures-input-listener.test.js new file mode 100644 index 00000000..ddad61dc --- /dev/null +++ b/exercises/convertTemperature/src/listeners/temperatures-input-listener.test.js @@ -0,0 +1,31 @@ +/** + * @jest-environment jsdom + */ + +import { ON_CHANGE, TEMPERATURES_INPUT } from '../data/constants'; +import { addTemperaturesChangeListener } from './temperatures-input-listener'; + +describe('addTemperatureChangeListener', () => { + beforeEach(() => { + document.body.innerHTML = ` +
    + +
    + `; + }); + + it('should add a handler to the temperature change', () => { + // GIVEN + const handler = jest.fn(); + const dispatchChangeTemperatures = () => { + document + .getElementById(TEMPERATURES_INPUT) + .dispatchEvent(new window.Event(ON_CHANGE)); + }; + // WHEN + addTemperaturesChangeListener(handler); + dispatchChangeTemperatures(); + // THEN + expect(handler).toHaveBeenCalledTimes(1); + }); +}); From 0353bcfec92f1457222dde3fae1b656efd7cf919 Mon Sep 17 00:00:00 2001 From: Dannyel Cardoso da Fonseca Date: Fri, 3 May 2024 00:03:04 +0200 Subject: [PATCH 6/9] Refactor the convertTemperaturesHandler --- .../convertTemperature/src/data/constants.js | 2 + .../handlers/convert-temperatures-handler.js | 74 ++++++++++++------- .../convert-temperatures-handler.test.js | 15 ++++ .../listeners/temperatures-input-listener.js | 5 ++ .../src/logic/convert-temperatures.js | 9 +++ .../src/logic/convert-temperatures.test.js | 14 ++++ .../convertTemperature/src/utils/parsers.js | 18 +++++ .../src/utils/parsers.test.js | 18 +++++ .../convertTemperature/src/utils/strings.js | 4 +- .../src/utils/validators.js | 11 +++ .../src/utils/validators.test.js | 18 +++++ 11 files changed, 161 insertions(+), 27 deletions(-) create mode 100644 exercises/convertTemperature/src/logic/convert-temperatures.js create mode 100644 exercises/convertTemperature/src/logic/convert-temperatures.test.js create mode 100644 exercises/convertTemperature/src/utils/parsers.js create mode 100644 exercises/convertTemperature/src/utils/parsers.test.js create mode 100644 exercises/convertTemperature/src/utils/validators.js create mode 100644 exercises/convertTemperature/src/utils/validators.test.js diff --git a/exercises/convertTemperature/src/data/constants.js b/exercises/convertTemperature/src/data/constants.js index 4d530615..e14994b4 100644 --- a/exercises/convertTemperature/src/data/constants.js +++ b/exercises/convertTemperature/src/data/constants.js @@ -7,6 +7,8 @@ export const CONVERT_TEMPERATURE_CONTAINER = 'convertedTemperatures'; // Regex export const SPACE_REGEX = /\s+/; +export const NUMBERS_SPACES_REGEX = /^[0-9\s]*$/; +export const PLACEHOLDER_REGEX = /{([0-9]+)}/g; // Messages export const MESSAGE_ERROR_NOT_INTEGER = diff --git a/exercises/convertTemperature/src/handlers/convert-temperatures-handler.js b/exercises/convertTemperature/src/handlers/convert-temperatures-handler.js index 7818d2d7..af79336a 100644 --- a/exercises/convertTemperature/src/handlers/convert-temperatures-handler.js +++ b/exercises/convertTemperature/src/handlers/convert-temperatures-handler.js @@ -1,43 +1,65 @@ import { - SPACE_REGEX, CONVERT_TEMPERATURE_CONTAINER, MESSAGE_ERROR_NOT_INTEGER, } from '../data/constants.js'; +import { fahrenheitToCelcius } from '../logic/convert-temperatures.js'; +import { parseToNumberArray } from '../utils/parsers.js'; import { replacePlaceholders } from '../utils/strings.js'; +import { containsOnlyNumbersAndSpaces } from '../utils/validators.js'; -export const convertTemperaturesHandler = (event) => { - //debugger; // each time the user changes the 'temperatures' input - +const prepareOutputContainer = () => { const convertedTemperaturesContainer = document.getElementById( CONVERT_TEMPERATURE_CONTAINER, ); - - // Erase previous content convertedTemperaturesContainer.innerHTML = ''; + return convertedTemperaturesContainer; +}; - // Get the text input - const fahrenheitTextList = event.target.value; +const renderOutputContainer = (outputContainer, celsiusList) => { + celsiusList.forEach((celsius) => { + const liString = `
  • ${celsius.toFixed(2)}
  • `; + outputContainer.innerHTML = outputContainer.innerHTML + liString; + }); +}; - // Validade it - if (fahrenheitTextList) { - if (!/^[0-9\s]*$/.test(fahrenheitTextList)) { - window.alert( - replacePlaceholders(MESSAGE_ERROR_NOT_INTEGER, fahrenheitTextList), - ); - return; - } +const validateTextInput = (text) => { + if (!text) { + throw new Error(); + } - // Sanitize it - const fahrenheitList = fahrenheitTextList.trim().split(SPACE_REGEX); - fahrenheitList.forEach((fahrenheit) => { - // Do the math - const celsius = ((fahrenheit - 32) * 5) / 9; + if (!containsOnlyNumbersAndSpaces(text)) { + throw new Error(replacePlaceholders(MESSAGE_ERROR_NOT_INTEGER, text)); + } +}; - // Render the result - const liString = `
  • ${celsius.toFixed(2)}
  • `; +const convertTemperatures = (fahrenheitTextList) => { + validateTextInput(fahrenheitTextList); + const fahrenheitList = parseToNumberArray(fahrenheitTextList); + const celsiusList = fahrenheitList.map((fahrenheit) => { + return fahrenheitToCelcius(fahrenheit); + }); + return celsiusList; +}; - convertedTemperaturesContainer.innerHTML = - convertedTemperaturesContainer.innerHTML + liString; - }); +/** + * Add an 'change' event listener to the element whose id is 'temperatures'. + * + * @param {Event} event - the 'change' event. + */ +export const convertTemperaturesHandler = (event) => { + try { + // Input + const convertedTemperaturesContainer = prepareOutputContainer(); + + // Logic + const fahrenheitTextList = event.target.value; + const celsiusList = convertTemperatures(fahrenheitTextList); + + // Output + renderOutputContainer(convertedTemperaturesContainer, celsiusList); + } catch (error) { + if (error.message) { + window.alert(error.message); + } } }; diff --git a/exercises/convertTemperature/src/handlers/convert-temperatures-handler.test.js b/exercises/convertTemperature/src/handlers/convert-temperatures-handler.test.js index 359af893..2b0942e8 100644 --- a/exercises/convertTemperature/src/handlers/convert-temperatures-handler.test.js +++ b/exercises/convertTemperature/src/handlers/convert-temperatures-handler.test.js @@ -4,8 +4,10 @@ import { CONVERT_TEMPERATURE_CONTAINER, + MESSAGE_ERROR_NOT_INTEGER, TEMPERATURES_INPUT, } from '../data/constants'; +import { replacePlaceholders } from '../utils/strings'; import { convertTemperaturesHandler } from './convert-temperatures-handler'; describe('convertTemperaturesHandler', () => { @@ -31,4 +33,17 @@ describe('convertTemperaturesHandler', () => { expect(innerHTML).toContain('>0.00<'); expect(innerHTML).toContain('>17.78<'); }); + + it('should show an alert when invalid input', () => { + // GIVEN + const input = '0 invalid 64'; + const event = { target: { value: input } }; + window.alert = jest.fn(); + // WHEN + convertTemperaturesHandler(event); + // THEN + expect(window.alert).toHaveBeenCalledWith( + replacePlaceholders(MESSAGE_ERROR_NOT_INTEGER, input), + ); + }); }); diff --git a/exercises/convertTemperature/src/listeners/temperatures-input-listener.js b/exercises/convertTemperature/src/listeners/temperatures-input-listener.js index d57b6d68..b9c3e71a 100644 --- a/exercises/convertTemperature/src/listeners/temperatures-input-listener.js +++ b/exercises/convertTemperature/src/listeners/temperatures-input-listener.js @@ -1,5 +1,10 @@ import { ON_CHANGE, TEMPERATURES_INPUT } from '../data/constants.js'; +/** + * Add an 'change' event listener to the element whose id is 'temperatures'. + * + * @param {function} handler - Callback function to be called when the event occurs. + */ export const addTemperaturesChangeListener = (handler) => { document .getElementById(TEMPERATURES_INPUT) diff --git a/exercises/convertTemperature/src/logic/convert-temperatures.js b/exercises/convertTemperature/src/logic/convert-temperatures.js new file mode 100644 index 00000000..5e0d5901 --- /dev/null +++ b/exercises/convertTemperature/src/logic/convert-temperatures.js @@ -0,0 +1,9 @@ +/** + * Convert a temperature in Fahrenheit degrees to Celcius degrees. + * + * @param {number} fahrenheit - Temperature in Fahrenheit degrees. + * @returns {number} - Temperature in Celcius degrees. + */ +export const fahrenheitToCelcius = (fahrenheit) => { + return ((fahrenheit - 32) * 5) / 9; +}; diff --git a/exercises/convertTemperature/src/logic/convert-temperatures.test.js b/exercises/convertTemperature/src/logic/convert-temperatures.test.js new file mode 100644 index 00000000..f65252ce --- /dev/null +++ b/exercises/convertTemperature/src/logic/convert-temperatures.test.js @@ -0,0 +1,14 @@ +import { fahrenheitToCelcius } from './convert-temperatures'; + +describe('fahrenheitToCelcius', () => { + describe.each` + fahrenheit | expected + ${0} | ${-17.77777777777778} + ${32} | ${0} + ${64} | ${17.77777777777778} + `('$fahrenheit fahrenheit', ({ fahrenheit, expected }) => { + it(`returns ${expected} celcius`, () => { + expect(fahrenheitToCelcius(fahrenheit)).toEqual(expected); + }); + }); +}); diff --git a/exercises/convertTemperature/src/utils/parsers.js b/exercises/convertTemperature/src/utils/parsers.js new file mode 100644 index 00000000..e1dadc23 --- /dev/null +++ b/exercises/convertTemperature/src/utils/parsers.js @@ -0,0 +1,18 @@ +import { SPACE_REGEX } from '../data/constants.js'; + +/** + * Convert a text containing numbers to an array of numbers. + * If the input text contains non valid numbers, NaN is used as its substitute. + * + * @param {string} text - The text to be parsed. + * @returns {number[]} - Array of numbers. + */ +export const parseToNumberArray = (text) => { + if (text) { + return text + .trim() + .split(SPACE_REGEX) + .map((textNumber) => Number(textNumber)); + } + return []; +}; diff --git a/exercises/convertTemperature/src/utils/parsers.test.js b/exercises/convertTemperature/src/utils/parsers.test.js new file mode 100644 index 00000000..e6b33ecc --- /dev/null +++ b/exercises/convertTemperature/src/utils/parsers.test.js @@ -0,0 +1,18 @@ +import { parseToNumberArray } from './parsers'; + +describe('parseToNumberArray', () => { + describe.each` + text | expected + ${null} | ${[]} + ${undefined} | ${[]} + ${''} | ${[]} + ${'1 2 3'} | ${[1, 2, 3]} + ${' \t 1 \n 2 3 '} | ${[1, 2, 3]} + ${'-1 -2 -3'} | ${[-1, -2, -3]} + ${'1 a @'} | ${[1, NaN, NaN]} + `('text $text', ({ text, expected }) => { + it(`returns ${expected}`, () => { + expect(parseToNumberArray(text)).toEqual(expected); + }); + }); +}); diff --git a/exercises/convertTemperature/src/utils/strings.js b/exercises/convertTemperature/src/utils/strings.js index 151d35ff..33e0f3d8 100644 --- a/exercises/convertTemperature/src/utils/strings.js +++ b/exercises/convertTemperature/src/utils/strings.js @@ -1,3 +1,5 @@ +import { PLACEHOLDER_REGEX } from '../data/constants.js'; + /** * Returns a new string with all placeholders ({0}, {1}, {n}) replaced. * Reference: https://medium.com/@onlinemsr/javascript-string-format-the-best-3-ways-to-do-it-c6a12b4b94ed @@ -8,7 +10,7 @@ */ export const replacePlaceholders = (template, ...args) => { if (template) { - return template.replace(/{([0-9]+)}/g, function (match, index) { + return template.replace(PLACEHOLDER_REGEX, function (match, index) { return typeof args[index] === 'undefined' ? match : args[index]; }); } diff --git a/exercises/convertTemperature/src/utils/validators.js b/exercises/convertTemperature/src/utils/validators.js new file mode 100644 index 00000000..6d991ea5 --- /dev/null +++ b/exercises/convertTemperature/src/utils/validators.js @@ -0,0 +1,11 @@ +import { NUMBERS_SPACES_REGEX } from '../data/constants.js'; + +/** + * Check if the input text contains only number characters or spaces. + * + * @param {string} text - The text to be checked. + * @returns {boolean} - It returns true if the text match with /^[0-9\s]*$/ or false otherwise. + */ +export const containsOnlyNumbersAndSpaces = (text) => { + return NUMBERS_SPACES_REGEX.test(text); +}; diff --git a/exercises/convertTemperature/src/utils/validators.test.js b/exercises/convertTemperature/src/utils/validators.test.js new file mode 100644 index 00000000..3905329e --- /dev/null +++ b/exercises/convertTemperature/src/utils/validators.test.js @@ -0,0 +1,18 @@ +import { containsOnlyNumbersAndSpaces } from './validators'; + +describe('containsOnlyNumbersAndSpaces', () => { + describe.each` + text | expected + ${null} | ${false} + ${undefined} | ${false} + ${''} | ${true} + ${'1 2 3'} | ${true} + ${' \t 1 \n 2 3 '} | ${true} + ${'-1 -2 -3'} | ${false} + ${'1 a @'} | ${false} + `('text $text', ({ text, expected }) => { + it(`returns ${expected}`, () => { + expect(containsOnlyNumbersAndSpaces(text)).toEqual(expected); + }); + }); +}); From 4b9b99e165c486ee9e1b594bacbedb10f3c6e6a8 Mon Sep 17 00:00:00 2001 From: Pallavi Sarwar Date: Tue, 7 May 2024 14:34:00 +0200 Subject: [PATCH 7/9] exercise list items --- .../state/list-items/data/constants.js | 3 +++ .../exercises/state/list-items/src/handler.js | 19 ++++++++++--- .../exercises/state/list-items/src/init.js | 15 +++++++++-- .../state/list-items/src/listener.js | 9 +++++++ .../exercises/state/list-items/src/util.js | 27 ++++++++++++++++++- .../state/list-items/src/util.spec.js | 25 ++++++++++++----- 6 files changed, 86 insertions(+), 12 deletions(-) diff --git a/6-refactoring/exercises/state/list-items/data/constants.js b/6-refactoring/exercises/state/list-items/data/constants.js index e69de29b..bd3f17b1 100644 --- a/6-refactoring/exercises/state/list-items/data/constants.js +++ b/6-refactoring/exercises/state/list-items/data/constants.js @@ -0,0 +1,3 @@ + + +export const bulletPoint = '*'; diff --git a/6-refactoring/exercises/state/list-items/src/handler.js b/6-refactoring/exercises/state/list-items/src/handler.js index 911a1c5d..6b5375f3 100644 --- a/6-refactoring/exercises/state/list-items/src/handler.js +++ b/6-refactoring/exercises/state/list-items/src/handler.js @@ -1,4 +1,17 @@ -import { list } from './utils.js'; -import { bulletPoint } from '../data/constants.js'; +import { generateList, displayResult, getUserInput } from './utils.js'; // Import utility functions +import { bulletPoint } from './constants.js'; // Import constant bulletPoint -export const listHandler = () => {}; +export function handleListButtonClick() { + // read & process user input + const allInputs = getUserInput(); + + // execute core logic + const stringList = generateList(allInputs, bulletPoint); + + // communicate result to user + const message = `all items:${stringList}`; + displayResult(message); +} + +// Add event listener +document.getElementById('list-them').addEventListener('click', handleListButtonClick); diff --git a/6-refactoring/exercises/state/list-items/src/init.js b/6-refactoring/exercises/state/list-items/src/init.js index f7e55374..a95207ff 100644 --- a/6-refactoring/exercises/state/list-items/src/init.js +++ b/6-refactoring/exercises/state/list-items/src/init.js @@ -1,4 +1,15 @@ -const bulletPoint = '*'; // data to refactor out of this file +import { setupEventListener } from './listeners.js'; + +// This is where you would initialize your application +// For example, setting up initial state or performing any setup tasks + +// Call the function to set up event listeners +setupEventListener(); + + + + +/*const bulletPoint = '*'; // data to refactor out of this file document.getElementById('list-them').addEventListener('click', () => { // read & process user input @@ -22,4 +33,4 @@ document.getElementById('list-them').addEventListener('click', () => { // communicate result to user const message = `all items:${stringList}`; alert(message); -}); +});*/ diff --git a/6-refactoring/exercises/state/list-items/src/listener.js b/6-refactoring/exercises/state/list-items/src/listener.js index 10f33d91..32bdfcd0 100644 --- a/6-refactoring/exercises/state/list-items/src/listener.js +++ b/6-refactoring/exercises/state/list-items/src/listener.js @@ -1 +1,10 @@ import { listHandler } from './handler.js'; +import { generateList, displayResult, getUserInput } from './utils.js'; +import { bulletPoint } from './constants.js'; + +document.getElementById('list-them').addEventListener('click', () => { + const allInputs = getUserInput(); + const stringList = generateList(allInputs, bulletPoint); + const message = `All items:${stringList}`; + displayResult(message); +}); diff --git a/6-refactoring/exercises/state/list-items/src/util.js b/6-refactoring/exercises/state/list-items/src/util.js index d8b1127b..a765fb99 100644 --- a/6-refactoring/exercises/state/list-items/src/util.js +++ b/6-refactoring/exercises/state/list-items/src/util.js @@ -1,4 +1,29 @@ /** * */ -export const list = () => {}; +export function generateList(allInputs, bulletPoint) { + let stringList = ''; + for (const input of allInputs) { + stringList += `\n${bulletPoint} ${input}`; + } + return stringList; + } + + export function displayResult(message) { + alert(message); + } + + export function getUserInput() { + const allInputs = []; + let acceptingInput = true; + while (acceptingInput) { + const nextInput = prompt('Enter a list item'); + if (nextInput !== null) { + allInputs.push(nextInput); + } else { + acceptingInput = false; + } + } + return allInputs; + } + diff --git a/6-refactoring/exercises/state/list-items/src/util.spec.js b/6-refactoring/exercises/state/list-items/src/util.spec.js index 22adc8cb..74f91390 100644 --- a/6-refactoring/exercises/state/list-items/src/util.spec.js +++ b/6-refactoring/exercises/state/list-items/src/util.spec.js @@ -1,33 +1,46 @@ -import { list } from './util.js'; +import { generateList, displayResult, getUserInput } from './util.js'; // Import correct functions from util.js describe('list: generates a list string from an array of strings', () => { describe('list: correctly lists items', () => { it('an empty array returns an empty string', () => { const expected = ''; - const actual = list([]); + const actual = generateList([]); // Call generateList function expect(actual).toEqual(expected); }); + it('can list a single item', () => { const expected = '\n- hi'; - const actual = list(['hi'], '-'); + const actual = generateList(['hi'], '-'); // Call generateList function expect(actual).toEqual(expected); }); + it('can list many items', () => { const expected = '\n+ a\n+ b\n+ c\n+ d\n+ e'; - const actual = list(['a', 'b', 'c', 'd', 'e'], '+'); + const actual = generateList(['a', 'b', 'c', 'd', 'e'], '+'); // Call generateList function expect(actual).toEqual(expected); }); + it('can use different bullet points', () => { const expected = '\n* a\n* b\n* c\n* d\n* e'; - const actual = list(['a', 'b', 'c', 'd', 'e'], '*'); + const actual = generateList(['a', 'b', 'c', 'd', 'e'], '*'); // Call generateList function expect(actual).toEqual(expected); }); }); + +describe('list: generates a list string from an array of strings', () => { describe('list: uses arguments correctly', () => { it('does not modify the array argument', () => { + // Mock the prompt function + global.prompt = jest.fn(() => null); + const arg = ['a', 'b', 'c', 'd']; - list(arg); + const result = getUserInput(arg); expect(arg).toEqual(['a', 'b', 'c', 'd']); + + // Remove the mock to avoid affecting other tests + global.prompt.mockRestore(); }); }); }); + + }); From 8144b6312263bd55c62c7b459767fef1f8040767 Mon Sep 17 00:00:00 2001 From: Pallavi Sarwar Date: Fri, 10 May 2024 13:46:06 +0200 Subject: [PATCH 8/9] code review comment resolved --- 6-refactoring/exercises/state/list-items/src/handler.js | 5 +++-- 6-refactoring/exercises/state/list-items/src/listener.js | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/6-refactoring/exercises/state/list-items/src/handler.js b/6-refactoring/exercises/state/list-items/src/handler.js index 6b5375f3..01ac8ddb 100644 --- a/6-refactoring/exercises/state/list-items/src/handler.js +++ b/6-refactoring/exercises/state/list-items/src/handler.js @@ -1,7 +1,7 @@ import { generateList, displayResult, getUserInput } from './utils.js'; // Import utility functions import { bulletPoint } from './constants.js'; // Import constant bulletPoint -export function handleListButtonClick() { +/*export function handleListButtonClick() { // read & process user input const allInputs = getUserInput(); @@ -11,7 +11,8 @@ export function handleListButtonClick() { // communicate result to user const message = `all items:${stringList}`; displayResult(message); -} +}*/ +const stringList = list(allInputs); // Add event listener document.getElementById('list-them').addEventListener('click', handleListButtonClick); diff --git a/6-refactoring/exercises/state/list-items/src/listener.js b/6-refactoring/exercises/state/list-items/src/listener.js index 32bdfcd0..39848ef9 100644 --- a/6-refactoring/exercises/state/list-items/src/listener.js +++ b/6-refactoring/exercises/state/list-items/src/listener.js @@ -1,10 +1,11 @@ import { listHandler } from './handler.js'; -import { generateList, displayResult, getUserInput } from './utils.js'; import { bulletPoint } from './constants.js'; +const setupEventListener = () => { document.getElementById('list-them').addEventListener('click', () => { const allInputs = getUserInput(); const stringList = generateList(allInputs, bulletPoint); const message = `All items:${stringList}`; displayResult(message); }); +} From b33920e2ab3f3bd636e24f2a3e71cddc21db4464 Mon Sep 17 00:00:00 2001 From: Pallavi Sarwar Date: Fri, 10 May 2024 14:44:17 +0200 Subject: [PATCH 9/9] code review comment resolved --- .../exercises/state/no-copies/data/state.js | 10 ++++- .../exercises/state/no-copies/src/handler.js | 14 ++++++- .../exercises/state/no-copies/src/init.js | 40 +++++++++---------- .../exercises/state/no-copies/src/listener.js | 13 +++++- .../exercises/state/no-copies/src/util.js | 18 ++++++++- .../state/no-copies/src/util.spec.js | 2 +- 6 files changed, 71 insertions(+), 26 deletions(-) diff --git a/6-refactoring/exercises/state/no-copies/data/state.js b/6-refactoring/exercises/state/no-copies/data/state.js index b6e03096..044af445 100644 --- a/6-refactoring/exercises/state/no-copies/data/state.js +++ b/6-refactoring/exercises/state/no-copies/data/state.js @@ -1 +1,9 @@ -export const state = _; + +const state = { + data: { + noCopies: [], + } + }; + +export default state; + diff --git a/6-refactoring/exercises/state/no-copies/src/handler.js b/6-refactoring/exercises/state/no-copies/src/handler.js index af72f6c0..688aa794 100644 --- a/6-refactoring/exercises/state/no-copies/src/handler.js +++ b/6-refactoring/exercises/state/no-copies/src/handler.js @@ -1,4 +1,14 @@ -import { saveNoCopies } from './utils.js'; +import { saveNoCopies } from './util.js'; import { state } from '../data/state.js'; -export const saveNoCopiesHandler = () => {}; +export const saveNoCopiesHandler = () => { + + + // communicate result to user + const message = saveNoCopies(); + alert(message); + + // log interaction + console.log(data); + }; + export default saveNoCopiesHandler; \ No newline at end of file diff --git a/6-refactoring/exercises/state/no-copies/src/init.js b/6-refactoring/exercises/state/no-copies/src/init.js index 4ef5574e..060302ae 100644 --- a/6-refactoring/exercises/state/no-copies/src/init.js +++ b/6-refactoring/exercises/state/no-copies/src/init.js @@ -1,25 +1,25 @@ // data to refactor out of this file -const data = { - noCopies: [], -}; +import callEventListener from "./listener.js"; -document.getElementById('no-copies-button').addEventListener('click', () => { - // read & process user input - let userInput = null; - while (userInput === null) { - userInput = prompt('enter a string to save'); - } +callEventListener(); - // execute core logic - const alreadySaved = data.noCopies.includes(userInput); - if (!alreadySaved) { - data.noCopies.push(userInput); - } +// document.getElementById('no-copies-button').addEventListener('click', () => { +// // read & process user input +// let userInput = null; +// while (userInput === null) { +// userInput = prompt('enter a string to save'); +// } - // communicate result to user - const message = data.noCopies.join(', '); - alert(message); +// // execute core logic +// const alreadySaved = data.noCopies.includes(userInput); +// if (!alreadySaved) { +// data.noCopies.push(userInput); +// } - // log interaction - console.log(data); -}); +// // communicate result to user +// const message = data.noCopies.join(', '); +// alert(message); + +// // log interaction +// console.log(data); +// }); diff --git a/6-refactoring/exercises/state/no-copies/src/listener.js b/6-refactoring/exercises/state/no-copies/src/listener.js index f1dc26f7..b47a59a7 100644 --- a/6-refactoring/exercises/state/no-copies/src/listener.js +++ b/6-refactoring/exercises/state/no-copies/src/listener.js @@ -1 +1,12 @@ -import { saveNoCopiesHandler } from './handler.js'; +import saveNoCopiesHandler from './handler.js'; + + +const callEventListener = () => { + // document + // .getElementById(TEMPERATURE_INPUT) + // .addEventListener('change', handlers); + document.getElementById('no-copies-button').addEventListener('click', saveNoCopiesHandler); + + }; + + export default callEventListener; \ No newline at end of file diff --git a/6-refactoring/exercises/state/no-copies/src/util.js b/6-refactoring/exercises/state/no-copies/src/util.js index 44848c21..4d6d4d73 100644 --- a/6-refactoring/exercises/state/no-copies/src/util.js +++ b/6-refactoring/exercises/state/no-copies/src/util.js @@ -1,4 +1,20 @@ /** * */ -export const saveNoCopies = () => {}; +import state from '../data/state.js'; + +export const saveNoCopies = () => { + let userInput = null; + while (userInput === null) { + userInput = prompt('enter a string to save'); + } + + // execute core logic + const alreadySaved = state.data.noCopies.includes(userInput); + if (!alreadySaved) { + state.data.noCopies.push(userInput); + } + + // communicate result to user + return state.data.noCopies.join(', '); +}; diff --git a/6-refactoring/exercises/state/no-copies/src/util.spec.js b/6-refactoring/exercises/state/no-copies/src/util.spec.js index fb53be18..ba934f23 100644 --- a/6-refactoring/exercises/state/no-copies/src/util.spec.js +++ b/6-refactoring/exercises/state/no-copies/src/util.spec.js @@ -1,4 +1,4 @@ -import { saveNoCopies } from './utils.js'; +import { saveNoCopies } from './util.js'; describe('saveNoCopies: ', () => { describe('adds a new items that are not in the array', () => {