testing

Standardized test page creation for Jupyter notebooks with FastHTML

source

create_theme_persistence_script

 create_theme_persistence_script ()

Create a JavaScript script that handles theme persistence using localStorage. This script: 1. Loads saved theme on page load 2. Listens for theme changes and saves them 3. Syncs radio button state with saved theme

Theme Selector Component

A reusable theme selector for testing:


source

create_theme_selector

 create_theme_selector (custom_themes:Optional[List[str]]=None)

Create a daisyUI theme selector dropdown component.

Type Default Details
custom_themes Optional None Optional list of custom theme names to include
Returns functools.partial(<function ft_hx at 0x7feee9f00e00>, ‘div’) Div containing theme selector dropdown

Test App Creation

A standardized way to create test apps in Jupyter notebooks:


source

create_test_app

 create_test_app (theme:Union[cjm_fasthtml_daisyui.core.themes.DaisyUIThem
                  e,str]=<DaisyUITheme.LIGHT: 'light'>, custom_css:Optiona
                  l[List[Union[str,functools.partial(<functionft_hxat0x7fe
                  ee9f00e00>,'link')]]]=None,
                  custom_js:Optional[List[Union[str,Script]]]=None,
                  custom_theme_css:Optional[str]=None, custom_theme_paths:
                  Optional[List[Union[str,pathlib.Path]]]=None,
                  custom_theme_names:Optional[List[str]]=None,
                  enable_theme_persistence:bool=True, debug:bool=True,
                  **kwargs)

Create a standardized test app for Jupyter notebooks with daisyUI and Tailwind.

Type Default Details
theme Union DaisyUITheme.LIGHT Default theme
custom_css Optional None Additional CSS
custom_js Optional None Additional JS
custom_theme_css Optional None Custom theme CSS as string
custom_theme_paths Optional None List of paths to custom theme CSS files
custom_theme_names Optional None Names of custom themes to include in selector
enable_theme_persistence bool True Enable localStorage theme persistence
debug bool True Enable debug mode
kwargs VAR_KEYWORD
Returns tuple Tuple containing (app, rt) - FastHTML app instance and route decorator

Test Page Wrapper

A wrapper for creating consistent test pages:


source

create_test_page

 create_test_page (title:str, *content, include_theme_selector:bool=True,
                   use_container:bool=True,
                   custom_theme_names:Optional[List[str]]=None)

Create a standardized test page layout with optional theme selector.

Type Default Details
title str Page title
content VAR_POSITIONAL Page content elements
include_theme_selector bool True Include theme selector
use_container bool True Wrap in container
custom_theme_names Optional None Custom themes for selector
Returns functools.partial(<function ft_hx at 0x7feee9f00e00>, ‘div’) Div containing complete page layout with navbar and content

Jupyter Notebook Utilities

Helper functions for working with FastHTML in Jupyter:


source

start_test_server

 start_test_server (app:fasthtml.core.FastHTML, port:int=8000)

*Start a test server and return the JupyUvi instance.

Usage: server = start_test_server(app) HTMX() # Display the app

# Later, in another cell:
server.stop()*
Type Default Details
app FastHTML FastHTML app instance created by create_test_app or fast_app
port int 8000 Port
Returns JupyUvi JupyUvi server instance for Jupyter notebook testing

Loading Custom Themes from CSS Files

You can also load custom themes from CSS files using Path objects:

# First, let's create a second custom theme and save it to a CSS file
from cjm_fasthtml_daisyui.core.themes import ThemeConfig, save_theme_css
from nbdev.config import get_config

# Get project directory
cfg = get_config()
project_dir = cfg.config_path
css_dir = project_dir / "css"
css_dir.mkdir(exist_ok=True, parents=True)

# Create a theme
custom_theme: ThemeConfig = {
    "name": "netwatch_cyberpunk",
    "default": False,
    "prefersdark": True,
    "color_scheme": "dark",
    "colors": {
        "base_100": "oklch(6.72% 0.000 0)",
        "base_200": "oklch(12% 0.005 240)",
        "base_300": "oklch(8% 0.01 235)",
        "base_content": "oklch(79.82% 0.136 184.06)",
        "primary": "oklch(61.39% 0.244 12.03)",
        "primary_content": "oklch(6.72% 0.000 0)",
        "secondary": "oklch(46.40% 0.184 10.98)",
        "secondary_content": "oklch(79.82% 0.136 184.06)",
        "accent": "oklch(25.77% 0.075 12.95)",
        "accent_content": "oklch(79.82% 0.136 184.06)",
        "neutral": "oklch(20% 0.01 240)",
        "neutral_content": "oklch(79.82% 0.136 184.06)",
        "info": "oklch(79.82% 0.136 184.06)",
        "info_content": "oklch(6.72% 0.000 0)",
        "success": "oklch(62% 0.20 140)",
        "success_content": "oklch(6.72% 0.000 0)",
        "warning": "oklch(75% 0.25 60)",
        "warning_content": "oklch(6.72% 0.000 0)",
        "error": "oklch(61.39% 0.244 12.03)",
        "error_content": "oklch(6.72% 0.000 0)"
    },
    "radius_selector": "0rem",
    "radius_field": "0rem",
    "radius_box": "0rem",
    "size_selector": "0.125rem",
    "size_field": "0.125rem",
    "border": "1px",
    "depth": 0,
    "noise": 0
}

