UNPKG

siesta-lite

Version:

Stress-free JavaScript unit testing and functional testing tool, works in NodeJS and browsers

995 lines (768 loc) 32.8 kB
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>The source code</title> <link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" /> <script type="text/javascript" src="../resources/prettify/prettify.js"></script> <style type="text/css"> .highlight { display: block; background-color: #ddd; } </style> <script type="text/javascript"> function highlight() { document.getElementById(location.hash.replace(/#/, "")).className = "highlight"; } </script> </head> <body onload="prettyPrint(); highlight();"> <pre class="prettyprint lang-js">/* Siesta 5.6.1 Copyright(c) 2009-2022 Bryntum AB https://bryntum.com/contact https://bryntum.com/products/siesta/license */ Ext.define(&#39;Siesta.Recorder.UI.RecorderPanel&#39;, { extend : &#39;Ext.tree.Panel&#39;, alias : &#39;widget.recorderpanel&#39;, requires : [ &#39;Ext.grid.plugin.CellEditing&#39;, &#39;Siesta.Recorder.UI.ActionColumn&#39;, &#39;Siesta.Recorder.UI.ActionIconColumn&#39;, &#39;Siesta.Recorder.UI.TargetColumn&#39;, &#39;Siesta.Recorder.UI.ContextMenu&#39;, &#39;Siesta.Recorder.UI.Store.Action&#39; ], mixins : [ &#39;Siesta.Project.Browser.UI.CanCopyToClipboard&#39; ], buttonAlign : &#39;left&#39;, border : false, cls : &#39;siesta-recorderpanel&#39;, selModel : { mode : &#39;MULTI&#39; }, store : { type : &#39;actionstore&#39; }, rootVisible : false, viewConfig : { markDirty : false, stripeRows : false, allowCopy : true, plugins : { ptype : &#39;treeviewdragdrop&#39; } }, newActionDefaults : { action : &#39;click&#39; }, lines : false, test : null, recorder : null, project : null, domContainer : null, recorderConfig : null, editing : null, bufferedRenderer : false, enableColumnMove : false, enableContextMenu : true, enableEditing : true, showToolbars : true, playbackOnly : false, <span id='global-event-startrecord'> /** </span> * @event startrecord * Fires when a recording is started * @param {Siesta.Recorder.UI.RecorderPanel} this * @param {Siesta.Test} test The test instance to which the recorder is attached */ <span id='global-event-stoprecord'> /** </span> * @event stoprecord * Fires when a recording is stopped * @param {Siesta.Recorder.UI.RecorderPanel} this */ <span id='global-event-play'> /** </span> * @event play * Fires when a recording is being played back * @param {Siesta.Recorder.UI.RecorderPanel} this */ initComponent : function () { var me = this; var R = Siesta.Resource(&#39;Siesta.Recorder.UI.RecorderPanel&#39;); var recorderConfig = this.recorderConfig || {}; if (!this.playbackOnly) { var recorderClass = this.project &amp;&amp; this.project.recorderClass || recorderConfig.recorderClass || Siesta.Recorder.ExtJS; if (typeof recorderClass === &#39;string&#39;) { recorderClass = Joose.S.strToClass(recorderClass); } var recorder = me.recorder = me.recorder || new recorderClass(recorderConfig); recorder.on(&quot;actionadd&quot;, this.onActionAdded, this) recorder.on(&quot;actionremove&quot;, this.onActionRemoved, this) recorder.on(&quot;actionupdate&quot;, this.onActionUpdated, this) recorder.on(&quot;clear&quot;, this.onRecorderClear, this) recorder.on(&quot;start&quot;, this.onRecorderStart, this) recorder.on(&quot;stop&quot;, this.onRecorderStop, this) } if (this.enableEditing) { me.plugins = me.plugins ? [].concat(me.plugins) : []; me.editing = me.editing || new Ext.grid.plugin.CellEditing({ clicksToEdit : 1 }); me.editing.on({ beforeedit : me.onBeforeEdit, validateedit : me.onValidateEdit, edit : me.afterEdit, canceledit : me.afterEdit, scope : me }); this.relayEvents(me.editing, [&#39;beforeedit&#39;, &#39;afteredit&#39;, &#39;validateedit&#39;]) me.plugins.push(me.editing); this.on(&#39;hide&#39;, function () { if (this.editing) { this.editing.completeEdit(); } }); this.on({ afteredit : this.onAfterEdit, validateedit : this.onAfterEdit, canceledit : this.onAfterEdit, scope : this, buffer : 200 }); } Ext.applyIf(me, { columns : [ { xtype : &#39;recorderactioniconcolumn&#39; }, { xtype : &#39;recorderactioncolumn&#39; }, { xtype : &#39;targetcolumn&#39;, highlightTarget : this.highlightTarget.bind(this) } ].concat( { header : R.get(&#39;offsetColumnHeader&#39;), dataIndex : &#39;__offset__&#39;, width : 60, sortable : false, menuDisabled : true, tdCls : &#39;siesta-recorderpanel-offsetcolumn&#39;, renderer : function (value, meta, record) { var target = record.getTarget() if (target &amp;&amp; target.offset) { return target.offset + &#39;&lt;div class=&quot;siesta-recorderpanel-clearoffset fa fa-close&quot;&gt;&lt;/div&gt;&#39; } }, editor : &#39;textfield&#39; } ).concat({ xtype : &#39;actioncolumn&#39;, width : 55, align : &#39;center&#39;, sortable : false, menuDisabled : true, tdCls : &#39;siesta-recorderpanel-actioncolumn&#39;, items : [ { iconCls : &#39;step-icon fa fa-close icon-delete-row&#39;, tooltip : &#39;Delete this action&#39;, handler : this.onDeleteStepClick, scope : this }, { iconCls : &#39;step-icon fa fa-play icon-play-row&#39;, tooltip : &#39;Play this action&#39;, handler : this.onPlaySingleStepClick, scope : this }, { iconCls : &#39;step-icon fa fa-forward icon-play-from-row&#39;, tooltip : &#39;Play from this action&#39;, handler : this.onPlayFromStepClick, scope : this } ] }) }); if (this.showToolbars) { me.createToolbars(); } me.callParent(); this.mon(Ext.getBody(), &#39;mousedown&#39;, this.onBodyMouseDown, this, { delegate : &#39;.target-inspector-label&#39; }) if (this.enableContextMenu) { this.contextMenu = new Siesta.Recorder.UI.ContextMenu({ panel : this }); } }, onAfterEdit : function () { if (!this.editing.editing) { this.hideHighlighter(); } }, onBodyMouseDown : function (e, t) { var focusedEl = document.activeElement; if (Ext.fly(focusedEl).up(&#39;.siesta-targeteditor&#39;)) { e.stopEvent(); e.preventDefault(); focusedEl.value = Ext.htmlDecode(t.innerHTML); } }, onRecorderStart : function () { this.fireEvent(&#39;startrecord&#39;, this, this.test); this.addCls(&#39;recorder-recording&#39;); }, onRecorderStop : function () { this.fireEvent(&#39;stoprecord&#39;, this); this.removeCls(&#39;recorder-recording&#39;); }, hideHighlighter : function () { if (this.test &amp;&amp; this.domContainer) { this.domContainer.clearHighlight(); } }, highlightTarget : function (target, offset) { if (!target) { // Pass no target =&gt; simply hide highlighter this.hideHighlighter(); return; } var test = this.test; if (!test) { this.hideHighlighter(); return { success : true } } var R = Siesta.Resource(&#39;Siesta.Recorder.UI.RecorderPanel&#39;); var resolved, el try { resolved = this.test.normalizeElement(target, true, true, true); el = resolved.el } catch (e) { // sizzle may break on various characters in the query (=, $, etc) } finally { if (!el) { return { success : false, warning : R.get(&#39;queryMatchesNothing&#39;) } } } var warning = resolved.matchingMultiple ? R.get(&#39;queryMatchesMultiple&#39;) : &#39;&#39; if (test.isElementVisible(el) &amp;&amp; this.domContainer) { var pointToVisualize = Ext.isArray(target) ? target : (offset || [&#39;50%&#39;, &#39;50%&#39;]); this.domContainer.highlightTarget(el, &#39;&lt;span class=&quot;target-inspector-label&quot;&gt;&#39; + target + &#39;&lt;/span&gt;&#39;, pointToVisualize); } else { // If target was provided but no element could be located, return false so // caller can get a hint there is potential trouble warning = warning || R.get(&#39;noVisibleElsFound&#39;) } return { success : !warning, message : warning }; }, createToolbars : function () { var me = this; var R = Siesta.Resource(&#39;Siesta.Recorder.UI.RecorderPanel&#39;); me.dockedItems = [ { xtype : &#39;toolbar&#39;, padding : &#39;3 5&#39;, cls : &#39;siesta-toolbar recorder-toolbar&#39;, dock : &#39;top&#39;, height : 45, style : &#39;border-color:transparent&#39;, items : [ { xtype : &#39;textfield&#39;, itemId : &#39;recording-name&#39;, fieldLabel : &#39;Name&#39;, height : 30, width : 200, labelWidth : 50, value : R.get(&#39;newRecording&#39;) }, { xtype : &#39;textfield&#39;, itemId : &#39;pageUrl&#39;, height : 30, flex : 1, labelWidth : 70, fieldLabel : R.get(&#39;pageUrl&#39;), enableKeyEvents : true, listeners : { keyup : function (field, e) { if (e.getKey() == e.ENTER) { this.onPageUrlFieldEnterKey(); } }, scope : this } } ] }, { xtype : &#39;toolbar&#39;, cls : &#39;siesta-toolbar siesta-recorder-button-toolbar&#39;, dock : &#39;top&#39;, height : 45, defaults : { scale : &#39;medium&#39;, tooltipType : &#39;title&#39;, scope : me }, items : [ { iconCls : &#39;fa fa-circle icon-record&#39;, action : &#39;recorder-start&#39;, cls : &#39;recorder-tool&#39;, whenIdle : true, tooltip : R.get(&#39;recordTooltip&#39;), handler : me.onRecordClick }, { iconCls : &#39;fa fa-square&#39;, cls : &#39;recorder-tool&#39;, action : &#39;recorder-stop&#39;, handler : me.stop, tooltip : R.get(&#39;stopTooltip&#39;) }, { iconCls : &#39;fa fa-play&#39;, action : &#39;recorder-play&#39;, cls : &#39;recorder-tool&#39;, handler : me.onPlayClick, tooltip : R.get(&#39;playTooltip&#39;) }, { iconCls : &#39;fa fa-close&#39;, action : &#39;recorder-remove-all&#39;, cls : &#39;recorder-tool icon-clear&#39;, handler : function () { var me = this; if (me.store.getCount() === 0) return; Ext.Msg.confirm(R.get(&#39;removeAllPromptTitle&#39;), R.get(&#39;removeAllPromptMessage&#39;), function (btn) { if (btn == &#39;yes&#39;) { // process text value and close... me.clear(); } }); }, tooltip : R.get(&#39;clearTooltip&#39;) }, { iconCls : &#39;fa fa-plus&#39;, action : &#39;recorder-add-step&#39;, tooltip : R.get(&#39;addNewTooltip&#39;), cls : &#39;recorder-tool&#39;, tooltipType : &#39;title&#39;, scope : me, handler : function () { var store = me.store; var selected = me.getSelectionModel().selected.first(); var model = new store.model(Ext.apply({}, this.newActionDefaults)); if (selected &amp;&amp; selected.isVisible()) { selected.parentNode.insertChild(selected.get(&#39;index&#39;) + 1, model); } else { store.getRootNode().appendChild(model); } me.editing.startEdit(model, 1); } }, &#39;-&gt;&#39;, { xtype : &#39;splitbutton&#39;, text : &#39;Show source&#39;, cls : &#39;recorder-tool&#39;, action : &#39;recorder-generate-code&#39;, handler : this.onGenerateCodeClick, scope : this, menu : { items : [ { text : R.get(&#39;showSourceInNewWindow&#39;), scope : this, handler : function () { var win = window.open(null); var body = win.document.body; var recordingName = this.getRecordingName(); var code = this.store.generateCode(recordingName); body.innerHTML = &#39;&lt;pre&gt;&#39; + code + &#39;&lt;/pre&gt;&#39;; } } ] } }, window.document.queryCommandSupported(&#39;copy&#39;) ? { iconCls : &#39;fa fa-clipboard&#39;, cls : &#39;recorder-tool&#39;, action : &#39;recorder-copy-to-clipboard&#39;, handler : me.onCopyToClipboard, tooltip : &#39;Copy generated code to clipboard&#39; } : null, me.closeButton ] }]; me.bbar = { xtype : &#39;component&#39;, cls : &#39;cheatsheet&#39;, // height : 70, html : this.getCheatSheetText() }; }, getCheatSheetText : function () { var text = &#39;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;cheatsheet-type&quot;&gt;CSS Query:&lt;/td&gt;&lt;td class=&quot;cheatsheet-sample&quot;&gt; .x-btn&lt;/td&gt;&lt;/tr&gt;&#39; if (this.recorder instanceof Siesta.Recorder.ExtJS) { text += &#39;&lt;tr&gt;&lt;td class=&quot;cheatsheet-type&quot;&gt;Component Query:&lt;/td&gt;&lt;td class=&quot;cheatsheet-sample&quot;&gt; &amp;gt;&amp;gt;toolbar button&lt;/td&gt;&lt;/tr&gt;&#39; + &#39;&lt;tr&gt;&lt;td class=&quot;cheatsheet-type&quot;&gt;Composite Query:&lt;/td&gt;&lt;td class=&quot;cheatsheet-sample&quot;&gt; toolbar =&amp;gt; .x-btn&lt;/td&gt;&lt;/tr&gt;&#39; } text += &#39;&lt;/table&gt;&#39; return text }, // Attach to a test (and optionally a specific iframe, only used for testing) attachTo : function (test, iframe) { var me = this; var doClear = me.test &amp;&amp; me.test.url !== test.url; var recorder = this.recorder this.setTest(test); var recWindow = (iframe &amp;&amp; iframe.contentWindow) || (test.scopeProvider &amp;&amp; test.scopeProvider.scope); if (recWindow) { me.recorder.attach(recWindow); } if (doClear) me.clear(); }, onPageUrlFieldEnterKey : function () { var descriptor = this.getRecorderTestDescriptor(); if (descriptor &amp;&amp; (descriptor.hostPageUrl || descriptor.pageUrl)) { this.domContainer.expand(); this.project.startSingle(descriptor); this.project.on(&#39;teststart&#39;, function (event, test) { if (test.url === descriptor.url) { // To ensure test is visible this.fireEvent(&#39;play&#39;, this, test, 0); } }, this, { single : true }); } }, onRecordClick : function () { var test = this.test if (!test) return; var R = Siesta.Resource(&#39;Siesta.Recorder.UI.RecorderPanel&#39;); var descriptor = this.getRecorderTestDescriptor(); if (descriptor) { if (this.isRecording()) { this.stop(); } else { var project = this.project; var pageUrl = this.down(&#39;#pageUrl&#39;).getValue(); // Grab a new test reference from project in case test was rerun while recorder was open test = project.getTestByURL(test.url) || test; this.setTest(test); var scopeProvider = test.scopeProvider; // If we&#39;re recording a new URL, or the test has no window (already was cleaned up) - // first launch the test to load the URL into the test iframe if (!scopeProvider || pageUrl &amp;&amp; scopeProvider.sourceURL !== pageUrl) { project.on(&#39;teststart&#39;, function (event, test) { if (test.url === descriptor.url) { this.attachTo(test); this.recorder.start(); } }, this, { single : true }); project.startSingle(descriptor); } else if (test &amp;&amp; test.global) { this.attachTo(test); this.recorder.start(); } } } else { Ext.Msg.alert(&#39;Error&#39;, R.get(&#39;noTestStarted&#39;)) } }, onPlayClick : function () { var me = this; var descriptor = this.getRecorderTestDescriptor(); if (descriptor) { me.stop(); var testStartListener = function (ev, runningTestInstance) { if (runningTestInstance.url === descriptor.url) { runningTestInstance.on(&#39;beforetestfinalizeearly&#39;, testFinalizeListener, null, { single : true }); } }; var testFinalizeListener = function (ev, test2) { // important, need to update our reference to the test me.setTest(test2); // Run test first, and before it ends - fire off the recorded steps me.playSteps(); }; var project = me.project; project.on(&#39;teststart&#39;, testStartListener, null, { single : true }); if (this.store.getCount() &gt; 0) { this.scrollRecordIntoView( this.store.getAt(0)); } project.startSingle(descriptor); } }, onCopyToClipboard : function (button) { var recordingName = this.getRecordingName() var success = this.copyToClipboard(this.store.generateCode(recordingName)) Ext.Msg.show({ animateTarget : button.getEl(), message : success ? &#39;Code copied successfully&#39; : &#39;Something went wrong, code was not copied&#39;, title : &#39;Copy to clipboard&#39;, modal : false }) setTimeout(function () { Ext.Msg.hide() }, 800) }, stop : function () { this.recorder.stop(); }, clear : function () { this.recorder.clear(); }, onRecorderClear : function () { this.store.getRootNode().removeAll(); }, getRecorderTestDescriptor : function () { var project = this.project; var pageUrl = this.down(&#39;#pageUrl&#39;).getValue(); var test = this.test; var descriptor = test &amp;&amp; project.getScriptDescriptor(test.url); var testHostUrl = descriptor &amp;&amp; project.getDescriptorConfig(descriptor, &#39;pageUrl&#39;); // If user changes target page URL - use an empty virtual test descriptor return pageUrl &amp;&amp; testHostUrl !== pageUrl ? { url : &#39;/&#39;, enablePageRedirect : true, testCode : &#39;StartTest(function(t) {})&#39;, pageUrl : pageUrl } : (test ? descriptor : null); }, setTest : function (test) { if (this.test) { this.test.un(&#39;beforechainstep&#39;, this.onBeforeActionExecute, this) } this.test = test if (test) { test.on(&#39;beforechainstep&#39;, this.onBeforeActionExecute, this) var field = this.down(&#39;#pageUrl&#39;) if (field &amp;&amp; test.scopeProvider) field.setValue(test.scopeProvider.sourceURL || &#39;&#39;) test.on(&#39;testfinalize&#39;, function () { if (test.scopeProvider &amp;&amp; test.scopeProvider.crossOriginFailed) { Ext.Msg.alert(&#39;Error&#39;, &#39;Recorder attached to the page from different origin - can\&#39;t record anything&#39;) } }) } }, generateSteps : function (events) { var me = this; var t = me.test; var steps = (events || this.store.getRange()).map(function (action, index) { if (action.isLeaf()) { var step = action.asStep(t); // wait for targets when playing entire test, // and // when playing actions manually from a certain step, wait for all steps but the first one if (action.getTarget()) step.waitForTarget = !events || index &gt; 0; return [ function (next) { index = events ? me.store.indexOf(action) : index; t.fireEvent(&#39;beforechainstep&#39;, t, index, step); next(); }, step ] } else { return me.generateSteps(action.childNodes); } }); return steps; }, onBeforeActionExecute : function(event, test, index, step) { var count = this.store.getCount(); this.getSelectionModel().select(index); if (index &lt; count - 2) { this.scrollRecordIntoView( this.store.getAt(index + 2)); } }, onActionAdded : function (event, action) { var root = this.store.getRootNode(); var targetParent = (root.lastChild &amp;&amp; root.lastChild.parentNode) || root; var newRecord = targetParent.appendChild(action); this.scrollRecordIntoView(newRecord) }, onActionRemoved : function (event, action) { this.store.getNodeById(action.id).remove(); }, onActionUpdated : function (event, action) { var model = this.store.getNodeById(action.id); model.callJoined(&#39;afterEdit&#39;, [[&#39;target&#39;, &#39;action&#39;, &#39;__offset__&#39;]]) }, getActions : function (asJooseInstances) { var actionModels = this.store.getRange() return asJooseInstances ? Ext.Array.pluck(actionModels, &#39;data&#39;) : actionModels }, onDestroy : function () { if (this.recorder) { this.recorder.stop(); } this.callParent(arguments); }, scrollRecordIntoView : function (record) { if (this.view.rendered) { this.ensureVisible(record, { animate : { duration : 100 } }); } }, onBeforeEdit : function (cellEditing, editingContext) { var column = editingContext.column; var editor; if (column.xtype === &quot;targetcolumn&quot;) { cellEditing.completeEdit(); column.setTargetEditor(editingContext.record); editingContext.value = editingContext.record.get(column.dataIndex); } else { editor = editingContext.column.getEditor(); if (editor.xtype === &#39;typeeditor&#39;) { // Can&#39;t populate until we have a test bound editor.populate(this.test); } } if (editingContext.column.dataIndex === &#39;target&#39; &amp;&amp; this.domContainer) { this.domContainer.startInspection(false); } // Offset only relevant for mouseinput actions return editingContext.field !== &#39;__offset__&#39; || editingContext.record.isMouseAction(); }, afterEdit : function (plug, e) { if (e.field === &#39;action&#39;) { var store = e.column.field.store; store.clearFilter(); if (store.getById(e.value).get(&#39;type&#39;) !== store.getById(e.originalValue).get(&#39;type&#39;)) { e.record.resetValues(); } } }, onValidateEdit : function (plug, e) { var value = e.value; if (e.field === &#39;action&#39; &amp;&amp; !value) return false; if (e.field === &#39;__offset__&#39;) { e.cancel = true; if (value) { var parsed = e.record.parseOffset(value); if (parsed) { e.record.setTargetOffset(parsed); } } else { e.record.clearTargetOffset(); } } else if (e.column.getEditor().applyChanges) { e.cancel = true; e.column.getEditor().applyChanges(e.record); } // Trigger manual refresh of node when &#39;set&#39; operation is more complex if (e.cancel) { this.afterEdit(plug, e); this.getView().refreshNode(e.record); } }, afterRender : function () { this.callParent(arguments); var view = this.getView(); view.el.on({ mousedown : function (e, t) { var record = view.getRecord(view.findItemByChild(t)); record.clearTargetOffset() view.refreshNode(record); e.stopEvent(); }, delegate : &#39;.siesta-recorderpanel-clearoffset&#39; }) }, isRecording : function () { return this.recorder.active; }, onGenerateCodeClick : function () { var R = Siesta.Resource(&#39;Siesta.Recorder.UI.RecorderPanel&#39;); var win = new Ext.Window({ title : R.get(&#39;codeWindowTitle&#39;), layout : &#39;fit&#39;, id : &#39;codeWindow&#39;, itemId : &#39;codeWindow&#39;, cls : &#39;si-recorder-sourcewindow&#39;, height : 400, width : 600, autoScroll : true, autoShow : true, constrain : true, modal : true, closeAction : &#39;destroy&#39;, stateful : true, items : { xtype : &#39;jseditor&#39;, mode : &#39;text/javascript&#39; } }); var field = win.items.first(); var recordingName = this.getRecordingName(); var code = this.store.generateCode(recordingName); field.setValue(code); field.editor.focus(); }, getRecordingName : function () { return this.down(&#39;#recording-name&#39;).getValue(); }, onDeleteStepClick : function (grid, rowIndex, colIndex, item, e, record) { this.editing &amp;&amp; this.editing.completeEdit(); record.remove(); }, onPlaySingleStepClick : function (cmp, rowIndex) { this.playSingle(rowIndex); }, onPlayFromStepClick : function (cmp, rowIndex) { this.playFromStep(rowIndex); }, playSingle : function(index) { if (this.test) { var action = this.store.getAt(index); var steps = this.generateSteps([action]); this.playSteps(steps); } }, playFromStep : function (startIndex) { if (this.test) { this.playRange(startIndex); } }, playRange : function (startIndex, endIndex) { if (this.test) { var steps = this.generateSteps(this.store.getRange(startIndex, endIndex)); this.playSteps(steps); } }, playSteps : function (steps) { var test = this.test if (test) { var me = this; steps = steps || this.generateSteps(); steps = steps instanceof Array ? steps : [ steps ]; test.chain( function (next) { me.fireEvent(&#39;play&#39;, me, test); next(); }, steps, function (next) { me.fireEvent(&#39;stop&#39;, me, test); next(); } ); } }, getRecorder : function () { return this.recorder; } }); </pre> </body> </html>