tests(e2e): improve coverage

This commit is contained in:
Lucas Nogueira
2026-09-22 21:54:21 -03:00
parent 684feb2510
commit c8c373b48b
50 changed files with 1893 additions and 183 deletions
+6
View File
@@ -11,10 +11,12 @@
},
"dependencies": {
"@tauri-apps/api": "^2.11.0",
"@tauri-apps/plugin-autostart": "workspace:*",
"@tauri-apps/plugin-barcode-scanner": "workspace:*",
"@tauri-apps/plugin-biometric": "workspace:*",
"@tauri-apps/plugin-cli": "workspace:*",
"@tauri-apps/plugin-clipboard-manager": "workspace:*",
"@tauri-apps/plugin-deep-link": "workspace:*",
"@tauri-apps/plugin-dialog": "workspace:*",
"@tauri-apps/plugin-fs": "workspace:*",
"@tauri-apps/plugin-geolocation": "workspace:*",
@@ -25,11 +27,15 @@
"@tauri-apps/plugin-notification": "workspace:*",
"@tauri-apps/plugin-opener": "workspace:*",
"@tauri-apps/plugin-os": "workspace:*",
"@tauri-apps/plugin-positioner": "workspace:*",
"@tauri-apps/plugin-process": "workspace:*",
"@tauri-apps/plugin-shell": "workspace:*",
"@tauri-apps/plugin-sql": "workspace:*",
"@tauri-apps/plugin-store": "workspace:*",
"@tauri-apps/plugin-stronghold": "workspace:*",
"@tauri-apps/plugin-updater": "workspace:*",
"@tauri-apps/plugin-upload": "workspace:*",
"@tauri-apps/plugin-websocket": "workspace:*",
"@zerodevx/svelte-json-view": "2.0.0"
},
"devDependencies": {
+11 -1
View File
@@ -39,12 +39,18 @@ tauri-plugin-opener = { path = "../../../plugins/opener", version = "2.5.5" }
tauri-plugin-shell = { path = "../../../plugins/shell", version = "2.3.6" }
tauri-plugin-store = { path = "../../../plugins/store", version = "2.4.5" }
tauri-plugin-upload = { path = "../../../plugins/upload", version = "2.3.0" }
tauri-plugin-deep-link = { path = "../../../plugins/deep-link", version = "2.4.10" }
tauri-plugin-sql = { path = "../../../plugins/sql", version = "2.4.1", features = [
"sqlite",
] }
tauri-plugin-stronghold = { path = "../../../plugins/stronghold", version = "2.3.2" }
tauri-plugin-websocket = { path = "../../../plugins/websocket", version = "2.4.3" }
# WebDriver automation bridge, used by the plugins e2e suite (packages/api-e2e).
# Desktop-only and behind the off-by-default `automation` feature so it never ships in a
# regular build.
[target."cfg(not(any(target_os = \"android\", target_os = \"ios\")))".dependencies]
tauri-plugin-automation = { version = "0.1.4", optional = true }
tauri-plugin-automation = { version = "0.2.0", optional = true }
[dependencies.tauri]
workspace = true
@@ -61,8 +67,12 @@ features = [
]
[target."cfg(any(target_os = \"macos\", windows, target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies]
tauri-plugin-autostart = { path = "../../../plugins/autostart", version = "2.5.1" }
tauri-plugin-cli = { path = "../../../plugins/cli", version = "2.4.1" }
tauri-plugin-global-shortcut = { path = "../../../plugins/global-shortcut", version = "2.3.2" }
tauri-plugin-positioner = { path = "../../../plugins/positioner", version = "2.3.4", features = [
"tray-icon",
] }
tauri-plugin-updater = { path = "../../../plugins/updater", version = "2.12.0" }
tauri-plugin-window-state = { path = "../../../plugins/window-state", version = "2.2.0" }
+12 -1
View File
@@ -132,6 +132,17 @@
"identifier": "opener:allow-open-path",
"allow": [{ "path": "$APPDATA" }, { "path": "$APPDATA/**" }]
},
"upload:default"
"upload:default",
"deep-link:default",
"deep-link:allow-register",
"deep-link:allow-unregister",
"deep-link:allow-is-registered",
"sql:default",
"sql:allow-execute",
"stronghold:default",
"stronghold:allow-destroy",
"stronghold:allow-remove-secret",
"stronghold:allow-remove-store-record",
"websocket:default"
]
}
@@ -5,7 +5,9 @@
"windows": ["main"],
"platforms": ["linux", "macOS", "windows"],
"permissions": [
"autostart:default",
"cli:default",
"positioner:default",
"updater:default",
"global-shortcut:allow-unregister",
"global-shortcut:allow-register",
+30 -1
View File
@@ -9,7 +9,7 @@ mod tray;
use serde::Serialize;
use tauri::{
webview::{PageLoadEvent, WebviewWindowBuilder},
App, AppHandle, Emitter, Listener, RunEvent, WebviewUrl,
App, AppHandle, Emitter, Listener, Manager, RunEvent, WebviewUrl,
};
#[derive(Clone, Serialize)]
@@ -55,11 +55,40 @@ pub fn run() {
.plugin(tauri_plugin_shell::init())
.plugin(tauri_plugin_store::Builder::default().build())
.plugin(tauri_plugin_upload::init())
.plugin(tauri_plugin_deep_link::init())
.plugin(tauri_plugin_websocket::init())
.plugin(
tauri_plugin_sql::Builder::new()
.add_migrations(
"sqlite:api.db",
vec![tauri_plugin_sql::Migration {
version: 1,
description: "create_todos_table",
sql: "CREATE TABLE todos (id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, done INTEGER NOT NULL DEFAULT 0);",
kind: tauri_plugin_sql::MigrationKind::Up,
}],
)
.build(),
)
.setup(move |app| {
// the argon2 salt lives next to the snapshots the frontend creates
let local_data_dir = app.path().app_local_data_dir()?;
std::fs::create_dir_all(&local_data_dir)?;
app.handle().plugin(
tauri_plugin_stronghold::Builder::with_argon2(&local_data_dir.join("salt.txt"))
.build(),
)?;
#[cfg(desktop)]
{
// registered before the tray, whose events it tracks
app.handle().plugin(tauri_plugin_positioner::init())?;
tray::create_tray(app.handle())?;
app.handle().plugin(tauri_plugin_cli::init())?;
app.handle().plugin(tauri_plugin_autostart::init(
tauri_plugin_autostart::MacosLauncher::LaunchAgent,
None,
))?;
app.handle()
.plugin(tauri_plugin_global_shortcut::Builder::new().build())?;
app.handle()
+2
View File
@@ -107,6 +107,8 @@ pub fn create_tray<R: Runtime>(app: &tauri::AppHandle<R>) -> tauri::Result<()> {
_ => {}
})
.on_tray_icon_event(|tray, event| {
// lets the positioner plugin place windows relative to the tray icon
tauri_plugin_positioner::on_tray_event(tray.app_handle(), &event);
if let TrayIconEvent::Click {
button_state: MouseButtonState::Down,
button: MouseButton::Left,
+36
View File
@@ -33,6 +33,12 @@
import Geolocation from './views/Geolocation.svelte'
import Haptics from './views/Haptics.svelte'
import Nfc from './views/Nfc.svelte'
import Autostart from './views/Autostart.svelte'
import DeepLink from './views/DeepLink.svelte'
import Positioner from './views/Positioner.svelte'
import Sql from './views/Sql.svelte'
import Stronghold from './views/Stronghold.svelte'
import WebSocket from './views/WebSocket.svelte'
import TitleBar from './lib/TitleBar.svelte'
@@ -131,6 +137,36 @@
component: WebRTC,
icon: 'i-ph-broadcast'
},
{
label: 'WebSocket',
component: WebSocket,
icon: 'i-ph-plugs-connected'
},
{
label: 'SQL',
component: Sql,
icon: 'i-ph-database'
},
{
label: 'Stronghold',
component: Stronghold,
icon: 'i-ph-lock-key'
},
{
label: 'Deep link',
component: DeepLink,
icon: 'i-ph-link'
},
!isMobile && {
label: 'Autostart',
component: Autostart,
icon: 'i-ph-power'
},
!isMobile && {
label: 'Positioner',
component: Positioner,
icon: 'i-ph-arrows-out-cardinal'
},
isMobile && {
label: 'Scanner',
component: Scanner,
+30
View File
@@ -0,0 +1,30 @@
<script>
import { enable, disable, isEnabled } from '@tauri-apps/plugin-autostart'
import { onMount } from 'svelte'
export let onMessage
let enabled = false
async function refresh() {
enabled = await isEnabled()
}
function toggle() {
;(enabled ? disable() : enable())
.then(refresh)
.then(() => onMessage(`Autostart ${enabled ? 'enabled' : 'disabled'}`))
.catch(onMessage)
}
onMount(() => {
refresh().catch(onMessage)
})
</script>
<div class="flex flex-row gap-2 items-center">
<button class="btn" on:click={toggle}>
{enabled ? 'Disable' : 'Enable'} autostart
</button>
<span>Launching at login is {enabled ? 'enabled' : 'disabled'}</span>
</div>
+67
View File
@@ -0,0 +1,67 @@
<script>
import {
getCurrent,
onOpenUrl,
register,
unregister,
isRegistered
} from '@tauri-apps/plugin-deep-link'
import { onMount, onDestroy } from 'svelte'
export let onMessage
let current = null
let protocol = 'tauri-api'
let unlisten
onMount(async () => {
try {
current = await getCurrent()
unlisten = await onOpenUrl((urls) => {
current = urls
onMessage(`Opened with ${urls.join(', ')}`)
})
} catch (error) {
onMessage(error)
}
})
onDestroy(() => {
unlisten?.()
})
function registerProtocol() {
register(protocol)
.then(() => onMessage(`Registered ${protocol}://`))
.catch(onMessage)
}
function unregisterProtocol() {
unregister(protocol)
.then(() => onMessage(`Unregistered ${protocol}://`))
.catch(onMessage)
}
function checkProtocol() {
isRegistered(protocol)
.then((registered) =>
onMessage(
`${protocol}:// is ${registered ? '' : 'not '}handled by this app`
)
)
.catch(onMessage)
}
</script>
<div class="flex flex-col gap-2">
<div>
Current deep link: <code>{current ? current.join(', ') : 'none'}</code>
</div>
<div class="flex flex-row gap-2 items-center">
<input class="input grow" placeholder="Scheme" bind:value={protocol} />
<button class="btn" on:click={registerProtocol}>Register</button>
<button class="btn" on:click={unregisterProtocol}>Unregister</button>
<button class="btn" on:click={checkProtocol}>Is registered?</button>
</div>
</div>
+40
View File
@@ -0,0 +1,40 @@
<script>
import {
moveWindow,
moveWindowConstrained,
Position
} from '@tauri-apps/plugin-positioner'
export let onMessage
// `Position` is a numeric enum, so it also maps the values back to their names
const positions = Object.keys(Position).filter((key) => isNaN(Number(key)))
let position = 'Center'
let constrained = false
function move() {
;(constrained ? moveWindowConstrained : moveWindow)(Position[position])
.then(() => onMessage(`Moved the window to ${position}`))
.catch(onMessage)
}
</script>
<div class="flex flex-col gap-2">
<p>
The tray positions only resolve once the tray icon has been clicked, which
reports its location to the plugin.
</p>
<div class="flex flex-row gap-2 items-center">
<select class="input" bind:value={position}>
{#each positions as name}
<option value={name}>{name}</option>
{/each}
</select>
<label>
<input type="checkbox" bind:checked={constrained} />
Constrain to the tray icon's monitor
</label>
<button class="btn" on:click={move}>Move window</button>
</div>
</div>
+68
View File
@@ -0,0 +1,68 @@
<script>
import Database from '@tauri-apps/plugin-sql'
import { onMount } from 'svelte'
export let onMessage
// the `todos` table is created by the migration the app registers for this database
let db
let todos = []
let title = ''
async function refresh() {
todos = await db.select('SELECT * FROM todos ORDER BY id')
}
onMount(async () => {
try {
db = await Database.load('sqlite:api.db')
await refresh()
} catch (error) {
onMessage(error)
}
})
function add() {
db.execute('INSERT INTO todos (title) VALUES ($1)', [title])
.then((result) => {
onMessage(result)
title = ''
return refresh()
})
.catch(onMessage)
}
function toggle(todo) {
db.execute('UPDATE todos SET done = $1 WHERE id = $2', [
todo.done ? 0 : 1,
todo.id
])
.then(refresh)
.catch(onMessage)
}
function remove(todo) {
db.execute('DELETE FROM todos WHERE id = $1', [todo.id])
.then(refresh)
.catch(onMessage)
}
</script>
<div class="flex flex-col gap-2">
<form class="flex flex-row gap-2" on:submit|preventDefault={add}>
<input class="input grow" placeholder="New todo" bind:value={title} />
<button class="btn" type="submit" disabled={!db || !title}>Add</button>
</form>
{#each todos as todo (todo.id)}
<div class="flex flex-row gap-2 items-center">
<input
type="checkbox"
checked={todo.done === 1}
on:change={() => toggle(todo)}
/>
<span class="grow" class:line-through={todo.done === 1}>{todo.title}</span>
<button class="btn" on:click={() => remove(todo)}>Delete</button>
</div>
{/each}
</div>
+71
View File
@@ -0,0 +1,71 @@
<script>
import { Stronghold } from '@tauri-apps/plugin-stronghold'
import { appLocalDataDir, join } from '@tauri-apps/api/path'
export let onMessage
const clientName = 'api-example'
const storeKey = 'secret'
let password = 'password'
let stronghold
let store
let record = ''
async function load() {
try {
const path = await join(await appLocalDataDir(), 'api.stronghold')
stronghold = await Stronghold.load(path, password)
let client
try {
client = await stronghold.loadClient(clientName)
} catch {
client = await stronghold.createClient(clientName)
}
store = client.getStore()
const value = await store.get(storeKey)
record = value ? new TextDecoder().decode(value) : ''
onMessage('Stronghold loaded')
} catch (error) {
stronghold = null
onMessage(error)
}
}
async function save() {
try {
await store.insert(storeKey, Array.from(new TextEncoder().encode(record)))
await stronghold.save()
onMessage('Record saved')
} catch (error) {
onMessage(error)
}
}
async function unload() {
await stronghold.unload().catch(onMessage)
stronghold = null
store = null
record = ''
}
</script>
<div class="flex flex-col gap-2">
{#if stronghold}
<div class="flex flex-row gap-2">
<input class="input grow" placeholder="Secret" bind:value={record} />
<button class="btn" on:click={save}>Save</button>
<button class="btn" on:click={unload}>Lock</button>
</div>
{:else}
<form class="flex flex-row gap-2" on:submit|preventDefault={load}>
<input
class="input grow"
type="password"
placeholder="Password"
bind:value={password}
/>
<button class="btn" type="submit">Unlock</button>
</form>
{/if}
</div>
+55
View File
@@ -0,0 +1,55 @@
<script>
import WebSocket from '@tauri-apps/plugin-websocket'
import { onDestroy } from 'svelte'
export let onMessage
let url = 'wss://echo.websocket.org'
let message = ''
let ws
async function connect() {
try {
ws = await WebSocket.connect(url)
ws.addListener((received) => {
onMessage(received)
if (received.type === 'Close') {
ws = null
}
})
onMessage(`Connected to ${url}`)
} catch (error) {
onMessage(error)
}
}
function send() {
ws.send(message)
.then(() => (message = ''))
.catch(onMessage)
}
function disconnect() {
ws.disconnect().catch(onMessage)
ws = null
}
onDestroy(() => {
ws?.disconnect().catch(() => {})
})
</script>
<div class="flex flex-col gap-2">
{#if ws}
<form class="flex flex-row gap-2" on:submit|preventDefault={send}>
<input class="input grow" placeholder="Message" bind:value={message} />
<button class="btn" type="submit">Send</button>
<button class="btn" type="button" on:click={disconnect}>Disconnect</button>
</form>
{:else}
<form class="flex flex-row gap-2" on:submit|preventDefault={connect}>
<input class="input grow" placeholder="ws:// or wss:// URL" bind:value={url} />
<button class="btn" type="submit">Connect</button>
</form>
{/if}
</div>