import { RuleTester } from 'eslint'; import noUntranslatedStrings from '../rules/no-untranslated-strings.cjs'; RuleTester.setDefaultConfig({ languageOptions: { ecmaVersion: 2018, sourceType: 'module', parserOptions: { ecmaFeatures: { jsx: true, }, }, }, }); const filename = 'public/app/features/some-feature/SomeFile.tsx'; const ruleTester = new RuleTester(); ruleTester.run('eslint no-untranslated-strings', noUntranslatedStrings, { test: [], valid: [ { name: 'Text in Trans component', code: `const Foo = () => Translated text`, }, { name: 'Text in Trans component with whitespace/JSXText elements', code: `const Foo = () => Translated text `, }, { name: 'Empty component', code: `
`, }, { name: 'Text using t() function', code: `
{t('translated.key', 'Translated text')}
`, }, { name: 'Prop using t() function', code: `
`, }, { name: 'Empty string prop', code: `
`, }, { name: 'Prop using boolean', code: `
`, }, { name: 'Prop using number', code: `
`, }, { name: 'Prop using null', code: `
`, }, { name: 'Prop using undefined', code: `
`, }, { name: 'Variable interpolation', code: `
{variable}
`, }, { name: 'Entirely non-alphanumeric text (prop)', code: `
`, }, { name: 'Entirely non-alphanumeric text', code: `
-
`, }, { name: 'Non-alphanumeric siblings', code: `
({variable})
`, }, { name: "Ternary in an attribute we don't care about", code: `
`, }, { name: 'Ternary with falsy strings', code: `
`, }, ], invalid: [ /** * FIXABLE CASES */ // Basic happy path case: // untranslated text, in a component, in a file location where we can extract a prefix, // and it can be fixed { name: 'Basic untranslated text in component', code: ` const Foo = () =>
Untranslated text
`, filename, errors: [ { messageId: 'noUntranslatedStrings', suggestions: [ { messageId: 'wrapWithTrans', output: ` import { Trans } from 'app/core/internationalization'; const Foo = () =>
Untranslated text
`, }, ], }, ], }, { name: 'Text inside JSXElement, not in a function', code: `const thing =
foo
`, filename, errors: [ { messageId: 'noUntranslatedStrings', suggestions: [ { messageId: 'wrapWithTrans', output: `import { Trans } from 'app/core/internationalization'; const thing =
foo
`, }, ], }, ], }, { name: 'Fixes medium length strings', code: ` const Foo = () =>
This is a longer string that we will translate
`, filename, errors: [ { messageId: 'noUntranslatedStrings', suggestions: [ { messageId: 'wrapWithTrans', output: ` import { Trans } from 'app/core/internationalization'; const Foo = () =>
This is a longer string that we will translate
`, }, ], }, ], }, { name: 'Fixes short strings with many words', code: ` const Foo = () =>
lots of sho rt word s to be filt ered
`, filename, errors: [ { messageId: 'noUntranslatedStrings', suggestions: [ { messageId: 'wrapWithTrans', output: ` import { Trans } from 'app/core/internationalization'; const Foo = () =>
lots of sho rt word s to be filt ered
`, }, ], }, ], }, { name: 'expression', code: ` const foo = <>hello`, filename, errors: [ { messageId: 'noUntranslatedStrings', suggestions: [ { messageId: 'wrapWithTrans', output: ` import { Trans } from 'app/core/internationalization'; const foo = <>hello`, }, ], }, ], }, { name: 'Fixes strings in JSX in props', code: ` const Foo = () =>
Test} />
`, filename, errors: [ { messageId: 'noUntranslatedStrings', suggestions: [ { messageId: 'wrapWithTrans', output: ` import { Trans } from 'app/core/internationalization'; const Foo = () =>
Test} />
`, }, ], }, ], }, { name: 'Fixes and uses ID from attribute if exists', code: ` import { t } from 'app/core/internationalization'; const Foo = () =>
`, filename, errors: [ { messageId: 'noUntranslatedStringsProp', suggestions: [ { messageId: 'wrapWithT', output: ` import { t } from 'app/core/internationalization'; const Foo = () =>
`, }, ], }, ], }, { name: 'Fixes correctly when Trans import already exists', code: ` import { Trans } from 'app/core/internationalization'; const Foo = () =>
Untranslated text
`, filename, errors: [ { messageId: 'noUntranslatedStrings', suggestions: [ { messageId: 'wrapWithTrans', output: ` import { Trans } from 'app/core/internationalization'; const Foo = () =>
Untranslated text
`, }, ], }, ], }, { name: 'Fixes correctly when t() import already exists', code: ` import { t } from 'app/core/internationalization'; const Foo = () =>
`, filename, errors: [ { messageId: 'noUntranslatedStringsProp', suggestions: [ { messageId: 'wrapWithT', output: ` import { t } from 'app/core/internationalization'; const Foo = () =>
`, }, ], }, ], }, { name: 'Fixes correctly when import exists but needs to add t()', code: ` import { Trans } from 'app/core/internationalization'; const Foo = () =>
`, filename, errors: [ { messageId: 'noUntranslatedStringsProp', suggestions: [ { messageId: 'wrapWithT', output: ` import { Trans, t } from 'app/core/internationalization'; const Foo = () =>
`, }, ], }, ], }, { name: 'Fixes correctly with a Class component', code: ` class Foo extends React.Component { render() { return
untranslated text
; } }`, filename, errors: [ { messageId: 'noUntranslatedStrings', suggestions: [ { messageId: 'wrapWithTrans', output: ` import { Trans } from 'app/core/internationalization'; class Foo extends React.Component { render() { return
untranslated text
; } }`, }, ], }, ], }, { name: 'Fixes basic prop case', code: ` const Foo = () =>
`, filename, errors: [ { messageId: 'noUntranslatedStringsProp', suggestions: [ { messageId: 'wrapWithT', output: ` import { t } from 'app/core/internationalization'; const Foo = () =>
`, }, ], }, ], }, { name: 'Fixes prop case with string literal inside expression container', code: ` const Foo = () =>
`, filename, errors: [ { messageId: 'noUntranslatedStringsProp', suggestions: [ { messageId: 'wrapWithT', output: ` import { t } from 'app/core/internationalization'; const Foo = () =>
`, }, ], }, ], }, { name: 'Fixes prop case with double quotes in value', code: ` const Foo = () =>
`, filename, errors: [ { messageId: 'noUntranslatedStringsProp', suggestions: [ { messageId: 'wrapWithT', output: ` import { t } from 'app/core/internationalization'; const Foo = () =>
`, }, ], }, ], }, { name: 'Fixes case with nested functions/components', code: ` import { Trans } from 'app/core/internationalization'; const Foo = () => { const getSomething = () => { return
foo
; } return
{getSomething()}
; } `, filename, errors: [ { messageId: 'noUntranslatedStrings', suggestions: [ { messageId: 'wrapWithTrans', output: ` import { Trans } from 'app/core/internationalization'; const Foo = () => { const getSomething = () => { return
foo
; } return
{getSomething()}
; } `, }, ], }, ], }, /** * UNFIXABLE CASES */ { name: 'Multiple untranslated strings in one element', code: `const Foo = () =>
test {name} example
`, filename, errors: [ { messageId: 'noUntranslatedStrings', }, ], }, { name: 'Cannot fix text with expression sibling', code: `const Foo = () =>
{name} Hello
`, filename, errors: [{ messageId: 'noUntranslatedStrings' }], }, { name: 'Cannot fix text with expression sibling in fragment', code: ` const Foo = () => { const bar = { baz: (<>Hello {name}) } }`, filename, errors: [{ messageId: 'noUntranslatedStrings' }], }, { name: 'Cannot fix text containing HTML entities', code: `const Foo = () =>
Something 
`, filename, errors: [{ messageId: 'noUntranslatedStrings' }], }, { name: 'Cannot fix text that is too long', code: `const Foo = () =>
This is something with lots of text that we don't want to translate automatically
`, filename, errors: [{ messageId: 'noUntranslatedStrings' }], }, { name: 'Cannot fix prop text that is too long', code: `const Foo = () =>
`, filename, errors: [{ messageId: 'noUntranslatedStringsProp' }], }, { name: 'Cannot fix text with HTML sibling', code: `const Foo = () =>
something foo bar
`, filename, errors: [{ messageId: 'noUntranslatedStrings' }], }, { name: 'JSXAttribute not in a function', code: `
`, filename, errors: [{ messageId: 'noUntranslatedStringsProp' }], }, { name: 'Cannot fix JSXExpression in attribute if it is template literal', code: `const Foo = () =>
`, filename, errors: [{ messageId: 'noUntranslatedStringsProp' }], }, { name: 'Cannot fix text outside correct directory location', code: `const Foo = () =>
Untranslated text
`, filename: 'public/something-else/foo/SomeOtherFile.tsx', errors: [{ messageId: 'noUntranslatedStrings' }], }, { name: 'Invalid when ternary with string literals', code: `const Foo = () =>
{isAThing ? 'Foo' : 'Bar'}
`, filename, errors: [{ messageId: 'noUntranslatedStrings' }, { messageId: 'noUntranslatedStrings' }], }, { name: 'Invalid when ternary with string literals - prop', code: `const Foo = () =>
`, filename, errors: [{ messageId: 'noUntranslatedStringsProp' }, { messageId: 'noUntranslatedStringsProp' }], }, ], });