@@ -59,15 +59,18 @@ def _expected_parts(fields: dict[str, Any], number: int) -> int:
5959
6060 One per ``qN.pM.text``, and one more where ``qN.solution`` is written beside a
6161 solution for every part there is: nothing is left for it to answer, so it is a part
62- of its own, as `in2lambda convert` writes one.
62+ of its own, as `in2lambda convert` writes one. A question with no parts written for
63+ it at all is one empty part, since a question exported holding none carries the
64+ template's placeholder wording instead.
6365 """
6466 written = [
6567 int (found [2 ])
6668 for key in fields
6769 if (found := PART .fullmatch (key )) and int (found [1 ]) == number
6870 ]
6971 answered = all (f"q{ number } .p{ part } .solution" in fields for part in written )
70- return len (written ) + (answered and f"q{ number } .solution" in fields )
72+ parts = len (written ) + (answered and f"q{ number } .solution" in fields )
73+ return parts or 1
7174
7275
7376def _reported (folder : Path ) -> list [dict [str , Any ]]:
@@ -568,9 +571,12 @@ def test_build_follows_the_report(folder: Path, tmp_path: Path, monkeypatch) ->
568571 for index , part in enumerate (question .parts , start = 1 ):
569572 if f"q{ number } .p{ index } .text" not in fields :
570573 # The question's own solution, written where every part is answered
571- # already: last, and holding nothing but that solution.
574+ # already: last, and holding nothing but that solution. Or, where the
575+ # question has no solution either, the empty part a question with no
576+ # parts written for it exports as.
572577 assert part .text == ""
573- assert part .worked_solution == fields [f"q{ number } .solution" ]["value" ]
578+ solution = fields .get (f"q{ number } .solution" )
579+ assert part .worked_solution == (solution ["value" ] if solution else "" )
574580 continue
575581 assert part .text == fields [f"q{ number } .p{ index } .text" ]["value" ]
576582 # A part's own solution, or the question's where it has none of its own.
0 commit comments