Skip to content
Open
21 changes: 18 additions & 3 deletions Sources/WordPressData/Swift/Blog+Features.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import Foundation
case siteMonitoring
case publicize
case shareButtons
case jetpackNewsletter
}

extension Blog {
Expand Down Expand Up @@ -82,7 +83,9 @@ extension Blog {
case .customThemes:
return supportsRestAPI && isAdmin && !isHostedAtWPcom
case .premiumThemes:
return supports(.customThemes) && (planID?.intValue == Self.jetpackProfessionalYearlyPlanId || planID?.intValue == Self.jetpackProfessionalMonthlyPlanId)
return supports(.customThemes)
&& (planID?.intValue == Self.jetpackProfessionalYearlyPlanId
|| planID?.intValue == Self.jetpackProfessionalMonthlyPlanId)
case .private:
return isHostedAtWPcom
case .sharing:
Expand Down Expand Up @@ -132,6 +135,8 @@ extension Blog {
return supportsPublicize
case .shareButtons:
return supportsShareButtons
case .jetpackNewsletter:
return supportsJetpackNewsletter
}
}

Expand Down Expand Up @@ -171,6 +176,14 @@ private extension Blog {
}
}

var supportsJetpackNewsletter: Bool {
guard supportsRestAPI else { return false }
if isHostedAtWPcom {
return true
}
return isJetpackModuleActive("subscriptions")
}

