mirror of
https://github.com/discourse/discourse.git
synced 2026-08-09 21:45:25 +08:00
805 lines
25 KiB
Ruby
Vendored
805 lines
25 KiB
Ruby
Vendored
# frozen_string_literal: true
|
|
|
|
module PageObjects
|
|
module Pages
|
|
class NestedView < PageObjects::Pages::Base
|
|
def visit_nested(topic, query: nil)
|
|
url = "/t/#{topic.slug}/#{topic.id}"
|
|
url += "?#{query}" if query
|
|
page.visit(url)
|
|
self
|
|
end
|
|
|
|
def visit_nested_context(topic, post_number:, context: nil)
|
|
url = "/t/#{topic.slug}/#{topic.id}/#{post_number}"
|
|
url += "?context=#{context}" if context
|
|
page.visit(url)
|
|
self
|
|
end
|
|
|
|
def reload_with_pending_pagination
|
|
# Capybara's refresh waits for AJAX to settle, but pagination is intentionally held open.
|
|
page.driver.with_playwright_page(&:reload)
|
|
self
|
|
end
|
|
|
|
# In-app navigation via DiscourseURL.routeTo — exercises the same
|
|
# routing code path a notification or in-page link click would,
|
|
# rather than doing a full page reload like visit_nested_context.
|
|
# Use this when the test needs to verify behavior that depends on
|
|
# the existing nested controller/components staying mounted across
|
|
# the transition.
|
|
def route_to(path)
|
|
page.execute_script(%(require("discourse/lib/url").default.routeTo(#{path.to_json});))
|
|
self
|
|
end
|
|
|
|
def route_to_nested_context(topic, post_number:, query: nil)
|
|
path = "/t/#{topic.slug}/#{topic.id}/#{post_number}"
|
|
path += "?#{query}" if query
|
|
route_to(path)
|
|
end
|
|
|
|
def route_to_topic_post(topic, post_number:)
|
|
route_to("/t/#{topic.slug}/#{topic.id}/#{post_number}")
|
|
end
|
|
|
|
# ── Root view assertions ──────────────────────────────────────
|
|
|
|
def has_nested_view?
|
|
has_css?(".nested-view")
|
|
end
|
|
|
|
def has_no_nested_view?
|
|
has_no_css?(".nested-view")
|
|
end
|
|
|
|
def has_root_post?(post)
|
|
has_css?(".nested-view__roots [data-post-number='#{post.post_number}']")
|
|
end
|
|
|
|
def has_no_root_post?(post)
|
|
has_no_css?(".nested-view__roots [data-post-number='#{post.post_number}']")
|
|
end
|
|
|
|
# ── Context view assertions ───────────────────────────────────
|
|
|
|
def has_context_view?
|
|
has_css?(".nested-context-view")
|
|
end
|
|
|
|
def has_no_context_view?
|
|
has_no_css?(".nested-context-view")
|
|
end
|
|
|
|
def has_context_banner?
|
|
has_css?(".nested-context-view__banner", text: I18n.t("js.nested_replies.context.banner"))
|
|
end
|
|
|
|
def has_no_context_banner?
|
|
has_no_css?(".nested-context-view__banner")
|
|
end
|
|
|
|
def has_view_full_thread_link?
|
|
has_css?(".nested-context-view__full-thread")
|
|
end
|
|
|
|
def has_view_parent_context_link?
|
|
has_css?(".nested-context-view__parent-context")
|
|
end
|
|
|
|
def has_no_view_parent_context_link?
|
|
has_no_css?(".nested-context-view__parent-context")
|
|
end
|
|
|
|
# ── Post assertions ───────────────────────────────────────────
|
|
|
|
def has_post_at_depth?(post, depth:)
|
|
has_css?(".nested-post.--depth-#{depth} [data-post-number='#{post.post_number}']")
|
|
end
|
|
|
|
def has_post?(post)
|
|
has_css?("[data-post-number='#{post.post_number}']")
|
|
end
|
|
|
|
def has_post_text?(text)
|
|
has_css?(".nested-post__article", text: text)
|
|
end
|
|
|
|
def has_no_post?(post)
|
|
has_no_css?("[data-post-number='#{post.post_number}']")
|
|
end
|
|
|
|
def has_continue_thread_for?(post)
|
|
within(post_container(post)) { has_css?(".nested-post__continue-link") }
|
|
end
|
|
|
|
def has_no_continue_thread_for?(post)
|
|
within(post_container(post)) { has_no_css?(".nested-post__continue-link") }
|
|
end
|
|
|
|
def has_highlighted_post?(post)
|
|
has_css?(".nested-post--highlighted [data-post-number='#{post.post_number}']", wait: 5)
|
|
end
|
|
|
|
def has_reply_button_for?(post)
|
|
has_css?("[data-post-number='#{post.post_number}'] .post-action-menu__reply")
|
|
end
|
|
|
|
def has_no_reply_button_for?(post)
|
|
has_no_css?("[data-post-number='#{post.post_number}'] .post-action-menu__reply")
|
|
end
|
|
|
|
def has_like_button_for?(post)
|
|
has_css?("[data-post-number='#{post.post_number}'] .post-action-menu__like")
|
|
end
|
|
|
|
def click_like_on_post(post)
|
|
find("[data-post-number='#{post.post_number}'] .post-action-menu__like").click
|
|
end
|
|
|
|
def has_replies_toggle_for?(post)
|
|
has_css?("[data-post-number='#{post.post_number}'] .nested-post__expand-replies")
|
|
end
|
|
|
|
def has_no_replies_toggle_for?(post)
|
|
has_no_css?("[data-post-number='#{post.post_number}'] .nested-post__expand-replies")
|
|
end
|
|
|
|
def has_load_more_children_for?(post)
|
|
has_css?(
|
|
wrapper_selector(
|
|
post,
|
|
"> .nested-post__main > .nested-post-children .nested-post-children__load-more",
|
|
),
|
|
)
|
|
end
|
|
|
|
def has_no_show_replies_button_for?(post)
|
|
has_no_css?("[data-post-number='#{post.post_number}'] .post-action-menu__show-replies")
|
|
end
|
|
|
|
def has_mobile_focus?
|
|
has_css?(".nested-view__mobile-focus")
|
|
end
|
|
|
|
def has_no_mobile_focus?
|
|
has_no_css?(".nested-view__mobile-focus")
|
|
end
|
|
|
|
def has_mobile_ancestor?(post)
|
|
has_css?(
|
|
"[data-test-nested-mobile-ancestor='#{post.post_number}']",
|
|
text: post.user.username,
|
|
)
|
|
end
|
|
|
|
def has_no_mobile_ancestor?(post)
|
|
has_no_css?("[data-test-nested-mobile-ancestor='#{post.post_number}']")
|
|
end
|
|
|
|
def has_no_mobile_ancestor_user_card_trigger?(post)
|
|
has_no_css?("[data-test-nested-mobile-ancestor='#{post.post_number}'] [data-user-card]")
|
|
end
|
|
|
|
def post_viewport_top(post)
|
|
page.evaluate_script(<<~JS)
|
|
document
|
|
.querySelector("[data-post-number='#{post.post_number}']")
|
|
.closest(".nested-post")
|
|
.getBoundingClientRect()
|
|
.top
|
|
JS
|
|
end
|
|
|
|
def mobile_ancestor_viewport_top(post)
|
|
page.evaluate_script(<<~JS)
|
|
document
|
|
.querySelector("[data-test-nested-mobile-ancestor='#{post.post_number}']")
|
|
.getBoundingClientRect()
|
|
.top
|
|
JS
|
|
end
|
|
|
|
def has_depth_line_for?(post)
|
|
has_css?(depth_line_selector(post))
|
|
end
|
|
|
|
def has_no_depth_line_for?(post)
|
|
has_no_css?(depth_line_selector(post))
|
|
end
|
|
|
|
def has_leaf_depth_line_for?(post)
|
|
has_css?(leaf_depth_line_selector(post))
|
|
end
|
|
|
|
def has_no_leaf_depth_line_for?(post)
|
|
has_no_css?(leaf_depth_line_selector(post))
|
|
end
|
|
|
|
def has_children_visible_for?(post)
|
|
has_css?(wrapper_selector(post, "> .nested-post__main > .nested-post-children"))
|
|
end
|
|
|
|
def has_no_children_visible_for?(post)
|
|
has_no_css?(wrapper_selector(post, "> .nested-post__main > .nested-post-children"))
|
|
end
|
|
|
|
def has_collapsed_bar_for?(post)
|
|
has_css?(wrapper_selector(post, "> .nested-post__main > .nested-post__collapsed-bar"))
|
|
end
|
|
|
|
def has_no_collapsed_bar_for?(post)
|
|
has_no_css?(wrapper_selector(post, "> .nested-post__main > .nested-post__collapsed-bar"))
|
|
end
|
|
|
|
def has_post_content_visible_for?(post)
|
|
has_css?(wrapper_selector(post, "> .nested-post__main > .nested-post__article"))
|
|
end
|
|
|
|
def has_no_post_content_visible_for?(post)
|
|
has_no_css?(wrapper_selector(post, "> .nested-post__main > .nested-post__article"))
|
|
end
|
|
|
|
def has_sort_active?(sort)
|
|
has_css?(".nested-sort-selector__trigger", text: I18n.t("js.nested_replies.sort.#{sort}"))
|
|
end
|
|
|
|
def has_root_post_count?(count)
|
|
has_css?(".nested-view__roots > .nested-post", count: count)
|
|
end
|
|
|
|
def current_scroll_position
|
|
page.evaluate_script("window.scrollY")
|
|
end
|
|
|
|
def with_root_pagination_paused(topic, &block)
|
|
pattern = %r{/n/#{Regexp.escape(topic.slug)}/#{topic.id}\.json\?.*\bpage=1(?:&|$)}
|
|
PageObjects::CDP.new.with_paused_request(pattern, &block)
|
|
end
|
|
|
|
def disable_cloaking
|
|
page.execute_script(
|
|
'require("discourse/modifiers/post-stream-viewport-tracker").disableCloaking()',
|
|
)
|
|
self
|
|
end
|
|
|
|
def scroll_to_bottom
|
|
page.execute_script("window.scrollTo(0, document.body.scrollHeight)")
|
|
self
|
|
end
|
|
|
|
def user_scroll_by(distance:)
|
|
start_position = current_scroll_position
|
|
|
|
page.driver.with_playwright_page do |playwright_page|
|
|
viewport = playwright_page.viewport_size
|
|
playwright_page.mouse.move(viewport[:width] * 0.65, viewport[:height] * 0.5)
|
|
playwright_page.mouse.wheel(0, distance)
|
|
end
|
|
|
|
page.evaluate_async_script(<<~JS, start_position)
|
|
const [startPosition, done] = arguments;
|
|
let previousPosition;
|
|
let stableFrames = 0;
|
|
|
|
const finishWhenStable = () => {
|
|
const currentPosition = window.scrollY;
|
|
const hasMoved = currentPosition !== startPosition;
|
|
stableFrames =
|
|
hasMoved && currentPosition === previousPosition
|
|
? stableFrames + 1
|
|
: 0;
|
|
previousPosition = currentPosition;
|
|
|
|
if (stableFrames === 2) {
|
|
done(currentPosition);
|
|
} else {
|
|
requestAnimationFrame(finishWhenStable);
|
|
}
|
|
};
|
|
|
|
requestAnimationFrame(finishWhenStable);
|
|
JS
|
|
end
|
|
|
|
def centered_root_post_number
|
|
page.evaluate_script(<<~JS)
|
|
(() => {
|
|
const roots = document.querySelector(".nested-view__roots");
|
|
const rootsRect = roots.getBoundingClientRect();
|
|
const element = document.elementFromPoint(
|
|
rootsRect.left + rootsRect.width / 2,
|
|
window.innerHeight / 2
|
|
);
|
|
const root = element?.closest(".nested-post");
|
|
const article = root?.querySelector(
|
|
":scope > .nested-post__main > [data-post-number]"
|
|
);
|
|
|
|
return Number(article?.dataset.postNumber);
|
|
})()
|
|
JS
|
|
end
|
|
|
|
def scroll_to_post(post)
|
|
page.execute_script(<<~JS)
|
|
document.querySelector("[data-post-number='#{post.post_number}']").scrollIntoView();
|
|
JS
|
|
self
|
|
end
|
|
|
|
def has_op_post?
|
|
has_css?(".nested-view__op")
|
|
end
|
|
|
|
def has_no_reply_button_on_op?
|
|
has_no_css?(".nested-view__op .post-action-menu__reply")
|
|
end
|
|
|
|
def has_topic_title_editor?
|
|
has_css?(".edit-topic-title")
|
|
end
|
|
|
|
def has_no_topic_title_editor?
|
|
has_no_css?(".edit-topic-title")
|
|
end
|
|
|
|
def has_topic_title_in_site_header?(topic)
|
|
has_css?(
|
|
"header.d-header .header-title .topic-link[data-topic-id='#{topic.id}']",
|
|
text: topic.title,
|
|
)
|
|
end
|
|
|
|
def has_no_topic_title_in_site_header?(topic)
|
|
has_no_css?(
|
|
"header.d-header .header-title .topic-link[data-topic-id='#{topic.id}']",
|
|
text: topic.title,
|
|
)
|
|
end
|
|
|
|
def has_topic_map?
|
|
has_css?(".nested-view__op > .nested-view__topic-map .topic-map__contents")
|
|
end
|
|
|
|
def has_no_top_replies_button?
|
|
has_no_css?(".nested-view__op > .nested-view__topic-map .top-replies")
|
|
end
|
|
|
|
def has_topic_actions_above_controls?
|
|
has_css?(".nested-view__topic-actions + .nested-view__controls")
|
|
end
|
|
|
|
def has_activity_log_link?
|
|
has_css?(".nested-view__activity-link", wait: 10)
|
|
end
|
|
|
|
def has_no_activity_log_link?
|
|
has_no_css?(".nested-view__activity-link")
|
|
end
|
|
|
|
def has_share_topic_action?
|
|
has_css?(".nested-view__topic-actions #topic-footer-button-share-and-invite")
|
|
end
|
|
|
|
def has_bookmark_topic_action?
|
|
has_css?(".nested-view__topic-actions .bookmark-menu__trigger")
|
|
end
|
|
|
|
def has_flag_topic_action?
|
|
has_css?(".nested-view__topic-actions #topic-footer-button-flag")
|
|
end
|
|
|
|
def has_no_topic_action_reply_button?
|
|
has_no_css?(".nested-view__topic-actions .create")
|
|
end
|
|
|
|
def has_floating_reply_button?
|
|
has_css?(".nested-view__floating-actions:not(.--hidden) .nested-view__floating-reply")
|
|
end
|
|
|
|
def has_no_floating_reply_button?
|
|
has_no_css?(".nested-view__floating-actions:not(.--hidden) .nested-view__floating-reply")
|
|
end
|
|
|
|
def has_floating_actions?
|
|
has_css?(".nested-view__floating-actions:not(.--hidden)")
|
|
end
|
|
|
|
def has_no_floating_actions?
|
|
has_no_css?(".nested-view__floating-actions:not(.--hidden)")
|
|
end
|
|
|
|
def has_notification_button?
|
|
has_css?(".nested-view__floating-actions .topic-notifications-button")
|
|
end
|
|
|
|
def has_no_notification_button?
|
|
has_no_css?(".nested-view__floating-actions .topic-notifications-button")
|
|
end
|
|
|
|
def has_admin_menu_button?
|
|
has_css?(".nested-view__floating-actions .toggle-admin-menu")
|
|
end
|
|
|
|
def has_no_admin_menu_button?
|
|
has_no_css?(".nested-view__floating-actions .toggle-admin-menu")
|
|
end
|
|
|
|
# ── Actions ───────────────────────────────────────────────────
|
|
|
|
def click_edit_topic
|
|
find(".nested-view__title .fancy-title").click
|
|
self
|
|
end
|
|
|
|
def open_activity_log
|
|
find(".nested-view__activity-link").click
|
|
self
|
|
end
|
|
|
|
def click_cancel_edit_topic
|
|
find(".edit-topic-title .cancel-edit").click
|
|
self
|
|
end
|
|
|
|
def click_save_edit_topic
|
|
find(".edit-topic-title .submit-edit").click
|
|
self
|
|
end
|
|
|
|
def fill_in_topic_title(title)
|
|
find(".edit-topic-title input#edit-title").fill_in(with: title)
|
|
self
|
|
end
|
|
|
|
def click_post_edit_button(post)
|
|
within("[data-post-number='#{post.post_number}']") do
|
|
find(".show-more-actions").click if has_css?(".show-more-actions", wait: 2)
|
|
find("button.edit").click
|
|
end
|
|
self
|
|
end
|
|
|
|
def click_view_full_thread
|
|
find(".nested-context-view__full-thread").click
|
|
self
|
|
end
|
|
|
|
def click_view_parent_context
|
|
find(".nested-context-view__parent-context").click
|
|
self
|
|
end
|
|
|
|
def click_reply_on_post(post)
|
|
find("[data-post-number='#{post.post_number}'] .post-action-menu__reply").click
|
|
self
|
|
end
|
|
|
|
def click_replies_toggle(post)
|
|
find("[data-post-number='#{post.post_number}'] .nested-post__expand-replies").click
|
|
self
|
|
end
|
|
|
|
def click_load_more_children(post)
|
|
find(
|
|
wrapper_selector(
|
|
post,
|
|
"> .nested-post__main > .nested-post-children .nested-post-children__load-more",
|
|
),
|
|
).click
|
|
self
|
|
end
|
|
|
|
def trigger_replies_toggle(post)
|
|
page.evaluate_script(<<~JS)
|
|
(() => {
|
|
const button = document.querySelector(
|
|
"[data-post-number='#{post.post_number}'] .nested-post__expand-replies"
|
|
);
|
|
document
|
|
.querySelectorAll(".nested-view__roots .nested-post [data-post-number]")
|
|
.forEach((article) =>
|
|
(article.closest(".nested-post") || article).getBoundingClientRect()
|
|
);
|
|
const scrollY = window.scrollY;
|
|
button.click();
|
|
return scrollY;
|
|
})()
|
|
JS
|
|
end
|
|
|
|
def scroll_post_near_top(post, offset: 80)
|
|
page.execute_script(<<~JS)
|
|
const post = document.querySelector("[data-post-number='#{post.post_number}']");
|
|
post.scrollIntoView();
|
|
window.scrollBy(0, -#{offset});
|
|
JS
|
|
self
|
|
end
|
|
|
|
def scroll_past_topic_title
|
|
page.execute_script(<<~JS)
|
|
window.scrollTo(0, document.body.scrollHeight);
|
|
JS
|
|
self
|
|
end
|
|
|
|
def click_reply_on_op
|
|
find(".nested-view__op .post-action-menu__reply").click
|
|
self
|
|
end
|
|
|
|
def click_continue_thread(post)
|
|
within(post_container(post)) { find(".nested-post__continue-link").click }
|
|
self
|
|
end
|
|
|
|
def click_depth_line(post)
|
|
find(depth_line_selector(post)).click
|
|
self
|
|
end
|
|
|
|
def click_collapsed_bar(post)
|
|
find(wrapper_selector(post, "> .nested-post__main > .nested-post__collapsed-bar")).click
|
|
self
|
|
end
|
|
|
|
def click_mobile_ancestor(post)
|
|
find("[data-test-nested-mobile-ancestor='#{post.post_number}']").click
|
|
self
|
|
end
|
|
|
|
def click_mobile_ancestor_avatar(post)
|
|
find(
|
|
"[data-test-nested-mobile-ancestor='#{post.post_number}'] .nested-view__mobile-ancestor-avatar",
|
|
).click
|
|
self
|
|
end
|
|
|
|
def click_mobile_focus_back
|
|
find(".nested-view__mobile-focus-back").click
|
|
self
|
|
end
|
|
|
|
def click_floating_reply_button
|
|
find(".nested-view__floating-reply").click
|
|
self
|
|
end
|
|
|
|
def open_admin_menu
|
|
find(".nested-view__floating-actions .toggle-admin-menu").click
|
|
self
|
|
end
|
|
|
|
def click_admin_close_topic
|
|
open_admin_menu
|
|
find(".topic-admin-close .btn").click
|
|
self
|
|
end
|
|
|
|
def click_admin_open_topic
|
|
open_admin_menu
|
|
find(".topic-admin-open .btn").click
|
|
self
|
|
end
|
|
|
|
def change_notification_level(level)
|
|
find(
|
|
".nested-view__floating-actions .topic-notifications-button .notifications-tracking-trigger-btn",
|
|
).click
|
|
find(".notifications-tracking-btn[data-level-id='#{level}']").click
|
|
self
|
|
end
|
|
|
|
def click_copy_link_on_op
|
|
within(".nested-view__op") do
|
|
find(".show-more-actions").click if has_css?(".show-more-actions", wait: 0)
|
|
find("button.post-action-menu__copy-link").click
|
|
end
|
|
self
|
|
end
|
|
|
|
def click_copy_link_on_post(post)
|
|
within("[data-post-number='#{post.post_number}']") do
|
|
find(".show-more-actions").click if has_css?(".show-more-actions", wait: 0)
|
|
find("button.post-action-menu__copy-link").click
|
|
end
|
|
self
|
|
end
|
|
|
|
def click_sort(sort)
|
|
find(".nested-sort-selector__trigger").click
|
|
find(".dropdown-menu .btn", text: I18n.t("js.nested_replies.sort.#{sort}")).click
|
|
self
|
|
end
|
|
|
|
def scroll_to_position(scroll_y)
|
|
page.evaluate_async_script(<<~JS)
|
|
const done = arguments[0];
|
|
window.scrollTo(0, #{scroll_y});
|
|
setTimeout(done, 50);
|
|
JS
|
|
self
|
|
end
|
|
|
|
def scroll_during_pending_restore(distance:)
|
|
page.evaluate_async_script(<<~JS)
|
|
const done = arguments[0];
|
|
const positions = { restored: window.scrollY };
|
|
|
|
setTimeout(() => {
|
|
window.scrollBy(0, #{distance});
|
|
positions.afterUserScroll = window.scrollY;
|
|
}, 20);
|
|
|
|
setTimeout(() => {
|
|
positions.afterRetries = window.scrollY;
|
|
done(positions);
|
|
}, 250);
|
|
JS
|
|
end
|
|
|
|
def scroll_position_after_restore_window
|
|
page.evaluate_async_script(<<~JS)
|
|
const done = arguments[0];
|
|
const { SCROLL_RESTORE_WINDOW_MS } = require("discourse/components/nested");
|
|
setTimeout(() => done(window.scrollY), SCROLL_RESTORE_WINDOW_MS + 50);
|
|
JS
|
|
end
|
|
|
|
# ── Deletion/recovery assertions ─────────────────────────────
|
|
|
|
def has_deleted_placeholder_for?(post)
|
|
has_css?("[data-post-number='#{post.post_number}'].nested-post__placeholder--deleted")
|
|
end
|
|
|
|
def has_no_deleted_placeholder_for?(post)
|
|
has_no_css?("[data-post-number='#{post.post_number}'].nested-post__placeholder--deleted")
|
|
end
|
|
|
|
def has_deleted_post_class_for?(post)
|
|
has_css?(".nested-post--deleted [data-post-number='#{post.post_number}']")
|
|
end
|
|
|
|
def has_toggle_deleted_content_button_for?(post)
|
|
has_css?("[data-post-number='#{post.post_number}'] button.toggle-deleted-content")
|
|
end
|
|
|
|
def has_no_toggle_deleted_content_button_for?(post)
|
|
has_no_css?("[data-post-number='#{post.post_number}'] button.toggle-deleted-content")
|
|
end
|
|
|
|
def click_toggle_deleted_content(post)
|
|
find("[data-post-number='#{post.post_number}'] button.toggle-deleted-content").click
|
|
self
|
|
end
|
|
|
|
def has_deleted_content_visible_for?(post)
|
|
has_css?(wrapper_selector(post, ".nested-post__placeholder-reveal"))
|
|
end
|
|
|
|
def has_no_deleted_content_visible_for?(post)
|
|
has_no_css?(wrapper_selector(post, ".nested-post__placeholder-reveal"))
|
|
end
|
|
|
|
# ── Ignored-user placeholder assertions ──────────────────────
|
|
|
|
def has_ignored_placeholder_for?(post)
|
|
has_css?("[data-post-number='#{post.post_number}'].nested-post__placeholder--ignored")
|
|
end
|
|
|
|
def has_no_ignored_placeholder_for?(post)
|
|
has_no_css?("[data-post-number='#{post.post_number}'].nested-post__placeholder--ignored")
|
|
end
|
|
|
|
def click_reveal_ignored(post)
|
|
find(
|
|
"button.nested-post__placeholder-avatar--reveal[data-post-number='#{post.post_number}']",
|
|
).click
|
|
self
|
|
end
|
|
|
|
# ── Post actions ────────────────────────────────────────────
|
|
|
|
def click_post_delete_button(post)
|
|
within("[data-post-number='#{post.post_number}']") do
|
|
find(".show-more-actions").click if has_css?(".show-more-actions", wait: 2)
|
|
find("button.delete").click
|
|
end
|
|
self
|
|
end
|
|
|
|
def click_post_recover_button(post)
|
|
selector = "[data-post-number='#{post.post_number}']"
|
|
if has_css?("#{selector} button.recover", wait: 5)
|
|
find("#{selector} button.recover").click
|
|
else
|
|
within(selector) do
|
|
find(".show-more-actions").click
|
|
find("button.recover").click
|
|
end
|
|
end
|
|
self
|
|
end
|
|
|
|
# ── Pinned post assertions ─────────────────────────────────
|
|
|
|
def has_pinned_post?(post)
|
|
has_css?(".nested-post--pinned [data-post-number='#{post.post_number}']")
|
|
end
|
|
|
|
def has_no_pinned_post?(post)
|
|
has_no_css?(".nested-post--pinned [data-post-number='#{post.post_number}']")
|
|
end
|
|
|
|
# ── Load more ───────────────────────────────────────────────
|
|
|
|
def has_load_more_roots_button?
|
|
has_css?(".nested-view__load-more")
|
|
end
|
|
|
|
def click_load_more_roots
|
|
find(".nested-view__load-more").click
|
|
self
|
|
end
|
|
|
|
# ── Cloaking ─────────────────────────────────────────────────
|
|
|
|
def has_cloaked_root?
|
|
has_css?(".nested-view__roots > .nested-post--cloaked")
|
|
end
|
|
|
|
def has_no_cloaked_root?
|
|
has_no_css?(".nested-view__roots > .nested-post--cloaked")
|
|
end
|
|
|
|
def has_cloaked_root_for?(post)
|
|
has_css?(".nested-post--cloaked [data-post-number='#{post.post_number}']")
|
|
end
|
|
|
|
def has_uncloaked_root_for?(post)
|
|
has_no_css?(".nested-post--cloaked [data-post-number='#{post.post_number}']") &&
|
|
has_css?("[data-post-number='#{post.post_number}']")
|
|
end
|
|
|
|
# ── Suggested topics ──────────────────────────────────────────
|
|
|
|
def has_suggested_topics?
|
|
has_css?("#suggested-topics")
|
|
end
|
|
|
|
def has_no_suggested_topics?
|
|
has_no_css?("#suggested-topics")
|
|
end
|
|
|
|
def has_suggested_topic?(topic)
|
|
has_css?("#suggested-topics .topic-list-item[data-topic-id='#{topic.id}']")
|
|
end
|
|
|
|
private
|
|
|
|
def post_container(post)
|
|
find("[data-post-number='#{post.post_number}']")
|
|
end
|
|
|
|
def wrapper_selector(post, child_selector = nil)
|
|
# Builds a CSS selector that targets the DIRECT .nested-post wrapper for
|
|
# the given post, without storing any find() results. Uses > combinators
|
|
# to avoid matching ancestor .nested-post wrappers in the nested tree.
|
|
base = ".nested-post:has(> .nested-post__main > [data-post-number='#{post.post_number}'])"
|
|
child_selector ? "#{base} #{child_selector}" : base
|
|
end
|
|
|
|
def depth_line_selector(post)
|
|
wrapper_selector(post, "> .nested-post__gutter .nested-post__depth-line")
|
|
end
|
|
|
|
def leaf_depth_line_selector(post)
|
|
wrapper_selector(post, "> .nested-post__gutter .nested-post__depth-line--leaf")
|
|
end
|
|
end
|
|
end
|
|
end
|