Repository: exercism/python
Pull request #2746 (2021-11-10 05:53:17)

[Grade-School]: Update from Problem Specifications
Pulled down updates from problem-specifications. 
Updated Jinja2 template & regenerated test file. 
Re-wrote `example.py` to pass new tests.
Updated `instructions.md`

github-actions[bot]: Hi & Welcome! 👋🏽&nbsp;👋

**Thank you** for contributing to `exercism/python` 💛&nbsp;💙 -- we really appreciate it! 🌟 🌈


<br>

This is an automated [🤖&nbsp;🤖 &nbsp;] comment for the  **`maintainers`** of this repository, notifying them of your contribution.  🎉
&nbsp;  Someone will review/reply to your changes shortly. (_usually within **72 hours**._)
You can safely ignore the **`maintainers`**  section below.

<br>
<details open>
  <summary>⚠️ &nbsp;<b>Please be aware</b>&nbsp;⚠️ </summary>

  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;_This repo does not generally accept Pull Requests unless they follow our [contributing guidelines](https://github.com/exercism/python/blob/main/CONTRIBUTING.md) and are:_

  &nbsp;&nbsp;&nbsp;&nbsp; 1️⃣ &nbsp;&nbsp;&nbsp; Small, contained fixes for typos/grammar/punctuation/code syntax on [one] exercise,
  &nbsp;&nbsp;&nbsp;&nbsp; 2️⃣ &nbsp;&nbsp;&nbsp;&nbsp;Medium changes that have been agreed/discussed via a filed issue,
  &nbsp;&nbsp;&nbsp;&nbsp; 3️⃣ &nbsp;&nbsp;&nbsp;&nbsp;Contributions from our [help wanted][help-wanted] issue list,
  &nbsp;&nbsp;&nbsp;&nbsp; 4️⃣ &nbsp;&nbsp;&nbsp;&nbsp;Larger (_previously agreed-upon_) contributions from recent & regular (_within the last 6 months_) contributors.

  &nbsp;&nbsp;&nbsp;&nbsp;Pull Requests not in these categories **will be closed**.  😞

</details>
<br>


---

 #### **`💙  It looks like you are changing/adding files in a Practice Exercise! 💙 `**

**`Please note:`** generally, changes to existing [practice exercises](https://github.com/exercism/docs/blob/main/building/tracks/practice-exercises.md) or the addition
of new ones are proposed & discussed in [problem-specifications](https://github.com/exercism/problem-specifications) first, so that all tracks can potentially benefit.
Once a change is approved there by three maintainers, it becomes available for various language tracks to then implement.

<br>
<details>
  <summary>‼️&nbsp;&nbsp;<b><em>Did You...</em></b></summary>
<br>
<table>
<td><br>

 - [ ]  &nbsp;Update & rebase your branch with any (recent) upstream changes.
 - [ ]  &nbsp;Spell and grammar check all prose changes.
 - [ ]  &nbsp;Run [Prettier](https://prettier.io/) on all markdown and JSON files.
     - (_Optionally_) run [yapf](https://github.com/google/yapf) ([_yapf config_](https://github.com/exercism/python/blob/main/.style.yapf)) to help format your code.
 - [ ]  &nbsp;Run [flake8](http://flake8.pycqa.org/) with [_flake8 config_](https://github.com/exercism/python/blob/main/.flake8) to check general code style standards.
 - [ ] &nbsp; Run [pylint](https://pylint.pycqa.org/en/v2.11.1/user_guide/index.html) with [_pylint config_](https://github.com/exercism/python/blob/main/pylintrc) to check extended code style standards.
 - [ ]  &nbsp;Use pytest or the [python-track-test-runner](https://github.com/exercism/python-test-runner) to test any changed `example.py`/`exemplar.py`files
 &nbsp;against their associated test files.
 - [ ]  &nbsp;Similarly, use [pytest](https://docs.pytest.org/en/6.2.x/contents.html) or the [python-track-test-runner](https://github.com/exercism/python-test-runner) to test any changed _**test**_ files.
     - Check that tests **fail** properly, as well as succeed.
     &nbsp;(_**e.g.**, make some tests fail on purpose to "test the tests" & failure messages_).
 - [ ]  &nbsp;Double-check all files for proper EOL.
 - [ ]  &nbsp;[Regenerate](https://github.com/exercism/python/blob/main/CONTRIBUTING.md#generating-practice-exercise-documents) exercise documents when you modified or created a `hints.md` file for a practice exercise.
 - [ ]  &nbsp;[Regenerate the test file](https://github.com/exercism/python/blob/main/CONTRIBUTING.md#auto-generated-test-files-and-test-templates) if you modified or created a `JinJa2` template file for a practice exercise.
     -  Run the generated test file result against its `example.py`.
 - [ ]  &nbsp;Run [`configlet-lint`](https://github.com/exercism/configlet#configlet-lint) if the track [config.json](https://github.com/exercism/docs/blob/main/building/tracks/config-json.md), or any other exercise `config.json` has been modified.

<br>
</td>
</table>
<br>
</details>

<details>
<summary>✅️&nbsp;&nbsp;<b><em>Have You Checked...</em></b></summary>
<br>
<table>
<td>

.
_**Are there any additional changes you need to make?**_
Please make sure all associated files are present and consistent with your changes!

[Practice Exercise Anatomy](https://github.com/exercism/docs/blob/main/building/tracks/practice-exercises.md)

   - [ ] `.docs/instructions.md`(**required**)
   - [ ] `.docs/introduction.md`(_optional_)
   - [ ] `.docs/introduction.append.md`(_optional_)
   - [ ] `.docs/instructions.append.md` (_optional_)
   - [ ] `.docs/hints.md`(_optional_)
   - [ ] `.meta/config.json` (**required**)
   - [ ] `.meta/example.py` (**required**)
   - [ ] `.meta/design.md` (_optional_)
   - [ ] `.meta/template.j2` (_template for generating tests from canonical data_)
   - [ ] `.meta/tests.toml` (_do you need to include or exclude any cases?_)
   - [ ] `<exercise-slug>_test.py` (_**do you need to regenerate this?**_)
   - [ ] `<exercise-slug>.py` (**required**)

</td>
</table>
</details>
<br>



<h2>🛠️ &nbsp;&nbsp; Maintainers &nbsp;&nbsp;</h2>
<h3> Please take note 📒  of the following sections/review items 👀   ✨ </h3>
<br>
<br>
<details>
<summary> 🌈  &nbsp;Acknowledgements and Reputation</summary>
<br>
<table>
<td>

- ❓ Does this PR need to receive a label with a [`reputation modifier`](https://github.com/exercism/docs/blob/main/using/product/reputation.md#creating-a-pull-request)?
     -  `medium` is awarded by default.
- ❓ Does this contributor need to be added to the **exercise** [`authors` or `contributors`](https://github.com/exercism/docs/blob/main/building/tracks/practice-exercises.md#file-metaconfigjson)?
- ❓ Does this contributor need to be added to the **concept** [`authors` or `contributors`](https://github.com/exercism/docs/blob/main/building/tracks/concepts.md#file-metaconfigjson)?
- ❓ Is there an associated issue or issues that should be linked to this PR?

</td>
</table>
</details>
<details>
<summary>💫 &nbsp;General Code Quality</summary>
<ul>
<table>
<td>
  <h4>Verify:</h4>

  -  [ ]  &nbsp;The branch was updated & rebased with any (recent) upstream changes.
  -  [ ]  &nbsp;All prose was checked for spelling and grammar.
  -  [ ]  &nbsp;Files are formatted via [yapf](https://github.com/google/yapf) ([_yapf config_](https://github.com/exercism/python/blob/main/.style.yapf)) & conform to our [coding standards](https://github.com/exercism/python/blob/main/CONTRIBUTING.md#coding-standards)
  -  [ ]  &nbsp;Files pass [flake8](http://flake8.pycqa.org/) with [_flake8 config_](https://github.com/exercism/python/blob/main/.flake8) & [pylint](https://pylint.pycqa.org/en/v2.11.1/user_guide/index.html) with [_pylint config_](https://github.com/exercism/python/blob/main/pylintrc).
  -  [ ]  &nbsp;Changed `example.py`/`exemplar.py` files still pass their associated **test files**.
  -  [ ]  &nbsp;Changed _**test files**_ still work with associated `example.py`/`exemplar.py` files.
      - Check that tests **fail** properly, as well as succeed.
             (_**e.g.**, make some tests fail on purpose to "test the tests" & failure messages_).
  -  [ ]  &nbsp;All files have proper EOL.
  -  [ ]  &nbsp;If a `JinJa2` template was modified/created, was the test file [regenerated](https://github.com/exercism/python/blob/main/CONTRIBUTING.md#auto-generated-test-files-and-test-templates)?
      - Does the regenerated test file successfully test the exercises `example.py` file?
  -  [ ]  &nbsp;The branch passes all CI checks & [`configlet-lint`](https://github.com/exercism/configlet#configlet-lint).

<br>
</td>
</table>
</ul>
<br>
</details>
<details>
<summary>🌿 &nbsp;Changes to Concept Exercises</summary>
<br>

  -   ❓ Are all required files still up-to-date & configured correctly for this change?_
  -   ❓ Does `<exercise>/.meta/design.md` need to be updated with new implementation/design decisions
  -   ❓ Do these changes require follow-on/supporting changes to related **concept** documents?

<ul>
<details>
<summary><em><b> ✨ &nbsp;Where applicable, check the following&nbsp;✨  </b></em></summary>
<br>
<ul>
<table>
<td>

(_as a reminder: [Concept Exercise Anatomy](https://github.com/exercism/docs/blob/main/building/tracks/concept-exercises.md)_)
  - [ ] Exercise `introduction.md`
    - [ ]  Do all code examples compile, run, and return the shown output?
    - [ ]  Are all the code examples formatted per the Python docs?
  - [ ]  Exercise `instructions.md`
  - [ ]  Exercise  `hints.md`
  - [ ]   Check that exercise `design.md` was fulfilled or edited appropriately
  - [ ]   Exercise `exemplar.py`
    - [ ]  Only uses syntax previously introduced or explained.
    - [ ]  Is correct and appropriate for the exercise and story.
  - [ ]   Exercise `<exercise_name>.py` (stub)
    - [ ]  Includes appropriate docstrings and function names.
    - [ ]  Includes `pass` for each function
    - [ ]  Includes an EOL at the end
  - [ ]   Exercise  `<exercise_name>_test.py`
    - [ ]   Tests cover all (reasonable) inputs and scenarios
    - [ ]   At least one test for each task in the exercise
    - [ ]   If using subtests or fixtures they're [formatted correctly](https://github.com/exercism/python/blob/main/CONTRIBUTING.md#Test-File_style) for the runner
    - [ ]   Classnames are `<ExerciseName>Test`
    - [ ]   Test functions are `test_<test_name>`
  - [ ]   Exercise `config.json` --> **valid UUID4**
  - [ ]   Corresponding concept `introduction.md`
  - [ ]   Corresponding concept `about.md`
  - [ ]   Concept  `config.json`
  - [ ]   All Markdown Files :  Prettier linting (_for all markdown docs_)
  - [ ]   All Code files: PyLint linting (**_except for test files_**)
  - [ ]   All files with text:  Spell check & grammar review.

</td>
</table>
</ul>
</details>
</ul>
</details>

<details>
<summary>🚀&nbsp;  Changes to Practice Exercises</summary>
<br>
<ul>
<table>
<td>

_Is the exercise is in line with [Practice Exercise Anatomy](https://github.com/exercism/docs/blob/main/building/tracks/practice-exercises.md)?_
- [ ] `.docs/instructions.md`(**required**)
   - Was this file updated and regenerated properly?
- [ ] `.docs/introduction.md`(_optional_)
- [ ] `.docs/introduction.append.md`(_optional_)
- [ ] `.docs/instructions.append.md` (_optional_)
   - Are any additional instructions needed/provided?
   &nbsp;(_e.g. error handling or information on classes_)
- [ ] `.docs/hints.md`(_optional_)
   - Was this file regenerated properly?
- [ ] `.meta/config.json` (**required**)
- [ ] `.meta/example.py` (**required**)
   - Does this pass all the current tests as written/generated?
- [ ] `.meta/design.md` (_optional_)
- [ ] `.meta/template.j2` (_template for generating tests from canonical data_)
  - Was a test file properly regenerated from this template?
- [ ] `.meta/tests.toml`
   -  Are there additional test cases to include or exclude?
   -  Are there any Python-specific test cases needed for this exercise?
- [ ] `<exercise-slug>_test.py`
   - Does this file need to be regenerated?
   - Does this file correctly test the `example.py` file?
   - Does this file correctly report test failures and messages?
- [ ] `<exercise-slug>.py` (**required**)
   - Does this stub have enough information to get
   &nbsp;the student started coding a valid solution?

</td>
</table>
</ul>
</details>
  <details>
    <summary>🐣 &nbsp;Brand-New Concept Exercises</summary>
<br>
<ul>
<table>
<td>

_Is the exercise is in line with [Concept Exercise Anatomy](https://github.com/exercism/docs/blob/main/building/tracks/concept-exercises.md)?_
  - [ ] Exercise `introduction.md`
    - [ ]  Do all code examples compile, run, and return the shown output?
    - [ ]  Are all the code examples formatted per the Python docs?
  - [ ]  Exercise `instructions.md`
  - [ ]  Exercise  `hints.md`
  - [ ]   Check that exercise `design.md` was fulfilled or edited appropriately
  - [ ]   Exercise `exemplar.py`
    - [ ]  Only uses syntax previously introduced or explained.
    - [ ]  Is correct and appropriate for the exercise and story.
  - [ ]   Exercise `<exercise_name>.py` (stub)
    - [ ]  Includes appropriate docstrings and function names.
    - [ ]  Includes `pass` for each function
    - [ ]  Includes an EOL at the end
  - [ ]   Exercise  `<exercise_name>_test.py`
    - [ ]   Tests cover all (reasonable) inputs and scenarios
    - [ ]   At least one test for each task in the exercise
    - [ ]   If using subtests or fixtures they're formatted correctly for the runner
    - [ ]   Classnames are `<ExerciseName>Test`
    - [ ]   Test functions are `test_<test_name>`
  - [ ]   Exercise `config.json` --> valid UUID4
  - [ ]   Corresponding concept `introduction.md`
  - [ ]   Corresponding concept `about.md`
  - [ ]   Concept  `config.json`
  - [ ]   All Markdown Files :  Prettier linting (_for all markdown docs_)
  - [ ]   All Code files: Flake8 & PyLint linting
  - [ ]   All Code Examples:  proper formatting and fencing.  Verify they run in the REPL
  - [ ]   All files with text:  Spell check & grammar review.

<br>
</td>
</table>
</ul>
</details>
<br>
<h3>Our &nbsp; 💖  &nbsp; for all your review efforts! 🌟 🦄</h3>


<!-- pr-commenter-metadata: any-file-changed,change-practice-exercise -->

Patch:
diff --git a/exercises/concept/currency-exchange/.docs/hints.md b/exercises/concept/currency-exchange/.docs/hints.md
index 4e68c213..cdffbb0a 100644
--- a/exercises/concept/currency-exchange/.docs/hints.md
+++ b/exercises/concept/currency-exchange/.docs/hints.md
@@ -2,19 +2,19 @@
 
 ## General
 
-- [The Python Numbers Tutorial](https://docs.python.org/3/tutorial/introduction.html#numbers) and [Python numeric types](https://docs.python.org/3.9/library/stdtypes.html#numeric-types-int-float-complex) can be a great introduction.
+- [The Python Numbers Tutorial][python-numbers-tutorial] and [Python numeric types][python-numeric-types] can be a great introduction.
 
 ## 1. Estimating exchangeable value
 
-- You can use the [division operator](https://docs.python.org/3/tutorial/introduction.html#numbers) to get the value of exchanged currency.
+- You can use the [division operator][division-operator] to get the value of exchanged currency.
 
 ## 2. Changes after exchanging
 
-- You can use the [subtraction operator](https://docs.python.org/3/tutorial/introduction.html#numbers) to get the amount of changes.
+- You can use the [subtraction operator][subtraction-operator] to get the amount of changes.
 
 ## 3. Calculate value of bills
 
-- You can use the [multiplication operator](https://docs.python.org/3/tutorial/introduction.html#numbers) to get the value of bills.
+- You can use the [multiplication operator][multiplication-operator] to get the value of bills.
 
 ## 4. Calculate number of bills
 
@@ -29,3 +29,10 @@
 - You need to calculate `spread` percent of `exchange_rate` using multiplication operator and add it to `exchange_rate` to get the exchanged currency.
 - The actual rate needs to be computed. Remember to add exchange rate and exchange fee.
 - You can get exchanged money affected by commission by using divide operation and type casting _int_.
+
+
+[python-numbers-tutorial]: https://docs.python.org/3/tutorial/introduction.html#numbers
+[python-numeric-types]: https://docs.python.org/3.9/library/stdtypes.html#numeric-types-int-float-complex
+[division-operator]: https://docs.python.org/3/tutorial/introduction.html#numbers
+[subtraction-operator]: https://docs.python.org/3/tutorial/introduction.html#numbers
+[multiplication-operator]: https://docs.python.org/3/tutorial/introduction.html#numbers
diff --git a/exercises/practice/grade-school/.docs/instructions.md b/exercises/practice/grade-school/.docs/instructions.md
index 8bbbf644..0c716a6e 100644
--- a/exercises/practice/grade-school/.docs/instructions.md
+++ b/exercises/practice/grade-school/.docs/instructions.md
@@ -21,8 +21,8 @@ In the end, you should be able to:
   and Jim in grade 5.
   So the answer is: Anna, Barb, Charlie, Alex, Peter, Zoe and Jim"
 
-Note that all our students only have one name.  (It's a small town, what
-do you want?)
+Note that all our students only have one name (It's a small town, what do you want?) and each student cannot be added more than once to a grade or the roster.
+In fact, when a test attempts to add the same student more than once, your implementation should indicate that this is incorrect.
 
 ## For bonus points
 
diff --git a/exercises/practice/grade-school/.meta/example.py b/exercises/practice/grade-school/.meta/example.py
index a65ca891..83ff1bcb 100644
--- a/exercises/practice/grade-school/.meta/example.py
+++ b/exercises/practice/grade-school/.meta/example.py
@@ -3,17 +3,38 @@ from collections import defaultdict
 
 class School:
     def __init__(self,):
-        self.db = defaultdict(set)
+        self.db = {}
+        self.add = []
+
+    def added(self):
+        result = self.add[:]
+        self.add = []
+        return result
 
     def add_student(self, name, grade):
-        self.db[grade].add(name)
 
-    def roster(self):
-        return [
-            name
-            for grade, names in sorted(self.db.items())
-            for name in sorted(names)
-        ]
+        if not self.db.get(name, 0):
+            self.db[name] = grade
+            self.add.append(True)
+
+        elif self.db[name] > grade:
+            self.db[name] = grade
+
+        else:
+            self.add.append(False)
+
+
+    def roster(self, grade=0):
+        grades_roster = defaultdict(list)
+
+        for key, value in self.db.items():
+            grades_roster[value].append(key)
+
+        if grade:
+            return sorted(grades_roster[grade])
+        else:
+            working_list = (sorted(grades_roster[key]) for key in sorted(grades_roster.keys()))
+            return [element for item in working_list for element in item]
 
     def grade(self, grade_number):
-        return sorted(self.db[grade_number])
+        return sorted(self.roster(grade_number))
diff --git a/exercises/practice/grade-school/.meta/template.j2 b/exercises/practice/grade-school/.meta/template.j2
index bea672a0..8a86c1a8 100644
--- a/exercises/practice/grade-school/.meta/template.j2
+++ b/exercises/practice/grade-school/.meta/template.j2
@@ -1,6 +1,7 @@
 {%- import "generator_macros.j2" as macros with context -%}
 {%- macro test_case( case) -%}
     {%- set input = case["input"] -%}
+    {%- set property = case["property"] -%}
     def test_{{ case["description"] | to_snake }}(self):
         school = School()
         {% for student, grade in input["students"] -%}
@@ -9,7 +10,9 @@
         expected = {{ case["expected"] }}
         {% if "desiredGrade" in input -%}
         self.assertEqual(school.{{ case["property"] | to_snake }}({{ input["desiredGrade"] }}), expected)
-        {%- else -%}
+        {%- elif "add" in property -%}
+        self.assertEqual(school.added(), expected)
+        {% else %}
         self.assertEqual(school.{{ case["property"] | to_snake }}(), expected)
         {%- endif %}
 {% endmacro -%}
@@ -19,5 +22,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
     {% for case in cases %}
     {{ test_case(case) }}
     {% endfor %}
-
-{{ macros.footer() }}
diff --git a/exercises/practice/hamming/.docs/instructions.md b/exercises/practice/hamming/.docs/instructions.md
index 56c5696d..12381074 100644
--- a/exercises/practice/hamming/.docs/instructions.md
+++ b/exercises/practice/hamming/.docs/instructions.md
@@ -16,9 +16,8 @@ They have 7 differences, and therefore the Hamming Distance is 7.
 
 The Hamming Distance is useful for lots of things in science, not just biology, so it's a nice phrase to be familiar with :)
 
-# Implementation notes
+## Implementation notes
 
 The Hamming distance is only defined for sequences of equal length, so
 an attempt to calculate it between sequences of different lengths should
-not work. The general handling of this situation (e.g., raising an
-exception vs returning a special value) may differ between languages.
+not work.
diff --git a/exercises/practice/high-scores/.meta/example.py b/exercises/practice/high-scores/.meta/example.py
index fd168ffc..48bfd03b 100644
--- a/exercises/practice/high-scores/.meta/example.py
+++ b/exercises/practice/high-scores/.meta/example.py
@@ -8,3 +8,11 @@ def personal_best(scores):
 
 def personal_top_three(scores):
     return sorted(scores, reverse=True)[:3]
+
+
+def latest_after_top_three(scores):
+    return latest(scores)
+
+
+def scores_after_top_three(scores):
+    return scores
diff --git a/exercises/practice/high-scores/.meta/template.j2 b/exercises/practice/high-scores/.meta/template.j2
index 4e9ce2eb..596cd34f 100644
--- a/exercises/practice/high-scores/.meta/template.j2
+++ b/exercises/practice/high-scores/.meta/template.j2
@@ -19,7 +19,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
     {% endif -%}
     {% endif -%}
     {% endfor %}
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/exercises/practice/high-scores/high_scores.py b/exercises/practice/high-scores/high_scores.py
index 0d24cdb1..327f81cf 100644
--- a/exercises/practice/high-scores/high_scores.py
+++ b/exercises/practice/high-scores/high_scores.py
@@ -8,3 +8,11 @@ def personal_best(scores):
 
 def personal_top_three(scores):
     pass
+
+
+def latest_after_top_three(scores):
+    pass
+
+
+def scores_after_top_three(scores):
+    pass
diff --git a/exercises/practice/isogram/.docs/instructions.md b/exercises/practice/isogram/.docs/instructions.md
index 9cc5350b..5e488447 100644
--- a/exercises/practice/isogram/.docs/instructions.md
+++ b/exercises/practice/isogram/.docs/instructions.md
@@ -2,7 +2,7 @@
 
 Determine if a word or phrase is an isogram.
 
-An isogram (also known as a "nonpattern word") is a word or phrase without a repeating letter, however spaces and hyphens are allowed to appear multiple times.
+An isogram (also known as a "non-pattern word") is a word or phrase without a repeating letter, however spaces and hyphens are allowed to appear multiple times.
 
 Examples of isograms:
 
diff --git a/exercises/practice/isogram/.meta/template.j2 b/exercises/practice/isogram/.meta/template.j2
index 1b8eb014..fbec4a93 100644
--- a/exercises/practice/isogram/.meta/template.j2
+++ b/exercises/practice/isogram/.meta/template.j2
@@ -17,5 +17,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
             {{ case["expected"] }}
         )
     {% endfor %}
-
-{{ macros.footer() }}
\ No newline at end of file
diff --git a/exercises/practice/kindergarten-garden/.meta/template.j2 b/exercises/practice/kindergarten-garden/.meta/template.j2
index 9b7afbbe..f1dc5425 100644
--- a/exercises/practice/kindergarten-garden/.meta/template.j2
+++ b/exercises/practice/kindergarten-garden/.meta/template.j2
@@ -42,7 +42,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
     {% endfor %}
     {%- endfor %}
     {%- endif %}
-
-
-
-{{ macros.footer() }}
diff --git a/exercises/practice/luhn/.docs/instructions.md b/exercises/practice/luhn/.docs/instructions.md
index c7c7d3e0..f1215dd3 100644
--- a/exercises/practice/luhn/.docs/instructions.md
+++ b/exercises/practice/luhn/.docs/instructions.md
@@ -9,14 +9,13 @@ Numbers.
 
 The task is to check if a given string is valid.
 
-Validating a Number
-------
+## Validating a Number
 
 Strings of length 1 or less are not valid. Spaces are allowed in the input,
 but they should be stripped before checking. All other non-digit characters
 are disallowed.
 
-## Example 1: valid credit card number
+### Example 1: valid credit card number
 
 ```text
 4539 3195 0343 6467
@@ -44,7 +43,7 @@ Then sum all of the digits:
 
 If the sum is evenly divisible by 10, then the number is valid. This number is valid!
 
-## Example 2: invalid credit card number
+### Example 2: invalid credit card number
 
 ```text
 8273 1232 7352 0569
diff --git a/exercises/practice/luhn/.meta/template.j2 b/exercises/practice/luhn/.meta/template.j2
index 7e6c05fe..af8489b3 100644
--- a/exercises/practice/luhn/.meta/template.j2
+++ b/exercises/practice/luhn/.meta/template.j2
@@ -41,5 +41,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
         number = Luhn("055 444 285")
         self.assertIs(number.valid(), True)
         self.assertIs(number.valid(), True)
-
-{{ macros.footer() }}
diff --git a/exercises/practice/markdown/.meta/template.j2 b/exercises/practice/markdown/.meta/template.j2
index da108136..a88bf822 100644
--- a/exercises/practice/markdown/.meta/template.j2
+++ b/exercises/practice/markdown/.meta/template.j2
@@ -3,6 +3,8 @@
 
 class {{ exercise | camel_case }}Test(unittest.TestCase):
     {% for case in cases -%}
+    {% if case["description"] == "with h7 header level" %}
+    {% else %}
     def test_{{ case["description"] | to_snake }}(self):
         self.assertEqual(
             {{ case["property"] | to_snake }}(
@@ -10,6 +12,5 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
             ),
             "{{ case["expected"] }}"
         )
+    {% endif %}
     {% endfor %}
-
-{{ macros.footer() }}
diff --git a/exercises/practice/prime-factors/.docs/instructions.md b/exercises/practice/prime-factors/.docs/instructions.md
index b5cb1657..4f33a1f2 100644
--- a/exercises/practice/prime-factors/.docs/instructions.md
+++ b/exercises/practice/prime-factors/.docs/instructions.md
@@ -6,25 +6,22 @@ A prime number is only evenly divisible by itself and 1.
 
 Note that 1 is not a prime number.
 
-## Example
-
+Example
 What are the prime factors of 60?
 
-- Our first divisor is 2. 2 goes into 60, leaving 30.
-- 2 goes into 30, leaving 15.
-  - 2 doesn't go cleanly into 15. So let's move on to our next divisor, 3.
-- 3 goes cleanly into 15, leaving 5.
-  - 3 does not go cleanly into 5. The next possible factor is 4.
-  - 4 does not go cleanly into 5. The next possible factor is 5.
-- 5 does go cleanly into 5.
-- We're left only with 1, so now, we're done.
-
-Our successful divisors in that computation represent the list of prime
-factors of 60: 2, 2, 3, and 5.
+Our first divisor is 2. 2 goes into 60, leaving 30.
+2 goes into 30, leaving 15.
+2 doesn't go cleanly into 15. So let's move on to our next divisor, 3.
+3 goes cleanly into 15, leaving 5.
+3 does not go cleanly into 5. The next possible factor is 4.
+4 does not go cleanly into 5. The next possible factor is 5.
+5 does go cleanly into 5.
+We're left only with 1, so now, we're done.
+Our successful divisors in that computation represent the list of prime factors of 60: 2, 2, 3, and 5.
 
 You can check this yourself:
 
-- 2 * 2 * 3 * 5
-- = 4 * 15
-- = 60
-- Success!
+2 * 2 * 3 * 5
+= 4 * 15
+= 60
+Success!
diff --git a/exercises/practice/prime-factors/.meta/template.j2 b/exercises/practice/prime-factors/.meta/template.j2
index dd9ad27d..4178af79 100644
--- a/exercises/practice/prime-factors/.meta/template.j2
+++ b/exercises/practice/prime-factors/.meta/template.j2
@@ -6,5 +6,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
         def test_{{ case["description"] | to_snake }}(self):
             self.assertEqual({{ case["property"] }}({{ case["input"]["value"] }}), {{ case["expected"] }})
         {% endfor %}
-
-{{ macros.footer() }}
diff --git a/exercises/practice/prime-factors/prime_factors.py b/exercises/practice/prime-factors/prime_factors.py
index c3b672cf..c6331342 100644
--- a/exercises/practice/prime-factors/prime_factors.py
+++ b/exercises/practice/prime-factors/prime_factors.py
@@ -1,2 +1,13 @@
+# def factors(value):
+#     pass
 def factors(value):
-    pass
+    factors = []
+    divisor = 2
+    while value > 1:
+        while value % divisor == 0:
+            factors.append(divisor)
+            value /= divisor
+
+        divisor += 1
+
+    return factors
diff --git a/exercises/practice/rational-numbers/.docs/instructions.md b/exercises/practice/rational-numbers/.docs/instructions.md
index 1a7d3f3b..d0c2e3ec 100644
--- a/exercises/practice/rational-numbers/.docs/instructions.md
+++ b/exercises/practice/rational-numbers/.docs/instructions.md
@@ -2,6 +2,12 @@
 
 A rational number is defined as the quotient of two integers `a` and `b`, called the numerator and denominator, respectively, where `b != 0`.
 
+~~~~exercism/note
+Note that mathematically, the denominator can't be zero.
+However in many implementations of rational numbers, you will find that the denominator is allowed to be zero with behaviour similar to positive or negative infinity in floating point numbers.
+In those cases, the denominator and numerator generally still can't both be zero at once.
+~~~~
+
 The absolute value `|r|` of the rational number `r = a/b` is equal to `|a|/|b|`.
 
 The sum of two rational numbers `r₁ = a₁/b₁` and `r₂ = a₂/b₂` is `r₁ + r₂ = a₁/b₁ + a₂/b₂ = (a₁ * b₂ + a₂ * b₁) / (b₁ * b₂)`.
@@ -21,8 +27,9 @@ Exponentiation of a rational number `r = a/b` to a real (floating-point) number
 Exponentiation of a real number `x` to a rational number `r = a/b` is `x^(a/b) = root(x^a, b)`, where `root(p, q)` is the `q`th root of `p`.
 
 Implement the following operations:
- - addition, subtraction, multiplication and division of two rational numbers,
- - absolute value, exponentiation of a given rational number to an integer power, exponentiation of a given rational number to a real (floating-point) power, exponentiation of a real number to a rational number.
+
+- addition, subtraction, multiplication and division of two rational numbers,
+- absolute value, exponentiation of a given rational number to an integer power, exponentiation of a given rational number to a real (floating-point) power, exponentiation of a real number to a rational number.
 
 Your implementation of rational numbers should always be reduced to lowest terms. For example, `4/4` should reduce to `1/1`, `30/60` should reduce to `1/2`, `12/8` should reduce to `3/2`, etc. To reduce a rational number `r = a/b`, divide `a` and `b` by the greatest common divisor (gcd) of `a` and `b`. So, for example, `gcd(12, 8) = 4`, so `r = 12/8` can be reduced to `(12/4)/(8/4) = 3/2`.
 
diff --git a/exercises/practice/resistor-color-duo/.docs/instructions.md b/exercises/practice/resistor-color-duo/.docs/instructions.md
index 85cf1130..68550f78 100644
--- a/exercises/practice/resistor-color-duo/.docs/instructions.md
+++ b/exercises/practice/resistor-color-duo/.docs/instructions.md
@@ -17,18 +17,17 @@ The program will take color names as input and output a two digit number, even i
 
 The band colors are encoded as follows:
 
-- Black: 0
-- Brown: 1
-- Red: 2
-- Orange: 3
-- Yellow: 4
-- Green: 5
-- Blue: 6
-- Violet: 7
-- Grey: 8
-- White: 9
+* Black: 0
+* Brown: 1
+* Red: 2
+* Orange: 3
+* Yellow: 4
+* Green: 5
+* Blue: 6
+* Violet: 7
+* Grey: 8
+* White: 9
 
 From the example above:
 brown-green should return 15
 brown-green-violet should return 15 too, ignoring the third color.
-
diff --git a/exercises/practice/resistor-color-duo/.meta/template.j2 b/exercises/practice/resistor-color-duo/.meta/template.j2
index 180a51d7..cabf271c 100644
--- a/exercises/practice/resistor-color-duo/.meta/template.j2
+++ b/exercises/practice/resistor-color-duo/.meta/template.j2
@@ -13,6 +13,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
     {% for case in cases -%}
         {{ test_case(case) }}
     {% endfor %}
-
-
-{{ macros.footer() }}
diff --git a/exercises/practice/sublist/.docs/instructions.md b/exercises/practice/sublist/.docs/instructions.md
index 45c3b964..7535931a 100644
--- a/exercises/practice/sublist/.docs/instructions.md
+++ b/exercises/practice/sublist/.docs/instructions.md
@@ -1,18 +1,25 @@
 # Instructions
 
-Given two lists determine if the first list is contained within the second
-list, if the second list is contained within the first list, if both lists are
-contained within each other or if none of these are true.
+Given any two lists `A` and `B`, determine if:
 
-Specifically, a list A is a sublist of list B if by dropping 0 or more elements
-from the front of B and 0 or more elements from the back of B you get a list
-that's completely equal to A.
+- List `A` is equal to list `B`; or
+- List `A` contains list `B` (`A` is a superlist of `B`); or
+- List `A` is contained by list `B` (`A` is a sublist of `B`); or
+- None of the above is true, thus lists `A` and `B` are unequal
+
+Specifically, list `A` is equal to list `B` if both lists have the same values in the same order.
+List `A` is a superlist of `B` if `A` contains a sub-sequence of values equal to `B`.
+List `A` is a sublist of `B` if `B` contains a sub-sequence of values equal to `A`.
 
 Examples:
 
- * A = [1, 2, 3], B = [1, 2, 3, 4, 5], A is a sublist of B
- * A = [3, 4, 5], B = [1, 2, 3, 4, 5], A is a sublist of B
- * A = [3, 4], B = [1, 2, 3, 4, 5], A is a sublist of B
- * A = [1, 2, 3], B = [1, 2, 3], A is equal to B
- * A = [1, 2, 3, 4, 5], B = [2, 3, 4], A is a superlist of B
- * A = [1, 2, 4], B = [1, 2, 3, 4, 5], A is not a superlist of, sublist of or equal to B
+- If `A = []` and `B = []` (both lists are empty), then `A` and `B` are equal
+- If `A = [1, 2, 3]` and `B = []`, then `A` is a superlist of `B`
+- If `A = []` and `B = [1, 2, 3]`, then `A` is a sublist of `B`
+- If `A = [1, 2, 3]` and `B = [1, 2, 3, 4, 5]`, then `A` is a sublist of `B`
+- If `A = [3, 4, 5]` and `B = [1, 2, 3, 4, 5]`, then `A` is a sublist of `B`
+- If `A = [3, 4]` and `B = [1, 2, 3, 4, 5]`, then `A` is a sublist of `B`
+- If `A = [1, 2, 3]` and `B = [1, 2, 3]`, then `A` and `B` are equal
+- If `A = [1, 2, 3, 4, 5]` and `B = [2, 3, 4]`, then `A` is a superlist of `B`
+- If `A = [1, 2, 4]` and `B = [1, 2, 3, 4, 5]`, then `A` and `B` are unequal
+- If `A = [1, 2, 3]` and `B = [1, 3, 2]`, then `A` and `B` are unequal
diff --git a/exercises/practice/sublist/.meta/template.j2 b/exercises/practice/sublist/.meta/template.j2
index 051b16ec..00c111f3 100644
--- a/exercises/practice/sublist/.meta/template.j2
+++ b/exercises/practice/sublist/.meta/template.j2
@@ -20,4 +20,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
     {{ test_case(case) }}
     {% endfor %}
     {%- endif %}
-{{ macros.footer() }}
\ No newline at end of file
diff --git a/exercises/practice/tournament/.meta/template.j2 b/exercises/practice/tournament/.meta/template.j2
index 9271b9e7..30ca2e94 100644
--- a/exercises/practice/tournament/.meta/template.j2
+++ b/exercises/practice/tournament/.meta/template.j2
@@ -10,5 +10,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
         self.assertEqual({{ case["property"] }}(results), table)
 
     {% endfor %}
-
-{{ macros.footer() }}
diff --git a/exercises/practice/transpose/.meta/template.j2 b/exercises/practice/transpose/.meta/template.j2
index 0b6f54f7..ef724132 100644
--- a/exercises/practice/transpose/.meta/template.j2
+++ b/exercises/practice/transpose/.meta/template.j2
@@ -9,5 +9,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
         self.assertEqual({{ case["property"] }}("\n".join(lines)), "\n".join(expected))
 
     {% endfor %}
-
-{{ macros.footer() }}
\ No newline at end of file


Test patch:
diff --git a/exercises/practice/grade-school/.meta/tests.toml b/exercises/practice/grade-school/.meta/tests.toml
index eada129d..d774e9e3 100644
--- a/exercises/practice/grade-school/.meta/tests.toml
+++ b/exercises/practice/grade-school/.meta/tests.toml
@@ -1,24 +1,83 @@
-# This is an auto-generated file. Regular comments will be removed when this
-# file is regenerated. Regenerating will not touch any manually added keys,
-# so comments can be added in a "comment" key.
+# 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.
+
+[a3f0fb58-f240-4723-8ddc-e644666b85cc]
+description = "Roster is empty when no student is added"
+
+[9337267f-7793-4b90-9b4a-8e3978408824]
+description = "Add a student"
 
 [6d0a30e4-1b4e-472e-8e20-c41702125667]
-description = "Adding a student adds them to the sorted roster"
+description = "Student is added to the roster"
+
+[73c3ca75-0c16-40d7-82f5-ed8fe17a8e4a]
+description = "Adding multiple students in the same grade in the roster"
 
 [233be705-dd58-4968-889d-fb3c7954c9cc]
-description = "Adding more student adds them to the sorted roster"
+description = "Multiple students in the same grade are added to the roster"
+
+[87c871c1-6bde-4413-9c44-73d59a259d83]
+description = "Cannot add student to same grade in the roster more than once"
+
+[c125dab7-2a53-492f-a99a-56ad511940d8]
+description = "A student can't be in two different grades"
+
+[a0c7b9b8-0e89-47f8-8b4a-c50f885e79d1]
+description = "A student can only be added to the same grade in the roster once"
+reimplements = "c125dab7-2a53-492f-a99a-56ad511940d8"
+
+[d7982c4f-1602-49f6-a651-620f2614243a]
+description = "Student not added to same grade in the roster more than once"
+reimplements = "a0c7b9b8-0e89-47f8-8b4a-c50f885e79d1"
+
+[e70d5d8f-43a9-41fd-94a4-1ea0fa338056]
+description = "Adding students in multiple grades"
 
 [75a51579-d1d7-407c-a2f8-2166e984e8ab]
-description = "Adding students to different grades adds them to the same sorted roster"
+description = "Students in multiple grades are added to the roster"
 
-[a3f0fb58-f240-4723-8ddc-e644666b85cc]
-description = "Roster returns an empty list if there are no students enrolled"
+[7df542f1-57ce-433c-b249-ff77028ec479]
+description = "Cannot add same student to multiple grades in the roster"
 
-[180a8ff9-5b94-43fc-9db1-d46b4a8c93b6]
-description = "Student names with grades are displayed in the same sorted roster"
+[6a03b61e-1211-4783-a3cc-fc7f773fba3f]
+description = "A student cannot be added to more than one grade in the sorted roster"
+reimplements = "c125dab7-2a53-492f-a99a-56ad511940d8"
 
-[1bfbcef1-e4a3-49e8-8d22-f6f9f386187e]
-description = "Grade returns the students in that grade in alphabetical order"
+[c7ec1c5e-9ab7-4d3b-be5c-29f2f7a237c5]
+description = "Student not added to multiple grades in the roster"
+reimplements = "6a03b61e-1211-4783-a3cc-fc7f773fba3f"
+
+[d9af4f19-1ba1-48e7-94d0-dabda4e5aba6]
+description = "Students are sorted by grades in the roster"
+
+[d9fb5bea-f5aa-4524-9d61-c158d8906807]
+description = "Students are sorted by name in the roster"
+
+[180a8ff9-5b94-43fc-9db1-d46b4a8c93b6]
+description = "Students are sorted by grades and then by name in the roster"
 
 [5e67aa3c-a3c6-4407-a183-d8fe59cd1630]
-description = "Grade returns an empty list if there are no students in that grade"
+description = "Grade is empty if no students in the roster"
+
+[1e0cf06b-26e0-4526-af2d-a2e2df6a51d6]
+description = "Grade is empty if no students in that grade"
+
+[2bfc697c-adf2-4b65-8d0f-c46e085f796e]
+description = "Student not added to same grade more than once"
+
+[66c8e141-68ab-4a04-a15a-c28bc07fe6b9]
+description = "Student not added to multiple grades"
+
+[c9c1fc2f-42e0-4d2c-b361-99271f03eda7]
+description = "Student not added to other grade for multiple grades"
+
+[1bfbcef1-e4a3-49e8-8d22-f6f9f386187e]
+description = "Students are sorted by name in a grade"
diff --git a/exercises/practice/grade-school/grade_school_test.py b/exercises/practice/grade-school/grade_school_test.py
index f1643de2..2ef26d2e 100644
--- a/exercises/practice/grade-school/grade_school_test.py
+++ b/exercises/practice/grade-school/grade_school_test.py
@@ -11,27 +11,133 @@ class GradeSchoolTest(unittest.TestCase):
     def test_roster_is_empty_when_no_student_is_added(self):
         school = School()
         expected = []
+
         self.assertEqual(school.roster(), expected)
 
+    def test_add_a_student(self):
+        school = School()
+        school.add_student(name="Aimee", grade=2)
+        expected = [True]
+        self.assertEqual(school.added(), expected)
+
     def test_student_is_added_to_the_roster(self):
         school = School()
         school.add_student(name="Aimee", grade=2)
         expected = ["Aimee"]
+
         self.assertEqual(school.roster(), expected)
 
+    def test_adding_multiple_students_in_the_same_grade_in_the_roster(self):
+        school = School()
+        school.add_student(name="Blair", grade=2)
+        school.add_student(name="James", grade=2)
+        school.add_student(name="Paul", grade=2)
+        expected = [True, True, True]
+        self.assertEqual(school.added(), expected)
+
     def test_multiple_students_in_the_same_grade_are_added_to_the_roster(self):
         school = School()
         school.add_student(name="Blair", grade=2)
         school.add_student(name="James", grade=2)
         school.add_student(name="Paul", grade=2)
         expected = ["Blair", "James", "Paul"]
+
+        self.assertEqual(school.roster(), expected)
+
+    def test_cannot_add_student_to_same_grade_in_the_roster_more_than_once(self):
+        school = School()
+        school.add_student(name="Blair", grade=2)
+        school.add_student(name="James", grade=2)
+        school.add_student(name="James", grade=2)
+        school.add_student(name="Paul", grade=2)
+        expected = [True, True, False, True]
+        self.assertEqual(school.added(), expected)
+
+    def test_a_student_can_t_be_in_two_different_grades(self):
+        school = School()
+        school.add_student(name="Aimee", grade=2)
+        school.add_student(name="Aimee", grade=1)
+        expected = []
+        self.assertEqual(school.roster(2), expected)
+
+    def test_a_student_can_only_be_added_to_the_same_grade_in_the_roster_once(self):
+        school = School()
+        school.add_student(name="Aimee", grade=2)
+        school.add_student(name="Aimee", grade=2)
+        expected = ["Aimee"]
+
+        self.assertEqual(school.roster(), expected)
+
+    def test_student_not_added_to_same_grade_in_the_roster_more_than_once(self):
+        school = School()
+        school.add_student(name="Blair", grade=2)
+        school.add_student(name="James", grade=2)
+        school.add_student(name="James", grade=2)
+        school.add_student(name="Paul", grade=2)
+        expected = ["Blair", "James", "Paul"]
+
         self.assertEqual(school.roster(), expected)
 
+    def test_adding_students_in_multiple_grades(self):
+        school = School()
+        school.add_student(name="Chelsea", grade=3)
+        school.add_student(name="Logan", grade=7)
+        expected = [True, True]
+        self.assertEqual(school.added(), expected)
+
     def test_students_in_multiple_grades_are_added_to_the_roster(self):
         school = School()
         school.add_student(name="Chelsea", grade=3)
         school.add_student(name="Logan", grade=7)
         expected = ["Chelsea", "Logan"]
+
+        self.assertEqual(school.roster(), expected)
+
+    def test_cannot_add_same_student_to_multiple_grades_in_the_roster(self):
+        school = School()
+        school.add_student(name="Blair", grade=2)
+        school.add_student(name="James", grade=2)
+        school.add_student(name="James", grade=3)
+        school.add_student(name="Paul", grade=3)
+        expected = [True, True, False, True]
+        self.assertEqual(school.added(), expected)
+
+    def test_a_student_cannot_be_added_to_more_than_one_grade_in_the_sorted_roster(
+        self,
+    ):
+        school = School()
+        school.add_student(name="Aimee", grade=2)
+        school.add_student(name="Aimee", grade=1)
+        expected = ["Aimee"]
+
+        self.assertEqual(school.roster(), expected)
+
+    def test_student_not_added_to_multiple_grades_in_the_roster(self):
+        school = School()
+        school.add_student(name="Blair", grade=2)
+        school.add_student(name="James", grade=2)
+        school.add_student(name="James", grade=3)
+        school.add_student(name="Paul", grade=3)
+        expected = ["Blair", "James", "Paul"]
+
+        self.assertEqual(school.roster(), expected)
+
+    def test_students_are_sorted_by_grades_in_the_roster(self):
+        school = School()
+        school.add_student(name="Jim", grade=3)
+        school.add_student(name="Peter", grade=2)
+        school.add_student(name="Anna", grade=1)
+        expected = ["Anna", "Peter", "Jim"]
+
+        self.assertEqual(school.roster(), expected)
+
+    def test_students_are_sorted_by_name_in_the_roster(self):
+        school = School()
+        school.add_student(name="Peter", grade=2)
+        school.add_student(name="Zoe", grade=2)
+        school.add_student(name="Alex", grade=2)
+        expected = ["Alex", "Peter", "Zoe"]
+
         self.assertEqual(school.roster(), expected)
 
     def test_students_are_sorted_by_grades_and_then_by_name_in_the_roster(self):
@@ -44,6 +150,7 @@ class GradeSchoolTest(unittest.TestCase):
         school.add_student(name="Jim", grade=3)
         school.add_student(name="Charlie", grade=1)
         expected = ["Anna", "Barb", "Charlie", "Alex", "Peter", "Zoe", "Jim"]
+
         self.assertEqual(school.roster(), expected)
 
     def test_grade_is_empty_if_no_students_in_the_roster(self):
@@ -51,6 +158,42 @@ class GradeSchoolTest(unittest.TestCase):
         expected = []
         self.assertEqual(school.grade(1), expected)
 
+    def test_grade_is_empty_if_no_students_in_that_grade(self):
+        school = School()
+        school.add_student(name="Peter", grade=2)
+        school.add_student(name="Zoe", grade=2)
+        school.add_student(name="Alex", grade=2)
+        school.add_student(name="Jim", grade=3)
+        expected = []
+        self.assertEqual(school.grade(1), expected)
+
+    def test_student_not_added_to_same_grade_more_than_once(self):
+        school = School()
+        school.add_student(name="Blair", grade=2)
+        school.add_student(name="James", grade=2)
+        school.add_student(name="James", grade=2)
+        school.add_student(name="Paul", grade=2)
+        expected = ["Blair", "James", "Paul"]
+        self.assertEqual(school.grade(2), expected)
+
+    def test_student_not_added_to_multiple_grades(self):
+        school = School()
+        school.add_student(name="Blair", grade=2)
+        school.add_student(name="James", grade=2)
+        school.add_student(name="James", grade=3)
+        school.add_student(name="Paul", grade=3)
+        expected = ["Blair", "James"]
+        self.assertEqual(school.grade(2), expected)
+
+    def test_student_not_added_to_other_grade_for_multiple_grades(self):
+        school = School()
+        school.add_student(name="Blair", grade=2)
+        school.add_student(name="James", grade=2)
+        school.add_student(name="James", grade=3)
+        school.add_student(name="Paul", grade=3)
+        expected = ["Paul"]
+        self.assertEqual(school.grade(3), expected)
+
     def test_students_are_sorted_by_name_in_a_grade(self):
         school = School()
         school.add_student(name="Franklin", grade=5)
@@ -58,7 +201,3 @@ class GradeSchoolTest(unittest.TestCase):
         school.add_student(name="Jeff", grade=1)
         expected = ["Bradley", "Franklin"]
         self.assertEqual(school.grade(5), expected)
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/hamming/.meta/tests.toml b/exercises/practice/hamming/.meta/tests.toml
index b2f80f48..0caf456c 100644
--- a/exercises/practice/hamming/.meta/tests.toml
+++ b/exercises/practice/hamming/.meta/tests.toml
@@ -1,6 +1,13 @@
-# This is an auto-generated file. Regular comments will be removed when this
-# file is regenerated. Regenerating will not touch any manually added keys,
-# so comments can be added in a "comment" key.
+# 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.
 
 [f6dcb64f-03b0-4b60-81b1-3c9dbf47e887]
 description = "empty strands"
@@ -20,11 +27,35 @@ description = "long different strands"
 [919f8ef0-b767-4d1b-8516-6379d07fcb28]
 description = "disallow first strand longer"
 
+[b9228bb1-465f-4141-b40f-1f99812de5a8]
+description = "disallow first strand longer"
+reimplements = "919f8ef0-b767-4d1b-8516-6379d07fcb28"
+
 [8a2d4ed0-ead5-4fdd-924d-27c4cf56e60e]
 description = "disallow second strand longer"
 
+[dab38838-26bb-4fff-acbe-3b0a9bfeba2d]
+description = "disallow second strand longer"
+reimplements = "8a2d4ed0-ead5-4fdd-924d-27c4cf56e60e"
+
 [5dce058b-28d4-4ca7-aa64-adfe4e17784c]
 description = "disallow left empty strand"
 
+[db92e77e-7c72-499d-8fe6-9354d2bfd504]
+description = "disallow left empty strand"
+reimplements = "5dce058b-28d4-4ca7-aa64-adfe4e17784c"
+
+[b764d47c-83ff-4de2-ab10-6cfe4b15c0f3]
+description = "disallow empty first strand"
+reimplements = "db92e77e-7c72-499d-8fe6-9354d2bfd504"
+
 [38826d4b-16fb-4639-ac3e-ba027dec8b5f]
 description = "disallow right empty strand"
+
+[920cd6e3-18f4-4143-b6b8-74270bb8f8a3]
+description = "disallow right empty strand"
+reimplements = "38826d4b-16fb-4639-ac3e-ba027dec8b5f"
+
+[9ab9262f-3521-4191-81f5-0ed184a5aa89]
+description = "disallow empty second strand"
+reimplements = "920cd6e3-18f4-4143-b6b8-74270bb8f8a3"
diff --git a/exercises/practice/hamming/hamming_test.py b/exercises/practice/hamming/hamming_test.py
index 1210f0d6..a0fa465b 100644
--- a/exercises/practice/hamming/hamming_test.py
+++ b/exercises/practice/hamming/hamming_test.py
@@ -30,6 +30,20 @@ class HammingTest(unittest.TestCase):
         self.assertEqual(type(err.exception), ValueError)
         self.assertEqual(err.exception.args[0], "Strands must be of equal length.")
 
+    def test_disallow_first_strand_longer(self):
+        with self.assertRaises(ValueError) as err:
+            distance("AATG", "AAA")
+
+        self.assertEqual(type(err.exception), ValueError)
+        self.assertEqual(err.exception.args[0], "Strands must be of equal length.")
+
+    def test_disallow_second_strand_longer(self):
+        with self.assertRaises(ValueError) as err:
+            distance("ATA", "AGTG")
+
+        self.assertEqual(type(err.exception), ValueError)
+        self.assertEqual(err.exception.args[0], "Strands must be of equal length.")
+
     def test_disallow_second_strand_longer(self):
         with self.assertRaises(ValueError) as err:
             distance("ATA", "AGTG")
@@ -44,9 +58,37 @@ class HammingTest(unittest.TestCase):
         self.assertEqual(type(err.exception), ValueError)
         self.assertEqual(err.exception.args[0], "Strands must be of equal length.")
 
+    def test_disallow_left_empty_strand(self):
+        with self.assertRaises(ValueError) as err:
+            distance("", "G")
+
+        self.assertEqual(type(err.exception), ValueError)
+        self.assertEqual(err.exception.args[0], "Strands must be of equal length.")
+
+    def test_disallow_empty_first_strand(self):
+        with self.assertRaises(ValueError) as err:
+            distance("", "G")
+
+        self.assertEqual(type(err.exception), ValueError)
+        self.assertEqual(err.exception.args[0], "Strands must be of equal length.")
+
+    def test_disallow_right_empty_strand(self):
+        with self.assertRaises(ValueError) as err:
+            distance("G", "")
+
+        self.assertEqual(type(err.exception), ValueError)
+        self.assertEqual(err.exception.args[0], "Strands must be of equal length.")
+
     def test_disallow_right_empty_strand(self):
         with self.assertRaises(ValueError) as err:
             distance("G", "")
 
         self.assertEqual(type(err.exception), ValueError)
         self.assertEqual(err.exception.args[0], "Strands must be of equal length.")
+
+    def test_disallow_empty_second_strand(self):
+        with self.assertRaises(ValueError) as err:
+            distance("G", "")
+
+        self.assertEqual(type(err.exception), ValueError)
+        self.assertEqual(err.exception.args[0], "Strands must be of equal length.")
diff --git a/exercises/practice/high-scores/.meta/tests.toml b/exercises/practice/high-scores/.meta/tests.toml
index 4008e012..c81c2288 100644
--- a/exercises/practice/high-scores/.meta/tests.toml
+++ b/exercises/practice/high-scores/.meta/tests.toml
@@ -1,6 +1,13 @@
-# This is an auto-generated file. Regular comments will be removed when this
-# file is regenerated. Regenerating will not touch any manually added keys,
-# so comments can be added in a "comment" key.
+# 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.
 
 [1035eb93-2208-4c22-bab8-fef06769a73c]
 description = "List of scores"
@@ -12,16 +19,22 @@ description = "Latest score"
 description = "Personal best"
 
 [3d996a97-c81c-4642-9afc-80b80dc14015]
-description = "Personal top three from a list of scores"
+description = "Top 3 scores -> Personal top three from a list of scores"
 
 [1084ecb5-3eb4-46fe-a816-e40331a4e83a]
-description = "Personal top highest to lowest"
+description = "Top 3 scores -> Personal top highest to lowest"
 
 [e6465b6b-5a11-4936-bfe3-35241c4f4f16]
-description = "Personal top when there is a tie"
+description = "Top 3 scores -> Personal top when there is a tie"
 
 [f73b02af-c8fd-41c9-91b9-c86eaa86bce2]
-description = "Personal top when there are less than 3"
+description = "Top 3 scores -> Personal top when there are less than 3"
 
 [16608eae-f60f-4a88-800e-aabce5df2865]
-description = "Personal top when there is only one"
+description = "Top 3 scores -> Personal top when there is only one"
+
+[2df075f9-fec9-4756-8f40-98c52a11504f]
+description = "Top 3 scores -> Latest score after personal top scores"
+
+[809c4058-7eb1-4206-b01e-79238b9b71bc]
+description = "Top 3 scores -> Scores after personal top scores"
diff --git a/exercises/practice/high-scores/high_scores_test.py b/exercises/practice/high-scores/high_scores_test.py
index 04b788a1..629d1bdb 100644
--- a/exercises/practice/high-scores/high_scores_test.py
+++ b/exercises/practice/high-scores/high_scores_test.py
@@ -2,8 +2,10 @@ import unittest
 
 from high_scores import (
     latest,
+    latest_after_top_three,
     personal_best,
     personal_top_three,
+    scores_after_top_three,
 )
 
 # Tests adapted from `problem-specifications//canonical-data.json`
@@ -45,6 +47,12 @@ class HighScoresTest(unittest.TestCase):
         expected = [40]
         self.assertEqual(personal_top_three(scores), expected)
 
+    def test_latest_score_after_personal_top_scores(self):
+        scores = [70, 50, 20, 30]
+        expected = 30
+        self.assertEqual(latest_after_top_three(scores), expected)
 
-if __name__ == "__main__":
-    unittest.main()
+    def test_scores_after_personal_top_scores(self):
+        scores = [30, 50, 20, 70]
+        expected = [30, 50, 20, 70]
+        self.assertEqual(scores_after_top_three(scores), expected)
diff --git a/exercises/practice/isogram/.meta/tests.toml b/exercises/practice/isogram/.meta/tests.toml
index 7187c340..ba04c664 100644
--- a/exercises/practice/isogram/.meta/tests.toml
+++ b/exercises/practice/isogram/.meta/tests.toml
@@ -1,6 +1,13 @@
-# This is an auto-generated file. Regular comments will be removed when this
-# file is regenerated. Regenerating will not touch any manually added keys,
-# so comments can be added in a "comment" key.
+# 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.
 
 [a0e97d2d-669e-47c7-8134-518a1e2c4555]
 description = "empty string"
@@ -40,3 +47,6 @@ description = "duplicated character in the middle"
 
 [310ac53d-8932-47bc-bbb4-b2b94f25a83e]
 description = "same first and last characters"
+
+[0d0b8644-0a1e-4a31-a432-2b3ee270d847]
+description = "word with duplicated character and with two hyphens"
diff --git a/exercises/practice/isogram/isogram_test.py b/exercises/practice/isogram/isogram_test.py
index 11aca026..8d00d2b5 100644
--- a/exercises/practice/isogram/isogram_test.py
+++ b/exercises/practice/isogram/isogram_test.py
@@ -47,6 +47,5 @@ class IsogramTest(unittest.TestCase):
     def test_same_first_and_last_characters(self):
         self.assertIs(is_isogram("angola"), False)
 
-
-if __name__ == "__main__":
-    unittest.main()
+    def test_word_with_duplicated_character_and_with_two_hyphens(self):
+        self.assertIs(is_isogram("up-to-date"), False)
diff --git a/exercises/practice/kindergarten-garden/.meta/tests.toml b/exercises/practice/kindergarten-garden/.meta/tests.toml
index 1778a615..0cdd9ad6 100644
--- a/exercises/practice/kindergarten-garden/.meta/tests.toml
+++ b/exercises/practice/kindergarten-garden/.meta/tests.toml
@@ -1,30 +1,61 @@
-# This is an auto-generated file. Regular comments will be removed when this
-# file is regenerated. Regenerating will not touch any manually added keys,
-# so comments can be added in a "comment" key.
+# 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.
 
 [1fc316ed-17ab-4fba-88ef-3ae78296b692]
-description = "garden with single student"
+description = "partial garden -> garden with single student"
 
 [acd19dc1-2200-4317-bc2a-08f021276b40]
-description = "different garden with single student"
+description = "partial garden -> different garden with single student"
 
 [c376fcc8-349c-446c-94b0-903947315757]
-description = "garden with two students"
+description = "partial garden -> garden with two students"
 
 [2d620f45-9617-4924-9d27-751c80d17db9]
-description = "second student's garden"
+description = "partial garden -> multiple students for the same garden with three students -> second student's garden"
 
 [57712331-4896-4364-89f8-576421d69c44]
-description = "third student's garden"
+description = "partial garden -> multiple students for the same garden with three students -> third student's garden"
 
 [149b4290-58e1-40f2-8ae4-8b87c46e765b]
-description = "first student's garden"
+description = "full garden -> for Alice, first student's garden"
 
 [ba25dbbc-10bd-4a37-b18e-f89ecd098a5e]
-description = "second student's garden"
+description = "full garden -> for Bob, second student's garden"
+
+[566b621b-f18e-4c5f-873e-be30544b838c]
+description = "full garden -> for Charlie"
+
+[3ad3df57-dd98-46fc-9269-1877abf612aa]
+description = "full garden -> for David"
+
+[0f0a55d1-9710-46ed-a0eb-399ba8c72db2]
+description = "full garden -> for Eve"
+
+[a7e80c90-b140-4ea1-aee3-f4625365c9a4]
+description = "full garden -> for Fred"
+
+[9d94b273-2933-471b-86e8-dba68694c615]
+description = "full garden -> for Ginny"
+
+[f55bc6c2-ade8-4844-87c4-87196f1b7258]
+description = "full garden -> for Harriet"
+
+[759070a3-1bb1-4dd4-be2c-7cce1d7679ae]
+description = "full garden -> for Ileana"
+
+[78578123-2755-4d4a-9c7d-e985b8dda1c6]
+description = "full garden -> for Joseph"
 
 [6bb66df7-f433-41ab-aec2-3ead6e99f65b]
-description = "second to last student's garden"
+description = "full garden -> for Kincaid, second to last student's garden"
 
 [d7edec11-6488-418a-94e6-ed509e0fa7eb]
-description = "last student's garden"
+description = "full garden -> for Larry, last student's garden"
diff --git a/exercises/practice/kindergarten-garden/kindergarten_garden_test.py b/exercises/practice/kindergarten-garden/kindergarten_garden_test.py
index 6b4ba358..95095cab 100644
--- a/exercises/practice/kindergarten-garden/kindergarten_garden_test.py
+++ b/exercises/practice/kindergarten-garden/kindergarten_garden_test.py
@@ -44,6 +44,50 @@ class KindergartenGardenTest(unittest.TestCase):
         garden = Garden("VRCGVVRVCGGCCGVRGCVCGCGV\nVRCCCGCRRGVCGCRVVCVGCGCV")
         self.assertEqual(garden.plants("Bob"), ["Clover", "Grass", "Clover", "Clover"])
 
+    def test_full_garden_for_charlie(self):
+        garden = Garden("VRCGVVRVCGGCCGVRGCVCGCGV\nVRCCCGCRRGVCGCRVVCVGCGCV")
+        self.assertEqual(
+            garden.plants("Charlie"), ["Violets", "Violets", "Clover", "Grass"]
+        )
+
+    def test_full_garden_for_david(self):
+        garden = Garden("VRCGVVRVCGGCCGVRGCVCGCGV\nVRCCCGCRRGVCGCRVVCVGCGCV")
+        self.assertEqual(
+            garden.plants("David"), ["Radishes", "Violets", "Clover", "Radishes"]
+        )
+
+    def test_full_garden_for_eve(self):
+        garden = Garden("VRCGVVRVCGGCCGVRGCVCGCGV\nVRCCCGCRRGVCGCRVVCVGCGCV")
+        self.assertEqual(garden.plants("Eve"), ["Clover", "Grass", "Radishes", "Grass"])
+
+    def test_full_garden_for_fred(self):
+        garden = Garden("VRCGVVRVCGGCCGVRGCVCGCGV\nVRCCCGCRRGVCGCRVVCVGCGCV")
+        self.assertEqual(
+            garden.plants("Fred"), ["Grass", "Clover", "Violets", "Clover"]
+        )
+
+    def test_full_garden_for_ginny(self):
+        garden = Garden("VRCGVVRVCGGCCGVRGCVCGCGV\nVRCCCGCRRGVCGCRVVCVGCGCV")
+        self.assertEqual(garden.plants("Ginny"), ["Clover", "Grass", "Grass", "Clover"])
+
+    def test_full_garden_for_harriet(self):
+        garden = Garden("VRCGVVRVCGGCCGVRGCVCGCGV\nVRCCCGCRRGVCGCRVVCVGCGCV")
+        self.assertEqual(
+            garden.plants("Harriet"), ["Violets", "Radishes", "Radishes", "Violets"]
+        )
+
+    def test_full_garden_for_ileana(self):
+        garden = Garden("VRCGVVRVCGGCCGVRGCVCGCGV\nVRCCCGCRRGVCGCRVVCVGCGCV")
+        self.assertEqual(
+            garden.plants("Ileana"), ["Grass", "Clover", "Violets", "Clover"]
+        )
+
+    def test_full_garden_for_joseph(self):
+        garden = Garden("VRCGVVRVCGGCCGVRGCVCGCGV\nVRCCCGCRRGVCGCRVVCVGCGCV")
+        self.assertEqual(
+            garden.plants("Joseph"), ["Violets", "Clover", "Violets", "Grass"]
+        )
+
     def test_full_garden_for_kincaid_second_to_last_student_s_garden(self):
         garden = Garden("VRCGVVRVCGGCCGVRGCVCGCGV\nVRCCCGCRRGVCGCRVVCVGCGCV")
         self.assertEqual(
@@ -73,7 +117,3 @@ class KindergartenGardenTest(unittest.TestCase):
         self.assertEqual(
             garden.plants("Xander"), ["Radishes", "Grass", "Clover", "Violets"]
         )
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/luhn/.meta/tests.toml b/exercises/practice/luhn/.meta/tests.toml
index 9bb82241..eea08d79 100644
--- a/exercises/practice/luhn/.meta/tests.toml
+++ b/exercises/practice/luhn/.meta/tests.toml
@@ -1,6 +1,13 @@
-# This is an auto-generated file. Regular comments will be removed when this
-# file is regenerated. Regenerating will not touch any manually added keys,
-# so comments can be added in a "comment" key.
+# 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.
 
 [792a7082-feb7-48c7-b88b-bbfec160865e]
 description = "single digit strings can not be valid"
@@ -50,8 +57,17 @@ description = "more than a single zero is valid"
 [ab56fa80-5de8-4735-8a4a-14dae588663e]
 description = "input digit 9 is correctly converted to output digit 9"
 
+[b9887ee8-8337-46c5-bc45-3bcab51bc36f]
+description = "very long input is valid"
+
+[8a7c0e24-85ea-4154-9cf1-c2db90eabc08]
+description = "valid luhn with an odd number of digits and non zero first digit"
+
 [39a06a5a-5bad-4e0f-b215-b042d46209b1]
 description = "using ascii value for non-doubled non-digit isn't allowed"
 
 [f94cf191-a62f-4868-bc72-7253114aa157]
 description = "using ascii value for doubled non-digit isn't allowed"
+
+[8b72ad26-c8be-49a2-b99c-bcc3bf631b33]
+description = "non-numeric, non-space char in the middle with a sum that's divisible by 10 isn't allowed"
diff --git a/exercises/practice/luhn/luhn_test.py b/exercises/practice/luhn/luhn_test.py
index b0ee748f..a60d8525 100644
--- a/exercises/practice/luhn/luhn_test.py
+++ b/exercises/practice/luhn/luhn_test.py
@@ -56,12 +56,23 @@ class LuhnTest(unittest.TestCase):
     def test_input_digit_9_is_correctly_converted_to_output_digit_9(self):
         self.assertIs(Luhn("091").valid(), True)
 
+    def test_very_long_input_is_valid(self):
+        self.assertIs(Luhn("9999999999 9999999999 9999999999 9999999999").valid(), True)
+
+    def test_valid_luhn_with_an_odd_number_of_digits_and_non_zero_first_digit(self):
+        self.assertIs(Luhn("109").valid(), True)
+
     def test_using_ascii_value_for_non_doubled_non_digit_isn_t_allowed(self):
         self.assertIs(Luhn("055b 444 285").valid(), False)
 
     def test_using_ascii_value_for_doubled_non_digit_isn_t_allowed(self):
         self.assertIs(Luhn(":9").valid(), False)
 
+    def test_non_numeric_non_space_char_in_the_middle_with_a_sum_that_s_divisible_by_10_isn_t_allowed(
+        self,
+    ):
+        self.assertIs(Luhn("59%59").valid(), False)
+
     # Additional tests for this track
 
     def test_is_valid_can_be_called_repeatedly(self):
@@ -71,7 +82,3 @@ class LuhnTest(unittest.TestCase):
         number = Luhn("055 444 285")
         self.assertIs(number.valid(), True)
         self.assertIs(number.valid(), True)
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/markdown/.meta/tests.toml b/exercises/practice/markdown/.meta/tests.toml
index fecbcf50..28b7baa7 100644
--- a/exercises/practice/markdown/.meta/tests.toml
+++ b/exercises/practice/markdown/.meta/tests.toml
@@ -1,6 +1,13 @@
-# This is an auto-generated file. Regular comments will be removed when this
-# file is regenerated. Regenerating will not touch any manually added keys,
-# so comments can be added in a "comment" key.
+# 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.
 
 [e75c8103-a6b8-45d9-84ad-e68520545f6e]
 description = "parses normal text as a paragraph"
@@ -20,9 +27,26 @@ description = "with h1 header level"
 [d0f7a31f-6935-44ac-8a9a-1e8ab16af77f]
 description = "with h2 header level"
 
+[9df3f500-0622-4696-81a7-d5babd9b5f49]
+description = "with h3 header level"
+
+[50862777-a5e8-42e9-a3b8-4ba6fcd0ed03]
+description = "with h4 header level"
+
+[ee1c23ac-4c86-4f2a-8b9c-403548d4ab82]
+description = "with h5 header level"
+
 [13b5f410-33f5-44f0-a6a7-cfd4ab74b5d5]
 description = "with h6 header level"
 
+[6dca5d10-5c22-4e2a-ac2b-bd6f21e61939]
+description = "with h7 header level"
+include = false
+
+[81c0c4db-435e-4d77-860d-45afacdad810]
+description = "h7 header level is a paragraph"
+reimplements = "6dca5d10-5c22-4e2a-ac2b-bd6f21e61939"
+
 [25288a2b-8edc-45db-84cf-0b6c6ee034d6]
 description = "unordered lists"
 
diff --git a/exercises/practice/markdown/markdown_test.py b/exercises/practice/markdown/markdown_test.py
index 5f18a6ba..5c059c72 100644
--- a/exercises/practice/markdown/markdown_test.py
+++ b/exercises/practice/markdown/markdown_test.py
@@ -35,11 +35,30 @@ class MarkdownTest(unittest.TestCase):
     def test_with_h2_header_level(self):
         self.assertEqual(parse("## This will be an h2"), "<h2>This will be an h2</h2>")
 
+    def test_with_h3_header_level(self):
+        self.assertEqual(parse("### This will be an h3"), "<h3>This will be an h3</h3>")
+
+    def test_with_h4_header_level(self):
+        self.assertEqual(
+            parse("#### This will be an h4"), "<h4>This will be an h4</h4>"
+        )
+
+    def test_with_h5_header_level(self):
+        self.assertEqual(
+            parse("##### This will be an h5"), "<h5>This will be an h5</h5>"
+        )
+
     def test_with_h6_header_level(self):
         self.assertEqual(
             parse("###### This will be an h6"), "<h6>This will be an h6</h6>"
         )
 
+    def test_h7_header_level_is_a_paragraph(self):
+        self.assertEqual(
+            parse("####### This will not be an h7"),
+            "<p>####### This will not be an h7</p>",
+        )
+
     def test_unordered_lists(self):
         self.assertEqual(
             parse("* Item 1\n* Item 2"), "<ul><li>Item 1</li><li>Item 2</li></ul>"
@@ -80,7 +99,3 @@ class MarkdownTest(unittest.TestCase):
             parse("# Start a list\n* Item 1\n* Item 2\nEnd a list"),
             "<h1>Start a list</h1><ul><li>Item 1</li><li>Item 2</li></ul><p>End a list</p>",
         )
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/prime-factors/.meta/tests.toml b/exercises/practice/prime-factors/.meta/tests.toml
index f3f05a3e..6f9cc8ce 100644
--- a/exercises/practice/prime-factors/.meta/tests.toml
+++ b/exercises/practice/prime-factors/.meta/tests.toml
@@ -1,6 +1,13 @@
-# This is an auto-generated file. Regular comments will be removed when this
-# file is regenerated. Regenerating will not touch any manually added keys,
-# so comments can be added in a "comment" key.
+# 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.
 
 [924fc966-a8f5-4288-82f2-6b9224819ccd]
 description = "no factors"
@@ -8,12 +15,27 @@ description = "no factors"
 [17e30670-b105-4305-af53-ddde182cb6ad]
 description = "prime number"
 
+[238d57c8-4c12-42ef-af34-ae4929f94789]
+description = "another prime number"
+
 [f59b8350-a180-495a-8fb1-1712fbee1158]
 description = "square of a prime"
 
+[756949d3-3158-4e3d-91f2-c4f9f043ee70]
+description = "product of first prime"
+
 [bc8c113f-9580-4516-8669-c5fc29512ceb]
 description = "cube of a prime"
 
+[7d6a3300-a4cb-4065-bd33-0ced1de6cb44]
+description = "product of second prime"
+
+[073ac0b2-c915-4362-929d-fc45f7b9a9e4]
+description = "product of third prime"
+
+[6e0e4912-7fb6-47f3-a9ad-dbcd79340c75]
+description = "product of first and second prime"
+
 [00485cd3-a3fe-4fbe-a64a-a4308fc1f870]
 description = "product of primes and non-primes"
 
diff --git a/exercises/practice/prime-factors/prime_factors_test.py b/exercises/practice/prime-factors/prime_factors_test.py
index 80deb17c..02b36e7a 100644
--- a/exercises/practice/prime-factors/prime_factors_test.py
+++ b/exercises/practice/prime-factors/prime_factors_test.py
@@ -14,12 +14,27 @@ class PrimeFactorsTest(unittest.TestCase):
     def test_prime_number(self):
         self.assertEqual(factors(2), [2])
 
+    def test_another_prime_number(self):
+        self.assertEqual(factors(3), [3])
+
     def test_square_of_a_prime(self):
         self.assertEqual(factors(9), [3, 3])
 
+    def test_product_of_first_prime(self):
+        self.assertEqual(factors(4), [2, 2])
+
     def test_cube_of_a_prime(self):
         self.assertEqual(factors(8), [2, 2, 2])
 
+    def test_product_of_second_prime(self):
+        self.assertEqual(factors(27), [3, 3, 3])
+
+    def test_product_of_third_prime(self):
+        self.assertEqual(factors(625), [5, 5, 5, 5])
+
+    def test_product_of_first_and_second_prime(self):
+        self.assertEqual(factors(6), [2, 3])
+
     def test_product_of_primes_and_non_primes(self):
         self.assertEqual(factors(12), [2, 2, 3])
 
@@ -28,7 +43,3 @@ class PrimeFactorsTest(unittest.TestCase):
 
     def test_factors_include_a_large_prime(self):
         self.assertEqual(factors(93819012551), [11, 9539, 894119])
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/rational-numbers/.meta/tests.toml b/exercises/practice/rational-numbers/.meta/tests.toml
index 2cf56c08..30cf65de 100644
--- a/exercises/practice/rational-numbers/.meta/tests.toml
+++ b/exercises/practice/rational-numbers/.meta/tests.toml
@@ -1,117 +1,127 @@
-# This is an auto-generated file. Regular comments will be removed when this
-# file is regenerated. Regenerating will not touch any manually added keys,
-# so comments can be added in a "comment" key.
+# 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.
 
 [0ba4d988-044c-4ed5-9215-4d0bb8d0ae9f]
-description = "Add two positive rational numbers"
+description = "Arithmetic -> Addition -> Add two positive rational numbers"
 
 [88ebc342-a2ac-4812-a656-7b664f718b6a]
-description = "Add a positive rational number and a negative rational number"
+description = "Arithmetic -> Addition -> Add a positive rational number and a negative rational number"
 
 [92ed09c2-991e-4082-a602-13557080205c]
-description = "Add two negative rational numbers"
+description = "Arithmetic -> Addition -> Add two negative rational numbers"
 
 [6e58999e-3350-45fb-a104-aac7f4a9dd11]
-description = "Add a rational number to its additive inverse"
+description = "Arithmetic -> Addition -> Add a rational number to its additive inverse"
 
 [47bba350-9db1-4ab9-b412-4a7e1f72a66e]
-description = "Subtract two positive rational numbers"
+description = "Arithmetic -> Subtraction -> Subtract two positive rational numbers"
 
 [93926e2a-3e82-4aee-98a7-fc33fb328e87]
-description = "Subtract a positive rational number and a negative rational number"
+description = "Arithmetic -> Subtraction -> Subtract a positive rational number and a negative rational number"
 
 [a965ba45-9b26-442b-bdc7-7728e4b8d4cc]
-description = "Subtract two negative rational numbers"
+description = "Arithmetic -> Subtraction -> Subtract two negative rational numbers"
 
 [0df0e003-f68e-4209-8c6e-6a4e76af5058]
-description = "Subtract a rational number from itself"
+description = "Arithmetic -> Subtraction -> Subtract a rational number from itself"
 
 [34fde77a-75f4-4204-8050-8d3a937958d3]
-description = "Multiply two positive rational numbers"
+description = "Arithmetic -> Multiplication -> Multiply two positive rational numbers"
 
 [6d015cf0-0ea3-41f1-93de-0b8e38e88bae]
-description = "Multiply a negative rational number by a positive rational number"
+description = "Arithmetic -> Multiplication -> Multiply a negative rational number by a positive rational number"
 
 [d1bf1b55-954e-41b1-8c92-9fc6beeb76fa]
-description = "Multiply two negative rational numbers"
+description = "Arithmetic -> Multiplication -> Multiply two negative rational numbers"
 
 [a9b8f529-9ec7-4c79-a517-19365d779040]
-description = "Multiply a rational number by its reciprocal"
+description = "Arithmetic -> Multiplication -> Multiply a rational number by its reciprocal"
 
 [d89d6429-22fa-4368-ab04-9e01a44d3b48]
-description = "Multiply a rational number by 1"
+description = "Arithmetic -> Multiplication -> Multiply a rational number by 1"
 
 [0d95c8b9-1482-4ed7-bac9-b8694fa90145]
-description = "Multiply a rational number by 0"
+description = "Arithmetic -> Multiplication -> Multiply a rational number by 0"
 
 [1de088f4-64be-4e6e-93fd-5997ae7c9798]
-description = "Divide two positive rational numbers"
+description = "Arithmetic -> Division -> Divide two positive rational numbers"
 
 [7d7983db-652a-4e66-981a-e921fb38d9a9]
-description = "Divide a positive rational number by a negative rational number"
+description = "Arithmetic -> Division -> Divide a positive rational number by a negative rational number"
 
 [1b434d1b-5b38-4cee-aaf5-b9495c399e34]
-description = "Divide two negative rational numbers"
+description = "Arithmetic -> Division -> Divide two negative rational numbers"
 
 [d81c2ebf-3612-45a6-b4e0-f0d47812bd59]
-description = "Divide a rational number by 1"
+description = "Arithmetic -> Division -> Divide a rational number by 1"
 
 [5fee0d8e-5955-4324-acbe-54cdca94ddaa]
-description = "Absolute value of a positive rational number"
+description = "Absolute value -> Absolute value of a positive rational number"
 
 [3cb570b6-c36a-4963-a380-c0834321bcaa]
-description = "Absolute value of a positive rational number with negative numerator and denominator"
+description = "Absolute value -> Absolute value of a positive rational number with negative numerator and denominator"
 
 [6a05f9a0-1f6b-470b-8ff7-41af81773f25]
-description = "Absolute value of a negative rational number"
+description = "Absolute value -> Absolute value of a negative rational number"
 
 [5d0f2336-3694-464f-8df9-f5852fda99dd]
-description = "Absolute value of a negative rational number with negative denominator"
+description = "Absolute value -> Absolute value of a negative rational number with negative denominator"
 
 [f8e1ed4b-9dca-47fb-a01e-5311457b3118]
-description = "Absolute value of zero"
+description = "Absolute value -> Absolute value of zero"
 
 [ea2ad2af-3dab-41e7-bb9f-bd6819668a84]
-description = "Raise a positive rational number to a positive integer power"
+description = "Exponentiation of a rational number -> Raise a positive rational number to a positive integer power"
 
 [8168edd2-0af3-45b1-b03f-72c01332e10a]
-description = "Raise a negative rational number to a positive integer power"
+description = "Exponentiation of a rational number -> Raise a negative rational number to a positive integer power"
 
 [e2f25b1d-e4de-4102-abc3-c2bb7c4591e4]
-description = "Raise zero to an integer power"
+description = "Exponentiation of a rational number -> Raise zero to an integer power"
 
 [431cac50-ab8b-4d58-8e73-319d5404b762]
-description = "Raise one to an integer power"
+description = "Exponentiation of a rational number -> Raise one to an integer power"
 
 [7d164739-d68a-4a9c-b99f-dd77ce5d55e6]
-description = "Raise a positive rational number to the power of zero"
+description = "Exponentiation of a rational number -> Raise a positive rational number to the power of zero"
 
 [eb6bd5f5-f880-4bcd-8103-e736cb6e41d1]
-description = "Raise a negative rational number to the power of zero"
+description = "Exponentiation of a rational number -> Raise a negative rational number to the power of zero"
 
 [30b467dd-c158-46f5-9ffb-c106de2fd6fa]
-description = "Raise a real number to a positive rational number"
+description = "Exponentiation of a real number to a rational number -> Raise a real number to a positive rational number"
 
 [6e026bcc-be40-4b7b-ae22-eeaafc5a1789]
-description = "Raise a real number to a negative rational number"
+description = "Exponentiation of a real number to a rational number -> Raise a real number to a negative rational number"
 
 [9f866da7-e893-407f-8cd2-ee85d496eec5]
-description = "Raise a real number to a zero rational number"
+description = "Exponentiation of a real number to a rational number -> Raise a real number to a zero rational number"
 
 [0a63fbde-b59c-4c26-8237-1e0c73354d0a]
-description = "Reduce a positive rational number to lowest terms"
+description = "Reduction to lowest terms -> Reduce a positive rational number to lowest terms"
+
+[5ed6f248-ad8d-4d4e-a545-9146c6727f33]
+description = "Reduction to lowest terms -> Reduce places the minus sign on the numerator"
 
 [f87c2a4e-d29c-496e-a193-318c503e4402]
-description = "Reduce a negative rational number to lowest terms"
+description = "Reduction to lowest terms -> Reduce a negative rational number to lowest terms"
 
 [3b92ffc0-5b70-4a43-8885-8acee79cdaaf]
-description = "Reduce a rational number with a negative denominator to lowest terms"
+description = "Reduction to lowest terms -> Reduce a rational number with a negative denominator to lowest terms"
 
 [c9dbd2e6-5ac0-4a41-84c1-48b645b4f663]
-description = "Reduce zero to lowest terms"
+description = "Reduction to lowest terms -> Reduce zero to lowest terms"
 
 [297b45ad-2054-4874-84d4-0358dc1b8887]
-description = "Reduce an integer to lowest terms"
+description = "Reduction to lowest terms -> Reduce an integer to lowest terms"
 
 [a73a17fe-fe8c-4a1c-a63b-e7579e333d9e]
-description = "Reduce one to lowest terms"
+description = "Reduction to lowest terms -> Reduce one to lowest terms"
diff --git a/exercises/practice/rational-numbers/rational_numbers_test.py b/exercises/practice/rational-numbers/rational_numbers_test.py
index f4486bce..323439d0 100644
--- a/exercises/practice/rational-numbers/rational_numbers_test.py
+++ b/exercises/practice/rational-numbers/rational_numbers_test.py
@@ -131,6 +131,9 @@ class RationalNumbersTest(unittest.TestCase):
     def test_reduce_a_positive_rational_number_to_lowest_terms(self):
         self.assertEqual(Rational(2, 4), Rational(1, 2))
 
+    def test_reduce_places_the_minus_sign_on_the_numerator(self):
+        self.assertEqual(Rational(3, -4), Rational(-3, 4))
+
     def test_reduce_a_negative_rational_number_to_lowest_terms(self):
         self.assertEqual(Rational(-4, 6), Rational(-2, 3))
 
diff --git a/exercises/practice/resistor-color-duo/.meta/tests.toml b/exercises/practice/resistor-color-duo/.meta/tests.toml
index 862b5770..9036fc78 100644
--- a/exercises/practice/resistor-color-duo/.meta/tests.toml
+++ b/exercises/practice/resistor-color-duo/.meta/tests.toml
@@ -1,6 +1,13 @@
-# This is an auto-generated file. Regular comments will be removed when this
-# file is regenerated. Regenerating will not touch any manually added keys,
-# so comments can be added in a "comment" key.
+# 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.
 
 [ce11995a-5b93-4950-a5e9-93423693b2fc]
 description = "Brown and black"
@@ -11,8 +18,14 @@ description = "Blue and grey"
 [f1886361-fdfd-4693-acf8-46726fe24e0c]
 description = "Yellow and violet"
 
+[b7a6cbd2-ae3c-470a-93eb-56670b305640]
+description = "White and red"
+
 [77a8293d-2a83-4016-b1af-991acc12b9fe]
 description = "Orange and orange"
 
 [0c4fb44f-db7c-4d03-afa8-054350f156a8]
 description = "Ignore additional colors"
+
+[4a8ceec5-0ab4-4904-88a4-daf953a5e818]
+description = "Black and brown, one-digit"
diff --git a/exercises/practice/resistor-color-duo/resistor_color_duo_test.py b/exercises/practice/resistor-color-duo/resistor_color_duo_test.py
index c259f5db..72df473f 100644
--- a/exercises/practice/resistor-color-duo/resistor_color_duo_test.py
+++ b/exercises/practice/resistor-color-duo/resistor_color_duo_test.py
@@ -17,12 +17,14 @@ class ResistorColorDuoTest(unittest.TestCase):
     def test_yellow_and_violet(self):
         self.assertEqual(value(["yellow", "violet"]), 47)
 
+    def test_white_and_red(self):
+        self.assertEqual(value(["white", "red"]), 92)
+
     def test_orange_and_orange(self):
         self.assertEqual(value(["orange", "orange"]), 33)
 
     def test_ignore_additional_colors(self):
         self.assertEqual(value(["green", "brown", "orange"]), 51)
 
-
-if __name__ == "__main__":
-    unittest.main()
+    def test_black_and_brown_one_digit(self):
+        self.assertEqual(value(["black", "brown"]), 1)
diff --git a/exercises/practice/sublist/.meta/tests.toml b/exercises/practice/sublist/.meta/tests.toml
index 74bd60cc..de5020a9 100644
--- a/exercises/practice/sublist/.meta/tests.toml
+++ b/exercises/practice/sublist/.meta/tests.toml
@@ -1,6 +1,13 @@
-# This is an auto-generated file. Regular comments will be removed when this
-# file is regenerated. Regenerating will not touch any manually added keys,
-# so comments can be added in a "comment" key.
+# 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.
 
 [97319c93-ebc5-47ab-a022-02a1980e1d29]
 description = "empty lists"
@@ -47,6 +54,9 @@ description = "first list missing element from second list"
 [83ffe6d8-a445-4a3c-8795-1e51a95e65c3]
 description = "second list missing element from first list"
 
+[7bc76cb8-5003-49ca-bc47-cdfbe6c2bb89]
+description = "first list missing additional digits from second list"
+
 [0d7ee7c1-0347-45c8-9ef5-b88db152b30b]
 description = "order matters to a list"
 
diff --git a/exercises/practice/sublist/sublist_test.py b/exercises/practice/sublist/sublist_test.py
index 03ff9ceb..b3dc9c7b 100644
--- a/exercises/practice/sublist/sublist_test.py
+++ b/exercises/practice/sublist/sublist_test.py
@@ -57,6 +57,9 @@ class SublistTest(unittest.TestCase):
     def test_second_list_missing_element_from_first_list(self):
         self.assertEqual(sublist([1, 2, 3], [1, 3]), UNEQUAL)
 
+    def test_first_list_missing_additional_digits_from_second_list(self):
+        self.assertEqual(sublist([1, 2], [1, 22]), UNEQUAL)
+
     def test_order_matters_to_a_list(self):
         self.assertEqual(sublist([1, 2, 3], [3, 2, 1]), UNEQUAL)
 
@@ -83,7 +86,3 @@ class SublistTest(unittest.TestCase):
         self.assertEqual(
             sublist(list(range(3, 200, 3)), list(range(15, 200, 15))), UNEQUAL
         )
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/tournament/.meta/tests.toml b/exercises/practice/tournament/.meta/tests.toml
index 59867708..0e33c87f 100644
--- a/exercises/practice/tournament/.meta/tests.toml
+++ b/exercises/practice/tournament/.meta/tests.toml
@@ -1,6 +1,13 @@
-# This is an auto-generated file. Regular comments will be removed when this
-# file is regenerated. Regenerating will not touch any manually added keys,
-# so comments can be added in a "comment" key.
+# 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.
 
 [67e9fab1-07c1-49cf-9159-bc8671cc7c9c]
 description = "just the header if no input"
@@ -34,3 +41,6 @@ description = "incomplete competition (not all pairs have played)"
 
 [3aa0386f-150b-4f99-90bb-5195e7b7d3b8]
 description = "ties broken alphabetically"
+
+[f9e20931-8a65-442a-81f6-503c0205b17a]
+description = "ensure points sorted numerically"
diff --git a/exercises/practice/tournament/tournament_test.py b/exercises/practice/tournament/tournament_test.py
index dac4c15c..22a81667 100644
--- a/exercises/practice/tournament/tournament_test.py
+++ b/exercises/practice/tournament/tournament_test.py
@@ -139,6 +139,17 @@ class TournamentTest(unittest.TestCase):
         ]
         self.assertEqual(tally(results), table)
 
-
-if __name__ == "__main__":
-    unittest.main()
+    def test_ensure_points_sorted_numerically(self):
+        results = [
+            "Devastating Donkeys;Blithering Badgers;win",
+            "Devastating Donkeys;Blithering Badgers;win",
+            "Devastating Donkeys;Blithering Badgers;win",
+            "Devastating Donkeys;Blithering Badgers;win",
+            "Blithering Badgers;Devastating Donkeys;win",
+        ]
+        table = [
+            "Team                           | MP |  W |  D |  L |  P",
+            "Devastating Donkeys            |  5 |  4 |  0 |  1 | 12",
+            "Blithering Badgers             |  5 |  1 |  0 |  4 |  3",
+        ]
+        self.assertEqual(tally(results), table)
diff --git a/exercises/practice/transpose/.meta/tests.toml b/exercises/practice/transpose/.meta/tests.toml
index 7faa8aea..32e366fb 100644
--- a/exercises/practice/transpose/.meta/tests.toml
+++ b/exercises/practice/transpose/.meta/tests.toml
@@ -1,6 +1,13 @@
-# This is an auto-generated file. Regular comments will be removed when this
-# file is regenerated. Regenerating will not touch any manually added keys,
-# so comments can be added in a "comment" key.
+# 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.
 
 [404b7262-c050-4df0-a2a2-0cb06cd6a821]
 description = "empty string"
@@ -34,3 +41,6 @@ description = "rectangle"
 
 [b80badc9-057e-4543-bd07-ce1296a1ea2c]
 description = "triangle"
+
+[76acfd50-5596-4d05-89f1-5116328a7dd9]
+description = "jagged triangle"
diff --git a/exercises/practice/transpose/transpose_test.py b/exercises/practice/transpose/transpose_test.py
index c47a3fdb..0a0f5c02 100644
--- a/exercises/practice/transpose/transpose_test.py
+++ b/exercises/practice/transpose/transpose_test.py
@@ -115,6 +115,7 @@ class TransposeTest(unittest.TestCase):
         expected = ["TEASER", " EASER", "  ASER", "   SER", "    ER", "     R"]
         self.assertEqual(transpose("\n".join(lines)), "\n".join(expected))
 
-
-if __name__ == "__main__":
-    unittest.main()
+    def test_jagged_triangle(self):
+        lines = ["11", "2", "3333", "444", "555555", "66666"]
+        expected = ["123456", "1 3456", "  3456", "  3 56", "    56", "    5"]
+        self.assertEqual(transpose("\n".join(lines)), "\n".join(expected))