Repository: qtile/qtile

Issue:
mouse_callbacks not working
# Issue description
I cannot, for the life of me, get mouse_callbacks to work with any of my widgets. The goal is to run a bash script when the widget is clicked. However, it seems like qtile is simply not recording my clicks at all.

```python
widget.KeyboardLayout(fmt='  Keyboard: {}', background=colors[1], mouse_callbacks={'Button2': lambda: qtile.cmd_spawn('bash /home/graeme/.local/bin/togglekeyboard.sh')}),
```

I've also tried `lazy.spawn`, `subprocess.run`, `subprocess.call`, and using a non-lambda function. Nothing seems to work.

# Qtile version
0.17.0

# Stack traces
Nothing relevant was printed to the log.

# Configuration
https://github.com/Graeme22/dotfiles



Discussion:
Have you tried having the callback write to the logfile e.g.
``` python
from libqtile.log_utils import logger
...
mouse_callbacks={'Button2': lambda: logger.info("Mouse callback - button 2 clicked")}
```
We need to see if callbacks are firing at all first.
> Have you tried having the callback write to the logfile e.g.
> 
> ```python
> from libqtile.log_utils import logger
> ...
> mouse_callbacks={'Button2': lambda: logger.info("Mouse callback - button 2 clicked")}
> ```
> 
> We need to see if callbacks are firing at all first.

Yup, nothing is logged, so they're not firing at all.
Weird. Have you tried running`xev` in a terminal and see if it captures any
mouse events?

On Wed, 26 May 2021, 15:18 Graeme Holliday, ***@***.***>
wrote:

> Have you tried having the callback write to the logfile e.g.
>
> from libqtile.log_utils import logger
> ...mouse_callbacks={'Button2': lambda: logger.info("Mouse callback - button 2 clicked")}
>
> We need to see if callbacks are firing at all first.
>
> Yup, nothing is logged, so they're not firing at all.
>
> —
> You are receiving this because you commented.
> Reply to this email directly, view it on GitHub
> <https://github.com/qtile/qtile/issues/2487#issuecomment-848808932>, or
> unsubscribe
> <https://github.com/notifications/unsubscribe-auth/AAHHAWMVHFDGRK3SKITJSCLTPT7MJANCNFSM45QFYWXQ>
> .
>

> Weird. Have you tried running`xev` in a terminal and see if it captures any mouse events?
> […](#)
> On Wed, 26 May 2021, 15:18 Graeme Holliday, ***@***.***> wrote: Have you tried having the callback write to the logfile e.g. from libqtile.log_utils import logger ...mouse_callbacks={'Button2': lambda: logger.info("Mouse callback - button 2 clicked")} We need to see if callbacks are firing at all first. Yup, nothing is logged, so they're not firing at all. — You are receiving this because you commented. Reply to this email directly, view it on GitHub <[#2487 (comment)](https://github.com/qtile/qtile/issues/2487#issuecomment-848808932)>, or unsubscribe <https://github.com/notifications/unsubscribe-auth/AAHHAWMVHFDGRK3SKITJSCLTPT7MJANCNFSM45QFYWXQ> .

Yes, `xev` captures clicks, moves, etc. I am able to get qtile to switch virtual desktops by clicking as well, so the mouse interaction seems to work with some widgets but not others.
> > Have you tried having the callback write to the logfile e.g.
> > ```python
> > from libqtile.log_utils import logger
> > ...
> > mouse_callbacks={'Button2': lambda: logger.info("Mouse callback - button 2 clicked")}
> > ```
> > 
> > 
> >     
> >       
> >     
> > 
> >       
> >     
> > 
> >     
> >   
> > We need to see if callbacks are firing at all first.
> 
> Yup, nothing is logged, so they're not firing at all.

Can you try with `logger.error` instead? Info logs are not included in the current default log level.
> > > Have you tried having the callback write to the logfile e.g.
> > > ```python
> > > from libqtile.log_utils import logger
> > > ...
> > > mouse_callbacks={'Button2': lambda: logger.info("Mouse callback - button 2 clicked")}
> > > ```
> > > 
> > > 
> > >     
> > >       
> > >     
> > > 
> > >       
> > >     
> > > 
> > >     
> > >   
> > > We need to see if callbacks are firing at all first.
> > 
> > 
> > Yup, nothing is logged, so they're not firing at all.
> 
> Can you try with `logger.error` instead? Info logs are not included in the current default log level.

Good catch. Nope, still nothing in the logs.
Anyone able to reproduce? I wonder if this doesn't have to do with the config at all.
No, can't recreate.

Just checked out `v0.17.0`, added the `Battery` widget and added a mouse callback to log a warning message. When I click on the widget, the message is printed in the log.

EDIT: Sorry, no idea why I tested the `Battery` widget, when you're not having issues with that one... Let me try `KeyboardLayout`.

EDIT 2: Callback works fine on `KeyboardLayout` too.
> No, can't recreate.
> 
> Just checked out `v0.17.0`, added the `Battery` widget and added a mouse callback to log a warning message. When I click on the widget, the message is printed in the log.
> 
> EDIT: Sorry, no idea why I tested the `Battery` widget, when you're not having issues with that one... Let me try `KeyboardLayout`.
> 
> EDIT 2: Callback works fine on `KeyboardLayout` too.

Hm. Let me start from a fresh config and see what happens.
Update: It is working just fine for me with the default config. So somehow, part of my config causes the callbacks to stop working. Let me see if I can narrow down what part.
This is really bizarre. I've traced it down, and it appears that the Wlan and KeyboardLayout widgets don't coexist. If I comment out my Wlan widget, everything works fine. But if I have both Wlan and KeyboardLayout in the bar, the callbacks stop working.

Edit: Code for Wlan widget:

```python
widget.Wlan(interface='wlp1s0', background=colors[6], disconnected_message='  Disconnected', format='  Network: {essid}'),
```

Edit 2: I should also mention, the Wlan widget works fine by itself. Nothing in the logs either.
On the default config, it looks like Wlan and KeyboardLayout can coexist, so that's not it. I'm going to try and find the smallest config that reproduces this.
It seems like it has to do with the *number* of widgets. My base config has 23. Is there a hidden limit on the number of widgets?

Or perhaps some hardware or OS restraints?
Here's a bit of an info dump.

### Minimal code to reproduce
Default config, but replace the widgets with:
```python
widget.CurrentLayoutIcon(),
widget.Spacer(length=5),
widget.TextBox(text='◣', fontsize=32, width=27, padding=-5),
widget.GroupBox(),
widget.TextBox(text='◣', fontsize=32, width=27, padding=-5),
widget.WindowName(),
widget.Spacer(),
widget.TextBox(text='◥', fontsize=32, width=27),
widget.TextBox(text='◥', fontsize=32, width=27),
widget.KeyboardLayout(fmt='  Keyboard: {}', configured_keyboards=['us', 'es', 'gr']),
widget.TextBox(text='◥', fontsize=32, width=27),
widget.Battery(charge_char='+', discharge_char='-', full_char='', show_short_text=False, format='  Power: {char}{percent:2.0%}'),
widget.TextBox(text='◥', fontsize=32, width=27),
widget.Volume(fmt='  Volume: {}'),
widget.TextBox(text='◥', fontsize=32, width=27),
widget.CPU(format='  CPU: {freq_current}GHz {load_percent}%'),
widget.TextBox(text='◥', fontsize=32, width=27),
widget.Memory(fmt='  RAM: {}'),
widget.TextBox(text='◥', fontsize=32, width=27),
widget.Wlan(interface='wlp1s0', disconnected_message='  Disconnected', format='  Network: {essid}'),
widget.TextBox(text='◥', fontsize=32, width=27),
widget.Clock(format='  %A, %B %d %H:%M '),
```
This doesn't work on my system; however, if I comment out Wlan, for example, it works. I can also comment out Volume and it works.

### System info
- OS: Arch Linux x86_64
- Kernel: 5.12.6-arch1-1
- Resolution: 1920x1080
- CPU: Intel Celeron J4115 (4)  2.500GHz
- GPU: Intel UHD Graphics 605
- Memory: 952MiB / 7770MiB
Odd. I wouldn't have expected the number of widgets to have this impact.

What if you comment out one of the spacers i.e. is it the total number of
widgets that's causing the problem or is it the type?


Could it be just a wrong offset calculation or alike, due to the bar being too crowded for the number of pixels it has?
In the list of widgets, I don't understand what is the use of the spacers() widget just after the windowname (they have both a stretched width). It's not that important that I understand, but I'd like to know what is the behavior if you remove it.

I start to believe it's a wrong offset calculation due to the number of stretched widgets.
The bar._resize method is still not obvious for me, but my main suspect for sure.
I think the second stretched widget as a wrong offset, which seems to "cover" more width than expected. Depending of the number of widgets after that second stretched widget, the keyboardlayout widget might or not be covered.

> In the list of widgets, I don't understand what is the use of the spacers() widget just after the windowname (they have both a stretched width). It's not that important that I understand, but I'd like to know what is the behavior if you remove it.

Yup, looks like it had to do with the `WindowName` and `Spacer` widgets combined. Somehow that messed up the offset once enough widgets were present.
Side note, I had no idea `WindowName` was a stretching widget, but looks like it's in the docs so that's on me.

> I start to believe it's a wrong offset calculation due to the number of stretched widgets.
> The bar._resize method is still not obvious for me, but my main suspect for sure.
> I think the second stretched widget as a wrong offset, which seems to "cover" more width than expected. Depending of the number of widgets after that second stretched widget, the keyboardlayout widget might or not be covered.

What I'm seeing matches up with this theory. The click events aren't working on the widgets next to the spacer, but they're working fine on the ones further away.
If y'all wanna just say "don't use spacer and windowname together" this issue can be closed, unless there's some wrong offset somewhere that needs to be addressed.
Definitely sounds like something needs to be fixed anyway. :)
I used the logger for almost all the variables of this function to finally find a described pr :D
So, most of the current behavior comes from #1774.

