import XCTest final class ViewDerivedDataTests: XCTestCase { func testGenericListViewDerivedCollectionsAreReferencedOncePerRenderPass() throws { let source = try loadSource(at: "Sora/Views/Generic/GenericListView.swift") let normalizedSource = strippingCommentsAndStrings(from: source) let filteredItemsUsages = referenceCount( for: "filteredItems", in: normalizedSource ) let sortedFilteredItemsUsages = referenceCount( for: "sortedFilteredItems", in: normalizedSource ) // swiftlint:disable:next prefer_nimble XCTAssertLessThanOrEqual( filteredItemsUsages, 1, "filteredItems should be consumed once per dependency change." ) // swiftlint:disable:next prefer_nimble XCTAssertLessThanOrEqual( sortedFilteredItemsUsages, 1, "sortedFilteredItems should be consumed once per dependency change." ) } func testPostGridViewDerivedCollectionsAreReferencedOncePerRenderPass() throws { let source = try loadSource(at: "Sora/Views/Post/Grid/PostGridView.swift") let normalizedSource = strippingCommentsAndStrings(from: source) let activePostsUsages = referenceCount( for: "activePosts", in: normalizedSource ) let getColumnsDataUsages = invocationCount( forFunction: "getColumnsData", in: normalizedSource ) // swiftlint:disable:next prefer_nimble XCTAssertLessThanOrEqual( activePostsUsages, 1, "activePosts-derived data should be consumed once per dependency change." ) // swiftlint:disable:next prefer_nimble XCTAssertLessThanOrEqual( getColumnsDataUsages, 1, "getColumnsData should be invoked once per dependency change." ) } func testFavoritesViewDerivedCollectionsAreReferencedOncePerRenderPass() throws { let source = try loadSource(at: "Sora/Views/FavoritesView.swift") let normalizedSource = strippingCommentsAndStrings(from: source) let filteredFavoritesUsages = referenceCount( for: "filteredFavorites", in: normalizedSource ) let sortedFilteredFavoritesUsages = referenceCount( for: "sortedFilteredFavorites", in: normalizedSource ) let inlinePostMappingCount = tokenCount( matching: #"\bsortedFilteredFavorites\s*\.\s*map\s*\{"#, in: normalizedSource ) let columnsSplitterUsages = invocationCount( forFunction: "getColumnsData", in: normalizedSource ) // swiftlint:disable:next prefer_nimble XCTAssertLessThanOrEqual( filteredFavoritesUsages, 1, "filteredFavorites should be consumed once per dependency change." ) // swiftlint:disable:next prefer_nimble XCTAssertLessThanOrEqual( sortedFilteredFavoritesUsages, 1, "sortedFilteredFavorites should be consumed once per dependency change." ) // swiftlint:disable:next prefer_nimble XCTAssertEqual( inlinePostMappingCount, 0, "Favorites grid should not remap sorted favorites to posts inline on each cell render." ) // swiftlint:disable:next prefer_nimble XCTAssertLessThanOrEqual( columnsSplitterUsages, 1, "Favorites grid column distribution should be derived once per dependency change." ) } func testFolderMenuHierarchyIsSharedAcrossConsumers() throws { let consumerPaths = [ "Sora/Views/Generic/GenericListView.swift", "Sora/Views/FavoritesView.swift", "Sora/Views/Post/Grid/PostGridView.swift", "Sora/Views/BookmarkMenuButtonView.swift", "Sora/Views/FavoriteMenuButtonView.swift", ] for consumerPath in consumerPaths { let source = try loadSource(at: consumerPath) let sharedMenuUsages = tokenCount( matching: #"\bFolderMenuView\s*\("#, in: source ) // swiftlint:disable:next prefer_nimble XCTAssertGreaterThan( sharedMenuUsages, 0, "\(consumerPath) should use FolderMenuView for folder hierarchy rendering." ) } } func testSearchSuggestionsCacheIsBuiltFromItemsChanges() throws { let source = try loadSource(at: "Sora/Views/SearchSuggestionsView.swift") let normalizedSource = strippingCommentsAndStrings(from: source) let stateCacheCount = tokenCount( matching: #"\@State\s+private\s+var\s+cachedTags"#, in: normalizedSource ) let itemsMapCount = tokenCount( matching: #"\bcachedTags\s*=\s*items\s*\.\s*map"#, in: normalizedSource ) let itemsChangeObserverCount = tokenCount( matching: #"\.onChange\s*\(\s*of:\s*itemsCacheKey\s*\)"#, in: normalizedSource ) // swiftlint:disable:next prefer_nimble XCTAssertGreaterThan( stateCacheCount, 0, "Search suggestions should keep preprocessed tags in view state." ) // swiftlint:disable:next prefer_nimble XCTAssertGreaterThan( itemsMapCount, 0, "Search suggestions should build cached tags from items." ) // swiftlint:disable:next prefer_nimble XCTAssertGreaterThan( itemsChangeObserverCount, 0, "Search suggestions should refresh cached tags only when items change." ) } func testPostDetailsImageActionsUseAsyncCachedImageLoading() throws { let source = try loadSource(at: "Sora/Views/Post/Details/PostDetailsImageView.swift") let normalizedSource = strippingCommentsAndStrings(from: source) let synchronousLoadCount = tokenCount( matching: #"\bNSData\s*\(\s*contentsOf:"#, in: normalizedSource ) let cachedLoaderCount = tokenCount( matching: #"\bImageCacheManager\s*\.\s*shared\s*\.\s*loadImageData\s*\("#, in: normalizedSource ) // swiftlint:disable:next prefer_nimble XCTAssertEqual( synchronousLoadCount, 0, "Post details image actions should avoid synchronous NSData file or network loads." ) // swiftlint:disable:next prefer_nimble XCTAssertGreaterThan( cachedLoaderCount, 0, "Post details image actions should use cache-backed async image loading." ) } func testListViewsAvoidComparatorRandomShuffleSorting() throws { let listViewSource = try loadSource(at: "Sora/Views/Generic/GenericListView.swift") let favoritesViewSource = try loadSource(at: "Sora/Views/FavoritesView.swift") let normalizedListViewSource = strippingCommentsAndStrings(from: listViewSource) let normalizedFavoritesViewSource = strippingCommentsAndStrings(from: favoritesViewSource) let listViewComparatorRandomCount = tokenCount( matching: #"\bBool\s*\.\s*random\s*\("#, in: normalizedListViewSource ) let favoritesViewComparatorRandomCount = tokenCount( matching: #"\bBool\s*\.\s*random\s*\("#, in: normalizedFavoritesViewSource ) // swiftlint:disable:next prefer_nimble XCTAssertEqual( listViewComparatorRandomCount, 0, "Generic list sorting should not use comparator-based random ordering." ) // swiftlint:disable:next prefer_nimble XCTAssertEqual( favoritesViewComparatorRandomCount, 0, "Favorites sorting should not use comparator-based random ordering." ) } func testBooruManagerRemovesUnusedXMLParserPoolPaths() throws { let source = try loadSource(at: "Sora/Data/Booru/BooruManager.swift") let normalizedSource = strippingCommentsAndStrings(from: source) let poolStorageCount = tokenCount( matching: #"\bxmlParserPool\b"#, in: normalizedSource ) let lockStorageCount = tokenCount( matching: #"\bparserPoolLock\b"#, in: normalizedSource ) let parserFactoryCount = invocationCount( forFunction: "xmlParser", in: normalizedSource ) let parserReturnCount = invocationCount( forFunction: "returnXMLParser", in: normalizedSource ) // swiftlint:disable:next prefer_nimble XCTAssertEqual( poolStorageCount, 0, "BooruManager should not keep an unused XML parser pool." ) // swiftlint:disable:next prefer_nimble XCTAssertEqual( lockStorageCount, 0, "BooruManager should not keep an unused parser pool lock." ) // swiftlint:disable:next prefer_nimble XCTAssertEqual( parserFactoryCount, 0, "BooruManager should not expose unused xml parser factory paths." ) // swiftlint:disable:next prefer_nimble XCTAssertEqual( parserReturnCount, 0, "BooruManager should not expose unused xml parser return paths." ) } private func referenceCount(for symbol: String, in source: String) -> Int { let totalMatches = tokenCount( matching: #"\b\#(symbol)\b"#, in: source ) let declarationMatches = tokenCount( matching: #"\b(?:var|let)\s+\#(symbol)\b"#, in: source ) return max(0, totalMatches - declarationMatches) } private func invocationCount(forFunction name: String, in source: String) -> Int { let totalMatches = tokenCount( matching: #"\b\#(name)\s*\("#, in: source ) let declarationMatches = tokenCount( matching: #"\bfunc\s+\#(name)\s*\("#, in: source ) return max(0, totalMatches - declarationMatches) } }