Deleting a novel removes its outline, characters, chapters and conversations.
- {
- if (confirm(`Delete "${project.title}" and everything in it? This cannot be undone.`)) {
- remove.mutate(projectId, { onSuccess: () => navigate('/') })
- }
- }}
- >
+ setConfirmingDelete(true)}>
Delete this novel
@@ -154,6 +162,15 @@ export default function SettingsPage() {
onImported={(newProjectId) => navigate(`/projects/${newProjectId}`)}
/>
)}
+
+ {confirmingDelete && (
+ remove.mutate(projectId, { onSuccess: () => navigate('/') })}
+ onClose={() => setConfirmingDelete(false)}
+ />
+ )}
)
}
diff --git a/src/Novelly.Web/src/pages/TagsPage.tsx b/src/Novelly.Web/src/pages/TagsPage.tsx
index 41bfca3..19e632c 100644
--- a/src/Novelly.Web/src/pages/TagsPage.tsx
+++ b/src/Novelly.Web/src/pages/TagsPage.tsx
@@ -2,6 +2,7 @@ import { useState } from 'react'
import { Link, useParams } from 'react-router-dom'
import { useDeleteTag, useTagReferences, useTags, useUpdateTag } from '../api/hooks'
import { EmptyState, ErrorNote, Spinner } from '../components/ui'
+import { ConfirmModal } from '../components/ConfirmModal'
import { TagChip } from '../components/TagEditor'
export default function TagsPage() {
@@ -65,6 +66,7 @@ function TagReferencePanel({ projectId, tagId }: { projectId: string; tagId: str
const { data, isPending, error } = useTagReferences(tagId)
const update = useUpdateTag(projectId)
const remove = useDeleteTag()
+ const [confirmingDelete, setConfirmingDelete] = useState(false)
if (isPending) return
if (error) return
@@ -96,20 +98,22 @@ function TagReferencePanel({ projectId, tagId }: { projectId: string; tagId: str
onBlur={(e) => update.mutate({ id: tagId, color: e.target.value })}
/>
-
- confirm(`Delete the tag “${data.tag.name}”? What carries it is left alone.`) &&
- remove.mutate(tagId)
- }
- >
+ setConfirmingDelete(true)}>
Delete tag
{update.error && }
+ {confirmingDelete && (
+ remove.mutate(tagId)}
+ onClose={() => setConfirmingDelete(false)}
+ />
+ )}
+
{empty && (
+ {
+ Assert.That(listed, Is.Not.Empty);
+ Assert.That(listed.Select(g => g.Name), Has.Member("Fantasy").And.Member("Literary Fiction"));
+ Assert.That(listed.Select(g => g.Name), Is.Ordered);
+ Assert.That(listed.Select(g => g.Id), Is.Unique);
+ });
+ }
+
+ [Test]
+ public async Task A_project_can_be_filed_under_a_genre_off_the_list()
+ {
+ var fantasy = (await Genres.ListAsync()).First(g => g.Name == "Fantasy");
+
+ var project = await Projects.CreateAsync(new CreateProjectRequest("The Salt Road", Genre: fantasy.Name));
+
+ Assert.That(project.Genre, Is.EqualTo("Fantasy"));
+ }
+
+ [Test]
+ public async Task A_project_can_still_carry_a_genre_that_is_not_on_the_list()
+ {
+ var project = await Projects.CreateAsync(
+ new CreateProjectRequest("The Salt Road", Genre: "Nautical Gothic"));
+
+ Assert.Multiple(async () =>
+ {
+ Assert.That(project.Genre, Is.EqualTo("Nautical Gothic"));
+ Assert.That((await Genres.ListAsync()).Select(g => g.Name), Has.No.Member("Nautical Gothic"));
+ });
+ }
+}
diff --git a/tests/Novelly.Api.Tests/ServiceTestFixture.cs b/tests/Novelly.Api.Tests/ServiceTestFixture.cs
index 07c3de9..6768c46 100644
--- a/tests/Novelly.Api.Tests/ServiceTestFixture.cs
+++ b/tests/Novelly.Api.Tests/ServiceTestFixture.cs
@@ -1,6 +1,7 @@
using Novelly.Api.Beats;
using Novelly.Api.Chapters;
using Novelly.Api.Characters;
+using Novelly.Api.Genres;
using Novelly.Api.Projects;
using Novelly.Api.Questions;
using Novelly.Api.Tags;
@@ -17,6 +18,7 @@ public abstract class ServiceTestFixture
protected BeatService Beats { get; private set; } = null!;
protected CharacterArcService Arcs { get; private set; } = null!;
protected OpenQuestionService Questions { get; private set; } = null!;
+ protected GenreService Genres { get; private set; } = null!;
protected CapturingLogger ProjectLogs { get; private set; } = null!;
protected CapturingLogger CharacterLogs { get; private set; } = null!;
@@ -25,6 +27,7 @@ public abstract class ServiceTestFixture
protected CapturingLogger TagLogs { get; private set; } = null!;
protected CapturingLogger ArcLogs { get; private set; } = null!;
protected CapturingLogger QuestionLogs { get; private set; } = null!;
+ protected CapturingLogger GenreLogs { get; private set; } = null!;
[SetUp]
public void SetUpFixture()
@@ -38,6 +41,7 @@ public abstract class ServiceTestFixture
BeatLogs = new CapturingLogger();
ArcLogs = new CapturingLogger();
QuestionLogs = new CapturingLogger();
+ GenreLogs = new CapturingLogger();
Tags = new TagService(Db.Context, TagLogs, new CreateTagRequestValidator(), new UpdateTagRequestValidator());
Projects = new ProjectService(Db.Context, ProjectLogs, new CreateProjectRequestValidator(), new UpdateProjectRequestValidator());
@@ -54,6 +58,7 @@ public abstract class ServiceTestFixture
Questions = new OpenQuestionService(
Db.Context, QuestionLogs,
new CreateOpenQuestionRequestValidator(), new UpdateOpenQuestionRequestValidator(), new ResolveOpenQuestionRequestValidator());
+ Genres = new GenreService(Db.Context, GenreLogs);
OnSetUp();
}