# Save themes to CSS files
save_theme_css(custom_theme, css_dir / f"{custom_theme['name']}.css")

print(f"Created theme files in: {css_dir}")
Created theme files in: /mnt/SN850X_8TB_EXT4/Projects/GitHub/cj-mills/cjm-fasthtml-daisyui/css

Example Usage

Here’s how to use the testing utilities in a notebook:

from cjm_fasthtml_tailwind.core.base import combine_classes
from cjm_fasthtml_tailwind.utilities.backgrounds import bg
from cjm_fasthtml_tailwind.utilities.flexbox_and_grid import flex_wrap, gap, flex_display
from cjm_fasthtml_tailwind.utilities.layout import display_tw
from cjm_fasthtml_tailwind.utilities.spacing import m, space, p
from cjm_fasthtml_tailwind.utilities.typography import font_size, font_weight, font_family, text_color
from cjm_fasthtml_daisyui.utilities.semantic_colors import text_dui
from cjm_fasthtml_daisyui.components.actions.button import btn, btn_colors, btn_styles

from nbdev.config import get_config

# Get project directory
cfg = get_config()
project_dir = cfg.config_path
css_dir = project_dir / "css"
css_dir.mkdir(exist_ok=True, parents=True)

# Now create an app that loads custom themes from CSS files
custom_theme_paths=list(css_dir.glob("*.css"))

custom_theme_names=[theme.stem for theme in custom_theme_paths]
custom_theme_names.sort()

# Create a test app with default settings
app, rt = create_test_app(theme=DaisyUITheme.LIGHT,
                          custom_theme_paths=custom_theme_paths,
                          custom_theme_names=custom_theme_names,  # Add both custom themes to selector
                         )

# Define a test route
@rt
def index():
    return create_test_page(
        "Component Test Page",
        Div(
            H2("Test Components", cls=combine_classes(font_size._2xl, m.b(4))),
            P("This is a test page for daisyUI components.", cls=str(text_dui.base_content)),
            Div(
                # Button colors
                Button("Neutral", cls=combine_classes(btn, btn_colors.neutral)),                    
                Button("Primary", cls=combine_classes(btn, btn_colors.primary)),                    
                Button("Secondary", cls=combine_classes(btn, btn_colors.secondary)),                    
                Button("Accent", cls=combine_classes(btn, btn_colors.accent)),                    
                Button("Info", cls=combine_classes(btn, btn_colors.info)),                    
                Button("Success", cls=combine_classes(btn, btn_colors.success)),                    
                Button("Warning", cls=combine_classes(btn, btn_colors.warning)), 
                Button("Error", cls=combine_classes(btn, btn_colors.error)),
                # Soft buttons
                Button("Default", cls=combine_classes(btn, btn_styles.soft)),                    
                Button("Primary", cls=combine_classes(btn, btn_styles.soft, btn_colors.primary)),                        
                # Outline buttons
                Button("Default", cls=combine_classes(btn, btn_styles.outline)),                    
                Button("Primary", cls=combine_classes(btn, btn_styles.outline, btn_colors.primary)),                        
                # Dash buttons
                Button("Default", cls=combine_classes(btn, btn_styles.dash)),                    
                Button("Primary", cls=combine_classes(btn, btn_styles.dash, btn_colors.primary)),                        
                cls=combine_classes(flex_display, gap(2), flex_wrap.wrap)
            )
        ), 
        use_container=True,
        custom_theme_names=custom_theme_names
    )


# Start the server
server = start_test_server(app)
HTMX()
# Stop the server when done
server.stop()

Custom JavaScript Support

You can include custom JavaScript files and inline scripts in your test app:

# Create a test app with custom JavaScript

