This commit is contained in:
2026-02-11 00:02:35 -08:00
parent be37eb9d5b
commit ccadea0576
59 changed files with 4569 additions and 2510 deletions

View File

@@ -3,19 +3,41 @@
from datetime import datetime
import pytest
from src.tarot.attributes import (
Month, Day, Weekday, Hour, ClockHour, Zodiac, Suit, Meaning, Letter, Sephera, Degree, Element,
AstrologicalInfluence, TreeOfLife, Correspondences, CardImage,
EnglishAlphabet, GreekAlphabet, HebrewAlphabet, Number, Color, Planet, God,
Cipher, CipherResult,
AstrologicalInfluence,
CardImage,
Cipher,
CipherResult,
ClockHour,
Color,
Correspondences,
Day,
Degree,
Element,
EnglishAlphabet,
God,
GreekAlphabet,
HebrewAlphabet,
Hour,
Letter,
Meaning,
Month,
Number,
Planet,
Sephera,
Suit,
TreeOfLife,
Weekday,
Zodiac,
)
from src.tarot.card.data import CardDataLoader, calculate_digital_root
# ============================================================================
# Basic Attribute Tests
# ============================================================================
class TestMonth:
def test_month_creation(self):
month = Month(1, "January", "Capricorn", "Aquarius")
@@ -24,10 +46,7 @@ class TestMonth:
assert month.zodiac_start == "Capricorn"
def test_month_all_months(self):
months = [
Month(i, f"Month_{i}", "Sign_1", "Sign_2")
for i in range(1, 13)
]
months = [Month(i, f"Month_{i}", "Sign_1", "Sign_2") for i in range(1, 13)]
assert len(months) == 12
assert months[0].number == 1
assert months[11].number == 12
@@ -41,10 +60,7 @@ class TestDay:
assert day.planetary_correspondence == "Sun"
def test_all_weekdays(self):
days = [
Day(i, f"Day_{i}", f"Planet_{i}")
for i in range(1, 8)
]
days = [Day(i, f"Day_{i}", f"Planet_{i}") for i in range(1, 8)]
assert len(days) == 7
@@ -99,6 +115,7 @@ class TestMeaning:
# Sepheric Tests
# ============================================================================
class TestSephera:
def test_sephera_creation(self):
sephera = Sephera(1, "Kether", "כתר", "Crown", "Metatron", "Chaioth", "Primum")
@@ -118,6 +135,7 @@ class TestSephera:
# Alphabet Tests
# ============================================================================
class TestEnglishAlphabet:
def test_english_letter_creation(self):
letter = EnglishAlphabet("A", 1, "ay")
@@ -189,6 +207,7 @@ class TestHebrewAlphabet:
# Number Tests
# ============================================================================
class TestNumber:
def test_number_creation(self):
num = Number(1, "Kether", "Spirit", 0) # compliment is auto-calculated
@@ -220,6 +239,7 @@ class TestNumber:
# Color Tests
# ============================================================================
class TestColor:
def test_color_creation(self):
color = Color("Red", "#FF0000", (255, 0, 0), "Gevurah", 5, "Fire", "Briah", "Power")
@@ -248,7 +268,9 @@ class TestColor:
for r in [0, 128, 255]:
for g in [0, 128, 255]:
for b in [0, 128, 255]:
color = Color("Test", "#000000", (r, g, b), "Sephera", 1, "Element", "Scale", "Meaning")
color = Color(
"Test", "#000000", (r, g, b), "Sephera", 1, "Element", "Scale", "Meaning"
)
assert color.rgb == (r, g, b)
@@ -260,7 +282,9 @@ class TestColor:
class TestPlanet:
def test_planet_creation(self):
number = Number(6, "Tiphareth", "Fire", 0)
color = Color("Gold", "#FFD700", (255, 215, 0), "Tiphareth", 6, "Fire", "Yetzirah", "Beauty")
color = Color(
"Gold", "#FFD700", (255, 215, 0), "Tiphareth", 6, "Fire", "Yetzirah", "Beauty"
)
planet = Planet(
name="Sun",
symbol="",
@@ -342,6 +366,7 @@ class TestGod:
# Cipher Tests
# ============================================================================
class TestCipher:
def test_cipher_mapping_basic(self):
cipher = Cipher("Test", "test", [1, 2, 3])
@@ -374,6 +399,7 @@ class TestCipherResult:
# Digital Root Tests
# ============================================================================
class TestDigitalRoot:
def test_digital_root_single_digit(self):
"""Single digits should return themselves."""
@@ -389,7 +415,7 @@ class TestDigitalRoot:
def test_digital_root_large_numbers(self):
"""Test large numbers."""
assert calculate_digital_root(99) == 9 # 9+9 = 18, 1+8 = 9
assert calculate_digital_root(99) == 9 # 9+9 = 18, 1+8 = 9
assert calculate_digital_root(100) == 1 # 1+0+0 = 1
assert calculate_digital_root(123) == 6 # 1+2+3 = 6
@@ -398,13 +424,13 @@ class TestDigitalRoot:
# Major Arcana cards 0-21
assert calculate_digital_root(14) == 5 # Card 14 (Temperance) -> 5
assert calculate_digital_root(21) == 3 # Card 21 (The World) -> 3
assert calculate_digital_root(1) == 1 # Card 1 (Magician) -> 1
assert calculate_digital_root(1) == 1 # Card 1 (Magician) -> 1
def test_digital_root_invalid_input(self):
"""Test that invalid inputs raise errors."""
with pytest.raises(ValueError):
calculate_digital_root(0)
with pytest.raises(ValueError):
calculate_digital_root(-5)
@@ -413,6 +439,7 @@ class TestDigitalRoot:
# CardDataLoader Tests
# ============================================================================
class TestCardDataLoader:
@pytest.fixture
def loader(self):
@@ -443,33 +470,35 @@ class TestCardDataLoader:
sephera = loader.sephera(i)
assert sephera is not None
assert sephera.number == i
def test_load_ciphers(self, loader):
"""Ensure cipher catalog is populated."""
ciphers = loader.cipher()
assert "english_simple" in ciphers
assert ciphers["english_simple"].default_alphabet == "english"
def test_word_cipher_request(self, loader):
"""word().cipher() should return meaningful totals."""
result = loader.word("tarot").cipher("english_simple")
assert isinstance(result, CipherResult)
assert result.total == 74
assert result.alphabet_name == "english"
def test_word_cipher_custom_alphabet(self, loader):
result = loader.word("אמש").cipher("kabbalah_three_mother")
assert result.values == (1, 40, 300)
def test_trigram_line_diagram(self, loader):
from letter import trigram
tri = trigram.trigram.name("Zhen") # Thunder
assert tri is not None
assert tri.data.line_diagram == "|::"
def test_hexagram_line_diagram(self, loader):
from letter import hexagram
hex_result = hexagram.hexagram.filter('number:1').first()
hex_result = hexagram.hexagram.filter("number:1").first()
assert hex_result is not None
assert hex_result.data.line_diagram == "||||||"
@@ -575,7 +604,7 @@ class TestCardDataLoader:
assert "english" in alphabets
assert "greek" in alphabets
assert "hebrew" in alphabets
assert len(alphabets["english"]) == 26
assert len(alphabets["greek"]) == 24
assert len(alphabets["hebrew"]) == 22
@@ -592,16 +621,16 @@ class TestCardDataLoader:
class TestDigitalRootIntegration:
"""Integration tests for digital root with Tarot cards."""
def test_all_major_arcana_digital_roots(self):
"""Test digital root for all Major Arcana cards (0-21)."""
loader = CardDataLoader()
# All Major Arcana cards should map to colors 1-9
for card_num in range(22):
if card_num == 0:
continue # Skip The Fool (0)
color = loader.color_by_number(card_num)
assert color is not None
assert 1 <= color.number <= 9
@@ -609,7 +638,7 @@ class TestDigitalRootIntegration:
def test_color_consistency(self):
"""Test that equivalent numbers map to same color."""
loader = CardDataLoader()
# 5 and 14 should map to same color (both have digital root 5)
color_5 = loader.color_by_number(5)
color_14 = loader.color_by_number(14)

View File

@@ -1,34 +1,37 @@
import pytest
from tarot.ui import CardDisplay
from tarot.deck import Card
from unittest.mock import MagicMock, patch
import pytest
from tarot.deck import Card
from tarot.ui import CardDisplay
def test_card_display_delegation():
"""Test that CardDisplay delegates to SpreadDisplay correctly."""
with patch('tarot.ui.SpreadDisplay') as MockSpreadDisplay:
with patch("tarot.ui.SpreadDisplay") as MockSpreadDisplay:
# Mock HAS_PILLOW to True to ensure we proceed
with patch('tarot.ui.HAS_PILLOW', True):
with patch("tarot.ui.HAS_PILLOW", True):
display = CardDisplay()
# Create dummy card
card = MagicMock(spec=Card)
card.name = "The Fool"
card.image_path = "fool.jpg"
cards = [card]
display.show_cards(cards, title="Test Spread")
# Verify SpreadDisplay was instantiated
assert MockSpreadDisplay.call_count == 1
# Verify run was called
MockSpreadDisplay.return_value.run.assert_called_once()
# Verify arguments passed to SpreadDisplay
args, _ = MockSpreadDisplay.call_args
reading = args[0]
deck_name = args[1]
assert deck_name == "default"
assert reading.spread.name == "Card List"
assert reading.spread.description == "Test Spread"

View File

@@ -1,6 +1,8 @@
import pytest
from tarot.ui import CubeDisplay
from tarot.tarot_api import Tarot
from tarot.ui import CubeDisplay
def test_cube_display_init():
cube = Tarot.cube
@@ -8,38 +10,40 @@ def test_cube_display_init():
assert display.current_wall_name == "North"
assert display.deck_name == "default"
def test_cube_navigation():
cube = Tarot.cube
display = CubeDisplay(cube)
# North -> Right -> East
display._navigate("Right")
assert display.current_wall_name == "East"
# East -> Up -> Above
display._navigate("Up")
assert display.current_wall_name == "Above"
# Above -> Down -> North
display._navigate("Down")
assert display.current_wall_name == "North"
# North -> Left -> West
display._navigate("Left")
assert display.current_wall_name == "West"
def test_find_card_for_direction():
cube = Tarot.cube
display = CubeDisplay(cube)
# North Wall, Center Direction -> Aleph -> The Fool
wall = cube.wall("North")
direction = wall.direction("Center") # Should be Aleph?
direction = wall.direction("Center") # Should be Aleph?
# Wait, let's check what Center of North is.
# Actually, let's just mock a direction
from kaballah.cube.attributes import WallDirection
# Aleph -> The Fool
d = WallDirection("Center", "Aleph")
card = display._find_card_for_direction(d)

View File

@@ -1,27 +1,30 @@
import pytest
from tarot.ui import CubeDisplay
from tarot.tarot_api import Tarot
from tarot.ui import CubeDisplay
def test_cube_zoom():
cube = Tarot.cube
display = CubeDisplay(cube)
assert display.zoom_level == 1.0
display._zoom(1.1)
assert display.zoom_level > 1.0
display._zoom(0.5)
assert display.zoom_level < 1.0
def test_cube_zoom_limits():
cube = Tarot.cube
display = CubeDisplay(cube)
# Test upper limit
for _ in range(20):
display._zoom(1.5)
assert display.zoom_level <= 3.0
# Test lower limit
for _ in range(20):
display._zoom(0.5)

View File

@@ -1,60 +1,131 @@
import pytest
from tarot.ui import CubeDisplay
from tarot.tarot_api import Tarot
import tkinter as tk
from unittest.mock import MagicMock, patch
import pytest
from tarot.tarot_api import Tarot
from tarot.ui import CubeDisplay
def test_zoom_limits():
# Mock Tk root
class MockRoot:
def __init__(self):
self.bindings = {}
self.images = []
def bind(self, key, callback): pass
def title(self, _): pass
def update_idletasks(self): pass
def winfo_reqwidth(self): return 800
def winfo_reqheight(self): return 600
def winfo_screenwidth(self): return 1920
def winfo_screenheight(self): return 1080
def geometry(self, _): pass
def mainloop(self): pass
def focus_force(self): pass
def bind(self, key, callback):
pass
def title(self, _):
pass
def update_idletasks(self):
pass
def winfo_reqwidth(self):
return 800
def winfo_reqheight(self):
return 600
def winfo_screenwidth(self):
return 1920
def winfo_screenheight(self):
return 1080
def geometry(self, _):
pass
def mainloop(self):
pass
def focus_force(self):
pass
# Mock Frame
class MockFrame:
def __init__(self, master=None, **kwargs):
self.children = []
self.master = master
def pack(self, **kwargs): pass
def place(self, **kwargs): pass
def grid(self, **kwargs): pass
def grid_propagate(self, flag): pass
def winfo_children(self): return self.children
def destroy(self): pass
def update_idletasks(self): pass
def winfo_reqwidth(self): return 100
def winfo_reqheight(self): return 100
def bind(self, event, callback): pass
def pack(self, **kwargs):
pass
def place(self, **kwargs):
pass
def grid(self, **kwargs):
pass
def grid_propagate(self, flag):
pass
def winfo_children(self):
return self.children
def destroy(self):
pass
def update_idletasks(self):
pass
def winfo_reqwidth(self):
return 100
def winfo_reqheight(self):
return 100
def bind(self, event, callback):
pass
# Mock Canvas
class MockCanvas:
def __init__(self, master=None, **kwargs):
self.master = master
def pack(self, **kwargs): pass
def bind(self, event, callback): pass
def create_window(self, coords, **kwargs): return 1
def config(self, **kwargs): pass
def bbox(self, tag): return (0,0,100,100)
def winfo_width(self): return 800
def winfo_height(self): return 600
def coords(self, item, x, y): pass
def scan_mark(self, x, y): pass
def scan_dragto(self, x, y, gain=1): pass
def canvasx(self, x): return x
def canvasy(self, y): return y
def xview_moveto(self, fraction): pass
def yview_moveto(self, fraction): pass
def pack(self, **kwargs):
pass
def bind(self, event, callback):
pass
def create_window(self, coords, **kwargs):
return 1
def config(self, **kwargs):
pass
def bbox(self, tag):
return (0, 0, 100, 100)
def winfo_width(self):
return 800
def winfo_height(self):
return 600
def coords(self, item, x, y):
pass
def scan_mark(self, x, y):
pass
def scan_dragto(self, x, y, gain=1):
pass
def canvasx(self, x):
return x
def canvasy(self, y):
return y
def xview_moveto(self, fraction):
pass
def yview_moveto(self, fraction):
pass
# Monkey patch tk
original_tk = tk.Tk
@@ -62,60 +133,68 @@ def test_zoom_limits():
original_canvas = tk.Canvas
original_label = tk.ttk.Label
original_button = tk.ttk.Button
# Mock Label and Button
class MockWidget:
def __init__(self, master=None, **kwargs):
self.master = master
def pack(self, **kwargs): pass
def place(self, **kwargs): pass
def grid(self, **kwargs): pass
def grid_propagate(self, flag): pass
def pack(self, **kwargs):
pass
def place(self, **kwargs):
pass
def grid(self, **kwargs):
pass
def grid_propagate(self, flag):
pass
try:
tk.Tk = MockRoot
tk.ttk.Frame = MockFrame
tk.Canvas = MockCanvas
tk.ttk.Label = MockWidget
tk.ttk.Button = MockWidget
# Mock Image to avoid memory issues
with patch('PIL.Image.open') as mock_open:
with patch("PIL.Image.open") as mock_open:
mock_img = MagicMock()
mock_img.size = (100, 100)
mock_img.resize.return_value = mock_img
mock_open.return_value = mock_img
with patch('PIL.ImageTk.PhotoImage') as mock_photo:
with patch("PIL.ImageTk.PhotoImage") as mock_photo:
cube = Tarot.cube
display = CubeDisplay(cube)
display.root = MockRoot()
display.canvas = MockCanvas()
display.content_frame = MockFrame()
display.canvas_window = 1 # Mock window ID
display.canvas_window = 1 # Mock window ID
# Test initial zoom
assert display.zoom_level == 1.0
# Test zoom in
display._zoom(1.22)
assert display.zoom_level == 1.22
# Test max limit (should be 50.0)
# Zoom way in
for _ in range(100):
display._zoom(1.22)
assert display.zoom_level == 50.0
# Test min limit (should be 0.1)
# Zoom way out
for _ in range(200):
display._zoom(0.5)
assert display.zoom_level == 0.1
finally:
tk.Tk = original_tk
tk.ttk.Frame = original_frame

View File

@@ -3,8 +3,9 @@ Tests for Tarot deck and card classes.
"""
import pytest
from src.tarot.deck import Deck, Card, MajorCard, MinorCard, PipCard, AceCard, CourtCard
from src.tarot.attributes import Meaning, Suit, CardImage
from src.tarot.attributes import CardImage, Meaning, Suit
from src.tarot.deck import AceCard, Card, CourtCard, Deck, MajorCard, MinorCard, PipCard
class TestCard:
@@ -30,7 +31,7 @@ class TestMajorCard:
name="The Magician",
meaning=Meaning("Upright", "Reversed"),
arcana="Major",
kabbalistic_number=1
kabbalistic_number=1,
)
assert card.number == 1
assert card.arcana == "Major"
@@ -42,7 +43,7 @@ class TestMajorCard:
name="Test",
meaning=Meaning("Up", "Rev"),
arcana="Major",
kabbalistic_number=-1
kabbalistic_number=-1,
)
def test_major_card_invalid_high(self):
@@ -52,16 +53,13 @@ class TestMajorCard:
name="Test",
meaning=Meaning("Up", "Rev"),
arcana="Major",
kabbalistic_number=22
kabbalistic_number=22,
)
def test_major_card_valid_range(self):
for i in range(22):
card = MajorCard(
number=i,
name=f"Card {i}",
meaning=Meaning("Up", "Rev"),
arcana="Major"
number=i, name=f"Card {i}", meaning=Meaning("Up", "Rev"), arcana="Major"
)
assert card.number == i
@@ -75,7 +73,7 @@ class TestMinorCard:
meaning=Meaning("Upright", "Reversed"),
arcana="Minor",
suit=suit,
pip=1
pip=1,
)
assert card.number == 1
assert card.suit.name == "Cups"
@@ -90,7 +88,7 @@ class TestMinorCard:
meaning=Meaning("Up", "Rev"),
arcana="Minor",
suit=suit,
pip=0
pip=0,
)
def test_minor_card_invalid_pip_high(self):
@@ -102,7 +100,7 @@ class TestMinorCard:
meaning=Meaning("Up", "Rev"),
arcana="Minor",
suit=suit,
pip=15
pip=15,
)
def test_minor_card_valid_pips(self):
@@ -114,7 +112,7 @@ class TestMinorCard:
meaning=Meaning("Up", "Rev"),
arcana="Minor",
suit=suit,
pip=i
pip=i,
)
assert card.pip == i
@@ -137,10 +135,10 @@ class TestDeck:
def test_deck_shuffle(self):
deck1 = Deck()
cards_before = [c.name for c in deck1.cards]
deck1.shuffle()
cards_after = [c.name for c in deck1.cards]
# After shuffle, order should change (with high probability)
# We don't assert they're different since shuffle could randomly give same order
assert len(cards_after) == 78
@@ -148,18 +146,18 @@ class TestDeck:
def test_deck_draw_single(self):
deck = Deck()
initial_count = len(deck.cards)
drawn = deck.draw(1)
assert len(drawn) == 1
assert len(deck.cards) == initial_count - 1
def test_deck_draw_multiple(self):
deck = Deck()
initial_count = len(deck.cards)
drawn = deck.draw(5)
assert len(drawn) == 5
assert len(deck.cards) == initial_count - 5
@@ -177,28 +175,28 @@ class TestDeck:
deck = Deck()
deck.draw(5)
assert len(deck.cards) < 78
deck.reset()
assert len(deck.cards) == 78
def test_deck_remaining(self):
deck = Deck()
assert deck.remaining() == 78
deck.draw(1)
assert deck.remaining() == 77
def test_deck_len(self):
deck = Deck()
assert len(deck) == 78
deck.draw(1)
assert len(deck) == 77
def test_deck_repr(self):
deck = Deck()
assert "78 cards" in repr(deck)
deck.draw(1)
assert "77 cards" in repr(deck)

View File

@@ -1,18 +1,25 @@
import pytest
from pathlib import Path
import pytest
from tarot.ui import CardDisplay
def test_card_display_init():
display = CardDisplay("default")
assert display.deck_name == "default"
# Check if path resolves correctly relative to src/tarot/ui.py
# src/tarot/ui.py -> src/tarot -> src/tarot/deck/default
expected_suffix = os.path.join("src", "tarot", "deck", "default")
assert str(display.deck_path).endswith(expected_suffix) or str(display.deck_path).endswith("default")
assert str(display.deck_path).endswith(expected_suffix) or str(display.deck_path).endswith(
"default"
)
def test_card_display_resolve_path():
display = CardDisplay("thoth")
assert display.deck_name == "thoth"
assert str(display.deck_path).endswith("thoth")
import os

View File

@@ -1,60 +1,63 @@
import pytest
from tarot.ui import CubeDisplay
from tarot.tarot_api import Tarot
import tkinter as tk
import pytest
from tarot.tarot_api import Tarot
from tarot.ui import CubeDisplay
def test_recursive_binding():
# Mock Tk root and widgets
class MockWidget:
def __init__(self):
self.children = []
self.bindings = {}
def bind(self, key, callback):
self.bindings[key] = callback
def winfo_children(self):
return self.children
def add_child(self, child):
self.children.append(child)
# Monkey patch tk
original_tk = tk.Tk
original_frame = tk.ttk.Frame
try:
# We don't need to mock everything, just enough to test _bind_recursive
cube = Tarot.cube
# We can instantiate CubeDisplay without showing it
display = CubeDisplay(cube)
# Create a mock widget tree
parent = MockWidget()
child1 = MockWidget()
child2 = MockWidget()
grandchild = MockWidget()
parent.add_child(child1)
parent.add_child(child2)
child1.add_child(grandchild)
# Run recursive binding
display._bind_recursive(parent)
# Verify bindings
assert "<ButtonPress-1>" in parent.bindings
assert "<B1-Motion>" in parent.bindings
assert "<ButtonPress-1>" in child1.bindings
assert "<B1-Motion>" in child1.bindings
assert "<ButtonPress-1>" in child2.bindings
assert "<B1-Motion>" in child2.bindings
assert "<ButtonPress-1>" in grandchild.bindings
assert "<B1-Motion>" in grandchild.bindings
finally:
pass

View File

@@ -1,72 +1,99 @@
import pytest
from tarot.ui import CubeDisplay
from tarot.tarot_api import Tarot
import tkinter as tk
import pytest
from tarot.tarot_api import Tarot
from tarot.ui import CubeDisplay
def test_zoom_key_bindings():
# This test verifies that the bindings are set up,
# This test verifies that the bindings are set up,
# but cannot easily simulate key presses in headless environment.
# We check if the bind method was called with correct keys.
# Mock Tk root
class MockRoot:
def __init__(self):
self.bindings = {}
def bind(self, key, callback):
self.bindings[key] = callback
def title(self, _): pass
def update_idletasks(self): pass
def winfo_reqwidth(self): return 800
def winfo_reqheight(self): return 600
def winfo_screenwidth(self): return 1920
def winfo_screenheight(self): return 1080
def geometry(self, _): pass
def mainloop(self): pass
def focus_force(self): pass
def title(self, _):
pass
def update_idletasks(self):
pass
def winfo_reqwidth(self):
return 800
def winfo_reqheight(self):
return 600
def winfo_screenwidth(self):
return 1920
def winfo_screenheight(self):
return 1080
def geometry(self, _):
pass
def mainloop(self):
pass
def focus_force(self):
pass
# Mock Frame
class MockFrame:
def __init__(self, master=None, **kwargs):
self.children = []
def pack(self, **kwargs): pass
def winfo_children(self): return self.children
def destroy(self): pass
def pack(self, **kwargs):
pass
def winfo_children(self):
return self.children
def destroy(self):
pass
# Monkey patch tk
original_tk = tk.Tk
original_frame = tk.ttk.Frame
try:
tk.Tk = MockRoot
tk.ttk.Frame = MockFrame
cube = Tarot.cube
display = CubeDisplay(cube)
# We need to call show() to trigger bindings, but avoid mainloop
# We can't easily mock show() without refactoring,
# We can't easily mock show() without refactoring,
# so we'll just inspect the code logic or trust the manual test.
# However, we can manually call the binding logic if we extract it.
# Since we can't easily mock the entire UI startup in a unit test without
# a display, we'll rely on the fact that we added the bindings in the code.
pass
finally:
tk.Tk = original_tk
tk.ttk.Frame = original_frame
def test_zoom_logic_direct():
cube = Tarot.cube
display = CubeDisplay(cube)
display.zoom_level = 1.0
# Simulate + key press effect
display._zoom(1.1)
assert display.zoom_level > 1.0
# Simulate - key press effect
display._zoom(0.9)
assert display.zoom_level < 1.1

View File

@@ -1,83 +1,140 @@
import pytest
from tarot.ui import CubeDisplay
from tarot.tarot_api import Tarot
import tkinter as tk
import pytest
from tarot.tarot_api import Tarot
from tarot.ui import CubeDisplay
def test_canvas_structure():
# Mock Tk root
class MockRoot:
def __init__(self):
self.bindings = {}
def bind(self, key, callback): pass
def title(self, _): pass
def update_idletasks(self): pass
def winfo_reqwidth(self): return 800
def winfo_reqheight(self): return 600
def winfo_screenwidth(self): return 1920
def winfo_screenheight(self): return 1080
def geometry(self, _): pass
def mainloop(self): pass
def focus_force(self): pass
def bind(self, key, callback):
pass
def title(self, _):
pass
def update_idletasks(self):
pass
def winfo_reqwidth(self):
return 800
def winfo_reqheight(self):
return 600
def winfo_screenwidth(self):
return 1920
def winfo_screenheight(self):
return 1080
def geometry(self, _):
pass
def mainloop(self):
pass
def focus_force(self):
pass
# Mock Frame
class MockFrame:
def __init__(self, master=None, **kwargs):
self.children = []
self.master = master
def pack(self, **kwargs): pass
def place(self, **kwargs): pass
def winfo_children(self): return self.children
def destroy(self): pass
def update_idletasks(self): pass
def winfo_reqwidth(self): return 100
def winfo_reqheight(self): return 100
def pack(self, **kwargs):
pass
def place(self, **kwargs):
pass
def winfo_children(self):
return self.children
def destroy(self):
pass
def update_idletasks(self):
pass
def winfo_reqwidth(self):
return 100
def winfo_reqheight(self):
return 100
# Mock Canvas
class MockCanvas:
def __init__(self, master=None, **kwargs):
self.master = master
def pack(self, **kwargs): pass
def bind(self, event, callback): pass
def create_window(self, coords, **kwargs): return 1
def config(self, **kwargs): pass
def bbox(self, tag): return (0,0,100,100)
def winfo_width(self): return 800
def winfo_height(self): return 600
def coords(self, item, x, y): pass
def scan_mark(self, x, y): pass
def scan_dragto(self, x, y, gain=1): pass
def pack(self, **kwargs):
pass
def bind(self, event, callback):
pass
def create_window(self, coords, **kwargs):
return 1
def config(self, **kwargs):
pass
def bbox(self, tag):
return (0, 0, 100, 100)
def winfo_width(self):
return 800
def winfo_height(self):
return 600
def coords(self, item, x, y):
pass
def scan_mark(self, x, y):
pass
def scan_dragto(self, x, y, gain=1):
pass
# Monkey patch tk
original_tk = tk.Tk
original_frame = tk.ttk.Frame
original_canvas = tk.Canvas
try:
tk.Tk = MockRoot
tk.ttk.Frame = MockFrame
tk.Canvas = MockCanvas
cube = Tarot.cube
display = CubeDisplay(cube)
# Trigger show to build UI
# We can't fully run show() because of mainloop, but we can instantiate parts
# Actually, show() creates the root.
# Let's just verify the structure by inspecting the code or trusting the manual test.
# But we can test the pan methods directly.
display.canvas = MockCanvas()
# Test pan methods
class MockEvent:
x = 10
y = 20
x_root = 110
y_root = 120
display._start_pan(MockEvent())
display._pan(MockEvent())
finally:
tk.Tk = original_tk
tk.ttk.Frame = original_frame

View File

@@ -1,68 +1,137 @@
import pytest
from tarot.ui import CubeDisplay
from tarot.tarot_api import Tarot
import tkinter as tk
from unittest.mock import MagicMock, patch
import pytest
from tarot.tarot_api import Tarot
from tarot.ui import CubeDisplay
def test_wasd_panning():
# Mock Tk root
class MockRoot:
def __init__(self):
self.bindings = {}
self.images = []
def bind(self, key, callback):
def bind(self, key, callback):
self.bindings[key] = callback
def title(self, _): pass
def update_idletasks(self): pass
def winfo_reqwidth(self): return 800
def winfo_reqheight(self): return 600
def winfo_screenwidth(self): return 1920
def winfo_screenheight(self): return 1080
def geometry(self, _): pass
def mainloop(self): pass
def focus_force(self): pass
def title(self, _):
pass
def update_idletasks(self):
pass
def winfo_reqwidth(self):
return 800
def winfo_reqheight(self):
return 600
def winfo_screenwidth(self):
return 1920
def winfo_screenheight(self):
return 1080
def geometry(self, _):
pass
def mainloop(self):
pass
def focus_force(self):
pass
# Mock Frame
class MockFrame:
def __init__(self, master=None, **kwargs):
self.children = []
self.master = master
def pack(self, **kwargs): pass
def place(self, **kwargs): pass
def grid(self, **kwargs): pass
def grid_propagate(self, flag): pass
def winfo_children(self): return self.children
def destroy(self): pass
def update_idletasks(self): pass
def winfo_reqwidth(self): return 100
def winfo_reqheight(self): return 100
def bind(self, event, callback): pass
def pack(self, **kwargs):
pass
def place(self, **kwargs):
pass
def grid(self, **kwargs):
pass
def grid_propagate(self, flag):
pass
def winfo_children(self):
return self.children
def destroy(self):
pass
def update_idletasks(self):
pass
def winfo_reqwidth(self):
return 100
def winfo_reqheight(self):
return 100
def bind(self, event, callback):
pass
# Mock Canvas
class MockCanvas:
def __init__(self, master=None, **kwargs):
self.master = master
self.x_scrolls = []
self.y_scrolls = []
def pack(self, **kwargs): pass
def bind(self, event, callback): pass
def create_window(self, coords, **kwargs): return 1
def config(self, **kwargs): pass
def bbox(self, tag): return (0,0,100,100)
def winfo_width(self): return 800
def winfo_height(self): return 600
def coords(self, item, x, y): pass
def scan_mark(self, x, y): pass
def scan_dragto(self, x, y, gain=1): pass
def canvasx(self, x): return x
def canvasy(self, y): return y
def xview_moveto(self, fraction): pass
def yview_moveto(self, fraction): pass
def pack(self, **kwargs):
pass
def bind(self, event, callback):
pass
def create_window(self, coords, **kwargs):
return 1
def config(self, **kwargs):
pass
def bbox(self, tag):
return (0, 0, 100, 100)
def winfo_width(self):
return 800
def winfo_height(self):
return 600
def coords(self, item, x, y):
pass
def scan_mark(self, x, y):
pass
def scan_dragto(self, x, y, gain=1):
pass
def canvasx(self, x):
return x
def canvasy(self, y):
return y
def xview_moveto(self, fraction):
pass
def yview_moveto(self, fraction):
pass
def xview_scroll(self, number, what):
self.x_scrolls.append((number, what))
def yview_scroll(self, number, what):
self.y_scrolls.append((number, what))
@@ -72,54 +141,62 @@ def test_wasd_panning():
original_canvas = tk.Canvas
original_label = tk.ttk.Label
original_button = tk.ttk.Button
# Mock Label and Button
class MockWidget:
def __init__(self, master=None, **kwargs):
self.master = master
def pack(self, **kwargs): pass
def place(self, **kwargs): pass
def grid(self, **kwargs): pass
def grid_propagate(self, flag): pass
def pack(self, **kwargs):
pass
def place(self, **kwargs):
pass
def grid(self, **kwargs):
pass
def grid_propagate(self, flag):
pass
try:
tk.Tk = MockRoot
tk.ttk.Frame = MockFrame
tk.Canvas = MockCanvas
tk.ttk.Label = MockWidget
tk.ttk.Button = MockWidget
# Mock Image to avoid memory issues
with patch('PIL.Image.open') as mock_open:
with patch("PIL.Image.open") as mock_open:
mock_img = MagicMock()
mock_img.size = (100, 100)
mock_img.resize.return_value = mock_img
mock_open.return_value = mock_img
with patch('PIL.ImageTk.PhotoImage') as mock_photo:
with patch("PIL.ImageTk.PhotoImage") as mock_photo:
cube = Tarot.cube
display = CubeDisplay(cube)
display.root = MockRoot()
display.canvas = MockCanvas()
display.content_frame = MockFrame()
display.canvas_window = 1
display.canvas_window = 1
# Manually trigger bindings (since we can't easily simulate key press in mock root without event loop)
# But we can call _pan_key directly to test logic
display._pan_key("up")
assert display.canvas.y_scrolls[-1] == (-1, "units")
display._pan_key("down")
assert display.canvas.y_scrolls[-1] == (1, "units")
display._pan_key("left")
assert display.canvas.x_scrolls[-1] == (-1, "units")
display._pan_key("right")
assert display.canvas.x_scrolls[-1] == (1, "units")
finally:
tk.Tk = original_tk
tk.ttk.Frame = original_frame