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." ) } 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) } }