aboutsummaryrefslogtreecommitdiff
path: root/packages/integrations/sitemap/test
diff options
context:
space:
mode:
Diffstat (limited to 'packages/integrations/sitemap/test')
-rw-r--r--packages/integrations/sitemap/test/base-path.test.js52
-rw-r--r--packages/integrations/sitemap/test/config.test.js125
-rw-r--r--packages/integrations/sitemap/test/dynamic-path.test.js24
-rw-r--r--packages/integrations/sitemap/test/fixtures/dynamic/astro.config.mjs7
-rw-r--r--packages/integrations/sitemap/test/fixtures/dynamic/package.json9
-rw-r--r--packages/integrations/sitemap/test/fixtures/dynamic/src/pages/[...slug].astro21
-rw-r--r--packages/integrations/sitemap/test/fixtures/ssr/astro.config.mjs12
-rw-r--r--packages/integrations/sitemap/test/fixtures/ssr/package.json10
-rw-r--r--packages/integrations/sitemap/test/fixtures/ssr/src/pages/one.astro8
-rw-r--r--packages/integrations/sitemap/test/fixtures/ssr/src/pages/two.astro8
-rw-r--r--packages/integrations/sitemap/test/fixtures/static/astro.config.mjs18
-rw-r--r--packages/integrations/sitemap/test/fixtures/static/deps.mjs1
-rw-r--r--packages/integrations/sitemap/test/fixtures/static/package.json9
-rw-r--r--packages/integrations/sitemap/test/fixtures/static/src/pages/123.astro8
-rw-r--r--packages/integrations/sitemap/test/fixtures/static/src/pages/404.astro8
-rw-r--r--packages/integrations/sitemap/test/fixtures/static/src/pages/[lang]/manifest.ts15
-rw-r--r--packages/integrations/sitemap/test/fixtures/static/src/pages/[slug].astro17
-rw-r--r--packages/integrations/sitemap/test/fixtures/static/src/pages/de/404.astro8
-rw-r--r--packages/integrations/sitemap/test/fixtures/static/src/pages/endpoint.json.ts6
-rw-r--r--packages/integrations/sitemap/test/fixtures/static/src/pages/products-by-id/[id].astro11
-rw-r--r--packages/integrations/sitemap/test/fixtures/trailing-slash/astro.config.mjs7
-rw-r--r--packages/integrations/sitemap/test/fixtures/trailing-slash/package.json9
-rw-r--r--packages/integrations/sitemap/test/fixtures/trailing-slash/src/pages/index.astro8
-rw-r--r--packages/integrations/sitemap/test/fixtures/trailing-slash/src/pages/one.astro8
-rw-r--r--packages/integrations/sitemap/test/fixtures/trailing-slash/src/pages/two.astro8
-rw-r--r--packages/integrations/sitemap/test/routes.test.js27
-rw-r--r--packages/integrations/sitemap/test/smoke.test.js3
-rw-r--r--packages/integrations/sitemap/test/ssr.test.js23
-rw-r--r--packages/integrations/sitemap/test/staticPaths.test.js48
-rw-r--r--packages/integrations/sitemap/test/test-utils.js29
-rw-r--r--packages/integrations/sitemap/test/trailing-slash.test.js127
-rw-r--r--packages/integrations/sitemap/test/units/generate-sitemap.test.js147
32 files changed, 821 insertions, 0 deletions
diff --git a/packages/integrations/sitemap/test/base-path.test.js b/packages/integrations/sitemap/test/base-path.test.js
new file mode 100644
index 000000000..fee031ff4
--- /dev/null
+++ b/packages/integrations/sitemap/test/base-path.test.js
@@ -0,0 +1,52 @@
+import assert from 'node:assert/strict';
+import { before, describe, it } from 'node:test';
+import { loadFixture, readXML } from './test-utils.js';
+
+describe('URLs with base path', () => {
+ /** @type {import('./test-utils').Fixture} */
+ let fixture;
+
+ describe('using node adapter', () => {
+ before(async () => {
+ fixture = await loadFixture({
+ root: './fixtures/ssr/',
+ base: '/base',
+ });
+ await fixture.build();
+ });
+
+ it('Base path is concatenated correctly', async () => {
+ const [sitemapZero, sitemapIndex] = await Promise.all([
+ readXML(fixture.readFile('/client/sitemap-0.xml')),
+ readXML(fixture.readFile('/client/sitemap-index.xml')),
+ ]);
+ assert.equal(sitemapZero.urlset.url[0].loc[0], 'http://example.com/base/one/');
+ assert.equal(
+ sitemapIndex.sitemapindex.sitemap[0].loc[0],
+ 'http://example.com/base/sitemap-0.xml',
+ );
+ });
+ });
+
+ describe('static', () => {
+ before(async () => {
+ fixture = await loadFixture({
+ root: './fixtures/static/',
+ base: '/base',
+ });
+ await fixture.build();
+ });
+
+ it('Base path is concatenated correctly', async () => {
+ const [sitemapZero, sitemapIndex] = await Promise.all([
+ readXML(fixture.readFile('/sitemap-0.xml')),
+ readXML(fixture.readFile('/sitemap-index.xml')),
+ ]);
+ assert.equal(sitemapZero.urlset.url[0].loc[0], 'http://example.com/base/123/');
+ assert.equal(
+ sitemapIndex.sitemapindex.sitemap[0].loc[0],
+ 'http://example.com/base/sitemap-0.xml',
+ );
+ });
+ });
+});
diff --git a/packages/integrations/sitemap/test/config.test.js b/packages/integrations/sitemap/test/config.test.js
new file mode 100644
index 000000000..f95333876
--- /dev/null
+++ b/packages/integrations/sitemap/test/config.test.js
@@ -0,0 +1,125 @@
+import assert from 'node:assert/strict';
+import { before, describe, it } from 'node:test';
+import { sitemap } from './fixtures/static/deps.mjs';
+import { loadFixture, readXML } from './test-utils.js';
+
+describe('Config', () => {
+ /** @type {import('./test-utils.js').Fixture} */
+ let fixture;
+
+ describe('Static', () => {
+ before(async () => {
+ fixture = await loadFixture({
+ root: './fixtures/static/',
+ integrations: [
+ sitemap({
+ filter: (page) => page === 'http://example.com/one/',
+ xslURL: '/sitemap.xsl',
+ }),
+ ],
+ });
+ await fixture.build();
+ });
+
+ it('filter: Just one page is added', async () => {
+ const data = await readXML(fixture.readFile('/sitemap-0.xml'));
+ const urls = data.urlset.url;
+ assert.equal(urls.length, 1);
+ });
+
+ it('xslURL: Includes xml-stylesheet', async () => {
+ const indexXml = await fixture.readFile('/sitemap-index.xml');
+ assert.ok(
+ indexXml.includes(
+ '<?xml-stylesheet type="text/xsl" href="http://example.com/sitemap.xsl"?>',
+ ),
+ indexXml,
+ );
+
+ const xml = await fixture.readFile('/sitemap-0.xml');
+ assert.ok(
+ xml.includes('<?xml-stylesheet type="text/xsl" href="http://example.com/sitemap.xsl"?>'),
+ xml,
+ );
+ });
+ });
+
+ describe('SSR', () => {
+ before(async () => {
+ fixture = await loadFixture({
+ root: './fixtures/ssr/',
+ integrations: [
+ sitemap({
+ filter: (page) => page === 'http://example.com/one/',
+ xslURL: '/sitemap.xsl',
+ }),
+ ],
+ });
+ await fixture.build();
+ });
+
+ it('filter: Just one page is added', async () => {
+ const data = await readXML(fixture.readFile('/client/sitemap-0.xml'));
+ const urls = data.urlset.url;
+ assert.equal(urls.length, 1);
+ });
+
+ it('xslURL: Includes xml-stylesheet', async () => {
+ const indexXml = await fixture.readFile('/client/sitemap-index.xml');
+ assert.ok(
+ indexXml.includes(
+ '<?xml-stylesheet type="text/xsl" href="http://example.com/sitemap.xsl"?>',
+ ),
+ indexXml,
+ );
+
+ const xml = await fixture.readFile('/client/sitemap-0.xml');
+ assert.ok(
+ xml.includes('<?xml-stylesheet type="text/xsl" href="http://example.com/sitemap.xsl"?>'),
+ xml,
+ );
+ });
+ });
+
+ describe('Configuring the filename', () => {
+ before(async () => {
+ fixture = await loadFixture({
+ root: './fixtures/static/',
+ integrations: [
+ sitemap({
+ filter: (page) => page === 'http://example.com/one/',
+ filenameBase: 'my-sitemap',
+ }),
+ ],
+ });
+ await fixture.build();
+ });
+
+ it('filenameBase: Sets the generated sitemap filename', async () => {
+ const data = await readXML(fixture.readFile('/my-sitemap-0.xml'));
+ const urls = data.urlset.url;
+ assert.equal(urls.length, 1);
+
+ const indexData = await readXML(fixture.readFile('/my-sitemap-index.xml'));
+ const sitemapUrls = indexData.sitemapindex.sitemap;
+ assert.equal(sitemapUrls.length, 1);
+ assert.equal(sitemapUrls[0].loc[0], 'http://example.com/my-sitemap-0.xml');
+ });
+ });
+
+ describe('Filtering pages - error handling', () => {
+ it('filter: uncaught errors are thrown', async () => {
+ fixture = await loadFixture({
+ root: './fixtures/static/',
+ integrations: [
+ sitemap({
+ filter: () => {
+ throw new Error('filter error');
+ },
+ }),
+ ],
+ });
+ await assert.rejects(fixture.build(), /^Error: filter error$/);
+ });
+ });
+});
diff --git a/packages/integrations/sitemap/test/dynamic-path.test.js b/packages/integrations/sitemap/test/dynamic-path.test.js
new file mode 100644
index 000000000..eab3b912c
--- /dev/null
+++ b/packages/integrations/sitemap/test/dynamic-path.test.js
@@ -0,0 +1,24 @@
+import assert from 'node:assert/strict';
+import { before, describe, it } from 'node:test';
+import { loadFixture, readXML } from './test-utils.js';
+
+describe('Dynamic with rest parameter', () => {
+ /** @type {import('./test-utils.js').Fixture} */
+ let fixture;
+
+ before(async () => {
+ fixture = await loadFixture({
+ root: './fixtures/dynamic',
+ });
+ await fixture.build();
+ });
+
+ it('Should generate correct urls', async () => {
+ const data = await readXML(fixture.readFile('/sitemap-0.xml'));
+ const urls = data.urlset.url.map((url) => url.loc[0]);
+
+ assert.ok(urls.includes('http://example.com/'));
+ assert.ok(urls.includes('http://example.com/blog/'));
+ assert.ok(urls.includes('http://example.com/test/'));
+ });
+});
diff --git a/packages/integrations/sitemap/test/fixtures/dynamic/astro.config.mjs b/packages/integrations/sitemap/test/fixtures/dynamic/astro.config.mjs
new file mode 100644
index 000000000..82d25b854
--- /dev/null
+++ b/packages/integrations/sitemap/test/fixtures/dynamic/astro.config.mjs
@@ -0,0 +1,7 @@
+import sitemap from '@astrojs/sitemap';
+import { defineConfig } from 'astro/config';
+
+export default defineConfig({
+ integrations: [sitemap()],
+ site: 'http://example.com'
+})
diff --git a/packages/integrations/sitemap/test/fixtures/dynamic/package.json b/packages/integrations/sitemap/test/fixtures/dynamic/package.json
new file mode 100644
index 000000000..1eac19a1b
--- /dev/null
+++ b/packages/integrations/sitemap/test/fixtures/dynamic/package.json
@@ -0,0 +1,9 @@
+{
+ "name": "@test/sitemap-dynamic",
+ "version": "0.0.0",
+ "private": true,
+ "dependencies": {
+ "astro": "workspace:*",
+ "@astrojs/sitemap": "workspace:*"
+ }
+}
diff --git a/packages/integrations/sitemap/test/fixtures/dynamic/src/pages/[...slug].astro b/packages/integrations/sitemap/test/fixtures/dynamic/src/pages/[...slug].astro
new file mode 100644
index 000000000..9622cb374
--- /dev/null
+++ b/packages/integrations/sitemap/test/fixtures/dynamic/src/pages/[...slug].astro
@@ -0,0 +1,21 @@
+---
+export async function getStaticPaths() {
+ return [
+ {
+ params: {
+ slug: undefined,
+ }
+ },
+ {
+ params: {
+ slug: '/blog'
+ }
+ },
+ {
+ params: {
+ slug: '/test'
+ }
+ }
+ ];
+}
+---
diff --git a/packages/integrations/sitemap/test/fixtures/ssr/astro.config.mjs b/packages/integrations/sitemap/test/fixtures/ssr/astro.config.mjs
new file mode 100644
index 000000000..ce84944bf
--- /dev/null
+++ b/packages/integrations/sitemap/test/fixtures/ssr/astro.config.mjs
@@ -0,0 +1,12 @@
+import nodeServer from '@astrojs/node'
+import sitemap from '@astrojs/sitemap';
+import { defineConfig } from 'astro/config';
+
+export default defineConfig({
+ integrations: [sitemap()],
+ site: 'http://example.com',
+ output: 'server',
+ adapter: nodeServer({
+ mode: "standalone"
+ })
+})
diff --git a/packages/integrations/sitemap/test/fixtures/ssr/package.json b/packages/integrations/sitemap/test/fixtures/ssr/package.json
new file mode 100644
index 000000000..4b5e6848d
--- /dev/null
+++ b/packages/integrations/sitemap/test/fixtures/ssr/package.json
@@ -0,0 +1,10 @@
+{
+ "name": "@test/sitemap-ssr",
+ "version": "0.0.0",
+ "private": true,
+ "dependencies": {
+ "astro": "workspace:*",
+ "@astrojs/node": "workspace:*",
+ "@astrojs/sitemap": "workspace:*"
+ }
+}
diff --git a/packages/integrations/sitemap/test/fixtures/ssr/src/pages/one.astro b/packages/integrations/sitemap/test/fixtures/ssr/src/pages/one.astro
new file mode 100644
index 000000000..0c7fb90a7
--- /dev/null
+++ b/packages/integrations/sitemap/test/fixtures/ssr/src/pages/one.astro
@@ -0,0 +1,8 @@
+<html>
+ <head>
+ <title>One</title>
+ </head>
+ <body>
+ <h1>One</h1>
+ </body>
+</html>
diff --git a/packages/integrations/sitemap/test/fixtures/ssr/src/pages/two.astro b/packages/integrations/sitemap/test/fixtures/ssr/src/pages/two.astro
new file mode 100644
index 000000000..e7ba9910e
--- /dev/null
+++ b/packages/integrations/sitemap/test/fixtures/ssr/src/pages/two.astro
@@ -0,0 +1,8 @@
+<html>
+ <head>
+ <title>Two</title>
+ </head>
+ <body>
+ <h1>Two</h1>
+ </body>
+</html>
diff --git a/packages/integrations/sitemap/test/fixtures/static/astro.config.mjs b/packages/integrations/sitemap/test/fixtures/static/astro.config.mjs
new file mode 100644
index 000000000..ae53a9342
--- /dev/null
+++ b/packages/integrations/sitemap/test/fixtures/static/astro.config.mjs
@@ -0,0 +1,18 @@
+import sitemap from '@astrojs/sitemap';
+import { defineConfig } from 'astro/config';
+
+export default defineConfig({
+ integrations: [sitemap({
+ i18n: {
+ defaultLocale: 'it',
+ locales: {
+ it: 'it-IT',
+ de: 'de-DE',
+ }
+ }
+ })],
+ site: 'http://example.com',
+ redirects: {
+ '/redirect': '/'
+ },
+})
diff --git a/packages/integrations/sitemap/test/fixtures/static/deps.mjs b/packages/integrations/sitemap/test/fixtures/static/deps.mjs
new file mode 100644
index 000000000..b24f26189
--- /dev/null
+++ b/packages/integrations/sitemap/test/fixtures/static/deps.mjs
@@ -0,0 +1 @@
+export { default as sitemap } from '@astrojs/sitemap';
diff --git a/packages/integrations/sitemap/test/fixtures/static/package.json b/packages/integrations/sitemap/test/fixtures/static/package.json
new file mode 100644
index 000000000..ed5f3670b
--- /dev/null
+++ b/packages/integrations/sitemap/test/fixtures/static/package.json
@@ -0,0 +1,9 @@
+{
+ "name": "@test/sitemap-static",
+ "version": "0.0.0",
+ "private": true,
+ "dependencies": {
+ "astro": "workspace:*",
+ "@astrojs/sitemap": "workspace:*"
+ }
+}
diff --git a/packages/integrations/sitemap/test/fixtures/static/src/pages/123.astro b/packages/integrations/sitemap/test/fixtures/static/src/pages/123.astro
new file mode 100644
index 000000000..115292de9
--- /dev/null
+++ b/packages/integrations/sitemap/test/fixtures/static/src/pages/123.astro
@@ -0,0 +1,8 @@
+<html>
+ <head>
+ <title>123</title>
+ </head>
+ <body>
+ <h1>123</h1>
+ </body>
+</html>
diff --git a/packages/integrations/sitemap/test/fixtures/static/src/pages/404.astro b/packages/integrations/sitemap/test/fixtures/static/src/pages/404.astro
new file mode 100644
index 000000000..9e307c5c2
--- /dev/null
+++ b/packages/integrations/sitemap/test/fixtures/static/src/pages/404.astro
@@ -0,0 +1,8 @@
+<html>
+ <head>
+ <title>404</title>
+ </head>
+ <body>
+ <h1>404</h1>
+ </body>
+</html>
diff --git a/packages/integrations/sitemap/test/fixtures/static/src/pages/[lang]/manifest.ts b/packages/integrations/sitemap/test/fixtures/static/src/pages/[lang]/manifest.ts
new file mode 100644
index 000000000..907b94a21
--- /dev/null
+++ b/packages/integrations/sitemap/test/fixtures/static/src/pages/[lang]/manifest.ts
@@ -0,0 +1,15 @@
+export const GET: APIRoute = async ({ params }) => {
+ const { lang } = params;
+
+ return new Response(`I'm a route in the "${lang}" language.`);
+};
+
+export async function getStaticPaths() {
+ return ['it', 'en'].map((language) => {
+ return {
+ params: {
+ lang: language,
+ },
+ };
+ });
+}
diff --git a/packages/integrations/sitemap/test/fixtures/static/src/pages/[slug].astro b/packages/integrations/sitemap/test/fixtures/static/src/pages/[slug].astro
new file mode 100644
index 000000000..205633c76
--- /dev/null
+++ b/packages/integrations/sitemap/test/fixtures/static/src/pages/[slug].astro
@@ -0,0 +1,17 @@
+---
+export function getStaticPaths() {
+ return [
+ { params: { slug: 'one' }, props: { title: 'One' } },
+ { params: { slug: 'two' }, props: { title: 'Two' } },
+ ]
+}
+---
+
+<html>
+ <head>
+ <title>{Astro.props.title}</title>
+ </head>
+ <body>
+ <h1>{Astro.props.title}</h1>
+ </body>
+</html>
diff --git a/packages/integrations/sitemap/test/fixtures/static/src/pages/de/404.astro b/packages/integrations/sitemap/test/fixtures/static/src/pages/de/404.astro
new file mode 100644
index 000000000..9e307c5c2
--- /dev/null
+++ b/packages/integrations/sitemap/test/fixtures/static/src/pages/de/404.astro
@@ -0,0 +1,8 @@
+<html>
+ <head>
+ <title>404</title>
+ </head>
+ <body>
+ <h1>404</h1>
+ </body>
+</html>
diff --git a/packages/integrations/sitemap/test/fixtures/static/src/pages/endpoint.json.ts b/packages/integrations/sitemap/test/fixtures/static/src/pages/endpoint.json.ts
new file mode 100644
index 000000000..2e088376f
--- /dev/null
+++ b/packages/integrations/sitemap/test/fixtures/static/src/pages/endpoint.json.ts
@@ -0,0 +1,6 @@
+export async function GET({}) {
+ return Response.json({
+ name: 'Astro',
+ url: 'https://astro.build/',
+ });
+}
diff --git a/packages/integrations/sitemap/test/fixtures/static/src/pages/products-by-id/[id].astro b/packages/integrations/sitemap/test/fixtures/static/src/pages/products-by-id/[id].astro
new file mode 100644
index 000000000..b10438a68
--- /dev/null
+++ b/packages/integrations/sitemap/test/fixtures/static/src/pages/products-by-id/[id].astro
@@ -0,0 +1,11 @@
+---
+export async function getStaticPaths() {
+ return [
+ { params: { id: '404' } },
+ { params: { id: '405' } },
+ ]
+}
+
+const { id } = Astro.params
+---
+<!DOCTYPE html><html> <head><title>Product #{id}</title></head> <body> <h1>Product #404</h1> <p>This is a product that just happens to have an ID of {id}. It is found!</p></body></html> \ No newline at end of file
diff --git a/packages/integrations/sitemap/test/fixtures/trailing-slash/astro.config.mjs b/packages/integrations/sitemap/test/fixtures/trailing-slash/astro.config.mjs
new file mode 100644
index 000000000..82d25b854
--- /dev/null
+++ b/packages/integrations/sitemap/test/fixtures/trailing-slash/astro.config.mjs
@@ -0,0 +1,7 @@
+import sitemap from '@astrojs/sitemap';
+import { defineConfig } from 'astro/config';
+
+export default defineConfig({
+ integrations: [sitemap()],
+ site: 'http://example.com'
+})
diff --git a/packages/integrations/sitemap/test/fixtures/trailing-slash/package.json b/packages/integrations/sitemap/test/fixtures/trailing-slash/package.json
new file mode 100644
index 000000000..980e02e73
--- /dev/null
+++ b/packages/integrations/sitemap/test/fixtures/trailing-slash/package.json
@@ -0,0 +1,9 @@
+{
+ "name": "@test/sitemap-trailing-slash",
+ "version": "0.0.0",
+ "private": true,
+ "dependencies": {
+ "astro": "workspace:*",
+ "@astrojs/sitemap": "workspace:*"
+ }
+}
diff --git a/packages/integrations/sitemap/test/fixtures/trailing-slash/src/pages/index.astro b/packages/integrations/sitemap/test/fixtures/trailing-slash/src/pages/index.astro
new file mode 100644
index 000000000..5a29cbdbe
--- /dev/null
+++ b/packages/integrations/sitemap/test/fixtures/trailing-slash/src/pages/index.astro
@@ -0,0 +1,8 @@
+<html>
+ <head>
+ <title>Index</title>
+ </head>
+ <body>
+ <h1>Index</h1>
+ </body>
+</html> \ No newline at end of file
diff --git a/packages/integrations/sitemap/test/fixtures/trailing-slash/src/pages/one.astro b/packages/integrations/sitemap/test/fixtures/trailing-slash/src/pages/one.astro
new file mode 100644
index 000000000..0c7fb90a7
--- /dev/null
+++ b/packages/integrations/sitemap/test/fixtures/trailing-slash/src/pages/one.astro
@@ -0,0 +1,8 @@
+<html>
+ <head>
+ <title>One</title>
+ </head>
+ <body>
+ <h1>One</h1>
+ </body>
+</html>
diff --git a/packages/integrations/sitemap/test/fixtures/trailing-slash/src/pages/two.astro b/packages/integrations/sitemap/test/fixtures/trailing-slash/src/pages/two.astro
new file mode 100644
index 000000000..e7ba9910e
--- /dev/null
+++ b/packages/integrations/sitemap/test/fixtures/trailing-slash/src/pages/two.astro
@@ -0,0 +1,8 @@
+<html>
+ <head>
+ <title>Two</title>
+ </head>
+ <body>
+ <h1>Two</h1>
+ </body>
+</html>
diff --git a/packages/integrations/sitemap/test/routes.test.js b/packages/integrations/sitemap/test/routes.test.js
new file mode 100644
index 000000000..00d6ccde3
--- /dev/null
+++ b/packages/integrations/sitemap/test/routes.test.js
@@ -0,0 +1,27 @@
+import assert from 'node:assert/strict';
+import { before, describe, it } from 'node:test';
+import { loadFixture, readXML } from './test-utils.js';
+
+describe('routes', () => {
+ /** @type {import('./test-utils.js').Fixture} */
+ let fixture;
+ /** @type {string[]} */
+ let urls;
+
+ before(async () => {
+ fixture = await loadFixture({
+ root: './fixtures/static/',
+ });
+ await fixture.build();
+ const data = await readXML(fixture.readFile('/sitemap-0.xml'));
+ urls = data.urlset.url.map((url) => url.loc[0]);
+ });
+
+ it('does not include endpoints', async () => {
+ assert.equal(urls.includes('http://example.com/endpoint.json'), false);
+ });
+
+ it('does not include redirects', async () => {
+ assert.equal(urls.includes('http://example.com/redirect'), false);
+ });
+});
diff --git a/packages/integrations/sitemap/test/smoke.test.js b/packages/integrations/sitemap/test/smoke.test.js
new file mode 100644
index 000000000..d24c191ec
--- /dev/null
+++ b/packages/integrations/sitemap/test/smoke.test.js
@@ -0,0 +1,3 @@
+import '../dist/index.js';
+
+// Just a smoke test, this would fail if there's a problem.
diff --git a/packages/integrations/sitemap/test/ssr.test.js b/packages/integrations/sitemap/test/ssr.test.js
new file mode 100644
index 000000000..b5c92698b
--- /dev/null
+++ b/packages/integrations/sitemap/test/ssr.test.js
@@ -0,0 +1,23 @@
+import assert from 'node:assert/strict';
+import { before, describe, it } from 'node:test';
+import { loadFixture, readXML } from './test-utils.js';
+
+describe('SSR support', () => {
+ /** @type {import('./test-utils.js').Fixture} */
+ let fixture;
+
+ before(async () => {
+ fixture = await loadFixture({
+ root: './fixtures/ssr/',
+ });
+ await fixture.build();
+ });
+
+ it('SSR pages require zero config', async () => {
+ const data = await readXML(fixture.readFile('/client/sitemap-0.xml'));
+ const urls = data.urlset.url;
+
+ assert.equal(urls[0].loc[0], 'http://example.com/one/');
+ assert.equal(urls[1].loc[0], 'http://example.com/two/');
+ });
+});
diff --git a/packages/integrations/sitemap/test/staticPaths.test.js b/packages/integrations/sitemap/test/staticPaths.test.js
new file mode 100644
index 000000000..7df9d5cb6
--- /dev/null
+++ b/packages/integrations/sitemap/test/staticPaths.test.js
@@ -0,0 +1,48 @@
+import assert from 'node:assert/strict';
+import { before, describe, it } from 'node:test';
+import { loadFixture, readXML } from './test-utils.js';
+
+describe('getStaticPaths support', () => {
+ /** @type {import('./test-utils.js').Fixture} */
+ let fixture;
+ /** @type {string[]} */
+ let urls;
+
+ before(async () => {
+ fixture = await loadFixture({
+ root: './fixtures/static/',
+ trailingSlash: 'always',
+ });
+ await fixture.build();
+
+ const data = await readXML(fixture.readFile('/sitemap-0.xml'));
+ urls = data.urlset.url.map((url) => url.loc[0]);
+ });
+
+ it('requires zero config for getStaticPaths', async () => {
+ assert.equal(urls.includes('http://example.com/one/'), true);
+ assert.equal(urls.includes('http://example.com/two/'), true);
+ });
+
+ it('does not include 404 pages', () => {
+ assert.equal(urls.includes('http://example.com/404/'), false);
+ });
+
+ it('does not include nested 404 pages', () => {
+ assert.equal(urls.includes('http://example.com/de/404/'), false);
+ });
+
+ it('includes numerical pages', () => {
+ assert.equal(urls.includes('http://example.com/123/'), true);
+ });
+
+ it('includes numerical 404 pages if not for i18n', () => {
+ assert.equal(urls.includes('http://example.com/products-by-id/405/'), true);
+ assert.equal(urls.includes('http://example.com/products-by-id/404/'), true);
+ });
+
+ it('should render the endpoint', async () => {
+ const page = await fixture.readFile('./it/manifest');
+ assert.match(page, /I'm a route in the "it" language./);
+ });
+});
diff --git a/packages/integrations/sitemap/test/test-utils.js b/packages/integrations/sitemap/test/test-utils.js
new file mode 100644
index 000000000..74bba6a44
--- /dev/null
+++ b/packages/integrations/sitemap/test/test-utils.js
@@ -0,0 +1,29 @@
+import * as xml2js from 'xml2js';
+import { loadFixture as baseLoadFixture } from '../../../astro/test/test-utils.js';
+
+/**
+ * @typedef {import('../../../astro/test/test-utils').Fixture} Fixture
+ */
+
+export function loadFixture(inlineConfig) {
+ if (!inlineConfig?.root) throw new Error("Must provide { root: './fixtures/...' }");
+
+ // resolve the relative root (i.e. "./fixtures/tailwindcss") to a full filepath
+ // without this, the main `loadFixture` helper will resolve relative to `packages/astro/test`
+ return baseLoadFixture({
+ ...inlineConfig,
+ root: new URL(inlineConfig.root, import.meta.url).toString(),
+ });
+}
+
+export function readXML(fileOrPromise) {
+ const parseString = xml2js.parseString;
+ return Promise.resolve(fileOrPromise).then((xml) => {
+ return new Promise((resolve, reject) => {
+ parseString(xml, function (err, result) {
+ if (err) return reject(err);
+ resolve(result);
+ });
+ });
+ });
+}
diff --git a/packages/integrations/sitemap/test/trailing-slash.test.js b/packages/integrations/sitemap/test/trailing-slash.test.js
new file mode 100644
index 000000000..181f0def5
--- /dev/null
+++ b/packages/integrations/sitemap/test/trailing-slash.test.js
@@ -0,0 +1,127 @@
+import assert from 'node:assert/strict';
+import { before, describe, it } from 'node:test';
+import { loadFixture, readXML } from './test-utils.js';
+
+describe('Trailing slash', () => {
+ /** @type {import('./test-utils').Fixture} */
+ let fixture;
+
+ describe('trailingSlash: ignore', () => {
+ describe('build.format: directory', () => {
+ before(async () => {
+ fixture = await loadFixture({
+ root: './fixtures/trailing-slash/',
+ trailingSlash: 'ignore',
+ build: {
+ format: 'directory',
+ },
+ });
+ await fixture.build();
+ });
+
+ it('URLs end with trailing slash', async () => {
+ const data = await readXML(fixture.readFile('/sitemap-0.xml'));
+ const urls = data.urlset.url;
+
+ assert.equal(urls[0].loc[0], 'http://example.com/');
+ assert.equal(urls[1].loc[0], 'http://example.com/one/');
+ assert.equal(urls[2].loc[0], 'http://example.com/two/');
+ });
+ });
+
+ describe('build.format: file', () => {
+ before(async () => {
+ fixture = await loadFixture({
+ root: './fixtures/trailing-slash/',
+ trailingSlash: 'ignore',
+ build: {
+ format: 'file',
+ },
+ });
+ await fixture.build();
+ });
+
+ it('URLs do not end with trailing slash', async () => {
+ const data = await readXML(fixture.readFile('/sitemap-0.xml'));
+ const urls = data.urlset.url;
+
+ assert.equal(urls[0].loc[0], 'http://example.com');
+ assert.equal(urls[1].loc[0], 'http://example.com/one');
+ assert.equal(urls[2].loc[0], 'http://example.com/two');
+ });
+ });
+ });
+
+ describe('trailingSlash: never', () => {
+ before(async () => {
+ fixture = await loadFixture({
+ root: './fixtures/trailing-slash/',
+ trailingSlash: 'never',
+ });
+ await fixture.build();
+ });
+
+ it('URLs do not end with trailing slash', async () => {
+ const data = await readXML(fixture.readFile('/sitemap-0.xml'));
+ const urls = data.urlset.url;
+
+ assert.equal(urls[0].loc[0], 'http://example.com');
+ assert.equal(urls[1].loc[0], 'http://example.com/one');
+ assert.equal(urls[2].loc[0], 'http://example.com/two');
+ });
+ describe('with base path', () => {
+ before(async () => {
+ fixture = await loadFixture({
+ root: './fixtures/trailing-slash/',
+ trailingSlash: 'never',
+ base: '/base',
+ });
+ await fixture.build();
+ });
+
+ it('URLs do not end with trailing slash', async () => {
+ const data = await readXML(fixture.readFile('/sitemap-0.xml'));
+ const urls = data.urlset.url;
+ assert.equal(urls[0].loc[0], 'http://example.com/base');
+ assert.equal(urls[1].loc[0], 'http://example.com/base/one');
+ assert.equal(urls[2].loc[0], 'http://example.com/base/two');
+ });
+ });
+ });
+
+ describe('trailingSlash: always', () => {
+ before(async () => {
+ fixture = await loadFixture({
+ root: './fixtures/trailing-slash/',
+ trailingSlash: 'always',
+ });
+ await fixture.build();
+ });
+
+ it('URLs end with trailing slash', async () => {
+ const data = await readXML(fixture.readFile('/sitemap-0.xml'));
+ const urls = data.urlset.url;
+ assert.equal(urls[0].loc[0], 'http://example.com/');
+ assert.equal(urls[1].loc[0], 'http://example.com/one/');
+ assert.equal(urls[2].loc[0], 'http://example.com/two/');
+ });
+ describe('with base path', () => {
+ before(async () => {
+ fixture = await loadFixture({
+ root: './fixtures/trailing-slash/',
+ trailingSlash: 'always',
+ base: '/base',
+ });
+ await fixture.build();
+ });
+
+ it('URLs end with trailing slash', async () => {
+ const data = await readXML(fixture.readFile('/sitemap-0.xml'));
+ const urls = data.urlset.url;
+ assert.equal(urls[0].loc[0], 'http://example.com/base/');
+ assert.equal(urls[1].loc[0], 'http://example.com/base/one/');
+ assert.equal(urls[2].loc[0], 'http://example.com/base/two/');
+ });
+ });
+ });
+});
diff --git a/packages/integrations/sitemap/test/units/generate-sitemap.test.js b/packages/integrations/sitemap/test/units/generate-sitemap.test.js
new file mode 100644
index 000000000..fbf4e7858
--- /dev/null
+++ b/packages/integrations/sitemap/test/units/generate-sitemap.test.js
@@ -0,0 +1,147 @@
+import assert from 'node:assert/strict';
+import { describe, it } from 'node:test';
+import { generateSitemap } from '../../dist/generate-sitemap.js';
+
+const site = 'http://example.com';
+
+describe('generateSitemap', () => {
+ describe('basic', () => {
+ it('works', () => {
+ const items = generateSitemap(
+ [
+ // All pages
+ `${site}/a`,
+ `${site}/b`,
+ `${site}/c`,
+ ],
+ site,
+ );
+ assert.equal(items.length, 3);
+ assert.equal(items[0].url, `${site}/a`);
+ assert.equal(items[1].url, `${site}/b`);
+ assert.equal(items[2].url, `${site}/c`);
+ });
+
+ it('sorts the items', () => {
+ const items = generateSitemap(
+ [
+ // All pages
+ `${site}/c`,
+ `${site}/a`,
+ `${site}/b`,
+ ],
+ site,
+ );
+ assert.equal(items.length, 3);
+ assert.equal(items[0].url, `${site}/a`);
+ assert.equal(items[1].url, `${site}/b`);
+ assert.equal(items[2].url, `${site}/c`);
+ });
+
+ it('sitemap props are passed to items', () => {
+ const now = new Date();
+ const items = generateSitemap(
+ [
+ // All pages
+ `${site}/a`,
+ `${site}/b`,
+ `${site}/c`,
+ ],
+ site,
+ {
+ changefreq: 'monthly',
+ lastmod: now,
+ priority: 0.5,
+ },
+ );
+
+ assert.equal(items.length, 3);
+
+ assert.equal(items[0].url, `${site}/a`);
+ assert.equal(items[0].changefreq, 'monthly');
+ assert.equal(items[0].lastmod, now.toISOString());
+ assert.equal(items[0].priority, 0.5);
+
+ assert.equal(items[1].url, `${site}/b`);
+ assert.equal(items[1].changefreq, 'monthly');
+ assert.equal(items[1].lastmod, now.toISOString());
+ assert.equal(items[1].priority, 0.5);
+
+ assert.equal(items[2].url, `${site}/c`);
+ assert.equal(items[2].changefreq, 'monthly');
+ assert.equal(items[2].lastmod, now.toISOString());
+ assert.equal(items[2].priority, 0.5);
+ });
+ });
+
+ describe('i18n', () => {
+ it('works', () => {
+ const items = generateSitemap(
+ [
+ // All pages
+ `${site}/a`,
+ `${site}/b`,
+ `${site}/c`,
+ `${site}/es/a`,
+ `${site}/es/b`,
+ `${site}/es/c`,
+ `${site}/fr/a`,
+ `${site}/fr/b`,
+ // `${site}/fr-CA/c`, (intentionally missing for testing)
+ ],
+ site,
+ {
+ i18n: {
+ defaultLocale: 'en',
+ locales: {
+ en: 'en-US',
+ es: 'es-ES',
+ fr: 'fr-CA',
+ },
+ },
+ },
+ );
+
+ assert.equal(items.length, 8);
+
+ const aLinks = [
+ { url: `${site}/a`, lang: 'en-US' },
+ { url: `${site}/es/a`, lang: 'es-ES' },
+ { url: `${site}/fr/a`, lang: 'fr-CA' },
+ ];
+ const bLinks = [
+ { url: `${site}/b`, lang: 'en-US' },
+ { url: `${site}/es/b`, lang: 'es-ES' },
+ { url: `${site}/fr/b`, lang: 'fr-CA' },
+ ];
+ const cLinks = [
+ { url: `${site}/c`, lang: 'en-US' },
+ { url: `${site}/es/c`, lang: 'es-ES' },
+ ];
+
+ assert.equal(items[0].url, `${site}/a`);
+ assert.deepEqual(items[0].links, aLinks);
+
+ assert.equal(items[1].url, `${site}/b`);
+ assert.deepEqual(items[1].links, bLinks);
+
+ assert.equal(items[2].url, `${site}/c`);
+ assert.deepEqual(items[2].links, cLinks);
+
+ assert.equal(items[3].url, `${site}/es/a`);
+ assert.deepEqual(items[3].links, aLinks);
+
+ assert.equal(items[4].url, `${site}/es/b`);
+ assert.deepEqual(items[4].links, bLinks);
+
+ assert.equal(items[5].url, `${site}/es/c`);
+ assert.deepEqual(items[5].links, cLinks);
+
+ assert.equal(items[6].url, `${site}/fr/a`);
+ assert.deepEqual(items[6].links, aLinks);
+
+ assert.equal(items[7].url, `${site}/fr/b`);
+ assert.deepEqual(items[7].links, bLinks);
+ });
+ });
+});