UNPKG

@revoloo/cypress6

Version:

Cypress.io end to end testing tool

178 lines (148 loc) 4.15 kB
interface BaseNode { name: string type: 'file' | 'folder' absolute: string } export interface FileNode extends BaseNode { type: 'file' } export interface FolderNode extends BaseNode { type: 'folder' files: TreeNode[] } export type TreeNode = FileNode | FolderNode export function getAllFolders (files: string[]): string[] { /** * Returns an array of all nested directories given an array of files and folders. * * const files = [ * 'foo.js', * 'foo/y/bar.js', * 'foo/bar', * 'a/b/c', * ] * * getAllFolderNodes(files) //=> ['foo', 'foo/y', 'foo/bar', 'a', 'a/b', 'a/b/c']) */ const dirs = new Set<string>() for (const file of files) { const path = file.split('/') if (path.length) { // Does it contain a file? Assumption: files have an extension. const hasFileNode = path[path.length - 1].includes('.') // Remove file if present. const dirOnly = hasFileNode ? path.splice(0, path.length - 1) : path // Add directory to set. for (let i = 0; i < dirOnly.length; i++) { const dir = dirOnly.slice(0, i + 1) dirs.add(dir.join('/')) } } } return Array.from(dirs) } export function getAllFileNodes (files: string[]): Record<string, string[]> { /** * Returns a key/value map of directories to contained files. * * { * foo: ['bar.js'] * 'foo/bar': ['qux.js'] * } */ const allFileNodes: Record<string, string[]> = { '/': [], } for (const file of files) { const split = file.split('/') const isFileNode = split[split.length - 1].includes('.') const isRoot = split.length === 1 const isFileNodeInFolderNode = split.length > 1 && split[split.length - 1].includes('.') if (isFileNodeInFolderNode) { const [file, ...path] = split.reverse() const dir = path.reverse().join('/') if (!allFileNodes[dir]) { allFileNodes[dir] = [file] } else { allFileNodes[dir] = allFileNodes[dir].concat(file) } } if (isFileNode && isRoot) { allFileNodes['/'] = allFileNodes['/'].concat(file) } } return allFileNodes } function charCount (str: string, letter: string) { let count = 0 for (let position = 0; position < str.length; position++) { if (str.charAt(position) === letter) { count += 1 } } return count } /** * Given a list of files and folders, returns an nested array structure * representing a file system with use metadata like type, name and absolute. * * const files: string[] = ['x/y/z.js'] * const actual = makeFileNodeHierarchy(files) * [ * { * name: 'x', * absolute: 'x', * type: 'folder', * files: [ * { * name: 'y', * absolute: 'x/y', * type: 'folder', * files: [ * { * name: 'z.js', * type: 'file', * absolute: 'x/y/z.js' * } * ], * } * ] * } * ] */ export function makeFileHierarchy (files: string[]): TreeNode[] { const allFolderNodes = getAllFolders(files) const allFileNodes = getAllFileNodes(files) const foldersByLength = allFolderNodes.reduce<Record<number, string[]>>((acc, curr) => { const count = charCount(curr, '/') if (!acc[count]) { return { ...acc, [count]: [curr] } } return { ...acc, [count]: [...acc[count], curr] } }, {}) function walk (dirs: string[], depth = 0): TreeNode[] { if (!dirs) { return [] } return dirs.map((dir) => { const nestedDirs = foldersByLength[depth + 1] ? walk(foldersByLength[depth + 1].filter((x) => x.startsWith(dir)), depth + 1) : [] const containedFileNodes = (allFileNodes[dir] || []).map<TreeNode>((file) => { return { type: 'file', name: file, absolute: `${dir}/${file}`, } }) const dirname = dir.split('/').reverse()[0] return { name: dirname, files: [...nestedDirs, ...containedFileNodes], type: 'folder', absolute: dir, } }) } return walk(foldersByLength[0]) }