Implement native iPhone entry editor
This commit is contained in:
@@ -9,7 +9,10 @@ use ironstorage::{
|
||||
document::{
|
||||
DocumentError, EntryDocumentService, EntryFieldDraft, EntryFieldKind, EntrySensitivity,
|
||||
},
|
||||
mobile_entry::{MobileEntryPage, MobileEntrySectionKind, field_value},
|
||||
mobile_entry::{
|
||||
MobileEntryDraft, MobileEntryEditorError, MobileEntryEditorFieldKind,
|
||||
MobileEntryEditorInput, MobileEntryPage, MobileEntrySectionKind, field_value,
|
||||
},
|
||||
recipient::RecipientPolicyManager,
|
||||
repository::{EntryPath, Repository, SecretBytes},
|
||||
write::{EntryCommit, EntryCommitError, EntryCommitter, WriteError},
|
||||
@@ -624,6 +627,133 @@ fn atomic_save_creates_updates_and_rejects_stale_documents() -> TestResult {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mobile_editor_creates_edits_generates_and_preserves_dynamic_fields() -> TestResult {
|
||||
let fixture = FixtureSet::load()?;
|
||||
let store = fixture.materialize_store("basic")?;
|
||||
let repository = Repository::open(store.path())?;
|
||||
let keys = KeyStore::load(fixture.path("keys"))?;
|
||||
let mut secrets = FixtureSecrets::all(&fixture);
|
||||
write_plaintext(
|
||||
&repository,
|
||||
&keys,
|
||||
"documents/mobile-editor",
|
||||
b"old password\nlogin: alice\ncomments: first\nsecond\nnote: repeated one\nnote: repeated two\nbinary: \xff\n",
|
||||
)?;
|
||||
let service = EntryDocumentService::new(&repository, &keys);
|
||||
let document = service.open("documents/mobile-editor", &mut secrets)?;
|
||||
let mut draft = MobileEntryDraft::new(document, false);
|
||||
let page = draft.page();
|
||||
assert!(!page.dirty());
|
||||
assert!(!page.creating());
|
||||
assert_eq!(page.default_password_length(), 25);
|
||||
assert_eq!(page.fields().len(), 6);
|
||||
assert!(page.fields()[2].multiline());
|
||||
assert_eq!(page.fields()[2].value(), Some("first\nsecond"));
|
||||
assert!(page.fields()[5].value().is_none());
|
||||
|
||||
let password = page.fields()[0].id();
|
||||
assert!(matches!(
|
||||
draft.remove(password),
|
||||
Err(MobileEntryEditorError::ProtectedPasswordField)
|
||||
));
|
||||
assert!(matches!(
|
||||
draft.reorder(password, 1),
|
||||
Err(MobileEntryEditorError::ProtectedPasswordField)
|
||||
));
|
||||
assert!(draft.generate_password(Some(0), false).is_err());
|
||||
|
||||
let repeated_one = page.fields()[3].id();
|
||||
let inputs = page
|
||||
.fields()
|
||||
.iter()
|
||||
.map(|field| {
|
||||
let (name, value) = if field.label() == "Login" {
|
||||
(Some("username".to_owned()), Some("bob".to_owned()))
|
||||
} else if field.label() == "Comments" {
|
||||
(
|
||||
field.name().map(str::to_owned),
|
||||
Some("heading\nline: preserved\n\nend".to_owned()),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
field.name().map(str::to_owned),
|
||||
field.value().map(str::to_owned),
|
||||
)
|
||||
};
|
||||
MobileEntryEditorInput::new(field.id(), name, value)
|
||||
})
|
||||
.collect();
|
||||
draft.apply(inputs)?;
|
||||
draft.add(
|
||||
MobileEntryEditorFieldKind::Field,
|
||||
Some("tags".to_owned()),
|
||||
"work, mobile".to_owned(),
|
||||
)?;
|
||||
draft.add(
|
||||
MobileEntryEditorFieldKind::Note,
|
||||
None,
|
||||
"open note\nsecond line".to_owned(),
|
||||
)?;
|
||||
draft.remove(repeated_one)?;
|
||||
let open_note = draft.page().fields().last().expect("open note").id();
|
||||
draft.reorder(open_note, 2)?;
|
||||
draft.generate_password(Some(32), true)?;
|
||||
|
||||
let edited = draft.page();
|
||||
assert!(edited.dirty());
|
||||
let password = edited.fields().first().expect("password").value().unwrap();
|
||||
assert_eq!(password.len(), 32);
|
||||
assert!(
|
||||
password
|
||||
.chars()
|
||||
.all(|character| character.is_ascii_alphanumeric())
|
||||
);
|
||||
assert_eq!(edited.fields()[2].value(), Some("open note\nsecond line"));
|
||||
|
||||
let mut committer = Committer::default();
|
||||
service.save_recoverable(draft.document(), None, &mut committer)?;
|
||||
let reopened = service.open("documents/mobile-editor", &mut secrets)?;
|
||||
assert_eq!(
|
||||
reopened
|
||||
.fields()
|
||||
.iter()
|
||||
.filter(|field| field.metadata().name() == Some("note"))
|
||||
.count(),
|
||||
1
|
||||
);
|
||||
assert!(
|
||||
reopened.fields().iter().any(|field| {
|
||||
field.metadata().name() == Some("username") && field.value() == b"bob"
|
||||
})
|
||||
);
|
||||
assert!(reopened.fields().iter().any(|field| {
|
||||
field.metadata().name() == Some("comments")
|
||||
&& field.value() == b"heading\nline: preserved\n\nend"
|
||||
}));
|
||||
assert!(reopened.fields().iter().any(|field| {
|
||||
field.metadata().name() == Some("tags") && field.value() == b"work, mobile"
|
||||
}));
|
||||
assert!(
|
||||
reopened
|
||||
.fields()
|
||||
.iter()
|
||||
.any(|field| { field.metadata().name() == Some("binary") && field.value() == b"\xff" })
|
||||
);
|
||||
|
||||
let mut created = service.open("documents/cancelled-mobile", &mut secrets)?;
|
||||
created.add(0, EntryFieldDraft::line(Vec::new())?)?;
|
||||
let created = MobileEntryDraft::new(created, true);
|
||||
assert!(created.page().creating());
|
||||
assert!(created.page().dirty());
|
||||
drop(created);
|
||||
assert!(matches!(
|
||||
repository.read_entry(&EntryPath::parse("documents/cancelled-mobile")?),
|
||||
Err(ironstorage::repository::RepositoryError::NotFound { .. })
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_compatibility_entry_round_trips_as_a_document() -> TestResult {
|
||||
let fixture = FixtureSet::load()?;
|
||||
|
||||
Reference in New Issue
Block a user