Back to Repositories

Testing Command-Line UI Interactions in thefuck

This test suite validates the user interface functionality of the ‘thefuck’ command line tool, focusing on command selection and user interaction features. The tests ensure proper handling of keyboard inputs, command navigation, and confirmation workflows.

Test Coverage Overview

The test suite provides comprehensive coverage of UI interactions and command selection logic.

Key areas tested include:
  • Keyboard input handling (Enter, Up/Down arrows, Ctrl+C)
  • Command selection and navigation
  • Confirmation dialogs and side effects
  • Edge cases like empty command lists

Implementation Analysis

The testing approach uses pytest fixtures and mocking to simulate user inputs and system interactions. The implementation follows a behavior-driven pattern with clear test case organization and separation of concerns.

Notable patterns include:
  • Fixture-based test setup
  • Monkeypatch usage for input simulation
  • Class-based test organization
  • Iterative command processing validation

Technical Details

Testing tools and configuration:
  • pytest as the primary testing framework
  • Custom fixtures for input simulation
  • Capsys for capturing system output
  • Monkeypatch for function mocking
  • UTF-8 encoding specification

Best Practices Demonstrated

The test suite exemplifies several testing best practices and quality standards.

Notable practices include:
  • Isolated test cases with clear setup
  • Comprehensive edge case coverage
  • Effective use of pytest fixtures
  • Clean separation of test concerns
  • Thorough output validation

nvbn/thefuck

tests/test_ui.py

            
# -*- encoding: utf-8 -*-

import pytest
from itertools import islice
from thefuck import ui
from thefuck.types import CorrectedCommand
from thefuck import const


@pytest.fixture
def patch_get_key(monkeypatch):
    def patch(vals):
        vals = iter(vals)
        monkeypatch.setattr('thefuck.ui.get_key', lambda: next(vals))

    return patch


def test_read_actions(patch_get_key):
    patch_get_key([
        # Enter:
        '
',
        # Enter:
        '\r',
        # Ignored:
        'x', 'y',
        # Up:
        const.KEY_UP, 'k',
        # Down:
        const.KEY_DOWN, 'j',
        # Ctrl+C:
        const.KEY_CTRL_C, 'q'])
    assert (list(islice(ui.read_actions(), 8))
            == [const.ACTION_SELECT, const.ACTION_SELECT,
                const.ACTION_PREVIOUS, const.ACTION_PREVIOUS,
                const.ACTION_NEXT, const.ACTION_NEXT,
                const.ACTION_ABORT, const.ACTION_ABORT])


def test_command_selector():
    selector = ui.CommandSelector(iter([1, 2, 3]))
    assert selector.value == 1
    selector.next()
    assert selector.value == 2
    selector.next()
    assert selector.value == 3
    selector.next()
    assert selector.value == 1
    selector.previous()
    assert selector.value == 3


@pytest.mark.usefixtures('no_colors')
class TestSelectCommand(object):
    @pytest.fixture
    def commands_with_side_effect(self):
        return [CorrectedCommand('ls', lambda *_: None, 100),
                CorrectedCommand('cd', lambda *_: None, 100)]

    @pytest.fixture
    def commands(self):
        return [CorrectedCommand('ls', None, 100),
                CorrectedCommand('cd', None, 100)]

    def test_without_commands(self, capsys):
        assert ui.select_command(iter([])) is None
        assert capsys.readouterr() == ('', 'No fucks given
')

    def test_without_confirmation(self, capsys, commands, settings):
        settings.require_confirmation = False
        assert ui.select_command(iter(commands)) == commands[0]
        assert capsys.readouterr() == ('', const.USER_COMMAND_MARK + 'ls
')

    def test_without_confirmation_with_side_effects(
            self, capsys, commands_with_side_effect, settings):
        settings.require_confirmation = False
        assert (ui.select_command(iter(commands_with_side_effect))
                == commands_with_side_effect[0])
        assert capsys.readouterr() == ('', const.USER_COMMAND_MARK + 'ls (+side effect)
')

    def test_with_confirmation(self, capsys, patch_get_key, commands):
        patch_get_key(['
'])
        assert ui.select_command(iter(commands)) == commands[0]
        assert capsys.readouterr() == (
            '', const.USER_COMMAND_MARK + u'\x1b[1K\rls [enter/↑/↓/ctrl+c]
')

    def test_with_confirmation_abort(self, capsys, patch_get_key, commands):
        patch_get_key([const.KEY_CTRL_C])
        assert ui.select_command(iter(commands)) is None
        assert capsys.readouterr() == (
            '', const.USER_COMMAND_MARK + u'\x1b[1K\rls [enter/↑/↓/ctrl+c]
Aborted
')

    def test_with_confirmation_with_side_effct(self, capsys, patch_get_key,
                                               commands_with_side_effect):
        patch_get_key(['
'])
        assert (ui.select_command(iter(commands_with_side_effect))
                == commands_with_side_effect[0])
        assert capsys.readouterr() == (
            '', const.USER_COMMAND_MARK + u'\x1b[1K\rls (+side effect) [enter/↑/↓/ctrl+c]
')

    def test_with_confirmation_select_second(self, capsys, patch_get_key, commands):
        patch_get_key([const.KEY_DOWN, '
'])
        assert ui.select_command(iter(commands)) == commands[1]
        stderr = (
            u'{mark}\x1b[1K\rls [enter/↑/↓/ctrl+c]'
            u'{mark}\x1b[1K\rcd [enter/↑/↓/ctrl+c]
'
        ).format(mark=const.USER_COMMAND_MARK)
        assert capsys.readouterr() == ('', stderr)