diff --git a/packages/mermaid/src/diagrams/xychart/parser/xychart.jison b/packages/mermaid/src/diagrams/xychart/parser/xychart.jison index d8e0d08d0..66e160ac0 100644 --- a/packages/mermaid/src/diagrams/xychart/parser/xychart.jison +++ b/packages/mermaid/src/diagrams/xychart/parser/xychart.jison @@ -12,6 +12,15 @@ %x acc_title %x acc_descr %x acc_descr_multiline +%x chart_config +%x chart_orientation +%x x_axis +%x y_axis +%x axis_title +%x axis_data +%x axis_data_band +%x axis_data_band_capture +%x axis_data_band_str %% \%\%\{ { this.begin('open_directive'); return 'open_directive'; } ((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; } @@ -34,6 +43,34 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multiline");} [\}] { this.popState(); } [^\}]* return "acc_descr_multiline_value"; +" "*"xychart-beta" {this.begin("chart_config"); return 'XYCHART';} +" "+("vertical"|"horizontal") {this.begin("chart_orientation"); return 'chart_orientation';} +[\s]* {this.popState(); this.popState(); return 'CHART_CONFIG_END';} +[\s]* {this.popState(); return 'CHART_CONFIG_END';} + +"x-axis"" "* { this.begin("x_axis"); return "X_AXIS";} +"y-axis"" "* { this.begin("y_axis"); return "Y_AXIS";} +["] {this.begin("axis_title");} +[^"]+ {return 'AXIS_TITLE';} +["]" "*(\r?\n) {this.popState(); this.popState();} +["]" "* {this.popState(); this.begin("axis_data");} +[^\s]+" "*(\r?\n) {this.popState(); return 'AXIS_TITLE';} +[^\s]+" "* {this.begin("axis_data"); return 'AXIS_TITLE'; } + +[+-]?\d+(\.\d+)?" "*"-->"" "*[+-]?\d+(\.\d+)?" "* { return 'AXIS_RANGE_DATA';} + +[\[]" "* {this.begin("axis_data_band"); this.begin("axis_data_band_capture")} +[,]" "* {this.begin("axis_data_band_capture")} +["] {this.begin("axis_data_band_str");} +[^"]+ {return "AXIS_BAND_DATA";} +["]" "* {this.popState(); this.popState();} +[^\s]+" "* {this.popState(); return "AXIS_BAND_DATA"} +[\]]" "* {this.popState(); return "AXIS_BAND_DATA_END"} + + +[\r\n]+ {this.popState(); this.popState();} + + ["][`] { this.begin("md_string");} [^`"]+ { return "MD_STR";} @@ -42,8 +79,6 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multiline");} ["] this.popState(); [^"]* return "STR"; -" "*"xychart"" "* return 'XYCHART'; - [A-Za-z]+ return 'ALPHA'; ":" return 'COLON'; \+ return 'PLUS'; @@ -72,9 +107,14 @@ start : eol start | SPACE start | directive start - | XYCHART document + | XYCHART chartConfig CHART_CONFIG_END document + | XYCHART CHART_CONFIG_END document ; +chartConfig + : chart_orientation {yy.setOrientation($1.trim());} + ; + document : /* empty */ | document line @@ -88,8 +128,25 @@ statement : | SPACE statement | directive + | X_AXIS parseXAxis + | Y_AXIS parseYAxis ; +parseXAxis + : AXIS_TITLE {yy.setXAxisTitle($1.trim());} + | AXIS_TITLE xAxisBandData {yy.setXAxisTitle($1.trim());} + | AXIS_TITLE AXIS_RANGE_DATA {yy.setXAxisTitle($1.trim()); $$ = $2.split("-->"); yy.setXAxisRangeData(Number($$[0]), Number($$[1]));} + ; + +xAxisBandData + : AXIS_BAND_DATA xAxisBandData {yy.addXAxisBand($1.trim());} + | AXIS_BAND_DATA_END + ; + +parseYAxis + : AXIS_TITLE {yy.setYAxisTitle($1.trim());} + | AXIS_TITLE AXIS_RANGE_DATA {yy.setYAxisTitle($1.trim()); $$ = $2.split("-->"); yy.setYAxisRangeData(Number($$[0]), Number($$[1]));} + ; directive : openDirective typeDirective closeDirective @@ -115,7 +172,7 @@ argDirective ; closeDirective - : close_directive { yy.parseDirective('}%%', 'close_directive', 'quadrantChart'); } + : close_directive { yy.parseDirective('}%%', 'close_directive', 'xychart'); } ; text: alphaNumToken diff --git a/packages/mermaid/src/diagrams/xychart/parser/xychart.jison.spec.ts b/packages/mermaid/src/diagrams/xychart/parser/xychart.jison.spec.ts new file mode 100644 index 000000000..029e90356 --- /dev/null +++ b/packages/mermaid/src/diagrams/xychart/parser/xychart.jison.spec.ts @@ -0,0 +1,140 @@ +// @ts-ignore: TODO Fix ts errors +import { parser } from './xychart.jison'; +import { Mock, vi } from 'vitest'; + +const parserFnConstructor = (str: string) => { + return () => { + parser.parse(str); + }; +}; + +const mockDB: Record> = { + parseDirective: vi.fn(), + setOrientation: vi.fn(), + setXAxisTitle: vi.fn(), + setXAxisRangeData: vi.fn(), + addXAxisBand: vi.fn(), + setYAxisTitle: vi.fn(), + setYAxisRangeData: vi.fn(), + addYAxisBand: vi.fn(), +}; + +function clearMocks() { + for (const key in mockDB) { + mockDB[key].mockRestore(); + } +} + +describe('Testing xychart jison file', () => { + beforeEach(() => { + parser.yy = mockDB; + clearMocks(); + }); + + it('should throw error if xychart-beta text is not there', () => { + const str = 'xychart-beta-1'; + expect(parserFnConstructor(str)).toThrow(); + }); + + it('should not throw error if only xychart is there', () => { + const str = 'xychart-beta'; + expect(parserFnConstructor(str)).not.toThrow(); + }); + + it('should be able to parse directive', () => { + const str = + '%%{init: {"xychart": {"chartWidth": 600, "chartHeight": 600} } }%% \n xychart-beta'; + expect(parserFnConstructor(str)).not.toThrow(); + expect(mockDB.parseDirective.mock.calls[0]).toEqual(['%%{', 'open_directive']); + expect(mockDB.parseDirective.mock.calls[1]).toEqual(['init', 'type_directive']); + expect(mockDB.parseDirective.mock.calls[2]).toEqual([ + '{"xychart": {"chartWidth": 600, "chartHeight": 600} }', + 'arg_directive', + ]); + expect(mockDB.parseDirective.mock.calls[3]).toEqual(['}%%', 'close_directive', 'xychart']); + }); + + it('parse chart orientation', () => { + let str = 'xychart-beta vertical'; + expect(parserFnConstructor(str)).not.toThrow(); + expect(mockDB.setOrientation).toHaveBeenCalledWith('vertical'); + + clearMocks(); + + str = 'xychart-beta horizontal '; + expect(parserFnConstructor(str)).not.toThrow(); + expect(mockDB.setOrientation).toHaveBeenCalledWith('horizontal'); + + str = 'xychart-beta abc'; + expect(parserFnConstructor(str)).toThrow(); + }); + + it('parse x-axis', () => { + let str = 'xychart-beta \nx-axis xAxisName\n'; + expect(parserFnConstructor(str)).not.toThrow(); + expect(mockDB.setXAxisTitle).toHaveBeenCalledWith('xAxisName'); + + clearMocks(); + + str = 'xychart-beta \nx-axis xAxisName \n'; + expect(parserFnConstructor(str)).not.toThrow(); + expect(mockDB.setXAxisTitle).toHaveBeenCalledWith('xAxisName'); + + clearMocks(); + + str = 'xychart-beta \n x-axis "xAxisName has space"\n'; + expect(parserFnConstructor(str)).not.toThrow(); + expect(mockDB.setXAxisTitle).toHaveBeenCalledWith('xAxisName has space'); + + clearMocks(); + + str = 'xychart-beta \n x-axis " xAxisName has space " \n'; + expect(parserFnConstructor(str)).not.toThrow(); + expect(mockDB.setXAxisTitle).toHaveBeenCalledWith('xAxisName has space'); + + clearMocks(); + str = 'xychart-beta \nx-axis xAxisName 45.5 --> 33 \n'; + expect(parserFnConstructor(str)).not.toThrow(); + expect(mockDB.setXAxisTitle).toHaveBeenCalledWith('xAxisName'); + expect(mockDB.setXAxisRangeData).toHaveBeenCalledWith(45.5, 33); + + clearMocks(); + + str = 'xychart-beta \nx-axis xAxisName [ "cat1" , cat2 ] \n'; + expect(parserFnConstructor(str)).not.toThrow(); + expect(mockDB.setXAxisTitle).toHaveBeenCalledWith('xAxisName'); + expect(mockDB.addXAxisBand).toHaveBeenCalledTimes(2); + expect(mockDB.addXAxisBand).toHaveBeenNthCalledWith(1, "cat2"); + expect(mockDB.addXAxisBand).toHaveBeenNthCalledWith(2, "cat1"); + }); + it('parse y-axis', () => { + let str = 'xychart-beta \ny-axis yAxisName\n'; + expect(parserFnConstructor(str)).not.toThrow(); + expect(mockDB.setYAxisTitle).toHaveBeenCalledWith('yAxisName'); + + clearMocks(); + + str = 'xychart-beta \ny-axis yAxisName \n'; + expect(parserFnConstructor(str)).not.toThrow(); + expect(mockDB.setYAxisTitle).toHaveBeenCalledWith('yAxisName'); + + clearMocks(); + + str = 'xychart-beta \n y-axis "yAxisName has space"\n'; + expect(parserFnConstructor(str)).not.toThrow(); + expect(mockDB.setYAxisTitle).toHaveBeenCalledWith('yAxisName has space'); + + clearMocks(); + + str = 'xychart-beta \n y-axis " yAxisName has space " \n'; + expect(parserFnConstructor(str)).not.toThrow(); + expect(mockDB.setYAxisTitle).toHaveBeenCalledWith('yAxisName has space'); + + clearMocks(); + str = 'xychart-beta \ny-axis yAxisName 45.5 --> 33 \n'; + expect(parserFnConstructor(str)).not.toThrow(); + expect(mockDB.setYAxisTitle).toHaveBeenCalledWith('yAxisName'); + expect(mockDB.setYAxisRangeData).toHaveBeenCalledWith(45.5, 33); + + }); +});