feat(ads/seo/release): v1.2.0 with live Google Ads, isolated ad profile, ads.txt, and SEO/AEO/GEO optimization
This commit is contained in:
parent
a88aafa1e5
commit
0e568f1f0a
975 changed files with 130593 additions and 16783 deletions
416
tests/Paca.Tests/ModernBrowsingMilestone13Phase113Tests.cs
Normal file
416
tests/Paca.Tests/ModernBrowsingMilestone13Phase113Tests.cs
Normal file
|
|
@ -0,0 +1,416 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Paca.Browser.Theming;
|
||||
using Xunit;
|
||||
|
||||
namespace Paca.Tests;
|
||||
|
||||
public sealed class ModernBrowsingMilestone13Phase113Tests : IDisposable
|
||||
{
|
||||
private readonly string _tempDir;
|
||||
|
||||
public ModernBrowsingMilestone13Phase113Tests()
|
||||
{
|
||||
_tempDir = Path.Combine(Path.GetTempPath(), "paca_m13_test_" + Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(_tempDir);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Directory.Exists(_tempDir)) Directory.Delete(_tempDir, true);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Gate189_ToolbarPaletteManager_ManagesOrderVisibilityAndPersistence()
|
||||
{
|
||||
// Arrange
|
||||
var configPath = Path.Combine(_tempDir, "toolbar_palette.json");
|
||||
var manager = new ToolbarPaletteManager(configPath);
|
||||
|
||||
// Act 1: Verify default items
|
||||
var items = manager.GetAllItems();
|
||||
Assert.NotEmpty(items);
|
||||
Assert.Contains(items, x => x.Id == "back");
|
||||
Assert.Contains(items, x => x.Id == "forward");
|
||||
Assert.Contains(items, x => x.Id == "omnibox");
|
||||
Assert.Contains(items, x => x.Id == "settings");
|
||||
Assert.Contains(items, x => x.Id == "bookmarks");
|
||||
|
||||
// Act 2: Visibility toggle
|
||||
var layoutChangedFired = false;
|
||||
manager.LayoutChanged += (_, _) => layoutChangedFired = true;
|
||||
|
||||
var bookmarksItem = manager.GetItem("bookmarks");
|
||||
Assert.NotNull(bookmarksItem);
|
||||
Assert.True(bookmarksItem.IsVisible);
|
||||
|
||||
var toggled = manager.SetVisibility("bookmarks", false);
|
||||
Assert.True(toggled);
|
||||
Assert.True(layoutChangedFired);
|
||||
|
||||
var visibleItems = manager.GetVisibleItems();
|
||||
Assert.DoesNotContain(visibleItems, x => x.Id == "bookmarks");
|
||||
Assert.Contains(manager.GetAllItems(), x => x.Id == "bookmarks" && !x.IsVisible);
|
||||
|
||||
// Omnibox is essential, cannot be hidden if CanHide is false
|
||||
var omniboxItem = manager.GetItem("omnibox");
|
||||
Assert.NotNull(omniboxItem);
|
||||
Assert.False(omniboxItem.CanHide);
|
||||
var hideOmniboxResult = manager.SetVisibility("omnibox", false);
|
||||
Assert.False(hideOmniboxResult);
|
||||
Assert.True(manager.GetItem("omnibox")!.IsVisible);
|
||||
|
||||
// Act 3: Move item (drag and drop)
|
||||
// Move "settings" to index 0
|
||||
var moved = manager.MoveItem("settings", 0);
|
||||
Assert.True(moved);
|
||||
Assert.Equal("settings", manager.GetAllItems()[0].Id);
|
||||
|
||||
// Act 4: Bulk reorder
|
||||
var currentIds = manager.GetAllItems().Select(i => i.Id).Reverse().ToList();
|
||||
var reordered = manager.Reorder(currentIds);
|
||||
Assert.True(reordered);
|
||||
Assert.Equal(currentIds, manager.GetAllItems().Select(i => i.Id).ToList());
|
||||
|
||||
// Act 5: Save & reload persistence
|
||||
manager.Save();
|
||||
Assert.True(File.Exists(configPath));
|
||||
|
||||
var reloaded = new ToolbarPaletteManager(configPath);
|
||||
Assert.Equal(currentIds, reloaded.GetAllItems().Select(i => i.Id).ToList());
|
||||
Assert.False(reloaded.GetItem("bookmarks")!.IsVisible);
|
||||
|
||||
// Act 6: Reset to default
|
||||
reloaded.ResetToDefault();
|
||||
Assert.True(reloaded.GetItem("bookmarks")!.IsVisible);
|
||||
Assert.Equal("back", reloaded.GetAllItems()[0].Id);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Gate190_StatusBarModuleCoordinator_CoordinatesZoomHostAndResourceStats()
|
||||
{
|
||||
// Arrange
|
||||
var coordinator = new StatusBarModuleCoordinator();
|
||||
var stateChangedCount = 0;
|
||||
coordinator.StateChanged += (_, _) => stateChangedCount++;
|
||||
|
||||
// Act 1: Zoom slider defaults and controls
|
||||
Assert.Equal(1.0, coordinator.Zoom.ZoomFactor);
|
||||
Assert.Equal(100, coordinator.Zoom.ZoomPercentage);
|
||||
Assert.True(coordinator.IsZoomModuleVisible);
|
||||
|
||||
coordinator.ZoomIn();
|
||||
Assert.True(coordinator.Zoom.ZoomFactor > 1.0);
|
||||
Assert.True(stateChangedCount > 0);
|
||||
|
||||
coordinator.ZoomOut();
|
||||
coordinator.ZoomOut();
|
||||
Assert.True(coordinator.Zoom.ZoomFactor < 1.0);
|
||||
|
||||
coordinator.SetZoom(2.5);
|
||||
Assert.Equal(2.5, coordinator.Zoom.ZoomFactor);
|
||||
Assert.Equal(250, coordinator.Zoom.ZoomPercentage);
|
||||
|
||||
// Zoom clamping
|
||||
coordinator.SetZoom(10.0);
|
||||
Assert.Equal(coordinator.Zoom.MaxZoom, coordinator.Zoom.ZoomFactor);
|
||||
|
||||
coordinator.SetZoom(0.01);
|
||||
Assert.Equal(coordinator.Zoom.MinZoom, coordinator.Zoom.ZoomFactor);
|
||||
|
||||
coordinator.ResetZoom();
|
||||
Assert.Equal(1.0, coordinator.Zoom.ZoomFactor);
|
||||
Assert.Equal(100, coordinator.Zoom.ZoomPercentage);
|
||||
|
||||
// Act 2: Host / IP Indicator
|
||||
coordinator.UpdateHost("github.com", "140.82.121.4", isSecure: true, port: 443);
|
||||
Assert.Equal("github.com", coordinator.HostIndicator.Host);
|
||||
Assert.Equal("140.82.121.4", coordinator.HostIndicator.IpAddress);
|
||||
Assert.True(coordinator.HostIndicator.IsSecure);
|
||||
Assert.Equal(443, coordinator.HostIndicator.Port);
|
||||
Assert.Contains("github.com", coordinator.HostIndicator.DisplayText);
|
||||
Assert.Contains("140.82.121.4", coordinator.HostIndicator.DisplayText);
|
||||
|
||||
coordinator.ClearHost();
|
||||
Assert.Equal(string.Empty, coordinator.HostIndicator.Host);
|
||||
Assert.Null(coordinator.HostIndicator.IpAddress);
|
||||
|
||||
// Act 3: Resource Size & Request Counter
|
||||
coordinator.AddResource(1024 * 512); // 512 KB
|
||||
Assert.Equal(512 * 1024, coordinator.ResourceCounter.TransferredBytes);
|
||||
Assert.Equal(1, coordinator.ResourceCounter.RequestCount);
|
||||
Assert.Contains("512 KB", coordinator.ResourceCounter.FormattedSize);
|
||||
|
||||
coordinator.AddResource(1024 * 1024 * 2); // +2 MB -> ~2.5 MB
|
||||
Assert.Equal(2, coordinator.ResourceCounter.RequestCount);
|
||||
Assert.Contains("2.5 MB", coordinator.ResourceCounter.FormattedSize);
|
||||
Assert.Contains("2 requests", coordinator.ResourceCounter.FormattedSummary);
|
||||
|
||||
coordinator.ResetResourceStats();
|
||||
Assert.Equal(0, coordinator.ResourceCounter.TransferredBytes);
|
||||
Assert.Equal(0, coordinator.ResourceCounter.RequestCount);
|
||||
Assert.Equal("0 B", coordinator.ResourceCounter.FormattedSize);
|
||||
|
||||
// Act 4: Module Visibility Toggles
|
||||
coordinator.IsResourceCounterVisible = false;
|
||||
Assert.False(coordinator.IsResourceCounterVisible);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Gate191_ThemeAutoScheduler_CalculatesScheduledAndSolarThemes()
|
||||
{
|
||||
// Arrange
|
||||
var scheduler = new ThemeAutoScheduler();
|
||||
|
||||
// Act 1: Explicit theme modes
|
||||
scheduler.Mode = ThemeMode.Light;
|
||||
Assert.Equal(AppTheme.Light, scheduler.GetEffectiveTheme(DateTime.Parse("2026-09-13 23:00:00")));
|
||||
|
||||
scheduler.Mode = ThemeMode.Dark;
|
||||
Assert.Equal(AppTheme.Dark, scheduler.GetEffectiveTheme(DateTime.Parse("2026-09-13 12:00:00")));
|
||||
|
||||
scheduler.Mode = ThemeMode.System;
|
||||
Assert.Equal(AppTheme.Light, scheduler.GetEffectiveTheme(DateTime.Now, systemTheme: AppTheme.Light));
|
||||
Assert.Equal(AppTheme.Dark, scheduler.GetEffectiveTheme(DateTime.Now, systemTheme: AppTheme.Dark));
|
||||
|
||||
// Act 2: Scheduled Window spanning midnight (20:00 to 07:00 is Dark)
|
||||
scheduler.Mode = ThemeMode.Scheduled;
|
||||
scheduler.ScheduledWindow = new TimeWindow(
|
||||
DarkStartTime: new TimeOnly(20, 0),
|
||||
DarkEndTime: new TimeOnly(7, 0));
|
||||
|
||||
// 20:00 -> Dark
|
||||
Assert.Equal(AppTheme.Dark, scheduler.GetEffectiveTheme(DateTime.Parse("2026-09-13 20:00:00")));
|
||||
// 23:30 -> Dark
|
||||
Assert.Equal(AppTheme.Dark, scheduler.GetEffectiveTheme(DateTime.Parse("2026-09-13 23:30:00")));
|
||||
// 03:15 -> Dark
|
||||
Assert.Equal(AppTheme.Dark, scheduler.GetEffectiveTheme(DateTime.Parse("2026-09-13 03:15:00")));
|
||||
// 06:59 -> Dark
|
||||
Assert.Equal(AppTheme.Dark, scheduler.GetEffectiveTheme(DateTime.Parse("2026-09-13 06:59:59")));
|
||||
// 07:00 -> Light
|
||||
Assert.Equal(AppTheme.Light, scheduler.GetEffectiveTheme(DateTime.Parse("2026-09-13 07:00:00")));
|
||||
// 12:00 -> Light
|
||||
Assert.Equal(AppTheme.Light, scheduler.GetEffectiveTheme(DateTime.Parse("2026-09-13 12:00:00")));
|
||||
// 19:59 -> Light
|
||||
Assert.Equal(AppTheme.Light, scheduler.GetEffectiveTheme(DateTime.Parse("2026-09-13 19:59:59")));
|
||||
|
||||
// Act 3: Solar Sunrise/Sunset calculation (Seoul, Lat 37.5665, Lon 126.9780)
|
||||
scheduler.Mode = ThemeMode.Solar;
|
||||
scheduler.Coordinates = new SolarCoordinates(37.5665, 126.9780);
|
||||
|
||||
var date = new DateTime(2026, 9, 13);
|
||||
var solarTimes = scheduler.CalculateSolarTimes(date, 37.5665, 126.9780);
|
||||
|
||||
// Sunrise should be morning (~6 AM), Sunset evening (~18:30 PM)
|
||||
Assert.True(solarTimes.Sunrise.Hour >= 5 && solarTimes.Sunrise.Hour <= 7, $"Sunrise was {solarTimes.Sunrise}");
|
||||
Assert.True(solarTimes.Sunset.Hour >= 18 && solarTimes.Sunset.Hour <= 20, $"Sunset was {solarTimes.Sunset}");
|
||||
|
||||
// Midday 12:00 -> Light
|
||||
var noon = new DateTime(2026, 9, 13, 12, 0, 0);
|
||||
Assert.Equal(AppTheme.Light, scheduler.GetEffectiveTheme(noon));
|
||||
|
||||
// Midnight 00:00 -> Dark
|
||||
var midnight = new DateTime(2026, 9, 13, 0, 0, 0);
|
||||
Assert.Equal(AppTheme.Dark, scheduler.GetEffectiveTheme(midnight));
|
||||
|
||||
// 23:00 -> Dark
|
||||
var night = new DateTime(2026, 9, 13, 23, 0, 0);
|
||||
Assert.Equal(AppTheme.Dark, scheduler.GetEffectiveTheme(night));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Gate192_SpeedDialFolderManager_ManagesThumbnailsAndHierarchies()
|
||||
{
|
||||
// Arrange
|
||||
var configPath = Path.Combine(_tempDir, "speed_dial.json");
|
||||
var manager = new SpeedDialFolderManager(configPath);
|
||||
|
||||
// Act 1: Add root tiles
|
||||
var githubTile = manager.AddTile("GitHub", "https://github.com");
|
||||
var hnTile = manager.AddTile("Hacker News", "https://news.ycombinator.com");
|
||||
Assert.NotNull(githubTile);
|
||||
Assert.NotNull(hnTile);
|
||||
Assert.Equal(2, manager.GetTilesInFolder(null).Count);
|
||||
|
||||
// Act 2: Custom thumbnail override
|
||||
var customThumb = "https://assets.example.com/thumbnails/gh-custom.png";
|
||||
var thumbUpdated = manager.SetCustomThumbnail(githubTile.Id, customThumb);
|
||||
Assert.True(thumbUpdated);
|
||||
Assert.Equal(customThumb, manager.GetTile(githubTile.Id)!.CustomThumbnailUrl);
|
||||
|
||||
// Act 3: Create folder grouping hierarchies
|
||||
var devFolder = manager.CreateFolder("Development");
|
||||
var newsFolder = manager.CreateFolder("News");
|
||||
var aiFolder = manager.CreateFolder("AI Tools", parentFolderId: devFolder.Id);
|
||||
|
||||
Assert.NotNull(devFolder);
|
||||
Assert.NotNull(newsFolder);
|
||||
Assert.NotNull(aiFolder);
|
||||
|
||||
// Subfolder query
|
||||
var devSubfolders = manager.GetSubFolders(devFolder.Id);
|
||||
Assert.Single(devSubfolders);
|
||||
Assert.Equal("AI Tools", devSubfolders[0].Name);
|
||||
|
||||
// Breadcrumbs for nested folder
|
||||
var breadcrumbs = manager.GetBreadcrumbs(aiFolder.Id);
|
||||
Assert.Equal(2, breadcrumbs.Count);
|
||||
Assert.Equal("Development", breadcrumbs[0].Name);
|
||||
Assert.Equal("AI Tools", breadcrumbs[1].Name);
|
||||
|
||||
// Act 4: Move tile into folders
|
||||
manager.MoveTile(githubTile.Id, devFolder.Id);
|
||||
var openaiTile = manager.AddTile("OpenAI", "https://openai.com", folderId: aiFolder.Id);
|
||||
|
||||
var devTiles = manager.GetTilesInFolder(devFolder.Id);
|
||||
Assert.Single(devTiles);
|
||||
Assert.Equal("GitHub", devTiles[0].Title);
|
||||
|
||||
var aiTiles = manager.GetTilesInFolder(aiFolder.Id);
|
||||
Assert.Single(aiTiles);
|
||||
Assert.Equal("OpenAI", aiTiles[0].Title);
|
||||
|
||||
// Root tiles now only contains Hacker News
|
||||
var rootTiles = manager.GetTilesInFolder(null);
|
||||
Assert.Single(rootTiles);
|
||||
Assert.Equal("Hacker News", rootTiles[0].Title);
|
||||
|
||||
// Act 5: Persistence save & reload
|
||||
manager.Save();
|
||||
Assert.True(File.Exists(configPath));
|
||||
|
||||
var reloaded = new SpeedDialFolderManager(configPath);
|
||||
Assert.Equal(customThumb, reloaded.GetTile(githubTile.Id)!.CustomThumbnailUrl);
|
||||
Assert.Equal(devFolder.Id, reloaded.GetTile(githubTile.Id)!.FolderId);
|
||||
Assert.Single(reloaded.GetTilesInFolder(aiFolder.Id));
|
||||
|
||||
// Act 6: Folder deletion handling (without deleting children: moves children to parent folder)
|
||||
var removed = reloaded.RemoveFolder(aiFolder.Id, deleteChildren: false);
|
||||
Assert.True(removed);
|
||||
// OpenAI tile should now be moved up to Development folder
|
||||
var devTilesAfterDelete = reloaded.GetTilesInFolder(devFolder.Id);
|
||||
Assert.Contains(devTilesAfterDelete, t => t.Title == "OpenAI");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Gate193_UiLanguageSwitcher_SwitchesLanguagesAndLocalizesStrings()
|
||||
{
|
||||
// Arrange
|
||||
var switcher = new UiLanguageSwitcher(defaultCulture: "en-US");
|
||||
|
||||
// Act 1: Default English strings
|
||||
Assert.Equal("en-US", switcher.CurrentLanguage);
|
||||
Assert.Contains("ko-KR", switcher.SupportedLanguages);
|
||||
Assert.Contains("ja-JP", switcher.SupportedLanguages);
|
||||
|
||||
Assert.Equal("Search or enter address", switcher.GetString("Omnibox_Placeholder"));
|
||||
Assert.Equal("Settings", switcher["Settings_Title"]);
|
||||
|
||||
// Act 2: Switch to Korean (ko-KR)
|
||||
string? previousLang = null;
|
||||
string? newLang = null;
|
||||
switcher.LanguageChanged += (_, e) =>
|
||||
{
|
||||
previousLang = e.PreviousLanguage;
|
||||
newLang = e.NewLanguage;
|
||||
};
|
||||
|
||||
var switchResultKo = switcher.SetLanguage("ko-KR");
|
||||
Assert.True(switchResultKo);
|
||||
Assert.Equal("ko-KR", switcher.CurrentLanguage);
|
||||
Assert.Equal("en-US", previousLang);
|
||||
Assert.Equal("ko-KR", newLang);
|
||||
|
||||
Assert.Equal("검색어 또는 웹 주소 입력", switcher.GetString("Omnibox_Placeholder"));
|
||||
Assert.Equal("설정", switcher["Settings_Title"]);
|
||||
Assert.Equal("새 탭", switcher["NewTab_Title"]);
|
||||
|
||||
// Act 3: Switch to Japanese (ja-JP)
|
||||
var switchResultJa = switcher.SetLanguage("ja-JP");
|
||||
Assert.True(switchResultJa);
|
||||
Assert.Equal("ja-JP", switcher.CurrentLanguage);
|
||||
Assert.Equal("検索またはアドレスを入力", switcher.GetString("Omnibox_Placeholder"));
|
||||
Assert.Equal("設定", switcher["Settings_Title"]);
|
||||
|
||||
// Act 4: Fallback for missing string
|
||||
// If a key doesn't exist in ja-JP, it falls back to en-US, or returns key itself
|
||||
Assert.Equal("NonExistent_Key_XYZ", switcher.GetString("NonExistent_Key_XYZ"));
|
||||
|
||||
// Act 5: Custom translation registration and string formatting
|
||||
switcher.RegisterTranslations("fr-FR", new Dictionary<string, string>
|
||||
{
|
||||
["Omnibox_Placeholder"] = "Rechercher ou entrer une adresse",
|
||||
["Tab_Count_Format"] = "{0} onglets ouverts"
|
||||
});
|
||||
|
||||
switcher.SetLanguage("fr-FR");
|
||||
Assert.Equal("Rechercher ou entrer une adresse", switcher.GetString("Omnibox_Placeholder"));
|
||||
Assert.Equal("5 onglets ouverts", switcher.GetString("Tab_Count_Format", 5));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Gate194_ScrollAutoHideCoordinator_ThresholdsAndAnimatesHeader()
|
||||
{
|
||||
// Arrange
|
||||
// Header height = 60, scroll threshold = 20
|
||||
var coordinator = new ScrollAutoHideCoordinator(headerHeight: 60.0, threshold: 20.0);
|
||||
|
||||
var offsetEvents = new List<double>();
|
||||
coordinator.OffsetChanged += (_, offset) => offsetEvents.Add(offset);
|
||||
|
||||
// Act 1: Initial state
|
||||
Assert.Equal(0.0, coordinator.CurrentOffset);
|
||||
Assert.Equal(1.0, coordinator.VisibilityFraction);
|
||||
Assert.Equal(HeaderVisibilityState.FullyVisible, coordinator.State);
|
||||
|
||||
// Act 2: Downward scroll below threshold (10px < 20px threshold)
|
||||
coordinator.ProcessScrollDelta(deltaY: 10.0, currentScrollY: 100.0);
|
||||
Assert.Equal(0.0, coordinator.CurrentOffset);
|
||||
Assert.Equal(1.0, coordinator.VisibilityFraction);
|
||||
Assert.Equal(HeaderVisibilityState.FullyVisible, coordinator.State);
|
||||
|
||||
// Act 3: Downward scroll exceeding threshold (+25px total delta > 20px threshold -> 15px over)
|
||||
// Accumulated 10 + 25 = 35 -> 15px over threshold -> offset = -15
|
||||
coordinator.ProcessScrollDelta(deltaY: 25.0, currentScrollY: 125.0);
|
||||
Assert.Equal(-15.0, coordinator.CurrentOffset);
|
||||
Assert.Equal(45.0 / 60.0, coordinator.VisibilityFraction);
|
||||
Assert.Equal(HeaderVisibilityState.PartiallyHidden, coordinator.State);
|
||||
Assert.NotEmpty(offsetEvents);
|
||||
|
||||
// Act 4: Scroll down further until fully hidden
|
||||
coordinator.ProcessScrollDelta(deltaY: 100.0, currentScrollY: 225.0);
|
||||
Assert.Equal(-60.0, coordinator.CurrentOffset);
|
||||
Assert.Equal(0.0, coordinator.VisibilityFraction);
|
||||
Assert.Equal(HeaderVisibilityState.FullyHidden, coordinator.State);
|
||||
|
||||
// Act 5: Upward scroll below threshold (-10px upward < 20px threshold)
|
||||
coordinator.ProcessScrollDelta(deltaY: -10.0, currentScrollY: 215.0);
|
||||
Assert.Equal(-60.0, coordinator.CurrentOffset);
|
||||
Assert.Equal(HeaderVisibilityState.FullyHidden, coordinator.State);
|
||||
|
||||
// Act 6: Upward scroll exceeding threshold (-25px upward -> accumulated -35 -> 15px over)
|
||||
// Offset moves from -60 to -60 + 15 = -45
|
||||
coordinator.ProcessScrollDelta(deltaY: -25.0, currentScrollY: 190.0);
|
||||
Assert.Equal(-45.0, coordinator.CurrentOffset);
|
||||
Assert.Equal(15.0 / 60.0, coordinator.VisibilityFraction);
|
||||
Assert.Equal(HeaderVisibilityState.PartiallyHidden, coordinator.State);
|
||||
|
||||
// Act 7: Snap to fully visible when scrolled to top of page (currentScrollY <= 0)
|
||||
coordinator.ProcessScrollDelta(deltaY: 5.0, currentScrollY: 0.0);
|
||||
Assert.Equal(0.0, coordinator.CurrentOffset);
|
||||
Assert.Equal(1.0, coordinator.VisibilityFraction);
|
||||
Assert.Equal(HeaderVisibilityState.FullyVisible, coordinator.State);
|
||||
|
||||
// Act 8: Pinning disables auto-hide
|
||||
coordinator.IsPinned = true;
|
||||
coordinator.ProcessScrollDelta(deltaY: 200.0, currentScrollY: 500.0);
|
||||
Assert.Equal(0.0, coordinator.CurrentOffset);
|
||||
Assert.Equal(HeaderVisibilityState.FullyVisible, coordinator.State);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue