diff --git a/.claude/skills/unit-tester/SKILL.md b/.claude/skills/unit-tester/SKILL.md index 5468dfe..0ca7078 100644 --- a/.claude/skills/unit-tester/SKILL.md +++ b/.claude/skills/unit-tester/SKILL.md @@ -8,11 +8,13 @@ disable-model-invocation: false # Unit Tester Mode -You are now in **Unit Tester Mode** - specialized mode for writing unit tests for existing code in the MyFastHtml project. +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 @@ -20,86 +22,147 @@ Write comprehensive unit tests for existing code by: ## Unit Test Rules (UTR) -### UTR-1: Test Analysis Before Implementation +### UTR-1: Communication Language + +- **Conversations**: French or English (match user's language) +- **Code, documentation, comments**: English only +- Before writing tests, **list all planned tests with explanations** +- Wait for validation before implementing tests + + +### UTR-2: 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`) + +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 +### UTR-3: 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-4: 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-5: 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" + """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) + """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) +### UTR-6: Test File Organization -- 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 +**File paths:** -### UTR-4: Do NOT Test Python Built-ins +- Always specify the full file path when creating test files +- Mirror source structure: `src/myfasthtml/core/commands.py` → `tests/core/test_commands.py` + +### UTR-7: What to choose, functions or classes during tests? + +- Use **functions** when tests are validating the same concern +- Use **classes** when grouping is required, for example + - behavior tests and rendering tests can be grouped into TestControlBehaviour and TestControlRender classes + - CRUD operation can be grouped into TestCreate, TestRead, TestUpdate, TestDelete classes + - When code documentation in the class to test explicitly shows the concerns +example +```python + # ------------------------------------------------------------------ + # Data initialisation + # ------------------------------------------------------------------ +``` +or +```python + # ------------------------------------------------------------------ + # Formula management + # ------------------------------------------------------------------ +``` + +- Never mix functions and classes in the same test file + +### UTR-8: 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 + """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 + """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 +### UTR-9: Test Business Logic Only **What TO test:** + - Your business logic and algorithms - Your validation rules - Your state transformations @@ -107,65 +170,35 @@ This test validates the `add_node()` method's logic: state management, relations - Your error handling for invalid inputs - Your side effects (database updates, command registration, etc.) -### UTR-6: Test Coverage Requirements +### UTR-10: 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 -``` +### UTR-11: IMPORTANT ! Required Reading for Control Render Tests **Test organization for Controls:** @@ -173,43 +206,42 @@ Controls are classes with `__ft__()` and `render()` methods. For these component ```python class TestControlBehaviour: - """Tests for control behavior and logic.""" + """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 - 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 + """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 - ---- +**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.0: Read the matcher documentation (MANDATORY PREREQUISITE)** @@ -218,129 +250,135 @@ class TestControlRender: **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 + - `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 +- **MyFastHtml test helpers** - `TestObject`, `TestLabel`, `TestIcon`, `TestIconNotStr`, `TestCommand`, `TestScript` **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. +**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) + """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 +**Naming exception:** This specific test does NOT follow the `test_i_can_xxx` pattern (UTR-5). Use a descriptive name like `test_empty_layout_is_rendered()` instead. All other render tests follow UTR-5 normally. + **Test order:** + 1. **First test:** Global structure (UTR-11.1) -2. **Following tests:** Details of each section (UTR-11.2 to UTR-11.11) +2. **Following tests:** Details of each section (UTR-11.2 to UTR-11.14) --- -#### **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** +#### **UTR-11.2: 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) + """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) ``` ---- +#### **UTR-11.3: Break down complex tests into explicit steps** -### **HOW TO SEARCH FOR ELEMENTS** +**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 `Div()` + comment for children tested after +- Step 2+: Extraction with `find_one()` + detailed validation --- + #### **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. @@ -348,6 +386,7 @@ def test_header_has_two_sides(self, layout): **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")) @@ -361,10 +400,12 @@ 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"))) @@ -378,19 +419,17 @@ assert len(resizers) == 1, "Left drawer should contain exactly one resizer eleme --- -### **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")) @@ -410,67 +449,24 @@ 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. +**Principle:** Use `TestIcon()` or `TestIconNotStr()` depending on how the icon is integrated in the code. See `docs/testing_rendered_components.md` section 7 for full documentation. -**Difference between the two:** -- **`TestIcon("icon_name")`**: Searches for the pattern `