Skip to content
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

Open
wants to merge 86 commits into
base: feature_schedule_posts
Choose a base branch
from

Conversation

harshilsharma63
Copy link
Member

@harshilsharma63 harshilsharma63 commented Jan 24, 2025

Summary

Integrated create scheduled post API

Ticket Link

Part of https://mattermost.atlassian.net/browse/MM-62279

Checklist

  • Added or updated unit tests (required for all new features)
  • Has UI changes
  • Includes text changes and localization file updates
  • Have tested against the 5 core themes to ensure consistency between them.
  • Have run E2E tests by adding label 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

None

@amyblais amyblais added this to the v2.26.0 milestone Jan 31, 2025
Copy link
Contributor

@larkox larkox left a 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};
Copy link
Contributor

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.

Copy link
Contributor

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}.

Copy link
Member Author

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> {
Copy link
Contributor

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.

Copy link
Member Author

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.

Comment on lines 69 to 70
expect(result).toBeDefined();
expect(result.error).toBeDefined();
Copy link
Contributor

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

Copy link
Contributor

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.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Comment on lines 81 to 84
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();
Copy link
Contributor

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.

Copy link
Member Author

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();
Copy link
Contributor

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?

Copy link
Contributor

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?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

});
});

describe('Message Actions', () => {
Copy link
Contributor

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 for persistentNotificationsConfirmation) 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({
Copy link
Contributor

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.

Copy link
Member Author

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();
Copy link
Contributor

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...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Copy link
Contributor

@rahimrahman rahimrahman left a 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();
Copy link
Contributor

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"?

Copy link
Member Author

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';
Copy link
Contributor

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.

  1. 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 .

Copy link
Member Author

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};
Copy link
Contributor

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}.

Comment on lines 33 to 34
// eslint-disable-next-line
// @ts-ignore
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you need both?

Copy link
Member Author

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.

Comment on lines 50 to 51
expect(result).toBeDefined();
expect(result.error).toBeDefined();
Copy link
Contributor

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.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Comment on lines 57 to 64
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);
}
});
Copy link
Contributor

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.

Copy link
Member Author

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();
Copy link
Contributor

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants