Add SwiftLint

This commit is contained in:
Otavio Cordeiro 2026-01-13 12:42:30 -03:00
parent d3b596c26f
commit db4cb42486
66 changed files with 395 additions and 213 deletions

View file

@ -2,9 +2,7 @@ name: Build
on: on:
pull_request: pull_request:
branches: branches: [ main ]
- main
- develop
jobs: jobs:
build: build:

View file

@ -2,9 +2,7 @@ name: SwiftFormat Check
on: on:
pull_request: pull_request:
branches: branches: [ main ]
- main
- develop
jobs: jobs:
swiftformat: swiftformat:
@ -19,9 +17,4 @@ jobs:
run: brew install swiftformat run: brew install swiftformat
- name: Check code formatting - name: Check code formatting
run: | run: swiftformat --lint .
swiftformat --lint .
if [ $? -ne 0 ]; then
echo "❌ Code formatting issues detected. Please run 'swiftformat .' locally to fix."
exit 1
fi

20
.github/workflows/swiftlint.yml vendored Normal file
View file

@ -0,0 +1,20 @@
name: SwiftLint Check
on:
pull_request:
branches: [ main ]
jobs:
swiftlint:
name: SwiftLint
runs-on: macos-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install SwiftLint
run: brew install swiftlint
- name: Run SwiftLint
run: swiftlint lint --reporter github-actions-logging

View file

@ -2,9 +2,7 @@ name: Unit Tests
on: on:
pull_request: pull_request:
branches: branches: [ main ]
- main
- develop
jobs: jobs:
test: test:

64
.swiftlint.yml Normal file
View file

@ -0,0 +1,64 @@
# Minimal SwiftLint configuration
# SwiftFormat handles all formatting, so SwiftLint focuses on code quality and best practices
disabled_rules:
- line_length
- opening_brace
- function_parameter_count
- nesting
- large_tuple
- type_name
- force_unwrapping
- blanket_disable_command
opt_in_rules:
- array_init
- contains_over_first_not_nil
- convenience_type
- discouraged_assert
- discouraged_object_literal
- empty_count
- empty_string
- empty_xctest_method
- explicit_init
- fatal_error_message
- first_where
- force_cast
- force_try
- implicit_return
- joined_default_parameter
- last_where
- legacy_random
- lower_acl_than_parent
- multiline_function_chains
- multiline_parameters
- multiline_parameters_brackets
- no_fallthrough_only
- operator_usage_whitespace
- overridden_super_call
- prohibited_super_call
- redundant_nil_coalescing
- single_test_class
- sorted_first_last
- static_operator
- switch_case_alignment
- trailing_closure
- unavailable_function
- unneeded_parentheses_in_closure_argument
- unused_control_flow_label
- vertical_whitespace_closing_braces
- xct_specific_matcher
- xctfail_message
- yoda_condition
multiline_parameters:
allows_single_line: false
excluded:
- .build
- "**/.build"
- DerivedData
- Pods
- Carthage
- .git
- node_modules

View file

@ -207,6 +207,7 @@ struct TritonEnvironment: TritonEnvironmentProtocol {
) )
} }
// swiftlint:disable function_body_length
init( init(
authSessionServiceFactory: any AuthSessionServiceFactoryProtocol, authSessionServiceFactory: any AuthSessionServiceFactoryProtocol,
sessionServiceFactory: any SessionServiceFactoryProtocol, sessionServiceFactory: any SessionServiceFactoryProtocol,
@ -233,11 +234,9 @@ struct TritonEnvironment: TritonEnvironmentProtocol {
allocation: .static allocation: .static
) { container in ) { container in
let authSessionService = container.resolve() as any AuthSessionServiceProtocol let authSessionService = container.resolve() as any AuthSessionServiceProtocol
return networkClient.makeOMGAPIClient( return networkClient.makeOMGAPIClient {
authTokenProvider: { await authSessionService.accessToken
await authSessionService.accessToken }
}
)
} }
container.register( container.register(
@ -375,4 +374,5 @@ struct TritonEnvironment: TritonEnvironmentProtocol {
) )
} }
} }
// swiftlint:enable function_body_length
} }

View file

@ -41,8 +41,8 @@ struct TritonScene: Scene {
#endif #endif
.environment(makeAccountUpdateService()) .environment(makeAccountUpdateService())
.handlesExternalEvents( .handlesExternalEvents(
preferring: Set(arrayLiteral: "viewer"), preferring: ["viewer"],
allowing: Set(arrayLiteral: "*") allowing: ["*"]
) )
.onAppear { .onAppear {
environment environment

View file

@ -70,10 +70,8 @@ actor AccountUpdateRepository: AccountUpdateRepositoryProtocol {
await fetchAccountInformation() await fetchAccountInformation()
} }
for await isLoggedIn in authSessionService.observeLoginState() { for await isLoggedIn in authSessionService.observeLoginState() where isLoggedIn {
if isLoggedIn { await fetchAccountInformation()
await fetchAccountInformation()
}
} }
} }
} }

