diff --git a/packages/integrations/mdx/package.json b/packages/integrations/mdx/package.json index 95fe5f8d5aab..bfae15c3f5a6 100644 --- a/packages/integrations/mdx/package.json +++ b/packages/integrations/mdx/package.json @@ -31,7 +31,8 @@ "build": "astro-scripts build \"src/**/*.ts\" && tsc", "build:ci": "astro-scripts build \"src/**/*.ts\"", "dev": "astro-scripts dev \"src/**/*.ts\"", - "test": "astro-scripts test --timeout 70000 \"test/**/*.test.js\"" + "test": "astro-scripts test --timeout 70000 \"test/**/*.test.ts\"", + "typecheck:tests": "tsc --build tsconfig.test.json" }, "dependencies": { "@astrojs/markdown-remark": "workspace:*", diff --git a/packages/integrations/mdx/test/css-head-mdx.test.js b/packages/integrations/mdx/test/css-head-mdx.test.ts similarity index 97% rename from packages/integrations/mdx/test/css-head-mdx.test.js rename to packages/integrations/mdx/test/css-head-mdx.test.ts index 81f1ee49170e..94ae75527b85 100644 --- a/packages/integrations/mdx/test/css-head-mdx.test.js +++ b/packages/integrations/mdx/test/css-head-mdx.test.ts @@ -3,10 +3,10 @@ import { before, describe, it } from 'node:test'; import mdx from '@astrojs/mdx'; import * as cheerio from 'cheerio'; import { parseHTML } from 'linkedom'; -import { loadFixture } from '../../../astro/test/test-utils.js'; +import { loadFixture, type Fixture } from '../../../astro/test/test-utils.js'; describe('Head injection w/ MDX', () => { - let fixture; + let fixture: Fixture; before(async () => { fixture = await loadFixture({ diff --git a/packages/integrations/mdx/test/invalid-mdx-component.test.js b/packages/integrations/mdx/test/invalid-mdx-component.test.ts similarity index 76% rename from packages/integrations/mdx/test/invalid-mdx-component.test.js rename to packages/integrations/mdx/test/invalid-mdx-component.test.ts index b8152e89c718..e6bfb248576c 100644 --- a/packages/integrations/mdx/test/invalid-mdx-component.test.js +++ b/packages/integrations/mdx/test/invalid-mdx-component.test.ts @@ -1,12 +1,12 @@ import * as assert from 'node:assert/strict'; import { before, describe, it } from 'node:test'; -import { loadFixture } from '../../../astro/test/test-utils.js'; +import { loadFixture, type Fixture } from '../../../astro/test/test-utils.js'; import mdx from '../dist/index.js'; const FIXTURE_ROOT = new URL('./fixtures/invalid-mdx-component/', import.meta.url); describe('MDX component with runtime error', () => { - let fixture; + let fixture: Fixture; before(async () => { fixture = await loadFixture({ @@ -16,22 +16,21 @@ describe('MDX component with runtime error', () => { }); describe('build', () => { - /** @type {Error | null} */ - let error; + let error: Error | null = null; before(async () => { error = null; try { await fixture.build(); } catch (e) { - error = e; + error = e as Error; } }); it('Throws the right error', async () => { assert.ok(error); assert.match( - error?.hint, + (error as Error & { hint?: string })?.hint ?? '', /This issue often occurs when your MDX component encounters runtime errors/, ); }); diff --git a/packages/integrations/mdx/test/mdx-astro-container-escape.test.js b/packages/integrations/mdx/test/mdx-astro-container-escape.test.ts similarity index 86% rename from packages/integrations/mdx/test/mdx-astro-container-escape.test.js rename to packages/integrations/mdx/test/mdx-astro-container-escape.test.ts index e3a7df509f13..106a10cdac79 100644 --- a/packages/integrations/mdx/test/mdx-astro-container-escape.test.js +++ b/packages/integrations/mdx/test/mdx-astro-container-escape.test.ts @@ -1,10 +1,10 @@ import assert from 'node:assert/strict'; import { before, describe, it } from 'node:test'; import * as cheerio from 'cheerio'; -import { loadFixture } from '../../../astro/test/test-utils.js'; +import { loadFixture, type Fixture } from '../../../astro/test/test-utils.js'; describe('MDX Component & Astro Container escape issue', () => { - let fixture; + let fixture: Fixture; before(async () => { fixture = await loadFixture({ diff --git a/packages/integrations/mdx/test/mdx-astro-markdown-remarkRehype.test.js b/packages/integrations/mdx/test/mdx-astro-markdown-remarkRehype.test.ts similarity index 79% rename from packages/integrations/mdx/test/mdx-astro-markdown-remarkRehype.test.js rename to packages/integrations/mdx/test/mdx-astro-markdown-remarkRehype.test.ts index f8cb3c98ff9a..b63c049c5252 100644 --- a/packages/integrations/mdx/test/mdx-astro-markdown-remarkRehype.test.js +++ b/packages/integrations/mdx/test/mdx-astro-markdown-remarkRehype.test.ts @@ -21,9 +21,9 @@ describe('MDX with Astro Markdown remark-rehype config', () => { const html = await fixture.readFile('/index.html'); const { document } = parseHTML(html); - assert.equal(document.querySelector('#footnote-label').textContent, 'Catatan kaki'); + assert.equal(document.querySelector('#footnote-label')!.textContent, 'Catatan kaki'); assert.equal( - document.querySelector('.data-footnote-backref').getAttribute('aria-label'), + document.querySelector('.data-footnote-backref')!.getAttribute('aria-label'), 'Kembali ke konten', ); }); @@ -50,9 +50,9 @@ describe('MDX with Astro Markdown remark-rehype config', () => { const html = await fixture.readFile('/index.html'); const { document } = parseHTML(html); - assert.equal(document.querySelector('#footnote-label').textContent, 'Catatan kaki'); + assert.equal(document.querySelector('#footnote-label')!.textContent, 'Catatan kaki'); assert.equal( - document.querySelector('.data-footnote-backref').getAttribute('aria-label'), + document.querySelector('.data-footnote-backref')!.getAttribute('aria-label'), 'Kembali ke konten', ); }); @@ -62,7 +62,6 @@ describe('MDX with Astro Markdown remark-rehype config', () => { root: new URL('./fixtures/mdx-astro-markdown-remarkRehype/', import.meta.url), integrations: [ mdx({ - extendPlugins: 'astroDefaults', remarkRehype: { footnoteLabel: 'Catatan kaki', }, @@ -79,9 +78,9 @@ describe('MDX with Astro Markdown remark-rehype config', () => { const html = await fixture.readFile('/index.html'); const { document } = parseHTML(html); - assert.equal(document.querySelector('#footnote-label').textContent, 'Catatan kaki'); + assert.equal(document.querySelector('#footnote-label')!.textContent, 'Catatan kaki'); assert.equal( - document.querySelector('.data-footnote-backref').getAttribute('aria-label'), + document.querySelector('.data-footnote-backref')!.getAttribute('aria-label'), 'Back to reference 1', ); }); diff --git a/packages/integrations/mdx/test/mdx-basics.test.js b/packages/integrations/mdx/test/mdx-basics.test.ts similarity index 92% rename from packages/integrations/mdx/test/mdx-basics.test.js rename to packages/integrations/mdx/test/mdx-basics.test.ts index b2e4de4818fa..064c2bb0062a 100644 --- a/packages/integrations/mdx/test/mdx-basics.test.js +++ b/packages/integrations/mdx/test/mdx-basics.test.ts @@ -3,7 +3,7 @@ import { after, before, describe, it } from 'node:test'; import mdx from '@astrojs/mdx'; import * as cheerio from 'cheerio'; import { parseHTML } from 'linkedom'; -import { loadFixture } from '../../../astro/test/test-utils.js'; +import { loadFixture, type Fixture, type DevServer } from '../../../astro/test/test-utils.js'; // Merged fixture: combines mdx-component, mdx-slots, mdx-frontmatter, // mdx-url-export, mdx-get-static-paths, and mdx-script-style-raw. @@ -11,7 +11,7 @@ import { loadFixture } from '../../../astro/test/test-utils.js'; const FIXTURE_ROOT = new URL('./fixtures/mdx-basics/', import.meta.url); describe('MDX basics (merged fixture)', () => { - let fixture; + let fixture: Fixture; before(async () => { fixture = await loadFixture({ @@ -32,8 +32,8 @@ describe('MDX basics (merged fixture)', () => { const html = await fixture.readFile('/component/index.html'); const { document } = parseHTML(html); - const h1 = document.querySelector('h1'); - const foo = document.querySelector('#foo'); + const h1 = document.querySelector('h1')!; + const foo = document.querySelector('#foo')!; assert.equal(h1.textContent, 'Hello component!'); assert.equal(foo.textContent, 'bar'); @@ -43,8 +43,8 @@ describe('MDX basics (merged fixture)', () => { const html = await fixture.readFile('/component/glob/index.html'); const { document } = parseHTML(html); - const h1 = document.querySelector('[data-default-export] h1'); - const foo = document.querySelector('[data-default-export] #foo'); + const h1 = document.querySelector('[data-default-export] h1')!; + const foo = document.querySelector('[data-default-export] #foo')!; assert.equal(h1.textContent, 'Hello component!'); assert.equal(foo.textContent, 'bar'); @@ -54,8 +54,8 @@ describe('MDX basics (merged fixture)', () => { const html = await fixture.readFile('/component/glob/index.html'); const { document } = parseHTML(html); - const h1 = document.querySelector('[data-content-export] h1'); - const foo = document.querySelector('[data-content-export] #foo'); + const h1 = document.querySelector('[data-content-export] h1')!; + const foo = document.querySelector('[data-content-export] #foo')!; assert.equal(h1.textContent, 'Hello component!'); assert.equal(foo.textContent, 'bar'); @@ -66,8 +66,8 @@ describe('MDX basics (merged fixture)', () => { const html = await fixture.readFile('/component/w-fragment/index.html'); const { document } = parseHTML(html); - const h1 = document.querySelector('h1'); - const p = document.querySelector('p'); + const h1 = document.querySelector('h1')!; + const p = document.querySelector('p')!; assert.equal(h1.textContent, 'MDX containing '); assert.equal(p.textContent, 'bar'); @@ -79,10 +79,10 @@ describe('MDX basics (merged fixture)', () => { const h = document.querySelector( '[data-default-export] [data-file="WithFragment.mdx"] h1', - ); + )!; const p = document.querySelector( '[data-default-export] [data-file="WithFragment.mdx"] p', - ); + )!; assert.equal(h.textContent, 'MDX containing '); assert.equal(p.textContent, 'bar'); @@ -94,10 +94,10 @@ describe('MDX basics (merged fixture)', () => { const h = document.querySelector( '[data-content-export] [data-file="WithFragment.mdx"] h1', - ); + )!; const p = document.querySelector( '[data-content-export] [data-file="WithFragment.mdx"] p', - ); + )!; assert.equal(h.textContent, 'MDX containing '); assert.equal(p.textContent, 'bar'); @@ -112,9 +112,9 @@ describe('MDX basics (merged fixture)', () => { const html = await fixture.readFile('/slots/index.html'); const { document } = parseHTML(html); - const h1 = document.querySelector('h1'); - const defaultSlot = document.querySelector('[data-default-slot]'); - const namedSlot = document.querySelector('[data-named-slot]'); + const h1 = document.querySelector('h1')!; + const defaultSlot = document.querySelector('[data-default-slot]')!; + const namedSlot = document.querySelector('[data-named-slot]')!; assert.equal(h1.textContent, 'Hello slotted component!'); assert.equal(defaultSlot.textContent, 'Default content.'); @@ -125,9 +125,9 @@ describe('MDX basics (merged fixture)', () => { const html = await fixture.readFile('/slots/glob/index.html'); const { document } = parseHTML(html); - const h1 = document.querySelector('[data-default-export] h1'); - const defaultSlot = document.querySelector('[data-default-export] [data-default-slot]'); - const namedSlot = document.querySelector('[data-default-export] [data-named-slot]'); + const h1 = document.querySelector('[data-default-export] h1')!; + const defaultSlot = document.querySelector('[data-default-export] [data-default-slot]')!; + const namedSlot = document.querySelector('[data-default-export] [data-named-slot]')!; assert.equal(h1.textContent, 'Hello slotted component!'); assert.equal(defaultSlot.textContent, 'Default content.'); @@ -138,9 +138,9 @@ describe('MDX basics (merged fixture)', () => { const html = await fixture.readFile('/slots/glob/index.html'); const { document } = parseHTML(html); - const h1 = document.querySelector('[data-content-export] h1'); - const defaultSlot = document.querySelector('[data-content-export] [data-default-slot]'); - const namedSlot = document.querySelector('[data-content-export] [data-named-slot]'); + const h1 = document.querySelector('[data-content-export] h1')!; + const defaultSlot = document.querySelector('[data-content-export] [data-default-slot]')!; + const namedSlot = document.querySelector('[data-content-export] [data-named-slot]')!; assert.equal(h1.textContent, 'Hello slotted component!'); assert.equal(defaultSlot.textContent, 'Default content.'); @@ -173,8 +173,8 @@ describe('MDX basics (merged fixture)', () => { const html = await fixture.readFile('/frontmatter/index.html'); const { document } = parseHTML(html); - const contentTitle = document.querySelector('[data-content-title]'); - const frontmatterTitle = document.querySelector('[data-frontmatter-title]'); + const contentTitle = document.querySelector('[data-content-title]')!; + const frontmatterTitle = document.querySelector('[data-frontmatter-title]')!; assert.equal(contentTitle.textContent, 'Using YAML frontmatter'); assert.equal(frontmatterTitle.textContent, 'Using YAML frontmatter'); @@ -253,14 +253,14 @@ describe('MDX basics (merged fixture)', () => { const html = await fixture.readFile('/script-style-raw/index.html'); const { document } = parseHTML(html); - const scriptContent = document.getElementById('test-script').innerHTML; + const scriptContent = document.getElementById('test-script')!.innerHTML; assert.equal( scriptContent.includes("console.log('raw script')"), true, 'script should not be html-escaped', ); - const styleContent = document.getElementById('test-style').innerHTML; + const styleContent = document.getElementById('test-style')!.innerHTML; assert.equal( styleContent.includes('h1[id="script-style-raw"]'), true, @@ -271,7 +271,7 @@ describe('MDX basics (merged fixture)', () => { }); describe('dev', () => { - let devServer; + let devServer: DevServer; before(async () => { devServer = await fixture.startDevServer(); @@ -291,8 +291,8 @@ describe('MDX basics (merged fixture)', () => { const html = await res.text(); const { document } = parseHTML(html); - const h1 = document.querySelector('h1'); - const foo = document.querySelector('#foo'); + const h1 = document.querySelector('h1')!; + const foo = document.querySelector('#foo')!; assert.equal(h1.textContent, 'Hello component!'); assert.equal(foo.textContent, 'bar'); @@ -305,8 +305,8 @@ describe('MDX basics (merged fixture)', () => { const html = await res.text(); const { document } = parseHTML(html); - const h1 = document.querySelector('[data-default-export] h1'); - const foo = document.querySelector('[data-default-export] #foo'); + const h1 = document.querySelector('[data-default-export] h1')!; + const foo = document.querySelector('[data-default-export] #foo')!; assert.equal(h1.textContent, 'Hello component!'); assert.equal(foo.textContent, 'bar'); @@ -319,8 +319,8 @@ describe('MDX basics (merged fixture)', () => { const html = await res.text(); const { document } = parseHTML(html); - const h1 = document.querySelector('[data-content-export] h1'); - const foo = document.querySelector('[data-content-export] #foo'); + const h1 = document.querySelector('[data-content-export] h1')!; + const foo = document.querySelector('[data-content-export] #foo')!; assert.equal(h1.textContent, 'Hello component!'); assert.equal(foo.textContent, 'bar'); @@ -334,8 +334,8 @@ describe('MDX basics (merged fixture)', () => { const html = await res.text(); const { document } = parseHTML(html); - const h1 = document.querySelector('h1'); - const p = document.querySelector('p'); + const h1 = document.querySelector('h1')!; + const p = document.querySelector('p')!; assert.equal(h1.textContent, 'MDX containing '); assert.equal(p.textContent, 'bar'); @@ -350,10 +350,10 @@ describe('MDX basics (merged fixture)', () => { const h = document.querySelector( '[data-default-export] [data-file="WithFragment.mdx"] h1', - ); + )!; const p = document.querySelector( '[data-default-export] [data-file="WithFragment.mdx"] p', - ); + )!; assert.equal(h.textContent, 'MDX containing '); assert.equal(p.textContent, 'bar'); @@ -368,10 +368,10 @@ describe('MDX basics (merged fixture)', () => { const h = document.querySelector( '[data-content-export] [data-file="WithFragment.mdx"] h1', - ); + )!; const p = document.querySelector( '[data-content-export] [data-file="WithFragment.mdx"] p', - ); + )!; assert.equal(h.textContent, 'MDX containing '); assert.equal(p.textContent, 'bar'); @@ -389,9 +389,9 @@ describe('MDX basics (merged fixture)', () => { const html = await res.text(); const { document } = parseHTML(html); - const h1 = document.querySelector('h1'); - const defaultSlot = document.querySelector('[data-default-slot]'); - const namedSlot = document.querySelector('[data-named-slot]'); + const h1 = document.querySelector('h1')!; + const defaultSlot = document.querySelector('[data-default-slot]')!; + const namedSlot = document.querySelector('[data-named-slot]')!; assert.equal(h1.textContent, 'Hello slotted component!'); assert.equal(defaultSlot.textContent, 'Default content.'); @@ -405,9 +405,9 @@ describe('MDX basics (merged fixture)', () => { const html = await res.text(); const { document } = parseHTML(html); - const h1 = document.querySelector('[data-default-export] h1'); - const defaultSlot = document.querySelector('[data-default-export] [data-default-slot]'); - const namedSlot = document.querySelector('[data-default-export] [data-named-slot]'); + const h1 = document.querySelector('[data-default-export] h1')!; + const defaultSlot = document.querySelector('[data-default-export] [data-default-slot]')!; + const namedSlot = document.querySelector('[data-default-export] [data-named-slot]')!; assert.equal(h1.textContent, 'Hello slotted component!'); assert.equal(defaultSlot.textContent, 'Default content.'); @@ -421,9 +421,9 @@ describe('MDX basics (merged fixture)', () => { const html = await res.text(); const { document } = parseHTML(html); - const h1 = document.querySelector('[data-content-export] h1'); - const defaultSlot = document.querySelector('[data-content-export] [data-default-slot]'); - const namedSlot = document.querySelector('[data-content-export] [data-named-slot]'); + const h1 = document.querySelector('[data-content-export] h1')!; + const defaultSlot = document.querySelector('[data-content-export] [data-default-slot]')!; + const namedSlot = document.querySelector('[data-content-export] [data-named-slot]')!; assert.equal(h1.textContent, 'Hello slotted component!'); assert.equal(defaultSlot.textContent, 'Default content.'); @@ -441,14 +441,14 @@ describe('MDX basics (merged fixture)', () => { const html = await res.text(); const { document } = parseHTML(html); - const scriptContent = document.getElementById('test-script').innerHTML; + const scriptContent = document.getElementById('test-script')!.innerHTML; assert.equal( scriptContent.includes("console.log('raw script')"), true, 'script should not be html-escaped', ); - const styleContent = document.getElementById('test-style').innerHTML; + const styleContent = document.getElementById('test-style')!.innerHTML; assert.equal( styleContent.includes('h1[id="script-style-raw"]'), true, diff --git a/packages/integrations/mdx/test/mdx-content-layer.test.js b/packages/integrations/mdx/test/mdx-content-layer.test.ts similarity index 74% rename from packages/integrations/mdx/test/mdx-content-layer.test.js rename to packages/integrations/mdx/test/mdx-content-layer.test.ts index c6998b26f725..887699750839 100644 --- a/packages/integrations/mdx/test/mdx-content-layer.test.js +++ b/packages/integrations/mdx/test/mdx-content-layer.test.ts @@ -1,12 +1,11 @@ import assert from 'node:assert/strict'; import { after, before, describe, it } from 'node:test'; -import { loadFixture } from '../../../astro/test/test-utils.js'; +import { loadFixture, type Fixture, type DevServer } from '../../../astro/test/test-utils.js'; describe('Content Layer MDX rendering dev', () => { - /** @type {import("../../../astro/test/test-utils.js").Fixture} */ - let fixture; + let fixture: Fixture; - let devServer; + let devServer: DevServer; before(async () => { fixture = await loadFixture({ root: new URL('./fixtures/content-layer/', import.meta.url), @@ -19,7 +18,7 @@ describe('Content Layer MDX rendering dev', () => { }); it('Render an MDX file', async () => { - const html = await fixture.fetch('/reptiles/iguana').then((r) => r.text()); + const html = await fixture.fetch('/reptiles/iguana').then((r: Response) => r.text()); assert.match(html, /Iguana/); assert.match(html, /This is a rendered entry/); @@ -27,8 +26,7 @@ describe('Content Layer MDX rendering dev', () => { }); describe('Content Layer MDX rendering build', () => { - /** @type {import("../../../astro/test/test-utils.js").Fixture} */ - let fixture; + let fixture: Fixture; before(async () => { fixture = await loadFixture({ root: new URL('./fixtures/content-layer/', import.meta.url), diff --git a/packages/integrations/mdx/test/mdx-frontmatter-injection.test.js b/packages/integrations/mdx/test/mdx-frontmatter-injection.test.ts similarity index 59% rename from packages/integrations/mdx/test/mdx-frontmatter-injection.test.js rename to packages/integrations/mdx/test/mdx-frontmatter-injection.test.ts index 5c8b69d07337..d82e7e4f3c4f 100644 --- a/packages/integrations/mdx/test/mdx-frontmatter-injection.test.js +++ b/packages/integrations/mdx/test/mdx-frontmatter-injection.test.ts @@ -1,12 +1,19 @@ import * as assert from 'node:assert/strict'; import { before, describe, it } from 'node:test'; import { parseHTML } from 'linkedom'; -import { loadFixture } from '../../../astro/test/test-utils.js'; +import { loadFixture, type Fixture } from '../../../astro/test/test-utils.js'; const FIXTURE_ROOT = new URL('./fixtures/mdx-frontmatter-injection/', import.meta.url); +type FrontmatterEntry = { + layout: string; + title: string; + description: string; + injectedReadingTime: { text: string; minutes: number; time: number; words: number } | null; +}; + describe('MDX frontmatter injection', () => { - let fixture; + let fixture: Fixture; before(async () => { fixture = await loadFixture({ @@ -15,28 +22,30 @@ describe('MDX frontmatter injection', () => { await fixture.build(); }); + const readFrontmatterByPage = async (): Promise => { + return JSON.parse(await fixture.readFile('/glob.json')) as FrontmatterEntry[]; + }; + it('remark supports custom vfile data - get title', async () => { - const frontmatterByPage = JSON.parse(await fixture.readFile('/glob.json')); - const titles = frontmatterByPage.map((frontmatter = {}) => frontmatter.title); + const frontmatterByPage = await readFrontmatterByPage(); + const titles = frontmatterByPage.map((frontmatter) => frontmatter.title); assert.equal(titles.includes('Page 1'), true); assert.equal(titles.includes('Page 2'), true); }); it('rehype supports custom vfile data - reading time', async () => { - const frontmatterByPage = JSON.parse(await fixture.readFile('/glob.json')); - const readingTimes = frontmatterByPage.map( - (frontmatter = {}) => frontmatter.injectedReadingTime, - ); + const frontmatterByPage = await readFrontmatterByPage(); + const readingTimes = frontmatterByPage.map((frontmatter) => frontmatter.injectedReadingTime); assert.equal(readingTimes.length > 0, true); - for (let readingTime of readingTimes) { + for (const readingTime of readingTimes) { assert.notEqual(readingTime, null); - assert.match(readingTime.text, /^\d+ min read/); + assert.match(readingTime!.text, /^\d+ min read/); } }); it('allow user frontmatter mutation', async () => { - const frontmatterByPage = JSON.parse(await fixture.readFile('/glob.json')); - const descriptions = frontmatterByPage.map((frontmatter = {}) => frontmatter.description); + const frontmatterByPage = await readFrontmatterByPage(); + const descriptions = frontmatterByPage.map((frontmatter) => frontmatter.description); assert.equal( descriptions.includes('Processed by remarkDescription plugin: Page 1 description'), true, @@ -51,8 +60,8 @@ describe('MDX frontmatter injection', () => { const html1 = await fixture.readFile('/page-1/index.html'); const html2 = await fixture.readFile('/page-2/index.html'); - const title1 = parseHTML(html1).document.querySelector('title'); - const title2 = parseHTML(html2).document.querySelector('title'); + const title1 = parseHTML(html1).document.querySelector('title')!; + const title2 = parseHTML(html2).document.querySelector('title')!; assert.equal(title1.innerHTML, 'Page 1'); assert.equal(title2.innerHTML, 'Page 2'); diff --git a/packages/integrations/mdx/test/mdx-get-headings.test.js b/packages/integrations/mdx/test/mdx-get-headings.test.ts similarity index 86% rename from packages/integrations/mdx/test/mdx-get-headings.test.js rename to packages/integrations/mdx/test/mdx-get-headings.test.ts index e4e07a281fc8..f889c5140153 100644 --- a/packages/integrations/mdx/test/mdx-get-headings.test.js +++ b/packages/integrations/mdx/test/mdx-get-headings.test.ts @@ -4,10 +4,10 @@ import { rehypeHeadingIds } from '@astrojs/markdown-remark'; import mdx from '@astrojs/mdx'; import { parseHTML } from 'linkedom'; import { visit } from 'unist-util-visit'; -import { loadFixture } from '../../../astro/test/test-utils.js'; +import { loadFixture, type Fixture } from '../../../astro/test/test-utils.js'; describe('MDX getHeadings', () => { - let fixture; + let fixture: Fixture; before(async () => { fixture = await loadFixture({ @@ -22,9 +22,9 @@ describe('MDX getHeadings', () => { const html = await fixture.readFile('/test/index.html'); const { document } = parseHTML(html); - const h2Ids = document.querySelectorAll('h2').map((el) => el?.id); - const h3Ids = document.querySelectorAll('h3').map((el) => el?.id); - assert.equal(document.querySelector('h1').id, 'heading-test'); + const h2Ids = Array.from(document.querySelectorAll('h2')).map((el) => el?.id); + const h3Ids = Array.from(document.querySelectorAll('h3')).map((el) => el?.id); + assert.equal(document.querySelector('h1')!.id, 'heading-test'); assert.equal(h2Ids.includes('section-1'), true); assert.equal(h2Ids.includes('section-2'), true); assert.equal(h3Ids.includes('subsection-1'), true); @@ -70,15 +70,15 @@ describe('MDX getHeadings', () => { const html = await fixture.readFile('/test-with-frontmatter/index.html'); const { document } = parseHTML(html); - const h3Ids = document.querySelectorAll('h3').map((el) => el?.id); + const h3Ids = Array.from(document.querySelectorAll('h3')).map((el) => el?.id); - assert.equal(document.querySelector('h1').id, 'the-frontmatter-title'); - assert.equal(document.querySelector('h2').id, 'frontmattertitle'); + assert.equal(document.querySelector('h1')!.id, 'the-frontmatter-title'); + assert.equal(document.querySelector('h2')!.id, 'frontmattertitle'); assert.equal(h3Ids.includes('keyword-2'), true); assert.equal(h3Ids.includes('tag-1'), true); - assert.equal(document.querySelector('h4').id, 'item-2'); - assert.equal(document.querySelector('h5').id, 'nested-item-3'); - assert.equal(document.querySelector('h6').id, 'frontmatterunknown'); + assert.equal(document.querySelector('h4')!.id, 'item-2'); + assert.equal(document.querySelector('h5')!.id, 'nested-item-3'); + assert.equal(document.querySelector('h6')!.id, 'frontmatterunknown'); }); it('generates correct getHeadings() export', async () => { @@ -100,7 +100,7 @@ describe('MDX getHeadings', () => { }); describe('MDX heading IDs can be customized by user plugins', () => { - let fixture; + let fixture: Fixture; before(async () => { fixture = await loadFixture({ @@ -132,7 +132,7 @@ describe('MDX heading IDs can be customized by user plugins', () => { assert.equal(h1?.textContent, 'Heading test'); assert.equal(h1?.getAttribute('id'), '0'); - const headingIDs = document.querySelectorAll('h1,h2,h3').map((el) => el.id); + const headingIDs = Array.from(document.querySelectorAll('h1,h2,h3')).map((el) => el.id); assert.equal( JSON.stringify(headingIDs), JSON.stringify(Array.from({ length: headingIDs.length }, (_, idx) => String(idx))), @@ -157,7 +157,7 @@ describe('MDX heading IDs can be customized by user plugins', () => { }); describe('MDX heading IDs can be injected before user plugins', () => { - let fixture; + let fixture: Fixture; before(async () => { fixture = await loadFixture({ diff --git a/packages/integrations/mdx/test/mdx-images.test.js b/packages/integrations/mdx/test/mdx-images.test.ts similarity index 77% rename from packages/integrations/mdx/test/mdx-images.test.js rename to packages/integrations/mdx/test/mdx-images.test.ts index 543b9021ebe2..304dcc163fa6 100644 --- a/packages/integrations/mdx/test/mdx-images.test.js +++ b/packages/integrations/mdx/test/mdx-images.test.ts @@ -1,13 +1,13 @@ import * as assert from 'node:assert/strict'; import { after, before, describe, it } from 'node:test'; import { parseHTML } from 'linkedom'; -import { loadFixture } from '../../../astro/test/test-utils.js'; +import { loadFixture, type Fixture, type DevServer } from '../../../astro/test/test-utils.js'; const imageTestRoutes = ['with-components', 'esm-import', 'content-collection']; describe('MDX Page', () => { - let devServer; - let fixture; + let devServer: DevServer; + let fixture: Fixture; before(async () => { fixture = await loadFixture({ @@ -31,17 +31,17 @@ describe('MDX Page', () => { const imgs = document.getElementsByTagName('img'); assert.equal(imgs.length, 6); // Image using a relative path - assert.equal(imgs.item(0).src.startsWith('/_image'), true); + assert.equal(imgs.item(0)!.src.startsWith('/_image'), true); // Image using an aliased path - assert.equal(imgs.item(1).src.startsWith('/_image'), true); + assert.equal(imgs.item(1)!.src.startsWith('/_image'), true); // Image with title - assert.equal(imgs.item(2).title, 'Houston title'); + assert.equal(imgs.item(2)!.title, 'Houston title'); // Image with spaces in the path - assert.equal(imgs.item(3).src.startsWith('/_image'), true); + assert.equal(imgs.item(3)!.src.startsWith('/_image'), true); // Image using a relative path with no slashes - assert.equal(imgs.item(4).src.startsWith('/_image'), true); + assert.equal(imgs.item(4)!.src.startsWith('/_image'), true); // Image using a relative path with nested directory - assert.equal(imgs.item(5).src.startsWith('/_image'), true); + assert.equal(imgs.item(5)!.src.startsWith('/_image'), true); }); for (const route of imageTestRoutes) { @@ -55,11 +55,11 @@ describe('MDX Page', () => { const imgs = document.getElementsByTagName('img'); assert.equal(imgs.length, 2); - const assetsImg = imgs.item(0); + const assetsImg = imgs.item(0)!; assert.equal(assetsImg.src.startsWith('/_image'), true); assert.equal(assetsImg.hasAttribute('data-my-image'), true); - const publicImg = imgs.item(1); + const publicImg = imgs.item(1)!; assert.equal(publicImg.src, '/favicon.svg'); assert.equal(publicImg.hasAttribute('data-my-image'), true); }); diff --git a/packages/integrations/mdx/test/mdx-infinite-loop.test.js b/packages/integrations/mdx/test/mdx-infinite-loop.test.ts similarity index 82% rename from packages/integrations/mdx/test/mdx-infinite-loop.test.js rename to packages/integrations/mdx/test/mdx-infinite-loop.test.ts index a4c78fcfff1e..383c89d9a52c 100644 --- a/packages/integrations/mdx/test/mdx-infinite-loop.test.js +++ b/packages/integrations/mdx/test/mdx-infinite-loop.test.ts @@ -1,10 +1,10 @@ import * as assert from 'node:assert/strict'; import { before, describe, it } from 'node:test'; import mdx from '@astrojs/mdx'; -import { loadFixture } from '../../../astro/test/test-utils.js'; +import { loadFixture, type Fixture } from '../../../astro/test/test-utils.js'; describe('MDX Infinite Loop', () => { - let fixture; + let fixture: Fixture; before(async () => { fixture = await loadFixture({ @@ -14,7 +14,7 @@ describe('MDX Infinite Loop', () => { }); describe('build', () => { - let err; + let err: unknown; before(async () => { try { await fixture.build(); diff --git a/packages/integrations/mdx/test/mdx-math.test.js b/packages/integrations/mdx/test/mdx-math.test.ts similarity index 100% rename from packages/integrations/mdx/test/mdx-math.test.js rename to packages/integrations/mdx/test/mdx-math.test.ts diff --git a/packages/integrations/mdx/test/mdx-namespace.test.js b/packages/integrations/mdx/test/mdx-namespace.test.ts similarity index 82% rename from packages/integrations/mdx/test/mdx-namespace.test.js rename to packages/integrations/mdx/test/mdx-namespace.test.ts index 13137e40e98c..3f7cd47bb375 100644 --- a/packages/integrations/mdx/test/mdx-namespace.test.js +++ b/packages/integrations/mdx/test/mdx-namespace.test.ts @@ -1,10 +1,10 @@ import * as assert from 'node:assert/strict'; import { after, before, describe, it } from 'node:test'; import { parseHTML } from 'linkedom'; -import { loadFixture } from '../../../astro/test/test-utils.js'; +import { loadFixture, type Fixture, type DevServer } from '../../../astro/test/test-utils.js'; describe('MDX Namespace', () => { - let fixture; + let fixture: Fixture; before(async () => { fixture = await loadFixture({ @@ -22,7 +22,7 @@ describe('MDX Namespace', () => { const { document } = parseHTML(html); const island = document.querySelector('astro-island'); - const component = document.querySelector('#component'); + const component = document.querySelector('#component')!; assert.notEqual(island, undefined); assert.equal(component.textContent, 'Hello world'); @@ -33,7 +33,7 @@ describe('MDX Namespace', () => { const { document } = parseHTML(html); const island = document.querySelector('astro-island'); - const component = document.querySelector('#component'); + const component = document.querySelector('#component')!; assert.notEqual(island, undefined); assert.equal(component.textContent, 'Hello world'); @@ -41,7 +41,7 @@ describe('MDX Namespace', () => { }); describe('dev', () => { - let devServer; + let devServer: DevServer; before(async () => { devServer = await fixture.startDevServer(); @@ -60,7 +60,7 @@ describe('MDX Namespace', () => { const { document } = parseHTML(html); const island = document.querySelector('astro-island'); - const component = document.querySelector('#component'); + const component = document.querySelector('#component')!; assert.notEqual(island, undefined); assert.equal(component.textContent, 'Hello world'); @@ -75,7 +75,7 @@ describe('MDX Namespace', () => { const { document } = parseHTML(html); const island = document.querySelector('astro-island'); - const component = document.querySelector('#component'); + const component = document.querySelector('#component')!; assert.notEqual(island, undefined); assert.equal(component.textContent, 'Hello world'); diff --git a/packages/integrations/mdx/test/mdx-optimize.test.js b/packages/integrations/mdx/test/mdx-optimize.test.ts similarity index 82% rename from packages/integrations/mdx/test/mdx-optimize.test.js rename to packages/integrations/mdx/test/mdx-optimize.test.ts index bc66a5732c91..3f79cf3742ee 100644 --- a/packages/integrations/mdx/test/mdx-optimize.test.js +++ b/packages/integrations/mdx/test/mdx-optimize.test.ts @@ -1,13 +1,12 @@ import * as assert from 'node:assert/strict'; import { before, describe, it } from 'node:test'; import { parseHTML } from 'linkedom'; -import { loadFixture } from '../../../astro/test/test-utils.js'; +import { loadFixture, type Fixture } from '../../../astro/test/test-utils.js'; const FIXTURE_ROOT = new URL('./fixtures/mdx-optimize/', import.meta.url); describe('MDX optimize', () => { - /** @type {import('../../../astro/test/test-utils').Fixture} */ - let fixture; + let fixture: Fixture; before(async () => { fixture = await loadFixture({ root: FIXTURE_ROOT, @@ -19,17 +18,17 @@ describe('MDX optimize', () => { const html = await fixture.readFile('/index.html'); const { document } = parseHTML(html); - assert.equal(document.querySelector('h1').textContent.includes('MDX page'), true); + assert.equal(document.querySelector('h1')!.textContent.includes('MDX page'), true); assert.equal( - document.querySelector('p').textContent.includes('I once heard a very inspirational quote:'), + document.querySelector('p')!.textContent.includes('I once heard a very inspirational quote:'), true, ); - const blockquote = document.querySelector('blockquote.custom-blockquote'); + const blockquote = document.querySelector('blockquote.custom-blockquote')!; assert.notEqual(blockquote, null); assert.equal(blockquote.textContent.includes('I like pancakes'), true); - const code = document.querySelector('pre.astro-code'); + const code = document.querySelector('pre.astro-code')!; assert.notEqual(code, null); assert.equal(code.textContent.includes(`const pancakes = 'yummy'`), true); }); @@ -38,13 +37,13 @@ describe('MDX optimize', () => { const html = await fixture.readFile('/import/index.html'); const { document } = parseHTML(html); - assert.equal(document.querySelector('h1').textContent.includes('Astro page'), true); + assert.equal(document.querySelector('h1')!.textContent.includes('Astro page'), true); assert.equal( - document.querySelector('p').textContent.includes('I once heard a very inspirational quote:'), + document.querySelector('p')!.textContent.includes('I once heard a very inspirational quote:'), true, ); - const blockquote = document.querySelector('blockquote.custom-blockquote'); + const blockquote = document.querySelector('blockquote.custom-blockquote')!; assert.notEqual(blockquote, null); assert.equal(blockquote.textContent.includes('I like pancakes'), true); }); @@ -90,7 +89,7 @@ describe('MDX optimize', () => { assert.doesNotMatch(html, /set:html=/); assert.equal( - document.getElementById('injected-root-hast').textContent, + document.getElementById('injected-root-hast')!.textContent, 'Injected root hast from rehype plugin', ); }); diff --git a/packages/integrations/mdx/test/mdx-page.test.js b/packages/integrations/mdx/test/mdx-page.test.ts similarity index 85% rename from packages/integrations/mdx/test/mdx-page.test.js rename to packages/integrations/mdx/test/mdx-page.test.ts index 0327bcaf0a82..6237f2185701 100644 --- a/packages/integrations/mdx/test/mdx-page.test.js +++ b/packages/integrations/mdx/test/mdx-page.test.ts @@ -2,10 +2,10 @@ import * as assert from 'node:assert/strict'; import { after, before, describe, it } from 'node:test'; import * as cheerio from 'cheerio'; import { parseHTML } from 'linkedom'; -import { loadFixture } from '../../../astro/test/test-utils.js'; +import { loadFixture, type Fixture, type DevServer } from '../../../astro/test/test-utils.js'; describe('MDX Page', () => { - let fixture; + let fixture: Fixture; before(async () => { fixture = await loadFixture({ @@ -24,7 +24,7 @@ describe('MDX Page', () => { const html = await fixture.readFile('/index.html'); const { document } = parseHTML(html); - const h1 = document.querySelector('h1'); + const h1 = document.querySelector('h1')!; assert.equal(h1.textContent, 'Hello page!'); }); @@ -59,13 +59,13 @@ describe('MDX Page', () => { const html = await fixture.readFile('/index.html'); const { document } = parseHTML(html); - const keyTest = document.querySelector('#key-test'); + const keyTest = document.querySelector('#key-test')!; assert.equal(keyTest.textContent, 'oranges'); }); }); describe('dev', () => { - let devServer; + let devServer: DevServer; before(async () => { devServer = await fixture.startDevServer(); @@ -83,7 +83,7 @@ describe('MDX Page', () => { const html = await res.text(); const { document } = parseHTML(html); - const h1 = document.querySelector('h1'); + const h1 = document.querySelector('h1')!; assert.equal(h1.textContent, 'Hello page!'); }); @@ -94,7 +94,7 @@ describe('MDX Page', () => { const html = await res.text(); const $ = cheerio.load(html); assert.equal($('h1').text(), '我的第一篇博客文章'); - assert.doesNotMatch(res.headers.get('content-type'), /charset=utf-8/); + assert.doesNotMatch(res.headers.get('content-type') ?? '', /charset=utf-8/); assert.match(html, / { const res = await fixture.fetch('/chinese-encoding-layout-frontmatter/'); assert.equal(res.status, 200); const html = await res.text(); - assert.doesNotMatch(res.headers.get('content-type'), /charset=utf-8/); + assert.doesNotMatch(res.headers.get('content-type') ?? '', /charset=utf-8/); assert.doesNotMatch(html, / { const res = await fixture.fetch('/chinese-encoding-layout-manual/'); assert.equal(res.status, 200); const html = await res.text(); - assert.doesNotMatch(res.headers.get('content-type'), /charset=utf-8/); + assert.doesNotMatch(res.headers.get('content-type') ?? '', /charset=utf-8/); assert.doesNotMatch(html, / { for (const extendMarkdownConfig of [true, false]) { describe(`extendMarkdownConfig = ${extendMarkdownConfig}`, () => { - let fixture; + let fixture: Fixture; before(async () => { fixture = await buildFixture({ // Use unique outDir to avoid cache pollution between builds with different configs @@ -75,8 +80,8 @@ describe('MDX plugins - Astro config integration', () => { const html = await fixture.readFile(FILE); const { document } = parseHTML(html); - assert.notEqual(selectRemarkExample(document, 'MDX remark plugins not applied.'), null); - assert.notEqual(selectRehypeExample(document, 'MDX rehype plugins not applied.'), null); + assert.notEqual(selectRemarkExample(document), null, 'MDX remark plugins not applied.'); + assert.notEqual(selectRehypeExample(document), null, 'MDX rehype plugins not applied.'); }); it('Handles Markdown plugins', async () => { @@ -84,11 +89,9 @@ describe('MDX plugins - Astro config integration', () => { const { document } = parseHTML(html); assert.equal( - selectTocLink( - document, - '`remarkToc` plugin applied unexpectedly. Should override Markdown config.', - ), + selectTocLink(document), null, + '`remarkToc` plugin applied unexpectedly. Should override Markdown config.', ); }); @@ -107,7 +110,7 @@ describe('MDX plugins - Astro config integration', () => { const html = await fixture.readFile(FILE); const { document } = parseHTML(html); - const quote = selectSmartypantsQuote(document); + const quote = selectSmartypantsQuote(document)!; if (extendMarkdownConfig === true) { // smartypants: false inherited from markdown config — straight quotes and dashes preserved @@ -129,7 +132,7 @@ describe('MDX plugins - Astro config integration', () => { } }); -async function buildFixture(config) { +async function buildFixture(config: AstroInlineConfig = {}): Promise { const fixture = await loadFixture({ root: FIXTURE_ROOT, ...config, @@ -138,41 +141,42 @@ async function buildFixture(config) { return fixture; } -function remarkExamplePlugin() { +const remarkExamplePlugin: RemarkPlugin = () => { return (tree) => { tree.children.push({ type: 'html', value: '
', }); }; -} +}; -function rehypeExamplePlugin() { +const rehypeExamplePlugin: RehypePlugin = () => { return (tree) => { tree.children.push({ type: 'element', tagName: 'div', properties: { 'data-rehype-plugin-works': 'true' }, + children: [], }); }; -} +}; -function selectTocLink(document) { +function selectTocLink(document: Document) { return document.querySelector('ul a[href="#section-1"]'); } -function selectGfmLink(document) { +function selectGfmLink(document: Document) { return document.querySelector('a[href="https://handle-me-gfm.com"]'); } -function selectSmartypantsQuote(document) { +function selectSmartypantsQuote(document: Document) { return document.querySelector('blockquote'); } -function selectRemarkExample(document) { +function selectRemarkExample(document: Document) { return document.querySelector('div[data-remark-plugin-works]'); } -function selectRehypeExample(document) { +function selectRehypeExample(document: Document) { return document.querySelector('div[data-rehype-plugin-works]'); } diff --git a/packages/integrations/mdx/test/mdx-plus-react-errors.test.js b/packages/integrations/mdx/test/mdx-plus-react-errors.test.ts similarity index 67% rename from packages/integrations/mdx/test/mdx-plus-react-errors.test.js rename to packages/integrations/mdx/test/mdx-plus-react-errors.test.ts index 9d87fa8a0045..67d909f6df1f 100644 --- a/packages/integrations/mdx/test/mdx-plus-react-errors.test.js +++ b/packages/integrations/mdx/test/mdx-plus-react-errors.test.ts @@ -4,30 +4,29 @@ import { loadFixture } from '../../../astro/test/test-utils.js'; function hookError() { const error = console.error; - const errors = []; - console.error = function (...args) { + const errors: unknown[][] = []; + console.error = function (...args: unknown[]) { errors.push(args); }; - return () => { + return (): unknown[][] => { console.error = error; return errors; }; } describe('MDX and React with build errors', () => { - let fixture; - let unhook; + let unhook: (() => unknown[][]) | undefined; it('shows correct error messages on build error', async () => { try { - fixture = await loadFixture({ + const fixture = await loadFixture({ root: new URL('./fixtures/mdx-plus-react-errors/', import.meta.url), }); unhook = hookError(); await fixture.build(); } catch (err) { - assert.equal(err.message, 'a is not defined'); + assert.equal((err as Error).message, 'a is not defined'); } - unhook(); + unhook?.(); }); }); diff --git a/packages/integrations/mdx/test/mdx-plus-react.test.js b/packages/integrations/mdx/test/mdx-plus-react.test.ts similarity index 82% rename from packages/integrations/mdx/test/mdx-plus-react.test.js rename to packages/integrations/mdx/test/mdx-plus-react.test.ts index 87f420fc06ef..7d821eb2302c 100644 --- a/packages/integrations/mdx/test/mdx-plus-react.test.js +++ b/packages/integrations/mdx/test/mdx-plus-react.test.ts @@ -1,23 +1,23 @@ import * as assert from 'node:assert/strict'; import { before, describe, it } from 'node:test'; import { parseHTML } from 'linkedom'; -import { loadFixture } from '../../../astro/test/test-utils.js'; +import { loadFixture, type Fixture } from '../../../astro/test/test-utils.js'; function hookError() { const error = console.error; - const errors = []; - console.error = function (...args) { + const errors: unknown[][] = []; + console.error = function (...args: unknown[]) { errors.push(args); }; - return () => { + return (): unknown[][] => { console.error = error; return errors; }; } describe('MDX and React', () => { - let fixture; - let unhook; + let fixture: Fixture; + let unhook: () => unknown[][]; before(async () => { fixture = await loadFixture({ @@ -31,7 +31,7 @@ describe('MDX and React', () => { const html = await fixture.readFile('/index.html'); const { document } = parseHTML(html); - const p = document.querySelector('p'); + const p = document.querySelector('p')!; assert.equal(p.textContent, 'Hello world'); }); @@ -39,7 +39,7 @@ describe('MDX and React', () => { it('mdx renders fine', async () => { const html = await fixture.readFile('/post/index.html'); const { document } = parseHTML(html); - const h = document.querySelector('#testing'); + const h = document.querySelector('#testing')!; assert.equal(h.textContent, 'Testing'); }); diff --git a/packages/integrations/mdx/test/mdx-syntax-highlighting.test.js b/packages/integrations/mdx/test/mdx-syntax-highlighting.test.ts similarity index 92% rename from packages/integrations/mdx/test/mdx-syntax-highlighting.test.js rename to packages/integrations/mdx/test/mdx-syntax-highlighting.test.ts index 2f72d4eb2866..f490ff15babc 100644 --- a/packages/integrations/mdx/test/mdx-syntax-highlighting.test.js +++ b/packages/integrations/mdx/test/mdx-syntax-highlighting.test.ts @@ -24,9 +24,12 @@ describe('MDX syntax highlighting', () => { const html = await fixture.readFile('/index.html'); const { document } = parseHTML(html); - const shikiCodeBlock = document.querySelector('pre.astro-code'); + const shikiCodeBlock = document.querySelector('pre.astro-code')!; assert.notEqual(shikiCodeBlock, null); - assert.equal(shikiCodeBlock.getAttribute('style').includes('background-color:#24292e'), true); + assert.equal( + shikiCodeBlock.getAttribute('style')!.includes('background-color:#24292e'), + true, + ); }); it('respects markdown.shikiConfig.theme', async () => { @@ -45,9 +48,12 @@ describe('MDX syntax highlighting', () => { const html = await fixture.readFile('/index.html'); const { document } = parseHTML(html); - const shikiCodeBlock = document.querySelector('pre.astro-code'); + const shikiCodeBlock = document.querySelector('pre.astro-code')!; assert.notEqual(shikiCodeBlock, null); - assert.equal(shikiCodeBlock.getAttribute('style').includes('background-color:#282A36'), true); + assert.equal( + shikiCodeBlock.getAttribute('style')!.includes('background-color:#282A36'), + true, + ); }); }); @@ -140,7 +146,7 @@ describe('MDX syntax highlighting', () => { [ rehypePrettyCode, { - onVisitHighlightedLine(node) { + onVisitHighlightedLine(node: { properties: Record }) { node.properties.style = 'background-color:#000000'; }, }, diff --git a/packages/integrations/mdx/test/mdx-vite-env-vars.test.js b/packages/integrations/mdx/test/mdx-vite-env-vars.test.ts similarity index 95% rename from packages/integrations/mdx/test/mdx-vite-env-vars.test.js rename to packages/integrations/mdx/test/mdx-vite-env-vars.test.ts index 213386ceb712..393c2b639719 100644 --- a/packages/integrations/mdx/test/mdx-vite-env-vars.test.js +++ b/packages/integrations/mdx/test/mdx-vite-env-vars.test.ts @@ -1,10 +1,10 @@ import * as assert from 'node:assert/strict'; import { before, describe, it } from 'node:test'; import { parseHTML } from 'linkedom'; -import { loadFixture } from '../../../astro/test/test-utils.js'; +import { loadFixture, type Fixture } from '../../../astro/test/test-utils.js'; describe('MDX - Vite env vars', () => { - let fixture; + let fixture: Fixture; before(async () => { fixture = await loadFixture({ root: new URL('./fixtures/mdx-vite-env-vars/', import.meta.url), @@ -54,7 +54,7 @@ describe('MDX - Vite env vars', () => { const html = await fixture.readFile('/vite-env-vars/index.html'); const { document } = parseHTML(html); - const dataAttrDump = document.querySelector('[data-env-dump]'); + const dataAttrDump = document.querySelector('[data-env-dump]')!; assert.notEqual(dataAttrDump, null); assert.equal(dataAttrDump.getAttribute('data-env-prod'), 'true'); diff --git a/packages/integrations/mdx/test/remark-imgattr.test.js b/packages/integrations/mdx/test/remark-imgattr.test.ts similarity index 75% rename from packages/integrations/mdx/test/remark-imgattr.test.js rename to packages/integrations/mdx/test/remark-imgattr.test.ts index 067d18e236c6..50528561ed08 100644 --- a/packages/integrations/mdx/test/remark-imgattr.test.js +++ b/packages/integrations/mdx/test/remark-imgattr.test.ts @@ -1,17 +1,15 @@ import * as assert from 'node:assert/strict'; import { after, before, describe, it } from 'node:test'; import * as cheerio from 'cheerio'; -import { loadFixture } from '../../../astro/test/test-utils.js'; +import { loadFixture, type Fixture, type DevServer } from '../../../astro/test/test-utils.js'; const FIXTURE_ROOT = new URL('./fixtures/image-remark-imgattr/', import.meta.url); describe('Testing remark plugins for image processing', () => { - /** @type {import('../../../astro/test/test-utils.js').Fixture} */ - let fixture; + let fixture: Fixture; describe('start dev server', () => { - /** @type {import('../../../astro/test/test-utils.js').DevServer} */ - let devServer; + let devServer: DevServer; before(async () => { fixture = await loadFixture({ @@ -26,7 +24,7 @@ describe('Testing remark plugins for image processing', () => { }); describe('Test image attributes can be added by remark plugins', () => { - let $; + let $: ReturnType; before(async () => { let res = await fixture.fetch('/'); let html = await res.text(); @@ -42,7 +40,10 @@ describe('Testing remark plugins for image processing', () => { it(' was processed properly', async () => { let $img = $('img'); - assert.equal(new URL($img.attr('src'), 'http://example.com').searchParams.get('w'), '300'); + assert.equal( + new URL($img.attr('src') ?? '', 'http://example.com').searchParams.get('w'), + '300', + ); }); }); }); diff --git a/packages/integrations/mdx/test/test-utils.ts b/packages/integrations/mdx/test/test-utils.ts new file mode 100644 index 000000000000..65da8a290573 --- /dev/null +++ b/packages/integrations/mdx/test/test-utils.ts @@ -0,0 +1,19 @@ +import type * as estree from 'estree'; +import type * as hast from 'hast'; +import type * as mdast from 'mdast'; +import type * as unified from 'unified'; + +export type RemarkPlugin = unified.Plugin< + PluginParameters, + mdast.Root +>; + +export type RehypePlugin = unified.Plugin< + PluginParameters, + hast.Root +>; + +export type RecmaPlugin = unified.Plugin< + PluginParameters, + estree.Program +>; diff --git a/packages/integrations/mdx/test/units/mdx-compilation.test.js b/packages/integrations/mdx/test/units/mdx-compilation.test.ts similarity index 88% rename from packages/integrations/mdx/test/units/mdx-compilation.test.js rename to packages/integrations/mdx/test/units/mdx-compilation.test.ts index 945131ff5d6e..c45e9c65dcbe 100644 --- a/packages/integrations/mdx/test/units/mdx-compilation.test.js +++ b/packages/integrations/mdx/test/units/mdx-compilation.test.ts @@ -1,17 +1,20 @@ import assert from 'node:assert/strict'; import { describe, it } from 'node:test'; -import { compile as _compile } from '@mdx-js/mdx'; import { rehypeHeadingIds } from '@astrojs/markdown-remark'; +import { compile as _compile, type CompileOptions, nodeTypes } from '@mdx-js/mdx'; +import type { AstroIntegrationLogger } from 'astro'; +import { visit as estreeVisit } from 'estree-util-visit'; +import rehypeRaw from 'rehype-raw'; import remarkGfm from 'remark-gfm'; import remarkSmartypants from 'remark-smartypants'; import { visit } from 'unist-util-visit'; +import { ignoreStringPlugins } from '../../dist/utils.js'; +import type { RecmaPlugin, RehypePlugin, RemarkPlugin } from '../test-utils.js'; /** * Compile MDX to JSX string output for inspection. - * @param {string} mdxCode - * @param {Readonly} options */ -async function compile(mdxCode, options = {}) { +async function compile(mdxCode: string, options: Readonly = {}) { const result = await _compile(mdxCode, { jsx: true, ...options, @@ -22,9 +25,10 @@ async function compile(mdxCode, options = {}) { /** * Compile MDX with rehype-raw (like Astro does) and return the JSX output. */ -async function compileWithRaw(mdxCode, options = {}) { - const { nodeTypes } = await import('@mdx-js/mdx'); - const rehypeRaw = (await import('rehype-raw')).default; +async function compileWithRaw( + mdxCode: string, + options: Readonly = {}, +): Promise { return compile(mdxCode, { rehypePlugins: [[rehypeRaw, { passThrough: nodeTypes }], ...(options.rehypePlugins || [])], remarkPlugins: options.remarkPlugins || [], @@ -96,14 +100,14 @@ describe('MDX SmartyPants plugin', () => { describe('MDX remark plugins', () => { it('supports custom remark plugins that modify the tree', async () => { /** Remark plugin that appends a div */ - function remarkAddDiv() { + const remarkAddDiv: RemarkPlugin = () => { return (tree) => { tree.children.push({ type: 'html', value: '
', }); }; - } + }; const code = await compileWithRaw('# Hello', { remarkPlugins: [remarkAddDiv], @@ -118,7 +122,7 @@ describe('MDX remark plugins', () => { describe('MDX rehype plugins', () => { it('supports custom rehype plugins that modify the tree', async () => { /** Rehype plugin that appends a div */ - function rehypeAddDiv() { + const rehypeAddDiv: RehypePlugin = () => { return (tree) => { tree.children.push({ type: 'element', @@ -127,7 +131,7 @@ describe('MDX rehype plugins', () => { children: [], }); }; - } + }; const code = await compileWithRaw('# Hello', { rehypePlugins: [rehypeAddDiv], @@ -139,7 +143,7 @@ describe('MDX rehype plugins', () => { }); it('supports rehype plugins with namespaced SVG attributes', async () => { - function rehypeSvg() { + const rehypeSvg: RehypePlugin = () => { return (tree) => { tree.children.push({ type: 'element', @@ -155,7 +159,7 @@ describe('MDX rehype plugins', () => { ], }); }; - } + }; const code = await compileWithRaw('# Hello', { rehypePlugins: [rehypeSvg], @@ -166,13 +170,12 @@ describe('MDX rehype plugins', () => { describe('MDX recma plugins', () => { it('supports custom recma plugins that transform the estree', async () => { - const { visit: estreeVisit } = await import('estree-util-visit'); - - function recmaExample() { + const recmaExample: RecmaPlugin = () => { return (tree) => { estreeVisit(tree, (node) => { if ( node.type === 'VariableDeclarator' && + node.id.type === 'Identifier' && node.id.name === 'recmaPluginWorking' && node.init?.type === 'Literal' ) { @@ -184,7 +187,7 @@ describe('MDX recma plugins', () => { } }); }; - } + }; const mdxCode = `export const recmaPluginWorking = false; @@ -226,7 +229,7 @@ describe('MDX heading IDs', () => { }); it('allows user plugins to override heading IDs', async () => { - function customIdPlugin() { + const customIdPlugin: RehypePlugin = () => { return (tree) => { let count = 0; visit(tree, 'element', (node) => { @@ -236,7 +239,7 @@ describe('MDX heading IDs', () => { } }); }; - } + }; const mdxCode = `# Hello @@ -256,10 +259,9 @@ describe('MDX string-based plugin filtering', () => { // When a string-based plugin is provided, the ignoreStringPlugins // function filters it out. We test the filter function directly in utils.test.js. // Here we verify that only function plugins affect output. - const { ignoreStringPlugins } = await import('../../dist/utils.js'); - const logger = { warn() {} }; + const logger = { warn() {} } as unknown as AstroIntegrationLogger; - const plugins = ['remark-toc', () => (tree) => tree]; + const plugins = ['remark-toc', () => (tree: unknown) => tree]; const filtered = ignoreStringPlugins(plugins, logger); assert.equal(filtered.length, 1, 'Should filter out string plugin'); diff --git a/packages/integrations/mdx/test/units/rehype-optimize-static.test.js b/packages/integrations/mdx/test/units/rehype-optimize-static.test.ts similarity index 81% rename from packages/integrations/mdx/test/units/rehype-optimize-static.test.js rename to packages/integrations/mdx/test/units/rehype-optimize-static.test.ts index 6121975a405a..f93b8d3f3338 100644 --- a/packages/integrations/mdx/test/units/rehype-optimize-static.test.js +++ b/packages/integrations/mdx/test/units/rehype-optimize-static.test.ts @@ -1,13 +1,9 @@ import assert from 'node:assert/strict'; import { describe, it } from 'node:test'; -import { compile as _compile } from '@mdx-js/mdx'; +import { compile as _compile, type CompileOptions } from '@mdx-js/mdx'; import { rehypeOptimizeStatic } from '../../dist/rehype-optimize-static.js'; -/** - * @param {string} mdxCode - * @param {Readonly} options - */ -async function compile(mdxCode, options) { +async function compile(mdxCode: string, options?: Readonly) { const result = await _compile(mdxCode, { jsx: true, rehypePlugins: [rehypeOptimizeStatic], @@ -20,12 +16,14 @@ async function compile(mdxCode, options) { return dedent(jsx); } -function dedent(str) { +function dedent(str: string) { const lines = str.split('\n'); if (lines.length <= 1) return str; // Get last line indent, and dedent this amount for the other lines - const lastLineIndent = lines[lines.length - 1].match(/^\s*/)[0].length; - return lines.map((line, i) => (i === 0 ? line : line.slice(lastLineIndent))).join('\n'); + const lastLineIndent = /^\s*/.exec(lines[lines.length - 1])![0].length; + return lines + .map((line: string, i: number) => (i === 0 ? line : line.slice(lastLineIndent))) + .join('\n'); } describe('rehype-optimize-static', () => { diff --git a/packages/integrations/mdx/test/units/rehype-plugins.test.js b/packages/integrations/mdx/test/units/rehype-plugins.test.ts similarity index 63% rename from packages/integrations/mdx/test/units/rehype-plugins.test.js rename to packages/integrations/mdx/test/units/rehype-plugins.test.ts index bbcc96241e1f..89e65acaa53b 100644 --- a/packages/integrations/mdx/test/units/rehype-plugins.test.js +++ b/packages/integrations/mdx/test/units/rehype-plugins.test.ts @@ -1,20 +1,23 @@ import assert from 'node:assert/strict'; import { describe, it } from 'node:test'; -import rehypeMetaString from '../../dist/rehype-meta-string.js'; +import type * as hast from 'hast'; +import { VFile } from 'vfile'; import { rehypeInjectHeadingsExport } from '../../dist/rehype-collect-headings.js'; +import rehypeMetaString from '../../dist/rehype-meta-string.js'; describe('rehypeMetaString', () => { - function createCodeNode(meta) { + function createCodeNode(meta: string | undefined): hast.Element { return { type: 'element', tagName: 'code', - properties: {}, data: meta != null ? { meta } : undefined, children: [{ type: 'text', value: 'const x = 1;' }], + position: undefined, + properties: {}, }; } - function createTree(children) { + function createTree(children: hast.RootContent[]): hast.Root { return { type: 'root', children }; } @@ -32,7 +35,7 @@ describe('rehypeMetaString', () => { const transform = rehypeMetaString(); transform(tree); - assert.equal(codeNode.properties.metastring, '{1:3}'); + assert.equal(codeNode.properties!.metastring, '{1:3}'); }); it('does not set metastring when no data.meta', () => { @@ -44,26 +47,28 @@ describe('rehypeMetaString', () => { const transform = rehypeMetaString(); transform(tree); - assert.equal(codeNode.properties.metastring, undefined); + assert.equal(codeNode.properties!.metastring, undefined); }); it('handles code elements without properties', () => { - const codeNode = { + const codeNode: hast.Element = { type: 'element', tagName: 'code', data: { meta: 'title="test"' }, children: [], + position: undefined, + properties: {}, }; const tree = createTree([codeNode]); const transform = rehypeMetaString(); transform(tree); - assert.equal(codeNode.properties.metastring, 'title="test"'); + assert.equal(codeNode.properties!.metastring, 'title="test"'); }); it('ignores non-code elements', () => { - const divNode = { + const divNode: hast.Element = { type: 'element', tagName: 'div', properties: {}, @@ -75,42 +80,52 @@ describe('rehypeMetaString', () => { const transform = rehypeMetaString(); transform(tree); - assert.equal(divNode.properties.metastring, undefined); + assert.equal(divNode.properties!.metastring, undefined); }); }); describe('rehypeInjectHeadingsExport', () => { it('injects getHeadings export from vfile headings data', () => { const headings = [ - { depth: 1, slug: 'hello', text: 'Hello' }, - { depth: 2, slug: 'world', text: 'World' }, + { depth: 1, slug: 'hello1', text: 'Hello2' }, + { depth: 2, slug: 'world3', text: 'World4' }, ]; - const tree = { type: 'root', children: [] }; - const vfile = { + const tree: hast.Root = { type: 'root', children: [] }; + const vfile = new VFile({ data: { - astro: { headings }, + astro: { + headings, + }, }, - }; + }); const transform = rehypeInjectHeadingsExport(); transform(tree, vfile); assert.equal(tree.children.length, 1); - const injectedNode = tree.children[0]; + const injectedNode = tree.children[0] as hast.Element & { + data: { estree: { type: string; body: unknown } }; + }; assert.equal(injectedNode.type, 'mdxjsEsm'); // The node should contain a getHeadings function with our headings data assert.ok(injectedNode.data.estree); assert.equal(injectedNode.data.estree.type, 'Program'); + // The function should contain the injected heading data + const functionBody = JSON.stringify(injectedNode.data.estree.body); + assert.match(functionBody, /hello1/); + assert.match(functionBody, /Hello2/); + assert.match(functionBody, /world3/); + assert.match(functionBody, /World4/); }); it('injects empty array when no headings', () => { - const tree = { type: 'root', children: [] }; - const vfile = { + const tree: hast.Root = { type: 'root', children: [] }; + const vfile = new VFile({ data: { astro: {}, }, - }; + }); const transform = rehypeInjectHeadingsExport(); transform(tree, vfile); @@ -121,13 +136,19 @@ describe('rehypeInjectHeadingsExport', () => { }); it('prepends to existing children', () => { - const existingChild = { type: 'element', tagName: 'p', children: [] }; - const tree = { type: 'root', children: [existingChild] }; - const vfile = { + const existingChild: hast.Element = { + type: 'element', + tagName: 'p', + children: [], + position: undefined, + properties: {}, + }; + const tree: hast.Root = { type: 'root', children: [existingChild] }; + const vfile = new VFile({ data: { astro: { headings: [] }, }, - }; + }); const transform = rehypeInjectHeadingsExport(); transform(tree, vfile); diff --git a/packages/integrations/mdx/test/units/server.test.js b/packages/integrations/mdx/test/units/server.test.ts similarity index 100% rename from packages/integrations/mdx/test/units/server.test.js rename to packages/integrations/mdx/test/units/server.test.ts diff --git a/packages/integrations/mdx/test/units/utils.test.js b/packages/integrations/mdx/test/units/utils.test.ts similarity index 89% rename from packages/integrations/mdx/test/units/utils.test.js rename to packages/integrations/mdx/test/units/utils.test.ts index 3c8cc4ea8c9b..0973d46699c4 100644 --- a/packages/integrations/mdx/test/units/utils.test.js +++ b/packages/integrations/mdx/test/units/utils.test.ts @@ -1,5 +1,6 @@ import assert from 'node:assert/strict'; import { describe, it } from 'node:test'; +import type { AstroConfig, AstroIntegrationLogger } from 'astro'; import { appendForwardSlash, getFileInfo, @@ -27,15 +28,14 @@ describe('utils', () => { }); describe('getFileInfo', () => { - /** @param {Partial} overrides */ - function mockConfig(overrides = {}) { + function mockConfig(overrides: Partial = {}): AstroConfig { return { root: new URL('file:///project/'), base: '/', site: undefined, trailingSlash: 'ignore', ...overrides, - }; + } as AstroConfig; } it('computes fileUrl for pages', () => { @@ -96,22 +96,23 @@ describe('utils', () => { describe('jsToTreeNode', () => { it('parses a simple export statement', () => { const node = jsToTreeNode('export const x = 1;'); + const estree = node.data!.estree!; assert.equal(node.type, 'mdxjsEsm'); - assert.equal(node.data.estree.type, 'Program'); - assert.equal(node.data.estree.sourceType, 'module'); - assert.ok(node.data.estree.body.length > 0); + assert.equal(estree.type, 'Program'); + assert.equal(estree.sourceType, 'module'); + assert.ok(estree.body.length > 0); }); it('parses an import statement', () => { const node = jsToTreeNode("import foo from 'bar';"); assert.equal(node.type, 'mdxjsEsm'); - assert.equal(node.data.estree.body[0].type, 'ImportDeclaration'); + assert.equal(node.data!.estree!.body[0].type, 'ImportDeclaration'); }); it('parses a function export', () => { const node = jsToTreeNode('export function getHeadings() { return []; }'); assert.equal(node.type, 'mdxjsEsm'); - const decl = node.data.estree.body[0]; + const decl = node.data!.estree!.body[0]; assert.equal(decl.type, 'ExportNamedDeclaration'); }); @@ -123,14 +124,14 @@ describe('utils', () => { }); describe('ignoreStringPlugins', () => { - function mockLogger() { - const warnings = []; + function mockLogger(): AstroIntegrationLogger & { warnings: string[] } { + const warnings: string[] = []; return { - warn(msg) { + warn: (msg: string) => { warnings.push(msg); }, warnings, - }; + } as AstroIntegrationLogger & { warnings: string[] }; } it('returns function plugins unchanged', () => { diff --git a/packages/integrations/mdx/test/units/vite-plugin-mdx-postprocess.test.js b/packages/integrations/mdx/test/units/vite-plugin-mdx-postprocess.test.ts similarity index 99% rename from packages/integrations/mdx/test/units/vite-plugin-mdx-postprocess.test.js rename to packages/integrations/mdx/test/units/vite-plugin-mdx-postprocess.test.ts index 6e63c83cc9c8..8cbddd643e5e 100644 --- a/packages/integrations/mdx/test/units/vite-plugin-mdx-postprocess.test.js +++ b/packages/integrations/mdx/test/units/vite-plugin-mdx-postprocess.test.ts @@ -14,7 +14,7 @@ await init; /** * Helper: parse code with es-module-lexer and return [imports, exports] */ -function parseCode(code) { +function parseCode(code: string) { return parse(code); } diff --git a/packages/integrations/mdx/tsconfig.test.json b/packages/integrations/mdx/tsconfig.test.json new file mode 100644 index 000000000000..853326403557 --- /dev/null +++ b/packages/integrations/mdx/tsconfig.test.json @@ -0,0 +1,13 @@ +{ + "extends": "../../../tsconfig.base.json", + "include": ["test/**/*.ts"], + "exclude": ["test/fixtures/**"], + "compilerOptions": { + "allowJs": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "rewriteRelativeImportExtensions": true, + "rootDir": "." + }, + "references": [{ "path": "../../astro/tsconfig.test.json" }] +}