from cjm_fasthtml_tailwind.core.base import combine_classes
from cjm_fasthtml_tailwind.utilities.backgrounds import bg_linear
from cjm_fasthtml_tailwind.utilities.layout import display_tw
from cjm_fasthtml_tailwind.utilities.effects import shadow
from cjm_fasthtml_tailwind.utilities.flexbox_and_grid import flex, justify, items, flex_display
from cjm_fasthtml_tailwind.utilities.sizing import container
from cjm_fasthtml_tailwind.utilities.spacing import m, p
from cjm_fasthtml_tailwind.utilities.typography import font_size, font_weight, font_family, text_color
from cjm_fasthtml_daisyui.utilities.semantic_colors import bg_dui
from cjm_fasthtml_daisyui.utilities.semantic_gradients import from_dui, to_dui
from cjm_fasthtml_daisyui.components.data_display.card import card, card_title
from cjm_fasthtml_daisyui.components.actions.button import btn, btn_colors, btn_sizes
from cjm_fasthtml_daisyui.components.feedback.alert import alert, alert_colors


from cjm_fasthtml_daisyui.core.resources import create_js_script

# Create inline custom JavaScript that adds interactivity
custom_js_inline = Script("""
// Custom JavaScript to add click counter functionality
document.addEventListener('DOMContentLoaded', function() {
    let clickCount = 0;
    const counterElement = document.getElementById('click-counter');
    const resetButton = document.getElementById('reset-counter');
    
    // Add click listener to all buttons with 'count-click' class
    document.querySelectorAll('.count-click').forEach(button => {
        button.addEventListener('click', function() {
            clickCount++;
            if (counterElement) {
                counterElement.textContent = clickCount;
                
                // Add visual feedback
                counterElement.classList.add('text-primary', 'font-bold');
                setTimeout(() => {
                    counterElement.classList.remove('text-primary');
                }, 300);
            }
        });
    });
    
    // Reset counter functionality
    if (resetButton) {
        resetButton.addEventListener('click', function() {
            clickCount = 0;
            if (counterElement) {
                counterElement.textContent = clickCount;
            }
        });
    }
    
    // Log to console to verify script is loaded
    console.log('Custom JavaScript loaded successfully!');
});
""")

# Create a custom external JavaScript reference (example using a CDN library)
# In this case, we'll use confetti for visual effects
confetti_js = create_js_script(
    src="https://cdn.jsdelivr.net/npm/canvas-confetti@1.9.2/dist/confetti.browser.min.js",
    defer=True
)

# Create app with custom JavaScript
app, rt = create_test_app(
    theme=DaisyUITheme.CUPCAKE,
    custom_js=[confetti_js, custom_js_inline],
)

# Create a test page that uses the custom JavaScript
@rt
def index():
    return create_test_page(
        "Custom JavaScript Test",
        Div(
            # Info card
            Div(
                H2("JavaScript Integration", cls=str(card_title)),
                P("This example demonstrates custom JavaScript support in test apps."),
                P("Click the buttons below to see the click counter in action!"),
                Div(
                    "Total clicks: ",
                    Span("0", id="click-counter", cls=combine_classes(font_size._2xl, font_weight.bold)),
                    cls=combine_classes(font_size.lg, m.t(4))
                ),
                cls=combine_classes(card, bg_dui.base_200, shadow.xl, p(6), m.b(4))
            ),
            
            # Interactive buttons
            Div(
                H3("Click Counter Demo", cls=combine_classes(font_size.xl, font_weight.bold, m.b(4))),
                Div(
                    Button("Click Me!", cls=combine_classes(btn, btn_colors.primary, "count-click")),
                    Button("Me Too!", cls=combine_classes(btn, btn_colors.secondary, "count-click")),
                    Button("And Me!", cls=combine_classes(btn, btn_colors.accent, "count-click")),
                    Button("Reset Counter", id="reset-counter", cls=combine_classes(btn, btn_colors.neutral)),
                    cls=combine_classes(space.x(2))
                ),
                cls=combine_classes(m.b(6))
            ),
            
            # Confetti button
            Div(
                H3("Visual Effects Demo", cls=combine_classes(font_size.xl, font_weight.bold, m.b(4))),
                P("Using an external JavaScript library (canvas-confetti):"),
                Button(
                    "🎉 Celebrate!",
                    cls=combine_classes(btn, btn_sizes.lg, btn_colors.primary),
                    onclick="confetti({particleCount: 100, spread: 70, origin: { y: 0.6 }})"
                ),
                cls=combine_classes(m.b(6))
            ),
            
            # Console message
            Div(
                P("💡 Open your browser's developer console to see the custom JavaScript log message."),
                cls=combine_classes(alert, alert_colors.info, m.t(4))
            )
        )
    )

# Start the server
server = start_test_server(app, port=8000)
HTMX()
# Stop the server when done
server.stop()

Export