mirror of
https://github.com/discourse/discourse.git
synced 2026-08-06 13:08:40 +08:00
## What Draft spike for rendering nested replies through the normal topic route/controller/template (`/t/:slug/:id[/post_number]`) instead of a separate Ember `/n/...` browser page route. This keeps `/n/...` for internal JSON/API endpoints and legacy browser redirects, while using the canonical topic route for user-facing nested topic navigation. ## Changes - Add nested-topic loading/setup to `topic.fromParams` so `/t/...` can render the nested tree. - Render `<Nested>` from the topic template when the topic is in nested mode. - Keep the nested controller as the nested tree state owner, bridged from the topic route/controller. - Redirect legacy client-side `/n/...` browser routes to the corresponding `/t/...` routes. - Preserve nested context depth through model processing, controller state, and cache snapshots. - Update nested post/admin URLs, notifications, activity, copy links, and system specs to canonical `/t/...` browser URLs. - Split scroll-anchor persistence from full nested model cache snapshots to avoid expensive cache writes on scroll. - Add bounded scroll restoration retries for mobile browser-back focus restoration.
189 lines
4.9 KiB
Text
Vendored
189 lines
4.9 KiB
Text
Vendored
import { click, currentURL, visit } from "@ember/test-helpers";
|
|
import { test } from "qunit";
|
|
import { acceptance } from "discourse/tests/helpers/qunit-helpers";
|
|
|
|
function topicAttrs(id, slug, title) {
|
|
return {
|
|
id,
|
|
slug,
|
|
title,
|
|
fancy_title: title,
|
|
posts_count: 4,
|
|
reply_count: 3,
|
|
highest_post_number: 4,
|
|
last_read_post_number: 4,
|
|
last_poster_username: "alice",
|
|
bumped_at: "2024-06-01T12:00:00.000Z",
|
|
last_posted_at: "2024-06-01T12:00:00.000Z",
|
|
created_at: "2024-06-01T10:00:00.000Z",
|
|
category_id: 1,
|
|
visible: true,
|
|
closed: false,
|
|
archived: false,
|
|
pinned: false,
|
|
pinned_globally: false,
|
|
deleted_at: null,
|
|
archetype: "regular",
|
|
details: { can_create_post: true, notification_level: 1 },
|
|
is_nested_view: true,
|
|
};
|
|
}
|
|
|
|
function postAttrs(topic, postNumber, id, cooked, replyToPostNumber = null) {
|
|
return {
|
|
id,
|
|
topic_id: topic.id,
|
|
topic_slug: topic.slug,
|
|
post_number: postNumber,
|
|
reply_to_post_number: replyToPostNumber,
|
|
username: "alice",
|
|
name: "Alice",
|
|
avatar_template: "/letter_avatar_proxy/v4/letter/a/{size}.png",
|
|
cooked: `<p>${cooked}</p>`,
|
|
created_at: "2024-06-01T10:00:00.000Z",
|
|
updated_at: "2024-06-01T10:00:00.000Z",
|
|
post_type: 1,
|
|
can_edit: true,
|
|
can_delete: false,
|
|
actions_summary: [],
|
|
reply_count: 0,
|
|
reads: 1,
|
|
read: true,
|
|
yours: false,
|
|
};
|
|
}
|
|
|
|
function nestedResponse({ id, slug, title, marker, suggestedTopic }) {
|
|
const topic = topicAttrs(id, slug, title);
|
|
const opPost = postAttrs(topic, 1, id * 10 + 1, `${marker} op`);
|
|
const firstReply = postAttrs(
|
|
topic,
|
|
2,
|
|
id * 10 + 2,
|
|
`${marker} first branch`,
|
|
1
|
|
);
|
|
const childOne = postAttrs(topic, 3, id * 10 + 3, `${marker} child one`, 2);
|
|
const childTwo = postAttrs(topic, 4, id * 10 + 4, `${marker} child two`, 2);
|
|
|
|
return {
|
|
topic,
|
|
op_post: opPost,
|
|
roots: [
|
|
{
|
|
...firstReply,
|
|
reply_count: 2,
|
|
direct_reply_count: 2,
|
|
total_descendant_count: 2,
|
|
children: [childOne, childTwo],
|
|
},
|
|
],
|
|
page: 0,
|
|
has_more_roots: false,
|
|
sort: "old",
|
|
pinned_post_ids: [],
|
|
message_bus_last_id: id,
|
|
suggested_topics: suggestedTopic ? [suggestedTopic] : [],
|
|
};
|
|
}
|
|
|
|
function topicViewResponse(id, slug, title) {
|
|
const topic = topicAttrs(id, slug, title);
|
|
const opPost = postAttrs(topic, 1, id * 10 + 1, `${title} op`);
|
|
|
|
return {
|
|
...topic,
|
|
post_stream: {
|
|
posts: [opPost],
|
|
stream: [opPost.id],
|
|
},
|
|
timeline_lookup: [[1, 0]],
|
|
};
|
|
}
|
|
|
|
acceptance("Horizon | Nested suggested topic navigation", function (needs) {
|
|
needs.user();
|
|
needs.settings({
|
|
nested_replies_enabled: true,
|
|
nested_replies_default_sort: "old",
|
|
});
|
|
|
|
needs.pretender((server, helper) => {
|
|
const firstTopicSuggestion = topicAttrs(
|
|
102,
|
|
"second-nested-topic",
|
|
"Second nested topic"
|
|
);
|
|
const secondTopicSuggestion = topicAttrs(
|
|
101,
|
|
"first-nested-topic",
|
|
"First nested topic"
|
|
);
|
|
|
|
server.get("/t/101.json", () =>
|
|
helper.response(
|
|
topicViewResponse(101, "first-nested-topic", "First nested topic")
|
|
)
|
|
);
|
|
|
|
server.get("/t/102.json", () =>
|
|
helper.response(
|
|
topicViewResponse(102, "second-nested-topic", "Second nested topic")
|
|
)
|
|
);
|
|
|
|
server.get("/n/first-nested-topic/101.json", () =>
|
|
helper.response(
|
|
nestedResponse({
|
|
id: 101,
|
|
slug: "first-nested-topic",
|
|
title: "First nested topic",
|
|
marker: "FIRST_TOPIC_UNIQUE",
|
|
suggestedTopic: firstTopicSuggestion,
|
|
})
|
|
)
|
|
);
|
|
|
|
server.get("/n/second-nested-topic/102.json", () =>
|
|
helper.response(
|
|
nestedResponse({
|
|
id: 102,
|
|
slug: "second-nested-topic",
|
|
title: "Second nested topic",
|
|
marker: "SECOND_TOPIC_UNIQUE",
|
|
suggestedTopic: secondTopicSuggestion,
|
|
})
|
|
)
|
|
);
|
|
});
|
|
|
|
test("row-clicking a suggested nested topic replaces the old root branch", async function (assert) {
|
|
await visit("/t/first-nested-topic/101");
|
|
|
|
assert
|
|
.dom(".nested-view__roots")
|
|
.includesText("FIRST_TOPIC_UNIQUE first branch");
|
|
assert
|
|
.dom(".nested-view__roots")
|
|
.includesText("FIRST_TOPIC_UNIQUE child one");
|
|
assert
|
|
.dom("#suggested-topics .topic-list-item")
|
|
.exists("renders suggested topics with Horizon's card row behavior");
|
|
|
|
await click("#suggested-topics .topic-list-item");
|
|
|
|
assert.strictEqual(currentURL(), "/t/second-nested-topic/102?sort=old");
|
|
assert
|
|
.dom(".nested-view__roots")
|
|
.includesText("SECOND_TOPIC_UNIQUE first branch");
|
|
assert
|
|
.dom(".nested-view__roots")
|
|
.includesText("SECOND_TOPIC_UNIQUE child one");
|
|
assert
|
|
.dom(".nested-view__roots")
|
|
.doesNotIncludeText("FIRST_TOPIC_UNIQUE first branch");
|
|
assert
|
|
.dom(".nested-view__roots")
|
|
.doesNotIncludeText("FIRST_TOPIC_UNIQUE child one");
|
|
});
|
|
});
|