@workday/canvas-kit-docs
Version:
Documentation components of Canvas Kit components
83 lines (82 loc) • 3.14 kB
JavaScript
import fs from 'node:fs/promises';
import path from 'node:path';
import typescript from 'typescript';
/**
* Reads a spec file by getting the file contents as a string, passing through the TypeScript
* transpiler, setting up custom `describe`, `it`, `beforeEach`, etc functions and runs the file
* contents through `eval()` with those custom functions to extract out a JSON object representing
* the structure of a Spec file. We use `eval` instead of an AST parser to handle meta coding like
* `[a, b].forEach(() => { it('...') })`. Running the contents through a JavaScript context means
* the file contents can be extracted via any runtime tricks used in spec files.
*/
export async function parseSpecFile(file) {
const [_, fileName] = file.match(/.+(cypress\/.+)$/) || [];
if (fileName) {
const contents = await fs
.readFile(file)
.then(contents => contents.toString())
.then(contents => contents.replace(/import (.+) from .+/g, (substr, imports) => {
if (imports.includes('{')) {
return `const ${imports.replace(/[{}]/g, '')} = () => {}`;
}
if (/react/g.test(imports)) {
return `const React = {createElement: () => {}}`;
}
return '';
})) // remove imports
.then(contents => typescript.transpile(contents, { jsx: typescript.JsxEmit.React }))
.then(contents => {
let children = [];
// eslint-disable-next-line no-empty-function
const noop = () => { };
// define for the eval()
// @ts-ignore
const before = noop;
// @ts-ignore
const beforeEach = noop;
// @ts-ignore
const after = noop;
// @ts-ignore
const afterEach = noop;
const describe = (name, optionsOrCb, cb) => {
const childrenBefore = children;
const obj = {
type: 'describe',
name,
children: [],
};
children.push(obj);
children = obj.children;
if (typeof optionsOrCb === 'function') {
optionsOrCb();
}
else if (typeof cb === 'function') {
cb();
}
children = childrenBefore;
};
describe.skip = noop;
describe.only = noop;
// @ts-ignore
const context = describe;
const it = (name) => {
const obj = {
type: 'it',
name,
};
children.push(obj);
};
it.skip = noop;
it.only = noop;
// eslint-disable-next-line no-eval
eval(contents);
return {
type: 'file',
name: path.basename(file).replace(`.spec${path.extname(file)}`, ''),
children,
};
});
return contents;
}
return null;
}