collab/mermaid/packages/parser/tests/radar.test.tsblame
View source
6dd74de1import { describe, expect, it } from 'vitest';
6dd74de2
6dd74de3import { Radar } from '../src/language/index.js';
6dd74de4import { expectNoErrorsOrAlternatives, radarParse as parse } from './test-util.js';
6dd74de5import { parse as parseAsync, MermaidParseError } from '../src/parse.js';
6dd74de6
6dd74de7const mutateGlobalSpacing = (context: string) => {
6dd74de8 return [
6dd74de9 context,
6dd74de10 ` ${context} `,
6dd74de11 `\t${context}\t`,
6dd74de12 `
6dd74de13 \t${context}
6dd74de14 `,
6dd74de15 ];
6dd74de16};
6dd74de17
6dd74de18describe('radar', () => {
6dd74de19 it.each([
6dd74de20 ...mutateGlobalSpacing('radar-beta'),
6dd74de21 ...mutateGlobalSpacing('radar-beta:'),
6dd74de22 ...mutateGlobalSpacing('radar-beta :'),
6dd74de23 ])('should handle regular radar', (context: string) => {
6dd74de24 const result = parse(context);
6dd74de25 expectNoErrorsOrAlternatives(result);
6dd74de26 expect(result.value.$type).toBe(Radar.$type);
6dd74de27 });
6dd74de28
6dd74de29 describe('should handle title, accDescr, and accTitle', () => {
6dd74de30 it.each([
6dd74de31 ...mutateGlobalSpacing(' title My Title'),
6dd74de32 ...mutateGlobalSpacing('\n title My Title'),
6dd74de33 ])('should handle title', (context: string) => {
6dd74de34 const result = parse(`radar-beta${context}`);
6dd74de35 expectNoErrorsOrAlternatives(result);
6dd74de36 expect(result.value.$type).toBe(Radar.$type);
6dd74de37
6dd74de38 const { title } = result.value;
6dd74de39 expect(title).toBe('My Title');
6dd74de40 });
6dd74de41
6dd74de42 it.each([
6dd74de43 ...mutateGlobalSpacing(' accDescr: My Accessible Description'),
6dd74de44 ...mutateGlobalSpacing('\n accDescr: My Accessible Description'),
6dd74de45 ])('should handle accDescr', (context: string) => {
6dd74de46 const result = parse(`radar-beta${context}`);
6dd74de47 expectNoErrorsOrAlternatives(result);
6dd74de48 expect(result.value.$type).toBe(Radar.$type);
6dd74de49
6dd74de50 const { accDescr } = result.value;
6dd74de51 expect(accDescr).toBe('My Accessible Description');
6dd74de52 });
6dd74de53
6dd74de54 it.each([
6dd74de55 ...mutateGlobalSpacing(' accTitle: My Accessible Title'),
6dd74de56 ...mutateGlobalSpacing('\n accTitle: My Accessible Title'),
6dd74de57 ])('should handle accTitle', (context: string) => {
6dd74de58 const result = parse(`radar-beta${context}`);
6dd74de59 expectNoErrorsOrAlternatives(result);
6dd74de60 expect(result.value.$type).toBe(Radar.$type);
6dd74de61
6dd74de62 const { accTitle } = result.value;
6dd74de63 expect(accTitle).toBe('My Accessible Title');
6dd74de64 });
6dd74de65
6dd74de66 it.each([
6dd74de67 ...mutateGlobalSpacing(
6dd74de68 ' title My Title\n accDescr: My Accessible Description\n accTitle: My Accessible Title'
6dd74de69 ),
6dd74de70 ...mutateGlobalSpacing(
6dd74de71 '\n title My Title\n accDescr: My Accessible Description\n accTitle: My Accessible Title'
6dd74de72 ),
6dd74de73 ])('should handle title + accDescr + accTitle', (context: string) => {
6dd74de74 const result = parse(`radar-beta${context}`);
6dd74de75 expectNoErrorsOrAlternatives(result);
6dd74de76 expect(result.value.$type).toBe(Radar.$type);
6dd74de77
6dd74de78 const { title, accDescr, accTitle } = result.value;
6dd74de79 expect(title).toBe('My Title');
6dd74de80 expect(accDescr).toBe('My Accessible Description');
6dd74de81 expect(accTitle).toBe('My Accessible Title');
6dd74de82 });
6dd74de83 });
6dd74de84
6dd74de85 describe('should handle axis', () => {
6dd74de86 it.each([`axis my-axis`, `axis my-axis["My Axis Label"]`])(
6dd74de87 'should handle one axis',
6dd74de88 (context: string) => {
6dd74de89 const result = parse(`radar-beta\n${context}`);
6dd74de90 expectNoErrorsOrAlternatives(result);
6dd74de91 expect(result.value.$type).toBe(Radar.$type);
6dd74de92
6dd74de93 const { axes } = result.value;
6dd74de94 expect(axes).toHaveLength(1);
6dd74de95 expect(axes[0].$type).toBe('Axis');
6dd74de96 expect(axes[0].name).toBe('my-axis');
6dd74de97 }
6dd74de98 );
6dd74de99
6dd74de100 it.each([
6dd74de101 `axis my-axis["My Axis Label"]
6dd74de102 axis my-axis2`,
6dd74de103 `axis my-axis, my-axis2`,
6dd74de104 `axis my-axis["My Axis Label"], my-axis2`,
6dd74de105 `axis my-axis, my-axis2["My Second Axis Label"]`,
6dd74de106 ])('should handle multiple axes', (context: string) => {
6dd74de107 const result = parse(`radar-beta\n${context}`);
6dd74de108 expectNoErrorsOrAlternatives(result);
6dd74de109 expect(result.value.$type).toBe(Radar.$type);
6dd74de110
6dd74de111 const { axes } = result.value;
6dd74de112 expect(axes).toHaveLength(2);
6dd74de113 expect(axes.every((axis) => axis.$type === 'Axis')).toBe(true);
6dd74de114 expect(axes[0].name).toBe('my-axis');
6dd74de115 expect(axes[1].name).toBe('my-axis2');
6dd74de116 });
6dd74de117
6dd74de118 it.each([
6dd74de119 `axis my-axis["My Axis Label"]
6dd74de120 axis my-axis2["My Second Axis Label"]`,
6dd74de121 `axis my-axis ["My Axis Label"], my-axis2\t["My Second Axis Label"]`,
6dd74de122 ])('should handle axis labels', (context: string) => {
6dd74de123 const result = parse(`radar-beta\n${context}`);
6dd74de124 expectNoErrorsOrAlternatives(result);
6dd74de125 expect(result.value.$type).toBe(Radar.$type);
6dd74de126
6dd74de127 const { axes } = result.value;
6dd74de128 expect(axes).toHaveLength(2);
6dd74de129 expect(axes[0].name).toBe('my-axis');
6dd74de130 expect(axes[0].label).toBe('My Axis Label');
6dd74de131 expect(axes[1].name).toBe('my-axis2');
6dd74de132 expect(axes[1].label).toBe('My Second Axis Label');
6dd74de133 });
6dd74de134
6dd74de135 it('should not allow empty axis names', () => {
6dd74de136 const result = parse(`radar-beta
6dd74de137 axis`);
6dd74de138 expect(result.parserErrors).not.toHaveLength(0);
6dd74de139 });
6dd74de140
6dd74de141 it('should not allow non-comma separated axis names', () => {
6dd74de142 const result = parse(`radar-beta
6dd74de143 axis my-axis my-axis2`);
6dd74de144 expect(result.parserErrors).not.toHaveLength(0);
6dd74de145 });
6dd74de146 });
6dd74de147
6dd74de148 describe('should handle curves', () => {
6dd74de149 it.each([
6dd74de150 `radar-beta
6dd74de151 curve my-curve`,
6dd74de152 `radar-beta
6dd74de153 curve my-curve["My Curve Label"]`,
6dd74de154 ])('should not allow curves without axes', (context: string) => {
6dd74de155 const result = parse(`radar-beta${context}`);
6dd74de156 expect(result.parserErrors).not.toHaveLength(0);
6dd74de157 });
6dd74de158
6dd74de159 it.each([
6dd74de160 `radar-beta
6dd74de161 axis my-axis
6dd74de162 curve my-curve`,
6dd74de163 `radar-beta
6dd74de164 axis my-axis
6dd74de165 curve my-curve["My Curve Label"]`,
6dd74de166 ])('should not allow curves without entries', (context: string) => {
6dd74de167 const result = parse(`radar-beta${context}`);
6dd74de168 expect(result.parserErrors).not.toHaveLength(0);
6dd74de169 });
6dd74de170
6dd74de171 it.each([
6dd74de172 `curve my-curve { 1 }`,
6dd74de173 `curve my-curve {
6dd74de174 1
6dd74de175 }`,
6dd74de176 `curve my-curve {
6dd74de177
6dd74de178 1
6dd74de179
6dd74de180 }`,
6dd74de181 ])('should handle one curve with one entry', (context: string) => {
6dd74de182 const result = parse(`radar-beta\naxis my-axis\n${context}`);
6dd74de183 expectNoErrorsOrAlternatives(result);
6dd74de184 expect(result.value.$type).toBe(Radar.$type);
6dd74de185
6dd74de186 const { curves } = result.value;
6dd74de187 expect(curves).toHaveLength(1);
6dd74de188 expect(curves[0].$type).toBe('Curve');
6dd74de189 expect(curves[0].name).toBe('my-curve');
6dd74de190 expect(curves[0].entries).toHaveLength(1);
6dd74de191 expect(curves[0].entries[0].$type).toBe('Entry');
6dd74de192 expect(curves[0].entries[0].value).toBe(1);
6dd74de193 });
6dd74de194
6dd74de195 it.each([
6dd74de196 `curve my-curve { my-axis 1 }`,
6dd74de197 `curve my-curve { my-axis : 1 }`,
6dd74de198 `curve my-curve {
6dd74de199 my-axis: 1
6dd74de200 }`,
6dd74de201 ])('should handle one curve with one detailed entry', (context: string) => {
6dd74de202 const result = parse(`radar-beta\naxis my-axis\n${context}`);
6dd74de203 expectNoErrorsOrAlternatives(result);
6dd74de204 expect(result.value.$type).toBe(Radar.$type);
6dd74de205
6dd74de206 const { curves } = result.value;
6dd74de207 expect(curves).toHaveLength(1);
6dd74de208 expect(curves[0].$type).toBe('Curve');
6dd74de209 expect(curves[0].name).toBe('my-curve');
6dd74de210 expect(curves[0].entries).toHaveLength(1);
6dd74de211 expect(curves[0].entries[0].$type).toBe('Entry');
6dd74de212 expect(curves[0].entries[0].value).toBe(1);
6dd74de213 expect(curves[0].entries[0]?.axis?.$refText).toBe('my-axis');
6dd74de214 });
6dd74de215
6dd74de216 it.each([
6dd74de217 `curve my-curve { ax1 1, ax2 2 }`,
6dd74de218 `curve my-curve {
6dd74de219 ax1 1,
6dd74de220 ax2 2
6dd74de221 }`,
6dd74de222 `curve my-curve["My Curve Label"] {
6dd74de223 ax1: 1, ax2: 2
6dd74de224 }`,
6dd74de225 ])('should handle one curve with multiple detailed entries', (context: string) => {
6dd74de226 const result = parse(`radar-beta\naxis ax1, ax1\n${context}`);
6dd74de227 expectNoErrorsOrAlternatives(result);
6dd74de228 expect(result.value.$type).toBe(Radar.$type);
6dd74de229
6dd74de230 const { curves } = result.value;
6dd74de231 expect(curves).toHaveLength(1);
6dd74de232 expect(curves[0].$type).toBe('Curve');
6dd74de233 expect(curves[0].name).toBe('my-curve');
6dd74de234 expect(curves[0].entries).toHaveLength(2);
6dd74de235 expect(curves[0].entries[0].$type).toBe('Entry');
6dd74de236 expect(curves[0].entries[0].value).toBe(1);
6dd74de237 expect(curves[0].entries[0]?.axis?.$refText).toBe('ax1');
6dd74de238 expect(curves[0].entries[1].$type).toBe('Entry');
6dd74de239 expect(curves[0].entries[1].value).toBe(2);
6dd74de240 expect(curves[0].entries[1]?.axis?.$refText).toBe('ax2');
6dd74de241 });
6dd74de242
6dd74de243 it.each([
6dd74de244 `curve c1 { ax1 1, ax2 2 }
6dd74de245 curve c2 { ax1 3, ax2 4 }`,
6dd74de246 `curve c1 {
6dd74de247 ax1 1,
6dd74de248 ax2 2
6dd74de249 }
6dd74de250 curve c2 {
6dd74de251 ax1 3,
6dd74de252 ax2 4
6dd74de253 }`,
6dd74de254 `curve c1{ 1, 2 }, c2{ 3, 4 }`,
6dd74de255 ])('should handle multiple curves', (context: string) => {
6dd74de256 const result = parse(`radar-beta\naxis ax1, ax1\n${context}`);
6dd74de257 expectNoErrorsOrAlternatives(result);
6dd74de258 expect(result.value.$type).toBe(Radar.$type);
6dd74de259
6dd74de260 const { curves } = result.value;
6dd74de261 expect(curves).toHaveLength(2);
6dd74de262 expect(curves.every((curve) => curve.$type === 'Curve')).toBe(true);
6dd74de263 expect(curves[0].name).toBe('c1');
6dd74de264 expect(curves[1].name).toBe('c2');
6dd74de265 });
6dd74de266
6dd74de267 it('should not allow empty curve names', () => {
6dd74de268 const result = parse(`radar-beta
6dd74de269 axis my-axis
6dd74de270 curve`);
6dd74de271 expect(result.parserErrors).not.toHaveLength(0);
6dd74de272 });
6dd74de273
6dd74de274 it('should not allow number and detailed entries in the same curve', () => {
6dd74de275 const result = parse(`radar-beta
6dd74de276 axis ax1, ax2
6dd74de277 curve my-curve { 1, ax1 2 }`);
6dd74de278 expect(result.parserErrors).not.toHaveLength(0);
6dd74de279 });
6dd74de280
6dd74de281 it('should not allow non-comma separated entries', () => {
6dd74de282 const result = parse(`radar-beta
6dd74de283 axis ax1, ax2
6dd74de284 curve my-curve { ax1 1 ax2 2 }`);
6dd74de285 expect(result.parserErrors).not.toHaveLength(0);
6dd74de286 });
6dd74de287 });
6dd74de288
6dd74de289 describe('should handle options', () => {
6dd74de290 it.each([`ticks 5`, `min 50`, `max 50`])(
6dd74de291 `should handle number option %s`,
6dd74de292 (context: string) => {
6dd74de293 const result = parse(`radar-beta
6dd74de294 axis ax1, ax2
6dd74de295 curve c1 { ax1 1, ax2 2 }
6dd74de296 ${context}`);
6dd74de297 expectNoErrorsOrAlternatives(result);
6dd74de298 expect(result.value.$type).toBe(Radar.$type);
6dd74de299
6dd74de300 const { options } = result.value;
6dd74de301 expect(options).toBeDefined();
6dd74de302 const option = options.find((option) => option.name === context.split(' ')[0]);
6dd74de303 expect(option).toBeDefined();
6dd74de304 expect(option?.value).toBe(Number(context.split(' ')[1]));
6dd74de305 }
6dd74de306 );
6dd74de307
6dd74de308 it.each([`graticule circle`, `graticule polygon`])(
6dd74de309 `should handle string option %s`,
6dd74de310 (context: string) => {
6dd74de311 const result = parse(`radar-beta
6dd74de312 axis ax1, ax2
6dd74de313 curve c1 { ax1 1, ax2 2 }
6dd74de314 ${context}`);
6dd74de315 expectNoErrorsOrAlternatives(result);
6dd74de316 expect(result.value.$type).toBe(Radar.$type);
6dd74de317
6dd74de318 const { options } = result.value;
6dd74de319 expect(options).toBeDefined();
6dd74de320 const option = options.find((option) => option.name === context.split(' ')[0]);
6dd74de321 expect(option).toBeDefined();
6dd74de322 expect(option?.value).toBe(context.split(' ')[1]);
6dd74de323 }
6dd74de324 );
6dd74de325
6dd74de326 it.each([`showLegend true`, `showLegend false`])(
6dd74de327 `should handle boolean option %s`,
6dd74de328 (context: string) => {
6dd74de329 const result = parse(`radar-beta
6dd74de330 axis ax1, ax2
6dd74de331 curve c1 { ax1 1, ax2 2 }
6dd74de332 ${context}`);
6dd74de333 expectNoErrorsOrAlternatives(result);
6dd74de334 expect(result.value.$type).toBe(Radar.$type);
6dd74de335
6dd74de336 const { options } = result.value;
6dd74de337 expect(options).toBeDefined();
6dd74de338 const option = options.find((option) => option.name === context.split(' ')[0]);
6dd74de339 expect(option).toBeDefined();
6dd74de340 expect(option?.value).toBe(context.split(' ')[1] === 'true');
6dd74de341 }
6dd74de342 );
6dd74de343 });
6dd74de344
6dd74de345 describe('error messages with line and column numbers', () => {
6dd74de346 it('should include line and column numbers in parser errors for radar diagrams', async () => {
6dd74de347 const invalidRadar = `radar-beta
6dd74de348 title Restaurant Comparison
6dd74de349 axis food["Food Quality"], service["Service"], price["Price"]
6dd74de350 axis ambiance["Ambiance"],
6dd74de351
6dd74de352 curve a["Restaurant A"]{4, 3, 2, 4}`;
6dd74de353
6dd74de354 try {
6dd74de355 await parseAsync('radar', invalidRadar);
6dd74de356 expect.fail('Should have thrown MermaidParseError');
6dd74de357 } catch (error: any) {
6dd74de358 expect(error).toBeInstanceOf(MermaidParseError);
6dd74de359 expect(error.message).toMatch(/line \d+/);
6dd74de360 expect(error.message).toMatch(/column \d+/);
6dd74de361 }
6dd74de362 });
6dd74de363
6dd74de364 it('should include line and column numbers for missing curve entries', async () => {
6dd74de365 const invalidRadar = `radar-beta
6dd74de366 axis my-axis
6dd74de367 curve my-curve`;
6dd74de368
6dd74de369 try {
6dd74de370 await parseAsync('radar', invalidRadar);
6dd74de371 expect.fail('Should have thrown MermaidParseError');
6dd74de372 } catch (error: any) {
6dd74de373 expect(error).toBeInstanceOf(MermaidParseError);
6dd74de374 // Line and column may be ? if not available
6dd74de375 expect(error.message).toMatch(/line (\d+|\?)/);
6dd74de376 expect(error.message).toMatch(/column (\d+|\?)/);
6dd74de377 }
6dd74de378 });
6dd74de379
6dd74de380 it('should include line and column numbers for invalid axis syntax', async () => {
6dd74de381 const invalidRadar = `radar-beta
6dd74de382 axis my-axis my-axis2`;
6dd74de383
6dd74de384 try {
6dd74de385 await parseAsync('radar', invalidRadar);
6dd74de386 expect.fail('Should have thrown MermaidParseError');
6dd74de387 } catch (error: any) {
6dd74de388 expect(error).toBeInstanceOf(MermaidParseError);
6dd74de389 expect(error.message).toMatch(/line \d+/);
6dd74de390 expect(error.message).toMatch(/column \d+/);
6dd74de391 }
6dd74de392 });
6dd74de393
6dd74de394 it('should handle lexer errors with line and column numbers', async () => {
6dd74de395 const invalidRadar = `radar-beta
6dd74de396 axis A
6dd74de397 curve B{1}
6dd74de398 invalid@symbol`;
6dd74de399
6dd74de400 try {
6dd74de401 await parseAsync('radar', invalidRadar);
6dd74de402 expect.fail('Should have thrown MermaidParseError');
6dd74de403 } catch (error: any) {
6dd74de404 expect(error).toBeInstanceOf(MermaidParseError);
6dd74de405 // Should have line and column in the error message
6dd74de406 expect(error.message).toMatch(/line (\d+|\?)/);
6dd74de407 expect(error.message).toMatch(/column (\d+|\?)/);
6dd74de408 }
6dd74de409 });
6dd74de410
6dd74de411 it('should format error message with "Parse error on line X, column Y" prefix', async () => {
6dd74de412 const invalidRadar = `radar-beta
6dd74de413 axis`;
6dd74de414
6dd74de415 try {
6dd74de416 await parseAsync('radar', invalidRadar);
6dd74de417 expect.fail('Should have thrown MermaidParseError');
6dd74de418 } catch (error: any) {
6dd74de419 expect(error).toBeInstanceOf(MermaidParseError);
6dd74de420 // Line and column may be ? if not available
6dd74de421 expect(error.message).toMatch(/Parse error on line (\d+|\?), column (\d+|\?):/);
6dd74de422 }
6dd74de423 });
6dd74de424 });
6dd74de425});