If I understand right, the logic does not take into account the possibility of a block with a length superior to the interval.

In a case like this : Block1, Stretch1, Block2, Stretch2, Blooooooooooooooooooock3
the calculated length of Stretch2 is **negative** (~interval minus Block3), which makes the calculation of the following offsets wrong.
The consequence is that some widgets share the same space, therefore the inoperative mouse_callbacks.

One solution could be to fallback to the previous logic when a block is bigger than the interval.
All stretched widgets get equal width by sharing the space left by the non-stretched widgets.
We'll lose the centered effect, which in that case seems impossible to obtain anyway.

Last point that comes to my mind, I haven't read this part of the code yet, but it seems that at some point we tried (and succeeded?) to draw a widget with a negative length. To log something when that happens could be a fair addition too.
Nice detective work!

With regard to losing the centred effect, this is something that does get asked quite a lot in the qtile-dev group so would be good to preserve it or find a new way of doing it if possible.
> Last point that comes to my mind, I haven't read this part of the code yet, but it seems that at some point we tried (and succeeded?) to draw a widget with a negative length. To log something when that happens could be a fair addition too.

Logging could make sense here, in my case that would've tipped me off that my config had two adjacent spacers which I didn't know nor want. However, I'd assume some people might want that behavior. Intuitively it seems like two adjacent widgets with width stretch should equally divide that space between them.

Resolving patch:
diff --git a/libqtile/bar.py b/libqtile/bar.py
--- a/libqtile/bar.py
+++ b/libqtile/bar.py
@@ -21,6 +21,7 @@
 from __future__ import annotations
 
 import typing
+from collections import defaultdict
 
 from libqtile import configurable
 from libqtile.command.base import CommandObject, ItemT
@@ -238,13 +239,6 @@ def _configure(self, qtile, screen):
         width = self.width + (self.border_width[1] + self.border_width[3])
         height = self.height + (self.border_width[0] + self.border_width[2])
 
-        for w in self.widgets:
-            # Executing _test_orientation_compatibility later, for example in
-            # the _configure() method of each widget, would still pass
-            # test/test_bar.py but a segfault would be raised when nosetests is
-            # about to exit
-            w._test_orientation_compatibility(self.horizontal)
-
         if self.window:
             # We get _configure()-ed with an existing window when screens are getting
             # reconfigured but this screen is present both before and after
@@ -364,13 +358,27 @@ def kill_window(self):
         self.window = None
 
     def _resize(self, length, widgets):
-        stretches = [i for i in widgets if i.length_type == STRETCH]
+        # We want consecutive stretch widgets to split one 'block' of space between them
+        stretches = []
+        consecutive_stretches = defaultdict(list)
+        prev_stretch = None
+        for widget in widgets:
+            if widget.length_type == STRETCH:
+                if prev_stretch:
+                    consecutive_stretches[prev_stretch].append(widget)
+                else:
+                    stretches.append(widget)
+                    prev_stretch = widget
+            else:
+                prev_stretch = None
+
         if stretches:
             stretchspace = length - sum(
                 [i.length for i in widgets if i.length_type != STRETCH]
             )
             stretchspace = max(stretchspace, 0)
             num_stretches = len(stretches)
