Merge pull request #1 from metamaps/develop

Updates from metamaps base 04/07/2017
This commit is contained in:
John Gillanders 2017-07-04 12:59:20 +12:00 committed by GitHub
commit db6d2e77da
13 changed files with 200 additions and 71 deletions

View file

@ -42,6 +42,7 @@ class Map < ApplicationRecord
after_create :after_created
after_update :after_updated
after_save :update_deferring_topics_and_synapses, if: :permission_changed?
before_destroy :before_destroyed
delegate :count, to: :topics, prefix: :topic # same as `def topic_count; topics.count; end`
delegate :count, to: :synapses, prefix: :synapse
@ -158,4 +159,10 @@ class Map < ApplicationRecord
end
end
handle_asynchronously :after_updated_async
def before_destroyed
Map.where(source_id: id).find_each do |forked_map|
forked_map.update(source_id: nil)
end
end
end

View file

@ -37,8 +37,8 @@ class Mapping < ApplicationRecord
'map_' + map.id.to_s,
type: 'synapseAdded',
synapse: mappable.filtered,
topic1: mappable.topic1.filtered,
topic2: mappable.topic2.filtered,
topic1: mappable.topic1&.filtered,
topic2: mappable.topic2&.filtered,
mapping_id: id
)
meta = { 'mapping_id': id }

View file

@ -37,13 +37,13 @@
map = notification.notified_object.map %>
added topic <span class="in-bold"><%= topic.name %></span> to map <span class="in-bold"><%= map.name %></span>
<% when TOPIC_CONNECTED_1 %>
<% topic1 = notification.notified_object.topic1
topic2 = notification.notified_object.topic2 %>
connected <span class="in-bold"><%= topic1.name %></span> to <span class="in-bold"><%= topic2.name %></span>
<% topic1 = notification.notified_object&.topic1 %>
<% topic2 = notification.notified_object&.topic2 %>
connected <span class="in-bold"><%= topic1&.name %></span> to <span class="in-bold"><%= topic2&.name %></span>
<% when TOPIC_CONNECTED_2 %>
<% topic1 = notification.notified_object.topic1
topic2 = notification.notified_object.topic2 %>
connected <span class="in-bold"><%= topic2.name %></span> to <span class="in-bold"><%= topic1.name %></span>
<% topic1 = notification.notified_object&.topic1 %>
<% topic2 = notification.notified_object&.topic2 %>
connected <span class="in-bold"><%= topic2&.name %></span> to <span class="in-bold"><%= topic1&.name %></span>
<% when MESSAGE_FROM_DEVS %>
<%= notification.subject %>
<% end %>

View file

@ -1,4 +1,4 @@
# frozen_string_literal: true
METAMAPS_VERSION = '3.4'
METAMAPS_VERSION = '3.4.1'
METAMAPS_BUILD = `git log -1 --pretty=%H`.chomp[0..11].freeze
METAMAPS_LAST_UPDATED = `git log -1 --pretty='%ad'`.split(' ').values_at(1, 2, 4).join(' ').freeze

View file

@ -0,0 +1,5 @@
module.exports = {
"rules": {
"no-unused-expressions": "off"
}
}

View file

