Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,14 @@
9014D5B10826A433A7412BAC /* PreviewPanelOverridesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D155E6889AE670D2B6097CC /* PreviewPanelOverridesTests.swift */; };
91A5DFA622284D666F20D46E /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29032ACE1D4AEB3E5D1BB51A /* MainViewController.swift */; };
93DDB4FCE61B74F35663301D /* LiveUpdatesTestScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A41CEBEA796C5A3179D691E /* LiveUpdatesTestScreen.swift */; };
9550770C73B171616E3E23C4 /* NestedButtonTapTestViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82AB63780A1D0B8ACBD0CA96 /* NestedButtonTapTestViewController.swift */; };
9E261E1E35994E3D7A652291 /* ScreenTrackingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2A09744E23C92FDCD9F5439 /* ScreenTrackingTests.swift */; };
9F3986444C6D6D3BCB8ECEA3 /* RichText.swift in Sources */ = {isa = PBXBuildFile; fileRef = 820BE574DFF5E022220890CD /* RichText.swift */; };
A375768DD5B98ECEC51B04C9 /* FlagViewTrackingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E79C1C23CB5619203CAD28A3 /* FlagViewTrackingTests.swift */; };
B1A5A931BBDFC3438E935012 /* OfflineBehaviorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 682E3BF881B8093C2D5EF2CE /* OfflineBehaviorTests.swift */; };
C04EF55982CA837E62CC0669 /* ContentfulFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = F383552F49942336F9725020 /* ContentfulFetcher.swift */; };
C23B6242DA30D283D76C1B3E /* Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5DDE184BA14D36BAC6A5936 /* Config.swift */; };
C4757AF18FE122AC807B27E7 /* NestedButtonTapTestScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B353BE52F9AEFA1D6213FB4 /* NestedButtonTapTestScreen.swift */; };
CE4104C9F52CE1A874B6D802 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = F082E0D07BC7C35FCC9901C4 /* App.swift */; };
D28A6D01EBA9877DD9174BCB /* LiveUpdatesTestViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B60B2B8362A57886F415EE4 /* LiveUpdatesTestViewController.swift */; };
D93DA9530B2B7E95815DB931 /* NestedContentEntryUIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C832ADCDDFECC716F4B04C /* NestedContentEntryUIView.swift */; };
Expand Down Expand Up @@ -87,6 +89,7 @@
0D155E6889AE670D2B6097CC /* PreviewPanelOverridesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewPanelOverridesTests.swift; sourceTree = "<group>"; };
102D5A209AF6AC8B69E4BD24 /* AnalyticsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsTests.swift; sourceTree = "<group>"; };
1324154A5A5CE58F32970813 /* ExtendedViewTrackingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtendedViewTrackingTests.swift; sourceTree = "<group>"; };
1B353BE52F9AEFA1D6213FB4 /* NestedButtonTapTestScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NestedButtonTapTestScreen.swift; sourceTree = "<group>"; };
29032ACE1D4AEB3E5D1BB51A /* MainViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = "<group>"; };
2E855C4FA2D4C37DB188398F /* OptimizationAppSwiftUI.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = OptimizationAppSwiftUI.app; sourceTree = BUILT_PRODUCTS_DIR; };
354A498C8D7BC1CB974D2454 /* NestedContentEntryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NestedContentEntryView.swift; sourceTree = "<group>"; };
Expand All @@ -100,6 +103,7 @@
7A41CEBEA796C5A3179D691E /* LiveUpdatesTestScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveUpdatesTestScreen.swift; sourceTree = "<group>"; };
7FDD088B0ACEC1271B3C5509 /* OptimizedEntryUIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OptimizedEntryUIView.swift; sourceTree = "<group>"; };
820BE574DFF5E022220890CD /* RichText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RichText.swift; sourceTree = "<group>"; };
82AB63780A1D0B8ACBD0CA96 /* NestedButtonTapTestViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NestedButtonTapTestViewController.swift; sourceTree = "<group>"; };
89CE81FC20185EE7C2FF7BEF /* EventStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventStore.swift; sourceTree = "<group>"; };
8C4808FBBA0BFA7C40F52FC7 /* NavigationTestViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationTestViewController.swift; sourceTree = "<group>"; };
8FB2B5375330439D2F81AACF /* OptimizationAppUITestsSwiftUI.xctest */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = OptimizationAppUITestsSwiftUI.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
Expand Down Expand Up @@ -233,6 +237,7 @@
6B60B2B8362A57886F415EE4 /* LiveUpdatesTestViewController.swift */,
29032ACE1D4AEB3E5D1BB51A /* MainViewController.swift */,
8C4808FBBA0BFA7C40F52FC7 /* NavigationTestViewController.swift */,
82AB63780A1D0B8ACBD0CA96 /* NestedButtonTapTestViewController.swift */,
);
path = Screens;
sourceTree = "<group>";
Expand All @@ -252,6 +257,7 @@
7A41CEBEA796C5A3179D691E /* LiveUpdatesTestScreen.swift */,
569F34F81667A8807581B59F /* MainScreen.swift */,
EEDF68085F694A44D33D237A /* NavigationTestScreen.swift */,
1B353BE52F9AEFA1D6213FB4 /* NestedButtonTapTestScreen.swift */,
);
path = Screens;
sourceTree = "<group>";
Expand Down Expand Up @@ -437,6 +443,7 @@
8BE046C5FD5539712A40551C /* MainScreen.swift in Sources */,
7E0AE3A92E2EB13F275145C5 /* MockPreviewContentfulClient.swift in Sources */,
71EE02D789A2D2D922B4DC0A /* NavigationTestScreen.swift in Sources */,
C4757AF18FE122AC807B27E7 /* NestedButtonTapTestScreen.swift in Sources */,
F510376F8D6D3EFE7BB197E1 /* NestedContentEntryView.swift in Sources */,
9F3986444C6D6D3BCB8ECEA3 /* RichText.swift in Sources */,
);
Expand Down Expand Up @@ -476,6 +483,7 @@
91A5DFA622284D666F20D46E /* MainViewController.swift in Sources */,
3EC1F3129468B71C9DE3C486 /* MockPreviewContentfulClient.swift in Sources */,
7CE0E6C067281C01D8F0A85F /* NavigationTestViewController.swift in Sources */,
9550770C73B171616E3E23C4 /* NestedButtonTapTestViewController.swift in Sources */,
D93DA9530B2B7E95815DB931 /* NestedContentEntryUIView.swift in Sources */,
072516CD147CCCA5D7F0BE53 /* OptimizedEntryUIView.swift in Sources */,
802A2114E9A91D5F798272B2 /* RichText.swift in Sources */,
Expand Down
8 changes: 8 additions & 0 deletions implementations/ios-sdk/swiftui/Screens/MainScreen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ struct MainScreen: View {
@State private var entries: [[String: Any]] = []
@State private var showNavigationTest = false
@State private var showLiveUpdatesTest = false
@State private var showNestedButtonTapTest = false
@State private var flagSubscribed = false
@State private var flagCancellable: AnyCancellable?

Expand All @@ -27,6 +28,8 @@ struct MainScreen: View {
NavigationTestScreen(onClose: { showNavigationTest = false })
} else if showLiveUpdatesTest {
LiveUpdatesTestScreen(onClose: { showLiveUpdatesTest = false })
} else if showNestedButtonTapTest {
NestedButtonTapTestScreen(onClose: { showNestedButtonTapTest = false })
} else {
mainContent
}
Expand All @@ -44,6 +47,11 @@ struct MainScreen: View {
}
Button("Navigation Test") { showNavigationTest = true }
.accessibilityIdentifier("navigation-test-button")
// Opens NestedButtonTapTestScreen — the [NT-3829] regression
// scenario. This button is only the entry point; the actual
// test setup lives in that screen.
Button("Nested Button Tap Test") { showNestedButtonTapTest = true }
.accessibilityIdentifier("nested-button-tap-test-button")
Button("Live Updates Test") { showLiveUpdatesTest = true }
.accessibilityIdentifier("live-updates-test-button")
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import ContentfulOptimization
import SwiftUI

/// On-screen setup for the `[NT-3829]` regression test
/// (`TapTrackingTests.testTappingNestedButtonWithTrackTapsEnabled`): renders
/// a "Book" `Button` nested inside an `OptimizedEntry` with `trackTaps: true`
/// — the exact shape that made the button untappable before the fix.
struct NestedButtonTapTestScreen: View {
let onClose: () -> Void
@State private var bookTapCount = 0

private var entry: [String: Any] {
[
"sys": ["id": "nested-button-test-entry"],
"fields": ["text": "Nested button test entry"],
]
}

var body: some View {
VStack(spacing: 16) {
Button("Close") { onClose() }
.accessibilityIdentifier("close-nested-button-test-button")

Text("Book taps: \(bookTapCount)")
.accessibilityLabel("Book taps: \(bookTapCount)")
.accessibilityIdentifier("book-tap-count")

OptimizedEntry(
entry: entry,
// This is the exact setting the bug report was about: with
// trackTaps enabled, the SDK's own tap-tracking gesture used
// to swallow the "Book" button's tap below before it fired.
trackTaps: true,
accessibilityIdentifier: "nested-button-test-entry"
) { resolvedEntry in
let fields = resolvedEntry["fields"] as? [String: Any]
let text = fields?["text"] as? String ?? ""
VStack {
Text(text)
Button("Book") { bookTapCount += 1 }
.accessibilityIdentifier("book-button")
}
.padding()
}
}
.padding()
}
}
16 changes: 15 additions & 1 deletion implementations/ios-sdk/uikit/Screens/MainViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ final class MainViewController: UIViewController {
private let identifyButton = UIButton(type: .system)
private let resetButton = UIButton(type: .system)
private let navigationTestButton = UIButton(type: .system)
private let nestedButtonTapTestButton = UIButton(type: .system)
private let liveUpdatesTestButton = UIButton(type: .system)
private let simulateOfflineButton = UIButton(type: .system)
private let simulateOnlineButton = UIButton(type: .system)
Expand Down Expand Up @@ -96,6 +97,13 @@ final class MainViewController: UIViewController {
navigationTestButton.accessibilityIdentifier = "navigation-test-button"
navigationTestButton.addAction(UIAction { [weak self] _ in self?.openNavigationTest() }, for: .touchUpInside)

// Opens NestedButtonTapTestViewController — the [NT-3829] regression
// scenario. This button is only the entry point; the actual test
// setup lives in that view controller.
nestedButtonTapTestButton.setTitle("Nested Button Tap Test", for: .normal)
nestedButtonTapTestButton.accessibilityIdentifier = "nested-button-tap-test-button"
nestedButtonTapTestButton.addAction(UIAction { [weak self] _ in self?.openNestedButtonTapTest() }, for: .touchUpInside)

liveUpdatesTestButton.setTitle("Live Updates Test", for: .normal)
liveUpdatesTestButton.accessibilityIdentifier = "live-updates-test-button"
liveUpdatesTestButton.addAction(UIAction { [weak self] _ in self?.openLiveUpdatesTest() }, for: .touchUpInside)
Expand All @@ -121,7 +129,7 @@ final class MainViewController: UIViewController {
}

private func layout() {
let buttonRow = UIStackView(arrangedSubviews: [identifyButton, resetButton, navigationTestButton, liveUpdatesTestButton])
let buttonRow = UIStackView(arrangedSubviews: [identifyButton, resetButton, navigationTestButton, nestedButtonTapTestButton, liveUpdatesTestButton])
buttonRow.axis = .horizontal
buttonRow.distribution = .fillEqually
buttonRow.spacing = 8
Expand Down Expand Up @@ -226,6 +234,12 @@ final class MainViewController: UIViewController {
present(live, animated: false)
}

private func openNestedButtonTapTest() {
let test = NestedButtonTapTestViewController(client: client)
test.modalPresentationStyle = .fullScreen
present(test, animated: false)
}

// MARK: - Helpers

private func isNestedContent(_ entry: [String: Any]) -> Bool {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import ContentfulOptimization
import UIKit

/// On-screen setup for the `[NT-3829]` regression test
/// (`TapTrackingTests.testTappingNestedButtonWithTrackTapsEnabled`) on the
/// UIKit shell: renders a "Book" `UIButton` nested inside an
/// `OptimizedEntryUIView` with `trackTaps: true`, mirroring
/// `NestedButtonTapTestScreen` on the SwiftUI shell.
final class NestedButtonTapTestViewController: UIViewController {

private let client: OptimizationClient
private var bookTapCount = 0

private let bookTapCountLabel = UILabel()
private let bookButton = UIButton(type: .system)

init(client: OptimizationClient) {
self.client = client
super.init(nibName: nil, bundle: nil)
}

@available(*, unavailable)
required init?(coder: NSCoder) { fatalError() }

override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground

let closeButton = UIButton(type: .system)
closeButton.setTitle("Close", for: .normal)
closeButton.accessibilityIdentifier = "close-nested-button-test-button"
closeButton.addAction(UIAction { [weak self] _ in self?.dismiss(animated: false) }, for: .touchUpInside)

updateBookTapCountLabel()
bookTapCountLabel.accessibilityIdentifier = "book-tap-count"

bookButton.setTitle("Book", for: .normal)
bookButton.accessibilityIdentifier = "book-button"
bookButton.addAction(UIAction { [weak self] _ in self?.handleBookTap() }, for: .touchUpInside)

let entry: [String: Any] = [
"sys": ["id": "nested-button-test-entry"],
"fields": ["text": "Nested button test entry"],
]

let entryLabel = UILabel()
entryLabel.text = "Nested button test entry"

let entryContent = UIStackView(arrangedSubviews: [entryLabel, bookButton])
entryContent.axis = .vertical
entryContent.alignment = .leading
entryContent.spacing = 8
entryContent.translatesAutoresizingMaskIntoConstraints = false

let optimized = OptimizedEntryUIView(
client: client,
entry: entry,
scrollView: nil,
// Same setting as the SwiftUI test screen: enabled tap tracking
// on an entry that wraps another interactive control.
trackTaps: true,
accessibilityIdentifier: "nested-button-test-entry"
) { _ in entryContent }

let root = UIStackView(arrangedSubviews: [closeButton, bookTapCountLabel, optimized])
root.axis = .vertical
root.alignment = .leading
root.spacing = 16
root.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(root)

NSLayoutConstraint.activate([
root.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 16),
root.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 16),
root.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -16),
])
}

private func handleBookTap() {
bookTapCount += 1
updateBookTapCountLabel()
}

private func updateBookTapCountLabel() {
let text = "Book taps: \(bookTapCount)"
bookTapCountLabel.text = text
bookTapCountLabel.accessibilityLabel = text
}
}
26 changes: 26 additions & 0 deletions implementations/ios-sdk/uitests/Tests/TapTrackingTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,30 @@ final class TapTrackingTests: XCTestCase {
scrollViewId: "main-scroll-view", app: app)
XCTAssertTrue(eventElement.waitForExistence(timeout: ELEMENT_VISIBILITY_TIMEOUT))
}

// [NT-3829] regression test: a Button the app puts *inside* an
// OptimizedEntry (with trackTaps: true) must still fire its own action
// when tapped, instead of the SDK's tap-tracking swallowing the touch.
// See NestedButtonTapTestScreen.swift / NestedButtonTapTestViewController.swift
// for the on-screen setup this test drives.
func testTappingNestedButtonWithTrackTapsEnabled() {
// 1. Open the dedicated test screen from the main screen's button row.
let openButton = app.buttons["nested-button-tap-test-button"]
waitForElement(openButton)
openButton.tap()

// 2. Tap the "Book" button, which the test screen renders nested
// inside an OptimizedEntry with trackTaps: true — this is the
// exact shape of the original bug report.
let bookButton = app.buttons["book-button"]
waitForElement(bookButton)
bookButton.tap()

// 3. If the tap reached the button (bug fixed), its own counter
// increments to 1. If the SDK's tap tracking swallowed the touch
// (bug present), the counter would stay at 0 and this fails.
waitForTextEquals("book-tap-count", expected: "Book taps: 1", app: app)

app.buttons["close-nested-button-test-button"].tap()
}
}
Loading
Loading