diff --git a/client/src/client/frame-runner.js b/client/src/client/frame-runner.js index 84a94dce7dd..e2bf59931fd 100644 --- a/client/src/client/frame-runner.js +++ b/client/src/client/frame-runner.js @@ -1,5 +1,6 @@ import '@babel/polyfill'; import jQuery from 'jquery'; +import curriculumHelpers from '../utils/curriculum-helpers'; window.$ = jQuery; @@ -48,6 +49,7 @@ async function initTestFrame(e = { code: {} }) { // eslint-disable-next-line no-inline-comments const { default: chai } = await import(/* webpackChunkName: "chai" */ 'chai'); const assert = chai.assert; + const __helpers = curriculumHelpers; /* eslint-enable no-unused-vars */ let Enzyme; diff --git a/client/src/client/workers/test-evaluator.js b/client/src/client/workers/test-evaluator.js index 4f146101ac9..cec40e4ebaa 100644 --- a/client/src/client/workers/test-evaluator.js +++ b/client/src/client/workers/test-evaluator.js @@ -2,6 +2,7 @@ import chai from 'chai'; import '@babel/polyfill'; import __toString from 'lodash/toString'; import { format as __format } from '../../utils/format'; +import curriculumHelpers from '../../utils/curriculum-helpers'; const __utils = (() => { const MAX_LOGS_SIZE = 64 * 1024; @@ -59,6 +60,7 @@ self.onmessage = async e => { const editableContents = (e.data?.code?.editableContents || '').slice(); const assert = chai.assert; + const __helpers = curriculumHelpers; // Fake Deep Equal dependency const DeepEqual = (a, b) => JSON.stringify(a) === JSON.stringify(b); diff --git a/client/src/utils/__fixtures/curriculum-helpers-css.js b/client/src/utils/__fixtures/curriculum-helpers-css.js new file mode 100644 index 00000000000..e34371664f8 --- /dev/null +++ b/client/src/utils/__fixtures/curriculum-helpers-css.js @@ -0,0 +1,45 @@ +const cssFullExample = ` +a { + color: green; + display: flex; +} +.aClass { + font-size: 32px; + /* the property below should not appear in final css string + width: 400px; + height: 200px; + */ + flex: 1; + flex-direction: row; +} +/* Set the background color to blue for screens that are 300px or less */ +@media screen and (max-width: 300px) { + body { + background-color: blue; + } +}`; + +const cssCodeWithCommentsRemoved = ` +a { + color: green; + display: flex; +} +.aClass { + font-size: 32px; + + flex: 1; + flex-direction: row; +} + +@media screen and (max-width: 300px) { + body { + background-color: blue; + } +}`; + +const testValues = { + cssFullExample, + cssCodeWithCommentsRemoved +}; + +export default testValues; diff --git a/client/src/utils/__fixtures/curriculum-helpers-html.js b/client/src/utils/__fixtures/curriculum-helpers-html.js new file mode 100644 index 00000000000..0e8580fc8a5 --- /dev/null +++ b/client/src/utils/__fixtures/curriculum-helpers-html.js @@ -0,0 +1,30 @@ +const htmlFullExample = ` + + +not a comment + +not a commment not a comment +not a comment + +not a comment +`; + +const htmlCodeWithCommentsRemoved = ` + + +not a comment + +not a commment not a comment +not a comment + +not a comment +`; + +const testValues = { + htmlFullExample, + htmlCodeWithCommentsRemoved +}; + +export default testValues; diff --git a/client/src/utils/__fixtures/curriculum-helpers-javascript.js b/client/src/utils/__fixtures/curriculum-helpers-javascript.js new file mode 100644 index 00000000000..399f5d84618 --- /dev/null +++ b/client/src/utils/__fixtures/curriculum-helpers-javascript.js @@ -0,0 +1,44 @@ +const jsCodeWithSingleAndMultLineComments = ` +function nonMutatingPush(original, newItem) { + /* This is a + multi-line comment + that should be removed. */ + return original.push(newItem); +} +var first = [1, 2, 3]; +// This is a single line comment +var second = [4, 5]; +nonMutatingPush(first, second);`; + +const jsCodeWithSingleAndMultLineCommentsRemoved = ` +function nonMutatingPush(original, newItem) { + + + + return original.push(newItem); +} +var first = [1, 2, 3]; + +var second = [4, 5]; +nonMutatingPush(first, second);`; + +const jsCodeWithUrl = ` +function nonMutatingPush(original, newItem) { + var url = 'https://freecodecamp.org'; // this comment should vanish + return original.push(newItem); +}`; + +const jsCodeWithUrlUnchanged = ` +function nonMutatingPush(original, newItem) { + var url = 'https://freecodecamp.org'; + return original.push(newItem); +}`; + +const testValues = { + jsCodeWithSingleAndMultLineComments, + jsCodeWithSingleAndMultLineCommentsRemoved, + jsCodeWithUrl, + jsCodeWithUrlUnchanged +}; + +export default testValues; diff --git a/client/src/utils/__fixtures/curriculum-helpers-remove-white-space.js b/client/src/utils/__fixtures/curriculum-helpers-remove-white-space.js new file mode 100644 index 00000000000..841279da240 --- /dev/null +++ b/client/src/utils/__fixtures/curriculum-helpers-remove-white-space.js @@ -0,0 +1,16 @@ +const stringWithWhiteSpaceChars = ` +This string sentence has various white spaces characters: +\t* This line starts with a tab character. +\t* This line has several preceding white space characters.`; + +/* eslint-disable max-len */ +const stringWithWhiteSpaceCharsRemoved = + 'Thisstringsentencehasvariouswhitespacescharacters:*Thislinestartswithatabcharacter.*Thislinehasseveralprecedingwhitespacecharacters.'; +/* esline-enable max-len */ + +const testValues = { + stringWithWhiteSpaceChars, + stringWithWhiteSpaceCharsRemoved +}; + +export default testValues; diff --git a/client/src/utils/curriculum-helpers.js b/client/src/utils/curriculum-helpers.js new file mode 100644 index 00000000000..fc9699dd0ab --- /dev/null +++ b/client/src/utils/curriculum-helpers.js @@ -0,0 +1,35 @@ +import { parse } from '@babel/parser'; +import generate from '@babel/generator'; + +const removeHtmlComments = str => str.replace(//g, ''); + +const removeCssComments = str => str.replace(/\/\*[\s\S]+?\*\//g, ''); + +const removeJSComments = codeStr => { + // Note: removes trailing new lines and tailing spaces at end of lines + const options = { + comments: false, + retainLines: true, + compact: false, + concise: false, + minified: false + }; + try { + const ast = parse(codeStr); + const { code } = generate(ast, options, codeStr); + return code; + } catch (err) { + return codeStr; + } +}; + +const removeWhiteSpace = (str = '') => { + return str.replace(/\s/g, ''); +}; + +export default { + removeHtmlComments, + removeCssComments, + removeJSComments, + removeWhiteSpace +}; diff --git a/client/src/utils/curriculum-helpers.test.js b/client/src/utils/curriculum-helpers.test.js new file mode 100644 index 00000000000..b8c5388f2cc --- /dev/null +++ b/client/src/utils/curriculum-helpers.test.js @@ -0,0 +1,87 @@ +/* global describe it expect */ + +import __testHelpers from './curriculum-helpers'; +import jsTestValues from './__fixtures/curriculum-helpers-javascript'; +import cssTestValues from './__fixtures/curriculum-helpers-css'; +import htmlTestValues from './__fixtures/curriculum-helpers-html'; +/* eslint-disable max-len */ +import whiteSpaceTestValues from './__fixtures/curriculum-helpers-remove-white-space'; +/* eslint-enable max-len */ + +const { + stringWithWhiteSpaceChars, + stringWithWhiteSpaceCharsRemoved +} = whiteSpaceTestValues; + +const { cssFullExample, cssCodeWithCommentsRemoved } = cssTestValues; + +const { htmlFullExample, htmlCodeWithCommentsRemoved } = htmlTestValues; + +const { + jsCodeWithSingleAndMultLineComments, + jsCodeWithSingleAndMultLineCommentsRemoved, + jsCodeWithUrl, + jsCodeWithUrlUnchanged +} = jsTestValues; + +describe('removeWhiteSpace', () => { + const { removeWhiteSpace } = __testHelpers; + it('returns a string', () => { + expect(typeof removeWhiteSpace('This should return a string')).toBe( + 'string' + ); + }); + + it('returns a string with no white space characters', () => { + expect(removeWhiteSpace(stringWithWhiteSpaceChars)).toBe( + stringWithWhiteSpaceCharsRemoved + ); + }); +}); + +describe('removeJSComments', () => { + const { removeJSComments } = __testHelpers; + it('returns a string', () => { + expect(typeof removeJSComments('const should = "return a string"')).toBe( + 'string' + ); + }); + + it('returns a string with no single or multi-line comments', () => { + expect(removeJSComments(jsCodeWithSingleAndMultLineComments)).toBe( + jsCodeWithSingleAndMultLineCommentsRemoved + ); + }); + + it('does not remove a url found in JS code', () => { + expect(removeJSComments(jsCodeWithUrl)).toBe(jsCodeWithUrlUnchanged); + }); +}); + +describe('removeCssComments', () => { + const { removeCssComments } = __testHelpers; + it('returns a string', () => { + expect(typeof removeCssComments('.aClass: { color: red; }')).toBe('string'); + }); + + it('returns a CSS string with no single or multi-line comments', () => { + expect(removeCssComments(cssFullExample)).toBe(cssCodeWithCommentsRemoved); + }); +}); + +describe('removeHtmlComments', () => { + const { removeHtmlComments } = __testHelpers; + it('returns a string', () => { + expect( + typeof removeHtmlComments( + '
ball1 and ball2, add an ball1 should be linear.
- testString: const ball1Animation = $('#ball1').css('animation-timing-function').replace(/\s/g, '');assert(ball1Animation == 'linear' || ball1Animation == 'cubic-bezier(0,0,1,1)');
+ testString: const ball1Animation = __helpers.removeWhiteSpace($('#ball1').css('animation-timing-function'));assert(ball1Animation == 'linear' || ball1Animation == 'cubic-bezier(0,0,1,1)');
- text: The value of the animation-timing-function property for the element with the id ball2 should be ease-out.
- testString: const ball2Animation = $('#ball2').css('animation-timing-function').replace(/\s/g, ''); assert(ball2Animation == 'ease-out' || ball2Animation == 'cubic-bezier(0,0,0.58,1)');
+ testString: const ball2Animation = __helpers.removeWhiteSpace($('#ball2').css('animation-timing-function')); assert(ball2Animation == 'ease-out' || ball2Animation == 'cubic-bezier(0,0,0.58,1)');
```
diff --git a/curriculum/challenges/english/01-responsive-web-design/applied-visual-design/learn-how-bezier-curves-work.english.md b/curriculum/challenges/english/01-responsive-web-design/applied-visual-design/learn-how-bezier-curves-work.english.md
index 15e8da813fb..102eb9cd9a7 100644
--- a/curriculum/challenges/english/01-responsive-web-design/applied-visual-design/learn-how-bezier-curves-work.english.md
+++ b/curriculum/challenges/english/01-responsive-web-design/applied-visual-design/learn-how-bezier-curves-work.english.md
@@ -28,7 +28,7 @@ tests:
- text: The value of the animation-timing-function property for the element with the id ball1 should be the linear-equivalent cubic-bezier function.
testString: assert($('#ball1').css('animation-timing-function') == 'cubic-bezier(0.25, 0.25, 0.75, 0.75)');
- text: The value of the animation-timing-function property for the element with the id ball2 should not change.
- testString: const ball2Animation = $('#ball2').css('animation-timing-function').replace(/\s/g, ''); assert(ball2Animation == 'ease-out' || ball2Animation == 'cubic-bezier(0,0,0.58,1)');
+ testString: const ball2Animation = __helpers.removeWhiteSpace($('#ball2').css('animation-timing-function')); assert(ball2Animation == 'ease-out' || ball2Animation == 'cubic-bezier(0,0,0.58,1)');
```
diff --git a/curriculum/challenges/english/01-responsive-web-design/applied-visual-design/use-a-bezier-curve-to-move-a-graphic.english.md b/curriculum/challenges/english/01-responsive-web-design/applied-visual-design/use-a-bezier-curve-to-move-a-graphic.english.md
index 9f4460f38f1..ff0d021dafb 100644
--- a/curriculum/challenges/english/01-responsive-web-design/applied-visual-design/use-a-bezier-curve-to-move-a-graphic.english.md
+++ b/curriculum/challenges/english/01-responsive-web-design/applied-visual-design/use-a-bezier-curve-to-move-a-graphic.english.md
@@ -29,7 +29,7 @@ tests:
- text: The element with the id red should no longer have the animation-timing-function property of linear.
testString: assert($('#red').css('animation-timing-function') !== 'linear');
- text: The value of the animation-timing-function property for the element with the id blue should not change.
- testString: const blueBallAnimation = $('#blue').css('animation-timing-function').replace(/\s/g, ''); assert(blueBallAnimation == 'ease-out' || blueBallAnimation == 'cubic-bezier(0,0,0.58,1)');
+ testString: const blueBallAnimation = __helpers.removeWhiteSpace($('#blue').css('animation-timing-function')); assert(blueBallAnimation == 'ease-out' || blueBallAnimation == 'cubic-bezier(0,0,0.58,1)');
```
diff --git a/curriculum/challenges/english/01-responsive-web-design/basic-css/use-clockwise-notation-to-specify-the-margin-of-an-element.english.md b/curriculum/challenges/english/01-responsive-web-design/basic-css/use-clockwise-notation-to-specify-the-margin-of-an-element.english.md
index 565ec4e8337..3ffe0969442 100644
--- a/curriculum/challenges/english/01-responsive-web-design/basic-css/use-clockwise-notation-to-specify-the-margin-of-an-element.english.md
+++ b/curriculum/challenges/english/01-responsive-web-design/basic-css/use-clockwise-notation-to-specify-the-margin-of-an-element.english.md
@@ -33,7 +33,7 @@ tests:
- text: Your blue-box class should give the left of elements 40px of margin.
testString: assert($(".blue-box").css("margin-left") === "40px");
- text: You should use the clockwise notation to set the margin of blue-box class.
- testString: const removeCssComments = str => str.replace(/\/\*[\s\S]+?\*\//g, '');assert(/\.blue-box\s*{[\s\S]*margin[\s]*:\s*\d+px\s+\d+px\s+\d+px\s+\d+px(;\s*[^}]+\s*}|;?\s*})/.test(removeCssComments($('style').text())));
+ testString: assert(/\.blue-box\s*{[\s\S]*margin[\s]*:\s*\d+px\s+\d+px\s+\d+px\s+\d+px(;\s*[^}]+\s*}|;?\s*})/.test(__helpers.removeCssComments($('style').text())));
```
diff --git a/curriculum/challenges/english/01-responsive-web-design/basic-css/use-clockwise-notation-to-specify-the-padding-of-an-element.english.md b/curriculum/challenges/english/01-responsive-web-design/basic-css/use-clockwise-notation-to-specify-the-padding-of-an-element.english.md
index 4a653fbc19a..80ad2f5acf5 100644
--- a/curriculum/challenges/english/01-responsive-web-design/basic-css/use-clockwise-notation-to-specify-the-padding-of-an-element.english.md
+++ b/curriculum/challenges/english/01-responsive-web-design/basic-css/use-clockwise-notation-to-specify-the-padding-of-an-element.english.md
@@ -32,7 +32,7 @@ tests:
- text: Your blue-box class should give the left of elements 40px of padding.
testString: assert($(".blue-box").css("padding-left") === "40px");
- text: You should use the clockwise notation to set the padding of blue-box class.
- testString: const removeCssComments = str => str.replace(/\/\*[\s\S]+?\*\//g, '');assert(/\.blue-box\s*{[\s\S]*padding[\s]*:\s*\d+px\s+\d+px\s+\d+px\s+\d+px(;\s*[^}]+\s*}|;?\s*})/.test(removeCssComments($('style').text())));
+ testString: assert(/\.blue-box\s*{[\s\S]*padding[\s]*:\s*\d+px\s+\d+px\s+\d+px\s+\d+px(;\s*[^}]+\s*}|;?\s*})/.test(__helpers.removeCssComments($('style').text())));
```
diff --git a/curriculum/challenges/english/01-responsive-web-design/basic-html-and-html5/add-images-to-your-website.english.md b/curriculum/challenges/english/01-responsive-web-design/basic-html-and-html5/add-images-to-your-website.english.md
index 4f82075ac9d..b0c03dcd3c2 100644
--- a/curriculum/challenges/english/01-responsive-web-design/basic-html-and-html5/add-images-to-your-website.english.md
+++ b/curriculum/challenges/english/01-responsive-web-design/basic-html-and-html5/add-images-to-your-website.english.md
@@ -38,7 +38,7 @@ tests:
- text: Your image should have a src attribute that points to the kitten image.
testString: assert(/^https:\/\/bit\.ly\/fcc-relaxing-cat$/i.test($("img").attr("src")));
- text: Your image element's alt attribute should not be empty.
- testString: assert($("img").attr("alt") && $("img").attr("alt").length && /li element should have a closing tag.
testString: assert(code.match(/<\/li>/g) && code.match(/li elements in your unordered list should not be empty.
- testString: $('ul li').each((i, val) => assert(val.textContent.replace(/\s/g, '')));
+ testString: $('ul li').each((i, val) => assert(__helpers.removeWhiteSpace(val.textContent)));
- text: The li elements in your ordered list should not be empty.
- testString: $('ol li').each((i, val) => assert(!!val.textContent.replace(/\s/g, '')));
+ testString: $('ol li').each((i, val) => assert(!!__helpers.removeWhiteSpace(val.textContent)));
```
diff --git a/curriculum/challenges/english/01-responsive-web-design/css-grid/divide-the-grid-into-an-area-template.english.md b/curriculum/challenges/english/01-responsive-web-design/css-grid/divide-the-grid-into-an-area-template.english.md
index 1a91452b872..f8a71277133 100644
--- a/curriculum/challenges/english/01-responsive-web-design/css-grid/divide-the-grid-into-an-area-template.english.md
+++ b/curriculum/challenges/english/01-responsive-web-design/css-grid/divide-the-grid-into-an-area-template.english.md
@@ -33,7 +33,7 @@ Place the area template so that the cell labeled advert becomes an
```yml
tests:
- text: container class should have a grid-template-areas property similar to the preview but has . instead of the advert area.
- testString: const removeCssComments = str => str.replace(/\/\*[\s\S]+?\*\//g, ''); assert(removeCssComments(code).match(/.container\s*?{[\s\S]*grid-template-areas\s*?:\s*?"\s*?header\s*?header\s*?header\s*?"\s*?"\s*?.\s*?content\s*?content\s*?"\s*?"\s*?footer\s*?footer\s*?footer\s*?"\s*?;[\s\S]*}/gi));
+ testString: assert(__helpers.removeCssComments(code).match(/.container\s*?{[\s\S]*grid-template-areas\s*?:\s*?"\s*?header\s*?header\s*?header\s*?"\s*?"\s*?.\s*?content\s*?content\s*?"\s*?"\s*?footer\s*?footer\s*?footer\s*?"\s*?;[\s\S]*}/gi));
```
diff --git a/curriculum/challenges/english/01-responsive-web-design/css-grid/place-items-in-grid-areas-using-the-grid-area-property.english.md b/curriculum/challenges/english/01-responsive-web-design/css-grid/place-items-in-grid-areas-using-the-grid-area-property.english.md
index d1b2b633342..60be806fca3 100644
--- a/curriculum/challenges/english/01-responsive-web-design/css-grid/place-items-in-grid-areas-using-the-grid-area-property.english.md
+++ b/curriculum/challenges/english/01-responsive-web-design/css-grid/place-items-in-grid-areas-using-the-grid-area-property.english.md
@@ -30,7 +30,7 @@ Place an element with the item5 class in the footer ar
```yml
tests:
- text: item5 class should have a grid-area property that has the value of footer.
- testString: const removeCssComments = str => str.replace(/\/\*[\s\S]+?\*\//g, '');assert(removeCssComments(code).match(/.item5\s*?{[\s\S]*grid-area\s*?:\s*?footer\s*?;[\s\S]*}/gi));
+ testString: assert(__helpers.removeCssComments(code).match(/.item5\s*?{[\s\S]*grid-area\s*?:\s*?footer\s*?;[\s\S]*}/gi));
```
diff --git a/curriculum/challenges/english/01-responsive-web-design/css-grid/use-grid-column-to-control-spacing.english.md b/curriculum/challenges/english/01-responsive-web-design/css-grid/use-grid-column-to-control-spacing.english.md
index 203a4852bea..865ec3d7073 100644
--- a/curriculum/challenges/english/01-responsive-web-design/css-grid/use-grid-column-to-control-spacing.english.md
+++ b/curriculum/challenges/english/01-responsive-web-design/css-grid/use-grid-column-to-control-spacing.english.md
@@ -33,7 +33,7 @@ Make the item with the class item5 consume the last two columns of
```yml
tests:
- text: item5 class should have a grid-column property.
- testString: assert($('style').text().replace(/\s/g, '').match(/\.item5{.*grid-column:.*}/g));
+ testString: assert(__helpers.removeWhiteSpace($('style').text()).match(/\.item5{.*grid-column:.*}/g));
- text: item5 class should have a grid-column property which results in it consuming the last two columns of the grid.
testString: "
const colStart = getComputedStyle($('.item5')[0]).gridColumnStart;
diff --git a/curriculum/challenges/english/01-responsive-web-design/css-grid/use-grid-row-to-control-spacing.english.md b/curriculum/challenges/english/01-responsive-web-design/css-grid/use-grid-row-to-control-spacing.english.md
index d497eed5714..b32308830e5 100644
--- a/curriculum/challenges/english/01-responsive-web-design/css-grid/use-grid-row-to-control-spacing.english.md
+++ b/curriculum/challenges/english/01-responsive-web-design/css-grid/use-grid-row-to-control-spacing.english.md
@@ -22,7 +22,7 @@ Make the element with the item5 class consume the last two rows.
```yml
tests:
- text: item5 class should have a grid-row property.
- testString: assert($('style').text().replace(/\s/g, '').match(/\.item5{.*grid-row:.*}/g));
+ testString: assert(__helpers.removeWhiteSpace($('style').text()).match(/\.item5{.*grid-row:.*}/g));
- text: item5 class should have a grid-row property which results in it consuming the last two rows of the grid.
testString: "
const rowStart = getComputedStyle($('.item5')[0]).gridRowStart;
diff --git a/curriculum/challenges/english/01-responsive-web-design/css-grid/use-media-queries-to-create-responsive-layouts.english.md b/curriculum/challenges/english/01-responsive-web-design/css-grid/use-media-queries-to-create-responsive-layouts.english.md
index 5fdc5102cba..1f682b71790 100644
--- a/curriculum/challenges/english/01-responsive-web-design/css-grid/use-media-queries-to-create-responsive-layouts.english.md
+++ b/curriculum/challenges/english/01-responsive-web-design/css-grid/use-media-queries-to-create-responsive-layouts.english.md
@@ -23,7 +23,7 @@ When the viewport width is 400px or more, make the header area occu
```yml
tests:
- text: When the viewport is 400px or more, container class should have a grid-template-areas property in which the header and footer areas occupy the top and bottom rows respectively and advert and content occupy the left and right columns of the middle row.
- testString: const removeCssComments = str => str.replace(/\/\*[\s\S]+?\*\//g, ''); assert(removeCssComments(code).match(/@media\s*?\(\s*?min-width\s*?:\s*?400px\s*?\)[\s\S]*.container\s*?{[\s\S]*grid-template-areas\s*?:\s*?"\s*?header\s*?header\s*?"\s*?"\s*?advert\s*?content\s*?"\s*?"\s*?footer\s*?footer\s*?"\s*?;[\s\S]*}/gi));
+ testString: assert(__helpers.removeCssComments(code).match(/@media\s*?\(\s*?min-width\s*?:\s*?400px\s*?\)[\s\S]*.container\s*?{[\s\S]*grid-template-areas\s*?:\s*?"\s*?header\s*?header\s*?"\s*?"\s*?advert\s*?content\s*?"\s*?"\s*?footer\s*?footer\s*?"\s*?;[\s\S]*}/gi));
```
diff --git a/curriculum/challenges/english/02-javascript-algorithms-and-data-structures/basic-data-structures/copy-an-array-with-the-spread-operator.english.md b/curriculum/challenges/english/02-javascript-algorithms-and-data-structures/basic-data-structures/copy-an-array-with-the-spread-operator.english.md
index c4351d13186..ff6bb4149ed 100644
--- a/curriculum/challenges/english/02-javascript-algorithms-and-data-structures/basic-data-structures/copy-an-array-with-the-spread-operator.english.md
+++ b/curriculum/challenges/english/02-javascript-algorithms-and-data-structures/basic-data-structures/copy-an-array-with-the-spread-operator.english.md
@@ -38,7 +38,7 @@ tests:
- text: copyMachine(["it works"], 3) should return [["it works"], ["it works"], ["it works"]]
testString: assert.deepEqual(copyMachine(['it works'], 3), [['it works'], ['it works'], ['it works']]);
- text: The copyMachine function should utilize the spread operator with array arr
- testString: assert(removeJSComments(code).match(/\.\.\.arr/));
+ testString: assert(__helpers.removeJSComments(code).match(/\.\.\.arr/));
```
@@ -66,15 +66,6 @@ console.log(copyMachine([true, false, true], 2));
-### After Test
-const arr = [2, 4, 5, 1, 7, 5, 2, 1];.
- testString: assert(code.replace(/\s/g, '').match(/constarr=\[2,4,5,1,7,5,2,1\];?/));
+ testString: assert(__helpers.removeWhiteSpace(code).match(/constarr=\[2,4,5,1,7,5,2,1\];?/));
- text: arr should only contain elements that sum to 10.
testString: assert.strictEqual(arr.reduce((a, b) => a + b), 10);
- text: Your code should utilize the splice() method on arr.
- testString: assert(code.replace(/\s/g, '').match(/arr\.splice\(/));
+ testString: assert(__helpers.removeWhiteSpace(code).match(/arr\.splice\(/));
- text: The splice should only remove elements from arr and not add any additional elements to arr.
- testString: assert(!code.replace(/\s/g, '').match(/arr\.splice\(\d+,\d+,\d+.*\)/g));
+ testString: assert(!__helpers.removeWhiteSpace(code).match(/arr\.splice\(\d+,\d+,\d+.*\)/g));
```
diff --git a/curriculum/challenges/english/02-javascript-algorithms-and-data-structures/basic-javascript/access-multi-dimensional-arrays-with-indexes.english.md b/curriculum/challenges/english/02-javascript-algorithms-and-data-structures/basic-javascript/access-multi-dimensional-arrays-with-indexes.english.md
index be1b328d764..cbd2f8d3b9a 100644
--- a/curriculum/challenges/english/02-javascript-algorithms-and-data-structures/basic-javascript/access-multi-dimensional-arrays-with-indexes.english.md
+++ b/curriculum/challenges/english/02-javascript-algorithms-and-data-structures/basic-javascript/access-multi-dimensional-arrays-with-indexes.english.md
@@ -39,7 +39,7 @@ tests:
- text: myData should be equal to 8.
testString: assert(myData === 8);
- text: You should be using bracket notation to read the correct value from myArray.
- testString: assert(/myData=myArray\[2\]\[1\]/.test(code.replace(/\s/g, '')));
+ testString: assert(/myData=myArray\[2\]\[1\]/.test(__helpers.removeWhiteSpace(code)));
```
diff --git a/curriculum/challenges/english/02-javascript-algorithms-and-data-structures/basic-javascript/iterate-through-an-array-with-a-for-loop.english.md b/curriculum/challenges/english/02-javascript-algorithms-and-data-structures/basic-javascript/iterate-through-an-array-with-a-for-loop.english.md
index 179f5f6addc..acd6d4e5b06 100644
--- a/curriculum/challenges/english/02-javascript-algorithms-and-data-structures/basic-javascript/iterate-through-an-array-with-a-for-loop.english.md
+++ b/curriculum/challenges/english/02-javascript-algorithms-and-data-structures/basic-javascript/iterate-through-an-array-with-a-for-loop.english.md
@@ -37,7 +37,7 @@ tests:
- text: You should use a for loop to iterate through myArr.
testString: assert(/for\s*\(/g.test(code) && /myArr\s*\[/g.test(code));
- text: You should not attempt to directly assign the value 20 to total.
- testString: assert(!code.replace(/\s/g, '').match(/total[=+-]0*[1-9]+/gm));
+ testString: assert(!__helpers.removeWhiteSpace(code).match(/total[=+-]0*[1-9]+/gm));
```
diff --git a/curriculum/challenges/english/02-javascript-algorithms-and-data-structures/basic-javascript/local-scope-and-functions.english.md b/curriculum/challenges/english/02-javascript-algorithms-and-data-structures/basic-javascript/local-scope-and-functions.english.md
index 0f223c304e5..9c858a5c4b6 100644
--- a/curriculum/challenges/english/02-javascript-algorithms-and-data-structures/basic-javascript/local-scope-and-functions.english.md
+++ b/curriculum/challenges/english/02-javascript-algorithms-and-data-structures/basic-javascript/local-scope-and-functions.english.md
@@ -43,7 +43,7 @@ tests:
}
assert.throws(declared, ReferenceError);
- text: You should add a local myVar variable.
- testString: assert(/functionmyLocalScope\(\)\{.+(var|let|const)myVar[\s\S]*}/.test(code.replace(/\s/g, '')));
+ testString: assert(/functionmyLocalScope\(\)\{.+(var|let|const)myVar[\s\S]*}/.test(__helpers.removeWhiteSpace(code)));
```
diff --git a/curriculum/challenges/english/02-javascript-algorithms-and-data-structures/basic-javascript/replace-loops-using-recursion.english.md b/curriculum/challenges/english/02-javascript-algorithms-and-data-structures/basic-javascript/replace-loops-using-recursion.english.md
index 0cf58778f40..ac1953f4a14 100644
--- a/curriculum/challenges/english/02-javascript-algorithms-and-data-structures/basic-javascript/replace-loops-using-recursion.english.md
+++ b/curriculum/challenges/english/02-javascript-algorithms-and-data-structures/basic-javascript/replace-loops-using-recursion.english.md
@@ -57,9 +57,9 @@ tests:
- text: sum([2, 3, 4, 5], 3) should equal 9.
testString: assert.equal(sum([2, 3, 4, 5], 3), 9);
- text: Your code should not rely on any kind of loops (for or while or higher order functions such as forEach, map, filter, or reduce.).
- testString: assert(!removeJSComments(code).match(/for|while|forEach|map|filter|reduce/g));
+ testString: assert(!__helpers.removeJSComments(code).match(/for|while|forEach|map|filter|reduce/g));
- text: You should use recursion to solve this problem.
- testString: assert(removeJSComments(sum.toString()).match(/sum\(.*\)/g).length > 1);
+ testString: assert(__helpers.removeJSComments(sum.toString()).match(/sum\(.*\)/g).length > 1);
```
@@ -80,16 +80,6 @@ function sum(arr, n) {
-### After Test
-difference should be equal to 12.
testString: assert(difference === 12);
- text: You should only subtract one number from 45.
- testString: assert(/difference=45-33;?/.test(code.replace(/\s/g, '')));
+ testString: assert(/difference=45-33;?/.test(__helpers.removeWhiteSpace(code)));
```
diff --git a/curriculum/challenges/english/02-javascript-algorithms-and-data-structures/basic-javascript/understanding-undefined-value-returned-from-a-function.english.md b/curriculum/challenges/english/02-javascript-algorithms-and-data-structures/basic-javascript/understanding-undefined-value-returned-from-a-function.english.md
index da06cb5d05e..430712ec916 100644
--- a/curriculum/challenges/english/02-javascript-algorithms-and-data-structures/basic-javascript/understanding-undefined-value-returned-from-a-function.english.md
+++ b/curriculum/challenges/english/02-javascript-algorithms-and-data-structures/basic-javascript/understanding-undefined-value-returned-from-a-function.english.md
@@ -39,7 +39,7 @@ tests:
- text: Returned value from addFive should be undefined.
testString: assert(addFive() === undefined);
- text: Inside the addFive function, you should add 5 to the sum variable.
- testString: assert(addFive.toString().replace(/\s/g, '').match(/sum=sum\+5|sum\+=5/));
+ testString: assert(__helpers.removeWhiteSpace(addFive.toString()).match(/sum=sum\+5|sum\+=5/));
```
diff --git a/curriculum/challenges/english/02-javascript-algorithms-and-data-structures/basic-javascript/use-recursion-to-create-a-countdown.english.md b/curriculum/challenges/english/02-javascript-algorithms-and-data-structures/basic-javascript/use-recursion-to-create-a-countdown.english.md
index 067beecc4f2..f043708d277 100644
--- a/curriculum/challenges/english/02-javascript-algorithms-and-data-structures/basic-javascript/use-recursion-to-create-a-countdown.english.md
+++ b/curriculum/challenges/english/02-javascript-algorithms-and-data-structures/basic-javascript/use-recursion-to-create-a-countdown.english.md
@@ -52,9 +52,9 @@ tests:
- text: countdown(5) should return [5, 4, 3, 2, 1]
testString: assert.deepStrictEqual(countdown(5), [5, 4, 3, 2, 1]);
- text: Your code should not rely on any kind of loops (for, while or higher order functions such as forEach, map, filter, and reduce).
- testString: assert(!removeJSComments(code).match(/for|while|forEach|map|filter|reduce/g));
+ testString: assert(!__helpers.removeJSComments(code).match(/for|while|forEach|map|filter|reduce/g));
- text: You should use recursion to solve this problem.
- testString: assert(removeJSComments(countdown.toString()).match(/countdown\s*\(.+\)/));
+ testString: assert(__helpers.removeJSComments(countdown.toString()).match(/countdown\s*\(.+\)/));
```
@@ -76,15 +76,6 @@ function countdown(n){
-### After Test
-for or while or higher order functions such as forEach, map, filter, or reduce).
- testString: assert(!removeJSComments(code).match(/for|while|forEach|map|filter|reduce/g));
+ testString: assert(!__helpers.removeJSComments(code).match(/for|while|forEach|map|filter|reduce/g));
- text: rangeOfNumbers should use recursion (call itself) to solve this challenge.
- testString: assert(removeJSComments(rangeOfNumbers.toString()).match(/rangeOfNumbers\s*\(.+\)/));
+ testString: assert(__helpers.removeJSComments(rangeOfNumbers.toString()).match(/rangeOfNumbers\s*\(.+\)/));
- text: rangeOfNumbers(1, 5) should return [1, 2, 3, 4, 5].
testString: assert.deepStrictEqual(rangeOfNumbers(1, 5), [1, 2, 3, 4, 5]);
- text: rangeOfNumbers(6, 9) should return [6, 7, 8, 9].
@@ -51,15 +51,6 @@ function rangeOfNumbers(startNum, endNum) {
-### After Test
-console.log to log the output variable. The
```yml
tests:
- text: You should use console.clear() to clear the browser console.
- testString: const removeJSComments = code.replace(/\/\*[\s\S]*?\*\/|\/\/.*$/gm, ''); const noSpaces = removeJSComments.replace(/\s/g, ''); assert(noSpaces.match(/console.clear\(\)/));
+ testString: assert(__helpers.removeWhiteSpace(__helpers.removeJSComments(code)).match(/console.clear\(\)/));
- text: You should use console.log() to print the output variable.
- testString: const noSpaces = code.replace(/\s/g, ''); assert(noSpaces.match(/console\.log\(output\)/));
+ testString: assert(__helpers.removeWhiteSpace(code).match(/console\.log\(output\)/));
```
@@ -53,8 +53,6 @@ let output = "Get this to log once in the freeCodeCamp console and twice in the
-
-
## Solution
diff --git a/curriculum/challenges/english/02-javascript-algorithms-and-data-structures/es6/complete-a-promise-with-resolve-and-reject.english.md b/curriculum/challenges/english/02-javascript-algorithms-and-data-structures/es6/complete-a-promise-with-resolve-and-reject.english.md
index 718d69f00f1..b67a808b1e0 100644
--- a/curriculum/challenges/english/02-javascript-algorithms-and-data-structures/es6/complete-a-promise-with-resolve-and-reject.english.md
+++ b/curriculum/challenges/english/02-javascript-algorithms-and-data-structures/es6/complete-a-promise-with-resolve-and-reject.english.md
@@ -33,9 +33,10 @@ Make the promise handle success and failure. If responseFromServer
```yml
tests:
- text: resolve should be called with the expected string when the if condition is true.
- testString: assert(removeJSComments(code).match(/if\s*\(\s*responseFromServer\s*\)\s*{\s*resolve\s*\(\s*('|"|`)We got the data\1\s*\)(\s*|\s*;\s*)}/g));
+ testString: assert(__helpers.removeJSComments(code).match(/if\s*\(\s*responseFromServer\s*\)\s*{\s*resolve\s*\(\s*('|"|`)We got the data\1\s*\)(\s*|\s*;\s*)}/g));
- text: reject should be called with the expected string when the if condition is false.
- testString: assert(removeJSComments(code).match(/}\s*else\s*{\s*reject\s*\(\s*('|"|`)Data not received\1\s*\)(\s*|\s*;\s*)}/g));
+ testString: assert(__helpers.removeJSComments(code).match(/}\s*else\s*{\s*reject\s*\(\s*('|"|`)Data not received\1\s*\)(\s*|\s*;\s*)}/g));
+
```
@@ -59,14 +60,6 @@ const makeServerRequest = new Promise((resolve, reject) => {
-### After Test
-then method to your promise. Use result as the
```yml
tests:
- text: You should call the then method on the promise.
- testString: assert(codeWithoutSpaces.match(/(makeServerRequest|\))\.then\(/g));
+ testString: assert(__helpers.removeWhiteSpace(code).match(/(makeServerRequest|\))\.then\(/g));
- text: Your then method should have a callback function with result as its parameter.
testString: assert(resultIsParameter);
- text: You should log result to the console.
- testString: assert(resultIsParameter && codeWithoutSpaces.match(/\.then\(.*?result.*?console.log\(result\).*?\)/));
+ testString: assert(resultIsParameter && __helpers.removeWhiteSpace(code).match(/\.then\(.*?result.*?console.log\(result\).*?\)/));
```
@@ -61,8 +61,7 @@ const makeServerRequest = new Promise((resolve, reject) => {
catch method to your promise. Use error as the
```yml
tests:
- text: You should call the catch method on the promise.
- testString: assert(codeWithoutSpaces.match(/(makeServerRequest|\))\.catch\(/g));
+ testString: assert(__helpers.removeWhiteSpace(code).match(/(makeServerRequest|\))\.catch\(/g));
- text: Your catch method should have a callback function with error as its parameter.
testString: assert(errorIsParameter);
- text: You should log error to the console.
- testString: assert(errorIsParameter && codeWithoutSpaces.match(/\.catch\(.*?error.*?console.log\(error\).*?\)/));
+ testString: assert(errorIsParameter && __helpers.removeWhiteSpace(code).match(/\.catch\(.*?error.*?console.log\(error\).*?\)/));
```
@@ -67,8 +67,7 @@ makeServerRequest.then(result => {
today variable.
- testString: assert(removeJSComments(code).match(/(var|let|const)\s*{\s*(today[^}]*|[^,]*,\s*today)\s*}\s*=\s*HIGH_TEMPERATURES(;|\s+|\/\/)/g));
+ testString: assert(__helpers.removeJSComments(code).match(/(var|let|const)\s*{\s*(today[^}]*|[^,]*,\s*today)\s*}\s*=\s*HIGH_TEMPERATURES(;|\s+|\/\/)/g));
- text: You should use destructuring to create the tomorrow variable.
- testString: assert(removeJSComments(code).match(/(var|let|const)\s*{\s*(tomorrow[^}]*|[^,]*,\s*tomorrow)\s*}\s*=\s*HIGH_TEMPERATURES(;|\s+|\/\/)/g));
+ testString: assert(__helpers.removeJSComments(code).match(/(var|let|const)\s*{\s*(tomorrow[^}]*|[^,]*,\s*tomorrow)\s*}\s*=\s*HIGH_TEMPERATURES(;|\s+|\/\/)/g));
- text: today should be equal to 77 and tomorrow should be equal to 80.
testString: assert(today === 77 && tomorrow === 80);
@@ -74,15 +74,6 @@ const tomorrow = HIGH_TEMPERATURES.tomorrow;
-### After Test
-half(stats) should be 28.015
testString: assert(half(stats) === 28.015);
- text: Destructuring should be used.
- testString: assert(code.replace(/\s/g, '').match(/half=\({\w+,\w+}\)/));
+ testString: assert(__helpers.removeWhiteSpace(code).match(/half=\({\w+,\w+}\)/));
- text: Destructured parameter should be used.
testString: assert(!code.match(/stats\.max|stats\.min/));
diff --git a/curriculum/challenges/english/02-javascript-algorithms-and-data-structures/es6/use-destructuring-assignment-with-the-rest-parameter-to-reassign-array-elements.english.md b/curriculum/challenges/english/02-javascript-algorithms-and-data-structures/es6/use-destructuring-assignment-with-the-rest-parameter-to-reassign-array-elements.english.md
index 68e6b3335ae..6d891165e63 100644
--- a/curriculum/challenges/english/02-javascript-algorithms-and-data-structures/es6/use-destructuring-assignment-with-the-rest-parameter-to-reassign-array-elements.english.md
+++ b/curriculum/challenges/english/02-javascript-algorithms-and-data-structures/es6/use-destructuring-assignment-with-the-rest-parameter-to-reassign-array-elements.english.md
@@ -37,7 +37,7 @@ tests:
- text: Array.slice() should not be used.
testString: getUserInput => assert(!getUserInput('index').match(/slice/g));
- text: Destructuring on list should be used.
- testString: assert(code.replace(/\s/g, '').match(/\[(([_$a-z]\w*)?,){1,}\.\.\.arr\]=list/i));
+ testString: assert(__helpers.removeWhiteSpace(code).match(/\[(([_$a-z]\w*)?,){1,}\.\.\.arr\]=list/i));
```
diff --git a/curriculum/challenges/english/02-javascript-algorithms-and-data-structures/es6/use-the-rest-parameter-with-function-parameters.english.md b/curriculum/challenges/english/02-javascript-algorithms-and-data-structures/es6/use-the-rest-parameter-with-function-parameters.english.md
index 5d003a699a6..1dc09b8e9cb 100644
--- a/curriculum/challenges/english/02-javascript-algorithms-and-data-structures/es6/use-the-rest-parameter-with-function-parameters.english.md
+++ b/curriculum/challenges/english/02-javascript-algorithms-and-data-structures/es6/use-the-rest-parameter-with-function-parameters.english.md
@@ -40,7 +40,7 @@ tests:
- text: The result of sum() should be 0
testString: assert(sum() === 0);
- text: The sum function should use the ... rest parameter on the args parameter.
- testString: assert(code.replace(/\s/g,'').match(/sum=\(\.\.\.args\)=>/));
+ testString: assert(__helpers.removeWhiteSpace(code).match(/sum=\(\.\.\.args\)=>/));
```
diff --git a/curriculum/challenges/english/02-javascript-algorithms-and-data-structures/es6/write-concise-declarative-functions-with-es6.english.md b/curriculum/challenges/english/02-javascript-algorithms-and-data-structures/es6/write-concise-declarative-functions-with-es6.english.md
index ea739090479..d7bda4a98d8 100644
--- a/curriculum/challenges/english/02-javascript-algorithms-and-data-structures/es6/write-concise-declarative-functions-with-es6.english.md
+++ b/curriculum/challenges/english/02-javascript-algorithms-and-data-structures/es6/write-concise-declarative-functions-with-es6.english.md
@@ -42,7 +42,7 @@ Refactor the function setGear inside the object bicycle assert(!removeJSComments(code).match(/function/));
+ testString: getUserInput => assert(!__helpers.removeJSComments(code).match(/function/));
- text: setGear should be a declarative function.
testString: assert(typeof bicycle.setGear === 'function' && code.match(/setGear\s*\(.+\)\s*\{/));
- text: bicycle.setGear(48) should change the gear value to 48.
@@ -72,15 +72,6 @@ console.log(bicycle.gear);
-### After Test
-squareList should be a function.
testString: assert.typeOf(squareList, 'function'), 'squareList should be a function';
- text: for or while loops or forEach should not be used.
- testString: assert(!removeJSComments(code).match(/for|while|forEach/g));
+ testString: assert(!__helpers.removeJSComments(code).match(/for|while|forEach/g));
- text: map, filter, or reduce should be used.
- testString: assert(removeJSComments(code).match(/\.(map|filter|reduce)\s*\(/g));
+ testString: assert(__helpers.removeJSComments(code).match(/\.(map|filter|reduce)\s*\(/g));
- text: The function should return an array.
testString: assert(Array.isArray(squareList([4, 5.6, -9.8, 3.14, 42, 6, 8.34, -2])));
- text: squareList([4, 5.6, -9.8, 3.14, 42, 6, 8.34, -2]) should return [16, 1764, 36].
@@ -55,14 +55,6 @@ console.log(squaredIntegers);
-watchList variable should not change.
testString: assert(watchList[0].Title === "Inception" && watchList[4].Director == "James Cameron");
- text: Your code should not use a for loop.
- testString: assert(!removeJSComments(code).match(/for\s*?\([\s\S]*?\)/));
+ testString: assert(!__helpers.removeJSComments(code).match(/for\s*?\([\s\S]*?\)/));
- text: Your code should use the map method.
testString: assert(code.match(/\.map/g));
- text: ratings should equal [{"title":"Inception","rating":"8.8"},{"title":"Interstellar","rating":"8.6"},{"title":"The Dark Knight","rating":"9.0"},{"title":"Batman Begins","rating":"8.3"},{"title":"Avatar","rating":"7.9"}].
@@ -185,15 +185,6 @@ console.log(JSON.stringify(ratings));
-### After Test
-DisplayMessages component should render an empty div element.
testString: assert((function() { const mockedComponent = Enzyme.mount(React.createElement(DisplayMessages)); return mockedComponent.find('div').text() === '' })());
- text: The DisplayMessages constructor should be called properly with super, passing in props.
- testString: getUserInput => assert((function() { const noWhiteSpace = getUserInput('index').replace(/\s/g,''); return noWhiteSpace.includes('constructor(props)') && noWhiteSpace.includes('super(props'); })());
+ testString: getUserInput => assert((function() { const noWhiteSpace = __helpers.removeWhiteSpace(getUserInput('index')); return noWhiteSpace.includes('constructor(props)') && noWhiteSpace.includes('super(props'); })());
- text: 'The DisplayMessages component should have an initial state equal to {input: "", messages: []}.'
testString: "assert((function() { const mockedComponent = Enzyme.mount(React.createElement(DisplayMessages)); const initialState = mockedComponent.state(); return typeof initialState === 'object' && initialState.input === '' && Array.isArray(initialState.messages) && initialState.messages.length === 0; })());"
diff --git a/curriculum/challenges/english/03-front-end-libraries/react-and-redux/use-provider-to-connect-redux-to-react.english.md b/curriculum/challenges/english/03-front-end-libraries/react-and-redux/use-provider-to-connect-redux-to-react.english.md
index 4a4590ff21b..0ce519d4a12 100644
--- a/curriculum/challenges/english/03-front-end-libraries/react-and-redux/use-provider-to-connect-redux-to-react.english.md
+++ b/curriculum/challenges/english/03-front-end-libraries/react-and-redux/use-provider-to-connect-redux-to-react.english.md
@@ -33,7 +33,7 @@ tests:
- text: The AppWrapper should render.
testString: assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); return mockedComponent.find('AppWrapper').length === 1; })());
- text: The Provider wrapper component should have a prop of store passed to it, equal to the Redux store.
- testString: getUserInput => assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); return getUserInput('index').replace(/\s/g,'').includes('DisplayMessages should render as a child of AppWrapper.
testString: assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); return mockedComponent.find('AppWrapper').find('DisplayMessages').length === 1; })());
- text: The DisplayMessages component should render an h2, input, button, and ul element.
diff --git a/curriculum/challenges/english/03-front-end-libraries/react/pass-props-to-a-stateless-functional-component.english.md b/curriculum/challenges/english/03-front-end-libraries/react/pass-props-to-a-stateless-functional-component.english.md
index 230e78c4574..9d3537e7e8f 100644
--- a/curriculum/challenges/english/03-front-end-libraries/react/pass-props-to-a-stateless-functional-component.english.md
+++ b/curriculum/challenges/english/03-front-end-libraries/react/pass-props-to-a-stateless-functional-component.english.md
@@ -44,7 +44,7 @@ tests:
- text: The date prop of the CurrentDate should contain a string of text.
testString: assert((function() { const mockedComponent = Enzyme.mount(React.createElement(Calendar)); const prop = mockedComponent.children().childAt(1).props().date; return( typeof prop === 'string' && prop.length > 0 ); })());
- text: The date prop should be generated by calling Date()
- testString: assert(/CurrentDate component should render the value from the date prop in the p tag.
testString: let date = "dummy date"; assert((function() { const mockedComponent = Enzyme.mount(React.createElement(CurrentDate, {date})); return mockedComponent.find('p').html().includes(date); })());
diff --git a/curriculum/challenges/english/03-front-end-libraries/react/review-using-props-with-stateless-functional-components.english.md b/curriculum/challenges/english/03-front-end-libraries/react/review-using-props-with-stateless-functional-components.english.md
index b92f45d6af9..b9eded62bfd 100644
--- a/curriculum/challenges/english/03-front-end-libraries/react/review-using-props-with-stateless-functional-components.english.md
+++ b/curriculum/challenges/english/03-front-end-libraries/react/review-using-props-with-stateless-functional-components.english.md
@@ -28,9 +28,9 @@ tests:
- text: The Camper component should render.
testString: assert((function() { const mockedComponent = Enzyme.mount(React.createElement(CampSite)); return mockedComponent.find('Camper').length === 1; })());
- text: The Camper component should include default props which assign the string CamperBot to the key name.
- testString: assert(/Camper.defaultProps={name:(['"`])CamperBot\1,?}/.test(code.replace(/\s/g, '')));
+ testString: assert(/Camper.defaultProps={name:(['"`])CamperBot\1,?}/.test(__helpers.removeWhiteSpace(code)));
- text: The Camper component should include prop types which require the name prop to be of type string.
- testString: assert(/Camper.propTypes={name:PropTypes.string.isRequired,?}/.test(code.replace(/\s/g, '')));
+ testString: assert(/Camper.propTypes={name:PropTypes.string.isRequired,?}/.test(__helpers.removeWhiteSpace(code)));
- text: The Camper component should contain a p element with only the text from the name prop.
testString: assert((function() { const mockedComponent = Enzyme.mount(React.createElement(CampSite)); return mockedComponent.find('p').text() === mockedComponent.find('Camper').props().name; })());
diff --git a/curriculum/challenges/english/03-front-end-libraries/react/use-proptypes-to-define-the-props-you-expect.english.md b/curriculum/challenges/english/03-front-end-libraries/react/use-proptypes-to-define-the-props-you-expect.english.md
index be1486971fd..8d5f038fb1c 100644
--- a/curriculum/challenges/english/03-front-end-libraries/react/use-proptypes-to-define-the-props-you-expect.english.md
+++ b/curriculum/challenges/english/03-front-end-libraries/react/use-proptypes-to-define-the-props-you-expect.english.md
@@ -33,7 +33,7 @@ tests:
- text: The Items component should render.
testString: assert((function() { const mockedComponent = Enzyme.mount(React.createElement(ShoppingCart)); return mockedComponent.find('Items').length === 1; })());
- text: The Items component should include a propTypes check that requires quantity to be a number.
- testString: getUserInput => assert((function() { const noWhiteSpace = getUserInput('index').replace(/ /g, ''); return noWhiteSpace.includes('quantity:PropTypes.number.isRequired') && noWhiteSpace.includes('Items.propTypes='); })());
+ testString: getUserInput => assert((function() { const noWhiteSpace = __helpers.removeWhiteSpace(getUserInput('index')); return noWhiteSpace.includes('quantity:PropTypes.number.isRequired') && noWhiteSpace.includes('Items.propTypes='); })());
```
diff --git a/curriculum/challenges/english/03-front-end-libraries/react/use-state-to-toggle-an-element.english.md b/curriculum/challenges/english/03-front-end-libraries/react/use-state-to-toggle-an-element.english.md
index 9eb51983d5e..468ffb45862 100644
--- a/curriculum/challenges/english/03-front-end-libraries/react/use-state-to-toggle-an-element.english.md
+++ b/curriculum/challenges/english/03-front-end-libraries/react/use-state-to-toggle-an-element.english.md
@@ -77,7 +77,7 @@ tests:
assert(!firstValue && secondValue && !thirdValue);
})();"
- text: An anonymous function should be passed to setState.
- testString: const paramRegex = '[a-zA-Z$_]\\w*(,[a-zA-Z$_]\\w*)?'; const noSpaces = code.replace(/\s/g, ''); assert(new RegExp('this\\.setState\\((function\\(' + paramRegex + '\\){|([a-zA-Z$_]\\w*|\\(' + paramRegex + '\\))=>)').test(noSpaces));
+ testString: const paramRegex = '[a-zA-Z$_]\\w*(,[a-zA-Z$_]\\w*)?'; assert(new RegExp('this\\.setState\\((function\\(' + paramRegex + '\\){|([a-zA-Z$_]\\w*|\\(' + paramRegex + '\\))=>)').test(__helpers.removeWhiteSpace(code)));
- text: this should not be used inside setState
testString: assert(!/this\.setState\([^}]*this/.test(code));
```
diff --git a/curriculum/challenges/english/03-front-end-libraries/react/write-a-react-component-from-scratch.english.md b/curriculum/challenges/english/03-front-end-libraries/react/write-a-react-component-from-scratch.english.md
index 4b77cdf6a91..c40c423d7a2 100644
--- a/curriculum/challenges/english/03-front-end-libraries/react/write-a-react-component-from-scratch.english.md
+++ b/curriculum/challenges/english/03-front-end-libraries/react/write-a-react-component-from-scratch.english.md
@@ -23,7 +23,7 @@ Render this component to the DOM using ReactDOM.render(). There is
```yml
tests:
- text: There should be a React component called MyComponent.
- testString: getUserInput => assert(getUserInput('index').replace(/\s/g, '').includes('classMyComponentextendsReact.Component{'));
+ testString: getUserInput => assert(__helpers.removeWhiteSpace(getUserInput('index')).includes('classMyComponentextendsReact.Component{'));
- text: MyComponent should contain an h1 tag with text My First React Component! Case and punctuation matter.
testString: assert((function() { const mockedComponent = Enzyme.mount(React.createElement(MyComponent)); return mockedComponent.find('h1').text() === 'My First React Component!'; })());
- text: MyComponent should render to the DOM.
diff --git a/curriculum/challenges/english/03-front-end-libraries/redux/combine-multiple-reducers.english.md b/curriculum/challenges/english/03-front-end-libraries/redux/combine-multiple-reducers.english.md
index 3a57e2c57c1..724fc7425ba 100644
--- a/curriculum/challenges/english/03-front-end-libraries/redux/combine-multiple-reducers.english.md
+++ b/curriculum/challenges/english/03-front-end-libraries/redux/combine-multiple-reducers.english.md
@@ -39,7 +39,7 @@ tests:
- text: 'The store state should have two keys: count, which holds a number, and auth, which holds an object. The auth object should have a property of authenticated, which holds a boolean.'
testString: "assert((function() { const state = store.getState(); return typeof state.auth === 'object' && typeof state.auth.authenticated === 'boolean' && typeof state.count === 'number' })());"
- text: The rootReducer should be a function that combines the counterReducer and the authReducer.
- testString: getUserInput => assert((function() { const noWhiteSpace = getUserInput('index').replace(/\s/g,''); return typeof rootReducer === 'function' && noWhiteSpace.includes('Redux.combineReducers') })());
+ testString: getUserInput => assert((function() { const noWhiteSpace = __helpers.removeWhiteSpace(getUserInput('index')); return typeof rootReducer === 'function' && noWhiteSpace.includes('Redux.combineReducers') })());
```
diff --git a/curriculum/challenges/english/03-front-end-libraries/redux/use-const-for-action-types.english.md b/curriculum/challenges/english/03-front-end-libraries/redux/use-const-for-action-types.english.md
index cdd2afee83b..a75df764b9f 100644
--- a/curriculum/challenges/english/03-front-end-libraries/redux/use-const-for-action-types.english.md
+++ b/curriculum/challenges/english/03-front-end-libraries/redux/use-const-for-action-types.english.md
@@ -35,9 +35,9 @@ tests:
- text: The authReducer function should handle multiple action types with a switch statement.
testString: getUserInput => assert((function() { return typeof authReducer === 'function' && getUserInput('index').toString().includes('switch') && getUserInput('index').toString().includes('case') && getUserInput('index').toString().includes('default') })());
- text: LOGIN and LOGOUT should be declared as const values and should be assigned strings of LOGINand LOGOUT.
- testString: const noWhiteSpace = code.replace(/\s/g, ''); assert(/constLOGIN=(['"`])LOGIN\1/.test(noWhiteSpace) && /constLOGOUT=(['"`])LOGOUT\1/.test(noWhiteSpace));
+ testString: const noWhiteSpace = __helpers.removeWhiteSpace(code); assert(/constLOGIN=(['"`])LOGIN\1/.test(noWhiteSpace) && /constLOGOUT=(['"`])LOGOUT\1/.test(noWhiteSpace));
- text: The action creators and the reducer should reference the LOGIN and LOGOUT constants.
- testString: getUserInput => assert((function() { const noWhiteSpace = getUserInput('index').toString().replace(/\s/g,''); return noWhiteSpace.includes('caseLOGIN:') && noWhiteSpace.includes('caseLOGOUT:') && noWhiteSpace.includes('type:LOGIN') && noWhiteSpace.includes('type:LOGOUT') })());
+ testString: getUserInput => assert((function() { const noWhiteSpace = __helpers.removeWhiteSpace(getUserInput('index').toString()); return noWhiteSpace.includes('caseLOGIN:') && noWhiteSpace.includes('caseLOGOUT:') && noWhiteSpace.includes('type:LOGIN') && noWhiteSpace.includes('type:LOGOUT') })());
```
diff --git a/curriculum/challenges/english/03-front-end-libraries/redux/use-middleware-to-handle-asynchronous-actions.english.md b/curriculum/challenges/english/03-front-end-libraries/redux/use-middleware-to-handle-asynchronous-actions.english.md
index 578e5852141..6ee818aacec 100644
--- a/curriculum/challenges/english/03-front-end-libraries/redux/use-middleware-to-handle-asynchronous-actions.english.md
+++ b/curriculum/challenges/english/03-front-end-libraries/redux/use-middleware-to-handle-asynchronous-actions.english.md
@@ -33,7 +33,7 @@ tests:
- text: Dispatching the requestingData action creator should update the store state property of fetching to true.
testString: assert((function() { const initialState = store.getState(); store.dispatch(requestingData()); const reqState = store.getState(); return initialState.fetching === false && reqState.fetching === true })());
- text: Dispatching handleAsync should dispatch the data request action and then dispatch the received data action after a delay.
- testString: assert((function() { const noWhiteSpace = handleAsync.toString().replace(/\s/g,''); return noWhiteSpace.includes('dispatch(requestingData())') === true && noWhiteSpace.includes('dispatch(receivedData(data))') === true })());
+ testString: assert((function() { const noWhiteSpace = __helpers.removeWhiteSpace(handleAsync.toString()); return noWhiteSpace.includes('dispatch(requestingData())') === true && noWhiteSpace.includes('dispatch(receivedData(data))') === true })());
```
diff --git a/curriculum/challenges/english/10-coding-interview-prep/project-euler/problem-43-sub-string-divisibility.english.md b/curriculum/challenges/english/10-coding-interview-prep/project-euler/problem-43-sub-string-divisibility.english.md
index 522356ce70e..bed55bd0730 100644
--- a/curriculum/challenges/english/10-coding-interview-prep/project-euler/problem-43-sub-string-divisibility.english.md
+++ b/curriculum/challenges/english/10-coding-interview-prep/project-euler/problem-43-sub-string-divisibility.english.md
@@ -41,7 +41,7 @@ tests:
- text: substringDivisibility() should return [ 1430952867, 1460357289, 1406357289, 4130952867, 4160357289, 4106357289 ].
testString: assert.sameMembers(substringDivisibility(), [ 1430952867, 1460357289, 1406357289, 4130952867, 4160357289, 4106357289 ]);
- text: You should not copy and return the array.
- testString: assert(!removeJSComments(code).match(/(1430952867)|(1460357289)|(1406357289)|(4130952867)|(4160357289)|(4106357289)/))
+ testString: assert(!__helpers.removeJSComments(code).match(/(1430952867)|(1460357289)|(1406357289)|(4130952867)|(4160357289)|(4106357289)/))
```
@@ -63,15 +63,6 @@ substringDivisibility();
-### After Test
-