Subject: [PATCH] Fix #23290: Add support for regions attribute
---
Index: resources/data/defaultpresets.xml
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/resources/data/defaultpresets.xml b/resources/data/defaultpresets.xml
--- a/resources/data/defaultpresets.xml	(revision 18895)
+++ b/resources/data/defaultpresets.xml	(date 1699393545196)
@@ -1465,7 +1465,7 @@
                     <list_entry value="unmarked" icon="presets/vehicle/crossing_unmarked.svg" />
                     <list_entry value="no" />
                 </combo>
-                <combo key="crossing_ref" text="Crossing type name (UK)" values="zebra,pelican,toucan,puffin,pegasus,tiger" />
+                <combo key="crossing_ref" text="Crossing type name (UK)" values="zebra,pelican,toucan,puffin,pegasus,tiger" regions="GB" />
                 <combo key="kerb" text="Kerb" values="flush,lowered,no,raised,rolled,yes" values_context="kerb" />
                 <combo key="supervised" text="Crossing attendant" delimiter="|" values="yes|no|06:00-20:00|Mo-Fr 09:00-18:00,Sa 08:00-14:00|May-Sep 09:30-12:30,14:15-19:30" values_i18n="false" values_sort="false" />
                 <check key="crossing:island" text="With island" />
@@ -1516,7 +1516,7 @@
                 <list_entry value="unmarked" icon="presets/vehicle/crossing_unmarked.svg" />
             </combo>
             <reference ref="crossing_markings" />
-            <combo key="crossing_ref" text="Crossing type name (UK)" values="zebra,pelican,toucan,puffin,pegasus,tiger" values_searchable="true" />
+            <combo key="crossing_ref" text="Crossing type name (UK)" values="zebra,pelican,toucan,puffin,pegasus,tiger" values_searchable="true" regions="GB" />
             <combo key="kerb" text="Kerb" values="flush,lowered,no,raised,rolled,yes" values_context="kerb" />
             <combo key="supervised" text="Crossing attendant" delimiter="|" values="yes|no|06:00-20:00|Mo-Fr 09:00-18:00,Sa 08:00-14:00|May-Sep 09:30-12:30,14:15-19:30" values_i18n="false" values_sort="false" />
             <combo key="tactile_paving" text="Tactile Paving" values="yes,no,incorrect" />
@@ -7264,10 +7264,10 @@
                 <list_entry value="war_memorial" short_description="A building, monument, statue or other edifice celebrating a victory or commemorating those who died or were injured in a war." />
                 <list_entry value="sculpture" short_description="A non figurative sculpture which does not match any of the other categories." />
                 <list_entry value="cross" short_description="A cross-shaped memorial." />
-                <list_entry value="blue_plaque" short_description="A blue plaque commemorating a historical link to that location. (UK)" />
+                <list_entry value="blue_plaque" short_description="A blue plaque commemorating a historical link to that location. (UK)" regions="GB" />
                 <list_entry value="obelisk" short_description="An obelisk." />
                 <list_entry value="ghost_bike" short_description="A bicycle painted white permanently placed as memorial for a victim of a road accident (usually cyclists)." />
-                <list_entry value="stolperstein" short_description="10cm × 10cm brass plaque commemorating victims of Nazi persecution. (EU,RU)" />
+                <list_entry value="stolperstein" short_description="10cm × 10cm brass plaque commemorating victims of Nazi persecution. (EU,RU)" regions="EU,RU" />
                 <list_entry value="bench" short_description="A bench placed in memory of someone or something; usually has a plaque, and sometimes a statue sitting on (part of) it." />
             </combo>
             <optional>
@@ -8217,7 +8217,7 @@
             <check key="payment:girocard" text="Girocard" match="keyvalue" />
             <check key="payment:laser" text="Laser" match="keyvalue" />
             <check key="payment:maestro" text="Maestro" match="keyvalue" />
-            <check key="payment:postfinance_card" text="PostFinance Card (ch)" match="keyvalue" />
+            <check key="payment:postfinance_card" text="PostFinance Card (ch)" match="keyvalue" region="CH" />
             <check key="payment:visa_debit" text="Visa Debit" match="keyvalue" />
             <check key="payment:visa_electron" text="Visa Electron" match="keyvalue" />
         </checkgroup>
@@ -8236,12 +8236,12 @@
         <space />
         <combo key="payment:electronic_purses" text="Electronic purses and Charge cards" values="yes,no" match="keyvalue" />
         <checkgroup columns="4">
-            <check key="payment:ep_avant" text="Avant (fi)" match="keyvalue" />
-            <check key="payment:ep_geldkarte" text="Geldkarte (de)" match="keyvalue" />
-            <check key="payment:ep_mep" text="Mep (pt)" match="keyvalue" />
-            <check key="payment:ep_minicash" text="Minicash (lu)" match="keyvalue" />
-            <check key="payment:ep_minipay" text="Minipay (it)" match="keyvalue" />
-            <check key="payment:ep_monedero4b" text="Mondero 4b (es)" match="keyvalue" />
+            <check key="payment:ep_avant" text="Avant (fi)" match="keyvalue" regions="FI" />
+            <check key="payment:ep_geldkarte" text="Geldkarte (de)" match="keyvalue" regions="DE" />
+            <check key="payment:ep_mep" text="Mep (pt)" match="keyvalue" regions="PT" />
+            <check key="payment:ep_minicash" text="Minicash (lu)" match="keyvalue" regions="LU" />
+            <check key="payment:ep_minipay" text="Minipay (it)" match="keyvalue" regions="IT" />
+            <check key="payment:ep_monedero4b" text="Mondero 4b (es)" match="keyvalue" regions="ES" />
         </checkgroup>
         <space />
         <combo key="payment:cryptocurrencies" text="Cryptocurrencies" values="yes,no" match="keyvalue" />
@@ -9924,11 +9924,11 @@
             <optional>
                 <text key="destination" text="Name of river/lake/sea/ocean it runs into" />
                 <text key="ref" text="Reference" />
-                <text key="ref:sandre" text="Reference Sandre (FR)" />
-                <text key="ref:fgkz" text="Reference FGKZ (DE)" />
-                <text key="ref:regine" text="Reference REGINE (NO)" />
-                <text key="ref:gnis" text="Reference GNIS (USA)" />
-                <text key="ref:gnbc" text="Reference GNBC (CA)" />
+                <text key="ref:sandre" text="Reference Sandre (FR)" regions="FR" />
+                <text key="ref:fgkz" text="Reference FGKZ (DE)" regions="DE" />
+                <text key="ref:regine" text="Reference REGINE (NO)" regions="NO" />
+                <text key="ref:gnis" text="Reference GNIS (USA)" regions="US" />
+                <text key="ref:gnbc" text="Reference GNBC (CA)" regions="CA" />
                 <reference ref="wikipedia_wikidata" />
             </optional>
             <roles>
Index: resources/data/tagging-preset.xsd
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/resources/data/tagging-preset.xsd b/resources/data/tagging-preset.xsd
--- a/resources/data/tagging-preset.xsd	(revision 18895)
+++ b/resources/data/tagging-preset.xsd	(date 1699371067970)
@@ -106,6 +106,7 @@
         <complexContent>
             <extension base="tns:group-parent">
                 <attributeGroup ref="tns:attributes.name" />
+                <attributeGroup ref="tns:attributes.regions"/>
             </extension>
         </complexContent>
     </complexType>
@@ -121,7 +122,7 @@
     <complexType name="item">
         <annotation>
             <documentation>
-                Every item is one annotation set to select from. name is required, type and preset_name_label are recommended, icon and name_template are optional attributes.
+                Every item is one annotation set to select from. name is required, type and preset_name_label are recommended and icon, name_template, regions and exclude_regions are optional attributes.
             </documentation>
         </annotation>
         <sequence>
@@ -134,6 +135,7 @@
         </sequence>
         <attributeGroup ref="tns:attributes.name" />
         <attributeGroup ref="tns:attributes.icon" />
