diff --git a/test/client/js/libs/handlebars/ircmessageparser/anyIntersection.js b/test/client/js/libs/handlebars/ircmessageparser/anyIntersection.js new file mode 100644 index 00000000..b80a44ed --- /dev/null +++ b/test/client/js/libs/handlebars/ircmessageparser/anyIntersection.js @@ -0,0 +1,30 @@ +"use strict"; + +const expect = require("chai").expect; +const anyIntersection = require("../../../../../../client/js/libs/handlebars/ircmessageparser/anyIntersection"); + +describe("anyIntersection", () => { + it("should not intersect on edges", () => { + const a = {start: 1, end: 2}; + const b = {start: 2, end: 3}; + + expect(anyIntersection(a, b)).to.equal(false); + expect(anyIntersection(b, a)).to.equal(false); + }); + + it("should intersect on overlapping", () => { + const a = {start: 0, end: 3}; + const b = {start: 1, end: 2}; + + expect(anyIntersection(a, b)).to.equal(true); + expect(anyIntersection(b, a)).to.equal(true); + }); + + it("should not intersect", () => { + const a = {start: 0, end: 1}; + const b = {start: 2, end: 3}; + + expect(anyIntersection(a, b)).to.equal(false); + expect(anyIntersection(b, a)).to.equal(false); + }); +}); diff --git a/test/client/js/libs/handlebars/ircmessageparser/findChannels.js b/test/client/js/libs/handlebars/ircmessageparser/findChannels.js new file mode 100644 index 00000000..93c119ee --- /dev/null +++ b/test/client/js/libs/handlebars/ircmessageparser/findChannels.js @@ -0,0 +1,123 @@ +"use strict"; + +const expect = require("chai").expect; +const analyseText = require("../../../../../../client/js/libs/handlebars/ircmessageparser/findChannels"); + +describe("findChannels", () => { + it("should find single letter channel", () => { + const input = "#a"; + const expected = [{ + channel: "#a", + start: 0, + end: 2 + }]; + + const actual = analyseText(input, ["#"], ["@", "+"]); + + expect(actual).to.deep.equal(expected); + }); + + it("should find utf8 channels", () => { + const input = "#äöü"; + const expected = [{ + channel: "#äöü", + start: 0, + end: 4 + }]; + + const actual = analyseText(input, ["#"], ["@", "+"]); + + expect(actual).to.deep.equal(expected); + }); + + it("should find inline channel", () => { + const input = "inline #channel text"; + const expected = [{ + channel: "#channel", + start: 7, + end: 15 + }]; + + const actual = analyseText(input, ["#"], ["@", "+"]); + + expect(actual).to.deep.equal(expected); + }); + + it("should stop at \\0x07", () => { + const input = "#chan\x07nel"; + const expected = [{ + channel: "#chan", + start: 0, + end: 5 + }]; + + const actual = analyseText(input, ["#"], ["@", "+"]); + + expect(actual).to.deep.equal(expected); + }); + + it("should allow classics pranks", () => { + const input = "#1,000"; + const expected = [{ + channel: "#1,000", + start: 0, + end: 6 + }]; + + const actual = analyseText(input, ["#"], ["@", "+"]); + + expect(actual).to.deep.equal(expected); + }); + + it("should work with whois reponses", () => { + const input = "@#a"; + const expected = [{ + channel: "#a", + start: 1, + end: 3 + }]; + + const actual = analyseText(input, ["#"], ["@", "+"]); + + expect(actual).to.deep.equal(expected); + }); + + it("should work with IRCv3.1 multi-prefix", () => { + const input = "!@%+#a"; + const expected = [{ + channel: "#a", + start: 4, + end: 6 + }]; + + const actual = analyseText(input, ["#"], ["!", "@", "%", "+"]); + + expect(actual).to.deep.equal(expected); + }); + + it("should work with custom channelPrefixes", () => { + const input = "@a"; + const expected = [{ + channel: "@a", + start: 0, + end: 2 + }]; + + const actual = analyseText(input, ["@"], ["#", "+"]); + + expect(actual).to.deep.equal(expected); + }); + + it("should handle multiple channelPrefix correctly", () => { + const input = "##test"; + const expected = [{ + channel: "##test", + start: 0, + end: 6 + }]; + + const actual = analyseText(input, ["#"], ["@", "+"]); + + expect(actual).to.deep.equal(expected); + }); +}); diff --git a/test/client/js/libs/handlebars/ircmessageparser/merge.js b/test/client/js/libs/handlebars/ircmessageparser/merge.js new file mode 100644 index 00000000..d55ac1a2 --- /dev/null +++ b/test/client/js/libs/handlebars/ircmessageparser/merge.js @@ -0,0 +1,63 @@ +"use strict"; + +const expect = require("chai").expect; +const merge = require("../../../../../../client/js/libs/handlebars/ircmessageparser/merge"); + +describe("merge", () => { + it("should split style information", () => { + const textParts = [{ + start: 0, + end: 10, + flag1: true + }, { + start: 10, + end: 20, + flag2: true + }]; + const styleFragments = [{ + start: 0, + end: 5, + text: "01234" + }, { + start: 5, + end: 15, + text: "5678901234" + }, { + start: 15, + end: 20, + text: "56789" + }]; + + const expected = [{ + start: 0, + end: 10, + flag1: true, + fragments: [{ + start: 0, + end: 5, + text: "01234" + }, { + start: 5, + end: 10, + text: "56789" + }] + }, { + start: 10, + end: 20, + flag2: true, + fragments: [{ + start: 10, + end: 15, + text: "01234" + }, { + start: 15, + end: 20, + text: "56789" + }] + }]; + + const actual = merge(textParts, styleFragments); + + expect(actual).to.deep.equal(expected); + }); +}); diff --git a/test/client/js/libs/handlebars/ircmessageparser/parseStyle.js b/test/client/js/libs/handlebars/ircmessageparser/parseStyle.js new file mode 100644 index 00000000..6af289c4 --- /dev/null +++ b/test/client/js/libs/handlebars/ircmessageparser/parseStyle.js @@ -0,0 +1,274 @@ +"use strict"; + +const expect = require("chai").expect; +const parseStyle = require("../../../../../../client/js/libs/handlebars/ircmessageparser/parseStyle"); + +describe("parseStyle", () => { + it("should skip control codes", () => { + const input = "text\x01with\x04control\x05codes"; + const expected = [{ + bold: false, + textColor: undefined, + bgColor: undefined, + reverse: false, + italic: false, + underline: false, + text: "textwithcontrolcodes", + + start: 0, + end: 20 + }]; + + const actual = parseStyle(input); + + expect(actual).to.deep.equal(expected); + }); + + it("should parse bold", () => { + const input = "\x02bold"; + const expected = [{ + bold: true, + textColor: undefined, + bgColor: undefined, + reverse: false, + italic: false, + underline: false, + text: "bold", + + start: 0, + end: 4 + }]; + + const actual = parseStyle(input); + + expect(actual).to.deep.equal(expected); + }); + + it("should parse textColor", () => { + const input = "\x038yellowText"; + const expected = [{ + bold: false, + textColor: 8, + bgColor: undefined, + reverse: false, + italic: false, + underline: false, + text: "yellowText", + + start: 0, + end: 10 + }]; + + const actual = parseStyle(input); + + expect(actual).to.deep.equal(expected); + }); + + it("should parse textColor and background", () => { + const input = "\x034,8yellowBG redText"; + const expected = [{ + textColor: 4, + bgColor: 8, + bold: false, + reverse: false, + italic: false, + underline: false, + text: "yellowBG redText", + + start: 0, + end: 16 + }]; + + const actual = parseStyle(input); + + expect(actual).to.deep.equal(expected); + }); + + it("should parse italic", () => { + const input = "\x1ditalic"; + const expected = [{ + bold: false, + textColor: undefined, + bgColor: undefined, + reverse: false, + italic: true, + underline: false, + text: "italic", + + start: 0, + end: 6 + }]; + + const actual = parseStyle(input); + + expect(actual).to.deep.equal(expected); + }); + + it("should carry state corretly forward", () => { + const input = "\x02bold\x038yellow\x02nonBold\x03default"; + const expected = [{ + bold: true, + textColor: undefined, + bgColor: undefined, + reverse: false, + italic: false, + underline: false, + text: "bold", + + start: 0, + end: 4 + }, { + bold: true, + textColor: 8, + bgColor: undefined, + reverse: false, + italic: false, + underline: false, + text: "yellow", + + start: 4, + end: 10 + }, { + bold: false, + textColor: 8, + bgColor: undefined, + reverse: false, + italic: false, + underline: false, + text: "nonBold", + + start: 10, + end: 17 + }, { + bold: false, + textColor: undefined, + bgColor: undefined, + reverse: false, + italic: false, + underline: false, + text: "default", + + start: 17, + end: 24 + }]; + + const actual = parseStyle(input); + + expect(actual).to.deep.equal(expected); + }); + + it("should toggle bold correctly", () => { + const input = "\x02bold\x02 \x02bold\x02"; + const expected = [{ + bold: true, + textColor: undefined, + bgColor: undefined, + reverse: false, + italic: false, + underline: false, + text: "bold", + + start: 0, + end: 4 + }, { + bold: false, + textColor: undefined, + bgColor: undefined, + reverse: false, + italic: false, + underline: false, + text: " ", + + start: 4, + end: 5 + }, { + bold: true, + textColor: undefined, + bgColor: undefined, + reverse: false, + italic: false, + underline: false, + text: "bold", + + start: 5, + end: 9 + }]; + + const actual = parseStyle(input); + + expect(actual).to.deep.equal(expected); + }); + + it("should reset all styles", () => { + const input = "\x02\x034\x16\x1d\x1ffull\x0fnone"; + const expected = [{ + bold: true, + textColor: 4, + bgColor: undefined, + reverse: true, + italic: true, + underline: true, + text: "full", + + start: 0, + end: 4 + }, { + bold: false, + textColor: undefined, + bgColor: undefined, + reverse: false, + italic: false, + underline: false, + text: "none", + + start: 4, + end: 8 + }]; + + const actual = parseStyle(input); + + expect(actual).to.deep.equal(expected); + }); + + it("should not emit empty fragments", () => { + const input = "\x031\x031,2\x031\x031,2\x031\x031,2\x03a"; + const expected = [{ + bold: false, + textColor: undefined, + bgColor: undefined, + reverse: false, + italic: false, + underline: false, + text: "a", + + start: 0, + end: 1 + }]; + + const actual = parseStyle(input); + + expect(actual).to.deep.equal(expected); + }); + + it("should optimize fragments", () => { + const rawString = "oh hi test text"; + const colorCode = "\x0312"; + const input = colorCode + rawString.split("").join(colorCode); + const expected = [{ + bold: false, + textColor: 12, + bgColor: undefined, + reverse: false, + italic: false, + underline: false, + text: rawString, + + start: 0, + end: rawString.length + }]; + + const actual = parseStyle(input); + + expect(actual).to.deep.equal(expected); + }); +}); diff --git a/test/client/js/libs/handlebars/parse.js b/test/client/js/libs/handlebars/parse.js new file mode 100644 index 00000000..7d1e025a --- /dev/null +++ b/test/client/js/libs/handlebars/parse.js @@ -0,0 +1,336 @@ +"use strict"; + +const expect = require("chai").expect; +const parse = require("../../../../../client/js/libs/handlebars/parse"); + +describe("parse Handlebars helper", () => { + it("should not introduce xss", () => { + const testCases = [{ + input: "", + expected: "<img onerror='location.href="//youtube.com"'>" + }, { + input: "#&\">bug", + expected: "#&">bug" + }]; + + const actual = testCases.map(testCase => parse(testCase.input)); + const expected = testCases.map(testCase => testCase.expected); + + expect(actual).to.deep.equal(expected); + }); + + it("should skip control codes", () => { + const testCases = [{ + input: "text\x01with\x04control\x05codes", + expected: "textwithcontrolcodes" + }]; + + const actual = testCases.map(testCase => parse(testCase.input)); + const expected = testCases.map(testCase => testCase.expected); + + expect(actual).to.deep.equal(expected); + }); + + it("should find urls", () => { + const testCases = [{ + input: "irc://freenode.net/thelounge", + expected: + "" + + "irc://freenode.net/thelounge" + + "" + }, { + input: "www.nooooooooooooooo.com", + expected: + "" + + "www.nooooooooooooooo.com" + + "" + }, { + input: "look at https://thelounge.github.io/ for more information", + expected: + "look at " + + "" + + "https://thelounge.github.io/" + + "" + + " for more information", + }, { + input: "use www.duckduckgo.com for privacy reasons", + expected: + "use " + + "" + + "www.duckduckgo.com" + + "" + + " for privacy reasons" + }, { + input: "svn+ssh://example.org", + expected: + "" + + "svn+ssh://example.org" + + "" + }]; + + const actual = testCases.map(testCase => parse(testCase.input)); + const expected = testCases.map(testCase => testCase.expected); + + expect(actual).to.deep.equal(expected); + }); + + it("url with a dot parsed correctly", () => { + const input = + "bonuspunkt: your URL parser misparses this URL: https://msdn.microsoft.com/en-us/library/windows/desktop/ms644989(v=vs.85).aspx"; + const correctResult = + "bonuspunkt: your URL parser misparses this URL: " + + "" + + "https://msdn.microsoft.com/en-us/library/windows/desktop/ms644989(v=vs.85).aspx" + + ""; + + const actual = parse(input); + + expect(actual).to.deep.equal(correctResult); + }); + + it("should balance brackets", () => { + const testCases = [{ + input: "", + expected: + "<" + + "" + + "https://theos.kyriasis.com/~kyrias/stats/archlinux.html" + + "" + + ">" + }, { + input: "abc (www.example.com)", + expected: + "abc (" + + "" + + "www.example.com" + + "" + + ")" + }, { + input: "http://example.com/Test_(Page)", + expected: + "" + + "http://example.com/Test_(Page)" + + "" + }, { + input: "www.example.com/Test_(Page)", + expected: + "" + + "www.example.com/Test_(Page)" + + "" + }]; + + const actual = testCases.map(testCase => parse(testCase.input)); + const expected = testCases.map(testCase => testCase.expected); + + expect(actual).to.deep.equal(expected); + }); + + it("should not find urls", () => { + const testCases = [{ + input: "text www. text", + expected: "text www. text" + }, { + input: "http://.", + expected: "http://." + }]; + + const actual = testCases.map(testCase => parse(testCase.input)); + const expected = testCases.map(testCase => testCase.expected); + + expect(actual).to.deep.equal(expected); + }); + + it("should find channels", () => { + const testCases = [{ + input: "#a", + expected: + "" + + "#a" + + "" + }, { + input: "#test", + expected: + "" + + "#test" + + "" + }, { + input: "#äöü", + expected: + "" + + "#äöü" + + "" + }, { + input: "inline #channel text", + expected: + "inline " + + "" + + "#channel" + + "" + + " text" + }, { + input: "#1,000", + expected: + "" + + "#1,000" + + "" + }, { + input: "@#a", + expected: + "@" + + "" + + "#a" + + "" + }]; + + const actual = testCases.map(testCase => parse(testCase.input)); + const expected = testCases.map(testCase => testCase.expected); + + expect(actual).to.deep.equal(expected); + }); + + it("should not find channels", () => { + const testCases = [{ + input: "hi#test", + expected: "hi#test" + }, { + input: "#", + expected: "#" + }]; + + const actual = testCases.map(testCase => parse(testCase.input)); + const expected = testCases.map(testCase => testCase.expected); + + expect(actual).to.deep.equal(expected); + }); + + it("should style like mirc", () => { + const testCases = [{ + input: "\x02bold", + expected: "bold" + }, { + input: "\x038yellowText", + expected: "yellowText" + }, { + input: "\x030,0white,white", + expected: "white,white" + }, { + input: "\x034,8yellowBGredText", + expected: "yellowBGredText" + }, { + input: "\x1ditalic", + expected: "italic" + }, { + input: "\x1funderline", + expected: "underline" + }, { + input: "\x02bold\x038yellow\x02nonBold\x03default", + expected: + "bold" + + "yellow" + + "nonBold" + + "default" + }, { + input: "\x02bold\x02 \x02bold\x02", + expected: + "bold" + + " " + + "bold" + }]; + + const actual = testCases.map(testCase => parse(testCase.input)); + const expected = testCases.map(testCase => testCase.expected); + + expect(actual).to.deep.equal(expected); + }); + + it("should go bonkers like mirc", () => { + const testCases = [{ + input: "\x02irc\x0f://\x1dfreenode.net\x0f/\x034,8thelounge", + expected: + "" + + "irc" + + "://" + + "freenode.net" + + "/" + + "thelounge" + + "" + }, { + input: "\x02#\x038,9thelounge", + expected: + "" + + "#" + + "thelounge" + + "" + }]; + + const actual = testCases.map(testCase => parse(testCase.input)); + const expected = testCases.map(testCase => testCase.expected); + + expect(actual).to.deep.equal(expected); + }); + + it("should optimize generated html", () => { + const testCases = [{ + input: "test \x0312#\x0312\x0312\"te\x0312st\x0312\x0312\x0312\x0312\x0312\x0312\x0312\x0312\x0312\x0312\x0312a", + expected: + "test " + + "" + + "#"testa" + + "" + }]; + + const actual = testCases.map(testCase => parse(testCase.input)); + const expected = testCases.map(testCase => testCase.expected); + + expect(actual).to.deep.equal(expected); + }); + + it("should trim commom protocols", () => { + const testCases = [{ + input: "like..http://example.com", + expected: + "like.." + + "" + + "http://example.com" + + "" + }, { + input: "like..HTTP://example.com", + expected: + "like.." + + "" + + "HTTP://example.com" + + "" + }]; + + const actual = testCases.map(testCase => parse(testCase.input)); + const expected = testCases.map(testCase => testCase.expected); + + expect(actual).to.deep.equal(expected); + }); + + it("should not find channel in fragment", () => { + const testCases = [{ + input: "http://example.com/#hash", + expected: + "" + + "" + + "http://example.com/#hash" + + "" + }]; + + const actual = testCases.map(testCase => parse(testCase.input)); + const expected = testCases.map(testCase => testCase.expected); + + expect(actual).to.deep.equal(expected); + }); + + it("should not overlap parts", () => { + const input = "Url: http://example.com/path Channel: ##channel"; + const actual = parse(input); + + expect(actual).to.equal( + "Url: http://example.com/path " + + "Channel: ##channel" + ); + }); +});