Skip to content

Commit

Permalink
Test(analytics): Introduce tests for the Twig scanner
Browse files Browse the repository at this point in the history
refs #DS-874
  • Loading branch information
literat committed Nov 29, 2023
1 parent b292f8c commit 5dd7d71
Show file tree
Hide file tree
Showing 9 changed files with 340 additions and 23 deletions.
6 changes: 5 additions & 1 deletion packages/analytics/config/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,15 @@ const config = {

// An array of regexp pattern strings that are matched against all file paths before executing the test.
// https://jestjs.io/docs/configuration#coveragepathignorepatterns-arraystring
coveragePathIgnorePatterns: ['__fixtures__'],
coveragePathIgnorePatterns: ['__fixtures__', 'bin'],

// A list of reporter names that Jest uses when writing coverage reports. Any istanbul reporter can be used.
// https://jestjs.io/docs/configuration#coveragereporters-arraystring--string-options
coverageReporters: ['text', 'text-summary', ['lcov', { projectRoot: '../../' }]],

// An array of regexp pattern strings that are matched against all source file paths before transformation.
// https://jestjs.io/docs/configuration#transformignorepatterns-arraystring
transformIgnorePatterns: ['<rootDir>/../../node_modules/zx'],
};

export default config;
59 changes: 59 additions & 0 deletions packages/analytics/src/__tests__/runner.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import scanner from 'react-scanner';
import { path } from 'zx';
import { runner } from '../runner';
import { RunnerConfig } from '../types';

jest.mock('../scanners/twigScanner', () => {
return jest.fn(() => Promise.resolve({ test: 'test' }));
});

