from dataclasses import dataclass from typing import NamedTuple from unittest.mock import MagicMock import pytest from gears import Gear from gears.connections import connect from gears.effect import Effect, effect_of, effect_of_2 def test_get_set(): g = Gear(1) assert g.get() == 1 assert g() == 1 g.set(3) assert g.get() == 3 assert g() == 3 def test_basic_effect(): g = Gear("E") last_arg: str | None = None @effect_of(g) def value_changed(v: str): nonlocal last_arg last_arg = v assert last_arg == "E" g.set("hello") assert last_arg == "hello" def test_effect_of_2(): g1 = Gear("E") g2 = Gear(3) last_arg: tuple[str, int] | None = None @effect_of_2(g1, g2) def value_changed(v1: str, v2: int): nonlocal last_arg last_arg = (v1, v2) assert last_arg == ("E", 3) g2.set(4) assert last_arg == ("E", 4) def test_connect(): my_str = Gear("123") my_int = connect(my_str, lambda s: int(s), lambda _, i: str(i)) assert my_int() == 123 my_int.set(321) assert my_str() == "321" # def test_dualis(): # val_both: Gear[tuple[int, int]] = Gear((5, 6)) # val0 = connect(val_both, lambda both: both[0], lambda both, val0: (val0, both[1])) # val1 = connect(val_both, lambda both: both[1], lambda both, val1: (both[0], val1)) def test_watts(): volts = Gear(3.0) amps = Gear(5.0) @effect_of_2(volts, amps) def on_watts_changed(volts: float, amps: float): watts = volts * amps if watts > 100: raise ValueError("OMG ITS BURNING") volts.set(5.0) with pytest.raises(ValueError): volts.set(50.0) def test_led(): battery_voltage = Gear(3.0) switch: Gear[bool] = Gear(True) # true means circuit closed # ohms_circuit = Gear(1.0) ohms_circuit = connect( switch, lambda x: 1.0 if x else 100000000.0, lambda _, y: y < 100 ) log: list[str] = [] @effect_of_2(battery_voltage, ohms_circuit) def on_led_inputs_changed(volts: float, ohms: float): amps = volts / ohms if amps > 1: print("LED ACTIVE!") log.append("LED ACTIVE!") else: print("LED INACTIVE!") log.append("LED INACTIVE!") # Effect(on_led_inputs_changed, battery_voltage, led_ohms) assert log == ["LED ACTIVE!"] switch.set(False) assert log == ["LED ACTIVE!", "LED INACTIVE!"] def test_connect_multiplication(): mul1 = Gear(10.0) mul2 = connect(mul1, lambda x: x * 2.0, lambda _, y: y / 2.0) mul4 = connect(mul2, lambda x: x * 2.0, lambda _, y: y / 2.0) mul3 = connect(mul1, lambda x: x * 3.0, lambda _, y: y / 3.0) assert mul2() == 20 assert mul3() == 30 mul2.set(30) assert mul1() == 15 assert mul3() == 45 assert mul4() == 60 # mul1 # |> mul2 # |> mul4 # |> mul3 # class Person: name: str class HouseHold: people: list[Person] class NameEntry: def __init__(self, name: Gear[str]): # self.name = name textBox = TextBox(...) effect_of(name)(textBox.setValue) textBox.onChange = name.set def test_connect_property(): class Foo(NamedTuple): bar: int baz: int foo = Gear(Foo(bar=1, baz=2)) bar = connect( foo, lambda foo: foo.bar, lambda foo, bar: Foo(bar=bar, baz=foo.baz), # TODO: look into dataclasses.replace ) assert bar() == 1 bar.set(5) assert bar() == 5 assert foo() == Foo(5, 2) # class IntEntryOld: # on_value_changed: Signal[int] # _val: int # def get_val(self, val: int): ... # def set_val(self, val: int): ... # class IntEntry: # def __init__(self, value: Gear[int]): ... # int_entry = IntEntry() # effect_of(bar)(int_entry.set_val) # int_entry.on_value_changed.connect(bar.set) # @effect_of(bar) # def update_ui(bar: int): # int_entry.set_val(bar) # def update_model(bar_val: int): # bar.set(bar_val) # int_entry.on_value_changed.connect(update_model) def hello(my_callback): my_callback(5) def test_demonstrate_magicmock(): m = MagicMock() # print(f"{m.foo(4).bar[0]=}") # m. hello(m) # assert m.call_count == 1 m.assert_called_once_with(5)