Add tests for snippets with DraftStateMixin enabled

pull/8806/head
Sage Abdullah 2022-07-07 11:07:51 +07:00 zatwierdzone przez Karl Hobley
rodzic 7596bde818
commit ab5a3390e3
3 zmienionych plików z 713 dodań i 1 usunięć

Wyświetl plik

@ -23,7 +23,7 @@ from wagtail.admin.admin_url_finder import AdminURLFinder
from wagtail.admin.forms import WagtailAdminModelForm
from wagtail.admin.panels import FieldPanel, ObjectList, Panel, get_edit_handler
from wagtail.blocks.field_block import FieldBlockAdapter
from wagtail.models import Locale, ModelLogEntry, Page
from wagtail.models import Locale, ModelLogEntry, Page, Revision
from wagtail.snippets.action_menu import (
ActionMenuItem,
get_base_snippet_action_menu_items,
@ -54,6 +54,7 @@ from wagtail.test.testapp.models import (
AdvertWithCustomPrimaryKey,
AdvertWithCustomUUIDPrimaryKey,
AdvertWithTabbedInterface,
DraftStateModel,
RevisableChildModel,
RevisableModel,
SnippetChooserModel,
@ -181,6 +182,28 @@ class TestSnippetListView(TestCase, WagtailTestUtils):
self.assertContains(response, "Dummy Button")
self.assertContains(response, "/dummy-button")
def test_use_latest_draft_as_title(self):
snippet = DraftStateModel.objects.create(text="Draft-enabled Foo, Published")
snippet.save_revision().publish()
snippet.text = "Draft-enabled Bar, In Draft"
snippet.save_revision()
response = self.client.get(
reverse("wagtailsnippets_tests_draftstatemodel:list"),
)
edit_url = reverse(
"wagtailsnippets_tests_draftstatemodel:edit",
args=[quote(snippet.pk)],
)
# Should use the latest draft title in the listing
self.assertContains(
response,
f'<a href="{edit_url}">Draft-enabled Bar, In Draft</a>',
html=True,
)
@override_settings(WAGTAIL_I18N_ENABLED=True)
class TestLocaleSelectorOnList(TestCase, WagtailTestUtils):
@ -616,6 +639,99 @@ class TestLocaleSelectorOnCreate(TestCase, WagtailTestUtils):
self.assertNotContains(response, "Switch locales")
class TestCreateDraftStateSnippet(TestCase, WagtailTestUtils):
def setUp(self):
self.user = self.login()
def get(self):
return self.client.get(reverse("wagtailsnippets_tests_draftstatemodel:add"))
def post(self, post_data={}):
return self.client.post(
reverse("wagtailsnippets_tests_draftstatemodel:add"),
post_data,
)
def test_get(self):
response = self.get()
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "wagtailsnippets/snippets/create.html")
# The save button should be labelled "Save draft"
self.assertContains(response, "Save draft")
# The publish button should exist
self.assertContains(response, "Publish")
# The publish button should have name="action-publish"
self.assertContains(
response,
'<button type="submit" name="action-publish" value="action-publish" class="button action-save button-longrunning" data-clicked-text="Publishing…">',
)
# The status side panel should not be shown
self.assertNotContains(
response,
'<div class="form-side__panel" data-side-panel="status">',
)
def test_save_draft(self):
response = self.post(post_data={"text": "Draft-enabled Foo"})
snippet = DraftStateModel.objects.get(text="Draft-enabled Foo")
self.assertRedirects(
response, reverse("wagtailsnippets_tests_draftstatemodel:list")
)
# The instance should be created
self.assertEqual(snippet.text, "Draft-enabled Foo")
# The instance should be a draft
self.assertFalse(snippet.live)
self.assertTrue(snippet.has_unpublished_changes)
self.assertIsNone(snippet.first_published_at)
self.assertIsNone(snippet.last_published_at)
self.assertIsNone(snippet.live_revision)
# A revision should be created and set as latest_revision
self.assertIsNotNone(snippet.latest_revision)
# The revision content should contain the data
self.assertEqual(snippet.latest_revision.content["text"], "Draft-enabled Foo")
def test_publish(self):
timestamp = now()
with freeze_time(timestamp):
response = self.post(
post_data={
"text": "Draft-enabled Foo, Published",
"action-publish": "action-publish",
}
)
snippet = DraftStateModel.objects.get(text="Draft-enabled Foo, Published")
self.assertRedirects(
response, reverse("wagtailsnippets_tests_draftstatemodel:list")
)
# The instance should be created
self.assertEqual(snippet.text, "Draft-enabled Foo, Published")
# The instance should be live
self.assertTrue(snippet.live)
self.assertFalse(snippet.has_unpublished_changes)
self.assertEqual(snippet.first_published_at, timestamp)
self.assertEqual(snippet.last_published_at, timestamp)
# A revision should be created and set as both latest_revision and live_revision
self.assertIsNotNone(snippet.live_revision)
self.assertEqual(snippet.live_revision, snippet.latest_revision)
# The revision content should contain the new data
self.assertEqual(
snippet.live_revision.content["text"],
"Draft-enabled Foo, Published",
)
class BaseTestSnippetEditView(TestCase, WagtailTestUtils):
def get(self, params={}):
snippet = self.test_snippet
@ -970,6 +1086,327 @@ class TestEditRevisionSnippet(BaseTestSnippetEditView):
self.assertEqual(log_entries.first().revision, revision)
class TestEditDraftStateSnippet(BaseTestSnippetEditView):
def setUp(self):
super().setUp()
self.test_snippet = DraftStateModel.objects.create(
text="Draft-enabled Foo", live=False
)
def test_get(self):
response = self.get()
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "wagtailsnippets/snippets/edit.html")
# The save button should be labelled "Save draft"
self.assertContains(response, "Save draft")
# The publish button should exist
self.assertContains(response, "Publish")
# The publish button should have name="action-publish"
self.assertContains(
response,
'<button type="submit" name="action-publish" value="action-publish" class="button action-save button-longrunning" data-clicked-text="Publishing…">',
)
def test_save_draft(self):
response = self.post(post_data={"text": "Draft-enabled Bar"})
self.test_snippet.refresh_from_db()
revisions = Revision.objects.for_instance(self.test_snippet)
latest_revision = self.test_snippet.latest_revision
self.assertRedirects(
response, reverse("wagtailsnippets_tests_draftstatemodel:list")
)
# The instance should not be updated
self.assertEqual(self.test_snippet.text, "Draft-enabled Foo")
# The instance should be a draft
self.assertFalse(self.test_snippet.live)
self.assertTrue(self.test_snippet.has_unpublished_changes)
self.assertIsNone(self.test_snippet.first_published_at)
self.assertIsNone(self.test_snippet.last_published_at)
self.assertIsNone(self.test_snippet.live_revision)
# The revision should be created and set as latest_revision
self.assertEqual(revisions.count(), 1)
self.assertEqual(latest_revision, revisions.first())
# The revision content should contain the new data
self.assertEqual(latest_revision.content["text"], "Draft-enabled Bar")
def test_publish(self):
timestamp = now()
with freeze_time(timestamp):
response = self.post(
post_data={
"text": "Draft-enabled Bar, Published",
"action-publish": "action-publish",
}
)
self.test_snippet.refresh_from_db()
revisions = Revision.objects.for_instance(self.test_snippet)
latest_revision = self.test_snippet.latest_revision
log_entries = ModelLogEntry.objects.filter(
content_type=ContentType.objects.get_for_model(DraftStateModel),
action="wagtail.publish",
object_id=self.test_snippet.pk,
)
log_entry = log_entries.first()
self.assertRedirects(
response, reverse("wagtailsnippets_tests_draftstatemodel:list")
)
# The instance should be updated
self.assertEqual(self.test_snippet.text, "Draft-enabled Bar, Published")
# The instance should be live
self.assertTrue(self.test_snippet.live)
self.assertFalse(self.test_snippet.has_unpublished_changes)
self.assertEqual(self.test_snippet.first_published_at, timestamp)
self.assertEqual(self.test_snippet.last_published_at, timestamp)
self.assertEqual(self.test_snippet.live_revision, latest_revision)
# The revision should be created and set as latest_revision
self.assertEqual(revisions.count(), 1)
self.assertEqual(latest_revision, revisions.first())
# The revision content should contain the new data
self.assertEqual(
latest_revision.content["text"],
"Draft-enabled Bar, Published",
)
# A log entry with wagtail.publish action should be created
self.assertEqual(log_entries.count(), 1)
self.assertEqual(log_entry.timestamp, timestamp)
def test_save_draft_then_publish(self):
save_timestamp = now()
with freeze_time(save_timestamp):
self.test_snippet.text = "Draft-enabled Bar, In Draft"
self.test_snippet.save_revision()
publish_timestamp = now()
with freeze_time(publish_timestamp):
response = self.post(
post_data={
"text": "Draft-enabled Bar, Now Published",
"action-publish": "action-publish",
}
)
self.test_snippet.refresh_from_db()
revisions = Revision.objects.for_instance(self.test_snippet).order_by("pk")
latest_revision = self.test_snippet.latest_revision
self.assertRedirects(
response, reverse("wagtailsnippets_tests_draftstatemodel:list")
)
# The instance should be updated
self.assertEqual(self.test_snippet.text, "Draft-enabled Bar, Now Published")
# The instance should be live
self.assertTrue(self.test_snippet.live)
self.assertFalse(self.test_snippet.has_unpublished_changes)
self.assertEqual(self.test_snippet.first_published_at, publish_timestamp)
self.assertEqual(self.test_snippet.last_published_at, publish_timestamp)
self.assertEqual(self.test_snippet.live_revision, latest_revision)
# The revision should be created and set as latest_revision
self.assertEqual(revisions.count(), 2)
self.assertEqual(latest_revision, revisions.last())
# The revision content should contain the new data
self.assertEqual(
latest_revision.content["text"],
"Draft-enabled Bar, Now Published",
)
def test_publish_then_save_draft(self):
publish_timestamp = now()
with freeze_time(publish_timestamp):
self.test_snippet.text = "Draft-enabled Bar, Published"
self.test_snippet.save_revision().publish()
save_timestamp = now()
with freeze_time(save_timestamp):
response = self.post(
post_data={"text": "Draft-enabled Bar, Published and In Draft"}
)
self.test_snippet.refresh_from_db()
revisions = Revision.objects.for_instance(self.test_snippet).order_by("pk")
latest_revision = self.test_snippet.latest_revision
self.assertRedirects(
response, reverse("wagtailsnippets_tests_draftstatemodel:list")
)
# The instance should be updated with the last published changes
self.assertEqual(self.test_snippet.text, "Draft-enabled Bar, Published")
# The instance should be live
self.assertTrue(self.test_snippet.live)
# The instance should have unpublished changes
self.assertTrue(self.test_snippet.has_unpublished_changes)
self.assertEqual(self.test_snippet.first_published_at, publish_timestamp)
self.assertEqual(self.test_snippet.last_published_at, publish_timestamp)
# The live revision should be the first revision
self.assertEqual(self.test_snippet.live_revision, revisions.first())
# The second revision should be created and set as latest_revision
self.assertEqual(revisions.count(), 2)
self.assertEqual(latest_revision, revisions.last())
# The revision content should contain the new data
self.assertEqual(
latest_revision.content["text"],
"Draft-enabled Bar, Published and In Draft",
)
def test_publish_twice(self):
first_timestamp = now()
with freeze_time(first_timestamp):
self.test_snippet.text = "Draft-enabled Bar, Published Once"
self.test_snippet.save_revision().publish()
second_timestamp = now() + datetime.timedelta(days=1)
with freeze_time(second_timestamp):
response = self.post(
post_data={
"text": "Draft-enabled Bar, Published Twice",
"action-publish": "action-publish",
}
)
self.test_snippet.refresh_from_db()
revisions = Revision.objects.for_instance(self.test_snippet).order_by("pk")
latest_revision = self.test_snippet.latest_revision
self.assertRedirects(
response, reverse("wagtailsnippets_tests_draftstatemodel:list")
)
# The instance should be updated with the last published changes
self.assertEqual(self.test_snippet.text, "Draft-enabled Bar, Published Twice")
# The instance should be live
self.assertTrue(self.test_snippet.live)
self.assertFalse(self.test_snippet.has_unpublished_changes)
# The first_published_at and last_published_at should be set correctly
self.assertEqual(self.test_snippet.first_published_at, first_timestamp)
self.assertEqual(self.test_snippet.last_published_at, second_timestamp)
# The live revision should be the second revision
self.assertEqual(self.test_snippet.live_revision, revisions.last())
# The second revision should be created and set as latest_revision
self.assertEqual(revisions.count(), 2)
self.assertEqual(latest_revision, revisions.last())
# The revision content should contain the new data
self.assertEqual(
latest_revision.content["text"],
"Draft-enabled Bar, Published Twice",
)
def test_get_after_save_draft(self):
self.post(post_data={"text": "Draft-enabled Bar"})
response = self.get()
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "wagtailsnippets/snippets/edit.html")
# Should not show the Live status
self.assertNotContains(
response,
'<h3 id="status-sidebar-live" class="w-label-1 !w-mt-0 w-mb-1"><span class="w-sr-only">Status: </span>Live</h3>',
html=True,
)
# Should show the Draft status
self.assertContains(
response,
'<h3 id="status-sidebar-draft" class="w-label-1 !w-mt-0 w-mb-1"><span class="w-sr-only">Status: </span>Draft</h3>',
html=True,
)
def test_get_after_publish(self):
self.post(
post_data={
"text": "Draft-enabled Bar, Published",
"action-publish": "action-publish",
}
)
response = self.get()
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "wagtailsnippets/snippets/edit.html")
# Should show the Live status
self.assertContains(
response,
'<h3 id="status-sidebar-live" class="w-label-1 !w-mt-0 w-mb-1"><span class="w-sr-only">Status: </span>Live</h3>',
html=True,
)
# Should not show the Draft status
self.assertNotContains(
response,
'<h3 id="status-sidebar-draft" class="w-label-1 !w-mt-0 w-mb-1"><span class="w-sr-only">Status: </span>Draft</h3>',
html=True,
)
def test_get_after_publish_and_save_draft(self):
self.post(
post_data={
"text": "Draft-enabled Bar, Published",
"action-publish": "action-publish",
}
)
self.post(post_data={"text": "Draft-enabled Bar, In Draft"})
response = self.get()
html = response.content.decode()
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "wagtailsnippets/snippets/edit.html")
# Should show the Live status
self.assertContains(
response,
'<h3 id="status-sidebar-live" class="w-label-1 !w-mt-0 w-mb-1"><span class="w-sr-only">Status: </span>Live</h3>',
html=True,
)
# Should show the Draft status
self.assertContains(
response,
'<h3 id="status-sidebar-draft" class="w-label-1 !w-mt-0 w-mb-1"><span class="w-sr-only">Status: </span>Draft</h3>',
html=True,
)
# Should use the latest draft content for the title
self.assertContains(
response,
'<h1 class="w-header__title"><svg class="icon icon-snippet w-header__glpyh" aria-hidden="true"><use href="#icon-snippet"></use></svg>Draft-enabled Bar, In Draft</h1>',
html=True,
)
# Should use the latest draft content for the form
self.assertTagInHTML(
'<textarea name="text">Draft-enabled Bar, In Draft</textarea>',
html,
allow_extra_attrs=True,
)
class TestSnippetDelete(TestCase, WagtailTestUtils):
fixtures = ["test.json"]
@ -1300,6 +1737,31 @@ class TestUsedBy(TestCase):
self.assertEqual(type(advert.get_usage()[0]), Page)
@override_settings(WAGTAIL_USAGE_COUNT_ENABLED=True)
class TestSnippetUsageView(TestCase, WagtailTestUtils):
def setUp(self):
self.user = self.login()
def test_use_latest_draft_as_title(self):
snippet = DraftStateModel.objects.create(text="Draft-enabled Foo, Published")
snippet.save_revision().publish()
snippet.text = "Draft-enabled Bar, In Draft"
snippet.save_revision()
response = self.client.get(
reverse(
"wagtailsnippets_tests_draftstatemodel:usage",
args=[quote(snippet.pk)],
)
)
# Should use the latest draft title in the header subtitle
self.assertContains(
response,
'<span class="w-header__subtitle">Draft-enabled Bar, In Draft</span>',
)
class TestSnippetHistory(TestCase, WagtailTestUtils):
fixtures = ["test.json"]
@ -1397,6 +1859,20 @@ class TestSnippetHistory(TestCase, WagtailTestUtils):
count=1,
)
def test_use_latest_draft_as_title(self):
snippet = DraftStateModel.objects.create(text="Draft-enabled Foo, Published")
snippet.save_revision().publish()
snippet.text = "Draft-enabled Bar, In Draft"
snippet.save_revision()
response = self.get(snippet)
# Should use the latest draft title in the header subtitle
self.assertContains(
response,
'<span class="w-header__subtitle">Draft-enabled Bar, In Draft</span>',
)
@override_settings(WAGTAIL_I18N_ENABLED=True)
def test_get_with_i18n_enabled(self):
response = self.get(self.non_revisable_snippet)
@ -1485,6 +1961,24 @@ class TestSnippetRevisions(TestCase, WagtailTestUtils):
response = self.get()
self.assertEqual(response.status_code, 302)
def test_get_with_draft_state_snippet(self):
self.snippet = DraftStateModel.objects.create(text="Draft-enabled Foo")
self.initial_revision = self.snippet.save_revision()
response = self.get()
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "wagtailsnippets/snippets/edit.html")
# The save button should be labelled "Replace current draft"
self.assertContains(response, "Replace current draft")
# The publish button should exist
self.assertContains(response, "Publish this version")
# The publish button should have name="action-publish"
self.assertContains(
response,
'<button type="submit" name="action-publish" value="action-publish" class="button action-save button-longrunning warning" data-clicked-text="Publishing…">',
)
def test_replace_revision(self):
get_response = self.get()
text_from_revision = get_response.context["form"].initial["text"]
@ -1535,6 +2029,104 @@ class TestSnippetRevisions(TestCase, WagtailTestUtils):
# Only the initial revision and edited revision, no revert revision
self.assertEqual(self.snippet.revisions.count(), 2)
def test_replace_draft(self):
self.snippet = DraftStateModel.objects.create(
text="Draft-enabled Foo", live=False
)
self.initial_revision = self.snippet.save_revision()
self.snippet.text = "Draft-enabled Foo edited"
self.edit_revision = self.snippet.save_revision()
get_response = self.get()
text_from_revision = get_response.context["form"].initial["text"]
post_response = self.post(
post_data={
"text": text_from_revision + " reverted",
"revision": self.initial_revision.pk,
}
)
self.assertRedirects(post_response, self.get_url("list", args=[]))
self.snippet.refresh_from_db()
latest_revision = self.snippet.get_latest_revision()
log_entry = ModelLogEntry.objects.filter(revision=latest_revision).first()
publish_log_entries = ModelLogEntry.objects.filter(
content_type=ContentType.objects.get_for_model(DraftStateModel),
action="wagtail.publish",
object_id=self.snippet.pk,
)
# The instance should not be updated
self.assertEqual(self.snippet.text, "Draft-enabled Foo")
# The initial revision, edited revision, and revert revision
self.assertEqual(self.snippet.revisions.count(), 3)
# The latest revision should be the revert revision
self.assertEqual(latest_revision.content["text"], "Draft-enabled Foo reverted")
# A new log entry with "wagtail.revert" action should be created
self.assertIsNotNone(log_entry)
self.assertEqual(log_entry.action, "wagtail.revert")
# There should be no log entries for the publish action
self.assertEqual(publish_log_entries.count(), 0)
# The instance should still be a draft
self.assertFalse(self.snippet.live)
self.assertTrue(self.snippet.has_unpublished_changes)
self.assertIsNone(self.snippet.first_published_at)
self.assertIsNone(self.snippet.last_published_at)
self.assertIsNone(self.snippet.live_revision)
def test_replace_publish(self):
self.snippet = DraftStateModel.objects.create(text="Draft-enabled Foo")
self.initial_revision = self.snippet.save_revision()
self.snippet.text = "Draft-enabled Foo edited"
self.edit_revision = self.snippet.save_revision()
get_response = self.get()
text_from_revision = get_response.context["form"].initial["text"]
timestamp = now()
with freeze_time(timestamp):
post_response = self.post(
post_data={
"text": text_from_revision + " reverted",
"revision": self.initial_revision.pk,
"action-publish": "action-publish",
}
)
self.assertRedirects(post_response, self.get_url("list", args=[]))
self.snippet.refresh_from_db()
latest_revision = self.snippet.get_latest_revision()
log_entry = ModelLogEntry.objects.filter(revision=latest_revision).first()
revert_log_entries = ModelLogEntry.objects.filter(
content_type=ContentType.objects.get_for_model(DraftStateModel),
action="wagtail.revert",
object_id=self.snippet.pk,
)
# The instance should be updated
self.assertEqual(self.snippet.text, "Draft-enabled Foo reverted")
# The initial revision, edited revision, and revert revision
self.assertEqual(self.snippet.revisions.count(), 3)
# The latest revision should be the revert revision
self.assertEqual(latest_revision.content["text"], "Draft-enabled Foo reverted")
# The latest log entry should use the "wagtail.publish" action
self.assertIsNotNone(log_entry)
self.assertEqual(log_entry.action, "wagtail.publish")
# There should be a log entry for the revert action
self.assertEqual(revert_log_entries.count(), 1)
# The instance should be live
self.assertTrue(self.snippet.live)
self.assertFalse(self.snippet.has_unpublished_changes)
self.assertEqual(self.snippet.first_published_at, timestamp)
self.assertEqual(self.snippet.last_published_at, timestamp)
self.assertEqual(self.snippet.live_revision, self.snippet.latest_revision)
class TestCompareRevisions(TestCase, WagtailTestUtils):
# Actual tests for the comparison classes can be found in test_compare.py

