diff --git a/frontend/src/Metamaps/Listeners.js b/frontend/src/Metamaps/Listeners.js index a5dc630f..363ec48b 100644 --- a/frontend/src/Metamaps/Listeners.js +++ b/frontend/src/Metamaps/Listeners.js @@ -141,7 +141,7 @@ const Listeners = { }) $(window).resize(function() { if (Visualize && Visualize.mGraph) { - Util.resizeCanvas(Visualize.mGraph.canvas) + Util.resizeCanvas(Visualize.mGraph.canvas, $) } if (Active.Map && Realtime.inConversation) Realtime.positionVideos() diff --git a/frontend/src/Metamaps/Util.js b/frontend/src/Metamaps/Util.js index 2bc49159..e4ebb921 100644 --- a/frontend/src/Metamaps/Util.js +++ b/frontend/src/Metamaps/Util.js @@ -1,5 +1,3 @@ -/* global $ */ - import { Parser, HtmlRenderer, Node } from 'commonmark' import { emojiIndex } from 'emoji-mart' import { escapeRegExp } from 'lodash' @@ -39,36 +37,30 @@ const Util = { }, decodeEntities: function(desc) { - let temp = document.createElement('p') - temp.innerHTML = desc // browser handles the topics - let str = temp.textContent || temp.innerText - temp = null // delete the element + let paragraph = document.createElement('p') + paragraph.innerHTML = desc // browser handles the topics + let str = paragraph.textContent || paragraph.innerText + paragraph = null // delete the element return str - }, // decodeEntities + }, getDistance: function(p1, p2) { return Math.sqrt(Math.pow((p2.x - p1.x), 2) + Math.pow((p2.y - p1.y), 2)) }, - // Try using Visualize.mGraph coordsToPixels: function(mGraph, coords) { - if (mGraph) { - const canvas = mGraph.canvas - const s = canvas.getSize() - const p = canvas.getPos() - const ox = canvas.translateOffsetX - const oy = canvas.translateOffsetY - const sx = canvas.scaleOffsetX - const sy = canvas.scaleOffsetY - return { - x: (coords.x / (1 / sx)) + p.x + s.width / 2 + ox, - y: (coords.y / (1 / sy)) + p.y + s.height / 2 + oy - } - } else { - return { - x: 0, - y: 0 - } + if (!mGraph) return { x: 0, y: 0 } + + const canvas = mGraph.canvas + const s = canvas.getSize() + const p = canvas.getPos() + const ox = canvas.translateOffsetX + const oy = canvas.translateOffsetY + const sx = canvas.scaleOffsetX + const sy = canvas.scaleOffsetY + return { + x: (coords.x / (1 / sx)) + p.x + s.width / 2 + ox, + y: (coords.y / (1 / sy)) + p.y + s.height / 2 + oy } }, @@ -164,7 +156,7 @@ const Util = { centreCoords: Util.pixelsToCoords(fakeMgraph, { x: canvas.canvases[0].size.width / 2, y: canvas.canvases[0].size.height / 2 }) } }, - resizeCanvas: function(canvas) { + resizeCanvas: function(canvas, $ = window.$) { // Store the current canvas attributes, i.e. scale and map-coordinate at the centre of the user's screen const oldAttr = Util.logCanvasAttributes(canvas) diff --git a/frontend/test/Metamaps/Util.spec.js b/frontend/test/Metamaps/Util.spec.js index 3cfe05d8..6dd242a0 100644 --- a/frontend/test/Metamaps/Util.spec.js +++ b/frontend/test/Metamaps/Util.spec.js @@ -1,8 +1,12 @@ -/* global describe, it */ +/* global describe, it, afterEach */ import { expect } from 'chai' +import sinon from 'sinon' import Util from '../../src/Metamaps/Util' +import * as EmojiMart from 'emoji-mart' + +const sandbox = sinon.sandbox.create() describe('Metamaps.Util.js', function() { describe('splitLine', function() { @@ -21,10 +25,42 @@ describe('Metamaps.Util.js', function() { }) }) describe('nowDateFormatted', function() { - it.skip('TODO need `Date`') + function assertNowDateFormatted(expected, { month, day, year }) { + const date = { + getDate: () => day, + getMonth: () => month - 1, // 0 to 11 + getFullYear: () => year + } + expect(Util.nowDateFormatted(date)).to.equal(expected) + } + it('formats dates with one digit properly', function() { + assertNowDateFormatted('01/01/2000', { month: 1, day: 1, year: 2000 }) + }) + it('formats dates with two digits properly', function() { + assertNowDateFormatted('10/10/2000', { month: 10, day: 10, year: 2000 }) + }) }) describe('decodeEntities', function() { - it.skip('TODO need `document`') + function assertDecodeEntities(expected, { textContent, innerText, desc }) { + const paragraph = { textContent, innerText } + sandbox.stub(document, 'createElement').withArgs('p').returns(paragraph) + + const actual = Util.decodeEntities(desc) + + expect(actual).to.equal(expected) + expect(paragraph.innerHTML).to.equal(desc) + } + afterEach(function() { + sandbox.restore() + }) + it('returns textContent if available', function() { + assertDecodeEntities('textContent', + { textContent: 'textContent', innerText: 'innerText', desc: 'desc' }) + }) + it('otherwise returns innerText', function() { + assertDecodeEntities('innerText', + { innerText: 'innerText', desc: 'desc' }) + }) }) describe('getDistance', function() { it('(0,0) -> (0,0) = 0', function() { @@ -40,19 +76,83 @@ describe('Metamaps.Util.js', function() { .to.equal('8.6023') }) }) - describe('coordsToPixels', function() { - it('returns 0,0 for null canvas', function() { + describe('coords/pixels conversions', function() { + function mockMGraph({ x, y, sx, sy, px, py, width, height, ox, oy }) { + return { + canvas: { + getSize: () => ({ width, height }), + getPos: () => ({ x: px, y: py }), + translateOffsetX: ox, + translateOffsetY: oy, + scaleOffsetX: sx, + scaleOffsetY: sy + } + } + } + + function assertConversion(testFunction, expectedX, expectedY, + { x, y, sx, sy, px, py, width, height, ox, oy }) { + const mGraph = mockMGraph({ + x, y, sx, sy, px, py, width, height, ox, oy + }) + const coords = { x, y } + const actual = testFunction(mGraph, coords) + expect(actual.x).to.equal(expectedX) + expect(actual.y).to.equal(expectedY) + } + + it('coordsToPixels returns 0,0 for null canvas', function() { expect(Util.coordsToPixels(null, {}).x).to.equal(0) expect(Util.coordsToPixels(null, {}).y).to.equal(0) }) - it.skip('TODO need initialized mGraph to test further') - }) - describe('pixelsToCoords', function() { - it('returns 0,0 for null canvas', function() { + it('coordsToPixels', function() { + assertConversion(Util.coordsToPixels, 0, 0, + { x: 0, y: 0, sx: 1, sy: 1, px: 0, py: 0, width: 0, height: 0, ox: 0, oy: 0 }) + assertConversion(Util.coordsToPixels, 1, 1, + { x: 1, y: 1, sx: 1, sy: 1, px: 0, py: 0, width: 0, height: 0, ox: 0, oy: 0 }) + assertConversion(Util.coordsToPixels, 2, 1, + { x: 1, y: 1, sx: 2, sy: 1, px: 0, py: 0, width: 0, height: 0, ox: 0, oy: 0 }) + assertConversion(Util.coordsToPixels, 2, 2, + { x: 1, y: 1, sx: 2, sy: 2, px: 0, py: 0, width: 0, height: 0, ox: 0, oy: 0 }) + assertConversion(Util.coordsToPixels, 3, 2, + { x: 1, y: 1, sx: 2, sy: 2, px: 1, py: 0, width: 0, height: 0, ox: 0, oy: 0 }) + assertConversion(Util.coordsToPixels, 3, 3, + { x: 1, y: 1, sx: 2, sy: 2, px: 1, py: 1, width: 0, height: 0, ox: 0, oy: 0 }) + assertConversion(Util.coordsToPixels, 4, 3, + { x: 1, y: 1, sx: 2, sy: 2, px: 1, py: 1, width: 2, height: 0, ox: 0, oy: 0 }) + assertConversion(Util.coordsToPixels, 4, 4, + { x: 1, y: 1, sx: 2, sy: 2, px: 1, py: 1, width: 2, height: 2, ox: 0, oy: 0 }) + assertConversion(Util.coordsToPixels, 9, 4, + { x: 1, y: 1, sx: 2, sy: 2, px: 1, py: 1, width: 2, height: 2, ox: 5, oy: 0 }) + assertConversion(Util.coordsToPixels, 9, 9, + { x: 1, y: 1, sx: 2, sy: 2, px: 1, py: 1, width: 2, height: 2, ox: 5, oy: 5 }) + }) + it('pixelsToCoords returns 0,0 for null canvas', function() { expect(Util.pixelsToCoords(null, {}).x).to.equal(0) expect(Util.pixelsToCoords(null, {}).y).to.equal(0) }) - it.skip('TODO need initialized mGraph to test further') + it('pixelsToCoords', function() { + assertConversion(Util.pixelsToCoords, 0, 0, + { x: 0, y: 0, px: 0, py: 0, width: 0, height: 0, ox: 0, oy: 0, sx: 1, sy: 1 }) + assertConversion(Util.pixelsToCoords, 5, 5, + { x: 5, y: 5, px: 0, py: 0, width: 0, height: 0, ox: 0, oy: 0, sx: 1, sy: 1 }) + assertConversion(Util.pixelsToCoords, 4, 5, + { x: 5, y: 5, px: 1, py: 0, width: 0, height: 0, ox: 0, oy: 0, sx: 1, sy: 1 }) + assertConversion(Util.pixelsToCoords, 4, 4, + { x: 5, y: 5, px: 1, py: 1, width: 0, height: 0, ox: 0, oy: 0, sx: 1, sy: 1 }) + assertConversion(Util.pixelsToCoords, 3, 4, + { x: 5, y: 5, px: 1, py: 1, width: 2, height: 0, ox: 0, oy: 0, sx: 1, sy: 1 }) + assertConversion(Util.pixelsToCoords, 3, 3, + { x: 5, y: 5, px: 1, py: 1, width: 2, height: 2, ox: 0, oy: 0, sx: 1, sy: 1 }) + assertConversion(Util.pixelsToCoords, 2, 3, + { x: 5, y: 5, px: 1, py: 1, width: 2, height: 2, ox: 1, oy: 0, sx: 1, sy: 1 }) + assertConversion(Util.pixelsToCoords, 2, 2, + { x: 5, y: 5, px: 1, py: 1, width: 2, height: 2, ox: 1, oy: 1, sx: 1, sy: 1 }) + assertConversion(Util.pixelsToCoords, 4, 2, + { x: 5, y: 5, px: 1, py: 1, width: 2, height: 2, ox: 1, oy: 1, sx: 0.5, sy: 1 }) + assertConversion(Util.pixelsToCoords, 4, 4, + { x: 5, y: 5, px: 1, py: 1, width: 2, height: 2, ox: 1, oy: 1, sx: 0.5, sy: 0.5 }) + }) }) describe('getPastelColor', function() { it('1 => fefefe', function() { @@ -96,7 +196,27 @@ describe('Metamaps.Util.js', function() { }) }) describe('openLink', function() { - it.skip('TODO need `window`') + function stubWindow({ popupsAllowed }) { + const open = sandbox.stub(window, 'open').returns(popupsAllowed) + const alert = sandbox.stub(window, 'alert') + return { open, alert } + } + afterEach(function() { + sandbox.restore() + }) + it('blank url returns true', function() { + expect(Util.openLink('')).to.equal(true) + }) + it('popus allowed returns true', function() { + stubWindow({ popupsAllowed: true }) + expect(Util.openLink('https://www.google.ca')).to.equal(true) + }) + it('popups blocked shows alert', function() { + const { alert } = stubWindow({ popupsAllowed: false }) + expect(Util.openLink('https://www.google.ca')).to.equal(false) + expect(alert.calledWith('Please allow popups in order to open the link')) + .to.equal(true) + }) }) describe('mdToHTML', function() { it('filters xss', function() { @@ -124,9 +244,82 @@ describe('Metamaps.Util.js', function() { }) }) describe('logCanvasAttributes', function() { - it.skip('TODO need a canvas') + it('returns correct canvas attributes', function() { + const canvas = { + scaleOffsetX: 1, + scaleOffsetY: 2, + canvases: [{ size: { width: 3, height: 4 } }] + } + sinon.stub(Util, 'pixelsToCoords').returnsArg(1) + + const actual = Util.logCanvasAttributes(canvas) + + expect(actual.scaleX).to.equal(1) + expect(actual.scaleY).to.equal(2) + + // stub will return the x/y coords passed to pixelsToCoords + expect(actual.centreCoords.x).to.equal(3 / 2) + expect(actual.centreCoords.y).to.equal(4 / 2) + }) }) describe('resizeCanvas', function() { - it.skip('TODO need a canvas') + it('resizes, scales, and translates canvas', function() { + const oldAttr = { + scaleX: 1, + scaleY: 2, + centreCoords: { x: 3, y: 4 } + } + const newAttr = { + centreCoords: { x: 5, y: 6 } + } + const canvas = { + resize: sinon.spy(), + scale: sinon.spy(), + translate: sinon.spy() + } + sinon.stub(Util, 'logCanvasAttributes').withArgs(canvas) + .onFirstCall().returns(oldAttr) + .onSecondCall().returns(newAttr) + const jQueryStub = sinon.stub().returns({ + width: () => 7, + height: () => 8 + }) + + Util.resizeCanvas(canvas, jQueryStub) + + expect(canvas.resize.calledWith(7, 8), 'resize not called') + .to.equal(true) + expect(canvas.scale.calledWith(1, 2), 'scale not called') + .to.equal(true) + expect(canvas.translate.calledWith(5 - 3, 6 - 4), + 'translate not called') + .to.equal(true) + }) + }) + describe('emoji', function() { + EmojiMart.emojiIndex = { + emojis: { + emoji1: { native: '__EMOJI1__', colons: ':emoji1:' }, + emoji2: { native: '__EMOJI2__', colons: ':emoji2:' } + }, + emoticons: { + ':)': 'emoji1', + ':(': 'emoji2' + } + } + const withEmojiText = 'test __EMOJI1__ test __EMOJI2__ test' + const noEmojiText = 'test :emoji1: test :emoji2: test' + const emoticonsText = 'test :) test :( test' + const emoticonsReplacedText = 'test __EMOJI1__ test __EMOJI2__ test' + it('removeEmoji replaces emoji with text', function() { + expect(Util.removeEmoji(withEmojiText)).to.equal(noEmojiText) + }) + it('addEmoji replaces text with emoji', function() { + expect(Util.addEmoji(noEmojiText, { emoticons: false })).to.equal(withEmojiText) + }) + it('addEmoji replaces emoticons with emoji', function() { + expect(Util.addEmoji(emoticonsText, { emoticons: true })) + .to.equal(emoticonsReplacedText) + }) }) })