var supportsShareButtons: Bool {
guard isAdmin, supportsRestAPI else {
return false
Expand All @@ -192,7 +205,8 @@ private extension Blog {
return true
}
if account == nil && !isHostedAtWPcom && selfHostedSiteRestApi != nil
&& hasRequiredWordPressVersion("5.5") {
&& hasRequiredWordPressVersion("5.5")
{
return true
}
return false
Expand All @@ -207,7 +221,8 @@ private extension Blog {

func hasRequiredJetpackVersion(_ requiredVersion: String) -> Bool {
guard supportsRestAPI, !isHostedAtWPcom,
let version = jetpack?.version else {
let version = jetpack?.version
else {
return false
}
return version.compare(requiredVersion, options: .numeric) != .orderedAscending
Expand Down
10 changes: 9 additions & 1 deletion Sources/WordPressData/Swift/PostMetadata.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ public struct PostMetadata: Hashable {
self.isJetpackNewsletterEmailDisabled = container.getAdaptiveBool(for: .jetpackNewsletterEmailDisabled)
}

public init(accessLevel: JetpackPostAccessLevel?, isJetpackNewsletterEmailDisabled: Bool = false) {
self.accessLevel = accessLevel
self.isJetpackNewsletterEmailDisabled = isJetpackNewsletterEmailDisabled
}

/// Applies the metadata values to the container and returns them
/// as metadata values.
public func encode(in container: inout PostMetadataContainer) {
Expand All @@ -28,7 +33,10 @@ public struct PostMetadata: Hashable {
container.accessLevel = accessLevel
}
if previous.isJetpackNewsletterEmailDisabled != isJetpackNewsletterEmailDisabled {
container.setValue(String(describing: isJetpackNewsletterEmailDisabled), for: .jetpackNewsletterEmailDisabled)
container.setValue(
String(describing: isJetpackNewsletterEmailDisabled),
for: .jetpackNewsletterEmailDisabled
)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,11 @@ struct CustomPostSettingsViewModelTests {
)

// Sanity: the parsed draft has the disabled connection from the post.
#expect(viewModel.settings.socialSharingDraft?.connectionsByID == [
"12345": .init(id: "12345", enabled: false)
])
#expect(
viewModel.settings.socialSharingDraft?.connectionsByID == [
"12345": .init(id: "12345", enabled: false)
]
)

// When: settings is reassigned to a value-equivalent copy (simulating
// `resolveTermNames` writing back resolved-but-identical tags).
Expand Down Expand Up @@ -195,6 +197,41 @@ struct CustomPostSettingsViewModelTests {
#expect(viewModel.v2SocialSharing == nil)
#expect(viewModel.getSettingsToSave(for: viewModel.settings).socialSharingDraft == nil)
}

// MARK: - shouldShow Jetpack rows

@Test("shouldShow .jetpackAccessLevel is true for post type when Jetpack newsletter is available")
func shouldShowAccessLevelTrue() throws {
let viewModel = try makeViewModel(postTypeSlug: "post", jetpackNewsletter: true)
#expect(viewModel.shouldShow(.jetpackAccessLevel))
}

@Test("shouldShow .jetpackAccessLevel is false for non-post type")
func shouldShowAccessLevelFalseForNonPost() throws {
let viewModel = try makeViewModel(postTypeSlug: "page", jetpackNewsletter: true)
#expect(!viewModel.shouldShow(.jetpackAccessLevel))
}

@Test("shouldShow .jetpackAccessLevel is false when Jetpack newsletter is unavailable")
func shouldShowAccessLevelFalseWithoutNewsletter() throws {
let viewModel = try makeViewModel(postTypeSlug: "post", jetpackNewsletter: false)
#expect(!viewModel.shouldShow(.jetpackAccessLevel))
}

@Test("shouldShow .jetpackNewsletterEmailOptions is true only in publishing context")
func shouldShowNewsletterTrueOnlyInPublishing() throws {
let publishingVM = try makeViewModel(postTypeSlug: "post", jetpackNewsletter: true, context: .publishing)
#expect(publishingVM.shouldShow(.jetpackNewsletterEmailOptions))

let settingsVM = try makeViewModel(postTypeSlug: "post", jetpackNewsletter: true, context: .settings)
#expect(!settingsVM.shouldShow(.jetpackNewsletterEmailOptions))
}

@Test("shouldShow .jetpackNewsletterEmailOptions is false for non-post type")
func shouldShowNewsletterFalseForNonPost() throws {
let viewModel = try makeViewModel(postTypeSlug: "page", jetpackNewsletter: true, context: .publishing)
#expect(!viewModel.shouldShow(.jetpackNewsletterEmailOptions))
}
}

// MARK: - Test Helpers
Expand Down Expand Up @@ -295,7 +332,44 @@ private func makeConnectionsService() -> SiteSocialConnectionsService {
)
}

private func makePostTypeDetails(supportsPublicize: Bool = true) -> PostTypeDetailsWithEditContext {
@MainActor
private func makeViewModel(
postTypeSlug: String,
jetpackNewsletter: Bool,
context: PostSettingsContext = .settings
) throws -> CustomPostSettingsViewModel {
let coreData = ContextManager.forTesting().mainContext
let builder = BlogBuilder(coreData)
let blog: Blog
if jetpackNewsletter {
blog = builder.withAnAccount().with(modules: ["subscriptions"]).build()
} else {
blog = builder.withAnAccount().build()
}
try coreData.save()
let post = try makePostWithDisabledConnection()
let details = makePostTypeDetails(supportsPublicize: true, slug: postTypeSlug)
let dependencies = try makeServiceDependencies()
let editorService = CustomPostEditorService(
blog: blog,
post: post,
details: details,
client: dependencies.client,
wpService: dependencies.wpService,
initialParams: nil
)
return CustomPostSettingsViewModel(
editorService: editorService,
blog: blog,
socialConnectionsService: nil,
context: context
)
}

private func makePostTypeDetails(
supportsPublicize: Bool = true,
slug: String = "test_post_type"
) -> PostTypeDetailsWithEditContext {
var supports: [PostTypeSupports: JsonValue] = [
.title: .bool(true),
.editor: .bool(true)
Expand All @@ -311,11 +385,11 @@ private func makePostTypeDetails(supportsPublicize: Bool = true) -> PostTypeDeta
viewable: true,
labels: makePostTypeLabels(),
name: "Test Post Type",
slug: "test_post_type",
slug: slug,
supports: PostTypeSupportsMap(map: supports),
hasArchive: .bool(false),
taxonomies: [],
restBase: "test_post_type",
restBase: slug,
restNamespace: "wp/v2",
visibility: PostTypeVisibility(showInNavMenus: true, showUi: true),
icon: nil
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import Testing
import Foundation
import WordPressAPIInternal
import WordPressData

@testable import WordPress

struct PostMetaJetpackNewsletterTests {

// MARK: - Access Level

@Test("addingJetpackNewsletterAccess round-trips through jetpackNewsletterAccess")
func accessLevelRoundTrip() {
let meta = PostMeta().addingJetpackNewsletterAccess(.subscribers)
#expect(meta.jetpackNewsletterAccess == .subscribers)
}

@Test("addingJetpackNewsletterAccess supports paid_subscribers")
func accessLevelPaidSubscribers() {
let meta = PostMeta().addingJetpackNewsletterAccess(.paidSubscribers)
#expect(meta.jetpackNewsletterAccess == .paidSubscribers)
}

@Test("addingJetpackNewsletterAccess with nil clears the value")
func accessLevelClear() {
let meta = PostMeta()
.addingJetpackNewsletterAccess(.subscribers)
.addingJetpackNewsletterAccess(nil)
#expect(meta.jetpackNewsletterAccess == nil)
}

@Test("jetpackNewsletterAccess returns nil when key is absent")
func accessLevelAbsent() {
#expect(PostMeta().jetpackNewsletterAccess == nil)
}

@Test("jetpackNewsletterAccess returns nil for unknown raw values")
func accessLevelUnknownRawValue() {
let meta = PostMeta().withValue(key: "_jetpack_newsletter_access", value: .string("not_a_level"))
#expect(meta.jetpackNewsletterAccess == nil)
}

// MARK: - Email Disabled

@Test("addingJetpackNewsletterEmailDisabled true round-trips")
func emailDisabledTrueRoundTrip() {
let meta = PostMeta().addingJetpackNewsletterEmailDisabled(true)
#expect(meta.isJetpackNewsletterEmailDisabled)
}

@Test("addingJetpackNewsletterEmailDisabled false round-trips")
func emailDisabledFalseRoundTrip() {
let meta = PostMeta().addingJetpackNewsletterEmailDisabled(false)
#expect(!meta.isJetpackNewsletterEmailDisabled)
}

@Test("isJetpackNewsletterEmailDisabled returns false when key is absent")
func emailDisabledAbsent() {
#expect(!PostMeta().isJetpackNewsletterEmailDisabled)
}
}
Loading