diff --git a/package.json b/package.json index b0b7949b..9bbb5282 100644 --- a/package.json +++ b/package.json @@ -74,6 +74,8 @@ "@babel/preset-env": "7.3.1", "@fortawesome/fontawesome-free": "5.7.1", "babel-loader": "8.0.5", + "@vue/server-test-utils": "1.0.0-beta.27", + "@vue/test-utils": "1.0.0-beta.27", "chai": "4.2.0", "copy-webpack-plugin": "4.6.0", "css-loader": "0.28.11", @@ -107,6 +109,7 @@ "undate": "0.3.0", "vue": "2.5.17", "vue-loader": "15.4.2", + "vue-server-renderer": "2.5.17", "vue-template-compiler": "2.5.17", "vuedraggable": "2.16.0", "webpack": "4.29.3", diff --git a/test/client/components/ParsedMessageTestWrapper.vue b/test/client/components/ParsedMessageTestWrapper.vue new file mode 100644 index 00000000..9a5d9dc8 --- /dev/null +++ b/test/client/components/ParsedMessageTestWrapper.vue @@ -0,0 +1,24 @@ + + + diff --git a/test/client/js/libs/handlebars/parse.js b/test/client/js/libs/handlebars/parse.js index 801d92f0..5849feef 100644 --- a/test/client/js/libs/handlebars/parse.js +++ b/test/client/js/libs/handlebars/parse.js @@ -1,19 +1,35 @@ "use strict"; const expect = require("chai").expect; -const parse = require("../../../../../client/js/libs/handlebars/parse"); + +import {renderToString} from "@vue/server-test-utils"; +import ParsedMessageTestWrapper from "../../../components/ParsedMessageTestWrapper.vue"; + +function getParsedMessageContents(text, message) { + let contents = renderToString(ParsedMessageTestWrapper, { + propsData: { + text, + message, + }, + }); + + // The wrapper adds a surrounding div to the message html, so we clean that out here + contents = contents.replace(/^
([^]+)<\/div>$/m, "$1"); + + return contents; +} describe("parse Handlebars helper", () => { it("should not introduce xss", () => { const testCases = [{ input: "", - expected: "<img onerror='location.href="//youtube.com"'>", + expected: "<img onerror='location.href="//youtube.com"'>", }, { input: '#&">bug', - expected: '#&">bug', + expected: '#&">bug', }]; - const actual = testCases.map((testCase) => parse(testCase.input)); + const actual = testCases.map((testCase) => getParsedMessageContents(testCase.input)); const expected = testCases.map((testCase) => testCase.expected); expect(actual).to.deep.equal(expected); @@ -25,7 +41,7 @@ describe("parse Handlebars helper", () => { expected: 'text\nwithcontrolcodestest', }]; - const actual = testCases.map((testCase) => parse(testCase.input)); + const actual = testCases.map((testCase) => getParsedMessageContents(testCase.input)); const expected = testCases.map((testCase) => testCase.expected); expect(actual).to.deep.equal(expected); @@ -68,7 +84,7 @@ describe("parse Handlebars helper", () => { "", }]; - const actual = testCases.map((testCase) => parse(testCase.input)); + const actual = testCases.map((testCase) => getParsedMessageContents(testCase.input)); const expected = testCases.map((testCase) => testCase.expected); expect(actual).to.deep.equal(expected); @@ -79,11 +95,11 @@ describe("parse Handlebars helper", () => { "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" + + '' + + "https://msdn.microsoft.com/en-us/library/windows/desktop/ms644989(v=vs.85).aspx" + ""; - const actual = parse(input); + const actual = getParsedMessageContents(input); expect(actual).to.deep.equal(correctResult); }); @@ -119,7 +135,7 @@ describe("parse Handlebars helper", () => { "", }]; - const actual = testCases.map((testCase) => parse(testCase.input)); + const actual = testCases.map((testCase) => getParsedMessageContents(testCase.input)); const expected = testCases.map((testCase) => testCase.expected); expect(actual).to.deep.equal(expected); @@ -134,7 +150,7 @@ describe("parse Handlebars helper", () => { expected: "http://.", }]; - const actual = testCases.map((testCase) => parse(testCase.input)); + const actual = testCases.map((testCase) => getParsedMessageContents(testCase.input)); const expected = testCases.map((testCase) => testCase.expected); expect(actual).to.deep.equal(expected); @@ -144,45 +160,45 @@ describe("parse Handlebars helper", () => { 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 actual = testCases.map((testCase) => getParsedMessageContents(testCase.input)); const expected = testCases.map((testCase) => testCase.expected); expect(actual).to.deep.equal(expected); @@ -197,7 +213,7 @@ describe("parse Handlebars helper", () => { expected: "#", }]; - const actual = testCases.map((testCase) => parse(testCase.input)); + const actual = testCases.map((testCase) => getParsedMessageContents(testCase.input)); const expected = testCases.map((testCase) => testCase.expected); expect(actual).to.deep.equal(expected); @@ -222,11 +238,11 @@ describe("parse Handlebars helper", () => { }, { name: "hex foreground color", input: "\x04663399rebeccapurple", - expected: 'rebeccapurple', + expected: 'rebeccapurple', }, { name: "hex foreground and background colors", input: "\x04415364,ff9e18The Lounge", - expected: 'The Lounge', + expected: 'The Lounge', }, { name: "italic", input: "\x1ditalic", @@ -281,22 +297,24 @@ describe("parse Handlebars helper", () => { 'bold', }].forEach((item) => { // TODO: In Node v6+, use `{name, input, expected}` it(`should handle style characters: ${item.name}`, function() { - expect(parse(item.input)).to.equal(item.expected); + expect(getParsedMessageContents(item.input)).to.equal(item.expected); }); }); it("should find nicks", () => { const testCases = [{ - users: ["MaxLeiter"], + message: { + users: ["MaxLeiter"], + }, input: "test, MaxLeiter", expected: "test, " + - '' + + '' + "MaxLeiter" + "", }]; - const actual = testCases.map((testCase) => parse(testCase.input, testCase.users)); + const actual = testCases.map((testCase) => getParsedMessageContents(testCase.input, testCase.message)); const expected = testCases.map((testCase) => testCase.expected); expect(actual).to.deep.equal(expected); @@ -307,7 +325,7 @@ describe("parse Handlebars helper", () => { users: ["MaxLeiter, test"], input: "#test-channelMaxLeiter", expected: - '' + + '' + "#test-channelMaxLeiter" + "", }, @@ -322,7 +340,7 @@ describe("parse Handlebars helper", () => { ]; - const actual = testCases.map((testCase) => parse(testCase.input)); + const actual = testCases.map((testCase) => getParsedMessageContents(testCase.input)); const expected = testCases.map((testCase) => testCase.expected); expect(actual).to.deep.equal(expected); @@ -342,13 +360,13 @@ describe("parse Handlebars helper", () => { }, { input: "\x02#\x038,9thelounge", expected: - '' + + '' + '#' + 'thelounge' + "", }]; - const actual = testCases.map((testCase) => parse(testCase.input)); + const actual = testCases.map((testCase) => getParsedMessageContents(testCase.input)); const expected = testCases.map((testCase) => testCase.expected); expect(actual).to.deep.equal(expected); @@ -358,20 +376,20 @@ describe("parse Handlebars helper", () => { [{ name: "in text", input: "Hello💬", - expected: 'Hello💬', + expected: 'Hello💬', }, { name: "complicated zero-join-width emoji", input: "🤦🏿‍♀️", - expected: '🤦🏿‍♀️', + expected: '🤦🏿‍♀️', }, { name: "with modifiers", input: "🤷‍♀️", - expected: '🤷‍♀️', + expected: '🤷‍♀️', }, { // FIXME: These multiple `span`s should be optimized into a single one. See https://github.com/thelounge/thelounge/issues/1783 name: "wrapped in style", input: "Super \x034💚 green!", - expected: 'Super 💚 green!', + expected: 'Super 💚 green!', }, { name: "wrapped in URLs", input: "https://i.❤️.thelounge.chat", @@ -381,10 +399,10 @@ describe("parse Handlebars helper", () => { name: "wrapped in channels", input: "#i❤️thelounge", // FIXME: Emoji in text should be `❤️`. See https://github.com/thelounge/thelounge/issues/1784 - expected: '#i❤️thelounge', + expected: '#i❤️thelounge', }].forEach((item) => { // TODO: In Node v6+, use `{name, input, expected}` it(`should find emoji: ${item.name}`, function() { - expect(parse(item.input)).to.equal(item.expected); + expect(getParsedMessageContents(item.input)).to.equal(item.expected); }); }); @@ -393,12 +411,12 @@ describe("parse Handlebars helper", () => { 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 actual = testCases.map((testCase) => getParsedMessageContents(testCase.input)); const expected = testCases.map((testCase) => testCase.expected); expect(actual).to.deep.equal(expected); @@ -421,7 +439,7 @@ describe("parse Handlebars helper", () => { "", }]; - const actual = testCases.map((testCase) => parse(testCase.input)); + const actual = testCases.map((testCase) => getParsedMessageContents(testCase.input)); const expected = testCases.map((testCase) => testCase.expected); expect(actual).to.deep.equal(expected); @@ -436,7 +454,7 @@ describe("parse Handlebars helper", () => { "", }]; - const actual = testCases.map((testCase) => parse(testCase.input)); + const actual = testCases.map((testCase) => getParsedMessageContents(testCase.input)); const expected = testCases.map((testCase) => testCase.expected); expect(actual).to.deep.equal(expected); @@ -444,20 +462,20 @@ describe("parse Handlebars helper", () => { it("should not overlap parts", () => { const input = "Url: http://example.com/path Channel: ##channel"; - const actual = parse(input); + const actual = getParsedMessageContents(input); expect(actual).to.equal( 'Url: http://example.com/path ' + - 'Channel: ##channel' + 'Channel: ##channel' ); }); it("should handle overlapping parts by using first starting", () => { const input = "#test-https://example.com"; - const actual = parse(input); + const actual = getParsedMessageContents(input); expect(actual).to.equal( - '' + + '' + "#test-https://example.com" + "" ); diff --git a/yarn.lock b/yarn.lock index 22d59ebd..33c33912 100644 --- a/yarn.lock +++ b/yarn.lock @@ -572,8 +572,8 @@ defer-to-connect "^1.0.1" "@types/node@*": - version "10.12.18" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.18.tgz#1d3ca764718915584fcd9f6344621b7672665c67" + version "10.9.4" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.9.4.tgz#0f4cb2dc7c1de6096055357f70179043c33e9897" "@types/unist@*", "@types/unist@^2.0.0": version "2.0.2" @@ -608,6 +608,21 @@ source-map "^0.5.6" vue-template-es2015-compiler "^1.6.0" +"@vue/server-test-utils@1.0.0-beta.27": + version "1.0.0-beta.27" + resolved "https://registry.yarnpkg.com/@vue/server-test-utils/-/server-test-utils-1.0.0-beta.27.tgz#0c61cb724d04bf44c70938b4a6054e73e914492a" + integrity sha512-HKwm1Nw180qNgknPyok/XWJF4ojBZC5vSANPf+f1WR2jYsSc0vvAxUyX2Qeq4F7KLXrfsFwdkfH3a5BxdrA9tg== + dependencies: + cheerio "^1.0.0-rc.2" + +"@vue/test-utils@1.0.0-beta.27": + version "1.0.0-beta.27" + resolved "https://registry.yarnpkg.com/@vue/test-utils/-/test-utils-1.0.0-beta.27.tgz#7e5f7b7180c00e28a4ca55c0ff0a7e754377fdb2" + integrity sha512-Lzrd4ZBkS70Tl8JbXbDrN/NcSaH9aZT6+7emU3QhTJ+CrorJpyFDA1dkvSIhH+rDTs8sHFbGeXjXV/qorXxtRw== + dependencies: + dom-event-types "^1.0.0" + lodash "^4.17.4" + "@webassemblyjs/ast@1.7.11": version "1.7.11" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.7.11.tgz#b988582cafbb2b095e8b556526f30c90d057cace" @@ -1614,6 +1629,17 @@ cheerio@0.22.0: lodash.reject "^4.4.0" lodash.some "^4.4.0" +cheerio@^1.0.0-rc.2: + version "1.0.0-rc.2" + resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.2.tgz#4b9f53a81b27e4d5dac31c0ffd0cfa03cc6830db" + dependencies: + css-select "~1.2.0" + dom-serializer "~0.1.0" + entities "~1.1.1" + htmlparser2 "^3.9.1" + lodash "^4.15.0" + parse5 "^3.0.1" + chokidar@^2.0.0, chokidar@^2.0.2: version "2.0.4" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.0.4.tgz#356ff4e2b0e8e43e322d18a372460bbcf3accd26" @@ -2365,6 +2391,11 @@ doctrine@^2.1.0: dependencies: esutils "^2.0.2" +dom-event-types@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/dom-event-types/-/dom-event-types-1.0.0.tgz#5830a0a29e1bf837fe50a70cd80a597232813cae" + integrity sha512-2G2Vwi2zXTHBGqXHsJ4+ak/iP0N8Ar+G8a7LiD2oup5o4sQWytwqqrZu/O6hIMV0KMID2PL69OhpshLO0n7UJQ== + dom-serializer@0, dom-serializer@~0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82" @@ -4363,6 +4394,10 @@ locate-path@^3.0.0: p-locate "^3.0.0" path-exists "^3.0.0" +lodash._reinterpolate@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" + lodash.assignin@^4.0.9: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.assignin/-/lodash.assignin-4.2.0.tgz#ba8df5fb841eb0a3e8044232b0e263a8dc6a28a2" @@ -4431,14 +4466,31 @@ lodash.some@^4.4.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.some/-/lodash.some-4.6.0.tgz#1bb9f314ef6b8baded13b549169b2a945eb68e4d" +lodash.template@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.4.0.tgz#e73a0385c8355591746e020b99679c690e68fba0" + dependencies: + lodash._reinterpolate "~3.0.0" + lodash.templatesettings "^4.0.0" + +lodash.templatesettings@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-4.1.0.tgz#2b4d4e95ba440d915ff08bc899e4553666713316" + dependencies: + lodash._reinterpolate "~3.0.0" + lodash.uniq@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" -lodash@4.17.11, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.3.0: +lodash@4.17.11, lodash@^4.17.11: version "4.17.11" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" +lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.3.0: + version "4.17.10" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7" + log-symbols@^2.0.0, log-symbols@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a" @@ -5392,6 +5444,12 @@ parse-passwd@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" +parse5@^3.0.1: + version "3.0.3" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-3.0.3.tgz#042f792ffdd36851551cf4e9e066b3874ab45b5c" + dependencies: + "@types/node" "*" + parseqs@0.0.5: version "0.0.5" resolved "https://registry.yarnpkg.com/parseqs/-/parseqs-0.0.5.tgz#d5208a3738e46766e291ba2ea173684921a8b89d" @@ -6350,11 +6408,11 @@ resolve-url@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" -resolve@^1.3.2: - version "1.10.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.10.0.tgz#3bdaaeaf45cc07f375656dfd2e54ed0810b101ba" +resolve@^1.2.0, resolve@^1.3.2: + version "1.8.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.8.1.tgz#82f1ec19a423ac1fbd080b0bab06ba36e84a7a26" dependencies: - path-parse "^1.0.6" + path-parse "^1.0.5" responselike@^1.0.2: version "1.0.2" @@ -6485,9 +6543,9 @@ send@0.16.2: range-parser "~1.2.0" statuses "~1.4.0" -serialize-javascript@^1.4.0: - version "1.6.1" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.6.1.tgz#4d1f697ec49429a847ca6f442a2a755126c4d879" +serialize-javascript@^1.3.0, serialize-javascript@^1.4.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.5.0.tgz#1aa336162c88a890ddad5384baebc93a655161fe" serve-index@^1.7.2: version "1.9.1" @@ -6730,6 +6788,10 @@ source-map-url@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" +source-map@0.5.6: + version "0.5.6" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412" + source-map@^0.5.0, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" @@ -7649,6 +7711,19 @@ vue-loader@15.4.2: vue-hot-reload-api "^2.3.0" vue-style-loader "^4.1.0" +vue-server-renderer@2.5.17: + version "2.5.17" + resolved "https://registry.yarnpkg.com/vue-server-renderer/-/vue-server-renderer-2.5.17.tgz#c1f24815a4b12a2797c154549b29b44b6be004b5" + dependencies: + chalk "^1.1.3" + hash-sum "^1.0.2" + he "^1.1.0" + lodash.template "^4.4.0" + lodash.uniq "^4.5.0" + resolve "^1.2.0" + serialize-javascript "^1.3.0" + source-map "0.5.6" + vue-style-loader@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/vue-style-loader/-/vue-style-loader-4.1.0.tgz#7588bd778e2c9f8d87bfc3c5a4a039638da7a863"