What can't be undone asks first
Deleting a post, taking it off the persona's wall, muting or blocking an account (from a card or a profile), leaving a group, dropping a scheduled post and signing out acted at the first tap, a few pixels from their neighbours. They now ask first: new ConfirmService (Ask returns whether the reader confirmed, as PublicNoticeService does) and ConfirmHost in MainLayout, a sheet from the bottom on a phone and a dialog from md, with Cancel and the action named, in red when it deletes or cuts off. Logout is a button that asks, then goes to /logout as before. The reversible ones (unmute, unblock, removing a conversation from the list, a picture from the composer) don't ask; the inline confirms of lists, filters and the login's deletion stay. The desktop tests that delete a reply, take a post off a wall or drop a scheduled post confirm; a phone flow deletes a post after cancelling once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LsXgEaXee4GCU1hwYgPJXw
This commit is contained in:
1 parent
931b7fcff1
commit
1c9773bab0
15 files changed
+211
-3
No files matched your search
@@ -0,0 +1,40 @@
|
|||||||
|
@inherits LocalizableComponentBase
|
||||||
|
@implements IDisposable
|
||||||
|
|
||||||
|
@* The question ConfirmService asks before what can't be undone, in MainLayout: a sheet from the bottom on a phone, a
|
||||||
|
dialog in the middle from md. Closing it in any way answers no. *@
|
||||||
|
<DModal Placement="DModalPlacement.ResponsiveBottom" IsOpen="Confirm.IsAsking" IsOpenChanged="Closed" CloseText="@Localizer["Close"]" data-confirm>
|
||||||
|
<Heading>@Confirm.Heading</Heading>
|
||||||
|
<ChildContent>
|
||||||
|
@if (Confirm.Text is { Length: > 0 } text)
|
||||||
|
{
|
||||||
|
<p class="text-sm">@text</p>
|
||||||
|
}
|
||||||
|
</ChildContent>
|
||||||
|
<Actions>
|
||||||
|
<DButton class="max-md:flex-1" OnClick="() => Confirm.Answer(false)" data-confirm-no>@Localizer["Cancel"]</DButton>
|
||||||
|
<DButton class="max-md:flex-1" Color="@(Confirm.Danger ? DColor.Error : DColor.Primary)" OnClick="() => Confirm.Answer(true)" data-confirm-yes>
|
||||||
|
@Confirm.ConfirmText
|
||||||
|
</DButton>
|
||||||
|
</Actions>
|
||||||
|
</DModal>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Inject] ConfirmService Confirm { get; set; }
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
Confirm.Changed += Refresh;
|
||||||
|
IsLoading = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Refresh() => InvokeAsync(StateHasChanged);
|
||||||
|
|
||||||
|
void Closed(bool open)
|
||||||
|
{
|
||||||
|
if (!open)
|
||||||
|
Confirm.Answer(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose() => Confirm.Changed -= Refresh;
|
||||||
|
}
|
||||||
@@ -477,6 +477,7 @@
|
|||||||
|
|
||||||
@code {
|
@code {
|
||||||
[Inject] StatusService StatusService { get; set; }
|
[Inject] StatusService StatusService { get; set; }
|
||||||
|
[Inject] ConfirmService Confirm { get; set; }
|
||||||
[Inject] TimelineService TimelineService { get; set; }
|
[Inject] TimelineService TimelineService { get; set; }
|
||||||
[Inject] InstanceService InstanceService { get; set; }
|
[Inject] InstanceService InstanceService { get; set; }
|
||||||
[Inject] ProvenanceService ProvenanceService { get; set; }
|
[Inject] ProvenanceService ProvenanceService { get; set; }
|
||||||
@@ -913,6 +914,9 @@
|
|||||||
|
|
||||||
async Task Delete()
|
async Task Delete()
|
||||||
{
|
{
|
||||||
|
if (!await Confirm.Ask(Localizer["Delete this post?"], Localizer["It goes from here, and the servers that have it are asked to delete it too."],
|
||||||
|
Localizer["Delete"], true))
|
||||||
|
return;
|
||||||
isActing = true;
|
isActing = true;
|
||||||
var result = await StatusService.Delete(Status.Id);
|
var result = await StatusService.Delete(Status.Id);
|
||||||
isActing = false;
|
isActing = false;
|
||||||
@@ -932,6 +936,8 @@
|
|||||||
|
|
||||||
async Task TakeOffWall()
|
async Task TakeOffWall()
|
||||||
{
|
{
|
||||||
|
if (!await Confirm.Ask(Localizer["Take this post off your wall?"], null, Localizer["Take off my wall"], true))
|
||||||
|
return;
|
||||||
isActing = true;
|
isActing = true;
|
||||||
var result = await StatusService.Delete(Status.Id);
|
var result = await StatusService.Delete(Status.Id);
|
||||||
isActing = false;
|
isActing = false;
|
||||||
@@ -947,6 +953,10 @@
|
|||||||
|
|
||||||
async Task AccountAction(string action)
|
async Task AccountAction(string action)
|
||||||
{
|
{
|
||||||
|
var name = Status.Account?.Name ?? Status.Account?.Acct;
|
||||||
|
if (!await Confirm.Ask(action == "block" ? Localizer["Block {0}?", name] : Localizer["Mute {0}?", name], null,
|
||||||
|
Localizer[action == "block" ? "Block" : "Mute"], action == "block"))
|
||||||
|
return;
|
||||||
isActing = true;
|
isActing = true;
|
||||||
var result = await StatusService.AccountAction(Status.Account.Id, action);
|
var result = await StatusService.AccountAction(Status.Account.Id, action);
|
||||||
isActing = false;
|
isActing = false;
|
||||||
|
|||||||
@@ -187,6 +187,7 @@
|
|||||||
static readonly string[] PostingPolicies = ["followers", "anyone", "moderators"];
|
static readonly string[] PostingPolicies = ["followers", "anyone", "moderators"];
|
||||||
|
|
||||||
[Inject] GroupService GroupService { get; set; }
|
[Inject] GroupService GroupService { get; set; }
|
||||||
|
[Inject] ConfirmService Confirm { get; set; }
|
||||||
|
|
||||||
List<ViewGroup> groups = [];
|
List<ViewGroup> groups = [];
|
||||||
List<ViewGroupMember> members = [];
|
List<ViewGroupMember> members = [];
|
||||||
@@ -281,6 +282,8 @@
|
|||||||
|
|
||||||
async Task Leave(ViewGroup group)
|
async Task Leave(ViewGroup group)
|
||||||
{
|
{
|
||||||
|
if (!await Confirm.Ask(Localizer["Leave {0}?", group.Name ?? group.UserName], null, Localizer["Leave"], true))
|
||||||
|
return;
|
||||||
isSaving = true;
|
isSaving = true;
|
||||||
var result = await GroupService.Leave(CascadingState.PersonaId, group.Id);
|
var result = await GroupService.Leave(CascadingState.PersonaId, group.Id);
|
||||||
isSaving = false;
|
isSaving = false;
|
||||||
|
|||||||
@@ -191,6 +191,7 @@
|
|||||||
|
|
||||||
@code {
|
@code {
|
||||||
[Inject] AccountService AccountService { get; set; }
|
[Inject] AccountService AccountService { get; set; }
|
||||||
|
[Inject] ConfirmService Confirm { get; set; }
|
||||||
[Inject] ListService ListService { get; set; }
|
[Inject] ListService ListService { get; set; }
|
||||||
[Inject] InstanceService InstanceService { get; set; }
|
[Inject] InstanceService InstanceService { get; set; }
|
||||||
[Inject] ILocalStorageService LocalStorage { get; set; }
|
[Inject] ILocalStorageService LocalStorage { get; set; }
|
||||||
@@ -329,6 +330,11 @@
|
|||||||
|
|
||||||
async Task Do(AccountAction action)
|
async Task Do(AccountAction action)
|
||||||
{
|
{
|
||||||
|
var name = account.Name ?? account.Acct;
|
||||||
|
if (action is AccountAction.Block or AccountAction.Mute
|
||||||
|
&& !await Confirm.Ask(action == AccountAction.Block ? Localizer["Block {0}?", name] : Localizer["Mute {0}?", name], null,
|
||||||
|
Localizer[action == AccountAction.Block ? "Block" : "Mute"], action == AccountAction.Block))
|
||||||
|
return;
|
||||||
isActing = true;
|
isActing = true;
|
||||||
var result = await AccountService.Act(account.Id, action);
|
var result = await AccountService.Act(account.Id, action);
|
||||||
isActing = false;
|
isActing = false;
|
||||||
|
|||||||
@@ -62,6 +62,7 @@
|
|||||||
|
|
||||||
@code {
|
@code {
|
||||||
[Inject] StatusService StatusService { get; set; }
|
[Inject] StatusService StatusService { get; set; }
|
||||||
|
[Inject] ConfirmService Confirm { get; set; }
|
||||||
|
|
||||||
List<ScheduledStatus> scheduled = [];
|
List<ScheduledStatus> scheduled = [];
|
||||||
readonly Dictionary<string, string> moves = [];
|
readonly Dictionary<string, string> moves = [];
|
||||||
@@ -129,6 +130,8 @@
|
|||||||
|
|
||||||
async Task Drop(ScheduledStatus item)
|
async Task Drop(ScheduledStatus item)
|
||||||
{
|
{
|
||||||
|
if (!await Confirm.Ask(Localizer["Never publish this post?"], null, Localizer["Never publish it"], true))
|
||||||
|
return;
|
||||||
isSaving = true;
|
isSaving = true;
|
||||||
var result = await StatusService.Unschedule(item.Id);
|
var result = await StatusService.Unschedule(item.Id);
|
||||||
isSaving = false;
|
isSaving = false;
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ builder.Services.AddOptions()
|
|||||||
.AddScoped<DiscoveryService>()
|
.AddScoped<DiscoveryService>()
|
||||||
.AddScoped<StreamingService>()
|
.AddScoped<StreamingService>()
|
||||||
.AddScoped<PublicNoticeService>()
|
.AddScoped<PublicNoticeService>()
|
||||||
|
.AddScoped<ConfirmService>()
|
||||||
.AddScoped<MediaService>()
|
.AddScoped<MediaService>()
|
||||||
.AddScoped<GroupService>()
|
.AddScoped<GroupService>()
|
||||||
.AddScoped<InstanceService>()
|
.AddScoped<InstanceService>()
|
||||||
|
|||||||
@@ -1826,4 +1826,31 @@
|
|||||||
<data name="Open the globe full screen" xml:space="preserve">
|
<data name="Open the globe full screen" xml:space="preserve">
|
||||||
<value>Apri il globo a schermo intero</value>
|
<value>Apri il globo a schermo intero</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Delete this post?" xml:space="preserve">
|
||||||
|
<value>Eliminare questo post?</value>
|
||||||
|
</data>
|
||||||
|
<data name="It goes from here, and the servers that have it are asked to delete it too." xml:space="preserve">
|
||||||
|
<value>Sparisce da qui, e ai server che lo hanno viene chiesto di eliminarlo.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Take this post off your wall?" xml:space="preserve">
|
||||||
|
<value>Togliere questo post dalla tua bacheca?</value>
|
||||||
|
</data>
|
||||||
|
<data name="Block {0}?" xml:space="preserve">
|
||||||
|
<value>Bloccare {0}?</value>
|
||||||
|
</data>
|
||||||
|
<data name="Mute {0}?" xml:space="preserve">
|
||||||
|
<value>Silenziare {0}?</value>
|
||||||
|
</data>
|
||||||
|
<data name="Leave {0}?" xml:space="preserve">
|
||||||
|
<value>Lasciare {0}?</value>
|
||||||
|
</data>
|
||||||
|
<data name="Never publish this post?" xml:space="preserve">
|
||||||
|
<value>Non pubblicare mai questo post?</value>
|
||||||
|
</data>
|
||||||
|
<data name="Log out?" xml:space="preserve">
|
||||||
|
<value>Uscire?</value>
|
||||||
|
</data>
|
||||||
|
<data name="Every persona of this login is signed out on this device." xml:space="preserve">
|
||||||
|
<value>Tutte le persone di questo accesso escono da questo dispositivo.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -1826,4 +1826,31 @@
|
|||||||
<data name="Open the globe full screen" xml:space="preserve">
|
<data name="Open the globe full screen" xml:space="preserve">
|
||||||
<value>Open the globe full screen</value>
|
<value>Open the globe full screen</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Delete this post?" xml:space="preserve">
|
||||||
|
<value>Delete this post?</value>
|
||||||
|
</data>
|
||||||
|
<data name="It goes from here, and the servers that have it are asked to delete it too." xml:space="preserve">
|
||||||
|
<value>It goes from here, and the servers that have it are asked to delete it too.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Take this post off your wall?" xml:space="preserve">
|
||||||
|
<value>Take this post off your wall?</value>
|
||||||
|
</data>
|
||||||
|
<data name="Block {0}?" xml:space="preserve">
|
||||||
|
<value>Block {0}?</value>
|
||||||
|
</data>
|
||||||
|
<data name="Mute {0}?" xml:space="preserve">
|
||||||
|
<value>Mute {0}?</value>
|
||||||
|
</data>
|
||||||
|
<data name="Leave {0}?" xml:space="preserve">
|
||||||
|
<value>Leave {0}?</value>
|
||||||
|
</data>
|
||||||
|
<data name="Never publish this post?" xml:space="preserve">
|
||||||
|
<value>Never publish this post?</value>
|
||||||
|
</data>
|
||||||
|
<data name="Log out?" xml:space="preserve">
|
||||||
|
<value>Log out?</value>
|
||||||
|
</data>
|
||||||
|
<data name="Every persona of this login is signed out on this device." xml:space="preserve">
|
||||||
|
<value>Every persona of this login is signed out on this device.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
namespace decePubClient.Services
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Asks before what can't be undone (deleting a post, blocking, leaving a group, signing out): Ask shows the question
|
||||||
|
/// in ConfirmHost (MainLayout) and returns whether the reader confirmed it. One question at a time; a second one asked
|
||||||
|
/// while the first is open answers the first no.
|
||||||
|
/// </summary>
|
||||||
|
public class ConfirmService
|
||||||
|
{
|
||||||
|
TaskCompletionSource<bool> pending;
|
||||||
|
|
||||||
|
public event Action Changed;
|
||||||
|
|
||||||
|
public bool IsAsking => pending is not null;
|
||||||
|
public string Heading { get; private set; }
|
||||||
|
public string Text { get; private set; }
|
||||||
|
public string ConfirmText { get; private set; }
|
||||||
|
/// <summary>The confirm button shows as a danger (it deletes or cuts something off).</summary>
|
||||||
|
public bool Danger { get; private set; }
|
||||||
|
|
||||||
|
public async Task<bool> Ask(string heading, string text, string confirmText, bool danger, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
pending?.TrySetResult(false);
|
||||||
|
var asking = new TaskCompletionSource<bool>();
|
||||||
|
pending = asking;
|
||||||
|
(Heading, Text, ConfirmText, Danger) = (heading, text, confirmText, danger);
|
||||||
|
Changed?.Invoke();
|
||||||
|
await using var cancel = cancellationToken.Register(() => asking.TrySetResult(false));
|
||||||
|
var confirmed = await asking.Task;
|
||||||
|
if (pending == asking)
|
||||||
|
{
|
||||||
|
pending = null;
|
||||||
|
Changed?.Invoke();
|
||||||
|
}
|
||||||
|
return confirmed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Answer(bool confirmed) => pending?.TrySetResult(confirmed);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,4 +11,5 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ToastHost />
|
<ToastHost />
|
||||||
<PublicNoticeHost />
|
<PublicNoticeHost />
|
||||||
|
<ConfirmHost />
|
||||||
+12
-2
@@ -89,10 +89,10 @@
|
|||||||
</AuthorizeView>
|
</AuthorizeView>
|
||||||
@if (CascadingState.IsSignedIn)
|
@if (CascadingState.IsSignedIn)
|
||||||
{
|
{
|
||||||
<NavLink ActiveClass="btn-active" class="btn justify-start" href="logout">
|
<DButton class="justify-start" OnClick="Logout" data-logout>
|
||||||
<i class="ion-md-log-out text-error"></i>
|
<i class="ion-md-log-out text-error"></i>
|
||||||
<span>@Localizer["Logout"]</span>
|
<span>@Localizer["Logout"]</span>
|
||||||
</NavLink>
|
</DButton>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -139,6 +139,8 @@
|
|||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
[Inject] ConfirmService Confirm { get; set; }
|
||||||
|
|
||||||
bool isMenuOpen;
|
bool isMenuOpen;
|
||||||
bool isPersonaMenuOpen;
|
bool isPersonaMenuOpen;
|
||||||
|
|
||||||
@@ -188,6 +190,14 @@
|
|||||||
|
|
||||||
private void ToggleNavMenu() => isMenuOpen = !isMenuOpen;
|
private void ToggleNavMenu() => isMenuOpen = !isMenuOpen;
|
||||||
|
|
||||||
|
async Task Logout()
|
||||||
|
{
|
||||||
|
isMenuOpen = false;
|
||||||
|
if (!await Confirm.Ask(Localizer["Log out?"], Localizer["Every persona of this login is signed out on this device."], Localizer["Logout"], true))
|
||||||
|
return;
|
||||||
|
Navigation.NavigateTo("logout");
|
||||||
|
}
|
||||||
|
|
||||||
protected async Task ResetToOriginalColour()
|
protected async Task ResetToOriginalColour()
|
||||||
{
|
{
|
||||||
var isDark = IsDark;
|
var isDark = IsDark;
|
||||||
|
|||||||
@@ -148,6 +148,7 @@ namespace decePubClient.E2E.Actions
|
|||||||
await reply.WaitForAsync(new() { Timeout = 15000 });
|
await reply.WaitForAsync(new() { Timeout = 15000 });
|
||||||
await reply.Locator("footer [title='Other']").First.ClickAsync();
|
await reply.Locator("footer [title='Other']").First.ClickAsync();
|
||||||
await reply.GetByRole(AriaRole.Button).Filter(new() { HasText = "Delete" }).First.ClickAsync();
|
await reply.GetByRole(AriaRole.Button).Filter(new() { HasText = "Delete" }).First.ClickAsync();
|
||||||
|
await session.Page.Locator("[data-confirm-yes]").ClickAsync();
|
||||||
Assert.True(await Pasture.Eventually(async () =>
|
Assert.True(await Pasture.Eventually(async () =>
|
||||||
{
|
{
|
||||||
var row = (await Pasture.Stored(origin, replyUri))[replyUri]!;
|
var row = (await Pasture.Stored(origin, replyUri))[replyUri]!;
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ namespace decePubClient.E2E.Actions
|
|||||||
(await PersonaApi.Call(persona, $"/api/v1/scheduled_statuses/{id}"))?["scheduled_at"]?.GetValue<DateTime>().ToUniversalTime()
|
(await PersonaApi.Call(persona, $"/api/v1/scheduled_statuses/{id}"))?["scheduled_at"]?.GetValue<DateTime>().ToUniversalTime()
|
||||||
is { } at && Math.Abs((at - later.ToUniversalTime()).TotalMinutes) < 2, 15), "PrivaPub never moved the post");
|
is { } at && Math.Abs((at - later.ToUniversalTime()).TotalMinutes) < 2, 15), "PrivaPub never moved the post");
|
||||||
await item.GetByRole(AriaRole.Button, new() { Name = "Never publish it" }).ClickAsync();
|
await item.GetByRole(AriaRole.Button, new() { Name = "Never publish it" }).ClickAsync();
|
||||||
|
await page.Locator("[data-confirm-yes]").ClickAsync();
|
||||||
await Assertions.Expect(item).ToHaveCountAsync(0, new() { Timeout = 15000 });
|
await Assertions.Expect(item).ToHaveCountAsync(0, new() { Timeout = 15000 });
|
||||||
Assert.Null(await PersonaApi.Call(persona, $"/api/v1/scheduled_statuses/{id}"));
|
Assert.Null(await PersonaApi.Call(persona, $"/api/v1/scheduled_statuses/{id}"));
|
||||||
await session.AssertGuards();
|
await session.AssertGuards();
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ namespace decePubClient.E2E.Actions
|
|||||||
await Assertions.Expect(card.Locator("[data-wall]")).ToContainTextAsync(me["display_name"]!.GetValue<string>() is { Length: > 0 } name ? name : me["username"]!.GetValue<string>());
|
await Assertions.Expect(card.Locator("[data-wall]")).ToContainTextAsync(me["display_name"]!.GetValue<string>() is { Length: > 0 } name ? name : me["username"]!.GetValue<string>());
|
||||||
await card.Locator("footer [title='Other'] > [role=button]").ClickAsync();
|
await card.Locator("footer [title='Other'] > [role=button]").ClickAsync();
|
||||||
await card.Locator("button[data-take-off-wall]").ClickAsync();
|
await card.Locator("button[data-take-off-wall]").ClickAsync();
|
||||||
|
await session.Page.Locator("[data-confirm-yes]").ClickAsync();
|
||||||
Assert.True(await Pasture.Eventually(async () => await PersonaApi.Call(persona, $"/api/v1/statuses/{status["id"]!.GetValue<string>()}") is null, 15),
|
Assert.True(await Pasture.Eventually(async () => await PersonaApi.Call(persona, $"/api/v1/statuses/{status["id"]!.GetValue<string>()}") is null, 15),
|
||||||
"the post is still on PrivaPub");
|
"the post is still on PrivaPub");
|
||||||
Assert.True(await Pasture.Eventually(async () =>
|
Assert.True(await Pasture.Eventually(async () =>
|
||||||
|
|||||||
@@ -225,6 +225,43 @@ namespace decePubClient.E2E.Mobile
|
|||||||
}, page);
|
}, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Deleting_a_post_asks_first()
|
||||||
|
{
|
||||||
|
var persona = Persona();
|
||||||
|
var text = $"to be deleted {Guid.NewGuid():N}"[..26];
|
||||||
|
var made = (await PersonaApi.Call(persona, "/api/v1/statuses", HttpMethod.Post, new JsonObject { ["status"] = text, ["visibility"] = "public" }))!;
|
||||||
|
var id = made["id"]!.GetValue<string>();
|
||||||
|
await using var session = await SignIn.As(browser, persona, Phone);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await Flow(session, "delete", async page =>
|
||||||
|
{
|
||||||
|
var card = page.Locator($"#feed-scroll article[data-uri='{made["uri"]}']").First;
|
||||||
|
await card.WaitForAsync(new() { Timeout = 30000 });
|
||||||
|
var more = card.Locator("footer [title='Other'] > [role=button]");
|
||||||
|
await more.TapAsync();
|
||||||
|
await card.Locator("[data-sheet] button", new() { HasText = "Delete" }).TapAsync();
|
||||||
|
var confirm = page.Locator("[data-confirm].modal-open");
|
||||||
|
await Assertions.Expect(confirm).ToBeVisibleAsync();
|
||||||
|
await AssertWhole(page, "[data-confirm] .modal-box");
|
||||||
|
await page.Locator("[data-confirm-no]").TapAsync();
|
||||||
|
await Assertions.Expect(confirm).ToHaveCountAsync(0);
|
||||||
|
Assert.NotNull(await PersonaApi.Call(persona, $"/api/v1/statuses/{id}"));
|
||||||
|
|
||||||
|
await more.TapAsync();
|
||||||
|
await card.Locator("[data-sheet] button", new() { HasText = "Delete" }).TapAsync();
|
||||||
|
await page.Locator("[data-confirm-yes]").TapAsync();
|
||||||
|
await Assertions.Expect(card).ToHaveCountAsync(0, new() { Timeout = 15000 });
|
||||||
|
Assert.True(await Pasture.Eventually(async () => await PersonaApi.Call(persona, $"/api/v1/statuses/{id}") is null, 15), "the post is still there");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
await PersonaApi.Call(persona, $"/api/v1/statuses/{id}", HttpMethod.Delete);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static async Task<JsonNode> Latest(Pasture.Persona persona) =>
|
static async Task<JsonNode> Latest(Pasture.Persona persona) =>
|
||||||
(await PersonaApi.Call(persona, $"/api/v1/accounts/{persona.LocalId}/statuses?limit=1"))!.AsArray().FirstOrDefault();
|
(await PersonaApi.Call(persona, $"/api/v1/accounts/{persona.LocalId}/statuses?limit=1"))!.AsArray().FirstOrDefault();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user