diff --git a/zerver/fixtures/narrow.json b/zerver/fixtures/narrow.json index 9d8160bec4..af38c77317 100644 --- a/zerver/fixtures/narrow.json +++ b/zerver/fixtures/narrow.json @@ -227,5 +227,27 @@ "mentioned" ] ] + }, + { + "accept_events":[ + { + "message":null, + "flags": [], + } + ], + "reject_events":[ + { + "message":null, + "flags":[ + "read" + ] + } + ], + "narrow":[ + [ + "is", + "unread" + ] + ] } ] diff --git a/zerver/lib/narrow.py b/zerver/lib/narrow.py index 3009c250ef..c60438c654 100644 --- a/zerver/lib/narrow.py +++ b/zerver/lib/narrow.py @@ -43,6 +43,9 @@ def build_narrow_filter(narrow): elif operator == "is" and operand in ["starred"]: if operand not in flags: return False + elif operator == "is" and operand == "unread": + if "read" in flags: + return False elif operator == "is" and operand in ["alerted", "mentioned"]: if "mentioned" not in flags: return False diff --git a/zerver/tests/test_narrow.py b/zerver/tests/test_narrow.py index 29bff82e79..4efd09bcb2 100644 --- a/zerver/tests/test_narrow.py +++ b/zerver/tests/test_narrow.py @@ -112,6 +112,16 @@ class NarrowBuilderTest(ZulipTestCase): term = dict(operator='is', operand=operand) self._do_add_term_test(term, 'WHERE (flags & :flags_1) != :param_1') + def test_add_term_using_is_operator_and_unread_operand(self): + # type: () -> None + term = dict(operator='is', operand='unread') + self._do_add_term_test(term, 'WHERE (flags & :flags_1) = :param_1') + + def test_add_term_using_is_operator_and_unread_operand_and_negated(self): # NEGATED + # type: () -> None + term = dict(operator='is', operand='unread', negated=True) + self._do_add_term_test(term, 'WHERE (flags & :flags_1) != :param_1') + def test_add_term_using_is_operator_non_private_operand_and_negated(self): # NEGATED # type: () -> None for operand in ['starred', 'mentioned', 'alerted']: @@ -335,7 +345,7 @@ class BuildNarrowFilterTest(TestCase): fixtures_path = os.path.join(os.path.dirname(__file__), '../fixtures/narrow.json') scenarios = ujson.loads(open(fixtures_path, 'r').read()) - self.assertTrue(len(scenarios) == 8) + self.assertTrue(len(scenarios) == 9) for scenario in scenarios: narrow = scenario['narrow'] accept_events = scenario['accept_events'] @@ -375,6 +385,12 @@ class IncludeHistoryTest(ZulipTestCase): ] self.assertFalse(ok_to_include_history(narrow, realm)) + # History doesn't apply to unread messages. + narrow = [ + dict(operator='is', operand='unread'), + ] + self.assertFalse(ok_to_include_history(narrow, realm)) + # If we are looking for something like starred messages, there is # no point in searching historical messages. narrow = [ diff --git a/zerver/views/messages.py b/zerver/views/messages.py index 97d3a0fe7c..f0216254d4 100644 --- a/zerver/views/messages.py +++ b/zerver/views/messages.py @@ -133,6 +133,9 @@ class NarrowBuilder(object): elif operand == 'starred': cond = column("flags").op("&")(UserMessage.flags.starred.mask) != 0 return query.where(maybe_negate(cond)) + elif operand == 'unread': + cond = column("flags").op("&")(UserMessage.flags.read.mask) == 0 + return query.where(maybe_negate(cond)) elif operand == 'mentioned' or operand == 'alerted': cond = column("flags").op("&")(UserMessage.flags.mentioned.mask) != 0 return query.where(maybe_negate(cond))