--- /dev/null
+/* eslint-env mocha */
+'use strict';
+
+const assert = require('assert');
+
+describe('RFC 8288 Header parser', function () {
+ const { parse, SyntaxError: PEGSyntaxError } = require('../../lib/rfc8288-web-linking');
+
+ function checkParser(linkHeader, expectedResult) {
+ const result = parse(linkHeader.replace(/\r?\n|\r/gm, ''));
+ assert.deepStrictEqual(result, expectedResult);
+ }
+
+ describe('§3.3 Relation Type', function () {
+ it('Must ignore occurrences after the first', function () {
+ const linkHeader = [
+ '<http://example.org/>;',
+ 'rel="start"; rel="ignore"; foo',
+ ].join('\n');
+ const expectedResult = [
+ {
+ target: 'http://example.org/',
+ attributes: [
+ {
+ extended: false,
+ name: 'rel',
+ value: 'start',
+ },
+ {
+ extended: false,
+ name: 'foo',
+ value: null,
+ },
+ ],
+ },
+ ];
+ checkParser(linkHeader, expectedResult);
+ });
+ });
+
+ describe('§3.5 Link Header Field Examples', function () {
+ it('a', function () {
+ const linkHeader = [
+ '<http://example.com/TheBook/chapter2>; rel="previous";',
+ 'title="previous chapter"',
+ ].join('\n');
+ const expectedResult = [
+ {
+ target: 'http://example.com/TheBook/chapter2',
+ attributes: [
+ {
+ extended: false,
+ name: 'rel',
+ value: 'previous',
+ },
+ {
+ extended: false,
+ name: 'title',
+ value: 'previous chapter',
+ },
+ ],
+ },
+ ];
+ checkParser(linkHeader, expectedResult);
+ });
+ it('b', function () {
+ const linkHeader = '</>; rel="http://example.net/foo"';
+ const expectedResult = [
+ {
+ target: '/',
+ attributes: [
+ {
+ extended: false,
+ name: 'rel',
+ value: 'http://example.net/foo',
+ },
+ ],
+ },
+ ];
+ checkParser(linkHeader, expectedResult);
+ });
+ it('c', function () {
+ const linkHeader = '</terms>; rel="copyright"; anchor="#foo"';
+ const expectedResult = [
+ {
+ target: '/terms',
+ attributes: [
+ {
+ extended: false,
+ name: 'rel',
+ value: 'copyright',
+ },
+ {
+ extended: false,
+ name: 'anchor',
+ value: '#foo',
+ },
+ ],
+ },
+ ];
+ checkParser(linkHeader, expectedResult);
+ });
+ it('d', function () {
+ const linkHeader = [
+ '</TheBook/chapter2>;',
+ 'rel="previous"; title*=UTF-8\'de\'letztes%20Kapitel,',
+ '</TheBook/chapter4>;',
+ 'rel="next"; title*=UTF-8\'de\'n%c3%a4chstes%20Kapitel',
+ ].join('\n');
+ const expectedResult = [
+ {
+ target: '/TheBook/chapter2',
+ attributes: [
+ {
+ extended: false,
+ name: 'rel',
+ value: 'previous',
+ },
+ {
+ extended: true,
+ name: 'title*',
+ value: 'UTF-8\'de\'letztes%20Kapitel',
+ },
+ ],
+ },
+ {
+ target: '/TheBook/chapter4',
+ attributes: [
+ {
+ extended: false,
+ name: 'rel',
+ value: 'next',
+ },
+ {
+ extended: true,
+ name: 'title*',
+ value: 'UTF-8\'de\'n%c3%a4chstes%20Kapitel',
+ },
+ ],
+ },
+ ];
+ checkParser(linkHeader, expectedResult);
+ });
+ it('e', function () {
+ const linkHeader = [
+ '<http://example.org/>;',
+ 'rel="start http://example.net/relation/other"',
+ ].join('\n');
+ const expectedResult = [
+ {
+ target: 'http://example.org/',
+ attributes: [
+ {
+ extended: false,
+ name: 'rel',
+ value: 'start http://example.net/relation/other',
+ },
+ ],
+ },
+ ];
+ checkParser(linkHeader, expectedResult);
+ });
+ it('f', function () {
+ const linkHeader = [
+ '<https://example.org/>; rel="start",',
+ ' <https://example.org/index>; rel="index"',
+ ].join('\n');
+ const expectedResult = [
+ {
+ target: 'https://example.org/',
+ attributes: [
+ {
+ extended: false,
+ name: 'rel',
+ value: 'start',
+ },
+ ],
+ },
+ {
+ target: 'https://example.org/index',
+ attributes: [
+ {
+ extended: false,
+ name: 'rel',
+ value: 'index',
+ },
+ ],
+ },
+ ];
+ checkParser(linkHeader, expectedResult);
+ });
+ }); // §3.5 Link Header Field Examples
+
+ describe('Unusual Cases', function () {
+ it('handles uris with semicolons', function () {
+ const linkHeader = [
+ '<http://example.com/strange;url;indeed>; rel="self";',
+ ].join('\n');
+ const expectedResult = [
+ {
+ target: 'http://example.com/strange;url;indeed',
+ attributes: [
+ {
+ extended: false,
+ name: 'rel',
+ value: 'self',
+ },
+ ],
+ },
+ ];
+ checkParser(linkHeader, expectedResult);
+ });
+ it('handles value-less attributes', function () {
+ const linkHeader = [
+ '</>; special; rel="special";',
+ ].join('\n');
+ const expectedResult = [
+ {
+ target: '/',
+ attributes: [
+ {
+ extended: false,
+ name: 'special',
+ value: null,
+ },
+ {
+ extended: false,
+ name: 'rel',
+ value: 'special',
+ },
+ ],
+ },
+ ];
+ checkParser(linkHeader, expectedResult);
+ });
+ }); // Unusual Cases
+
+ describe('Extended Values', function () {
+ it('decodes extended value', function () {
+ const extendedValue = 'UTF-8\'\'%c2%a3%20and%20%e2%82%ac%20rates';
+ const expectedResult = {
+ encoding: 'UTF-8',
+ language: null,
+ value: '£ and € rates',
+ };
+ const result = parse(extendedValue, { startRule: 'extendedValue' });
+ assert.deepStrictEqual(result, expectedResult);
+ });
+ }); // Extended Values
+
+ describe('Failures', function () {
+ it('does not parse invalid header', function () {
+ const linkHeader = 'not a link header';
+ const expectedResult = [];
+ try {
+ checkParser(linkHeader, expectedResult);
+ assert.fail('unexpected success');
+ } catch (e) {
+ assert.strictEqual(e.name, 'SyntaxError');
+ }
+ });
+ it('does not parse partial invalid header', function () {
+ const linkHeader = '<https://example.com/foo>; rel="valid", not a link header';
+ const expectedResult = [];
+ try {
+ checkParser(linkHeader, expectedResult);
+ assert.fail('unexpected success');
+ } catch (e) {
+ assert(e instanceof PEGSyntaxError);
+ assert.strictEqual(e.name, 'SyntaxError');
+ }
+ });
+ }); // Failures
+
+});
\ No newline at end of file