#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
mod support;
use support::{Harness, token_for};
use turnframe_core::ids::TurnId;
use turnframe_core::interaction::InteractionStatus;
use turnframe_core::locale::Locale;
use turnframe_core::understanding::ActTarget;
use turnframe_runtime::policy::ConfirmationCopy;
use turnframe_runtime::reduce::NoticeCopy;
use turnframe_test::providers::UnderstandingBuilder;
use turnframe_test::workflows::trip::{complete_case, incomplete_case, operations};
fn turn_one() -> TurnId {
TurnId::from(uuid::Uuid::from_u128(1))
}
fn italian() -> Locale {
Locale::from("it-IT")
}
fn italian_cards() -> ConfirmationCopy {
let mut copy = ConfirmationCopy::english();
copy.confirm_title = copy.confirm_title.with(italian(), "Confermi?");
copy.confirm_label = copy.confirm_label.with(italian(), "Conferma");
copy.decline_label = copy.decline_label.with(italian(), "Annulla");
copy
}
fn italian_notices() -> NoticeCopy {
let mut copy = NoticeCopy::english();
copy.select_target_title = copy.select_target_title.with(italian(), "Quale intendevi?");
copy.select_target_cancel = copy
.select_target_cancel
.with(italian(), "Nessuna di queste");
copy
}
async fn confirmation_card(copy: Option<ConfirmationCopy>, locale: Locale) -> Vec<String> {
let turn = turn_one();
let text = "Cancel that trip";
let understood = UnderstandingBuilder::of(text)
.apply(
operations::WITHDRAW,
token_for(turn, "trip", "trip-1"),
serde_json::json!(null),
text,
)
.build()
.unwrap();
let mut builder = Harness::builder()
.trip("trip-1", "Trip 1", 3, complete_case())
.understands(understood)
.without_narration();
if let Some(copy) = copy {
builder = builder.confirmation_copy(copy);
}
let harness = builder.build().await;
let mut input = harness.turn(turn, text);
input.locale = locale.clone();
let answered = harness.handle(input).await.unwrap();
let card = answered
.blocks
.iter()
.find_map(|block| match block {
turnframe_core::response::ResponseBlock::Interaction(block) => Some(&block.view),
_ => None,
})
.expect("a confirmation card is on screen");
assert_eq!(card.status, InteractionStatus::Active);
let mut labels = vec![card.title.resolve(&locale).to_owned()];
labels.extend(
card.options
.iter()
.map(|option| option.label.resolve(&locale).to_owned()),
);
labels
}
#[tokio::test]
async fn confirmation_card_copy_can_be_supplied() {
let labels = confirmation_card(Some(italian_cards()), italian()).await;
assert!(
labels.iter().any(|label| label == "Conferma"),
"the confirming button carries the deployment's word: {labels:?}"
);
assert!(
labels.iter().any(|label| label == "Annulla"),
"and so does the declining one: {labels:?}"
);
assert!(
!labels
.iter()
.any(|label| label == "Confirm" || label == "Cancel"),
"no English survives on the buttons of an Italian turn: {labels:?}"
);
}
#[tokio::test]
async fn a_deployment_that_supplies_nothing_still_gets_a_card() {
let labels = confirmation_card(None, italian()).await;
assert!(
labels.iter().any(|label| label == "Conferma"),
"the shipped default speaks the turn's language: {labels:?}"
);
}
#[tokio::test]
async fn supplied_copy_is_additive_rather_than_a_replacement() {
let labels = confirmation_card(Some(italian_cards()), Locale::from("de-DE")).await;
assert!(
labels.iter().any(|label| label == "Confirm"),
"a locale nobody translated falls back to what was shipped: {labels:?}"
);
assert!(
labels.iter().all(|label| !label.is_empty()),
"and never to nothing: {labels:?}"
);
}
async fn selection_card(copy: Option<NoticeCopy>, locale: Locale) -> Vec<String> {
let turn = turn_one();
let text = "Set the name on the Ferri trip to Lisbon";
let either = ActTarget::Ambiguous {
candidates: vec![
token_for(turn, "trip", "trip-1"),
token_for(turn, "trip", "trip-2"),
],
};
let understood = UnderstandingBuilder::of(text)
.apply_to(
operations::SET_NAME,
either,
serde_json::json!({"value": "Lisbon"}),
text,
)
.build()
.unwrap();
let mut builder = Harness::builder()
.trip("trip-1", "Ferri", 3, incomplete_case())
.trip("trip-2", "Ferri", 1, incomplete_case())
.understands(understood)
.without_narration();
if let Some(copy) = copy {
builder = builder.notice_copy(copy);
}
let harness = builder.build().await;
let mut input = harness.turn(turn, text);
input.locale = locale.clone();
let answered = harness.handle(input).await.unwrap();
let card = answered
.blocks
.iter()
.find_map(|block| match block {
turnframe_core::response::ResponseBlock::Interaction(block) => Some(&block.view),
_ => None,
})
.expect("an ambiguous target asks rather than guessing");
let mut labels = vec![card.title.resolve(&locale).to_owned()];
labels.extend(
card.options
.iter()
.map(|option| option.label.resolve(&locale).to_owned()),
);
labels
}
#[tokio::test]
async fn reducer_notice_copy_can_be_supplied() {
let labels = Box::pin(selection_card(Some(italian_notices()), italian())).await;
assert!(
labels.iter().any(|label| label == "Quale intendevi?"),
"the reducer asks in the deployment's language: {labels:?}"
);
assert!(
labels.iter().any(|label| label == "Nessuna di queste"),
"including the way out of the card: {labels:?}"
);
assert!(
!labels
.iter()
.any(|label| label == "Which one did you mean?"),
"no English survives on an Italian turn: {labels:?}"
);
}
#[tokio::test]
async fn a_deployment_that_supplies_no_notice_copy_still_gets_asked() {
let labels = Box::pin(selection_card(None, italian())).await;
assert!(
labels.iter().any(|label| label == "Quale intendevi?"),
"the shipped default speaks the turn's language: {labels:?}"
);
}
#[test]
fn notice_copy_is_additive_in_both_directions() {
let copy = italian_notices();
assert_eq!(
copy.select_target_title.resolve(&italian()),
"Quale intendevi?"
);
assert_eq!(
copy.select_target_title.resolve(&Locale::from("en-GB")),
"Which one did you mean?",
"the shipped sentence is still there for a reader who asked for it"
);
assert_eq!(
copy.draft_only.resolve(&italian()),
NoticeCopy::english().draft_only.resolve(&italian()),
"an untranslated field is the shipped sentence, not an empty one"
);
}
#[test]
fn every_copy_family_is_reachable_from_a_builder() {
let _ = turnframe_runtime::orchestrator::Orchestrator::builder()
.notice_copy(NoticeCopy::english())
.confirmation_copy(ConfirmationCopy::english());
let _ = turnframe_runtime::compose::Composer::with_copy;
}