-
Notifications
You must be signed in to change notification settings - Fork 23
refactor: UI Component Refactoring - ChatMessageItem.razor #469
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
sikutisa
merged 21 commits into
aliencube:main
from
qoweh:feat/301-UIComponentRefactoring-ChatMessageItem-razor
Oct 5, 2025
+162
−27
Merged
Changes from 14 commits
Commits
Show all changes
21 commits
Select commit
Hold shift + click to select a range
4714dd8
refactor: split UI logic in ChatHeader.razor
qoweh 04735cc
test: add integration tests for NewChat button and icon visibility
qoweh a8f0a7d
Merge remote-tracking branch 'upstream/main'
qoweh 6e68171
Merge remote-tracking branch 'origin/main'
qoweh 1f5783c
Merge branch 'aliencube:main' into main
qoweh 5094a9f
Merge branch 'aliencube:main' into main
qoweh 8ed4bfd
Merge branch 'aliencube:main' into main
qoweh 39354bc
Merge branch 'aliencube:main' into main
qoweh d2aab18
Merge remote-tracking branch 'aliencube/main' into feat/301-UICompone…
qoweh 0390aed
refactor: Move ChatMessageItem logic to separate code-behind file
qoweh b3db7d8
test: Add integration tests for ChatMessageItem component
qoweh 1588a7b
refactor: Rename test method for clarity and reintroduce assistant ic…
qoweh 1569fc3
refactor: Improve readability of assistant message visibility check i…
qoweh 12d19ab
Merge remote-tracking branch 'aliencube/main' into feat/301-UICompone…
qoweh 1ba2589
Merge remote-tracking branch 'aliencube/main' into feat/301-UICompone…
qoweh 4636787
fix: Subtract ChatHeaderUITests from commit
qoweh 931f917
refactor: for improved reliability in ChatMessageItemUITests
qoweh 46095a2
refactor: remove unused using directive for improved code clarity
qoweh 87953f9
fix: finalContent in ChatMessageItemUITests
qoweh ad026c6
refactor: remove unused directive and empty line
qoweh d4b0b73
Merge remote-tracking branch 'aliencube/main' into feat/301-UICompone…
qoweh File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
29 changes: 29 additions & 0 deletions
29
src/OpenChat.PlaygroundApp/Components/Pages/Chat/ChatMessageItem.razor.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
using System.Runtime.CompilerServices; | ||
using Microsoft.AspNetCore.Components; | ||
using Microsoft.Extensions.AI; | ||
|
||
namespace OpenChat.PlaygroundApp.Components.Pages.Chat; | ||
|
||
public partial class ChatMessageItem : ComponentBase | ||
{ | ||
private static readonly ConditionalWeakTable<ChatMessage, ChatMessageItem> SubscribersLookup = new(); | ||
|
||
[Parameter, EditorRequired] | ||
public required ChatMessage Message { get; set; } | ||
|
||
[Parameter] | ||
public bool InProgress { get; set; } | ||
|
||
protected override void OnInitialized() | ||
{ | ||
SubscribersLookup.AddOrUpdate(Message, this); | ||
} | ||
|
||
public static void NotifyChanged(ChatMessage source) | ||
{ | ||
if (SubscribersLookup.TryGetValue(source, out var subscriber)) | ||
{ | ||
subscriber.StateHasChanged(); | ||
} | ||
} | ||
} |
2 changes: 1 addition & 1 deletion
2
test/OpenChat.PlaygroundApp.Tests/Components/Pages/Chat/ChatHeaderUITests.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
157 changes: 157 additions & 0 deletions
157
test/OpenChat.PlaygroundApp.Tests/Components/Pages/Chat/ChatMessageItemUITests.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
using System; | ||
using Microsoft.Playwright; | ||
using Microsoft.Playwright.Xunit; | ||
|
||
namespace OpenChat.PlaygroundApp.Tests.Components.Pages.Chat; | ||
|
||
public class ChatMessageItemUITests : PageTest | ||
{ | ||
public override async Task InitializeAsync() | ||
{ | ||
await base.InitializeAsync(); | ||
await Page.GotoAsync(TestConstants.LocalhostUrl); | ||
await Page.WaitForLoadStateAsync(LoadState.NetworkIdle); | ||
} | ||
|
||
[Trait("Category", "IntegrationTest")] | ||
[Theory] | ||
[InlineData("Input usermessage")] | ||
public async Task Given_UserMessage_When_Sent_Then_UserMessage_Count_Should_Increment(string userMessage) | ||
{ | ||
// Arrange | ||
var textArea = Page.GetByRole(AriaRole.Textbox, new() { Name = "User Message Textarea" }); | ||
var userMessages = Page.Locator(".user-message"); | ||
var initialCount = await userMessages.CountAsync(); | ||
|
||
// Act | ||
await textArea.FillAsync(userMessage); | ||
await textArea.PressAsync("Enter"); | ||
await Page.WaitForFunctionAsync( | ||
"args => document.querySelectorAll(args.selector).length >= args.expected", | ||
new { selector = ".user-message", expected = initialCount + 1 } | ||
); | ||
|
||
// Assert | ||
var finalCount = await userMessages.CountAsync(); | ||
finalCount.ShouldBe(initialCount + 1); | ||
} | ||
|
||
[Trait("Category", "IntegrationTest")] | ||
[Theory] | ||
[InlineData("Input usermessage")] | ||
public async Task Given_UserMessage_When_Sent_Then_Rendered_Text_Should_Match_UserMessage(string userMessage) | ||
{ | ||
// Arrange | ||
var textArea = Page.GetByRole(AriaRole.Textbox, new() { Name = "User Message Textarea" }); | ||
var userMessages = Page.Locator(".user-message"); | ||
var initialCount = await userMessages.CountAsync(); | ||
|
||
// Act | ||
await textArea.FillAsync(userMessage); | ||
await textArea.PressAsync("Enter"); | ||
await Page.WaitForFunctionAsync( | ||
"args => document.querySelectorAll(args.selector).length >= args.expected", | ||
new { selector = ".user-message", expected = initialCount + 1 } | ||
); | ||
|
||
// Assert | ||
var latestUserMessage = userMessages.Nth(initialCount); | ||
var renderedText = await latestUserMessage.InnerTextAsync(); | ||
renderedText.ShouldBe(userMessage); | ||
} | ||
|
||
[Trait("Category", "IntegrationTest")] | ||
[Trait("Category", "LLMRequired")] | ||
[Theory] | ||
[InlineData("Input usermessage")] | ||
public async Task Given_AssistantResponse_When_Streamed_Then_Latest_Text_Should_NotBeEmpty(string userMessage) | ||
{ | ||
// Arrange | ||
var textArea = Page.GetByRole(AriaRole.Textbox, new() { Name = "User Message Textarea" }); | ||
var assistantTexts = Page.Locator(".assistant-message-text"); | ||
var initialTextCount = await assistantTexts.CountAsync(); | ||
|
||
// Act | ||
await textArea.FillAsync(userMessage); | ||
await textArea.PressAsync("Enter"); | ||
await Page.WaitForFunctionAsync( | ||
"args => document.querySelectorAll(args.selector).length >= args.expected", | ||
new { selector = ".assistant-message-text", expected = initialTextCount + 1 } | ||
); | ||
await Page.WaitForFunctionAsync( | ||
@"selector => | ||
{ | ||
const elements = document.querySelectorAll(selector); | ||
const latest = elements[elements.length - 1]; | ||
if (latest == null) | ||
{ | ||
return false; | ||
} | ||
|
||
return latest.innerText?.trim().length > 0; | ||
}", | ||
".assistant-message-text" | ||
); | ||
|
||
// Assert | ||
var latestAssistantText = assistantTexts.Nth(initialTextCount); | ||
var finalContent = await latestAssistantText.InnerTextAsync(); | ||
finalContent.ShouldNotBeNullOrWhiteSpace(); | ||
} | ||
|
||
[Trait("Category", "IntegrationTest")] | ||
[Trait("Category", "LLMRequired")] | ||
[Theory] | ||
[InlineData("Input usermessage")] | ||
public async Task Given_AssistantResponse_When_Message_Arrives_Then_Assistant_Icon_Should_Be_Visible(string userMessage) | ||
{ | ||
// Arrange | ||
var textArea = Page.GetByRole(AriaRole.Textbox, new() { Name = "User Message Textarea" }); | ||
var assistantHeaders = Page.Locator(".assistant-message-header"); | ||
var assistantIcons = Page.Locator(".assistant-message-icon svg"); | ||
var initialHeaderCount = await assistantHeaders.CountAsync(); | ||
|
||
// Act | ||
await textArea.FillAsync(userMessage); | ||
await textArea.PressAsync("Enter"); | ||
await Page.WaitForFunctionAsync( | ||
"args => document.querySelectorAll(args.selector).length >= args.expected", | ||
new { selector = ".assistant-message-header", expected = initialHeaderCount + 1 } | ||
); | ||
|
||
// Assert | ||
var finalIconCount = await assistantIcons.CountAsync(); | ||
var iconIndex = finalIconCount - 1; | ||
var iconVisible = await assistantIcons.Nth(iconIndex).IsVisibleAsync(); | ||
iconVisible.ShouldBeTrue(); | ||
} | ||
|
||
[Trait("Category", "IntegrationTest")] | ||
[Trait("Category", "LLMRequired")] | ||
[Theory] | ||
[InlineData("Input usermessage")] | ||
public async Task Given_Response_InProgress_When_Stream_Completes_Then_Spinner_Should_Toggle(string userMessage) | ||
{ | ||
// Arrange | ||
var textArea = Page.GetByRole(AriaRole.Textbox, new() { Name = "User Message Textarea" }); | ||
var spinner = Page.Locator(".lds-ellipsis"); | ||
|
||
// Act | ||
await textArea.FillAsync(userMessage); | ||
await textArea.PressAsync("Enter"); | ||
await spinner.WaitForAsync(new() { State = WaitForSelectorState.Visible }); | ||
var visibleWhileStreaming = await spinner.IsVisibleAsync(); | ||
await spinner.WaitForAsync(new() { State = WaitForSelectorState.Hidden }); | ||
var visibleAfterComplete = await spinner.IsVisibleAsync(); | ||
|
||
// Assert | ||
visibleWhileStreaming.ShouldBeTrue(); | ||
visibleAfterComplete.ShouldBeFalse(); | ||
} | ||
|
||
public override async Task DisposeAsync() | ||
{ | ||
await Page.CloseAsync(); | ||
await base.DisposeAsync(); | ||
} | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이 파일은 왜 수정하신 거죠?