added some more gui test cases

This commit is contained in:
hneemann 2018-01-20 21:34:42 +01:00
parent 30b7e69870
commit 2af1a66518
3 changed files with 412 additions and 94 deletions

View File

@ -2,14 +2,14 @@ package de.neemann.digital.integration;
import de.neemann.digital.draw.elements.Circuit;
import de.neemann.digital.gui.Main;
import jdk.nashorn.internal.scripts.JD;
import de.neemann.digital.gui.components.karnaugh.KarnaughMap;
import junit.framework.Assert;
import javax.sql.rowset.JdbcRowSet;
import javax.swing.FocusManager;
import javax.swing.*;
import javax.swing.text.JTextComponent;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.io.File;
import java.io.IOException;
@ -117,8 +117,13 @@ public class GuiTester {
return this;
}
public GuiTester mouseClick(int x, int y, int buttons) {
add((gs) -> gs.mouseClickNow(x, y, buttons));
public GuiTester mouseMove(int x, int y) {
add((gs) -> gs.mouseMoveNow(x, y));
return this;
}
public GuiTester mouseClick(int buttons) {
add((gs) -> gs.mouseClickNow(buttons));
return this;
}
@ -202,28 +207,52 @@ public class GuiTester {
if ((mod & ALT_DOWN_MASK) != 0) keyRelease(KeyEvent.VK_ALT);
}
public void mouseClickNow(int x, int y, int buttons) {
mouseMoveNow(x, y);
mouseClickNow(buttons);
}
/**
* Clicks the mouse
*
* @param x the x coordinate relative to the topmost window
* @param y the x coordinate relative to the topmost window
* @param buttons the button mask
*/
public void mouseClickNow(int x, int y, int buttons) {
Container activeWindow = FocusManager.getCurrentManager().getActiveWindow();
Point p = new Point(x, y);
public void mouseClickNow(int buttons) {
mouseDownNow(buttons);
mouseReleaseNow(buttons);
}
if (activeWindow instanceof JDialog)
activeWindow = ((JDialog) activeWindow).getContentPane();
else if (activeWindow instanceof JFrame)
activeWindow = ((JFrame) activeWindow).getContentPane();
SwingUtilities.convertPointToScreen(p, activeWindow);
robot.mouseMove(p.x, p.y);
robot.mousePress(buttons);
public void mouseReleaseNow(int buttons) {
robot.mouseRelease(buttons);
}
public void mouseDownNow(int buttons) {
robot.mousePress(buttons);
}
/**
* Moves the mouse
*
* @param x the x coordinate relative to the topmost window
* @param y the x coordinate relative to the topmost window
*/
public void mouseMoveNow(int x, int y) {
Container baseContainer = getBaseContainer();
Point p = new Point(x, y);
SwingUtilities.convertPointToScreen(p, baseContainer);
robot.mouseMove(p.x, p.y);
}
public static Container getBaseContainer() {
Container baseContainer = FocusManager.getCurrentManager().getActiveWindow();
if (baseContainer instanceof JDialog)
baseContainer = ((JDialog) baseContainer).getContentPane();
else if (baseContainer instanceof JFrame)
baseContainer = ((JFrame) baseContainer).getContentPane();
return baseContainer;
}
private void keyPress(int keyCode) {
robot.keyPress(keyCode);
}
@ -232,6 +261,10 @@ public class GuiTester {
robot.keyRelease(keyCode);
}
public Robot getRobot() {
return robot;
}
/**
* Every test step implements this Runnable
*/
@ -252,6 +285,7 @@ public class GuiTester {
*/
public static class WindowCheck<W extends Window> implements Runnable {
private final Class<W> expectedClass;
private final WindowCheckInterface<W> windowCheckInterface;
/**
* Creates a new instance
@ -259,7 +293,17 @@ public class GuiTester {
* @param expectedClass the expected window class
*/
public WindowCheck(Class<W> expectedClass) {
this(expectedClass, null);
}
/**
* Creates a new instance
*
* @param expectedClass the expected window class
*/
public WindowCheck(Class<W> expectedClass, WindowCheckInterface<W> windowCheckInterface) {
this.expectedClass = expectedClass;
this.windowCheckInterface = windowCheckInterface;
}
@Override
@ -288,10 +332,16 @@ public class GuiTester {
* @param guiTester the GuiTester
* @param window the found window of expected type
*/
public void checkWindow(GuiTester guiTester, W window) {
public void checkWindow(GuiTester guiTester, W window) throws Exception {
if (windowCheckInterface != null)
windowCheckInterface.checkWindow(guiTester, window);
}
}
interface WindowCheckInterface<W extends Window> {
void checkWindow(GuiTester guiTester, W window) throws Exception;
}
/**
* Traverses all the components in the topmost window.
*/
@ -401,4 +451,74 @@ public class GuiTester {
}
}
public static class ColorPicker implements Runnable {
private final int x;
private final int y;
private final ColorPickerInterface cpi;
public ColorPicker(int x, int y, ColorPickerInterface cpi) {
this.x = x;
this.y = y;
this.cpi = cpi;
}
@Override
public void run(GuiTester guiTester) throws Exception {
if (x < 0 || y < 0) pickPosition(guiTester, Math.abs(x), Math.abs(y));
else {
Point p = new Point(x, y);
SwingUtilities.convertPointToScreen(p, getBaseContainer());
cpi.checkColor(guiTester.getRobot().getPixelColor(p.x, p.y));
}
}
private void pickPosition(GuiTester guiTester, int x, int y) {
new PositionPicker(guiTester, x, y).setVisible(true);
}
}
public interface ColorPickerInterface {
void checkColor(Color pixelColor);
}
private static class PositionPicker extends JDialog {
private final JLabel label;
private int x;
private int y;
private PositionPicker(GuiTester gt, int xo, int yo) {
super(null, "Position picker", ModalityType.APPLICATION_MODAL);
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
label = new JLabel("Pick position");
label.setFocusable(true);
getContentPane().add(label);
pack();
setLocationRelativeTo(null);
Point l = new Point(xo,yo);
Container baseContainer = getBaseContainer();
SwingUtilities.convertPointToScreen(l, baseContainer);
x = l.x;
y = l.y;
label.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent keyEvent) {
if (keyEvent.getKeyCode() == KeyEvent.VK_ENTER) {
Point p = new Point(x, y);
SwingUtilities.convertPointFromScreen(p, baseContainer);
System.out.println(p.x + ", " + p.y);
System.exit(0);
}
if (keyEvent.getKeyCode() == KeyEvent.VK_RIGHT) x++;
if (keyEvent.getKeyCode() == KeyEvent.VK_LEFT) x--;
if (keyEvent.getKeyCode() == KeyEvent.VK_UP) y--;
if (keyEvent.getKeyCode() == KeyEvent.VK_DOWN) y++;
new Thread(() -> gt.getRobot().mouseMove(x, y)).start();
}
});
SwingUtilities.invokeLater(label::requestFocusInWindow);
}
}
}

