From e0657bf02998d93547e3f125c63fc9f7a9636ef5 Mon Sep 17 00:00:00 2001 From: Scott Clampet <110618242+scottkicks@users.noreply.github.com> Date: Wed, 20 Sep 2023 14:25:54 -0500 Subject: [PATCH] [MBL-982] Support Project Fragment's Flagging Property (#1855) * update GraphQL project fragment and adapters so that we can parse out the flagging field * add viewmodel outputs so that the projectpageviewcontroller knows if a project has been flagged by the user previously * update tests * format --- .../ProjectPageViewController.swift | 6 + KsApi/GraphAPI.swift | 475 ++++++++++++++++++ KsApi/models/Project.swift | 3 + .../Project+FetchProjectQueryData.swift | 2 + .../Project+FetchProjectQueryDataTests.swift | 3 + .../adapters/Project+ProjectFragment.swift | 65 +-- .../Project+ProjectFragmentTests.swift | 2 + KsApi/models/lenses/ProjectLenses.swift | 50 +- KsApi/models/templates/ProjectTemplates.swift | 1 + KsApi/queries/FetchProjectByIdQuery.graphql | 4 + KsApi/queries/FetchProjectBySlugQuery.graphql | 4 + Library/ViewModels/ProjectPageViewModel.swift | 7 + .../ProjectPageViewModelTests.swift | 19 + 13 files changed, 585 insertions(+), 56 deletions(-) diff --git a/Kickstarter-iOS/Features/ProjectPage/Controller/ProjectPageViewController.swift b/Kickstarter-iOS/Features/ProjectPage/Controller/ProjectPageViewController.swift index c117601209..9b366e44b8 100644 --- a/Kickstarter-iOS/Features/ProjectPage/Controller/ProjectPageViewController.swift +++ b/Kickstarter-iOS/Features/ProjectPage/Controller/ProjectPageViewController.swift @@ -530,6 +530,12 @@ public final class ProjectPageViewController: UIViewController, MessageBannerVie .observeValues { [weak self] in self?.tableView.reloadData() } + + self.viewModel.outputs.projectFlagged + .observeForUI() + .observeValues { _ in + // TODO: Use this flag to hide or show the Report this project label [MBL-983](https://kickstarter.atlassian.net/browse/MBL-983) + } } private func prepareToPlayAudioVideoURL(audioVideoURL: URL, diff --git a/KsApi/GraphAPI.swift b/KsApi/GraphAPI.swift index d8887b9e6b..d2c8f50e55 100644 --- a/KsApi/GraphAPI.swift +++ b/KsApi/GraphAPI.swift @@ -1230,6 +1230,349 @@ public enum GraphAPI { } } + /// The bucket for a flagging (general reason). + public enum FlaggingKind: RawRepresentable, Equatable, Hashable, CaseIterable, Apollo.JSONDecodable, Apollo.JSONEncodable { + public typealias RawValue = String + /// prohibited-items + case prohibitedItems + /// charity + case charity + /// resale + case resale + /// false-claims + case falseClaims + /// misrep-support + case misrepSupport + /// not-project + case notProject + /// guidelines-violation + case guidelinesViolation + /// post-funding-issues + case postFundingIssues + /// spam + case spam + /// abuse + case abuse + /// vices-drugs + case vicesDrugs + /// vices-alcohol + case vicesAlcohol + /// vices-weapons + case vicesWeapons + /// health-claims + case healthClaims + /// health-regulations + case healthRegulations + /// health-gmos + case healthGmos + /// health-live-animals + case healthLiveAnimals + /// health-energy-food-and-drink + case healthEnergyFoodAndDrink + /// financial-contests-coupons + case financialContestsCoupons + /// financial-services + case financialServices + /// financial-political-donations + case financialPoliticalDonations + /// offensive-content-hate + case offensiveContentHate + /// offensive-content-porn + case offensiveContentPorn + /// reselling + case reselling + /// plagiarism + case plagiarism + /// prototype-misrepresentation + case prototypeMisrepresentation + /// misrep-support-impersonation + case misrepSupportImpersonation + /// misrep-support-outstanding-fulfillment + case misrepSupportOutstandingFulfillment + /// misrep-support-suspicious-pledging + case misrepSupportSuspiciousPledging + /// misrep-support-other + case misrepSupportOther + /// not-project-charity + case notProjectCharity + /// not-project-stunt-or-hoax + case notProjectStuntOrHoax + /// not-project-personal-expenses + case notProjectPersonalExpenses + /// not-project-barebones + case notProjectBarebones + /// not-project-other + case notProjectOther + /// guidelines-spam + case guidelinesSpam + /// guidelines-abuse + case guidelinesAbuse + /// post-funding-reward-not-as-described + case postFundingRewardNotAsDescribed + /// post-funding-reward-delayed + case postFundingRewardDelayed + /// post-funding-shipped-never-received + case postFundingShippedNeverReceived + /// post-funding-creator-selling-elsewhere + case postFundingCreatorSellingElsewhere + /// post-funding-creator-uncommunicative + case postFundingCreatorUncommunicative + /// post-funding-creator-inappropriate + case postFundingCreatorInappropriate + /// post-funding-suspicious-third-party + case postFundingSuspiciousThirdParty + /// comment-abuse + case commentAbuse + /// comment-doxxing + case commentDoxxing + /// comment-offtopic + case commentOfftopic + /// comment-spam + case commentSpam + /// backing-abuse + case backingAbuse + /// backing-doxxing + case backingDoxxing + /// backing-fraud + case backingFraud + /// backing-spam + case backingSpam + /// Auto generated constant for unknown enum values + case __unknown(RawValue) + + public init?(rawValue: RawValue) { + switch rawValue { + case "PROHIBITED_ITEMS": self = .prohibitedItems + case "CHARITY": self = .charity + case "RESALE": self = .resale + case "FALSE_CLAIMS": self = .falseClaims + case "MISREP_SUPPORT": self = .misrepSupport + case "NOT_PROJECT": self = .notProject + case "GUIDELINES_VIOLATION": self = .guidelinesViolation + case "POST_FUNDING_ISSUES": self = .postFundingIssues + case "SPAM": self = .spam + case "ABUSE": self = .abuse + case "VICES_DRUGS": self = .vicesDrugs + case "VICES_ALCOHOL": self = .vicesAlcohol + case "VICES_WEAPONS": self = .vicesWeapons + case "HEALTH_CLAIMS": self = .healthClaims + case "HEALTH_REGULATIONS": self = .healthRegulations + case "HEALTH_GMOS": self = .healthGmos + case "HEALTH_LIVE_ANIMALS": self = .healthLiveAnimals + case "HEALTH_ENERGY_FOOD_AND_DRINK": self = .healthEnergyFoodAndDrink + case "FINANCIAL_CONTESTS_COUPONS": self = .financialContestsCoupons + case "FINANCIAL_SERVICES": self = .financialServices + case "FINANCIAL_POLITICAL_DONATIONS": self = .financialPoliticalDonations + case "OFFENSIVE_CONTENT_HATE": self = .offensiveContentHate + case "OFFENSIVE_CONTENT_PORN": self = .offensiveContentPorn + case "RESELLING": self = .reselling + case "PLAGIARISM": self = .plagiarism + case "PROTOTYPE_MISREPRESENTATION": self = .prototypeMisrepresentation + case "MISREP_SUPPORT_IMPERSONATION": self = .misrepSupportImpersonation + case "MISREP_SUPPORT_OUTSTANDING_FULFILLMENT": self = .misrepSupportOutstandingFulfillment + case "MISREP_SUPPORT_SUSPICIOUS_PLEDGING": self = .misrepSupportSuspiciousPledging + case "MISREP_SUPPORT_OTHER": self = .misrepSupportOther + case "NOT_PROJECT_CHARITY": self = .notProjectCharity + case "NOT_PROJECT_STUNT_OR_HOAX": self = .notProjectStuntOrHoax + case "NOT_PROJECT_PERSONAL_EXPENSES": self = .notProjectPersonalExpenses + case "NOT_PROJECT_BAREBONES": self = .notProjectBarebones + case "NOT_PROJECT_OTHER": self = .notProjectOther + case "GUIDELINES_SPAM": self = .guidelinesSpam + case "GUIDELINES_ABUSE": self = .guidelinesAbuse + case "POST_FUNDING_REWARD_NOT_AS_DESCRIBED": self = .postFundingRewardNotAsDescribed + case "POST_FUNDING_REWARD_DELAYED": self = .postFundingRewardDelayed + case "POST_FUNDING_SHIPPED_NEVER_RECEIVED": self = .postFundingShippedNeverReceived + case "POST_FUNDING_CREATOR_SELLING_ELSEWHERE": self = .postFundingCreatorSellingElsewhere + case "POST_FUNDING_CREATOR_UNCOMMUNICATIVE": self = .postFundingCreatorUncommunicative + case "POST_FUNDING_CREATOR_INAPPROPRIATE": self = .postFundingCreatorInappropriate + case "POST_FUNDING_SUSPICIOUS_THIRD_PARTY": self = .postFundingSuspiciousThirdParty + case "COMMENT_ABUSE": self = .commentAbuse + case "COMMENT_DOXXING": self = .commentDoxxing + case "COMMENT_OFFTOPIC": self = .commentOfftopic + case "COMMENT_SPAM": self = .commentSpam + case "BACKING_ABUSE": self = .backingAbuse + case "BACKING_DOXXING": self = .backingDoxxing + case "BACKING_FRAUD": self = .backingFraud + case "BACKING_SPAM": self = .backingSpam + default: self = .__unknown(rawValue) + } + } + + public var rawValue: RawValue { + switch self { + case .prohibitedItems: return "PROHIBITED_ITEMS" + case .charity: return "CHARITY" + case .resale: return "RESALE" + case .falseClaims: return "FALSE_CLAIMS" + case .misrepSupport: return "MISREP_SUPPORT" + case .notProject: return "NOT_PROJECT" + case .guidelinesViolation: return "GUIDELINES_VIOLATION" + case .postFundingIssues: return "POST_FUNDING_ISSUES" + case .spam: return "SPAM" + case .abuse: return "ABUSE" + case .vicesDrugs: return "VICES_DRUGS" + case .vicesAlcohol: return "VICES_ALCOHOL" + case .vicesWeapons: return "VICES_WEAPONS" + case .healthClaims: return "HEALTH_CLAIMS" + case .healthRegulations: return "HEALTH_REGULATIONS" + case .healthGmos: return "HEALTH_GMOS" + case .healthLiveAnimals: return "HEALTH_LIVE_ANIMALS" + case .healthEnergyFoodAndDrink: return "HEALTH_ENERGY_FOOD_AND_DRINK" + case .financialContestsCoupons: return "FINANCIAL_CONTESTS_COUPONS" + case .financialServices: return "FINANCIAL_SERVICES" + case .financialPoliticalDonations: return "FINANCIAL_POLITICAL_DONATIONS" + case .offensiveContentHate: return "OFFENSIVE_CONTENT_HATE" + case .offensiveContentPorn: return "OFFENSIVE_CONTENT_PORN" + case .reselling: return "RESELLING" + case .plagiarism: return "PLAGIARISM" + case .prototypeMisrepresentation: return "PROTOTYPE_MISREPRESENTATION" + case .misrepSupportImpersonation: return "MISREP_SUPPORT_IMPERSONATION" + case .misrepSupportOutstandingFulfillment: return "MISREP_SUPPORT_OUTSTANDING_FULFILLMENT" + case .misrepSupportSuspiciousPledging: return "MISREP_SUPPORT_SUSPICIOUS_PLEDGING" + case .misrepSupportOther: return "MISREP_SUPPORT_OTHER" + case .notProjectCharity: return "NOT_PROJECT_CHARITY" + case .notProjectStuntOrHoax: return "NOT_PROJECT_STUNT_OR_HOAX" + case .notProjectPersonalExpenses: return "NOT_PROJECT_PERSONAL_EXPENSES" + case .notProjectBarebones: return "NOT_PROJECT_BAREBONES" + case .notProjectOther: return "NOT_PROJECT_OTHER" + case .guidelinesSpam: return "GUIDELINES_SPAM" + case .guidelinesAbuse: return "GUIDELINES_ABUSE" + case .postFundingRewardNotAsDescribed: return "POST_FUNDING_REWARD_NOT_AS_DESCRIBED" + case .postFundingRewardDelayed: return "POST_FUNDING_REWARD_DELAYED" + case .postFundingShippedNeverReceived: return "POST_FUNDING_SHIPPED_NEVER_RECEIVED" + case .postFundingCreatorSellingElsewhere: return "POST_FUNDING_CREATOR_SELLING_ELSEWHERE" + case .postFundingCreatorUncommunicative: return "POST_FUNDING_CREATOR_UNCOMMUNICATIVE" + case .postFundingCreatorInappropriate: return "POST_FUNDING_CREATOR_INAPPROPRIATE" + case .postFundingSuspiciousThirdParty: return "POST_FUNDING_SUSPICIOUS_THIRD_PARTY" + case .commentAbuse: return "COMMENT_ABUSE" + case .commentDoxxing: return "COMMENT_DOXXING" + case .commentOfftopic: return "COMMENT_OFFTOPIC" + case .commentSpam: return "COMMENT_SPAM" + case .backingAbuse: return "BACKING_ABUSE" + case .backingDoxxing: return "BACKING_DOXXING" + case .backingFraud: return "BACKING_FRAUD" + case .backingSpam: return "BACKING_SPAM" + case .__unknown(let value): return value + } + } + + public static func == (lhs: FlaggingKind, rhs: FlaggingKind) -> Bool { + switch (lhs, rhs) { + case (.prohibitedItems, .prohibitedItems): return true + case (.charity, .charity): return true + case (.resale, .resale): return true + case (.falseClaims, .falseClaims): return true + case (.misrepSupport, .misrepSupport): return true + case (.notProject, .notProject): return true + case (.guidelinesViolation, .guidelinesViolation): return true + case (.postFundingIssues, .postFundingIssues): return true + case (.spam, .spam): return true + case (.abuse, .abuse): return true + case (.vicesDrugs, .vicesDrugs): return true + case (.vicesAlcohol, .vicesAlcohol): return true + case (.vicesWeapons, .vicesWeapons): return true + case (.healthClaims, .healthClaims): return true + case (.healthRegulations, .healthRegulations): return true + case (.healthGmos, .healthGmos): return true + case (.healthLiveAnimals, .healthLiveAnimals): return true + case (.healthEnergyFoodAndDrink, .healthEnergyFoodAndDrink): return true + case (.financialContestsCoupons, .financialContestsCoupons): return true + case (.financialServices, .financialServices): return true + case (.financialPoliticalDonations, .financialPoliticalDonations): return true + case (.offensiveContentHate, .offensiveContentHate): return true + case (.offensiveContentPorn, .offensiveContentPorn): return true + case (.reselling, .reselling): return true + case (.plagiarism, .plagiarism): return true + case (.prototypeMisrepresentation, .prototypeMisrepresentation): return true + case (.misrepSupportImpersonation, .misrepSupportImpersonation): return true + case (.misrepSupportOutstandingFulfillment, .misrepSupportOutstandingFulfillment): return true + case (.misrepSupportSuspiciousPledging, .misrepSupportSuspiciousPledging): return true + case (.misrepSupportOther, .misrepSupportOther): return true + case (.notProjectCharity, .notProjectCharity): return true + case (.notProjectStuntOrHoax, .notProjectStuntOrHoax): return true + case (.notProjectPersonalExpenses, .notProjectPersonalExpenses): return true + case (.notProjectBarebones, .notProjectBarebones): return true + case (.notProjectOther, .notProjectOther): return true + case (.guidelinesSpam, .guidelinesSpam): return true + case (.guidelinesAbuse, .guidelinesAbuse): return true + case (.postFundingRewardNotAsDescribed, .postFundingRewardNotAsDescribed): return true + case (.postFundingRewardDelayed, .postFundingRewardDelayed): return true + case (.postFundingShippedNeverReceived, .postFundingShippedNeverReceived): return true + case (.postFundingCreatorSellingElsewhere, .postFundingCreatorSellingElsewhere): return true + case (.postFundingCreatorUncommunicative, .postFundingCreatorUncommunicative): return true + case (.postFundingCreatorInappropriate, .postFundingCreatorInappropriate): return true + case (.postFundingSuspiciousThirdParty, .postFundingSuspiciousThirdParty): return true + case (.commentAbuse, .commentAbuse): return true + case (.commentDoxxing, .commentDoxxing): return true + case (.commentOfftopic, .commentOfftopic): return true + case (.commentSpam, .commentSpam): return true + case (.backingAbuse, .backingAbuse): return true + case (.backingDoxxing, .backingDoxxing): return true + case (.backingFraud, .backingFraud): return true + case (.backingSpam, .backingSpam): return true + case (.__unknown(let lhsValue), .__unknown(let rhsValue)): return lhsValue == rhsValue + default: return false + } + } + + public static var allCases: [FlaggingKind] { + return [ + .prohibitedItems, + .charity, + .resale, + .falseClaims, + .misrepSupport, + .notProject, + .guidelinesViolation, + .postFundingIssues, + .spam, + .abuse, + .vicesDrugs, + .vicesAlcohol, + .vicesWeapons, + .healthClaims, + .healthRegulations, + .healthGmos, + .healthLiveAnimals, + .healthEnergyFoodAndDrink, + .financialContestsCoupons, + .financialServices, + .financialPoliticalDonations, + .offensiveContentHate, + .offensiveContentPorn, + .reselling, + .plagiarism, + .prototypeMisrepresentation, + .misrepSupportImpersonation, + .misrepSupportOutstandingFulfillment, + .misrepSupportSuspiciousPledging, + .misrepSupportOther, + .notProjectCharity, + .notProjectStuntOrHoax, + .notProjectPersonalExpenses, + .notProjectBarebones, + .notProjectOther, + .guidelinesSpam, + .guidelinesAbuse, + .postFundingRewardNotAsDescribed, + .postFundingRewardDelayed, + .postFundingShippedNeverReceived, + .postFundingCreatorSellingElsewhere, + .postFundingCreatorUncommunicative, + .postFundingCreatorInappropriate, + .postFundingSuspiciousThirdParty, + .commentAbuse, + .commentDoxxing, + .commentOfftopic, + .commentSpam, + .backingAbuse, + .backingDoxxing, + .backingFraud, + .backingSpam, + ] + } + } + /// Various backing states. public enum BackingState: RawRepresentable, Equatable, Hashable, CaseIterable, Apollo.JSONDecodable, Apollo.JSONEncodable { public typealias RawValue = String @@ -6605,6 +6948,11 @@ public enum GraphAPI { __typename id } + flagging { + __typename + id + kind + } } } """ @@ -6723,6 +7071,7 @@ public enum GraphAPI { GraphQLField("__typename", type: .nonNull(.scalar(String.self))), GraphQLFragmentSpread(ProjectFragment.self), GraphQLField("backing", type: .object(Backing.selections)), + GraphQLField("flagging", type: .object(Flagging.selections)), ] } @@ -6751,6 +7100,16 @@ public enum GraphAPI { } } + /// A report by the current user for the project. + public var flagging: Flagging? { + get { + return (resultMap["flagging"] as? ResultMap).flatMap { Flagging(unsafeResultMap: $0) } + } + set { + resultMap.updateValue(newValue?.resultMap, forKey: "flagging") + } + } + public var fragments: Fragments { get { return Fragments(unsafeResultMap: resultMap) @@ -6815,6 +7174,56 @@ public enum GraphAPI { } } } + + public struct Flagging: GraphQLSelectionSet { + public static let possibleTypes: [String] = ["Flagging"] + + public static var selections: [GraphQLSelection] { + return [ + GraphQLField("__typename", type: .nonNull(.scalar(String.self))), + GraphQLField("id", type: .nonNull(.scalar(GraphQLID.self))), + GraphQLField("kind", type: .scalar(FlaggingKind.self)), + ] + } + + public private(set) var resultMap: ResultMap + + public init(unsafeResultMap: ResultMap) { + self.resultMap = unsafeResultMap + } + + public init(id: GraphQLID, kind: FlaggingKind? = nil) { + self.init(unsafeResultMap: ["__typename": "Flagging", "id": id, "kind": kind]) + } + + public var __typename: String { + get { + return resultMap["__typename"]! as! String + } + set { + resultMap.updateValue(newValue, forKey: "__typename") + } + } + + public var id: GraphQLID { + get { + return resultMap["id"]! as! GraphQLID + } + set { + resultMap.updateValue(newValue, forKey: "id") + } + } + + /// The general reason for the flagging. + public var kind: FlaggingKind? { + get { + return resultMap["kind"] as? FlaggingKind + } + set { + resultMap.updateValue(newValue, forKey: "kind") + } + } + } } } } @@ -6835,6 +7244,11 @@ public enum GraphAPI { __typename id } + flagging { + __typename + id + kind + } } } """ @@ -6953,6 +7367,7 @@ public enum GraphAPI { GraphQLField("__typename", type: .nonNull(.scalar(String.self))), GraphQLFragmentSpread(ProjectFragment.self), GraphQLField("backing", type: .object(Backing.selections)), + GraphQLField("flagging", type: .object(Flagging.selections)), ] } @@ -6981,6 +7396,16 @@ public enum GraphAPI { } } + /// A report by the current user for the project. + public var flagging: Flagging? { + get { + return (resultMap["flagging"] as? ResultMap).flatMap { Flagging(unsafeResultMap: $0) } + } + set { + resultMap.updateValue(newValue?.resultMap, forKey: "flagging") + } + } + public var fragments: Fragments { get { return Fragments(unsafeResultMap: resultMap) @@ -7045,6 +7470,56 @@ public enum GraphAPI { } } } + + public struct Flagging: GraphQLSelectionSet { + public static let possibleTypes: [String] = ["Flagging"] + + public static var selections: [GraphQLSelection] { + return [ + GraphQLField("__typename", type: .nonNull(.scalar(String.self))), + GraphQLField("id", type: .nonNull(.scalar(GraphQLID.self))), + GraphQLField("kind", type: .scalar(FlaggingKind.self)), + ] + } + + public private(set) var resultMap: ResultMap + + public init(unsafeResultMap: ResultMap) { + self.resultMap = unsafeResultMap + } + + public init(id: GraphQLID, kind: FlaggingKind? = nil) { + self.init(unsafeResultMap: ["__typename": "Flagging", "id": id, "kind": kind]) + } + + public var __typename: String { + get { + return resultMap["__typename"]! as! String + } + set { + resultMap.updateValue(newValue, forKey: "__typename") + } + } + + public var id: GraphQLID { + get { + return resultMap["id"]! as! GraphQLID + } + set { + resultMap.updateValue(newValue, forKey: "id") + } + } + + /// The general reason for the flagging. + public var kind: FlaggingKind? { + get { + return resultMap["kind"] as? FlaggingKind + } + set { + resultMap.updateValue(newValue, forKey: "kind") + } + } + } } } } diff --git a/KsApi/models/Project.swift b/KsApi/models/Project.swift index c2a8ea6f66..9ce4b6db5b 100644 --- a/KsApi/models/Project.swift +++ b/KsApi/models/Project.swift @@ -12,6 +12,7 @@ public struct Project { public var memberData: MemberData public var dates: Dates public var displayPrelaunch: Bool? + public var flagging: Bool? public var id: Int public var location: Location public var name: String @@ -272,6 +273,7 @@ extension Project: Decodable { case category case creator case displayPrelaunch = "display_prelaunch" + case flagging case id case location case name @@ -297,6 +299,7 @@ extension Project: Decodable { self.dates = try Project.Dates(from: decoder) self.displayPrelaunch = try values.decodeIfPresent(Bool.self, forKey: .displayPrelaunch) self.extendedProjectProperties = nil + self.flagging = try values.decodeIfPresent(Bool.self, forKey: .flagging) ?? false self.id = try values.decode(Int.self, forKey: .id) self.location = (try? values.decodeIfPresent(Location.self, forKey: .location)) ?? Location.none self.name = try values.decode(String.self, forKey: .name) diff --git a/KsApi/models/graphql/adapters/Project+FetchProjectQueryData.swift b/KsApi/models/graphql/adapters/Project+FetchProjectQueryData.swift index 72758cdbed..76dec62be4 100644 --- a/KsApi/models/graphql/adapters/Project+FetchProjectQueryData.swift +++ b/KsApi/models/graphql/adapters/Project+FetchProjectQueryData.swift @@ -61,6 +61,7 @@ extension Project { let fragment = data.project?.fragments.projectFragment, let project = Project.project( from: fragment, + flagging: data.project?.flagging != nil, rewards: [noRewardReward(from: fragment)], addOns: nil, backing: nil, @@ -83,6 +84,7 @@ extension Project { let fragment = data.project?.fragments.projectFragment, let project = Project.project( from: fragment, + flagging: data.project?.flagging != nil, rewards: [noRewardReward(from: fragment)], addOns: nil, backing: nil, diff --git a/KsApi/models/graphql/adapters/Project+FetchProjectQueryDataTests.swift b/KsApi/models/graphql/adapters/Project+FetchProjectQueryDataTests.swift index 8704fb4cac..9a2e7cc5c0 100644 --- a/KsApi/models/graphql/adapters/Project+FetchProjectQueryDataTests.swift +++ b/KsApi/models/graphql/adapters/Project+FetchProjectQueryDataTests.swift @@ -129,6 +129,9 @@ final class Project_FetchProjectQueryDataTests: XCTestCase { // Project Send Capi Events XCTAssertEqual(project.sendMetaCapiEvents, true) + // Project Flagging + XCTAssertEqual(project.flagging, false) + /// Project User XCTAssertEqual( project.creator.avatar.large, diff --git a/KsApi/models/graphql/adapters/Project+ProjectFragment.swift b/KsApi/models/graphql/adapters/Project+ProjectFragment.swift index c0aed4265f..307321094a 100644 --- a/KsApi/models/graphql/adapters/Project+ProjectFragment.swift +++ b/KsApi/models/graphql/adapters/Project+ProjectFragment.swift @@ -10,6 +10,7 @@ extension Project { */ static func project( from projectFragment: GraphAPI.ProjectFragment, + flagging: Bool? = nil, rewards: [Reward] = [], addOns: [Reward]? = nil, backing: Backing? = nil, @@ -66,37 +67,39 @@ extension Project { let extendedProjectProperties = extendedProject(from: projectFragment) - return Project( - availableCardTypes: availableCardTypes, - blurb: projectFragment.description, - category: category, - country: country, - creator: creator, - extendedProjectProperties: extendedProjectProperties, - memberData: memberData, - dates: dates, - displayPrelaunch: displayPrelaunch, - id: projectFragment.pid, - location: location, - name: projectFragment.name, - personalization: projectPersonalization( - isStarred: projectFragment.isWatched, - backing: backing, - friends: [] - ), - photo: photo, - prelaunchActivated: projectFragment.prelaunchActivated, - rewardData: RewardData(addOns: addOns, rewards: rewards), - sendMetaCapiEvents: projectFragment.sendMetaCapiEvents, - slug: generatedSlug ?? projectFragment.slug, - staffPick: projectFragment.isProjectWeLove, - state: state, - stats: projectStats(from: projectFragment, currentUserChosenCurrency: currentUserChosenCurrency), - tags: discoverTags, - urls: urls, - video: projectVideo(from: projectFragment), - watchesCount: projectFragment.watchesCount - ) + return + Project( + availableCardTypes: availableCardTypes, + blurb: projectFragment.description, + category: category, + country: country, + creator: creator, + extendedProjectProperties: extendedProjectProperties, + memberData: memberData, + dates: dates, + displayPrelaunch: displayPrelaunch, + flagging: flagging, + id: projectFragment.pid, + location: location, + name: projectFragment.name, + personalization: projectPersonalization( + isStarred: projectFragment.isWatched, + backing: backing, + friends: [] + ), + photo: photo, + prelaunchActivated: projectFragment.prelaunchActivated, + rewardData: RewardData(addOns: addOns, rewards: rewards), + sendMetaCapiEvents: projectFragment.sendMetaCapiEvents, + slug: generatedSlug ?? projectFragment.slug, + staffPick: projectFragment.isProjectWeLove, + state: state, + stats: projectStats(from: projectFragment, currentUserChosenCurrency: currentUserChosenCurrency), + tags: discoverTags, + urls: urls, + video: projectVideo(from: projectFragment), + watchesCount: projectFragment.watchesCount + ) } } diff --git a/KsApi/models/graphql/adapters/Project+ProjectFragmentTests.swift b/KsApi/models/graphql/adapters/Project+ProjectFragmentTests.swift index ad2d7d4d29..62e0175dee 100644 --- a/KsApi/models/graphql/adapters/Project+ProjectFragmentTests.swift +++ b/KsApi/models/graphql/adapters/Project+ProjectFragmentTests.swift @@ -13,6 +13,7 @@ final class Project_ProjectFragmentTests: XCTestCase { let project = Project.project( from: fragment, + flagging: false, currentUserChosenCurrency: nil ) @@ -60,6 +61,7 @@ final class Project_ProjectFragmentTests: XCTestCase { XCTAssertTrue(project.staffPick) XCTAssertTrue(project.prelaunchActivated!) XCTAssertFalse(project.displayPrelaunch!) + XCTAssertEqual(project.flagging, false) XCTAssertNil(project.personalization.backing) XCTAssertNil(project.rewardData.addOns) XCTAssertEqual(project.sendMetaCapiEvents, false) diff --git a/KsApi/models/lenses/ProjectLenses.swift b/KsApi/models/lenses/ProjectLenses.swift index f14f0885f4..71cb384fc0 100644 --- a/KsApi/models/lenses/ProjectLenses.swift +++ b/KsApi/models/lenses/ProjectLenses.swift @@ -25,7 +25,7 @@ extension Project { availableCardTypes: $0, blurb: $1.blurb, category: $1.category, country: $1.country, creator: $1.creator, extendedProjectProperties: $1.extendedProjectProperties, memberData: $1.memberData, dates: $1.dates, - displayPrelaunch: $1.displayPrelaunch, id: $1.id, + displayPrelaunch: $1.displayPrelaunch, flagging: $1.flagging, id: $1.id, location: $1.location, name: $1.name, personalization: $1.personalization, photo: $1.photo, prelaunchActivated: $1.prelaunchActivated, rewardData: $1.rewardData, sendMetaCapiEvents: $1.sendMetaCapiEvents, slug: $1.slug, @@ -40,7 +40,7 @@ extension Project { availableCardTypes: $1.availableCardTypes, blurb: $0, category: $1.category, country: $1.country, creator: $1.creator, extendedProjectProperties: $1.extendedProjectProperties, memberData: $1.memberData, dates: $1.dates, - displayPrelaunch: $1.displayPrelaunch, id: $1.id, + displayPrelaunch: $1.displayPrelaunch, flagging: $1.flagging, id: $1.id, location: $1.location, name: $1.name, personalization: $1.personalization, photo: $1.photo, prelaunchActivated: $1.prelaunchActivated, rewardData: $1.rewardData, sendMetaCapiEvents: $1.sendMetaCapiEvents, slug: $1.slug, staffPick: @@ -55,7 +55,7 @@ extension Project { availableCardTypes: $1.availableCardTypes, blurb: $1.blurb, category: $1.category, country: $1.country, creator: $1.creator, extendedProjectProperties: $1.extendedProjectProperties, memberData: $1.memberData, dates: $1.dates, - displayPrelaunch: $1.displayPrelaunch, id: $1.id, + displayPrelaunch: $1.displayPrelaunch, flagging: $1.flagging, id: $1.id, location: $1.location, name: $1.name, personalization: $1.personalization, photo: $1.photo, prelaunchActivated: $1.prelaunchActivated, rewardData: $1.rewardData, sendMetaCapiEvents: $1.sendMetaCapiEvents, slug: $1.slug, @@ -71,7 +71,7 @@ extension Project { creator: $1.creator, extendedProjectProperties: $1.extendedProjectProperties, memberData: $1.memberData, dates: $1.dates, - displayPrelaunch: $1.displayPrelaunch, id: $1.id, + displayPrelaunch: $1.displayPrelaunch, flagging: $1.flagging, id: $1.id, location: $1.location, name: $1.name, personalization: $1.personalization, photo: $1.photo, prelaunchActivated: $1.prelaunchActivated, rewardData: $1.rewardData, sendMetaCapiEvents: $1.sendMetaCapiEvents, slug: $1.slug, @@ -86,7 +86,7 @@ extension Project { availableCardTypes: $1.availableCardTypes, blurb: $1.blurb, category: $1.category, country: $0, creator: $1.creator, extendedProjectProperties: $1.extendedProjectProperties, memberData: $1.memberData, dates: $1.dates, - displayPrelaunch: $1.displayPrelaunch, id: $1.id, + displayPrelaunch: $1.displayPrelaunch, flagging: $1.flagging, id: $1.id, location: $1.location, name: $1.name, personalization: $1.personalization, photo: $1.photo, prelaunchActivated: $1.prelaunchActivated, rewardData: $1.rewardData, sendMetaCapiEvents: $1.sendMetaCapiEvents, slug: $1.slug, @@ -102,7 +102,7 @@ extension Project { country: $1.country, creator: $0, extendedProjectProperties: $1.extendedProjectProperties, memberData: $1.memberData, dates: $1.dates, - displayPrelaunch: $1.displayPrelaunch, id: $1.id, + displayPrelaunch: $1.displayPrelaunch, flagging: $1.flagging, id: $1.id, location: $1.location, name: $1.name, personalization: $1.personalization, photo: $1.photo, prelaunchActivated: $1.prelaunchActivated, rewardData: $1.rewardData, sendMetaCapiEvents: $1.sendMetaCapiEvents, slug: $1.slug, @@ -118,7 +118,7 @@ extension Project { country: $1.country, creator: $1.creator, extendedProjectProperties: $1.extendedProjectProperties, memberData: $1.memberData, dates: $0, - displayPrelaunch: $1.displayPrelaunch, id: $1.id, + displayPrelaunch: $1.displayPrelaunch, flagging: $1.flagging, id: $1.id, location: $1.location, name: $1.name, personalization: $1.personalization, photo: $1.photo, prelaunchActivated: $1.prelaunchActivated, rewardData: $1.rewardData, sendMetaCapiEvents: $1.sendMetaCapiEvents, slug: $1.slug, @@ -133,7 +133,7 @@ extension Project { availableCardTypes: $1.availableCardTypes, blurb: $1.blurb, category: $1.category, country: $1.country, creator: $1.creator, extendedProjectProperties: $1.extendedProjectProperties, memberData: $1.memberData, dates: $1.dates, - displayPrelaunch: $0, id: $1.id, + displayPrelaunch: $0, flagging: $1.flagging, id: $1.id, location: $1.location, name: $1.name, personalization: $1.personalization, photo: $1.photo, prelaunchActivated: $1.prelaunchActivated, rewardData: $1.rewardData, sendMetaCapiEvents: $1.sendMetaCapiEvents, slug: $1.slug, @@ -148,7 +148,7 @@ extension Project { availableCardTypes: $1.availableCardTypes, blurb: $1.blurb, category: $1.category, country: $1.country, creator: $1.creator, extendedProjectProperties: $0, memberData: $1.memberData, dates: $1.dates, - displayPrelaunch: $1.displayPrelaunch, id: $1.id, + displayPrelaunch: $1.displayPrelaunch, flagging: $1.flagging, id: $1.id, location: $1.location, name: $1.name, personalization: $1.personalization, photo: $1.photo, prelaunchActivated: $1.prelaunchActivated, rewardData: $1.rewardData, sendMetaCapiEvents: $1.sendMetaCapiEvents, slug: $1.slug, @@ -164,7 +164,7 @@ extension Project { country: $1.country, creator: $1.creator, extendedProjectProperties: $1.extendedProjectProperties, memberData: $1.memberData, dates: $1.dates, - displayPrelaunch: $1.displayPrelaunch, id: $0, + displayPrelaunch: $1.displayPrelaunch, flagging: $1.flagging, id: $0, location: $1.location, name: $1.name, personalization: $1.personalization, photo: $1.photo, prelaunchActivated: $1.prelaunchActivated, rewardData: $1.rewardData, sendMetaCapiEvents: $1.sendMetaCapiEvents, slug: $1.slug, @@ -180,7 +180,7 @@ extension Project { country: $1.country, creator: $1.creator, extendedProjectProperties: $1.extendedProjectProperties, memberData: $1.memberData, dates: $1.dates, - displayPrelaunch: $1.displayPrelaunch, id: $1.id, + displayPrelaunch: $1.displayPrelaunch, flagging: $1.flagging, id: $1.id, location: $0, name: $1.name, personalization: $1.personalization, photo: $1.photo, prelaunchActivated: $1.prelaunchActivated, rewardData: $1.rewardData, sendMetaCapiEvents: $1.sendMetaCapiEvents, slug: $1.slug, @@ -196,7 +196,7 @@ extension Project { country: $1.country, creator: $1.creator, extendedProjectProperties: $1.extendedProjectProperties, memberData: $0, dates: $1.dates, - displayPrelaunch: $1.displayPrelaunch, id: $1.id, + displayPrelaunch: $1.displayPrelaunch, flagging: $1.flagging, id: $1.id, location: $1.location, name: $1.name, personalization: $1.personalization, photo: $1.photo, prelaunchActivated: $1.prelaunchActivated, rewardData: $1.rewardData, sendMetaCapiEvents: $1.sendMetaCapiEvents, slug: $1.slug, @@ -212,7 +212,7 @@ extension Project { country: $1.country, creator: $1.creator, extendedProjectProperties: $1.extendedProjectProperties, memberData: $1.memberData, dates: $1.dates, - displayPrelaunch: $1.displayPrelaunch, id: $1.id, + displayPrelaunch: $1.displayPrelaunch, flagging: $1.flagging, id: $1.id, location: $1.location, name: $0, personalization: $1.personalization, photo: $1.photo, prelaunchActivated: $1.prelaunchActivated, rewardData: $1.rewardData, sendMetaCapiEvents: $1.sendMetaCapiEvents, slug: $1.slug, @@ -228,7 +228,7 @@ extension Project { country: $1.country, creator: $1.creator, extendedProjectProperties: $1.extendedProjectProperties, memberData: $1.memberData, dates: $1.dates, - displayPrelaunch: $1.displayPrelaunch, id: $1.id, + displayPrelaunch: $1.displayPrelaunch, flagging: $1.flagging, id: $1.id, location: $1.location, name: $1.name, personalization: $0, photo: $1.photo, prelaunchActivated: $1.prelaunchActivated, rewardData: $1.rewardData, sendMetaCapiEvents: $1.sendMetaCapiEvents, slug: $1.slug, @@ -244,7 +244,7 @@ extension Project { country: $1.country, creator: $1.creator, extendedProjectProperties: $1.extendedProjectProperties, memberData: $1.memberData, dates: $1.dates, - displayPrelaunch: $1.displayPrelaunch, id: $1.id, + displayPrelaunch: $1.displayPrelaunch, flagging: $1.flagging, id: $1.id, location: $1.location, name: $1.name, personalization: $1.personalization, photo: $0, prelaunchActivated: $1.prelaunchActivated, rewardData: $1.rewardData, sendMetaCapiEvents: $1.sendMetaCapiEvents, slug: $1.slug, @@ -260,7 +260,7 @@ extension Project { country: $1.country, creator: $1.creator, extendedProjectProperties: $1.extendedProjectProperties, memberData: $1.memberData, dates: $1.dates, - displayPrelaunch: $1.displayPrelaunch, id: $1.id, + displayPrelaunch: $1.displayPrelaunch, flagging: $1.flagging, id: $1.id, location: $1.location, name: $1.name, personalization: $1.personalization, photo: $1.photo, prelaunchActivated: $0, rewardData: $1.rewardData, sendMetaCapiEvents: $1.sendMetaCapiEvents, slug: $1.slug, @@ -276,7 +276,7 @@ extension Project { country: $1.country, creator: $1.creator, extendedProjectProperties: $1.extendedProjectProperties, memberData: $1.memberData, dates: $1.dates, - displayPrelaunch: $1.displayPrelaunch, id: $1.id, + displayPrelaunch: $1.displayPrelaunch, flagging: $1.flagging, id: $1.id, location: $1.location, name: $1.name, personalization: $1.personalization, photo: $1.photo, prelaunchActivated: $1.prelaunchActivated, rewardData: $0, sendMetaCapiEvents: $1.sendMetaCapiEvents, slug: $1.slug, staffPick: $1.staffPick, @@ -292,7 +292,7 @@ extension Project { country: $1.country, creator: $1.creator, extendedProjectProperties: $1.extendedProjectProperties, memberData: $1.memberData, dates: $1.dates, - displayPrelaunch: $1.displayPrelaunch, id: $1.id, + displayPrelaunch: $1.displayPrelaunch, flagging: $1.flagging, id: $1.id, location: $1.location, name: $1.name, personalization: $1.personalization, photo: $1.photo, prelaunchActivated: $1.prelaunchActivated, rewardData: $1.rewardData, sendMetaCapiEvents: $0, slug: $1.slug, @@ -309,7 +309,7 @@ extension Project { country: $1.country, creator: $1.creator, extendedProjectProperties: $1.extendedProjectProperties, memberData: $1.memberData, dates: $1.dates, - displayPrelaunch: $1.displayPrelaunch, id: $1.id, + displayPrelaunch: $1.displayPrelaunch, flagging: $1.flagging, id: $1.id, location: $1.location, name: $1.name, personalization: $1.personalization, photo: $1.photo, prelaunchActivated: $1.prelaunchActivated, rewardData: $1.rewardData, sendMetaCapiEvents: $1.sendMetaCapiEvents, slug: $0, @@ -326,7 +326,7 @@ extension Project { country: $1.country, creator: $1.creator, extendedProjectProperties: $1.extendedProjectProperties, memberData: $1.memberData, dates: $1.dates, - displayPrelaunch: $1.displayPrelaunch, id: $1.id, + displayPrelaunch: $1.displayPrelaunch, flagging: $1.flagging, id: $1.id, location: $1.location, name: $1.name, personalization: $1.personalization, photo: $1.photo, prelaunchActivated: $1.prelaunchActivated, rewardData: $1.rewardData, sendMetaCapiEvents: $1.sendMetaCapiEvents, slug: $1.slug, @@ -342,7 +342,7 @@ extension Project { country: $1.country, creator: $1.creator, extendedProjectProperties: $1.extendedProjectProperties, memberData: $1.memberData, dates: $1.dates, - displayPrelaunch: $1.displayPrelaunch, id: $1.id, + displayPrelaunch: $1.displayPrelaunch, flagging: $1.flagging, id: $1.id, location: $1.location, name: $1.name, personalization: $1.personalization, photo: $1.photo, prelaunchActivated: $1.prelaunchActivated, rewardData: $1.rewardData, sendMetaCapiEvents: $1.sendMetaCapiEvents, slug: $1.slug, @@ -358,7 +358,7 @@ extension Project { country: $1.country, creator: $1.creator, extendedProjectProperties: $1.extendedProjectProperties, memberData: $1.memberData, dates: $1.dates, - displayPrelaunch: $1.displayPrelaunch, id: $1.id, + displayPrelaunch: $1.displayPrelaunch, flagging: $1.flagging, id: $1.id, location: $1.location, name: $1.name, personalization: $1.personalization, photo: $1.photo, prelaunchActivated: $1.prelaunchActivated, rewardData: $1.rewardData, sendMetaCapiEvents: $1.sendMetaCapiEvents, slug: $1.slug, @@ -374,7 +374,7 @@ extension Project { country: $1.country, creator: $1.creator, extendedProjectProperties: $1.extendedProjectProperties, memberData: $1.memberData, dates: $1.dates, - displayPrelaunch: $1.displayPrelaunch, id: $1.id, + displayPrelaunch: $1.displayPrelaunch, flagging: $1.flagging, id: $1.id, location: $1.location, name: $1.name, personalization: $1.personalization, photo: $1.photo, prelaunchActivated: $1.prelaunchActivated, rewardData: $1.rewardData, sendMetaCapiEvents: $1.sendMetaCapiEvents, slug: $1.slug, @@ -390,7 +390,7 @@ extension Project { country: $1.country, creator: $1.creator, extendedProjectProperties: $1.extendedProjectProperties, memberData: $1.memberData, dates: $1.dates, - displayPrelaunch: $1.displayPrelaunch, id: $1.id, + displayPrelaunch: $1.displayPrelaunch, flagging: $1.flagging, id: $1.id, location: $1.location, name: $1.name, personalization: $1.personalization, photo: $1.photo, prelaunchActivated: $1.prelaunchActivated, rewardData: $1.rewardData, sendMetaCapiEvents: $1.sendMetaCapiEvents, slug: $1.slug, @@ -406,7 +406,7 @@ extension Project { country: $1.country, creator: $1.creator, extendedProjectProperties: $1.extendedProjectProperties, memberData: $1.memberData, dates: $1.dates, - displayPrelaunch: $1.displayPrelaunch, id: $1.id, + displayPrelaunch: $1.displayPrelaunch, flagging: $1.flagging, id: $1.id, location: $1.location, name: $1.name, personalization: $1.personalization, photo: $1.photo, prelaunchActivated: $1.prelaunchActivated, rewardData: $1.rewardData, sendMetaCapiEvents: $1.sendMetaCapiEvents, slug: $1.slug, diff --git a/KsApi/models/templates/ProjectTemplates.swift b/KsApi/models/templates/ProjectTemplates.swift index bc95b2c7cb..0a09b6475e 100644 --- a/KsApi/models/templates/ProjectTemplates.swift +++ b/KsApi/models/templates/ProjectTemplates.swift @@ -32,6 +32,7 @@ extension Project { ).timeIntervalSince1970 - 60.0 * 60.0 * 24.0 * 15.0 ), displayPrelaunch: nil, + flagging: false, id: 1, location: .template, name: "The Project", diff --git a/KsApi/queries/FetchProjectByIdQuery.graphql b/KsApi/queries/FetchProjectByIdQuery.graphql index 87f073c9ee..acb5f95f7d 100644 --- a/KsApi/queries/FetchProjectByIdQuery.graphql +++ b/KsApi/queries/FetchProjectByIdQuery.graphql @@ -7,5 +7,9 @@ query FetchProjectById($projectId: Int!, $withStoredCards: Boolean!) { backing { id } + flagging { + id + kind + } } } diff --git a/KsApi/queries/FetchProjectBySlugQuery.graphql b/KsApi/queries/FetchProjectBySlugQuery.graphql index 2cfcbb5c07..ab844bef05 100644 --- a/KsApi/queries/FetchProjectBySlugQuery.graphql +++ b/KsApi/queries/FetchProjectBySlugQuery.graphql @@ -7,5 +7,9 @@ query FetchProjectBySlug($slug: String!, $withStoredCards: Boolean!) { backing { id } + flagging { + id + kind + } } } diff --git a/Library/ViewModels/ProjectPageViewModel.swift b/Library/ViewModels/ProjectPageViewModel.swift index 713772f7ab..9716b5d213 100644 --- a/Library/ViewModels/ProjectPageViewModel.swift +++ b/Library/ViewModels/ProjectPageViewModel.swift @@ -129,6 +129,9 @@ public protocol ProjectPageViewModelOutputs { /// Emits `[ImageViewElement]` when the project has campaign data to download for an image row as soon as the urls are available. var prefetchImageURLsOnFirstLoad: Signal<[ImageViewElement], Never> { get } + /// Emits a `Bool` when a project is flagged. + var projectFlagged: Signal { get } + /// Emits a signal when an orientation change happens if the currently selected tab is campaign. var reloadCampaignData: Signal { get } @@ -198,6 +201,9 @@ public final class ProjectPageViewModel: ProjectPageViewModelType, ProjectPageVi let project = freshProjectAndRefTag .map(first) + self.projectFlagged = project.signal + .map { $0.flagging ?? false } + self.prefetchImageURLs = project.signal .skip(first: 1) .combineLatest(with: self.prepareImageAtProperty.signal.skipNil()) @@ -620,6 +626,7 @@ public final class ProjectPageViewModel: ProjectPageViewModelType, ProjectPageVi public let precreateAudioVideoURLsOnFirstLoad: Signal<[AudioVideoViewElement], Never> public let prefetchImageURLs: Signal<([URL], IndexPath), Never> public let prefetchImageURLsOnFirstLoad: Signal<[ImageViewElement], Never> + public let projectFlagged: Signal public let reloadCampaignData: Signal public let showHelpWebViewController: Signal public let updateDataSource: Signal<(NavigationSection, Project, RefTag?, [Bool], [URL]), Never> diff --git a/Library/ViewModels/ProjectPageViewModelTests.swift b/Library/ViewModels/ProjectPageViewModelTests.swift index a878f87346..cac6bf3d49 100644 --- a/Library/ViewModels/ProjectPageViewModelTests.swift +++ b/Library/ViewModels/ProjectPageViewModelTests.swift @@ -50,6 +50,7 @@ final class ProjectPageViewModelTests: TestCase { private let prefetchImageURLsFirstLoad = TestObserver<[ImageViewElement], Never>() private let precreateAudioVideoURLs = TestObserver<(AudioVideoViewElement, IndexPath), Never>() private let precreateAudioVideoURLsFirstLoad = TestObserver<[AudioVideoViewElement], Never>() + private let projectFlagged = TestObserver() private let reloadCampaignData = TestObserver<(), Never>() private let showHelpWebViewController = TestObserver() private let updateDataSourceNavigationSection = TestObserver() @@ -116,6 +117,7 @@ final class ProjectPageViewModelTests: TestCase { self.vm.outputs.precreateAudioVideoURLsOnFirstLoad.observe(self.precreateAudioVideoURLsFirstLoad.observer) self.vm.outputs.prefetchImageURLs.observe(self.prefetchImageURLs.observer) self.vm.outputs.prefetchImageURLsOnFirstLoad.observe(self.prefetchImageURLsFirstLoad.observer) + self.vm.outputs.projectFlagged.observe(self.projectFlagged.observer) self.vm.outputs.reloadCampaignData.observe(self.reloadCampaignData.observer) self.vm.outputs.showHelpWebViewController.observe(self.showHelpWebViewController.observer) self.vm.outputs.updateDataSource.map { $0.0 } @@ -1388,6 +1390,23 @@ final class ProjectPageViewModelTests: TestCase { self.presentMessageDialog.assertValues([.template]) } + func testOutput_ProjectFlagged_False() { + self.vm.inputs.configureWith(projectOrParam: .left(.template), refTag: nil) + self.vm.inputs.viewDidLoad() + + self.projectFlagged.assertValue(false) + } + + func testOutput_ProjectFlagged_True() { + var project = Project.template + project.flagging = true + + self.vm.inputs.configureWith(projectOrParam: .left(project), refTag: nil) + self.vm.inputs.viewDidLoad() + + self.projectFlagged.assertValue(true) + } + func testOutput_ShowHelpWebViewController() { self.vm.inputs.configureWith(projectOrParam: .left(.template), refTag: nil) self.vm.inputs.viewDidLoad()