+
             if num_stretches == 1:
                 stretches[0].length = stretchspace
             else:
@@ -379,12 +387,13 @@ def _resize(self, length, widgets):
                 for i in widgets:
                     if i.length_type != STRETCH:
                         block += i.length
-                    else:
+                    elif i in stretches:  # False for consecutive_stretches
                         blocks.append(block)
                         block = 0
                 if block:
                     blocks.append(block)
                 interval = length // num_stretches
+
                 for idx, i in enumerate(stretches):
                     if idx == 0:
                         i.length = interval - blocks[0] - blocks[1] // 2
@@ -393,9 +402,19 @@ def _resize(self, length, widgets):
                     else:
                         i.length = int(interval - blocks[idx] / 2 - blocks[idx + 1] / 2)
                     stretchspace -= i.length
+
                 stretches[0].length += stretchspace // 2
                 stretches[-1].length += stretchspace - stretchspace // 2
 
+            for i, followers in consecutive_stretches.items():
+                length = i.length // (len(followers) + 1)
+                rem = i.length - length
+                i.length = length
+                for f in followers:
+                    f.length = length
+                    rem -= length
+                i.length += rem
+
         if self.horizontal:
             offset = self.border_width[3]
             for i in widgets:
diff --git a/libqtile/widget/base.py b/libqtile/widget/base.py
--- a/libqtile/widget/base.py
+++ b/libqtile/widget/base.py
@@ -148,10 +148,12 @@ def __init__(self, length, **config):
         if length in (bar.CALCULATED, bar.STRETCH):
             self.length_type = length
             self.length = 0
-        else:
-            assert isinstance(length, int)
+        elif isinstance(length, int):
             self.length_type = bar.STATIC
             self.length = length
+        else:
+            raise confreader.ConfigError("Widget width must be an int")
+
         self.configured = False
         self._futures: List[asyncio.TimerHandle] = []
 
@@ -183,8 +185,6 @@ def offset(self):
             return self.offsetx
         return self.offsety
 
-    # Do not start the name with "test", or nosetests will try to test it
-    # directly (prepend an underscore instead)
     def _test_orientation_compatibility(self, horizontal):
         if horizontal:
             if not self.orientations & ORIENTATION_HORIZONTAL:
@@ -204,6 +204,8 @@ def timer_setup(self):
         pass
 
     def _configure(self, qtile, bar):
+        self._test_orientation_compatibility(bar.horizontal)
+
         self.qtile = qtile
         self.bar = bar
         self.drawer = bar.window.create_drawer(self.bar.width, self.bar.height)


Test patch:
diff --git a/test/test_bar.py b/test/test_bar.py
--- a/test/test_bar.py
+++ b/test/test_bar.py
@@ -277,8 +277,8 @@ def offy(dwidget_list):
         assert off(dwidget_list) == [0, 10, 50, 90]
 
         b._resize(101, dwidget_list)
