// Copyright (c) 2025 The Brave Authors. All rights reserved. // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this file, // You can obtain one at https://mozilla.org/MPL/2.0/. import * as assert from 'node:assert' import * as React from 'react' import { formatString } from './formatString' // Note: We need to unmock the locale module here because we want to test it! jest.unmock('$web-common/locale') describe('formatString', () => { it('should format a string with replacements', () => { assert.equal(formatString('Hello $1', { $1: 'world' }), 'Hello world') }) it('should allow multiple replacements', () => { assert.equal(formatString('$1 say $2!', { $1: 'I', $2: 'Kiora' }), 'I say Kiora!') }) it('should allow replacing with a function', () => { assert.equal(formatString('$1 say $2', { $1: 'I', $2: () => 'Kiora' + '!' }), 'I say Kiora!') }) it('should allow providing an array of replacements', () => { assert.equal(formatString('$1 say $2', ['I', () => 'Kiora' + '!']), 'I say Kiora!') }) it('should allow replacing a range', () => { assert.equal(formatString('$1 in $2nz/$2 say $3', { $1: 'People', $2: 'NZ', $3: 'Kiora' }), 'People in NZ say Kiora') }) it('should allow replacing a range using an array of replacements', () => { assert.equal(formatString('$1 in $2nz/$2 say $3', ['People', 'NZ', 'Kiora']), 'People in NZ say Kiora') }) it('should allow replacing with a range and a function', () => { assert.equal(formatString('$1 in $2nz/$2 say $3', { $1: 'People', $2: (content) => content.toUpperCase(), $3: 'Kiora' }), 'People in NZ say Kiora') }) it('should log an error if a replacement does not exist', () => { const consoleSpy = jest.spyOn(console, 'error').mockImplementation() formatString('$1 in $2nz/$2 say $3', { $1: 'People', $2: (content) => content.toUpperCase(), $3: 'Kiora', $4: 'MISSING!' }) expect(consoleSpy).toHaveBeenCalled() consoleSpy.mockRestore() }) it('should log an error if a replacement does not exist using an array of replacements', () => { const consoleSpy = jest.spyOn(console, 'error').mockImplementation() formatString('$1 in $2nz/$2 say $3', ['People', 'NZ', 'Kiora', 'MISSING!']) expect(consoleSpy).toHaveBeenCalled() consoleSpy.mockRestore() }) it('should not fail if a replacement does not exist if noErrorOnMissingReplacement is true', () => { expect(formatString('$1 in $2nz/$2 say $3', { $1: 'People', $2: (content) => content.toUpperCase(), $3: 'Kiora', $4: 'MISSING!' }, { noErrorOnMissingReplacement: true })).toBe('People in NZ say Kiora') }) it('should not fail if a replacement does not exist if noErrorOnMissingReplacement is true using an array of replacements', () => { expect(formatString('$1 in $2nz/$2 say $3', ['People', 'NZ', 'Kiora', 'MISSING!'], { noErrorOnMissingReplacement: true })).toBe('People in NZ say Kiora') }) it('should not change text with no replacements', () => { assert.equal(formatString('Hello world', {}), 'Hello world') }) it('should not change text with no replacements using an array of replacements', () => { assert.equal(formatString('Hello world', []), 'Hello world') }) it('should not change text with placeholders and no replacements using an array of replacements', () => { assert.equal(formatString('Hello $1', []), 'Hello $1') }) it('should accept React nodes as replacements', () => { assert.deepEqual(formatString('Hello $1', { $1: world }), <>Hello world) }) it('should accept React nodes as array replacements', () => { assert.deepEqual(formatString('Hello $1', [world]), <>Hello world) }) it('should be able to return React nodes and take content as an argument', () => { assert.deepEqual(formatString('Hello $1world/$1', { $1: (content) => {content} }), <>Hello world) }) it('should be possible to mix text and React nodes', () => { assert.deepEqual(formatString('$1Shields are UP/$1 for $2', { $1: (content) => {content}, $2: 'brave.com' }), <>Shields are UP for {"brave.com"}) }) it('should be possible to only replace some keys', () => { assert.deepEqual(formatString('$1Shields are UP/$1 for $2', { $1: (content) => {content}, }), <>Shields are UP for $2) }) it('should be possible to only replace some keys out of order', () => { assert.deepEqual(formatString('$1Shields are UP/$1 for $2', { $2: "brave.com", }), "$1Shields are UP/$1 for brave.com") }) it('one of everything', () => { assert.deepEqual(formatString('$1Shields are UP/$1 for $2 (count: $3) $4nz/$4', { $1: (content) => {content}, $2: "brave.com", $3: over 9000, $4: code => code.toUpperCase() }), <> Shields are UP for {"brave.com"} (count: over 9000) {"NZ"} ) }) it('should be possible to have a placeholder in a tag', () => { assert.deepEqual(formatString('Hello $1and goodbye $2/$1', { $1: (content) => {content}, $2: world }), <>Hello <>and goodbye world) }) it('should not break for Czech shields', () => { assert.deepEqual(formatString('$1Štíty pro $2 jsou zapnuty/$1', { $1: content => {content}, $2: 'brave.com' }), <>Štíty pro brave.com jsou zapnuty) assert.deepEqual(formatString('$1Štíty pro $2 jsou vypnuty/$1', { $1: content => {content}, $2: 'brave.com' }), <>Štíty pro brave.com jsou vypnuty) }) it('should be possible to nest tags', () => { assert.deepEqual(formatString('Hello $1and $3goodbye $2/$3/$1', { $1: (content) => {content}, $3: content => {content}, $2: world }), <>Hello <>and <>goodbye world) }) it('should be possible to replace multiple instances of the same key', () => { assert.deepEqual(formatString('Hello from $1 to $1', { $1: 'me' }), 'Hello from me to me') }) it('should be possible to replace multiple instances of the same key with an array of replacements', () => { assert.deepEqual(formatString('Hello from $1 to $1', ['me']), 'Hello from me to me') }) it('should be possible to replace multiple instances of the same key with tags and nesting', () => { assert.deepEqual(formatString('Hello from $2$1/$2 to $1. $2bob/$2 says hi too', { $1: 'me', $2: (content) => {content} }), <>Hello from me{" to "}{"me"}. bob says hi too) }) it('should only support single-digit placeholders ($1-$9)', () => { // $10 should be treated as $1 followed by literal "0" assert.equal(formatString('Hello $10', { $1: 'world' }), 'Hello world0') }) })