mirror of
https://github.com/n8n-io/n8n.git
synced 2026-06-07 12:07:38 +02:00
165 lines
5.1 KiB
TypeScript
165 lines
5.1 KiB
TypeScript
import { describe, it, expect } from '@jest/globals';
|
|
|
|
import { RESERVED_KEYWORDS, toVarName, getVarName, getUniqueVarName } from './variable-names';
|
|
|
|
describe('variable-names', () => {
|
|
describe('RESERVED_KEYWORDS', () => {
|
|
it('includes JavaScript reserved words', () => {
|
|
expect(RESERVED_KEYWORDS.has('break')).toBe(true);
|
|
expect(RESERVED_KEYWORDS.has('case')).toBe(true);
|
|
expect(RESERVED_KEYWORDS.has('const')).toBe(true);
|
|
expect(RESERVED_KEYWORDS.has('function')).toBe(true);
|
|
expect(RESERVED_KEYWORDS.has('return')).toBe(true);
|
|
expect(RESERVED_KEYWORDS.has('while')).toBe(true);
|
|
});
|
|
|
|
it('includes JavaScript literals', () => {
|
|
expect(RESERVED_KEYWORDS.has('null')).toBe(true);
|
|
expect(RESERVED_KEYWORDS.has('true')).toBe(true);
|
|
expect(RESERVED_KEYWORDS.has('false')).toBe(true);
|
|
expect(RESERVED_KEYWORDS.has('undefined')).toBe(true);
|
|
});
|
|
|
|
it('includes SDK functions', () => {
|
|
expect(RESERVED_KEYWORDS.has('workflow')).toBe(true);
|
|
expect(RESERVED_KEYWORDS.has('trigger')).toBe(true);
|
|
expect(RESERVED_KEYWORDS.has('node')).toBe(true);
|
|
expect(RESERVED_KEYWORDS.has('merge')).toBe(true);
|
|
expect(RESERVED_KEYWORDS.has('splitInBatches')).toBe(true);
|
|
});
|
|
|
|
it('includes dangerous globals', () => {
|
|
expect(RESERVED_KEYWORDS.has('eval')).toBe(true);
|
|
expect(RESERVED_KEYWORDS.has('Function')).toBe(true);
|
|
expect(RESERVED_KEYWORDS.has('require')).toBe(true);
|
|
expect(RESERVED_KEYWORDS.has('process')).toBe(true);
|
|
expect(RESERVED_KEYWORDS.has('global')).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('toVarName', () => {
|
|
it('converts node name to variable name', () => {
|
|
expect(toVarName('My Node')).toBe('my_Node');
|
|
});
|
|
|
|
it('replaces special characters with underscores', () => {
|
|
expect(toVarName('My-Node@test')).toBe('my_Node_test');
|
|
});
|
|
|
|
it('collapses multiple underscores', () => {
|
|
expect(toVarName('My Node')).toBe('my_Node');
|
|
});
|
|
|
|
it('removes trailing underscore', () => {
|
|
// 'Node' after processing becomes 'node' which is a reserved keyword -> 'node_node'
|
|
expect(toVarName('Node_')).toBe('node_node');
|
|
// A non-keyword example (first letter is lowercased)
|
|
expect(toVarName('Process_')).toBe('process_node'); // 'process' is reserved
|
|
expect(toVarName('Custom_')).toBe('custom');
|
|
});
|
|
|
|
it('preserves leading underscore when followed by digit', () => {
|
|
expect(toVarName('_2nd Node')).toBe('_2nd_Node');
|
|
});
|
|
|
|
it('removes leading underscore when followed by letter', () => {
|
|
// Removes the underscore but preserves original casing of the letter after
|
|
expect(toVarName('_Foo')).toBe('Foo');
|
|
});
|
|
|
|
it('prefixes with underscore if starts with digit', () => {
|
|
expect(toVarName('1st Node')).toBe('_1st_Node');
|
|
});
|
|
|
|
it('lowercases first letter', () => {
|
|
expect(toVarName('ProcessData')).toBe('processData');
|
|
});
|
|
|
|
it('appends _node for reserved keywords', () => {
|
|
expect(toVarName('function')).toBe('function_node');
|
|
expect(toVarName('return')).toBe('return_node');
|
|
expect(toVarName('workflow')).toBe('workflow_node');
|
|
});
|
|
|
|
it('handles empty string with hash fallback', () => {
|
|
expect(toVarName('')).toBe('node_0');
|
|
});
|
|
|
|
it('handles non-ASCII names with hash fallback', () => {
|
|
const result = toVarName('ニュース整形');
|
|
expect(result).toMatch(/^node_[a-z0-9]+$/);
|
|
// Should be deterministic
|
|
expect(toVarName('ニュース整形')).toBe(result);
|
|
});
|
|
|
|
it('handles single special character with hash fallback', () => {
|
|
const result = toVarName('/');
|
|
expect(result).toMatch(/^node_[a-z0-9]+$/);
|
|
});
|
|
});
|
|
|
|
describe('getVarName', () => {
|
|
it('returns mapped name if exists in context', () => {
|
|
const ctx = createContext();
|
|
ctx.nodeNameToVarName.set('My Node', 'myNodeVar');
|
|
|
|
expect(getVarName('My Node', ctx)).toBe('myNodeVar');
|
|
});
|
|
|
|
it('returns base var name if not in context', () => {
|
|
const ctx = createContext();
|
|
|
|
expect(getVarName('My Node', ctx)).toBe('my_Node');
|
|
});
|
|
});
|
|
|
|
describe('getUniqueVarName', () => {
|
|
it('returns and registers unique var name', () => {
|
|
const ctx = createContext();
|
|
|
|
const result = getUniqueVarName('My Node', ctx);
|
|
|
|
expect(result).toBe('my_Node');
|
|
expect(ctx.nodeNameToVarName.get('My Node')).toBe('my_Node');
|
|
expect(ctx.usedVarNames.has('my_Node')).toBe(true);
|
|
});
|
|
|
|
it('returns same name on repeated calls', () => {
|
|
const ctx = createContext();
|
|
|
|
const first = getUniqueVarName('My Node', ctx);
|
|
const second = getUniqueVarName('My Node', ctx);
|
|
|
|
expect(first).toBe(second);
|
|
});
|
|
|
|
it('appends number for collisions', () => {
|
|
const ctx = createContext();
|
|
ctx.usedVarNames.add('my_Node');
|
|
|
|
const result = getUniqueVarName('My Node', ctx);
|
|
|
|
expect(result).toBe('my_Node1');
|
|
});
|
|
|
|
it('increments counter until unique', () => {
|
|
const ctx = createContext();
|
|
ctx.usedVarNames.add('my_Node');
|
|
ctx.usedVarNames.add('my_Node1');
|
|
ctx.usedVarNames.add('my_Node2');
|
|
|
|
const result = getUniqueVarName('My Node', ctx);
|
|
|
|
expect(result).toBe('my_Node3');
|
|
});
|
|
});
|
|
});
|
|
|
|
// Helper to create a minimal context for testing
|
|
function createContext() {
|
|
return {
|
|
nodeNameToVarName: new Map<string, string>(),
|
|
usedVarNames: new Set<string>(),
|
|
};
|
|
}
|