+        <attributeGroup ref="tns:attributes.regions"/>
         <attribute name="type" type="string">
             <annotation>
                 <documentation><![CDATA[
@@ -364,7 +366,7 @@
     <complexType name="list_entry">
         <annotation>
             <documentation><![CDATA[
-                Used in <combo/> and <multiselect/>. More information see short_descriptions below. The attributes are value, display_value, short_description, icon and icon_size.
+                Used in <combo/> and <multiselect/>. More information see short_descriptions below. The attributes are value, display_value, short_description, icon, icon_size, regions, and exclude_regions.
             ]]></documentation>
         </annotation>
         <attribute name="value" type="string" use="required" />
@@ -378,6 +380,7 @@
             </annotation>
         </attribute>
         <attributeGroup ref="tns:attributes.icon" />
+        <attributeGroup ref="tns:attributes.regions"/>
         <anyAttribute processContents="skip" />
     </complexType>
 
@@ -489,6 +492,7 @@
         </attribute>
         <attribute name="match" type="tns:match" />
         <attributeGroup ref="tns:attributes.icon" />
+        <attributeGroup ref="tns:attributes.regions"/>
 
         <attribute name="name" use="prohibited" />
         <attribute name="type" use="prohibited" />
@@ -558,7 +562,7 @@
     <complexType name="role">
         <annotation>
             <documentation>
-                To specify possible roles of members in relations. The key attribute is required, text, requisite, count, type and member_expression are optional.
+                To specify possible roles of members in relations. The key attribute is required, text, requisite, count, type, member_expression, regions, and exclude_regions are optional.
             </documentation>
         </annotation>
         <attribute name="key" type="string">
@@ -574,6 +578,7 @@
         <attribute name="count" type="integer" />
         <attribute name="member_expression" type="string" />
         <attribute name="regexp" type="boolean" />
+        <attributeGroup ref="tns:attributes.regions"/>
         <anyAttribute processContents="skip" />
     </complexType>
 
@@ -728,5 +733,22 @@
             </annotation>
         </attribute>
     </attributeGroup>
+
+    <attributeGroup name="attributes.regions">
+        <attribute name="regions" type="string">
+            <annotation>
+                <documentation>
+                    Comma separated list of countries this preset group or item is applicable for. If not specified, the preset is applicable for all countries.
+                </documentation>
+            </annotation>
+        </attribute>
+        <attribute name="exclude_regions" type="boolean">
+            <annotation>
+                <documentation>
+                    If true, invert the meaning of regions.
+                </documentation>
+            </annotation>
+        </attribute>
+    </attributeGroup>
 
 </schema>
Index: src/org/openstreetmap/josm/data/validation/tests/TagChecker.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/org/openstreetmap/josm/data/validation/tests/TagChecker.java b/src/org/openstreetmap/josm/data/validation/tests/TagChecker.java
--- a/src/org/openstreetmap/josm/data/validation/tests/TagChecker.java	(revision 18895)
+++ b/src/org/openstreetmap/josm/data/validation/tests/TagChecker.java	(date 1699562139490)
@@ -37,8 +37,10 @@
 import org.openstreetmap.josm.command.ChangePropertyKeyCommand;
 import org.openstreetmap.josm.command.Command;
 import org.openstreetmap.josm.command.SequenceCommand;
+import org.openstreetmap.josm.data.coor.LatLon;
 import org.openstreetmap.josm.data.osm.AbstractPrimitive;
 import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.data.osm.Node;
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
 import org.openstreetmap.josm.data.osm.OsmUtils;
 import org.openstreetmap.josm.data.osm.Relation;
@@ -62,13 +64,19 @@
 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets;
 import org.openstreetmap.josm.gui.tagging.presets.items.Check;
 import org.openstreetmap.josm.gui.tagging.presets.items.CheckGroup;
+import org.openstreetmap.josm.gui.tagging.presets.items.ComboMultiSelect;
+import org.openstreetmap.josm.gui.tagging.presets.items.Key;
 import org.openstreetmap.josm.gui.tagging.presets.items.KeyedItem;
+import org.openstreetmap.josm.gui.tagging.presets.items.PresetListEntry;
+import org.openstreetmap.josm.gui.tagging.presets.items.RegionSpecific;
 import org.openstreetmap.josm.gui.widgets.EditableList;
 import org.openstreetmap.josm.io.CachedFile;
 import org.openstreetmap.josm.spi.preferences.Config;
 import org.openstreetmap.josm.tools.GBC;
+import org.openstreetmap.josm.tools.JosmRuntimeException;
 import org.openstreetmap.josm.tools.Logging;
 import org.openstreetmap.josm.tools.MultiMap;
+import org.openstreetmap.josm.tools.Territories;
 import org.openstreetmap.josm.tools.Utils;
 
 /**
@@ -131,6 +139,7 @@
      * The preference key to check presets
      */
     public static final String PREF_CHECK_PRESETS_TYPES = PREFIX + ".checkPresetsTypes";
+    public static final String PREF_CHECK_REGIONS = PREFIX + ".checkPresetsRegions";
 
     /**
      * The preference key for source files
@@ -159,6 +168,7 @@
      * The preference key to search for presets - used before upload
      */
     public static final String PREF_CHECK_PRESETS_TYPES_BEFORE_UPLOAD = PREF_CHECK_PRESETS_TYPES + BEFORE_UPLOAD;
+    public static final String PREF_CHECK_REGIONS_BEFORE_UPLOAD = PREF_CHECK_REGIONS + BEFORE_UPLOAD;
 
     /**
      * The preference key for the list of tag keys that are allowed to be the same on a multipolygon and an outer way
@@ -175,18 +185,21 @@
     protected boolean checkComplex;
     protected boolean checkFixmes;
     protected boolean checkPresetsTypes;
+    protected boolean checkRegions;
 
     protected JCheckBox prefCheckKeys;
     protected JCheckBox prefCheckValues;
     protected JCheckBox prefCheckComplex;
     protected JCheckBox prefCheckFixmes;
     protected JCheckBox prefCheckPresetsTypes;
+    protected JCheckBox prefCheckRegions;
 
     protected JCheckBox prefCheckKeysBeforeUpload;
     protected JCheckBox prefCheckValuesBeforeUpload;
     protected JCheckBox prefCheckComplexBeforeUpload;
     protected JCheckBox prefCheckFixmesBeforeUpload;
     protected JCheckBox prefCheckPresetsTypesBeforeUpload;
+    protected JCheckBox prefCheckRegionsBeforeUpload;
 
     // CHECKSTYLE.OFF: SingleSpaceSeparator
     protected static final int EMPTY_VALUES                     = 1200;
@@ -210,6 +223,7 @@
     protected static final int MULTIPOLYGON_INCOMPLETE          = 1219;
     protected static final int MULTIPOLYGON_MAYBE_NO_AREA       = 1220;
     protected static final int MULTIPOLYGON_SAME_TAG_ON_OUTER   = 1221;
+    protected static final int INVALID_REGION                   = 1222;
     // CHECKSTYLE.ON: SingleSpaceSeparator
 
     protected EditableList sourcesList;
@@ -559,7 +573,7 @@
      * @param key key
      * @return {@code true} if the given key is in internal presets
      * @since 9023
-     * @deprecated Use {@link TaggingPresets#isKeyInPresets(String)} instead
+     * @deprecated since 18281 -- use {@link TaggingPresets#isKeyInPresets(String)} instead
      */
     @Deprecated
     public static boolean isKeyInPresets(String key) {
@@ -652,42 +666,192 @@
         }
 
         if (p instanceof Relation && p.hasTag("type", "multipolygon")) {
-        checkMultipolygonTags(p);
+            checkMultipolygonTags(p);
         }
 
-        if (checkPresetsTypes) {
-            TagMap tags = p.getKeys();
-            TaggingPresetType presetType = TaggingPresetType.forPrimitive(p);
-            EnumSet<TaggingPresetType> presetTypes = EnumSet.of(presetType);
-
-            Collection<TaggingPreset> matchingPresets = presetIndex.entrySet().stream()
+        final Collection<TaggingPreset> matchingPresets;
+        TagMap tags;
+        if (checkPresetsTypes || checkRegions) {
+            tags = p.getKeys();
+            matchingPresets = presetIndex.entrySet().stream()
                     .filter(e -> TaggingPresetItem.matches(e.getValue(), tags))
                     .map(Entry::getKey)
                     .collect(Collectors.toCollection(LinkedHashSet::new));
-            Collection<TaggingPreset> matchingPresetsOK = matchingPresets.stream().filter(
-                    tp -> tp.typeMatches(presetTypes)).collect(Collectors.toList());
-            Collection<TaggingPreset> matchingPresetsKO = matchingPresets.stream().filter(
-                    tp -> !tp.typeMatches(presetTypes)).collect(Collectors.toList());
+        } else {
+            matchingPresets = null;
+            tags = null;
+        }
+
+        if (checkPresetsTypes) {
+            checkPresetsTypes(p, matchingPresets, tags);
+        }
+
+        if (checkRegions) {
+            checkRegions(p, matchingPresets);
+        }
+    }
+
+    /**
+     * Check that the primitive matches the preset types for the preset
+     * @param p The primitive to check
+     * @param matchingPresets The presets to go through
+     * @param tags Tags from the primitive to check
+     */
+    private void checkPresetsTypes(OsmPrimitive p, Collection<TaggingPreset> matchingPresets, Map<String, String> tags) {
+        TaggingPresetType presetType = TaggingPresetType.forPrimitive(p);
+        EnumSet<TaggingPresetType> presetTypes = EnumSet.of(presetType);
+
+        Collection<TaggingPreset> matchingPresetsOK = matchingPresets.stream().filter(
+                tp -> tp.typeMatches(presetTypes)).collect(Collectors.toList());
+        Collection<TaggingPreset> matchingPresetsKO = matchingPresets.stream().filter(
+                tp -> !tp.typeMatches(presetTypes)).collect(Collectors.toList());
 
-            for (TaggingPreset tp : matchingPresetsKO) {
-                // Potential error, unless matching tags are all known by a supported preset
-                Map<String, String> matchingTags = tp.data.stream()
+        for (TaggingPreset tp : matchingPresetsKO) {
+            // Potential error, unless matching tags are all known by a supported preset
+            Map<String, String> matchingTags = tp.data.stream()
                     .filter(i -> Boolean.TRUE.equals(i.matches(tags)))
                     .filter(i -> i instanceof KeyedItem).map(i -> ((KeyedItem) i).key)
                     .collect(Collectors.toMap(k -> k, tags::get));
-                if (matchingPresetsOK.stream().noneMatch(
-                        tp2 -> matchingTags.entrySet().stream().allMatch(
-                                e -> tp2.data.stream().anyMatch(
-                                        i -> i instanceof KeyedItem && ((KeyedItem) i).key.equals(e.getKey()))))) {
-                    errors.add(TestError.builder(this, Severity.OTHER, INVALID_PRESETS_TYPE)
-                            .message(tr("Object type not in preset"),
-                                    marktr("Object type {0} is not supported by tagging preset: {1}"),
-                                    tr(presetType.getName()), tp.getLocaleName())
-                            .primitives(p)
-                            .build());
-                }
-            }
-        }
+            if (matchingPresetsOK.stream().noneMatch(
+                    tp2 -> matchingTags.entrySet().stream().allMatch(
+                            e -> tp2.data.stream().anyMatch(
+                                    i -> i instanceof KeyedItem && ((KeyedItem) i).key.equals(e.getKey()))))) {
+                errors.add(TestError.builder(this, Severity.OTHER, INVALID_PRESETS_TYPE)
+                        .message(tr("Object type not in preset"),
+                                marktr("Object type {0} is not supported by tagging preset: {1}"),
+                                tr(presetType.getName()), tp.getLocaleName())
+                        .primitives(p)
+                        .build());
+            }
+        }
+    }
+
+    /**
+     * Check that the preset is valid for the region the primitive is in
+     * @param p The primitive to check
+     * @param matchingPresets The presets to check against
+     */
+    private void checkRegions(OsmPrimitive p, Collection<TaggingPreset> matchingPresets) {
+        LatLon center;
+        if (p instanceof Node) {
+            center = ((Node) p).getCoor();
+        } else {
+            center = p.getBBox().getCenter();
+        }
+        for (TaggingPreset preset : matchingPresets) {
+            if (preset.regions() != null) {
+                boolean isInRegion = false; //true if the object is in an applicable region
+                for (String region : preset.regions()) {
+                    if (Territories.isIso3166Code(region, center)) { //check if center of the object is in a region
+                        isInRegion = true;
+                    }
+                }
+                if (isInRegion == preset.exclude_regions()) {
+                    errors.add(TestError.builder(this, Severity.WARNING, INVALID_REGION)
+                            .message(tr("Invalid region for this preset"),
+                                    marktr("Preset {0} should not be applied in this region"),
+                                    preset.getLocaleName())
+                            .primitives(p)
+                            .build());
+                }
+            }
+            // Check the tags
+            tagCheck(preset, p, center, preset.data);
+        }
+    }
+
+    /**
+     * Perform the checks against a given preset value
+     * @param preset The originating preset (used for error creation)
+     * @param p The originating primitive (used for error creation)
+     * @param center The center of the primitive or other location of the primitive to check
+     * @param tagInformation The sub items for the preset
+     */
+    private void tagCheck(TaggingPreset preset, OsmPrimitive p, LatLon center, List<? extends TaggingPresetItem> tagInformation) {
+        for (TaggingPresetItem item : tagInformation) {
+            if (item instanceof CheckGroup) {
+                tagCheckReal(preset, p, center, ((CheckGroup) item).checks);
+            } else if (item instanceof ComboMultiSelect) {
+                tagCheckReal(preset, p, center, ((ComboMultiSelect) item).presetListEntries());
+            }
+            if (item instanceof RegionSpecific && ((RegionSpecific) item).regions() != null) {
+                tagCheckReal(preset, p, center, (RegionSpecific) item);
+            }
+        }
+    }
+
+    /**
+     * Perform the checks against a given preset value
+     * @param preset The originating preset (used for error creation)
+     * @param p The originating primitive (used for error creation)
+     * @param center The center of the primitive or other location of the primitive to check
+     * @param data The data for the region specific information
+     */
+    private void tagCheckReal(TaggingPreset preset, OsmPrimitive p, LatLon center, List<? extends RegionSpecific> data) {
+        for (RegionSpecific regionSpecific : data) {
+            if (regionSpecific.regions() != null) {
+                tagCheckReal(preset, p, center, regionSpecific);
+            }
+        }
+    }
+
+    /**
+     * Perform the checks against a given preset value
+     * @param preset The originating preset (used for error creation)
+     * @param p The originating primitive (used for error creation)
+     * @param center The center of the primitive or other location of the primitive to check
+     * @param data The data for the region specific information
+     */
+    private void tagCheckReal(TaggingPreset preset, OsmPrimitive p, LatLon center, RegionSpecific data) {
+        // First, check if we aren't in the region for the tag
+        if (latLonInRegions(center, data.regions()) == data.exclude_regions()) {
+            final String key;
+            final String value;
+            if (data instanceof PresetListEntry) {
+                key = ((PresetListEntry) data).cms.key;
+                value = ((PresetListEntry) data).value;
+            } else if (data instanceof KeyedItem) {
+                key = ((KeyedItem) data).key;
+                if (data instanceof Key) {
+                    value = ((Key) data).value;
+                } else {
+                    value = null;
+                }
+            } else {
+                throw new JosmRuntimeException("Unknown implementor for RegionSpecific");
+            }
+            if (p.hasTag(key) && (value == null || value.equals(p.get(key)))) {
+                final TestError.Builder builder = TestError.builder(this, Severity.WARNING, INVALID_REGION)
+                        .primitives(p);
+                if (value == null) {
+                    builder.message(tr("Invalid region for this preset"),
+                            marktr("Preset {0} should not have the key {1}"),
+                            preset.getLocaleName(), key);
+                } else {
+                    builder.message(tr("Invalid region for this preset"),
+                            marktr("Preset {0} should not have the tag {1}={2}"),
+                            preset.getLocaleName(), key, value);
+                }
+                errors.add(builder.build());
+            }
+        }
+    }
+
+    /**
+     * Check if the specified latlon is inside any of the specified regions
+     * @param latLon The {@link LatLon} to check
+     * @param regions The regions to see if the {@link LatLon} is in
+     * @return {@code true} if the coordinate is inside any of the regions
+     */
+    private static boolean latLonInRegions(LatLon latLon, Collection<String> regions) {
+        if (regions != null) {
+            for (String region : regions) {
+                if (Territories.isIso3166Code(region, latLon)) {
+                    return true;
+                }
+            }
+        }
+        return false;
     }
 
     private static final Collection<String> NO_AREA_KEYS = Arrays.asList("name", "area", "ref", "access", "operator");
@@ -1100,6 +1264,11 @@
         if (isBeforeUpload) {
             checkPresetsTypes = checkPresetsTypes && Config.getPref().getBoolean(PREF_CHECK_PRESETS_TYPES_BEFORE_UPLOAD, true);
         }
+
+        checkRegions = includeOtherSeverity && Config.getPref().getBoolean(PREF_CHECK_REGIONS, true);
+        if (isBeforeUpload) {
+            checkRegions = checkRegions && Config.getPref().getBoolean(PREF_CHECK_REGIONS_BEFORE_UPLOAD, true);
+        }
         deprecatedChecker = OsmValidator.getTest(MapCSSTagChecker.class);
         ignoreForOuterMPSameTagCheck.addAll(Config.getPref().getList(PREF_KEYS_IGNORE_OUTER_MP_SAME_TAG, Collections.emptyList()));
     }
