Skip to content

Commit 301289c

Browse files
authored
Resolving cypress tests (#2356)
* Fix bug - trying to sort a read-only property of array * Fix spacing * Fix error where copied nested objects are immutable * Define how fields should be merged (Apollo InMemoryCache error) * Parse userId when fetching and updating account * Linter fix * Parse user id when getting todos data * Clean up * Add try/catch * Parse buying phone number limit * Target limit table cell more precisely for integration tests * Clean up
1 parent 3c21079 commit 301289c

9 files changed

Lines changed: 134 additions & 81 deletions

File tree

__test__/cypress/integration/phone-inventory.test.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@ describe("Phone number management screen in the Admin interface", () => {
3434
// Waits until job run completes
3535
cy.waitUntil(
3636
() =>
37-
cy.get(`tr:contains(${testAreaCode}) td:nth-child(4)`).contains("1"),
37+
cy
38+
.get(`tr:contains(${testAreaCode}) td:nth-child(4) div`)
39+
.contains("1"),
3840
{ timeout: 1000 }
3941
);
4042
});

src/components/AssignmentTexter/Controls.jsx

Lines changed: 48 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import {
3535
import { dataTest } from "../../lib/attributes";
3636
import ContactToolbar from "./ContactToolbar";
3737
import { getCookie, setCookie } from "../../lib/cookie";
38+
import { deepCopy } from "../utils";
3839

3940
export class AssignmentTexterContactControls extends React.Component {
4041
constructor(props) {
@@ -124,7 +125,10 @@ export class AssignmentTexterContactControls extends React.Component {
124125
let currentInteractionStep = null;
125126

126127
if (availableSteps.length > 0) {
127-
currentInteractionStep = availableSteps[availableSteps.length - 1];
128+
const currentInteractionStep = deepCopy(
129+
availableSteps[availableSteps.length - 1]
130+
);
131+
128132
currentInteractionStep.question.filteredAnswerOptions =
129133
currentInteractionStep.question.answerOptions;
130134
}
@@ -508,8 +512,8 @@ export class AssignmentTexterContactControls extends React.Component {
508512

509513
const otherResponsesLink =
510514
currentInteractionStep &&
511-
currentInteractionStep.question.filteredAnswerOptions.length > 6 &&
512-
filteredCannedResponses.length ? (
515+
currentInteractionStep.question.filteredAnswerOptions.length > 6 &&
516+
filteredCannedResponses.length ? (
513517
<div className={css(flexStyles.popoverLink)} key={"otherresponses"}>
514518
<a
515519
href="#otherresponses"
@@ -522,8 +526,8 @@ export class AssignmentTexterContactControls extends React.Component {
522526

523527
const searchBar = currentInteractionStep &&
524528
currentInteractionStep.question.answerOptions.length +
525-
campaign.cannedResponses.length >
526-
5 && (
529+
campaign.cannedResponses.length >
530+
5 && (
527531
<SearchBar
528532
onRequestSearch={this.handleSearchChange}
529533
onChange={this.handleSearchChange}
@@ -661,12 +665,12 @@ export class AssignmentTexterContactControls extends React.Component {
661665
margin: "9px",
662666
color:
663667
this.state.optOutMessageText ===
664-
this.props.campaign.organization.optOutMessage
668+
this.props.campaign.organization.optOutMessage
665669
? "white"
666670
: "#494949",
667671
backgroundColor:
668672
this.state.optOutMessageText ===
669-
this.props.campaign.organization.optOutMessage
673+
this.props.campaign.organization.optOutMessage
670674
? "#727272"
671675
: "white"
672676
}}
@@ -879,9 +883,9 @@ export class AssignmentTexterContactControls extends React.Component {
879883
shortCannedResponses = shortCannedResponses.filter(script => {
880884
var textLength = global.HIDE_BRANCHED_SCRIPTS
881885
? this.getShortButtonText(
882-
script.title,
883-
cannedResponseScript ? 40 : 13
884-
).length
886+
script.title,
887+
cannedResponseScript ? 40 : 13
888+
).length
885889
: script.title.length;
886890

887891
if (joinedLength + 1 + textLength < 80) {
@@ -965,7 +969,7 @@ export class AssignmentTexterContactControls extends React.Component {
965969
<div className={css(flexStyles.subButtonsExitButtons)}>
966970
<Button
967971
onClick={
968-
!disabled ? this.handleOpenAnswerResponsePopover : noAction => { }
972+
!disabled ? this.handleOpenAnswerResponsePopover : noAction => {}
969973
}
970974
style={{
971975
backgroundColor: this.props.muiTheme.palette.background.default,
@@ -1197,40 +1201,40 @@ export class AssignmentTexterContactControls extends React.Component {
11971201
const content = firstMessage
11981202
? this.renderFirstMessage(enabledSideboxes)
11991203
: [
1200-
this.renderToolbar(enabledSideboxes),
1201-
<div
1202-
key="superSectionMessagePage"
1203-
className={css(flexStyles.superSectionMessagePage)}
1204-
>
1205-
{this.state.contactListOpen &&
1206-
this.renderAssignmentContactsList(
1207-
this.props.assignment.contacts,
1208-
this.props.contact,
1209-
this.props.updateCurrentContactById
1210-
)}
1211-
<div className={css(flexStyles.superSectionMessageListAndControls)}>
1212-
<ContactToolbar
1213-
campaignContact={this.props.contact}
1214-
campaign={this.props.campaign}
1215-
navigationToolbarChildren={this.props.navigationToolbarChildren}
1216-
toggleContactList={this.toggleContactList}
1217-
/>
1218-
{this.renderMessageBox(
1219-
<MessageList
1220-
contact={this.props.contact}
1221-
currentUser={this.props.currentUser}
1222-
messages={this.props.contact.messages}
1223-
organizationId={this.props.organizationId}
1224-
review={this.props.review}
1225-
styles={messageListStyles}
1226-
hideMedia={this.state.hideMedia}
1227-
/>,
1228-
enabledSideboxes
1229-
)}
1230-
{this.renderMessageControls(enabledSideboxes)}
1204+
this.renderToolbar(enabledSideboxes),
1205+
<div
1206+
key="superSectionMessagePage"
1207+
className={css(flexStyles.superSectionMessagePage)}
1208+
>
1209+
{this.state.contactListOpen &&
1210+
this.renderAssignmentContactsList(
1211+
this.props.assignment.contacts,
1212+
this.props.contact,
1213+
this.props.updateCurrentContactById
1214+
)}
1215+
<div className={css(flexStyles.superSectionMessageListAndControls)}>
1216+
<ContactToolbar
1217+
campaignContact={this.props.contact}
1218+
campaign={this.props.campaign}
1219+
navigationToolbarChildren={this.props.navigationToolbarChildren}
1220+
toggleContactList={this.toggleContactList}
1221+
/>
1222+
{this.renderMessageBox(
1223+
<MessageList
1224+
contact={this.props.contact}
1225+
currentUser={this.props.currentUser}
1226+
messages={this.props.contact.messages}
1227+
organizationId={this.props.organizationId}
1228+
review={this.props.review}
1229+
styles={messageListStyles}
1230+
hideMedia={this.state.hideMedia}
1231+
/>,
1232+
enabledSideboxes
1233+
)}
1234+
{this.renderMessageControls(enabledSideboxes)}
1235+
</div>
12311236
</div>
1232-
</div>
1233-
];
1237+
];
12341238
return (
12351239
<div
12361240
className={css(flexStyles.topContainer)}

src/components/utils.jsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,16 @@ export function getButtonProps(props) {
2727
];
2828
return pick(props, validProps);
2929
}
30+
31+
// Create a deep copy of an object so nested properties are also mutable
32+
export function deepCopy(obj) {
33+
if (Array.isArray(obj)) {
34+
return obj.map(item => deepCopy(item));
35+
} else if (typeof obj === "object" && obj !== null) {
36+
return Object.fromEntries(
37+
Object.entries(obj).map(([key, value]) => [key, deepCopy(value)])
38+
);
39+
} else {
40+
return obj;
41+
}
42+
}

src/containers/AdminIncomingMessageList.jsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ export class AdminIncomingMessageList extends Component {
2424
constructor(props) {
2525
super(props);
2626

27-
const query = props.location.query;
2827
const filters = getConversationFiltersFromQuery(
2928
props.location.query,
3029
props.organization.organization.tags

src/containers/AdminPhoneNumberInventory.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -492,7 +492,7 @@ const mutations = {
492492
variables: {
493493
organizationId: ownProps.params.organizationId,
494494
areaCode,
495-
limit
495+
limit: parseInt(limit)
496496
},
497497
refetchQueries: () => ["getOrganizationData"]
498498
}),

src/containers/PaginatedUsersRetriever.jsx

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -91,16 +91,25 @@ export class PaginatedUsersRetriever extends Component {
9191
let offset = 0;
9292
let total = undefined;
9393
let users = [];
94+
let results;
9495
do {
95-
const results = await fetchPeople(
96-
offset,
97-
pageSize,
98-
organizationId,
99-
campaignsFilter,
100-
sortBy || "FIRST_NAME",
101-
roleFilter
102-
);
103-
const { pageInfo, users: newUsers } = results.data.people;
96+
try {
97+
results = await fetchPeople(
98+
offset,
99+
pageSize,
100+
organizationId,
101+
campaignsFilter,
102+
sortBy || "FIRST_NAME",
103+
roleFilter
104+
);
105+
} catch (error) {
106+
console.error("Error fetching people:", error);
107+
}
108+
109+
const { pageInfo, users: newUsers } = results?.data?.people || {
110+
pageInfo: {},
111+
users: []
112+
};
104113
users = users.concat(newUsers);
105114
offset += pageSize;
106115
total = pageInfo.total;

src/containers/TexterTodoList.jsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@ class TexterTodoList extends React.Component {
5858
}
5959

6060
renderTodoList(assignments) {
61-
return assignments
61+
const sortedAssignments = [...assignments];
62+
return sortedAssignments
6263
.sort((x, y) => {
6364
// Sort with feedback at the top, and then based on Text assignment size
6465
const xHasFeedback =
@@ -266,7 +267,7 @@ const queries = {
266267
query: dataQuery,
267268
options: ownProps => ({
268269
variables: {
269-
userId: ownProps.params.userId || null,
270+
userId: parseInt(ownProps.params.userId) || null,
270271
organizationId: ownProps.params.organizationId,
271272
todosOrg:
272273
ownProps.location.query["org"] == "all" ||
@@ -301,7 +302,7 @@ const queries = {
301302
);
302303
return {
303304
variables: {
304-
userId: ownProps.params.userId || null,
305+
userId: parseInt(ownProps.params.userId) || null,
305306
organizationId: ownProps.params.organizationId
306307
},
307308
fetchPolicy: "network-only",

src/containers/UserEdit.jsx

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -62,23 +62,29 @@ const styles = StyleSheet.create({
6262
}
6363
});
6464

65-
const fetchUser = async (organizationId, userId) =>
66-
apolloClient.query({
67-
query: gql`
68-
query getEditedUser($organizationId: String!, $userId: Int!) {
69-
user(organizationId: $organizationId, userId: $userId) {
70-
id
71-
firstName
72-
email
73-
lastName
74-
alias
75-
cell
76-
extra
65+
const fetchUser = async (organizationId, userId) => {
66+
try {
67+
const response = await apolloClient.query({
68+
query: gql`
69+
query getEditedUser($organizationId: String!, $userId: Int!) {
70+
user(organizationId: $organizationId, userId: $userId) {
71+
id
72+
firstName
73+
email
74+
lastName
75+
alias
76+
cell
77+
extra
78+
}
7779
}
78-
}
79-
`,
80-
variables: { organizationId, userId }
81-
});
80+
`,
81+
variables: { organizationId, userId }
82+
});
83+
return response;
84+
} catch (error) {
85+
console.error("Error fetching user:", error);
86+
}
87+
};
8288

8389
const fetchOrg = async organizationId =>
8490
apolloClient.query({
@@ -130,7 +136,7 @@ export class UserEditBase extends React.Component {
130136
if (!this.props.authType && this.props.userId) {
131137
const response = await fetchUser(
132138
this.props.organizationId,
133-
this.props.userId
139+
parseInt(this.props.userId)
134140
);
135141
this.setState({
136142
editedUser: response.data
@@ -483,7 +489,7 @@ const mutations = {
483489
editUser: ownProps => userData => ({
484490
mutation: editUserMutation,
485491
variables: {
486-
userId: ownProps.userId,
492+
userId: parseInt(ownProps.userId),
487493
organizationId: ownProps.organizationId,
488494
userData
489495
}
@@ -507,9 +513,7 @@ const mutations = {
507513
};
508514

509515
const UserEdit = withMuiTheme(
510-
withRouter(
511-
loadData({ queries, mutations})(UserEditBase)
512-
)
516+
withRouter(loadData({ queries, mutations })(UserEditBase))
513517
);
514518

515519
export default UserEdit;

src/network/apollo-client-singleton.js

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,32 @@ const cache = new InMemoryCache({
6060
}
6161
return null;
6262
},
63-
// FUTURE: Apollo Client 3.0 allows this much more easily:
6463
typePolicies: {
6564
ContactTag: {
6665
// key is just the tag id and the value is contact-specific
6766
keyFields: false
67+
},
68+
// Define custom merge functions for multiple fields
69+
// https://go.apollo.dev/c/merging-non-normalized-objects
70+
// https://www.apollographql.com/docs/react/caching/cache-field-behavior/#merging-arrays
71+
Query: {
72+
fields: {
73+
organization: {
74+
merge: true
75+
}
76+
}
77+
},
78+
Campaign: {
79+
fields: {
80+
ingestMethod: {
81+
merge: true
82+
},
83+
pendingJobs: {
84+
merge(existing = [], incoming) {
85+
return incoming;
86+
}
87+
}
88+
}
6889
}
6990
}
7091
});

0 commit comments

Comments
 (0)