mirror of https://github.com/artf/grapesjs.git
nocodeframeworkdrag-and-dropsite-buildersite-generatortemplate-builderui-builderweb-builderweb-builder-frameworkwebsite-builderno-codepage-builder
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
338 lines
12 KiB
338 lines
12 KiB
import { isFunction, isObject, isString, isUndefined, printRow, printError, log, ensureDir, normalizeJsonOpt, buildWebpackArgs, copyRecursiveSync, babelConfig, originalRequire, resolve, rootResolve } from "../src/utils";
|
|
import chalk from 'chalk';
|
|
import fs from 'fs';
|
|
import path from 'path';
|
|
import * as process from 'process';
|
|
|
|
const typeTestValues = {
|
|
undefinedValue: undefined,
|
|
nullValue: null,
|
|
stringValue: 'hello',
|
|
emptyObject: {},
|
|
nonEmptyObject: { key: 'value' },
|
|
emptyArray: [],
|
|
functionValue: () => { },
|
|
numberValue: 42,
|
|
booleanValue: true,
|
|
dateValue: new Date(),
|
|
};
|
|
|
|
function runTypeCheck(typeCheckFunction: (value: any) => boolean) {
|
|
const keysWithPassingTypeChecks = Object.keys(typeTestValues).filter(key => {
|
|
const value = typeTestValues[key];
|
|
return typeCheckFunction(value);
|
|
});
|
|
|
|
return keysWithPassingTypeChecks;
|
|
}
|
|
|
|
jest.mock('fs');
|
|
jest.mock('fs/promises');
|
|
|
|
describe('utils', () => {
|
|
afterEach(() => {
|
|
jest.clearAllMocks();
|
|
});
|
|
|
|
describe('isString', () => {
|
|
it('should correctly identify strings', () => {
|
|
const result = runTypeCheck(isString);
|
|
expect(result).toEqual(['stringValue']);
|
|
});
|
|
});
|
|
|
|
describe('isUndefined', () => {
|
|
it('should correctly identify undefined values', () => {
|
|
const result = runTypeCheck(isUndefined);
|
|
expect(result).toEqual(['undefinedValue']);
|
|
});
|
|
});
|
|
|
|
describe('isFunction', () => {
|
|
it('should correctly identify functions', () => {
|
|
const result = runTypeCheck(isFunction);
|
|
expect(result).toEqual(['functionValue']);
|
|
});
|
|
});
|
|
|
|
describe('isObject', () => {
|
|
it('should correctly identify objects', () => {
|
|
const result = runTypeCheck(isObject);
|
|
expect(result).toEqual(['emptyObject', 'nonEmptyObject', 'dateValue']);
|
|
});
|
|
});
|
|
|
|
|
|
describe('printRow', () => {
|
|
// TODO: We should refactor the function to make lineDown a boolean not a number
|
|
it('should console.log the given string with the specified color and line breaks', () => {
|
|
const str = 'Test string';
|
|
const color = 'blue';
|
|
const lineDown = 1;
|
|
|
|
console.log = jest.fn();
|
|
|
|
printRow(str, { color, lineDown });
|
|
|
|
expect(console.log).toHaveBeenCalledTimes(3); // 1 for empty line, 1 for colored string, 1 for line break
|
|
expect(console.log.mock.calls[1][0]).toEqual(chalk[color].bold(str));
|
|
});
|
|
|
|
it('should not add a line break if lineDown is false', () => {
|
|
const str = 'Test string';
|
|
const color = 'green';
|
|
const lineDown = 0;
|
|
|
|
console.log = jest.fn();
|
|
|
|
printRow(str, { color, lineDown });
|
|
|
|
expect(console.log).toHaveBeenCalledTimes(2); // 1 for empty line, 1 for colored string
|
|
});
|
|
});
|
|
|
|
describe('printError', () => {
|
|
it('should print the given string in red', () => {
|
|
const str = 'Error message';
|
|
|
|
console.log = jest.fn();
|
|
|
|
printError(str);
|
|
|
|
expect(console.log).toHaveBeenCalledTimes(3); // 1 for empty line, 1 for red string, 1 for line break
|
|
expect(console.log.mock.calls[1][0]).toEqual(chalk.red.bold(str));
|
|
});
|
|
});
|
|
|
|
describe('log', () => {
|
|
it('should call console.log with the given arguments', () => {
|
|
const arg1 = 'Argument 1';
|
|
const arg2 = 'Argument 2';
|
|
|
|
console.log = jest.fn();
|
|
|
|
log(arg1, arg2);
|
|
|
|
expect(console.log).toHaveBeenCalledWith(arg1, arg2);
|
|
});
|
|
});
|
|
|
|
describe('ensureDir', () => {
|
|
it('should return true when the directory already exists', () => {
|
|
(fs.existsSync as jest.Mock).mockReturnValue(true);
|
|
|
|
const result = ensureDir('/path/to/file.txt');
|
|
expect(result).toBe(true);
|
|
expect(fs.existsSync).toHaveBeenCalledWith('/path/to');
|
|
expect(fs.mkdirSync).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should create the directory when it does not exist', () => {
|
|
(fs.existsSync as jest.Mock).mockReturnValueOnce(false).mockReturnValueOnce(true);
|
|
|
|
const result = ensureDir('/path/to/file.txt');
|
|
expect(result).toBe(true);
|
|
expect(fs.existsSync).toHaveBeenCalledWith('/path/to');
|
|
expect(fs.mkdirSync).toHaveBeenCalledWith('/path/to');
|
|
});
|
|
|
|
it('should create parent directories recursively when they do not exist', () => {
|
|
(fs.existsSync as jest.Mock)
|
|
.mockReturnValueOnce(false) // Check /path/to (does not exist)
|
|
.mockReturnValueOnce(false) // Check /path (does not exist)
|
|
.mockReturnValueOnce(true); // Check / (root, exists)
|
|
|
|
const result = ensureDir('/path/to/file.txt');
|
|
expect(result).toBe(true);
|
|
expect(fs.existsSync).toHaveBeenCalledTimes(3); // /path/to, /path, /
|
|
expect(fs.mkdirSync).toHaveBeenCalledTimes(2); // /path, /path/to
|
|
});
|
|
});
|
|
|
|
describe('normalizeJsonOpt', () => {
|
|
it('should return the object if the option is already an object', () => {
|
|
const opts = { babel: { presets: ['@babel/preset-env'] } };
|
|
const result = normalizeJsonOpt(opts, 'babel');
|
|
expect(result).toEqual(opts.babel);
|
|
});
|
|
|
|
it('should parse and return the object if the option is a valid JSON string', () => {
|
|
const opts = { babel: '{"presets":["@babel/preset-env"]}' };
|
|
const result = normalizeJsonOpt(opts, 'babel');
|
|
expect(result).toEqual({ presets: ['@babel/preset-env'] });
|
|
});
|
|
|
|
it('should return an empty object if the option is an invalid JSON string', () => {
|
|
const opts = { babel: '{"presets":["@babel/preset-env"]' }; // Invalid JSON
|
|
const result = normalizeJsonOpt(opts, 'babel');
|
|
expect(result).toEqual({});
|
|
});
|
|
|
|
it('should return an empty object if the option is not provided', () => {
|
|
const opts = {};
|
|
const result = normalizeJsonOpt(opts, 'babel');
|
|
expect(result).toEqual({});
|
|
});
|
|
});
|
|
|
|
describe('buildWebpackArgs', () => {
|
|
it('should return the options with normalized JSON options for babel and htmlWebpack', () => {
|
|
const opts = {
|
|
babel: '{"presets":["@babel/preset-env"]}',
|
|
htmlWebpack: '{"template":"./src/index.html"}',
|
|
otherOption: 'someValue'
|
|
};
|
|
|
|
const result = buildWebpackArgs(opts);
|
|
expect(result).toEqual({
|
|
babel: { presets: ['@babel/preset-env'] },
|
|
htmlWebpack: { template: './src/index.html' },
|
|
otherOption: 'someValue',
|
|
});
|
|
});
|
|
|
|
it('should return empty objects for babel and htmlWebpack if they are invalid JSON strings', () => {
|
|
const opts = {
|
|
babel: '{"presets":["@babel/preset-env"]', // Invalid JSON
|
|
htmlWebpack: '{"template":"./src/index.html', // Invalid JSON
|
|
};
|
|
|
|
const result = buildWebpackArgs(opts);
|
|
expect(result).toEqual({
|
|
babel: {},
|
|
htmlWebpack: {},
|
|
});
|
|
});
|
|
|
|
it('should return the original objects if babel and htmlWebpack are already objects', () => {
|
|
const opts = {
|
|
babel: { presets: ['@babel/preset-env'] },
|
|
htmlWebpack: { template: './src/index.html' },
|
|
};
|
|
|
|
const result = buildWebpackArgs(opts);
|
|
expect(result).toEqual({
|
|
babel: opts.babel,
|
|
htmlWebpack: opts.htmlWebpack,
|
|
});
|
|
});
|
|
|
|
it('should handle missing babel and htmlWebpack keys gracefully', () => {
|
|
const opts = { otherOption: 'someValue' };
|
|
|
|
const result = buildWebpackArgs(opts);
|
|
expect(result).toEqual({
|
|
babel: {},
|
|
htmlWebpack: {},
|
|
otherOption: 'someValue',
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('copyRecursiveSync', () => {
|
|
// TODO: Maybe this test case is a bit complex and we should think of an easier solution
|
|
it('should copy a directory and its contents recursively', () => {
|
|
/**
|
|
* First call: Mock as a directory with two files
|
|
* Subsequent calls: Mock as a file
|
|
*/
|
|
const existsSyncMock = (fs.existsSync as jest.Mock).mockReturnValue(true);
|
|
const statSyncMock = (fs.statSync as jest.Mock).mockReturnValueOnce({ isDirectory: () => true }).mockReturnValue({ isDirectory: () => false });
|
|
const readdirSyncMock = (fs.readdirSync as jest.Mock).mockReturnValueOnce(['file1.txt', 'file2.txt']).mockReturnValue([]);
|
|
const copyFileSyncMock = (fs.copyFileSync as jest.Mock).mockImplementation(() => { });
|
|
|
|
copyRecursiveSync('/src', '/dest');
|
|
|
|
expect(existsSyncMock).toHaveBeenCalledWith('/src');
|
|
expect(statSyncMock).toHaveBeenCalledWith('/src');
|
|
expect(fs.mkdirSync).toHaveBeenCalledWith('/dest');
|
|
expect(readdirSyncMock).toHaveBeenCalledWith('/src');
|
|
expect(copyFileSyncMock).toHaveBeenCalledWith(path.normalize('/src/file1.txt'), path.normalize('/dest/file1.txt'));
|
|
expect(copyFileSyncMock).toHaveBeenCalledWith(path.normalize('/src/file2.txt'), path.normalize('/dest/file2.txt'));
|
|
});
|
|
|
|
it('should copy a file when source is a file', () => {
|
|
(fs.existsSync as jest.Mock).mockReturnValue(true);
|
|
(fs.statSync as jest.Mock).mockReturnValue({ isDirectory: () => false });
|
|
|
|
copyRecursiveSync('/src/file.txt', '/dest/file.txt');
|
|
|
|
expect(fs.existsSync).toHaveBeenCalledWith('/src/file.txt');
|
|
expect(fs.statSync).toHaveBeenCalledWith('/src/file.txt');
|
|
expect(fs.copyFileSync).toHaveBeenCalledWith('/src/file.txt', '/dest/file.txt');
|
|
});
|
|
|
|
// Maybe we can change the behavior to throw an error if the `src` doesn't exist
|
|
it('should do nothing when source does not exist', () => {
|
|
(fs.existsSync as jest.Mock).mockReturnValue(false);
|
|
|
|
copyRecursiveSync('/src/file.txt', '/dest/file.txt');
|
|
|
|
expect(fs.existsSync).toHaveBeenCalledWith('/src/file.txt');
|
|
expect(fs.statSync).not.toHaveBeenCalled();
|
|
expect(fs.mkdirSync).not.toHaveBeenCalled();
|
|
expect(fs.copyFileSync).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe('rootResolve', () => {
|
|
it('should resolve a relative path to an absolute path', () => {
|
|
const result = rootResolve('src/index.js');
|
|
|
|
expect(result).toBe(path.join(process.cwd(), 'src/index.js'));
|
|
});
|
|
});
|
|
|
|
describe('originalRequire', () => {
|
|
it('should return the original require.resolve function', () => {
|
|
const originalRequireMock = jest.fn();
|
|
global.__non_webpack_require__ = originalRequireMock;
|
|
|
|
const result = originalRequire();
|
|
|
|
expect(result).toBe(originalRequireMock);
|
|
});
|
|
});
|
|
|
|
describe('resolve', () => {
|
|
it('should resolve a module path using the original require.resolve', () => {
|
|
const originalRequireMock = {
|
|
resolve: jest.fn().mockReturnValue('resolved/path'),
|
|
};
|
|
global.__non_webpack_require__ = originalRequireMock;
|
|
|
|
const result = resolve('my-module');
|
|
|
|
expect(result).toBe('resolved/path');
|
|
expect(originalRequireMock.resolve).toHaveBeenCalledWith('my-module');
|
|
});
|
|
});
|
|
|
|
describe('babelConfig', () => {
|
|
afterEach(() => {
|
|
jest.restoreAllMocks();
|
|
});
|
|
|
|
it('should return a Babel configuration object with specified presets and plugins', () => {
|
|
const result = babelConfig();
|
|
|
|
expect(result).toEqual({
|
|
presets: [
|
|
[resolve('@babel/preset-env'), { targets: undefined }],
|
|
],
|
|
plugins: [resolve('@babel/plugin-transform-runtime')],
|
|
});
|
|
});
|
|
|
|
it('should include the specified targets in the Babel configuration', () => {
|
|
const result = babelConfig({ targets: 'node 14' });
|
|
|
|
expect(result).toEqual({
|
|
presets: [
|
|
[resolve('@babel/preset-env'), { targets: 'node 14' }],
|
|
],
|
|
plugins: [resolve('@babel/plugin-transform-runtime')],
|
|
});
|
|
});
|
|
});
|
|
});
|