fix(editor): Add smart decimals directive (#14054)

This commit is contained in:
Csaba Tuncsik
2025-03-20 10:52:51 +01:00
committed by GitHub
parent 996026f358
commit 1a26fc2762
4 changed files with 330 additions and 0 deletions

View File

@@ -0,0 +1,35 @@
import { smartDecimal } from './smartDecimal';
describe('smartDecimal', () => {
it('should return the same value if it is an integer', () => {
expect(smartDecimal(42)).toBe(42);
});
it('should return the same value if it has only one decimal place', () => {
expect(smartDecimal(42.5)).toBe(42.5);
});
it('should round to two decimal places by default', () => {
expect(smartDecimal(42.567)).toBe(42.57);
});
it('should round to the specified number of decimal places', () => {
expect(smartDecimal(42.567, 1)).toBe(42.6);
});
it('should handle negative numbers correctly', () => {
expect(smartDecimal(-42.567, 2)).toBe(-42.57);
});
it('should handle zero correctly', () => {
expect(smartDecimal(0)).toBe(0);
});
it('should handle very small numbers correctly', () => {
expect(smartDecimal(0.000567, 5)).toBe(0.00057);
});
it('should round to two decimal if it is smaller than the given one', () => {
expect(smartDecimal(42.56, 3)).toBe(42.56);
});
});

View File

@@ -0,0 +1,13 @@
export const smartDecimal = (value: number, decimals = 2): number => {
// Check if integer
if (Number.isInteger(value)) {
return value;
}
// Check if it has only one decimal place
if (value.toString().split('.')[1].length <= decimals) {
return value;
}
return Number(value.toFixed(decimals));
};

View File

@@ -0,0 +1,245 @@
import { render } from '@testing-library/vue';
import { n8nSmartDecimal } from './n8n-smart-decimal';
describe('Directive n8n-truncate', () => {
it('should leave number as is without decimals', async () => {
const { html } = render(
{
props: {
text: {
type: String,
},
},
template: '<p v-n8n-smart-decimal>{{text}}</p>',
},
{
props: {
text: '42',
},
global: {
directives: {
n8nSmartDecimal,
},
},
},
);
expect(html()).toBe('<p>42</p>');
});
it('should leave number as is without decimals with binding arg', async () => {
const { html } = render(
{
props: {
text: {
type: String,
},
},
template: '<p v-n8n-smart-decimal:3>{{text}}</p>',
},
{
props: {
text: '42',
},
global: {
directives: {
n8nSmartDecimal,
},
},
},
);
expect(html()).toBe('<p>42</p>');
});
it('should leave the number with 1 decimal', async () => {
const { html } = render(
{
props: {
text: {
type: String,
},
},
template: '<p v-n8n-smart-decimal>{{text}}</p>',
},
{
props: {
text: '42.1',
},
global: {
directives: {
n8nSmartDecimal,
},
},
},
);
expect(html()).toBe('<p>42.1</p>');
});
it('should format number to 2 decimal places by default', async () => {
const { html } = render(
{
props: {
text: {
type: String,
},
},
template: '<p v-n8n-smart-decimal>{{text}}</p>',
},
{
props: {
text: '42.123456',
},
global: {
directives: {
n8nSmartDecimal,
},
},
},
);
expect(html()).toBe('<p>42.12</p>');
});
it('should format number to 1 decimal place', async () => {
const { html } = render(
{
props: {
text: {
type: String,
},
},
template: '<p v-n8n-smart-decimal:1>{{text}}</p>',
},
{
props: {
text: '42.123456',
},
global: {
directives: {
n8nSmartDecimal,
},
},
},
);
expect(html()).toBe('<p>42.1</p>');
});
it('should format number to 3 decimal places', async () => {
const { html } = render(
{
props: {
text: {
type: String,
},
},
template: '<p v-n8n-smart-decimal:3>{{text}}</p>',
},
{
props: {
text: '42.123456',
},
global: {
directives: {
n8nSmartDecimal,
},
},
},
);
expect(html()).toBe('<p>42.123</p>');
});
it('should handle negative numbers correctly', () => {
const { html } = render(
{
props: {
text: {
type: String,
},
},
template: '<p v-n8n-smart-decimal>{{text}}</p>',
},
{
props: {
text: '-42.123456',
},
global: {
directives: {
n8nSmartDecimal,
},
},
},
);
expect(html()).toBe('<p>-42.12</p>');
});
it('should handle zero correctly', () => {
const { html } = render(
{
props: {
text: {
type: String,
},
},
template: '<p v-n8n-smart-decimal>{{text}}</p>',
},
{
props: {
text: '0',
},
global: {
directives: {
n8nSmartDecimal,
},
},
},
);
expect(html()).toBe('<p>0</p>');
});
it('should handle very small numbers correctly', () => {
const { html } = render(
{
props: {
text: {
type: String,
},
},
template: '<p v-n8n-smart-decimal:5>{{text}}</p>',
},
{
props: {
text: '0.000567',
},
global: {
directives: {
n8nSmartDecimal,
},
},
},
);
expect(html()).toBe('<p>0.00057</p>');
});
it('should format number to 2 decimal places if that has fewer decimals than the desired', async () => {
const { html } = render(
{
props: {
text: {
type: String,
},
},
template: '<p v-n8n-smart-decimal:3>{{text}}</p>',
},
{
props: {
text: '42.12',
},
global: {
directives: {
n8nSmartDecimal,
},
},
},
);
expect(html()).toBe('<p>42.12</p>');
});
});

View File

@@ -0,0 +1,37 @@
import { smartDecimal } from '@n8n/utils/number/smartDecimal';
import type { DirectiveBinding, FunctionDirective } from 'vue';
/**
* Custom directive `n8nSmartDecimal` to format numbers with smart decimal places.
*
* Usage:
* In your Vue template, use the directive `v-n8n-smart-decimal` passing the number and optionally the decimal places.
*
* Example:
* <p v-n8n-smart-decimal>42.567</p>
*
* Compiles to: <p>42.57</p>
*
* Example with specified decimal places:
* <p v-n8n-smart-decimal:4>42.56789</p>
*
* Compiles to: <p>42.5679</p>
*
* Function Shorthand:
* https://vuejs.org/guide/reusability/custom-directives#function-shorthand
*
* Hint: Do not use it on components
* https://vuejs.org/guide/reusability/custom-directives#usage-on-components
*/
export const n8nSmartDecimal: FunctionDirective = (
el: HTMLElement,
binding: DirectiveBinding<number | undefined>,
) => {
const value = parseFloat(el.textContent ?? '');
if (!isNaN(value)) {
const decimals = isNaN(Number(binding.arg)) ? undefined : Number(binding.arg);
const formattedValue = smartDecimal(value, decimals);
el.textContent = formattedValue.toString();
}
};