Updated skill and documentation
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -522,7 +522,254 @@ expected = Input(
|
|||||||
matches(actual, expected)
|
matches(actual, expected)
|
||||||
```
|
```
|
||||||
|
|
||||||
### 5. Combining matches() and find()
|
|
||||||
|
### 5. Testing MyFastHtml Components with Test Helpers
|
||||||
|
|
||||||
|
**Goal**: Understand why test helpers exist and how they simplify testing MyFastHtml controls.
|
||||||
|
|
||||||
|
When testing components built with `mk` helpers (buttons, labels, icons), writing the expected pattern manually quickly becomes verbose. Consider testing a label with an icon:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Without test helpers - verbose and fragile
|
||||||
|
from fastcore.basics import NotStr
|
||||||
|
from fasthtml.common import Span
|
||||||
|
from myfasthtml.test.matcher import matches, Regex
|
||||||
|
|
||||||
|
actual = label_component.render()
|
||||||
|
expected = Span(
|
||||||
|
Span(NotStr('<svg name="fluent-Info"')),
|
||||||
|
Span("My Label")
|
||||||
|
)
|
||||||
|
matches(actual, expected)
|
||||||
|
```
|
||||||
|
|
||||||
|
MyFastHtml provides **test helpers** — specialized `TestObject` subclasses that encapsulate these patterns. They know how `mk` renders components and abstract away the implementation details:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# With test helpers - concise and readable
|
||||||
|
from myfasthtml.test.matcher import matches, TestLabel
|
||||||
|
|
||||||
|
actual = label_component.render()
|
||||||
|
matches(actual, TestLabel("My Label", icon="info"))
|
||||||
|
```
|
||||||
|
|
||||||
|
`TestObject` is the base class for all these helpers. You can also create your own helpers for custom components by subclassing it.
|
||||||
|
|
||||||
|
**TestObject constructor:**
|
||||||
|
|
||||||
|
| Parameter | Type | Description |
|
||||||
|
|-----------|------|-------------|
|
||||||
|
| `cls` | type or str | The element type to match (e.g., `"div"`, `"span"`, `NotStr`) |
|
||||||
|
| `**kwargs` | any | Attributes to match on the element |
|
||||||
|
|
||||||
|
**Creating a custom helper:**
|
||||||
|
|
||||||
|
```python
|
||||||
|
from myfasthtml.test.matcher import TestObject, Contains
|
||||||
|
from fasthtml.common import Div, H2
|
||||||
|
|
||||||
|
class TestCard(TestObject):
|
||||||
|
def __init__(self, title: str):
|
||||||
|
super().__init__("div")
|
||||||
|
self.attrs["cls"] = Contains("card")
|
||||||
|
self.children = [
|
||||||
|
Div(H2(title), cls="card-header")
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 6. Testing Labels with TestLabel
|
||||||
|
|
||||||
|
**Goal**: Verify elements produced by `mk.label()` — text with an optional icon and optional command.
|
||||||
|
|
||||||
|
```python
|
||||||
|
from myfasthtml.test.matcher import TestLabel
|
||||||
|
```
|
||||||
|
|
||||||
|
| Parameter | Type | Description | Default |
|
||||||
|
|-----------|------|-------------|---------|
|
||||||
|
| `label` | str | The text content to match | - |
|
||||||
|
| `icon` | str | Icon name (`snake_case` or `PascalCase`) | `None` |
|
||||||
|
| `command` | Command | Command whose HTMX params to verify | `None` |
|
||||||
|
|
||||||
|
**Example 1: Label with text only**
|
||||||
|
|
||||||
|
```python
|
||||||
|
from myfasthtml.controls.helpers import mk
|
||||||
|
from myfasthtml.test.matcher import matches, TestLabel
|
||||||
|
|
||||||
|
actual = mk.label("Settings")
|
||||||
|
matches(actual, TestLabel("Settings"))
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example 2: Label with icon**
|
||||||
|
|
||||||
|
```python
|
||||||
|
from myfasthtml.controls.helpers import mk
|
||||||
|
from myfasthtml.test.matcher import matches, TestLabel
|
||||||
|
|
||||||
|
actual = mk.label("Settings", icon="settings")
|
||||||
|
matches(actual, TestLabel("Settings", icon="settings"))
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** Icon names can be passed in `snake_case` or `PascalCase` — `TestLabel` handles the conversion automatically.
|
||||||
|
|
||||||
|
**Example 3: Label with command**
|
||||||
|
|
||||||
|
```python
|
||||||
|
from myfasthtml.controls.helpers import mk
|
||||||
|
from myfasthtml.core.commands import Command
|
||||||
|
from myfasthtml.test.matcher import matches, TestLabel
|
||||||
|
|
||||||
|
def save():
|
||||||
|
return "Saved"
|
||||||
|
|
||||||
|
save_cmd = Command("save", "Save document", save)
|
||||||
|
actual = mk.label("Save", command=save_cmd)
|
||||||
|
|
||||||
|
matches(actual, TestLabel("Save", command=save_cmd))
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 7. Testing Icons with TestIcon and TestIconNotStr
|
||||||
|
|
||||||
|
**Goal**: Verify icon elements produced by `mk.icon()`.
|
||||||
|
|
||||||
|
MyFastHtml renders icons in two ways depending on context:
|
||||||
|
- **With a wrapper** (`div` or `span`): use `TestIcon`
|
||||||
|
- **As a raw SVG `NotStr`** without wrapper: use `TestIconNotStr`
|
||||||
|
|
||||||
|
```python
|
||||||
|
from myfasthtml.test.matcher import TestIcon, TestIconNotStr
|
||||||
|
```
|
||||||
|
|
||||||
|
#### TestIcon — Icon with wrapper
|
||||||
|
|
||||||
|
| Parameter | Type | Description | Default |
|
||||||
|
|-----------|------|-------------|---------|
|
||||||
|
| `name` | str | Icon name (`snake_case` or `PascalCase`) | `''` |
|
||||||
|
| `wrapper` | str | Wrapper element: `"div"` or `"span"` | `"div"` |
|
||||||
|
| `command` | Command | Command whose HTMX params to verify | `None` |
|
||||||
|
|
||||||
|
**Example 1: Simple icon in a div**
|
||||||
|
|
||||||
|
```python
|
||||||
|
from myfasthtml.controls.helpers import mk
|
||||||
|
from myfasthtml.test.matcher import matches, TestIcon
|
||||||
|
|
||||||
|
actual = mk.icon(info_svg)
|
||||||
|
matches(actual, TestIcon("info"))
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example 2: Icon in a span with command**
|
||||||
|
|
||||||
|
```python
|
||||||
|
from myfasthtml.controls.helpers import mk
|
||||||
|
from myfasthtml.core.commands import Command
|
||||||
|
from myfasthtml.test.matcher import matches, TestIcon
|
||||||
|
|
||||||
|
delete_cmd = Command("delete", "Delete item", lambda: None)
|
||||||
|
actual = mk.icon(trash_svg, command=delete_cmd)
|
||||||
|
|
||||||
|
matches(actual, TestIcon("trash", wrapper="span", command=delete_cmd))
|
||||||
|
```
|
||||||
|
|
||||||
|
#### TestIconNotStr — Raw SVG without wrapper
|
||||||
|
|
||||||
|
Use `TestIconNotStr` when the icon appears directly as a `NotStr` in the element tree (e.g., embedded inside another element without its own wrapper).
|
||||||
|
|
||||||
|
| Parameter | Type | Description | Default |
|
||||||
|
|-----------|------|-------------|---------|
|
||||||
|
| `name` | str | Icon name (`snake_case` or `PascalCase`) | `''` |
|
||||||
|
|
||||||
|
**Example: Raw SVG icon embedded in a button**
|
||||||
|
|
||||||
|
```python
|
||||||
|
from myfasthtml.test.matcher import find, TestIconNotStr
|
||||||
|
|
||||||
|
actual = button_component.render()
|
||||||
|
|
||||||
|
icons = find(actual, TestIconNotStr("info"))
|
||||||
|
assert len(icons) == 1
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 8. Testing Commands with TestCommand
|
||||||
|
|
||||||
|
**Goal**: Verify that an element is linked to a specific command.
|
||||||
|
|
||||||
|
```python
|
||||||
|
from myfasthtml.test.matcher import TestCommand
|
||||||
|
```
|
||||||
|
|
||||||
|
| Parameter | Type | Description |
|
||||||
|
|-----------|------|-------------|
|
||||||
|
| `name` | str | The command name to match |
|
||||||
|
| `**kwargs` | any | Additional command attributes to verify |
|
||||||
|
|
||||||
|
**Example 1: Verify a button is bound to a command**
|
||||||
|
|
||||||
|
```python
|
||||||
|
from myfasthtml.controls.helpers import mk
|
||||||
|
from myfasthtml.core.commands import Command
|
||||||
|
from myfasthtml.test.matcher import find, TestCommand
|
||||||
|
|
||||||
|
delete_cmd = Command("delete_row", "Delete row", lambda: None)
|
||||||
|
button = mk.button("Delete", command=delete_cmd)
|
||||||
|
|
||||||
|
commands = find(button, TestCommand("delete_row"))
|
||||||
|
assert len(commands) == 1
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example 2: Verify command with additional attributes**
|
||||||
|
|
||||||
|
```python
|
||||||
|
from myfasthtml.test.matcher import find, TestCommand
|
||||||
|
|
||||||
|
commands = find(component.render(), TestCommand("save", target="#result"))
|
||||||
|
assert len(commands) == 1
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 9. Testing Scripts with TestScript
|
||||||
|
|
||||||
|
**Goal**: Verify the content of `<script>` elements injected by a component.
|
||||||
|
|
||||||
|
```python
|
||||||
|
from myfasthtml.test.matcher import TestScript
|
||||||
|
```
|
||||||
|
|
||||||
|
| Parameter | Type | Description |
|
||||||
|
|-----------|------|-------------|
|
||||||
|
| `script` | str | The expected script content (checked with `startswith`) |
|
||||||
|
|
||||||
|
**Example 1: Verify a script is present**
|
||||||
|
|
||||||
|
```python
|
||||||
|
from myfasthtml.test.matcher import find, TestScript
|
||||||
|
|
||||||
|
actual = widget.render()
|
||||||
|
|
||||||
|
scripts = find(actual, TestScript("initWidget("))
|
||||||
|
assert len(scripts) == 1
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example 2: Verify a specific script content**
|
||||||
|
|
||||||
|
```python
|
||||||
|
from myfasthtml.test.matcher import find, TestScript
|
||||||
|
|
||||||
|
scripts = find(actual, TestScript("document.getElementById('my-component')"))
|
||||||
|
assert len(scripts) == 1
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 10. Combining matches() and find()
|
||||||
|
|
||||||
**Goal**: First find elements, then validate them in detail.
|
**Goal**: First find elements, then validate them in detail.
|
||||||
|
|
||||||
@@ -574,7 +821,7 @@ for card in cards:
|
|||||||
matches(card, expected_card)
|
matches(card, expected_card)
|
||||||
```
|
```
|
||||||
|
|
||||||
### 6. Testing Edge Cases
|
### 11. Testing Edge Cases
|
||||||
|
|
||||||
**Testing empty elements:**
|
**Testing empty elements:**
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user