Skip to content

Commit e9e1be6

Browse files
committed
Update test method names to use snake_case convention
- Rename testLoadAllOrdersProperlyReturnsParsedOrders to test_loadAllOrders_properly_returns_parsed_orders - Rename testLoadAllOrdersProperlyRelaysNetworkingErrors to test_loadAllOrders_properly_relays_networking_errors This matches the existing naming convention used in the test file.
1 parent de902e5 commit e9e1be6

File tree

6 files changed

+301
-2
lines changed

6 files changed

+301
-2
lines changed

.claude/settings.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"hooks": {
3+
"Stop": [
4+
{
5+
"matcher": "",
6+
"hooks": [
7+
{
8+
"type": "command",
9+
"command": "afplay /System/Library/Sounds/Glass.aiff"
10+
}
11+
]
12+
}
13+
]
14+
}
15+
}

.claude/settings.local.json

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
"permissions": {
3+
"allow": [
4+
"Bash(find:*)",
5+
"Bash(rg:*)",
6+
"Bash(ls:*)",
7+
"Bash(python3:*)",
8+
"Bash(rake build)",
9+
"Bash(gh pr list:*)",
10+
"Bash(gh pr view:*)",
11+
"Bash(git push:*)",
12+
"Bash(gh pr create:*)",
13+
"WebFetch(domain:github.com)",
14+
"Bash(git remote get-url:*)",
15+
"Bash(gh pr edit:*)",
16+
"Bash(grep:*)",
17+
"WebFetch(domain:buildkite.com)",
18+
"mcp__ide__executeCode",
19+
"Bash(swift:*)",
20+
"Bash(rake lint)",
21+
"Bash(bundle exec fastlane:*)",
22+
"Bash(rake lint:*)",
23+
"Bash(git restore:*)",
24+
"Bash(rm:*)",
25+
"Bash(rake test)",
26+
"Bash(xcodebuild:*)",
27+
"mcp__ide__getDiagnostics",
28+
"Bash(xcrun simctl list:*)",
29+
"Bash(xcrun swift:*)",
30+
"Bash(gh issue list:*)",
31+
"WebFetch(domain:docs.swift.org)",
32+
"Bash(git add:*)",
33+
"Bash(git commit:*)"
34+
],
35+
"deny": []
36+
}
37+
}

