init solid-type-index package

main
Jackson Morgan 8 months ago
parent 1f9388871d
commit 6915dcadce
  1. 3
      packages/solid-type-index/.eslintrc
  2. 1
      packages/solid-type-index/.gitignore
  3. 21
      packages/solid-type-index/LICENSE.txt
  4. 14
      packages/solid-type-index/README.md
  5. 6
      packages/solid-type-index/jest.config.js
  6. 2
      packages/solid-type-index/jest.setup.ts
  7. 50
      packages/solid-type-index/package.json
  8. 19
      packages/solid-type-index/src/.ldo/profile.context.ts
  9. 64
      packages/solid-type-index/src/.ldo/profile.schema.ts
  10. 19
      packages/solid-type-index/src/.ldo/profile.shapeTypes.ts
  11. 27
      packages/solid-type-index/src/.ldo/profile.typings.ts
  12. 47
      packages/solid-type-index/src/.ldo/typeIndex.context.ts
  13. 144
      packages/solid-type-index/src/.ldo/typeIndex.schema.ts
  14. 28
      packages/solid-type-index/src/.ldo/typeIndex.shapeTypes.ts
  15. 58
      packages/solid-type-index/src/.ldo/typeIndex.typings.ts
  16. 10
      packages/solid-type-index/src/.shapes/profile.shex
  17. 23
      packages/solid-type-index/src/.shapes/typeIndex.shex
  18. 0
      packages/solid-type-index/src/index.ts
  19. 0
      packages/solid-type-index/src/react/useAddToTypeIndex.ts
  20. 0
      packages/solid-type-index/src/react/useProfile.ts
  21. 0
      packages/solid-type-index/src/react/useRemoveFromTypeIndex.ts
  22. 0
      packages/solid-type-index/src/react/useType.ts
  23. 32
      packages/solid-type-index/test/.ldo/post.context.ts
  24. 155
      packages/solid-type-index/test/.ldo/post.schema.ts
  25. 19
      packages/solid-type-index/test/.ldo/post.shapeTypes.ts
  26. 45
      packages/solid-type-index/test/.ldo/post.typings.ts
  27. 431
      packages/solid-type-index/test/Integration.test.tsx
  28. 110
      packages/solid-type-index/test/setUpServer.ts
  29. 52
      packages/solid-type-index/test/test-server/configs/components-config/unauthenticatedServer.json
  30. 9
      packages/solid-type-index/test/test-server/configs/solid-css-seed.json
  31. 13
      packages/solid-type-index/test/test-server/configs/template/wac/.acl.hbs
  32. 19
      packages/solid-type-index/test/test-server/configs/template/wac/profile/card.acl.hbs
  33. 7
      packages/solid-type-index/test/test-server/runServer.ts
  34. 39
      packages/solid-type-index/test/test-server/solidServer.helper.ts
  35. 8
      packages/solid-type-index/tsconfig.build.json

@ -0,0 +1,3 @@
{
"extends": ["../../.eslintrc"]
}

@ -0,0 +1 @@
test/test-server/data

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 Jackson Morgan
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

@ -0,0 +1,14 @@
# @ldo/solid-type-index
Alpha
// TODO: Write readme
## Sponsorship
This project was made possible by a grant from NGI Zero Entrust via nlnet. Learn more on the [NLnet project page](https://nlnet.nl/project/SolidUsableApps/).
[<img src="https://nlnet.nl/logo/banner.png" alt="nlnet foundation logo" width="300" />](https://nlnet.nl/)
[<img src="https://nlnet.nl/image/logos/NGI0Entrust_tag.svg" alt="NGI Zero Entrust Logo" width="300" />](https://nlnet.nl/)
## Liscense
MIT

@ -0,0 +1,6 @@
const sharedConfig = require("../../jest.config.js");
module.exports = {
...sharedConfig,
rootDir: "./",
testEnvironment: "jsdom",
};

@ -0,0 +1,2 @@
import "@inrupt/jest-jsdom-polyfills";
globalThis.fetch = async () => new Response();

@ -0,0 +1,50 @@
{
"name": "@ldo/solid-type-index",
"version": "0.0.1-alpha.28",
"description": "Solid Type Index support for LDO",
"main": "dist/index.js",
"scripts": {
"build": "tsc --project tsconfig.build.json",
"watch": "tsc --watch",
"test": "npm run test:integration",
"test:watch": "jest --watch",
"prepublishOnly": "npm run test && npm run build",
"build:ldo": "ldo build --input src/.shapes --output src/.ldo",
"lint": "eslint src/** --fix --no-error-on-unmatched-pattern",
"test:integration": "start-server-and-test start-test-server http://localhost:3003 start-integration-test",
"start-test-server": "ts-node ./test/test-server/runServer.ts",
"start-integration-test": "jest --coverage"
},
"repository": {
"type": "git",
"url": "git+https://github.com/o-development/ldobjects.git"
},
"author": "Jackson Morgan",
"license": "MIT",
"bugs": {
"url": "https://github.com/o-development/ldobjects/issues"
},
"homepage": "https://github.com/o-development/ldobjects/tree/main/packages/solid-react#readme",
"devDependencies": {
"@ldo/rdf-utils": "^0.0.1-alpha.24",
"@rdfjs/types": "^1.0.1",
"@testing-library/react": "^14.1.2",
"@types/jest": "^27.0.3",
"jest-environment-jsdom": "^27.0.0",
"start-server-and-test": "^2.0.3",
"ts-jest": "^27.1.2",
"ts-node": "^10.9.2"
},
"dependencies": {
"@ldo/solid": "^0.0.1-alpha.28",
"@ldo/solid-react": "^0.0.1-alpha.28"
},
"files": [
"dist",
"src"
],
"publishConfig": {
"access": "public"
},
"gitHead": "c63f055aab22155b60a5fdee4172979b9c287dfa"
}

@ -0,0 +1,19 @@
import { ContextDefinition } from "jsonld";
/**
* =============================================================================
* profileContext: JSONLD Context for profile
* =============================================================================
*/
export const profileContext: ContextDefinition = {
privateTypeIndex: {
"@id": "http://www.w3.org/ns/solid/terms#privateTypeIndex",
"@type": "@id",
"@isCollection": true,
},
publicTypeIndex: {
"@id": "http://www.w3.org/ns/solid/terms#publicTypeIndex",
"@type": "@id",
"@isCollection": true,
},
};

@ -0,0 +1,64 @@
import { Schema } from "shexj";
/**
* =============================================================================
* profileSchema: ShexJ Schema for profile
* =============================================================================
*/
export const profileSchema: Schema = {
type: "Schema",
shapes: [
{
id: "https://shaperepo.com/schemas/solidProfile#TypeIndexProfile",
type: "ShapeDecl",
shapeExpr: {
type: "Shape",
expression: {
type: "EachOf",
expressions: [
{
type: "TripleConstraint",
predicate: "http://www.w3.org/ns/solid/terms#privateTypeIndex",
valueExpr: {
type: "NodeConstraint",
nodeKind: "iri",
},
min: 0,
max: -1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value:
"A registry of all types used on the user's Pod (for private access only)",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/ns/solid/terms#publicTypeIndex",
valueExpr: {
type: "NodeConstraint",
nodeKind: "iri",
},
min: 0,
max: -1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value:
"A registry of all types used on the user's Pod (for public access)",
},
},
],
},
],
},
},
},
],
};

