Skip to content

Commit fda5dce

Browse files
authored
Merge branch 'develop' into feat/openapi-oauth-apps-create
2 parents afd1bf0 + cc95107 commit fda5dce

File tree

4 files changed

+177
-87
lines changed

4 files changed

+177
-87
lines changed
Lines changed: 65 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,91 +1,90 @@
1-
import { useUser, useUserSubscriptions, useRoomAvatarPath } from '@rocket.chat/ui-contexts';
1+
import { MockedAppRootBuilder } from '@rocket.chat/mock-providers/dist/MockedAppRootBuilder';
2+
import type { SubscriptionWithRoom } from '@rocket.chat/ui-contexts';
23
import { render, screen, waitFor } from '@testing-library/react';
34
import userEvent from '@testing-library/user-event';
45

56
import UserAndRoomAutoCompleteMultiple from './UserAndRoomAutoCompleteMultiple';
7+
import { createFakeSubscription, createFakeUser } from '../../../tests/mocks/data';
68
import { roomCoordinator } from '../../lib/rooms/roomCoordinator';
79

8-
// Mock dependencies
9-
jest.mock('@rocket.chat/ui-contexts', () => ({
10-
useUser: jest.fn(),
11-
useUserSubscriptions: jest.fn(),
12-
useRoomAvatarPath: jest.fn(),
13-
}));
10+
const user = createFakeUser({
11+
active: true,
12+
roles: ['admin'],
13+
type: 'user',
14+
});
15+
16+
const direct = createFakeSubscription({
17+
t: 'd',
18+
name: 'Direct',
19+
});
20+
21+
const channel = createFakeSubscription({
22+
t: 'c',
23+
name: 'General',
24+
});
25+
26+
const appRoot = new MockedAppRootBuilder()
27+
.withSubscriptions([
28+
{ ...direct, ro: false },
29+
{ ...channel, ro: true },
30+
] as unknown as SubscriptionWithRoom[])
31+
.withUser(user);
32+
1433
jest.mock('../../lib/rooms/roomCoordinator', () => ({
15-
roomCoordinator: { readOnly: jest.fn() },
34+
roomCoordinator: {
35+
readOnly: jest.fn(),
36+
},
1637
}));
1738

18-
const mockUser = { _id: 'user1', username: 'testuser' };
39+
beforeEach(() => {
40+
(roomCoordinator.readOnly as jest.Mock).mockReturnValue(false);
41+
});
1942

