Skip to content

Commit 1a26fc2

Browse files
authored
fix(editor): Add smart decimals directive (#14054)
1 parent 996026f commit 1a26fc2

File tree

4 files changed

+330
-0
lines changed

4 files changed

+330
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { smartDecimal } from './smartDecimal';
2+
3+
describe('smartDecimal', () => {
4+
it('should return the same value if it is an integer', () => {
5+
expect(smartDecimal(42)).toBe(42);
6+
});
7+
8+
it('should return the same value if it has only one decimal place', () => {
9+
expect(smartDecimal(42.5)).toBe(42.5);
10+
});
11+
12+
it('should round to two decimal places by default', () => {
13+
expect(smartDecimal(42.567)).toBe(42.57);
14+
});
15+
16+
it('should round to the specified number of decimal places', () => {
17+
expect(smartDecimal(42.567, 1)).toBe(42.6);
18+
});
19+
20+
it('should handle negative numbers correctly', () => {
21+
expect(smartDecimal(-42.567, 2)).toBe(-42.57);
22+
});
23+
24+
it('should handle zero correctly', () => {
25+
expect(smartDecimal(0)).toBe(0);
26+
});
27+
28+
it('should handle very small numbers correctly', () => {
29+
expect(smartDecimal(0.000567, 5)).toBe(0.00057);
30+
});
31+
32+
it('should round to two decimal if it is smaller than the given one', () => {
33+
expect(smartDecimal(42.56, 3)).toBe(42.56);
34+
});
35+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
export const smartDecimal = (value: number, decimals = 2): number => {
2+
// Check if integer
3+
if (Number.isInteger(value)) {
4+
return value;
5+
}
6+
7+
// Check if it has only one decimal place
8+
if (value.toString().split('.')[1].length <= decimals) {
9+
return value;
10+
}
11+
12+
return Number(value.toFixed(decimals));
13+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
import { render } from '@testing-library/vue';
2+
3+
import { n8nSmartDecimal } from './n8n-smart-decimal';
4+
5+
describe('Directive n8n-truncate', () => {
6+
it('should leave number as is without decimals', async () => {
7+
const { html } = render(
8+
{
9+
props: {
10+
text: {
11+
type: String,
12+
},
13+
},
14+
template: '<p v-n8n-smart-decimal>{{text}}</p>',
15+
},
16+
{
17+
props: {
18+
text: '42',
19+
},
20+
global: {
21+
directives: {
22+
n8nSmartDecimal,
23+
},
24+
},
25+
},
26+
);
27+
expect(html()).toBe('<p>42</p>');
28+
});
29+
30+
it('should leave number as is without decimals with binding arg', async () => {
31+
const { html } = render(
32+
{
33+
props: {
34+
text: {
35+
type: String,
36+
},
37+
},
38+
template: '<p v-n8n-smart-decimal:3>{{text}}</p>',
39+
},
40+
{
41+
props: {
42+
text: '42',
43+
},
44+
global: {
45+
directives: {
46+
n8nSmartDecimal,
47+
},
48+
},
49+
},
50+
);
51+
expect(html()).toBe('<p>42</p>');
52+
});
53+
54+
it('should leave the number with 1 decimal', async () => {
55+
const { html } = render(
56+
{
57+
props: {
58+
text: {
59+
type: String,
60+
},
61+
},
62+
template: '<p v-n8n-smart-decimal>{{text}}</p>',
63+
},
64+
{
65+
props: {
66+
text: '42.1',
67+
},
68+
global: {
69+
directives: {
70+
n8nSmartDecimal,
71+
},
72+
},
73+
},
74+
);
75+
expect(html()).toBe('<p>42.1</p>');
76+
});
77+
78+
it('should format number to 2 decimal places by default', async () => {
79+
const { html } = render(
80+
{
81+
props: {
82+
text: {
83+
type: String,
84+
},
85+
},
86+
template: '<p v-n8n-smart-decimal>{{text}}</p>',
87+
},
88+
{
89+
props: {
90+
text: '42.123456',
91+
},
92+
global: {
93+
directives: {
94+
n8nSmartDecimal,
95+
},
96+
},
97+
},
98+
);
99+
expect(html()).toBe('<p>42.12</p>');
100+
});
101+
102+
it('should format number to 1 decimal place', async () => {
103+
const { html } = render(
104+
{
105+
props: {
106+
text: {
107+
type: String,
108+
},
109+
},
110+
template: '<p v-n8n-smart-decimal:1>{{text}}</p>',
111+
},
112+
{
113+
props: {
114+
text: '42.123456',
115+
},
116+
global: {
117+
directives: {
118+
n8nSmartDecimal,
119+
},
120+
},
121+
},
122+
);
123+
expect(html()).toBe('<p>42.1</p>');
124+
});
125+
126+
it('should format number to 3 decimal places', async () => {
127+
const { html } = render(
128+
{
129+
props: {
130+
text: {
131+
type: String,
132+
},
133+
},
134+
template: '<p v-n8n-smart-decimal:3>{{text}}</p>',
135+
},
136+
{
137+
props: {
138+
text: '42.123456',
139+
},
140+
global: {
141+
directives: {
142+
n8nSmartDecimal,
143+
},
144+
},
145+
},
146+
);
147+
expect(html()).toBe('<p>42.123</p>');
148+
});
149+
150+
it('should handle negative numbers correctly', () => {
151+
const { html } = render(
152+
{
153+
props: {
154+
text: {
155+
type: String,
156+
},
157+
},
158+
template: '<p v-n8n-smart-decimal>{{text}}</p>',
159+
},
160+
{
161+
props: {
162+
text: '-42.123456',
163+
},
164+
global: {
165+
directives: {
166+
n8nSmartDecimal,
167+
},
168+
},
169+
},
170+
);
171+
expect(html()).toBe('<p>-42.12</p>');
172+
});
173+
174+
it('should handle zero correctly', () => {
175+
const { html } = render(
176+
{
177+
props: {
178+
text: {
179+
type: String,
180+
},
181+
},
182+
template: '<p v-n8n-smart-decimal>{{text}}</p>',
183+
},
184+
{
185+
props: {
186+
text: '0',
187+
},
188+
global: {
189+
directives: {
190+
n8nSmartDecimal,
191+
},
192+
},
193+
},
194+
);
195+
expect(html()).toBe('<p>0</p>');
196+
});
197+
198+
it('should handle very small numbers correctly', () => {
199+
const { html } = render(
200+
{
201+
props: {
202+
text: {
203+
type: String,
204+
},
205+
},
206+
template: '<p v-n8n-smart-decimal:5>{{text}}</p>',
207+
},
208+
{
209+
props: {
210+
text: '0.000567',
211+
},
212+
global: {
213+
directives: {
214+
n8nSmartDecimal,
215+
},
216+
},
217+
},
218+
);
219+
expect(html()).toBe('<p>0.00057</p>');
220+
});
221+
222+
it('should format number to 2 decimal places if that has fewer decimals than the desired', async () => {
223+
const { html } = render(
224+
{
225+
props: {
226+
text: {
227+
type: String,
228+
},
229+
},
230+
template: '<p v-n8n-smart-decimal:3>{{text}}</p>',
231+
},
232+
{
233+
props: {
234+
text: '42.12',
235+
},
236+
global: {
237+
directives: {
238+
n8nSmartDecimal,
239+
},
240+
},
241+
},
242+
);
243+
expect(html()).toBe('<p>42.12</p>');
244+
});
245+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { smartDecimal } from '@n8n/utils/number/smartDecimal';
2+
import type { DirectiveBinding, FunctionDirective } from 'vue';
3+
4+
/**
5+
* Custom directive `n8nSmartDecimal` to format numbers with smart decimal places.
6+
*
7+
* Usage:
8+
* In your Vue template, use the directive `v-n8n-smart-decimal` passing the number and optionally the decimal places.
9+
*
10+
* Example:
11+
* <p v-n8n-smart-decimal>42.567</p>
12+
*
13+
* Compiles to: <p>42.57</p>
14+
*
15+
* Example with specified decimal places:
16+
* <p v-n8n-smart-decimal:4>42.56789</p>
17+
*
18+
* Compiles to: <p>42.5679</p>
19+
*
20+
* Function Shorthand:
21+
* https://vuejs.org/guide/reusability/custom-directives#function-shorthand
22+
*
23+
* Hint: Do not use it on components
24+
* https://vuejs.org/guide/reusability/custom-directives#usage-on-components
25+
*/
26+
27+
export const n8nSmartDecimal: FunctionDirective = (
28+
el: HTMLElement,
29+
binding: DirectiveBinding<number | undefined>,
30+
) => {
31+
const value = parseFloat(el.textContent ?? '');
32+
if (!isNaN(value)) {
33+
const decimals = isNaN(Number(binding.arg)) ? undefined : Number(binding.arg);
34+
const formattedValue = smartDecimal(value, decimals);
35+
el.textContent = formattedValue.toString();
36+
}
37+
};

0 commit comments

Comments
 (0)