@ -0,0 +1,19 @@
import { ShapeType } from "@ldo/ldo";
import { profileSchema } from "./profile.schema";
import { profileContext } from "./profile.context";
import { TypeIndexProfile } from "./profile.typings";
/**
* =============================================================================
* LDO ShapeTypes profile
* =============================================================================
*/
/**
* TypeIndexProfile ShapeType
*/
export const TypeIndexProfileShapeType: ShapeType<TypeIndexProfile> = {
schema: profileSchema,
shape: "https://shaperepo.com/schemas/solidProfile#TypeIndexProfile",
context: profileContext,
};

@ -0,0 +1,27 @@
import { ContextDefinition } from "jsonld";
/**
* =============================================================================
* Typescript Typings for profile
* =============================================================================
*/
/**
* TypeIndexProfile Type
*/
export interface TypeIndexProfile {
"@id"?: string;
"@context"?: ContextDefinition;
/**
* A registry of all types used on the user's Pod (for private access only)
*/
privateTypeIndex?: {
"@id": string;
}[];
/**
* A registry of all types used on the user's Pod (for public access)
*/
publicTypeIndex?: {
"@id": string;
}[];
}

@ -0,0 +1,47 @@
import { ContextDefinition } from "jsonld";
/**
* =============================================================================
* typeIndexContext: JSONLD Context for typeIndex
* =============================================================================
*/
export const typeIndexContext: ContextDefinition = {
TypeIndex: {
"@id": "http://www.w3.org/ns/solid/terms#TypeIndex",
"@context": {
type: {
"@id": "@type",
},
},
},
ListedDocument: {
"@id": "http://www.w3.org/ns/solid/terms#ListedDocument",
"@context": {
type: {
"@id": "@type",
},
},
},
TypeRegistration: {
"@id": "http://www.w3.org/ns/solid/terms#TypeRegistration",
"@context": {
type: {
"@id": "@type",
},
forClass: {
"@id": "http://www.w3.org/ns/solid/terms#forClass",
"@type": "@id",
},
instance: {
"@id": "http://www.w3.org/ns/solid/terms#instance",
"@type": "@id",
"@isCollection": true,
},
instanceContainer: {
"@id": "http://www.w3.org/ns/solid/terms#instanceContainer",
"@type": "@id",
"@isCollection": true,
},
},
},
};

