297 lines
11 KiB
TypeScript
297 lines
11 KiB
TypeScript
import { renderToStaticMarkup } from "react-dom/server";
|
|
import { describe, expect, it } from "vitest";
|
|
import type { ComponentPackageStatus as ComponentPackageStatusDto } from "../../domain/types";
|
|
import {
|
|
ComponentPackageStatus,
|
|
ComponentPackageStatusLoading,
|
|
ComponentPackageStatusUnavailable,
|
|
isNumericVersionNewer,
|
|
offlineUpdateDisabledReason,
|
|
} from "./ComponentPackageStatus";
|
|
|
|
function packageStatus(
|
|
overrides: Partial<ComponentPackageStatusDto> = {},
|
|
): ComponentPackageStatusDto {
|
|
return {
|
|
componentId: "proxifyre",
|
|
installedVersion: "2.2.1",
|
|
bundledVersion: "2.2.1",
|
|
availableOfflineVersion: "2.2.1",
|
|
latestKnownVersion: null,
|
|
lastCheckedAt: null,
|
|
freshness: "never_checked",
|
|
updateState: "unknown_offline",
|
|
installSource: "bundled",
|
|
offlinePackageSource: "bundled",
|
|
canInstallOffline: true,
|
|
offlineUnavailableReason: null,
|
|
canDownload: false,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function renderStatus(
|
|
status: ComponentPackageStatusDto,
|
|
uacCancelled = false,
|
|
updateBlockedReason: string | null = null,
|
|
externalBusyReason: string | null = null,
|
|
) {
|
|
return renderToStaticMarkup(
|
|
<ComponentPackageStatus
|
|
status={status}
|
|
busyAction={null}
|
|
updateBlockedReason={updateBlockedReason}
|
|
externalBusyReason={externalBusyReason}
|
|
uacCancelled={uacCancelled}
|
|
onCheck={() => undefined}
|
|
onDownload={() => undefined}
|
|
onUpdate={() => undefined}
|
|
/>,
|
|
);
|
|
}
|
|
|
|
function openingButtonTag(markup: string, label: string) {
|
|
const labelIndex = markup.indexOf(label);
|
|
const start = markup.lastIndexOf("<button", labelIndex);
|
|
return markup.slice(start, markup.indexOf(">", start) + 1);
|
|
}
|
|
|
|
describe("ComponentPackageStatus", () => {
|
|
it("compares only numeric versions for offline update", () => {
|
|
expect(isNumericVersionNewer("2.10.0", "2.9.9")).toBe(true);
|
|
expect(isNumericVersionNewer("2.2.1", "2.2.1.0")).toBe(false);
|
|
expect(isNumericVersionNewer("2.2.0", "2.2.1")).toBe(false);
|
|
expect(isNumericVersionNewer("v2.3.0", "2.2.1")).toBe(false);
|
|
});
|
|
|
|
it("enables offline update only for a newer verified local version", () => {
|
|
const enabledStatus = packageStatus({
|
|
availableOfflineVersion: "2.3.0",
|
|
offlinePackageSource: "cache",
|
|
});
|
|
const enabledMarkup = renderStatus(enabledStatus);
|
|
const disabledMarkup = renderStatus(packageStatus());
|
|
|
|
expect(offlineUpdateDisabledReason(enabledStatus)).toBeNull();
|
|
expect(openingButtonTag(enabledMarkup, "Обновить компонент")).not.toContain(
|
|
"disabled",
|
|
);
|
|
expect(enabledMarkup).toContain("повторно проверяется в сети");
|
|
expect(openingButtonTag(disabledMarkup, "Обновить компонент")).toContain(
|
|
"disabled",
|
|
);
|
|
});
|
|
|
|
it("does not offer the cache-only update action for a bundled package", () => {
|
|
const markup = renderStatus(
|
|
packageStatus({
|
|
updateState: "update_available",
|
|
installedVersion: "2.2.1",
|
|
availableOfflineVersion: "2.4.0",
|
|
offlinePackageSource: "bundled",
|
|
}),
|
|
);
|
|
|
|
expect(markup).toContain("отдельно скачанного и проверенного пакета");
|
|
expect(markup).toContain("Встроенная версия новее");
|
|
expect(openingButtonTag(markup, "Обновить компонент")).toContain(
|
|
"disabled",
|
|
);
|
|
});
|
|
|
|
it("shows local truth without claiming an offline update", () => {
|
|
const markup = renderStatus(packageStatus());
|
|
|
|
expect(markup).toContain("Только локальные данные");
|
|
expect(markup).toContain("это не означает, что компонент устарел");
|
|
expect(markup).toContain("Установлена");
|
|
expect(markup).toContain("Встроена");
|
|
expect(markup).toContain("Последняя известная");
|
|
expect(markup.match(/<button/g)).toHaveLength(3);
|
|
expect(markup).not.toContain(">Установить<");
|
|
expect(markup).not.toContain(">Запустить<");
|
|
expect(markup).not.toContain(">Остановить<");
|
|
});
|
|
|
|
it.each([
|
|
["current", "Актуально", "новая версия не требуется"],
|
|
["update_available", "Есть обновление", "доступна для скачивания"],
|
|
["check_stale", "Проверка устарела", "стоит повторить"],
|
|
] as const)("renders the %s package state", (updateState, label, summary) => {
|
|
const markup = renderStatus(
|
|
packageStatus({
|
|
updateState,
|
|
canDownload: updateState === "update_available",
|
|
}),
|
|
);
|
|
|
|
expect(markup).toContain(label);
|
|
expect(markup).toContain(summary);
|
|
});
|
|
|
|
it("describes an already-downloaded update as local", () => {
|
|
const markup = renderStatus(
|
|
packageStatus({
|
|
updateState: "update_available",
|
|
installedVersion: "2.2.1",
|
|
latestKnownVersion: "2.4.0",
|
|
availableOfflineVersion: "2.4.0",
|
|
offlinePackageSource: "cache",
|
|
canDownload: false,
|
|
}),
|
|
);
|
|
|
|
expect(markup).toContain("сохранено локально");
|
|
expect(markup).toContain("уже сохранено локально");
|
|
expect(markup).not.toContain("Сначала выполни явную проверку");
|
|
});
|
|
|
|
it("does not call an unverified remote version downloadable", () => {
|
|
const markup = renderStatus(
|
|
packageStatus({
|
|
updateState: "update_available",
|
|
latestKnownVersion: "2.4.0",
|
|
availableOfflineVersion: "2.2.1",
|
|
canDownload: false,
|
|
}),
|
|
);
|
|
|
|
expect(markup).toContain("независимая проверка пакета недоступна");
|
|
expect(markup).toContain("Скачивание заблокировано");
|
|
expect(markup).not.toContain("доступна для скачивания");
|
|
});
|
|
|
|
it("keeps a verified local update usable when observed latest differs", () => {
|
|
const markup = renderStatus(
|
|
packageStatus({
|
|
updateState: "update_available",
|
|
installedVersion: "2.2.1",
|
|
availableOfflineVersion: "2.4.0",
|
|
latestKnownVersion: "2.5.0",
|
|
offlinePackageSource: "cache",
|
|
canInstallOffline: true,
|
|
canDownload: false,
|
|
}),
|
|
);
|
|
|
|
expect(markup).toContain("Проверенное локальное обновление готово");
|
|
expect(markup).toContain("обнаруженная в сети версия требует");
|
|
expect(openingButtonTag(markup, "Обновить компонент")).not.toContain(
|
|
"disabled",
|
|
);
|
|
});
|
|
|
|
it("describes both verified local and downloadable newer updates truthfully", () => {
|
|
const markup = renderStatus(
|
|
packageStatus({
|
|
updateState: "update_available",
|
|
installedVersion: "2.2.1",
|
|
availableOfflineVersion: "2.4.0",
|
|
latestKnownVersion: "2.5.0",
|
|
offlinePackageSource: "cache",
|
|
canInstallOffline: true,
|
|
canDownload: true,
|
|
}),
|
|
);
|
|
|
|
expect(markup).toContain("Проверенное локальное обновление готово");
|
|
expect(markup).toContain("более новая проверенная версия доступна");
|
|
expect(markup).not.toContain("требует отдельной проверки");
|
|
expect(openingButtonTag(markup, "Скачать обновление")).not.toContain(
|
|
"disabled",
|
|
);
|
|
expect(openingButtonTag(markup, "Обновить компонент")).not.toContain(
|
|
"disabled",
|
|
);
|
|
});
|
|
|
|
it("does not claim the network was skipped after a fresh incomparable check", () => {
|
|
const markup = renderStatus(
|
|
packageStatus({
|
|
installedVersion: null,
|
|
latestKnownVersion: "2.4.0",
|
|
lastCheckedAt: 1_788_000_000,
|
|
freshness: "fresh",
|
|
updateState: "unknown_offline",
|
|
}),
|
|
);
|
|
|
|
expect(markup).toContain("Проверка выполнена");
|
|
expect(markup).toContain("нельзя подтвердить");
|
|
expect(markup).not.toContain("Сеть не проверялась");
|
|
});
|
|
|
|
it("does not render path-like or fingerprint-like version values", () => {
|
|
const fingerprint = "a".repeat(64);
|
|
const markup = renderStatus(
|
|
packageStatus({
|
|
installedVersion: "C:\\private\\ProxiFyre.exe",
|
|
bundledVersion: fingerprint,
|
|
latestKnownVersion: "https://secret.example/version",
|
|
}),
|
|
);
|
|
|
|
expect(markup).not.toContain("private");
|
|
expect(markup).not.toContain(fingerprint);
|
|
expect(markup).not.toContain("secret.example");
|
|
});
|
|
|
|
it("presents UAC cancellation as neutral preserved state", () => {
|
|
const markup = renderStatus(packageStatus(), true);
|
|
|
|
expect(markup).toContain("Запрос прав администратора отменён");
|
|
expect(markup).toContain("Данные не изменены");
|
|
expect(markup).not.toContain('class="error"');
|
|
});
|
|
|
|
it("blocks component mutation while cutover is nonterminal", () => {
|
|
const markup = renderStatus(
|
|
packageStatus({
|
|
installedVersion: "2.2.1",
|
|
availableOfflineVersion: "2.4.0",
|
|
canInstallOffline: true,
|
|
}),
|
|
false,
|
|
"Сначала заверши перенос компонента.",
|
|
);
|
|
|
|
expect(markup).toContain("Сначала заверши перенос компонента");
|
|
expect(openingButtonTag(markup, "Обновить компонент")).toContain(
|
|
"disabled",
|
|
);
|
|
});
|
|
|
|
it("blocks every package action while another lifecycle action runs", () => {
|
|
const markup = renderStatus(
|
|
packageStatus({ availableOfflineVersion: "2.4.0" }),
|
|
false,
|
|
null,
|
|
"Дождись завершения операции со службой.",
|
|
);
|
|
|
|
expect(markup.match(/<button[^>]*disabled=""/g)).toHaveLength(3);
|
|
expect(markup).toContain("Дождись завершения операции со службой");
|
|
});
|
|
|
|
it("keeps local loading and failure states visible without actions", () => {
|
|
const loading = renderToStaticMarkup(
|
|
<ComponentPackageStatusLoading componentId="proxifyre" />,
|
|
);
|
|
const failed = renderToStaticMarkup(
|
|
<ComponentPackageStatusUnavailable
|
|
componentId="proxifyre"
|
|
error={{
|
|
code: "component_package_status_failed",
|
|
message: "Локальный каталог временно недоступен.",
|
|
details: [],
|
|
}}
|
|
/>,
|
|
);
|
|
|
|
expect(loading).toContain("Читаю только локальный каталог");
|
|
expect(failed).toContain("Установка и обновление оставлены");
|
|
expect(failed).toContain("Локальный каталог временно недоступен");
|
|
expect(`${loading}${failed}`).not.toContain("<button");
|
|
});
|
|
});
|