Added more webdriver tests testing against a real browser.

This commit is contained in:
orangemug 2018-01-05 17:45:55 +00:00
parent 6e86c60f89
commit 942b2240a7
47 changed files with 2244 additions and 637 deletions

View file

@ -1,4 +1,13 @@
{ {
"presets": ["env", "react"], "presets": ["env", "react"],
"plugins": ["transform-object-rest-spread", "transform-class-properties"] "plugins": ["transform-object-rest-spread", "transform-class-properties"],
"env": {
"test": {
"plugins": [
["istanbul", {
exclude: ["node_modules/**", "test/**"]
}]
]
}
}
} }

27
.circleci/config.yml Normal file
View file

@ -0,0 +1,27 @@
version: 2
jobs:
build:
docker:
# specify the version you desire here
- image: node:8
- image: selenium/standalone-chrome:3.1.0
working_directory: ~/repo
steps:
- checkout
- run:
name: "Create artifacts directory"
command: mkdir /tmp/artifacts
- restore_cache:
key: v1-dependencies-{{ checksum "package.json" }}
- run: npm install
- save_cache:
paths:
- node_modules
key: v1-dependencies-{{ checksum "package.json" }}
- run: mkdir -p /tmp/artifacts/logs
- run: DOCKER_HOST=localhost npm test
- run: npm run build
- run: ./node_modules/.bin/istanbul report --include /tmp/artifacts/coverage/coverage.json --dir /tmp/artifacts/coverage html lcov

2
.gitignore vendored
View file

@ -30,3 +30,5 @@ node_modules
# Ignore build files # Ignore build files
public public
/errorShots
/old

View file

@ -1,6 +0,0 @@
machine:
node:
version: 6
test:
post:
- npm run build

View file

