mirror of
https://github.com/node-red/node-red.git
synced 2025-03-01 10:36:34 +00:00
Merge branch 'upstream-update'
This commit is contained in:
commit
37b54ab9ae
524
CHANGELOG.md
524
CHANGELOG.md
@ -1,524 +0,0 @@
|
||||
#### 2.2.2: Maintenance Release
|
||||
|
||||
Nodes
|
||||
|
||||
- Fix "close timed out" error when performing full deploy or modifying broker node. (#3451) @Steve-Mcl
|
||||
|
||||
|
||||
#### 2.2.1: Maintenance Release
|
||||
|
||||
Editor
|
||||
|
||||
- Handle mixed-cased filter terms in keyboard shortcut dialog (#3444) @knolleary
|
||||
- Prevent duplicate links being added between nodes (#3442) @knolleary
|
||||
- Fix to hide tooltip after removing subflow tab (#3391) @HiroyasuNishiyama
|
||||
- Fix node validation to be applied to config node (#3397) @HiroyasuNishiyama
|
||||
- Fix: Dont add wires to undo buffer twice (#3437) @Steve-Mcl
|
||||
|
||||
Runtime
|
||||
|
||||
- Improve module location parsing (of stack info) when adding hook (#3447) @Steve-Mcl
|
||||
- Fix substitution of NR_NODE_PATH (#3445) @HiroyasuNishiyama
|
||||
- Remove console.log when ignoring disabled module (#3439) @knolleary
|
||||
- Improve "Unexpected Node Error" logging (#3446) @Steve-Mcl
|
||||
|
||||
Nodes
|
||||
|
||||
- Debug: Fix no-prototype-builtins bug in debug node and utils (#3394) @Alkarex
|
||||
- Delay: Fix Japanese message of delay node (#3434)
|
||||
- Allow nbRateUnits to be undefined when validating (#3443) @knolleary
|
||||
- Coding help for recently added node-red Predefined Environment Variables (#3440) @Steve-Mcl
|
||||
|
||||
|
||||
#### 2.2.0: Milestone Release
|
||||
|
||||
Editor
|
||||
|
||||
- Add editorTheme.tours property to default settings file (#3375) @knolleary
|
||||
- Remember Zoom level and Sidebar tab selection between sessions (#3361) @knolleary
|
||||
- Fix timing issue when merging background changes fixes #3364 (#3373) @Steve-Mcl
|
||||
- Use a nodes palette label in help tree (#3372) @Steve-Mcl
|
||||
- Subflow: Add labels to OUTPUT nodes (#3352) @ralphwetzel
|
||||
- Fix vertical align subflow port (#3370) @knolleary
|
||||
- Make actions list i18n ready and Japanese translation (#3359) @HiroyasuNishiyama
|
||||
- Update tour for 2.2.0 (#3378) @knolleary
|
||||
- Include paletteLabel when building search index (#3380) @Steve-Mcl
|
||||
- Fix opening/closing subflow template not to make subflow changed (#3382) @HiroyasuNishiyama
|
||||
- Add Japanese translations for v2.2.0 (#3353, #3381) @kazuhitoyokoi
|
||||
|
||||
Runtime
|
||||
|
||||
- Add support for accessing node id & name as environment variable (#3356) @HiroyasuNishiyama
|
||||
- Clear context contents when switching projects (#3243) @knolleary
|
||||
|
||||
Nodes
|
||||
|
||||
- MQTT: reject invalid topics (#3374) @Steve-Mcl
|
||||
- Function: Expose node.path property (#3371) @knolleary
|
||||
- Function: Update `node` declarations in func.d.ts (#3377) @Steve-Mcl
|
||||
|
||||
#### 2.2.0-beta.1: Beta Release
|
||||
|
||||
Editor
|
||||
|
||||
- Add search history to main search box (#3262) @knolleary
|
||||
- Check availability of type of config node on deploy (#3304) @k-toumura
|
||||
- Add wire-slice mode to delete wires with Ctrl-RHClick-Drag (#3340) @knolleary
|
||||
- Wiring keyboard shortcuts (#3288) @knolleary
|
||||
- Snap nodes on grid using either edge as reference (#3289) @knolleary
|
||||
- Detach node action (#3338) @knolleary
|
||||
- Highlight links when selecting nodes (#3323) @knolleary
|
||||
- Allow multiple links to be selected by ctrl-click (#3294) @knolleary
|
||||
|
||||
Nodes
|
||||
|
||||
- JSON: Let JSON node attempt to parse buffer if it contains a valid string (#3296) @dceejay
|
||||
- Remove use of verbose flag in core nodes - and use node.debug level instead (#3300) @dceejay
|
||||
- TCP: Add TLS option to tcp client nodes (#3307) @dceejay
|
||||
- WebSocket: Implemented support for Websocket Subprotocols in WS Client Node. (#3333) @tobiasoort
|
||||
|
||||
#### 2.1.6: Maintenance Release
|
||||
|
||||
Editor
|
||||
|
||||
- Revert copy-text change and apply alternative fix (#3363) @knolleary
|
||||
- Update marked to latest (#3362) @knolleary
|
||||
- fix to make start of property error tooltip messages aligned (#3358) @HiroyasuNishiyama
|
||||
|
||||
Nodes
|
||||
|
||||
- Inject: fix JSON propety validation of inject node (#3349) @HiroyasuNishiyama
|
||||
- Delay: fix unit value validation of delay node (#3351) @HiroyasuNishiyama
|
||||
|
||||
#### 2.1.5: Maintenance Release
|
||||
|
||||
Runtime
|
||||
|
||||
- Handle reporting error location when stack is truncated (#3346) @knolleary
|
||||
- Initialize passport when only adminAuth.tokens is set (#3343) @knolleary
|
||||
- Add log logging (#3342) @knolleary
|
||||
|
||||
Editor
|
||||
|
||||
- Fix copy buttons on the debug window (another method) (#3331) @kazuhitoyokoi
|
||||
- Add Japanese translations for hidden flow (#3302) @kazuhitoyokoi
|
||||
- Improve jsonata legacy mode detection regex (#3345) @knolleary
|
||||
- Fix generating flow name with incrementing number (#3347) @knolleary
|
||||
- resume focus after import/export dialog close (#3337) @HiroyasuNishiyama
|
||||
- Fix findPreviousVisibleTab action (#3321) @knolleary
|
||||
- Fix storing hidden tab state when not hidden via action (#3312) @knolleary
|
||||
- Avoid adding empty env properties to tabs/groups (#3311) @knolleary
|
||||
- Fix hide icon in tour guide (#3301) @kazuhitoyokoi
|
||||
|
||||
Nodes
|
||||
|
||||
- File: Update file node examples according to node name change (#3335) @HiroyasuNishiyama
|
||||
- Filter (RBE): Fix for filter node narrrowbandEq mode start condition failure (#3339) @dceejay
|
||||
- Function: Prevent function scrollbar from obscuring expand button (#3348) @knolleary
|
||||
- Function: load extralibs when expanding monaco. fixes #3319 (#3334) @Steve-Mcl
|
||||
- Function: Update Function to use correct api to access env vars (#3310) @knolleary
|
||||
- HTTP Request: Fix basic auth with empty username or password (#3325) @hardillb
|
||||
- Inject: Fix incorrect clearing of blank payload property in Inject node (#3322) @knolleary
|
||||
- Link Call: add link call example (#3336) @HiroyasuNishiyama
|
||||
- WebSocket: Only setup ws client heartbeat once it is connected (#3344) @knolleary
|
||||
- Update Japanese translations in node help (#3332) @kazuhitoyokoi
|
||||
|
||||
#### 2.1.4: Maintenance Release
|
||||
|
||||
Runtime
|
||||
|
||||
- fix env var access using $parent for groups (#3278) @HiroyasuNishiyama
|
||||
- Add proper error handling for 404 errors when serving debug files (#3277) @knolleary
|
||||
- Add Japanese translations for Node-RED v2.1.0-beta.1 (#3179) @kazuhitoyokoi
|
||||
- Include full user object on login audit events (#3269) @knolleary
|
||||
- Remove styling from de locale files (#3237) @knolleary
|
||||
|
||||
Editor
|
||||
|
||||
- Change tab hide button icon to an eye and add search option (#3282) @knolleary
|
||||
- Fix i18n handling of namespaces with spaces in (#3281) @knolleary
|
||||
- Trigger change event when autoComplete fills in input (#3280) @knolleary
|
||||
- Apply CN i18n fix (#3279) @knolleary
|
||||
- fix select menu label of config node to use paletteLabel (#3273) @HiroyasuNishiyama
|
||||
- fix removed tab not to cause node conflict (#3275) @HiroyasuNishiyama
|
||||
- Group diff fix (#3239) @knolleary
|
||||
- Only toggle disabled workspace flag if on activeWorkspace (#3252) @knolleary
|
||||
- Do not show status for disabled nodes (#3253) @knolleary
|
||||
- Set dimension value for tour guide (#3265) @kazuhitoyokoi
|
||||
- Avoid redundant initialisation of TypedInput type (#3263) @knolleary
|
||||
- Don't let themes change flow port label color (#3270) @bonanitech
|
||||
- Fix treeList gutter calculation to handle floating gutters (#3238) @knolleary
|
||||
|
||||
Nodes
|
||||
|
||||
- Debug: Handle RegExp types in Debug sidebar (#3251) @knolleary
|
||||
- Delay: fix 2nd output when in rate limit per topic modes (#3261) @dceejay
|
||||
- Link: fix to show link target when selected (#3267) @HiroyasuNishiyama
|
||||
- Inject: Do not modify inject node props in oneditprepare (#3242) @knolleary
|
||||
- HTTP Request: HTTP Basic Auth should always add : between username and password even if empty (#3236) @hardillb
|
||||
|
||||
#### 2.1.3: Maintenance Release
|
||||
|
||||
Runtime
|
||||
|
||||
- Update gen-publish script to update 'next' tag for main releases
|
||||
- Add environment variable to enable/disable tours (#3221) @hardillb
|
||||
- Fix loading non-default language files leaving runtime in wrong locale (#3225) @knolleary
|
||||
|
||||
Editor
|
||||
|
||||
- Refresh editor settings whenever a node is added or enabled (#3227) @knolleary
|
||||
- Revert spinner css change that made it shrink in some cases (#3229) @knolleary
|
||||
- Fix import notification message when importing config nodes (#3224) @knolleary
|
||||
- Handle changing types of TypedInput repeatedly (#3223) @knolleary
|
||||
|
||||
|
||||
#### 2.1.2: Maintenance Release
|
||||
|
||||
|
||||
Runtime
|
||||
|
||||
- node-red-pi: Remove bash dependency (#3216) @a16bitsysop
|
||||
|
||||
Editor
|
||||
|
||||
- Improved regex for markdown renderer (#3213) @GerwinvBeek
|
||||
- Fix TypedInput initialisation (#3220) @knolleary
|
||||
|
||||
Nodes
|
||||
|
||||
- MQTT: fix datatype in node config not used. fixes #3215 (#3219) @Steve-Mcl
|
||||
|
||||
#### 2.1.1: Maintenance Release
|
||||
|
||||
Editor
|
||||
|
||||
- Ensure tourGuide popover doesn't fall offscreen (#3212) @knolleary
|
||||
- Fix issue with old inject nodes that migrated topic to 'string' type (#3210) @knolleary
|
||||
- Add cache-busting query params to index.mst (#3211) @knolleary
|
||||
- Fix TypedInput validation of type without options (#3207) @knolleary
|
||||
|
||||
#### 2.1.0: Milestone Release
|
||||
|
||||
Editor
|
||||
|
||||
- Position popover properly on a scrolled page
|
||||
- Fixes from 2.1.0-beta.2 (#3202) @knolleary
|
||||
|
||||
Nodes
|
||||
|
||||
- Link Out: Fix saving link out node links (#3201) @knolleary
|
||||
- Switch: Refix #3170 - copy switch rule type when adding new rule
|
||||
- TCP Request: Add string option to TCP request node output (#3204) @dceejay
|
||||
|
||||
#### 2.1.0-beta.2: Beta Release
|
||||
|
||||
Editor
|
||||
|
||||
- Fix switching projects (#3199) @knolleary
|
||||
- Use locale setting when installing/enabling node (#3198) @knolleary
|
||||
- Do not show projects-wecome dialog until welcome tour completes (#3197) @knolleary
|
||||
- Fix converting selection to subflow (#3196) @knolleary
|
||||
- Avoid conflicts with native browser cmd-ctrl type shortcuts (#3195) @knolleary
|
||||
- Ensure message tools stay attached to top-level entry in Debug/Context (#3186) @knolleary
|
||||
- Ensure tab state updates properly when toggling enable state (#3175) @knolleary
|
||||
- Improve handling of long labels in TreeList (#3176) @knolleary
|
||||
- Shift-click tab scroll arrows to jump to start/end (#3177) @knolleary
|
||||
|
||||
Runtime
|
||||
|
||||
- Update package dependencies
|
||||
- Update to latest node-red-admin
|
||||
|
||||
Nodes
|
||||
|
||||
- Dynamic MQTT connections (#3189)
|
||||
- Link: Filter out Link Out Return nodes in Link In edit dialog Fixes #3187
|
||||
- Link: Fix link call label (#3200) @knolleary
|
||||
- Debug: Redesign debug filter options and make them persistant (#3183) @knolleary
|
||||
- Inject: Widen Inject interval box for >1 digit (#3184) @knolleary
|
||||
- Switch: Fix rule focus when switch 'otherwise' rule is used (#3185) @knolleary
|
||||
|
||||
#### 2.1.0-beta.1: Beta Release
|
||||
|
||||
Editor
|
||||
|
||||
- Add Tour Guide component (#3136) @knolleary
|
||||
- Allow tabs to be hidden (#3120) @knolleary
|
||||
- Add align actions to editor (#3110) @knolleary
|
||||
- Add support of environment variable for tab & group (#3112) @HiroyasuNishiyama
|
||||
- Add autoComplete widget and add to TypedInput for msg. props (#3171) @knolleary
|
||||
- Render node documentation to node-red style guide when written in markdown. (#3169) @Steve-Mcl
|
||||
- Allow colouring of tab icon svg (#3140) @harmonic7
|
||||
- Restore tab selection after merging conflicts (#3151) @GerwinvBeek
|
||||
- Fix serving of theme files on Windows (#3154) @knolleary
|
||||
- Ensure config-node select inherits width properly from input (#3155) @knolleary
|
||||
- Do better remembering TypedInput values whilst switching types (#3159) @knolleary
|
||||
- Update monaco to 0.28.1 (#3153) @knolleary
|
||||
- Improve themeing of tourGuide (#3161) @knolleary
|
||||
- Allow a node to specify a filter for the config nodes it can pick from (#3160) @knolleary
|
||||
- Allow RED.notify.update to modify any notification setting (#3163) @knolleary
|
||||
- Fix typo in ko editor.json Fixes #3119
|
||||
- Improve RED.actions api to ensure actions cannot be overridden
|
||||
- Ensure treeList row has suitable min-height when no content Fixes #3109
|
||||
- Refactor edit dialogs to use separate edit panes
|
||||
- Ensure type select button is not focussable when TypedInput only has one type
|
||||
- Place close tab link in front of fade
|
||||
|
||||
Runtime
|
||||
|
||||
- Improve error reporting with oauth login strategies (#3148) @knolleary
|
||||
- Add allowUpdate feature to externalModules.palette (#3143) @knolleary
|
||||
- Improve node install error reporting (#3158) @knolleary
|
||||
- Improve unit test coverage (#3168) @knolleary
|
||||
- Allow coreNodesDir to be set to false (#3149) @hardillb
|
||||
- Update package dependencies
|
||||
- uncaughtException debug improvements (#3146) @renatojuniorrs
|
||||
|
||||
Nodes
|
||||
|
||||
- Change: Add option to deep-clone properties in Change node (#3156) @knolleary
|
||||
- Delay: Add push to front of rate limit queue. (#3069) @dceejay
|
||||
- File: Add paletteLabel to file nodes to make read/write more obvious (#3157) @knolleary
|
||||
- HTTP Request: Extend HTTP request node to log detailed timing information (#3116) @k-toumura
|
||||
- HTTP Response: Fix sizing of HTTP Response header fields (#3164) @knolleary
|
||||
- Join: Support for msg.restartTimeout (#3121) @magma1447
|
||||
- Link Call: Add Link Call node (#3152) @knolleary
|
||||
- Switch: Copy previous rule type when adding rule to switch node (#3170) @knolleary
|
||||
- Delay node: add option to send intermediate messages on separate output (#3166) @knolleary
|
||||
- Typo in http request set method translation (#3173) @mailsvb
|
||||
|
||||
#### 2.0.6: Maintenance Release
|
||||
|
||||
Editor
|
||||
|
||||
- Fix typo in ko editor.json Fixes #3119
|
||||
- Change fade color when hovering an inactive tab (#3106) @bonanitech
|
||||
- Ensure treeList row has suitable min-height when no content Fixes #3109
|
||||
|
||||
Runtime
|
||||
|
||||
- Update tar to latest (#3128) @aksswami
|
||||
- Give passport verify callback the same arity as the original callback (#3117) @dschmidt
|
||||
- Handle HTTPS Key and certificate as string or buffer (#3115) @bartbutenaers
|
||||
|
||||
#### 2.0.5: Maintenance Release
|
||||
|
||||
Editor
|
||||
|
||||
- Remove default ctrl-enter keybinding from monaco editor Fixes #3093
|
||||
|
||||
Runtime
|
||||
|
||||
- Update tar dependency
|
||||
- Add support for maintenance streams in generate-publish-script
|
||||
|
||||
|
||||
Nodes
|
||||
|
||||
- Fix regression in Join node when manual joining array with msg.parts present Fixes #3096
|
||||
|
||||
#### 2.0.4: Maintenance Release
|
||||
|
||||
Editor
|
||||
|
||||
- Fix tab fade CSS for when using themes (#3085) @bonanitech
|
||||
- Handle just-copied-but-not-deployed node with credentials in editor Fixes #3090
|
||||
|
||||
Nodes
|
||||
|
||||
- Filter: Fix RBE node handling of default topi property Fixes #3087
|
||||
- HTTP Request: Handle partially encoded url query strings in request node
|
||||
- HTTP Request: Fix support for supplied CA certs (#3089) @hardillb
|
||||
- HTTP Request: Ensure TLS Cert is used (#3092) @hardillb
|
||||
- Inject: Fix inject now button unable to send empty props
|
||||
- Inject: Inject now button success notification should use label with updated props
|
||||
|
||||
#### 2.0.3: Maintenance Release
|
||||
|
||||
Nodes
|
||||
|
||||
- HTML: Fix HTML parsing when body is included in the select tag Fixes #3079
|
||||
- HTTP Request: Preserve case of user-provided http headers in request node Fixes #3081
|
||||
- HTTP Request: Set decompress to false for HTTP Request to keep 1.x compatibility Fixes #3083
|
||||
- HTTP Request: Add unit tests for HTTP Request encodeURI and error response
|
||||
- HTTP Request: Do not throw HTTP errors in request node Fixes #3082
|
||||
- HTTP Request: Ensure uri is properly encoded before passing to got module Fixes #3080
|
||||
|
||||
#### 2.0.2: Maintenance Release
|
||||
|
||||
Runtime
|
||||
|
||||
- Use file:// url with dynamic import
|
||||
- Detect if agent-base has patched https.request and undo it Fixes #3072
|
||||
|
||||
Editor
|
||||
|
||||
- Fix tab fade css because Safari Fixes #3073
|
||||
- Fix error closing library dialog with monaco
|
||||
- Handle other error types in Manage Palette view
|
||||
|
||||
|
||||
Nodes
|
||||
|
||||
- HTTP Request node - ignore invalid cookies rather than fail request Fixes #3075
|
||||
- Fix msg.reset handling in Delay node Fixes #3074
|
||||
|
||||
#### 2.0.1: Maintenance Release
|
||||
|
||||
Nodes
|
||||
|
||||
- Function: Ensure default module export is exposed in Function node
|
||||
|
||||
#### 2.0.0: Milestone Release
|
||||
|
||||
**Migration from 1.x**
|
||||
|
||||
- Node-RED now requires Node.js 12.x or later.
|
||||
|
||||
- The following nodes have had significant dependency updates. Unless stated,
|
||||
they should be fully backward compatible.
|
||||
|
||||
- RBE: Relabelled as 'filter' to make it more discoverable and made part of
|
||||
the core palette, rather than as a separate module.
|
||||
- Tail: This node has been removed from the default palette. You can reinstall it
|
||||
from node-red-node-tail
|
||||
- HTTP Request: Reimplemented with a different underlying module. We have
|
||||
tried to maintain 100% functional compatibility, but it is possible
|
||||
some edge cases remain.
|
||||
- JSON: The schema validation option no longer supports JSON-Schema draft-04
|
||||
- HTML: Its underlying module has had a major version update. Should be fully
|
||||
backward compatible.
|
||||
|
||||
- `functionExternalModules` is now enabled by default for new installs.
|
||||
If you have an existing settings file that contains this setting, you will
|
||||
need to set it to `true` yourself.
|
||||
|
||||
The external modules will now get installed in your Node-RED user directory,
|
||||
(`~/.node-red`) rather than in a subdirectory. This means all dependencies will
|
||||
be listed in your top-level `package.json`. If you have existing external modules,
|
||||
they will get reinstalled to the new location when you first run Node-RED 2.0.
|
||||
|
||||
|
||||
Runtime
|
||||
|
||||
- Fix missing dependencies (#3052, #2057) @kazuhitoyokoi
|
||||
- Ensure node.types is defined if node html file missing
|
||||
- Fix reporting of type_already_registered error
|
||||
- Move install location of external modules (#3064) @knolleary
|
||||
|
||||
Editor
|
||||
|
||||
- Update translations (#3063) @kazuhitoyokoi
|
||||
- Add a slight fade to tab labels that overflow
|
||||
- Show config node details when selected in outliner
|
||||
- Fix layout of info outliner for subflow entries
|
||||
|
||||
Nodes
|
||||
|
||||
- Delay: let `msg.flush` specify how many messages to flush from node (#3059) @dceejay
|
||||
- Function: external modules is now enabled by default (#3065) @knolleary
|
||||
- Function: external modules now supports both ES6 and CJS modules (#3065) @knolleary
|
||||
- WebSocket: add option for client node to send automatic pings (#3056) @knolleary
|
||||
|
||||
|
||||
##### 2.0.0-beta.2: Beta Release
|
||||
|
||||
Runtime
|
||||
|
||||
- Add `node-red admin init` (via `node-red-admin@2.1.0`)
|
||||
- Move to GH Actions rather than Travis for build (#3042) @knolleary
|
||||
|
||||
Editor
|
||||
|
||||
- Include hasUser=false config nodes when exporting whole flow (#3048)
|
||||
- Emit nodes:change for any updated config node when node deleted/added
|
||||
- Fix padding of compact notification Closes #3045
|
||||
- Ensure any html in changelog is escaped before displaying
|
||||
- Add support for Map/Set property types on Debug (#3040) @knolleary
|
||||
- Add 'theme' to default settings file
|
||||
- Add RED.view.annotations api (#3032) @knolleary
|
||||
- Update monaco editor to V0.25.2 (#3031) @Steve-Mcl
|
||||
- Lower tray zIndex when overlay tray being opened Fixes #3019
|
||||
- Reduce z-Index of Function expand buttons to prevent overlap Part of #3019
|
||||
- Ensure RED.clipboard.import displays the right library Fixes #3021
|
||||
- Batch messages sent over comms to prevent flooding (#3025) @knolleary
|
||||
- Allow RED.popover.panel to specify a closeButton to ignore click events on
|
||||
- Use browser default language for initial page load
|
||||
- Add css var for node font color
|
||||
- Fix label padding of toggleButton
|
||||
- Give sidebar open tab a bit more room for its label
|
||||
- Various Monaco updates (#3015) @Steve-Mcl
|
||||
- Log readOnly on startup (#3024) @sammachin
|
||||
- Translation updates (#3020 #3022) @HiroyasuNishiyama @kazuhitoyokoi
|
||||
|
||||
Nodes
|
||||
|
||||
- HTTP Request: Fix proxy handling (#3044) @hardillb
|
||||
- HTTP Request: Handle basic auth with @ in username (#3017) @hardillb
|
||||
- Add Japanese translation for file-in node (#3037 #3039) @kazuhitoyokoi
|
||||
- File In: Add option for file-in node to include all properties (default off) (#3035) @dceejay
|
||||
- Exec: add windowsHide option to hide windows under Windows (#3026) @natcl
|
||||
- Support loading external module sub path Fixes #3023
|
||||
|
||||
##### 2.0.0-beta.1: Beta Release
|
||||
|
||||
|
||||
|
||||
Runtime
|
||||
|
||||
- [MAJOR] Set minimum node version to 12.
|
||||
- [MAJOR] Fix flowfile name to flows.json in settings (#2951) @dceejay
|
||||
- [MAJOR] Update to latest i18n in editor and runtime (#2940) @knolleary
|
||||
- [MAJOR] Deprecate usage of httpRoot (#2953) @knolleary
|
||||
- Add pre/postInstall hooks to npm install handling (#2936) @knolleary
|
||||
- Add engine-strict flag to npm install args (#2965) @nileio
|
||||
- Restructure default settings.js to be more organised (#3012) @knolleary
|
||||
- Ensure httpServerOptions gets applied to ALL the express apps
|
||||
- Allow RED.settings.set to replace string property with object property
|
||||
- Update debug tests to handle compact comms format
|
||||
- Updates to encode/decode message when passed over debug comms link
|
||||
- Remove all input event listeners on a node once it is closed
|
||||
- Move hooks to util package
|
||||
- Rework hooks structure to be a linkedlist
|
||||
- Update dependencies (#2922) @knolleary
|
||||
|
||||
Editor
|
||||
|
||||
- [MAJOR] Change node id generation to give fixed length values without '.' (#2987) @knolleary
|
||||
- [MAJOR] Add Monaco code editor (#2971) @Steve-Mcl
|
||||
- Update to latest Monaco (#3007) @Steve-Mcl
|
||||
- Update Node-RED Function typings in Monaco (#3008) @Steve-Mcl
|
||||
- Add css named variables for certain key colours (#2994) @knolleary
|
||||
- Improve contrast of export dialog JSON font color
|
||||
- Switch editableList buttons from <a> to <button> elements
|
||||
- Add option to RED.nodes.createCompleteNodeSet to include node dimensions
|
||||
- Fix css of node help table of contents elements
|
||||
- Improve red-ui-node-icon css and add red-ui-node-icon-small modifier class
|
||||
- Add RED.hooks to editor
|
||||
- Add viewAddPort viewRemovePort viewAddNode viewRemoveNode hooks to view
|
||||
- Use paletteLabel if set in help sidebar
|
||||
- Add missing args from JSONata $now signature
|
||||
|
||||
Nodes
|
||||
|
||||
- [MAJOR] Relabel RBE node as 'filter' and move into core. Also remove tail (#2944) @dceejay
|
||||
- [MAJOR] HTTP Request: migrate to 'got' module (#2952) @knolleary
|
||||
- [MAJOR] Move Inject node to CronosJS module (#2959) @knolleary
|
||||
- [MAJOR] JSON: Update ajv to 8.2.0 - drop support for JSON-Schema draft-04 (#2969) @knolleary
|
||||
- [MAJOR] HTML node: cheerio update to 1.x (#3011) @knolleary
|
||||
- Join: change default manual mode to object (#2931) @knolleary
|
||||
- File node: Add fileWorkingDirectory (#2932) @knolleary
|
||||
- Delay node enhancements (#2294) @kazuhitoyokoi (#2949) @dceejay
|
||||
- Add Japanese translations for delay node enhancements (#2958) @kazuhitoyokoi
|
||||
- Inject node: reorder TypedInput options (#2961) @dceejay
|
||||
- HTTP Request: update to work with proxies (#2983) @hardillb (#3009) @hardillb
|
||||
- HTTP Request: fix msg.responseUrl (#2986) @hardillb
|
||||
- TLS: Add ALPN support to TLS node (#2988) @hardillb
|
||||
- Inject: add "Inject now" button to edit dialog (#2990) @Steve-Mcl
|
||||
|
||||
|
||||
|
||||
#### Older Releases
|
||||
|
||||
Change logs for older releases are available on GitHub: https://github.com/node-red/node-red/releases
|
@ -1,16 +0,0 @@
|
||||
# Contributing to Sparkles Guide
|
||||
|
||||
Submit an [issue](https://github.com/defenseunicorns/Sparkles-Guide/issues/new) if unsure.
|
||||
|
||||
### To Sparkle Guides
|
||||
|
||||
See [this](docs/sparkles-guides-contribution.md) guide, if you are looking to contribute to the guides and flows.
|
||||
|
||||
### To Code
|
||||
|
||||
See [this](docs/code-contribution.md) guide, if you are looking to add/remove/modify code implementing Sparkle's Guide.
|
||||
|
||||
## Copyright and license
|
||||
|
||||
Copyright OpenJS Foundation and other contributors, https://openjsf.org under [the Apache 2.0 license](LICENSE).
|
||||
December 2021 --- this project and modifications within it are a derivative work of [node-red](https://github.com/node-red/node-red)
|
1869
package-lock.json
generated
1869
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -78,6 +78,7 @@
|
||||
"bcrypt": "5.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"cypress": "^9.3.1",
|
||||
"dompurify": "2.3.5",
|
||||
"grunt": "1.4.1",
|
||||
"grunt-chmod": "~1.1.1",
|
||||
|
@ -2135,7 +2135,7 @@ RED.nodes = (function() {
|
||||
n = new_nodes[i];
|
||||
if (n.wires) {
|
||||
for (var w1=0;w1<n.wires.length;w1++) {
|
||||
var wires = (n.wires[w1] instanceof Array)?n.wires[w1]:[n.wires[w1]];
|
||||
var wires = (Array.isArray(n.wires[w1]))?n.wires[w1]:[n.wires[w1]];
|
||||
for (var w2=0;w2<wires.length;w2++) {
|
||||
if (node_map.hasOwnProperty(wires[w2])) {
|
||||
if (n.z === node_map[wires[w2]].z) {
|
||||
|
@ -252,8 +252,21 @@ var RED = (function() {
|
||||
if (/^#flow\/.+$/.test(currentHash)) {
|
||||
RED.workspaces.show(currentHash.substring(6),true);
|
||||
}
|
||||
if (RED.workspaces.active() === 0 && RED.workspaces.count() > 0) {
|
||||
RED.workspaces.show(RED.nodes.getWorkspaceOrder()[0])
|
||||
if (RED.workspaces.count() > 0) {
|
||||
const hiddenTabs = JSON.parse(RED.settings.getLocal("hiddenTabs")||"{}");
|
||||
const workspaces = RED.nodes.getWorkspaceOrder();
|
||||
if (RED.workspaces.active() === 0) {
|
||||
for (let index = 0; index < workspaces.length; index++) {
|
||||
const ws = workspaces[index];
|
||||
if (!hiddenTabs[ws]) {
|
||||
RED.workspaces.show(ws);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (RED.workspaces.active() === 0) {
|
||||
RED.workspaces.show(workspaces[0]);
|
||||
}
|
||||
}
|
||||
} catch(err) {
|
||||
console.warn(err);
|
||||
@ -461,7 +474,7 @@ var RED = (function() {
|
||||
var parts = topic.split("/");
|
||||
var node = RED.nodes.node(parts[1]);
|
||||
if (node) {
|
||||
if (msg.hasOwnProperty("text") && msg.text !== null && /^[a-zA-Z]/.test(msg.text)) {
|
||||
if (msg.hasOwnProperty("text") && msg.text !== null && /^[@a-zA-Z]/.test(msg.text)) {
|
||||
msg.text = node._(msg.text.toString(),{defaultValue:msg.text.toString()});
|
||||
}
|
||||
node.status = msg;
|
||||
|
@ -315,205 +315,208 @@ RED.deploy = (function() {
|
||||
},delta);
|
||||
});
|
||||
}
|
||||
function save(skipValidation,force) {
|
||||
if (!$("#red-ui-header-button-deploy").hasClass("disabled")) {
|
||||
if (!RED.user.hasPermission("flows.write")) {
|
||||
RED.notify(RED._("user.errors.deploy"),"error");
|
||||
function save(skipValidation, force) {
|
||||
if ($("#red-ui-header-button-deploy").hasClass("disabled")) {
|
||||
return; //deploy is disabled
|
||||
}
|
||||
if ($("#red-ui-header-shade").is(":visible")) {
|
||||
return; //deploy is shaded
|
||||
}
|
||||
if (!RED.user.hasPermission("flows.write")) {
|
||||
RED.notify(RED._("user.errors.deploy"), "error");
|
||||
return;
|
||||
}
|
||||
if (!skipValidation) {
|
||||
let hasUnknown = false;
|
||||
let hasInvalid = false;
|
||||
let hasUnusedConfig = false;
|
||||
const unknownNodes = [];
|
||||
const invalidNodes = [];
|
||||
|
||||
RED.nodes.eachConfig(function (node) {
|
||||
if (node.valid === undefined) {
|
||||
RED.editor.validateNode(node);
|
||||
}
|
||||
if (!node.valid && !node.d) {
|
||||
invalidNodes.push(getNodeInfo(node));
|
||||
}
|
||||
if (node.type === "unknown") {
|
||||
if (unknownNodes.indexOf(node.name) == -1) {
|
||||
unknownNodes.push(node.name);
|
||||
}
|
||||
}
|
||||
});
|
||||
RED.nodes.eachNode(function (node) {
|
||||
if (!node.valid && !node.d) {
|
||||
invalidNodes.push(getNodeInfo(node));
|
||||
}
|
||||
if (node.type === "unknown") {
|
||||
if (unknownNodes.indexOf(node.name) == -1) {
|
||||
unknownNodes.push(node.name);
|
||||
}
|
||||
}
|
||||
});
|
||||
hasUnknown = unknownNodes.length > 0;
|
||||
hasInvalid = invalidNodes.length > 0;
|
||||
|
||||
const unusedConfigNodes = [];
|
||||
RED.nodes.eachConfig(function (node) {
|
||||
if ((node._def.hasUsers !== false) && (node.users.length === 0)) {
|
||||
unusedConfigNodes.push(getNodeInfo(node));
|
||||
hasUnusedConfig = true;
|
||||
}
|
||||
});
|
||||
|
||||
let showWarning = false;
|
||||
let notificationMessage;
|
||||
let notificationButtons = [];
|
||||
let notification;
|
||||
if (hasUnknown && !ignoreDeployWarnings.unknown) {
|
||||
showWarning = true;
|
||||
notificationMessage = "<p>" + RED._('deploy.confirm.unknown') + "</p>" +
|
||||
'<ul class="red-ui-deploy-dialog-confirm-list"><li>' + cropList(unknownNodes).map(function (n) { return sanitize(n) }).join("</li><li>") + "</li></ul><p>" +
|
||||
RED._('deploy.confirm.confirm') +
|
||||
"</p>";
|
||||
|
||||
notificationButtons = [
|
||||
{
|
||||
id: "red-ui-deploy-dialog-confirm-deploy-deploy",
|
||||
text: RED._("deploy.confirm.button.confirm"),
|
||||
class: "primary",
|
||||
click: function () {
|
||||
save(true);
|
||||
notification.close();
|
||||
}
|
||||
}
|
||||
];
|
||||
} else if (hasInvalid && !ignoreDeployWarnings.invalid) {
|
||||
showWarning = true;
|
||||
invalidNodes.sort(sortNodeInfo);
|
||||
|
||||
notificationMessage = "<p>" + RED._('deploy.confirm.improperlyConfigured') + "</p>" +
|
||||
'<ul class="red-ui-deploy-dialog-confirm-list"><li>' + cropList(invalidNodes.map(function (A) { return sanitize((A.tab ? "[" + A.tab + "] " : "") + A.label + " (" + A.type + ")") })).join("</li><li>") + "</li></ul><p>" +
|
||||
RED._('deploy.confirm.confirm') +
|
||||
"</p>";
|
||||
notificationButtons = [
|
||||
{
|
||||
id: "red-ui-deploy-dialog-confirm-deploy-deploy",
|
||||
text: RED._("deploy.confirm.button.confirm"),
|
||||
class: "primary",
|
||||
click: function () {
|
||||
save(true);
|
||||
notification.close();
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
if (showWarning) {
|
||||
notificationButtons.unshift(
|
||||
{
|
||||
text: RED._("common.label.cancel"),
|
||||
click: function () {
|
||||
notification.close();
|
||||
}
|
||||
}
|
||||
);
|
||||
notification = RED.notify(notificationMessage, {
|
||||
modal: true,
|
||||
fixed: true,
|
||||
buttons: notificationButtons
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!skipValidation) {
|
||||
var hasUnknown = false;
|
||||
var hasInvalid = false;
|
||||
var hasUnusedConfig = false;
|
||||
|
||||
var unknownNodes = [];
|
||||
var invalidNodes = [];
|
||||
|
||||
RED.nodes.eachConfig(function(node) {
|
||||
if (node.valid === undefined) {
|
||||
RED.editor.validateNode(node);
|
||||
}
|
||||
if (!node.valid && !node.d) {
|
||||
invalidNodes.push(getNodeInfo(node));
|
||||
}
|
||||
if (node.type === "unknown") {
|
||||
if (unknownNodes.indexOf(node.name) == -1) {
|
||||
unknownNodes.push(node.name);
|
||||
}
|
||||
}
|
||||
});
|
||||
RED.nodes.eachNode(function(node) {
|
||||
if (!node.valid && !node.d) {
|
||||
invalidNodes.push(getNodeInfo(node));
|
||||
}
|
||||
if (node.type === "unknown") {
|
||||
if (unknownNodes.indexOf(node.name) == -1) {
|
||||
unknownNodes.push(node.name);
|
||||
}
|
||||
}
|
||||
});
|
||||
hasUnknown = unknownNodes.length > 0;
|
||||
hasInvalid = invalidNodes.length > 0;
|
||||
|
||||
var unusedConfigNodes = [];
|
||||
RED.nodes.eachConfig(function(node) {
|
||||
if ((node._def.hasUsers !== false) && (node.users.length === 0)) {
|
||||
unusedConfigNodes.push(getNodeInfo(node));
|
||||
hasUnusedConfig = true;
|
||||
}
|
||||
});
|
||||
|
||||
var showWarning = false;
|
||||
var notificationMessage;
|
||||
var notificationButtons = [];
|
||||
var notification;
|
||||
if (hasUnknown && !ignoreDeployWarnings.unknown) {
|
||||
showWarning = true;
|
||||
notificationMessage = "<p>"+RED._('deploy.confirm.unknown')+"</p>"+
|
||||
'<ul class="red-ui-deploy-dialog-confirm-list"><li>'+cropList(unknownNodes).map(function(n) { return sanitize(n) }).join("</li><li>")+"</li></ul><p>"+
|
||||
RED._('deploy.confirm.confirm')+
|
||||
"</p>";
|
||||
|
||||
notificationButtons= [
|
||||
{
|
||||
id: "red-ui-deploy-dialog-confirm-deploy-deploy",
|
||||
text: RED._("deploy.confirm.button.confirm"),
|
||||
class: "primary",
|
||||
click: function() {
|
||||
save(true);
|
||||
notification.close();
|
||||
}
|
||||
}
|
||||
];
|
||||
} else if (hasInvalid && !ignoreDeployWarnings.invalid) {
|
||||
showWarning = true;
|
||||
invalidNodes.sort(sortNodeInfo);
|
||||
|
||||
notificationMessage = "<p>"+RED._('deploy.confirm.improperlyConfigured')+"</p>"+
|
||||
'<ul class="red-ui-deploy-dialog-confirm-list"><li>'+cropList(invalidNodes.map(function(A) { return sanitize( (A.tab?"["+A.tab+"] ":"")+A.label+" ("+A.type+")")})).join("</li><li>")+"</li></ul><p>"+
|
||||
RED._('deploy.confirm.confirm')+
|
||||
"</p>";
|
||||
notificationButtons= [
|
||||
{
|
||||
id: "red-ui-deploy-dialog-confirm-deploy-deploy",
|
||||
text: RED._("deploy.confirm.button.confirm"),
|
||||
class: "primary",
|
||||
click: function() {
|
||||
save(true);
|
||||
notification.close();
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
if (showWarning) {
|
||||
notificationButtons.unshift(
|
||||
{
|
||||
text: RED._("common.label.cancel"),
|
||||
click: function() {
|
||||
notification.close();
|
||||
}
|
||||
}
|
||||
);
|
||||
notification = RED.notify(notificationMessage,{
|
||||
modal: true,
|
||||
fixed: true,
|
||||
buttons:notificationButtons
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var nns = RED.nodes.createCompleteNodeSet();
|
||||
|
||||
var startTime = Date.now();
|
||||
$(".red-ui-deploy-button-content").css('opacity',0);
|
||||
$(".red-ui-deploy-button-spinner").show();
|
||||
$("#red-ui-header-button-deploy").addClass("disabled");
|
||||
|
||||
var data = {flows:nns};
|
||||
|
||||
if (!force) {
|
||||
data.rev = RED.nodes.version();
|
||||
}
|
||||
|
||||
deployInflight = true;
|
||||
$("#red-ui-header-shade").show();
|
||||
$("#red-ui-editor-shade").show();
|
||||
$("#red-ui-palette-shade").show();
|
||||
$("#red-ui-sidebar-shade").show();
|
||||
$.ajax({
|
||||
url:"flows",
|
||||
type: "POST",
|
||||
data: JSON.stringify(data),
|
||||
contentType: "application/json; charset=utf-8",
|
||||
headers: {
|
||||
"Node-RED-Deployment-Type":deploymentType
|
||||
}
|
||||
}).done(function(data,textStatus,xhr) {
|
||||
RED.nodes.dirty(false);
|
||||
RED.nodes.version(data.rev);
|
||||
RED.nodes.originalFlow(nns);
|
||||
if (hasUnusedConfig) {
|
||||
RED.notify(
|
||||
'<p>'+RED._("deploy.successfulDeploy")+'</p>'+
|
||||
'<p>'+RED._("deploy.unusedConfigNodes")+' <a href="#" onclick="RED.sidebar.config.show(true); return false;">'+RED._("deploy.unusedConfigNodesLink")+'</a></p>',"success",false,6000);
|
||||
} else {
|
||||
RED.notify('<p>'+RED._("deploy.successfulDeploy")+'</p>',"success");
|
||||
}
|
||||
RED.nodes.eachNode(function(node) {
|
||||
if (node.changed) {
|
||||
node.dirty = true;
|
||||
node.changed = false;
|
||||
}
|
||||
if (node.moved) {
|
||||
node.dirty = true;
|
||||
node.moved = false;
|
||||
}
|
||||
if(node.credentials) {
|
||||
delete node.credentials;
|
||||
}
|
||||
});
|
||||
RED.nodes.eachConfig(function (confNode) {
|
||||
confNode.changed = false;
|
||||
if (confNode.credentials) {
|
||||
delete confNode.credentials;
|
||||
}
|
||||
});
|
||||
RED.nodes.eachSubflow(function(subflow) {
|
||||
subflow.changed = false;
|
||||
});
|
||||
RED.nodes.eachWorkspace(function(ws) {
|
||||
ws.changed = false;
|
||||
});
|
||||
// Once deployed, cannot undo back to a clean state
|
||||
RED.history.markAllDirty();
|
||||
RED.view.redraw();
|
||||
RED.events.emit("deploy");
|
||||
}).fail(function(xhr,textStatus,err) {
|
||||
RED.nodes.dirty(true);
|
||||
$("#red-ui-header-button-deploy").removeClass("disabled");
|
||||
if (xhr.status === 401) {
|
||||
RED.notify(RED._("deploy.deployFailed",{message:RED._("user.notAuthorized")}),"error");
|
||||
} else if (xhr.status === 409) {
|
||||
resolveConflict(nns, true);
|
||||
} else if (xhr.responseText) {
|
||||
RED.notify(RED._("deploy.deployFailed",{message:xhr.responseText}),"error");
|
||||
} else {
|
||||
RED.notify(RED._("deploy.deployFailed",{message:RED._("deploy.errors.noResponse")}),"error");
|
||||
}
|
||||
}).always(function() {
|
||||
deployInflight = false;
|
||||
var delta = Math.max(0,300-(Date.now()-startTime));
|
||||
setTimeout(function() {
|
||||
$(".red-ui-deploy-button-content").css('opacity',1);
|
||||
$(".red-ui-deploy-button-spinner").hide();
|
||||
$("#red-ui-header-shade").hide();
|
||||
$("#red-ui-editor-shade").hide();
|
||||
$("#red-ui-palette-shade").hide();
|
||||
$("#red-ui-sidebar-shade").hide();
|
||||
},delta);
|
||||
});
|
||||
}
|
||||
|
||||
const nns = RED.nodes.createCompleteNodeSet();
|
||||
const startTime = Date.now();
|
||||
|
||||
$(".red-ui-deploy-button-content").css('opacity', 0);
|
||||
$(".red-ui-deploy-button-spinner").show();
|
||||
$("#red-ui-header-button-deploy").addClass("disabled");
|
||||
|
||||
const data = { flows: nns };
|
||||
|
||||
if (!force) {
|
||||
data.rev = RED.nodes.version();
|
||||
}
|
||||
|
||||
deployInflight = true;
|
||||
$("#red-ui-header-shade").show();
|
||||
$("#red-ui-editor-shade").show();
|
||||
$("#red-ui-palette-shade").show();
|
||||
$("#red-ui-sidebar-shade").show();
|
||||
$.ajax({
|
||||
url: "flows",
|
||||
type: "POST",
|
||||
data: JSON.stringify(data),
|
||||
contentType: "application/json; charset=utf-8",
|
||||
headers: {
|
||||
"Node-RED-Deployment-Type": deploymentType
|
||||
}
|
||||
}).done(function (data, textStatus, xhr) {
|
||||
RED.nodes.dirty(false);
|
||||
RED.nodes.version(data.rev);
|
||||
RED.nodes.originalFlow(nns);
|
||||
if (hasUnusedConfig) {
|
||||
RED.notify(
|
||||
'<p>' + RED._("deploy.successfulDeploy") + '</p>' +
|
||||
'<p>' + RED._("deploy.unusedConfigNodes") + ' <a href="#" onclick="RED.sidebar.config.show(true); return false;">' + RED._("deploy.unusedConfigNodesLink") + '</a></p>', "success", false, 6000);
|
||||
} else {
|
||||
RED.notify('<p>' + RED._("deploy.successfulDeploy") + '</p>', "success");
|
||||
}
|
||||
RED.nodes.eachNode(function (node) {
|
||||
if (node.changed) {
|
||||
node.dirty = true;
|
||||
node.changed = false;
|
||||
}
|
||||
if (node.moved) {
|
||||
node.dirty = true;
|
||||
node.moved = false;
|
||||
}
|
||||
if (node.credentials) {
|
||||
delete node.credentials;
|
||||
}
|
||||
});
|
||||
RED.nodes.eachConfig(function (confNode) {
|
||||
confNode.changed = false;
|
||||
if (confNode.credentials) {
|
||||
delete confNode.credentials;
|
||||
}
|
||||
});
|
||||
RED.nodes.eachSubflow(function (subflow) {
|
||||
subflow.changed = false;
|
||||
});
|
||||
RED.nodes.eachWorkspace(function (ws) {
|
||||
ws.changed = false;
|
||||
});
|
||||
// Once deployed, cannot undo back to a clean state
|
||||
RED.history.markAllDirty();
|
||||
RED.view.redraw();
|
||||
RED.events.emit("deploy");
|
||||
}).fail(function (xhr, textStatus, err) {
|
||||
RED.nodes.dirty(true);
|
||||
$("#red-ui-header-button-deploy").removeClass("disabled");
|
||||
if (xhr.status === 401) {
|
||||
RED.notify(RED._("deploy.deployFailed", { message: RED._("user.notAuthorized") }), "error");
|
||||
} else if (xhr.status === 409) {
|
||||
resolveConflict(nns, true);
|
||||
} else if (xhr.responseText) {
|
||||
RED.notify(RED._("deploy.deployFailed", { message: xhr.responseText }), "error");
|
||||
} else {
|
||||
RED.notify(RED._("deploy.deployFailed", { message: RED._("deploy.errors.noResponse") }), "error");
|
||||
}
|
||||
}).always(function () {
|
||||
deployInflight = false;
|
||||
const delta = Math.max(0, 300 - (Date.now() - startTime));
|
||||
setTimeout(function () {
|
||||
$(".red-ui-deploy-button-content").css('opacity', 1);
|
||||
$(".red-ui-deploy-button-spinner").hide();
|
||||
$("#red-ui-header-shade").hide();
|
||||
$("#red-ui-editor-shade").hide();
|
||||
$("#red-ui-palette-shade").hide();
|
||||
$("#red-ui-sidebar-shade").hide();
|
||||
}, delta);
|
||||
});
|
||||
}
|
||||
return {
|
||||
init: init,
|
||||
|
@ -168,7 +168,7 @@
|
||||
'b': { before:"**", after: "**", tooltip: RED._("markdownEditor.bold")},
|
||||
'i': { before:"_", after: "_", tooltip: RED._("markdownEditor.italic")},
|
||||
'code': { before:"`", after: "`", tooltip: RED._("markdownEditor.code")},
|
||||
'ol': { before:" * ", newline: true, tooltip: RED._("markdownEditor.ordered-list")},
|
||||
'ol': { before:" 1. ", newline: true, tooltip: RED._("markdownEditor.ordered-list")},
|
||||
'ul': { before:" - ", newline: true, tooltip: RED._("markdownEditor.unordered-list")},
|
||||
'bq': { before:"> ", newline: true, tooltip: RED._("markdownEditor.quote")},
|
||||
'link': { before:"[", after: "]()", tooltip: RED._("markdownEditor.link")},
|
||||
|
@ -107,7 +107,7 @@
|
||||
newValue = "";
|
||||
}
|
||||
}
|
||||
if (node[d] != newValue) {
|
||||
if (!isEqual(node[d], newValue)) {
|
||||
if (node._def.defaults[d].type) {
|
||||
// Change to a related config node
|
||||
var configNode = RED.nodes.node(node[d]);
|
||||
@ -139,6 +139,23 @@
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Compares `newValue` with `originalValue` for equality.
|
||||
* @param {*} originalValue Original value
|
||||
* @param {*} newValue New value
|
||||
* @returns {boolean} true if originalValue equals newValue, otherwise false
|
||||
*/
|
||||
function isEqual(originalValue, newValue) {
|
||||
try {
|
||||
if(originalValue == newValue) {
|
||||
return true;
|
||||
}
|
||||
return JSON.stringify(originalValue) === JSON.stringify(newValue);
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the node credentials from the edit form
|
||||
* @param node - the node containing the credentials
|
||||
|
@ -346,7 +346,7 @@ RED.sidebar.config = (function() {
|
||||
refreshConfigNodeList();
|
||||
}
|
||||
});
|
||||
RED.popover.tooltip($('#red-ui-sidebar-config-filter-all'), RED._("sidebar.config.showAllUnusedConfigNodes"));
|
||||
RED.popover.tooltip($('#red-ui-sidebar-config-filter-all'), RED._("sidebar.config.showAllConfigNodes"));
|
||||
RED.popover.tooltip($('#red-ui-sidebar-config-filter-unused'), RED._("sidebar.config.showAllUnusedConfigNodes"));
|
||||
|
||||
}
|
||||
|
@ -5743,7 +5743,7 @@
|
||||
node.dirty = true;
|
||||
RED.workspaces.show(node.z);
|
||||
|
||||
var screenSize = [chart.width()/scaleFactor,chart.height()/scaleFactor];
|
||||
var screenSize = [chart[0].clientWidth/scaleFactor,chart[0].clientHeight/scaleFactor];
|
||||
var scrollPos = [chart.scrollLeft()/scaleFactor,chart.scrollTop()/scaleFactor];
|
||||
var cx = node.x;
|
||||
var cy = node.y;
|
||||
|
@ -151,6 +151,8 @@ $popover-button-border-color-hover: #666;
|
||||
$diff-text-header-color: $secondary-text-color;
|
||||
$diff-text-header-background: #ffd;
|
||||
$diff-text-header-background-hover: #ffc;
|
||||
$diff-state-color: $primary-text-color;
|
||||
$diff-state-prefix-color: $secondary-text-color;
|
||||
$diff-state-added: #009900;
|
||||
$diff-state-deleted: #f80000;
|
||||
$diff-state-changed: #f89406;
|
||||
|
@ -562,7 +562,7 @@ ul.red-ui-deploy-dialog-confirm-list {
|
||||
width: 30px;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
color: $secondary-text-color;
|
||||
color: $diff-state-prefix-color;
|
||||
}
|
||||
|
||||
&.added {
|
||||
@ -577,9 +577,11 @@ ul.red-ui-deploy-dialog-confirm-list {
|
||||
}
|
||||
td.added {
|
||||
background: $diff-state-added-background;
|
||||
color: $diff-state-color;
|
||||
}
|
||||
td.removed {
|
||||
background: $diff-state-deleted-background;
|
||||
color: $diff-state-color;
|
||||
}
|
||||
tr.mergeHeader td {
|
||||
color: $diff-merge-header-color;
|
||||
@ -652,7 +654,7 @@ ul.red-ui-deploy-dialog-confirm-list {
|
||||
font-family: $monospace-font;
|
||||
padding: 5px 10px;
|
||||
text-align: left;
|
||||
color: $secondary-text-color;
|
||||
color: $diff-text-header-color;
|
||||
background: $diff-text-header-background;
|
||||
height: 30px;
|
||||
vertical-align: middle;
|
||||
|
@ -1,113 +0,0 @@
|
||||
[
|
||||
{
|
||||
"id": "84222b92.d65d18",
|
||||
"type": "inject",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "",
|
||||
"props": [
|
||||
{
|
||||
"p": "payload"
|
||||
},
|
||||
{
|
||||
"p": "topic",
|
||||
"vt": "str"
|
||||
}
|
||||
],
|
||||
"repeat": "",
|
||||
"crontab": "",
|
||||
"once": false,
|
||||
"onceDelay": 0.1,
|
||||
"topic": "",
|
||||
"payload": "Hello, World!",
|
||||
"payloadType": "str",
|
||||
"x": 190,
|
||||
"y": 180,
|
||||
"wires": [
|
||||
[
|
||||
"b4b9f603.739598"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "7b014430.dfd94c",
|
||||
"type": "comment",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "Write string to a file, then read from the file",
|
||||
"info": "Read file node can read string from a file.",
|
||||
"x": 220,
|
||||
"y": 100,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "b4b9f603.739598",
|
||||
"type": "file",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "",
|
||||
"filename": "/tmp/hello.txt",
|
||||
"appendNewline": true,
|
||||
"createDir": false,
|
||||
"overwriteFile": "true",
|
||||
"encoding": "none",
|
||||
"x": 380,
|
||||
"y": 180,
|
||||
"wires": [
|
||||
[
|
||||
"6dc01cac.5c4bf4"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "2587adb9.7e60f2",
|
||||
"type": "debug",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "",
|
||||
"active": true,
|
||||
"tosidebar": true,
|
||||
"console": false,
|
||||
"tostatus": false,
|
||||
"complete": "false",
|
||||
"statusVal": "",
|
||||
"statusType": "auto",
|
||||
"x": 770,
|
||||
"y": 180,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "6dc01cac.5c4bf4",
|
||||
"type": "file in",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "",
|
||||
"filename": "/tmp/hello.txt",
|
||||
"format": "utf8",
|
||||
"chunk": false,
|
||||
"sendError": false,
|
||||
"encoding": "none",
|
||||
"x": 580,
|
||||
"y": 180,
|
||||
"wires": [
|
||||
[
|
||||
"2587adb9.7e60f2"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "f4b4309a.3b78a",
|
||||
"type": "comment",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "↑read result from file",
|
||||
"info": "",
|
||||
"x": 590,
|
||||
"y": 220,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "672d3693.3cabd8",
|
||||
"type": "comment",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "↓write to /tmp/hello.txt",
|
||||
"info": "",
|
||||
"x": 400,
|
||||
"y": 140,
|
||||
"wires": []
|
||||
}
|
||||
]
|
@ -1,114 +0,0 @@
|
||||
[
|
||||
{
|
||||
"id": "8997398f.c5d628",
|
||||
"type": "inject",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "",
|
||||
"props": [
|
||||
{
|
||||
"p": "payload"
|
||||
},
|
||||
{
|
||||
"p": "topic",
|
||||
"vt": "str"
|
||||
}
|
||||
],
|
||||
"repeat": "",
|
||||
"crontab": "",
|
||||
"once": false,
|
||||
"onceDelay": 0.1,
|
||||
"topic": "",
|
||||
"payload": "😀",
|
||||
"payloadType": "str",
|
||||
"x": 170,
|
||||
"y": 260,
|
||||
"wires": [
|
||||
[
|
||||
"56e32d23.050f44"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "4e598e65.1799d",
|
||||
"type": "comment",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "Read data in specified encoding",
|
||||
"info": "Read file node can specify encoding of data read from a file.",
|
||||
"x": 190,
|
||||
"y": 180,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "56e32d23.050f44",
|
||||
"type": "file",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "",
|
||||
"filename": "/tmp/hello.txt",
|
||||
"appendNewline": true,
|
||||
"createDir": false,
|
||||
"overwriteFile": "true",
|
||||
"encoding": "none",
|
||||
"x": 340,
|
||||
"y": 260,
|
||||
"wires": [
|
||||
[
|
||||
"38fa0579.f2cd8a"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "d28c8994.99c0a8",
|
||||
"type": "debug",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "",
|
||||
"active": true,
|
||||
"tosidebar": true,
|
||||
"console": false,
|
||||
"tostatus": false,
|
||||
"complete": "false",
|
||||
"statusVal": "",
|
||||
"statusType": "auto",
|
||||
"x": 730,
|
||||
"y": 260,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "38fa0579.f2cd8a",
|
||||
"type": "file in",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "",
|
||||
"filename": "/tmp/hello.txt",
|
||||
"format": "utf8",
|
||||
"chunk": false,
|
||||
"sendError": false,
|
||||
"encoding": "base64",
|
||||
"allProps": false,
|
||||
"x": 540,
|
||||
"y": 260,
|
||||
"wires": [
|
||||
[
|
||||
"d28c8994.99c0a8"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "fa22ca20.ae4528",
|
||||
"type": "comment",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "↑read data from file as base64 string",
|
||||
"info": "",
|
||||
"x": 600,
|
||||
"y": 300,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "148e25ad.98891a",
|
||||
"type": "comment",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "↓write to /tmp/hello.txt",
|
||||
"info": "",
|
||||
"x": 360,
|
||||
"y": 220,
|
||||
"wires": []
|
||||
}
|
||||
]
|
@ -1,132 +0,0 @@
|
||||
[
|
||||
{
|
||||
"id": "6a0b1d03.d4cee4",
|
||||
"type": "inject",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "",
|
||||
"props": [
|
||||
{
|
||||
"p": "payload"
|
||||
},
|
||||
{
|
||||
"p": "topic",
|
||||
"vt": "str"
|
||||
}
|
||||
],
|
||||
"repeat": "",
|
||||
"crontab": "",
|
||||
"once": false,
|
||||
"onceDelay": 0.1,
|
||||
"topic": "",
|
||||
"payload": "",
|
||||
"payloadType": "date",
|
||||
"x": 160,
|
||||
"y": 220,
|
||||
"wires": [
|
||||
[
|
||||
"d4b00cb7.a5a23"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "f17ea1d1.8ecc3",
|
||||
"type": "comment",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "Read data breaking lines into individual messages",
|
||||
"info": "Read file node can break read text into messages with individual lines",
|
||||
"x": 230,
|
||||
"y": 140,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "99ae7806.1d6428",
|
||||
"type": "file",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "",
|
||||
"filename": "/tmp/hello.txt",
|
||||
"appendNewline": true,
|
||||
"createDir": false,
|
||||
"overwriteFile": "true",
|
||||
"encoding": "none",
|
||||
"x": 480,
|
||||
"y": 220,
|
||||
"wires": [
|
||||
[
|
||||
"70d7892f.d27db8"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "7ed8282c.92b338",
|
||||
"type": "debug",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "",
|
||||
"active": true,
|
||||
"tosidebar": true,
|
||||
"console": false,
|
||||
"tostatus": false,
|
||||
"complete": "false",
|
||||
"statusVal": "",
|
||||
"statusType": "auto",
|
||||
"x": 750,
|
||||
"y": 280,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "70d7892f.d27db8",
|
||||
"type": "file in",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "",
|
||||
"filename": "/tmp/hello.txt",
|
||||
"format": "lines",
|
||||
"chunk": false,
|
||||
"sendError": false,
|
||||
"encoding": "none",
|
||||
"x": 560,
|
||||
"y": 280,
|
||||
"wires": [
|
||||
[
|
||||
"7ed8282c.92b338"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "c1b7e05.1d94b2",
|
||||
"type": "comment",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "↑read data from file breaking lines into messages",
|
||||
"info": "",
|
||||
"x": 660,
|
||||
"y": 320,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "a5f647b2.cf27a8",
|
||||
"type": "comment",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "↓write to /tmp/hello.txt",
|
||||
"info": "",
|
||||
"x": 500,
|
||||
"y": 180,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "d4b00cb7.a5a23",
|
||||
"type": "template",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "data",
|
||||
"field": "payload",
|
||||
"fieldType": "msg",
|
||||
"format": "handlebars",
|
||||
"syntax": "plain",
|
||||
"template": "one\ntwo\nthree!",
|
||||
"output": "str",
|
||||
"x": 310,
|
||||
"y": 220,
|
||||
"wires": [
|
||||
[
|
||||
"99ae7806.1d6428"
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
@ -1,113 +0,0 @@
|
||||
[
|
||||
{
|
||||
"id": "84222b92.d65d18",
|
||||
"type": "inject",
|
||||
"z": "5132b95f037524f9",
|
||||
"name": "",
|
||||
"props": [
|
||||
{
|
||||
"p": "payload"
|
||||
},
|
||||
{
|
||||
"p": "topic",
|
||||
"vt": "str"
|
||||
}
|
||||
],
|
||||
"repeat": "",
|
||||
"crontab": "",
|
||||
"once": false,
|
||||
"onceDelay": 0.1,
|
||||
"topic": "",
|
||||
"payload": "Hello, World!",
|
||||
"payloadType": "str",
|
||||
"x": 150,
|
||||
"y": 220,
|
||||
"wires": [
|
||||
[
|
||||
"b4b9f603.739598"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "7b014430.dfd94c",
|
||||
"type": "comment",
|
||||
"z": "5132b95f037524f9",
|
||||
"name": "Write string to a file, then read from the file",
|
||||
"info": "Write file node can write string from a file.",
|
||||
"x": 180,
|
||||
"y": 140,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "b4b9f603.739598",
|
||||
"type": "file",
|
||||
"z": "5132b95f037524f9",
|
||||
"name": "",
|
||||
"filename": "/tmp/hello.txt",
|
||||
"appendNewline": true,
|
||||
"createDir": false,
|
||||
"overwriteFile": "true",
|
||||
"encoding": "none",
|
||||
"x": 340,
|
||||
"y": 220,
|
||||
"wires": [
|
||||
[
|
||||
"6dc01cac.5c4bf4"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "2587adb9.7e60f2",
|
||||
"type": "debug",
|
||||
"z": "5132b95f037524f9",
|
||||
"name": "",
|
||||
"active": true,
|
||||
"tosidebar": true,
|
||||
"console": false,
|
||||
"tostatus": false,
|
||||
"complete": "false",
|
||||
"statusVal": "",
|
||||
"statusType": "auto",
|
||||
"x": 730,
|
||||
"y": 220,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "6dc01cac.5c4bf4",
|
||||
"type": "file in",
|
||||
"z": "5132b95f037524f9",
|
||||
"name": "",
|
||||
"filename": "/tmp/hello.txt",
|
||||
"format": "utf8",
|
||||
"chunk": false,
|
||||
"sendError": false,
|
||||
"encoding": "none",
|
||||
"x": 540,
|
||||
"y": 220,
|
||||
"wires": [
|
||||
[
|
||||
"2587adb9.7e60f2"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "f4b4309a.3b78a",
|
||||
"type": "comment",
|
||||
"z": "5132b95f037524f9",
|
||||
"name": "↑read result from file",
|
||||
"info": "",
|
||||
"x": 550,
|
||||
"y": 260,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "672d3693.3cabd8",
|
||||
"type": "comment",
|
||||
"z": "5132b95f037524f9",
|
||||
"name": "↓write to /tmp/hello.txt",
|
||||
"info": "",
|
||||
"x": 360,
|
||||
"y": 180,
|
||||
"wires": []
|
||||
}
|
||||
]
|
@ -1,118 +0,0 @@
|
||||
[
|
||||
{
|
||||
"id": "704479e1.399388",
|
||||
"type": "inject",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "",
|
||||
"props": [
|
||||
{
|
||||
"p": "payload"
|
||||
},
|
||||
{
|
||||
"p": "filename",
|
||||
"v": "/tmp/hello.txt",
|
||||
"vt": "str"
|
||||
},
|
||||
{
|
||||
"p": "topic",
|
||||
"vt": "str"
|
||||
}
|
||||
],
|
||||
"repeat": "",
|
||||
"crontab": "",
|
||||
"once": false,
|
||||
"onceDelay": 0.1,
|
||||
"topic": "",
|
||||
"payload": "Hello, World!",
|
||||
"payloadType": "str",
|
||||
"x": 190,
|
||||
"y": 260,
|
||||
"wires": [
|
||||
[
|
||||
"402f3b7e.988014"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "8e876a75.e9beb8",
|
||||
"type": "comment",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "Write string to a file specied by filename property, the read from the file",
|
||||
"info": "Write file node can target file using `filename` property.",
|
||||
"x": 310,
|
||||
"y": 180,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "402f3b7e.988014",
|
||||
"type": "file",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "",
|
||||
"filename": "",
|
||||
"appendNewline": true,
|
||||
"createDir": false,
|
||||
"overwriteFile": "true",
|
||||
"encoding": "none",
|
||||
"x": 350,
|
||||
"y": 260,
|
||||
"wires": [
|
||||
[
|
||||
"26e077d6.bbcd98"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "97b6b6b2.a54b38",
|
||||
"type": "debug",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "",
|
||||
"active": true,
|
||||
"tosidebar": true,
|
||||
"console": false,
|
||||
"tostatus": false,
|
||||
"complete": "false",
|
||||
"statusVal": "",
|
||||
"statusType": "auto",
|
||||
"x": 730,
|
||||
"y": 260,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "26e077d6.bbcd98",
|
||||
"type": "file in",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "",
|
||||
"filename": "/tmp/hello.txt",
|
||||
"format": "utf8",
|
||||
"chunk": false,
|
||||
"sendError": false,
|
||||
"encoding": "none",
|
||||
"x": 540,
|
||||
"y": 260,
|
||||
"wires": [
|
||||
[
|
||||
"97b6b6b2.a54b38"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "85062297.da79",
|
||||
"type": "comment",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "↑read result from file",
|
||||
"info": "",
|
||||
"x": 550,
|
||||
"y": 300,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "7316c4fc.b1dcdc",
|
||||
"type": "comment",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "↓write to file specified by filename property",
|
||||
"info": "",
|
||||
"x": 460,
|
||||
"y": 220,
|
||||
"wires": []
|
||||
}
|
||||
]
|
@ -1,85 +0,0 @@
|
||||
[
|
||||
{
|
||||
"id": "4ac00fb0.d5f52",
|
||||
"type": "inject",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "",
|
||||
"props": [
|
||||
{
|
||||
"p": "payload"
|
||||
},
|
||||
{
|
||||
"p": "topic",
|
||||
"vt": "str"
|
||||
}
|
||||
],
|
||||
"repeat": "",
|
||||
"crontab": "",
|
||||
"once": false,
|
||||
"onceDelay": 0.1,
|
||||
"topic": "",
|
||||
"payload": "",
|
||||
"payloadType": "date",
|
||||
"x": 180,
|
||||
"y": 220,
|
||||
"wires": [
|
||||
[
|
||||
"542cc2f4.92857c"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "671f8295.0e6f6c",
|
||||
"type": "comment",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "Delete a file",
|
||||
"info": "Write file node can delete a file.",
|
||||
"x": 130,
|
||||
"y": 160,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "542cc2f4.92857c",
|
||||
"type": "file",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "",
|
||||
"filename": "/tmp/hello.txt",
|
||||
"appendNewline": true,
|
||||
"createDir": false,
|
||||
"overwriteFile": "delete",
|
||||
"encoding": "none",
|
||||
"x": 380,
|
||||
"y": 220,
|
||||
"wires": [
|
||||
[
|
||||
"a24da523.5babe8"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "a24da523.5babe8",
|
||||
"type": "debug",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "",
|
||||
"active": true,
|
||||
"tosidebar": true,
|
||||
"console": false,
|
||||
"tostatus": false,
|
||||
"complete": "false",
|
||||
"statusVal": "",
|
||||
"statusType": "auto",
|
||||
"x": 590,
|
||||
"y": 220,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "51157051.2f62",
|
||||
"type": "comment",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "↓delete a file",
|
||||
"info": "",
|
||||
"x": 350,
|
||||
"y": 180,
|
||||
"wires": []
|
||||
}
|
||||
]
|
@ -1,113 +0,0 @@
|
||||
[
|
||||
{
|
||||
"id": "e4ef1f5e.7cd82",
|
||||
"type": "inject",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "Base64 encoded string",
|
||||
"props": [
|
||||
{
|
||||
"p": "payload"
|
||||
},
|
||||
{
|
||||
"p": "topic",
|
||||
"vt": "str"
|
||||
}
|
||||
],
|
||||
"repeat": "",
|
||||
"crontab": "",
|
||||
"once": false,
|
||||
"onceDelay": 0.1,
|
||||
"topic": "",
|
||||
"payload": "8J+YgA==",
|
||||
"payloadType": "str",
|
||||
"x": 200,
|
||||
"y": 220,
|
||||
"wires": [
|
||||
[
|
||||
"72b37cc8.177054"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "f5997af4.5a9298",
|
||||
"type": "comment",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "Specify encoding of written data",
|
||||
"info": "Write file node can specify encoding of data.",
|
||||
"x": 170,
|
||||
"y": 140,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "72b37cc8.177054",
|
||||
"type": "file",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "",
|
||||
"filename": "/tmp/hello.txt",
|
||||
"appendNewline": true,
|
||||
"createDir": false,
|
||||
"overwriteFile": "true",
|
||||
"encoding": "base64",
|
||||
"x": 420,
|
||||
"y": 220,
|
||||
"wires": [
|
||||
[
|
||||
"2da33ec.f45cac2"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "2e814354.278c8c",
|
||||
"type": "debug",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "",
|
||||
"active": true,
|
||||
"tosidebar": true,
|
||||
"console": false,
|
||||
"tostatus": false,
|
||||
"complete": "false",
|
||||
"statusVal": "",
|
||||
"statusType": "auto",
|
||||
"x": 810,
|
||||
"y": 220,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "2da33ec.f45cac2",
|
||||
"type": "file in",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "",
|
||||
"filename": "/tmp/hello.txt",
|
||||
"format": "utf8",
|
||||
"chunk": false,
|
||||
"sendError": false,
|
||||
"encoding": "none",
|
||||
"x": 620,
|
||||
"y": 220,
|
||||
"wires": [
|
||||
[
|
||||
"2e814354.278c8c"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "ec754c99.84bfd",
|
||||
"type": "comment",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "↓write string with base64 encoding",
|
||||
"info": "",
|
||||
"x": 480,
|
||||
"y": 180,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "3e6704ff.4ce25c",
|
||||
"type": "comment",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "↑read result from file",
|
||||
"info": "",
|
||||
"x": 630,
|
||||
"y": 260,
|
||||
"wires": []
|
||||
}
|
||||
]
|
@ -582,7 +582,7 @@ class Flow {
|
||||
reportingNode = node;
|
||||
}
|
||||
if (!muteStatusEvent) {
|
||||
if (statusMessage.hasOwnProperty("text") && typeof(statusMessage.text !== "string")) {
|
||||
if (statusMessage.hasOwnProperty("text") && typeof statusMessage.text !== "string") {
|
||||
try {
|
||||
statusMessage.text = statusMessage.text.toString();
|
||||
}
|
||||
|
@ -1,539 +0,0 @@
|
||||
|
||||
var should = require("should");
|
||||
var helper = require("node-red-node-test-helper");
|
||||
|
||||
var testNode = require("nr-test-utils").require("@node-red/nodes/core/function/rbe.js");
|
||||
|
||||
describe('rbe node', function() {
|
||||
"use strict";
|
||||
|
||||
beforeEach(function(done) {
|
||||
helper.startServer(done);
|
||||
});
|
||||
|
||||
afterEach(function(done) {
|
||||
helper.unload().then(function() {
|
||||
helper.stopServer(done);
|
||||
});
|
||||
});
|
||||
|
||||
it("should be loaded with correct defaults", function(done) {
|
||||
var flow = [{"id":"n1", "type":"rbe", "name":"rbe1", "wires":[[]]}];
|
||||
helper.load(testNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
n1.should.have.property("name", "rbe1");
|
||||
n1.should.have.property("func", "rbe");
|
||||
n1.should.have.property("gap", "0");
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should only send output if payload changes - with multiple topics (rbe)', function(done) {
|
||||
var flow = [{"id":"n1", "type":"rbe", func:"rbe", gap:"0", wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"} ];
|
||||
helper.load(testNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
var c = 0;
|
||||
n2.on("input", function(msg) {
|
||||
if (c === 0) {
|
||||
msg.should.have.a.property("payload", "a");
|
||||
c+=1;
|
||||
}
|
||||
else if (c === 1) {
|
||||
msg.should.have.a.property("payload", 2);
|
||||
c+=1;
|
||||
}
|
||||
else if (c == 2) {
|
||||
msg.should.have.a.property("payload");
|
||||
msg.payload.should.have.a.property("b",1);
|
||||
msg.payload.should.have.a.property("c",2);
|
||||
c+=1;
|
||||
}
|
||||
else if (c == 3) {
|
||||
msg.should.have.a.property("payload",true);
|
||||
c+=1;
|
||||
}
|
||||
else if (c == 4) {
|
||||
msg.should.have.a.property("payload",false);
|
||||
c+=1;
|
||||
}
|
||||
else if (c == 5) {
|
||||
msg.should.have.a.property("payload",true);
|
||||
c+=1;
|
||||
}
|
||||
else if (c == 6) {
|
||||
msg.should.have.a.property("topic","a");
|
||||
msg.should.have.a.property("payload",1);
|
||||
c+=1;
|
||||
}
|
||||
else if (c == 7) {
|
||||
msg.should.have.a.property("topic","b");
|
||||
msg.should.have.a.property("payload",1);
|
||||
c+=1;
|
||||
}
|
||||
else {
|
||||
c += 1;
|
||||
msg.should.have.a.property("topic","c");
|
||||
msg.should.have.a.property("payload",1);
|
||||
done();
|
||||
}
|
||||
});
|
||||
n1.emit("input", {payload:"a"});
|
||||
n1.emit("input", {payload:"a"});
|
||||
n1.emit("input", {payload:"a"});
|
||||
n1.emit("input", {payload:2});
|
||||
n1.emit("input", {payload:2});
|
||||
n1.emit("input", {payload:{b:1,c:2}});
|
||||
n1.emit("input", {payload:{c:2,b:1}});
|
||||
n1.emit("input", {payload:{c:2,b:1}});
|
||||
n1.emit("input", {payload:true});
|
||||
n1.emit("input", {payload:false});
|
||||
n1.emit("input", {payload:false});
|
||||
n1.emit("input", {payload:true});
|
||||
|
||||
n1.emit("input", {topic:"a",payload:1});
|
||||
n1.emit("input", {topic:"b",payload:1});
|
||||
n1.emit("input", {topic:"b",payload:1});
|
||||
n1.emit("input", {topic:"a",payload:1});
|
||||
n1.emit("input", {topic:"c",payload:1});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
it('should ignore multiple topics if told to (rbe)', function(done) {
|
||||
var flow = [{id:"n1", type:"rbe", func:"rbe", gap:"0", septopics:false, wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"} ];
|
||||
helper.load(testNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
var c = 0;
|
||||
n2.on("input", function(msg) {
|
||||
if (c === 0) {
|
||||
msg.should.have.a.property("payload", "a");
|
||||
c+=1;
|
||||
}
|
||||
else if (c === 1) {
|
||||
msg.should.have.a.property("payload", 2);
|
||||
c+=1;
|
||||
}
|
||||
else if (c == 2) {
|
||||
msg.should.have.a.property("payload");
|
||||
msg.payload.should.have.a.property("b",1);
|
||||
msg.payload.should.have.a.property("c",2);
|
||||
c+=1;
|
||||
}
|
||||
else if (c == 3) {
|
||||
msg.should.have.a.property("payload",true);
|
||||
c+=1;
|
||||
}
|
||||
else if (c == 4) {
|
||||
msg.should.have.a.property("payload",false);
|
||||
c+=1;
|
||||
}
|
||||
else if (c == 5) {
|
||||
msg.should.have.a.property("payload",true);
|
||||
c+=1;
|
||||
}
|
||||
else if (c == 6) {
|
||||
msg.should.have.a.property("topic","a");
|
||||
msg.should.have.a.property("payload",1);
|
||||
c+=1;
|
||||
}
|
||||
else {
|
||||
msg.should.have.a.property("topic","a");
|
||||
msg.should.have.a.property("payload",2);
|
||||
done();
|
||||
}
|
||||
});
|
||||
n1.emit("input", {topic:"a",payload:"a"});
|
||||
n1.emit("input", {topic:"b",payload:"a"});
|
||||
n1.emit("input", {topic:"c",payload:"a"});
|
||||
n1.emit("input", {topic:"a",payload:2});
|
||||
n1.emit("input", {topic:"b",payload:2});
|
||||
n1.emit("input", {payload:{b:1,c:2}});
|
||||
n1.emit("input", {payload:{c:2,b:1}});
|
||||
n1.emit("input", {payload:{c:2,b:1}});
|
||||
n1.emit("input", {topic:"a",payload:true});
|
||||
n1.emit("input", {topic:"b",payload:false});
|
||||
n1.emit("input", {topic:"c",payload:false});
|
||||
n1.emit("input", {topic:"d",payload:true});
|
||||
|
||||
n1.emit("input", {topic:"a",payload:1});
|
||||
n1.emit("input", {topic:"b",payload:1});
|
||||
n1.emit("input", {topic:"c",payload:1});
|
||||
n1.emit("input", {topic:"d",payload:1});
|
||||
n1.emit("input", {topic:"a",payload:2});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
it('should only send output if another chosen property changes - foo (rbe)', function(done) {
|
||||
var flow = [{"id":"n1", "type":"rbe", func:"rbe", gap:"0", property:"foo", wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"} ];
|
||||
helper.load(testNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
var c = 0;
|
||||
n2.on("input", function(msg) {
|
||||
if (c === 0) {
|
||||
msg.should.have.a.property("foo", "a");
|
||||
c+=1;
|
||||
}
|
||||
else if (c === 1) {
|
||||
msg.should.have.a.property("foo", "b");
|
||||
c+=1;
|
||||
}
|
||||
else {
|
||||
msg.should.have.a.property("foo");
|
||||
msg.foo.should.have.a.property("b",1);
|
||||
msg.foo.should.have.a.property("c",2);
|
||||
done();
|
||||
}
|
||||
});
|
||||
n1.emit("input", {foo:"a"});
|
||||
n1.emit("input", {payload:"a"});
|
||||
n1.emit("input", {foo:"a"});
|
||||
n1.emit("input", {payload:"a"});
|
||||
n1.emit("input", {foo:"a"});
|
||||
n1.emit("input", {foo:"b"});
|
||||
n1.emit("input", {foo:{b:1,c:2}});
|
||||
n1.emit("input", {foo:{c:2,b:1}});
|
||||
n1.emit("input", {payload:{c:2,b:1}});
|
||||
});
|
||||
});
|
||||
|
||||
it('should only send output if payload changes - ignoring first value (rbei)', function(done) {
|
||||
var flow = [{"id":"n1", "type":"rbe", func:"rbei", gap:"0", wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"} ];
|
||||
helper.load(testNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
var c = 0;
|
||||
n2.on("input", function(msg) {
|
||||
if (c === 0) {
|
||||
msg.should.have.a.property("payload", "b");
|
||||
msg.should.have.a.property("topic", "a");
|
||||
c+=1;
|
||||
}
|
||||
else if (c === 1) {
|
||||
msg.should.have.a.property("payload", "b");
|
||||
msg.should.have.a.property("topic", "b");
|
||||
c+=1;
|
||||
}
|
||||
else if (c === 2) {
|
||||
msg.should.have.a.property("payload", "c");
|
||||
msg.should.have.a.property("topic", "a");
|
||||
c+=1;
|
||||
}
|
||||
else {
|
||||
msg.should.have.a.property("payload", "c");
|
||||
msg.should.have.a.property("topic", "b");
|
||||
done();
|
||||
}
|
||||
|
||||
});
|
||||
n1.emit("input", {payload:"a", topic:"a"});
|
||||
n1.emit("input", {payload:"a", topic:"b"});
|
||||
n1.emit("input", {payload:"a", topic:"a"});
|
||||
n1.emit("input", {payload:"b", topic:"a"});
|
||||
n1.emit("input", {payload:"b", topic:"b"});
|
||||
n1.emit("input", {payload:"c", topic:"a"});
|
||||
n1.emit("input", {payload:"c", topic:"b"});
|
||||
});
|
||||
});
|
||||
|
||||
it('should send output if queue is reset (rbe)', function(done) {
|
||||
var flow = [{"id":"n1", "type":"rbe", func:"rbe", gap:"0", wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"} ];
|
||||
helper.load(testNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
var c = 0;
|
||||
n2.on("input", function(msg) {
|
||||
if (c === 0) {
|
||||
msg.should.have.a.property("payload", "a");
|
||||
c+=1;
|
||||
}
|
||||
else if (c === 1) {
|
||||
msg.should.have.a.property("payload", "b");
|
||||
c+=1;
|
||||
}
|
||||
else if (c === 2) {
|
||||
msg.should.have.a.property("payload", "a");
|
||||
c+=1;
|
||||
}
|
||||
else if (c === 3) {
|
||||
msg.should.have.a.property("payload", "b");
|
||||
c+=1;
|
||||
}
|
||||
else if (c === 4) {
|
||||
msg.should.have.a.property("payload", "b");
|
||||
c+=1;
|
||||
}
|
||||
else if (c === 5) {
|
||||
msg.should.have.a.property("payload", "b");
|
||||
c+=1;
|
||||
}
|
||||
else if (c === 6) {
|
||||
msg.should.have.a.property("payload", "a");
|
||||
c+=1;
|
||||
}
|
||||
else {
|
||||
msg.should.have.a.property("payload", "c");
|
||||
done();
|
||||
}
|
||||
});
|
||||
n1.emit("input", {topic:"a", payload:"a"});
|
||||
n1.emit("input", {topic:"a", payload:"a"});
|
||||
n1.emit("input", {topic:"b", payload:"b"});
|
||||
n1.emit("input", {reset:true}); // reset all
|
||||
n1.emit("input", {topic:"a", payload:"a"});
|
||||
n1.emit("input", {topic:"b", payload:"b"});
|
||||
n1.emit("input", {topic:"b", payload:"b"});
|
||||
n1.emit("input", {topic:"b", reset:""}); // reset b
|
||||
n1.emit("input", {topic:"b", payload:"b"});
|
||||
n1.emit("input", {topic:"a", payload:"a"});
|
||||
n1.emit("input", {reset:""}); // reset all
|
||||
n1.emit("input", {topic:"b", payload:"b"});
|
||||
n1.emit("input", {topic:"a", payload:"a"});
|
||||
n1.emit("input", {topic:"c"}); // don't reset a non topic
|
||||
n1.emit("input", {topic:"b", payload:"b"});
|
||||
n1.emit("input", {topic:"a", payload:"a"});
|
||||
n1.emit("input", {topic:"c", payload:"c"});
|
||||
});
|
||||
});
|
||||
|
||||
it('should only send output if x away from original value (deadbandEq)', function(done) {
|
||||
var flow = [{"id":"n1", "type":"rbe", func:"deadbandEq", gap:"10", inout:"out", wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"} ];
|
||||
helper.load(testNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
var c = 0;
|
||||
n2.on("input", function(msg) {
|
||||
c = c + 1;
|
||||
if (c === 1) {
|
||||
msg.should.have.a.property("payload", 0);
|
||||
}
|
||||
else if (c === 2) {
|
||||
msg.should.have.a.property("payload", 10);
|
||||
}
|
||||
else if (c == 3) {
|
||||
msg.should.have.a.property("payload", 20);
|
||||
done();
|
||||
}
|
||||
});
|
||||
n1.emit("input", {payload:0});
|
||||
n1.emit("input", {payload:2});
|
||||
n1.emit("input", {payload:4});
|
||||
n1.emit("input", {payload:6});
|
||||
n1.emit("input", {payload:8});
|
||||
n1.emit("input", {payload:10});
|
||||
n1.emit("input", {payload:15});
|
||||
n1.emit("input", {payload:20});
|
||||
});
|
||||
});
|
||||
|
||||
it('should only send output if more than x away from original value (deadband)', function(done) {
|
||||
var flow = [{"id":"n1", "type":"rbe", func:"deadband", gap:"10", wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"} ];
|
||||
helper.load(testNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
var c = 0;
|
||||
n2.on("input", function(msg) {
|
||||
c = c + 1;
|
||||
//console.log(c,msg);
|
||||
if (c === 1) {
|
||||
msg.should.have.a.property("payload", 0);
|
||||
}
|
||||
else if (c === 2) {
|
||||
msg.should.have.a.property("payload", 20);
|
||||
}
|
||||
else {
|
||||
msg.should.have.a.property("payload", "5 deg");
|
||||
done();
|
||||
}
|
||||
});
|
||||
n1.emit("input", {payload:0});
|
||||
n1.emit("input", {payload:2});
|
||||
n1.emit("input", {payload:4});
|
||||
n1.emit("input", {payload:"6 deg"});
|
||||
n1.emit("input", {payload:8});
|
||||
n1.emit("input", {payload:20});
|
||||
n1.emit("input", {payload:15});
|
||||
n1.emit("input", {payload:"5 deg"});
|
||||
});
|
||||
});
|
||||
|
||||
it('should only send output if more than x% away from original value (deadband)', function(done) {
|
||||
var flow = [{"id":"n1", "type":"rbe", func:"deadband", gap:"10%", wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"} ];
|
||||
helper.load(testNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
var c = 0;
|
||||
n2.on("input", function(msg) {
|
||||
c = c + 1;
|
||||
if (c === 1) {
|
||||
msg.should.have.a.property("payload", 100);
|
||||
}
|
||||
else if (c === 2) {
|
||||
msg.should.have.a.property("payload", 111);
|
||||
}
|
||||
else if (c === 3) {
|
||||
msg.should.have.a.property("payload", 135);
|
||||
done();
|
||||
}
|
||||
});
|
||||
n1.emit("input", {payload:100});
|
||||
n1.emit("input", {payload:95});
|
||||
n1.emit("input", {payload:105});
|
||||
n1.emit("input", {payload:111});
|
||||
n1.emit("input", {payload:120});
|
||||
n1.emit("input", {payload:135});
|
||||
});
|
||||
});
|
||||
|
||||
it('should warn if no number found in deadband mode', function(done) {
|
||||
var flow = [{"id":"n1", "type":"rbe", func:"deadband", gap:"10", wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"} ];
|
||||
helper.load(testNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
var c = 0;
|
||||
n2.on("input", function(msg) {
|
||||
c += 1;
|
||||
});
|
||||
setTimeout( function() {
|
||||
c.should.equal(0);
|
||||
helper.log().called.should.be.true;
|
||||
var logEvents = helper.log().args.filter(function (evt) {
|
||||
return evt[0].type == "rbe";
|
||||
});
|
||||
logEvents.should.have.length(1);
|
||||
var msg = logEvents[0][0];
|
||||
msg.should.have.property('level', helper.log().WARN);
|
||||
msg.should.have.property('id', 'n1');
|
||||
msg.should.have.property('type', 'rbe');
|
||||
msg.should.have.property('msg', 'rbe.warn.nonumber');
|
||||
done();
|
||||
},50);
|
||||
n1.emit("input", {payload:"banana"});
|
||||
});
|
||||
});
|
||||
|
||||
it('should not send output if x away or greater from original value (narrowbandEq)', function(done) {
|
||||
var flow = [{"id":"n1", "type":"rbe", func:"narrowbandEq", gap:"10", inout:"out", start:"1", wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"} ];
|
||||
helper.load(testNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
var c = 0;
|
||||
n2.on("input", function(msg) {
|
||||
c = c + 1;
|
||||
//console.log(c,msg);
|
||||
if (c === 1) {
|
||||
msg.should.have.a.property("payload", 0);
|
||||
}
|
||||
else if (c === 2) {
|
||||
msg.should.have.a.property("payload", 5);
|
||||
}
|
||||
else if (c === 3) {
|
||||
msg.should.have.a.property("payload", 10);
|
||||
done();
|
||||
}
|
||||
});
|
||||
n1.emit("input", {payload:100});
|
||||
n1.emit("input", {payload:0});
|
||||
n1.emit("input", {payload:10});
|
||||
n1.emit("input", {payload:5});
|
||||
n1.emit("input", {payload:15});
|
||||
n1.emit("input", {payload:10});
|
||||
n1.emit("input", {payload:20});
|
||||
n1.emit("input", {payload:25});
|
||||
});
|
||||
});
|
||||
|
||||
it('should not send output if more than x away from original value (narrowband)', function(done) {
|
||||
var flow = [{"id":"n1", "type":"rbe", func:"narrowband", gap:"10", wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"} ];
|
||||
helper.load(testNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
var c = 0;
|
||||
n2.on("input", function(msg) {
|
||||
if (c === 0) {
|
||||
msg.should.have.a.property("payload", 0);
|
||||
}
|
||||
else if (c === 1) {
|
||||
msg.should.have.a.property("payload","6 deg");
|
||||
}
|
||||
else {
|
||||
msg.should.have.a.property("payload", "5 deg");
|
||||
done();
|
||||
}
|
||||
c += 1;
|
||||
});
|
||||
n1.emit("input", {payload:0});
|
||||
n1.emit("input", {payload:20});
|
||||
n1.emit("input", {payload:40});
|
||||
n1.emit("input", {payload:"6 deg"});
|
||||
n1.emit("input", {payload:18});
|
||||
n1.emit("input", {payload:20});
|
||||
n1.emit("input", {payload:50});
|
||||
n1.emit("input", {payload:"5 deg"});
|
||||
});
|
||||
});
|
||||
|
||||
it('should send output if gap is 0 and input doesnt change (narrowband)', function(done) {
|
||||
var flow = [{"id":"n1", "type":"rbe", func:"narrowband", gap:"0", wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"} ];
|
||||
helper.load(testNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
var c = 0;
|
||||
n2.on("input", function(msg) {
|
||||
if (c === 0) {
|
||||
msg.should.have.a.property("payload", 1);
|
||||
}
|
||||
else if (c === 4) {
|
||||
msg.should.have.a.property("payload",1);
|
||||
done();
|
||||
}
|
||||
c += 1;
|
||||
});
|
||||
n1.emit("input", {payload:1});
|
||||
n1.emit("input", {payload:1});
|
||||
n1.emit("input", {payload:1});
|
||||
n1.emit("input", {payload:1});
|
||||
n1.emit("input", {payload:0});
|
||||
n1.emit("input", {payload:1});
|
||||
});
|
||||
});
|
||||
|
||||
it('should not send output if more than x away from original value (narrowband in step mode)', function(done) {
|
||||
var flow = [{"id":"n1", "type":"rbe", func:"narrowband", gap:"10", inout:"in", start:"500", wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"} ];
|
||||
helper.load(testNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
var c = 0;
|
||||
n2.on("input", function(msg) {
|
||||
if (c === 0) {
|
||||
msg.should.have.a.property("payload", 55);
|
||||
}
|
||||
else if (c === 1) {
|
||||
msg.should.have.a.property("payload", 205);
|
||||
done();
|
||||
}
|
||||
c += 1;
|
||||
});
|
||||
n1.emit("input", {payload:50});
|
||||
n1.emit("input", {payload:55});
|
||||
n1.emit("input", {payload:200});
|
||||
n1.emit("input", {payload:205});
|
||||
});
|
||||
});
|
||||
});
|
610
test/nodes/core/network/21-mqtt_spec.js
Normal file
610
test/nodes/core/network/21-mqtt_spec.js
Normal file
@ -0,0 +1,610 @@
|
||||
|
||||
/* These tests are only supposed to be executed at development time (for now)*/
|
||||
|
||||
"use strict";
|
||||
const should = require("should");
|
||||
const helper = require("node-red-node-test-helper");
|
||||
const mqttNodes = require("nr-test-utils").require("@node-red/nodes/core/network/10-mqtt.js");
|
||||
const BROKER_HOST = process.env.MQTT_BROKER_SERVER || "localhost";
|
||||
const BROKER_PORT = process.env.MQTT_BROKER_PORT || 1883;
|
||||
//By default, MQTT tests are disabled. Set ENV VAR NR_MQTT_TESTS to "1" or "true" to enable
|
||||
const skipTests = process.env.NR_MQTT_TESTS != "true" && process.env.NR_MQTT_TESTS != "1";
|
||||
|
||||
describe('MQTT Nodes', function () {
|
||||
|
||||
before(function (done) {
|
||||
helper.startServer(done);
|
||||
});
|
||||
|
||||
after(function (done) {
|
||||
helper.stopServer(done);
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
try {
|
||||
helper.unload();
|
||||
} catch (error) { }
|
||||
});
|
||||
|
||||
it('should be loaded and have default values', function (done) {
|
||||
this.timeout = 2000;
|
||||
const { flow, nodes } = buildBasicMQTTSendRecvFlow({ id: "mqtt.broker", name: "mqtt_broker", autoConnect: false }, { id: "mqtt.in", topic: "in_topic" }, { id: "mqtt.out", topic: "out_topic" });
|
||||
helper.load(mqttNodes, flow, function () {
|
||||
try {
|
||||
const mqttIn = helper.getNode("mqtt.in");
|
||||
const mqttOut = helper.getNode("mqtt.out");
|
||||
const mqttBroker = helper.getNode("mqtt.broker");
|
||||
|
||||
should(mqttIn).be.type("object", "mqtt in node should be an object")
|
||||
mqttIn.should.have.property('broker', nodes.mqtt_broker.id); //should be the id of the broker node
|
||||
mqttIn.should.have.property('datatype', 'utf8'); //default: 'utf8'
|
||||
mqttIn.should.have.property('isDynamic', false); //default: false
|
||||
mqttIn.should.have.property('inputs', 0); //default: 0
|
||||
mqttIn.should.have.property('qos', 2); //default: 2
|
||||
mqttIn.should.have.property('topic', "in_topic");
|
||||
mqttIn.should.have.property('wires', [["helper.node"]]);
|
||||
|
||||
should(mqttOut).be.type("object", "mqtt out node should be an object")
|
||||
mqttOut.should.have.property('broker', nodes.mqtt_broker.id); //should be the id of the broker node
|
||||
mqttOut.should.have.property('topic', "out_topic");
|
||||
|
||||
should(mqttBroker).be.type("object", "mqtt broker node should be an object")
|
||||
mqttBroker.should.have.property('broker', BROKER_HOST);
|
||||
mqttBroker.should.have.property('port', BROKER_PORT);
|
||||
mqttBroker.should.have.property('brokerurl');
|
||||
// mqttBroker.should.have.property('autoUnsubscribe', true);//default: true
|
||||
mqttBroker.should.have.property('autoConnect', false);//Set "autoConnect:false" in brokerOptions
|
||||
mqttBroker.should.have.property('options');
|
||||
mqttBroker.options.should.have.property('clean', true);
|
||||
mqttBroker.options.should.have.property('clientId');
|
||||
mqttBroker.options.clientId.should.containEql('nodered_');
|
||||
mqttBroker.options.should.have.property('keepalive').type("number");
|
||||
mqttBroker.options.should.have.property('reconnectPeriod').type("number");
|
||||
done();
|
||||
} catch (error) {
|
||||
done(error)
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (skipTests) {
|
||||
it('skipping MQTT tests. Set env var "NR_MQTT_TESTS=true" to enable. Requires a v5 capable broker running on localhost:1883.', function (done) {
|
||||
done();
|
||||
});
|
||||
}
|
||||
|
||||
/** Conditional test runner (only run if skipTests=false) */
|
||||
function itConditional(title, test) {
|
||||
return !skipTests ? it(title, test) : it.skip(title, test);
|
||||
}
|
||||
|
||||
//#region ################### BASIC TESTS ################### #//
|
||||
|
||||
itConditional('basic send and receive tests', function (done) {
|
||||
if (skipTests) { return this.skip() }
|
||||
this.timeout = 2000;
|
||||
const options = {}
|
||||
options.sendMsg = {
|
||||
topic: nextTopic(),
|
||||
payload: "hello",
|
||||
qos: 0
|
||||
}
|
||||
options.expectMsg = Object.assign({}, options.sendMsg);
|
||||
testSendRecv({}, { datatype: "auto", topicType: "static" }, {}, options, { done: done });
|
||||
});
|
||||
itConditional('should send JSON and receive string (auto)', function (done) {
|
||||
if (skipTests) { return this.skip() }
|
||||
this.timeout = 2000;
|
||||
const options = {}
|
||||
options.sendMsg = {
|
||||
topic: nextTopic(),
|
||||
payload: '{"prop":"value1", "num":1}',
|
||||
qos: 1
|
||||
}
|
||||
options.expectMsg = Object.assign({}, options.sendMsg);
|
||||
testSendRecv({}, { datatype: "auto", topicType: "static" }, {}, options, { done: done });
|
||||
})
|
||||
itConditional('should send JSON and receive string (utf8)', function (done) {
|
||||
if (skipTests) { return this.skip() }
|
||||
this.timeout = 2000;
|
||||
const options = {}
|
||||
options.sendMsg = {
|
||||
topic: nextTopic(),
|
||||
payload: '{"prop":"value2", "num":2}',
|
||||
qos: 2
|
||||
}
|
||||
options.expectMsg = Object.assign({}, options.sendMsg);
|
||||
testSendRecv({}, { datatype: "utf8", topicType: "static" }, {}, options, { done: done });
|
||||
});
|
||||
itConditional('should send JSON and receive Object (json)', function (done) {
|
||||
if (skipTests) { return this.skip() }
|
||||
this.timeout = 2000;
|
||||
const options = {}
|
||||
options.sendMsg = {
|
||||
topic: nextTopic(),
|
||||
payload: '{"prop":"value3", "num":3}'// send a string ...
|
||||
}
|
||||
options.expectMsg = Object.assign({}, options.sendMsg, { payload: { "prop": "value3", "num": 3 } });//expect an object
|
||||
testSendRecv({}, { datatype: "json", topicType: "static" }, {}, options, { done: done });
|
||||
});
|
||||
itConditional('should send String and receive Buffer (buffer)', function (done) {
|
||||
if (skipTests) { return this.skip() }
|
||||
this.timeout = 2000;
|
||||
const options = {}
|
||||
options.sendMsg = {
|
||||
topic: nextTopic(),
|
||||
payload: "a b c" //send string ...
|
||||
}
|
||||
options.expectMsg = Object.assign({}, options.sendMsg, { payload: Buffer.from(options.sendMsg.payload) });//expect Buffer.from(msg.payload)
|
||||
testSendRecv({}, { datatype: "buffer", topicType: "static" }, {}, options, { done: done });
|
||||
});
|
||||
itConditional('should send utf8 Buffer and receive String (auto)', function (done) {
|
||||
if (skipTests) { return this.skip() }
|
||||
this.timeout = 2000;
|
||||
const options = {}
|
||||
options.sendMsg = {
|
||||
topic: nextTopic(),
|
||||
payload: Buffer.from([0x78, 0x20, 0x79, 0x20, 0x7a]) // "x y z"
|
||||
}
|
||||
options.expectMsg = Object.assign({}, options.sendMsg, { payload: "x y z" });//set expected payload to "x y z"
|
||||
testSendRecv({}, { datatype: "auto", topicType: "static" }, {}, options, { done: done });
|
||||
});
|
||||
itConditional('should send non utf8 Buffer and receive Buffer (auto)', function (done) {
|
||||
if (skipTests) { return this.skip() }
|
||||
this.timeout = 2000;
|
||||
const options = {}
|
||||
const hooks = { done: done, beforeLoad: null, afterLoad: null, afterConnect: null }
|
||||
options.sendMsg = {
|
||||
topic: nextTopic(),
|
||||
payload: Buffer.from([0xC0, 0xC1, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF]) //non valid UTF8
|
||||
}
|
||||
options.expectMsg = Object.assign({}, options.sendMsg);
|
||||
testSendRecv({}, { datatype: "auto", topicType: "static" }, {}, options, hooks);
|
||||
});
|
||||
itConditional('should send/receive all v5 flags and settings', function (done) {
|
||||
if (skipTests) { return this.skip() }
|
||||
this.timeout = 2000;
|
||||
const t = nextTopic();
|
||||
const options = {}
|
||||
const hooks = { done: done, beforeLoad: null, afterLoad: null, afterConnect: null }
|
||||
options.sendMsg = {
|
||||
topic: t + "/command", payload: Buffer.from("v5"), qos: 1, retain: true,
|
||||
responseTopic: t + "/response",
|
||||
userProperties: { prop1: "val1" },
|
||||
contentType: "application/json",
|
||||
correlationData: Buffer.from([1, 2, 3]),
|
||||
payloadFormatIndicator: true,
|
||||
messageExpiryInterval: 2000,
|
||||
}
|
||||
options.expectMsg = Object.assign({}, options.sendMsg);
|
||||
options.expectMsg.payload = options.expectMsg.payload.toString(); //auto mode + payloadFormatIndicator should make a string
|
||||
delete options.expectMsg.payloadFormatIndicator; //Seems mqtt.js only publishes payloadFormatIndicator the will msg
|
||||
const inOptions = {
|
||||
datatype: "auto", topicType: "static",
|
||||
qos: 1, nl: false, rap: true, rh: 1
|
||||
}
|
||||
testSendRecv({ protocolVersion: 5 }, inOptions, {}, options, hooks);
|
||||
});
|
||||
itConditional('should subscribe dynamically via action', function (done) {
|
||||
if (skipTests) { return this.skip() }
|
||||
this.timeout = 2000;
|
||||
const options = {}
|
||||
const hooks = { done: done, beforeLoad: null, afterLoad: null, afterConnect: null }
|
||||
options.sendMsg = {
|
||||
topic: nextTopic(), payload: "abc"
|
||||
}
|
||||
options.expectMsg = Object.assign({}, options.sendMsg);
|
||||
testSendRecv({ protocolVersion: 5 }, { datatype: "utf8", topicType: "dynamic" }, {}, options, hooks);
|
||||
});
|
||||
//#endregion BASIC TESTS
|
||||
|
||||
//#region ################### ADVANCED TESTS ################### #//
|
||||
itConditional('should connect via "connect" action', function (done) {
|
||||
if (skipTests) { return this.skip() }
|
||||
this.timeout = 2000;
|
||||
const options = {}
|
||||
const hooks = { done: null, beforeLoad: null, afterLoad: null, afterConnect: null }
|
||||
hooks.afterLoad = (helperNode, mqttBroker, mqttIn, mqttOut) => {
|
||||
mqttBroker.should.have.property("autoConnect", false);
|
||||
mqttBroker.should.have.property("connecting", false);//should not attempt to connect (autoConnect:false)
|
||||
mqttIn.receive({ "action": "connect" }); //now request connect action
|
||||
return true; //handled
|
||||
}
|
||||
hooks.afterConnect = (helperNode, mqttBroker, mqttIn, mqttOut) => {
|
||||
done();//if we got here, it connected :)
|
||||
return true;
|
||||
}
|
||||
testSendRecv({ protocolVersion: 5, autoConnect: false }, { datatype: "utf8", topicType: "dynamic" }, {}, options, hooks);
|
||||
});
|
||||
itConditional('should disconnect via "disconnect" action', function (done) {
|
||||
if (skipTests) { return this.skip() }
|
||||
this.timeout = 2000;
|
||||
const options = {}
|
||||
const hooks = { done: done, beforeLoad: null, afterLoad: null, afterConnect: null }
|
||||
hooks.beforeLoad = (flow) => { //add a status node pointed at MQTT Out node (to watch for connection status change)
|
||||
flow.push({ "id": "status.node", "type": "status", "name": "status_node", "scope": ["mqtt.out"], "wires": [["helper.node"]] });//add status node to watch mqtt_out
|
||||
}
|
||||
hooks.afterLoad = (helperNode, mqttBroker, mqttIn, mqttOut) => {
|
||||
mqttBroker.should.have.property("autoConnect", true);
|
||||
mqttBroker.should.have.property("connecting", true);//should be trying to connect (autoConnect:true)
|
||||
return true; //handled
|
||||
}
|
||||
hooks.afterConnect = (helperNode, mqttBroker, mqttIn, mqttOut) => {
|
||||
//connected - now add the "on" handler then send "disconnect" action
|
||||
helperNode.on("input", function (msg) {
|
||||
try {
|
||||
msg.should.have.property("status");
|
||||
msg.status.should.have.property("text");
|
||||
msg.status.text.should.containEql('disconnect');
|
||||
done(); //it disconnected - yey!
|
||||
} catch (error) {
|
||||
done(error)
|
||||
}
|
||||
})
|
||||
mqttOut.receive({ "action": "disconnect" });
|
||||
return true; //handed
|
||||
}
|
||||
testSendRecv({ protocolVersion: 5 }, null, {}, options, hooks);
|
||||
});
|
||||
itConditional('should publish birth message', function (done) {
|
||||
if (skipTests) { return this.skip() }
|
||||
this.timeout = 2000;
|
||||
const baseTopic = nextTopic();
|
||||
const brokerOptions = {
|
||||
protocolVersion: 4,
|
||||
birthTopic: baseTopic + "/birth",
|
||||
birthPayload: "broker connected",
|
||||
birthQos: 2,
|
||||
}
|
||||
const options = {};
|
||||
const hooks = { done: done, beforeLoad: null, afterLoad: null, afterConnect: null };
|
||||
options.expectMsg = {
|
||||
topic: brokerOptions.birthTopic,
|
||||
payload: brokerOptions.birthPayload,
|
||||
qos: brokerOptions.birthQos
|
||||
};
|
||||
testSendRecv(brokerOptions, { topic: brokerOptions.birthTopic }, {}, options, hooks);
|
||||
});
|
||||
itConditional('should publish close message', function (done) {
|
||||
if (skipTests) { return this.skip() }
|
||||
this.timeout = 2000;
|
||||
const baseTopic = nextTopic();
|
||||
const broker1Options = { id: "mqtt.broker1" }//Broker 1 - stays connected to receive the close message
|
||||
const broker2Options = { id: "mqtt.broker2", closeTopic: baseTopic + "/close", closePayload: '{"msg":"close"}', closeQos: 1, }//Broker 2 - connects to same broker but has a LWT message.
|
||||
const { flow } = buildBasicMQTTSendRecvFlow(broker1Options, { broker: broker1Options.id, topic: broker2Options.closeTopic, datatype: "json" }, { broker: broker2Options.id })
|
||||
flow.push(buildMQTTBrokerNode(broker2Options.id, broker2Options.name, BROKER_HOST, BROKER_PORT, broker2Options)); //add second broker
|
||||
helper.load(mqttNodes, flow, function () {
|
||||
const helperNode = helper.getNode("helper.node");
|
||||
const mqttOut = helper.getNode("mqtt.out");
|
||||
const mqttBroker1 = helper.getNode("mqtt.broker1");
|
||||
const mqttBroker2 = helper.getNode("mqtt.broker2");
|
||||
waitBrokerConnect([mqttBroker1, mqttBroker2], function connected() {
|
||||
//connected - add the on handler and call to disconnect
|
||||
helperNode.on("input", function (msg) {
|
||||
try {
|
||||
msg.should.have.property("topic", broker2Options.closeTopic);
|
||||
msg.should.have.property('payload', JSON.parse(broker2Options.closePayload));
|
||||
msg.should.have.property('qos', broker2Options.closeQos);
|
||||
done();
|
||||
} catch (error) {
|
||||
done(error)
|
||||
}
|
||||
})
|
||||
mqttOut.receive({ "action": "disconnect" });//close broker2
|
||||
})
|
||||
});
|
||||
});
|
||||
itConditional('should publish will message', function (done) {
|
||||
if (skipTests) { return this.skip() }
|
||||
this.timeout = 2000;
|
||||
const baseTopic = nextTopic();
|
||||
const broker1Options = { id: "mqtt.broker1" }//Broker 1 - stays connected to receive the will message
|
||||
const broker2Options = { id: "mqtt.broker2", willTopic: baseTopic + "/will", willPayload: '{"msg":"will"}', willQos: 2, }//Broker 2 - connects to same broker but has a LWT message.
|
||||
const { flow } = buildBasicMQTTSendRecvFlow(broker1Options, { broker: broker1Options.id, topic: broker2Options.willTopic, datatype: "utf8" }, { broker: broker2Options.id })
|
||||
flow.push(buildMQTTBrokerNode(broker2Options.id, broker2Options.name, BROKER_HOST, BROKER_PORT, broker2Options)); //add second broker
|
||||
|
||||
helper.load(mqttNodes, flow, function () {
|
||||
const helperNode = helper.getNode("helper.node");
|
||||
const mqttBroker1 = helper.getNode("mqtt.broker1");
|
||||
const mqttBroker2 = helper.getNode("mqtt.broker2");
|
||||
waitBrokerConnect([mqttBroker1, mqttBroker2], function connected() {
|
||||
//connected - add the on handler and call to disconnect
|
||||
helperNode.on("input", function (msg) {
|
||||
try {
|
||||
msg.should.have.property("topic", broker2Options.willTopic);
|
||||
msg.should.have.property('payload', broker2Options.willPayload);
|
||||
msg.should.have.property('qos', broker2Options.willQos);
|
||||
done();
|
||||
} catch (error) {
|
||||
done(error)
|
||||
}
|
||||
});
|
||||
mqttBroker2.client.end(true); //force closure
|
||||
})
|
||||
});
|
||||
});
|
||||
itConditional('should publish will message with V5 properties', function (done) {
|
||||
if (skipTests) { return this.skip() }
|
||||
// return this.skip(); //Issue receiving v5 props on will msg. Issue raised here: https://github.com/mqttjs/MQTT.js/issues/1455
|
||||
this.timeout = 2000;
|
||||
const baseTopic = nextTopic();
|
||||
//Broker 1 - stays connected to receive the will message when broker 2 is killed
|
||||
const broker1Options = { id: "mqtt.broker1", name: "mqtt_broker1", protocolVersion: 5, datatype: "utf8" }
|
||||
//Broker 2 - connects to same broker but has a LWT message. Broker 2 gets killed shortly after connection so that the will message is sent from broker
|
||||
const broker2Options = {
|
||||
id: "mqtt.broker2", name: "mqtt_broker2", protocolVersion: 5,
|
||||
willTopic: baseTopic + "/will",
|
||||
willPayload: '{"msg":"will"}',
|
||||
willQos: 2,
|
||||
willMsg: {
|
||||
contentType: 'application/json',
|
||||
userProps: { "will": "value" },
|
||||
respTopic: baseTopic + "/resp",
|
||||
correl: Buffer.from("abc"),
|
||||
expiry: 2000,
|
||||
payloadFormatIndicator: true
|
||||
}
|
||||
}
|
||||
const expectMsg = {
|
||||
topic: broker2Options.willTopic,
|
||||
payload: broker2Options.willPayload,
|
||||
qos: broker2Options.willQos,
|
||||
contentType: broker2Options.willMsg.contentType,
|
||||
userProperties: broker2Options.willMsg.userProps,
|
||||
responseTopic: broker2Options.willMsg.respTopic,
|
||||
correlationData: broker2Options.willMsg.correl,
|
||||
messageExpiryInterval: broker2Options.willMsg.expiry,
|
||||
// payloadFormatIndicator: broker2Options.willMsg.payloadFormatIndicator,
|
||||
};
|
||||
const { flow, nodes } = buildBasicMQTTSendRecvFlow(broker1Options, { broker: broker1Options.id, topic: broker2Options.willTopic, datatype: "utf8" }, { broker: broker2Options.id })
|
||||
flow.push(buildMQTTBrokerNode(broker2Options.id, broker2Options.name, nodes.mqtt_broker1.broker, nodes.mqtt_broker1.port, broker2Options)) //add second broker with will msg set
|
||||
helper.load(mqttNodes, flow, function () {
|
||||
const helperNode = helper.getNode("helper.node");
|
||||
const mqttBroker1 = helper.getNode("mqtt.broker1");
|
||||
const mqttBroker2 = helper.getNode("mqtt.broker2");
|
||||
waitBrokerConnect([mqttBroker1, mqttBroker2], function connected() {
|
||||
//connected - add the on handler and call to disconnect
|
||||
helperNode.on("input", function (msg) {
|
||||
try {
|
||||
compareMsgToExpected(msg, expectMsg);
|
||||
done();
|
||||
} catch (error) {
|
||||
done(error)
|
||||
}
|
||||
});
|
||||
mqttBroker2.client.end(true); //force closure
|
||||
})
|
||||
});
|
||||
});
|
||||
//#endregion ADVANCED TESTS
|
||||
});
|
||||
|
||||
//#region ################### HELPERS ################### #//
|
||||
|
||||
/**
|
||||
* A basic unit test that builds a flow containing 1 broker, 1 mqtt-in, one mqtt-out and a helper.
|
||||
* It performs the following steps: builds flow, loads flow, waits for connection, sends `sendMsg`,
|
||||
* waits for msg then compares `sendMsg` to `expectMsg`, and finally calls `done`
|
||||
* @param {object} brokerOptions anything that can be set in an MQTTBrokerNode (e.g. id, name, url, broker, server, port, protocolVersion, ...)
|
||||
* @param {object} inNodeOptions anything that can be set in an MQTTInNode (e.g. id, name, broker, topic, rh, nl, rap, ... )
|
||||
* @param {object} outNodeOptions anything that can be set in an MQTTOutNode (e.g. id, name, broker, ...)
|
||||
* @param {object} options an object for passing in test properties like `sendMsg` and `expectMsg`
|
||||
* @param {object} hooks an object containing hook functions...
|
||||
* * [fn] `done()` - the tests done function. If excluded, an error will be thrown upon test error
|
||||
* * [fn] `beforeLoad(flow)` - provides opportunity to adjust the flow JSON before loading into runtime
|
||||
* * [fn] `afterLoad(helperNode, mqttBroker, mqttIn, mqttOut)` - called before connection attempt
|
||||
* * [fn] `afterConnect(helperNode, mqttBroker, mqttIn, mqttOut)` - called before connection attempt
|
||||
*/
|
||||
function testSendRecv(brokerOptions, inNodeOptions, outNodeOptions, options, hooks) {
|
||||
options = options || {};
|
||||
brokerOptions = brokerOptions || {};
|
||||
inNodeOptions = inNodeOptions || {};
|
||||
outNodeOptions = outNodeOptions || {};
|
||||
const sendMsg = options.sendMsg || {};
|
||||
sendMsg.topic = sendMsg.topic || nextTopic();
|
||||
const expectMsg = options.expectMsg || Object.assign({}, sendMsg);
|
||||
expectMsg.payload = inNodeOptions.payload === undefined ? expectMsg.payload : inNodeOptions.payload;
|
||||
if (inNodeOptions.topicType != "dynamic") {
|
||||
inNodeOptions.topic = inNodeOptions.topic || sendMsg.topic;
|
||||
}
|
||||
|
||||
const { flow, nodes } = buildBasicMQTTSendRecvFlow(brokerOptions, inNodeOptions, outNodeOptions);
|
||||
if (hooks.beforeLoad) { hooks.beforeLoad(flow) }
|
||||
helper.load(mqttNodes, flow, function () {
|
||||
try {
|
||||
const helperNode = helper.getNode("helper.node");
|
||||
const mqttBroker = helper.getNode(brokerOptions.id);
|
||||
const mqttIn = helper.getNode(nodes.mqtt_in.id);
|
||||
const mqttOut = helper.getNode(nodes.mqtt_out.id);
|
||||
let afterLoadHandled = false;
|
||||
if (hooks.afterLoad) {
|
||||
afterLoadHandled = hooks.afterLoad(helperNode, mqttBroker, mqttIn, mqttOut)
|
||||
}
|
||||
if (!afterLoadHandled) {
|
||||
helperNode.on("input", function (msg) {
|
||||
try {
|
||||
compareMsgToExpected(msg, expectMsg);
|
||||
if (hooks.done) { hooks.done(); }
|
||||
} catch (err) {
|
||||
if (hooks.done) { hooks.done(err); }
|
||||
else { throw err; }
|
||||
}
|
||||
});
|
||||
}
|
||||
waitBrokerConnect(mqttBroker, function () {
|
||||
//finally, connected!
|
||||
if (hooks.afterConnect) {
|
||||
let handled = hooks.afterConnect(helperNode, mqttBroker, mqttIn, mqttOut);
|
||||
if (handled) { return }
|
||||
}
|
||||
if (mqttIn.isDynamic) {
|
||||
mqttIn.receive({ "action": "subscribe", "topic": sendMsg.topic })
|
||||
}
|
||||
mqttOut.receive(sendMsg);
|
||||
})
|
||||
} catch (err) {
|
||||
if (hooks.done) { hooks.done(err); }
|
||||
else { throw err; }
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a flow containing 2 parts.
|
||||
* * 1: MQTT Out node (with broker configured).
|
||||
* * 2: MQTT In node (with broker configured) --> helper node `id:helper.node`
|
||||
*/
|
||||
function buildBasicMQTTSendRecvFlow(brokerOptions, inOptions, outOptions) {
|
||||
brokerOptions = brokerOptions || {};
|
||||
brokerOptions.broker = brokerOptions.broker || BROKER_HOST;
|
||||
brokerOptions.port = brokerOptions.port || BROKER_PORT;
|
||||
brokerOptions.autoConnect = String(brokerOptions.autoConnect) == "false" ? false : true;
|
||||
const broker = buildMQTTBrokerNode(brokerOptions.id, brokerOptions.name, brokerOptions.broker, brokerOptions.port, brokerOptions);
|
||||
const inNode = buildMQTTInNode(inOptions.id, inOptions.name, inOptions.broker || broker.id, inOptions.topic, inOptions, ["helper.node"]);
|
||||
const outNode = buildMQTTOutNode(outOptions.id, outOptions.name, outOptions.broker || broker.id, outOptions.topic, outOptions);
|
||||
const helper = buildNode("helper", "helper.node", "helper_node", {});
|
||||
return {
|
||||
nodes: {
|
||||
[broker.name]: broker,
|
||||
[inNode.name]: inNode,
|
||||
[outNode.name]: outNode,
|
||||
[helper.name]: helper,
|
||||
},
|
||||
flow: [broker, inNode, outNode, helper]
|
||||
}
|
||||
}
|
||||
|
||||
function buildMQTTBrokerNode(id, name, brokerHost, brokerPort, options) {
|
||||
// url,broker,port,clientid,autoConnect,usetls,usews,verifyservercert,compatmode,protocolVersion,keepalive,
|
||||
//cleansession,sessionExpiry,topicAliasMaximum,maximumPacketSize,receiveMaximum,userProperties,userPropertiesType,autoUnsubscribe
|
||||
options = options || {};
|
||||
const node = buildNode("mqtt-broker", id || "mqtt.broker", name || "mqtt_broker", options);
|
||||
node.url = options.url;
|
||||
node.broker = brokerHost || options.broker || BROKER_HOST;
|
||||
node.port = brokerPort || options.port || BROKER_PORT;
|
||||
node.clientid = options.clientid || "";
|
||||
node.cleansession = String(options.cleansession) == "false" ? false : true;
|
||||
node.autoUnsubscribe = String(options.autoUnsubscribe) == "false" ? false : true;
|
||||
node.autoConnect = String(options.autoConnect) == "false" ? false : true;
|
||||
|
||||
if (options.birthTopic) {
|
||||
node.birthTopic = options.birthTopic;
|
||||
node.birthQos = options.birthQos || "0";
|
||||
node.birthPayload = options.birthPayload || "";
|
||||
}
|
||||
if (options.closeTopic) {
|
||||
node.closeTopic = options.closeTopic;
|
||||
node.closeQos = options.closeQos || "0";
|
||||
node.closePayload = options.closePayload || "";
|
||||
}
|
||||
if (options.willTopic) {
|
||||
node.willTopic = options.willTopic;
|
||||
node.willQos = options.willQos || "0";
|
||||
node.willPayload = options.willPayload || "";
|
||||
}
|
||||
updateNodeOptions(options, node);
|
||||
return node;
|
||||
}
|
||||
|
||||
function buildMQTTInNode(id, name, brokerId, topic, options, wires) {
|
||||
//{ "id": "mqtt.in", "type": "mqtt in", "name": "mqtt_in", "topic": "test/in", "qos": "2", "datatype": "auto", "broker": "mqtt.broker", "nl": false, "rap": true, "rh": 0, "inputs": 0, "wires": [["mqtt.out"]] }
|
||||
options = options || {};
|
||||
options.broker = options.broker || "mqtt.broker";
|
||||
const node = buildNode("mqtt in", id || "mqtt.in", name || "mqtt_in", options);
|
||||
node.topic = topic || "";
|
||||
node.broker = brokerId;
|
||||
node.topicType = options.topicType == "dynamic" ? "dynamic" : "static",
|
||||
node.inputs = options.topicType == "dynamic" ? 1 : 0,
|
||||
updateNodeOptions(node, options, wires);
|
||||
return node;
|
||||
}
|
||||
|
||||
function buildMQTTOutNode(id, name, brokerId, topic, options) {
|
||||
//{ "id": "mqtt.out", "type": "mqtt out", "name": "mqtt_out", "topic": "test/out", "qos": "", "retain": "", "respTopic": "", "contentType": "", "userProps": "", "correl": "", "expiry": "", "broker": brokerId, "wires": [] },
|
||||
options = options || {};
|
||||
options.broker = options.broker || "mqtt.broker";
|
||||
const node = buildNode("mqtt out", id || "mqtt.out", name || "mqtt_out", options);
|
||||
node.topic = topic || "";
|
||||
node.broker = brokerId;
|
||||
updateNodeOptions(node, options, null);
|
||||
return node;
|
||||
}
|
||||
|
||||
function buildNode(type, id, name, options, wires) {
|
||||
//{ "id": "mqtt.in", "type": "mqtt in", "name": "mqtt_in", "topic": "test/in", "qos": "2", "datatype": "auto", "broker": "mqtt.broker", "nl": false, "rap": true, "rh": 0, "inputs": 0, "wires": [["mqtt.out"]] }
|
||||
options = options || {};
|
||||
const node = {
|
||||
"id": id || (type.replace(/[\W]/g, ".")),
|
||||
"type": type,
|
||||
"name": name || (type.replace(/[\W]/g, "_")),
|
||||
"wires": []
|
||||
}
|
||||
if (node.id.indexOf(".") == -1) { node.is += ".node" }
|
||||
updateNodeOptions(node, options, wires);
|
||||
return node;
|
||||
}
|
||||
|
||||
function updateNodeOptions(node, options, wires) {
|
||||
let keys = Object.keys(options);
|
||||
for (let index = 0; index < keys.length; index++) {
|
||||
const key = keys[index];
|
||||
const val = options[key];
|
||||
if (node[key] === undefined) {
|
||||
node[key] = val;
|
||||
}
|
||||
}
|
||||
if (wires && Array.isArray(wires)) {
|
||||
node.wires[0] = [...wires];
|
||||
}
|
||||
}
|
||||
|
||||
function compareMsgToExpected(msg, expectMsg) {
|
||||
msg.should.have.property("topic", expectMsg.topic);
|
||||
msg.should.have.property("payload", expectMsg.payload);
|
||||
if (hasProperty(expectMsg, "retain")) { msg.retain.should.eql(expectMsg.retain); }
|
||||
if (hasProperty(expectMsg, "qos")) {
|
||||
msg.qos.should.eql(expectMsg.qos);
|
||||
} else {
|
||||
msg.qos.should.eql(0);
|
||||
}
|
||||
if (hasProperty(expectMsg, "userProperties")) { msg.should.have.property("userProperties", expectMsg.userProperties); }
|
||||
if (hasProperty(expectMsg, "contentType")) { msg.should.have.property("contentType", expectMsg.contentType); }
|
||||
if (hasProperty(expectMsg, "correlationData")) { msg.should.have.property("correlationData", expectMsg.correlationData); }
|
||||
if (hasProperty(expectMsg, "responseTopic")) { msg.should.have.property("responseTopic", expectMsg.responseTopic); }
|
||||
if (hasProperty(expectMsg, "payloadFormatIndicator")) { msg.should.have.property("payloadFormatIndicator", expectMsg.payloadFormatIndicator); }
|
||||
if (hasProperty(expectMsg, "messageExpiryInterval")) { msg.should.have.property("messageExpiryInterval", expectMsg.messageExpiryInterval); }
|
||||
}
|
||||
|
||||
function waitBrokerConnect(broker, callback, timeLimit) {
|
||||
timeLimit = timeLimit || 2000;
|
||||
const brokers = Array.isArray(broker) ? broker : [broker];
|
||||
wait();
|
||||
function wait() {
|
||||
if (brokers.every(e => e.connected == true)) {
|
||||
callback(); //yey - connected!
|
||||
} else {
|
||||
timeLimit = timeLimit - 15;
|
||||
if (timeLimit <= 0) {
|
||||
throw new Error("Timeout waiting broker connect")
|
||||
}
|
||||
setTimeout(wait, 15);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function hasProperty(obj, propName) {
|
||||
return Object.prototype.hasOwnProperty.call(obj, propName);
|
||||
}
|
||||
|
||||
const base_topic = "nr" + Date.now().toString() + "/";
|
||||
let topicNo = 0;
|
||||
function nextTopic(topic) {
|
||||
topicNo++;
|
||||
if (!topic) { topic = "unittest" }
|
||||
if (topic.startsWith("/")) { topic = topic.substring(1); }
|
||||
if (topic.startsWith(base_topic)) { return topic + String(topicNo) }
|
||||
return (base_topic + topic + String(topicNo));
|
||||
}
|
||||
|
||||
//#endregion HELPERS
|
@ -1,592 +0,0 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
var ws = require("ws");
|
||||
var should = require("should");
|
||||
var helper = require("node-red-node-test-helper");
|
||||
var websocketNode = require("nr-test-utils").require("@node-red/nodes/core/network/22-websocket.js");
|
||||
|
||||
var sockets = [];
|
||||
|
||||
function getWsUrl(path) {
|
||||
return helper.url().replace(/http/, "ws") + path;
|
||||
}
|
||||
|
||||
function createClient(listenerid) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
var node = helper.getNode(listenerid);
|
||||
var url = getWsUrl(node.path);
|
||||
var sock = new ws(url);
|
||||
sockets.push(sock);
|
||||
|
||||
sock.on("open", function() {
|
||||
resolve(sock);
|
||||
});
|
||||
|
||||
sock.on("error", function(err) {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function closeAll() {
|
||||
for (var i = 0; i < sockets.length; i++) {
|
||||
sockets[i].close();
|
||||
}
|
||||
sockets = [];
|
||||
}
|
||||
|
||||
function getSocket(listenerid) {
|
||||
var node = helper.getNode(listenerid);
|
||||
return node.server;
|
||||
}
|
||||
|
||||
describe('websocket Node', function() {
|
||||
|
||||
before(function(done) {
|
||||
helper.startServer(done);
|
||||
});
|
||||
|
||||
after(function(done) {
|
||||
helper.stopServer(done);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
closeAll();
|
||||
helper.unload();
|
||||
});
|
||||
|
||||
describe('websocket-listener', function() {
|
||||
it('should load', function(done) {
|
||||
var flow = [{ id: "n1", type: "websocket-listener", path: "/ws" }];
|
||||
helper.load(websocketNode, flow, function() {
|
||||
helper.getNode("n1").should.have.property("path", "/ws");
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should be server', function(done) {
|
||||
var flow = [{ id: "n1", type: "websocket-listener", path: "/ws" }];
|
||||
helper.load(websocketNode, flow, function() {
|
||||
helper.getNode("n1").should.have.property('isServer', true);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle wholemsg property', function(done) {
|
||||
var flow = [
|
||||
{ id: "n1", type: "websocket-listener", path: "/ws" },
|
||||
{ id: "n2", type: "websocket-listener", path: "/ws2", wholemsg: "true" }];
|
||||
helper.load(websocketNode, flow, function() {
|
||||
helper.getNode("n1").should.have.property("wholemsg", false);
|
||||
helper.getNode("n2").should.have.property("wholemsg", true);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should create socket', function(done) {
|
||||
var flow = [
|
||||
{ id: "n1", type: "websocket-listener", path: "/ws" },
|
||||
{ id: "n2", type: "websocket in", server: "n1" }];
|
||||
helper.load(websocketNode, flow, function() {
|
||||
createClient("n1").then(function(sock) {
|
||||
done();
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should close socket on delete', function(done) {
|
||||
var flow = [{ id: "n1", type: "websocket-listener", path: "/ws" }];
|
||||
helper.load(websocketNode, flow, function() {
|
||||
createClient("n1").then(function(sock) {
|
||||
sock.on("close", function(code, msg) {
|
||||
done();
|
||||
});
|
||||
helper.clearFlows();
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should receive data', function(done) {
|
||||
var flow = [
|
||||
{ id: "n1", type: "websocket-listener", path: "/ws" },
|
||||
{ id: "n2", type: "websocket in", server: "n1", wires: [["n3"]] },
|
||||
{ id: "n3", type: "helper" }];
|
||||
helper.load(websocketNode, flow, function() {
|
||||
createClient("n1").then(function(sock) {
|
||||
helper.getNode("n3").on("input", function(msg) {
|
||||
msg.should.have.property("payload", "hello");
|
||||
done();
|
||||
});
|
||||
sock.send("hello");
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should receive wholemsg', function(done) {
|
||||
var flow = [
|
||||
{ id: "n1", type: "websocket-listener", path: "/ws", wholemsg: "true" },
|
||||
{ id: "n2", type: "websocket in", server: "n1", wires: [["n3"]] },
|
||||
{ id: "n3", type: "helper" }];
|
||||
helper.load(websocketNode, flow, function() {
|
||||
createClient("n1").then(function(sock) {
|
||||
sock.send('{"text":"hello"}');
|
||||
helper.getNode("n3").on("input", function(msg) {
|
||||
msg.should.have.property("text", "hello");
|
||||
done();
|
||||
});
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should receive wholemsg when data not JSON', function(done) {
|
||||
var flow = [
|
||||
{ id: "n1", type: "websocket-listener", path: "/ws", wholemsg: "true" },
|
||||
{ id: "n2", type: "websocket in", server: "n1", wires: [["n3"]] },
|
||||
{ id: "n3", type: "helper" }];
|
||||
helper.load(websocketNode, flow, function() {
|
||||
createClient("n1").then(function(sock) {
|
||||
sock.send('hello');
|
||||
helper.getNode("n3").on("input", function(msg) {
|
||||
msg.should.have.property("payload", "hello");
|
||||
done();
|
||||
});
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should receive wholemsg when data not object', function(done) {
|
||||
var flow = [
|
||||
{ id: "n1", type: "websocket-listener", path: "/ws", wholemsg: "true" },
|
||||
{ id: "n2", type: "websocket in", server: "n1", wires: [["n3"]] },
|
||||
{ id: "n3", type: "helper" }];
|
||||
helper.load(websocketNode, flow, function() {
|
||||
createClient("n1").then(function(sock) {
|
||||
helper.getNode("n3").on("input", function(msg) {
|
||||
msg.should.have.property("payload", 123);
|
||||
done();
|
||||
});
|
||||
sock.send(123);
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should send', function(done) {
|
||||
var flow = [
|
||||
{ id: "n1", type: "websocket-listener", path: "/ws" },
|
||||
{ id: "n2", type: "helper", wires: [["n3"]] },
|
||||
{ id: "n3", type: "websocket out", server: "n1" }];
|
||||
helper.load(websocketNode, flow, function() {
|
||||
createClient("n1").then(function(sock) {
|
||||
sock.on("message", function(msg, flags) {
|
||||
msg.should.equal("hello");
|
||||
done();
|
||||
});
|
||||
helper.getNode("n2").send({
|
||||
payload: "hello"
|
||||
});
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should send wholemsg', function(done) {
|
||||
var flow = [
|
||||
{ id: "n1", type: "websocket-listener", path: "/ws", wholemsg: "true" },
|
||||
{ id: "n2", type: "websocket out", server: "n1" },
|
||||
{ id: "n3", type: "helper", wires: [["n2"]] }];
|
||||
helper.load(websocketNode, flow, function() {
|
||||
createClient("n1").then(function(sock) {
|
||||
sock.on("message", function(msg, flags) {
|
||||
JSON.parse(msg).should.have.property("text", "hello");
|
||||
done();
|
||||
});
|
||||
helper.getNode("n3").send({
|
||||
text: "hello"
|
||||
});
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should do nothing if no payload', function(done) {
|
||||
var flow = [
|
||||
{ id: "n1", type: "websocket-listener", path: "/ws" },
|
||||
{ id: "n2", type: "helper", wires: [["n3"]] },
|
||||
{ id: "n3", type: "websocket out", server: "n1" }];
|
||||
helper.load(websocketNode, flow, function() {
|
||||
createClient("n1").then(function(sock) {
|
||||
setTimeout(function() {
|
||||
var logEvents = helper.log().args.filter(function(evt) {
|
||||
return evt[0].type == "file";
|
||||
});
|
||||
logEvents.should.have.length(0);
|
||||
done();
|
||||
},100);
|
||||
helper.getNode("n2").send({topic: "hello"});
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should echo', function(done) {
|
||||
var flow = [
|
||||
{ id: "n1", type: "websocket-listener", path: "/ws" },
|
||||
{ id: "n2", type: "websocket in", server: "n1", wires: [["n3"]] },
|
||||
{ id: "n3", type: "websocket out", server: "n1" }];
|
||||
helper.load(websocketNode, flow, function() {
|
||||
createClient("n1").then(function(sock) {
|
||||
sock.on("message", function(msg, flags) {
|
||||
msg.should.equal("hello");
|
||||
done();
|
||||
});
|
||||
sock.send("hello");
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should echo wholemsg', function(done) {
|
||||
var flow = [
|
||||
{ id: "n1", type: "websocket-listener", path: "/ws", wholemsg: "true" },
|
||||
{ id: "n2", type: "websocket in", server: "n1", wires: [["n3"]] },
|
||||
{ id: "n3", type: "websocket out", server: "n1" }];
|
||||
helper.load(websocketNode, flow, function() {
|
||||
createClient("n1").then(function(sock) {
|
||||
sock.on("message", function(msg, flags) {
|
||||
JSON.parse(msg).should.have.property("text", "hello");
|
||||
done();
|
||||
});
|
||||
sock.send('{"text":"hello"}');
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should broadcast', function(done) {
|
||||
var flow = [
|
||||
{ id: "n1", type: "websocket-listener", path: "/ws" },
|
||||
{ id: "n2", type: "websocket out", server: "n1" },
|
||||
{ id: "n3", type: "helper", wires: [["n2"]] }];
|
||||
helper.load(websocketNode, flow, function() {
|
||||
Promise.all([createClient("n1"), createClient("n1")]).then(function(socks) {
|
||||
var promises = [
|
||||
new Promise((resolve,reject) => {
|
||||
socks[0].on("message", function(msg, flags) {
|
||||
try {
|
||||
msg.should.equal("hello");
|
||||
resolve();
|
||||
} catch(err) {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
}),
|
||||
new Promise((resolve,reject) => {
|
||||
socks[1].on("message", function(msg, flags) {
|
||||
try {
|
||||
msg.should.equal("hello");
|
||||
resolve();
|
||||
} catch(err) {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
})
|
||||
];
|
||||
helper.getNode("n3").send({
|
||||
payload: "hello"
|
||||
});
|
||||
return Promise.all(promises).then(() => {done()});
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('websocket-client', function() {
|
||||
it('should load', function(done) {
|
||||
var flow = [
|
||||
{ id: "server", type: "websocket-listener", path: "/ws" },
|
||||
{ id: "n1", type: "websocket-client", path: getWsUrl("/ws") }];
|
||||
helper.load(websocketNode, flow, function() {
|
||||
helper.getNode("n1").should.have.property('path', getWsUrl("/ws"));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not be server', function(done) {
|
||||
var flow = [
|
||||
{ id: "server", type: "websocket-listener", path: "/ws" },
|
||||
{ id: "n1", type: "websocket-client", path: getWsUrl("/ws") }];
|
||||
helper.load(websocketNode, flow, function() {
|
||||
helper.getNode("n1").should.have.property('isServer', false);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle wholemsg property', function(done) {
|
||||
var flow = [
|
||||
{ id: "server", type: "websocket-listener", path: "/ws" },
|
||||
{ id: "n1", type: "websocket-client", path: getWsUrl("/ws") },
|
||||
{ id: "n2", type: "websocket-client", path: getWsUrl("/ws"), wholemsg: "true" }];
|
||||
helper.load(websocketNode, flow, function() {
|
||||
helper.getNode("n1").should.have.property("wholemsg", false);
|
||||
helper.getNode("n2").should.have.property("wholemsg", true);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle protocol property', function(done) {
|
||||
var flow = [
|
||||
{ id: "server", type: "websocket-listener", path: "/ws" },
|
||||
{ id: "n1", type: "websocket-client", path: getWsUrl("/ws") },
|
||||
{ id: "n2", type: "websocket-client", path: getWsUrl("/ws"), subprotocol: "testprotocol1, testprotocol2" }];
|
||||
helper.load(websocketNode, flow, function() {
|
||||
helper.getNode("n1").should.have.property("subprotocol", []);
|
||||
helper.getNode("n2").should.have.property("subprotocol", ["testprotocol1","testprotocol2"]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should connect to server', function(done) {
|
||||
var flow = [
|
||||
{ id: "server", type: "websocket-listener", path: "/ws" },
|
||||
{ id: "n2", type: "websocket-client", path: getWsUrl("/ws") }];
|
||||
helper.load(websocketNode, flow, function() {
|
||||
getSocket('server').on('connection', function(sock) {
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
it('should initiate with subprotocol', function(done) {
|
||||
var flow = [
|
||||
{ id: "server", type: "websocket-listener", path: "/ws" },
|
||||
{ id: "n2", type: "websocket-client", path: getWsUrl("/ws"), subprotocol: "testprotocol" }];
|
||||
helper.load(websocketNode, flow, function() {
|
||||
getSocket('server').on('connection', function (sock) {
|
||||
sock.should.have.property("protocol", "testprotocol")
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should close on delete', function(done) {
|
||||
var flow = [
|
||||
{ id: "server", type: "websocket-listener", path: "/ws" },
|
||||
{ id: "n2", type: "websocket-client", path: getWsUrl("/ws") }];
|
||||
helper.load(websocketNode, flow, function() {
|
||||
getSocket('server').on('connection', function(sock) {
|
||||
sock.on('close', function() {
|
||||
done();
|
||||
});
|
||||
helper.getNode("n2").close();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should receive data', function(done) {
|
||||
var flow = [
|
||||
{ id: "server", type: "websocket-listener", path: "/ws" },
|
||||
{ id: "n1", type: "websocket-client", path: getWsUrl("/ws") },
|
||||
{ id: "n2", type: "websocket in", client: "n1", wires: [["n3"]] },
|
||||
{ id: "n3", type: "helper" }];
|
||||
helper.load(websocketNode, flow, function() {
|
||||
getSocket('server').on('connection', function(sock) {
|
||||
sock.send('hello');
|
||||
});
|
||||
|
||||
helper.getNode("n3").on("input", function(msg) {
|
||||
msg.should.have.property("payload", "hello");
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should receive wholemsg data ', function(done) {
|
||||
var flow = [
|
||||
{ id: "server", type: "websocket-listener", path: "/ws" },
|
||||
{ id: "n1", type: "websocket-client", path: getWsUrl("/ws"), wholemsg: "true" },
|
||||
{ id: "n2", type: "websocket in", client: "n1", wires: [["n3"]] },
|
||||
{ id: "n3", type: "helper" }];
|
||||
helper.load(websocketNode, flow, function() {
|
||||
getSocket('server').on('connection', function(sock) {
|
||||
sock.send('{"text":"hello"}');
|
||||
});
|
||||
helper.getNode("n3").on("input", function(msg) {
|
||||
msg.should.have.property("text", "hello");
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should receive wholemsg when data not JSON', function(done) {
|
||||
var flow = [
|
||||
{ id: "server", type: "websocket-listener", path: "/ws" },
|
||||
{ id: "n1", type: "websocket-client", path: getWsUrl("/ws"), wholemsg: "true" },
|
||||
{ id: "n2", type: "websocket in", client: "n1", wires: [["n3"]] },
|
||||
{ id: "n3", type: "helper" }];
|
||||
helper.load(websocketNode, flow, function() {
|
||||
getSocket('server').on('connection', function(sock) {
|
||||
sock.send('hello');
|
||||
});
|
||||
helper.getNode("n3").on("input", function(msg) {
|
||||
msg.should.have.property("payload", "hello");
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should send', function(done) {
|
||||
var flow = [
|
||||
{ id: "server", type: "websocket-listener", path: "/ws" },
|
||||
{ id: "n1", type: "websocket-client", path: getWsUrl("/ws") },
|
||||
{ id: "n2", type: "websocket out", client: "n1" },
|
||||
{ id: "n3", type: "helper", wires: [["n2"]] }];
|
||||
helper.load(websocketNode, flow, function() {
|
||||
getSocket('server').on('connection', function(sock) {
|
||||
sock.on('message', function(msg) {
|
||||
msg.should.equal("hello");
|
||||
done();
|
||||
});
|
||||
});
|
||||
getSocket("n1").on("open", function() {
|
||||
helper.getNode("n3").send({
|
||||
payload: "hello"
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should send buffer', function(done) {
|
||||
var flow = [
|
||||
{ id: "server", type: "websocket-listener", path: "/ws" },
|
||||
{ id: "n1", type: "websocket-client", path: getWsUrl("/ws") },
|
||||
{ id: "n2", type: "websocket out", client: "n1" },
|
||||
{ id: "n3", type: "helper", wires: [["n2"]] }];
|
||||
helper.load(websocketNode, flow, function() {
|
||||
getSocket('server').on('connection', function(sock) {
|
||||
sock.on('message', function(msg) {
|
||||
Buffer.isBuffer(msg).should.be.true();
|
||||
msg.should.have.length(5);
|
||||
done();
|
||||
});
|
||||
});
|
||||
getSocket("n1").on("open", function() {
|
||||
helper.getNode("n3").send({
|
||||
payload: Buffer.from("hello")
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should send wholemsg', function(done) {
|
||||
var flow = [
|
||||
{ id: "server", type: "websocket-listener", path: "/ws" },
|
||||
{ id: "n1", type: "websocket-client", path: getWsUrl("/ws"), wholemsg: "true" },
|
||||
{ id: "n2", type: "websocket out", client: "n1" },
|
||||
{ id: "n3", type: "helper", wires: [["n2"]] }];
|
||||
helper.load(websocketNode, flow, function() {
|
||||
getSocket('server').on('connection', function(sock) {
|
||||
sock.on('message', function(msg) {
|
||||
JSON.parse(msg).should.have.property("text", "hello");
|
||||
done();
|
||||
});
|
||||
});
|
||||
getSocket("n1").on('open', function(){
|
||||
helper.getNode("n3").send({
|
||||
text: "hello"
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should NOT feedback more than once', function(done) {
|
||||
var flow = [
|
||||
{ id: "server", type: "websocket-listener", path: "/ws", wholemsg: "true" },
|
||||
{ id: "client", type: "websocket-client", path: getWsUrl("/ws"), wholemsg: "true" },
|
||||
{ id: "n1", type: "websocket in", client: "client", wires: [["n2", "output"]] },
|
||||
{ id: "n2", type: "websocket out", server: "server" },
|
||||
{ id: "n3", type: "helper", wires: [["n2"]] },
|
||||
{ id: "output", type: "helper" }];
|
||||
helper.load(websocketNode, flow, function() {
|
||||
getSocket('client').on('open', function() {
|
||||
helper.getNode("n3").send({
|
||||
payload: "ping"
|
||||
});
|
||||
});
|
||||
var acc = 0;
|
||||
helper.getNode("output").on("input", function(msg) {
|
||||
acc = acc + 1;
|
||||
});
|
||||
setTimeout( function() {
|
||||
acc.should.equal(1);
|
||||
helper.clearFlows();
|
||||
done();
|
||||
}, 250);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('websocket in node', function() {
|
||||
it('should report error if no server config', function(done) {
|
||||
var flow = [{ id: "n1", type: "websocket in", mode: "server" }];
|
||||
helper.load(websocketNode, flow, function() {
|
||||
var logEvents = helper.log().args.filter(function(evt) {
|
||||
return evt[0].type == "websocket in";
|
||||
});
|
||||
logEvents.should.have.length(1);
|
||||
logEvents[0][0].should.have.a.property('msg');
|
||||
logEvents[0][0].msg.toString().should.startWith("websocket.errors.missing-conf");
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('websocket out node', function() {
|
||||
it('should report error if no server config', function(done) {
|
||||
var flow = [{ id: "n1", type: "websocket out", mode: "server" }];
|
||||
helper.load(websocketNode, flow, function() {
|
||||
var logEvents = helper.log().args.filter(function(evt) {
|
||||
return evt[0].type == "websocket out";
|
||||
});
|
||||
//console.log(logEvents);
|
||||
logEvents.should.have.length(1);
|
||||
logEvents[0][0].should.have.a.property('msg');
|
||||
logEvents[0][0].msg.toString().should.startWith("websocket.errors.missing-conf");
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -1,340 +0,0 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
var net = require("net");
|
||||
var should = require("should");
|
||||
var stoppable = require('stoppable');
|
||||
var helper = require("node-red-node-test-helper");
|
||||
var tcpinNode = require("nr-test-utils").require("@node-red/nodes/core/network/31-tcpin.js");
|
||||
var RED = require("nr-test-utils").require("node-red/lib/red.js");
|
||||
|
||||
|
||||
describe('TCP Request Node', function() {
|
||||
var server = undefined;
|
||||
var port = 9000;
|
||||
|
||||
function startServer(done) {
|
||||
port += 1;
|
||||
server = stoppable(net.createServer(function(c) {
|
||||
c.on('data', function(data) {
|
||||
var rdata = "ACK:"+data.toString();
|
||||
c.write(rdata);
|
||||
});
|
||||
c.on('error', function(err) {
|
||||
startServer(done);
|
||||
});
|
||||
})).listen(port, "127.0.0.1", function(err) {
|
||||
done();
|
||||
});
|
||||
}
|
||||
|
||||
before(function(done) {
|
||||
startServer(done);
|
||||
});
|
||||
|
||||
after(function(done) {
|
||||
server.stop(done);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
helper.unload();
|
||||
});
|
||||
|
||||
function testTCP(flow, val0, val1, done) {
|
||||
helper.load(tcpinNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
n2.on("input", function(msg) {
|
||||
try {
|
||||
if (typeof val1 === 'object') {
|
||||
msg.should.have.properties(Object.assign({}, val1, {payload: Buffer.from(val1.payload)}));
|
||||
} else {
|
||||
msg.should.have.property('payload', Buffer.from(val1));
|
||||
}
|
||||
done();
|
||||
} catch(err) {
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
if((typeof val0) === 'object') {
|
||||
n1.receive(val0);
|
||||
} else {
|
||||
n1.receive({payload:val0});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function testTCPMany(flow, values, result, done) {
|
||||
helper.load(tcpinNode, flow, () => {
|
||||
const n1 = helper.getNode("n1");
|
||||
const n2 = helper.getNode("n2");
|
||||
n2.on("input", msg => {
|
||||
try {
|
||||
if (typeof result === 'object') {
|
||||
if (flow[0].ret === "string") {
|
||||
msg.should.have.properties(Object.assign({}, result, {payload: result.payload}));
|
||||
} else {
|
||||
msg.should.have.properties(Object.assign({}, result, {payload: Buffer.from(result.payload)}));
|
||||
}
|
||||
} else {
|
||||
if (flow[0].ret === "string") {
|
||||
msg.should.have.property('payload', result);
|
||||
} else {
|
||||
msg.should.have.property('payload', Buffer.from(result));
|
||||
}
|
||||
}
|
||||
done();
|
||||
} catch(err) {
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
values.forEach(value => {
|
||||
n1.receive(typeof value === 'object' ? value : {payload: value});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
describe('single message', function () {
|
||||
it('should send & recv data', function(done) {
|
||||
var flow = [{id:"n1", type:"tcp request", server:"localhost", port:port, out:"time", splitc: "0", wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"}];
|
||||
testTCP(flow, {
|
||||
payload: 'foo',
|
||||
topic: 'bar'
|
||||
}, {
|
||||
payload: 'ACK:foo',
|
||||
topic: 'bar'
|
||||
}, done);
|
||||
});
|
||||
|
||||
it('should retain complete message', function(done) {
|
||||
var flow = [{id:"n1", type:"tcp request", server:"localhost", port:port, out:"time", splitc: "0", wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"}];
|
||||
testTCP(flow, {
|
||||
payload: 'foo',
|
||||
topic: 'bar'
|
||||
}, {
|
||||
payload: 'ACK:foo',
|
||||
topic: 'bar'
|
||||
}, done);
|
||||
});
|
||||
|
||||
it('should send & recv data when specified character received', function(done) {
|
||||
var flow = [{id:"n1", type:"tcp request", server:"localhost", port:port, out:"char", splitc: "0", wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"}];
|
||||
testTCP(flow, {
|
||||
payload: 'foo0bar0',
|
||||
topic: 'bar'
|
||||
}, {
|
||||
payload: 'ACK:foo0',
|
||||
topic: 'bar'
|
||||
}, done);
|
||||
});
|
||||
|
||||
it('should send & recv data after fixed number of chars received', function(done) {
|
||||
var flow = [{id:"n1", type:"tcp request", server:"localhost", port:port, out:"count", splitc: "7", wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"}];
|
||||
testTCP(flow, {
|
||||
payload: 'foo bar',
|
||||
topic: 'bar'
|
||||
}, {
|
||||
payload: 'ACK:foo',
|
||||
topic: 'bar'
|
||||
}, done);
|
||||
});
|
||||
|
||||
it('should send & receive, then keep connection', function(done) {
|
||||
var flow = [{id:"n1", type:"tcp request", server:"localhost", port:port, out:"sit", splitc: "5", wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"}];
|
||||
testTCP(flow, {
|
||||
payload: 'foo',
|
||||
topic: 'bar'
|
||||
}, {
|
||||
payload: 'ACK:foo',
|
||||
topic: 'bar'
|
||||
}, done);
|
||||
});
|
||||
|
||||
it('should send & recv data to/from server:port from msg', function(done) {
|
||||
var flow = [{id:"n1", type:"tcp request", server:"", port:"", out:"time", splitc: "0", wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"}];
|
||||
testTCP(flow, {
|
||||
payload: "foo",
|
||||
host: "localhost",
|
||||
port: port
|
||||
}, {
|
||||
payload: "ACK:foo",
|
||||
host: 'localhost',
|
||||
port: port
|
||||
}, done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('many messages', function () {
|
||||
it('should send & recv data', function(done) {
|
||||
var flow = [{id:"n1", type:"tcp request", server:"localhost", port:port, out:"time", splitc: "0", wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"}];
|
||||
testTCPMany(flow, [{
|
||||
payload: 'f',
|
||||
topic: 'bar'
|
||||
}, {
|
||||
payload: 'o',
|
||||
topic: 'bar'
|
||||
}, {
|
||||
payload: 'o',
|
||||
topic: 'bar'
|
||||
}], {
|
||||
payload: 'ACK:foo',
|
||||
topic: 'bar'
|
||||
}, done);
|
||||
});
|
||||
|
||||
it('should send & recv data when specified character received', function(done) {
|
||||
var flow = [{id:"n1", type:"tcp request", server:"localhost", port:port, out:"char", splitc: "0", wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"}];
|
||||
testTCPMany(flow, [{
|
||||
payload: "foo0",
|
||||
topic: 'bar'
|
||||
}, {
|
||||
payload: "bar0",
|
||||
topic: 'bar'
|
||||
}], {
|
||||
payload: "ACK:foo0",
|
||||
topic: 'bar'
|
||||
}, done);
|
||||
});
|
||||
|
||||
it('should send & recv data after fixed number of chars received', function(done) {
|
||||
var flow = [{id:"n1", type:"tcp request", server:"localhost", port:port, out:"count", splitc: "7", wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"}];
|
||||
testTCPMany(flow, [{
|
||||
payload: "fo",
|
||||
topic: 'bar'
|
||||
}, {
|
||||
payload: "ob",
|
||||
topic: 'bar'
|
||||
}, {
|
||||
payload: "ar",
|
||||
topic: 'bar'
|
||||
}], {
|
||||
payload: "ACK:foo",
|
||||
topic: 'bar'
|
||||
}, done);
|
||||
});
|
||||
|
||||
it('should send & receive, then keep connection', function(done) {
|
||||
var flow = [{id:"n1", type:"tcp request", server:"localhost", port:port, out:"sit", splitc: "5", wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"}];
|
||||
testTCPMany(flow, [{
|
||||
payload: "foo",
|
||||
topic: 'bar'
|
||||
}, {
|
||||
payload: "bar",
|
||||
topic: 'bar'
|
||||
}, {
|
||||
payload: "baz",
|
||||
topic: 'bar'
|
||||
}], {
|
||||
payload: "ACK:foobarbaz",
|
||||
topic: 'bar'
|
||||
}, done);
|
||||
});
|
||||
|
||||
it('should send & receive, then keep connection, and not split return strings', function(done) {
|
||||
var flow = [{id:"n1", type:"tcp request", server:"localhost", port:port, out:"sit", ret:"string", newline:"", wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"}];
|
||||
testTCPMany(flow, [{
|
||||
payload: "foo",
|
||||
topic: 'boo'
|
||||
}, {
|
||||
payload: "bar<A>\nfoo",
|
||||
topic: 'boo'
|
||||
}], {
|
||||
payload: "ACK:foobar<A>\nfoo",
|
||||
topic: 'boo'
|
||||
}, done);
|
||||
});
|
||||
|
||||
it('should send & receive, then keep connection, and split return strings', function(done) {
|
||||
var flow = [{id:"n1", type:"tcp request", server:"localhost", port:port, out:"sit", ret:"string", newline:"<A>\\n", wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"}];
|
||||
testTCPMany(flow, [{
|
||||
payload: "foo",
|
||||
topic: 'boo'
|
||||
}, {
|
||||
payload: "bar<A>\nfoo",
|
||||
topic: 'boo'
|
||||
}], {
|
||||
payload: "ACK:foobar<A>",
|
||||
topic: 'boo'
|
||||
}, done);
|
||||
});
|
||||
|
||||
it('should send & recv data to/from server:port from msg', function(done) {
|
||||
var flow = [{id:"n1", type:"tcp request", server:"", port:"", out:"time", splitc: "0", wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"}];
|
||||
testTCPMany(flow, [
|
||||
{
|
||||
payload: "f",
|
||||
host: "localhost",
|
||||
port: port
|
||||
},
|
||||
{
|
||||
payload: "o",
|
||||
host: "localhost",
|
||||
port: port
|
||||
},
|
||||
{
|
||||
payload: "o",
|
||||
host: "localhost",
|
||||
port: port
|
||||
}
|
||||
], {
|
||||
payload: "ACK:foo",
|
||||
host: 'localhost',
|
||||
port: port
|
||||
}, done);
|
||||
});
|
||||
|
||||
it('should limit the queue size', function (done) {
|
||||
RED.settings.tcpMsgQueueSize = 10;
|
||||
var flow = [{id:"n1", type:"tcp request", server:"localhost", port:port, out:"sit", splitc: "5", wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"}];
|
||||
// create one more msg than is allowed
|
||||
const msgs = new Array(RED.settings.tcpMsgQueueSize + 1).fill('x');
|
||||
const expected = msgs.slice(0, -1);
|
||||
testTCPMany(flow, msgs, "ACK:" + expected.join(''), done);
|
||||
});
|
||||
|
||||
it('should only retain the latest message', function(done) {
|
||||
var flow = [{id:"n1", type:"tcp request", server:"localhost", port:port, out:"time", splitc: "0", wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"}];
|
||||
testTCPMany(flow, [{
|
||||
payload: 'f',
|
||||
topic: 'bar'
|
||||
}, {
|
||||
payload: 'o',
|
||||
topic: 'baz'
|
||||
}, {
|
||||
payload: 'o',
|
||||
topic: 'quux'
|
||||
}], {
|
||||
payload: 'ACK:foo',
|
||||
topic: 'quux'
|
||||
}, done);
|
||||
});
|
||||
});
|
||||
});
|
@ -1,590 +0,0 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
var should = require("should");
|
||||
var jsonNode = require("nr-test-utils").require("@node-red/nodes/core/parsers/70-JSON.js");
|
||||
var helper = require("node-red-node-test-helper");
|
||||
|
||||
describe('JSON node', function() {
|
||||
|
||||
before(function(done) {
|
||||
helper.startServer(done);
|
||||
});
|
||||
|
||||
after(function(done) {
|
||||
helper.stopServer(done);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
helper.unload();
|
||||
});
|
||||
|
||||
it('should convert a valid json string to a javascript object', function(done) {
|
||||
var flow = [{id:"jn1",type:"json",wires:[["jn2"]]},
|
||||
{id:"jn2", type:"helper"}];
|
||||
helper.load(jsonNode, flow, function() {
|
||||
var jn1 = helper.getNode("jn1");
|
||||
var jn2 = helper.getNode("jn2");
|
||||
jn2.on("input", function(msg) {
|
||||
msg.should.have.property('topic', 'bar');
|
||||
msg.payload.should.have.property('employees');
|
||||
msg.payload.employees[0].should.have.property('firstName', 'John');
|
||||
msg.payload.employees[0].should.have.property('lastName', 'Smith');
|
||||
done();
|
||||
});
|
||||
var jsonString = '{"employees":[{"firstName":"John", "lastName":"Smith"}]}';
|
||||
jn1.receive({payload:jsonString,topic: "bar"});
|
||||
});
|
||||
});
|
||||
|
||||
it('should convert a buffer of a valid json string to a javascript object', function(done) {
|
||||
var flow = [{id:"jn1",type:"json",action:"obj",wires:[["jn2"]]},
|
||||
{id:"jn2", type:"helper"}];
|
||||
helper.load(jsonNode, flow, function() {
|
||||
var jn1 = helper.getNode("jn1");
|
||||
var jn2 = helper.getNode("jn2");
|
||||
jn2.on("input", function(msg) {
|
||||
msg.should.have.property('topic', 'bar');
|
||||
msg.payload.should.have.property('employees');
|
||||
msg.payload.employees[0].should.have.property('firstName', 'John');
|
||||
msg.payload.employees[0].should.have.property('lastName', 'Smith');
|
||||
done();
|
||||
});
|
||||
var jsonString = Buffer.from('{"employees":[{"firstName":"John", "lastName":"Smith"}]}');
|
||||
jn1.receive({payload:jsonString,topic: "bar"});
|
||||
});
|
||||
});
|
||||
|
||||
it('should convert a javascript object to a json string', function(done) {
|
||||
var flow = [{id:"jn1",type:"json",wires:[["jn2"]]},
|
||||
{id:"jn2", type:"helper"}];
|
||||
helper.load(jsonNode, flow, function() {
|
||||
var jn1 = helper.getNode("jn1");
|
||||
var jn2 = helper.getNode("jn2");
|
||||
jn2.on("input", function(msg) {
|
||||
should.equal(msg.payload, '{"employees":[{"firstName":"John","lastName":"Smith"}]}');
|
||||
done();
|
||||
});
|
||||
var obj = {employees:[{firstName:"John", lastName:"Smith"}]};
|
||||
jn1.receive({payload:obj});
|
||||
});
|
||||
});
|
||||
|
||||
it('should convert a array to a json string', function(done) {
|
||||
var flow = [{id:"jn1",type:"json",wires:[["jn2"]]},
|
||||
{id:"jn2", type:"helper"}];
|
||||
helper.load(jsonNode, flow, function() {
|
||||
var jn1 = helper.getNode("jn1");
|
||||
var jn2 = helper.getNode("jn2");
|
||||
jn2.on("input", function(msg) {
|
||||
should.equal(msg.payload, '[1,2,3]');
|
||||
done();
|
||||
});
|
||||
var obj = [1,2,3];
|
||||
jn1.receive({payload:obj});
|
||||
});
|
||||
});
|
||||
|
||||
it('should convert a boolean to a json string', function(done) {
|
||||
var flow = [{id:"jn1",type:"json",wires:[["jn2"]]},
|
||||
{id:"jn2", type:"helper"}];
|
||||
helper.load(jsonNode, flow, function() {
|
||||
var jn1 = helper.getNode("jn1");
|
||||
var jn2 = helper.getNode("jn2");
|
||||
jn2.on("input", function(msg) {
|
||||
should.equal(msg.payload, 'true');
|
||||
done();
|
||||
});
|
||||
var obj = true;
|
||||
jn1.receive({payload:obj});
|
||||
});
|
||||
});
|
||||
|
||||
it('should convert a json string to a boolean', function(done) {
|
||||
var flow = [{id:"jn1",type:"json",wires:[["jn2"]]},
|
||||
{id:"jn2", type:"helper"}];
|
||||
helper.load(jsonNode, flow, function() {
|
||||
var jn1 = helper.getNode("jn1");
|
||||
var jn2 = helper.getNode("jn2");
|
||||
jn2.on("input", function(msg) {
|
||||
should.equal(msg.payload, true);
|
||||
done();
|
||||
});
|
||||
var obj = "true";
|
||||
jn1.receive({payload:obj});
|
||||
});
|
||||
});
|
||||
|
||||
it('should convert a number to a json string', function(done) {
|
||||
var flow = [{id:"jn1",type:"json",wires:[["jn2"]]},
|
||||
{id:"jn2", type:"helper"}];
|
||||
helper.load(jsonNode, flow, function() {
|
||||
var jn1 = helper.getNode("jn1");
|
||||
var jn2 = helper.getNode("jn2");
|
||||
jn2.on("input", function(msg) {
|
||||
should.equal(msg.payload, '2019');
|
||||
done();
|
||||
});
|
||||
var obj = 2019;
|
||||
jn1.receive({payload:obj});
|
||||
});
|
||||
});
|
||||
|
||||
it('should convert a json string to a number', function(done) {
|
||||
var flow = [{id:"jn1",type:"json",wires:[["jn2"]]},
|
||||
{id:"jn2", type:"helper"}];
|
||||
helper.load(jsonNode, flow, function() {
|
||||
var jn1 = helper.getNode("jn1");
|
||||
var jn2 = helper.getNode("jn2");
|
||||
jn2.on("input", function(msg) {
|
||||
should.equal(msg.payload, 1962);
|
||||
done();
|
||||
});
|
||||
var obj = '1962';
|
||||
jn1.receive({payload:obj});
|
||||
});
|
||||
});
|
||||
|
||||
it('should log an error if asked to parse an invalid json string', function(done) {
|
||||
var flow = [{id:"jn1",type:"json",wires:[["jn2"]]},
|
||||
{id:"jn2", type:"helper"}];
|
||||
helper.load(jsonNode, flow, function() {
|
||||
try {
|
||||
var jn1 = helper.getNode("jn1");
|
||||
var jn2 = helper.getNode("jn2");
|
||||
jn1.receive({payload:'foo',topic: "bar"});
|
||||
setTimeout(function() {
|
||||
try {
|
||||
var logEvents = helper.log().args.filter(function(evt) {
|
||||
return evt[0].type == "json";
|
||||
});
|
||||
logEvents.should.have.length(1);
|
||||
logEvents[0][0].should.have.a.property('msg');
|
||||
logEvents[0][0].msg.should.startWith("Unexpected token o");
|
||||
logEvents[0][0].should.have.a.property('level',helper.log().ERROR);
|
||||
done();
|
||||
} catch(err) { done(err) }
|
||||
},20);
|
||||
} catch(err) {
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should log an error if asked to parse an invalid json string in a buffer', function(done) {
|
||||
var flow = [{id:"jn1",type:"json",action:"obj",wires:[["jn2"]]},
|
||||
{id:"jn2", type:"helper"}];
|
||||
helper.load(jsonNode, flow, function() {
|
||||
try {
|
||||
var jn1 = helper.getNode("jn1");
|
||||
var jn2 = helper.getNode("jn2");
|
||||
jn1.receive({payload:Buffer.from('{"name":foo}'),topic: "bar"});
|
||||
setTimeout(function() {
|
||||
try {
|
||||
var logEvents = helper.log().args.filter(function(evt) {
|
||||
return evt[0].type == "json";
|
||||
});
|
||||
logEvents.should.have.length(1);
|
||||
logEvents[0][0].should.have.a.property('msg');
|
||||
logEvents[0][0].msg.should.startWith("Unexpected token o");
|
||||
logEvents[0][0].should.have.a.property('level',helper.log().ERROR);
|
||||
done();
|
||||
} catch(err) { done(err) }
|
||||
},20);
|
||||
} catch(err) {
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// it('should log an error if asked to parse something thats not json or js and not in force object mode', function(done) {
|
||||
// var flow = [{id:"jn1",type:"json",wires:[["jn2"]]},
|
||||
// {id:"jn2", type:"helper"}];
|
||||
// helper.load(jsonNode, flow, function() {
|
||||
// var jn1 = helper.getNode("jn1");
|
||||
// var jn2 = helper.getNode("jn2");
|
||||
// setTimeout(function() {
|
||||
// try {
|
||||
// var logEvents = helper.log().args.filter(function(evt) {
|
||||
// return evt[0].type == "json";
|
||||
// });
|
||||
// logEvents.should.have.length(1);
|
||||
// logEvents[0][0].should.have.a.property('msg');
|
||||
// logEvents[0][0].msg.toString().should.eql('json.errors.dropped');
|
||||
// done();
|
||||
// } catch(err) {
|
||||
// done(err);
|
||||
// }
|
||||
// },50);
|
||||
// jn1.receive({payload:Buffer.from("abcd")});
|
||||
// });
|
||||
// });
|
||||
|
||||
it('should pass straight through if no payload set', function(done) {
|
||||
var flow = [{id:"jn1",type:"json",wires:[["jn2"]]},
|
||||
{id:"jn2", type:"helper"}];
|
||||
helper.load(jsonNode, flow, function() {
|
||||
var jn1 = helper.getNode("jn1");
|
||||
var jn2 = helper.getNode("jn2");
|
||||
jn2.on("input", function(msg) {
|
||||
msg.should.have.property('topic', 'bar');
|
||||
msg.should.not.have.property('payload');
|
||||
done();
|
||||
});
|
||||
jn1.receive({topic: "bar"});
|
||||
});
|
||||
});
|
||||
|
||||
it('should ensure the result is a json string', function(done) {
|
||||
var flow = [{id:"jn1",type:"json",action:"str",wires:[["jn2"]]},
|
||||
{id:"jn2", type:"helper"}];
|
||||
helper.load(jsonNode, flow, function() {
|
||||
var jn1 = helper.getNode("jn1");
|
||||
var jn2 = helper.getNode("jn2");
|
||||
var count = 0;
|
||||
jn2.on("input", function(msg) {
|
||||
try {
|
||||
should.equal(msg.payload, '{"employees":[{"firstName":"John","lastName":"Smith"}]}');
|
||||
count++;
|
||||
if (count === 2) {
|
||||
done();
|
||||
}
|
||||
} catch(err) {
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
var obj = {employees:[{firstName:"John", lastName:"Smith"}]};
|
||||
jn1.receive({payload:obj,topic: "bar"});
|
||||
jn1.receive({payload:JSON.stringify(obj),topic: "bar"});
|
||||
});
|
||||
});
|
||||
|
||||
it('should ensure the result is a JS Object', function(done) {
|
||||
var flow = [{id:"jn1",type:"json",action:"obj",wires:[["jn2"]]},
|
||||
{id:"jn2", type:"helper"}];
|
||||
helper.load(jsonNode, flow, function() {
|
||||
var jn1 = helper.getNode("jn1");
|
||||
var jn2 = helper.getNode("jn2");
|
||||
var count = 0;
|
||||
jn2.on("input", function(msg) {
|
||||
try {
|
||||
msg.should.have.property('topic', 'bar');
|
||||
msg.payload.should.have.property('employees');
|
||||
msg.payload.employees[0].should.have.property('firstName', 'John');
|
||||
msg.payload.employees[0].should.have.property('lastName', 'Smith');
|
||||
count++;
|
||||
if (count === 2) {
|
||||
done();
|
||||
}
|
||||
} catch(err) {
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
var obj = {employees:[{firstName:"John", lastName:"Smith"}]};
|
||||
jn1.receive({payload:obj,topic: "bar"});
|
||||
jn1.receive({payload:JSON.stringify(obj),topic: "bar"});
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle any msg property - receive existing string', function(done) {
|
||||
var flow = [{id:"jn1",type:"json",property:"one.two",wires:[["jn2"]]},
|
||||
{id:"jn2", type:"helper"}];
|
||||
helper.load(jsonNode, flow, function() {
|
||||
var jn1 = helper.getNode("jn1");
|
||||
var jn2 = helper.getNode("jn2");
|
||||
jn2.on("input", function(msg) {
|
||||
try {
|
||||
msg.should.have.property('topic', 'bar');
|
||||
msg.should.have.property('one');
|
||||
msg.one.should.have.property('two');
|
||||
msg.one.two.should.have.property('employees');
|
||||
msg.one.two.employees[0].should.have.property('firstName', 'John');
|
||||
msg.one.two.employees[0].should.have.property('lastName', 'Smith');
|
||||
done();
|
||||
} catch(err) {
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
var jsonString = '{"employees":[{"firstName":"John", "lastName":"Smith"}]}';
|
||||
jn1.receive({payload:"",one:{two:jsonString},topic: "bar"});
|
||||
|
||||
var logEvents = helper.log().args.filter(function(evt) {
|
||||
return evt[0].type == "json";
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle any msg property - receive existing obj', function(done) {
|
||||
var flow = [{id:"jn1",type:"json",property:"one.two",wires:[["jn2"]]},
|
||||
{id:"jn2", type:"helper"}];
|
||||
helper.load(jsonNode, flow, function() {
|
||||
var jn1 = helper.getNode("jn1");
|
||||
var jn2 = helper.getNode("jn2");
|
||||
jn2.on("input", function(msg) {
|
||||
try {
|
||||
should.equal(msg.one.two, '{"employees":[{"firstName":"John","lastName":"Smith"}]}');
|
||||
done();
|
||||
} catch(err) {
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
var jsonString = '{"employees":[{"firstName":"John", "lastName":"Smith"}]}';
|
||||
jn1.receive({payload:"",one:{two:JSON.parse(jsonString)},topic: "bar"});
|
||||
|
||||
var logEvents = helper.log().args.filter(function(evt) {
|
||||
return evt[0].type == "json";
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should pass an object if provided a valid JSON string and schema', function(done) {
|
||||
var flow = [{id:"jn1",type:"json",wires:[["jn2"]]},
|
||||
{id:"jn2", type:"helper"}];
|
||||
helper.load(jsonNode, flow, function() {
|
||||
var jn1 = helper.getNode("jn1");
|
||||
var jn2 = helper.getNode("jn2");
|
||||
jn2.on("input", function(msg) {
|
||||
should.equal(msg.payload.number, 3);
|
||||
should.equal(msg.payload.string, "allo");
|
||||
done();
|
||||
});
|
||||
var jsonString = '{"number": 3, "string": "allo"}';
|
||||
var schema = {title: "testSchema", type: "object", properties: {number: {type: "number"}, string: {type: "string" }}};
|
||||
jn1.receive({payload:jsonString, schema:schema});
|
||||
});
|
||||
});
|
||||
|
||||
it('should pass an object if provided a valid object and schema and action is object', function(done) {
|
||||
var flow = [{id:"jn1",type:"json",action:"obj",wires:[["jn2"]]},
|
||||
{id:"jn2", type:"helper"}];
|
||||
helper.load(jsonNode, flow, function() {
|
||||
var jn1 = helper.getNode("jn1");
|
||||
var jn2 = helper.getNode("jn2");
|
||||
jn2.on("input", function(msg) {
|
||||
should.equal(msg.payload.number, 3);
|
||||
should.equal(msg.payload.string, "allo");
|
||||
done();
|
||||
});
|
||||
var obj = {"number": 3, "string": "allo"};
|
||||
var schema = {title: "testSchema", type: "object", properties: {number: {type: "number"}, string: {type: "string" }}};
|
||||
jn1.receive({payload:obj, schema:schema});
|
||||
});
|
||||
});
|
||||
|
||||
it('should pass a string if provided a valid object and schema', function(done) {
|
||||
var flow = [{id:"jn1",type:"json",wires:[["jn2"]]},
|
||||
{id:"jn2", type:"helper"}];
|
||||
helper.load(jsonNode, flow, function() {
|
||||
var jn1 = helper.getNode("jn1");
|
||||
var jn2 = helper.getNode("jn2");
|
||||
jn2.on("input", function(msg) {
|
||||
should.equal(msg.payload, '{"number":3,"string":"allo"}');
|
||||
done();
|
||||
});
|
||||
var obj = {"number": 3, "string": "allo"};
|
||||
var schema = {title: "testSchema", type: "object", properties: {number: {type: "number"}, string: {type: "string" }}};
|
||||
jn1.receive({payload:obj, schema:schema});
|
||||
});
|
||||
});
|
||||
|
||||
it('should pass a string if provided a valid JSON string and schema and action is string', function(done) {
|
||||
var flow = [{id:"jn1",type:"json",action:"str",wires:[["jn2"]]},
|
||||
{id:"jn2", type:"helper"}];
|
||||
helper.load(jsonNode, flow, function() {
|
||||
var jn1 = helper.getNode("jn1");
|
||||
var jn2 = helper.getNode("jn2");
|
||||
jn2.on("input", function(msg) {
|
||||
should.equal(msg.payload, '{"number":3,"string":"allo"}');
|
||||
done();
|
||||
});
|
||||
var jsonString = '{"number":3,"string":"allo"}';
|
||||
var schema = {title: "testSchema", type: "object", properties: {number: {type: "number"}, string: {type: "string" }}};
|
||||
jn1.receive({payload:jsonString, schema:schema});
|
||||
});
|
||||
});
|
||||
|
||||
it('should log an error if passed an invalid object and valid schema', function(done) {
|
||||
var flow = [{id:"jn1",type:"json",wires:[["jn2"]]},
|
||||
{id:"jn2", type:"helper"}];
|
||||
helper.load(jsonNode, flow, function() {
|
||||
try {
|
||||
var jn1 = helper.getNode("jn1");
|
||||
var jn2 = helper.getNode("jn2");
|
||||
var schema = {title: "testSchema", type: "object", properties: {number: {type: "number"}, string: {type: "string" }}};
|
||||
var obj = {"number": "foo", "string": 3};
|
||||
jn1.receive({payload:obj, schema:schema});
|
||||
setTimeout(function() {
|
||||
try {
|
||||
var logEvents = helper.log().args.filter(function(evt) {
|
||||
return evt[0].type == "json";
|
||||
});
|
||||
logEvents.should.have.length(1);
|
||||
logEvents[0][0].should.have.a.property('msg');
|
||||
logEvents[0][0].msg.should.startWith("json.errors.schema-error");
|
||||
logEvents[0][0].should.have.a.property('level',helper.log().ERROR);
|
||||
done();
|
||||
} catch(err) { done(err) }
|
||||
},50);
|
||||
} catch(err) {
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should log an error if passed an invalid object and valid schema and action is object', function(done) {
|
||||
var flow = [{id:"jn1",type:"json",action:"obj",wires:[["jn2"]]},
|
||||
{id:"jn2", type:"helper"}];
|
||||
helper.load(jsonNode, flow, function() {
|
||||
try {
|
||||
var jn1 = helper.getNode("jn1");
|
||||
var jn2 = helper.getNode("jn2");
|
||||
var schema = {title: "testSchema", type: "object", properties: {number: {type: "number"}, string: {type: "string" }}};
|
||||
var obj = {"number": "foo", "string": 3};
|
||||
jn1.receive({payload:obj, schema:schema});
|
||||
setTimeout(function() {
|
||||
try {
|
||||
var logEvents = helper.log().args.filter(function(evt) {
|
||||
return evt[0].type == "json";
|
||||
});
|
||||
logEvents.should.have.length(1);
|
||||
logEvents[0][0].should.have.a.property('msg');
|
||||
logEvents[0][0].msg.should.startWith("json.errors.schema-error");
|
||||
logEvents[0][0].should.have.a.property('level',helper.log().ERROR);
|
||||
done();
|
||||
} catch(err) { done(err) }
|
||||
},50);
|
||||
} catch(err) {
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should log an error if passed an invalid JSON string and valid schema', function(done) {
|
||||
var flow = [{id:"jn1",type:"json",wires:[["jn2"]]},
|
||||
{id:"jn2", type:"helper"}];
|
||||
helper.load(jsonNode, flow, function() {
|
||||
try {
|
||||
var jn1 = helper.getNode("jn1");
|
||||
var jn2 = helper.getNode("jn2");
|
||||
var schema = {title: "testSchema", type: "object", properties: {number: {type: "number"}, string: {type: "string" }}};
|
||||
var jsonString = '{"number":"Hello","string":3}';
|
||||
jn1.receive({payload:jsonString, schema:schema});
|
||||
setTimeout(function() {
|
||||
try {
|
||||
var logEvents = helper.log().args.filter(function(evt) {
|
||||
return evt[0].type == "json";
|
||||
});
|
||||
logEvents.should.have.length(1);
|
||||
logEvents[0][0].should.have.a.property('msg');
|
||||
logEvents[0][0].msg.should.startWith("json.errors.schema-error");
|
||||
logEvents[0][0].should.have.a.property('level',helper.log().ERROR);
|
||||
done();
|
||||
} catch(err) { done(err) }
|
||||
},50);
|
||||
} catch(err) {
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should log an error if passed an invalid JSON string and valid schema and action is string', function(done) {
|
||||
var flow = [{id:"jn1",type:"json",action:"str",wires:[["jn2"]]},
|
||||
{id:"jn2", type:"helper"}];
|
||||
helper.load(jsonNode, flow, function() {
|
||||
try {
|
||||
var jn1 = helper.getNode("jn1");
|
||||
var jn2 = helper.getNode("jn2");
|
||||
var schema = {title: "testSchema", type: "object", properties: {number: {type: "number"}, string: {type: "string" }}};
|
||||
var jsonString = '{"number":"Hello","string":3}';
|
||||
jn1.receive({payload:jsonString, schema:schema});
|
||||
setTimeout(function() {
|
||||
try {
|
||||
var logEvents = helper.log().args.filter(function(evt) {
|
||||
return evt[0].type == "json";
|
||||
});
|
||||
logEvents.should.have.length(1);
|
||||
logEvents[0][0].should.have.a.property('msg');
|
||||
logEvents[0][0].msg.should.startWith("json.errors.schema-error");
|
||||
logEvents[0][0].should.have.a.property('level',helper.log().ERROR);
|
||||
done();
|
||||
} catch(err) { done(err) }
|
||||
},50);
|
||||
} catch(err) {
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should log an error if passed a valid object and invalid schema', function(done) {
|
||||
var flow = [{id:"jn1",type:"json",wires:[["jn2"]]},
|
||||
{id:"jn2", type:"helper"}];
|
||||
helper.load(jsonNode, flow, function() {
|
||||
try {
|
||||
var jn1 = helper.getNode("jn1");
|
||||
var jn2 = helper.getNode("jn2");
|
||||
var schema = "garbage";
|
||||
var obj = {"number": "foo", "string": 3};
|
||||
jn1.receive({payload:obj, schema:schema});
|
||||
setTimeout(function() {
|
||||
try {
|
||||
var logEvents = helper.log().args.filter(function(evt) {
|
||||
return evt[0].type == "json";
|
||||
});
|
||||
logEvents.should.have.length(1);
|
||||
logEvents[0][0].should.have.a.property('msg');
|
||||
logEvents[0][0].msg.should.equal("json.errors.schema-error-compile");
|
||||
logEvents[0][0].should.have.a.property('level',helper.log().ERROR);
|
||||
done();
|
||||
} catch(err) { done(err) }
|
||||
},50);
|
||||
} catch(err) {
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('msg.schema property should be deleted before sending to next node (string input)', function(done) {
|
||||
var flow = [{id:"jn1",type:"json",action:"str",wires:[["jn2"]]},
|
||||
{id:"jn2", type:"helper"}];
|
||||
helper.load(jsonNode, flow, function() {
|
||||
var jn1 = helper.getNode("jn1");
|
||||
var jn2 = helper.getNode("jn2");
|
||||
jn2.on("input", function(msg) {
|
||||
should.equal(msg.schema, undefined);
|
||||
done();
|
||||
});
|
||||
var jsonString = '{"number":3,"string":"allo"}';
|
||||
var schema = {title: "testSchema", type: "object", properties: {number: {type: "number"}, string: {type: "string" }}};
|
||||
jn1.receive({payload:jsonString, schema:schema});
|
||||
});
|
||||
});
|
||||
|
||||
it('msg.schema property should be deleted before sending to next node (object input)', function(done) {
|
||||
var flow = [{id:"jn1",type:"json",action:"str",wires:[["jn2"]]},
|
||||
{id:"jn2", type:"helper"}];
|
||||
helper.load(jsonNode, flow, function() {
|
||||
var jn1 = helper.getNode("jn1");
|
||||
var jn2 = helper.getNode("jn2");
|
||||
jn2.on("input", function(msg) {
|
||||
should.equal(msg.schema, undefined);
|
||||
done();
|
||||
});
|
||||
var jsonObject = {"number":3,"string":"allo"};
|
||||
var schema = {title: "testSchema", type: "object", properties: {number: {type: "number"}, string: {type: "string" }}};
|
||||
jn1.receive({payload:jsonObject, schema:schema});
|
||||
});
|
||||
});
|
||||
});
|
@ -1,609 +0,0 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
var should = require("should");
|
||||
var functionNode = require("nr-test-utils").require("@node-red/nodes/core/function/10-function.js");
|
||||
var helper = require("node-red-node-test-helper");
|
||||
|
||||
// Notice:
|
||||
// - nodes should have x, y, z property when defining subflow.
|
||||
|
||||
describe('subflow', function() {
|
||||
|
||||
before(function(done) {
|
||||
helper.startServer(done);
|
||||
});
|
||||
|
||||
after(function(done) {
|
||||
helper.stopServer(done);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
helper.unload();
|
||||
});
|
||||
|
||||
it('should define subflow', function(done) {
|
||||
var flow = [
|
||||
{id:"t1", type:"tab"},
|
||||
{id:"n1", z:"t1", type:"subflow:s1", wires:[["n2"]]},
|
||||
{id:"n2", z:"t1", type:"helper", wires:[]},
|
||||
// Subflow
|
||||
{id:"s1", type:"subflow", name:"Subflow", info:"",
|
||||
in:[{wires:[ {id:"s1-n1"} ]}],
|
||||
out:[{wires:[ {id:"s1-n1", port:0} ]}]},
|
||||
{id:"s1-n1", z:"s1", type:"function",
|
||||
func:"return msg;", wires:[]}
|
||||
];
|
||||
helper.load(functionNode, flow, function() {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should pass data to/from subflow', function(done) {
|
||||
var flow = [
|
||||
{id:"t0", type:"tab", label:"", disabled:false, info:""},
|
||||
{id:"n1", x:10, y:10, z:"t0", type:"subflow:s1", wires:[["n2"]]},
|
||||
{id:"n2", x:10, y:10, z:"t0", type:"helper", wires:[]},
|
||||
// Subflow
|
||||
{id:"s1", type:"subflow", name:"Subflow", info:"",
|
||||
in:[{
|
||||
x:10, y:10,
|
||||
wires:[ {id:"s1-n1"} ]
|
||||
}],
|
||||
out:[{
|
||||
x:10, y:10,
|
||||
wires:[ {id:"s1-n1", port:0} ]
|
||||
}]
|
||||
},
|
||||
{id:"s1-n1", x:10, y:10, z:"s1", type:"function",
|
||||
func:"msg.payload = msg.payload+'bar'; return msg;", wires:[]}
|
||||
];
|
||||
helper.load(functionNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
n2.on("input", function(msg) {
|
||||
msg.should.have.property("payload", "foobar");
|
||||
done();
|
||||
});
|
||||
n1.receive({payload:"foo"});
|
||||
});
|
||||
});
|
||||
|
||||
it('should pass data to/from nested subflow', function(done) {
|
||||
var flow = [
|
||||
{id:"t0", type:"tab", label:"", disabled:false, info:""},
|
||||
{id:"n1", x:10, y:10, z:"t0", type:"subflow:s1", wires:[["n2"]]},
|
||||
{id:"n2", x:10, y:10, z:"t0", type:"helper", wires:[]},
|
||||
// Subflow1
|
||||
{id:"s1", type:"subflow", name:"Subflow1", info:"",
|
||||
in:[{
|
||||
x:10, y:10,
|
||||
wires:[ {id:"s1-n1"} ]
|
||||
}],
|
||||
out:[{
|
||||
x:10, y:10,
|
||||
wires:[ {id:"s1-n2", port:0} ]
|
||||
}]
|
||||
},
|
||||
{id:"s1-n1", x:10, y:10, z:"s1", type:"subflow:s2",
|
||||
wires:[["s1-n2"]]},
|
||||
{id:"s1-n2", x:10, y:10, z:"s1", type:"function",
|
||||
func:"msg.payload = msg.payload+'baz'; return msg;", wires:[]},
|
||||
// Subflow2
|
||||
{id:"s2", type:"subflow", name:"Subflow2", info:"",
|
||||
in:[{
|
||||
x:10, y:10,
|
||||
wires:[ {id:"s2-n1"} ]
|
||||
}],
|
||||
out:[{
|
||||
x:10, y:10,
|
||||
wires:[ {id:"s2-n1", port:0} ]
|
||||
}]
|
||||
},
|
||||
{id:"s2-n1", x:10, y:10, z:"s2", type:"function",
|
||||
func:"msg.payload=msg.payload+'bar'; return msg;", wires:[]}
|
||||
];
|
||||
helper.load(functionNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
n2.on("input", function(msg) {
|
||||
msg.should.have.property("payload", "foobarbaz");
|
||||
done();
|
||||
});
|
||||
n1.receive({payload:"foo"});
|
||||
});
|
||||
});
|
||||
|
||||
it('should access env var of subflow template', function(done) {
|
||||
var flow = [
|
||||
{id:"t0", type:"tab", label:"", disabled:false, info:""},
|
||||
{id:"n1", x:10, y:10, z:"t0", type:"subflow:s1", wires:[["n2"]]},
|
||||
{id:"n2", x:10, y:10, z:"t0", type:"helper", wires:[]},
|
||||
// Subflow
|
||||
{id:"s1", type:"subflow", name:"Subflow", info:"",
|
||||
env: [
|
||||
{name: "K", type: "str", value: "V"}
|
||||
],
|
||||
in:[{
|
||||
x:10, y:10,
|
||||
wires:[ {id:"s1-n1"} ]
|
||||
}],
|
||||
out:[{
|
||||
x:10, y:10,
|
||||
wires:[ {id:"s1-n1", port:0} ]
|
||||
}]
|
||||
},
|
||||
{id:"s1-n1", x:10, y:10, z:"s1", type:"function",
|
||||
func:"msg.V = env.get('K'); return msg;",
|
||||
wires:[]}
|
||||
];
|
||||
helper.load(functionNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
n2.on("input", function(msg) {
|
||||
try {
|
||||
msg.should.have.property("V", "V");
|
||||
done();
|
||||
}
|
||||
catch (e) {
|
||||
console.log(e);
|
||||
done(e);
|
||||
}
|
||||
});
|
||||
n1.receive({payload:"foo"});
|
||||
});
|
||||
});
|
||||
|
||||
it('should access env var of subflow instance', function(done) {
|
||||
var flow = [
|
||||
{id:"t0", type:"tab", label:"", disabled:false, info:""},
|
||||
{id:"n1", x:10, y:10, z:"t0", type:"subflow:s1",
|
||||
env: [
|
||||
{name: "K", type: "str", value: "V"}
|
||||
],
|
||||
wires:[["n2"]]},
|
||||
{id:"n2", x:10, y:10, z:"t0", type:"helper", wires:[]},
|
||||
// Subflow
|
||||
{id:"s1", type:"subflow", name:"Subflow", info:"",
|
||||
in:[{
|
||||
x:10, y:10,
|
||||
wires:[ {id:"s1-n1"} ]
|
||||
}],
|
||||
out:[{
|
||||
x:10, y:10,
|
||||
wires:[ {id:"s1-n1", port:0} ]
|
||||
}]
|
||||
},
|
||||
{id:"s1-n1", x:10, y:10, z:"s1", type:"function",
|
||||
func:"msg.V = env.get('K'); return msg;",
|
||||
wires:[]}
|
||||
];
|
||||
helper.load(functionNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
n2.on("input", function(msg) {
|
||||
try {
|
||||
msg.should.have.property("V", "V");
|
||||
done();
|
||||
}
|
||||
catch (e) {
|
||||
console.log(e);
|
||||
done(e);
|
||||
}
|
||||
});
|
||||
n1.receive({payload:"foo"});
|
||||
});
|
||||
});
|
||||
|
||||
it('should access last env var with same name', function(done) {
|
||||
var flow = [
|
||||
{id:"t0", type:"tab", label:"", disabled:false, info:""},
|
||||
{id:"n1", x:10, y:10, z:"t0", type:"subflow:s1",
|
||||
env: [
|
||||
{name: "K", type: "str", value: "V0"},
|
||||
{name: "X", type: "str", value: "VX"},
|
||||
{name: "K", type: "str", value: "V1"}
|
||||
],
|
||||
wires:[["n2"]]},
|
||||
{id:"n2", x:10, y:10, z:"t0", type:"helper", wires:[]},
|
||||
// Subflow
|
||||
{id:"s1", type:"subflow", name:"Subflow", info:"",
|
||||
in:[{
|
||||
x:10, y:10,
|
||||
wires:[ {id:"s1-n1"} ]
|
||||
}],
|
||||
out:[{
|
||||
x:10, y:10,
|
||||
wires:[ {id:"s1-n1", port:0} ]
|
||||
}]
|
||||
},
|
||||
{id:"s1-n1", x:10, y:10, z:"s1", type:"function",
|
||||
func:"msg.V = env.get('K'); return msg;",
|
||||
wires:[]}
|
||||
];
|
||||
helper.load(functionNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
n2.on("input", function(msg) {
|
||||
try {
|
||||
msg.should.have.property("V", "V1");
|
||||
done();
|
||||
}
|
||||
catch (e) {
|
||||
console.log(e);
|
||||
done(e);
|
||||
}
|
||||
});
|
||||
n1.receive({payload:"foo"});
|
||||
});
|
||||
});
|
||||
|
||||
it('should access typed value of env var', function(done) {
|
||||
var flow = [
|
||||
{id:"t0", type:"tab", label:"", disabled:false, info:""},
|
||||
{id:"n1", x:10, y:10, z:"t0", type:"subflow:s1",
|
||||
env: [
|
||||
{name: "KN", type: "num", value: "100"},
|
||||
{name: "KB", type: "bool", value: "true"},
|
||||
{name: "KJ", type: "json", value: "[1,2,3]"},
|
||||
{name: "Kb", type: "bin", value: "[65,65]"},
|
||||
{name: "Ke", type: "env", value: "KS"}
|
||||
],
|
||||
wires:[["n2"]]},
|
||||
{id:"n2", x:10, y:10, z:"t0", type:"helper", wires:[]},
|
||||
// Subflow
|
||||
{id:"s1", type:"subflow", name:"Subflow", info:"",
|
||||
in:[{
|
||||
x:10, y:10,
|
||||
wires:[ {id:"s1-n1"} ]
|
||||
}],
|
||||
out:[{
|
||||
x:10, y:10,
|
||||
wires:[ {id:"s1-n1", port:0} ]
|
||||
}],
|
||||
env: [
|
||||
{name: "KS", type: "str", value: "STR"}
|
||||
]
|
||||
},
|
||||
{id:"s1-n1", x:10, y:10, z:"s1", type:"function",
|
||||
func:"msg.VE = env.get('Ke'); msg.VS = env.get('KS'); msg.VN = env.get('KN'); msg.VB = env.get('KB'); msg.VJ = env.get('KJ'); msg.Vb = env.get('Kb'); return msg;",
|
||||
wires:[]}
|
||||
];
|
||||
helper.load(functionNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
n2.on("input", function(msg) {
|
||||
try {
|
||||
msg.should.have.property("VS", "STR");
|
||||
msg.should.have.property("VN", 100);
|
||||
msg.should.have.property("VB", true);
|
||||
msg.should.have.property("VJ", [1,2,3]);
|
||||
msg.should.have.property("Vb");
|
||||
should.ok(msg.Vb instanceof Buffer);
|
||||
msg.should.have.property("VE","STR");
|
||||
done();
|
||||
}
|
||||
catch (e) {
|
||||
done(e);
|
||||
}
|
||||
});
|
||||
n1.receive({payload:"foo"});
|
||||
});
|
||||
});
|
||||
|
||||
it('should overwrite env var of subflow template by env var of subflow instance', function(done) {
|
||||
var flow = [
|
||||
{id:"t0", type:"tab", label:"", disabled:false, info:""},
|
||||
{id:"n1", x:10, y:10, z:"t0", type:"subflow:s1",
|
||||
env: [
|
||||
{name: "K", type: "str", value: "V"}
|
||||
],
|
||||
wires:[["n2"]]},
|
||||
{id:"n2", x:10, y:10, z:"t0", type:"helper", wires:[]},
|
||||
// Subflow
|
||||
{id:"s1", type:"subflow", name:"Subflow", info:"",
|
||||
env: [
|
||||
{name: "K", type: "str", value: "TV"}
|
||||
],
|
||||
in:[{
|
||||
x:10, y:10,
|
||||
wires:[ {id:"s1-n1"} ]
|
||||
}],
|
||||
out:[{
|
||||
x:10, y:10,
|
||||
wires:[ {id:"s1-n1", port:0} ]
|
||||
}]
|
||||
},
|
||||
{id:"s1-n1", x:10, y:10, z:"s1", type:"function",
|
||||
func:"msg.V = env.get('K'); return msg;",
|
||||
wires:[]}
|
||||
];
|
||||
helper.load(functionNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
n2.on("input", function(msg) {
|
||||
try {
|
||||
msg.should.have.property("V", "V");
|
||||
done();
|
||||
}
|
||||
catch (e) {
|
||||
console.log(e);
|
||||
done(e);
|
||||
}
|
||||
});
|
||||
n1.receive({payload:"foo"});
|
||||
});
|
||||
});
|
||||
|
||||
it('should access env var of parent subflow template', function(done) {
|
||||
var flow = [
|
||||
{id:"t0", type:"tab", label:"", disabled:false, info:""},
|
||||
{id:"n1", x:10, y:10, z:"t0", type:"subflow:s1", wires:[["n2"]]},
|
||||
{id:"n2", x:10, y:10, z:"t0", type:"helper", wires:[]},
|
||||
// Subflow1
|
||||
{id:"s1", type:"subflow", name:"Subflow1", info:"",
|
||||
env: [
|
||||
{name: "K", type: "str", value: "V"},
|
||||
],
|
||||
in:[{
|
||||
x:10, y:10,
|
||||
wires:[ {id:"s1-n1"} ]
|
||||
}],
|
||||
out:[{
|
||||
x:10, y:10,
|
||||
wires:[ {id:"s1-n2", port:0} ]
|
||||
}]
|
||||
},
|
||||
{id:"s1-n1", x:10, y:10, z:"s1", type:"subflow:s2",
|
||||
wires:[["s1-n2"]]},
|
||||
{id:"s1-n2", x:10, y:10, z:"s1", type:"function",
|
||||
func:"return msg;", wires:[]},
|
||||
// Subflow2
|
||||
{id:"s2", type:"subflow", name:"Subflow2", info:"",
|
||||
in:[{
|
||||
x:10, y:10,
|
||||
wires:[ {id:"s2-n1"} ]
|
||||
}],
|
||||
out:[{
|
||||
x:10, y:10,
|
||||
wires:[ {id:"s2-n1", port:0} ]
|
||||
}]
|
||||
},
|
||||
{id:"s2-n1", x:10, y:10, z:"s2", type:"function",
|
||||
func:"msg.V = env.get('K'); return msg;",
|
||||
wires:[]}
|
||||
];
|
||||
helper.load(functionNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
n2.on("input", function(msg) {
|
||||
msg.should.have.property("V", "V");
|
||||
done();
|
||||
});
|
||||
n1.receive({payload:"foo"});
|
||||
});
|
||||
});
|
||||
|
||||
it('should access env var of parent subflow instance', function(done) {
|
||||
var flow = [
|
||||
{id:"t0", type:"tab", label:"", disabled:false, info:""},
|
||||
{id:"n1", x:10, y:10, z:"t0", type:"subflow:s1",
|
||||
env: [
|
||||
{name: "K", type: "str", value: "V"}
|
||||
],
|
||||
wires:[["n2"]]},
|
||||
{id:"n2", x:10, y:10, z:"t0", type:"helper", wires:[]},
|
||||
// Subflow1
|
||||
{id:"s1", type:"subflow", name:"Subflow1", info:"",
|
||||
in:[{
|
||||
x:10, y:10,
|
||||
wires:[ {id:"s1-n1"} ]
|
||||
}],
|
||||
out:[{
|
||||
x:10, y:10,
|
||||
wires:[ {id:"s1-n2", port:0} ]
|
||||
}]
|
||||
},
|
||||
{id:"s1-n1", x:10, y:10, z:"s1", type:"subflow:s2",
|
||||
wires:[["s1-n2"]]},
|
||||
{id:"s1-n2", x:10, y:10, z:"s1", type:"function",
|
||||
func:"return msg;", wires:[]},
|
||||
// Subflow2
|
||||
{id:"s2", type:"subflow", name:"Subflow2", info:"",
|
||||
in:[{
|
||||
x:10, y:10,
|
||||
wires:[ {id:"s2-n1"} ]
|
||||
}],
|
||||
out:[{
|
||||
x:10, y:10,
|
||||
wires:[ {id:"s2-n1", port:0} ]
|
||||
}]
|
||||
},
|
||||
{id:"s2-n1", x:10, y:10, z:"s2", type:"function",
|
||||
func:"msg.V = env.get('K'); return msg;",
|
||||
wires:[]}
|
||||
];
|
||||
helper.load(functionNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
n2.on("input", function(msg) {
|
||||
msg.should.have.property("V", "V");
|
||||
done();
|
||||
});
|
||||
n1.receive({payload:"foo"});
|
||||
});
|
||||
});
|
||||
|
||||
it('should access env var of tab', function(done) {
|
||||
var flow = [
|
||||
{id:"t0", type:"tab", label:"", disabled:false, info:"", env: [
|
||||
{name: "K", type: "str", value: "V"}
|
||||
]},
|
||||
{id:"n1", x:10, y:10, z:"t0", type:"subflow:s1", wires:[["n2"]]},
|
||||
{id:"n2", x:10, y:10, z:"t0", type:"helper", wires:[]},
|
||||
// Subflow
|
||||
{id:"s1", type:"subflow", name:"Subflow", info:"", env: [],
|
||||
in:[{
|
||||
x:10, y:10,
|
||||
wires:[ {id:"s1-n1"} ]
|
||||
}],
|
||||
out:[{
|
||||
x:10, y:10,
|
||||
wires:[ {id:"s1-n1", port:0} ]
|
||||
}]
|
||||
},
|
||||
{id:"s1-n1", x:10, y:10, z:"s1", type:"function",
|
||||
func:"msg.V = env.get('K'); return msg;",
|
||||
wires:[]}
|
||||
];
|
||||
helper.load(functionNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
n2.on("input", function(msg) {
|
||||
try {
|
||||
msg.should.have.property("V", "V");
|
||||
done();
|
||||
}
|
||||
catch (e) {
|
||||
console.log(e);
|
||||
done(e);
|
||||
}
|
||||
});
|
||||
n1.receive({payload:"foo"});
|
||||
});
|
||||
});
|
||||
|
||||
it('should access env var of group', function(done) {
|
||||
var flow = [
|
||||
{id:"t0", type:"tab", label:"", disabled:false, info:""},
|
||||
{id:"g1", z:"t0", type:"group", env:[
|
||||
{name: "K", type: "str", value: "V"}
|
||||
]},
|
||||
{id:"n1", x:10, y:10, z:"t0", g:"g1", type:"subflow:s1", wires:[["n2"]]},
|
||||
{id:"n2", x:10, y:10, z:"t0", type:"helper", wires:[]},
|
||||
// Subflow
|
||||
{id:"s1", type:"subflow", name:"Subflow", info:"", env: [],
|
||||
in:[{
|
||||
x:10, y:10,
|
||||
wires:[ {id:"s1-n1"} ]
|
||||
}],
|
||||
out:[{
|
||||
x:10, y:10,
|
||||
wires:[ {id:"s1-n1", port:0} ]
|
||||
}]
|
||||
},
|
||||
{id:"s1-n1", x:10, y:10, z:"s1", type:"function",
|
||||
func:"msg.V = env.get('K'); return msg;",
|
||||
wires:[]}
|
||||
];
|
||||
helper.load(functionNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
n2.on("input", function(msg) {
|
||||
try {
|
||||
msg.should.have.property("V", "V");
|
||||
done();
|
||||
}
|
||||
catch (e) {
|
||||
console.log(e);
|
||||
done(e);
|
||||
}
|
||||
});
|
||||
n1.receive({payload:"foo"});
|
||||
});
|
||||
});
|
||||
|
||||
it('should access env var of nested group', function(done) {
|
||||
var flow = [
|
||||
{id:"t0", type:"tab", label:"", disabled:false, info:""},
|
||||
{id:"g1", z:"t0", type:"group", env:[
|
||||
{name: "K", type: "str", value: "V"}
|
||||
]},
|
||||
{id:"g2", z:"t0", g:"g1", type:"group", env:[]},
|
||||
{id:"n1", x:10, y:10, z:"t0", g:"g2", type:"subflow:s1", wires:[["n2"]]},
|
||||
{id:"n2", x:10, y:10, z:"t0", type:"helper", wires:[]},
|
||||
// Subflow
|
||||
{id:"s1", type:"subflow", name:"Subflow", info:"", env: [],
|
||||
in:[{
|
||||
x:10, y:10,
|
||||
wires:[ {id:"s1-n1"} ]
|
||||
}],
|
||||
out:[{
|
||||
x:10, y:10,
|
||||
wires:[ {id:"s1-n1", port:0} ]
|
||||
}]
|
||||
},
|
||||
{id:"s1-n1", x:10, y:10, z:"s1", type:"function",
|
||||
func:"msg.V = env.get('K'); return msg;",
|
||||
wires:[]}
|
||||
];
|
||||
helper.load(functionNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
n2.on("input", function(msg) {
|
||||
try {
|
||||
msg.should.have.property("V", "V");
|
||||
done();
|
||||
}
|
||||
catch (e) {
|
||||
console.log(e);
|
||||
done(e);
|
||||
}
|
||||
});
|
||||
n1.receive({payload:"foo"});
|
||||
});
|
||||
});
|
||||
|
||||
it('should access NR_NODE_PATH env var within subflow instance', function(done) {
|
||||
var flow = [
|
||||
{id:"t0", type:"tab", label:"", disabled:false, info:""},
|
||||
{id:"n1", x:10, y:10, z:"t0", type:"subflow:s1",
|
||||
env: [], wires:[["n2"]]},
|
||||
{id:"n2", x:10, y:10, z:"t0", type:"helper", wires:[]},
|
||||
// Subflow
|
||||
{id:"s1", type:"subflow", name:"Subflow", info:"",
|
||||
in:[{
|
||||
x:10, y:10,
|
||||
wires:[ {id:"s1-n1"} ]
|
||||
}],
|
||||
out:[{
|
||||
x:10, y:10,
|
||||
wires:[ {id:"s1-n1", port:0} ]
|
||||
}]
|
||||
},
|
||||
{id:"s1-n1", x:10, y:10, z:"s1", type:"function",
|
||||
func:"msg.payload = env.get('NR_NODE_PATH'); return msg;",
|
||||
wires:[]}
|
||||
];
|
||||
helper.load(functionNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
n2.on("input", function(msg) {
|
||||
try {
|
||||
msg.should.have.property("payload", "t0/n1/s1-n1");
|
||||
done();
|
||||
}
|
||||
catch (e) {
|
||||
console.log(e);
|
||||
done(e);
|
||||
}
|
||||
});
|
||||
n1.receive({payload:"foo"});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
});
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user