Complete useChange hooks

main
Jackson Morgan 3 weeks ago
parent 1784a2d322
commit 8c63295043
  1. 8
      packages/connected/src/ConnectedLdoTransactionDataset.ts
  2. 2
      packages/connected/src/trackingProxy/TrackingProxyContext.ts
  3. 2
      packages/connected/src/trackingProxy/TrackingSubjectProxy.ts
  4. 1
      packages/connected/src/trackingProxy/createTrackingProxy.ts
  5. 8
      packages/connected/test/mocks/MockConnectedPlugin.ts
  6. 24
      packages/connected/test/mocks/MockResource.ts
  7. 5
      packages/react/src/methods/change/useChangeDataset.ts
  8. 31
      packages/react/src/methods/change/useChangeMatchObject.ts
  9. 33
      packages/react/src/methods/change/useChangeMatchSubject.ts
  10. 3
      packages/react/src/methods/change/useChangeSubject.ts
  11. 2
      packages/react/src/methods/useSubject.ts
  12. 4
      packages/react/temptest/src/App.tsx
  13. 55
      packages/react/test/useLdoForm.test.tsx
  14. 2
      packages/subscribable-dataset/src/util.ts

@ -96,13 +96,6 @@ export class ConnectedLdoTransactionDataset<Plugins extends ConnectedPlugin[]>
this.instanceId = ConnectedLdoTransactionDataset.nextId++;
}
add(quad: Quad): this {
console.log("Adding");
super.add(quad);
console.log(this);
return this;
}
getResource<
Name extends Plugins[number]["name"],
Plugin extends Extract<Plugins[number], { name: Name }>,
@ -231,6 +224,7 @@ export class ConnectedLdoTransactionDataset<Plugins extends ConnectedPlugin[]>
);
// If one has errored, return error
console.log(results);
const errors = (
results.map((result) => result[2]) as (SuccessResult | ErrorResult)[]
).filter((result): result is ErrorResult => result.isError);