20-
const mockRooms = [
21-
{
22-
rid: 'room1',
23-
fname: 'General',
24-
name: 'general',
25-
t: 'c',
26-
avatarETag: 'etag1',
27-
},
28-
{
29-
rid: 'room2',
30-
fname: 'Direct',
31-
name: 'direct',
32-
t: 'd',
33-
avatarETag: 'etag2',
34-
blocked: false,
35-
blocker: false,
36-
},
37-
];
38-
39-
describe('UserAndRoomAutoCompleteMultiple', () => {
40-
beforeEach(() => {
41-
(useUser as jest.Mock).mockReturnValue(mockUser);
42-
(useUserSubscriptions as jest.Mock).mockReturnValue(mockRooms);
43-
(useRoomAvatarPath as jest.Mock).mockReturnValue((rid: string) => `/avatar/path/${rid}`);
44-
(roomCoordinator.readOnly as jest.Mock).mockReturnValue(false);
45-
});
43+
afterEach(() => jest.clearAllMocks());
4644

47-
it('should render options based on user subscriptions', async () => {
48-
render(<UserAndRoomAutoCompleteMultiple value={[]} onChange={jest.fn()} />);
45+
it('should render options based on user subscriptions', async () => {
46+
render(<UserAndRoomAutoCompleteMultiple value={[]} onChange={jest.fn()} />, { wrapper: appRoot.build() });
4947

50-
const input = screen.getByRole('textbox');
51-
await userEvent.click(input);
48+
const input = screen.getByRole('textbox');
49+
await userEvent.click(input);
5250

53-
await waitFor(() => {
54-
expect(screen.getByText('General')).toBeInTheDocument();
55-
});
51+
await waitFor(() => {
52+
expect(screen.getByText('Direct')).toBeInTheDocument();
53+
});
5654

57-
await waitFor(() => {
58-
expect(screen.getByText('Direct')).toBeInTheDocument();
59-
});
55+
await waitFor(() => {
56+
expect(screen.getByText('General')).toBeInTheDocument();
6057
});
58+
});
6159

62-
it('should filter out read-only rooms', async () => {
63-
(roomCoordinator.readOnly as jest.Mock).mockImplementation((rid) => rid === 'room1');
64-
render(<UserAndRoomAutoCompleteMultiple value={[]} onChange={jest.fn()} />);
60+
it('should filter out read-only rooms', async () => {
61+
(roomCoordinator.readOnly as jest.Mock).mockReturnValueOnce(true);
6562

66-
const input = screen.getByRole('textbox');
67-
await userEvent.click(input);
63+
render(<UserAndRoomAutoCompleteMultiple value={[]} onChange={jest.fn()} />, { wrapper: appRoot.build() });
6864

69-
await waitFor(() => {
70-
expect(screen.queryByText('General')).not.toBeInTheDocument();
71-
});
72-
await waitFor(() => {
73-
expect(screen.getByText('Direct')).toBeInTheDocument();
74-
});
65+
const input = screen.getByRole('textbox');
66+
await userEvent.click(input);
67+
68+
await waitFor(() => {
69+
expect(screen.getByText('General')).toBeInTheDocument();
7570
});
7671

77-
it('should call onChange when selecting an option', async () => {
78-
const handleChange = jest.fn();
79-
render(<UserAndRoomAutoCompleteMultiple value={[]} onChange={handleChange} />);
72+
await waitFor(() => {
73+
expect(screen.queryByText('Direct')).not.toBeInTheDocument();
74+
});
75+
});
8076

81-
const input = screen.getByRole('textbox');
82-
await userEvent.click(input);
77+
it('should call onChange when selecting an option', async () => {
78+
const handleChange = jest.fn();
79+
render(<UserAndRoomAutoCompleteMultiple value={[]} onChange={handleChange} />, { wrapper: appRoot.build() });
8380

84-
await waitFor(() => {
85-
expect(screen.getByText('General')).toBeInTheDocument();
86-
});
81+
const input = screen.getByRole('textbox');
82+
await userEvent.click(input);
8783

88-
await userEvent.click(screen.getByText('General'));
89-
expect(handleChange).toHaveBeenCalled();
84+
await waitFor(() => {
85+
expect(screen.getByText('General')).toBeInTheDocument();
9086
});
87+
88+
await userEvent.click(screen.getByText('General'));
89+
expect(handleChange).toHaveBeenCalled();
9190
});

packages/message-parser/src/grammar.pegjs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
unorderedList,
3535
timestamp,
3636
timestampFromHours,
37+
timestampFromIsoTime,
3738
} = require('./utils');
3839

3940
let skipBold = false;
@@ -83,17 +84,18 @@ TimestampType = "t" / "T" / "d" / "D" / "f" / "F" / "R"
8384

8485
Unixtime = d:Digit |10| { return d.join(''); }
8586

86-
TimestampHoursMinutesSeconds = hours:Digit |2| ":" minutes:Digit|2| ":" seconds:Digit |2| "Z"? { return timestampFromHours(hours.join(''), minutes.join(''), seconds.join('')); }
87+
TimestampHoursMinutesSeconds = hours:Digit |2| ":" minutes:Digit|2| ":" seconds:Digit |2| tz:Timezone? { return timestampFromHours(hours.join(''), minutes.join(''), seconds.join(''), tz); }
8788

88-
TimestampHoursMinutes = hours:Digit |2| ":" minutes:Digit|2| "Z"? { return timestampFromHours(hours.join(''), minutes.join('')); }
89+
TimestampHoursMinutes = hours:Digit |2| ":" minutes:Digit|2| tz:Timezone? { return timestampFromHours(hours.join(''), minutes.join(''),undefined, tz); }
8990

9091

9192
Timestamp = TimestampHoursMinutesSeconds / TimestampHoursMinutes
9293

94+
Timezone = offset:('+'/'-') tzHour: Digit |2| ':' tzMinute: Digit |2| { return `${offset}${tzHour.join('')}:${tzMinute.join('')}` }
9395

94-
ISO8601Date = year:Digit |4| "-" month:Digit |2| "-" day:Digit |2| "T" hours:Digit |2| ":" minutes:Digit|2| ":" seconds:Digit |2| "." milliseconds:Digit |3| "Z"? { return new Date(year.join('') + '-' + month.join('') + '-' + day.join('') + 'T' + hours.join('') + ':' + minutes.join('') + ':' + seconds.join('') + '.' + milliseconds.join('') + 'Z').getTime().toString(); }
96+
ISO8601Date = year:Digit |4| "-" month:Digit |2| "-" day:Digit |2| "T" hours:Digit |2| ":" minutes:Digit|2| ":" seconds:Digit |2| "." milliseconds:Digit |3| tz:Timezone? { return timestampFromIsoTime({year: year.join(''), month: month.join(''), day: day.join(''), hours: hours.join(''), minutes: minutes.join(''), seconds: seconds.join(''), milliseconds: milliseconds.join(''), timezone: tz}) }
9597

96-
ISO8601DateWithoutMilliseconds = year:Digit |4| "-" month:Digit |2| "-" day:Digit |2| "T" hours:Digit |2| ":" minutes:Digit|2| ":" seconds:Digit |2| "Z"? { return new Date(year.join('') + '-' + month.join('') + '-' + day.join('') + 'T' + hours.join('') + ':' + minutes.join('') + ':' + seconds.join('') + 'Z').getTime().toString(); }
98+
ISO8601DateWithoutMilliseconds = year:Digit |4| "-" month:Digit |2| "-" day:Digit |2| "T" hours:Digit |2| ":" minutes:Digit|2| ":" seconds:Digit |2| tz:Timezone? { return timestampFromIsoTime({year: year.join(''), month: month.join(''), day: day.join(''), hours: hours.join(''), minutes: minutes.join(''), seconds: seconds.join(''), timezone: tz}) }
9799

98100

99101
TimestampRules = "<t:" date:(Unixtime / ISO8601Date / ISO8601DateWithoutMilliseconds / Timestamp) ":" format:TimestampType ">" { return timestamp(date, format); } / "<t:" date:(Unixtime / ISO8601Date / ISO8601DateWithoutMilliseconds / Timestamp) ">" { return timestamp(date); }

packages/message-parser/src/utils.ts

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -255,14 +255,48 @@ export const timestampFromHours = (
255255
hours: string,
256256
minutes = '00',
257257
seconds = '00',
258+
timezone = '',
258259
) => {
259260
const date = new Date();
260261

261262
const yearMonthDay = date.toISOString().split('T')[0];
262263

263-
return new Date(`${yearMonthDay}T${hours}:${minutes}:${seconds}Z`)
264-
.getTime()
265-
.toString();
264+
const timestamp =
265+
(new Date(
266+
`${yearMonthDay}T${hours}:${minutes}:${seconds}${timezone}`,
267+
).getTime() /
268+
1000) |
269+
0;
270+
271+
return timestamp.toString();
272+
};
273+
274+
export const timestampFromIsoTime = ({
275+
year,
276+
month,
277+
day,
278+
hours,
279+
minutes,
280+
seconds,
281+
milliseconds,
282+
timezone,
283+
}: {
284+
year: string[];
285+
month: string[];
286+
day: string[];
287+
hours: string[];
288+
minutes: string[];
289+
seconds: string[];
290+
milliseconds?: string[];
291+
timezone?: string;
292+
}) => {
293+
const date =
294+
(new Date(
295+
`${year}-${month}-${day}T${hours}:${minutes}:${seconds}.${milliseconds || '000'}${timezone ? `${timezone}` : ''}`,
296+
).getTime() /
297+
1000) |
298+
0;
299+
return date.toString();
266300
};
267301

268302
export const extractFirstResult = (

packages/message-parser/tests/timestamp.test.ts

Lines changed: 69 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -35,34 +35,89 @@ test.each([
3535

3636
test.each([
3737
[
38-
'<t:2025-07-22T10:00:00.000Z:R>',
39-
[paragraph([timestamp('1753178400000', 'R')])],
38+
'<t:2025-07-22T10:00:00.000+00:00:R>',
39+
[
40+
paragraph([
41+
timestamp(
42+
(Date.parse('2025-07-22T10:00:00.000+00:00') / 1000).toString(),
43+
'R',
44+
),
45+
]),
46+
],
4047
],
4148
[
42-
'<t:2025-07-22T10:00:00.000Z:R>',
43-
[paragraph([timestamp('1753178400000', 'R')])],
49+
'<t:2025-07-22T10:00:00.000+00:00:R>',
50+
[
51+
paragraph([
52+
timestamp(
53+
(Date.parse('2025-07-22T10:00:00.000+00:00') / 1000).toString(),
54+
'R',
55+
),
56+
]),
57+
],
4458
],
4559
[
46-
'<t:2025-07-22T10:00:00.000:R>',
47-
[paragraph([timestamp('1753178400000', 'R')])],
60+
'<t:2025-07-22T10:00:00.000+00:00:R>',
61+
[
62+
paragraph([
63+
timestamp(
64+
(Date.parse('2025-07-22T10:00:00.000+00:00') / 1000).toString(),
65+
'R',
66+
),
67+
]),
68+
],
4869
],
49-
['<t:2025-07-22T10:00:00:R>', [paragraph([timestamp('1753178400000', 'R')])]],
5070
[
51-
'<t:10:00:00:R>',
52-
[paragraph([timestamp(timestampFromHours('10', '00', '00'), 'R')])],
71+
'<t:2025-07-22T10:00:00+00:00:R>',
72+
[
73+
paragraph([
74+
timestamp(
75+
(Date.parse('2025-07-22T10:00:00+00:00') / 1000).toString(),
76+
'R',
77+
),
78+
]),
79+
],
5380
],
5481
[
55-
'<t:10:00:R>',
56-
[paragraph([timestamp(timestampFromHours('10', '00', '00'), 'R')])],
82+
'<t:10:00:00+00:00:R>',
83+
[
84+
paragraph([
85+
timestamp(timestampFromHours('10', '00', '00', '+00:00'), 'R'),
86+
]),
87+
],
5788
],
5889
[
59-
'<t:10:00:00>',
60-
[paragraph([timestamp(timestampFromHours('10', '00', '00'), 't')])],
90+
'<t:10:00+00:00:R>',
91+
[
92+
paragraph([
93+
timestamp(timestampFromHours('10', '00', '00', '+00:00'), 'R'),
94+
]),
95+
],
96+
],
97+
[
98+
'<t:10:00:05+00:00>',
99+
[
100+
paragraph([
101+
timestamp(timestampFromHours('10', '00', '05', '+00:00'), 't'),
102+
]),
103+
],
61104
],
62105
[
63-
'<t:10:00>',
106+
'<t:10:00+00:00>',
64107
[paragraph([timestamp(timestampFromHours('10', '00', '00'), 't')])],
65108
],
109+
110+
[
111+
'<t:2025-07-24T20:19:58.154+00:00:R>',
112+
[
113+
paragraph([
114+
timestamp(
115+
((Date.parse('2025-07-24T20:19:58.154+00:00') / 1000) | 0).toString(),
116+
'R',
117+
),
118+
]),
119+
],
120+
],
66121
])('parses %p', (input, output) => {
67122
expect(parse(input)).toMatchObject(output);
68123
});

0 commit comments

Comments
 (0)