Repository: wagtail/wagtail

Issue:
Tests failing on latest `main`
<!--
Found a bug? Please fill out the sections below. 👍
-->

### Issue Summary

Some tests for the ~~legacy~~ moderation feature seems to be failing on latest `main` after #9817. Weirdly, they passed when the CI was run on the PR: https://github.com/wagtail/wagtail/actions/runs/3956994621

Will investigate later today.

<!--
A summary of the issue.
-->

### Steps to Reproduce

1. (for example) Start a new project with `wagtail start myproject`
2. Edit models.py as follows...
3. ...

Any other relevant information. For example, why do you consider this a bug and what did you expect to happen instead?

-   I have confirmed that this issue can be reproduced as described on a fresh Wagtail project: (yes / no)

### Technical details

-   Python version: Run `python --version`.
-   Django version: Look in your requirements.txt, or run `pip show django | grep Version`.
-   Wagtail version: Look at the bottom of the Settings menu in the Wagtail admin, or run `pip show wagtail | grep Version:`.
-   Browser version: You can use https://www.whatsmybrowser.org/ to find this out.



Discussion:
I think this might just be an issue with the `icon` template tag.
Ah yep, [this line](https://github.com/wagtail/wagtail/blob/main/wagtail/admin/templatetags/wagtailadmin_tags.py#L768) looks suspect - presumably #9817 reintroduced an instance of the old `class_name` attribute, triggering the warning where there wasn't one before. I think we could just make the warning message a static string instead of trying to interpolate the `class_name` value...
(actually, no, it doesn't look suspect - that's just me forgetting how `f`-strings work...)
Wait, suddenly #9920 doesn't fix this again. I swear it fixed it before when I ran it locally...
Ahh, I get it. This was caused by `is_latest_revision()` in https://github.com/wagtail/wagtail/pull/9881/files#diff-3238f8b64dedf1d4ed1538d66d664cd017eb872b05544cac5179ab07ba78e8a3
That on its own is fine, but fails in combination with #9817. Investigating now. Probably due to how the time is frozen in the tests, and how the testcases have been split into base classes etc.

Resolving patch:
diff --git a/wagtail/models/__init__.py b/wagtail/models/__init__.py
--- a/wagtail/models/__init__.py
+++ b/wagtail/models/__init__.py
@@ -2944,15 +2944,16 @@ def is_latest_revision(self):
             # newer than any revision that might exist in the database
             return True
 
-        return (
-            not Revision.objects.filter(
+        latest_revision_id = (
+            Revision.objects.filter(
                 base_content_type_id=self.base_content_type_id,
                 object_id=self.object_id,
-                created_at__gte=self.created_at,
             )
-            .exclude(id=self.id)
-            .exists()
+            .order_by("-created_at", "-id")
+            .values_list("id", flat=True)
+            .first()
         )
+        return latest_revision_id == self.id
 
     def delete(self):
         # Update revision_created fields for comments that reference the current revision, if applicable.


Test patch:
diff --git a/wagtail/admin/tests/test_workflows.py b/wagtail/admin/tests/test_workflows.py
--- a/wagtail/admin/tests/test_workflows.py
+++ b/wagtail/admin/tests/test_workflows.py
@@ -1044,7 +1044,6 @@ def setup_object(self):
             has_unpublished_changes=True,
         )
         root_page.add_child(instance=self.object)
-        self.object.save_revision()
         self.object_class = self.object.specific_class
 
         # Assign to workflow
@@ -1112,8 +1111,11 @@ def model_name(self):
         return self.model._meta.verbose_name
 
     def setup_object(self):
-        self.object = self.model.objects.create(text="Hello world!", live=False)
-        self.object.save_revision()
+        self.object = self.model.objects.create(
+            text="Hello world!",
+            live=False,
+            has_unpublished_changes=True,
+        )
         self.object_class = type(self.object)
 
         # Assign to workflow
@@ -1139,6 +1141,11 @@ def post(self, action, data=None):
 
 
 class TestSubmitPageToWorkflow(BasePageWorkflowTests):
+    def setUp(self):
+        super().setUp()
+        # Ensure a revision exists
+        self.object.save_revision()
+
     def test_submit_for_approval_creates_states(self):
         """Test that WorkflowState and TaskState objects are correctly created when an object is submitted for approval"""
 
diff --git a/wagtail/tests/test_revision_model.py b/wagtail/tests/test_revision_model.py
--- a/wagtail/tests/test_revision_model.py
+++ b/wagtail/tests/test_revision_model.py
@@ -1,5 +1,8 @@
+import datetime
+
 from django.contrib.contenttypes.models import ContentType
 from django.test import TestCase
+from freezegun import freeze_time
 
 from wagtail.models import Page, Revision, get_default_page_content_type
 from wagtail.test.testapp.models import (
@@ -125,3 +128,43 @@ def test_as_object_with_page(self):
         self.assertIsInstance(instance, SimplePage)
         self.assertEqual(instance.content, "updated")
         self.assertEqual(hello_page.content, "hello")
+
+    def test_is_latest_revision_newer_creation_date_and_id(self):
+        first = self.instance.save_revision()
+        self.assertTrue(first.is_latest_revision())
+
+        second = self.instance.save_revision()
+        self.assertFalse(first.is_latest_revision())
+        self.assertTrue(second.is_latest_revision())
+
+        # Normal case, both creation date and id are newer
+        self.assertLess(first.created_at, second.created_at)
+        self.assertLess(first.id, second.id)
+
+    def test_is_latest_revision_newer_creation_date_older_id(self):
+        first = self.instance.save_revision()
+        self.assertTrue(first.is_latest_revision())
+
+        second = self.instance.save_revision()
+        first.created_at = second.created_at + datetime.timedelta(days=9)
+        first.save()
+
+        self.assertTrue(first.is_latest_revision())
+        self.assertFalse(second.is_latest_revision())
+
+        # The creation date takes precedence over the id
+        self.assertGreater(first.created_at, second.created_at)
+        self.assertLess(first.id, second.id)
+
+    @freeze_time("2023-01-19")
+    def test_is_latest_revision_same_creation_dates(self):
+        first = self.instance.save_revision()
+        self.assertTrue(first.is_latest_revision())
+
+        second = self.instance.save_revision()
+        self.assertFalse(first.is_latest_revision())
+        self.assertTrue(second.is_latest_revision())
+
+        # The id is used as a tie breaker
+        self.assertEqual(first.created_at, second.created_at)
+        self.assertLess(first.id, second.id)