@ -2,36 +2,36 @@ var webpack = require("webpack");
var WebpackDevServer = require("webpack-dev-server"); var WebpackDevServer = require("webpack-dev-server");
var webpackConfig = require("./webpack.production.config"); var webpackConfig = require("./webpack.production.config");
var testConfig = require("../test/config/specs"); var testConfig = require("../test/config/specs");
var artifacts = require("../test/artifacts");
var isDocker = require("is-docker");
var server; var server;
var SCREENSHOT_PATH = artifacts.pathSync("screenshots");
exports.config = { exports.config = {
specs: [ specs: [
'./test/specs/**/*.js' './test/functional/advanced.js'
], ],
exclude: [ exclude: [
], ],
maxInstances: 10, maxInstances: 10,
capabilities: [{ capabilities: [{
maxInstances: 5, maxInstances: 5,
browserName: 'firefox' browserName: 'chrome'
}], }],
sync: true, sync: true,
logLevel: 'verbose', logLevel: 'verbose',
coloredLogs: true, coloredLogs: true,
bail: 0, bail: 0,
screenshotPath: './errorShots/', screenshotPath: SCREENSHOT_PATH,
host: (isDocker() ? process.env.DOCKER_HOST : "127.0.0.1"),
baseUrl: 'http://localhost', baseUrl: 'http://localhost',
waitforTimeout: 10000, waitforTimeout: 10000,
connectionRetryTimeout: 90000, connectionRetryTimeout: 90000,
connectionRetryCount: 3, connectionRetryCount: 3,
services: ['phantomjs'],
framework: 'mocha', framework: 'mocha',
reporters: ['spec'], reporters: ['spec'],
phantomjsOpts: {
webdriverLogfile: 'phantomjs.log'
},
mochaOpts: { mochaOpts: {
ui: 'bdd', ui: 'bdd',
// Because we don't know how long the initial build will take... // Because we don't know how long the initial build will take...
@ -43,6 +43,6 @@ exports.config = {
server.listen(testConfig.port); server.listen(testConfig.port);
}, },
onComplete: function(exitCode) { onComplete: function(exitCode) {
server.close(); server.close()
} }
} }

View file

@ -1,4 +1,3 @@
var webpack = require('webpack'); var webpack = require('webpack');
var path = require('path'); var path = require('path');
var loaders = require('./webpack.loaders'); var loaders = require('./webpack.loaders');
@ -7,14 +6,9 @@ var HtmlWebpackPlugin = require('html-webpack-plugin');
var WebpackCleanupPlugin = require('webpack-cleanup-plugin'); var WebpackCleanupPlugin = require('webpack-cleanup-plugin');
var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
var CopyWebpackPlugin = require('copy-webpack-plugin'); var CopyWebpackPlugin = require('copy-webpack-plugin');
var artifacts = require("../test/artifacts");
var OUTPATH; var OUTPATH = artifacts.pathSync("/build");
if(process.env.CIRCLE_ARTIFACTS) {
OUTPATH = path.join(process.env.CIRCLE_ARTIFACTS, "build");
}
else {
OUTPATH = path.join(__dirname, '..', 'public');
}
module.exports = { module.exports = {
entry: { entry: {

1177
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -39,14 +39,14 @@
"ol-mapbox-style": "^1.0.1", "ol-mapbox-style": "^1.0.1",
"openlayers": "^4.4.2", "openlayers": "^4.4.2",
"prop-types": "^15.6.0", "prop-types": "^15.6.0",
"react": "16.0.0", "react": "^15.6.2",
"react-addons-pure-render-mixin": "^15.6.2", "react-addons-pure-render-mixin": "^15.6.2",
"react-autocomplete": "^1.7.2", "react-autocomplete": "^1.7.2",
"react-codemirror2": "^3.0.7", "react-codemirror2": "^3.0.7",
"react-collapse": "^4.0.3", "react-collapse": "^4.0.3",
"react-color": "^2.13.8", "react-color": "^2.13.8",
"react-copy-to-clipboard": "^5.0.1", "react-copy-to-clipboard": "^5.0.1",
"react-dom": "16.0.0", "react-dom": "^15.6.2",
"react-file-reader-input": "^1.1.4", "react-file-reader-input": "^1.1.4",
"react-height": "^3.0.0", "react-height": "^3.0.0",
"react-icon-base": "^2.1.1", "react-icon-base": "^2.1.1",
@ -90,6 +90,7 @@
"babel-core": "^6.26.0", "babel-core": "^6.26.0",
"babel-eslint": "^8.0.2", "babel-eslint": "^8.0.2",
"babel-loader": "7.1.1", "babel-loader": "7.1.1",
"babel-plugin-istanbul": "^4.1.5",
"babel-plugin-transform-class-properties": "^6.24.1", "babel-plugin-transform-class-properties": "^6.24.1",
"babel-plugin-transform-decorators-legacy": "^1.3.4", "babel-plugin-transform-decorators-legacy": "^1.3.4",
"babel-plugin-transform-flow-strip-types": "^6.22.0", "babel-plugin-transform-flow-strip-types": "^6.22.0",
@ -97,34 +98,38 @@
"babel-plugin-transform-runtime": "^6.23.0", "babel-plugin-transform-runtime": "^6.23.0",
"babel-preset-env": "^1.6.1", "babel-preset-env": "^1.6.1",
"babel-preset-react": "^6.24.1", "babel-preset-react": "^6.24.1",
"babel-register": "^6.26.0",
"babel-runtime": "^6.26.0", "babel-runtime": "^6.26.0",
"base64-loader": "^1.0.0", "base64-loader": "^1.0.0",
"copy-webpack-plugin": "^4.2.0", "copy-webpack-plugin": "^4.2.0",
"cors": "^2.8.4",
"css-loader": "^0.28.7", "css-loader": "^0.28.7",
"eslint": "^4.10.0", "eslint": "^4.10.0",
"eslint-plugin-react": "^7.4.0", "eslint-plugin-react": "^7.4.0",
"express": "^4.16.2",
"extract-text-webpack-plugin": "^3.0.2", "extract-text-webpack-plugin": "^3.0.2",
"file-loader": "^1.1.5", "file-loader": "^1.1.5",
"html-webpack-plugin": "^2.30.1", "html-webpack-plugin": "^2.30.1",
"is-docker": "^1.1.0",
"istanbul": "^0.4.5",
"istanbul-lib-coverage": "^1.1.1",
"json-loader": "^0.5.7", "json-loader": "^0.5.7",
"karma": "^1.7.1", "mkdirp": "^0.5.1",
"karma-chrome-launcher": "^2.2.0",
"karma-firefox-launcher": "^1.0.1",
"karma-mocha": "^1.3.0",
"karma-webpack": "^2.0.5",
"mocha": "^4.0.1", "mocha": "^4.0.1",
"mocha-loader": "^1.1.1",
"node-sass": "^4.6.0", "node-sass": "^4.6.0",
"react-hot-loader": "^3.1.1", "react-hot-loader": "^3.1.1",
"sass-loader": "^6.0.6", "sass-loader": "^6.0.6",
"selenium-standalone": "^6.11.0",
"style-loader": "^0.19.0", "style-loader": "^0.19.0",
"stylelint": "^7.13.0", "stylelint": "^7.13.0",
"stylelint-config-standard": "^15.0.1", "stylelint-config-standard": "^15.0.1",
"transform-loader": "^0.2.4", "transform-loader": "^0.2.4",
"uuid": "^3.1.0",
"wdio-mocha-framework": "^0.5.11", "wdio-mocha-framework": "^0.5.11",
"wdio-phantomjs-service": "^0.2.2", "wdio-phantomjs-service": "^0.2.2",
"wdio-selenium-standalone-service": "0.0.9",
"wdio-spec-reporter": "^0.1.2", "wdio-spec-reporter": "^0.1.2",
"webdriverio": "^4.8.0", "webdriverio": "^4.9.8",
"webpack": "^3.8.1", "webpack": "^3.8.1",
"webpack-bundle-analyzer": "^2.9.0", "webpack-bundle-analyzer": "^2.9.0",
"webpack-cleanup-plugin": "^0.5.1", "webpack-cleanup-plugin": "^0.5.1",

View file

@ -20,6 +20,7 @@ import { RevisionStore } from '../libs/revisions'
import LayerWatcher from '../libs/layerwatcher' import LayerWatcher from '../libs/layerwatcher'
import tokens from '../config/tokens.json' import tokens from '../config/tokens.json'
import isEqual from 'lodash.isequal' import isEqual from 'lodash.isequal'
import Debug from '../libs/debug'
function updateRootSpec(spec, fieldName, newValues) { function updateRootSpec(spec, fieldName, newValues) {
return { return {
@ -53,9 +54,19 @@ export default class App extends React.Component {
this.styleStore = new StyleStore() this.styleStore = new StyleStore()
} }
this.styleStore.latestStyle(mapStyle => this.onStyleChanged(mapStyle)) this.styleStore.latestStyle(mapStyle => this.onStyleChanged(mapStyle))
if(Debug.enabled()) {
Debug.set("maputnik", "styleStore", this.styleStore);
Debug.set("maputnik", "revisionStore", this.revisionStore);
}
}) })
} }
if(Debug.enabled()) {
Debug.set("maputnik", "revisionStore", this.revisionStore);
Debug.set("maputnik", "styleStore", this.styleStore);
}
this.state = { this.state = {
errors: [], errors: [],
infos: [], infos: [],
@ -70,22 +81,28 @@ export default class App extends React.Component {
this.layerWatcher = new LayerWatcher({ this.layerWatcher = new LayerWatcher({
onVectorLayersChange: v => this.setState({ vectorLayers: v }) onVectorLayersChange: v => this.setState({ vectorLayers: v })
}) })
this.onKeyDown = this.onKeyDown.bind(this);
}
onKeyDown(e) {
console.log("??? keyCode ctrlKey="+e.ctrlKey+", keyCode="+e.keyCode)
// Control + Z
if(e.ctrlKey && e.keyCode === 90) {
this.onUndo(e);
}
else if(e.ctrlKey && e.keyCode === 89) {
this.onRedo(e);
}
} }
componentDidMount() { componentDidMount() {
this.fetchSources(); this.fetchSources();
Mousetrap.bind(['ctrl+z'], this.onUndo.bind(this)); document.addEventListener("keydown", this.onKeyDown);
Mousetrap.bind(['ctrl+y'], this.onRedo.bind(this));
} }
componentWillUnmount() { componentWillUnmount() {
Mousetrap.unbind(['ctrl+z'], this.onUndo.bind(this)); document.removeEventListener("keydown", this.onKeyDown);
Mousetrap.unbind(['ctrl+y'], this.onRedo.bind(this));
}
onReset() {
this.styleStore.purge()
loadDefaultStyle(mapStyle => this.onStyleOpen(mapStyle))
} }
saveStyle(snapshotStyle) { saveStyle(snapshotStyle) {

View file

@ -4,6 +4,7 @@ import classnames from 'classnames'
class Button extends React.Component { class Button extends React.Component {
static propTypes = { static propTypes = {
"data-wd-key": PropTypes.string,
onClick: PropTypes.func, onClick: PropTypes.func,
style: PropTypes.object, style: PropTypes.object,
className: PropTypes.string, className: PropTypes.string,
@ -14,6 +15,7 @@ class Button extends React.Component {
return <a return <a
onClick={this.props.onClick} onClick={this.props.onClick}
className={classnames("maputnik-button", this.props.className)} className={classnames("maputnik-button", this.props.className)}
data-wd-key={this.props["data-wd-key"]}
style={this.props.style}> style={this.props.style}>
{this.props.children} {this.props.children}
</a> </a>

View file

@ -63,6 +63,7 @@ class ToolbarAction extends React.Component {
render() { render() {
return <a return <a
className='maputnik-toolbar-action' className='maputnik-toolbar-action'
data-wd-key={this.props.wdKey}
onClick={this.props.onClick} onClick={this.props.onClick}
> >
{this.props.children} {this.props.children}
@ -139,23 +140,23 @@ export default class Toolbar extends React.Component {
<h1>Maputnik</h1> <h1>Maputnik</h1>
</ToolbarLink> </ToolbarLink>
<div className="maputnik-toolbar__actions"> <div className="maputnik-toolbar__actions">
<ToolbarAction onClick={this.toggleModal.bind(this, 'open')}> <ToolbarAction wdKey="nav:open" onClick={this.toggleModal.bind(this, 'open')}>
<OpenIcon /> <OpenIcon />
<IconText>Open</IconText> <IconText>Open</IconText>
</ToolbarAction> </ToolbarAction>
<ToolbarAction onClick={this.toggleModal.bind(this, 'export')}> <ToolbarAction wdKey="nav:export" onClick={this.toggleModal.bind(this, 'export')}>
<MdFileDownload /> <MdFileDownload />
<IconText>Export</IconText> <IconText>Export</IconText>
</ToolbarAction> </ToolbarAction>
<ToolbarAction onClick={this.toggleModal.bind(this, 'sources')}> <ToolbarAction wdKey="nav:sources" onClick={this.toggleModal.bind(this, 'sources')}>
<SourcesIcon /> <SourcesIcon />
<IconText>Sources</IconText> <IconText>Sources</IconText>
</ToolbarAction> </ToolbarAction>
<ToolbarAction onClick={this.toggleModal.bind(this, 'settings')}> <ToolbarAction wdKey="nav:settings" onClick={this.toggleModal.bind(this, 'settings')}>
<SettingsIcon /> <SettingsIcon />
<IconText>Style Settings</IconText> <IconText>Style Settings</IconText>
</ToolbarAction> </ToolbarAction>
<ToolbarAction onClick={this.props.onInspectModeToggle}> <ToolbarAction wdKey="nav:inspect" onClick={this.props.onInspectModeToggle}>
<InspectionIcon /> <InspectionIcon />
<IconText> <IconText>
{ this.props.inspectModeEnabled && <span>Map Mode</span> } { this.props.inspectModeEnabled && <span>Map Mode</span> }

View file

@ -131,8 +131,7 @@ export default class FunctionSpecProperty extends React.Component {
/> />
) )
} }
return <div className={propClass} data-wd-key={"spec-field:"+this.props.fieldName}>
return <div className={propClass}>
{specField} {specField}
</div> </div>
} }

View file

@ -58,6 +58,8 @@ export default class SpecField extends React.Component {
name: this.props.fieldName, name: this.props.fieldName,
onChange: newValue => this.props.onChange(this.props.fieldName, newValue) onChange: newValue => this.props.onChange(this.props.fieldName, newValue)
} }
function childNodes() {
switch(this.props.fieldSpec.type) { switch(this.props.fieldSpec.type) {
case 'number': return ( case 'number': return (
<NumberInput <NumberInput
@ -124,4 +126,11 @@ export default class SpecField extends React.Component {
default: return null default: return null
} }
} }
return (
<div data-wd-key={"spec-field:"+this.props.fieldName}>
{childNodes.call(this)}
</div>
);
}
} }

View file

@ -89,7 +89,7 @@ export default class CombiningFilterEditor extends React.Component {
} }
return <div className="maputnik-filter-editor"> return <div className="maputnik-filter-editor">
<div className="maputnik-filter-editor-compound-select"> <div className="maputnik-filter-editor-compound-select" data-wd-key="layer-filter">
<DocLabel <DocLabel
label={"Compound Filter"} label={"Compound Filter"}
doc={styleSpec.latest.layer.filter.doc + " Combine multiple filters together by using a compound filter."} doc={styleSpec.latest.layer.filter.doc + " Combine multiple filters together by using a compound filter."}
@ -103,6 +103,7 @@ export default class CombiningFilterEditor extends React.Component {
{editorBlocks} {editorBlocks}
<div className="maputnik-filter-editor-add-wrapper"> <div className="maputnik-filter-editor-add-wrapper">
<Button <Button
data-wd-key="layer-filter-button"
className="maputnik-add-filter" className="maputnik-add-filter"
onClick={this.addFilterItem.bind(this)}> onClick={this.addFilterItem.bind(this)}>
Add filter Add filter

View file

@ -23,7 +23,6 @@ class LayerIcon extends React.Component {
case 'line': return <LineIcon {...iconProps} /> case 'line': return <LineIcon {...iconProps} />
case 'symbol': return <SymbolIcon {...iconProps} /> case 'symbol': return <SymbolIcon {...iconProps} />
case 'circle': return <CircleIcon {...iconProps} /> case 'circle': return <CircleIcon {...iconProps} />
default: return null
} }
} }
} }

View file

@ -94,8 +94,14 @@ class AutocompleteInput extends React.Component {
value={this.props.value} value={this.props.value}
items={this.props.options} items={this.props.options}
getItemValue={(item) => item[0]} getItemValue={(item) => item[0]}
onSelect={v => this.props.onChange(v)} onSelect={v => {
onChange={(e, v) => this.props.onChange(v)} console.log("@@ onSelect", v)
this.props.onChange(v)
}}
onChange={(e, v) => {
console.log("@@ onChange", v)
this.props.onChange(v)
}}
renderItem={(item, isHighlighted) => ( renderItem={(item, isHighlighted) => (
<div <div
key={item[0]} key={item[0]}

View file

@ -6,6 +6,7 @@ import DocLabel from '../fields/DocLabel'
/** Wrap a component with a label */ /** Wrap a component with a label */
class InputBlock extends React.Component { class InputBlock extends React.Component {
static propTypes = { static propTypes = {
"data-wd-key": PropTypes.string,
label: PropTypes.oneOfType([ label: PropTypes.oneOfType([
PropTypes.string, PropTypes.string,
PropTypes.element, PropTypes.element,
@ -24,6 +25,7 @@ class InputBlock extends React.Component {
render() { render() {
return <div style={this.props.style} return <div style={this.props.style}
data-wd-key={this.props["data-wd-key"]}
className={classnames({ className={classnames({
"maputnik-input-block": true, "maputnik-input-block": true,
"maputnik-action-block": this.props.action "maputnik-action-block": this.props.action

View file

@ -4,6 +4,7 @@ import PropTypes from 'prop-types'
class SelectInput extends React.Component { class SelectInput extends React.Component {
static propTypes = { static propTypes = {
value: PropTypes.string.isRequired, value: PropTypes.string.isRequired,
"data-wd-key": PropTypes.string.isRequired,
options: PropTypes.array.isRequired, options: PropTypes.array.isRequired,
style: PropTypes.object, style: PropTypes.object,
onChange: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,
@ -18,6 +19,7 @@ class SelectInput extends React.Component {
return <select return <select
className="maputnik-select" className="maputnik-select"
data-wd-key={this.props["data-wd-key"]}
style={this.props.style} style={this.props.style}
value={this.props.value} value={this.props.value}
onChange={e => this.props.onChange(e.target.value)} onChange={e => this.props.onChange(e.target.value)}

View file

@ -3,6 +3,7 @@ import PropTypes from 'prop-types'
class StringInput extends React.Component { class StringInput extends React.Component {
static propTypes = { static propTypes = {
"data-wd-key": PropTypes.string,
value: PropTypes.string, value: PropTypes.string,
style: PropTypes.object, style: PropTypes.object,
default: PropTypes.string, default: PropTypes.string,
@ -18,6 +19,7 @@ class StringInput extends React.Component {
} }
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
console.log("@@ STRING componentWillReceiveProps", JSON.stringify(nextProps))
this.setState({ value: nextProps.value || '' }) this.setState({ value: nextProps.value || '' })
} }
@ -40,12 +42,19 @@ class StringInput extends React.Component {
} }
return React.createElement(tag, { return React.createElement(tag, {
"data-wd-key": this.props["data-wd-key"],
className: classes.join(" "), className: classes.join(" "),
style: this.props.style, style: this.props.style,
value: this.state.value, value: this.state.value,
placeholder: this.props.default, placeholder: this.props.default,
onChange: e => this.setState({ value: e.target.value }), onChange: e => {
console.log("@@ STRING CHANGE", JSON.stringify(e.target.value));
this.setState({
value: e.target.value
})
},
onBlur: () => { onBlur: () => {
console.log("@@ STRING BLUR", JSON.stringify(this.state.value), "props:", JSON.stringify(this.props.value));
if(this.state.value!==this.props.value) this.props.onChange(this.state.value) if(this.state.value!==this.props.value) this.props.onChange(this.state.value)
} }
}); });

View file

@ -11,7 +11,11 @@ class MetadataBlock extends React.Component {
} }
render() { render() {
return <InputBlock label={"Comments"} doc={"Comments for the current layer. This is non-standard and not in the spec."}> return <InputBlock
label={"Comments"}
doc={"Comments for the current layer. This is non-standard and not in the spec."}
data-wd-key="layer-comment"
>
<StringInput <StringInput
multi={true} multi={true}
value={this.props.value} value={this.props.value}

View file

@ -4,7 +4,6 @@ import PropTypes from 'prop-types'
import {Controlled as CodeMirror} from 'react-codemirror2' import {Controlled as CodeMirror} from 'react-codemirror2'
import InputBlock from '../inputs/InputBlock' import InputBlock from '../inputs/InputBlock'
import StringInput from '../inputs/StringInput' import StringInput from '../inputs/StringInput'
import SelectInput from '../inputs/SelectInput'
import 'codemirror/mode/javascript/javascript' import 'codemirror/mode/javascript/javascript'
import 'codemirror/addon/lint/lint' import 'codemirror/addon/lint/lint'

View file

@ -19,11 +19,6 @@ import MultiButtonInput from '../inputs/MultiButtonInput'
import { changeType, changeProperty } from '../../libs/layer' import { changeType, changeProperty } from '../../libs/layer'
import layout from '../../config/layout.json' import layout from '../../config/layout.json'
class UnsupportedLayer extends React.Component {
render() {
return <div></div>
}
}
function layoutGroups(layerType) { function layoutGroups(layerType) {
const layerGroup = { const layerGroup = {
@ -121,6 +116,7 @@ export default class LayerEditor extends React.Component {
case 'layer': return <div> case 'layer': return <div>
<LayerIdBlock <LayerIdBlock
value={this.props.layer.id} value={this.props.layer.id}
wdKey="layer-editor.layer-id"
onChange={newId => this.props.onLayerIdChange(this.props.layer.id, newId)} onChange={newId => this.props.onLayerIdChange(this.props.layer.id, newId)}
/> />
<LayerTypeBlock <LayerTypeBlock
@ -171,7 +167,6 @@ export default class LayerEditor extends React.Component {
layer={this.props.layer} layer={this.props.layer}
onChange={this.props.onLayerChanged} onChange={this.props.onLayerChanged}
/> />
default: return null
} }
} }
@ -181,6 +176,7 @@ export default class LayerEditor extends React.Component {
return !(layerType === 'background' && group.type === 'source') return !(layerType === 'background' && group.type === 'source')
}).map(group => { }).map(group => {
return <LayerEditorGroup return <LayerEditorGroup
data-wd-key={group.title}
key={group.title} key={group.title}
title={group.title} title={group.title}
isActive={this.state.editorGroups[group.title]} isActive={this.state.editorGroups[group.title]}

View file

@ -5,6 +5,7 @@ import Collapser from './Collapser'
export default class LayerEditorGroup extends React.Component { export default class LayerEditorGroup extends React.Component {
static propTypes = { static propTypes = {
"data-wd-key": PropTypes.string,
title: PropTypes.string.isRequired, title: PropTypes.string.isRequired,
isActive: PropTypes.bool.isRequired, isActive: PropTypes.bool.isRequired,
children: PropTypes.element.isRequired, children: PropTypes.element.isRequired,
@ -14,6 +15,7 @@ export default class LayerEditorGroup extends React.Component {
render() { render() {
return <div> return <div>
<div className="maputnik-layer-editor-group" <div className="maputnik-layer-editor-group"
data-wd-key={"layer-editor-group:"+this.props["data-wd-key"]}
onClick={e => this.props.onActiveToggle(!this.props.isActive)} onClick={e => this.props.onActiveToggle(!this.props.isActive)}
> >
<span>{this.props.title}</span> <span>{this.props.title}</span>

View file

@ -8,11 +8,14 @@ import StringInput from '../inputs/StringInput'
class LayerIdBlock extends React.Component { class LayerIdBlock extends React.Component {
static propTypes = { static propTypes = {
value: PropTypes.string.isRequired, value: PropTypes.string.isRequired,
wdKey: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,
} }
render() { render() {
return <InputBlock label={"ID"} doc={styleSpec.latest.layer.id.doc}> return <InputBlock label={"ID"} doc={styleSpec.latest.layer.id.doc}
data-wd-key={this.props.wdKey}
>
<StringInput <StringInput
value={this.props.value} value={this.props.value}
onChange={this.props.onChange} onChange={this.props.onChange}

View file

@ -162,6 +162,7 @@ class LayerListContainer extends React.Component {
const groupPrefix = layerPrefix(layers[0].id) const groupPrefix = layerPrefix(layers[0].id)
if(layers.length > 1) { if(layers.length > 1) {
const grp = <LayerListGroup const grp = <LayerListGroup
data-wd-key={[groupPrefix, idx].join('-')}
key={[groupPrefix, idx].join('-')} key={[groupPrefix, idx].join('-')}
title={groupPrefix} title={groupPrefix}
isActive={!this.isCollapsed(groupPrefix, idx)} isActive={!this.isCollapsed(groupPrefix, idx)}
@ -217,6 +218,7 @@ class LayerListContainer extends React.Component {
<div className="maputnik-multibutton"> <div className="maputnik-multibutton">
<a <a
onClick={this.toggleModal.bind(this, 'add')} onClick={this.toggleModal.bind(this, 'add')}
data-wd-key="layer-list:add-layer"
className="maputnik-button maputnik-button-selected"> className="maputnik-button maputnik-button-selected">
Add Layer Add Layer
</a> </a>

View file

@ -5,6 +5,7 @@ import Collapser from './Collapser'
export default class LayerListGroup extends React.Component { export default class LayerListGroup extends React.Component {
static propTypes = { static propTypes = {
title: PropTypes.string.isRequired, title: PropTypes.string.isRequired,
"data-wd-key": PropTypes.string,
isActive: PropTypes.bool.isRequired, isActive: PropTypes.bool.isRequired,
onActiveToggle: PropTypes.func.isRequired onActiveToggle: PropTypes.func.isRequired
} }
@ -12,6 +13,7 @@ export default class LayerListGroup extends React.Component {
render() { render() {
return <li className="maputnik-layer-list-group"> return <li className="maputnik-layer-list-group">
<div className="maputnik-layer-list-group-header" <div className="maputnik-layer-list-group-header"
data-wd-key={"layer-list-group:"+this.props["data-wd-key"]}
onClick={e => this.props.onActiveToggle(!this.props.isActive)} onClick={e => this.props.onActiveToggle(!this.props.isActive)}
> >
<span className="maputnik-layer-list-group-title">{this.props.title}</span> <span className="maputnik-layer-list-group-title">{this.props.title}</span>

View file

@ -33,6 +33,7 @@ class IconAction extends React.Component {
static propTypes = { static propTypes = {
action: PropTypes.string.isRequired, action: PropTypes.string.isRequired,
onClick: PropTypes.func.isRequired, onClick: PropTypes.func.isRequired,
wdKey: PropTypes.string
} }
renderIcon() { renderIcon() {
@ -41,13 +42,13 @@ class IconAction extends React.Component {
case 'show': return <VisibilityIcon /> case 'show': return <VisibilityIcon />
case 'hide': return <VisibilityOffIcon /> case 'hide': return <VisibilityOffIcon />
case 'delete': return <DeleteIcon /> case 'delete': return <DeleteIcon />
default: return null
} }
} }
render() { render() {
return <a return <a
className="maputnik-layer-list-icon-action" className="maputnik-layer-list-icon-action"
data-wd-key={this.props.wdKey}
onClick={this.props.onClick} onClick={this.props.onClick}
> >
{this.renderIcon()} {this.renderIcon()}
@ -92,6 +93,7 @@ class LayerListItem extends React.Component {
return <li return <li
key={this.props.layerId} key={this.props.layerId}
onClick={e => this.props.onLayerSelect(this.props.layerId)} onClick={e => this.props.onLayerSelect(this.props.layerId)}
data-wd-key={"layer-list-item:"+this.props.layerId}
className={classnames({ className={classnames({
"maputnik-layer-list-item": true, "maputnik-layer-list-item": true,
"maputnik-layer-list-item-selected": this.props.isSelected, "maputnik-layer-list-item-selected": this.props.isSelected,
@ -101,14 +103,17 @@ class LayerListItem extends React.Component {
<span className="maputnik-layer-list-item-id">{this.props.layerId}</span> <span className="maputnik-layer-list-item-id">{this.props.layerId}</span>
<span style={{flexGrow: 1}} /> <span style={{flexGrow: 1}} />
<IconAction <IconAction
wdKey={"layer-list-item:"+this.props.layerId+":delete"}
action={'delete'} action={'delete'}
onClick={e => this.props.onLayerDestroy(this.props.layerId)} onClick={e => this.props.onLayerDestroy(this.props.layerId)}
/> />
<IconAction <IconAction
wdKey={"layer-list-item:"+this.props.layerId+":copy"}
action={'copy'} action={'copy'}
onClick={e => this.props.onLayerCopy(this.props.layerId)} onClick={e => this.props.onLayerCopy(this.props.layerId)}
/> />
<IconAction <IconAction
wdKey={"layer-list-item:"+this.props.layerId+":toggle-visibility"}
action={this.props.visibility === 'visible' ? 'hide' : 'show'} action={this.props.visibility === 'visible' ? 'hide' : 'show'}
onClick={e => this.props.onLayerVisibilityToggle(this.props.layerId)} onClick={e => this.props.onLayerVisibilityToggle(this.props.layerId)}
/> />

View file

@ -4,12 +4,12 @@ import PropTypes from 'prop-types'
import styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec' import styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
import InputBlock from '../inputs/InputBlock' import InputBlock from '../inputs/InputBlock'
import StringInput from '../inputs/StringInput' import StringInput from '../inputs/StringInput'
import SelectInput from '../inputs/SelectInput'
import AutocompleteInput from '../inputs/AutocompleteInput' import AutocompleteInput from '../inputs/AutocompleteInput'
class LayerSourceBlock extends React.Component { class LayerSourceBlock extends React.Component {
static propTypes = { static propTypes = {
value: PropTypes.string, value: PropTypes.string,
wdKey: PropTypes.string,
onChange: PropTypes.func, onChange: PropTypes.func,
sourceIds: PropTypes.array, sourceIds: PropTypes.array,
} }
@ -20,7 +20,9 @@ class LayerSourceBlock extends React.Component {
} }
render() { render() {
return <InputBlock label={"Source"} doc={styleSpec.latest.layer.source.doc}> return <InputBlock label={"Source"} doc={styleSpec.latest.layer.source.doc}
data-wd-key={this.props.wdKey}
>
<AutocompleteInput <AutocompleteInput
value={this.props.value} value={this.props.value}
onChange={this.props.onChange} onChange={this.props.onChange}

View file

@ -4,7 +4,6 @@ import PropTypes from 'prop-types'
import styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec' import styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
import InputBlock from '../inputs/InputBlock' import InputBlock from '../inputs/InputBlock'
import StringInput from '../inputs/StringInput' import StringInput from '../inputs/StringInput'
import SelectInput from '../inputs/SelectInput'
import AutocompleteInput from '../inputs/AutocompleteInput' import AutocompleteInput from '../inputs/AutocompleteInput'
class LayerSourceLayer extends React.Component { class LayerSourceLayer extends React.Component {
@ -22,7 +21,9 @@ class LayerSourceLayer extends React.Component {
} }
render() { render() {
return <InputBlock label={"Source Layer"} doc={styleSpec.latest.layer['source-layer'].doc}> return <InputBlock label={"Source Layer"} doc={styleSpec.latest.layer['source-layer'].doc}
data-wd-key="layer-source-layer"
>
<AutocompleteInput <AutocompleteInput
keepMenuWithinWindowBounds={!!this.props.isFixed} keepMenuWithinWindowBounds={!!this.props.isFixed}
value={this.props.value} value={this.props.value}

View file

@ -8,11 +8,14 @@ import SelectInput from '../inputs/SelectInput'
class LayerTypeBlock extends React.Component { class LayerTypeBlock extends React.Component {
static propTypes = { static propTypes = {
value: PropTypes.string.isRequired, value: PropTypes.string.isRequired,
wdKey: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,
} }
render() { render() {
return <InputBlock label={"Type"} doc={styleSpec.latest.layer.type.doc}> return <InputBlock label={"Type"} doc={styleSpec.latest.layer.type.doc}
data-wd-key={this.props.wdKey}
>
<SelectInput <SelectInput
options={[ options={[
['background', 'Background'], ['background', 'Background'],

View file

@ -12,7 +12,9 @@ class MaxZoomBlock extends React.Component {
} }
render() { render() {
return <InputBlock label={"Max Zoom"} doc={styleSpec.latest.layer.maxzoom.doc}> return <InputBlock label={"Max Zoom"} doc={styleSpec.latest.layer.maxzoom.doc}
data-wd-key="max-zoom"
>
<NumberInput <NumberInput
value={this.props.value} value={this.props.value}
onChange={this.props.onChange} onChange={this.props.onChange}

View file

@ -12,7 +12,9 @@ class MinZoomBlock extends React.Component {
} }
render() { render() {
return <InputBlock label={"Min Zoom"} doc={styleSpec.latest.layer.minzoom.doc}> return <InputBlock label={"Min Zoom"} doc={styleSpec.latest.layer.minzoom.doc}
data-wd-key="min-zoom"
>
<NumberInput <NumberInput
value={this.props.value} value={this.props.value}
onChange={this.props.onChange} onChange={this.props.onChange}

View file

@ -4,7 +4,6 @@ import PropTypes from 'prop-types'
import Button from '../Button' import Button from '../Button'
import InputBlock from '../inputs/InputBlock' import InputBlock from '../inputs/InputBlock'
import StringInput from '../inputs/StringInput' import StringInput from '../inputs/StringInput'
import SelectInput from '../inputs/SelectInput'
import Modal from './Modal' import Modal from './Modal'
import LayerTypeBlock from '../layers/LayerTypeBlock' import LayerTypeBlock from '../layers/LayerTypeBlock'
@ -108,19 +107,26 @@ class AddModal extends React.Component {
isOpen={this.props.isOpen} isOpen={this.props.isOpen}
onOpenToggle={this.props.onOpenToggle} onOpenToggle={this.props.onOpenToggle}
title={'Add Layer'} title={'Add Layer'}
data-wd-key="modal:add-layer"
> >
<div className="maputnik-add-layer"> <div className="maputnik-add-layer">
<LayerIdBlock <LayerIdBlock
value={this.state.id} value={this.state.id}
onChange={v => this.setState({ id: v })} wdKey="add-layer.layer-id"
onChange={v => {
console.log("@@ upper_id_change", JSON.stringify(v))
this.setState({ id: v })
}}
/> />
<LayerTypeBlock <LayerTypeBlock
value={this.state.type} value={this.state.type}
wdKey="add-layer.layer-type"
onChange={v => this.setState({ type: v })} onChange={v => this.setState({ type: v })}
/> />
{this.state.type !== 'background' && {this.state.type !== 'background' &&
<LayerSourceBlock <LayerSourceBlock
sourceIds={sources} sourceIds={sources}
wdKey="add-layer.layer-source-block"
value={this.state.source} value={this.state.source}
onChange={v => this.setState({ source: v })} onChange={v => this.setState({ source: v })}
/> />
@ -133,7 +139,11 @@ class AddModal extends React.Component {
onChange={v => this.setState({ 'source-layer': v })} onChange={v => this.setState({ 'source-layer': v })}
/> />
} }
<Button className="maputnik-add-layer-button" onClick={this.addLayer.bind(this)}> <Button
className="maputnik-add-layer-button"
onClick={this.addLayer.bind(this)}
data-wd-key="add-layer"
>
Add Layer Add Layer
</Button> </Button>
</div> </div>

View file

@ -5,7 +5,6 @@ import { saveAs } from 'file-saver'
import styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec' import styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
import InputBlock from '../inputs/InputBlock' import InputBlock from '../inputs/InputBlock'
import StringInput from '../inputs/StringInput' import StringInput from '../inputs/StringInput'
import SelectInput from '../inputs/SelectInput'
import CheckboxInput from '../inputs/CheckboxInput' import CheckboxInput from '../inputs/CheckboxInput'
import Button from '../Button' import Button from '../Button'
import Modal from './Modal' import Modal from './Modal'
@ -215,6 +214,7 @@ class ExportModal extends React.Component {
render() { render() {
return <Modal return <Modal
data-wd-key="export-modal"
isOpen={this.props.isOpen} isOpen={this.props.isOpen}
onOpenToggle={this.props.onOpenToggle} onOpenToggle={this.props.onOpenToggle}
title={'Export Style'} title={'Export Style'}

View file

@ -5,6 +5,7 @@ import Overlay from './Overlay'
class Modal extends React.Component { class Modal extends React.Component {
static propTypes = { static propTypes = {
"data-wd-key": PropTypes.string,
isOpen: PropTypes.bool.isRequired, isOpen: PropTypes.bool.isRequired,
title: PropTypes.string.isRequired, title: PropTypes.string.isRequired,
onOpenToggle: PropTypes.func.isRequired, onOpenToggle: PropTypes.func.isRequired,
@ -13,12 +14,15 @@ class Modal extends React.Component {
render() { render() {
return <Overlay isOpen={this.props.isOpen}> return <Overlay isOpen={this.props.isOpen}>
<div className="maputnik-modal"> <div className="maputnik-modal"
data-wd-key={this.props["data-wd-key"]}
>
<header className="maputnik-modal-header"> <header className="maputnik-modal-header">
<h1 className="maputnik-modal-header-title">{this.props.title}</h1> <h1 className="maputnik-modal-header-title">{this.props.title}</h1>
<span className="maputnik-modal-header-space"></span> <span className="maputnik-modal-header-space"></span>
<a className="maputnik-modal-header-toggle" <a className="maputnik-modal-header-toggle"
onClick={() => this.props.onOpenToggle(false)} onClick={() => this.props.onOpenToggle(false)}
data-wd-key={this.props["data-wd-key"]+".close-modal"}
> >
<CloseIcon /> <CloseIcon />
</a> </a>

View file

@ -133,6 +133,7 @@ class OpenModal extends React.Component {
} }
return <Modal return <Modal
data-wd-key="open-modal"
isOpen={this.props.isOpen} isOpen={this.props.isOpen}
onOpenToggle={() => this.onOpenToggle()} onOpenToggle={() => this.onOpenToggle()}
title={'Open Style'} title={'Open Style'}
@ -151,9 +152,9 @@ class OpenModal extends React.Component {
<p> <p>
Load from a URL. Note that the URL must have <a href="https://enable-cors.org" target="_blank" rel="noopener noreferrer">CORS enabled</a>. Load from a URL. Note that the URL must have <a href="https://enable-cors.org" target="_blank" rel="noopener noreferrer">CORS enabled</a>.
</p> </p>
<input type="text" ref={(input) => this.styleUrlElement = input} className="maputnik-input" placeholder="Enter URL..."/> <input data-wd-key="open-modal.url.input" type="text" ref={(input) => this.styleUrlElement = input} className="maputnik-input" placeholder="Enter URL..."/>
<div> <div>
<Button className="maputnik-big-button" onClick={this.onOpenUrl.bind(this)}>Open URL</Button> <Button data-wd-key="open-modal.url.button" className="maputnik-big-button" onClick={this.onOpenUrl.bind(this)}>Open URL</Button>
</div> </div>
</section> </section>

View file

@ -42,6 +42,7 @@ class SettingsModal extends React.Component {
const metadata = this.props.mapStyle.metadata || {} const metadata = this.props.mapStyle.metadata || {}
const inputProps = { } const inputProps = { }
return <Modal return <Modal
data-wd-key="modal-settings"
isOpen={this.props.isOpen} isOpen={this.props.isOpen}
onOpenToggle={this.props.onOpenToggle} onOpenToggle={this.props.onOpenToggle}
title={'Style Settings'} title={'Style Settings'}
@ -49,18 +50,21 @@ class SettingsModal extends React.Component {
<div style={{minWidth: 350}}> <div style={{minWidth: 350}}>
<InputBlock label={"Name"} doc={styleSpec.latest.$root.name.doc}> <InputBlock label={"Name"} doc={styleSpec.latest.$root.name.doc}>
<StringInput {...inputProps} <StringInput {...inputProps}
data-wd-key="modal-settings.name"
value={this.props.mapStyle.name} value={this.props.mapStyle.name}
onChange={this.changeStyleProperty.bind(this, "name")} onChange={this.changeStyleProperty.bind(this, "name")}
/> />
</InputBlock> </InputBlock>
<InputBlock label={"Owner"} doc={"Owner ID of the style. Used by Mapbox or future style APIs."}> <InputBlock label={"Owner"} doc={"Owner ID of the style. Used by Mapbox or future style APIs."}>
<StringInput {...inputProps} <StringInput {...inputProps}
data-wd-key="modal-settings.owner"
value={this.props.mapStyle.owner} value={this.props.mapStyle.owner}
onChange={this.changeStyleProperty.bind(this, "owner")} onChange={this.changeStyleProperty.bind(this, "owner")}
/> />
</InputBlock> </InputBlock>
<InputBlock label={"Sprite URL"} doc={styleSpec.latest.$root.sprite.doc}> <InputBlock label={"Sprite URL"} doc={styleSpec.latest.$root.sprite.doc}>
<StringInput {...inputProps} <StringInput {...inputProps}
data-wd-key="modal-settings.sprite"
value={this.props.mapStyle.sprite} value={this.props.mapStyle.sprite}
onChange={this.changeStyleProperty.bind(this, "sprite")} onChange={this.changeStyleProperty.bind(this, "sprite")}
/> />
@ -68,6 +72,7 @@ class SettingsModal extends React.Component {
<InputBlock label={"Glyphs URL"} doc={styleSpec.latest.$root.glyphs.doc}> <InputBlock label={"Glyphs URL"} doc={styleSpec.latest.$root.glyphs.doc}>
<StringInput {...inputProps} <StringInput {...inputProps}
data-wd-key="modal-settings.glyphs"
value={this.props.mapStyle.glyphs} value={this.props.mapStyle.glyphs}
onChange={this.changeStyleProperty.bind(this, "glyphs")} onChange={this.changeStyleProperty.bind(this, "glyphs")}
/> />
@ -75,6 +80,7 @@ class SettingsModal extends React.Component {
<InputBlock label={"Mapbox Access Token"} doc={"Public access token for Mapbox services."}> <InputBlock label={"Mapbox Access Token"} doc={"Public access token for Mapbox services."}>
<StringInput {...inputProps} <StringInput {...inputProps}
data-wd-key="modal-settings.maputnik:mapbox_access_token"
value={metadata['maputnik:mapbox_access_token']} value={metadata['maputnik:mapbox_access_token']}
onChange={this.changeMetadataProperty.bind(this, "maputnik:mapbox_access_token")} onChange={this.changeMetadataProperty.bind(this, "maputnik:mapbox_access_token")}
/> />
@ -82,6 +88,7 @@ class SettingsModal extends React.Component {
<InputBlock label={"OpenMapTiles Access Token"} doc={"Public access token for the OpenMapTiles CDN."}> <InputBlock label={"OpenMapTiles Access Token"} doc={"Public access token for the OpenMapTiles CDN."}>
<StringInput {...inputProps} <StringInput {...inputProps}
data-wd-key="modal-settings.maputnik:openmaptiles_access_token"
value={metadata['maputnik:openmaptiles_access_token']} value={metadata['maputnik:openmaptiles_access_token']}
onChange={this.changeMetadataProperty.bind(this, "maputnik:openmaptiles_access_token")} onChange={this.changeMetadataProperty.bind(this, "maputnik:openmaptiles_access_token")}
/> />
@ -89,6 +96,7 @@ class SettingsModal extends React.Component {
<InputBlock label={"Style Renderer"} doc={"Choose the default Maputnik renderer for this style."}> <InputBlock label={"Style Renderer"} doc={"Choose the default Maputnik renderer for this style."}>
<SelectInput {...inputProps} <SelectInput {...inputProps}
data-wd-key="modal-settings.maputnik:renderer"
options={[ options={[
['mbgljs', 'MapboxGL JS'], ['mbgljs', 'MapboxGL JS'],
['ol3', 'Open Layers 3'], ['ol3', 'Open Layers 3'],

44
src/libs/debug.js Normal file
View file

@ -0,0 +1,44 @@
import querystring from 'querystring'
const debugStore = {};
function enabled() {
const qs = querystring.parse(window.location.search.slice(1));
if(qs.hasOwnProperty("debug")) {
return !!qs.debug.match(/^(|1|true)$/);
}
else {
return false;
}
}
function genErr() {
return new Error("Debug not enabled, enable by appending '?debug' to your query string");
}
function set(namespace, key, value) {
if(!enabled()) {
throw genErr();
}
debugStore[namespace] = debugStore[namespace] || {};
debugStore[namespace][key] = value;
}
function get(namespace, key) {
if(!enabled()) {
throw genErr();
}
if(debugStore.hasOwnProperty(namespace)) {
return debugStore[namespace][key];
}
}
const mod = {
enabled,
get,
set
}
window.debug = mod;
export default mod;

View file

@ -36,3 +36,15 @@ $toolbar-offset: 0;
@import 'zoomproperty'; @import 'zoomproperty';
@import 'popup'; @import 'popup';
@import 'map'; @import 'map';
/**
* Hacks for webdriverio isVisibleWithinViewport
*/
#app {
height: 100vh;
}
.maputnik-layout {
height: 100vh;
}

39
test/artifacts.js Normal file
View file

@ -0,0 +1,39 @@
var path = require("path");
var mkdirp = require("mkdirp");
function genPath(subPath) {
subPath = subPath || ".";
var buildPath;
if(process.env.CIRCLE_ARTIFACTS) {
buildPath = path.join(process.env.CIRCLE_ARTIFACTS, subPath);
}
else {
buildPath = path.join(__dirname, '..', 'build', subPath);
}
return buildPath;
}
module.exports.path = function(subPath) {
var dirPath = genPath(subPath);
return new Promise(function(resolve, reject) {
mkdirp(dirPath, function(err) {
if(err) {
reject(err);
}
else {
resolve(dirPath);
}
});
});
}
module.exports.pathSync = function(subPath) {
var dirPath = genPath(subPath);
mkdirp.sync(dirPath);
return dirPath;
}

12
test/example-style.json Normal file
View file

@ -0,0 +1,12 @@
{
"id": "test-style",
"version": 8,
"name": "Test Style",
"metadata": {
"maputnik:renderer": "mbgljs"
},
"sources": {},
"glyphs": "https://demo.tileserver.org/fonts/{fontstack}/{range}.pbf",
"sprites": "https://demo.tileserver.org/fonts/{fontstack}/{range}.pbf",
"layers": []
}

1077
test/functional/advanced.js Normal file

File diff suppressed because it is too large Load diff

93
test/geojson-server.js Normal file
View file

@ -0,0 +1,93 @@
const cors = require("cors");
const express = require("express");
const fs = require("fs");
const sourceData = require("./sources");
var app = express();
app.use(cors());
function buildStyle(opts) {
opts = opts || {};
opts = Object.assign({
sources: {}
}, opts);
return {
"id": "test-style",
"version": 8,
"name": "Test Style",
"metadata": {
"maputnik:renderer": "mbgljs"
},
"sources": opts.sources,
"glyphs": "https://demo.tileserver.org/fonts/{fontstack}/{range}.pbf",
"sprites": "https://demo.tileserver.org/fonts/{fontstack}/{range}.pbf",
"layers": []
}
}
function buildGeoJSONSource(data) {
return {
type: "vector",
data: data
};
}
function buildResterSource(req, key) {
return {
"tileSize": 256,
"tiles": [
req.protocol + '://' + req.get('host') + "/" + key + "/{x}/{y}/{z}"
],
"type": "raster"
};
}
app.get("/sources/raster/{x}/{y}/{z}", function(req, res) {
res.status(404).end();
})
app.get("/styles/empty/:sources", function(req, res) {
var reqSources = req.params.sources.split(",");
var sources = {};
// reqSources.forEach(function(key) {
// sources[key] = buildGeoJSONSource(sourceData[key]);
// });
reqSources.forEach(function(key) {
var parts = key.split(":");
var type = parts[0];
var key = parts[1];
if(type === "geojson") {
sources[key] = buildGeoJSONSource(sourceData[key]);
}
else if(type === "raster") {
sources[key] = buildResterSource(req, key);
}
else {
console.error("ERR: Invalid type: %s", type);
throw "Invalid type"
}
});
var json = buildStyle({
sources: sources
});
res.send(json);
})
app.get("/example-style.json", function(req, res) {
res.json(
JSON.parse(
fs.readFileSync(__dirname+"/example-style.json").toString()
)
);
})
module.exports = app;

15
test/sources/example.json Normal file
View file

@ -0,0 +1,15 @@
{
"type":"FeatureCollection",
"features":[
{
"type":"Feature",
"properties": {
"name": "Dinagat Islands"
},
"geometry":{
"type": "Point",
"coordinates": [125.6, 10.1]
}
}
]
}

3
test/sources/index.js Normal file
View file

@ -0,0 +1,3 @@
module.exports = {
example: require("./example")
};

View file

@ -1,15 +0,0 @@
var assert = require('assert');
var config = require("../config/specs");
describe('maputnik', function() {
it('check logo exists', function () {
browser.url(config.baseUrl);
browser.waitForExist(".maputnik-toolbar-link");
var src = browser.getAttribute(".maputnik-toolbar-link img", "src");
assert.equal(src, config.baseUrl+'/img/logo-color.svg');
});
});

6
test/wd-helper.js Normal file
View file

@ -0,0 +1,6 @@
module.exports = {
"$": function(key, selector) {
selector = selector || "";
return "*[data-wd-key='"+key+"'] "+selector;
}
}