-        assert wd(dwidget_list) == [10, 40, 41, 10]
-        assert off(dwidget_list) == [0, 10, 50, 91]
+        assert wd(dwidget_list) == [10, 41, 40, 10]
+        assert off(dwidget_list) == [0, 10, 51, 91]
 
         dwidget_list = [
             DWidget(10, libqtile.bar.CALCULATED)
@@ -333,32 +333,6 @@ def _configure(self, qtile, bar):
         raise self.exception_class
 
 
-def test_incompatible_widget(manager_nospawn):
-    class IncompatibleWidgetConf(libqtile.confreader.Config):
-        groups = [libqtile.config.Group("a")]
-        screens = [
-            libqtile.config.Screen(
-                left=libqtile.bar.Bar(
-                    [
-                        # This widget doesn't support vertical orientation
-                        ExampleWidget(),
-                    ],
-                    10
-                ),
-            )
-        ]
-
-    # Ensure that adding a widget that doesn't support the orientation of the
-    # bar raises ConfigError
-    m = manager_nospawn.create_manager(IncompatibleWidgetConf)
-
-    try:
-        with pytest.raises(libqtile.confreader.ConfigError):
-            m.load_config()
-    finally:
-        m.core.finalize()
-
-
 def test_basic(manager_nospawn):
     config = GeomConf
     config.screens = [
@@ -437,6 +411,45 @@ def test_nospacer(manager_nospawn):
     libqtile.hook.clear()
 
 
+def test_consecutive_spacer(manager_nospawn):
+    config = GeomConf
+    config.screens = [
+        libqtile.config.Screen(
+            bottom=libqtile.bar.Bar(
+                [
+                    ExampleWidget(),  # Left
+                    libqtile.widget.Spacer(libqtile.bar.STRETCH),
+                    libqtile.widget.Spacer(libqtile.bar.STRETCH),
+                    ExampleWidget(),  # Centre
+                    ExampleWidget(),
+                    libqtile.widget.Spacer(libqtile.bar.STRETCH),
+                    ExampleWidget(),  # Right
+                ],
+                10
+            )
+        )
+    ]
+
+    manager_nospawn.start(config)
+
+    i = manager_nospawn.c.bar["bottom"].info()
+    assert i["widgets"][0]["offset"] == 0
+    assert i["widgets"][0]["width"] == 10
+    assert i["widgets"][1]["offset"] == 10
+    assert i["widgets"][1]["width"] == 190
+    assert i["widgets"][2]["offset"] == 200
+    assert i["widgets"][2]["width"] == 190
+    assert i["widgets"][3]["offset"] == 390
+    assert i["widgets"][3]["width"] == 10
+    assert i["widgets"][4]["offset"] == 400
+    assert i["widgets"][4]["width"] == 10
+    assert i["widgets"][5]["offset"] == 410
+    assert i["widgets"][5]["width"] == 380
+    assert i["widgets"][6]["offset"] == 790
+    assert i["widgets"][6]["width"] == 10
+    libqtile.hook.clear()
+
+
 def test_configure_broken_widgets(manager_nospawn):
     config = GeomConf
 
diff --git a/test/widgets/conftest.py b/test/widgets/conftest.py
--- a/test/widgets/conftest.py
+++ b/test/widgets/conftest.py
@@ -5,19 +5,28 @@
 
 import pytest
 
-import libqtile.bar
 import libqtile.config
 import libqtile.confreader
 import libqtile.layout
+from libqtile.bar import Bar
+from libqtile.widget.base import ORIENTATION_HORIZONTAL
 
 
 @pytest.fixture(scope='function')
 def fake_bar():
-    from libqtile.bar import Bar
-    height = 24
-    b = Bar([], height)
-    b.height = height
-    return b
+    return FakeBar([])
+
+
+class FakeBar(Bar):
+    def __init__(self, widgets, size=24, width=100, window=None, **config):
+        Bar.__init__(self, widgets, size, **config)
+        self.height = size
+        self.width = width
+        self.window = window
+        self.horizontal = ORIENTATION_HORIZONTAL
+
+    def draw(self):
+        pass
 
 
 TEST_DIR = os.path.dirname(os.path.abspath(__file__))
diff --git a/test/widgets/test_battery.py b/test/widgets/test_battery.py
--- a/test/widgets/test_battery.py
+++ b/test/widgets/test_battery.py
@@ -2,7 +2,6 @@
 import pytest
 
 from libqtile import images
-from libqtile.bar import Bar
 from libqtile.widget import battery
 from libqtile.widget.battery import (
     Battery,
@@ -10,7 +9,7 @@
     BatteryState,
     BatteryStatus,
 )
-from test.widgets.conftest import TEST_DIR
+from test.widgets.conftest import TEST_DIR, FakeBar
 
 
 class DummyBattery:
@@ -241,11 +240,7 @@ def test_battery_background(fake_qtile, fake_window, monkeypatch):
             low_percentage=0.2, low_background=low_background, background=background
         )
 
-    fakebar = Bar([batt], 24)
-    fakebar.window = fake_window
-    fakebar.width = 10
-    fakebar.height = 10
-    fakebar.draw = lambda *a, **k: None
+    fakebar = FakeBar([batt], window=fake_window)
     batt._configure(fake_qtile, fakebar)
 
     assert batt.background == background
diff --git a/test/widgets/test_caps_num_lock_indicator.py b/test/widgets/test_caps_num_lock_indicator.py
--- a/test/widgets/test_caps_num_lock_indicator.py
+++ b/test/widgets/test_caps_num_lock_indicator.py
@@ -23,8 +23,8 @@
 
 import pytest
 
-from libqtile.bar import Bar
 from libqtile.widget import caps_num_lock_indicator
+from test.widgets.conftest import FakeBar
 
 
 class MockCapsNumLockIndicator:
@@ -88,11 +88,7 @@ def patched_cnli(monkeypatch):
 
 def test_cnli(fake_qtile, patched_cnli, fake_window):
     widget = patched_cnli.CapsNumLockIndicator()
-    fakebar = Bar([widget], 24)
-    fakebar.window = fake_window
-    fakebar.width = 10
-    fakebar.height = 10
-    fakebar.draw = no_op
+    fakebar = FakeBar([widget], window=fake_window)
     widget._configure(fake_qtile, fakebar)
     text = widget.poll()
 
@@ -105,11 +101,7 @@ def test_cnli_caps_on(fake_qtile, patched_cnli, fake_window):
     # Simulate Caps on
     MockCapsNumLockIndicator.index = 1
 
-    fakebar = Bar([widget], 24)
-    fakebar.window = fake_window
-    fakebar.width = 10
-    fakebar.height = 10
-    fakebar.draw = no_op
+    fakebar = FakeBar([widget], window=fake_window)
     widget._configure(fake_qtile, fakebar)
     text = widget.poll()
 
@@ -122,11 +114,7 @@ def test_cnli_error_handling(fake_qtile, patched_cnli, fake_window):
     # Simulate a CalledProcessError exception
     MockCapsNumLockIndicator.is_error = True
 
-    fakebar = Bar([widget], 24)
-    fakebar.window = fake_window
-    fakebar.width = 10
-    fakebar.height = 10
-    fakebar.draw = no_op
+    fakebar = FakeBar([widget], window=fake_window)
     widget._configure(fake_qtile, fakebar)
     text = widget.poll()
 
diff --git a/test/widgets/test_check_updates.py b/test/widgets/test_check_updates.py
--- a/test/widgets/test_check_updates.py
+++ b/test/widgets/test_check_updates.py
@@ -1,11 +1,6 @@
 import libqtile.config
-from libqtile.bar import Bar
 from libqtile.widget.check_updates import CheckUpdates, Popen  # noqa: F401
-
-
-def no_op(*args, **kwargs):
-    pass
-
+from test.widgets.conftest import FakeBar
 
 wrong_distro = "Barch"
 good_distro = "Arch"
@@ -28,11 +23,7 @@ def test_update_available(fake_qtile, fake_window):
                        custom_command=cmd_1_line,
                        colour_have_updates="#123456"
                        )
-    fakebar = Bar([cu2], 24)
-    fakebar.window = fake_window
-    fakebar.width = 10
-    fakebar.height = 10
-    fakebar.draw = no_op
+    fakebar = FakeBar([cu2], window=fake_window)
     cu2._configure(fake_qtile, fakebar)
     text = cu2.poll()
     assert text == "Updates: 1"
@@ -42,11 +33,7 @@ def test_update_available(fake_qtile, fake_window):
 def test_no_update_available_without_no_update_string(fake_qtile, fake_window):
     """ test output with no update (without dedicated string nor color) """
     cu3 = CheckUpdates(distro=good_distro, custom_command=cmd_0_line)
-    fakebar = Bar([cu3], 24)
-    fakebar.window = fake_window
-    fakebar.width = 10
-    fakebar.height = 10
-    fakebar.draw = no_op
+    fakebar = FakeBar([cu3], window=fake_window)
     cu3._configure(fake_qtile, fakebar)
     text = cu3.poll()
     assert text == ""
@@ -61,11 +48,7 @@ def test_no_update_available_with_no_update_string_and_color_no_updates(
                        no_update_string=nus,
                        colour_no_updates="#654321"
                        )
-    fakebar = Bar([cu4], 24)
-    fakebar.window = fake_window
-    fakebar.width = 10
-    fakebar.height = 10
-    fakebar.draw = no_op
+    fakebar = FakeBar([cu4], window=fake_window)
     cu4._configure(fake_qtile, fakebar)
     text = cu4.poll()
     assert text == nus
@@ -79,11 +62,7 @@ def test_update_available_with_restart_indicator(monkeypatch, fake_qtile, fake_w
                        restart_indicator="*",
                        )
     monkeypatch.setattr("os.path.exists", lambda x: True)
-    fakebar = Bar([cu5], 24)
-    fakebar.window = fake_window
-    fakebar.width = 10
-    fakebar.height = 10
-    fakebar.draw = no_op
+    fakebar = FakeBar([cu5], window=fake_window)
     cu5._configure(fake_qtile, fakebar)
     text = cu5.poll()
     assert text == "Updates: 1*"
