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,
moduleNameMapper: {
// 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
'@docusaurus/(browserContext|BrowserOnly|ComponentCreator|constants|docusaurusContext|ExecutionEnvironment|Head|Interpolate|isInternalUrl|Link|Noop|renderRoutes|router|Translate|use.*)':
'@docusaurus/core/src/client/exports/$1',
// 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?
'@theme/(.*)': '@docusaurus/theme-classic/src/theme/$1',
'@site/(.*)': 'website/$1',
@ -50,7 +50,7 @@ export default {
'@docusaurus/plugin-content-docs/client':
'@docusaurus/plugin-content-docs/src/client/index.ts',
},
snapshotSerializers: ['<rootDir>/jest/snapshotPathNormalizer.js'],
snapshotSerializers: ['<rootDir>/jest/snapshotPathNormalizer.ts'],
snapshotFormat: {
printBasicPrototype: false,
},

View file

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

View file

@ -57,10 +57,13 @@ describe('users data', () => {
.message('')
.required(),
// The preview should be jest/emptyModule
preview: Joi.object({}).unknown(false).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().',
}),
preview: Joi.object({default: Joi.any()})
.unknown(false)
.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()
.items(...TagList)
.required(),