Skip to content

Commit

Permalink
Merge pull request #180 from WoodWing/feature/CSH-9935-chart-property
Browse files Browse the repository at this point in the history
CSH-9935: add chart property
  • Loading branch information
wwjhu authored Sep 6, 2023
2 parents f9882d7 + fcf2d33 commit 0cf4f5c
Show file tree
Hide file tree
Showing 10 changed files with 280 additions and 2 deletions.
11 changes: 11 additions & 0 deletions lib/components-schema-v1_11_x.ts
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,16 @@ const componentPropertyDefinition: {
},
},
},
{
additionalProperties: false,
required: ['type'],
properties: {
type: {
enum: ['chart'],
description: 'Adds chart options',
},
},
},
{
additionalProperties: false,
required: ['type'],
Expand Down Expand Up @@ -488,6 +498,7 @@ const componentPropertyDefinition: {
'doc-html',
'doc-slideshow',
'doc-media',
'doc-chart',
'doc-interactive',
'doc-link',
],
Expand Down
11 changes: 11 additions & 0 deletions lib/models/component-property-controls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export type ComponentPropertyControl =
| ComponentPropertyControlImageEditor
| ComponentPropertyControlDropCapital
| ComponentPropertyControlMediaProperties
| ComponentPropertyControlChartProperties
| ComponentPropertyControlFitting
| ComponentPropertyControlSlides
| ComponentPropertyControlInteractive
Expand Down Expand Up @@ -213,6 +214,16 @@ export function isMediaProperties(
return control.type === 'media-properties';
}

/**
* Enables chart-properties for an doc-chart directive
*/
export interface ComponentPropertyControlChartProperties {
type: 'chart-properties';
}
export function isChart(control: ComponentPropertyControl): control is ComponentPropertyControlChartProperties {
return control.type === 'chart-properties';
}

/**
* Enables fitting option for an image directive
*/
Expand Down
1 change: 1 addition & 0 deletions lib/models/component-set.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ export enum DirectiveType {
slideshow = 'slideshow',
link = 'link',
media = 'media',
chart = 'chart',
interactive = 'interactive',

unknown = 'unknown',
Expand Down
1 change: 1 addition & 0 deletions lib/models/components-definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ export type ComponentPropertyDataType =
| 'doc-html'
| 'doc-slideshow'
| 'doc-media'
| 'doc-chart'
| 'doc-interactive'
| 'doc-link';

Expand Down
4 changes: 4 additions & 0 deletions lib/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import {
DocContainerGroupsValidator,
DocContainerValidator,
DocMediaValidator,
DocChartValidator,
DocSlideshowValidator,
DropCapitalValidator,
FittingValidator,
Expand Down Expand Up @@ -352,5 +353,8 @@ export function getValidators(
if (semver.satisfies(version, '>=1.6.0', semVerOptions)) {
validators = validators.concat(new StripStylingOnPasteValidator(error, componentSet));
}
if (semver.satisfies(version, '>=1.11.0-next', semVerOptions)) {
validators = validators.concat(new DocChartValidator(error, componentSet));
}
return validators.length > 0 ? validators : null;
}
2 changes: 1 addition & 1 deletion lib/validators/directive-properties-validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import { Validator } from './validator';
import { DirectiveType } from '../models';

const CONTROLS = ['image-editor', 'interactive', 'media-properties'];
const CONTROLS = ['image-editor', 'interactive', 'media-properties', 'chart-properties'];

export class DirectivePropertiesValidator extends Validator {
async validate(): Promise<void> {
Expand Down
89 changes: 89 additions & 0 deletions lib/validators/doc-chart-validator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/**
* Validates usage of doc-chart directive
*
* Rules:
* - A component is not allowed to have more than one doc-chart directive.
* - A component with 1 doc-chart directive -must- have a property control type "chart-properties"
* - A component property with a "chart-properties" control type MUST be applied to a "chart" directive
* - A component without a doc-chart directive can't have any "chart-properties" control types
*/

import { Validator } from './validator';
import { ComponentProperty, DirectiveType, Component } from '../models';

export class DocChartValidator extends Validator {
async validate(): Promise<void> {
Object.values(this.componentSet.components).forEach((c: Component) => this.validateComponent(c));
}

private validateComponent(component: Component) {
const numChartDirectives = this.countChartDirectives(component);
if (numChartDirectives === 0) {
this.validateComponentWithoutChartDirective(component);
return;
}

if (numChartDirectives > 1) {
this.error(`Component "${component.name}" can only have one "doc-chart" directive in the HTML definition`);
return;
}

this.validateComponentWithChartDirective(component);
}

private validateComponentWithoutChartDirective(component: Component) {
if (this.countChartPropertiesProperties(component) > 0) {
this.error(
`Component "${component.name}" has a "chart-properties" control type, but only components with a "doc-chart" directive can have a property with this control type`,
);
}
}

private validateComponentWithChartDirective(component: Component) {
if (this.countChartPropertiesProperties(component) !== 1) {
this.error(
`Component "${
component.name
}" with "doc-chart" directive must have exactly one "chart-properties" property (found ${this.countChartPropertiesProperties(
component,
)})`,
);
return;
}

// Check whether the chart-properties control type property is applied to the doc-chart directive.
for (const chartProperty of Object.values(this.chartPropertiesProperties(component))) {
this.validateChartProperty(component, chartProperty);
}
}

private validateChartProperty(component: Component, chartProperty: ComponentProperty) {
if (!chartProperty.directiveKey) {
this.error(
`Component "${component.name}" must configure "directiveKey" for the property with control type "chart-properties"`,
);
return;
}

const directive = component.directives[chartProperty.directiveKey];
if (!directive || directive.type !== DirectiveType.chart) {
this.error(
`Component "${component.name}" has a control type "chart-properties" applied to the wrong directive, which can only be used with "doc-chart" directives`,
);
}
}

private countChartDirectives(component: Component): number {
return Object.values(component.directives).filter((directive) => directive.type === DirectiveType.chart).length;
}

/** Count number of "chart-properties" properties */
private countChartPropertiesProperties(component: Component): number {
return this.chartPropertiesProperties(component).length;
}

/** Get "chart-properties" properties definitions (collection of nested properties behaving as a single property) */
private chartPropertiesProperties(component: Component) {
return Object.values(component.properties).filter((property) => property.control.type === 'chart-properties');
}
}
1 change: 1 addition & 0 deletions lib/validators/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export * from './doc-slideshow-validator';
export * from './drop-capital-validator';
export * from './fitting-validator';
export * from './focuspoint-validator';
export * from './doc-chart-validator';
export * from './groups-validator';
export * from './icons-validator';
export * from './image-editor-validator';
Expand Down
2 changes: 1 addition & 1 deletion test/validate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,6 @@ describe('getValidators', () => {

it('should return amount of validators for version >= 1.11.0', () => {
const validators = getValidatorsForVersion('1.11.0-next');
expect(validators && validators.length).toEqual(29);
expect(validators && validators.length).toEqual(30);
});
});
160 changes: 160 additions & 0 deletions test/validators/doc-chart-validator.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import { DocChartValidator } from '../../lib/validators/doc-chart-validator';

describe('DocChartValidator', () => {
let definition: any;
let error: jest.Mock;
let validator: DocChartValidator;

beforeEach(() => {
definition = {
// valid definition (cut)
components: {
infographic: {
name: 'Infographic',
directives: {
d1: {
type: 'chart',
tag: 'div',
},
},
properties: [
{
name: 'chartproperty',
directiveKey: 'd1',
control: {
type: 'chart-properties',
},
},
],
},
// Do not fail on different components without chart directive
body: {
name: 'body',
directives: {
d1: {
type: 'editable',
tag: 'p',
},
},
properties: [],
},
},
};
error = jest.fn();
validator = new DocChartValidator(error, definition);
});

describe('validate', () => {
it('should pass on a valid definition', () => {
validator.validate();
expect(error).not.toHaveBeenCalled();
});

it('should not pass when there is a chart directive but no chart properties', () => {
definition.components.infographic.properties = [];

validator.validate();
expect(error).toHaveBeenCalledWith(
`Component "Infographic" with "doc-chart" directive must have exactly one "chart-properties" property (found 0)`,
);
});

it('should pass with one chart directives and other directive types', () => {
definition.components.infographic.directives.d2 = {
type: 'editable',
tag: 'p',
};
definition.components.infographic.directives.d3 = {
type: 'link',
tag: 'a',
};
validator.validate();
expect(error).not.toHaveBeenCalled();
});

it('should not pass with multiple chart type directives', () => {
definition.components.infographic.directives.d2 = {
type: 'chart',
tag: 'div',
};
validator.validate();
expect(error).toHaveBeenCalledWith(
`Component "Infographic" can only have one "doc-chart" directive in the HTML definition`,
);
});

it('should fail if a component property with a chart-properties control type is not applied to a chart directive', () => {
definition.components.wrongdirectivekey = {
name: 'wrongdirectivekey',
directives: {
d1: {
type: 'editable',
tag: 'p',
},
d2: {
type: 'chart',
tag: 'div',
},
},
properties: [
{
name: 'chartproperty',
directiveKey: 'd1',
control: {
type: 'chart-properties',
logo: 'logos/chart.svg',
link: 'www.chart.com',
},
},
],
};
validator.validate();
expect(error).toHaveBeenCalledWith(
`Component "wrongdirectivekey" has a control type "chart-properties" applied to the wrong directive, which can only be used with "doc-chart" directives`,
);
});

it('should fail in case a "chart-properties" property does not have a directive key', () => {
definition.components.nodirectivekey = {
name: 'nodirectivekey',
directives: {
d1: {
type: 'chart',
tag: 'div',
},
},
properties: [
{
name: 'chartproperty',
control: {
type: 'chart-properties',
},
},
],
};
validator.validate();
expect(error).toHaveBeenCalledWith(
`Component "nodirectivekey" must configure "directiveKey" for the property with control type "chart-properties"`,
);
});

it('should fail in case a component without a chart directive has "chart-properties" property', () => {
definition.components.body.properties = [
{
name: 'chartproperty',
directiveKey: 'd1',
control: {
type: 'chart-properties',
logo: 'logos/chart.svg',
link: 'www.chart.com',
},
},
];

validator.validate();
expect(error).toHaveBeenCalledWith(
`Component "body" has a "chart-properties" control type, but only components with a "doc-chart" directive can have a property with this control type`,
);
});
});
});

0 comments on commit 0cf4f5c

Please sign in to comment.