Commit af189195 authored by Fabien Amarger's avatar Fabien Amarger
Browse files

feat(v1.0.0): Fire !!

parent 4f0bdf3da320
{
"name": "@logilab/libview",
"version": "0.6.4",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
......@@ -98,12 +98,6 @@
"integrity": "sha512-0ARSQootUG1RljH2HncpsY2TJBfGQIKOOi7kxzUY6z54ePu/ZD+wJA8zI2Q6v8rol2qpG/rvqsReco8zNMPvhQ==",
"dev": true
},
"@types/rdflib": {
"version": "0.17.1",
"resolved": "https://registry.npmjs.org/@types/rdflib/-/rdflib-0.17.1.tgz",
"integrity": "sha512-5vvVZrRYeVLmaG76BjJvlIMkWUcyn2fWvCoylVvm2yw5Sxun0i7c093wK37MNcwMZOyHzjGv77PGKbLhnpGv/Q==",
"dev": true
},
"@types/shelljs": {
"version": "0.7.7",
"resolved": "https://registry.npmjs.org/@types/shelljs/-/shelljs-0.7.7.tgz",
......
{
"name": "@logilab/libview",
"version": "0.6.4",
"version": "1.0.0",
"description": "Library for view specification and definition for a data browser.",
"repository": {
"type": "mercurial",
"url": "https://forge.extranet.logilab.fr/open-source/LDBrowser/libview"
"url": "https://forge.extranet.logilab.fr/open-source/SemWeb/libview"
},
"keywords": [
"DataBrowser",
......@@ -18,7 +18,6 @@
"devDependencies": {
"@types/chai": "^4.1.7",
"@types/mocha": "^5.2.5",
"@types/rdflib": "^0.17.0",
"@types/xhr-mock": "^2.0.0",
"chai": "^4.2.0",
"copy-webpack-plugin": "^4.5.2",
......
/*******************************************************************************
* Copyright 2003-2018 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
* contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
*
* This file is part of CubicWeb.
*
* CubicWeb is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free
* Software Foundation, either version 2.1 of the License, or (at your option)
* any later version.
*
* CubicWeb is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
import {
serializableResult,
RenderingResult,
NO_LANGUAGE,
uriEquivalents,
newResourceFromUri,
cloneResource,
sameResources,
probeResourceWith,
Resource,
withTimeout,
} from './application';
import * as chai from 'chai';
const expect = chai.expect;
const fail = chai.assert.fail;
describe('@logilab/libview/application', () => {
it('should handle a promise that complete correctly', async () => {
let promise = new Promise<string>(
(
resolve: (result: string) => void,
reject: (reason: any) => void
) => {
resolve('test');
}
);
let settled = false;
await withTimeout(promise, 100)
.then((value: string) => {
expect(value).equals('test');
settled = true;
})
.catch((reason: any) => {
fail('expected the promise to completed successfully');
});
expect(settled).equals(true);
});
it('should handle a promise that is rejected', async () => {
let promise = new Promise<string>(
(
resolve: (result: string) => void,
reject: (reason: any) => void
) => {
reject('test');
}
);
let settled = false;
await withTimeout(promise, 100)
.then((value: string) => {
fail('expected the promise to be rejected');
})
.catch((reason: any) => {
expect(reason).equals('test');
settled = true;
});
expect(settled).equals(true);
});
it('should handle a promise that times out', async () => {
let promise = new Promise<string>(
(
resolve: (result: string) => void,
reject: (reason: any) => void
) => {
// do nothing
}
);
let settled = false;
await withTimeout(promise, 100)
.then((value: string) => {
settled = true;
})
.catch((reason: any) => {
expect(reason).equals('timeout');
});
expect(settled).equals(false);
});
it('should correctly compute equivalent URIs on simple URI', () => {
let original = 'http://google.com/';
let equivalents = uriEquivalents(original);
expect(equivalents.length).equals(1);
expect(equivalents[0]).equals(original);
});
it('should correctly compute equivalent URIs with accented characters', () => {
let original = 'http://dbpedia.org/resource/Paul_Valéry';
let expected = [
original,
'http://dbpedia.org/resource/Paul_Val\u00E9ry',
'http://dbpedia.org/resource/Paul_Val%C3%A9ry',
];
let equivalents = uriEquivalents(original);
expect(equivalents.length).equals(expected.length);
expected.forEach((e: string) => {
expect(equivalents).contains(e);
});
});
it('should correctly compute equivalent URIs for already unicode escaped strings', () => {
let original = 'http://dbpedia.org/resource/Paul_Val\u00E9ry';
let expected = [
original,
'http://dbpedia.org/resource/Paul_Valéry',
'http://dbpedia.org/resource/Paul_Val%C3%A9ry',
];
let equivalents = uriEquivalents(original);
expect(equivalents.length).equals(expected.length);
expected.forEach((e: string) => {
expect(equivalents).contains(e);
});
});
it('should correctly compute equivalent URIs for already percent encoded strings', () => {
let original = 'http://dbpedia.org/resource/Paul_Val%C3%A9ry';
let expected = [
original,
'http://dbpedia.org/resource/Paul_Valéry',
'http://dbpedia.org/resource/Paul_Val\u00E9ry',
];
let equivalents = uriEquivalents(original);
expect(equivalents.length).equals(expected.length);
expected.forEach((e: string) => {
expect(equivalents).contains(e);
});
});
it('should fully clone resources', () => {
let original = 'http://dbpedia.org/resource/Paul_Valéry';
let resource1 = newResourceFromUri(original);
let resource2 = cloneResource(resource1);
// modify resource 2
resource2.uri = 'xxx';
resource2.uris.push('xxx');
expect(resource1.uri).equals(original);
expect(resource2.uris.length).equals(resource1.uris.length + 1);
});
it('should compare same resource with URIs in different order', () => {
let resource1 = newResourceFromUri(
'http://dbpedia.org/resource/Paul_Valéry'
);
let resource2 = newResourceFromUri(
'http://dbpedia.org/resource/Paul_Val%C3%A9ry'
);
expect(sameResources(resource1, resource2)).equals(true);
});
it('should compare different resources', () => {
let resource1 = newResourceFromUri(
'http://dbpedia.org/resource/Paul_Valéry'
);
let resource2 = newResourceFromUri(
'http://dbpedia.org/resource/Paul_Valery'
);
expect(sameResources(resource1, resource2)).equals(false);
});
it('should probe all URIs in a resource', async () => {
let resource = newResourceFromUri(
'http://dbpedia.org/resource/Paul_Valéry'
);
let probed: string[] = [];
let probe = function (uri: string): Promise<string> {
return new Promise<string>(
(
resolve: (result: string) => void,
reject: (reason: any) => void
) => {
probed.push(uri);
resolve(uri);
}
);
};
await probeResourceWith(resource, probe)
.then((target: Resource) => {
expect(probed.length).equals(resource.uris.length);
resource.uris.forEach((uri: string) => {
expect(probed).contain(uri);
});
})
.catch((reason: any) => {
fail(reason);
});
});
it('should only return successfully probed URIs', async () => {
let resource = {
uri: 'http://example.com/a',
uris: [
'http://example.com/a',
'http://example.com/b',
'http://example.com/c',
],
};
let expected = ['http://example.com/b', 'http://example.com/c'];
let probe = function (uri: string): Promise<string> {
return new Promise<string>(
(
resolve: (result: string) => void,
reject: (reason: any) => void
) => {
if (uri === 'http://example.com/a') {
reject('none');
} else {
resolve(uri);
}
}
);
};
await probeResourceWith(resource, probe)
.then((target: Resource) => {
expect(target.uris.length).equals(expected.length);
target.uris.forEach((uri: string) => {
expect(expected).contain(uri);
});
expect(target.uris).contains(target.uri);
expect(target.uris).does.not.contain('http://example.com/a');
})
.catch((reason: any) => {
fail(reason);
});
});
it('should use a timeout when probing URIs', async () => {
let resource = {
uri: 'http://example.com/a',
uris: ['http://example.com/a', 'http://example.com/b'],
};
let expected = ['http://example.com/a'];
let probe = function (uri: string): Promise<string> {
return new Promise<string>(
(
resolve: (result: string) => void,
reject: (reason: any) => void
) => {
if (uri === 'http://example.com/a') {
resolve('http://example.com/a');
}
}
);
};
await probeResourceWith(resource, probe)
.then((target: Resource) => {
expect(target.uris.length).equals(expected.length);
target.uris.forEach((uri: string) => {
expect(expected).contain(uri);
});
expect(target.uris).contains(target.uri);
})
.catch((reason: any) => {
fail(reason);
});
});
it('should serialize a rendering result without the dom', () => {
let r1: RenderingResult = {
dom: new Object() as HTMLElement,
viewId: 'test',
suggestedResources: [],
evaluations: [
{
viewId: 'xxx',
priority: 10,
},
],
languages: [NO_LANGUAGE],
};
let r2 = serializableResult(r1);
expect(r2.dom).equals(null);
expect(r2.viewId).equals(r1.viewId);
expect(r2.suggestedResources).equals(r1.suggestedResources);
expect(r2.evaluations).equals(r1.evaluations);
expect(r2.languages).equals(r1.languages);
});
});
/*******************************************************************************
* Copyright 2003-2018 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
* contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
*
* This file is part of CubicWeb.
*
* CubicWeb is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free
* Software Foundation, either version 2.1 of the License, or (at your option)
* any later version.
*
* CubicWeb is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
import * as $rdf from 'rdflib';
/**
* Gets the equivalent uri with unicode escape sequences
* @param uri The original uri
*/
function uriUnicodeEscape(uri: string): string {
let result = '';
for (let i = 0; i !== uri.length; i++) {
let cc = uri.charCodeAt(i);
if (cc >= 0x80) {
let offset = cc >= 0xd800 && cc <= 0xdbff ? 1 : 0;
let cp = uri.codePointAt(i);
if (cp === undefined) {
console.log(
'Failed to identify codepoint at ' + i + " in '" + uri + '"'
);
i += 1;
} else {
let cpAsString = cp.toString(16).toUpperCase();
result += '\\u';
let leading = 4 + offset * 4 - cpAsString.length;
while (leading > 0) {
result += '0';
leading--;
}
result += cpAsString;
i += offset;
}
} else {
result += uri.charAt(i);
}
}
return result;
}
/**
* Gets the equivalent extended uri
* @param uri The original uri
*/
function uriUnicodeUnescape(uri: string): string {
let result = '';
for (let i = 0; i !== uri.length; i++) {
let c = uri.charAt(i);
if (c === '\\') {
let n = uri.charAt(i + 1);
if (n === 'u') {
// \ u XXXX for unicode characters in the BMP
let codepoint = Number.parseInt(
uri.substring(i + 2, i + 6),
16
);
result += String.fromCodePoint(codepoint);
i += 5;
} else if (n === 'U') {
// \ U XXXXXXXX for unicode characters outside the BMP
let codepoint = Number.parseInt(
uri.substring(i + 2, i + 10),
16
);
result += String.fromCodePoint(codepoint);
i += 9;
} else {
// \C for C, where C is any character other than 0, a, t, b, r, n, f, u and U
result += n;
i++;
}
} else {
// not the start of an escape sequence, replace as is
result += c;
}
}
return result;
}
/**
* Gets the sets of equivalents URIs (escaped and unescaped)
* @param uri Thr original URI
*/
export function uriEquivalents(uri: string): string[] {
let canonical = uriUnicodeUnescape(decodeURI(uri));
let uriEncoded = encodeURI(canonical);
let uriUnicode = uriUnicodeEscape(canonical);
let result = [uri];
if (result.indexOf(canonical) < 0) result.push(canonical);
if (result.indexOf(uriEncoded) < 0) result.push(uriEncoded);
if (result.indexOf(uriUnicode) < 0) result.push(uriUnicode);
return result;
}
/**
* Represents a resource for the browser
*/
export interface Resource {
/**
* The supposed canonical URI
*/
uri: string;
/**
* The set of all URIs that refers to the same resouce,
* either because they are syntactically equivalent,
* or because they are aliases
*/
uris: string[];
}
/**
* Creates a new resource
* @param uri The 'main' URI
*/
export function newResourceFromUri(uri: string): Resource {
return {
uri: uri,
uris: uriEquivalents(uri),
};
}
/**
* Clones a resource
* @param original The original resource
*/
export function cloneResource(original: Resource): Resource {
return {
uri: original.uri,
uris: original.uris.slice(0),
};
}
/**
* Determines whether this resource matches another
* @param first The first resource
* @param second The other resource
*/
export function sameResources(first: Resource, second: Resource): boolean {
for (let i = 0; i !== first.uris.length; i++) {
if (second.uris.indexOf(first.uris[i]) >= 0) return true;
}
return false;
}
/**
* Probes the different URIs for a resource and remove the dead ones
* Returns a promise for clean version of the resource
* @param resource The resource
*/
export function probeResource(resource: Resource): Promise<Resource> {
return probeResourceWith(resource, probeResourceUri);
}
/**
* Probes the different URIs for a resource and remove the dead ones
* Returns a promise for clean version of the resource
* @param resource The resource
* @param probe The probe function to use
*/
export function probeResourceWith(
resource: Resource,
probe: (uri: string) => Promise<string>
): Promise<Resource> {
return new Promise<Resource>(
(
resolve: (result: Resource) => void,
reject: (reason: any) => void
) => {
let remaining: String[] = [];
let count = resource.uris.length;
let onFinished = function () {
count -= 1;
if (count === 0) {
let main =
remaining.indexOf(resource.uri) >= 0
? resource.uri
: remaining[0];
resolve({uri: main, uris: remaining} as Resource);
}
};
resource.uris.forEach((uri: string) =>
withTimeout(probe(uri), 250)
.then((uri: string) => {
remaining.push(uri);
onFinished();
})
.catch(onFinished)
);
}
);
}
/**
* Wraps a promise in a new one with a timeout
* @param original The original promise
* @param wait The time to wait
*/
export function withTimeout<T>(original: Promise<T>, wait: number): Promise<T> {
return new Promise<T>(
(resolve: (result: T) => void, reject: (reason: any) => void) => {
let timeoutTriggered = false;
let onTimeout = () => {
timeoutTriggered = true;
reject('timeout');
};
let handle = setTimeout(onTimeout, wait);
original
.then((result: T) => {
if (timeoutTriggered) return;
clearTimeout(handle);
resolve(result);
})
.catch((reason: any) => {
if (timeoutTriggered) return;
clearTimeout(handle);
reject(reason);
});
}
);
}
/**
* Probes an URI for a resource to check whether it is valid
* @param uri An URI
*/
function probeResourceUri(uri: string): Promise<string> {
return new Promise<string>(
(resolve: (result: string) => void, reject: (reason: any) => void) => {
let xmlHttp = new XMLHttpRequest();
xmlHttp.onreadystatechange = function () {
if (xmlHttp.readyState === 4) {
if (xmlHttp.status < 200 || xmlHttp.status >= 300) {
return reject('HTTP error: ' + xmlHttp.status);
}
resolve(uri);
}
};
xmlHttp.open('HEAD', uri, true);
xmlHttp.send();
}
);
}
/**
* The data about a language
*/
export interface Language {
/**
* The ISO 639-2 language code
*/
iso639_2: string;