Skip to content

Commit

Permalink
feat: initial collect test results
Browse files Browse the repository at this point in the history
  • Loading branch information
atanasster committed Apr 11, 2021
1 parent 4991b5b commit e6daeec
Show file tree
Hide file tree
Showing 16 changed files with 226 additions and 22 deletions.
2 changes: 1 addition & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@
"name": "jest instrument",
"program": "${workspaceFolder}/node_modules/.bin/jest",
"cwd": "${workspaceFolder}/core/instrument",
"args": ["esm-props-info-external"],
"args": ["jest-tests"],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"disableOptimisticBPs": true,
Expand Down
6 changes: 6 additions & 0 deletions core/core/src/components.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { CodeLocation, ImportType, Imports } from './utility';
import { FileInfo } from './files';
import { JestTests } from './jest';

export type TypeValue =
| 'any'
Expand Down Expand Up @@ -181,6 +182,11 @@ export interface Component {
* source file info
*/
fileInfo?: FileInfo;

/**
* jest test and coverage results for the component
*/
jest?: JestTests;
}
/**
* given a component, return its name
Expand Down
1 change: 1 addition & 0 deletions core/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export * from './propsInfo';
export * from './document';
export * from './document-utils';
export * from './files';
export * from './jest';
export * from './utility';
export { randomizeData, canRandomizeControl } from './controls-randomize';
export * from './controls-smart';
Expand Down
30 changes: 30 additions & 0 deletions core/core/src/jest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
export interface JestTestResults {
ancestorTitles: string[];
failureDetails: unknown[];
failureMessages: string[];
numPassingAsserts: number;
status: 'passed' | 'failed' | 'skipped' | 'pending' | 'todo' | 'disabled';
title: string;
}

export interface CoverageMetrics {
total: number;
covered: number;
skipped: number;
pct: number;
}

export interface JestCoverage {
lines: CoverageMetrics;
functions: CoverageMetrics;
statements: CoverageMetrics;
branches: CoverageMetrics;
}

export interface JestTestFile {
testFileName: string;
testResults: JestTestResults[];
coverage: Record<string, JestCoverage>;
}

export type JestTests = JestTestFile[];
1 change: 1 addition & 0 deletions core/instrument/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"@babel/parser": "^7.12.5",
"@babel/traverse": "^7.12.5",
"@component-controls/core": "^3.6.3",
"@component-controls/jest-extract": "^3.6.3",
"@hutson/parse-repository-url": "^5.0.0",
"@mdx-js/loader": "^1.5.5",
"@mdx-js/react": "^1.6.5",
Expand Down
8 changes: 6 additions & 2 deletions core/instrument/src/babel/extract-component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import { followImports } from './follow-imports';
import { analyze_components } from './analyze-component';
import { packageInfo } from '../misc/package-info';
import { readSourceFile } from '../misc/source-options';

import { LoadingDocStore, InstrumentOptions } from '../types';
import { extractJestTests } from '../misc/jest-tests';

export interface ComponentParseData {
component?: Component;
Expand All @@ -34,7 +34,7 @@ export const extractComponent = async (
return globalCache[cacheKey];
}
const follow = followImports(componentName, filePath, source, options);
const { components, resolver: resolveOptions } = options || {};
const { components, resolver: resolveOptions, jest } = options || {};

let component: Component;
let componentPackage: PackageInfo | undefined;
Expand Down Expand Up @@ -116,6 +116,10 @@ export const extractComponent = async (
componentName,
follow.filePath,
);
if (jest !== false) {
const testResults = await extractJestTests(component.request, jest);
component.jest = testResults;
}
if (saveSource) {
component.source = saveSource;
component.loc = follow.loc;
Expand Down
2 changes: 2 additions & 0 deletions core/instrument/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ export const parseStories = async (
stories: storiesOptions = {},
mdx: mdxOptions = {},
propsLoaders,
jest: jestOptions = {},
} = options || {};

const mergedOptions: Required<InstrumentOptions> = {
Expand All @@ -150,6 +151,7 @@ export const parseStories = async (
stories: deepmerge<StoriesOptions>(defaultStoriesOptions, storiesOptions),
mdx: deepmerge<MDXOptions>(defaultMDXOptions, mdxOptions),
propsLoaders: propsLoaders || [],
jest: jestOptions,
};
let code: string;
const {
Expand Down
59 changes: 59 additions & 0 deletions core/instrument/src/misc/jest-tests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import path from 'path';
import fs from 'fs';
import { createHash } from 'crypto';
import { JestTests } from '@component-controls/core';
import { JestConfig } from '@component-controls/jest-extract';
import {
JestResults,
runTests,
getRelatedTests,
} from '@component-controls/jest-extract';
import { CachedFileResource } from './chached-file';

export const extractJestTests = async (
filePath: string,
options?: JestConfig,
): Promise<JestTests | undefined> => {
const testFiles = getRelatedTests(filePath);
const dateModified = fs.statSync(filePath).mtime;

const fileDir = path.dirname(filePath);
if (testFiles.length) {
const results: JestTests = [];
for (const testFile of testFiles) {
const fileHash = createHash('md5')
.update(dateModified.toString())
.update(filePath)
.digest('hex');

const cached = new CachedFileResource<JestResults>(
'jest-tests',
`${testFile}-${fileHash}`,
testFile,
);
const cachedTest = cached.get();
if (cachedTest) {
results.push({
testFileName: path.relative(fileDir, testFile),
testResults: cachedTest.testResults,
coverage: cachedTest.coverage,
});
}
const rootDir = path.relative(
path.resolve(path.dirname(testFile), 'config'),
fileDir,
);
const result = await runTests(testFile, { rootDir, ...options });
if (result) {
cached.set(result);
results.push({
testFileName: path.relative(fileDir, testFile),
testResults: result.testResults,
coverage: result.coverage,
});
}
}
return results.length ? results : undefined;
}
return undefined;
};
7 changes: 7 additions & 0 deletions core/instrument/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { ParserOptions } from '@babel/parser';
import { Document, Store } from '@component-controls/core';
import { JestConfig } from '@component-controls/jest-extract';
import images from 'remark-images';
import emoji from 'remark-emoji';
import { SyncOpts as ResolveOptions } from 'resolve';

import {
Options,
ResolveConfigOptions as ResolvePrettierConfigOptions,
Expand Down Expand Up @@ -279,6 +281,11 @@ export interface InstrumentOptions {
* mdx-js parsing options
*/
mdx?: MDXOptions;

