feat(mobile): advance Paca.Mobile to production excellence with rich design assets and core systems
- Generate high-resolution glassmorphism app icon and splash screen assets - Implement MobileIconSystem with semantic SF Symbols/Material glyph mappings - Add OLEDBlack theme and contrast tokens to MobileThemeSystem - Implement VideoSnifferParser.NegotiateBestQuality for multi-stream quality resolution - Enhance DiscoverPage, FavoritesPage, and SyncSettingsPage with production controls - Add Gate 103-114 test suites with 100% test pass rate
This commit is contained in:
parent
dbc50abb71
commit
f1949024fc
81 changed files with 8611 additions and 0 deletions
199
tests/Paca.Tests/Mobile/MobileProductionExcellenceGateTests.cs
Normal file
199
tests/Paca.Tests/Mobile/MobileProductionExcellenceGateTests.cs
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
using Paca.Mobile;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Paca.Mobile.Core;
|
||||
using Xunit;
|
||||
|
||||
namespace Paca.Tests.Mobile;
|
||||
|
||||
/// <summary>
|
||||
/// 모바일 프로덕션 레벨 종합 고도화 게이트 테스트 (Gate 103 ~ Gate 110)
|
||||
/// - 아이콘 시스템, 테마 시스템, 탭 시스템, 즐겨찾기, 영상 찾기, 재시도 큐 관리, 설정, 탭 동기화
|
||||
/// </summary>
|
||||
public class MobileProductionExcellenceGateTests
|
||||
{
|
||||
// [Gate 103] 아이콘 시스템: 이모지 배제 및 시맨틱 아이콘 글리프 매핑 무결성
|
||||
[Theory]
|
||||
[InlineData(MobileIconKind.Search, "magnifyingglass")]
|
||||
[InlineData(MobileIconKind.Queue, "list.bullet")]
|
||||
[InlineData(MobileIconKind.Download, "arrow.down.circle")]
|
||||
[InlineData(MobileIconKind.Favorites, "star.fill")]
|
||||
[InlineData(MobileIconKind.Settings, "gearshape.fill")]
|
||||
[InlineData(MobileIconKind.Retry, "arrow.clockwise")]
|
||||
public void Gate103_MobileIconSystem_SemanticGlyph_Audit(MobileIconKind kind, string expectedGlyph)
|
||||
{
|
||||
var glyph = MobileIconSystem.GetGlyph(kind);
|
||||
Assert.Equal(expectedGlyph, glyph);
|
||||
// 유니코드 이모지가 포함되지 않았는지 검증
|
||||
Assert.DoesNotMatch(@"[\uD83C-\uDBFF\uDC00-\uDFFF]", glyph);
|
||||
}
|
||||
|
||||
// [Gate 104] 테마 시스템: OLED Black을 포함한 4대 테마 토큰 및 명도 대비
|
||||
[Theory]
|
||||
[InlineData(MobileThemeMode.Dark, "#0B0C10", "#161822", "#FFFFFF", "#5E56E8")]
|
||||
[InlineData(MobileThemeMode.Light, "#F8FAFC", "#FFFFFF", "#0F172A", "#4F46E5")]
|
||||
[InlineData(MobileThemeMode.OLEDBlack, "#000000", "#090A0F", "#FFFFFF", "#00F0FF")]
|
||||
[InlineData(MobileThemeMode.HighContrast, "#000000", "#000000", "#FFFFFF", "#FFFF00")]
|
||||
public void Gate104_MobileThemeSystem_Tokens_And_Contrast_Audit(
|
||||
MobileThemeMode mode, string bg, string card, string text, string accent)
|
||||
{
|
||||
var p = MobileThemeSystem.GetPalette(mode);
|
||||
Assert.Equal(bg, p.Background);
|
||||
Assert.Equal(card, p.CardBackground);
|
||||
Assert.Equal(text, p.TextPrimary);
|
||||
Assert.Equal(accent, p.Accent);
|
||||
}
|
||||
|
||||
// [Gate 105] 탭 시스템: 탭 추가, 전환, 닫기, 시크릿 탭 모드 라이프사이클
|
||||
[Fact]
|
||||
public void Gate105_MobileTabManager_MultiTab_Lifecycle_Audit()
|
||||
{
|
||||
var tm = new MobileTabManager();
|
||||
var tab1 = tm.NewTab("https://www.youtube.com", "YouTube");
|
||||
var tab2 = tm.NewTab("https://vimeo.com", "Vimeo", isIncognito: true);
|
||||
|
||||
Assert.Equal(3, tm.Tabs.Count);
|
||||
Assert.Equal(tab2.Id, tm.ActiveTab?.Id);
|
||||
Assert.True(tab2.IsIncognito);
|
||||
|
||||
// 탭 1로 전환
|
||||
tm.SwitchTab(tab1.Id);
|
||||
Assert.Equal(tab1.Id, tm.ActiveTab?.Id);
|
||||
|
||||
// 탭 닫기
|
||||
tm.CloseTab(tab2.Id);
|
||||
Assert.Equal(2, tm.Tabs.Count);
|
||||
Assert.Equal(tab1.Id, tm.ActiveTab?.Id);
|
||||
}
|
||||
|
||||
// [Gate 106] 즐겨찾기 & 보관함(Stash): 태그 필터링 및 다운로드 대기 큐 연계
|
||||
[Fact]
|
||||
public void Gate106_MobileFavoritesManager_TagFilter_And_Stash_Audit()
|
||||
{
|
||||
var fav = new MobileFavoritesManager();
|
||||
fav.AddFavorite("https://youtube.com/v1", "Rock Festival Live", "음악");
|
||||
fav.AddFavorite("https://youtube.com/v2", "Dev Conference 2026", "기술");
|
||||
var f3 = fav.AddFavorite("https://youtube.com/v3", "Jazz Night", "음악");
|
||||
|
||||
fav.ToggleStash(f3.Id);
|
||||
|
||||
var musicList = fav.GetByTag("음악");
|
||||
Assert.Equal(2, musicList.Count);
|
||||
|
||||
var stashed = fav.GetStashedItems();
|
||||
Assert.Single(stashed);
|
||||
Assert.Equal(f3.Id, stashed[0].Id);
|
||||
}
|
||||
|
||||
// [Gate 107] 영상 찾기(스니퍼 탐색): 플랫폼 감지 및 메타데이터 추출
|
||||
[Fact]
|
||||
public void Gate107_MobileDiscoveryEngine_PlatformDetection_And_FormatFilter_Audit()
|
||||
{
|
||||
var res1 = MobileDiscoveryEngine.DetectPlatform("https://youtu.be/abcdefg");
|
||||
Assert.True(res1.IsSupported);
|
||||
Assert.Equal("YouTube", res1.PlatformName);
|
||||
Assert.Contains("1080p", res1.RecommendedQualities);
|
||||
|
||||
var res2 = MobileDiscoveryEngine.DetectPlatform("https://example.com/live.m3u8");
|
||||
Assert.True(res2.IsSupported);
|
||||
Assert.Equal("HLS Stream", res2.PlatformName);
|
||||
}
|
||||
|
||||
// [Gate 108] 영상 재시도 및 큐 관리: 우선순위 핀, 실패 재시도, 일괄 비우기
|
||||
[Fact]
|
||||
public void Gate108_MobileQueueManager_Priorities_And_FailureRecovery_Audit()
|
||||
{
|
||||
var qm = new MobileQueueManager();
|
||||
var i1 = qm.Enqueue("https://v1.mp4", "Video 1");
|
||||
var i2 = qm.Enqueue("https://v2.mp4", "Video 2");
|
||||
var i3 = qm.Enqueue("https://v3.mp4", "Video 3");
|
||||
|
||||
i2.Status = MobileQueueStatus.Failed;
|
||||
i2.ErrorMessage = "Network Timeout";
|
||||
|
||||
// 실패 항목만 재시도
|
||||
var count = qm.RetryFailedItems();
|
||||
Assert.Equal(1, count);
|
||||
Assert.Equal(MobileQueueStatus.Queued, i2.Status);
|
||||
|
||||
// 중요 항목 고정
|
||||
qm.Pin(i3.Id);
|
||||
Assert.Equal(i3.Id, qm.Items[0].Id);
|
||||
|
||||
// 완료된 항목 일괄 정리
|
||||
i1.Status = MobileQueueStatus.Completed;
|
||||
qm.ClearCompleted();
|
||||
Assert.DoesNotContain(qm.Items, x => x.Id == i1.Id);
|
||||
}
|
||||
|
||||
// [Gate 109] 설정 관리자: 영속성 및 기본값
|
||||
[Fact]
|
||||
public void Gate109_MobileSettingsManager_Defaults_And_Persistence_Audit()
|
||||
{
|
||||
var settings = new MobileSettingsManager();
|
||||
Assert.Equal(MobileThemeMode.Dark, settings.ThemeMode);
|
||||
Assert.False(settings.WifiOnlyDownload);
|
||||
Assert.Equal(0, settings.SpeedLimitKbps); // 0 = 무제한
|
||||
|
||||
settings.SpeedLimitKbps = 2048;
|
||||
settings.ThemeMode = MobileThemeMode.OLEDBlack;
|
||||
Assert.Equal(2048, settings.SpeedLimitKbps);
|
||||
Assert.Equal(MobileThemeMode.OLEDBlack, settings.ThemeMode);
|
||||
}
|
||||
|
||||
// [Gate 110] 사용자 탭 동기화: PC 열린 탭 원격 수신 및 동기화 요청
|
||||
[Fact]
|
||||
public void Gate110_MobileTabSyncEngine_RemoteTabMapping_Audit()
|
||||
{
|
||||
var sync = new MobileTabSyncEngine();
|
||||
sync.UpdateRemoteTabs(new List<RemoteTabInfo>
|
||||
{
|
||||
new("t1", "Design System Talk", "https://talk.com/1"),
|
||||
new("t2", "Tutorial Part 2", "https://tutorial.com/2")
|
||||
});
|
||||
|
||||
Assert.Equal(2, sync.RemoteTabs.Count);
|
||||
|
||||
string? requestedUrl = null;
|
||||
sync.OnRemoteDownloadRequested += u => requestedUrl = u;
|
||||
sync.RequestRemoteDownload("t2");
|
||||
Assert.Equal("https://tutorial.com/2", requestedUrl);
|
||||
}
|
||||
|
||||
// [Gate 114] 비디오 스니퍼: 다중 스트림 품질 자동 협상 (Quality Negotiation)
|
||||
[Fact]
|
||||
public void Gate114_VideoSniffer_QualityNegotiator_Audit()
|
||||
{
|
||||
var streams = new List<VideoSniffItem>
|
||||
{
|
||||
new() { StreamUrl = "https://cdn.com/4k.mp4", Resolution = "4K" },
|
||||
new() { StreamUrl = "https://cdn.com/1080p.mp4", Resolution = "1080p" },
|
||||
new() { StreamUrl = "https://cdn.com/720p.mp4", Resolution = "720p" },
|
||||
new() { StreamUrl = "https://cdn.com/audio.m4a", Resolution = "Audio Only" }
|
||||
};
|
||||
|
||||
// 1. 선호 화질 1080p -> 1080p 매칭
|
||||
var best1080 = VideoSnifferParser.NegotiateBestQuality(streams, "1080p");
|
||||
Assert.NotNull(best1080);
|
||||
Assert.Equal("1080p", best1080.Resolution);
|
||||
|
||||
// 2. 선호 화질 1440p (목록에 없음) -> 1440p 이하 최고 화질인 1080p 선택
|
||||
var fallback = VideoSnifferParser.NegotiateBestQuality(streams, "1440p");
|
||||
Assert.NotNull(fallback);
|
||||
Assert.Equal("1080p", fallback.Resolution);
|
||||
|
||||
// 3. 선호 화질 최고 화질("Max") -> 4K 선택
|
||||
var max = VideoSnifferParser.NegotiateBestQuality(streams, "Max");
|
||||
Assert.NotNull(max);
|
||||
Assert.Equal("4K", max.Resolution);
|
||||
|
||||
// 4. 선호 화질 오디오("Audio Only") -> Audio Only 선택
|
||||
var audio = VideoSnifferParser.NegotiateBestQuality(streams, "Audio Only");
|
||||
Assert.NotNull(audio);
|
||||
Assert.Equal("Audio Only", audio.Resolution);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue