Files
rust_browser/tests/navigation_tests.rs
Zachary D. Rowitsch efa555396f Add two-tier resource cache to avoid redundant work on resize and navigation
Network layer: cache successful responses in NetworkStack so identical URLs
return cached data instead of hitting disk/network again. Reload calls
clear_cache() to force re-fetching.

App layer: preserve computed styles and image map across window resize so
only layout is re-run (the relayout fast path), skipping HTML parsing, CSS
fetching, style computation, and image decoding. Navigation clears all
cached state.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 00:15:39 -05:00

318 lines
9.8 KiB
Rust

//! Integration tests for navigation functionality.
//!
//! These tests verify the navigation lifecycle including:
//! - File URL loading
//! - Error page generation
//! - History navigation (back/forward)
//! - Reload functionality
use browser_runtime::{generate_error_html, BrowsingContext, NavigationError};
use net::NetworkStack;
use shared::BrowserUrl;
use std::fs;
use tempfile::TempDir;
/// Create a temporary HTML file and return its URL.
fn create_temp_html(dir: &TempDir, name: &str, content: &str) -> BrowserUrl {
let path = dir.path().join(name);
fs::write(&path, content).expect("Failed to write temp file");
BrowserUrl::from_file_path(&path).expect("Failed to create URL")
}
// =============================================================================
// File Navigation Tests
// =============================================================================
#[test]
fn test_file_navigation_complete_lifecycle() {
let dir = TempDir::new().unwrap();
let url = create_temp_html(&dir, "test.html", "<html><body>Test</body></html>");
// Create browsing context and network stack
let mut ctx = BrowsingContext::new();
let network = NetworkStack::new();
// Start navigation
let nav_id = ctx.navigate(url.clone());
assert!(nav_id.id() > 0);
assert!(ctx.is_navigating());
// Begin loading
assert!(ctx.begin_loading());
// Load the URL
let response = network.load_sync(&url);
assert!(response.is_ok());
let response = response.unwrap();
assert!(response.is_success());
assert!(response.body_as_string().unwrap().contains("Test"));
// Commit and complete navigation
assert!(ctx.commit_navigation());
assert_eq!(ctx.current_url(), Some(&url));
assert!(ctx.complete_navigation());
assert!(!ctx.is_navigating());
}
#[test]
fn test_navigation_to_unsupported_scheme() {
// Unsupported schemes should fail during parsing
// BrowserUrl::parse only accepts http, https, and file schemes
// Let's verify that invalid schemes are rejected at parse time
let result = BrowserUrl::parse("ftp://example.com");
assert!(result.is_err(), "ftp:// should not be a valid scheme");
}
#[test]
fn test_navigation_to_nonexistent_file() {
let network = NetworkStack::new();
let url = BrowserUrl::parse("file:///nonexistent/path/to/file.html").unwrap();
let result = network.load_sync(&url);
assert!(result.is_err());
let error = result.unwrap_err();
assert!(error.is_not_found());
}
#[test]
fn test_navigation_error_generates_error_page() {
// Test NotFound error
let error = NavigationError::NotFound("/test/file.html".to_string());
let html = generate_error_html(&error);
assert!(html.contains("Not Found"));
assert!(html.contains("/test/file.html"));
// Test network error
let error = NavigationError::NetworkError("Connection refused".to_string());
let html = generate_error_html(&error);
assert!(html.contains("Network Error"));
assert!(html.contains("Connection refused"));
// Test timeout error
let error = NavigationError::Timeout;
let html = generate_error_html(&error);
assert!(html.contains("timed out"));
}
#[test]
fn test_reload_reloads_current_url() {
let dir = TempDir::new().unwrap();
let url = create_temp_html(&dir, "reload.html", "<html>Version 1</html>");
let mut ctx = BrowsingContext::new();
let network = NetworkStack::new();
// Initial navigation
ctx.navigate(url.clone());
ctx.begin_loading();
let response = network.load_sync(&url);
assert!(response.is_ok());
ctx.commit_navigation();
ctx.complete_navigation();
ctx.push_to_history(url.clone());
// Update the file
let path = url.to_file_path().unwrap();
fs::write(&path, "<html>Version 2</html>").unwrap();
// Simulate reload by clearing cache and loading the same URL again
// (In a real browser, this would be triggered by Cmd+R which calls clear_cache())
network.clear_cache();
let reload_response = network.load_sync(&url);
assert!(reload_response.is_ok());
let body = reload_response.unwrap().body_as_string().unwrap();
assert!(body.contains("Version 2"));
// History should not change on reload
assert_eq!(ctx.history_len(), 1);
}
// =============================================================================
// History Navigation Tests
// =============================================================================
#[test]
fn test_history_back_forward() {
let dir = TempDir::new().unwrap();
let url1 = create_temp_html(&dir, "page1.html", "<html>Page 1</html>");
let url2 = create_temp_html(&dir, "page2.html", "<html>Page 2</html>");
let url3 = create_temp_html(&dir, "page3.html", "<html>Page 3</html>");
let mut ctx = BrowsingContext::new();
// Navigate to page 1
ctx.navigate(url1.clone());
ctx.commit_navigation();
ctx.push_to_history(url1.clone());
assert_eq!(ctx.history_len(), 1);
assert!(!ctx.can_go_back());
// Navigate to page 2
ctx.navigate(url2.clone());
ctx.commit_navigation();
ctx.push_to_history(url2.clone());
assert_eq!(ctx.history_len(), 2);
assert!(ctx.can_go_back());
assert!(!ctx.can_go_forward());
// Navigate to page 3
ctx.navigate(url3.clone());
ctx.commit_navigation();
ctx.push_to_history(url3.clone());
assert_eq!(ctx.history_len(), 3);
// Go back to page 2
let back_url = ctx.go_back();
assert_eq!(back_url.as_ref(), Some(&url2));
assert!(ctx.can_go_back());
assert!(ctx.can_go_forward());
// Go back to page 1
let back_url = ctx.go_back();
assert_eq!(back_url.as_ref(), Some(&url1));
assert!(!ctx.can_go_back());
assert!(ctx.can_go_forward());
// Go forward to page 2
let forward_url = ctx.go_forward();
assert_eq!(forward_url.as_ref(), Some(&url2));
assert!(ctx.can_go_back());
assert!(ctx.can_go_forward());
// Go forward to page 3
let forward_url = ctx.go_forward();
assert_eq!(forward_url.as_ref(), Some(&url3));
assert!(ctx.can_go_back());
assert!(!ctx.can_go_forward());
}
#[test]
fn test_history_navigation_clears_forward() {
let dir = TempDir::new().unwrap();
let url1 = create_temp_html(&dir, "page1.html", "<html>Page 1</html>");
let url2 = create_temp_html(&dir, "page2.html", "<html>Page 2</html>");
let url3 = create_temp_html(&dir, "page3.html", "<html>Page 3</html>");
let url4 = create_temp_html(&dir, "page4.html", "<html>Page 4</html>");
let mut ctx = BrowsingContext::new();
// Build history: 1 -> 2 -> 3
ctx.push_to_history(url1.clone());
ctx.push_to_history(url2.clone());
ctx.push_to_history(url3.clone());
assert_eq!(ctx.history_len(), 3);
// Go back to page 2
ctx.go_back();
assert!(ctx.can_go_forward());
// Navigate to new page 4 - should clear forward history
ctx.push_to_history(url4.clone());
assert_eq!(ctx.history_len(), 3); // [1, 2, 4]
assert!(!ctx.can_go_forward()); // Forward history cleared
assert!(ctx.can_go_back());
// Verify the history is [1, 2, 4]
let back1 = ctx.go_back();
assert_eq!(back1.as_ref(), Some(&url2));
let back2 = ctx.go_back();
assert_eq!(back2.as_ref(), Some(&url1));
}
// =============================================================================
// Stop/Cancel Tests
// =============================================================================
#[test]
fn test_stop_cancels_navigation() {
let mut ctx = BrowsingContext::new();
let url = BrowserUrl::parse("http://example.com/slow").unwrap();
// Start navigation
ctx.navigate(url);
ctx.begin_loading();
assert!(ctx.is_navigating());
// Cancel navigation
assert!(ctx.cancel_navigation());
assert!(ctx.has_error());
let error = ctx.error();
assert!(matches!(error, Some(NavigationError::Cancelled)));
}
#[test]
fn test_cancel_pending_network_load() {
let mut network = NetworkStack::new();
let url = BrowserUrl::parse("file:///some/file.html").unwrap();
// Start a load
let load_id = network.start_load(url);
assert!(network.has_pending());
// Cancel it before processing
let cancelled = network.cancel_pending(load_id);
assert!(cancelled);
assert!(!network.has_pending());
// Process should do nothing
network.process_pending().unwrap();
assert_eq!(network.completed_count(), 0);
}
// =============================================================================
// Edge Cases
// =============================================================================
#[test]
fn test_navigate_same_url_no_duplicate_history() {
let dir = TempDir::new().unwrap();
let url = create_temp_html(&dir, "page.html", "<html>Page</html>");
let mut ctx = BrowsingContext::new();
// Navigate to URL
ctx.push_to_history(url.clone());
assert_eq!(ctx.history_len(), 1);
// Navigate to same URL again
ctx.push_to_history(url.clone());
assert_eq!(ctx.history_len(), 1); // Should not add duplicate
}
#[test]
fn test_empty_history_operations() {
let mut ctx = BrowsingContext::new();
// Empty history operations should return None/false
assert!(!ctx.can_go_back());
assert!(!ctx.can_go_forward());
assert!(ctx.go_back().is_none());
assert!(ctx.go_forward().is_none());
assert_eq!(ctx.history_len(), 0);
assert_eq!(ctx.history_index(), -1);
}
#[test]
fn test_navigation_with_query_string() {
let url = BrowserUrl::parse("http://example.com/page?query=value&foo=bar").unwrap();
let mut ctx = BrowsingContext::new();
ctx.navigate(url.clone());
ctx.commit_navigation();
assert_eq!(ctx.current_url(), Some(&url));
}
#[test]
fn test_navigation_with_fragment() {
let url = BrowserUrl::parse("http://example.com/page#section").unwrap();
let mut ctx = BrowsingContext::new();
ctx.navigate(url.clone());
ctx.commit_navigation();
assert_eq!(ctx.current_url(), Some(&url));
}