diff --git a/6-refactoring/exercises/dom-and-events/mouse-coordinates/data/constants.js b/6-refactoring/exercises/dom-and-events/mouse-coordinates/data/constants.js
index e69de29b..c19eca8f 100644
--- a/6-refactoring/exercises/dom-and-events/mouse-coordinates/data/constants.js
+++ b/6-refactoring/exercises/dom-and-events/mouse-coordinates/data/constants.js
@@ -0,0 +1,3 @@
+export const MOUSE_USER_INTERFACE = 'user-interface';
+export const MOUSE_MOVE = 'mousemove';
+export const MOUSE_POSITION = 'mouse-position';
\ No newline at end of file
diff --git a/6-refactoring/exercises/dom-and-events/mouse-coordinates/src/handler.js b/6-refactoring/exercises/dom-and-events/mouse-coordinates/src/handler.js
index 68c01a10..fd08f2c3 100644
--- a/6-refactoring/exercises/dom-and-events/mouse-coordinates/src/handler.js
+++ b/6-refactoring/exercises/dom-and-events/mouse-coordinates/src/handler.js
@@ -1,3 +1,16 @@
-import { formatCoordinates } from './utils.js';
+import { formatCoordinates } from './util.js';
-export const showMouseCoordinates = () => {};
+export const showMouseCoordinates = (event) => {
+ debugger;
+ // read & process user input
+ const xValue = event.pageX;
+ const yValue = event.pageY;
+ // execute core logic
+ const formattedCoordinates = formatCoordinates(xValue, yValue);
+ // render result for user
+ document.getElementById(MOUSE_POSITION).innerHTML = formattedCoordinates;
+ // log action for developers
+ console.log('\n--- new coordinates ---');
+ console.log('x:', xValue);
+ console.log('y:', yValue);
+ };
\ No newline at end of file
diff --git a/6-refactoring/exercises/dom-and-events/mouse-coordinates/src/init.js b/6-refactoring/exercises/dom-and-events/mouse-coordinates/src/init.js
index 52f28e2d..0a5662e2 100644
--- a/6-refactoring/exercises/dom-and-events/mouse-coordinates/src/init.js
+++ b/6-refactoring/exercises/dom-and-events/mouse-coordinates/src/init.js
@@ -1,4 +1,7 @@
-document
+
+import './listener.js'
+
+/*document
.getElementById('user-interface')
.addEventListener('mousemove', (event) => {
debugger;
@@ -16,4 +19,4 @@ document
console.log('\n--- new coordinates ---');
console.log('x:', xValue);
console.log('y:', yValue);
- });
+ });*/
diff --git a/6-refactoring/exercises/dom-and-events/mouse-coordinates/src/listener.js b/6-refactoring/exercises/dom-and-events/mouse-coordinates/src/listener.js
index 8c9e2681..62990cd8 100644
--- a/6-refactoring/exercises/dom-and-events/mouse-coordinates/src/listener.js
+++ b/6-refactoring/exercises/dom-and-events/mouse-coordinates/src/listener.js
@@ -1 +1,13 @@
import { showMouseCoordinates } from './handler.js';
+document
+ .getElementById(MOUSE_USER_INTERFACE)
+ .addEventListener(MOUSE_MOVE, showMouseCoordinates);
+
+
+
+
+
+
+
+
+
diff --git a/6-refactoring/exercises/dom-and-events/mouse-coordinates/src/util.js b/6-refactoring/exercises/dom-and-events/mouse-coordinates/src/util.js
index 713bde23..f3e7eb4a 100644
--- a/6-refactoring/exercises/dom-and-events/mouse-coordinates/src/util.js
+++ b/6-refactoring/exercises/dom-and-events/mouse-coordinates/src/util.js
@@ -1,4 +1,5 @@
-/**
- *
- */
-export const formatCoordinates = () => {};
+export const formatCoordinates = (xValue , yValue) => {
+ // execute core logic
+ const formattedCoordinates = 'X: ' + xValue + '\nY: ' + yValue;
+ return formattedCoordinates;
+};
\ No newline at end of file
diff --git a/6-refactoring/exercises/dom-and-events/mouse-coordinates/src/util.spec.js b/6-refactoring/exercises/dom-and-events/mouse-coordinates/src/util.spec.js
index d4aca866..ae902a69 100644
--- a/6-refactoring/exercises/dom-and-events/mouse-coordinates/src/util.spec.js
+++ b/6-refactoring/exercises/dom-and-events/mouse-coordinates/src/util.spec.js
@@ -1,4 +1,4 @@
-import { formatCoordinates } from './utils.js';
+import { formatCoordinates } from './util.js';
describe('formatCoordinates: formats two numbers into a coordinates string', () => {
it('positive numbers', () => {
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/data/constants.js b/exercises/convertTemperature/src/data/constants.js
new file mode 100644
index 00000000..e14994b4
--- /dev/null
+++ b/exercises/convertTemperature/src/data/constants.js
@@ -0,0 +1,15 @@
+// Events
+export const ON_CHANGE = 'change';
+
+// Selectors
+export const TEMPERATURES_INPUT = 'temperatures';
+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 =
+ "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..af79336a
--- /dev/null
+++ b/exercises/convertTemperature/src/handlers/convert-temperatures-handler.js
@@ -0,0 +1,65 @@
+import {
+ 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';
+
+const prepareOutputContainer = () => {
+ const convertedTemperaturesContainer = document.getElementById(
+ CONVERT_TEMPERATURE_CONTAINER,
+ );
+ convertedTemperaturesContainer.innerHTML = '';
+ return convertedTemperaturesContainer;
+};
+
+const renderOutputContainer = (outputContainer, celsiusList) => {
+ celsiusList.forEach((celsius) => {
+ const liString = `${celsius.toFixed(2)}`;
+ outputContainer.innerHTML = outputContainer.innerHTML + liString;
+ });
+};
+
+const validateTextInput = (text) => {
+ if (!text) {
+ throw new Error();
+ }
+
+ if (!containsOnlyNumbersAndSpaces(text)) {
+ throw new Error(replacePlaceholders(MESSAGE_ERROR_NOT_INTEGER, text));
+ }
+};
+
+const convertTemperatures = (fahrenheitTextList) => {
+ validateTextInput(fahrenheitTextList);
+ const fahrenheitList = parseToNumberArray(fahrenheitTextList);
+ const celsiusList = fahrenheitList.map((fahrenheit) => {
+ return fahrenheitToCelcius(fahrenheit);
+ });
+ return celsiusList;
+};
+
+/**
+ * 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
new file mode 100644
index 00000000..2b0942e8
--- /dev/null
+++ b/exercises/convertTemperature/src/handlers/convert-temperatures-handler.test.js
@@ -0,0 +1,49 @@
+/**
+ * @jest-environment jsdom
+ */
+
+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', () => {
+ 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<');
+ });
+
+ 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/init.js b/exercises/convertTemperature/src/init.js
new file mode 100644
index 00000000..9ff9e758
--- /dev/null
+++ b/exercises/convertTemperature/src/init.js
@@ -0,0 +1,4 @@
+import { convertTemperaturesHandler } from './handlers/convert-temperatures-handler.js';
+import { addTemperaturesChangeListener } from './listeners/temperatures-input-listener.js';
+
+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..b9c3e71a
--- /dev/null
+++ b/exercises/convertTemperature/src/listeners/temperatures-input-listener.js
@@ -0,0 +1,12 @@
+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)
+ ?.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);
+ });
+});
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
new file mode 100644
index 00000000..33e0f3d8
--- /dev/null
+++ b/exercises/convertTemperature/src/utils/strings.js
@@ -0,0 +1,18 @@
+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
+ *
+ * @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(PLACEHOLDER_REGEX, 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);
+ });
+ },
+ );
+});
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);
+ });
+ });
+});
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;
+}
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"
}
}