diff --git a/packages/selenium-ide/src/neo/IO/legacy/goto-elimination.js b/packages/selenium-ide/src/neo/IO/legacy/goto-elimination.js new file mode 100644 index 000000000..aadb19bf7 --- /dev/null +++ b/packages/selenium-ide/src/neo/IO/legacy/goto-elimination.js @@ -0,0 +1,367 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you 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. + +// GENENRAL NOTE: procedure is non-mutable while p is +// Do not mutate statements, create new references +// Don't break goto references, you do not need to change them anyway (just their location) +export function eliminate(procedure) { + let p = [...procedure]; + + // Change all gotos to conditionals + const unconditionalGotos = p.filter(statement => (statement.command === "goto" || statement.command === "gotoIf")); + unconditionalGotos.forEach(goto => { + const index = p.indexOf(goto); + p.splice(index, 1, ...transformToConditional(goto)); + }); + + const labels = p.filter(statement => statement.command === "label"); + const gotos = p.filter(statement => statement.command === "goto"); + + // start eliminating + while (gotos.length) { + const goto = gotos[0]; + const label = findMatchingLabel(labels, goto); + + const relationship = relation(p, goto, label); + if (relationship === Relationship.IndirectlyRelated) { + // Make goto and label directly related + // Ignoring switch and else statements + p = transformOutward(p, goto); + } else if (relationship === Relationship.DirectlyRelated) { + // Make goto and label siblings + if (calculateLevel(p, goto) > calculateLevel(p, label)) { + p = transformOutward(p, goto); + } else { + if (p.indexOf(goto) > p.indexOf(label)) { + p = lift(p, goto, label); + } + // Inward transformation + p = transformInward(p, goto, label); + } + } else { + // goto and label are siblings + + // eliminate goto + p = eliminateGoto(p, goto, label); + // remove it from the list of gotos + gotos.shift(); + } + } + + return optimize(eliminateLabels(p)); +} + +export function transformToConditional(goto) { + return ([ + { + command: "if", + target: goto.command === "gotoIf" ? goto.target : "true" + }, + { + command: "goto", + target: goto.command === "gotoIf" ? goto.value : goto.target + }, + { + command: "end" + } + ]); +} + +function optimize(procedure) { + return procedure.filter((statement, i) => { + if (isBlock(statement)) return !isBlockEnd(procedure[i + 1]); + if (isBlockEnd(statement)) return !isBlock(procedure[i - 1]); + return true; + }); +} + +export function eliminateLabels(procedure) { + return procedure.filter(s => s.command !== "label"); +} + +export function eliminateGoto(procedure, goto, label) { + // only for siblings + const p = [...procedure]; + const gotoIndex = procedure.indexOf(goto), labelIndex = procedure.indexOf(label); + if (gotoIndex < labelIndex) { + // eliminate using conditional + const ifIndex = gotoIndex - 1; + p[ifIndex] = Object.assign({...p[ifIndex]}, {target: `!${p[ifIndex].target}`}); + p.splice(labelIndex, 0, { command: "end" }); + p.splice(gotoIndex, 2); + } else { + // eliminate using do while + const ifIndex = gotoIndex - 1; + p.splice(ifIndex, 3, { command: "endDo", target: p[ifIndex].target }); + p.splice(labelIndex, 0, { command: "do" }); + } + return p; +} + +export function transformOutward(procedure, goto) { + const block = findEnclosingBlock(procedure, goto); + const end = findBlockClose(procedure, block); + const p = [...procedure]; + if (block.command !== "if") { + // outward loop movement + transformOutwardLoop(p, goto, block, end); + } else { + // outward conditional movement + transformOutwardConditional(p, goto, block, end); + } + return p; +} + +function transformOutwardLoop(p, goto, block, end) { + const ifIndex = p.indexOf(goto) - 1; + p.splice(ifIndex, 3, + { command: "store", target: p[ifIndex].target, value: goto.target }, + { command: "if", target: `\${${goto.target}}` }, + { command: "break" }, + { command: "end" } + ); + const endIndex = p.indexOf(end); + p.splice(endIndex + 1, 0, + { command: "if", target: `\${${goto.target}}` }, + goto, + { command: "end" } + ); +} + +function transformOutwardConditional(p, goto, block, end) { + const ifIndex = p.indexOf(goto) - 1; + p.splice(ifIndex, 3, + { command: "store", target: p[ifIndex].target, value: goto.target }, + { command: "if", target: `!\${${goto.target}}` } + ); + const endIndex = p.indexOf(end); + p.splice(endIndex + 1, 0, + { command: "end" }, + { command: "if", target: `\${${goto.target}}` }, + goto, + { command: "end" } + ); +} + +export function transformInward(procedure, goto, label) { + const block = findFirstEnclosingBlock(procedure, procedure.indexOf(goto), label); + const p = [...procedure]; + if (block.command !== "else") { + // outward loop movement + transformInwardLoopConditional(p, goto, block, label); + } else { + // outward conditional movement + transformInwardElseConditional(p, goto, block, label); + } + return p; +} + +function transformInwardLoopConditional(p, goto, block, label) { + const ifIndex = p.indexOf(goto) - 1; + p.splice(ifIndex, 3, + { command: "store", target: p[ifIndex].target, value: goto.target }, + { command: "if", target: `!\${${goto.target}}` } + ); + const blockIndex = p.indexOf(block); + p.splice(blockIndex, 1, + { command: "end" }, + { command: block.command, target: `\${${goto.target}} || ${block.target}` }, + { command: "if", target: `\${${goto.target}}` }, + goto, + { command: "end" } + ); + const labelIndex = p.indexOf(label); + + // make sure that the next time the while condition isn't shorted + if (block.command === "while") { + p.splice(labelIndex + 1, 0, + { command: "store", target: "false", value: goto.target } + ); + } + return p; +} + +function transformInwardElseConditional() { + throw new Error("not implemented"); +} + +export function lift(procedure, goto, label) { + const p = [ + { command: "store", target: "false", value: goto.target }, + ...procedure + ]; + const labelIndex = p.indexOf(label); + p.splice(labelIndex, 0, + { + command: "do" + }, + { + command: "if", + target: `\${${goto.target}}` + }, + goto, + { + command: "end" + } + ); + const ifIndex = p.lastIndexOf(goto) - 1; + p.splice(ifIndex, 3, + { + command: "store", + target: "condition", + value: goto.target + }, + { + command: "endDo", + target: `\${${goto.target}}` + } + ); + return p; +} + +function findFirstEnclosingBlock(procedure, offset, statement) { + const blocks = []; + for (let i = offset; i < procedure.length; i++) { + const block = procedure[i]; + if (isBlock(block)) { + blocks.push(block); + } else if (isBlockEnd(block)) { + blocks.pop(block); + } else if (block === statement) { + return blocks[0]; + } + } +} + +function findEnclosingBlock(procedure, goto) { + // remember to skip the enclosing if + for (let i = procedure.indexOf(goto) - 2; i >= 0; i--) { + if (isBlock(procedure[i])) return procedure[i]; + } +} + +function findBlockClose(procedure, block) { + let level = 1, index = procedure.indexOf(block); + while (level !== 0) { + index++; + level = levelIncrement(procedure[index], level); + } + return procedure[index]; +} + +function findMatchingLabel(listOfLabels, goto) { + return listOfLabels.find(statement => (statement.command === "label" && statement.target === goto.target)); +} + +function isBlock(statement) { + switch(statement.command) { + case "if": + case "do": + case "while": + return true; + default: + return false; + } +} + +function isBlockEnd(statement) { + switch(statement.command) { + case "end": + case "endDo": + return true; + default: + return false; + } +} + +function levelIncrement(statement, level = 0) { + if (isBlock(statement)) { + level++; + } else if (isBlockEnd(statement)) { + level--; + } + return level; +} + +function calculateLevel(procedure, statement) { + // ignore the goto condition in terms of level + let level = statement.command === "goto" ? -1 : 0; + for (let index = 0; index < procedure.length; index++) { + level = levelIncrement(procedure[index], level); + if (procedure[index] === statement) return level; + } +} + +function isConditionalGoto(procedure, goto) { + const index = procedure.indexOf(goto); + return (procedure[index].command === "goto" && procedure[index - 1].command === "if" && procedure[index + 1].command === "end"); +} + +// for this calculation a tree would've been a better data structure +export function relation(procedure, goto, label) { + let level, first, outOfBlock; + for (let index = 0; index < procedure.length; index++) { + const statement = procedure[index]; + // if its a conditional goto, decrease the level, and skip the end + if (isConditionalGoto(procedure, statement)) { + level--; + index++; + } else { + level = levelIncrement(statement, level); + } + // 0 is truthy + if (first !== undefined && outOfBlock === undefined && level < first) { + outOfBlock = level; + } else if (first !== undefined && outOfBlock !== undefined && level > outOfBlock && !isBlock(statement)) { + return Relationship.IndirectlyRelated; + } + + if (statement === goto || statement === label) { + if (first === undefined) { + // matched the first one + first = level; + } else if (first !== undefined) { + // matched the second one + if (level === first) { + return Relationship.Siblings; + } else { + return Relationship.DirectlyRelated; + } + } + } + } +} + +export const Relationship = { + Siblings: "siblings", + DirectlyRelated: "related", + IndirectlyRelated: "unrelated" +}; + +export function prettyPrint(procedure) { + let level = 0; + return procedure.map(statement => { + if (isBlockEnd(statement)) { + level--; + } + let r = `${" ".repeat(level)}${statement.command} ${statement.target || ""}`; + if (isBlock(statement)) { + level++; + } + return r; + }).join("\n"); +} diff --git a/packages/selenium-ide/src/neo/IO/legacy/migrate.js b/packages/selenium-ide/src/neo/IO/legacy/migrate.js index 8ea7bca98..d6d4b1fa2 100644 --- a/packages/selenium-ide/src/neo/IO/legacy/migrate.js +++ b/packages/selenium-ide/src/neo/IO/legacy/migrate.js @@ -19,6 +19,7 @@ import convert from "xml-js"; import xmlescape from "xml-escape"; import xmlunescape from "unescape"; import JSZip from "jszip"; +import { eliminate } from "./goto-elimination"; export function migrateProject(zippedData) { return JSZip.loadAsync(zippedData).then(zip => { @@ -100,13 +101,13 @@ export function migrateTestCase(data) { { id: data, name: result.html.body.table.thead.tr.td._text, - commands: result.html.body.table.tbody.tr.filter(row => (row.td[0]._text && !/^wait/.test(row.td[0]._text))).map(row => ( + commands: eliminate(result.html.body.table.tbody.tr.filter(row => (row.td[0]._text && !/^wait/.test(row.td[0]._text))).map(row => ( { command: row.td[0]._text && row.td[0]._text.replace("AndWait", ""), target: xmlunescape(parseTarget(row.td[1])), value: xmlunescape(row.td[2]._text || "") } - )) + ))) } ], suites: [] diff --git a/packages/selenium-ide/src/neo/__test__/IO/legacy/goto-elimination.spec.js b/packages/selenium-ide/src/neo/__test__/IO/legacy/goto-elimination.spec.js new file mode 100644 index 000000000..bd8cb585c --- /dev/null +++ b/packages/selenium-ide/src/neo/__test__/IO/legacy/goto-elimination.spec.js @@ -0,0 +1,1051 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you 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. + +import { + transformToConditional, + eliminateGoto, + eliminateLabels, + transformOutward, + transformInward, + lift, + relation, + Relationship +} from "../../../IO/legacy/goto-elimination"; + +describe("goto conditional conversion", () => { + it("should convert unconditional goto to conditional goto", () => { + const procedure = [ + { + command: "goto", + target: "label_1" + } + ]; + expect(transformToConditional(procedure[0])).toEqual([ + { + command: "if", + target: "true" + }, + { + command: "goto", + target: "label_1" + }, + { + command: "end" + } + ]); + }); + it("should convert compound goto to conditional goto", () => { + const procedure = [ + { + command: "gotoIf", + target: "condition", + value: "label_1" + } + ]; + expect(transformToConditional(procedure[0])).toEqual([ + { + command: "if", + target: "condition" + }, + { + command: "goto", + target: "label_1" + }, + { + command: "end" + } + ]); + }); +}); + +describe("goto elimination transformation", () => { + it("should eliminate goto before label statement", () => { + const procedure = [ + { + command: "statement" + }, + { + command: "if", + target: "true" + }, + { + command: "goto", + target: "label_1" + }, + { + command: "end" + }, + { + command: "statement" + }, + { + command: "statement" + }, + { + command: "statement" + }, + { + command: "label", + target: "label_1" + }, + { + command: "statement" + } + ]; + expect(eliminateGoto(procedure, procedure[2], procedure[7])).toEqual([ + { + command: "statement" + }, + { + command: "if", + target: "!true" + }, + { + command: "statement" + }, + { + command: "statement" + }, + { + command: "statement" + }, + { + command: "end" + }, + { + command: "label", + target: "label_1" + }, + { + command: "statement" + } + ]); + }); + it("should eliminate goto after label statement", () => { + const procedure = [ + { + command: "statement" + }, + + { + command: "statement" + }, + { + command: "label", + target: "label_1" + }, + { + command: "statement" + }, + { + command: "statement" + }, + { + command: "if", + target: "true" + }, + { + command: "goto", + target: "label_1" + }, + { + command: "end" + }, + { + command: "statement" + } + ]; + expect(eliminateGoto(procedure, procedure[6], procedure[2])).toEqual([ + { + command: "statement" + }, + + { + command: "statement" + }, + { + command: "do" + }, + { + command: "label", + target: "label_1" + }, + { + command: "statement" + }, + { + command: "statement" + }, + { + command: "endDo", + target: "true" + }, + { + command: "statement" + } + ]); + }); +}); + +describe("label elimination", () => { + it("should eliminate labels", () => { + const label = { + command: "label", + target: "label_1" + }; + const procedure = [ + { + command: "statement" + }, + label, + { + command: "statement" + } + ]; + expect(eliminateLabels(procedure)).toEqual([ + { + command: "statement" + }, + { + command: "statement" + } + ]); + }); +}); + +describe("outward movement transformation", () => { + it("should move goto out of a while", () => { + const procedure = [ + { + command: "statement" + }, + { + command: "while", + target: "condition" + }, + { + command: "statement" + }, + { + command: "if", + target: "condition2" + }, + { + command: "goto", + target: "label_1" + }, + { + command: "end" + }, + { + command: "statement" + }, + { + command: "statement" + }, + { + command: "end" + }, + { + command: "statement" + }, + { + command: "label", + target: "label_1" + }, + { + command: "statement" + } + ]; + expect(transformOutward(procedure, procedure[4])).toEqual([ + { + command: "statement" + }, + { + command: "while", + target: "condition" + }, + { + command: "statement" + }, + { + command: "store", + target: "condition2", + value: "label_1" + }, + { + command: "if", + target: "${label_1}" + }, + { + command: "break" + }, + { + command: "end" + }, + { + command: "statement" + }, + { + command: "statement" + }, + { + command: "end" + }, + { + command: "if", + target: "${label_1}" + }, + { + command: "goto", + target: "label_1" + }, + { + command: "end" + }, + { + command: "statement" + }, + { + command: "label", + target: "label_1" + }, + { + command: "statement" + } + ]); + }); + it("should move goto out of an if", () => { + const procedure = [ + { + command: "statement" + }, + { + command: "if", + target: "condition" + }, + { + command: "statement" + }, + { + command: "if", + target: "condition2" + }, + { + command: "goto", + target: "label_1" + }, + { + command: "end" + }, + { + command: "statement" + }, + { + command: "statement" + }, + { + command: "end" + }, + { + command: "statement" + }, + { + command: "label", + target: "label_1" + }, + { + command: "statement" + } + ]; + expect(transformOutward(procedure, procedure[4])).toEqual([ + { + command: "statement" + }, + { + command: "if", + target: "condition" + }, + { + command: "statement" + }, + { + command: "store", + target: "condition2", + value: "label_1" + }, + { + command: "if", + target: "!${label_1}" + }, + { + command: "statement" + }, + { + command: "statement" + }, + { + command: "end" + }, + { + command: "end" + }, + { + command: "if", + target: "${label_1}" + }, + { + command: "goto", + target: "label_1" + }, + { + command: "end" + }, + { + command: "statement" + }, + { + command: "label", + target: "label_1" + }, + { + command: "statement" + } + ]); + }); +}); + +describe("inward movement transformation", () => { + it("should move goto into a while", () => { + const procedure = [ + { + command: "statement" + }, + { + command: "if", + target: "condition" + }, + { + command: "goto", + target: "label_1" + }, + { + command: "end" + }, + { + command: "statement" + }, + { + command: "statement" + }, + { + command: "while", + target: "condition2" + }, + { + command: "statement" + }, + { + command: "label", + target: "label_1" + }, + { + command: "statement" + }, + { + command: "end" + }, + { + command: "statement" + } + ]; + expect(transformInward(procedure, procedure[2], procedure[8])).toEqual([ + { + command: "statement" + }, + { + command: "store", + target: "condition", + value: "label_1" + }, + { + command: "if", + target: "!${label_1}" + }, + { + command: "statement" + }, + { + command: "statement" + }, + { + command: "end" + }, + { + command: "while", + target: "${label_1} || condition2" + }, + { + command: "if", + target: "${label_1}" + }, + { + command: "goto", + target: "label_1" + }, + { + command: "end" + }, + { + command: "statement" + }, + { + command: "label", + target: "label_1" + }, + { + command: "store", + target: "false", + value: "label_1" + }, + { + command: "statement" + }, + { + command: "end" + }, + { + command: "statement" + } + ]); + }); + it("should move goto into an if's then", () => { + const procedure = [ + { + command: "statement" + }, + { + command: "if", + target: "condition" + }, + { + command: "goto", + target: "label_1" + }, + { + command: "end" + }, + { + command: "statement" + }, + { + command: "statement" + }, + { + command: "if", + target: "condition2" + }, + { + command: "statement" + }, + { + command: "label", + target: "label_1" + }, + { + command: "statement" + }, + { + command: "else" + }, + { + command: "statement" + }, + { + command: "statement" + }, + { + command: "end" + }, + { + command: "statement" + } + ]; + expect(transformInward(procedure, procedure[2], procedure[8])).toEqual([ + { + command: "statement" + }, + { + command: "store", + target: "condition", + value: "label_1" + }, + { + command: "if", + target: "!${label_1}" + }, + { + command: "statement" + }, + { + command: "statement" + }, + { + command: "end" + }, + { + command: "if", + target: "${label_1} || condition2" + }, + { + command: "if", + target: "${label_1}" + }, + { + command: "goto", + target: "label_1" + }, + { + command: "end" + }, + { + command: "statement" + }, + { + command: "label", + target: "label_1" + }, + { + command: "statement" + }, + { + command: "else" + }, + { + command: "statement" + }, + { + command: "statement" + }, + { + command: "end" + }, + { + command: "statement" + } + ]); + }); + it.skip("should move goto into an if's else", () => { + const procedure = [ + { + command: "statement" + }, + { + command: "if", + target: "condition" + }, + { + command: "goto", + target: "label_1" + }, + { + command: "end" + }, + { + command: "statement" + }, + { + command: "statement" + }, + { + command: "if", + target: "condition2" + }, + { + command: "statement" + }, + { + command: "statement" + }, + { + command: "else" + }, + { + command: "statement" + }, + { + command: "label", + target: "label_1" + }, + { + command: "statement" + }, + { + command: "end" + }, + { + command: "statement" + } + ]; + expect(transformInward(procedure, procedure[2], procedure[11])).toEqual([ + { + command: "statement" + }, + { + command: "store", + target: "condition", + value: "label_1" + }, + { + command: "if", + target: "!${label_1}" + }, + { + command: "statement" + }, + { + command: "statement" + }, + { + command: "end" + }, + { + command: "if", + target: "!${label_1} && condition2" + }, + { + command: "statement" + }, + { + command: "statement" + }, + { + command: "else" + }, + { + command: "if", + target: "${label_1}" + }, + { + command: "goto", + target: "label_1" + }, + { + command: "end" + }, + { + command: "statement" + }, + { + command: "label", + target: "label_1" + }, + { + command: "statement" + }, + { + command: "end" + }, + { + command: "statement" + } + ]); + }); +}); + +describe("lifting transformation", () => { + it("should lift the goto above it's label", () => { + const procedure = [ + { + command: "statement" + }, + { + command: "label", + target: "label_1" + }, + { + command: "statement" + }, + { + command: "if", + target: "condition" + }, + { + command: "goto", + target: "label_1" + }, + { + command: "end" + }, + { + command: "statement" + } + ]; + expect(lift(procedure, procedure[4], procedure[1])).toEqual([ + { + command: "store", + target: "false", + value: "label_1" + }, + { + command: "statement" + }, + { + command: "do" + }, + { + command: "if", + target: "${label_1}" + }, + { + command: "goto", + target: "label_1" + }, + { + command: "end" + }, + { + command: "label", + target: "label_1" + }, + { + command: "statement" + }, + { + command: "store", + target: "condition", + value: "label_1" + }, + { + command: "endDo", + target: "${label_1}" + }, + { + command: "statement" + } + ]); + }); +}); + +describe("goto-label relations", () => { + it("goto should be sibling of label at level 0", () => { + const procedure = [ + { + command: "statement" + }, + { + command: "if", + target: "true" + }, + { + command: "goto", + target: "label_1" + }, + { + command: "end" + }, + { + command: "statement" + }, + { + command: "label", + target: "label_1" + }, + { + command: "statement" + } + ]; + expect(relation(procedure, procedure[2], procedure[5])).toBe(Relationship.Siblings); + }); + it("goto should be sibling of label inside the same block", () => { + const procedure = [ + { + command: "statement" + }, + { + command: "while", + target: "true" + }, + { + command: "if", + target: "true" + }, + { + command: "goto", + target: "label_1" + }, + { + command: "end" + }, + { + command: "statement" + }, + { + command: "label", + target: "label_1" + }, + { + command: "statement" + }, + { + command: "end" + } + ]; + expect(relation(procedure, procedure[3], procedure[6])).toBe(Relationship.Siblings); + }); + it("should work for reverse label-goto as well", () => { + const procedure = [ + { + command: "label", + target: "label_1" + }, + { + command: "if", + target: "true" + }, + { + command: "goto", + target: "label_1" + }, + { + command: "end" + } + ]; + expect(relation(procedure, procedure[2], procedure[0])).toBe(Relationship.Siblings); + }); + it("goto should be sibling of label even with block between them", () => { + const procedure = [ + { + command: "if", + target: "true" + }, + { + command: "goto", + target: "label_1" + }, + { + command: "end" + }, + { + command: "while" + }, + { + command: "statement" + }, + { + command: "end" + }, + { + command: "label", + target: "label_1" + } + ]; + expect(relation(procedure, procedure[1], procedure[5])).toBe(Relationship.Siblings); + }); + it("should be directly related if the goto is nested inside the label block", () => { + const procedure = [ + { + command: "while", + target: "true" + }, + { + command: "if", + target: "true" + }, + { + command: "goto", + target: "label_1" + }, + { + command: "end" + }, + { + command: "end" + }, + { + command: "label", + target: "label_1" + } + ]; + expect(relation(procedure, procedure[2], procedure[5])).toBe(Relationship.DirectlyRelated); + }); + it("should be directly related if the label is nested inside the goto block", () => { + const procedure = [ + { + command: "if", + target: "true" + }, + { + command: "label", + target: "label_1" + }, + { + command: "end" + }, + { + command: "if", + target: "true" + }, + { + command: "goto", + target: "label_1" + }, + { + command: "end" + } + ]; + expect(relation(procedure, procedure[4], procedure[1])).toBe(Relationship.DirectlyRelated); + }); + it("should be indirectly related if goto and label are nested inside different branches of the procedure", () => { + const procedure = [ + { + command: "if", + target: "true" + }, + { + command: "label", + target: "label_1" + }, + { + command: "end" + }, + { + command: "if", + target: "true" + }, + { + command: "while", + target: "true" + }, + { + command: "if", + target: "true" + }, + { + command: "goto", + target: "label_1" + }, + { + command: "end" + }, + { + command: "end" + } + ]; + expect(relation(procedure, procedure[6], procedure[1])).toBe(Relationship.IndirectlyRelated); + }); +}); diff --git a/packages/selenium-ide/src/neo/__test__/IO/legacy/migrate.spec.js b/packages/selenium-ide/src/neo/__test__/IO/legacy/migrate.spec.js index f589ef3bd..fab42c6a7 100644 --- a/packages/selenium-ide/src/neo/__test__/IO/legacy/migrate.spec.js +++ b/packages/selenium-ide/src/neo/__test__/IO/legacy/migrate.spec.js @@ -57,7 +57,7 @@ describe("selenium test case migration", () => { it("should decode the input post conversion", () => { const file = fs.readFileSync(path.join(__dirname, "IDE_test_8.html")).toString(); const project = migrateTestCase(file); - expect(project.tests[0].commands[14].target).toBe("//a[@onclick='return confirm(\"Wollen Sie den Datensatz wirklich löschen?\")']"); + expect(project.tests[0].commands[13].target).toBe("//a[@onclick='return confirm(\"Wollen Sie den Datensatz wirklich löschen?\")']"); }); }); diff --git a/packages/selenium-ide/src/neo/__test__/models/Command.spec.js b/packages/selenium-ide/src/neo/__test__/models/Command.spec.js index 536b273d5..dd2fa085e 100644 --- a/packages/selenium-ide/src/neo/__test__/models/Command.spec.js +++ b/packages/selenium-ide/src/neo/__test__/models/Command.spec.js @@ -41,6 +41,17 @@ describe("Command", () => { command.setCommand("test invalid"); expect(command.isValid).toBeFalsy(); }); + it("should replace undefined input with empty strings", () => { + const command = new Command(); + command.setCommand(); + command.setTarget(); + command.setValue(); + command.setComment(); + expect(command.command).toEqual(""); + expect(command.target).toEqual(""); + expect(command.value).toEqual(""); + expect(command.comment).toEqual(""); + }); it("should set the target", () => { const command = new Command(); command.setTarget("a"); diff --git a/packages/selenium-ide/src/neo/models/Command.js b/packages/selenium-ide/src/neo/models/Command.js index 71df0bfd1..e1613c0e6 100644 --- a/packages/selenium-ide/src/neo/models/Command.js +++ b/packages/selenium-ide/src/neo/models/Command.js @@ -43,23 +43,23 @@ export default class Command { } @action.bound setComment(comment) { - this.comment = comment; + this.comment = comment || ""; } @action.bound setCommand(command) { if (CommandsValues[command]) { this.command = CommandsValues[command]; } else { - this.command = command; + this.command = command || ""; } } @action.bound setTarget(target) { - this.target = target; + this.target = target || ""; } @action.bound setValue(value) { - this.value = value.replace(/\n/g, "\\n"); + this.value = value ? value.replace(/\n/g, "\\n") : ""; } static fromJS = function(jsRep) {