+ ? html`
${repostIconTemplate()} Reposted by
${getDisplayName(repostAuthor)}
`
diff --git a/tests/specs/templates/avatar.template.test.js b/tests/specs/templates/avatar.template.test.js
index 5b67f95e..2b0b613d 100644
--- a/tests/specs/templates/avatar.template.test.js
+++ b/tests/specs/templates/avatar.template.test.js
@@ -1,68 +1,147 @@
import { TestSuite } from "../../testSuite.js";
-import { assert } from "../../testHelpers.js";
+import { assert, assertEquals } from "../../testHelpers.js";
import { avatarTemplate } from "/js/templates/avatar.template.js";
import { post } from "../../fixtures.js";
+import { render } from "/js/lib/lit-html.js";
const t = new TestSuite("avatarTemplate");
t.describe("avatarTemplate", (it) => {
- it("should render avatar with author info", () => {
+ it("should render avatar container", () => {
const result = avatarTemplate({ author: post.author });
- assert(result instanceof Object);
+ const container = document.createElement("div");
+ render(result, container);
+ assert(container.querySelector("[data-testid='avatar']") !== null);
});
- it("should render avatar without avatar URL", () => {
+ it("should render avatar image with author info", () => {
+ const result = avatarTemplate({ author: post.author });
+ const container = document.createElement("div");
+ render(result, container);
+ const img = container.querySelector("[data-testid='avatar-image']");
+ assert(img !== null);
+ assert(img.getAttribute("src").includes(post.author.did));
+ });
+
+ it("should render fallback avatar when no avatar URL", () => {
const author = { ...post.author, avatar: null };
const result = avatarTemplate({ author });
- assert(result instanceof Object);
+ const container = document.createElement("div");
+ render(result, container);
+ const img = container.querySelector("[data-testid='avatar-image']");
+ assert(img !== null);
+ assert(img.getAttribute("src").includes("avatar-fallback.svg"));
+ });
+
+ it("should render as link by default", () => {
+ const result = avatarTemplate({ author: post.author });
+ const container = document.createElement("div");
+ render(result, container);
+ const link = container.querySelector("a.avatar-link");
+ assert(link !== null);
+ });
+
+ it("should render as lightbox when clickAction is lightbox", () => {
+ const result = avatarTemplate({
+ author: post.author,
+ clickAction: "lightbox",
+ });
+ const container = document.createElement("div");
+ render(result, container);
+ const lightbox = container.querySelector("lightbox-image-group");
+ assert(lightbox !== null);
+ });
+
+ it("should render without wrapper when clickAction is none", () => {
+ const result = avatarTemplate({
+ author: post.author,
+ clickAction: "none",
+ });
+ const container = document.createElement("div");
+ render(result, container);
+ assertEquals(container.querySelector("a.avatar-link"), null);
+ assertEquals(container.querySelector("lightbox-image-group"), null);
+ });
+
+ it("should use lazy loading when lazyLoad is true", () => {
+ const result = avatarTemplate({
+ author: post.author,
+ lazyLoad: true,
+ });
+ const container = document.createElement("div");
+ render(result, container);
+ const img = container.querySelector("[data-testid='avatar-image']");
+ assertEquals(img.getAttribute("loading"), "lazy");
+ });
+
+ it("should use eager loading by default", () => {
+ const result = avatarTemplate({ author: post.author });
+ const container = document.createElement("div");
+ render(result, container);
+ const img = container.querySelector("[data-testid='avatar-image']");
+ assertEquals(img.getAttribute("loading"), "eager");
});
});
t.describe("avatarTemplate - labeler profiles", (it) => {
- it("should render avatar for labeler profile", () => {
+ it("should render avatar for labeler profile with labeler class", () => {
const labelerAuthor = {
...post.author,
associated: { labeler: true },
};
const result = avatarTemplate({ author: labelerAuthor });
- assert(result instanceof Object);
+ const container = document.createElement("div");
+ render(result, container);
+ const img = container.querySelector("[data-testid='avatar-image']");
+ assert(img.classList.contains("labeler-avatar"));
});
- it("should render avatar for non-labeler profile", () => {
+ it("should render avatar for non-labeler profile without labeler class", () => {
const normalAuthor = {
...post.author,
associated: { labeler: false },
};
const result = avatarTemplate({ author: normalAuthor });
- assert(result instanceof Object);
+ const container = document.createElement("div");
+ render(result, container);
+ const img = container.querySelector("[data-testid='avatar-image']");
+ assert(!img.classList.contains("labeler-avatar"));
});
it("should render avatar when associated is undefined", () => {
const authorWithoutAssociated = { ...post.author };
delete authorWithoutAssociated.associated;
const result = avatarTemplate({ author: authorWithoutAssociated });
- assert(result instanceof Object);
+ const container = document.createElement("div");
+ render(result, container);
+ assert(container.querySelector("[data-testid='avatar-image']") !== null);
});
- it("should handle labeler profile with custom avatar URL", () => {
+ it("should use labeler fallback avatar for labeler without avatar URL", () => {
const labelerAuthor = {
...post.author,
- avatar:
- "https://cdn.bsky.app/img/avatar/plain/did:plc:labeler/avatar@jpeg",
+ avatar: null,
associated: { labeler: true },
};
const result = avatarTemplate({ author: labelerAuthor });
- assert(result instanceof Object);
+ const container = document.createElement("div");
+ render(result, container);
+ const img = container.querySelector("[data-testid='avatar-image']");
+ assert(img.getAttribute("src").includes("labeler-avatar-fallback.svg"));
});
- it("should handle labeler profile without avatar URL", () => {
- const labelerAuthor = {
+ it("should use regular fallback avatar for non-labeler without avatar URL", () => {
+ const normalAuthor = {
...post.author,
avatar: null,
- associated: { labeler: true },
+ associated: { labeler: false },
};
- const result = avatarTemplate({ author: labelerAuthor });
- assert(result instanceof Object);
+ const result = avatarTemplate({ author: normalAuthor });
+ const container = document.createElement("div");
+ render(result, container);
+ const img = container.querySelector("[data-testid='avatar-image']");
+ assert(img.getAttribute("src").includes("avatar-fallback.svg"));
+ assert(!img.getAttribute("src").includes("labeler"));
});
});
diff --git a/tests/specs/templates/largePost.template.test.js b/tests/specs/templates/largePost.template.test.js
index 9a287bed..7589d723 100644
--- a/tests/specs/templates/largePost.template.test.js
+++ b/tests/specs/templates/largePost.template.test.js
@@ -1,7 +1,8 @@
import { TestSuite } from "../../testSuite.js";
-import { assert } from "../../testHelpers.js";
+import { assert, assertEquals } from "../../testHelpers.js";
import { largePostTemplate } from "/js/templates/largePost.template.js";
import { post } from "../../fixtures.js";
+import { render } from "/js/lib/lit-html.js";
const noop = () => {};
const postInteractionHandler = {
@@ -19,18 +20,155 @@ const postInteractionHandler = {
const t = new TestSuite("largePostTemplate");
t.describe("largePostTemplate", (it) => {
- it("should render the post", () => {
+ it("should render the post container", () => {
const result = largePostTemplate({ post, postInteractionHandler });
- assert(result instanceof Object);
+ const container = document.createElement("div");
+ render(result, container);
+ assert(container.querySelector("[data-testid='large-post']") !== null);
});
- it("should render with reply context", () => {
+ it("should render post with avatar", () => {
+ const result = largePostTemplate({ post, postInteractionHandler });
+ const container = document.createElement("div");
+ render(result, container);
+ assert(container.querySelector("[data-testid='avatar']") !== null);
+ });
+
+ it("should render post with author name", () => {
+ const result = largePostTemplate({ post, postInteractionHandler });
+ const container = document.createElement("div");
+ render(result, container);
+ assert(
+ container.querySelector("[data-testid='post-author-name']") !== null,
+ );
+ });
+
+ it("should render post text content", () => {
+ const postWithText = {
+ ...post,
+ record: { ...post.record, text: "Hello world!" },
+ };
+ const result = largePostTemplate({
+ post: postWithText,
+ postInteractionHandler,
+ });
+ const container = document.createElement("div");
+ render(result, container);
+ assert(container.textContent.includes("Hello world!"));
+ });
+
+ it("should render post action bar", () => {
+ const result = largePostTemplate({ post, postInteractionHandler });
+ const container = document.createElement("div");
+ render(result, container);
+ assert(container.querySelector("[data-testid='reply-button']") !== null);
+ assert(container.querySelector("[data-testid='repost-button']") !== null);
+ assert(container.querySelector("[data-testid='bookmark-button']") !== null);
+ });
+
+ it("should render with reply context line when replyContext is parent", () => {
const result = largePostTemplate({
post,
postInteractionHandler,
replyContext: "parent",
});
- assert(result instanceof Object);
+ const container = document.createElement("div");
+ render(result, container);
+ assert(container.querySelector(".reply-context-line-in") !== null);
+ });
+
+ it("should render with reply context line when replyContext is reply", () => {
+ const result = largePostTemplate({
+ post,
+ postInteractionHandler,
+ replyContext: "reply",
+ });
+ const container = document.createElement("div");
+ render(result, container);
+ assert(container.querySelector(".reply-context-line-in") !== null);
+ });
+
+ it("should not render reply context line when no replyContext", () => {
+ const result = largePostTemplate({ post, postInteractionHandler });
+ const container = document.createElement("div");
+ render(result, container);
+ assertEquals(container.querySelector(".reply-context-line-in"), null);
+ });
+});
+
+t.describe("largePostTemplate - blocked/unavailable posts", (it) => {
+ it("should render blocked post template for blocked post", () => {
+ const blockedPost = {
+ $type: "app.bsky.feed.defs#blockedPost",
+ uri: "blocked-uri",
+ blocked: true,
+ };
+ const result = largePostTemplate({
+ post: blockedPost,
+ postInteractionHandler,
+ });
+ const container = document.createElement("div");
+ render(result, container);
+ assert(container.textContent.includes("unavailable"));
+ });
+
+ it("should render not found post template for not found post", () => {
+ const notFoundPost = {
+ $type: "app.bsky.feed.defs#notFoundPost",
+ uri: "not-found-uri",
+ notFound: true,
+ };
+ const result = largePostTemplate({
+ post: notFoundPost,
+ postInteractionHandler,
+ });
+ const container = document.createElement("div");
+ render(result, container);
+ assert(container.textContent.includes("deleted"));
+ });
+});
+
+t.describe("largePostTemplate - moderation", (it) => {
+ it("should show moderation warning for post with muted word", () => {
+ const mutedPost = {
+ ...post,
+ viewer: { ...post.viewer, hasMutedWord: true },
+ };
+ const result = largePostTemplate({
+ post: mutedPost,
+ postInteractionHandler,
+ });
+ const container = document.createElement("div");
+ render(result, container);
+ assert(container.querySelector("moderation-warning") !== null);
+ });
+
+ it("should show moderation warning for hidden post", () => {
+ const hiddenPost = {
+ ...post,
+ viewer: { ...post.viewer, isHidden: true },
+ };
+ const result = largePostTemplate({
+ post: hiddenPost,
+ postInteractionHandler,
+ });
+ const container = document.createElement("div");
+ render(result, container);
+ assert(container.querySelector("moderation-warning") !== null);
+ });
+
+ it("should not show moderation warning for normal post", () => {
+ const normalPost = {
+ ...post,
+ viewer: { ...post.viewer, hasMutedWord: false, isHidden: false },
+ };
+ const result = largePostTemplate({
+ post: normalPost,
+ postInteractionHandler,
+ });
+ const container = document.createElement("div");
+ render(result, container);
+ assertEquals(container.querySelector("moderation-warning"), null);
});
});
diff --git a/tests/specs/templates/postActionBar.template.test.js b/tests/specs/templates/postActionBar.template.test.js
index f7a5cc79..891bb310 100644
--- a/tests/specs/templates/postActionBar.template.test.js
+++ b/tests/specs/templates/postActionBar.template.test.js
@@ -1,29 +1,229 @@
import { TestSuite } from "../../testSuite.js";
-import { assert } from "../../testHelpers.js";
+import { assert, assertEquals } from "../../testHelpers.js";
import { postActionBarTemplate } from "/js/templates/postActionBar.template.js";
import { post } from "../../fixtures.js";
+import { render } from "/js/lib/lit-html.js";
const t = new TestSuite("postActionBarTemplate");
t.describe("postActionBarTemplate", (it) => {
- it("should render action bar", () => {
+ it("should render action bar with reply button", () => {
const result = postActionBarTemplate({
post,
- isLiked: false,
- numLikes: 10,
+ isAuthenticated: true,
onClickLike: () => {},
});
- assert(result instanceof Object);
+ const container = document.createElement("div");
+ render(result, container);
+ assert(container.querySelector("[data-testid='reply-button']") !== null);
});
- it("should render action bar with liked state", () => {
+ it("should render action bar with repost button", () => {
const result = postActionBarTemplate({
post,
- isLiked: true,
- numLikes: 10,
+ isAuthenticated: true,
onClickLike: () => {},
});
- assert(result instanceof Object);
+ const container = document.createElement("div");
+ render(result, container);
+ assert(container.querySelector("[data-testid='repost-button']") !== null);
+ });
+
+ it("should render action bar with bookmark button", () => {
+ const result = postActionBarTemplate({
+ post,
+ isAuthenticated: true,
+ onClickLike: () => {},
+ });
+ const container = document.createElement("div");
+ render(result, container);
+ assert(container.querySelector("[data-testid='bookmark-button']") !== null);
+ });
+
+ it("should show reply count when post has replies", () => {
+ const postWithReplies = {
+ ...post,
+ replyCount: 5,
+ };
+ const result = postActionBarTemplate({
+ post: postWithReplies,
+ isAuthenticated: true,
+ onClickLike: () => {},
+ });
+ const container = document.createElement("div");
+ render(result, container);
+ const replyCount = container.querySelector("[data-testid='reply-count']");
+ assert(replyCount !== null);
+ assertEquals(replyCount.textContent.trim(), "5");
+ });
+
+ it("should not show reply count when post has no replies", () => {
+ const postWithNoReplies = {
+ ...post,
+ replyCount: 0,
+ };
+ const result = postActionBarTemplate({
+ post: postWithNoReplies,
+ isAuthenticated: true,
+ onClickLike: () => {},
+ });
+ const container = document.createElement("div");
+ render(result, container);
+ assertEquals(container.querySelector("[data-testid='reply-count']"), null);
+ });
+
+ it("should show repost count when post has reposts", () => {
+ const postWithReposts = {
+ ...post,
+ repostCount: 10,
+ };
+ const result = postActionBarTemplate({
+ post: postWithReposts,
+ isAuthenticated: true,
+ onClickLike: () => {},
+ });
+ const container = document.createElement("div");
+ render(result, container);
+ const repostCount = container.querySelector("[data-testid='repost-count']");
+ assert(repostCount !== null);
+ assertEquals(repostCount.textContent.trim(), "10");
+ });
+
+ it("should not show repost count when post has no reposts", () => {
+ const postWithNoReposts = {
+ ...post,
+ repostCount: 0,
+ };
+ const result = postActionBarTemplate({
+ post: postWithNoReposts,
+ isAuthenticated: true,
+ onClickLike: () => {},
+ });
+ const container = document.createElement("div");
+ render(result, container);
+ assertEquals(container.querySelector("[data-testid='repost-count']"), null);
+ });
+
+ it("should add reposted class when post is reposted", () => {
+ const repostedPost = {
+ ...post,
+ viewer: { ...post.viewer, repost: "repost-uri" },
+ };
+ const result = postActionBarTemplate({
+ post: repostedPost,
+ isAuthenticated: true,
+ onClickLike: () => {},
+ });
+ const container = document.createElement("div");
+ render(result, container);
+ const repostButton = container.querySelector(
+ "[data-testid='repost-button']",
+ );
+ assert(repostButton.classList.contains("reposted"));
+ });
+
+ it("should not have reposted class when post is not reposted", () => {
+ const notRepostedPost = {
+ ...post,
+ viewer: { ...post.viewer, repost: null },
+ };
+ const result = postActionBarTemplate({
+ post: notRepostedPost,
+ isAuthenticated: true,
+ onClickLike: () => {},
+ });
+ const container = document.createElement("div");
+ render(result, container);
+ const repostButton = container.querySelector(
+ "[data-testid='repost-button']",
+ );
+ assert(!repostButton.classList.contains("reposted"));
+ });
+
+ it("should add bookmarked class when post is bookmarked", () => {
+ const bookmarkedPost = {
+ ...post,
+ viewer: { ...post.viewer, bookmarked: true },
+ };
+ const result = postActionBarTemplate({
+ post: bookmarkedPost,
+ isAuthenticated: true,
+ onClickLike: () => {},
+ });
+ const container = document.createElement("div");
+ render(result, container);
+ const bookmarkButton = container.querySelector(
+ "[data-testid='bookmark-button']",
+ );
+ assert(bookmarkButton.classList.contains("bookmarked"));
+ });
+
+ it("should not have bookmarked class when post is not bookmarked", () => {
+ const notBookmarkedPost = {
+ ...post,
+ viewer: { ...post.viewer, bookmarked: false },
+ };
+ const result = postActionBarTemplate({
+ post: notBookmarkedPost,
+ isAuthenticated: true,
+ onClickLike: () => {},
+ });
+ const container = document.createElement("div");
+ render(result, container);
+ const bookmarkButton = container.querySelector(
+ "[data-testid='bookmark-button']",
+ );
+ assert(!bookmarkButton.classList.contains("bookmarked"));
+ });
+});
+
+t.describe("postActionBarTemplate - callbacks", (it) => {
+ it("should call onClickBookmark when bookmark button clicked", () => {
+ let callArgs = null;
+ const testPost = {
+ ...post,
+ viewer: { ...post.viewer, bookmarked: false },
+ };
+ const onClickBookmark = (p, doBookmark) => {
+ callArgs = { post: p, doBookmark };
+ };
+ const result = postActionBarTemplate({
+ post: testPost,
+ isAuthenticated: true,
+ onClickBookmark,
+ });
+ const container = document.createElement("div");
+ render(result, container);
+ const bookmarkButton = container.querySelector(
+ "[data-testid='bookmark-button']",
+ );
+ bookmarkButton.click();
+ assert(callArgs !== null);
+ assertEquals(callArgs.doBookmark, true);
+ });
+
+ it("should call onClickBookmark with false when unbookmarking", () => {
+ let callArgs = null;
+ const testPost = {
+ ...post,
+ viewer: { ...post.viewer, bookmarked: true },
+ };
+ const onClickBookmark = (p, doBookmark) => {
+ callArgs = { post: p, doBookmark };
+ };
+ const result = postActionBarTemplate({
+ post: testPost,
+ isAuthenticated: true,
+ onClickBookmark,
+ });
+ const container = document.createElement("div");
+ render(result, container);
+ const bookmarkButton = container.querySelector(
+ "[data-testid='bookmark-button']",
+ );
+ bookmarkButton.click();
+ assert(callArgs !== null);
+ assertEquals(callArgs.doBookmark, false);
});
});
diff --git a/tests/specs/templates/postEmbed.template.test.js b/tests/specs/templates/postEmbed.template.test.js
index 5106eb44..9055c4af 100644
--- a/tests/specs/templates/postEmbed.template.test.js
+++ b/tests/specs/templates/postEmbed.template.test.js
@@ -1,11 +1,12 @@
import { TestSuite } from "../../testSuite.js";
-import { assert } from "../../testHelpers.js";
+import { assert, assertEquals } from "../../testHelpers.js";
import { postEmbedTemplate } from "/js/templates/postEmbed.template.js";
import { post } from "../../fixtures.js";
+import { render } from "/js/lib/lit-html.js";
const t = new TestSuite("postEmbedTemplate");
-t.describe("postEmbedTemplate", (it) => {
+t.describe("postEmbedTemplate - images", (it) => {
it("should render image embed", () => {
const embed = {
$type: "app.bsky.embed.images#view",
@@ -21,9 +22,62 @@ t.describe("postEmbedTemplate", (it) => {
labels: [],
isAuthenticated: true,
});
- assert(result instanceof Object);
+ const container = document.createElement("div");
+ render(result, container);
+ assert(container.querySelector("[data-testid='post-images']") !== null);
});
+ it("should render multiple images", () => {
+ const embed = {
+ $type: "app.bsky.embed.images#view",
+ images: [
+ { thumb: "https://example.com/image1.jpg", alt: "Image 1" },
+ { thumb: "https://example.com/image2.jpg", alt: "Image 2" },
+ ],
+ };
+ const result = postEmbedTemplate({
+ embed,
+ labels: [],
+ isAuthenticated: true,
+ });
+ const container = document.createElement("div");
+ render(result, container);
+ const images = container.querySelectorAll(".post-image");
+ assertEquals(images.length, 2);
+ });
+
+ it("should show ALT indicator when image has alt text", () => {
+ const embed = {
+ $type: "app.bsky.embed.images#view",
+ images: [{ thumb: "https://example.com/image.jpg", alt: "Test image" }],
+ };
+ const result = postEmbedTemplate({
+ embed,
+ labels: [],
+ isAuthenticated: true,
+ });
+ const container = document.createElement("div");
+ render(result, container);
+ assert(container.querySelector(".alt-indicator") !== null);
+ });
+
+ it("should not show ALT indicator when image has no alt text", () => {
+ const embed = {
+ $type: "app.bsky.embed.images#view",
+ images: [{ thumb: "https://example.com/image.jpg", alt: "" }],
+ };
+ const result = postEmbedTemplate({
+ embed,
+ labels: [],
+ isAuthenticated: true,
+ });
+ const container = document.createElement("div");
+ render(result, container);
+ assertEquals(container.querySelector(".alt-indicator"), null);
+ });
+});
+
+t.describe("postEmbedTemplate - external links", (it) => {
it("should render external link embed", () => {
const embed = {
$type: "app.bsky.embed.external#view",
@@ -39,9 +93,61 @@ t.describe("postEmbedTemplate", (it) => {
labels: [],
isAuthenticated: true,
});
- assert(result instanceof Object);
+ const container = document.createElement("div");
+ render(result, container);
+ assert(container.querySelector("[data-testid='external-link']") !== null);
+ });
+
+ it("should render external link with title", () => {
+ const embed = {
+ $type: "app.bsky.embed.external#view",
+ external: {
+ uri: "https://example.com",
+ title: "Example Title",
+ description: "Test description",
+ },
+ };
+ const result = postEmbedTemplate({
+ embed,
+ labels: [],
+ isAuthenticated: true,
+ });
+ const container = document.createElement("div");
+ render(result, container);
+ assertEquals(
+ container
+ .querySelector("[data-testid='external-link-title']")
+ .textContent.trim(),
+ "Example Title",
+ );
+ });
+
+ it("should render external link with domain", () => {
+ const embed = {
+ $type: "app.bsky.embed.external#view",
+ external: {
+ uri: "https://example.com/page",
+ title: "Example",
+ description: "Test description",
+ },
+ };
+ const result = postEmbedTemplate({
+ embed,
+ labels: [],
+ isAuthenticated: true,
+ });
+ const container = document.createElement("div");
+ render(result, container);
+ assertEquals(
+ container
+ .querySelector("[data-testid='external-link-domain']")
+ .textContent.trim(),
+ "example.com",
+ );
});
+});
+t.describe("postEmbedTemplate - quoted posts", (it) => {
it("should render quoted post embed", () => {
const embed = {
$type: "app.bsky.embed.record#view",
@@ -57,7 +163,9 @@ t.describe("postEmbedTemplate", (it) => {
labels: [],
isAuthenticated: true,
});
- assert(result instanceof Object);
+ const container = document.createElement("div");
+ render(result, container);
+ assert(container.querySelector(".quoted-post") !== null);
});
it("should render blocked quote embed", () => {
@@ -74,7 +182,45 @@ t.describe("postEmbedTemplate", (it) => {
labels: [],
isAuthenticated: true,
});
- assert(result instanceof Object);
+ const container = document.createElement("div");
+ render(result, container);
+ assert(container.querySelector("[data-testid='blocked-quote']") !== null);
+ });
+
+ it("should render not found quote embed", () => {
+ const embed = {
+ $type: "app.bsky.embed.record#view",
+ record: {
+ $type: "app.bsky.embed.record#viewNotFound",
+ uri: "not-found-uri",
+ },
+ };
+ const result = postEmbedTemplate({
+ embed,
+ labels: [],
+ isAuthenticated: true,
+ });
+ const container = document.createElement("div");
+ render(result, container);
+ assert(container.querySelector("[data-testid='not-found-quote']") !== null);
+ });
+
+ it("should render detached/removed quote embed", () => {
+ const embed = {
+ $type: "app.bsky.embed.record#view",
+ record: {
+ $type: "app.bsky.embed.record#viewDetached",
+ uri: "detached-uri",
+ },
+ };
+ const result = postEmbedTemplate({
+ embed,
+ labels: [],
+ isAuthenticated: true,
+ });
+ const container = document.createElement("div");
+ render(result, container);
+ assert(container.querySelector("[data-testid='removed-quote']") !== null);
});
});
diff --git a/tests/specs/templates/postHeaderText.template.test.js b/tests/specs/templates/postHeaderText.template.test.js
index f154651a..0b93dd05 100644
--- a/tests/specs/templates/postHeaderText.template.test.js
+++ b/tests/specs/templates/postHeaderText.template.test.js
@@ -1,35 +1,122 @@
import { TestSuite } from "../../testSuite.js";
-import { assert } from "../../testHelpers.js";
+import { assert, assertEquals } from "../../testHelpers.js";
import { postHeaderTextTemplate } from "/js/templates/postHeaderText.template.js";
import { post } from "../../fixtures.js";
+import { render } from "/js/lib/lit-html.js";
const t = new TestSuite("postHeaderTextTemplate");
t.describe("postHeaderTextTemplate", (it) => {
+ it("should render header with author name", () => {
+ const result = postHeaderTextTemplate({
+ author: post.author,
+ timestamp: post.record.createdAt,
+ includeTime: true,
+ });
+ const container = document.createElement("div");
+ render(result, container);
+ assertEquals(
+ container
+ .querySelector("[data-testid='post-author-name']")
+ .textContent.trim(),
+ post.author.displayName,
+ );
+ });
+
+ it("should render header with author handle", () => {
+ const result = postHeaderTextTemplate({
+ author: post.author,
+ timestamp: post.record.createdAt,
+ includeTime: true,
+ });
+ const container = document.createElement("div");
+ render(result, container);
+ assertEquals(
+ container
+ .querySelector("[data-testid='post-author-handle']")
+ .textContent.trim(),
+ `@${post.author.handle}`,
+ );
+ });
+
it("should render header with time", () => {
const result = postHeaderTextTemplate({
author: post.author,
timestamp: post.record.createdAt,
includeTime: true,
});
- assert(result instanceof Object);
+ const container = document.createElement("div");
+ render(result, container);
+ assert(container.querySelector("[data-testid='post-time']") !== null);
});
- it("should render header without time", () => {
+ it("should render header without time when includeTime is false", () => {
const result = postHeaderTextTemplate({
author: post.author,
timestamp: post.record.createdAt,
includeTime: false,
});
- assert(result instanceof Object);
+ const container = document.createElement("div");
+ render(result, container);
+ assertEquals(container.querySelector("[data-testid='post-time']"), null);
+ });
+
+ it("should render header without handle when includeHandle is false", () => {
+ const result = postHeaderTextTemplate({
+ author: post.author,
+ timestamp: post.record.createdAt,
+ includeHandle: false,
+ });
+ const container = document.createElement("div");
+ render(result, container);
+ assertEquals(
+ container.querySelector("[data-testid='post-author-handle']"),
+ null,
+ );
+ });
+
+ it("should render handle as name when displayName is missing", () => {
+ const authorWithoutDisplayName = { ...post.author, displayName: null };
+ const result = postHeaderTextTemplate({
+ author: authorWithoutDisplayName,
+ timestamp: post.record.createdAt,
+ });
+ const container = document.createElement("div");
+ render(result, container);
+ assertEquals(
+ container
+ .querySelector("[data-testid='post-author-name']")
+ .textContent.trim(),
+ post.author.handle,
+ );
+ });
+
+ it("should render as link when enableProfileLink is true", () => {
+ const result = postHeaderTextTemplate({
+ author: post.author,
+ timestamp: post.record.createdAt,
+ enableProfileLink: true,
+ });
+ const container = document.createElement("div");
+ render(result, container);
+ const nameElement = container.querySelector(
+ "[data-testid='post-author-name']",
+ );
+ assertEquals(nameElement.tagName.toLowerCase(), "a");
});
- it("should render header with default includeTime", () => {
+ it("should render as span when enableProfileLink is false", () => {
const result = postHeaderTextTemplate({
author: post.author,
timestamp: post.record.createdAt,
+ enableProfileLink: false,
});
- assert(result instanceof Object);
+ const container = document.createElement("div");
+ render(result, container);
+ const nameElement = container.querySelector(
+ "[data-testid='post-author-name']",
+ );
+ assertEquals(nameElement.tagName.toLowerCase(), "span");
});
});
diff --git a/tests/specs/templates/postSkeleton.template.test.js b/tests/specs/templates/postSkeleton.template.test.js
index 069c5838..c651a3c8 100644
--- a/tests/specs/templates/postSkeleton.template.test.js
+++ b/tests/specs/templates/postSkeleton.template.test.js
@@ -1,13 +1,39 @@
import { TestSuite } from "../../testSuite.js";
import { assert } from "../../testHelpers.js";
import { postSkeletonTemplate } from "/js/templates/postSkeleton.template.js";
+import { render } from "/js/lib/lit-html.js";
const t = new TestSuite("postSkeletonTemplate");
t.describe("postSkeletonTemplate", (it) => {
it("should render skeleton", () => {
const result = postSkeletonTemplate();
- assert(result instanceof Object);
+ const container = document.createElement("div");
+ render(result, container);
+ assert(container.querySelector("[data-testid='post-skeleton']") !== null);
+ });
+
+ it("should render skeleton avatar", () => {
+ const result = postSkeletonTemplate();
+ const container = document.createElement("div");
+ render(result, container);
+ assert(container.querySelector(".skeleton-avatar") !== null);
+ });
+
+ it("should render skeleton content lines", () => {
+ const result = postSkeletonTemplate();
+ const container = document.createElement("div");
+ render(result, container);
+ const lines = container.querySelectorAll(".skeleton-line");
+ assert(lines.length === 2);
+ });
+
+ it("should render skeleton action icons", () => {
+ const result = postSkeletonTemplate();
+ const container = document.createElement("div");
+ render(result, container);
+ const actions = container.querySelectorAll(".skeleton-action");
+ assert(actions.length === 3);
});
});
diff --git a/tests/specs/templates/profileCard.template.test.js b/tests/specs/templates/profileCard.template.test.js
index a3919b6c..a5346763 100644
--- a/tests/specs/templates/profileCard.template.test.js
+++ b/tests/specs/templates/profileCard.template.test.js
@@ -1,6 +1,7 @@
import { TestSuite } from "../../testSuite.js";
-import { assert } from "../../testHelpers.js";
+import { assert, assertEquals } from "../../testHelpers.js";
import { profileCardTemplate } from "/js/templates/profileCard.template.js";
+import { render } from "/js/lib/lit-html.js";
const t = new TestSuite("profileCardTemplate");
@@ -24,7 +25,33 @@ t.describe("profileCardTemplate", (it) => {
profile: mockProfile,
onClickFollow: () => {},
});
- assert(result instanceof Object);
+ const container = document.createElement("div");
+ render(result, container);
+ assertEquals(
+ container
+ .querySelector("[data-testid='profile-name']")
+ .textContent.trim(),
+ "Test User",
+ );
+ });
+
+ it("should render profile card with not following state", () => {
+ const profile = {
+ ...mockProfile,
+ viewer: { following: false, followedBy: false },
+ };
+ const result = profileCardTemplate({
+ profile,
+ onClickFollow: () => {},
+ });
+ const container = document.createElement("div");
+ render(result, container);
+ assertEquals(
+ container
+ .querySelector("[data-testid='follow-button']")
+ .textContent.trim(),
+ "+ Follow",
+ );
});
it("should render profile card with following state", () => {
@@ -36,7 +63,31 @@ t.describe("profileCardTemplate", (it) => {
profile,
onClickFollow: () => {},
});
- assert(result instanceof Object);
+ const container = document.createElement("div");
+ render(result, container);
+ assertEquals(
+ container
+ .querySelector("[data-testid='follow-button']")
+ .textContent.trim(),
+ "Following",
+ );
+ });
+
+ it("should not render followedBy indicator when not followed by", () => {
+ const profile = {
+ ...mockProfile,
+ viewer: { following: false, followedBy: false },
+ };
+ const result = profileCardTemplate({
+ profile,
+ onClickFollow: () => {},
+ });
+ const container = document.createElement("div");
+ render(result, container);
+ assertEquals(
+ container.querySelector("[data-testid='follows-you-badge']"),
+ null,
+ );
});
it("should render profile card with followedBy indicator", () => {
@@ -48,7 +99,61 @@ t.describe("profileCardTemplate", (it) => {
profile,
onClickFollow: () => {},
});
- assert(result instanceof Object);
+ const container = document.createElement("div");
+ render(result, container);
+ assert(
+ container.querySelector("[data-testid='follows-you-badge']") !== null,
+ );
+ });
+
+ it("should call onClickFollow when follow button clicked", () => {
+ let followCallArgs = null;
+ const profile = {
+ ...mockProfile,
+ viewer: { following: false, followedBy: false },
+ };
+ const onClickFollow = (p, shouldFollow) => {
+ followCallArgs = { profile: p, shouldFollow };
+ };
+ const result = profileCardTemplate({
+ profile,
+ isAuthenticated: true,
+ onClickFollow,
+ });
+ const container = document.createElement("div");
+ render(result, container);
+ const followButton = container.querySelector(
+ "[data-testid='follow-button']",
+ );
+ followButton.click();
+ assert(followCallArgs !== null);
+ assertEquals(followCallArgs.profile, profile);
+ assertEquals(followCallArgs.shouldFollow, true);
+ });
+
+ it("should call onClickFollow with false when unfollow button clicked", () => {
+ let followCallArgs = null;
+ const profile = {
+ ...mockProfile,
+ viewer: { following: true, followedBy: false },
+ };
+ const onClickFollow = (p, shouldFollow) => {
+ followCallArgs = { profile: p, shouldFollow };
+ };
+ const result = profileCardTemplate({
+ profile,
+ isAuthenticated: true,
+ onClickFollow,
+ });
+ const container = document.createElement("div");
+ render(result, container);
+ const followButton = container.querySelector(
+ "[data-testid='follow-button']",
+ );
+ followButton.click();
+ assert(followCallArgs !== null);
+ assertEquals(followCallArgs.profile, profile);
+ assertEquals(followCallArgs.shouldFollow, false);
});
});
@@ -61,11 +166,19 @@ t.describe("profileCardTemplate - labeler support", (it) => {
const result = profileCardTemplate({
profile,
isLabeler: true,
+ showSubscribeButton: true,
isSubscribed: false,
isAuthenticated: true,
onClickSubscribe: () => {},
});
- assert(result instanceof Object);
+ const container = document.createElement("div");
+ render(result, container);
+ assertEquals(
+ container
+ .querySelector("[data-testid='subscribe-button']")
+ .textContent.trim(),
+ "+ Subscribe",
+ );
});
it("should render subscribed button for labeler profile when subscribed", () => {
@@ -76,11 +189,19 @@ t.describe("profileCardTemplate - labeler support", (it) => {
const result = profileCardTemplate({
profile,
isLabeler: true,
+ showSubscribeButton: true,
isSubscribed: true,
isAuthenticated: true,
onClickSubscribe: () => {},
});
- assert(result instanceof Object);
+ const container = document.createElement("div");
+ render(result, container);
+ assertEquals(
+ container
+ .querySelector("[data-testid='subscribe-button']")
+ .textContent.trim(),
+ "Subscribed",
+ );
});
it("should render follow button for labeler in context menu", () => {
@@ -97,7 +218,35 @@ t.describe("profileCardTemplate - labeler support", (it) => {
onClickFollow: () => {},
onClickSubscribe: () => {},
});
- assert(result instanceof Object);
+ const container = document.createElement("div");
+ render(result, container);
+ assert(
+ container.querySelector("[data-testid='context-menu-follow']") !== null,
+ );
+ });
+
+ it("should render unfollow button for labeler in context menu when following", () => {
+ const profile = {
+ ...mockProfile,
+ viewer: { following: true, followedBy: false },
+ };
+ const result = profileCardTemplate({
+ profile,
+ isLabeler: true,
+ isSubscribed: false,
+ isAuthenticated: true,
+ isCurrentUser: false,
+ onClickFollow: () => {},
+ onClickSubscribe: () => {},
+ });
+ const container = document.createElement("div");
+ render(result, container);
+ assertEquals(
+ container
+ .querySelector("[data-testid='context-menu-follow']")
+ .textContent.trim(),
+ "Unfollow account",
+ );
});
it("should call onClickSubscribe when subscribe button clicked for labeler", () => {
@@ -112,13 +261,20 @@ t.describe("profileCardTemplate - labeler support", (it) => {
const result = profileCardTemplate({
profile,
isLabeler: true,
+ showSubscribeButton: true,
isSubscribed: false,
isAuthenticated: true,
onClickSubscribe,
});
- // Template rendered successfully with callback
- assert(result instanceof Object);
- assert(subscribeCallArgs === null); // Not called until button is clicked
+ const container = document.createElement("div");
+ render(result, container);
+ const subscribeButton = container.querySelector(
+ "[data-testid='subscribe-button']",
+ );
+ subscribeButton.click();
+ assert(subscribeCallArgs !== null);
+ assertEquals(subscribeCallArgs.profile, profile);
+ assertEquals(subscribeCallArgs.shouldSubscribe, true);
});
});
@@ -134,7 +290,9 @@ t.describe("profileCardTemplate - blocked profile", (it) => {
isCurrentUser: false,
onClickBlock: () => {},
});
- assert(result instanceof Object);
+ const container = document.createElement("div");
+ render(result, container);
+ assert(container.querySelector("[data-testid='unblock-button']") !== null);
});
it("should show blocked badge and hide stats for blocked profile", () => {
@@ -148,7 +306,32 @@ t.describe("profileCardTemplate - blocked profile", (it) => {
isCurrentUser: false,
onClickBlock: () => {},
});
- assert(result instanceof Object);
+ const container = document.createElement("div");
+ render(result, container);
+ assert(container.querySelector("[data-testid='blocked-badge']") !== null);
+ assertEquals(
+ container.querySelector("[data-testid='profile-stats']"),
+ null,
+ );
+ });
+
+ it("should hide followedBy badge for blocked profile", () => {
+ const profile = {
+ ...mockProfile,
+ viewer: { following: false, followedBy: true, blocking: "block-uri" },
+ };
+ const result = profileCardTemplate({
+ profile,
+ isAuthenticated: true,
+ isCurrentUser: false,
+ onClickBlock: () => {},
+ });
+ const container = document.createElement("div");
+ render(result, container);
+ assertEquals(
+ container.querySelector("[data-testid='follows-you-badge']"),
+ null,
+ );
});
});
@@ -163,7 +346,9 @@ t.describe("profileCardTemplate - authentication states", (it) => {
isAuthenticated: false,
isCurrentUser: false,
});
- assert(result instanceof Object);
+ const container = document.createElement("div");
+ render(result, container);
+ assertEquals(container.querySelector("[data-testid='chat-button']"), null);
});
it("should not render interaction buttons for current user", () => {
@@ -176,7 +361,45 @@ t.describe("profileCardTemplate - authentication states", (it) => {
isAuthenticated: true,
isCurrentUser: true,
});
- assert(result instanceof Object);
+ const container = document.createElement("div");
+ render(result, container);
+ assertEquals(container.querySelector("[data-testid='chat-button']"), null);
+ assertEquals(
+ container.querySelector("[data-testid='follow-button']"),
+ null,
+ );
+ });
+
+ it("should render chat button for authenticated user viewing other profile", () => {
+ const profile = {
+ ...mockProfile,
+ viewer: { following: false, followedBy: false },
+ };
+ const result = profileCardTemplate({
+ profile,
+ isAuthenticated: true,
+ isCurrentUser: false,
+ onClickChat: () => {},
+ });
+ const container = document.createElement("div");
+ render(result, container);
+ assert(container.querySelector("[data-testid='chat-button']") !== null);
+ });
+
+ it("should render stats for non-blocked profile", () => {
+ const profile = {
+ ...mockProfile,
+ viewer: { following: false, followedBy: false },
+ };
+ const result = profileCardTemplate({
+ profile,
+ isAuthenticated: true,
+ isCurrentUser: false,
+ onClickFollow: () => {},
+ });
+ const container = document.createElement("div");
+ render(result, container);
+ assert(container.querySelector("[data-testid='profile-stats']") !== null);
});
});
@@ -200,12 +423,20 @@ t.describe("profileCardTemplate - labelerInfo parameter", (it) => {
const result = profileCardTemplate({
profile,
isLabeler: true,
+ showSubscribeButton: true,
isSubscribed: true,
isAuthenticated: true,
labelerInfo: mockLabelerInfo,
onClickSubscribe: () => {},
});
- assert(result instanceof Object);
+ const container = document.createElement("div");
+ render(result, container);
+ assertEquals(
+ container
+ .querySelector("[data-testid='subscribe-button']")
+ .textContent.trim(),
+ "Subscribed",
+ );
});
it("should render labeler profile without labelerInfo", () => {
@@ -216,12 +447,20 @@ t.describe("profileCardTemplate - labelerInfo parameter", (it) => {
const result = profileCardTemplate({
profile,
isLabeler: true,
+ showSubscribeButton: true,
isSubscribed: false,
isAuthenticated: true,
labelerInfo: null,
onClickSubscribe: () => {},
});
- assert(result instanceof Object);
+ const container = document.createElement("div");
+ render(result, container);
+ assertEquals(
+ container
+ .querySelector("[data-testid='subscribe-button']")
+ .textContent.trim(),
+ "+ Subscribe",
+ );
});
it("should render non-labeler profile with labelerInfo set to null", () => {
@@ -235,7 +474,14 @@ t.describe("profileCardTemplate - labelerInfo parameter", (it) => {
labelerInfo: null,
onClickFollow: () => {},
});
- assert(result instanceof Object);
+ const container = document.createElement("div");
+ render(result, container);
+ assertEquals(
+ container
+ .querySelector("[data-testid='follow-button']")
+ .textContent.trim(),
+ "+ Follow",
+ );
});
it("should render labeler profile with empty policies", () => {
@@ -250,12 +496,20 @@ t.describe("profileCardTemplate - labelerInfo parameter", (it) => {
const result = profileCardTemplate({
profile,
isLabeler: true,
+ showSubscribeButton: true,
isSubscribed: true,
isAuthenticated: true,
labelerInfo: emptyLabelerInfo,
onClickSubscribe: () => {},
});
- assert(result instanceof Object);
+ const container = document.createElement("div");
+ render(result, container);
+ assertEquals(
+ container
+ .querySelector("[data-testid='subscribe-button']")
+ .textContent.trim(),
+ "Subscribed",
+ );
});
});
diff --git a/tests/specs/templates/richText.template.test.js b/tests/specs/templates/richText.template.test.js
index b81ee948..2ec2773b 100644
--- a/tests/specs/templates/richText.template.test.js
+++ b/tests/specs/templates/richText.template.test.js
@@ -1,6 +1,7 @@
import { TestSuite } from "../../testSuite.js";
-import { assert } from "../../testHelpers.js";
+import { assert, assertEquals } from "../../testHelpers.js";
import { richTextTemplate } from "/js/templates/richText.template.js";
+import { render } from "/js/lib/lit-html.js";
const t = new TestSuite("richTextTemplate");
@@ -10,7 +11,11 @@ t.describe("richTextTemplate", (it) => {
text: "Hello world",
facets: [],
});
- assert(result instanceof Object);
+ const container = document.createElement("div");
+ render(result, container);
+ const richText = container.querySelector("[data-testid='rich-text']");
+ assert(richText !== null);
+ assert(richText.textContent.includes("Hello world"));
});
it("should render text with link facet", () => {
@@ -27,7 +32,12 @@ t.describe("richTextTemplate", (it) => {
},
];
const result = richTextTemplate({ text, facets });
- assert(result instanceof Object);
+ const container = document.createElement("div");
+ render(result, container);
+ const link = container.querySelector("a");
+ assert(link !== null);
+ assert(link.getAttribute("href").startsWith("https://example.com"));
+ assertEquals(link.textContent, "example.com");
});
it("should render text with mention facet", () => {
@@ -44,7 +54,12 @@ t.describe("richTextTemplate", (it) => {
},
];
const result = richTextTemplate({ text, facets });
- assert(result instanceof Object);
+ const container = document.createElement("div");
+ render(result, container);
+ const link = container.querySelector("a");
+ assert(link !== null);
+ assert(link.getAttribute("href").includes("did:plc:123"));
+ assertEquals(link.textContent, "@user");
});
it("should render text with tag facet", () => {
@@ -61,7 +76,12 @@ t.describe("richTextTemplate", (it) => {
},
];
const result = richTextTemplate({ text, facets });
- assert(result instanceof Object);
+ const container = document.createElement("div");
+ render(result, container);
+ const link = container.querySelector("a");
+ assert(link !== null);
+ assert(link.getAttribute("href").includes("world"));
+ assertEquals(link.textContent, "#world");
});
it("should render text with multiple facets", () => {
@@ -87,7 +107,29 @@ t.describe("richTextTemplate", (it) => {
},
];
const result = richTextTemplate({ text, facets });
- assert(result instanceof Object);
+ const container = document.createElement("div");
+ render(result, container);
+ const links = container.querySelectorAll("a");
+ assertEquals(links.length, 2);
+ });
+
+ it("should render multiline text with separate divs", () => {
+ const text = "Line one\nLine two\nLine three";
+ const result = richTextTemplate({ text, facets: [] });
+ const container = document.createElement("div");
+ render(result, container);
+ const richText = container.querySelector("[data-testid='rich-text']");
+ const divs = richText.querySelectorAll("div");
+ assertEquals(divs.length, 3);
+ });
+
+ it("should render empty line as br element", () => {
+ const text = "Line one\n\nLine three";
+ const result = richTextTemplate({ text, facets: [] });
+ const container = document.createElement("div");
+ render(result, container);
+ const br = container.querySelector("br");
+ assert(br !== null);
});
});
diff --git a/tests/specs/templates/smallPost.template.test.js b/tests/specs/templates/smallPost.template.test.js
index c8644243..e9c0ee98 100644
--- a/tests/specs/templates/smallPost.template.test.js
+++ b/tests/specs/templates/smallPost.template.test.js
@@ -1,7 +1,8 @@
import { TestSuite } from "../../testSuite.js";
-import { assert } from "../../testHelpers.js";
+import { assert, assertEquals } from "../../testHelpers.js";
import { smallPostTemplate } from "/js/templates/smallPost.template.js";
import { post } from "../../fixtures.js";
+import { render } from "/js/lib/lit-html.js";
const noop = () => {};
const postInteractionHandler = {
@@ -10,6 +11,7 @@ const postInteractionHandler = {
handleRepost: noop,
handleQuotePost: noop,
handleBookmark: noop,
+ handleHidePost: noop,
handleMuteAuthor: noop,
handleBlockAuthor: noop,
handleDeletePost: noop,
@@ -19,12 +21,242 @@ const postInteractionHandler = {
const t = new TestSuite("smallPostTemplate");
t.describe("smallPostTemplate", (it) => {
- it("should render the post", () => {
+ it("should render the post container", () => {
const result = smallPostTemplate({
post: post,
postInteractionHandler,
});
- assert(result instanceof Object);
+ const container = document.createElement("div");
+ render(result, container);
+ assert(container.querySelector("[data-testid='small-post']") !== null);
+ });
+
+ it("should render post with avatar", () => {
+ const result = smallPostTemplate({
+ post: post,
+ postInteractionHandler,
+ });
+ const container = document.createElement("div");
+ render(result, container);
+ assert(container.querySelector("[data-testid='avatar']") !== null);
+ });
+
+ it("should render post with author name", () => {
+ const result = smallPostTemplate({
+ post: post,
+ postInteractionHandler,
+ });
+ const container = document.createElement("div");
+ render(result, container);
+ assert(
+ container.querySelector("[data-testid='post-author-name']") !== null,
+ );
+ });
+
+ it("should render post text content", () => {
+ const postWithText = {
+ ...post,
+ record: { ...post.record, text: "Hello small world!" },
+ };
+ const result = smallPostTemplate({
+ post: postWithText,
+ postInteractionHandler,
+ });
+ const container = document.createElement("div");
+ render(result, container);
+ assert(container.textContent.includes("Hello small world!"));
+ });
+
+ it("should render post action bar", () => {
+ const result = smallPostTemplate({
+ post: post,
+ postInteractionHandler,
+ });
+ const container = document.createElement("div");
+ render(result, container);
+ assert(container.querySelector("[data-testid='reply-button']") !== null);
+ assert(container.querySelector("[data-testid='repost-button']") !== null);
+ assert(container.querySelector("[data-testid='bookmark-button']") !== null);
+ });
+});
+
+t.describe("smallPostTemplate - pinned posts", (it) => {
+ it("should show pinned label when isPinned is true", () => {
+ const result = smallPostTemplate({
+ post: post,
+ postInteractionHandler,
+ isPinned: true,
+ });
+ const container = document.createElement("div");
+ render(result, container);
+ assert(container.querySelector("[data-testid='pinned-label']") !== null);
+ });
+
+ it("should not show pinned label when isPinned is false", () => {
+ const result = smallPostTemplate({
+ post: post,
+ postInteractionHandler,
+ isPinned: false,
+ });
+ const container = document.createElement("div");
+ render(result, container);
+ assertEquals(container.querySelector("[data-testid='pinned-label']"), null);
+ });
+});
+
+t.describe("smallPostTemplate - reposts", (it) => {
+ it("should show repost label when repostAuthor is provided", () => {
+ const result = smallPostTemplate({
+ post: post,
+ postInteractionHandler,
+ repostAuthor: {
+ displayName: "Reposter Name",
+ handle: "reposter.bsky.social",
+ },
+ });
+ const container = document.createElement("div");
+ render(result, container);
+ const repostLabel = container.querySelector("[data-testid='repost-label']");
+ assert(repostLabel !== null);
+ assert(repostLabel.textContent.includes("Reposted by"));
+ });
+
+ it("should not show repost label when no repostAuthor", () => {
+ const result = smallPostTemplate({
+ post: post,
+ postInteractionHandler,
+ });
+ const container = document.createElement("div");
+ render(result, container);
+ assertEquals(container.querySelector("[data-testid='repost-label']"), null);
+ });
+});
+
+t.describe("smallPostTemplate - reply context", (it) => {
+ it("should render reply context line-in when replyContext is parent", () => {
+ const result = smallPostTemplate({
+ post: post,
+ postInteractionHandler,
+ replyContext: "parent",
+ });
+ const container = document.createElement("div");
+ render(result, container);
+ assert(container.querySelector(".reply-context-line-in") !== null);
+ });
+
+ it("should render reply context line-out when replyContext is root", () => {
+ const result = smallPostTemplate({
+ post: post,
+ postInteractionHandler,
+ replyContext: "root",
+ });
+ const container = document.createElement("div");
+ render(result, container);
+ assert(container.querySelector(".reply-context-line-out") !== null);
+ });
+
+ it("should render both lines when replyContext is parent", () => {
+ const result = smallPostTemplate({
+ post: post,
+ postInteractionHandler,
+ replyContext: "parent",
+ });
+ const container = document.createElement("div");
+ render(result, container);
+ assert(container.querySelector(".reply-context-line-in") !== null);
+ assert(container.querySelector(".reply-context-line-out") !== null);
+ });
+});
+
+t.describe("smallPostTemplate - blocked/unavailable posts", (it) => {
+ it("should render blocked post template for blocked post", () => {
+ const blockedPost = {
+ $type: "app.bsky.feed.defs#blockedPost",
+ uri: "blocked-uri",
+ blocked: true,
+ };
+ const result = smallPostTemplate({
+ post: blockedPost,
+ postInteractionHandler,
+ });
+ const container = document.createElement("div");
+ render(result, container);
+ assert(container.textContent.includes("unavailable"));
+ });
+
+ it("should render not found post template for not found post", () => {
+ const notFoundPost = {
+ $type: "app.bsky.feed.defs#notFoundPost",
+ uri: "not-found-uri",
+ notFound: true,
+ };
+ const result = smallPostTemplate({
+ post: notFoundPost,
+ postInteractionHandler,
+ });
+ const container = document.createElement("div");
+ render(result, container);
+ assert(container.textContent.includes("deleted"));
+ });
+});
+
+t.describe("smallPostTemplate - moderation", (it) => {
+ it("should wrap in muted-reply-toggle for muted account when hideMutedAccount is true", () => {
+ const mutedAccountPost = {
+ ...post,
+ author: { ...post.author, viewer: { muted: true } },
+ };
+ const result = smallPostTemplate({
+ post: mutedAccountPost,
+ postInteractionHandler,
+ hideMutedAccount: true,
+ });
+ const container = document.createElement("div");
+ render(result, container);
+ assert(container.querySelector("muted-reply-toggle") !== null);
+ });
+
+ it("should wrap in muted-reply-toggle for post with muted word", () => {
+ const mutedWordPost = {
+ ...post,
+ viewer: { ...post.viewer, hasMutedWord: true },
+ };
+ const result = smallPostTemplate({
+ post: mutedWordPost,
+ postInteractionHandler,
+ });
+ const container = document.createElement("div");
+ render(result, container);
+ assert(container.querySelector("muted-reply-toggle") !== null);
+ });
+
+ it("should wrap in muted-reply-toggle for hidden post", () => {
+ const hiddenPost = {
+ ...post,
+ viewer: { ...post.viewer, isHidden: true },
+ };
+ const result = smallPostTemplate({
+ post: hiddenPost,
+ postInteractionHandler,
+ });
+ const container = document.createElement("div");
+ render(result, container);
+ assert(container.querySelector("muted-reply-toggle") !== null);
+ });
+
+ it("should not wrap in muted-reply-toggle for normal post", () => {
+ const normalPost = {
+ ...post,
+ viewer: { ...post.viewer, hasMutedWord: false, isHidden: false },
+ author: { ...post.author, viewer: { muted: false } },
+ };
+ const result = smallPostTemplate({
+ post: normalPost,
+ postInteractionHandler,
+ });
+ const container = document.createElement("div");
+ render(result, container);
+ assertEquals(container.querySelector("muted-reply-toggle"), null);
});
});