feat: advance legacy UI behavior parity
This commit is contained in:
@@ -79,17 +79,59 @@ public sealed class LegacyThemeWorkflowTests
|
||||
workflow.CreateInitial(ThemeSession.NotApplicable));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, ThemeSession.PreMarket)]
|
||||
[InlineData(12, ThemeSession.PreMarket)]
|
||||
[InlineData(13, ThemeSession.AfterMarket)]
|
||||
[InlineData(23, ThemeSession.AfterMarket)]
|
||||
public void CurrentNxtSession_UsesTheOriginalNativeLocalHourBoundary(
|
||||
int hour,
|
||||
ThemeSession expected)
|
||||
{
|
||||
var workflow = new LegacyThemeWorkflow(
|
||||
new FakeThemeSelectionService(),
|
||||
() => new DateTimeOffset(2026, 7, 18, hour, 0, 0, TimeSpan.FromHours(9)));
|
||||
|
||||
Assert.Equal(expected, workflow.CurrentNxtSession);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SearchAsync_NormalizesQueryPreservesExplicitIdentityAndOrdersRows()
|
||||
public async Task ResetForTabActivation_RestoresTheNewUc4Defaults()
|
||||
{
|
||||
var identity = Identity(
|
||||
ThemeMarket.Krx,
|
||||
ThemeSession.NotApplicable,
|
||||
"AI",
|
||||
"00000001");
|
||||
var workflow = new LegacyThemeWorkflow(
|
||||
ServiceWithTheme(identity, [new(0, "P005930", "삼성전자")]));
|
||||
var selected = workflow.SelectSort(
|
||||
await SearchAndSelectAsync(workflow, ThemeSession.AfterMarket),
|
||||
"GAIN_DESC");
|
||||
|
||||
var reset = workflow.ResetForTabActivation(selected);
|
||||
|
||||
Assert.Equal(selected.Revision + 1, reset.Revision);
|
||||
Assert.Equal(string.Empty, reset.Query);
|
||||
Assert.Equal(ThemeSession.PreMarket, reset.NxtSession);
|
||||
Assert.Equal(LegacyThemeSort.InputOrder, reset.Sort);
|
||||
Assert.Empty(reset.SearchResults);
|
||||
Assert.Null(reset.SelectedResultId);
|
||||
Assert.Null(reset.SelectedTheme);
|
||||
Assert.Empty(reset.PreviewItems);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SearchAsync_NormalizesQueryAndPreservesProviderOrderWithinKrxThenNxt()
|
||||
{
|
||||
var service = new FakeThemeSelectionService
|
||||
{
|
||||
SearchHandler = (query, session) => SearchResult(
|
||||
query,
|
||||
session,
|
||||
Selector(ThemeMarket.Nxt, session, "Robotics", "00000003"),
|
||||
Selector(ThemeMarket.Krx, ThemeSession.NotApplicable, "Zulu", "00000002"),
|
||||
Selector(ThemeMarket.Krx, ThemeSession.NotApplicable, "Alpha", "00000001"))
|
||||
Selector(ThemeMarket.Krx, ThemeSession.NotApplicable, "Alpha", "00000001"),
|
||||
Selector(ThemeMarket.Nxt, session, "Robotics", "00000003"))
|
||||
};
|
||||
var workflow = new LegacyThemeWorkflow(service);
|
||||
var initial = workflow.CreateInitial();
|
||||
@@ -110,12 +152,12 @@ public sealed class LegacyThemeWorkflowTests
|
||||
row =>
|
||||
{
|
||||
Assert.Equal("theme-result-001", row.ResultId);
|
||||
Assert.Equal("Alpha", row.DisplayTitle);
|
||||
Assert.Equal("Zulu", row.DisplayTitle);
|
||||
Assert.Equal(ThemeMarket.Krx, row.Market);
|
||||
Assert.Equal(ThemeSession.NotApplicable, row.Session);
|
||||
Assert.Equal(DataSourceKind.Oracle, row.Source);
|
||||
},
|
||||
row => Assert.Equal("Zulu", row.DisplayTitle),
|
||||
row => Assert.Equal("Alpha", row.DisplayTitle),
|
||||
row =>
|
||||
{
|
||||
Assert.Equal("theme-result-003", row.ResultId);
|
||||
@@ -195,8 +237,30 @@ public sealed class LegacyThemeWorkflowTests
|
||||
session,
|
||||
Selector(ThemeMarket.Krx, ThemeSession.NotApplicable, "AI", "00000001"),
|
||||
Selector(ThemeMarket.Krx, ThemeSession.NotApplicable, "AI", "00000002"));
|
||||
await Assert.ThrowsAsync<LegacyThemeWorkflowDataException>(() =>
|
||||
workflow.SearchAsync(snapshot, "AI", ThemeSession.PreMarket));
|
||||
var duplicateTitles = await workflow.SearchAsync(
|
||||
snapshot,
|
||||
"AI",
|
||||
ThemeSession.PreMarket);
|
||||
Assert.Equal(2, duplicateTitles.SearchResults.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SearchAsync_RejectsProviderRowsThatWouldRequireReordering()
|
||||
{
|
||||
var service = new FakeThemeSelectionService
|
||||
{
|
||||
SearchHandler = (query, session) => SearchResult(
|
||||
query,
|
||||
session,
|
||||
Selector(ThemeMarket.Nxt, session, "NXT first", "00000002"),
|
||||
Selector(ThemeMarket.Krx, ThemeSession.NotApplicable, "KRX later", "00000001"))
|
||||
};
|
||||
var workflow = new LegacyThemeWorkflow(service);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<LegacyThemeWorkflowDataException>(() =>
|
||||
workflow.SearchAsync(workflow.CreateInitial(), "", ThemeSession.PreMarket));
|
||||
|
||||
Assert.Contains("search provider order", exception.Message, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -279,6 +343,38 @@ public sealed class LegacyThemeWorkflowTests
|
||||
Assert.Single(selected.PreviewItems);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SearchAsync_RetainsOnlyACorrelatedSelectionAndPreview()
|
||||
{
|
||||
var identity = Identity(
|
||||
ThemeMarket.Krx,
|
||||
ThemeSession.NotApplicable,
|
||||
"AI",
|
||||
"00000001");
|
||||
var service = ServiceWithTheme(identity, [new(0, "P005930", "삼성전자")]);
|
||||
var workflow = new LegacyThemeWorkflow(service);
|
||||
var selected = workflow.SelectSort(await SearchAndSelectAsync(workflow), "GAIN_DESC");
|
||||
|
||||
var refreshed = await workflow.SearchAsync(
|
||||
selected,
|
||||
"AI",
|
||||
ThemeSession.AfterMarket);
|
||||
|
||||
Assert.Equal(
|
||||
new LegacySelectedThemeSummary(
|
||||
ThemeMarket.Krx,
|
||||
ThemeSession.NotApplicable,
|
||||
"AI",
|
||||
1,
|
||||
false),
|
||||
refreshed.SelectedTheme);
|
||||
Assert.NotNull(refreshed.SelectedResultId);
|
||||
Assert.Single(refreshed.PreviewItems);
|
||||
Assert.Equal("삼성전자", refreshed.PreviewItems[0].ItemName);
|
||||
Assert.Equal(LegacyThemeSort.GainRateDescending, refreshed.Sort);
|
||||
Assert.Single(service.PreviewCalls);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SelectAsync_RejectsUnknownResultWithoutCallingPreview()
|
||||
{
|
||||
@@ -398,7 +494,18 @@ public sealed class LegacyThemeWorkflowTests
|
||||
Assert.Equal(action.Code, draft.Code);
|
||||
Assert.Equal(action.Aliases, draft.Aliases);
|
||||
Assert.Equal("AI", draft.Title);
|
||||
Assert.Equal($"{action.Label} · {sortLabel}", draft.Detail);
|
||||
var expectedGraphicType = action.Id switch
|
||||
{
|
||||
"five-row-current" or "five-row-expected" => "5단 표그래프",
|
||||
"six-row-current" => "6종목 현재가",
|
||||
"twelve-row-current" => "12종목 현재가",
|
||||
_ => throw new InvalidOperationException()
|
||||
};
|
||||
var expectedSubtype =
|
||||
$"테마-{(action.ValueKind == LegacyThemeValueKind.Expected ? "예상체결가" : "현재가")}({sortLabel})";
|
||||
Assert.Equal(expectedGraphicType, draft.PlaylistGraphicType);
|
||||
Assert.Equal(expectedSubtype, draft.PlaylistSubtype);
|
||||
Assert.Equal(expectedSubtype, draft.Detail);
|
||||
Assert.Equal("plate", draft.Category);
|
||||
Assert.Equal("theme", draft.Market);
|
||||
Assert.Equal("PAGED_THEME", draft.Selection.GroupCode);
|
||||
@@ -418,7 +525,7 @@ public sealed class LegacyThemeWorkflowTests
|
||||
[InlineData("INPUT_ORDER")]
|
||||
[InlineData("GAIN_DESC")]
|
||||
[InlineData("GAIN_ASC")]
|
||||
public async Task MaterializePlaylistDraft_NxtKeepsSessionAndBlocksExpected(
|
||||
public async Task MaterializePlaylistDraft_NxtKeepsSessionAndExpectedClickShowsOriginalWarning(
|
||||
string sortId)
|
||||
{
|
||||
var identity = Identity(
|
||||
@@ -427,7 +534,9 @@ public sealed class LegacyThemeWorkflowTests
|
||||
"Robotics",
|
||||
"00000002");
|
||||
var service = ServiceWithTheme(identity, CreateItems(7, ThemeMarket.Nxt));
|
||||
var workflow = new LegacyThemeWorkflow(service);
|
||||
var workflow = new LegacyThemeWorkflow(
|
||||
service,
|
||||
() => new DateTimeOffset(2026, 7, 18, 14, 0, 0, TimeSpan.FromHours(9)));
|
||||
var selected = workflow.SelectSort(
|
||||
await SearchAndSelectAsync(workflow, ThemeSession.AfterMarket),
|
||||
sortId);
|
||||
@@ -451,11 +560,38 @@ public sealed class LegacyThemeWorkflowTests
|
||||
}
|
||||
|
||||
var expectedState = workflow.GetActionState(selected, "five-row-expected");
|
||||
Assert.False(expectedState.IsAvailable);
|
||||
Assert.Equal("expected-theme-is-krx-only", expectedState.UnavailableReason);
|
||||
Assert.True(expectedState.IsAvailable);
|
||||
Assert.Null(expectedState.UnavailableReason);
|
||||
var exception = Assert.Throws<InvalidOperationException>(() =>
|
||||
workflow.MaterializePlaylistDraft(selected, "five-row-expected"));
|
||||
Assert.Contains("expected-price", exception.Message, StringComparison.Ordinal);
|
||||
Assert.Equal("NXT는 데이터가 없습니다.", exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task MaterializePlaylistDraft_NxtReevaluatesTheNativeHourAtActionTime()
|
||||
{
|
||||
var localNow = new DateTimeOffset(
|
||||
2026,
|
||||
7,
|
||||
18,
|
||||
10,
|
||||
0,
|
||||
0,
|
||||
TimeSpan.FromHours(9));
|
||||
var identity = Identity(
|
||||
ThemeMarket.Nxt,
|
||||
ThemeSession.PreMarket,
|
||||
"Robotics",
|
||||
"00000002");
|
||||
var workflow = new LegacyThemeWorkflow(
|
||||
ServiceWithTheme(identity, CreateItems(1, ThemeMarket.Nxt)),
|
||||
() => localNow);
|
||||
var selected = await SearchAndSelectAsync(workflow, ThemeSession.PreMarket);
|
||||
localNow = localNow.AddHours(4);
|
||||
|
||||
var draft = workflow.MaterializePlaylistDraft(selected, "five-row-current");
|
||||
|
||||
Assert.Equal("AFTER_MARKET", draft.Selection.GraphicType);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
||||
Reference in New Issue
Block a user