Skip to content

Commit

Permalink
fix: correctly handle fit mode in Bunny.net, Cloudflare and Kontent.ai (
Browse files Browse the repository at this point in the history
#68)

* fix: correctly handle fit mode in Bunny.net, Cloudflare and Kontent.ai

* Add note on e2e test

* chore: fix tests
  • Loading branch information
ascorbic authored Jul 3, 2023
1 parent 0b8b9a1 commit 4b2bf38
Show file tree
Hide file tree
Showing 12 changed files with 103 additions and 32 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@ jobs:
- name: Typecheck
run: deno check mod.ts
- name: Test
run: deno test src/
run: deno test -A src/
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ if supported, to ensure consistent behaviour across all CDNs:
should be AVIF, WebP, then the original format.
- Fit = cover. The image should fill the requested dimensions, cropping if
necessary and without distortion. This is the equivalent of the CSS
`object-fit: cover` setting.
`object-fit: cover` setting. There is an e2e test for this.
- No upscaling. The image should not be upscaled if it is smaller than the
requested dimensions. Instead it should return the largest available size, but
maintain the requested aspect ratio.
4 changes: 2 additions & 2 deletions demo/src/examples.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@
],
"builder.io": [
"Builder.io",
"https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F462d29d57dda42cb9e26441501db535f?format=webp&fit=cover"
"https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F462d29d57dda42cb9e26441501db535f"
],
"cloudinary": [
"Cloudinary",
"https://res.cloudinary.com/demo/image/upload/c_lfill,w_800,h_550,f_auto/dog.webp"
],
"imgix": [
"Imgix (Unsplash)",
"Imgix",
"https://images.unsplash.com/photo-1674255909399-9bcb2cab6489?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=996&q=80"
],
"wordpress": [
Expand Down
10 changes: 10 additions & 0 deletions deno.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

47 changes: 47 additions & 0 deletions src/e2e.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import {
assertEquals,
assertExists,
} from "https://deno.land/std@0.172.0/testing/asserts.ts";
import examples from "../demo/src/examples.json" assert { type: "json" };
import { getPixels } from "https://deno.land/x/get_pixels@1.0.0/mod.ts";
import { transformUrl } from "./transform.ts";
import type { ImageCdn } from "./types.ts";

Deno.test("E2E tests", async (t) => {
for (const [cdn, example] of Object.entries(examples)) {
const [name, url] = example;
await t.step(`${name} resizes an image`, async () => {
const image = transformUrl({
url,
width: 100,
cdn: cdn as ImageCdn,
});

assertExists(image, `Failed to resize ${name} with ${cdn}`);
const { width } = await getPixels(image);

assertEquals(width, 100);
});

// Builder.io handles crop incorrectly at the moment
if (cdn === "builder.io") {
continue;
}

await t.step(`${name} returns requested aspect ratio`, async () => {
const image = transformUrl({
url,
width: 100,
height: 50,
cdn: cdn as ImageCdn,
});

assertExists(image, `Failed to resize ${name} with ${cdn}`);

const { width, height } = await getPixels(image);

assertEquals(width, 100);
assertEquals(height, 50);
});
}
});
8 changes: 4 additions & 4 deletions src/transformers/builder.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ Deno.test("builder.io", async (t) => {
});
assertEquals(
result?.toString(),
"https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F462d29d57dda42cb9e26441501db535f?fit=cover&width=200&height=100",
"https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F462d29d57dda42cb9e26441501db535f?width=200&height=100&fit=cover",
);
});
await t.step("should not set height if not provided", () => {
const result = transform({ url: img, width: 200 });
assertEquals(
result?.toString(),
"https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F462d29d57dda42cb9e26441501db535f?fit=cover&width=200",
"https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F462d29d57dda42cb9e26441501db535f?width=200",
);
});
await t.step("should delete height if not set", () => {
Expand All @@ -30,7 +30,7 @@ Deno.test("builder.io", async (t) => {
const result = transform({ url, width: 200 });
assertEquals(
result?.toString(),
"https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F462d29d57dda42cb9e26441501db535f?fit=cover&width=200",
"https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F462d29d57dda42cb9e26441501db535f?width=200",
);
});

Expand All @@ -42,7 +42,7 @@ Deno.test("builder.io", async (t) => {
});
assertEquals(
result?.toString(),
"https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F462d29d57dda42cb9e26441501db535f?fit=cover&width=201&height=100",
"https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F462d29d57dda42cb9e26441501db535f?width=201&height=100&fit=cover",
);
});

Expand Down
4 changes: 3 additions & 1 deletion src/transformers/builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,11 @@ export const transform: UrlTransformer = (
{ url: originalUrl, width, height, format },
) => {
const url = new URL(originalUrl);
setParamIfUndefined(url, "fit", "cover");
setParamIfDefined(url, "width", width, true, true);
setParamIfDefined(url, "height", height, true, true);
setParamIfDefined(url, "format", format);
if (width && height) {
setParamIfUndefined(url, "fit", "cover");
}
return url;
};
10 changes: 8 additions & 2 deletions src/transformers/bunny.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { UrlParser, UrlTransformer } from "../types.ts";
import { getNumericParam, setParamIfDefined } from "../utils.ts";
import {
getNumericParam,
setParamIfDefined,
setParamIfUndefined,
} from "../utils.ts";

