From 847b9273670066f752d980bafe152e3c0823ee63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Sun, 21 Jan 2024 13:00:26 +0100 Subject: [PATCH] tests: Port fixMarkup() test to jasmine Based on https://bugzilla.gnome.org/show_bug.cgi?id=783738 from Sam Spilsbury. Part-of: --- tests/meson.build | 2 +- tests/unit/markup.js | 300 ++++++++++++++++++++++++++----------------- 2 files changed, 184 insertions(+), 118 deletions(-) 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 &amp; 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 &lt; 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 &lt; that &gt; 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 &lt;<i>that</i>&gt;'); + }); -assertConverts('this & that'); -assertEscapes('this & that', 'this &amp; 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 &lt; 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> &#9786;!'); + }); -assertConverts('this < that > the other'); -assertEscapes('this < that > the other', 'this &lt; that &gt; 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 &lt;<i>that</i>&gt;'); + 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 &#9786;!'); -assertEscapes('smile ☺!', '<b>smile</b> &#9786;!'); + 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>'); + }); +});