View file

@ -25,10 +25,8 @@ final class AuthAppViewModel {
let currentState = await authSessionService.isLoggedIn let currentState = await authSessionService.isLoggedIn
isLoggedIn = currentState isLoggedIn = currentState
for await loginState in authSessionService.observeLoginState() { for await loginState in authSessionService.observeLoginState() where isLoggedIn != loginState {
if isLoggedIn != loginState { isLoggedIn = loginState
isLoggedIn = loginState
}
} }
} }
} }

View file

@ -81,7 +81,7 @@ final class AuthSessionServiceTests: XCTestCase {
) )
} }
func testObserveLoginStateYieldsCurrentState() async { func testObserveLoginStateYieldsCurrentState() async throws {
// Given // Given
let initialState = await service.isLoggedIn let initialState = await service.isLoggedIn
@ -97,9 +97,8 @@ final class AuthSessionServiceTests: XCTestCase {
var iterator = stream.makeAsyncIterator() var iterator = stream.makeAsyncIterator()
let firstValue = await iterator.next() let firstValue = await iterator.next()
XCTAssertEqual( XCTAssertFalse(
firstValue, try XCTUnwrap(firstValue),
false,
"It should yielded the first value correctly" "It should yielded the first value correctly"
) )
} }

View file

@ -185,10 +185,10 @@ public struct SelectionToolbarItem<Option: Hashable & CaseIterable>: View {
DropdownMenuView( DropdownMenuView(
options: options, options: options,
selection: selection, selection: selection,
itemLabel: itemLabel, itemLabel: itemLabel
label: { ) {
AnyView(style.makeLabel(helpText: helpText ?? style.defaultHelpText)) let helpText = helpText ?? style.defaultHelpText
} return AnyView(style.makeLabel(helpText: helpText))
) }
} }
} }

View file

@ -78,14 +78,16 @@ public struct TagListView: View {
#Preview("Regular Tags") { #Preview("Regular Tags") {
TagListView( TagListView(
tags: ["swift", "ios", "macos"], tags: ["swift", "ios", "macos"],
helpText: { "Select \($0)" } helpText: { "Select \($0)" },
) { _ in } action: { _ in }
)
} }
#Preview("Remove Tags") { #Preview("Remove Tags") {
TagListView( TagListView(
tags: ["swift", "ios", "macos"], tags: ["swift", "ios", "macos"],
style: .remove, style: .remove,
helpText: { "Remove \($0)" } helpText: { "Remove \($0)" },
) { _ in } action: { _ in }
)
} }

View file

@ -1,6 +1,8 @@
import Testing import Testing
@testable import FoundationExtensions @testable import FoundationExtensions
// swiftlint:disable file_length type_body_length
@Suite("ArrayContains Tests") @Suite("ArrayContains Tests")
struct ArrayContainsTests { struct ArrayContainsTests {
@ -535,3 +537,5 @@ struct ArrayContainsTests {
) )
} }
} }
// swiftlint:enable file_length type_body_length

View file

@ -90,7 +90,7 @@ struct StringSlugTests {
// Then // Then
#expect( #expect(
result == "", result.isEmpty,
"It should handle empty string" "It should handle empty string"
) )
} }
@ -105,7 +105,7 @@ struct StringSlugTests {
// Then // Then
#expect( #expect(
result == "", result.isEmpty,
"It should handle string with only whitespace" "It should handle string with only whitespace"
) )
} }

View file

@ -2,6 +2,8 @@ import Foundation
import Testing import Testing
@testable import FoundationExtensions @testable import FoundationExtensions
// swiftlint:disable file_length type_body_length
@Suite("StringWeblog Tests") @Suite("StringWeblog Tests")
struct StringWeblogTests { struct StringWeblogTests {
@ -496,3 +498,5 @@ struct StringWeblogTests {
) )
} }
} }
// swiftlint:enable file_length type_body_length

View file

@ -41,6 +41,7 @@ struct NowEnvironment {
) )
} }
// swiftlint:disable function_body_length
init( init(
networkServiceFactory: NowNetworkServiceFactoryProtocol, networkServiceFactory: NowNetworkServiceFactoryProtocol,
persistenceServiceFactory: NowPersistenceServiceFactoryProtocol, persistenceServiceFactory: NowPersistenceServiceFactoryProtocol,
@ -113,4 +114,5 @@ struct NowEnvironment {
) )
} }
} }
// swiftlint:enable function_body_length
} }

