feat(rhema-code): add the colors setting with four palettes
The stored `colors` key names a palette — rhema (the default, Jadeite), aquamarine, black-and-white, or dracula — and the Appearance page of /settings cycles it live, like the theme and icon toggles beside it. Each palette carries a dark and a light appearance under the existing `theme` key; dracula's light side is derived, the official palette ships only dark. The appearance decision moves off token comparison: Theme now answers is_dark() from the ground/text luminance, so palettes beyond the original two get the right per-profile fallback tones.
This commit is contained in:
commit
81da26b955
159 files changed
+50628
No files matched your search
@@ -0,0 +1,76 @@
|
||||
//! Offline integration tests: the ICS backend against an in-process
|
||||
//! wiremock server. Zero network.
|
||||
|
||||
use rhema_calendar_integration::{Calendar, CalendarError, IcsCalendar, Window};
|
||||
use time::OffsetDateTime;
|
||||
use time::format_description::well_known::Rfc3339;
|
||||
use wiremock::matchers::{header, method, path};
|
||||
use wiremock::{Mock, MockServer, ResponseTemplate};
|
||||
|
||||
const FEED_BODY: &str = "BEGIN:VCALENDAR\r\nVERSION:2.0\r\n\
|
||||
BEGIN:VEVENT\r\nUID:meet-1@x\r\nDTSTART:20261020T140000Z\r\nDTEND:20261020T150000Z\r\n\
|
||||
SUMMARY:kickoff\\, phase one\r\nLOCATION:paris\r\n\
|
||||
ORGANIZER:mailto:boss@example.net\r\nATTENDEE:mailto:alice@example.net\r\n\
|
||||
URL:https://meet.example.net/1\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n";
|
||||
|
||||
fn window() -> Window {
|
||||
let from = OffsetDateTime::parse("2026-10-20T00:00:00Z", &Rfc3339).expect("parses");
|
||||
Window::new(from, from + time::Duration::hours(24))
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn a_poll_is_one_get_and_normalizes_the_feed() {
|
||||
let server = MockServer::start().await;
|
||||
Mock::given(method("GET"))
|
||||
.and(path("/remote.php/dav/calendars/personal.ics"))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_string(FEED_BODY))
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
let feed = IcsCalendar::new(format!(
|
||||
"{}/remote.php/dav/calendars/personal.ics",
|
||||
server.uri()
|
||||
));
|
||||
let events = feed.events(&window()).await.expect("polls");
|
||||
assert_eq!(events.len(), 1);
|
||||
let event = &events[0];
|
||||
assert_eq!(event.title, "kickoff, phase one");
|
||||
assert_eq!(event.location.as_deref(), Some("paris"));
|
||||
assert_eq!(event.organizer.as_deref(), Some("boss@example.net"));
|
||||
assert_eq!(event.attendees, vec!["alice@example.net".to_owned()]);
|
||||
assert_eq!(event.url.as_deref(), Some("https://meet.example.net/1"));
|
||||
assert_eq!(
|
||||
event.start,
|
||||
OffsetDateTime::parse("2026-10-20T14:00:00Z", &Rfc3339).expect("parses")
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn a_token_rides_the_authorization_header_only() {
|
||||
let server = MockServer::start().await;
|
||||
Mock::given(method("GET"))
|
||||
.and(header("Authorization", "Bearer sekrit"))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_string(FEED_BODY))
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
let feed = IcsCalendar::new(server.uri()).with_token("sekrit");
|
||||
feed.events(&window()).await.expect("polls");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn a_server_error_is_named_with_a_bounded_body() {
|
||||
let server = MockServer::start().await;
|
||||
Mock::given(method("GET"))
|
||||
.respond_with(ResponseTemplate::new(503).set_body_string("maintenance"))
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
let feed = IcsCalendar::new(server.uri());
|
||||
let err = feed.events(&window()).await.expect_err("loud");
|
||||
assert!(
|
||||
matches!(err, CalendarError::Api { status: 503, .. }),
|
||||
"got: {err:?}"
|
||||
);
|
||||
assert!(err.to_string().contains("maintenance"), "got: {err}");
|
||||
}
|
||||
Reference in new issue
Block a user