@@ -155,11 +134,7 @@ def test_update_process_error(fake_qtile, fake_window):
                        custom_command=cmd_error,
                        no_update_string="ERROR",
                        )
-    fakebar = Bar([cu7], 24)
-    fakebar.window = fake_window
-    fakebar.width = 10
-    fakebar.height = 10
-    fakebar.draw = no_op
+    fakebar = FakeBar([cu7], window=fake_window)
     cu7._configure(fake_qtile, fakebar)
     text = cu7.poll()
     assert text == "ERROR"
@@ -176,11 +151,7 @@ def mock_process(*args, **kwargs):
     cu8 = CheckUpdates(distro="Fedora")
 
     monkeypatch.setattr(cu8, "call_process", mock_process)
-    fakebar = Bar([cu8], 24)
-    fakebar.window = fake_window
-    fakebar.width = 10
-    fakebar.height = 10
-    fakebar.draw = no_op
+    fakebar = FakeBar([cu8], window=fake_window)
     cu8._configure(fake_qtile, fakebar)
     text = cu8.poll()
 
diff --git a/test/widgets/test_clock.py b/test/widgets/test_clock.py
--- a/test/widgets/test_clock.py
+++ b/test/widgets/test_clock.py
@@ -27,8 +27,8 @@
 import pytest
 
 import libqtile.config
-from libqtile.bar import Bar
 from libqtile.widget import clock
+from test.widgets.conftest import FakeBar
 
 
 def no_op(*args, **kwargs):
@@ -69,11 +69,7 @@ def test_clock(fake_qtile, monkeypatch, fake_window):
     """ test clock output with default settings """
     monkeypatch.setattr("libqtile.widget.clock.datetime", MockDatetime)
     clk1 = clock.Clock()
-    fakebar = Bar([clk1], 24)
-    fakebar.window = fake_window
-    fakebar.width = 10
-    fakebar.height = 10
-    fakebar.draw = no_op
+    fakebar = FakeBar([clk1], window=fake_window)
     clk1._configure(fake_qtile, fakebar)
     text = clk1.poll()
     assert text == "10:20"
@@ -104,11 +100,7 @@ def gettz(cls, val):
     # Fake datetime module just adds the timezone value to the time
     clk2 = clock.Clock(timezone="1")
 
-    fakebar = Bar([clk2], 24)
-    fakebar.window = fake_window
-    fakebar.width = 10
-    fakebar.height = 10
-    fakebar.draw = no_op
+    fakebar = FakeBar([clk2], window=fake_window)
     clk2._configure(fake_qtile, fakebar)
 
     # An invalid timezone current causes a TypeError
@@ -136,11 +128,7 @@ def gettz(cls, val):
     # Fake datetime module just adds the timezone value to the time
     clk3 = clock.Clock(timezone=1)
 
-    fakebar = Bar([clk3], 24)
-    fakebar.window = fake_window
-    fakebar.width = 10
-    fakebar.height = 10
-    fakebar.draw = no_op
+    fakebar = FakeBar([clk3], window=fake_window)
     clk3._configure(fake_qtile, fakebar)
     text = clk3.poll()
 
@@ -178,11 +166,7 @@ def timezone(cls, value):
     # Pytz timezone must be a string
     clk4 = clock.Clock(timezone="1")
 
-    fakebar = Bar([clk4], 24)
-    fakebar.window = fake_window
-    fakebar.width = 10
-    fakebar.height = 10
-    fakebar.draw = no_op
+    fakebar = FakeBar([clk4], window=fake_window)
     clk4._configure(fake_qtile, fakebar)
     text = clk4.poll()
 
@@ -217,11 +201,7 @@ def gettz(cls, val):
     # Pytz timezone must be a string
     clk5 = clock.Clock(timezone="1")
 
-    fakebar = Bar([clk5], 24)
-    fakebar.window = fake_window
-    fakebar.width = 10
-    fakebar.height = 10
-    fakebar.draw = no_op
+    fakebar = FakeBar([clk5], window=fake_window)
     clk5._configure(fake_qtile, fakebar)
     text = clk5.poll()
 
diff --git a/test/widgets/test_cmus.py b/test/widgets/test_cmus.py
--- a/test/widgets/test_cmus.py
+++ b/test/widgets/test_cmus.py
@@ -25,8 +25,8 @@
 import pytest
 
 import libqtile.config
-from libqtile.bar import Bar
 from libqtile.widget import cmus
+from test.widgets.conftest import FakeBar
 
 
 class MockCmusRemoteProcess:
@@ -144,11 +144,7 @@ def patched_cmus(monkeypatch):
 
 def test_cmus(fake_qtile, patched_cmus, fake_window):
     widget = patched_cmus.Cmus()
-    fakebar = Bar([widget], 24)
-    fakebar.window = fake_window
-    fakebar.width = 10
-    fakebar.height = 10
-    fakebar.draw = no_op
+    fakebar = FakeBar([widget], window=fake_window)
     widget._configure(fake_qtile, fakebar)
     text = widget.poll()
     assert text == "♫ Rick Astley - Never Gonna Give You Up"
@@ -165,11 +161,7 @@ def test_cmus_play_stopped(fake_qtile, patched_cmus, fake_window):
 
     # Set track to a stopped item
     MockCmusRemoteProcess.index = 2
-    fakebar = Bar([widget], 24)
-    fakebar.window = fake_window
-    fakebar.width = 10
-    fakebar.height = 10
-    fakebar.draw = no_op
+    fakebar = FakeBar([widget], window=fake_window)
     widget._configure(fake_qtile, fakebar)
     text = widget.poll()
 
@@ -219,11 +211,7 @@ def test_cmus_buttons(minimal_conf_noscreen, manager_nospawn, patched_cmus):
 def test_cmus_error_handling(fake_qtile, patched_cmus, fake_window):
     widget = patched_cmus.Cmus()
     MockCmusRemoteProcess.is_error = True
-    fakebar = Bar([widget], 24)
-    fakebar.window = fake_window
-    fakebar.width = 10
-    fakebar.height = 10
-    fakebar.draw = no_op
+    fakebar = FakeBar([widget], window=fake_window)
     widget._configure(fake_qtile, fakebar)
     text = widget.poll()
 
@@ -237,11 +225,7 @@ def test_escape_text(fake_qtile, patched_cmus, fake_window):
 
     # Set track to a stopped item
     MockCmusRemoteProcess.index = 3
