mirror of
https://github.com/ankitects/anki.git
synced 2025-09-20 06:52:21 -04:00
fix renaming corner cases and decks.update()
- .update() should update a single deck and preserve usn by default, as that's what existing code expects - decks are automatically renamed when they conflict with an existing name
This commit is contained in:
parent
df48fa8cf7
commit
7daa417dc8
6 changed files with 126 additions and 87 deletions
|
@ -75,7 +75,7 @@ class DeckManager:
|
||||||
self.update_config(g)
|
self.update_config(g)
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
self.update(g)
|
self.update(g, preserve_usn=False)
|
||||||
|
|
||||||
# legacy
|
# legacy
|
||||||
def flush(self):
|
def flush(self):
|
||||||
|
@ -97,7 +97,7 @@ class DeckManager:
|
||||||
|
|
||||||
deck = self.new_deck_legacy(bool(type))
|
deck = self.new_deck_legacy(bool(type))
|
||||||
deck["name"] = name
|
deck["name"] = name
|
||||||
self.update(deck)
|
self.update(deck, preserve_usn=False)
|
||||||
|
|
||||||
# fixme
|
# fixme
|
||||||
hooks.deck_added(deck)
|
hooks.deck_added(deck)
|
||||||
|
@ -205,7 +205,7 @@ class DeckManager:
|
||||||
return self.get_legacy(id)
|
return self.get_legacy(id)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def update(self, g: Dict[str, Any], preserve_usn=False) -> None:
|
def update(self, g: Dict[str, Any], preserve_usn=True) -> None:
|
||||||
"Add or update an existing deck. Used for syncing and merging."
|
"Add or update an existing deck. Used for syncing and merging."
|
||||||
try:
|
try:
|
||||||
self.col.backend.add_or_update_deck_legacy(g, preserve_usn)
|
self.col.backend.add_or_update_deck_legacy(g, preserve_usn)
|
||||||
|
@ -217,12 +217,9 @@ class DeckManager:
|
||||||
def rename(self, g: Dict[str, Any], newName: str) -> None:
|
def rename(self, g: Dict[str, Any], newName: str) -> None:
|
||||||
"Rename deck prefix to NAME if not exists. Updates children."
|
"Rename deck prefix to NAME if not exists. Updates children."
|
||||||
g["name"] = newName
|
g["name"] = newName
|
||||||
self.update(g)
|
self.update(g, preserve_usn=False)
|
||||||
return
|
return
|
||||||
|
|
||||||
# fixme: ensure rename of b in a::b::c generates new b
|
|
||||||
# fixme: renaming may have altered active did order
|
|
||||||
|
|
||||||
def renameForDragAndDrop(self, draggedDeckDid: int, ontoDeckDid: Any) -> None:
|
def renameForDragAndDrop(self, draggedDeckDid: int, ontoDeckDid: Any) -> None:
|
||||||
draggedDeck = self.get(draggedDeckDid)
|
draggedDeck = self.get(draggedDeckDid)
|
||||||
draggedDeckName = draggedDeck["name"]
|
draggedDeckName = draggedDeck["name"]
|
||||||
|
|
|
@ -72,8 +72,10 @@ def test_rename():
|
||||||
assert "hello::world" not in names
|
assert "hello::world" not in names
|
||||||
# create another deck
|
# create another deck
|
||||||
id = d.decks.id("tmp")
|
id = d.decks.id("tmp")
|
||||||
# we can't rename it if it conflicts
|
# automatically adjusted if a duplicate name
|
||||||
assertException(Exception, lambda: d.decks.rename(d.decks.get(id), "foo"))
|
d.decks.rename(d.decks.get(id), "FOO")
|
||||||
|
names = [n.name for n in d.decks.all_names_and_ids()]
|
||||||
|
assert "FOO+" in names
|
||||||
# when renaming, the children should be renamed too
|
# when renaming, the children should be renamed too
|
||||||
d.decks.id("one::two::three")
|
d.decks.id("one::two::three")
|
||||||
id = d.decks.id("one")
|
id = d.decks.id("one")
|
||||||
|
@ -88,11 +90,6 @@ def test_rename():
|
||||||
child = d.decks.get(childId)
|
child = d.decks.get(childId)
|
||||||
assertException(DeckRenameError, lambda: d.decks.rename(child, "filtered::child"))
|
assertException(DeckRenameError, lambda: d.decks.rename(child, "filtered::child"))
|
||||||
assertException(DeckRenameError, lambda: d.decks.rename(child, "FILTERED::child"))
|
assertException(DeckRenameError, lambda: d.decks.rename(child, "FILTERED::child"))
|
||||||
# changing case
|
|
||||||
parentId = d.decks.id("PARENT")
|
|
||||||
d.decks.id("PARENT::CHILD")
|
|
||||||
assertException(DeckRenameError, lambda: d.decks.rename(child, "PARENT::CHILD"))
|
|
||||||
assertException(DeckRenameError, lambda: d.decks.rename(child, "PARENT::child"))
|
|
||||||
|
|
||||||
|
|
||||||
def test_renameForDragAndDrop():
|
def test_renameForDragAndDrop():
|
||||||
|
@ -137,18 +134,10 @@ def test_renameForDragAndDrop():
|
||||||
d.decks.renameForDragAndDrop(chinese_did, None)
|
d.decks.renameForDragAndDrop(chinese_did, None)
|
||||||
assert deckNames() == ["Chinese", "Chinese::HSK", "Languages"]
|
assert deckNames() == ["Chinese", "Chinese::HSK", "Languages"]
|
||||||
|
|
||||||
# can't drack a deck where sibling have same name
|
# decks are renamed if necessary
|
||||||
new_hsk_did = d.decks.id("HSK")
|
|
||||||
assertException(
|
|
||||||
DeckRenameError, lambda: d.decks.renameForDragAndDrop(new_hsk_did, chinese_did)
|
|
||||||
)
|
|
||||||
d.decks.rem(new_hsk_did)
|
|
||||||
|
|
||||||
# can't drack a deck where sibling have same name different case
|
|
||||||
new_hsk_did = d.decks.id("hsk")
|
new_hsk_did = d.decks.id("hsk")
|
||||||
assertException(
|
d.decks.renameForDragAndDrop(new_hsk_did, chinese_did)
|
||||||
DeckRenameError, lambda: d.decks.renameForDragAndDrop(new_hsk_did, chinese_did)
|
assert deckNames() == ["Chinese", "Chinese::HSK", "Chinese::hsk+", "Languages"]
|
||||||
)
|
|
||||||
d.decks.rem(new_hsk_did)
|
d.decks.rem(new_hsk_did)
|
||||||
|
|
||||||
# '' is a convenient alias for the top level DID
|
# '' is a convenient alias for the top level DID
|
||||||
|
|
|
@ -1046,7 +1046,14 @@ impl Backend {
|
||||||
self.with_col(|col| {
|
self.with_col(|col| {
|
||||||
let schema11: DeckSchema11 = serde_json::from_slice(&input.deck)?;
|
let schema11: DeckSchema11 = serde_json::from_slice(&input.deck)?;
|
||||||
let mut deck: Deck = schema11.into();
|
let mut deck: Deck = schema11.into();
|
||||||
col.add_or_update_deck(&mut deck, input.preserve_usn_and_mtime)?;
|
if input.preserve_usn_and_mtime {
|
||||||
|
col.transact(None, |col| {
|
||||||
|
let usn = col.usn()?;
|
||||||
|
col.add_or_update_single_deck(&mut deck, usn)
|
||||||
|
})?;
|
||||||
|
} else {
|
||||||
|
col.add_or_update_deck(&mut deck)?;
|
||||||
|
}
|
||||||
Ok(deck.id.0)
|
Ok(deck.id.0)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -154,8 +154,9 @@ impl Collection {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_missing_deck_ids(&mut self, out: &mut CheckDatabaseOutput) -> Result<()> {
|
fn check_missing_deck_ids(&mut self, out: &mut CheckDatabaseOutput) -> Result<()> {
|
||||||
|
let usn = self.usn()?;
|
||||||
for did in self.storage.missing_decks()? {
|
for did in self.storage.missing_decks()? {
|
||||||
self.recover_missing_deck(did)?;
|
self.recover_missing_deck(did, usn)?;
|
||||||
out.decks_missing += 1;
|
out.decks_missing += 1;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -89,16 +89,6 @@ impl Deck {
|
||||||
self.name.replace("\x1f", "::")
|
self.name.replace("\x1f", "::")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn prepare_for_update(&mut self) {
|
|
||||||
// fixme - we currently only do this when converting from human; should be done in pub methods instead
|
|
||||||
|
|
||||||
// if self.name.contains(invalid_char_for_deck_component) {
|
|
||||||
// self.name = self.name.replace(invalid_char_for_deck_component, "");
|
|
||||||
// }
|
|
||||||
// ensure_string_in_nfc(&mut self.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
// fixme: unify with prepare for update
|
|
||||||
pub(crate) fn set_modified(&mut self, usn: Usn) {
|
pub(crate) fn set_modified(&mut self, usn: Usn) {
|
||||||
self.mtime_secs = TimestampSecs::now();
|
self.mtime_secs = TimestampSecs::now();
|
||||||
self.usn = usn;
|
self.usn = usn;
|
||||||
|
@ -115,11 +105,11 @@ impl Deck {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// fixme: need to bump usn on upgrade if we rename
|
|
||||||
fn invalid_char_for_deck_component(c: char) -> bool {
|
fn invalid_char_for_deck_component(c: char) -> bool {
|
||||||
c.is_ascii_control() || c == '"'
|
c.is_ascii_control() || c == '"'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fixme: need to bump usn on upgrade if we rename
|
||||||
fn normalized_deck_name_component(comp: &str) -> Cow<str> {
|
fn normalized_deck_name_component(comp: &str) -> Cow<str> {
|
||||||
let mut out = normalize_to_nfc(comp);
|
let mut out = normalize_to_nfc(comp);
|
||||||
if out.contains(invalid_char_for_deck_component) {
|
if out.contains(invalid_char_for_deck_component) {
|
||||||
|
@ -127,15 +117,29 @@ fn normalized_deck_name_component(comp: &str) -> Cow<str> {
|
||||||
}
|
}
|
||||||
let trimmed = out.trim();
|
let trimmed = out.trim();
|
||||||
if trimmed.is_empty() {
|
if trimmed.is_empty() {
|
||||||
"blank".into()
|
"blank".to_string().into()
|
||||||
} else if trimmed.len() != out.len() {
|
} else if trimmed.len() != out.len() {
|
||||||
// fixme: trimming leading/trailing spaces may break old clients if we don't bump mod
|
|
||||||
trimmed.to_string().into()
|
trimmed.to_string().into()
|
||||||
} else {
|
} else {
|
||||||
out
|
out
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn normalize_native_name(name: &str) -> Cow<str> {
|
||||||
|
if name
|
||||||
|
.split('\x1f')
|
||||||
|
.any(|comp| matches!(normalized_deck_name_component(comp), Cow::Owned(_)))
|
||||||
|
{
|
||||||
|
name.split('\x1f')
|
||||||
|
.map(normalized_deck_name_component)
|
||||||
|
.collect::<String>()
|
||||||
|
.into()
|
||||||
|
} else {
|
||||||
|
// no changes required
|
||||||
|
name.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn human_deck_name_to_native(name: &str) -> String {
|
pub(crate) fn human_deck_name_to_native(name: &str) -> String {
|
||||||
let mut out = String::with_capacity(name.len());
|
let mut out = String::with_capacity(name.len());
|
||||||
for comp in name.split("::") {
|
for comp in name.split("::") {
|
||||||
|
@ -146,7 +150,6 @@ pub(crate) fn human_deck_name_to_native(name: &str) -> String {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Collection {
|
impl Collection {
|
||||||
// fixme: this cache may belong in CardGenContext?
|
|
||||||
pub(crate) fn get_deck(&mut self, did: DeckID) -> Result<Option<Arc<Deck>>> {
|
pub(crate) fn get_deck(&mut self, did: DeckID) -> Result<Option<Arc<Deck>>> {
|
||||||
if let Some(deck) = self.state.deck_cache.get(&did) {
|
if let Some(deck) = self.state.deck_cache.get(&did) {
|
||||||
return Ok(Some(deck.clone()));
|
return Ok(Some(deck.clone()));
|
||||||
|
@ -192,43 +195,52 @@ impl Collection {
|
||||||
self.storage.deck_is_empty(DeckID(1))
|
self.storage.deck_is_empty(DeckID(1))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn add_or_update_deck(&mut self, deck: &mut Deck, preserve_usn: bool) -> Result<()> {
|
/// Normalize deck name and rename if not unique. Bumps mtime and usn if
|
||||||
// fixme: vet cache clearing
|
/// deck was modified.
|
||||||
|
fn prepare_deck_for_update(&mut self, deck: &mut Deck, usn: Usn) -> Result<()> {
|
||||||
|
if let Cow::Owned(name) = normalize_native_name(&deck.name) {
|
||||||
|
deck.name = name;
|
||||||
|
deck.set_modified(usn);
|
||||||
|
}
|
||||||
|
self.ensure_deck_name_unique(deck, usn)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add or update an existing deck modified by the user. May add parents,
|
||||||
|
/// or rename children as required.
|
||||||
|
pub(crate) fn add_or_update_deck(&mut self, deck: &mut Deck) -> Result<()> {
|
||||||
self.state.deck_cache.clear();
|
self.state.deck_cache.clear();
|
||||||
|
|
||||||
self.transact(None, |col| {
|
self.transact(None, |col| {
|
||||||
let usn = col.usn()?;
|
let usn = col.usn()?;
|
||||||
|
|
||||||
deck.prepare_for_update();
|
|
||||||
|
|
||||||
// fixme: bail
|
|
||||||
assert!(!deck.name.contains("::"));
|
|
||||||
|
|
||||||
// fixme: check deck name is not duplicate
|
|
||||||
// handle blank deck name, etc
|
|
||||||
|
|
||||||
if !preserve_usn {
|
|
||||||
deck.set_modified(usn);
|
deck.set_modified(usn);
|
||||||
}
|
|
||||||
|
|
||||||
if deck.id.0 == 0 {
|
if deck.id.0 == 0 {
|
||||||
|
col.prepare_deck_for_update(deck, usn)?;
|
||||||
col.match_or_create_parents(deck)?;
|
col.match_or_create_parents(deck)?;
|
||||||
col.storage.add_deck(deck)
|
col.storage.add_deck(deck)
|
||||||
} else {
|
} else if let Some(existing_deck) = col.storage.get_deck(deck.id)? {
|
||||||
if let Some(existing_deck) = col.storage.get_deck(deck.id)? {
|
|
||||||
if existing_deck.name != deck.name {
|
if existing_deck.name != deck.name {
|
||||||
return col.update_renamed_deck(existing_deck, deck, usn);
|
col.update_renamed_deck(existing_deck, deck, usn)
|
||||||
|
} else {
|
||||||
|
col.add_or_update_single_deck(deck, usn)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// fixme: this should only happen in the syncing case, and we should
|
Err(AnkiError::invalid_input("updating non-existent deck"))
|
||||||
// ensure there are no missing parents at the end of the sync
|
|
||||||
}
|
|
||||||
col.storage.update_deck(deck)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ensure_deck_name_unique(&self, deck: &mut Deck) -> Result<()> {
|
/// Add/update a single deck when syncing/importing. Ensures name is unique
|
||||||
|
/// & normalized, but does not check parents/children or update mtime
|
||||||
|
/// (unless the name was changed). Caller must set up transaction.
|
||||||
|
pub(crate) fn add_or_update_single_deck(&mut self, deck: &mut Deck, usn: Usn) -> Result<()> {
|
||||||
|
self.state.deck_cache.clear();
|
||||||
|
self.prepare_deck_for_update(deck, usn)?;
|
||||||
|
self.storage.update_deck(deck)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ensure_deck_name_unique(&self, deck: &mut Deck, usn: Usn) -> Result<()> {
|
||||||
loop {
|
loop {
|
||||||
match self.storage.get_deck_id(&deck.name)? {
|
match self.storage.get_deck_id(&deck.name)? {
|
||||||
Some(did) if did == deck.id => {
|
Some(did) if did == deck.id => {
|
||||||
|
@ -238,20 +250,18 @@ impl Collection {
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
deck.name += "+";
|
deck.name += "+";
|
||||||
|
deck.set_modified(usn);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn recover_missing_deck(&mut self, did: DeckID) -> Result<()> {
|
pub(crate) fn recover_missing_deck(&mut self, did: DeckID, usn: Usn) -> Result<()> {
|
||||||
let mut deck = Deck::new_normal();
|
let mut deck = Deck::new_normal();
|
||||||
deck.id = did;
|
deck.id = did;
|
||||||
deck.name = format!("recovered{}", did);
|
deck.name = format!("recovered{}", did);
|
||||||
self.ensure_deck_name_unique(&mut deck)?;
|
deck.set_modified(usn);
|
||||||
deck.prepare_for_update();
|
self.add_or_update_single_deck(&mut deck, usn)
|
||||||
self.storage.update_deck(&deck)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_or_create_normal_deck(&mut self, human_name: &str) -> Result<Deck> {
|
pub fn get_or_create_normal_deck(&mut self, human_name: &str) -> Result<Deck> {
|
||||||
|
@ -261,26 +271,23 @@ impl Collection {
|
||||||
} else {
|
} else {
|
||||||
let mut deck = Deck::new_normal();
|
let mut deck = Deck::new_normal();
|
||||||
deck.name = native_name;
|
deck.name = native_name;
|
||||||
self.add_or_update_deck(&mut deck, false)?;
|
self.add_or_update_deck(&mut deck)?;
|
||||||
Ok(deck)
|
Ok(deck)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_renamed_deck(&mut self, existing: Deck, updated: &mut Deck, usn: Usn) -> Result<()> {
|
fn update_renamed_deck(&mut self, existing: Deck, updated: &mut Deck, usn: Usn) -> Result<()> {
|
||||||
// new name should not conflict with a different deck
|
// match closest parent name
|
||||||
if let Some(other_did) = self.storage.get_deck_id(&updated.name)? {
|
|
||||||
if other_did != updated.id {
|
|
||||||
// fixme: this could break when syncing
|
|
||||||
return Err(AnkiError::Existing);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.match_or_create_parents(updated)?;
|
self.match_or_create_parents(updated)?;
|
||||||
self.storage.update_deck(updated)?;
|
// rename children
|
||||||
self.rename_child_decks(&existing, &updated.name, usn)
|
self.rename_child_decks(&existing, &updated.name, usn)?;
|
||||||
|
// update deck
|
||||||
|
self.add_or_update_single_deck(updated, usn)?;
|
||||||
|
// after updating, we need to ensure all grandparents exist, which may not be the case
|
||||||
|
// in the parent->child case
|
||||||
|
self.create_missing_parents(&updated.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// fixme: make sure this handles foo::bar and FOO::baz
|
|
||||||
fn rename_child_decks(&mut self, old: &Deck, new_name: &str, usn: Usn) -> Result<()> {
|
fn rename_child_decks(&mut self, old: &Deck, new_name: &str, usn: Usn) -> Result<()> {
|
||||||
let children = self.storage.child_decks(old)?;
|
let children = self.storage.child_decks(old)?;
|
||||||
let old_component_count = old.name.matches('\x1f').count() + 1;
|
let old_component_count = old.name.matches('\x1f').count() + 1;
|
||||||
|
@ -372,6 +379,9 @@ impl Collection {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_deck_and_child_decks(&mut self, did: DeckID) -> Result<()> {
|
pub fn remove_deck_and_child_decks(&mut self, did: DeckID) -> Result<()> {
|
||||||
|
// fixme: vet cache clearing
|
||||||
|
self.state.deck_cache.clear();
|
||||||
|
|
||||||
self.transact(None, |col| {
|
self.transact(None, |col| {
|
||||||
let usn = col.usn()?;
|
let usn = col.usn()?;
|
||||||
|
|
||||||
|
@ -400,9 +410,8 @@ impl Collection {
|
||||||
let mut deck = deck.to_owned();
|
let mut deck = deck.to_owned();
|
||||||
// fixme: separate key
|
// fixme: separate key
|
||||||
deck.name = self.i18n.tr(TR::DeckConfigDefaultName).into();
|
deck.name = self.i18n.tr(TR::DeckConfigDefaultName).into();
|
||||||
self.ensure_deck_name_unique(&mut deck)?;
|
|
||||||
deck.set_modified(usn);
|
deck.set_modified(usn);
|
||||||
self.storage.update_deck(&deck)?;
|
self.add_or_update_single_deck(&mut deck, usn)?;
|
||||||
} else {
|
} else {
|
||||||
self.storage.remove_deck(deck.id)?;
|
self.storage.remove_deck(deck.id)?;
|
||||||
self.storage.add_deck_grave(deck.id, usn)?;
|
self.storage.add_deck_grave(deck.id, usn)?;
|
||||||
|
@ -525,12 +534,48 @@ mod test {
|
||||||
let _ = col.get_or_create_normal_deck("foo::bar::baz")?;
|
let _ = col.get_or_create_normal_deck("foo::bar::baz")?;
|
||||||
let mut top_deck = col.get_or_create_normal_deck("foo")?;
|
let mut top_deck = col.get_or_create_normal_deck("foo")?;
|
||||||
top_deck.name = "other".into();
|
top_deck.name = "other".into();
|
||||||
col.add_or_update_deck(&mut top_deck, false)?;
|
col.add_or_update_deck(&mut top_deck)?;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
sorted_names(&col),
|
sorted_names(&col),
|
||||||
vec!["Default", "other", "other::bar", "other::bar::baz"]
|
vec!["Default", "other", "other::bar", "other::bar::baz"]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// should do the right thing in the middle of the tree as well
|
||||||
|
let mut middle = col.get_or_create_normal_deck("other::bar")?;
|
||||||
|
middle.name = "quux\x1ffoo".into();
|
||||||
|
col.add_or_update_deck(&mut middle)?;
|
||||||
|
assert_eq!(
|
||||||
|
sorted_names(&col),
|
||||||
|
vec!["Default", "other", "quux", "quux::foo", "quux::foo::baz"]
|
||||||
|
);
|
||||||
|
|
||||||
|
// add another child
|
||||||
|
let _ = col.get_or_create_normal_deck("quux::foo::baz2");
|
||||||
|
|
||||||
|
// quux::foo -> quux::foo::baz::four
|
||||||
|
// means quux::foo::baz2 should be quux::foo::baz::four::baz2
|
||||||
|
// and a new quux::foo should have been created
|
||||||
|
middle.name = "quux\x1ffoo\x1fbaz\x1ffour".into();
|
||||||
|
col.add_or_update_deck(&mut middle)?;
|
||||||
|
assert_eq!(
|
||||||
|
sorted_names(&col),
|
||||||
|
vec![
|
||||||
|
"Default",
|
||||||
|
"other",
|
||||||
|
"quux",
|
||||||
|
"quux::foo",
|
||||||
|
"quux::foo::baz",
|
||||||
|
"quux::foo::baz::four",
|
||||||
|
"quux::foo::baz::four::baz",
|
||||||
|
"quux::foo::baz::four::baz2"
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
// should handle name conflicts
|
||||||
|
middle.name = "other".into();
|
||||||
|
col.add_or_update_deck(&mut middle)?;
|
||||||
|
assert_eq!(middle.name, "other+");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -542,7 +587,7 @@ mod test {
|
||||||
|
|
||||||
let mut default = col.get_or_create_normal_deck("default")?;
|
let mut default = col.get_or_create_normal_deck("default")?;
|
||||||
default.name = "one\x1ftwo".into();
|
default.name = "one\x1ftwo".into();
|
||||||
col.add_or_update_deck(&mut default, false)?;
|
col.add_or_update_deck(&mut default)?;
|
||||||
|
|
||||||
// create a non-default deck confusingly named "default"
|
// create a non-default deck confusingly named "default"
|
||||||
let _fake_default = col.get_or_create_normal_deck("default")?;
|
let _fake_default = col.get_or_create_normal_deck("default")?;
|
||||||
|
|
|
@ -335,9 +335,9 @@ mod test {
|
||||||
|
|
||||||
// simulate answering a card
|
// simulate answering a card
|
||||||
child_deck.common.new_studied = 1;
|
child_deck.common.new_studied = 1;
|
||||||
col.add_or_update_deck(&mut child_deck, false)?;
|
col.add_or_update_deck(&mut child_deck)?;
|
||||||
parent_deck.common.new_studied = 1;
|
parent_deck.common.new_studied = 1;
|
||||||
col.add_or_update_deck(&mut parent_deck, false)?;
|
col.add_or_update_deck(&mut parent_deck)?;
|
||||||
|
|
||||||
// with the default limit of 20, there should still be 4 due
|
// with the default limit of 20, there should still be 4 due
|
||||||
let tree = col.deck_tree(true, None)?;
|
let tree = col.deck_tree(true, None)?;
|
||||||
|
|
Loading…
Reference in a new issue