Skip to content

Commit a4e1639

Browse files
authored
Merge pull request #685 from OpenTrafficCam/feature/7812-rearrange-buttons-in-section-and-flow-form
feature/7812-rearrange-buttons-in-section-and-flow-form
2 parents da0f986 + 78063b0 commit a4e1639

5 files changed

Lines changed: 483 additions & 22 deletions

File tree

OTAnalytics/plugin_ui/nicegui_gui/nicegui/elements/table.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ def build(self) -> None:
8585
selection=self._selection,
8686
) as table:
8787
self.__table = table
88+
self.__table.style("width: 100%")
8889
self._add_header_slot()
8990
self._add_body_slot()
9091
self._register_callback()

OTAnalytics/plugin_ui/nicegui_gui/pages/sections_and_flow_form/flow_form.py

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,14 @@
1616
CustomTable,
1717
)
1818

19+
BUTTON_WIDTH = "max-width: 45%; width: 100%"
20+
BASIC_WIDTH = "width: 100%"
21+
MARKER_FLOW_TABLE = "marker-flow-table"
22+
MARKER_BUTTON_ADD = "marker-button-add"
23+
MARKER_BUTTON_GENERATE = "marker-button-generate"
24+
MARKER_BUTTON_REMOVE = "marker-button-remove"
25+
MARKER_BUTTON_PROPERTIES = "marker-button-properties"
26+
1927

2028
def create_columns(resource_manager: ResourceManager) -> list[dict[str, str]]:
2129
return [
@@ -49,6 +57,7 @@ def __init__(
4957
rows=[],
5058
on_select_method=lambda e: self._select_flow(e.selection),
5159
selection="single",
60+
marker=MARKER_FLOW_TABLE,
5261
)
5362
self._button_remove: ui.button | None = None
5463
self._button_add: ui.button | None = None
@@ -67,22 +76,28 @@ def _select_flow(self, e: dict) -> None:
6776

6877
def build(self) -> Self:
6978
self._flow_table.build()
70-
with ui.row():
79+
with ui.row().style(BASIC_WIDTH):
7180
self._button_add = ui.button(
7281
self._resource_manager.get(FlowKeys.BUTTON_ADD), on_click=self.add_flow
73-
)
82+
).style(BUTTON_WIDTH)
83+
self._button_add.mark(MARKER_BUTTON_ADD)
7484
self._button_generate = ui.button(
7585
self._resource_manager.get(FlowKeys.BUTTON_GENERATE),
7686
on_click=self.generate_flow,
77-
)
78-
self._button_remove = ui.button(
79-
self._resource_manager.get(FlowKeys.BUTTON_REMOVE),
80-
on_click=self.remove_flow,
81-
)
82-
self._button_properties = ui.button(
83-
self._resource_manager.get(FlowKeys.BUTTON_PROPERTIES),
84-
on_click=self.show_flow_properties,
85-
)
87+
).style(BUTTON_WIDTH)
88+
self._button_generate.mark(MARKER_BUTTON_GENERATE)
89+
90+
with ui.row().style(BASIC_WIDTH):
91+
self._button_remove = ui.button(
92+
self._resource_manager.get(FlowKeys.BUTTON_REMOVE),
93+
on_click=self.remove_flow,
94+
).style(BUTTON_WIDTH)
95+
self._button_remove.mark(MARKER_BUTTON_REMOVE)
96+
self._button_properties = ui.button(
97+
self._resource_manager.get(FlowKeys.BUTTON_PROPERTIES),
98+
on_click=self.show_flow_properties,
99+
).style(BUTTON_WIDTH)
100+
self._button_properties.mark(MARKER_BUTTON_PROPERTIES)
86101
self.update_items()
87102
return self
88103

OTAnalytics/plugin_ui/nicegui_gui/pages/sections_and_flow_form/sections_form.py

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Iterable, Self
1+
from typing import Any, Iterable, Self
22

33
from nicegui import ui
44
from nicegui.elements.button import Button
@@ -17,6 +17,15 @@
1717
CustomTable,
1818
)
1919

20+
BUTTON_WIDTH = "max-width: 45%; width: 100%"
21+
BASIC_WIDTH = "width: 100%"
22+
MARKER_SECTION_TABLE = "marker-section-table"
23+
MARKER_BUTTON_ADD_LINE = "marker-button-add-line"
24+
MARKER_BUTTON_ADD_AREA = "marker-button-add-area"
25+
MARKER_BUTTON_EDIT = "marker-button-edit"
26+
MARKER_BUTTON_PROPERTIES = "marker-button-properties"
27+
MARKER_BUTTON_REMOVE = "marker-button-remove"
28+
2029