-    fakebar = Bar([widget], 24)
-    fakebar.window = fake_window
-    fakebar.width = 10
-    fakebar.height = 10
-    fakebar.draw = no_op
+    fakebar = FakeBar([widget], window=fake_window)
     widget._configure(fake_qtile, fakebar)
     text = widget.poll()
 
diff --git a/test/widgets/test_df.py b/test/widgets/test_df.py
--- a/test/widgets/test_df.py
+++ b/test/widgets/test_df.py
@@ -26,12 +26,8 @@
 
 import pytest
 
-from libqtile.bar import Bar
 from libqtile.widget import df
-
-
-def no_op(*args, **kwargs):
-    pass
+from test.widgets.conftest import FakeBar
 
 
 class FakeOS(ModuleType):
@@ -72,12 +68,7 @@ def patched_df(monkeypatch):
 def test_df_no_warning(fake_qtile, fake_window):
     ''' Test no text when free space over threshold '''
     df1 = df.DF()
-    fakebar = Bar([df1], 24)
-    fakebar.horizontal = True
-    fakebar.window = fake_window
-    fakebar.width = 10
-    fakebar.height = 10
-    fakebar.draw = no_op
+    fakebar = FakeBar([df1], window=fake_window)
     df1._configure(fake_qtile, fakebar)
     text = df1.poll()
     assert text == ""
@@ -90,12 +81,7 @@ def test_df_no_warning(fake_qtile, fake_window):
 def test_df_always_visible(fake_qtile, fake_window):
     ''' Test text is always displayed '''
     df2 = df.DF(visible_on_warn=False)
-    fakebar = Bar([df2], 24)
-    fakebar.horizontal = True
-    fakebar.window = fake_window
-    fakebar.width = 10
-    fakebar.height = 10
-    fakebar.draw = no_op
+    fakebar = FakeBar([df2], window=fake_window)
     df2._configure(fake_qtile, fakebar)
     text = df2.poll()
 
