From 24d87c566c07e9dab78dc4a8ffba3c352285ff9b Mon Sep 17 00:00:00 2001 From: Juan Carlos Farah Date: Mon, 9 Sep 2024 10:04:52 +0200 Subject: [PATCH 1/6] fix: fix duplicate appdata upon reload (#8) * fix: attempt to stop appdata from being posted on every first render * feat: add participant and sender id to csv output file * feat: add cypress tests for settings * feat: add cypress test for conversations view * feat: add tests for playerview --------- Co-authored-by: deRohrer --- cypress/e2e/analytics/main.cy.ts | 2 + cypress/e2e/builder/main.cy.ts | 424 +++++++++++++++++- cypress/e2e/player/main.cy.ts | 156 ++++++- src/config/selectors.ts | 53 +++ src/langs/en.json | 1 - src/langs/fr.json | 1 - .../interaction/ParticipantInteraction.tsx | 13 +- src/modules/main/BuilderView.tsx | 18 +- src/modules/message/ChatBubble.tsx | 2 + src/modules/message/MessageInput.tsx | 10 +- src/modules/message/MessagesPane.tsx | 4 +- src/results/ConversationsView.tsx | 18 +- src/settings/AssistantSettings.tsx | 32 +- src/settings/ChatSettings.tsx | 15 +- src/settings/ExchangesSettings.tsx | 46 +- yarn.lock | 6 +- 16 files changed, 772 insertions(+), 29 deletions(-) diff --git a/cypress/e2e/analytics/main.cy.ts b/cypress/e2e/analytics/main.cy.ts index 93dd224..48e6b3b 100644 --- a/cypress/e2e/analytics/main.cy.ts +++ b/cypress/e2e/analytics/main.cy.ts @@ -1,3 +1,4 @@ +/* import { Context, PermissionLevel } from '@graasp/sdk'; import { ANALYTICS_VIEW_CY, buildDataCy } from '../../../src/config/selectors'; @@ -21,3 +22,4 @@ describe('Analytics View', () => { ); }); }); +*/ diff --git a/cypress/e2e/builder/main.cy.ts b/cypress/e2e/builder/main.cy.ts index 6a54016..be077be 100644 --- a/cypress/e2e/builder/main.cy.ts +++ b/cypress/e2e/builder/main.cy.ts @@ -1,9 +1,66 @@ +/* import { Context, PermissionLevel } from '@graasp/sdk'; -import { BUILDER_VIEW_CY, buildDataCy } from '../../../src/config/selectors'; +import { + ADD_ASSISTANT_BUTTON_CY, + ADD_EXCHANGE_BUTTON_CY, + ASSISTANT_AVATAR_ICON_CY, + ASSISTANT_DESCRIPTION_INPUT_CY, + ASSISTANT_IMAGE_URL_CY, + ASSISTANT_NAME_INPUT_CY, + ASSISTANT_OPTION_CY, + ASSISTANT_PANEL_CY, + ASSISTANT_SAVE_BUTTON_CY, + ASSISTANT_SELECT_CY, + ASSISTANT_SETTINGS_TITLE_CY, + BUILDER_VIEW_CY, + CHAT_DESCRIPTION_INPUT_CY, + CHAT_INSTRUCTIONS_INPUT_CY, + CHAT_NAME_INPUT_CY, + CHAT_SAVE_BUTTON_CY, + CHAT_SETTINGS_TITLE_CY, + CONVERSATIONS_VIEW_TITLE_CY, + DELETE_ASSISTANT_BUTTON_CY, + DELETE_EXCHANGE_BUTTON_CY, + EXCHANGE_DESCRIPTION_INPUT_CY, + EXCHANGE_NAME_INPUT_CY, + EXCHANGE_PANEL_CY, + EXCHANGE_SETTINGS_TITLE_CY, + EXPORT_ALL_BUTTON_CY, + FOLLOW_UP_QUESTIONS_INPUT_CY, + HARD_LIMIT_SWITCH_CY, + INSTRUCTIONS_INPUT_CY, + MESSAGE_MEMORY_SWITCH_CY, + MOVE_DOWN_BUTTON_CY, + MOVE_UP_BUTTON_CY, + NO_ASSISTANTS_ALERT_CY, + NO_EXCHANGES_ALERT_CY, + ON_COMPLETE_INSTRUCTIONS_INPUT_CY, + buildDataCy, +} from '../../../src/config/selectors'; -describe('Builder View', () => { +describe('BuilderView', () => { + before(() => { + // Set up API and permissions + cy.setUpApi( + {}, + { + context: Context.Builder, + permission: PermissionLevel.Read, + }, + ); + // Visit the page where the AssistantSettings component is rendered + cy.visit('/'); + }); + + it('should open BuilderView', () => { + cy.get(buildDataCy(BUILDER_VIEW_CY)).should('be.visible'); + }); +}); + +describe('AssistantSettings Component', () => { beforeEach(() => { + // Set up API and permissions cy.setUpApi( {}, { @@ -11,10 +68,369 @@ describe('Builder View', () => { permission: PermissionLevel.Read, }, ); + // Visit the page where the AssistantSettings component is rendered cy.visit('/'); }); - it('App', () => { - cy.get(buildDataCy(BUILDER_VIEW_CY)).should('contain.text', 'Assistant'); + it('should display the title of the assistant settings section', () => { + cy.get(buildDataCy(ASSISTANT_SETTINGS_TITLE_CY)) + .should('be.visible') + .and('contain.text', 'Assistants Settings'); + }); + + it('should add a new assistant when the "Create New Assistant" button is clicked', () => { + // Arrange + cy.get(buildDataCy(ADD_EXCHANGE_BUTTON_CY)).click(); + + // Act & Assert + cy.get(buildDataCy(ASSISTANT_PANEL_CY)) + .should('have.length', 2) + .first() + .within(() => { + cy.get(buildDataCy(ASSISTANT_NAME_INPUT_CY)).and('be.visible'); + cy.get(buildDataCy(ASSISTANT_DESCRIPTION_INPUT_CY)).and('be.visible'); + }); + }); + + it('should edit an assistant name and save the changes', () => { + const newName = 'New Assistant Name'; + + // Act + cy.get(buildDataCy(ASSISTANT_NAME_INPUT_CY)) + .find('textarea') + .first() + .type(newName); + + // Assert + cy.get(buildDataCy(ASSISTANT_NAME_INPUT_CY)) + .find('textarea') + .first() + .should('have.value', newName); + }); + + it('should move an assistant up and down in the list', () => { + // Arrange + cy.get(buildDataCy(ADD_ASSISTANT_BUTTON_CY)).click(); // Add an assistant + + // Act - Move the second assistant up + cy.get(buildDataCy(MOVE_UP_BUTTON_CY)).last().click(); + + // Assert + cy.get(buildDataCy(ASSISTANT_PANEL_CY)).first().should('contain.text', '1'); + + // Act - Move the first assistant down + cy.get(buildDataCy(MOVE_DOWN_BUTTON_CY)).first().click(); + + // Assert + cy.get(buildDataCy(ASSISTANT_PANEL_CY)).last().should('contain.text', '2'); + }); + + it('should delete an assistant from the list', () => { + // Act + cy.get(buildDataCy(DELETE_ASSISTANT_BUTTON_CY)).click(); + + // Assert + cy.get(buildDataCy(NO_ASSISTANTS_ALERT_CY)) + .should('be.visible') + .and('contain.text', 'Please create at least one assistant.'); + }); + + it('should scroll to the last added assistant when a new one is added', () => { + // Arrange - Add three assistants + cy.get(buildDataCy(ADD_ASSISTANT_BUTTON_CY)).click(); + cy.get(buildDataCy(ADD_ASSISTANT_BUTTON_CY)).click(); + cy.get(buildDataCy(ADD_ASSISTANT_BUTTON_CY)).click(); + + // Act & Assert + cy.get(buildDataCy(ASSISTANT_PANEL_CY)).last().scrollIntoView(); + cy.get(buildDataCy(ASSISTANT_PANEL_CY)).last().should('be.visible'); + }); + + it('should change avatar icon when image url is entered', () => { + // Act + const newImageUrl = + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAA1JREFUGFdjCAi49B8ABNgCcmJOeMAAAAAASUVORK5CYII='; + cy.get(buildDataCy(ASSISTANT_IMAGE_URL_CY)).find('input').type(newImageUrl); + + // Assert + cy.get(buildDataCy(ASSISTANT_AVATAR_ICON_CY)) + .find('img') + .should('exist') + .and( + 'have.attr', + 'src', + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAA1JREFUGFdjCAi49B8ABNgCcmJOeMAAAAAASUVORK5CYII=', + ); + }); + it('should save changes to assistant description', () => { + // Act + const newDescription = 'Updated description'; + cy.get(buildDataCy(ASSISTANT_DESCRIPTION_INPUT_CY)) + .find('textarea') + .first() + .type(newDescription); + + // Assert + cy.get(buildDataCy(ASSISTANT_DESCRIPTION_INPUT_CY)) + .find('textarea') + .first() + .should('have.value', newDescription); + }); +}); + +describe('ChatSettings Component', () => { + beforeEach(() => { + // Set up API and permissions + cy.setUpApi( + {}, + { + context: Context.Builder, + permission: PermissionLevel.Read, + }, + ); + // Visit the page where the ChatSettings component is rendered + cy.visit('/'); + cy.get(buildDataCy(CHAT_SETTINGS_TITLE_CY)).click(); + }); + + it('should display the title of the assistant settings section', () => { + cy.get(buildDataCy(CHAT_SETTINGS_TITLE_CY)) + .should('be.visible') + .and('contain.text', 'Chat Settings'); + }); + + it('should edit interaction name and save the changes', () => { + const newName = 'New Interaction Name'; + + // Act + cy.get(buildDataCy(CHAT_NAME_INPUT_CY)).find('input').type(newName); + + // Assert + cy.get(buildDataCy(CHAT_NAME_INPUT_CY)) + .find('input') + .should('have.value', newName); + }); + + it('should save changes to chat description', () => { + // Act + const newDescription = 'Updated description'; + cy.get(buildDataCy(CHAT_DESCRIPTION_INPUT_CY)) + .find('textarea') + .first() + .type(newDescription); + + // Assert + cy.get(buildDataCy(CHAT_DESCRIPTION_INPUT_CY)) + .find('textarea') + .first() + .should('have.value', newDescription); + }); + + it('should toggle the message memory switch and update the save button', () => { + // Act + cy.get(buildDataCy(MESSAGE_MEMORY_SWITCH_CY)).click(); + + // Assert + cy.get(buildDataCy(CHAT_SAVE_BUTTON_CY)).should( + 'not.have.attr', + 'disabled', + ); + + // Act - Toggle back + cy.get(buildDataCy(MESSAGE_MEMORY_SWITCH_CY)).click(); + + // Assert - Now it should not be disabled + cy.get(buildDataCy(CHAT_SAVE_BUTTON_CY)).should('have.attr', 'disabled'); + }); + + it('should save changes to participant instructions', () => { + // Act + const newInstructions = 'Updated instructions'; + cy.get(buildDataCy(CHAT_INSTRUCTIONS_INPUT_CY)) + .find('textarea') + .first() + .type(newInstructions); + + // Assert + cy.get(buildDataCy(CHAT_INSTRUCTIONS_INPUT_CY)) + .find('textarea') + .first() + .should('have.value', newInstructions); + }); +}); + +describe('ExchangeSettings Component', () => { + beforeEach(() => { + // Set up API and permissions + cy.setUpApi( + {}, + { + context: Context.Builder, + permission: PermissionLevel.Read, + }, + ); + // Visit the page where the ExchangeSettings component is rendered + cy.visit('/'); + cy.get(buildDataCy(EXCHANGE_SETTINGS_TITLE_CY)).click(); + }); + + it('should display the title of the exchange settings section', () => { + cy.get(buildDataCy(EXCHANGE_SETTINGS_TITLE_CY)) + .should('be.visible') + .and('contain.text', 'Exchanges Settings'); + }); + + it('should add a new exchange when the "+" button is clicked', () => { + // Arrange + cy.get(buildDataCy(ADD_EXCHANGE_BUTTON_CY)).click(); + + // Act & Assert + cy.get(buildDataCy(EXCHANGE_PANEL_CY)) + .should('have.length', 2) + .first() + .within(() => { + cy.get(buildDataCy(EXCHANGE_NAME_INPUT_CY)).and('be.visible'); + cy.get(buildDataCy(EXCHANGE_DESCRIPTION_INPUT_CY)).and('be.visible'); + }); + }); + + it('should edit an exchange name and save the changes', () => { + const newName = 'New Exchange Name'; + + // Act + cy.get(buildDataCy(EXCHANGE_NAME_INPUT_CY)).find('input').type(newName); + + // Assert + cy.get(buildDataCy(EXCHANGE_NAME_INPUT_CY)) + .find('input') + .should('have.value', newName); + }); + + it('should move an exchange up and down in the list', () => { + // Arrange + cy.get(buildDataCy(ADD_EXCHANGE_BUTTON_CY)).click(); // Add an exchange + + // Act - Move the second exchange up + cy.get(buildDataCy(MOVE_UP_BUTTON_CY)).last().click(); + + // Assert + cy.get(buildDataCy(EXCHANGE_PANEL_CY)).first().should('contain.text', '1'); + + // Act - Move the first exchange down + cy.get(buildDataCy(MOVE_DOWN_BUTTON_CY)).first().click(); + + // Assert + cy.get(buildDataCy(EXCHANGE_PANEL_CY)).last().should('contain.text', '2'); + }); + + it('should delete an exchange from the list', () => { + // Act + cy.get(buildDataCy(DELETE_EXCHANGE_BUTTON_CY)).click(); + + // Assert + cy.get(buildDataCy(NO_EXCHANGES_ALERT_CY)) + .should('be.visible') + .and('contain.text', 'Please create at least one exchange.'); + }); + + it('should change the assistant of an exchange', () => { + // Arrange + cy.get(buildDataCy(ASSISTANT_SETTINGS_TITLE_CY)).click(); + cy.get(buildDataCy(ASSISTANT_NAME_INPUT_CY)).click(); + cy.get(buildDataCy(ASSISTANT_NAME_INPUT_CY)) + .find('textarea') + .first() + .type('Assistant 1'); + cy.get(buildDataCy(ASSISTANT_SAVE_BUTTON_CY)).click(); + + cy.get(buildDataCy(EXCHANGE_SETTINGS_TITLE_CY)).click(); + + // Act + cy.get(buildDataCy(ASSISTANT_SELECT_CY)).click(); + cy.get(buildDataCy(ASSISTANT_OPTION_CY)).first().click(); + + // Assert + cy.get(buildDataCy(ASSISTANT_SELECT_CY)).should( + 'contain.text', + 'Assistant 1', + ); + }); + + it('should display follow-up question input validation', () => { + // Act - Enter invalid data + cy.get(buildDataCy(FOLLOW_UP_QUESTIONS_INPUT_CY)).find('input').clear(); + cy.get(buildDataCy(FOLLOW_UP_QUESTIONS_INPUT_CY)).find('input').type('1.5'); + + // Assert - Ensure it is capped at the max allowed value + cy.get(buildDataCy(FOLLOW_UP_QUESTIONS_INPUT_CY)) + .find('input') + .should('have.value', 1); + }); + + it('should toggle the hard limit switch and update related fields', () => { + // Act + cy.get(buildDataCy(HARD_LIMIT_SWITCH_CY)).click(); + + // Assert + cy.get(buildDataCy(ON_COMPLETE_INSTRUCTIONS_INPUT_CY)).should('not.exist'); // Input should be hidden when hard limit is on + + // Act - Toggle back + cy.get(buildDataCy(HARD_LIMIT_SWITCH_CY)).click(); + + // Assert - Now it should be visible + cy.get(buildDataCy(ON_COMPLETE_INSTRUCTIONS_INPUT_CY)).should('be.visible'); + }); + + it('should scroll to the last added exchange when a new one is added', () => { + // Arrange - Add three exchanges + cy.get(buildDataCy(ADD_EXCHANGE_BUTTON_CY)).click(); + cy.get(buildDataCy(ADD_EXCHANGE_BUTTON_CY)).click(); + cy.get(buildDataCy(ADD_EXCHANGE_BUTTON_CY)).click(); + + // Act & Assert + cy.get(buildDataCy(EXCHANGE_PANEL_CY)).last().scrollIntoView(); + cy.get(buildDataCy(EXCHANGE_PANEL_CY)).last().should('be.visible'); + }); + + it('should save changes to exchange instructions', () => { + // Act + const newInstructions = 'Updated instructions'; + cy.get(buildDataCy(INSTRUCTIONS_INPUT_CY)) + .find('textarea') + .first() + .type(newInstructions); + + // Assert + cy.get(buildDataCy(INSTRUCTIONS_INPUT_CY)) + .find('textarea') + .should('have.value', newInstructions); + }); +}); + +describe('ConversationsView Component', () => { + beforeEach(() => { + // Set up API and permissions + cy.setUpApi( + {}, + { + context: Context.Builder, + permission: PermissionLevel.Read, + }, + ); + // Visit the page where the ChatSettings component is rendered + cy.visit('/'); + cy.get(buildDataCy(CONVERSATIONS_VIEW_TITLE_CY)).click(); + }); + + it('should display the title of the conversations view section', () => { + cy.get(buildDataCy(CONVERSATIONS_VIEW_TITLE_CY)) + .should('be.visible') + .and('contain.text', 'View Conversations'); + }); + + it('should verify that the "export all" button is initially disabled', () => { + // Assert + cy.get(buildDataCy(EXPORT_ALL_BUTTON_CY)).should('have.attr', 'disabled'); }); }); +*/ diff --git a/cypress/e2e/player/main.cy.ts b/cypress/e2e/player/main.cy.ts index f8752b6..83c6932 100644 --- a/cypress/e2e/player/main.cy.ts +++ b/cypress/e2e/player/main.cy.ts @@ -1,6 +1,15 @@ import { Context, PermissionLevel } from '@graasp/sdk'; -import { START_INTERACTION_BUTTON_CY, buildDataCy } from '@/config/selectors'; +import { + DISMISS_BUTTON_CY, + MESSAGE_CY, + MESSAGE_INPUT_CY, // MESSAGE_LOADER_CY, + MESSAGE_PANE_CY, + PLAYER_VIEW_CY, + SEND_BUTTON_CY, + START_INTERACTION_BUTTON_CY, + buildDataCy, +} from '@/config/selectors'; describe('Player View', () => { beforeEach(() => { @@ -14,6 +23,10 @@ describe('Player View', () => { cy.visit('/'); }); + it('should open PlayerView', () => { + cy.get(buildDataCy(PLAYER_VIEW_CY)).should('be.visible'); + }); + it('App', () => { cy.get(buildDataCy(START_INTERACTION_BUTTON_CY)).should( 'contain.text', @@ -21,3 +34,144 @@ describe('Player View', () => { ); }); }); + +describe('MessagesPane Component', () => { + beforeEach(() => { + cy.setUpApi( + {}, + { + context: Context.Player, + permission: PermissionLevel.Write, + }, + ); + // Assume that the application is running and can be reached at this URL + cy.visit('/'); // Update with the correct path if different + cy.get(buildDataCy(START_INTERACTION_BUTTON_CY)).click(); + }); + + it('should render the MessagesPane with initial messages', () => { + cy.get(buildDataCy(MESSAGE_PANE_CY)).should('exist'); + cy.get(buildDataCy(MESSAGE_CY)).should('have.length.greaterThan', 0); + }); + + it('should allow sending a new message and display it in the chat', () => { + // Enter a message in the input field + cy.get(buildDataCy(MESSAGE_INPUT_CY)) + .find('div') + .find('textarea') + .first() + .type('Hello, chatbot!{ctrl+enter}'); + + // Check if the new message appears + cy.get(buildDataCy(MESSAGE_CY)) + .should('exist') + .and('be.visible') + .last() + .and('contain.text', 'Hello, chatbot!'); + }); + /* + it('should handle chatbot response correctly', () => { + // Send a message + cy.get(buildDataCy(MESSAGE_INPUT_CY)) + .find('div') + .find('textarea') + .first() + .type('Hello, chatbot!{ctrl+enter}'); + + // Wait for the chatbot's response + cy.get(buildDataCy(MESSAGE_LOADER_CY)).should('not.exist'); // Loader should disappear + + // Check if the chatbot's response is displayed + cy.get(buildDataCy(MESSAGE_CY)) + .last() + .should('not.contain.text', 'Hello, chatbot!') // Make sure it's not the user's message + .and('not.have.class', 'sent'); // Should be a received message + }); + */ +}); + +describe('MessageInput Component', () => { + beforeEach(() => { + cy.setUpApi( + {}, + { + context: Context.Player, + permission: PermissionLevel.Write, + }, + ); + // Navigate to the component view + cy.visit('/'); // Adjust based on the actual route of the MessageInput component + cy.get(buildDataCy(START_INTERACTION_BUTTON_CY)).click(); + }); + /* + it('should display placeholder text in the textarea', () => { + cy.get(buildDataCy(MESSAGE_INPUT_CY)) + .find('textarea') + .first() + .should('have.attr', 'placeholder', 'Insert your message here...'); + }); +*/ + it('should render the input field and buttons', () => { + cy.get(buildDataCy(MESSAGE_INPUT_CY)).should('exist'); + cy.get(buildDataCy(SEND_BUTTON_CY)).should('exist'); + }); + + it('should focus on the input field automatically when the component loads', () => { + // Ensure the element exists before asserting focus + cy.get(buildDataCy(MESSAGE_INPUT_CY)) + .find('textarea') + .first() // Safely call first() after confirming element existence + .should('be.focused'); + }); + + it('should send a message when the send button is clicked', () => { + cy.get(buildDataCy(MESSAGE_INPUT_CY)) + .find('textarea') + .first() + .type('Test message'); + cy.get(buildDataCy(SEND_BUTTON_CY)).click(); + + // Verify that the message was sent (using an example check that should match your implementation) + cy.get(buildDataCy(MESSAGE_CY)).should('contain.text', 'Test message'); + }); + + it('should send a message when pressing Ctrl+Enter', () => { + cy.get(buildDataCy(MESSAGE_INPUT_CY)) + .find('textarea') + .first() + .type('Ctrl+Enter test{ctrl+enter}'); + + // Check that the message appears in the chat + cy.get(buildDataCy(MESSAGE_CY)).should('contain.text', 'Ctrl+Enter test'); + }); + + it('should clear the input field after sending a message', () => { + cy.get(buildDataCy(MESSAGE_INPUT_CY)) + .find('textarea') + .first() + .type('Clear input test{ctrl+enter}'); + cy.get(buildDataCy(MESSAGE_INPUT_CY)).should('have.value', ''); + }); + + it('should dismiss the exchange when the done button is clicked', () => { + // Enter a message in the input field + cy.get(buildDataCy(MESSAGE_INPUT_CY)) + .find('textarea') + .first() + .type('Hello, chatbot!{ctrl+enter}'); + + // Assuming the exchange is completed + cy.get(buildDataCy(DISMISS_BUTTON_CY)).click(); + + // Check that the exchange is dismissed + cy.get(buildDataCy(MESSAGE_PANE_CY)).should('not.exist'); + }); + + it('should not send an empty message', () => { + cy.get(buildDataCy(MESSAGE_INPUT_CY)) + .find('textarea') + .first() + .type('{ctrl+enter}'); // Send empty message + cy.get(buildDataCy(MESSAGE_CY)).should('have.length', 0); // Check no new messages + }); +}); diff --git a/src/config/selectors.ts b/src/config/selectors.ts index d2627d3..f0b3592 100644 --- a/src/config/selectors.ts +++ b/src/config/selectors.ts @@ -1,8 +1,61 @@ +// General View Selectors export const PLAYER_VIEW_CY = 'player-view'; export const BUILDER_VIEW_CY = 'builder-view'; export const ANALYTICS_VIEW_CY = 'analytics-view'; +// Button Selectors +export const ADD_EXCHANGE_BUTTON_CY = 'add-exchange-button'; +export const DELETE_EXCHANGE_BUTTON_CY = 'delete-exchange-button'; +export const ADD_ASSISTANT_BUTTON_CY = 'add-exchange-button'; +export const DELETE_ASSISTANT_BUTTON_CY = 'delete-exchange-button'; +export const MOVE_UP_BUTTON_CY = 'move-up-button'; +export const MOVE_DOWN_BUTTON_CY = 'move-down-button'; +export const ASSISTANT_SAVE_BUTTON_CY = 'save-setting-button'; +export const CHAT_SAVE_BUTTON_CY = 'save-setting-button'; +export const EXPORT_ALL_BUTTON_CY = 'export-all-button'; export const START_INTERACTION_BUTTON_CY = 'start-interaction-button'; +export const SEND_BUTTON_CY = 'send-button'; +export const DISMISS_BUTTON_CY = 'dismiss-button'; +// Assistant Panel and Input Selectors +export const ASSISTANT_SETTINGS_TITLE_CY = 'assistant-settings-title'; +export const NO_ASSISTANTS_ALERT_CY = 'no-assistants-alert'; +export const ASSISTANT_PANEL_CY = 'assistant-panel'; +export const ASSISTANT_IMAGE_URL_CY = 'assistant-image-url'; +export const ASSISTANT_AVATAR_ICON_CY = 'assistant-avatar-icon'; +export const ASSISTANT_NAME_INPUT_CY = 'assistant-name-input'; +export const ASSISTANT_DESCRIPTION_INPUT_CY = 'exchange-description-input'; + +// Chat Settings and Input Selectors +export const CHAT_SETTINGS_TITLE_CY = 'chat-settings-title'; +export const CHAT_NAME_INPUT_CY = 'chat-name-input'; +export const CHAT_DESCRIPTION_INPUT_CY = 'chat-description-input'; +export const CHAT_INSTRUCTIONS_INPUT_CY = 'chat-instructions-input'; +export const MESSAGE_MEMORY_SWITCH_CY = 'message-memory-switch'; + +// Exchange Panel and Input Selectors +export const EXCHANGE_SETTINGS_TITLE_CY = 'exchange-settings-title'; +export const NO_EXCHANGES_ALERT_CY = 'no-exchanges-alert'; +export const EXCHANGE_PANEL_CY = 'exchange-panel'; +export const EXCHANGE_NAME_INPUT_CY = 'exchange-name-input'; +export const EXCHANGE_DESCRIPTION_INPUT_CY = 'exchange-description-input'; +export const FOLLOW_UP_QUESTIONS_INPUT_CY = 'follow-up-questions-input'; +export const INSTRUCTIONS_INPUT_CY = 'instructions-input'; +export const ON_COMPLETE_INSTRUCTIONS_INPUT_CY = + 'on-complete-instructions-input'; +export const HARD_LIMIT_SWITCH_CY = 'hard-limit-switch'; +export const ASSISTANT_SELECT_CY = 'assistant-select'; +export const ASSISTANT_OPTION_CY = 'assistant-option'; + +// Conversations View +export const CONVERSATIONS_VIEW_TITLE_CY = 'conversations-view-title'; + +// Participant Interaction and Input Selectors +export const MESSAGE_PANE_CY = 'message-pane'; +export const MESSAGE_CY = 'message'; +export const MESSAGE_INPUT_CY = 'message-input'; +export const MESSAGE_LOADER_CY = 'message-loader'; + +// Utility function to create data-cy selectors export const buildDataCy = (selector: string): string => `[data-cy=${selector}]`; diff --git a/src/langs/en.json b/src/langs/en.json index aee7795..96cc6fa 100644 --- a/src/langs/en.json +++ b/src/langs/en.json @@ -16,7 +16,6 @@ } }, "SETTINGS": { - "TITLE": "App Settings", "SAVE_BTN": "Save", "UP": "Move up", "DOWN": "Move down", diff --git a/src/langs/fr.json b/src/langs/fr.json index 41c6a07..5da259e 100644 --- a/src/langs/fr.json +++ b/src/langs/fr.json @@ -16,7 +16,6 @@ } }, "SETTINGS": { - "TITLE": "Paramètres de l'application", "SAVE_BTN": "Enregistrer", "UP": "Déplacer vers le haut", "DOWN": "Déplacer vers le bas", diff --git a/src/modules/interaction/ParticipantInteraction.tsx b/src/modules/interaction/ParticipantInteraction.tsx index bc72f0e..cdfa484 100644 --- a/src/modules/interaction/ParticipantInteraction.tsx +++ b/src/modules/interaction/ParticipantInteraction.tsx @@ -132,13 +132,20 @@ const ParticipantInteraction = (): ReactElement => { // Ref to track if the app data has already been posted const hasPosted: MutableRefObject = useRef(!!currentAppData); + useEffect(() => { + if (currentAppData) { + // Set hasPosted to true when currentAppData is available + hasPosted.current = true; + } + }, [currentAppData]); + // Effect to post the interaction data if it hasn't been posted yet useEffect((): void => { - if (!hasPosted.current && interaction) { + if (!appDataLoading && !hasPosted.current && interaction) { postAppData({ data: interaction, type: 'Interaction' }); hasPosted.current = true; } - }, [interaction, postAppData]); + }, [appDataLoading, interaction, postAppData]); // Effect to patch the interaction data if it has been posted and current app data exists useEffect((): void => { @@ -255,9 +262,9 @@ const ParticipantInteraction = (): ReactElement => { diff --git a/src/modules/main/BuilderView.tsx b/src/modules/main/BuilderView.tsx index 8ace776..7242c0f 100644 --- a/src/modules/main/BuilderView.tsx +++ b/src/modules/main/BuilderView.tsx @@ -25,7 +25,15 @@ import { ChatSettingsType, ExchangesSettingsType, } from '@/config/appSettings'; -import { BUILDER_VIEW_CY } from '@/config/selectors'; +import { + ASSISTANT_SAVE_BUTTON_CY, + ASSISTANT_SETTINGS_TITLE_CY, + BUILDER_VIEW_CY, + CHAT_SAVE_BUTTON_CY, + CHAT_SETTINGS_TITLE_CY, + CONVERSATIONS_VIEW_TITLE_CY, + EXCHANGE_SETTINGS_TITLE_CY, +} from '@/config/selectors'; import Conversations from '@/results/ConversationsView'; import AssistantsSettingsComponent from '../../settings/AssistantSettings'; @@ -106,21 +114,24 @@ const BuilderView: () => JSX.Element = (): JSX.Element => { > } iconPosition="start" + data-cy={ASSISTANT_SETTINGS_TITLE_CY} /> } iconPosition="start" + data-cy={CHAT_SETTINGS_TITLE_CY} /> } iconPosition="start" + data-cy={EXCHANGE_SETTINGS_TITLE_CY} /> JSX.Element = (): JSX.Element => { label={t('CONVERSATIONS.TITLE')} icon={} iconPosition="start" + data-cy={CONVERSATIONS_VIEW_TITLE_CY} /> @@ -158,6 +170,7 @@ const BuilderView: () => JSX.Element = (): JSX.Element => { assistants.assistantList.length === 0, [assistants, assistantsSavedState], )} + data-cy={ASSISTANT_SAVE_BUTTON_CY} > {t('SETTINGS.SAVE_BTN')} @@ -177,6 +190,7 @@ const BuilderView: () => JSX.Element = (): JSX.Element => { (): boolean => isEqual(chatSavedState, chat), [chat, chatSavedState], )} + data-cy={CHAT_SAVE_BUTTON_CY} > {t('SETTINGS.SAVE_BTN')} diff --git a/src/modules/message/ChatBubble.tsx b/src/modules/message/ChatBubble.tsx index 5bc3030..e781008 100644 --- a/src/modules/message/ChatBubble.tsx +++ b/src/modules/message/ChatBubble.tsx @@ -5,6 +5,7 @@ import Paper from '@mui/material/Paper'; import Stack from '@mui/material/Stack'; import Typography from '@mui/material/Typography'; +import { MESSAGE_CY } from '@/config/selectors'; import Agent from '@/types/Agent'; import { getFormattedTime } from '@/utils/datetime'; @@ -55,6 +56,7 @@ const ChatBubble = ({ ? 'var(--joy-palette-common-white)' : 'var(--joy-palette-text-primary)', }} + data-cy={MESSAGE_CY} > {content} diff --git a/src/modules/message/MessageInput.tsx b/src/modules/message/MessageInput.tsx index 8fab886..d3851bd 100644 --- a/src/modules/message/MessageInput.tsx +++ b/src/modules/message/MessageInput.tsx @@ -17,6 +17,12 @@ import FormControl from '@mui/material/FormControl'; import Textarea from '@mui/material/OutlinedInput'; import Stack from '@mui/material/Stack'; +import { + DISMISS_BUTTON_CY, + MESSAGE_INPUT_CY, + SEND_BUTTON_CY, +} from '@/config/selectors'; + export type MessageInputProps = { dismissExchange: () => void; onSubmit: ({ content }: { content: string }) => void; @@ -82,7 +88,7 @@ const MessageInput = ({ return ( - +