-
Notifications
You must be signed in to change notification settings - Fork 1.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Create scheduled post api integration #8519
base: feature_schedule_posts
Are you sure you want to change the base?
Create scheduled post api integration #8519
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some comments on the tests. I haven't looked at them all yet, so I will keep adding comments when I get back to this.
Nevertheless, nothing blocking.
} | ||
return {scheduledPost: created}; | ||
// TODO - record scheduled post in database here | ||
return {data: true, response}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still think that data should be the response directly, not a boolean. See for example fetchThread
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would tend to agree with you @larkox, but apparently this is an else for createPost(), which also returns {data: true/false}.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It has to be data: boolean and response separately as doSubmitMessage
call either createPost or createScheduledPost and both need to return the same structure. Create Post returns data: boolean, and so for that reason, so does createScheduledPost have to and updating createPost is beyond the scope of this feature.
export async function createScheduledPost(serverUrl: string, schedulePost: ScheduledPost, connectionId?: string, fetchOnly = false) { | ||
import type {CreateResponse} from '@hooks/handle_send_message'; | ||
|
||
export async function createScheduledPost(serverUrl: string, schedulePost: ScheduledPost, connectionId?: string): Promise<CreateResponse> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Not a request for changes, just a knowledge share)
In general for remote actions we add another optional parameter fetchOnly
, default to false. This is so we can decide not to update the local state when we get certain information. This may be done for several reasons, like fetching data that is not going to be kept up to date (nor by websocket nor syncing logic), so we don't want it in the database. This could be, for example, browse channels results.
In this particular case, I don't foresee any case where we want to create a schedule post and not update the local database, so it should be just fine not having the parameter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had fetchOnly but since this doesn't make aAPI calls, the linter complained about an ununsed param and I removed it. I will add it when needed.
expect(result).toBeDefined(); | ||
expect(result.error).toBeDefined(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: We could check that on error, we are also logging the error and calling logout if necessary.
We would just have to mock the logDebug and the forceLogoutIfNecessary and making sure they are called.
You could also make sure the error logged / returned is the one returned by the client function.
0/5 if we have to be so meticulous though
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like it meticulous. Say somebody remove the forceLogoutIfNecessary by accident, that meticulousness will flag and bring a discussion if that removal is necessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
expect(getByTestId('draft_input')).toBeTruthy(); | ||
expect(getByTestId('draft_input.post.input')).toBeTruthy(); | ||
expect(getByTestId('draft_input.quick_actions')).toBeTruthy(); | ||
expect(getByTestId('draft_input.send_action.send.button')).toBeTruthy(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any strong opinion on why to use toBeTruthy
and not toBeVisible
? In theory toBeVisible
will check a few more interesting things, like that it has a size, but 0/5.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. Updated elsewhere as well.
it('sends message on press', () => { | ||
const {getByTestId} = renderWithEverything(<DraftInput {...baseProps}/>, {database}); | ||
fireEvent.press(getByTestId('draft_input.send_action.send.button')); | ||
expect(baseProps.sendMessage).toHaveBeenCalled(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should you verify it has been called with no arguments?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
^^
Would it be beneficial to use toHaveBeenCalledWith() instead, and confirm sendMessage() is passing the right arguments?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
}); | ||
}); | ||
|
||
describe('Message Actions', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are a couple of details you are not testing. Mainly that you are passing the right action to the popups (i.e. openAsBottomSheet
and persistentNotificationsConfirmation
).
What could be done to test that is the following:
- Mock the implementation of
openAsBottomSheet
(and similar forpersistentNotificationsConfirmation
) like this:
jest.mocked(openAsBottomSheet).mockImplementation(({props: {onSchedule}}) => onSchedule(SCHEDULING_INFO))
- Verify send message is called
- Verify that if you had post priority, you still get the warning
This way you verify that if the bottom sheet (or the verification for priority messages) does what you expect them to do, your code does things right (in other words... you are passing the right functions into the right places).
|
||
const {getByTestId} = renderWithEverything(<DraftInput {...baseProps}/>, {database}); | ||
fireEvent(getByTestId('draft_input.send_action.send.button'), 'longPress'); | ||
expect(openAsBottomSheet).toHaveBeenCalledWith(expect.objectContaining({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just as sanity check, we could add here also a check to make sure the other function (sendMessage
if I am not mistaken) is not called.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
const {getByTestId} = renderWithEverything(<DraftInput {...props}/>, {database}); | ||
const sendButton = getByTestId('draft_input.send_action.send.button.disabled'); | ||
expect(sendButton).toBeTruthy(); | ||
expect(sendButton).toBeDisabled(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, just as sanity check, we could double check that both press and long press actions do not call the functions, since the button is disabled.
But that may be already covered by the button being marked as disabled...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be continued...
const {toJSON} = renderWithIntl( | ||
<SendAction {...baseProps}/>, | ||
); | ||
expect(toJSON()).toMatchSnapshot(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would love to not have this. But if you insist, please tell me in test words that the send button is there, or some sort of text like "Send"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done, replaced by specific tests.
@@ -7,6 +7,7 @@ import {View} from 'react-native'; | |||
import CompassIcon from '@components/compass_icon'; | |||
import TouchableWithFeedback from '@components/touchable_with_feedback'; | |||
import {useTheme} from '@context/theme'; | |||
import {usePreventDoubleTap} from '@hooks/utils'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment is for this entire file. And I'm still iffy on the standard.
What I understand is if it's an index.tsx file, then this is an observable file. Like database observable file.
If it's a presentation file, it would be named send_action.tsx.
- If the file is for send_action, why not call the component SendAction? It it's SendButton component, why not make the file name send_button.tsx?
When I search for SendAction, I was expecting this particular file to be showing in my search result, to make developer's experience a bit easier.
I'm still new with the mobile repo, and the conventions, but we need to establish it properly.
Open for discussion @larkox .
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Its an existing where I made a two line changes. The issues described above are existing, not new. A separate ticket can be filed for these unrelated changes.
} | ||
return {scheduledPost: created}; | ||
// TODO - record scheduled post in database here | ||
return {data: true, response}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would tend to agree with you @larkox, but apparently this is an else for createPost(), which also returns {data: true/false}.
// eslint-disable-next-line | ||
// @ts-ignore |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you need both?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, removed and used ts-expect-error, otherwise we'd need both.
expect(result).toBeDefined(); | ||
expect(result.error).toBeDefined(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Kinda redundant. If you have result.error, you know result is defined.
But it would be more beneficial to know the error is what you expect. Like 'database not found' or something.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
expect(result).toBeDefined(); | ||
expect(result.error).toBeUndefined(); | ||
expect(result.data).toBe(true); | ||
expect(result.response).toBeDefined(); | ||
if (result.response) { | ||
expect(result.response.id).toBe(scheduledPost.id); | ||
} | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above, I think you can skip result toBeDefined().
The check for result.response is unnecessary if you have already expect result.response toBeDefined() right?
I barely see any need for if-else statements in test. You're not creating conditions in your test, you're trying to paint a story of what you're expecting.
In this instance:
expect(result.response.id).toBe(scheduledPost.id);
negates the need to check if it's defined, or that result.response is true/exists.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
it('sends message on press', () => { | ||
const {getByTestId} = renderWithEverything(<DraftInput {...baseProps}/>, {database}); | ||
fireEvent.press(getByTestId('draft_input.send_action.send.button')); | ||
expect(baseProps.sendMessage).toHaveBeenCalled(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
^^
Would it be beneficial to use toHaveBeenCalledWith() instead, and confirm sendMessage() is passing the right arguments?
Summary
Integrated create scheduled post API
Ticket Link
Part of https://mattermost.atlassian.net/browse/MM-62279
Checklist
E2E iOS tests for PR
.Device Information
iOS emulator
Screenshots
Recording.at.2025-01-24.13.56.28.Cropped.in.2025-01-24.13.57.29.mp4
Release Note