mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-30 07:05:24 +00:00
* feat: shell controller, TabStage, layout and theme plugins with repair view * feat: move the classic layout into its own bundled plugin * feat: move color themes into a bundled themes plugin * fix: plugin tests and type checks work with a linked sdk * fix: home tab, layout switch and missing layout fallbacks for looks * chore: add classic layout and themes to the readme * chore: drop example plugin links from the plugin guide * feat: plugin filters by type, paged browse, hide plugins needing newer termix * feat: move desktop, status, import, audit and branding features into plugins * feat: host lock api so a plugin can load hosts from a config file (#1581) * chore: add hosts config to the plugin table * feat: login policy hook for plugins and optional https redirect (#1584) * chore: add login security to the plugin table * feat: api key socket tickets so a master can open remote sessions (#1654) * feat: sync moves to a plugin, desktop gets multiple accounts (#1580, #1654) * chore: add sync to the plugin table * feat: move the sync screens and wizard out of core into the plugin (#1580) * fix: add a neutral linked desktop hook for plugins (#1580) * fix: show the linked account's name and admin flag in the app (#1580) * feat: ssh transport providers, core ssh relay and plugin folders api * feat: support FIDO2 security keys through ssh agent * feat: add companion panels beside host tabs * feat: mTLS client certificates for desktop server links * feat: native web view for plugins in the desktop app * fix: detect the desktop link window by Electron's default user agent Fixes #1675 * fix: changelog for desktop link fix * fix: keep text primary keys in the plugin data export * fix: ship sharp binaries for Windows arm64 builds * feat: client cert on the sync link types (sdk 1.0.18) * chore: update translations * feat: themed title bar for the desktop app (#1690) * chore: changelog for the themed title bar * fix: draw the title bar in the sidebar color * feat: per host layout for the terminal panel bar * chore: sdk 1.0.19 for the panel bar layout * chore: bundle all 43 plugins for 26.10.3 * chore: update translations --------- Co-authored-by: SirHephaistos <60616553+SirHephaistos@users.noreply.github.com> Co-authored-by: Giovanny Aranda <garanda21@gmail.com>
167 lines
4.9 KiB
TypeScript
167 lines
4.9 KiB
TypeScript
/**
|
|
* Shared vitest setup for core and for every plugin suite.
|
|
*
|
|
* The root vitest.setup.ts re-exports this so there is one copy of the
|
|
* polyfills rather than two that drift apart.
|
|
*/
|
|
|
|
import { afterEach, vi } from "vitest";
|
|
import { installSafePorts } from "./safe-ports.js";
|
|
|
|
installSafePorts();
|
|
|
|
// Radix measures some controls (Checkbox, Switch) and jsdom has no observer.
|
|
if (typeof globalThis.ResizeObserver === "undefined") {
|
|
globalThis.ResizeObserver = class {
|
|
observe() {}
|
|
unobserve() {}
|
|
disconnect() {}
|
|
} as unknown as typeof ResizeObserver;
|
|
}
|
|
|
|
if (typeof window !== "undefined" && !window.matchMedia) {
|
|
window.matchMedia = vi.fn().mockImplementation((query: string) => ({
|
|
matches: false,
|
|
media: query,
|
|
onchange: null,
|
|
addEventListener: vi.fn(),
|
|
removeEventListener: vi.fn(),
|
|
addListener: vi.fn(),
|
|
removeListener: vi.fn(),
|
|
dispatchEvent: vi.fn(),
|
|
}));
|
|
}
|
|
|
|
/**
|
|
* jsdom in this project ships without a `localStorage` global, and Node only
|
|
* provides one when started with --localstorage-file (which persists to disk
|
|
* and is shared across test files -- both wrong for tests). Suites that touch
|
|
* storage therefore threw `Cannot read properties of undefined`, taking out
|
|
* every test in the file rather than just the ones that used it.
|
|
*
|
|
* A per-process in-memory implementation is enough: it satisfies the Storage
|
|
* interface the app uses and keeps nothing on disk.
|
|
*/
|
|
if (typeof globalThis.localStorage === "undefined") {
|
|
const createMemoryStorage = (): Storage => {
|
|
const store = new Map<string, string>();
|
|
return {
|
|
get length() {
|
|
return store.size;
|
|
},
|
|
key: (index: number) => Array.from(store.keys())[index] ?? null,
|
|
getItem: (key: string) => (store.has(key) ? store.get(key)! : null),
|
|
setItem: (key: string, value: string) => {
|
|
store.set(key, String(value));
|
|
},
|
|
removeItem: (key: string) => {
|
|
store.delete(key);
|
|
},
|
|
clear: () => {
|
|
store.clear();
|
|
},
|
|
} as Storage;
|
|
};
|
|
|
|
Object.defineProperty(globalThis, "localStorage", {
|
|
value: createMemoryStorage(),
|
|
configurable: true,
|
|
writable: true,
|
|
});
|
|
Object.defineProperty(globalThis, "sessionStorage", {
|
|
value: createMemoryStorage(),
|
|
configurable: true,
|
|
writable: true,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* A plain plugin host for components a test renders on its own: keys come
|
|
* back as text, every permission is held, and nothing reaches a server.
|
|
* renderWithApp installs Termix's real host instead, which takes precedence.
|
|
*/
|
|
const noop = () => {};
|
|
const offline = () =>
|
|
Promise.reject(new Error("No plugin API in a unit test; mock it"));
|
|
(globalThis as Record<string, unknown>).__termixTestPluginHost = {
|
|
usePluginId: () => "test-plugin",
|
|
useDocsUrl: () => null,
|
|
useTranslation: () => ({
|
|
t: (key: string) => key,
|
|
language: "en",
|
|
}),
|
|
usePermission: () => true,
|
|
useSettings: () => ({ values: {}, loaded: true, save: async () => {} }),
|
|
useHost: () => null,
|
|
useHosts: () => ({ hosts: [], loaded: true }),
|
|
useHostStatus: () => null,
|
|
useCurrentUser: () => null,
|
|
useTheme: () => ({ theme: "dark" }),
|
|
useShell: () => null,
|
|
toast: { success: noop, error: noop, info: noop, warning: noop },
|
|
getApi: () => ({
|
|
get: offline,
|
|
delete: offline,
|
|
post: offline,
|
|
put: offline,
|
|
patch: offline,
|
|
}),
|
|
useTabs: () => ({
|
|
openTab: noop,
|
|
openSingletonTab: noop,
|
|
connectHost: noop,
|
|
closeTab: noop,
|
|
getLayout: () => null,
|
|
applyLayout: async () => ({ skipped: [] }),
|
|
onChange: () => noop,
|
|
onReady: () => noop,
|
|
}),
|
|
invokeAction: async () => undefined,
|
|
useSshAuthTypes: () => ({
|
|
types: ["password", "key", "credential", "agent", "none"].map((type) => ({
|
|
type,
|
|
labelKey: type,
|
|
pluginId: "core",
|
|
credentialType: type === "password" || type === "key",
|
|
supportsBackground: type !== "none",
|
|
quickConnect: true,
|
|
})),
|
|
loaded: true,
|
|
}),
|
|
useSlotContributions: () => [],
|
|
usePluginComponent: () => undefined,
|
|
getApiFor: () => ({
|
|
get: offline,
|
|
delete: offline,
|
|
post: offline,
|
|
put: offline,
|
|
patch: offline,
|
|
}),
|
|
hostProtocols: () => ["ssh"],
|
|
useHostActions: () => [],
|
|
useCompanions: () => [],
|
|
useActivityTypes: () => [],
|
|
activityTarget: () => undefined,
|
|
useExtensions: () => [],
|
|
getExtension: () => undefined,
|
|
usePluginUiPreferences: () => ({ values: {}, set: noop }),
|
|
core: {
|
|
logActivity: async () => {},
|
|
getHostPassword: async () => null,
|
|
patchOpenTab: async () => {},
|
|
getCustomKeybindings: async () => [],
|
|
runKeybindingAction: () => false,
|
|
getClientPreference: () => undefined,
|
|
setClientPreference: () => {},
|
|
listHosts: async () => [],
|
|
listCredentials: async () => [],
|
|
notifyHostsChanged: () => {},
|
|
getHostStatusColorScheme: () => "accent",
|
|
getLocalAuthToken: () => null,
|
|
},
|
|
};
|
|
|
|
afterEach(() => {
|
|
vi.restoreAllMocks();
|
|
});
|