View file

@ -12,12 +12,12 @@
count: Int, count: Int,
in container: ModelContainer in container: ModelContainer
) { ) {
for i in 0..<count { for index in 0..<count {
let now = Now( let now = Now(
listed: true, listed: true,
markdown: "Foobar \(i)", markdown: "Foobar \(index)",
submitted: true, submitted: true,
timestamp: 123_123 * Double(i), timestamp: 123_123 * Double(index),
address: "otaviocc" address: "otaviocc"
) )

View file

@ -17,8 +17,15 @@
} }
} }
func fetchNowPage(for address: String) async throws {} func fetchNowPage(
func updateNowPage(address: String, content: String, listed: Bool) async throws {} for address: String
) async throws {}
func updateNowPage(
address: String,
content: String,
listed: Bool
) async throws {}
} }
// MARK: - Public // MARK: - Public

View file

@ -37,7 +37,12 @@
// MARK: - Public // MARK: - Public
func fetchNowPage() async throws {} func fetchNowPage() async throws {}
func updateNowPage(address: String, content: String, isListed: Bool) async throws {}
func updateNowPage(
address: String,
content: String,
isListed: Bool
) async throws {}
} }
// MARK: - Public // MARK: - Public

View file

@ -3,5 +3,6 @@ import XCTest
final class NowTests: XCTestCase { final class NowTests: XCTestCase {
// swiftlint:disable:next empty_xctest_method
func testExample() throws {} func testExample() throws {}
} }

View file

@ -5,16 +5,4 @@ public struct CreateOrUpdatePasteRequest: Encodable, Sendable {
let title: String let title: String
let content: String let content: String
let listed: Bool let listed: Bool
// MARK: - Lifecycle
init(
title: String,
content: String,
listed: Bool
) {
self.title = title
self.content = content
self.listed = listed
}
} }

View file

@ -5,16 +5,4 @@ public struct CreatePURLRequest: Encodable, Sendable {
let address: String let address: String
let name: String let name: String
let url: String let url: String
// MARK: - Lifecycle
init(
address: String,
name: String,
url: String
) {
self.address = address
self.name = name
self.url = url
}
} }

View file

@ -4,14 +4,4 @@ public struct UpdateNowPageRequest: Encodable, Sendable {
let content: String let content: String
let listed: Int let listed: Int
// MARK: - Lifecycle
init(
content: String,
listed: Int
) {
self.content = content
self.listed = listed
}
} }

View file

@ -4,14 +4,4 @@ public struct UpdateWebpageRequest: Encodable, Sendable {
let content: String let content: String
let publish: Bool let publish: Bool
// MARK: - Lifecycle
init(
content: String,
publish: Bool
) {
self.content = content
self.publish = publish
}
} }

View file

@ -5,12 +5,4 @@ public struct UploadPictureRequest: Encodable, Sendable {
// MARK: - Properties // MARK: - Properties
let pic: String let pic: String
// MARK: - Lifecycle
init(
pic: String
) {
self.pic = pic
}
} }

View file

@ -40,22 +40,22 @@ struct AuthRequestFactoryTests {
) )
#expect( #expect(
queryItems.contains(where: { $0.name == "client_id" }), queryItems.contains { $0.name == "client_id" },
"It should include client_id parameter" "It should include client_id parameter"
) )
#expect( #expect(
queryItems.contains(where: { $0.name == "scope" && $0.value == "everything" }), queryItems.contains { $0.name == "scope" && $0.value == "everything" },
"It should include scope parameter with 'everything' value" "It should include scope parameter with 'everything' value"
) )
#expect( #expect(
queryItems.contains(where: { $0.name == "response_type" && $0.value == "code" }), queryItems.contains { $0.name == "response_type" && $0.value == "code" },
"It should include response_type parameter with 'code' value" "It should include response_type parameter with 'code' value"
) )
#expect( #expect(
queryItems.contains(where: { $0.name == "redirect_uri" }), queryItems.contains { $0.name == "redirect_uri" },
"It should include redirect_uri parameter" "It should include redirect_uri parameter"
) )
} }
@ -82,27 +82,27 @@ struct AuthRequestFactoryTests {
let queryItems = request.queryItems let queryItems = request.queryItems
#expect( #expect(
queryItems.contains(where: { $0.name == "client_id" }), queryItems.contains { $0.name == "client_id" },
"It should include client_id query parameter" "It should include client_id query parameter"
) )
#expect( #expect(
queryItems.contains(where: { $0.name == "client_secret" }), queryItems.contains { $0.name == "client_secret" },
"It should include client_secret query parameter" "It should include client_secret query parameter"
) )
#expect( #expect(
queryItems.contains(where: { $0.name == "redirect_uri" }), queryItems.contains { $0.name == "redirect_uri" },
"It should include redirect_uri query parameter" "It should include redirect_uri query parameter"
) )
#expect( #expect(
queryItems.contains(where: { $0.name == "code" && $0.value == authCode }), queryItems.contains { $0.name == "code" && $0.value == authCode },
"It should include code query parameter with provided auth code" "It should include code query parameter with provided auth code"
) )
#expect( #expect(
queryItems.contains(where: { $0.name == "scope" && $0.value == "everything" }), queryItems.contains { $0.name == "scope" && $0.value == "everything" },
"It should include scope query parameter" "It should include scope query parameter"
) )
} }