@ -0,0 +1,144 @@
import { Schema } from "shexj";
/**
* =============================================================================
* typeIndexSchema: ShexJ Schema for typeIndex
* =============================================================================
*/
export const typeIndexSchema: Schema = {
type: "Schema",
shapes: [
{
id: "https://shaperepo.com/schemas/solidProfile#TypeIndexDocument",
type: "ShapeDecl",
shapeExpr: {
type: "Shape",
expression: {
type: "EachOf",
expressions: [
{
type: "TripleConstraint",
predicate: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type",
valueExpr: {
type: "NodeConstraint",
values: ["http://www.w3.org/ns/solid/terms#TypeIndex"],
},
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "Defines the node as a TypeIndex",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type",
valueExpr: {
type: "NodeConstraint",
values: ["http://www.w3.org/ns/solid/terms#ListedDocument"],
},
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "Defines the node as a Listed Document",
},
},
],
},
],
},
extra: ["http://www.w3.org/1999/02/22-rdf-syntax-ns#type"],
},
},
{
id: "https://shaperepo.com/schemas/solidProfile#TypeRegistration",
type: "ShapeDecl",
shapeExpr: {
type: "Shape",
expression: {
type: "EachOf",
expressions: [
{
type: "TripleConstraint",
predicate: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type",
valueExpr: {
type: "NodeConstraint",
values: ["http://www.w3.org/ns/solid/terms#TypeRegistration"],
},
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "Defines this node as a Type Registration",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/ns/solid/terms#forClass",
valueExpr: {
type: "NodeConstraint",
nodeKind: "iri",
},
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "The class of object at this type.",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/ns/solid/terms#instance",
valueExpr: {
type: "NodeConstraint",
nodeKind: "iri",
},
min: 0,
max: -1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "A specific resource that contains the class.",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/ns/solid/terms#instanceContainer",
valueExpr: {
type: "NodeConstraint",
nodeKind: "iri",
},
min: 0,
max: -1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "Containers that contain resources with the class.",
},
},
],
},
],
},
extra: ["http://www.w3.org/1999/02/22-rdf-syntax-ns#type"],
},
},
],
};

@ -0,0 +1,28 @@
import { ShapeType } from "@ldo/ldo";
import { typeIndexSchema } from "./typeIndex.schema";
import { typeIndexContext } from "./typeIndex.context";
import { TypeIndexDocument, TypeRegistration } from "./typeIndex.typings";
/**
* =============================================================================
* LDO ShapeTypes typeIndex
* =============================================================================
*/
/**
* TypeIndexDocument ShapeType
*/
export const TypeIndexDocumentShapeType: ShapeType<TypeIndexDocument> = {
schema: typeIndexSchema,
shape: "https://shaperepo.com/schemas/solidProfile#TypeIndexDocument",
context: typeIndexContext,
};
/**
* TypeRegistration ShapeType
*/
export const TypeRegistrationShapeType: ShapeType<TypeRegistration> = {
schema: typeIndexSchema,
shape: "https://shaperepo.com/schemas/solidProfile#TypeRegistration",
context: typeIndexContext,
};

@ -0,0 +1,58 @@
import { ContextDefinition } from "jsonld";
/**
* =============================================================================
* Typescript Typings for typeIndex
* =============================================================================
*/
/**
* TypeIndexDocument Type
*/
export interface TypeIndexDocument {
"@id"?: string;
"@context"?: ContextDefinition;
/**
* Defines the node as a TypeIndex | Defines the node as a Listed Document
*/
type: (
| {
"@id": "TypeIndex";
}
| {
"@id": "ListedDocument";
}
)[];
}
/**
* TypeRegistration Type
*/
export interface TypeRegistration {
"@id"?: string;
"@context"?: ContextDefinition;
/**
* Defines this node as a Type Registration
*/
type: {
"@id": "TypeRegistration";
};
/**
* The class of object at this type.
*/
forClass: {
"@id": string;
};
/**
* A specific resource that contains the class.
*/
instance?: {
"@id": string;
}[];
/**
* Containers that contain resources with the class.
*/
instanceContainer?: {
"@id": string;
}[];
}

@ -0,0 +1,10 @@
PREFIX srs: <https://shaperepo.com/schemas/solidProfile#>
PREFIX solid: <http://www.w3.org/ns/solid/terms#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
srs:TypeIndexProfile {
solid:privateTypeIndex IRI *
// rdfs:comment "A registry of all types used on the user's Pod (for private access only)" ;
solid:publicTypeIndex IRI *
// rdfs:comment "A registry of all types used on the user's Pod (for public access)" ;
}

@ -0,0 +1,23 @@
PREFIX srs: <https://shaperepo.com/schemas/solidProfile#>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX solid: <http://www.w3.org/ns/solid/terms#>
PREFIX vcard: <http://www.w3.org/2006/vcard/ns#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
srs:TypeIndexDocument EXTRA a {
a [ solid:TypeIndex ]
// rdfs:comment "Defines the node as a TypeIndex" ;
a [ solid:ListedDocument ]
// rdfs:comment "Defines the node as a Listed Document" ;
}
srs:TypeRegistration EXTRA a {
a [ solid:TypeRegistration ]
// rdfs:comment "Defines this node as a Type Registration" ;
solid:forClass IRI
// rdfs:comment "The class of object at this type." ;
solid:instance IRI *
// rdfs:comment "A specific resource that contains the class." ;
solid:instanceContainer IRI *
// rdfs:comment "Containers that contain resources with the class." ;
}

