refactor: convert Jest infrastructure to TS (#6910)

This commit is contained in:
Joshua Chen 2022-03-14 08:43:51 +08:00 committed by GitHub
parent 1305977098
commit dc975fecbf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 59 additions and 86 deletions

View file

@ -35,13 +35,13 @@ export default {
errorOnDeprecated: true, errorOnDeprecated: true,
moduleNameMapper: { moduleNameMapper: {
// Jest can't resolve CSS or asset imports // Jest can't resolve CSS or asset imports
'^.+\\.(css|jpe?g|png|svg)$': '<rootDir>/jest/emptyModule.js', '^.+\\.(css|jpe?g|png|svg|webp)$': '<rootDir>/jest/emptyModule.ts',
// Using src instead of lib, so we always get fresh source // Using src instead of lib, so we always get fresh source
'@docusaurus/(browserContext|BrowserOnly|ComponentCreator|constants|docusaurusContext|ExecutionEnvironment|Head|Interpolate|isInternalUrl|Link|Noop|renderRoutes|router|Translate|use.*)': '@docusaurus/(browserContext|BrowserOnly|ComponentCreator|constants|docusaurusContext|ExecutionEnvironment|Head|Interpolate|isInternalUrl|Link|Noop|renderRoutes|router|Translate|use.*)':
'@docusaurus/core/src/client/exports/$1', '@docusaurus/core/src/client/exports/$1',
// Maybe point to a fixture? // Maybe point to a fixture?
'@generated/.*': '<rootDir>/jest/emptyModule.js', '@generated/.*': '<rootDir>/jest/emptyModule.ts',
// TODO use "projects" + multiple configs if we work on another theme? // TODO use "projects" + multiple configs if we work on another theme?
'@theme/(.*)': '@docusaurus/theme-classic/src/theme/$1', '@theme/(.*)': '@docusaurus/theme-classic/src/theme/$1',
'@site/(.*)': 'website/$1', '@site/(.*)': 'website/$1',
@ -50,7 +50,7 @@ export default {
'@docusaurus/plugin-content-docs/client': '@docusaurus/plugin-content-docs/client':
'@docusaurus/plugin-content-docs/src/client/index.ts', '@docusaurus/plugin-content-docs/src/client/index.ts',
}, },
snapshotSerializers: ['<rootDir>/jest/snapshotPathNormalizer.js'], snapshotSerializers: ['<rootDir>/jest/snapshotPathNormalizer.ts'],
snapshotFormat: { snapshotFormat: {
printBasicPrototype: false, printBasicPrototype: false,
}, },

View file

@ -5,4 +5,4 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
module.exports = {}; export default {};

View file

@ -9,82 +9,57 @@
// Forked from https://github.com/tribou/jest-serializer-path/blob/master/lib/index.js // Forked from https://github.com/tribou/jest-serializer-path/blob/master/lib/index.js
// Added some project-specific handlers // Added some project-specific handlers
const _ = require('lodash'); import _ from 'lodash';
const {escapePath} = require('@docusaurus/utils'); import {escapePath} from '@docusaurus/utils';
const {version} = require('@docusaurus/core/package.json'); import {version} from '@docusaurus/core/package.json';
const os = require('os'); import os from 'os';
const path = require('path'); import path from 'path';
const fs = require('fs'); import fs from 'fs';
module.exports = { export function print(
print(val, serialize) { val: unknown,
let normalizedValue = val; serialize: (val: unknown) => string,
): string {
if (_.isError(normalizedValue)) { if (val instanceof Error) {
const message = normalizePaths(normalizedValue.message); const message = normalizePaths(val.message);
const error = new Error(message); const error = new Error(message);
const allKeys = [
// Clone hidden props ...Object.getOwnPropertyNames(error),
const ownProps = Object.getOwnPropertyNames(error); ...Object.keys(val),
// eslint-disable-next-line no-restricted-syntax ] as (keyof Error)[];
for (const index in ownProps) { allKeys.forEach((key) => {
if (Object.prototype.hasOwnProperty.call(ownProps, index)) { error[key] = normalizePaths(val[key]) as never;
const key = ownProps[index]; });
return serialize(error);
error[key] = normalizePaths(normalizedValue[key]); } else if (val && typeof val === 'object') {
} const normalizedValue = _.cloneDeep(val) as Record<string, unknown>;
}
// Clone normal props
// eslint-disable-next-line no-restricted-syntax
for (const index in normalizedValue) {
if (Object.prototype.hasOwnProperty.call(normalizedValue, index)) {
error[index] = normalizePaths(normalizedValue[index]);
}
}
normalizedValue = error;
} else if (typeof normalizedValue === 'object') {
normalizedValue = _.cloneDeep(normalizedValue);
Object.keys(normalizedValue).forEach((key) => {
normalizedValue[key] = normalizePaths(normalizedValue[key]);
});
} else {
normalizedValue = normalizePaths(normalizedValue);
}
Object.keys(normalizedValue).forEach((key) => {
normalizedValue[key] = normalizePaths(normalizedValue[key]);
});
return serialize(normalizedValue); return serialize(normalizedValue);
}, }
test(val) { return serialize(normalizePaths(val));
let has = false; }
if (val && typeof val === 'object') { export function test(val: unknown): boolean {
// val.message is non-enumerable in an error return (
if (val.message && shouldUpdate(val.message)) { (typeof val === 'object' &&
has = true; val &&
} Object.keys(val).some((key) =>
shouldUpdate((val as Record<string, unknown>)[key]),
Object.keys(val).forEach((key) => { )) ||
if (shouldUpdate(val[key])) { // val.message is non-enumerable in an error
has = true; (val instanceof Error && shouldUpdate(val.message)) ||
} shouldUpdate(val)
}); );
} else if (shouldUpdate(val)) { }
has = true;
}
return has;
},
normalizePaths,
getRealPath,
};
/** /**
* Normalize paths across platforms. * Normalize paths across platforms.
* Filters must be ran on all platforms to guard against false positives * Filters must be ran on all platforms to guard against false positives
*/ */
function normalizePaths(value) { function normalizePaths<T>(value: T): T {
if (typeof value !== 'string') { if (typeof value !== 'string') {
return value; return value;
} }
@ -101,7 +76,7 @@ function normalizePaths(value) {
const homeRealRelativeToTempReal = path.relative(tempDirReal, homeDirReal); const homeRealRelativeToTempReal = path.relative(tempDirReal, homeDirReal);
const homeRealRelativeToTemp = path.relative(tempDir, homeDirReal); const homeRealRelativeToTemp = path.relative(tempDir, homeDirReal);
const runner = [ const runner: ((val: string) => string)[] = [
// Replace process.cwd with <PROJECT_ROOT> // Replace process.cwd with <PROJECT_ROOT>
(val) => val.split(cwdReal).join('<PROJECT_ROOT>'), (val) => val.split(cwdReal).join('<PROJECT_ROOT>'),
(val) => val.split(cwd).join('<PROJECT_ROOT>'), (val) => val.split(cwd).join('<PROJECT_ROOT>'),
@ -149,28 +124,23 @@ function normalizePaths(value) {
(val) => val.replace(/\\(?!")/g, '/'), (val) => val.replace(/\\(?!")/g, '/'),
]; ];
let result = value; let result = value as string;
runner.forEach((current) => { runner.forEach((current) => {
result = current(result); result = current(result);
}); });
return result; return result as T & string;
} }
function shouldUpdate(value) { function shouldUpdate(value: unknown) {
if (typeof value !== 'string') {
return false;
}
// return true if value is different from normalized value // return true if value is different from normalized value
return normalizePaths(value) !== value; return typeof value === 'string' && normalizePaths(value) !== value;
} }
function getRealPath(pathname) { function getRealPath(pathname: string) {
try { try {
// eslint-disable-next-line no-restricted-properties // eslint-disable-next-line no-restricted-properties
const realPath = fs.realpathSync(pathname); const realPath = fs.realpathSync(pathname);
return realPath; return realPath;
} catch (error) { } catch (error) {
return pathname; return pathname;

View file

@ -57,10 +57,13 @@ describe('users data', () => {
.message('') .message('')
.required(), .required(),
// The preview should be jest/emptyModule // The preview should be jest/emptyModule
preview: Joi.object({}).unknown(false).required().messages({ preview: Joi.object({default: Joi.any()})
'object.base': .unknown(false)
'The image should be hosted on Docusaurus site, and not use remote HTTP or HTTPS URLs. It must be imported with require().', .required()
}), .messages({
'object.base':
'The image should be hosted on Docusaurus site, and not use remote HTTP or HTTPS URLs. It must be imported with require().',
}),
tags: Joi.array() tags: Joi.array()
.items(...TagList) .items(...TagList)
.required(), .required(),