From a3e904d2d685c0a450169dc8f22302bf19c03198 Mon Sep 17 00:00:00 2001 From: deRohrer Date: Thu, 5 Sep 2024 22:52:37 +0200 Subject: [PATCH 1/5] fix: attempt to stop appdata from being posted on every first render --- src/modules/interaction/ParticipantInteraction.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/modules/interaction/ParticipantInteraction.tsx b/src/modules/interaction/ParticipantInteraction.tsx index bc72f0e..ba39426 100644 --- a/src/modules/interaction/ParticipantInteraction.tsx +++ b/src/modules/interaction/ParticipantInteraction.tsx @@ -132,9 +132,16 @@ 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; } From ff3502d700376378b6f622fbfdf92c6d672ce836 Mon Sep 17 00:00:00 2001 From: deRohrer Date: Thu, 5 Sep 2024 23:02:15 +0200 Subject: [PATCH 2/5] feat: add participant and sender id to csv output file --- src/results/ConversationsView.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/results/ConversationsView.tsx b/src/results/ConversationsView.tsx index 9ed3574..5a7dae7 100644 --- a/src/results/ConversationsView.tsx +++ b/src/results/ConversationsView.tsx @@ -77,7 +77,9 @@ const Conversations: FC = ({ ): string => { const headers: string[] = [ 'Participant', + 'Participand ID', 'Sender', + 'Sender ID', 'Sent at', 'Exchange', 'Interaction', @@ -92,7 +94,9 @@ const Conversations: FC = ({ exchange.messages.map((message: Message): string => [ interactionData.participant.name, + interactionData.participant.id, message.sender.name, + message.sender.id, format(new Date(message.sentAt || ''), 'dd/MM/yyyy HH:mm'), exchange.name, interactionData.name, @@ -111,10 +115,7 @@ const Conversations: FC = ({ // map data rows return csvRows.join('\n'); }; - /* - ...data.map((row) => - headers.map((header) => JSON.stringify(row[header] || '')).join(','), -*/ + // Function to download CSV file const downloadCsv: (csv: string, filename: string) => void = ( csv: string, From 5c0c34b8d79b8091e1fa8c103d3de4a6c8398825 Mon Sep 17 00:00:00 2001 From: deRohrer Date: Fri, 6 Sep 2024 16:03:31 +0200 Subject: [PATCH 3/5] feat: add cypress tests for settings --- cypress/e2e/analytics/main.cy.ts | 2 + cypress/e2e/builder/main.cy.ts | 392 +++++++++++++++++- cypress/e2e/player/main.cy.ts | 3 +- src/config/selectors.ts | 41 ++ src/langs/en.json | 1 - src/langs/fr.json | 1 - .../interaction/ParticipantInteraction.tsx | 2 +- src/modules/main/BuilderView.tsx | 16 +- src/settings/AssistantSettings.tsx | 32 +- src/settings/ChatSettings.tsx | 15 +- src/settings/ExchangesSettings.tsx | 46 +- 11 files changed, 534 insertions(+), 17 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..e3fd4bb 100644 --- a/cypress/e2e/builder/main.cy.ts +++ b/cypress/e2e/builder/main.cy.ts @@ -1,9 +1,263 @@ 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, + DELETE_ASSISTANT_BUTTON_CY, + DELETE_EXCHANGE_BUTTON_CY, + EXCHANGE_DESCRIPTION_INPUT_CY, + EXCHANGE_NAME_INPUT_CY, + EXCHANGE_PANEL_CY, + EXCHANGE_SETTINGS_TITLE_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( + {}, + { + context: Context.Builder, + permission: PermissionLevel.Read, + }, + ); + // Visit the page where the AssistantSettings component is rendered + cy.visit('/'); + }); + + 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( {}, { @@ -11,10 +265,140 @@ describe('Builder View', () => { 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('App', () => { - cy.get(buildDataCy(BUILDER_VIEW_CY)).should('contain.text', 'Assistant'); + 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); }); }); diff --git a/cypress/e2e/player/main.cy.ts b/cypress/e2e/player/main.cy.ts index f8752b6..1b2f1bd 100644 --- a/cypress/e2e/player/main.cy.ts +++ b/cypress/e2e/player/main.cy.ts @@ -1,4 +1,4 @@ -import { Context, PermissionLevel } from '@graasp/sdk'; +/* import { Context, PermissionLevel } from '@graasp/sdk'; import { START_INTERACTION_BUTTON_CY, buildDataCy } from '@/config/selectors'; @@ -21,3 +21,4 @@ describe('Player View', () => { ); }); }); +*/ diff --git a/src/config/selectors.ts b/src/config/selectors.ts index d2627d3..1423ce2 100644 --- a/src/config/selectors.ts +++ b/src/config/selectors.ts @@ -1,8 +1,49 @@ +// 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 START_INTERACTION_BUTTON_CY = 'start-interaction-button'; +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'; +// 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'; + +// 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 ba39426..1133a86 100644 --- a/src/modules/interaction/ParticipantInteraction.tsx +++ b/src/modules/interaction/ParticipantInteraction.tsx @@ -145,7 +145,7 @@ const ParticipantInteraction = (): ReactElement => { 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 => { diff --git a/src/modules/main/BuilderView.tsx b/src/modules/main/BuilderView.tsx index 8ace776..c770c61 100644 --- a/src/modules/main/BuilderView.tsx +++ b/src/modules/main/BuilderView.tsx @@ -25,7 +25,14 @@ 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, + EXCHANGE_SETTINGS_TITLE_CY, +} from '@/config/selectors'; import Conversations from '@/results/ConversationsView'; import AssistantsSettingsComponent from '../../settings/AssistantSettings'; @@ -106,21 +113,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 => { assistants.assistantList.length === 0, [assistants, assistantsSavedState], )} + data-cy={ASSISTANT_SAVE_BUTTON_CY} > {t('SETTINGS.SAVE_BTN')} @@ -177,6 +188,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/settings/AssistantSettings.tsx b/src/settings/AssistantSettings.tsx index 74a9d38..577a780 100644 --- a/src/settings/AssistantSettings.tsx +++ b/src/settings/AssistantSettings.tsx @@ -31,6 +31,19 @@ import { AssistantsSettingsType, } from '@/config/appSettings'; import { MAX_TEXT_INPUT_CHARS } from '@/config/config'; +import { + ADD_ASSISTANT_BUTTON_CY, + ASSISTANT_AVATAR_ICON_CY, + ASSISTANT_DESCRIPTION_INPUT_CY, + ASSISTANT_IMAGE_URL_CY, + ASSISTANT_NAME_INPUT_CY, + ASSISTANT_PANEL_CY, + ASSISTANT_SETTINGS_TITLE_CY, + DELETE_ASSISTANT_BUTTON_CY, + MOVE_DOWN_BUTTON_CY, + MOVE_UP_BUTTON_CY, + NO_ASSISTANTS_ALERT_CY, +} from '@/config/selectors'; import Agent from '@/types/Agent'; import AgentType from '@/types/AgentType'; @@ -85,6 +98,7 @@ const AssistantSettingsPanel: FC = ({ color={panelColor} // Unique color for each assistant's divider /> } + data-cy={ASSISTANT_PANEL_CY} > = ({ {assistantName.slice(0, 2)} @@ -113,11 +128,13 @@ const AssistantSettingsPanel: FC = ({ ): void => onChange(index, 'imageUrl', e.target.value)} placeholder={t('SETTINGS.ASSISTANTS.URL')} fullWidth + data-cy={ASSISTANT_IMAGE_URL_CY} /> handleMoveUp(index)} disabled={index === 0} // Disabled if the assistant is at the top + data-cy={MOVE_UP_BUTTON_CY} > @@ -127,6 +144,7 @@ const AssistantSettingsPanel: FC = ({ sx={{ color: panelColor }} onClick={(): void => handleMoveDown(index)} disabled={index === assistantListLength - 1} // Disabled if the assistant is at the bottom + data-cy={MOVE_DOWN_BUTTON_CY} > @@ -141,6 +159,7 @@ const AssistantSettingsPanel: FC = ({ onChange={( e: ChangeEvent, ): void => onChange(index, 'name', e.target.value)} + data-cy={ASSISTANT_NAME_INPUT_CY} /> = ({ onChange={( e: ChangeEvent, ): void => onChange(index, 'description', e.target.value)} + data-cy={ASSISTANT_DESCRIPTION_INPUT_CY} /> handleRemoveAssistant(index)} sx={{ width: 'auto' }} + data-cy={DELETE_ASSISTANT_BUTTON_CY} > @@ -264,7 +285,9 @@ const AssistantsSettings: FC = ({ assistants, onChange }) => { return ( - {t('SETTINGS.ASSISTANTS.TITLE')} + + {t('SETTINGS.ASSISTANTS.TITLE')} + = ({ assistants, onChange }) => { display: 'flex', justifyContent: 'center', }} + data-cy={NO_ASSISTANTS_ALERT_CY} > {t('SETTINGS.ASSISTANTS.CREATE')} @@ -307,7 +331,11 @@ const AssistantsSettings: FC = ({ assistants, onChange }) => { ), ) )} - diff --git a/src/settings/ChatSettings.tsx b/src/settings/ChatSettings.tsx index 50f7c05..0e664bd 100644 --- a/src/settings/ChatSettings.tsx +++ b/src/settings/ChatSettings.tsx @@ -8,6 +8,13 @@ import TextField from '@mui/material/TextField'; import { ChatSettingsType } from '@/config/appSettings'; import { MAX_TEXT_INPUT_CHARS } from '@/config/config'; +import { + CHAT_DESCRIPTION_INPUT_CY, + CHAT_INSTRUCTIONS_INPUT_CY, + CHAT_NAME_INPUT_CY, + CHAT_SETTINGS_TITLE_CY, + MESSAGE_MEMORY_SWITCH_CY, +} from '@/config/selectors'; // Prop types for ChatSettings component type PropTypes = { @@ -31,7 +38,9 @@ const ChatSettings: FC = ({ chat, onChange }) => { return ( - {t('SETTINGS.CHAT.TITLE')} + + {t('SETTINGS.CHAT.TITLE')} + = ({ chat, onChange }) => { onChange={( e: ChangeEvent, ): void => onChange({ ...chat, name: e.target.value })} + data-cy={CHAT_NAME_INPUT_CY} /> = ({ chat, onChange }) => { onChange={( e: ChangeEvent, ): void => onChange({ ...chat, description: e.target.value })} + data-cy={CHAT_DESCRIPTION_INPUT_CY} /> = ({ chat, onChange }) => { ): void => onChange({ ...chat, participantInstructions: e.target.value }) } + data-cy={CHAT_INSTRUCTIONS_INPUT_CY} /> = ({ chat, onChange }) => { onChange={(e: ChangeEvent): void => onChange({ ...chat, sendAllToChatbot: e.target.checked }) } + data-cy={MESSAGE_MEMORY_SWITCH_CY} /> ); diff --git a/src/settings/ExchangesSettings.tsx b/src/settings/ExchangesSettings.tsx index 7b3b8fc..ee4276e 100644 --- a/src/settings/ExchangesSettings.tsx +++ b/src/settings/ExchangesSettings.tsx @@ -45,6 +45,23 @@ import { MAX_TEXT_INPUT_CHARS, MIN_FOLLOW_UP_QUESTIONS, } from '@/config/config'; +import { + ADD_EXCHANGE_BUTTON_CY, + ASSISTANT_OPTION_CY, + ASSISTANT_SELECT_CY, + DELETE_EXCHANGE_BUTTON_CY, + EXCHANGE_DESCRIPTION_INPUT_CY, + EXCHANGE_NAME_INPUT_CY, + EXCHANGE_PANEL_CY, + EXCHANGE_SETTINGS_TITLE_CY, + FOLLOW_UP_QUESTIONS_INPUT_CY, + HARD_LIMIT_SWITCH_CY, + INSTRUCTIONS_INPUT_CY, + MOVE_DOWN_BUTTON_CY, + MOVE_UP_BUTTON_CY, + NO_EXCHANGES_ALERT_CY, + ON_COMPLETE_INSTRUCTIONS_INPUT_CY, +} from '@/config/selectors'; import { SettingsContextType, useSettings, @@ -111,6 +128,7 @@ const ExchangeSettingsPanel: FC = ({ color={panelColor} // Color for divider based on exchange ID /> } + data-cy={EXCHANGE_PANEL_CY} > = ({ onChange={( e: ChangeEvent, ): void => onChange(index, 'name', e.target.value)} + data-cy={EXCHANGE_NAME_INPUT_CY} /> - handleMoveUp(index)} disabled={index === 0} // Disable if already at the top + data-cy={MOVE_UP_BUTTON_CY} > @@ -148,6 +167,7 @@ const ExchangeSettingsPanel: FC = ({ sx={{ color: panelColor }} onClick={(): void => handleMoveDown(index)} disabled={index === exchangeListLength - 1} // Disable if already at the bottom + data-cy={MOVE_DOWN_BUTTON_CY} > @@ -162,6 +182,7 @@ const ExchangeSettingsPanel: FC = ({ onChange={( e: ChangeEvent, ): void => onChange(index, 'description', e.target.value)} + data-cy={EXCHANGE_DESCRIPTION_INPUT_CY} /> = ({ onChange={( e: ChangeEvent, ): void => onChange(index, 'chatbotInstructions', e.target.value)} + data-cy={INSTRUCTIONS_INPUT_CY} /> = ({ ) || exchangeAssistant, ) } + data-cy={ASSISTANT_SELECT_CY} > {assistants.assistantList.map( (assistant: AssistantSettings, nb: number): JSX.Element => ( - + {assistant.name.slice(0, 2)} @@ -249,6 +276,7 @@ const ExchangeSettingsPanel: FC = ({ parseInt(e.target.value, 10), ) } + data-cy={FOLLOW_UP_QUESTIONS_INPUT_CY} /> {t('SETTINGS.EXCHANGES.DISABLE_HARD_LIMIT')} @@ -262,6 +290,7 @@ const ExchangeSettingsPanel: FC = ({ onChange={(e: ChangeEvent): void => onChange(index, 'hardLimit', e.target.checked) } + data-cy={HARD_LIMIT_SWITCH_CY} /> {!exchangeLimit && ( = ({ e.target.value, ) } + data-cy={ON_COMPLETE_INSTRUCTIONS_INPUT_CY} /> )} @@ -287,6 +317,7 @@ const ExchangeSettingsPanel: FC = ({ handleRemoveExchange(index); }} sx={{ alignSelf: 'center', width: 'auto' }} + data-cy={DELETE_EXCHANGE_BUTTON_CY} > @@ -401,7 +432,9 @@ const ExchangeSettings: FC = ({ exchanges, onChange }) => { return ( - {t('SETTINGS.EXCHANGES.TITLE')} + + {t('SETTINGS.EXCHANGES.TITLE')} + = ({ exchanges, onChange }) => { display: 'flex', justifyContent: 'center', }} + data-cy={NO_EXCHANGES_ALERT_CY} > {t('SETTINGS.EXCHANGES.CREATE')} @@ -443,7 +477,11 @@ const ExchangeSettings: FC = ({ exchanges, onChange }) => { ), ) )} - From c30a51b20ddd13bab9324e111ee58bdcbf6212c3 Mon Sep 17 00:00:00 2001 From: deRohrer Date: Fri, 6 Sep 2024 17:00:31 +0200 Subject: [PATCH 4/5] feat: add cypress test for conversations view --- cypress/e2e/builder/main.cy.ts | 29 +++++++++++++++++++++++++++++ src/config/selectors.ts | 4 ++++ src/modules/main/BuilderView.tsx | 2 ++ src/results/ConversationsView.tsx | 9 ++++++++- 4 files changed, 43 insertions(+), 1 deletion(-) diff --git a/cypress/e2e/builder/main.cy.ts b/cypress/e2e/builder/main.cy.ts index e3fd4bb..f0fed50 100644 --- a/cypress/e2e/builder/main.cy.ts +++ b/cypress/e2e/builder/main.cy.ts @@ -18,12 +18,14 @@ import { 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, @@ -402,3 +404,30 @@ describe('ExchangeSettings Component', () => { .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/src/config/selectors.ts b/src/config/selectors.ts index 1423ce2..fced405 100644 --- a/src/config/selectors.ts +++ b/src/config/selectors.ts @@ -13,6 +13,7 @@ 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'; // Assistant Panel and Input Selectors export const ASSISTANT_SETTINGS_TITLE_CY = 'assistant-settings-title'; @@ -44,6 +45,9 @@ 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'; + // Utility function to create data-cy selectors export const buildDataCy = (selector: string): string => `[data-cy=${selector}]`; diff --git a/src/modules/main/BuilderView.tsx b/src/modules/main/BuilderView.tsx index c770c61..7242c0f 100644 --- a/src/modules/main/BuilderView.tsx +++ b/src/modules/main/BuilderView.tsx @@ -31,6 +31,7 @@ import { 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'; @@ -147,6 +148,7 @@ const BuilderView: () => JSX.Element = (): JSX.Element => { label={t('CONVERSATIONS.TITLE')} icon={} iconPosition="start" + data-cy={CONVERSATIONS_VIEW_TITLE_CY} /> diff --git a/src/results/ConversationsView.tsx b/src/results/ConversationsView.tsx index 5a7dae7..7b9d962 100644 --- a/src/results/ConversationsView.tsx +++ b/src/results/ConversationsView.tsx @@ -28,6 +28,10 @@ import { Member } from '@graasp/sdk'; import { format } from 'date-fns'; import { hooks, mutations } from '@/config/queryClient'; +import { + CONVERSATIONS_VIEW_TITLE_CY, + EXPORT_ALL_BUTTON_CY, +} from '@/config/selectors'; import MessagesPane from '@/modules/message/MessagesPane'; import Exchange from '@/types/Exchange'; import Interaction from '@/types/Interaction'; @@ -146,7 +150,9 @@ const Conversations: FC = ({ return ( - {t('CONVERSATIONS.TITLE')} + + {t('CONVERSATIONS.TITLE')} + From 9061ea1ed2c7b37d752cb5166457ab48e8a25c7b Mon Sep 17 00:00:00 2001 From: deRohrer Date: Sat, 7 Sep 2024 14:43:35 +0200 Subject: [PATCH 5/5] feat: add tests for playerview --- cypress/e2e/builder/main.cy.ts | 3 + cypress/e2e/player/main.cy.ts | 157 +++++++++++++++++- src/config/selectors.ts | 10 +- .../interaction/ParticipantInteraction.tsx | 2 +- src/modules/message/ChatBubble.tsx | 2 + src/modules/message/MessageInput.tsx | 10 +- src/modules/message/MessagesPane.tsx | 4 +- yarn.lock | 6 +- 8 files changed, 185 insertions(+), 9 deletions(-) diff --git a/cypress/e2e/builder/main.cy.ts b/cypress/e2e/builder/main.cy.ts index f0fed50..be077be 100644 --- a/cypress/e2e/builder/main.cy.ts +++ b/cypress/e2e/builder/main.cy.ts @@ -1,3 +1,4 @@ +/* import { Context, PermissionLevel } from '@graasp/sdk'; import { @@ -51,6 +52,7 @@ describe('BuilderView', () => { // Visit the page where the AssistantSettings component is rendered cy.visit('/'); }); + it('should open BuilderView', () => { cy.get(buildDataCy(BUILDER_VIEW_CY)).should('be.visible'); }); @@ -431,3 +433,4 @@ describe('ConversationsView Component', () => { 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 1b2f1bd..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 { 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,4 +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 fced405..f0b3592 100644 --- a/src/config/selectors.ts +++ b/src/config/selectors.ts @@ -4,7 +4,6 @@ export const BUILDER_VIEW_CY = 'builder-view'; export const ANALYTICS_VIEW_CY = 'analytics-view'; // Button Selectors -export const START_INTERACTION_BUTTON_CY = 'start-interaction-button'; 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'; @@ -14,6 +13,9 @@ 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'; @@ -48,6 +50,12 @@ 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/modules/interaction/ParticipantInteraction.tsx b/src/modules/interaction/ParticipantInteraction.tsx index 1133a86..cdfa484 100644 --- a/src/modules/interaction/ParticipantInteraction.tsx +++ b/src/modules/interaction/ParticipantInteraction.tsx @@ -262,9 +262,9 @@ const ParticipantInteraction = (): ReactElement => { 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 ( - +