From 65e5690eea9a07de785febd53ae5646b4173b472 Mon Sep 17 00:00:00 2001 From: Ryan Hartlage <2488333+ryanplusplus@users.noreply.github.com> Date: Sat, 7 Nov 2026 08:07:50 -0500 Subject: [PATCH 1/5] Spec generator for pangram --- .../practice/pangram/.meta/spec_generator.lua | 11 +++++ exercises/practice/pangram/pangram_spec.lua | 41 +++++++------------ 2 files changed, 25 insertions(+), 27 deletions(-) create mode 100644 exercises/practice/pangram/.meta/spec_generator.lua diff --git a/exercises/practice/pangram/.meta/spec_generator.lua b/exercises/practice/pangram/.meta/spec_generator.lua new file mode 100644 index 00000000..ce19cfd9 --- /dev/null +++ b/exercises/practice/pangram/.meta/spec_generator.lua @@ -0,0 +1,11 @@ +local utils = require 'utils' + +return { + module_name = 'is_pangram', + + generate_test = function(case) + local template = [[ + assert.is_%s(is_pangram(%s))]] + return template:format(case.expected, utils.stringify(case.input.sentence)) + end +} diff --git a/exercises/practice/pangram/pangram_spec.lua b/exercises/practice/pangram/pangram_spec.lua index 2b7abdf2..ebe1be13 100644 --- a/exercises/practice/pangram/pangram_spec.lua +++ b/exercises/practice/pangram/pangram_spec.lua @@ -1,56 +1,43 @@ local is_pangram = require('pangram') describe('pangram', function() - it('should not consider the empty string to be a pangram', function() + it('empty sentence', function() assert.is_false(is_pangram('')) end) - it('should not consider a sentence with 26 of the same letter to be a pangram', function() - assert.is_false(is_pangram(string.rep('a', 26))) - end) - - it('should consider the alphabet to be a pangram', function() + it('perfect lower case', function() assert.is_true(is_pangram('abcdefghijklmnopqrstuvwxyz')) end) - it('should consider a sentence with one of each letter to be a pangram', function() + it('only lower case', function() assert.is_true(is_pangram('the quick brown fox jumps over the lazy dog')) end) - it('should allow mixed case in pangrams', function() - assert.is_true(is_pangram('The Quick Brown Fox Jumps Over The Lazy Dog')) + it("missing the letter 'x'", function() + assert.is_false(is_pangram('a quick movement of the enemy will jeopardize five gunboats')) end) - it('should allow punctuation in pangrams', function() - assert.is_true(is_pangram('the.quick.brown.fox.jumps.over.the.lazy.dog')) + it("missing the letter 'h'", function() + assert.is_false(is_pangram('five boxing wizards jump quickly at it')) end) - it('should allow underscores in pangrams', function() - assert.is_true(is_pangram('the_quick_brown_fox_jumps_over_the_lazy.dog')) + it('with underscores', function() + assert.is_true(is_pangram('the_quick_brown_fox_jumps_over_the_lazy_dog')) end) - it('should allow numbers in pangrams', function() + it('with numbers', function() assert.is_true(is_pangram('the 1 quick brown fox jumps over the 2 lazy dogs')) end) - it('should not allow letters in pangrams to be replaced by numbers', function() + it('missing letters replaced by numbers', function() assert.is_false(is_pangram('7h3 qu1ck brown fox jumps ov3r 7h3 lazy dog')) end) - it('should not consider a sentence with only 25 unique letters to be a pangram', function() - assert.is_false(is_pangram('a quick movement of the enemy will jeopardize five gunboats')) - assert.is_false(is_pangram('the quick brown fish jumps over the lazy dog')) - end) - - it('should allow non-ASCII characters in pangrams', function() - assert.is_true(is_pangram('Victor jagt zwölf Boxkämpfer quer über den großen Sylter Deich.')) - end) - - it('should not consider non-ASCII characters to be letters', function() - assert.is_false(is_pangram('äbcdefghijklmnopqrstuvwxyz')) + it('mixed case and punctuation', function() + assert.is_true(is_pangram('"Five quacking Zephyrs jolt my wax bed."')) end) - it('should not consider a-m and A-M to be a pangram', function() + it('a-m and a-m are 26 different characters but not a pangram', function() assert.is_false(is_pangram('abcdefghijklm ABCDEFGHIJKLM')) end) end) From d7e710e11989e1d5b038b9aff20eee79354bc8b0 Mon Sep 17 00:00:00 2001 From: Ryan Hartlage <2488333+ryanplusplus@users.noreply.github.com> Date: Sat, 7 Nov 2026 08:09:19 -0500 Subject: [PATCH 2/5] Spec generator for reverse-string --- .../practice/reverse-string/.meta/spec_generator.lua | 11 +++++++++++ .../practice/reverse-string/reverse-string_spec.lua | 4 ++++ 2 files changed, 15 insertions(+) create mode 100644 exercises/practice/reverse-string/.meta/spec_generator.lua diff --git a/exercises/practice/reverse-string/.meta/spec_generator.lua b/exercises/practice/reverse-string/.meta/spec_generator.lua new file mode 100644 index 00000000..7ff12823 --- /dev/null +++ b/exercises/practice/reverse-string/.meta/spec_generator.lua @@ -0,0 +1,11 @@ +local utils = require 'utils' + +return { + module_name = 'reverse_string', + + generate_test = function(case) + local template = [[ + assert.are.equal(%s, reverse_string(%s))]] + return template:format(utils.stringify(case.expected), utils.stringify(case.input.value)) + end +} diff --git a/exercises/practice/reverse-string/reverse-string_spec.lua b/exercises/practice/reverse-string/reverse-string_spec.lua index 0ac8f06e..b87f1702 100644 --- a/exercises/practice/reverse-string/reverse-string_spec.lua +++ b/exercises/practice/reverse-string/reverse-string_spec.lua @@ -20,4 +20,8 @@ describe('reverse-string', function() it('a palindrome', function() assert.are.equal('racecar', reverse_string('racecar')) end) + + it('an even-sized word', function() + assert.are.equal('reward', reverse_string('drawer')) + end) end) From 29574f4462c15fbddb19641dfb98bcf4215f59cc Mon Sep 17 00:00:00 2001 From: Ryan Hartlage <2488333+ryanplusplus@users.noreply.github.com> Date: Sat, 7 Nov 2026 08:15:10 -0500 Subject: [PATCH 3/5] Spec generator for rna-transcription --- .../rna-transcription/.meta/spec_generator.lua | 11 +++++++++++ .../rna-transcription_spec.lua | 18 +++++++++++------- 2 files changed, 22 insertions(+), 7 deletions(-) create mode 100644 exercises/practice/rna-transcription/.meta/spec_generator.lua diff --git a/exercises/practice/rna-transcription/.meta/spec_generator.lua b/exercises/practice/rna-transcription/.meta/spec_generator.lua new file mode 100644 index 00000000..834f4b80 --- /dev/null +++ b/exercises/practice/rna-transcription/.meta/spec_generator.lua @@ -0,0 +1,11 @@ +local utils = require 'utils' + +return { + module_name = 'to_rna', + + generate_test = function(case) + local template = [[ + assert.are.equal(%s, to_rna(%s))]] + return template:format(utils.stringify(case.expected), utils.stringify(case.input.dna)) + end +} diff --git a/exercises/practice/rna-transcription/rna-transcription_spec.lua b/exercises/practice/rna-transcription/rna-transcription_spec.lua index 3f59ef88..f3c57d1b 100644 --- a/exercises/practice/rna-transcription/rna-transcription_spec.lua +++ b/exercises/practice/rna-transcription/rna-transcription_spec.lua @@ -1,23 +1,27 @@ local to_rna = require('rna-transcription') describe('rna-transcription', function() - it('transcribes cytosine to guanosine', function() + it('empty rna sequence', function() + assert.are.equal('', to_rna('')) + end) + + it('rna complement of cytosine is guanine', function() assert.are.equal('G', to_rna('C')) end) - it('transcribes guanosine to cytosine', function() + it('rna complement of guanine is cytosine', function() assert.are.equal('C', to_rna('G')) end) - it('transcribes adenosine to uracil', function() - assert.are.equal('U', to_rna('A')) + it('rna complement of thymine is adenine', function() + assert.are.equal('A', to_rna('T')) end) - it('transcribes thymine to adenosine', function() - assert.are.equal('A', to_rna('T')) + it('rna complement of adenine is uracil', function() + assert.are.equal('U', to_rna('A')) end) - it('transcribes all DNA nucleotides to their RNA complements', function() + it('rna complement', function() assert.are.equal('UGCACCAGAAUU', to_rna('ACGTGGTCTTAA')) end) end) From 406c7ab56f5d70e7640e9a7b38374d2ecc360780 Mon Sep 17 00:00:00 2001 From: Ryan Hartlage <2488333+ryanplusplus@users.noreply.github.com> Date: Sat, 7 Nov 2026 08:21:09 -0500 Subject: [PATCH 4/5] Spec generator for scrabble-score --- .../scrabble-score/.meta/spec_generator.lua | 11 +++++ .../scrabble-score/scrabble-score_spec.lua | 46 +++++++++++++------ 2 files changed, 44 insertions(+), 13 deletions(-) create mode 100644 exercises/practice/scrabble-score/.meta/spec_generator.lua diff --git a/exercises/practice/scrabble-score/.meta/spec_generator.lua b/exercises/practice/scrabble-score/.meta/spec_generator.lua new file mode 100644 index 00000000..6aca7796 --- /dev/null +++ b/exercises/practice/scrabble-score/.meta/spec_generator.lua @@ -0,0 +1,11 @@ +local utils = require 'utils' + +return { + module_name = 'scrabble_score', + + generate_test = function(case) + local template = [[ + assert.are.equal(%d, scrabble_score.score(%s))]] + return template:format(case.expected, utils.stringify(case.input.word)) + end +} diff --git a/exercises/practice/scrabble-score/scrabble-score_spec.lua b/exercises/practice/scrabble-score/scrabble-score_spec.lua index 9b8fa003..630c13f0 100644 --- a/exercises/practice/scrabble-score/scrabble-score_spec.lua +++ b/exercises/practice/scrabble-score/scrabble-score_spec.lua @@ -1,27 +1,47 @@ -local score = require('scrabble-score').score +local scrabble_score = require('scrabble-score') describe('scrabble-score', function() - it('scores an empty word as zero', function() - assert.are.equal(0, score('')) + it('lowercase letter', function() + assert.are.equal(1, scrabble_score.score('a')) end) - it('scores a nil as zero', function() - assert.are.equal(0, score(nil)) + it('uppercase letter', function() + assert.are.equal(1, scrabble_score.score('A')) end) - it('scores a very short word', function() - assert.are.equal(1, score('a')) + it('valuable letter', function() + assert.are.equal(4, scrabble_score.score('f')) end) - it('scores the word by the number of letters', function() - assert.are.equal(6, score('street')) + it('short word', function() + assert.are.equal(2, scrabble_score.score('at')) end) - it('scores more complicated words with more', function() - assert.are.equal(22, score('quirky')) + it('short, valuable word', function() + assert.are.equal(12, scrabble_score.score('zoo')) end) - it('scores case insensitive words', function() - assert.are.equal(41, score('OXYPHENBUTAZONE')) + it('medium word', function() + assert.are.equal(6, scrabble_score.score('street')) + end) + + it('medium, valuable word', function() + assert.are.equal(22, scrabble_score.score('quirky')) + end) + + it('long, mixed-case word', function() + assert.are.equal(41, scrabble_score.score('OxyphenButazone')) + end) + + it('english-like word', function() + assert.are.equal(8, scrabble_score.score('pinata')) + end) + + it('empty input', function() + assert.are.equal(0, scrabble_score.score('')) + end) + + it('entire alphabet available', function() + assert.are.equal(87, scrabble_score.score('abcdefghijklmnopqrstuvwxyz')) end) end) From 72b66f0897e0bbd468c9df11f0a2e6b80e8c6b98 Mon Sep 17 00:00:00 2001 From: Ryan Hartlage <2488333+ryanplusplus@users.noreply.github.com> Date: Sat, 7 Nov 2026 08:31:53 -0500 Subject: [PATCH 5/5] Spec generator for nucleotide-count --- .../nucleotide-count/.meta/example.lua | 7 -- .../nucleotide-count/.meta/spec_generator.lua | 35 +++++++++ .../nucleotide-count_spec.lua | 72 +++++-------------- 3 files changed, 52 insertions(+), 62 deletions(-) create mode 100644 exercises/practice/nucleotide-count/.meta/spec_generator.lua diff --git a/exercises/practice/nucleotide-count/.meta/example.lua b/exercises/practice/nucleotide-count/.meta/example.lua index 2c3fa952..0c3fec89 100644 --- a/exercises/practice/nucleotide-count/.meta/example.lua +++ b/exercises/practice/nucleotide-count/.meta/example.lua @@ -14,11 +14,4 @@ function DNA:new(str) return setmetatable({ nucleotideCounts = nc, NUCLEOTIDES = n }, self) end -function DNA:count(symbol) - if self.nucleotideCounts[symbol] then - return self.nucleotideCounts[symbol] - end - return error('Invalid Nucleotide') -end - return DNA diff --git a/exercises/practice/nucleotide-count/.meta/spec_generator.lua b/exercises/practice/nucleotide-count/.meta/spec_generator.lua new file mode 100644 index 00000000..8545ac43 --- /dev/null +++ b/exercises/practice/nucleotide-count/.meta/spec_generator.lua @@ -0,0 +1,35 @@ +local utils = require 'utils' + +local function render_counts(counts) + local keys = {} + for k in pairs(counts) do + table.insert(keys, k) + end + table.sort(keys) + + local entries = utils.map(keys, function(k) + return k .. ' = ' .. counts[k] + end) + + return '{ ' .. table.concat(entries, ', ') .. ' }' +end + +return { + module_name = 'DNA', + + generate_test = function(case) + if case.expected.error then + local template = [[ + assert.has_error(function() + DNA:new(%s) + end)]] + return template:format(utils.stringify(case.input.strand)) + else + local template = [[ + local dna = DNA:new(%s) + local expected = %s + assert.are.same(expected, dna.nucleotideCounts)]] + return template:format(utils.stringify(case.input.strand), render_counts(case.expected)) + end + end +} diff --git a/exercises/practice/nucleotide-count/nucleotide-count_spec.lua b/exercises/practice/nucleotide-count/nucleotide-count_spec.lua index 17472ff9..437629d0 100644 --- a/exercises/practice/nucleotide-count/nucleotide-count_spec.lua +++ b/exercises/practice/nucleotide-count/nucleotide-count_spec.lua @@ -1,71 +1,33 @@ local DNA = require('nucleotide-count') describe('nucleotide-count', function() - it('has no nucleotides', function() - local expected = { A = 0, T = 0, C = 0, G = 0 } - dna = DNA:new('') - local result = dna.nucleotideCounts - assert.are.same(expected, result) - end) - - it('has no adenosine', function() + it('empty strand', function() local dna = DNA:new('') - local expected = 0 - result = dna:count('A') - assert.are.same(expected, result) - end) - - it('repetitive cytosine gets counts', function() - local dna = DNA:new('CCCCC') - local expected = 5 - result = dna:count('C') - assert.are.same(expected, result) + local expected = { A = 0, C = 0, G = 0, T = 0 } + assert.are.same(expected, dna.nucleotideCounts) end) - it('repetitive sequence has only guanosine', function() - local dna = DNA:new('GGGGGGGG') - expected = { A = 0, T = 0, C = 0, G = 8 } - result = dna.nucleotideCounts - assert.are.same(expected, result) + it('can count one nucleotide in single-character input', function() + local dna = DNA:new('G') + local expected = { A = 0, C = 0, G = 1, T = 0 } + assert.are.same(expected, dna.nucleotideCounts) end) - it('counts only thymine', function() - local dna = DNA:new('GGGGTAACCCGG') - expected = 1 - result = dna:count('T') - assert.are.same(expected, result) + it('strand with repeated nucleotide', function() + local dna = DNA:new('GGGGGGG') + local expected = { A = 0, C = 0, G = 7, T = 0 } + assert.are.same(expected, dna.nucleotideCounts) end) - it('counts a nucleotide only once', function() - local dna = DNA:new('GGTTGG') - expected = 2 - result = dna:count('T') - assert.are.same(expected, result) + it('strand with multiple nucleotides', function() + local dna = DNA:new('AGCTTTTCATTCTGACTGCAACGGGCAATATGTCTCTGTGTGGATTAAAAAAAGAGTGTCTGATAGCAGC') + local expected = { A = 20, C = 12, G = 17, T = 21 } + assert.are.same(expected, dna.nucleotideCounts) end) - it('validates dna sequences', function() - assert.has.errors(function() - DNA:new('INVALID') - end) + it('strand with invalid nucleotides', function() assert.has_error(function() - DNA:new('INVALID') - end, 'Invalid Sequence') - end) - - it('validates counting nucleotides', function() - local dna = DNA:new('GGTTGG') - assert.has.errors(function() - dna:count('X') + DNA:new('AGXXACT') end) - assert.has_error(function() - dna:count('X') - end, 'Invalid Nucleotide') - end) - - it('counts all nucleotides', function() - local dna = DNA:new('AGCTTTTCATTCTGACTGCAACGGGCAATATGTCTCTGTGTGGATTAAAAAAAGAGTGTCTGATAGCAGC') - expected = { A = 20, T = 21, G = 17, C = 12 } - result = dna.nucleotideCounts - assert.are.same(expected, result) end) end)