// Test cases for MessageList markup parsing import Pango from 'gi://Pango'; import 'resource:///org/gnome/shell/ui/environment.js'; import {fixMarkup} from 'resource:///org/gnome/shell/misc/util.js'; describe('fixMarkup()', () => { function convertAndEscape(text) { return { converted: fixMarkup(text, true), escaped: fixMarkup(text, false), }; } 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; return { pass: isMarkupValid(actual) && actual === expected, message: `Expected "${actual}" to parse correctly and equal "${expected}"`, }; }, }; }, }); }); 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>'); }); 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'); }); 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>'); }); it('converts and escapes ampersands', () => { const text = 'this & that'; const {converted, escaped} = convertAndEscape(text); expect(converted).toParseCorrectlyAndMatch(); expect(escaped).toParseCorrectlyAndMatch('this &amp; that'); }); it('converts and escapes <', () => { const text = 'this < that'; const {converted, escaped} = convertAndEscape(text); expect(converted).toParseCorrectlyAndMatch(); expect(escaped).toParseCorrectlyAndMatch('this &lt; that'); }); 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'); }); 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;'); }); 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>'); }); 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;!'); }); 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>'); }); 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'); }); 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>>'); }); 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>'); }); 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>'); }); 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>'); }); 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'); }); 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>'); }); 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>'); }); 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>'); }); 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>'); }); });