Repository: napari/napari

Issue:
Tracks layer broken
## 🐛 Bug

It appears that the Tracks layer is missing a `new_layer.svg` icon in `napari/resources/icons/`. Since #2493 the tracks layer has been broken (doesn't show on the layer list), and since #2633 the error surfaces so the tracks layer never gets built. I have confirmed that rebasing #2633 on top of #2493 is sufficient to recreate the error on master.

Additionally, the fix is as simple as creating a `new_tracks.svg` file in `napari/resources/icons/`. I verified this by coping `new_shapes.svg` to `new_tracks.svg` within that folder and checking that everything works as expected.

The test script to try for this is [`examples/tracks_2d.py`](https://github.com/napari/napari/blob/ec67cf7c921e662fdd4204eb1c797159c6cb0671/examples/tracks_2d.py).

The full traceback can be seen below:

<details>

<summary>

```pytb
ValueError: unrecognized icon name: 'new_tracks'. Known names: {'visibility_off': ...
```

</summary>

```
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
~/projects/napari/napari/_qt/containers/_layer_delegate.py in paint(self=<napari._qt.containers._layer_delegate.LayerDelegate object>, painter=<PyQt5.QtGui.QPainter object>, option=<PyQt5.QtWidgets.QStyleOptionViewItem object>, index=<PyQt5.QtCore.QModelIndex object>)
     79         # update the icon based on layer type
     80
---> 81         self.get_layer_icon(option, index)
        self.get_layer_icon = <bound method LayerDelegate.get_layer_icon of <napari._qt.containers._layer_delegate.LayerDelegate object at 0x7f439c505160>>
        option = <PyQt5.QtWidgets.QStyleOptionViewItem object at 0x7f438c144890>
        index = <PyQt5.QtCore.QModelIndex object at 0x7f438c144580>
     82         # paint the standard itemView (includes name, icon, and vis. checkbox)
     83         super().paint(painter, option, index)

~/projects/napari/napari/_qt/containers/_layer_delegate.py in get_layer_icon(self=<napari._qt.containers._layer_delegate.LayerDelegate object>, option=<PyQt5.QtWidgets.QStyleOptionViewItem object>, index=<PyQt5.QtCore.QModelIndex object>)
     94             icon_name = f'new_{layer._type_string}'
     95
---> 96         icon = QColoredSVGIcon.from_resources(icon_name)
        icon = undefined
        global QColoredSVGIcon.from_resources = <functools._lru_cache_wrapper object at 0x7f439e4d4a60>
        icon_name = 'new_tracks'
     97         # guessing theme rather than passing it through.
     98         bg = option.palette.color(option.palette.Background).red()

~/projects/napari/napari/_qt/qt_resources/_svg.py in from_resources(icon_name='new_tracks')
    108         from ...resources import get_icon_path
    109
--> 110         path = get_icon_path(icon_name)
        path = undefined
        get_icon_path = <function get_icon_path at 0x7f439e4b8430>
        icon_name = 'new_tracks'
    111         return QColoredSVGIcon(path)
    112

~/projects/napari/napari/resources/_icons.py in get_icon_path(name='new_tracks')
     11     """Return path to an SVG in the theme icons."""
     12     if name not in ICONS:
---> 13         raise ValueError(
        global ValueError = undefined
     14             f"unrecognized icon name: {name!r}. Known names: {ICONS}"
     15         )

ValueError: unrecognized icon name: 'new_tracks'. Known names: {'visibility_off': '/home/jni/projects/napari/napari/resources/icons/visibility_off.svg', 'direct': '/home/jni/projects/napari/napari/resources/icons/direct.svg', 'minus': '/home/jni/projects/napari/napari/resources/icons/minus.svg', 'visibility': '/home/jni/projects/napari/napari/resources/icons/visibility.svg', 'right_arrow': '/home/jni/projects/napari/napari/resources/icons/right_arrow.svg', 'vertex_insert': '/home/jni/projects/napari/napari/resources/icons/vertex_insert.svg', 'new_shapes': '/home/jni/projects/napari/napari/resources/icons/new_shapes.svg', 'long_right_arrow': '/home/jni/projects/napari/napari/resources/icons/long_right_arrow.svg', 'erase': '/home/jni/projects/napari/napari/resources/icons/erase.svg', 'select': '/home/jni/projects/napari/napari/resources/icons/select.svg', 'roll': '/home/jni/projects/napari/napari/resources/icons/roll.svg', 'check': '/home/jni/projects/napari/napari/resources/icons/check.svg', 'circle': '/home/jni/projects/napari/napari/resources/icons/circle.svg', 'new_points': '/home/jni/projects/napari/napari/resources/icons/new_points.svg', 'plus': '/home/jni/projects/napari/napari/resources/icons/plus.svg', 'delete_shape': '/home/jni/projects/napari/napari/resources/icons/delete_shape.svg', 'drop_down': '/home/jni/projects/napari/napari/resources/icons/drop_down.svg', 'chevron_up': '/home/jni/projects/napari/napari/resources/icons/chevron_up.svg', 'delete': '/home/jni/projects/napari/napari/resources/icons/delete.svg', 'paint': '/home/jni/projects/napari/napari/resources/icons/paint.svg', 'grid': '/home/jni/projects/napari/napari/resources/icons/grid.svg', 'new_labels': '/home/jni/projects/napari/napari/resources/icons/new_labels.svg', 'console': '/home/jni/projects/napari/napari/resources/icons/console.svg', 'chevron_down': '/home/jni/projects/napari/napari/resources/icons/chevron_down.svg', 'long_left_arrow': '/home/jni/projects/napari/napari/resources/icons/long_left_arrow.svg', 'path': '/home/jni/projects/napari/napari/resources/icons/path.svg', 'down_arrow': '/home/jni/projects/napari/napari/resources/icons/down_arrow.svg', 'shuffle': '/home/jni/projects/napari/napari/resources/icons/shuffle.svg', 'square': '/home/jni/projects/napari/napari/resources/icons/square.svg', 'chevron_left': '/home/jni/projects/napari/napari/resources/icons/chevron_left.svg', 'left_arrow': '/home/jni/projects/napari/napari/resources/icons/left_arrow.svg', 'zoom': '/home/jni/projects/napari/napari/resources/icons/zoom.svg', 'move_front': '/home/jni/projects/napari/napari/resources/icons/move_front.svg', 'copy_to_clipboard': '/home/jni/projects/napari/napari/resources/icons/copy_to_clipboard.svg', 'fill': '/home/jni/projects/napari/napari/resources/icons/fill.svg', 'transpose': '/home/jni/projects/napari/napari/resources/icons/transpose.svg', 'rectangle': '/home/jni/projects/napari/napari/resources/icons/rectangle.svg', 'warning': '/home/jni/projects/napari/napari/resources/icons/warning.svg', 'pop_out': '/home/jni/projects/napari/napari/resources/icons/pop_out.svg', 'add': '/home/jni/projects/napari/napari/resources/icons/add.svg', 'line': '/home/jni/projects/napari/napari/resources/icons/line.svg', 'info': '/home/jni/projects/napari/napari/resources/icons/info.svg', '3D': '/home/jni/projects/napari/napari/resources/icons/3D.svg', 'step_left': '/home/jni/projects/napari/napari/resources/icons/step_left.svg', 'picker': '/home/jni/projects/napari/napari/resources/icons/picker.svg', 'polygon': '/home/jni/projects/napari/napari/resources/icons/polygon.svg', 'home': '/home/jni/projects/napari/napari/resources/icons/home.svg', 'new_image': '/home/jni/projects/napari/napari/resources/icons/new_image.svg', 'move_back': '/home/jni/projects/napari/napari/resources/icons/move_back.svg', 'vertex_remove': '/home/jni/projects/napari/napari/resources/icons/vertex_remove.svg', 'ellipse': '/home/jni/projects/napari/napari/resources/icons/ellipse.svg', '2D': '/home/jni/projects/napari/napari/resources/icons/2D.svg', 'new_vectors': '/home/jni/projects/napari/napari/resources/icons/new_vectors.svg', 'step_right': '/home/jni/projects/napari/napari/resources/icons/step_right.svg', 'up_arrow': '/home/jni/projects/napari/napari/resources/icons/up_arrow.svg', 'new_surface': '/home/jni/projects/napari/napari/resources/icons/new_surface.svg'}
```

</details>

A final surprising element to this is that none of our tests picked this up! Clearly we need to add a test that creates a viewer with a tracks layer.



Discussion:

Resolving patch:
diff --git a/napari/resources/_icons.py b/napari/resources/_icons.py
--- a/napari/resources/_icons.py
+++ b/napari/resources/_icons.py
@@ -17,7 +17,7 @@ def get_icon_path(name: str) -> str:
                 "unrecognized icon name: {name!r}. Known names: {icons}",
                 deferred=True,
                 name=name,
-                icons=ICONS,
+                icons=set(ICONS),
             )
         )
     return ICONS[name]


Test patch:
diff --git a/napari/_tests/test_adding_removing.py b/napari/_tests/test_adding_removing.py
--- a/napari/_tests/test_adding_removing.py
+++ b/napari/_tests/test_adding_removing.py
@@ -1,3 +1,5 @@
+import os
+
 import numpy as np
 import pytest
 
@@ -6,6 +8,14 @@
 from napari.utils.events.event import WarningEmitter
 
 
+@pytest.mark.skipif(bool(not os.getenv("CI")), reason='shows viewer')
+@pytest.mark.parametrize('Layer, data, _', layer_test_data)
+def test_add_all_layers(make_napari_viewer, Layer, data, _):
+    """Make sure that all layers can show in the viewer."""
+    viewer = make_napari_viewer(show=True)
+    viewer.layers.append(Layer(data))
+
+
 def test_layers_removed_on_close(make_napari_viewer):
     """Test layers removed on close."""
     viewer = make_napari_viewer()