Merge remote-tracking branch 'upstream/v2.6.x-release' into fix-polls-multiple-answers-v26

This commit is contained in:
Gustavo Trott 2022-10-17 22:12:53 -03:00
commit 8c3114019d
22 changed files with 541 additions and 214 deletions

View File

@ -783,7 +783,7 @@ class BreakoutRoom extends PureComponent {
};
return (
<Styled.BoxContainer key="rooms-grid-" ref={(r) => { this.listOfUsers = r; }}>
<Styled.BoxContainer key="rooms-grid-" ref={(r) => { this.listOfUsers = r; }} data-test="roomGrid">
<Styled.Alert valid={leastOneUserIsValid} role="alert">
<Styled.FreeJoinLabel>
<Styled.BreakoutNameInput
@ -797,7 +797,7 @@ class BreakoutRoom extends PureComponent {
<Styled.BreakoutBox id="breakoutBox-0" onDrop={drop(0)} onDragOver={allowDrop} tabIndex={0}>
{this.renderUserItemByRoom(0)}
</Styled.BreakoutBox>
<Styled.SpanWarn valid={leastOneUserIsValid}>
<Styled.SpanWarn data-test="warningNoUserAssigned" valid={leastOneUserIsValid}>
{intl.formatMessage(intlMessages.leastOneWarnBreakout)}
</Styled.SpanWarn>
</Styled.Alert>
@ -814,6 +814,7 @@ class BreakoutRoom extends PureComponent {
onBlur={changeRoomName(value)}
aria-label={`${this.getRoomName(value)}`}
aria-describedby={this.getRoomName(value).length === 0 ? `room-error-${value}` : `room-input-${value}`}
data-test={this.getRoomName(value).length === 0 ? `room-error-${value}` : `roomName-${value}`}
readOnly={isUpdate}
/>
<div aria-hidden id={`room-input-${value}`} className="sr-only">
@ -885,6 +886,7 @@ class BreakoutRoom extends PureComponent {
onChange={this.changeDurationTime}
onBlur={this.blurDurationTime}
aria-label={intl.formatMessage(intlMessages.duration)}
data-test="durationTime"
/>
<Styled.HoldButtonWrapper
key="decrease-breakout-time"
@ -902,6 +904,7 @@ class BreakoutRoom extends PureComponent {
hideLabel
circle
size="sm"
data-test="decreaseBreakoutTime"
/>
</Styled.HoldButtonWrapper>
<Styled.HoldButtonWrapper
@ -918,6 +921,7 @@ class BreakoutRoom extends PureComponent {
hideLabel
circle
size="sm"
data-test="increaseBreakoutTime"
/>
</Styled.HoldButtonWrapper>
</Styled.DurationArea>

View File

@ -1,4 +1,4 @@
import React, { Component } from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import { defineMessages } from 'react-intl';
import _ from 'lodash';
@ -34,6 +34,10 @@ const intlMessages = defineMessages({
id: 'app.poll.abstention',
description: 'Poll Abstention option value',
},
typedRespLabel: {
id: 'app.poll.userResponse.label',
description: 'quick poll typed response label',
},
});
const propTypes = {
@ -44,172 +48,190 @@ const propTypes = {
amIPresenter: PropTypes.bool.isRequired,
};
const handleClickQuickPoll = (layoutContextDispatch) => {
layoutContextDispatch({
type: ACTIONS.SET_SIDEBAR_CONTENT_IS_OPEN,
value: true,
});
layoutContextDispatch({
type: ACTIONS.SET_SIDEBAR_CONTENT_PANEL,
value: PANELS.POLL,
});
Session.set('forcePollOpen', true);
Session.set('pollInitiated', true);
};
const QuickPollDropdown = (props) => {
const {
amIPresenter,
intl,
parseCurrentSlideContent,
startPoll,
currentSlide,
activePoll,
className,
layoutContextDispatch,
pollTypes,
} = props;
const getAvailableQuickPolls = (
slideId, parsedSlides, startPoll, pollTypes, layoutContextDispatch,
) => {
const pollItemElements = parsedSlides.map((poll) => {
const { poll: label } = poll;
const { type } = poll;
let itemLabel = label;
const letterAnswers = [];
const handleClickQuickPoll = (lCDispatch) => {
lCDispatch({
type: ACTIONS.SET_SIDEBAR_CONTENT_IS_OPEN,
value: true,
});
lCDispatch({
type: ACTIONS.SET_SIDEBAR_CONTENT_PANEL,
value: PANELS.POLL,
});
Session.set('forcePollOpen', true);
Session.set('pollInitiated', true);
};
if (type !== pollTypes.YesNo
&& type !== pollTypes.YesNoAbstention
&& type !== pollTypes.TrueFalse) {
const { options } = itemLabel;
itemLabel = options.join('/').replace(/[\n.)]/g, '');
if (type === pollTypes.Custom) {
for (let i = 0; i < options.length; i += 1) {
const letterOption = options[i]?.replace(/[\r.)]/g, '').toUpperCase();
if (letterAnswers.length < MAX_CUSTOM_FIELDS) {
letterAnswers.push(letterOption);
} else {
break;
const getAvailableQuickPolls = (
slideId, parsedSlides, funcStartPoll, _pollTypes, _layoutContextDispatch,
) => {
const pollItemElements = parsedSlides.map((poll) => {
const { poll: label } = poll;
const { type, poll: pollData } = poll;
let itemLabel = label;
const letterAnswers = [];
if (type === 'R-') {
return (
<Dropdown.DropdownListItem
label={intl.formatMessage(intlMessages.typedRespLabel)}
key={_.uniqueId('quick-poll-item')}
onClick={() => {
handleClickQuickPoll(_layoutContextDispatch);
funcStartPoll(type, slideId, letterAnswers, pollData?.question);
}}
question={pollData?.question}
/>
);
}
if (type !== _pollTypes.YesNo
&& type !== _pollTypes.YesNoAbstention
&& type !== _pollTypes.TrueFalse) {
const { options } = itemLabel;
itemLabel = options.join('/').replace(/[\n.)]/g, '');
if (type === _pollTypes.Custom) {
for (let i = 0; i < options.length; i += 1) {
const letterOption = options[i]?.replace(/[\r.)]/g, '').toUpperCase();
if (letterAnswers.length < MAX_CUSTOM_FIELDS) {
letterAnswers.push(letterOption);
} else {
break;
}
}
}
}
}
// removes any whitespace from the label
itemLabel = itemLabel?.replace(/\s+/g, '').toUpperCase();
// removes any whitespace from the label
itemLabel = itemLabel?.replace(/\s+/g, '').toUpperCase();
const numChars = {
1: 'A', 2: 'B', 3: 'C', 4: 'D', 5: 'E',
};
itemLabel = itemLabel.split('').map((c) => {
if (numChars[c]) return numChars[c];
return c;
}).join('');
const numChars = {
1: 'A', 2: 'B', 3: 'C', 4: 'D', 5: 'E',
};
itemLabel = itemLabel.split('').map((c) => {
if (numChars[c]) return numChars[c];
return c;
}).join('');
return (
<Dropdown.DropdownListItem
label={itemLabel}
key={_.uniqueId('quick-poll-item')}
onClick={() => {
handleClickQuickPoll(layoutContextDispatch);
startPoll(type, slideId, letterAnswers);
}}
answers={letterAnswers}
/>
);
});
return (
<Dropdown.DropdownListItem
label={itemLabel}
key={_.uniqueId('quick-poll-item')}
onClick={() => {
handleClickQuickPoll(_layoutContextDispatch);
funcStartPoll(type, slideId, letterAnswers, '', pollData?.multiResp);
}}
answers={letterAnswers}
multiResp={pollData?.multiResp}
/>
);
});
const sizes = [];
return pollItemElements.filter((el) => {
const { label } = el.props;
if (label.length === sizes[sizes.length - 1]) return false;
sizes.push(label.length);
return el;
});
};
const sizes = [];
return pollItemElements.filter((el) => {
const { label } = el.props;
if (label.length === sizes[sizes.length - 1]) return false;
sizes.push(label.length);
return el;
});
};
class QuickPollDropdown extends Component {
render() {
const {
amIPresenter,
intl,
parseCurrentSlideContent,
startPoll,
currentSlide,
activePoll,
className,
layoutContextDispatch,
pollTypes,
} = this.props;
const parsedSlide = parseCurrentSlideContent(
intl.formatMessage(intlMessages.yesOptionLabel),
intl.formatMessage(intlMessages.noOptionLabel),
intl.formatMessage(intlMessages.abstentionOptionLabel),
intl.formatMessage(intlMessages.trueOptionLabel),
intl.formatMessage(intlMessages.falseOptionLabel),
);
const parsedSlide = parseCurrentSlideContent(
intl.formatMessage(intlMessages.yesOptionLabel),
intl.formatMessage(intlMessages.noOptionLabel),
intl.formatMessage(intlMessages.abstentionOptionLabel),
intl.formatMessage(intlMessages.trueOptionLabel),
intl.formatMessage(intlMessages.falseOptionLabel),
);
const { slideId, quickPollOptions } = parsedSlide;
const quickPolls = getAvailableQuickPolls(
slideId, quickPollOptions, startPoll, pollTypes, layoutContextDispatch,
);
const { slideId, quickPollOptions } = parsedSlide;
const quickPolls = getAvailableQuickPolls(
slideId, quickPollOptions, startPoll, pollTypes, layoutContextDispatch,
);
if (quickPollOptions.length === 0) return null;
if (quickPollOptions.length === 0) return null;
let answers = null;
let question = '';
let quickPollLabel = '';
let multiResponse = false;
let answers = null;
let quickPollLabel = '';
if (quickPolls.length > 0) {
const { props: pollProps } = quickPolls[0];
quickPollLabel = pollProps.label;
answers = pollProps.answers;
}
if (quickPolls.length > 0) {
const { props: pollProps } = quickPolls[0];
quickPollLabel = pollProps?.label;
answers = pollProps?.answers;
question = pollProps?.question;
multiResponse = pollProps?.multiResp;
}
let singlePollType = null;
if (quickPolls.length === 1 && quickPollOptions.length) {
const { type } = quickPollOptions[0];
singlePollType = type;
}
let singlePollType = null;
if (quickPolls.length === 1 && quickPollOptions.length) {
const { type } = quickPollOptions[0];
singlePollType = type;
}
let btn = (
let btn = (
<Styled.QuickPollButton
aria-label={intl.formatMessage(intlMessages.quickPollLabel)}
label={quickPollLabel}
tooltipLabel={intl.formatMessage(intlMessages.quickPollLabel)}
onClick={() => {
handleClickQuickPoll(layoutContextDispatch);
startPoll(singlePollType, currentSlide.id, answers, question, multiResponse);
}}
size="lg"
disabled={!!activePoll}
data-test="quickPollBtn"
/>
);
const usePollDropdown = quickPollOptions && quickPollOptions.length && quickPolls.length > 1;
let dropdown = null;
if (usePollDropdown) {
btn = (
<Styled.QuickPollButton
aria-label={intl.formatMessage(intlMessages.quickPollLabel)}
label={quickPollLabel}
tooltipLabel={intl.formatMessage(intlMessages.quickPollLabel)}
onClick={() => {
handleClickQuickPoll(layoutContextDispatch);
startPoll(singlePollType, currentSlide.id, answers);
}}
onClick={() => null}
size="lg"
disabled={!!activePoll}
data-test="quickPollBtn"
/>
);
const usePollDropdown = quickPollOptions && quickPollOptions.length && quickPolls.length > 1;
let dropdown = null;
if (usePollDropdown) {
btn = (
<Styled.QuickPollButton
aria-label={intl.formatMessage(intlMessages.quickPollLabel)}
label={quickPollLabel}
tooltipLabel={intl.formatMessage(intlMessages.quickPollLabel)}
onClick={() => null}
size="lg"
disabled={!!activePoll}
/>
);
dropdown = (
<Dropdown className={className}>
<Dropdown.DropdownTrigger tabIndex={0}>
{btn}
</Dropdown.DropdownTrigger>
<Dropdown.DropdownContent>
<Dropdown.DropdownList>
{quickPolls}
</Dropdown.DropdownList>
</Dropdown.DropdownContent>
</Dropdown>
);
}
return amIPresenter && usePollDropdown ? (
dropdown
) : (
btn
dropdown = (
<Dropdown className={className}>
<Dropdown.DropdownTrigger tabIndex={0}>
{btn}
</Dropdown.DropdownTrigger>
<Dropdown.DropdownContent>
<Dropdown.DropdownList>
{quickPolls}
</Dropdown.DropdownList>
</Dropdown.DropdownContent>
</Dropdown>
);
}
}
return amIPresenter && usePollDropdown ? (
dropdown
) : (
btn
);
};
QuickPollDropdown.propTypes = propTypes;

View File

@ -2,7 +2,7 @@ import React from 'react';
const BreakoutRemainingTime = props => (
<span data-test="breakoutRemainingTime">
<span data-test="timeRemaining">
{props.children}
</span>
);

View File

@ -68,7 +68,7 @@ class breakoutRemainingTimeContainer extends React.Component {
<BreakoutRemainingTimeComponent>
<Text>{text}</Text>
<br />
<Time>{time}</Time>
<Time data-test="breakoutRemainingTime">{time}</Time>
</BreakoutRemainingTimeComponent>
);
}

View File

@ -429,11 +429,10 @@ class BreakoutRoom extends PureComponent {
} = this.state;
const { animations } = Settings.application;
const roomItems = breakoutRooms.map((breakout) => (
<Styled.BreakoutItems key={`breakoutRoomItems-${breakout.breakoutId}`} >
<Styled.Content key={`breakoutRoomList-${breakout.breakoutId}`}>
<Styled.BreakoutRoomListNameLabel aria-hidden>
<Styled.BreakoutRoomListNameLabel data-test={breakout.shortName} aria-hidden>
{breakout.isDefaultName
? intl.formatMessage(intlMessages.breakoutRoom, { 0: breakout.sequence })
: breakout.shortName}
@ -454,7 +453,9 @@ class BreakoutRoom extends PureComponent {
breakout.shortName,
)}
</Styled.Content>
<Styled.JoinedUserNames>
<Styled.JoinedUserNames
data-test={`userNameBreakoutRoom-${breakout.shortName}`}
>
{breakout.joinedUsers
.sort(BreakoutRoom.sortById)
.filter((value, idx, arr) => !(value.userId === (arr[idx + 1] || {}).userId))
@ -467,7 +468,7 @@ class BreakoutRoom extends PureComponent {
return (
<Styled.BreakoutColumn>
<Styled.BreakoutScrollableList>
<Styled.BreakoutScrollableList data-test="breakoutRoomList">
{roomItems}
</Styled.BreakoutScrollableList>
</Styled.BreakoutColumn>
@ -518,6 +519,7 @@ class BreakoutRoom extends PureComponent {
&nbsp;
&nbsp;
<Styled.EndButton
data-test="sendButtonDurationTime"
color="primary"
disabled={!isMeteorConnected}
size="sm"

View File

@ -81,7 +81,7 @@ class LiveResult extends PureComponent {
if (response) {
const formattedAnswers = [];
response.answerIds.forEach((answerId) => {
const formattedMessageIndex = answers[answerId].key.toLowerCase();
const formattedMessageIndex = answers[answerId]?.key?.toLowerCase();
const formattedAnswer = defaultPoll && pollAnswerIds[formattedMessageIndex]
? intl.formatMessage(pollAnswerIds[formattedMessageIndex])
: answers[answerId].key;
@ -115,7 +115,7 @@ class LiveResult extends PureComponent {
const pollStats = [];
answers.reduce(caseInsensitiveReducer, []).map((obj) => {
const formattedMessageIndex = obj.key.toLowerCase();
const formattedMessageIndex = obj?.key?.toLowerCase();
const pct = Math.round(obj.numVotes / numResponders * 100);
const pctFotmatted = `${Number.isNaN(pct) ? 0 : pct}%`;

View File

@ -146,7 +146,7 @@ class Polling extends Component {
}
<Styled.PollingAnswers removeColumns={answers.length === 1} stacked={stackOptions}>
{answers.map((pollAnswer) => {
const formattedMessageIndex = pollAnswer?.key.toLowerCase();
const formattedMessageIndex = pollAnswer?.key?.toLowerCase();
let label = pollAnswer.key;
if (defaultPoll && pollAnswerIds[formattedMessageIndex]) {
label = intl.formatMessage(pollAnswerIds[formattedMessageIndex]);
@ -235,8 +235,8 @@ class Polling extends Component {
)}
<Styled.MultipleResponseAnswersTable>
{poll.answers.map((pollAnswer) => {
const formattedMessageIndex = pollAnswer.key.toLowerCase();
let label = pollAnswer.key;
const formattedMessageIndex = pollAnswer?.key?.toLowerCase();
let label = pollAnswer?.key;
if (pollAnswerIds[formattedMessageIndex]) {
label = intl.formatMessage(pollAnswerIds[formattedMessageIndex]);
}

View File

@ -43,12 +43,12 @@ export default withTracker((params) => {
presentationId,
} = params;
const startPoll = (type, id, answers) => {
const startPoll = (type, id, answers = [], question = '', multiResp = false) => {
Session.set('openPanel', 'poll');
Session.set('forcePollOpen', true);
window.dispatchEvent(new Event('panelChanged'));
makeCall('startPoll', PollService.pollTypes, type, id, false, '', false, answers);
makeCall('startPoll', PollService.pollTypes, type, id, false, question, multiResp, answers);
};
return {

View File

@ -330,18 +330,17 @@ class PresentationUploader extends Component {
let shouldUpdateState = isOpen && !prevProps.isOpen;
const presState = Object.values({
...propPresentations,
...presentations,
...JSON.parse(JSON.stringify(propPresentations)),
...JSON.parse(JSON.stringify(presentations)),
});
if (propPresentations.length > prevPropPresentations.length) {
shouldUpdateState = true;
const propsDiffs = propPresentations.filter(p => !prevPropPresentations.includes(p))
const propsDiffs = propPresentations.filter(p =>
!prevPropPresentations.some(presentation => p.id === presentation.id
|| p.temporaryPresentationId === presentation.temporaryPresentationId));
propsDiffs.forEach(p => {
const index = presState.findIndex(pres => {
if (p.isCurrent) {
pres.isCurrent = false;
}
return pres.temporaryPresentationId === p.temporaryPresentationId || pres.id === p.id;
});
if (index === -1) {

View File

@ -83,6 +83,12 @@ const parseCurrentSlideContent = (yesValue, noValue, abstentionValue, trueValue,
content,
} = currentSlide;
const questionRegex = /.*?\?$/gm;
let question = content.match(questionRegex) || '';
const doubleQuestionRegex = /\?{2}/gm;
let doubleQuestion = content.match(doubleQuestionRegex) || null;
const pollRegex = /[1-9A-Ia-i][.)].*/g;
let optionsPoll = content.match(pollRegex) || [];
if (optionsPoll) optionsPoll = optionsPoll.map((opt) => `\r${opt[0]}.`);
@ -123,6 +129,7 @@ const parseCurrentSlideContent = (yesValue, noValue, abstentionValue, trueValue,
}, []).filter(({
options,
}) => options.length > 1 && options.length < 10).forEach((poll) => {
if (doubleQuestion) poll.multiResp = true;
if (poll.options.length <= 5 || MAX_CUSTOM_FIELDS <= 5) {
const maxAnswer = poll.options.length > MAX_CUSTOM_FIELDS
? MAX_CUSTOM_FIELDS
@ -139,6 +146,15 @@ const parseCurrentSlideContent = (yesValue, noValue, abstentionValue, trueValue,
}
});
if (question.length > 0 && optionsPoll.length === 0 && !doubleQuestion) {
quickPollOptions.push({
type: `R-`,
poll: {
question: question[0],
}
});
}
if (quickPollOptions.length > 0) {
content = content.replace(new RegExp(pollRegex), '');
}

View File

@ -15,6 +15,7 @@ const propTypes = {
};
const ROLE_MODERATOR = Meteor.settings.public.user.role_moderator;
const ALWAYS_SHOW_WAITING_ROOM = Meteor.settings.public.app.alwaysShowWaitingRoomUI;
class UserContent extends PureComponent {
render() {
@ -26,7 +27,7 @@ class UserContent extends PureComponent {
compact,
} = this.props;
const showWaitingRoom = (isGuestLobbyMessageEnabled && isWaitingRoomEnabled)
const showWaitingRoom = (ALWAYS_SHOW_WAITING_ROOM && isWaitingRoomEnabled)
|| pendingUsers.length > 0;
return (

View File

@ -57,6 +57,7 @@ public:
allowUserLookup: false
dynamicGuestPolicy: true
enableGuestLobbyMessage: true
alwaysShowWaitingRoomUI: true
enableLimitOfViewersInWebcam: false
enableMultipleCameras: true
# Allow users to open webcam video modal/preview when video is already

View File

@ -311,7 +311,7 @@
"app.poll.clickHereToSelect": "Click here to select",
"app.poll.question.label" : "Write your question...",
"app.poll.optionalQuestion.label" : "Write your question (optional)...",
"app.poll.userResponse.label" : "User Response",
"app.poll.userResponse.label" : "Typed Response",
"app.poll.responseTypes.label" : "Response Types",
"app.poll.optionDelete.label" : "Delete",
"app.poll.responseChoices.label" : "Response Choices",

View File

@ -3,38 +3,122 @@ const { Create } = require('./create');
const { Join } = require('./join');
test.describe.parallel('Breakout', () => {
test('Create Breakout room @ci', async ({ browser, context, page }) => {
const create = new Create(browser, context);
await create.initPages(page);
await create.create();
test.describe.parallel('Creating', () => {
test('Create Breakout room @ci', async ({ browser, context, page }) => {
const create = new Create(browser, context);
await create.initPages(page);
await create.create();
});
test('Change number of rooms', async ({ browser, context, page }) => {
const create = new Create(browser, context);
await create.initPages(page);
await create.changeNumberOfRooms();
});
test('Change duration time', async ({ browser, context, page }) => {
const create = new Create(browser, context);
await create.initPages(page);
await create.changeDurationTime();
});
test('Change rooms name', async ({ browser, context, page }) => {
const create = new Create(browser, context);
await create.initPages(page);
await create.changeRoomsName();
});
test('Remove and reset assignments', async ({ browser, context, page }) => {
const create = new Create(browser, context);
await create.initPages(page);
await create.removeAndResetAssignments();
});
test('Drag and drop user in a room', async ({ browser, context, page }) => {
const create = new Create(browser, context);
await create.initPages(page);
await create.dragDropUserInRoom();
});
});
// https://docs.bigbluebutton.org/2.6/release-tests.html#moderators-creating-breakout-rooms-and-assiging-users-automated
test('Join Breakout room @ci', async ({ browser, context, page }) => {
const join = new Join(browser, context);
await join.initPages(page);
await join.create()
await join.joinRoom();
});
test.describe.parallel('After creating', () => {
// https://docs.bigbluebutton.org/2.6/release-tests.html#moderators-creating-breakout-rooms-and-assiging-users-automated
test('Join Breakout room @ci', async ({ browser, context, page }) => {
const join = new Join(browser, context);
await join.initPages(page);
await join.create()
await join.joinRoom();
});
test('Join Breakout room and share webcam', async ({ browser, context, page }) => {
const join = new Join(browser, context);
await join.initPages(page);
await join.create()
await join.joinAndShareWebcam();
});
test('Join Breakout room and share webcam', async ({ browser, context, page }) => {
const join = new Join(browser, context);
await join.initPages(page);
await join.create()
await join.joinAndShareWebcam();
});
test('Join Breakout room and share screen', async ({ browser, context, page }) => {
const join = new Join(browser, context);
await join.initPages(page);
await join.create();
await join.joinAndShareScreen();
});
test('Join Breakout room and share screen', async ({ browser, context, page }) => {
const join = new Join(browser, context);
await join.initPages(page);
await join.create();
await join.joinAndShareScreen();
});
test('Join Breakout room with Audio', async ({ browser, context, page }) => {
const join = new Join(browser, context);
await join.initPages(page);
await join.create();
await join.joinWithAudio();
test('Join Breakout room with Audio', async ({ browser, context, page }) => {
const join = new Join(browser, context);
await join.initPages(page);
await join.create();
await join.joinWithAudio();
});
test('Message to all rooms', async ({ browser, context, page }) => {
const join = new Join(browser, context);
await join.initPages(page);
await join.create();
await join.messageToAllRooms();
});
test('Change duration time', async ({ browser, context, page }) => {
const join = new Join(browser, context);
await join.initPages(page);
await join.create();
await join.changeDurationTime();
});
test('User name shows below rooms name', async ({ browser, context, page }) => {
const join = new Join(browser, context);
await join.initPages(page);
await join.create();
await join.usernameShowsBelowRoomsName();
});
test('Show breakout room time remaining', async ({ browser, context, page }) => {
const join = new Join(browser, context);
await join.initPages(page);
await join.create();
await join.showBreakoutRoomTimeRemaining();
});
test('End all breakout rooms', async ({ browser, context, page }) => {
const join = new Join(browser, context);
await join.initPages(page);
await join.create();
await join.endAllBreakoutRooms();
});
test('Invite user after creating rooms', async ({ browser, context, page }) => {
const join = new Join(browser, context);
await join.initPages(page);
await join.create();
await join.inviteUserAfterCreatingRooms();
});
test('Move user to another room', async ({ browser, context, page }) => {
const join = new Join(browser, context);
await join.initPages(page);
await join.create();
await join.moveUserToOtherRoom();
});
});
});

View File

@ -1,6 +1,7 @@
const { MultiUsers } = require('../user/multiusers');
const e = require('../core/elements');
const { ELEMENT_WAIT_LONGER_TIME } = require('../core/constants');
const { expect } = require('@playwright/test');
class Create extends MultiUsers {
constructor(browser, context) {
@ -12,6 +13,7 @@ class Create extends MultiUsers {
await this.modPage.waitAndClick(e.manageUsers);
await this.modPage.waitAndClick(e.createBreakoutRooms);
//Randomly assignment
await this.modPage.waitAndClick(e.randomlyAssign);
await this.modPage.waitAndClick(e.modalConfirmButton, ELEMENT_WAIT_LONGER_TIME);
@ -19,6 +21,85 @@ class Create extends MultiUsers {
await this.userPage.waitAndClick(e.modalDismissButton);
await this.modPage.hasElement(e.breakoutRoomsItem);
}
async changeNumberOfRooms() {
await this.modPage.waitAndClick(e.manageUsers);
await this.modPage.waitAndClick(e.createBreakoutRooms);
await this.modPage.waitAndClick(e.randomlyAssign);
await this.modPage.getLocator(e.selectNumberOfRooms).selectOption('7');
await this.modPage.waitAndClick(e.modalConfirmButton, ELEMENT_WAIT_LONGER_TIME);
await this.modPage.waitAndClick(e.breakoutRoomsItem);
await this.modPage.checkElementCount(e.userNameBreakoutRoom7, 1);
}
async changeDurationTime() {
await this.modPage.waitAndClick(e.manageUsers);
await this.modPage.waitAndClick(e.createBreakoutRooms);
await this.modPage.waitAndClick(e.randomlyAssign);
//test minimum 5 minutes
await this.modPage.getLocator(e.durationTime).press('Backspace');
await this.modPage.getLocator(e.durationTime).press('Backspace');
await this.modPage.type(e.durationTime, '5');
await this.modPage.waitAndClick(e.decreaseBreakoutTime);
await this.modPage.hasValue(e.durationTime, '5');
await this.modPage.getLocator(e.durationTime).press('Backspace');
await this.modPage.type(e.durationTime, '15');
await this.modPage.waitAndClick(e.increaseBreakoutTime);
await this.modPage.waitAndClick(e.modalConfirmButton, ELEMENT_WAIT_LONGER_TIME);
await this.modPage.waitAndClick(e.breakoutRoomsItem);
await this.modPage.hasText(e.breakoutRemainingTime, /15:[0-5][0-9]/);
}
async changeRoomsName() {
await this.modPage.waitAndClick(e.manageUsers);
await this.modPage.waitAndClick(e.createBreakoutRooms);
await this.modPage.waitAndClick(e.randomlyAssign);
//Change room's name
await this.modPage.type(e.roomNameInput, 'Test');
await this.modPage.waitAndClick(e.modalConfirmButton, ELEMENT_WAIT_LONGER_TIME);
await this.modPage.waitAndClick(e.breakoutRoomsItem);
await this.modPage.hasText(e.roomName1Test, /Test/);
}
async removeAndResetAssignments() {
await this.modPage.waitAndClick(e.manageUsers);
await this.modPage.waitAndClick(e.createBreakoutRooms);
//Reset assignments
await this.modPage.waitAndClick(e.randomlyAssign);
await this.modPage.hasText(e.breakoutBox1, /Attendee/);
await this.modPage.waitAndClick(e.resetAssignments);
await this.modPage.hasText(e.breakoutBox0, /Attendee/);
//Remove specific assignment
await this.modPage.waitAndClick(e.randomlyAssign);
await this.modPage.dragDropSelector(e.moveUser, e.breakoutBox0);
await this.modPage.hasText(e.breakoutBox0, /Attendee/);
}
async dragDropUserInRoom() {
await this.modPage.waitAndClick(e.manageUsers);
await this.modPage.waitAndClick(e.createBreakoutRooms);
//testing no user assigned
await this.modPage.waitAndClick(e.modalConfirmButton);
await this.modPage.hasElement(e.warningNoUserAssigned);
//await this.modPage.hasElementDisabled(e.modalConfirmButton);
const modalConfirmButton = await this.modPage.getLocator(e.modalConfirmButton);
await expect(modalConfirmButton, 'Getting error when trying to create a breakout room without designating any user.').toBeDisabled();
await this.modPage.dragDropSelector(e.userTest, e.breakoutBox1);
await this.modPage.hasText(e.breakoutBox1, /Attendee/);
await this.modPage.waitAndClick(e.modalConfirmButton, ELEMENT_WAIT_LONGER_TIME);
await this.userPage.waitAndClick(e.modalConfirmButton);
await this.modPage.waitAndClick(e.breakoutRoomsItem);
await this.modPage.hasText(e.userNameBreakoutRoom, /Attendee/);
}
}
exports.Create = Create;

View File

@ -3,6 +3,7 @@ const utilScreenShare = require('../screenshare/util');
const e = require('../core/elements');
const { ELEMENT_WAIT_LONGER_TIME } = require('../core/constants');
const { getSettings } = require('../core/settings');
const { expect } = require('@playwright/test');
class Join extends Create {
constructor(browser, context) {
@ -51,6 +52,93 @@ class Join extends Create {
await breakoutUserPage.waitForSelector(e.talkingIndicator);
await breakoutUserPage.hasElement(e.isTalking);
}
async messageToAllRooms() {
const breakoutUserPage = await this.joinRoom();
await breakoutUserPage.hasElement(e.presentationTitle);
await this.modPage.waitAndClick(e.breakoutRoomsItem);
await this.modPage.type(e.chatBox, "test");
await this.modPage.waitAndClick(e.sendButton);
await breakoutUserPage.hasElement(e.chatUserMessageText);
}
async changeDurationTime() {
const breakoutUserPage = await this.joinRoom();
await breakoutUserPage.hasElement(e.presentationTitle);
await this.modPage.waitAndClick(e.breakoutRoomsItem);
await this.modPage.waitAndClick(e.breakoutOptionsMenu);
await this.modPage.waitAndClick(e.openBreakoutTimeManager);
await this.modPage.getLocator(e.inputSetTimeSelector).press('Backspace');
await this.modPage.type(e.inputSetTimeSelector, '2');
await this.modPage.waitAndClick(e.sendButtonDurationTime);
await this.modPage.hasText(e.breakoutRemainingTime, /[11-12]:[0-5][0-9]/);
await breakoutUserPage.hasText(e.timeRemaining, /[11-12]:[0-5][0-9]/);
}
async inviteUserAfterCreatingRooms() {
await this.modPage.waitAndClick(e.breakoutRoomsItem);
await this.modPage.waitAndClick(e.breakoutOptionsMenu);
await this.modPage.waitAndClick(e.openUpdateBreakoutUsersModal);
await this.modPage.dragDropSelector(e.userTest, e.breakoutBox1);
await this.modPage.hasText(e.breakoutBox1, /Attendee/);
await this.modPage.waitAndClick(e.modalConfirmButton);
await this.userPage.hasElement(e.modalConfirmButton);
await this.userPage.waitAndClick(e.modalDismissButton);
}
async usernameShowsBelowRoomsName() {
const breakoutUserPage = await this.joinRoom();
await this.modPage.waitAndClick(e.breakoutRoomsItem);
await this.modPage.hasText(e.userNameBreakoutRoom, /Attendee/);
}
async showBreakoutRoomTimeRemaining() {
const breakoutUserPage = await this.joinRoom();
await breakoutUserPage.hasElement(e.presentationTitle);
await this.modPage.waitAndClick(e.breakoutRoomsItem);
await this.modPage.waitAndClick(e.breakoutOptionsMenu);
await this.modPage.waitAndClick(e.openBreakoutTimeManager);
await this.modPage.getLocator(e.inputSetTimeSelector).press('Backspace');
await this.modPage.type(e.inputSetTimeSelector, '2');
await this.modPage.waitAndClick(e.sendButtonDurationTime);
await this.modPage.hasText(e.breakoutRemainingTime, /[11-12]:[0-5][0-9]/);
await breakoutUserPage.hasText(e.timeRemaining,/[11-12]:[0-5][0-9]/);
}
async endAllBreakoutRooms() {
await this.modPage.waitAndClick(e.breakoutRoomsItem);
await this.modPage.waitAndClick(e.breakoutOptionsMenu);
await this.modPage.waitAndClick(e.endAllBreakouts);
await this.modPage.wasRemoved(e.breakoutRoomsItem);
}
async moveUserToOtherRoom() {
const breakoutUserPage = await this.joinRoom();
await breakoutUserPage.hasElement(e.presentationTitle);
await this.modPage.waitAndClick(e.breakoutRoomsItem);
await this.modPage.hasText(e.userNameBreakoutRoom, /Attendee/);
await this.modPage.waitAndClick(e.breakoutOptionsMenu);
await this.modPage.waitAndClick(e.openUpdateBreakoutUsersModal);
await this.modPage.dragDropSelector(e.moveUser, e.breakoutBox2);
await this.modPage.waitAndClick(e.modalConfirmButton);
await this.userPage.waitForSelector(e.modalConfirmButton);
await expect(breakoutUserPage.page.isClosed(), "Previous breakout room page did not close!").toBeTruthy();
await this.userPage.waitAndClick(e.modalConfirmButton);
await this.modPage.hasText(e.userNameBreakoutRoom2, /Attendee/);
}
}
exports.Join = Join;

View File

@ -13,25 +13,22 @@ class Chat extends Page {
async sendPublicMessage() {
await openChat(this);
const message = this.getLocator(e.chatUserMessageText);
await expect(message).toHaveCount(0);
await this.checkElementCount(e.chatUserMessageText, 0);
await this.type(e.chatBox, e.message);
await this.waitAndClick(e.sendButton);
await this.waitForSelector(e.chatUserMessageText);
await expect(message).toHaveCount(1);
await this.checkElementCount(e.chatUserMessageText, 1);
}
async clearChat() {
await openChat(this);
const message = this.getLocator(e.chatUserMessageText);
await this.type(e.chatBox, e.message);
await this.waitAndClick(e.sendButton);
await this.waitForSelector(e.chatUserMessageText);
// 1 message
await expect(message).toHaveCount(1);
await this.checkElementCount(e.chatUserMessageText, 1);
// clear
await this.waitAndClick(e.chatOptions);
@ -80,27 +77,25 @@ class Chat extends Page {
async characterLimit() {
await openChat(this);
const messageLocator = this.getLocator(e.chatUserMessageText);
const { maxMessageLength } = getSettings();
await this.page.fill(e.chatBox, e.uniqueCharacterMessage.repeat(maxMessageLength));
await this.waitAndClick(e.sendButton);
await this.waitForSelector(e.chatUserMessageText);
await expect(messageLocator).toHaveCount(1);
await this.checkElementCount(e.chatUserMessageText, 1);
await this.page.fill(e.chatBox, e.uniqueCharacterMessage.repeat(maxMessageLength + 1));
await this.waitForSelector(e.typingIndicator);
await this.waitAndClick(e.sendButton);
await this.waitForSelector(e.chatUserMessageText);
await expect(messageLocator).toHaveCount(1);
await this.checkElementCount(e.chatUserMessageText, 1);
}
async emptyMessage() {
await openChat(this);
const messageLocator = this.getLocator(e.chatUserMessageText);
await this.waitAndClick(e.sendButton);
await expect(messageLocator).toHaveCount(0);
await this.checkElementCount(e.chatUserMessageText, 0);
}
// Emojis

View File

@ -34,8 +34,7 @@ class ConnectionStatus extends MultiUsers {
await this.modPage.hasElement(e.connectionStatusItemEmpty);
await this.modPage.page.evaluate(() => window.dispatchEvent(new CustomEvent('socketstats', { detail: { rtt: 2000 } })));
await this.modPage.wasRemoved(e.connectionStatusItemEmpty);
const status = this.modPage.getLocator(e.connectionStatusItemUser);
await expect(status).toHaveCount(1);
await this.modPage.checkElementCount(e.connectionStatusItemUser, 1);
}
async linkToSettingsTest() {

View File

@ -49,6 +49,7 @@ exports.muteMicButton = 'button[data-test="muteMicButton"]';
// Breakout
exports.createBreakoutRooms = 'li[data-test="createBreakoutRooms"]';
exports.randomlyAssign = 'button[data-test="randomlyAssign"]';
exports.resetAssignments = 'button[data-test="resetAssignments"]'
exports.breakoutRoomsItem = 'div[data-test="breakoutRoomsItem"]';
exports.alreadyConnected = 'span[data-test="alreadyConnected"]';
exports.askJoinRoom1 = 'button[data-test="askToJoinRoom1"]';
@ -56,6 +57,31 @@ exports.joinRoom1 = 'button[data-test="joinRoom1"]';
exports.allowChoiceRoom = 'input[id="freeJoinCheckbox"]';
exports.labelGeneratingURL = 'span[data-test="labelGeneratingURL"]';
exports.endBreakoutRoomsButton = 'button[data-test="endBreakoutRoomsButton"]';
exports.durationTime = 'input[data-test="durationTime"]';
exports.decreaseBreakoutTime = 'button[data-test="decreaseBreakoutTime"]';
exports.increaseBreakoutTime = 'button[data-test="increaseBreakoutTime"]';
exports.selectNumberOfRooms = 'select[id="numberOfRooms"]';
exports.roomGrid = 'div[data-test="roomGrid"] >> input';
exports.breakoutBox0 = 'div[id="breakoutBox-0"]';
exports.breakoutBox1 = 'div[id="breakoutBox-1"]';
exports.breakoutBox2 = 'div[id="breakoutBox-2"]';
exports.breakoutOptionsMenu = 'button[data-test="breakoutOptionsMenu"]';
exports.openUpdateBreakoutUsersModal = 'li[data-test="openUpdateBreakoutUsersModal"]';
exports.userTest = 'div[id="breakoutBox-0"] >> p:nth-child(2)';
exports.moveUser = 'div[id="breakoutBox-1"] >> p:nth-child(1)';
exports.openBreakoutTimeManager = 'li[data-test="openBreakoutTimeManager"]';
exports.inputSetTimeSelector = 'input[id="inputSetTimeSelector"]';
exports.sendButtonDurationTime = 'button[data-test="sendButtonDurationTime"]';
exports.breakoutRemainingTime = 'span[data-test="breakoutRemainingTime"]';
exports.roomNameInput = 'input[data-test="roomName-1"]';
exports.roomName1Test = 'span[data-test="Room 1Test"]';
exports.userNameBreakoutRoom = 'div[data-test="userNameBreakoutRoom-Room 1"]';
exports.userNameBreakoutRoom2 = 'div[data-test="userNameBreakoutRoom-Room 2"]';
exports.userNameBreakoutRoom7 = 'div[data-test="userNameBreakoutRoom-Room 7"]';
exports.endAllBreakouts = 'li[data-test="endAllBreakouts"]';
exports.breakoutRoomList = 'div[data-test="breakoutRoomList"]';
exports.warningNoUserAssigned = 'span[data-test="warningNoUserAssigned"]';
exports.timeRemaining = 'span[data-test="timeRemaining"]';
// Chat
exports.chatBox = 'textarea[id="message-input"]';

View File

@ -210,6 +210,20 @@ class Page {
async up(key) {
await this.page.keyboard.up(key);
}
async dragDropSelector(selector, position) {
await this.page.locator(selector).dragTo(this.page.locator(position));
}
async checkElementCount(selector, count) {
const locator = await this.page.locator(selector);
await expect(locator).toHaveCount(count);
}
async hasValue(selector, value) {
const locator = await this.page.locator(selector);
await expect(locator).toHaveValue(value);
}
}
module.exports = exports = Page;

View File

@ -72,8 +72,7 @@ class LockViewers extends MultiUsers {
await this.modPage.type(e.chatBox, e.message);
await this.modPage.waitAndClick(e.sendButton);
await this.userPage.waitForSelector(e.chatUserMessageText);
const messagesCount = this.userPage.getLocator(e.chatUserMessageText);
await expect(messagesCount).toHaveCount(1);
await this.userPage.checkElementCount(e.chatUserMessageText, 1);
}
async lockSendPrivateChatMessages() {

View File

@ -80,14 +80,10 @@ class MultiUsers {
}
async userPresence() {
const firstUserOnModPage = this.modPage.getLocator(e.currentUser);
const secondUserOnModPage = this.modPage.getLocator(e.userListItem);
const firstUserOnUserPage = this.userPage.getLocator(e.currentUser);
const secondUserOnUserPage = this.userPage.getLocator(e.userListItem);
await expect(firstUserOnModPage).toHaveCount(1);
await expect(secondUserOnModPage).toHaveCount(1);
await expect(firstUserOnUserPage).toHaveCount(1);
await expect(secondUserOnUserPage).toHaveCount(1);
await this.modPage.checkElementCount(e.currentUser, 1);
await this.modPage.checkElementCount(e.userListItem, 1);
await this.userPage.checkElementCount(e.currentUser, 1);
await this.userPage.checkElementCount(e.userListItem, 1);
}
async makePresenter() {