@cccsaurora/howler-ui 2.19.0-dev.931 → 2.19.0-dev.938
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/components/app/App.js
CHANGED
|
@@ -54,6 +54,7 @@ import dayjs from 'dayjs';
|
|
|
54
54
|
import 'dayjs/locale/fr-ca';
|
|
55
55
|
import duration from 'dayjs/plugin/duration';
|
|
56
56
|
import localizedFormat from 'dayjs/plugin/localizedFormat';
|
|
57
|
+
import minMax from 'dayjs/plugin/minMax';
|
|
57
58
|
import relativeTime from 'dayjs/plugin/relativeTime';
|
|
58
59
|
import utc from 'dayjs/plugin/utc';
|
|
59
60
|
import i18n from '@cccsaurora/howler-ui/i18n';
|
|
@@ -85,6 +86,7 @@ dayjs.extend(utc);
|
|
|
85
86
|
dayjs.extend(duration);
|
|
86
87
|
dayjs.extend(relativeTime);
|
|
87
88
|
dayjs.extend(localizedFormat);
|
|
89
|
+
dayjs.extend(minMax);
|
|
88
90
|
dayjs.locale(i18n.language === 'en' ? 'en' : 'fr-ca');
|
|
89
91
|
loader.config({ monaco });
|
|
90
92
|
const RoleRoute = ({ roles }) => {
|
|
@@ -11,16 +11,16 @@ import { useContextSelector } from 'use-context-selector';
|
|
|
11
11
|
const CustomSpan = () => {
|
|
12
12
|
const { t } = useTranslation();
|
|
13
13
|
const span = useContextSelector(ParameterContext, ctx => ctx.span);
|
|
14
|
-
const defaultStartDate = dayjs().subtract(2, 'days');
|
|
15
|
-
const defaultEndDate = dayjs().subtract(1, 'day');
|
|
16
|
-
const startDate = useContextSelector(ParameterContext, ctx => ctx.startDate ? dayjs(ctx.startDate) : defaultStartDate);
|
|
17
14
|
const setCustomSpan = useContextSelector(ParameterContext, ctx => ctx.setCustomSpan);
|
|
18
|
-
const
|
|
15
|
+
const startDate = useContextSelector(ParameterContext, ctx => (ctx.startDate ? dayjs(ctx.startDate) : null));
|
|
16
|
+
const endDate = useContextSelector(ParameterContext, ctx => (ctx.endDate ? dayjs(ctx.endDate) : null));
|
|
19
17
|
useEffect(() => {
|
|
20
|
-
if (span?.endsWith('custom')) {
|
|
21
|
-
|
|
18
|
+
if (span?.endsWith('custom') && (!startDate || !endDate)) {
|
|
19
|
+
const _startDate = startDate ?? dayjs().subtract(2, 'day');
|
|
20
|
+
const _endDate = endDate ?? dayjs().subtract(1, 'day');
|
|
21
|
+
setCustomSpan(dayjs.min(_startDate, _endDate).toISOString(), dayjs.max(_startDate, _endDate).toISOString());
|
|
22
22
|
}
|
|
23
23
|
}, [endDate, setCustomSpan, span, startDate]);
|
|
24
|
-
return span?.endsWith('custom') ? (_jsx(LocalizationProvider, { dateAdapter: AdapterDayjs, children: _jsxs(Stack, { direction: "row", spacing: 1, useFlexGap: true, flexWrap: "wrap", children: [_jsx(DateTimePicker, { sx: { minWidth: '175px', flexGrow: 1, marginTop: 1 }, slotProps: { textField: { size: 'small' } }, label: t('date.select.start'), value: startDate ? dayjs(startDate) : dayjs().subtract(1, 'days'),
|
|
24
|
+
return span?.endsWith('custom') ? (_jsx(LocalizationProvider, { dateAdapter: AdapterDayjs, children: _jsxs(Stack, { direction: "row", spacing: 1, useFlexGap: true, flexWrap: "wrap", children: [_jsx(DateTimePicker, { sx: { minWidth: '175px', flexGrow: 1, marginTop: 1 }, slotProps: { textField: { size: 'small' } }, label: t('date.select.start'), value: startDate ? dayjs(startDate) : dayjs().subtract(1, 'days'), maxDateTime: endDate, onChange: (newStartDate) => setCustomSpan(newStartDate.toISOString(), endDate.toISOString()), ampm: false, disableFuture: true }), _jsx(DateTimePicker, { sx: { minWidth: '175px', flexGrow: 1, marginTop: 1 }, slotProps: { textField: { size: 'small' } }, label: t('date.select.end'), value: endDate, minDateTime: startDate, onChange: (newEndDate) => setCustomSpan(startDate.toISOString(), newEndDate.toISOString()), ampm: false, disableFuture: true })] }) })) : null;
|
|
25
25
|
};
|
|
26
26
|
export default memo(CustomSpan);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useContext } from 'react';
|
|
3
|
+
vi.mock('use-context-selector', async () => {
|
|
4
|
+
const actual = await vi.importActual('use-context-selector');
|
|
5
|
+
return {
|
|
6
|
+
...actual,
|
|
7
|
+
createContext,
|
|
8
|
+
useContextSelector: (_context, selector) => selector(useContext(_context))
|
|
9
|
+
};
|
|
10
|
+
});
|
|
11
|
+
vi.mock('react-i18next', () => ({
|
|
12
|
+
useTranslation: () => ({ t: (key) => key })
|
|
13
|
+
}));
|
|
14
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
|
15
|
+
import { ParameterContext } from '@cccsaurora/howler-ui/components/app/providers/ParameterProvider';
|
|
16
|
+
import dayjs from 'dayjs';
|
|
17
|
+
import minMax from 'dayjs/plugin/minMax';
|
|
18
|
+
import {} from 'react';
|
|
19
|
+
dayjs.extend(minMax);
|
|
20
|
+
globalThis.IS_REACT_ACT_ENVIRONMENT = true;
|
|
21
|
+
// Import after mocks
|
|
22
|
+
import CustomSpan from './CustomSpan';
|
|
23
|
+
const mockSetCustomSpan = vi.fn();
|
|
24
|
+
const defaultCtx = {
|
|
25
|
+
span: 'date.range.custom',
|
|
26
|
+
startDate: undefined,
|
|
27
|
+
endDate: undefined,
|
|
28
|
+
setCustomSpan: mockSetCustomSpan
|
|
29
|
+
};
|
|
30
|
+
const makeWrapper = (ctx) => function ({ children }) {
|
|
31
|
+
return (_jsx(ParameterContext.Provider, { value: { ...defaultCtx, ...ctx }, children: children }));
|
|
32
|
+
};
|
|
33
|
+
describe('CustomSpan', () => {
|
|
34
|
+
beforeEach(() => {
|
|
35
|
+
vi.clearAllMocks();
|
|
36
|
+
});
|
|
37
|
+
describe('useEffect – date initialisation', () => {
|
|
38
|
+
it('should set both dates to defaults when both are null', async () => {
|
|
39
|
+
render(_jsx(CustomSpan, {}), {
|
|
40
|
+
wrapper: makeWrapper({ span: 'date.range.custom', startDate: undefined, endDate: undefined })
|
|
41
|
+
});
|
|
42
|
+
await waitFor(() => expect(mockSetCustomSpan).toHaveBeenCalledOnce());
|
|
43
|
+
const [start, end] = mockSetCustomSpan.mock.calls[0];
|
|
44
|
+
const startDate = dayjs(start);
|
|
45
|
+
const endDate = dayjs(end);
|
|
46
|
+
expect(startDate.isBefore(endDate)).toBe(true);
|
|
47
|
+
expect(dayjs().diff(startDate, 'hour')).toBeGreaterThanOrEqual(47);
|
|
48
|
+
expect(dayjs().diff(startDate, 'hour')).toBeLessThanOrEqual(49);
|
|
49
|
+
expect(dayjs().diff(endDate, 'hour')).toBeGreaterThanOrEqual(23);
|
|
50
|
+
expect(dayjs().diff(endDate, 'hour')).toBeLessThanOrEqual(25);
|
|
51
|
+
});
|
|
52
|
+
it('should set default start date when only endDate is provided', async () => {
|
|
53
|
+
// Use a recent end date that's after the default start (now - 2 days)
|
|
54
|
+
const existingEnd = dayjs().subtract(1, 'hour').toISOString();
|
|
55
|
+
render(_jsx(CustomSpan, {}), {
|
|
56
|
+
wrapper: makeWrapper({ span: 'date.range.custom', startDate: undefined, endDate: existingEnd })
|
|
57
|
+
});
|
|
58
|
+
await waitFor(() => expect(mockSetCustomSpan).toHaveBeenCalledOnce());
|
|
59
|
+
const [start, end] = mockSetCustomSpan.mock.calls[0];
|
|
60
|
+
expect(end).toBe(existingEnd);
|
|
61
|
+
expect(dayjs(start).isBefore(dayjs(end))).toBe(true);
|
|
62
|
+
});
|
|
63
|
+
it('should set default end date when only startDate is provided', async () => {
|
|
64
|
+
// Use a recent start date that's before the default end (now - 1 day)
|
|
65
|
+
const existingStart = dayjs().subtract(3, 'day').toISOString();
|
|
66
|
+
render(_jsx(CustomSpan, {}), {
|
|
67
|
+
wrapper: makeWrapper({ span: 'date.range.custom', startDate: existingStart, endDate: undefined })
|
|
68
|
+
});
|
|
69
|
+
await waitFor(() => expect(mockSetCustomSpan).toHaveBeenCalledOnce());
|
|
70
|
+
const [start, end] = mockSetCustomSpan.mock.calls[0];
|
|
71
|
+
expect(start).toBe(existingStart);
|
|
72
|
+
expect(dayjs(end).isAfter(dayjs(start))).toBe(true);
|
|
73
|
+
});
|
|
74
|
+
it('should NOT call setCustomSpan when both dates are already set', () => {
|
|
75
|
+
render(_jsx(CustomSpan, {}), {
|
|
76
|
+
wrapper: makeWrapper({
|
|
77
|
+
span: 'date.range.custom',
|
|
78
|
+
startDate: dayjs().subtract(3, 'day').toISOString(),
|
|
79
|
+
endDate: dayjs().subtract(1, 'hour').toISOString()
|
|
80
|
+
})
|
|
81
|
+
});
|
|
82
|
+
expect(mockSetCustomSpan).not.toHaveBeenCalled();
|
|
83
|
+
});
|
|
84
|
+
it('should NOT call setCustomSpan when span is not custom', () => {
|
|
85
|
+
render(_jsx(CustomSpan, {}), {
|
|
86
|
+
wrapper: makeWrapper({ span: 'date.range.1.month', startDate: undefined, endDate: undefined })
|
|
87
|
+
});
|
|
88
|
+
expect(mockSetCustomSpan).not.toHaveBeenCalled();
|
|
89
|
+
});
|
|
90
|
+
it('should NOT call setCustomSpan when span is undefined', () => {
|
|
91
|
+
render(_jsx(CustomSpan, {}), {
|
|
92
|
+
wrapper: makeWrapper({ span: undefined, startDate: undefined, endDate: undefined })
|
|
93
|
+
});
|
|
94
|
+
expect(mockSetCustomSpan).not.toHaveBeenCalled();
|
|
95
|
+
});
|
|
96
|
+
it('should ensure startDate is before endDate even if existing startDate is after default endDate', async () => {
|
|
97
|
+
const futureStart = dayjs().add(1, 'hour').toISOString();
|
|
98
|
+
render(_jsx(CustomSpan, {}), {
|
|
99
|
+
wrapper: makeWrapper({ span: 'date.range.custom', startDate: futureStart, endDate: undefined })
|
|
100
|
+
});
|
|
101
|
+
await waitFor(() => expect(mockSetCustomSpan).toHaveBeenCalledOnce());
|
|
102
|
+
const [start, end] = mockSetCustomSpan.mock.calls[0];
|
|
103
|
+
expect(dayjs(start).isBefore(dayjs(end))).toBe(true);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
describe('rendering', () => {
|
|
107
|
+
it('should render nothing when span is not custom', () => {
|
|
108
|
+
const { container } = render(_jsx(CustomSpan, {}), {
|
|
109
|
+
wrapper: makeWrapper({ span: 'date.range.1.month' })
|
|
110
|
+
});
|
|
111
|
+
expect(container.innerHTML).toBe('');
|
|
112
|
+
});
|
|
113
|
+
it('should render nothing when span is undefined', () => {
|
|
114
|
+
const { container } = render(_jsx(CustomSpan, {}), {
|
|
115
|
+
wrapper: makeWrapper({ span: undefined })
|
|
116
|
+
});
|
|
117
|
+
expect(container.innerHTML).toBe('');
|
|
118
|
+
});
|
|
119
|
+
it('should render two date pickers when span is custom', () => {
|
|
120
|
+
render(_jsx(CustomSpan, {}), {
|
|
121
|
+
wrapper: makeWrapper({
|
|
122
|
+
span: 'date.range.custom',
|
|
123
|
+
startDate: dayjs().subtract(3, 'day').toISOString(),
|
|
124
|
+
endDate: dayjs().subtract(1, 'hour').toISOString()
|
|
125
|
+
})
|
|
126
|
+
});
|
|
127
|
+
expect(screen.getByLabelText('date.select.start')).toBeInTheDocument();
|
|
128
|
+
expect(screen.getByLabelText('date.select.end')).toBeInTheDocument();
|
|
129
|
+
});
|
|
130
|
+
it('should render pickers for any span ending with "custom"', () => {
|
|
131
|
+
render(_jsx(CustomSpan, {}), {
|
|
132
|
+
wrapper: makeWrapper({
|
|
133
|
+
span: 'my.prefix.custom',
|
|
134
|
+
startDate: dayjs().subtract(3, 'day').toISOString(),
|
|
135
|
+
endDate: dayjs().subtract(1, 'hour').toISOString()
|
|
136
|
+
})
|
|
137
|
+
});
|
|
138
|
+
expect(screen.getByLabelText('date.select.start')).toBeInTheDocument();
|
|
139
|
+
expect(screen.getByLabelText('date.select.end')).toBeInTheDocument();
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
describe('onChange handlers', () => {
|
|
143
|
+
it('should not call setCustomSpan when both dates are present on render', () => {
|
|
144
|
+
render(_jsx(CustomSpan, {}), {
|
|
145
|
+
wrapper: makeWrapper({
|
|
146
|
+
span: 'date.range.custom',
|
|
147
|
+
startDate: dayjs().subtract(3, 'day').toISOString(),
|
|
148
|
+
endDate: dayjs().subtract(1, 'hour').toISOString()
|
|
149
|
+
})
|
|
150
|
+
});
|
|
151
|
+
const startPicker = screen.getByLabelText('date.select.start');
|
|
152
|
+
expect(startPicker).toBeInTheDocument();
|
|
153
|
+
expect(mockSetCustomSpan).not.toHaveBeenCalled();
|
|
154
|
+
});
|
|
155
|
+
it('should render start picker with fallback value when startDate is null', async () => {
|
|
156
|
+
render(_jsx(CustomSpan, {}), {
|
|
157
|
+
wrapper: makeWrapper({
|
|
158
|
+
span: 'date.range.custom',
|
|
159
|
+
startDate: undefined,
|
|
160
|
+
endDate: dayjs().subtract(1, 'hour').toISOString()
|
|
161
|
+
})
|
|
162
|
+
});
|
|
163
|
+
// When startDate is null, the picker's value falls back to dayjs().subtract(1, 'days')
|
|
164
|
+
const startInput = screen.getByLabelText('date.select.start');
|
|
165
|
+
expect(startInput).toBeInTheDocument();
|
|
166
|
+
// The input should have a value (the fallback), not be empty
|
|
167
|
+
expect(startInput.value).not.toBe('');
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
});
|