# coding: utf-8 import pytest from anki.consts import * from anki.rsbackend import BuiltinSortKind from tests.shared import getEmptyCol, isNearCutoff class DummyCollection: def weakref(self): return None def test_findCards(): deck = getEmptyCol() f = deck.newNote() f["Front"] = "dog" f["Back"] = "cat" f.tags.append("monkey animal_1 * %") f1id = f.id deck.addNote(f) firstCardId = f.cards()[0].id f = deck.newNote() f["Front"] = "goats are fun" f["Back"] = "sheep" f.tags.append("sheep goat horse animal11") deck.addNote(f) f2id = f.id f = deck.newNote() f["Front"] = "cat" f["Back"] = "sheep" deck.addNote(f) catCard = f.cards()[0] m = deck.models.current() mm = deck.models t = mm.newTemplate("Reverse") t["qfmt"] = "{{Back}}" t["afmt"] = "{{Front}}" mm.addTemplate(m, t) mm.save(m) f = deck.newNote() f["Front"] = "test" f["Back"] = "foo bar" deck.addNote(f) deck.save() latestCardIds = [c.id for c in f.cards()] # tag searches assert len(deck.findCards("tag:*")) == 5 assert len(deck.findCards("tag:\\*")) == 1 assert len(deck.findCards("tag:%")) == 5 assert len(deck.findCards("tag:\\%")) == 1 assert len(deck.findCards("tag:animal_1")) == 2 assert len(deck.findCards("tag:animal\\_1")) == 1 assert not deck.findCards("tag:donkey") assert len(deck.findCards("tag:sheep")) == 1 assert len(deck.findCards("tag:sheep tag:goat")) == 1 assert len(deck.findCards("tag:sheep tag:monkey")) == 0 assert len(deck.findCards("tag:monkey")) == 1 assert len(deck.findCards("tag:sheep -tag:monkey")) == 1 assert len(deck.findCards("-tag:sheep")) == 4 deck.tags.bulkAdd(deck.db.list("select id from notes"), "foo bar") assert len(deck.findCards("tag:foo")) == len(deck.findCards("tag:bar")) == 5 deck.tags.bulkRem(deck.db.list("select id from notes"), "foo") assert len(deck.findCards("tag:foo")) == 0 assert len(deck.findCards("tag:bar")) == 5 # text searches assert len(deck.findCards("cat")) == 2 assert len(deck.findCards("cat -dog")) == 1 assert len(deck.findCards("cat -dog")) == 1 assert len(deck.findCards("are goats")) == 1 assert len(deck.findCards('"are goats"')) == 0 assert len(deck.findCards('"goats are"')) == 1 # card states c = f.cards()[0] c.queue = c.type = CARD_TYPE_REV assert deck.findCards("is:review") == [] c.flush() assert deck.findCards("is:review") == [c.id] assert deck.findCards("is:due") == [] c.due = 0 c.queue = QUEUE_TYPE_REV c.flush() assert deck.findCards("is:due") == [c.id] assert len(deck.findCards("-is:due")) == 4 c.queue = -1 # ensure this card gets a later mod time c.flush() deck.db.execute("update cards set mod = mod + 1 where id = ?", c.id) assert deck.findCards("is:suspended") == [c.id] # nids assert deck.findCards("nid:54321") == [] assert len(deck.findCards("nid:%d" % f.id)) == 2 assert len(deck.findCards("nid:%d,%d" % (f1id, f2id))) == 2 # templates assert len(deck.findCards("card:foo")) == 0 assert len(deck.findCards('"card:card 1"')) == 4 assert len(deck.findCards("card:reverse")) == 1 assert len(deck.findCards("card:1")) == 4 assert len(deck.findCards("card:2")) == 1 # fields assert len(deck.findCards("front:dog")) == 1 assert len(deck.findCards("-front:dog")) == 4 assert len(deck.findCards("front:sheep")) == 0 assert len(deck.findCards("back:sheep")) == 2 assert len(deck.findCards("-back:sheep")) == 3 assert len(deck.findCards("front:do")) == 0 assert len(deck.findCards("front:*")) == 5 # ordering deck.conf["sortType"] = "noteCrt" deck.flush() assert deck.findCards("front:*", order=True)[-1] in latestCardIds assert deck.findCards("", order=True)[-1] in latestCardIds deck.conf["sortType"] = "noteFld" deck.flush() assert deck.findCards("", order=True)[0] == catCard.id assert deck.findCards("", order=True)[-1] in latestCardIds deck.conf["sortType"] = "cardMod" deck.flush() assert deck.findCards("", order=True)[-1] in latestCardIds assert deck.findCards("", order=True)[0] == firstCardId deck.conf["sortBackwards"] = True deck.flush() assert deck.findCards("", order=True)[0] in latestCardIds assert ( deck.find_cards("", order=BuiltinSortKind.CARD_DUE, reverse=False)[0] == firstCardId ) assert ( deck.find_cards("", order=BuiltinSortKind.CARD_DUE, reverse=True)[0] != firstCardId ) # model assert len(deck.findCards("note:basic")) == 5 assert len(deck.findCards("-note:basic")) == 0 assert len(deck.findCards("-note:foo")) == 5 # deck assert len(deck.findCards("deck:default")) == 5 assert len(deck.findCards("-deck:default")) == 0 assert len(deck.findCards("-deck:foo")) == 5 assert len(deck.findCards("deck:def*")) == 5 assert len(deck.findCards("deck:*EFAULT")) == 5 assert len(deck.findCards("deck:*cefault")) == 0 # full search f = deck.newNote() f["Front"] = "helloworld" f["Back"] = "abc" deck.addNote(f) # as it's the sort field, it matches assert len(deck.findCards("helloworld")) == 2 # assert len(deck.findCards("helloworld", full=True)) == 2 # if we put it on the back, it won't (f["Front"], f["Back"]) = (f["Back"], f["Front"]) f.flush() assert len(deck.findCards("helloworld")) == 0 # assert len(deck.findCards("helloworld", full=True)) == 2 # assert len(deck.findCards("back:helloworld", full=True)) == 2 # searching for an invalid special tag should not error with pytest.raises(Exception): len(deck.findCards("is:invalid")) # should be able to limit to parent deck, no children id = deck.db.scalar("select id from cards limit 1") deck.db.execute( "update cards set did = ? where id = ?", deck.decks.id("Default::Child"), id ) deck.save() assert len(deck.findCards("deck:default")) == 7 assert len(deck.findCards("deck:default::child")) == 1 assert len(deck.findCards("deck:default -deck:default::*")) == 6 # properties id = deck.db.scalar("select id from cards limit 1") deck.db.execute( "update cards set queue=2, ivl=10, reps=20, due=30, factor=2200 " "where id = ?", id, ) assert len(deck.findCards("prop:ivl>5")) == 1 assert len(deck.findCards("prop:ivl<5")) > 1 assert len(deck.findCards("prop:ivl>=5")) == 1 assert len(deck.findCards("prop:ivl=9")) == 0 assert len(deck.findCards("prop:ivl=10")) == 1 assert len(deck.findCards("prop:ivl!=10")) > 1 assert len(deck.findCards("prop:due>0")) == 1 # due dates should work assert len(deck.findCards("prop:due=29")) == 0 assert len(deck.findCards("prop:due=30")) == 1 # ease factors assert len(deck.findCards("prop:ease=2.3")) == 0 assert len(deck.findCards("prop:ease=2.2")) == 1 assert len(deck.findCards("prop:ease>2")) == 1 assert len(deck.findCards("-prop:ease>2")) > 1 # recently failed if not isNearCutoff(): assert len(deck.findCards("rated:1:1")) == 0 assert len(deck.findCards("rated:1:2")) == 0 c = deck.sched.getCard() deck.sched.answerCard(c, 2) assert len(deck.findCards("rated:1:1")) == 0 assert len(deck.findCards("rated:1:2")) == 1 c = deck.sched.getCard() deck.sched.answerCard(c, 1) assert len(deck.findCards("rated:1:1")) == 1 assert len(deck.findCards("rated:1:2")) == 1 assert len(deck.findCards("rated:1")) == 2 assert len(deck.findCards("rated:0:2")) == 0 assert len(deck.findCards("rated:2:2")) == 1 # added assert len(deck.findCards("added:0")) == 0 deck.db.execute("update cards set id = id - 86400*1000 where id = ?", id) assert len(deck.findCards("added:1")) == deck.cardCount() - 1 assert len(deck.findCards("added:2")) == deck.cardCount() else: print("some find tests disabled near cutoff") # empty field assert len(deck.findCards("front:")) == 0 f = deck.newNote() f["Front"] = "" f["Back"] = "abc2" assert deck.addNote(f) == 1 assert len(deck.findCards("front:")) == 1 # OR searches and nesting assert len(deck.findCards("tag:monkey or tag:sheep")) == 2 assert len(deck.findCards("(tag:monkey OR tag:sheep)")) == 2 assert len(deck.findCards("-(tag:monkey OR tag:sheep)")) == 6 assert len(deck.findCards("tag:monkey or (tag:sheep sheep)")) == 2 assert len(deck.findCards("tag:monkey or (tag:sheep octopus)")) == 1 # flag with pytest.raises(Exception): deck.findCards("flag:12") def test_findReplace(): deck = getEmptyCol() f = deck.newNote() f["Front"] = "foo" f["Back"] = "bar" deck.addNote(f) f2 = deck.newNote() f2["Front"] = "baz" f2["Back"] = "foo" deck.addNote(f2) nids = [f.id, f2.id] # should do nothing assert deck.findReplace(nids, "abc", "123") == 0 # global replace assert deck.findReplace(nids, "foo", "qux") == 2 f.load() assert f["Front"] == "qux" f2.load() assert f2["Back"] == "qux" # single field replace assert deck.findReplace(nids, "qux", "foo", field="Front") == 1 f.load() assert f["Front"] == "foo" f2.load() assert f2["Back"] == "qux" # regex replace assert deck.findReplace(nids, "B.r", "reg") == 0 f.load() assert f["Back"] != "reg" assert deck.findReplace(nids, "B.r", "reg", regex=True) == 1 f.load() assert f["Back"] == "reg" def test_findDupes(): deck = getEmptyCol() f = deck.newNote() f["Front"] = "foo" f["Back"] = "bar" deck.addNote(f) f2 = deck.newNote() f2["Front"] = "baz" f2["Back"] = "bar" deck.addNote(f2) f3 = deck.newNote() f3["Front"] = "quux" f3["Back"] = "bar" deck.addNote(f3) f4 = deck.newNote() f4["Front"] = "quuux" f4["Back"] = "nope" deck.addNote(f4) r = deck.findDupes("Back") assert r[0][0] == "bar" assert len(r[0][1]) == 3 # valid search r = deck.findDupes("Back", "bar") assert r[0][0] == "bar" assert len(r[0][1]) == 3 # excludes everything r = deck.findDupes("Back", "invalid") assert not r # front isn't dupe assert deck.findDupes("Front") == []