Skip to content

Commit e14b6cb

Browse files
committed
attributesTransformerを追加し、要素に属性を追加する機能を実装
1 parent 316d369 commit e14b6cb

File tree

7 files changed

+106
-23
lines changed

7 files changed

+106
-23
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { describe, expect, test } from "vitest";
2+
import attributesTransformer from "../../src/transformer/attributes";
3+
import { cheerioLoad } from "../test-utils";
4+
5+
describe("attributesTransformer", () => {
6+
test("正常系: 要素に属性が追加されること", async () => {
7+
const $ = cheerioLoad(`
8+
<div id="id"></div>
9+
`);
10+
11+
await attributesTransformer({
12+
elements: {
13+
div: {
14+
addAttributes: {
15+
class: "test-class",
16+
"data-test": "test-value",
17+
id: ($element) => {
18+
const originalId = $element.attr("id");
19+
return `${originalId}-modified`;
20+
},
21+
},
22+
},
23+
},
24+
})($);
25+
26+
expect($.html()).toBe(`
27+
<div id="id-modified" class="test-class" data-test="test-value"></div>
28+
`);
29+
});
30+
});

__tests__/transformer/responsive-image.test.ts

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import responsiveImageTransformer from "../../src/transformer/responsive-image";
33
import { cheerioLoad } from "../test-utils";
44

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

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

@@ -54,7 +54,7 @@ describe("ResponsiveImageTransformer", () => {
5454
expect(height).toEqual(expectedHeight);
5555
});
5656

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

67-
await responsiveImageTransformer({
67+
responsiveImageTransformer({
6868
attributes: {
6969
style: "border: 1px solid red",
7070
},
@@ -80,7 +80,7 @@ describe("ResponsiveImageTransformer", () => {
8080
expect(style).toEqual(expectedStyle);
8181
});
8282

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

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

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

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

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

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

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

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

165-
await expect(() =>
165+
expect(() =>
166166
responsiveImageTransformer({
167167
attributes: { sizes: "(max-width: 640px) 100vw, 1200px" },
168168
// @ts-ignore
169169
formats: ["default", "invalid"],
170170
})($),
171-
).rejects.toThrowError("Invalid format: invalid");
171+
).toThrowError("Invalid format: invalid");
172172
});
173173

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

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

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

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

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

196-
await responsiveImageTransformer({
196+
responsiveImageTransformer({
197197
attributes: { sizes: "(max-width: 640px) 100vw, 1200px" },
198198
deviceSizes: [640, 640],
199199
})($);
@@ -205,7 +205,7 @@ describe("ResponsiveImageTransformer", () => {
205205
consoleWarnSpy.mockRestore();
206206
});
207207

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

218-
await expect(() =>
218+
expect(() =>
219219
responsiveImageTransformer({
220220
attributes: { sizes: "(max-width: 640px) 100vw, 1200px" },
221221
formats: [],
222222
})($),
223-
).rejects.toThrowError("At least one format must be specified");
223+
).toThrowError("At least one format must be specified");
224224
});
225225
});

src/extractor/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
import type { CheerioAPI } from "cheerio";
22

3-
export type Extractor<T> = ($: CheerioAPI) => Promise<T>;
3+
export type Extractor<T> = ($: CheerioAPI) => Promise<T> | T;

src/transformer/attributes.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import type { Cheerio, SelectorType } from "cheerio";
2+
import type { Element } from "domhandler";
3+
import type { Transformer } from "./types";
4+
5+
type Options = {
6+
/**
7+
* 処理したい要素の一覧
8+
*/
9+
elements: {
10+
/**
11+
* 要素名
12+
*/
13+
[tagName: string]: {
14+
/**
15+
* 追加した属性名と値の一覧
16+
* 単純文字列のほかに対象の要素を引数に取る関数を指定することもできる
17+
*/
18+
addAttributes: {
19+
[attributeName: string]:
20+
| string
21+
| (($element: Cheerio<Element>) => string);
22+
};
23+
};
24+
};
25+
};
26+
27+
/**
28+
* 任意の要素に対して属性を追加したり変更したりする
29+
*/
30+
const attributesTransformer: (options: Options) => Transformer =
31+
(options) => ($) => {
32+
for (const [_tagName, { addAttributes }] of Object.entries(
33+
options.elements,
34+
)) {
35+
const tagName = _tagName as SelectorType;
36+
37+
$(tagName).each((_, element) => {
38+
const $element = $(element);
39+
for (const [attributeName, attributeValue] of Object.entries(
40+
addAttributes,
41+
)) {
42+
if (typeof attributeValue === "string") {
43+
$element.attr(attributeName, attributeValue);
44+
} else {
45+
$element.attr(attributeName, attributeValue($element));
46+
}
47+
}
48+
});
49+
}
50+
};
51+
52+
export default attributesTransformer;

src/transformer/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ export type { Transformer } from "./types";
22

33
export { default as responsiveImageTransformer } from "./responsive-image";
44
export { default as syntaxHighlightingByShikiTransformer } from "./syntax-highlighting-by-shiki";
5+
export { default as attributesTransformer } from "./attributes";

src/transformer/responsive-image.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ type Options = {
4444
*/
4545
const responsiveImageTransformer: (
4646
options?: Options | undefined,
47-
) => Transformer = (options) => async ($) => {
47+
) => Transformer = (options) => ($) => {
4848
// バリデーション
4949
if (options?.formats) {
5050
for (const format of options.formats) {

src/transformer/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
import type { CheerioAPI } from "cheerio";
22

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

0 commit comments

Comments
 (0)