Skip to content

Commit

Permalink
migrate command: "about"
Browse files Browse the repository at this point in the history
Test migration of a simple command: "about". This has an unexpectedly
large impact because the command is used as a generic representation by
a lot of tests, but that just improves the validation of the change.
  • Loading branch information
MikkelPaulson committed Nov 1, 2024
1 parent 95a1ae4 commit d3261c5
Show file tree
Hide file tree
Showing 13 changed files with 879 additions and 179 deletions.
236 changes: 137 additions & 99 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ serde = { version = "1.0", features = ["derive"] }
uuid = { version = "0.8", features = ["v4", "serde"] }

initiative-macros = { path = "../macros" }
async-stream = "0.3.5"

[dev-dependencies]
serde_json = "1.0"
tokio = { version = "1.40.0", features = ["rt"] }
tokio-test = "0.4"

[features]
Expand Down
53 changes: 25 additions & 28 deletions core/src/app/command/alias.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,17 +175,14 @@ impl fmt::Display for CommandAlias {
mod tests {
use super::*;
use crate::app::{assert_autocomplete, AppCommand, Command, Event};
use crate::command::TransitionalCommand;
use crate::storage::NullDataStore;
use std::collections::HashSet;
use tokio_test::block_on;

#[test]
fn literal_constructor_test() {
let alias = CommandAlias::literal(
"term".to_string(),
"summary".to_string(),
AppCommand::About.into(),
);
let alias = CommandAlias::literal("term".to_string(), "summary".to_string(), about());

if let CommandAlias::Literal {
term,
Expand All @@ -195,18 +192,18 @@ mod tests {
{
assert_eq!("term", term);
assert_eq!("summary", summary);
assert_eq!(Box::new(Command::from(AppCommand::About)), command);
assert_eq!(Box::new(about()), command);
} else {
panic!("{:?}", alias);
}
}

#[test]
fn wildcard_constructor_test() {
let alias = CommandAlias::strict_wildcard(AppCommand::About.into());
let alias = CommandAlias::strict_wildcard(about());

if let CommandAlias::StrictWildcard { command } = alias {
assert_eq!(Box::new(Command::from(AppCommand::About)), command);
assert_eq!(Box::new(about()), command);
} else {
panic!("{:?}", alias);
}
Expand All @@ -215,39 +212,36 @@ mod tests {
#[test]
fn eq_test() {
assert_eq!(
literal("foo", "foo", AppCommand::About.into()),
literal("foo", "foo", about()),
literal("foo", "bar", AppCommand::Help.into()),
);
assert_ne!(
literal("foo", "foo", AppCommand::About.into()),
literal("bar", "foo", AppCommand::About.into()),
literal("foo", "foo", about()),
literal("bar", "foo", about()),
);

assert_eq!(
strict_wildcard(AppCommand::About.into()),
strict_wildcard(about()),
strict_wildcard(AppCommand::Help.into()),
);
assert_ne!(
literal("", "", AppCommand::About.into()),
strict_wildcard(AppCommand::About.into()),
);
assert_ne!(literal("", "", about()), strict_wildcard(about()));
}

#[test]
fn hash_test() {
let mut set = HashSet::with_capacity(2);

assert!(set.insert(literal("foo", "", AppCommand::About.into())));
assert!(set.insert(literal("bar", "", AppCommand::About.into())));
assert!(set.insert(strict_wildcard(AppCommand::About.into())));
assert!(set.insert(literal("foo", "", about())));
assert!(set.insert(literal("bar", "", about())));
assert!(set.insert(strict_wildcard(about())));
assert!(!set.insert(literal("foo", "", AppCommand::Help.into())));
assert!(!set.insert(literal("FOO", "", AppCommand::Help.into())));
assert!(!set.insert(strict_wildcard(AppCommand::Help.into())));
}

#[test]
fn runnable_test_literal() {
let about_alias = literal("about alias", "about summary", AppCommand::About.into());
let about_alias = literal("about alias", "about summary", about());

let mut app_meta = app_meta();
app_meta.command_aliases.insert(about_alias.clone());
Expand Down Expand Up @@ -278,21 +272,20 @@ mod tests {
);

{
let (about_result, about_alias_result) = (
block_on(AppCommand::About.run("about alias", &mut app_meta)),
block_on(about_alias.run("about alias", &mut app_meta)),
);
let about_alias_result = block_on(about_alias.run("about alias", &mut app_meta));
assert!(!app_meta.command_aliases.is_empty());

let about_result = block_on(about().run("about", &mut app_meta));
assert!(app_meta.command_aliases.is_empty());

assert!(about_result.is_ok(), "{:?}", about_result);
assert_eq!(about_result, about_alias_result);

assert!(!app_meta.command_aliases.is_empty());
}
}

#[test]
fn runnable_test_strict_wildcard() {
let about_alias = strict_wildcard(AppCommand::About.into());
let about_alias = strict_wildcard(about());

let mut app_meta = app_meta();
app_meta.command_aliases.insert(about_alias.clone());
Expand All @@ -312,7 +305,7 @@ mod tests {
assert_eq!(2, app_meta.command_aliases.len());

let (about_result, about_alias_result) = (
block_on(AppCommand::About.run("about", &mut app_meta)),
block_on(about().run("about", &mut app_meta)),
block_on(about_alias.run("about", &mut app_meta)),
);

Expand All @@ -322,6 +315,10 @@ mod tests {
}
}

fn about() -> Command {
Command::from(TransitionalCommand::new("about"))
}

fn event_dispatcher(_event: Event) {}

fn app_meta() -> AppMeta {
Expand Down
72 changes: 24 additions & 48 deletions core/src/app/command/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ use std::fmt;

#[derive(Clone, Debug, Eq, PartialEq)]
pub enum AppCommand {
About,
Changelog,
Debug,
Help,
Expand All @@ -20,9 +19,6 @@ pub enum AppCommand {
impl Runnable for AppCommand {
async fn run(self, _input: &str, app_meta: &mut AppMeta) -> Result<String, String> {
Ok(match self {
Self::About => include_str!("../../../../data/about.md")
.trim_end()
.to_string(),
Self::Debug => format!(
"{:?}\n\n{:?}",
app_meta,
Expand Down Expand Up @@ -55,9 +51,7 @@ impl Runnable for AppCommand {
#[async_trait(?Send)]
impl ContextAwareParse for AppCommand {
async fn parse_input(input: &str, _app_meta: &AppMeta) -> CommandMatches<Self> {
if input.eq_ci("about") {
CommandMatches::new_canonical(Self::About)
} else if input.eq_ci("changelog") {
if input.eq_ci("changelog") {
CommandMatches::new_canonical(Self::Changelog)
} else if input.eq_ci("debug") {
CommandMatches::new_canonical(Self::Debug)
Expand All @@ -83,7 +77,6 @@ impl Autocomplete for AppCommand {
}

[
AutocompleteSuggestion::new("about", "about initiative.sh"),
AutocompleteSuggestion::new("changelog", "show latest updates"),
AutocompleteSuggestion::new("help", "how to use initiative.sh"),
]
Expand All @@ -102,7 +95,6 @@ impl Autocomplete for AppCommand {
impl fmt::Display for AppCommand {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match self {
Self::About => write!(f, "about"),
Self::Changelog => write!(f, "changelog"),
Self::Debug => write!(f, "debug"),
Self::Help => write!(f, "help"),
Expand Down Expand Up @@ -149,7 +141,6 @@ mod test {
let app_meta = app_meta();

[
("about", "about initiative.sh"),
("changelog", "show latest updates"),
("help", "how to use initiative.sh"),
]
Expand All @@ -166,16 +157,6 @@ mod test {
);
});

assert_autocomplete(
&[("about", "about initiative.sh")][..],
block_on(AppCommand::autocomplete("a", &app_meta)),
);

assert_autocomplete(
&[("about", "about initiative.sh")][..],
block_on(AppCommand::autocomplete("A", &app_meta)),
);

assert_autocomplete(
&[("roll [dice]", "roll eg. 8d6 or d20+3")][..],
block_on(AppCommand::autocomplete("roll", &app_meta)),
Expand All @@ -192,34 +173,29 @@ mod test {
fn display_test() {
let app_meta = app_meta();

[
AppCommand::About,
AppCommand::Changelog,
AppCommand::Debug,
AppCommand::Help,
]
.into_iter()
.for_each(|command| {
let command_string = command.to_string();
assert_ne!("", command_string);

assert_eq!(
CommandMatches::new_canonical(command.clone()),
block_on(AppCommand::parse_input(&command_string, &app_meta)),
"{}",
command_string,
);

assert_eq!(
CommandMatches::new_canonical(command),
block_on(AppCommand::parse_input(
&command_string.to_uppercase(),
&app_meta
)),
"{}",
command_string.to_uppercase(),
);
});
[AppCommand::Changelog, AppCommand::Debug, AppCommand::Help]
.into_iter()
.for_each(|command| {
let command_string = command.to_string();
assert_ne!("", command_string);

assert_eq!(
CommandMatches::new_canonical(command.clone()),
block_on(AppCommand::parse_input(&command_string, &app_meta)),
"{}",
command_string,
);

assert_eq!(
CommandMatches::new_canonical(command),
block_on(AppCommand::parse_input(
&command_string.to_uppercase(),
&app_meta
)),
"{}",
command_string.to_uppercase(),
);
});

assert_eq!("roll d20", AppCommand::Roll("d20".to_string()).to_string());

Expand Down
27 changes: 24 additions & 3 deletions core/src/app/command/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ mod runnable;
mod tutorial;

use super::AppMeta;
use crate::command::TransitionalCommand;
use crate::reference::ReferenceCommand;
use crate::storage::StorageCommand;
use crate::time::TimeCommand;
Expand Down Expand Up @@ -51,6 +52,7 @@ impl Command {
ReferenceCommand::parse_input(input, app_meta),
StorageCommand::parse_input(input, app_meta),
TimeCommand::parse_input(input, app_meta),
TransitionalCommand::parse_input(input, app_meta),
TutorialCommand::parse_input(input, app_meta),
WorldCommand::parse_input(input, app_meta),
);
Expand All @@ -62,7 +64,8 @@ impl Command {
.union(parse_results.3)
.union(parse_results.4)
.union(parse_results.5)
.union(parse_results.6);
.union(parse_results.6)
.union(parse_results.7);

// While it is normally a fatal error to encounter two command subtypes claiming canonical
// matches on a given input, the exception is where aliases are present. In this case, we
Expand Down Expand Up @@ -186,6 +189,7 @@ pub enum CommandType {
Reference(ReferenceCommand),
Storage(StorageCommand),
Time(TimeCommand),
Transitional(TransitionalCommand),
Tutorial(TutorialCommand),
World(WorldCommand),
}
Expand All @@ -202,6 +206,7 @@ impl CommandType {
Self::Reference(c) => c.run(input, app_meta).await,
Self::Storage(c) => c.run(input, app_meta).await,
Self::Time(c) => c.run(input, app_meta).await,
Self::Transitional(c) => c.run(input, app_meta).await,
Self::Tutorial(c) => c.run(input, app_meta).await,
Self::World(c) => c.run(input, app_meta).await,
}
Expand All @@ -216,6 +221,7 @@ impl fmt::Display for CommandType {
Self::Reference(c) => write!(f, "{}", c),
Self::Storage(c) => write!(f, "{}", c),
Self::Time(c) => write!(f, "{}", c),
Self::Transitional(c) => write!(f, "{}", c),
Self::Tutorial(c) => write!(f, "{}", c),
Self::World(c) => write!(f, "{}", c),
}
Expand Down Expand Up @@ -260,6 +266,12 @@ impl From<TimeCommand> for CommandType {
}
}

impl From<TransitionalCommand> for CommandType {
fn from(c: TransitionalCommand) -> CommandType {
CommandType::Transitional(c)
}
}

impl From<TutorialCommand> for CommandType {
fn from(c: TutorialCommand) -> CommandType {
CommandType::Tutorial(c)
Expand Down Expand Up @@ -288,9 +300,9 @@ mod test {

assert_eq!(
Command::from(CommandMatches::new_canonical(CommandType::App(
AppCommand::About
AppCommand::Changelog
))),
block_on(Command::parse_input("about", &app_meta))
block_on(Command::parse_input("changelog", &app_meta))
.take_best_match()
.unwrap(),
);
Expand All @@ -311,6 +323,15 @@ mod test {
.unwrap(),
);

assert_eq!(
Command::from(CommandMatches::new_canonical(CommandType::Transitional(
TransitionalCommand::new("about"),
))),
block_on(Command::parse_input("about", &app_meta))
.take_best_match()
.unwrap(),
);

assert_eq!(
Command::from(CommandMatches::new_canonical(CommandType::World(
WorldCommand::Create {
Expand Down
2 changes: 1 addition & 1 deletion core/src/app/command/runnable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ pub fn assert_autocomplete(
assert_eq!(expected, actual);
}

#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize)]
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
#[serde(into = "(Cow<'static, str>, Cow<'static, str>)")]
pub struct AutocompleteSuggestion {
pub term: Cow<'static, str>,
Expand Down
Loading

0 comments on commit d3261c5

Please sign in to comment.