diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index f7a8a9dd..43e35432 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -14,9 +14,9 @@ jobs: steps: - uses: actions/checkout@v3 - - uses: pnpm/action-setup@v2 + - uses: pnpm/action-setup@v4 with: - version: 8 + version: 9 - name: パッケージをインストール run: pnpm install diff --git a/.github/workflows/publish-canary.yml b/.github/workflows/publish-canary.yml index efba9b7f..499897bd 100644 --- a/.github/workflows/publish-canary.yml +++ b/.github/workflows/publish-canary.yml @@ -18,9 +18,9 @@ jobs: - name: Checkout uses: actions/checkout@v3 - - uses: pnpm/action-setup@v2 + - uses: pnpm/action-setup@v4 with: - version: 8 + version: 9 - name: Setup node uses: actions/setup-node@v3 diff --git a/.github/workflows/publish-latest.yml b/.github/workflows/publish-latest.yml index f6adef2e..f785348e 100644 --- a/.github/workflows/publish-latest.yml +++ b/.github/workflows/publish-latest.yml @@ -18,9 +18,9 @@ jobs: - name: Checkout uses: actions/checkout@v3 - - uses: pnpm/action-setup@v2 + - uses: pnpm/action-setup@v4 with: - version: 8 + version: 9 - name: Setup node uses: actions/setup-node@v2 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 299ebde5..df116eb5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,9 +14,9 @@ jobs: steps: - uses: actions/checkout@v3 - - uses: pnpm/action-setup@v2 + - uses: pnpm/action-setup@v4 with: - version: 8 + version: 9 - name: パッケージをインストール run: pnpm install diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..bd5535a6 --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +pnpm-lock.yaml diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 00000000..d7df89c9 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["esbenp.prettier-vscode", "dbaeumer.vscode-eslint"] +} diff --git a/.vscode/settings.json b/.vscode/settings.json index 124dc6d4..dac431c4 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,30 @@ { "editor.formatOnSave": true, "editor.codeActionsOnSave": { - "source.fixAll.eslint": true + "source.fixAll.eslint": "explicit" + }, + "[typescript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[typescriptreact]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[javascript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[json]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[jsonc]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[yaml]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[css]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[scss]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" } } diff --git a/lerna.json b/lerna.json index 4a359871..2c4a9d13 100644 --- a/lerna.json +++ b/lerna.json @@ -2,6 +2,6 @@ "packages": [ "packages/*" ], - "version": "0.1.154", + "version": "0.1.155-alpha.3", "npmClient": "pnpm" } diff --git a/package.json b/package.json index 1eb1ae8a..7cd2ed66 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "devDependencies": { "@lerna-lite/cli": "^1.15.1", "npm-run-all": "^4.1.5", + "prettier": "^3.3.3", "turbo": "^1.7.4" }, "scripts": { @@ -12,6 +13,7 @@ "lint:lockfile": "npx lockfile-lint --path pnpm-lock.yaml --allowed-hosts npm yarn --validate-https", "strict:lint": "turbo strict:lint", "fix": "turbo fix", + "fix:prettier": "turbo fix:prettier", "publish:all": "pnpm test && pnpm build && lerna publish", "publish:canary": "pnpm build && lerna publish --pre-dist-tag canary", "test": "turbo test" @@ -32,6 +34,6 @@ "engines": { "npm": "use pnpm please!", "yarn": "use pnpm please!", - "pnpm": ">=8" + "pnpm": ">=9 <10" } } diff --git a/packages/zenn-cli/.prettierignore b/packages/zenn-cli/.prettierignore index 9b1c8b13..7fcd8656 100644 --- a/packages/zenn-cli/.prettierignore +++ b/packages/zenn-cli/.prettierignore @@ -1 +1,2 @@ /dist +*.md diff --git a/packages/zenn-cli/package.json b/packages/zenn-cli/package.json index 1b234e28..8b5f207a 100644 --- a/packages/zenn-cli/package.json +++ b/packages/zenn-cli/package.json @@ -1,6 +1,6 @@ { "name": "zenn-cli", - "version": "0.1.154", + "version": "0.1.155-alpha.3", "description": "Preview Zenn content locally.", "repository": { "type": "git", @@ -78,7 +78,6 @@ "npm-run-all": "^4.1.5", "open": "^8.4.1", "path": "^0.12.7", - "prettier": "^2.8.4", "react": "^18.2.0", "react-dom": "^18.2.0", "react-icons": "^4.7.1", diff --git a/packages/zenn-cli/src/client/components/Loading.tsx b/packages/zenn-cli/src/client/components/Loading.tsx index 4f4f745e..18b04a52 100644 --- a/packages/zenn-cli/src/client/components/Loading.tsx +++ b/packages/zenn-cli/src/client/components/Loading.tsx @@ -29,5 +29,7 @@ const StyledLoading = styled.div` border: 4px solid var(--c-primary); border-radius: 50%; border-top-color: var(--c-primary-bg); - animation: ${rotate} 0.8s linear infinite, ${fadein} 0.7s; + animation: + ${rotate} 0.8s linear infinite, + ${fadein} 0.7s; `; diff --git a/packages/zenn-cli/src/client/components/books/show/BookHeader.tsx b/packages/zenn-cli/src/client/components/books/show/BookHeader.tsx index 86b29501..5b3b2ea3 100644 --- a/packages/zenn-cli/src/client/components/books/show/BookHeader.tsx +++ b/packages/zenn-cli/src/client/components/books/show/BookHeader.tsx @@ -81,7 +81,8 @@ const StyledBookHeader = styled.header` .book-header__cover-img { background: #fff; object-fit: cover; - box-shadow: 0 9px 20px -9px rgba(0, 27, 68, 0.52), + box-shadow: + 0 9px 20px -9px rgba(0, 27, 68, 0.52), 0 0 3px rgba(0, 21, 60, 0.1); border-radius: 5px; } diff --git a/packages/zenn-cli/src/client/index.html b/packages/zenn-cli/src/client/index.html index a7a4987c..9a4a112b 100644 --- a/packages/zenn-cli/src/client/index.html +++ b/packages/zenn-cli/src/client/index.html @@ -1,4 +1,4 @@ - + diff --git a/packages/zenn-cli/src/server/__tests__/preview/app.test.ts b/packages/zenn-cli/src/server/__tests__/preview/app.test.ts index 30de9e01..4a3e6585 100644 --- a/packages/zenn-cli/src/server/__tests__/preview/app.test.ts +++ b/packages/zenn-cli/src/server/__tests__/preview/app.test.ts @@ -62,7 +62,7 @@ describe('/api/articles/:slug', () => { type: 'tech', topics: [], published: true, - bodyHtml: expect.stringContaining('

Hello!

'), + bodyHtml: expect.stringMatching(/Hello!<\/p>/), }) ); }); @@ -216,7 +216,7 @@ describe('/api/books/:book_slug/chapters/:chapter_filename', () => { title: 'title2', free: true, position: 0, - bodyHtml: expect.stringContaining('

Hello!

'), + bodyHtml: expect.stringMatching(/Hello!<\/p>/), }) ); }); diff --git a/packages/zenn-cli/src/server/commands/init.ts b/packages/zenn-cli/src/server/commands/init.ts index ca08d2db..548874fc 100644 --- a/packages/zenn-cli/src/server/commands/init.ts +++ b/packages/zenn-cli/src/server/commands/init.ts @@ -1,6 +1,10 @@ import { CliExecFn } from '../types'; import * as Log from '../lib/log'; -import { detectPackageExecutor, getWorkingPath, generateFileIfNotExist } from '../lib/helper'; +import { + detectPackageExecutor, + getWorkingPath, + generateFileIfNotExist, +} from '../lib/helper'; import { initHelpText, invalidOptionText } from '../lib/messages'; import arg from 'arg'; diff --git a/packages/zenn-content-css/package.json b/packages/zenn-content-css/package.json index 44528623..e1bc3826 100644 --- a/packages/zenn-content-css/package.json +++ b/packages/zenn-content-css/package.json @@ -1,6 +1,6 @@ { "name": "zenn-content-css", - "version": "0.1.154", + "version": "0.1.155-alpha.3", "license": "MIT", "description": "Zenn flavor content style.", "repository": { @@ -15,7 +15,9 @@ "scripts": { "dev": "sass ./src/index.scss ./lib/index.css --watch --no-source-map", "build": "sass ./src/index.scss ./lib/index.css --style compressed --no-source-map", - "test": "echo 'no test yet'" + "test": "echo 'no test yet'", + "lint:prettier": "prettier ./src", + "fix:prettier": "prettier -w ./src" }, "devDependencies": { "sass": "^1.58.1" diff --git a/packages/zenn-embed-elements/package.json b/packages/zenn-embed-elements/package.json index 5cc08c5a..5fc06e14 100644 --- a/packages/zenn-embed-elements/package.json +++ b/packages/zenn-embed-elements/package.json @@ -1,6 +1,6 @@ { "name": "zenn-embed-elements", - "version": "0.1.154", + "version": "0.1.155-alpha.3", "license": "MIT", "description": "Web components for embedded contents.", "repository": { @@ -17,7 +17,9 @@ "build": "pnpm clean && run-p build:*", "build:tsc": "tsc -p .", "lint": "eslint . --ext .ts,.tsx", + "lint:prettier": "prettier .", "fix": "eslint . --ext .ts,.tsx --fix", + "fix:prettier": "prettier -w .", "strict:lint": "eslint . --ext .ts,.tsx --max-warnings 0", "test": "echo 'no test yet'" }, diff --git a/packages/zenn-markdown-html/__tests__/basic.test.ts b/packages/zenn-markdown-html/__tests__/basic.test.ts index a809afb9..32c19e00 100644 --- a/packages/zenn-markdown-html/__tests__/basic.test.ts +++ b/packages/zenn-markdown-html/__tests__/basic.test.ts @@ -1,14 +1,23 @@ import { describe, test, expect } from 'vitest'; import markdownToHtml from '../src/index'; +import { parse } from 'node-html-parser'; describe('MarkdownからHTMLへの変換テスト', () => { test('markdownからhtmlへ変換する', () => { const html = markdownToHtml('Hello\n## hey\n\n- first\n- second\n'); - expect(html).toContain(`

Hello

`); - expect(html).toContain( - `

hey

` + const p = parse(html).querySelector('p'); + const h2 = parse(html).querySelector('h2'); + const ul = parse(html).querySelector('ul'); + const liElms = parse(html).querySelectorAll('li'); + + expect(p?.innerHTML).toBe('Hello'); + expect(h2?.innerHTML).toBe( + ' hey' ); - expect(html).toContain(`\n`); + expect(ul).not.toBeNull(); + expect(liElms?.length).toBe(2); + expect(liElms[0].innerHTML).toBe('first'); + expect(liElms[1].innerHTML).toBe('second'); }); test('インラインコメントはhtmlに変換しない', () => { diff --git a/packages/zenn-markdown-html/__tests__/br.test.ts b/packages/zenn-markdown-html/__tests__/br.test.ts index ef0e44cb..b9058dad 100644 --- a/packages/zenn-markdown-html/__tests__/br.test.ts +++ b/packages/zenn-markdown-html/__tests__/br.test.ts @@ -6,7 +6,7 @@ describe('
のテスト', () => { const patterns = ['foo
bar', 'foo
bar', 'foo
bar']; patterns.forEach((pattern) => { const html = markdownToHtml(pattern); - expect(html).toMatch(/

foo
bar<\/p>/); + expect(html).toContain('foo
bar'); }); }); test('テーブル内の
は保持する', () => { @@ -20,7 +20,7 @@ describe('
のテスト', () => { }); test('インラインコード内の
はエスケープする', () => { const html = markdownToHtml('foo`
`bar'); - expect(html).toMatch(/

foo<br><\/code>bar<\/p>/); + expect(html).toContain('foo<br>bar'); }); test('コードブロック内の
はエスケープする', () => { const html = markdownToHtml('```\n
\n```'); diff --git a/packages/zenn-markdown-html/__tests__/dollar.test.ts b/packages/zenn-markdown-html/__tests__/dollar.test.ts index 083d141c..98d97b51 100644 --- a/packages/zenn-markdown-html/__tests__/dollar.test.ts +++ b/packages/zenn-markdown-html/__tests__/dollar.test.ts @@ -4,22 +4,22 @@ import markdownToHtml from '../src/index'; describe('$ マークのテスト', () => { test('リンクと同じ行にある $ は katex に変換される', () => { const html = markdownToHtml('$a,b,c$foo[foo](https://foo.bar)bar'); - expect(html).toEqual( - '

a,b,cfoofoobar

\n' + expect(html).toContain( + 'a,b,cfoofoobar' ); }); test('リンクの後に無効な $ が続く場合はそのままにする', () => { const html = markdownToHtml('$a,b,c$foo[foo](http://foo.bar)$bar'); - expect(html).toEqual( - '

a,b,cfoofoo$bar

\n' + expect(html).toContain( + 'a,b,cfoofoo$bar' ); }); test('リンク名に $ が含まれる場合はそのままにする', () => { const html = markdownToHtml('$a,b,c$foo[$bar](http://foo.bar)bar'); - expect(html).toEqual( - '

a,b,cfoo$barbar

\n' + expect(html).toContain( + 'a,b,cfoo$barbar' ); }); test('リンクのhrefに $ が含まれる場合はそのままにする', () => { @@ -35,8 +35,8 @@ describe('$ マークのテスト', () => { describe('HTMLタグにエスケープするテスト', () => { test('katex内のをエスケープする', () => { const html = markdownToHtml('$a,,c$'); - expect(html).toEqual( - `

a,<script>alert("XSS")</script>,c

\n` + expect(html).toContain( + `a,<script>alert("XSS")</script>,c` ); }); }); @@ -44,36 +44,36 @@ describe('HTMLタグにエスケープするテスト', () => { describe('$ のペアのテスト', () => { test('リンクの前後にある一文字だけを含む$のペアをkatexに変換する', () => { const html = markdownToHtml('$a$foo[foo](https://foo.bar)bar,refs:$(2)$'); - expect(html).toEqual( - '

afoofoobar,refs:(2)

\n' + expect(html).toContain( + 'afoofoobar,refs:(2)' ); }); test('リンク前後にある$のペアをkatexに変換する', () => { const html = markdownToHtml( '$a,b,c$foo[foo](https://foo.bar)bar,refs:$(2)$' ); - expect(html).toEqual( - '

a,b,cfoofoobar,refs:(2)

\n' + expect(html).toContain( + 'a,b,cfoofoobar,refs:(2)' ); }); test('リンク前後にある三つの$のペアをkatexに変換する', () => { const html = markdownToHtml( '$a,b,c$foo[foo](https://foo.bar)bar,refs:$(2)$,and:$(3)$' ); - expect(html).toEqual( - '

a,b,cfoofoobar,refs:(2),and:(3)

\n' + expect(html).toContain( + 'a,b,cfoofoobar,refs:(2),and:(3)' ); }); test('リンク周りにある$のペアをkatexに変換する', () => { const html = markdownToHtml('$a,b,c$foo[foo](https://foo.bar)bar,refs:$2$'); - expect(html).toEqual( - '

a,b,cfoofoobar,refs:2

\n' + expect(html).toContain( + 'a,b,cfoofoobar,refs:2' ); }); test('二つの$のペアをkatexに変換する', () => { const html = markdownToHtml('$a,b,c$foobar,refs:$(2)$'); - expect(html).toEqual( - '

a,b,cfoobar,refs:(2)

\n' + expect(html).toContain( + 'a,b,cfoobar,refs:(2)' ); }); }); diff --git a/packages/zenn-markdown-html/__tests__/highlight.test.ts b/packages/zenn-markdown-html/__tests__/highlight.test.ts index 6d738de7..82f7ad7c 100644 --- a/packages/zenn-markdown-html/__tests__/highlight.test.ts +++ b/packages/zenn-markdown-html/__tests__/highlight.test.ts @@ -2,6 +2,7 @@ import loadLanguages from 'prismjs/components/index'; import { describe, test, expect } from 'vitest'; import markdownToHtml from '../src/index'; +import parse from 'node-html-parser'; // markdownToHtml で diff を使っているので、あらかじめ読み込んでおく loadLanguages('diff'); @@ -11,7 +12,10 @@ describe('コードハイライトのテスト', () => { const html = markdownToHtml( `\`\`\`js:foo.js\nconsole.log("hello")\n\`\`\`` ); - expect(html).toContain(''); + // が取得できないので
で取得する
+    const pre: any = parse(html).querySelector('pre');
+    const code = parse(pre?.innerHTML).querySelector('code.language-js');
+    expect(code).toBeTruthy();
     expect(html).toContain('foo.js');
   });
 
