Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions src/components/shared/Dialogs/ComponentDetailsDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useSuspenseQuery } from "@tanstack/react-query";
import { Code, InfoIcon, ListFilter } from "lucide-react";
import { type ReactNode, useCallback, useMemo, useState } from "react";

Expand All @@ -13,7 +14,8 @@ import { Icon } from "@/components/ui/icon";
import { BlockStack, InlineStack } from "@/components/ui/layout";
import { Skeleton } from "@/components/ui/skeleton";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { useHydrateComponentReference } from "@/hooks/useHydrateComponentReference";
import { useGuaranteedHydrateComponentReference } from "@/hooks/useHydrateComponentReference";
import { useComponentLibrary } from "@/providers/ComponentLibraryProvider/ComponentLibraryProvider";
import type { ComponentReference } from "@/utils/componentSpec";

import InfoIconButton from "../Buttons/InfoIconButton";
Expand Down Expand Up @@ -74,7 +76,14 @@ const ComponentDetailsDialogContent = withSuspenseWrapper(
"remote-component-library-search",
);

const componentRef = useHydrateComponentReference(component);
const componentRef = useGuaranteedHydrateComponentReference(component);
const { getComponentLibrary } = useComponentLibrary();
const userComponentsLibrary = getComponentLibrary("user_components");

const { data: isUserComponent } = useSuspenseQuery({
queryKey: ["is-user-component", componentRef.digest],
queryFn: () => userComponentsLibrary.hasComponent(componentRef),
});

