Repository: googlefonts/ufo2ft
Pull request #716 (2023-03-01 17:18:58)

Rename dottedCircleFilter module
https://github.com/googlefonts/ufo2ft/pull/715 was wrong actually and broke main. I decided to rename the filter module instead and put in a redirection stub.

Closes https://github.com/googlefonts/ufo2ft/issues/707.

khaledhosny: I suggest renaming then adding the old name in two different commits so that git can still track file history.
anthrotype: please do what khaled said, otherwise LGTM (merge when done thanks)
madig: Ok, will do.

Hm, user warnings aren't displayed to users by default. I'll subclass and log a regular warning.

Patch:
diff --git a/Lib/ufo2ft/filters/__init__.py b/Lib/ufo2ft/filters/__init__.py
index 912f337..ce4e405 100644
--- a/Lib/ufo2ft/filters/__init__.py
+++ b/Lib/ufo2ft/filters/__init__.py
@@ -9,7 +9,7 @@ from .base import BaseFilter
 from .cubicToQuadratic import CubicToQuadraticFilter
 from .decomposeComponents import DecomposeComponentsFilter
 from .decomposeTransformedComponents import DecomposeTransformedComponentsFilter
-from .dottedCircleFilter import DottedCircleFilter
+from .dottedCircle import DottedCircleFilter
 from .explodeColorLayerGlyphs import ExplodeColorLayerGlyphsFilter
 from .flattenComponents import FlattenComponentsFilter
 from .propagateAnchors import PropagateAnchorsFilter
@@ -48,7 +48,9 @@ def getFilterClass(filterName, pkg="ufo2ft.filters"):
     moduleName = filterName[0].lower() + filterName[1:]
     module = importlib.import_module(".".join([pkg, moduleName]))
     # if filter name is 'Foo Bar', the class should be called 'FooBarFilter'
-    className = filterName[0].upper() + filterName[1:] + "Filter"
+    className = filterName[0].upper() + filterName[1:]
+    if not className.endswith("Filter"):
+        className += "Filter"
     return getattr(module, className)
 
 