describe('runner', () => {
jest.spyOn(path, 'resolve').mockImplementation((...args: string[]) => args.join('/'));

it('should return tracked data for react type', async () => {
const config = {
react: {},
twig: {},
} as RunnerConfig;
const source = '/path/to/source';
const type = 'react';
jest.spyOn(scanner, 'run').mockResolvedValue({ test: 'test' });

const trackedData = await runner(config, source, type);

expect(trackedData.spiritVersion).toBeDefined();
expect(trackedData.trackedData.react).toEqual({ test: 'test' });
expect(trackedData.trackedData.twig).toEqual({});
});

it('should return tracked data for twig type', async () => {
const config = {
react: {},
twig: {},
} as RunnerConfig;
const source = '/path/to/source';
const type = 'twig';

const trackedData = await runner(config, source, type);

expect(trackedData.spiritVersion).toBeDefined();
expect(trackedData.trackedData.react).toEqual({});
expect(trackedData.trackedData.twig).toEqual({ test: 'test' });
});

it('should return tracked data for both react and twig types', async () => {
const config = {
react: {},
twig: {},
} as RunnerConfig;
const source = '/path/to/source';
const type = null;
jest.spyOn(scanner, 'run').mockResolvedValue({ test: 'test' });

const trackedData = await runner(config, source, type);

expect(trackedData.spiritVersion).toBeDefined();
expect(trackedData.trackedData.react).toEqual({ test: 'test' });
expect(trackedData.trackedData.twig).toEqual({ test: 'test' });
});
});
8 changes: 4 additions & 4 deletions packages/analytics/src/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,18 @@ const getTrackedData = async ({
let twigOutput = {};

if (type === 'react' || type === null) {
reactOutput = await reactScanner({ ...config, crawlFrom });
reactOutput = await reactScanner({ ...config.react, crawlFrom });
}

if (type === 'twig' || type === null) {
twigOutput = await twigScanner({ ...config, crawlFrom });
twigOutput = await twigScanner({ ...config.twig, crawlFrom });
}

return {
spiritVersion,
trackedData: {
...reactOutput,
...twigOutput,
react: reactOutput,
twig: twigOutput,
},
};
};
Expand Down
19 changes: 19 additions & 0 deletions packages/analytics/src/scanners/__tests__/reactScanner.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import scanner from 'react-scanner';
import reactScanner from '../reactScanner';

describe('reactScanner', () => {
it('should return the output of the scanner', async () => {
const mockedScannerRun = jest.spyOn(scanner, 'run');
mockedScannerRun.mockResolvedValue('scanner output');

const config = {
crawlFrom: 'path/to/crawl/from',
config: 'path/to/config',
};

const output = await reactScanner(config);

expect(output).toBe('scanner output');
expect(mockedScannerRun).toHaveBeenCalledWith(config);
});
});
224 changes: 224 additions & 0 deletions packages/analytics/src/scanners/__tests__/twigScanner.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
import { fs, path } from 'zx';
import * as twigScanner from '../twigScanner';

describe('twigScanner', () => {
describe('getComponentsFromDirectory', () => {
it('should return an array of component names, capitalize first letter and remove `.twig` extension', () => {
const mockedReaddirSync = jest
.spyOn(fs, 'readdirSync')
// @ts-expect-error TS2322: Type 'string' is not assignable to type 'Dirent'.
.mockReturnValue(['component1.twig', 'component2.twig', 'component3.twig']);
const directoryPath = '/path/to/directory';
const expectedComponents = ['Component1', 'Component2', 'Component3'];

const result = twigScanner.getComponentsFromDirectory(directoryPath);

expect(result).toEqual(expectedComponents);
expect(mockedReaddirSync).toHaveBeenCalledWith(directoryPath);
});
});

describe('getPathsFromYamlConfig', () => {
it('should return an array of component names, capitalize first letter and remove `.twig` extension', () => {
const twigConfig = `
spirit_web_twig:
paths:
- "%kernel.project_dir%/app/Resources/views"
- "%kernel.project_dir%/../spirit-web-twig-bundle/docs/twig-components"
icons:
paths:
- "%kernel.project_dir%/../spirit-web-twig-bundle/static"
`;
const mockedReadFileSync = jest.spyOn(fs, 'readFileSync').mockReturnValue(twigConfig);
const configFile = '/path/to/config/file';
const expectedResult = [
'./app/Resources/views/*.twig',
'./../spirit-web-twig-bundle/docs/twig-components/*.twig',
'./../spirit-web-twig-bundle/static/*.twig',
];

const result = twigScanner.getPathsFromYamlConfig(configFile);

expect(result).toEqual(expectedResult);
expect(mockedReadFileSync).toHaveBeenCalledWith(configFile, 'utf8');
});
});

describe('determineModuleNameFromComponents', () => {
it('should return "local_component" if the nodeName is in the localComponents array', () => {
const nodeName = 'Component1';
const localComponents = ['Component1', 'Component2', 'Component3'];
const baseComponents = ['BaseComponent1', 'BaseComponent2'];

const result = twigScanner.determineModuleNameFromComponents(nodeName, localComponents, baseComponents);

expect(result).toBe('local_component');
});

it('should return "@lmc-eu/spirit-web-twig" if the nodeName is in the baseComponents array', () => {
const nodeName = 'BaseComponent2';
const localComponents = ['Component1', 'Component2', 'Component3'];
const baseComponents = ['BaseComponent1', 'BaseComponent2'];

const result = twigScanner.determineModuleNameFromComponents(nodeName, localComponents, baseComponents);

expect(result).toBe('@lmc-eu/spirit-web-twig');
});

it('should return "html_element" if the nodeName is not in the localComponents or baseComponents array', () => {
const nodeName = 'UnknownComponent';
const localComponents = ['Component1', 'Component2', 'Component3'];
const baseComponents = ['BaseComponent1', 'BaseComponent2'];

const result = twigScanner.determineModuleNameFromComponents(nodeName, localComponents, baseComponents);

expect(result).toBe('html_element');
});
});

describe('searchFileForComponents', () => {
it('should return an empty object if file content is empty', () => {
const file = '';
const localComponents: Array<string> = [];
const baseComponents: Array<string> = [];
jest.spyOn(fs, 'readFileSync').mockReturnValue(file);

const result = twigScanner.searchFileForComponents(file, localComponents, baseComponents);

expect(result).toEqual({});
});

it('should return the correct components with their props', () => {
const file = 'app/Resources/views/.../card.twig';
const fileContent = `
<Button color="primary" size="small">Click me</Button>
<Input type="text" placeholder="Enter your name" />
<CustomComponent prop1="value1" prop2="value2" />
`;
const localComponents = ['Button', 'Input', 'CustomComponent'];
const baseComponents: Array<string> = [];
jest.spyOn(fs, 'readFileSync').mockReturnValue(fileContent);

const result = twigScanner.searchFileForComponents(file, localComponents, baseComponents);

expect(result).toEqual({
'local_component:Button': [
{
path: 'app/Resources/views/.../card.twig:2',
props: {
color: 'primary',
size: 'small',
},
},
],
'local_component:CustomComponent': [
{
path: 'app/Resources/views/.../card.twig:4',
props: {
prop1: 'value1',
prop2: 'value2',
},
},
],
'local_component:Input': [
{
path: 'app/Resources/views/.../card.twig:3',
props: {
placeholder: 'Enter your name',
type: 'text',
},
},
],
});
});
});

describe('searchDirectoryForComponents', () => {
it('should return an empty object if the directory is excluded', () => {
const dir = '/path/to/excluded/directory';
const localComponents: Array<string> = [];
const baseComponents: Array<string> = [];
const exclude: Array<string> = ['/path/to/excluded/directory'];

const lstatResult = { isDirectory: () => false } as fs.Stats;
const dirContent: fs.Dirent[] = ['file1.twig', 'file2.twig'] as unknown as fs.Dirent[];
jest.spyOn(fs, 'readdirSync').mockReturnValue(dirContent);
jest.spyOn(fs, 'lstatSync').mockReturnValue(lstatResult);
jest.spyOn(path, 'extname').mockReturnValue('.twig');
jest.spyOn(path, 'basename').mockReturnValue(dir);
jest.spyOn(path, 'join').mockImplementation((directory, file) => `${directory}/${file}`);

const result = twigScanner.searchDirectoryForComponents(dir, localComponents, baseComponents, exclude);

expect(result).toEqual({});
});

it('should return an empty object if the directory is empty', () => {
const dir = '/path/to/empty/directory';
const localComponents: Array<string> = [];
const baseComponents: Array<string> = [];
const exclude: Array<string> = [];

const lstatResult = { isDirectory: () => false } as fs.Stats;
jest.spyOn(fs, 'lstatSync').mockReturnValue(lstatResult);
jest.spyOn(path, 'extname').mockReturnValue('.twig');
jest.spyOn(path, 'join').mockImplementation((directory, file) => `${directory}/${file}`);
jest.spyOn(fs, 'readdirSync').mockReturnValue([]);

const result = twigScanner.searchDirectoryForComponents(dir, localComponents, baseComponents, exclude);

expect(result).toEqual({});
expect(fs.readdirSync).toHaveBeenCalledWith(dir);
});

it('should recursively search the directory for .twig files and call searchFileForComponents', () => {
const dir = '/path/to/directory';
const localComponents = ['Button', 'Input', 'CustomComponent'];
const baseComponents: Array<string> = [];
const exclude: Array<string> = [];
const lstatResult = { isDirectory: () => false } as fs.Stats;
const dirContent: fs.Dirent[] = ['file1.twig', 'file2.twig'] as unknown as fs.Dirent[];

const mockedReaddirSync = jest.spyOn(fs, 'readdirSync').mockReturnValue(dirContent);
const lstatSync = jest.spyOn(fs, 'lstatSync').mockReturnValue(lstatResult);
const mockedExtname = jest.spyOn(path, 'extname').mockReturnValue('.twig');
const mockedPathJoin = jest.spyOn(path, 'join').mockImplementation((directory, file) => `${directory}/${file}`);

const result = twigScanner.searchDirectoryForComponents(dir, localComponents, baseComponents, exclude);

expect(result).toEqual({
'local_component:Button': [
{
path: '/path/to/directory/file2.twig:2',
props: {
color: 'primary',
size: 'small',
},
},
],
'local_component:CustomComponent': [
{
path: '/path/to/directory/file2.twig:4',
props: {
prop1: 'value1',
prop2: 'value2',
},
},
],
'local_component:Input': [
{
path: '/path/to/directory/file2.twig:3',
props: {
placeholder: 'Enter your name',
type: 'text',
},
},
],
});
expect(mockedReaddirSync).toHaveBeenCalledWith(dir);
expect(lstatSync).toHaveBeenCalledTimes(2);
expect(mockedExtname).toHaveBeenCalledTimes(2);
expect(mockedPathJoin).toHaveBeenCalledTimes(2);
});
});
});
4 changes: 2 additions & 2 deletions packages/analytics/src/scanners/reactScanner.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import scanner from 'react-scanner';
import { RunnerConfig } from '../types';
import { ReactScannerConfig } from '../types';

export default async function reactScanner(config: RunnerConfig) {
export default async function reactScanner(config: ReactScannerConfig) {
const output = await scanner.run({ ...config });

return output;
Expand Down
Loading

0 comments on commit 5dd7d71

Please sign in to comment.