feat: add board interface with cards, columns and dialogs
This commit is contained in:
1 parent
f36561cb7f
commit
830f1153fd
45 files changed
+2843
-101
No files matched your search
@@ -21,9 +21,10 @@ public class AppConfigStoreTests
|
||||
Assert.AreEqual(AppConfig.CurrentFormatVersion, config.Version);
|
||||
Assert.AreEqual(AppConfig.DefaultLanguage, config.Language);
|
||||
Assert.AreEqual(ThemeOption.FollowSystem, config.Theme);
|
||||
Assert.AreEqual(CardSortOption.Id, config.Sort.Card);
|
||||
Assert.AreEqual(ColumnWidthPreset.Standard, config.ColumnWidth.Preset);
|
||||
Assert.AreEqual(320, config.ColumnWidth.CustomPixels);
|
||||
Assert.AreEqual(SortOption.IdAscending, config.Display.CardSort);
|
||||
Assert.AreEqual(SortOption.IdAscending, config.Display.ColumnSort);
|
||||
Assert.AreEqual(ColumnWidthPreset.Standard, config.Display.ColumnWidth.Preset);
|
||||
Assert.AreEqual(320, config.Display.ColumnWidth.CustomPixels);
|
||||
Assert.IsTrue(config.Confirmations.DeleteCard);
|
||||
Assert.IsTrue(config.Confirmations.DeleteColumn);
|
||||
Assert.IsTrue(config.Confirmations.DeleteTag);
|
||||
@@ -66,8 +67,11 @@ public class AppConfigStoreTests
|
||||
"version": {{AppConfig.CurrentFormatVersion}},
|
||||
"language": "zh-Hans",
|
||||
"theme": "dark",
|
||||
"sort": { "card": "title" },
|
||||
"column-width": { "preset": "custom", "custom-pixels": 400 },
|
||||
"display": {
|
||||
"column-width": { "preset": "custom", "custom-pixels": 400 },
|
||||
"card-sort": "title-asc",
|
||||
"column-sort": "id-desc"
|
||||
},
|
||||
"confirmations": {
|
||||
"delete-card": false,
|
||||
"delete-column": false,
|
||||
@@ -82,9 +86,10 @@ public class AppConfigStoreTests
|
||||
|
||||
Assert.AreEqual("zh-Hans", config.Language);
|
||||
Assert.AreEqual(ThemeOption.Dark, config.Theme);
|
||||
Assert.AreEqual(CardSortOption.Title, config.Sort.Card);
|
||||
Assert.AreEqual(ColumnWidthPreset.Custom, config.ColumnWidth.Preset);
|
||||
Assert.AreEqual(400, config.ColumnWidth.CustomPixels);
|
||||
Assert.AreEqual(SortOption.TitleAscending, config.Display.CardSort);
|
||||
Assert.AreEqual(SortOption.IdDescending, config.Display.ColumnSort);
|
||||
Assert.AreEqual(ColumnWidthPreset.Custom, config.Display.ColumnWidth.Preset);
|
||||
Assert.AreEqual(400, config.Display.ColumnWidth.CustomPixels);
|
||||
Assert.IsFalse(config.Confirmations.DeleteCard);
|
||||
Assert.IsFalse(config.Confirmations.DeleteColumn);
|
||||
Assert.IsFalse(config.Confirmations.DeleteTag);
|
||||
@@ -101,10 +106,10 @@ public class AppConfigStoreTests
|
||||
|
||||
AppConfig config = store.Load();
|
||||
|
||||
Assert.IsNotNull(config.Sort);
|
||||
Assert.IsNotNull(config.ColumnWidth);
|
||||
Assert.IsNotNull(config.Display);
|
||||
Assert.IsNotNull(config.Display.ColumnWidth);
|
||||
Assert.IsNotNull(config.Confirmations);
|
||||
Assert.AreEqual(ColumnWidthPreset.Standard, config.ColumnWidth.Preset);
|
||||
Assert.AreEqual(ColumnWidthPreset.Standard, config.Display.ColumnWidth.Preset);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -117,8 +122,12 @@ public class AppConfigStoreTests
|
||||
var config = new AppConfig
|
||||
{
|
||||
Theme = ThemeOption.FollowSystem,
|
||||
Sort = new SortSettings { Card = CardSortOption.UpdatedAt },
|
||||
ColumnWidth = new ColumnWidthSettings { Preset = ColumnWidthPreset.Custom, CustomPixels = 500 },
|
||||
Display = new DisplaySettings
|
||||
{
|
||||
CardSort = SortOption.UpdatedDescending,
|
||||
ColumnSort = SortOption.CreatedAscending,
|
||||
ColumnWidth = new ColumnWidthSettings { Preset = ColumnWidthPreset.Custom, CustomPixels = 500 },
|
||||
},
|
||||
Confirmations = new ConfirmationSettings { DeleteColumn = false },
|
||||
};
|
||||
store.Save(config);
|
||||
@@ -126,9 +135,12 @@ public class AppConfigStoreTests
|
||||
string json = File.ReadAllText(path);
|
||||
StringAssert.Contains(json, "\"delete-column\"");
|
||||
StringAssert.Contains(json, "\"discard-edit\"");
|
||||
StringAssert.Contains(json, "\"card-sort\"");
|
||||
StringAssert.Contains(json, "\"column-sort\"");
|
||||
StringAssert.Contains(json, "\"custom-pixels\"");
|
||||
StringAssert.Contains(json, "\"follow-system\"");
|
||||
StringAssert.Contains(json, "\"updated-at\"");
|
||||
StringAssert.Contains(json, "\"updated-desc\"");
|
||||
StringAssert.Contains(json, "\"created-asc\"");
|
||||
// No member-name CamelCase must leak into the file.
|
||||
Assert.IsFalse(json.Contains("deleteColumn", StringComparison.Ordinal));
|
||||
Assert.IsFalse(json.Contains("CustomPixels", StringComparison.Ordinal));
|
||||
@@ -145,8 +157,12 @@ public class AppConfigStoreTests
|
||||
{
|
||||
Language = "zh-Hans",
|
||||
Theme = ThemeOption.Light,
|
||||
Sort = new SortSettings { Card = CardSortOption.CreatedAt },
|
||||
ColumnWidth = new ColumnWidthSettings { Preset = ColumnWidthPreset.Wide },
|
||||
Display = new DisplaySettings
|
||||
{
|
||||
CardSort = SortOption.CreatedDescending,
|
||||
ColumnSort = SortOption.TitleAscending,
|
||||
ColumnWidth = new ColumnWidthSettings { Preset = ColumnWidthPreset.Wide },
|
||||
},
|
||||
Confirmations = new ConfirmationSettings { DiscardEdit = false },
|
||||
};
|
||||
store.Save(config);
|
||||
@@ -154,8 +170,9 @@ public class AppConfigStoreTests
|
||||
AppConfig reloaded = store.Load();
|
||||
Assert.AreEqual(config.Language, reloaded.Language);
|
||||
Assert.AreEqual(config.Theme, reloaded.Theme);
|
||||
Assert.AreEqual(config.Sort.Card, reloaded.Sort.Card);
|
||||
Assert.AreEqual(config.ColumnWidth.Preset, reloaded.ColumnWidth.Preset);
|
||||
Assert.AreEqual(config.Display.CardSort, reloaded.Display.CardSort);
|
||||
Assert.AreEqual(config.Display.ColumnSort, reloaded.Display.ColumnSort);
|
||||
Assert.AreEqual(config.Display.ColumnWidth.Preset, reloaded.Display.ColumnWidth.Preset);
|
||||
Assert.AreEqual(config.Confirmations.DiscardEdit, reloaded.Confirmations.DiscardEdit);
|
||||
}
|
||||
}
|
||||
@@ -6,34 +6,43 @@ using YKanBan.Tests.TestUtilities;
|
||||
namespace YKanBan.Tests.Storage.Workspace;
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the preset column titles follow the UI language active when
|
||||
/// they are added and then live as ordinary data. These tests mutate the
|
||||
/// global <see cref="Resources.Culture"/>, so they are marked do-not-parallelize.
|
||||
/// Verifies that the preset columns and tags follow the UI language active when
|
||||
/// they are added and then live as ordinary data. These tests mutate the global
|
||||
/// <see cref="Resources.Culture"/>, so they are marked do-not-parallelize.
|
||||
/// </summary>
|
||||
[TestClass]
|
||||
[DoNotParallelize]
|
||||
public class WorkspacePresetTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void AddPresetColumnsStoresEnglishTitlesUnderEnglishCulture()
|
||||
public void AddPresetColumnsStoresEnglishTextUnderEnglishCulture()
|
||||
{
|
||||
CultureInfo? original = Resources.Culture;
|
||||
try
|
||||
{
|
||||
Resources.Culture = new CultureInfo("en");
|
||||
string[] expected =
|
||||
[
|
||||
Resources.PresetColumn_Todo,
|
||||
Resources.PresetColumn_InProgress,
|
||||
Resources.PresetColumn_Done,
|
||||
];
|
||||
|
||||
using var directory = new TempDirectory();
|
||||
WorkspaceInitializer.Initialize(directory.FullPath);
|
||||
WorkspacePreset.AddPresetColumns(directory.FullPath);
|
||||
|
||||
using SqliteConnection connection = SqliteTestHelper.OpenWorkspace(directory.FullPath);
|
||||
CollectionAssert.AreEqual(expected, SqliteTestHelper.GetColumnTitles(connection));
|
||||
CollectionAssert.AreEqual(
|
||||
new[]
|
||||
{
|
||||
Resources.PresetColumn_Todo,
|
||||
Resources.PresetColumn_InProgress,
|
||||
Resources.PresetColumn_Done,
|
||||
},
|
||||
SqliteTestHelper.GetColumnTitles(connection));
|
||||
CollectionAssert.AreEqual(
|
||||
new[]
|
||||
{
|
||||
Resources.PresetColumn_Todo_Description,
|
||||
Resources.PresetColumn_InProgress_Description,
|
||||
Resources.PresetColumn_Done_Description,
|
||||
},
|
||||
SqliteTestHelper.GetColumnDescriptions(connection));
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -42,25 +51,29 @@ public class WorkspacePresetTests
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void AddPresetColumnsStoresChineseTitlesUnderChineseCulture()
|
||||
public void AddPresetColumnsStoresChineseTextUnderChineseCulture()
|
||||
{
|
||||
CultureInfo? original = Resources.Culture;
|
||||
try
|
||||
{
|
||||
Resources.Culture = new CultureInfo("zh-Hans");
|
||||
string[] expected =
|
||||
[
|
||||
Resources.PresetColumn_Todo,
|
||||
Resources.PresetColumn_InProgress,
|
||||
Resources.PresetColumn_Done,
|
||||
];
|
||||
|
||||
using var directory = new TempDirectory();
|
||||
WorkspaceInitializer.Initialize(directory.FullPath);
|
||||
WorkspacePreset.AddPresetColumns(directory.FullPath);
|
||||
|
||||
using SqliteConnection connection = SqliteTestHelper.OpenWorkspace(directory.FullPath);
|
||||
CollectionAssert.AreEqual(expected, SqliteTestHelper.GetColumnTitles(connection));
|
||||
CollectionAssert.AreEqual(
|
||||
new[]
|
||||
{
|
||||
Resources.PresetColumn_Todo,
|
||||
Resources.PresetColumn_InProgress,
|
||||
Resources.PresetColumn_Done,
|
||||
},
|
||||
SqliteTestHelper.GetColumnTitles(connection));
|
||||
// Chinese resources must resolve to different text than English.
|
||||
Assert.AreEqual("待办", Resources.PresetColumn_Todo);
|
||||
Assert.AreEqual("已完成的任务。", Resources.PresetColumn_Done_Description);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -69,27 +82,74 @@ public class WorkspacePresetTests
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PresetTitlesAreDataAndIgnoreLaterCultureChanges()
|
||||
public void AddPresetTagsStoresEightTagsUnderEnglishCulture()
|
||||
{
|
||||
CultureInfo? original = Resources.Culture;
|
||||
try
|
||||
{
|
||||
Resources.Culture = new CultureInfo("en");
|
||||
|
||||
using var directory = new TempDirectory();
|
||||
WorkspaceInitializer.Initialize(directory.FullPath);
|
||||
WorkspacePreset.AddPresetTags(directory.FullPath);
|
||||
|
||||
using SqliteConnection connection = SqliteTestHelper.OpenWorkspace(directory.FullPath);
|
||||
List<(string Name, string Color, string Description)> tags = SqliteTestHelper.GetTags(connection);
|
||||
|
||||
Assert.AreEqual(8, tags.Count);
|
||||
Assert.AreEqual(("bug", "#d73a4a", "Something isn't working"), tags[0]);
|
||||
Assert.AreEqual(("documentation", "#0075ca", "Improvements or additions to documentation"), tags[1]);
|
||||
Assert.AreEqual(("wontfix", "#ffffff", "This will not be worked on"), tags[7]);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Resources.Culture = original;
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void AddPresetTagsStoresChineseNamesUnderChineseCulture()
|
||||
{
|
||||
CultureInfo? original = Resources.Culture;
|
||||
try
|
||||
{
|
||||
Resources.Culture = new CultureInfo("zh-Hans");
|
||||
|
||||
using var directory = new TempDirectory();
|
||||
WorkspaceInitializer.Initialize(directory.FullPath);
|
||||
WorkspacePreset.AddPresetTags(directory.FullPath);
|
||||
|
||||
using SqliteConnection connection = SqliteTestHelper.OpenWorkspace(directory.FullPath);
|
||||
List<(string Name, string Color, string Description)> tags = SqliteTestHelper.GetTags(connection);
|
||||
|
||||
Assert.AreEqual(8, tags.Count);
|
||||
Assert.AreEqual("缺陷", tags[0].Name);
|
||||
Assert.AreEqual("功能没有正常工作", tags[0].Description);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Resources.Culture = original;
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PresetContentIsDataAndIgnoresLaterCultureChanges()
|
||||
{
|
||||
CultureInfo? original = Resources.Culture;
|
||||
try
|
||||
{
|
||||
Resources.Culture = new CultureInfo("en");
|
||||
string[] expected =
|
||||
[
|
||||
Resources.PresetColumn_Todo,
|
||||
Resources.PresetColumn_InProgress,
|
||||
Resources.PresetColumn_Done,
|
||||
];
|
||||
|
||||
using var directory = new TempDirectory();
|
||||
WorkspaceInitializer.Initialize(directory.FullPath);
|
||||
WorkspacePreset.AddPresetColumns(directory.FullPath);
|
||||
WorkspacePreset.AddPresetTags(directory.FullPath);
|
||||
|
||||
// Switching the language after adding the presets must not rewrite stored data.
|
||||
Resources.Culture = new CultureInfo("zh-Hans");
|
||||
using SqliteConnection connection = SqliteTestHelper.OpenWorkspace(directory.FullPath);
|
||||
CollectionAssert.AreEqual(expected, SqliteTestHelper.GetColumnTitles(connection));
|
||||
Assert.AreEqual("To Do", SqliteTestHelper.GetColumnTitles(connection)[0]);
|
||||
Assert.AreEqual("bug", SqliteTestHelper.GetTags(connection)[0].Name);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -87,6 +87,44 @@ public static class SqliteTestHelper
|
||||
return titles;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the descriptions of all columns, ordered by id ascending.
|
||||
/// </summary>
|
||||
/// <param name="connection">An open database connection.</param>
|
||||
/// <returns>The column descriptions in id order.</returns>
|
||||
public static List<string> GetColumnDescriptions(SqliteConnection connection)
|
||||
{
|
||||
using var command = connection.CreateCommand();
|
||||
command.CommandText = "SELECT description FROM columns ORDER BY id ASC;";
|
||||
using var reader = command.ExecuteReader();
|
||||
|
||||
var descriptions = new List<string>();
|
||||
while (reader.Read())
|
||||
{
|
||||
descriptions.Add(reader.GetString(0));
|
||||
}
|
||||
return descriptions;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all tags as name/color/description tuples, ordered by id ascending.
|
||||
/// </summary>
|
||||
/// <param name="connection">An open database connection.</param>
|
||||
/// <returns>The tags in id order.</returns>
|
||||
public static List<(string Name, string Color, string Description)> GetTags(SqliteConnection connection)
|
||||
{
|
||||
using var command = connection.CreateCommand();
|
||||
command.CommandText = "SELECT name, color, description FROM tags ORDER BY id ASC;";
|
||||
using var reader = command.ExecuteReader();
|
||||
|
||||
var tags = new List<(string, string, string)>();
|
||||
while (reader.Read())
|
||||
{
|
||||
tags.Add((reader.GetString(0), reader.GetString(1), reader.GetString(2)));
|
||||
}
|
||||
return tags;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens a workspace database with the real workspace migration list.
|
||||
/// </summary>
|
||||
|
||||
Reference in new issue
Block a user