Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use sitemap index path for all sitemaps #1955

Draft
wants to merge 4 commits into
base: feat/separate-workspace
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/tricky-windows-destroy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@faustwp/core': patch
---

Fix incorrect sitemap URLs when using sitemapIndexPath
19 changes: 9 additions & 10 deletions packages/faustwp-core/src/server/sitemaps/createSitemaps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,7 @@ export async function createRootSitemapIndex(
throw new Error('Request object must have URL');
}

// get sitemapIndexPath config param
// fetch sitemap from WP
// Get the trimmed sitemap index path
const trimmedWpUrl = trim(getWpUrl(), '/');
const trimmedFrontendUrl = trim(frontendUrl, '/');
const trimmedSitemapIndexPath = trim(
Expand All @@ -75,10 +74,10 @@ export async function createRootSitemapIndex(
let sitemaps: SitemapSchemaSitemapElement[] = [];

if (!isUndefined(pages) && isArray(pages) && pages.length) {
const trimmedFaustPagesPart = `${trim(
SITEMAP_INDEX_PATH,
const trimmedFaustPagesPart = `${trimmedSitemapIndexPath}?sitemap=${trim(
FAUST_PAGES_PATHNAME,
'/',
)}?sitemap=${trim(FAUST_PAGES_PATHNAME, '/')}`;
)}`;
const sitemapFaustPagesUrl = `${trimmedFrontendUrl}/${trimmedFaustPagesPart}`;

sitemaps = [
Expand Down Expand Up @@ -170,11 +169,11 @@ export async function createRootSitemapIndex(
*
* @example
* Replaces http://headless.local/wp-sitemap-posts-page-1.xml with
* http://localhost:3000/wp-sitemap-posts-page-1.xml
* http://localhost:3000/sitemap_index.xml?sitemap=wp-sitemap-posts-page-1.xml
*/
wpSitemaps.forEach((sitemap) => {
const url = new URL(sitemap.loc);
const sitemapUrl = `${trim(frontendUrl, '/')}/sitemap.xml?sitemap=${trim(
const sitemapUrl = `${trimmedFrontendUrl}/${trimmedSitemapIndexPath}?sitemap=${trim(
url.pathname,
'/',
)}`;
Expand Down Expand Up @@ -293,11 +292,11 @@ export async function handleSitemapPath(
let urls: SitemapSchemaUrlElement[] = [];

/**
* Replace the existing WordPress URL in each "loc" with the headless URL
* Replace the existing WordPress URL in each "loc" with the frontend URL
*
* @example
* Replaces http://headless.local/wp-sitemap-posts-page-1.xml with
* http://localhost:3000/wp-sitemap-posts-page-1.xml
* Replaces http://headless.local/page-1/ with
* http://localhost:3000/page-1/
*/
wpSitemapUrls.forEach((url) => {
urls = [
Expand Down
185 changes: 146 additions & 39 deletions packages/faustwp-core/tests/server/sitemaps/createSitemaps.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,17 @@ describe('createRootSitemapIndex', () => {
process.env = envBackup;
});

const invalidSitemapIndexXML = `<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet type="text/xsl" href="//headless.local/wp-content/plugins/wordpress-seo/css/main-sitemap.xsl"?>
const invalidSitemapIndexXML = `<?xml version="1.0" encoding="UTF-8"?>
<!-- XML Sitemap generated by Yoast SEO -->`;

const validSitemapIndex1RecordXML = `<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet type="text/xsl" href="//headless.local/wp-content/plugins/wordpress-seo/css/main-sitemap.xsl"?>
const validSitemapIndex1RecordXML = `<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<sitemap>
<loc>http://headless.local/post-sitemap.xml</loc>
</sitemap>
</sitemapindex>
<!-- XML Sitemap generated by Yoast SEO -->`;
</sitemapindex>`;

const validSitemapIndexXML = `<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet type="text/xsl" href="//headless.local/wp-content/plugins/wordpress-seo/css/main-sitemap.xsl"?>
const validSitemapIndexXML = `<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<sitemap>
<loc>http://headless.local/post-sitemap.xml</loc>
Expand All @@ -41,8 +40,7 @@ describe('createRootSitemapIndex', () => {
<loc>http://headless.local/author-sitemap.xml</loc>
<lastmod>2021-12-17T16:56:55+00:00</lastmod>
</sitemap>
</sitemapindex>
<!-- XML Sitemap generated by Yoast SEO -->`;
</sitemapindex>`;

it('returns undefined when the response to the WP sitemap is not ok', async () => {
jest.spyOn(global, 'fetch').mockImplementationOnce(() => {
Expand Down Expand Up @@ -86,10 +84,7 @@ describe('createRootSitemapIndex', () => {
});

it('ignores sitemaps that match the sitemapPathsToIgnore property', async () => {
const createSitemapIndexSpy = jest.spyOn(
sitemapUtils,
'createSitemapIndex',
);
const createSitemapIndexSpy = jest.spyOn(sitemapUtils, 'createSitemapIndex');
jest.spyOn(global, 'fetch').mockImplementationOnce(() => {
return Promise.resolve({
ok: true,
Expand Down Expand Up @@ -123,10 +118,7 @@ describe('createRootSitemapIndex', () => {
});

it('ignores sitemaps that match a sitemapPathsToIgnore wildcard', async () => {
const createSitemapIndexSpy = jest.spyOn(
sitemapUtils,
'createSitemapIndex',
);
const createSitemapIndexSpy = jest.spyOn(sitemapUtils, 'createSitemapIndex');
jest.spyOn(global, 'fetch').mockImplementationOnce(() => {
return Promise.resolve({
ok: true,
Expand Down Expand Up @@ -160,10 +152,7 @@ describe('createRootSitemapIndex', () => {
});

it('returns the proper sitemap urls with replacing URLs', async () => {
const createSitemapIndexSpy = jest.spyOn(
sitemapUtils,
'createSitemapIndex',
);
const createSitemapIndexSpy = jest.spyOn(sitemapUtils, 'createSitemapIndex');
jest.spyOn(global, 'fetch').mockImplementationOnce(() => {
return Promise.resolve({
ok: true,
Expand Down Expand Up @@ -211,10 +200,7 @@ describe('createRootSitemapIndex', () => {
* configured properly. Ensure it returns an array of URLs
*/
it('returns the proper sitemap urls with only 1 record', async () => {
const createSitemapIndexSpy = jest.spyOn(
sitemapUtils,
'createSitemapIndex',
);
const createSitemapIndexSpy = jest.spyOn(sitemapUtils, 'createSitemapIndex');
jest.spyOn(global, 'fetch').mockImplementationOnce(() => {
return Promise.resolve({
ok: true,
Expand Down Expand Up @@ -242,6 +228,130 @@ describe('createRootSitemapIndex', () => {

expect(createSitemapIndexSpy).toHaveBeenCalledWith(expectedSitemaps);
});

it('uses the default sitemapIndexPath when none is provided', async () => {
const createSitemapIndexSpy = jest.spyOn(sitemapUtils, 'createSitemapIndex');
jest.spyOn(global, 'fetch').mockImplementationOnce((url) => {
expect(url).toBe('http://headless.local/sitemap.xml');
return Promise.resolve({
ok: true,
status: 200,
text: () => Promise.resolve(validSitemapIndexXML),
}) as Promise<Response>;
});

const req = {
url: 'http://localhost:3000/sitemap.xml',
} as NextRequest;

const config: GetSitemapPropsConfig = {
frontendUrl: 'http://localhost:3000',
};

await createSitemaps.createRootSitemapIndex(req, config);

const expectedSitemaps = [
{
loc: 'http://localhost:3000/sitemap.xml?sitemap=post-sitemap.xml',
},
{
loc: 'http://localhost:3000/sitemap.xml?sitemap=page-sitemap.xml',
},
{
loc: 'http://localhost:3000/sitemap.xml?sitemap=category-sitemap.xml',
},
{
loc: 'http://localhost:3000/sitemap.xml?sitemap=author-sitemap.xml',
lastmod: '2021-12-17T16:56:55+00:00',
},
];

expect(createSitemapIndexSpy).toHaveBeenCalledWith(expectedSitemaps);
});

it('uses the custom sitemapIndexPath when provided', async () => {
jest.spyOn(global, 'fetch').mockImplementationOnce((url) => {
expect(url).toBe('http://headless.local/custom-sitemap-index.xml');
return Promise.resolve({
ok: true,
status: 200,
text: () => Promise.resolve(validSitemapIndexXML),
}) as Promise<Response>;
});

const req = {
url: 'http://localhost:3000/custom-sitemap-index.xml',
} as NextRequest;

const config: GetSitemapPropsConfig = {
frontendUrl: 'http://localhost:3000',
sitemapIndexPath: '/custom-sitemap-index.xml',
};

const createSitemapIndexSpy = jest.spyOn(sitemapUtils, 'createSitemapIndex');

await createSitemaps.createRootSitemapIndex(req, config);

const expectedSitemaps = [
{
loc: 'http://localhost:3000/custom-sitemap-index.xml?sitemap=post-sitemap.xml',
},
{
loc: 'http://localhost:3000/custom-sitemap-index.xml?sitemap=page-sitemap.xml',
},
{
loc: 'http://localhost:3000/custom-sitemap-index.xml?sitemap=category-sitemap.xml',
},
{
loc: 'http://localhost:3000/custom-sitemap-index.xml?sitemap=author-sitemap.xml',
lastmod: '2021-12-17T16:56:55+00:00',
},
];

expect(createSitemapIndexSpy).toHaveBeenCalledWith(expectedSitemaps);
});

it('constructs correct child sitemap URLs with custom sitemapIndexPath', async () => {
jest.spyOn(global, 'fetch').mockImplementationOnce((url) => {
expect(url).toBe('http://headless.local/sitemap_index.xml');
return Promise.resolve({
ok: true,
status: 200,
text: () => Promise.resolve(validSitemapIndexXML),
}) as Promise<Response>;
});

const req = {
url: 'http://localhost:3000/sitemap_index.xml',
} as NextRequest;

const config: GetSitemapPropsConfig = {
frontendUrl: 'http://localhost:3000',
sitemapIndexPath: '/sitemap_index.xml',
};

const createSitemapIndexSpy = jest.spyOn(sitemapUtils, 'createSitemapIndex');

await createSitemaps.createRootSitemapIndex(req, config);

const expectedSitemaps = [
{
loc: 'http://localhost:3000/sitemap_index.xml?sitemap=post-sitemap.xml',
},
{
loc: 'http://localhost:3000/sitemap_index.xml?sitemap=page-sitemap.xml',
},
{
loc: 'http://localhost:3000/sitemap_index.xml?sitemap=category-sitemap.xml',
},
{
loc: 'http://localhost:3000/sitemap_index.xml?sitemap=author-sitemap.xml',
lastmod: '2021-12-17T16:56:55+00:00',
},
];

expect(createSitemapIndexSpy).toHaveBeenCalledWith(expectedSitemaps);
});
});

describe('createPagesSitemap()', () => {
Expand All @@ -259,7 +369,7 @@ describe('createPagesSitemap()', () => {
frontendUrl: 'http://localhost:3000',
};

let req = {
const req = {
url: 'http://localhost:3000/sitemap-faust-pages.xml',
} as NextRequest;

Expand Down Expand Up @@ -310,7 +420,7 @@ describe('createPagesSitemap()', () => {

const createSitemapSpy = jest.spyOn(sitemapUtils, 'createSitemap');

let req = {
const req = {
url: 'http://localhost:3000/sitemap-faust-pages.xml',
} as NextRequest;

Expand All @@ -330,8 +440,8 @@ describe('handleSitemapPath()', () => {
afterAll(() => {
process.env = envBackup;
});
const validSitemapXML = `<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet type="text/xsl" href="//headless.local/wp-content/plugins/wordpress-seo/css/main-sitemap.xsl"?>
<urlset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd http://www.google.com/schemas/sitemap-image/1.1 http://www.google.com/schemas/sitemap-image/1.1/sitemap-image.xsd" xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
const validSitemapXML = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>http://headless.local/</loc>
</url>
Expand All @@ -347,19 +457,16 @@ describe('handleSitemapPath()', () => {
<changefreq>weekly</changefreq>
<priority>0.5</priority>
</url>
</urlset>`;

</urlset>
<!-- XML Sitemap generated by Yoast SEO -->`;

const validSitemap1RecordXML = `<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet type="text/xsl" href="//headless.local/wp-content/plugins/wordpress-seo/css/main-sitemap.xsl"?>
<urlset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd http://www.google.com/schemas/sitemap-image/1.1 http://www.google.com/schemas/sitemap-image/1.1/sitemap-image.xsd" xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
const validSitemap1RecordXML = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>http://headless.local/about</loc>
</url>
</urlset>
<!-- XML Sitemap generated by Yoast SEO -->`;
</urlset>`;

const invalidSitemapXML = `<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet type="text/xsl" href="//headless.local/wp-content/plugins/wordpress-seo/css/main-sitemap.xsl"?>
const invalidSitemapXML = `<?xml version="1.0" encoding="UTF-8"?>
<!-- XML Sitemap generated by Yoast SEO -->`;

it('returns undefined when the response to the WP sitemap is not ok', async () => {
Expand All @@ -371,7 +478,7 @@ describe('handleSitemapPath()', () => {
});

const req = {
url: 'http://localhost:3000/sitemap-posts.xml',
url: 'http://localhost:3000/sitemap.xml?sitemap=post-sitemap.xml',
} as NextRequest;

const config: GetSitemapPropsConfig = {
Expand All @@ -392,7 +499,7 @@ describe('handleSitemapPath()', () => {
});

const req = {
url: 'http://localhost:3000/sitemap-posts.xml',
url: 'http://localhost:3000/sitemap.xml?sitemap=post-sitemap.xml',
} as NextRequest;

const config: GetSitemapPropsConfig = {
Expand All @@ -415,7 +522,7 @@ describe('handleSitemapPath()', () => {
});

const req = {
url: 'http://localhost:5000/sitemap-pages.xml',
url: 'http://localhost:5000/sitemap.xml?sitemap=page-sitemap.xml',
} as NextRequest;

const config: GetSitemapPropsConfig = {
Expand Down Expand Up @@ -461,7 +568,7 @@ describe('handleSitemapPath()', () => {
});

const req = {
url: 'http://localhost:3000/sitemap-pages.xml',
url: 'http://localhost:3000/sitemap.xml?sitemap=page-sitemap.xml',
} as NextRequest;

const config: GetSitemapPropsConfig = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,4 +105,35 @@ describe('validateConfig', () => {
'The pages path property must be a string',
);
});

it('throws an error if sitemapIndexPath is not a string', () => {
const config: any = {
frontendUrl: 'http://localhost:3000',
sitemapIndexPath: 123,
};

expect(() => getSitemapProps.validateConfig(config)).toThrow(
'sitemapIndexPath must be a string',
);
});

it('throws an error if sitemapIndexPath does not start with a forward slash', () => {
const config: any = {
frontendUrl: 'http://localhost:3000',
sitemapIndexPath: 'sitemap_index.xml',
};

expect(() => getSitemapProps.validateConfig(config)).toThrow(
'sitemapIndexPath must start with a forward slash',
);
});

it('passes validation when sitemapIndexPath is a valid string', () => {
const config: any = {
frontendUrl: 'http://localhost:3000',
sitemapIndexPath: '/sitemap_index.xml',
};

expect(() => getSitemapProps.validateConfig(config)).not.toThrow();
});
});
Loading