@ -0,0 +1,32 @@
import { ContextDefinition } from "jsonld";
/**
* =============================================================================
* postContext: JSONLD Context for post
* =============================================================================
*/
export const postContext: ContextDefinition = {
type: {
"@id": "@type",
},
SocialMediaPosting: "http://schema.org/SocialMediaPosting",
CreativeWork: "http://schema.org/CreativeWork",
Thing: "http://schema.org/Thing",
articleBody: {
"@id": "http://schema.org/articleBody",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
uploadDate: {
"@id": "http://schema.org/uploadDate",
"@type": "http://www.w3.org/2001/XMLSchema#date",
},
image: {
"@id": "http://schema.org/image",
"@type": "@id",
},
publisher: {
"@id": "http://schema.org/publisher",
"@type": "@id",
"@container": "@set",
},
};

@ -0,0 +1,155 @@
import { Schema } from "shexj";
/**
* =============================================================================
* postSchema: ShexJ Schema for post
* =============================================================================
*/
export const postSchema: Schema = {
type: "Schema",
shapes: [
{
id: "https://example.com/PostSh",
type: "ShapeDecl",
shapeExpr: {
type: "Shape",
expression: {
type: "EachOf",
expressions: [
{
type: "TripleConstraint",
predicate: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type",
valueExpr: {
type: "NodeConstraint",
values: [
"http://schema.org/SocialMediaPosting",
"http://schema.org/CreativeWork",
"http://schema.org/Thing",
],
},
},
{
type: "TripleConstraint",
predicate: "http://schema.org/articleBody",
valueExpr: {
type: "NodeConstraint",
datatype: "http://www.w3.org/2001/XMLSchema#string",
},
min: 0,
max: 1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#label",
object: {
value: "articleBody",
},
},
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "The actual body of the article. ",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://schema.org/uploadDate",
valueExpr: {
type: "NodeConstraint",
datatype: "http://www.w3.org/2001/XMLSchema#date",
},
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#label",
object: {
value: "uploadDate",
},
},
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value:
"Date when this media object was uploaded to this site.",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://schema.org/image",
valueExpr: {
type: "NodeConstraint",
nodeKind: "iri",
},
min: 0,
max: 1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#label",
object: {
value: "image",
},
},
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value:
"A media object that encodes this CreativeWork. This property is a synonym for encoding.",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://schema.org/publisher",
valueExpr: {
type: "NodeConstraint",
nodeKind: "iri",
},
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#label",
object: {
value: "publisher",
},
},
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "The publisher of the creative work.",
},
},
],
},
],
},
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#label",
object: {
value: "SocialMediaPost",
},
},
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value:
"A post to a social media platform, including blog posts, tweets, Facebook posts, etc.",
},
},
],
},
},
],
};

@ -0,0 +1,19 @@
import { ShapeType } from "@ldo/ldo";
import { postSchema } from "./post.schema";
import { postContext } from "./post.context";
import { PostSh } from "./post.typings";
/**
* =============================================================================
* LDO ShapeTypes post
* =============================================================================
*/
/**
* PostSh ShapeType
*/
export const PostShShapeType: ShapeType<PostSh> = {
schema: postSchema,
shape: "https://example.com/PostSh",
context: postContext,
};

@ -0,0 +1,45 @@
import { ContextDefinition } from "jsonld";
/**
* =============================================================================
* Typescript Typings for post
* =============================================================================
*/
/**
* PostSh Type
*/
export interface PostSh {
"@id"?: string;
"@context"?: ContextDefinition;
type:
| {
"@id": "SocialMediaPosting";
}
| {
"@id": "CreativeWork";
}
| {
"@id": "Thing";
};
/**
* The actual body of the article.
*/
articleBody?: string;
/**
* Date when this media object was uploaded to this site.
*/
uploadDate: string;
/**
* A media object that encodes this CreativeWork. This property is a synonym for encoding.
*/
image?: {
"@id": string;
};
/**
* The publisher of the creative work.
*/
publisher: {
"@id": string;
}[];
}

