Files
openless/openless-all/app/scripts/less-computer-opencode-contract.test.mjs
T
cd48a037a4 fix(less-computer): restore OpenCode model discovery and execution (#840)
* fix(less-computer): restore OpenCode model execution

* fix(capsule): gate set_ignore_cursor_events behind #[cfg(not(mobile))]

#887 introduced desktop-only capsule cursor-passthrough logic into the
unconditionally-compiled capsule_focus module, breaking Android cargo check
(E0599: no method set_ignore_cursor_events on tauri::WebviewWindow). Gate the
block to desktop; variables it consumes (classic_style, selection_polish,
visible) are still used on mobile outside the block.

---------

Co-authored-by: sim <sim@local>
Co-authored-by: Chris233 <h-chris233@outlook.com>
2026-08-03 16:03:41 +08:00

124 lines
5.2 KiB
JavaScript

import assert from 'node:assert/strict';
import { readFile } from 'node:fs/promises';
import { join } from 'node:path';
import { fileURLToPath } from 'node:url';
const appRoot = fileURLToPath(new URL('..', import.meta.url));
const read = (relativePath) => readFile(join(appRoot, relativePath), 'utf8');
const [settings, ipc, opencode, dictation, coordinator, lib, onboarding, lessComputerIpc, qaCommands, credentialCommands, miscCommands] = await Promise.all([
read('src/pages/settings/CodingAgentSection.tsx'),
read('src/lib/ipc/coding-agent.ts'),
read('src-tauri/src/coding_agent/opencode.rs'),
read('src-tauri/src/coordinator/dictation.rs'),
read('src-tauri/src/coordinator.rs'),
read('src-tauri/src/lib.rs'),
read('src/components/Onboarding.tsx'),
read('src/lib/ipc/less-computer.ts'),
read('src-tauri/src/commands/qa.rs'),
read('src-tauri/src/commands/credentials.rs'),
read('src-tauri/src/commands/misc.rs'),
]);
assert(
settings.includes('codingAgentListOpencodeModels(exe, true)'),
'OpenCode settings must automatically refresh the available models after detection',
);
assert(
settings.includes('settings.codingAgent.opencodeModelsRefresh'),
'OpenCode settings must expose the localized manual refresh action',
);
assert(
ipc.includes('"coding_agent_list_opencode_models"'),
'frontend IPC must call the OpenCode model-list command',
);
assert(opencode.includes('"--refresh"'), 'OpenCode model discovery must refresh its model cache');
assert(opencode.includes('"--auto"'), 'OpenCode runs must use the current automatic permission flag');
assert(opencode.includes('"--continue"'), 'OpenCode runs must preserve follow-up session context');
assert(
!opencode.includes('--dangerously-skip-permissions'),
'OpenCode adapter must not use the removed Claude-style permission flag',
);
assert(
dictation.includes('resolve_coding_agent_model(provider'),
'Less Computer must resolve model defaults per provider',
);
const microphoneMenuStart = lib.indexOf('fn build_microphone_tray_menu');
const microphoneMenuEnd = lib.indexOf('pub(crate) fn refresh_tray_microphone_menu');
const microphoneMenu = lib.slice(microphoneMenuStart, microphoneMenuEnd);
assert(
microphoneMenu.includes('TrayMicrophoneDeviceCache'),
'tray menu construction must read the asynchronous microphone-device cache',
);
assert(
!microphoneMenu.includes('recorder::list_input_devices'),
'tray menu construction must never enumerate CoreAudio devices on the AppKit main thread',
);
assert(
lib.includes('.name("openless-tray-mic-event".into())'),
'native microphone notifications must dispatch enumeration to a background thread',
);
assert(
credentialCommands.includes('pub async fn get_credentials()') &&
credentialCommands.includes('tauri::async_runtime::spawn_blocking'),
'Keychain reads must not block the AppKit main thread while settings load',
);
assert(
miscCommands.includes('pub async fn list_microphone_devices()') &&
miscCommands.includes('microphone device worker failed'),
'settings microphone enumeration must not block the AppKit main thread',
);
assert(
onboarding.includes("t('onboarding.continueToSettings')"),
'users must be able to configure OpenCode without granting unrelated system permissions first',
);
assert(
settings.includes('lessComputerWindowOpen()') &&
lessComputerIpc.includes('"less_computer_window_open"') &&
qaCommands.includes('window.label() != "main"'),
'Advanced settings must expose a main-window-only text entry point for Less Computer',
);
const showLessComputerStart = lib.indexOf('pub(crate) fn show_less_computer_window');
const showLessComputerEnd = lib.indexOf('pub(crate) fn hide_less_computer_window');
const showLessComputer = lib.slice(showLessComputerStart, showLessComputerEnd);
assert(
showLessComputer.includes('window_clone.show()'),
'the first lazy Less Computer window must clear Tauri visible=false before ordering its NSPanel',
);
assert(
showLessComputer.indexOf('run_on_main_thread') <
showLessComputer.indexOf('position_less_computer_window(&window_clone)'),
'Less Computer NSPanel positioning must run on the AppKit main thread',
);
const submitTextStart = coordinator.indexOf('pub fn less_computer_submit_text');
const submitTextEnd = coordinator.indexOf('pub fn history', submitTextStart);
const submitText = coordinator.slice(submitTextStart, submitTextEnd);
assert(
submitText.includes('tauri::async_runtime::spawn') && !submitText.includes('tokio::spawn'),
'Less Computer text submit must spawn through the Tauri runtime from the WebKit IPC thread',
);
const localeFiles = ['zh-CN.ts', 'zh-TW.ts', 'en.ts', 'ja.ts', 'ko.ts'];
const localizedKeys = [
'opencodeModelDefault',
'opencodeModelHint',
'opencodeModelsRefresh',
'opencodeModelsRefreshing',
'opencodeModelsLoaded',
'opencodeModelsEmpty',
'opencodeModelsError',
'openPanel',
'openPanelHint',
'openPanelAction',
];
for (const localeFile of localeFiles) {
const locale = await read(`src/i18n/${localeFile}`);
assert(locale.includes('continueToSettings:'), `${localeFile} is missing continueToSettings`);
for (const key of localizedKeys) {
assert(locale.includes(`${key}:`), `${localeFile} is missing ${key}`);
}
}
console.log('less-computer-opencode-contract.test.mjs passed');