diff --git a/.changeset/slow-mayflies-cough.md b/.changeset/slow-mayflies-cough.md new file mode 100644 index 000000000..e0ad7f7a8 --- /dev/null +++ b/.changeset/slow-mayflies-cough.md @@ -0,0 +1,5 @@ +--- +'@faustwp/wordpress-plugin': patch +--- + +Improved plugin's process for handling blockset file uploads by leveraging WordPress' native [unzip_file](https://developer.wordpress.org/reference/functions/unzip_file/) function. \ No newline at end of file diff --git a/packages/faustwp-cli/src/blockset.ts b/packages/faustwp-cli/src/blockset.ts index b266badf2..b9dd4649e 100644 --- a/packages/faustwp-cli/src/blockset.ts +++ b/packages/faustwp-cli/src/blockset.ts @@ -7,19 +7,24 @@ import archiver from 'archiver'; import { spawnSync } from 'child_process'; import { getWpUrl, getWpSecret, hasYarn } from './utils/index.js'; -import { infoLog } from './stdout/index.js'; +import { debugLog } from './stdout/index.js'; + +// File paths used throughout the blockset process +export const ROOT_DIR = process.cwd(); +export const FAUST_DIR = path.join(ROOT_DIR, '.faust'); +export const FAUST_BUILD_DIR = path.join(FAUST_DIR, 'build'); +export const BLOCKS_DIR = path.join(FAUST_DIR, 'blocks'); +export const MANIFEST_PATH = path.join(BLOCKS_DIR, 'manifest.json'); -const ROOT_DIR = process.cwd(); -const FAUST_DIR = path.join(ROOT_DIR, '.faust'); -const FAUST_BUILD_DIR = path.join(FAUST_DIR, 'build'); -const BLOCKS_DIR = path.join(FAUST_DIR, 'blocks'); -const MANIFEST_PATH = path.join(BLOCKS_DIR, 'manifest.json'); const IGNORE_NODE_MODULES = '**/node_modules/**'; const FAUST_BLOCKS_SRC_DIR = 'wp-blocks'; -// Ensure required directories exist +// Ensure that the required directories for block processing exist fs.ensureDirSync(BLOCKS_DIR); +/** + * Represents the structure of the manifest file. + */ export type Manifest = { blocks: any[]; timestamp: string; @@ -30,10 +35,54 @@ const manifest: Manifest = { timestamp: new Date().toISOString(), }; +/** + * Interface representing the structure of the parsed PHP asset file. + */ +export interface PhpAsset { + dependencies?: string[]; + version?: string; +} + +/** + * Parses a PHP asset file content and converts it into a JSON object. + * + * @param {string} phpContent - The content of the PHP asset file. + * @returns {PhpAsset} - A JSON object representing the parsed content. + */ +export function parsePhpAssetFile(phpContent: string): PhpAsset { + const jsonObject: PhpAsset = {}; + + // Match the PHP array structure + const matches = /return\s+array\(([^;]+)\);/.exec(phpContent); + if (!matches || matches.length < 2) { + console.error('Error: Unable to parse PHP file.'); + return {}; + } + + // Extract dependencies if present + const dependenciesMatch = matches[1].match( + /'dependencies'\s*=>\s*array\(([^)]+)\)/, + ); + if (dependenciesMatch) { + jsonObject.dependencies = dependenciesMatch[1] + .split(',') + .map((dep) => dep.trim().replace(/'/g, '')); + } + + // Extract version if present + const versionMatch = matches[1].match(/'version'\s*=>\s*'([^']+)'/); + if (versionMatch) { + const [, version] = versionMatch; // destructures versionMatch and skips the first element (which is the full match of the regex), directly assigning the second element (the captured group) to the variable version. + jsonObject.version = version; + } + + return jsonObject; +} + /** * Fetches paths to all block.json files while ignoring node_modules. * - * @returns {Promise} An array of paths to block.json files. + * @returns {Promise} - An array of paths to block.json files. */ export async function fetchBlockFiles(): Promise { return glob(`${FAUST_BUILD_DIR}/**/block.json`, { @@ -42,32 +91,56 @@ export async function fetchBlockFiles(): Promise { } /** - * Processes each block.json file, copying its directory and updating the manifest. + * Processes each block.json file by copying its directory, updating the manifest, + * and handling PHP files. * * @param {string[]} files - An array of paths to block.json files. * @returns {Promise} */ export async function processBlockFiles(files: string[]): Promise { await fs.emptyDir(BLOCKS_DIR); - // Use Promise.all and map instead of for...of loop - await Promise.all( - files.map(async (filePath) => { - const blockDir = path.dirname(filePath); - const blockName = path.basename(blockDir); - const destDir = path.join(BLOCKS_DIR, blockName); - - await fs.copy(blockDir, destDir); - - const blockJson = await fs.readJson(filePath); - manifest.blocks.push(blockJson); - }), - ); + + const fileProcessingPromises = files.map(async (filePath) => { + const blockDir = path.dirname(filePath); + const blockName = path.basename(blockDir); + const destDir = path.join(BLOCKS_DIR, blockName); + + await fs.copy(blockDir, destDir); + + if (path.extname(filePath) === '.json') { + try { + const blockJson = await fs.readJson(filePath); + manifest.blocks.push(blockJson); + } catch (error) { + console.error(`Error reading JSON file: ${filePath}`, error); + } + } + + // Handle PHP asset file + const phpAssetPath = path.join(blockDir, 'index.asset.php'); + if (await fs.pathExists(phpAssetPath)) { + const phpContent = await fs.readFile(phpAssetPath, 'utf8'); + const assetData = parsePhpAssetFile(phpContent); + await fs.writeJson(path.join(destDir, 'index.asset.json'), assetData, { + spaces: 2, + }); + await fs.remove(phpAssetPath); + } + + // Remove any other PHP files + const phpFiles = await glob(`${destDir}/**/*.php`, { + ignore: IGNORE_NODE_MODULES, + }); + await Promise.all(phpFiles.map((file) => fs.remove(file))); + }); + + await Promise.all(fileProcessingPromises); } /** * Creates a ZIP archive of the blocks. * - * @returns {Promise} Path to the created ZIP archive. + * @returns {Promise} - Path to the created ZIP archive. */ export async function createZipArchive(): Promise { const zipPath = path.join(FAUST_DIR, 'blocks.zip'); @@ -114,7 +187,7 @@ export async function uploadToWordPress(zipPath: string): Promise { } try { - infoLog('WordPress:', await response.json()); + console.log(await response.json()); } catch (jsonError) { if (jsonError instanceof Error) { throw new Error('Error parsing response from WordPress.'); @@ -133,12 +206,12 @@ export async function uploadToWordPress(zipPath: string): Promise { } /** - * Compiles the blocks and places them into the faust build dir. + * Compiles the blocks and places them into the FAUST build directory. * * @returns {Promise} */ export async function compileBlocks(): Promise { - infoLog(`Faust: Compiling Blocks into ${FAUST_BUILD_DIR}`); + debugLog(`Faust: Compiling Blocks into ${FAUST_BUILD_DIR}`); await fs.emptyDir(FAUST_BUILD_DIR); const command = hasYarn() ? 'yarn' : 'npm'; let args = ['exec', 'wp-scripts', 'start', '--package=@wordpress/scripts']; diff --git a/packages/faustwp-cli/tests/blockset/blockset.test.ts b/packages/faustwp-cli/tests/blockset/blockset.test.ts index ebedf24e0..ad690e903 100644 --- a/packages/faustwp-cli/tests/blockset/blockset.test.ts +++ b/packages/faustwp-cli/tests/blockset/blockset.test.ts @@ -6,7 +6,15 @@ jest.mock('../../src/utils/hasYarn.js', () => ({ hasYarn: jest.fn().mockReturnValueOnce(true).mockReturnValueOnce(false), })); -import { compileBlocks } from '../../src/blockset'; +import { + compileBlocks, + parsePhpAssetFile, + processBlockFiles, + BLOCKS_DIR, + FAUST_DIR +} from '../../src/blockset'; +import fs from 'fs-extra'; +import path from 'path'; const spawnSyncMock = spawnSync as unknown as jest.Mock< Partial> @@ -62,4 +70,123 @@ describe('blockset command', () => { ); }); }); + + describe('PHP file processing', () => { + const mockSourceDir = path.join(__dirname, 'mockSourceDir'); + const mockPhpFilePath = path.join(mockSourceDir, 'index.asset.php'); + + const mockPhpContent = ` + array( + 'react', + 'wp-block-editor', + 'wp-blocks', + 'wp-i18n', + 'wp-components', + 'wp-hooks' + ), + 'version' => '00000000000000001234' +); +`; + + const expectedJson = { + dependencies: [ + 'react', + 'wp-block-editor', + 'wp-blocks', + 'wp-i18n', + 'wp-components', + 'wp-hooks', + ], + version: '00000000000000001234', + }; + + beforeAll(async () => { + await fs.ensureDir(mockSourceDir); + await fs.writeFile(mockPhpFilePath, mockPhpContent); + }); + + afterAll(async () => { + await fs.remove(mockSourceDir); + await fs.remove(FAUST_DIR); + }); + + it('should convert PHP file to JSON and remove the original PHP file', async () => { + await processBlockFiles([mockPhpFilePath]); + + // Use the BLOCKS_DIR for locating the JSON file + const blockName = path.basename(path.dirname(mockPhpFilePath)); + const jsonFilePath = path.join(BLOCKS_DIR, blockName, 'index.asset.json'); + expect(await fs.pathExists(jsonFilePath)).toBeTruthy(); + + // Check JSON file content + const jsonContent = await fs.readJson(jsonFilePath); + expect(jsonContent).toEqual(expectedJson); + + // Check PHP file removal + expect(await fs.pathExists(mockPhpFilePath)).toBeFalsy(); + }); + }); + + // Test with correctly formatted PHP content + it('correctly parses valid PHP content', () => { + const validPhpContent = ` + array( + 'react', + 'wp-block-editor' + ), + 'version' => '1.0.0' + ); + `; + const expectedJson = { + dependencies: ['react', 'wp-block-editor'], + version: '1.0.0' + }; + expect(parsePhpAssetFile(validPhpContent)).toEqual(expectedJson); + }); + + it('returns an empty object for invalid PHP content', () => { + const invalidPhpContent = ``; + expect(parsePhpAssetFile(invalidPhpContent)).toEqual({}); + }); + + it('returns an empty object for empty PHP content', () => { + const emptyPhpContent = ''; + expect(parsePhpAssetFile(emptyPhpContent)).toEqual({}); + }); + + it('handles missing dependencies', () => { + const missingDependencies = ` + '1.0.0' + ); + `; + expect(parsePhpAssetFile(missingDependencies)).toEqual({ version: '1.0.0' }); + }); + + it('handles missing version', () => { + const missingVersion = ` + array('react') + ); + `; + expect(parsePhpAssetFile(missingVersion)).toEqual({ dependencies: ['react'] }); + }); + + it('parses content with extra whitespace and different formatting', () => { + const formattedPhpContent = ` + array( 'react', 'wp-editor' ), 'version' => '2.0.0' ); + `; + const expectedJson = { + dependencies: ['react', 'wp-editor'], + version: '2.0.0' + }; + expect(parsePhpAssetFile(formattedPhpContent)).toEqual(expectedJson); + }); }); diff --git a/plugins/faustwp/composer.json b/plugins/faustwp/composer.json index eff1d087d..d631309d6 100644 --- a/plugins/faustwp/composer.json +++ b/plugins/faustwp/composer.json @@ -4,6 +4,7 @@ "type": "project", "minimum-stability": "stable", "require-dev": { + "antecedent/patchwork": "^2.1", "brain/monkey": "^2.6", "codeception/codeception": "^4.1", "codeception/module-asserts": "^1.0", diff --git a/plugins/faustwp/composer.lock b/plugins/faustwp/composer.lock index 2eba69bfd..1347079f0 100644 --- a/plugins/faustwp/composer.lock +++ b/plugins/faustwp/composer.lock @@ -4,21 +4,21 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "1340e8cfcb481303a155f4640576bfc9", + "content-hash": "ffdfd37544d341b7675f2abebf2ed1d5", "packages": [], "packages-dev": [ { "name": "antecedent/patchwork", - "version": "2.1.21", + "version": "2.1.26", "source": { "type": "git", "url": "https://github.com/antecedent/patchwork.git", - "reference": "25c1fa0cd9a6e6d0d13863d8df8f050b6733f16d" + "reference": "f2dae0851b2eae4c51969af740fdd0356d7f8f55" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/antecedent/patchwork/zipball/25c1fa0cd9a6e6d0d13863d8df8f050b6733f16d", - "reference": "25c1fa0cd9a6e6d0d13863d8df8f050b6733f16d", + "url": "https://api.github.com/repos/antecedent/patchwork/zipball/f2dae0851b2eae4c51969af740fdd0356d7f8f55", + "reference": "f2dae0851b2eae4c51969af740fdd0356d7f8f55", "shasum": "" }, "require": { @@ -51,9 +51,9 @@ ], "support": { "issues": "https://github.com/antecedent/patchwork/issues", - "source": "https://github.com/antecedent/patchwork/tree/2.1.21" + "source": "https://github.com/antecedent/patchwork/tree/2.1.26" }, - "time": "2022-02-07T07:28:34+00:00" + "time": "2023-09-18T08:18:37+00:00" }, { "name": "behat/gherkin", diff --git a/plugins/faustwp/includes/blocks/callbacks.php b/plugins/faustwp/includes/blocks/callbacks.php index 10c475ae6..b3496e8c9 100644 --- a/plugins/faustwp/includes/blocks/callbacks.php +++ b/plugins/faustwp/includes/blocks/callbacks.php @@ -13,7 +13,7 @@ add_action( 'init', __NAMESPACE__ . '\\register_custom_blocks' ); /** - * Register Gutenberg blocks from block.json files located in the specified paths. + * Register Gutenberg blocks from block.json and index.asset.json files located in the specified paths. */ function register_custom_blocks() { static $initialized = false; @@ -38,12 +38,46 @@ function register_custom_blocks() { $block_dirs = array_filter( glob( $base_dir . '*' ), 'is_dir' ); foreach ( $block_dirs as $dir ) { - // Path to the block.json file. $metadata_file = trailingslashit( $dir ) . 'block.json'; + $asset_file = trailingslashit( $dir ) . 'index.asset.json'; - // Check if block.json exists and register the block. if ( file_exists( $metadata_file ) ) { - register_block_type( $metadata_file ); + // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents + $block_metadata = json_decode( file_get_contents( $metadata_file ), true ); + // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents + $asset_data = file_exists( $asset_file ) ? json_decode( file_get_contents( $asset_file ), true ) : array(); + $block_name = basename( $dir ); + + $dependencies = $asset_data['dependencies'] ?? array(); + $version = $asset_data['version'] ?? ''; + + $block_args = array(); + + // Register editor script. + if ( isset( $block_metadata['editorScript'] ) ) { + $editor_script_handle = register_block_asset( $block_metadata, 'editorScript', $block_name, $dependencies, $version ); + if ( $editor_script_handle ) { + $block_args['editor_script'] = $editor_script_handle; + } + } + + // Register editor style. + if ( isset( $block_metadata['editorStyle'] ) ) { + $editor_style_handle = register_block_asset( $block_metadata, 'editorStyle', $block_name, array(), $version ); + if ( $editor_style_handle ) { + $block_args['editor_style'] = $editor_style_handle; + } + } + + // Register style. + if ( isset( $block_metadata['style'] ) ) { + $style_handle = register_block_asset( $block_metadata, 'style', $block_name, array(), $version ); + if ( $style_handle ) { + $block_args['style'] = $style_handle; + } + } + + register_block_type( $metadata_file, $block_args ); } } diff --git a/plugins/faustwp/includes/blocks/functions.php b/plugins/faustwp/includes/blocks/functions.php index f5a84605e..3f1037d67 100644 --- a/plugins/faustwp/includes/blocks/functions.php +++ b/plugins/faustwp/includes/blocks/functions.php @@ -1,70 +1,200 @@ is_readable( $file['tmp_name'] ) ) { + return new WP_Error( 'file_read_error', esc_html__( 'Uploaded file is not readable', 'faustwp' ) ); } - // Define directories. + return true; +} + +/** + * Defines and returns necessary directories for file processing. + * + * @return array + */ +function define_directories() { $upload_dir = wp_upload_dir(); - $target_dir = trailingslashit( $upload_dir['basedir'] ) . 'faustwp/'; - $blocks_dir = $target_dir . 'blocks/'; - $tmp_dir = $target_dir . 'tmp_blocks/'; + $base_dir = trailingslashit( $upload_dir['basedir'] ) . trailingslashit( FAUSTWP_SLUG ); + + return array( + 'target' => $base_dir . 'blocks', + 'temp' => $base_dir . 'tmp_blocks', + ); +} - // Ensure temporary directory exists. - if ( ! file_exists( $tmp_dir ) && ! wp_mkdir_p( $tmp_dir ) ) { - return new \WP_Error( 'mkdir_error', __( 'Could not create temporary directory', 'faustwp' ) ); +/** + * Ensures that the necessary directories exist. + * + * @param array $dirs Directories array. + * @return WP_Error|true + */ +function ensure_directories_exist( $dirs ) { + foreach ( $dirs as $dir ) { + if ( ! wp_mkdir_p( $dir ) ) { + /* translators: %s: directory path */ + return new WP_Error( 'mkdir_error', sprintf( esc_html__( 'Could not create directory: %s', 'faustwp' ), $dir ) ); + } } - // Move the uploaded file. - $target_file = $target_dir . sanitize_file_name( basename( $file['name'] ) ); - if ( ! move_uploaded_file( $file['tmp_name'], $target_file ) ) { - return new \WP_Error( 'move_error', __( 'Could not move uploaded file', 'faustwp' ) ); + return true; +} + +/** + * Moves the uploaded file to the target directory. + * + * @param WP_Filesystem_Base $wp_filesystem Filesystem object. + * @param array $file The uploaded file details. + * @param string $target_file The target file path. + * @return WP_Error|bool True on success, WP_Error on failure. + */ +function move_uploaded_file( $wp_filesystem, $file, $target_file ) { + if ( ! $wp_filesystem->move( $file['tmp_name'], $target_file, true ) ) { + return new WP_Error( 'move_error', esc_html__( 'Could not move uploaded file', 'faustwp' ) ); } + return true; +} - // Unzip the file to the temporary directory. - if ( ! unzip_to_directory( $target_file, $tmp_dir ) ) { - rrmdir( $tmp_dir ); // Cleanup the temporary directory in case of unzip failure. - return new \WP_Error( 'unzip_error', __( 'Could not unzip the file', 'faustwp' ) ); +/** + * Unzips the uploaded file. + * + * @param string $target_file The target file path. + * @param string $destination The destination directory for unzipping. + * @return WP_Error|bool True on success, WP_Error on failure. + */ +function unzip_uploaded_file( $target_file, $destination ) { + $unzip_result = unzip_file( $target_file, $destination ); + if ( is_wp_error( $unzip_result ) ) { + return $unzip_result; } + return true; +} - // Replace the old blocks directory with the new content. - if ( is_dir( $blocks_dir ) ) { - rrmdir( $blocks_dir ); +/** + * Cleans up temporary files or directories. + * + * @param WP_Filesystem_Base $wp_filesystem Filesystem object. + * @param string $temp_dir The temporary directory path. + * @return void + */ +function cleanup_temp_directory( $wp_filesystem, $temp_dir ) { + if ( $wp_filesystem->is_dir( $temp_dir ) ) { + $wp_filesystem->delete( $temp_dir, true ); } +} - if ( ! rename( $tmp_dir, $blocks_dir ) ) { - return new \WP_Error( 'rename_error', __( 'Could not rename the directory', 'faustwp' ) ); +/** + * Registers a block asset (script or style) if the file exists. + * + * This function checks for the existence of the asset file based on the provided metadata + * and field name, and then registers the asset with WordPress if the file is found. + * + * @param array $metadata Block metadata, typically from block.json. + * @param string $field_name Asset type field name (e.g., editorScript, editorStyle). + * @param string $block_name Unique name of the block. + * @param array $dependencies Array of script or style dependencies. + * @param string $version Version string for the asset. + * @return string|false Registered handle on success, false on failure. + */ +function register_block_asset( $metadata, $field_name, $block_name, $dependencies, $version ) { + // Ensure that the asset path is set in the metadata. + if ( empty( $metadata[ $field_name ] ) ) { + return false; } - return true; + // Process the asset path and construct the full URL. + $processed_asset_path = remove_block_asset_path_prefix( $metadata[ $field_name ] ); + $full_url = trailingslashit( wp_upload_dir()['baseurl'] ) . 'faustwp/blocks/' . $block_name . '/' . ltrim( $processed_asset_path, '/' ); + + // Construct the file system path to check for file existence. + $file_system_path = trailingslashit( wp_upload_dir()['basedir'] ) . 'faustwp/blocks/' . $block_name . '/' . ltrim( $processed_asset_path, '/' ); + + // Check if the asset file exists in the file system. + if ( ! file_exists( $file_system_path ) ) { + return false; + } + + // Generate a handle and register the asset. + $handle = $block_name . '-' . strtolower( $field_name ); + if ( strpos( strtolower( $field_name ), 'script' ) !== false ) { + wp_register_script( $handle, $full_url, $dependencies, $version, true ); + } elseif ( strpos( strtolower( $field_name ), 'style' ) !== false ) { + wp_register_style( $handle, $full_url, $dependencies, $version ); + } else { + return false; + } + + return $handle; } diff --git a/plugins/faustwp/includes/rest/callbacks.php b/plugins/faustwp/includes/rest/callbacks.php index cede43dd8..d7cb861f7 100644 --- a/plugins/faustwp/includes/rest/callbacks.php +++ b/plugins/faustwp/includes/rest/callbacks.php @@ -148,7 +148,14 @@ function handle_blockset_callback( \WP_REST_Request $request ) { return $result; } - return new \WP_REST_Response( __( 'Blockset sync complete.', 'faustwp' ), 200 ); + return new \WP_REST_Response( + sprintf( + /* Translators: %s is replaced with the emoji indicating a successful sync. */ + esc_html__( '%s Blockset sync complete!', 'faustwp' ), + '✅' + ), + 200 + ); } /** diff --git a/plugins/faustwp/includes/utilities/functions.php b/plugins/faustwp/includes/utilities/functions.php index 8b5ec8cf2..0cc189b8e 100644 --- a/plugins/faustwp/includes/utilities/functions.php +++ b/plugins/faustwp/includes/utilities/functions.php @@ -43,49 +43,3 @@ function plugin_version() { return $plugin['Version']; } - -/** - * Unzip a file to a specified directory. - * - * @param string $file_path Path to the zip file. - * @param string $destination Directory to unzip to. - * @return bool True on success, false on failure. - */ -function unzip_to_directory( $file_path, $destination ) { - $zip = new \ZipArchive(); - if ( true !== $zip->open( $file_path ) ) { - return false; - } - - $zip->extractTo( $destination ); - $zip->close(); - unlink( $file_path ); // Delete the zip file. - - return true; -} - -/** - * Recursive function to remove a directory and its contents. - * - * @param string $dir Directory path. - */ -function rrmdir( $dir ) { - if ( ! is_dir( $dir ) ) { - return; - } - - $objects = scandir( $dir ); - foreach ( $objects as $object ) { - if ( '.' === $object || '..' === $object ) { - continue; - } - - $item_path = $dir . '/' . $object; - if ( is_dir( $item_path ) ) { - rrmdir( $item_path ); - } else { - unlink( $item_path ); - } - } - rmdir( $dir ); -} diff --git a/plugins/faustwp/phpunit-multisite.xml.dist b/plugins/faustwp/phpunit-multisite.xml.dist index 5f3bb6677..ebcbdc0a9 100644 --- a/plugins/faustwp/phpunit-multisite.xml.dist +++ b/plugins/faustwp/phpunit-multisite.xml.dist @@ -11,7 +11,8 @@ xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd" > - + + diff --git a/plugins/faustwp/phpunit.xml.dist b/plugins/faustwp/phpunit.xml.dist index 10e26df8b..14b76e3d9 100644 --- a/plugins/faustwp/phpunit.xml.dist +++ b/plugins/faustwp/phpunit.xml.dist @@ -10,6 +10,9 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd" > + + + ./includes diff --git a/plugins/faustwp/tests/bootstrap.php b/plugins/faustwp/tests/bootstrap.php index 5b67f2105..939e5498e 100644 --- a/plugins/faustwp/tests/bootstrap.php +++ b/plugins/faustwp/tests/bootstrap.php @@ -6,6 +6,7 @@ */ $_tests_dir = getenv( 'WP_TESTS_DIR' ); +$_load_patchwork = getenv( 'LOAD_PATCHWORK' ); define( 'WP_TEST_PLUGINS_DIR', '/var/www/html/wp-content/plugins' ); @@ -18,6 +19,10 @@ exit( 1 ); } +if ( $_load_patchwork ) { + require_once __DIR__ . '/../vendor/antecedent/patchwork/Patchwork.php'; +} + require_once $_tests_dir . '/includes/functions.php'; diff --git a/plugins/faustwp/tests/integration/UtilitiesFunctionsTests.php b/plugins/faustwp/tests/integration/UtilitiesFunctionsTests.php index 68ca77bec..0409401fa 100644 --- a/plugins/faustwp/tests/integration/UtilitiesFunctionsTests.php +++ b/plugins/faustwp/tests/integration/UtilitiesFunctionsTests.php @@ -12,43 +12,18 @@ /** * Class UtilitiesTest */ -class UtilitiesTest extends \WP_UnitTestCase { - /** - * Path to the test directory. - * - * @var string - */ - private $testDir; - - /** - * Path to the test zip file. - * - * @var string - */ - private $testZip; - +class UtilitiesFunctionsTests extends \WP_UnitTestCase { /** * Setup runs before every test. */ protected function setUp(): void { parent::setUp(); - - $this->testDir = sys_get_temp_dir() . '/faustwp_test_directory'; - $this->testZip = sys_get_temp_dir() . '/test.zip'; } /** * Cleanup runs after every test. */ protected function tearDown(): void { - if ( is_dir( $this->testDir ) ) { - Utilities\rrmdir( $this->testDir ); - } - - if ( file_exists( $this->testZip ) ) { - unlink( $this->testZip ); - } - parent::tearDown(); } @@ -70,38 +45,4 @@ public function testPluginVersion() { // This test assumes FAUSTWP_FILE is defined correctly. $this->assertIsString( Utilities\plugin_version() ); } - - /** - * Test the unzip_to_directory function. - */ - public function testUnzipToDirectory() { - // Create a dummy zip file for testing. - $zip = new \ZipArchive(); - $zip->open( $this->testZip, \ZipArchive::CREATE ); - $zip->addFromString( 'testfile.txt', 'Test content' ); - $zip->close(); - - $this->assertTrue( Utilities\unzip_to_directory( $this->testZip, $this->testDir ) ); - $this->assertFileExists( $this->testDir . '/testfile.txt' ); - $this->assertFalse( file_exists( $this->testZip ) ); - - // Test non-existent file. - $this->assertFalse( Utilities\unzip_to_directory( 'nonexistent.zip', $this->testDir ) ); - } - - /** - * Test the rrmdir function. - */ - public function testRrmdir() { - mkdir( $this->testDir . '/subdir', 0777, true ); - touch( $this->testDir . '/file.txt' ); - touch( $this->testDir . '/subdir/file2.txt' ); - - Utilities\rrmdir( $this->testDir ); - $this->assertFalse( is_dir( $this->testDir ) ); - - // Test rrmdir on non-existent directory. - Utilities\rrmdir( $this->testDir ); - $this->assertFalse( is_dir( $this->testDir ) ); - } } diff --git a/plugins/faustwp/tests/unit/BlockFunctionTests.php b/plugins/faustwp/tests/unit/BlockFunctionTests.php new file mode 100644 index 000000000..8b3139a63 --- /dev/null +++ b/plugins/faustwp/tests/unit/BlockFunctionTests.php @@ -0,0 +1,139 @@ + 'test.zip', + 'type' => 'application/zip', + 'tmp_name' => '/tmp/test.zip' + ]; + $dirs = [ + 'target' => '/path/to/target', + 'temp' => '/path/to/temp' + ]; + + stubs([ + 'WPE\FaustWP\Blocks\validate_uploaded_file' => true, + 'WPE\FaustWP\Blocks\define_directories' => $dirs, + 'WPE\FaustWP\Blocks\ensure_directories_exist' => true, + 'WPE\FaustWP\Blocks\process_and_replace_blocks' => true, + ]); + + $this->assertTrue( Blocks\handle_uploaded_blockset( $file ) ); + } + + /** + * Test handle_uploaded_blockset with an invalid file type. + */ + public function test_handle_uploaded_blockset_with_invalid_file_type() { + $file = [ + 'name' => 'test.txt', + 'type' => 'text/plain', + 'tmp_name' => '/tmp/test.txt' + ]; + + stubs([ + 'WPE\FaustWP\Blocks\validate_uploaded_file' => function() { + return new WP_Error( 'wrong_type', 'Not a zip file' ); + } + ]); + + $result = Blocks\handle_uploaded_blockset( $file ); + $this->assertInstanceOf( WP_Error::class, $result ); + $this->assertEquals( 'wrong_type', $result->get_error_code() ); + } + + /** + * Test validate_uploaded_file with a valid zip file. + */ + public function test_validate_uploaded_file_with_valid_zip() { + $file = [ + 'type' => 'application/zip', + 'tmp_name' => '/tmp/test.zip' + ]; + + $filesystem = Mockery::mock( WP_Filesystem_Base::class ); + $filesystem->shouldReceive( 'is_readable' )->with( $file['tmp_name'] )->andReturn( true ); + + $this->assertTrue( Blocks\validate_uploaded_file( $filesystem, $file ) ); + } + + /** + * Test validate_uploaded_file with an invalid file type. + */ + public function test_validate_uploaded_file_with_invalid_type() { + $file = [ + 'type' => 'text/plain', + 'tmp_name' => '/tmp/test.txt' + ]; + + $filesystem = Mockery::mock( WP_Filesystem_Base::class ); + + $result = Blocks\validate_uploaded_file( $filesystem, $file ); + $this->assertInstanceOf( WP_Error::class, $result ); + $this->assertEquals( 'wrong_type', $result->get_error_code() ); + } + + /** + * Test validate_uploaded_file with a non-readable file. + */ + public function test_validate_uploaded_file_with_non_readable_file() { + $file = [ + 'type' => 'application/zip', + 'tmp_name' => '/tmp/test.zip' + ]; + + $filesystem = Mockery::mock( WP_Filesystem_Base::class ); + $filesystem->shouldReceive( 'is_readable' )->with( $file['tmp_name'] )->andReturn( false ); + + $result = Blocks\validate_uploaded_file( $filesystem, $file ); + $this->assertInstanceOf( WP_Error::class, $result ); + $this->assertEquals( 'file_read_error', $result->get_error_code() ); + } + + /** + * Test define_directories to ensure it returns correct paths. + */ + public function test_define_directories() { + $dirs = Blocks\define_directories(); + + $this->assertIsArray( $dirs ); + $this->assertArrayHasKey( 'target', $dirs ); + $this->assertArrayHasKey( 'temp', $dirs ); + } + + /** + * Test ensure_directories_exist for existing directories. + */ + public function test_ensure_directories_exist() { + $dirs = Blocks\define_directories(); + + $filesystem = Mockery::mock( 'WP_Filesystem_Base' ); + $filesystem->shouldReceive( 'is_dir' )->andReturn( true ); + $filesystem->shouldReceive( 'mkdir' )->andReturn( true ); + + $this->assertTrue( Blocks\ensure_directories_exist( $dirs ) ); + } + +}