Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 19 additions & 12 deletions components/Book/expanding-side-img.scss
Original file line number Diff line number Diff line change
Expand Up @@ -217,20 +217,27 @@ div.expanding-side-img-container {
display: none !important;
}

.expanding-side-img {
img {
width: 100%
}
&:has(.caption) {
text-align: center;
div.expanding-side-img {
position: static;
width: 100%;
max-width: 100%;
transform: none;

&::after {
display: none;
}
img {
width: 100%;
height: auto;
}

img {
margin-bottom: 0;
}
&:has(.caption) {
text-align: center;

&::after {
display: none;
}

img {
margin-bottom: 0;
}

.caption, .children {
font-weight: 400 !important;
Expand Down
191 changes: 191 additions & 0 deletions e2e/book-page.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ async function paintedLineCount(locator: Locator) {
});
}

const SIDE_IMAGE_ALT = "Synthetic side image regression fixture";
const SIDE_IMAGE_CAPTION = "Side image test fixture.";
const SIDE_IMAGE_CHILDREN =
"This repository-owned image exists only to verify side-image rendering.";
const FOLLOWING_CONTENT =
"The side image should remain visible alongside this ordinary chapter content.";
const GEOMETRY_TOLERANCE = 1;

test("renders the representative book page", async ({ page }, testInfo) => {
await page.goto(BOOK_PATH);

Expand Down Expand Up @@ -174,3 +182,186 @@ test("renders the representative book page", async ({ page }, testInfo) => {
);
expect(horizontalOverflow).toBe(false);
});

test("renders side images on narrow screens", async ({ page }, testInfo) => {
await page.goto(BOOK_PATH);

const sideImage = page
.locator(".expanding-side-img")
.getByRole("img", { name: SIDE_IMAGE_ALT });
const wrapper = sideImage.locator("..");
const container = wrapper.locator("..");
const caption = wrapper.getByText(SIDE_IMAGE_CAPTION, { exact: true });
const children = wrapper.getByText(SIDE_IMAGE_CHILDREN, { exact: true });
const followingContent = page.getByText(FOLLOWING_CONTENT, { exact: true });

await expect(container).toBeAttached();
await expect(wrapper).toBeAttached();
await expect(sideImage).toBeAttached();
await expect(sideImage).toHaveJSProperty("complete", true);
await container.scrollIntoViewIfNeeded();

const [containerBox, wrapperBox, imageBox, followingContentBox] =
await Promise.all([
container.boundingBox(),
wrapper.boundingBox(),
sideImage.boundingBox(),
followingContent.boundingBox(),
]);
const withEdges = (
box: { x: number; y: number; width: number; height: number } | null
) =>
box && {
...box,
top: box.y,
right: box.x + box.width,
bottom: box.y + box.height,
left: box.x,
};
const containerBounds = withEdges(containerBox);
const wrapperBounds = withEdges(wrapperBox);
const imageBounds = withEdges(imageBox);
const followingContentBounds = withEdges(followingContentBox);
const elementDetails = await sideImage.evaluate((img: HTMLImageElement) => {
const wrapperStyle = getComputedStyle(img.parentElement!);
const imageStyle = getComputedStyle(img);
const documentElement = document.documentElement;
const body = document.body;

return {
wrapper: {
position: wrapperStyle.position,
transform: wrapperStyle.transform,
},
image: {
position: imageStyle.position,
transform: imageStyle.transform,
complete: img.complete,
naturalWidth: img.naturalWidth,
naturalHeight: img.naturalHeight,
src: img.currentSrc || img.src,
},
horizontalOverflow:
documentElement.scrollWidth > documentElement.clientWidth ||
body.scrollWidth > body.clientWidth,
};
});
const viewport = page.viewportSize()!;
const visibility = {
image: await sideImage.isVisible(),
caption: await caption.isVisible(),
children: await children.isVisible(),
followingContent: await followingContent.isVisible(),
};
const intersectsViewport = Boolean(
imageBounds &&
imageBounds.width > 0 &&
imageBounds.height > 0 &&
imageBounds.x + imageBounds.width > 0 &&
imageBounds.y + imageBounds.height > 0 &&
imageBounds.x < viewport.width &&
imageBounds.y < viewport.height
);
const diagnostics = {
viewport,
containerBounds,
wrapper: { bounds: wrapperBounds, ...elementDetails.wrapper },
img: { bounds: imageBounds, ...elementDetails.image },
followingContentBounds,
intersectsViewport,
horizontalOverflow: elementDetails.horizontalOverflow,
visibility,
};

const artifactPrefix =
testInfo.project.name === MOBILE_PROJECT ? "mobile" : "desktop";
const diagnosticsPath = testInfo.outputPath(
`${artifactPrefix}-side-image-diagnostics.json`
);
await fs.writeFile(
diagnosticsPath,
JSON.stringify(diagnostics, null, 2)
);
await testInfo.attach(`${artifactPrefix}-side-image-diagnostics`, {
path: diagnosticsPath,
contentType: "application/json",
});

const fullPagePath = testInfo.outputPath(
`${artifactPrefix}-side-image-full-page.png`
);
await page.screenshot({ path: fullPagePath, fullPage: true });
await testInfo.attach(`${artifactPrefix}-side-image-full-page`, {
path: fullPagePath,
contentType: "image/png",
});

await page
.getByRole("heading", { name: "Side image fixture" })
.scrollIntoViewIfNeeded();
const regionPath = testInfo.outputPath(
`${artifactPrefix}-side-image-region.png`
);
await page.screenshot({ path: regionPath });
await testInfo.attach(`${artifactPrefix}-side-image-region`, {
path: regionPath,
contentType: "image/png",
});

expect(
diagnostics.img.naturalWidth,
"Expected the side-image asset to load"
).toBeGreaterThan(0);
expect(diagnostics.img.naturalHeight).toBeGreaterThan(0);
expect(diagnostics.wrapper.bounds?.width ?? 0).toBeGreaterThan(0);
expect(diagnostics.wrapper.bounds?.height ?? 0).toBeGreaterThan(0);
expect(diagnostics.img.bounds.width).toBeGreaterThan(0);
expect(diagnostics.img.bounds.height).toBeGreaterThan(0);
await expect(
sideImage,
"Expected the side image to be visible on mobile and desktop"
).toBeVisible();

if (testInfo.project.name !== MOBILE_PROJECT) {
expect(
diagnostics.wrapper.bounds!.right,
"Expected the desktop image to remain in the side column"
).toBeLessThanOrEqual(
diagnostics.containerBounds!.left + GEOMETRY_TOLERANCE
);
expect(diagnostics.wrapper.bounds!.width).toBeLessThan(
diagnostics.containerBounds!.width
);
return;
}

await expect(caption).toBeVisible();
await expect(children).toBeVisible();
await expect(followingContent).toBeVisible();
expect(diagnostics.intersectsViewport).toBe(true);
expect(diagnostics.wrapper.bounds!.left).toBeGreaterThanOrEqual(
-GEOMETRY_TOLERANCE
);
expect(diagnostics.wrapper.bounds!.right).toBeLessThanOrEqual(
diagnostics.viewport.width + GEOMETRY_TOLERANCE
);
expect(diagnostics.wrapper.bounds!.left).toBeGreaterThanOrEqual(
diagnostics.containerBounds!.left - GEOMETRY_TOLERANCE
);
expect(diagnostics.wrapper.bounds!.right).toBeLessThanOrEqual(
diagnostics.containerBounds!.right + GEOMETRY_TOLERANCE
);
expect(diagnostics.img.bounds.left).toBeGreaterThanOrEqual(
diagnostics.wrapper.bounds!.left - GEOMETRY_TOLERANCE
);
expect(diagnostics.img.bounds.right).toBeLessThanOrEqual(
diagnostics.wrapper.bounds!.right + GEOMETRY_TOLERANCE
);
expect(
diagnostics.followingContentBounds!.top,
"Expected following content to start below the inline side image"
).toBeGreaterThanOrEqual(
diagnostics.wrapper.bounds!.bottom - GEOMETRY_TOLERANCE
);
expect(diagnostics.horizontalOverflow).toBe(false);
});
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions e2e/fixtures/notes/mobile-baseline/chapter-one/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,17 @@ It includes a short list:

- desktop Chromium renders this public book page;
- mobile WebKit renders the same content through Playwright's iPhone device emulation.

## Side image fixture

This synthetic example exercises the same expandable side-image component used by Notes content.

<ExpandingSideImg
src="images/side-image-test.svg"
alt="Synthetic side image regression fixture"
caption="Side image test fixture."
>
This repository-owned image exists only to verify side-image rendering.
</ExpandingSideImg>

The side image should remain visible alongside this ordinary chapter content.
Loading