diff --git a/Lib/ufo2ft/filters/dottedCircle.py b/Lib/ufo2ft/filters/dottedCircle.py
new file mode 100644
index 0000000..68fcfea
--- /dev/null
+++ b/Lib/ufo2ft/filters/dottedCircle.py
@@ -0,0 +1,262 @@
+"""
+Dotted Circle Filter
+
+This filter checks whether a font contains a glyph for U+25CC (DOTTED CIRCLE),
+which is inserted by complex shapers to display mark glyphs which have no
+associated base glyph, usually as a result of broken clusters but also for
+pedagogical reasons. (For example, to display the marks in a table of glyphs.)
+
+If no dotted circle glyph is present in the font, then one is drawn and added.
+
+Next, the filter creates any additional anchors for the dotted circle glyph to
+ensure that all marks can be attached to it. It does this by gathering a list
+of anchors, finding the set of base glyphs for each anchor, computing the
+average position of the anchor on the base glyph (relative to the glyph's width),
+and then creating an anchor at that average position on the dotted circle glyph.
+
+The filter must be run as a "pre" filter. This can be done from the command
+line like so::
+
+    fontmake -o ttf -g MyFont.glyphs --filter "DottedCircleFilter(pre=True)"
+
+or in the ``lib.plist`` file of a UFO::
+
+    <key>com.github.googlei18n.ufo2ft.filters</key>
+    <array>
+      <dict>
+        <key>name</key>
+        <string>dottedCircle</string>
+        <key>pre</key>
+        <true/>
+      </dict>
+    </array>
+
+The filter supports the following options:
+
+margin
+    When drawing a dotted circle, the vertical space in units around the dotted
+    circle.
+sidebearings
+    When drawing a dotted circle, additional horizontal space in units around
+    the dotted circle.
+dots
+    Number of dots in the circle.
+
+"""
+import logging
+import math
+from statistics import mean
+
+from fontTools.misc.fixedTools import otRound
+
+from ufo2ft.constants import OPENTYPE_CATEGORIES_KEY
+from ufo2ft.featureCompiler import parseLayoutFeatures
+from ufo2ft.featureWriters import ast
+from ufo2ft.filters import BaseFilter
+from ufo2ft.fontInfoData import getAttrWithFallback
+from ufo2ft.util import _getNewGlyphFactory, _GlyphSet, _LazyFontName, _setGlyphMargin
+
+logger = logging.getLogger(__name__)
+
+DO_NOTHING = -1  # Sentinel value (not a valid glyph name)
+
+# Length of cubic Bezier handle used when drawing quarter circles.
+# See https://pomax.github.io/bezierinfo/#circles_cubic
+CIRCULAR_SUPERNESS = 0.551784777779014
+
+
+def circle(pen, origin, radius):
+    w = (origin[0] - radius, origin[1])
+    n = (origin[0], origin[1] + radius)
+    e = (origin[0] + radius, origin[1])
+    s = (origin[0], origin[1] - radius)
+
+    pen.moveTo(w)
+    pen.curveTo(
+        (w[0], w[1] + radius * CIRCULAR_SUPERNESS),
+        (n[0] - radius * CIRCULAR_SUPERNESS, n[1]),
+        n,
+    )
+    pen.curveTo(
+        (n[0] + radius * CIRCULAR_SUPERNESS, n[1]),
+        (e[0], e[1] + radius * CIRCULAR_SUPERNESS),
+        e,
+    )
+    pen.curveTo(
+        (e[0], e[1] - radius * CIRCULAR_SUPERNESS),
+        (s[0] + radius * CIRCULAR_SUPERNESS, s[1]),
+        s,
+    )
+    pen.curveTo(
+        (s[0] - radius * CIRCULAR_SUPERNESS, s[1]),
+        (w[0], w[1] - radius * CIRCULAR_SUPERNESS),
+        w,
+    )
+    pen.closePath()
+
+
+class DottedCircleFilter(BaseFilter):
+    _kwargs = {"margin": 80, "sidebearing": 160, "dots": 12}
+
+    def __call__(self, font, glyphSet=None):
+        fontName = _LazyFontName(font)
+        if glyphSet is not None and getattr(glyphSet, "name", None):
+            logger.info("Running %s on %s-%s", self.name, fontName, glyphSet.name)
+        else:
+            logger.info("Running %s on %s", self.name, fontName)
+
+        if glyphSet is None:
+            glyphSet = _GlyphSet.from_layer(font)
+
+        self.set_context(font, glyphSet)
+        added_glyph = False
+        dotted_circle_glyph = self.check_dotted_circle()
+
+        if dotted_circle_glyph == DO_NOTHING:
+            return []
+
+        if not dotted_circle_glyph:
+            dotted_circle_glyph = self.draw_dotted_circle(glyphSet)
+            added_glyph = True
+
+        added_anchors = self.check_and_add_anchors(dotted_circle_glyph)
+
+        if added_anchors:
+            self.ensure_base(dotted_circle_glyph)
+
+        if added_glyph or added_anchors:
+            return [dotted_circle_glyph.name]
+        else:
+            return []
+
+    def check_dotted_circle(self):
+        """Check for the presence of a dotted circle glyph and return it"""
+        font = self.context.font
+        glyphset = self.context.glyphSet
+        dotted_circle = next((g.name for g in font if 0x25CC in g.unicodes), None)
+        if dotted_circle:
+            if dotted_circle not in glyphset:
+                logger.debug(
+                    "Found dotted circle glyph %s in font but not in glyphset",
+                    dotted_circle,
+                )
+                return DO_NOTHING
+            logger.debug("Found dotted circle glyph %s", dotted_circle)
+            return glyphset[dotted_circle]
+
+    def draw_dotted_circle(self, glyphSet):
+        """Add a new dotted circle glyph, drawing its outlines"""
+        font = self.context.font
+        logger.debug("Adding dotted circle glyph")
+
+        proto = font.layers.defaultLayer.instantiateGlyphObject()
+        glyph = _getNewGlyphFactory(proto)(name="uni25CC", unicodes=[0x25CC])
+        pen = glyph.getPen()
+
+        xHeight = getAttrWithFallback(font.info, "xHeight")
+        bigradius = (xHeight - 2 * self.options.margin) / 2
+        littleradius = bigradius / 6
+        left = self.options.sidebearing + littleradius
+        right = self.options.sidebearing + bigradius * 2 - littleradius
+        middleY = xHeight / 2
+        middleX = (left + right) / 2
+        subangle = 2 * math.pi / self.options.dots
+        for t in range(self.options.dots):
+            angle = t * subangle
+            cx = middleX + bigradius * math.cos(angle)
+            cy = middleY + bigradius * math.sin(angle)
+            circle(pen, (cx, cy), littleradius)
+
+        _setGlyphMargin(glyph, "right", self.options.sidebearing)
+
+        glyphSet["uni25CC"] = glyph
+        return glyph
+
+    def check_and_add_anchors(self, dotted_circle_glyph):
+        """Check that all mark-attached anchors are present on the dotted
+        circle glyph, synthesizing a position for any missing anchors."""
+        font = self.context.font
+
+        # First we will gather information about all the anchors in the
+        # font at present; for the anchors on marks (starting with "_")
+        # we just want to know their names, so we can match them with
+        # bases later. For the anchors on bases, we also want to store
+        # the position of the anchor so we can average them.
+        all_anchors = {}
+        any_added = False
+        for glyph in font:
+            width = None
+            try:
+                bounds = glyph.getBounds(font)
+                if bounds:
+                    width = bounds.xMax - bounds.xMin
+            except AttributeError:
+                bounds = glyph.bounds
+                if bounds:
+                    width = bounds[2] - bounds[0]
+            if width is None:
+                width = glyph.width
+            for anchor in glyph.anchors:
+                if anchor.name.startswith("_"):
+                    all_anchors[anchor.name] = []
+                    continue
+                if not width:
+                    continue
+                x_percentage = anchor.x / width
+                all_anchors.setdefault(anchor.name, []).append((x_percentage, anchor.y))
+
+        # Now we move to the dotted circle. What anchors do we have already?
+        dsanchors = set([a.name for a in dotted_circle_glyph.anchors])
+        for anchor, positions in all_anchors.items():
+            # Skip existing anchors on the dotted-circle, and any anchors
+            # which don't have a matching mark glyph (mark-to-lig etc.).
+            if anchor in dsanchors or f"_{anchor}" not in all_anchors:
+                continue
+
+            # And now we're creating a new one
+            anchor_x = dotted_circle_glyph.width * mean([v[0] for v in positions])
+            anchor_y = mean([v[1] for v in positions])
+            logger.debug(
+                "Adding anchor %s to dotted circle glyph at %i,%i",
+                anchor,
+                anchor_x,
+                anchor_y,
+            )
+            dotted_circle_glyph.appendAnchor(
+                {"x": otRound(anchor_x), "y": otRound(anchor_y), "name": anchor}
+            )
+            any_added = True
+        return any_added
+
+    # We have added some anchors to the dotted circle glyph. Now we need to
+    # ensure the glyph is a base (and specifically a base glyph, not just
+    # unclassified), or else it won't be in the list of base glyphs when
+    # we come to the mark features writer, and all our work will be for nothing.
+    # Also note that if we had a dotted circle glyph in the font already and
+    # we have come from Glyphs, glyphsLib would only consider the glyph to
+    # be a base if it has anchors, and it might not have had any when glyphsLib
+    # wrote the GDEF table.
+    # So we have to go digging around for a GDEF table and modify it.
+    def ensure_base(self, dotted_circle_glyph):
+        dotted_circle = dotted_circle_glyph.name
+        font = self.context.font
+        feaFile = parseLayoutFeatures(font)
+        if ast.findTable(feaFile, "GDEF") is None:
+            # We have no GDEF table. GDEFFeatureWriter will create one
+            # using the font's lib.
+            if OPENTYPE_CATEGORIES_KEY in font.lib:
+                font.lib[OPENTYPE_CATEGORIES_KEY][dotted_circle] = "base"
+            return
+        # We have GDEF table, so we need to find the GlyphClassDef, and add
+        # ourselves to the baseGlyphs set.
+        for st in feaFile.statements:
+            if isinstance(st, ast.TableBlock) and st.name == "GDEF":
+                for st2 in st.statements:
+                    if isinstance(st2, ast.GlyphClassDefStatement):
+                        if (
+                            st2.baseGlyphs
+                            and dotted_circle not in st2.baseGlyphs.glyphSet()
+                        ):
+                            st2.baseGlyphs.glyphs.append(dotted_circle)
+        # And then put the modified feature file back into the font
+        font.features.text = feaFile.asFea()
diff --git a/Lib/ufo2ft/filters/dottedCircleFilter.py b/Lib/ufo2ft/filters/dottedCircleFilter.py
index 553ace0..a26be18 100644
--- a/Lib/ufo2ft/filters/dottedCircleFilter.py
+++ b/Lib/ufo2ft/filters/dottedCircleFilter.py
@@ -1,262 +1,12 @@
-"""
-Dotted Circle Filter
+"""This is a redirection stub, because the original module was
+misnamed."""
 
-This filter checks whether a font contains a glyph for U+25CC (DOTTED CIRCLE),
-which is inserted by complex shapers to display mark glyphs which have no
-associated base glyph, usually as a result of broken clusters but also for
-pedagogical reasons. (For example, to display the marks in a table of glyphs.)
+import warnings
 
-If no dotted circle glyph is present in the font, then one is drawn and added.
+from .dottedCircle import DottedCircleFilter  # noqa
 
-Next, the filter creates any additional anchors for the dotted circle glyph to
-ensure that all marks can be attached to it. It does this by gathering a list
-of anchors, finding the set of base glyphs for each anchor, computing the
-average position of the anchor on the base glyph (relative to the glyph's width),
-and then creating an anchor at that average position on the dotted circle glyph.
-
-The filter must be run as a "pre" filter. This can be done from the command
-line like so::
-
-    fontmake -o ttf -g MyFont.glyphs --filter "DottedCircleFilter(pre=True)"
-
-or in the ``lib.plist`` file of a UFO::
-
-    <key>com.github.googlei18n.ufo2ft.filters</key>
-    <array>
-      <dict>
-        <key>name</key>
-        <string>DottedCircleFilter</string>
-        <key>pre</key>
-        <true/>
-      </dict>
-    </array>
-
-The filter supports the following options:
-
-margin
-    When drawing a dotted circle, the vertical space in units around the dotted
-    circle.
-sidebearings
-    When drawing a dotted circle, additional horizontal space in units around
-    the dotted circle.
-dots
-    Number of dots in the circle.
-
-"""
-import logging
-import math
-from statistics import mean
-
-from fontTools.misc.fixedTools import otRound
-
-from ufo2ft.constants import OPENTYPE_CATEGORIES_KEY
-from ufo2ft.featureCompiler import parseLayoutFeatures
-from ufo2ft.featureWriters import ast
-from ufo2ft.filters import BaseFilter
-from ufo2ft.fontInfoData import getAttrWithFallback
-from ufo2ft.util import _getNewGlyphFactory, _GlyphSet, _LazyFontName, _setGlyphMargin
-
-logger = logging.getLogger(__name__)
-
-DO_NOTHING = -1  # Sentinel value (not a valid glyph name)
-
-# Length of cubic Bezier handle used when drawing quarter circles.
-# See https://pomax.github.io/bezierinfo/#circles_cubic
-CIRCULAR_SUPERNESS = 0.551784777779014
-
-
-def circle(pen, origin, radius):
-    w = (origin[0] - radius, origin[1])
-    n = (origin[0], origin[1] + radius)
-    e = (origin[0] + radius, origin[1])
-    s = (origin[0], origin[1] - radius)
-
-    pen.moveTo(w)
-    pen.curveTo(
-        (w[0], w[1] + radius * CIRCULAR_SUPERNESS),
-        (n[0] - radius * CIRCULAR_SUPERNESS, n[1]),
-        n,
-    )
-    pen.curveTo(
-        (n[0] + radius * CIRCULAR_SUPERNESS, n[1]),
-        (e[0], e[1] + radius * CIRCULAR_SUPERNESS),
-        e,
-    )
-    pen.curveTo(
-        (e[0], e[1] - radius * CIRCULAR_SUPERNESS),
-        (s[0] + radius * CIRCULAR_SUPERNESS, s[1]),
-        s,
-    )
-    pen.curveTo(
-        (s[0] - radius * CIRCULAR_SUPERNESS, s[1]),
-        (w[0], w[1] - radius * CIRCULAR_SUPERNESS),
-        w,
-    )
-    pen.closePath()
-
-
-class DottedCircleFilter(BaseFilter):
-    _kwargs = {"margin": 80, "sidebearing": 160, "dots": 12}
-
-    def __call__(self, font, glyphSet=None):
-        fontName = _LazyFontName(font)
-        if glyphSet is not None and getattr(glyphSet, "name", None):
-            logger.info("Running %s on %s-%s", self.name, fontName, glyphSet.name)
-        else:
-            logger.info("Running %s on %s", self.name, fontName)
-
-        if glyphSet is None:
-            glyphSet = _GlyphSet.from_layer(font)
-
-        self.set_context(font, glyphSet)
-        added_glyph = False
-        dotted_circle_glyph = self.check_dotted_circle()
-
-        if dotted_circle_glyph == DO_NOTHING:
-            return []
-
-        if not dotted_circle_glyph:
-            dotted_circle_glyph = self.draw_dotted_circle(glyphSet)
-            added_glyph = True
-
-        added_anchors = self.check_and_add_anchors(dotted_circle_glyph)
-
-        if added_anchors:
-            self.ensure_base(dotted_circle_glyph)
-
-        if added_glyph or added_anchors:
-            return [dotted_circle_glyph.name]
-        else:
-            return []
-
-    def check_dotted_circle(self):
-        """Check for the presence of a dotted circle glyph and return it"""
-        font = self.context.font
-        glyphset = self.context.glyphSet
-        dotted_circle = next((g.name for g in font if 0x25CC in g.unicodes), None)
-        if dotted_circle:
-            if dotted_circle not in glyphset:
-                logger.debug(
-                    "Found dotted circle glyph %s in font but not in glyphset",
-                    dotted_circle,
-                )
-                return DO_NOTHING
-            logger.debug("Found dotted circle glyph %s", dotted_circle)
-            return glyphset[dotted_circle]
-
-    def draw_dotted_circle(self, glyphSet):
-        """Add a new dotted circle glyph, drawing its outlines"""
-        font = self.context.font
-        logger.debug("Adding dotted circle glyph")
-
-        proto = font.layers.defaultLayer.instantiateGlyphObject()
-        glyph = _getNewGlyphFactory(proto)(name="uni25CC", unicodes=[0x25CC])
-        pen = glyph.getPen()
-
-        xHeight = getAttrWithFallback(font.info, "xHeight")
-        bigradius = (xHeight - 2 * self.options.margin) / 2
-        littleradius = bigradius / 6
-        left = self.options.sidebearing + littleradius
-        right = self.options.sidebearing + bigradius * 2 - littleradius
-        middleY = xHeight / 2
-        middleX = (left + right) / 2
-        subangle = 2 * math.pi / self.options.dots
-        for t in range(self.options.dots):
-            angle = t * subangle
-            cx = middleX + bigradius * math.cos(angle)
-            cy = middleY + bigradius * math.sin(angle)
-            circle(pen, (cx, cy), littleradius)
-
-        _setGlyphMargin(glyph, "right", self.options.sidebearing)
-
-        glyphSet["uni25CC"] = glyph
-        return glyph
-
-    def check_and_add_anchors(self, dotted_circle_glyph):
-        """Check that all mark-attached anchors are present on the dotted
-        circle glyph, synthesizing a position for any missing anchors."""
-        font = self.context.font
-
-        # First we will gather information about all the anchors in the
-        # font at present; for the anchors on marks (starting with "_")
-        # we just want to know their names, so we can match them with
-        # bases later. For the anchors on bases, we also want to store
-        # the position of the anchor so we can average them.
-        all_anchors = {}
-        any_added = False
-        for glyph in font:
-            width = None
-            try:
-                bounds = glyph.getBounds(font)
-                if bounds:
-                    width = bounds.xMax - bounds.xMin
-            except AttributeError:
-                bounds = glyph.bounds
-                if bounds:
-                    width = bounds[2] - bounds[0]
-            if width is None:
-                width = glyph.width
-            for anchor in glyph.anchors:
-                if anchor.name.startswith("_"):
-                    all_anchors[anchor.name] = []
-                    continue
-                if not width:
-                    continue
-                x_percentage = anchor.x / width
-                all_anchors.setdefault(anchor.name, []).append((x_percentage, anchor.y))
-
-        # Now we move to the dotted circle. What anchors do we have already?
-        dsanchors = set([a.name for a in dotted_circle_glyph.anchors])
-        for anchor, positions in all_anchors.items():
-            # Skip existing anchors on the dotted-circle, and any anchors
-            # which don't have a matching mark glyph (mark-to-lig etc.).
-            if anchor in dsanchors or f"_{anchor}" not in all_anchors:
-                continue
-
-            # And now we're creating a new one
-            anchor_x = dotted_circle_glyph.width * mean([v[0] for v in positions])
-            anchor_y = mean([v[1] for v in positions])
-            logger.debug(
-                "Adding anchor %s to dotted circle glyph at %i,%i",
-                anchor,
-                anchor_x,
-                anchor_y,
-            )
-            dotted_circle_glyph.appendAnchor(
-                {"x": otRound(anchor_x), "y": otRound(anchor_y), "name": anchor}
-            )
-            any_added = True
-        return any_added
-
-    # We have added some anchors to the dotted circle glyph. Now we need to
-    # ensure the glyph is a base (and specifically a base glyph, not just
-    # unclassified), or else it won't be in the list of base glyphs when
-    # we come to the mark features writer, and all our work will be for nothing.
-    # Also note that if we had a dotted circle glyph in the font already and
-    # we have come from Glyphs, glyphsLib would only consider the glyph to
-    # be a base if it has anchors, and it might not have had any when glyphsLib
-    # wrote the GDEF table.
-    # So we have to go digging around for a GDEF table and modify it.
-    def ensure_base(self, dotted_circle_glyph):
-        dotted_circle = dotted_circle_glyph.name
-        font = self.context.font
-        feaFile = parseLayoutFeatures(font)
-        if ast.findTable(feaFile, "GDEF") is None:
-            # We have no GDEF table. GDEFFeatureWriter will create one
-            # using the font's lib.
-            if OPENTYPE_CATEGORIES_KEY in font.lib:
-                font.lib[OPENTYPE_CATEGORIES_KEY][dotted_circle] = "base"
-            return
-        # We have GDEF table, so we need to find the GlyphClassDef, and add
-        # ourselves to the baseGlyphs set.
-        for st in feaFile.statements:
-            if isinstance(st, ast.TableBlock) and st.name == "GDEF":
-                for st2 in st.statements:
-                    if isinstance(st2, ast.GlyphClassDefStatement):
-                        if (
-                            st2.baseGlyphs
-                            and dotted_circle not in st2.baseGlyphs.glyphSet()
-                        ):
-                            st2.baseGlyphs.glyphs.append(dotted_circle)
-        # And then put the modified feature file back into the font
-        font.features.text = feaFile.asFea()
+warnings.warn(
+    "The dottedCircleFilter module is deprecated, please import dottedCircle instead.",
+    UserWarning,
+    stacklevel=1,
+)


