Update storybook and tests
This commit is contained in:
@@ -14,10 +14,14 @@ export default function ContentBanner({ post }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getBannerImageMd = (post) => {
|
const getBannerImageMd = (post) => {
|
||||||
// Use banner.horizontal when provided; fallback to default banner asset
|
// Use banner.horizontal when provided; fallback to horizontal thumbnail
|
||||||
if (post.frontmatter?.banner?.horizontal) {
|
if (post.frontmatter?.banner?.horizontal) {
|
||||||
return `/content/blog/${post.frontmatter.banner.horizontal}`;
|
return `/content/blog/${post.frontmatter.banner.horizontal}`;
|
||||||
}
|
}
|
||||||
|
// Fallback to horizontal thumbnail, then default banner
|
||||||
|
if (post.frontmatter?.thumbnail?.horizontal) {
|
||||||
|
return `/content/blog/${post.frontmatter.thumbnail.horizontal}`;
|
||||||
|
}
|
||||||
return getAssetPath("assets/Content_Banner_2.svg");
|
return getAssetPath("assets/Content_Banner_2.svg");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ related:
|
|||||||
thumbnail:
|
thumbnail:
|
||||||
vertical: "resolving-active-conflicts-vertical.svg"
|
vertical: "resolving-active-conflicts-vertical.svg"
|
||||||
horizontal: "resolving-active-conflicts-horizontal.svg"
|
horizontal: "resolving-active-conflicts-horizontal.svg"
|
||||||
|
banner:
|
||||||
|
horizontal: "resolving-active-conflicts-banner.svg"
|
||||||
background:
|
background:
|
||||||
color: "#E2EFFF"
|
color: "#E2EFFF"
|
||||||
---
|
---
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 82 KiB |
@@ -8,6 +8,13 @@ const mockBlogPost = {
|
|||||||
"This is a sample article description that explains what the article covers.",
|
"This is a sample article description that explains what the article covers.",
|
||||||
author: "Sample Author",
|
author: "Sample Author",
|
||||||
date: "2025-01-15",
|
date: "2025-01-15",
|
||||||
|
thumbnail: {
|
||||||
|
horizontal: "resolving-active-conflicts-horizontal.svg",
|
||||||
|
vertical: "resolving-active-conflicts-vertical.svg",
|
||||||
|
},
|
||||||
|
banner: {
|
||||||
|
horizontal: "resolving-active-conflicts-banner.svg",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
htmlContent:
|
htmlContent:
|
||||||
"<p>This is the main content of the sample article.</p><p>It has multiple paragraphs.</p>",
|
"<p>This is the main content of the sample article.</p><p>It has multiple paragraphs.</p>",
|
||||||
@@ -20,7 +27,7 @@ export default {
|
|||||||
docs: {
|
docs: {
|
||||||
description: {
|
description: {
|
||||||
component:
|
component:
|
||||||
"The ContentBanner component displays the header information for blog articles, including title, description, author, and date. Note: page background colors are applied at the blog page level using a hex color from frontmatter (background.color), not inside this component. Thumbnail images should be uploaded via the content pipeline to public/content/blog/ and referenced in frontmatter (thumbnail.horizontal / thumbnail.vertical).",
|
"The ContentBanner component displays the header information for blog articles, including title, description, author, and date.\n\nImages: sm uses thumbnail.horizontal; md+ uses banner.horizontal when provided, otherwise falls back to thumbnail.horizontal; final fallback is assets/Content_Banner_2.svg.\n\nNote: page background colors are applied at the blog page level using a hex color from frontmatter (background.color), not inside this component. Thumbnail and banner images should be uploaded via the content pipeline to public/content/blog/ and referenced in frontmatter.",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -38,6 +45,31 @@ export const Default = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const NoBannerFallbackToThumbnail = {
|
||||||
|
args: {
|
||||||
|
post: {
|
||||||
|
...mockBlogPost,
|
||||||
|
frontmatter: {
|
||||||
|
...mockBlogPost.frontmatter,
|
||||||
|
banner: undefined,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const NoImagesFallbackToDefault = {
|
||||||
|
args: {
|
||||||
|
post: {
|
||||||
|
...mockBlogPost,
|
||||||
|
frontmatter: {
|
||||||
|
...mockBlogPost.frontmatter,
|
||||||
|
thumbnail: undefined,
|
||||||
|
banner: undefined,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export const LongTitle = {
|
export const LongTitle = {
|
||||||
args: {
|
args: {
|
||||||
post: {
|
post: {
|
||||||
|
|||||||
@@ -30,6 +30,12 @@ const mockPost = {
|
|||||||
description: "This is a test article description",
|
description: "This is a test article description",
|
||||||
author: "Test Author",
|
author: "Test Author",
|
||||||
date: "2025-04-15",
|
date: "2025-04-15",
|
||||||
|
thumbnail: {
|
||||||
|
horizontal: "test-article-horizontal.svg",
|
||||||
|
},
|
||||||
|
banner: {
|
||||||
|
horizontal: "test-article-banner.svg",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -39,7 +45,7 @@ describe("ContentBanner", () => {
|
|||||||
|
|
||||||
// Check that the banner container exists - it's the first div with the specific classes
|
// Check that the banner container exists - it's the first div with the specific classes
|
||||||
const banner = document.querySelector(
|
const banner = document.querySelector(
|
||||||
"div[class*='pt-[var(--measures-spacing-016)]']",
|
"div[class*='pt-[var(--measures-spacing-016)]']"
|
||||||
);
|
);
|
||||||
expect(banner).toBeInTheDocument();
|
expect(banner).toBeInTheDocument();
|
||||||
expect(banner).toHaveClass(
|
expect(banner).toHaveClass(
|
||||||
@@ -54,7 +60,7 @@ describe("ContentBanner", () => {
|
|||||||
"xl:h-[504px]",
|
"xl:h-[504px]",
|
||||||
"relative",
|
"relative",
|
||||||
"w-full",
|
"w-full",
|
||||||
"sm:overflow-hidden",
|
"sm:overflow-hidden"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -63,7 +69,7 @@ describe("ContentBanner", () => {
|
|||||||
|
|
||||||
// Check for background div with correct styling
|
// Check for background div with correct styling
|
||||||
const backgroundDiv = document.querySelector(
|
const backgroundDiv = document.querySelector(
|
||||||
"div[style*='background-image']",
|
"div[style*='background-image']"
|
||||||
);
|
);
|
||||||
expect(backgroundDiv).toBeInTheDocument();
|
expect(backgroundDiv).toBeInTheDocument();
|
||||||
expect(backgroundDiv).toHaveClass(
|
expect(backgroundDiv).toHaveClass(
|
||||||
@@ -73,16 +79,16 @@ describe("ContentBanner", () => {
|
|||||||
"h-full",
|
"h-full",
|
||||||
"bg-cover",
|
"bg-cover",
|
||||||
"bg-no-repeat",
|
"bg-no-repeat",
|
||||||
"aspect-[320/225.5]",
|
"aspect-[320/225.5]"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("shows different background image at md breakpoint and above", () => {
|
it("shows banner image at md breakpoint and above", () => {
|
||||||
render(<ContentBanner post={mockPost} />);
|
render(<ContentBanner post={mockPost} />);
|
||||||
|
|
||||||
// Check for the md+ background div
|
// Check for the md+ background div with banner image
|
||||||
const mdBackgroundDiv = document.querySelector(
|
const mdBackgroundDiv = document.querySelector(
|
||||||
"div[style*='Content_Banner_2.svg']",
|
"div[style*='test-article-banner.svg']"
|
||||||
);
|
);
|
||||||
expect(mdBackgroundDiv).toBeInTheDocument();
|
expect(mdBackgroundDiv).toBeInTheDocument();
|
||||||
expect(mdBackgroundDiv).toHaveClass("hidden", "md:block");
|
expect(mdBackgroundDiv).toHaveClass("hidden", "md:block");
|
||||||
@@ -98,7 +104,7 @@ describe("ContentBanner", () => {
|
|||||||
render(<ContentBanner post={mockPost} />);
|
render(<ContentBanner post={mockPost} />);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
screen.getByText("This is a test article description"),
|
screen.getByText("This is a test article description")
|
||||||
).toBeInTheDocument();
|
).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -114,7 +120,7 @@ describe("ContentBanner", () => {
|
|||||||
|
|
||||||
// Check the content container div
|
// Check the content container div
|
||||||
const contentContainer = document.querySelector(
|
const contentContainer = document.querySelector(
|
||||||
"div[class*='relative z-10']",
|
"div[class*='relative z-10']"
|
||||||
);
|
);
|
||||||
expect(contentContainer).toBeInTheDocument();
|
expect(contentContainer).toBeInTheDocument();
|
||||||
expect(contentContainer).toHaveClass(
|
expect(contentContainer).toHaveClass(
|
||||||
@@ -122,7 +128,7 @@ describe("ContentBanner", () => {
|
|||||||
"z-10",
|
"z-10",
|
||||||
"h-full",
|
"h-full",
|
||||||
"flex",
|
"flex",
|
||||||
"flex-col",
|
"flex-col"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -135,7 +141,7 @@ describe("ContentBanner", () => {
|
|||||||
"font-medium",
|
"font-medium",
|
||||||
"text-[18px]",
|
"text-[18px]",
|
||||||
"leading-[120%]",
|
"leading-[120%]",
|
||||||
"text-[var(--color-content-inverse-brand-royal)]",
|
"text-[var(--color-content-inverse-brand-royal)]"
|
||||||
);
|
);
|
||||||
|
|
||||||
const description = screen.getByText("This is a test article description");
|
const description = screen.getByText("This is a test article description");
|
||||||
@@ -144,7 +150,7 @@ describe("ContentBanner", () => {
|
|||||||
"font-normal",
|
"font-normal",
|
||||||
"text-[12px]",
|
"text-[12px]",
|
||||||
"leading-[16px]",
|
"leading-[16px]",
|
||||||
"text-[var(--color-content-inverse-brand-royal)]",
|
"text-[var(--color-content-inverse-brand-royal)]"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -157,7 +163,7 @@ describe("ContentBanner", () => {
|
|||||||
"font-normal",
|
"font-normal",
|
||||||
"text-[10px]",
|
"text-[10px]",
|
||||||
"leading-[14px]",
|
"leading-[14px]",
|
||||||
"text-[var(--color-content-inverse-brand-royal)]",
|
"text-[var(--color-content-inverse-brand-royal)]"
|
||||||
);
|
);
|
||||||
|
|
||||||
const date = screen.getByText("April 2025");
|
const date = screen.getByText("April 2025");
|
||||||
@@ -166,7 +172,7 @@ describe("ContentBanner", () => {
|
|||||||
"font-normal",
|
"font-normal",
|
||||||
"text-[10px]",
|
"text-[10px]",
|
||||||
"leading-[14px]",
|
"leading-[14px]",
|
||||||
"text-[var(--color-content-inverse-brand-royal)]",
|
"text-[var(--color-content-inverse-brand-royal)]"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -175,7 +181,7 @@ describe("ContentBanner", () => {
|
|||||||
|
|
||||||
// Check the ContentContainer spacing
|
// Check the ContentContainer spacing
|
||||||
const contentContainer = document.querySelector(
|
const contentContainer = document.querySelector(
|
||||||
"div[class*='relative z-20']",
|
"div[class*='relative z-20']"
|
||||||
);
|
);
|
||||||
expect(contentContainer).toHaveClass("gap-[var(--measures-spacing-012)]");
|
expect(contentContainer).toHaveClass("gap-[var(--measures-spacing-012)]");
|
||||||
});
|
});
|
||||||
@@ -184,13 +190,13 @@ describe("ContentBanner", () => {
|
|||||||
render(<ContentBanner post={mockPost} />);
|
render(<ContentBanner post={mockPost} />);
|
||||||
|
|
||||||
const outerContainer = document.querySelector(
|
const outerContainer = document.querySelector(
|
||||||
"div[class*='pt-[var(--measures-spacing-016)]']",
|
"div[class*='pt-[var(--measures-spacing-016)]']"
|
||||||
);
|
);
|
||||||
expect(outerContainer).toHaveClass(
|
expect(outerContainer).toHaveClass(
|
||||||
"pt-[var(--measures-spacing-016)]",
|
"pt-[var(--measures-spacing-016)]",
|
||||||
"md:pt-[var(--measures-spacing-008)]",
|
"md:pt-[var(--measures-spacing-008)]",
|
||||||
"lg:pt-[50px]",
|
"lg:pt-[50px]",
|
||||||
"xl:pt-[112px]",
|
"xl:pt-[112px]"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -208,6 +214,45 @@ describe("ContentBanner", () => {
|
|||||||
expect(screen.getByText("Incomplete Post")).toBeInTheDocument();
|
expect(screen.getByText("Incomplete Post")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("falls back to thumbnail.horizontal when banner.horizontal is missing", () => {
|
||||||
|
const postWithoutBanner = {
|
||||||
|
...mockPost,
|
||||||
|
frontmatter: {
|
||||||
|
...mockPost.frontmatter,
|
||||||
|
banner: undefined,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
render(<ContentBanner post={postWithoutBanner} />);
|
||||||
|
|
||||||
|
// Should use thumbnail.horizontal for md+ breakpoint
|
||||||
|
const mdBackgroundDiv = document.querySelector(
|
||||||
|
"div[style*='test-article-horizontal.svg']"
|
||||||
|
);
|
||||||
|
expect(mdBackgroundDiv).toBeInTheDocument();
|
||||||
|
expect(mdBackgroundDiv).toHaveClass("hidden", "md:block");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("falls back to default banner when no images are provided", () => {
|
||||||
|
const postWithoutImages = {
|
||||||
|
...mockPost,
|
||||||
|
frontmatter: {
|
||||||
|
...mockPost.frontmatter,
|
||||||
|
thumbnail: undefined,
|
||||||
|
banner: undefined,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
render(<ContentBanner post={postWithoutImages} />);
|
||||||
|
|
||||||
|
// Should use default banner for md+ breakpoint
|
||||||
|
const mdBackgroundDiv = document.querySelector(
|
||||||
|
"div[style*='Content_Banner_2.svg']"
|
||||||
|
);
|
||||||
|
expect(mdBackgroundDiv).toBeInTheDocument();
|
||||||
|
expect(mdBackgroundDiv).toHaveClass("hidden", "md:block");
|
||||||
|
});
|
||||||
|
|
||||||
it("applies responsive text sizing", () => {
|
it("applies responsive text sizing", () => {
|
||||||
render(<ContentBanner post={mockPost} />);
|
render(<ContentBanner post={mockPost} />);
|
||||||
|
|
||||||
@@ -216,7 +261,7 @@ describe("ContentBanner", () => {
|
|||||||
"sm:text-[24px]",
|
"sm:text-[24px]",
|
||||||
"md:text-[32px]",
|
"md:text-[32px]",
|
||||||
"lg:text-[44px]",
|
"lg:text-[44px]",
|
||||||
"xl:text-[64px]",
|
"xl:text-[64px]"
|
||||||
);
|
);
|
||||||
|
|
||||||
const description = screen.getByText("This is a test article description");
|
const description = screen.getByText("This is a test article description");
|
||||||
@@ -224,7 +269,7 @@ describe("ContentBanner", () => {
|
|||||||
"sm:text-[14px]",
|
"sm:text-[14px]",
|
||||||
"md:text-[14px]",
|
"md:text-[14px]",
|
||||||
"lg:text-[18px]",
|
"lg:text-[18px]",
|
||||||
"xl:text-[24px]",
|
"xl:text-[24px]"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -28,21 +28,24 @@ const mockPost = {
|
|||||||
"This is a test description for the blog post that should be long enough to test truncation.",
|
"This is a test description for the blog post that should be long enough to test truncation.",
|
||||||
author: "Test Author",
|
author: "Test Author",
|
||||||
date: "2025-04-15",
|
date: "2025-04-15",
|
||||||
backgroundImages: ["/test-image-1.jpg", "/test-image-2.jpg"],
|
thumbnail: {
|
||||||
|
vertical: "test-post-vertical.svg",
|
||||||
|
horizontal: "test-post-horizontal.svg",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
describe("ContentThumbnailTemplate", () => {
|
describe("ContentThumbnailTemplate", () => {
|
||||||
describe("Vertical Variant", () => {
|
describe("Vertical Variant", () => {
|
||||||
it("should render vertical variant with correct dimensions", () => {
|
it("should render vertical variant with responsive dimensions", () => {
|
||||||
render(<ContentThumbnailTemplate post={mockPost} />);
|
render(<ContentThumbnailTemplate post={mockPost} />);
|
||||||
|
|
||||||
const container = screen.getByRole("link");
|
const container = screen.getByRole("link");
|
||||||
expect(container).toBeInTheDocument();
|
expect(container).toBeInTheDocument();
|
||||||
|
|
||||||
// Check that the component has the correct classes for dimensions
|
// Check that the component has the correct classes for responsive dimensions
|
||||||
const thumbnailDiv = container.querySelector("div");
|
const thumbnailDiv = container.querySelector("div");
|
||||||
expect(thumbnailDiv).toHaveClass("w-[260px]", "h-[390px]");
|
expect(thumbnailDiv).toHaveClass("w-full", "aspect-[2/3]");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should display post title and description", () => {
|
it("should display post title and description", () => {
|
||||||
@@ -50,7 +53,7 @@ describe("ContentThumbnailTemplate", () => {
|
|||||||
|
|
||||||
expect(screen.getByText("Test Blog Post Title")).toBeInTheDocument();
|
expect(screen.getByText("Test Blog Post Title")).toBeInTheDocument();
|
||||||
expect(
|
expect(
|
||||||
screen.getByText(/This is a test description/),
|
screen.getByText(/This is a test description/)
|
||||||
).toBeInTheDocument();
|
).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -63,7 +66,7 @@ describe("ContentThumbnailTemplate", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("Horizontal Variant", () => {
|
describe("Horizontal Variant", () => {
|
||||||
it("should render horizontal variant", () => {
|
it("should render horizontal variant with responsive sizing", () => {
|
||||||
render(<ContentThumbnailTemplate post={mockPost} variant="horizontal" />);
|
render(<ContentThumbnailTemplate post={mockPost} variant="horizontal" />);
|
||||||
|
|
||||||
const container = screen.getByRole("link");
|
const container = screen.getByRole("link");
|
||||||
@@ -71,7 +74,11 @@ describe("ContentThumbnailTemplate", () => {
|
|||||||
|
|
||||||
// Check that the component has the correct classes for horizontal layout
|
// Check that the component has the correct classes for horizontal layout
|
||||||
const thumbnailDiv = container.querySelector("div");
|
const thumbnailDiv = container.querySelector("div");
|
||||||
expect(thumbnailDiv).toHaveClass("h-[225.5px]");
|
expect(thumbnailDiv).toHaveClass(
|
||||||
|
"min-w-[320px]",
|
||||||
|
"max-w-[800px]",
|
||||||
|
"h-[225.5px]"
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should display post information in horizontal layout", () => {
|
it("should display post information in horizontal layout", () => {
|
||||||
@@ -79,7 +86,7 @@ describe("ContentThumbnailTemplate", () => {
|
|||||||
|
|
||||||
expect(screen.getByText("Test Blog Post Title")).toBeInTheDocument();
|
expect(screen.getByText("Test Blog Post Title")).toBeInTheDocument();
|
||||||
expect(
|
expect(
|
||||||
screen.getByText(/This is a test description/),
|
screen.getByText(/This is a test description/)
|
||||||
).toBeInTheDocument();
|
).toBeInTheDocument();
|
||||||
expect(screen.getByText("Test Author")).toBeInTheDocument();
|
expect(screen.getByText("Test Author")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
@@ -88,7 +95,7 @@ describe("ContentThumbnailTemplate", () => {
|
|||||||
describe("Props and Customization", () => {
|
describe("Props and Customization", () => {
|
||||||
it("should apply custom className", () => {
|
it("should apply custom className", () => {
|
||||||
render(
|
render(
|
||||||
<ContentThumbnailTemplate post={mockPost} className="custom-class" />,
|
<ContentThumbnailTemplate post={mockPost} className="custom-class" />
|
||||||
);
|
);
|
||||||
|
|
||||||
const container = screen.getByRole("link");
|
const container = screen.getByRole("link");
|
||||||
@@ -117,12 +124,12 @@ describe("ContentThumbnailTemplate", () => {
|
|||||||
expect(screen.getByText("Test Blog Post Title")).toBeInTheDocument();
|
expect(screen.getByText("Test Blog Post Title")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should handle posts without background images", () => {
|
it("should handle posts without thumbnail images", () => {
|
||||||
const postWithoutImages = {
|
const postWithoutImages = {
|
||||||
...mockPost,
|
...mockPost,
|
||||||
frontmatter: {
|
frontmatter: {
|
||||||
...mockPost.frontmatter,
|
...mockPost.frontmatter,
|
||||||
backgroundImages: undefined,
|
thumbnail: undefined,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -131,6 +138,28 @@ describe("ContentThumbnailTemplate", () => {
|
|||||||
// Should still render without errors
|
// Should still render without errors
|
||||||
expect(screen.getByText("Test Blog Post Title")).toBeInTheDocument();
|
expect(screen.getByText("Test Blog Post Title")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should use article-specific thumbnail images when provided", () => {
|
||||||
|
render(<ContentThumbnailTemplate post={mockPost} />);
|
||||||
|
|
||||||
|
// Check that the background image uses the article-specific thumbnail
|
||||||
|
const backgroundImg = document.querySelector(
|
||||||
|
"img[alt*='Background for']"
|
||||||
|
);
|
||||||
|
expect(backgroundImg).toBeInTheDocument();
|
||||||
|
expect(backgroundImg.src).toContain("test-post-vertical.svg");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should use article-specific horizontal images for horizontal variant", () => {
|
||||||
|
render(<ContentThumbnailTemplate post={mockPost} variant="horizontal" />);
|
||||||
|
|
||||||
|
// Check that the background image uses the article-specific horizontal thumbnail
|
||||||
|
const backgroundImg = document.querySelector(
|
||||||
|
"img[alt*='Background for']"
|
||||||
|
);
|
||||||
|
expect(backgroundImg).toBeInTheDocument();
|
||||||
|
expect(backgroundImg.src).toContain("test-post-horizontal.svg");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Default Behavior", () => {
|
describe("Default Behavior", () => {
|
||||||
@@ -138,7 +167,7 @@ describe("ContentThumbnailTemplate", () => {
|
|||||||
render(<ContentThumbnailTemplate post={mockPost} />);
|
render(<ContentThumbnailTemplate post={mockPost} />);
|
||||||
|
|
||||||
const thumbnailDiv = screen.getByRole("link").querySelector("div");
|
const thumbnailDiv = screen.getByRole("link").querySelector("div");
|
||||||
expect(thumbnailDiv).toHaveClass("w-[260px]", "h-[390px]");
|
expect(thumbnailDiv).toHaveClass("w-full", "aspect-[2/3]");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should show metadata by default", () => {
|
it("should show metadata by default", () => {
|
||||||
|
|||||||
+34
-43
@@ -8,58 +8,55 @@ import Header, {
|
|||||||
} from "../../app/components/Header.js";
|
} from "../../app/components/Header.js";
|
||||||
|
|
||||||
describe("Header", () => {
|
describe("Header", () => {
|
||||||
const mockOnToggle = vi.fn();
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockOnToggle.mockClear();
|
|
||||||
// Clear any existing rendered content
|
// Clear any existing rendered content
|
||||||
document.body.innerHTML = "";
|
document.body.innerHTML = "";
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Accessibility and Landmarks", () => {
|
describe("Accessibility and Landmarks", () => {
|
||||||
test("renders header with correct structure and accessibility attributes", () => {
|
test("renders header with correct structure and accessibility attributes", () => {
|
||||||
const { container } = render(<Header onToggle={mockOnToggle} />);
|
const { container } = render(<Header />);
|
||||||
|
|
||||||
// Check main header structure - use container to scope the search
|
// Check main header structure - use container to scope the search
|
||||||
const header = container.querySelector(
|
const header = container.querySelector(
|
||||||
'[role="banner"][aria-label="Main navigation header"]',
|
'[role="banner"][aria-label="Main navigation header"]'
|
||||||
);
|
);
|
||||||
expect(header).toBeInTheDocument();
|
expect(header).toBeInTheDocument();
|
||||||
expect(header).toHaveAttribute("aria-label", "Main navigation header");
|
expect(header).toHaveAttribute("aria-label", "Main navigation header");
|
||||||
|
|
||||||
// Check navigation - use container to scope the search
|
// Check navigation - use container to scope the search
|
||||||
const nav = container.querySelector(
|
const nav = container.querySelector(
|
||||||
'[role="navigation"][aria-label="Main navigation"]',
|
'[role="navigation"][aria-label="Main navigation"]'
|
||||||
);
|
);
|
||||||
expect(nav).toBeInTheDocument();
|
expect(nav).toBeInTheDocument();
|
||||||
expect(nav).toHaveAttribute("aria-label", "Main navigation");
|
expect(nav).toHaveAttribute("aria-label", "Main navigation");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("renders all navigation items with proper accessibility", () => {
|
test("renders all navigation items with proper accessibility", () => {
|
||||||
render(<Header onToggle={mockOnToggle} />);
|
render(<Header />);
|
||||||
|
|
||||||
// Check all navigation items have proper aria-labels - use menuitem role since they're in a menubar
|
// Check all navigation items have proper aria-labels - use menuitem role since they're in a menubar
|
||||||
expect(
|
expect(
|
||||||
screen.getAllByRole("menuitem", { name: "Navigate to Use cases page" })
|
screen.getAllByRole("menuitem", { name: "Navigate to Use cases page" })
|
||||||
.length,
|
.length
|
||||||
).toBeGreaterThan(0);
|
).toBeGreaterThan(0);
|
||||||
expect(
|
expect(
|
||||||
screen.getAllByRole("menuitem", { name: "Navigate to Learn page" })
|
screen.getAllByRole("menuitem", { name: "Navigate to Learn page" })
|
||||||
.length,
|
.length
|
||||||
).toBeGreaterThan(0);
|
).toBeGreaterThan(0);
|
||||||
expect(
|
expect(
|
||||||
screen.getAllByRole("menuitem", { name: "Navigate to About page" })
|
screen.getAllByRole("menuitem", { name: "Navigate to About page" })
|
||||||
.length,
|
.length
|
||||||
).toBeGreaterThan(0);
|
).toBeGreaterThan(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Schema Markup", () => {
|
describe("Schema Markup", () => {
|
||||||
test("renders schema markup for site navigation", () => {
|
test("renders schema markup for site navigation", () => {
|
||||||
render(<Header onToggle={mockOnToggle} />);
|
render(<Header />);
|
||||||
|
|
||||||
const script = document.querySelector(
|
const script = document.querySelector(
|
||||||
'script[type="application/ld+json"]',
|
'script[type="application/ld+json"]'
|
||||||
);
|
);
|
||||||
expect(script).toBeInTheDocument();
|
expect(script).toBeInTheDocument();
|
||||||
|
|
||||||
@@ -126,14 +123,14 @@ describe("Header", () => {
|
|||||||
|
|
||||||
describe("Logo Configuration", () => {
|
describe("Logo Configuration", () => {
|
||||||
test("renders correct number of logo variants", () => {
|
test("renders correct number of logo variants", () => {
|
||||||
render(<Header onToggle={mockOnToggle} />);
|
render(<Header />);
|
||||||
|
|
||||||
const logoWrappers = screen.getAllByTestId("logo-wrapper");
|
const logoWrappers = screen.getAllByTestId("logo-wrapper");
|
||||||
expect(logoWrappers).toHaveLength(logoConfig.length);
|
expect(logoWrappers).toHaveLength(logoConfig.length);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("logo wrappers include expected breakpoint classes", () => {
|
test("logo wrappers include expected breakpoint classes", () => {
|
||||||
render(<Header onToggle={mockOnToggle} />);
|
render(<Header />);
|
||||||
|
|
||||||
const logoWrappers = screen.getAllByTestId("logo-wrapper");
|
const logoWrappers = screen.getAllByTestId("logo-wrapper");
|
||||||
|
|
||||||
@@ -156,7 +153,7 @@ describe("Header", () => {
|
|||||||
|
|
||||||
describe("Navigation Structure", () => {
|
describe("Navigation Structure", () => {
|
||||||
test("renders all breakpoint-specific navigation containers", () => {
|
test("renders all breakpoint-specific navigation containers", () => {
|
||||||
render(<Header onToggle={mockOnToggle} />);
|
render(<Header />);
|
||||||
|
|
||||||
expect(screen.getByTestId("nav-xs")).toBeInTheDocument();
|
expect(screen.getByTestId("nav-xs")).toBeInTheDocument();
|
||||||
expect(screen.getByTestId("nav-sm")).toBeInTheDocument();
|
expect(screen.getByTestId("nav-sm")).toBeInTheDocument();
|
||||||
@@ -166,7 +163,7 @@ describe("Header", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("navigation containers use expected breakpoint classes", () => {
|
test("navigation containers use expected breakpoint classes", () => {
|
||||||
render(<Header onToggle={mockOnToggle} />);
|
render(<Header />);
|
||||||
|
|
||||||
// XSmall navigation
|
// XSmall navigation
|
||||||
const navXs = screen.getByTestId("nav-xs");
|
const navXs = screen.getByTestId("nav-xs");
|
||||||
@@ -190,7 +187,7 @@ describe("Header", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("renders navigation items with correct text and links", () => {
|
test("renders navigation items with correct text and links", () => {
|
||||||
render(<Header onToggle={mockOnToggle} />);
|
render(<Header />);
|
||||||
|
|
||||||
// Check navigation items
|
// Check navigation items
|
||||||
expect(screen.getAllByText("Use cases").length).toBeGreaterThan(0);
|
expect(screen.getAllByText("Use cases").length).toBeGreaterThan(0);
|
||||||
@@ -199,7 +196,7 @@ describe("Header", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("renders multiple instances of navigation items for responsive design", () => {
|
test("renders multiple instances of navigation items for responsive design", () => {
|
||||||
render(<Header onToggle={mockOnToggle} />);
|
render(<Header />);
|
||||||
|
|
||||||
// Should have multiple instances of navigation items for different breakpoints
|
// Should have multiple instances of navigation items for different breakpoints
|
||||||
const useCasesLinks = screen.getAllByText("Use cases");
|
const useCasesLinks = screen.getAllByText("Use cases");
|
||||||
@@ -214,7 +211,7 @@ describe("Header", () => {
|
|||||||
|
|
||||||
describe("Authentication Structure", () => {
|
describe("Authentication Structure", () => {
|
||||||
test("renders all breakpoint-specific auth containers", () => {
|
test("renders all breakpoint-specific auth containers", () => {
|
||||||
render(<Header onToggle={mockOnToggle} />);
|
render(<Header />);
|
||||||
|
|
||||||
expect(screen.getByTestId("auth-xs")).toBeInTheDocument();
|
expect(screen.getByTestId("auth-xs")).toBeInTheDocument();
|
||||||
expect(screen.getByTestId("auth-sm")).toBeInTheDocument();
|
expect(screen.getByTestId("auth-sm")).toBeInTheDocument();
|
||||||
@@ -224,7 +221,7 @@ describe("Header", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("auth containers use expected breakpoint classes", () => {
|
test("auth containers use expected breakpoint classes", () => {
|
||||||
render(<Header onToggle={mockOnToggle} />);
|
render(<Header />);
|
||||||
|
|
||||||
// XSmall auth
|
// XSmall auth
|
||||||
const authXs = screen.getByTestId("auth-xs");
|
const authXs = screen.getByTestId("auth-xs");
|
||||||
@@ -248,7 +245,7 @@ describe("Header", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("renders login button with correct accessibility", () => {
|
test("renders login button with correct accessibility", () => {
|
||||||
render(<Header onToggle={mockOnToggle} />);
|
render(<Header />);
|
||||||
|
|
||||||
const loginLinks = screen.getAllByRole("menuitem", {
|
const loginLinks = screen.getAllByRole("menuitem", {
|
||||||
name: "Log in to your account",
|
name: "Log in to your account",
|
||||||
@@ -258,7 +255,7 @@ describe("Header", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("renders multiple login buttons for responsive design", () => {
|
test("renders multiple login buttons for responsive design", () => {
|
||||||
render(<Header onToggle={mockOnToggle} />);
|
render(<Header />);
|
||||||
|
|
||||||
// Should have multiple login buttons for different breakpoints
|
// Should have multiple login buttons for different breakpoints
|
||||||
const loginButtons = screen.getAllByText("Log in");
|
const loginButtons = screen.getAllByText("Log in");
|
||||||
@@ -266,7 +263,7 @@ describe("Header", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("renders create rule button with avatar decoration", () => {
|
test("renders create rule button with avatar decoration", () => {
|
||||||
render(<Header onToggle={mockOnToggle} />);
|
render(<Header />);
|
||||||
|
|
||||||
const createRuleButtons = screen.getAllByRole("button", {
|
const createRuleButtons = screen.getAllByRole("button", {
|
||||||
name: "Create a new rule with avatar decoration",
|
name: "Create a new rule with avatar decoration",
|
||||||
@@ -276,7 +273,7 @@ describe("Header", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("renders multiple create rule buttons for responsive design", () => {
|
test("renders multiple create rule buttons for responsive design", () => {
|
||||||
render(<Header onToggle={mockOnToggle} />);
|
render(<Header />);
|
||||||
|
|
||||||
// Should have multiple create rule buttons for different breakpoints
|
// Should have multiple create rule buttons for different breakpoints
|
||||||
const createRuleButtons = screen.getAllByText("Create rule");
|
const createRuleButtons = screen.getAllByText("Create rule");
|
||||||
@@ -286,7 +283,7 @@ describe("Header", () => {
|
|||||||
|
|
||||||
describe("Avatar Images", () => {
|
describe("Avatar Images", () => {
|
||||||
test("renders avatar images with correct attributes", () => {
|
test("renders avatar images with correct attributes", () => {
|
||||||
render(<Header onToggle={mockOnToggle} />);
|
render(<Header />);
|
||||||
|
|
||||||
const avatars = screen.getAllByRole("img");
|
const avatars = screen.getAllByRole("img");
|
||||||
expect(avatars.length).toBeGreaterThan(0);
|
expect(avatars.length).toBeGreaterThan(0);
|
||||||
@@ -296,45 +293,39 @@ describe("Header", () => {
|
|||||||
(img) =>
|
(img) =>
|
||||||
img.alt === "Avatar 1" ||
|
img.alt === "Avatar 1" ||
|
||||||
img.alt === "Avatar 2" ||
|
img.alt === "Avatar 2" ||
|
||||||
img.alt === "Avatar 3",
|
img.alt === "Avatar 3"
|
||||||
);
|
);
|
||||||
expect(avatarImages.length).toBeGreaterThan(0);
|
expect(avatarImages.length).toBeGreaterThan(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("User Interactions", () => {
|
describe("Sticky Header Behavior", () => {
|
||||||
test("calls onToggle when navigation items are clicked", async () => {
|
test("applies sticky positioning classes", () => {
|
||||||
const user = userEvent.setup();
|
const { container } = render(<Header />);
|
||||||
render(<Header onToggle={mockOnToggle} />);
|
|
||||||
|
|
||||||
// Find and click a navigation item - use menuitem role since they're in a menubar
|
const header = container.querySelector(
|
||||||
const useCasesLinks = screen.getAllByRole("menuitem", {
|
'[role="banner"][aria-label="Main navigation header"]'
|
||||||
name: "Navigate to Use cases page",
|
);
|
||||||
});
|
expect(header).toHaveClass("sticky", "top-0", "z-50");
|
||||||
expect(useCasesLinks.length).toBeGreaterThan(0);
|
|
||||||
const useCasesLink = useCasesLinks[0];
|
|
||||||
await user.click(useCasesLink);
|
|
||||||
|
|
||||||
expect(mockOnToggle).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("CSS Classes and Styling", () => {
|
describe("CSS Classes and Styling", () => {
|
||||||
test("has correct CSS classes for styling", () => {
|
test("has correct CSS classes for styling", () => {
|
||||||
const { container } = render(<Header onToggle={mockOnToggle} />);
|
const { container } = render(<Header />);
|
||||||
|
|
||||||
const header = container.querySelector(
|
const header = container.querySelector(
|
||||||
'[role="banner"][aria-label="Main navigation header"]',
|
'[role="banner"][aria-label="Main navigation header"]'
|
||||||
);
|
);
|
||||||
expect(header).toHaveClass("bg-[var(--color-surface-default-primary)]");
|
expect(header).toHaveClass("bg-[var(--color-surface-default-primary)]");
|
||||||
expect(header).toHaveClass("w-full");
|
expect(header).toHaveClass("w-full");
|
||||||
expect(header).toHaveClass("border-b");
|
expect(header).toHaveClass("border-b");
|
||||||
expect(header).toHaveClass(
|
expect(header).toHaveClass(
|
||||||
"border-[var(--border-color-default-tertiary)]",
|
"border-[var(--border-color-default-tertiary)]"
|
||||||
);
|
);
|
||||||
|
|
||||||
const nav = container.querySelector(
|
const nav = container.querySelector(
|
||||||
'[role="navigation"][aria-label="Main navigation"]',
|
'[role="navigation"][aria-label="Main navigation"]'
|
||||||
);
|
);
|
||||||
expect(nav).toHaveClass("flex");
|
expect(nav).toHaveClass("flex");
|
||||||
expect(nav).toHaveClass("items-center");
|
expect(nav).toHaveClass("items-center");
|
||||||
|
|||||||
@@ -15,6 +15,16 @@ describe("Blog Post Validation", () => {
|
|||||||
author: "Test Author",
|
author: "Test Author",
|
||||||
date: "2025-04-15",
|
date: "2025-04-15",
|
||||||
related: ["post-1", "post-2"],
|
related: ["post-1", "post-2"],
|
||||||
|
thumbnail: {
|
||||||
|
vertical: "test-vertical.svg",
|
||||||
|
horizontal: "test-horizontal.svg",
|
||||||
|
},
|
||||||
|
banner: {
|
||||||
|
horizontal: "test-banner.svg",
|
||||||
|
},
|
||||||
|
background: {
|
||||||
|
color: "#F4F3F1",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = validateBlogPost(validPost);
|
const result = validateBlogPost(validPost);
|
||||||
@@ -84,11 +94,14 @@ describe("Blog Post Validation", () => {
|
|||||||
description: "Test description",
|
description: "Test description",
|
||||||
author: "Test Author",
|
author: "Test Author",
|
||||||
date: "2025-04-15",
|
date: "2025-04-15",
|
||||||
// Missing related
|
// Missing related, thumbnail, banner, background
|
||||||
};
|
};
|
||||||
|
|
||||||
const sanitized = sanitizeBlogPost(post);
|
const sanitized = sanitizeBlogPost(post);
|
||||||
expect(sanitized.related).toEqual([]);
|
expect(sanitized.related).toEqual([]);
|
||||||
|
expect(sanitized.thumbnail).toBeNull();
|
||||||
|
expect(sanitized.banner).toBeNull();
|
||||||
|
expect(sanitized.background).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should preserve existing optional fields", () => {
|
it("should preserve existing optional fields", () => {
|
||||||
@@ -98,10 +111,30 @@ describe("Blog Post Validation", () => {
|
|||||||
author: "Test Author",
|
author: "Test Author",
|
||||||
date: "2025-04-15",
|
date: "2025-04-15",
|
||||||
related: ["custom-post"],
|
related: ["custom-post"],
|
||||||
|
thumbnail: {
|
||||||
|
vertical: "custom-vertical.svg",
|
||||||
|
horizontal: "custom-horizontal.svg",
|
||||||
|
},
|
||||||
|
banner: {
|
||||||
|
horizontal: "custom-banner.svg",
|
||||||
|
},
|
||||||
|
background: {
|
||||||
|
color: "#E2EFFF",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const sanitized = sanitizeBlogPost(post);
|
const sanitized = sanitizeBlogPost(post);
|
||||||
expect(sanitized.related).toEqual(["custom-post"]);
|
expect(sanitized.related).toEqual(["custom-post"]);
|
||||||
|
expect(sanitized.thumbnail).toEqual({
|
||||||
|
vertical: "custom-vertical.svg",
|
||||||
|
horizontal: "custom-horizontal.svg",
|
||||||
|
});
|
||||||
|
expect(sanitized.banner).toEqual({
|
||||||
|
horizontal: "custom-banner.svg",
|
||||||
|
});
|
||||||
|
expect(sanitized.background).toEqual({
|
||||||
|
color: "#E2EFFF",
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -112,6 +145,9 @@ describe("Blog Post Validation", () => {
|
|||||||
expect(BLOG_POST_SCHEMA).toHaveProperty("author");
|
expect(BLOG_POST_SCHEMA).toHaveProperty("author");
|
||||||
expect(BLOG_POST_SCHEMA).toHaveProperty("date");
|
expect(BLOG_POST_SCHEMA).toHaveProperty("date");
|
||||||
expect(BLOG_POST_SCHEMA).toHaveProperty("related");
|
expect(BLOG_POST_SCHEMA).toHaveProperty("related");
|
||||||
|
expect(BLOG_POST_SCHEMA).toHaveProperty("thumbnail");
|
||||||
|
expect(BLOG_POST_SCHEMA).toHaveProperty("banner");
|
||||||
|
expect(BLOG_POST_SCHEMA).toHaveProperty("background");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should have correct required field configuration", () => {
|
it("should have correct required field configuration", () => {
|
||||||
@@ -120,6 +156,9 @@ describe("Blog Post Validation", () => {
|
|||||||
expect(BLOG_POST_SCHEMA.author.required).toBe(true);
|
expect(BLOG_POST_SCHEMA.author.required).toBe(true);
|
||||||
expect(BLOG_POST_SCHEMA.date.required).toBe(true);
|
expect(BLOG_POST_SCHEMA.date.required).toBe(true);
|
||||||
expect(BLOG_POST_SCHEMA.related.required).toBe(false);
|
expect(BLOG_POST_SCHEMA.related.required).toBe(false);
|
||||||
|
expect(BLOG_POST_SCHEMA.thumbnail.required).toBe(false);
|
||||||
|
expect(BLOG_POST_SCHEMA.banner.required).toBe(false);
|
||||||
|
expect(BLOG_POST_SCHEMA.background.required).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user