File size: 4,201 Bytes
60e41bb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d70a4af
 
60e41bb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
import json

import pytest

from waterleaf.application import WaterleafApplication
from waterleaf.models import TaxonCandidate
from waterleaf.storage import GardenStore
from waterleaf.ui import _candidate_label, _parse_optional_interval, build_ui


class NoopCare:
    def get_care(self, scientific_name):
        raise AssertionError("Not called while building UI")


class NoopWeather:
    def geocode(self, query):
        raise AssertionError("Not called while building UI")

    def forecast(self, latitude, longitude):
        raise AssertionError("Not called while building UI")


class NoopIdentification:
    def identify(self, image_paths):
        raise AssertionError("Not called while building UI")


class NoopTaxonomy:
    def suggest(self, query, limit=10):
        return []


def test_ui_contains_dashboard_capture_confirmation_and_export(tmp_path):
    application = WaterleafApplication(
        store=GardenStore(tmp_path / "waterleaf.sqlite3"),
        media_directory=tmp_path / "media",
        export_directory=tmp_path / "exports",
        public_base_url="https://waterleaf.example",
        care=NoopCare(),
        weather=NoopWeather(),
    )

    demo = build_ui(
        application,
        identification=NoopIdentification(),
        taxonomy=NoopTaxonomy(),
        sample_image="assets/sample-lavender.png",
    )
    config_file = demo.get_config_file()
    config = json.dumps(config_file, default=str)

    assert "Waterleaf" in config
    assert "Add plant" in config
    assert "Analyze photos" in config
    assert "Confirm species" in config
    assert "Generate 30-day calendar" in config
    assert "Sign in with Hugging Face" in config
    assert "Plant photo (required)" in config
    assert "Database match (required)" in config
    assert "Plant nickname (required)" in config
    assert "City (required)" in config
    assert "Watering time (required)" in config
    assert "Watering interval in days" in config
    assert "Leave blank when a local baseline is available" in config
    assert "Example: 7" in config
    assert "Garden location" not in config
    assert "Interval override" not in config
    city = next(
        component
        for component in config_file["components"]
        if component.get("props", {}).get("label") == "City (required)"
    )
    assert city["props"]["value"] == "Stockholm, Sweden"


def test_ui_handlers_are_not_exposed_as_public_api(tmp_path):
    application = WaterleafApplication(
        store=GardenStore(tmp_path / "waterleaf.sqlite3"),
        media_directory=tmp_path / "media",
        export_directory=tmp_path / "exports",
        public_base_url="https://waterleaf.example",
        care=NoopCare(),
        weather=NoopWeather(),
    )

    demo = build_ui(
        application,
        identification=NoopIdentification(),
        taxonomy=NoopTaxonomy(),
        sample_image="assets/sample-lavender.png",
    )
    dependencies = demo.get_config_file()["dependencies"]

    assert dependencies
    assert all(item["api_visibility"] != "public" for item in dependencies)
    save_dependency = next(
        item for item in dependencies if item["api_name"] == "save"
    )
    assert len(save_dependency["outputs"]) == 2


def test_optional_watering_interval_accepts_blank_or_one_to_thirty_days():
    assert _parse_optional_interval("") is None
    assert _parse_optional_interval("7") == 7

    with pytest.raises(ValueError, match="whole number from 1 to 30"):
        _parse_optional_interval("0")
    with pytest.raises(ValueError, match="whole number from 1 to 30"):
        _parse_optional_interval("weekly")


def test_candidate_label_shows_common_and_scientific_names():
    candidate = TaxonCandidate(
        taxon_key="2927305",
        scientific_name="Lavandula angustifolia",
        common_name="English lavender",
        confidence=0.8,
    )
    unavailable = candidate.model_copy(
        update={"common_name": "Lavandula angustifolia"}
    )

    assert _candidate_label(candidate) == (
        "English lavender | Lavandula angustifolia | 80%"
    )
    assert _candidate_label(unavailable) == (
        "Lavandula angustifolia | Common name unavailable | 80%"
    )