@ -41,7 +41,6 @@ export class TrackingProxyContext extends ProxyContext {
listener: nodeEventListener<Quad>,
) {
super(options);
console.log("trackingProxyContextDataset", options.dataset);
this.subscribableDataset = options.dataset;
this.listener = listener;
}
@ -52,7 +51,6 @@ export class TrackingProxyContext extends ProxyContext {
if (!listeners.includes(this.listener)) {
this.subscribableDataset.on(eventName, this.listener);
}
console.log("Added Listener", this.subscribableDataset);
}
protected createNewSubjectProxy(node: NamedNode | BlankNode): SubjectProxy {

@ -24,7 +24,6 @@ export function createTrackingSubjectProxy(
key: string | symbol,
receiver,
) => {
console.log("Should be calling this get function");
const subject = target["@id"];
const rdfTypes = proxyContext.getRdfType(subject);
if (typeof key === "symbol") {
@ -32,7 +31,6 @@ export function createTrackingSubjectProxy(
} else if (key === "@id") {
proxyContext.addListener([subject, null, null, null]);
} else if (!proxyContext.contextUtil.isSet(key, rdfTypes)) {
console.log("Should be registering here!!!!", key);
const predicate = namedNode(
proxyContext.contextUtil.keyToIri(key, rdfTypes),
);

@ -25,7 +25,6 @@ export function createTrackingProxyBuilder<Type extends LdoBase>(
// Rebuild the LdoBuilder from scratch to inject TrackingProxyContext
const contextUtil = new ContextUtil(shapeType.context);
console.log("Creating proxy", dataset);
const proxyContext = new TrackingProxyContext(
{
dataset,

@ -25,13 +25,13 @@ export const mockConnectedPlugin: MockConnectedPlugin = {
getResource: function (
uri: string,
_context: ConnectedContext<MockConnectedPlugin[]>,
context: ConnectedContext<MockConnectedPlugin[]>,
): MockResource {
return new MockResource(uri);
return new MockResource(uri, context);
},
createResource: async function (): Promise<MockResource> {
return new MockResource(v4());
createResource: async function (context): Promise<MockResource> {
return new MockResource(v4(), context);
},
isUriValid: function (uri: string): uri is string {

@ -1,5 +1,6 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import EventEmitter from "events";
import type { ConnectedContext } from "../../src/index.js";
import {
Unfetched,
type ConnectedResult,
@ -9,8 +10,10 @@ import {
} from "../../src/index.js";
import type { DatasetChanges } from "@ldo/rdf-utils";
import type { ReadSuccess } from "../../src/results/success/ReadSuccess.js";
import type { UpdateSuccess } from "../../src/results/success/UpdateSuccess.js";
import { UpdateSuccess } from "../../src/results/success/UpdateSuccess.js";
import { vi } from "vitest";
import type { MockConnectedPlugin } from "./MockConnectedPlugin.js";
import type { Quad } from "@rdfjs/types";
export class MockResource
extends (EventEmitter as new () => ResourceEventEmitter)
@ -21,10 +24,13 @@ export class MockResource
type = "mock" as const;
status: ConnectedResult;
constructor(uri: string) {
protected context: ConnectedContext<MockConnectedPlugin[]>;
constructor(uri: string, context: ConnectedContext<MockConnectedPlugin[]>) {
super();
this.uri = uri;
this.status = new Unfetched(this);
this.context = context;
}
isLoading = vi.fn<() => boolean>();
@ -38,12 +44,14 @@ export class MockResource
read = vi.fn<() => Promise<ReadSuccess<any> | ResourceError<any>>>();
readIfUnfetched =
vi.fn<() => Promise<ReadSuccess<any> | ResourceError<any>>>();
update =
vi.fn<
(
changes: DatasetChanges,
) => Promise<UpdateSuccess<any> | ResourceError<any>>
>();
update = vi.fn<
(
changes: DatasetChanges<Quad>,
) => Promise<UpdateSuccess<any> | ResourceError<any>>
>((changes) => {
this.context.dataset.bulk(changes);
return new UpdateSuccess(this);
});
subscribeToNotifications =
vi.fn<

@ -32,7 +32,6 @@ export function createUseChangeDataset<Plugins extends ConnectedPlugin[]>(
specificDataset?: IConnectedLdoDataset<Plugins>,
): useChangeDatasetReturn<Plugins> {
const transactionDataset = useMemo(() => {
console.log("Uh oh! Getting another dataset!!!");
return (
specificDataset ?? dataset
).startTransaction() as ConnectedLdoTransactionDataset<Plugins>;
@ -48,10 +47,6 @@ export function createUseChangeDataset<Plugins extends ConnectedPlugin[]>(
);
const commitData = useCallback<useChangeCommitData<Plugins>>(() => {
const changes = transactionDataset.getChanges();
console.log("Changes!!!!!");
console.log(changes.added?.toString());
console.log(changes.removed?.toString());
return transactionDataset.commitToRemote();
}, [transactionDataset]);

@ -1,11 +1,12 @@
import { useCallback, useMemo } from "react";
import type { ConnectedLdoDataset, ConnectedPlugin } from "@ldo/connected";
import { createUseChangeDataset } from "./useChangeDataset.js";
import type { LdoBase, LdSet, ShapeType } from "@ldo/ldo";
import { write, type LdoBase, type LdSet, type ShapeType } from "@ldo/ldo";
import type { QuadMatch } from "@ldo/rdf-utils";
import type { UseMatchObjectOptions } from "../useMatchObject.js";
import { createUseMatchObject } from "../useMatchObject.js";
import type { useChangeReturn, useChangeSetData } from "./types.js";
import { createProxyInteractOptions } from "@ldo/jsonld-dataset-proxy";
/**
* @internal
@ -21,7 +22,7 @@ export function createUseChangeMatchObject<Plugins extends ConnectedPlugin[]>(
/**
* Returns a list of matched objects that can be modified and committed
*/
return function useChangeSubject<Type extends LdoBase>(
return function useChangeMatchObject<Type extends LdoBase>(
shapeType: ShapeType<Type>,
subject?: QuadMatch[0] | string,
predicate?: QuadMatch[1] | string,
@ -32,28 +33,34 @@ export function createUseChangeMatchObject<Plugins extends ConnectedPlugin[]>(
options?.dataset,
);
const ldObjects = useMatchObject(shapeType, subject, predicate, graph, {
const ldObject = useMatchObject(shapeType, subject, predicate, graph, {
dataset: transactionDataset,
});
const setData = useCallback<useChangeSetData<LdSet<Type>, Plugins>>(
(writeResource, changer, _other) => {
const setData = useCallback<useChangeSetData<Type, Plugins>>(
(writeResource, changer, otherType?) => {
setDataset((dataset) => {
const ldSet = dataset
.usingType(shapeType)
.write(writeResource.uri)
.matchObject(subject, predicate, graph);
const ldObject = otherType
? write(writeResource.uri).usingCopy(
createProxyInteractOptions("dataset", dataset).usingCopy(
otherType,
)[0],
)[0]
: dataset
.usingType(shapeType)
.write(writeResource.uri)
.matchObject(subject, predicate, graph);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
changer(ldSet);
changer(ldObject);
});
},
[setDataset, subject, predicate, graph, shapeType],
);
return useMemo(
() => [ldObjects, setData, commitData],
[ldObjects, setData, commitData],
() => [ldObject, setData, commitData],
[ldObject, setData, commitData],
);
};
}

@ -1,13 +1,14 @@
import { useCallback, useMemo } from "react";
import type { ConnectedLdoDataset, ConnectedPlugin } from "@ldo/connected";
import { createUseChangeDataset } from "./useChangeDataset.js";
import type { LdoBase, LdSet, ShapeType } from "@ldo/ldo";
import { write, type LdoBase, type LdSet, type ShapeType } from "@ldo/ldo";
import type { QuadMatch } from "@ldo/rdf-utils";
import type { useChangeReturn, useChangeSetData } from "./types.js";
import {
createUseMatchSubject,
type UseMatchSubjectOptions,
} from "../useMatchSubject.js";
import { createProxyInteractOptions } from "@ldo/jsonld-dataset-proxy";
/**
* @internal
@ -23,7 +24,7 @@ export function createUseChangeMatchSubject<Plugins extends ConnectedPlugin[]>(
/**
* Returns a list of matched subjects that can be modified and committed
*/
return function useChangeSubject<Type extends LdoBase>(
return function useChangeMatchSubject<Type extends LdoBase>(
shapeType: ShapeType<Type>,
predicate?: QuadMatch[1] | string,
object?: QuadMatch[2] | string,
@ -34,28 +35,34 @@ export function createUseChangeMatchSubject<Plugins extends ConnectedPlugin[]>(
options?.dataset,
);
const ldSubjects = useMatchSubject(shapeType, predicate, object, graph, {
const ldObject = useMatchSubject(shapeType, predicate, object, graph, {
dataset: transactionDataset,
});
const setData = useCallback<useChangeSetData<LdSet<Type>, Plugins>>(
(writeResource, changer, _other) => {
const setData = useCallback<useChangeSetData<Type, Plugins>>(
(writeResource, changer, otherType?) => {
setDataset((dataset) => {
const ldSet = dataset
.usingType(shapeType)
.write(writeResource.uri)
.matchSubject(predicate, object, graph);
const ldObject = otherType
? write(writeResource.uri).usingCopy(
createProxyInteractOptions("dataset", dataset).usingCopy(
otherType,
)[0],
)[0]
: dataset
.usingType(shapeType)
.write(writeResource.uri)
.matchSubject(predicate, object, graph);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
changer(ldSet);
changer(ldObject);
});
},
[setDataset, object, predicate, graph, shapeType],
[setDataset, predicate, object, graph, shapeType],
);
return useMemo(
() => [ldSubjects, setData, commitData],
[ldSubjects, setData, commitData],
() => [ldObject, setData, commitData],
[ldObject, setData, commitData],
);
};
}

@ -55,7 +55,6 @@ export function createUseChangeSubject<Plugins extends ConnectedPlugin[]>(
const setData = useCallback<useChangeSetData<Type, Plugins>>(
(writeResource, changer, otherType?) => {
console.log("Setting data");
if (!subject) return;
setDataset((dataset) => {
const ldObject = otherType
@ -76,8 +75,6 @@ export function createUseChangeSubject<Plugins extends ConnectedPlugin[]>(
[setDataset, subject, shapeType],
);
console.log("This is ldObject", ldObject);
return useMemo(
() => [ldObject, setData, commitData],
[ldObject, setData, commitData],

@ -50,8 +50,6 @@ export function createUseSubject<Plugins extends ConnectedPlugin[]>(
subject?: string | SubjectNode,
options?: UseSubjectOptions<Plugins>,
): Type | undefined {
console.log("options", options);
const fromSubject = useCallback(
(builder: LdoBuilder<Type>) => {
if (!subject) return;

@ -14,8 +14,6 @@ const FormTest: FunctionComponent = () => {
"Example0",
);
console.log("This is data", data);
return (
<div>
<h1>Form</h1>
@ -24,7 +22,6 @@ const FormTest: FunctionComponent = () => {
onSubmit={async (e) => {
e.preventDefault();
const result = await commitData();
console.log(result);
}}
>
{/* Primary name field */}
@ -33,7 +30,6 @@ const FormTest: FunctionComponent = () => {
value={data?.fn ?? ""}
onChange={(e) => {
setData(randomResource, (profile) => {
console.log("Inside changer");
profile.fn = e.target.value;
})
}}

@ -2,7 +2,7 @@
/// <reference types="@testing-library/jest-dom" />
import { useResource, useChangeSubject, useSubject } from "./mockLdoMethods.js";
import type { FunctionComponent } from "react";
import React from "react";
import React, { useState } from "react";
import { describe, it, expect, beforeEach } from "vitest";
import { render, screen, within } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
@ -15,34 +15,35 @@ import "@testing-library/jest-dom/vitest";
* accessible labels, and committed data shown via useSubject.
*/
const FormTest: FunctionComponent = () => {
const randomResource = useResource("random");
const randomResource = useResource("http://example.com/resource.ttl");
const submittedData = useSubject(SolidProfileShapeShapeType, "Example0");
const [count, setCount] = useState(1);
const [data, setData, commitData] = useChangeSubject(
SolidProfileShapeShapeType,
randomResource,
"Example0",
);
return (
<div>
<h1>Form</h1>
<button onClick={() => setCount(count + 1)}>Rerender {count}</button>
<form
onSubmit={async (e) => {
e.preventDefault();
const result = await commitData();
console.log(result);
await commitData();
}}
>
{/* Primary name field */}
<input
aria-label="Name"
value={data?.fn ?? ""}
onChange={(e) =>
setData((profile) => {
onChange={(e) => {
setData(randomResource, (profile) => {
profile.fn = e.target.value;
})
}
});
}}
/>
{/* Friends */}
@ -53,15 +54,19 @@ const FormTest: FunctionComponent = () => {
aria-label={`Friend name for ${person["@id"]}`}
value={person?.fn ?? ""}
onChange={(e) =>
setData((p) => {
p.fn = e.target.value;
}, person)
setData(
randomResource,
(p) => {
p.fn = e.target.value;
},
person,
)
}
/>
<button
type="button"
onClick={() => {
setData((cData) => {
setData(randomResource, (cData) => {
cData.knows?.delete(person);
});
}}
@ -75,9 +80,9 @@ const FormTest: FunctionComponent = () => {
type="button"
onClick={() => {
// Auto-generate deterministic IDs: Example1, Example2, ...
const count = data?.knows?.size ?? 0;
const friendId = `Example${count + 1}`;
setData((cData) => {
const friendId = `Example${count}`;
setCount(count + 1);
setData(randomResource, (cData) => {
cData.knows?.add({
"@id": friendId,
type: set({ "@id": "Person" }),
@ -89,7 +94,7 @@ const FormTest: FunctionComponent = () => {
Add Friend
</button>
<button type="submit">Submit</button>
<input type="submit" value="Submit" />
</form>
<hr />
@ -190,15 +195,25 @@ describe("useChangeSubject", () => {
expect(screen.queryByTestId("friend-Example2")).not.toBeInTheDocument();
// 5) Submit -> committed data reflects Example0 + Example1 only
await user.click(screen.getByRole("button", { name: /submit/i }));
await user.click(screen.getByRole("button", { name: /Submit/i }));
// Form retained its values
expect(nameInput).toHaveValue("Example0");
expect(friend1Input).toHaveValue("Example1");
expect(screen.queryByTestId("friend-Example2")).not.toBeInTheDocument();
// Committed view updated
expect(submittedName).toHaveTextContent("Name: Example0");
// Wait for the submitted data to appear in the document before asserting
const submittedSection2 = screen.getByRole("region", {
name: /submitted data/i,
});
const submittedName2 =
within(submittedSection2).getByTestId("submitted-name");
// Use `findByText` from the `within` helper to wait for the change
await within(submittedSection2).findByText("Name: Example0");
// Now that we've successfully waited, we can safely make our assertions
expect(submittedName2).toHaveTextContent("Name: Example0");
const itemsAfterSubmit = within(submittedList).getAllByRole("listitem");
expect(itemsAfterSubmit).toHaveLength(1);
expect(itemsAfterSubmit[0]).toHaveTextContent(

@ -11,10 +11,8 @@ export function updateDatasetInBulk<InAndOutQuad extends BaseQuad = BaseQuad>(
dataset: Dataset<InAndOutQuad>,
datasetChanges: DatasetChanges<InAndOutQuad>,
) {
console.log("Update Dataset In Bulk");
// eslint-disable-next-line @typescript-eslint/no-explicit-any
if ((dataset as any).bulk) {
console.log("Updating in bulk");
(dataset as IBulkEditableDataset<InAndOutQuad>).bulk(datasetChanges);
} else {
if (datasetChanges.added) {

Loading…
Cancel
Save