View file

@ -232,7 +232,7 @@ struct PicsRequestFactoryTests {
} }
@Test("It should create picture edit request with empty tags array") @Test("It should create picture edit request with empty tags array")
func makeEditPictureRequest_withOnlyWithEmptyTagsArray_createsRequest() { func makeEditPictureRequest_withOnlyWithEmptyTagsArray_createsRequest() throws {
// Given // Given
let address = "dave" let address = "dave"
let pictureID = "pic-456" let pictureID = "pic-456"
@ -256,23 +256,26 @@ struct PicsRequestFactoryTests {
"It should use PUT method" "It should use PUT method"
) )
let body = try #require(request.body)
let bodyTags = try #require(body.tags)
#expect( #expect(
request.body?.tags == "", bodyTags.isEmpty == true,
"It should include tags in request body" "It should include tags in request body"
) )
#expect( #expect(
request.body?.caption == nil, body.caption == nil,
"It should have nil caption when not provided" "It should have nil caption when not provided"
) )
#expect( #expect(
request.body?.alt == nil, body.alt == nil,
"It should have nil alt text when not provided" "It should have nil alt text when not provided"
) )
#expect( #expect(
request.body?.isHidden == nil, body.isHidden == nil,
"It should have nil isHidden when not provided" "It should have nil isHidden when not provided"
) )
} }

View file

@ -43,6 +43,7 @@ struct PURLsEnvironment {
) )
} }
// swiftlint:disable function_body_length
init( init(
networkServiceFactory: PURLsNetworkServiceFactoryProtocol, networkServiceFactory: PURLsNetworkServiceFactoryProtocol,
persistenceServiceFactory: PURLsPersistenceServiceFactoryProtocol, persistenceServiceFactory: PURLsPersistenceServiceFactoryProtocol,
@ -123,4 +124,5 @@ struct PURLsEnvironment {
) )
} }
} }
// swiftlint:enable function_body_length
} }

View file

@ -9,8 +9,14 @@
private final class FakeClipboardService: ClipboardServiceProtocol { private final class FakeClipboardService: ClipboardServiceProtocol {
func copy(_ string: String) {} func copy(
func copy(_ data: Data, type: String) {} _ string: String
) {}
func copy(
_ data: Data,
type: String
) {}
} }
// MARK: - Public // MARK: - Public

View file

@ -13,10 +13,10 @@
count: Int, count: Int,
in container: ModelContainer in container: ModelContainer
) { ) {
for i in 0..<count { for index in 0..<count {
let purl = PURL( let purl = PURL(
name: "purl\(i)", name: "purl\(index)",
url: URL(string: "http://subdomain\(i).otavio.lol")!, url: URL(string: "http://subdomain\(index).otavio.lol")!,
address: "otaviocc" address: "otaviocc"
) )

View file

@ -17,9 +17,20 @@
} }
} }
func fetchPURLs(for address: String) {} func fetchPURLs(
func addPURL(address: String, name: String, url: String) {} for address: String
func deletePURL(address: String, name: String) async throws {} ) {}
func addPURL(
address: String,
name: String,
url: String
) {}
func deletePURL(
address: String,
name: String
) async throws {}
} }
// MARK: - Public // MARK: - Public

View file

@ -37,8 +37,17 @@
// MARK: - Public // MARK: - Public
func fetchPURLs() {} func fetchPURLs() {}
func addPURL(address: String, name: String, url: String) {}
func deletePURL(address: String, name: String) async throws {} func addPURL(
address: String,
name: String,
url: String
) {}
func deletePURL(
address: String,
name: String
) async throws {}
} }
// MARK: - Public // MARK: - Public

View file

