Skip to content

Commit

Permalink
Merge pull request #9 from garrettmflynn/electron-test-fixes
Browse files Browse the repository at this point in the history
Electron test fixes
  • Loading branch information
garrettmflynn committed Dec 13, 2023
2 parents a073656 + c711000 commit 9dd9c8f
Show file tree
Hide file tree
Showing 16 changed files with 154 additions and 125 deletions.
23 changes: 18 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,6 @@
"@capacitor/cli": "^5.5.1",
"vitepress": "^1.0.0-rc.20",
"vitest": "^0.34.5"
}
}
},
"main": "/Users/garrettflynn/Documents/GitHub/commoners/packages/core/tests/.commoners/.temp/main.js"
}
11 changes: 6 additions & 5 deletions packages/core/globals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,10 @@ export const globalTempDir = join(globalWorkspacePath, '.temp')
const callbacks = []
export const onExit = (callback) => callbacks.push(callback)

const runBeforeExitCallbacks = () => {
const runBeforeExitCallbacks = (code) => {
console.log('Running before exit callbacks', code)
callbacks.forEach(cb => {
if (!cb.called) cb()
if (!cb.called) cb(code)
cb.called = true
})
}
Expand All @@ -47,9 +48,9 @@ export const initialize = (tempDir = globalTempDir) => {

process.on('exit', runBeforeExitCallbacks);

process.on('SIGINT', () => {
runBeforeExitCallbacks()
process.exit(0)
process.on('SIGINT', (code) => {
runBeforeExitCallbacks(code)
process.exit(code as any)
})

// Always clear the temp directory on exit
Expand Down
7 changes: 5 additions & 2 deletions packages/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,12 @@ export const resolveConfigPath = (base = '') => resolveFile(join(base, 'commoner

export async function loadConfigFromFile(filesystemPath: string = resolveConfigPath()) {

if (lstatSync(filesystemPath).isDirectory()) filesystemPath = resolveConfigPath(filesystemPath)
if (filesystemPath && lstatSync(filesystemPath).isDirectory()) filesystemPath = resolveConfigPath(filesystemPath)

if (!filesystemPath) return {} as UserConfig
if (!filesystemPath) {
console.log(`No config file found in ${process.cwd()}`)
return {} as UserConfig
}

// Bundle config file
const result = await build({
Expand Down
7 changes: 6 additions & 1 deletion packages/core/launch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,12 @@ export default async function (options: LaunchOptions) {

if (!desktopInfo.filepath || !existsSync(desktopInfo.filepath)) throw new Error(`This application has not been built for ${PLATFORM} yet.`)

await spawnProcess(PLATFORM === 'mac' ? 'open' : 'start', [`${join(desktopInfo.base, `"${desktopInfo.filename}"`)}`, '--args', `--remote-debugging-port=${electronDebugPort}`]);
await spawnProcess(PLATFORM === 'mac' ? 'open' : 'start', [
`${join(desktopInfo.base, `"${desktopInfo.filename}"`)}`,
'--args',
`--remote-debugging-port=${electronDebugPort}`,
`--remote-allow-origins=*`
]);

const debugUrl = `http://localhost:${electronDebugPort}`
console.log(chalk.gray(`Debug ${desktopInfo.name} at ${debugUrl}`))
Expand Down
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"pkg": "^5.8.1",
"plist": "^3.1.0",
"vite": "^4.2.1",
"vite-plugin-electron": "^0.11.2",
"vite-plugin-electron": "0.15.4",
"vite-plugin-pwa": "^0.16.4"
},
"devDependencies": {
Expand Down
10 changes: 2 additions & 8 deletions packages/core/start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,15 @@ export default async function ( opts: UserConfig = {} ) {
const isDesktopTarget = isDesktop(target)
const isMobileTarget = isMobile(target)

if (target === 'electron' && root !== process.cwd()) {
console.log(`\n👎 Cannot start ${chalk.bold(name)} (electron) when targeting a different root.\n`)
process.exit(1)
}

console.log(`\n✊ Starting ${chalk.bold(chalk.greenBright(name))} for ${target}\n`)


// Create URLs that will be shared with the frontend
if (isMobileTarget) resolvedConfig.services = updateServicesWithLocalIP(resolvedConfig.services)

const { services: resolvedServices } = resolvedConfig

const createAllServices = () => {
console.log(`\n👊 Creating ${chalk.bold('Services')}\n`)
console.log(`\n👊 Starting ${chalk.bold('Services')}\n`)
return createServices(resolvedServices, { root }) // Run services in parallel
}

Expand Down Expand Up @@ -76,7 +70,7 @@ export default async function ( opts: UserConfig = {} ) {
}

// Configure the desktop instance
if (isDesktopTarget) await configureForDesktop(outDir, root) // Configure the desktop instance
if (isDesktopTarget) await configureForDesktop(outDir) // NOTE: Do not correct for root here

// Create all services
else activeInstances.services = await createAllServices()
Expand Down
11 changes: 4 additions & 7 deletions packages/core/templates/electron/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ import * as services from '../services/index'

import dotenv from 'dotenv'

process.env.COMMONERS_ELECTRON = true

let mainWindow;

function send(this: BrowserWindow, channel: string, ...args: any[]) {
Expand Down Expand Up @@ -73,6 +71,7 @@ const isProduction = !devServerURL
// Enable remote debugging port for Vitest
if (process.env.VITEST) {
app.commandLine.appendSwitch('remote-debugging-port', `${8315}`) // Mirrors the global electronDebugPort variable
app.commandLine.appendSwitch('remote-allow-origins', '*') // Allow all remote origins
}

// Populate platform variable if it doesn't exist
Expand Down Expand Up @@ -254,16 +253,14 @@ runPlugins(null, 'preload').then(() => {
// NOTE: Services cannot be filtered in desktop mode
const { active } = await services.createAll(config.services, {
mode: isProduction ? 'local' : undefined,
root: isProduction ? __dirname : undefined,
root: isProduction ? __dirname : join(__dirname, '..', '..'), // Back out of default outDir
onClosed: (id, code) => serviceSend(id, 'closed', code),
onLog: (id, msg) => serviceSend(id, 'log', msg.toString()),
})

if (active) {

for (let id in active) serviceOn(id, 'status', (event) => event.returnValue = active[id].status)

process.env.COMMONERS_SERVICES = JSON.stringify(services.sanitize(active)) // Expose to renderer process (and ensure URLs are correct)
ipcMain.on('commoners:services', (event) => event.returnValue = services.sanitize(active)) // Expose to renderer process (and ensure URLs are correct)
}

// Proxy the services through the custom protocol
Expand Down Expand Up @@ -302,5 +299,5 @@ app.on('before-quit', async (ev) => {
const result = await runPlugins(null, 'unload')
if (result.includes(false)) return

try { services.close() } catch (err) { console.error(err); } finally { app.exit() }
try { services.close() } catch (err) { console.error(err); } finally { app.exit() } // Exit gracefully
});
4 changes: 2 additions & 2 deletions packages/core/templates/electron/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ import { ipcRenderer } from 'electron'
import { contextBridge } from 'electron'

const globalVariableName = '__commoners'
const services = process.env.COMMONERS_SERVICES
const services = ipcRenderer.sendSync('commoners:services')

const TEMP_COMMONERS = {
quit: () => ipcRenderer.send('commoners:quit'),
services: services ? JSON.parse(services) : null, // Ensure correct ports
services, // Ensure correct ports

// Will be scoped by plugin in onload.ts
on: (channel, listener) => ipcRenderer.on(channel, listener),
Expand Down
2 changes: 1 addition & 1 deletion packages/core/templates/services/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export async function resolveService (
// Define default build command
if ( autoBuild ) {

const outDir = relative(process.cwd(), join(root, globalServiceWorkspacePath)) // process.env.COMMONERS_ELECTRON ? join(globalWorkspacePath, '.temp', 'electron', globalServiceWorkspacePath) : globalServiceWorkspacePath
const outDir = relative(process.cwd(), join(root, globalServiceWorkspacePath))
const out = join(outDir, name)

resolvedConfig.build = {
Expand Down
10 changes: 6 additions & 4 deletions packages/core/tests/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import { resolve } from 'node:path'
import { name } from './commoners.config'
import { projectBase, registerBuildTest, registerStartTest, serviceTests } from './utils'


describe('Custom project base is loaded', () => {

test('Config is resolved', () => {
Expand All @@ -30,8 +29,11 @@ describe('Start', () => {

registerStartTest('Web')

// NOTE: Skipped because I can't close the Electron instance programmatically
registerStartTest('Desktop', { target: 'electron'}, false)
registerStartTest(
'Desktop',
{ target: 'electron'},
false // NOTE: Valid test suite—but causes a SIGABRT that results in a crash
)

// NOTE: Skipped because Ruby Gems needs to be updated
registerStartTest('Mobile', { target: 'mobile' }, false)
Expand All @@ -45,7 +47,7 @@ describe('Share', () => {
serviceTests.share.basic(output)
serviceTests.echo('http', output)
serviceTests.echo('express', output)
// serviceTests.echo('python')
serviceTests.echo('manual', output)
})

describe('Share specific service', () => {
Expand Down
47 changes: 30 additions & 17 deletions packages/core/tests/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// sum.test.js
import { expect, test, describe, beforeAll, afterAll } from 'vitest'
import { expect, test, describe } from 'vitest'

import { normalizeTarget } from '../index'

Expand All @@ -19,29 +19,47 @@ const getServices = (registrationOutput) => ((registrationOutput.commoners ?? re
export const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms))

const e2eTests = {
basic: (registrationOutput, { target }) => {
basic: (registrationOutput, { target }, mode = 'dev') => {

const normalizedTarget = normalizeTarget(target)

describe('Basic E2E Test', () => {

test("Global variable is valid", async () => {

const { commoners } = registrationOutput
const { commoners = {} } = registrationOutput
const { name, target, version, plugins, services, ready } = commoners
expect(name).toBe(config.name);
expect(version).toBe(userPkg.version);
expect(target).toBe(normalizedTarget);
expect('echo' in plugins).toBe(true);
expect(services).instanceOf(Object)
expect(ready).instanceOf(Object) // Really is promise. Is passed as an object
expect(ready).instanceOf(Object) // Resolved Promise

// Services cleared on mobile and web (not remote...)
if (normalizedTarget === 'desktop') {
expect('http' in services).toBe(true);
expect(typeof services.url).toBe('string'); // NOTE: Actually specify the URL later
expect(commoners.quit).instanceOf(Object)
}

const isDev = mode === 'dev'

Object.entries(config.services).forEach(([name, service]) => {

// Web / PWA / Mobile builds will have cleared services (that are not remote)
expect(name in services).toBe(isDev);

if (isDev) {
expect(typeof services[name].url).toBe('string');
if ('port' in service) expect(parseInt(new URL(services[name].url).port)).toBe(service.port)
}

if (normalizedTarget === 'desktop') {
expect(typeof services[name].filepath).toBe('string');
expect(services[name].status).toBe(true)
expect(services[name].onActivityDetected).instanceOf(Object) // Function
expect(services[name].onClosed).instanceOf(Object) // Function
}
})

});
})
}
Expand All @@ -50,6 +68,7 @@ const e2eTests = {
export const registerStartTest = (name, { target = 'web' } = {}, enabled = true) => {

const describeCommand = enabled ? describe : describe.skip

describeCommand(name, () => {

// start(projectBase, { target })
Expand Down Expand Up @@ -98,7 +117,7 @@ export const registerBuildTest = (name, { target = 'web'} = {}, enabled = true)

describeFn('Launched application tests', async () => {
const output = startBrowserTest({ launch: opts })
e2eTests.basic(output, { target })
e2eTests.basic(output, { target }, 'local')
})

})
Expand All @@ -107,9 +126,7 @@ export const registerBuildTest = (name, { target = 'web'} = {}, enabled = true)
const runAllServiceTests = (registrationOutput) => {
serviceTests.echo('http', registrationOutput)
serviceTests.echo('express', registrationOutput)
// serviceTests.echo('python')
// serviceTests.complex()
// serviceTests.basic('python')
serviceTests.echo('manual', registrationOutput)
}

export const serviceTests = {
Expand Down Expand Up @@ -144,11 +161,7 @@ export const serviceTests = {
const services = getServices(registrationOutput)

// Request an echo response
const res = await fetch(new URL('echo', services[id].url), {
method: "POST",
body: JSON.stringify({ randomNumber })
}).then(res => res.json())

const res = await fetch(new URL('echo', services[id].url), { method: "POST", body: JSON.stringify({ randomNumber }) }).then(res => res.json())
expect(res.randomNumber).toBe(randomNumber)
})
},
Expand Down
5 changes: 4 additions & 1 deletion packages/core/utils/assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,10 @@ export const buildAssets = async (config: ResolvedConfig, mode?: AssetServiceOpt
// Force a build format if the proper extension is specified
const format = resolvedExtension === 'mjs' ? 'esm' : resolvedExtension === 'cjs' ? 'cjs' : undefined

const buildForNode = () => buildForBrowser({ platform: 'node', external: [ "*.node" ] })
const buildForNode = () => buildForBrowser({
platform: 'node',
external: [ "*.node" ]
})

const buildForBrowser = (opts = {}) => esbuild.build({ ...baseConfig, format, ...opts})

Expand Down
Loading

0 comments on commit 9dd9c8f

Please sign in to comment.