Files
core-js/tests/unit-global/es.string.replace.js
T
2026-06-26 21:20:44 +07:00

238 lines
9.6 KiB
JavaScript

/* eslint-disable prefer-regex-literals, regexp/no-unused-capturing-group, unicorn/prefer-string-replace-all -- required for testing */
import { GLOBAL, NATIVE, STRICT } from '../helpers/constants.js';
import { patchRegExp$exec } from '../helpers/helpers.js';
const Symbol = GLOBAL.Symbol || {};
const run = assert => {
assert.isFunction(''.replace);
assert.arity(''.replace, 2);
assert.name(''.replace, 'replace');
assert.looksNative(''.replace);
assert.nonEnumerable(String.prototype, 'replace');
let instance = Object(true);
instance.replace = String.prototype.replace;
assert.same(instance.replace(true, 1), '1', 'S15.5.4.11_A1_T1');
instance = Object(false);
instance.replace = String.prototype.replace;
assert.same(instance.replace(false, undefined), 'undefined', 'S15.5.4.11_A1_T2');
assert.same('gnulluna'.replace(null, (a1, a2) => `${ a2 }`), 'g1una', 'S15.5.4.11_A1_T4');
assert.same('gnulluna'.replace(null, () => { /* empty */ }), 'gundefineduna', 'S15.5.4.11_A1_T5');
assert.same(Object('undefined').replace(undefined, (a1, a2) => a2 + 42), '42', 'S15.5.4.11_A1_T6');
assert.same('undefined'.replace('e', undefined), 'undundefinedfined', 'S15.5.4.11_A1_T7');
assert.same(String({
toString() { /* empty */ },
}).replace(/e/g, undefined), 'undundefinedfinundefinedd', 'S15.5.4.11_A1_T8');
assert.same(new String({
valueOf() { /* empty */ },
toString: undefined,
}).replace(function () { /* empty */ }(), (a1, a2, a3) => a1 + a2 + a3), 'undefined0undefined', 'S15.5.4.11_A1_T9');
assert.same('ABB\u0041BABAB'.replace({
toString() {
return '\u0041B';
},
}, () => { /* empty */ }), 'undefinedBABABAB', 'S15.5.4.11_A1_T10');
if (NATIVE) {
try {
'ABB\u0041BABAB'.replace({
toString() {
throw new Error('insearchValue');
},
}, {
toString() {
throw new Error('inreplaceValue');
},
});
assert.avoid('S15.5.4.11_A1_T11 #1 lead to throwing exception');
} catch (error) {
assert.same(error.message, 'insearchValue', 'S15.5.4.11_A1_T11 #2');
}
try {
Object('ABB\u0041BABAB').replace({
toString() {
return {};
},
valueOf() {
throw new Error('insearchValue');
},
}, {
toString() {
throw new Error('inreplaceValue');
},
});
assert.avoid('S15.5.4.11_A1_T12 #1 lead to throwing exception');
} catch (error) {
assert.same(error.message, 'insearchValue', 'S15.5.4.11_A1_T12 #2');
}
}
try {
'ABB\u0041BABAB\u0031BBAA'.replace({
toString() {
return {};
},
valueOf() {
throw new Error('insearchValue');
},
}, {
toString() {
return 1;
},
});
assert.avoid('S15.5.4.11_A1_T13 #1 lead to throwing exception');
} catch (error) {
assert.same(error.message, 'insearchValue', 'S15.5.4.11_A1_T13 #2');
}
assert.same('ABB\u0041BABAB\u0037\u0037BBAA'.replace(new RegExp('77'), 1), 'ABBABABAB\u0031BBAA', 'S15.5.4.11_A1_T14');
instance = Object(1100.00777001);
instance.replace = String.prototype.replace;
try {
instance.replace({
toString() {
return /77/;
},
}, 1);
assert.avoid('S15.5.4.11_A1_T15 #1 lead to throwing exception');
} catch (error) {
assert.true(error instanceof TypeError, 'S15.5.4.11_A1_T15 #2');
}
instance = Object(1100.00777001);
instance.replace = String.prototype.replace;
try {
instance.replace(/77/, {
toString() {
return (a1, a2) => `${ a2 }z`;
},
});
assert.avoid('S15.5.4.11_A1_T16 #1 lead to throwing exception');
} catch (error) {
assert.true(error instanceof TypeError, 'S15.5.4.11_A1_T16 #2');
}
assert.same('asdf'.replace(RegExp('', 'g'), '1'), '1a1s1d1f1', 'S15.5.4.11_A1_T17');
assert.same('She sells seashells by the seashore.'.replace(/sh/g, 'sch'), 'She sells seaschells by the seaschore.', 'S15.5.4.11_A2_T1');
assert.same('She sells seashells by the seashore.'.replace(/sh/g, '$$sch'), 'She sells sea$schells by the sea$schore.', 'S15.5.4.11_A2_T2');
assert.same('She sells seashells by the seashore.'.replace(/sh/g, '$&sch'), 'She sells seashschells by the seashschore.', 'S15.5.4.11_A2_T3');
assert.same('She sells seashells by the seashore.'.replace(/sh/g, '$`sch'), 'She sells seaShe sells seaschells by the seaShe sells seashells by the seaschore.', 'S15.5.4.11_A2_T4');
assert.same('She sells seashells by the seashore.'.replace(/sh/g, "$'sch"), 'She sells seaells by the seashore.schells by the seaore.schore.', 'S15.5.4.11_A2_T5');
assert.same('She sells seashells by the seashore.'.replace(/sh/, 'sch'), 'She sells seaschells by the seashore.', 'S15.5.4.11_A2_T6');
assert.same('She sells seashells by the seashore.'.replace(/sh/, '$$sch'), 'She sells sea$schells by the seashore.', 'S15.5.4.11_A2_T7');
assert.same('She sells seashells by the seashore.'.replace(/sh/, '$&sch'), 'She sells seashschells by the seashore.', 'S15.5.4.11_A2_T8');
assert.same('She sells seashells by the seashore.'.replace(/sh/, '$`sch'), 'She sells seaShe sells seaschells by the seashore.', 'S15.5.4.11_A2_T9');
assert.same('She sells seashells by the seashore.'.replace(/sh/, "$'sch"), 'She sells seaells by the seashore.schells by the seashore.', 'S15.5.4.11_A2_T10');
assert.same('uid=31'.replace(/(uid=)(\d+)/, '$1115'), 'uid=115', 'S15.5.4.11_A3_T1');
assert.same('uid=31'.replace(/(uid=)(\d+)/, '$11A15'), 'uid=1A15', 'S15.5.4.11_A3_T3');
assert.same('abc12 def34'.replace(/([a-z]+)(\d+)/, (a, b, c) => c + b), '12abc def34', 'S15.5.4.11_A4_T1');
// eslint-disable-next-line regexp/optimal-quantifier-concatenation -- required for testing
assert.same('aaaaaaaaaa,aaaaaaaaaaaaaaa'.replace(/^(a+)\1*,\1+$/, '$1'), 'aaaaa', 'S15.5.4.11_A5_T1');
// https://github.com/zloirock/core-js/issues/471
// eslint-disable-next-line regexp/no-useless-dollar-replacements, regexp/strict -- required for testing
assert.same('{price} Retail'.replace(/{price}/g, '$25.00'), '$25.00 Retail');
// eslint-disable-next-line regexp/prefer-escape-replacement-dollar-char -- required for testing
assert.same('a'.replace(/(.)/, '$0'), '$0');
assert.throws(() => ''.replace.call(Symbol('replace test'), /./, ''), 'throws on symbol context');
assert.same('.a'.replace(new RegExp('a', 'y'), '.'), '.a', 'Replacement for y');
};
QUnit.test('String#replace regression', run);
QUnit.test('RegExp#@@replace appearance', assert => {
const replace = /./[Symbol.replace];
assert.isFunction(replace);
// assert.name(replace, '[Symbol.replace]');
assert.arity(replace, 2);
assert.looksNative(replace);
assert.nonEnumerable(RegExp.prototype, Symbol.replace);
});
QUnit.test('RegExp#@@replace basic behavior', assert => {
assert.same(/([a-z]+)(\d+)/[Symbol.replace]('abc12 def34', (a, b, c) => c + b), '12abc def34');
});
QUnit.test('String#replace delegates to @@replace', assert => {
const string = STRICT ? 'string' : Object('string');
const number = STRICT ? 42 : Object(42);
const object = {};
object[Symbol.replace] = function (a, b) {
return { a, b };
};
assert.same(string.replace(object, 42).a, string);
assert.same(string.replace(object, 42).b, 42);
assert.same(''.replace.call(number, object, 42).a, number);
assert.same(''.replace.call(number, object, 42).b, 42);
const regexp = /./;
regexp[Symbol.replace] = function (a, b) {
return { a, b };
};
assert.same(string.replace(regexp, 42).a, string);
assert.same(string.replace(regexp, 42).b, 42);
assert.same(''.replace.call(number, regexp, 42).a, number);
assert.same(''.replace.call(number, regexp, 42).b, 42);
});
QUnit.test('RegExp#@@replace delegates to exec', assert => {
const exec = function (...args) {
execCalled = true;
return /./.exec.apply(this, args);
};
let execCalled = false;
let re = /[ac]/;
re.exec = exec;
assert.deepEqual(re[Symbol.replace]('abc', 'f'), 'fbc');
assert.true(execCalled);
assert.same(re.lastIndex, 0);
execCalled = false;
re = /[ac]/g;
re.exec = exec;
assert.deepEqual(re[Symbol.replace]('abc', 'f'), 'fbf');
assert.true(execCalled);
assert.same(re.lastIndex, 0);
re = /a/;
// Not a function, should be ignored
re.exec = 3;
assert.deepEqual(re[Symbol.replace]('abc', 'f'), 'fbc');
re = /a/;
// Does not return an object, should throw
re.exec = () => 3;
assert.throws(() => re[Symbol.replace]('abc', 'f'));
});
QUnit.test('RegExp#@@replace correctly handles substitutions', assert => {
const re = /./;
re.exec = function () {
const result = ['23', '7'];
result.groups = { '!!!': '7' };
result.index = 1;
return result;
};
// eslint-disable-next-line regexp/no-useless-dollar-replacements -- false positive
assert.same('1234'.replace(re, '$1'), '174');
// eslint-disable-next-line regexp/no-useless-dollar-replacements -- required for testing
assert.same('1234'.replace(re, '$<!!!>'), '174');
assert.same('1234'.replace(re, '$`'), '114');
assert.same('1234'.replace(re, "$'"), '144');
assert.same('1234'.replace(re, '$$'), '1$4');
assert.same('1234'.replace(re, '$&'), '1234');
// eslint-disable-next-line regexp/prefer-escape-replacement-dollar-char -- required for testing
assert.same('1234'.replace(re, '$x'), '1$x4');
let args;
assert.same('1234'.replace(re, (...$args) => {
args = $args;
return 'x';
}), '1x4');
assert.deepEqual(args, ['23', '7', 1, '1234', { '!!!': '7' }]);
});
QUnit.test('RegExp#@@replace implementation', patchRegExp$exec(run));
QUnit.test('RegExp#@@replace global+unicode empty match at string end', assert => {
// eslint-disable-next-line regexp/no-empty-group -- testing
assert.same('abc'.replace(/(?:)/gu, '-'), '-a-b-c-', 'does not infinite loop on global+unicode empty match');
});