Test patch:
diff --git a/tests/filters/dottedCircle_test.py b/tests/filters/dottedCircle_test.py
index 5df478c..cccda7f 100644
--- a/tests/filters/dottedCircle_test.py
+++ b/tests/filters/dottedCircle_test.py
@@ -1,4 +1,7 @@
-from ufo2ft.filters.dottedCircleFilter import DottedCircleFilter
+import pytest
+
+from ufo2ft.filters import loadFilters
+from ufo2ft.filters.dottedCircle import DottedCircleFilter
 from ufo2ft.util import _GlyphSet
 
 
@@ -40,7 +43,30 @@ def test_empty_font(FontClass):
     appropriate."""
 
     font = FontClass()
-    philter = DottedCircleFilter()
+    font.lib["com.github.googlei18n.ufo2ft.filters"] = [
+        {"name": "dottedCircle", "pre": True}
+    ]
+
+    pre_filters, _ = loadFilters(font)
+    (philter,) = pre_filters
+    glyphset = _GlyphSet.from_layer(font)
+
+    modified = philter(font, glyphset)
+
+    assert "uni25CC" in modified
+
+
+@pytest.mark.filterwarnings("ignore:Please update")
+def test_empty_font_deprecated(FontClass):
+    """Check that the module redirection works."""
+
+    font = FontClass()
+    font.lib["com.github.googlei18n.ufo2ft.filters"] = [
+        {"name": "DottedCircleFilter", "pre": True}
+    ]
+
+    pre_filters, _ = loadFilters(font)
+    (philter,) = pre_filters
     glyphset = _GlyphSet.from_layer(font)
 
     modified = philter(font, glyphset)
diff --git a/tests/filters/filters_test.py b/tests/filters/filters_test.py
index 2fe9ab3..f5c3682 100644
--- a/tests/filters/filters_test.py
+++ b/tests/filters/filters_test.py
@@ -41,6 +41,8 @@ def test_getFilterClass():
     assert getFilterClass("fooBar") == FooBarFilter
     with pytest.raises(ImportError):
         getFilterClass("Baz")
+    with pytest.raises(ImportError):
+        getFilterClass("FooBarFilter")
 
     with _TempModule("myfilters"), _TempModule("myfilters.fooBar") as temp_module:
         with pytest.raises(AttributeError):