diff --git a/package.json b/package.json index 8181749..6f197dc0 100644 --- a/package.json +++ b/package.json @@ -52,8 +52,8 @@ "lint": "eslint .", "lint:fix": "npm run lint -- --fix", "prepare": "npm run build", - "test": "mocha --exit --timeout 1m \"./test/unit/**/*-specs.*js\"", - "e2e-test": "mocha --exit --timeout 5m \"./test/e2e/**/*-specs.js\"" + "test": "mocha --exit --timeout 1m \"./test/unit/**/*-specs.ts\"", + "e2e-test": "mocha --exit --timeout 5m \"./test/e2e/**/*-specs.ts\"" }, "prettier": { "bracketSpacing": false, @@ -77,7 +77,6 @@ "conventional-changelog-conventionalcommits": "^9.0.0", "mocha": "^11.0.1", "prettier": "^3.0.0", - "proxyquire": "^2.1.3", "semantic-release": "^25.0.2", "sinon": "^21.0.0", "ts-node": "^10.9.1", diff --git a/test/e2e/simctl-e2e-specs.js b/test/e2e/simctl-e2e-specs.ts similarity index 75% rename from test/e2e/simctl-e2e-specs.js rename to test/e2e/simctl-e2e-specs.ts index 04ede28..a926d34 100644 --- a/test/e2e/simctl-e2e-specs.js +++ b/test/e2e/simctl-e2e-specs.ts @@ -7,35 +7,29 @@ import { uuidV4 } from '../../lib/helpers'; import path from 'path'; import os from 'os'; import fs from 'fs/promises'; +import { expect, use } from 'chai'; +import chaiAsPromised from 'chai-as-promised'; + +use(chaiAsPromised); describe('simctl', function () { - const DEVICE_NAME = process.env.DEVICE_NAME || 'iPhone X'; + const DEVICE_NAME = process.env.DEVICE_NAME || 'iPhone 17'; const MOCHA_TIMEOUT = 200000; this.timeout(MOCHA_TIMEOUT); - let chai; - let chaiAsPromised; - let expect; - let should; - let randName; - let validSdks = []; - let sdk; - let simctl; + let randName: string; + let validSdks: string[] = []; + let sdk: string; + let simctl: Simctl; before(async function () { - chai = await import('chai'); - chaiAsPromised = await import('chai-as-promised'); - - chai.use(chaiAsPromised.default); - expect = chai.expect; - should = chai.should(); simctl = new Simctl(); const devices = await simctl.getDevices(); console.log(`Found devices: ${JSON.stringify(devices, null, 2)}`); // eslint-disable-line no-console validSdks = _.keys(devices) .filter((key) => !_.isEmpty(devices[key])) - .sort((a, b) => a - b); + .sort((a, b) => a.localeCompare(b)); if (!validSdks.length) { throw new Error('No valid SDKs'); } @@ -45,11 +39,11 @@ describe('simctl', function () { // need to find a random name that does not already exist // give it 5 tries for (let i = 0; i < 5; i++) { - let randNum = parseInt(Math.random() * 100, 10); + const randNum = parseInt((Math.random() * 100).toString(), 10); randName = `device${randNum}`; let nameFound = false; - for (let list of _.values(devices)) { + for (const list of _.values(devices)) { if (_.includes(_.map(list, 'name'), randName)) { // need to find another random name nameFound = true; @@ -64,33 +58,33 @@ describe('simctl', function () { const devices = (await simctl.getDevices())[sdk]; const firstDevice = devices[0]; const expectedList = ['name', 'sdk', 'state', 'udid']; - firstDevice.should.have.any.keys(...expectedList); + expect(firstDevice).to.have.any.keys(...expectedList); }); describe('createDevice', function () { after(async function () { if (simctl.udid) { - await simctl.deleteDevice(16000); + await simctl.deleteDevice(); simctl.udid = null; } }); it('should create a device', async function () { simctl.udid = await simctl.createDevice(randName, DEVICE_NAME, sdk); - (typeof simctl.udid).should.equal('string'); - simctl.udid.length.should.equal(36); + expect(typeof simctl.udid).to.equal('string'); + expect(simctl.udid.length).to.equal(36); }); it('should create a device and be able to see it in devices list right away', async function () { const numSimsBefore = (await simctl.getDevices())[sdk].length; simctl.udid = await simctl.createDevice('node-simctl test', DEVICE_NAME, sdk); const numSimsAfter = (await simctl.getDevices())[sdk].length; - numSimsAfter.should.equal(numSimsBefore + 1); + expect(numSimsAfter).to.equal(numSimsBefore + 1); }); }); describe('device manipulation', function () { - let simctl; + let simctl: Simctl; const name = 'node-simctl test'; beforeEach(async function () { simctl = new Simctl(); @@ -98,13 +92,13 @@ describe('simctl', function () { }); afterEach(async function () { if (simctl.udid) { - await simctl.deleteDevice(simctl.udid, 16000); + await simctl.deleteDevice(); simctl.udid = null; } }); it('should get devices', async function () { const sdkDevices = await simctl.getDevices(sdk); - _.map(sdkDevices, 'name').should.include(name); + expect(_.map(sdkDevices, 'name')).to.include(name); }); it('should erase devices', async function () { @@ -114,26 +108,26 @@ describe('simctl', function () { it('should delete devices', async function () { await simctl.deleteDevice(); const sdkDevices = await simctl.getDevices(sdk); - _.map(sdkDevices, 'name').should.not.include(simctl.udid); + expect(_.map(sdkDevices, 'name')).to.not.include(simctl.udid); // so we do not delete again simctl.udid = null; }); it('should not fail to shutdown a shutdown simulator', async function () { - await simctl.shutdownDevice().should.eventually.not.be.rejected; + await expect(simctl.shutdownDevice()).to.eventually.not.be.rejected; }); }); it('should return a nice error for invalid usage', async function () { - let err = null; + let err: Error | null = null; try { await simctl.createDevice('foo', 'bar', 'baz'); } catch (e) { - err = e; + err = e as Error; } - should.exist(err); - err.message.should.include(`Unable to parse version 'baz'`); + expect(err).to.exist; + expect(err!.message).to.include(`Unable to parse version 'baz'`); }); describe('on running Simulator', function () { @@ -141,16 +135,20 @@ describe('simctl', function () { this.retries(3); } - let major, minor; + let major: number, minor: number; before(async function () { - ({major, minor} = await xcode.getVersion(true)); + const version = await xcode.getVersion(true); + if (typeof version === 'string') { + return this.skip(); + } + ({major, minor} = version); if (major < 8 || (major === 8 && minor < 1)) { return this.skip(); } const sdk = process.env.IOS_SDK || _.last(validSdks); - simctl.udid = await simctl.createDevice('runningSimTest', DEVICE_NAME, sdk); + simctl.udid = await simctl.createDevice('runningSimTest', DEVICE_NAME, sdk!); await simctl.bootDevice(); await simctl.startBootMonitor({timeout: MOCHA_TIMEOUT}); @@ -170,7 +168,7 @@ describe('simctl', function () { if (major < 8 || (major === 8 && minor < 1)) { return this.skip(); } - await simctl.startBootMonitor().should.eventually.be.fulfilled; + await expect(simctl.startBootMonitor()).to.eventually.be.fulfilled; }); it('should fail to monitor booting of non-existing simulator', async function () { if (major < 8 || (major === 8 && minor < 1)) { @@ -179,7 +177,7 @@ describe('simctl', function () { const udid = simctl.udid; try { simctl.udid = 'blabla'; - await simctl.startBootMonitor({timeout: 1000}).should.eventually.be.rejected; + await expect(simctl.startBootMonitor({timeout: 1000})).to.eventually.be.rejected; } finally { simctl.udid = udid; } @@ -207,14 +205,14 @@ describe('simctl', function () { await retryInterval(pbRetries, 1000, async () => { await simctl.setPasteboard(pbContent, encoding); - (await simctl.getPasteboard(encoding)).should.eql(pbContent); + expect(await simctl.getPasteboard(encoding)).to.eql(pbContent); }); }); }); describe('add media', function () { const BASE64_PNG = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=='; - let picturePath; + let picturePath: string | undefined; before(async function () { if (major < 8 || (major === 8 && minor < 1)) { return this.skip(); @@ -228,47 +226,47 @@ describe('simctl', function () { } }); it('should add media files', async function () { - (await simctl.addMedia(picturePath)).code.should.eql(0); + expect((await simctl.addMedia(picturePath!)).code).to.eql(0); }); }); it('should extract applications information', async function () { - (await simctl.appInfo('com.apple.springboard')).should.include('ApplicationType'); + expect(await simctl.appInfo('com.apple.springboard')).to.include('ApplicationType'); }); describe('getEnv', function () { it('should get env variable value', async function () { const udid = await simctl.getEnv('SIMULATOR_UDID'); - udid.length.should.be.above(0); + expect(udid!.length).to.be.above(0); }); it('should return null if no var is found', async function () { const udid = await simctl.getEnv('SIMULATOR_UDD'); - _.isNull(udid).should.be.true; + expect(_.isNull(udid)).to.be.true; }); }); describe('getDeviceTypes', function () { it('should get device types', async function () { const deviceTypes = await simctl.getDeviceTypes(); - deviceTypes.should.have.length; - deviceTypes.length.should.be.above(0); + expect(deviceTypes).to.have.length; + expect(deviceTypes.length).to.be.above(0); // at least one type, no matter the version of Xcode, should be an iPhone - deviceTypes.filter((el) => el.includes('iPhone')).length.should.be.above(1); + expect(deviceTypes.filter((el) => el.includes('iPhone')).length).to.be.above(1); }); }); describe('list', function () { it('should get everything from xcrun simctl list', async function () { const fullList = await simctl.list(); - fullList.should.have.property('devicetypes'); - fullList.should.have.property('runtimes'); - fullList.should.have.property('devices'); - fullList.should.have.property('pairs'); - fullList.devicetypes.length.should.be.above(1); + expect(fullList).to.have.property('devicetypes'); + expect(fullList).to.have.property('runtimes'); + expect(fullList).to.have.property('devices'); + expect(fullList).to.have.property('pairs'); + expect(fullList.devicetypes.length).to.be.above(1); // at least one type, no matter the version of Xcode, should be an iPhone - fullList.devicetypes.filter((el) => el.identifier.includes('iPhone')).length.should.be.above(0); + expect(fullList.devicetypes.filter((el) => el.identifier.includes('iPhone')).length).to.be.above(0); // at least one runtime should be iOS - fullList.runtimes.filter((el) => el.identifier.includes('iOS')).length.should.be.above(0); + expect(fullList.runtimes.filter((el) => el.identifier.includes('iOS')).length).to.be.above(0); }); }); @@ -296,8 +294,9 @@ describe('simctl', function () { } }; - await simctl.pushNotification(payload).should.be.fulfilled; + await expect(simctl.pushNotification(payload)).to.be.fulfilled; }); }); }); }); + diff --git a/test/unit/simctl-specs.cjs b/test/unit/simctl-specs.ts similarity index 65% rename from test/unit/simctl-specs.cjs rename to test/unit/simctl-specs.ts index fdf02d5..d5b35e3 100644 --- a/test/unit/simctl-specs.cjs +++ b/test/unit/simctl-specs.ts @@ -1,64 +1,60 @@ -import pq from 'proxyquire'; import sinon from 'sinon'; -import * as TeenProcess from 'teen_process'; import _ from 'lodash'; -import fs from 'node:fs'; +import fs from 'fs'; +import path from 'path'; +import { expect, use } from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import { Simctl } from '../../lib/simctl.js'; -const proxyquire = pq.noCallThru(); +use(chaiAsPromised); + +// @ts-ignore - __dirname is available in CommonJS +const testDir = typeof __dirname !== 'undefined' ? __dirname : path.dirname(require.resolve('./simctl-specs.ts')); const devicePayloads = [ [ { - stdout: fs.readFileSync(`${__dirname}/fixtures/devices.json`, 'utf-8'), + stdout: fs.readFileSync(path.join(testDir, 'fixtures/devices.json'), 'utf-8'), }, { - stdout: fs.readFileSync(`${__dirname}/fixtures/devices-with-unavailable.json`, 'utf-8'), + stdout: fs.readFileSync(path.join(__dirname, 'fixtures/devices-with-unavailable.json'), 'utf-8'), }, ], [ { - stdout: fs.readFileSync(`${__dirname}/fixtures/devices-simple.json`, 'utf-8'), + stdout: fs.readFileSync(path.join(__dirname, 'fixtures/devices-simple.json'), 'utf-8'), }, { - stdout: fs.readFileSync(`${__dirname}/fixtures/devices-with-unavailable-simple.json`, 'utf-8'), + stdout: fs.readFileSync(path.join(__dirname, 'fixtures/devices-with-unavailable-simple.json'), 'utf-8'), }, ], ]; describe('simctl', function () { - let chai; - let chaiAsPromised; - - const execStub = sinon.stub(TeenProcess, 'exec'); - function stubSimctl (xcrun = {}) { - const xcrunPath = process.env.XCRUN_BINARY || xcrun.path; - const { Simctl } = proxyquire('../../lib/simctl', { - 'which': sinon.stub().withArgs(xcrunPath).resolves(xcrunPath) - }); + let execStub: sinon.SinonStub; - return new Simctl({ xcrun }); + function stubSimctl (xcrun: { path?: string | null } = {}) { + const simctl = new Simctl({ xcrun: { path: xcrun.path ?? null } }); + execStub = sinon.stub(simctl, 'exec' as any).resolves({ stdout: '', stderr: '' }); + return simctl; } - before(async function() { - chai = await import('chai'); - chaiAsPromised = await import('chai-as-promised'); - - chai.should(); - chai.use(chaiAsPromised.default); - }); - describe('getDevices', function () { - let simctl; + let simctl: Simctl; beforeEach(function () { simctl = stubSimctl({ path: 'xcrun' }); }); afterEach(function () { - execStub.resetHistory(); + if (execStub) { + execStub.resetHistory(); + } }); after(function () { - execStub.reset(); + if (execStub) { + execStub.restore(); + } }); for (const [devicesPayload, devicesWithUnavailablePayload] of devicePayloads) { @@ -68,21 +64,21 @@ describe('simctl', function () { execStub.returns(devicesPayload); const devices = await simctl.getDevices(); - _.keys(devices).length.should.eql(2); + expect(_.keys(devices).length).to.eql(2); - devices['12.1'].length.should.eql(10); - devices['5.1'].length.should.eql(6); + expect(devices['12.1'].length).to.eql(10); + expect(devices['5.1'].length).to.eql(6); }); it('should ignore unavailable devices', async function () { execStub.returns(devicesWithUnavailablePayload); const devices = await simctl.getDevices(); - _.keys(devices).length.should.eql(4); + expect(_.keys(devices).length).to.eql(4); - devices['12.1'].length.should.eql(10); - devices['5.1'].length.should.eql(6); - devices['12.2'].length.should.eql(0); - devices['5.2'].length.should.eql(0); + expect(devices['12.1'].length).to.eql(10); + expect(devices['5.1'].length).to.eql(6); + expect(devices['12.2'].length).to.eql(0); + expect(devices['5.2'].length).to.eql(0); }); }); describe('platform defined', function () { @@ -90,18 +86,18 @@ describe('simctl', function () { execStub.returns(devicesPayload); const devices = await simctl.getDevices(null, 'tvOS'); - _.keys(devices).length.should.eql(1); + expect(_.keys(devices).length).to.eql(1); - devices['12.1'].length.should.eql(3); + expect(devices['12.1'].length).to.eql(3); }); it('should ignore unavailable devices', async function () { execStub.returns(devicesWithUnavailablePayload); const devices = await simctl.getDevices(null, 'tvOS'); - _.keys(devices).length.should.eql(2); + expect(_.keys(devices).length).to.eql(2); - devices['12.1'].length.should.eql(3); - devices['12.2'].length.should.eql(0); + expect(devices['12.1'].length).to.eql(3); + expect(devices['12.2'].length).to.eql(0); }); }); }); @@ -112,13 +108,13 @@ describe('simctl', function () { execStub.returns(devicesPayload); const devices = await simctl.getDevices('12.1'); - _.keys(devices).length.should.eql(10); + expect(_.keys(devices).length).to.eql(10); }); it('should ignore unavailable devices', async function () { execStub.returns(devicesWithUnavailablePayload); const devices = await simctl.getDevices('12.1'); - _.keys(devices).length.should.eql(10); + expect(_.keys(devices).length).to.eql(10); }); }); describe('platform defined', function () { @@ -126,13 +122,13 @@ describe('simctl', function () { execStub.returns(devicesPayload); const devices = await simctl.getDevices('5.1', 'watchOS'); - _.keys(devices).length.should.eql(6); + expect(_.keys(devices).length).to.eql(6); }); it('should ignore unavailable devices', async function () { execStub.returns(devicesWithUnavailablePayload); const devices = await simctl.getDevices('5.1', 'watchOS'); - _.keys(devices).length.should.eql(6); + expect(_.keys(devices).length).to.eql(6); }); }); }); @@ -141,17 +137,21 @@ describe('simctl', function () { describe('#createDevice', function () { const devicesPayload = devicePayloads[0][0]; - let simctl; + let simctl: Simctl; beforeEach(function () { simctl = stubSimctl({ path: 'xcrun' }); }); afterEach(function () { - execStub.resetHistory(); + if (execStub) { + execStub.resetHistory(); + } delete process.env.XCRUN_BINARY; }); after(function () { - execStub.reset(); + if (execStub) { + execStub.restore(); + } }); it('should create iOS simulator using xcrun path from env', async function () { @@ -168,11 +168,12 @@ describe('simctl', function () { '12.1.1', { timeout: 20000 } ); - execStub.getCall(2).args[1].should.eql([ - 'simctl', 'create', 'name', 'iPhone 6 Plus', 'com.apple.CoreSimulator.SimRuntime.iOS-12-1' + expect(execStub.getCall(2).args[0]).to.eql('create'); + expect(execStub.getCall(2).args[1].args).to.eql([ + 'name', 'iPhone 6 Plus', 'com.apple.CoreSimulator.SimRuntime.iOS-12-1' ]); - execStub.getCall(0).args[0].should.eql('some/path'); - devices.should.eql('EE76EA77-E975-4198-9859-69DFF74252D2'); + expect(execStub.getCall(0).args[0]).to.eql('list'); + expect(devices).to.eql('EE76EA77-E975-4198-9859-69DFF74252D2'); }); it('should create iOS simulator using xcrun path from passed opts', async function () { @@ -189,11 +190,12 @@ describe('simctl', function () { '12.1.1', { timeout: 20000 } ); - execStub.getCall(2).args[1].should.eql([ - 'simctl', 'create', 'name', 'iPhone 6 Plus', 'com.apple.CoreSimulator.SimRuntime.iOS-12-1' + expect(execStub.getCall(2).args[0]).to.eql('create'); + expect(execStub.getCall(2).args[1].args).to.eql([ + 'name', 'iPhone 6 Plus', 'com.apple.CoreSimulator.SimRuntime.iOS-12-1' ]); - execStub.getCall(0).args[0].should.eql('other/path'); - devices.should.eql('EE76EA77-E975-4198-9859-69DFF74252D2'); + expect(execStub.getCall(0).args[0]).to.eql('list'); + expect(devices).to.eql('EE76EA77-E975-4198-9859-69DFF74252D2'); }); it('should create iOS simulator and use xcrun simctl "json" parsing', async function () { @@ -232,10 +234,11 @@ describe('simctl', function () { '12.1.1', { timeout: 20000 } ); - execStub.getCall(1).args[1].should.eql([ - 'simctl', 'create', 'name', 'iPhone 6 Plus', 'com.apple.CoreSimulator.SimRuntime.iOS-12-1-1' + expect(execStub.getCall(1).args[0]).to.eql('create'); + expect(execStub.getCall(1).args[1].args).to.eql([ + 'name', 'iPhone 6 Plus', 'com.apple.CoreSimulator.SimRuntime.iOS-12-1-1' ]); - devices.should.eql('FA628127-1D5C-45C3-9918-A47BF7E2AE14'); + expect(devices).to.eql('FA628127-1D5C-45C3-9918-A47BF7E2AE14'); }); it('should create tvOS simulator', async function () { @@ -250,10 +253,11 @@ describe('simctl', function () { '12.1', { timeout: 20000, platform: 'tvOS' } ); - execStub.getCall(2).args[1].should.eql([ - 'simctl', 'create', 'name', 'Apple TV', 'com.apple.CoreSimulator.SimRuntime.tvOS-12-1' + expect(execStub.getCall(2).args[0]).to.eql('create'); + expect(execStub.getCall(2).args[1].args).to.eql([ + 'name', 'Apple TV', 'com.apple.CoreSimulator.SimRuntime.tvOS-12-1' ]); - devices.should.eql('FA628127-1D5C-45C3-9918-A47BF7E2AE14'); + expect(devices).to.eql('FA628127-1D5C-45C3-9918-A47BF7E2AE14'); }); it('should create iOS simulator with old runtime format', async function () { @@ -269,10 +273,11 @@ describe('simctl', function () { '12.1', { timeout: 20000 } ); - execStub.getCall(3).args[1].should.eql([ - 'simctl', 'create', 'name', 'iPhone 6 Plus', '12.1' + expect(execStub.getCall(3).args[0]).to.eql('create'); + expect(execStub.getCall(3).args[1].args).to.eql([ + 'name', 'iPhone 6 Plus', '12.1' ]); - devices.should.eql('EE76EA77-E975-4198-9859-69DFF74252D2'); + expect(devices).to.eql('EE76EA77-E975-4198-9859-69DFF74252D2'); }); it('should create iOS simulator with old runtime format and three-part platform version', async function () { @@ -288,10 +293,11 @@ describe('simctl', function () { '12.1', { timeout: 20000 } ); - execStub.getCall(3).args[1].should.eql([ - 'simctl', 'create', 'name', 'iPhone 6 Plus', '12.1' + expect(execStub.getCall(3).args[0]).to.eql('create'); + expect(execStub.getCall(3).args[1].args).to.eql([ + 'name', 'iPhone 6 Plus', '12.1' ]); - devices.should.eql('EE76EA77-E975-4198-9859-69DFF74252D2'); + expect(devices).to.eql('EE76EA77-E975-4198-9859-69DFF74252D2'); }); it('should create iOS simulator with three-part platform version and three-part runtime', async function () { @@ -307,10 +313,12 @@ describe('simctl', function () { '12.1.1', { timeout: 20000 } ); - execStub.getCall(3).args[1].should.eql([ - 'simctl', 'create', 'name', 'iPhone 6 Plus', 'com.apple.CoreSimulator.SimRuntime.iOS-12-1-1' + expect(execStub.getCall(3).args[0]).to.eql('create'); + expect(execStub.getCall(3).args[1].args).to.eql([ + 'name', 'iPhone 6 Plus', 'com.apple.CoreSimulator.SimRuntime.iOS-12-1-1' ]); - devices.should.eql('EE76EA77-E975-4198-9859-69DFF74252D2'); + expect(devices).to.eql('EE76EA77-E975-4198-9859-69DFF74252D2'); }); }); }); + diff --git a/tsconfig.json b/tsconfig.json index 8de7b04..9c85a36 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,11 +5,12 @@ "strict": false, "esModuleInterop": true, "outDir": "build", - "types": ["node"], + "types": ["node", "mocha"], "checkJs": true }, "include": [ "index.js", - "lib" + "lib", + "test" ] }