@ -3,5 +3,6 @@ import XCTest
final class PURLsTests: XCTestCase { final class PURLsTests: XCTestCase {
// swiftlint:disable:next empty_xctest_method
func testExample() throws {} func testExample() throws {}
} }

View file

@ -43,6 +43,7 @@ struct PastebinEnvironment {
) )
} }
// swiftlint:disable function_body_length
init( init(
networkServiceFactory: PastebinNetworkServiceFactoryProtocol, networkServiceFactory: PastebinNetworkServiceFactoryProtocol,
persistenceServiceFactory: PastebinPersistenceServiceFactoryProtocol, persistenceServiceFactory: PastebinPersistenceServiceFactoryProtocol,
@ -123,4 +124,5 @@ struct PastebinEnvironment {
) )
} }
} }
// swiftlint:enable function_body_length
} }

View file

@ -10,7 +10,11 @@
private final class FakeClipboardService: ClipboardServiceProtocol { private final class FakeClipboardService: ClipboardServiceProtocol {
func copy(_ string: String) {} func copy(_ string: String) {}
func copy(_ data: Data, type: String) {}
func copy(
_ data: Data,
type: String
) {}
} }
// MARK: - Public // MARK: - Public

View file

@ -12,13 +12,13 @@
count: Int, count: Int,
in container: ModelContainer in container: ModelContainer
) { ) {
for i in 0..<count { for index in 0..<count {
let paste = Paste( let paste = Paste(
title: "paste\(i).md", title: "paste\(index).md",
content: "hello, world!", content: "hello, world!",
timestamp: 123_123_123, timestamp: 123_123_123,
address: "otaviocc", address: "otaviocc",
listed: i % 2 == 0 listed: index % 2 == 0
) )
container.mainContext.insert( container.mainContext.insert(

View file

@ -17,9 +17,21 @@
} }
} }
func fetchPastes(for address: String) async throws {} func fetchPastes(
func createOrUpdatePaste(address: String, title: String, content: String, listed: Bool) async throws {} for address: String
func deletePaste(address: String, title: String) async throws {} ) async throws {}
func createOrUpdatePaste(
address: String,
title: String,
content: String,
listed: Bool
) async throws {}
func deletePaste(
address: String,
title: String
) async throws {}
} }
// MARK: - Public // MARK: - Public

View file

@ -37,8 +37,18 @@
// MARK: - Public // MARK: - Public
func fetchPastes() async throws {} func fetchPastes() async throws {}
func createOrUpdatePaste(address: String, title: String, content: String, isListed: Bool) async throws {}
func deletePaste(address: String, title: String) async throws {} func createOrUpdatePaste(
address: String,
title: String,
content: String,
isListed: Bool
) async throws {}
func deletePaste(
address: String,
title: String
) async throws {}
} }
// MARK: - Public // MARK: - Public

View file

@ -3,5 +3,6 @@ import XCTest
final class PastebinTests: XCTestCase { final class PastebinTests: XCTestCase {
// swiftlint:disable:next empty_xctest_method
func testExample() throws {} func testExample() throws {}
} }

View file

@ -42,6 +42,7 @@ struct PicsEnvironment {
) )
} }
// swiftlint:disable function_body_length
init( init(
networkServiceFactory: PicsNetworkServiceFactoryProtocol, networkServiceFactory: PicsNetworkServiceFactoryProtocol,
persistenceServiceFactory: PicsPersistenceServiceFactoryProtocol, persistenceServiceFactory: PicsPersistenceServiceFactoryProtocol,
@ -119,4 +120,5 @@ struct PicsEnvironment {
) )
} }
} }
// swiftlint:enable function_body_length
} }

View file

@ -9,8 +9,14 @@
private final class FakeClipboardService: ClipboardServiceProtocol { private final class FakeClipboardService: ClipboardServiceProtocol {
func copy(_ string: String) {} func copy(
func copy(_ data: Data, type: String) {} _ string: String
) {}
func copy(
_ data: Data,
type: String
) {}
} }
// MARK: - Public // MARK: - Public

View file