@ -0,0 +1,431 @@
import React, { useCallback, useEffect, useState } from "react";
import type { FunctionComponent } from "react";
import { render, screen, fireEvent, act } from "@testing-library/react";
import {
SAMPLE_BINARY_URI,
SAMPLE_DATA_URI,
SERVER_DOMAIN,
setUpServer,
} from "./setUpServer";
import { UnauthenticatedSolidLdoProvider } from "../src/UnauthenticatedSolidLdoProvider";
import { useResource } from "../src/useResource";
import { useRootContainerFor } from "../src/useRootContainer";
import { useLdo } from "../src/SolidLdoProvider";
import { PostShShapeType } from "./.ldo/post.shapeTypes";
import type { PostSh } from "./.ldo/post.typings";
import { useSubject } from "../src/useSubject";
// Use an increased timeout, since the CSS server takes too much setup time.
jest.setTimeout(40_000);
describe("Integration Tests", () => {
setUpServer();
/**
* ===========================================================================
* useResource
* ===========================================================================
*/
describe("useResource", () => {
it("Fetches a resource and indicates it is loading while doing so", async () => {
const UseResourceTest: FunctionComponent = () => {
const resource = useResource(SAMPLE_DATA_URI);
if (resource?.isLoading()) return <p>Loading</p>;
return <p role="status">{resource.status.type}</p>;
};
render(
<UnauthenticatedSolidLdoProvider>
<UseResourceTest />
</UnauthenticatedSolidLdoProvider>,
);
await screen.findByText("Loading");
const resourceStatus = await screen.findByRole("status");
expect(resourceStatus.innerHTML).toBe("dataReadSuccess");
});
it("returns undefined when no uri is provided, then rerenders when one is", async () => {
const UseResourceUndefinedTest: FunctionComponent = () => {
const [uri, setUri] = useState<string | undefined>(undefined);
const resource = useResource(uri, { suppressInitialRead: true });
if (!resource)
return (
<div>
<p>Undefined</p>
<button onClick={() => setUri(SAMPLE_DATA_URI)}>Next</button>
</div>
);
return <p role="status">{resource.status.type}</p>;
};
render(
<UnauthenticatedSolidLdoProvider>
<UseResourceUndefinedTest />
</UnauthenticatedSolidLdoProvider>,
);
await screen.findByText("Undefined");
fireEvent.click(screen.getByText("Next"));
const resourceStatus = await screen.findByRole("status");
expect(resourceStatus.innerHTML).toBe("unfetched");
});
it("Reloads the data on mount", async () => {
const ReloadTest: FunctionComponent = () => {
const resource = useResource(SAMPLE_DATA_URI, { reloadOnMount: true });
if (resource?.isLoading()) return <p>Loading</p>;
return <p role="status">{resource.status.type}</p>;
};
const ReloadParent: FunctionComponent = () => {
const [showComponent, setShowComponent] = useState(true);
return (
<div>
<button onClick={() => setShowComponent(!showComponent)}>
Show Component
</button>
{showComponent ? <ReloadTest /> : <p>Hidden</p>}
</div>
);
};
render(
<UnauthenticatedSolidLdoProvider>
<ReloadParent />
</UnauthenticatedSolidLdoProvider>,
);
await screen.findByText("Loading");
const resourceStatus1 = await screen.findByRole("status");
expect(resourceStatus1.innerHTML).toBe("dataReadSuccess");
fireEvent.click(screen.getByText("Show Component"));
await screen.findByText("Hidden");
fireEvent.click(screen.getByText("Show Component"));
await screen.findByText("Loading");
const resourceStatus2 = await screen.findByRole("status", undefined, {
timeout: 5000,
});
expect(resourceStatus2.innerHTML).toBe("dataReadSuccess");
});
it("handles swapping to a new resource", async () => {
const SwapResourceTest: FunctionComponent = () => {
const [uri, setUri] = useState(SAMPLE_DATA_URI);
const resource = useResource(uri);
if (resource?.isLoading()) return <p>Loading</p>;
return (
<div>
<p role="status">{resource.status.type}</p>
<button onClick={() => setUri(SAMPLE_BINARY_URI)}>
Update URI
</button>
</div>
);
};
render(
<UnauthenticatedSolidLdoProvider>
<SwapResourceTest />
</UnauthenticatedSolidLdoProvider>,
);
await screen.findByText("Loading");
const resourceStatus1 = await screen.findByRole("status");
expect(resourceStatus1.innerHTML).toBe("dataReadSuccess");
fireEvent.click(screen.getByText("Update URI"));
await screen.findByText("Loading");
const resourceStatus2 = await screen.findByRole("status");
expect(resourceStatus2.innerHTML).toBe("binaryReadSuccess");
});
});
/**
* ===========================================================================
* useRootContainer
* ===========================================================================
*/
describe("useRootContainer", () => {
it("gets the root container for a sub-resource", async () => {
const RootContainerTest: FunctionComponent = () => {
const rootContainer = useRootContainerFor(SAMPLE_DATA_URI, {
suppressInitialRead: true,
});
return rootContainer ? <p role="root">{rootContainer?.uri}</p> : <></>;
};
render(
<UnauthenticatedSolidLdoProvider>
<RootContainerTest />
</UnauthenticatedSolidLdoProvider>,
);
const container = await screen.findByRole("root");
expect(container.innerHTML).toBe(SERVER_DOMAIN);
});
it("returns undefined when a URI is not provided", async () => {
const RootContainerTest: FunctionComponent = () => {
const rootContainer = useRootContainerFor(undefined, {
suppressInitialRead: true,
});
return rootContainer ? (
<p role="root">{rootContainer?.uri}</p>
) : (
<p role="undefined">Undefined</p>
);
};
render(
<UnauthenticatedSolidLdoProvider>
<RootContainerTest />
</UnauthenticatedSolidLdoProvider>,
);
const container = await screen.findByRole("undefined");
expect(container.innerHTML).toBe("Undefined");
});
});
/**
* ===========================================================================
* useLdoMethods
* ===========================================================================
*/
describe("useLdoMethods", () => {
it("uses get subject to get a linked data object", async () => {
const GetSubjectTest: FunctionComponent = () => {
const [subject, setSubject] = useState<PostSh | undefined>();
const { getSubject } = useLdo();
useEffect(() => {
const someSubject = getSubject(
PostShShapeType,
"https://example.com/subject",
);
setSubject(someSubject);
}, []);
return subject ? <p role="subject">{subject["@id"]}</p> : <></>;
};
render(
<UnauthenticatedSolidLdoProvider>
<GetSubjectTest />
</UnauthenticatedSolidLdoProvider>,
);
const container = await screen.findByRole("subject");
expect(container.innerHTML).toBe("https://example.com/subject");
});
it("uses createData to create a new data object", async () => {
const GetSubjectTest: FunctionComponent = () => {
const [subject, setSubject] = useState<PostSh | undefined>();
const { createData, getResource } = useLdo();
useEffect(() => {
const someSubject = createData(
PostShShapeType,
"https://example.com/subject",
getResource("https://example.com/"),
);
someSubject.articleBody = "Cool Article";
setSubject(someSubject);
}, []);
return subject ? <p role="subject">{subject.articleBody}</p> : <></>;
};
render(
<UnauthenticatedSolidLdoProvider>
<GetSubjectTest />
</UnauthenticatedSolidLdoProvider>,
);
const container = await screen.findByRole("subject");
expect(container.innerHTML).toBe("Cool Article");
});
});
/**
* ===========================================================================
* useSubject
* ===========================================================================
*/
describe("useSubject", () => {
it("renders the article body from the useSubject value", async () => {
const UseSubjectTest: FunctionComponent = () => {
useResource(SAMPLE_DATA_URI);
const post = useSubject(PostShShapeType, `${SAMPLE_DATA_URI}#Post1`);
return <p role="article">{post.articleBody}</p>;
};
render(
<UnauthenticatedSolidLdoProvider>
<UseSubjectTest />
</UnauthenticatedSolidLdoProvider>,
);
await screen.findByText("test");
});
it("renders the array value from the useSubject value", async () => {
const UseSubjectTest: FunctionComponent = () => {
const resource = useResource(SAMPLE_DATA_URI);
const post = useSubject(PostShShapeType, `${SAMPLE_DATA_URI}#Post1`);
if (resource.isLoading() || !post) return <p>loading</p>;
return (
<div>
<p role="single">{post.publisher[0]["@id"]}</p>
<ul role="list">
{post.publisher.map((publisher) => {
return <li key={publisher["@id"]}>{publisher["@id"]}</li>;
})}
</ul>
</div>
);
};
render(
<UnauthenticatedSolidLdoProvider>
<UseSubjectTest />
</UnauthenticatedSolidLdoProvider>,
);
const single = await screen.findByRole("single");
expect(single.innerHTML).toBe("https://example.com/Publisher1");
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");
});
it("returns undefined in the subject URI is undefined", async () => {
const UseSubjectTest: FunctionComponent = () => {
useResource(SAMPLE_DATA_URI, { suppressInitialRead: true });
const post = useSubject(PostShShapeType, undefined);
return (
<p role="article">
{post === undefined ? "Undefined" : "Not Undefined"}
</p>
);
};
render(
<UnauthenticatedSolidLdoProvider>
<UseSubjectTest />
</UnauthenticatedSolidLdoProvider>,
);
const article = await screen.findByRole("article");
expect(article.innerHTML).toBe("Undefined");
});
it("returns nothing if a symbol key is provided", async () => {
const UseSubjectTest: FunctionComponent = () => {
const resource = useResource(SAMPLE_DATA_URI);
const post = useSubject(PostShShapeType, `${SAMPLE_DATA_URI}#Post1`);
if (resource.isLoading() || !post) return <p>loading</p>;
return <p role="value">{typeof post[Symbol.hasInstance]}</p>;
};
render(
<UnauthenticatedSolidLdoProvider>
<UseSubjectTest />
</UnauthenticatedSolidLdoProvider>,
);
const article = await screen.findByRole("value");
expect(article.innerHTML).toBe("undefined");
});
it("returns an id if an id key is provided", async () => {
const UseSubjectTest: FunctionComponent = () => {
const resource = useResource(SAMPLE_DATA_URI);
const post = useSubject(PostShShapeType, `${SAMPLE_DATA_URI}#Post1`);
if (resource.isLoading() || !post) return <p>loading</p>;
return <p role="value">{post["@id"]}</p>;
};
render(
<UnauthenticatedSolidLdoProvider>
<UseSubjectTest />
</UnauthenticatedSolidLdoProvider>,
);
const article = await screen.findByRole("value");
expect(article.innerHTML).toBe(`${SAMPLE_DATA_URI}#Post1`);
});
it("does not set a value if a value is attempted to be set", async () => {
const warn = jest.spyOn(console, "warn").mockImplementation(() => {});
const UseSubjectTest: FunctionComponent = () => {
const resource = useResource(SAMPLE_DATA_URI);
const post = useSubject(PostShShapeType, `${SAMPLE_DATA_URI}#Post1`);
if (resource.isLoading() || !post) return <p>loading</p>;
return (
<div>
<p role="value">{post.articleBody}</p>
<button onClick={() => (post.articleBody = "bad")}>
Attempt Change
</button>
</div>
);
};
render(
<UnauthenticatedSolidLdoProvider>
<UseSubjectTest />
</UnauthenticatedSolidLdoProvider>,
);
const article = await screen.findByRole("value");
expect(article.innerHTML).toBe(`test`);
fireEvent.click(screen.getByText("Attempt Change"));
expect(article.innerHTML).not.toBe("bad");
expect(warn).toHaveBeenCalledWith(
"You've attempted to set a value on a Linked Data Object from the useSubject, useMatchingSubject, or useMatchingObject hooks. These linked data objects should only be used to render data, not modify it. To modify data, use the `changeData` function.",
);
warn.mockReset();
});
it("rerenders when asked to subscribe to a resource", async () => {
const NotificationTest: FunctionComponent = () => {
const resource = useResource(SAMPLE_DATA_URI, { subscribe: true });
const post = useSubject(PostShShapeType, `${SAMPLE_DATA_URI}#Post1`);
const addPublisher = useCallback(async () => {
await fetch(SAMPLE_DATA_URI, {
method: "PATCH",
body: `INSERT DATA { <${SAMPLE_DATA_URI}#Post1> <http://schema.org/publisher> <https://example.com/Publisher3> . }`,
headers: {
"Content-Type": "application/sparql-update",
},
});
}, []);
if (resource.isLoading() || !post) return <p>loading</p>;
return (
<div>
<ul role="list">
{post.publisher.map((publisher) => {
return <li key={publisher["@id"]}>{publisher["@id"]}</li>;
})}
</ul>
<button onClick={addPublisher}>Add Publisher</button>
</div>
);
};
const { unmount } = render(
<UnauthenticatedSolidLdoProvider>
<NotificationTest />
</UnauthenticatedSolidLdoProvider>,
);
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");
// Wait for subscription to connect
await act(async () => {
await new Promise((resolve) => setTimeout(resolve, 1000));
});
// Click button to add a publisher
await fireEvent.click(screen.getByText("Add Publisher"));
await screen.findByText("https://example.com/Publisher3");
// Verify the new publisher is in the list
const updatedList = await screen.findByRole("list");
expect(updatedList.children[2].innerHTML).toBe(
"https://example.com/Publisher3",
);
unmount();
await act(async () => {
await new Promise((resolve) => setTimeout(resolve, 1000));
});
});
});
});

