Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions rust/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions rust/agama-l10n/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ zbus = "5.11.0"
async-trait = "0.1.89"

[dev-dependencies]
test-context = "0.4.1"
tokio-test = "0.4.4"

[lints.rust]
Expand Down
214 changes: 209 additions & 5 deletions rust/agama-l10n/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,221 @@
//! The service can be started by calling the [start_service] function, which
//! returns a [agama_utils::actors::ActorHandler] to interact with the system.

pub mod start;
pub use start::start;

pub mod service;
pub use service::Service;
pub use service::{Service, Starter};

mod model;
pub use model::{Model, ModelAdapter};
pub use model::{KeymapsDatabase, LocalesDatabase, Model, ModelAdapter, TimezonesDatabase};

mod config;
mod dbus;
pub mod helpers;
pub mod message;
mod monitor;

pub mod test_utils;

#[cfg(test)]
mod tests {
use crate::{
message,
service::{self, Service},
test_utils::TestModel,
};

use agama_utils::{
actor::Handler,
api::{self, event::Event, scope::Scope},
issue, test,
};
use test_context::{test_context, AsyncTestContext};
use tokio::sync::broadcast;

struct Context {
events_rx: broadcast::Receiver<Event>,
handler: Handler<Service>,
issues: Handler<issue::Service>,
}

impl AsyncTestContext for Context {
async fn setup() -> Context {
let (events_tx, events_rx) = broadcast::channel::<Event>(16);
let dbus = test::dbus::connection().await.unwrap();
let issues = issue::start(events_tx.clone(), dbus).await.unwrap();

let model = TestModel::with_sample_data();
let handler = Service::starter(events_tx, issues.clone())
.with_model(model)
.start()
.await
.expect("Could not start the l10n service");

Self {
events_rx,
handler,
issues,
}
}
}

#[test_context(Context)]
#[tokio::test]
async fn test_get_and_set_config(ctx: &mut Context) -> Result<(), Box<dyn std::error::Error>> {
let config = ctx.handler.call(message::GetConfig).await.unwrap();
assert_eq!(config.locale, Some("en_US.UTF-8".to_string()));

let input_config = api::l10n::Config {
locale: Some("es_ES.UTF-8".to_string()),
keymap: Some("es".to_string()),
timezone: Some("Atlantic/Canary".to_string()),
};
ctx.handler
.call(message::SetConfig::with(input_config.clone()))
.await?;

let updated = ctx.handler.call(message::GetConfig).await?;
assert_eq!(&updated, &input_config);

let proposal = ctx.handler.call(message::GetProposal).await?;
assert!(proposal.is_some());

let event = ctx
.events_rx
.recv()
.await
.expect("Did not receive the event");
assert!(matches!(
event,
Event::ProposalChanged { scope: Scope::L10n }
));

let input_config = api::l10n::Config {
locale: None,
keymap: Some("es".to_string()),
timezone: None,
};

// Use system info for missing values.
ctx.handler
.call(message::SetConfig::with(input_config.clone()))
.await?;

let updated = ctx.handler.call(message::GetConfig).await?;
assert_eq!(
updated,
api::l10n::Config {
locale: Some("en_US.UTF-8".to_string()),
keymap: Some("es".to_string()),
timezone: Some("Europe/Berlin".to_string()),
}
);

Ok(())
}

#[test_context(Context)]
#[tokio::test]
async fn test_reset_config(ctx: &mut Context) -> Result<(), Box<dyn std::error::Error>> {
ctx.handler.call(message::SetConfig::new(None)).await?;

let config = ctx.handler.call(message::GetConfig).await?;
assert_eq!(
config,
api::l10n::Config {
locale: Some("en_US.UTF-8".to_string()),
keymap: Some("us".to_string()),
timezone: Some("Europe/Berlin".to_string()),
}
);

Ok(())
}

#[test_context(Context)]
#[tokio::test]
async fn test_set_invalid_config(ctx: &mut Context) -> Result<(), Box<dyn std::error::Error>> {
let input_config = api::l10n::Config {
locale: Some("es-ES.UTF-8".to_string()),
..Default::default()
};

let result = ctx
.handler
.call(message::SetConfig::with(input_config.clone()))
.await;
assert!(matches!(result, Err(service::Error::InvalidLocale(_))));
Ok(())
}

#[test_context(Context)]
#[tokio::test]
async fn test_set_config_without_changes(
ctx: &mut Context,
) -> Result<(), Box<dyn std::error::Error>> {
let config = ctx.handler.call(message::GetConfig).await?;
assert_eq!(config.locale, Some("en_US.UTF-8".to_string()));
let message = message::SetConfig::with(config.clone());
ctx.handler.call(message).await?;
// Wait until the action is dispatched.
let _ = ctx.handler.call(message::GetConfig).await?;

let event = ctx.events_rx.try_recv();
assert!(matches!(event, Err(broadcast::error::TryRecvError::Empty)));
Ok(())
}

#[test_context(Context)]
#[tokio::test]
async fn test_set_config_unknown_values(
ctx: &mut Context,
) -> Result<(), Box<dyn std::error::Error>> {
let config = api::l10n::Config {
keymap: Some("jk".to_string()),
locale: Some("xx_XX.UTF-8".to_string()),
timezone: Some("Unknown/Unknown".to_string()),
};
let _ = ctx.handler.call(message::SetConfig::with(config)).await?;

let found_issues = ctx.issues.call(issue::message::Get).await?;
let l10n_issues = found_issues.get(&Scope::L10n).unwrap();
assert_eq!(l10n_issues.len(), 3);

let proposal = ctx.handler.call(message::GetProposal).await?;
assert!(proposal.is_none());
Ok(())
}

#[test_context(Context)]
#[tokio::test]
async fn test_get_system(ctx: &mut Context) -> Result<(), Box<dyn std::error::Error>> {
let system = ctx.handler.call(message::GetSystem).await?;
assert_eq!(system.keymaps.len(), 2);

Ok(())
}

#[test_context(Context)]
#[tokio::test]
async fn test_get_proposal(ctx: &mut Context) -> Result<(), Box<dyn std::error::Error>> {
let input_config = api::l10n::Config {
locale: Some("es_ES.UTF-8".to_string()),
keymap: Some("es".to_string()),
timezone: Some("Atlantic/Canary".to_string()),
};
let message = message::SetConfig::with(input_config.clone());
ctx.handler.call(message).await?;

let proposal = ctx
.handler
.call(message::GetProposal)
.await?
.expect("Could not get the proposal");
assert_eq!(proposal.locale.to_string(), input_config.locale.unwrap());
assert_eq!(proposal.keymap.to_string(), input_config.keymap.unwrap());
assert_eq!(
proposal.timezone.to_string(),
input_config.timezone.unwrap()
);
Ok(())
}
}
1 change: 0 additions & 1 deletion rust/agama-l10n/src/model/keyboard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ impl KeymapsDatabase {
Self::default()
}

#[cfg(test)]
pub fn with_entries(data: &[Keymap]) -> Self {
Self {
keymaps: data.to_vec(),
Expand Down
1 change: 0 additions & 1 deletion rust/agama-l10n/src/model/locale.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ impl LocalesDatabase {
Self::default()
}

#[cfg(test)]
pub fn with_entries(data: &[LocaleEntry]) -> Self {
Self {
known_locales: data.iter().map(|l| l.id.clone()).collect(),
Expand Down
1 change: 0 additions & 1 deletion rust/agama-l10n/src/model/timezone.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ impl TimezonesDatabase {
Self::default()
}

#[cfg(test)]
pub fn with_entries(data: &[TimezoneEntry]) -> Self {
Self {
timezones: data.to_vec(),
Expand Down
Loading
Loading