/**
* jest instrumentation options
*/
jest?: JestConfig | false;
}

export interface MDXExportType {
Expand Down
77 changes: 77 additions & 0 deletions core/instrument/test/jest-tests.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import path from 'path';
import { extractJestTests } from '../src/misc/jest-tests';

describe('component-tests', () => {
it('', async () => {
const tests = await extractJestTests(
path.resolve(__dirname, '../../../ui/components/src/Header/Header.tsx'),
);
expect(tests).toMatchObject({
testFileName: 'Header.test.ts',
testResults: [
{
ancestorTitles: ['Header'],
failureDetails: [],
failureMessages: [],
numPassingAsserts: 0,
status: 'passed',
title: 'overview',
},
],
coverage: {
'Header.stories.tsx': {
lines: {
total: 4,
covered: 4,
skipped: 0,
pct: 100,
},
functions: {
total: 1,
covered: 1,
skipped: 0,
pct: 100,
},
statements: {
total: 6,
covered: 6,
skipped: 0,
pct: 100,
},
branches: {
total: 0,
covered: 0,
skipped: 0,
pct: 100,
},
},
'Header.tsx': {
lines: {
total: 2,
covered: 2,
skipped: 0,
pct: 100,
},
functions: {
total: 1,
covered: 1,
skipped: 0,
pct: 100,
},
statements: {
total: 6,
covered: 6,
skipped: 0,
pct: 100,
},
branches: {
total: 0,
covered: 0,
skipped: 0,
pct: 100,
},
},
},
});
}, 100000);
});
2 changes: 1 addition & 1 deletion core/jest-extract/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
"dependencies": {
"@component-controls/core": "^3.6.3",
"babel-jest": "^26.4.4",
"jest-cli": "^26.4.2",
"jest": "^26.4.2",
"path": "^0.12.7",
"ts-jest": "^26.4.4"
},
Expand Down
34 changes: 25 additions & 9 deletions core/jest-extract/src/related-tests.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import path from 'path';
import { Config } from '@jest/types';
import { sync as globSync } from 'glob';
import { run, JestResults } from './run-tests';
import { runTests, JestResults } from './run-tests';

export type RelatedTest = {
/**
Expand All @@ -12,12 +12,14 @@ export type RelatedTest = {

export type RelatedTests = RelatedTest[];

export const runRelatedTests = async (
fileName: string,
jestConfig?: Partial<Config.Argv>,
): Promise<RelatedTests> => {
const name = path.basename(fileName).split('.')[0];
const fileDir = path.dirname(fileName);
/**
* find all test files related to a component file
* @param filePath full path of the component file
* @returns a list of file names of the tests
*/
export const getRelatedTests = (filePath: string): string[] => {
const fileDir = path.dirname(filePath);
const name = path.basename(filePath).split('.')[0];
const globs = [
`${fileDir}${path.sep}${name}*.@(test|spec).@(j|t)s?(x)`,
`${fileDir}${path.sep}__tests__${path.sep}**${path.sep}*.@(j|t)s?(x)`,
Expand All @@ -29,21 +31,35 @@ export const runRelatedTests = async (
});
return [...acc, ...newFiles];
}, []);
return testFiles;
};

