Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 32 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,37 @@
# python cache files
# Python cache files
.mypy_cache/
.pytest_cache/
.venv/
__pycache__/
*.pyc

# Testing and coverage
.coverage
htmlcov/
coverage.xml
*.cover
*.py,cover
.hypothesis/

# Claude
.claude/*

# Build artifacts
dist/
build/
*.egg-info/
*.egg

# Virtual environments
venv/
env/
ENV/
virtualenv/

# IDE files
.vscode/
.idea/
*.swp
*.swo
*~
.DS_Store
282 changes: 282 additions & 0 deletions poetry.lock

Large diffs are not rendered by default.

88 changes: 88 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
[tool.poetry]
name = "ansi-color-build"
version = "0.1.0"
description = "ANSI color build output plugin for Sublime Text"
authors = ["Your Name <you@example.com>"]
readme = "README.md"
packages = [{include = "*.py"}]

[tool.poetry.dependencies]
python = "^3.8"

[tool.poetry.group.dev.dependencies]
pytest = "^8.3.3"
pytest-cov = "^5.0.0"
pytest-mock = "^3.14.0"

[tool.poetry.scripts]
test = "pytest:main"
tests = "pytest:main"

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py", "*_test.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
addopts = [
"--strict-markers",
"--verbose",
"--cov=.",
"--cov-report=term-missing",
"--cov-report=html",
"--cov-report=xml",
"--cov-fail-under=80",
]
markers = [
"unit: Unit tests",
"integration: Integration tests",
"slow: Slow running tests",
]
filterwarnings = [
"error",
"ignore::UserWarning",
"ignore::DeprecationWarning",
]

[tool.coverage.run]
source = ["."]
omit = [
"*/tests/*",
"*/__pycache__/*",
"*/venv/*",
"*/virtualenv/*",
"*/.venv/*",
"*/dist/*",
"*/build/*",
"*.egg-info/*",
"setup.py",
"conftest.py",
"test_*.py",
"*_test.py",
]

[tool.coverage.report]
exclude_lines = [
"pragma: no cover",
"def __repr__",
"if self.debug:",
"if settings.DEBUG",
"raise AssertionError",
"raise NotImplementedError",
"if 0:",
"if __name__ == .__main__.:",
"class .*\\bProtocol\\):",
"@(abc\\.)?abstractmethod",
]
precision = 2
show_missing = true
skip_covered = false

[tool.coverage.html]
directory = "htmlcov"

[tool.coverage.xml]
output = "coverage.xml"
Empty file added tests/__init__.py
Empty file.
220 changes: 220 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
"""Shared pytest fixtures and configuration for all tests."""

import os
import sys
import tempfile
from pathlib import Path
from typing import Generator
from unittest.mock import MagicMock

import pytest


@pytest.fixture
def temp_dir() -> Generator[Path, None, None]:
"""Provide a temporary directory that is automatically cleaned up."""
with tempfile.TemporaryDirectory() as tmp_dir:
yield Path(tmp_dir)


@pytest.fixture
def temp_file(temp_dir: Path) -> Generator[Path, None, None]:
"""Provide a temporary file path in the temp directory."""
temp_path = temp_dir / "test_file.txt"
temp_path.touch()
yield temp_path


@pytest.fixture
def mock_sublime():
"""Mock the sublime module for testing Sublime Text plugins."""
sublime = MagicMock()
sublime.HOVER_TEXT = 1
sublime.HOVER_GUTTER = 2
sublime.HOVER_MARGIN = 3
sublime.ENCODED_POSITION = 1
sublime.TRANSIENT = 4
sublime.FORCE_GROUP = 8
sublime.IGNORECASE = 2
sublime.LITERAL = 1
sublime.MONOSPACE_FONT = 1
sublime.KEEP_OPEN_ON_FOCUS_LOST = 2
sublime.HTML = 1
sublime.COOPERATE_WITH_AUTO_COMPLETE = 2
sublime.HIDE_ON_MOUSE_MOVE = 4
sublime.HIDE_ON_MOUSE_MOVE_AWAY = 8
sublime.KEEP_ON_SELECTION_MODIFIED = 16
sublime.HIDE_ON_CHARACTER_EVENT = 32

# Common sublime methods
sublime.status_message = MagicMock()
sublime.error_message = MagicMock()
sublime.message_dialog = MagicMock()
sublime.ok_cancel_dialog = MagicMock(return_value=True)
sublime.load_settings = MagicMock()
sublime.save_settings = MagicMock()
sublime.active_window = MagicMock()
sublime.windows = MagicMock(return_value=[])
sublime.run_command = MagicMock()
sublime.set_timeout = MagicMock()
sublime.set_timeout_async = MagicMock()

return sublime


@pytest.fixture
def mock_sublime_plugin():
"""Mock the sublime_plugin module for testing Sublime Text plugins."""
sublime_plugin = MagicMock()

# Base classes
class MockTextCommand:
def __init__(self, view):
self.view = view

def run(self, edit, **kwargs):
pass

def is_enabled(self, **kwargs):
return True

def is_visible(self, **kwargs):
return True

def description(self):
return None

class MockWindowCommand:
def __init__(self, window):
self.window = window

def run(self, **kwargs):
pass

def is_enabled(self, **kwargs):
return True

def is_visible(self, **kwargs):
return True

class MockEventListener:
pass

sublime_plugin.TextCommand = MockTextCommand
sublime_plugin.WindowCommand = MockWindowCommand
sublime_plugin.EventListener = MockEventListener

return sublime_plugin


@pytest.fixture
def mock_view():
"""Mock a Sublime Text view object."""
view = MagicMock()
view.id.return_value = 1
view.buffer_id.return_value = 1
view.file_name.return_value = "/test/file.py"
view.is_dirty.return_value = False
view.is_read_only.return_value = False
view.is_scratch.return_value = False
view.size.return_value = 100
view.sel.return_value = []
view.settings.return_value = MagicMock()
view.window.return_value = MagicMock()

return view


@pytest.fixture
def mock_window():
"""Mock a Sublime Text window object."""
window = MagicMock()
window.id.return_value = 1
window.active_view.return_value = MagicMock()
window.views.return_value = []
window.num_groups.return_value = 1
window.active_group.return_value = 0
window.focus_view = MagicMock()
window.focus_group = MagicMock()
window.run_command = MagicMock()
window.new_file = MagicMock()
window.open_file = MagicMock()
window.find_open_file = MagicMock(return_value=None)
window.show_input_panel = MagicMock()
window.show_quick_panel = MagicMock()
window.create_output_panel = MagicMock()
window.destroy_output_panel = MagicMock()
window.status_message = MagicMock()

return window


@pytest.fixture
def mock_settings():
"""Mock Sublime Text settings object."""
settings = MagicMock()
settings_dict = {}

def get(key, default=None):
return settings_dict.get(key, default)

def set(key, value):
settings_dict[key] = value

def has(key):
return key in settings_dict

def erase(key):
settings_dict.pop(key, None)

settings.get = get
settings.set = set
settings.has = has
settings.erase = erase

return settings


@pytest.fixture
def sample_ansi_output():
"""Sample ANSI colored output for testing."""
return {
"simple": "Hello \x1b[31mWorld\x1b[0m!",
"multiple_colors": "\x1b[32mGreen\x1b[0m \x1b[33mYellow\x1b[0m \x1b[34mBlue\x1b[0m",
"bold": "\x1b[1mBold Text\x1b[0m",
"underline": "\x1b[4mUnderlined Text\x1b[0m",
"combined": "\x1b[1;31mBold Red\x1b[0m \x1b[4;32mUnderlined Green\x1b[0m",
"error": "\x1b[31mError:\x1b[0m File not found",
"warning": "\x1b[33mWarning:\x1b[0m Deprecated function",
"success": "\x1b[32mSuccess:\x1b[0m All tests passed",
}


@pytest.fixture(autouse=True)
def reset_modules(monkeypatch):
"""Reset module imports between tests to ensure clean state."""
# Store original modules
original_modules = dict(sys.modules)

yield

# Remove any modules added during the test
modules_to_remove = []
for name in sys.modules:
if name not in original_modules:
modules_to_remove.append(name)

for name in modules_to_remove:
del sys.modules[name]


@pytest.fixture
def capture_stdout(monkeypatch):
"""Capture stdout for testing print statements."""
import io
import sys

captured = io.StringIO()
monkeypatch.setattr(sys, 'stdout', captured)

return captured
Empty file added tests/integration/__init__.py
Empty file.
Loading