Wyświetl plik

@ -0,0 +1,104 @@
# Generated by Django 4.0.5 on 2022-07-07 04:13
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
("wagtailcore", "0076_modellogentry_revision"),
("tests", "0002_recreate_setting_models"),
]
operations = [
migrations.CreateModel(
name="DraftStateModel",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"live",
models.BooleanField(
default=True, editable=False, verbose_name="live"
),
),
(
"has_unpublished_changes",
models.BooleanField(
default=False,
editable=False,
verbose_name="has unpublished changes",
),
),
(
"first_published_at",
models.DateTimeField(
blank=True,
db_index=True,
null=True,
verbose_name="first published at",
),
),
(
"last_published_at",
models.DateTimeField(
editable=False, null=True, verbose_name="last published at"
),
),
(
"go_live_at",
models.DateTimeField(
blank=True, null=True, verbose_name="go live date/time"
),
),
(
"expire_at",
models.DateTimeField(
blank=True, null=True, verbose_name="expiry date/time"
),
),
(
"expired",
models.BooleanField(
default=False, editable=False, verbose_name="expired"
),
),
("text", models.TextField()),
(
"latest_revision",
models.ForeignKey(
blank=True,
editable=False,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="+",
to="wagtailcore.revision",
verbose_name="latest revision",
),
),
(
"live_revision",
models.ForeignKey(
blank=True,
editable=False,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="+",
to="wagtailcore.revision",
verbose_name="live revision",
),
),
],
options={
"abstract": False,
},
),
]

Wyświetl plik

@ -58,6 +58,7 @@ from wagtail.images import get_image_model
from wagtail.images.blocks import ImageChooserBlock
from wagtail.images.models import AbstractImage, AbstractRendition, Image
from wagtail.models import (
DraftStateMixin,
Orderable,
Page,
PageManager,
@ -967,6 +968,21 @@ class RevisableGrandChildModel(RevisableChildModel):
pass
# Models with DraftStateMixin
class DraftStateModel(DraftStateMixin, RevisionMixin, models.Model):
text = models.TextField()
panels = [
FieldPanel("text"),
]
def __str__(self):
return self.text
register_snippet(DraftStateModel)
class StandardIndex(Page):
"""Index for the site"""