Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,32 @@ WordPress-iOS uses a modular architecture with the main app and separate Swift p
- **Accessibility**: Use proper accessibility labels and traits
- **Localization**: follow best practices from @docs/localization.md

## Build & Test

**Always check for the Xcode MCP server first.**
If it is connected, use it to build and test — no exceptions.

If the Xcode MCP fails (e.g. build errors from unrelated targets), fall back to the Fastlane `test` lane:

```bash
bundle exec fastlane test
bundle exec fastlane test only_testing:TargetName/Class/method
```

If Fastlane also fails, fall back to `xcodebuild` directly:

```bash
xcodebuild \
-workspace WordPress.xcworkspace \
-scheme "${SCHEME}" \
-destination "platform=iOS Simulator,name=${DEVICE}" \
test \
| xcbeautify
```

Some test targets (e.g. `WordPressDataTests`) have their own scheme and are not part of the main `WordPress` scheme's test plan.
When the `WordPress` scheme build fails due to an unrelated target, try using the target's dedicated scheme instead.

## Coding Standards
- Follow Swift API Design Guidelines
- Use strict access control modifiers where possible
Expand Down
14 changes: 14 additions & 0 deletions Modules/Sources/DesignSystem/Foundation/Bundle+DesignSystem.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import Foundation

extension Bundle {
class var designSystemBundle: Bundle {
#if DEBUG
// Workaround for https://forums.swift.org/t/swift-5-3-swiftpm-resources-in-tests-uses-wrong-bundle-path/37051
if let testBundlePath = ProcessInfo.processInfo.environment["XCTestBundlePath"],
let bundle = Bundle(path: "\(testBundlePath)/Modules_DesignSystem.bundle") {
return bundle
}
#endif
return Bundle.module
}
}
4 changes: 2 additions & 2 deletions Modules/Sources/DesignSystem/Foundation/IconName.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,15 @@ public enum IconName: String, CaseIterable {
public extension UIImage {
enum DS {
public static func icon(named name: IconName, with configuration: UIImage.Configuration? = nil) -> UIImage? {
return UIImage(named: name.rawValue, in: .module, with: configuration)
return UIImage(named: name.rawValue, in: .designSystemBundle, with: configuration)
}
}
}

public extension Image {
enum DS {
public static func icon(named name: IconName) -> Image {
return Image(name.rawValue, bundle: .module)
return Image(name.rawValue, bundle: .designSystemBundle)
}
}
}
2 changes: 1 addition & 1 deletion Modules/Sources/DesignSystem/Typography/FontManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public enum FontManager {

// Makes sure it's performed only once.
private static let register: Void = {
let fontURLs = Bundle.module
let fontURLs = Bundle.designSystemBundle
.urls(forResourcesWithExtension: "otf", subdirectory: nil)
for fontURL in (fontURLs ?? []) {
if !CTFontManagerRegisterFontsForURL(fontURL as CFURL, .process, nil) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ import Reachability
/// to set the view values before presenting the No Results View.
///
@objc public class func controller() -> NoResultsViewController {
let storyBoard = UIStoryboard(name: "NoResults", bundle: Bundle.module)
let storyBoard = UIStoryboard(name: "NoResults", bundle: Bundle.wordPressUIBundle)
let controller = storyBoard.instantiateViewController(withIdentifier: "NoResults") as! NoResultsViewController
return controller
}
Expand Down
5 changes: 0 additions & 5 deletions Modules/Tests/DesignSystemTests/IconTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,6 @@ import SwiftUI

final class IconTests: XCTestCase {

// This test will fail if DesignSystem is built as a dynamic library. For some reason, Xcode can't locate
// the library's resource bundle.
//
// DesignSystem will be built as a dynamic library if it's a dependency of a dynamic library, such as
// the WordPressAuthenticator target.
func testCanLoadAllIconsAsUIImage() throws {
for icon in IconName.allCases {
let _ = try XCTUnwrap(UIImage.DS.icon(named: icon))
Expand Down
4 changes: 2 additions & 2 deletions Modules/Tests/JetpackStatsTests/MockStatsServiceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ struct MockStatsServiceTests {
@Test("getTopListData returns valid data for posts")
func testGetTopListDataPosts() async throws {
// GIVEN
let service = MockStatsService(timeZone: .current)
let service = MockStatsService(timeZone: .eastern)
let dateInterval = calendar.makeDateInterval(for: .today)

// WHEN
Expand Down Expand Up @@ -43,7 +43,7 @@ struct MockStatsServiceTests {
@Test("Verify getChartData returns valid data for views metric with today range")
func testGetChartDataViewsToday() async throws {
// GIVEN
let service = MockStatsService(timeZone: .current)
let service = MockStatsService(timeZone: .eastern)
let dateInterval = calendar.makeDateInterval(for: .today)
let granularity = dateInterval.preferredGranularity

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import XCTest
@testable import WordPress
@testable import WordPressUI

class NoResultsViewControllerTests: XCTestCase {

Expand Down
51 changes: 0 additions & 51 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,12 @@ require 'zlib'

RUBY_REPO_VERSION = File.read('./.ruby-version').rstrip
XCODE_WORKSPACE = 'WordPress.xcworkspace'
XCODE_SCHEME = 'WordPress'
XCODE_CONFIGURATION = 'Debug'
EXPECTED_XCODE_VERSION = File.read('.xcode-version').rstrip
GUTENBERG_VERSION = 'v1.121.0'

PROJECT_DIR = __dir__
abort('Project directory contains one or more spaces – unable to continue.') if PROJECT_DIR.include?(' ')

task default: %w[test]

desc 'Install required dependencies'
task dependencies: %w[dependencies:check assets:check dependencies:gutenberg_xcframeworks]

Expand Down Expand Up @@ -175,36 +171,6 @@ task :mocks do
sh "#{File.join(PROJECT_DIR, 'API-Mocks', 'scripts', 'start.sh')} 8282"
end

desc "Build #{XCODE_SCHEME}"
task build: [:dependencies] do
xcodebuild(:build)
end

desc "Profile build #{XCODE_SCHEME}"
task buildprofile: [:dependencies] do
ENV['verbose'] = '1'
xcodebuild(:build, "OTHER_SWIFT_FLAGS='-Xfrontend -debug-time-compilation -Xfrontend -debug-time-expression-type-checking'")
end

task timed_build: [:clean] do
require 'benchmark'
time = Benchmark.measure do
Rake::Task['build'].invoke
end
puts "CPU Time: #{time.total}"
puts "Wall Time: #{time.real}"
end

desc 'Run test suite'
task test: [:dependencies] do
xcodebuild(:build, :test)
end

desc 'Remove any temporary products'
task :clean do
xcodebuild(:clean)
end

desc 'Checks the source for style errors'
task :lint do
sh 'pushd BuildTools; export SDKROOT=$(xcrun --sdk macosx --show-sdk-path); swift package plugin --allow-writing-to-directory .. --allow-writing-to-package-directory swiftlint --working-directory .. --quiet; popd'
Expand Down Expand Up @@ -634,23 +600,6 @@ def display_prompt_response?
response == 'Y'
end

def xcodebuild(*build_cmds)
cmd = 'xcodebuild'
cmd += " -destination 'platform=iOS Simulator,name=iPhone 16'"
cmd += ' -sdk iphonesimulator'
cmd += " -workspace #{XCODE_WORKSPACE}"
cmd += " -scheme #{XCODE_SCHEME}"
cmd += " -configuration #{xcode_configuration}"
cmd += ' '
cmd += build_cmds.map(&:to_s).join(' ')
cmd += ' | bundle exec xcpretty && exit ${PIPESTATUS[0]}' unless ENV['verbose']
sh(cmd)
end

def xcode_configuration
ENV.fetch('XCODE_CONFIGURATION') { XCODE_CONFIGURATION }
end

def command?(command)
system("which #{command} > /dev/null 2>&1")
end
Expand Down
56 changes: 35 additions & 21 deletions Tests/KeystoneTests/WordPressUnitTests.xctestplan
Original file line number Diff line number Diff line change
Expand Up @@ -33,58 +33,65 @@
"testTargets" : [
{
"target" : {
"containerPath" : "container:..\/Modules",
"identifier" : "WordPressFluxTests",
"name" : "WordPressFluxTests"
"containerPath" : "container:WordPress.xcodeproj",
"identifier" : "4AD953BA2C21451700D0EEFA",
"name" : "WordPressAuthenticatorTests"
}
},
{
"target" : {
"containerPath" : "container:WordPress.xcodeproj",
"identifier" : "E16AB92914D978240047A2E5",
"name" : "WordPressTest"
"containerPath" : "container:..\/Modules",
"identifier" : "DesignSystemTests",
"name" : "DesignSystemTests"
}
},
{
"target" : {
"containerPath" : "container:..\/Modules",
"identifier" : "WordPressCoreTests",
"name" : "WordPressCoreTests"
"identifier" : "WordPressSharedTests",
"name" : "WordPressSharedTests"
}
},
{
"target" : {
"containerPath" : "container:..\/Modules",
"identifier" : "WordPressSharedTests",
"name" : "WordPressSharedTests"
"identifier" : "JetpackStatsWidgetsCoreTests",
"name" : "JetpackStatsWidgetsCoreTests"
}
},
{
"target" : {
"containerPath" : "container:WordPress.xcodeproj",
"identifier" : "4A8280FC2E5FE9B60037E180",
"name" : "WordPressKitTests"
"containerPath" : "container:..\/Modules",
"identifier" : "WordPressCoreTests",
"name" : "WordPressCoreTests"
}
},
{
"target" : {
"containerPath" : "container:..\/Modules",
"identifier" : "JetpackStatsWidgetsCoreTests",
"name" : "JetpackStatsWidgetsCoreTests"
"identifier" : "WordPressUIUnitTests",
"name" : "WordPressUIUnitTests"
}
},
{
"target" : {
"containerPath" : "container:WordPress.xcodeproj",
"identifier" : "4AD953BA2C21451700D0EEFA",
"name" : "WordPressAuthenticatorTests"
"identifier" : "E16AB92914D978240047A2E5",
"name" : "WordPressTest"
}
},
{
"target" : {
"containerPath" : "container:..\/Modules",
"identifier" : "WordPressUIUnitTests",
"name" : "WordPressUIUnitTests"
"identifier" : "JetpackStatsTests",
"name" : "JetpackStatsTests"
}
},
{
"target" : {
"containerPath" : "container:..\/Modules",
"identifier" : "AsyncImageKitTests",
"name" : "AsyncImageKitTests"
}
},
{
Expand All @@ -97,8 +104,15 @@
{
"target" : {
"containerPath" : "container:..\/Modules",
"identifier" : "AsyncImageKitTests",
"name" : "AsyncImageKitTests"
"identifier" : "WordPressFluxTests",
"name" : "WordPressFluxTests"
}
},
{
"target" : {
"containerPath" : "container:WordPress.xcodeproj",
"identifier" : "4A8280FC2E5FE9B60037E180",
"name" : "WordPressKitTests"
}
}
],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "2630"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "WordPressShared"
BuildableName = "WordPressShared"
BlueprintName = "WordPressShared"
ReferencedContainer = "container:../Modules">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<TestPlans>
<TestPlanReference
reference = "container:../Modules/Tests/WordPressSharedTests/WordPressShared.xctestplan"
default = "YES">
</TestPlanReference>
</TestPlans>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "WordPressShared"
BuildableName = "WordPressShared"
BlueprintName = "WordPressShared"
ReferencedContainer = "container:../Modules">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
2 changes: 1 addition & 1 deletion fastlane/Fastfile
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ before_all do |lane|
ENV['FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT'] = '120'

# Skip these checks/steps for test lane (not needed for testing)
next if lane == :test_without_building
next if %i[test test_without_building].include?(lane)

# Ensure we use the latest version of the toolkit
check_for_toolkit_updates unless is_ci || ENV['FASTLANE_SKIP_TOOLKIT_UPDATE_CHECK']
Expand Down
Loading