@@ -1112,7 +1281,7 @@
 
     @Override
     public void visit(Collection<OsmPrimitive> selection) {
-        if (checkKeys || checkValues || checkComplex || checkFixmes || checkPresetsTypes) {
+        if (checkKeys || checkValues || checkComplex || checkFixmes || checkPresetsTypes || checkRegions) {
             super.visit(selection);
         }
     }
@@ -1177,6 +1346,14 @@
         prefCheckPresetsTypesBeforeUpload = new JCheckBox();
         prefCheckPresetsTypesBeforeUpload.setSelected(Config.getPref().getBoolean(PREF_CHECK_PRESETS_TYPES_BEFORE_UPLOAD, true));
         testPanel.add(prefCheckPresetsTypesBeforeUpload, a);
+
+        prefCheckRegions = new JCheckBox(tr("Check for regions."), Config.getPref().getBoolean(PREF_CHECK_REGIONS, true));
+        prefCheckRegions.setToolTipText(tr("Validate that objects are in the correct region."));
+        testPanel.add(prefCheckRegions, GBC.std().insets(20, 0, 0, 0));
+
+        prefCheckRegionsBeforeUpload = new JCheckBox();
+        prefCheckRegionsBeforeUpload.setSelected(Config.getPref().getBoolean(PREF_CHECK_REGIONS_BEFORE_UPLOAD, true));
+        testPanel.add(prefCheckRegionsBeforeUpload, a);
     }
 
     /**
@@ -1199,11 +1376,13 @@
         Config.getPref().putBoolean(PREF_CHECK_KEYS, prefCheckKeys.isSelected());
         Config.getPref().putBoolean(PREF_CHECK_FIXMES, prefCheckFixmes.isSelected());
         Config.getPref().putBoolean(PREF_CHECK_PRESETS_TYPES, prefCheckPresetsTypes.isSelected());
+        Config.getPref().putBoolean(PREF_CHECK_REGIONS, prefCheckRegions.isSelected());
         Config.getPref().putBoolean(PREF_CHECK_VALUES_BEFORE_UPLOAD, prefCheckValuesBeforeUpload.isSelected());
         Config.getPref().putBoolean(PREF_CHECK_COMPLEX_BEFORE_UPLOAD, prefCheckComplexBeforeUpload.isSelected());
         Config.getPref().putBoolean(PREF_CHECK_KEYS_BEFORE_UPLOAD, prefCheckKeysBeforeUpload.isSelected());
         Config.getPref().putBoolean(PREF_CHECK_FIXMES_BEFORE_UPLOAD, prefCheckFixmesBeforeUpload.isSelected());
         Config.getPref().putBoolean(PREF_CHECK_PRESETS_TYPES_BEFORE_UPLOAD, prefCheckPresetsTypesBeforeUpload.isSelected());
+        Config.getPref().putBoolean(PREF_CHECK_REGIONS_BEFORE_UPLOAD, prefCheckRegionsBeforeUpload.isSelected());
         return Config.getPref().putList(PREF_SOURCES, sourcesList.getItems());
     }
 
Index: src/org/openstreetmap/josm/gui/tagging/presets/items/ComboMultiSelect.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/org/openstreetmap/josm/gui/tagging/presets/items/ComboMultiSelect.java b/src/org/openstreetmap/josm/gui/tagging/presets/items/ComboMultiSelect.java
--- a/src/org/openstreetmap/josm/gui/tagging/presets/items/ComboMultiSelect.java	(revision 18895)
+++ b/src/org/openstreetmap/josm/gui/tagging/presets/items/ComboMultiSelect.java	(date 1699392025144)
@@ -10,6 +10,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.TreeMap;
@@ -402,6 +403,14 @@
         return use_last_as_default == 2;
     }
 
+    /**
+     * Get the entries for this {@link ComboMultiSelect} object
+     * @return The {@link PresetListEntry} values for this object
+     */
+    public List<PresetListEntry> presetListEntries() {
+        return Collections.unmodifiableList(this.presetListEntries);
+    }
+
     /**
      * Adds a preset list entry.
      * @param e list entry to add
Index: src/org/openstreetmap/josm/gui/tagging/presets/items/KeyedItem.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/org/openstreetmap/josm/gui/tagging/presets/items/KeyedItem.java b/src/org/openstreetmap/josm/gui/tagging/presets/items/KeyedItem.java
--- a/src/org/openstreetmap/josm/gui/tagging/presets/items/KeyedItem.java	(revision 18895)
+++ b/src/org/openstreetmap/josm/gui/tagging/presets/items/KeyedItem.java	(date 1699545423589)
@@ -7,8 +7,8 @@
 import java.util.EnumSet;
 import java.util.HashMap;
 import java.util.Map;
-import java.util.SortedMap;
 import java.util.NoSuchElementException;
+import java.util.SortedMap;
 import java.util.TreeMap;
 
 import javax.swing.JPopupMenu;
@@ -24,7 +24,7 @@
 /**
  * Preset item associated to an OSM key.
  */