@ -12,8 +12,15 @@
// MARK: - Public // MARK: - Public
func fetchPictures(for address: String) async throws -> [PictureResponse] { [] } func fetchPictures(
func deletePicture(address: String, pictureID: String) async throws {} for address: String
) async throws -> [PictureResponse] { [] }
func deletePicture(
address: String,
pictureID: String
) async throws {}
func updatePicture( func updatePicture(
address: String, address: String,
pictureID: String, pictureID: String,
@ -21,6 +28,7 @@
alt: String, alt: String,
tags: [String] tags: [String]
) async throws {} ) async throws {}
func uploadPicture( func uploadPicture(
address: String, address: String,
data: Data, data: Data,

View file

@ -39,7 +39,12 @@
// MARK: - Public // MARK: - Public
func fetchPictures() async throws {} func fetchPictures() async throws {}
func deletePicture(address: String, pictureID: String) async throws {}
func deletePicture(
address: String,
pictureID: String
) async throws {}
func updatePicture( func updatePicture(
address: String, address: String,
pictureID: String, pictureID: String,
@ -47,6 +52,7 @@
alt: String, alt: String,
tags: [String] tags: [String]
) async throws {} ) async throws {}
func uploadPicture( func uploadPicture(
address: String, address: String,
data: Data, data: Data,

View file

@ -13,9 +13,9 @@
count: Int = 3, count: Int = 3,
in container: ModelContainer in container: ModelContainer
) { ) {
for i in 0..<count { for index in 0..<count {
SomePicture.makePicture( SomePicture.makePicture(
created: Double(i * i), created: Double(index * index),
in: container in: container
) )
} }

View file

@ -98,12 +98,13 @@ struct EditPictureView: View {
if !viewModel.suggestedTags.isEmpty { if !viewModel.suggestedTags.isEmpty {
TagListView( TagListView(
tags: viewModel.suggestedTags, tags: viewModel.suggestedTags,
helpText: { "Add existing tag '\($0)'" } helpText: { "Add existing tag '\($0)'" },
) { tag in action: { tag in
withAnimation { withAnimation {
viewModel.addTag(tag) viewModel.addTag(tag)
}
} }
} )
} }
} }
@ -113,12 +114,13 @@ struct EditPictureView: View {
TagListView( TagListView(
tags: viewModel.tags, tags: viewModel.tags,
style: .remove, style: .remove,
helpText: { "Remove tag '\($0)'" } helpText: { "Remove tag '\($0)'" },
) { tag in action: { tag in
withAnimation { withAnimation {
viewModel.removeTag(tag) viewModel.removeTag(tag)
}
} }
} )
} }
} }

View file

@ -21,7 +21,7 @@ struct PicturesListView: View {
_pictures = .init(viewModel.fetchDescriptor()) _pictures = .init(viewModel.fetchDescriptor())
} }
// MARK; - Public // MARK: - Public
var body: some View { var body: some View {
Group { Group {

View file

@ -209,12 +209,13 @@ struct UploadView: View {
if !viewModel.suggestedTags.isEmpty { if !viewModel.suggestedTags.isEmpty {
TagListView( TagListView(
tags: viewModel.suggestedTags, tags: viewModel.suggestedTags,
helpText: { "Add existing tag '\($0)'" } helpText: { "Add existing tag '\($0)'" },
) { tag in action: { tag in
withAnimation(.easeInOut(duration: 0.2)) { withAnimation(.easeInOut(duration: 0.2)) {
viewModel.addTag(tag) viewModel.addTag(tag)
}
} }
} )
} }
} }
@ -224,12 +225,13 @@ struct UploadView: View {
TagListView( TagListView(
tags: viewModel.tags, tags: viewModel.tags,
style: .remove, style: .remove,
helpText: { "Remove tag '\($0)'" } helpText: { "Remove tag '\($0)'" },
) { tag in action: { tag in
withAnimation(.easeInOut(duration: 0.2)) { withAnimation(.easeInOut(duration: 0.2)) {
viewModel.removeTag(tag) viewModel.removeTag(tag)
}
} }
} )
} }
} }

View file

@ -42,6 +42,7 @@ struct StatusEnvironment {
) )
} }
// swiftlint:disable function_body_length
init( init(
repositoryFactory: StatusRepositoryFactoryProtocol, repositoryFactory: StatusRepositoryFactoryProtocol,
networkServiceFactory: StatusNetworkServiceFactoryProtocol, networkServiceFactory: StatusNetworkServiceFactoryProtocol,
@ -120,4 +121,5 @@ struct StatusEnvironment {
) )
} }
} }
// swiftlint:enable function_body_length
} }

View file

@ -9,8 +9,14 @@
private final class FakeClipboardService: ClipboardServiceProtocol { private final class FakeClipboardService: ClipboardServiceProtocol {
func copy(_ string: String) {} func copy(
func copy(_ data: Data, type: String) {} _ string: String
) {}
func copy(
_ data: Data,
type: String
) {}
} }
// MARK: - Public // MARK: - Public

View file

