From cdf27920e3e382165a255f8124ad4be8a6a6f4e0 Mon Sep 17 00:00:00 2001 From: Owen Rees Date: Sun, 8 Mar 2026 22:36:50 +0100 Subject: [PATCH 01/16] allow multiple assertions to be loaded from assertions library --- exercises/practice/hello-world/.meta/testTemplate.js | 7 ++++--- .../practice/hello-world/tests/HelloWorld_test.res | 6 +++--- test_generator/assertions.js | 1 + test_generator/testGenerator.js | 10 +++++++--- 4 files changed, 15 insertions(+), 9 deletions(-) create mode 100644 test_generator/assertions.js diff --git a/exercises/practice/hello-world/.meta/testTemplate.js b/exercises/practice/hello-world/.meta/testTemplate.js index 4767482..163f586 100644 --- a/exercises/practice/hello-world/.meta/testTemplate.js +++ b/exercises/practice/hello-world/.meta/testTemplate.js @@ -1,8 +1,9 @@ +import { stringEqual } from "../../../../test_generator/assertions.js"; + export const slug = 'hello-world'; -export const assertionFunctions = `let stringEqual = (~message=?, a: string, b: string) => - assertion(~message?, ~operator="stringEqual", (a, b) => a == b, a, b)`; +export const assertionFunctions = [ stringEqual ] export const template = (c, moduleName) => { - return `stringEqual(~message="${c.description}", ${moduleName}.hello(), "${c.expected}")` + return `stringEqual(~message="${c.description}", hello(), "${c.expected}")` } \ No newline at end of file diff --git a/exercises/practice/hello-world/tests/HelloWorld_test.res b/exercises/practice/hello-world/tests/HelloWorld_test.res index 0192f17..d06fc37 100644 --- a/exercises/practice/hello-world/tests/HelloWorld_test.res +++ b/exercises/practice/hello-world/tests/HelloWorld_test.res @@ -1,8 +1,8 @@ open Test +open HelloWorld -let stringEqual = (~message=?, a: string, b: string) => - assertion(~message?, ~operator="stringEqual", (a, b) => a == b, a, b) +let stringEqual = (~message=?, a: string, b: string) => assertion(~message?, ~operator="stringEqual", (a, b) => a == b, a, b) test("Say Hi!", () => { - stringEqual(~message="Say Hi!", HelloWorld.hello(), "Hello, World!") + stringEqual(~message="Say Hi!", hello(), "Hello, World!") }) \ No newline at end of file diff --git a/test_generator/assertions.js b/test_generator/assertions.js new file mode 100644 index 0000000..cce70b6 --- /dev/null +++ b/test_generator/assertions.js @@ -0,0 +1 @@ +export const stringEqual = `let stringEqual = (~message=?, a: string, b: string) => assertion(~message?, ~operator="stringEqual", (a, b) => a == b, a, b)`; \ No newline at end of file diff --git a/test_generator/testGenerator.js b/test_generator/testGenerator.js index 55c9417..38b227c 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 { template } from '../exercises/practice/hello-world/.meta/testTemplate.js'; +import { template, assertionFunctions } from '../exercises/practice/hello-world/.meta/testTemplate.js'; export const toPascalCase = (slug) => slug.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(''); @@ -8,8 +8,12 @@ export const toPascalCase = (slug) => export const generate = (outputPath, slug, cases, config) => { const moduleName = toPascalCase(slug); - let output = `open Test\n\n`; - output += `${config.assertionFunctions}\n\n`; + let output = `open Test +open ${moduleName}\n\n`; + + assertionFunctions.forEach(fn => { + output += `${fn}\n\n` + }) cases.forEach((c) => { output += `test("${c.description}", () => { From fc1b5c62c3fdddc84c302a86cc82d8d31589d80f Mon Sep 17 00:00:00 2001 From: Owen Rees Date: Sun, 8 Mar 2026 22:43:01 +0100 Subject: [PATCH 02/16] update arguments in test generator functions --- exercises/practice/hello-world/.meta/generateTests.js | 8 ++++---- exercises/practice/hello-world/.meta/testTemplate.js | 2 +- test_generator/testGenerator.js | 8 +++----- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/exercises/practice/hello-world/.meta/generateTests.js b/exercises/practice/hello-world/.meta/generateTests.js index 620c69a..4f38791 100644 --- a/exercises/practice/hello-world/.meta/generateTests.js +++ b/exercises/practice/hello-world/.meta/generateTests.js @@ -2,10 +2,10 @@ import { fileURLToPath } from 'node:url'; import path from 'node:path'; import getValidCases from '../../../../test_generator/getCases.js'; import { generate, toPascalCase } from '../../../../test_generator/testGenerator.js'; -import * as template from './testTemplate.js'; +import * as config from './testTemplate.js'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const cases = getValidCases(template.slug); -const outputPath = path.resolve(__dirname, '..', 'tests', `${toPascalCase(template.slug)}_test.res`); +const cases = getValidCases(config.slug); +const outputPath = path.resolve(__dirname, '..', 'tests', `${toPascalCase(config.slug)}_test.res`); -generate(outputPath, template.slug, cases, template); \ No newline at end of file +generate(outputPath, config.slug, cases); \ No newline at end of file diff --git a/exercises/practice/hello-world/.meta/testTemplate.js b/exercises/practice/hello-world/.meta/testTemplate.js index 163f586..7075f9f 100644 --- a/exercises/practice/hello-world/.meta/testTemplate.js +++ b/exercises/practice/hello-world/.meta/testTemplate.js @@ -4,6 +4,6 @@ export const slug = 'hello-world'; export const assertionFunctions = [ stringEqual ] -export const template = (c, moduleName) => { +export const template = (c) => { return `stringEqual(~message="${c.description}", hello(), "${c.expected}")` } \ No newline at end of file diff --git a/test_generator/testGenerator.js b/test_generator/testGenerator.js index 38b227c..c64243a 100644 --- a/test_generator/testGenerator.js +++ b/test_generator/testGenerator.js @@ -5,19 +5,17 @@ import { template, assertionFunctions } from '../exercises/practice/hello-world/ export const toPascalCase = (slug) => slug.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(''); -export const generate = (outputPath, slug, cases, config) => { +export const generate = (outputPath, slug, cases) => { const moduleName = toPascalCase(slug); let output = `open Test open ${moduleName}\n\n`; - assertionFunctions.forEach(fn => { - output += `${fn}\n\n` - }) + assertionFunctions.forEach(fn => output += `${fn}\n\n`) cases.forEach((c) => { output += `test("${c.description}", () => { - ${template(c, moduleName)} + ${template(c)} })` }); From aaf57ea35807bf9cce803ce4021f79debfaae532 Mon Sep 17 00:00:00 2001 From: Owen Rees Date: Sun, 8 Mar 2026 22:46:15 +0100 Subject: [PATCH 03/16] copy test generators to new exercise --- exercises/practice/two-fer/.meta/generateTests.js | 11 +++++++++++ exercises/practice/two-fer/.meta/testTemplate.js | 9 +++++++++ 2 files changed, 20 insertions(+) create mode 100644 exercises/practice/two-fer/.meta/generateTests.js create mode 100644 exercises/practice/two-fer/.meta/testTemplate.js diff --git a/exercises/practice/two-fer/.meta/generateTests.js b/exercises/practice/two-fer/.meta/generateTests.js new file mode 100644 index 0000000..4f38791 --- /dev/null +++ b/exercises/practice/two-fer/.meta/generateTests.js @@ -0,0 +1,11 @@ +import { fileURLToPath } from 'node:url'; +import path from 'node:path'; +import getValidCases from '../../../../test_generator/getCases.js'; +import { generate, toPascalCase } from '../../../../test_generator/testGenerator.js'; +import * as config from './testTemplate.js'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const cases = getValidCases(config.slug); +const outputPath = path.resolve(__dirname, '..', 'tests', `${toPascalCase(config.slug)}_test.res`); + +generate(outputPath, config.slug, cases); \ No newline at end of file diff --git a/exercises/practice/two-fer/.meta/testTemplate.js b/exercises/practice/two-fer/.meta/testTemplate.js new file mode 100644 index 0000000..19378ab --- /dev/null +++ b/exercises/practice/two-fer/.meta/testTemplate.js @@ -0,0 +1,9 @@ +import { stringEqual } from "../../../../test_generator/assertions.js"; + +export const slug = 'two-fer'; + +export const assertionFunctions = [ stringEqual ] + +export const template = (c) => { + return `stringEqual(~message="${c.description}", twoFer(), "${c.expected}")` +} \ No newline at end of file From a47fe059cfe35f49c9313d79b8cd7d81384fcc90 Mon Sep 17 00:00:00 2001 From: Owen Rees Date: Sun, 8 Mar 2026 22:46:36 +0100 Subject: [PATCH 04/16] update formatting when multiple tests are needed --- 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 c64243a..e3b63af 100644 --- a/test_generator/testGenerator.js +++ b/test_generator/testGenerator.js @@ -16,7 +16,7 @@ open ${moduleName}\n\n`; cases.forEach((c) => { output += `test("${c.description}", () => { ${template(c)} -})` +})\n\n` }); if (!fs.existsSync(path.dirname(outputPath))) { From 06ec0257ba4fe3a9336505c77f577bdced0335b5 Mon Sep 17 00:00:00 2001 From: Owen Rees Date: Sun, 8 Mar 2026 22:46:51 +0100 Subject: [PATCH 05/16] updated test formatting --- exercises/practice/hello-world/tests/HelloWorld_test.res | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/exercises/practice/hello-world/tests/HelloWorld_test.res b/exercises/practice/hello-world/tests/HelloWorld_test.res index d06fc37..2e080e8 100644 --- a/exercises/practice/hello-world/tests/HelloWorld_test.res +++ b/exercises/practice/hello-world/tests/HelloWorld_test.res @@ -5,4 +5,5 @@ let stringEqual = (~message=?, a: string, b: string) => assertion(~message?, ~op test("Say Hi!", () => { stringEqual(~message="Say Hi!", hello(), "Hello, World!") -}) \ No newline at end of file +}) + From b5b45cd237595ef1858b7953f5a83f2a855080a3 Mon Sep 17 00:00:00 2001 From: Owen Rees Date: Sun, 8 Mar 2026 23:33:46 +0100 Subject: [PATCH 06/16] add two-fer --- config.json | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/config.json b/config.json index d61f665..c7a8a25 100644 --- a/config.json +++ b/config.json @@ -19,21 +19,11 @@ "average_run_time": 7 }, "files": { - "solution": [ - "src/%{pascal_slug}.res" - ], - "test": [ - "tests/%{pascal_slug}_test.res" - ], - "example": [ - ".meta/%{pascal_slug}.res" - ], - "exemplar": [ - ".meta/src/%{pascal_slug}.res" - ], - "editor": [ - "src/%{pascal_slug}.resi" - ] + "solution": ["src/%{pascal_slug}.res"], + "test": ["tests/%{pascal_slug}_test.res"], + "example": [".meta/%{pascal_slug}.res"], + "exemplar": [".meta/src/%{pascal_slug}.res"], + "editor": ["src/%{pascal_slug}.resi"] }, "exercises": { "practice": [ @@ -44,6 +34,14 @@ "practices": [], "prerequisites": [], "difficulty": 1 + }, + { + "slug": "two-fer", + "name": "Two-Fer", + "uuid": "2a03dbf7-5aae-4859-8fde-b51c11cab02f", + "practices": [], + "prerequisites": [], + "difficulty": 1 } ] }, From 3be92105fced5e13f7279f4158d3c366d9ae5db8 Mon Sep 17 00:00:00 2001 From: Owen Rees Date: Sun, 8 Mar 2026 23:34:10 +0100 Subject: [PATCH 07/16] update test imports --- exercises/practice/hello-world/.meta/generateTests.js | 8 ++++---- exercises/practice/two-fer/.meta/generateTests.js | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/exercises/practice/hello-world/.meta/generateTests.js b/exercises/practice/hello-world/.meta/generateTests.js index 4f38791..3d1bf96 100644 --- a/exercises/practice/hello-world/.meta/generateTests.js +++ b/exercises/practice/hello-world/.meta/generateTests.js @@ -2,10 +2,10 @@ import { fileURLToPath } from 'node:url'; import path from 'node:path'; import getValidCases from '../../../../test_generator/getCases.js'; import { generate, toPascalCase } from '../../../../test_generator/testGenerator.js'; -import * as config from './testTemplate.js'; +import { slug, assertionFunctions, template } from './testTemplate.js'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const cases = getValidCases(config.slug); -const outputPath = path.resolve(__dirname, '..', 'tests', `${toPascalCase(config.slug)}_test.res`); +const cases = getValidCases(slug); +const outputPath = path.resolve(__dirname, '..', 'tests', `${toPascalCase(slug)}_test.res`); -generate(outputPath, config.slug, cases); \ No newline at end of file +generate(outputPath, slug, cases, assertionFunctions, template); \ No newline at end of file diff --git a/exercises/practice/two-fer/.meta/generateTests.js b/exercises/practice/two-fer/.meta/generateTests.js index 4f38791..3d1bf96 100644 --- a/exercises/practice/two-fer/.meta/generateTests.js +++ b/exercises/practice/two-fer/.meta/generateTests.js @@ -2,10 +2,10 @@ import { fileURLToPath } from 'node:url'; import path from 'node:path'; import getValidCases from '../../../../test_generator/getCases.js'; import { generate, toPascalCase } from '../../../../test_generator/testGenerator.js'; -import * as config from './testTemplate.js'; +import { slug, assertionFunctions, template } from './testTemplate.js'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const cases = getValidCases(config.slug); -const outputPath = path.resolve(__dirname, '..', 'tests', `${toPascalCase(config.slug)}_test.res`); +const cases = getValidCases(slug); +const outputPath = path.resolve(__dirname, '..', 'tests', `${toPascalCase(slug)}_test.res`); -generate(outputPath, config.slug, cases); \ No newline at end of file +generate(outputPath, slug, cases, assertionFunctions, template); \ No newline at end of file From 6b7a83e04961ea83c7f6e5ecb2db082423c87838 Mon Sep 17 00:00:00 2001 From: Owen Rees Date: Sun, 8 Mar 2026 23:34:29 +0100 Subject: [PATCH 08/16] new test template for two-fer, with options --- exercises/practice/two-fer/.meta/testTemplate.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/exercises/practice/two-fer/.meta/testTemplate.js b/exercises/practice/two-fer/.meta/testTemplate.js index 19378ab..a8e6101 100644 --- a/exercises/practice/two-fer/.meta/testTemplate.js +++ b/exercises/practice/two-fer/.meta/testTemplate.js @@ -5,5 +5,9 @@ export const slug = 'two-fer'; export const assertionFunctions = [ stringEqual ] export const template = (c) => { - return `stringEqual(~message="${c.description}", twoFer(), "${c.expected}")` + 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}")` + } } \ No newline at end of file From 4d8098fae9e5400a25b48080ee4169ebd30bd455 Mon Sep 17 00:00:00 2001 From: Owen Rees Date: Sun, 8 Mar 2026 23:35:07 +0100 Subject: [PATCH 09/16] update assertionFunction to accept an array of assertions to use in template --- test_generator/testGenerator.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test_generator/testGenerator.js b/test_generator/testGenerator.js index e3b63af..9131f59 100644 --- a/test_generator/testGenerator.js +++ b/test_generator/testGenerator.js @@ -1,17 +1,18 @@ import fs from 'node:fs'; import path from 'node:path'; -import { template, assertionFunctions } from '../exercises/practice/hello-world/.meta/testTemplate.js'; export const toPascalCase = (slug) => slug.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(''); -export const generate = (outputPath, slug, cases) => { +export const generate = (outputPath, slug, cases, assertionFunctions, template) => { const moduleName = toPascalCase(slug); let output = `open Test open ${moduleName}\n\n`; - assertionFunctions.forEach(fn => output += `${fn}\n\n`) + if (Array.isArray(assertionFunctions)) { + output += assertionFunctions.map(fn => fn.trim()).join('\n\n') + '\n\n'; + } cases.forEach((c) => { output += `test("${c.description}", () => { From dacec9fced4a714ab51644f43432dc8be3024b2e Mon Sep 17 00:00:00 2001 From: Owen Rees Date: Sun, 8 Mar 2026 23:35:22 +0100 Subject: [PATCH 10/16] add two-fer exercise --- .../practice/two-fer/.docs/instructions.md | 24 +++++++++++++++++++ .../practice/two-fer/.docs/introduction.md | 8 +++++++ exercises/practice/two-fer/.meta/TwoFer.res | 6 +++++ exercises/practice/two-fer/.meta/config.json | 22 +++++++++++++++++ exercises/practice/two-fer/.meta/tests.toml | 19 +++++++++++++++ exercises/practice/two-fer/rescript.json | 15 ++++++++++++ exercises/practice/two-fer/src/TwoFer.res | 0 exercises/practice/two-fer/src/TwoFer.resi | 1 + .../practice/two-fer/tests/TwoFer_test.res | 17 +++++++++++++ 9 files changed, 112 insertions(+) create mode 100644 exercises/practice/two-fer/.docs/instructions.md create mode 100644 exercises/practice/two-fer/.docs/introduction.md create mode 100644 exercises/practice/two-fer/.meta/TwoFer.res create mode 100644 exercises/practice/two-fer/.meta/config.json create mode 100644 exercises/practice/two-fer/.meta/tests.toml create mode 100644 exercises/practice/two-fer/rescript.json create mode 100644 exercises/practice/two-fer/src/TwoFer.res create mode 100644 exercises/practice/two-fer/src/TwoFer.resi create mode 100644 exercises/practice/two-fer/tests/TwoFer_test.res diff --git a/exercises/practice/two-fer/.docs/instructions.md b/exercises/practice/two-fer/.docs/instructions.md new file mode 100644 index 0000000..adc5348 --- /dev/null +++ b/exercises/practice/two-fer/.docs/instructions.md @@ -0,0 +1,24 @@ +# Instructions + +Your task is to determine what you will say as you give away the extra cookie. + +If you know the person's name (e.g. if they're named Do-yun), then you will say: + +```text +One for Do-yun, one for me. +``` + +If you don't know the person's name, you will say _you_ instead. + +```text +One for you, one for me. +``` + +Here are some examples: + +| Name | Dialogue | +| :----- | :-------------------------- | +| Alice | One for Alice, one for me. | +| Bohdan | One for Bohdan, one for me. | +| | One for you, one for me. | +| Zaphod | One for Zaphod, one for me. | diff --git a/exercises/practice/two-fer/.docs/introduction.md b/exercises/practice/two-fer/.docs/introduction.md new file mode 100644 index 0000000..5947a22 --- /dev/null +++ b/exercises/practice/two-fer/.docs/introduction.md @@ -0,0 +1,8 @@ +# Introduction + +In some English accents, when you say "two for" quickly, it sounds like "two fer". +Two-for-one is a way of saying that if you buy one, you also get one for free. +So the phrase "two-fer" often implies a two-for-one offer. + +Imagine a bakery that has a holiday offer where you can buy two cookies for the price of one ("two-fer one!"). +You take the offer and (very generously) decide to give the extra cookie to someone else in the queue. diff --git a/exercises/practice/two-fer/.meta/TwoFer.res b/exercises/practice/two-fer/.meta/TwoFer.res new file mode 100644 index 0000000..73db48c --- /dev/null +++ b/exercises/practice/two-fer/.meta/TwoFer.res @@ -0,0 +1,6 @@ +let twoFer = name => { + switch name { + | None => "One for you, one for me." + | Some(nameString) => "One for " ++ nameString ++ ", one for me." + } +} diff --git a/exercises/practice/two-fer/.meta/config.json b/exercises/practice/two-fer/.meta/config.json new file mode 100644 index 0000000..ab9fae2 --- /dev/null +++ b/exercises/practice/two-fer/.meta/config.json @@ -0,0 +1,22 @@ +{ + "authors": [ + "tejasbubane", + "therealowenrees" + ], + "files": { + "solution": [ + "src/TwoFer.res" + ], + "test": [ + "tests/TwoFer_test.res" + ], + "example": [ + ".meta/TwoFer.res" + ], + "editor": [ + "src/TwoFer.resi" + ] + }, + "blurb": "Create a sentence of the form \"One for X, one for me.\".", + "source_url": "https://github.com/exercism/problem-specifications/issues/757" +} diff --git a/exercises/practice/two-fer/.meta/tests.toml b/exercises/practice/two-fer/.meta/tests.toml new file mode 100644 index 0000000..d0e3857 --- /dev/null +++ b/exercises/practice/two-fer/.meta/tests.toml @@ -0,0 +1,19 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[1cf3e15a-a3d7-4a87-aeb3-ba1b43bc8dce] +description = "no name given" + +[b4c6dbb8-b4fb-42c2-bafd-10785abe7709] +description = "a name given" + +[3549048d-1a6e-4653-9a79-b0bda163e8d5] +description = "another name given" diff --git a/exercises/practice/two-fer/rescript.json b/exercises/practice/two-fer/rescript.json new file mode 100644 index 0000000..3db8c50 --- /dev/null +++ b/exercises/practice/two-fer/rescript.json @@ -0,0 +1,15 @@ +{ + "name": "@exercism/rescript", + "sources": [ + { "dir": "src", "subdirs": true, "type": "dev" }, + { "dir": "tests", "subdirs": true, "type": "dev" } + ], + "package-specs": [ + { + "module": "esmodule", + "in-source": true + } + ], + "suffix": ".res.js", + "dev-dependencies": ["rescript-test"] +} diff --git a/exercises/practice/two-fer/src/TwoFer.res b/exercises/practice/two-fer/src/TwoFer.res new file mode 100644 index 0000000..e69de29 diff --git a/exercises/practice/two-fer/src/TwoFer.resi b/exercises/practice/two-fer/src/TwoFer.resi new file mode 100644 index 0000000..62f49c2 --- /dev/null +++ b/exercises/practice/two-fer/src/TwoFer.resi @@ -0,0 +1 @@ +let twoFer: option => string diff --git a/exercises/practice/two-fer/tests/TwoFer_test.res b/exercises/practice/two-fer/tests/TwoFer_test.res new file mode 100644 index 0000000..128a4f1 --- /dev/null +++ b/exercises/practice/two-fer/tests/TwoFer_test.res @@ -0,0 +1,17 @@ +open Test +open TwoFer + +let stringEqual = (~message=?, a: string, b: string) => assertion(~message?, ~operator="stringEqual", (a, b) => a == b, a, b) + +test("no name given", () => { + stringEqual(~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("another name given", () => { + stringEqual(~message="another name given", twoFer(Some("Bob")), "One for Bob, one for me.") +}) + From 55c545f68800c1bd85ece14269d7d05fd7b67501 Mon Sep 17 00:00:00 2001 From: Owen Rees Date: Mon, 9 Mar 2026 08:06:02 +0100 Subject: [PATCH 11/16] fix formatting --- config.json | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/config.json b/config.json index c7a8a25..5cfec7d 100644 --- a/config.json +++ b/config.json @@ -19,11 +19,21 @@ "average_run_time": 7 }, "files": { - "solution": ["src/%{pascal_slug}.res"], - "test": ["tests/%{pascal_slug}_test.res"], - "example": [".meta/%{pascal_slug}.res"], - "exemplar": [".meta/src/%{pascal_slug}.res"], - "editor": ["src/%{pascal_slug}.resi"] + "solution": [ + "src/%{pascal_slug}.res" + ], + "test": [ + "tests/%{pascal_slug}_test.res" + ], + "example": [ + ".meta/%{pascal_slug}.res" + ], + "exemplar": [ + ".meta/src/%{pascal_slug}.res" + ], + "editor": [ + "src/%{pascal_slug}.resi" + ] }, "exercises": { "practice": [ From 8865f17ffb3d3bda56ee1af8dd6b6a61942497a8 Mon Sep 17 00:00:00 2001 From: Owen Rees Date: Mon, 9 Mar 2026 08:08:18 +0100 Subject: [PATCH 12/16] change difficulty rating of two-fer --- config.json | 2 +- problem-specifications | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 160000 problem-specifications diff --git a/config.json b/config.json index 5cfec7d..982ebc4 100644 --- a/config.json +++ b/config.json @@ -51,7 +51,7 @@ "uuid": "2a03dbf7-5aae-4859-8fde-b51c11cab02f", "practices": [], "prerequisites": [], - "difficulty": 1 + "difficulty": 2 } ] }, diff --git a/problem-specifications b/problem-specifications new file mode 160000 index 0000000..0ebef3c --- /dev/null +++ b/problem-specifications @@ -0,0 +1 @@ +Subproject commit 0ebef3c03248e718b261815dc703c1a1600630f0 From 1de39e817fff5901f862846ceef6c48696f974f4 Mon Sep 17 00:00:00 2001 From: Owen Rees Date: Mon, 9 Mar 2026 08:12:50 +0100 Subject: [PATCH 13/16] add exercise code stub for student --- exercises/practice/two-fer/src/TwoFer.res | 1 + 1 file changed, 1 insertion(+) diff --git a/exercises/practice/two-fer/src/TwoFer.res b/exercises/practice/two-fer/src/TwoFer.res index e69de29..28ece47 100644 --- a/exercises/practice/two-fer/src/TwoFer.res +++ b/exercises/practice/two-fer/src/TwoFer.res @@ -0,0 +1 @@ +let twoFer = _ => panic("'twoFer not yet implemented") From 18539f1a78c9dfbc0b7d5a7f0a2725725b38f93e Mon Sep 17 00:00:00 2001 From: Owen Rees Date: Mon, 9 Mar 2026 08:26:12 +0100 Subject: [PATCH 14/16] get slug name from filepath --- exercises/practice/two-fer/.meta/testTemplate.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/exercises/practice/two-fer/.meta/testTemplate.js b/exercises/practice/two-fer/.meta/testTemplate.js index a8e6101..3bb0fb7 100644 --- a/exercises/practice/two-fer/.meta/testTemplate.js +++ b/exercises/practice/two-fer/.meta/testTemplate.js @@ -1,6 +1,9 @@ +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +const __dirname = path.dirname(fileURLToPath(import.meta.url)); import { stringEqual } from "../../../../test_generator/assertions.js"; -export const slug = 'two-fer'; +export const slug = path.basename(path.resolve(__dirname, '..')) export const assertionFunctions = [ stringEqual ] From e64d5b6b7a6cd63efed594ceb2e2fcdc9bdd2ac4 Mon Sep 17 00:00:00 2001 From: Owen Rees Date: Mon, 9 Mar 2026 09:00:59 +0100 Subject: [PATCH 15/16] move generateTests out of .meta folder and into the test generator folder, passing in the needed directory name as an argument --- Makefile | 4 ++-- .../practice/hello-world/.meta/generateTests.js | 11 ----------- exercises/practice/hello-world/.meta/testTemplate.js | 12 ++++++++++-- exercises/practice/two-fer/.meta/generateTests.js | 11 ----------- exercises/practice/two-fer/.meta/testTemplate.js | 11 ++++++++--- test_generator/generateTests.js | 10 ++++++++++ 6 files changed, 30 insertions(+), 29 deletions(-) delete mode 100644 exercises/practice/hello-world/.meta/generateTests.js delete mode 100644 exercises/practice/two-fer/.meta/generateTests.js create mode 100644 test_generator/generateTests.js diff --git a/Makefile b/Makefile index ab6cf62..6a0bca2 100644 --- a/Makefile +++ b/Makefile @@ -53,9 +53,9 @@ format: generate-tests: @echo "Generating tests for all exercises..." @for exercise in $(EXERCISES); do \ - if [ -f exercises/practice/$$exercise/.meta/generateTests.js ]; then \ + if [ -f exercises/practice/$$exercise/.meta/testTemplate.js ]; then \ echo "-> Generating: $$exercise"; \ - node exercises/practice/$$exercise/.meta/generateTests.js || exit 1; \ + node exercises/practice/$$exercise/.meta/testTemplate.js || exit 1; \ else \ echo "-> Skipping: $$exercise (no generator found)"; \ fi \ diff --git a/exercises/practice/hello-world/.meta/generateTests.js b/exercises/practice/hello-world/.meta/generateTests.js deleted file mode 100644 index 3d1bf96..0000000 --- a/exercises/practice/hello-world/.meta/generateTests.js +++ /dev/null @@ -1,11 +0,0 @@ -import { fileURLToPath } from 'node:url'; -import path from 'node:path'; -import getValidCases from '../../../../test_generator/getCases.js'; -import { generate, toPascalCase } from '../../../../test_generator/testGenerator.js'; -import { slug, assertionFunctions, template } from './testTemplate.js'; - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const cases = getValidCases(slug); -const outputPath = path.resolve(__dirname, '..', 'tests', `${toPascalCase(slug)}_test.res`); - -generate(outputPath, slug, cases, assertionFunctions, template); \ No newline at end of file diff --git a/exercises/practice/hello-world/.meta/testTemplate.js b/exercises/practice/hello-world/.meta/testTemplate.js index 7075f9f..757ce2f 100644 --- a/exercises/practice/hello-world/.meta/testTemplate.js +++ b/exercises/practice/hello-world/.meta/testTemplate.js @@ -1,9 +1,17 @@ +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; import { stringEqual } from "../../../../test_generator/assertions.js"; +import { generateTests } from '../../../../test_generator/generateTests.js'; -export const slug = 'hello-world'; +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}")` -} \ No newline at end of file +} + +generateTests(__dirname, slug, assertionFunctions, template) \ No newline at end of file diff --git a/exercises/practice/two-fer/.meta/generateTests.js b/exercises/practice/two-fer/.meta/generateTests.js deleted file mode 100644 index 3d1bf96..0000000 --- a/exercises/practice/two-fer/.meta/generateTests.js +++ /dev/null @@ -1,11 +0,0 @@ -import { fileURLToPath } from 'node:url'; -import path from 'node:path'; -import getValidCases from '../../../../test_generator/getCases.js'; -import { generate, toPascalCase } from '../../../../test_generator/testGenerator.js'; -import { slug, assertionFunctions, template } from './testTemplate.js'; - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const cases = getValidCases(slug); -const outputPath = path.resolve(__dirname, '..', 'tests', `${toPascalCase(slug)}_test.res`); - -generate(outputPath, slug, cases, assertionFunctions, template); \ No newline at end of file diff --git a/exercises/practice/two-fer/.meta/testTemplate.js b/exercises/practice/two-fer/.meta/testTemplate.js index 3bb0fb7..470f8c9 100644 --- a/exercises/practice/two-fer/.meta/testTemplate.js +++ b/exercises/practice/two-fer/.meta/testTemplate.js @@ -1,16 +1,21 @@ import path from 'node:path'; import { fileURLToPath } from 'node:url'; -const __dirname = path.dirname(fileURLToPath(import.meta.url)); import { stringEqual } from "../../../../test_generator/assertions.js"; +import { generateTests } from '../../../../test_generator/generateTests.js'; -export const slug = path.basename(path.resolve(__dirname, '..')) +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}")` } -} \ No newline at end of file +} + +generateTests(__dirname, slug, assertionFunctions, template) \ No newline at end of file diff --git a/test_generator/generateTests.js b/test_generator/generateTests.js new file mode 100644 index 0000000..2638edf --- /dev/null +++ b/test_generator/generateTests.js @@ -0,0 +1,10 @@ +import path from 'node:path'; +import getValidCases from './getCases.js'; +import { generate, toPascalCase } from './testGenerator.js'; + +export const generateTests = (dir, slug, assertionFunctions, template) => { + const outputPath = path.resolve(dir, '..', 'tests', `${toPascalCase(slug)}_test.res`); + const cases = getValidCases(slug); + generate(outputPath, slug, cases, assertionFunctions, template); +} + From f26effc6da1b5b354f6328dc2f5529382d7b14a5 Mon Sep 17 00:00:00 2001 From: Owen Rees Date: Mon, 9 Mar 2026 09:09:41 +0100 Subject: [PATCH 16/16] reduce test generator files and expose single function from testGenerator.js --- exercises/practice/hello-world/.meta/testTemplate.js | 2 +- exercises/practice/two-fer/.meta/testTemplate.js | 2 +- test_generator/generateTests.js | 10 ---------- test_generator/testGenerator.js | 11 +++++++++-- 4 files changed, 11 insertions(+), 14 deletions(-) delete mode 100644 test_generator/generateTests.js diff --git a/exercises/practice/hello-world/.meta/testTemplate.js b/exercises/practice/hello-world/.meta/testTemplate.js index 757ce2f..0db698e 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/generateTests.js'; +import { generateTests } from '../../../../test_generator/testGenerator.js'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const slug = path.basename(path.resolve(__dirname, '..')) diff --git a/exercises/practice/two-fer/.meta/testTemplate.js b/exercises/practice/two-fer/.meta/testTemplate.js index 470f8c9..94e90a9 100644 --- a/exercises/practice/two-fer/.meta/testTemplate.js +++ b/exercises/practice/two-fer/.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/generateTests.js'; +import { generateTests } from '../../../../test_generator/testGenerator.js'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const slug = path.basename(path.resolve(__dirname, '..')) diff --git a/test_generator/generateTests.js b/test_generator/generateTests.js deleted file mode 100644 index 2638edf..0000000 --- a/test_generator/generateTests.js +++ /dev/null @@ -1,10 +0,0 @@ -import path from 'node:path'; -import getValidCases from './getCases.js'; -import { generate, toPascalCase } from './testGenerator.js'; - -export const generateTests = (dir, slug, assertionFunctions, template) => { - const outputPath = path.resolve(dir, '..', 'tests', `${toPascalCase(slug)}_test.res`); - const cases = getValidCases(slug); - generate(outputPath, slug, cases, assertionFunctions, template); -} - diff --git a/test_generator/testGenerator.js b/test_generator/testGenerator.js index 9131f59..ac7730a 100644 --- a/test_generator/testGenerator.js +++ b/test_generator/testGenerator.js @@ -1,10 +1,17 @@ import fs from 'node:fs'; import path from 'node:path'; +import getValidCases from './getCases.js'; -export const toPascalCase = (slug) => +export const generateTests = (dir, slug, assertionFunctions, template) => { + const outputPath = path.resolve(dir, '..', 'tests', `${toPascalCase(slug)}_test.res`); + const cases = getValidCases(slug); + generate(outputPath, slug, cases, assertionFunctions, template); +} + +const toPascalCase = (slug) => slug.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(''); -export const generate = (outputPath, slug, cases, assertionFunctions, template) => { +const generate = (outputPath, slug, cases, assertionFunctions, template) => { const moduleName = toPascalCase(slug); let output = `open Test