@ -0,0 +1,110 @@
import type { ContainerUri, LeafUri } from "@ldo/solid";
import fetch from "cross-fetch";
export const SERVER_DOMAIN = process.env.SERVER || "http://localhost:3001/";
export const ROOT_ROUTE = process.env.ROOT_CONTAINER || "example/";
export const ROOT_CONTAINER = `${SERVER_DOMAIN}${ROOT_ROUTE}`;
export const TEST_CONTAINER_SLUG = "test_ldo/";
export const TEST_CONTAINER_URI =
`${ROOT_CONTAINER}${TEST_CONTAINER_SLUG}` as ContainerUri;
export const SAMPLE_DATA_URI = `${TEST_CONTAINER_URI}sample.ttl` as LeafUri;
export const SAMPLE2_DATA_SLUG = "sample2.ttl";
export const SAMPLE2_DATA_URI =
`${TEST_CONTAINER_URI}${SAMPLE2_DATA_SLUG}` as LeafUri;
export const SAMPLE_BINARY_URI = `${TEST_CONTAINER_URI}sample.txt` as LeafUri;
export const SAMPLE2_BINARY_SLUG = `sample2.txt`;
export const SAMPLE2_BINARY_URI =
`${TEST_CONTAINER_URI}${SAMPLE2_BINARY_SLUG}` as LeafUri;
export const SAMPLE_CONTAINER_URI =
`${TEST_CONTAINER_URI}sample_container/` as ContainerUri;
export const EXAMPLE_POST_TTL = `@prefix schema: <http://schema.org/> .
<#Post1>
a schema:CreativeWork, schema:Thing, schema:SocialMediaPosting ;
schema:image <https://example.com/postImage.jpg> ;
schema:articleBody "test" ;
schema:publisher <https://example.com/Publisher1>, <https://example.com/Publisher2> .`;
export const TEST_CONTAINER_TTL = `@prefix dc: <http://purl.org/dc/terms/>.
@prefix ldp: <http://www.w3.org/ns/ldp#>.
@prefix posix: <http://www.w3.org/ns/posix/stat#>.
@prefix xsd: <http://www.w3.org/2001/XMLSchema#>.
<> <urn:npm:solid:community-server:http:slug> "sample.txt";
a ldp:Container, ldp:BasicContainer, ldp:Resource;
dc:modified "2023-10-20T13:57:14.000Z"^^xsd:dateTime.
<sample.ttl> a ldp:Resource, <http://www.w3.org/ns/iana/media-types/text/turtle#Resource>;
dc:modified "2023-10-20T13:57:14.000Z"^^xsd:dateTime.
<sample.txt> a ldp:Resource, <http://www.w3.org/ns/iana/media-types/text/plain#Resource>;
dc:modified "2023-10-20T13:57:14.000Z"^^xsd:dateTime.
<> posix:mtime 1697810234;
ldp:contains <sample.ttl>, <sample.txt>.
<sample.ttl> posix:mtime 1697810234;
posix:size 522.
<sample.txt> posix:mtime 1697810234;
posix:size 10.`;
export interface SetUpServerReturn {
authFetch: typeof fetch;
fetchMock: jest.Mock<
Promise<Response>,
[input: RequestInfo | URL, init?: RequestInit | undefined]
>;
}
export function setUpServer(): SetUpServerReturn {
// Ignore to build s
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const s: SetUpServerReturn = {};
beforeAll(async () => {
// s.authFetch = await getAuthenticatedFetch();
s.authFetch = fetch;
});
beforeEach(async () => {
s.fetchMock = jest.fn(s.authFetch);
// Create a new document called sample.ttl
await s.authFetch(ROOT_CONTAINER, {
method: "POST",
headers: {
link: '<http://www.w3.org/ns/ldp#Container>; rel="type"',
slug: TEST_CONTAINER_SLUG,
},
});
await Promise.all([
s.authFetch(TEST_CONTAINER_URI, {
method: "POST",
headers: { "content-type": "text/turtle", slug: "sample.ttl" },
body: EXAMPLE_POST_TTL,
}),
s.authFetch(TEST_CONTAINER_URI, {
method: "POST",
headers: { "content-type": "text/plain", slug: "sample.txt" },
body: "some text.",
}),
]);
});
afterEach(async () => {
await Promise.all([
s.authFetch(SAMPLE_DATA_URI, {
method: "DELETE",
}),
s.authFetch(SAMPLE2_DATA_URI, {
method: "DELETE",
}),
s.authFetch(SAMPLE_BINARY_URI, {
method: "DELETE",
}),
s.authFetch(SAMPLE2_BINARY_URI, {
method: "DELETE",
}),
s.authFetch(SAMPLE_CONTAINER_URI, {
method: "DELETE",
}),
]);
});
return s;
}