@ -12,11 +12,11 @@
count: Int, count: Int,
in container: ModelContainer in container: ModelContainer
) { ) {
for i in 0..<count { for index in 0..<count {
let status = Status( let status = Status(
username: "user\(i)", username: "user\(index)",
statusID: "(i)", statusID: "(index)",
timestamp: Double(i), timestamp: Double(index),
icon: "🤣", icon: "🤣",
content: "Nulla purus urna, bibendum nec purus." content: "Nulla purus urna, bibendum nec purus."
) )

View file

@ -45,9 +45,10 @@ public struct StatusApp: View {
ToolbarItemGroup { ToolbarItemGroup {
SelectionToolbarItem( SelectionToolbarItem(
options: StatusListFilter.allCases, options: StatusListFilter.allCases,
selection: $filter, selection: $filter
itemLabel: { $0.localizedTitle } ) { label in
) label.localizedTitle
}
.selectionToolbarItemStyle(FilterSelectionToolbarItemStyle()) .selectionToolbarItemStyle(FilterSelectionToolbarItemStyle())
} }

View file

@ -43,7 +43,13 @@ struct ClipboardService: ClipboardServiceProtocol {
service.copy(string) service.copy(string)
} }
func copy(_ data: Data, type: String) { func copy(
service.copy(data, type: type) _ data: Data,
type: String
) {
service.copy(
data,
type: type
)
} }
} }

View file

@ -17,7 +17,10 @@
) )
} }
func copy(_ data: Data, type: String) { func copy(
_ data: Data,
type: String
) {
NSPasteboard NSPasteboard
.general .general
.clearContents() .clearContents()

View file

@ -42,6 +42,7 @@ struct WeblogEnvironment {
) )
} }
// swiftlint:disable function_body_length
init( init(
networkServiceFactory: WeblogNetworkServiceFactoryProtocol, networkServiceFactory: WeblogNetworkServiceFactoryProtocol,
persistenceServiceFactory: WeblogPersistenceServiceFactoryProtocol, persistenceServiceFactory: WeblogPersistenceServiceFactoryProtocol,
@ -119,4 +120,5 @@ struct WeblogEnvironment {
) )
} }
} }
// swiftlint:enable function_body_length
} }

View file

@ -9,8 +9,14 @@
private final class FakeClipboardService: ClipboardServiceProtocol { private final class FakeClipboardService: ClipboardServiceProtocol {
func copy(_ string: String) {} func copy(
func copy(_ data: Data, type: String) {} _ string: String
) {}
func copy(
_ data: Data,
type: String
) {}
} }
// MARK: - Public // MARK: - Public

View file

@ -27,14 +27,14 @@
} }
static func makeEntryResponses(count: Int = 5) -> [EntryResponse] { static func makeEntryResponses(count: Int = 5) -> [EntryResponse] {
(0..<count).map { i in (0..<count).map { index in
makeEntryResponse( makeEntryResponse(
id: "entry-\(i)", id: "entry-\(index)",
location: "test-entry-\(i)", location: "test-entry-\(index)",
date: Double(1_700_000_000 + (i * 86400)), date: Double(1_700_000_000 + (index * 86400)),
status: i % 3 == 0 ? "draft" : "published", status: index % 3 == 0 ? "draft" : "published",
title: "Test Entry \(i)", title: "Test Entry \(index)",
body: "Content for test entry \(i)" body: "Content for test entry \(index)"
) )
} }
} }

View file

@ -12,14 +12,14 @@
count: Int, count: Int,
in container: ModelContainer in container: ModelContainer
) { ) {
for i in 0..<count { for index in 0..<count {
let entry = WeblogEntry( let entry = WeblogEntry(
id: "entry-\(i)", id: "entry-\(index)",
title: "Test Entry \(i)", title: "Test Entry \(index)",
body: "This is the body for test entry \(i). Lorem ipsum dolor sit amet, consectetur adipiscing elit.", body: "This is the body for test entry \(index). Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
date: Double(1_700_000_000 + (i * 86400)), date: Double(1_700_000_000 + (index * 86400)),
status: i % 3 == 0 ? "draft" : "published", status: index % 3 == 0 ? "draft" : "published",
location: "test-entry-\(i)", location: "test-entry-\(index)",
address: "otaviocc" address: "otaviocc"
) )

View file

@ -12,11 +12,16 @@
// MARK: - Public // MARK: - Public
func fetchWeblogEntry(for address: String, entryID: String) async throws -> EntryResponse { func fetchWeblogEntry(
for address: String,
entryID: String
) async throws -> EntryResponse {
EntryResponseMother.makeEntryResponse() EntryResponseMother.makeEntryResponse()
} }
func fetchWeblogEntries(for address: String) async throws -> [EntryResponse] { func fetchWeblogEntries(
for address: String
) async throws -> [EntryResponse] {
EntryResponseMother.makeEntryResponses(count: 2) EntryResponseMother.makeEntryResponses(count: 2)
} }
@ -41,7 +46,10 @@
EntryResponseMother.makeEntryResponse() EntryResponseMother.makeEntryResponse()
} }
func deleteWeblogEntry(address: String, entryID: String) async throws {} func deleteWeblogEntry(
address: String,
entryID: String
) async throws {}
} }
// MARK: - Public // MARK: - Public

