Skip to content

Commit

Permalink
attributesTransformerを追加し、要素に属性を追加する機能を実装
Browse files Browse the repository at this point in the history
  • Loading branch information
dc7290 committed Dec 15, 2024
1 parent 316d369 commit e14b6cb
Show file tree
Hide file tree
Showing 7 changed files with 106 additions and 23 deletions.
30 changes: 30 additions & 0 deletions __tests__/transformer/attributes.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { describe, expect, test } from "vitest";
import attributesTransformer from "../../src/transformer/attributes";
import { cheerioLoad } from "../test-utils";

describe("attributesTransformer", () => {
test("正常系: 要素に属性が追加されること", async () => {
const $ = cheerioLoad(`
<div id="id"></div>
`);

await attributesTransformer({
elements: {
div: {
addAttributes: {
class: "test-class",
"data-test": "test-value",
id: ($element) => {
const originalId = $element.attr("id");
return `${originalId}-modified`;
},
},
},
},
})($);

expect($.html()).toBe(`
<div id="id-modified" class="test-class" data-test="test-value"></div>
`);
});
});
40 changes: 20 additions & 20 deletions __tests__/transformer/responsive-image.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import responsiveImageTransformer from "../../src/transformer/responsive-image";
import { cheerioLoad } from "../test-utils";

