diff --git a/__tests__/fixtures/slef-multiple-builds.json b/__tests__/fixtures/slef-multiple-builds.json new file mode 100644 index 00000000..d6b95126 --- /dev/null +++ b/__tests__/fixtures/slef-multiple-builds.json @@ -0,0 +1,366 @@ +[ + { + "header": { + "appName": "Inara", + "appVersion": "1.0", + "appURL": "https:\/\/inara.cz\/cmdr-fleet\/123\/123\/", + "appCustomProperties": { + "inaraCommanderID": 123, + "inaraShipID": 123 + } + }, + "data": { + "Ship": "krait_mkii", + "ShipID": 7, + "ShipName": "pancake hammer", + "ShipIdent": "PH-01", + "HullValue": 44160710, + "ModulesValue": 111274094, + "Rebuy": 7771743, + "Modules": [ + { + "Slot": "largehardpoint1", + "Item": "hpt_mininglaser_fixed_small", + "On": true + }, + { + "Slot": "largehardpoint2", + "Item": "hpt_cannon_gimbal_large", + "On": true, + "Engineering": { + "BlueprintName": "weapon_overcharged", + "Level": 2, + "Quality": 1, + "ExperimentalEffect": "special_auto_loader" + } + }, + { + "Slot": "largehardpoint3", + "Item": "hpt_cannon_gimbal_large", + "On": true, + "Engineering": { + "BlueprintName": "weapon_overcharged", + "Level": 2, + "Quality": 1, + "ExperimentalEffect": "special_auto_loader" + } + }, + { + "Slot": "mediumhardpoint1", + "Item": "hpt_basicmissilerack_fixed_medium", + "On": true, + "Engineering": { + "BlueprintName": "weapon_highcapacity", + "Level": 5, + "Quality": 1 + } + }, + { + "Slot": "mediumhardpoint2", + "Item": "hpt_basicmissilerack_fixed_medium", + "On": true + }, + { + "Slot": "tinyhardpoint1", + "Item": "hpt_heatsinklauncher_turret_tiny", + "On": true + }, + { + "Slot": "tinyhardpoint2", + "Item": "hpt_cloudscanner_size0_class3", + "On": true + }, + { + "Slot": "tinyhardpoint3", + "Item": "hpt_shieldbooster_size0_class5", + "On": true + }, + { + "Slot": "tinyhardpoint4", + "Item": "hpt_shieldbooster_size0_class5", + "On": true, + "Priority": 1 + }, + { + "Slot": "slot01_size6", + "Item": "int_cargorack_size6_class1", + "On": true, + "Priority": 1 + }, + { + "Slot": "slot02_size6", + "Item": "int_cargorack_size6_class1", + "On": true, + "Priority": 1 + }, + { + "Slot": "slot03_size5", + "Item": "int_guardianfsdbooster_size5", + "On": true + }, + { + "Slot": "slot04_size5", + "Item": "int_fighterbay_size5_class1", + "On": true + }, + { + "Slot": "slot05_size4", + "Item": "int_shieldgenerator_size4_class5", + "On": true + }, + { + "Slot": "slot06_size3", + "Item": "int_dronecontrol_collection_size3_class4", + "On": true + }, + { + "Slot": "slot07_size3", + "Item": "int_dronecontrol_collection_size3_class4", + "On": true + }, + { + "Slot": "slot08_size2", + "Item": "int_refinery_size2_class2", + "On": true + }, + { + "Slot": "slot09_size1", + "Item": "int_dronecontrol_prospector_size1_class4", + "On": true + }, + { + "Slot": "powerplant", + "Item": "int_powerplant_size7_class5", + "On": true, + "Priority": 1 + }, + { + "Slot": "mainengines", + "Item": "int_engine_size6_class5", + "On": true + }, + { + "Slot": "frameshiftdrive", + "Item": "int_hyperdrive_size5_class5", + "On": true, + "Engineering": { + "BlueprintName": "fsd_longrange", + "Level": 2, + "Quality": 0.861 + } + }, + { + "Slot": "lifesupport", + "Item": "int_lifesupport_size4_class2", + "On": true, + "Priority": 3 + }, + { + "Slot": "powerdistributor", + "Item": "int_powerdistributor_size7_class5", + "On": true + }, + { + "Slot": "radar", + "Item": "int_sensors_size6_class2", + "On": true + }, + { + "Slot": "fueltank", + "Item": "int_fueltank_size5_class3", + "On": true, + "Priority": 1 + }, + { + "Slot": "armour", + "Item": "krait_mkii_armour_grade3", + "On": true, + "Priority": 1, + "Engineering": { + "BlueprintName": "armour_heavyduty", + "Level": 5, + "Quality": 1 + } + } + ] + } + }, + { + "header": { + "appName": "Inara", + "appVersion": "1.0", + "appURL": "https:\/\/inara.cz\/cmdr-fleet\/123\/123\/", + "appCustomProperties": { + "inaraCommanderID": 123, + "inaraShipID": 123 + } + }, + "data": { + "Ship": "diamondbackxl", + "ShipID": 11, + "ShipName": "star Hopper", + "ShipIdent": "PH-02", + "HullValue": 1615649, + "ModulesValue": 16981039, + "Rebuy": 929837, + "Modules": [ + { + "Slot": "tinyhardpoint1", + "Item": "hpt_heatsinklauncher_turret_tiny", + "On": true, + "Value": 3072 + }, + { + "Slot": "slot01_size4", + "Item": "int_fuelscoop_size4_class5", + "On": true, + "Priority": 3, + "Value": 2862364 + }, + { + "Slot": "slot02_size4", + "Item": "int_guardianfsdbooster_size4", + "On": true, + "Value": 2847499 + }, + { + "Slot": "slot03_size3", + "Item": "int_shieldgenerator_size3_class2", + "On": true, + "Value": 18812, + "Engineering": { + "BlueprintName": "shieldgenerator_thermic", + "Level": 3, + "Quality": 1, + "ExperimentalEffect": "special_shield_health" + } + }, + { + "Slot": "slot04_size3", + "Item": "int_repairer_size3_class5", + "On": true, + "Value": 2302911 + }, + { + "Slot": "slot05_size2", + "Item": "int_buggybay_size2_class2", + "On": true, + "Priority": 3, + "Value": 21600 + }, + { + "Slot": "slot06_size2", + "Item": "int_cargorack_size2_class1", + "On": true, + "Priority": 1, + "Value": 2852 + }, + { + "Slot": "slot07_size1", + "Item": "int_supercruiseassist", + "On": true, + "Priority": 3, + "Value": 9121 + }, + { + "Slot": "slot08_size1", + "Item": "int_detailedsurfacescanner_tiny", + "On": true, + "Value": 250000, + "Engineering": { + "BlueprintName": "sensor_expanded", + "Level": 5, + "Quality": 1 + } + }, + { + "Slot": "powerplant", + "Item": "int_powerplant_size4_class5", + "On": true, + "Priority": 1, + "Value": 1441233, + "Engineering": { + "BlueprintName": "powerplant_boosted", + "Level": 1, + "Quality": 1 + } + }, + { + "Slot": "mainengines", + "Item": "int_engine_size4_class5", + "On": true, + "Value": 1610080, + "Engineering": { + "BlueprintName": "engine_dirty", + "Level": 5, + "Quality": 1, + "ExperimentalEffect": "special_engine_lightweight" + } + }, + { + "Slot": "frameshiftdrive", + "Item": "int_hyperdrive_size5_class5", + "On": true, + "Value": 5103953, + "Engineering": { + "BlueprintName": "fsd_longrange", + "Level": 5, + "Quality": 1, + "ExperimentalEffect": "special_fsd_lightweight" + } + }, + { + "Slot": "lifesupport", + "Item": "int_lifesupport_size3_class2", + "On": true, + "Value": 10133, + "Engineering": { + "BlueprintName": "misc_lightweight", + "Level": 3, + "Quality": 1 + } + }, + { + "Slot": "powerdistributor", + "Item": "int_powerdistributor_size4_class5", + "On": true, + "Value": 389022, + "Engineering": { + "BlueprintName": "powerdistributor_highfrequency", + "Level": 4, + "Quality": 1 + } + }, + { + "Slot": "radar", + "Item": "int_sensors_size3_class2", + "On": true, + "Value": 10133, + "Engineering": { + "BlueprintName": "sensor_lightweight", + "Level": 5, + "Quality": 1 + } + }, + { + "Slot": "fueltank", + "Item": "int_fueltank_size5_class3", + "On": true, + "Priority": 1, + "Value": 97754 + }, + { + "Slot": "armour", + "Item": "diamondbackxl_armour_grade1", + "On": true, + "Priority": 1, + "Engineering": { + "BlueprintName": "armour_heavyduty", + "Level": 5, + "Quality": 1 + } + } + ] + } + } +] diff --git a/__tests__/fixtures/slef-multiple-expected-builds.json b/__tests__/fixtures/slef-multiple-expected-builds.json new file mode 100644 index 00000000..19c2bd7f --- /dev/null +++ b/__tests__/fixtures/slef-multiple-expected-builds.json @@ -0,0 +1,8 @@ +{ + "krait_mkii": { + "Imported pancake hammer": "A2pptkFflidussf52l1o1o2g2g020g040405051Ofr45C9C91oP3.Iw18eQ==.AwRgzKIkA===." + }, + "diamondback_explorer": { + "Imported star Hopper": "A0pataFflddfsdf5---02---321P430iv6013w2i.Iw18SQ==.AwRm44GYpKg=." + } +} diff --git a/__tests__/fixtures/slef-single-build.json b/__tests__/fixtures/slef-single-build.json new file mode 100644 index 00000000..1b578b83 --- /dev/null +++ b/__tests__/fixtures/slef-single-build.json @@ -0,0 +1,188 @@ +[ + { + "header": { + "appName": "Inara", + "appVersion": "1.0", + "appURL": "https:\/\/inara.cz\/cmdr-fleet\/123\/123\/", + "appCustomProperties": { + "inaraCommanderID": 123, + "inaraShipID": 123 + } + }, + "data": { + "Ship": "krait_mkii", + "ShipID": 7, + "ShipName": "pancake hammer", + "ShipIdent": "PH-01", + "HullValue": 44160710, + "ModulesValue": 111274094, + "Rebuy": 7771743, + "Modules": [ + { + "Slot": "largehardpoint1", + "Item": "hpt_mininglaser_fixed_small", + "On": true + }, + { + "Slot": "largehardpoint2", + "Item": "hpt_cannon_gimbal_large", + "On": true, + "Engineering": { + "BlueprintName": "weapon_overcharged", + "Level": 2, + "Quality": 1, + "ExperimentalEffect": "special_auto_loader" + } + }, + { + "Slot": "largehardpoint3", + "Item": "hpt_cannon_gimbal_large", + "On": true, + "Engineering": { + "BlueprintName": "weapon_overcharged", + "Level": 2, + "Quality": 1, + "ExperimentalEffect": "special_auto_loader" + } + }, + { + "Slot": "mediumhardpoint1", + "Item": "hpt_basicmissilerack_fixed_medium", + "On": true, + "Engineering": { + "BlueprintName": "weapon_highcapacity", + "Level": 5, + "Quality": 1 + } + }, + { + "Slot": "mediumhardpoint2", + "Item": "hpt_basicmissilerack_fixed_medium", + "On": true + }, + { + "Slot": "tinyhardpoint1", + "Item": "hpt_heatsinklauncher_turret_tiny", + "On": true + }, + { + "Slot": "tinyhardpoint2", + "Item": "hpt_cloudscanner_size0_class3", + "On": true + }, + { + "Slot": "tinyhardpoint3", + "Item": "hpt_shieldbooster_size0_class5", + "On": true + }, + { + "Slot": "tinyhardpoint4", + "Item": "hpt_shieldbooster_size0_class5", + "On": true, + "Priority": 1 + }, + { + "Slot": "slot01_size6", + "Item": "int_cargorack_size6_class1", + "On": true, + "Priority": 1 + }, + { + "Slot": "slot02_size6", + "Item": "int_cargorack_size6_class1", + "On": true, + "Priority": 1 + }, + { + "Slot": "slot03_size5", + "Item": "int_guardianfsdbooster_size5", + "On": true + }, + { + "Slot": "slot04_size5", + "Item": "int_fighterbay_size5_class1", + "On": true + }, + { + "Slot": "slot05_size4", + "Item": "int_shieldgenerator_size4_class5", + "On": true + }, + { + "Slot": "slot06_size3", + "Item": "int_dronecontrol_collection_size3_class4", + "On": true + }, + { + "Slot": "slot07_size3", + "Item": "int_dronecontrol_collection_size3_class4", + "On": true + }, + { + "Slot": "slot08_size2", + "Item": "int_refinery_size2_class2", + "On": true + }, + { + "Slot": "slot09_size1", + "Item": "int_dronecontrol_prospector_size1_class4", + "On": true + }, + { + "Slot": "powerplant", + "Item": "int_powerplant_size7_class5", + "On": true, + "Priority": 1 + }, + { + "Slot": "mainengines", + "Item": "int_engine_size6_class5", + "On": true + }, + { + "Slot": "frameshiftdrive", + "Item": "int_hyperdrive_size5_class5", + "On": true, + "Engineering": { + "BlueprintName": "fsd_longrange", + "Level": 2, + "Quality": 0.861 + } + }, + { + "Slot": "lifesupport", + "Item": "int_lifesupport_size4_class2", + "On": true, + "Priority": 3 + }, + { + "Slot": "powerdistributor", + "Item": "int_powerdistributor_size7_class5", + "On": true + }, + { + "Slot": "radar", + "Item": "int_sensors_size6_class2", + "On": true + }, + { + "Slot": "fueltank", + "Item": "int_fueltank_size5_class3", + "On": true, + "Priority": 1 + }, + { + "Slot": "armour", + "Item": "krait_mkii_armour_grade3", + "On": true, + "Priority": 1, + "Engineering": { + "BlueprintName": "armour_heavyduty", + "Level": 5, + "Quality": 1 + } + } + ] + } + } +] diff --git a/__tests__/test-import.js b/__tests__/test-import.js index 89e77f6b..c7c484cc 100644 --- a/__tests__/test-import.js +++ b/__tests__/test-import.js @@ -206,7 +206,7 @@ describe('Import Modal', function() { }); }); - describe('Import Detaild Builds Array', function() { + describe('Import Detailed Builds Array', function() { beforeEach(reset); @@ -328,4 +328,41 @@ describe('Import Modal', function() { }); }); + describe('Imports SLEF data', () => { + beforeEach(reset); + + it('imports a single valid SLEF build', () => { + const importData = require('./fixtures/slef-single-build.json'); + pasteText(JSON.stringify(importData)); + + expect(modal.state.importValid).toBeTruthy(); + expect(modal.state.errorMsg).toEqual(null); + expect(modal.state.singleBuild).toBe(true); + clickProceed(); + expect(MockRouter.go.mock.calls.length).toBe(1); + expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/krait_mkii?code=A2pptkFflidussf52l1o1o2g2g020g040405051Ofr45C9C91oP3.Iw18eQ%3D%3D.AwRgzKIkA%3D%3D%3D.&bn=Imported%20pancake%20hammer'); + }); + + it('imports multiple SLEF builds', () => { + const importData = require('./fixtures/slef-multiple-builds.json'); + const expectedBuilds = require('./fixtures/slef-multiple-expected-builds.json'); + pasteText(JSON.stringify(importData)); + + expect(modal.state.importValid).toBeTruthy(); + expect(modal.state.errorMsg).toEqual(null); + expect(modal.state.singleBuild).toBe(false); + clickProceed(); + expect(modal.state.processed).toBeTruthy(); + clickImport(); + + const builds = Persist.getBuilds(); + + for (const shipModel in builds) { + for (const buildName in builds[shipModel]) { + expect(builds[shipModel][buildName]) + .toEqual(expectedBuilds[shipModel][buildName]); + } + } + }); + }); }); diff --git a/src/app/components/ModalImport.jsx b/src/app/components/ModalImport.jsx index cf48e138..4dc34b7c 100644 --- a/src/app/components/ModalImport.jsx +++ b/src/app/components/ModalImport.jsx @@ -11,7 +11,8 @@ import * as ModuleUtils from '../shipyard/ModuleUtils'; import { fromDetailedBuild } from '../shipyard/Serializer'; import { Download } from './SvgIcons'; import { outfitURL } from '../utils/UrlGenerators'; -import * as CompanionApiUtils from '../utils/CompanionApiUtils'; +import { shipFromJson, shipModelFromJson } from '../utils/CompanionApiUtils'; +import { shipFromLoadoutJSON } from '../utils/JournalUtils'; const zlib = require('pako'); @@ -214,8 +215,8 @@ export default class ModalImport extends TranslatedComponent { * @throws {string} if parse/import fails */ _importCompanionApiBuild(build) { - const shipModel = CompanionApiUtils.shipModelFromJson(build); - const ship = CompanionApiUtils.shipFromJson(build); + const shipModel = shipModelFromJson(build); + const ship = shipFromJson(build); let builds = {}; builds[shipModel] = {}; @@ -321,6 +322,30 @@ export default class ModalImport extends TranslatedComponent { this.setState({ builds, singleBuild: true }); } + /** + * Import SLEF formatted builds. Sets state to a map of the builds on success + * and flags if there was only a single build. + * + * @param {string} importData - Array of the list of builds. + * @throws {string} If parse / import fails + */ + _importSlefBuilds(importData) { + const builds = importData.reduce((memo, { data }) => { + const shipModel = shipModelFromJson(data); + const ship = shipFromLoadoutJSON(data); + const shipTemplate = Ships[shipModel]; + const shipName = data.ShipName || shipTemplate.properties.name; + + const key = `Imported ${shipName}`; + memo[shipModel] = {}; + memo[shipModel][key] = ship.toString(); + + return memo; + }, {}); + + this.setState({ builds, singleBuild: Object.keys(builds).length === 1 }); + } + /** * Validate the import string / text box contents * @param {SyntheticEvent} event Event @@ -355,8 +380,10 @@ export default class ModalImport extends TranslatedComponent { throw 'Must be an object or array!'; } - if (importData.modules != null && importData.modules.Armour != null) { // Only the companion API has this information - this._importCompanionApiBuild(importData); // Single sihp definition + if (importData?.[0]?.header?.appName) { // has SLEF envelope? + this._importSlefBuilds(importData); + } else if (importData.modules != null && importData.modules.Armour != null) { // Only the companion API has this information + this._importCompanionApiBuild(importData); // Single ship definition } else if (importData.ship != null && importData.ship.modules != null && importData.ship.modules.Armour != null) { // Only the companion API has this information this._importCompanionApiBuild(importData.ship); // Complete API dump } else if (importData instanceof Array) { // Must be detailed export json @@ -542,7 +569,7 @@ export default class ModalImport extends TranslatedComponent { {comparisonRows} - ); + ); } if(this.state.canEdit) {