From c4940c6ba613952e90c413df8940cb639336d8d0 Mon Sep 17 00:00:00 2001 From: Bart Tadych Date: Wed, 20 Sep 2023 21:01:54 +0200 Subject: [PATCH] 0.9.0. (#19) * 0.9.0. --- CHANGELOG.md | 6 ++ demos/webpack-app/package.json | 6 +- editor/package.json | 8 +- .../src/components/dynamic-list-component.ts | 13 ++- editor/src/components/index.ts | 10 +++ editor/src/core/index.ts | 5 ++ editor/src/editor-extension.ts | 10 +++ editor/src/editor-provider-configuration.ts | 2 + editor/src/editor-provider.ts | 20 +++-- editor/src/editor.ts | 4 +- editor/src/external-types.ts | 1 + editor/src/index.ts | 2 + editor/src/property-editor/property-editor.ts | 2 +- .../dynamic/dynamic-value-editor.ts | 2 +- .../value-editor-factory-resolver.ts | 70 ++++++++++------ editor/src/value-editors/value-editor.ts | 3 +- model/package.json | 2 +- model/src/context/value-context.ts | 4 +- model/src/context/variables-provider.ts | 6 +- model/src/model.ts | 1 + model/src/test-tools/value-context-stub.ts | 5 +- model/src/validator/step-validator-context.ts | 2 +- .../boolean-value-model-configuration.ts | 1 + .../boolean-value-model-validator.spec.ts | 17 ++++ .../boolean/boolean-value-model-validator.ts | 11 +++ .../boolean/boolean-value-model.ts | 4 +- .../branches-value-model-configuration.ts | 16 ++++ .../branches-value-model-validator.spec.ts | 80 +++++++++++++++++++ .../branches-value-model-validator.ts | 30 +++++++ .../branches/branches-value-model.ts | 12 +-- model/src/value-models/branches/index.ts | 1 + .../number-value-model-configuration.ts | 1 + .../value-models/number/number-value-model.ts | 1 + .../sequence/sequence-value-model.ts | 1 - .../string-value-model-configuration.ts | 1 + .../value-models/string/string-value-model.ts | 1 + yarn.lock | 8 +- 37 files changed, 301 insertions(+), 68 deletions(-) create mode 100644 editor/src/components/index.ts create mode 100644 editor/src/editor-extension.ts create mode 100644 model/src/value-models/boolean/boolean-value-model-validator.spec.ts create mode 100644 model/src/value-models/boolean/boolean-value-model-validator.ts create mode 100644 model/src/value-models/branches/branches-value-model-configuration.ts create mode 100644 model/src/value-models/branches/branches-value-model-validator.spec.ts create mode 100644 model/src/value-models/branches/branches-value-model-validator.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index a5c05fc..d46b8b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.9.0 + +* Improved validation for the `boolean` value model. +* Improved validation for the `branches` value model. +* Internal changes preparing for the upcoming pro version. + ## 0.8.0 Updated the `sequential-workflow-model` dependency to the version `0.2.0`. diff --git a/demos/webpack-app/package.json b/demos/webpack-app/package.json index 435ed27..bf5e525 100644 --- a/demos/webpack-app/package.json +++ b/demos/webpack-app/package.json @@ -16,10 +16,10 @@ "dependencies": { "xstate": "^4.38.2", "sequential-workflow-model": "^0.2.0", - "sequential-workflow-designer": "^0.14.1", + "sequential-workflow-designer": "^0.16.0", "sequential-workflow-machine": "^0.4.0", - "sequential-workflow-editor-model": "^0.8.0", - "sequential-workflow-editor": "^0.8.0" + "sequential-workflow-editor-model": "^0.9.0", + "sequential-workflow-editor": "^0.9.0" }, "devDependencies": { "ts-loader": "^9.4.2", diff --git a/editor/package.json b/editor/package.json index d36e687..3b9f900 100644 --- a/editor/package.json +++ b/editor/package.json @@ -1,6 +1,6 @@ { "name": "sequential-workflow-editor", - "version": "0.8.0", + "version": "0.9.0", "type": "module", "main": "./lib/esm/index.js", "types": "./lib/index.d.ts", @@ -46,11 +46,11 @@ "prettier:fix": "prettier --write ./src ./css" }, "dependencies": { - "sequential-workflow-editor-model": "^0.8.0", + "sequential-workflow-editor-model": "^0.9.0", "sequential-workflow-model": "^0.2.0" }, "peerDependencies": { - "sequential-workflow-editor-model": "^0.8.0", + "sequential-workflow-editor-model": "^0.9.0", "sequential-workflow-model": "^0.2.0" }, "devDependencies": { @@ -79,4 +79,4 @@ "lowcode", "flow" ] -} +} \ No newline at end of file diff --git a/editor/src/components/dynamic-list-component.ts b/editor/src/components/dynamic-list-component.ts index b87ce9e..bd3dea3 100644 --- a/editor/src/components/dynamic-list-component.ts +++ b/editor/src/components/dynamic-list-component.ts @@ -8,8 +8,9 @@ export interface DynamicListComponent extends Component { add(item: TItem): void; } -export interface DynamicListComponentConfiguration { +export interface DynamicListComponentConfiguration { emptyMessage?: string; + canDelete?: (item: TItem) => string | null; } export interface DynamicListItemComponent extends Component { @@ -22,7 +23,7 @@ export function dynamicListComponent( initialItems: TItem[], itemComponentFactory: (item: TItem) => DynamicListItemComponent, context: ValueContext, - configuration?: DynamicListComponentConfiguration + configuration?: DynamicListComponentConfiguration ): DynamicListComponent { const onChanged = new SimpleEvent(); const items = [...initialItems]; @@ -38,6 +39,14 @@ export function dynamicListComponent( } function onItemDeleted(index: number) { + if (configuration && configuration.canDelete) { + const error = configuration.canDelete(items[index]); + if (error) { + window.alert(error); + return; + } + } + items.splice(index, 1); forward(); reloadList(); diff --git a/editor/src/components/index.ts b/editor/src/components/index.ts new file mode 100644 index 0000000..66d1480 --- /dev/null +++ b/editor/src/components/index.ts @@ -0,0 +1,10 @@ +export * from './button-component'; +export * from './component'; +export * from './dynamic-list-component'; +export * from './input-component'; +export * from './prepended-input-component'; +export * from './row-component'; +export * from './select-component'; +export * from './textarea-component'; +export * from './validation-error-component'; +export * from './value-editor-container-component'; diff --git a/editor/src/core/index.ts b/editor/src/core/index.ts index ce2d9f0..b4264af 100644 --- a/editor/src/core/index.ts +++ b/editor/src/core/index.ts @@ -1,2 +1,7 @@ +export * from './append-multiline-text'; +export * from './filter-value-types'; +export * from './filter-value-types'; export * from './html'; +export * from './icons'; +export * from './stacked-simple-event'; export * from './variable-name-formatter'; diff --git a/editor/src/editor-extension.ts b/editor/src/editor-extension.ts new file mode 100644 index 0000000..9bb248b --- /dev/null +++ b/editor/src/editor-extension.ts @@ -0,0 +1,10 @@ +import { ValueEditorFactory } from './value-editors'; + +export interface EditorExtension { + valueEditors?: ValueEditorExtension[]; +} + +export interface ValueEditorExtension { + editorId: string; + factory: ValueEditorFactory; +} diff --git a/editor/src/editor-provider-configuration.ts b/editor/src/editor-provider-configuration.ts index ef48ee5..6b177f5 100644 --- a/editor/src/editor-provider-configuration.ts +++ b/editor/src/editor-provider-configuration.ts @@ -1,8 +1,10 @@ import { UidGenerator } from 'sequential-workflow-editor-model'; import { DefinitionWalker } from 'sequential-workflow-model'; +import { EditorExtension } from './editor-extension'; export interface EditorProviderConfiguration { uidGenerator: UidGenerator; definitionWalker?: DefinitionWalker; isHeaderHidden?: boolean; + extensions?: EditorExtension[]; } diff --git a/editor/src/editor-provider.ts b/editor/src/editor-provider.ts index 4b2a17f..becd919 100644 --- a/editor/src/editor-provider.ts +++ b/editor/src/editor-provider.ts @@ -8,7 +8,7 @@ import { Path, StepValidatorContext } from 'sequential-workflow-editor-model'; -import { EditorServices, ValueEditorEditorFactoryResolver } from './value-editors'; +import { EditorServices, ValueEditorFactoryResolver } from './value-editors'; import { GlobalEditorContext, RootEditorProvider, @@ -30,12 +30,13 @@ export class EditorProvider { const definitionWalker = configuration.definitionWalker ?? new DefinitionWalker(); const activator = ModelActivator.create(definitionModel, configuration.uidGenerator); const validator = DefinitionValidator.create(definitionModel, definitionWalker); - return new EditorProvider(activator, validator, definitionModel, definitionWalker, configuration); + const valueEditorFactoryResolver = ValueEditorFactoryResolver.create(configuration.extensions); + return new EditorProvider(activator, validator, definitionModel, definitionWalker, valueEditorFactoryResolver, configuration); } private readonly services: EditorServices = { activator: this.activator, - valueEditorFactoryResolver: ValueEditorEditorFactoryResolver.resolve + valueEditorFactoryResolver: this.valueEditorFactoryResolver }; private constructor( @@ -43,6 +44,7 @@ export class EditorProvider { private readonly validator: DefinitionValidator, private readonly definitionModel: DefinitionModel, private readonly definitionWalker: DefinitionWalker, + private readonly valueEditorFactoryResolver: ValueEditorFactoryResolver, private readonly configuration: EditorProviderConfiguration ) {} @@ -82,11 +84,17 @@ export class EditorProvider { const editor = Editor.create(headerData, validator, propertyModels, definitionContext, this.services, typeClassName); editor.onValuesChanged.subscribe((paths: Path[]) => { - if (paths.some(path => path.equals(stepModel.name.value.path))) { + const isNameChanged = paths.some(path => path.equals(stepModel.name.value.path)); + if (isNameChanged) { context.notifyNameChanged(); - } else { - context.notifyPropertiesChanged(); + return; } + const areBranchesChanged = paths.some(path => path.equals('branches')); + if (areBranchesChanged) { + context.notifyChildrenChanged(); + return; + } + context.notifyPropertiesChanged(); }); return editor.root; }; diff --git a/editor/src/editor.ts b/editor/src/editor.ts index 72e8c15..3aceb41 100644 --- a/editor/src/editor.ts +++ b/editor/src/editor.ts @@ -1,6 +1,6 @@ import { DefinitionContext, Path, PropertyModel, PropertyModels } from 'sequential-workflow-editor-model'; import { PropertyEditor } from './property-editor/property-editor'; -import { EditorServices, ValueEditorEditorFactoryResolver } from './value-editors'; +import { EditorServices } from './value-editors'; import { EditorHeader, EditorHeaderData } from './editor-header'; import { StackedSimpleEvent } from './core/stacked-simple-event'; import { ValidationErrorComponent, validationErrorComponent } from './components/validation-error-component'; @@ -32,7 +32,7 @@ export class Editor { const editors = new Map(); for (const propertyModel of propertyModels) { - if (ValueEditorEditorFactoryResolver.isHidden(propertyModel.value.id)) { + if (editorServices.valueEditorFactoryResolver.isHidden(propertyModel.value.id, propertyModel.value.editorId)) { continue; } diff --git a/editor/src/external-types.ts b/editor/src/external-types.ts index 7d7ca1c..07619f4 100644 --- a/editor/src/external-types.ts +++ b/editor/src/external-types.ts @@ -3,6 +3,7 @@ import { Definition, Step } from 'sequential-workflow-model'; export interface StepEditorContext { notifyNameChanged(): void; notifyPropertiesChanged(): void; + notifyChildrenChanged(): void; } export interface GlobalEditorContext { diff --git a/editor/src/index.ts b/editor/src/index.ts index a2fda98..4e5253f 100644 --- a/editor/src/index.ts +++ b/editor/src/index.ts @@ -1,4 +1,6 @@ +export * from './components'; export * from './core'; export * from './value-editors'; +export * from './editor-extension'; export * from './editor-provider-configuration'; export * from './editor-provider'; diff --git a/editor/src/property-editor/property-editor.ts b/editor/src/property-editor/property-editor.ts index 07aa3d6..696312a 100644 --- a/editor/src/property-editor/property-editor.ts +++ b/editor/src/property-editor/property-editor.ts @@ -20,7 +20,7 @@ export class PropertyEditor implements Component { editorServices: EditorServices ): PropertyEditor { const valueContext = ValueContext.create(propertyModel.value, propertyModel, definitionContext); - const valueEditorFactory = editorServices.valueEditorFactoryResolver(propertyModel.value.id); + const valueEditorFactory = editorServices.valueEditorFactoryResolver.resolve(propertyModel.value.id, propertyModel.value.editorId); const valueEditor = valueEditorFactory(valueContext, editorServices); let hint: PropertyHintComponent | null = null; diff --git a/editor/src/value-editors/dynamic/dynamic-value-editor.ts b/editor/src/value-editors/dynamic/dynamic-value-editor.ts index aa69201..e3e2034 100644 --- a/editor/src/value-editors/dynamic/dynamic-value-editor.ts +++ b/editor/src/value-editors/dynamic/dynamic-value-editor.ts @@ -30,7 +30,7 @@ export function dynamicValueEditor(context: ValueContext, ser } const childContext = context.createChildContext(model); - editor = services.valueEditorFactoryResolver(model.id)(childContext, services); + editor = services.valueEditorFactoryResolver.resolve(model.id, model.editorId)(childContext, services); placeholder.appendChild(editor.view); } diff --git a/editor/src/value-editors/value-editor-factory-resolver.ts b/editor/src/value-editors/value-editor-factory-resolver.ts index dff46fc..1c21729 100644 --- a/editor/src/value-editors/value-editor-factory-resolver.ts +++ b/editor/src/value-editors/value-editor-factory-resolver.ts @@ -15,35 +15,55 @@ import { nullableAnyVariableValueEditor, nullableAnyVariableValueEditorId } from import { booleanValueEditor, booleanValueEditorId } from './boolean/boolean-value-editor'; import { generatedStringValueEditor, generatedStringValueEditorId } from './generated-string/generated-string-value-editor'; import { stringDictionaryValueEditor } from './string-dictionary/string-dictionary-value-editor'; +import { EditorExtension } from '../editor-extension'; -const editors: { id: string; factory: ValueEditorFactory | null }[] = [ - { id: anyVariablesValueEditorId, factory: anyVariablesValueEditor as ValueEditorFactory }, - { id: booleanValueEditorId, factory: booleanValueEditor as ValueEditorFactory }, - { id: choiceValueEditorId, factory: choiceValueEditor as ValueEditorFactory }, - { id: nullableAnyVariableValueEditorId, factory: nullableAnyVariableValueEditor as ValueEditorFactory }, - { id: dynamicValueEditorId, factory: dynamicValueEditor as ValueEditorFactory }, - { id: generatedStringValueEditorId, factory: generatedStringValueEditor as ValueEditorFactory }, - { id: nullableVariableValueEditorId, factory: nullableVariableValueEditor as ValueEditorFactory }, - { id: nullableVariableDefinitionValueEditorId, factory: nullableVariableDefinitionValueEditor as ValueEditorFactory }, - { id: stringValueEditorId, factory: stringValueEditor as ValueEditorFactory }, - { id: stringDictionaryValueModelId, factory: stringDictionaryValueEditor as ValueEditorFactory }, - { id: numberValueEditorId, factory: numberValueEditor as ValueEditorFactory }, - { id: variableDefinitionsValueEditorId, factory: variableDefinitionsValueEditor as ValueEditorFactory }, - { id: sequenceValueModelId, factory: null }, - { id: branchesValueModelId, factory: null } -]; +const defaultMap: ValueEditorMap = { + [anyVariablesValueEditorId]: anyVariablesValueEditor as ValueEditorFactory, + [booleanValueEditorId]: booleanValueEditor as ValueEditorFactory, + [choiceValueEditorId]: choiceValueEditor as ValueEditorFactory, + [nullableAnyVariableValueEditorId]: nullableAnyVariableValueEditor as ValueEditorFactory, + [dynamicValueEditorId]: dynamicValueEditor as ValueEditorFactory, + [generatedStringValueEditorId]: generatedStringValueEditor as ValueEditorFactory, + [nullableVariableValueEditorId]: nullableVariableValueEditor as ValueEditorFactory, + [nullableVariableDefinitionValueEditorId]: nullableVariableDefinitionValueEditor as ValueEditorFactory, + [stringValueEditorId]: stringValueEditor as ValueEditorFactory, + [stringDictionaryValueModelId]: stringDictionaryValueEditor as ValueEditorFactory, + [numberValueEditorId]: numberValueEditor as ValueEditorFactory, + [variableDefinitionsValueEditorId]: variableDefinitionsValueEditor as ValueEditorFactory, + [sequenceValueModelId]: null, + [branchesValueModelId]: null +}; -export class ValueEditorEditorFactoryResolver { - public static resolve(valueModelId: string): ValueEditorFactory { - const editor = editors.find(editor => editor.id === valueModelId); - if (!editor || !editor.factory) { - throw new Error(`Value model id: ${valueModelId} is not supported`); +type ValueEditorMap = Record; + +export class ValueEditorFactoryResolver { + public static create(extensions?: EditorExtension[]): ValueEditorFactoryResolver { + let map: ValueEditorMap; + if (extensions) { + map = { ...defaultMap }; + extensions.forEach(extension => { + if (extension.valueEditors) { + extension.valueEditors.forEach(e => (map[e.editorId] = e.factory)); + } + }); + } else { + map = defaultMap; + } + return new ValueEditorFactoryResolver(map); + } + + private constructor(private readonly map: ValueEditorMap) {} + + public resolve(valueModelId: string, editorId: string | undefined): ValueEditorFactory { + const id = editorId ?? valueModelId; + const editor = this.map[id]; + if (!editor) { + throw new Error(`Editor id ${id} is not supported`); } - return editor.factory; + return editor; } - public static isHidden(valueModelId: string): boolean { - const editor = editors.find(editor => editor.id === valueModelId); - return editor ? editor.factory === null : false; + public isHidden(valueModelId: string, editorId: string | undefined): boolean { + return !this.map[editorId ?? valueModelId]; } } diff --git a/editor/src/value-editors/value-editor.ts b/editor/src/value-editors/value-editor.ts index d4f8b27..f6f565c 100644 --- a/editor/src/value-editors/value-editor.ts +++ b/editor/src/value-editors/value-editor.ts @@ -1,4 +1,5 @@ import { ModelActivator, ValueModel, ValueContext } from 'sequential-workflow-editor-model'; +import { ValueEditorFactoryResolver } from './value-editor-factory-resolver'; import { Component } from '../components/component'; // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -12,8 +13,6 @@ export type ValueEditorFactory = ( services: EditorServices ) => ValueEditor; -export type ValueEditorFactoryResolver = (valueModelId: string) => ValueEditorFactory; - export interface EditorServices { valueEditorFactoryResolver: ValueEditorFactoryResolver; activator: ModelActivator; diff --git a/model/package.json b/model/package.json index 07790f7..fc334cf 100644 --- a/model/package.json +++ b/model/package.json @@ -1,6 +1,6 @@ { "name": "sequential-workflow-editor-model", - "version": "0.8.0", + "version": "0.9.0", "homepage": "https://nocode-js.com/", "author": { "name": "NoCode JS", diff --git a/model/src/context/value-context.ts b/model/src/context/value-context.ts index efbb2ec..c55432f 100644 --- a/model/src/context/value-context.ts +++ b/model/src/context/value-context.ts @@ -31,9 +31,9 @@ export class ValueContext { return this.model.validate(this); - } + }; public readonly getPropertyValue = (name: Key): TProperties[Key] => { return readPropertyValue(name, this.propertyModel, this.definitionContext.object); diff --git a/model/src/context/variables-provider.ts b/model/src/context/variables-provider.ts index 3eba3de..7c8c07b 100644 --- a/model/src/context/variables-provider.ts +++ b/model/src/context/variables-provider.ts @@ -37,7 +37,7 @@ export class ParentsProvider { if (this.step) { const parents = this.definitionWalker.getParents(this.definition, this.step); - const count = parents.length - 1; // skips variable definitions from itself + const count = parents.length; for (let index = 0; index < count; index++) { const parent = parents[index]; if (typeof parent === 'string') { @@ -81,10 +81,10 @@ export class ParentsProvider { } } - public readonly getStepTypes = (): string[] => { + public readonly getParentStepTypes = (): string[] => { if (this.step) { const parents = this.definitionWalker.getParents(this.definition, this.step); - return (parents.filter(p => typeof p === 'object') as Step[]).map(p => p.type); + return (parents.slice(0, parents.length - 1).filter(p => typeof p === 'object') as Step[]).map(p => p.type); } return []; }; diff --git a/model/src/model.ts b/model/src/model.ts index 92aaf06..6d2199c 100644 --- a/model/src/model.ts +++ b/model/src/model.ts @@ -64,6 +64,7 @@ export interface ValueModel< TProperties extends Properties = Properties > { id: ValueModelId; + editorId?: string; path: Path; label: string; configuration: TConfiguration; diff --git a/model/src/test-tools/value-context-stub.ts b/model/src/test-tools/value-context-stub.ts index b160929..0baa3bc 100644 --- a/model/src/test-tools/value-context-stub.ts +++ b/model/src/test-tools/value-context-stub.ts @@ -1,7 +1,10 @@ import { ValueContext } from '../context'; import { ValueModel } from '../model'; -export function createValueContextStub(value: unknown, configuration: object): ValueContext { +export function createValueContextStub( + value: unknown, + configuration: TValueModel['configuration'] +): ValueContext { return { getValue: () => value, model: { diff --git a/model/src/validator/step-validator-context.ts b/model/src/validator/step-validator-context.ts index 7e86e45..1d51ca6 100644 --- a/model/src/validator/step-validator-context.ts +++ b/model/src/validator/step-validator-context.ts @@ -12,5 +12,5 @@ export class StepValidatorContext { * @returns The parent step types. * @example `['loop', 'if']` */ - public readonly getParentStepTypes = this.parentsProvider.getStepTypes; + public readonly getParentStepTypes = this.parentsProvider.getParentStepTypes; } diff --git a/model/src/value-models/boolean/boolean-value-model-configuration.ts b/model/src/value-models/boolean/boolean-value-model-configuration.ts index d32e9bd..9cc42af 100644 --- a/model/src/value-models/boolean/boolean-value-model-configuration.ts +++ b/model/src/value-models/boolean/boolean-value-model-configuration.ts @@ -1,3 +1,4 @@ export interface BooleanValueModelConfiguration { defaultValue?: boolean; + editorId?: string; } diff --git a/model/src/value-models/boolean/boolean-value-model-validator.spec.ts b/model/src/value-models/boolean/boolean-value-model-validator.spec.ts new file mode 100644 index 0000000..76e5744 --- /dev/null +++ b/model/src/value-models/boolean/boolean-value-model-validator.spec.ts @@ -0,0 +1,17 @@ +import { createValueContextStub } from '../../test-tools/value-context-stub'; +import { BooleanValueModel } from './boolean-value-model'; +import { booleanValueModelValidator } from './boolean-value-model-validator'; + +describe('booleanValueModelValidator', () => { + it('returns null if value is boolean', () => { + const context = createValueContextStub(true, {}); + const error = booleanValueModelValidator(context); + expect(error).toBeNull(); + }); + + it('returns "The value must be a boolean" if value is not a boolean', () => { + const context = createValueContextStub('this is not a boolean', {}); + const error = booleanValueModelValidator(context); + expect(error?.$).toBe('The value must be a boolean.'); + }); +}); diff --git a/model/src/value-models/boolean/boolean-value-model-validator.ts b/model/src/value-models/boolean/boolean-value-model-validator.ts new file mode 100644 index 0000000..255e54b --- /dev/null +++ b/model/src/value-models/boolean/boolean-value-model-validator.ts @@ -0,0 +1,11 @@ +import { ValueContext } from '../../context'; +import { ValidationResult, createValidationSingleError } from '../../model'; +import { BooleanValueModel } from './boolean-value-model'; + +export function booleanValueModelValidator(context: ValueContext): ValidationResult { + const value = context.getValue(); + if (typeof value !== 'boolean') { + return createValidationSingleError('The value must be a boolean.'); + } + return null; +} diff --git a/model/src/value-models/boolean/boolean-value-model.ts b/model/src/value-models/boolean/boolean-value-model.ts index c75c8be..6a9b50b 100644 --- a/model/src/value-models/boolean/boolean-value-model.ts +++ b/model/src/value-models/boolean/boolean-value-model.ts @@ -1,6 +1,7 @@ import { ValueModel, ValueModelFactoryFromModel } from '../../model'; import { Path } from '../../core/path'; import { BooleanValueModelConfiguration } from './boolean-value-model-configuration'; +import { booleanValueModelValidator } from './boolean-value-model-validator'; export type BooleanValueModel = ValueModel; @@ -9,6 +10,7 @@ export const booleanValueModelId = 'boolean'; export const createBooleanValueModel = (configuration: BooleanValueModelConfiguration): ValueModelFactoryFromModel => ({ create: (path: Path) => ({ id: booleanValueModelId, + editorId: configuration.editorId, label: 'Boolean', path, configuration, @@ -19,6 +21,6 @@ export const createBooleanValueModel = (configuration: BooleanValueModelConfigur return false; }, getVariableDefinitions: () => null, - validate: () => null + validate: booleanValueModelValidator }) }); diff --git a/model/src/value-models/branches/branches-value-model-configuration.ts b/model/src/value-models/branches/branches-value-model-configuration.ts new file mode 100644 index 0000000..5a57f53 --- /dev/null +++ b/model/src/value-models/branches/branches-value-model-configuration.ts @@ -0,0 +1,16 @@ +export interface BranchesValueModelConfiguration { + /** + * @description Branches of the branched step. Each branch is a list of step types. + */ + branches: Record; + + /** + * @description If true, the branches are dynamic and can be changed by the user. + */ + dynamic?: boolean; + + /** + * @description Override the default editor for the branches. + */ + editorId?: string; +} diff --git a/model/src/value-models/branches/branches-value-model-validator.spec.ts b/model/src/value-models/branches/branches-value-model-validator.spec.ts new file mode 100644 index 0000000..fbe4997 --- /dev/null +++ b/model/src/value-models/branches/branches-value-model-validator.spec.ts @@ -0,0 +1,80 @@ +import { Branches } from 'sequential-workflow-model'; +import { createValueContextStub } from '../../test-tools/value-context-stub'; +import { BranchesValueModel } from './branches-value-model'; +import { branchesValueModelValidator } from './branches-value-model-validator'; + +describe('branchesValueModelValidator', () => { + it('returns "No branches defined" if there is not any branch', () => { + const context = createValueContextStub>( + {}, + { + branches: {} + } + ); + const error = branchesValueModelValidator(context); + expect(error?.$).toBe('No branches defined.'); + }); + + it('returns "Missing branch" if branch is missing', () => { + const context = createValueContextStub>( + { + true: [], + zero: [] + }, + { + branches: { + true: [], + false: [] + } + } + ); + const error = branchesValueModelValidator(context); + expect(error?.$).toBe('Missing branch: false.'); + }); + + it('returns "Invalid number of branches" if there is more branches', () => { + const context = createValueContextStub>( + { + true: [], + false: [], + error: [] + }, + { + branches: { + true: [], + false: [] + } + } + ); + const error = branchesValueModelValidator(context); + expect(error?.$).toBe('Invalid number of branches.'); + }); + + it('returns "The value must be object" if there is not any branch', () => { + const context = createValueContextStub>(1234567890, { + branches: { + true: [], + false: [] + } + }); + const error = branchesValueModelValidator(context); + expect(error?.$).toBe('The value must be object.'); + }); + + it('returns null if there is valid branches', () => { + const context = createValueContextStub>( + { + true: [], + false: [] + }, + { + branches: { + true: [], + false: [] + } + } + ); + const error = branchesValueModelValidator(context); + expect(error).toBeNull(); + }); +}); diff --git a/model/src/value-models/branches/branches-value-model-validator.ts b/model/src/value-models/branches/branches-value-model-validator.ts new file mode 100644 index 0000000..840b6f6 --- /dev/null +++ b/model/src/value-models/branches/branches-value-model-validator.ts @@ -0,0 +1,30 @@ +import { Branches } from 'sequential-workflow-model'; +import { BranchesValueModel } from './branches-value-model'; +import { ValueContext } from '../../context'; +import { ValidationResult, createValidationSingleError } from '../../model'; + +export function branchesValueModelValidator( + context: ValueContext> +): ValidationResult { + const configuration = context.model.configuration; + const branches = context.getValue(); + + if (typeof branches !== 'object') { + return createValidationSingleError('The value must be object.'); + } + const branchNames = Object.keys(branches); + if (branchNames.length === 0) { + return createValidationSingleError('No branches defined.'); + } + if (!configuration.dynamic) { + const configurationBranchNames = Object.keys(configuration.branches); + if (branchNames.length !== configurationBranchNames.length) { + return createValidationSingleError('Invalid number of branches.'); + } + const missingBranchName = configurationBranchNames.find(branchName => !branchNames.includes(branchName)); + if (missingBranchName) { + return createValidationSingleError(`Missing branch: ${missingBranchName}.`); + } + } + return null; +} diff --git a/model/src/value-models/branches/branches-value-model.ts b/model/src/value-models/branches/branches-value-model.ts index 3bac082..4495e7e 100644 --- a/model/src/value-models/branches/branches-value-model.ts +++ b/model/src/value-models/branches/branches-value-model.ts @@ -2,13 +2,8 @@ import { Branches, Sequence } from 'sequential-workflow-model'; import { ValueModel, ValueModelFactoryFromModel } from '../../model'; import { Path } from '../../core/path'; import { DefaultValueContext } from '../../context/default-value-context'; - -export interface BranchesValueModelConfiguration { - /** - * @description Branches of the branched step. Each branch is a list of step types. - */ - branches: Record; -} +import { BranchesValueModelConfiguration } from './branches-value-model-configuration'; +import { branchesValueModelValidator } from './branches-value-model-validator'; export type BranchesValueModel = ValueModel; @@ -21,6 +16,7 @@ export const createBranchesValueModel = >> => ({ create: (path: Path) => ({ id: branchesValueModelId, + editorId: configuration.editorId, label: 'Branches', path, configuration, @@ -32,6 +28,6 @@ export const createBranchesValueModel = ; }, getVariableDefinitions: () => null, - validate: () => null + validate: branchesValueModelValidator }) }); diff --git a/model/src/value-models/branches/index.ts b/model/src/value-models/branches/index.ts index 778cbe7..53123b2 100644 --- a/model/src/value-models/branches/index.ts +++ b/model/src/value-models/branches/index.ts @@ -1 +1,2 @@ +export * from './branches-value-model-configuration'; export * from './branches-value-model'; diff --git a/model/src/value-models/number/number-value-model-configuration.ts b/model/src/value-models/number/number-value-model-configuration.ts index 0516481..4273e1e 100644 --- a/model/src/value-models/number/number-value-model-configuration.ts +++ b/model/src/value-models/number/number-value-model-configuration.ts @@ -2,4 +2,5 @@ export interface NumberValueModelConfiguration { defaultValue?: number; min?: number; max?: number; + editorId?: string; } diff --git a/model/src/value-models/number/number-value-model.ts b/model/src/value-models/number/number-value-model.ts index ef36a83..949e9a2 100644 --- a/model/src/value-models/number/number-value-model.ts +++ b/model/src/value-models/number/number-value-model.ts @@ -10,6 +10,7 @@ export const numberValueModelId = 'number'; export const createNumberValueModel = (configuration: NumberValueModelConfiguration): ValueModelFactoryFromModel => ({ create: (path: Path) => ({ id: numberValueModelId, + editorId: configuration.editorId, label: 'Number', path, configuration, diff --git a/model/src/value-models/sequence/sequence-value-model.ts b/model/src/value-models/sequence/sequence-value-model.ts index 693cd28..17e0819 100644 --- a/model/src/value-models/sequence/sequence-value-model.ts +++ b/model/src/value-models/sequence/sequence-value-model.ts @@ -19,7 +19,6 @@ export const createSequenceValueModel = ( label: 'Sequence', path, configuration, - editorId: null, getDefaultValue(context: DefaultValueContext): Sequence { return configuration.sequence.map(type => context.activateStep(type)); }, diff --git a/model/src/value-models/string/string-value-model-configuration.ts b/model/src/value-models/string/string-value-model-configuration.ts index 873b667..4580def 100644 --- a/model/src/value-models/string/string-value-model-configuration.ts +++ b/model/src/value-models/string/string-value-model-configuration.ts @@ -3,4 +3,5 @@ export interface StringValueModelConfiguration { defaultValue?: string; pattern?: RegExp; multiline?: boolean | number; + editorId?: string; } diff --git a/model/src/value-models/string/string-value-model.ts b/model/src/value-models/string/string-value-model.ts index 084f4d3..935f767 100644 --- a/model/src/value-models/string/string-value-model.ts +++ b/model/src/value-models/string/string-value-model.ts @@ -10,6 +10,7 @@ export const stringValueModelId = 'string'; export const createStringValueModel = (configuration: StringValueModelConfiguration): ValueModelFactoryFromModel => ({ create: (path: Path) => ({ id: stringValueModelId, + editorId: configuration.editorId, label: 'String', path, configuration, diff --git a/yarn.lock b/yarn.lock index 02487e7..7eead8c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5163,10 +5163,10 @@ semver@^7.3.4, semver@^7.3.7, semver@^7.3.8: dependencies: lru-cache "^6.0.0" -sequential-workflow-designer@^0.14.1: - version "0.14.1" - resolved "https://registry.yarnpkg.com/sequential-workflow-designer/-/sequential-workflow-designer-0.14.1.tgz#1f7f5b2a1c4a337044e77eaef36fe1333ba22c4b" - integrity sha512-wPgNKnl/4q4QMbYqn19O9On6eYf/MXzityXAFmzo/WQBAl6RJhclY4LmL8HxotdkcGSQznW80E3gOpNERZCdLw== +sequential-workflow-designer@^0.16.0: + version "0.16.0" + resolved "https://registry.yarnpkg.com/sequential-workflow-designer/-/sequential-workflow-designer-0.16.0.tgz#9771cdb00063e6da395df3d9bc5a280460cf087c" + integrity sha512-9xWdA3C7dQ/z2O61BNA+8QORFR5WGWT4vX7mTAk5HwTE4YcJ+GI4XTjx+5bE8k2E+PL/QmuvbY8rQBFoV9wXvw== dependencies: sequential-workflow-model "^0.2.0"