mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-11-03 06:48:18 +00:00
150 lines
6.3 KiB
TypeScript
150 lines
6.3 KiB
TypeScript
import { EVENT_PLUGIN_UNLOADED } from "@vrtmrz/livesync-commonlib/compat/events/coreEvents";
|
|
import type {
|
|
ComponentHasResult,
|
|
DialogHostProps,
|
|
SvelteDialogManagerDependencies,
|
|
} from "@vrtmrz/livesync-commonlib/compat/services/implements/base/SvelteDialog";
|
|
import { createServiceContext } from "@vrtmrz/livesync-commonlib/context";
|
|
import type { Component } from "svelte";
|
|
import { describe, expect, it, vi } from "vitest";
|
|
|
|
import {
|
|
SvelteDialogSession,
|
|
type SvelteDialogRenderer,
|
|
type SvelteDialogSurface,
|
|
} from "@/modules/services/SvelteDialogSession";
|
|
|
|
describe("SvelteDialogSession", () => {
|
|
it("mounts, resolves the selected result, and unmounts through the owning surface", async () => {
|
|
const context = createServiceContext();
|
|
const replaceChildren = vi.fn();
|
|
const setTitle = vi.fn();
|
|
const close = vi.fn();
|
|
const mounted = {};
|
|
const unmount = vi.fn().mockResolvedValue(undefined);
|
|
let mountedProps: DialogHostProps<string, string> | undefined;
|
|
const renderer: SvelteDialogRenderer = {
|
|
mount(_dialogHost, _target, props) {
|
|
mountedProps = props as DialogHostProps<string, string>;
|
|
return mounted as never;
|
|
},
|
|
unmount,
|
|
};
|
|
const session = new SvelteDialogSession({
|
|
surface: {
|
|
contentEl: { replaceChildren } as unknown as HTMLElement,
|
|
close,
|
|
setTitle,
|
|
},
|
|
context,
|
|
dependencies: {} as SvelteDialogManagerDependencies<typeof context>,
|
|
dialogHost: (() => {}) as unknown as Component<DialogHostProps>,
|
|
component: (() => {}) as unknown as ComponentHasResult<string, string>,
|
|
initialData: "initial",
|
|
renderer,
|
|
});
|
|
|
|
session.onOpen();
|
|
const result = session.waitForClose();
|
|
mountedProps?.setTitle("Dialogue title");
|
|
mountedProps?.setResult("selected");
|
|
session.onClose();
|
|
|
|
await expect(result).resolves.toBe("selected");
|
|
expect(replaceChildren).toHaveBeenCalledOnce();
|
|
expect(setTitle).toHaveBeenCalledWith("Dialogue title");
|
|
const getInitialData = mountedProps?.getInitialData;
|
|
expect(getInitialData).toBeTypeOf("function");
|
|
expect(getInitialData?.()).toBe("initial");
|
|
await vi.waitFor(() => expect(unmount).toHaveBeenCalledWith(mounted));
|
|
});
|
|
|
|
it("rejects the pending result and closes the surface when the owning context unloads", async () => {
|
|
const context = createServiceContext();
|
|
const close = vi.fn();
|
|
const session = new SvelteDialogSession({
|
|
surface: {
|
|
contentEl: { replaceChildren: vi.fn() } as unknown as HTMLElement,
|
|
close,
|
|
setTitle: vi.fn(),
|
|
},
|
|
context,
|
|
dependencies: {} as SvelteDialogManagerDependencies<typeof context>,
|
|
dialogHost: (() => {}) as unknown as Component<DialogHostProps>,
|
|
component: (() => {}) as unknown as ComponentHasResult<string, string>,
|
|
renderer: {
|
|
mount: () => ({}) as never,
|
|
unmount: vi.fn().mockResolvedValue(undefined),
|
|
},
|
|
});
|
|
|
|
session.onOpen();
|
|
const result = session.waitForClose();
|
|
context.events.emitEvent(EVENT_PLUGIN_UNLOADED);
|
|
|
|
await expect(result).rejects.toThrow("Plug-in unloaded");
|
|
expect(close).toHaveBeenCalledOnce();
|
|
});
|
|
});
|
|
|
|
describe("SvelteDialogSession owner signal", () => {
|
|
it("rejects and releases the mounted dialogue when its owner aborts", async () => {
|
|
const context = createServiceContext();
|
|
const controller = new AbortController();
|
|
const lifecycle = { signal: controller.signal };
|
|
const unmount = vi.fn().mockResolvedValue(undefined);
|
|
let session: SvelteDialogSession<string, string, typeof context>;
|
|
const close = vi.fn(() => session.onClose());
|
|
session = new SvelteDialogSession({
|
|
...lifecycle,
|
|
surface: {
|
|
contentEl: { replaceChildren: vi.fn() } as unknown as HTMLElement,
|
|
close,
|
|
setTitle: vi.fn(),
|
|
},
|
|
context,
|
|
dependencies: {} as SvelteDialogManagerDependencies<typeof context>,
|
|
dialogHost: (() => {}) as unknown as Component<DialogHostProps>,
|
|
component: (() => {}) as unknown as ComponentHasResult<string, string>,
|
|
renderer: { mount: () => ({}) as never, unmount },
|
|
});
|
|
session.onOpen();
|
|
let observed: unknown = "pending";
|
|
void session.waitForClose().catch((error) => {
|
|
observed = error;
|
|
});
|
|
const reason = new Error("fixture owner ended");
|
|
controller.abort(reason);
|
|
await vi.waitFor(() => expect(observed).toBe(reason), { timeout: 1000, interval: 20 });
|
|
expect(close).toHaveBeenCalledOnce();
|
|
expect(unmount).toHaveBeenCalledOnce();
|
|
context.events.emitEvent(EVENT_PLUGIN_UNLOADED);
|
|
expect(close).toHaveBeenCalledOnce();
|
|
});
|
|
|
|
it("detaches an owner signal when normal close has already settled the result", async () => {
|
|
const context = createServiceContext();
|
|
const controller = new AbortController();
|
|
const remove = vi.spyOn(controller.signal, "removeEventListener");
|
|
const lifecycle = { signal: controller.signal };
|
|
const close = vi.fn();
|
|
const session = new SvelteDialogSession({
|
|
...lifecycle,
|
|
surface: { contentEl: { replaceChildren: vi.fn() } as unknown as HTMLElement, close, setTitle: vi.fn() },
|
|
context,
|
|
dependencies: {} as SvelteDialogManagerDependencies<typeof context>,
|
|
dialogHost: (() => {}) as unknown as Component<DialogHostProps>,
|
|
component: (() => {}) as unknown as ComponentHasResult<string, string>,
|
|
renderer: { mount: () => ({}) as never, unmount: vi.fn().mockResolvedValue(undefined) },
|
|
});
|
|
session.onOpen();
|
|
const result = session.waitForClose();
|
|
session.onClose();
|
|
await expect(result).resolves.toBeUndefined();
|
|
expect(remove).toHaveBeenCalledWith("abort", expect.any(Function));
|
|
controller.abort();
|
|
context.events.emitEvent(EVENT_PLUGIN_UNLOADED);
|
|
expect(close).not.toHaveBeenCalled();
|
|
});
|
|
});
|