-public abstract class KeyedItem extends TextItem {
+public abstract class KeyedItem extends TextItem implements RegionSpecific {
 
     /** The constant value {@code "<different>"}. */
     protected static final String DIFFERENT = "<different>";
@@ -52,6 +52,15 @@
      */
     public String match = getDefaultMatch().getValue(); // NOSONAR
 
+    /**
+     * List of regions the preset is applicable for.
+     */
+    private Collection<String> regions;
+    /**
+     * If true, invert the meaning of regions.
+     */
+    private boolean excludeRegions;
+
     /**
      * Enum denoting how a match (see {@link TaggingPresetItem#matches}) is performed.
      */
@@ -260,6 +269,26 @@
         return popupMenu;
     }
 
+    @Override
+    public final Collection<String> regions() {
+        return this.regions;
+    }
+
+    @Override
+    public final void realSetRegions(Collection<String> regions) {
+        this.regions = regions;
+    }
+
+    @Override
+    public final boolean exclude_regions() {
+        return this.excludeRegions;
+    }
+
+    @Override
+    public final void setExclude_regions(boolean excludeRegions) {
+        this.excludeRegions = excludeRegions;
+    }
+
     @Override
     public String toString() {
         return "KeyedItem [key=" + key + ", text=" + text
Index: src/org/openstreetmap/josm/gui/tagging/presets/items/PresetListEntry.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/org/openstreetmap/josm/gui/tagging/presets/items/PresetListEntry.java b/src/org/openstreetmap/josm/gui/tagging/presets/items/PresetListEntry.java
--- a/src/org/openstreetmap/josm/gui/tagging/presets/items/PresetListEntry.java	(revision 18895)
+++ b/src/org/openstreetmap/josm/gui/tagging/presets/items/PresetListEntry.java	(date 1699545423551)
@@ -5,6 +5,7 @@
 import static org.openstreetmap.josm.tools.I18n.tr;
 import static org.openstreetmap.josm.tools.I18n.trc;
 
+import java.util.Collection;
 import java.util.Objects;
 
 import javax.swing.ImageIcon;
@@ -20,7 +21,7 @@
  * Used for controls that offer a list of items to choose from like {@link Combo} and
  * {@link MultiSelect}.
  */
-public class PresetListEntry implements Comparable<PresetListEntry> {
+public class PresetListEntry implements Comparable<PresetListEntry>, RegionSpecific {
     /** Used to display an entry matching several different values. */
     protected static final PresetListEntry ENTRY_DIFFERENT = new PresetListEntry(KeyedItem.DIFFERENT, null);
     /** Used to display an empty entry used to clear values. */
@@ -47,13 +48,23 @@
     /** The localized version of {@link #short_description}. */
     public String locale_short_description; // NOSONAR
 
+    /**
+     * List of regions the entry is applicable for.
+     */
+    private Collection<String> regions;
+
+    /**
+     * If true, invert the meaning of regions.
+     */
+    private boolean excludeRegions;
+
     private String cachedDisplayValue;
     private String cachedShortDescription;
     private ImageIcon cachedIcon;
 
     /**
      * Constructs a new {@code PresetListEntry}, uninitialized.
-     *
+     * <p>
      * Public default constructor is needed by {@link org.openstreetmap.josm.tools.XmlObjectParser.Parser#startElement}
      */
     public PresetListEntry() {
@@ -73,7 +84,7 @@
 
     /**
      * Returns the contents displayed in the dropdown list.
-     *
+     * <p>
      * This is the contents that would be displayed in the current view plus a short description to
      * aid the user. The whole content is wrapped to {@code width}.
      *
@@ -163,6 +174,26 @@
         return tr("Clears the key ''{0}''.", key);
     }
 
+    @Override
+    public Collection<String> regions() {
+        return this.regions;
+    }
+
+    @Override
+    public void realSetRegions(Collection<String> regions) {
+        this.regions = regions;
+    }
+
+    @Override
+    public boolean exclude_regions() {
+        return this.excludeRegions;
+    }
+
+    @Override
+    public void setExclude_regions(boolean excludeRegions) {
+        this.excludeRegions = excludeRegions;
+    }
+
     // toString is mainly used to initialize the Editor
     @Override
     public String toString() {
Index: src/org/openstreetmap/josm/gui/tagging/presets/items/RegionSpecific.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/org/openstreetmap/josm/gui/tagging/presets/items/RegionSpecific.java b/src/org/openstreetmap/josm/gui/tagging/presets/items/RegionSpecific.java
new file mode 100644
--- /dev/null	(date 1699546057868)
+++ b/src/org/openstreetmap/josm/gui/tagging/presets/items/RegionSpecific.java	(date 1699546057868)
@@ -0,0 +1,66 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.tagging.presets.items;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.openstreetmap.josm.tools.Territories;
+import org.openstreetmap.josm.tools.Utils;
+import org.xml.sax.SAXException;
+
+/**
+ * Indicates that this object may be specific to a region
+ * @since xxx
+ */
+public interface RegionSpecific {
+    /**
+     * Get the regions for the item
+     * @return The regions that the item is valid for
+     * @apiNote This is not {@code getRegions} just in case we decide to make the {@link RegionSpecific} record classes.
+     * @since xxx
+     */
+    Collection<String> regions();
+
+    /**
+     * Set the regions for the preset
+     * @param regions The region list (comma delimited)
+     * @throws SAXException if an unknown ISO 3166-2 is found
+     * @since xxx
+     */
+    default void setRegions(String regions) throws SAXException {
+        Set<String> regionSet = Collections.unmodifiableSet(Arrays.stream(regions.split(","))
+                .map(Utils::intern).collect(Collectors.toSet()));
+        for (String region : regionSet) {
+            if (!Territories.getKnownIso3166Codes().contains(region)) {
+                throw new SAXException(tr("Unknown ISO-3166 Code: {0}", region));
+            }
+        }
+        this.realSetRegions(regionSet);
+    }
+
+    /**
+     * Set the regions for the preset
+     * @param regions The region collection
+     * @since xxx
+     */
+    void realSetRegions(Collection<String> regions);
+
+    /**
+     * Get the exclude_regions for the preset
+     * @apiNote This is not {@code getExclude_regions} just in case we decide to make {@link RegionSpecific} a record class.
+     * @since xxx
+     */
+    boolean exclude_regions();
+
+    /**
+     * Set if the preset should not be used in the given region
+     * @param excludeRegions if true the function of regions is inverted
+     * @since xxx
+     */
+    void setExclude_regions(boolean excludeRegions);
+}
Index: src/org/openstreetmap/josm/gui/tagging/presets/TaggingPreset.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPreset.java b/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPreset.java
--- a/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPreset.java	(revision 18895)
+++ b/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPreset.java	(date 1699545423535)
@@ -67,6 +67,7 @@
 import org.openstreetmap.josm.gui.tagging.presets.items.PresetLink;
 import org.openstreetmap.josm.gui.tagging.presets.items.Roles;
 import org.openstreetmap.josm.gui.tagging.presets.items.Space;
+import org.openstreetmap.josm.gui.tagging.presets.items.RegionSpecific;
 import org.openstreetmap.josm.gui.util.GuiHelper;
 import org.openstreetmap.josm.tools.GBC;
 import org.openstreetmap.josm.tools.ImageProvider;
@@ -88,7 +89,8 @@
  * It is also able to construct dialogs out of preset definitions.
  * @since 294
  */
-public class TaggingPreset extends AbstractAction implements ActiveLayerChangeListener, AdaptableAction, Predicate<IPrimitive> {
+public class TaggingPreset extends AbstractAction implements ActiveLayerChangeListener, AdaptableAction, Predicate<IPrimitive>,
+        RegionSpecific {
 
     /** The user pressed the "Apply" button */
     public static final int DIALOG_ANSWER_APPLY = 1;
@@ -142,6 +144,14 @@
      */
     public transient Set<TaggingPresetType> types;
     /**
+     * List of regions the preset is applicable for.
+     */
+    private Collection<String> regions;
+    /**
+     * If true, invert the meaning of regions.
+     */
+    private boolean excludeRegions;
+    /**
      * The list of preset items
      */
     public final transient List<TaggingPresetItem> data = new ArrayList<>(2);
@@ -349,6 +359,26 @@
         }
     }
 
+    @Override
+    public final Collection<String> regions() {
+        return this.regions != null || this.group == null ? this.regions : this.group.regions();
+    }
+
+    @Override
+    public final void realSetRegions(Collection<String> regions) {
+        this.regions = regions;
+    }
+
+    @Override
+    public final boolean exclude_regions() {
+        return this.excludeRegions;
+    }
+
+    @Override
+    public final void setExclude_regions(boolean excludeRegions) {
+        this.excludeRegions = excludeRegions;
+    }
+
     private static class PresetPanel extends JPanel {
         private boolean hasElements;
 
Index: src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetValidation.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetValidation.java b/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetValidation.java
--- a/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetValidation.java	(revision 18895)
+++ b/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetValidation.java	(date 1699367850049)
@@ -24,7 +24,9 @@
 import org.openstreetmap.josm.data.validation.TestError;
 import org.openstreetmap.josm.data.validation.tests.MapCSSTagChecker;
 import org.openstreetmap.josm.data.validation.tests.OpeningHourTest;
+import org.openstreetmap.josm.data.validation.tests.TagChecker;
 import org.openstreetmap.josm.gui.MainApplication;
+import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
 import org.openstreetmap.josm.gui.util.GuiHelper;
 import org.openstreetmap.josm.tools.Logging;
 import org.openstreetmap.josm.tools.SubclassFilteredCollection;
@@ -55,11 +57,16 @@
         try {
             MapCSSTagChecker mapCSSTagChecker = OsmValidator.getTest(MapCSSTagChecker.class);
             OpeningHourTest openingHourTest = OsmValidator.getTest(OpeningHourTest.class);
-            OsmValidator.initializeTests(Arrays.asList(mapCSSTagChecker, openingHourTest));
+            TagChecker tagChecker = OsmValidator.getTest(TagChecker.class);
+            tagChecker.startTest(NullProgressMonitor.INSTANCE); //since initializeTest works if test is enabled
+            OsmValidator.initializeTests(Arrays.asList(mapCSSTagChecker, openingHourTest, tagChecker));
+
 
             List<TestError> errors = new ArrayList<>();
             openingHourTest.addErrorsForPrimitive(primitive, errors);
             errors.addAll(mapCSSTagChecker.getErrorsForPrimitive(primitive, ValidatorPrefHelper.PREF_OTHER.get()));
+            tagChecker.check(primitive);
+            errors.addAll(tagChecker.getErrors());
 
             boolean visible = !errors.isEmpty();
             String toolTipText = "<html>" + Utils.joinAsHtmlUnorderedList(Utils.transform(errors, e ->
Index: test/unit/org/openstreetmap/josm/data/validation/tests/TagCheckerTest.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/test/unit/org/openstreetmap/josm/data/validation/tests/TagCheckerTest.java b/test/unit/org/openstreetmap/josm/data/validation/tests/TagCheckerTest.java
--- a/test/unit/org/openstreetmap/josm/data/validation/tests/TagCheckerTest.java	(revision 18895)
+++ b/test/unit/org/openstreetmap/josm/data/validation/tests/TagCheckerTest.java	(date 1699562472646)
@@ -228,6 +228,27 @@
         assertFalse(errors.get(0).isFixable());
     }
 
+    @Test
+    void testRegionKey() throws IOException {
+        final List<TestError> errors = test(OsmUtils.createPrimitive("node highway=crossing crossing_ref=zebra"));
+        assertEquals(1, errors.size());
+        assertEquals("Invalid region for this preset", errors.get(0).getMessage());
+        assertEquals("Preset Pedestrian Crossing should not have the key crossing_ref", errors.get(0).getDescription());
+        assertEquals(Severity.WARNING, errors.get(0).getSeverity());
+        assertFalse(errors.get(0).isFixable());
+
+    }
+
+    @Test
+    void testRegionTag() throws IOException {
+        final List<TestError> errors = test(OsmUtils.createPrimitive("relation type=waterway ref:gnis=123456"));
+        assertEquals(1, errors.size());
+        assertEquals("Invalid region for this preset", errors.get(0).getMessage());
+        assertEquals("Preset Waterway should not have the key ref:gnis", errors.get(0).getDescription());
+        assertEquals(Severity.WARNING, errors.get(0).getSeverity());
+        assertFalse(errors.get(0).isFixable());
+    }
+
     /**
      * Key in presets but not in ignored.cfg. Caused a NPE with r14727.
      * @throws IOException if any I/O error occurs
Index: test/unit/org/openstreetmap/josm/gui/tagging/presets/items/CheckGroupTest.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/CheckGroupTest.java b/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/CheckGroupTest.java
--- a/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/CheckGroupTest.java	(revision 18895)
+++ b/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/CheckGroupTest.java	(date 1699558232234)
@@ -1,28 +1,15 @@
 // License: GPL. For details, see LICENSE file.
 package org.openstreetmap.josm.gui.tagging.presets.items;
 
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-import javax.swing.JPanel;
-
-import org.junit.jupiter.api.Test;
-import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItem;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemTest;
 
 /**
  * Unit tests of {@link CheckGroup} class.
  */
-class CheckGroupTest {
-    /**
-     * Unit test for {@link CheckGroup#addToPanel}.
-     */
-    @Test
-    void testAddToPanel() {
-        CheckGroup cg = new CheckGroup();
-        JPanel p = new JPanel();
-        assertEquals(0, p.getComponentCount());
-        assertFalse(cg.addToPanel(p, TaggingPresetItemGuiSupport.create(false)));
-        assertTrue(p.getComponentCount() > 0);
+class CheckGroupTest implements TaggingPresetItemTest {
+    @Override
+    public TaggingPresetItem getInstance() {
+        return new CheckGroup();
     }
 }
Index: test/unit/org/openstreetmap/josm/gui/tagging/presets/items/CheckTest.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/CheckTest.java b/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/CheckTest.java
--- a/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/CheckTest.java	(revision 18895)
+++ b/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/CheckTest.java	(date 1699559073413)
@@ -8,21 +8,30 @@
 
 import org.junit.jupiter.api.Test;
 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemTest;
 import org.openstreetmap.josm.testutils.annotations.Main;
 
 /**
  * Unit tests of {@link Check} class.
  */
 @Main
-class CheckTest {
+class CheckTest implements RegionSpecificTest, TaggingPresetItemTest {
+    @Override
+    public Check getInstance() {
+        final Check check = new Check();
+        check.key = "crossing:island";
+        return check;
+    }
+
     /**
      * Unit test for {@link Check#addToPanel}.
      */
+    @Override
     @Test
-    void testAddToPanel() {
+    public void testAddToPanel() {
         JPanel p = new JPanel();
         assertEquals(0, p.getComponentCount());
-        assertTrue(new Check().addToPanel(p, TaggingPresetItemGuiSupport.create(false)));
+        assertTrue(getInstance().addToPanel(p, TaggingPresetItemGuiSupport.create(false)));
         assertTrue(p.getComponentCount() > 0);
     }
 }
Index: test/unit/org/openstreetmap/josm/gui/tagging/presets/items/ComboTest.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/ComboTest.java b/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/ComboTest.java
--- a/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/ComboTest.java	(revision 18895)
+++ b/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/ComboTest.java	(date 1699559127332)
@@ -12,6 +12,7 @@
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
 import org.openstreetmap.josm.data.osm.OsmUtils;
 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemTest;
 import org.openstreetmap.josm.testutils.annotations.BasicPreferences;
 import org.openstreetmap.josm.testutils.annotations.I18n;
 import org.openstreetmap.josm.testutils.annotations.Main;
@@ -22,15 +23,21 @@
 @BasicPreferences
 @I18n("de")
 @Main
-class ComboTest {
+class ComboTest implements TaggingPresetItemTest {
+    @Override
+    public Combo getInstance() {
+        return new Combo();
+    }
+
     /**
-     * Unit test for {@link Combo#addToPanel}.
+     * Unit test for {@link Check#addToPanel}.
      */
+    @Override
     @Test
-    void testAddToPanel() {
+    public void testAddToPanel() {
         JPanel p = new JPanel();
         assertEquals(0, p.getComponentCount());
-        assertTrue(new Combo().addToPanel(p, TaggingPresetItemGuiSupport.create(false)));
+        assertTrue(getInstance().addToPanel(p, TaggingPresetItemGuiSupport.create(false)));
         assertTrue(p.getComponentCount() > 0);
     }
 
@@ -39,7 +46,7 @@
      */
     @Test
     void testUseLastAsDefault() {
-        Combo combo = new Combo();
+        final Combo combo = getInstance();
         combo.key = "addr:country";
         combo.values_from = "java.util.Locale#getISOCountries";
         OsmPrimitive way = OsmUtils.createPrimitive("way");
@@ -118,7 +125,7 @@
 
     @Test
     void testColor() {
-        Combo combo = new Combo();
+        final Combo combo = getInstance();
         combo.key = "colour";
         combo.values = "red;green;blue;black";
         combo.values_context = "color";
Index: test/unit/org/openstreetmap/josm/gui/tagging/presets/items/ItemSeparatorTest.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/ItemSeparatorTest.java b/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/ItemSeparatorTest.java
--- a/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/ItemSeparatorTest.java	(revision 18895)
+++ b/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/ItemSeparatorTest.java	(date 1699558232172)
@@ -1,27 +1,14 @@
 // License: GPL. For details, see LICENSE file.
 package org.openstreetmap.josm.gui.tagging.presets.items;
 
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-import javax.swing.JPanel;
-
-import org.junit.jupiter.api.Test;
-import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemTest;
 
 /**
  * Unit tests of {@link ItemSeparator} class.
  */
-class ItemSeparatorTest {
-    /**
-     * Unit test for {@link ItemSeparator#addToPanel}.
-     */
-    @Test
-    void testAddToPanel() {
-        JPanel p = new JPanel();
-        assertEquals(0, p.getComponentCount());
-        assertFalse(new ItemSeparator().addToPanel(p, TaggingPresetItemGuiSupport.create(false)));
-        assertTrue(p.getComponentCount() > 0);
+class ItemSeparatorTest implements TaggingPresetItemTest {
+    @Override
+    public ItemSeparator getInstance() {
+        return new ItemSeparator();
     }
 }
Index: test/unit/org/openstreetmap/josm/gui/tagging/presets/items/KeyTest.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/KeyTest.java b/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/KeyTest.java
--- a/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/KeyTest.java	(revision 18895)
+++ b/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/KeyTest.java	(date 1699558232199)
@@ -8,16 +8,26 @@
 
 import org.junit.jupiter.api.Test;
 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemTest;
 
 /**
  * Unit tests of {@link Key} class.
  */
-class KeyTest {
+class KeyTest implements RegionSpecificTest, TaggingPresetItemTest {
+    @Override
+    public Key getInstance() {
+        final Key key = new Key();
+        key.key = "highway";
+        key.value = "residential";
+        return key;
+    }
+
     /**
      * Unit test for {@link Key#addToPanel}.
      */
     @Test
-    void testAddToPanel() {
+    @Override
+    public void testAddToPanel() {
         JPanel p = new JPanel();
         assertEquals(0, p.getComponentCount());
         assertFalse(new Key().addToPanel(p, TaggingPresetItemGuiSupport.create(false)));
Index: test/unit/org/openstreetmap/josm/gui/tagging/presets/items/LabelTest.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/LabelTest.java b/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/LabelTest.java
--- a/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/LabelTest.java	(revision 18895)
+++ b/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/LabelTest.java	(date 1699559073436)
@@ -8,19 +8,26 @@
 
 import org.junit.jupiter.api.Test;
 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemTest;
 
 /**
  * Unit tests of {@link Label} class.
  */
-class LabelTest {
+class LabelTest implements TaggingPresetItemTest {
+    @Override
+    public Label getInstance() {
+        return new Label();
+    }
+
     /**
-     * Unit test for {@link Label#addToPanel}.
+     * Unit test for {@link Check#addToPanel}.
      */
+    @Override
     @Test
-    void testAddToPanel() {
+    public void testAddToPanel() {
         JPanel p = new JPanel();
         assertEquals(0, p.getComponentCount());
-        assertTrue(new Label().addToPanel(p, TaggingPresetItemGuiSupport.create(false)));
+        assertTrue(getInstance().addToPanel(p, TaggingPresetItemGuiSupport.create(false)));
         assertTrue(p.getComponentCount() > 0);
     }
 }
Index: test/unit/org/openstreetmap/josm/gui/tagging/presets/items/LinkTest.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/LinkTest.java b/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/LinkTest.java
--- a/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/LinkTest.java	(revision 18895)
+++ b/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/LinkTest.java	(date 1699558232204)
@@ -9,18 +9,25 @@
 
 import org.junit.jupiter.api.Test;
 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemTest;
 import org.openstreetmap.josm.spi.preferences.Config;
 
 /**
  * Unit tests of {@link Link} class.
  */
-class LinkTest {
+class LinkTest implements TaggingPresetItemTest {
+    @Override
+    public Link getInstance() {
+        return new Link();
+    }
+
     /**
      * Unit test for {@link Link#addToPanel}.
      */
+    @Override
     @Test
-    void testAddToPanel() {
-        Link l = new Link();
+    public void testAddToPanel() {
+        Link l = getInstance();
         JPanel p = new JPanel();
         assertEquals(0, p.getComponentCount());
         assertFalse(l.addToPanel(p, TaggingPresetItemGuiSupport.create(false)));
Index: test/unit/org/openstreetmap/josm/gui/tagging/presets/items/MultiSelectTest.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/MultiSelectTest.java b/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/MultiSelectTest.java
--- a/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/MultiSelectTest.java	(revision 18895)
+++ b/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/MultiSelectTest.java	(date 1699559073429)
@@ -8,21 +8,28 @@
 
 import org.junit.jupiter.api.Test;
 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemTest;
 import org.openstreetmap.josm.testutils.annotations.Main;
 
 /**
  * Unit tests of {@link MultiSelect} class.
  */
 @Main
-class MultiSelectTest {
+class MultiSelectTest implements TaggingPresetItemTest {
+    @Override
+    public MultiSelect getInstance() {
+        return new MultiSelect();
+    }
+
     /**
-     * Unit test for {@link MultiSelect#addToPanel}.
+     * Unit test for {@link Check#addToPanel}.
      */
+    @Override
     @Test
-    void testAddToPanel() {
+    public void testAddToPanel() {
         JPanel p = new JPanel();
         assertEquals(0, p.getComponentCount());
-        assertTrue(new MultiSelect().addToPanel(p, TaggingPresetItemGuiSupport.create(false)));
+        assertTrue(getInstance().addToPanel(p, TaggingPresetItemGuiSupport.create(false)));
         assertTrue(p.getComponentCount() > 0);
     }
 }
Index: test/unit/org/openstreetmap/josm/gui/tagging/presets/items/OptionalTest.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/OptionalTest.java b/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/OptionalTest.java
--- a/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/OptionalTest.java	(revision 18895)
+++ b/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/OptionalTest.java	(date 1699558232180)
@@ -1,27 +1,14 @@
 // License: GPL. For details, see LICENSE file.
 package org.openstreetmap.josm.gui.tagging.presets.items;
 
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-import javax.swing.JPanel;
-
-import org.junit.jupiter.api.Test;
-import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemTest;
 
 /**
  * Unit tests of {@link Optional} class.
  */
-class OptionalTest {
-    /**
-     * Unit test for {@link Optional#addToPanel}.
-     */
-    @Test
-    void testAddToPanel() {
-        JPanel p = new JPanel();
-        assertEquals(0, p.getComponentCount());
-        assertFalse(new Optional().addToPanel(p, TaggingPresetItemGuiSupport.create(false)));
-        assertTrue(p.getComponentCount() > 0);
+class OptionalTest implements TaggingPresetItemTest {
+    @Override
+    public Optional getInstance() {
+        return new Optional();
     }
 }
Index: test/unit/org/openstreetmap/josm/gui/tagging/presets/items/PresetLinkTest.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/PresetLinkTest.java b/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/PresetLinkTest.java
--- a/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/PresetLinkTest.java	(revision 18895)
+++ b/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/PresetLinkTest.java	(date 1699558232208)
@@ -1,31 +1,18 @@
 // License: GPL. For details, see LICENSE file.
 package org.openstreetmap.josm.gui.tagging.presets.items;
 
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-import javax.swing.JPanel;
-
-import org.junit.jupiter.api.Test;
-import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemTest;
 import org.openstreetmap.josm.testutils.annotations.TaggingPresets;
 
 /**
  * Unit tests of {@link PresetLink} class.
  */
 @TaggingPresets
-class PresetLinkTest {
-    /**
-     * Unit test for {@link PresetLink#addToPanel}.
-     */
-    @Test
-    void testAddToPanel() {
-        PresetLink l = new PresetLink();
-        l.preset_name = "River";
-        JPanel p = new JPanel();
-        assertEquals(0, p.getComponentCount());
-        assertFalse(l.addToPanel(p, TaggingPresetItemGuiSupport.create(false)));
-        assertTrue(p.getComponentCount() > 0);
+class PresetLinkTest implements TaggingPresetItemTest {
+    @Override
+    public PresetLink getInstance() {
+        PresetLink presetLink = new PresetLink();
+        presetLink.preset_name = "River";
+        return presetLink;
     }
 }
Index: test/unit/org/openstreetmap/josm/gui/tagging/presets/items/RegionSpecificTest.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/RegionSpecificTest.java b/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/RegionSpecificTest.java
new file mode 100644
--- /dev/null	(date 1699563077122)
+++ b/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/RegionSpecificTest.java	(date 1699563077122)
@@ -0,0 +1,50 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.tagging.presets.items;
+
+import static org.junit.jupiter.api.Assertions.assertAll;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.junit.jupiter.api.Test;
+import org.openstreetmap.josm.testutils.annotations.Territories;
+import org.xml.sax.SAXException;
+
+/**
+ * Test class for {@link RegionSpecific}
+ */
+@Territories
+interface RegionSpecificTest {
+    /**
+     * Get the test instance
+     * @return The instance to test
+     */
+    RegionSpecific getInstance();
+
+    @Test
+    default void testSetRegions() throws SAXException {
+        final RegionSpecific regionSpecific = getInstance();
+        if ("java.lang.Record".equals(regionSpecific.getClass().getSuperclass().getCanonicalName())) {
+            assertThrows(UnsupportedOperationException.class, () -> regionSpecific.setRegions("US"));
+        } else {
+            assertFalse(regionSpecific.regions() != null && regionSpecific.regions().contains("US"),
+                    "Using US as the test region for setting regions");
+            regionSpecific.setRegions("US");
+            assertAll(() -> assertEquals(1, regionSpecific.regions().size()),
+                    () -> assertEquals("US", regionSpecific.regions().iterator().next()));
+        }
+    }
+
+    @Test
+    default void testSetExcludeRegions() {
+        final RegionSpecific regionSpecific = getInstance();
+        if ("java.lang.Record".equals(regionSpecific.getClass().getSuperclass().getCanonicalName())) {
+            assertThrows(UnsupportedOperationException.class, () -> regionSpecific.setExclude_regions(true));
+        } else {
+            final boolean oldExclude = regionSpecific.exclude_regions();
+            regionSpecific.setExclude_regions(!oldExclude);
+            assertNotEquals(oldExclude, regionSpecific.exclude_regions());
+        }
+    }
+}
Index: test/unit/org/openstreetmap/josm/gui/tagging/presets/items/RolesTest.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/RolesTest.java b/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/RolesTest.java
--- a/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/RolesTest.java	(revision 18895)
+++ b/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/RolesTest.java	(date 1699558232252)
@@ -1,27 +1,15 @@
 // License: GPL. For details, see LICENSE file.
 package org.openstreetmap.josm.gui.tagging.presets.items;
 
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-import javax.swing.JPanel;
-
-import org.junit.jupiter.api.Test;
-import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItem;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemTest;
 
 /**
  * Unit tests of {@link Roles} class.
  */
-class RolesTest {
-    /**
-     * Unit test for {@link Roles#addToPanel}.
-     */
-    @Test
-    void testAddToPanel() {
-        JPanel p = new JPanel();
-        assertEquals(0, p.getComponentCount());
-        assertFalse(new Roles().addToPanel(p, TaggingPresetItemGuiSupport.create(false)));
-        assertTrue(p.getComponentCount() > 0);
+class RolesTest implements TaggingPresetItemTest {
+    @Override
+    public TaggingPresetItem getInstance() {
+        return new Roles();
     }
 }
Index: test/unit/org/openstreetmap/josm/gui/tagging/presets/items/SpaceTest.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/SpaceTest.java b/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/SpaceTest.java
--- a/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/SpaceTest.java	(revision 18895)
+++ b/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/SpaceTest.java	(date 1699558232246)
@@ -1,27 +1,14 @@
 // License: GPL. For details, see LICENSE file.
 package org.openstreetmap.josm.gui.tagging.presets.items;
 
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-import javax.swing.JPanel;
-
-import org.junit.jupiter.api.Test;
-import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemTest;
 
 /**
  * Unit tests of {@link Space} class.
  */
-class SpaceTest {
-    /**
-     * Unit test for {@link Space#addToPanel}.
-     */
-    @Test
-    void testAddToPanel() {
-        JPanel p = new JPanel();
-        assertEquals(0, p.getComponentCount());
-        assertFalse(new Space().addToPanel(p, TaggingPresetItemGuiSupport.create(false)));
-        assertTrue(p.getComponentCount() > 0);
+class SpaceTest implements TaggingPresetItemTest {
+    @Override
+    public Space getInstance() {
+        return new Space();
     }
 }
Index: test/unit/org/openstreetmap/josm/gui/tagging/presets/items/TextTest.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/TextTest.java b/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/TextTest.java
--- a/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/TextTest.java	(revision 18895)
+++ b/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/TextTest.java	(date 1699559073420)
@@ -8,21 +8,28 @@
 
 import org.junit.jupiter.api.Test;
 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemTest;
 import org.openstreetmap.josm.testutils.annotations.Main;
 
 /**
  * Unit tests of {@link Text} class.
  */
 @Main
-class TextTest {
+class TextTest implements TaggingPresetItemTest {
+    @Override
+    public Text getInstance() {
+        return new Text();
+    }
+
     /**
-     * Unit test for {@link Text#addToPanel}.
+     * Unit test for {@link Check#addToPanel}.
      */
+    @Override
     @Test
-    void testAddToPanel() {
+    public void testAddToPanel() {
         JPanel p = new JPanel();
         assertEquals(0, p.getComponentCount());
-        assertTrue(new Text().addToPanel(p, TaggingPresetItemGuiSupport.create(false)));
+        assertTrue(getInstance().addToPanel(p, TaggingPresetItemGuiSupport.create(false)));
         assertTrue(p.getComponentCount() > 0);
     }
 }
Index: test/unit/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetItemTest.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/test/unit/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetItemTest.java b/test/unit/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetItemTest.java
new file mode 100644
--- /dev/null	(date 1699558866646)
+++ b/test/unit/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetItemTest.java	(date 1699558866646)
@@ -0,0 +1,34 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.tagging.presets;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+
+import javax.swing.JPanel;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test class for {@link TaggingPresetItem}
+ */
+public interface TaggingPresetItemTest {
+    /**
+     * Get the instance to test
+     *
+     * @return The item to test
+     */
+    TaggingPresetItem getInstance();
+
+    /**
+     * Test method for {@link TaggingPresetItem#addToPanel(JPanel, TaggingPresetItemGuiSupport)}
+     */
+    @Test
+    default void testAddToPanel() {
+        TaggingPresetItem item = getInstance();
+        JPanel p = new JPanel();
+        assertEquals(0, p.getComponentCount());
+        assertFalse(item.addToPanel(p, TaggingPresetItemGuiSupport.create(false)));
+        assertNotEquals(0, p.getComponentCount());
+    }
+}
Index: test/unit/org/openstreetmap/josm/testutils/annotations/TaggingPresets.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/test/unit/org/openstreetmap/josm/testutils/annotations/TaggingPresets.java b/test/unit/org/openstreetmap/josm/testutils/annotations/TaggingPresets.java
--- a/test/unit/org/openstreetmap/josm/testutils/annotations/TaggingPresets.java	(revision 18895)
+++ b/test/unit/org/openstreetmap/josm/testutils/annotations/TaggingPresets.java	(date 1699561966102)
@@ -8,6 +8,7 @@
 import java.lang.annotation.Target;
 import java.util.Collection;
 
+import org.junit.jupiter.api.extension.BeforeAllCallback;
 import org.junit.jupiter.api.extension.BeforeEachCallback;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.junit.jupiter.api.extension.ExtensionContext;
@@ -24,19 +25,25 @@
 @Retention(RetentionPolicy.RUNTIME)
 @Target({ ElementType.METHOD, ElementType.TYPE })
 @BasicPreferences
+@Territories
 @ExtendWith(TaggingPresets.TaggingPresetsExtension.class)
 public @interface TaggingPresets {
 
-    class TaggingPresetsExtension implements BeforeEachCallback {
+    class TaggingPresetsExtension implements BeforeEachCallback, BeforeAllCallback {
         private static int expectedHashcode = 0;
 
+        @Override
+        public void beforeAll(ExtensionContext extensionContext) throws Exception {
+            setup();
+        }
+
         @Override
         public void beforeEach(ExtensionContext extensionContext) {
             setup();
         }
 
         /**
-         * Setup the tagging presets
+         * Set up the tagging presets
          */
         public static synchronized void setup() {
             final Collection<TaggingPreset> oldPresets = org.openstreetmap.josm.gui.tagging.presets.TaggingPresets.getTaggingPresets();
