diff --git a/packages/uix-host-react/src/components/Extensible.test.tsx b/packages/uix-host-react/src/components/Extensible.test.tsx
index e0c46f9..b40c752 100644
--- a/packages/uix-host-react/src/components/Extensible.test.tsx
+++ b/packages/uix-host-react/src/components/Extensible.test.tsx
@@ -104,7 +104,7 @@ describe("Extensible", () => {
});
});
- it("should not call unload if host was never created", () => {
+ it("should still create a host when extensions resolve empty, and unload it on unmount", async () => {
const extensionsProvider = jest.fn().mockResolvedValue({});
const { unmount } = render(
@@ -113,11 +113,74 @@ describe("Extensible", () => {
);
+ // The host must exist even with zero extensions, so consumers relying on
+ // host readiness (e.g. useHost/loading state) aren't stuck waiting forever.
+ await waitFor(() => {
+ expect(MockedHost).toHaveBeenCalled();
+ });
+
+ // load() must not be called since there is nothing to load
+ expect(mockLoad).not.toHaveBeenCalled();
+
+ unmount();
+
+ await waitFor(() => {
+ expect(mockUnload).toHaveBeenCalled();
+ });
+ });
+
+ it("should not call unload if the extensions fetch never resolved before unmount", () => {
+ const extensionsProvider = jest
+ .fn()
+ .mockReturnValue(new Promise(() => {}));
+
+ const { unmount } = render(
+
+ Test Child
+
+ );
+
unmount();
// Unload should not be called if host was never created
expect(mockUnload).not.toHaveBeenCalled();
});
+
+ it("reuses the host (does not recreate) once extensions arrive after an initial empty resolve", async () => {
+ const emptyProvider = jest.fn().mockResolvedValue({});
+ const nonEmptyExtensions: InstalledExtensions = {
+ "ext-1": { id: "ext-1", url: "https://example.com/ext1" },
+ };
+ const nonEmptyProvider = jest.fn().mockResolvedValue(nonEmptyExtensions);
+
+ const { rerender } = render(
+
+ Test Child
+
+ );
+
+ // Host is created immediately even though there is nothing to load yet.
+ await waitFor(() => {
+ expect(MockedHost).toHaveBeenCalledTimes(1);
+ });
+ expect(mockLoad).not.toHaveBeenCalled();
+
+ // Extensions arrive later (e.g. a slower registry response, or a
+ // subsequent provider swap). The existing host must be reused, not
+ // torn down and recreated, and load() should now be called.
+ rerender(
+
+ Test Child
+
+ );
+
+ await waitFor(() => {
+ expect(mockLoad).toHaveBeenCalledWith(nonEmptyExtensions, undefined);
+ });
+
+ expect(MockedHost).toHaveBeenCalledTimes(1);
+ expect(mockUnload).not.toHaveBeenCalled();
+ });
});
describe("Old host unloading when creating new host", () => {
diff --git a/packages/uix-host-react/src/components/Extensible.tsx b/packages/uix-host-react/src/components/Extensible.tsx
index c60498b..5bc58d6 100644
--- a/packages/uix-host-react/src/components/Extensible.tsx
+++ b/packages/uix-host-react/src/components/Extensible.tsx
@@ -188,11 +188,16 @@ export function Extensible({
};
}
- if (!extensions || !Object.keys(extensions).length) {
+ if (!extensionListFetched) {
return;
}
+ const hasExtensions = !!extensions && !!Object.keys(extensions).length;
+
const loadExtensions = (hostInstance: Host) => {
+ if (!hasExtensions) {
+ return;
+ }
hostInstance
.load(extensions, guestOptions)
.catch(logError("Load of extensions failed!"));
@@ -220,7 +225,7 @@ export function Extensible({
} else {
loadExtensions(host);
}
- }, [debug, hostName, runtimeContainer, extensions]);
+ }, [debug, hostName, runtimeContainer, extensions, extensionListFetched]);
const contextValue = useMemo(
() => ({ host, extensionListFetched }),