From 5181a63aa1d9eaf84b16757b913d3ab880ea3ab9 Mon Sep 17 00:00:00 2001 From: Owen Date: Mon, 16 Mar 2026 10:13:20 +0100 Subject: [PATCH 01/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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]) From 6587d4d205b7159cd9074034196582c6f4bb6c3b Mon Sep 17 00:00:00 2001 From: Owen Date: Fri, 20 Mar 2026 09:38:03 +0100 Subject: [PATCH 27/44] covnert armstrong number tests --- .../armstrong-numbers/.meta/testTemplate.js | 17 --------- .../tests/ArmstrongNumbers_test.res | 38 +++++-------------- test_templates/ArmstrongNumbers_template.res | 16 ++++++++ 3 files changed, 26 insertions(+), 45 deletions(-) delete mode 100644 exercises/practice/armstrong-numbers/.meta/testTemplate.js create mode 100644 test_templates/ArmstrongNumbers_template.res diff --git a/exercises/practice/armstrong-numbers/.meta/testTemplate.js b/exercises/practice/armstrong-numbers/.meta/testTemplate.js deleted file mode 100644 index 9a7ed37..0000000 --- a/exercises/practice/armstrong-numbers/.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}", isArmstrongNumber(${c.input.number}), ${c.expected})` -} - -generateTests(__dirname, slug, assertionFunctions, template) \ No newline at end of file diff --git a/exercises/practice/armstrong-numbers/tests/ArmstrongNumbers_test.res b/exercises/practice/armstrong-numbers/tests/ArmstrongNumbers_test.res index 6741e38..e9bf310 100644 --- a/exercises/practice/armstrong-numbers/tests/ArmstrongNumbers_test.res +++ b/exercises/practice/armstrong-numbers/tests/ArmstrongNumbers_test.res @@ -1,40 +1,22 @@ open Test open ArmstrongNumbers -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("Zero is an Armstrong number", () => { - assertEqual(~message="Zero is an Armstrong number", isArmstrongNumber(0), true) -}) +test("Zero is an Armstrong number", () => {equal(~message="Zero is an Armstrong number", isArmstrongNumber(0), true)}) -test("Single-digit numbers are Armstrong numbers", () => { - assertEqual(~message="Single-digit numbers are Armstrong numbers", isArmstrongNumber(5), true) -}) +test("Single-digit numbers are Armstrong numbers", () => {equal(~message="Single-digit numbers are Armstrong numbers", isArmstrongNumber(5), true)}) -test("There are no two-digit Armstrong numbers", () => { - assertEqual(~message="There are no two-digit Armstrong numbers", isArmstrongNumber(10), false) -}) +test("There are no two-digit Armstrong numbers", () => {equal(~message="There are no two-digit Armstrong numbers", isArmstrongNumber(10), false)}) -test("Three-digit number that is an Armstrong number", () => { - assertEqual(~message="Three-digit number that is an Armstrong number", isArmstrongNumber(153), true) -}) +test("Three-digit number that is an Armstrong number", () => {equal(~message="Three-digit number that is an Armstrong number", isArmstrongNumber(153), true)}) -test("Three-digit number that is not an Armstrong number", () => { - assertEqual(~message="Three-digit number that is not an Armstrong number", isArmstrongNumber(100), false) -}) +test("Three-digit number that is not an Armstrong number", () => {equal(~message="Three-digit number that is not an Armstrong number", isArmstrongNumber(100), false)}) -test("Four-digit number that is an Armstrong number", () => { - assertEqual(~message="Four-digit number that is an Armstrong number", isArmstrongNumber(9474), true) -}) +test("Four-digit number that is an Armstrong number", () => {equal(~message="Four-digit number that is an Armstrong number", isArmstrongNumber(9474), true)}) -test("Four-digit number that is not an Armstrong number", () => { - assertEqual(~message="Four-digit number that is not an Armstrong number", isArmstrongNumber(9475), false) -}) +test("Four-digit number that is not an Armstrong number", () => {equal(~message="Four-digit number that is not an Armstrong number", isArmstrongNumber(9475), false)}) -test("Seven-digit number that is an Armstrong number", () => { - assertEqual(~message="Seven-digit number that is an Armstrong number", isArmstrongNumber(9926315), true) -}) +test("Seven-digit number that is an Armstrong number", () => {equal(~message="Seven-digit number that is an Armstrong number", isArmstrongNumber(9926315), true)}) -test("Seven-digit number that is not an Armstrong number", () => { - assertEqual(~message="Seven-digit number that is not an Armstrong number", isArmstrongNumber(9926314), false) -}) +test("Seven-digit number that is not an Armstrong number", () => {equal(~message="Seven-digit number that is not an Armstrong number", isArmstrongNumber(9926314), false)}) diff --git a/test_templates/ArmstrongNumbers_template.res b/test_templates/ArmstrongNumbers_template.res new file mode 100644 index 0000000..e46e9c0 --- /dev/null +++ b/test_templates/ArmstrongNumbers_template.res @@ -0,0 +1,16 @@ +open Node + +let slug = fileURLToPath(%raw(`import.meta.url`))->basename->Utils.filenameToSlug + +let template = (case: GetCases.case) => { + let number = Utils.getTestCaseInput(case, "number") + + // EDIT THIS WITH YOUR ASSERTIONS + AssertionGenerators.equal( + ~message=case.description, + ~actual=`isArmstrongNumber(${number})`, + ~expected=JSON.stringify(case.expected), + ) +} + +TestGenerator.generateTests(slug, template, [Equal]) From 29a9d48b1b30e8202cc2a0a19770a9e81939d2e5 Mon Sep 17 00:00:00 2001 From: Owen Date: Fri, 20 Mar 2026 09:41:24 +0100 Subject: [PATCH 28/44] convert eliuds eggs --- .../practice/eliuds-eggs/.meta/testTemplate.js | 18 ------------------ .../eliuds-eggs/tests/EliudsEggs_test.res | 18 +++++------------- test_templates/EliudsEggs_template.res | 16 ++++++++++++++++ 3 files changed, 21 insertions(+), 31 deletions(-) delete mode 100644 exercises/practice/eliuds-eggs/.meta/testTemplate.js create mode 100644 test_templates/EliudsEggs_template.res diff --git a/exercises/practice/eliuds-eggs/.meta/testTemplate.js b/exercises/practice/eliuds-eggs/.meta/testTemplate.js deleted file mode 100644 index c641070..0000000 --- a/exercises/practice/eliuds-eggs/.meta/testTemplate.js +++ /dev/null @@ -1,18 +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 { number } = c.input - return `assertEqual(~message="${c.description}", eggCount(${number}), ${c.expected})` -} - -generateTests(__dirname, slug, assertionFunctions, template) \ No newline at end of file diff --git a/exercises/practice/eliuds-eggs/tests/EliudsEggs_test.res b/exercises/practice/eliuds-eggs/tests/EliudsEggs_test.res index f4b9b89..3711c91 100644 --- a/exercises/practice/eliuds-eggs/tests/EliudsEggs_test.res +++ b/exercises/practice/eliuds-eggs/tests/EliudsEggs_test.res @@ -1,20 +1,12 @@ open Test open EliudsEggs -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("0 eggs", () => { - assertEqual(~message="0 eggs", eggCount(0), 0) -}) +test("0 eggs", () => {equal(~message="0 eggs", eggCount(0), 0)}) -test("1 egg", () => { - assertEqual(~message="1 egg", eggCount(16), 1) -}) +test("1 egg", () => {equal(~message="1 egg", eggCount(16), 1)}) -test("4 eggs", () => { - assertEqual(~message="4 eggs", eggCount(89), 4) -}) +test("4 eggs", () => {equal(~message="4 eggs", eggCount(89), 4)}) -test("13 eggs", () => { - assertEqual(~message="13 eggs", eggCount(2000000000), 13) -}) +test("13 eggs", () => {equal(~message="13 eggs", eggCount(2000000000), 13)}) diff --git a/test_templates/EliudsEggs_template.res b/test_templates/EliudsEggs_template.res new file mode 100644 index 0000000..a6673c1 --- /dev/null +++ b/test_templates/EliudsEggs_template.res @@ -0,0 +1,16 @@ +open Node + +let slug = fileURLToPath(%raw(`import.meta.url`))->basename->Utils.filenameToSlug + +let template = (case: GetCases.case) => { + let number = Utils.getTestCaseInput(case, "number") + + // EDIT THIS WITH YOUR ASSERTIONS + AssertionGenerators.equal( + ~message=case.description, + ~actual=`eggCount(${number})`, + ~expected=JSON.stringify(case.expected), + ) +} + +TestGenerator.generateTests(slug, template, [Equal]) From 1f90bc7b0011d2420b9e4d781f66551402e43045 Mon Sep 17 00:00:00 2001 From: Owen Date: Fri, 20 Mar 2026 09:46:04 +0100 Subject: [PATCH 29/44] update isogram tests --- .../practice/isogram/.meta/testTemplate.js | 17 ------ .../practice/isogram/tests/Isogram_test.res | 58 +++++-------------- test_templates/Isogram_template.res | 16 +++++ 3 files changed, 31 insertions(+), 60 deletions(-) delete mode 100644 exercises/practice/isogram/.meta/testTemplate.js create mode 100644 test_templates/Isogram_template.res diff --git a/exercises/practice/isogram/.meta/testTemplate.js b/exercises/practice/isogram/.meta/testTemplate.js deleted file mode 100644 index 99d39ab..0000000 --- a/exercises/practice/isogram/.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}", isIsogram("${c.input.phrase}"), ${c.expected})` -} - -generateTests(__dirname, slug, assertionFunctions, template) \ No newline at end of file diff --git a/exercises/practice/isogram/tests/Isogram_test.res b/exercises/practice/isogram/tests/Isogram_test.res index be09eb3..4c05f86 100644 --- a/exercises/practice/isogram/tests/Isogram_test.res +++ b/exercises/practice/isogram/tests/Isogram_test.res @@ -1,60 +1,32 @@ open Test open Isogram -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("empty string", () => { - assertEqual(~message="empty string", isIsogram(""), true) -}) +test("empty string", () => {equal(~message="empty string", isIsogram(""), true)}) -test("isogram with only lower case characters", () => { - assertEqual(~message="isogram with only lower case characters", isIsogram("isogram"), true) -}) +test("isogram with only lower case characters", () => {equal(~message="isogram with only lower case characters", isIsogram("isogram"), true)}) -test("word with one duplicated character", () => { - assertEqual(~message="word with one duplicated character", isIsogram("eleven"), false) -}) +test("word with one duplicated character", () => {equal(~message="word with one duplicated character", isIsogram("eleven"), false)}) -test("word with one duplicated character from the end of the alphabet", () => { - assertEqual(~message="word with one duplicated character from the end of the alphabet", isIsogram("zzyzx"), false) -}) +test("word with one duplicated character from the end of the alphabet", () => {equal(~message="word with one duplicated character from the end of the alphabet", isIsogram("zzyzx"), false)}) -test("longest reported english isogram", () => { - assertEqual(~message="longest reported english isogram", isIsogram("subdermatoglyphic"), true) -}) +test("longest reported english isogram", () => {equal(~message="longest reported english isogram", isIsogram("subdermatoglyphic"), true)}) -test("word with duplicated character in mixed case", () => { - assertEqual(~message="word with duplicated character in mixed case", isIsogram("Alphabet"), false) -}) +test("word with duplicated character in mixed case", () => {equal(~message="word with duplicated character in mixed case", isIsogram("Alphabet"), false)}) -test("word with duplicated character in mixed case, lowercase first", () => { - assertEqual(~message="word with duplicated character in mixed case, lowercase first", isIsogram("alphAbet"), false) -}) +test("word with duplicated character in mixed case, lowercase first", () => {equal(~message="word with duplicated character in mixed case, lowercase first", isIsogram("alphAbet"), false)}) -test("hypothetical isogrammic word with hyphen", () => { - assertEqual(~message="hypothetical isogrammic word with hyphen", isIsogram("thumbscrew-japingly"), true) -}) +test("hypothetical isogrammic word with hyphen", () => {equal(~message="hypothetical isogrammic word with hyphen", isIsogram("thumbscrew-japingly"), true)}) -test("hypothetical word with duplicated character following hyphen", () => { - assertEqual(~message="hypothetical word with duplicated character following hyphen", isIsogram("thumbscrew-jappingly"), false) -}) +test("hypothetical word with duplicated character following hyphen", () => {equal(~message="hypothetical word with duplicated character following hyphen", isIsogram("thumbscrew-jappingly"), false)}) -test("isogram with duplicated hyphen", () => { - assertEqual(~message="isogram with duplicated hyphen", isIsogram("six-year-old"), true) -}) +test("isogram with duplicated hyphen", () => {equal(~message="isogram with duplicated hyphen", isIsogram("six-year-old"), true)}) -test("made-up name that is an isogram", () => { - assertEqual(~message="made-up name that is an isogram", isIsogram("Emily Jung Schwartzkopf"), true) -}) +test("made-up name that is an isogram", () => {equal(~message="made-up name that is an isogram", isIsogram("Emily Jung Schwartzkopf"), true)}) -test("duplicated character in the middle", () => { - assertEqual(~message="duplicated character in the middle", isIsogram("accentor"), false) -}) +test("duplicated character in the middle", () => {equal(~message="duplicated character in the middle", isIsogram("accentor"), false)}) -test("same first and last characters", () => { - assertEqual(~message="same first and last characters", isIsogram("angola"), false) -}) +test("same first and last characters", () => {equal(~message="same first and last characters", isIsogram("angola"), false)}) -test("word with duplicated character and with two hyphens", () => { - assertEqual(~message="word with duplicated character and with two hyphens", isIsogram("up-to-date"), false) -}) +test("word with duplicated character and with two hyphens", () => {equal(~message="word with duplicated character and with two hyphens", isIsogram("up-to-date"), false)}) diff --git a/test_templates/Isogram_template.res b/test_templates/Isogram_template.res new file mode 100644 index 0000000..06c840c --- /dev/null +++ b/test_templates/Isogram_template.res @@ -0,0 +1,16 @@ +open Node + +let slug = fileURLToPath(%raw(`import.meta.url`))->basename->Utils.filenameToSlug + +let template = (case: GetCases.case) => { + let number = Utils.getTestCaseInput(case, "phrase") + + // EDIT THIS WITH YOUR ASSERTIONS + AssertionGenerators.equal( + ~message=case.description, + ~actual=`isIsogram(${number})`, + ~expected=JSON.stringify(case.expected), + ) +} + +TestGenerator.generateTests(slug, template, [Equal]) From ff8cc5bff1bac1f4763741cc68c93e1d6e4e85df Mon Sep 17 00:00:00 2001 From: Owen Date: Fri, 20 Mar 2026 09:47:23 +0100 Subject: [PATCH 30/44] rename args --- test_templates/Leap_template.res | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 test_templates/Leap_template.res diff --git a/test_templates/Leap_template.res b/test_templates/Leap_template.res new file mode 100644 index 0000000..bf192a7 --- /dev/null +++ b/test_templates/Leap_template.res @@ -0,0 +1,16 @@ +open Node + +let slug = fileURLToPath(%raw(`import.meta.url`))->basename->Utils.filenameToSlug + +let template = (case: GetCases.case) => { + let year = Utils.getTestCaseInput(case, "year") + + // EDIT THIS WITH YOUR ASSERTIONS + AssertionGenerators.equal( + ~message=case.description, + ~actual=`isLeapYear(${year})`, + ~expected=JSON.stringify(case.expected), + ) +} + +TestGenerator.generateTests(slug, template, [Equal]) From 0c85a9ba270308128b40dafb1e6b167b74a7cce9 Mon Sep 17 00:00:00 2001 From: Owen Date: Fri, 20 Mar 2026 09:48:31 +0100 Subject: [PATCH 31/44] update leap tests --- exercises/practice/leap/.meta/testTemplate.js | 19 ---------- exercises/practice/leap/tests/Leap_test.res | 38 +++++-------------- test_templates/Isogram_template.res | 4 +- 3 files changed, 12 insertions(+), 49 deletions(-) delete mode 100644 exercises/practice/leap/.meta/testTemplate.js diff --git a/exercises/practice/leap/.meta/testTemplate.js b/exercises/practice/leap/.meta/testTemplate.js deleted file mode 100644 index bd22cca..0000000 --- a/exercises/practice/leap/.meta/testTemplate.js +++ /dev/null @@ -1,19 +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 { year } = c.input - - return `assertEqual(~message="${c.description}", isLeapYear(${year}), ${c.expected})` -} - -generateTests(__dirname, slug, assertionFunctions, template) \ No newline at end of file diff --git a/exercises/practice/leap/tests/Leap_test.res b/exercises/practice/leap/tests/Leap_test.res index c04cf56..aaeb2c9 100644 --- a/exercises/practice/leap/tests/Leap_test.res +++ b/exercises/practice/leap/tests/Leap_test.res @@ -1,40 +1,22 @@ open Test open Leap -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("year not divisible by 4 in common year", () => { - assertEqual(~message="year not divisible by 4 in common year", isLeapYear(2015), false) -}) +test("year not divisible by 4 in common year", () => {equal(~message="year not divisible by 4 in common year", isLeapYear(2015), false)}) -test("year divisible by 2, not divisible by 4 in common year", () => { - assertEqual(~message="year divisible by 2, not divisible by 4 in common year", isLeapYear(1970), false) -}) +test("year divisible by 2, not divisible by 4 in common year", () => {equal(~message="year divisible by 2, not divisible by 4 in common year", isLeapYear(1970), false)}) -test("year divisible by 4, not divisible by 100 in leap year", () => { - assertEqual(~message="year divisible by 4, not divisible by 100 in leap year", isLeapYear(1996), true) -}) +test("year divisible by 4, not divisible by 100 in leap year", () => {equal(~message="year divisible by 4, not divisible by 100 in leap year", isLeapYear(1996), true)}) -test("year divisible by 4 and 5 is still a leap year", () => { - assertEqual(~message="year divisible by 4 and 5 is still a leap year", isLeapYear(1960), true) -}) +test("year divisible by 4 and 5 is still a leap year", () => {equal(~message="year divisible by 4 and 5 is still a leap year", isLeapYear(1960), true)}) -test("year divisible by 100, not divisible by 400 in common year", () => { - assertEqual(~message="year divisible by 100, not divisible by 400 in common year", isLeapYear(2100), false) -}) +test("year divisible by 100, not divisible by 400 in common year", () => {equal(~message="year divisible by 100, not divisible by 400 in common year", isLeapYear(2100), false)}) -test("year divisible by 100 but not by 3 is still not a leap year", () => { - assertEqual(~message="year divisible by 100 but not by 3 is still not a leap year", isLeapYear(1900), false) -}) +test("year divisible by 100 but not by 3 is still not a leap year", () => {equal(~message="year divisible by 100 but not by 3 is still not a leap year", isLeapYear(1900), false)}) -test("year divisible by 400 is leap year", () => { - assertEqual(~message="year divisible by 400 is leap year", isLeapYear(2000), true) -}) +test("year divisible by 400 is leap year", () => {equal(~message="year divisible by 400 is leap year", isLeapYear(2000), true)}) -test("year divisible by 400 but not by 125 is still a leap year", () => { - assertEqual(~message="year divisible by 400 but not by 125 is still a leap year", isLeapYear(2400), true) -}) +test("year divisible by 400 but not by 125 is still a leap year", () => {equal(~message="year divisible by 400 but not by 125 is still a leap year", isLeapYear(2400), true)}) -test("year divisible by 200, not divisible by 400 in common year", () => { - assertEqual(~message="year divisible by 200, not divisible by 400 in common year", isLeapYear(1800), false) -}) +test("year divisible by 200, not divisible by 400 in common year", () => {equal(~message="year divisible by 200, not divisible by 400 in common year", isLeapYear(1800), false)}) diff --git a/test_templates/Isogram_template.res b/test_templates/Isogram_template.res index 06c840c..0fd39b2 100644 --- a/test_templates/Isogram_template.res +++ b/test_templates/Isogram_template.res @@ -3,12 +3,12 @@ open Node let slug = fileURLToPath(%raw(`import.meta.url`))->basename->Utils.filenameToSlug let template = (case: GetCases.case) => { - let number = Utils.getTestCaseInput(case, "phrase") + let phrase = Utils.getTestCaseInput(case, "phrase") // EDIT THIS WITH YOUR ASSERTIONS AssertionGenerators.equal( ~message=case.description, - ~actual=`isIsogram(${number})`, + ~actual=`isIsogram(${phrase})`, ~expected=JSON.stringify(case.expected), ) } From b3aa06c9bcedf3d7df8950b56e1312e6d3d534ae Mon Sep 17 00:00:00 2001 From: Owen Date: Fri, 20 Mar 2026 09:50:24 +0100 Subject: [PATCH 32/44] update pangram tests --- .../practice/pangram/.meta/testTemplate.js | 18 -------- .../practice/pangram/tests/Pangram_test.res | 42 +++++-------------- test_templates/Pangram_template.res | 16 +++++++ 3 files changed, 27 insertions(+), 49 deletions(-) delete mode 100644 exercises/practice/pangram/.meta/testTemplate.js create mode 100644 test_templates/Pangram_template.res diff --git a/exercises/practice/pangram/.meta/testTemplate.js b/exercises/practice/pangram/.meta/testTemplate.js deleted file mode 100644 index f1417f7..0000000 --- a/exercises/practice/pangram/.meta/testTemplate.js +++ /dev/null @@ -1,18 +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 sentence = JSON.stringify(c.input.sentence) - return `assertEqual(~message="${c.description}", isPangram(${sentence}), ${c.expected})` -} - -generateTests(__dirname, slug, assertionFunctions, template) \ No newline at end of file diff --git a/exercises/practice/pangram/tests/Pangram_test.res b/exercises/practice/pangram/tests/Pangram_test.res index 9e60de6..ff7630c 100644 --- a/exercises/practice/pangram/tests/Pangram_test.res +++ b/exercises/practice/pangram/tests/Pangram_test.res @@ -1,44 +1,24 @@ open Test open Pangram -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("empty sentence", () => { - assertEqual(~message="empty sentence", isPangram(""), false) -}) +test("empty sentence", () => {equal(~message="empty sentence", isPangram(""), false)}) -test("perfect lower case", () => { - assertEqual(~message="perfect lower case", isPangram("abcdefghijklmnopqrstuvwxyz"), true) -}) +test("perfect lower case", () => {equal(~message="perfect lower case", isPangram("abcdefghijklmnopqrstuvwxyz"), true)}) -test("only lower case", () => { - assertEqual(~message="only lower case", isPangram("the quick brown fox jumps over the lazy dog"), true) -}) +test("only lower case", () => {equal(~message="only lower case", isPangram("the quick brown fox jumps over the lazy dog"), true)}) -test("missing the letter 'x'", () => { - assertEqual(~message="missing the letter 'x'", isPangram("a quick movement of the enemy will jeopardize five gunboats"), false) -}) +test("missing the letter 'x'", () => {equal(~message="missing the letter 'x'", isPangram("a quick movement of the enemy will jeopardize five gunboats"), false)}) -test("missing the letter 'h'", () => { - assertEqual(~message="missing the letter 'h'", isPangram("five boxing wizards jump quickly at it"), false) -}) +test("missing the letter 'h'", () => {equal(~message="missing the letter 'h'", isPangram("five boxing wizards jump quickly at it"), false)}) -test("with underscores", () => { - assertEqual(~message="with underscores", isPangram("the_quick_brown_fox_jumps_over_the_lazy_dog"), true) -}) +test("with underscores", () => {equal(~message="with underscores", isPangram("the_quick_brown_fox_jumps_over_the_lazy_dog"), true)}) -test("with numbers", () => { - assertEqual(~message="with numbers", isPangram("the 1 quick brown fox jumps over the 2 lazy dogs"), true) -}) +test("with numbers", () => {equal(~message="with numbers", isPangram("the 1 quick brown fox jumps over the 2 lazy dogs"), true)}) -test("missing letters replaced by numbers", () => { - assertEqual(~message="missing letters replaced by numbers", isPangram("7h3 qu1ck brown fox jumps ov3r 7h3 lazy dog"), false) -}) +test("missing letters replaced by numbers", () => {equal(~message="missing letters replaced by numbers", isPangram("7h3 qu1ck brown fox jumps ov3r 7h3 lazy dog"), false)}) -test("mixed case and punctuation", () => { - assertEqual(~message="mixed case and punctuation", isPangram("\"Five quacking Zephyrs jolt my wax bed.\""), true) -}) +test("mixed case and punctuation", () => {equal(~message="mixed case and punctuation", isPangram("\"Five quacking Zephyrs jolt my wax bed.\""), true)}) -test("a-m and A-M are 26 different characters but not a pangram", () => { - assertEqual(~message="a-m and A-M are 26 different characters but not a pangram", isPangram("abcdefghijklm ABCDEFGHIJKLM"), false) -}) +test("a-m and A-M are 26 different characters but not a pangram", () => {equal(~message="a-m and A-M are 26 different characters but not a pangram", isPangram("abcdefghijklm ABCDEFGHIJKLM"), false)}) diff --git a/test_templates/Pangram_template.res b/test_templates/Pangram_template.res new file mode 100644 index 0000000..7d5a14f --- /dev/null +++ b/test_templates/Pangram_template.res @@ -0,0 +1,16 @@ +open Node + +let slug = fileURLToPath(%raw(`import.meta.url`))->basename->Utils.filenameToSlug + +let template = (case: GetCases.case) => { + let sentence = Utils.getTestCaseInput(case, "sentence") + + // EDIT THIS WITH YOUR ASSERTIONS + AssertionGenerators.equal( + ~message=case.description, + ~actual=`isPangram(${sentence})`, + ~expected=JSON.stringify(case.expected), + ) +} + +TestGenerator.generateTests(slug, template, [Equal]) From 96eb634901bc2eb622eea30e95e40360b2c75b6c Mon Sep 17 00:00:00 2001 From: Owen Date: Fri, 20 Mar 2026 09:56:58 +0100 Subject: [PATCH 33/44] updater phone-number tests --- .../phone-number/.meta/testTemplate.js | 22 ----------- .../phone-number/tests/PhoneNumber_test.res | 39 +++++++++---------- test_templates/PhoneNumber_template.res | 21 ++++++++++ 3 files changed, 40 insertions(+), 42 deletions(-) delete mode 100644 exercises/practice/phone-number/.meta/testTemplate.js create mode 100644 test_templates/PhoneNumber_template.res diff --git a/exercises/practice/phone-number/.meta/testTemplate.js b/exercises/practice/phone-number/.meta/testTemplate.js deleted file mode 100644 index e9d28cb..0000000 --- a/exercises/practice/phone-number/.meta/testTemplate.js +++ /dev/null @@ -1,22 +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'; -import { type } from 'node:os'; - -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 expected = typeof c.expected == 'string' - ? `Some("${c.expected}")` - : "None" - - return `assertEqual(~message="${c.description}", clean("${c.input.phrase}"), ${expected})` -} - -generateTests(__dirname, slug, assertionFunctions, template) \ No newline at end of file diff --git a/exercises/practice/phone-number/tests/PhoneNumber_test.res b/exercises/practice/phone-number/tests/PhoneNumber_test.res index 93cb3f6..4010726 100644 --- a/exercises/practice/phone-number/tests/PhoneNumber_test.res +++ b/exercises/practice/phone-number/tests/PhoneNumber_test.res @@ -1,19 +1,18 @@ open Test open PhoneNumber -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("cleans the number", () => { - assertEqual(~message="cleans the number", clean("(223) 456-7890"), Some("2234567890")) + equal(~message="cleans the number", clean("(223) 456-7890"), Some("2234567890")) }) test("cleans numbers with dots", () => { - assertEqual(~message="cleans numbers with dots", clean("223.456.7890"), Some("2234567890")) + equal(~message="cleans numbers with dots", clean("223.456.7890"), Some("2234567890")) }) test("cleans numbers with multiple spaces", () => { - assertEqual( + equal( ~message="cleans numbers with multiple spaces", clean("223 456 7890 "), Some("2234567890"), @@ -21,15 +20,15 @@ test("cleans numbers with multiple spaces", () => { }) test("invalid when 9 digits", () => { - assertEqual(~message="invalid when 9 digits", clean("123456789"), None) + equal(~message="invalid when 9 digits", clean("123456789"), None) }) test("invalid when 11 digits does not start with a 1", () => { - assertEqual(~message="invalid when 11 digits does not start with a 1", clean("22234567890"), None) + equal(~message="invalid when 11 digits does not start with a 1", clean("22234567890"), None) }) test("valid when 11 digits and starting with 1", () => { - assertEqual( + equal( ~message="valid when 11 digits and starting with 1", clean("12234567890"), Some("2234567890"), @@ -37,7 +36,7 @@ test("valid when 11 digits and starting with 1", () => { }) test("valid when 11 digits and starting with 1 even with punctuation", () => { - assertEqual( + equal( ~message="valid when 11 digits and starting with 1 even with punctuation", clean("+1 (223) 456-7890"), Some("2234567890"), @@ -45,35 +44,35 @@ test("valid when 11 digits and starting with 1 even with punctuation", () => { }) test("invalid when more than 11 digits", () => { - assertEqual(~message="invalid when more than 11 digits", clean("321234567890"), None) + equal(~message="invalid when more than 11 digits", clean("321234567890"), None) }) test("invalid with letters", () => { - assertEqual(~message="invalid with letters", clean("523-abc-7890"), None) + equal(~message="invalid with letters", clean("523-abc-7890"), None) }) test("invalid with punctuations", () => { - assertEqual(~message="invalid with punctuations", clean("523-@:!-7890"), None) + equal(~message="invalid with punctuations", clean("523-@:!-7890"), None) }) test("invalid if area code starts with 0", () => { - assertEqual(~message="invalid if area code starts with 0", clean("(023) 456-7890"), None) + equal(~message="invalid if area code starts with 0", clean("(023) 456-7890"), None) }) test("invalid if area code starts with 1", () => { - assertEqual(~message="invalid if area code starts with 1", clean("(123) 456-7890"), None) + equal(~message="invalid if area code starts with 1", clean("(123) 456-7890"), None) }) test("invalid if exchange code starts with 0", () => { - assertEqual(~message="invalid if exchange code starts with 0", clean("(223) 056-7890"), None) + equal(~message="invalid if exchange code starts with 0", clean("(223) 056-7890"), None) }) test("invalid if exchange code starts with 1", () => { - assertEqual(~message="invalid if exchange code starts with 1", clean("(223) 156-7890"), None) + equal(~message="invalid if exchange code starts with 1", clean("(223) 156-7890"), None) }) test("invalid if area code starts with 0 on valid 11-digit number", () => { - assertEqual( + equal( ~message="invalid if area code starts with 0 on valid 11-digit number", clean("1 (023) 456-7890"), None, @@ -81,7 +80,7 @@ test("invalid if area code starts with 0 on valid 11-digit number", () => { }) test("invalid if area code starts with 1 on valid 11-digit number", () => { - assertEqual( + equal( ~message="invalid if area code starts with 1 on valid 11-digit number", clean("1 (123) 456-7890"), None, @@ -89,7 +88,7 @@ test("invalid if area code starts with 1 on valid 11-digit number", () => { }) test("invalid if exchange code starts with 0 on valid 11-digit number", () => { - assertEqual( + equal( ~message="invalid if exchange code starts with 0 on valid 11-digit number", clean("1 (223) 056-7890"), None, @@ -97,7 +96,7 @@ test("invalid if exchange code starts with 0 on valid 11-digit number", () => { }) test("invalid if exchange code starts with 1 on valid 11-digit number", () => { - assertEqual( + equal( ~message="invalid if exchange code starts with 1 on valid 11-digit number", clean("1 (223) 156-7890"), None, diff --git a/test_templates/PhoneNumber_template.res b/test_templates/PhoneNumber_template.res new file mode 100644 index 0000000..1384a71 --- /dev/null +++ b/test_templates/PhoneNumber_template.res @@ -0,0 +1,21 @@ +open Node + +let slug = fileURLToPath(%raw(`import.meta.url`))->basename->Utils.filenameToSlug + +let template = (case: GetCases.case) => { + let phrase = Utils.getTestCaseInput(case, "phrase") + + let expectedStr = switch case.expected->JSON.Decode.string { + | Some(str) => `Some(${JSON.stringify(JSON.Encode.string(str))})` + | None => "None" + } + + // EDIT THIS WITH YOUR ASSERTIONS + AssertionGenerators.equal( + ~message=case.description, + ~actual=`clean(${phrase})`, + ~expected=expectedStr, + ) +} + +TestGenerator.generateTests(slug, template, [Equal]) From 0fece9f03e7081ce8f88e51728c1130a144ab01a Mon Sep 17 00:00:00 2001 From: Owen Date: Fri, 20 Mar 2026 12:45:53 +0100 Subject: [PATCH 34/44] update prism tests --- .../practice/prism/.meta/testTemplate.js | 46 --- exercises/practice/prism/tests/Prism_test.res | 274 +----------------- test_generator/Utils.res | 7 + test_templates/Prism_template.res | 66 +++++ 4 files changed, 88 insertions(+), 305 deletions(-) delete mode 100644 exercises/practice/prism/.meta/testTemplate.js create mode 100644 test_templates/Prism_template.res diff --git a/exercises/practice/prism/.meta/testTemplate.js b/exercises/practice/prism/.meta/testTemplate.js deleted file mode 100644 index ced3836..0000000 --- a/exercises/practice/prism/.meta/testTemplate.js +++ /dev/null @@ -1,46 +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, '..')) - -export const assertionFunctions = [assertEqual] - -const float = n => Number.isInteger(n) ? `${n}.0` : `${n}` - -const point = ({x, y, angle}) => - `{x: ${float(x)}, y: ${float(y)}, angle: ${float(angle)}}` - -export const template = (c) => { - const lines = [] - lines.push(`let start: point = ${point(c.input.start)}`) - - if (c.input.prisms.length <= 1) { - const inner = c.input.prisms.map(p => `{id: ${p.id}, point: ${point(p)}}`).join(', ') - lines.push(`let prisms: array = [${inner}]`) - } else { - lines.push('let prisms: array = [') - for (const p of c.input.prisms) - lines.push(` {id: ${p.id}, point: ${point(p)}},`) - lines.push(' ]') - } - - lines.push('let result = findSequence(start, prisms)') - - const seq = c.expected.sequence - if (seq.length <= 10) { - lines.push(`let expected = [${seq.join(', ')}]`) - } else { - lines.push('let expected = [') - for (let i = 0; i < seq.length; i += 10) - lines.push(' ' + seq.slice(i, i + 10).join(', ') + ',') - lines.push(' ]') - } - - lines.push(`assertEqual(~message="${c.description}", result, expected)`) - return lines.join('\n ') -} - -generateTests(__dirname, slug, assertionFunctions, template) diff --git a/exercises/practice/prism/tests/Prism_test.res b/exercises/practice/prism/tests/Prism_test.res index 45c09c1..480916e 100644 --- a/exercises/practice/prism/tests/Prism_test.res +++ b/exercises/practice/prism/tests/Prism_test.res @@ -1,276 +1,32 @@ open Test open Prism -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("zero prisms", () => { - let start: point = {x: 0.0, y: 0.0, angle: 0.0} - let prisms: array = [] - let result = findSequence(start, prisms) - let expected = [] - assertEqual(~message="zero prisms", result, expected) -}) +test("zero prisms", () => {equal(~message="zero prisms", findSequence({x: 0., y: 0., angle: 0.}, []), [])}) -test("one prism one hit", () => { - let start: point = {x: 0.0, y: 0.0, angle: 0.0} - let prisms: array = [{id: 1, point: {x: 10.0, y: 0.0, angle: 0.0}}] - let result = findSequence(start, prisms) - let expected = [1] - assertEqual(~message="one prism one hit", result, expected) -}) +test("one prism one hit", () => {equal(~message="one prism one hit", findSequence({x: 0., y: 0., angle: 0.}, [{id: 1, point: {x: 10., y: 0., angle: 0.}}]), [1])}) -test("one prism zero hits", () => { - let start: point = {x: 0.0, y: 0.0, angle: 0.0} - let prisms: array = [{id: 1, point: {x: -10.0, y: 0.0, angle: 0.0}}] - let result = findSequence(start, prisms) - let expected = [] - assertEqual(~message="one prism zero hits", result, expected) -}) +test("one prism zero hits", () => {equal(~message="one prism zero hits", findSequence({x: 0., y: 0., angle: 0.}, [{id: 1, point: {x: -10., y: 0., angle: 0.}}]), [])}) -test("going up zero hits", () => { - let start: point = {x: 0.0, y: 0.0, angle: 90.0} - let prisms: array = [ - {id: 3, point: {x: 0.0, y: -10.0, angle: 0.0}}, - {id: 1, point: {x: -10.0, y: 0.0, angle: 0.0}}, - {id: 2, point: {x: 10.0, y: 0.0, angle: 0.0}}, - ] - let result = findSequence(start, prisms) - let expected = [] - assertEqual(~message="going up zero hits", result, expected) -}) +test("going up zero hits", () => {equal(~message="going up zero hits", findSequence({x: 0., y: 0., angle: 90.}, [{id: 3, point: {x: 0., y: -10., angle: 0.}}, {id: 1, point: {x: -10., y: 0., angle: 0.}}, {id: 2, point: {x: 10., y: 0., angle: 0.}}]), [])}) -test("going down zero hits", () => { - let start: point = {x: 0.0, y: 0.0, angle: -90.0} - let prisms: array = [ - {id: 1, point: {x: 10.0, y: 0.0, angle: 0.0}}, - {id: 2, point: {x: 0.0, y: 10.0, angle: 0.0}}, - {id: 3, point: {x: -10.0, y: 0.0, angle: 0.0}}, - ] - let result = findSequence(start, prisms) - let expected = [] - assertEqual(~message="going down zero hits", result, expected) -}) +test("going down zero hits", () => {equal(~message="going down zero hits", findSequence({x: 0., y: 0., angle: -90.}, [{id: 1, point: {x: 10., y: 0., angle: 0.}}, {id: 2, point: {x: 0., y: 10., angle: 0.}}, {id: 3, point: {x: -10., y: 0., angle: 0.}}]), [])}) -test("going left zero hits", () => { - let start: point = {x: 0.0, y: 0.0, angle: 180.0} - let prisms: array = [ - {id: 2, point: {x: 0.0, y: 10.0, angle: 0.0}}, - {id: 3, point: {x: 10.0, y: 0.0, angle: 0.0}}, - {id: 1, point: {x: 0.0, y: -10.0, angle: 0.0}}, - ] - let result = findSequence(start, prisms) - let expected = [] - assertEqual(~message="going left zero hits", result, expected) -}) +test("going left zero hits", () => {equal(~message="going left zero hits", findSequence({x: 0., y: 0., angle: 180.}, [{id: 2, point: {x: 0., y: 10., angle: 0.}}, {id: 3, point: {x: 10., y: 0., angle: 0.}}, {id: 1, point: {x: 0., y: -10., angle: 0.}}]), [])}) -test("negative angle", () => { - let start: point = {x: 0.0, y: 0.0, angle: -180.0} - let prisms: array = [ - {id: 1, point: {x: 0.0, y: -10.0, angle: 0.0}}, - {id: 2, point: {x: 0.0, y: 10.0, angle: 0.0}}, - {id: 3, point: {x: 10.0, y: 0.0, angle: 0.0}}, - ] - let result = findSequence(start, prisms) - let expected = [] - assertEqual(~message="negative angle", result, expected) -}) +test("negative angle", () => {equal(~message="negative angle", findSequence({x: 0., y: 0., angle: -180.}, [{id: 1, point: {x: 0., y: -10., angle: 0.}}, {id: 2, point: {x: 0., y: 10., angle: 0.}}, {id: 3, point: {x: 10., y: 0., angle: 0.}}]), [])}) -test("large angle", () => { - let start: point = {x: 0.0, y: 0.0, angle: 2340.0} - let prisms: array = [{id: 1, point: {x: 10.0, y: 0.0, angle: 0.0}}] - let result = findSequence(start, prisms) - let expected = [] - assertEqual(~message="large angle", result, expected) -}) +test("large angle", () => {equal(~message="large angle", findSequence({x: 0., y: 0., angle: 2340.}, [{id: 1, point: {x: 10., y: 0., angle: 0.}}]), [])}) -test("upward refraction two hits", () => { - let start: point = {x: 0.0, y: 0.0, angle: 0.0} - let prisms: array = [ - {id: 1, point: {x: 10.0, y: 10.0, angle: 0.0}}, - {id: 2, point: {x: 10.0, y: 0.0, angle: 90.0}}, - ] - let result = findSequence(start, prisms) - let expected = [2, 1] - assertEqual(~message="upward refraction two hits", result, expected) -}) +test("upward refraction two hits", () => {equal(~message="upward refraction two hits", findSequence({x: 0., y: 0., angle: 0.}, [{id: 1, point: {x: 10., y: 10., angle: 0.}}, {id: 2, point: {x: 10., y: 0., angle: 90.}}]), [2, 1])}) -test("downward refraction two hits", () => { - let start: point = {x: 0.0, y: 0.0, angle: 0.0} - let prisms: array = [ - {id: 1, point: {x: 10.0, y: 0.0, angle: -90.0}}, - {id: 2, point: {x: 10.0, y: -10.0, angle: 0.0}}, - ] - let result = findSequence(start, prisms) - let expected = [1, 2] - assertEqual(~message="downward refraction two hits", result, expected) -}) +test("downward refraction two hits", () => {equal(~message="downward refraction two hits", findSequence({x: 0., y: 0., angle: 0.}, [{id: 1, point: {x: 10., y: 0., angle: -90.}}, {id: 2, point: {x: 10., y: -10., angle: 0.}}]), [1, 2])}) -test("same prism twice", () => { - let start: point = {x: 0.0, y: 0.0, angle: 0.0} - let prisms: array = [ - {id: 2, point: {x: 10.0, y: 0.0, angle: 0.0}}, - {id: 1, point: {x: 20.0, y: 0.0, angle: -180.0}}, - ] - let result = findSequence(start, prisms) - let expected = [2, 1, 2] - assertEqual(~message="same prism twice", result, expected) -}) +test("same prism twice", () => {equal(~message="same prism twice", findSequence({x: 0., y: 0., angle: 0.}, [{id: 2, point: {x: 10., y: 0., angle: 0.}}, {id: 1, point: {x: 20., y: 0., angle: -180.}}]), [2, 1, 2])}) -test("simple path", () => { - let start: point = {x: 0.0, y: 0.0, angle: 0.0} - let prisms: array = [ - {id: 3, point: {x: 30.0, y: 10.0, angle: 45.0}}, - {id: 1, point: {x: 10.0, y: 10.0, angle: -90.0}}, - {id: 2, point: {x: 10.0, y: 0.0, angle: 90.0}}, - {id: 4, point: {x: 20.0, y: 0.0, angle: 0.0}}, - ] - let result = findSequence(start, prisms) - let expected = [2, 1, 3] - assertEqual(~message="simple path", result, expected) -}) +test("simple path", () => {equal(~message="simple path", findSequence({x: 0., y: 0., angle: 0.}, [{id: 3, point: {x: 30., y: 10., angle: 45.}}, {id: 1, point: {x: 10., y: 10., angle: -90.}}, {id: 2, point: {x: 10., y: 0., angle: 90.}}, {id: 4, point: {x: 20., y: 0., angle: 0.}}]), [2, 1, 3])}) -test("multiple prisms floating point precision", () => { - let start: point = {x: 0.0, y: 0.0, angle: -6.429} - let prisms: array = [ - {id: 26, point: {x: 5.8, y: 73.4, angle: 6.555}}, - {id: 24, point: {x: 36.2, y: 65.2, angle: -0.304}}, - {id: 20, point: {x: 20.4, y: 82.8, angle: 45.17}}, - {id: 31, point: {x: -20.2, y: 48.8, angle: 30.615}}, - {id: 30, point: {x: 24.0, y: 0.6, angle: 28.771}}, - {id: 29, point: {x: 31.4, y: 79.4, angle: 61.327}}, - {id: 28, point: {x: 36.4, y: 31.4, angle: -18.157}}, - {id: 22, point: {x: 47.0, y: 57.8, angle: 54.745}}, - {id: 38, point: {x: 36.4, y: 79.2, angle: 49.05}}, - {id: 10, point: {x: 37.8, y: 55.2, angle: 11.978}}, - {id: 18, point: {x: -26.0, y: 42.6, angle: 22.661}}, - {id: 25, point: {x: 38.8, y: 76.2, angle: 51.958}}, - {id: 2, point: {x: 0.0, y: 42.4, angle: -21.817}}, - {id: 35, point: {x: 21.4, y: 44.8, angle: -171.579}}, - {id: 7, point: {x: 14.2, y: -1.6, angle: 19.081}}, - {id: 33, point: {x: 11.2, y: 44.4, angle: -165.941}}, - {id: 11, point: {x: 15.4, y: 82.6, angle: 66.262}}, - {id: 16, point: {x: 30.8, y: 6.6, angle: 35.852}}, - {id: 15, point: {x: -3.0, y: 79.2, angle: 53.782}}, - {id: 4, point: {x: 29.0, y: 75.4, angle: 17.016}}, - {id: 23, point: {x: 41.6, y: 59.8, angle: 70.763}}, - {id: 8, point: {x: -10.0, y: 15.8, angle: -9.24}}, - {id: 13, point: {x: 48.6, y: 51.8, angle: 45.812}}, - {id: 1, point: {x: 13.2, y: 77.0, angle: 17.937}}, - {id: 34, point: {x: -8.8, y: 36.8, angle: -4.199}}, - {id: 21, point: {x: 24.4, y: 75.8, angle: 20.783}}, - {id: 17, point: {x: -4.4, y: 74.6, angle: 24.709}}, - {id: 9, point: {x: 30.8, y: 41.8, angle: -165.413}}, - {id: 32, point: {x: 4.2, y: 78.6, angle: 40.892}}, - {id: 37, point: {x: -15.8, y: 47.0, angle: 33.29}}, - {id: 6, point: {x: 1.0, y: 80.6, angle: 51.295}}, - {id: 36, point: {x: -27.0, y: 47.8, angle: 92.52}}, - {id: 14, point: {x: -2.0, y: 34.4, angle: -52.001}}, - {id: 5, point: {x: 23.2, y: 80.2, angle: 31.866}}, - {id: 27, point: {x: -5.6, y: 32.8, angle: -75.303}}, - {id: 12, point: {x: -1.0, y: 0.2, angle: 0.0}}, - {id: 3, point: {x: -6.6, y: 3.2, angle: 46.72}}, - {id: 19, point: {x: -13.8, y: 24.2, angle: -9.205}}, - ] - let result = findSequence(start, prisms) - let expected = [ - 7, 30, 16, 28, 13, 22, 23, 10, 9, 24, - 25, 38, 29, 4, 35, 21, 5, 20, 11, 1, - 33, 26, 32, 6, 15, 17, 2, 14, 27, 34, - 37, 31, 36, 18, 19, 8, 3, 12, - ] - assertEqual(~message="multiple prisms floating point precision", result, expected) -}) +test("multiple prisms floating point precision", () => {equal(~message="multiple prisms floating point precision", findSequence({x: 0., y: 0., angle: -6.429}, [{id: 26, point: {x: 5.8, y: 73.4, angle: 6.555}}, {id: 24, point: {x: 36.2, y: 65.2, angle: -0.304}}, {id: 20, point: {x: 20.4, y: 82.8, angle: 45.17}}, {id: 31, point: {x: -20.2, y: 48.8, angle: 30.615}}, {id: 30, point: {x: 24., y: 0.6, angle: 28.771}}, {id: 29, point: {x: 31.4, y: 79.4, angle: 61.327}}, {id: 28, point: {x: 36.4, y: 31.4, angle: -18.157}}, {id: 22, point: {x: 47., y: 57.8, angle: 54.745}}, {id: 38, point: {x: 36.4, y: 79.2, angle: 49.05}}, {id: 10, point: {x: 37.8, y: 55.2, angle: 11.978}}, {id: 18, point: {x: -26., y: 42.6, angle: 22.661}}, {id: 25, point: {x: 38.8, y: 76.2, angle: 51.958}}, {id: 2, point: {x: 0., y: 42.4, angle: -21.817}}, {id: 35, point: {x: 21.4, y: 44.8, angle: -171.579}}, {id: 7, point: {x: 14.2, y: -1.6, angle: 19.081}}, {id: 33, point: {x: 11.2, y: 44.4, angle: -165.941}}, {id: 11, point: {x: 15.4, y: 82.6, angle: 66.262}}, {id: 16, point: {x: 30.8, y: 6.6, angle: 35.852}}, {id: 15, point: {x: -3., y: 79.2, angle: 53.782}}, {id: 4, point: {x: 29., y: 75.4, angle: 17.016}}, {id: 23, point: {x: 41.6, y: 59.8, angle: 70.763}}, {id: 8, point: {x: -10., y: 15.8, angle: -9.24}}, {id: 13, point: {x: 48.6, y: 51.8, angle: 45.812}}, {id: 1, point: {x: 13.2, y: 77., angle: 17.937}}, {id: 34, point: {x: -8.8, y: 36.8, angle: -4.199}}, {id: 21, point: {x: 24.4, y: 75.8, angle: 20.783}}, {id: 17, point: {x: -4.4, y: 74.6, angle: 24.709}}, {id: 9, point: {x: 30.8, y: 41.8, angle: -165.413}}, {id: 32, point: {x: 4.2, y: 78.6, angle: 40.892}}, {id: 37, point: {x: -15.8, y: 47., angle: 33.29}}, {id: 6, point: {x: 1., y: 80.6, angle: 51.295}}, {id: 36, point: {x: -27., y: 47.8, angle: 92.52}}, {id: 14, point: {x: -2., y: 34.4, angle: -52.001}}, {id: 5, point: {x: 23.2, y: 80.2, angle: 31.866}}, {id: 27, point: {x: -5.6, y: 32.8, angle: -75.303}}, {id: 12, point: {x: -1., y: 0.2, angle: 0.}}, {id: 3, point: {x: -6.6, y: 3.2, angle: 46.72}}, {id: 19, point: {x: -13.8, y: 24.2, angle: -9.205}}]), [7, 30, 16, 28, 13, 22, 23, 10, 9, 24, 25, 38, 29, 4, 35, 21, 5, 20, 11, 1, 33, 26, 32, 6, 15, 17, 2, 14, 27, 34, 37, 31, 36, 18, 19, 8, 3, 12])}) -test("complex path with multiple prisms floating point precision", () => { - let start: point = {x: 0.0, y: 0.0, angle: 0.0} - let prisms: array = [ - {id: 46, point: {x: 37.4, y: 20.6, angle: -88.332}}, - {id: 72, point: {x: -24.2, y: 23.4, angle: -90.774}}, - {id: 25, point: {x: 78.6, y: 7.8, angle: 98.562}}, - {id: 60, point: {x: -58.8, y: 31.6, angle: 115.56}}, - {id: 22, point: {x: 75.2, y: 28.0, angle: 63.515}}, - {id: 2, point: {x: 89.8, y: 27.8, angle: 91.176}}, - {id: 23, point: {x: 9.8, y: 30.8, angle: 30.829}}, - {id: 69, point: {x: 22.8, y: 20.6, angle: -88.315}}, - {id: 44, point: {x: -0.8, y: 15.6, angle: -116.565}}, - {id: 36, point: {x: -24.2, y: 8.2, angle: -90.0}}, - {id: 53, point: {x: -1.2, y: 0.0, angle: 0.0}}, - {id: 52, point: {x: 14.2, y: 24.0, angle: -143.896}}, - {id: 5, point: {x: -65.2, y: 21.6, angle: 93.128}}, - {id: 66, point: {x: 5.4, y: 15.6, angle: 31.608}}, - {id: 51, point: {x: -72.6, y: 21.0, angle: -100.976}}, - {id: 65, point: {x: 48.0, y: 10.2, angle: 87.455}}, - {id: 21, point: {x: -41.8, y: 0.0, angle: 68.352}}, - {id: 18, point: {x: -46.2, y: 19.2, angle: -128.362}}, - {id: 10, point: {x: 74.4, y: 0.4, angle: 90.939}}, - {id: 15, point: {x: 67.6, y: 0.4, angle: 84.958}}, - {id: 35, point: {x: 14.8, y: -0.4, angle: 89.176}}, - {id: 1, point: {x: 83.0, y: 0.2, angle: 89.105}}, - {id: 68, point: {x: 14.6, y: 28.0, angle: -29.867}}, - {id: 67, point: {x: 79.8, y: 18.6, angle: -136.643}}, - {id: 38, point: {x: 53.0, y: 14.6, angle: -90.848}}, - {id: 31, point: {x: -58.0, y: 6.6, angle: -61.837}}, - {id: 74, point: {x: -30.8, y: 0.4, angle: 85.966}}, - {id: 48, point: {x: -4.6, y: 10.0, angle: -161.222}}, - {id: 12, point: {x: 59.0, y: 5.0, angle: -91.164}}, - {id: 33, point: {x: -16.4, y: 18.4, angle: 90.734}}, - {id: 4, point: {x: 82.6, y: 27.6, angle: 71.127}}, - {id: 75, point: {x: -10.2, y: 30.6, angle: -1.108}}, - {id: 28, point: {x: 38.0, y: 0.0, angle: 86.863}}, - {id: 11, point: {x: 64.4, y: -0.2, angle: 92.353}}, - {id: 9, point: {x: -51.4, y: 31.6, angle: 67.249}}, - {id: 26, point: {x: -39.8, y: 30.8, angle: 61.113}}, - {id: 30, point: {x: -34.2, y: 0.6, angle: 111.33}}, - {id: 56, point: {x: -51.0, y: 0.2, angle: 70.445}}, - {id: 41, point: {x: -12.0, y: 0.0, angle: 91.219}}, - {id: 24, point: {x: 63.8, y: 14.4, angle: 86.586}}, - {id: 70, point: {x: -72.8, y: 13.4, angle: -87.238}}, - {id: 3, point: {x: 22.4, y: 7.0, angle: -91.685}}, - {id: 13, point: {x: 34.4, y: 7.0, angle: 90.0}}, - {id: 16, point: {x: -47.4, y: 11.4, angle: -136.02}}, - {id: 6, point: {x: 90.0, y: 0.2, angle: 90.415}}, - {id: 54, point: {x: 44.0, y: 27.8, angle: 85.969}}, - {id: 32, point: {x: -9.0, y: 0.0, angle: 91.615}}, - {id: 8, point: {x: -31.6, y: 30.8, angle: 0.535}}, - {id: 39, point: {x: -12.0, y: 8.2, angle: 90.0}}, - {id: 14, point: {x: -79.6, y: 32.4, angle: 92.342}}, - {id: 42, point: {x: 65.8, y: 20.8, angle: -85.867}}, - {id: 40, point: {x: -65.0, y: 14.0, angle: 87.109}}, - {id: 45, point: {x: 10.6, y: 18.8, angle: 23.697}}, - {id: 71, point: {x: -24.2, y: 18.6, angle: -88.531}}, - {id: 7, point: {x: -72.6, y: 6.4, angle: -89.148}}, - {id: 62, point: {x: -32.0, y: 24.8, angle: -140.8}}, - {id: 49, point: {x: 34.4, y: -0.2, angle: 89.415}}, - {id: 63, point: {x: 74.2, y: 12.6, angle: -138.429}}, - {id: 59, point: {x: 82.8, y: 13.0, angle: -140.177}}, - {id: 34, point: {x: -9.4, y: 23.2, angle: -88.238}}, - {id: 76, point: {x: -57.6, y: 0.0, angle: 1.2}}, - {id: 43, point: {x: 7.0, y: 0.0, angle: 116.565}}, - {id: 20, point: {x: 45.8, y: -0.2, angle: 1.469}}, - {id: 37, point: {x: -16.6, y: 13.2, angle: 84.785}}, - {id: 58, point: {x: -79.0, y: -0.2, angle: 89.481}}, - {id: 50, point: {x: -24.2, y: 12.8, angle: -86.987}}, - {id: 64, point: {x: 59.2, y: 10.2, angle: -92.203}}, - {id: 61, point: {x: -72.0, y: 26.4, angle: -83.66}}, - {id: 47, point: {x: 45.4, y: 5.8, angle: -82.992}}, - {id: 17, point: {x: -52.2, y: 17.8, angle: -52.938}}, - {id: 57, point: {x: -61.8, y: 32.0, angle: 84.627}}, - {id: 29, point: {x: 47.2, y: 28.2, angle: 92.954}}, - {id: 27, point: {x: -4.6, y: 0.2, angle: 87.397}}, - {id: 55, point: {x: -61.4, y: 26.4, angle: 94.086}}, - {id: 73, point: {x: -40.4, y: 13.4, angle: -62.229}}, - {id: 19, point: {x: 53.2, y: 20.6, angle: -87.181}}, - ] - let result = findSequence(start, prisms) - let expected = [ - 43, 44, 66, 45, 52, 35, 49, 13, 3, 69, - 46, 28, 20, 11, 24, 38, 19, 42, 15, 10, - 63, 25, 59, 1, 6, 2, 4, 67, 22, 29, - 65, 64, 12, 47, 54, 68, 23, 75, 8, 26, - 18, 9, 60, 17, 31, 7, 70, 40, 5, 51, - 61, 55, 57, 14, 58, 76, 56, 16, 21, 30, - 73, 62, 74, 41, 39, 36, 50, 37, 33, 71, - 72, 34, 32, 27, 48, 53, - ] - assertEqual(~message="complex path with multiple prisms floating point precision", result, expected) -}) +test("complex path with multiple prisms floating point precision", () => {equal(~message="complex path with multiple prisms floating point precision", findSequence({x: 0., y: 0., angle: 0.}, [{id: 46, point: {x: 37.4, y: 20.6, angle: -88.332}}, {id: 72, point: {x: -24.2, y: 23.4, angle: -90.774}}, {id: 25, point: {x: 78.6, y: 7.8, angle: 98.562}}, {id: 60, point: {x: -58.8, y: 31.6, angle: 115.56}}, {id: 22, point: {x: 75.2, y: 28., angle: 63.515}}, {id: 2, point: {x: 89.8, y: 27.8, angle: 91.176}}, {id: 23, point: {x: 9.8, y: 30.8, angle: 30.829}}, {id: 69, point: {x: 22.8, y: 20.6, angle: -88.315}}, {id: 44, point: {x: -0.8, y: 15.6, angle: -116.565}}, {id: 36, point: {x: -24.2, y: 8.2, angle: -90.}}, {id: 53, point: {x: -1.2, y: 0., angle: 0.}}, {id: 52, point: {x: 14.2, y: 24., angle: -143.896}}, {id: 5, point: {x: -65.2, y: 21.6, angle: 93.128}}, {id: 66, point: {x: 5.4, y: 15.6, angle: 31.608}}, {id: 51, point: {x: -72.6, y: 21., angle: -100.976}}, {id: 65, point: {x: 48., y: 10.2, angle: 87.455}}, {id: 21, point: {x: -41.8, y: 0., angle: 68.352}}, {id: 18, point: {x: -46.2, y: 19.2, angle: -128.362}}, {id: 10, point: {x: 74.4, y: 0.4, angle: 90.939}}, {id: 15, point: {x: 67.6, y: 0.4, angle: 84.958}}, {id: 35, point: {x: 14.8, y: -0.4, angle: 89.176}}, {id: 1, point: {x: 83., y: 0.2, angle: 89.105}}, {id: 68, point: {x: 14.6, y: 28., angle: -29.867}}, {id: 67, point: {x: 79.8, y: 18.6, angle: -136.643}}, {id: 38, point: {x: 53., y: 14.6, angle: -90.848}}, {id: 31, point: {x: -58., y: 6.6, angle: -61.837}}, {id: 74, point: {x: -30.8, y: 0.4, angle: 85.966}}, {id: 48, point: {x: -4.6, y: 10., angle: -161.222}}, {id: 12, point: {x: 59., y: 5., angle: -91.164}}, {id: 33, point: {x: -16.4, y: 18.4, angle: 90.734}}, {id: 4, point: {x: 82.6, y: 27.6, angle: 71.127}}, {id: 75, point: {x: -10.2, y: 30.6, angle: -1.108}}, {id: 28, point: {x: 38., y: 0., angle: 86.863}}, {id: 11, point: {x: 64.4, y: -0.2, angle: 92.353}}, {id: 9, point: {x: -51.4, y: 31.6, angle: 67.249}}, {id: 26, point: {x: -39.8, y: 30.8, angle: 61.113}}, {id: 30, point: {x: -34.2, y: 0.6, angle: 111.33}}, {id: 56, point: {x: -51., y: 0.2, angle: 70.445}}, {id: 41, point: {x: -12., y: 0., angle: 91.219}}, {id: 24, point: {x: 63.8, y: 14.4, angle: 86.586}}, {id: 70, point: {x: -72.8, y: 13.4, angle: -87.238}}, {id: 3, point: {x: 22.4, y: 7., angle: -91.685}}, {id: 13, point: {x: 34.4, y: 7., angle: 90.}}, {id: 16, point: {x: -47.4, y: 11.4, angle: -136.02}}, {id: 6, point: {x: 90., y: 0.2, angle: 90.415}}, {id: 54, point: {x: 44., y: 27.8, angle: 85.969}}, {id: 32, point: {x: -9., y: 0., angle: 91.615}}, {id: 8, point: {x: -31.6, y: 30.8, angle: 0.535}}, {id: 39, point: {x: -12., y: 8.2, angle: 90.}}, {id: 14, point: {x: -79.6, y: 32.4, angle: 92.342}}, {id: 42, point: {x: 65.8, y: 20.8, angle: -85.867}}, {id: 40, point: {x: -65., y: 14., angle: 87.109}}, {id: 45, point: {x: 10.6, y: 18.8, angle: 23.697}}, {id: 71, point: {x: -24.2, y: 18.6, angle: -88.531}}, {id: 7, point: {x: -72.6, y: 6.4, angle: -89.148}}, {id: 62, point: {x: -32., y: 24.8, angle: -140.8}}, {id: 49, point: {x: 34.4, y: -0.2, angle: 89.415}}, {id: 63, point: {x: 74.2, y: 12.6, angle: -138.429}}, {id: 59, point: {x: 82.8, y: 13., angle: -140.177}}, {id: 34, point: {x: -9.4, y: 23.2, angle: -88.238}}, {id: 76, point: {x: -57.6, y: 0., angle: 1.2}}, {id: 43, point: {x: 7., y: 0., angle: 116.565}}, {id: 20, point: {x: 45.8, y: -0.2, angle: 1.469}}, {id: 37, point: {x: -16.6, y: 13.2, angle: 84.785}}, {id: 58, point: {x: -79., y: -0.2, angle: 89.481}}, {id: 50, point: {x: -24.2, y: 12.8, angle: -86.987}}, {id: 64, point: {x: 59.2, y: 10.2, angle: -92.203}}, {id: 61, point: {x: -72., y: 26.4, angle: -83.66}}, {id: 47, point: {x: 45.4, y: 5.8, angle: -82.992}}, {id: 17, point: {x: -52.2, y: 17.8, angle: -52.938}}, {id: 57, point: {x: -61.8, y: 32., angle: 84.627}}, {id: 29, point: {x: 47.2, y: 28.2, angle: 92.954}}, {id: 27, point: {x: -4.6, y: 0.2, angle: 87.397}}, {id: 55, point: {x: -61.4, y: 26.4, angle: 94.086}}, {id: 73, point: {x: -40.4, y: 13.4, angle: -62.229}}, {id: 19, point: {x: 53.2, y: 20.6, angle: -87.181}}]), [43, 44, 66, 45, 52, 35, 49, 13, 3, 69, 46, 28, 20, 11, 24, 38, 19, 42, 15, 10, 63, 25, 59, 1, 6, 2, 4, 67, 22, 29, 65, 64, 12, 47, 54, 68, 23, 75, 8, 26, 18, 9, 60, 17, 31, 7, 70, 40, 5, 51, 61, 55, 57, 14, 58, 76, 56, 16, 21, 30, 73, 62, 74, 41, 39, 36, 50, 37, 33, 71, 72, 34, 32, 27, 48, 53])}) diff --git a/test_generator/Utils.res b/test_generator/Utils.res index 435d44c..c14d713 100644 --- a/test_generator/Utils.res +++ b/test_generator/Utils.res @@ -15,3 +15,10 @@ let filenameToSlug = (str: string) => { ->String.replaceRegExp(/([a-z0-9])([A-Z])/g, "$1-$2") ->String.toLowerCase } + +// JSON helper - if there's no decimal point, add one for ReScript float syntax (e.g., "10" -> "10.") +let toResFloat = (json: JSON.t) => { + let f = json->JSON.Decode.float->Option.getOr(0.0) + let s = f->Float.toString + !String.includes(s, ".") ? s ++ "." : s +} diff --git a/test_templates/Prism_template.res b/test_templates/Prism_template.res new file mode 100644 index 0000000..f89dce1 --- /dev/null +++ b/test_templates/Prism_template.res @@ -0,0 +1,66 @@ +open Node +open Utils + +let slug = fileURLToPath(%raw(`import.meta.url`))->basename->Utils.filenameToSlug + +let toPointLit = (json): string => { + let dict = json->JSON.Decode.object->Option.getOrThrow + let x = dict->Dict.get("x")->Option.mapOr("0.", toResFloat) + let y = dict->Dict.get("y")->Option.mapOr("0.", toResFloat) + let angle = dict->Dict.get("angle")->Option.mapOr("0.", toResFloat) + + `{x: ${x}, y: ${y}, angle: ${angle}}` +} + +let toPrismLit = (json): string => { + let dict = json->JSON.Decode.object->Option.getOrThrow + + let id = + dict + ->Dict.get("id") + ->Option.flatMap(JSON.Decode.float) + ->Option.mapOr("0", f => f->Float.toInt->Int.toString) + + let point = toPointLit(json) + + `{id: ${id}, point: ${point}}` +} + +let expectedSeq = (case: GetCases.case) => + case.expected + ->JSON.Decode.object + ->Option.flatMap(dict => dict->Dict.get("sequence")) + ->Option.flatMap(JSON.Decode.array) + ->Option.map(arr => { + let items = + arr + ->Array.map(item => item->JSON.Decode.float->Option.getOr(0.)->Float.toInt->Int.toString) + ->Array.join(", ") + "[" ++ items ++ "]" + }) + ->Option.getOr("[]") + +let template = (case: GetCases.case) => { + let inputDict = case.input->JSON.Decode.object->Option.getOrThrow + + let start = + inputDict + ->Dict.get("start") + ->Option.map(toPointLit) + ->Option.getOr("{}") + + let prisms = + inputDict + ->Dict.get("prisms") + ->Option.flatMap(JSON.Decode.array) + ->Option.map(arr => "[" ++ arr->Array.map(toPrismLit)->Array.join(", ") ++ "]") + ->Option.getOr("[]") + + AssertionGenerators.equal( + ~message=case.description, + ~actual=`findSequence(${start}, ${prisms})`, + ~expected=expectedSeq(case), + ) +} + +TestGenerator.generateTests(slug, template, [Equal]) From 000fc954cec0a63f1ea841c238e06e86383899d3 Mon Sep 17 00:00:00 2001 From: Owen Date: Fri, 20 Mar 2026 12:48:57 +0100 Subject: [PATCH 35/44] update raindrops tests --- .../practice/raindrops/.meta/testTemplate.js | 17 ----- .../raindrops/tests/Raindrops_test.res | 74 +++++-------------- test_templates/Raindrops_template.res | 16 ++++ 3 files changed, 35 insertions(+), 72 deletions(-) delete mode 100644 exercises/practice/raindrops/.meta/testTemplate.js create mode 100644 test_templates/Raindrops_template.res diff --git a/exercises/practice/raindrops/.meta/testTemplate.js b/exercises/practice/raindrops/.meta/testTemplate.js deleted file mode 100644 index 7e2ad5b..0000000 --- a/exercises/practice/raindrops/.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}", convert(${c.input.number}), "${c.expected}")` -} - -generateTests(__dirname, slug, assertionFunctions, template) \ No newline at end of file diff --git a/exercises/practice/raindrops/tests/Raindrops_test.res b/exercises/practice/raindrops/tests/Raindrops_test.res index 45ad2a7..6211a8d 100644 --- a/exercises/practice/raindrops/tests/Raindrops_test.res +++ b/exercises/practice/raindrops/tests/Raindrops_test.res @@ -1,76 +1,40 @@ open Test open Raindrops -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("the sound for 1 is 1", () => { - assertEqual(~message="the sound for 1 is 1", convert(1), "1") -}) +test("the sound for 1 is 1", () => {equal(~message="the sound for 1 is 1", convert(1), "1")}) -test("the sound for 3 is Pling", () => { - assertEqual(~message="the sound for 3 is Pling", convert(3), "Pling") -}) +test("the sound for 3 is Pling", () => {equal(~message="the sound for 3 is Pling", convert(3), "Pling")}) -test("the sound for 5 is Plang", () => { - assertEqual(~message="the sound for 5 is Plang", convert(5), "Plang") -}) +test("the sound for 5 is Plang", () => {equal(~message="the sound for 5 is Plang", convert(5), "Plang")}) -test("the sound for 7 is Plong", () => { - assertEqual(~message="the sound for 7 is Plong", convert(7), "Plong") -}) +test("the sound for 7 is Plong", () => {equal(~message="the sound for 7 is Plong", convert(7), "Plong")}) -test("the sound for 6 is Pling as it has a factor 3", () => { - assertEqual(~message="the sound for 6 is Pling as it has a factor 3", convert(6), "Pling") -}) +test("the sound for 6 is Pling as it has a factor 3", () => {equal(~message="the sound for 6 is Pling as it has a factor 3", convert(6), "Pling")}) -test("2 to the power 3 does not make a raindrop sound as 3 is the exponent not the base", () => { - assertEqual(~message="2 to the power 3 does not make a raindrop sound as 3 is the exponent not the base", convert(8), "8") -}) +test("2 to the power 3 does not make a raindrop sound as 3 is the exponent not the base", () => {equal(~message="2 to the power 3 does not make a raindrop sound as 3 is the exponent not the base", convert(8), "8")}) -test("the sound for 9 is Pling as it has a factor 3", () => { - assertEqual(~message="the sound for 9 is Pling as it has a factor 3", convert(9), "Pling") -}) +test("the sound for 9 is Pling as it has a factor 3", () => {equal(~message="the sound for 9 is Pling as it has a factor 3", convert(9), "Pling")}) -test("the sound for 10 is Plang as it has a factor 5", () => { - assertEqual(~message="the sound for 10 is Plang as it has a factor 5", convert(10), "Plang") -}) +test("the sound for 10 is Plang as it has a factor 5", () => {equal(~message="the sound for 10 is Plang as it has a factor 5", convert(10), "Plang")}) -test("the sound for 14 is Plong as it has a factor of 7", () => { - assertEqual(~message="the sound for 14 is Plong as it has a factor of 7", convert(14), "Plong") -}) +test("the sound for 14 is Plong as it has a factor of 7", () => {equal(~message="the sound for 14 is Plong as it has a factor of 7", convert(14), "Plong")}) -test("the sound for 15 is PlingPlang as it has factors 3 and 5", () => { - assertEqual(~message="the sound for 15 is PlingPlang as it has factors 3 and 5", convert(15), "PlingPlang") -}) +test("the sound for 15 is PlingPlang as it has factors 3 and 5", () => {equal(~message="the sound for 15 is PlingPlang as it has factors 3 and 5", convert(15), "PlingPlang")}) -test("the sound for 21 is PlingPlong as it has factors 3 and 7", () => { - assertEqual(~message="the sound for 21 is PlingPlong as it has factors 3 and 7", convert(21), "PlingPlong") -}) +test("the sound for 21 is PlingPlong as it has factors 3 and 7", () => {equal(~message="the sound for 21 is PlingPlong as it has factors 3 and 7", convert(21), "PlingPlong")}) -test("the sound for 25 is Plang as it has a factor 5", () => { - assertEqual(~message="the sound for 25 is Plang as it has a factor 5", convert(25), "Plang") -}) +test("the sound for 25 is Plang as it has a factor 5", () => {equal(~message="the sound for 25 is Plang as it has a factor 5", convert(25), "Plang")}) -test("the sound for 27 is Pling as it has a factor 3", () => { - assertEqual(~message="the sound for 27 is Pling as it has a factor 3", convert(27), "Pling") -}) +test("the sound for 27 is Pling as it has a factor 3", () => {equal(~message="the sound for 27 is Pling as it has a factor 3", convert(27), "Pling")}) -test("the sound for 35 is PlangPlong as it has factors 5 and 7", () => { - assertEqual(~message="the sound for 35 is PlangPlong as it has factors 5 and 7", convert(35), "PlangPlong") -}) +test("the sound for 35 is PlangPlong as it has factors 5 and 7", () => {equal(~message="the sound for 35 is PlangPlong as it has factors 5 and 7", convert(35), "PlangPlong")}) -test("the sound for 49 is Plong as it has a factor 7", () => { - assertEqual(~message="the sound for 49 is Plong as it has a factor 7", convert(49), "Plong") -}) +test("the sound for 49 is Plong as it has a factor 7", () => {equal(~message="the sound for 49 is Plong as it has a factor 7", convert(49), "Plong")}) -test("the sound for 52 is 52", () => { - assertEqual(~message="the sound for 52 is 52", convert(52), "52") -}) +test("the sound for 52 is 52", () => {equal(~message="the sound for 52 is 52", convert(52), "52")}) -test("the sound for 105 is PlingPlangPlong as it has factors 3, 5 and 7", () => { - assertEqual(~message="the sound for 105 is PlingPlangPlong as it has factors 3, 5 and 7", convert(105), "PlingPlangPlong") -}) +test("the sound for 105 is PlingPlangPlong as it has factors 3, 5 and 7", () => {equal(~message="the sound for 105 is PlingPlangPlong as it has factors 3, 5 and 7", convert(105), "PlingPlangPlong")}) -test("the sound for 3125 is Plang as it has a factor 5", () => { - assertEqual(~message="the sound for 3125 is Plang as it has a factor 5", convert(3125), "Plang") -}) +test("the sound for 3125 is Plang as it has a factor 5", () => {equal(~message="the sound for 3125 is Plang as it has a factor 5", convert(3125), "Plang")}) diff --git a/test_templates/Raindrops_template.res b/test_templates/Raindrops_template.res new file mode 100644 index 0000000..89bb29e --- /dev/null +++ b/test_templates/Raindrops_template.res @@ -0,0 +1,16 @@ +open Node + +let slug = fileURLToPath(%raw(`import.meta.url`))->basename->Utils.filenameToSlug + +let template = (case: GetCases.case) => { + let number = Utils.getTestCaseInput(case, "number") + + // EDIT THIS WITH YOUR ASSERTIONS + AssertionGenerators.equal( + ~message=case.description, + ~actual=`convert(${number})`, + ~expected=JSON.stringify(case.expected), + ) +} + +TestGenerator.generateTests(slug, template, [Equal]) From f448515f9e88bbc083a63f6820b8520d0430d66f Mon Sep 17 00:00:00 2001 From: Owen Date: Fri, 20 Mar 2026 13:33:12 +0100 Subject: [PATCH 36/44] update resistor color duo tests --- .../resistor-color-duo/.meta/testTemplate.js | 17 ----------- .../tests/ResistorColorDuo_test.res | 30 +++++-------------- test_templates/ResistorColorDuo_template.res | 16 ++++++++++ 3 files changed, 24 insertions(+), 39 deletions(-) delete mode 100644 exercises/practice/resistor-color-duo/.meta/testTemplate.js create mode 100644 test_templates/ResistorColorDuo_template.res diff --git a/exercises/practice/resistor-color-duo/.meta/testTemplate.js b/exercises/practice/resistor-color-duo/.meta/testTemplate.js deleted file mode 100644 index 400cdf3..0000000 --- a/exercises/practice/resistor-color-duo/.meta/testTemplate.js +++ /dev/null @@ -1,17 +0,0 @@ -import path from 'node:path'; -import { fileURLToPath } from 'node:url'; -import { intEqual } 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 = [ intEqual ] - -// EDIT THIS WITH YOUR TEST TEMPLATES -export const template = (c) => { - return `intEqual(~message="${c.description}", value(${JSON.stringify(c.input.colors)}), ${c.expected})` -} - -generateTests(__dirname, slug, assertionFunctions, template) diff --git a/exercises/practice/resistor-color-duo/tests/ResistorColorDuo_test.res b/exercises/practice/resistor-color-duo/tests/ResistorColorDuo_test.res index dd5490f..86835a3 100644 --- a/exercises/practice/resistor-color-duo/tests/ResistorColorDuo_test.res +++ b/exercises/practice/resistor-color-duo/tests/ResistorColorDuo_test.res @@ -1,32 +1,18 @@ open Test open ResistorColorDuo -let intEqual = (~message=?, a: int, b: int) => assertion(~message?, ~operator="intEqual", (a, b) => a == b, a, b) +let equal = (~message=?, a, b) => assertion(~message?, ~operator="equal", (a, b) => a == b, a, b) -test("Brown and black", () => { - intEqual(~message="Brown and black", value(["brown","black"]), 10) -}) +test("Brown and black", () => {equal(~message="Brown and black", value(["brown","black"]), 10)}) -test("Blue and grey", () => { - intEqual(~message="Blue and grey", value(["blue","grey"]), 68) -}) +test("Blue and grey", () => {equal(~message="Blue and grey", value(["blue","grey"]), 68)}) -test("Yellow and violet", () => { - intEqual(~message="Yellow and violet", value(["yellow","violet"]), 47) -}) +test("Yellow and violet", () => {equal(~message="Yellow and violet", value(["yellow","violet"]), 47)}) -test("White and red", () => { - intEqual(~message="White and red", value(["white","red"]), 92) -}) +test("White and red", () => {equal(~message="White and red", value(["white","red"]), 92)}) -test("Orange and orange", () => { - intEqual(~message="Orange and orange", value(["orange","orange"]), 33) -}) +test("Orange and orange", () => {equal(~message="Orange and orange", value(["orange","orange"]), 33)}) -test("Ignore additional colors", () => { - intEqual(~message="Ignore additional colors", value(["green","brown","orange"]), 51) -}) +test("Ignore additional colors", () => {equal(~message="Ignore additional colors", value(["green","brown","orange"]), 51)}) -test("Black and brown, one-digit", () => { - intEqual(~message="Black and brown, one-digit", value(["black","brown"]), 1) -}) +test("Black and brown, one-digit", () => {equal(~message="Black and brown, one-digit", value(["black","brown"]), 1)}) diff --git a/test_templates/ResistorColorDuo_template.res b/test_templates/ResistorColorDuo_template.res new file mode 100644 index 0000000..43d0ce2 --- /dev/null +++ b/test_templates/ResistorColorDuo_template.res @@ -0,0 +1,16 @@ +open Node + +let slug = fileURLToPath(%raw(`import.meta.url`))->basename->Utils.filenameToSlug + +let template = (case: GetCases.case) => { + let colors = Utils.getTestCaseInput(case, "colors") + + // EDIT THIS WITH YOUR ASSERTIONS + AssertionGenerators.equal( + ~message=case.description, + ~actual=`value(${colors})`, + ~expected=JSON.stringify(case.expected), + ) +} + +TestGenerator.generateTests(slug, template, [Equal]) From 695e659c706b050a8d1475680da50fe0fef1a6a4 Mon Sep 17 00:00:00 2001 From: Owen Date: Fri, 20 Mar 2026 13:37:44 +0100 Subject: [PATCH 37/44] update rotational cipher tests --- .../rotational-cipher/.meta/testTemplate.js | 26 -------- .../tests/RotationalCipher_test.res | 66 ++++--------------- test_templates/RotationalCipher_template.res | 17 +++++ 3 files changed, 28 insertions(+), 81 deletions(-) delete mode 100644 exercises/practice/rotational-cipher/.meta/testTemplate.js create mode 100644 test_templates/RotationalCipher_template.res diff --git a/exercises/practice/rotational-cipher/.meta/testTemplate.js b/exercises/practice/rotational-cipher/.meta/testTemplate.js deleted file mode 100644 index 32ab58d..0000000 --- a/exercises/practice/rotational-cipher/.meta/testTemplate.js +++ /dev/null @@ -1,26 +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 singleLine = `assertEqual(~message="${c.description}", rotate("${c.input.text}", ${c.input.shiftKey}), "${c.expected}")`; - if (2 + singleLine.length <= 80) { - return singleLine; - } - - return `assertEqual( - ~message="${c.description}", - rotate("${c.input.text}", ${c.input.shiftKey}), - "${c.expected}" - )`; -} - -generateTests(__dirname, slug, assertionFunctions, template) diff --git a/exercises/practice/rotational-cipher/tests/RotationalCipher_test.res b/exercises/practice/rotational-cipher/tests/RotationalCipher_test.res index 7e82099..b26b646 100644 --- a/exercises/practice/rotational-cipher/tests/RotationalCipher_test.res +++ b/exercises/practice/rotational-cipher/tests/RotationalCipher_test.res @@ -1,68 +1,24 @@ open Test open RotationalCipher -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("rotate a by 0, same output as input", () => { - assertEqual( - ~message="rotate a by 0, same output as input", - rotate("a", 0), - "a" - ) -}) +test("rotate a by 0, same output as input", () => {equal(~message="rotate a by 0, same output as input", rotate("a", 0), "a")}) -test("rotate a by 1", () => { - assertEqual(~message="rotate a by 1", rotate("a", 1), "b") -}) +test("rotate a by 1", () => {equal(~message="rotate a by 1", rotate("a", 1), "b")}) -test("rotate a by 26, same output as input", () => { - assertEqual( - ~message="rotate a by 26, same output as input", - rotate("a", 26), - "a" - ) -}) +test("rotate a by 26, same output as input", () => {equal(~message="rotate a by 26, same output as input", rotate("a", 26), "a")}) -test("rotate m by 13", () => { - assertEqual(~message="rotate m by 13", rotate("m", 13), "z") -}) +test("rotate m by 13", () => {equal(~message="rotate m by 13", rotate("m", 13), "z")}) -test("rotate n by 13 with wrap around alphabet", () => { - assertEqual( - ~message="rotate n by 13 with wrap around alphabet", - rotate("n", 13), - "a" - ) -}) +test("rotate n by 13 with wrap around alphabet", () => {equal(~message="rotate n by 13 with wrap around alphabet", rotate("n", 13), "a")}) -test("rotate capital letters", () => { - assertEqual(~message="rotate capital letters", rotate("OMG", 5), "TRL") -}) +test("rotate capital letters", () => {equal(~message="rotate capital letters", rotate("OMG", 5), "TRL")}) -test("rotate spaces", () => { - assertEqual(~message="rotate spaces", rotate("O M G", 5), "T R L") -}) +test("rotate spaces", () => {equal(~message="rotate spaces", rotate("O M G", 5), "T R L")}) -test("rotate numbers", () => { - assertEqual( - ~message="rotate numbers", - rotate("Testing 1 2 3 testing", 4), - "Xiwxmrk 1 2 3 xiwxmrk" - ) -}) +test("rotate numbers", () => {equal(~message="rotate numbers", rotate("Testing 1 2 3 testing", 4), "Xiwxmrk 1 2 3 xiwxmrk")}) -test("rotate punctuation", () => { - assertEqual( - ~message="rotate punctuation", - rotate("Let's eat, Grandma!", 21), - "Gzo'n zvo, Bmviyhv!" - ) -}) +test("rotate punctuation", () => {equal(~message="rotate punctuation", rotate("Let's eat, Grandma!", 21), "Gzo'n zvo, Bmviyhv!")}) -test("rotate all letters", () => { - assertEqual( - ~message="rotate all letters", - rotate("The quick brown fox jumps over the lazy dog.", 13), - "Gur dhvpx oebja sbk whzcf bire gur ynml qbt." - ) -}) +test("rotate all letters", () => {equal(~message="rotate all letters", rotate("The quick brown fox jumps over the lazy dog.", 13), "Gur dhvpx oebja sbk whzcf bire gur ynml qbt.")}) diff --git a/test_templates/RotationalCipher_template.res b/test_templates/RotationalCipher_template.res new file mode 100644 index 0000000..a11eaf1 --- /dev/null +++ b/test_templates/RotationalCipher_template.res @@ -0,0 +1,17 @@ +open Node + +let slug = fileURLToPath(%raw(`import.meta.url`))->basename->Utils.filenameToSlug + +let template = (case: GetCases.case) => { + let text = Utils.getTestCaseInput(case, "text") + let shiftKey = Utils.getTestCaseInput(case, "shiftKey") + + // EDIT THIS WITH YOUR ASSERTIONS + AssertionGenerators.equal( + ~message=case.description, + ~actual=`rotate(${text}, ${shiftKey})`, + ~expected=JSON.stringify(case.expected), + ) +} + +TestGenerator.generateTests(slug, template, [Equal]) From a16d690488f7b39064d6619dd7301c3b08b1eb24 Mon Sep 17 00:00:00 2001 From: Owen Date: Fri, 20 Mar 2026 14:27:07 +0100 Subject: [PATCH 38/44] add floatApproximatelyEqual with a precision constructor --- test_generator/AssertionGenerators.res | 4 ++++ test_generator/Assertions.res | 12 +++++++++++- test_generator/TestGenerator.res | 1 + 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/test_generator/AssertionGenerators.res b/test_generator/AssertionGenerators.res index d91b548..b9ff144 100644 --- a/test_generator/AssertionGenerators.res +++ b/test_generator/AssertionGenerators.res @@ -3,3 +3,7 @@ let equal = (~message, ~actual, ~expected) => { `equal(~message="${message}", ${actual}, ${expected})` } + +let floatApproximatelyEqual = (~message, ~actual, ~expected) => { + `floatApproximatelyEqual(~message="${message}", ${actual}, ${expected})` +} diff --git a/test_generator/Assertions.res b/test_generator/Assertions.res index 8cec200..cc54196 100644 --- a/test_generator/Assertions.res +++ b/test_generator/Assertions.res @@ -1,10 +1,11 @@ // Comparator functions in template string format, injectable into generated tests. -type assertionTag = Equal | DictEqual | Throws +type assertionTag = Equal | DictEqual | Throws | FloatApproximatelyEqual(int) 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) @@ -13,6 +14,15 @@ let getAssertionSource = tag => { } assertion(~message?, ~operator="dictEqual", (a, b) => toSorted(a) == toSorted(b), a, b) }` + + | FloatApproximatelyEqual(precision) => { + let tolerance = precision->Int.toString + let toleranceLit = !String.includes(tolerance, ".") ? tolerance ++ "." : tolerance + + `let floatApproximatelyEqual = (~message=?, a, b) => + assertion(~message?, ~operator="floatApprox", (a, b) => Math.abs(a -. b) <= ${toleranceLit}, a, b)` + } + | Throws => `let throws = (~message=?, f) => /* throws logic */` } } diff --git a/test_generator/TestGenerator.res b/test_generator/TestGenerator.res index c851f3f..f3a1f63 100644 --- a/test_generator/TestGenerator.res +++ b/test_generator/TestGenerator.res @@ -23,6 +23,7 @@ let generate = (outputPath, slug, template, requiredAssertions) => { 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}` From 92da35b232219b485ef98935615c36ef79b00ba7 Mon Sep 17 00:00:00 2001 From: Owen Date: Fri, 20 Mar 2026 14:27:14 +0100 Subject: [PATCH 39/44] udpate space age tests --- .../practice/space-age/.meta/testTemplate.js | 19 ------------- .../space-age/tests/SpaceAge_test.res | 28 +++++++------------ test_templates/SpaceAge_template.res | 24 ++++++++++++++++ 3 files changed, 34 insertions(+), 37 deletions(-) delete mode 100644 exercises/practice/space-age/.meta/testTemplate.js create mode 100644 test_templates/SpaceAge_template.res diff --git a/exercises/practice/space-age/.meta/testTemplate.js b/exercises/practice/space-age/.meta/testTemplate.js deleted file mode 100644 index 676915b..0000000 --- a/exercises/practice/space-age/.meta/testTemplate.js +++ /dev/null @@ -1,19 +0,0 @@ -import path from 'node:path'; -import { fileURLToPath } from 'node:url'; -import { floatEqual } 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 = [ floatEqual ] - -// EDIT THIS WITH YOUR TEST TEMPLATES -export const template = (c) => { - const { planet, seconds } = c.input - - return `floatEqual(~message="${c.description}", age(${planet}, ${seconds}), ${c.expected})` -} - -generateTests(__dirname, slug, assertionFunctions, template) \ No newline at end of file diff --git a/exercises/practice/space-age/tests/SpaceAge_test.res b/exercises/practice/space-age/tests/SpaceAge_test.res index a00be9c..56e22de 100644 --- a/exercises/practice/space-age/tests/SpaceAge_test.res +++ b/exercises/practice/space-age/tests/SpaceAge_test.res @@ -1,45 +1,37 @@ open Test open SpaceAge -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, - ) -} +let floatApproximatelyEqual = (~message=?, a, b) => + assertion(~message?, ~operator="floatApprox", (a, b) => Math.abs(a -. b) <= 2., a, b) test("age on Earth", () => { - floatEqual(~message="age on Earth", age(Earth, 1000000000), 31.69) + floatApproximatelyEqual(~message="age on Earth", age(Earth, 1000000000), 31.69) }) test("age on Mercury", () => { - floatEqual(~message="age on Mercury", age(Mercury, 2134835688), 280.88) + floatApproximatelyEqual(~message="age on Mercury", age(Mercury, 2134835688), 280.88) }) test("age on Venus", () => { - floatEqual(~message="age on Venus", age(Venus, 189839836), 9.78) + floatApproximatelyEqual(~message="age on Venus", age(Venus, 189839836), 9.78) }) test("age on Mars", () => { - floatEqual(~message="age on Mars", age(Mars, 2129871239), 35.88) + floatApproximatelyEqual(~message="age on Mars", age(Mars, 2129871239), 35.88) }) test("age on Jupiter", () => { - floatEqual(~message="age on Jupiter", age(Jupiter, 901876382), 2.41) + floatApproximatelyEqual(~message="age on Jupiter", age(Jupiter, 901876382), 2.41) }) test("age on Saturn", () => { - floatEqual(~message="age on Saturn", age(Saturn, 2000000000), 2.15) + floatApproximatelyEqual(~message="age on Saturn", age(Saturn, 2000000000), 2.15) }) test("age on Uranus", () => { - floatEqual(~message="age on Uranus", age(Uranus, 1210123456), 0.46) + floatApproximatelyEqual(~message="age on Uranus", age(Uranus, 1210123456), 0.46) }) test("age on Neptune", () => { - floatEqual(~message="age on Neptune", age(Neptune, 1821023456), 0.35) + floatApproximatelyEqual(~message="age on Neptune", age(Neptune, 1821023456), 0.35) }) diff --git a/test_templates/SpaceAge_template.res b/test_templates/SpaceAge_template.res new file mode 100644 index 0000000..47141ff --- /dev/null +++ b/test_templates/SpaceAge_template.res @@ -0,0 +1,24 @@ +open Node + +let slug = fileURLToPath(%raw(`import.meta.url`))->basename->Utils.filenameToSlug + +let template = (case: GetCases.case) => { + let inputDict = case.input->JSON.Decode.object->Option.getOrThrow + + let planet = + inputDict + ->Dict.get("planet") + ->Option.flatMap(JSON.Decode.string) + ->Option.getOrThrow + + let seconds = Utils.getTestCaseInput(case, "seconds") + + // EDIT THIS WITH YOUR ASSERTIONS + AssertionGenerators.floatApproximatelyEqual( + ~message=case.description, + ~actual=`age(${planet}, ${seconds})`, + ~expected=JSON.stringify(case.expected), + ) +} + +TestGenerator.generateTests(slug, template, [FloatApproximatelyEqual(2)]) From ff132a9cbcd2f07da9f94726f7b15bf491183054 Mon Sep 17 00:00:00 2001 From: Owen Date: Fri, 20 Mar 2026 14:46:40 +0100 Subject: [PATCH 40/44] add property key from test cases --- test_generator/GetCases.res | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test_generator/GetCases.res b/test_generator/GetCases.res index 3cd3720..f96941d 100644 --- a/test_generator/GetCases.res +++ b/test_generator/GetCases.res @@ -1,6 +1,7 @@ open Node type case = { + property: string, description: string, expected: JSON.t, input: JSON.t, @@ -40,6 +41,7 @@ let getValidCases = (slug: string): array => { if (uuid && m && m.include !== false) { // validCases.push(testCase); validCases.push({ + property: testCase.property, description: testCase.description, expected: testCase.expected, input: testCase.input From 749501062662e2302a0c3627e473af886039699b Mon Sep 17 00:00:00 2001 From: Owen Date: Fri, 20 Mar 2026 14:47:24 +0100 Subject: [PATCH 41/44] update strain tests --- .../practice/strain/.meta/testTemplate.js | 61 ---------- .../practice/strain/tests/Strain_test.res | 110 +++--------------- test_templates/Strain_template.res | 34 ++++++ 3 files changed, 49 insertions(+), 156 deletions(-) delete mode 100644 exercises/practice/strain/.meta/testTemplate.js create mode 100644 test_templates/Strain_template.res diff --git a/exercises/practice/strain/.meta/testTemplate.js b/exercises/practice/strain/.meta/testTemplate.js deleted file mode 100644 index fbe9fa4..0000000 --- a/exercises/practice/strain/.meta/testTemplate.js +++ /dev/null @@ -1,61 +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, '..')) - -const serializeValue = (v) => { - if (Array.isArray(v)) { - return `[${v.map(serializeValue).join(', ')}]`; - } else if (typeof v === 'string') { - return `"${v}"`; - } else { - return String(v); - } -}; - -const serializeMultiLineArray = (arr, indent) => { - const innerIndent = indent + ' '; - const items = arr.map(v => `${innerIndent}${serializeValue(v)},`).join('\n'); - return `[\n${items}\n${indent}]`; -}; - -const predicateMap = { - 'fn(x) -> true': '_ => true', - 'fn(x) -> false': '_ => false', - 'fn(x) -> x % 2 == 1': 'x => mod(x, 2) == 1', - 'fn(x) -> x % 2 == 0': 'x => mod(x, 2) == 0', - "fn(x) -> starts_with(x, 'z')": 'x => String.startsWith(x, "z")', - 'fn(x) -> contains(x, 5)': 'x => Array.includes(x, 5)', -}; - -// EDIT THIS WITH YOUR ASSERTIONS -export const assertionFunctions = [ assertEqual ] - - -// EDIT THIS WITH YOUR TEST TEMPLATES -export const template = (c) => { - const values = serializeValue(c.input.list); - const predicate = predicateMap[c.input.predicate]; - const expected = serializeValue(c.expected); - - const call = `${c.property}(${values}, ${predicate})`; - const singleLine = `assertEqual(~message="${c.description}", ${call}, ${expected})`; - if (2 + singleLine.length <= 80) { - return singleLine; - } - - // Extract and format list, predicate, and expected - const list = (` let list = ${values}`).length > 80 - ? serializeMultiLineArray(c.input.list, ' ') - : values; - - return `let list = ${list} - let predicate = ${predicate} - let expected = ${expected} - assertEqual(~message="${c.description}", ${c.property}(list, predicate), expected)`; -} - -generateTests(__dirname, slug, assertionFunctions, template) diff --git a/exercises/practice/strain/tests/Strain_test.res b/exercises/practice/strain/tests/Strain_test.res index ec5484d..5b883cd 100644 --- a/exercises/practice/strain/tests/Strain_test.res +++ b/exercises/practice/strain/tests/Strain_test.res @@ -1,112 +1,32 @@ open Test open Strain -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("keep on empty list returns empty list", () => { - let list = [] - let predicate = _ => true - let expected = [] - assertEqual(~message="keep on empty list returns empty list", keep(list, predicate), expected) -}) +test("keep on empty list returns empty list", () => {equal(~message="keep on empty list returns empty list", keep([], (_ => true)), [])}) -test("keeps everything", () => { - let list = [1, 3, 5] - let predicate = _ => true - let expected = [1, 3, 5] - assertEqual(~message="keeps everything", keep(list, predicate), expected) -}) +test("keeps everything", () => {equal(~message="keeps everything", keep([1,3,5], (_ => true)), [1,3,5])}) -test("keeps nothing", () => { - assertEqual(~message="keeps nothing", keep([1, 3, 5], _ => false), []) -}) +test("keeps nothing", () => {equal(~message="keeps nothing", keep([1,3,5], (_ => false)), [])}) -test("keeps first and last", () => { - let list = [1, 2, 3] - let predicate = x => mod(x, 2) == 1 - let expected = [1, 3] - assertEqual(~message="keeps first and last", keep(list, predicate), expected) -}) +test("keeps first and last", () => {equal(~message="keeps first and last", keep([1,2,3], (x => mod(x, 2) == 1)), [1,3])}) -test("keeps neither first nor last", () => { - let list = [1, 2, 3] - let predicate = x => mod(x, 2) == 0 - let expected = [2] - assertEqual(~message="keeps neither first nor last", keep(list, predicate), expected) -}) +test("keeps neither first nor last", () => {equal(~message="keeps neither first nor last", keep([1,2,3], (x => mod(x, 2) == 0)), [2])}) -test("keeps strings", () => { - let list = ["apple", "zebra", "banana", "zombies", "cherimoya", "zealot"] - let predicate = x => String.startsWith(x, "z") - let expected = ["zebra", "zombies", "zealot"] - assertEqual(~message="keeps strings", keep(list, predicate), expected) -}) +test("keeps strings", () => {equal(~message="keeps strings", keep(["apple","zebra","banana","zombies","cherimoya","zealot"], (x => String.startsWith(x, "z"))), ["zebra","zombies","zealot"])}) -test("keeps lists", () => { - let list = [ - [1, 2, 3], - [5, 5, 5], - [5, 1, 2], - [2, 1, 2], - [1, 5, 2], - [2, 2, 1], - [1, 2, 5], - ] - let predicate = x => Array.includes(x, 5) - let expected = [[5, 5, 5], [5, 1, 2], [1, 5, 2], [1, 2, 5]] - assertEqual(~message="keeps lists", keep(list, predicate), expected) -}) +test("keeps lists", () => {equal(~message="keeps lists", keep([[1,2,3],[5,5,5],[5,1,2],[2,1,2],[1,5,2],[2,2,1],[1,2,5]], (x => Array.includes(x, 5))), [[5,5,5],[5,1,2],[1,5,2],[1,2,5]])}) -test("discard on empty list returns empty list", () => { - let list = [] - let predicate = _ => true - let expected = [] - assertEqual(~message="discard on empty list returns empty list", discard(list, predicate), expected) -}) +test("discard on empty list returns empty list", () => {equal(~message="discard on empty list returns empty list", discard([], (_ => true)), [])}) -test("discards everything", () => { - assertEqual(~message="discards everything", discard([1, 3, 5], _ => true), []) -}) +test("discards everything", () => {equal(~message="discards everything", discard([1,3,5], (_ => true)), [])}) -test("discards nothing", () => { - let list = [1, 3, 5] - let predicate = _ => false - let expected = [1, 3, 5] - assertEqual(~message="discards nothing", discard(list, predicate), expected) -}) +test("discards nothing", () => {equal(~message="discards nothing", discard([1,3,5], (_ => false)), [1,3,5])}) -test("discards first and last", () => { - let list = [1, 2, 3] - let predicate = x => mod(x, 2) == 1 - let expected = [2] - assertEqual(~message="discards first and last", discard(list, predicate), expected) -}) +test("discards first and last", () => {equal(~message="discards first and last", discard([1,2,3], (x => mod(x, 2) == 1)), [2])}) -test("discards neither first nor last", () => { - let list = [1, 2, 3] - let predicate = x => mod(x, 2) == 0 - let expected = [1, 3] - assertEqual(~message="discards neither first nor last", discard(list, predicate), expected) -}) +test("discards neither first nor last", () => {equal(~message="discards neither first nor last", discard([1,2,3], (x => mod(x, 2) == 0)), [1,3])}) -test("discards strings", () => { - let list = ["apple", "zebra", "banana", "zombies", "cherimoya", "zealot"] - let predicate = x => String.startsWith(x, "z") - let expected = ["apple", "banana", "cherimoya"] - assertEqual(~message="discards strings", discard(list, predicate), expected) -}) +test("discards strings", () => {equal(~message="discards strings", discard(["apple","zebra","banana","zombies","cherimoya","zealot"], (x => String.startsWith(x, "z"))), ["apple","banana","cherimoya"])}) -test("discards lists", () => { - let list = [ - [1, 2, 3], - [5, 5, 5], - [5, 1, 2], - [2, 1, 2], - [1, 5, 2], - [2, 2, 1], - [1, 2, 5], - ] - let predicate = x => Array.includes(x, 5) - let expected = [[1, 2, 3], [2, 1, 2], [2, 2, 1]] - assertEqual(~message="discards lists", discard(list, predicate), expected) -}) +test("discards lists", () => {equal(~message="discards lists", discard([[1,2,3],[5,5,5],[5,1,2],[2,1,2],[1,5,2],[2,2,1],[1,2,5]], (x => Array.includes(x, 5))), [[1,2,3],[2,1,2],[2,2,1]])}) diff --git a/test_templates/Strain_template.res b/test_templates/Strain_template.res new file mode 100644 index 0000000..eb9b63a --- /dev/null +++ b/test_templates/Strain_template.res @@ -0,0 +1,34 @@ +open Node + +let slug = fileURLToPath(%raw(`import.meta.url`))->basename->Utils.filenameToSlug + +let template = (case: GetCases.case) => { + let inputDict = case.input->JSON.Decode.object->Option.getOrThrow + + let predicateKey = + inputDict + ->Dict.get("predicate") + ->Option.flatMap(JSON.Decode.string) + ->Option.getOr("") + + let predicate = switch predicateKey { + | "fn(x) -> true" => "(_ => true)" + | "fn(x) -> false" => "(_ => false)" + | "fn(x) -> x % 2 == 1" => "(x => mod(x, 2) == 1)" + | "fn(x) -> x % 2 == 0" => "(x => mod(x, 2) == 0)" + | "fn(x) -> starts_with(x, 'z')" => "(x => String.startsWith(x, \"z\"))" + | "fn(x) -> contains(x, 5)" => "(x => Array.includes(x, 5))" + | _ => "(_ => true)" // Default fallback + } + + let list = Utils.getTestCaseInput(case, "list") + + // EDIT THIS WITH YOUR ASSERTIONS + AssertionGenerators.equal( + ~message=case.description, + ~actual=`${case.property}(${list}, ${predicate})`, + ~expected=JSON.stringify(case.expected), + ) +} + +TestGenerator.generateTests(slug, template, [Equal]) From de3d915babc1738a51aa5c8f9e804b0a2f64aea2 Mon Sep 17 00:00:00 2001 From: Owen Date: Fri, 20 Mar 2026 15:05:19 +0100 Subject: [PATCH 42/44] two-fer tests updated --- .../practice/two-fer/.meta/testTemplate.js | 21 ------------------ .../practice/two-fer/tests/TwoFer_test.res | 14 ++++-------- test_templates/TwoFer_template.res | 22 +++++++++++++++++++ 3 files changed, 26 insertions(+), 31 deletions(-) delete mode 100644 exercises/practice/two-fer/.meta/testTemplate.js create mode 100644 test_templates/TwoFer_template.res diff --git a/exercises/practice/two-fer/.meta/testTemplate.js b/exercises/practice/two-fer/.meta/testTemplate.js deleted file mode 100644 index 94e90a9..0000000 --- a/exercises/practice/two-fer/.meta/testTemplate.js +++ /dev/null @@ -1,21 +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) => { - if (c.input.name) { - return `stringEqual(~message="${c.description}", twoFer(Some("${c.input.name}")), "${c.expected}")` - } else { - return `stringEqual(~message="${c.description}", twoFer(None), "${c.expected}")` - } -} - -generateTests(__dirname, slug, assertionFunctions, template) \ No newline at end of file diff --git a/exercises/practice/two-fer/tests/TwoFer_test.res b/exercises/practice/two-fer/tests/TwoFer_test.res index 5fa85d5..9779737 100644 --- a/exercises/practice/two-fer/tests/TwoFer_test.res +++ b/exercises/practice/two-fer/tests/TwoFer_test.res @@ -1,16 +1,10 @@ open Test open TwoFer -let stringEqual = (~message=?, a: string, b: string) => assertion(~message?, ~operator="stringEqual", (a, b) => a == b, a, b) +let equal = (~message=?, a, b) => assertion(~message?, ~operator="equal", (a, b) => a == b, a, b) -test("no name given", () => { - stringEqual(~message="no name given", twoFer(None), "One for you, one for me.") -}) +test("no name given", () => {equal(~message="no name given", twoFer(None), "One for you, one for me.")}) -test("a name given", () => { - stringEqual(~message="a name given", twoFer(Some("Alice")), "One for Alice, one for me.") -}) +test("a name given", () => {equal(~message="a name given", twoFer(Some("Alice")), "One for Alice, one for me.")}) -test("another name given", () => { - stringEqual(~message="another name given", twoFer(Some("Bob")), "One for Bob, one for me.") -}) +test("another name given", () => {equal(~message="another name given", twoFer(Some("Bob")), "One for Bob, one for me.")}) diff --git a/test_templates/TwoFer_template.res b/test_templates/TwoFer_template.res new file mode 100644 index 0000000..59a115c --- /dev/null +++ b/test_templates/TwoFer_template.res @@ -0,0 +1,22 @@ +open Node + +let slug = fileURLToPath(%raw(`import.meta.url`))->basename->Utils.filenameToSlug + +let template = (case: GetCases.case) => { + let inputDict = case.input->JSON.Decode.object->Option.getOrThrow + let nameOption = inputDict->Dict.get("name")->Option.flatMap(JSON.Decode.string) + + let actualStr = switch nameOption { + | Some(name) => `twoFer(Some("${name}"))` + | None => `twoFer(None)` + } + + // EDIT THIS WITH YOUR ASSERTIONS + AssertionGenerators.equal( + ~message=case.description, + ~actual=actualStr, + ~expected=JSON.stringify(case.expected), + ) +} + +TestGenerator.generateTests(slug, template, [Equal]) From 0ac627b0f415e4c4d3cafe6c0089070c4c82ecfe Mon Sep 17 00:00:00 2001 From: Owen Date: Fri, 20 Mar 2026 15:36:34 +0100 Subject: [PATCH 43/44] update word count tests --- .../word-count/tests/WordCount_test.res | 159 +++--------------- test_templates/WordCount_template.res | 18 ++ 2 files changed, 39 insertions(+), 138 deletions(-) create mode 100644 test_templates/WordCount_template.res diff --git a/exercises/practice/word-count/tests/WordCount_test.res b/exercises/practice/word-count/tests/WordCount_test.res index 14655ce..b7f73a7 100644 --- a/exercises/practice/word-count/tests/WordCount_test.res +++ b/exercises/practice/word-count/tests/WordCount_test.res @@ -2,155 +2,38 @@ open Test open WordCount 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) -} + 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) + } -test("count one word", () => { - dictEqual(~message="count one word", countWords("word"), dict{"word": 1}) -}) +test("count one word", () => {dictEqual(~message="count one word", countWords("word"), dict{"word":1})}) -test("count one of each word", () => { - dictEqual( - ~message="count one of each word", - countWords("one of each"), - dict{"each": 1, "of": 1, "one": 1}, - ) -}) +test("count one of each word", () => {dictEqual(~message="count one of each word", countWords("one of each"), dict{"one":1,"of":1,"each":1})}) -test("multiple occurrences of a word", () => { - dictEqual( - ~message="multiple occurrences of a word", - countWords("one fish two fish red fish blue fish"), - dict{ - "blue": 1, - "fish": 4, - "one": 1, - "red": 1, - "two": 1, - }, - ) -}) +test("multiple occurrences of a word", () => {dictEqual(~message="multiple occurrences of a word", countWords("one fish two fish red fish blue fish"), dict{"one":1,"fish":4,"two":1,"red":1,"blue":1})}) -test("handles cramped lists", () => { - dictEqual( - ~message="handles cramped lists", - countWords("one,two,three"), - dict{"one": 1, "three": 1, "two": 1}, - ) -}) +test("handles cramped lists", () => {dictEqual(~message="handles cramped lists", countWords("one,two,three"), dict{"one":1,"two":1,"three":1})}) -test("handles expanded lists", () => { - dictEqual( - ~message="handles expanded lists", - countWords("one,\ntwo,\nthree"), - dict{"one": 1, "three": 1, "two": 1}, - ) -}) +test("handles expanded lists", () => {dictEqual(~message="handles expanded lists", countWords("one,\ntwo,\nthree"), dict{"one":1,"two":1,"three":1})}) -test("ignore punctuation", () => { - dictEqual( - ~message="ignore punctuation", - countWords("car: carpet as java: javascript!!&@$%^&"), - dict{ - "as": 1, - "car": 1, - "carpet": 1, - "java": 1, - "javascript": 1, - }, - ) -}) +test("ignore punctuation", () => {dictEqual(~message="ignore punctuation", countWords("car: carpet as java: javascript!!&@$%^&"), dict{"car":1,"carpet":1,"as":1,"java":1,"javascript":1})}) -test("include numbers", () => { - dictEqual( - ~message="include numbers", - countWords("testing, 1, 2 testing"), - dict{"1": 1, "2": 1, "testing": 2}, - ) -}) +test("include numbers", () => {dictEqual(~message="include numbers", countWords("testing, 1, 2 testing"), dict{"1":1,"2":1,"testing":2})}) -test("normalize case", () => { - dictEqual( - ~message="normalize case", - countWords("go Go GO Stop stop"), - dict{"go": 3, "stop": 2}, - ) -}) +test("normalize case", () => {dictEqual(~message="normalize case", countWords("go Go GO Stop stop"), dict{"go":3,"stop":2})}) -test("with apostrophes", () => { - dictEqual( - ~message="with apostrophes", - countWords("'First: don't laugh. Then: don't cry. You're getting it.'"), - dict{ - "cry": 1, - "don't": 2, - "first": 1, - "getting": 1, - "it": 1, - "laugh": 1, - "then": 1, - "you're": 1, - }, - ) -}) +test("with apostrophes", () => {dictEqual(~message="with apostrophes", countWords("'First: don't laugh. Then: don't cry. You're getting it.'"), dict{"first":1,"don't":2,"laugh":1,"then":1,"cry":1,"you're":1,"getting":1,"it":1})}) -test("with quotations", () => { - dictEqual( - ~message="with quotations", - countWords("Joe can't tell between 'large' and large."), - dict{ - "and": 1, - "between": 1, - "can't": 1, - "joe": 1, - "large": 2, - "tell": 1, - }, - ) -}) +test("with quotations", () => {dictEqual(~message="with quotations", countWords("Joe can't tell between 'large' and large."), dict{"joe":1,"can't":1,"tell":1,"between":1,"large":2,"and":1})}) -test("substrings from the beginning", () => { - dictEqual( - ~message="substrings from the beginning", - countWords("Joe can't tell between app, apple and a."), - dict{ - "a": 1, - "and": 1, - "app": 1, - "apple": 1, - "between": 1, - "can't": 1, - "joe": 1, - "tell": 1, - }, - ) -}) +test("substrings from the beginning", () => {dictEqual(~message="substrings from the beginning", countWords("Joe can't tell between app, apple and a."), dict{"joe":1,"can't":1,"tell":1,"between":1,"app":1,"apple":1,"and":1,"a":1})}) -test("multiple spaces not detected as a word", () => { - dictEqual( - ~message="multiple spaces not detected as a word", - countWords(" multiple whitespaces"), - dict{"multiple": 1, "whitespaces": 1}, - ) -}) +test("multiple spaces not detected as a word", () => {dictEqual(~message="multiple spaces not detected as a word", countWords(" multiple whitespaces"), dict{"multiple":1,"whitespaces":1})}) -test("alternating word separators not detected as a word", () => { - dictEqual( - ~message="alternating word separators not detected as a word", - countWords(",\n,one,\n ,two \n 'three'"), - dict{"one": 1, "three": 1, "two": 1}, - ) -}) +test("alternating word separators not detected as a word", () => {dictEqual(~message="alternating word separators not detected as a word", countWords(",\n,one,\n ,two \n 'three'"), dict{"one":1,"two":1,"three":1})}) -test("quotation for word with apostrophe", () => { - dictEqual( - ~message="quotation for word with apostrophe", - countWords("can, can't, 'can't'"), - dict{"can": 1, "can't": 2}, - ) -}) +test("quotation for word with apostrophe", () => {dictEqual(~message="quotation for word with apostrophe", countWords("can, can't, 'can't'"), dict{"can":1,"can't":2})}) diff --git a/test_templates/WordCount_template.res b/test_templates/WordCount_template.res new file mode 100644 index 0000000..8c28649 --- /dev/null +++ b/test_templates/WordCount_template.res @@ -0,0 +1,18 @@ +open Node + +let slug = fileURLToPath(%raw(`import.meta.url`))->basename->Utils.filenameToSlug + +let template = (case: GetCases.case) => { + let sentence = Utils.getTestCaseInput(case, "sentence") + + let expectedDict = "dict" ++ JSON.stringify(case.expected) + + // EDIT THIS WITH YOUR ASSERTIONS + AssertionGenerators.dictEqual( + ~message=case.description, + ~actual=`countWords(${sentence})`, + ~expected=expectedDict, + ) +} + +TestGenerator.generateTests(slug, template, [DictEqual]) From 939181c1e76d71d140bea1d310c8fa0674f284fa Mon Sep 17 00:00:00 2001 From: Owen Date: Fri, 20 Mar 2026 15:36:50 +0100 Subject: [PATCH 44/44] create dictEqual assertion --- test_generator/AssertionGenerators.res | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test_generator/AssertionGenerators.res b/test_generator/AssertionGenerators.res index b9ff144..e8ef288 100644 --- a/test_generator/AssertionGenerators.res +++ b/test_generator/AssertionGenerators.res @@ -7,3 +7,7 @@ let equal = (~message, ~actual, ~expected) => { let floatApproximatelyEqual = (~message, ~actual, ~expected) => { `floatApproximatelyEqual(~message="${message}", ${actual}, ${expected})` } + +let dictEqual = (~message, ~actual, ~expected) => { + `dictEqual(~message="${message}", ${actual}, ${expected})` +}