Skip to content

Commit de5d6ac

Browse files
authored
feat: always clamp poll max_votes_allowed between 2 and 10 during the poll composition (#1688)
1 parent 4fd627d commit de5d6ac

2 files changed

Lines changed: 134 additions & 7 deletions

File tree

  • src/messageComposer/middleware/pollComposer
  • test/unit/MessageComposer/middleware/pollComposer

src/messageComposer/middleware/pollComposer/state.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,13 +106,23 @@ export const isTargetedOptionTextUpdate = (
106106
typeof (value as TargetedPollOptionTextUpdate)?.index === 'number' &&
107107
typeof (value as TargetedPollOptionTextUpdate)?.text === 'string';
108108

109+
const clampMaxVotesAllowed = (value: unknown): string => {
110+
if (value === '' || value == null) return '';
111+
const num = typeof value === 'string' ? parseInt(value, 10) : Number(value);
112+
if (!Number.isInteger(num) || Number.isNaN(num)) return '';
113+
return String(Math.min(10, Math.max(2, num)));
114+
};
115+
109116
export const pollCompositionStateProcessors: Partial<
110117
Record<keyof PollComposerState['data'], PollCompositionStateProcessor>
111118
> = {
112119
enforce_unique_vote: ({ value }) => ({
113120
enforce_unique_vote: value,
114121
max_votes_allowed: '',
115122
}),
123+
max_votes_allowed: ({ value }) => ({
124+
max_votes_allowed: clampMaxVotesAllowed(value),
125+
}),
116126
options: ({ value, data }) => {
117127
// If it's a direct array update (like drag-drop reordering)
118128
if (Array.isArray(value)) {

test/unit/MessageComposer/middleware/pollComposer/state.test.ts

Lines changed: 124 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -65,19 +65,136 @@ describe('PollComposerStateMiddleware', () => {
6565
expect(result.status).toBeUndefined;
6666
});
6767

68-
it('should validate max_votes_allowed field with invalid value', async () => {
68+
it('should clamp max_votes_allowed below 2 to 2', async () => {
6969
const stateMiddleware = setup();
7070
const result = await stateMiddleware.handlers.handleFieldChange(
7171
setupHandlerParams({
72-
nextState: { ...getInitialState() },
73-
previousState: { ...getInitialState() },
74-
targetFields: { max_votes_allowed: '1' }, // Invalid value (less than 2)
72+
nextState: {
73+
...getInitialState(),
74+
data: { ...getInitialState().data, enforce_unique_vote: false },
75+
},
76+
previousState: {
77+
...getInitialState(),
78+
data: { ...getInitialState().data, enforce_unique_vote: false },
79+
},
80+
targetFields: { max_votes_allowed: '1' },
7581
}),
7682
);
7783

78-
expect(result.state.nextState.errors.max_votes_allowed).toBeDefined();
79-
expect(result.state.nextState.data.max_votes_allowed).toBe('1');
80-
expect(result.status).toBeUndefined;
84+
expect(result.state.nextState.data.max_votes_allowed).toBe('2');
85+
expect(result.state.nextState.errors.max_votes_allowed).toBeUndefined();
86+
});
87+
88+
it('should clamp max_votes_allowed above 10 to 10', async () => {
89+
const stateMiddleware = setup();
90+
const result = await stateMiddleware.handlers.handleFieldChange(
91+
setupHandlerParams({
92+
nextState: {
93+
...getInitialState(),
94+
data: { ...getInitialState().data, enforce_unique_vote: false },
95+
},
96+
previousState: {
97+
...getInitialState(),
98+
data: { ...getInitialState().data, enforce_unique_vote: false },
99+
},
100+
targetFields: { max_votes_allowed: '15' },
101+
}),
102+
);
103+
104+
expect(result.state.nextState.data.max_votes_allowed).toBe('10');
105+
expect(result.state.nextState.errors.max_votes_allowed).toBeUndefined();
106+
});
107+
108+
it('should leave max_votes_allowed unchanged when between 2 and 10', async () => {
109+
const stateMiddleware = setup();
110+
const result = await stateMiddleware.handlers.handleFieldChange(
111+
setupHandlerParams({
112+
nextState: {
113+
...getInitialState(),
114+
data: { ...getInitialState().data, enforce_unique_vote: false },
115+
},
116+
previousState: {
117+
...getInitialState(),
118+
data: { ...getInitialState().data, enforce_unique_vote: false },
119+
},
120+
targetFields: { max_votes_allowed: '5' },
121+
}),
122+
);
123+
124+
expect(result.state.nextState.data.max_votes_allowed).toBe('5');
125+
expect(result.state.nextState.errors.max_votes_allowed).toBeUndefined();
126+
});
127+
128+
it('should keep max_votes_allowed boundaries 2 and 10 as-is', async () => {
129+
const stateMiddleware = setup();
130+
const resultTwo = await stateMiddleware.handlers.handleFieldChange(
131+
setupHandlerParams({
132+
nextState: {
133+
...getInitialState(),
134+
data: { ...getInitialState().data, enforce_unique_vote: false },
135+
},
136+
previousState: {
137+
...getInitialState(),
138+
data: { ...getInitialState().data, enforce_unique_vote: false },
139+
},
140+
targetFields: { max_votes_allowed: '2' },
141+
}),
142+
);
143+
const resultTen = await stateMiddleware.handlers.handleFieldChange(
144+
setupHandlerParams({
145+
nextState: {
146+
...getInitialState(),
147+
data: { ...getInitialState().data, enforce_unique_vote: false },
148+
},
149+
previousState: {
150+
...getInitialState(),
151+
data: { ...getInitialState().data, enforce_unique_vote: false },
152+
},
153+
targetFields: { max_votes_allowed: '10' },
154+
}),
155+
);
156+
157+
expect(resultTwo.state.nextState.data.max_votes_allowed).toBe('2');
158+
expect(resultTen.state.nextState.data.max_votes_allowed).toBe('10');
159+
});
160+
161+
it('should set max_votes_allowed to empty when value is empty', async () => {
162+
const stateMiddleware = setup();
163+
const result = await stateMiddleware.handlers.handleFieldChange(
164+
setupHandlerParams({
165+
nextState: {
166+
...getInitialState(),
167+
data: { ...getInitialState().data, enforce_unique_vote: false },
168+
},
169+
previousState: {
170+
...getInitialState(),
171+
data: { ...getInitialState().data, enforce_unique_vote: false },
172+
},
173+
targetFields: { max_votes_allowed: '' },
174+
}),
175+
);
176+
177+
expect(result.state.nextState.data.max_votes_allowed).toBe('');
178+
});
179+
180+
it('should set max_votes_allowed to empty when value is non-numeric', async () => {
181+
const stateMiddleware = setup();
182+
const result = await stateMiddleware.handlers.handleFieldChange(
183+
setupHandlerParams({
184+
nextState: {
185+
...getInitialState(),
186+
data: { ...getInitialState().data, enforce_unique_vote: false },
187+
},
188+
previousState: {
189+
...getInitialState(),
190+
data: { ...getInitialState().data, enforce_unique_vote: false },
191+
},
192+
targetFields: { max_votes_allowed: 'abc' },
193+
}),
194+
);
195+
196+
expect(result.state.nextState.data.max_votes_allowed).toBe('');
197+
expect(result.state.nextState.errors.max_votes_allowed).toBeUndefined();
81198
});
82199

83200
it('should not validate max_votes_allowed field with valid value if enforce_unique_vote is true', async () => {

0 commit comments

Comments
 (0)