View File

@ -1,12 +1,16 @@
package de.neemann.digital.integration;
import de.neemann.digital.analyse.expression.Expression;
import de.neemann.digital.core.basic.And;
import de.neemann.digital.core.element.Keys;
import de.neemann.digital.draw.elements.Circuit;
import de.neemann.digital.draw.elements.VisualElement;
import de.neemann.digital.draw.elements.Wire;
import de.neemann.digital.draw.graphics.GraphicMinMax;
import de.neemann.digital.draw.graphics.Style;
import de.neemann.digital.draw.graphics.Vector;
import de.neemann.digital.gui.Main;
import de.neemann.digital.gui.components.CircuitComponent;
import de.neemann.digital.gui.components.karnaugh.KarnaughMapDialog;
import de.neemann.digital.gui.components.table.AllSolutionsDialog;
import de.neemann.digital.gui.components.table.ExpressionListenerStore;
@ -20,6 +24,8 @@ import javax.swing.*;
import java.awt.*;
import java.awt.event.InputEvent;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import static de.neemann.digital.draw.shapes.GenericShape.SIZE;
@ -36,7 +42,8 @@ public class TestInGUI extends TestCase {
.press("SPACE")
.add(new CheckErrorDialog("01_fastRuntime.dig", Lang.get("err_burnError")))
.add(new GuiTester.CloseTopMost())
.ask("Is the driver output colored red?")
.add(new GuiTester.ColorPicker(695, 626, (c) -> assertEquals(Style.ERROR.getColor(), c)))
// .ask("Is the driver output colored red?")
.add(new GuiTester.WindowCheck<>(Main.class))
.execute();
}
@ -55,7 +62,8 @@ public class TestInGUI extends TestCase {
.press("SPACE")
.add(new CheckErrorDialog("06_initPhase.dig", Lang.get("err_burnError")))
.add(new GuiTester.CloseTopMost())
.ask("Is the driver output colored red?")
.add(new GuiTester.ColorPicker(871,450, (c) -> assertEquals(Style.ERROR.getColor(), c)))
// .ask("Is the driver output colored red?")
.add(new GuiTester.WindowCheck<>(Main.class))
.execute();
}
@ -65,7 +73,8 @@ public class TestInGUI extends TestCase {
.press("SPACE")
.add(new CheckErrorDialog("07_creationPhase.dig", "ErrorY"))
.add(new GuiTester.CloseTopMost())
.ask("Is the output circled red?")
.add(new GuiTester.ColorPicker(597, 459, (c) -> assertEquals(Style.ERROR.getColor(), c)))
// .ask("Is the output circled red?")
.add(new GuiTester.WindowCheck<>(Main.class))
.execute();
}
@ -94,7 +103,8 @@ public class TestInGUI extends TestCase {
.press("F7")
.add(new CheckErrorDialog("05_runToBreak.dig", Lang.get("err_burnError")))
.add(new GuiTester.CloseTopMost())
.ask("Is the driver output colored red?")
.add(new GuiTester.ColorPicker(873, 546, (c) -> assertEquals(Style.ERROR.getColor(), c)))
// .ask("Is the driver output colored red?")
.add(new GuiTester.WindowCheck<>(Main.class))
.execute();
}
@ -106,7 +116,8 @@ public class TestInGUI extends TestCase {
.press('a')
.add(new CheckErrorDialog("03_fastRuntimeButton.dig", Lang.get("err_burnError")))
.add(new GuiTester.CloseTopMost())
.ask("Is the driver output colored red?")
.add(new GuiTester.ColorPicker(821, 585, (c) -> assertEquals(Style.ERROR.getColor(), c)))
// .ask("Is the driver output colored red?")
.add(new GuiTester.WindowCheck<>(Main.class))
.execute();
}
@ -118,24 +129,16 @@ public class TestInGUI extends TestCase {
.add(new TableDialogCheck("and(B,C)"))
.press("F1")
.delay(500)
.add(new GuiTester.WindowCheck<KarnaughMapDialog>(KarnaughMapDialog.class) {
@Override
public void checkWindow(GuiTester guiTester, KarnaughMapDialog kMapDialog) {
List<ExpressionListenerStore.Result> res = kMapDialog.getResults();
assertEquals(1, res.size());
Expression r = res.get(0).getExpression();
assertEquals("and(B,C)", r.toString());
}
})
.add(new GuiTester.WindowCheck<>(KarnaughMapDialog.class, (guiTester, kMapDialog) -> {
List<ExpressionListenerStore.Result> res = kMapDialog.getResults();
assertEquals(1, res.size());
Expression r = res.get(0).getExpression();
assertEquals("and(B,C)", r.toString());
}))
.add(new GuiTester.CloseTopMost())
.press("F2")
.add(new GuiTester.WindowCheck<Main>(Main.class) {
@Override
public void checkWindow(GuiTester guiTester, Main main) {
Circuit c = main.getCircuitComponent().getCircuit();
assertEquals(4, c.getElements().size());
}
})
.add(new GuiTester.WindowCheck<>(Main.class,
(gt, main) -> assertEquals(4, main.getCircuitComponent().getCircuit().getElements().size())))
.add(new GuiTester.CloseTopMost())
.add(new GuiTester.WindowCheck<>(TableDialog.class))
.execute();
@ -152,13 +155,8 @@ public class TestInGUI extends TestCase {
.press("TAB", 2)
.press("SPACE")
.delay(500)
.add(new GuiTester.WindowCheck<Main>(Main.class) {
@Override
public void checkWindow(GuiTester guiTester, Main main) {
Circuit c = main.getCircuitComponent().getCircuit();
assertEquals(7, c.getElements().size());
}
})
.add(new GuiTester.WindowCheck<>(Main.class,
(gt, main) -> assertEquals(7, main.getCircuitComponent().getCircuit().getElements().size())))
.press("F9")
.delay(500)
.add(new TableDialogCheck("or(and(a,b),and(b,c))"))
@ -235,12 +233,7 @@ public class TestInGUI extends TestCase {
.press("DOWN", 2)
.press("ENTER")
.delay(500)
.add(new GuiTester.WindowCheck<AllSolutionsDialog>(AllSolutionsDialog.class) {
@Override
public void checkWindow(GuiTester guiTester, AllSolutionsDialog asd) {
asd.getParent().requestFocus();
}
})
.add(new GuiTester.WindowCheck<>(AllSolutionsDialog.class, (gt, asd) -> asd.getParent().requestFocus()))
.delay(500);
public void testCounterJK() {
@ -276,20 +269,48 @@ public class TestInGUI extends TestCase {
.add(new DrawCircuit("../../main/dig/sequential/JK-MS.dig"))
.press("F8")
.delay(500)
.add(new GuiTester.CheckTextInWindow<>(ValueTableDialog.class, "ok"))
.add(new GuiTester.CheckTextInWindow<>(ValueTableDialog.class, Lang.get("msg_test_N_Passed", "")))
.add(new GuiTester.CheckTableRows<>(ValueTableDialog.class, 8))
.add(new GuiTester.CloseTopMost())
.press("control typed z",65)
.press("control typed z", 65)
.delay(1000)
.press("control typed y",65)
.press("control typed y", 65)
.press("F8")
.delay(500)
.add(new GuiTester.CheckTextInWindow<>(ValueTableDialog.class, "ok"))
.add(new GuiTester.CheckTextInWindow<>(ValueTableDialog.class, Lang.get("msg_test_N_Passed", "")))
.add(new GuiTester.CheckTableRows<>(ValueTableDialog.class, 8))
.add(new GuiTester.CloseTopMost())
.execute();
}
public void testCopyPaste() {
new GuiTester("../../main/dig/sequential/JK-MS.dig")
.add(new SelectAll())
.press("control typed c")
.press("F10")
.press("RIGHT", 2)
.press("DOWN", 9)
.press("ENTER")
.delay(500)
.press("F8")
.delay(500)
.add(new GuiTester.CheckTextInWindow<>(ValueTableDialog.class, Lang.get("msg_test_N_Passed", "")))
.add(new GuiTester.CheckTableRows<>(ValueTableDialog.class, 8))
.add(new GuiTester.CloseTopMost())
.add(new SelectAll())
.press("DELETE")
.press("control typed v")
.mouseClick(InputEvent.BUTTON1_MASK)
.press("F1")
.press("F8")
.delay(500)
.add(new GuiTester.CheckTextInWindow<>(ValueTableDialog.class, Lang.get("msg_test_N_Passed", "")))
.add(new GuiTester.CheckTableRows<>(ValueTableDialog.class, 8))
.add(new GuiTester.CloseTopMost())
.add(new GuiTester.CloseTopMost())
.execute();
}
public void testHardware() {
new GuiTester("dig/manualError/10_hardware.dig")
.press("F9")
@ -316,7 +337,8 @@ public class TestInGUI extends TestCase {
public void testTestEditor() {
new GuiTester("dig/manualError/11_editTest.dig")
.mouseClick(200, 200, InputEvent.BUTTON3_MASK)
.mouseMove(200, 200)
.mouseClick(InputEvent.BUTTON3_MASK)
.type("testIdentzz")
.press("TAB")
.press("SPACE")
@ -328,13 +350,74 @@ public class TestInGUI extends TestCase {
.press("SPACE")
.press("F8")
.delay(500)
.add(new GuiTester.CheckTextInWindow<>(ValueTableDialog.class, "testIdentzz", "ok"))
.add(new GuiTester.CheckTextInWindow<>(ValueTableDialog.class, Lang.get("msg_test_N_Passed", "testIdentzz")))
.add(new GuiTester.CheckTableRows<>(ValueTableDialog.class, 4))
.add(new GuiTester.CloseTopMost())
.add(new GuiTester.CloseTopMost())
.execute();
}
public void testSplitWire() {
new GuiTester()
.mouseMove(100, 100)
.mouseClick(InputEvent.BUTTON1_MASK)
.press('d')
.mouseMove(300, 300)
.mouseClick(InputEvent.BUTTON1_MASK)
.mouseClick(InputEvent.BUTTON3_MASK)
.add(new GuiTester.WindowCheck<>(Main.class,
(gt, main) -> assertEquals(1, main.getCircuitComponent().getCircuit().getWires().size())))
.mouseMove(200, 200)
.press('s')
.mouseMove(250, 150)
.mouseClick(InputEvent.BUTTON1_MASK)
.add(new GuiTester.WindowCheck<>(Main.class,
(gt, main) -> assertEquals(2, main.getCircuitComponent().getCircuit().getWires().size())))
.execute();
}
public void testMoveSelected() {
new GuiTester()
.mouseMove(110, 110)
.add(new GuiTester.WindowCheck<>(Main.class, (gt, main) -> {
final CircuitComponent cc = main.getCircuitComponent();
final VisualElement ve = new VisualElement(And.DESCRIPTION.getName())
.setShapeFactory(cc.getLibrary().getShapeFactory());
cc.setPartToInsert(ve);
}))
.mouseClick(InputEvent.BUTTON1_MASK)
.mouseMove(100, 100)
.mouseClick(InputEvent.BUTTON1_MASK)
.mouseMove(400, 400)
.mouseClick(InputEvent.BUTTON1_MASK)
.add(new GuiTester.WindowCheck<>(Main.class, (gt, main) -> {
final Circuit c = main.getCircuitComponent().getCircuit();
assertEquals(1, c.getElements().size());
final Vector pos = c.getElements().get(0).getPos();
assertTrue(pos.x > 300);
assertTrue(pos.y > 300);
}))
.execute();
}
public void testGroupEdit() {
new GuiTester("dig/manualError/12_groupEdit.dig")
.add(new SelectAll())
.mouseClick(InputEvent.BUTTON3_MASK)
.press("TAB", 2)
.type("6")
.press("ENTER")
.add(new GuiTester.WindowCheck<>(Main.class, (guiTester, main) -> {
ArrayList<VisualElement> l = main.getCircuitComponent().getCircuit().getElements();
assertEquals(8, l.size());
for (VisualElement e : l)
assertEquals(16, (int) e.getElementAttributes().get(Keys.BITS));
}))
.execute();
}
public static class CheckErrorDialog extends GuiTester.WindowCheck<ErrorMessage.ErrorDialog> {
private final String[] expected;
@ -396,50 +479,62 @@ public class TestInGUI extends TestCase {
}
@Override
public void checkWindow(GuiTester guiTester, Main main) {
public void checkWindow(GuiTester guiTester, Main main) throws InterruptedException, IOException {
File file = new File(Resources.getRoot(), filename);
try {
Circuit circuit = Circuit.loadCircuit(file, main.getCircuitComponent().getLibrary().getShapeFactory());
Circuit circuit = Circuit.loadCircuit(file, main.getCircuitComponent().getLibrary().getShapeFactory());
int xMin = Integer.MAX_VALUE;
int yMin = Integer.MAX_VALUE;
for (Wire w : circuit.getWires()) {
if (w.p1.x < xMin) xMin = w.p1.x;
if (w.p2.x < xMin) xMin = w.p2.x;
if (w.p1.y < yMin) yMin = w.p1.y;
if (w.p2.y < yMin) yMin = w.p2.y;
}
int xMin = Integer.MAX_VALUE;
int yMin = Integer.MAX_VALUE;
for (Wire w : circuit.getWires()) {
if (w.p1.x < xMin) xMin = w.p1.x;
if (w.p2.x < xMin) xMin = w.p2.x;
if (w.p1.y < yMin) yMin = w.p1.y;
if (w.p2.y < yMin) yMin = w.p2.y;
}
Point loc = main.getCircuitComponent().getLocation();
xMin -= loc.x + SIZE * 5;
yMin -= loc.y + SIZE * 2;
Point loc = main.getCircuitComponent().getLocation();
xMin -= loc.x + SIZE * 5;
yMin -= loc.y + SIZE * 2;
for (Wire w : circuit.getWires()) {
guiTester.mouseClickNow(w.p1.x - xMin, w.p1.y - yMin, InputEvent.BUTTON1_MASK);
Thread.sleep(100);
if (w.p1.x != w.p2.x && w.p1.y != w.p2.y)
guiTester.typeNow("typed d");
for (Wire w : circuit.getWires()) {
guiTester.mouseClickNow(w.p1.x - xMin, w.p1.y - yMin, InputEvent.BUTTON1_MASK);
Thread.sleep(100);
if (w.p1.x != w.p2.x && w.p1.y != w.p2.y)
guiTester.typeNow("typed d");
guiTester.mouseClickNow(w.p2.x - xMin, w.p2.y - yMin, InputEvent.BUTTON1_MASK);
guiTester.mouseClickNow(w.p2.x - xMin, w.p2.y - yMin, InputEvent.BUTTON3_MASK);
Thread.sleep(100);
}
guiTester.mouseClickNow(w.p2.x - xMin, w.p2.y - yMin, InputEvent.BUTTON1_MASK);
guiTester.mouseClickNow(w.p2.x - xMin, w.p2.y - yMin, InputEvent.BUTTON3_MASK);
Thread.sleep(100);
}
for (VisualElement v : circuit.getElements()) {
Vector pos = v.getPos();
v.setPos(new Vector(0, 0));
final GraphicMinMax minMax = v.getMinMax(false);
pos = pos.add(minMax.getMax());
main.getCircuitComponent().setPartToInsert(v);
guiTester.mouseClickNow(pos.x - xMin, pos.y - yMin, InputEvent.BUTTON1_MASK);
Thread.sleep(200);
}
} catch (Exception e) {
throw new RuntimeException(e);
for (VisualElement v : circuit.getElements()) {
Vector pos = v.getPos();
v.setPos(new Vector(0, 0));
final GraphicMinMax minMax = v.getMinMax(false);
pos = pos.add(minMax.getMax());
main.getCircuitComponent().setPartToInsert(v);
guiTester.mouseClickNow(pos.x - xMin, pos.y - yMin, InputEvent.BUTTON1_MASK);
Thread.sleep(200);
}
}
}
private class SelectAll extends GuiTester.WindowCheck<Main> {
/**
* Creates a new instance
*/
public SelectAll() {
super(Main.class);
}
@Override
public void checkWindow(GuiTester guiTester, Main main) {
final CircuitComponent c = main.getCircuitComponent();
Point loc = c.getLocation();
guiTester.mouseMoveNow(loc.x + 2, loc.y + 2);
guiTester.mouseDownNow(InputEvent.BUTTON1_MASK);
guiTester.mouseMoveNow(loc.x + c.getWidth() - 2, loc.y + c.getHeight() - 2);
guiTester.mouseReleaseNow(InputEvent.BUTTON1_MASK);
}
}
}

View File

@ -0,0 +1,103 @@
<?xml version="1.0" encoding="utf-8"?>
<circuit>
<version>1</version>
<attributes/>
<visualElements>
<visualElement>
<elementName>And</elementName>
<elementAttributes/>
<pos x="500" y="200"/>
</visualElement>
<visualElement>
<elementName>In</elementName>
<elementAttributes>
<entry>
<string>Label</string>
<string>A</string>
</entry>
</elementAttributes>
<pos x="480" y="200"/>
</visualElement>
<visualElement>
<elementName>In</elementName>
<elementAttributes>
<entry>
<string>Label</string>
<string>B</string>
</entry>
</elementAttributes>
<pos x="480" y="240"/>
</visualElement>
<visualElement>
<elementName>Out</elementName>
<elementAttributes>
<entry>
<string>Label</string>
<string>C</string>
</entry>
</elementAttributes>
<pos x="580" y="220"/>
</visualElement>
<visualElement>
<elementName>And</elementName>
<elementAttributes/>
<pos x="500" y="120"/>
</visualElement>
<visualElement>
<elementName>In</elementName>
<elementAttributes>
<entry>
<string>Label</string>
<string>A</string>
</entry>
</elementAttributes>
<pos x="480" y="120"/>
</visualElement>
<visualElement>
<elementName>In</elementName>
<elementAttributes>
<entry>
<string>Label</string>
<string>B</string>
</entry>
</elementAttributes>
<pos x="480" y="160"/>
</visualElement>
<visualElement>
<elementName>Out</elementName>
<elementAttributes>
<entry>
<string>Label</string>
<string>C</string>
</entry>
</elementAttributes>
<pos x="580" y="140"/>
</visualElement>
</visualElements>
<wires>
<wire>
<p1 x="480" y="240"/>
<p2 x="500" y="240"/>
</wire>
<wire>
<p1 x="480" y="160"/>
<p2 x="500" y="160"/>
</wire>
<wire>
<p1 x="480" y="200"/>
<p2 x="500" y="200"/>
</wire>
<wire>
<p1 x="480" y="120"/>
<p2 x="500" y="120"/>
</wire>
<wire>
<p1 x="560" y="220"/>
<p2 x="580" y="220"/>
</wire>
<wire>
<p1 x="560" y="140"/>
<p2 x="580" y="140"/>
</wire>
</wires>
</circuit>