export const parse: UrlParser<{ fit?: string }> = (url) => {
const parsedUrl = new URL(url);
Expand All @@ -25,6 +29,8 @@ export const transform: UrlTransformer = (
) => {
const url = new URL(originalUrl);
setParamIfDefined(url, "width", width, true, true);
setParamIfDefined(url, "height", height, true, true);
if (width && height) {
setParamIfUndefined(url, "aspect_ratio", `${width}:${height}`);
}
return url;
};
4 changes: 2 additions & 2 deletions src/transformers/cloudflare.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { ParsedUrl } from "../types.ts";
import { CloudflareParams, parse, transform } from "./cloudflare.ts";

const img =
"https://assets.brevity.io/cdn-cgi/image/background=red,width=128,height=128,f=auto/uploads/generic/avatar-sample.jpeg"
"https://assets.brevity.io/cdn-cgi/image/background=red,width=128,height=128,f=auto/uploads/generic/avatar-sample.jpeg";

Deno.test("cloudflare parser", () => {
const parsed = parse(img);
Expand Down Expand Up @@ -33,7 +33,7 @@ Deno.test("cloudflare transformer", async (t) => {
});
assertEquals(
result?.toString(),
"https://assets.brevity.io/cdn-cgi/image/background=red,width=100,height=200,f=auto/uploads/generic/avatar-sample.jpeg"
"https://assets.brevity.io/cdn-cgi/image/background=red,width=100,height=200,f=auto,fit=cover/uploads/generic/avatar-sample.jpeg",
);
});
});
7 changes: 5 additions & 2 deletions src/transformers/cloudflare.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const formatUrl = (
{
host,
transformations = {},
path
path,
}: CloudflareParams,
): string => {
const transformString = Object.entries(transformations).map(
Expand All @@ -27,7 +27,7 @@ const formatUrl = (
"cdn-cgi",
"image",
transformString,
path
path,
].join("/");
return `https://${pathSegments}`;
};
Expand Down Expand Up @@ -87,6 +87,9 @@ export const generate: UrlGenerator<CloudflareParams> = (
if (format) {
props.transformations.f = format;
}

props.transformations.fit ||= "cover";

return new URL(formatUrl(props));
};

Expand Down
33 changes: 18 additions & 15 deletions src/transformers/kontentai.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ import { assertEquals } from "https://deno.land/std@0.172.0/testing/asserts.ts";
import { transform } from "./kontentai.ts";

const img =
"https://assets-us-01.kc-usercontent.com/b744f382-bfc7-434d-93e7-a65d51249bc7/cc0afdc7-23d7-4fde-be2c-f58ad54d2934/daylight.jpg"
"https://assets-us-01.kc-usercontent.com/b744f382-bfc7-434d-93e7-a65d51249bc7/cc0afdc7-23d7-4fde-be2c-f58ad54d2934/daylight.jpg";

Deno.test("kontent.ai", async (t) => {
await t.step("should format a URL", () => {
const result = transform({
url: img,
width: 200,
height: 100,
format: 'webp'
format: "webp",
});
assertEquals(
result?.toString(),
Expand All @@ -22,7 +22,7 @@ Deno.test("kontent.ai", async (t) => {
const result = transform({ url: img, width: 200 });
assertEquals(
result?.toString(),
"https://assets-us-01.kc-usercontent.com/b744f382-bfc7-434d-93e7-a65d51249bc7/cc0afdc7-23d7-4fde-be2c-f58ad54d2934/daylight.jpg?w=200&fit=crop",
"https://assets-us-01.kc-usercontent.com/b744f382-bfc7-434d-93e7-a65d51249bc7/cc0afdc7-23d7-4fde-be2c-f58ad54d2934/daylight.jpg?w=200",
);
});

Expand All @@ -38,24 +38,27 @@ Deno.test("kontent.ai", async (t) => {
);
});

await t.step("should add fit=scale when height or width (or both) provided and no other fit setting", () => {
const result = transform({
url: img,
width: 200,
height: 100,
});
assertEquals(
result?.toString(),
"https://assets-us-01.kc-usercontent.com/b744f382-bfc7-434d-93e7-a65d51249bc7/cc0afdc7-23d7-4fde-be2c-f58ad54d2934/daylight.jpg?w=200&h=100&fit=crop",
);
});
await t.step(
"should add fit=scale when height or width (or both) provided and no other fit setting",
() => {
const result = transform({
url: img,
width: 200,
height: 100,
});
assertEquals(
result?.toString(),
"https://assets-us-01.kc-usercontent.com/b744f382-bfc7-434d-93e7-a65d51249bc7/cc0afdc7-23d7-4fde-be2c-f58ad54d2934/daylight.jpg?w=200&h=100&fit=crop",
);
},
);
await t.step("should not set fit=scale if another value exists", () => {
const url = new URL(img);
url.searchParams.set("fit", "scale");
const result = transform({
url: url,
width: 200,
height: 100
height: 100,
});
assertEquals(
result?.toString(),
Expand Down
4 changes: 2 additions & 2 deletions src/transformers/kontentai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { UrlParser, UrlTransformer } from "../types.ts";
import {
getNumericParam,
setParamIfDefined,
setParamIfUndefined
setParamIfUndefined,
} from "../utils.ts";

export const parse: UrlParser<{ fit?: string }> = (url) => {
Expand Down Expand Up @@ -32,7 +32,7 @@ export const transform: UrlTransformer = (
setParamIfDefined(url, "w", width, true, true);
setParamIfDefined(url, "h", height, true, true);
setParamIfDefined(url, "fm", format, true);
if (width || height) {
if (width && height) {
setParamIfUndefined(url, "fit", "crop");
}
return url;
Expand Down

0 comments on commit 4b2bf38

Please sign in to comment.