@@ -113,12 +99,7 @@ def test_df_warn_space(fake_qtile, fake_window):
         below threshold
     '''
     df3 = df.DF(warn_space=40)
-    fakebar = Bar([df3], 24)
-    fakebar.horizontal = True
-    fakebar.window = fake_window
-    fakebar.width = 10
-    fakebar.height = 10
-    fakebar.draw = no_op
+    fakebar = FakeBar([df3], window=fake_window)
     df3._configure(fake_qtile, fakebar)
     text = df3.poll()
 
diff --git a/test/widgets/test_gmail_checker.py b/test/widgets/test_gmail_checker.py
--- a/test/widgets/test_gmail_checker.py
+++ b/test/widgets/test_gmail_checker.py
@@ -24,12 +24,8 @@
 from importlib import reload
 from types import ModuleType
 
-from libqtile.bar import Bar
 from libqtile.widget import gmail_checker
-
-
-def no_op(*args, **kwargs):
-    pass
+from test.widgets.conftest import FakeBar
 
 
 class FakeIMAP(ModuleType):
@@ -56,11 +52,7 @@ def test_gmail_checker_valid_response(fake_qtile, monkeypatch, fake_window):
     reload(gmail_checker)
 
     gmc = gmail_checker.GmailChecker(username="qtile", password="test")
-    fakebar = Bar([gmc], 24)
-    fakebar.window = fake_window
-    fakebar.width = 10
-    fakebar.height = 10
-    fakebar.draw = no_op
+    fakebar = FakeBar([gmc], window=fake_window)
     gmc._configure(fake_qtile, fakebar)
     text = gmc.poll()
     assert text == "inbox[10],unseen[2]"
@@ -71,11 +63,7 @@ def test_gmail_checker_invalid_response(fake_qtile, monkeypatch, fake_window):
     reload(gmail_checker)
 
     gmc = gmail_checker.GmailChecker()
-    fakebar = Bar([gmc], 24)
-    fakebar.window = fake_window
-    fakebar.width = 10
-    fakebar.height = 10
-    fakebar.draw = no_op
+    fakebar = FakeBar([gmc], window=fake_window)
     gmc._configure(fake_qtile, fakebar)
     text = gmc.poll()
     assert text == "UNKNOWN ERROR"
@@ -93,11 +81,7 @@ def test_gmail_checker_only_unseen(fake_qtile, monkeypatch, fake_window):
         username="qtile",
         password="test"
     )
-    fakebar = Bar([gmc], 24)
-    fakebar.window = fake_window
-    fakebar.width = 10
-    fakebar.height = 10
-    fakebar.draw = no_op
+    fakebar = FakeBar([gmc], window=fake_window)
     gmc._configure(fake_qtile, fakebar)
     text = gmc.poll()
     assert text == "unseen[2]"
diff --git a/test/widgets/test_imapwidget.py b/test/widgets/test_imapwidget.py
--- a/test/widgets/test_imapwidget.py
+++ b/test/widgets/test_imapwidget.py
@@ -26,11 +26,7 @@
 
 import pytest
 
-from libqtile.bar import Bar
-
-
-def no_op(*args, **kwargs):
-    pass
+from test.widgets.conftest import FakeBar
 
 
 class FakeIMAP(ModuleType):
@@ -82,11 +78,7 @@ def patched_imap(monkeypatch):
 
 def test_imapwidget(fake_qtile, monkeypatch, fake_window, patched_imap):
     imap = patched_imap.ImapWidget(user="qtile")
-    fakebar = Bar([imap], 24)
-    fakebar.window = fake_window
-    fakebar.width = 10
-    fakebar.height = 10
-    fakebar.draw = no_op
+    fakebar = FakeBar([imap], window=fake_window)
     imap._configure(fake_qtile, fakebar)
     text = imap.poll()
     assert text == "INBOX: 2"
@@ -95,11 +87,7 @@ def test_imapwidget(fake_qtile, monkeypatch, fake_window, patched_imap):
 def test_imapwidget_keyring_error(fake_qtile, monkeypatch, fake_window, patched_imap):
     patched_imap.keyring.valid = False
     imap = patched_imap.ImapWidget(user="qtile")
-    fakebar = Bar([imap], 24)
-    fakebar.window = fake_window
-    fakebar.width = 10
-    fakebar.height = 10
-    fakebar.draw = no_op
+    fakebar = FakeBar([imap], window=fake_window)
     imap._configure(fake_qtile, fakebar)
     text = imap.poll()
     assert text == "Gnome Keyring Error"
@@ -115,11 +103,7 @@ def test_imapwidget_password_none(fake_qtile, monkeypatch, fake_window, patched_
     patched_imap.keyring.error = False
 
     imap = patched_imap.ImapWidget(user="qtile")
-    fakebar = Bar([imap], 24)
-    fakebar.window = fake_window
-    fakebar.width = 10
-    fakebar.height = 10
-    fakebar.draw = no_op
+    fakebar = FakeBar([imap], window=fake_window)
     imap._configure(fake_qtile, fakebar)
     with pytest.raises(AttributeError):
         with pytest.raises(UnboundLocalError):
diff --git a/test/widgets/test_keyboardkbdd.py b/test/widgets/test_keyboardkbdd.py
--- a/test/widgets/test_keyboardkbdd.py
+++ b/test/widgets/test_keyboardkbdd.py
@@ -31,11 +31,7 @@
 
 import pytest
 
-from libqtile.bar import Bar
-
-
-def no_op(*args, **kwargs):
-    pass
+from test.widgets.conftest import FakeBar
 
 
 async def mock_signal_receiver(*args, **kwargs):
@@ -80,11 +76,7 @@ def patched_widget(monkeypatch):
 def test_keyboardkbdd_process_running(fake_qtile, patched_widget, fake_window):
     MockSpawn.call_count = 1
     kbd = patched_widget.KeyboardKbdd(configured_keyboards=["gb", "us"])
-    fakebar = Bar([kbd], 24)
-    fakebar.window = fake_window
-    fakebar.width = 10
-    fakebar.height = 10
-    fakebar.draw = no_op
+    fakebar = FakeBar([kbd], window=fake_window)
     kbd._configure(fake_qtile, fakebar)
     assert kbd.is_kbdd_running
     assert kbd.keyboard == "gb"
@@ -103,11 +95,7 @@ def test_keyboardkbdd_process_running(fake_qtile, patched_widget, fake_window):
 def test_keyboardkbdd_process_not_running(fake_qtile, patched_widget, fake_window):
     MockSpawn.call_count = 0
     kbd = patched_widget.KeyboardKbdd(configured_keyboards=["gb", "us"])
-    fakebar = Bar([kbd], 24)
-    fakebar.window = fake_window
-    fakebar.width = 10
-    fakebar.height = 10
-    fakebar.draw = no_op
+    fakebar = FakeBar([kbd], window=fake_window)
     kbd._configure(fake_qtile, fakebar)
     assert not kbd.is_kbdd_running
     assert kbd.keyboard == "N/A"
@@ -126,11 +114,7 @@ def test_keyboard_kbdd_colours(fake_qtile, patched_widget, fake_window):
         configured_keyboards=["gb", "us"],
         colours=["#ff0000", "#00ff00"]
     )
-    fakebar = Bar([kbd], 24)
-    fakebar.window = fake_window
-    fakebar.width = 10
-    fakebar.height = 10
-    fakebar.draw = no_op
+    fakebar = FakeBar([kbd], window=fake_window)
     kbd._configure(fake_qtile, fakebar)
 
     # Create a message with the index of the active keyboard
diff --git a/test/widgets/test_moc.py b/test/widgets/test_moc.py
--- a/test/widgets/test_moc.py
+++ b/test/widgets/test_moc.py
@@ -25,8 +25,8 @@
 import pytest
 
 import libqtile.config
-from libqtile.bar import Bar
 from libqtile.widget import moc
+from test.widgets.conftest import FakeBar
 
 
 class MockMocpProcess:
@@ -103,11 +103,7 @@ def patched_moc(fake_qtile, monkeypatch, fake_window):
     MockMocpProcess.reset()
     monkeypatch.setattr(widget, "call_process", MockMocpProcess.run)
     monkeypatch.setattr("libqtile.widget.moc.subprocess.Popen", MockMocpProcess.run)
-    fakebar = Bar([widget], 24)
-    fakebar.window = fake_window
-    fakebar.width = 10
-    fakebar.height = 10
-    fakebar.draw = no_op
+    fakebar = FakeBar([widget], window=fake_window)
     widget._configure(fake_qtile, fakebar)
     return widget
 
diff --git a/test/widgets/test_mpris2widget.py b/test/widgets/test_mpris2widget.py
--- a/test/widgets/test_mpris2widget.py
+++ b/test/widgets/test_mpris2widget.py
@@ -26,7 +26,7 @@
 
 import pytest
 
-from libqtile.bar import Bar
+from test.widgets.conftest import FakeBar
 
 
 def no_op(*args, **kwargs):
@@ -124,11 +124,7 @@ def patched_module(monkeypatch):
 
 def test_mpris2_signal_handling(fake_qtile, patched_module, fake_window):
     mp = patched_module.Mpris2(scroll_chars=20, scroll_wait_intervals=5)
-    fakebar = Bar([mp], 24)
-    fakebar.window = fake_window
-    fakebar.width = 10
-    fakebar.height = 10
-    fakebar.draw = no_op
+    fakebar = FakeBar([mp], window=fake_window)
     mp.timeout_add = fake_timer
     mp._configure(fake_qtile, fakebar)
 
@@ -187,11 +183,7 @@ def test_mpris2_signal_handling(fake_qtile, patched_module, fake_window):
 
 def test_mpris2_custom_stop_text(fake_qtile, patched_module, fake_window):
     mp = patched_module.Mpris2(stop_pause_text="Test Paused")
-    fakebar = Bar([mp], 24)
-    fakebar.window = fake_window
-    fakebar.width = 10
-    fakebar.height = 10
-    fakebar.draw = no_op
+    fakebar = FakeBar([mp], window=fake_window)
     mp.timeout_add = fake_timer
     mp._configure(fake_qtile, fakebar)
     mp.configured = True
@@ -208,11 +200,7 @@ def test_mpris2_custom_stop_text(fake_qtile, patched_module, fake_window):
 
 def test_mpris2_no_metadata(fake_qtile, patched_module, fake_window):
     mp = patched_module.Mpris2(stop_pause_text="Test Paused")
-    fakebar = Bar([mp], 24)
-    fakebar.window = fake_window
-    fakebar.width = 10
-    fakebar.height = 10
-    fakebar.draw = no_op
+    fakebar = FakeBar([mp], window=fake_window)
     mp.timeout_add = fake_timer
     mp._configure(fake_qtile, fakebar)
     mp.configured = True
@@ -225,11 +213,7 @@ def test_mpris2_no_scroll(fake_qtile, patched_module, fake_window):
     # If no scrolling, then the update function creates the text to display
     # and draws the bar.
     mp = patched_module.Mpris2(scroll_chars=None)
-    fakebar = Bar([mp], 24)
-    fakebar.window = fake_window
-    fakebar.width = 10
-    fakebar.height = 10
-    fakebar.draw = no_op
+    fakebar = FakeBar([mp], window=fake_window)
     mp.timeout_add = fake_timer
     mp._configure(fake_qtile, fakebar)
     mp.configured = True
@@ -243,11 +227,7 @@ def test_mpris2_no_scroll(fake_qtile, patched_module, fake_window):
 
 def test_mpris2_clear_after_scroll(fake_qtile, patched_module, fake_window):
     mp = patched_module.Mpris2(scroll_chars=60, scroll_wait_intervals=2)
-    fakebar = Bar([mp], 24)
-    fakebar.window = fake_window
-    fakebar.width = 10
-    fakebar.height = 10
-    fakebar.draw = no_op
+    fakebar = FakeBar([mp], window=fake_window)
     mp.timeout_add = fake_timer
     mp._configure(fake_qtile, fakebar)
     mp.configured = True
diff --git a/test/widgets/test_net.py b/test/widgets/test_net.py
--- a/test/widgets/test_net.py
+++ b/test/widgets/test_net.py
@@ -26,11 +26,7 @@
 
 import pytest
 
-from libqtile.bar import Bar
-
-
-def no_op(*args, **kwargs):
-    pass
+from test.widgets.conftest import FakeBar
 
 
 # Net widget only needs bytes_recv/sent attributes
@@ -73,11 +69,7 @@ def build_widget(**kwargs):
                 format='{interface}: U {up} D {down} T {total}',
                 **kwargs
             )
-        fakebar = Bar([widget], 24)
-        fakebar.window = fake_window
-        fakebar.width = 10
-        fakebar.height = 10
-        fakebar.draw = no_op
+        fakebar = FakeBar([widget], window=fake_window)
         widget._configure(fake_qtile, fakebar)
 
         return widget
diff --git a/test/widgets/test_nvidia_sensors.py b/test/widgets/test_nvidia_sensors.py
--- a/test/widgets/test_nvidia_sensors.py
+++ b/test/widgets/test_nvidia_sensors.py
@@ -1,10 +1,10 @@
 import pytest
 
-from libqtile import bar
 from libqtile.widget.nvidia_sensors import (
     NvidiaSensors,
     _all_sensors_names_correct,
 )
+from test.widgets.conftest import FakeBar
 
 
 def test_nvidia_sensors_input_regex():
@@ -34,11 +34,7 @@ def fake_nvidia(fake_qtile, monkeypatch, fake_window):
     # on the test computer having the required hardware.
     monkeypatch.setattr(n, "call_process", MockNvidiaSMI.get_temperature)
     monkeypatch.setattr("libqtile.widget.moc.subprocess.Popen", MockNvidiaSMI.get_temperature)
-    fakebar = bar.Bar([n], 24)
-    fakebar.window = fake_window
-    fakebar.width = 10
-    fakebar.height = 10
-    fakebar.draw = None
+    fakebar = FakeBar([n], window=fake_window)
     n._configure(fake_qtile, fakebar)
     return n
 
diff --git a/test/widgets/test_pomodoro.py b/test/widgets/test_pomodoro.py
--- a/test/widgets/test_pomodoro.py
+++ b/test/widgets/test_pomodoro.py
@@ -22,8 +22,8 @@
 
 import pytest
 
-from libqtile.bar import Bar
 from libqtile.widget import pomodoro
+from test.widgets.conftest import FakeBar
 
 COLOR_INACTIVE = "123456"
 COLOR_ACTIVE = "654321"
@@ -35,10 +35,6 @@
 PREFIX_PAUSED = "PAUSING"
 
 
-def no_op(*args, **kwargs):
-    pass
-
-
 # Mock Datetime object that returns a set datetime but can
 # be adjusted via the '_adjustment' property
 class MockDatetime(datetime):
@@ -75,11 +71,7 @@ def test_pomodoro(fake_qtile, fake_window):
         prefix_paused=PREFIX_PAUSED
     )
 
-    fakebar = Bar([widget], 24)
-    fakebar.window = fake_window
-    fakebar.width = 10
-    fakebar.height = 10
-    fakebar.draw = no_op
+    fakebar = FakeBar([widget], window=fake_window)
     widget._configure(fake_qtile, fakebar)
 
     # When we start, widget is inactive
diff --git a/test/widgets/test_widget_init_configure.py b/test/widgets/test_widget_init_configure.py
--- a/test/widgets/test_widget_init_configure.py
+++ b/test/widgets/test_widget_init_configure.py
@@ -27,7 +27,10 @@
 import libqtile.confreader
 import libqtile.layout
 import libqtile.widget as widgets
+from libqtile.widget.base import ORIENTATION_VERTICAL
+from libqtile.widget.clock import Clock
 from libqtile.widget.crashme import _CrashMe
+from test.widgets.conftest import FakeBar
 
 # This file runs a very simple test to check that widgets can be initialised
 # and that keyword arguments are added to default values.
@@ -117,3 +120,11 @@ def test_widget_init_config(manager_nospawn, minimal_conf_noscreen, widget_class
 
     # Check widget is registered by checking names of widgets in bar
     assert i["widgets"][0]["name"] == widget.name
+
+
+def test_incompatible_orientation(fake_qtile, fake_window):
+    clk1 = Clock()
+    clk1.orientations = ORIENTATION_VERTICAL
+    fakebar = FakeBar([clk1], window=fake_window)
+    with pytest.raises(libqtile.confreader.ConfigError):
+        clk1._configure(fake_qtile, fakebar)
diff --git a/test/widgets/test_widgetbox.py b/test/widgets/test_widgetbox.py
--- a/test/widgets/test_widgetbox.py
+++ b/test/widgets/test_widgetbox.py
@@ -1,10 +1,6 @@
 import libqtile.config
-from libqtile.bar import Bar
 from libqtile.widget import TextBox, WidgetBox
-
-
-def no_op(*args, **kwargs):
-    pass
+from test.widgets.conftest import FakeBar
 
 
 def test_widgetbox_widget(fake_qtile, fake_window):
@@ -18,11 +14,7 @@ def test_widgetbox_widget(fake_qtile, fake_window):
                            fontsize=10)
 
     # Create a bar and set attributes needed to run widget
-    fakebar = Bar([widget_box], 24)
-    fakebar.window = fake_window
-    fakebar.width = 10
-    fakebar.height = 10
-    fakebar.draw = no_op
+    fakebar = FakeBar([widget_box], window=fake_window)
 
     # Configure the widget box
     widget_box._configure(fake_qtile, fakebar)