-
Notifications
You must be signed in to change notification settings - Fork 48
Fix: Detail page auto-dismissal caused by nested navigation #79
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,11 +16,7 @@ struct FeedPage: BaseHomePageView { | |
| var selecedTab: TabId | ||
|
|
||
| var isSelected: Bool { | ||
| let selected = selecedTab == .feed | ||
| if selected && !state.hasLoadedOnce { | ||
| dispatch(FeedActions.FetchData.Start(autoLoad: true)) | ||
| } | ||
| return selected | ||
| selecedTab == .feed | ||
| } | ||
|
Comment on lines
18
to
20
|
||
|
|
||
| var body: some View { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -56,10 +56,9 @@ struct FeedDetailPage: StateView, KeyboardReadable, InstanceIdentifiable { | |
| return state.showProgressView | ||
| || (!isContentEmpty && !self.rendered) | ||
| } | ||
|
|
||
| var body: some View { | ||
| contentView | ||
| .navigatable() | ||
| .sheet(isPresented: $showingSafari) { | ||
| if let url = safariURL { | ||
| SafariView(url: url) | ||
|
|
@@ -70,7 +69,6 @@ struct FeedDetailPage: StateView, KeyboardReadable, InstanceIdentifiable { | |
| @ViewBuilder | ||
| private var contentView: some View { | ||
| VStack (spacing: 0) { | ||
| // TODO: improve here | ||
| VStack(spacing: 0) { | ||
| AuthorInfoView(initData: initData, data: state.model.headerInfo) | ||
| if !isContentEmpty { | ||
|
|
@@ -89,7 +87,6 @@ struct FeedDetailPage: StateView, KeyboardReadable, InstanceIdentifiable { | |
| withAnimation { | ||
| hideTitleViews = !(scrollY <= -100) | ||
| } | ||
| // replyIsFocused = false | ||
| } | ||
| .onTapGesture { | ||
| replyIsFocused = false | ||
|
|
@@ -111,16 +108,15 @@ struct FeedDetailPage: StateView, KeyboardReadable, InstanceIdentifiable { | |
| dispatch(FeedDetailActions.FetchData.Start(id: instanceId, feedId: initData?.id, autoLoad: !state.hasLoadedOnce)) | ||
| } | ||
| .onDisappear { | ||
| if !isPresented { | ||
| log("onPageClosed----->") | ||
| let data: FeedInfo.Item? | ||
| if state.model.headerInfo != nil { | ||
| data = state.model.headerInfo?.toFeedItemInfo() | ||
| } else { | ||
| data = initData | ||
| } | ||
| dispatch(MyRecentActions.RecordAction(data: data)) | ||
| guard !isPresented else { return } | ||
| log("onPageClosed----->") | ||
| let data: FeedInfo.Item? | ||
| if state.model.headerInfo != nil { | ||
| data = state.model.headerInfo?.toFeedItemInfo() | ||
| } else { | ||
| data = initData | ||
| } | ||
| dispatch(MyRecentActions.RecordAction(data: data)) | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -150,18 +146,14 @@ struct FeedDetailPage: StateView, KeyboardReadable, InstanceIdentifiable { | |
| } | ||
| .background(Color.lightGray) | ||
| .clipShape(RoundedRectangle(cornerRadius: 12)) | ||
| // if isKeyboardVisiable { | ||
| // actionBar | ||
| // .transition(.opacity) | ||
| // } | ||
| } | ||
| .padding(.bottom, isKeyboardVisiable ? 0 : topSafeAreaInset().bottom * 0.9) | ||
| .padding(.top, 10) | ||
| .padding(.horizontal, 10) | ||
| .background(Color.itemBg) | ||
| } | ||
| } | ||
|
|
||
| @ViewBuilder | ||
| private var actionBar: some View { | ||
| HStack (spacing: 10) { | ||
|
|
@@ -182,7 +174,7 @@ struct FeedDetailPage: StateView, KeyboardReadable, InstanceIdentifiable { | |
| .padding(.vertical, 10) | ||
| .padding(.horizontal, 16) | ||
| } | ||
|
|
||
| @ViewBuilder | ||
| private var navBar: some View { | ||
| NavbarHostView(paddingH: 0) { | ||
|
|
@@ -197,10 +189,7 @@ struct FeedDetailPage: StateView, KeyboardReadable, InstanceIdentifiable { | |
| .foregroundColor(.tintColor) | ||
| } | ||
| Group { | ||
| // FIXME: use real value | ||
| NavigationLink(destination: UserDetailPage(userId: initData?.id ?? .empty)) { | ||
| AvatarView(url: state.model.headerInfo?.avatar ?? .empty, size: 32) | ||
| } | ||
| AvatarView(url: state.model.headerInfo?.avatar ?? .empty, size: 32) | ||
|
||
| VStack(alignment: .leading) { | ||
| Text("话题") | ||
| .font(.headline) | ||
|
|
@@ -271,7 +260,7 @@ struct FeedDetailPage: StateView, KeyboardReadable, InstanceIdentifiable { | |
| } | ||
| .visualBlur() | ||
| } | ||
|
|
||
| @ViewBuilder | ||
| private var replayListView: some View { | ||
| LazyVStack(spacing: 0) { | ||
|
|
@@ -280,12 +269,5 @@ struct FeedDetailPage: StateView, KeyboardReadable, InstanceIdentifiable { | |
| } | ||
| } | ||
| } | ||
|
|
||
| } | ||
|
|
||
| //struct NewsDetailPage_Previews: PreviewProvider { | ||
| // static var previews: some View { | ||
| // FeedDetailPage(id: .empty) | ||
| // .environmentObject(Store.shared) | ||
| // } | ||
| //} | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,7 +18,6 @@ struct MyFavoritePage: StateView { | |
|
|
||
| var body: some View { | ||
| contentView | ||
| .navigatable() | ||
| } | ||
|
|
||
| @ViewBuilder | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -45,9 +45,7 @@ struct RecentItemView<Data: FeedItemProtocol>: View { | |||||||||||
| var body: some View { | ||||||||||||
| VStack(spacing: 0) { | ||||||||||||
| HStack(alignment: .top) { | ||||||||||||
| NavigationLink(destination: UserDetailPage(userId: data.userName.safe)) { | ||||||||||||
| AvatarView(url: data.avatar) | ||||||||||||
| } | ||||||||||||
| AvatarView(url: data.avatar) | ||||||||||||
| VStack(alignment: .leading, spacing: 5) { | ||||||||||||
| Text(data.userName.safe) | ||||||||||||
| .lineLimit(1) | ||||||||||||
|
|
@@ -57,7 +55,9 @@ struct RecentItemView<Data: FeedItemProtocol>: View { | |||||||||||
| .foregroundColor(Color.tintColor) | ||||||||||||
| } | ||||||||||||
| Spacer() | ||||||||||||
| NodeView(id: data.nodeId.safe, name: data.nodeName.safe) | ||||||||||||
| Text(data.nodeName.safe) | ||||||||||||
| .font(.footnote) | ||||||||||||
| .foregroundColor(.gray) | ||||||||||||
|
||||||||||||
| .foregroundColor(.gray) | |
| .foregroundColor(.gray) | |
| .to { | |
| TagDetailPage(nodeName: data.nodeName.safe) | |
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -16,7 +16,7 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||
| // In UI tests it is usually best to stop immediately when a failure occurs. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| continueAfterFailure = false | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| override func tearDownWithError() throws { | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -32,6 +32,47 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||
| // Use XCTAssert and related functions to verify your tests produce the correct results. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| func testFeedNavigationStaysOpen() throws { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| let app = XCUIApplication() | ||||||||||||||||||||||||||||||||||||||||||||||||||
| app.launch() | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| // Wait for feed to load | ||||||||||||||||||||||||||||||||||||||||||||||||||
| sleep(6) | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| // Find a feed item by looking for text that says "评论" (comment count) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| let commentLabels = app.staticTexts.matching(NSPredicate(format: "label CONTAINS '评论'")) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| guard commentLabels.count > 0 else { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| XCTFail("No feed items found (no comment labels)") | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| // Tap on the first comment label's parent area | ||||||||||||||||||||||||||||||||||||||||||||||||||
| let firstCommentLabel = commentLabels.element(boundBy: 0) | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| // Wait for feed to load | |
| sleep(6) | |
| // Find a feed item by looking for text that says "评论" (comment count) | |
| let commentLabels = app.staticTexts.matching(NSPredicate(format: "label CONTAINS '评论'")) | |
| guard commentLabels.count > 0 else { | |
| XCTFail("No feed items found (no comment labels)") | |
| return | |
| } | |
| // Tap on the first comment label's parent area | |
| let firstCommentLabel = commentLabels.element(boundBy: 0) | |
| // Wait for feed to load by waiting for the first comment label to appear | |
| let commentLabels = app.staticTexts.matching(NSPredicate(format: "label CONTAINS '评论'")) | |
| let firstCommentLabel = commentLabels.element(boundBy: 0) | |
| XCTAssertTrue(firstCommentLabel.waitForExistence(timeout: 10), "Feed should load and show at least one comment label") | |
| // Find a feed item by looking for text that says "评论" (comment count) | |
| guard commentLabels.count > 0 else { | |
| XCTFail("No feed items found (no comment labels)") | |
| return | |
| } | |
| // Tap on the first comment label's parent area |
Outdated
Copilot
AI
Dec 1, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replace sleep(3) with waitForExistence(timeout:) on the expected detail page element to make the test more reliable and faster.
| // Wait for navigation animation | |
| sleep(3) | |
| // Verify we're on detail page - look for "话题" text or "发表回复" placeholder | |
| let topicLabel = app.staticTexts["话题"] | |
| let replyPlaceholder = app.textViews.firstMatch | |
| // Wait for navigation animation by waiting for detail page element to appear | |
| let topicLabel = app.staticTexts["话题"] | |
| let replyPlaceholder = app.textViews.firstMatch | |
| let appeared = topicLabel.waitForExistence(timeout: 5) || replyPlaceholder.waitForExistence(timeout: 5) | |
| XCTAssertTrue(appeared, "Detail page did not appear after tapping comment label") | |
| // Verify we're on detail page - look for "话题" text or "发表回复" placeholder |
Outdated
Copilot
AI
Dec 1, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replace sleep(3) with a proper assertion mechanism. If testing that the page stays open, consider using XCTWaiter to wait a specific time then assert, or verify the element remains accessible.
| sleep(3) | |
| let expectation = XCTestExpectation(description: "Wait 3 seconds to verify detail page stays open") | |
| let result = XCTWaiter.wait(for: [expectation], timeout: 3.0) |
Outdated
Copilot
AI
Dec 1, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove this sleep(10) for visual inspection. UI tests should be automated and not require manual inspection during execution. If manual testing is needed, do it separately from automated tests.
| sleep(10) | |
| // Removed sleep(10) for automation; UI tests should not require manual inspection. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replacing
NodeViewwith plainTextremoves the navigation functionality to TagDetailPage. Users can no longer tap on node names to navigate to tag details from feed items. Consider restoring this functionality with proper NavigationStack-compatible navigation.