@ -0,0 +1,52 @@
{
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^7.0.0/components/context.jsonld",
"import": [
"css:config/app/init/initialize-intro.json",
"css:config/app/main/default.json",
"css:config/app/variables/default.json",
"css:config/http/handler/default.json",
"css:config/http/middleware/default.json",
"css:config/http/notifications/all.json",
"css:config/http/server-factory/http.json",
"css:config/http/static/default.json",
"css:config/identity/access/public.json",
"css:config/identity/email/default.json",
"css:config/identity/handler/default.json",
"css:config/identity/oidc/default.json",
"css:config/identity/ownership/token.json",
"css:config/identity/pod/static.json",
"css:config/ldp/authentication/dpop-bearer.json",
"css:config/ldp/authorization/webacl.json",
"css:config/ldp/handler/default.json",
"css:config/ldp/metadata-parser/default.json",
"css:config/ldp/metadata-writer/default.json",
"css:config/ldp/modes/default.json",
"css:config/storage/backend/memory.json",
"css:config/storage/key-value/resource-store.json",
"css:config/storage/location/root.json",
"css:config/storage/middleware/default.json",
"css:config/util/auxiliary/acl.json",
"css:config/util/identifiers/suffix.json",
"css:config/util/index/default.json",
"css:config/util/logging/winston.json",
"css:config/util/representation-conversion/default.json",
"css:config/util/resource-locker/memory.json",
"css:config/util/variables/default.json"
],
"@graph": [
{
"comment": "A Solid server that stores its resources in memory and uses WAC for authorization."
},
{
"comment": "The location of the new pod templates folder.",
"@type": "Override",
"overrideInstance": {
"@id": "urn:solid-server:default:PodResourcesGenerator"
},
"overrideParameters": {
"@type": "StaticFolderGenerator",
"templateFolder": "./test/test-server/configs/template"
}
}
]
}

