diff --git a/.eslintrc.cjs b/.eslintrc.cjs
index 28ddba1e6..2be3344d0 100644
--- a/.eslintrc.cjs
+++ b/.eslintrc.cjs
@@ -19,4 +19,18 @@ module.exports = {
'@typescript-eslint/no-shadow': ['error'],
'no-only-tests/no-only-tests': 'error',
},
+ overrides: [
+ {
+ files: ['packages/**/test/*.js', 'packages/**/*.test.js'],
+ env: {
+ mocha: true,
+ },
+ globals: {
+ globalThis: false, // false means read-only
+ },
+ rules: {
+ 'no-console': 'off',
+ },
+ },
+ ],
};
diff --git a/packages/astro/test/.eslintrc.cjs b/packages/astro/test/.eslintrc.cjs
deleted file mode 100644
index 0e6acda5e..000000000
--- a/packages/astro/test/.eslintrc.cjs
+++ /dev/null
@@ -1,5 +0,0 @@
-module.exports = {
- rules: {
- 'no-console': 'off',
- },
-};
diff --git a/packages/astro/test/fixtures/special-chars-in-component-imports/astro.config.mjs b/packages/astro/test/fixtures/special-chars-in-component-imports/astro.config.mjs
new file mode 100644
index 000000000..5c044b69d
--- /dev/null
+++ b/packages/astro/test/fixtures/special-chars-in-component-imports/astro.config.mjs
@@ -0,0 +1,8 @@
+import { defineConfig } from 'astro/config';
+import react from '@astrojs/react';
+import mdx from '@astrojs/mdx';
+
+// https://astro.build/config
+export default defineConfig({
+ integrations: [react(), mdx()],
+});
diff --git a/packages/astro/test/fixtures/special-chars-in-component-imports/package.json b/packages/astro/test/fixtures/special-chars-in-component-imports/package.json
new file mode 100644
index 000000000..d8fbad235
--- /dev/null
+++ b/packages/astro/test/fixtures/special-chars-in-component-imports/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "@test/special-chars-in-component-imports",
+ "version": "0.0.0",
+ "private": true,
+ "dependencies": {
+ "@astrojs/mdx": "workspace:*",
+ "@astrojs/react": "workspace:*",
+ "astro": "workspace:*",
+ "react": "^18.1.0",
+ "react-dom": "^18.1.0"
+ }
+}
diff --git a/packages/astro/test/fixtures/special-chars-in-component-imports/src/components/^--with-carets/Counter.tsx b/packages/astro/test/fixtures/special-chars-in-component-imports/src/components/^--with-carets/Counter.tsx
new file mode 100644
index 000000000..5e1dec1b5
--- /dev/null
+++ b/packages/astro/test/fixtures/special-chars-in-component-imports/src/components/^--with-carets/Counter.tsx
@@ -0,0 +1,12 @@
+import React, { useState } from 'react';
+
+export default function Counter ({ id }) {
+ const [count, setCount] = useState(0);
+
+ return (
+
+
{id}: {count}
+
+
+ );
+}
diff --git a/packages/astro/test/fixtures/special-chars-in-component-imports/src/components/and-rockets-🚀/Counter.tsx b/packages/astro/test/fixtures/special-chars-in-component-imports/src/components/and-rockets-🚀/Counter.tsx
new file mode 100644
index 000000000..5e1dec1b5
--- /dev/null
+++ b/packages/astro/test/fixtures/special-chars-in-component-imports/src/components/and-rockets-🚀/Counter.tsx
@@ -0,0 +1,12 @@
+import React, { useState } from 'react';
+
+export default function Counter ({ id }) {
+ const [count, setCount] = useState(0);
+
+ return (
+
+
{id}: {count}
+
+
+ );
+}
diff --git a/packages/astro/test/fixtures/special-chars-in-component-imports/src/components/now-100%-better/Counter.tsx b/packages/astro/test/fixtures/special-chars-in-component-imports/src/components/now-100%-better/Counter.tsx
new file mode 100644
index 000000000..5e1dec1b5
--- /dev/null
+++ b/packages/astro/test/fixtures/special-chars-in-component-imports/src/components/now-100%-better/Counter.tsx
@@ -0,0 +1,12 @@
+import React, { useState } from 'react';
+
+export default function Counter ({ id }) {
+ const [count, setCount] = useState(0);
+
+ return (
+
+
{id}: {count}
+
+
+ );
+}
diff --git a/packages/astro/test/fixtures/special-chars-in-component-imports/src/components/with some spaces/Counter.tsx b/packages/astro/test/fixtures/special-chars-in-component-imports/src/components/with some spaces/Counter.tsx
new file mode 100644
index 000000000..5e1dec1b5
--- /dev/null
+++ b/packages/astro/test/fixtures/special-chars-in-component-imports/src/components/with some spaces/Counter.tsx
@@ -0,0 +1,12 @@
+import React, { useState } from 'react';
+
+export default function Counter ({ id }) {
+ const [count, setCount] = useState(0);
+
+ return (
+
+
{id}: {count}
+
+
+ );
+}
diff --git a/packages/astro/test/fixtures/special-chars-in-component-imports/src/components/with-(round-brackets)/Counter.tsx b/packages/astro/test/fixtures/special-chars-in-component-imports/src/components/with-(round-brackets)/Counter.tsx
new file mode 100644
index 000000000..5e1dec1b5
--- /dev/null
+++ b/packages/astro/test/fixtures/special-chars-in-component-imports/src/components/with-(round-brackets)/Counter.tsx
@@ -0,0 +1,12 @@
+import React, { useState } from 'react';
+
+export default function Counter ({ id }) {
+ const [count, setCount] = useState(0);
+
+ return (
+
+
{id}: {count}
+
+
+ );
+}
diff --git a/packages/astro/test/fixtures/special-chars-in-component-imports/src/components/with-[square-brackets]/Counter.tsx b/packages/astro/test/fixtures/special-chars-in-component-imports/src/components/with-[square-brackets]/Counter.tsx
new file mode 100644
index 000000000..5e1dec1b5
--- /dev/null
+++ b/packages/astro/test/fixtures/special-chars-in-component-imports/src/components/with-[square-brackets]/Counter.tsx
@@ -0,0 +1,12 @@
+import React, { useState } from 'react';
+
+export default function Counter ({ id }) {
+ const [count, setCount] = useState(0);
+
+ return (
+
+
{id}: {count}
+
+
+ );
+}
diff --git a/packages/astro/test/fixtures/special-chars-in-component-imports/src/pages/index.astro b/packages/astro/test/fixtures/special-chars-in-component-imports/src/pages/index.astro
new file mode 100644
index 000000000..5e7ff90cf
--- /dev/null
+++ b/packages/astro/test/fixtures/special-chars-in-component-imports/src/pages/index.astro
@@ -0,0 +1,19 @@
+---
+import CaretCounter from '../components/^--with-carets/Counter';
+import RocketCounter from '../components/and-rockets-🚀/Counter';
+import PercentCounter from '../components/now-100%-better/Counter';
+import SpaceCounter from '../components/with some spaces/Counter';
+import RoundBracketCounter from '../components/with-(round-brackets)/Counter';
+import SquareBracketCounter from '../components/with-[square-brackets]/Counter';
+---
+
+
+ Special chars in component import paths from an .astro file
+
+
+
+
+
+
+
+
diff --git a/packages/astro/test/fixtures/special-chars-in-component-imports/src/pages/mdx.mdx b/packages/astro/test/fixtures/special-chars-in-component-imports/src/pages/mdx.mdx
new file mode 100644
index 000000000..f9ccc9e2b
--- /dev/null
+++ b/packages/astro/test/fixtures/special-chars-in-component-imports/src/pages/mdx.mdx
@@ -0,0 +1,15 @@
+import CaretCounter from '../components/^--with-carets/Counter'
+import RocketCounter from '../components/and-rockets-🚀/Counter'
+import PercentCounter from '../components/now-100%-better/Counter'
+import SpaceCounter from '../components/with some spaces/Counter'
+import RoundBracketCounter from '../components/with-(round-brackets)/Counter'
+import SquareBracketCounter from '../components/with-[square-brackets]/Counter'
+
+# Special chars in component import paths from an .mdx file
+
+
+
+
+
+
+
diff --git a/packages/astro/test/special-chars-in-component-imports.test.js b/packages/astro/test/special-chars-in-component-imports.test.js
new file mode 100644
index 000000000..6d3b007b8
--- /dev/null
+++ b/packages/astro/test/special-chars-in-component-imports.test.js
@@ -0,0 +1,126 @@
+import { expect } from 'chai';
+import { load as cheerioLoad } from 'cheerio';
+import { isWindows, loadFixture } from './test-utils.js';
+
+describe.skip('Special chars in component import paths', () => {
+ /** @type {import('./test-utils').Fixture} */
+ let fixture;
+
+ const componentIds = ['caret', 'rocket', 'percent', 'space', 'round-bracket', 'square-bracket'];
+
+ before(async () => {
+ fixture = await loadFixture({
+ root: './fixtures/special-chars-in-component-imports/',
+ });
+ });
+
+ describe('build', () => {
+ before(async () => {
+ await fixture.build();
+ });
+
+ it('Build succeeds', async () => {
+ const html = await fixture.readFile('/index.html');
+ expect(html).to.contain('');
+ });
+
+ it('Special chars in imports work from .astro files', async () => {
+ const html = await fixture.readFile('/index.html');
+ const $ = cheerioLoad(html);
+
+ // Test 1: Correct page
+ expect($('h1').text()).to.contain('.astro');
+
+ // Test 2: All components exist
+ componentIds.forEach((componentId) => {
+ expect($(`#${componentId}`), `Component #${componentId} does not exist`).to.have.lengthOf(1);
+ });
+
+ // Test 3: Component contents were rendered properly
+ componentIds.forEach((componentId) => {
+ expect($(`#${componentId} > div`).text()).to.equal(`${componentId}: 0`);
+ });
+
+ // Test 4: There is an island for each component
+ expect($('astro-island[uid]')).to.have.lengthOf(componentIds.length);
+ });
+
+ it('Special chars in imports work from .mdx files', async () => {
+ const html = await fixture.readFile('/mdx/index.html');
+ const $ = cheerioLoad(html);
+
+ // Test 1: Correct page
+ expect($('h1').text()).to.contain('.mdx');
+
+ // Test 2: All components exist
+ componentIds.forEach((componentId) => {
+ expect($(`#${componentId}`), `Component #${componentId} does not exist`).to.have.lengthOf(1);
+ });
+
+ // Test 3: Component contents were rendered properly
+ componentIds.forEach((componentId) => {
+ expect($(`#${componentId} > div`).text()).to.equal(`${componentId}: 0`);
+ });
+
+ // Test 4: There is an island for each component
+ expect($('astro-island[uid]')).to.have.lengthOf(componentIds.length);
+ });
+
+ });
+
+ if (isWindows) return;
+
+ describe('dev', () => {
+ let devServer;
+
+ before(async () => {
+ devServer = await fixture.startDevServer();
+ });
+
+ after(async () => {
+ await devServer.stop();
+ });
+
+ it('Special chars in imports work from .astro files', async () => {
+ const html = await fixture.fetch('/').then((res) => res.text());
+ const $ = cheerioLoad(html);
+
+ // Test 1: Correct page
+ expect($('h1').text()).to.contain('.astro');
+
+ // Test 2: All components exist
+ componentIds.forEach((componentId) => {
+ expect($(`#${componentId}`), `Component #${componentId} does not exist`).to.have.lengthOf(1);
+ });
+
+ // Test 3: Component contents were rendered properly
+ componentIds.forEach((componentId) => {
+ expect($(`#${componentId} > div`).text()).to.equal(`${componentId}: 0`);
+ });
+
+ // Test 4: There is an island for each component
+ expect($('astro-island[uid]')).to.have.lengthOf(componentIds.length);
+ });
+
+ it('Special chars in imports work from .mdx files', async () => {
+ const html = await fixture.fetch('/mdx').then((res) => res.text());
+ const $ = cheerioLoad(html);
+
+ // Test 1: Correct page
+ expect($('h1').text()).to.contain('.mdx');
+
+ // Test 2: All components exist
+ componentIds.forEach((componentId) => {
+ expect($(`#${componentId}`), `Component #${componentId} does not exist`).to.have.lengthOf(1);
+ });
+
+ // Test 3: Component contents were rendered properly
+ componentIds.forEach((componentId) => {
+ expect($(`#${componentId} > div`).text()).to.equal(`${componentId}: 0`);
+ });
+
+ // Test 4: There is an island for each component
+ expect($('astro-island[uid]')).to.have.lengthOf(componentIds.length);
+ });
+ });
+});
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 7575d01fd..a317f90ab 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1864,6 +1864,20 @@ importers:
react: 18.2.0
react-dom: 18.2.0_react@18.2.0
+ packages/astro/test/fixtures/special-chars-in-component-imports:
+ specifiers:
+ '@astrojs/mdx': workspace:*
+ '@astrojs/react': workspace:*
+ astro: workspace:*
+ react: ^18.1.0
+ react-dom: ^18.1.0
+ dependencies:
+ '@astrojs/mdx': link:../../../../integrations/mdx
+ '@astrojs/react': link:../../../../integrations/react
+ astro: link:../../..
+ react: 18.2.0
+ react-dom: 18.2.0_react@18.2.0
+
packages/astro/test/fixtures/ssr-api-route:
specifiers:
astro: workspace:*