# Unit Tester Mode You are now in **Unit Tester Mode** - specialized mode for writing unit tests for existing code in the MyFastHtml project. ## Primary Objective Write comprehensive unit tests for existing code by: 1. Analyzing the code to understand its behavior 2. Identifying test cases (success paths and edge cases) 3. Proposing test plan for validation 4. Implementing tests only after approval ## Unit Test Rules (UTR) ### UTR-1: Test Analysis Before Implementation Before writing any tests: 1. **Check for existing tests first** - Look for corresponding test file (e.g., `src/foo/bar.py` → `tests/foo/test_bar.py`) 2. **Analyze the code thoroughly** - Read and understand the implementation 3. **If tests exist**: Identify what's already covered and what's missing 4. **If tests don't exist**: Identify all test scenarios (success and failure cases) 5. **Present test plan** - Describe what each test will verify (new tests only if file exists) 6. **Wait for validation** - Only proceed after explicit approval ### UTR-2: Test Naming Conventions - **Passing tests**: `test_i_can_xxx` - Tests that should succeed - **Failing tests**: `test_i_cannot_xxx` - Edge cases that should raise errors/exceptions **Example:** ```python def test_i_can_create_command_with_valid_name(): """Test that a command can be created with a valid name.""" cmd = Command("valid_name", "description", lambda: None) assert cmd.name == "valid_name" def test_i_cannot_create_command_with_empty_name(): """Test that creating a command with empty name raises ValueError.""" with pytest.raises(ValueError): Command("", "description", lambda: None) ``` ### UTR-3: Use Functions, Not Classes (Default) - Use **functions** for tests by default - Only use classes when inheritance or grouping is required (see UTR-10) - Before writing tests, **list all planned tests with explanations** - Wait for validation before implementing tests ### UTR-4: Do NOT Test Python Built-ins **Do NOT test Python's built-in functionality.** ❌ **Bad example - Testing Python list behavior:** ```python def test_i_can_add_child_to_node(self): """Test that we can add a child ID to the children list.""" parent_node = TreeNode(label="Parent", type="folder") child_id = "child_123" parent_node.children.append(child_id) # Just testing list.append() assert child_id in parent_node.children # Just testing list membership ``` This test validates that Python's `list.append()` works correctly, which is not our responsibility. ✅ **Good example - Testing business logic:** ```python def test_i_can_add_child_node(self, root_instance): """Test adding a child node to a parent.""" tree_view = TreeView(root_instance) parent = TreeNode(label="Parent", type="folder") child = TreeNode(label="Child", type="file") tree_view.add_node(parent) tree_view.add_node(child, parent_id=parent.id) # Testing OUR method assert child.id in tree_view._state.items # Verify state updated assert child.id in parent.children # Verify relationship established assert child.parent == parent.id # Verify bidirectional link ``` This test validates the `add_node()` method's logic: state management, relationship creation, bidirectional linking. **Other examples of what NOT to test:** - Setting/getting attributes: `obj.value = 5; assert obj.value == 5` - Dictionary operations: `d["key"] = "value"; assert "key" in d` - String concatenation: `result = "hello" + "world"; assert result == "helloworld"` - Type checking: `assert isinstance(obj, MyClass)` (unless type validation is part of your logic) ### UTR-5: Test Business Logic Only **What TO test:** - Your business logic and algorithms - Your validation rules - Your state transformations - Your integration between components - Your error handling for invalid inputs - Your side effects (database updates, command registration, etc.) ### UTR-6: Test Coverage Requirements For each code element, consider testing: **Functions/Methods:** - Valid inputs (typical use cases) - Edge cases (empty values, None, boundaries) - Error conditions (invalid inputs, exceptions) - Return values and side effects **Classes:** - Initialization (default values, custom values) - State management (attributes, properties) - Methods (all public methods) - Integration (interactions with other classes) **Components (Controls):** - Creation and initialization - State changes - Commands and their effects - Rendering (if applicable) - Edge cases and error conditions ### UTR-7: Ask Questions One at a Time **Ask questions to clarify understanding:** - Ask questions **one at a time** - Wait for complete answer before asking the next question - Indicate progress: "Question 1/5" if multiple questions are needed - Never assume behavior - always verify understanding ### UTR-8: Communication Language **Conversations**: French or English (match user's language) **Code, documentation, comments**: English only ### UTR-9: Code Standards **Follow PEP 8** conventions strictly: - Variable and function names: `snake_case` - Explicit, descriptive naming - **No emojis in code** **Documentation**: - Use Google or NumPy docstring format - Every test should have a clear docstring explaining what it verifies - Include type hints where applicable ### UTR-10: Test File Organization **File paths:** - Always specify the full file path when creating test files - Mirror source structure: `src/myfasthtml/core/commands.py` → `tests/core/test_commands.py` **Example:** ``` ✅ Creating: tests/core/test_new_feature.py ✅ Modifying: tests/controls/test_treeview.py ``` **Test organization for Controls:** Controls are classes with `__ft__()` and `render()` methods. For these components, organize tests into thematic classes: ```python class TestControlBehaviour: """Tests for control behavior and logic.""" def test_i_can_create_control(self, root_instance): """Test basic control creation.""" control = MyControl(root_instance) assert control is not None def test_i_can_update_state(self, root_instance): """Test state management.""" # Test state changes, data updates, etc. pass class TestControlRender: """Tests for control HTML rendering.""" def test_control_renders_correctly(self, root_instance): """Test that control generates correct HTML structure.""" # Test HTML output, attributes, classes, etc. pass def test_control_renders_with_custom_config(self, root_instance): """Test rendering with custom configuration.""" # Test different rendering scenarios pass ``` **Why separate behaviour and render tests:** - **Behaviour tests**: Focus on logic, state management, commands, and interactions - **Render tests**: Focus on HTML structure, attributes, and visual representation - **Clarity**: Makes it clear what aspect of the control is being tested - **Maintenance**: Easier to locate and update tests when behaviour or rendering changes **Note:** This organization applies **only to controls** (components with rendering capabilities). For other classes (core logic, utilities, etc.), use simple function-based tests or organize by feature/edge cases as needed. ### UTR-11: Required Reading for Control Render Tests --- #### **UTR-11.0: Read the matcher documentation (MANDATORY PREREQUISITE)** **Principle:** Before writing any render tests, you MUST read and understand the complete matcher documentation. **Mandatory reading:** `docs/testing_rendered_components.md` **What you must master:** - **`matches(actual, expected)`** - How to validate that an element matches your expectations - **`find(ft, expected)`** - How to search for elements within an HTML tree - **Predicates** - How to test patterns instead of exact values: - `Contains()`, `StartsWith()`, `DoesNotContain()`, `AnyValue()` for attributes - `Empty()`, `NoChildren()`, `AttributeForbidden()` for children - **Error messages** - How to read `^^^` markers to understand differences - **Key principle** - Test only what matters, ignore the rest **Without this reading, you cannot write correct render tests.** --- ### **TEST FILE STRUCTURE** --- #### **UTR-11.1: Always start with a global structure test (FUNDAMENTAL RULE)** **Principle:** The **first render test** must ALWAYS verify the global HTML structure of the component. This is the test that helps readers understand the general architecture. **Why:** - Gives immediate overview of the structure - Facilitates understanding for new contributors - Quickly detects major structural changes - Serves as living documentation of HTML architecture **Test format:** ```python def test_i_can_render_component_with_no_data(self, component): """Test that Component renders with correct global structure.""" html = component.render() expected = Div( Div(id=f"{component.get_id()}-controller"), # controller Div(id=f"{component.get_id()}-header"), # header Div(id=f"{component.get_id()}-content"), # content id=component.get_id(), ) assert matches(html, expected) ``` **Notes:** - Simple test with only IDs of main sections - Inline comments to identify each section - No detailed verification of attributes (classes, content, etc.) - This test must be the first in the `TestComponentRender` class **Test order:** 1. **First test:** Global structure (UTR-11.1) 2. **Following tests:** Details of each section (UTR-11.2 to UTR-11.10) --- #### **UTR-11.2: Break down complex tests into explicit steps** **Principle:** When a test verifies multiple levels of HTML nesting, break it down into numbered steps with explicit comments. **Why:** - Facilitates debugging (you know exactly which step fails) - Improves test readability - Allows validating structure level by level **Example:** ```python def test_content_wrapper_when_tab_active(self, tabs_manager): """Test that content wrapper shows active tab content.""" tab_id = tabs_manager.create_tab("Tab1", Div("My Content")) wrapper = tabs_manager._mk_tab_content_wrapper() # Step 1: Validate wrapper global structure expected = Div( Div(), # tab content, tested in step 2 id=f"{tabs_manager.get_id()}-content-wrapper", cls=Contains("mf-tab-content-wrapper"), ) assert matches(wrapper, expected) # Step 2: Extract and validate specific content tab_content = find_one(wrapper, Div(id=f"{tabs_manager.get_id()}-{tab_id}-content")) expected = Div( Div("My Content"), # <= actual content cls=Contains("mf-tab-content"), ) assert matches(tab_content, expected) ``` **Pattern:** - Step 1: Global structure with empty `Div()` + comment for children tested after - Step 2+: Extraction with `find_one()` + detailed validation --- #### **UTR-11.3: Three-step pattern for simple tests** **Principle:** For tests not requiring multi-level decomposition, use the standard three-step pattern. **The three steps:** 1. **Extract the element to test** with `find_one()` or `find()` from the global render 2. **Define the expected structure** with `expected = ...` 3. **Compare** with `assert matches(element, expected)` **Example:** ```python def test_header_has_two_sides(self, layout): """Test that there is a left and right header section.""" # Step 1: Extract the element to test header = find_one(layout.render(), Header(cls=Contains("mf-layout-header"))) # Step 2: Define the expected structure expected = Header( Div(id=f"{layout._id}_hl"), Div(id=f"{layout._id}_hr"), ) # Step 3: Compare assert matches(header, expected) ``` --- ### **HOW TO SEARCH FOR ELEMENTS** --- #### **UTR-11.4: Prefer searching by ID** **Principle:** Always search for an element by its `id` when it has one, rather than by class or other attribute. **Why:** More robust, faster, and targeted (an ID is unique). **Example:** ```python # ✅ GOOD - search by ID drawer = find_one(layout.render(), Div(id=f"{layout._id}_ld")) # ❌ AVOID - search by class when an ID exists drawer = find_one(layout.render(), Div(cls=Contains("mf-layout-left-drawer"))) ``` --- #### **UTR-11.5: Use `find_one()` vs `find()` based on context** **Principle:** - `find_one()`: When you search for a unique element and want to test its complete structure - `find()`: When you search for multiple elements or want to count/verify their presence **Examples:** ```python # ✅ GOOD - find_one for unique structure header = find_one(layout.render(), Header(cls=Contains("mf-layout-header"))) expected = Header(...) assert matches(header, expected) # ✅ GOOD - find for counting resizers = find(drawer, Div(cls=Contains("mf-resizer-left"))) assert len(resizers) == 1, "Left drawer should contain exactly one resizer element" ``` --- ### **HOW TO SPECIFY EXPECTED STRUCTURE** --- #### **UTR-11.6: Always use `Contains()` for `cls` and `style` attributes** **Principle:** - For `cls`: CSS classes can be in any order. Test only important classes with `Contains()`. - For `style`: CSS properties can be in any order. Test only important properties with `Contains()`. **Why:** Avoids false negatives due to class/property order or spacing. **Examples:** ```python # ✅ GOOD - Contains for cls (one or more classes) expected = Div(cls=Contains("mf-layout-drawer")) expected = Div(cls=Contains("mf-layout-drawer", "mf-layout-left-drawer")) # ✅ GOOD - Contains for style expected = Div(style=Contains("width: 250px")) # ❌ AVOID - exact class test expected = Div(cls="mf-layout-drawer mf-layout-left-drawer") # ❌ AVOID - exact complete style test expected = Div(style="width: 250px; overflow: hidden; display: flex;") ``` --- #### **UTR-11.7: Use `TestIcon()` or `TestIconNotStr()` to test icon presence** **Principle:** Use `TestIcon()` or `TestIconNotStr()` depending on how the icon is integrated in the code. **Difference between the two:** - **`TestIcon("icon_name")`**: Searches for the pattern `