~ knotless Search/
↑↓ move↵ open⇧↵ all results

grain.social/grain-ios

1 collaborator
0
1
main
164 lines · 6.9 KB Raw History
@testable import Grain
import SwiftUI
import XCTest

/// What a screen shows when the server has nothing to give it, or nothing at
/// all to say.
///
/// The rest of the render suites drive screens against a healthy server, so the
/// "no galleries yet" and "that didn't work" branches — which is what someone
/// on a bad connection or a brand new account actually sees — went unexercised.
@MainActor
final class EmptyAndFailedStateRenderTests: GrainTestCase {
    private var account: TestAccount!

    /// Async overrides: the synchronous `setUp`/`tearDown` are nonisolated, so
    /// touching main-actor state from them warns.
    override func setUp() async throws {
        try await super.setUp()
        account = TestAccount()
    }

    override func tearDown() async throws {
        MockURLProtocol.handler = nil
        account.restore()
        try await super.tearDown()
    }

    private let settle: TimeInterval = 0.4

    /// Everything answers, but with nothing in it.
    private func respondEmpty() {
        MockURLProtocol.respondByPath([
            "getFeed": #"{"items": [], "cursor": null}"#,
            "searchGalleries": #"{"items": [], "cursor": null}"#,
            "comment.getThread": #"{"comments": [], "cursor": null}"#,
            "story.getAuthors": #"{"authors": []}"#,
            "story.getArchive": #"{"stories": [], "cursor": null}"#,
            "getStories": #"{"stories": []}"#,
            "listNotifications": #"{"notifications": [], "cursor": null}"#,
            "getBlocks": #"{"items": [], "cursor": null}"#,
            "getMutes": #"{"items": [], "cursor": null}"#,
            "getFollowers": #"{"items": [], "cursor": null}"#,
            "getFollowing": #"{"items": [], "cursor": null}"#,
            "getKnownFollowers": #"{"items": []}"#,
            "gallery.getFavorites": #"{"items": [], "cursor": null}"#,
            "getSuggestedFollows": #"{"items": []}"#,
            "getLocations": #"{"locations": []}"#,
            "getCameras": #"{"cameras": []}"#,
            "actor.getProfile": Fixtures.profileDetailed,
            "getGallery": Fixtures.galleryResponse,
        ], fallback: "{}")
    }

    // MARK: - Failures

    /// Every screen with a fetch, against a server that is refusing.
    func testEveryScreenRendersWhenEverythingFails() {
        MockURLProtocol.respondWithError(statusCode: 503)
        let env = TestEnvironment()

        ViewRender.render(FeedView(client: env.client).withTestEnvironment(env), settle: settle)
        ViewRender.render(SearchView(client: env.client).withTestEnvironment(env), settle: settle)
        ViewRender.render(SettingsView(client: env.client).withTestEnvironment(env), settle: settle)
        ViewRender.render(EditProfileView(client: env.client).withTestEnvironment(env), settle: settle)
        ViewRender.render(ModerationView(client: env.client).withTestEnvironment(env), settle: settle)
        ViewRender.render(BlockedUsersView(client: env.client).withTestEnvironment(env), settle: settle)
        ViewRender.render(MutedUsersView(client: env.client).withTestEnvironment(env), settle: settle)
        ViewRender.render(NotificationSettingsView(client: env.client).withTestEnvironment(env), settle: settle)
        ViewRender.render(StoryCreateView(client: env.client).withTestEnvironment(env), settle: settle)
    }

    func testEveryFeedRendersWhenItsFetchFails() {
        MockURLProtocol.respondWithError(statusCode: 500)
        let env = TestEnvironment()

        ViewRender.render(
            HashtagFeedView(client: env.client, tag: "film").withTestEnvironment(env), settle: settle
        )
        ViewRender.render(
            CameraFeedView(client: env.client, camera: "Leica M6").withTestEnvironment(env), settle: settle
        )
        ViewRender.render(
            LocationFeedView(client: env.client, h3Index: "8a2a1072b59ffff", locationName: "Lisboa")
                .withTestEnvironment(env),
            settle: settle
        )
        ViewRender.render(
            ProfileGalleryFeedView(
                viewModel: ProfileDetailViewModel(client: env.client),
                client: env.client,
                did: "did:plc:test",
                initialUri: "at://did:plc:test/social.grain.gallery/1",
                source: .galleries
            )
            .withTestEnvironment(env),
            settle: settle
        )
        ViewRender.render(
            GalleryDetailView(client: env.client, galleryUri: "at://did:plc:test/social.grain.gallery/1")
                .withTestEnvironment(env),
            settle: settle
        )
        for mode in [FollowListMode.followers, .following, .knownFollowers] {
            ViewRender.render(
                FollowListView(client: env.client, did: "did:plc:test", mode: mode).withTestEnvironment(env),
                settle: settle
            )
        }
    }

    // MARK: - Empty

    /// The same screens against a server that answers with nothing — which is
    /// a different branch again from a failure.
    func testEveryScreenRendersWithNothingToShow() {
        respondEmpty()
        let env = TestEnvironment()

        ViewRender.render(FeedView(client: env.client).withTestEnvironment(env), settle: settle)
        ViewRender.render(SearchView(client: env.client).withTestEnvironment(env), settle: settle)
        ViewRender.render(BlockedUsersView(client: env.client).withTestEnvironment(env), settle: settle)
        ViewRender.render(MutedUsersView(client: env.client).withTestEnvironment(env), settle: settle)
        ViewRender.render(
            HashtagFeedView(client: env.client, tag: "film").withTestEnvironment(env), settle: settle
        )
        ViewRender.render(
            CameraFeedView(client: env.client, camera: "Leica M6").withTestEnvironment(env), settle: settle
        )
        ViewRender.render(
            LocationFeedView(client: env.client, h3Index: "8a2a1072b59ffff", locationName: "Lisboa")
                .withTestEnvironment(env),
            settle: settle
        )
    }

    /// A profile with nothing posted, on each of its tabs.
    func testAnEmptyProfileRendersOnEveryTab() {
        respondEmpty()
        let env = TestEnvironment()

        for mode in ProfileViewMode.allCases {
            ViewRender.render(
                ProfileView(client: env.client, did: "did:plc:test", isRoot: true, viewMode: mode)
                    .withTestEnvironment(env),
                settle: settle
            )
        }
    }

    /// Someone else's profile, where the "followed by" row and the follow
    /// button live.
    func testSomeoneElsesProfileRendersOnEveryTab() {
        MockURLProtocol.respondByPath(Fixtures.routes)
        let env = TestEnvironment()

        for mode in ProfileViewMode.allCases {
            ViewRender.render(
                ProfileView(client: env.client, did: "did:plc:other", isRoot: false, viewMode: mode)
                    .withTestEnvironment(env),
                settle: 0.6
            )
        }
    }
}