Fix TUI field visibility and contrast
This commit is contained in:
@@ -20,6 +20,8 @@ const MINIMUM_WIDTH: u16 = 40;
|
||||
const MINIMUM_HEIGHT: u16 = 8;
|
||||
const LARGE_OTP_HEIGHT: u16 = 8;
|
||||
const LARGE_OTP_ROWS: usize = 5;
|
||||
const SELECTED_FOREGROUND: Color = Color::Black;
|
||||
const SELECTED_BACKGROUND: Color = Color::White;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum ColorCapability {
|
||||
@@ -72,7 +74,9 @@ pub fn draw_with_color_capability(frame: &mut Frame, app: &App, capability: Colo
|
||||
draw_inner(frame, app);
|
||||
if capability == ColorCapability::Monochrome {
|
||||
for cell in &mut frame.buffer_mut().content {
|
||||
if cell.fg == Color::White && cell.bg == Color::Blue {
|
||||
if (cell.fg == SELECTED_FOREGROUND && cell.bg == SELECTED_BACKGROUND)
|
||||
|| (cell.fg == Color::White && cell.bg == Color::Blue)
|
||||
{
|
||||
cell.modifier.insert(Modifier::REVERSED);
|
||||
}
|
||||
cell.set_fg(Color::Reset).set_bg(Color::Reset);
|
||||
@@ -560,9 +564,12 @@ fn pane_block(title: &'static str, focused: bool) -> Block<'static> {
|
||||
}
|
||||
|
||||
fn selected_line<'a>(mut spans: Vec<Span<'a>>) -> Line<'a> {
|
||||
let style = Style::default().bg(Color::Blue).fg(Color::White);
|
||||
let style = Style::default()
|
||||
.fg(SELECTED_FOREGROUND)
|
||||
.bg(SELECTED_BACKGROUND)
|
||||
.add_modifier(Modifier::BOLD);
|
||||
for span in &mut spans {
|
||||
span.style = span.style.bg(Color::Blue).fg(Color::White);
|
||||
span.style = style;
|
||||
}
|
||||
Line::from(spans).style(style)
|
||||
}
|
||||
@@ -984,7 +991,7 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::app::Transition;
|
||||
use crate::sidebar::TestTreeNode;
|
||||
use crate::viewer::test_support::fixture_document;
|
||||
use crate::viewer::test_support::{fixture_document, fixture_document_from_plaintext};
|
||||
|
||||
fn render(width: u16, height: u16, app: &App) -> String {
|
||||
let backend = TestBackend::new(width, height);
|
||||
@@ -1373,6 +1380,60 @@ mod tests {
|
||||
assert!(!format!("{app:?}").contains("pässwörd-猫"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn authenticated_viewer_renders_ordinary_metadata_and_rehides_secrets() {
|
||||
let (_store, document) = fixture_document_from_plaintext(
|
||||
"metadata/account",
|
||||
b"vault-secret\nautoType_enabled: true\nicon: Internet\nicon: Custom Icon\ntitle: Caf\xc3\xa9\ncustom: hidden-value\n",
|
||||
);
|
||||
let mut app = App::new();
|
||||
app.sidebar_mut().replace_test_tree(vec![TestTreeNode {
|
||||
path: "metadata/account".to_owned(),
|
||||
name: "account".to_owned(),
|
||||
directory: false,
|
||||
indicators: ironstorage::read::TreeNodeIndicators::default(),
|
||||
children: vec![],
|
||||
}]);
|
||||
assert!(matches!(
|
||||
app.dispatch(crate::action::Action::Activate),
|
||||
crate::app::AppEffect::AuthenticateEntry(entry) if entry == "metadata/account"
|
||||
));
|
||||
assert!(app.authentication_granted("metadata/account".to_owned()));
|
||||
let token = app.begin_request();
|
||||
assert_eq!(
|
||||
app.apply_result(crate::app::AsyncResult {
|
||||
token,
|
||||
payload: Ok(crate::app::AsyncPayload::DocumentLoaded {
|
||||
entry: "metadata/account".to_owned(),
|
||||
document: Box::new(document),
|
||||
}),
|
||||
}),
|
||||
crate::app::ResultDisposition::Applied
|
||||
);
|
||||
|
||||
let unlocked = render(140, 20, &app);
|
||||
assert!(unlocked.contains("autoType_enabled: true"));
|
||||
assert!(unlocked.contains("icon: Internet"));
|
||||
assert!(unlocked.contains("icon: Custom Icon"));
|
||||
assert!(unlocked.contains("title: Café"));
|
||||
assert!(unlocked.contains("password: ••••••••"));
|
||||
assert!(unlocked.contains("custom: ••••••••"));
|
||||
assert!(!unlocked.contains("vault-secret"));
|
||||
assert!(!unlocked.contains("hidden-value"));
|
||||
|
||||
app.dispatch(crate::action::Action::Reveal);
|
||||
assert!(render(140, 20, &app).contains("vault-secret"));
|
||||
app.dispatch(crate::action::Action::FocusNext);
|
||||
assert!(!render(140, 20, &app).contains("vault-secret"));
|
||||
|
||||
app.dispatch(crate::action::Action::FocusPrevious);
|
||||
app.dispatch(crate::action::Action::Reveal);
|
||||
app.forced_relock("Authentication expired");
|
||||
let locked = render(140, 20, &app);
|
||||
assert!(!locked.contains("vault-secret"));
|
||||
assert!(!locked.contains("autoType_enabled: true"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reveal_is_explicit_and_focus_change_or_lock_removes_secret_from_rendering() {
|
||||
let mut app = App::new();
|
||||
@@ -1591,13 +1652,17 @@ mod tests {
|
||||
.any(|span| span.content.contains("code 123456"))
|
||||
);
|
||||
assert!(selected.spans.iter().all(|span| {
|
||||
span.style.fg == Some(Color::White) && span.style.bg == Some(Color::Blue)
|
||||
span.style.fg == Some(SELECTED_FOREGROUND)
|
||||
&& span.style.bg == Some(SELECTED_BACKGROUND)
|
||||
&& span.style.add_modifier.contains(Modifier::BOLD)
|
||||
}));
|
||||
|
||||
let editor = EntryEditor::new(fixture_document("email/personal"));
|
||||
let editor_selected = &editor_lines(&editor)[0];
|
||||
assert!(editor_selected.spans.iter().all(|span| {
|
||||
span.style.fg == Some(Color::White) && span.style.bg == Some(Color::Blue)
|
||||
span.style.fg == Some(SELECTED_FOREGROUND)
|
||||
&& span.style.bg == Some(SELECTED_BACKGROUND)
|
||||
&& span.style.add_modifier.contains(Modifier::BOLD)
|
||||
}));
|
||||
|
||||
let backend = TestBackend::new(60, 16);
|
||||
|
||||
@@ -160,8 +160,9 @@ pub(crate) mod test_support {
|
||||
use ironstorage::{
|
||||
crypto::{KeyInfo, KeyStore, SecretProvider, SecretProviderError},
|
||||
document::{EntryDocument, EntryDocumentService},
|
||||
repository::{Repository, SecretBytes},
|
||||
repository::{EntryPath, Repository, SecretBytes},
|
||||
};
|
||||
use tempfile::TempDir;
|
||||
|
||||
struct FixtureSecrets;
|
||||
|
||||
@@ -191,6 +192,31 @@ pub(crate) mod test_support {
|
||||
.open(entry, &mut FixtureSecrets)
|
||||
.expect("fixture document")
|
||||
}
|
||||
|
||||
pub(crate) fn fixture_document_from_plaintext(
|
||||
entry: &str,
|
||||
plaintext: &[u8],
|
||||
) -> (TempDir, EntryDocument) {
|
||||
let root = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("../../crates/storage/tests/fixtures/compatibility");
|
||||
let store = tempfile::tempdir().expect("temporary fixture store");
|
||||
let repository = Repository::open(store.path()).expect("fixture repository");
|
||||
let keys = KeyStore::load(root.join("keys")).expect("fixture keys");
|
||||
let recipients = keys
|
||||
.resolve_recipients(b"7E5C5241B25F6FFAAD717EBFA132DCB2DC23AE30\n")
|
||||
.expect("fixture recipient");
|
||||
let ciphertext = keys
|
||||
.encrypt(SecretBytes::new(plaintext.to_vec()), &recipients)
|
||||
.expect("fixture encryption");
|
||||
let path = EntryPath::parse(entry).expect("fixture entry path");
|
||||
repository
|
||||
.write_entry(&path, &ciphertext)
|
||||
.expect("fixture entry write");
|
||||
let document = EntryDocumentService::new(&repository, &keys)
|
||||
.open(entry, &mut FixtureSecrets)
|
||||
.expect("fixture document");
|
||||
(store, document)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
Reference in New Issue
Block a user