diff --git a/packages/integrations/markdoc/package.json b/packages/integrations/markdoc/package.json
index 7c4fc4713b..7af2e1b154 100644
--- a/packages/integrations/markdoc/package.json
+++ b/packages/integrations/markdoc/package.json
@@ -59,8 +59,7 @@
     "build": "astro-scripts build \"src/**/*.ts\" && tsc",
     "build:ci": "astro-scripts build \"src/**/*.ts\"",
     "dev": "astro-scripts dev \"src/**/*.ts\"",
-    "test": "mocha --exit --timeout 20000",
-    "test:match": "mocha --timeout 20000 -g"
+    "test": "astro-scripts test \"test/**/*.test.js\""
   },
   "dependencies": {
     "@astrojs/internal-helpers": "workspace:*",
@@ -78,16 +77,12 @@
   },
   "devDependencies": {
     "@astrojs/markdown-remark": "workspace:*",
-    "@types/chai": "^4.3.10",
     "@types/html-escaper": "^3.0.2",
     "@types/markdown-it": "^13.0.6",
-    "@types/mocha": "^10.0.4",
     "astro": "workspace:*",
     "astro-scripts": "workspace:*",
-    "chai": "^4.3.7",
     "devalue": "^4.3.2",
     "linkedom": "^0.16.4",
-    "mocha": "^10.2.0",
     "vite": "^5.0.12"
   },
   "engines": {
diff --git a/packages/integrations/markdoc/test/content-collections.test.js b/packages/integrations/markdoc/test/content-collections.test.js
index aad389e0cc..cfd1c80c7e 100644
--- a/packages/integrations/markdoc/test/content-collections.test.js
+++ b/packages/integrations/markdoc/test/content-collections.test.js
@@ -1,7 +1,8 @@
 import { parse as parseDevalue } from 'devalue';
-import { expect } from 'chai';
 import { loadFixture, fixLineEndings } from '../../../astro/test/test-utils.js';
 import markdoc from '../dist/index.js';
+import assert from 'node:assert/strict';
+import { after, before, describe, it } from 'node:test';
 
 function formatPost(post) {
 	return {
@@ -36,19 +37,18 @@ describe('Markdoc - Content Collections', () => {
 		it('loads entry', async () => {
 			const res = await baseFixture.fetch('/entry.json');
 			const post = parseDevalue(await res.text());
-			expect(formatPost(post)).to.deep.equal(post1Entry);
+			assert.deepEqual(formatPost(post), post1Entry);
 		});
 
 		it('loads collection', async () => {
 			const res = await baseFixture.fetch('/collection.json');
 			const posts = parseDevalue(await res.text());
-			expect(posts).to.not.be.null;
+			assert.notEqual(posts, null);
 
-			expect(posts.sort().map((post) => formatPost(post))).to.deep.equal([
-				post1Entry,
-				post2Entry,
-				post3Entry,
-			]);
+			assert.deepEqual(
+				posts.sort().map((post) => formatPost(post)),
+				[post1Entry, post2Entry, post3Entry]
+			);
 		});
 	});
 
@@ -60,18 +60,17 @@ describe('Markdoc - Content Collections', () => {
 		it('loads entry', async () => {
 			const res = await baseFixture.readFile('/entry.json');
 			const post = parseDevalue(res);
-			expect(formatPost(post)).to.deep.equal(post1Entry);
+			assert.deepEqual(formatPost(post), post1Entry);
 		});
 
 		it('loads collection', async () => {
 			const res = await baseFixture.readFile('/collection.json');
 			const posts = parseDevalue(res);
-			expect(posts).to.not.be.null;
-			expect(posts.sort().map((post) => formatPost(post))).to.deep.equal([
-				post1Entry,
-				post2Entry,
-				post3Entry,
-			]);
+			assert.notEqual(posts, null);
+			assert.deepEqual(
+				posts.sort().map((post) => formatPost(post)),
+				[post1Entry, post2Entry, post3Entry]
+			);
 		});
 	});
 });
diff --git a/packages/integrations/markdoc/test/headings.test.js b/packages/integrations/markdoc/test/headings.test.js
index 5468e8c6b3..b87f6408e6 100644
--- a/packages/integrations/markdoc/test/headings.test.js
+++ b/packages/integrations/markdoc/test/headings.test.js
@@ -1,6 +1,7 @@
 import { parseHTML } from 'linkedom';
-import { expect } from 'chai';
 import { loadFixture } from '../../../astro/test/test-utils.js';
+import assert from 'node:assert/strict';
+import { after, before, describe, it } from 'node:test';
 
 async function getFixture(name) {
 	return await loadFixture({
@@ -195,20 +196,20 @@ const depthToHeadingMap = {
 /** @param {Document} document */
 function idTest(document) {
 	for (const [depth, info] of Object.entries(depthToHeadingMap)) {
-		expect(document.querySelector(`h${depth}`)?.getAttribute('id')).to.equal(info.slug);
+		assert.equal(document.querySelector(`h${depth}`)?.getAttribute('id'), info.slug);
 	}
 }
 
 /** @param {Document} document */
 function tocTest(document) {
 	const toc = document.querySelector('[data-toc] > ul');
-	expect(toc.children).to.have.lengthOf(Object.keys(depthToHeadingMap).length);
+	assert.equal(toc.children.length, Object.keys(depthToHeadingMap).length);
 
 	for (const [depth, info] of Object.entries(depthToHeadingMap)) {
 		const linkEl = toc.querySelector(`a[href="#${info.slug}"]`);
-		expect(linkEl).to.exist;
-		expect(linkEl.getAttribute('data-depth')).to.equal(depth);
-		expect(linkEl.textContent.trim()).to.equal(info.text);
+		assert.ok(linkEl);
+		assert.equal(linkEl.getAttribute('data-depth'), depth);
+		assert.equal(linkEl.textContent.trim(), info.text);
 	}
 }
 
@@ -217,6 +218,6 @@ function astroComponentTest(document) {
 	const headings = document.querySelectorAll('h1, h2, h3, h4, h5, h6');
 
 	for (const heading of headings) {
-		expect(heading.hasAttribute('data-custom-heading')).to.be.true;
+		assert.equal(heading.hasAttribute('data-custom-heading'), true);
 	}
 }
diff --git a/packages/integrations/markdoc/test/image-assets.test.js b/packages/integrations/markdoc/test/image-assets.test.js
index 7339960dd2..ae576b5cfd 100644
--- a/packages/integrations/markdoc/test/image-assets.test.js
+++ b/packages/integrations/markdoc/test/image-assets.test.js
@@ -1,6 +1,7 @@
 import { parseHTML } from 'linkedom';
-import { expect } from 'chai';
 import { loadFixture } from '../../../astro/test/test-utils.js';
+import assert from 'node:assert/strict';
+import { after, before, describe, it } from 'node:test';
 
 const root = new URL('./fixtures/image-assets/', import.meta.url);
 
@@ -28,14 +29,15 @@ describe('Markdoc - Image assets', () => {
 			const res = await baseFixture.fetch('/');
 			const html = await res.text();
 			const { document } = parseHTML(html);
-			expect(document.querySelector('#public > img')?.src).to.equal('/favicon.svg');
+			assert.equal(document.querySelector('#public > img')?.src, '/favicon.svg');
 		});
 
 		it('transforms relative image paths to optimized path', async () => {
 			const res = await baseFixture.fetch('/');
 			const html = await res.text();
 			const { document } = parseHTML(html);
-			expect(document.querySelector('#relative > img')?.src).to.match(
+			assert.match(
+				document.querySelector('#relative > img')?.src,
 				/\/_image\?href=.*%2Fsrc%2Fassets%2Frelative%2Foar.jpg%3ForigWidth%3D420%26origHeight%3D630%26origFormat%3Djpg&f=webp/
 			);
 		});
@@ -44,7 +46,8 @@ describe('Markdoc - Image assets', () => {
 			const res = await baseFixture.fetch('/');
 			const html = await res.text();
 			const { document } = parseHTML(html);
-			expect(document.querySelector('#alias > img')?.src).to.match(
+			assert.match(
+				document.querySelector('#alias > img')?.src,
 				/\/_image\?href=.*%2Fsrc%2Fassets%2Falias%2Fcityscape.jpg%3ForigWidth%3D420%26origHeight%3D280%26origFormat%3Djpg&f=webp/
 			);
 		});
@@ -58,19 +61,19 @@ describe('Markdoc - Image assets', () => {
 		it('uses public/ image paths unchanged', async () => {
 			const html = await baseFixture.readFile('/index.html');
 			const { document } = parseHTML(html);
-			expect(document.querySelector('#public > img')?.src).to.equal('/favicon.svg');
+			assert.equal(document.querySelector('#public > img')?.src, '/favicon.svg');
 		});
 
 		it('transforms relative image paths to optimized path', async () => {
 			const html = await baseFixture.readFile('/index.html');
 			const { document } = parseHTML(html);
-			expect(document.querySelector('#relative > img')?.src).to.match(/^\/_astro\/oar.*\.webp$/);
+			assert.match(document.querySelector('#relative > img')?.src, /^\/_astro\/oar.*\.webp$/);
 		});
 
 		it('transforms aliased image paths to optimized path', async () => {
 			const html = await baseFixture.readFile('/index.html');
 			const { document } = parseHTML(html);
-			expect(document.querySelector('#alias > img')?.src).to.match(/^\/_astro\/cityscape.*\.webp$/);
+			assert.match(document.querySelector('#alias > img')?.src, /^\/_astro\/cityscape.*\.webp$/);
 		});
 	});
 });
diff --git a/packages/integrations/markdoc/test/propagated-assets.test.js b/packages/integrations/markdoc/test/propagated-assets.test.js
index 4326233c1d..652c60014a 100644
--- a/packages/integrations/markdoc/test/propagated-assets.test.js
+++ b/packages/integrations/markdoc/test/propagated-assets.test.js
@@ -1,6 +1,7 @@
 import { parseHTML } from 'linkedom';
-import { expect } from 'chai';
 import { loadFixture } from '../../../astro/test/test-utils.js';
+import assert from 'node:assert/strict';
+import { after, before, describe, it } from 'node:test';
 
 describe('Markdoc - propagated assets', () => {
 	let fixture;
@@ -44,23 +45,23 @@ describe('Markdoc - propagated assets', () => {
 				let styleContents;
 				if (mode === 'dev') {
 					const styles = stylesDocument.querySelectorAll('style');
-					expect(styles).to.have.lengthOf(1);
+					assert.equal(styles.length, 1);
 					styleContents = styles[0].textContent;
 				} else {
 					const links = stylesDocument.querySelectorAll('link[rel="stylesheet"]');
-					expect(links).to.have.lengthOf(1);
+					assert.equal(links.length, 1);
 					styleContents = await fixture.readFile(links[0].href);
 				}
-				expect(styleContents).to.include('--color-base-purple: 269, 79%;');
+				assert.equal(styleContents.includes('--color-base-purple: 269, 79%;'), true);
 			});
 
 			it('[fails] Does not bleed styles to other page', async () => {
 				if (mode === 'dev') {
 					const styles = scriptsDocument.querySelectorAll('style');
-					expect(styles).to.have.lengthOf(0);
+					assert.equal(styles.length, 0);
 				} else {
 					const links = scriptsDocument.querySelectorAll('link[rel="stylesheet"]');
-					expect(links).to.have.lengthOf(0);
+					assert.equal(links.length, 0);
 				}
 			});
 		});
diff --git a/packages/integrations/markdoc/test/render-html.test.js b/packages/integrations/markdoc/test/render-html.test.js
index a0c38ace31..d83a916071 100644
--- a/packages/integrations/markdoc/test/render-html.test.js
+++ b/packages/integrations/markdoc/test/render-html.test.js
@@ -1,6 +1,7 @@
 import { parseHTML } from 'linkedom';
-import { expect } from 'chai';
 import { loadFixture } from '../../../astro/test/test-utils.js';
+import assert from 'node:assert/strict';
+import { after, before, describe, it } from 'node:test';
 
 async function getFixture(name) {
 	return await loadFixture({
@@ -91,24 +92,24 @@ function renderSimpleChecks(html) {
 	const { document } = parseHTML(html);
 
 	const h2 = document.querySelector('h2');
-	expect(h2.textContent).to.equal('Simple post header');
+	assert.equal(h2.textContent, 'Simple post header');
 
 	const spanInsideH2 = document.querySelector('h2 > span');
-	expect(spanInsideH2.textContent).to.equal('post');
-	expect(spanInsideH2.className).to.equal('inside-h2');
-	expect(spanInsideH2.style.color).to.equal('fuscia');
+	assert.equal(spanInsideH2.textContent, 'post');
+	assert.equal(spanInsideH2.className, 'inside-h2');
+	assert.equal(spanInsideH2.style.color, 'fuscia');
 
 	const p1 = document.querySelector('article > p:nth-of-type(1)');
-	expect(p1.children.length).to.equal(1);
-	expect(p1.textContent).to.equal('This is a simple Markdoc post.');
+	assert.equal(p1.children.length, 1);
+	assert.equal(p1.textContent, 'This is a simple Markdoc post.');
 
 	const p2 = document.querySelector('article > p:nth-of-type(2)');
-	expect(p2.children.length).to.equal(0);
-	expect(p2.textContent).to.equal('This is a paragraph!');
+	assert.equal(p2.children.length, 0);
+	assert.equal(p2.textContent, 'This is a paragraph!');
 
 	const p3 = document.querySelector('article > p:nth-of-type(3)');
-	expect(p3.children.length).to.equal(1);
-	expect(p3.textContent).to.equal('This is a span inside a paragraph!');
+	assert.equal(p3.children.length, 1);
+	assert.equal(p3.textContent, 'This is a span inside a paragraph!');
 }
 
 /** @param {string} html */
@@ -116,47 +117,47 @@ function renderNestedHTMLChecks(html) {
 	const { document } = parseHTML(html);
 
 	const p1 = document.querySelector('p:nth-of-type(1)');
-	expect(p1.id).to.equal('p1');
-	expect(p1.textContent).to.equal('before inner after');
-	expect(p1.children.length).to.equal(1);
+	assert.equal(p1.id, 'p1');
+	assert.equal(p1.textContent, 'before inner after');
+	assert.equal(p1.children.length, 1);
 
 	const p1Span1 = p1.querySelector('span');
-	expect(p1Span1.textContent).to.equal('inner');
-	expect(p1Span1.id).to.equal('inner1');
-	expect(p1Span1.className).to.equal('inner-class');
-	expect(p1Span1.style.color).to.equal('hotpink');
+	assert.equal(p1Span1.textContent, 'inner');
+	assert.equal(p1Span1.id, 'inner1');
+	assert.equal(p1Span1.className, 'inner-class');
+	assert.equal(p1Span1.style.color, 'hotpink');
 
 	const p2 = document.querySelector('p:nth-of-type(2)');
-	expect(p2.id).to.equal('p2');
-	expect(p2.textContent).to.equal('\n  before\n  inner\n  after\n');
-	expect(p2.children.length).to.equal(1);
+	assert.equal(p2.id, 'p2');
+	assert.equal(p2.textContent, '\n  before\n  inner\n  after\n');
+	assert.equal(p2.children.length, 1);
 
 	const divL1 = document.querySelector('div:nth-of-type(1)');
-	expect(divL1.id).to.equal('div-l1');
-	expect(divL1.children.length).to.equal(2);
+	assert.equal(divL1.id, 'div-l1');
+	assert.equal(divL1.children.length, 2);
 
 	const divL2_1 = divL1.querySelector('div:nth-of-type(1)');
-	expect(divL2_1.id).to.equal('div-l2-1');
-	expect(divL2_1.children.length).to.equal(1);
+	assert.equal(divL2_1.id, 'div-l2-1');
+	assert.equal(divL2_1.children.length, 1);
 
 	const p3 = divL2_1.querySelector('p:nth-of-type(1)');
-	expect(p3.id).to.equal('p3');
-	expect(p3.textContent).to.equal('before inner after');
-	expect(p3.children.length).to.equal(1);
+	assert.equal(p3.id, 'p3');
+	assert.equal(p3.textContent, 'before inner after');
+	assert.equal(p3.children.length, 1);
 
 	const divL2_2 = divL1.querySelector('div:nth-of-type(2)');
-	expect(divL2_2.id).to.equal('div-l2-2');
-	expect(divL2_2.children.length).to.equal(2);
+	assert.equal(divL2_2.id, 'div-l2-2');
+	assert.equal(divL2_2.children.length, 2);
 
 	const p4 = divL2_2.querySelector('p:nth-of-type(1)');
-	expect(p4.id).to.equal('p4');
-	expect(p4.textContent).to.equal('before inner after');
-	expect(p4.children.length).to.equal(1);
+	assert.equal(p4.id, 'p4');
+	assert.equal(p4.textContent, 'before inner after');
+	assert.equal(p4.children.length, 1);
 
 	const p5 = divL2_2.querySelector('p:nth-of-type(2)');
-	expect(p5.id).to.equal('p5');
-	expect(p5.textContent).to.equal('before inner after');
-	expect(p5.children.length).to.equal(1);
+	assert.equal(p5.id, 'p5');
+	assert.equal(p5.textContent, 'before inner after');
+	assert.equal(p5.children.length, 1);
 }
 
 /**
@@ -172,17 +173,17 @@ function renderRandomlyCasedHTMLAttributesChecks(html) {
 
 	// all four <td>'s which had randomly cased variants of colspan/rowspan should all be rendered lowercased at this point
 
-	expect(td1.getAttribute('colspan')).to.equal('3');
-	expect(td1.getAttribute('rowspan')).to.equal('2');
+	assert.equal(td1.getAttribute('colspan'), '3');
+	assert.equal(td1.getAttribute('rowspan'), '2');
 
-	expect(td2.getAttribute('colspan')).to.equal('3');
-	expect(td2.getAttribute('rowspan')).to.equal('2');
+	assert.equal(td2.getAttribute('colspan'), '3');
+	assert.equal(td2.getAttribute('rowspan'), '2');
 
-	expect(td3.getAttribute('colspan')).to.equal('3');
-	expect(td3.getAttribute('rowspan')).to.equal('2');
+	assert.equal(td3.getAttribute('colspan'), '3');
+	assert.equal(td3.getAttribute('rowspan'), '2');
 
-	expect(td4.getAttribute('colspan')).to.equal('3');
-	expect(td4.getAttribute('rowspan')).to.equal('2');
+	assert.equal(td4.getAttribute('colspan'), '3');
+	assert.equal(td4.getAttribute('rowspan'), '2');
 }
 
 /**
@@ -193,95 +194,95 @@ function renderComponentsHTMLChecks(html) {
 	const { document } = parseHTML(html);
 
 	const naturalP1 = document.querySelector('article > p:nth-of-type(1)');
-	expect(naturalP1.textContent).to.equal('This is a inline mark in regular Markdown markup.');
-	expect(naturalP1.children.length).to.equal(1);
+	assert.equal(naturalP1.textContent, 'This is a inline mark in regular Markdown markup.');
+	assert.equal(naturalP1.children.length, 1);
 
 	const p1 = document.querySelector('article > p:nth-of-type(2)');
-	expect(p1.id).to.equal('p1');
-	expect(p1.textContent).to.equal('This is a inline mark under some HTML');
-	expect(p1.children.length).to.equal(1);
+	assert.equal(p1.id, 'p1');
+	assert.equal(p1.textContent, 'This is a inline mark under some HTML');
+	assert.equal(p1.children.length, 1);
 	assertInlineMark(p1.children[0]);
 
 	const div1p1 = document.querySelector('article > #div1 > p:nth-of-type(1)');
-	expect(div1p1.id).to.equal('div1-p1');
-	expect(div1p1.textContent).to.equal('This is a inline mark under some HTML');
-	expect(div1p1.children.length).to.equal(1);
+	assert.equal(div1p1.id, 'div1-p1');
+	assert.equal(div1p1.textContent, 'This is a inline mark under some HTML');
+	assert.equal(div1p1.children.length, 1);
 	assertInlineMark(div1p1.children[0]);
 
 	const div1p2 = document.querySelector('article > #div1 > p:nth-of-type(2)');
-	expect(div1p2.id).to.equal('div1-p2');
-	expect(div1p2.textContent).to.equal('This is a inline mark under some HTML');
-	expect(div1p2.children.length).to.equal(1);
+	assert.equal(div1p2.id, 'div1-p2');
+	assert.equal(div1p2.textContent, 'This is a inline mark under some HTML');
+	assert.equal(div1p2.children.length, 1);
 
 	const div1p2span1 = div1p2.querySelector('span');
-	expect(div1p2span1.id).to.equal('div1-p2-span1');
-	expect(div1p2span1.textContent).to.equal('inline mark');
-	expect(div1p2span1.children.length).to.equal(1);
+	assert.equal(div1p2span1.id, 'div1-p2-span1');
+	assert.equal(div1p2span1.textContent, 'inline mark');
+	assert.equal(div1p2span1.children.length, 1);
 	assertInlineMark(div1p2span1.children[0]);
 
 	const aside1 = document.querySelector('article > aside:nth-of-type(1)');
 	const aside1Title = aside1.querySelector('p.title');
-	expect(aside1Title.textContent.trim()).to.equal('Aside One');
+	assert.equal(aside1Title.textContent.trim(), 'Aside One');
 	const aside1Section = aside1.querySelector('section');
 	const aside1SectionP1 = aside1Section.querySelector('p:nth-of-type(1)');
-	expect(aside1SectionP1.textContent).to.equal(
+	assert.equal(
+		aside1SectionP1.textContent,
 		"I'm a Markdown paragraph inside an top-level aside tag"
 	);
 	const aside1H2_1 = aside1Section.querySelector('h2:nth-of-type(1)');
-	expect(aside1H2_1.id).to.equal('im-an-h2-via-markdown-markup'); // automatic slug
-	expect(aside1H2_1.textContent).to.equal("I'm an H2 via Markdown markup");
+	assert.equal(aside1H2_1.id, 'im-an-h2-via-markdown-markup'); // automatic slug
+	assert.equal(aside1H2_1.textContent, "I'm an H2 via Markdown markup");
 	const aside1H2_2 = aside1Section.querySelector('h2:nth-of-type(2)');
-	expect(aside1H2_2.id).to.equal('h-two');
-	expect(aside1H2_2.textContent).to.equal("I'm an H2 via HTML markup");
+	assert.equal(aside1H2_2.id, 'h-two');
+	assert.equal(aside1H2_2.textContent, "I'm an H2 via HTML markup");
 	const aside1SectionP2 = aside1Section.querySelector('p:nth-of-type(2)');
-	expect(aside1SectionP2.textContent).to.equal('Markdown bold vs HTML bold');
-	expect(aside1SectionP2.children.length).to.equal(2);
+	assert.equal(aside1SectionP2.textContent, 'Markdown bold vs HTML bold');
+	assert.equal(aside1SectionP2.children.length, 2);
 	const aside1SectionP2Strong1 = aside1SectionP2.querySelector('strong:nth-of-type(1)');
-	expect(aside1SectionP2Strong1.textContent).to.equal('Markdown bold');
+	assert.equal(aside1SectionP2Strong1.textContent, 'Markdown bold');
 	const aside1SectionP2Strong2 = aside1SectionP2.querySelector('strong:nth-of-type(2)');
-	expect(aside1SectionP2Strong2.textContent).to.equal('HTML bold');
+	assert.equal(aside1SectionP2Strong2.textContent, 'HTML bold');
 
 	const article = document.querySelector('article');
-	expect(article.textContent).to.contain('RENDERED');
-	expect(article.textContent).to.not.contain('NOT RENDERED');
+	assert.equal(article.textContent.includes('RENDERED'), true);
+	assert.notEqual(article.textContent.includes('NOT RENDERED'), true);
 
 	const section1 = document.querySelector('article > #section1');
 	const section1div1 = section1.querySelector('#div1');
 	const section1Aside1 = section1div1.querySelector('aside:nth-of-type(1)');
 	const section1Aside1Title = section1Aside1.querySelector('p.title');
-	expect(section1Aside1Title.textContent.trim()).to.equal('Nested un-indented Aside');
+	assert.equal(section1Aside1Title.textContent.trim(), 'Nested un-indented Aside');
 	const section1Aside1Section = section1Aside1.querySelector('section');
 	const section1Aside1SectionP1 = section1Aside1Section.querySelector('p:nth-of-type(1)');
-	expect(section1Aside1SectionP1.textContent).to.equal('regular Markdown markup');
+	assert.equal(section1Aside1SectionP1.textContent, 'regular Markdown markup');
 	const section1Aside1SectionP4 = section1Aside1Section.querySelector('p:nth-of-type(2)');
-	expect(section1Aside1SectionP4.textContent).to.equal('nested inline mark content');
-	expect(section1Aside1SectionP4.children.length).to.equal(1);
+	assert.equal(section1Aside1SectionP4.textContent, 'nested inline mark content');
+	assert.equal(section1Aside1SectionP4.children.length, 1);
 	assertInlineMark(section1Aside1SectionP4.children[0]);
 
 	const section1div2 = section1.querySelector('#div2');
 	const section1Aside2 = section1div2.querySelector('aside:nth-of-type(1)');
 	const section1Aside2Title = section1Aside2.querySelector('p.title');
-	expect(section1Aside2Title.textContent.trim()).to.equal('Nested indented Aside 💀');
+	assert.equal(section1Aside2Title.textContent.trim(), 'Nested indented Aside 💀');
 	const section1Aside2Section = section1Aside2.querySelector('section');
 	const section1Aside2SectionP1 = section1Aside2Section.querySelector('p:nth-of-type(1)');
-	expect(section1Aside2SectionP1.textContent).to.equal('regular Markdown markup');
+	assert.equal(section1Aside2SectionP1.textContent, 'regular Markdown markup');
 	const section1Aside1SectionP5 = section1Aside2Section.querySelector('p:nth-of-type(2)');
-	expect(section1Aside1SectionP5.id).to.equal('p5');
-	expect(section1Aside1SectionP5.children.length).to.equal(1);
+	assert.equal(section1Aside1SectionP5.id, 'p5');
+	assert.equal(section1Aside1SectionP5.children.length, 1);
 	const section1Aside1SectionP5Span1 = section1Aside1SectionP5.children[0];
-	expect(section1Aside1SectionP5Span1.textContent).to.equal('inline mark');
-	expect(section1Aside1SectionP5Span1.children.length).to.equal(1);
+	assert.equal(section1Aside1SectionP5Span1.textContent, 'inline mark');
+	assert.equal(section1Aside1SectionP5Span1.children.length, 1);
 	const section1Aside1SectionP5Span1Span1 = section1Aside1SectionP5Span1.children[0];
-	expect(section1Aside1SectionP5Span1Span1.textContent).to.equal(' mark');
+	assert.equal(section1Aside1SectionP5Span1Span1.textContent, ' mark');
 }
 
 /** @param {HTMLElement | null | undefined} el */
 
 function assertInlineMark(el) {
-	expect(el).to.not.be.null;
-	expect(el).to.not.be.undefined;
-	expect(el.children.length).to.equal(0);
-	expect(el.textContent).to.equal('inline mark');
-	expect(el.className).to.equal('mark');
-	expect(el.style.color).to.equal('hotpink');
+	assert.ok(el);
+	assert.equal(el.children.length, 0);
+	assert.equal(el.textContent, 'inline mark');
+	assert.equal(el.className, 'mark');
+	assert.equal(el.style.color, 'hotpink');
 }
diff --git a/packages/integrations/markdoc/test/render.test.js b/packages/integrations/markdoc/test/render.test.js
index f1760a8e61..e5b8da74fa 100644
--- a/packages/integrations/markdoc/test/render.test.js
+++ b/packages/integrations/markdoc/test/render.test.js
@@ -1,6 +1,7 @@
 import { parseHTML } from 'linkedom';
-import { expect } from 'chai';
 import { loadFixture } from '../../../astro/test/test-utils.js';
+import assert from 'node:assert/strict';
+import { describe, it } from 'node:test';
 
 async function getFixture(name) {
 	return await loadFixture({
@@ -146,78 +147,76 @@ describe('Markdoc - render', () => {
 function renderNullChecks(html) {
 	const { document } = parseHTML(html);
 	const h2 = document.querySelector('h2');
-	expect(h2.textContent).to.equal('Post with render null');
-	expect(h2.parentElement?.tagName).to.equal('BODY');
+	assert.equal(h2.textContent, 'Post with render null');
+	assert.equal(h2.parentElement?.tagName, 'BODY');
 }
 
 /** @param {string} html */
 function renderComponentsChecks(html) {
 	const { document } = parseHTML(html);
 	const h2 = document.querySelector('h2');
-	expect(h2.textContent).to.equal('Post with components');
+	assert.equal(h2.textContent, 'Post with components');
 
 	// Renders custom shortcode component
 	const marquee = document.querySelector('marquee');
-	expect(marquee).to.not.be.null;
-	expect(marquee.hasAttribute('data-custom-marquee')).to.equal(true);
+	assert.notEqual(marquee, null);
+	assert.equal(marquee.hasAttribute('data-custom-marquee'), true);
 
 	// Renders Astro Code component
 	const pre = document.querySelector('pre');
-	expect(pre).to.not.be.null;
-	expect(pre.className).to.equal('astro-code github-dark');
+	assert.notEqual(pre, null);
+	assert.equal(pre.className, 'astro-code github-dark');
 }
 
 /** @param {string} html */
 function renderIndentedComponentsChecks(html) {
 	const { document } = parseHTML(html);
 	const h2 = document.querySelector('h2');
-	expect(h2.textContent).to.equal('Post with indented components');
+	assert.equal(h2.textContent, 'Post with indented components');
 
 	// Renders custom shortcode components
 	const marquees = document.querySelectorAll('marquee');
-	expect(marquees.length).to.equal(2);
+	assert.equal(marquees.length, 2);
 
 	// Renders h3
 	const h3 = document.querySelector('h3');
-	expect(h3.textContent).to.equal('I am an h3!');
+	assert.equal(h3.textContent, 'I am an h3!');
 
 	// Renders Astro Code component
 	const pre = document.querySelector('pre');
-	expect(pre).to.not.be.null;
-	expect(pre.className).to.equal('astro-code github-dark');
+	assert.notEqual(pre, null);
+	assert.equal(pre.className, 'astro-code github-dark');
 }
 
 /** @param {string} html */
 function renderConfigChecks(html) {
 	const { document } = parseHTML(html);
 	const h2 = document.querySelector('h2');
-	expect(h2.textContent).to.equal('Post with config');
+	assert.equal(h2.textContent, 'Post with config');
 	const textContent = html;
 
-	expect(textContent).to.not.include('Hello');
-	expect(textContent).to.include('Hola');
-	expect(textContent).to.include(`Konnichiwa`);
+	assert.notEqual(textContent.includes('Hello'), true);
+	assert.equal(textContent.includes('Hola'), true);
+	assert.equal(textContent.includes('Konnichiwa'), true);
 
 	const runtimeVariable = document.querySelector('#runtime-variable');
-	expect(runtimeVariable?.textContent?.trim()).to.equal('working!');
+	assert.equal(runtimeVariable?.textContent?.trim(), 'working!');
 }
 
 /** @param {string} html */
 function renderSimpleChecks(html) {
 	const { document } = parseHTML(html);
 	const h2 = document.querySelector('h2');
-	expect(h2.textContent).to.equal('Simple post');
+	assert.equal(h2.textContent, 'Simple post');
 	const p = document.querySelector('p');
-	expect(p.textContent).to.equal('This is a simple Markdoc post.');
+	assert.equal(p.textContent, 'This is a simple Markdoc post.');
 }
 
 /** @param {string} html */
 function renderWithRootFolderContainingSpace(html) {
 	const { document } = parseHTML(html);
 	const h2 = document.querySelector('h2');
-	expect(h2.textContent).to.equal('Simple post with root folder containing a space');
+	assert.equal(h2.textContent, 'Simple post with root folder containing a space');
 	const p = document.querySelector('p');
-	expect(p.textContent).to.equal(
-		'This is a simple Markdoc post with root folder containing a space.'
-	);
+	assert.equal(p.textContent, 'This is a simple Markdoc post with root folder containing a space.');
 }
diff --git a/packages/integrations/markdoc/test/syntax-highlighting.test.js b/packages/integrations/markdoc/test/syntax-highlighting.test.js
index 1530e0c825..1d0f6127ba 100644
--- a/packages/integrations/markdoc/test/syntax-highlighting.test.js
+++ b/packages/integrations/markdoc/test/syntax-highlighting.test.js
@@ -1,10 +1,11 @@
 import { parseHTML } from 'linkedom';
-import { expect } from 'chai';
 import Markdoc from '@markdoc/markdoc';
 import shiki from '../dist/extensions/shiki.js';
 import prism from '../dist/extensions/prism.js';
 import { setupConfig } from '../dist/runtime.js';
 import { isHTMLString } from 'astro/runtime/server/index.js';
+import assert from 'node:assert/strict';
+import { describe, it } from 'node:test';
 
 const entry = `
 \`\`\`ts
@@ -24,13 +25,13 @@ describe('Markdoc - syntax highlighting', () => {
 			const ast = Markdoc.parse(entry);
 			const content = Markdoc.transform(ast, await getConfigExtendingShiki());
 
-			expect(content.children).to.have.lengthOf(2);
+			assert.equal(content.children.length, 2);
 			for (const codeBlock of content.children) {
-				expect(isHTMLString(codeBlock)).to.be.true;
+				assert.equal(isHTMLString(codeBlock), true);
 
 				const pre = parsePreTag(codeBlock);
-				expect(pre.classList).to.include('astro-code');
-				expect(pre.classList).to.include('github-dark');
+				assert.equal(pre.classList.contains('astro-code'), true);
+				assert.equal(pre.classList.contains('github-dark'), true);
 			}
 		});
 		it('transforms with `theme` property', async () => {
@@ -41,13 +42,13 @@ describe('Markdoc - syntax highlighting', () => {
 					theme: 'dracula',
 				})
 			);
-			expect(content.children).to.have.lengthOf(2);
+			assert.equal(content.children.length, 2);
 			for (const codeBlock of content.children) {
-				expect(isHTMLString(codeBlock)).to.be.true;
+				assert.equal(isHTMLString(codeBlock), true);
 
 				const pre = parsePreTag(codeBlock);
-				expect(pre.classList).to.include('astro-code');
-				expect(pre.classList).to.include('dracula');
+				assert.equal(pre.classList.contains('astro-code'), true);
+				assert.equal(pre.classList.contains('dracula'), true);
 			}
 		});
 		it('transforms with `wrap` property', async () => {
@@ -58,13 +59,13 @@ describe('Markdoc - syntax highlighting', () => {
 					wrap: true,
 				})
 			);
-			expect(content.children).to.have.lengthOf(2);
+			assert.equal(content.children.length, 2);
 			for (const codeBlock of content.children) {
-				expect(isHTMLString(codeBlock)).to.be.true;
+				assert.equal(isHTMLString(codeBlock), true);
 
 				const pre = parsePreTag(codeBlock);
-				expect(pre.getAttribute('style')).to.include('white-space: pre-wrap');
-				expect(pre.getAttribute('style')).to.include('word-wrap: break-word');
+				assert.equal(pre.getAttribute('style').includes('white-space: pre-wrap'), true);
+				assert.equal(pre.getAttribute('style').includes('word-wrap: break-word'), true);
 			}
 		});
 	});
@@ -77,17 +78,17 @@ describe('Markdoc - syntax highlighting', () => {
 			});
 			const content = Markdoc.transform(ast, config);
 
-			expect(content.children).to.have.lengthOf(2);
+			assert.equal(content.children.length, 2);
 			const [tsBlock, cssBlock] = content.children;
 
-			expect(isHTMLString(tsBlock)).to.be.true;
-			expect(isHTMLString(cssBlock)).to.be.true;
+			assert.equal(isHTMLString(tsBlock), true);
+			assert.equal(isHTMLString(cssBlock), true);
 
 			const preTs = parsePreTag(tsBlock);
-			expect(preTs.classList).to.include('language-ts');
+			assert.equal(preTs.classList.contains('language-ts'), true);
 
 			const preCss = parsePreTag(cssBlock);
-			expect(preCss.classList).to.include('language-css');
+			assert.equal(preCss.classList.contains('language-css'), true);
 		});
 	});
 });
@@ -109,6 +110,6 @@ async function getConfigExtendingShiki(config) {
 function parsePreTag(html) {
 	const { document } = parseHTML(html);
 	const pre = document.querySelector('pre');
-	expect(pre).to.exist;
+	assert.ok(pre);
 	return pre;
 }
diff --git a/packages/integrations/markdoc/test/variables.test.js b/packages/integrations/markdoc/test/variables.test.js
index 90d5fe2763..c26ca3c45b 100644
--- a/packages/integrations/markdoc/test/variables.test.js
+++ b/packages/integrations/markdoc/test/variables.test.js
@@ -1,7 +1,8 @@
 import { parseHTML } from 'linkedom';
-import { expect } from 'chai';
 import { loadFixture } from '../../../astro/test/test-utils.js';
 import markdoc from '../dist/index.js';
+import assert from 'node:assert/strict';
+import { after, before, describe, it } from 'node:test';
 
 const root = new URL('./fixtures/variables/', import.meta.url);
 
@@ -30,12 +31,10 @@ describe('Markdoc - Variables', () => {
 			const res = await baseFixture.fetch('/');
 			const html = await res.text();
 			const { document } = parseHTML(html);
-			expect(document.querySelector('h1')?.textContent).to.equal('Processed by schema: Test entry');
-			expect(document.getElementById('id')?.textContent?.trim()).to.equal('id: entry.mdoc');
-			expect(document.getElementById('slug')?.textContent?.trim()).to.equal('slug: entry');
-			expect(document.getElementById('collection')?.textContent?.trim()).to.equal(
-				'collection: blog'
-			);
+			assert.equal(document.querySelector('h1')?.textContent, 'Processed by schema: Test entry');
+			assert.equal(document.getElementById('id')?.textContent?.trim(), 'id: entry.mdoc');
+			assert.equal(document.getElementById('slug')?.textContent?.trim(), 'slug: entry');
+			assert.equal(document.getElementById('collection')?.textContent?.trim(), 'collection: blog');
 		});
 	});
 
@@ -47,12 +46,10 @@ describe('Markdoc - Variables', () => {
 		it('has expected entry properties', async () => {
 			const html = await baseFixture.readFile('/index.html');
 			const { document } = parseHTML(html);
-			expect(document.querySelector('h1')?.textContent).to.equal('Processed by schema: Test entry');
-			expect(document.getElementById('id')?.textContent?.trim()).to.equal('id: entry.mdoc');
-			expect(document.getElementById('slug')?.textContent?.trim()).to.equal('slug: entry');
-			expect(document.getElementById('collection')?.textContent?.trim()).to.equal(
-				'collection: blog'
-			);
+			assert.equal(document.querySelector('h1')?.textContent, 'Processed by schema: Test entry');
+			assert.equal(document.getElementById('id')?.textContent?.trim(), 'id: entry.mdoc');
+			assert.equal(document.getElementById('slug')?.textContent?.trim(), 'slug: entry');
+			assert.equal(document.getElementById('collection')?.textContent?.trim(), 'collection: blog');
 		});
 	});
 });
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index fca1b54939..228f5cfc6d 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -3926,39 +3926,27 @@ importers:
       '@astrojs/markdown-remark':
         specifier: workspace:*
         version: link:../../markdown/remark
-      '@types/chai':
-        specifier: ^4.3.10
-        version: 4.3.11
       '@types/html-escaper':
         specifier: ^3.0.2
         version: 3.0.2
       '@types/markdown-it':
         specifier: ^13.0.6
         version: 13.0.7
-      '@types/mocha':
-        specifier: ^10.0.4
-        version: 10.0.6
       astro:
         specifier: workspace:*
         version: link:../../astro
       astro-scripts:
         specifier: workspace:*
         version: link:../../../scripts
-      chai:
-        specifier: ^4.3.7
-        version: 4.3.10
       devalue:
         specifier: ^4.3.2
         version: 4.3.2
       linkedom:
         specifier: ^0.16.4
         version: 0.16.6
-      mocha:
-        specifier: ^10.2.0
-        version: 10.2.0
       vite:
         specifier: ^5.0.12
-        version: 5.0.12(@types/node@18.19.4)(sass@1.69.6)
+        version: 5.0.12
 
   packages/integrations/markdoc/test/fixtures/content-collections:
     dependencies:
@@ -15696,6 +15684,41 @@ packages:
       svgo: 3.2.0
     dev: false
 
+  /vite@5.0.12:
+    resolution: {integrity: sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w==}
+    engines: {node: ^18.0.0 || >=20.0.0}
+    hasBin: true
+    peerDependencies:
+      '@types/node': ^18.0.0 || >=20.0.0
+      less: '*'
+      lightningcss: ^1.21.0
+      sass: '*'
+      stylus: '*'
+      sugarss: '*'
+      terser: ^5.4.0
+    peerDependenciesMeta:
+      '@types/node':
+        optional: true
+      less:
+        optional: true
+      lightningcss:
+        optional: true
+      sass:
+        optional: true
+      stylus:
+        optional: true
+      sugarss:
+        optional: true
+      terser:
+        optional: true
+    dependencies:
+      esbuild: 0.19.11
+      postcss: 8.4.33
+      rollup: 4.9.2
+    optionalDependencies:
+      fsevents: 2.3.3
+    dev: true
+
   /vite@5.0.12(@types/node@18.19.4)(sass@1.69.6):
     resolution: {integrity: sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w==}
     engines: {node: ^18.0.0 || >=20.0.0}