summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFuwn <[email protected]>2026-02-23 11:18:18 -0800
committerFuwn <[email protected]>2026-02-23 13:33:42 -0800
commit6a1fd75d265c7e85c538cca07cbaea7e7da8fec7 (patch)
treeebee4775b0a133b558429efc8edba11b445028eb
parentfix: guard post detail url actions against invalid urls (diff)
downloadsora-testing-6a1fd75d265c7e85c538cca07cbaea7e7da8fec7.tar.xz
sora-testing-6a1fd75d265c7e85c538cca07cbaea7e7da8fec7.zip
feat: add explicit accessibility metadata for grid cells
-rw-r--r--Sora/Views/FavoritesView.swift23
-rw-r--r--Sora/Views/Post/Grid/PostGridView.swift23
-rw-r--r--SoraTests/ViewDerivedDataTests.swift111
3 files changed, 147 insertions, 10 deletions
diff --git a/Sora/Views/FavoritesView.swift b/Sora/Views/FavoritesView.swift
index a0357da..1217441 100644
--- a/Sora/Views/FavoritesView.swift
+++ b/Sora/Views/FavoritesView.swift
@@ -642,6 +642,29 @@ struct FavoritesView: View { // swiftlint:disable:this type_body_length
Label("Delete", systemImage: "trash")
}
}
+ .accessibilityElement(children: .ignore)
+ .accessibilityLabel(Text(favoriteAccessibilityLabel(for: favorite)))
+ .accessibilityValue(Text(favoriteAccessibilityValue(for: favorite)))
+ .accessibilityHint(Text("Opens post details."))
+ }
+
+ private func favoriteAccessibilityLabel(for favorite: SettingsFavoritePost) -> String {
+ let tagSummary = favorite.tags
+ .prefix(3)
+ .map { tag in
+ tag.replacingOccurrences(of: "_", with: " ")
+ }
+ .joined(separator: ", ")
+
+ if tagSummary.isEmpty {
+ return "Favorite post \(favorite.postId)"
+ }
+
+ return tagSummary
+ }
+
+ private func favoriteAccessibilityValue(for favorite: SettingsFavoritePost) -> String {
+ "Rating \(favorite.rating.rawValue.uppercased()). Provider \(favorite.provider.rawValue)."
}
}
diff --git a/Sora/Views/Post/Grid/PostGridView.swift b/Sora/Views/Post/Grid/PostGridView.swift
index 7a7b5fa..37c207e 100644
--- a/Sora/Views/Post/Grid/PostGridView.swift
+++ b/Sora/Views/Post/Grid/PostGridView.swift
@@ -514,6 +514,10 @@ struct PostGridView: View { // swiftlint:disable:this type_body_length
Label("Add to Collection", systemImage: "folder.badge.plus")
}
}
+ .accessibilityElement(children: .ignore)
+ .accessibilityLabel(Text(postAccessibilityLabel(for: post)))
+ .accessibilityValue(Text(postAccessibilityValue(for: post)))
+ .accessibilityHint(Text("Opens post details."))
}
private func isFavoritedInFolder(post: BooruPost, folderId: UUID) -> Bool {
@@ -536,6 +540,25 @@ struct PostGridView: View { // swiftlint:disable:this type_body_length
}
}
+ private func postAccessibilityLabel(for post: BooruPost) -> String {
+ let tagSummary = post.tags
+ .prefix(3)
+ .map { tag in
+ tag.replacingOccurrences(of: "_", with: " ")
+ }
+ .joined(separator: ", ")
+
+ if tagSummary.isEmpty {
+ return "Post \(post.id)"
+ }
+
+ return tagSummary
+ }
+
+ private func postAccessibilityValue(for post: BooruPost) -> String {
+ "Rating \(post.rating.rawValue.uppercased()). Score \(post.score)."
+ }
+
// MARK: - Local Search Methods
private func performLocalSearch() async {
let inputTags = localSearchText.components(separatedBy: .whitespaces).filter { component in
diff --git a/SoraTests/ViewDerivedDataTests.swift b/SoraTests/ViewDerivedDataTests.swift
index c20e9d1..7ec8f9f 100644
--- a/SoraTests/ViewDerivedDataTests.swift
+++ b/SoraTests/ViewDerivedDataTests.swift
@@ -245,6 +245,86 @@ final class ViewDerivedDataTests: XCTestCase { // swiftlint:disable:this type_b
)
}
+ func testPostGridCellsProvideExplicitAccessibilityMetadata() throws {
+ let source = try loadSource(at: "Sora/Views/Post/Grid/PostGridView.swift")
+ let functionSource = try extractFunction(
+ named: "private func waterfallGridContent(post: BooruPost) -> some View",
+ from: source
+ )
+ let normalizedSource = strippingCommentsAndStrings(from: functionSource)
+ let accessibilityLabelCount = tokenCount(
+ matching: #"\.accessibilityLabel\s*\("#,
+ in: normalizedSource
+ )
+ let accessibilityHintCount = tokenCount(
+ matching: #"\.accessibilityHint\s*\("#,
+ in: normalizedSource
+ )
+ let accessibilityValueCount = tokenCount(
+ matching: #"\.accessibilityValue\s*\("#,
+ in: normalizedSource
+ )
+
+ // swiftlint:disable:next prefer_nimble
+ XCTAssertGreaterThan(
+ accessibilityLabelCount,
+ 0,
+ "Post grid cells should expose an explicit accessibility label."
+ )
+ // swiftlint:disable:next prefer_nimble
+ XCTAssertGreaterThan(
+ accessibilityHintCount,
+ 0,
+ "Post grid cells should expose an explicit accessibility hint."
+ )
+ // swiftlint:disable:next prefer_nimble
+ XCTAssertGreaterThan(
+ accessibilityValueCount,
+ 0,
+ "Post grid cells should expose structured accessibility metadata."
+ )
+ }
+
+ func testFavoriteCellsProvideExplicitAccessibilityMetadata() throws {
+ let source = try loadSource(at: "Sora/Views/FavoritesView.swift")
+ let functionSource = try extractFunction(
+ named: "private func favoriteGridContent(",
+ from: source
+ )
+ let normalizedSource = strippingCommentsAndStrings(from: functionSource)
+ let accessibilityLabelCount = tokenCount(
+ matching: #"\.accessibilityLabel\s*\("#,
+ in: normalizedSource
+ )
+ let accessibilityHintCount = tokenCount(
+ matching: #"\.accessibilityHint\s*\("#,
+ in: normalizedSource
+ )
+ let accessibilityValueCount = tokenCount(
+ matching: #"\.accessibilityValue\s*\("#,
+ in: normalizedSource
+ )
+
+ // swiftlint:disable:next prefer_nimble
+ XCTAssertGreaterThan(
+ accessibilityLabelCount,
+ 0,
+ "Favorite grid cells should expose an explicit accessibility label."
+ )
+ // swiftlint:disable:next prefer_nimble
+ XCTAssertGreaterThan(
+ accessibilityHintCount,
+ 0,
+ "Favorite grid cells should expose an explicit accessibility hint."
+ )
+ // swiftlint:disable:next prefer_nimble
+ XCTAssertGreaterThan(
+ accessibilityValueCount,
+ 0,
+ "Favorite grid cells should expose structured accessibility metadata."
+ )
+ }
+
func testListViewsAvoidComparatorRandomShuffleSorting() throws {
let listViewSource = try loadSource(at: "Sora/Views/Generic/GenericListView.swift")
let favoritesViewSource = try loadSource(at: "Sora/Views/FavoritesView.swift")
@@ -357,11 +437,14 @@ final class ViewDerivedDataTests: XCTestCase { // swiftlint:disable:this type_b
from: source
)
let danbooruCaseStart = try XCTUnwrap(
- urlBuilderSection.range(of: "case .danbooru:")?.lowerBound)
+ urlBuilderSection.range(of: "case .danbooru:")?.lowerBound
+ )
let danbooruCaseEnd = try XCTUnwrap(
urlBuilderSection.range(
- of: "case .moebooru:", range: danbooruCaseStart..<urlBuilderSection.endIndex)?
- .lowerBound
+ of: "case .moebooru:",
+ range: danbooruCaseStart..<urlBuilderSection.endIndex
+ )?
+ .lowerBound
)
let danbooruSection = String(urlBuilderSection[danbooruCaseStart..<danbooruCaseEnd])
let danbooruLimitQueryCount = tokenCount(
@@ -384,11 +467,14 @@ final class ViewDerivedDataTests: XCTestCase { // swiftlint:disable:this type_b
from: source
)
let moebooruCaseStart = try XCTUnwrap(
- urlBuilderSection.range(of: "case .moebooru:")?.lowerBound)
+ urlBuilderSection.range(of: "case .moebooru:")?.lowerBound
+ )
let moebooruCaseEnd = try XCTUnwrap(
urlBuilderSection.range(
- of: "case .gelbooru:", range: moebooruCaseStart..<urlBuilderSection.endIndex)?
- .lowerBound
+ of: "case .gelbooru:",
+ range: moebooruCaseStart..<urlBuilderSection.endIndex
+ )?
+ .lowerBound
)
let moebooruSection = String(urlBuilderSection[moebooruCaseStart..<moebooruCaseEnd])
let moebooruTagHelperSection = try extractFunction(
@@ -526,11 +612,14 @@ final class ViewDerivedDataTests: XCTestCase { // swiftlint:disable:this type_b
from: source
)
let danbooruCaseStart = try XCTUnwrap(
- urlBuilderSection.range(of: "case .danbooru:")?.lowerBound)
+ urlBuilderSection.range(of: "case .danbooru:")?.lowerBound
+ )
let danbooruCaseEnd = try XCTUnwrap(
urlBuilderSection.range(
- of: "case .moebooru:", range: danbooruCaseStart..<urlBuilderSection.endIndex)?
- .lowerBound
+ of: "case .moebooru:",
+ range: danbooruCaseStart..<urlBuilderSection.endIndex
+ )?
+ .lowerBound
)
let danbooruSection = String(urlBuilderSection[danbooruCaseStart..<danbooruCaseEnd])
let pageTokenFunctionSection = try extractFunction(
@@ -719,7 +808,9 @@ final class ViewDerivedDataTests: XCTestCase { // swiftlint:disable:this type_b
let source = try loadSource(at: "Sora/Data/Danbooru/DanbooruPost.swift")
let optionalFileURLCount = tokenCount(matching: #"let\s+fileURL:\s+String\?"#, in: source)
let optionalLargeFileURLCount = tokenCount(
- matching: #"let\s+largeFileURL:\s+String\?"#, in: source)
+ matching: #"let\s+largeFileURL:\s+String\?"#,
+ in: source
+ )
let optionalPreviewURLCount = tokenCount(
matching: #"let\s+previewFileURL:\s+String\?"#,
in: source