diff --git a/.eslintrc.js b/.eslintrc.js index d31aec2e0a19..04e9657ae9fb 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -202,7 +202,13 @@ module.exports = { 'import/no-unresolved': [ ERROR, { - ignore: ['^@theme', '^@docusaurus', '^@generated', '^@site'], + ignore: [ + '^@theme', + '^@docusaurus', + '^@generated', + '^@site', + '^@testing-utils', + ], }, ], 'import/order': OFF, diff --git a/jest.config.mjs b/jest.config.mjs index 6a012fdadb5c..7163875c4149 100644 --- a/jest.config.mjs +++ b/jest.config.mjs @@ -60,6 +60,8 @@ export default { // Using src instead of lib, so we always get fresh source '@docusaurus/plugin-content-docs/client': '@docusaurus/plugin-content-docs/src/client/index.ts', + + '@testing-utils/(.*)': '/jest/utils/$1.ts', }, snapshotSerializers: [ '/jest/snapshotPathNormalizer.ts', diff --git a/jest/utils/git.ts b/jest/utils/git.ts new file mode 100644 index 000000000000..38db021dccc9 --- /dev/null +++ b/jest/utils/git.ts @@ -0,0 +1,63 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import fs from 'fs-extra'; +import os from 'os'; +import path from 'path'; +import shell from 'shelljs'; + +class Git { + constructor(private dir: string) { + const res = shell.exec('git init', {cwd: dir, silent: true}); + if (res.code !== 0) { + throw new Error(`git init exited with code ${res.code}. +stderr: ${res.stderr} +stdout: ${res.stdout}`); + } + // Doesn't matter currently + shell.exec('git config user.email "test@jc-verse.com"', { + cwd: dir, + silent: true, + }); + shell.exec('git config user.name "Test"', {cwd: dir, silent: true}); + + shell.exec('git commit --allow-empty -m "First commit"', { + cwd: dir, + silent: true, + }); + } + commit(msg: string, date: string, author: string): void { + const addRes = shell.exec('git add .', {cwd: this.dir, silent: true}); + const commitRes = shell.exec( + `git commit -m "${msg}" --date "${date}T00:00:00Z" --author "${author}"`, + { + cwd: this.dir, + env: {GIT_COMMITTER_DATE: `${date}T00:00:00Z`}, + silent: true, + }, + ); + if (addRes.code !== 0) { + throw new Error(`git add exited with code ${addRes.code}. +stderr: ${addRes.stderr} +stdout: ${addRes.stdout}`); + } + if (commitRes.code !== 0) { + throw new Error(`git commit exited with code ${commitRes.code}. +stderr: ${commitRes.stderr} +stdout: ${commitRes.stdout}`); + } + } +} + +// This function is sync so the same mock repo can be shared across tests +export function createTempRepo(): {repoDir: string; git: Git} { + const repoDir = fs.mkdtempSync(path.join(os.tmpdir(), 'git-test-repo')); + + const git = new Git(repoDir); + + return {repoDir, git}; +} diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/lastUpdate.test.ts b/packages/docusaurus-plugin-content-docs/src/__tests__/lastUpdate.test.ts index 17cfaba48e9f..7f3e41bd195c 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/lastUpdate.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/lastUpdate.test.ts @@ -5,6 +5,7 @@ * LICENSE file in the root directory of this source tree. */ +import {createTempRepo} from '@testing-utils/git'; import {jest} from '@jest/globals'; import fs from 'fs-extra'; import path from 'path'; @@ -69,7 +70,8 @@ describe('getFileLastUpdate', () => { const consoleMock = jest .spyOn(console, 'warn') .mockImplementation(() => {}); - const tempFilePath = path.join(__dirname, '__fixtures__', '.temp'); + const {repoDir} = createTempRepo(); + const tempFilePath = path.join(repoDir, 'file.md'); await fs.writeFile(tempFilePath, 'Lorem ipsum :)'); await expect(getFileLastUpdate(tempFilePath)).resolves.toBeNull(); expect(consoleMock).toHaveBeenCalledTimes(1); @@ -79,6 +81,25 @@ describe('getFileLastUpdate', () => { await fs.unlink(tempFilePath); }); + it('multiple files not tracked by git', async () => { + const consoleMock = jest + .spyOn(console, 'warn') + .mockImplementation(() => {}); + const {repoDir} = createTempRepo(); + const tempFilePath1 = path.join(repoDir, 'file1.md'); + const tempFilePath2 = path.join(repoDir, 'file2.md'); + await fs.writeFile(tempFilePath1, 'Lorem ipsum :)'); + await fs.writeFile(tempFilePath2, 'Lorem ipsum :)'); + await expect(getFileLastUpdate(tempFilePath1)).resolves.toBeNull(); + await expect(getFileLastUpdate(tempFilePath2)).resolves.toBeNull(); + expect(consoleMock).toHaveBeenCalledTimes(1); + expect(consoleMock).toHaveBeenLastCalledWith( + expect.stringMatching(/not tracked by git./), + ); + await fs.unlink(tempFilePath1); + await fs.unlink(tempFilePath2); + }); + it('git does not exist', async () => { const mock = jest.spyOn(shell, 'which').mockImplementationOnce(() => null); const consoleMock = jest diff --git a/packages/docusaurus-plugin-content-docs/src/lastUpdate.ts b/packages/docusaurus-plugin-content-docs/src/lastUpdate.ts index c77ccae4efed..a0eb4d775997 100644 --- a/packages/docusaurus-plugin-content-docs/src/lastUpdate.ts +++ b/packages/docusaurus-plugin-content-docs/src/lastUpdate.ts @@ -31,17 +31,18 @@ export async function getFileLastUpdate( }); return {timestamp: result.timestamp, author: result.author}; } catch (err) { - if (err instanceof GitNotFoundError && !showedGitRequirementError) { - logger.warn('Sorry, the docs plugin last update options require Git.'); - showedGitRequirementError = true; - } else if ( - err instanceof FileNotTrackedError && - !showedFileNotTrackedError - ) { - logger.warn( - 'Cannot infer the update date for some files, as they are not tracked by git.', - ); - showedFileNotTrackedError = true; + if (err instanceof GitNotFoundError) { + if (!showedGitRequirementError) { + logger.warn('Sorry, the docs plugin last update options require Git.'); + showedGitRequirementError = true; + } + } else if (err instanceof FileNotTrackedError) { + if (!showedFileNotTrackedError) { + logger.warn( + 'Cannot infer the update date for some files, as they are not tracked by git.', + ); + showedFileNotTrackedError = true; + } } else { logger.warn(err); } diff --git a/packages/docusaurus-utils/src/__tests__/gitUtils.test.ts b/packages/docusaurus-utils/src/__tests__/gitUtils.test.ts index 64bbf62a4824..1d4519536cc4 100644 --- a/packages/docusaurus-utils/src/__tests__/gitUtils.test.ts +++ b/packages/docusaurus-utils/src/__tests__/gitUtils.test.ts @@ -8,51 +8,12 @@ import {FileNotTrackedError, getFileCommitDate} from '../gitUtils'; import fs from 'fs-extra'; import path from 'path'; -import os from 'os'; -import shell from 'shelljs'; +import {createTempRepo} from '@testing-utils/git'; -// This function is sync so the same mock repo can be shared across tests /* eslint-disable no-restricted-properties */ -function createTempRepo() { - const repoDir = fs.mkdtempSync(path.join(os.tmpdir(), 'git-test-repo')); - class Git { - constructor(private dir: string) { - const res = shell.exec('git init', {cwd: dir, silent: true}); - if (res.code !== 0) { - throw new Error(`git init exited with code ${res.code}. -stderr: ${res.stderr} -stdout: ${res.stdout}`); - } - // Doesn't matter currently - shell.exec('git config user.email "test@jc-verse.com"', { - cwd: dir, - silent: true, - }); - shell.exec('git config user.name "Test"', {cwd: dir, silent: true}); - } - commit(msg: string, date: string, author: string) { - const addRes = shell.exec('git add .', {cwd: this.dir, silent: true}); - const commitRes = shell.exec( - `git commit -m "${msg}" --date "${date}T00:00:00Z" --author "${author}"`, - { - cwd: this.dir, - env: {GIT_COMMITTER_DATE: `${date}T00:00:00Z`}, - silent: true, - }, - ); - if (addRes.code !== 0) { - throw new Error(`git add exited with code ${addRes.code}. -stderr: ${addRes.stderr} -stdout: ${addRes.stdout}`); - } - if (commitRes.code !== 0) { - throw new Error(`git commit exited with code ${commitRes.code}. -stderr: ${commitRes.stderr} -stdout: ${commitRes.stdout}`); - } - } - } - const git = new Git(repoDir); +function initializeTempRepo() { + const {repoDir, git} = createTempRepo(); + fs.writeFileSync(path.join(repoDir, 'test.txt'), 'Some content'); git.commit( 'Create test.txt', @@ -79,11 +40,12 @@ stdout: ${commitRes.stdout}`); 'Josh-Cena ', ); fs.writeFileSync(path.join(repoDir, 'untracked.txt'), "I'm untracked"); + return repoDir; } describe('getFileCommitDate', () => { - const repoDir = createTempRepo(); + const repoDir = initializeTempRepo(); it('returns earliest commit date', async () => { expect(getFileCommitDate(path.join(repoDir, 'test.txt'), {})).toEqual({ date: new Date('2020-06-19'),