Remove Scenes, group beats by multiple characters; strip comments repo-wide
Drop the Scene entity/grouping in favor of chapters carrying prose directly and beats belonging to many characters. Add markdown editor + character multi-select components to the web client. Remove all XML doc and inline comments across the touched C#/TS/CSS files in favor of self-documenting names, and record that convention in CLAUDE.md. Add .mcp.json (local MCP server config, no secrets) and ignore .idea/.
This commit is contained in:
@@ -1,9 +1,7 @@
|
||||
using Novelly.Api.Beats;
|
||||
using Novelly.Api.Chapters;
|
||||
using Novelly.Api.Characters;
|
||||
using Novelly.Api.Common;
|
||||
using Novelly.Api.Projects;
|
||||
using Novelly.Api.Scenes;
|
||||
|
||||
namespace Novelly.Api.Tests;
|
||||
|
||||
@@ -75,22 +73,20 @@ public class BeatServiceTests : ServiceTestFixture
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task A_beat_resolves_its_character_and_scene_names()
|
||||
public async Task A_beat_can_carry_several_characters()
|
||||
{
|
||||
var ines = await Characters.CreateAsync(_projectId, new CreateCharacterRequest("Ines"));
|
||||
var scene = await Scenes.CreateAsync(_chapterId, new CreateSceneRequest("The dock at dawn"));
|
||||
var mara = await Characters.CreateAsync(_projectId, new CreateCharacterRequest("Mara"));
|
||||
|
||||
var beat = await Beats.CreateAsync(_chapterId, new CreateBeatRequest(
|
||||
"She burns the atlas",
|
||||
CharacterId: ines.Id,
|
||||
CharacterIds: [ines.Id, mara.Id],
|
||||
WhatHappened: "The pages go up faster than she expected.",
|
||||
WhatsNext: "Nothing to navigate by but memory.",
|
||||
SceneId: scene.Id));
|
||||
WhatsNext: "Nothing to navigate by but memory."));
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(beat.Character!.Name, Is.EqualTo("Ines"));
|
||||
Assert.That(beat.Scene!.Title, Is.EqualTo("The dock at dawn"));
|
||||
Assert.That(beat.Characters.Select(c => c.Name), Is.EquivalentTo(new[] { "Ines", "Mara" }));
|
||||
Assert.That(beat.WhatHappened, Does.Contain("faster than she expected"));
|
||||
});
|
||||
}
|
||||
@@ -103,41 +99,10 @@ public class BeatServiceTests : ServiceTestFixture
|
||||
|
||||
Assert.That(
|
||||
async () => await Beats.CreateAsync(
|
||||
_chapterId, new CreateBeatRequest("A beat", CharacterId: stranger.Id)),
|
||||
_chapterId, new CreateBeatRequest("A beat", CharacterIds: [stranger.Id])),
|
||||
Throws.TypeOf<InvalidOperationException>().With.Message.Contains("same project"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task A_beat_cannot_be_grouped_under_a_scene_from_another_chapter()
|
||||
{
|
||||
var elsewhere = await Chapters.CreateAsync(_projectId, new CreateChapterRequest("Elsewhere"));
|
||||
var scene = await Scenes.CreateAsync(elsewhere.Id, new CreateSceneRequest("Another scene"));
|
||||
|
||||
Assert.That(
|
||||
async () => await Beats.CreateAsync(
|
||||
_chapterId, new CreateBeatRequest("A beat", SceneId: scene.Id)),
|
||||
Throws.TypeOf<InvalidOperationException>().With.Message.Contains("same chapter"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Deleting_a_scene_leaves_its_beats_alone()
|
||||
{
|
||||
var scene = await Scenes.CreateAsync(_chapterId, new CreateSceneRequest("The dock at dawn"));
|
||||
var beat = await Beats.CreateAsync(
|
||||
_chapterId, new CreateBeatRequest("She burns the atlas", SceneId: scene.Id));
|
||||
|
||||
await Scenes.DeleteAsync(scene.Id);
|
||||
|
||||
// The plan outlives a decision about prose — the beat is simply ungrouped.
|
||||
var survivor = (await Beats.GetAsync(beat.Id))!;
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(survivor.SceneId, Is.Null);
|
||||
Assert.That(survivor.Title, Is.EqualTo("She burns the atlas"));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Updating_leaves_omitted_fields_alone_and_clears_on_empty_string()
|
||||
{
|
||||
@@ -164,23 +129,17 @@ public class BeatServiceTests : ServiceTestFixture
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ClearCharacter_and_ClearScene_detach_the_reference_since_a_null_id_means_leave_it_alone()
|
||||
public async Task An_empty_CharacterIds_list_clears_a_beats_characters_since_null_means_leave_it_alone()
|
||||
{
|
||||
var ines = await Characters.CreateAsync(_projectId, new CreateCharacterRequest("Ines"));
|
||||
var scene = await Scenes.CreateAsync(_chapterId, new CreateSceneRequest("The dock at dawn"));
|
||||
var beat = await Beats.CreateAsync(_chapterId, new CreateBeatRequest(
|
||||
"She burns the atlas", CharacterId: ines.Id, SceneId: scene.Id));
|
||||
"She burns the atlas", CharacterIds: [ines.Id]));
|
||||
|
||||
var untouched = (await Beats.UpdateAsync(beat.Id, new UpdateBeatRequest(Title: "She burns it")))!;
|
||||
Assert.That(untouched.Character!.Name, Is.EqualTo("Ines"));
|
||||
Assert.That(untouched.Characters.Select(c => c.Name), Is.EqualTo(new[] { "Ines" }));
|
||||
|
||||
var cleared = (await Beats.UpdateAsync(
|
||||
beat.Id, new UpdateBeatRequest(ClearCharacter: true, ClearScene: true)))!;
|
||||
var cleared = (await Beats.UpdateAsync(beat.Id, new UpdateBeatRequest(CharacterIds: [])))!;
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(cleared.Character, Is.Null);
|
||||
Assert.That(cleared.Scene, Is.Null);
|
||||
});
|
||||
Assert.That(cleared.Characters, Is.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user