From 34f9f28c16ce46ee96689a8018dd51d4ff36ee41 Mon Sep 17 00:00:00 2001 From: Matthew Turney Date: Sun, 14 Jun 2020 10:33:00 -0500 Subject: [PATCH 1/3] Import tests fail due to Jest API change with mock functions --- __tests__/test-import.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/__tests__/test-import.js b/__tests__/test-import.js index 2bcd2c22..d0915f6c 100644 --- a/__tests__/test-import.js +++ b/__tests__/test-import.js @@ -18,13 +18,13 @@ describe('Import Modal', function() { 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() + openMenu: jest.fn(), + closeMenu: jest.fn(), + showModal: jest.fn(), + hideModal: jest.fn(), + tooltip: jest.fn(), + termtip: jest.fn(), + onWindowResize: jest.fn() }; let modal, render, ContextProvider = Utils.createContextProvider(mockContext); From 7de304bdbed32763be0c8b5934dd119dbc44489a Mon Sep 17 00:00:00 2001 From: Matthew Turney Date: Sun, 14 Jun 2020 12:27:45 -0500 Subject: [PATCH 2/3] Tests for ImportModal are failing All of the compressed data in the Persisted storage has subtly changed. It is not entirely clear why it has changed and the imports still function correctly. Another change is the 'Import Backup' tests. At some point there was a change to just remove invalid builds instead of throwing validation errors. The tests were never updated to fit this use-case. --- __tests__/fixtures/expected-builds.json | 48 ++++++++++++------------- __tests__/test-import.js | 28 ++++++++------- 2 files changed, 40 insertions(+), 36 deletions(-) diff --git a/__tests__/fixtures/expected-builds.json b/__tests__/fixtures/expected-builds.json index 2e5498a6..c9ed326a 100644 --- a/__tests__/fixtures/expected-builds.json +++ b/__tests__/fixtures/expected-builds.json @@ -1,50 +1,50 @@ { "type_6_transporter": { - "Cargo": "A0p0tdFal8d8s8f4-----04040303430101.Iw1/kA==.Aw1/kA==.", - "Miner": "A0p5tdFal8d8s8f42l2l---040403451q0101.Iw1/kA==.Aw1/kA==.", - "Hopper": "A0p0tdFal8d0s8f41717---030302024300-.Iw1/kA==.Aw1/kA==." + "Cargo": "A0p0tdFal8d8s8f4-----04040303430101-.Iw18UA==.Aw18UA==.", + "Miner": "A0p5tdFal8d8s8f42l2l---040403451q0101-.Iw18UA==.Aw18UA==.", + "Hopper": "A0p0tdFal8d0s8f41717---030302024300--.Iw18UA==.Aw18UA==." }, "type_7_transport": { - "Cargo": "A0p0tiFfliddsdf5--------0505040403480101.Iw18aQ==.Aw18aQ==.", - "Miner": "A0pdtiFflid8sdf5--2l2l----0505041v03450000.Iw18aQ==.Aw18aQ==." + "Cargo": "A0p0tiFfliddsdf5--------0505040403480101--.Iw18eQ==.Aw18eQ==.", + "Miner": "A0pdtiFflid8sdf5--2l2l----0505041v03450000--.Iw18eQ==.Aw18eQ==." }, "federal_dropship": { - "Cargo": "A0pdtiFflnddsif4-1717------05040448--020201.Iw18eQ==.Aw18eQ==." + "Cargo": "A0pdtiFflnddsif4-1717------05040448--020201-.Iw18RQ==.Aw18RQ==." }, "asp": { - "Miner": "A2pftfFflidfskf50s0s24242l2l---04054a1q02022o27.Iw18WQ==.Aw18WQ==." + "Miner": "A2pftfFflidfskf50s0s24242l2l---04054a1q02022o27-.Iw18eQ==.Aw18eQ==." }, "imperial_clipper": { - "Cargo": "A0p5tiFflndisnf4--0s0s----0605450302020101.Iw18aQ==.Aw18aQ==.", - "Dream": "A2pktkFflndpskf40v0v0s0s0404040n4k5n5d2b29292o-.AwRj4yWU1I==.CwBhCYy6YRigzLIA.", - "Current": "A0patkFflndfskf4----------------.AwRj4yWU1I==.CwBhCYy6YRigzLIA." + "Cargo": "A0p5tiFflndisnf4--0s0s----0605450302020101-.Iw18WQ==.Aw18WQ==.", + "Dream": "A2pktkFflndpskf40v0v0s0s0404040n4k5n5d2b29292o--.AwRj4yWU1Yg=.CwBhCYy6YRigzPIA.", + "Current": "A0patkFflndfskf4-----------------.AwRj4yWU1Yg=.CwBhCYy6YRigzPIA." }, "type_9_heavy": { - "Current": "A0patsFklndnsif6---------0706054a0303020224.AwRj4yoo.EwBhEYy6dsg=." + "Current": "A0patsFklndnsif6---------0706054a0303020224--.AwRj4yo5iA==.EwBhEYy6d6g=." }, "python": { - "Cargo": "A0patnFflidsssf5---------050505040448020201.Iw18eQ==.Aw18eQ==.", - "Miner": "A0pktkFflidpspf50v0v0v2m2m0404--050505Ce4a1v02022o.Iw18eQ==.IwBhBYy6dkCYg===.", - "Dream": "A2pptkFfliduspf50v0v0v27270404040m5n5n4f2d2d032t0201.Iw1+gDBxA===.EwBhEYy6e0WEA===.", - "Missile": "A0pttoFjljdystf52f2g2d2ePh----04044j03---002h.Iw18eQ==.Aw18eQ==." + "Cargo": "A0patnFflidsssf5---------050505040448020201-.Iw18eAMQ.Aw18RQ==.", + "Miner": "A0pktkFflidpspf50v0v0v2m2m0404--050505Ce4a1v02022o-.Iw18eAMQ.IwBhBYy6dkCYRA==.", + "Dream": "A2pptkFfliduspf50v0v0v27270404040m5n5n4f2d2d032t0201-.Iw1+gDByUA==.EwBhEYy6e0VEA===.", + "Missile": "A0pttoFjljdystf52f2g2d2ePh----04044j03---00--.Iw18eAMQ.Aw18RQ==." }, "anaconda": { - "Dream": "A4putpFklndzsuf52c0o0o0o1m1m0q0q0404040l0b0100004k5n5n112d2d04-0303326b.AwRj4yo5dyg=.MwBhCYy6duvARiA=.", - "Cargo": "A0patnFklndnsxf5----------------06050505040404-45030301.Iw18ZVA=.Aw18ZVA=.", - "Current": "A0patnFklndksxf5----------------06050505040404-03034524.Iw18ZVA=.Aw18ZVA=.", - "Explorer": "A0patnFklndksxf5--------0202------f7050505040s37-2f2i4524.AwRj4yVKJ9hA.AwhMIyumQRhEA===.", - "Test": "A4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04---0303326b.Iw18ZVA=.Aw18ZVA=." + "Dream": "A4putpFklndzsuf52c0o0o0o1m1m0q0q0404040l0b0100004k5n5n112d2d04-0303326b-.AwRj4yo5dzhA.MwBhCYy6duvARhEA.", + "Cargo": "A0patnFklndnsxf5----------------06050505040404-45030301-.Iw18ZUAxA===.Aw18ZXEA.", + "Current": "A0patnFklndksxf5----------------06050505040404-03034524-.Iw18ZUAxA===.Aw18ZXEA.", + "Explorer": "A0patnFklndksxf5--------0202------f7050505040s37--2i4524-.AwRj4yVKJ9jCA===.AwhMIyumQRgkA===.", + "Test": "A4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04---0303326b-.Iw18ZUAxA===.Aw18ZXEA." }, "diamondback_explorer": { - "Explorer": "A0p0tdFfldddsdf5---0202--320p432i2f-.AwRj4zTYg===.AwiMIyoo." + "Explorer": "A0p0tdFfldddsdf5---0202--320p432i----.AwRj4zTZaA==.AwiMIyqo." }, "vulture": { - "Bounty Hunter": "A3patcFalddksff31e1e0404-0l4a-5d27662j.AwRj4z2I.MwBhBYy6oJmAjLIA." + "Bounty Hunter": "A3patcFalddksff31e1e0404-0l4a-5d27662j--.AwRj4z2Gg===.MwBhBYy6oJmAjLMQ." }, "fer_de_lance": { - "Attack": "A2pfthFalidpsff31r0s0s0s0s000404-04-4a-5d27-.Iw18aQ==.CwBhrSu8EZyA." + "Attack": "A2pfthFalidpsff31r0s0s0s0s000404-04-4a-5d27--.Iw18aAMQ.CwBhrSu8EZxEA===." }, "eagle": { - "Figther": "A4p0t5F5l3d5s5f20p0p24-4053-2j-.Iw18kA==.Aw18kA==." + "Figther": "A4p0t5F5l3d5s5f20p0p24-4053-2j---.Iw18gDJQ.Aw19kA==." } } diff --git a/__tests__/test-import.js b/__tests__/test-import.js index d0915f6c..89e77f6b 100644 --- a/__tests__/test-import.js +++ b/__tests__/test-import.js @@ -110,21 +110,25 @@ describe('Import Modal', function() { it('catches an invalid backup', function() { const importData = require('./fixtures/valid-backup'); let invalidImportData = Object.assign({}, importData); - //invalidImportData.builds.asp = null; // Remove Asp Miner build used in comparison + // Remove Asp Miner build used in comparison delete(invalidImportData.builds.asp); 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')); + + pasteText(JSON.stringify(importData).replace(/anaconda/g, 'invalid_ship')); expect(modal.state.importValid).toBeFalsy(); - expect(modal.state.errorMsg).toEqual('"invalid_ship" is not a valid Ship Id!'); + expect(Object.keys(modal.state.builds)).not.toContain('anaconda'); + 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!'); + expect(Object.keys(modal.state.builds.imperial_clipper).length).toEqual(3); + pasteText(JSON.stringify(invalidImportData)); expect(modal.state.importValid).toBeFalsy(); expect(modal.state.errorMsg).toEqual('asp build "Miner" data is missing!'); @@ -144,7 +148,7 @@ describe('Import Modal', function() { expect(modal.state.singleBuild).toBe(true); clickProceed(); expect(MockRouter.go.mock.calls.length).toBe(1); - expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/anaconda?code=A4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04---0303326b.AwRj4zNLaA%3D%3D.CwBhCYzBGW9qCTSqq5xA.&bn=Test%20My%20Ship'); + expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/anaconda?code=A4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04---0303326b-.AwRj4zNLeI%3D%3D.CwBhCYzBGW9qCTSqq5JA.&bn=Test%20My%20Ship'); }); it('catches an invalid build', function() { @@ -169,7 +173,7 @@ describe('Import Modal', function() { expect(modal.state.singleBuild).toBe(true); clickProceed(); expect(MockRouter.go.mock.calls.length).toBe(1); - expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/anaconda?code=A4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04---0303326b.AwRj4zNLaA%3D%3D.CwBhCYzBGW9qCTSqq5xA.H4sIAAAAAAAAA2MUe8HMwPD%2FPwMAAGvB0AkAAAA%3D&bn=Test%20My%20Ship'); + expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/anaconda?code=A4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04---0303326b-.AwRj4zNLeI%3D%3D.CwBhCYzBGW9qCTSqq5JA.H4sIAAAAAAAAE2MUe8HMwPD%2FPwMAAGvB0AkAAAA%3D&bn=Test%20My%20Ship'); }); }); @@ -186,7 +190,7 @@ describe('Import Modal', function() { expect(modal.state.singleBuild).toBe(true); clickProceed(); expect(MockRouter.go.mock.calls.length).toBe(1); - expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/asp?code=A0pftiFflfddsnf5------020202033c044002v62f2i.AwRj4yvI.CwRgDBldHnJA.H4sIAAAAAAAAA2P858DAwPCXEUhwHPvx%2F78YG5AltB7I%2F8%2F0TwImJboDSPJ%2F%2B%2Ff%2Fv%2FKlX%2F%2F%2Fi3AwMTBIfARK%2FGf%2BJwVSxArStVAYqOjvz%2F%2F%2FJVo5GRhE2IBc4SKQSSz%2FDGEmCa398P8%2F%2F2%2BgTf%2F%2FA7kMAExxqlSAAAAA&bn=Multi-purpose%20Asp%20Explorer'); + expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/asp?code=A0pftiFflfddsnf5------020202033c044002v6-2i-.AwRj4yvYg%3D%3D%3D.CwRgDBldHn5A.H4sIAAAAAAAAE2P858DAwPCXEUhwHPvx%2F78YG5AltB7I%2F8%2F0TwImJboDSPJ%2F%2B%2Ff%2Fv%2FKlX%2F%2F%2Fi3AwMTBIfARK%2FGf%2BJwVSxArStVAYqOjvz%2F%2F%2FJVo5GRhE2IBc4SKQSSz%2FDGEmCa398P8%2F%2F2%2BgTf%2F%2FA7kMAExxqlSAAAAA&bn=Multi-purpose%20Asp%20Explorer'); }); it('imports a valid v4 build with modifications', function() { @@ -198,7 +202,7 @@ describe('Import Modal', function() { expect(modal.state.singleBuild).toBe(true); clickProceed(); expect(MockRouter.go.mock.calls.length).toBe(1); - expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/imperial_courier?code=A0patzF5l0das8f31a1a270202000e402t0101-2f.AwRj4zKA.CwRgDBldLiQ%3D.H4sIAAAAAAAAA12OPUvDYBSFT1OTfkRJjUkbbC3Yj8mlODgUISAtdOlety5ODv0Vgji7O7kJ%2FgzBQX%2BEY7Gg0NKhfY%2FnHQLFDBdynufe9%2BRMCmCb06g29oCgacjiRx6gY6oWKUT8UgLaszqQfHmSnpVFN1uSeXNsJVcj%2FA2EHlZkspIUpUc6UjTXGT85qwHuSEuVc%2F16r99kDQeSSjvSbSjpyUpNK10uJJ3aYqk6smwm1lQ9bOxw71TMm8VanEqq9JW1r3Qo%2BREOLnQHvbWmb7rZIu5VLIyGQGOukPv%2F0WQk5LeEAjPOUDwtAP6bShy2HKAz0HPO%2B5KsP25I79O2I7LvD%2Bz4Il1XAQAA&bn=Multi-purpose%20Imperial%20Courier'); + expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/imperial_courier?code=A0patzF5l0das8f31a1a270202000e402t0101----.AwRj4zOYg%3D%3D%3D.CwRgDBldLuZA.H4sIAAAAAAAAE12OPUvDYBSFT1OTfkRJjUkbbC3Yj8mlODgUISAtdOlety5ODv0Vgji7O7kJ%2FgzBQX%2BEY7Gg0NKhfY%2FnHQLFDBdynufe9%2BRMCmCb06g29oCgacjiRx6gY6oWKUT8UgLaszqQfHmSnpVFN1uSeXNsJVcj%2FA2EHlZkspIUpUc6UjTXGT85qwHuSEuVc%2F16r99kDQeSSjvSbSjpyUpNK10uJJ3aYqk6smwm1lQ9bOxw71TMm8VanEqq9JW1r3Qo%2BREOLnQHvbWmb7rZIu5VLIyGQGOukPv%2F0WQk5LeEAjPOUDwtAP6bShy2HKAz0HPO%2B5KsP25I79O2I7LvD%2Bz4Il1XAQAA&bn=Multi-purpose%20Imperial%20Courier'); }); }); @@ -240,7 +244,7 @@ describe('Import Modal', function() { expect(modal.state.singleBuild).toBe(true); clickProceed(); expect(MockRouter.go.mock.calls.length).toBe(1); - expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/federal_corvette?code=A2putsFklndzsxf50x0x7l28281919040404040402020l06p05sf63c5ifr--v66g2f.AwRj4zNaqA%3D%3D.CwRgDBldUExuBiIlUA%3D%3D.H4sIAAAAAAAAA12STy8DURTFb1szU53Ga8dg2qqqDmJDIoKFxJImumYjVrVqfAALC4lNbcUnkLCoDbEQu0bSlQVhI8JHsJBIQ73rXMkwMYuT9%2Bb87nl%2F7ovoRSL6ikD6TYNINZg5XsWUo7pfrBikr2USlRyXyDuLAhr6ZHanNLOzD5tjOiskysk5dOBvfTB7bjeRW0MNG3ohSBq1bKKxKwyLLUAjmwjpPu4wJx4xVbNI57heDfbUKUAy2xaRUQZpllHoHMHxKqjhhF4LgjtJiFHDmqbrEeVnUJOax7%2FSdRfRwBNotv9wo5kAuZMD2egKyDYcdYl1OBki6z%2BZQjaFnBPyFCM1LefF%2BcgrY0es9FKwbW8ZYj9gmBbxRVRdglMh6BNqnwsk4ouoO4HSIehNoBuBRHwR1QOmsBvHmk6IfMbd2fdCEka%2BjNSexPWGoEkcyX6CnxbxRZQtd%2BPpym%2B31xFtn0iSFPkf%2BBkttZlzB9KDFyBuFRfAGV0Ogoff8SSsCfjjD5hGWtLIwZB%2FgX5Zt%2BLHMI9My7sp6nzgZzekswTxVvCOkq%2FSXqb%2F3zfLxh6HrwIAAA%3D%3D&bn=Imported%20Federal%20Corvette'); + expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/federal_corvette?code=A2putsFklndzsxf50x0x7l28281919040404040402020l06p05sf63c5ifr--v66g--.AwRj4zNapI%3D%3D.CwRgDBldUExuBiIlWIA%3D.&bn=Imported%20Federal%20Corvette'); }); it('imports a valid companion API build', function() { @@ -252,7 +256,7 @@ describe('Import Modal', function() { expect(modal.state.singleBuild).toBe(true); clickProceed(); expect(MockRouter.go.mock.calls.length).toBe(1); - expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/beluga?code=A0pktsFplCdpsnf70t0t2727270004040404043c4fmimlmm04mc0iv62i2f.AwRj4yukg%3D%3D%3D.CwRgDBldHi8IUA%3D%3D.H4sIAAAAAAAAA2P8Z8%2FAwPCXEUiIKTMxMPCv%2F%2Ff%2FP8cFIPGf6Z8YTEr0GjMDg%2FJWICERBOTzn%2Fn7%2F7%2FIO5Ai5n9SIEWsQEIoSxAolfbt%2F3%2BJPk4GBhE7YQYGYVmgcuVnf4Aq%2FwOVAAAyiFctbgAAAA%3D%3D&bn=Imported%20Beluga%20Liner'); + expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/beluga?code=A0pktsFplCdpsnf70t0t2727270004040404043c4fmimlmm04mc0iv62i--.AwRj4yusg%3D%3D%3D.CwRgDBldHi8IWIA%3D.&bn=Imported%20Beluga%20Liner'); }); it('imports a valid companion API build', function() { @@ -264,7 +268,7 @@ describe('Import Modal', function() { expect(modal.state.singleBuild).toBe(true); clickProceed(); expect(MockRouter.go.mock.calls.length).toBe(1); - expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/type_7_transport?code=A0patfFflidasdf5----0404040005050504044d2402.AwRj4yrI.CwRgDBlVK7EiA%3D%3D%3D.&bn=Imported%20Type-7%20Transporter'); + expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/type_7_transport?code=A0patfFflidasdf5----0404040005050504044d2402--.AwRj4yoo.CwRgDBlVK7HjEA%3D%3D.&bn=Imported%20Type-7%20Transporter'); }); it('imports a valid companion API build', function() { @@ -276,7 +280,7 @@ describe('Import Modal', function() { expect(modal.state.singleBuild).toBe(true); clickProceed(); expect(MockRouter.go.mock.calls.length).toBe(1); - expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/cobra_mk_iii?code=A0p0tdFaldd3sdf4------34---2f2i.AwRj4yKA.CwRgDMYExrezBUg%3D.&bn=Imported%20Cobra%20Mk%20III'); + expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/cobra_mk_iii?code=A0p0tdFaldd3sdf4------34----2i--.AwRj4yqA.CwRgDMYExrezBig%3D.&bn=Imported%20Cobra%20Mk%20III'); }); }); From 4c70806a5a02df536ab432bbc6708ed7c5954b94 Mon Sep 17 00:00:00 2001 From: Matthew Turney Date: Sun, 14 Jun 2020 09:55:34 -0500 Subject: [PATCH 3/3] Support SLEF import format for importing builds. Inara uses the [SLEF] format to export builds. This format is mostly just a wrapper around the standard journal loadout format and includes support for source app metadata and exporting of multiple loadouts at one time. This change adds support for this format in the manual importer. Eventually it would be good to support this in the import route as well so Inara (or any other apps) can link directly to coriolis. [SLEF]: https://inara.cz/inara-impexp-slef/ --- __tests__/fixtures/slef-multiple-builds.json | 366 ++++++++++++++++++ .../slef-multiple-expected-builds.json | 8 + __tests__/fixtures/slef-single-build.json | 188 +++++++++ __tests__/test-import.js | 39 +- src/app/components/ModalImport.jsx | 39 +- 5 files changed, 633 insertions(+), 7 deletions(-) create mode 100644 __tests__/fixtures/slef-multiple-builds.json create mode 100644 __tests__/fixtures/slef-multiple-expected-builds.json create mode 100644 __tests__/fixtures/slef-single-build.json 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) {