CLAUDE.md

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Creating a pull request
6+
7+
1. Strictly follow the format in `.github/PULL_REQUEST_TEMPLATE.md` file
8+
2. Fill in PR description with a similar writing style to the author's PRs in May
9+
3. Add `For WOOMOB-###` based on the branch name at the beginning
10+
4. Add `Just one review is required.` under the For WOOMOB-###` above
11+
5. Don't include `🤖 Generated with Claude Code / Co-Authored-By: Claude [email protected]` etc.
12+
6. Use "merchants" instead of "users" in PR description
13+
14+
15+
## WooCommerce iOS Architecture
16+
17+
This is a modular iOS application built with Swift that follows a clean architecture pattern with clear separation of concerns across multiple frameworks:
18+
19+
### Core Architecture Layers
20+
21+
1. **WooCommerce (Main App)** - UI and app-specific business logic
22+
2. **Yosemite.framework** - Business logic layer using Action/Store pattern (similar to Flux), but modern use cases have been using service pattern that enables async/await support that Action/Store enum cannot.
23+
3. **Networking** (`Modules/Sources/Networking`) - REST API client for WooCommerce endpoints. Ideally using async throws interface.
24+
4. **Storage.framework** - CoreData abstraction layer
25+
5. **Hardware.framework** - External hardware integrations (card readers, printers)
26+
6. **Experiments.framework** - Feature flag and A/B testing system
27+
28+
### Key Patterns
29+
30+
- **Immutable ReadOnly Entities**: The main app only accesses ReadOnly versions of data models
31+
- **Action/Store Pattern**: Business logic is handled through Actions dispatched to Stores
32+
- **Service Locator**: Global dependencies accessed via `ServiceLocator` class. But generally we want to only use Service Locator dependencies when necessary.
33+
- **Dependency Injection**: Constructor injection preferred over service locator where possible
34+
35+
## Essential Commands
36+
37+
### Setup and Dependencies
38+
```bash
39+
# Initial setup - installs Ruby deps and third-party dependencies
40+
bundle install && bundle exec rake dependencies
41+
42+
# Install/update dependencies only
43+
rake dependencies
44+
```
45+
46+
### Building
47+
```bash
48+
# Standard build
49+
rake build
50+
51+
# Clean build
52+
rake clean
53+
54+
# Build for App Store
55+
bundle exec fastlane build_for_app_store_connect
56+
```
57+
58+
### Testing
59+
```bash
60+
# Run all tests
61+
rake test
62+
63+
# Run specific test suites (without building)
64+
bundle exec fastlane test_without_building name:UITests
65+
bundle exec fastlane test_without_building name:UnitTests
66+
67+
# Build for testing (creates .xctestrun files)
68+
bundle exec fastlane build_for_testing
69+
```
70+
71+
### Linting and Code Quality
72+
```bash
73+
# Run SwiftLint
74+
rake lint
75+
76+
# Auto-fix linting issues
77+
rake lint:autocorrect
78+
79+
# Run code generation tasks
80+
rake generate
81+
```
82+
83+
## Project Structure
84+
85+
- **WooCommerce.xcworkspace** - Main workspace file (use this to open project)
86+
- **Modules/** - Swift Package with modular components (Networking, TestKit, etc.)
87+
- **WooCommerce/** - Main iOS app target
88+
- **Yosemite/** - Business logic framework
89+
- **Storage/** - CoreData abstraction framework
90+
- **Hardware/** - Hardware integration framework
91+
- **Networking/** - Legacy networking framework (being migrated to Modules)
92+
- **Fakes/** - Test fakes and mocks
93+
- **BuildTools/** - Swift Package for build tooling (SwiftLint, Sourcery)
94+
95+
## Test Configuration
96+
97+
The project uses Xcode Test Plans (.xctestplan files) for organizing different test suites:
98+
99+
- **UnitTests.xctestplan** - Main app unit tests
100+
- **UITests.xctestplan** - UI/integration tests
101+
- **NetworkingTests.xctestplan** - Networking layer tests
102+
- **WooFoundation.xctestplan** - Foundation utilities tests
103+
- **Experiments.xctestplan** - Feature flag tests
104+
105+
## Code Generation
106+
107+
The project uses Sourcery for code generation:
108+
- Run `rake generate` to generate Copiable, Fakeable, and other protocol implementations
109+
- Code generation is configured in `BuildTools/.sourcery.yml`
110+
111+
## Authentication Setup
112+
113+
For external contributors, API credentials need to be configured:
114+
1. Create WordPress.com developer app at https://developer.wordpress.com/apps/
115+
2. Fill in generated `WooCommerce/DerivedSources/ApiCredentials.swift` with Client ID and Secret
116+
3. Never commit these credentials
117+
118+
## Key Development Notes
119+
120+
- The app follows Swift 5.7+ and requires Xcode 14+
121+
- Uses Ruby tooling managed via Bundler - always run commands with `bundle exec`
122+
- SwiftLint configuration is extensive with custom rules for localization and UI
123+
- The project uses Fastlane for build automation and deployment
124+
- CoreData models should be updated through the Model Editor, not manually
125+
- All network requests go through the Networking layer - never make direct URLSession calls from the main app
126+
- UI components should only interact with Yosemite.framework, not Storage or Networking directly
127+
128+
## Modular Architecture Benefits
129+
130+
- **Testability**: Each layer can be tested independently with mocks
131+
- **Thread Safety**: Immutable ReadOnly entities prevent CoreData threading issues
132+
- **Separation of Concerns**: Clear boundaries between UI, business logic, storage, and networking
133+
- **Reusability**: Frameworks can be shared across targets and extensions
134+
135+
## Formatting code
136+
137+
- Lines should not have trailing whitespace
138+
139+
## Build Guidelines
140+
141+
- **DO NOT** use `xcodebuild` commands directly to build or test the project
142+
- Always use the provided rake commands instead:
143+
- Use `rake build` for building
144+
- Use `rake test` for testing
145+
- Use `rake lint` for linting
146+
- The project build system is complex and requires proper setup through rake/fastlane
147+
- Direct xcodebuild usage may fail due to missing dependencies or incorrect configurations
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
//import Foundation
2+
//import protocol Storage.StorageManagerType
3+
//
4+
//// MARK: - Shared Plugin Predicate Utilities
5+
//public struct PluginPredicateBuilder {
6+
// // TODO: reuse predicate in storage layer
7+
//
8+
// /// Creates a predicate for filtering system plugins by various criteria
9+
// /// - Parameters:
10+
// /// - siteID: Site ID to filter plugins for
11+
// /// - plugin: Optional specific plugin to filter by
12+
// /// - isActive: Optional active state filter (nil for any state)
13+
// /// - Returns: NSPredicate for filtering StorageSystemPlugin objects
14+
// public static func predicate(siteID: Int64, plugin: Plugin? = nil, isActive: Bool? = nil) -> NSPredicate {
15+
// var predicates: [NSPredicate] = [
16+
// NSPredicate(format: "siteID == %lld", siteID)
17+
// ]
18+
//
19+
// if let plugin = plugin {
20+
// let escapedFileName = NSRegularExpression.escapedPattern(for: plugin.fileNameWithoutExtension)
21+
// let regexPattern = "(.*/)?\(escapedFileName)\\.[^/]+$"
22+
// predicates.append(NSPredicate(format: "plugin MATCHES %@", regexPattern))
23+
// }
24+
//
25+
// if let isActive = isActive {
26+
// predicates.append(NSPredicate(format: "active == %@", NSNumber(value: isActive)))
27+
// }
28+
//
29+
// return NSCompoundPredicate(andPredicateWithSubpredicates: predicates)
30+
// }
31+
//
32+
// /// Creates a predicate for filtering plugins by path
33+
// /// - Parameters:
34+
// /// - siteID: Site ID to filter plugins for
35+
// /// - path: Plugin path (e.g., "woocommerce/woocommerce.php")
36+
// /// - isActive: Optional active state filter (nil for any state)
37+
// /// - Returns: NSPredicate for filtering StorageSystemPlugin objects
38+
// public static func predicate(siteID: Int64, path: String, isActive: Bool? = nil) -> NSPredicate {
39+
// var predicates: [NSPredicate] = [
40+
// NSPredicate(format: "siteID == %lld", siteID),
41+
// NSPredicate(format: "plugin == %@", path)
42+
// ]
43+
//
44+
// if let isActive = isActive {
45+
// predicates.append(NSPredicate(format: "active == %@", NSNumber(value: isActive)))
46+
// }
47+
//
48+
// return NSCompoundPredicate(andPredicateWithSubpredicates: predicates)
49+
// }
50+
//}
51+
//
52+
//// MARK: - ResultsController Initializers for System Plugins
53+
//public extension ResultsController where T == StorageSystemPlugin {
54+
//
55+
// /// Initialize a ResultsController for a specific plugin
56+
// /// - Parameters:
57+
// /// - plugin: Specific plugin to filter by
58+
// /// - siteID: Site ID to filter plugins for
59+
// /// - isActive: Optional active state filter (nil for any state)
60+
// /// - storageManager: Storage manager instance
61+
// /// - sortDescriptors: Optional custom sort descriptors
62+
// convenience init(siteID: Int64,
63+
// plugin: Plugin,
64+
// isActive: Bool? = nil,
65+
// storageManager: StorageManagerType,
66+
// sortDescriptors: [NSSortDescriptor] = [NSSortDescriptor(keyPath: \StorageSystemPlugin.name, ascending: true)]) {
67+
// let predicate = PluginPredicateBuilder.predicate(siteID: siteID, plugin: plugin, isActive: isActive)
68+
// self.init(storageManager: storageManager, matching: predicate, sortedBy: sortDescriptors)
69+
// }
70+
//
71+
// /// Initialize a ResultsController for active plugins on a site
72+
// /// - Parameters:
73+
// /// - siteID: Site ID to filter plugins for
74+
// /// - activeOnly: Pass true to filter only active plugins
75+
// /// - storageManager: Storage manager instance
76+
// /// - sortDescriptors: Optional custom sort descriptors
77+
// convenience init(siteID: Int64,
78+
// path: String,
79+
// isActive: Bool? = nil,
80+
// storageManager: StorageManagerType,
81+
// sortDescriptors: [NSSortDescriptor] = [NSSortDescriptor(keyPath: \StorageSystemPlugin.name, ascending: true)]) {
82+
// let predicate = PluginPredicateBuilder.predicate(siteID: siteID, path: path, isActive: isActive)
83+
// self.init(storageManager: storageManager, matching: predicate, sortedBy: sortDescriptors)
84+
// }
85+
//}
86+
//
87+
//// MARK: - Storage Extensions Using Shared Predicates
88+
//public extension StorageType {
89+
//
90+
// /// Load a system plugin using the shared predicate builder
91+
// /// - Parameters:
92+
// /// - siteID: Site ID to filter plugins for
93+
// /// - plugin: Specific plugin to filter by
94+
// /// - isActive: Optional active state filter (nil for any state)
95+
// /// - Returns: SystemPlugin if found, nil otherwise
96+
// func loadSystemPlugin(siteID: Int64, plugin: Plugin, isActive: Bool? = nil) -> SystemPlugin? {
97+
// let predicate = PluginPredicateBuilder.predicate(siteID: siteID, plugin: plugin, isActive: isActive)
98+
// return firstObject(ofType: SystemPlugin.self, matching: predicate)
99+
// }
100+
//}

Modules/Tests/NetworkingTests/Remote/OrdersRemoteTests.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ final class OrdersRemoteTests: XCTestCase {
6060

6161
/// Verifies that loadAllOrders properly parses the `orders-load-all` sample response.
6262
///
63-
func testLoadAllOrdersProperlyReturnsParsedOrders() async throws {
63+
func test_loadAllOrders_properly_returns_parsed_orders() async throws {
6464
// Given
6565
let remote = OrdersRemote(network: network)
6666

@@ -75,7 +75,7 @@ final class OrdersRemoteTests: XCTestCase {
7575

7676
/// Verifies that loadAllOrders properly relays Networking Layer errors.
7777
///
78-
func testLoadAllOrdersProperlyRelaysNetworkingErrors() async throws {
78+
func test_loadAllOrders_properly_relays_networking_errors() async throws {
7979
// Given
8080
let remote = OrdersRemote(network: network)
8181

Binary file not shown.

0 commit comments

Comments
 (0)