2130
def create_columns(resource_manager: ResourceManager) -> list[dict[str, str]]:
2231

@@ -51,6 +60,7 @@ def __init__(
5160
rows=[],
5261
on_select_method=lambda e: self._select_section(e.selection),
5362
selection="single",
63+
marker=MARKER_SECTION_TABLE,
5464
)
5565
self._toggle = False
5666
self._button_edit: ui.button | None = None
@@ -65,36 +75,45 @@ def _introduce_to_viewmodel(self) -> None:
6575
self._viewmodel.set_sections_frame(self)
6676
self._viewmodel.set_treeview_sections(self)
6777

68-
def _select_section(self, events: dict) -> None:
78+
def _select_section(self, events: list[dict[str, Any]]) -> None:
6979
selected_sections = [event[COLUMN_ID] for event in events]
7080
self._viewmodel.set_selected_section_ids(selected_sections)
7181

7282
def build(self) -> Self:
7383
self._section_table.build()
74-
with ui.grid(rows=3):
75-
with ui.row():
84+
with ui.grid(rows=3).style(BASIC_WIDTH):
85+
with ui.row().style(BASIC_WIDTH):
7686
self._button_add_line = ui.button(
7787
self._resource_manager.get(SectionKeys.BUTTON_ADD_LINE),
7888
on_click=self._viewmodel.add_line_section,
79-
)
89+
).style(BUTTON_WIDTH)
90+
self._button_add_line.mark(MARKER_BUTTON_ADD_LINE)
91+
8092
self._button_add_areas = ui.button(
8193
self._resource_manager.get(SectionKeys.BUTTON_ADD_AREA),
8294
on_click=self._viewmodel.add_area_section,
83-
)
84-
with ui.row():
95+
).style(BUTTON_WIDTH)
96+
self._button_add_areas.mark(MARKER_BUTTON_ADD_AREA)
97+
98+
with ui.row().style(BASIC_WIDTH):
8599
self._button_edit = ui.button(
86100
self._resource_manager.get(SectionKeys.BUTTON_EDIT),
87101
on_click=self._viewmodel.edit_section_geometry,
88-
)
102+
).style(BUTTON_WIDTH)
103+
self._button_edit.mark(MARKER_BUTTON_EDIT)
104+
89105
self._button_properties = ui.button(
90106
self._resource_manager.get(SectionKeys.BUTTON_PROPERTIES),
91107
on_click=self._viewmodel.edit_selected_section_metadata,
92-
)
93-
with ui.row():
108+
).style(BUTTON_WIDTH)
109+
self._button_properties.mark(MARKER_BUTTON_PROPERTIES)
110+
111+
with ui.row().style(BASIC_WIDTH):
94112
self._button_remove = ui.button(
95113
self._resource_manager.get(SectionKeys.BUTTON_REMOVE),
96114
on_click=self._viewmodel.remove_sections,
97-
)
115+
).style(BASIC_WIDTH)
116+
self._button_remove.mark(MARKER_BUTTON_REMOVE)
98117
self.update_items()
99118
return self
100119

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
from typing import Any
2+
from unittest.mock import MagicMock, Mock
3+
4+
import pytest
5+
from nicegui import ui
6+
from nicegui.testing import User
7+
8+
from OTAnalytics.adapter_ui.view_model import ViewModel
9+
from OTAnalytics.application.resources.resource_manager import ResourceManager
10+
from OTAnalytics.application.state import FlowState
11+
from OTAnalytics.plugin_ui.nicegui_gui.pages.sections_and_flow_form.flow_form import (
12+
MARKER_BUTTON_ADD,
13+
MARKER_BUTTON_GENERATE,
14+
MARKER_BUTTON_PROPERTIES,
15+
MARKER_BUTTON_REMOVE,
16+
MARKER_FLOW_TABLE,
17+
FlowForm,
18+
)
19+
20+
# Constants for testing
21+
ENDPOINT_NAME = "/test-flow-form"
22+
FLOW_ID_1 = "flow-1"
23+
FLOW_ID_2 = "flow-2"
24+
FLOW_NAME_1 = "Flow 1"
25+
FLOW_NAME_2 = "Flow 2"
26+
27+
28+
class MockFlow:
29+
def __init__(self, flow_id: str, name: str) -> None:
30+
self.id = flow_id
31+
self.name = name
32+
33+
def to_dict(self) -> dict[str, Any]:
34+
return {"id": self.id, "name": self.name}
35+
36+
37+
@pytest.fixture
38+
def viewmodel() -> Mock:
39+
viewmodel = MagicMock(spec=ViewModel)
40+
41+
# Set up mock methods
42+
viewmodel.get_all_flows.return_value = [
43+
MockFlow(FLOW_ID_1, FLOW_NAME_1),
44+
MockFlow(FLOW_ID_2, FLOW_NAME_2),
45+
]
46+
47+
return viewmodel
48+
49+
50+
@pytest.fixture
51+
def flow_state() -> Mock:
52+
flow_state = MagicMock(spec=FlowState)
53+
54+
# Set up selected_flows observable
55+
selected_flows_mock = MagicMock()
56+
selected_flows_mock.get.return_value = []
57+
flow_state.selected_flows = selected_flows_mock
58+
59+
return flow_state
60+
61+
62+
@pytest.fixture
63+
def flow_form(
64+
viewmodel: ViewModel, flow_state: FlowState, resource_manager: ResourceManager
65+
) -> FlowForm:
66+
return FlowForm(viewmodel, flow_state, resource_manager)
67+
68+
69+
class TestFlowForm:
70+
71+
@pytest.mark.asyncio
72+
async def test_form_build_up(
73+
self,
74+
user: User,
75+
flow_form: FlowForm,
76+
resource_manager: ResourceManager,
77+
) -> None:
78+
"""Test that the form builds correctly and displays all buttons."""
79+
80+
@ui.page(ENDPOINT_NAME)
81+
def page() -> None:
82+
flow_form.build()
83+
84+
await user.open(ENDPOINT_NAME)
85+
86+
# Check that all buttons are visible
87+
await user.should_see(marker=MARKER_BUTTON_ADD)
88+
await user.should_see(marker=MARKER_BUTTON_GENERATE)
89+
await user.should_see(marker=MARKER_BUTTON_REMOVE)
90+
await user.should_see(marker=MARKER_BUTTON_PROPERTIES)
91+
92+
# Just check that all buttons are visible, skip checking the table for now
93+
# The table is built, but we can't easily check its contents in this test
94+
95+
@pytest.mark.asyncio
96+
async def test_generate_flow_button_calls_viewmodel(
97+
self,
98+
user: User,
99+
flow_form: FlowForm,
100+
viewmodel: Mock,
101+
resource_manager: ResourceManager,
102+
) -> None:
103+
"""Test that clicking the generate flow button calls the viewmodel method."""
104+
105+
@ui.page(ENDPOINT_NAME)
106+
def page() -> None:
107+
flow_form.build()
108+
109+
await user.open(ENDPOINT_NAME)
110+
111+
# Directly call the generate_flow method
112+
user.find(marker=MARKER_BUTTON_GENERATE).click()
113+
114+
# Verify that the viewmodel method was called
115+
viewmodel.generate_flows.assert_called_once()
116+
117+
@pytest.mark.asyncio
118+
async def test_remove_flow_button_calls_viewmodel(
119+
self,
120+
user: User,
121+
flow_form: FlowForm,
122+
viewmodel: Mock,
123+
resource_manager: ResourceManager,
124+
) -> None:
125+
"""Test that clicking the remove flow button calls the viewmodel method."""
126+
127+
@ui.page(ENDPOINT_NAME)
128+
def page() -> None:
129+
flow_form.build()
130+
131+
await user.open(ENDPOINT_NAME)
132+
133+
# Directly call the remove_flow method
134+
user.find(marker=MARKER_BUTTON_REMOVE).click()
135+
136+
# Verify that the viewmodel method was called
137+
viewmodel.remove_flows.assert_called_once()
138+
139+
@pytest.mark.asyncio
140+
async def test_update_items_displays_flows(
141+
self,
142+
user: User,
143+
flow_form: FlowForm,
144+
viewmodel: Mock,
145+
) -> None:
146+
"""Test that update_items displays the flows from the viewmodel."""
147+
148+
@ui.page(ENDPOINT_NAME)
149+
def page() -> None:
150+
flow_form.build()
151+
152+
await user.open(ENDPOINT_NAME)
153+
154+
# Verify that the table is displayed
155+
await user.should_see(marker=MARKER_FLOW_TABLE)
156+
157+
# Verify that the flows are in the table's rows
158+
names = [row.get("name") for row in flow_form._flow_table._rows]
159+
assert names == [FLOW_NAME_1, FLOW_NAME_2]
160+
161+
# Change the flows returned by the viewmodel
162+
new_flow = MockFlow("flow-3", "New Flow")
163+
viewmodel.get_all_flows.return_value = [new_flow]
164+
165+
# Update the items
166+
flow_form.update_items()
167+
168+
# Verify that the new flow is in the table's rows
169+
assert names == [FLOW_NAME_1, FLOW_NAME_2]

0 commit comments

Comments
 (0)