@uxland/primary-shell 7.37.2 → 7.38.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{component-dvdLH6KG.js → component-CWibLJ86.js} +2 -2
- package/dist/{component-dvdLH6KG.js.map → component-CWibLJ86.js.map} +1 -1
- package/dist/{index-CXxEmHmi.js → index-kl9Zgtus.js} +494 -467
- package/dist/index-kl9Zgtus.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/index.umd.cjs +31 -31
- package/dist/index.umd.cjs.map +1 -1
- package/dist/primary/shell/src/UI/shared-components/primaria-region/primaria-region.d.ts +7 -0
- package/dist/primary/shell/src/api/api.d.ts +2 -0
- package/dist/primary/shell/src/api/exit-guard-manager/exit-guard-manager.d.ts +12 -0
- package/dist/primary/shell/src/api/exit-guard-manager/exit-guard-manager.test.d.ts +1 -0
- package/dist/primary/shell/src/api/plugin-busy-manager/plugin-busy-manager.d.ts +6 -0
- package/dist/primary/shell/src/features/exit/handler.d.ts +0 -1
- package/package.json +1 -1
- package/src/UI/shared-components/primaria-region/primaria-region.ts +17 -4
- package/src/api/api.ts +8 -19
- package/src/api/exit-guard-manager/exit-guard-manager.test.ts +75 -0
- package/src/api/exit-guard-manager/exit-guard-manager.ts +31 -0
- package/src/api/plugin-busy-manager/plugin-busy-manager.ts +6 -0
- package/src/api/region-manager/region-manager.ts +24 -1
- package/src/features/exit/handler.test.ts +20 -52
- package/src/features/exit/handler.ts +2 -16
- package/dist/index-CXxEmHmi.js.map +0 -1
|
@@ -26,6 +26,13 @@ export declare class PrimariaRegion extends PrimariaRegion_base {
|
|
|
26
26
|
* and to generate the container ID.
|
|
27
27
|
*/
|
|
28
28
|
name: string;
|
|
29
|
+
/**
|
|
30
|
+
* Rendering mode for the region.
|
|
31
|
+
* - "multi" (default): all registered views are shown simultaneously (MultipleActiveAdapter).
|
|
32
|
+
* - "single": only one view is shown at a time (SelectableAdapter via primaria-content-switcher).
|
|
33
|
+
* Each plugin activates its own view; the adapter deactivates the rest automatically.
|
|
34
|
+
*/
|
|
35
|
+
mode: "multi" | "single";
|
|
29
36
|
/**
|
|
30
37
|
* Render in light DOM instead of shadow DOM.
|
|
31
38
|
* This allows the region content to be visible in the parent's DOM tree.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ApiFactory, HarmonixApi } from '@uxland/harmonix';
|
|
2
2
|
import { PrimariaBroker } from './broker/primaria-broker';
|
|
3
3
|
import { EcapEventManager } from './ecap-event-manager/ecap-event-manager';
|
|
4
|
+
import { ExitGuardManager } from './exit-guard-manager/exit-guard-manager';
|
|
4
5
|
import { PrimariaGlobalStateManager } from './global-state/global-state';
|
|
5
6
|
import { HttpClient } from './http-client/http-client';
|
|
6
7
|
import { PrimariaInteractionService } from './interaction-service';
|
|
@@ -25,6 +26,7 @@ export interface PrimariaApi extends HarmonixApi {
|
|
|
25
26
|
userManager: UserManager;
|
|
26
27
|
ecapEventManager: EcapEventManager;
|
|
27
28
|
pluginBusyManager: PluginBusyManager;
|
|
29
|
+
exitGuardManager: ExitGuardManager;
|
|
28
30
|
quickActionBusyManager: QuickActionBusyManager;
|
|
29
31
|
pdfViewerManager: PdfViewerManager;
|
|
30
32
|
importDataManager: PrimariaImportDataManager;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type ExitGuardCanDispose = () => Promise<boolean>;
|
|
2
|
+
export declare abstract class ExitGuardManager {
|
|
3
|
+
abstract register(id: string, canDispose: ExitGuardCanDispose): void;
|
|
4
|
+
abstract unregister(id: string): void;
|
|
5
|
+
abstract canExit(): Promise<boolean>;
|
|
6
|
+
}
|
|
7
|
+
export declare class ExitGuardManagerImpl implements ExitGuardManager {
|
|
8
|
+
private guards;
|
|
9
|
+
register(id: string, canDispose: ExitGuardCanDispose): void;
|
|
10
|
+
unregister(id: string): void;
|
|
11
|
+
canExit(): Promise<boolean>;
|
|
12
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -2,6 +2,11 @@ export interface PluginTask {
|
|
|
2
2
|
taskId: string;
|
|
3
3
|
taskDescription: string;
|
|
4
4
|
}
|
|
5
|
+
/**
|
|
6
|
+
* @deprecated Use the `canDispose(api)` plugin lifecycle hook instead. Plugins should
|
|
7
|
+
* decide whether they can be disposed (e.g. show their own confirmation modal) rather
|
|
8
|
+
* than relying on shell-level busy tasks. This API is kept for backwards compatibility.
|
|
9
|
+
*/
|
|
5
10
|
export declare abstract class PluginBusyManager {
|
|
6
11
|
abstract addTask(task: PluginTask): void;
|
|
7
12
|
abstract removeTask(taskId: string): void;
|
|
@@ -9,6 +14,7 @@ export declare abstract class PluginBusyManager {
|
|
|
9
14
|
abstract isBusy(): boolean;
|
|
10
15
|
abstract getTasks(): PluginTask[];
|
|
11
16
|
}
|
|
17
|
+
/** @deprecated See {@link PluginBusyManager}. */
|
|
12
18
|
export declare class PluginBusyManagerImpl implements PluginBusyManager {
|
|
13
19
|
private tasks;
|
|
14
20
|
constructor();
|
package/package.json
CHANGED
|
@@ -31,7 +31,14 @@ export class PrimariaRegion extends PrimariaRegionHost(LitElement) {
|
|
|
31
31
|
*/
|
|
32
32
|
@property({ type: String })
|
|
33
33
|
name = "";
|
|
34
|
-
|
|
34
|
+
/**
|
|
35
|
+
* Rendering mode for the region.
|
|
36
|
+
* - "multi" (default): all registered views are shown simultaneously (MultipleActiveAdapter).
|
|
37
|
+
* - "single": only one view is shown at a time (SelectableAdapter via primaria-content-switcher).
|
|
38
|
+
* Each plugin activates its own view; the adapter deactivates the rest automatically.
|
|
39
|
+
*/
|
|
40
|
+
@property({ type: String })
|
|
41
|
+
mode: "multi" | "single" = "multi";
|
|
35
42
|
/**
|
|
36
43
|
* Render in light DOM instead of shadow DOM.
|
|
37
44
|
* This allows the region content to be visible in the parent's DOM tree.
|
|
@@ -88,10 +95,16 @@ export class PrimariaRegion extends PrimariaRegionHost(LitElement) {
|
|
|
88
95
|
if (this.name) {
|
|
89
96
|
const targetId = `${this.name}-container`;
|
|
90
97
|
|
|
91
|
-
// Create the container
|
|
92
|
-
|
|
98
|
+
// Create the container element based on mode.
|
|
99
|
+
// "single" uses primaria-content-switcher so the region mixin picks up
|
|
100
|
+
// selectableAdapterFactory (already registered in UI bootstrapper), giving
|
|
101
|
+
// SingleActiveAdapter behaviour: activating one view auto-deactivates the rest.
|
|
102
|
+
const container =
|
|
103
|
+
this.mode === "single" ? document.createElement("primaria-content-switcher") : document.createElement("div");
|
|
93
104
|
container.id = targetId;
|
|
94
|
-
|
|
105
|
+
if (this.mode !== "single") {
|
|
106
|
+
(container as HTMLElement).style.cssText = "width: 100%; height: 100%; min-height: 1px";
|
|
107
|
+
}
|
|
95
108
|
this.appendChild(container);
|
|
96
109
|
|
|
97
110
|
// Set the region metadata directly on the instance constructor
|
package/src/api/api.ts
CHANGED
|
@@ -1,15 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ApiFactory,
|
|
3
|
-
HarmonixApi,
|
|
4
|
-
PluginInfo,
|
|
5
|
-
RegionManager,
|
|
6
|
-
createRegionHost,
|
|
7
|
-
createRegionManager,
|
|
8
|
-
} from "@uxland/harmonix";
|
|
1
|
+
import { ApiFactory, HarmonixApi, PluginInfo, RegionManager, createRegionHost, createRegionManager } from "@uxland/harmonix";
|
|
9
2
|
import { primariaShellId } from "../constants";
|
|
10
3
|
import { createBroker } from "./broker/factory";
|
|
11
4
|
import { PrimariaBroker } from "./broker/primaria-broker";
|
|
12
5
|
import { EcapEventManager, createEcapEventManager } from "./ecap-event-manager/ecap-event-manager";
|
|
6
|
+
import { ExitGuardManager, ExitGuardManagerImpl } from "./exit-guard-manager/exit-guard-manager";
|
|
13
7
|
import { PrimariaGlobalStateManager, createGlobalStateManager } from "./global-state/global-state";
|
|
14
8
|
import { HttpClient, createHttpClient } from "./http-client/http-client";
|
|
15
9
|
import { PrimariaInteractionService } from "./interaction-service";
|
|
@@ -18,14 +12,8 @@ import { createLocaleManager } from "./localization/localization";
|
|
|
18
12
|
import { PrimariaNotificationService } from "./notification-service/notification-service";
|
|
19
13
|
import { PrimariaNotificationServiceImpl } from "./notification-service/notification.service-impl";
|
|
20
14
|
import { PdfViewerManager, createPdfViewerManager } from "./pdf-viewer-manager/pdf-viewer-manager";
|
|
21
|
-
import {
|
|
22
|
-
|
|
23
|
-
PluginBusyManagerImpl,
|
|
24
|
-
} from "./plugin-busy-manager/plugin-busy-manager";
|
|
25
|
-
import {
|
|
26
|
-
QuickActionBusyManager,
|
|
27
|
-
QuickActionBusyManagerImpl,
|
|
28
|
-
} from "./quick-action-busy-manager/quick-action-busy-manager";
|
|
15
|
+
import { PluginBusyManager, PluginBusyManagerImpl } from "./plugin-busy-manager/plugin-busy-manager";
|
|
16
|
+
import { QuickActionBusyManager, QuickActionBusyManagerImpl } from "./quick-action-busy-manager/quick-action-busy-manager";
|
|
29
17
|
import { PrimariaRegionManager, createRegionManagerProxy } from "./region-manager/region-manager";
|
|
30
18
|
import { TokenManager, createTokenManager } from "./token-manager/token-manager";
|
|
31
19
|
import { UserManager, createUserManager } from "./user-manager/user-manager";
|
|
@@ -46,6 +34,7 @@ export interface PrimariaApi extends HarmonixApi {
|
|
|
46
34
|
userManager: UserManager;
|
|
47
35
|
ecapEventManager: EcapEventManager;
|
|
48
36
|
pluginBusyManager: PluginBusyManager;
|
|
37
|
+
exitGuardManager: ExitGuardManager;
|
|
49
38
|
quickActionBusyManager: QuickActionBusyManager;
|
|
50
39
|
pdfViewerManager: PdfViewerManager;
|
|
51
40
|
importDataManager: PrimariaImportDataManager;
|
|
@@ -58,6 +47,7 @@ const userManager = createUserManager(tokenManager);
|
|
|
58
47
|
const globalStateManager: PrimariaGlobalStateManager = createGlobalStateManager(broker);
|
|
59
48
|
const contextManager = createContextManager();
|
|
60
49
|
const pluginBusyManager = new PluginBusyManagerImpl();
|
|
50
|
+
const exitGuardManager = new ExitGuardManagerImpl();
|
|
61
51
|
const quickActionBusyManager = new QuickActionBusyManagerImpl(broker);
|
|
62
52
|
const interactionService = new ParimariaInteractionServiceImpl();
|
|
63
53
|
const notificationService = new PrimariaNotificationServiceImpl();
|
|
@@ -71,9 +61,7 @@ const importDataManager = new ImportDataManagerImpl(interactionService);
|
|
|
71
61
|
* @param {PluginInfo} pluginInfo - Information about the plugin
|
|
72
62
|
* @return {PrimariaApi} The created Primaria API instance
|
|
73
63
|
*/
|
|
74
|
-
export const primariaApiFactory: ApiFactory<PrimariaApi> = (
|
|
75
|
-
pluginInfo: PluginInfo,
|
|
76
|
-
): PrimariaApi => {
|
|
64
|
+
export const primariaApiFactory: ApiFactory<PrimariaApi> = (pluginInfo: PluginInfo): PrimariaApi => {
|
|
77
65
|
const regionManagerProxy = createRegionManagerProxy(pluginInfo, regionManager, broker);
|
|
78
66
|
|
|
79
67
|
return {
|
|
@@ -88,6 +76,7 @@ export const primariaApiFactory: ApiFactory<PrimariaApi> = (
|
|
|
88
76
|
userManager,
|
|
89
77
|
ecapEventManager,
|
|
90
78
|
pluginBusyManager,
|
|
79
|
+
exitGuardManager,
|
|
91
80
|
quickActionBusyManager,
|
|
92
81
|
interactionService,
|
|
93
82
|
notificationService,
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
import { ExitGuardManagerImpl } from "./exit-guard-manager";
|
|
3
|
+
|
|
4
|
+
describe("ExitGuardManagerImpl", () => {
|
|
5
|
+
let manager: ExitGuardManagerImpl;
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
manager = new ExitGuardManagerImpl();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("canExit resolves true when no guards are registered", async () => {
|
|
12
|
+
await expect(manager.canExit()).resolves.toBe(true);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("canExit resolves true when every guard returns true", async () => {
|
|
16
|
+
manager.register("a", async () => true);
|
|
17
|
+
manager.register("b", async () => true);
|
|
18
|
+
|
|
19
|
+
await expect(manager.canExit()).resolves.toBe(true);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("canExit resolves false when any guard returns false", async () => {
|
|
23
|
+
manager.register("a", async () => true);
|
|
24
|
+
manager.register("b", async () => false);
|
|
25
|
+
|
|
26
|
+
await expect(manager.canExit()).resolves.toBe(false);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("canExit short-circuits and does not run guards after a false", async () => {
|
|
30
|
+
const guardA = vi.fn(async () => false);
|
|
31
|
+
const guardB = vi.fn(async () => true);
|
|
32
|
+
|
|
33
|
+
manager.register("a", guardA);
|
|
34
|
+
manager.register("b", guardB);
|
|
35
|
+
|
|
36
|
+
await manager.canExit();
|
|
37
|
+
|
|
38
|
+
expect(guardA).toHaveBeenCalled();
|
|
39
|
+
expect(guardB).not.toHaveBeenCalled();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("unregister removes the guard", async () => {
|
|
43
|
+
const guard = vi.fn(async () => false);
|
|
44
|
+
manager.register("a", guard);
|
|
45
|
+
manager.unregister("a");
|
|
46
|
+
|
|
47
|
+
await expect(manager.canExit()).resolves.toBe(true);
|
|
48
|
+
expect(guard).not.toHaveBeenCalled();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("register overwrites an existing guard with the same id", async () => {
|
|
52
|
+
manager.register("a", async () => false);
|
|
53
|
+
manager.register("a", async () => true);
|
|
54
|
+
|
|
55
|
+
await expect(manager.canExit()).resolves.toBe(true);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("swallows errors in guards and continues with the remaining ones", async () => {
|
|
59
|
+
const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
60
|
+
const failing = vi.fn(async () => {
|
|
61
|
+
throw new Error("boom");
|
|
62
|
+
});
|
|
63
|
+
const allowing = vi.fn(async () => true);
|
|
64
|
+
|
|
65
|
+
manager.register("a", failing);
|
|
66
|
+
manager.register("b", allowing);
|
|
67
|
+
|
|
68
|
+
await expect(manager.canExit()).resolves.toBe(true);
|
|
69
|
+
expect(failing).toHaveBeenCalled();
|
|
70
|
+
expect(allowing).toHaveBeenCalled();
|
|
71
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith("Exit guard failed:", expect.any(Error));
|
|
72
|
+
|
|
73
|
+
consoleErrorSpy.mockRestore();
|
|
74
|
+
});
|
|
75
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export type ExitGuardCanDispose = () => Promise<boolean>;
|
|
2
|
+
|
|
3
|
+
export abstract class ExitGuardManager {
|
|
4
|
+
abstract register(id: string, canDispose: ExitGuardCanDispose): void;
|
|
5
|
+
abstract unregister(id: string): void;
|
|
6
|
+
abstract canExit(): Promise<boolean>;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class ExitGuardManagerImpl implements ExitGuardManager {
|
|
10
|
+
private guards = new Map<string, ExitGuardCanDispose>();
|
|
11
|
+
|
|
12
|
+
register(id: string, canDispose: ExitGuardCanDispose): void {
|
|
13
|
+
this.guards.set(id, canDispose);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
unregister(id: string): void {
|
|
17
|
+
this.guards.delete(id);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async canExit(): Promise<boolean> {
|
|
21
|
+
for (const guard of this.guards.values()) {
|
|
22
|
+
try {
|
|
23
|
+
const can = await guard();
|
|
24
|
+
if (!can) return false;
|
|
25
|
+
} catch (e) {
|
|
26
|
+
console.error("Exit guard failed:", e);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -6,6 +6,11 @@ export interface PluginTask {
|
|
|
6
6
|
taskDescription: string;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* @deprecated Use the `canDispose(api)` plugin lifecycle hook instead. Plugins should
|
|
11
|
+
* decide whether they can be disposed (e.g. show their own confirmation modal) rather
|
|
12
|
+
* than relying on shell-level busy tasks. This API is kept for backwards compatibility.
|
|
13
|
+
*/
|
|
9
14
|
export abstract class PluginBusyManager {
|
|
10
15
|
abstract addTask(task: PluginTask): void;
|
|
11
16
|
abstract removeTask(taskId: string): void;
|
|
@@ -14,6 +19,7 @@ export abstract class PluginBusyManager {
|
|
|
14
19
|
abstract getTasks(): PluginTask[];
|
|
15
20
|
}
|
|
16
21
|
|
|
22
|
+
/** @deprecated See {@link PluginBusyManager}. */
|
|
17
23
|
export class PluginBusyManagerImpl implements PluginBusyManager {
|
|
18
24
|
private tasks: PluginTask[] = [];
|
|
19
25
|
|
|
@@ -39,7 +39,10 @@ class RegionManagerProxy implements PrimariaRegionManager {
|
|
|
39
39
|
* @return {Promise<void>} A promise that resolves when the view is successfully registered.
|
|
40
40
|
*/
|
|
41
41
|
registerView(regionName: string, view: HarmonixViewDefinition): Promise<void> {
|
|
42
|
-
|
|
42
|
+
const key = `${this.pluginInfo.pluginId}::${view.id}`;
|
|
43
|
+
|
|
44
|
+
this.regionManager.registerViewWithRegion(regionName, key, view);
|
|
45
|
+
this.activateIfDefault(regionName, key, view);
|
|
43
46
|
return Promise.resolve();
|
|
44
47
|
}
|
|
45
48
|
/**
|
|
@@ -131,6 +134,26 @@ class RegionManagerProxy implements PrimariaRegionManager {
|
|
|
131
134
|
return mainView?.id;
|
|
132
135
|
}
|
|
133
136
|
|
|
137
|
+
/**
|
|
138
|
+
* Activates a view if it is marked as default and the region currently has no active views.
|
|
139
|
+
*
|
|
140
|
+
* @param {string} regionName - The name of the region to activate the view in.
|
|
141
|
+
* @param {string} key - The key of the view to activate.
|
|
142
|
+
* @param {HarmonixViewDefinition} view - The view to activate.
|
|
143
|
+
* @return {void}
|
|
144
|
+
*/
|
|
145
|
+
activateIfDefault(regionName: string, key: string, view: HarmonixViewDefinition) {
|
|
146
|
+
if (!(view as any).isDefault) return;
|
|
147
|
+
|
|
148
|
+
const region = this.regionManager.getRegion(regionName);
|
|
149
|
+
if (!region || typeof (region as any).activate !== "function") return;
|
|
150
|
+
|
|
151
|
+
const hasActiveView = region.currentActiveViews?.length > 0;
|
|
152
|
+
if (!hasActiveView) {
|
|
153
|
+
(region as any).activate(key);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
134
157
|
_notifyMainViewChanged(viewId: string) {
|
|
135
158
|
this.broker.publish(BROKER_EVENTS.shell.mainViewChanged, { viewId });
|
|
136
159
|
}
|
|
@@ -3,9 +3,7 @@ import { ExitShellHandler } from "./handler";
|
|
|
3
3
|
import { ExitShell } from "./request";
|
|
4
4
|
import { disposePlugins } from "../../handle-plugins";
|
|
5
5
|
import { disposeShell, raiseCloseEvent, raiseCustomCloseEvent } from "../../disposer";
|
|
6
|
-
import { PluginBusyTask } from "../../api/plugin-busy-manager/plugin-busy-manager";
|
|
7
6
|
import { PrimariaApi } from "../../api/api";
|
|
8
|
-
import { PluginBusyList } from "../../api/plugin-busy-manager/plugin-busy-list/component";
|
|
9
7
|
|
|
10
8
|
vi.mock("../../handle-plugins", () => ({
|
|
11
9
|
disposePlugins: vi.fn(),
|
|
@@ -19,11 +17,8 @@ vi.mock("../../disposer", () => ({
|
|
|
19
17
|
|
|
20
18
|
const createMockApi = (): PrimariaApi =>
|
|
21
19
|
({
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
},
|
|
25
|
-
interactionService: {
|
|
26
|
-
confirm: vi.fn(),
|
|
20
|
+
exitGuardManager: {
|
|
21
|
+
canExit: vi.fn(),
|
|
27
22
|
},
|
|
28
23
|
notificationService: {
|
|
29
24
|
error: vi.fn(),
|
|
@@ -40,21 +35,22 @@ describe("ExitShellHandler", () => {
|
|
|
40
35
|
vi.clearAllMocks();
|
|
41
36
|
});
|
|
42
37
|
|
|
43
|
-
it("
|
|
38
|
+
it("disposes and raises custom close event when canExit resolves true and message has ecapEvent", async () => {
|
|
44
39
|
const message = { ecapEvent: {} } as ExitShell;
|
|
45
|
-
mockApi.
|
|
40
|
+
(mockApi.exitGuardManager.canExit as any).mockResolvedValue(true);
|
|
46
41
|
(disposePlugins as any).mockResolvedValue(undefined);
|
|
47
42
|
|
|
48
43
|
await handler.handle(message);
|
|
49
44
|
|
|
45
|
+
expect(mockApi.exitGuardManager.canExit).toHaveBeenCalled();
|
|
50
46
|
expect(disposePlugins).toHaveBeenCalled();
|
|
51
47
|
expect(disposeShell).toHaveBeenCalled();
|
|
52
48
|
expect(raiseCustomCloseEvent).toHaveBeenCalledWith(message);
|
|
53
49
|
});
|
|
54
50
|
|
|
55
|
-
it("
|
|
51
|
+
it("raises default close event when no ecapEvent is present", async () => {
|
|
56
52
|
const message = {} as ExitShell;
|
|
57
|
-
mockApi.
|
|
53
|
+
(mockApi.exitGuardManager.canExit as any).mockResolvedValue(true);
|
|
58
54
|
(disposePlugins as any).mockResolvedValue(undefined);
|
|
59
55
|
|
|
60
56
|
await handler.handle(message);
|
|
@@ -65,50 +61,23 @@ describe("ExitShellHandler", () => {
|
|
|
65
61
|
expect(raiseCustomCloseEvent).not.toHaveBeenCalled();
|
|
66
62
|
});
|
|
67
63
|
|
|
68
|
-
it("
|
|
69
|
-
const busyTasks: PluginBusyTask[] = [{ pluginId: "x" }] as any;
|
|
70
|
-
const message = { ecapEvent: {} } as ExitShell;
|
|
71
|
-
mockApi.pluginBusyManager.getTasks = vi.fn().mockReturnValue(busyTasks);
|
|
72
|
-
mockApi.interactionService.confirm = vi.fn().mockResolvedValue({ confirmed: true });
|
|
73
|
-
(disposePlugins as any).mockResolvedValue(undefined);
|
|
74
|
-
|
|
75
|
-
await handler.handle(message);
|
|
76
|
-
|
|
77
|
-
expect(mockApi.interactionService.confirm).toHaveBeenCalledWith(
|
|
78
|
-
{ busyTasks },
|
|
79
|
-
{ component: PluginBusyList },
|
|
80
|
-
{
|
|
81
|
-
title: "actions.askExit",
|
|
82
|
-
state: "error",
|
|
83
|
-
confirmButtonText: "Sí",
|
|
84
|
-
cancelButtonText: "No",
|
|
85
|
-
},
|
|
86
|
-
);
|
|
87
|
-
expect(disposePlugins).toHaveBeenCalled();
|
|
88
|
-
expect(disposeShell).toHaveBeenCalled();
|
|
89
|
-
expect(raiseCustomCloseEvent).toHaveBeenCalledWith(message);
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
it("should not continue if confirmation is declined", async () => {
|
|
93
|
-
const busyTasks: PluginBusyTask[] = [{ pluginId: "x" }] as any;
|
|
64
|
+
it("aborts the exit when any guard returns false", async () => {
|
|
94
65
|
const message = { ecapEvent: {} } as ExitShell;
|
|
95
|
-
mockApi.
|
|
96
|
-
mockApi.interactionService.confirm = vi.fn().mockResolvedValue({ confirmed: false });
|
|
66
|
+
(mockApi.exitGuardManager.canExit as any).mockResolvedValue(false);
|
|
97
67
|
|
|
98
68
|
await handler.handle(message);
|
|
99
69
|
|
|
100
|
-
expect(mockApi.
|
|
70
|
+
expect(mockApi.exitGuardManager.canExit).toHaveBeenCalled();
|
|
101
71
|
expect(disposePlugins).not.toHaveBeenCalled();
|
|
102
72
|
expect(disposeShell).not.toHaveBeenCalled();
|
|
103
73
|
expect(raiseCustomCloseEvent).not.toHaveBeenCalled();
|
|
104
74
|
expect(raiseCloseEvent).not.toHaveBeenCalled();
|
|
105
75
|
});
|
|
106
76
|
|
|
107
|
-
it("
|
|
108
|
-
const error = new Error("Something went wrong");
|
|
77
|
+
it("notifies and raises custom close event when dispose throws and ecapEvent is present", async () => {
|
|
109
78
|
const message = { ecapEvent: {} } as ExitShell;
|
|
110
|
-
mockApi.
|
|
111
|
-
(disposePlugins as any).mockRejectedValue(
|
|
79
|
+
(mockApi.exitGuardManager.canExit as any).mockResolvedValue(true);
|
|
80
|
+
(disposePlugins as any).mockRejectedValue(new Error("boom"));
|
|
112
81
|
|
|
113
82
|
await handler.handle(message);
|
|
114
83
|
|
|
@@ -117,11 +86,10 @@ describe("ExitShellHandler", () => {
|
|
|
117
86
|
expect(raiseCloseEvent).not.toHaveBeenCalled();
|
|
118
87
|
});
|
|
119
88
|
|
|
120
|
-
it("
|
|
121
|
-
const error = new Error("Something went wrong");
|
|
89
|
+
it("notifies and raises default close event when dispose throws and no ecapEvent", async () => {
|
|
122
90
|
const message = {} as ExitShell;
|
|
123
|
-
mockApi.
|
|
124
|
-
(disposePlugins as any).mockRejectedValue(
|
|
91
|
+
(mockApi.exitGuardManager.canExit as any).mockResolvedValue(true);
|
|
92
|
+
(disposePlugins as any).mockRejectedValue(new Error("boom"));
|
|
125
93
|
|
|
126
94
|
await handler.handle(message);
|
|
127
95
|
|
|
@@ -130,9 +98,9 @@ describe("ExitShellHandler", () => {
|
|
|
130
98
|
expect(raiseCustomCloseEvent).not.toHaveBeenCalled();
|
|
131
99
|
});
|
|
132
100
|
|
|
133
|
-
it("
|
|
101
|
+
it("proceeds if disposePlugins exceeds the 10s timeout", async () => {
|
|
134
102
|
const message = { ecapEvent: {} } as ExitShell;
|
|
135
|
-
mockApi.
|
|
103
|
+
(mockApi.exitGuardManager.canExit as any).mockResolvedValue(true);
|
|
136
104
|
(disposePlugins as any).mockImplementation(() => new Promise((resolve) => setTimeout(resolve, 11000)));
|
|
137
105
|
|
|
138
106
|
await handler.handle(message);
|
|
@@ -142,8 +110,8 @@ describe("ExitShellHandler", () => {
|
|
|
142
110
|
expect(raiseCustomCloseEvent).toHaveBeenCalledWith(message);
|
|
143
111
|
}, 15000);
|
|
144
112
|
|
|
145
|
-
it("
|
|
146
|
-
mockApi.
|
|
113
|
+
it("raises raiseCloseEvent when no exitEvent is passed", async () => {
|
|
114
|
+
(mockApi.exitGuardManager.canExit as any).mockResolvedValue(true);
|
|
147
115
|
(disposePlugins as any).mockResolvedValue(undefined);
|
|
148
116
|
|
|
149
117
|
await handler.handle(undefined as any);
|
|
@@ -4,8 +4,6 @@ import { inject } from "inversify";
|
|
|
4
4
|
import { ExitShell } from "./request";
|
|
5
5
|
import { disposeShell, raiseCloseEvent, raiseCustomCloseEvent } from "../../disposer";
|
|
6
6
|
import { disposePlugins } from "../../handle-plugins";
|
|
7
|
-
import { PluginBusyTask } from "../../api/plugin-busy-manager/plugin-busy-manager";
|
|
8
|
-
import { PluginBusyList } from "../../api/plugin-busy-manager/plugin-busy-list/component";
|
|
9
7
|
import { translate } from "../../locales";
|
|
10
8
|
|
|
11
9
|
export class ExitShellHandler {
|
|
@@ -14,11 +12,8 @@ export class ExitShellHandler {
|
|
|
14
12
|
const evt = exitEvent && exitEvent.ecapEvent !== undefined ? exitEvent : undefined;
|
|
15
13
|
|
|
16
14
|
try {
|
|
17
|
-
const
|
|
18
|
-
if (
|
|
19
|
-
const { confirmed } = await this.askForClose(busyTasks);
|
|
20
|
-
if (!confirmed) return;
|
|
21
|
-
}
|
|
15
|
+
const canExit = await this.api.exitGuardManager.canExit();
|
|
16
|
+
if (!canExit) return;
|
|
22
17
|
|
|
23
18
|
// Per si un plugin tarda molt en fer dispose, màxim deixarem 5 segons, per no interrompre el tancar infinitament
|
|
24
19
|
await Promise.race([
|
|
@@ -33,15 +28,6 @@ export class ExitShellHandler {
|
|
|
33
28
|
}
|
|
34
29
|
}
|
|
35
30
|
|
|
36
|
-
private askForClose(busyTasks: PluginBusyTask[]) {
|
|
37
|
-
return this.api.interactionService.confirm({ busyTasks }, {component: PluginBusyList}, {
|
|
38
|
-
title: translate("actions.askExit"),
|
|
39
|
-
state: "error",
|
|
40
|
-
confirmButtonText: "Sí",
|
|
41
|
-
cancelButtonText: "No",
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
|
|
45
31
|
private timeout(ms: number) {
|
|
46
32
|
return new Promise<void>((resolve) => setTimeout(resolve, ms));
|
|
47
33
|
}
|