""" Tests for the core functionality of the PyperCard project. """ import os import pytest from unittest import mock from pypercard.core import Card, CardApp, Inputs, palette from kivy.uix.screenmanager import Screen, ScreenManager def test_palette_colour_name(): """ If given a known colour name (e.g. "red"), return a tuple representing the Kivy colour. """ result = palette("red") assert isinstance(result, tuple) def test_palette_colour_name_capitalised(): """ If given a colour name (e.g. "RED"), return a tuple representing the Kivy colour. """ result = palette("RED") assert isinstance(result, tuple) def test_palette_raw_hex(): """ If given a raw hex value as a string (e.g. "0xFFCC99"), return a tuple representing a Kivy colour. """ result = palette("0xFFFFFF") assert result == (1.0, 1.0, 1.0) def test_palette_html_hex(): """ If given an HTML hex value as a string (e.g. "#FFCC99"), return a tuple representing a Kivy colour. """ result = palette("#FFFFFF") assert result == (1.0, 1.0, 1.0) def test_palette_no_such_colour(): """ If given an unknown colour or value that can't be converted from HEX then raise a ValueError. """ with pytest.raises(ValueError): palette("foobarbaz") def test_card_init(): """ Ensure that the arguments passed into the __init__ are properly "processed" and assigned to the instance attributes. """ with mock.patch("pypercard.core.Card._verify") as mock_verify: title = "the card's unique title" text = "this is some textual content for the card." text_color = None # Should default to white. text_size = 32 form = Inputs.MULTICHOICE options = ("foo", "bar", "baz") sound = "boop.wav" sound_repeat = True background = "blue" buttons = [{"label": "Button1", "target": "Another Card"}] auto_advance = 1.234 auto_target = "Another Card" card = Card( title=title, text=text, text_color=text_color, text_size=text_size, form=form, options=options, sound=sound, sound_repeat=sound_repeat, background=background, buttons=buttons, auto_advance=auto_advance, auto_target=auto_target, ) assert card.title == title assert card.text == text assert card.text_color == (1.0, 1.0, 1.0, 1.0) # defaults to white. assert card.text_size == text_size assert card.form == form assert card.options == options assert card.sound == sound assert card.sound_repeat == sound_repeat assert card.background == (0.0, 0.0, 1.0, 1.0) # use colour from name. assert card.buttons == buttons assert card.auto_advance == auto_advance assert card.auto_target == auto_target mock_verify.assert_called_once_with() def test_card_init_text_colour_background_path_no_buttons(): """ If the background argument is not a colour, then retain it as it should be a path to an image file. """ with mock.patch("pypercard.core.Card._verify") as mock_verify: title = "the card's unique title" text = "this is some textual content for the card." text_color = "red" text_size = 32 form = Inputs.MULTICHOICE options = ("foo", "bar", "baz") sound = "boop.wav" sound_repeat = True background = "filename.bmp" buttons = None auto_advance = 1.234 auto_target = "Another Card" card = Card( title=title, text=text, text_color=text_color, text_size=text_size, form=form, options=options, sound=sound, sound_repeat=sound_repeat, background=background, buttons=buttons, auto_advance=auto_advance, auto_target=auto_target, ) assert card.title == title assert card.text == text assert card.text_color == (1.0, 0.0, 0.0, 1.0) # now a Kivy colour. assert card.text_size == text_size assert card.form == form assert card.options == options assert card.sound == sound assert card.sound_repeat == sound_repeat assert card.background == "filename.bmp" # remains a file path. assert card.buttons == [] # an empty list of buttons. assert card.auto_advance == auto_advance assert card.auto_target == auto_target mock_verify.assert_called_once_with() def test_card_verify_form_no_text(): """ If there is a form, there must also be text (to act as the form's label / instructions). """ title = "title" form = Inputs.TEXTBOX with pytest.raises(ValueError): Card(title=title, form=form) # Check the good case. Card(title=title, form=form, text="Label for the form.") def test_card_verify_multichoice_options(): """ If the form is a multi-choice picker, there must be a list of options to select. """ title = "title" text = "Instructions for the form." form = Inputs.MULTICHOICE with pytest.raises(ValueError): Card(title=title, text=text, form=form) def test_card_verify_multichoice_options_not_strings(): """ If the form is a multi-choice picker, the available options MUST be strings. """ title = "title" text = "Instructions for the form." form = Inputs.MULTICHOICE options = ("foo", "bar", 123) with pytest.raises(ValueError): Card(title=title, text=text, form=form, options=options) # Check the good case. options = ("foo", "bar", "baz") Card(title=title, text=text, form=form, options=options) def test_card_verify_select_options(): """ If the form is a single item selector, there must be a list of options from which to choose an item. """ title = "title" text = "Instructions for the form." form = Inputs.SELECT with pytest.raises(ValueError): Card(title=title, text=text, form=form) def test_card_verify_select_options_not_strings(): """ If the form is a single item selector, the available options MUST be strings. """ title = "title" text = "Instructions for the form." form = Inputs.SELECT options = ("foo", "bar", 123) with pytest.raises(ValueError): Card(title=title, text=text, form=form, options=options) # Check the good case. options = ("foo", "bar", "baz") Card(title=title, text=text, form=form, options=options) def test_card_verify_slider_options(): """ If the form is a slider, there must be an options list containing the min, max and (optional) step values. """ title = "title" text = "Instructions for the form." form = Inputs.SLIDER with pytest.raises(ValueError): Card(title=title, text=text, form=form) def test_card_verify_slider_options_size(): """ If the form is a slider, the options must contain the correct number of items (at least two, no more than three). """ title = "title" text = "Instructions for the form." form = Inputs.SLIDER options = (1,) with pytest.raises(ValueError): Card(title=title, text=text, form=form, options=options) options = (1, 2, 3, 4) with pytest.raises(ValueError): Card(title=title, text=text, form=form, options=options) # Check the good case. options = (1, 100, 10) Card(title=title, text=text, form=form, options=options) def test_card_verify_slider_options_numeric(): """ If the form is a slider, the options must contain values that are numeric. """ title = "title" text = "Instructions for the form." form = Inputs.SLIDER options = (1, 100, "hello") with pytest.raises(ValueError): Card(title=title, text=text, form=form, options=options) options = (0.1, 0.9, "hello") with pytest.raises(ValueError): Card(title=title, text=text, form=form, options=options) # Check the good case. options = (0.1, 1) Card(title=title, text=text, form=form, options=options) def test_card_verify_buttons_not_dict(): """ The definitions of buttons must be expressed as dictionaries. """ title = "title" buttons = ["not a dictionary"] with pytest.raises(ValueError): Card(title=title, buttons=buttons) def test_card_verify_buttons_missing_attributes(): """ Each dictinary definition of a button MUST contain a label and target attribute. """ title = "title" buttons = [{"label": "Hello"}] with pytest.raises(ValueError): Card(title=title, buttons=buttons) buttons = [{"target": "Target Card"}] with pytest.raises(ValueError): Card(title=title, buttons=buttons) # Check the good case. buttons = [{"label": "Hello", "target": "Target Card"}] Card(title=title, buttons=buttons) def test_card_verify_buttons_attribute_values(): """ Each attribute of the button must be of a certain type: * labels must be strings. * targets must be either strings or callable. """ title = "title" buttons = [{"label": 42, "target": "Target Card"}] with pytest.raises(ValueError): Card(title=title, buttons=buttons) title = "title" buttons = [{"label": "Hello", "target": True}] with pytest.raises(ValueError): Card(title=title, buttons=buttons) # Check the good case. buttons = [{"label": "Hello", "target": lambda x: x}] Card(title=title, buttons=buttons) def test_card_verify_auto_advance(): """ If the auto_advance value is set, there MUST be an auto_target. """ with pytest.raises(ValueError): Card(title="title", auto_advance=1.0) # Check the good case. Card(title="title", auto_advance=1.0, auto_target="Another card") def test_card_screen_empty(): """ Ensure a relatively empty card results in the expected Screen object and the passed in ScreenManager instance and data_store is set for the card. """ mock_screen_manager = mock.MagicMock() data_store = {"foo": "bar"} card = Card("title") result = card.screen(mock_screen_manager, data_store) assert card.screen_manager == mock_screen_manager assert card.data_store == data_store assert isinstance(result, Screen) def test_card_screen_with_form(): """ A card with a form has the expected _draw_form method called to paint it onto the screen. """ mock_screen_manager = mock.MagicMock() data_store = {"foo": "bar"} card = Card("title", form=Inputs.TEXTBOX, text="Form instructions...") card._draw_form = mock.MagicMock() card.screen(mock_screen_manager, data_store) card._draw_form.assert_called_once_with() def test_card_screen_with_text_only(): """ If the card has only textual content, ensure the _draw_text method is called to paint it onto the screen. """ mock_screen_manager = mock.MagicMock() data_store = {"foo": "bar"} card = Card("title", text="Textual content...") card._draw_text = mock.MagicMock() card.screen(mock_screen_manager, data_store) card._draw_text.assert_called_once_with() def test_card_screen_with_sound(): """ Ensure that if a sound is associated with a card, that it is instantiated and configured correctly as part of the screen generation process. """ mock_screen_manager = mock.MagicMock() data_store = {"foo": "bar"} card = Card("title", sound="music.wav", sound_repeat=True) mock_loader = mock.MagicMock() with mock.patch("pypercard.core.SoundLoader.load", mock_loader): card.screen(mock_screen_manager, data_store) mock_loader.assert_called_once_with("music.wav") mock_player = mock_loader() assert mock_player.loop is True assert card.player == mock_player def test_card_screen_with_background_colour(): """ If a background colour is set for the card, ensure this is correctly configured for the layout associated with the card's screen. """ mock_screen_manager = mock.MagicMock() data_store = {"foo": "bar"} card = Card("title", background="red") mock_layout = mock.MagicMock() with mock.patch( "pypercard.core.BoxLayout", return_value=mock_layout ), mock.patch("pypercard.core.Screen"), mock.patch( "pypercard.core.Color" ) as mock_colour, mock.patch( "pypercard.core.Rectangle" ) as mock_rectangle: card.screen(mock_screen_manager, data_store) mock_layout.bind.assert_called_once_with( size=card._update_rect, pos=card._update_rect ) mock_colour.assert_called_once_with(1.0, 0.0, 0.0, 1.0) # "red" mock_rectangle.assert_called_once_with( size=mock_layout.size, pos=mock_layout.pos ) assert card.rect == mock_rectangle() def test_card_screen_with_background_image(): """ If a background image is set for the card, ensure this is correctly configured for the layout associated with the card's screen. """ mock_screen_manager = mock.MagicMock() data_store = {"foo": "bar"} card = Card("title", background="image.png") mock_layout = mock.MagicMock() with mock.patch( "pypercard.core.BoxLayout", return_value=mock_layout ), mock.patch("pypercard.core.Screen"), mock.patch( "pypercard.core.Rectangle" ) as mock_rectangle: card.screen(mock_screen_manager, data_store) mock_rectangle.assert_called_once_with( source="image.png", size=mock_layout.size, pos=mock_layout.pos ) def test_card_screen_with_buttons(): """ If buttons are configured for the card, ensure the expected _draw_buttons method is called to paint the buttons onto the screen for the card. """ mock_screen_manager = mock.MagicMock() data_store = {"foo": "bar"} card = Card( "title", buttons=[{"label": "A Button", "target": "AnotherButton"}] ) card._draw_buttons = mock.MagicMock() card.screen(mock_screen_manager, data_store) card._draw_buttons.assert_called_once_with() def test_card_draw_text(): """ Ensure the expected Label object is added to the screen's layout instance. """ card = Card("title", text="This is some text for the label...") card.layout = mock.MagicMock() card.font_size = "48sp" card._draw_text() card.layout.add_widget.assert_called_once_with(card.text_label) assert card.text_label.text == "This is some text for the label..." assert card.text_label.font_size == 48.0 assert card.text_label.markup is True assert card.text_label.color == [1.0, 1.0, 1.0, 1.0] assert card.text_label.padding == [10, 10] assert card.text_label.valign == "middle" assert card.text_label.halign == "center" def test_card_draw_form_textbox(): """ Ensure the expected form widget and associated label are added to the screen's layout instance. """ card = Card("title", form=Inputs.TEXTBOX, text="Form label...") card.layout = mock.MagicMock() card.font_size = "48sp" card._draw_form() assert card.form_label.text == "Form label..." assert card.form_label.font_size == 48.0 assert card.form_label.markup is True assert card.form_label.color == [1.0, 1.0, 1.0, 1.0] assert card.form_label.valign == "top" assert card.form_label.halign == "left" assert card.textbox.text == "" assert card.textbox.multiline is False assert card.textbox.font_size == 48.0 def test_card_draw_form_textarea(): """ Ensure the expected form widget and associated label are added to the screen's layout instance. """ card = Card("title", form=Inputs.TEXTAREA, text="Form label...") card.layout = mock.MagicMock() card.font_size = "48sp" card._draw_form() assert card.form_label.text == "Form label..." assert card.form_label.font_size == 48.0 assert card.form_label.markup is True assert card.form_label.color == [1.0, 1.0, 1.0, 1.0] assert card.form_label.valign == "top" assert card.form_label.halign == "left" assert card.textarea.text == "" assert card.textarea.multiline is True assert card.textarea.font_size == 48.0 def test_card_draw_form_multichoice(): """ Ensure the expected form widget and associated label are added to the screen's layout instance. """ card = Card( "title", form=Inputs.MULTICHOICE, text="Form label...", options=["foo", "bar", "baz"], ) card.layout = mock.MagicMock() card.font_size = "48sp" card._draw_form() assert card.form_label.text == "Form label..." assert card.form_label.font_size == 48.0 assert card.form_label.markup is True assert card.form_label.color == [1.0, 1.0, 1.0, 1.0] assert card.form_label.valign == "top" assert card.form_label.halign == "left" assert len(card.multichoice) == 3 assert card.multichoice[0].text == "foo" assert card.multichoice[0].group is None assert card.multichoice[1].text == "bar" assert card.multichoice[1].group is None assert card.multichoice[2].text == "baz" assert card.multichoice[2].group is None def test_card_draw_form_select(): """ Ensure the expected form widget and associated label are added to the screen's layout instance. """ card = Card( "title", form=Inputs.SELECT, text="Form label...", options=["foo", "bar", "baz"], ) card.layout = mock.MagicMock() card.font_size = "48sp" card._draw_form() assert card.form_label.text == "Form label..." assert card.form_label.font_size == 48.0 assert card.form_label.markup is True assert card.form_label.color == [1.0, 1.0, 1.0, 1.0] assert card.form_label.valign == "top" assert card.form_label.halign == "left" assert len(card.select) == 3 assert card.select[0].text == "foo" assert card.select[0].group == "title" assert card.select[1].text == "bar" assert card.select[1].group == "title" assert card.select[2].text == "baz" assert card.select[2].group == "title" def test_card_draw_form_slider_with_step(): """ Ensure the expected form widget and associated label are added to the screen's layout instance. """ card = Card( "title", form=Inputs.SLIDER, text="Form label...", options=(1, 100, 10) ) card.layout = mock.MagicMock() card.font_size = "48sp" card._draw_form() assert card.form_label.text == "Form label..." assert card.form_label.font_size == 48.0 assert card.form_label.markup is True assert card.form_label.color == [1.0, 1.0, 1.0, 1.0] assert card.form_label.valign == "top" assert card.form_label.halign == "left" assert card.slider_label.text == "0" assert card.slider_label.font_size == 64.0 assert card.slider.min == 1 assert card.slider.max == 100 assert card.slider.step == 10 assert card.slider.value_track is True def test_card_draw_form_slider_default_step(): """ Ensure the expected form widget and associated label are added to the screen's layout instance. """ card = Card( "title", form=Inputs.SLIDER, text="Form label...", options=(1, 100) ) card.layout = mock.MagicMock() card.font_size = "48sp" card._draw_form() assert card.form_label.text == "Form label..." assert card.form_label.font_size == 48.0 assert card.form_label.markup is True assert card.form_label.color == [1.0, 1.0, 1.0, 1.0] assert card.form_label.valign == "top" assert card.form_label.halign == "left" assert card.slider_label.text == "0" assert card.slider_label.font_size == 64.0 assert card.slider.min == 1 assert card.slider.max == 100 assert card.slider.step == 1 assert card.slider.value_track is True def test_card_draw_buttons(): """ Ensure the expected buttons are created and linked to an event handler. """ card = Card( "title", buttons=[{"label": "Button1", "target": "AnotherCard"}] ) card.layout = mock.MagicMock() card._button_click = mock.MagicMock() card._draw_buttons() assert len(card.button_widgets) == 1 assert card.button_widgets[0].text == "Button1" assert card.button_widgets[0].color == [1.0, 1.0, 1.0, 1.0] # white. assert card.button_widgets[0].background_color == [ 0.7450980392156863, 0.7450980392156863, 0.7450980392156863, 1.0, ] # grey. assert card.button_widgets[0].font_size == 24.0 card._button_click.assert_called_once_with("AnotherCard") assert card.layout.add_widget.call_count == 1 def test_card_draw_buttons_custom_size_colours(): """ Ensure that customisations to the buttons text size, text colour and background colour are set as expected. """ card = Card( "title", buttons=[ { "label": "Button1", "target": "AnotherCard", "text_size": 32, "text_color": "red", "background_color": "blue", } ], ) card.layout = mock.MagicMock() card._button_click = mock.MagicMock() card._draw_buttons() assert len(card.button_widgets) == 1 assert card.button_widgets[0].text == "Button1" assert card.button_widgets[0].font_size == 32.0 assert card.button_widgets[0].color == [1.0, 0.0, 0.0, 1.0] assert card.button_widgets[0].background_color == [0.0, 0.0, 1.0, 1.0] card._button_click.assert_called_once_with("AnotherCard") assert card.layout.add_widget.call_count == 1 def test_card_form_value_no_form(): """ Must return None if there is no form associated with the card. """ card = Card("title") assert card.form_value() is None def test_card_form_value_textbox(): """ Return the current value of the textbox widget. """ card = Card("title", form=Inputs.TEXTBOX, text="Form label...") mock_screen_manager = mock.MagicMock() data_store = {} card.screen(mock_screen_manager, data_store) card.textbox.text = "foobarbaz" assert card.form_value() == "foobarbaz" def test_card_form_value_textarea(): """ Return the current value of the textarea widget. """ card = Card("title", form=Inputs.TEXTAREA, text="Form label...") mock_screen_manager = mock.MagicMock() data_store = {} card.screen(mock_screen_manager, data_store) card.textarea.text = "foobarbaz" assert card.form_value() == "foobarbaz" def test_card_form_value_multichoice(): """ Return the current value of the multichoice widget. """ card = Card( "title", form=Inputs.MULTICHOICE, text="Form label...", options=["foo", "bar", "baz"], ) mock_screen_manager = mock.MagicMock() data_store = {} card.screen(mock_screen_manager, data_store) card.multichoice[0].state = "down" card.multichoice[2].state = "down" assert card.form_value() == ["foo", "baz"] def test_card_form_value_select(): """ Return the current value of the select widget. """ card = Card( "title", form=Inputs.SELECT, text="Form label...", options=["foo", "bar", "baz"], ) mock_screen_manager = mock.MagicMock() data_store = {} card.screen(mock_screen_manager, data_store) card.select[0].state = "down" assert card.form_value() == "foo" def test_card_form_value_select_nothing(): """ Return the current value of the select widget, if none are selected (should return None). """ card = Card( "title", form=Inputs.SELECT, text="Form label...", options=["foo", "bar", "baz"], ) mock_screen_manager = mock.MagicMock() data_store = {} card.screen(mock_screen_manager, data_store) assert card.form_value() is None def test_card_form_value_slider(): """ Return the current value of the slider widget. """ card = Card( "title", form=Inputs.SLIDER, text="Form label...", options=[1, 100] ) mock_screen_manager = mock.MagicMock() data_store = {} card.screen(mock_screen_manager, data_store) card.slider_label.text = "50" assert card.form_value() == 50.0 def test_card_pre_enter_with_form(): """ The _pre_enter method is called before the card is displayed to the user. Ensure that all textual content is updated with a formatted string with the data_store as potential values. """ card = Card("title") card.text = "Hello {foo}" card.form = Inputs.TEXTBOX card.form_label = mock.MagicMock() card.buttons = [{"label": "Hello {foo}", "target": "AnotherCard"}] card.screen(mock.MagicMock(), {"foo": "world"}) card._pre_enter(card) assert card.form_label.text == "Hello world" assert card.button_widgets[0].text == "Hello world" def test_card_pre_enter_with_text(): """ The _pre_enter method is called before the card is displayed to the user. Ensure that all textual content is updated with a formatted string with the data_store as potential values. """ card = Card("title") card.text = "Hello {foo}" card.text_label = mock.MagicMock() card.buttons = [{"label": "Hello {foo}", "target": "AnotherCard"}] card.screen(mock.MagicMock(), {"foo": "world"}) card._pre_enter(card) assert card.text_label.text == "Hello world" assert card.button_widgets[0].text == "Hello world" def test_card_enter(): """ The _enter method is called when the card is displayed to the user. Ensure that the sound player starts from position zero and the auto advance is scheduled. """ card = Card("title") card.player = mock.MagicMock() card.auto_advance = 1.5 with mock.patch("pypercard.core.Clock") as mock_clock: card._enter(card) mock_clock.schedule_once.assert_called_once_with( card._next_card, card.auto_advance ) assert card.auto_event == mock_clock.schedule_once() card.player.play.assert_called_once_with() card.player.seek.assert_called_once_with(0) def test_card_leave(): """ The _leave method is called when the card is hidden from the user. Ensure that the sound player is stopped. """ card = Card("title") card.player = mock.MagicMock() mock_auto_event = mock.MagicMock() card.auto_event = mock_auto_event card._leave(card) card.player.stop.assert_called_once_with() mock_auto_event.cancel.assert_called_once_with() assert card.auto_event is None def test_card_update_rect(): """ Called whenever the application size changes to ensure that the rectangle that contains the screen to be drawn is also updated in size (so the background colour / image is updated, if required). """ card = Card("title") card.rect = mock.MagicMock() instance = mock.MagicMock() instance.pos = 400 instance.size = 500 card._update_rect(instance, 100) assert card.rect.pos == instance.pos assert card.rect.size == instance.size def test_card_button_click_with_callable(): """ Ensure the function returned from the _button_click method works by transitioning the screen manager to the string value it returns. """ def fn(data_store, form_value): return "AnotherCard" card = Card("title") card.data_store = {} card.form_value = mock.MagicMock(return_value=None) card.screen_manager = mock.MagicMock() handler = card._button_click(fn) assert callable(handler) handler(None) assert card.screen_manager.current == "AnotherCard" def test_card_button_click_with_string(): """ Ensure the function returned from the _button_click method works by transitioning the screen manager to the string value it returns. """ card = Card("title") card.data_store = {} card.form_value = mock.MagicMock(return_value=None) card.screen_manager = mock.MagicMock() handler = card._button_click("AnotherCard") assert callable(handler) handler(None) assert card.screen_manager.current == "AnotherCard" def test_card_slider_change(): """ Ensure this event handler updates the slider_label with the passed in value. """ card = Card("title") card.slider_label = mock.MagicMock() card._slider_change(None, 10) assert card.slider_label.text == "10.0" def test_card_next_card_with_callable(): """ Ensure the string return value of the function set as the auto_target is used to transition the screen manager. """ def fn(data_store, form_value): return "AnotherCard" card = Card("title") card.data_store = {} card.form_value = mock.MagicMock(return_value=None) card.auto_target = fn card.screen_manager = mock.MagicMock() card._next_card(0.1) assert card.screen_manager.current == "AnotherCard" def test_card_next_card_with_string(): """ Ensure the string value set as the auto_target is used to transition the screen manager. """ card = Card("title") card.data_store = {} card.form_value = mock.MagicMock(return_value=None) card.auto_target = "AnotherCard" card.screen_manager = mock.MagicMock() card._next_card(0.1) assert card.screen_manager.current == "AnotherCard" def test_cardapp_init_no_data_store(): """ Ensure the CardApp instance is set up with the expected defaults. """ app = CardApp() assert app.data_store == {} assert app.cards == {} assert isinstance(app.screen_manager, ScreenManager) assert app.title == "A PyperCard Application :-)" def test_cardapp_init_title_datastore_and_stack(): """ Ensure the CardApp instance is set up with the expected user defined values. """ stack = [Card("test")] app = CardApp("An App", {"foo": "bar"}, stack) assert app.data_store == {"foo": "bar"} assert app.cards == {"test": stack[0]} assert isinstance(app.screen_manager, ScreenManager) assert app.title == "An App" def test_cardapp_add_card(): """ The referenced card should be added to the app's screen_manager instance and the cards dictionary. """ app = CardApp() app.screen_manager = mock.MagicMock() card = Card("title") card.screen = mock.MagicMock() app.add_card(card) assert app.cards["title"] == card app.screen_manager.add_widget.assert_called_once_with( card.screen(None, None) ) def test_cardapp_add_card_title_collision(): """ If the new card's title attribute is already taken, then the application should raise a ValueError. """ app = CardApp() app.screen_manager = mock.MagicMock() card = Card("title") card.screen = mock.MagicMock() app.add_card(card) new_card = Card("title") with pytest.raises(ValueError): app.add_card(new_card) def test_cardapp_load(): """ Ensure the referenced JSON file is used to instantiate a card and add it to the application. """ current_dir = os.path.dirname(os.path.realpath(__file__)) path = os.path.abspath(os.path.join(current_dir, "test_stack.json")) app = CardApp() app.add_card = mock.MagicMock() app.load(path) assert app.add_card.call_count == 2 def test_cardapp_build(): """ Ensure the screen_manager instance is returned to display as the application. """ app = CardApp() assert isinstance(app.build(), ScreenManager)