From 1f9388871d1937beaa6990a9af4d3ac2c03456d9 Mon Sep 17 00:00:00 2001 From: Jackson Morgan Date: Sat, 4 Jan 2025 22:34:49 -0500 Subject: [PATCH] Match subject and object are now available as hooks --- packages/solid-react/src/index.ts | 2 + packages/solid-react/src/useMatchObject.ts | 21 +++++ packages/solid-react/src/useMatchSubject.ts | 21 +++++ packages/solid-react/src/useSubject.ts | 59 +++---------- .../solid-react/src/util/useTrackingProxy.ts | 55 +++++++++++++ .../solid-react/test/Integration.test.tsx | 82 +++++++++++++++++++ packages/solid-react/test/setUpServer.ts | 4 +- 7 files changed, 195 insertions(+), 49 deletions(-) create mode 100644 packages/solid-react/src/useMatchObject.ts create mode 100644 packages/solid-react/src/useMatchSubject.ts create mode 100644 packages/solid-react/src/util/useTrackingProxy.ts diff --git a/packages/solid-react/src/index.ts b/packages/solid-react/src/index.ts index 85c0a69..edbcc74 100644 --- a/packages/solid-react/src/index.ts +++ b/packages/solid-react/src/index.ts @@ -7,4 +7,6 @@ export { useLdo } from "./SolidLdoProvider"; // hooks export * from "./useResource"; export * from "./useSubject"; +export * from "./useMatchSubject"; +export * from "./useMatchObject"; export * from "./useRootContainer"; diff --git a/packages/solid-react/src/useMatchObject.ts b/packages/solid-react/src/useMatchObject.ts new file mode 100644 index 0000000..53d7980 --- /dev/null +++ b/packages/solid-react/src/useMatchObject.ts @@ -0,0 +1,21 @@ +import type { LdoBase, ShapeType } from "@ldo/ldo"; +import type { QuadMatch } from "@ldo/rdf-utils"; +import type { LdoBuilder } from "@ldo/ldo"; +import { useCallback } from "react"; +import { useTrackingProxy } from "./util/useTrackingProxy"; + +export function useMatchObject( + shapeType: ShapeType, + subject?: QuadMatch[0] | string, + predicate?: QuadMatch[1] | string, + graph?: QuadMatch[3] | string, +): Type[] { + const matchObject = useCallback( + (builder: LdoBuilder) => { + return builder.matchObject(subject, predicate, graph); + }, + [subject, predicate, graph], + ); + + return useTrackingProxy(shapeType, matchObject); +} diff --git a/packages/solid-react/src/useMatchSubject.ts b/packages/solid-react/src/useMatchSubject.ts new file mode 100644 index 0000000..80c1d81 --- /dev/null +++ b/packages/solid-react/src/useMatchSubject.ts @@ -0,0 +1,21 @@ +import type { LdoBase, ShapeType } from "@ldo/ldo"; +import type { QuadMatch } from "@ldo/rdf-utils"; +import type { LdoBuilder } from "@ldo/ldo"; +import { useCallback } from "react"; +import { useTrackingProxy } from "./util/useTrackingProxy"; + +export function useMatchSubject( + shapeType: ShapeType, + predicate?: QuadMatch[1] | string, + object?: QuadMatch[2] | string, + graph?: QuadMatch[3] | string, +): Type[] { + const matchSubject = useCallback( + (builder: LdoBuilder) => { + return builder.matchSubject(predicate, object, graph); + }, + [predicate, object, graph], + ); + + return useTrackingProxy(shapeType, matchSubject); +} diff --git a/packages/solid-react/src/useSubject.ts b/packages/solid-react/src/useSubject.ts index bfb69ef..c728c6f 100644 --- a/packages/solid-react/src/useSubject.ts +++ b/packages/solid-react/src/useSubject.ts @@ -1,15 +1,10 @@ import type { SubjectNode } from "@ldo/rdf-utils"; -import { - ContextUtil, - JsonldDatasetProxyBuilder, -} from "@ldo/jsonld-dataset-proxy"; import type { ShapeType } from "@ldo/ldo"; -import { LdoBuilder } from "@ldo/ldo"; +import type { LdoBuilder } from "@ldo/ldo"; import type { LdoBase } from "@ldo/ldo"; -import { useLdo } from "./SolidLdoProvider"; -import { useCallback, useEffect, useMemo, useState } from "react"; -import { TrackingProxyContext } from "./util/TrackingProxyContext"; -import { defaultGraph } from "@rdfjs/data-model"; +import { useCallback } from "react"; + +import { useTrackingProxy } from "./util/useTrackingProxy"; export function useSubject( shapeType: ShapeType, @@ -23,45 +18,13 @@ export function useSubject( shapeType: ShapeType, subject?: string | SubjectNode, ): Type | undefined { - const { dataset } = useLdo(); - - const [forceUpdateCounter, setForceUpdateCounter] = useState(0); - const forceUpdate = useCallback( - () => setForceUpdateCounter((val) => val + 1), - [], + const fromSubject = useCallback( + (builder: LdoBuilder) => { + if (!subject) return; + return builder.fromSubject(subject); + }, + [subject], ); - // The main linked data object - const linkedDataObject = useMemo(() => { - if (!subject) return; - - // Remove all current subscriptions - dataset.removeListenerFromAllEvents(forceUpdate); - - // Rebuild the LdoBuilder from scratch to inject TrackingProxyContext - const contextUtil = new ContextUtil(shapeType.context); - const proxyContext = new TrackingProxyContext( - { - dataset, - contextUtil, - writeGraphs: [defaultGraph()], - languageOrdering: ["none", "en", "other"], - }, - forceUpdate, - ); - const builder = new LdoBuilder( - new JsonldDatasetProxyBuilder(proxyContext), - shapeType, - ); - return builder.fromSubject(subject); - }, [shapeType, subject, dataset, forceUpdateCounter, forceUpdate]); - - useEffect(() => { - // Unregister force update listener upon unmount - return () => { - dataset.removeListenerFromAllEvents(forceUpdate); - }; - }, [shapeType, subject]); - - return linkedDataObject; + return useTrackingProxy(shapeType, fromSubject); } diff --git a/packages/solid-react/src/util/useTrackingProxy.ts b/packages/solid-react/src/util/useTrackingProxy.ts new file mode 100644 index 0000000..05fa452 --- /dev/null +++ b/packages/solid-react/src/util/useTrackingProxy.ts @@ -0,0 +1,55 @@ +import { + ContextUtil, + JsonldDatasetProxyBuilder, +} from "@ldo/jsonld-dataset-proxy"; +import { LdoBuilder } from "@ldo/ldo"; +import type { LdoBase, ShapeType } from "@ldo/ldo"; +import { useCallback, useEffect, useMemo, useState } from "react"; +import { TrackingProxyContext } from "./TrackingProxyContext"; +import { defaultGraph } from "@rdfjs/data-model"; +import { useLdo } from "../SolidLdoProvider"; + +export function useTrackingProxy( + shapeType: ShapeType, + createLdo: (builder: LdoBuilder) => ReturnType, +): ReturnType { + const { dataset } = useLdo(); + + const [forceUpdateCounter, setForceUpdateCounter] = useState(0); + const forceUpdate = useCallback( + () => setForceUpdateCounter((val) => val + 1), + [], + ); + + // The main linked data object + const linkedDataObject = useMemo(() => { + // Remove all current subscriptions + dataset.removeListenerFromAllEvents(forceUpdate); + + // Rebuild the LdoBuilder from scratch to inject TrackingProxyContext + const contextUtil = new ContextUtil(shapeType.context); + const proxyContext = new TrackingProxyContext( + { + dataset, + contextUtil, + writeGraphs: [defaultGraph()], + languageOrdering: ["none", "en", "other"], + }, + forceUpdate, + ); + const builder = new LdoBuilder( + new JsonldDatasetProxyBuilder(proxyContext), + shapeType, + ); + return createLdo(builder); + }, [shapeType, dataset, forceUpdateCounter, forceUpdate, createLdo]); + + useEffect(() => { + // Unregister force update listener upon unmount + return () => { + dataset.removeListenerFromAllEvents(forceUpdate); + }; + }, [shapeType]); + + return linkedDataObject; +} diff --git a/packages/solid-react/test/Integration.test.tsx b/packages/solid-react/test/Integration.test.tsx index 8c8ae0b..d4d47dd 100644 --- a/packages/solid-react/test/Integration.test.tsx +++ b/packages/solid-react/test/Integration.test.tsx @@ -14,6 +14,8 @@ import { useLdo } from "../src/SolidLdoProvider"; import { PostShShapeType } from "./.ldo/post.shapeTypes"; import type { PostSh } from "./.ldo/post.typings"; import { useSubject } from "../src/useSubject"; +import { useMatchSubject } from "../src/useMatchSubject"; +import { useMatchObject } from "../src/useMatchObject"; // Use an increased timeout, since the CSS server takes too much setup time. jest.setTimeout(40_000); @@ -428,4 +430,84 @@ describe("Integration Tests", () => { }); }); }); + + /** + * =========================================================================== + * useMatchSubject + * =========================================================================== + */ + describe("useMatchSubject", () => { + it("returns an array of matched subjects", async () => { + const UseMatchSubjectTest: FunctionComponent = () => { + const resource = useResource(SAMPLE_DATA_URI); + const posts = useMatchSubject( + PostShShapeType, + "http://www.w3.org/1999/02/22-rdf-syntax-ns#type", + "http://schema.org/CreativeWork", + ); + if (resource.isLoading()) return

