Most plugin code is a thin wrapper: build the options, call the plugin, decide what happens next. That logic tests in milliseconds, without a device, once the plugin module is replaced with spies. BookNest uses Vitest 4.0.18 102,989 (MIT, npm 2,036 install -D vitest jsdom 21,691 , as in the Ionic 20,753 starter of ionic start), with a vitest.config.js selecting the jsdom environment so show() finds a document. The tests pin down update() (Capawesome Live Update):
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { LiveUpdate } from '@capawesome/capacitor-live-update';
import { linkToRoute, update } from '../../src/js/lab.js';
// Replace the native plugin with spies: the test checks what the wrapper asks for
vi.mock('@capawesome/capacitor-live-update', () => ({
LiveUpdate: {
downloadBundle: vi.fn(async () => {}),
setNextBundle: vi.fn(async () => {}),
getBundles: vi.fn(async () => ({ bundleIds: ['1.1.0'] })),
reload: vi.fn(async () => {}),
},
}));
beforeEach(() => vi.clearAllMocks());
describe('update()', () => {
it('downloads the bundle, makes it next and reloads', async () => {
await update('1.1.0');
expect(LiveUpdate.downloadBundle).toHaveBeenCalledWith(
{ url: 'http://10.0.2.2:8480/bundles/1.1.0.zip', bundleId: '1.1.0' });
expect(LiveUpdate.setNextBundle).toHaveBeenCalledWith({ bundleId: '1.1.0' });
expect(LiveUpdate.reload).toHaveBeenCalledOnce();
});
it('never reloads after a failed download', async () => {
LiveUpdate.downloadBundle.mockRejectedValueOnce(new Error('CLEARTEXT not permitted'));
await expect(update('1.1.0')).rejects.toThrow('CLEARTEXT');
expect(LiveUpdate.setNextBundle).not.toHaveBeenCalled();
expect(LiveUpdate.reload).not.toHaveBeenCalled();
});
});vi.mock() is hoisted above the imports, so lab.js gets the spies. npx vitest run --reporter=verbose:
✓ tests/unit/lab.test.js > update() > downloads the bundle, makes it next and reloads 44ms
✓ tests/unit/lab.test.js > update() > never reloads after a failed download 34ms
✓ tests/unit/lab.test.js > linkToRoute() > booknest://lab/ -> /lab/ 2ms
✓ tests/unit/lab.test.js > linkToRoute() > booknest://books/3/ -> /books/3/ 1ms
✓ tests/unit/lab.test.js > linkToRoute() > https://booknest.example.com/books/3/?tab=notes ->
/books/3/?tab=notes 1ms
Test Files 1 passed (1)
Tests 5 passed (5)The second test proves that a failed download never reloads the app into a missing bundle. For the native halves, the plugin generator of Scaffolding a Plugin adds verify:android (./gradlew clean build test, JUnit 48,319 ) and verify:ios (xcodebuild tests).