| // Copyright 2020 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import * as Platform from './platform.js'; |
| |
| describe('StringUtilities', () => { |
| describe('escapeCharacters', () => { |
| it('escapes the given characters', () => { |
| const inputString = 'My string with a single quote \' in the middle'; |
| const charsToEscape = '\''; |
| const outputString = Platform.StringUtilities.escapeCharacters(inputString, charsToEscape); |
| |
| assert.strictEqual(outputString, 'My string with a single quote \\\' in the middle'); |
| }); |
| |
| it('leaves the string alone if the characters are not found', () => { |
| const inputString = 'Just a boring string'; |
| const charsToEscape = '\''; |
| const outputString = Platform.StringUtilities.escapeCharacters(inputString, charsToEscape); |
| assert.strictEqual(outputString, inputString); |
| }); |
| }); |
| |
| describe('toBase64', () => { |
| it('encodes correctly and supports unicode characters', () => { |
| const fixtures = new Map([ |
| ['', ''], |
| ['a', 'YQ=='], |
| ['bc', 'YmM='], |
| ['def', 'ZGVm'], |
| ['ghij', 'Z2hpag=='], |
| ['klmno', 'a2xtbm8='], |
| ['pqrstu', 'cHFyc3R1'], |
| ['\u0444\u5555\u6666\u7777', '0YTllZXmmabnnbc='], |
| ]); |
| for (const [inputString, encodedString] of fixtures) { |
| assert.strictEqual( |
| encodedString, Platform.StringUtilities.toBase64(inputString), `failed to encode ${inputString} correctly`); |
| } |
| }); |
| }); |
| |
| describe('findIndexesOfSubstring', () => { |
| it('finds the expected indexes', () => { |
| const inputString = '111111F1111111F11111111F'; |
| const indexes = Platform.StringUtilities.findIndexesOfSubString(inputString, 'F'); |
| assert.deepEqual(indexes, [6, 14, 23]); |
| }); |
| }); |
| |
| describe('findLineEndingIndexes', () => { |
| it('finds the indexes of the line endings and returns them', () => { |
| const inputString = `1234 |
| 56 |
| 78 |
| 9`; |
| const indexes = Platform.StringUtilities.findLineEndingIndexes(inputString); |
| assert.deepEqual(indexes, [4, 7, 10, 12]); |
| }); |
| }); |
| |
| describe('isWhitespace', () => { |
| it('correctly recognizes different kinds of whitespace', () => { |
| assert.isTrue(Platform.StringUtilities.isWhitespace('')); |
| assert.isTrue(Platform.StringUtilities.isWhitespace(' ')); |
| assert.isTrue(Platform.StringUtilities.isWhitespace('\t')); |
| assert.isTrue(Platform.StringUtilities.isWhitespace('\n')); |
| |
| assert.isFalse(Platform.StringUtilities.isWhitespace(' foo ')); |
| }); |
| }); |
| |
| describe('trimURL', () => { |
| it('trims the protocol and an optional domain from URLs', () => { |
| const baseURLDomain = 'www.chromium.org'; |
| const fixtures = new Map([ |
| ['http://www.chromium.org/foo/bar', '/foo/bar'], |
| ['https://www.CHromium.ORG/BAZ/zoo', '/BAZ/zoo'], |
| ['https://example.com/foo[]', 'example.com/foo[]'], |
| ]); |
| for (const [url, expected] of fixtures) { |
| assert.strictEqual(Platform.StringUtilities.trimURL(url, baseURLDomain), expected, url); |
| } |
| }); |
| }); |
| |
| describe('collapseWhitespace', () => { |
| it('collapses consecutive whitespace chars down to a single one', () => { |
| const inputString = 'look at this!'; |
| const outputString = Platform.StringUtilities.collapseWhitespace(inputString); |
| assert.strictEqual(outputString, 'look at this!'); |
| }); |
| |
| it('matches globally and collapses all whitespace sections', () => { |
| const inputString = 'a b c'; |
| const outputString = Platform.StringUtilities.collapseWhitespace(inputString); |
| assert.strictEqual(outputString, 'a b c'); |
| }); |
| }); |
| |
| describe('reverse', () => { |
| it('reverses the string', () => { |
| const inputString = 'abc'; |
| assert.strictEqual(Platform.StringUtilities.reverse(inputString), 'cba'); |
| }); |
| |
| it('does nothing to an empty string', () => { |
| assert.strictEqual('', Platform.StringUtilities.reverse('')); |
| }); |
| }); |
| |
| describe('replaceControlCharacters', () => { |
| it('replaces C0 and C1 control character sets with the replacement character', () => { |
| const charsThatShouldBeEscaped = [ |
| '\0', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', '\b', '\x0B', '\f', '\x0E', '\x0F', |
| '\x10', '\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', '\x1A', '\x1B', '\x1C', |
| '\x1D', '\x1E', '\x1F', '\x80', '\x81', '\x82', '\x83', '\x84', '\x85', '\x86', '\x87', '\x88', '\x89', |
| '\x8A', '\x8B', '\x8C', '\x8D', '\x8E', '\x8F', '\x90', '\x91', '\x92', '\x93', '\x94', '\x95', '\x96', |
| '\x97', '\x98', '\x99', '\x9A', '\x9B', '\x9C', '\x9D', '\x9E', '\x9F', |
| ]; |
| |
| const inputString = charsThatShouldBeEscaped.join(''); |
| const outputString = Platform.StringUtilities.replaceControlCharacters(inputString); |
| |
| const replacementCharacter = '\uFFFD'; |
| const expectedString = charsThatShouldBeEscaped.fill(replacementCharacter).join(''); |
| assert.strictEqual(outputString, expectedString); |
| }); |
| |
| it('does not replace \n \t or \r', () => { |
| const inputString = '\nhello world\t\r'; |
| const outputString = Platform.StringUtilities.replaceControlCharacters(inputString); |
| assert.strictEqual(inputString, outputString); |
| }); |
| }); |
| |
| describe('countWtf8Bytes', () => { |
| it('produces the correct WTF-8 byte size', () => { |
| assert.strictEqual(Platform.StringUtilities.countWtf8Bytes('a'), 1); |
| assert.strictEqual(Platform.StringUtilities.countWtf8Bytes('\x7F'), 1); |
| assert.strictEqual(Platform.StringUtilities.countWtf8Bytes('\u07FF'), 2); |
| assert.strictEqual(Platform.StringUtilities.countWtf8Bytes('\uD800'), 3); |
| assert.strictEqual(Platform.StringUtilities.countWtf8Bytes('\uDBFF'), 3); |
| assert.strictEqual(Platform.StringUtilities.countWtf8Bytes('\uDC00'), 3); |
| assert.strictEqual(Platform.StringUtilities.countWtf8Bytes('\uDFFF'), 3); |
| assert.strictEqual(Platform.StringUtilities.countWtf8Bytes('\uFFFF'), 3); |
| assert.strictEqual(Platform.StringUtilities.countWtf8Bytes('\u{10FFFF}'), 4); |
| assert.strictEqual(Platform.StringUtilities.countWtf8Bytes('Iñtërnâtiônà lizætiøn☃💩'), 34); |
| |
| // An arbitrary lead surrogate (D800..DBFF). |
| const leadSurrogate = '\uDABC'; |
| // An arbitrary trail surrogate (DC00..DFFF). |
| const trailSurrogate = '\uDEF0'; |
| assert.strictEqual(Platform.StringUtilities.countWtf8Bytes(`${leadSurrogate}${trailSurrogate}`), 4); |
| assert.strictEqual(Platform.StringUtilities.countWtf8Bytes(`${trailSurrogate}${leadSurrogate}`), 6); |
| assert.strictEqual(Platform.StringUtilities.countWtf8Bytes(`${leadSurrogate}`), 3); |
| assert.strictEqual(Platform.StringUtilities.countWtf8Bytes(`${trailSurrogate}`), 3); |
| }); |
| }); |
| |
| describe('stripLineBreaks', () => { |
| it('strips linebreaks from strings', () => { |
| assert.strictEqual(Platform.StringUtilities.stripLineBreaks('a\nb'), 'ab'); |
| assert.strictEqual(Platform.StringUtilities.stripLineBreaks('a\r\nb'), 'ab'); |
| }); |
| }); |
| |
| describe('isExtendedKebabCase', () => { |
| const {isExtendedKebabCase} = Platform.StringUtilities; |
| |
| it('yields `true` for kebab case strings', () => { |
| assert.isTrue(isExtendedKebabCase('a-b-c')); |
| assert.isTrue(isExtendedKebabCase('a-b')); |
| assert.isTrue(isExtendedKebabCase('abc')); |
| }); |
| |
| it('yields `true` for kebab case strings with dots', () => { |
| assert.isTrue(isExtendedKebabCase('quick-open.show')); |
| assert.isTrue(isExtendedKebabCase('main.target.reload-page')); |
| }); |
| |
| it('yields `false` for broken kebab case', () => { |
| assert.isFalse(isExtendedKebabCase('a-b-')); |
| assert.isFalse(isExtendedKebabCase('-abc')); |
| assert.isFalse(isExtendedKebabCase('a--c')); |
| }); |
| |
| it('yields `false` for other cases', () => { |
| assert.isFalse(isExtendedKebabCase('quickOpen.show')); |
| assert.isFalse(isExtendedKebabCase('inspector_main.reload')); |
| assert.isFalse(isExtendedKebabCase('Main.target.ReloadPage')); |
| }); |
| }); |
| |
| describe('toTitleCase', () => { |
| it('converts a string to title case', () => { |
| const output = Platform.StringUtilities.toTitleCase('foo bar baz'); |
| assert.strictEqual(output, 'Foo bar baz'); |
| }); |
| }); |
| |
| describe('removeURLFragment', () => { |
| it('removes the URL fragment if found', () => { |
| const input = 'http://www.example.com/foo.html#blah'; |
| assert.strictEqual(Platform.StringUtilities.removeURLFragment(input), 'http://www.example.com/foo.html'); |
| }); |
| |
| it('returns the same string if there is no fragment', () => { |
| const input = 'http://www.example.com/foo.html'; |
| assert.strictEqual(Platform.StringUtilities.removeURLFragment(input), input); |
| }); |
| |
| it('does not strip query parameters', () => { |
| const input = 'http://www.example.com/foo.html?x=1#blah'; |
| assert.strictEqual(Platform.StringUtilities.removeURLFragment(input), 'http://www.example.com/foo.html?x=1'); |
| }); |
| }); |
| describe('filterRegex', () => { |
| it('should prepend [^\\0 ]* patterns for all characters', () => { |
| const regex = Platform.StringUtilities.filterRegex('bar'); |
| assert.strictEqual(regex.toString(), '/^(?:.*\\0)?[^\\0b]*b[^\\0a]*a[^\\0r]*r/i'); |
| }); |
| |
| it('should escape special characters', () => { |
| const regex = Platform.StringUtilities.filterRegex('{?}'); |
| assert.strictEqual(regex.toString(), '/^(?:.*\\0)?[^\\0\\{]*\\{[^\\0\\?]*\\?[^\\0\\}]*\\}/i'); |
| }); |
| |
| it('should match strings that have the query characters in the same order', () => { |
| const testCases = [ |
| {query: 'abc', pos: ['abc', 'adabxac', 'AbC', 'a\x00abc'], neg: ['ab', 'acb', 'a\x00bc']}, |
| {query: 'aba', pos: ['abba', 'abracadabra'], neg: ['ab', 'aab', 'baa']}, |
| {query: '.?a*', pos: ['x.y?ax*b'], neg: ['', 'a?a*', 'a*', '.?']}, |
| ]; |
| for (const {query, pos, neg} of testCases) { |
| const regex = Platform.StringUtilities.filterRegex(query); |
| assert.exists(regex, `Could not create regex from query "${query}"`); |
| for (const example of pos) { |
| assert.isTrue(regex.test(example), `query "${query}" should match "${example}"`); |
| } |
| for (const example of neg) { |
| assert.isFalse(regex.test(example), `query "${query}" should not match "${example}"`); |
| } |
| } |
| }); |
| }); |
| |
| describe('createSearchRegex', () => { |
| it('returns a case sensitive regex if the call states it is case sensitive', () => { |
| const regex = Platform.StringUtilities.createSearchRegex('foo', true, false); |
| assert.isFalse(regex.ignoreCase); |
| assert.strictEqual(regex.source, 'foo'); |
| }); |
| |
| it('creates a regex from plain text if the given input is not already a regex', () => { |
| const regex = Platform.StringUtilities.createSearchRegex('[foo]', false, false); |
| assert.strictEqual(regex.source, '\\[foo\\]'); |
| }); |
| |
| it('leaves the input be if it is already a regex', () => { |
| const regex = Platform.StringUtilities.createSearchRegex('[foo]', false, true); |
| assert.strictEqual(regex.source, '[foo]'); |
| }); |
| }); |
| |
| describe('hashCode', () => { |
| it('hashes strings', () => { |
| const stringA = ' '.repeat(10000); |
| const stringB = stringA + ' '; |
| const hashA = Platform.StringUtilities.hashCode(stringA); |
| assert.notStrictEqual(hashA, Platform.StringUtilities.hashCode(stringB)); |
| assert.isTrue(isFinite(hashA)); |
| assert.notStrictEqual(hashA + 1, hashA); |
| }); |
| }); |
| |
| describe('compare', () => { |
| it('returns 1 if the string is > the other string', () => { |
| const result = Platform.StringUtilities.compare('b', 'a'); |
| assert.strictEqual(result, 1); |
| }); |
| |
| it('returns -1 if the string is < the other string', () => { |
| const result = Platform.StringUtilities.compare('a', 'b'); |
| assert.strictEqual(result, -1); |
| }); |
| |
| it('returns 0 if the strings are equal', () => { |
| const result = Platform.StringUtilities.compare('a', 'a'); |
| assert.strictEqual(result, 0); |
| }); |
| }); |
| |
| describe('trimMiddle', () => { |
| const fixtures = [ |
| '', |
| '!', |
| '\u{1F648}A\u{1F648}L\u{1F648}I\u{1F648}N\u{1F648}A\u{1F648}\u{1F648}', |
| 'test', |
| ]; |
| |
| for (let i = 0; i < fixtures.length; i++) { |
| const string = fixtures[i]; |
| it(`trims the middle of strings, fixture ${i}`, () => { |
| for (let maxLength = string.length + 1; maxLength > 0; --maxLength) { |
| const trimmed = Platform.StringUtilities.trimMiddle(string, maxLength); |
| assert.isTrue(trimmed.length <= maxLength); |
| } |
| }); |
| } |
| }); |
| |
| describe('escapeForRegExp', () => { |
| it('escapes regex characters', () => { |
| const inputString = '^[]{}()\\.^$*+?|-'; |
| const outputString = Platform.StringUtilities.escapeForRegExp(inputString); |
| assert.strictEqual(outputString, '\\^\\[\\]\\{\\}\\(\\)\\\\\\.\\^\\$\\*\\+\\?\\|\\-'); |
| }); |
| }); |
| |
| describe('naturalOrderComparator', () => { |
| it('sorts natural order', () => { |
| const testArray = [ |
| 'dup', 'a1', 'a4222', 'a91', 'a07', 'dup', 'a7', 'a007', 'abc00', 'abc0', |
| 'abc', 'abcd', 'abc000', 'x10y20z30', 'x9y19z29', 'dup', 'x09y19z29', 'x10y22z23', 'x10y19z43', '1', |
| '10', '11', 'dup', '2', '2', '2', '555555', '5', '5555', 'dup', |
| ]; |
| |
| for (let i = 0, n = testArray.length; i < n; ++i) { |
| assert.strictEqual( |
| 0, Platform.StringUtilities.naturalOrderComparator(testArray[i], testArray[i]), 'comparing equal strings'); |
| } |
| |
| testArray.sort(Platform.StringUtilities.naturalOrderComparator); |
| |
| // Check comparator's transitivity. |
| for (let i = 0, n = testArray.length; i < n; ++i) { |
| for (let j = 0; j < n; ++j) { |
| const a = testArray[i]; |
| const b = testArray[j]; |
| const diff = Platform.StringUtilities.naturalOrderComparator(a, b); |
| if (diff === 0) { |
| assert.strictEqual(a, b, 'zero diff'); |
| } else if (diff < 0) { |
| assert.isTrue(i < j); |
| } else { |
| assert.isTrue(i > j); |
| } |
| } |
| } |
| }); |
| }); |
| |
| describe('base64ToSize', () => { |
| it('calculates length correctly', () => { |
| const inputString = 'foo'; |
| const base64String = btoa(inputString); |
| assert.strictEqual(Platform.StringUtilities.base64ToSize(base64String), inputString.length); |
| }); |
| |
| it('calculates length of null string correctly', () => { |
| const inputString = null; |
| assert.strictEqual(Platform.StringUtilities.base64ToSize(inputString), 0); |
| }); |
| |
| it('calcualtes length of string with two = at the end', () => { |
| const inputString = 'fooo'; |
| const base64String = btoa(inputString); |
| assert.strictEqual(base64String, 'Zm9vbw=='); |
| assert.strictEqual(Platform.StringUtilities.base64ToSize(base64String), inputString.length); |
| }); |
| |
| it('calcualtes length of string with one = at the end', () => { |
| const inputString = 'foooo'; |
| const base64String = btoa(inputString); |
| assert.strictEqual(base64String, 'Zm9vb28='); |
| assert.strictEqual(Platform.StringUtilities.base64ToSize(base64String), inputString.length); |
| }); |
| }); |
| |
| describe('formatAsJSLiteral', () => { |
| it('wraps plain string in single quotes', () => { |
| const inputString = 'foo'; |
| assert.strictEqual(String.raw`'foo'`, Platform.StringUtilities.formatAsJSLiteral(inputString)); |
| }); |
| |
| it('wraps string containing single quotes in double quotes', () => { |
| const inputString = String.raw`'foo' and 'bar'`; |
| assert.strictEqual(String.raw`"'foo' and 'bar'"`, Platform.StringUtilities.formatAsJSLiteral(inputString)); |
| }); |
| |
| it('wraps string containing both single and double quotes in back ticks', () => { |
| const inputString = String.raw`'foo' and "bar"`; |
| assert.strictEqual('`\'foo\' and "bar"`', Platform.StringUtilities.formatAsJSLiteral(inputString)); |
| }); |
| |
| it('wraps string containing all three quotes in single quotes', () => { |
| const inputString = '\'foo\' `and` "bar"'; |
| assert.strictEqual('\'\\\'foo\\\' `and` "bar"\'', Platform.StringUtilities.formatAsJSLiteral(inputString)); |
| }); |
| |
| it('does not use back ticks when content contains ${', () => { |
| const inputString = '\'foo\' "and" ${bar}'; |
| assert.strictEqual('\'\\\'foo\\\' "and" ${bar}\'', Platform.StringUtilities.formatAsJSLiteral(inputString)); |
| }); |
| |
| it('should escape lone leading surrogates', () => { |
| const inputString = '\uD800 \uDA00 \uDBFF'; |
| assert.strictEqual('\'\\uD800 \\uDA00 \\uDBFF\'', Platform.StringUtilities.formatAsJSLiteral(inputString)); |
| }); |
| |
| it('should escape lone trail surrogates', () => { |
| const inputString = '\uDC00 \uDEEE \uDFFF'; |
| assert.strictEqual('\'\\uDC00 \\uDEEE \\uDFFF\'', Platform.StringUtilities.formatAsJSLiteral(inputString)); |
| }); |
| |
| it('should not escape valid surrogate pairs', () => { |
| const inputString = '\uD800\uDC00 \uDA00\uDEEE \uDBFF\uDFFF'; |
| assert.strictEqual(`'${inputString}'`, Platform.StringUtilities.formatAsJSLiteral(inputString)); |
| }); |
| |
| it('should escape invalid surrogate pairs', () => { |
| const inputString = '\uDC00\uD800 \uDA00\uDA00 \uDEEE\uDEEE'; |
| const expectedString = '\'\\uDC00\\uD800 \\uDA00\\uDA00 \\uDEEE\\uDEEE\''; |
| assert.strictEqual(expectedString, Platform.StringUtilities.formatAsJSLiteral(inputString)); |
| }); |
| |
| it('escapes whitespace characters appropriately', () => { |
| const inputString = |
| '\t\n\v\f\r \x85\xA0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u2028\u2029\u202F\u205F\u3000'; |
| const expectedString = |
| '\\t\\n\\v\\f\\r \\x85\xA0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u2028\u2029\u202F\u205F\u3000'; |
| assert.strictEqual('\'' + expectedString + '\'', Platform.StringUtilities.formatAsJSLiteral(inputString)); |
| }); |
| |
| it('escapes problematic script tags', () => { |
| const inputString = '<!-- <script </script'; |
| const expectedString = String.raw`\x3C!-- \x3Cscript \x3C/script`; |
| assert.strictEqual('\'' + expectedString + '\'', Platform.StringUtilities.formatAsJSLiteral(inputString)); |
| }); |
| |
| it('escapes \\x00-\\x1F and \\x7F-\\x9F', () => { |
| const inputStrings = [ |
| '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', '\b', '\v', '\f', '\x0E', '\x0F', |
| '\x10', '\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', '\x1A', '\x1B', '\x1C', |
| '\x1D', '\x1E', '\x1F', '\x80', '\x81', '\x82', '\x83', '\x84', '\x85', '\x86', '\x87', '\x88', '\x89', |
| '\x8A', '\x8B', '\x8C', '\x8D', '\x8E', '\x8F', '\x90', '\x91', '\x92', '\x93', '\x94', '\x95', '\x96', |
| '\x97', '\x98', '\x99', '\x9A', '\x9B', '\x9C', '\x9D', '\x9E', '\x9F', |
| ]; |
| const expectedStrings = [ |
| '\'\\x00\'', '\'\\x01\'', '\'\\x02\'', '\'\\x03\'', '\'\\x04\'', '\'\\x05\'', '\'\\x06\'', '\'\\x07\'', |
| '\'\\b\'', '\'\\v\'', '\'\\f\'', '\'\\x0E\'', '\'\\x0F\'', '\'\\x10\'', '\'\\x11\'', '\'\\x12\'', |
| '\'\\x13\'', '\'\\x14\'', '\'\\x15\'', '\'\\x16\'', '\'\\x17\'', '\'\\x18\'', '\'\\x19\'', '\'\\x1A\'', |
| '\'\\x1B\'', '\'\\x1C\'', '\'\\x1D\'', '\'\\x1E\'', '\'\\x1F\'', '\'\\x80\'', '\'\\x81\'', '\'\\x82\'', |
| '\'\\x83\'', '\'\\x84\'', '\'\\x85\'', '\'\\x86\'', '\'\\x87\'', '\'\\x88\'', '\'\\x89\'', '\'\\x8A\'', |
| '\'\\x8B\'', '\'\\x8C\'', '\'\\x8D\'', '\'\\x8E\'', '\'\\x8F\'', '\'\\x90\'', '\'\\x91\'', '\'\\x92\'', |
| '\'\\x93\'', '\'\\x94\'', '\'\\x95\'', '\'\\x96\'', '\'\\x97\'', '\'\\x98\'', '\'\\x99\'', '\'\\x9A\'', |
| '\'\\x9B\'', '\'\\x9C\'', '\'\\x9D\'', '\'\\x9E\'', '\'\\x9F\'', |
| ]; |
| assert.strictEqual(expectedStrings.join(), inputStrings.map(Platform.StringUtilities.formatAsJSLiteral).join()); |
| }); |
| |
| it('escapes backslashes', () => { |
| const inputString = '\\'; |
| const expectedString = String.raw`\\`; |
| assert.strictEqual('\'' + expectedString + '\'', Platform.StringUtilities.formatAsJSLiteral(inputString)); |
| }); |
| }); |
| |
| describe('findUnclosedCssQuote', () => { |
| it('correctly finds unclosed quotes', () => { |
| assert.strictEqual(Platform.StringUtilities.findUnclosedCssQuote('\'de'), Platform.StringUtilities.SINGLE_QUOTE); |
| assert.strictEqual( |
| Platform.StringUtilities.findUnclosedCssQuote('abc\'de\'f\'g'), Platform.StringUtilities.SINGLE_QUOTE); |
| assert.strictEqual( |
| Platform.StringUtilities.findUnclosedCssQuote('abc\\\'de\'fg'), Platform.StringUtilities.SINGLE_QUOTE); |
| assert.strictEqual( |
| Platform.StringUtilities.findUnclosedCssQuote('\'ab"c\'de\\\'f\'g'), Platform.StringUtilities.SINGLE_QUOTE); |
| assert.strictEqual(Platform.StringUtilities.findUnclosedCssQuote('"de'), Platform.StringUtilities.DOUBLE_QUOTE); |
| assert.strictEqual( |
| Platform.StringUtilities.findUnclosedCssQuote('a\\"b\\""c\'de\'f\'g'), Platform.StringUtilities.DOUBLE_QUOTE); |
| assert.strictEqual( |
| Platform.StringUtilities.findUnclosedCssQuote('"ab"c"de\\\'f\'g'), Platform.StringUtilities.DOUBLE_QUOTE); |
| assert.strictEqual(Platform.StringUtilities.findUnclosedCssQuote('a'), ''); |
| assert.strictEqual(Platform.StringUtilities.findUnclosedCssQuote('"ab"c\'de\'f'), ''); |
| assert.strictEqual(Platform.StringUtilities.findUnclosedCssQuote('"a\\\'b"c\\\'de\'f\\\'\''), ''); |
| }); |
| }); |
| |
| describe('countUnmatchedLeftParentheses', () => { |
| it('correctly counts unmatched left parentheses', () => { |
| assert.strictEqual(Platform.StringUtilities.countUnmatchedLeftParentheses('a(b'), 1); |
| assert.strictEqual(Platform.StringUtilities.countUnmatchedLeftParentheses('a(b)'), 0); |
| assert.strictEqual(Platform.StringUtilities.countUnmatchedLeftParentheses(')a(b)'), 0); |
| assert.strictEqual(Platform.StringUtilities.countUnmatchedLeftParentheses(')a(()bc(d(f)('), 3); |
| assert.strictEqual(Platform.StringUtilities.countUnmatchedLeftParentheses('"(\')"'), 0); |
| assert.strictEqual(Platform.StringUtilities.countUnmatchedLeftParentheses('("(\')"'), 1); |
| assert.strictEqual(Platform.StringUtilities.countUnmatchedLeftParentheses('abc(def"g(h)"i)jkl'), 0); |
| }); |
| }); |
| |
| describe('sprintf', () => { |
| it('correctly deals with empty format string', () => { |
| assert.strictEqual(Platform.StringUtilities.sprintf(''), ''); |
| assert.strictEqual(Platform.StringUtilities.sprintf('', 1), ''); |
| }); |
| |
| it('replaces %% with %', () => { |
| assert.strictEqual(Platform.StringUtilities.sprintf('%%s %%d %%f'), '%s %d %f'); |
| }); |
| |
| it('correctly substitutes %d', () => { |
| assert.strictEqual(Platform.StringUtilities.sprintf('%d', NaN), '0'); |
| assert.strictEqual(Platform.StringUtilities.sprintf('%d days', 1.5), '1 days'); |
| }); |
| |
| it('correctly substitutes %d with precision', () => { |
| assert.strictEqual(Platform.StringUtilities.sprintf('%.1d', 2), '2'); |
| assert.strictEqual(Platform.StringUtilities.sprintf('%.2d', 3), '03'); |
| assert.strictEqual(Platform.StringUtilities.sprintf('%.2d', 333), '333'); |
| }); |
| |
| it('correctly substitutes %f', () => { |
| assert.strictEqual(Platform.StringUtilities.sprintf('%f', NaN), '0'); |
| assert.strictEqual(Platform.StringUtilities.sprintf('%f', 1), '1'); |
| }); |
| |
| it('correctly substitutes %f with precision', () => { |
| assert.strictEqual(Platform.StringUtilities.sprintf('%.2f', NaN), '0.00'); |
| assert.strictEqual(Platform.StringUtilities.sprintf('%.2f', 1), '1.00'); |
| assert.strictEqual(Platform.StringUtilities.sprintf('%.2f', 1.23456), '1.23'); |
| assert.strictEqual(Platform.StringUtilities.sprintf('%.3f', 1.23456), '1.235'); |
| }); |
| |
| it('correctly substitutes %s', () => { |
| assert.strictEqual(Platform.StringUtilities.sprintf('Hello %s!', 'World'), 'Hello World!'); |
| assert.strictEqual(Platform.StringUtilities.sprintf('Hello %s!', '%d', 1), 'Hello %d!'); |
| }); |
| |
| it('correctly substitutes %s with precision', () => { |
| assert.strictEqual(Platform.StringUtilities.sprintf('Hello %.1s!', 'World'), 'Hello W!'); |
| assert.strictEqual(Platform.StringUtilities.sprintf('Hello %.10s!', 'World'), 'Hello World!'); |
| }); |
| |
| it('triggers correct type conversion', () => { |
| const obj = { |
| toString() { |
| return '5'; |
| }, |
| valueOf() { |
| return 6; |
| }, |
| }; |
| assert.strictEqual(Platform.StringUtilities.sprintf('%d', obj), '6'); |
| assert.strictEqual(Platform.StringUtilities.sprintf('%.2f', obj), '6.00'); |
| assert.strictEqual(Platform.StringUtilities.sprintf('%s', obj), '5'); |
| }); |
| |
| it('deals with parameter indices', () => { |
| assert.strictEqual(Platform.StringUtilities.sprintf('%2$s %1$s!', 'World', 'Hello'), 'Hello World!'); |
| assert.throws(() => Platform.StringUtilities.sprintf('%0$s', 'World')); |
| }); |
| |
| it('signals error when too few parameters are given', () => { |
| assert.throws(() => Platform.StringUtilities.sprintf('%2$s', 'World')); |
| assert.throws(() => Platform.StringUtilities.sprintf('%2$s %s!', 'World', 'Hello')); |
| assert.throws(() => Platform.StringUtilities.sprintf('%s %d', 'World')); |
| }); |
| }); |
| |
| describe('LowerCaseString', () => { |
| function fnExpectingLowerCaseString(lowerCaseString: Platform.StringUtilities.LowerCaseString): void { |
| lowerCaseString; |
| } |
| |
| // @ts-expect-error Passing a plain string when LowerCaseString is expected |
| fnExpectingLowerCaseString('Foo'); |
| |
| const lower = Platform.StringUtilities.toLowerCaseString('lower case string'); |
| fnExpectingLowerCaseString(lower); |
| }); |
| |
| describe('replaceLast', () => { |
| it('should return the input string when the search is not found', () => { |
| const output = Platform.StringUtilities.replaceLast('input', 'search', 'repl'); |
| assert.strictEqual(output, 'input'); |
| }); |
| |
| it('should replace the occurrance when the search exists inside the input', () => { |
| const output = Platform.StringUtilities.replaceLast('input', 'pu', 'r'); |
| assert.strictEqual(output, 'inrt'); |
| }); |
| |
| it('should replace the last occurrence when there are multiple matches', () => { |
| const output = Platform.StringUtilities.replaceLast('inpuput', 'pu', 'r'); |
| assert.strictEqual(output, 'inpurt'); |
| }); |
| }); |
| |
| describe('stringifyWithPrecision', () => { |
| it('should stringify with 2 precision if precision argument is not given', () => { |
| assert.strictEqual('0.69', Platform.StringUtilities.stringifyWithPrecision(0.685733)); |
| }); |
| |
| it('should stringify with given precision', () => { |
| assert.strictEqual('0.686', Platform.StringUtilities.stringifyWithPrecision(0.685733, 3)); |
| }); |
| }); |
| |
| describe('concatBase64', () => { |
| it('correctly concatenates two base64 strings', () => { |
| const str = 'This is a small sample sentence for encoding.'; |
| const strAsBase64 = globalThis.btoa(str); |
| |
| for (let i = 0; i < str.length; ++i) { |
| const lhs = globalThis.btoa(str.substring(0, i)); |
| const rhs = globalThis.btoa(str.substring(i)); |
| |
| assert.strictEqual(Platform.StringUtilities.concatBase64(lhs, rhs), strAsBase64); |
| } |
| }); |
| }); |
| |
| describe('toKebabCase', () => { |
| const toKebabCase = Platform.StringUtilities.toKebabCase; |
| it('should convert camelCase to kebab-case', () => { |
| assert.strictEqual(toKebabCase('activeKeybindSet'), 'active-keybind-set'); |
| }); |
| |
| it('should convert PascalCase to kebab-case', () => { |
| assert.strictEqual(toKebabCase('MediaPanelSplitViewState'), 'media-panel-split-view-state'); |
| }); |
| |
| it('should convert snake_case to kebab-case', () => { |
| assert.strictEqual(toKebabCase('recorder_preferred_copy_format'), 'recorder-preferred-copy-format'); |
| }); |
| |
| it('should convert UPPER_SNAKE_CASE to kebab-case', () => { |
| assert.strictEqual(toKebabCase('REGULAR_BREAKPOINT'), 'regular-breakpoint'); |
| }); |
| |
| it('should handle uppercase acronyms as words', () => { |
| assert.strictEqual(toKebabCase('showUAShadowDOM'), 'show-ua-shadow-dom'); |
| }); |
| |
| it('should handle uppercase acronyms as words', () => { |
| assert.strictEqual(toKebabCase('showUAShadowDOM'), 'show-ua-shadow-dom'); |
| }); |
| |
| it('should preserve \'.\' characters', () => { |
| assert.strictEqual( |
| toKebabCase('InspectorView.screencastSplitViewState'), 'inspector-view.screencast-split-view-state'); |
| assert.strictEqual(toKebabCase('version1.2.3'), 'version-1.2.3'); |
| }); |
| |
| it('should handle numeronyms', () => { |
| assert.strictEqual(toKebabCase('lighthouse.cat_a11y'), 'lighthouse.cat-a11y'); |
| assert.strictEqual(toKebabCase('i18n'), 'i18n'); |
| assert.strictEqual(toKebabCase('timeline-v8-runtime-call-stats'), 'timeline-v8-runtime-call-stats'); |
| }); |
| |
| it('should handle numbers', () => { |
| assert.strictEqual(toKebabCase('Margin: 2px'), 'margin-2px'); |
| assert.strictEqual(toKebabCase('Margin2px'), 'margin-2px'); |
| assert.strictEqual(toKebabCase('Layers 3D display'), 'layers-3d-display'); |
| assert.strictEqual(toKebabCase('perfmonActiveIndicators2'), 'perfmon-active-indicators-2'); |
| assert.strictEqual( |
| toKebabCase('HideIssueByCodeSetting-Experiment-2021'), 'hide-issue-by-code-setting-experiment-2021'); |
| }); |
| |
| it('should handle mixed cases', () => { |
| assert.strictEqual(toKebabCase('CamelCase_with.DOTS123'), 'camel-case-with.dots-123'); |
| }); |
| }); |
| |
| describe('toKebabCaseKeys', () => { |
| const {toKebabCaseKeys} = Platform.StringUtilities; |
| |
| it('converts dictionaries with numeric values', () => { |
| assert.deepEqual( |
| toKebabCaseKeys({['FOO_BAR']: 1, ['FOO_BAZ']: 2}), |
| {['foo-bar']: 1, ['foo-baz']: 2}, |
| ); |
| }); |
| |
| it('converts dictionaries with mixed values', () => { |
| assert.deepEqual( |
| toKebabCaseKeys({['FOO_BAR']: 1, ['FOO_BAZ']: 'test'}), |
| {['foo-bar']: 1, ['foo-baz']: 'test'}, |
| ); |
| }); |
| }); |
| |
| describe('toSnakeCase', () => { |
| it('should convert a string to snake_case', () => { |
| const testCases = [ |
| {input: 'hello world', expected: 'hello_world'}, |
| {input: 'LastName', expected: 'last_name'}, |
| {input: 'Already_snake_case', expected: 'already_snake_case'}, |
| {input: 'singleword', expected: 'singleword'}, |
| {input: 'String With Spaces', expected: 'string_with_spaces'}, |
| {input: ' Multiple Spaces ', expected: 'multiple_spaces'}, |
| {input: ' Leading And Trailing Spaces ', expected: 'leading_and_trailing_spaces'}, |
| {input: 'string-with-hyphens', expected: 'string_with_hyphens'}, |
| {input: 'path/to/file', expected: 'path_to_file'}, |
| {input: 'data.column_name', expected: 'data_column_name'}, |
| {input: 'item_#id_value', expected: 'item_id_value'}, |
| {input: 'text!@#$%^&*()', expected: 'text'}, |
| {input: 'string 123 with numbers', expected: 'string_123_with_numbers'}, |
| {input: 'ALLCAPS', expected: 'allcaps'}, |
| {input: ' _--Some__Mixed-String_With_123!!_ ', expected: 'some_mixed_string_with_123'}, |
| {input: '', expected: ''}, |
| {input: '---', expected: ''}, |
| {input: ' _ ', expected: ''}, |
| {input: 'myVariableName', expected: 'my_variable_name'}, |
| {input: 'aNewHope', expected: 'a_new_hope'}, |
| {input: 'APIFlags', expected: 'api_flags'}, |
| {input: 'HTTPRequest', expected: 'http_request'}, |
| {input: 'version2Update', expected: 'version_2_update'}, |
| {input: 'data-for-HTTPRequest', expected: 'data_for_http_request'}, |
| {input: 'My-API-is-Awesome', expected: 'my_api_is_awesome'}, |
| ]; |
| testCases.forEach(({input, expected}) => { |
| assert.strictEqual(Platform.StringUtilities.toSnakeCase(input), expected, `Input: ${input}`); |
| }); |
| }); |
| }); |
| }); |