toBe('hello world');
});
test('html_escape encodes angle brackets', function () {
expect(html_escape(''))->toContain('<')
->and(html_escape(''))->toContain('>');
});
test('html_escape encodes double quotes', function () {
expect(html_escape('say "hello"'))->toContain('"');
});
test('html_escape encodes single quotes', function () {
$result = html_escape("it's");
// ENT_QUOTES | ENT_HTML5 produces '
expect($result)->toContain(''');
});
test('html_escape replaces backtick with entity', function () {
expect(html_escape('a`b'))->toContain('`');
});
test('html_escape returns empty string for empty input', function () {
expect(html_escape(''))->toBe('');
});
test('html_escape preserves zero values', function () {
expect(html_escape('0'))->toBe('0')
->and(html_escape(0))->toBe('0');
});
test('html_escape returns empty string for null input', function () {
expect(html_escape(null))->toBe('');
});
test('html_escape does not double-encode existing entities', function () {
expect(html_escape('&'))->toBe('&');
});
test('html_escape handles ampersand in plain text', function () {
expect(html_escape('a&b'))->toBe('a&b');
});
test('html_escape_attr encodes attribute delimiters', function () {
$result = html_escape_attr('" onmouseover="alert(1)');
expect($result)->toBe('" onmouseover="alert(1)');
});
test('html_escape_attr encodes backticks', function () {
expect(html_escape_attr('` autofocus onfocus=alert(1)'))->toBe('` autofocus onfocus=alert(1)');
});
test('html_escape_attr double-encodes a pre-encoded entity', function () {
// an attacker-supplied literal " must not survive into the attribute
// and be decoded back into a quote by the browser; double_encode turns
// the leading '&' of the existing entity into '&' so it stays inert.
expect(html_escape_attr('" onmouseover="alert(1)'))->toBe('" onmouseover="alert(1)');
});
test('html_attributes makes pre-encoded quotes inert', function () {
expect(html_attributes(['value' => '' autofocus onfocus=alert(1)']))
->toBe(" value='' autofocus onfocus=alert(1)'");
});
test('html_escape_url encodes URL attribute delimiters', function () {
expect(html_escape_url('https://example.invalid/?q=" onmouseover="alert(1)'))
->toBe('https://example.invalid/?q=" onmouseover="alert(1)');
});
test('cacti_js_encode escapes script-sensitive characters', function () {
$result = cacti_js_encode("
");
expect($result)->toContain('\u003C')
->and($result)->toContain('\u0027')
->and($result)->not->toContain('');
});
test('cacti_js_encode escapes U+2028 and U+2029 line terminators', function () {
// left literal, these are valid JSON but JavaScript treats them as line
// terminators, breaking parsing of a JSON literal embedded inline in
",
]);
expect($result)->toContain("type='application/json'")
->and($result)->toContain("id='ctx" onmouseover="alert(1)'")
->and($result)->toContain('\u003C\/script\u003E')
->and($result)->not->toContain('
");
});
test('html_text_input escapes value and additional attributes', function () {
$result = html_text_input('rfilter', "a' autofocus", [
'class' => 'ui-state-default',
'onChange' => 'applyFilter()',
'bad attr' => 'drop',
]);
expect($result)->toContain("value='a' autofocus'")
->and($result)->toContain("onChange='applyFilter()'")
->and($result)->not->toContain("badattr='drop'");
});
test('html input helpers preserve zero values', function () {
expect(html_hidden_input('selected_id', 0))->toContain("value='0'")
->and(html_text_input('rfilter', '0'))->toContain("value='0'");
});
test('html navigation encodes script values and confines callback identifiers', function () {
$result = html_nav_bar(
"graphs.php?filter=';&",
10,
2,
10,
100,
5,
'
',
"page');alert(1);//",
"main');alert(1);//"
);
$label_result = html_nav_bar('graphs.php', 10, 1, 10, 5, 5, '
');
expect($result)->toContain('function gotopage(pageNo)')
->and($result)->toContain('\\u003C\\/script\\u003E')
->and($result)->toContain('\\u0027')
->and($label_result)->toContain('<img src=x onerror=alert(1)>')
->and($result)->not->toContain('')
->and($result)->not->toContain("elementId: 'main');alert(1);//'");
});
// =====================================================================
// html_split_string tests
// =====================================================================
test('html_split_string returns short string unchanged', function () {
expect(html_split_string('short string', 90))->toBe('short string');
});
test('html_split_string breaks long string at word boundary', function () {
$words = 'The quick brown fox jumps over the lazy dog and keeps running far away';
$result = html_split_string($words, 30, 10);
expect($result)->toContain('
');
});
test('html_split_string respects forgiveness parameter', function () {
// 40 chars no space, then a space, then more
$str = str_repeat('a', 35) . ' ' . str_repeat('b', 20);
$result = html_split_string($str, 40, 10);
expect($result)->toContain('
');
});
test('html_split_string limits iterations to 5 maximum', function () {
$long = str_repeat('word ', 200);
$result = html_split_string($long, 20, 10);
$breaks = substr_count($result, '
');
// loop runs j=0..4 (5 iterations), breaks when j>4, so up to 5
tags
expect($breaks)->toBeLessThanOrEqual(5);
});