describe("ResponsiveImageTransformer", () => {
test("正常系: imgタグの属性を使ってpictureタグに置き換わること", async () => {
test("正常系: imgタグの属性を使ってpictureタグに置き換わること", () => {
const $ = cheerioLoad(`<figure>
<img
src="https://images.microcms-assets.io/assets/88f12f69f3bd4e13b769561fe720d255/04a3790c7ab94dff9379025420f60040/blog-template.png"
Expand All @@ -13,7 +13,7 @@ describe("ResponsiveImageTransformer", () => {
>
</figure>`);

await responsiveImageTransformer({
responsiveImageTransformer({
attributes: { sizes: "(max-width: 640px) 100vw, 1200px" },
})($);

Expand Down Expand Up @@ -54,7 +54,7 @@ describe("ResponsiveImageTransformer", () => {
expect(height).toEqual(expectedHeight);
});

test("正常系: 指定したattributesが使われること", async () => {
test("正常系: 指定したattributesが使われること", () => {
const $ = cheerioLoad(`<figure>
<img
src="https://images.microcms-assets.io/assets/88f12f69f3bd4e13b769561fe720d255/04a3790c7ab94dff9379025420f60040/blog-template.png"
Expand All @@ -64,7 +64,7 @@ describe("ResponsiveImageTransformer", () => {
>
</figure>`);

await responsiveImageTransformer({
responsiveImageTransformer({
attributes: {
style: "border: 1px solid red",
},
Expand All @@ -80,7 +80,7 @@ describe("ResponsiveImageTransformer", () => {
expect(style).toEqual(expectedStyle);
});

test("正常系: formats が指定されている場合はそれを使う", async () => {
test("正常系: formats が指定されている場合はそれを使う", () => {
const $ = cheerioLoad(`<figure>
<img
src="https://images.microcms-assets.io/assets/88f12f69f3bd4e13b769561fe720d255/04a3790c7ab94dff9379025420f60040/blog-template.png"
Expand All @@ -90,7 +90,7 @@ describe("ResponsiveImageTransformer", () => {
>
</figure>`);

await responsiveImageTransformer({
responsiveImageTransformer({
attributes: { sizes: "(max-width: 640px) 100vw, 1200px" },
formats: ["webp", "avif"],
})($);
Expand All @@ -114,7 +114,7 @@ describe("ResponsiveImageTransformer", () => {
expect(imgSrcset).toEqual(expectedImgSrcset);
});

test("正常系: deviceSizes が指定されている場合はそれを使う", async () => {
test("正常系: deviceSizes が指定されている場合はそれを使う", () => {
const $ = cheerioLoad(`<figure>
<img
src="https://images.microcms-assets.io/assets/88f12f69f3bd4e13b769561fe720d255/04a3790c7ab94dff9379025420f60040/blog-template.png"
Expand All @@ -124,7 +124,7 @@ describe("ResponsiveImageTransformer", () => {
>
</figure>`);

await responsiveImageTransformer({
responsiveImageTransformer({
attributes: { sizes: "(max-width: 640px) 100vw, 1200px" },
deviceSizes: [640, 750, 828],
})($);
Expand All @@ -148,35 +148,35 @@ describe("ResponsiveImageTransformer", () => {
expect(imgSrcset).toEqual(expectedImgSrcset);
});

test("正常系: imgタグが存在しない場合は何もしない", async () => {
test("正常系: imgタグが存在しない場合は何もしない", () => {
const $ = cheerioLoad("<div></div>");

await responsiveImageTransformer({
responsiveImageTransformer({
attributes: { sizes: "(max-width: 640px) 100vw, 1200px" },
formats: ["default", "webp"],
})($);

expect($.html()).toEqual("<div></div>");
});

test("異常系: formats に不正な値が含まれている場合はエラー", async () => {
test("異常系: formats に不正な値が含まれている場合はエラー", () => {
const $ = cheerioLoad("<div></div>");

await expect(() =>
expect(() =>
responsiveImageTransformer({
attributes: { sizes: "(max-width: 640px) 100vw, 1200px" },
// @ts-ignore
formats: ["default", "invalid"],
})($),
).rejects.toThrowError("Invalid format: invalid");
).toThrowError("Invalid format: invalid");
});

test("異常系: formats に重複する値が含まれている場合は警告", async () => {
test("異常系: formats に重複する値が含まれている場合は警告", () => {
const $ = cheerioLoad("<div></div>");

const consoleWarnSpy = vi.spyOn(console, "warn");

await responsiveImageTransformer({
responsiveImageTransformer({
attributes: { sizes: "(max-width: 640px) 100vw, 1200px" },
formats: ["default", "default"],
})($);
Expand All @@ -188,12 +188,12 @@ describe("ResponsiveImageTransformer", () => {
consoleWarnSpy.mockRestore();
});

test("異常系: deviceSizes に重複する値が含まれている場合は警告", async () => {
test("異常系: deviceSizes に重複する値が含まれている場合は警告", () => {
const $ = cheerioLoad("<div></div>");

const consoleWarnSpy = vi.spyOn(console, "warn");

await responsiveImageTransformer({
responsiveImageTransformer({
attributes: { sizes: "(max-width: 640px) 100vw, 1200px" },
deviceSizes: [640, 640],
})($);
Expand All @@ -205,7 +205,7 @@ describe("ResponsiveImageTransformer", () => {
consoleWarnSpy.mockRestore();
});

test("異常系: formats が空の場合はエラー", async () => {
test("異常系: formats が空の場合はエラー", () => {
const $ = cheerioLoad(`<figure>
<img
src="https://images.microcms-assets.io/assets/88f12f69f3bd4e13b769561fe720d255/04a3790c7ab94dff9379025420f60040/blog-template.png"
Expand All @@ -215,11 +215,11 @@ describe("ResponsiveImageTransformer", () => {
>
</figure>`);

await expect(() =>
expect(() =>
responsiveImageTransformer({
attributes: { sizes: "(max-width: 640px) 100vw, 1200px" },
formats: [],
})($),
).rejects.toThrowError("At least one format must be specified");
).toThrowError("At least one format must be specified");
});
});
2 changes: 1 addition & 1 deletion src/extractor/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import type { CheerioAPI } from "cheerio";

export type Extractor<T> = ($: CheerioAPI) => Promise<T>;
export type Extractor<T> = ($: CheerioAPI) => Promise<T> | T;
52 changes: 52 additions & 0 deletions src/transformer/attributes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import type { Cheerio, SelectorType } from "cheerio";
import type { Element } from "domhandler";
import type { Transformer } from "./types";

type Options = {
/**
* 処理したい要素の一覧
*/
elements: {
/**
* 要素名
*/
[tagName: string]: {
/**
* 追加した属性名と値の一覧
* 単純文字列のほかに対象の要素を引数に取る関数を指定することもできる
*/
addAttributes: {
[attributeName: string]:
| string
| (($element: Cheerio<Element>) => string);
};
};
};
};

/**
* 任意の要素に対して属性を追加したり変更したりする
*/
const attributesTransformer: (options: Options) => Transformer =
(options) => ($) => {
for (const [_tagName, { addAttributes }] of Object.entries(
options.elements,
)) {
const tagName = _tagName as SelectorType;

$(tagName).each((_, element) => {
const $element = $(element);
for (const [attributeName, attributeValue] of Object.entries(
addAttributes,
)) {
if (typeof attributeValue === "string") {
$element.attr(attributeName, attributeValue);
} else {
$element.attr(attributeName, attributeValue($element));
}
}
});
}
};

export default attributesTransformer;
1 change: 1 addition & 0 deletions src/transformer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export type { Transformer } from "./types";

export { default as responsiveImageTransformer } from "./responsive-image";
export { default as syntaxHighlightingByShikiTransformer } from "./syntax-highlighting-by-shiki";
export { default as attributesTransformer } from "./attributes";
2 changes: 1 addition & 1 deletion src/transformer/responsive-image.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ type Options = {
*/
const responsiveImageTransformer: (
options?: Options | undefined,
) => Transformer = (options) => async ($) => {
) => Transformer = (options) => ($) => {
// バリデーション
if (options?.formats) {
for (const format of options.formats) {
Expand Down
2 changes: 1 addition & 1 deletion src/transformer/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import type { CheerioAPI } from "cheerio";

export type Transformer = ($: CheerioAPI) => Promise<void>;
export type Transformer = ($: CheerioAPI) => Promise<void> | void;

0 comments on commit e14b6cb

Please sign in to comment.