test(nodejs): Port nodejs module tests from E2E to integraton (#867)
Replaces the existing nodejs module end-to-end tests with integration tests that don't require preinstalled environmental dependencies. - Moved the tests to the same file as the module they test - Created a render_module utility function for rendering modules within tests - Removed Node.js installation during CI setup - Add Shell to Context to allow for tests to not run shell-specific code
This commit is contained in:
parent
5342dcc658
commit
3365beae09
|
@ -133,11 +133,6 @@ jobs:
|
|||
ARGS: --resolver nightly-2019-09-21
|
||||
run: stack $ARGS ghc -- --numeric-version --no-install-ghc
|
||||
|
||||
# Install Node.js at a fixed version
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: "12.0.0"
|
||||
|
||||
# Install Golang at a fixed version
|
||||
- uses: actions/setup-go@v1
|
||||
with:
|
||||
|
|
|
@ -31,6 +31,9 @@ pub struct Context<'a> {
|
|||
|
||||
/// Private field to store Git information for modules who need it
|
||||
repo: OnceCell<Repo>,
|
||||
|
||||
/// The shell the user is assumed to be running
|
||||
pub shell: Shell,
|
||||
}
|
||||
|
||||
impl<'a> Context<'a> {
|
||||
|
@ -71,12 +74,15 @@ impl<'a> Context<'a> {
|
|||
// TODO: Currently gets the physical directory. Get the logical directory.
|
||||
let current_dir = Context::expand_tilde(dir.into());
|
||||
|
||||
let shell = Context::get_shell();
|
||||
|
||||
Context {
|
||||
config,
|
||||
properties,
|
||||
current_dir,
|
||||
dir_files: OnceCell::new(),
|
||||
repo: OnceCell::new(),
|
||||
shell,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -160,6 +166,18 @@ impl<'a> Context<'a> {
|
|||
Ok(dir_files)
|
||||
})
|
||||
}
|
||||
|
||||
fn get_shell() -> Shell {
|
||||
let shell = std::env::var("STARSHIP_SHELL").unwrap_or_default();
|
||||
match shell.as_str() {
|
||||
"bash" => Shell::Bash,
|
||||
"fish" => Shell::Fish,
|
||||
"ion" => Shell::Ion,
|
||||
"powershell" => Shell::PowerShell,
|
||||
"zsh" => Shell::Zsh,
|
||||
_ => Shell::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Repo {
|
||||
|
@ -252,6 +270,16 @@ fn get_current_branch(repository: &Repository) -> Option<String> {
|
|||
shorthand.map(std::string::ToString::to_string)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Shell {
|
||||
Bash,
|
||||
Fish,
|
||||
Ion,
|
||||
PowerShell,
|
||||
Zsh,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use crate::config::SegmentConfig;
|
||||
use crate::context::Shell;
|
||||
use crate::segment::Segment;
|
||||
use ansi_term::Style;
|
||||
use ansi_term::{ANSIString, ANSIStrings};
|
||||
|
@ -134,10 +135,10 @@ impl<'a> Module<'a> {
|
|||
/// Returns a vector of colored ANSIString elements to be later used with
|
||||
/// `ANSIStrings()` to optimize ANSI codes
|
||||
pub fn ansi_strings(&self) -> Vec<ANSIString> {
|
||||
self.ansi_strings_for_prompt(true)
|
||||
self.ansi_strings_for_shell(Shell::Unknown)
|
||||
}
|
||||
|
||||
pub fn ansi_strings_for_prompt(&self, is_prompt: bool) -> Vec<ANSIString> {
|
||||
pub fn ansi_strings_for_shell(&self, shell: Shell) -> Vec<ANSIString> {
|
||||
let mut ansi_strings = self
|
||||
.segments
|
||||
.iter()
|
||||
|
@ -147,20 +148,17 @@ impl<'a> Module<'a> {
|
|||
ansi_strings.insert(0, self.prefix.ansi_string());
|
||||
ansi_strings.push(self.suffix.ansi_string());
|
||||
|
||||
if is_prompt {
|
||||
let shell = std::env::var("STARSHIP_SHELL").unwrap_or_default();
|
||||
ansi_strings = match shell.as_str() {
|
||||
"bash" => ansi_strings_modified(ansi_strings, shell),
|
||||
"zsh" => ansi_strings_modified(ansi_strings, shell),
|
||||
ansi_strings = match shell {
|
||||
Shell::Bash => ansi_strings_modified(ansi_strings, shell),
|
||||
Shell::Zsh => ansi_strings_modified(ansi_strings, shell),
|
||||
_ => ansi_strings,
|
||||
};
|
||||
}
|
||||
|
||||
ansi_strings
|
||||
}
|
||||
|
||||
pub fn to_string_without_prefix(&self) -> String {
|
||||
ANSIStrings(&self.ansi_strings()[1..]).to_string()
|
||||
pub fn to_string_without_prefix(&self, shell: Shell) -> String {
|
||||
ANSIStrings(&self.ansi_strings_for_shell(shell)[1..]).to_string()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -174,7 +172,7 @@ impl<'a> fmt::Display for Module<'a> {
|
|||
/// Many shells cannot deal with raw unprintable characters (like ANSI escape sequences) and
|
||||
/// miscompute the cursor position as a result, leading to strange visual bugs. Here, we wrap these
|
||||
/// characters in shell-specific escape codes to indicate to the shell that they are zero-length.
|
||||
fn ansi_strings_modified(ansi_strings: Vec<ANSIString>, shell: String) -> Vec<ANSIString> {
|
||||
fn ansi_strings_modified(ansi_strings: Vec<ANSIString>, shell: Shell) -> Vec<ANSIString> {
|
||||
const ESCAPE_BEGIN: char = '\u{1b}';
|
||||
const MAYBE_ESCAPE_END: char = 'm';
|
||||
ansi_strings
|
||||
|
@ -187,18 +185,18 @@ fn ansi_strings_modified(ansi_strings: Vec<ANSIString>, shell: String) -> Vec<AN
|
|||
.map(|x| match x {
|
||||
ESCAPE_BEGIN => {
|
||||
escaped = true;
|
||||
match shell.as_str() {
|
||||
"bash" => String::from("\u{5c}\u{5b}\u{1b}"), // => \[ESC
|
||||
"zsh" => String::from("\u{25}\u{7b}\u{1b}"), // => %{ESC
|
||||
match shell {
|
||||
Shell::Bash => String::from("\u{5c}\u{5b}\u{1b}"), // => \[ESC
|
||||
Shell::Zsh => String::from("\u{25}\u{7b}\u{1b}"), // => %{ESC
|
||||
_ => x.to_string(),
|
||||
}
|
||||
}
|
||||
MAYBE_ESCAPE_END => {
|
||||
if escaped {
|
||||
escaped = false;
|
||||
match shell.as_str() {
|
||||
"bash" => String::from("m\u{5c}\u{5d}"), // => m\]
|
||||
"zsh" => String::from("m\u{25}\u{7d}"), // => m%}
|
||||
match shell {
|
||||
Shell::Bash => String::from("m\u{5c}\u{5d}"), // => m\]
|
||||
Shell::Zsh => String::from("m\u{25}\u{7d}"), // => m%}
|
||||
_ => x.to_string(),
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
use super::{Context, Module, RootModuleConfig};
|
||||
use super::{Context, Module, RootModuleConfig, Shell};
|
||||
use crate::configs::battery::BatteryConfig;
|
||||
|
||||
/// Creates a module for the battery percentage and charging state
|
||||
pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
|
||||
// TODO: Update when v1.0 printing refactor is implemented to only
|
||||
// print escapes in a prompt context.
|
||||
let shell = std::env::var("STARSHIP_SHELL").unwrap_or_default();
|
||||
let percentage_char = match shell.as_str() {
|
||||
"zsh" => "%%", // % is an escape in zsh, see PROMPT in `man zshmisc`
|
||||
let percentage_char = match context.shell {
|
||||
Shell::Zsh => "%%", // % is an escape in zsh, see PROMPT in `man zshmisc`
|
||||
_ => "%",
|
||||
};
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
use super::{Context, Module, RootModuleConfig};
|
||||
|
||||
use super::{Context, Module, RootModuleConfig, Shell};
|
||||
use crate::configs::character::CharacterConfig;
|
||||
|
||||
/// Creates a module for the prompt character
|
||||
|
@ -25,7 +24,6 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
|
|||
let props = &context.properties;
|
||||
let exit_code_default = std::string::String::from("0");
|
||||
let exit_code = props.get("status_code").unwrap_or(&exit_code_default);
|
||||
let shell = std::env::var("STARSHIP_SHELL").unwrap_or_default();
|
||||
let keymap_default = std::string::String::from("viins");
|
||||
let keymap = props.get("keymap").unwrap_or(&keymap_default);
|
||||
let exit_success = exit_code == "0";
|
||||
|
@ -35,8 +33,8 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
|
|||
// Unfortunately, this is also the name of the non-vi default mode.
|
||||
// We do some environment detection in src/init.rs to translate.
|
||||
// The result: in non-vi fish, keymap is always reported as "insert"
|
||||
let mode = match (shell.as_str(), keymap.as_str()) {
|
||||
("fish", "default") | ("zsh", "vicmd") => ShellEditMode::Normal,
|
||||
let mode = match (&context.shell, keymap.as_str()) {
|
||||
(Shell::Fish, "default") | (Shell::Zsh, "vicmd") => ShellEditMode::Normal,
|
||||
_ => ASSUMED_MODE,
|
||||
};
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use byte_unit::{Byte, ByteUnit};
|
||||
use sysinfo::{RefreshKind, SystemExt};
|
||||
|
||||
use super::{Context, Module, RootModuleConfig};
|
||||
use super::{Context, Module, RootModuleConfig, Shell};
|
||||
|
||||
use crate::configs::memory_usage::MemoryConfig;
|
||||
|
||||
|
@ -19,9 +19,8 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
|
|||
|
||||
// TODO: Update when v1.0 printing refactor is implemented to only
|
||||
// print escapes in a prompt context.
|
||||
let shell = std::env::var("STARSHIP_SHELL").unwrap_or_default();
|
||||
let percent_sign = match shell.as_str() {
|
||||
"zsh" => "%%", // % is an escape in zsh, see PROMPT in `man zshmisc`
|
||||
let percent_sign = match context.shell {
|
||||
Shell::Zsh => "%%", // % is an escape in zsh, see PROMPT in `man zshmisc`
|
||||
_ => "%",
|
||||
};
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ mod utils;
|
|||
mod battery;
|
||||
|
||||
use crate::config::{RootModuleConfig, SegmentConfig};
|
||||
use crate::context::Context;
|
||||
use crate::context::{Context, Shell};
|
||||
use crate::module::Module;
|
||||
|
||||
pub fn handle<'a>(module: &str, context: &'a Context) -> Option<Module<'a>> {
|
||||
|
|
|
@ -34,3 +34,55 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
|
|||
|
||||
Some(module)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::modules::utils::test::render_module;
|
||||
use ansi_term::Color;
|
||||
use std::fs::{self, File};
|
||||
use std::io;
|
||||
use tempfile;
|
||||
|
||||
#[test]
|
||||
fn folder_without_node_files() -> io::Result<()> {
|
||||
let dir = tempfile::tempdir()?;
|
||||
let actual = render_module("nodejs", dir.path());
|
||||
let expected = None;
|
||||
assert_eq!(expected, actual);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn folder_with_package_json() -> io::Result<()> {
|
||||
let dir = tempfile::tempdir()?;
|
||||
File::create(dir.path().join("package.json"))?.sync_all()?;
|
||||
|
||||
let actual = render_module("nodejs", dir.path());
|
||||
let expected = Some(format!("via {} ", Color::Green.bold().paint("⬢ v12.0.0")));
|
||||
assert_eq!(expected, actual);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn folder_with_js_file() -> io::Result<()> {
|
||||
let dir = tempfile::tempdir()?;
|
||||
File::create(dir.path().join("index.js"))?.sync_all()?;
|
||||
|
||||
let actual = render_module("nodejs", dir.path());
|
||||
let expected = Some(format!("via {} ", Color::Green.bold().paint("⬢ v12.0.0")));
|
||||
assert_eq!(expected, actual);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn folder_with_node_modules() -> io::Result<()> {
|
||||
let dir = tempfile::tempdir()?;
|
||||
let node_modules = dir.path().join("node_modules");
|
||||
fs::create_dir_all(&node_modules)?;
|
||||
|
||||
let actual = render_module("nodejs", dir.path());
|
||||
let expected = Some(format!("via {} ", Color::Green.bold().paint("⬢ v12.0.0")));
|
||||
assert_eq!(expected, actual);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,2 +1,5 @@
|
|||
pub mod directory;
|
||||
pub mod java_version_parser;
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod test;
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
use crate::config::StarshipConfig;
|
||||
use crate::context::{Context, Shell};
|
||||
use std::path::Path;
|
||||
|
||||
/// Render a specific starship module by name
|
||||
pub fn render_module(module_name: &str, path: &Path) -> Option<String> {
|
||||
let mut context = Context::new_with_dir(clap::ArgMatches::default(), path);
|
||||
context.config = StarshipConfig { config: None };
|
||||
context.shell = Shell::Unknown;
|
||||
|
||||
crate::print::get_module(module_name, context)
|
||||
}
|
19
src/print.rs
19
src/print.rs
|
@ -1,3 +1,4 @@
|
|||
use ansi_term::ANSIStrings;
|
||||
use clap::ArgMatches;
|
||||
use rayon::prelude::*;
|
||||
use std::fmt::Write as FmtWrite;
|
||||
|
@ -35,10 +36,11 @@ pub fn get_prompt(context: Context) -> String {
|
|||
for module in printable {
|
||||
// Skip printing the prefix of a module after the line_break
|
||||
if print_without_prefix {
|
||||
let module_without_prefix = module.to_string_without_prefix();
|
||||
let module_without_prefix = module.to_string_without_prefix(context.shell.clone());
|
||||
write!(buf, "{}", module_without_prefix).unwrap()
|
||||
} else {
|
||||
write!(buf, "{}", module).unwrap();
|
||||
let module = module.ansi_strings_for_shell(context.shell.clone());
|
||||
write!(buf, "{}", ANSIStrings(&module)).unwrap();
|
||||
}
|
||||
|
||||
print_without_prefix = module.get_name() == "line_break"
|
||||
|
@ -49,15 +51,14 @@ pub fn get_prompt(context: Context) -> String {
|
|||
|
||||
pub fn module(module_name: &str, args: ArgMatches) {
|
||||
let context = Context::new(args);
|
||||
|
||||
// If the module returns `None`, print an empty string
|
||||
let module = modules::handle(module_name, &context)
|
||||
.map(|m| m.to_string())
|
||||
.unwrap_or_default();
|
||||
|
||||
let module = get_module(module_name, context).unwrap_or_default();
|
||||
print!("{}", module);
|
||||
}
|
||||
|
||||
pub fn get_module(module_name: &str, context: Context) -> Option<String> {
|
||||
modules::handle(module_name, &context).map(|m| m.to_string())
|
||||
}
|
||||
|
||||
pub fn explain(args: ArgMatches) {
|
||||
let context = Context::new(args);
|
||||
|
||||
|
@ -73,7 +74,7 @@ pub fn explain(args: ArgMatches) {
|
|||
.into_iter()
|
||||
.filter(|module| !dont_print.contains(&module.get_name().as_str()))
|
||||
.map(|module| {
|
||||
let ansi_strings = module.ansi_strings_for_prompt(false);
|
||||
let ansi_strings = module.ansi_strings();
|
||||
let value = module.get_segments().join("");
|
||||
ModuleInfo {
|
||||
value: ansi_term::ANSIStrings(&ansi_strings[1..ansi_strings.len() - 1]).to_string(),
|
||||
|
|
|
@ -36,8 +36,11 @@ pub fn exec_cmd(cmd: &str, args: &[&str]) -> Option<CommandOutput> {
|
|||
0 => String::from(cmd),
|
||||
_ => format!("{} {}", cmd, args.join(" ")),
|
||||
};
|
||||
|
||||
match command.as_str() {
|
||||
"node --version" => Some(CommandOutput {
|
||||
stdout: String::from("v12.0.0"),
|
||||
stderr: String::default(),
|
||||
}),
|
||||
"dummy_command" => Some(CommandOutput {
|
||||
stdout: String::from("stdout ok!"),
|
||||
stderr: String::from("stderr ok!"),
|
||||
|
|
|
@ -19,7 +19,6 @@ mod jobs;
|
|||
mod line_break;
|
||||
mod modules;
|
||||
mod nix_shell;
|
||||
mod nodejs;
|
||||
mod python;
|
||||
mod ruby;
|
||||
mod terraform;
|
||||
|
|
|
@ -1,93 +0,0 @@
|
|||
use ansi_term::Color;
|
||||
use std::fs::{self, File};
|
||||
use std::io;
|
||||
use tempfile;
|
||||
|
||||
use crate::common;
|
||||
|
||||
/// Wrapper around common::render_module("nodejs") to work around platform quirks
|
||||
fn render_node_module() -> std::process::Command {
|
||||
let mut command = common::render_module("nodejs");
|
||||
|
||||
// If SYSTEMROOT is not set on Windows node will refuse to print its version
|
||||
if cfg!(windows) {
|
||||
let system_root = std::env::var("SYSTEMROOT")
|
||||
.map(|i| {
|
||||
if i.trim().is_empty() {
|
||||
"C:\\WINDOWS".into()
|
||||
} else {
|
||||
i
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|_| "C:\\WINDOWS".into());
|
||||
command.env("SYSTEMROOT", system_root);
|
||||
}
|
||||
command
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn folder_without_node_files() -> io::Result<()> {
|
||||
let dir = tempfile::tempdir()?;
|
||||
|
||||
let output = render_node_module()
|
||||
.arg("--path")
|
||||
.arg(dir.path())
|
||||
.output()?;
|
||||
let actual = String::from_utf8(output.stdout).unwrap();
|
||||
|
||||
let expected = "";
|
||||
assert_eq!(expected, actual);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn folder_with_package_json() -> io::Result<()> {
|
||||
let dir = tempfile::tempdir()?;
|
||||
File::create(dir.path().join("package.json"))?.sync_all()?;
|
||||
|
||||
let output = render_node_module()
|
||||
.arg("--path")
|
||||
.arg(dir.path())
|
||||
.output()?;
|
||||
let actual = String::from_utf8(output.stdout).unwrap();
|
||||
|
||||
let expected = format!("via {} ", Color::Green.bold().paint("⬢ v12.0.0"));
|
||||
assert_eq!(expected, actual);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn folder_with_js_file() -> io::Result<()> {
|
||||
let dir = tempfile::tempdir()?;
|
||||
File::create(dir.path().join("index.js"))?.sync_all()?;
|
||||
|
||||
let output = render_node_module()
|
||||
.arg("--path")
|
||||
.arg(dir.path())
|
||||
.output()?;
|
||||
let actual = String::from_utf8(output.stdout).unwrap();
|
||||
|
||||
let expected = format!("via {} ", Color::Green.bold().paint("⬢ v12.0.0"));
|
||||
assert_eq!(expected, actual);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn folder_with_node_modules() -> io::Result<()> {
|
||||
let dir = tempfile::tempdir()?;
|
||||
let node_modules = dir.path().join("node_modules");
|
||||
fs::create_dir_all(&node_modules)?;
|
||||
|
||||
let output = render_node_module()
|
||||
.arg("--path")
|
||||
.arg(dir.path())
|
||||
.output()?;
|
||||
let actual = String::from_utf8(output.stdout).unwrap();
|
||||
|
||||
let expected = format!("via {} ", Color::Green.bold().paint("⬢ v12.0.0"));
|
||||
assert_eq!(expected, actual);
|
||||
Ok(())
|
||||
}
|
Loading…
Reference in New Issue