Skip to content

Commit

Permalink
test: remove test reliance on file system
Browse files Browse the repository at this point in the history
  • Loading branch information
ChristopherPHolder committed Feb 19, 2024
1 parent 81abd2a commit 8680e02
Show file tree
Hide file tree
Showing 7 changed files with 148 additions and 139 deletions.
3 changes: 2 additions & 1 deletion packages/cli/src/lib/commands/init/command-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {handleFlowGeneration} from "./processes/generate-userflow";
import {handleGhWorkflowGeneration} from "./processes/generate-workflow";
import {handleBudgetsGeneration} from "./processes/generate-lh-budgets";
import {SETUP_CONFIRM_MESSAGE} from "./constants";
import { fileSystemManager } from '../../core/file';

export async function runInitCommand(argv: RcJsonAsArgv) {
const { interactive } = getGlobalOptionsFromArgv(argv);
Expand All @@ -19,7 +20,7 @@ export async function runInitCommand(argv: RcJsonAsArgv) {
await run([
collectRcJson,
updateRcJson,
handleFlowGeneration({ interactive: !!interactive, generateFlow }),
handleFlowGeneration({ interactive: !!interactive, generateFlow }, fileSystemManager),
handleGhWorkflowGeneration({ generateGhWorkflow }),
handleBudgetsGeneration({ generateBudgets, lhr }),
])(cfg );
Expand Down
13 changes: 2 additions & 11 deletions packages/cli/src/lib/commands/init/index.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,7 @@
import { YargsCommandObject } from '../../core/yargs/types';
import { log, logVerbose } from '../../core/loggin';
import { logVerbose } from '../../core/loggin';
import { INIT_OPTIONS } from './options';
import { getInitCommandOptionsFromArgv } from './utils';
import { collectRcJson } from './processes/collect-rc-json';
import { run } from '../../core/processing/behaviors';
import { SETUP_CONFIRM_MESSAGE } from './constants';
import { updateRcJson } from './processes/update-rc-json';
import { handleFlowGeneration } from './processes/generate-userflow';
import { getGlobalOptionsFromArgv } from '../../global/utils';
import { handleGhWorkflowGeneration } from './processes/generate-workflow';
import { handleBudgetsGeneration } from './processes/generate-lh-budgets';
import {runInitCommand} from "./command-impl";
import { runInitCommand } from './command-impl';

export const initCommand: YargsCommandObject = {
command: 'init',
Expand Down

This file was deleted.

67 changes: 37 additions & 30 deletions packages/cli/src/lib/commands/init/processes/generate-userflow.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import { RcJson } from '../../../types';
import { join } from 'path';
import { readFile, writeFile } from '../../../core/file';
import { log } from '../../../core/loggin';
import { mkdirSync, readdirSync } from 'fs';
import { FileSystemManager } from '../../../core/file';
import { log, logVerbose } from '../../../core/loggin';
import { FlowExampleMap } from '../constants';
import { FlowExamples } from '../types';
import { ifThenElse } from '../../../core/processing/behaviors';
import { askToSkip } from '../../../core/prompt';
import { CLIProcess } from '../../../core/processing/types';
import { logVerbose } from '../../../core/loggin';
import { PROMPT_INIT_GENERATE_FLOW } from '../options/generateFlow.constants';

const exampleName = 'basic-navigation';
Expand All @@ -18,44 +16,53 @@ export function getExamplePathDest(flowExample: FlowExamples, folder: string): s
return join(folder, fileName);
}

export const userflowIsNotCreated = (cfg?: RcJson) => Promise.resolve(cfg ? readFile(getExamplePathDest(exampleName, cfg.collect.ufPath)) === '' : false);

export async function generateUserFlow(cliCfg: RcJson): Promise<RcJson> {
const ufPath = cliCfg.collect.ufPath;
// DX create directory if it does ot exist
try {
readdirSync(ufPath);
} catch (e) {
mkdirSync(ufPath, { recursive: true });
export const userflowIsNotCreated = (fSM: FileSystemManager) => {
return (cfg: RcJson): Promise<boolean> => {
const path = getExamplePathDest(exampleName, cfg.collect.ufPath);
return Promise.resolve(!fSM.existSync(path));
}
const tplFileName = FlowExampleMap[exampleName];
const exampleSourceLocation = join(__dirname,'..', 'static', tplFileName);
const exampleDestination = join(ufPath, tplFileName);
};

if (readFile(exampleDestination) !== '') {
logVerbose(`User flow ${exampleName} already generated under ${exampleDestination}.`);
return Promise.resolve(cliCfg);
}
export const generateUserFlow = (fSM: FileSystemManager): CLIProcess => {
return (cfg: RcJson): Promise<RcJson> => {
const ufPath = cfg.collect.ufPath;
// DX create directory if it does ot exist
try {
fSM.readdirSync(ufPath);
} catch (e) {
fSM.mkdirSync(ufPath, { recursive: true });
}
const tplFileName = FlowExampleMap[exampleName];
const exampleSourceLocation = join(__dirname, '..', 'static', tplFileName);
const exampleDestination = join(ufPath, tplFileName);

const fileContent = readFile(exampleSourceLocation, { fail: true }).toString();
writeFile(exampleDestination, fileContent);
if (fSM.readFile(exampleDestination) !== '') {
logVerbose(`User flow ${exampleName} already generated under ${exampleDestination}.`);
return Promise.resolve(cfg);
}
const fileContent = fSM.readFile(exampleSourceLocation, { fail: true }).toString();
fSM.writeFile(exampleDestination, fileContent);

log(`setup user-flow for basic navigation in ${ufPath} successfully`);
return Promise.resolve(cliCfg);
}
log(`setup user-flow for basic navigation in ${ufPath} successfully`);
return Promise.resolve(cfg);
}
};

export function handleFlowGeneration({ generateFlow, interactive }: {interactive: boolean, generateFlow?: boolean}): CLIProcess {
export function handleFlowGeneration(
{ generateFlow, interactive }: {interactive: boolean, generateFlow?: boolean},
fSM: FileSystemManager
): CLIProcess {
return ifThenElse(
// if `withFlow` is not used in the CLI is in interactive mode
() => interactive == true && generateFlow === undefined,
() => interactive && generateFlow === undefined,
// Prompt for flow generation
askToSkip(PROMPT_INIT_GENERATE_FLOW, generateUserFlow,
askToSkip(PROMPT_INIT_GENERATE_FLOW, generateUserFlow(fSM),
// if the flow is not created already, otherwise skip creation
{ precondition: userflowIsNotCreated }),
{ precondition: userflowIsNotCreated(fSM) }),
// else `withFlow` is used and true
ifThenElse(() => !!generateFlow,
// generate the file => else do nothing
generateUserFlow)
generateUserFlow(fSM))
)
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { INITIATED_RC_JSON } from 'test-data';
import { handleFlowGeneration } from './generate-userflow';
import { PathLike } from 'node:fs';
import { fileSystemManager, FileSystemManager } from '../../../core/file';

describe('generate userflow', () => {
let fSM: FileSystemManager;

beforeAll(() => {
jest.mock((''))
})
beforeEach(() => {
fSM = fileSystemManager
})

/**
* Legend:
* - [nf] - flow file does not exist
* - [f] - flow file does exist
* - [F] - flow file creation
* - [nF] - no flow file creation
* - [P] - Prompt executes
* - [L] - Log occurs
* - [N] - no operation
*
* Cases:
* [nf] init --interactive => [P]: "Setup user flow" -> Y:[F]/n[nF],
* [f] init --interactive => [L]: "File already here"; [nF]
* [nf] init --no-interactive => [N]
* [f] init --no-interactive => [N]
* [nf] init --interactive --generateFlow => [L]: "File already here"; [nF]
* [f] init --interactive --generateFlow => [F]
* [f] init --interactive --no-generateFlow => [N]
*
*/

/*
@TODO create a test helper for functions with prompt side effects
// [nf] init --interactive => [P]: "Setup user flow" -> Y:[F]/n[nF]
it('should prompt with --interactive', withUserFlowProject(EMPTY_PRJ_CFG, async (_) => {
const expectedFilePath = join(INITIATED_PRJ_CFG.root, INITIATED_RC_JSON.collect.ufPath,'basic-navigation.uf.ts');
expect(existsSync(expectedFilePath)).toBeFalsy();
let o = '';
(console as any).log = (...v) => o += v.join(', ')
await handleFlowGeneration({interactive: true})(INITIATED_RC_JSON);
expect(o).toContain('Setup user flow')
expect(existsSync(expectedFilePath)).toBeTruthy();
})
);*/

// [f] init --interactive => [L]: "File already here"; [nF]
it('should not prompt with --interactive if file exists',async () => {
fSM.existSync = (path: PathLike): boolean => true;
const spy = jest.spyOn(fSM, 'existSync');
await handleFlowGeneration({interactive: true}, fSM )(INITIATED_RC_JSON);
expect(spy).toHaveBeenCalled();
});

// [f] init --no-interactive => [N]
it('should not prompt with --no-interactive', async () => {
fSM.existSync = (path: PathLike): boolean => true;
const spy = jest.spyOn(fSM, 'existSync');
await handleFlowGeneration({interactive: false}, fSM)(INITIATED_RC_JSON);
expect(spy).toHaveBeenCalledTimes(0);
});

// [nf] init --no-interactive => [N]
it('should not prompt with --no-interactive', async () => {
fSM.existSync = (path: PathLike): boolean => false;
const spy = jest.spyOn(fSM, 'existSync');
await handleFlowGeneration({interactive: false}, fSM)(INITIATED_RC_JSON);
expect(spy).toHaveBeenCalledTimes(0);
});

// [nf] init --interactive --generateFlow => [L]: "File already here"; [nF]
it('should create flow when --generateFlow is used',async () => {
fSM.existSync = (path: PathLike): boolean => true;
fSM.writeFile = (filePath: string, data: string) => {};
fSM.readdirSync = (path: PathLike) => [''] as any;
fSM.readFile = (path: PathLike) => '' as any;
const spy = jest.spyOn(fSM, 'writeFile');
await handleFlowGeneration({interactive: true, generateFlow: true}, fSM)(INITIATED_RC_JSON);
expect(spy).toHaveBeenCalled();
});

// [f] init --interactive --no-generateFlow => [N]
it('should not create flow when --no-generateFlow is used', async () => {
fSM.existSync = (path: PathLike): boolean => true;
fSM.writeFile = (filePath: string, data: string) => {};
const spy = jest.spyOn(fSM, 'writeFile');
await handleFlowGeneration({interactive: true, generateFlow: false}, fSM)(INITIATED_RC_JSON);
expect(spy).toHaveBeenCalledTimes(0);
});
});
15 changes: 12 additions & 3 deletions packages/cli/src/lib/core/file/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { dirname } from 'path';
import { existsSync, readFileSync, writeFileSync, mkdirSync, lstatSync } from 'fs';
import { existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'fs';
import { logVerbose } from '../loggin';
import { getParserFromExtname, formatCode } from '../prettier';
import { formatCode, getParserFromExtname } from '../prettier';
import { ReadFileConfig } from '../../commands/collect/utils/replay/types';
import { ExtToOutPut, ResolveFileResult } from './types';

Expand Down Expand Up @@ -61,7 +61,7 @@ export function readFile<R extends any = undefined, T extends ReadFileConfig = {
/**
* Ensures the folder exists before writing it
*/
export function writeFile(filePath: string, data: string) {
export function writeFile(filePath: string, data: string): void {
const dir = dirname(filePath);
if (!existsSync(dir)) {
logVerbose(`Created dir ${dir} to save ${filePath}`);
Expand Down Expand Up @@ -101,3 +101,12 @@ export function resolveAnyFile<T>(path: string): ResolveFileResult<T> {
return { exports, path };
}

export const fileSystemManager = {
writeFile: writeFile,
readFile: readFile,
existSync: existsSync,
readdirSync: readdirSync,
mkdirSync: mkdirSync,
}

export type FileSystemManager = typeof fileSystemManager;
2 changes: 1 addition & 1 deletion packages/cli/src/lib/core/prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export function askToSkip(
question: string,
cliProcess: CLIProcess,
options: {
precondition?: (r?: RcJson) => Promise<boolean>;
precondition?: (r: RcJson) => Promise<boolean>;
} = {
precondition: async () => true,
}
Expand Down

0 comments on commit 8680e02

Please sign in to comment.