diff --git a/tests/meson.build b/tests/meson.build
index 0b333ad10..ef66ba835 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -24,6 +24,7 @@ unit_tests = [
'injectionManager',
'insertSorted',
'jsParse',
+ 'markup',
]
foreach test : unit_tests
@@ -42,7 +43,6 @@ foreach test : unit_tests
endforeach
legacy_tests = [
- 'markup',
'params',
'signalTracker',
'url',
diff --git a/tests/unit/markup.js b/tests/unit/markup.js
index 78c120976..e48e4bd32 100644
--- a/tests/unit/markup.js
+++ b/tests/unit/markup.js
@@ -1,150 +1,216 @@
-// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
-
// Test cases for MessageList markup parsing
-const JsUnit = imports.jsUnit;
import Pango from 'gi://Pango';
+import 'resource:///org/gnome/shell/ui/environment.js';
import {fixMarkup} from 'resource:///org/gnome/shell/misc/util.js';
-/**
- * Assert that `input`, assumed to be markup, gets "fixed" to `output`,
- * which is valid markup. If `output` is null, `input` is expected to
- * convert to itself
- *
- * @param {string} input the input
- * @param {string} output the output
- */
-function assertConverts(input, output) {
- if (!output)
- output = input;
- let fixed = fixMarkup(input, true);
- JsUnit.assertEquals(output, fixed);
+describe('fixMarkup()', () => {
+ function convertAndEscape(text) {
+ return {
+ converted: fixMarkup(text, true),
+ escaped: fixMarkup(text, false),
+ };
+ }
- let parsed = false;
- try {
- Pango.parse_markup(fixed, -1, '');
- parsed = true;
- } catch (e) {}
- JsUnit.assertEquals(true, parsed);
-}
+ beforeAll(() => {
+ jasmine.addMatchers({
+ toParseCorrectlyAndMatch: () => {
+ function isMarkupValid(markup) {
+ try {
+ Pango.parse_markup(markup, -1, '');
+ } catch (e) {
+ return false;
+ }
+ return true;
+ }
+ return {
+ compare: (actual, expected) => {
+ if (!expected)
+ expected = actual;
-/**
- * Assert that `input`, assumed to be plain text, gets escaped to `output`,
- * which is valid markup.
- *
- * @param {string} input the input
- * @param {string} output the output
- */
-function assertEscapes(input, output) {
- let fixed = fixMarkup(input, false);
- JsUnit.assertEquals(output, fixed);
+ return {
+ pass: isMarkupValid(actual) && actual === expected,
+ message: `Expected "${actual}" to parse correctly and equal "${expected}"`,
+ };
+ },
+ };
+ },
+ });
+ });
- let parsed = false;
- try {
- Pango.parse_markup(fixed, -1, '');
- parsed = true;
- } catch (e) {}
- JsUnit.assertEquals(true, parsed);
-}
+ it('does not do anything on no markup', () => {
+ const text = 'foo';
+ const result = convertAndEscape(text);
+ expect(result.converted).toParseCorrectlyAndMatch(text);
+ expect(result.escaped).toParseCorrectlyAndMatch(text);
+ });
+ it('converts and escapes bold markup', () => {
+ const text = 'foo';
+ const {converted, escaped} = convertAndEscape(text);
+ expect(converted).toParseCorrectlyAndMatch();
+ expect(escaped).toParseCorrectlyAndMatch('<b>foo</b>');
+ });
+ it('converts and escapes italic markup', () => {
+ const text = 'something foo';
+ const {converted, escaped} = convertAndEscape(text);
+ expect(converted).toParseCorrectlyAndMatch();
+ expect(escaped).toParseCorrectlyAndMatch('something <i>foo</i>');
+ });
-// CORRECT MARKUP
+ it('converts and escapes underlined markup', () => {
+ const text = 'foo something';
+ const {converted, escaped} = convertAndEscape(text);
+ expect(converted).toParseCorrectlyAndMatch();
+ expect(escaped).toParseCorrectlyAndMatch('<u>foo</u> something');
+ });
-assertConverts('foo');
-assertEscapes('foo', 'foo');
+ it('converts and escapes non-nested bold italic and underline markup', () => {
+ const text = 'bold italic and underlined';
+ const {converted, escaped} = convertAndEscape(text);
+ expect(converted).toParseCorrectlyAndMatch();
+ expect(escaped).toParseCorrectlyAndMatch('<b>bold</b> <i>italic <u>and underlined</u></i>');
+ });
-assertConverts('foo');
-assertEscapes('foo', '<b>foo</b>');
+ it('converts and escapes ampersands', () => {
+ const text = 'this & that';
+ const {converted, escaped} = convertAndEscape(text);
+ expect(converted).toParseCorrectlyAndMatch();
+ expect(escaped).toParseCorrectlyAndMatch('this & that');
+ });
-assertConverts('something foo');
-assertEscapes('something foo', 'something <i>foo</i>');
+ it('converts and escapes <', () => {
+ const text = 'this < that';
+ const {converted, escaped} = convertAndEscape(text);
+ expect(converted).toParseCorrectlyAndMatch();
+ expect(escaped).toParseCorrectlyAndMatch('this < that');
+ });
-assertConverts('foo something');
-assertEscapes('foo something', '<u>foo</u> something');
+ it('converts and escapes >', () => {
+ const text = 'this < that > the other';
+ const {converted, escaped} = convertAndEscape(text);
+ expect(converted).toParseCorrectlyAndMatch();
+ expect(escaped).toParseCorrectlyAndMatch('this < that > the other');
+ });
-assertConverts('bold italic and underlined');
-assertEscapes('bold italic and underlined', '<b>bold</b> <i>italic <u>and underlined</u></i>');
+ it('converts and escapes HTML markup inside escaped tags', () => {
+ const text = 'this <that>';
+ const {converted, escaped} = convertAndEscape(text);
+ expect(converted).toParseCorrectlyAndMatch();
+ expect(escaped).toParseCorrectlyAndMatch('this <<i>that</i>>');
+ });
-assertConverts('this & that');
-assertEscapes('this & that', 'this & that');
+ it('converts and escapes angle brackets within HTML markup', () => {
+ const text = 'this > that';
+ const {converted, escaped} = convertAndEscape(text);
+ expect(converted).toParseCorrectlyAndMatch();
+ expect(escaped).toParseCorrectlyAndMatch('<b>this</b> > <i>that</i>');
+ });
-assertConverts('this < that');
-assertEscapes('this < that', 'this < that');
+ it('converts and escapes markup whilst still keeping an unrecognized entity', () => {
+ const text = 'smile ☺!';
+ const {converted, escaped} = convertAndEscape(text);
+ expect(converted).toParseCorrectlyAndMatch();
+ expect(escaped).toParseCorrectlyAndMatch('<b>smile</b> ☺!');
+ });
-assertConverts('this < that > the other');
-assertEscapes('this < that > the other', 'this < that > the other');
+ it('converts and escapes markup and a stray ampersand', () => {
+ const text = 'this & that';
+ const {converted, escaped} = convertAndEscape(text);
+ expect(converted).toParseCorrectlyAndMatch('this & that');
+ expect(escaped).toParseCorrectlyAndMatch('<b>this</b> & <i>that</i>');
+ });
-assertConverts('this <that>');
-assertEscapes('this <that>', 'this <<i>that</i>>');
+ it('converts and escapes a stray <', () => {
+ const text = 'this < that';
+ const {converted, escaped} = convertAndEscape(text);
+ expect(converted).toParseCorrectlyAndMatch('this < that');
+ expect(escaped).toParseCorrectlyAndMatch('this < that');
+ });
-assertConverts('this > that');
-assertEscapes('this > that', '<b>this</b> > <i>that</i>');
+ it('converts and escapes markup with a stray <', () => {
+ const text = 'this < that';
+ const {converted, escaped} = convertAndEscape(text);
+ expect(converted).toParseCorrectlyAndMatch('this < that');
+ expect(escaped).toParseCorrectlyAndMatch('<b>this</b> < <i>that</i>');
+ });
+ it('converts and escapes stray less than and greater than characters that do not form tags', () => {
+ const text = 'this < that > the other';
+ const {converted, escaped} = convertAndEscape(text);
+ expect(converted).toParseCorrectlyAndMatch('this < that > the other');
+ expect(escaped).toParseCorrectlyAndMatch('this < that > the other');
+ });
+ it('converts and escapes stray less than and greater than characters next to HTML markup tags', () => {
+ const text = 'this <that>';
+ const {converted, escaped} = convertAndEscape(text);
+ expect(converted).toParseCorrectlyAndMatch('this <that>');
+ expect(escaped).toParseCorrectlyAndMatch('this <<i>that</i>>');
+ });
-// PARTIALLY CORRECT MARKUP
-// correct bits are kept, incorrect bits are escaped
+ it('converts and escapes angle brackets around unknown tags', () => {
+ const text = 'tag';
+ const {converted, escaped} = convertAndEscape(text);
+ expect(converted).toParseCorrectlyAndMatch('<unknown>tag</unknown>');
+ expect(escaped).toParseCorrectlyAndMatch('<unknown>tag</unknown>');
+ });
-// unrecognized entity
-assertConverts('smile ☺!', 'smile ☺!');
-assertEscapes('smile ☺!', '<b>smile</b> ☺!');
+ it('converts and escapes angle brackets around unknown tags where the first letter might otherwise be valid HTML markup', () => {
+ const text = 'tag';
+ const {converted, escaped} = convertAndEscape(text);
+ expect(converted).toParseCorrectlyAndMatch('<bunknown>tag</bunknown>');
+ expect(escaped).toParseCorrectlyAndMatch('<bunknown>tag</bunknown>');
+ });
-// stray '&'; this is really a bug, but it's easier to do it this way
-assertConverts('this & that', 'this & that');
-assertEscapes('this & that', '<b>this</b> & <i>that</i>');
+ it('converts good tags but escapes bad tags', () => {
+ const text = 'known and tag';
+ const {converted, escaped} = convertAndEscape(text);
+ expect(converted).toParseCorrectlyAndMatch('known and <unknown>tag</unknown>');
+ expect(escaped).toParseCorrectlyAndMatch('<i>known</i> and <unknown>tag</unknown>');
+ });
-// likewise with stray '<'
-assertConverts('this < that', 'this < that');
-assertEscapes('this < that', 'this < that');
+ it('completely escapes mismatched tags where the mismatch is at the beginning', () => {
+ const text = 'incomplete';
+ const {converted, escaped} = convertAndEscape(text);
+ expect(converted).toParseCorrectlyAndMatch('<b>in<i>com</i>plete');
+ expect(escaped).toParseCorrectlyAndMatch('<b>in<i>com</i>plete');
+ });
-assertConverts('this < that', 'this < that');
-assertEscapes('this < that', '<b>this</b> < <i>that</i>');
+ it('completely escapes mismatched tags where the mismatch is at the end', () => {
+ const text = 'incomplete';
+ const {converted, escaped} = convertAndEscape(text);
+ expect(converted).toParseCorrectlyAndMatch('in<i>com</i>plete</b>');
+ expect(escaped).toParseCorrectlyAndMatch('in<i>com</i>plete</b>');
+ });
-assertConverts('this < that > the other', 'this < that > the other');
-assertEscapes('this < that > the other', 'this < that > the other');
+ it('escapes all tags where there are attributes', () => {
+ const text = 'good and bad';
+ const {converted, escaped} = convertAndEscape(text);
+ expect(converted).toParseCorrectlyAndMatch('<b>good</b> and <b style='bad'>bad</b>');
+ expect(escaped).toParseCorrectlyAndMatch('<b>good</b> and <b style='bad'>bad</b>');
+ });
+ it('escapes all tags where syntax is invalid', () => {
+ const text = 'unrecognized';
+ const {converted, escaped} = convertAndEscape(text);
+ expect(converted).toParseCorrectlyAndMatch('<b>unrecognized</b stuff>');
+ expect(escaped).toParseCorrectlyAndMatch('<b>unrecognized</b stuff>');
+ });
-assertConverts('this <that>', 'this <that>');
-assertEscapes('this <that>', 'this <<i>that</i>>');
+ it('escapes completely mismatched tags', () => {
+ const text = 'mismatched';
+ const {converted, escaped} = convertAndEscape(text);
+ expect(converted).toParseCorrectlyAndMatch('<b>mismatched</i>');
+ expect(escaped).toParseCorrectlyAndMatch('<b>mismatched</i>');
+ });
-// unknown tags
-assertConverts('tag', '<unknown>tag</unknown>');
-assertEscapes('tag', '<unknown>tag</unknown>');
-
-// make sure we check beyond the first letter
-assertConverts('tag', '<bunknown>tag</bunknown>');
-assertEscapes('tag', '<bunknown>tag</bunknown>');
-
-// with mix of good and bad, we keep the good and escape the bad
-assertConverts('known and tag', 'known and <unknown>tag</unknown>');
-assertEscapes('known and tag', '<i>known</i> and <unknown>tag</unknown>');
-
-
-
-// FULLY INCORRECT MARKUP
-// (fall back to escaping the whole thing)
-
-// tags not matched up
-assertConverts('incomplete', '<b>in<i>com</i>plete');
-assertEscapes('incomplete', '<b>in<i>com</i>plete');
-
-assertConverts('incomplete', 'in<i>com</i>plete</b>');
-assertEscapes('incomplete', 'in<i>com</i>plete</b>');
-
-// we don't support attributes, and it's too complicated to try
-// to escape both start and end tags, so we just treat it as bad
-assertConverts('good and bad', '<b>good</b> and <b style='bad'>bad</b>');
-assertEscapes('good and bad', '<b>good</b> and <b style='bad'>bad</b>');
-
-// this is just syntactically invalid
-assertConverts('unrecognized', '<b>unrecognized</b stuff>');
-assertEscapes('unrecognized', '<b>unrecognized</b stuff>');
-
-// mismatched tags
-assertConverts('mismatched', '<b>mismatched</i>');
-assertEscapes('mismatched', '<b>mismatched</i>');
-
-assertConverts('mismatched/unknown', '<b>mismatched/unknown</bunknown>');
-assertEscapes('mismatched/unknown', '<b>mismatched/unknown</bunknown>');
+ it('escapes mismatched tags where the first character is mismatched', () => {
+ const text = 'mismatched/unknown';
+ const {converted, escaped} = convertAndEscape(text);
+ expect(converted).toParseCorrectlyAndMatch('<b>mismatched/unknown</bunknown>');
+ expect(escaped).toParseCorrectlyAndMatch('<b>mismatched/unknown</bunknown>');
+ });
+});