diff --git a/docs/en/nodes/widgets/ui-template.md b/docs/en/nodes/widgets/ui-template.md index 6e4829f11..e12dc0779 100644 --- a/docs/en/nodes/widgets/ui-template.md +++ b/docs/en/nodes/widgets/ui-template.md @@ -45,6 +45,7 @@ When Node-RED uses its default Monaco code editor, the template editor understan - Vue syntax is highlighted, including directives (`v-if`, `v-for`, ...), bindings (`:prop`), events (`@click`), slots (`#activator`) and `{{ }}` expressions, which are highlighted as JavaScript. - Typing `` inside a component, that component's slots (`#`). Props with a fixed set of values, such as `variant`, suggest those values. - Hovering over a Vuetify component, prop, event or slot shows its type, default and description, with a link to the Vuetify API docs. +- With a Node-RED version that supports code in `') } + } + + describe('findComponent', function () { + it('finds the object of a single export default, like Dashboard does', function () { + const body = '\n// a comment\nexport default {\n data () { return {} }\n}\n' + const c = script.findComponent(body) + body[c.open].should.equal('{') + body[c.close].should.equal('}') + body.slice(c.close + 1).should.equal('\n') + should(script.findComponent('/* x */ export default {};')).not.be.null() + }) + it('allows // and /* */ comments before and after the component', function () { + const comments = ['// a {', '/* a { */', '/*\n * a {\n */'] + for (const before of comments) { + for (const after of comments) { + const body = `\n${before}\nexport default {}\n${after}\n` + const c = script.findComponent(body) + should(c).not.be.null() + body.slice(c.open, c.close + 1).should.equal('{}') + } + } + for (const body of ['export default {} // bye', 'export default {}; /* bye */', '/* a */ export default {}\n// b\n/* c */']) { + should(script.findComponent(body)).not.be.null() + } + }) + it('treats anything else as raw JavaScript', function () { + should(script.findComponent('const a = 1\nexport default {}')).be.null() + should(script.findComponent('this.send({ payload: 1 })')).be.null() + should(script.findComponent('export default { }\nconsole.log(1)')).be.null() + }) + }) + + describe('wrapScript', function () { + it('wraps a component in __uiTemplate( ... )', function () { + const text = '\n' + const wrapped = applyInsertions(text, script.wrapScript(text, scriptRegion(text), 0)) + wrapped.should.containEql('export default __uiTemplate({\n data () { return { a: 1 } }\n})\n') + }) + it('wraps raw JavaScript in a function whose this is the component', function () { + const text = '
\n' + const wrapped = applyInsertions(text, script.wrapScript(text, scriptRegion(text), 2)) + wrapped.should.containEql('') + }) + it('only inserts inside the script region', function () { + const text = '' + const region = scriptRegion(text) + for (const ins of script.wrapScript(text, region, 0)) { + ins.at.should.be.within(region.start, region.end) + } + }) + }) + + describe('UI_TEMPLATE_TYPES', function () { + it('describes what Dashboard adds to the component', function () { + for (const member of ['send(msg: any)', 'submit(event: Event)', 'readonly msg:', 'readonly id:', 'readonly props:', 'value: any', 'readonly $socket:', 'readonly $store:', 'readonly $router:', 'readonly $vuetify:']) { + script.UI_TEMPLATE_TYPES.should.containEql(member) + } + script.UI_TEMPLATE_TYPES.should.containEql('declare function __uiTemplate(') + }) + }) + + describe('activate', function () { + afterEach(function () { delete global.RED }) + + it('registers the wrapper, types and CSS with Node-RED', function () { + const calls = [] + global.RED = { editor: { codeEditor: { monaco: { registerEmbeddedLanguages: (id, options) => calls.push({ id, options }) } } } } + script.activate({}, { languageId: 'vue-sfc' }).should.be.true() + calls.should.have.length(1) + calls[0].id.should.equal('vue-sfc') + const { javascript, css } = calls[0].options + javascript.wrap.should.equal(script.wrapScript) + javascript.extraLibs[0].content.should.equal(script.UI_TEMPLATE_TYPES) + javascript.hiddenNames.test('__uiTemplateScript0').should.be.true() + javascript.demotedNames.test('$socket').should.be.true() + css.should.eql({}) + }) + it('does nothing on Node-RED versions without registerEmbeddedLanguages', function () { + global.RED = { editor: { codeEditor: { monaco: {} } } } + script.activate({}, { languageId: 'vue-sfc' }).should.be.false() + }) + }) +})