import { RuleTester } from '@typescript-eslint/rule-tester'; import { NoEmojiInOptionsRule } from './no-emoji-in-options.js'; const ruleTester = new RuleTester(); function createNodeCode(body: string): string { return ` import type { INodeType, INodeTypeDescription } from 'n8n-workflow'; export class TestNode implements INodeType { description: INodeTypeDescription = { ${body} }; } `; } /** * Versioned node layout from the node-building docs: the version class assigns * `description` in its constructor instead of as a property initializer. */ function createVersionedNodeCode(body: string): string { return ` import type { INodeType, INodeTypeBaseDescription, INodeTypeDescription } from 'n8n-workflow'; export class TestNodeV1 implements INodeType { description: INodeTypeDescription; constructor(baseDescription: INodeTypeBaseDescription) { this.description = { ...baseDescription, ${body} }; } } `; } ruleTester.run('no-emoji-in-options', NoEmojiInOptionsRule, { valid: [ { name: 'class that does not implement INodeType', filename: '/tmp/TestNode.node.ts', code: ` export class NotANode { description = { displayName: 'πŸš€ Rocket' }; } `, }, { name: 'non .node.ts file is ignored', filename: '/tmp/helper.ts', code: ` export class TestNode { description = { displayName: 'πŸš€ Rocket' }; } `, }, { name: 'node and option labels without emoji', filename: '/tmp/TestNode.node.ts', code: createNodeCode(` displayName: 'Test Node', name: 'testNode', defaults: { name: 'Test Node' }, properties: [ { displayName: 'Operation', name: 'operation', type: 'options', options: [ { name: 'Create', value: 'create' }, { name: 'Delete', value: 'delete' }, ], default: 'create', }, ], `), }, { name: 'accented and non-latin characters are allowed', filename: '/tmp/TestNode.node.ts', code: createNodeCode(` displayName: 'CrΓ©er un cafΓ©', name: 'testNode', properties: [ { displayName: 'ζ—₯本θͺž', name: 'field', type: 'string', default: '', }, ], `), }, ], invalid: [ { name: 'emoji in option name of a versioned node assigning description in its constructor', filename: '/tmp/v1/TestNodeV1.node.ts', code: createVersionedNodeCode(` version: 1, properties: [ { displayName: 'Operation', name: 'operation', type: 'options', options: [ { name: 'βœ… Create', value: 'create' }, { name: 'Delete', value: 'delete' }, ], default: 'create', }, ], `), errors: [{ messageId: 'emojiInOption', data: { key: 'name', emoji: 'βœ…' } }], }, { name: 'emoji in a description that is cast with `as`', filename: '/tmp/TestNode.node.ts', code: ` import type { INodeType, INodeTypeDescription } from 'n8n-workflow'; export class TestNode implements INodeType { description = { displayName: 'πŸš€ Rocket Node', name: 'testNode', properties: [], } as INodeTypeDescription; } `, errors: [{ messageId: 'emojiInOption', data: { key: 'displayName', emoji: 'πŸš€' } }], }, { name: 'emoji in a constructor-assigned description that is cast with `as`', filename: '/tmp/v1/TestNodeV1.node.ts', code: ` import type { INodeType, INodeTypeBaseDescription, INodeTypeDescription } from 'n8n-workflow'; export class TestNodeV1 implements INodeType { description: INodeTypeDescription; constructor(baseDescription: INodeTypeBaseDescription) { this.description = { ...baseDescription, displayName: 'πŸš€ Rocket Node', properties: [], } as INodeTypeDescription; } } `, errors: [{ messageId: 'emojiInOption', data: { key: 'displayName', emoji: 'πŸš€' } }], }, { name: 'emoji in node displayName', filename: '/tmp/TestNode.node.ts', code: createNodeCode(` displayName: 'πŸš€ Rocket Node', name: 'testNode', properties: [], `), errors: [{ messageId: 'emojiInOption', data: { key: 'displayName', emoji: 'πŸš€' } }], }, { name: 'emoji in option name within options array', filename: '/tmp/TestNode.node.ts', code: createNodeCode(` displayName: 'Test Node', name: 'testNode', properties: [ { displayName: 'Operation', name: 'operation', type: 'options', options: [ { name: 'βœ… Create', value: 'create' }, { name: 'Delete', value: 'delete' }, ], default: 'create', }, ], `), errors: [{ messageId: 'emojiInOption', data: { key: 'name', emoji: 'βœ…' } }], }, { name: 'emoji in property displayName', filename: '/tmp/TestNode.node.ts', code: createNodeCode(` displayName: 'Test Node', name: 'testNode', properties: [ { displayName: 'First Name πŸ™‚', name: 'firstName', type: 'string', default: '', }, ], `), errors: [{ messageId: 'emojiInOption', data: { key: 'displayName', emoji: 'πŸ™‚' } }], }, { name: 'flag emoji built from regional indicators', filename: '/tmp/TestNode.node.ts', code: createNodeCode(` displayName: 'Region πŸ‡ΊπŸ‡Έ', name: 'testNode', properties: [], `), errors: [{ messageId: 'emojiInOption', data: { key: 'displayName', emoji: 'πŸ‡Ί πŸ‡Έ' } }], }, { name: 'multiple emoji values reported separately', filename: '/tmp/TestNode.node.ts', code: createNodeCode(` displayName: 'πŸš€ Node', name: 'testNode', properties: [ { displayName: 'Field πŸŽ‰', name: 'field', type: 'string', default: '', }, ], `), errors: [ { messageId: 'emojiInOption', data: { key: 'displayName', emoji: 'πŸš€' } }, { messageId: 'emojiInOption', data: { key: 'displayName', emoji: 'πŸŽ‰' } }, ], }, ], });