You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Whilst working with Yeoman to generate a project I had an issue when trying to write tests for it.
The generator makes one or more requests to download a few .zip files, extracts them and copies them to a directory. This can take a variable amount of time and when I write a test to assert file presence what ends up happening is that the assertions occur while the files are still being downloaded.
The code of my generator is:
"use strict";// Required for generator to workconstGenerator=require("yeoman-generator");constchalk=require("chalk");constyosay=require("yosay");// Used to download and unzip files// const https = require('https');consthttps=require("follow-redirects/https");constfs=require("fs");constAdmZip=require("adm-zip");constfse=require("fs-extra");// Used to convey loading states in the terminal (loading, downloading...)constMultispinner=require("multispinner");module.exports=classextendsGenerator{prompting(){// Have Yeoman greet the user.this.log(yosay(`The ${chalk.blue("hozokit")} theme generator.`));constprompts=[{type: "input",name: "projectName",message: "What is your project name?",default: "hozokit"// Default to current folder name},{type: "confirm",name: "installWordpress",message: "Would you like Wordpress to be installed?",default: true}];returnthis.prompt(prompts).then(props=>{// To access props later use this.props.someAnswer;this.props=props;this.props.projectFolderName=this._dashify(this.props.projectName);});}writing(){// Installs Wordpress and Hozokitif(this.props.installWordpress){this._installWordpress(this._dashify(this.props.projectFolderName));}else{// Installs Hozokitthis._installHozokit(this._dashify(this.props.projectFolderName));}}/** * Downloads and installs the latest version of Wordpress in the project root directory. * This functionality should be optional and a Hozokit project should still be able to be generated whether or not this function runs. * @param {String} projectName Name of the project, used to name root folder. e.g 'hozokit' or this.props.projectName */_installWordpress(projectName){// Creates the project directory if one is not already in place.this._createProjectDirectory(projectName);constspinners=["Downloading Wordpress"];constm=newMultispinner(spinners);// Downloads a zipped copy of Wordpress into the folder.constzipPath=`./${projectName}/wordpress.zip`;constfile=fs.createWriteStream(zipPath);constdownloadURL="https://wordpress.org/latest.zip";// This message is shown later to the user if any issues with the download come up.letdownloadError=null;https.get(downloadURL,function(response){response.pipe(file);// Use to add logic for when a request is in progress.// response.on('data', (data) => {// });response.on("end",()=>{if(response.statusCode===200){m.success(spinners[0]);}else{downloadError=`Download has failed. (${response.statusCode})`;m.error(spinners[0]);}});}).on("error",error=>{downloadError=error;m.error(spinners[0]);});// Displays a message once download is complete.m.on("success",()=>{// This.log(`${chalk.green('Success:')} Download of Wordpress has completed.`);// this._extractWordpress(projectName)this._extractZip(projectName,"wordpress.zip",`./${projectName}/`,"Extracting Wordpress");this._installHozokit(this._dashify(this.props.projectFolderName));}).on("err",error=>{if(downloadError){this.log(`${chalk.red("Error:")}${downloadError}`);}else{this.log(`${chalk.red("Error:")}${error} Download has been cancelled with an unknown error.`);}});}/** * Downloads and installs the latest version of Hozokit in the project root directory. * @param {String} projectName Name of the project, used to name root folder. e.g 'hozokit' or this.props.projectName */_installHozokit(projectName){// Creates the project directory if one is not already in place.this._createProjectDirectory(projectName);// Generates progress spinners for user to see on the terminal.constspinners=["Looking up latest Hozokit release","Downloading Hozokit"];constm=newMultispinner(spinners);// Downloads a zipped copy of Wordpress into the folder.constzipPath=`./${projectName}/hozokit-main.zip`;constfile=fs.createWriteStream(zipPath);// Getting data on the URL is necessary to be passed into the request options.constreleaseUrl=newURL("https://api.github.com/repos/csalmeida/hozokit/releases/latest");// Data on the latest hozokit release.lethozokit=null;// This message is shown later to the user if any issues with the download come up.letdownloadError=null;// Retrieves information on the latest available release of Hozokit.// User-Agent is required for GitHub to take request.constoptions={host: releaseUrl.host,path: releaseUrl.pathname,headers: {"User-Agent": "Hozokit Generator v0.0"}};https.get(options,function(response){// Stores the response body for later use.letbody="";response.on("data",function(chunk){body+=chunk;});response.on("end",()=>{if(response.statusCode===200){hozokit=JSON.parse(body);m.success(spinners[0]);if(hozokit){constdownloadUrl=newURL(hozokit.zipball_url);letoptions={host: downloadUrl.host,path: downloadUrl.pathname,headers: {"User-Agent": "Hozokit Generator v0.0"}};// Downloads the zip file of the latest release from Github.https.get(options,function(response){response.pipe(file);response.on("end",()=>{if(response.statusCode===200){m.success(spinners[1]);}else{downloadError=`Download has failed. (${response.statusCode})`;m.error(spinners[1]);}});}).on("error",error=>{downloadError=error;m.error(spinners[1]);});}}else{downloadError=`Request has failed. (${response.statusCode})`;m.error(spinners[0]);}});}).on("error",error=>{downloadError=error;m.error(spinners[0]);});// Displays a message once download is complete.m.on("success",()=>{this._extractZip(projectName,"hozokit-main.zip",`./${projectName}/`,`Extracting Hozokit ${hozokit.name}`);}).on("err",error=>{if(downloadError){this.log(`${chalk.red("Error:")}${downloadError}`);}else{this.log(`${chalk.red("Error:")}${error} Download has been cancelled with an unknown error.`);}});}/** * Extracts zip archives into a folder and moves them to a desired location. * Original zip and created folder are removed after uncompressing. * Used when installing Wordpress and Hozokit. * @param {String} projectName Name of the project, used to name root folder. e.g 'hozokit' or this.props.projectName * @param {String} fileZipName The name of the zip to be extracted. e.g 'hozokit-main.zip' * @param {String} copyPath (optional) The target path files should be copied to. This is a move since files are removed after extraction. Defaults to project directory. e.g './project-name' * @param {String} spinnerText (optional) The message shown whilst the spinner is in progress. */_extractZip(projectName,fileZipName,copyPath=null,spinnerText="Extracting"){constspinners=[spinnerText];constm=newMultispinner(spinners);// Extracts contents of Wordpress.constextractPath=`./${projectName}/${fileZipName}`;constzip=newAdmZip(extractPath);// Makes use of the entries to figure out which folder name was created when file was extracted.constzipEntries=zip.getEntries();constextractedFolder=`./${projectName}/${zipEntries[0].entryName}`;zip.extractAllTo(`${projectName}/`,true);letextractError=null;// If a copy path is not provided files won't be moved.if(copyPath){fse.copy(extractedFolder,copyPath,{overwrite: true},err=>{if(err){extractError=` Could not copy files to ./${copyPath}. \n ./${err} `;}else{// Cleans up by removing extracted folder and zip.try{fs.rmdirSync(extractedFolder,{recursive: true});}catch(err){extractError=` Could not remove extractedFolder. \n ./${err} `;m.error(spinners[0]);}// Remove zip file as it is not longer needed.try{fs.unlinkSync(extractPath);}catch(error){extractError=` Could not remove ./${extractPath}. \n ./${error} `;m.error(spinners[0]);}}// If no error has been set, mark as successful.if(extractError===null){m.success(spinners[0]);}});}else{// Cleans up by removing extracted folder and zip.// Lets user know that program did not work as intended.try{fs.rmdirSync(extractedFolder,{recursive: true});}catch(err){extractError=` Could not remove extractedFolder. \n ./${err} `;m.error(spinners[0]);}// Remove zip file as it is not longer needed.try{fs.unlinkSync(extractPath);}catch(error){extractError=` Could not remove ./${extractPath}. \n ./${error} `;m.error(spinners[0]);}this.log(`${chalk.red("Error:")} Could not copy files (copyPath is not present). Zip file and extracted files were removed.`);}// Displays error messages once extract is complete.m.on("err",error=>{if(extractError){this.log(`${chalk.red("Error:")}${extractError}`);}else{this.log(`${chalk.red("Error:")}${error} Extract has been stopped with an unknown error.`);}});}/** * It creates the project directory if one is not already in place. * It is useful when running generators separately that * require the project root folder to be in place before a task is performed. * @param {String} projectName Name of the project, used to name root folder. e.g 'hozokit' or this.props.projectName */_createProjectDirectory(projectName){constdirectory=`./${projectName}`;try{if(!fs.existsSync(directory)){fs.mkdirSync(directory);// This.log("Created temporary directory.");}}catch(err){this.log(err);}}/** * It transforms a string separated by spaces into a dash separated one. * For example Hozokit Generator Project will be converted to hozokit-generator-project. * This is useful to create project directories for users without prompting them for the project folder name. * @param {String} value The value to be dashified. e.g 'Hozokit Generator Project' * @param {String} target (optional) The string that will be replaced with the separator. The default is a space ' '. * @param {String} separator (optional) The string the target value should be replaced with. The default is a dash '-'. */_dashify(value,target=" ",separator="-"){constlowerCaseValue=value.toLowerCase();returnlowerCaseValue.split(target).join(separator);}};
This is the test I attempted to write (tried a few more things but all of them have similar results):
'use strict';constpath=require('path');constassert=require('yeoman-assert');consthelpers=require('yeoman-test');describe('generator-hozokit:app',()=>{beforeAll(()=>{returnhelpers.run(path.join(__dirname,'../generators/app')).withPrompts({projectName: 'Hozokit Test',installWordpress: false}).then(function(){// Checks that Hozokit was successfully extracted and present.assert.file(['../hozokit-test/wp-content/themes/hozokit-test','../hozokit-test/wp-content/themes/hozokit-test/index.php','../hozokit-test/wp-content/themes/hozokit-test/templates/base.twig']);// Checks that Wordpres has not been installed.assert.noFile(['wp-login.php','index.php','wp-includes']);// Checks that no zip files remain on the system.assert.noFile(['*.zip',]);});});});
Writing tests is something I'm still learning about but the issue I'm having I just can't seem to make sense of a solution or find more relevant information online or on the docs, I wonder if someone could help me since I'm really curious to know what can be done to solve it and would be a great lesson for me.
Any help or tips on how to get around this would be very appreciated, thank you. 🙏
The text was updated successfully, but these errors were encountered:
Whilst working with Yeoman to generate a project I had an issue when trying to write tests for it.
The generator makes one or more requests to download a few .zip files, extracts them and copies them to a directory. This can take a variable amount of time and when I write a test to assert file presence what ends up happening is that the assertions occur while the files are still being downloaded.
The code of my generator is:
This is the test I attempted to write (tried a few more things but all of them have similar results):
Writing tests is something I'm still learning about but the issue I'm having I just can't seem to make sense of a solution or find more relevant information online or on the docs, I wonder if someone could help me since I'm really curious to know what can be done to solve it and would be a great lesson for me.
Any help or tips on how to get around this would be very appreciated, thank you. 🙏
The text was updated successfully, but these errors were encountered: