Something went wrong. Try again.
MIRROR: javascript for 🐜's, a tiny runtime with big ambitions ant.themackabu.dev
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295import { test, testDeep, testThrows, summary } from './helpers.js';
console.log('String Tests\n');
let str = 'hello world';test('indexOf found', str.indexOf('world'), 6);test('indexOf first occurrence', str.indexOf('o'), 4);test('indexOf not found', str.indexOf('xyz'), -1);test('indexOf empty string', str.indexOf(''), 0);test('indexOf UTF-16 offset', 'a𝄞bé𝄞'.indexOf('𝄞'), 1);test('indexOf UTF-16 start', 'a𝄞bé𝄞'.indexOf('𝄞', 2), 5);test('indexOf non-ASCII prefix', 'éa'.indexOf('a'), 1);test('indexOf embedded NUL', 'a\0b\0c'.indexOf('\0b'), 1);test('indexOf coerces numeric search', 'a12b'.indexOf(12), 1);test('indexOf missing search is undefined', 'xundefinedy'.indexOf(), 1);test('indexOf coerces string position', 'abcabc'.indexOf('b', '2'), 4);test('indexOf truncates fractional position', 'abcabc'.indexOf('b', 1.9), 1);test('indexOf NaN position is zero', 'abc'.indexOf('a', NaN), 0);test('indexOf positive infinity clamps high', 'abc'.indexOf('', Infinity), 3);test('indexOf negative infinity clamps low', 'abc'.indexOf('a', -Infinity), 0);test('indexOf leading surrogate', '𝄞'.indexOf('\uD834'), 0);test('indexOf trailing surrogate', '𝄞'.indexOf('\uDD1E'), 1);test('indexOf long Unicode first-unit mismatch', 'é'.repeat(100000).indexOf('x'.repeat(10000)), -1);test('indexOf Unicode match after rejected candidates', 'é'.repeat(1000).concat('xyz').indexOf('xyz'), 1000);test('indexOf trailing surrogate and suffix', 'é𝄞x'.indexOf('\uDD1Ex'), 2);
let js = 'JavaScript';test('substring start end', js.substring(0, 4), 'Java');test('substring start only', js.substring(4), 'Script');test('substring swaps if end < start', js.substring(10, 4), 'Script');test('substring astral leading surrogate', '𝄞'.substring(0, 1), '\uD834');test('substring astral trailing surrogate', '𝄞'.substring(1, 2), '\uDD1E');test('substring coerces string indexes', 'abcdef'.substring('2', '4'), 'cd');test('substring truncates fractional indexes', 'abcdef'.substring(1.9, 4.9), 'bcd');test('substring NaN start is zero', 'abcdef'.substring(NaN, 2), 'ab');test('substring undefined end uses length', 'abcdef'.substring(2, undefined), 'cdef');test('substring infinities clamp', 'abcdef'.substring(Infinity, -Infinity), 'abcdef');
let intrinsicOrder = [];let intrinsicReceiver = { indexOf: String.prototype.indexOf, substring: String.prototype.substring, toString() { intrinsicOrder.push('receiver'); return 'abcabc'; }};let intrinsicSearch = { toString() { intrinsicOrder.push('search'); return 'b'; }};let intrinsicPosition = { valueOf() { intrinsicOrder.push('position'); return 2; }};test('indexOf intrinsic coerces receiver/search/position', intrinsicReceiver.indexOf(intrinsicSearch, intrinsicPosition), 4);testDeep('indexOf intrinsic coercion order', intrinsicOrder, ['receiver', 'search', 'position']);
let substringOrder = [];let substringReceiver = { substring: String.prototype.substring, toString() { substringOrder.push('receiver'); return 'abcdef'; }};let substringStart = { valueOf() { substringOrder.push('start'); return 1; }};let substringEnd = { valueOf() { substringOrder.push('end'); return 3; }};test('substring intrinsic coerces indexes', substringReceiver.substring(substringStart, substringEnd), 'bc');testDeep('substring intrinsic coercion order', substringOrder, ['receiver', 'start', 'end']);
function hotStringCalls(value) { return value.indexOf('/') + value.substring(0, 1).charCodeAt(0);}let hotStringResult = 0;for (let i = 0; i < 300; i++) hotStringResult = hotStringCalls('/path');test('hot string intrinsic result', hotStringResult, 47);
function jitIndexOf(receiver, search, position) { return receiver.indexOf(search, position);}for (let i = 0; i < 300; i++) jitIndexOf('abcabc', 'b', 0);function growIntrinsicStack(depth) { if (depth === 0) return 0; return 1 + growIntrinsicStack(depth - 1);}const relocatingSearch = { toString() { growIntrinsicStack(300); const ballast = []; for (let i = 0; i < 2_000; i++) ballast.push({ value: i }); if (ballast.length !== 2_000) throw new Error('allocation failed'); return 'b'; }};test('JIT string intrinsic snapshots args across stack growth', jitIndexOf('abcabc', relocatingSearch, 2), 4);
const savedIndexOf = String.prototype.indexOf;String.prototype.indexOf = function () { return 77; };test('indexOf intrinsic observes overridden builtin', 'abc'.indexOf('b'), 77);String.prototype.indexOf = savedIndexOf;
let customLookupCount = 0;let customReceiver = { get indexOf() { customLookupCount++; return function (value) { return this.prefix + value; }; }, prefix: 'seen:'};test('indexOf intrinsic falls back to custom method', customReceiver.indexOf('x'), 'seen:x');test('indexOf intrinsic observes custom getter once', customLookupCount, 1);test('indexOf spelling on arrays falls back normally', [1, 2, 3].indexOf(2), 1);test('optional string call short-circuits', null?.indexOf('x'), undefined);testThrows('indexOf rejects null receiver', () => String.prototype.indexOf.call(null, 'x'));testThrows('substring rejects undefined receiver', () => String.prototype.substring.call(undefined, 0));testThrows('indexOf rejects Symbol search', () => 'abc'.indexOf(Symbol('x')));testThrows('indexOf rejects BigInt position', () => 'abc'.indexOf('a', 0n));testThrows('substring rejects Symbol start', () => 'abc'.substring(Symbol('x')));
let fox = 'The quick brown fox';test('slice start end', fox.slice(0, 3), 'The');test('slice start only', fox.slice(10), 'brown fox');test('slice negative start', fox.slice(-3), 'fox');test('slice negative end', fox.slice(0, -4), 'The quick brown');test('slice astral leading surrogate', '𝄞'.slice(0, 1), '\uD834');test('slice astral trailing surrogate', '𝄞'.slice(1, 2), '\uDD1E');test('slice astral split boundaries', '𝄞x💙'.slice(1, 4), '\uDD1Ex\uD83D');test('substr astral leading surrogate', '𝄞'.substr(0, 1), '\uD834');test('substr astral trailing surrogate', '𝄞'.substr(1, 1), '\uDD1E');test('substr coerces string start', 'abcdef'.substr('2', 3), 'cde');test('substr coerces string length', 'abcdef'.substr(2, '3'), 'cde');test('substr truncates negative fractional start', 'abcdef'.substr(-1.5), 'f');
let csv = 'apple,banana,cherry';let parts = csv.split(',');test('split length', parts.length, 3);test('split first', parts[0], 'apple');test('split last', parts[2], 'cherry');
let chars = 'hello'.split('');test('split empty', chars.length, 5);test('split empty first', chars[0], 'h');
test('includes true', 'quick brown fox'.includes('quick'), true);test('includes false', 'quick brown fox'.includes('cat'), false);test('includes empty', 'hello'.includes(''), true);
test('startsWith true', 'Hello, World!'.startsWith('Hello'), true);test('startsWith false', 'Hello, World!'.startsWith('World'), false);test('startsWith position true', 'Hello, World!'.startsWith('World', 7), true);test('startsWith position false', 'Hello, World!'.startsWith('Hello', 1), false);test('startsWith position clamps negative', 'Hello'.startsWith('Hello', -1), true);test('startsWith position clamps past end for empty search', 'Hello'.startsWith('', 99), true);test('startsWith position past end rejects non-empty search', 'Hello'.startsWith('H', 99), false);test('startsWith Hono query fast path shape', 'http://localhost:18080/?q=hello'.startsWith('q', 24), true);test('startsWith astral leading surrogate', '𝄞'.startsWith('\uD834'), true);test('startsWith astral trailing surrogate', '𝄞'.startsWith('\uDD1E', 1), true);
test('endsWith true', 'index.html'.endsWith('.html'), true);test('endsWith false', 'index.html'.endsWith('.js'), false);test('endsWith end position true', 'index.html'.endsWith('index', 5), true);test('endsWith end position false', 'index.html'.endsWith('.html', 5), false);test('endsWith end position clamps negative for empty search', 'index.html'.endsWith('', -1), true);test('endsWith end position clamps negative for non-empty search', 'index.html'.endsWith('i', -1), false);test('endsWith astral leading surrogate', '𝄞'.endsWith('\uD834', 1), true);test('endsWith astral trailing surrogate', '𝄞'.endsWith('\uDD1E'), true);
test('toUpperCase', 'hello'.toUpperCase(), 'HELLO');test('toLowerCase', 'HELLO'.toLowerCase(), 'hello');
test('trim', ' hello '.trim(), 'hello');test('trimStart', ' hello'.trimStart(), 'hello');test('trimEnd', 'hello '.trimEnd(), 'hello');
test('padStart', '5'.padStart(3, '0'), '005');test('padEnd', '5'.padEnd(3, '0'), '500');
test('repeat', 'ab'.repeat(3), 'ababab');
test('charAt', 'hello'.charAt(1), 'e');test('charAt default index', 'hello'.charAt(), 'h');test('charAt coercion', 'hello'.charAt('1'), 'e');test('charCodeAt', 'ABC'.charCodeAt(0), 65);test('charCodeAt default index', 'ABC'.charCodeAt(), 65);test('charCodeAt coercion', 'ABC'.charCodeAt('1'), 66);test('codePointAt ascii', 'ABC'.codePointAt(0), 65);test('codePointAt default index', 'ABC'.codePointAt(), 65);test('codePointAt coercion', 'ABC'.codePointAt('1'), 66);test('codePointAt out of bounds', 'ABC'.codePointAt(10), undefined);test('codePointAt utf8 2-byte', 'é'.codePointAt(0), 233);test('codePointAt utf8 3-byte', '中'.codePointAt(0), 20013);test('codePointAt utf8 4-byte', '😀'.codePointAt(0), 128512);test('charAt astral leading surrogate', '💙'.charAt(0).charCodeAt(0), 0xD83D);test('charAt astral trailing surrogate', '💙'.charAt(1).charCodeAt(0), 0xDC99);
test('replace', 'hello world'.replace('world', 'there'), 'hello there');test('replaceAll', 'a-b-c'.replaceAll('-', '_'), 'a_b_c');
test( 'template missing placeholders stay empty', 'Hello, {{name}}. Missing: {{missing}}.'.template({ name: 'Ant' }), 'Hello, Ant. Missing: .');test( 'template placeholder values use normal string coercion', '{{nil}}/{{undef}}/{{obj}}/{{arr}}/{{ok}}'.template({ nil: null, undef: undefined, obj: { toString() { return 'custom'; } }, arr: [1, 2], ok: false }), 'null/undefined/custom/1,2/false');test( 'template unterminated placeholders remain literal', 'before {{name after'.template({ name: 'ignored' }), 'before {{name after');
testDeep('match', 'test123'.match(/\d+/), ['123']);
test('at positive', 'hello'.at(1), 'e');test('at negative', 'hello'.at(-1), 'o');
test('length', 'hello'.length, 5);test('bracket access', 'hello'[0], 'h');test('bracket access astral leading surrogate', '💙'[0].charCodeAt(0), 0xD83D);test('bracket access astral trailing surrogate', '💙'[1].charCodeAt(0), 0xDC99);
test('concat', 'hello'.concat(' ', 'world'), 'hello world');
test('lastIndexOf', 'hello world world'.lastIndexOf('world'), 12);test('lastIndexOf utf16 result', 'abc🎈def🎈xyz'.lastIndexOf('🎈'), 8);test('lastIndexOf utf16 position', 'abc🎈def🎈xyz'.lastIndexOf('🎈', 7), 3);test('lastIndexOf utf16 position on match', 'abc🎈def🎈xyz'.lastIndexOf('🎈', 8), 8);test('lastIndexOf astral search miss', 'abc🎈def'.lastIndexOf('💙'), -1);test('lastIndexOf empty search utf16 clamp', 'ab🎈cd'.lastIndexOf(''), 6);test('lastIndexOf empty search with position', 'ab🎈cd'.lastIndexOf('', 3), 3);test('lastIndexOf mid-surrogate position floors', '𝔀b'.lastIndexOf('b', 1), -1);test('lastIndexOf mid-surrogate keeps earlier match', '𝔀b'.lastIndexOf('𝔀', 1), 0);test('lastIndexOf exact boundary after astral', '𝔀b'.lastIndexOf('b', 2), 2);test('lastIndexOf empty search mid-surrogate not floored', '𝔀b'.lastIndexOf('', 1), 1);test('lastIndexOf multibyte BMP boundary', 'éb'.lastIndexOf('b', 1), 1);
test('String.fromCharCode', String.fromCharCode(65, 66, 67), 'ABC');test('String.raw tagged template', String.raw`line1\nline2`, 'line1\\nline2');test('String.raw substitutions', String.raw({ raw: ['a', 'b', 'c'] }, 1, 2), 'a1b2c');
test('empty string length', ''.length, 0);test('empty indexOf', ''.indexOf('x'), -1);test('empty includes empty', ''.includes(''), true);test('empty startsWith empty', ''.startsWith(''), true);
let nfc = '\u0041\u006d\u0065\u0301\u006c\u0069\u0065';test('normalize default (NFC)', nfc.normalize(), 'Am\u00E9lie');test('normalize NFC explicit', nfc.normalize('NFC'), 'Am\u00E9lie');
let composed = 'Am\u00E9lie';test('normalize NFD', composed.normalize('NFD'), 'Ame\u0301lie');
test('normalize NFKC fi ligature', '\uFB01'.normalize('NFKC'), 'fi');test('normalize NFKC fullwidth', '\uFF21'.normalize('NFKC'), 'A');
test('normalize NFKD fi ligature', '\uFB01'.normalize('NFKD'), 'fi');test('normalize NFKD fullwidth', '\uFF21'.normalize('NFKD'), 'A');
test('normalize empty string', ''.normalize(), '');test('normalize ascii passthrough', 'hello'.normalize(), 'hello');test('normalize no arg same as NFC', '\u00E9'.normalize(), '\u00E9');
let template = `Value: ${1 + 2}`;test('template literal', template, 'Value: 3');
let multi = `line1line2`;test('multiline template', multi.includes('\n'), true);
summary();