diff --git a/novelwriter/core/status.py b/novelwriter/core/status.py index a3792764..4e9317f2 100644 --- a/novelwriter/core/status.py +++ b/novelwriter/core/status.py @@ -106,6 +106,13 @@ class NWStatus(): del self._reverse[self._store[key]["name"]] del self._store[key] + keys = list(self._store.keys()) + if key == self._default: + if len(keys) > 0: + self._default = keys[0] + else: + self._default = None + return True def check(self, value): @@ -242,6 +249,9 @@ class NWStatus(): # Iterator Bits ## + def __len__(self): + return len(self._store) + def __getitem__(self, key): return self._store[key] diff --git a/tests/test_core/test_core_status.py b/tests/test_core/test_core_status.py index a7a2d55e..611f8327 100644 --- a/tests/test_core/test_core_status.py +++ b/tests/test_core/test_core_status.py @@ -20,6 +20,7 @@ along with this program. If not, see . """ import pytest +import random from lxml import etree @@ -29,103 +30,268 @@ from novelwriter.core.status import NWStatus @pytest.mark.core -def testCoreStatus_Entries(): - """Test all the simple setters for the NWItem class. +def testCoreStatus_Internal(constData): + """Test all the internal functions of the NWStatus class. """ - theStatus = NWStatus() + random.seed(42) + theStatus = NWStatus(NWStatus.STATUS) + theImport = NWStatus(NWStatus.IMPORT) - # Add entries - theStatus.addEntry("New", (100, 100, 100)) - theStatus.addEntry("Minor", (200, 50, 0)) - theStatus.addEntry("Major", (200, 150, 0)) - theStatus.addEntry("Main", (50, 200, 0)) + with pytest.raises(Exception): + NWStatus(999) - assert theStatus._theLabels == ["New", "Minor", "Major", "Main"] - assert theStatus._theColours == [(100, 100, 100), (200, 50, 0), (200, 150, 0), (50, 200, 0)] - assert theStatus._theCounts == [0, 0, 0, 0] - assert theStatus._theMap["New"] == 0 - assert theStatus._theMap["Minor"] == 1 - assert theStatus._theMap["Major"] == 2 - assert theStatus._theMap["Main"] == 3 - assert theStatus._theLength == 4 + # Generate Key + # ============ - # Lookups - assert theStatus._getIndex(None) is None - assert theStatus._getIndex("stuff") is None - assert theStatus._getIndex("Main") == 3 + assert theStatus._newKey() == constData.statusKeys[0] + assert theStatus._newKey() == constData.statusKeys[1] - # Checks - assert theStatus.checkEntry(123) == "New" - assert theStatus.checkEntry("Stuff") == "New" - assert theStatus.checkEntry("New ") == "New" - assert theStatus.checkEntry(" Main ") == "Main" + # Key collision, should move to key 3 + theStatus.write(constData.statusKeys[2], "Crash", (0, 0, 0)) + assert theStatus._newKey() == constData.statusKeys[3] - # Icons - assert isinstance(theStatus.getIcon("Stuff"), QIcon) - assert isinstance(theStatus.getIcon("New"), QIcon) + assert theImport._newKey() == constData.importKeys[0] + assert theImport._newKey() == constData.importKeys[1] - # Set new list - newList = [ - ("New", 1, 1, 1, "New"), - ("Minor", 2, 2, 2, "Minor"), - ("Major", 3, 3, 3, "Major"), - ("Min", 4, 4, 4, "Main"), - ("Max", 5, 5, 5, None), - ] - assert theStatus.setNewEntries(None) == {} - assert theStatus.setNewEntries(newList) == {"Main": "Min"} + # Key collision, should move to key 3 + theImport.write(constData.importKeys[2], "Crash", (0, 0, 0)) + assert theImport._newKey() == constData.importKeys[3] - assert theStatus._theLabels == ["New", "Minor", "Major", "Min", "Max"] - assert theStatus._theColours == [(1, 1, 1), (2, 2, 2), (3, 3, 3), (4, 4, 4), (5, 5, 5)] - assert theStatus._theCounts == [0, 0, 0, 0, 0] - assert theStatus._theMap["New"] == 0 - assert theStatus._theMap["Minor"] == 1 - assert theStatus._theMap["Major"] == 2 - assert theStatus._theMap["Min"] == 3 - assert theStatus._theMap["Max"] == 4 - assert theStatus._theLength == 5 + # Check Key + # ========= - # Add counts - countTo = [3, 5, 7, 9, 11] - for i, n in enumerate(countTo): - for _ in range(n): - theStatus.countEntry(theStatus._theLabels[i]) - assert theStatus._theCounts == countTo + assert theStatus._isKey(None) is False # Not a string + assert theStatus._isKey("s00000") is False # Too short + assert theStatus._isKey("s000000") is True # Correct length + assert theStatus._isKey("s0000000") is False # Too long + assert theStatus._isKey("i000000") is False # Wrong type + assert theStatus._isKey("q000000") is False # Wrong type + assert theStatus._isKey("s12345H") is False # Not a hex value + assert theStatus._isKey("s12345F") is False # Not a lower case hex value + assert theStatus._isKey("s12345f") is True # Valid hex value + + assert theImport._isKey(None) is False # Not a string + assert theImport._isKey("i00000") is False # Too short + assert theImport._isKey("i000000") is True # Correct length + assert theImport._isKey("i0000000") is False # Too long + assert theImport._isKey("s000000") is False # Wrong type + assert theImport._isKey("q000000") is False # Wrong type + assert theImport._isKey("i12345H") is False # Not a hex value + assert theImport._isKey("i12345F") is False # Not a lower case hex value + assert theImport._isKey("i12345f") is True # Valid hex value + +# END Test testCoreStatus_Internal + + +@pytest.mark.core +def testCoreStatus_Iterator(constData): + """Test the iterator functions of the NWStatus class. + """ + random.seed(42) + theStatus = NWStatus(NWStatus.STATUS) + theStatus.write(None, "New", (100, 100, 100)) + theStatus.write(None, "Note", (200, 50, 0)) + theStatus.write(None, "Draft", (200, 150, 0)) + theStatus.write(None, "Finished", (50, 200, 0)) + + # Direct access + entry = theStatus[constData.statusKeys[0]] + assert entry["cols"] == (100, 100, 100) + assert entry["name"] == "New" + assert entry["count"] == 0 + assert isinstance(entry["icon"], QIcon) # Iterate - for i, (sA, sB, sC, sD) in enumerate(theStatus): - assert sA == theStatus._theLabels[i] - assert sB == theStatus._theColours[i] - assert sC == theStatus._theCounts[i] - assert sD == theStatus._theIcons[i] + entries = list(theStatus) + assert len(entries) == 4 + assert len(theStatus) == 4 - sA, sB, sC, sD = theStatus[9] - assert sA is None - assert sB is None - assert sC is None - assert isinstance(sD, QIcon) + # Keys + assert list(theStatus.keys()) == constData.statusKeys + + # Items + for index, (key, entry) in enumerate(theStatus.items()): + assert key == constData.statusKeys[index] + assert "cols" in entry + assert "name" in entry + assert "count" in entry + assert "icon" in entry + + # Valuse + for entry in theStatus.values(): + assert "cols" in entry + assert "name" in entry + assert "count" in entry + assert "icon" in entry + +# END Test testCoreStatus_Iterator + + +@pytest.mark.core +def testCoreStatus_Entries(constData): + """Test all the simple setters for the NWStatus class. + """ + random.seed(42) + theStatus = NWStatus(NWStatus.STATUS) + + # Write + # ===== + + # Have a key + theStatus.write(constData.statusKeys[0], "Entry 1", (200, 100, 50)) + assert theStatus[constData.statusKeys[0]]["name"] == "Entry 1" + assert theStatus[constData.statusKeys[0]]["cols"] == (200, 100, 50) + + # Don't have a key + theStatus.write(None, "Entry 2", (210, 110, 60)) + assert theStatus[constData.statusKeys[1]]["name"] == "Entry 2" + assert theStatus[constData.statusKeys[1]]["cols"] == (210, 110, 60) + + # Wrong colour spec + theStatus.write(None, "Entry 3", "what?") + assert theStatus[constData.statusKeys[2]]["name"] == "Entry 3" + assert theStatus[constData.statusKeys[2]]["cols"] == (100, 100, 100) + + # Wrong colour count + theStatus.write(None, "Entry 4", (10, 20)) + assert theStatus[constData.statusKeys[3]]["name"] == "Entry 4" + assert theStatus[constData.statusKeys[3]]["cols"] == (100, 100, 100) + + # Check reverse map + assert theStatus._reverse == { + "Entry 1": constData.statusKeys[0], + "Entry 2": constData.statusKeys[1], + "Entry 3": constData.statusKeys[2], + "Entry 4": constData.statusKeys[3], + } + + # Check + # ===== + + # Normal lookup + for key in constData.statusKeys: + assert theStatus.check(key) == key + + # Reverse map lookup + assert theStatus.check("Entry 1") == constData.statusKeys[0] + assert theStatus.check("Entry 2") == constData.statusKeys[1] + assert theStatus.check("Entry 3") == constData.statusKeys[2] + assert theStatus.check("Entry 4") == constData.statusKeys[3] + + # Non-existing name + assert theStatus.check("Entry 5") == constData.statusKeys[0] + + # Name Access + # =========== + + assert theStatus.name(constData.statusKeys[0]) == "Entry 1" + assert theStatus.name(constData.statusKeys[1]) == "Entry 2" + assert theStatus.name(constData.statusKeys[2]) == "Entry 3" + assert theStatus.name(constData.statusKeys[3]) == "Entry 4" + assert theStatus.name("blablabla") == "Entry 1" + + # Colour Access + # ============= + + assert theStatus.cols(constData.statusKeys[0]) == (200, 100, 50) + assert theStatus.cols(constData.statusKeys[1]) == (210, 110, 60) + assert theStatus.cols(constData.statusKeys[2]) == (100, 100, 100) + assert theStatus.cols(constData.statusKeys[3]) == (100, 100, 100) + assert theStatus.cols("blablabla") == (200, 100, 50) + + # Icon Access + # =========== + + assert isinstance(theStatus.icon(constData.statusKeys[0]), QIcon) + assert isinstance(theStatus.icon(constData.statusKeys[1]), QIcon) + assert isinstance(theStatus.icon(constData.statusKeys[2]), QIcon) + assert isinstance(theStatus.icon(constData.statusKeys[3]), QIcon) + assert isinstance(theStatus.icon("blablabla"), QIcon) + + # Increment and Count Access + # ========================== + + countTo = [3, 5, 7, 9] + for i, n in enumerate(countTo): + for _ in range(n): + theStatus.increment(constData.statusKeys[i]) + + assert theStatus.count(constData.statusKeys[0]) == countTo[0] + assert theStatus.count(constData.statusKeys[1]) == countTo[1] + assert theStatus.count(constData.statusKeys[2]) == countTo[2] + assert theStatus.count(constData.statusKeys[3]) == countTo[3] + assert theStatus.count("blablabla") == countTo[0] - # Clear counts theStatus.resetCounts() - assert theStatus._theCounts == [0, 0, 0, 0, 0] + + assert theStatus.count(constData.statusKeys[0]) == 0 + assert theStatus.count(constData.statusKeys[1]) == 0 + assert theStatus.count(constData.statusKeys[2]) == 0 + assert theStatus.count(constData.statusKeys[3]) == 0 + + # Default + # ======= + + default = theStatus._default + theStatus._default = None + + assert theStatus.check("Entry 5") == "" + assert theStatus.name("blablabla") == "" + assert theStatus.cols("blablabla") == (100, 100, 100) + assert theStatus.count("blablabla") == 0 + assert isinstance(theStatus.icon("blablabla"), QIcon) + + theStatus._default = default + + # Remove + # ====== + + # Non-existing entry + assert theStatus.remove("blablabla") is False + + # Non-zero entry + theStatus.increment(constData.statusKeys[3]) + assert theStatus.remove(constData.statusKeys[3]) is False + + # Delete last entry + theStatus.resetCounts() + lastName = theStatus.name(constData.statusKeys[3]) + assert lastName == "Entry 4" + assert theStatus.remove(constData.statusKeys[3]) is True + assert theStatus.check(constData.statusKeys[3]) == theStatus._default + assert theStatus.check(lastName) == theStatus._default + + # Delete default entry, Entry 2 is new default + firstName = theStatus.name(theStatus._default) + assert firstName == "Entry 1" + assert theStatus.remove(theStatus._default) is True + assert theStatus.name(firstName) == "Entry 2" + + # Remove remaining entries + assert theStatus.remove(constData.statusKeys[1]) is True + assert theStatus.remove(constData.statusKeys[2]) is True + + assert len(theStatus) == 0 + assert theStatus._default is None # END Test testCoreStatus_Entries @pytest.mark.core -def testCoreStatus_XMLPackUnpack(): - """Test all the simple setters for the NWItem class. +def testCoreStatus_XMLPackUnpack(constData): + """Test all the XML pack/unpack of the NWStatus class. """ - theStatus = NWStatus() - theStatus.addEntry("New", (100, 100, 100)) - theStatus.addEntry("Minor", (200, 50, 0)) - theStatus.addEntry("Major", (200, 150, 0)) - theStatus.addEntry("Main", (50, 200, 0)) + random.seed(42) + theStatus = NWStatus(NWStatus.STATUS) + theStatus.write(None, "New", (100, 100, 100)) + theStatus.write(None, "Note", (200, 50, 0)) + theStatus.write(None, "Draft", (200, 150, 0)) + theStatus.write(None, "Finished", (50, 200, 0)) countTo = [3, 5, 7, 9] for i, n in enumerate(countTo): for _ in range(n): - theStatus.countEntry(theStatus._theLabels[i]) + theStatus.increment(constData.statusKeys[i]) nwXML = etree.Element("novelWriterXML") @@ -134,23 +300,29 @@ def testCoreStatus_XMLPackUnpack(): theStatus.packXML(xStatus) assert etree.tostring(xStatus, pretty_print=False, encoding="utf-8") == ( b'' - b'New' - b'Minor' - b'Major' - b'Main' + b'New' + b'Note' + b'Draft' + b'Finished' b'' ) # Unpack - theStatus = NWStatus() + theStatus = NWStatus(NWStatus.STATUS) assert theStatus.unpackXML(xStatus) - assert theStatus._theLabels == ["New", "Minor", "Major", "Main"] - assert theStatus._theColours == [(100, 100, 100), (200, 50, 0), (200, 150, 0), (50, 200, 0)] - assert theStatus._theCounts == [0, 0, 0, 0] - assert theStatus._theMap["New"] == 0 - assert theStatus._theMap["Minor"] == 1 - assert theStatus._theMap["Major"] == 2 - assert theStatus._theMap["Main"] == 3 - assert theStatus._theLength == 4 + assert len(theStatus._store) == 4 + assert list(theStatus._store.keys()) == constData.statusKeys + assert theStatus._store[constData.statusKeys[0]]["name"] == "New" + assert theStatus._store[constData.statusKeys[1]]["name"] == "Note" + assert theStatus._store[constData.statusKeys[2]]["name"] == "Draft" + assert theStatus._store[constData.statusKeys[3]]["name"] == "Finished" + assert theStatus._store[constData.statusKeys[0]]["cols"] == (100, 100, 100) + assert theStatus._store[constData.statusKeys[1]]["cols"] == (200, 50, 0) + assert theStatus._store[constData.statusKeys[2]]["cols"] == (200, 150, 0) + assert theStatus._store[constData.statusKeys[3]]["cols"] == (50, 200, 0) + assert theStatus._store[constData.statusKeys[0]]["count"] == countTo[0] + assert theStatus._store[constData.statusKeys[1]]["count"] == countTo[1] + assert theStatus._store[constData.statusKeys[2]]["count"] == countTo[2] + assert theStatus._store[constData.statusKeys[3]]["count"] == countTo[3] # END Test testCoreStatus_XMLPackUnpack