diff --git a/package-lock.json b/package-lock.json index 0e1fc40c..e32cfe2e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1601,6 +1601,11 @@ "preact": "^10.11.2" } }, + "node_modules/@bpmn-io/dmn-variable-resolver": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@bpmn-io/dmn-variable-resolver/-/dmn-variable-resolver-0.3.1.tgz", + "integrity": "sha512-wKuL15nVAb0EO7mwto1+sB/CFZLbwxJAg2MKJa7a0gFL59sgO1vS9wwXwBiCCcvFKiEkCSNxhyFPjQItVEgS8A==" + }, "node_modules/@bpmn-io/feel-editor": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/@bpmn-io/feel-editor/-/feel-editor-0.9.1.tgz", @@ -19779,6 +19784,7 @@ "version": "14.4.1", "license": "SEE LICENSE IN LICENSE", "dependencies": { + "@bpmn-io/dmn-variable-resolver": "^0.3.1", "css.escape": "^1.5.1", "diagram-js": "^12.0.0", "dmn-js-shared": "^14.4.1", @@ -19815,6 +19821,7 @@ "version": "14.4.3", "license": "SEE LICENSE IN LICENSE", "dependencies": { + "@bpmn-io/dmn-variable-resolver": "^0.3.1", "diagram-js": "^12.0.0", "dmn-js-shared": "^14.4.1", "escape-html": "^1.0.3", @@ -20911,6 +20918,11 @@ "preact": "^10.11.2" } }, + "@bpmn-io/dmn-variable-resolver": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@bpmn-io/dmn-variable-resolver/-/dmn-variable-resolver-0.3.1.tgz", + "integrity": "sha512-wKuL15nVAb0EO7mwto1+sB/CFZLbwxJAg2MKJa7a0gFL59sgO1vS9wwXwBiCCcvFKiEkCSNxhyFPjQItVEgS8A==" + }, "@bpmn-io/feel-editor": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/@bpmn-io/feel-editor/-/feel-editor-0.9.1.tgz", @@ -24822,6 +24834,7 @@ "dmn-js-decision-table": { "version": "file:packages/dmn-js-decision-table", "requires": { + "@bpmn-io/dmn-variable-resolver": "^0.3.1", "css.escape": "^1.5.1", "diagram-js": "^12.0.0", "dmn-font": "^0.6.2", @@ -24852,6 +24865,7 @@ "dmn-js-literal-expression": { "version": "file:packages/dmn-js-literal-expression", "requires": { + "@bpmn-io/dmn-variable-resolver": "^0.3.1", "diagram-js": "^12.0.0", "dmn-font": "^0.6.2", "dmn-js-shared": "^14.4.1", diff --git a/packages/dmn-js-decision-table/package.json b/packages/dmn-js-decision-table/package.json index bbb18c3d..4d46158c 100644 --- a/packages/dmn-js-decision-table/package.json +++ b/packages/dmn-js-decision-table/package.json @@ -30,6 +30,7 @@ "inferno-test-utils": "~5.6.2" }, "dependencies": { + "@bpmn-io/dmn-variable-resolver": "^0.3.1", "css.escape": "^1.5.1", "diagram-js": "^12.0.0", "dmn-js-shared": "^14.4.1", diff --git a/packages/dmn-js-decision-table/src/Editor.js b/packages/dmn-js-decision-table/src/Editor.js index 6245b839..cc3d3a5b 100644 --- a/packages/dmn-js-decision-table/src/Editor.js +++ b/packages/dmn-js-decision-table/src/Editor.js @@ -1,3 +1,4 @@ +import { DmnVariableResolverModule } from '@bpmn-io/dmn-variable-resolver'; import Viewer from './Viewer'; import addRuleModule from './features/add-rule'; @@ -76,7 +77,8 @@ export default class Editor extends Viewer { simpleDurationEditModule, simpleNumberEditModule, simpleStringEditModule, - simpleTimeEditModule + simpleTimeEditModule, + DmnVariableResolverModule ]; } diff --git a/packages/dmn-js-decision-table/src/features/decision-rules/components/DecisionRulesCellEditorComponent.js b/packages/dmn-js-decision-table/src/features/decision-rules/components/DecisionRulesCellEditorComponent.js index 8ec21ee5..274b3341 100644 --- a/packages/dmn-js-decision-table/src/features/decision-rules/components/DecisionRulesCellEditorComponent.js +++ b/packages/dmn-js-decision-table/src/features/decision-rules/components/DecisionRulesCellEditorComponent.js @@ -124,6 +124,7 @@ class TableCellEditor extends Component { this._expressionLanguages = context.injector.get('expressionLanguages'); this._translate = context.injector.get('translate'); + this._variableResolver = context.injector.get('variableResolver', false); } isDefaultExpressionLanguage(businessObject) { @@ -187,6 +188,13 @@ class TableCellEditor extends Component { this.getDefaultExpressionLanguage(businessObject).value; } + _getVariables() { + const { businessObject } = this.props; + + return this._variableResolver && + this._variableResolver.getVariables(businessObject); + } + render() { const { businessObject, @@ -204,6 +212,7 @@ class TableCellEditor extends Component { const isScript = this.isScript(businessObject); const Editor = this.getEditor(); + const variables = this._getVariables(); return (
@@ -217,6 +226,7 @@ class TableCellEditor extends Component { onInput={ onChange } value={ value } placeholder={ placeholder } + variables={ variables } /> { !isDefaultExpressionLanguage && ( diff --git a/packages/dmn-js-decision-table/src/features/decision-table-head/editor/components/InputCellContextMenu.js b/packages/dmn-js-decision-table/src/features/decision-table-head/editor/components/InputCellContextMenu.js index 4ab2aa33..105d7426 100644 --- a/packages/dmn-js-decision-table/src/features/decision-table-head/editor/components/InputCellContextMenu.js +++ b/packages/dmn-js-decision-table/src/features/decision-table-head/editor/components/InputCellContextMenu.js @@ -79,6 +79,7 @@ export default class InputCellContextMenu extends Component { ); } diff --git a/packages/dmn-js-decision-table/src/features/decision-table-head/editor/components/InputEditor.js b/packages/dmn-js-decision-table/src/features/decision-table-head/editor/components/InputEditor.js index 67f32a85..cce280ea 100644 --- a/packages/dmn-js-decision-table/src/features/decision-table-head/editor/components/InputEditor.js +++ b/packages/dmn-js-decision-table/src/features/decision-table-head/editor/components/InputEditor.js @@ -10,6 +10,7 @@ export default class InputEditor extends Component { this.translate = context.injector ? context.injector.get('translate') : noopTranslate; this.expressionLanguages = context.injector.get('expressionLanguages', false); + this.variableResolver = context.injector.get('variableResolver', false); this.handleValue = (text) => { @@ -54,6 +55,11 @@ export default class InputEditor extends Component { } }; + _getVariables() { + return this.variableResolver && + this.variableResolver.getVariables(this.props.element); + } + render() { const { @@ -63,6 +69,8 @@ export default class InputEditor extends Component { const ExpressionEditor = this.getExpressionEditorComponent(); + const variables = this._getVariables(); + return (
@@ -92,7 +100,8 @@ export default class InputEditor extends Component { ].join(' ') } onInput={ this.handleValue } - value={ text || '' } /> + value={ text || '' } + variables={ variables } />
); diff --git a/packages/dmn-js-decision-table/test/spec/features/decision-rules/DecisionRulesEditorSpec.js b/packages/dmn-js-decision-table/test/spec/features/decision-rules/DecisionRulesEditorSpec.js index c0e73a56..ed4a487f 100644 --- a/packages/dmn-js-decision-table/test/spec/features/decision-rules/DecisionRulesEditorSpec.js +++ b/packages/dmn-js-decision-table/test/spec/features/decision-rules/DecisionRulesEditorSpec.js @@ -2,6 +2,8 @@ import { bootstrapModeler, inject, act } from 'test/helper'; import { query as domQuery } from 'min-dom'; +import { DmnVariableResolverModule } from '@bpmn-io/dmn-variable-resolver'; + import { triggerInputEvent } from 'dmn-js-shared/test/util/EventUtil'; import { queryEditor } from 'dmn-js-shared/test/util/EditorUtil'; @@ -367,6 +369,44 @@ describe('features/decision-rules', function() { }); + + describe('integration', function() { + + beforeEach(bootstrapModeler(emptyRuleXML, { + modules: [ + CoreModule, + ModelingModule, + DecisionRulesModule, + DecisionRulesEditorModule, + DmnVariableResolverModule + ], + debounceInput: false + })); + + + it('should pass variables to editor', async function() { + + // given + const editor = queryEditor('[data-element-id="unaryTest_1"]', testContainer); + + await act(() => editor.focus()); + + // when + await changeInput(document.activeElement, 'Var'); + + // then + await expectEventually(() => { + const options = testContainer.querySelectorAll('[role="option"]'); + + expect(options).to.exist; + expect(options).to.satisfy(options => { + const result = Array.from(options).some( + option => option.textContent === 'Variable'); + return result; + }); + }); + }); + }); }); // helpers ////////////////// @@ -390,4 +430,19 @@ function isFirefox() { function skipFF() { return isFirefox() ? it.only : it; -} \ No newline at end of file +} + +async function expectEventually(fn) { + for (let i = 0; i < 10; i++) { + try { + await act(() => {}); + await fn(); + return; + } catch (e) { + + // wait + } + } + + return fn(); +} diff --git a/packages/dmn-js-decision-table/test/spec/features/decision-rules/empty-rule.dmn b/packages/dmn-js-decision-table/test/spec/features/decision-rules/empty-rule.dmn index 60543eb2..d320ed54 100644 --- a/packages/dmn-js-decision-table/test/spec/features/decision-rules/empty-rule.dmn +++ b/packages/dmn-js-decision-table/test/spec/features/decision-rules/empty-rule.dmn @@ -1,6 +1,9 @@ - + + + + @@ -18,4 +21,20 @@ + + + + + + + + + + + + + + + + diff --git a/packages/dmn-js-decision-table/test/spec/features/decision-table-head/editor/input/InputEditor.dmn b/packages/dmn-js-decision-table/test/spec/features/decision-table-head/editor/input/InputEditor.dmn index 79188c80..1e0f6db3 100644 --- a/packages/dmn-js-decision-table/test/spec/features/decision-table-head/editor/input/InputEditor.dmn +++ b/packages/dmn-js-decision-table/test/spec/features/decision-table-head/editor/input/InputEditor.dmn @@ -1,6 +1,9 @@ - + + + + @@ -86,4 +89,20 @@ else + + + + + + + + + + + + + + + + diff --git a/packages/dmn-js-decision-table/test/spec/features/decision-table-head/editor/input/InputEditorCellSpec.js b/packages/dmn-js-decision-table/test/spec/features/decision-table-head/editor/input/InputEditorCellSpec.js index 9a5c23f5..36fc8f40 100644 --- a/packages/dmn-js-decision-table/test/spec/features/decision-table-head/editor/input/InputEditorCellSpec.js +++ b/packages/dmn-js-decision-table/test/spec/features/decision-table-head/editor/input/InputEditorCellSpec.js @@ -1,10 +1,12 @@ -import { bootstrapModeler, inject } from 'test/helper'; +import { bootstrapModeler, inject, act } from 'test/helper'; import { triggerInputEvent, triggerMouseEvent } from 'dmn-js-shared/test/util/EventUtil'; +import { DmnVariableResolverModule } from '@bpmn-io/dmn-variable-resolver'; + import { query as domQuery } from 'min-dom'; import TestContainer from 'mocha-test-container-support'; @@ -26,7 +28,8 @@ describe('decision-table-head/editor - input', function() { DecisionTableHeadModule, DecisionTableHeadEditorModule, ModelingModule, - KeyboardModule + KeyboardModule, + DmnVariableResolverModule ], debounceInput: false })); @@ -182,6 +185,32 @@ describe('decision-table-head/editor - input', function() { expect(inputBo.inputExpression.text).to.equal('foo'); })); }); + + + describe('integration', function() { + + it('should pass variables to editor', async function() { + + // given + const editorEl = openEditor('input2'); + const input = getControl('.ref-text [role="textbox"]', editorEl); + + // when + await changeInput(input, 'Var'); + + // then + await expectEventually(() => { + const options = testContainer.querySelectorAll('[role="option"]'); + + expect(options).to.exist; + expect(options).to.satisfy(options => { + const result = Array.from(options).some( + option => option.textContent === 'Variable'); + return result; + }); + }); + }); + }); }); @@ -240,11 +269,22 @@ function getControl(selector, parent) { * @param {string} value */ function changeInput(input, value) { - input.textContent = value; - - return new Promise(resolve => { - requestAnimationFrame(() => { - resolve(); - }); + return act(() => { + input.textContent = value; }); } + +async function expectEventually(fn) { + for (let i = 0; i < 10; i++) { + try { + await act(() => {}); + await fn(); + return; + } catch (e) { + + // wait + } + } + + return fn(); +} \ No newline at end of file diff --git a/packages/dmn-js-literal-expression/package.json b/packages/dmn-js-literal-expression/package.json index 026e358c..a5f5e6f5 100644 --- a/packages/dmn-js-literal-expression/package.json +++ b/packages/dmn-js-literal-expression/package.json @@ -30,6 +30,7 @@ "inferno-test-utils": "~5.6.2" }, "dependencies": { + "@bpmn-io/dmn-variable-resolver": "^0.3.1", "diagram-js": "^12.0.0", "dmn-js-shared": "^14.4.1", "escape-html": "^1.0.3", diff --git a/packages/dmn-js-literal-expression/src/Editor.js b/packages/dmn-js-literal-expression/src/Editor.js index 09ce9ccf..91da0303 100644 --- a/packages/dmn-js-literal-expression/src/Editor.js +++ b/packages/dmn-js-literal-expression/src/Editor.js @@ -1,3 +1,5 @@ +import { DmnVariableResolverModule } from '@bpmn-io/dmn-variable-resolver'; + import ExpressionLanguagesModule from 'dmn-js-shared/lib/features/expression-languages'; import DataTypesModule from 'dmn-js-shared/lib/features/data-types'; @@ -26,7 +28,8 @@ export default class Editor extends Viewer { ModelingModule, ExpressionLanguagesModule, DataTypesModule, - TextareaEditorComponent + TextareaEditorComponent, + DmnVariableResolverModule ]; } } \ No newline at end of file diff --git a/packages/dmn-js-literal-expression/src/features/textarea/components/TextareaEditorComponent.js b/packages/dmn-js-literal-expression/src/features/textarea/components/TextareaEditorComponent.js index 60bfb07b..ccd299b2 100644 --- a/packages/dmn-js-literal-expression/src/features/textarea/components/TextareaEditorComponent.js +++ b/packages/dmn-js-literal-expression/src/features/textarea/components/TextareaEditorComponent.js @@ -12,6 +12,7 @@ export default class TextareaEditorComponent extends Component { this._viewer = context.injector.get('viewer'); this._expressionLanguages = context.injector.get('expressionLanguages'); + this._variableResolver = context.injector.get('variableResolver', false); this.editLiteralExpressionText = this.editLiteralExpressionText.bind(this); this.onElementsChanged = this.onElementsChanged.bind(this); @@ -49,17 +50,26 @@ export default class TextareaEditorComponent extends Component { this._expressionLanguages.getDefault().value; } + _getVariables() { + const businessObject = this.getLiteralExpression(); + + return this._variableResolver && + this._variableResolver.getVariables(businessObject); + } + render() { // there is only one single element const { text } = this.getLiteralExpression(); const Editor = this.getEditor(); + const variables = this._getVariables(); return ( + onChange={ this.editLiteralExpressionText } + variables={ variables } /> ); } } @@ -70,6 +80,7 @@ class FeelEditor extends Component { className={ this.props.className } value={ this.props.value } onInput={ this.props.onChange } + variables={ this.props.variables } />; } } diff --git a/packages/dmn-js-literal-expression/test/spec/features/textarea/TextareaEditorSpec.js b/packages/dmn-js-literal-expression/test/spec/features/textarea/TextareaEditorSpec.js index 7cb58cf1..3a314939 100644 --- a/packages/dmn-js-literal-expression/test/spec/features/textarea/TextareaEditorSpec.js +++ b/packages/dmn-js-literal-expression/test/spec/features/textarea/TextareaEditorSpec.js @@ -2,6 +2,8 @@ import { bootstrapModeler, inject } from 'test/helper'; import { query as domQuery } from 'min-dom'; +import { DmnVariableResolverModule } from '@bpmn-io/dmn-variable-resolver'; + import { queryEditor } from 'dmn-js-shared/test/util/EditorUtil'; import ExpressionLanguagesModule from 'dmn-js-shared/lib/features/expression-languages'; @@ -26,7 +28,8 @@ describe('textarea editor', function() { CoreModule, TextareaEditorModule, ModelingModule, - ExpressionLanguagesModule + ExpressionLanguagesModule, + DmnVariableResolverModule ], debounceInput: false })); @@ -75,6 +78,33 @@ describe('textarea editor', function() { expect(viewer.getDecision().decisionLogic.text).to.equal('foo'); })); + + describe('integration', function() { + + it('should pass variables to editor', inject(async function(viewer) { + + // given + const editor = queryEditor('.textarea', testContainer); + + await act(() => editor.focus()); + + // when + await changeInput(document.activeElement, 'Var'); + + // then + await expectEventually(() => { + const options = testContainer.querySelectorAll('[role="option"]'); + + expect(options).to.exist; + expect(options).to.satisfy(options => { + const result = Array.from(options).some( + option => option.textContent === 'Variable'); + return result; + }); + }); + })); + }); + }); // helpers ////////// @@ -87,11 +117,26 @@ function changeInput(input, value) { return act(() => input.textContent = value); } -function act(fn) { - fn(); +async function act(fn) { + await fn(); return new Promise(resolve => { requestAnimationFrame(() => { resolve(); }); }); } + +async function expectEventually(fn) { + for (let i = 0; i < 10; i++) { + try { + await act(() => {}); + await fn(); + return; + } catch (e) { + + // wait + } + } + + return fn(); +} diff --git a/packages/dmn-js-literal-expression/test/spec/literal-expression.dmn b/packages/dmn-js-literal-expression/test/spec/literal-expression.dmn index 9d5b7410..bdf8de40 100644 --- a/packages/dmn-js-literal-expression/test/spec/literal-expression.dmn +++ b/packages/dmn-js-literal-expression/test/spec/literal-expression.dmn @@ -1,16 +1,28 @@ - + + + + calendar.getSeason(date) + - + + + + + + + + + diff --git a/packages/dmn-js-shared/src/components/LiteralExpression.js b/packages/dmn-js-shared/src/components/LiteralExpression.js index 1fbaecd3..f5dbe1d5 100644 --- a/packages/dmn-js-shared/src/components/LiteralExpression.js +++ b/packages/dmn-js-shared/src/components/LiteralExpression.js @@ -46,7 +46,8 @@ export default class LiteralExpression extends Component { this.editor = new FeelEditor({ container: this.node, onChange: this.handleChange, - value: this.state.value + value: this.state.value, + variables: this.props.variables || [], }); this.node.addEventListener('mousedown', this.handleMouseEvent); @@ -70,6 +71,10 @@ export default class LiteralExpression extends Component { this.editor.setValue(value); }); } + + if (!deepEqual(prevProps.variables, this.props.variables)) { + this.editor.setVariables(this.props.variables); + } } componentWillUnmount() { @@ -149,3 +154,7 @@ function isCmd(event) { function isAutocompleteOpen(node) { return node.querySelector('.cm-tooltip-autocomplete'); } + +function deepEqual(a, b) { + return JSON.stringify(a) === JSON.stringify(b); +}