if (!componentRef) {
return (
Expand All @@ -87,7 +96,7 @@ const ComponentDetailsDialogContent = withSuspenseWrapper(
const { url, spec: componentSpec, digest: componentDigest } = componentRef;

const hasPublishSection =
remoteComponentLibrarySearchEnabled && component.owned;
remoteComponentLibrarySearchEnabled && isUserComponent;

return (
<>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ const createMockComponentLibraryContext = (
searchComponentLibrary: vi.fn(),
addToComponentLibrary: vi.fn(),
removeFromComponentLibrary: vi.fn(),
checkIfUserComponent: vi.fn().mockReturnValue(false),
getComponentLibrary: vi.fn(),
};
};
Expand Down
24 changes: 13 additions & 11 deletions src/components/shared/FavoriteComponentToggle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -142,27 +142,29 @@ const FavoriteToggleButton = withSuspenseWrapper(
);

const useComponentFlags = (component: ComponentReference) => {
const { checkIfUserComponent, getComponentLibrary } = useComponentLibrary();
const { getComponentLibrary } = useComponentLibrary();
const componentLibrary = getComponentLibrary("standard_components");
const userComponentsLibrary = getComponentLibrary("user_components");

const isUserComponent = useMemo(
() => checkIfUserComponent(component),
[component, checkIfUserComponent],
);

const { data: isInLibrary } = useSuspenseQuery({
const { data } = useSuspenseQuery({
queryKey: ["component", "flags", component.digest],
queryFn: async () => {
if (!componentLibrary) return false;
if (!componentLibrary)
return { isInLibrary: false, isUserComponent: false };

if (isUserComponent) return true;
const isUserComponent =
await userComponentsLibrary.hasComponent(component);

return componentLibrary.hasComponent(component);
return {
isInLibrary:
isUserComponent || (await componentLibrary.hasComponent(component)),
isUserComponent,
};
},
staleTime: 10 * MINUTES,
});

return { isInLibrary, isUserComponent };
return data;
};

const ComponentFavoriteToggleInternal = ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,15 +127,11 @@ function ComponentLibrarySection() {
useComponentLibrary();

const favoriteComponentsLibrary = getComponentLibrary("favorite_components");
const userComponentsLibrary = getComponentLibrary("user_components");

const { updateSearchFilter } = useForcedSearchContext();
const {
usedComponentsFolder,
userComponentsFolder,
isLoading,
error,
searchResult,
} = useComponentLibrary();
const { usedComponentsFolder, isLoading, error, searchResult } =
useComponentLibrary();

const standardComponentsLibrary = getComponentLibrary("standard_components");

Expand Down Expand Up @@ -163,10 +159,6 @@ function ComponentLibrarySection() {
usedComponentsFolder?.components &&
usedComponentsFolder.components.length > 0;

const hasUserComponents =
userComponentsFolder?.components &&
userComponentsFolder.components.length > 0;

return (
<BlockStack gap="2">
{remoteComponentLibrarySearchEnabled && <UpgradeAvailableAlertBox />}
Expand All @@ -186,13 +178,11 @@ function ComponentLibrarySection() {
icon="Star"
/>

{hasUserComponents && (
<FolderItem
key="my-components-folder"
folder={userComponentsFolder}
icon="Puzzle"
/>
)}
<LibraryFolderItem
key="my-components-library-folder"
library={userComponentsLibrary}
icon="Puzzle"
/>
<Separator />
<FolderItem
key="graph-inputs-outputs-folder"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@ import yaml from "js-yaml";
import { type ReactNode } from "react";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";

import type {
ComponentFolder,
ComponentLibrary,
} from "@/types/componentLibrary";
import type { ComponentFolder } from "@/types/componentLibrary";
import type {
ComponentReference,
ComponentSpec,
Expand Down Expand Up @@ -43,30 +40,20 @@ vi.mock("./componentLibrary");
import * as componentLibraryUtils from "@/providers/ComponentLibraryProvider/componentLibrary";
import * as componentStore from "@/utils/componentStore";
import * as getComponentName from "@/utils/getComponentName";
import * as localforage from "@/utils/localforage";

// Mock implementations

const mockFetchUserComponents = vi.mocked(
componentLibraryUtils.fetchUserComponents,
);
const mockFetchUsedComponents = vi.mocked(
componentLibraryUtils.fetchUsedComponents,
);
const mockPopulateComponentRefs = vi.mocked(
componentLibraryUtils.populateComponentRefs,
);
const mockFlattenFolders = vi.mocked(componentLibraryUtils.flattenFolders);
const mockFilterToUniqueByDigest = vi.mocked(
componentLibraryUtils.filterToUniqueByDigest,
);
const mockImportComponent = vi.mocked(componentStore.importComponent);
const mockDeleteComponentFileFromList = vi.mocked(
componentStore.deleteComponentFileFromList,
);
const mockGetUserComponentByName = vi.mocked(
localforage.getUserComponentByName,
);

const mockGetComponentName = vi.mocked(getComponentName.getComponentName);

describe("ComponentLibraryProvider - Component Management", () => {
Expand All @@ -79,36 +66,6 @@ describe("ComponentLibraryProvider - Component Management", () => {
},
};

const mockComponentLibrary: ComponentLibrary = {
folders: [
{
name: "Test Folder",
components: [
{
name: "test-component",
digest: "test-digest-1",
url: "https://example.com/component1.yaml",
spec: mockComponentSpec,
},
],
folders: [],
},
],
};

const mockUserComponentsFolder: ComponentFolder = {
name: "User Components",
components: [
{
name: "user-component",
digest: "user-digest-1",
spec: mockComponentSpec,
text: "test yaml content",
},
],
folders: [],
};

const createWrapper = ({ children }: { children: ReactNode }) => {
const queryClient = new QueryClient({
defaultOptions: {
Expand All @@ -131,13 +88,12 @@ describe("ComponentLibraryProvider - Component Management", () => {
componentDuplicateDialogProps.handleImportComponent = undefined;

// Setup default mock implementations
mockFetchUserComponents.mockResolvedValue(mockUserComponentsFolder);
mockFetchUsedComponents.mockReturnValue({
name: "Used Components",
components: [],
folders: [],
});
mockPopulateComponentRefs.mockImplementation((lib) => Promise.resolve(lib));

mockFlattenFolders.mockImplementation((folder) => {
if ("folders" in folder) {
return folder.folders?.flatMap((f) => f.components || []) || [];
Expand Down Expand Up @@ -207,16 +163,11 @@ describe("ComponentLibraryProvider - Component Management", () => {

// Mock that there's an existing component with the same name
mockFlattenFolders.mockReturnValue([existingComponent]);
mockGetUserComponentByName.mockResolvedValue(mockUserComponent);

const { result } = renderHook(() => useComponentLibrary(), {
wrapper: createWrapper,
});

await waitFor(() => {
expect(result.current.isLoading).toBe(false);
});

await act(async () => {
void result.current.addToComponentLibrary(newComponent);
});
Expand Down Expand Up @@ -481,51 +432,4 @@ describe("ComponentLibraryProvider - Component Management", () => {
consoleSpy.mockRestore();
});
});

describe("Component Checks", () => {
it("should correctly identify user components", async () => {
const userComponent: ComponentReference = {
name: "user-component",
digest: "user-digest-1",
spec: mockComponentSpec,
};

mockFlattenFolders.mockReturnValue([userComponent]);
mockFilterToUniqueByDigest.mockReturnValue([userComponent]);

const { result } = renderHook(() => useComponentLibrary(), {
wrapper: createWrapper,
});

await waitFor(() => {
expect(result.current.isLoading).toBe(false);
});

const isUserComponent =
result.current.checkIfUserComponent(userComponent);
expect(isUserComponent).toBe(true);
});

it("should correctly identify non-user components", async () => {
const standardComponent: ComponentReference = {
name: "standard-component",
digest: "standard-digest",
spec: mockComponentSpec,
};

mockFlattenFolders.mockReturnValue([]); // No user components

const { result } = renderHook(() => useComponentLibrary(), {
wrapper: createWrapper,
});

await waitFor(() => {
expect(result.current.isLoading).toBe(false);
});

const isUserComponent =
result.current.checkIfUserComponent(standardComponent);
expect(isUserComponent).toBe(false);
});
});
});
Loading