Skip to content
Open
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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,8 @@ If you are writing a blog post, refer to the [blog post guide](https://rescript-
If your company uses ReScript and should appear in the "Trusted by our users" section on the front page:

- Add a black and white `.svg` logo using `#979AAD` as the fill color.
- Put the file in [`public/lp`](public/lp).
- Update [`src/common/OurUsers.res`](src/common/OurUsers.res).
- Put the file in [`apps/docs/public/lp`](apps/docs/public/lp).
- Update [`apps/docs/src/data/OurUsers.res`](apps/docs/src/data/OurUsers.res), including the logo's intrinsic `width` and `height`. For SVGs without explicit dimensions, use their `viewBox` dimensions; scale fractional dimensions together to integers without changing the aspect ratio.
- Commit, push, and open a PR.

## Contributing
Expand Down
64 changes: 64 additions & 0 deletions apps/docs/__tests__/ImageAsset_.test.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
open Vitest

let metadata: ImageAsset.metadata = {
availableWidths: [360, 640, 1000],
imageUrlFor: (width, format) => {
let extension = switch format {
| #avif => "avif"
| #webp => "webp"
}
Some(`/images/photo-${width->Int.toString}.${extension}`)
},
}

let original: ImageAsset.t = {
src: "/images/photo.avif",
width: 1000,
height: 667,
source: Original,
}

test("responsive source sets include every available width for the requested format", async () => {
expect(ImageAsset.sourceSet(metadata, #avif))->toEqual(
Some("/images/photo-360.avif 360w, /images/photo-640.avif 640w, /images/photo-1000.avif 1000w"),
)
expect(ImageAsset.sourceSet(metadata, #webp))->toEqual(
Some("/images/photo-360.webp 360w, /images/photo-640.webp 640w, /images/photo-1000.webp 1000w"),
)
})

test("source sets omit missing and blank image URLs", async () => {
let partial: ImageAsset.metadata = {
availableWidths: [360, 640, 1000],
imageUrlFor: (width, _format) =>
switch width {
| 360 => Some("/images/photo-360.avif")
| 640 => None
| _ => Some(" ")
},
}
expect(ImageAsset.sourceSet(partial, #avif))->toEqual(Some("/images/photo-360.avif 360w"))
})

test("empty and unavailable candidates omit the source set entirely", async () => {
expect(ImageAsset.sourceSet({...metadata, availableWidths: []}, #avif))->toEqual(None)
expect(
ImageAsset.sourceSet({...metadata, imageUrlFor: (_width, _format) => None}, #webp),
)->toEqual(None)
})

test("the fallback preserves originals and selects a generated WebP when available", async () => {
expect(ImageAsset.fallback(original))->toBe("/images/photo.avif")
expect(ImageAsset.fallback({...original, source: Responsive(metadata)}))->toBe(
"/images/photo-1000.webp",
)
})

test("missing or blank generated fallback URLs retain the original image", async () => {
let missing = {...metadata, imageUrlFor: (_width, _format) => None}
let blank = {...metadata, imageUrlFor: (_width, _format) => Some("")}
expect(ImageAsset.fallback({...original, source: Responsive(missing)}))->toBe(
"/images/photo.avif",
)
expect(ImageAsset.fallback({...original, source: Responsive(blank)}))->toBe("/images/photo.avif")
})
32 changes: 21 additions & 11 deletions apps/docs/__tests__/ImageGallery_.test.res
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
open Vitest

let images = ["/lp/community-3.avif", "/lp/community-2.avif", "/lp/community-1.avif"]
let firstImage: ImageAsset.t = {
src: "/lp/community-3.avif",
width: 1000,
height: 667,
source: Original,
}
let images: array<ImageAsset.t> = [
firstImage,
{src: "/lp/community-2.avif", width: 1355, height: 904, source: Original},
{src: "/lp/community-1.avif", width: 1000, height: 667, source: Original},
]

test(
"gallery selectors show their photo and the next control wraps after the last photo",
async () => {
let screen = await render(<ImageGallery imgSrcs=images />)
let screen = await render(<ImageGallery images />)
let first = await screen->getByLabelText("Show community photo 1")
let third = await screen->getByLabelText("Show community photo 3")
let next = await screen->getByLabelText("Next community photo")
Expand All @@ -25,18 +35,18 @@ test(
)

test("gallery renders no controls for an empty image list", async () => {
let screen = await render(<ImageGallery imgSrcs=[] />)
let screen = await render(<ImageGallery images=[] />)
expect(screen->container->textContent->Nullable.toOption)->toEqual(Some(""))
let next = await screen->getByLabelText("Next community photo")
await element(next)->notToBeInTheDocument
})

test("gallery returns to the first image when the selected image is removed", async () => {
let screen = await render(<ImageGallery imgSrcs=images />)
let screen = await render(<ImageGallery images />)
let third = await screen->getByLabelText("Show community photo 3")
await third->click

await screen->rerender(<ImageGallery imgSrcs=["/lp/community-3.avif"] />)
await screen->rerender(<ImageGallery images=[firstImage] />)

let firstImage = await screen->getByAltText("ReScript community photo 1")
await element(firstImage)->toHaveAttribute("src", "/lp/community-3.avif")
Expand All @@ -46,15 +56,15 @@ test("gallery returns to the first image when the selected image is removed", as
})

test("gallery keeps the first image selected when a shortened list grows again", async () => {
let screen = await render(<ImageGallery imgSrcs=images />)
let screen = await render(<ImageGallery images />)
let third = await screen->getByLabelText("Show community photo 3")
await third->click

await screen->rerender(<ImageGallery imgSrcs=["/lp/community-3.avif"] />)
await screen->rerender(<ImageGallery images=[firstImage] />)
let first = await screen->getByLabelText("Show community photo 1")
await element(first)->toHaveAttribute("aria-pressed", "true")

await screen->rerender(<ImageGallery imgSrcs=images />)
await screen->rerender(<ImageGallery images />)

await element(first)->toHaveAttribute("aria-pressed", "true")
await element(third)->toHaveAttribute("aria-pressed", "false")
Expand All @@ -63,15 +73,15 @@ test("gallery keeps the first image selected when a shortened list grows again",
})

test("gallery keeps the first image selected after an empty list is restored", async () => {
let screen = await render(<ImageGallery imgSrcs=images />)
let screen = await render(<ImageGallery images />)
let third = await screen->getByLabelText("Show community photo 3")
await third->click

await screen->rerender(<ImageGallery imgSrcs=[] />)
await screen->rerender(<ImageGallery images=[] />)
let next = await screen->getByLabelText("Next community photo")
await element(next)->notToBeInTheDocument

await screen->rerender(<ImageGallery imgSrcs=images />)
await screen->rerender(<ImageGallery images />)

let first = await screen->getByLabelText("Show community photo 1")
await element(first)->toHaveAttribute("aria-pressed", "true")
Expand Down
28 changes: 28 additions & 0 deletions apps/docs/__tests__/LandingPageVideo_.test.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
open Vitest

let attribute = (selector, name) =>
document
->WebAPI.Document.querySelector(selector)
->Null.toOption
->Option.flatMap(element => element->WebAPI.Element.getAttribute(name)->Null.toOption)

test(
"homepage videos reserve dimensions and defer media loading while retaining controls",
async () => {
let _screen = await render(
<LandingPageVideo
poster="/lp/fast-build-preview.avif"
src="https://assets-17077.kxcdn.com/videos/fast-build-3.mp4"
/>,
)
expect(attribute("video", "preload"))->toEqual(Some("none"))
expect(attribute("video", "width"))->toEqual(Some("1750"))
expect(attribute("video", "height"))->toEqual(Some("1116"))
expect(attribute("video", "poster"))->toEqual(Some("/lp/fast-build-preview.avif"))
expect(attribute("video", "controls"))->toEqual(Some(""))
expect(attribute("video source", "src"))->toEqual(
Some("https://assets-17077.kxcdn.com/videos/fast-build-3.mp4"),
)
expect(attribute("video source", "type"))->toEqual(Some("video/mp4"))
},
)
122 changes: 122 additions & 0 deletions apps/docs/__tests__/ResponsiveImage_.test.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
open Vitest

let metadata: ImageAsset.metadata = {
availableWidths: [360, 640, 1000],
imageUrlFor: (width, format) => {
let format = switch format {
| #avif => "avif"
| #webp => "webp"
}
Some(`/lp/community-3.avif?width=${width->Int.toString}&format=${format}`)
},
}

let original: ImageAsset.t = {
src: "/lp/community-3.avif",
width: 1000,
height: 667,
source: Original,
}

// Source elements have no accessible role; inspect their native media-selection attributes.
let sourceAttribute = name =>
document
->WebAPI.Document.querySelector("picture source")
->Null.toOption
->Option.flatMap(source => source->WebAPI.Element.getAttribute(name)->Null.toOption)

let imageAttribute = name =>
document
->WebAPI.Document.querySelector("img")
->Null.toOption
->Option.flatMap(image => image->WebAPI.Element.getAttribute(name)->Null.toOption)

test("original images retain a plain image with dimensions and loading attributes", async () => {
let screen = await render(
<ResponsiveImage image=original sizes="343px" alt="Community photo" loading=#lazy />,
)
let image = await screen->getByAltText("Community photo")
await element(image)->toHaveAttribute("src", "/lp/community-3.avif")
await element(image)->toHaveAttribute("width", "1000")
await element(image)->toHaveAttribute("height", "667")
await element(image)->toHaveAttribute("loading", "lazy")
await element(image)->toHaveAttribute("decoding", "async")
expect(document->WebAPI.Document.querySelector("picture")->Null.toOption)->toEqual(None)
expect(imageAttribute("srcset"))->toEqual(None)
})

test("responsive images render one AVIF source and a sized WebP image fallback", async () => {
let screen = await render(
<ResponsiveImage
image={{...original, source: Responsive(metadata)}}
sizes="(min-width: 768px) 623.2px, 622px"
alt="Responsive community photo"
className="gallery-photo"
loading=#eager
/>,
)
let image = await screen->getByAltText("Responsive community photo")
await element(image)->toHaveAttribute("src", "/lp/community-3.avif?width=1000&format=webp")
await element(image)->toHaveAttribute(
"srcset",
"/lp/community-3.avif?width=360&format=webp 360w, /lp/community-3.avif?width=640&format=webp 640w, /lp/community-3.avif?width=1000&format=webp 1000w",
)
await element(image)->toHaveAttribute("sizes", "(min-width: 768px) 623.2px, 622px")
await element(image)->toHaveAttribute("width", "1000")
await element(image)->toHaveAttribute("height", "667")
await element(image)->toHaveAttribute("loading", "eager")
await element(image)->toHaveClass("gallery-photo")
expect(sourceAttribute("type"))->toEqual(Some("image/avif"))
expect(sourceAttribute("sizes"))->toEqual(Some("(min-width: 768px) 623.2px, 622px"))
expect(sourceAttribute("srcset"))->toEqual(
Some(
"/lp/community-3.avif?width=360&format=avif 360w, /lp/community-3.avif?width=640&format=avif 640w, /lp/community-3.avif?width=1000&format=avif 1000w",
),
)
expect(document->WebAPI.Document.querySelector("source ~ source")->Null.toOption)->toEqual(None)
})

test(
"unavailable responsive URLs render the valid original without empty source sets",
async () => {
let missing = {...metadata, imageUrlFor: (_width, _format) => None}
let screen = await render(
<ResponsiveImage
image={{...original, source: Responsive(missing)}} sizes="343px" alt="Fallback photo"
/>,
)
let image = await screen->getByAltText("Fallback photo")
await element(image)->toHaveAttribute("src", "/lp/community-3.avif")
expect(imageAttribute("srcset"))->toEqual(None)
expect(sourceAttribute("srcset"))->toEqual(None)
expect(document->WebAPI.Document.querySelector("picture")->Null.toOption)->toEqual(None)
},
)

test(
"real plugin metadata renders generated assets with the requested image dimensions",
async () => {
let screen = await render(
<ResponsiveImage
image=LandingPageImages.tooling
sizes=LandingPageImages.screenshotSizes
alt="ReScript editor tooling"
/>,
)
let image = await screen->getByAltText("ReScript editor tooling")
await element(image)->toHaveAttribute("width", "1000")
await element(image)->toHaveAttribute("height", "497")
await element(image)->toHaveAttribute("sizes", LandingPageImages.screenshotSizes)
expect(sourceAttribute("type"))->toEqual(Some("image/avif"))
let generatedWidths = switch LandingPageImages.tooling.source {
| Original => []
| Responsive(metadata) => metadata.availableWidths
}
expect(generatedWidths)->toEqual([360, 640, 1000])
let hasGeneratedFallback =
imageAttribute("src")->Option.map(src =>
src->String.includes("/@responsive-image/vite-plugin/webp/1000/")
)
expect(hasGeneratedFallback)->toEqual(Some(true))
},
)
Loading
Loading