Can't use pytest to test components #3912
-
DescriptionThe following code is everything I made to create a TreeViewer with the possibility of lazy_loading, for that I created a TreeNode class for better manipulation of the data that is going to feed the tree import asyncio
from pathlib import Path
from typing import Optional, Callable, Any, Coroutine
from typing import List
from nicegui.slot import Slot
from nicegui import ui
from ..utils.logging import LoggerFactory, loggedfunction
logger = LoggerFactory.create(__name__)
logger.debug(f'Imported module {Path(__file__)}')
class TreeNode:
@loggedfunction(logger=logger)
def __init__(self, id: str, name: str, iri: str, classname: str, icon: str, header: str, body: str,
description: str,
lazy: str,
structure_mapping: int,
children: Optional[List['TreeNode']] = None):
self._lazy = lazy
self._id = id
self._name = name
self._iri = iri
self._classname = classname
self._icon = icon
self._header = header
self._body = body
self._description = description
self._structure_mapping = structure_mapping
self._children = children or []
@loggedfunction(logger=logger)
def add_children(self, children: List['TreeNode']):
self._children.extend(children)
@loggedfunction(logger=logger)
def to_dict(self):
result = {
'id': self._id,
'name': self._name,
'iri': self._iri,
'classname': self._classname,
'icon': self._icon,
'header': self._header,
'body': self._body,
'description': self._description,
'structure_mapping': self._structure_mapping,
'children': [child.to_dict() for child in self._children]
}
if self._lazy == 'true':
result['lazy'] = 'true'
return result
@classmethod
@loggedfunction(logger=logger)
def from_dict(cls, data: dict) -> 'TreeNode':
lazy = data.get('lazy', 'false')
node = cls(
id=data.get('id'),
name=data.get('name'),
iri=data.get('iri'),
classname=data.get('classname'),
icon=data.get('icon'),
header=data.get('header'),
body=data.get('body'),
description=data.get('description'),
lazy=lazy,
structure_mapping=data.get('structure_mapping'),
children=None
)
if 'children' in data and data['children']:
node.add_children([cls.from_dict(child) for child in data['children']])
return node
@property
def id(self):
return self._id
@property
def name(self):
return self._name
@property
def iri(self):
return self._iri
@property
def classname(self):
return self._classname
@property
def structure_mapping(self):
return self._structure_mapping
@property
def body(self):
return self._body
@body.setter
def body(self, value):
self._body = value
@property
def header(self):
return self._header
@header.setter
def header(self, value):
self._header = value
@property
def lazy(self):
return self._lazy
@lazy.setter
def lazy(self, value):
self._lazy = value
@property
def children(self):
return self._children
class TreeStructure:
@loggedfunction(logger=logger)
def __init__(self, classname: str, icon: str, callback: Callable[..., Coroutine[Any, Any, Any]]):
self._classname = classname
self._icon = icon
self._callback = callback
@property
def classname(self):
return self._classname
@property
def icon(self):
return self._icon
@loggedfunction(logger=logger)
async def execute_callback(self, *args, **kwargs):
try:
if callable(self._callback):
# If it's a callable (async function), call it with the provided arguments
result = await self._callback(*args, **kwargs)
elif asyncio.iscoroutine(self._callback):
# If it's already a coroutine object, just await it
result = await self._callback
else:
raise TypeError("Callback must be either a callable returning a coroutine or a coroutine object")
return result
except Exception as e:
logger.debug("Unable to execute callback function:", e)
return None
class TreeViewer(ui.tree):
"""
Tree viewer
"""
@loggedfunction(logger=logger)
def __init__(self, nodes: List[dict] = None, tree_structure: Optional[list] = None):
self._nodes = nodes
self._tree_structure = tree_structure
self._expand_nodes = []
self._selected_node = "empty"
self._create_tree()
@loggedfunction(logger=logger)
def _create_tree(self):
super().__init__(nodes=self._nodes, label_key='name', node_key='id')
super().on('lazy-load', lambda e: self.handle_lazy_load(e))
super().on('update:expanded', lambda e: self.update_expanded_nodes(e))
super().on('update:selected', lambda e: self.toggle_node_description(e))
self._header_slot: Slot = super().add_slot('default-header',
'''
<div class='flex'
v-bind:class = "(props.node.description && props.node.description!=='')?'lis-tree-node-with-description-hover':['pointer-events-none','cursor-default']"
v-bind:class = "(props.node.description && props.node.description!=='' && props.node.body==='true')?'lis-tree-node-with-description-selected':'cursor-default'"
:props="props">
<q-spinner v-if="props.node.header==='loading'" color="primary" size="xs"/>
<div :class="['text-2xl', 'leading-4', props.node.icon]" v-else> </div>
<div class='ms-1'>{{props.node.name}}</div>
</div>
''' # noqa: W291
)
self._body_slot = super().add_slot('default-body', '''
<span :props="props" v-if="props.node.description!=='' && props.node.body==='true'">{{ props.node.description }}</span>
''' # noqa: W291
)
super().expand(self._expand_nodes)
@loggedfunction(logger=logger)
async def initialize(self, tree_structure: List[TreeStructure]):
if tree_structure:
nodes = await self.create_nodes(tree_structure[0], 0, callback_args='')
self._nodes = [node.to_dict() for node in nodes]
self._create_tree()
self.refresh_tree()
@loggedfunction(logger=logger)
async def create_nodes(self, node_structure: TreeStructure, structure_mapping, callback_args: Optional[str]):
nodes = []
payload = await node_structure.execute_callback(callback_args)
lazy = 'false' if structure_mapping + 1 >= len(self._tree_structure) else 'true'
for index, item in enumerate(payload):
node_id = node_structure.classname + str(index)
if self._nodes:
if self.find_node_by_id([TreeNode.from_dict(item) for item in self._nodes], node_id) is not None:
node_id += str(index)
node = TreeNode(id=node_id, name=item.name, iri=item.__str__(),
classname=node_structure.classname,
icon=node_structure.icon, header='default', body='false', description='',
lazy=lazy,
structure_mapping=structure_mapping, children=[])
nodes.append(node)
return nodes
@loggedfunction(logger=logger)
def find_node_by_iri(self, nodes: List['TreeNode'], target_iri: str, parents: List['TreeNode']):
for node in nodes:
if node.iri == target_iri:
parents.append(node)
if len(node.children) > 0:
self.find_node_by_iri(node.children, target_iri, parents)
@loggedfunction(logger=logger)
def find_node_by_id(self, nodes: List['TreeNode'], target_id: str):
try:
for node in nodes:
if node.id == target_id:
return node
else:
result = self.find_node_by_id(node.children, target_id)
if result:
return result
except Exception as e:
return None
@loggedfunction(logger=logger)
async def handle_lazy_load(self, event):
nodes = [TreeNode.from_dict(item) for item in self._nodes]
parent = self.find_node_by_id(nodes, event.args['node']['id'])
children_structure_mapping = parent.structure_mapping + 1
if parent.lazy == 'true' and children_structure_mapping < len(self._tree_structure):
self.set_node_header(parent, 'loading', nodes)
children_nodes = await self.create_nodes(self._tree_structure[children_structure_mapping],
children_structure_mapping, parent.iri)
self.add_children_to_node(children_nodes, parent)
if event.args['node']['id'] not in self._expand_nodes:
self._expand_nodes.append(event.args['node']['id'])
self.set_node_header(parent, 'default', nodes)
@loggedfunction(logger=logger)
def add_children_to_node(self, children: List['TreeNode'], parent: TreeNode):
parent.lazy = 'false'
parent.add_children(children)
@loggedfunction(logger=logger)
def set_node_header(self, node: TreeNode, header: str, nodes: List['TreeNode']):
node.header = header
self._nodes = [node.to_dict() for node in nodes]
self.refresh_tree()
@loggedfunction(logger=logger)
def set_node_body(self, node: TreeNode, body: str, nodes: List['TreeNode']):
node.body = body
self._nodes = [node.to_dict() for node in nodes]
self.refresh_tree()
@loggedfunction(logger=logger)
def update_expanded_nodes(self, e):
self._expand_nodes = e.args
@loggedfunction(logger=logger)
def toggle_node_description(self, e):
self._selected_node = e.sender._props['selected']
nodes = [TreeNode.from_dict(item) for item in self._nodes]
if self._selected_node:
node = self.find_node_by_id(nodes, e.sender._props['selected'])
self.set_node_body(node, 'true', nodes) if node.body == 'false' \
else self.set_node_body(node, 'false', nodes)
else:
for node in nodes:
self.set_node_body(node, 'false', nodes)
@loggedfunction(logger=logger)
def refresh_tree(self):
self.delete()
self._create_tree()
@property
def nodes(self):
return self._nodes the problem I'm facing is testing this TreeViewer component, where it throws this error: @pytest.mark.asyncio
async def test_tree_viewer_initialization(self, mock_callback):
tree_structure = [
TreeStructure(
classname="RootClass",
icon=Icons.production_line,
callback=mock_callback
)
]
viewer = TreeViewer(tree_structure=tree_structure)
await viewer.initialize(viewer._tree_structure) tried multiple things to make this test work but it always falls back to this error |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 5 replies
-
Hi @mariajmoreira, I recommend looking into our documentation about testing and our pytest example. There we explain how to write pytests for a NiceGUI app. |
Beta Was this translation helpful? Give feedback.
The
User
fixture is built for tests like this, where you don't need any frontend functionality. Here's an example: