From 5181a63aa1d9eaf84b16757b913d3ab880ea3ab9 Mon Sep 17 00:00:00 2001 From: Owen Date: Mon, 16 Mar 2026 10:13:20 +0100 Subject: [PATCH 01/26] create Node bindings --- test_generator/Node.res | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 test_generator/Node.res diff --git a/test_generator/Node.res b/test_generator/Node.res new file mode 100644 index 0000000..b808b23 --- /dev/null +++ b/test_generator/Node.res @@ -0,0 +1,13 @@ +// @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 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" From 08c45a2fdb8596f9e8b2f60b0a52050cea2ca99b Mon Sep 17 00:00:00 2001 From: Owen Date: Mon, 16 Mar 2026 10:21:02 +0100 Subject: [PATCH 02/26] convert getCases to ReScript --- test_generator/GetCases.res | 44 +++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 test_generator/GetCases.res diff --git a/test_generator/GetCases.res b/test_generator/GetCases.res new file mode 100644 index 0000000..3faeed8 --- /dev/null +++ b/test_generator/GetCases.res @@ -0,0 +1,44 @@ +open Node + +@module("toml") external parseToml: string => dict<'a> = "parse" + +let getValidCases = (slug: string) => { + let __dirname = dirname(fileURLToPath(%raw("import.meta.url"))) + + let tomlPath = join([__dirname, "..", "exercises", "practice", slug, ".meta", "tests.toml"]) + let jsonPath = join([ + __dirname, + "..", + "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); + } + } + } + }; + extract(data.cases); + return validCases; + } + `) + + extractCases(canonicalData, testMeta) +} From 97fc94a6f53fe76e11591c563957aa07f5cb09d2 Mon Sep 17 00:00:00 2001 From: Owen Date: Mon, 16 Mar 2026 10:45:26 +0100 Subject: [PATCH 03/26] build test generator on project build --- rescript.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rescript.json b/rescript.json index 957e8c1..bcc49b4 100644 --- a/rescript.json +++ b/rescript.json @@ -2,7 +2,8 @@ "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" } ], "package-specs": [ { From 1b94202bb8f665a1f591ef54e9974b97fbf26c47 Mon Sep 17 00:00:00 2001 From: Owen Date: Mon, 16 Mar 2026 11:24:33 +0100 Subject: [PATCH 04/26] change type of cases --- test_generator/GetCases.res | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/test_generator/GetCases.res b/test_generator/GetCases.res index 3faeed8..e738e16 100644 --- a/test_generator/GetCases.res +++ b/test_generator/GetCases.res @@ -1,8 +1,14 @@ open Node +type case = { + description: string, + expected: JSON.t, + input: JSON.t, +} + @module("toml") external parseToml: string => dict<'a> = "parse" -let getValidCases = (slug: string) => { +let getValidCases = (slug: string): array => { let __dirname = dirname(fileURLToPath(%raw("import.meta.url"))) let tomlPath = join([__dirname, "..", "exercises", "practice", slug, ".meta", "tests.toml"]) @@ -19,7 +25,7 @@ let getValidCases = (slug: string) => { 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(` + let extractCases: (JSON.t, dict<'a>) => array = %raw(` (data, meta) => { const validCases = []; const extract = (cases) => { From 94ed34acb5ee6b2441cb4a8b7a6d7aeda25ff69d Mon Sep 17 00:00:00 2001 From: Owen Date: Mon, 16 Mar 2026 11:24:40 +0100 Subject: [PATCH 05/26] include resolve4 --- test_generator/Node.res | 1 + 1 file changed, 1 insertion(+) diff --git a/test_generator/Node.res b/test_generator/Node.res index b808b23..1e990c0 100644 --- a/test_generator/Node.res +++ b/test_generator/Node.res @@ -2,6 +2,7 @@ @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" From 57b693ca4a7640ad934aed21107b161fa3ffe7b1 Mon Sep 17 00:00:00 2001 From: Owen Date: Mon, 16 Mar 2026 11:25:01 +0100 Subject: [PATCH 06/26] only export generateTests --- test_generator/TestGenerator.resi | 1 + 1 file changed, 1 insertion(+) create mode 100644 test_generator/TestGenerator.resi diff --git a/test_generator/TestGenerator.resi b/test_generator/TestGenerator.resi new file mode 100644 index 0000000..ab079f5 --- /dev/null +++ b/test_generator/TestGenerator.resi @@ -0,0 +1 @@ +let generateTests: (string, string, array, GetCases.case => string) => unit From 46c3496eb121a542788a55c08ef591d3d0844222 Mon Sep 17 00:00:00 2001 From: Owen Date: Mon, 16 Mar 2026 11:25:20 +0100 Subject: [PATCH 07/26] remove commented code --- test_generator/testGenerator.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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`); From 08f4c5c956ecb0126583963b743db7cef3e48807 Mon Sep 17 00:00:00 2001 From: Owen Date: Mon, 16 Mar 2026 11:27:07 +0100 Subject: [PATCH 08/26] convert generateTests to rescript --- test_generator/TestGenerator.res | 50 ++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 test_generator/TestGenerator.res diff --git a/test_generator/TestGenerator.res b/test_generator/TestGenerator.res new file mode 100644 index 0000000..830cba7 --- /dev/null +++ b/test_generator/TestGenerator.res @@ -0,0 +1,50 @@ +open Node + +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, assertionFunctions, template) => { + let moduleName = toPascalCase(slug) + let cases = GetCases.getValidCases(slug) + let lastCaseIndex = Array.length(cases) - 1 + + let output = ref(`open Test\nopen ${moduleName}\n\n`) + + if Array.isArray(assertionFunctions) { + let assertionsStr = assertionFunctions->Array.map(fn => String.trim(fn))->Array.join("\n\n") + output := output.contents ++ assertionsStr ++ "\n\n" + } + + 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 = (dir, slug, assertionFunctions, template) => { + resolve4(dir, "..", "tests", `${toPascalCase(slug)}_test.res}`)->generate( + slug, + assertionFunctions, + template, + ) +} From 55c894d210d29a42ac6d204d7179a887ec1dfa8d Mon Sep 17 00:00:00 2001 From: Owen Date: Mon, 16 Mar 2026 11:34:08 +0100 Subject: [PATCH 09/26] change test case formatting --- test_generator/TestGenerator.res | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/test_generator/TestGenerator.res b/test_generator/TestGenerator.res index 830cba7..fbbb83f 100644 --- a/test_generator/TestGenerator.res +++ b/test_generator/TestGenerator.res @@ -24,11 +24,7 @@ let generate = (outputPath, slug, assertionFunctions, template) => { let testContent = template(c) let spacing = index == lastCaseIndex ? "\n" : "\n\n" - output := - output.contents ++ - `test("${description}", () => { - ${testContent} - })${spacing}` + output := output.contents ++ `test("${description}", () => {${testContent}})${spacing}` }) let dir = dirname(outputPath) @@ -42,7 +38,7 @@ let generate = (outputPath, slug, assertionFunctions, template) => { } let generateTests = (dir, slug, assertionFunctions, template) => { - resolve4(dir, "..", "tests", `${toPascalCase(slug)}_test.res}`)->generate( + resolve4(dir, "..", "tests", `${toPascalCase(slug)}_test.res`)->generate( slug, assertionFunctions, template, From 0378ab71be772474dd8a1c87af0bddb657c8715f Mon Sep 17 00:00:00 2001 From: Owen Date: Mon, 16 Mar 2026 11:35:45 +0100 Subject: [PATCH 10/26] change test generator import --- exercises/practice/hello-world/.meta/testTemplate.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/hello-world/.meta/testTemplate.js b/exercises/practice/hello-world/.meta/testTemplate.js index 0db698e..76befb1 100644 --- a/exercises/practice/hello-world/.meta/testTemplate.js +++ b/exercises/practice/hello-world/.meta/testTemplate.js @@ -1,7 +1,7 @@ import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { stringEqual } from "../../../../test_generator/assertions.js"; -import { generateTests } from '../../../../test_generator/testGenerator.js'; +import { generateTests } from '../../../../test_generator/TestGenerator.res.js'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const slug = path.basename(path.resolve(__dirname, '..')) From 325729074a583e0dd7adc149a8cf6bb7a307444c Mon Sep 17 00:00:00 2001 From: Owen Date: Mon, 16 Mar 2026 14:59:09 +0100 Subject: [PATCH 11/26] generate hello world tests --- .../hello-world/tests/HelloWorld_test.res | 7 ++--- rescript.json | 3 ++- test_generator/Assertions.res | 27 +++++++++++++++++++ test_generator/GetCases.res | 15 ++++++++--- test_generator/TestGenerator.res | 20 +++++--------- test_generator/TestGenerator.resi | 2 +- test_templates/HelloWorldTemplate.res | 14 ++++++++++ 7 files changed, 64 insertions(+), 24 deletions(-) create mode 100644 test_generator/Assertions.res create mode 100644 test_templates/HelloWorldTemplate.res diff --git a/exercises/practice/hello-world/tests/HelloWorld_test.res b/exercises/practice/hello-world/tests/HelloWorld_test.res index 634d7cd..7aa8374 100644 --- a/exercises/practice/hello-world/tests/HelloWorld_test.res +++ b/exercises/practice/hello-world/tests/HelloWorld_test.res @@ -1,8 +1,5 @@ open Test +open Assertions open HelloWorld -let stringEqual = (~message=?, a: string, b: string) => assertion(~message?, ~operator="stringEqual", (a, b) => a == b, a, b) - -test("Say Hi!", () => { - stringEqual(~message="Say Hi!", hello(), "Hello, World!") -}) +test("Say Hi!", () => {assertEqual(~message="Say Hi!", hello(), "Hello, World!")}) diff --git a/rescript.json b/rescript.json index bcc49b4..0b7a23a 100644 --- a/rescript.json +++ b/rescript.json @@ -3,7 +3,8 @@ "sources": [ { "dir": "tmp/src", "subdirs": true, "type": "dev" }, { "dir": "tmp/tests", "subdirs": true, "type": "dev" }, - { "dir": "test_generator", "subdirs": true, "type": "dev" } + { "dir": "test_generator", "subdirs": true, "type": "dev" }, + { "dir": "test_templates", "subdirs": true, "type": "dev" } ], "package-specs": [ { diff --git a/test_generator/Assertions.res b/test_generator/Assertions.res new file mode 100644 index 0000000..d3a8913 --- /dev/null +++ b/test_generator/Assertions.res @@ -0,0 +1,27 @@ +open Test + +let assertEqual = (~message=?, a, b) => + assertion(~message?, ~operator="assertEqual", (a, b) => a == b, a, b) + +// This returns the string of code the user actually sees in the test +let genAssertEqual = (~message, ~actual, ~expected) => { + `assertEqual(~message="${message}", ${actual}, ${expected})` +} + +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) +} + +/** * Asserts that two floats are equal within a specified precision. + * The `digits` argument determines the tolerance (10^-digits). + * Defaults to 2 decimal places (0.01 tolerance). + */ +let floatEqual = (~message=?, ~digits=2, a: float, b: float) => { + let tolerance = 10.0 ** -.Float.fromInt(digits) + assertion(~message?, ~operator="floatEqual", (a, b) => Math.abs(a -. b) <= tolerance, a, b) +} diff --git a/test_generator/GetCases.res b/test_generator/GetCases.res index e738e16..3cd3720 100644 --- a/test_generator/GetCases.res +++ b/test_generator/GetCases.res @@ -11,10 +11,12 @@ type case = { let getValidCases = (slug: string): array => { let __dirname = dirname(fileURLToPath(%raw("import.meta.url"))) - let tomlPath = join([__dirname, "..", "exercises", "practice", slug, ".meta", "tests.toml"]) + let projectRoot = resolve(__dirname, "..") + + let tomlPath = join([projectRoot, "exercises", "practice", slug, ".meta", "tests.toml"]) + let jsonPath = join([ - __dirname, - "..", + projectRoot, "problem-specifications", "exercises", slug, @@ -36,7 +38,12 @@ let getValidCases = (slug: string): array => { const { uuid } = testCase; const m = meta[uuid]; if (uuid && m && m.include !== false) { - validCases.push(testCase); + // validCases.push(testCase); + validCases.push({ + description: testCase.description, + expected: testCase.expected, + input: testCase.input + }); } } } diff --git a/test_generator/TestGenerator.res b/test_generator/TestGenerator.res index fbbb83f..f5831d7 100644 --- a/test_generator/TestGenerator.res +++ b/test_generator/TestGenerator.res @@ -7,17 +7,12 @@ let toPascalCase = slug => { ->Array.join("") } -let generate = (outputPath, slug, assertionFunctions, template) => { +let generate = (outputPath, slug, template) => { let moduleName = toPascalCase(slug) let cases = GetCases.getValidCases(slug) let lastCaseIndex = Array.length(cases) - 1 - let output = ref(`open Test\nopen ${moduleName}\n\n`) - - if Array.isArray(assertionFunctions) { - let assertionsStr = assertionFunctions->Array.map(fn => String.trim(fn))->Array.join("\n\n") - output := output.contents ++ assertionsStr ++ "\n\n" - } + let output = ref(`open Test\nopen Assertions\nopen ${moduleName}\n\n`) cases->Array.forEachWithIndex((c, index) => { let {description} = c @@ -37,10 +32,9 @@ let generate = (outputPath, slug, assertionFunctions, template) => { Console.log(`Generated: ${basename(outputPath)}`) } -let generateTests = (dir, slug, assertionFunctions, template) => { - resolve4(dir, "..", "tests", `${toPascalCase(slug)}_test.res`)->generate( - slug, - assertionFunctions, - template, - ) +let generateTests = (slug, template) => { + 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) } diff --git a/test_generator/TestGenerator.resi b/test_generator/TestGenerator.resi index ab079f5..4b483e8 100644 --- a/test_generator/TestGenerator.resi +++ b/test_generator/TestGenerator.resi @@ -1 +1 @@ -let generateTests: (string, string, array, GetCases.case => string) => unit +let generateTests: (string, GetCases.case => string) => unit diff --git a/test_templates/HelloWorldTemplate.res b/test_templates/HelloWorldTemplate.res new file mode 100644 index 0000000..1903f10 --- /dev/null +++ b/test_templates/HelloWorldTemplate.res @@ -0,0 +1,14 @@ +open HelloWorld + +// let slug = basename(resolve(__dirname, "..")) +let slug = "hello-world" + +// 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) + Assertions.genAssertEqual(~message=case.description, ~actual="hello()", ~expected=expectedStr) +} + +TestGenerator.generateTests(slug, template) From 25b496b7624dda2ad743981d8c859a7b05d45d55 Mon Sep 17 00:00:00 2001 From: Owen Date: Mon, 16 Mar 2026 15:48:56 +0100 Subject: [PATCH 12/26] find input from JSON --- test_generator/Utils.res | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 test_generator/Utils.res diff --git a/test_generator/Utils.res b/test_generator/Utils.res new file mode 100644 index 0000000..f1ba0b5 --- /dev/null +++ b/test_generator/Utils.res @@ -0,0 +1,3 @@ +let getTestCaseInput = (case: GetCases.case, inputName: string) => { + case.input->JSON.Decode.object->Option.getOrThrow->Dict.get(inputName)->Option.getOrThrow +} From 6b5290e28939fb0abfb1b61e7b83c149617a97bb Mon Sep 17 00:00:00 2001 From: Owen Date: Mon, 16 Mar 2026 15:49:13 +0100 Subject: [PATCH 13/26] update test --- .../practice/hello-world/tests/HelloWorld_test.res | 3 +-- test_templates/HelloWorld_template.res | 11 +++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 test_templates/HelloWorld_template.res diff --git a/exercises/practice/hello-world/tests/HelloWorld_test.res b/exercises/practice/hello-world/tests/HelloWorld_test.res index 7aa8374..9c8f80b 100644 --- a/exercises/practice/hello-world/tests/HelloWorld_test.res +++ b/exercises/practice/hello-world/tests/HelloWorld_test.res @@ -1,5 +1,4 @@ open Test -open Assertions open HelloWorld -test("Say Hi!", () => {assertEqual(~message="Say Hi!", hello(), "Hello, World!")}) +test("Say Hi!", () => {Assertions.assertEqual(~message="Say Hi!", hello(), "Hello, World!")}) diff --git a/test_templates/HelloWorld_template.res b/test_templates/HelloWorld_template.res new file mode 100644 index 0000000..08f8d2c --- /dev/null +++ b/test_templates/HelloWorld_template.res @@ -0,0 +1,11 @@ +let slug = "hello-world" + +// 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) + Assertions.genAssertEqual(~message=case.description, ~actual="hello()", ~expected=expectedStr) +} + +TestGenerator.generateTests(slug, template) From a8d54d1190a8e6cb641a192d645ba30b6ba162bd Mon Sep 17 00:00:00 2001 From: Owen Date: Mon, 16 Mar 2026 15:49:31 +0100 Subject: [PATCH 14/26] acronym test gen --- .../practice/acronym/tests/Acronym_test.res | 38 +++++-------------- .../hello-world/.meta/testTemplate.js | 17 --------- test_generator/Assertions.res | 2 +- test_generator/TestGenerator.res | 2 +- ...WorldTemplate.res => Acronym_template.res} | 15 +++++--- 5 files changed, 20 insertions(+), 54 deletions(-) delete mode 100644 exercises/practice/hello-world/.meta/testTemplate.js rename test_templates/{HelloWorldTemplate.res => Acronym_template.res} (50%) diff --git a/exercises/practice/acronym/tests/Acronym_test.res b/exercises/practice/acronym/tests/Acronym_test.res index c119a20..d045885 100644 --- a/exercises/practice/acronym/tests/Acronym_test.res +++ b/exercises/practice/acronym/tests/Acronym_test.res @@ -1,40 +1,20 @@ open Test open Acronym -let stringEqual = (~message=?, a: string, b: string) => assertion(~message?, ~operator="stringEqual", (a, b) => a == b, a, b) +test("basic", () => {Assertions.assertEqual(~message="basic", abbreviate("Portable Network Graphics"), "PNG")}) -test("basic", () => { - stringEqual(~message="basic", abbreviate("Portable Network Graphics"), "PNG") -}) +test("lowercase words", () => {Assertions.assertEqual(~message="lowercase words", abbreviate("Ruby on Rails"), "ROR")}) -test("lowercase words", () => { - stringEqual(~message="lowercase words", abbreviate("Ruby on Rails"), "ROR") -}) +test("punctuation", () => {Assertions.assertEqual(~message="punctuation", abbreviate("First In, First Out"), "FIFO")}) -test("punctuation", () => { - stringEqual(~message="punctuation", abbreviate("First In, First Out"), "FIFO") -}) +test("all caps word", () => {Assertions.assertEqual(~message="all caps word", abbreviate("GNU Image Manipulation Program"), "GIMP")}) -test("all caps word", () => { - stringEqual(~message="all caps word", abbreviate("GNU Image Manipulation Program"), "GIMP") -}) +test("punctuation without whitespace", () => {Assertions.assertEqual(~message="punctuation without whitespace", abbreviate("Complementary metal-oxide semiconductor"), "CMOS")}) -test("punctuation without whitespace", () => { - stringEqual(~message="punctuation without whitespace", abbreviate("Complementary metal-oxide semiconductor"), "CMOS") -}) +test("very long abbreviation", () => {Assertions.assertEqual(~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", () => { - stringEqual(~message="very long abbreviation", abbreviate("Rolling On The Floor Laughing So Hard That My Dogs Came Over And Licked Me"), "ROTFLSHTMDCOALM") -}) +test("consecutive delimiters", () => {Assertions.assertEqual(~message="consecutive delimiters", abbreviate("Something - I made up from thin air"), "SIMUFTA")}) -test("consecutive delimiters", () => { - stringEqual(~message="consecutive delimiters", abbreviate("Something - I made up from thin air"), "SIMUFTA") -}) +test("apostrophes", () => {Assertions.assertEqual(~message="apostrophes", abbreviate("Halley's Comet"), "HC")}) -test("apostrophes", () => { - stringEqual(~message="apostrophes", abbreviate("Halley's Comet"), "HC") -}) - -test("underscore emphasis", () => { - stringEqual(~message="underscore emphasis", abbreviate("The Road _Not_ Taken"), "TRNT") -}) +test("underscore emphasis", () => {Assertions.assertEqual(~message="underscore emphasis", abbreviate("The Road _Not_ Taken"), "TRNT")}) diff --git a/exercises/practice/hello-world/.meta/testTemplate.js b/exercises/practice/hello-world/.meta/testTemplate.js deleted file mode 100644 index 76befb1..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.res.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/test_generator/Assertions.res b/test_generator/Assertions.res index d3a8913..ff57c1b 100644 --- a/test_generator/Assertions.res +++ b/test_generator/Assertions.res @@ -5,7 +5,7 @@ let assertEqual = (~message=?, a, b) => // This returns the string of code the user actually sees in the test let genAssertEqual = (~message, ~actual, ~expected) => { - `assertEqual(~message="${message}", ${actual}, ${expected})` + `Assertions.assertEqual(~message="${message}", ${actual}, ${expected})` } let dictEqual = (~message=?, a: Dict.t<'a>, b: Dict.t<'a>) => { diff --git a/test_generator/TestGenerator.res b/test_generator/TestGenerator.res index f5831d7..c13416e 100644 --- a/test_generator/TestGenerator.res +++ b/test_generator/TestGenerator.res @@ -12,7 +12,7 @@ let generate = (outputPath, slug, template) => { let cases = GetCases.getValidCases(slug) let lastCaseIndex = Array.length(cases) - 1 - let output = ref(`open Test\nopen Assertions\nopen ${moduleName}\n\n`) + let output = ref(`open Test\nopen ${moduleName}\n\n`) cases->Array.forEachWithIndex((c, index) => { let {description} = c diff --git a/test_templates/HelloWorldTemplate.res b/test_templates/Acronym_template.res similarity index 50% rename from test_templates/HelloWorldTemplate.res rename to test_templates/Acronym_template.res index 1903f10..be73290 100644 --- a/test_templates/HelloWorldTemplate.res +++ b/test_templates/Acronym_template.res @@ -1,14 +1,17 @@ -open HelloWorld +let slug = "acronym" -// let slug = basename(resolve(__dirname, "..")) -let slug = "hello-world" - -// EDIT THIS WITH YOUR TEST TEMPLATES 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="hello()", ~expected=expectedStr) + Assertions.genAssertEqual( + ~message=case.description, + ~actual=`abbreviate(${phrase})`, + ~expected=expectedStr, + ) } TestGenerator.generateTests(slug, template) From 9713b0c1fad2c339d9870579d66050690a7536b5 Mon Sep 17 00:00:00 2001 From: Owen Date: Mon, 16 Mar 2026 16:01:53 +0100 Subject: [PATCH 15/26] test template and copy --- Makefile | 28 ++++++++++++------- .../practice/acronym/.meta/testTemplate.js | 17 ----------- templates/Test_template.res | 23 +++++++++++++++ 3 files changed, 41 insertions(+), 27 deletions(-) delete mode 100644 exercises/practice/acronym/.meta/testTemplate.js create mode 100644 templates/Test_template.res 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/templates/Test_template.res b/templates/Test_template.res new file mode 100644 index 0000000..e80fb09 --- /dev/null +++ b/templates/Test_template.res @@ -0,0 +1,23 @@ +// UNCOMMENT CODE + +// EDIT THIS WITH THE EXERCISE SLUG, eg. all-your-base +let slug = "exercise-name" + +// REMOVE WHEN IMPLEMENTING TEST +panic("test not yet implemented") + +// 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) From 767c7daefe501811a916e270cbf215ce3d0ea05f Mon Sep 17 00:00:00 2001 From: Owen Date: Mon, 16 Mar 2026 16:04:32 +0100 Subject: [PATCH 16/26] rework Bob tests --- exercises/practice/bob/.meta/testTemplate.js | 17 --- exercises/practice/bob/tests/Bob_test.res | 151 ++++++------------- test_templates/Bob_template.res | 17 +++ 3 files changed, 65 insertions(+), 120 deletions(-) delete mode 100644 exercises/practice/bob/.meta/testTemplate.js create mode 100644 test_templates/Bob_template.res 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..ac7ba58 100644 --- a/exercises/practice/bob/tests/Bob_test.res +++ b/exercises/practice/bob/tests/Bob_test.res @@ -1,107 +1,52 @@ open Test open Bob -let assertEqual = (~message=?, a: 'a, b: 'a) => assertion(~message?, ~operator="assertEqual", (a, b) => a == b, a, b) +test("stating something", () => {Assertions.assertEqual(~message="stating something", response("Tom-ay-to, tom-aaaah-to."), "Whatever.")}) -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("shouting", () => {Assertions.assertEqual(~message="shouting", response("WATCH OUT!"), "Whoa, chill out!")}) + +test("shouting gibberish", () => {Assertions.assertEqual(~message="shouting gibberish", response("FCECDFCAAB"), "Whoa, chill out!")}) + +test("asking a question", () => {Assertions.assertEqual(~message="asking a question", response("Does this cryogenic chamber make me look fat?"), "Sure.")}) + +test("asking a numeric question", () => {Assertions.assertEqual(~message="asking a numeric question", response("You are, what, like 15?"), "Sure.")}) + +test("asking gibberish", () => {Assertions.assertEqual(~message="asking gibberish", response("fffbbcbeab?"), "Sure.")}) + +test("talking forcefully", () => {Assertions.assertEqual(~message="talking forcefully", response("Hi there!"), "Whatever.")}) + +test("using acronyms in regular speech", () => {Assertions.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", () => {Assertions.assertEqual(~message="forceful question", response("WHAT'S GOING ON?"), "Calm down, I know what I'm doing!")}) + +test("shouting numbers", () => {Assertions.assertEqual(~message="shouting numbers", response("1, 2, 3 GO!"), "Whoa, chill out!")}) + +test("no letters", () => {Assertions.assertEqual(~message="no letters", response("1, 2, 3"), "Whatever.")}) + +test("question with no letters", () => {Assertions.assertEqual(~message="question with no letters", response("4?"), "Sure.")}) + +test("shouting with special characters", () => {Assertions.assertEqual(~message="shouting with special characters", response("ZOMG THE %^*@#$(*^ ZOMBIES ARE COMING!!11!!1!"), "Whoa, chill out!")}) + +test("shouting with no exclamation mark", () => {Assertions.assertEqual(~message="shouting with no exclamation mark", response("I HATE THE DENTIST"), "Whoa, chill out!")}) + +test("statement containing question mark", () => {Assertions.assertEqual(~message="statement containing question mark", response("Ending with ? means a question."), "Whatever.")}) + +test("non-letters with question", () => {Assertions.assertEqual(~message="non-letters with question", response(":) ?"), "Sure.")}) + +test("prattling on", () => {Assertions.assertEqual(~message="prattling on", response("Wait! Hang on. Are you going to be OK?"), "Sure.")}) + +test("silence", () => {Assertions.assertEqual(~message="silence", response(""), "Fine. Be that way!")}) + +test("prolonged silence", () => {Assertions.assertEqual(~message="prolonged silence", response(" "), "Fine. Be that way!")}) + +test("alternate silence", () => {Assertions.assertEqual(~message="alternate silence", response("\t\t\t\t\t\t\t\t\t\t"), "Fine. Be that way!")}) + +test("starting with whitespace", () => {Assertions.assertEqual(~message="starting with whitespace", response(" hmmmmmmm..."), "Whatever.")}) + +test("ending with whitespace", () => {Assertions.assertEqual(~message="ending with whitespace", response("Okay if like my spacebar quite a bit? "), "Sure.")}) + +test("other whitespace", () => {Assertions.assertEqual(~message="other whitespace", response("\n\r \t"), "Fine. Be that way!")}) + +test("non-question ending with whitespace", () => {Assertions.assertEqual(~message="non-question ending with whitespace", response("This is a statement ending with whitespace "), "Whatever.")}) + +test("multiple line question", () => {Assertions.assertEqual(~message="multiple line question", response("\nDoes this cryogenic chamber make\n me look fat?"), "Sure.")}) diff --git a/test_templates/Bob_template.res b/test_templates/Bob_template.res new file mode 100644 index 0000000..07117ba --- /dev/null +++ b/test_templates/Bob_template.res @@ -0,0 +1,17 @@ +let slug = "bob" + +let template = (case: GetCases.case) => { + let expectedStr = JSON.stringify(case.expected) + + let input = Utils.getTestCaseInput(case, "heyBob") + 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=`response(${phrase})`, + ~expected=expectedStr, + ) +} + +TestGenerator.generateTests(slug, template) From d199458f9c1193c62f947a3501498833d5571997 Mon Sep 17 00:00:00 2001 From: Owen Date: Mon, 16 Mar 2026 16:14:59 +0100 Subject: [PATCH 17/26] update All Your Base --- .../all-your-base/tests/AllYourBase_test.res | 86 +++++-------------- 1 file changed, 21 insertions(+), 65 deletions(-) diff --git a/exercises/practice/all-your-base/tests/AllYourBase_test.res b/exercises/practice/all-your-base/tests/AllYourBase_test.res index 01bd9ed..0c8da18 100644 --- a/exercises/practice/all-your-base/tests/AllYourBase_test.res +++ b/exercises/practice/all-your-base/tests/AllYourBase_test.res @@ -1,88 +1,44 @@ open Test open AllYourBase -let assertEqual = (~message=?, a: 'a, b: 'a) => assertion(~message?, ~operator="assertEqual", (a, b) => a == b, a, b) +test("single bit one to decimal", () => {Assertions.assertEqual(~message="single bit one to decimal", rebase(2, [1], 10), Some([1]))}) -test("single bit one to decimal", () => { - assertEqual(~message="single bit one to decimal", rebase(2, [1], 10), Some([1])) -}) +test("binary to single decimal", () => {Assertions.assertEqual(~message="binary to single decimal", rebase(2, [1,0,1], 10), Some([5]))}) -test("binary to single decimal", () => { - assertEqual(~message="binary to single decimal", rebase(2, [1, 0, 1], 10), Some([5])) -}) +test("single decimal to binary", () => {Assertions.assertEqual(~message="single decimal to binary", rebase(10, [5], 2), Some([1,0,1]))}) -test("single decimal to binary", () => { - assertEqual(~message="single decimal to binary", rebase(10, [5], 2), Some([1, 0, 1])) -}) +test("binary to multiple decimal", () => {Assertions.assertEqual(~message="binary to multiple decimal", rebase(2, [1,0,1,0,1,0], 10), Some([4,2]))}) -test("binary to multiple decimal", () => { - assertEqual(~message="binary to multiple decimal", rebase(2, [1, 0, 1, 0, 1, 0], 10), Some([4, 2])) -}) +test("decimal to binary", () => {Assertions.assertEqual(~message="decimal to binary", rebase(10, [4,2], 2), Some([1,0,1,0,1,0]))}) -test("decimal to binary", () => { - assertEqual(~message="decimal to binary", rebase(10, [4, 2], 2), Some([1, 0, 1, 0, 1, 0])) -}) +test("trinary to hexadecimal", () => {Assertions.assertEqual(~message="trinary to hexadecimal", rebase(3, [1,1,2,0], 16), Some([2,10]))}) -test("trinary to hexadecimal", () => { - assertEqual(~message="trinary to hexadecimal", rebase(3, [1, 1, 2, 0], 16), Some([2, 10])) -}) +test("hexadecimal to trinary", () => {Assertions.assertEqual(~message="hexadecimal to trinary", rebase(16, [2,10], 3), Some([1,1,2,0]))}) -test("hexadecimal to trinary", () => { - assertEqual(~message="hexadecimal to trinary", rebase(16, [2, 10], 3), Some([1, 1, 2, 0])) -}) +test("15-bit integer", () => {Assertions.assertEqual(~message="15-bit integer", rebase(97, [3,46,60], 73), Some([6,10,45]))}) -test("15-bit integer", () => { - assertEqual(~message="15-bit integer", rebase(97, [3, 46, 60], 73), Some([6, 10, 45])) -}) +test("empty list", () => {Assertions.assertEqual(~message="empty list", rebase(2, [], 10), Some([0]))}) -test("empty list", () => { - assertEqual(~message="empty list", rebase(2, [], 10), Some([0])) -}) +test("single zero", () => {Assertions.assertEqual(~message="single zero", rebase(10, [0], 2), Some([0]))}) -test("single zero", () => { - assertEqual(~message="single zero", rebase(10, [0], 2), Some([0])) -}) +test("multiple zeros", () => {Assertions.assertEqual(~message="multiple zeros", rebase(10, [0,0,0], 2), Some([0]))}) -test("multiple zeros", () => { - assertEqual(~message="multiple zeros", rebase(10, [0, 0, 0], 2), Some([0])) -}) +test("leading zeros", () => {Assertions.assertEqual(~message="leading zeros", rebase(7, [0,6,0], 10), Some([4,2]))}) -test("leading zeros", () => { - assertEqual(~message="leading zeros", rebase(7, [0, 6, 0], 10), Some([4, 2])) -}) +test("input base is one", () => {Assertions.assertEqual(~message="input base is one", rebase(1, [0], 10), None)}) -test("input base is one", () => { - assertEqual(~message="input base is one", rebase(1, [0], 10), None) -}) +test("input base is zero", () => {Assertions.assertEqual(~message="input base is zero", rebase(0, [], 10), None)}) -test("input base is zero", () => { - assertEqual(~message="input base is zero", rebase(0, [], 10), None) -}) +test("input base is negative", () => {Assertions.assertEqual(~message="input base is negative", rebase(-2, [1], 10), None)}) -test("input base is negative", () => { - assertEqual(~message="input base is negative", rebase(-2, [1], 10), None) -}) +test("negative digit", () => {Assertions.assertEqual(~message="negative digit", rebase(2, [1,-1,1,0,1,0], 10), None)}) -test("negative digit", () => { - assertEqual(~message="negative digit", rebase(2, [1, -1, 1, 0, 1, 0], 10), None) -}) +test("invalid positive digit", () => {Assertions.assertEqual(~message="invalid positive digit", rebase(2, [1,2,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("output base is one", () => {Assertions.assertEqual(~message="output base is one", rebase(2, [1,0,1,0,1,0], 1), 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 zero", () => {Assertions.assertEqual(~message="output base is zero", rebase(10, [7], 0), None)}) -test("output base is zero", () => { - assertEqual(~message="output base is zero", rebase(10, [7], 0), None) -}) +test("output base is negative", () => {Assertions.assertEqual(~message="output base is negative", rebase(2, [1], -7), None)}) -test("output base is negative", () => { - assertEqual(~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", () => {Assertions.assertEqual(~message="both bases are negative", rebase(-2, [1], -7), None)}) From a543d27fc43a3764fe7893c2215bc6146d98d79a Mon Sep 17 00:00:00 2001 From: Owen Date: Mon, 16 Mar 2026 16:15:27 +0100 Subject: [PATCH 18/26] update JSON stringification of input --- test_generator/Utils.res | 7 ++++++- test_templates/Acronym_template.res | 3 +-- test_templates/AllYourBase_template.res | 18 ++++++++++++++++++ test_templates/Bob_template.res | 3 +-- 4 files changed, 26 insertions(+), 5 deletions(-) create mode 100644 test_templates/AllYourBase_template.res diff --git a/test_generator/Utils.res b/test_generator/Utils.res index f1ba0b5..740c0e7 100644 --- a/test_generator/Utils.res +++ b/test_generator/Utils.res @@ -1,3 +1,8 @@ let getTestCaseInput = (case: GetCases.case, inputName: string) => { - case.input->JSON.Decode.object->Option.getOrThrow->Dict.get(inputName)->Option.getOrThrow + case.input + ->JSON.Decode.object + ->Option.getOrThrow + ->Dict.get(inputName) + ->Option.getOrThrow + ->JSON.stringify } diff --git a/test_templates/Acronym_template.res b/test_templates/Acronym_template.res index be73290..c9a0174 100644 --- a/test_templates/Acronym_template.res +++ b/test_templates/Acronym_template.res @@ -3,8 +3,7 @@ let slug = "acronym" let template = (case: GetCases.case) => { let expectedStr = JSON.stringify(case.expected) - let input = Utils.getTestCaseInput(case, "phrase") - let phrase = JSON.stringify(input) + let phrase = Utils.getTestCaseInput(case, "phrase") // EDIT THIS WITH YOUR ASSERTIONS (use genAssert... name to generate an assertion in the template) Assertions.genAssertEqual( diff --git a/test_templates/AllYourBase_template.res b/test_templates/AllYourBase_template.res new file mode 100644 index 0000000..0e141c5 --- /dev/null +++ b/test_templates/AllYourBase_template.res @@ -0,0 +1,18 @@ +let slug = "all-your-base" + +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 (use genAssert... name to generate an assertion in the template) + Assertions.genAssertEqual( + ~message=case.description, + ~actual=`rebase(${inputBase}, ${digits}, ${outputBase})`, + ~expected=expectedStr, + ) +} + +TestGenerator.generateTests(slug, template) diff --git a/test_templates/Bob_template.res b/test_templates/Bob_template.res index 07117ba..03d5e97 100644 --- a/test_templates/Bob_template.res +++ b/test_templates/Bob_template.res @@ -3,8 +3,7 @@ let slug = "bob" let template = (case: GetCases.case) => { let expectedStr = JSON.stringify(case.expected) - let input = Utils.getTestCaseInput(case, "heyBob") - let phrase = JSON.stringify(input) + let phrase = Utils.getTestCaseInput(case, "heyBob") // EDIT THIS WITH YOUR ASSERTIONS (use genAssert... name to generate an assertion in the template) Assertions.genAssertEqual( From 2370a385a40d2af126ef9d22630436412ad59033 Mon Sep 17 00:00:00 2001 From: Owen Date: Mon, 16 Mar 2026 21:47:43 +0100 Subject: [PATCH 19/26] inject assertions into test files --- .../practice/acronym/tests/Acronym_test.res | 20 +++---- .../all-your-base/tests/AllYourBase_test.res | 44 ++++++++-------- exercises/practice/bob/tests/Bob_test.res | 52 ++++++++++--------- .../hello-world/tests/HelloWorld_test.res | 4 +- test_generator/AssertionGenerators.res | 5 ++ test_generator/Assertions.res | 8 +-- test_generator/TestGenerator.res | 25 +++++++-- test_generator/TestGenerator.resi | 4 +- test_templates/Acronym_template.res | 6 +-- test_templates/AllYourBase_template.res | 7 +-- test_templates/Bob_template.res | 4 +- test_templates/HelloWorld_template.res | 4 +- 12 files changed, 106 insertions(+), 77 deletions(-) create mode 100644 test_generator/AssertionGenerators.res diff --git a/exercises/practice/acronym/tests/Acronym_test.res b/exercises/practice/acronym/tests/Acronym_test.res index d045885..274653b 100644 --- a/exercises/practice/acronym/tests/Acronym_test.res +++ b/exercises/practice/acronym/tests/Acronym_test.res @@ -1,20 +1,22 @@ open Test open Acronym -test("basic", () => {Assertions.assertEqual(~message="basic", abbreviate("Portable Network Graphics"), "PNG")}) +let equal = (~message=?, a, b) => assertion(~message?, ~operator="equal", (a, b) => a == b, a, b) -test("lowercase words", () => {Assertions.assertEqual(~message="lowercase words", abbreviate("Ruby on Rails"), "ROR")}) +test("basic", () => {equal(~message="basic", abbreviate("Portable Network Graphics"), "PNG")}) -test("punctuation", () => {Assertions.assertEqual(~message="punctuation", abbreviate("First In, First Out"), "FIFO")}) +test("lowercase words", () => {equal(~message="lowercase words", abbreviate("Ruby on Rails"), "ROR")}) -test("all caps word", () => {Assertions.assertEqual(~message="all caps word", abbreviate("GNU Image Manipulation Program"), "GIMP")}) +test("punctuation", () => {equal(~message="punctuation", abbreviate("First In, First Out"), "FIFO")}) -test("punctuation without whitespace", () => {Assertions.assertEqual(~message="punctuation without whitespace", abbreviate("Complementary metal-oxide semiconductor"), "CMOS")}) +test("all caps word", () => {equal(~message="all caps word", abbreviate("GNU Image Manipulation Program"), "GIMP")}) -test("very long abbreviation", () => {Assertions.assertEqual(~message="very long abbreviation", abbreviate("Rolling On The Floor Laughing So Hard That My Dogs Came Over And Licked Me"), "ROTFLSHTMDCOALM")}) +test("punctuation without whitespace", () => {equal(~message="punctuation without whitespace", abbreviate("Complementary metal-oxide semiconductor"), "CMOS")}) -test("consecutive delimiters", () => {Assertions.assertEqual(~message="consecutive delimiters", abbreviate("Something - I made up from thin air"), "SIMUFTA")}) +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("apostrophes", () => {Assertions.assertEqual(~message="apostrophes", abbreviate("Halley's Comet"), "HC")}) +test("consecutive delimiters", () => {equal(~message="consecutive delimiters", abbreviate("Something - I made up from thin air"), "SIMUFTA")}) -test("underscore emphasis", () => {Assertions.assertEqual(~message="underscore emphasis", abbreviate("The Road _Not_ Taken"), "TRNT")}) +test("apostrophes", () => {equal(~message="apostrophes", abbreviate("Halley's Comet"), "HC")}) + +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 0c8da18..7832f5c 100644 --- a/exercises/practice/all-your-base/tests/AllYourBase_test.res +++ b/exercises/practice/all-your-base/tests/AllYourBase_test.res @@ -1,44 +1,46 @@ open Test open AllYourBase -test("single bit one to decimal", () => {Assertions.assertEqual(~message="single bit one to decimal", rebase(2, [1], 10), Some([1]))}) +let equal = (~message=?, a, b) => assertion(~message?, ~operator="equal", (a, b) => a == b, a, b) -test("binary to single decimal", () => {Assertions.assertEqual(~message="binary to single decimal", rebase(2, [1,0,1], 10), Some([5]))}) +test("single bit one to decimal", () => {equal(~message="single bit one to decimal", rebase(2, [1], 10), Some([1]))}) -test("single decimal to binary", () => {Assertions.assertEqual(~message="single decimal to binary", rebase(10, [5], 2), Some([1,0,1]))}) +test("binary to single decimal", () => {equal(~message="binary to single decimal", rebase(2, [1,0,1], 10), Some([5]))}) -test("binary to multiple decimal", () => {Assertions.assertEqual(~message="binary to multiple decimal", rebase(2, [1,0,1,0,1,0], 10), Some([4,2]))}) +test("single decimal to binary", () => {equal(~message="single decimal to binary", rebase(10, [5], 2), Some([1,0,1]))}) -test("decimal to binary", () => {Assertions.assertEqual(~message="decimal to binary", rebase(10, [4,2], 2), Some([1,0,1,0,1,0]))}) +test("binary to multiple decimal", () => {equal(~message="binary to multiple decimal", rebase(2, [1,0,1,0,1,0], 10), Some([4,2]))}) -test("trinary to hexadecimal", () => {Assertions.assertEqual(~message="trinary to hexadecimal", rebase(3, [1,1,2,0], 16), Some([2,10]))}) +test("decimal to binary", () => {equal(~message="decimal to binary", rebase(10, [4,2], 2), Some([1,0,1,0,1,0]))}) -test("hexadecimal to trinary", () => {Assertions.assertEqual(~message="hexadecimal to trinary", rebase(16, [2,10], 3), Some([1,1,2,0]))}) +test("trinary to hexadecimal", () => {equal(~message="trinary to hexadecimal", rebase(3, [1,1,2,0], 16), Some([2,10]))}) -test("15-bit integer", () => {Assertions.assertEqual(~message="15-bit integer", rebase(97, [3,46,60], 73), Some([6,10,45]))}) +test("hexadecimal to trinary", () => {equal(~message="hexadecimal to trinary", rebase(16, [2,10], 3), Some([1,1,2,0]))}) -test("empty list", () => {Assertions.assertEqual(~message="empty list", rebase(2, [], 10), Some([0]))}) +test("15-bit integer", () => {equal(~message="15-bit integer", rebase(97, [3,46,60], 73), Some([6,10,45]))}) -test("single zero", () => {Assertions.assertEqual(~message="single zero", rebase(10, [0], 2), Some([0]))}) +test("empty list", () => {equal(~message="empty list", rebase(2, [], 10), Some([0]))}) -test("multiple zeros", () => {Assertions.assertEqual(~message="multiple zeros", rebase(10, [0,0,0], 2), Some([0]))}) +test("single zero", () => {equal(~message="single zero", rebase(10, [0], 2), Some([0]))}) -test("leading zeros", () => {Assertions.assertEqual(~message="leading zeros", rebase(7, [0,6,0], 10), Some([4,2]))}) +test("multiple zeros", () => {equal(~message="multiple zeros", rebase(10, [0,0,0], 2), Some([0]))}) -test("input base is one", () => {Assertions.assertEqual(~message="input base is one", rebase(1, [0], 10), None)}) +test("leading zeros", () => {equal(~message="leading zeros", rebase(7, [0,6,0], 10), Some([4,2]))}) -test("input base is zero", () => {Assertions.assertEqual(~message="input base is zero", rebase(0, [], 10), None)}) +test("input base is one", () => {equal(~message="input base is one", rebase(1, [0], 10), None)}) -test("input base is negative", () => {Assertions.assertEqual(~message="input base is negative", rebase(-2, [1], 10), None)}) +test("input base is zero", () => {equal(~message="input base is zero", rebase(0, [], 10), None)}) -test("negative digit", () => {Assertions.assertEqual(~message="negative digit", rebase(2, [1,-1,1,0,1,0], 10), None)}) +test("input base is negative", () => {equal(~message="input base is negative", rebase(-2, [1], 10), None)}) -test("invalid positive digit", () => {Assertions.assertEqual(~message="invalid positive digit", rebase(2, [1,2,1,0,1,0], 10), None)}) +test("negative digit", () => {equal(~message="negative digit", rebase(2, [1,-1,1,0,1,0], 10), None)}) -test("output base is one", () => {Assertions.assertEqual(~message="output base is one", rebase(2, [1,0,1,0,1,0], 1), None)}) +test("invalid positive digit", () => {equal(~message="invalid positive digit", rebase(2, [1,2,1,0,1,0], 10), None)}) -test("output base is zero", () => {Assertions.assertEqual(~message="output base is zero", rebase(10, [7], 0), 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 negative", () => {Assertions.assertEqual(~message="output base is negative", rebase(2, [1], -7), None)}) +test("output base is zero", () => {equal(~message="output base is zero", rebase(10, [7], 0), None)}) -test("both bases are negative", () => {Assertions.assertEqual(~message="both bases are 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", () => {equal(~message="both bases are negative", rebase(-2, [1], -7), None)}) diff --git a/exercises/practice/bob/tests/Bob_test.res b/exercises/practice/bob/tests/Bob_test.res index ac7ba58..d86058b 100644 --- a/exercises/practice/bob/tests/Bob_test.res +++ b/exercises/practice/bob/tests/Bob_test.res @@ -1,52 +1,54 @@ open Test open Bob -test("stating something", () => {Assertions.assertEqual(~message="stating something", response("Tom-ay-to, tom-aaaah-to."), "Whatever.")}) +let equal = (~message=?, a, b) => assertion(~message?, ~operator="equal", (a, b) => a == b, a, b) -test("shouting", () => {Assertions.assertEqual(~message="shouting", response("WATCH OUT!"), "Whoa, chill out!")}) +test("stating something", () => {equal(~message="stating something", response("Tom-ay-to, tom-aaaah-to."), "Whatever.")}) -test("shouting gibberish", () => {Assertions.assertEqual(~message="shouting gibberish", response("FCECDFCAAB"), "Whoa, chill out!")}) +test("shouting", () => {equal(~message="shouting", response("WATCH OUT!"), "Whoa, chill out!")}) -test("asking a question", () => {Assertions.assertEqual(~message="asking a question", response("Does this cryogenic chamber make me look fat?"), "Sure.")}) +test("shouting gibberish", () => {equal(~message="shouting gibberish", response("FCECDFCAAB"), "Whoa, chill out!")}) -test("asking a numeric question", () => {Assertions.assertEqual(~message="asking a numeric question", response("You are, what, like 15?"), "Sure.")}) +test("asking a question", () => {equal(~message="asking a question", response("Does this cryogenic chamber make me look fat?"), "Sure.")}) -test("asking gibberish", () => {Assertions.assertEqual(~message="asking gibberish", response("fffbbcbeab?"), "Sure.")}) +test("asking a numeric question", () => {equal(~message="asking a numeric question", response("You are, what, like 15?"), "Sure.")}) -test("talking forcefully", () => {Assertions.assertEqual(~message="talking forcefully", response("Hi there!"), "Whatever.")}) +test("asking gibberish", () => {equal(~message="asking gibberish", response("fffbbcbeab?"), "Sure.")}) -test("using acronyms in regular speech", () => {Assertions.assertEqual(~message="using acronyms in regular speech", response("It's OK if you don't want to go work for NASA."), "Whatever.")}) +test("talking forcefully", () => {equal(~message="talking forcefully", response("Hi there!"), "Whatever.")}) -test("forceful question", () => {Assertions.assertEqual(~message="forceful question", response("WHAT'S GOING ON?"), "Calm down, I know what I'm doing!")}) +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("shouting numbers", () => {Assertions.assertEqual(~message="shouting numbers", response("1, 2, 3 GO!"), "Whoa, chill out!")}) +test("forceful question", () => {equal(~message="forceful question", response("WHAT'S GOING ON?"), "Calm down, I know what I'm doing!")}) -test("no letters", () => {Assertions.assertEqual(~message="no letters", response("1, 2, 3"), "Whatever.")}) +test("shouting numbers", () => {equal(~message="shouting numbers", response("1, 2, 3 GO!"), "Whoa, chill out!")}) -test("question with no letters", () => {Assertions.assertEqual(~message="question with no letters", response("4?"), "Sure.")}) +test("no letters", () => {equal(~message="no letters", response("1, 2, 3"), "Whatever.")}) -test("shouting with special characters", () => {Assertions.assertEqual(~message="shouting with special characters", response("ZOMG THE %^*@#$(*^ ZOMBIES ARE COMING!!11!!1!"), "Whoa, chill out!")}) +test("question with no letters", () => {equal(~message="question with no letters", response("4?"), "Sure.")}) -test("shouting with no exclamation mark", () => {Assertions.assertEqual(~message="shouting with no exclamation mark", response("I HATE THE DENTIST"), "Whoa, chill out!")}) +test("shouting with special characters", () => {equal(~message="shouting with special characters", response("ZOMG THE %^*@#$(*^ ZOMBIES ARE COMING!!11!!1!"), "Whoa, chill out!")}) -test("statement containing question mark", () => {Assertions.assertEqual(~message="statement containing question mark", response("Ending with ? means a question."), "Whatever.")}) +test("shouting with no exclamation mark", () => {equal(~message="shouting with no exclamation mark", response("I HATE THE DENTIST"), "Whoa, chill out!")}) -test("non-letters with question", () => {Assertions.assertEqual(~message="non-letters with question", response(":) ?"), "Sure.")}) +test("statement containing question mark", () => {equal(~message="statement containing question mark", response("Ending with ? means a question."), "Whatever.")}) -test("prattling on", () => {Assertions.assertEqual(~message="prattling on", response("Wait! Hang on. Are you going to be OK?"), "Sure.")}) +test("non-letters with question", () => {equal(~message="non-letters with question", response(":) ?"), "Sure.")}) -test("silence", () => {Assertions.assertEqual(~message="silence", response(""), "Fine. Be that way!")}) +test("prattling on", () => {equal(~message="prattling on", response("Wait! Hang on. Are you going to be OK?"), "Sure.")}) -test("prolonged silence", () => {Assertions.assertEqual(~message="prolonged silence", response(" "), "Fine. Be that way!")}) +test("silence", () => {equal(~message="silence", response(""), "Fine. Be that way!")}) -test("alternate silence", () => {Assertions.assertEqual(~message="alternate silence", response("\t\t\t\t\t\t\t\t\t\t"), "Fine. Be that way!")}) +test("prolonged silence", () => {equal(~message="prolonged silence", response(" "), "Fine. Be that way!")}) -test("starting with whitespace", () => {Assertions.assertEqual(~message="starting with whitespace", response(" hmmmmmmm..."), "Whatever.")}) +test("alternate silence", () => {equal(~message="alternate silence", response("\t\t\t\t\t\t\t\t\t\t"), "Fine. Be that way!")}) -test("ending with whitespace", () => {Assertions.assertEqual(~message="ending with whitespace", response("Okay if like my spacebar quite a bit? "), "Sure.")}) +test("starting with whitespace", () => {equal(~message="starting with whitespace", response(" hmmmmmmm..."), "Whatever.")}) -test("other whitespace", () => {Assertions.assertEqual(~message="other whitespace", response("\n\r \t"), "Fine. Be that way!")}) +test("ending with whitespace", () => {equal(~message="ending with whitespace", response("Okay if like my spacebar quite a bit? "), "Sure.")}) -test("non-question ending with whitespace", () => {Assertions.assertEqual(~message="non-question ending with whitespace", response("This is a statement ending with whitespace "), "Whatever.")}) +test("other whitespace", () => {equal(~message="other whitespace", response("\n\r \t"), "Fine. Be that way!")}) -test("multiple line question", () => {Assertions.assertEqual(~message="multiple line question", response("\nDoes this cryogenic chamber make\n me look fat?"), "Sure.")}) +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/tests/HelloWorld_test.res b/exercises/practice/hello-world/tests/HelloWorld_test.res index 9c8f80b..3e49478 100644 --- a/exercises/practice/hello-world/tests/HelloWorld_test.res +++ b/exercises/practice/hello-world/tests/HelloWorld_test.res @@ -1,4 +1,6 @@ open Test open HelloWorld -test("Say Hi!", () => {Assertions.assertEqual(~message="Say Hi!", hello(), "Hello, World!")}) +let equal = (~message=?, a, b) => assertion(~message?, ~operator="equal", (a, b) => a == b, a, b) + +test("Say Hi!", () => {equal(~message="Say Hi!", hello(), "Hello, World!")}) diff --git a/test_generator/AssertionGenerators.res b/test_generator/AssertionGenerators.res new file mode 100644 index 0000000..fe6d68f --- /dev/null +++ b/test_generator/AssertionGenerators.res @@ -0,0 +1,5 @@ +let equalSource = "equal(~message, ~actual, ~expected)" + +let equal = (~message, ~actual, ~expected) => { + `equal(~message="${message}", ${actual}, ${expected})` +} diff --git a/test_generator/Assertions.res b/test_generator/Assertions.res index ff57c1b..968cb2b 100644 --- a/test_generator/Assertions.res +++ b/test_generator/Assertions.res @@ -1,12 +1,6 @@ open Test -let assertEqual = (~message=?, a, b) => - assertion(~message?, ~operator="assertEqual", (a, b) => a == b, a, b) - -// This returns the string of code the user actually sees in the test -let genAssertEqual = (~message, ~actual, ~expected) => { - `Assertions.assertEqual(~message="${message}", ${actual}, ${expected})` -} +let equal = (~message=?, a, b) => assertion(~message?, ~operator="equal", (a, b) => a == b, a, b) let dictEqual = (~message=?, a: Dict.t<'a>, b: Dict.t<'a>) => { let toSorted = d => { diff --git a/test_generator/TestGenerator.res b/test_generator/TestGenerator.res index c13416e..6e4ca0f 100644 --- a/test_generator/TestGenerator.res +++ b/test_generator/TestGenerator.res @@ -1,5 +1,15 @@ open Node +type assertionTag = Equal | DeepEqual | Throws + +let getAssertionSource = tag => { + switch tag { + | Equal => `let equal = (~message=?, a, b) => assertion(~message?, ~operator="equal", (a, b) => a == b, a, b)` + | DeepEqual => `let deepEqual = (~message=?, a, b) => /* deep equal logic */` + | Throws => `let throws = (~message=?, f) => /* throws logic */` + } +} + let toPascalCase = slug => { slug ->String.split("-") @@ -7,13 +17,18 @@ let toPascalCase = slug => { ->Array.join("") } -let generate = (outputPath, slug, template) => { +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) @@ -32,9 +47,13 @@ let generate = (outputPath, slug, template) => { Console.log(`Generated: ${basename(outputPath)}`) } -let generateTests = (slug, template) => { +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) + resolve(exercisePath, `${toPascalCase(slug)}_test.res`)->generate( + slug, + template, + requiredAssertions, + ) } diff --git a/test_generator/TestGenerator.resi b/test_generator/TestGenerator.resi index 4b483e8..ea7e867 100644 --- a/test_generator/TestGenerator.resi +++ b/test_generator/TestGenerator.resi @@ -1 +1,3 @@ -let generateTests: (string, GetCases.case => string) => unit +type assertionTag = Equal | DeepEqual | Throws + +let generateTests: (string, GetCases.case => string, array) => unit diff --git a/test_templates/Acronym_template.res b/test_templates/Acronym_template.res index c9a0174..7cf3c18 100644 --- a/test_templates/Acronym_template.res +++ b/test_templates/Acronym_template.res @@ -5,12 +5,12 @@ let template = (case: GetCases.case) => { let phrase = Utils.getTestCaseInput(case, "phrase") - // EDIT THIS WITH YOUR ASSERTIONS (use genAssert... name to generate an assertion in the template) - Assertions.genAssertEqual( + // EDIT THIS WITH YOUR ASSERTIONS + AssertionGenerators.equal( ~message=case.description, ~actual=`abbreviate(${phrase})`, ~expected=expectedStr, ) } -TestGenerator.generateTests(slug, template) +TestGenerator.generateTests(slug, template, [Equal]) diff --git a/test_templates/AllYourBase_template.res b/test_templates/AllYourBase_template.res index 0e141c5..118f9f5 100644 --- a/test_templates/AllYourBase_template.res +++ b/test_templates/AllYourBase_template.res @@ -7,12 +7,13 @@ let template = (case: GetCases.case) => { let digits = Utils.getTestCaseInput(case, "digits") let outputBase = Utils.getTestCaseInput(case, "outputBase") - // EDIT THIS WITH YOUR ASSERTIONS (use genAssert... name to generate an assertion in the template) - Assertions.genAssertEqual( + // 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, ) } -TestGenerator.generateTests(slug, template) +// ADD ASSERTION DEPENDENCIES (e.g. [#equal, #deepEqual]) +TestGenerator.generateTests(slug, template, [Equal]) diff --git a/test_templates/Bob_template.res b/test_templates/Bob_template.res index 03d5e97..42f2340 100644 --- a/test_templates/Bob_template.res +++ b/test_templates/Bob_template.res @@ -6,11 +6,11 @@ let template = (case: GetCases.case) => { let phrase = Utils.getTestCaseInput(case, "heyBob") // EDIT THIS WITH YOUR ASSERTIONS (use genAssert... name to generate an assertion in the template) - Assertions.genAssertEqual( + AssertionGenerators.equal( ~message=case.description, ~actual=`response(${phrase})`, ~expected=expectedStr, ) } -TestGenerator.generateTests(slug, template) +TestGenerator.generateTests(slug, template, [Equal]) diff --git a/test_templates/HelloWorld_template.res b/test_templates/HelloWorld_template.res index 08f8d2c..b17e692 100644 --- a/test_templates/HelloWorld_template.res +++ b/test_templates/HelloWorld_template.res @@ -5,7 +5,7 @@ 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) - Assertions.genAssertEqual(~message=case.description, ~actual="hello()", ~expected=expectedStr) + AssertionGenerators.equal(~message=case.description, ~actual="hello()", ~expected=expectedStr) } -TestGenerator.generateTests(slug, template) +TestGenerator.generateTests(slug, template, [Equal]) From f152f9e31d1bb56d2d07bb7ca144cfc14d68574f Mon Sep 17 00:00:00 2001 From: Owen Date: Mon, 16 Mar 2026 21:54:29 +0100 Subject: [PATCH 20/26] rework assertions as template strings to allow injection into test files --- test_generator/Assertions.res | 31 +++++++++++++------------------ test_generator/TestGenerator.res | 11 +---------- test_generator/TestGenerator.resi | 4 +--- 3 files changed, 15 insertions(+), 31 deletions(-) diff --git a/test_generator/Assertions.res b/test_generator/Assertions.res index 968cb2b..61128dc 100644 --- a/test_generator/Assertions.res +++ b/test_generator/Assertions.res @@ -1,21 +1,16 @@ -open Test +type assertionTag = Equal | DictEqual | Throws -let equal = (~message=?, a, b) => assertion(~message?, ~operator="equal", (a, b) => a == b, a, b) - -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 +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 */` } - assertion(~message?, ~operator="dictEqual", (a, b) => toSorted(a) == toSorted(b), a, b) -} - -/** * Asserts that two floats are equal within a specified precision. - * The `digits` argument determines the tolerance (10^-digits). - * Defaults to 2 decimal places (0.01 tolerance). - */ -let floatEqual = (~message=?, ~digits=2, a: float, b: float) => { - let tolerance = 10.0 ** -.Float.fromInt(digits) - assertion(~message?, ~operator="floatEqual", (a, b) => Math.abs(a -. b) <= tolerance, a, b) } diff --git a/test_generator/TestGenerator.res b/test_generator/TestGenerator.res index 6e4ca0f..c851f3f 100644 --- a/test_generator/TestGenerator.res +++ b/test_generator/TestGenerator.res @@ -1,14 +1,5 @@ open Node - -type assertionTag = Equal | DeepEqual | Throws - -let getAssertionSource = tag => { - switch tag { - | Equal => `let equal = (~message=?, a, b) => assertion(~message?, ~operator="equal", (a, b) => a == b, a, b)` - | DeepEqual => `let deepEqual = (~message=?, a, b) => /* deep equal logic */` - | Throws => `let throws = (~message=?, f) => /* throws logic */` - } -} +open Assertions let toPascalCase = slug => { slug diff --git a/test_generator/TestGenerator.resi b/test_generator/TestGenerator.resi index ea7e867..c17321f 100644 --- a/test_generator/TestGenerator.resi +++ b/test_generator/TestGenerator.resi @@ -1,3 +1 @@ -type assertionTag = Equal | DeepEqual | Throws - -let generateTests: (string, GetCases.case => string, array) => unit +let generateTests: (string, GetCases.case => string, array) => unit From 5d4511541069a9570e02115688f631a5b30dcb78 Mon Sep 17 00:00:00 2001 From: Owen Date: Mon, 16 Mar 2026 21:56:27 +0100 Subject: [PATCH 21/26] remove unused code --- test_generator/AssertionGenerators.res | 2 -- 1 file changed, 2 deletions(-) diff --git a/test_generator/AssertionGenerators.res b/test_generator/AssertionGenerators.res index fe6d68f..350f89b 100644 --- a/test_generator/AssertionGenerators.res +++ b/test_generator/AssertionGenerators.res @@ -1,5 +1,3 @@ -let equalSource = "equal(~message, ~actual, ~expected)" - let equal = (~message, ~actual, ~expected) => { `equal(~message="${message}", ${actual}, ${expected})` } From 2cc954553fb028b04728b4e455edbab0519e0122 Mon Sep 17 00:00:00 2001 From: Owen Date: Mon, 16 Mar 2026 22:12:18 +0100 Subject: [PATCH 22/26] update test template to retrieve slug from filename --- templates/Test_template.res | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/templates/Test_template.res b/templates/Test_template.res index e80fb09..293e7ee 100644 --- a/templates/Test_template.res +++ b/templates/Test_template.res @@ -1,11 +1,11 @@ -// UNCOMMENT CODE +open Node -// EDIT THIS WITH THE EXERCISE SLUG, eg. all-your-base -let slug = "exercise-name" +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) From 88fe2691d58cccc2bcc53ec173b81a6c2bd4d8e7 Mon Sep 17 00:00:00 2001 From: Owen Date: Mon, 16 Mar 2026 22:12:25 +0100 Subject: [PATCH 23/26] get slug from filename --- test_generator/Utils.res | 9 +++++++++ test_templates/Acronym_template.res | 4 +++- test_templates/AllYourBase_template.res | 4 +++- test_templates/Bob_template.res | 4 +++- test_templates/HelloWorld_template.res | 4 +++- 5 files changed, 21 insertions(+), 4 deletions(-) diff --git a/test_generator/Utils.res b/test_generator/Utils.res index 740c0e7..435d44c 100644 --- a/test_generator/Utils.res +++ b/test_generator/Utils.res @@ -6,3 +6,12 @@ let getTestCaseInput = (case: GetCases.case, inputName: string) => { ->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_templates/Acronym_template.res b/test_templates/Acronym_template.res index 7cf3c18..5aa9f33 100644 --- a/test_templates/Acronym_template.res +++ b/test_templates/Acronym_template.res @@ -1,4 +1,6 @@ -let slug = "acronym" +open Node + +let slug = fileURLToPath(%raw(`import.meta.url`))->basename->Utils.filenameToSlug let template = (case: GetCases.case) => { let expectedStr = JSON.stringify(case.expected) diff --git a/test_templates/AllYourBase_template.res b/test_templates/AllYourBase_template.res index 118f9f5..c12e3cb 100644 --- a/test_templates/AllYourBase_template.res +++ b/test_templates/AllYourBase_template.res @@ -1,4 +1,6 @@ -let slug = "all-your-base" +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" diff --git a/test_templates/Bob_template.res b/test_templates/Bob_template.res index 42f2340..2f17b10 100644 --- a/test_templates/Bob_template.res +++ b/test_templates/Bob_template.res @@ -1,4 +1,6 @@ -let slug = "bob" +open Node + +let slug = fileURLToPath(%raw(`import.meta.url`))->basename->Utils.filenameToSlug let template = (case: GetCases.case) => { let expectedStr = JSON.stringify(case.expected) diff --git a/test_templates/HelloWorld_template.res b/test_templates/HelloWorld_template.res index b17e692..9e127bf 100644 --- a/test_templates/HelloWorld_template.res +++ b/test_templates/HelloWorld_template.res @@ -1,4 +1,6 @@ -let slug = "hello-world" +open Node + +let slug = fileURLToPath(%raw(`import.meta.url`))->basename->Utils.filenameToSlug // EDIT THIS WITH YOUR TEST TEMPLATES let template = (case: GetCases.case) => { From cd04551cb888dca0a5d63a51cf883b079e97b1a4 Mon Sep 17 00:00:00 2001 From: Owen Date: Mon, 16 Mar 2026 22:14:59 +0100 Subject: [PATCH 24/26] added comments for assertions --- test_generator/AssertionGenerators.res | 2 ++ test_generator/Assertions.res | 2 ++ 2 files changed, 4 insertions(+) diff --git a/test_generator/AssertionGenerators.res b/test_generator/AssertionGenerators.res index 350f89b..d91b548 100644 --- a/test_generator/AssertionGenerators.res +++ b/test_generator/AssertionGenerators.res @@ -1,3 +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 index 61128dc..8cec200 100644 --- a/test_generator/Assertions.res +++ b/test_generator/Assertions.res @@ -1,3 +1,5 @@ +// Comparator functions in template string format, injectable into generated tests. + type assertionTag = Equal | DictEqual | Throws let getAssertionSource = tag => { From fa50680de1e6b8a8c3a3d3e2522d257a21826892 Mon Sep 17 00:00:00 2001 From: Owen Date: Mon, 16 Mar 2026 22:26:15 +0100 Subject: [PATCH 25/26] convert anagram tests --- .../practice/anagram/tests/Anagram_test.res | 74 +++++-------------- test_templates/Anagram_template.res | 22 ++++++ 2 files changed, 41 insertions(+), 55 deletions(-) create mode 100644 test_templates/Anagram_template.res 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/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]) From aba0c83fe08a087156ab1f7668c519837345d688 Mon Sep 17 00:00:00 2001 From: Owen Date: Mon, 16 Mar 2026 22:43:03 +0100 Subject: [PATCH 26/26] convert Binary Search tests --- .../practice/anagram/.meta/testTemplate.js | 23 ---------- .../binary-search/tests/BinarySearch_test.res | 46 +++++-------------- test_templates/BinarySearch_template.res | 22 +++++++++ 3 files changed, 34 insertions(+), 57 deletions(-) delete mode 100644 exercises/practice/anagram/.meta/testTemplate.js create mode 100644 test_templates/BinarySearch_template.res 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/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/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])