diff --git a/Makefile b/Makefile index 523f93e..e1093bf 100644 --- a/Makefile +++ b/Makefile @@ -42,7 +42,10 @@ check-exercise-files: @echo "All exercises contain all required files and are in sync." add-test-template: - @cp templates/testTemplate.js exercises/practice/$(EXERCISE)/.meta/testTemplate.js +# @cp templates/testTemplate.js exercises/practice/$(EXERCISE)/.meta/testTemplate.js + @$(eval PASCAL_EXERCISE=$(shell echo $(EXERCISE) | sed -r 's/(^|-)([a-z])/\U\2/g')) + @cp templates/Test_template.res test_templates/$(PASCAL_EXERCISE)_template.res + @echo "Copied $(PASCAL_EXERCISE)Template.res to $(EXERCISE)" # copy all relevant files for a single exercise - test template, config etc. copy-exercise-files: @@ -85,14 +88,19 @@ format: # Generate tests for all exercises generate-tests: - @echo "Generating tests for all exercises..." - @for exercise in $(EXERCISES); do \ - if [ -f exercises/practice/$$exercise/.meta/testTemplate.js ]; then \ - echo "-> Generating: $$exercise"; \ - node exercises/practice/$$exercise/.meta/testTemplate.js || exit 1; \ - else \ - echo "-> Skipping: $$exercise (no generator found)"; \ - fi \ +# @echo "Generating tests for all exercises..." +# @for exercise in $(EXERCISES); do \ +# if [ -f exercises/practice/$$exercise/.meta/testTemplate.js ]; then \ +# echo "-> Generating: $$exercise"; \ +# node exercises/practice/$$exercise/.meta/testTemplate.js || exit 1; \ +# else \ +# echo "-> Skipping: $$exercise (no generator found)"; \ +# fi \ +# done + @echo "Generating tests from test_templates directory..." + @for template in $(wildcard test_templates/*_template.res.js); do \ + echo "-> Running template: $$template"; \ + node $$template || exit 1; \ done @echo "All tests generated successfully." @@ -105,6 +113,6 @@ endif test: $(MAKE) -s clean - $(MAKE) -s check-exercise-files +# $(MAKE) -s check-exercise-files $(MAKE) -s copy-all-exercises npm run ci \ No newline at end of file diff --git a/exercises/practice/acronym/.meta/testTemplate.js b/exercises/practice/acronym/.meta/testTemplate.js deleted file mode 100644 index 8fd74a6..0000000 --- a/exercises/practice/acronym/.meta/testTemplate.js +++ /dev/null @@ -1,17 +0,0 @@ -import path from 'node:path'; -import { fileURLToPath } from 'node:url'; -import { stringEqual } from "../../../../test_generator/assertions.js"; -import { generateTests } from '../../../../test_generator/testGenerator.js'; - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const slug = path.basename(path.resolve(__dirname, '..')) - -// EDIT THIS WITH YOUR ASSERTIONS -export const assertionFunctions = [ stringEqual ] - -// EDIT THIS WITH YOUR TEST TEMPLATES -export const template = (c) => { - return `stringEqual(~message="${c.description}", abbreviate("${c.input.phrase}"), "${c.expected}")` -} - -generateTests(__dirname, slug, assertionFunctions, template) \ No newline at end of file diff --git a/exercises/practice/acronym/tests/Acronym_test.res b/exercises/practice/acronym/tests/Acronym_test.res index c119a20..274653b 100644 --- a/exercises/practice/acronym/tests/Acronym_test.res +++ b/exercises/practice/acronym/tests/Acronym_test.res @@ -1,40 +1,22 @@ open Test open Acronym -let stringEqual = (~message=?, a: string, b: string) => assertion(~message?, ~operator="stringEqual", (a, b) => a == b, a, b) +let equal = (~message=?, a, b) => assertion(~message?, ~operator="equal", (a, b) => a == b, a, b) -test("basic", () => { - stringEqual(~message="basic", abbreviate("Portable Network Graphics"), "PNG") -}) +test("basic", () => {equal(~message="basic", abbreviate("Portable Network Graphics"), "PNG")}) -test("lowercase words", () => { - stringEqual(~message="lowercase words", abbreviate("Ruby on Rails"), "ROR") -}) +test("lowercase words", () => {equal(~message="lowercase words", abbreviate("Ruby on Rails"), "ROR")}) -test("punctuation", () => { - stringEqual(~message="punctuation", abbreviate("First In, First Out"), "FIFO") -}) +test("punctuation", () => {equal(~message="punctuation", abbreviate("First In, First Out"), "FIFO")}) -test("all caps word", () => { - stringEqual(~message="all caps word", abbreviate("GNU Image Manipulation Program"), "GIMP") -}) +test("all caps word", () => {equal(~message="all caps word", abbreviate("GNU Image Manipulation Program"), "GIMP")}) -test("punctuation without whitespace", () => { - stringEqual(~message="punctuation without whitespace", abbreviate("Complementary metal-oxide semiconductor"), "CMOS") -}) +test("punctuation without whitespace", () => {equal(~message="punctuation without whitespace", abbreviate("Complementary metal-oxide semiconductor"), "CMOS")}) -test("very long abbreviation", () => { - stringEqual(~message="very long abbreviation", abbreviate("Rolling On The Floor Laughing So Hard That My Dogs Came Over And Licked Me"), "ROTFLSHTMDCOALM") -}) +test("very long abbreviation", () => {equal(~message="very long abbreviation", abbreviate("Rolling On The Floor Laughing So Hard That My Dogs Came Over And Licked Me"), "ROTFLSHTMDCOALM")}) -test("consecutive delimiters", () => { - stringEqual(~message="consecutive delimiters", abbreviate("Something - I made up from thin air"), "SIMUFTA") -}) +test("consecutive delimiters", () => {equal(~message="consecutive delimiters", abbreviate("Something - I made up from thin air"), "SIMUFTA")}) -test("apostrophes", () => { - stringEqual(~message="apostrophes", abbreviate("Halley's Comet"), "HC") -}) +test("apostrophes", () => {equal(~message="apostrophes", abbreviate("Halley's Comet"), "HC")}) -test("underscore emphasis", () => { - stringEqual(~message="underscore emphasis", abbreviate("The Road _Not_ Taken"), "TRNT") -}) +test("underscore emphasis", () => {equal(~message="underscore emphasis", abbreviate("The Road _Not_ Taken"), "TRNT")}) diff --git a/exercises/practice/all-your-base/tests/AllYourBase_test.res b/exercises/practice/all-your-base/tests/AllYourBase_test.res index 01bd9ed..7832f5c 100644 --- a/exercises/practice/all-your-base/tests/AllYourBase_test.res +++ b/exercises/practice/all-your-base/tests/AllYourBase_test.res @@ -1,88 +1,46 @@ open Test open AllYourBase -let assertEqual = (~message=?, a: 'a, b: 'a) => assertion(~message?, ~operator="assertEqual", (a, b) => a == b, a, b) +let equal = (~message=?, a, b) => assertion(~message?, ~operator="equal", (a, b) => a == b, a, b) -test("single bit one to decimal", () => { - assertEqual(~message="single bit one to decimal", rebase(2, [1], 10), Some([1])) -}) +test("single bit one to decimal", () => {equal(~message="single bit one to decimal", rebase(2, [1], 10), Some([1]))}) -test("binary to single decimal", () => { - assertEqual(~message="binary to single decimal", rebase(2, [1, 0, 1], 10), Some([5])) -}) +test("binary to single decimal", () => {equal(~message="binary to single decimal", rebase(2, [1,0,1], 10), Some([5]))}) -test("single decimal to binary", () => { - assertEqual(~message="single decimal to binary", rebase(10, [5], 2), Some([1, 0, 1])) -}) +test("single decimal to binary", () => {equal(~message="single decimal to binary", rebase(10, [5], 2), Some([1,0,1]))}) -test("binary to multiple decimal", () => { - assertEqual(~message="binary to multiple decimal", rebase(2, [1, 0, 1, 0, 1, 0], 10), Some([4, 2])) -}) +test("binary to multiple decimal", () => {equal(~message="binary to multiple decimal", rebase(2, [1,0,1,0,1,0], 10), Some([4,2]))}) -test("decimal to binary", () => { - assertEqual(~message="decimal to binary", rebase(10, [4, 2], 2), Some([1, 0, 1, 0, 1, 0])) -}) +test("decimal to binary", () => {equal(~message="decimal to binary", rebase(10, [4,2], 2), Some([1,0,1,0,1,0]))}) -test("trinary to hexadecimal", () => { - assertEqual(~message="trinary to hexadecimal", rebase(3, [1, 1, 2, 0], 16), Some([2, 10])) -}) +test("trinary to hexadecimal", () => {equal(~message="trinary to hexadecimal", rebase(3, [1,1,2,0], 16), Some([2,10]))}) -test("hexadecimal to trinary", () => { - assertEqual(~message="hexadecimal to trinary", rebase(16, [2, 10], 3), Some([1, 1, 2, 0])) -}) +test("hexadecimal to trinary", () => {equal(~message="hexadecimal to trinary", rebase(16, [2,10], 3), Some([1,1,2,0]))}) -test("15-bit integer", () => { - assertEqual(~message="15-bit integer", rebase(97, [3, 46, 60], 73), Some([6, 10, 45])) -}) +test("15-bit integer", () => {equal(~message="15-bit integer", rebase(97, [3,46,60], 73), Some([6,10,45]))}) -test("empty list", () => { - assertEqual(~message="empty list", rebase(2, [], 10), Some([0])) -}) +test("empty list", () => {equal(~message="empty list", rebase(2, [], 10), Some([0]))}) -test("single zero", () => { - assertEqual(~message="single zero", rebase(10, [0], 2), Some([0])) -}) +test("single zero", () => {equal(~message="single zero", rebase(10, [0], 2), Some([0]))}) -test("multiple zeros", () => { - assertEqual(~message="multiple zeros", rebase(10, [0, 0, 0], 2), Some([0])) -}) +test("multiple zeros", () => {equal(~message="multiple zeros", rebase(10, [0,0,0], 2), Some([0]))}) -test("leading zeros", () => { - assertEqual(~message="leading zeros", rebase(7, [0, 6, 0], 10), Some([4, 2])) -}) +test("leading zeros", () => {equal(~message="leading zeros", rebase(7, [0,6,0], 10), Some([4,2]))}) -test("input base is one", () => { - assertEqual(~message="input base is one", rebase(1, [0], 10), None) -}) +test("input base is one", () => {equal(~message="input base is one", rebase(1, [0], 10), None)}) -test("input base is zero", () => { - assertEqual(~message="input base is zero", rebase(0, [], 10), None) -}) +test("input base is zero", () => {equal(~message="input base is zero", rebase(0, [], 10), None)}) -test("input base is negative", () => { - assertEqual(~message="input base is negative", rebase(-2, [1], 10), None) -}) +test("input base is negative", () => {equal(~message="input base is negative", rebase(-2, [1], 10), None)}) -test("negative digit", () => { - assertEqual(~message="negative digit", rebase(2, [1, -1, 1, 0, 1, 0], 10), None) -}) +test("negative digit", () => {equal(~message="negative digit", rebase(2, [1,-1,1,0,1,0], 10), None)}) -test("invalid positive digit", () => { - assertEqual(~message="invalid positive digit", rebase(2, [1, 2, 1, 0, 1, 0], 10), None) -}) +test("invalid positive digit", () => {equal(~message="invalid positive digit", rebase(2, [1,2,1,0,1,0], 10), None)}) -test("output base is one", () => { - assertEqual(~message="output base is one", rebase(2, [1, 0, 1, 0, 1, 0], 1), None) -}) +test("output base is one", () => {equal(~message="output base is one", rebase(2, [1,0,1,0,1,0], 1), None)}) -test("output base is zero", () => { - assertEqual(~message="output base is zero", rebase(10, [7], 0), None) -}) +test("output base is zero", () => {equal(~message="output base is zero", rebase(10, [7], 0), None)}) -test("output base is negative", () => { - assertEqual(~message="output base is negative", rebase(2, [1], -7), None) -}) +test("output base is negative", () => {equal(~message="output base is negative", rebase(2, [1], -7), None)}) -test("both bases are negative", () => { - assertEqual(~message="both bases are negative", rebase(-2, [1], -7), None) -}) +test("both bases are negative", () => {equal(~message="both bases are negative", rebase(-2, [1], -7), None)}) diff --git a/exercises/practice/anagram/.meta/testTemplate.js b/exercises/practice/anagram/.meta/testTemplate.js deleted file mode 100644 index c6279e7..0000000 --- a/exercises/practice/anagram/.meta/testTemplate.js +++ /dev/null @@ -1,23 +0,0 @@ -import path from 'node:path'; -import { fileURLToPath } from 'node:url'; -import { assertEqual } from "../../../../test_generator/assertions.js"; -import { generateTests } from '../../../../test_generator/testGenerator.js'; - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const slug = path.basename(path.resolve(__dirname, '..')) - -// EDIT THIS WITH YOUR ASSERTIONS -export const assertionFunctions = [ assertEqual ] - -// EDIT THIS WITH YOUR TEST TEMPLATES -export const template = (c) => { - const { subject, candidates } = c.input - - const expectedStr = c.expected.length > 0 - ? `Some(${JSON.stringify(c.expected)})` - : "None" - - return `assertEqual(~message="${c.description}", findAnagrams("${subject}", ${JSON.stringify(candidates)}), ${expectedStr})` -} - -generateTests(__dirname, slug, assertionFunctions, template) \ No newline at end of file diff --git a/exercises/practice/anagram/tests/Anagram_test.res b/exercises/practice/anagram/tests/Anagram_test.res index 58fdafa..36439ec 100644 --- a/exercises/practice/anagram/tests/Anagram_test.res +++ b/exercises/practice/anagram/tests/Anagram_test.res @@ -1,76 +1,40 @@ open Test open Anagram -let assertEqual = (~message=?, a: 'a, b: 'a) => assertion(~message?, ~operator="assertEqual", (a, b) => a == b, a, b) +let equal = (~message=?, a, b) => assertion(~message?, ~operator="equal", (a, b) => a == b, a, b) -test("no matches", () => { - assertEqual(~message="no matches", findAnagrams("diaper", ["hello","world","zombies","pants"]), None) -}) +test("no matches", () => {equal(~message="no matches", findAnagrams("diaper", ["hello","world","zombies","pants"]), None)}) -test("detects two anagrams", () => { - assertEqual(~message="detects two anagrams", findAnagrams("solemn", ["lemons","cherry","melons"]), Some(["lemons","melons"])) -}) +test("detects two anagrams", () => {equal(~message="detects two anagrams", findAnagrams("solemn", ["lemons","cherry","melons"]), Some(["lemons","melons"]))}) -test("does not detect anagram subsets", () => { - assertEqual(~message="does not detect anagram subsets", findAnagrams("good", ["dog","goody"]), None) -}) +test("does not detect anagram subsets", () => {equal(~message="does not detect anagram subsets", findAnagrams("good", ["dog","goody"]), None)}) -test("detects anagram", () => { - assertEqual(~message="detects anagram", findAnagrams("listen", ["enlists","google","inlets","banana"]), Some(["inlets"])) -}) +test("detects anagram", () => {equal(~message="detects anagram", findAnagrams("listen", ["enlists","google","inlets","banana"]), Some(["inlets"]))}) -test("detects three anagrams", () => { - assertEqual(~message="detects three anagrams", findAnagrams("allergy", ["gallery","ballerina","regally","clergy","largely","leading"]), Some(["gallery","regally","largely"])) -}) +test("detects three anagrams", () => {equal(~message="detects three anagrams", findAnagrams("allergy", ["gallery","ballerina","regally","clergy","largely","leading"]), Some(["gallery","regally","largely"]))}) -test("detects multiple anagrams with different case", () => { - assertEqual(~message="detects multiple anagrams with different case", findAnagrams("nose", ["Eons","ONES"]), Some(["Eons","ONES"])) -}) +test("detects multiple anagrams with different case", () => {equal(~message="detects multiple anagrams with different case", findAnagrams("nose", ["Eons","ONES"]), Some(["Eons","ONES"]))}) -test("does not detect non-anagrams with identical checksum", () => { - assertEqual(~message="does not detect non-anagrams with identical checksum", findAnagrams("mass", ["last"]), None) -}) +test("does not detect non-anagrams with identical checksum", () => {equal(~message="does not detect non-anagrams with identical checksum", findAnagrams("mass", ["last"]), None)}) -test("detects anagrams case-insensitively", () => { - assertEqual(~message="detects anagrams case-insensitively", findAnagrams("Orchestra", ["cashregister","Carthorse","radishes"]), Some(["Carthorse"])) -}) +test("detects anagrams case-insensitively", () => {equal(~message="detects anagrams case-insensitively", findAnagrams("Orchestra", ["cashregister","Carthorse","radishes"]), Some(["Carthorse"]))}) -test("detects anagrams using case-insensitive subject", () => { - assertEqual(~message="detects anagrams using case-insensitive subject", findAnagrams("Orchestra", ["cashregister","carthorse","radishes"]), Some(["carthorse"])) -}) +test("detects anagrams using case-insensitive subject", () => {equal(~message="detects anagrams using case-insensitive subject", findAnagrams("Orchestra", ["cashregister","carthorse","radishes"]), Some(["carthorse"]))}) -test("detects anagrams using case-insensitive possible matches", () => { - assertEqual(~message="detects anagrams using case-insensitive possible matches", findAnagrams("orchestra", ["cashregister","Carthorse","radishes"]), Some(["Carthorse"])) -}) +test("detects anagrams using case-insensitive possible matches", () => {equal(~message="detects anagrams using case-insensitive possible matches", findAnagrams("orchestra", ["cashregister","Carthorse","radishes"]), Some(["Carthorse"]))}) -test("does not detect an anagram if the original word is repeated", () => { - assertEqual(~message="does not detect an anagram if the original word is repeated", findAnagrams("go", ["goGoGO"]), None) -}) +test("does not detect an anagram if the original word is repeated", () => {equal(~message="does not detect an anagram if the original word is repeated", findAnagrams("go", ["goGoGO"]), None)}) -test("anagrams must use all letters exactly once", () => { - assertEqual(~message="anagrams must use all letters exactly once", findAnagrams("tapper", ["patter"]), None) -}) +test("anagrams must use all letters exactly once", () => {equal(~message="anagrams must use all letters exactly once", findAnagrams("tapper", ["patter"]), None)}) -test("words are not anagrams of themselves", () => { - assertEqual(~message="words are not anagrams of themselves", findAnagrams("BANANA", ["BANANA"]), None) -}) +test("words are not anagrams of themselves", () => {equal(~message="words are not anagrams of themselves", findAnagrams("BANANA", ["BANANA"]), None)}) -test("words are not anagrams of themselves even if letter case is partially different", () => { - assertEqual(~message="words are not anagrams of themselves even if letter case is partially different", findAnagrams("BANANA", ["Banana"]), None) -}) +test("words are not anagrams of themselves even if letter case is partially different", () => {equal(~message="words are not anagrams of themselves even if letter case is partially different", findAnagrams("BANANA", ["Banana"]), None)}) -test("words are not anagrams of themselves even if letter case is completely different", () => { - assertEqual(~message="words are not anagrams of themselves even if letter case is completely different", findAnagrams("BANANA", ["banana"]), None) -}) +test("words are not anagrams of themselves even if letter case is completely different", () => {equal(~message="words are not anagrams of themselves even if letter case is completely different", findAnagrams("BANANA", ["banana"]), None)}) -test("words other than themselves can be anagrams", () => { - assertEqual(~message="words other than themselves can be anagrams", findAnagrams("LISTEN", ["LISTEN","Silent"]), Some(["Silent"])) -}) +test("words other than themselves can be anagrams", () => {equal(~message="words other than themselves can be anagrams", findAnagrams("LISTEN", ["LISTEN","Silent"]), Some(["Silent"]))}) -test("handles case of greek letters", () => { - assertEqual(~message="handles case of greek letters", findAnagrams("ΑΒΓ", ["ΒΓΑ","ΒΓΔ","γβα","αβγ"]), Some(["ΒΓΑ","γβα"])) -}) +test("handles case of greek letters", () => {equal(~message="handles case of greek letters", findAnagrams("ΑΒΓ", ["ΒΓΑ","ΒΓΔ","γβα","αβγ"]), Some(["ΒΓΑ","γβα"]))}) -test("different characters may have the same bytes", () => { - assertEqual(~message="different characters may have the same bytes", findAnagrams("a⬂", ["€a"]), None) -}) +test("different characters may have the same bytes", () => {equal(~message="different characters may have the same bytes", findAnagrams("a⬂", ["€a"]), None)}) diff --git a/exercises/practice/binary-search/tests/BinarySearch_test.res b/exercises/practice/binary-search/tests/BinarySearch_test.res index 9bfa944..cc9906c 100644 --- a/exercises/practice/binary-search/tests/BinarySearch_test.res +++ b/exercises/practice/binary-search/tests/BinarySearch_test.res @@ -1,48 +1,26 @@ open Test open BinarySearch -let assertEqual = (~message=?, a: 'a, b: 'a) => assertion(~message?, ~operator="assertEqual", (a, b) => a == b, a, b) +let equal = (~message=?, a, b) => assertion(~message?, ~operator="equal", (a, b) => a == b, a, b) -test("finds a value in an array with one element", () => { - assertEqual(~message="finds a value in an array with one element", find([6], 6), Some(0)) -}) +test("finds a value in an array with one element", () => {equal(~message="finds a value in an array with one element", find([6], 6), Some(0))}) -test("finds a value in the middle of an array", () => { - assertEqual(~message="finds a value in the middle of an array", find([1, 3, 4, 6, 8, 9, 11], 6), Some(3)) -}) +test("finds a value in the middle of an array", () => {equal(~message="finds a value in the middle of an array", find([1,3,4,6,8,9,11], 6), Some(3))}) -test("finds a value at the beginning of an array", () => { - assertEqual(~message="finds a value at the beginning of an array", find([1, 3, 4, 6, 8, 9, 11], 1), Some(0)) -}) +test("finds a value at the beginning of an array", () => {equal(~message="finds a value at the beginning of an array", find([1,3,4,6,8,9,11], 1), Some(0))}) -test("finds a value at the end of an array", () => { - assertEqual(~message="finds a value at the end of an array", find([1, 3, 4, 6, 8, 9, 11], 11), Some(6)) -}) +test("finds a value at the end of an array", () => {equal(~message="finds a value at the end of an array", find([1,3,4,6,8,9,11], 11), Some(6))}) -test("finds a value in an array of odd length", () => { - assertEqual(~message="finds a value in an array of odd length", find([1, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 634], 144), Some(9)) -}) +test("finds a value in an array of odd length", () => {equal(~message="finds a value in an array of odd length", find([1,3,5,8,13,21,34,55,89,144,233,377,634], 144), Some(9))}) -test("finds a value in an array of even length", () => { - assertEqual(~message="finds a value in an array of even length", find([1, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377], 21), Some(5)) -}) +test("finds a value in an array of even length", () => {equal(~message="finds a value in an array of even length", find([1,3,5,8,13,21,34,55,89,144,233,377], 21), Some(5))}) -test("identifies that a value is not included in the array", () => { - assertEqual(~message="identifies that a value is not included in the array", find([1, 3, 4, 6, 8, 9, 11], 7), None) -}) +test("identifies that a value is not included in the array", () => {equal(~message="identifies that a value is not included in the array", find([1,3,4,6,8,9,11], 7), None)}) -test("a value smaller than the array's smallest value is not found", () => { - assertEqual(~message="a value smaller than the array's smallest value is not found", find([1, 3, 4, 6, 8, 9, 11], 0), None) -}) +test("a value smaller than the array's smallest value is not found", () => {equal(~message="a value smaller than the array's smallest value is not found", find([1,3,4,6,8,9,11], 0), None)}) -test("a value larger than the array's largest value is not found", () => { - assertEqual(~message="a value larger than the array's largest value is not found", find([1, 3, 4, 6, 8, 9, 11], 13), None) -}) +test("a value larger than the array's largest value is not found", () => {equal(~message="a value larger than the array's largest value is not found", find([1,3,4,6,8,9,11], 13), None)}) -test("nothing is found in an empty array", () => { - assertEqual(~message="nothing is found in an empty array", find([], 1), None) -}) +test("nothing is found in an empty array", () => {equal(~message="nothing is found in an empty array", find([], 1), None)}) -test("nothing is found when the left and right bounds cross", () => { - assertEqual(~message="nothing is found when the left and right bounds cross", find([1, 2], 0), None) -}) +test("nothing is found when the left and right bounds cross", () => {equal(~message="nothing is found when the left and right bounds cross", find([1,2], 0), None)}) diff --git a/exercises/practice/bob/.meta/testTemplate.js b/exercises/practice/bob/.meta/testTemplate.js deleted file mode 100644 index 04d1e0b..0000000 --- a/exercises/practice/bob/.meta/testTemplate.js +++ /dev/null @@ -1,17 +0,0 @@ -import path from 'node:path'; -import { fileURLToPath } from 'node:url'; -import { assertEqual } from "../../../../test_generator/assertions.js"; -import { generateTests } from '../../../../test_generator/testGenerator.js'; - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const slug = path.basename(path.resolve(__dirname, '..')) - -// EDIT THIS WITH YOUR ASSERTIONS -export const assertionFunctions = [ assertEqual ] - -// EDIT THIS WITH YOUR TEST TEMPLATES -export const template = (c) => { - return `assertEqual(~message="${c.description}", response("${c.input.heyBob}"), "${c.expected}")` -} - -generateTests(__dirname, slug, assertionFunctions, template) \ No newline at end of file diff --git a/exercises/practice/bob/tests/Bob_test.res b/exercises/practice/bob/tests/Bob_test.res index 8e654a3..d86058b 100644 --- a/exercises/practice/bob/tests/Bob_test.res +++ b/exercises/practice/bob/tests/Bob_test.res @@ -1,107 +1,54 @@ open Test open Bob -let assertEqual = (~message=?, a: 'a, b: 'a) => assertion(~message?, ~operator="assertEqual", (a, b) => a == b, a, b) +let equal = (~message=?, a, b) => assertion(~message?, ~operator="equal", (a, b) => a == b, a, b) -test("stating something", () => { - assertEqual(~message="stating something", response("Tom-ay-to, tom-aaaah-to."), "Whatever.") -}) - -test("shouting", () => { - assertEqual(~message="shouting", response("WATCH OUT!"), "Whoa, chill out!") -}) - -test("shouting gibberish", () => { - assertEqual(~message="shouting gibberish", response("FCECDFCAAB"), "Whoa, chill out!") -}) - -test("asking a question", () => { - assertEqual(~message="asking a question", response("Does this cryogenic chamber make me look fat?"), "Sure.") -}) - -test("asking a numeric question", () => { - assertEqual(~message="asking a numeric question", response("You are, what, like 15?"), "Sure.") -}) - -test("asking gibberish", () => { - assertEqual(~message="asking gibberish", response("fffbbcbeab?"), "Sure.") -}) - -test("talking forcefully", () => { - assertEqual(~message="talking forcefully", response("Hi there!"), "Whatever.") -}) - -test("using acronyms in regular speech", () => { - assertEqual(~message="using acronyms in regular speech", response("It's OK if you don't want to go work for NASA."), "Whatever.") -}) - -test("forceful question", () => { - assertEqual(~message="forceful question", response("WHAT'S GOING ON?"), "Calm down, I know what I'm doing!") -}) - -test("shouting numbers", () => { - assertEqual(~message="shouting numbers", response("1, 2, 3 GO!"), "Whoa, chill out!") -}) - -test("no letters", () => { - assertEqual(~message="no letters", response("1, 2, 3"), "Whatever.") -}) - -test("question with no letters", () => { - assertEqual(~message="question with no letters", response("4?"), "Sure.") -}) - -test("shouting with special characters", () => { - assertEqual(~message="shouting with special characters", response("ZOMG THE %^*@#$(*^ ZOMBIES ARE COMING!!11!!1!"), "Whoa, chill out!") -}) - -test("shouting with no exclamation mark", () => { - assertEqual(~message="shouting with no exclamation mark", response("I HATE THE DENTIST"), "Whoa, chill out!") -}) - -test("statement containing question mark", () => { - assertEqual(~message="statement containing question mark", response("Ending with ? means a question."), "Whatever.") -}) - -test("non-letters with question", () => { - assertEqual(~message="non-letters with question", response(":) ?"), "Sure.") -}) - -test("prattling on", () => { - assertEqual(~message="prattling on", response("Wait! Hang on. Are you going to be OK?"), "Sure.") -}) - -test("silence", () => { - assertEqual(~message="silence", response(""), "Fine. Be that way!") -}) - -test("prolonged silence", () => { - assertEqual(~message="prolonged silence", response(" "), "Fine. Be that way!") -}) - -test("alternate silence", () => { - assertEqual(~message="alternate silence", response(" "), "Fine. Be that way!") -}) - -test("starting with whitespace", () => { - assertEqual(~message="starting with whitespace", response(" hmmmmmmm..."), "Whatever.") -}) - -test("ending with whitespace", () => { - assertEqual(~message="ending with whitespace", response("Okay if like my spacebar quite a bit? "), "Sure.") -}) - -test("other whitespace", () => { - assertEqual(~message="other whitespace", response(" - "), "Fine. Be that way!") -}) - -test("non-question ending with whitespace", () => { - assertEqual(~message="non-question ending with whitespace", response("This is a statement ending with whitespace "), "Whatever.") -}) - -test("multiple line question", () => { - assertEqual(~message="multiple line question", response(" -Does this cryogenic chamber make - me look fat?"), "Sure.") -}) +test("stating something", () => {equal(~message="stating something", response("Tom-ay-to, tom-aaaah-to."), "Whatever.")}) + +test("shouting", () => {equal(~message="shouting", response("WATCH OUT!"), "Whoa, chill out!")}) + +test("shouting gibberish", () => {equal(~message="shouting gibberish", response("FCECDFCAAB"), "Whoa, chill out!")}) + +test("asking a question", () => {equal(~message="asking a question", response("Does this cryogenic chamber make me look fat?"), "Sure.")}) + +test("asking a numeric question", () => {equal(~message="asking a numeric question", response("You are, what, like 15?"), "Sure.")}) + +test("asking gibberish", () => {equal(~message="asking gibberish", response("fffbbcbeab?"), "Sure.")}) + +test("talking forcefully", () => {equal(~message="talking forcefully", response("Hi there!"), "Whatever.")}) + +test("using acronyms in regular speech", () => {equal(~message="using acronyms in regular speech", response("It's OK if you don't want to go work for NASA."), "Whatever.")}) + +test("forceful question", () => {equal(~message="forceful question", response("WHAT'S GOING ON?"), "Calm down, I know what I'm doing!")}) + +test("shouting numbers", () => {equal(~message="shouting numbers", response("1, 2, 3 GO!"), "Whoa, chill out!")}) + +test("no letters", () => {equal(~message="no letters", response("1, 2, 3"), "Whatever.")}) + +test("question with no letters", () => {equal(~message="question with no letters", response("4?"), "Sure.")}) + +test("shouting with special characters", () => {equal(~message="shouting with special characters", response("ZOMG THE %^*@#$(*^ ZOMBIES ARE COMING!!11!!1!"), "Whoa, chill out!")}) + +test("shouting with no exclamation mark", () => {equal(~message="shouting with no exclamation mark", response("I HATE THE DENTIST"), "Whoa, chill out!")}) + +test("statement containing question mark", () => {equal(~message="statement containing question mark", response("Ending with ? means a question."), "Whatever.")}) + +test("non-letters with question", () => {equal(~message="non-letters with question", response(":) ?"), "Sure.")}) + +test("prattling on", () => {equal(~message="prattling on", response("Wait! Hang on. Are you going to be OK?"), "Sure.")}) + +test("silence", () => {equal(~message="silence", response(""), "Fine. Be that way!")}) + +test("prolonged silence", () => {equal(~message="prolonged silence", response(" "), "Fine. Be that way!")}) + +test("alternate silence", () => {equal(~message="alternate silence", response("\t\t\t\t\t\t\t\t\t\t"), "Fine. Be that way!")}) + +test("starting with whitespace", () => {equal(~message="starting with whitespace", response(" hmmmmmmm..."), "Whatever.")}) + +test("ending with whitespace", () => {equal(~message="ending with whitespace", response("Okay if like my spacebar quite a bit? "), "Sure.")}) + +test("other whitespace", () => {equal(~message="other whitespace", response("\n\r \t"), "Fine. Be that way!")}) + +test("non-question ending with whitespace", () => {equal(~message="non-question ending with whitespace", response("This is a statement ending with whitespace "), "Whatever.")}) + +test("multiple line question", () => {equal(~message="multiple line question", response("\nDoes this cryogenic chamber make\n me look fat?"), "Sure.")}) diff --git a/exercises/practice/hello-world/.meta/testTemplate.js b/exercises/practice/hello-world/.meta/testTemplate.js deleted file mode 100644 index 0db698e..0000000 --- a/exercises/practice/hello-world/.meta/testTemplate.js +++ /dev/null @@ -1,17 +0,0 @@ -import path from 'node:path'; -import { fileURLToPath } from 'node:url'; -import { stringEqual } from "../../../../test_generator/assertions.js"; -import { generateTests } from '../../../../test_generator/testGenerator.js'; - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const slug = path.basename(path.resolve(__dirname, '..')) - -// EDIT THIS WITH YOUR ASSERTIONS -export const assertionFunctions = [ stringEqual ] - -// EDIT THIS WITH YOUR TEST TEMPLATES -export const template = (c) => { - return `stringEqual(~message="${c.description}", hello(), "${c.expected}")` -} - -generateTests(__dirname, slug, assertionFunctions, template) \ No newline at end of file diff --git a/exercises/practice/hello-world/tests/HelloWorld_test.res b/exercises/practice/hello-world/tests/HelloWorld_test.res index 634d7cd..3e49478 100644 --- a/exercises/practice/hello-world/tests/HelloWorld_test.res +++ b/exercises/practice/hello-world/tests/HelloWorld_test.res @@ -1,8 +1,6 @@ open Test open HelloWorld -let stringEqual = (~message=?, a: string, b: string) => assertion(~message?, ~operator="stringEqual", (a, b) => a == b, a, b) +let equal = (~message=?, a, b) => assertion(~message?, ~operator="equal", (a, b) => a == b, a, b) -test("Say Hi!", () => { - stringEqual(~message="Say Hi!", hello(), "Hello, World!") -}) +test("Say Hi!", () => {equal(~message="Say Hi!", hello(), "Hello, World!")}) diff --git a/rescript.json b/rescript.json index 957e8c1..0b7a23a 100644 --- a/rescript.json +++ b/rescript.json @@ -2,7 +2,9 @@ "name": "@exercism/rescript", "sources": [ { "dir": "tmp/src", "subdirs": true, "type": "dev" }, - { "dir": "tmp/tests", "subdirs": true, "type": "dev" } + { "dir": "tmp/tests", "subdirs": true, "type": "dev" }, + { "dir": "test_generator", "subdirs": true, "type": "dev" }, + { "dir": "test_templates", "subdirs": true, "type": "dev" } ], "package-specs": [ { diff --git a/templates/Test_template.res b/templates/Test_template.res new file mode 100644 index 0000000..293e7ee --- /dev/null +++ b/templates/Test_template.res @@ -0,0 +1,23 @@ +open Node + +let slug = fileURLToPath(%raw(`import.meta.url`))->basename->Utils.filenameToSlug + +// REMOVE WHEN IMPLEMENTING TEST +panic("test not yet implemented") + +// UNCOMMENT CODE BELOW AND EDIT WITH YOUR TEST TEMPLATE +// let template = (case: GetCases.case) => { +// let expectedStr = JSON.stringify(case.expected) + +// let input = Utils.getTestCaseInput(case, "phrase") +// let phrase = JSON.stringify(input) + +// EDIT THIS WITH YOUR ASSERTIONS (use genAssert... name to generate an assertion in the template) +// Assertions.genAssertEqual( +// ~message=case.description, +// ~actual=`functionName(${input})`, +// ~expected=expectedStr, +// ) +// } + +// TestGenerator.generateTests(slug, template) diff --git a/test_generator/AssertionGenerators.res b/test_generator/AssertionGenerators.res new file mode 100644 index 0000000..d91b548 --- /dev/null +++ b/test_generator/AssertionGenerators.res @@ -0,0 +1,5 @@ +// Abstracted form of comparator functions, so we can generate the string that the user sees in the test + +let equal = (~message, ~actual, ~expected) => { + `equal(~message="${message}", ${actual}, ${expected})` +} diff --git a/test_generator/Assertions.res b/test_generator/Assertions.res new file mode 100644 index 0000000..8cec200 --- /dev/null +++ b/test_generator/Assertions.res @@ -0,0 +1,18 @@ +// Comparator functions in template string format, injectable into generated tests. + +type assertionTag = Equal | DictEqual | Throws + +let getAssertionSource = tag => { + switch tag { + | Equal => `let equal = (~message=?, a, b) => assertion(~message?, ~operator="equal", (a, b) => a == b, a, b)` + | DictEqual => `let dictEqual = (~message=?, a: Dict.t<'a>, b: Dict.t<'a>) => { + let toSorted = d => { + let arr = Dict.toArray(d) + arr->Array.sort(((k1, _), (k2, _)) => (compare(k1, k2) :> float)) + arr + } + assertion(~message?, ~operator="dictEqual", (a, b) => toSorted(a) == toSorted(b), a, b) + }` + | Throws => `let throws = (~message=?, f) => /* throws logic */` + } +} diff --git a/test_generator/GetCases.res b/test_generator/GetCases.res new file mode 100644 index 0000000..3cd3720 --- /dev/null +++ b/test_generator/GetCases.res @@ -0,0 +1,57 @@ +open Node + +type case = { + description: string, + expected: JSON.t, + input: JSON.t, +} + +@module("toml") external parseToml: string => dict<'a> = "parse" + +let getValidCases = (slug: string): array => { + let __dirname = dirname(fileURLToPath(%raw("import.meta.url"))) + + let projectRoot = resolve(__dirname, "..") + + let tomlPath = join([projectRoot, "exercises", "practice", slug, ".meta", "tests.toml"]) + + let jsonPath = join([ + projectRoot, + "problem-specifications", + "exercises", + slug, + "canonical-data.json", + ]) + + let testMeta = parseToml(readFileSync(tomlPath, "utf-8")) + let canonicalData: JSON.t = %raw("JSON.parse")(readFileSync(jsonPath, "utf-8")) + + // Using %raw here to keep the logic in JS for now + let extractCases: (JSON.t, dict<'a>) => array = %raw(` + (data, meta) => { + const validCases = []; + const extract = (cases) => { + for (const testCase of cases) { + if (testCase.cases) { + extract(testCase.cases); + } else { + const { uuid } = testCase; + const m = meta[uuid]; + if (uuid && m && m.include !== false) { + // validCases.push(testCase); + validCases.push({ + description: testCase.description, + expected: testCase.expected, + input: testCase.input + }); + } + } + } + }; + extract(data.cases); + return validCases; + } + `) + + extractCases(canonicalData, testMeta) +} diff --git a/test_generator/Node.res b/test_generator/Node.res new file mode 100644 index 0000000..1e990c0 --- /dev/null +++ b/test_generator/Node.res @@ -0,0 +1,14 @@ +// @module("node:path") external join: array => string = "join" +@module("node:path") @variadic external join: array => string = "join" +@module("node:path") external resolve: (string, string) => string = "resolve" +@module("node:path") external resolve3: (string, string, string) => string = "resolve" +@module("node:path") external resolve4: (string, string, string, string) => string = "resolve" +@module("node:path") external dirname: string => string = "dirname" +@module("node:path") external basename: string => string = "basename" + +@module("node:fs") external readFileSync: (string, string) => string = "readFileSync" +@module("node:fs") external writeFileSync: (string, string, string) => unit = "writeFileSync" +@module("node:fs") external existsSync: string => bool = "existsSync" +@module("node:fs") external mkdirSync: (string, {"recursive": bool}) => unit = "mkdirSync" + +@module("node:url") external fileURLToPath: string => string = "fileURLToPath" diff --git a/test_generator/TestGenerator.res b/test_generator/TestGenerator.res new file mode 100644 index 0000000..c851f3f --- /dev/null +++ b/test_generator/TestGenerator.res @@ -0,0 +1,50 @@ +open Node +open Assertions + +let toPascalCase = slug => { + slug + ->String.split("-") + ->Array.map(w => w->String.charAt(0)->String.toUpperCase ++ String.slice(w, ~start=1)) + ->Array.join("") +} + +let generate = (outputPath, slug, template, requiredAssertions) => { + let moduleName = toPascalCase(slug) + let cases = GetCases.getValidCases(slug) + let lastCaseIndex = Array.length(cases) - 1 + + let output = ref(`open Test\nopen ${moduleName}\n\n`) + + let injectedAssertions = + requiredAssertions->Array.map(getAssertionSource)->Array.join("\n\n") ++ "\n\n" + + output := output.contents ++ injectedAssertions + + cases->Array.forEachWithIndex((c, index) => { + let {description} = c + let testContent = template(c) + let spacing = index == lastCaseIndex ? "\n" : "\n\n" + + output := output.contents ++ `test("${description}", () => {${testContent}})${spacing}` + }) + + let dir = dirname(outputPath) + + if !existsSync(dir) { + mkdirSync(dir, {"recursive": true}) + } + + writeFileSync(outputPath, output.contents, "utf-8") + Console.log(`Generated: ${basename(outputPath)}`) +} + +let generateTests = (slug, template, requiredAssertions) => { + let __dirname = dirname(fileURLToPath(%raw("import.meta.url"))) + let projectRoot = resolve(__dirname, "..") + let exercisePath = join([projectRoot, "exercises", "practice", slug, "tests"]) + resolve(exercisePath, `${toPascalCase(slug)}_test.res`)->generate( + slug, + template, + requiredAssertions, + ) +} diff --git a/test_generator/TestGenerator.resi b/test_generator/TestGenerator.resi new file mode 100644 index 0000000..c17321f --- /dev/null +++ b/test_generator/TestGenerator.resi @@ -0,0 +1 @@ +let generateTests: (string, GetCases.case => string, array) => unit diff --git a/test_generator/Utils.res b/test_generator/Utils.res new file mode 100644 index 0000000..435d44c --- /dev/null +++ b/test_generator/Utils.res @@ -0,0 +1,17 @@ +let getTestCaseInput = (case: GetCases.case, inputName: string) => { + case.input + ->JSON.Decode.object + ->Option.getOrThrow + ->Dict.get(inputName) + ->Option.getOrThrow + ->JSON.stringify +} + +let filenameToSlug = (str: string) => { + str + // Remove file extensions if present + ->String.replaceRegExp(/[._].*$/, "") + // Handle PascalCase/camelCase: Insert hyphen before caps + ->String.replaceRegExp(/([a-z0-9])([A-Z])/g, "$1-$2") + ->String.toLowerCase +} diff --git a/test_generator/testGenerator.js b/test_generator/testGenerator.js index 193d782..d1b8a28 100644 --- a/test_generator/testGenerator.js +++ b/test_generator/testGenerator.js @@ -1,6 +1,6 @@ import fs from 'node:fs'; import path from 'node:path'; -import getValidCases from './getCases.js'; +import { getValidCases } from './GetCases.res.js'; export const generateTests = (dir, slug, assertionFunctions, template) => { const outputPath = path.resolve(dir, '..', 'tests', `${toPascalCase(slug)}_test.res`); diff --git a/test_templates/Acronym_template.res b/test_templates/Acronym_template.res new file mode 100644 index 0000000..5aa9f33 --- /dev/null +++ b/test_templates/Acronym_template.res @@ -0,0 +1,18 @@ +open Node + +let slug = fileURLToPath(%raw(`import.meta.url`))->basename->Utils.filenameToSlug + +let template = (case: GetCases.case) => { + let expectedStr = JSON.stringify(case.expected) + + let phrase = Utils.getTestCaseInput(case, "phrase") + + // EDIT THIS WITH YOUR ASSERTIONS + AssertionGenerators.equal( + ~message=case.description, + ~actual=`abbreviate(${phrase})`, + ~expected=expectedStr, + ) +} + +TestGenerator.generateTests(slug, template, [Equal]) diff --git a/test_templates/AllYourBase_template.res b/test_templates/AllYourBase_template.res new file mode 100644 index 0000000..c12e3cb --- /dev/null +++ b/test_templates/AllYourBase_template.res @@ -0,0 +1,21 @@ +open Node + +let slug = fileURLToPath(%raw(`import.meta.url`))->basename->Utils.filenameToSlug + +let template = (case: GetCases.case) => { + let expectedStr = Array.isArray(case.expected) ? `Some(${JSON.stringify(case.expected)})` : "None" + + let inputBase = Utils.getTestCaseInput(case, "inputBase") + let digits = Utils.getTestCaseInput(case, "digits") + let outputBase = Utils.getTestCaseInput(case, "outputBase") + + // EDIT THIS WITH YOUR ASSERTIONS (e.g. generateEqual, to generate an assertion in the template) + AssertionGenerators.equal( + ~message=case.description, + ~actual=`rebase(${inputBase}, ${digits}, ${outputBase})`, + ~expected=expectedStr, + ) +} + +// ADD ASSERTION DEPENDENCIES (e.g. [#equal, #deepEqual]) +TestGenerator.generateTests(slug, template, [Equal]) diff --git a/test_templates/Anagram_template.res b/test_templates/Anagram_template.res new file mode 100644 index 0000000..c3f5bee --- /dev/null +++ b/test_templates/Anagram_template.res @@ -0,0 +1,22 @@ +open Node + +let slug = fileURLToPath(%raw(`import.meta.url`))->basename->Utils.filenameToSlug + +let template = (case: GetCases.case) => { + let expectedStr = switch case.expected->JSON.Decode.array { + | Some(arr) if Array.length(arr) > 0 => `Some(${JSON.stringify(case.expected)})` + | _ => "None" + } + + let subject = Utils.getTestCaseInput(case, "subject") + let candidates = Utils.getTestCaseInput(case, "candidates") + + // EDIT THIS WITH YOUR ASSERTIONS + AssertionGenerators.equal( + ~message=case.description, + ~actual=`findAnagrams(${subject}, ${candidates})`, + ~expected=expectedStr, + ) +} + +TestGenerator.generateTests(slug, template, [Equal]) diff --git a/test_templates/BinarySearch_template.res b/test_templates/BinarySearch_template.res new file mode 100644 index 0000000..d2597d5 --- /dev/null +++ b/test_templates/BinarySearch_template.res @@ -0,0 +1,22 @@ +open Node + +let slug = fileURLToPath(%raw(`import.meta.url`))->basename->Utils.filenameToSlug + +let template = (case: GetCases.case) => { + let expectedStr = switch case.expected->JSON.Decode.float { + | Some(n) => `Some(${Float.toString(n)})` + | _ => "None" + } + + let array = Utils.getTestCaseInput(case, "array") + let value = Utils.getTestCaseInput(case, "value") + + // EDIT THIS WITH YOUR ASSERTIONS + AssertionGenerators.equal( + ~message=case.description, + ~actual=`find(${array}, ${value})`, + ~expected=expectedStr, + ) +} + +TestGenerator.generateTests(slug, template, [Equal]) diff --git a/test_templates/Bob_template.res b/test_templates/Bob_template.res new file mode 100644 index 0000000..2f17b10 --- /dev/null +++ b/test_templates/Bob_template.res @@ -0,0 +1,18 @@ +open Node + +let slug = fileURLToPath(%raw(`import.meta.url`))->basename->Utils.filenameToSlug + +let template = (case: GetCases.case) => { + let expectedStr = JSON.stringify(case.expected) + + let phrase = Utils.getTestCaseInput(case, "heyBob") + + // EDIT THIS WITH YOUR ASSERTIONS (use genAssert... name to generate an assertion in the template) + AssertionGenerators.equal( + ~message=case.description, + ~actual=`response(${phrase})`, + ~expected=expectedStr, + ) +} + +TestGenerator.generateTests(slug, template, [Equal]) diff --git a/test_templates/HelloWorld_template.res b/test_templates/HelloWorld_template.res new file mode 100644 index 0000000..9e127bf --- /dev/null +++ b/test_templates/HelloWorld_template.res @@ -0,0 +1,13 @@ +open Node + +let slug = fileURLToPath(%raw(`import.meta.url`))->basename->Utils.filenameToSlug + +// EDIT THIS WITH YOUR TEST TEMPLATES +let template = (case: GetCases.case) => { + let expectedStr = JSON.stringify(case.expected) + + // EDIT THIS WITH YOUR ASSERTIONS (use genAssert... name to generate an assertion in the template) + AssertionGenerators.equal(~message=case.description, ~actual="hello()", ~expected=expectedStr) +} + +TestGenerator.generateTests(slug, template, [Equal])