@ -0,0 +1,9 @@
[
{
"email": "hello@example.com",
"password": "abc123",
"pods": [
{ "name": "example" }
]
}
]

@ -0,0 +1,13 @@
@prefix : <#>.
@prefix acl: <http://www.w3.org/ns/auth/acl#>.
@prefix foaf: <http://xmlns.com/foaf/0.1/>.
@prefix eve: <./>.
@prefix c: <./profile/card#>.
:ControlReadWrite
a acl:Authorization;
acl:accessTo eve:;
acl:agent c:me, <mailto:info@o.team>;
acl:agentClass foaf:Agent;
acl:default eve:;
acl:mode acl:Control, acl:Read, acl:Write.

@ -0,0 +1,19 @@
# ACL resource for the WebID profile document
@prefix acl: <http://www.w3.org/ns/auth/acl#>.
@prefix foaf: <http://xmlns.com/foaf/0.1/>.
# The WebID profile is readable by the public.
# This is required for discovery and verification,
# e.g. when checking identity providers.
<#public>
a acl:Authorization;
acl:agentClass foaf:Agent;
acl:accessTo <./card>;
acl:mode acl:Read.
# The owner has full access to the profile
<#owner>
a acl:Authorization;
acl:agent <{{webId}}>;
acl:accessTo <./card>;
acl:mode acl:Read, acl:Write, acl:Control.

@ -0,0 +1,7 @@
import { createApp } from "./solidServer.helper";
async function run() {
const app = await createApp();
await app.start();
}
run();

@ -0,0 +1,39 @@
// Taken from https://github.com/comunica/comunica/blob/b237be4265c353a62a876187d9e21e3bc05123a3/engines/query-sparql/test/QuerySparql-solid-test.ts#L9
import * as path from "path";
import type { App } from "@solid/community-server";
import { AppRunner, resolveModulePath } from "@solid/community-server";
export async function createApp(): Promise<App> {
if (process.env.SERVER) {
return {
start: () => {},
stop: () => {},
} as App;
}
const appRunner = new AppRunner();
return appRunner.create({
loaderProperties: {
mainModulePath: resolveModulePath(""),
typeChecking: false,
},
config: path.join(
__dirname,
"configs",
"components-config",
"unauthenticatedServer.json",
),
variableBindings: {},
shorthand: {
port: 3_001,
loggingLevel: "off",
seedConfig: path.join(__dirname, "configs", "solid-css-seed.json"),
},
});
}
export interface ISecretData {
id: string;
secret: string;
}

@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./dist",
"lib": ["dom"]
},
"include": ["./src"]
}
Loading…
Cancel
Save