@ -1,10 +1,8 @@
/* global describe, it */
import chai from 'chai'
import { expect } from 'chai'
import Import from '../src/Metamaps/Import'
const { expect } = chai
import Import from '../../src/Metamaps/Import.js'
describe('Metamaps.Import.js', function() {
it('has a topic whitelist', function() {

View file

@ -1,10 +1,8 @@
/* global describe, it */
import chai from 'chai'
import { expect } from 'chai'
import Util from '../src/Metamaps/Util'
const { expect } = chai
import Util from '../../src/Metamaps/Util'
describe('Metamaps.Util.js', function() {
describe('splitLine', function() {

View file

@ -1,50 +0,0 @@
/* global describe, it */
import React from 'react'
import TestUtils from 'react-addons-test-utils' // ES6
import ImportDialogBox from '../../src/components/ImportDialogBox.js'
import Dropzone from 'react-dropzone'
import chai from 'chai'
const { expect } = chai
describe('ImportDialogBox', function() {
it('has an Export CSV button', function(done) {
const onExport = format => {
if (format === 'csv') done()
}
const detachedComp = TestUtils.renderIntoDocument(<ImportDialogBox onExport={onExport} />)
const button = TestUtils.findRenderedDOMComponentWithClass(detachedComp, 'export-csv')
const buttonNode = React.findDOMNode(button)
expect(button).to.exist;
TestUtils.Simulate.click(buttonNode)
})
it('has an Export JSON button', function(done) {
const onExport = format => {
if (format === 'json') done()
}
const detachedComp = TestUtils.renderIntoDocument(<ImportDialogBox onExport={onExport} />)
const button = TestUtils.findRenderedDOMComponentWithClass(detachedComp, 'export-json')
const buttonNode = React.findDOMNode(button)
expect(button).to.exist;
TestUtils.Simulate.click(buttonNode)
})
it('has a Download screenshot button', function(done) {
const downloadScreenshot = () => { done() }
const detachedComp = TestUtils.renderIntoDocument(<ImportDialogBox downloadScreenshot={downloadScreenshot()} />)
const button = TestUtils.findRenderedDOMComponentWithClass(detachedComp, 'download-screenshot')
const buttonNode = React.findDOMNode(button)
expect(button).to.exist;
TestUtils.Simulate.click(buttonNode)
})
it('has a file uploader', function(done) {
const uploadedFile = { file: 'mock a file' }
const onFileAdded = file => { if (file === uploadedFile) done() }
const detachedComp = TestUtils.renderIntoDocument(<ImportDialogBox onExport={() => {}} onFileAdded={onFileAdded} />)
const dropzone = TestUtils.findRenderedComponentWithType(detachedComp, Dropzone)
expect(dropzone).to.exist;
dropzone.props.onDropAccepted([uploadedFile], { preventDefault: () => {} })
})
})

View file

@ -0,0 +1,58 @@
/* global describe, it */
import React from 'react'
import ImportDialogBox from '../../../src/components/MapView/ImportDialogBox.js'
import Dropzone from 'react-dropzone'
import { expect } from 'chai'
import { shallow } from 'enzyme'
import sinon from 'sinon'
describe('ImportDialogBox', function() {
const csvExport = sinon.spy()
const jsonExport = sinon.spy()
const onExport = format => () => {
if (format === 'csv') {
csvExport()
} else if (format === 'json') {
jsonExport()
}
}
const testExportButton = ({ description, cssClass, exporter }) => {
it(description, () => {
const wrapper = shallow(<ImportDialogBox onExport={onExport} />)
const button = wrapper.find(cssClass)
expect(button).to.exist
button.simulate('click')
expect(exporter).to.have.property('callCount', 1)
})
}
testExportButton({
description: 'has an Export CSV button',
cssClass: '.export-csv',
exporter: csvExport
})
testExportButton({
description: 'has an Export JSON button',
cssClass: '.export-json',
exporter: jsonExport
})
it('has a Download screenshot button', () => {
const downloadScreenshot = sinon.spy()
const wrapper = shallow(<ImportDialogBox onExport={() => null} downloadScreenshot={downloadScreenshot} />)
const button = wrapper.find('.download-screenshot')
expect(button).to.exist
button.simulate('click')
expect(downloadScreenshot).to.have.property('callCount', 1)
})
it('has a file uploader', () => {
const uploadedFile = {}
const onFileAdded = sinon.spy()
const wrapper = shallow(<ImportDialogBox onExport={() => null} onFileAdded={onFileAdded} />)
const dropzone = wrapper.find(Dropzone)
dropzone.props().onDropAccepted([uploadedFile], { preventDefault: () => {} })
expect(onFileAdded).to.have.property('callCount', 1)
expect(onFileAdded.calledWith(uploadedFile)).to.equal(true)
})
})

View file

@ -0,0 +1,101 @@
/* global describe, it */
import React from 'react'
import { expect } from 'chai'
import { shallow } from 'enzyme'
import sinon from 'sinon'
import InfoAndHelp from '../../../src/components/common/InfoAndHelp.js'
import MapInfoBox from '../../../src/components/MapView/MapInfoBox.js'
function assertTooltip({ wrapper, description, cssClass, tooltipText, callback }) {
it(description, function() {
expect(wrapper.find(cssClass)).to.exist
expect(wrapper.find(`${cssClass} .tooltipsAbove`).text()).to.equal(tooltipText)
wrapper.find(cssClass).simulate('click')
expect(callback).to.have.property('callCount', 1)
})
}
function assertContent({ currentUser, map }) {
const onInfoClick = sinon.spy()
const onHelpClick = sinon.spy()
const onStarClick = sinon.spy()
const wrapper = shallow(
<InfoAndHelp map={map} currentUser={currentUser}
onInfoClick={onInfoClick}
onHelpClick={onHelpClick}
onMapStar={onStarClick}
mapIsStarred={false}
/>)
if (map) {
it('renders MapInfoBox', () => expect(wrapper.find(MapInfoBox)).to.exist)
assertTooltip({
wrapper,
description: 'renders Map Info icon',
cssClass: '.mapInfoIcon',
tooltipText: 'Map Info',
callback: onInfoClick
})
} else {
it('does not render MapInfoBox', () => expect(wrapper.find(MapInfoBox).length).to.equal(0))
it('does not render Map Info icon', () => expect(wrapper.find('.mapInfoIcon').length).to.equal(0))
}
if (map && currentUser) {
it('renders Star icon', () => {
expect(wrapper.find('.starMap')).to.exist
wrapper.find('.starMap').simulate('click')
expect(onStarClick).to.have.property('callCount', 1)
})
} else {
it('does not render the Star icon', () => expect(wrapper.find('.starMap').length).to.equal(0))
}
// common content
assertTooltip({
wrapper,
description: 'renders Help icon',
cssClass: '.openCheatsheet',
tooltipText: 'Help',
callback: onHelpClick
})
it('renders clearfloat at the end', function() {
const clearfloat = wrapper.find('.clearfloat')
expect(clearfloat).to.exist
expect(wrapper.find('.infoAndHelp').children().last()).to.eql(clearfloat)
})
}
function assertStarLogic({ mapIsStarred }) {
const onMapStar = sinon.spy()
const onMapUnstar = sinon.spy()
const wrapper = shallow(
<InfoAndHelp map={{}} currentUser={{}}
onMapStar={onMapStar}
onMapUnstar={onMapUnstar}
mapIsStarred={mapIsStarred}
/>)
const starWrapper = wrapper.find('.starMap')
starWrapper.simulate('click')
it(mapIsStarred ? 'has unstar content' : 'has star content', () => {
expect(starWrapper.hasClass('starred')).to.equal(mapIsStarred)
expect(starWrapper.find('.tooltipsAbove').text()).to.equal(mapIsStarred ? 'Unstar' : 'Star')
expect(onMapStar).to.have.property('callCount', mapIsStarred ? 0 : 1)
expect(onMapUnstar).to.have.property('callCount', mapIsStarred ? 1 : 0)
})
}
describe('InfoAndHelp', function() {
describe('no currentUser, map is present', function() {
assertContent({ currentUser: null, map: {} })
})
describe('currentUser is present, map is present', function() {
assertContent({ currentUser: {}, map: {} })
})
describe('no currentUser, no map', function() {
assertContent({ currentUser: null, map: null })
})
assertStarLogic({ mapIsStarred: true })
assertStarLogic({ mapIsStarred: false })
})

View file

@ -5,12 +5,12 @@ const win = doc.defaultView
global.document = doc
global.window = win
// take all properties of the window object and also attach it to the
// take all properties of the window object and also attach it to the
// mocha global object
propagateToGlobal(win)
// from mocha-jsdom https://github.com/rstacruz/mocha-jsdom/blob/master/index.js#L80
function propagateToGlobal (window) {
function propagateToGlobal(window) {
for (let key in window) {
if (!window.hasOwnProperty(key)) continue
if (key in global) continue

View file

@ -5,7 +5,7 @@
"scripts": {
"build": "webpack",
"build:watch": "webpack --watch",
"test": "mocha-webpack --webpack-config webpack.test.config.js --require frontend/test/support/dom.js frontend/test",
"test": "mocha-webpack --webpack-config webpack.test.config.js --require frontend/test_support/dom.js --recursive frontend/test",
"eslint": "eslint frontend",
"eslint:fix": "eslint --fix frontend"
},
@ -58,6 +58,7 @@
"babel-eslint": "^7.1.1",
"chai": "^3.5.0",
"circular-dependency-plugin": "^2.0.0",
"enzyme": "^2.8.2",
"eslint": "^3.11.1",
"eslint-config-standard": "^6.2.1",
"eslint-plugin-promise": "^3.4.0",
@ -66,7 +67,8 @@
"jsdom": "^9.11.0",
"mocha": "^3.2.0",
"mocha-webpack": "^0.7.0",
"react-addons-test-utils": "^15.4.2"
"react-addons-test-utils": "^15.5.1",
"sinon": "^2.2.0"
},
"optionalDependencies": {
"raml2html": "4.0.5"

View file

@ -1,5 +1,15 @@
const config = require('./webpack.config')
config.target = 'node'
config.externals = config.externals.concat([
'react/lib/ExecutionEnvironment',
'react/lib/ReactContext',
'react/addons',
'react-test-renderer/shallow',
'react-dom/test-utils',
'canvas',
'bufferutil',
'utf-8-validate'
])
module.exports = config