blob: cfba7f92d2f864866e9dfd373855c5ad88e9795e [file] [log] [blame]
/**
* @license
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { parse, stringify } from '../src';
// Test examples from the spec: https://www.w3.org/TR/2017/NOTE-selectors-states-20170223/#json-examples-converted-to-fragment-identifiers
import specExamplesRaw from './spec-examples.json';
// The JSON file has the full examples; pull out the parts we need.
const specExamples = Object.fromEntries(Object.entries(specExamplesRaw).map(
([name, { uri, obj: { selector, state } }]) =>
[name, { fragId: uri.split('#')[1], selector, state }]
));
const specialCasesToStringify = {
'Value with parentheses (to be percent-encoded)': {
fragId: 'selector(type=TextQuoteSelector,exact=example%20%28with%20parentheses%29)',
selector: {
type: 'TextQuoteSelector',
exact: 'example (with parentheses)',
},
},
};
describe('stringify', () => {
// Test examples in the spec, ignoring their URI encoding
for (const [name, example] of Object.entries(specExamples)) {
it(`should properly stringify (disregarding URI-encoding): '${name}'`, () => {
const result = stringify(example.selector || example.state);
assert.equal(
decodeURIComponent(result),
decodeURIComponent(example.fragId)
);
});
}
for (const [name, example] of Object.entries(specialCasesToStringify)) {
it(`should properly stringify: '${name}'`, () => {
const result = stringify(example.selector || example.state);
assert.equal(result, example.fragId);
});
}
});
const specialCasesToParse = {
'One closing parenthesis inside a value': {
fragId: 'selector(type=TextQuoteSelector,exact=(not)%20a%20problem)',
selector: {
type: 'TextQuoteSelector',
exact: '(not) a problem',
},
},
'Two closing parenthesis inside a value': {
fragId: 'selector(type=TextQuoteSelector,exact=Hey))%20this%20breaks)',
selector: {
type: 'TextQuoteSelector',
exact: 'Hey)) this breaks',
},
},
'Two closing parentheses: one of value, one of selector': {
fragId: 'selector(type=TextQuoteSelector,exact=example%20(that%20fails))',
selector: {
type: 'TextQuoteSelector',
exact: 'example (that fails)',
},
},
'Three closing parentheses: one of the value, two of nested selectors': {
// Example from <https://github.com/w3c/web-annotation/issues/443>
fragId: `
selector(
type=RangeSelector,
startSelector=selector(type=TextQuoteSelector,exact=(but),
endSelector=selector(type=TextQuoteSelector,exact=crazy))
)
`.replace(/\s/g, ''),
selector: {
type: 'RangeSelector',
startSelector: {
type: 'TextQuoteSelector',
exact: '(but',
},
endSelector: {
type: 'TextQuoteSelector',
exact: 'crazy)',
},
},
},
};
describe('parse', () => {
const allCasesToParse = { ...specExamples, ...specialCasesToParse };
for (const [name, example] of Object.entries(allCasesToParse)) {
it(`should properly parse: ${name}`, () => {
const expected = (example.selector !== undefined)
? { selector: example.selector }
: { state: example.state };
const result = parse(example.fragId);
assert.deepEqual(result, expected);
});
}
it('should throw when given an unknown type of fragment identifier', () => {
assert.throws(() => parse('section4'));
assert.throws(() => parse('t=3,8'));
});
});