def test_scale(self) -> None: sr_2 = SubRecipe(Ingredient(SVS("spam"), Quantity(2)), (SVS([2, "spams"]), )) sr_6 = SubRecipe(Ingredient(SVS("spam"), Quantity(6)), (SVS([6, "spams"]), )) assert sr_2.scale(3) == sr_6
def test_reference_compilation(self) -> None: sub_recipe = SubRecipe( Step(SVS("open"), (Ingredient(SVS("spam")), )), (SVS("spam"), SVS("tin")), True, ) assert compile([ "spam, tin = open(spam)\nspam\n1/3*spam\n25% of the spam\nleft over spam\n2 'tin'\n50g spam" # noqa: E501 ]) == [ Recipe(( sub_recipe, # spam Reference(sub_recipe, 0, Proportion(1.0)), # 1/3*spam Reference(sub_recipe, 0, Proportion(Fraction(1, 3), preposition="*")), # 25% of the spam Reference( sub_recipe, 0, Proportion(0.25, percentage=True, preposition="% of the"), ), # remaining Reference(sub_recipe, 0, Proportion(None, remainder_wording="left over")), # 2 tin Reference(sub_recipe, 1, Quantity(2.0)), # 50g spam Reference(sub_recipe, 0, Quantity(50.0, "g")), )), ]
def test_scale(self) -> None: step = Step(SVS(["fry in ", 4, " blocks"]), (Ingredient(SVS("spam"), Quantity(2)), )) assert step.scale(3) == Step( SVS(["fry in ", 12, " blocks"]), (Ingredient(SVS("spam"), Quantity(6)), ), )
def test_scale(self) -> None: sr_2 = SubRecipe(Ingredient(SVS("spam"), Quantity(2)), (SVS([2, " blocks of spam"]), )) sr_6 = SubRecipe(Ingredient(SVS("spam"), Quantity(6)), (SVS([6, " blocks of spam"]), )) assert Reference(sr_2, 0).scale(3) == Reference(sr_6, 0) assert Reference(sr_2, 0, Quantity(100, "g")).scale(3) == Reference( sr_6, 0, Quantity(300, "g"), )
def test_scale(self) -> None: sr_2 = SubRecipe(Ingredient(SVS("spam"), Quantity(2)), (SVS("spam"), )) ref_sr_2 = Reference(sr_2) sr_6 = SubRecipe(Ingredient(SVS("spam"), Quantity(6)), (SVS("spam"), )) ref_sr_6 = Reference(sr_6) first_rec_2 = Recipe((sr_2, )) second_rec_2 = Recipe((ref_sr_2, ), follows=first_rec_2) first_rec_6 = Recipe((sr_6, )) second_rec_6 = Recipe((ref_sr_6, ), follows=first_rec_6) assert first_rec_2.scale(3) == first_rec_6 assert second_rec_2.scale(3) == second_rec_6
def test_separate_recipes(self) -> None: compiled = compile_markdown( dedent(""" Fried egg: ```recipe 1 egg ``` ```recipe fry(egg) ``` Boiled egg: ```new-recipe 2 egg ``` ```recipe boil(egg) ``` """).strip()) e1 = SubRecipe( Ingredient(SVS("egg"), Quantity(1)), (SVS("egg"), ), show_output_names=False, ) r1 = Recipe((e1, )) r2 = Recipe((Step(SVS("fry"), (Reference(e1), )), ), follows=r1) e2 = SubRecipe( Ingredient(SVS("egg"), Quantity(2)), (SVS("egg"), ), show_output_names=False, ) r3 = Recipe((e2, )) r4 = Recipe((Step(SVS("boil"), (Reference(e2), )), ), follows=r3) assert compiled.recipes == [[r1, r2], [r3, r4]] # Check anchor IDs are unique too html = compiled.render() assert 'id="recipe-egg"' in html assert 'href="#recipe-egg"' in html assert 'id="recipe2-egg"' in html assert 'href="#recipe2-egg"' in html
def test_dont_inline_partial_uses_of_a_subrecipe(self) -> None: sub_recipe = SubRecipe( Ingredient(SVS("spam"), Quantity(100, "g")), (SVS("spam"), ), False, ) assert compile(["100g spam\nfry(50g spam, eggs)"]) == [ Recipe(( sub_recipe, Step( SVS("fry"), ( Reference(sub_recipe, 0, Quantity(50, "g")), Ingredient(SVS("eggs")), ), ), )) ]
def test_dont_inline_definitions_from_earlier_blocks(self) -> None: sub_recipe = SubRecipe( Ingredient(SVS("spam"), Quantity(100, "g")), (SVS("spam"), ), False, ) recipe0 = Recipe((sub_recipe, )) recipe1 = Recipe( (Step( SVS("fry"), ( Reference(sub_recipe, 0, Quantity(50, "g")), Ingredient(SVS("eggs")), ), ), ), follows=recipe0, ) assert compile(["100g spam", "fry(50g spam, eggs)"]) == [recipe0, recipe1]
def test_ingredient_compilation(self) -> None: assert compile( ["500g spam\n2 eggs\n1 kg foo\n1 can of dog food\nheat"]) == [ Recipe(( # With unit SubRecipe( Ingredient(SVS("spam"), Quantity(500.0, "g")), (SVS("spam"), ), False, ), # No unit SubRecipe( Ingredient(SVS("eggs"), Quantity(2.0)), (SVS("eggs"), ), False, ), # With spacing between number and unit SubRecipe( Ingredient(SVS("foo"), Quantity(1.0, "kg", value_unit_spacing=" ")), (SVS("foo"), ), False, ), # With spacing between number and unit SubRecipe( Ingredient( SVS("dog food"), Quantity( 1, "can", value_unit_spacing=" ", preposition=" of", ), ), (SVS("dog food"), ), False, ), # No quantity SubRecipe(Ingredient(SVS("heat")), (SVS("heat"), ), False), )) ]
def test_inlining_single_references(self, quantity_spec: str) -> None: assert compile( [f"meat = 10g spam, sliced\nfry({quantity_spec} meat, eggs)"]) == [ Recipe((Step( SVS("fry"), ( Step( SVS("sliced"), (Ingredient(SVS("spam"), Quantity(10, "g")), ), ), Ingredient(SVS("eggs")), ), ), )) ]
def test_recipe_code_blocks_and_scaled_rendering(self, source: str) -> None: compiled = compile_markdown(dedent(source).strip()) assert compiled.title == "A recipe" assert compiled.servings == 2 assert compiled.recipes == [[ Recipe((Step( SVS("fry"), ( Ingredient(SVS("spam"), Quantity(100, "g")), Ingredient(SVS("eggs"), Quantity(2)), ), ), )), ]] assert (compiled.render(2) == dedent(""" <header><h1 class="rg-title-scalable">A recipe <span class="rg-serving-count">for <span class="rg-scaled-value">4</span></span></h1><p>Rescaled from <span class="rg-original-servings">2 servings</span>.</p></header> <div class="rg-recipe-block"> <table class="rg-table"> <tr> <td class="rg-ingredient rg-border-left-sub-recipe rg-border-top-sub-recipe"> <span class="rg-quantity-with-conversions rg-scaled-value" tabindex="0"> 200g<ul class="rg-quantity-conversions"> <li><sup>1</sup>⁄<sub>5</sub>kg</li> <li>0.441lb</li> <li>7.05oz</li> </ul> </span> spam </td> <td class="rg-step rg-border-right-sub-recipe rg-border-top-sub-recipe rg-border-bottom-sub-recipe" rowspan="2">fry</td> </tr> <tr><td class="rg-ingredient rg-border-left-sub-recipe rg-border-bottom-sub-recipe"><span class="rg-quantity-unitless rg-scaled-value">4</span> eggs</td></tr> </table> </div><p>Ta-da!</p> """ # noqa: E501 ).lstrip())
def _compile_quantity(self, ast_quantity: ast.Quantity) -> Quantity: unit: Optional[str] = None if ast_quantity.unit is not None: unit_string = compile_string(ast_quantity.unit) # The grammar disallows scaled values in unit names, this assertion # is just a sanity check to that effect assert unit_string == unit_string.scale(0) unit = str(unit_string) return Quantity( value=ast_quantity.value, unit=unit, value_unit_spacing=ast_quantity.value_unit_spacing, preposition=ast_quantity.preposition, )
def test_inlines_within_inlines(self) -> None: recipe = Recipe((Step( SVS("boil"), ( SubRecipe( Step( SVS("fry"), (Ingredient(SVS("spam"), Quantity(100, "g")), ), ), (SVS("fried spam"), ), ), Ingredient(SVS("water")), ), ), )) assert compile([ "100g spam\nfried spam := fry(spam)\nboil(fried spam, water)" ]) == [recipe]
def test_inlining_within_a_block(self) -> None: recipe0 = Recipe((SubRecipe(Ingredient(SVS("egg")), (SVS("egg"), ), show_output_names=False), )) recipe1 = Recipe( (Step(SVS("fry"), (Ingredient(SVS("spam"), Quantity(50, "g")), )), ), follows=recipe0, ) recipe2 = Recipe( (SubRecipe(Ingredient(SVS("potato")), (SVS("potato"), ), show_output_names=False), ), follows=recipe1, ) assert compile(["egg", "50g spam\nfry(spam)", "potato"]) == [ recipe0, recipe1, recipe2, ]
class TestQuantity: @pytest.mark.parametrize( "a, b, exp", [ # Unitless (Quantity(1), Quantity(1), True), (Quantity(1), Quantity(2), False), (Quantity(1), Quantity(1, "g"), False), (Quantity(1, "g"), Quantity(1), False), # Same unit (Quantity(1, "g"), Quantity(1, "g"), True), (Quantity(1, "g"), Quantity(1, "G"), True), (Quantity(1, "g"), Quantity(2, "g"), False), (Quantity(1, "g"), Quantity(1, "kg"), False), # Unit conversion used (Quantity(1, "pounds"), Quantity(16, "ounces"), True), (Quantity(1, "pounds"), Quantity(17, "ounces"), False), # Incompatible, but valid units (Quantity(1, "kg"), Quantity(1, "l"), False), # Compatible but unknown units (Quantity(123, "foo"), Quantity(123, "foo"), True), (Quantity(123, "FOO"), Quantity(123, "foo"), True), # Floating point errors mean an approximate comparison is required # here (Quantity(10, "g"), Quantity(0.01, "kg"), True), ], ) def test_has_equal_value_to(self, a: Quantity, b: Quantity, exp: bool) -> None: assert a.has_equal_value_to(b) is exp def test_scale(self) -> None: assert Quantity(123, "foo").scale(10) == Quantity(1230, "foo")
def test_scale(self) -> None: assert Quantity(123, "foo").scale(10) == Quantity(1230, "foo")
(SVS("spam"), )), 0), None), # Can't name sub recipes (they're already named!) (SubRecipe(Ingredient(SVS("spam")), (SVS("spam"), )), None), ], ) def test_infer_output_name(recipe_tree: RecipeTreeNode, exp: Optional[SVS]) -> None: assert infer_output_name(recipe_tree) == exp @pytest.mark.parametrize( "recipe_tree, exp", [ # Workable cases (Ingredient(SVS("spam")), None), (Ingredient(SVS("spam"), Quantity(100)), Quantity(100)), (Ingredient(SVS("spam"), Quantity(100, "g")), Quantity(100, "g")), (Step(SVS("fry"), (Ingredient(SVS("spam"), Quantity(100)), )), Quantity(100)), ( SubRecipe(Ingredient(SVS("spam"), Quantity(100)), (SVS("out"), )), Quantity(100), ), # Can't name step with several inputs ( Step( SVS("fry"), ( Ingredient(SVS("spam"), Quantity(100)), Ingredient(SVS("eggs"), Quantity(2)), ),
# Fraction (Fraction(2, 3), "<sup>2</sup>⁄<sub>3</sub>"), # Improper fraction (Fraction(5, 3), "1 <sup>2</sup>⁄<sub>3</sub>"), ], ) def test_render_number(number: Union[float, int, Fraction], exp: str) -> None: assert render_number(number) == exp @pytest.mark.parametrize( "quantity, exp", [ # Unitless ( Quantity(123), '<span class="rg-quantity-unitless rg-scaled-value">123</span>', ), # Unitless with preposition ( Quantity(123, preposition=" of>"), '<span class="rg-quantity-unitless rg-scaled-value">123</span> of>', ), # Custom (unknown) unit with HTML char in ( Quantity(123, "<foo>"), '<span class="rg-quantity-without-conversions rg-scaled-value">123<foo></span>', ), # Custom unit with spacing and preposition ( Quantity(
def test_has_equal_value_to(self, a: Quantity, b: Quantity, exp: bool) -> None: assert a.has_equal_value_to(b) is exp