loading

; + + return ( +
+
    + {posts.map((post) => { + return
  • {post["@id"]}
  • ; + })} +
+
+ ); + }; + render( + + + , + ); + + const list = await screen.findByRole("list"); + expect(list.children[0].innerHTML).toBe( + "http://localhost:3001/example/test_ldo/sample.ttl#Post1", + ); + expect(list.children[1].innerHTML).toBe( + "http://localhost:3001/example/test_ldo/sample.ttl#Post2", + ); + }); + }); + + /** + * =========================================================================== + * useMatchObject + * =========================================================================== + */ + describe("useMatchObject", () => { + it("returns an array of matched objects", async () => { + const UseMatchObjectTest: FunctionComponent = () => { + const resource = useResource(SAMPLE_DATA_URI); + const publishers = useMatchObject( + PostShShapeType, + "http://localhost:3001/example/test_ldo/sample.ttl#Post1", + "http://schema.org/publisher", + ); + if (resource.isLoading()) return

loading

; + + return ( +
+
    + {publishers.map((publisher) => { + return
  • {publisher["@id"]}
  • ; + })} +
+
+ ); + }; + render( + + + , + ); + + const list = await screen.findByRole("list"); + expect(list.children[0].innerHTML).toBe("https://example.com/Publisher1"); + expect(list.children[1].innerHTML).toBe("https://example.com/Publisher2"); + }); + }); }); diff --git a/packages/solid-react/test/setUpServer.ts b/packages/solid-react/test/setUpServer.ts index 0551ccf..61be170 100644 --- a/packages/solid-react/test/setUpServer.ts +++ b/packages/solid-react/test/setUpServer.ts @@ -23,7 +23,9 @@ export const EXAMPLE_POST_TTL = `@prefix schema: . a schema:CreativeWork, schema:Thing, schema:SocialMediaPosting ; schema:image ; schema:articleBody "test" ; - schema:publisher , .`; + schema:publisher , . +<#Post2> + a schema:CreativeWork, schema:Thing, schema:SocialMediaPosting .`; export const TEST_CONTAINER_TTL = `@prefix dc: . @prefix ldp: . @prefix posix: .