From b42a812a4583aa28478a11991725ad620a9a76a2 Mon Sep 17 00:00:00 2001 From: Colin McLeod Date: Wed, 27 Jan 2016 00:52:57 -0800 Subject: [PATCH] React port nearing completetion. Adding tests --- README.md | 2 + __tests__/fixtures/valid-backup.json | 2 +- __tests__/test-controller-import.js | 196 ---------------- __tests__/test-import.js | 218 ++++++++++++++++++ ...rvice-serializer.js => test-serializer.js} | 0 __tests__/testUtils.js | 24 ++ devServer.js | 1 + package.json | 7 +- src/app/Coriolis.jsx | 14 +- src/app/components/ActiveLink.jsx | 1 + src/app/components/CostSection.jsx | 1 + src/app/components/Header.jsx | 2 +- src/app/components/LineChart.jsx | 21 +- src/app/components/ModalImport.jsx | 47 ++-- src/app/components/PowerManagement.jsx | 4 +- src/app/components/Slider.jsx | 30 ++- src/app/pages/OutfittingPage.jsx | 4 +- src/app/shipyard/Ship.js | 8 +- src/app/stores/Persist.js | 63 +++-- src/index.html | 1 + src/less/charts.less | 2 +- src/less/modal.less | 2 +- src/less/outfit.less | 10 +- 23 files changed, 362 insertions(+), 298 deletions(-) delete mode 100644 __tests__/test-controller-import.js create mode 100644 __tests__/test-import.js rename __tests__/{test-service-serializer.js => test-serializer.js} (100%) create mode 100644 __tests__/testUtils.js diff --git a/README.md b/README.md index 9aea0b4b..3acf221e 100755 --- a/README.md +++ b/README.md @@ -10,6 +10,8 @@ Coriolis was created using assets and imagery from Elite: Dangerous, with the pe ## Contributing +Chat to us on [HipChat](https://www.hipchat.com/gfYQiZcmy)! + Please [submit issues](https://github.com/cmmcleod/coriolis/issues), or better yet [pull requests](https://github.com/cmmcleod/coriolis/pulls) for any corrections or additions to the database or the code. ### Feature Requests, Suggestions & Bugs diff --git a/__tests__/fixtures/valid-backup.json b/__tests__/fixtures/valid-backup.json index 9067d141..683511e1 100644 --- a/__tests__/fixtures/valid-backup.json +++ b/__tests__/fixtures/valid-backup.json @@ -16,7 +16,7 @@ "Miner": "25A5A5A4D4A5A5C0s0s24242l2l---04054a1q02022o27" }, "cobra_mk_iii": { - "Example": "24A4A4A3D3A3A4C0s0s2d2d0m0445032b2o2753.AwRj4yKA.CwBhEYyrKhmMQ===", + "Example": "24A4A4A3D3A3A4C0s0s2d2d0m0445032b2o2753.AwRj4yKA.CwBhEYyrKhmMQ===" }, "imperial_clipper": { "Cargo": "03A5D5A5D4D5D4C--0s0s----0605450302020101", diff --git a/__tests__/test-controller-import.js b/__tests__/test-controller-import.js deleted file mode 100644 index 8ae919b9..00000000 --- a/__tests__/test-controller-import.js +++ /dev/null @@ -1,196 +0,0 @@ -xdescribe('Import Controller', function() { - beforeEach(module('app')); - - var importController, $rootScope, $stateParams, scope; - - var eventStub = { - preventDefault: function(){ }, - stopPropagation: function(){ } - }; - - beforeEach(inject(function(_$rootScope_, $controller) { - $rootScope = _$rootScope_; - $rootScope.discounts = { - ship: 1, - components: 1 - }; - $stateParams = { }; - scope = $rootScope.$new(); - scope.$parent.dismiss = function() {}; - - var store = {}; - - spyOn(localStorage, 'getItem').and.callFake(function (key) { - return store[key]; - }); - spyOn(localStorage, 'setItem').and.callFake(function (key, value) { - return store[key] = value + ''; - }); - spyOn(localStorage, 'clear').and.callFake(function () { - store = {}; - }); - - importController = $controller('ImportController', { $rootScope: $rootScope, $scope: scope, $stateParams: $stateParams }); - })); - - describe('Import Backup', function() { - - it('imports a valid backup', function() { - var importData = __json__['fixtures/valid-backup']; - scope.importString = angular.toJson(importData); - scope.validateImport(); - expect(scope.importValid).toBeTruthy(); - expect(scope.errorMsg).toEqual(null); - scope.process(); - expect(scope.processed).toBeTruthy(); - scope.import(); - expect(angular.fromJson(localStorage.getItem('builds'))).toEqual(importData.builds); - expect(angular.fromJson(localStorage.getItem('comparisons'))).toEqual(importData.comparisons); - expect(localStorage.getItem('insurance')).toEqual(importData.insurance); - expect(angular.fromJson(localStorage.getItem('discounts'))).toEqual(importData.discounts); - }); - - it('imports an old valid backup', function() { - var importData = __json__['fixtures/old-valid-export']; - scope.importString = angular.toJson(importData); - scope.validateImport(); - expect(scope.importValid).toBeTruthy(); - expect(scope.errorMsg).toEqual(null); - scope.process(); - expect(scope.processed).toBeTruthy(); - scope.import(); - expect(angular.fromJson(localStorage.getItem('builds'))).toEqual(importData.builds); - }); - - it('catches an invalid backup', function() { - var importData = __json__['fixtures/valid-backup']; - - scope.importString = 'null'; - scope.validateImport(); - expect(scope.importValid).toBeFalsy(); - expect(scope.errorMsg).toEqual('Must be an object or array!'); - - scope.importString = '{ "builds": "Should not be a string" }'; - scope.validateImport(); - expect(scope.importValid).toBeFalsy(); - expect(scope.errorMsg).toEqual('builds must be an object!'); - - scope.importString = angular.toJson(importData).replace('anaconda', 'invalid_ship'); - scope.validateImport(); - expect(scope.importValid).toBeFalsy(); - expect(scope.errorMsg).toEqual('"invalid_ship" is not a valid Ship Id!'); - - scope.importString = angular.toJson(importData).replace('Dream', ''); - scope.validateImport(); - expect(scope.importValid).toBeFalsy(); - expect(scope.errorMsg).toEqual('Imperial Clipper build "" must be a string at least 1 character long!'); - - invalidImportData = angular.copy(importData); - invalidImportData.builds.asp = null; // Remove Asp Miner build used in comparison - scope.importString = angular.toJson(invalidImportData); - scope.validateImport(); - expect(scope.importValid).toBeFalsy(); - expect(scope.errorMsg).toEqual('asp build "Miner" data is missing!'); - - }); - - }); - - describe('Import Detailed Build', function() { - - it('imports a valid v1 build', function() { - var importData = __json__['fixtures/anaconda-test-detailed-export-v1']; - scope.importString = angular.toJson(importData); - scope.validateImport(); - expect(scope.importValid).toBeTruthy(); - expect(scope.errorMsg).toEqual(null); - scope.process(); - expect(scope.processed).toBeTruthy(); - scope.import(); - expect(angular.fromJson(localStorage.getItem('builds'))).toEqual({ - anaconda: { 'Test': '48A6A6A5A8A8A5C2c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04--0303326b.Iw18ZlA=.Aw18ZlA=' } - }); - }); - - it('imports a valid v2 build', function() { - var importData = __json__['fixtures/anaconda-test-detailed-export-v2']; - scope.importString = angular.toJson(importData); - scope.validateImport(); - expect(scope.importValid).toBeTruthy(); - expect(scope.errorMsg).toEqual(null); - scope.process(); - expect(scope.processed).toBeTruthy(); - scope.import(); - expect(angular.fromJson(localStorage.getItem('builds'))).toEqual({ - anaconda: { 'Test': '48A6A6A5A8A8A5C2c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04--0303326b.AwRj4zNKqA==.CwBhCYzBGW9qCTSqs5xA' } - }); - }); - - it('catches an invalid build', function() { - var importData = __json__['fixtures/anaconda-test-detailed-export-v2']; - scope.importString = angular.toJson(importData).replace('components', 'comps'); - scope.validateImport(); - expect(scope.importValid).toBeFalsy(); - expect(scope.errorMsg).toEqual('Anaconda Build "Test": Invalid data'); - }); - - }); - - describe('Import Detaild Builds Array', function() { - - it('imports all builds', function() { - var importData = __json__['fixtures/valid-detailed-export']; - var expectedBuilds = __json__['fixtures/expected-builds']; - scope.importString = angular.toJson(importData); - scope.validateImport(); - expect(scope.importValid).toBeTruthy(); - expect(scope.errorMsg).toEqual(null); - scope.process(); - expect(scope.processed).toBeTruthy(); - scope.import(); - var builds = angular.fromJson(localStorage.getItem('builds')); - for (var s in builds) { - for (var b in builds[s]) { - expect(builds[s][b]).toEqual(expectedBuilds[s][b]); - } - } - }); - - }); - - describe('Import E:D Shipyard Builds', function() { - - it('imports a valid builds', function() { - var imports = __json__['fixtures/ed-shipyard-import-valid']; - - for (var i = 0; i < imports.length; i++ ) { - scope.importString = imports[i].buildText; - scope.validateImport(); - expect(scope.importValid).toBeTruthy(); - expect(scope.errorMsg).toEqual(null, 'Build #' + i + ': ' + imports[i].buildName); - - if (scope.importValid) { // No point in carrying out other assertions - scope.process(); - expect(scope.processed).toBeTruthy(); - scope.import(); - var allBuilds = angular.fromJson(localStorage.getItem('builds')); - var shipBuilds = allBuilds ? allBuilds[imports[i].shipId] : null; - var build = shipBuilds ? shipBuilds[imports[i].buildName] : null; - expect(build).toEqual(imports[i].buildCode, 'Build #' + i + ': ' + imports[i].buildName); - } - } - }); - - it('catches invalid builds', function() { - var imports = __json__['fixtures/ed-shipyard-import-invalid']; - for (var i = 0; i < imports.length; i++ ) { - scope.importString = imports[i].buildText; - scope.validateImport(); - expect(scope.importValid).toBeFalsy(); - expect(scope.errorMsg).toEqual(imports[i].errorMsg); - } - }); - - }); - -}); diff --git a/__tests__/test-import.js b/__tests__/test-import.js new file mode 100644 index 00000000..c0d71233 --- /dev/null +++ b/__tests__/test-import.js @@ -0,0 +1,218 @@ +jest.dontMock('../src/app/stores/Persist'); +jest.dontMock('../src/app/components/TranslatedComponent'); +jest.dontMock('../src/app/components/ModalImport'); + +import React from 'react'; +import ReactDOM from 'react-dom'; +import TU from 'react-testutils-additions'; +import Utils from './testUtils'; +import Persist from '../src/app/stores/Persist'; +import { getLanguage } from '../src/app/i18n/Language'; + +describe('Import Controller', function() { + + const Persist = require('../src/app/stores/Persist').default; + const ModalImport = require('../src/app/components/ModalImport').default; + const mockContext = { + language: getLanguage('en'), + sizeRatio: 1, + openMenu: jest.genMockFunction(), + closeMenu: jest.genMockFunction(), + showModal: jest.genMockFunction(), + hideModal: jest.genMockFunction(), + tooltip: jest.genMockFunction(), + termtip: jest.genMockFunction(), + onWindowResize: jest.genMockFunction() + }; + + let modal, render, ContextProvider = Utils.createContextProvider(mockContext); + + /** + * Clear saved builds, and reset React DOM + */ + function reset() { + Persist.deleteAll(); + render = TU.renderIntoDocument(); + modal = TU.findRenderedComponentWithType(render, ModalImport); + } + + /** + * Simulate user import text entry / paste + * @param {string} text Import text / raw data + */ + function pasteText(text) { + let textarea = TU.findRenderedDOMComponentWithTag(render, 'textarea'); + TU.Simulate.change(textarea, { target: { value: text } }); + } + + /** + * Simulate click on Proceed button + */ + function clickProceed() { + let proceedButton = TU.findRenderedDOMComponentWithId(render, 'proceed'); + TU.Simulate.click(proceedButton); + } + + /** + * Simulate click on Import button + */ + function clickImport() { + let importButton = TU.findRenderedDOMComponentWithId(render, 'import'); + TU.Simulate.click(importButton); + } + + describe('Import Backup', function() { + + beforeEach(reset); + + it('imports a valid backup', function() { + let importData = require('./fixtures/valid-backup'); + let importString = JSON.stringify(importData); + + expect(modal.state.importValid).toEqual(false); + expect(modal.state.errorMsg).toEqual(null); + pasteText(importString); + expect(modal.state.importValid).toBe(true); + expect(modal.state.errorMsg).toEqual(null); + expect(modal.state.builds).toEqual(importData.builds); + expect(modal.state.comparisons).toEqual(importData.comparisons); + expect(modal.state.discounts).toEqual(importData.discounts); + expect(modal.state.insurance).toBe(importData.insurance.toLowerCase()); + clickProceed(); + expect(modal.state.processed).toBe(true); + expect(modal.state.errorMsg).toEqual(null); + clickImport(); + expect(Persist.getBuilds()).toEqual(importData.builds); + expect(Persist.getComparisons()).toEqual(importData.comparisons); + expect(Persist.getInsurance()).toEqual(importData.insurance.toLowerCase()); + expect(Persist.getShipDiscount()).toEqual(importData.discounts[0]); + expect(Persist.getModuleDiscount()).toEqual(importData.discounts[1]); + }); + + it('imports an old valid backup', function() { + let importData = require('./fixtures/old-valid-export'); + let importStr = JSON.stringify(importData); + + pasteText(importStr); + expect(modal.state.builds).toEqual(importData.builds); + expect(modal.state.importValid).toBe(true); + expect(modal.state.errorMsg).toEqual(null); + clickProceed(); + expect(modal.state.processed).toBeTruthy(); + clickImport(); + expect(Persist.getBuilds()).toEqual(importData.builds); + }); + + it('catches an invalid backup', function() { + let importData = require('./fixtures/valid-backup'); + let invalidImportData = Object.assign({}, importData); + invalidImportData.builds.asp = null; // Remove Asp Miner build used in comparison + + pasteText('"this is not valid"'); + expect(modal.state.importValid).toBeFalsy(); + expect(modal.state.errorMsg).toEqual('Must be an object or array!'); + pasteText('{ "builds": "Should not be a string" }'); + expect(modal.state.importValid).toBeFalsy(); + expect(modal.state.errorMsg).toEqual('builds must be an object!'); + pasteText(JSON.stringify(importData).replace('anaconda', 'invalid_ship')); + expect(modal.state.importValid).toBeFalsy(); + expect(modal.state.errorMsg).toEqual('"invalid_ship" is not a valid Ship Id!'); + pasteText(JSON.stringify(importData).replace('Dream', '')); + expect(modal.state.importValid).toBeFalsy(); + expect(modal.state.errorMsg).toEqual('Imperial Clipper build "" must be a string at least 1 character long!'); + pasteText(JSON.stringify(invalidImportData)); + expect(modal.state.importValid).toBeFalsy(); + expect(modal.state.errorMsg).toEqual('asp build "Miner" data is missing!'); + }); + }); + + describe('Import Detailed Build', function() { + + beforeEach(reset); + + it('imports a valid v3 build', function() { + let importData = require('./fixtures/anaconda-test-detailed-export-v3'); + pasteText(JSON.stringify(importData)); + + expect(modal.state.importValid).toBeTruthy(); + expect(modal.state.errorMsg).toEqual(null); + + clickProceed(); + + expect(modal.state.processed).toBeTruthy(); + + clickImport(); + + expect(Persist.getBuilds()).toEqual({ + anaconda: { 'Test': '48A6A6A5A8A8A5C2c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04--0303326b.AwRj4zNKqA==.CwBhCYzBGW9qCTSqs5xA' } + }); + }); + + it('catches an invalid build', function() { + let importData = require('./fixtures/anaconda-test-detailed-export-v3'); + pasteText(JSON.stringify(importData).replace('components', 'comps')); + + expect(modal.state.importValid).toBeFalsy(); + expect(modal.state.errorMsg).toEqual('Anaconda Build "Test": Invalid data'); + }); + }); + + describe('Import Detaild Builds Array', function() { + + beforeEach(reset); + + it('imports all builds', function() { + let importData = require('./fixtures/valid-detailed-export'); + let expectedBuilds = require('./fixtures/expected-builds'); + + pasteText(JSON.stringify(importData)); + expect(modal.state.importValid).toBeTruthy(); + expect(modal.state.errorMsg).toEqual(null); + clickProceed(); + expect(modal.state.processed).toBeTruthy(); + clickImport(); + + let builds = Persist.getBuilds(); + + for (let s in builds) { + for (let b in builds[s]) { + expect(builds[s][b]).toEqual(expectedBuilds[s][b]); + } + } + }); + }); + + describe('Import E:D Shipyard Builds', function() { + + it('imports a valid builds', function() { + let imports = require('./fixtures/ed-shipyard-import-valid'); + + for (let i = 0; i < imports.length; i++ ) { + reset(); + pasteText(imports[i].buildText); + expect(modal.state.importValid).toBeTruthy(); + expect(modal.state.errorMsg).toEqual(null, 'Build #' + i + ': ' + imports[i].buildName); + clickProceed(); + expect(modal.state.processed).toBeTruthy(); + clickImport(); + let allBuilds = Persist.getBuilds(); + let shipBuilds = allBuilds ? allBuilds[imports[i].shipId] : null; + let build = shipBuilds ? shipBuilds[imports[i].buildName] : null; + expect(build).toEqual(imports[i].buildCode, 'Build #' + i + ': ' + imports[i].buildName); + } + }); + + it('catches invalid builds', function() { + let imports = require('./fixtures/ed-shipyard-import-invalid'); + + for (let i = 0; i < imports.length; i++ ) { + reset(); + pasteText(imports[i].buildText); + expect(modal.state.importValid).toBeFalsy(); + expect(modal.state.errorMsg).toEqual(imports[i].errorMsg); + } + }); + + }); + +}); diff --git a/__tests__/test-service-serializer.js b/__tests__/test-serializer.js similarity index 100% rename from __tests__/test-service-serializer.js rename to __tests__/test-serializer.js diff --git a/__tests__/testUtils.js b/__tests__/testUtils.js new file mode 100644 index 00000000..662f87e8 --- /dev/null +++ b/__tests__/testUtils.js @@ -0,0 +1,24 @@ +import React from 'react'; + +const TestUtils = { + createContextProvider: function(context) { + var _contextTypes = {}; + + Object.keys(context).forEach(function(key) { + _contextTypes[key] = React.PropTypes.any; + }); + + return React.createClass({ + displayName: 'ContextProvider', + childContextTypes: _contextTypes, + getChildContext() { return context; }, + + render() { + return React.Children.only(this.props.children); + } + }); + } +}; + + +export default TestUtils; \ No newline at end of file diff --git a/devServer.js b/devServer.js index eb8d8c80..fc5a0098 100644 --- a/devServer.js +++ b/devServer.js @@ -5,6 +5,7 @@ var config = require('./webpack.config.dev'); new WebpackDevServer(webpack(config), { publicPath: config.output.publicPath, hot: true, + headers: { "Access-Control-Allow-Origin": "*" }, historyApiFallback: { rewrites: [ // For some reason connect-history-api-fallback does not allow '.' in the URL for history fallback... diff --git a/package.json b/package.json index 019aeacc..3691386e 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "/node_modules/react", "/node_modules/react-dom", "/node_modules/react-addons-test-utils", + "/node_modules/react-testutils-additions", "/node_modules/fbjs", "/node_modules/fbemitter", "/node_modules/classnames", @@ -44,7 +45,8 @@ "/node_modules/coriolis-data", "/src/app/shipyard", "/src/app/i18n", - "/src/app/utils" + "/src/app/utils", + "/__tests__" ] }, "devDependencies": { @@ -56,7 +58,7 @@ "babel-preset-es2015": "^6.3.13", "babel-preset-react": "^6.3.13", "babel-preset-stage-0": "^6.3.13", - "css-loader": "^0.23.0", + "css-loader": "^0.23.0", "eslint": "2.0.0-beta.1", "eslint-plugin-react": "^3.15.0", "expose-loader": "^0.7.1", @@ -69,6 +71,7 @@ "less": "^2.5.3", "less-loader": "^2.2.1", "react-addons-test-utils": "^0.14.6", + "react-testutils-additions": "^0.16.0", "rimraf": "^2.4.3", "style-loader": "^0.13.0", "url-loader": "^0.5.6", diff --git a/src/app/Coriolis.jsx b/src/app/Coriolis.jsx index 2b0b5036..8e771650 100644 --- a/src/app/Coriolis.jsx +++ b/src/app/Coriolis.jsx @@ -238,13 +238,11 @@ export default class Coriolis extends React.Component { * @return {React.Component} The main app */ render() { - return ( -
-
- { this.state.page ? : } - { this.state.modal } - { this.state.tooltip } -
- ); + return
+
+ { this.state.page ? : } + { this.state.modal } + { this.state.tooltip } +
; } } diff --git a/src/app/components/ActiveLink.jsx b/src/app/components/ActiveLink.jsx index 26cf40cd..13cb5ee9 100644 --- a/src/app/components/ActiveLink.jsx +++ b/src/app/components/ActiveLink.jsx @@ -5,6 +5,7 @@ import cn from 'classnames'; /** * Returns true if the current window location equals the link + * @param {string} href URL/Href * @return {boolean} If matches */ function isActive(href) { diff --git a/src/app/components/CostSection.jsx b/src/app/components/CostSection.jsx index c3a01901..8acefa2c 100644 --- a/src/app/components/CostSection.jsx +++ b/src/app/components/CostSection.jsx @@ -613,6 +613,7 @@ export default class CostSection extends TranslatedComponent { if (nextProps.ship != this.props.ship || nextProps.code != this.props.code) { this._updateAmmoCosts(nextProps.ship); this._updateRetrofit(nextProps.ship, retrofitShip); + this.setState({ total: nextProps.ship.totalCost }); this._sortCost(nextProps.ship); } } diff --git a/src/app/components/Header.jsx b/src/app/components/Header.jsx index 40e0e4a5..eec26052 100644 --- a/src/app/components/Header.jsx +++ b/src/app/components/Header.jsx @@ -260,7 +260,7 @@ export default class Header extends TranslatedComponent {
{translate('tooltips')} -
{(tips ? '✔' : '✖')}
+
{(tips ? '✓' : '✗')}

{translate('insurance')} diff --git a/src/app/components/LineChart.jsx b/src/app/components/LineChart.jsx index ada105d9..595b7b46 100644 --- a/src/app/components/LineChart.jsx +++ b/src/app/components/LineChart.jsx @@ -88,10 +88,13 @@ export default class LineChart extends TranslatedComponent { y0 = func(x0), tips = this.tipContainer, yTotal = 0, - flip = (xPos / innerWidth > 0.65), + flip = (xPos / innerWidth > 0.60), tipWidth = 0, tipHeightPx = tips.selectAll('rect').node().getBoundingClientRect().height; + + xPos = xScale(x0); // Clamp xPos + tips.selectAll('text.label.y').text(function(d, i) { let yVal = series ? y0[series[i]] : y0; yTotal += yVal; @@ -110,7 +113,7 @@ export default class LineChart extends TranslatedComponent { tips.attr('transform', 'translate(' + xPos + ',' + tipY + ')'); tips.selectAll('text.label').attr('x', flip ? -12 : 12).style('text-anchor', flip ? 'end' : 'start'); tips.selectAll('text.label.x').text(formats.f2(x0)).append('tspan').attr('class', 'metric').text(' ' + xUnit); - tips.selectAll('rect').attr('width', tipWidth + 4).attr('x', flip ? -tipWidth - 12 : 8).style('text-anchor', flip ? 'end' : 'start'); + tips.selectAll('rect').attr('width', tipWidth + 4).attr('x', flip ? -tipWidth - 12 : 8).attr('y', 0).style('text-anchor', flip ? 'end' : 'start'); this.markersContainer.selectAll('circle').attr('cx', xPos).attr('cy', (d, i) => yScale(series ? y0[series[i]] : y0)); } @@ -136,9 +139,10 @@ export default class LineChart extends TranslatedComponent { * @param {SyntheticEvent} e Event */ _showTip(e) { - this._moveTip(e); + e.preventDefault(); this.tipContainer.style('display', null); this.markersContainer.style('display', null); + this._moveTip(e); } /** @@ -146,13 +150,16 @@ export default class LineChart extends TranslatedComponent { * @param {SyntheticEvent} e Event */ _moveTip(e) { - this._tooltip(Math.round(e.clientX - e.target.getBoundingClientRect().left)); + let clientX = e.touches ? e.touches[0].clientX : e.clientX; + this._tooltip(Math.round(clientX - e.currentTarget.getBoundingClientRect().left)); } /** * Hide tooltip + * @param {SyntheticEvent} e Event */ - _hideTip() { + _hideTip(e) { + e.preventDefault(); this.tipContainer.style('display', 'none'); this.markersContainer.style('display', 'none'); } @@ -238,8 +245,8 @@ export default class LineChart extends TranslatedComponent { {` (${yUnit})`} - this.tipContainer = d3.select(g)} className='tooltip' style={{ display: 'none' }}> - + this.tipContainer = d3.select(g)} style={{ display: 'none' }}> + {detailElems} this.markersContainer = d3.select(g)} style={{ display: 'none' }}> diff --git a/src/app/components/ModalImport.jsx b/src/app/components/ModalImport.jsx index 71bbf6b3..34f69b6a 100644 --- a/src/app/components/ModalImport.jsx +++ b/src/app/components/ModalImport.jsx @@ -4,7 +4,7 @@ import TranslatedComponent from './TranslatedComponent'; import Persist from '../stores/Persist'; import { Ships } from 'coriolis-data'; import Ship from '../shipyard/Ship'; -import { ModuleNameToGroup } from '../shipyard/Constants'; +import { ModuleNameToGroup, Insurance } from '../shipyard/Constants'; import * as ModuleUtils from '../shipyard/ModuleUtils'; import { fromDetailedBuild } from '../shipyard/Serializer'; import { Download } from './SvgIcons'; @@ -146,8 +146,14 @@ export default class ModalImport extends TranslatedComponent { if (importData.discounts instanceof Array && importData.discounts.length == 2) { this.setState({ discounts: importData.discounts }); } - if (typeof importData.insurance == 'string' && importData.insurance.length > 3) { - this.setState({ insurance: importData.insurance }); + if (typeof importData.insurance == 'string') { + let insurance = importData.insurance.toLowerCase(); + + if (Insurance[insurance] !== undefined) { + this.setState({ insurance }); + } else { + throw 'Invalid insurance type: ' + insurance; + } } } @@ -220,7 +226,7 @@ export default class ModalImport extends TranslatedComponent { if (!slot) { throw 'No hardpoint slot available for: "' + line + '"'; } - group = ModuleNameToGroup[name.trim()]; + group = ModuleNameToGroup[name.toLowerCase()]; let hp = ModuleUtils.findHardpoint(group, cl, rating, group ? null : name, mount, missile); @@ -238,7 +244,7 @@ export default class ModalImport extends TranslatedComponent { if (ship.standard[standardIndex].maxClass < cl) { throw name + ' exceeds max class for the ' + ship.name; } - ship.use(ship.standard[standardIndex], cl + rating, ModuleUtils.standard(standardIndex, cl + rating), true); + ship.use(ship.standard[standardIndex], ModuleUtils.standard(standardIndex, cl + rating), true); } else { throw 'Unknown component: "' + line + '"'; } @@ -249,13 +255,13 @@ export default class ModalImport extends TranslatedComponent { if (!slot) { throw 'No internal slot available for: "' + line + '"'; } - group = ModuleNameToGroup[name.trim()]; + group = ModuleNameToGroup[name.toLowerCase()]; let intComp = ModuleUtils.findInternal(group, cl, rating, group ? null : name); if (!intComp) { throw 'Unknown component: "' + line + '"'; } - ship.use(slot, intComp.id, intComp); + ship.use(slot, intComp); } } @@ -272,7 +278,7 @@ export default class ModalImport extends TranslatedComponent { */ _validateImport(event) { let importData = null; - let importString = event.target.value; + let importString = event.target.value.trim(); this.setState({ builds: null, comparisons: null, @@ -334,7 +340,7 @@ export default class ModalImport extends TranslatedComponent { } if (this.state.comparisons) { - let comparisons = this.state.comparisons; + comparisons = this.state.comparisons; for (let name in comparisons) { comparisons[name].useName = name; } @@ -371,8 +377,9 @@ export default class ModalImport extends TranslatedComponent { } } - if (this.state.discounts) { - Persist.setDiscount(this.state.discounts); + if (this.state.discounts && this.state.discounts.length == 2) { + Persist.setShipDiscount(this.state.discounts[0]); + Persist.setModuleDiscount(this.state.discounts[1]); } if (this.state.insurance) { @@ -384,11 +391,11 @@ export default class ModalImport extends TranslatedComponent { /** * Capture build name changes - * @param {Object} build Build import object + * @param {Object} item Build/Comparison import object * @param {SyntheticEvent} e Event */ - _changeBuildName(build, e) { - build.useName = e.target.value; + _changeName(item, e) { + item.useName = e.target.value; this.forceUpdate(); } @@ -415,7 +422,7 @@ export default class ModalImport extends TranslatedComponent { importStage = (