Skip to content

Commit 16c2a2c

Browse files
committed
some more adjustment of allowNegativeChanged
1 parent 2699b8f commit 16c2a2c

5 files changed

Lines changed: 75 additions & 73 deletions

File tree

examples/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ const ExampleForm = ({
6969
<br />
7070
<input type="checkbox" name="disableSelectionHandling" defaultChecked={disableSelectionHandling} /> Disable selection handling
7171
<br />
72-
{/* <input type="text" name="value" placeholder="Value" defaultValue={value} />Value<br /> */}
72+
<input type="text" name="value" placeholder="Value" defaultValue={value} />Value<br />
7373
<input
7474
type="text"
7575
name="decimalSeparator"
@@ -163,7 +163,7 @@ function FormContainer() {
163163
setDisableSelectionHandling(
164164
document.getElementsByName("disableSelectionHandling")[0].checked
165165
);
166-
// setValue(document.getElementsByName('value')[0].value);
166+
setValue(document.getElementsByName('value')[0].value);
167167
setDecimalSeparator(document.getElementsByName("decimalSeparator")[0].value);
168168
setThousandSeparator(
169169
document.getElementsByName("thousandSeparator")[0].value

playwright.config.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@ export default defineConfig({
4646
{
4747
name: 'controlled-value tests',
4848
testMatch: '**/controlled-value.spec.ts',
49+
dependencies: ['base tests'],
50+
},
51+
{
52+
name: 'input-type tests',
53+
testMatch: '**/input-type.spec.ts',
54+
dependencies: ['base tests'],
4955
},
5056
{
5157
name: 'chromium',

src/index.tsx

Lines changed: 39 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -125,22 +125,23 @@ class CurrencyInput extends React.Component<CurrencyInputProps, CurrencyInputSta
125125
* General function used to cleanup and define the final props used for rendering
126126
* @returns CurrencyInputState
127127
*/
128-
static prepareProps({
129-
onChangeEvent,
130-
value: propValue,
131-
decimalSeparator,
132-
thousandSeparator,
133-
precision,
134-
inputType,
135-
allowNegative,
136-
allowEmpty,
137-
prefix,
138-
suffix,
139-
selectAllOnFocus,
140-
autoFocus,
141-
disableSelectionHandling: propDisableSelectionHandling,
142-
...customProps
143-
}: Readonly<CurrencyInputProps>): CurrencyInputState {
128+
static prepareProps(props: Readonly<CurrencyInputProps>): CurrencyInputState {
129+
const {
130+
onChangeEvent,
131+
value: propValue,
132+
decimalSeparator,
133+
thousandSeparator,
134+
precision,
135+
inputType,
136+
allowNegative,
137+
allowEmpty,
138+
prefix,
139+
suffix,
140+
selectAllOnFocus,
141+
autoFocus,
142+
disableSelectionHandling: propDisableSelectionHandling,
143+
...customProps
144+
} = props;
144145
let initialValue = propValue;
145146
if (initialValue === null) {
146147
initialValue = allowEmpty ? null : '';
@@ -167,8 +168,7 @@ class CurrencyInput extends React.Component<CurrencyInputProps, CurrencyInputSta
167168
);
168169

169170
const disableSelectionHandling = propDisableSelectionHandling || inputType === 'number';
170-
// Note: previousProps will be set in getDerivedStateFromProps
171-
return { maskedValue, value, customProps, disableSelectionHandling };
171+
return { maskedValue, value, customProps, disableSelectionHandling, previousProps: props };
172172
}
173173

174174
/**
@@ -184,11 +184,11 @@ class CurrencyInput extends React.Component<CurrencyInputProps, CurrencyInputSta
184184
* @see https://reactjs.org/docs/react-component.html#static-getderivedstatefromprops
185185
*/
186186
static getDerivedStateFromProps(nextProps: Readonly<CurrencyInputProps>, prevState: Readonly<CurrencyInputState>) {
187-
const previousProps = prevState.previousProps || nextProps; // First call has no previous props
187+
const previousProps = prevState.previousProps || nextProps; // First call uses the initial props snapshot
188188

189189
// Check if the VALUE prop itself changed (parent is controlling the input)
190190
const valueChanged = nextProps.value !== previousProps.value;
191-
191+
192192
// Check if separators or display formatting changed (these require reformatting the current value)
193193
const formatChanged =
194194
nextProps.decimalSeparator !== previousProps.decimalSeparator ||
@@ -202,7 +202,7 @@ class CurrencyInput extends React.Component<CurrencyInputProps, CurrencyInputSta
202202
const newState = CurrencyInput.prepareProps(nextProps);
203203
return { ...newState, previousProps: nextProps };
204204
}
205-
205+
206206
if (formatChanged) {
207207
// Display formatting changed - reformat the current value with new formatting
208208
const propsWithCurrentValue = { ...nextProps, value: prevState.value };
@@ -212,16 +212,25 @@ class CurrencyInput extends React.Component<CurrencyInputProps, CurrencyInputSta
212212

213213
// Check if allowNegative changed
214214
const allowNegativeChanged = nextProps.allowNegative !== previousProps.allowNegative;
215-
216-
if (allowNegativeChanged && !nextProps.allowNegative) {
217-
// allowNegative was disabled
218-
// If current value is negative, make it positive
219-
const currentValue = typeof prevState.value === 'number' ? prevState.value : 0;
220-
if (currentValue < 0) {
221-
const propsWithPositiveValue = { ...nextProps, value: Math.abs(currentValue) };
222-
const newState = CurrencyInput.prepareProps(propsWithPositiveValue);
223-
return { ...newState, previousProps: nextProps };
215+
216+
if (allowNegativeChanged) {
217+
if (!nextProps.allowNegative) {
218+
// allowNegative was disabled
219+
// If current value is negative, make it positive
220+
const parsedValue = typeof prevState.value === 'number'
221+
? prevState.value
222+
: prevState.value === null
223+
? 0
224+
: CurrencyInput.stringValueToFloat(String(prevState.value), nextProps.thousandSeparator, nextProps.decimalSeparator);
225+
226+
if (parsedValue < 0) {
227+
const propsWithPositiveValue = { ...nextProps, value: Math.abs(parsedValue) };
228+
const newState = CurrencyInput.prepareProps(propsWithPositiveValue);
229+
return { ...newState, previousProps: nextProps };
230+
}
224231
}
232+
// allowNegative toggled on or no sign change needed: still advance previousProps
233+
return { ...prevState, previousProps: nextProps };
225234
}
226235

227236
// Other props changed but value and display formatting didn't

tests/base.spec.ts

Lines changed: 22 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ test.describe('base tests', () => {
1111
});
1212

1313
test('sanity startup', async ({ page }) => {
14-
const currencyInput = await page.locator('#currency-input');
14+
const currencyInput = page.locator('#currency-input');
1515
await expect(currencyInput).toHaveValue('$0.00 USD');
1616
});
1717

@@ -96,8 +96,8 @@ test.describe('component parameters', () => {
9696
await decimalInput.fill(',');
9797
await applyBtn.click();
9898

99-
await page.waitForTimeout(100);
10099
const currencyInput = page.locator('#currency-input');
100+
await expect(currencyInput).toHaveValue('0,00');
101101
await currencyInput.focus();
102102
await currencyInput.fill('');
103103
await currencyInput.type('12345');
@@ -120,8 +120,8 @@ test.describe('component parameters', () => {
120120
await precisionInput.fill('2');
121121
await applyBtn.click();
122122

123-
await page.waitForTimeout(100);
124123
const currencyInput = page.locator('#currency-input');
124+
await expect(currencyInput).toHaveValue('0,00');
125125
await currencyInput.focus();
126126
await currencyInput.fill('');
127127
await currencyInput.type('1234567');
@@ -145,8 +145,8 @@ test.describe('component parameters', () => {
145145
await precisionInput.fill('0');
146146
await applyBtn.click();
147147

148-
await page.waitForTimeout(100);
149148
const currencyInput = page.locator('#currency-input');
149+
await expect(currencyInput).toHaveValue('0');
150150
await currencyInput.focus();
151151
await currencyInput.fill('');
152152
await currencyInput.type('12345');
@@ -167,8 +167,8 @@ test.describe('component parameters', () => {
167167
await precisionInput.fill('3');
168168
await applyBtn.click();
169169

170-
await page.waitForTimeout(100);
171170
const currencyInput = page.locator('#currency-input');
171+
await expect(currencyInput).toHaveValue('0.000');
172172
await currencyInput.focus();
173173
await currencyInput.fill('');
174174
await currencyInput.type('12345');
@@ -194,26 +194,24 @@ test.describe('component parameters', () => {
194194
// Enable allowNegative via form control
195195
const allowNegativeCheckbox = page.locator('[name=allowNegative]');
196196
const applyBtn = page.locator('[name=apply]');
197+
const currencyInput = page.locator('#currency-input');
197198

198199
await allowNegativeCheckbox.check();
199200
await applyBtn.click();
200201

201-
// Wait for the state to update and component to re-render
202-
await page.waitForTimeout(200);
203-
204-
const currencyInput = page.locator('#currency-input');
202+
await expect(currencyInput).toHaveValue('$0.00 USD');
205203
await currencyInput.focus();
206204
await currencyInput.selectText();
207-
205+
208206
// First input a number (can't have negative zero)
209207
await currencyInput.pressSequentially('50');
210-
208+
211209
let value = await currencyInput.inputValue();
212210
expect(value).toContain('0.50');
213-
211+
214212
// Now add the minus sign - should toggle the number to negative
215213
await currencyInput.press('Minus');
216-
214+
217215
// Should now contain minus sign
218216
value = await currencyInput.inputValue();
219217
expect(value).toContain('-');
@@ -223,27 +221,25 @@ test.describe('component parameters', () => {
223221
// First enable allowNegative
224222
const allowNegativeCheckbox = page.locator('[name=allowNegative]');
225223
const applyBtn = page.locator('[name=apply]');
224+
const currencyInput = page.locator('#currency-input');
226225

227226
await allowNegativeCheckbox.check();
228227
await applyBtn.click();
229-
await page.waitForTimeout(200);
230-
231-
const currencyInput = page.locator('#currency-input');
228+
await expect(currencyInput).toHaveValue('$0.00 USD');
232229
await currencyInput.focus();
233230
await currencyInput.selectText();
234-
231+
235232
// Input a negative number
236233
await currencyInput.pressSequentially('50');
237234
await currencyInput.press('Minus');
238-
235+
239236
let value = await currencyInput.inputValue();
240237
expect(value).toContain('-');
241-
238+
242239
// Now disable allowNegative
243240
await allowNegativeCheckbox.uncheck();
244241
await applyBtn.click();
245-
await page.waitForTimeout(200);
246-
242+
247243
// Value should now be positive
248244
value = await currencyInput.inputValue();
249245
expect(value).not.toContain('-');
@@ -275,18 +271,14 @@ test.describe('component parameters', () => {
275271
await selectAllCheckbox.check();
276272
await applyBtn.click();
277273

278-
await page.waitForTimeout(100);
279274
const currencyInput = page.locator('#currency-input');
280275
await currencyInput.focus();
281276

282-
// With selectAllOnFocus, all text should be selected
283-
// The selection should encompass the content
284-
const inputValue = await currencyInput.inputValue();
285-
const selectionStart = await currencyInput.evaluate((el: HTMLInputElement) => el.selectionStart);
286-
const selectionEnd = await currencyInput.evaluate((el: HTMLInputElement) => el.selectionEnd);
287-
288-
// Should have selected content
289-
expect(selectionEnd - selectionStart).toBeGreaterThan(0);
277+
await expect.poll(async () => {
278+
const selectionStart = await currencyInput.evaluate((el: HTMLInputElement) => el.selectionStart ?? 0);
279+
const selectionEnd = await currencyInput.evaluate((el: HTMLInputElement) => el.selectionEnd ?? 0);
280+
return selectionEnd - selectionStart;
281+
}).toBeGreaterThan(0);
290282
});
291283
});
292284

tests/controlled-value.spec.ts

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,7 @@ test.describe('controlled component (value prop updates)', () => {
1111
});
1212

1313
test('should update input value when value prop changes externally', async ({ page }) => {
14-
// This test simulates the reported bug:
15-
// A parent component changes the value prop (e.g., via a button click that calls setAmount(22.22))
16-
// and the CurrencyInput should reflect that change.
14+
// Simulate a parent-driven prop change by updating the form-controlled value field.
1715

1816
const currencyInput = page.locator('#currency-input');
1917

@@ -30,17 +28,14 @@ test.describe('controlled component (value prop updates)', () => {
3028
await expect(currencyInput).toHaveValue('$50.00 USD');
3129

3230
// Now change the value via the form control (simulating external prop change)
33-
const prefixInput = page.locator('[name=prefix]');
34-
const suffixInput = page.locator('[name=suffix]');
31+
const valueInput = page.locator('[name=value]');
3532
const applyBtn = page.locator('[name=apply]');
3633

37-
// Set a different prefix/suffix to force a refresh, but keep the same value
38-
// The component should preserve the currently formatted value
39-
await prefixInput.fill('$');
40-
await suffixInput.fill(' USD');
34+
// Simulate a parent setting the controlled value to 22.22
35+
await valueInput.fill('22.22');
4136
await applyBtn.click();
4237

43-
// Input should still show the value it had
44-
await expect(currencyInput).toHaveValue('$50.00 USD');
38+
// Input should reflect the externally provided value (22.22)
39+
await expect(currencyInput).toHaveValue('$22.22 USD');
4540
});
4641
});

0 commit comments

Comments
 (0)