diff --git a/packages/zenn-markdown-html/__tests__/link.test.ts b/packages/zenn-markdown-html/__tests__/link.test.ts
index 93ca3b4f..f2724140 100644
--- a/packages/zenn-markdown-html/__tests__/link.test.ts
+++ b/packages/zenn-markdown-html/__tests__/link.test.ts
@@ -51,29 +51,24 @@ describe('Linkifyのテスト', () => {
 
     test('URLの前にテキストが存在する場合はリンクをリンクカードに変換しない', () => {
       const html = renderLink('foo https://example.com');
-      expect(html).toEqual(
-        '

foo https://example.com

\n' + expect(html).toContain( + 'foo https://example.com' ); }); test('意図的にリンクしているURLはリンクカードに変換しない', () => { const html = renderLink('[https://example.com](https://example.com)'); - expect(html).toEqual( - '

https://example.com

\n' - ); - }); - - test('リンク内のリンクを変換しない', () => { - const html = renderLink('- https://example.com\n- second'); - expect(html).toEqual( - '\n' + expect(html).toContain( + 'https://example.com' ); }); test('
内のリンクはリンクカードに変換しない', () => { const html = renderLink(':::message alert\nhttps://example.com\n:::'); - expect(html).toEqual( - '\n' + const iframe = parse(html).querySelector('aside iframe'); + expect(iframe).toBeNull(); + expect(html).toContain( + 'https://example.com' ); }); @@ -81,43 +76,48 @@ describe('Linkifyのテスト', () => { const html = renderLink( ':::message alert\nhello\n\nhttps://example.com\n:::' ); + const iframes = parse(html).querySelectorAll('aside iframe'); + expect(iframes.length).toBe(0); + console.log(html); expect(html).toContain( - '' + 'https://example.com' ); }); test('リスト内のリンクをリンクカードに変換しない', () => { const html = renderLink('- https://example.com\n- second'); - expect(html).toEqual( - '\n' + const iframe = parse(html).querySelector('aside iframe'); + expect(iframe).toBeNull(); + expect(html).toContain( + 'https://example.com' ); }); test('URLにテキストが続く場合はリンクカードに変換しない', () => { const html = renderLink('https://example.com foo'); - expect(html).toEqual( - '

https://example.com foo

\n' + expect(html).toContain( + 'https://example.com foo' ); }); test('同じ段落内のテキストを含むリンクをリンクカードに変換しない', () => { const html = renderLink(`a: https://example.com\nb: https://example.com`); - expect(html).toEqual( - '

a: https://example.com
\nb: https://example.com

\n' + expect(html).toContain( + 'a: https://example.com
\nb: https://example.com' ); }); test('URLにテキストが続くならリンクが先頭であってもリンクカードに変換しない', () => { const html = renderLink('\n\nhttps://example.com text'); - expect(html).toEqual( - '

https://example.com text

\n' + expect(html).toContain( + 'https://example.com text' ); }); test('URLの前にテキストがあるならリンクが行末でもリンクカードに変換しない', () => { const html = renderLink('text https://example.com\n\n'); - expect(html).toEqual( - '

text https://example.com

\n' + expect(html).toContain( + 'text https://example.com' ); }); }); diff --git a/packages/zenn-markdown-html/__tests__/matchers/isCodesandboxUrl.test.ts b/packages/zenn-markdown-html/__tests__/matchers/isCodesandboxUrl.test.ts index c7ea6708..cbb87fbc 100644 --- a/packages/zenn-markdown-html/__tests__/matchers/isCodesandboxUrl.test.ts +++ b/packages/zenn-markdown-html/__tests__/matchers/isCodesandboxUrl.test.ts @@ -4,8 +4,7 @@ import { isCodesandboxUrl } from '../../src/utils/url-matcher'; describe('isCodesandboxUrlのテスト', () => { describe('Trueを返す場合', () => { test('Codesandboxの埋め込みURL', () => { - const url = - 'https://codesandbox.io/embed/new?view=Editor+%2B+Preview'; + const url = 'https://codesandbox.io/embed/new?view=Editor+%2B+Preview'; expect(isCodesandboxUrl(url)).toBe(true); }); diff --git a/packages/zenn-markdown-html/__tests__/source-map.test.ts b/packages/zenn-markdown-html/__tests__/source-map.test.ts new file mode 100644 index 00000000..26be12d5 --- /dev/null +++ b/packages/zenn-markdown-html/__tests__/source-map.test.ts @@ -0,0 +1,127 @@ +import { describe, test, expect } from "vitest"; +import markdownToHtml from "../src/index"; +import parse from "node-html-parser"; + +describe("ソースマップ(data-line属性)のテスト", () => { + test("Header", () => { + const html = markdownToHtml(`# Header1 +## Header2 +### Header3 +#### Header4 +##### Header5 +###### Header6`); + const h1 = parse(html).querySelector("h1"); + const h2 = parse(html).querySelector("h2"); + const h3 = parse(html).querySelector("h3"); + const h4 = parse(html).querySelector("h4"); + const h5 = parse(html).querySelector("h5"); + const h6 = parse(html).querySelector("h6"); + expect(h1?.getAttribute("data-line")).toEqual("0"); + expect(h1?.classList.contains("code-line")).toBe(true); + expect(h2?.getAttribute("data-line")).toEqual("1"); + expect(h2?.classList.contains("code-line")).toBe(true); + expect(h3?.getAttribute("data-line")).toEqual("2"); + expect(h3?.classList.contains("code-line")).toBe(true); + expect(h4?.getAttribute("data-line")).toEqual("3"); + expect(h4?.classList.contains("code-line")).toBe(true); + expect(h5?.getAttribute("data-line")).toEqual("4"); + expect(h5?.classList.contains("code-line")).toBe(true); + expect(h6?.getAttribute("data-line")).toEqual("5"); + expect(h6?.classList.contains("code-line")).toBe(true); + }); + + test("Paragraph", () => { + const html = markdownToHtml(`Paragraph1\n\nhttps://example.com`); + const p = parse(html).querySelectorAll("p"); + expect(p?.[0].getAttribute("data-line")).toEqual("0"); + expect(p?.[0].classList.contains("code-line")).toBe(true); + expect(p?.[1].getAttribute("data-line")).toEqual("2"); + expect(p?.[1].classList.contains("code-line")).toBe(true); + }); + + test("List(unordered)", () => { + const html = markdownToHtml(`- item1\n- item2`); + const ul = parse(html).querySelector("ul"); + const li = parse(html).querySelectorAll("li"); + expect(ul?.getAttribute("data-line")).toEqual("0"); + expect(ul?.classList.contains("code-line")).toBe(true); + expect(li?.[0].getAttribute("data-line")).toEqual("0"); + expect(li?.[0].classList.contains("code-line")).toBe(true); + expect(li?.[1].getAttribute("data-line")).toEqual("1"); + expect(li?.[1].classList.contains("code-line")).toBe(true); + }); + + test("List(ordered)", () => { + const html = markdownToHtml(`1. item1\n2. item2`); + const ol = parse(html).querySelector("ol"); + const li = parse(html).querySelectorAll("li"); + expect(ol?.getAttribute("data-line")).toEqual("0"); + expect(ol?.classList.contains("code-line")).toBe(true); + expect(li?.[0].getAttribute("data-line")).toEqual("0"); + expect(li?.[0].classList.contains("code-line")).toBe(true); + expect(li?.[1].getAttribute("data-line")).toEqual("1"); + expect(li?.[1].classList.contains("code-line")).toBe(true); + }); + + test("Table", () => { + const html = markdownToHtml(`| a | b |\n| --- | --- |\n| c | d |`); + const table = parse(html).querySelector("table"); + const thead = parse(html).querySelector("thead"); + const tbody = parse(html).querySelector("tbody"); + const tr = parse(html).querySelectorAll("tr"); + expect(table?.getAttribute("data-line")).toEqual("0"); + expect(table?.classList.contains("code-line")).toBe(true); + expect(thead?.getAttribute("data-line")).toEqual("0"); + expect(thead?.classList.contains("code-line")).toBe(true); + expect(tbody?.getAttribute("data-line")).toEqual("2"); + expect(tbody?.classList.contains("code-line")).toBe(true); + expect(tr?.[0].getAttribute("data-line")).toEqual("0"); + expect(tr?.[0].classList.contains("code-line")).toBe(true); + expect(tr?.[1].getAttribute("data-line")).toEqual("2"); + expect(tr?.[1].classList.contains("code-line")).toBe(true); + }); + + test("Code Block", () => { + const html = markdownToHtml("```\ncode\n```"); + // が取得できないので
で取得する
+    const innerHTML: any = parse(html).querySelector("pre")?.innerHTML;
+    const code = parse(innerHTML).querySelector("code");
+    expect(code?.getAttribute("data-line")).toEqual("0"); // フェンス開始時の行番号
+    expect(code?.classList.contains("code-line")).toBe(true);
+  });
+
+  test("Katex", () => {
+    const html = markdownToHtml(`$$\na\n$$`);
+    const katex = parse(html).querySelector("section");
+    expect(katex?.getAttribute("data-line")).toEqual("0");
+    expect(katex?.classList.contains("code-line")).toBe(true);
+  });
+
+  test("Blockquote", () => {
+    const html = markdownToHtml("> quote");
+    const blockquote = parse(html).querySelector("blockquote");
+    expect(blockquote?.getAttribute("data-line")).toEqual("0");
+    expect(blockquote?.classList.contains("code-line")).toBe(true);
+  });
+
+  test("Horizontal Rule", () => {
+    const html = markdownToHtml(`---`);
+    const hr = parse(html).querySelector("hr");
+    expect(hr?.getAttribute("data-line")).toEqual("0");
+    expect(hr?.classList.contains("code-line")).toBe(true);
+  });
+
+  test("Alert", () => {
+    const html = markdownToHtml(":::message\nhello\n:::");
+    const p = parse(html).querySelector("p");
+    expect(p?.getAttribute("data-line")).toEqual("1");
+    expect(p?.classList.contains("code-line")).toBe(true);
+  });
+
+  test("Details/Summary", () => {
+    const html = markdownToHtml(`:::details タイトル\nhello\n:::`);
+    const p = parse(html).querySelector("p");
+    expect(p?.getAttribute("data-line")).toEqual("1");
+    expect(p?.classList.contains("code-line")).toBe(true);
+  });
+});
diff --git a/packages/zenn-markdown-html/__tests__/xss.test.ts b/packages/zenn-markdown-html/__tests__/xss.test.ts
index ef773a7b..99b8c003 100644
--- a/packages/zenn-markdown-html/__tests__/xss.test.ts
+++ b/packages/zenn-markdown-html/__tests__/xss.test.ts
@@ -4,12 +4,12 @@ import markdownToHtml from '../src/index';
 describe('XSS脆弱性のテスト', () => {
   test('');
-    expect(html).toMatch('

<script>alert("XSS!")</script>

'); + expect(html).toMatch('<script>alert("XSS!")</script>'); }); test('"javascript:"構文をリンクにせずにそのままにする', () => { const html = markdownToHtml('javascript:alert(1)'); - expect(html).toMatch('

javascript:alert(1)

'); + expect(html).toMatch('javascript:alert(1)'); }); test('katex内のをエスケープする', () => { @@ -30,8 +30,8 @@ describe('XSS脆弱性のテスト', () => { const html = markdownToHtml( `\`\`\`">\nany\n\`\`\`` ); - expect(html).toContain( - '
any\n
' + expect(html).toBe( + '
any\n
' ); }); test('コードブロックのファイル名に仕込まれた\nany\n\`\`\`` ); expect(html).toContain( - '
<script>alert("XSS")</script>
any\n
' + '<script>alert("XSS")</script>' ); }); test('コードブロック内の