export type JestConfig = Partial<Config.Argv>;
/**
* retrieves all the tests related to a componnet file
* @param filePath full path of the component file
* @param jestConfig jest custom configuration
* @returns a list of the test results
*/
export const runRelatedTests = async (
filePath: string,
jestConfig?: JestConfig,
): Promise<RelatedTests> => {
const testFiles = getRelatedTests(filePath);
const fileDir = path.dirname(filePath);
const results: RelatedTests = await Promise.all(
testFiles.map(async testFile => {
const rootDir = path.relative(
path.resolve(path.dirname(testFile), 'config'),
fileDir,
);
const result = await run(testFile, { rootDir, ...jestConfig });
const result = await runTests(testFile, { rootDir, ...jestConfig });

return {
testFileName: path.relative(fileDir, testFile),
...result,
} as RelatedTest;
}),
);

return results;
};
5 changes: 3 additions & 2 deletions core/jest-extract/src/run-tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export interface JestResults {
*/
coverage: Record<string, CoverageSummaryData>;
}
export const run = async (
export const runTests = async (
testFilePath: string,
jestConfig: Partial<Config.Argv> = {},
): Promise<JestResults | undefined> => {
Expand Down Expand Up @@ -64,10 +64,11 @@ export const run = async (
runResults = await runCLI(
{
testRegex: testFile,
maxWorkers: 1,
testPathIgnorePatterns: ['/node_modules/', '/__snapshots__/'],
silent: true,
verbose: false,
// reporters: [],
reporters: [],
coverage: true,
coverageReporters: ['none'],
watchman: false,
Expand Down
6 changes: 3 additions & 3 deletions core/jest-extract/test/tests/run/react-component.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import path from 'path';
import { Await } from '@component-controls/core';
import { run } from '../../../src';
import { runTests } from '../../../src';

let results: Await<ReturnType<typeof run>>;
let results: Await<ReturnType<typeof runTests>>;
beforeAll(async () => {
results = await run(
results = await runTests(
path.resolve(__dirname, '../../fixtures/component/Link.react.test.js'),
);
}, 50000);
Expand Down
Loading

0 comments on commit e6daeec

Please sign in to comment.