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>
318 lines
9.8 KiB
Rust
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));
|
|
}
|