mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
feat: Add HTML container listing.
This commit is contained in:
parent
c0dac12111
commit
1394b9cb56
27
config/util/representation-conversion/converters/errors.json
Normal file
27
config/util/representation-conversion/converters/errors.json
Normal file
@ -0,0 +1,27 @@
|
||||
{
|
||||
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^1.0.0/components/context.jsonld",
|
||||
"import": [
|
||||
"files-scs:config/util/representation-conversion/converters/content-type-replacer.json",
|
||||
"files-scs:config/util/representation-conversion/converters/quad-to-rdf.json",
|
||||
"files-scs:config/util/representation-conversion/converters/rdf-to-quad.json",
|
||||
"files-scs:config/util/representation-conversion/converters/markdown.json"
|
||||
],
|
||||
"@graph": [
|
||||
{
|
||||
"@id": "urn:solid-server:default:ErrorToQuadConverter",
|
||||
"@type": "ErrorToQuadConverter",
|
||||
},
|
||||
{
|
||||
"comment": "Converts an error into a Markdown description of its details.",
|
||||
"@id": "urn:solid-server:default:ErrorToTemplateConverter",
|
||||
"@type": "ErrorToTemplateConverter",
|
||||
"templateEngine": {
|
||||
"@type": "HandlebarsTemplateEngine",
|
||||
"template": "$PACKAGE_ROOT/templates/error/main.md.hbs"
|
||||
},
|
||||
"templatePath": "$PACKAGE_ROOT/templates/error/descriptions/",
|
||||
"extension": ".md.hbs",
|
||||
"contentType": "text/markdown"
|
||||
}
|
||||
]
|
||||
}
|
@ -14,6 +14,16 @@
|
||||
"@type": "HandlebarsTemplateEngine",
|
||||
"template": "$PACKAGE_ROOT/templates/main.html.hbs"
|
||||
}
|
||||
},
|
||||
{
|
||||
"comment": "Converts a container into a Markdown listing of its contents.",
|
||||
"@id": "urn:solid-server:default:ContainerToTemplateConverter",
|
||||
"@type": "ContainerToTemplateConverter",
|
||||
"templateEngine": {
|
||||
"@type": "HandlebarsTemplateEngine",
|
||||
"template": "$PACKAGE_ROOT/templates/container.md.hbs"
|
||||
},
|
||||
"contentType": "text/markdown"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -2,9 +2,10 @@
|
||||
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^1.0.0/components/context.jsonld",
|
||||
"import": [
|
||||
"files-scs:config/util/representation-conversion/converters/content-type-replacer.json",
|
||||
"files-scs:config/util/representation-conversion/converters/errors.json",
|
||||
"files-scs:config/util/representation-conversion/converters/markdown.json",
|
||||
"files-scs:config/util/representation-conversion/converters/quad-to-rdf.json",
|
||||
"files-scs:config/util/representation-conversion/converters/rdf-to-quad.json",
|
||||
"files-scs:config/util/representation-conversion/converters/markdown.json"
|
||||
"files-scs:config/util/representation-conversion/converters/rdf-to-quad.json"
|
||||
],
|
||||
"@graph": [
|
||||
{
|
||||
@ -26,18 +27,9 @@
|
||||
"converters": [
|
||||
{ "@id": "urn:solid-server:default:RdfToQuadConverter" },
|
||||
{ "@id": "urn:solid-server:default:QuadToRdfConverter" },
|
||||
{ "@type": "ErrorToQuadConverter" },
|
||||
{
|
||||
"comment": "Converts an error into a Markdown description of its details.",
|
||||
"@type": "ErrorToTemplateConverter",
|
||||
"templateEngine": {
|
||||
"@type": "HandlebarsTemplateEngine",
|
||||
"template": "$PACKAGE_ROOT/templates/error/main.md.hbs"
|
||||
},
|
||||
"templatePath": "$PACKAGE_ROOT/templates/error/descriptions/",
|
||||
"extension": ".md.hbs",
|
||||
"contentType": "text/markdown"
|
||||
},
|
||||
{ "@id": "urn:solid-server:default:ContainerToTemplateConverter" },
|
||||
{ "@id": "urn:solid-server:default:ErrorToQuadConverter" },
|
||||
{ "@id": "urn:solid-server:default:ErrorToTemplateConverter" },
|
||||
{ "@id": "urn:solid-server:default:MarkdownToHtmlConverter" }
|
||||
]
|
||||
}
|
||||
|
34
package-lock.json
generated
34
package-lock.json
generated
@ -18,6 +18,7 @@
|
||||
"@types/bcrypt": "^5.0.0",
|
||||
"@types/cors": "^2.8.10",
|
||||
"@types/end-of-stream": "^1.4.0",
|
||||
"@types/lodash.orderby": "^4.6.6",
|
||||
"@types/marked": "^2.0.3",
|
||||
"@types/mime-types": "^2.1.0",
|
||||
"@types/n3": "^1.10.0",
|
||||
@ -47,6 +48,7 @@
|
||||
"fetch-sparql-endpoint": "^2.0.1",
|
||||
"handlebars": "^4.7.7",
|
||||
"jose": "^3.11.6",
|
||||
"lodash.orderby": "^4.6.0",
|
||||
"marked": "^2.1.3",
|
||||
"mime-types": "^2.1.31",
|
||||
"n3": "^1.10.0",
|
||||
@ -6004,8 +6006,7 @@
|
||||
"node_modules/@types/lodash": {
|
||||
"version": "4.14.170",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.170.tgz",
|
||||
"integrity": "sha512-bpcvu/MKHHeYX+qeEN8GE7DIravODWdACVA1ctevD8CN24RhPZIKMn9ntfAsrvLfSX3cR5RrBKAbYm9bGs0A+Q==",
|
||||
"dev": true
|
||||
"integrity": "sha512-bpcvu/MKHHeYX+qeEN8GE7DIravODWdACVA1ctevD8CN24RhPZIKMn9ntfAsrvLfSX3cR5RrBKAbYm9bGs0A+Q=="
|
||||
},
|
||||
"node_modules/@types/lodash.clonedeep": {
|
||||
"version": "4.5.6",
|
||||
@ -6016,6 +6017,14 @@
|
||||
"@types/lodash": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/lodash.orderby": {
|
||||
"version": "4.6.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash.orderby/-/lodash.orderby-4.6.6.tgz",
|
||||
"integrity": "sha512-wQzu6xK+bSwhu45OeMI7fjywiIZiiaBzJB8W3fwnF1SJXHoOXRLutrSnVmq4yHPOM036qsy8lx9wHQcAbXNjJw==",
|
||||
"dependencies": {
|
||||
"@types/lodash": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/lru-cache": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/lru-cache/-/lru-cache-5.1.0.tgz",
|
||||
@ -14304,6 +14313,11 @@
|
||||
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/lodash.orderby": {
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.orderby/-/lodash.orderby-4.6.0.tgz",
|
||||
"integrity": "sha1-5pfwTOXXhSL1TZM4syuBozk+TrM="
|
||||
},
|
||||
"node_modules/lodash.template": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz",
|
||||
@ -23673,8 +23687,7 @@
|
||||
"@types/lodash": {
|
||||
"version": "4.14.170",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.170.tgz",
|
||||
"integrity": "sha512-bpcvu/MKHHeYX+qeEN8GE7DIravODWdACVA1ctevD8CN24RhPZIKMn9ntfAsrvLfSX3cR5RrBKAbYm9bGs0A+Q==",
|
||||
"dev": true
|
||||
"integrity": "sha512-bpcvu/MKHHeYX+qeEN8GE7DIravODWdACVA1ctevD8CN24RhPZIKMn9ntfAsrvLfSX3cR5RrBKAbYm9bGs0A+Q=="
|
||||
},
|
||||
"@types/lodash.clonedeep": {
|
||||
"version": "4.5.6",
|
||||
@ -23685,6 +23698,14 @@
|
||||
"@types/lodash": "*"
|
||||
}
|
||||
},
|
||||
"@types/lodash.orderby": {
|
||||
"version": "4.6.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash.orderby/-/lodash.orderby-4.6.6.tgz",
|
||||
"integrity": "sha512-wQzu6xK+bSwhu45OeMI7fjywiIZiiaBzJB8W3fwnF1SJXHoOXRLutrSnVmq4yHPOM036qsy8lx9wHQcAbXNjJw==",
|
||||
"requires": {
|
||||
"@types/lodash": "*"
|
||||
}
|
||||
},
|
||||
"@types/lru-cache": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/lru-cache/-/lru-cache-5.1.0.tgz",
|
||||
@ -30166,6 +30187,11 @@
|
||||
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.orderby": {
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.orderby/-/lodash.orderby-4.6.0.tgz",
|
||||
"integrity": "sha1-5pfwTOXXhSL1TZM4syuBozk+TrM="
|
||||
},
|
||||
"lodash.template": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz",
|
||||
|
@ -84,6 +84,7 @@
|
||||
"@types/bcrypt": "^5.0.0",
|
||||
"@types/cors": "^2.8.10",
|
||||
"@types/end-of-stream": "^1.4.0",
|
||||
"@types/lodash.orderby": "^4.6.6",
|
||||
"@types/marked": "^2.0.3",
|
||||
"@types/mime-types": "^2.1.0",
|
||||
"@types/n3": "^1.10.0",
|
||||
@ -113,6 +114,7 @@
|
||||
"fetch-sparql-endpoint": "^2.0.1",
|
||||
"handlebars": "^4.7.7",
|
||||
"jose": "^3.11.6",
|
||||
"lodash.orderby": "^4.6.0",
|
||||
"marked": "^2.1.3",
|
||||
"mime-types": "^2.1.31",
|
||||
"n3": "^1.10.0",
|
||||
|
@ -216,6 +216,7 @@ export * from './storage/accessors/SparqlDataAccessor';
|
||||
// Storage/Conversion
|
||||
export * from './storage/conversion/ChainedConverter';
|
||||
export * from './storage/conversion/ConstantConverter';
|
||||
export * from './storage/conversion/ContainerToTemplateConverter';
|
||||
export * from './storage/conversion/ContentTypeReplacer';
|
||||
export * from './storage/conversion/ConversionUtil';
|
||||
export * from './storage/conversion/ErrorToQuadConverter';
|
||||
|
@ -1,6 +1,4 @@
|
||||
import type { Readable } from 'stream';
|
||||
import { promisify } from 'util';
|
||||
import eos from 'end-of-stream';
|
||||
import type { AuxiliaryIdentifierStrategy } from '../ldp/auxiliary/AuxiliaryIdentifierStrategy';
|
||||
import type { Patch } from '../ldp/http/Patch';
|
||||
import { BasicRepresentation } from '../ldp/representation/BasicRepresentation';
|
||||
@ -9,10 +7,10 @@ import type { RepresentationPreferences } from '../ldp/representation/Representa
|
||||
import type { ResourceIdentifier } from '../ldp/representation/ResourceIdentifier';
|
||||
import { getLoggerFor } from '../logging/LogUtil';
|
||||
import type { ExpiringReadWriteLocker } from '../util/locking/ExpiringReadWriteLocker';
|
||||
import { endOfStream } from '../util/StreamUtil';
|
||||
import type { AtomicResourceStore } from './AtomicResourceStore';
|
||||
import type { Conditions } from './Conditions';
|
||||
import type { ResourceStore } from './ResourceStore';
|
||||
const endOfStream = promisify(eos);
|
||||
|
||||
/**
|
||||
* Store that for every call acquires a lock before executing it on the requested resource,
|
||||
|
80
src/storage/conversion/ContainerToTemplateConverter.ts
Normal file
80
src/storage/conversion/ContainerToTemplateConverter.ts
Normal file
@ -0,0 +1,80 @@
|
||||
import type { Readable } from 'stream';
|
||||
import orderBy from 'lodash.orderby';
|
||||
import type { Quad } from 'rdf-js';
|
||||
import { BasicRepresentation } from '../../ldp/representation/BasicRepresentation';
|
||||
import type { Representation } from '../../ldp/representation/Representation';
|
||||
import { INTERNAL_QUADS } from '../../util/ContentTypes';
|
||||
import { NotImplementedHttpError } from '../../util/errors/NotImplementedHttpError';
|
||||
import { isContainerIdentifier, isContainerPath } from '../../util/PathUtil';
|
||||
import { endOfStream } from '../../util/StreamUtil';
|
||||
import type { TemplateEngine } from '../../util/templates/TemplateEngine';
|
||||
import { LDP } from '../../util/Vocabularies';
|
||||
import type { RepresentationConverterArgs } from './RepresentationConverter';
|
||||
import { TypedRepresentationConverter } from './TypedRepresentationConverter';
|
||||
|
||||
interface ResourceDetails {
|
||||
name: string;
|
||||
identifier: string;
|
||||
container: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link RepresentationConverter} that creates a templated representation of a container.
|
||||
*/
|
||||
export class ContainerToTemplateConverter extends TypedRepresentationConverter {
|
||||
private readonly templateEngine: TemplateEngine;
|
||||
private readonly contentType: string;
|
||||
|
||||
public constructor(templateEngine: TemplateEngine, contentType: string) {
|
||||
super(INTERNAL_QUADS, contentType);
|
||||
this.templateEngine = templateEngine;
|
||||
this.contentType = contentType;
|
||||
}
|
||||
|
||||
public async canHandle(args: RepresentationConverterArgs): Promise<void> {
|
||||
if (!isContainerIdentifier(args.identifier)) {
|
||||
throw new NotImplementedHttpError('Can only convert containers.');
|
||||
}
|
||||
await super.canHandle(args);
|
||||
}
|
||||
|
||||
public async handle({ identifier, representation }: RepresentationConverterArgs): Promise<Representation> {
|
||||
const rendered = await this.templateEngine.render({
|
||||
container: this.getLocalName(identifier.path),
|
||||
children: await this.getChildResources(identifier.path, representation.data),
|
||||
});
|
||||
return new BasicRepresentation(rendered, representation.metadata, this.contentType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects the children of the container as simple objects.
|
||||
*/
|
||||
private async getChildResources(container: string, quads: Readable): Promise<ResourceDetails[]> {
|
||||
// Collect the needed bits of information from the containment triples
|
||||
const resources = new Set<string>();
|
||||
quads.on('data', ({ subject, predicate, object }: Quad): void => {
|
||||
if (subject.value === container && predicate.equals(LDP.terms.contains)) {
|
||||
resources.add(object.value);
|
||||
}
|
||||
});
|
||||
await endOfStream(quads);
|
||||
|
||||
// Create a simplified object for every resource
|
||||
const children = [ ...resources ].map((resource: string): ResourceDetails => ({
|
||||
identifier: resource,
|
||||
name: this.getLocalName(resource),
|
||||
container: isContainerPath(resource),
|
||||
}));
|
||||
|
||||
// Sort the resulting list
|
||||
return orderBy(children, [ 'container', 'identifier' ], [ 'desc', 'asc' ]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Derives a short name for the given resource.
|
||||
*/
|
||||
private getLocalName(iri: string, keepTrailingSlash = false): string {
|
||||
const match = /:\/+[^/]+.*\/(([^/]+)\/?)$/u.exec(iri);
|
||||
return match ? decodeURIComponent(match[keepTrailingSlash ? 1 : 2]) : '/';
|
||||
}
|
||||
}
|
@ -1,12 +1,16 @@
|
||||
import type { Writable, ReadableOptions, DuplexOptions } from 'stream';
|
||||
import { Readable, Transform } from 'stream';
|
||||
import { promisify } from 'util';
|
||||
import arrayifyStream from 'arrayify-stream';
|
||||
import eos from 'end-of-stream';
|
||||
import pump from 'pump';
|
||||
import { getLoggerFor } from '../logging/LogUtil';
|
||||
import { isHttpRequest } from '../server/HttpRequest';
|
||||
import type { Guarded } from './GuardedStream';
|
||||
import { guardStream } from './GuardedStream';
|
||||
|
||||
export const endOfStream = promisify(eos);
|
||||
|
||||
const logger = getLoggerFor('StreamUtil');
|
||||
|
||||
/**
|
||||
|
@ -11,7 +11,8 @@
|
||||
},
|
||||
|
||||
{
|
||||
"@id": "urn:solid-server:template:IdentifierStrategy",
|
||||
"comment": "Custom pods always use the suffix strategy with their pod URL as base.",
|
||||
"@id": "urn:solid-server:default:IdentifierStrategy",
|
||||
"@type": "SingleRootIdentifierStrategy",
|
||||
"baseUrl": {
|
||||
"@id": "urn:solid-server:template:variable:baseUrl"
|
||||
@ -25,7 +26,7 @@
|
||||
"@id": "urn:solid-server:template:DataAccessor"
|
||||
},
|
||||
"identifierStrategy": {
|
||||
"@id": "urn:solid-server:template:IdentifierStrategy"
|
||||
"@id": "urn:solid-server:default:IdentifierStrategy"
|
||||
},
|
||||
"auxiliaryStrategy": {
|
||||
"@id": "urn:solid-server:default:AuxiliaryStrategy"
|
||||
|
@ -12,7 +12,7 @@
|
||||
"@id": "urn:solid-server:template:DataAccessor",
|
||||
"@type": "InMemoryDataAccessor",
|
||||
"identifierStrategy": {
|
||||
"@id": "urn:solid-server:template:IdentifierStrategy"
|
||||
"@id": "urn:solid-server:default:IdentifierStrategy"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
8
templates/container.md.hbs
Normal file
8
templates/container.md.hbs
Normal file
@ -0,0 +1,8 @@
|
||||
# Contents of {{container}}
|
||||
<ul class="container">
|
||||
{{#each children}}
|
||||
<li class="{{#if container}}container{{else}}document{{/if}}">
|
||||
<a href="{{identifier}}">{{name}}</a>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
@ -174,3 +174,12 @@ form ul.actions > li {
|
||||
display: inline;
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
ul.container > li {
|
||||
margin: 0.25em 0;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
ul.container > li.container > a {
|
||||
font-weight: 800;
|
||||
}
|
||||
|
@ -0,0 +1,95 @@
|
||||
import { namedNode as nn, quad } from '@rdfjs/data-model';
|
||||
import { BasicRepresentation } from '../../../../src/ldp/representation/BasicRepresentation';
|
||||
import { ContainerToTemplateConverter } from '../../../../src/storage/conversion/ContainerToTemplateConverter';
|
||||
import { readableToString } from '../../../../src/util/StreamUtil';
|
||||
import type { TemplateEngine } from '../../../../src/util/templates/TemplateEngine';
|
||||
import { LDP, RDF } from '../../../../src/util/Vocabularies';
|
||||
|
||||
describe('A ContainerToTemplateConverter', (): void => {
|
||||
const preferences = {};
|
||||
let templateEngine: jest.Mocked<TemplateEngine>;
|
||||
let converter: ContainerToTemplateConverter;
|
||||
|
||||
beforeEach(async(): Promise<void> => {
|
||||
templateEngine = {
|
||||
render: jest.fn().mockReturnValue(Promise.resolve('<html>')),
|
||||
};
|
||||
converter = new ContainerToTemplateConverter(templateEngine, 'text/html');
|
||||
});
|
||||
|
||||
it('supports containers.', async(): Promise<void> => {
|
||||
const container = { path: 'http://test.com/foo/bar/container/' };
|
||||
const representation = new BasicRepresentation([], 'internal/quads', false);
|
||||
await expect(converter.canHandle({ identifier: container, representation, preferences }))
|
||||
.resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it('does not support documents.', async(): Promise<void> => {
|
||||
const document = { path: 'http://test.com/foo/bar/document' };
|
||||
const representation = new BasicRepresentation([], 'internal/quads', false);
|
||||
await expect(converter.canHandle({ identifier: document, representation, preferences }))
|
||||
.rejects.toThrow('Can only convert containers');
|
||||
});
|
||||
|
||||
it('calls the template with the contained resources.', async(): Promise<void> => {
|
||||
const container = { path: 'http://test.com/foo/bar/my-container/' };
|
||||
const representation = new BasicRepresentation([
|
||||
quad(nn(container.path), RDF.terms.type, LDP.terms.BasicContainer),
|
||||
quad(nn(container.path), LDP.terms.contains, nn(`${container.path}b`)),
|
||||
quad(nn(container.path), LDP.terms.contains, nn(`${container.path}a`)),
|
||||
quad(nn(container.path), LDP.terms.contains, nn(`${container.path}a`)),
|
||||
quad(nn(container.path), LDP.terms.contains, nn(`${container.path}ccc`)),
|
||||
quad(nn(container.path), LDP.terms.contains, nn(`${container.path}d/`)),
|
||||
quad(nn(`${container.path}d/`), LDP.terms.contains, nn(`${container.path}d`)),
|
||||
], 'internal/quads', false);
|
||||
const converted = await converter.handle({ identifier: container, representation, preferences });
|
||||
|
||||
expect(converted.binary).toBe(true);
|
||||
expect(converted.metadata.contentType).toBe('text/html');
|
||||
await expect(readableToString(converted.data)).resolves.toBe('<html>');
|
||||
|
||||
expect(templateEngine.render).toHaveBeenCalledTimes(1);
|
||||
expect(templateEngine.render).toHaveBeenCalledWith({
|
||||
container: 'my-container',
|
||||
children: [
|
||||
{
|
||||
identifier: `${container.path}d/`,
|
||||
name: 'd/',
|
||||
container: true,
|
||||
},
|
||||
{
|
||||
identifier: `${container.path}a`,
|
||||
name: 'a',
|
||||
container: false,
|
||||
},
|
||||
{
|
||||
identifier: `${container.path}b`,
|
||||
name: 'b',
|
||||
container: false,
|
||||
},
|
||||
{
|
||||
identifier: `${container.path}ccc`,
|
||||
name: 'ccc',
|
||||
container: false,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('converts the root container.', async(): Promise<void> => {
|
||||
const container = { path: 'http://test.com/' };
|
||||
const representation = new BasicRepresentation([
|
||||
quad(nn(container.path), RDF.terms.type, LDP.terms.BasicContainer),
|
||||
quad(nn(container.path), LDP.terms.contains, nn(`${container.path}a`)),
|
||||
quad(nn(container.path), LDP.terms.contains, nn(`${container.path}b`)),
|
||||
quad(nn(container.path), LDP.terms.contains, nn(`${container.path}c`)),
|
||||
], 'internal/quads', false);
|
||||
await converter.handle({ identifier: container, representation, preferences });
|
||||
|
||||
expect(templateEngine.render).toHaveBeenCalledTimes(1);
|
||||
expect(templateEngine.render).toHaveBeenCalledWith({
|
||||
container: '/',
|
||||
children: expect.objectContaining({ length: 3 }),
|
||||
});
|
||||
});
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user