/** * To "debug" this test locally, you need to set at least one of these * environment variables: * * - CHANGED_FILES * - DELETED_FILES * * They both need to a whitespace-separated list of paths to content files. * For example: * * export CHANGED_FILES="content/get-started/index.md content/get-started/start-your-journey/hello-world.md" * * If any of the paths in there, split by ' ', don't match real files, the * test will fail before it even starts. Meaning, it will throw an error * rather than failing an `expect(...)` assertion. * * Technically, the value is any whitespace. So you can actually use: * * export DELETED_FILES=`git diff --name-only main...` * * which will make the environment variable be newline-separated and that * works too. * * So, for example, if you've made some deletions and some edits the * staged files: * * export DELETED_FILES=`git diff --name-only --diff-filter=D main...` * export CHANGED_FILES=`git diff --name-only --diff-filter=M main...` * npm run test -- src/content-render/tests/render-changed-and-deleted-files.ts */ import path from 'path' import { describe, expect, test, vi } from 'vitest' import { head, get } from '@/tests/helpers/e2etest' import { loadPages } from '@/frame/lib/page-data' const EMPTY = Symbol('EMPTY') const pageList = await loadPages(undefined, ['en']) function getChangedContentFiles() { return getContentFiles(process.env.CHANGED_FILES) } function getDeletedContentFiles() { return getContentFiles(process.env.DELETED_FILES) } function getContentFiles(spaceSeparatedList: string | undefined): string[] { return (spaceSeparatedList || '').split(/\s+/g).filter((filePath) => { // This filters out things like '', or `data/foo.md` or `content/something/README.md` return ( filePath.endsWith('.md') && filePath.split(path.sep)[0] === 'content' && path.basename(filePath) !== 'README.md' ) }) } // If the list of changed pages is very large, this test can take a long time. // It can also happen if some of the pages involves are infamously slow. // For example guide pages because they involved a lot of processing // to gather and preview linked data. vi.setConfig({ testTimeout: 60 * 1000 }) describe('changed-content', () => { const changedContentFiles = getChangedContentFiles() // `test.each` will throw if the array is empty, so we need to add a dummy // when there are no changed files in the environment. const testFiles: Array = changedContentFiles.length ? changedContentFiles : [EMPTY] test.each(testFiles)('changed-content: %s', async (file: string | symbol) => { // Necessary because `test.each` will throw if the array is empty if (file === EMPTY) return const page = pageList.find((p) => { return path.join(p.basePath, p.relativePath) === file }) if (!page) { throw new Error(`Could not find page for ${file as string} in all loaded English content`) } // Each version of the page should successfully render for (const { href } of page.permalinks) { const res = await get(href) if (!res.ok) { let msg = `This error happened when rendering from: ${file as string}\n` msg += 'To see the full error from vitest re-run the test with DEBUG_MIDDLEWARE_TESTS=true set\n' msg += `Or, to view it locally start the server (npm run dev) and visit http://localhost:4000${href}` console.log(msg) throw new Error(`Rendering ${href} failed with status ${res.statusCode}`) } } }) }) describe('deleted-content', () => { const deletedContentFiles = getDeletedContentFiles() // `test.each` will throw if the array is empty, so we need to add a dummy // when there are no deleted files in the environment. const testFiles: Array = deletedContentFiles.length ? deletedContentFiles : [EMPTY] test.each(testFiles)('deleted-content: %s', async (file: string | symbol) => { // Necessary because `test.each` will throw if the array is empty if (file === EMPTY) return const page = pageList.find((p) => { return path.join(p.basePath, p.relativePath) === file }) if (page) { throw new Error( `The supposedly deleted file ${file as string} is still in list of loaded pages`, ) } // You can't know what the possible permalinks were for a deleted page, // because it's deleted so we can't look at its `versions` front matter. // However, we always make sure all pages work in versionless. const indexmdSuffixRegex = new RegExp(`${path.sep}index\\.md$`) const mdSuffixRegex = /\.md$/ const relativePath = (file as string).split(path.sep).slice(1).join(path.sep) const href = `/en/${relativePath.replace(indexmdSuffixRegex, '').replace(mdSuffixRegex, '')}` const res = await head(href) const error = res.statusCode === 404 ? `The deleted file ${file as string} did not set up a redirect when deleted.` : '' // Certain articles that are deleted and moved under a directory with the same article name // should just route to the subcategory page instead of redirecting (docs content team confirmed). // So, in this scenario, we'd get a 200 status code. expect(res.statusCode === 301 || res.statusCode === 200, error).toBe(true) }) })