View file

@ -39,7 +39,11 @@
// MARK: - Public // MARK: - Public
func fetchEntries() async throws {} func fetchEntries() async throws {}
func deleteEntry(address: String, entryID: String) async throws {}
func deleteEntry(
address: String,
entryID: String
) async throws {}
func createOrUpdateEntry( func createOrUpdateEntry(
address: String, address: String,

View file

@ -53,6 +53,7 @@ struct EditorView: View {
} }
@ViewBuilder @ViewBuilder
// swiftlint:disable:next function_body_length
private func makeSidebarView() -> some View { private func makeSidebarView() -> some View {
Grid(alignment: .leading, horizontalSpacing: 16, verticalSpacing: 16) { Grid(alignment: .leading, horizontalSpacing: 16, verticalSpacing: 16) {
GridRow(alignment: .firstTextBaseline) { GridRow(alignment: .firstTextBaseline) {
@ -146,12 +147,13 @@ struct EditorView: View {
if !viewModel.suggestedTags.isEmpty { if !viewModel.suggestedTags.isEmpty {
TagListView( TagListView(
tags: viewModel.suggestedTags, tags: viewModel.suggestedTags,
helpText: { "Add existing tag '\($0)'" } helpText: { "Add existing tag '\($0)'" },
) { tag in action: { tag in
withAnimation { withAnimation {
viewModel.addTag(tag) viewModel.addTag(tag)
}
} }
} )
} }
} }
@ -170,12 +172,13 @@ struct EditorView: View {
TagListView( TagListView(
tags: viewModel.tags, tags: viewModel.tags,
style: .remove, style: .remove,
helpText: { "Remove tag '\($0)'" } helpText: { "Remove tag '\($0)'" },
) { tag in action: { tag in
withAnimation { withAnimation {
viewModel.removeTag(tag) viewModel.removeTag(tag)
}
} }
} )
} }
} }

View file

@ -21,7 +21,7 @@ struct WeblogEntriesListView: View {
_entries = .init(viewModel.fetchDescriptor()) _entries = .init(viewModel.fetchDescriptor())
} }
// MARK; - Public // MARK: - Public
var body: some View { var body: some View {
Group { Group {

View file

@ -40,6 +40,7 @@ struct WebpageEnvironment {
) )
} }
// swiftlint:disable function_body_length
init( init(
networkServiceFactory: WebpageNetworkServiceFactoryProtocol, networkServiceFactory: WebpageNetworkServiceFactoryProtocol,
persistenceServiceFactory: WebpagePersistenceServiceFactoryProtocol, persistenceServiceFactory: WebpagePersistenceServiceFactoryProtocol,
@ -112,4 +113,5 @@ struct WebpageEnvironment {
) )
} }
} }
// swiftlint:enable function_body_length
} }

View file

@ -12,11 +12,11 @@
count: Int, count: Int,
in container: ModelContainer in container: ModelContainer
) { ) {
for i in 0..<count { for index in 0..<count {
let page = Webpage( let page = Webpage(
address: "otaviocc", address: "otaviocc",
markdown: "Foobar \(i)", markdown: "Foobar \(index)",
timestamp: 123_123 * Double(i) timestamp: 123_123 * Double(index)
) )
container.mainContext.insert( container.mainContext.insert(

View file

@ -17,8 +17,14 @@
} }
} }
func fetchWebpage(for address: String) async throws {} func fetchWebpage(
func updateWebpage(address: String, content: String) async throws {} for address: String
) async throws {}
func updateWebpage(
address: String,
content: String
) async throws {}
} }
// MARK: - Public // MARK: - Public

View file

@ -37,7 +37,11 @@
// MARK: - Public // MARK: - Public
func fetchWebpage() async throws {} func fetchWebpage() async throws {}
func updateWebpage(address: String, content: String) async throws {}
func updateWebpage(
address: String,
content: String
) async throws {}
} }
// MARK: - Public // MARK: - Public

View file

@ -3,5 +3,6 @@ import XCTest
final class WebpageTests: XCTestCase { final class WebpageTests: XCTestCase {
// swiftlint:disable:next empty_xctest_method
func testExample() throws {} func testExample() throws {}
} }