299 lines
7.6 KiB
TypeScript
299 lines
7.6 KiB
TypeScript
/*
|
|
* Copyright (C) 2026 Fluxer Contributors
|
|
*
|
|
* This file is part of Fluxer.
|
|
*
|
|
* Fluxer is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* Fluxer 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 Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
import {describe, expect, test} from 'vitest';
|
|
import {Parser} from '../parser/parser';
|
|
import {EmojiKind, NodeType, ParserFlags, TimestampStyle} from '../types/enums';
|
|
|
|
describe('Fluxer Markdown Parser', () => {
|
|
test('timestamp default', () => {
|
|
const input = 'Current time: <t:1618953630>';
|
|
const flags = 0;
|
|
const parser = new Parser(input, flags);
|
|
const {nodes: ast} = parser.parse();
|
|
|
|
expect(ast).toEqual([
|
|
{type: NodeType.Text, content: 'Current time: '},
|
|
{
|
|
type: NodeType.Timestamp,
|
|
timestamp: 1618953630,
|
|
style: TimestampStyle.ShortDateTime,
|
|
},
|
|
]);
|
|
});
|
|
|
|
test('timestamp with style', () => {
|
|
const input = '<t:1618953630:d> is the date.';
|
|
const flags = 0;
|
|
const parser = new Parser(input, flags);
|
|
const {nodes: ast} = parser.parse();
|
|
|
|
expect(ast).toEqual([
|
|
{
|
|
type: NodeType.Timestamp,
|
|
timestamp: 1618953630,
|
|
style: TimestampStyle.ShortDate,
|
|
},
|
|
{type: NodeType.Text, content: ' is the date.'},
|
|
]);
|
|
});
|
|
|
|
test('timestamp with short date & short time style', () => {
|
|
const input = '<t:1618953630:s>';
|
|
const flags = 0;
|
|
const parser = new Parser(input, flags);
|
|
const {nodes: ast} = parser.parse();
|
|
|
|
expect(ast).toEqual([
|
|
{
|
|
type: NodeType.Timestamp,
|
|
timestamp: 1618953630,
|
|
style: TimestampStyle.ShortDateShortTime,
|
|
},
|
|
]);
|
|
});
|
|
|
|
test('timestamp with short date & medium time style', () => {
|
|
const input = '<t:1618953630:S>';
|
|
const flags = 0;
|
|
const parser = new Parser(input, flags);
|
|
const {nodes: ast} = parser.parse();
|
|
|
|
expect(ast).toEqual([
|
|
{
|
|
type: NodeType.Timestamp,
|
|
timestamp: 1618953630,
|
|
style: TimestampStyle.ShortDateMediumTime,
|
|
},
|
|
]);
|
|
});
|
|
|
|
test('timestamp invalid style', () => {
|
|
const input = 'Check this <t:1618953630:z> time.';
|
|
const flags = 0;
|
|
const parser = new Parser(input, flags);
|
|
const {nodes: ast} = parser.parse();
|
|
|
|
expect(ast).toEqual([{type: NodeType.Text, content: 'Check this <t:1618953630:z> time.'}]);
|
|
});
|
|
|
|
test('timestamp non numeric', () => {
|
|
const input = 'Check <t:abc123> time.';
|
|
const flags = 0;
|
|
const parser = new Parser(input, flags);
|
|
const {nodes: ast} = parser.parse();
|
|
|
|
expect(ast).toEqual([{type: NodeType.Text, content: 'Check <t:abc123> time.'}]);
|
|
});
|
|
|
|
test('timestamp with milliseconds should not parse', () => {
|
|
const input = 'Time: <t:1618953630.123>';
|
|
const flags = 0;
|
|
const parser = new Parser(input, flags);
|
|
const {nodes: ast} = parser.parse();
|
|
|
|
expect(ast).toEqual([{type: NodeType.Text, content: 'Time: <t:1618953630.123>'}]);
|
|
});
|
|
|
|
test('timestamp with partial milliseconds should not parse', () => {
|
|
const input = '<t:1618953630.1>';
|
|
const flags = 0;
|
|
const parser = new Parser(input, flags);
|
|
const {nodes: ast} = parser.parse();
|
|
|
|
expect(ast).toEqual([{type: NodeType.Text, content: '<t:1618953630.1>'}]);
|
|
});
|
|
|
|
test('timestamp with excess millisecond precision should not parse', () => {
|
|
const input = '<t:1618953630.123456>';
|
|
const flags = 0;
|
|
const parser = new Parser(input, flags);
|
|
const {nodes: ast} = parser.parse();
|
|
|
|
expect(ast).toEqual([{type: NodeType.Text, content: '<t:1618953630.123456>'}]);
|
|
});
|
|
|
|
test('timestamp with milliseconds and style should not parse', () => {
|
|
const input = '<t:1618953630.123:R>';
|
|
const flags = 0;
|
|
const parser = new Parser(input, flags);
|
|
const {nodes: ast} = parser.parse();
|
|
|
|
expect(ast).toEqual([{type: NodeType.Text, content: '<t:1618953630.123:R>'}]);
|
|
});
|
|
|
|
test('timestamp in mixed content', () => {
|
|
const input = 'Hello <a:wave:12345> 🦶 <t:1618953630:d> <:smile:9876>';
|
|
const flags = 0;
|
|
const parser = new Parser(input, flags);
|
|
const {nodes: ast} = parser.parse();
|
|
|
|
expect(ast).toEqual([
|
|
{type: NodeType.Text, content: 'Hello '},
|
|
{
|
|
type: NodeType.Emoji,
|
|
kind: {
|
|
kind: EmojiKind.Custom,
|
|
name: 'wave',
|
|
id: '12345',
|
|
animated: true,
|
|
},
|
|
},
|
|
{type: NodeType.Text, content: ' '},
|
|
{
|
|
type: NodeType.Emoji,
|
|
kind: {
|
|
kind: EmojiKind.Standard,
|
|
raw: '🦶',
|
|
codepoints: '1f9b6',
|
|
name: expect.any(String),
|
|
},
|
|
},
|
|
{type: NodeType.Text, content: ' '},
|
|
{
|
|
type: NodeType.Timestamp,
|
|
timestamp: 1618953630,
|
|
style: TimestampStyle.ShortDate,
|
|
},
|
|
{type: NodeType.Text, content: ' '},
|
|
{
|
|
type: NodeType.Emoji,
|
|
kind: {
|
|
kind: EmojiKind.Custom,
|
|
name: 'smile',
|
|
id: '9876',
|
|
animated: false,
|
|
},
|
|
},
|
|
]);
|
|
});
|
|
|
|
test('timestamp edge cases', () => {
|
|
const inputs = ['<t:>', '<t:1618953630:>', '<t::d>', '<t:1618953630::d>', '<t:1618953630:d:R>'];
|
|
|
|
for (const input of inputs) {
|
|
const parser = new Parser(input, 0);
|
|
const {nodes: ast} = parser.parse();
|
|
|
|
expect(ast).toEqual([{type: NodeType.Text, content: input}]);
|
|
}
|
|
});
|
|
|
|
test('very large valid timestamp', () => {
|
|
const input = '<t:9999999999>';
|
|
const flags = 0;
|
|
const parser = new Parser(input, flags);
|
|
const {nodes: ast} = parser.parse();
|
|
|
|
expect(ast).toEqual([
|
|
{
|
|
type: NodeType.Timestamp,
|
|
timestamp: 9999999999,
|
|
style: TimestampStyle.ShortDateTime,
|
|
},
|
|
]);
|
|
});
|
|
|
|
test('timestamp with leading zeros', () => {
|
|
const input = '<t:0001618953630>';
|
|
const flags = 0;
|
|
const parser = new Parser(input, flags);
|
|
const {nodes: ast} = parser.parse();
|
|
|
|
expect(ast).toEqual([
|
|
{
|
|
type: NodeType.Timestamp,
|
|
timestamp: 1618953630,
|
|
style: TimestampStyle.ShortDateTime,
|
|
},
|
|
]);
|
|
});
|
|
|
|
test('timestamp in code block should not be parsed', () => {
|
|
const input = '```\n<t:1618953630>\n```';
|
|
const flags = ParserFlags.ALLOW_CODE_BLOCKS;
|
|
const parser = new Parser(input, flags);
|
|
const {nodes: ast} = parser.parse();
|
|
|
|
expect(ast).toEqual([
|
|
{
|
|
type: NodeType.CodeBlock,
|
|
language: undefined,
|
|
content: '<t:1618953630>\n',
|
|
},
|
|
]);
|
|
});
|
|
|
|
test('timestamp in inline code should not be parsed', () => {
|
|
const input = '`<t:1618953630>`';
|
|
const flags = 0;
|
|
const parser = new Parser(input, flags);
|
|
const {nodes: ast} = parser.parse();
|
|
|
|
expect(ast).toEqual([
|
|
{
|
|
type: NodeType.InlineCode,
|
|
content: '<t:1618953630>',
|
|
},
|
|
]);
|
|
});
|
|
|
|
test('timestamp with non-digit characters should not parse', () => {
|
|
const inputs = [
|
|
'<t:12a34>',
|
|
'<t:12.34>',
|
|
'<t:12,34>',
|
|
'<t:1234e5>',
|
|
'<t:+1234>',
|
|
'<t:-1234>',
|
|
'<t:1234 >',
|
|
'<t: 1234>',
|
|
'<t:1_234>',
|
|
'<t:0x1234>',
|
|
'<t:0b1010>',
|
|
'<t:123.456.789>',
|
|
];
|
|
|
|
for (const input of inputs) {
|
|
const parser = new Parser(input, 0);
|
|
const {nodes: ast} = parser.parse();
|
|
|
|
expect(ast).toEqual([{type: NodeType.Text, content: input}]);
|
|
}
|
|
});
|
|
|
|
test('timestamp with valid integer formats', () => {
|
|
const inputs = ['<t:1234>', '<t:01234>', '<t:9999999999>'];
|
|
|
|
for (const input of inputs) {
|
|
const parser = new Parser(input, 0);
|
|
const {nodes: ast} = parser.parse();
|
|
|
|
expect(ast[0].type).toBe(NodeType.Timestamp);
|
|
}
|
|
});
|
|
|
|
test('timestamp with zero value should not parse', () => {
|
|
const parser = new Parser('<t:0>', 0);
|
|
const {nodes: ast} = parser.parse();
|
|
|
|
expect(ast).toEqual([{type: NodeType.Text, content: '<t:0>'}]);
|
|
});
|
|
});
|