feat: import leftover student fields into notes and show them on the roster

Store leftover CSV columns on hidden student notes, move catalog import to /events/import, and persist Students index columns from Chapter Settings.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-29 23:55:03 -04:00
co-authored by Cursor
parent 4c91db37c2
commit 4cfd85b902
39 changed files with 2437 additions and 166 deletions
+179
View File
@@ -0,0 +1,179 @@
using Core.Notes;
namespace Tests.Notes;
[TestFixture]
public class ImportedFieldsTable_Tests
{
[Test]
public void NormalizeValue_X_BecomesYes()
{
Assert.That(ImportedFieldsTable.NormalizeValue("x"), Is.EqualTo("Yes"));
Assert.That(ImportedFieldsTable.NormalizeValue("X"), Is.EqualTo("Yes"));
Assert.That(ImportedFieldsTable.NormalizeValue(" x "), Is.EqualTo("Yes"));
}
[Test]
public void NormalizeValue_Blank_StaysEmpty()
{
Assert.That(ImportedFieldsTable.NormalizeValue(null), Is.EqualTo(string.Empty));
Assert.That(ImportedFieldsTable.NormalizeValue(" "), Is.EqualTo(string.Empty));
}
[Test]
public void Merge_IncomingWins_AndAppendsNewFields()
{
var existing = """
Freeform note
## Imported fields
| Field | Value |
| --- | --- |
| Interview Time | 1:00-1:15 |
| Application | Yes |
""";
var result = ImportedFieldsTable.Merge(existing,
[
new ImportedField("Interview Time", "3:20-3:35"),
new ImportedField("Teacher Rec 1", "Fuqua")
]);
Assert.That(result.Changed, Is.True);
Assert.That(result.Changes, Has.Count.EqualTo(2));
var fields = ImportedFieldsTable.ParseFields(result.Markdown);
Assert.That(fields.Single(f => f.Name == "Interview Time").Value, Is.EqualTo("3:20-3:35"));
Assert.That(fields.Single(f => f.Name == "Application").Value, Is.EqualTo("Yes"));
Assert.That(fields.Single(f => f.Name == "Teacher Rec 1").Value, Is.EqualTo("Fuqua"));
Assert.That(result.Markdown, Does.Contain("Freeform note"));
}
[Test]
public void Merge_SameDataTwice_IsNoOp()
{
var first = ImportedFieldsTable.Merge(null,
[
new ImportedField("Interview Time", "3:20-3:35"),
new ImportedField("Application", "x")
]);
Assert.That(first.Changed, Is.True);
Assert.That(ImportedFieldsTable.ParseFields(first.Markdown).Single(f => f.Name == "Application").Value, Is.EqualTo("Yes"));
var second = ImportedFieldsTable.Merge(first.Markdown,
[
new ImportedField("Interview Time", "3:20-3:35"),
new ImportedField("Application", "x")
]);
Assert.That(second.Changed, Is.False);
Assert.That(second.Changes, Is.Empty);
Assert.That(ImportedFieldsTable.ParseFields(second.Markdown), Is.EqualTo(ImportedFieldsTable.ParseFields(first.Markdown)));
}
[Test]
public void GetFieldValue_ReadsImportedTable()
{
var markdown = ImportedFieldsTable.FormatSection(
[
new ImportedField("Interview Time", "3:20-3:35")
]);
Assert.That(ImportedFieldsTable.GetFieldValue(markdown, "Interview Time"), Is.EqualTo("3:20-3:35"));
Assert.That(ImportedFieldsTable.GetFieldValue(markdown, "Application"), Is.Null);
}
[Test]
public void Merge_PreservesTextAfterSection()
{
var existing = """
## Imported fields
| Field | Value |
| --- | --- |
| Application | Yes |
## Advisor comments
Great interview.
""";
var result = ImportedFieldsTable.Merge(existing, [new ImportedField("Application", "Yes")]);
Assert.That(result.Changed, Is.False);
Assert.That(result.Markdown, Does.Contain("## Advisor comments"));
Assert.That(result.Markdown, Does.Contain("Great interview."));
}
[Test]
public void ParseFields_KeepsBlankValues()
{
var markdown = ImportedFieldsTable.FormatSection(
[
new ImportedField("Application", "Yes"),
new ImportedField("Teacher Rec 3", "")
]);
var fields = ImportedFieldsTable.ParseFields(markdown);
Assert.That(fields.Single(f => f.Name == "Teacher Rec 3").Value, Is.EqualTo(string.Empty));
Assert.That(ImportedFieldsTable.GetFieldValue(markdown, "Teacher Rec 3"), Is.EqualTo(string.Empty));
}
[Test]
public void Merge_BlankFields_SameDataTwice_IsNoOp()
{
var first = ImportedFieldsTable.Merge(null,
[
new ImportedField("Interview Time", "3:20-3:35"),
new ImportedField("Teacher Rec 3", "")
]);
Assert.That(first.Changed, Is.True);
Assert.That(ImportedFieldsTable.ParseFields(first.Markdown), Has.Count.EqualTo(2));
var second = ImportedFieldsTable.Merge(first.Markdown,
[
new ImportedField("Interview Time", "3:20-3:35"),
new ImportedField("Teacher Rec 3", "")
]);
Assert.That(second.Changed, Is.False);
Assert.That(second.Changes, Is.Empty);
}
[Test]
public void Merge_IncomingBlank_ClearsExistingValue()
{
var existing = ImportedFieldsTable.Merge(null, [new ImportedField("Teacher Rec 1", "Fuqua")]).Markdown;
var result = ImportedFieldsTable.Merge(existing, [new ImportedField("Teacher Rec 1", "")]);
Assert.That(result.Changed, Is.True);
Assert.That(ImportedFieldsTable.GetFieldValue(result.Markdown, "Teacher Rec 1"), Is.EqualTo(string.Empty));
}
[Test]
public void GetFieldValue_IsCaseInsensitive()
{
var markdown = ImportedFieldsTable.FormatSection([new ImportedField("Interview Time", "3:20-3:35")]);
Assert.That(ImportedFieldsTable.GetFieldValue(markdown, "interview time"), Is.EqualTo("3:20-3:35"));
}
[Test]
public void DistinctFieldNames_UnionsNotes_AndKeepsFirstCasing()
{
var first = ImportedFieldsTable.FormatSection(
[
new ImportedField("Interview Time", "3:20-3:35"),
new ImportedField("Application", "Yes")
]);
var second = ImportedFieldsTable.FormatSection(
[
new ImportedField("application", "Yes"),
new ImportedField("Teacher Rec 1", "Fuqua")
]);
var names = ImportedFieldsTable.DistinctFieldNames([first, second, null, ""]);
Assert.That(names, Is.EqualTo(new[] { "Application", "Interview Time", "Teacher Rec 1" }));
}
}