diff --git a/src/components/AddLayerModal/index.less b/src/components/AddLayerModal/index.less
new file mode 100644
index 000000000..51d2c812a
--- /dev/null
+++ b/src/components/AddLayerModal/index.less
@@ -0,0 +1,7 @@
+.add-layer-modal {
+
+ .ant-table-cell {
+ padding: 0.2rem;
+ }
+
+}
diff --git a/src/components/AddLayerModal/index.spec.tsx b/src/components/AddLayerModal/index.spec.tsx
new file mode 100644
index 000000000..fcc14cfa8
--- /dev/null
+++ b/src/components/AddLayerModal/index.spec.tsx
@@ -0,0 +1,9 @@
+import AddLayerModal from './index';
+
+describe('', () => {
+
+ it('is defined', () => {
+ expect(AddLayerModal).not.toBeUndefined();
+ });
+
+});
diff --git a/src/components/AddLayerModal/index.tsx b/src/components/AddLayerModal/index.tsx
new file mode 100644
index 000000000..e2ac0d70d
--- /dev/null
+++ b/src/components/AddLayerModal/index.tsx
@@ -0,0 +1,183 @@
+import React, {
+ useState
+} from 'react';
+
+import {
+ Button,
+ Input,
+ Modal,
+ ModalProps,
+ notification,
+ Table
+} from 'antd';
+
+import {
+ getUid
+} from 'ol';
+import OlLayerGroup from 'ol/layer/Group';
+
+import {
+ useTranslation
+} from 'react-i18next';
+
+import {
+ CapabilitiesUtil,
+ MapUtil
+} from '@terrestris/ol-util';
+import {
+ WMSLayer
+} from '@terrestris/ol-util/dist/types';
+
+import {
+ useMap
+} from '@terrestris/react-geo/dist/Hook/useMap';
+
+import useAppDispatch from '../../hooks/useAppDispatch';
+import useAppSelector from '../../hooks/useAppSelector';
+import {
+ hide
+} from '../../store/addLayerModal';
+import {
+ unsetSelectedKey
+} from '../../store/toolMenu';
+
+import './index.less';
+
+export type AddLayerModalProps = {} & Partial;
+
+export const AddLayerModal: React.FC = ({
+ ...restProps
+}): JSX.Element => {
+ const [loading, setLoading] = useState(false);
+ const [layers, setLayers] = useState([]);
+ const [selectedRowKeys, setSelectedRowKeys] = useState([]);
+ const [url, setUrl] = useState(
+ 'https://sgx.geodatenzentrum.de/wms_topplus_open?request=GetCapabilities&service=wms'
+ );
+
+ const isModalVisible = useAppSelector(state => state.addLayerModal.visible);
+
+ const dispatch = useAppDispatch();
+
+ const map = useMap();
+
+ const {
+ t
+ } = useTranslation();
+
+ const getCapabilities = async (capabilitiesUrl: string) => {
+ try {
+ setLoading(true);
+
+ const capabilities = await CapabilitiesUtil.getWmsCapabilities(capabilitiesUrl);
+ const externalLayers = CapabilitiesUtil.getLayersFromWmsCapabilities(capabilities, 'Title');
+
+ setLayers(externalLayers);
+ } catch (error) {
+ notification.error({
+ message: t('AddLayerModal.errorMessage'),
+ description: t('AddLayerModal.errorDescription')
+ });
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const closeModal = () => {
+ setSelectedRowKeys([]);
+ setLayers([]);
+ dispatch(hide());
+ dispatch(unsetSelectedKey('addLayer'));
+ };
+
+ const onAddSelected = () => {
+ const layersToAdd = layers.filter(layer => selectedRowKeys.includes(getUid(layer)));
+ addLayers(layersToAdd);
+ };
+
+ const onAddAll = () => {
+ addLayers(layers);
+ };
+
+ const addLayers = (layersToAdd: WMSLayer[]) => {
+ if (!map) {
+ return;
+ }
+
+ const targetFolderName = t('AddLayerModal.externalWmsFolder');
+ let targetGroup = MapUtil.getLayerByName(map, targetFolderName) as OlLayerGroup;
+ if (!targetGroup) {
+ targetGroup = new OlLayerGroup();
+ targetGroup.set('name', targetFolderName);
+ const existingGroups = map.getLayerGroup().getLayers();
+ existingGroups.insertAt(existingGroups?.getLength() || 0, targetGroup);
+ }
+
+ layersToAdd.forEach(layerToAdd => {
+ if (!targetGroup.getLayers().getArray().includes(layerToAdd)) {
+ layerToAdd.set('isExternalLayer', true);
+ targetGroup.getLayers().push(layerToAdd);
+ }
+ });
+
+ targetGroup.set('hideInLayerTree', targetGroup.getLayers().getLength() < 1);
+
+ closeModal();
+ };
+
+ return (
+
+ {t('AddLayerModal.addSelectedLayers')}
+ ,
+
+ ]}
+ {...restProps}
+ >
+ {
+ setUrl(event.target.value);
+ }}
+ onSearch={getCapabilities}
+ enterButton={true}
+ />
+ {
+ return record.get('title');
+ }
+ }
+ ]}
+ rowKey={(record: any) => getUid(record)}
+ rowSelection={{
+ selectedRowKeys,
+ onChange: setSelectedRowKeys
+ }}
+ pagination={false}
+ dataSource={layers}
+ />
+
+ );
+};
+
+export default AddLayerModal;
diff --git a/src/components/ApplicationInfo/index.spec.tsx b/src/components/ApplicationInfo/index.spec.tsx
new file mode 100644
index 000000000..69e665449
--- /dev/null
+++ b/src/components/ApplicationInfo/index.spec.tsx
@@ -0,0 +1,9 @@
+import ApplicationInfo from './index';
+
+describe('', () => {
+
+ it('is defined', () => {
+ expect(ApplicationInfo).not.toBeUndefined();
+ });
+
+});
diff --git a/src/components/BasicMapComponent/index.spec.tsx b/src/components/BasicMapComponent/index.spec.tsx
new file mode 100644
index 000000000..7f539e78f
--- /dev/null
+++ b/src/components/BasicMapComponent/index.spec.tsx
@@ -0,0 +1,9 @@
+import BasicMapComponent from './index';
+
+describe('', () => {
+
+ it('is defined', () => {
+ expect(BasicMapComponent).not.toBeUndefined();
+ });
+
+});
diff --git a/src/components/BasicNominatimSearch/index.spec.tsx b/src/components/BasicNominatimSearch/index.spec.tsx
new file mode 100644
index 000000000..24412e659
--- /dev/null
+++ b/src/components/BasicNominatimSearch/index.spec.tsx
@@ -0,0 +1,9 @@
+import BasicNominatimSearch from './index';
+
+describe('', () => {
+
+ it('is defined', () => {
+ expect(BasicNominatimSearch).not.toBeUndefined();
+ });
+
+});
diff --git a/src/components/Footer/index.spec.tsx b/src/components/Footer/index.spec.tsx
new file mode 100644
index 000000000..2d36eb4c1
--- /dev/null
+++ b/src/components/Footer/index.spec.tsx
@@ -0,0 +1,9 @@
+import Footer from './index';
+
+describe('', () => {
+
+ it('is defined', () => {
+ expect(Footer).not.toBeUndefined();
+ });
+
+});
diff --git a/src/components/Header/index.spec.tsx b/src/components/Header/index.spec.tsx
new file mode 100644
index 000000000..b160eb3e2
--- /dev/null
+++ b/src/components/Header/index.spec.tsx
@@ -0,0 +1,9 @@
+import Header from './index';
+
+describe('', () => {
+
+ it('is defined', () => {
+ expect(Header).not.toBeUndefined();
+ });
+
+});
diff --git a/src/components/LanguageSelector/index.spec.tsx b/src/components/LanguageSelector/index.spec.tsx
new file mode 100644
index 000000000..712f2942e
--- /dev/null
+++ b/src/components/LanguageSelector/index.spec.tsx
@@ -0,0 +1,9 @@
+import LanguageSelector from './index';
+
+describe('', () => {
+
+ it('is defined', () => {
+ expect(LanguageSelector).not.toBeUndefined();
+ });
+
+});
diff --git a/src/components/Permalink/index.spec.tsx b/src/components/Permalink/index.spec.tsx
new file mode 100644
index 000000000..618b59727
--- /dev/null
+++ b/src/components/Permalink/index.spec.tsx
@@ -0,0 +1,9 @@
+import Permalink from './index';
+
+describe('', () => {
+
+ it('is defined', () => {
+ expect(Permalink).not.toBeUndefined();
+ });
+
+});
diff --git a/src/components/PrintForm/CustomFieldInput/index.spec.tsx b/src/components/PrintForm/CustomFieldInput/index.spec.tsx
new file mode 100644
index 000000000..ce270487f
--- /dev/null
+++ b/src/components/PrintForm/CustomFieldInput/index.spec.tsx
@@ -0,0 +1,9 @@
+import CustomFieldInput from './index';
+
+describe('', () => {
+
+ it('is defined', () => {
+ expect(CustomFieldInput).not.toBeUndefined();
+ });
+
+});
diff --git a/src/components/PrintForm/LayoutSelect/index.spec.tsx b/src/components/PrintForm/LayoutSelect/index.spec.tsx
new file mode 100644
index 000000000..196fc63c3
--- /dev/null
+++ b/src/components/PrintForm/LayoutSelect/index.spec.tsx
@@ -0,0 +1,9 @@
+import LayoutSelect from './index';
+
+describe('', () => {
+
+ it('is defined', () => {
+ expect(LayoutSelect).not.toBeUndefined();
+ });
+
+});
diff --git a/src/components/PrintForm/OutputFormatSelect/index.spec.tsx b/src/components/PrintForm/OutputFormatSelect/index.spec.tsx
new file mode 100644
index 000000000..787ac14ff
--- /dev/null
+++ b/src/components/PrintForm/OutputFormatSelect/index.spec.tsx
@@ -0,0 +1,9 @@
+import OutputFormatSelect from './index';
+
+describe('', () => {
+
+ it('is defined', () => {
+ expect(OutputFormatSelect).not.toBeUndefined();
+ });
+
+});
diff --git a/src/components/PrintForm/ResolutionSelect/index.spec.tsx b/src/components/PrintForm/ResolutionSelect/index.spec.tsx
new file mode 100644
index 000000000..a42a297cf
--- /dev/null
+++ b/src/components/PrintForm/ResolutionSelect/index.spec.tsx
@@ -0,0 +1,9 @@
+import ResolutionSelect from './index';
+
+describe('', () => {
+
+ it('is defined', () => {
+ expect(ResolutionSelect).not.toBeUndefined();
+ });
+
+});
diff --git a/src/components/PrintForm/index.spec.tsx b/src/components/PrintForm/index.spec.tsx
new file mode 100644
index 000000000..829cedf9d
--- /dev/null
+++ b/src/components/PrintForm/index.spec.tsx
@@ -0,0 +1,9 @@
+import PrintForm from './index';
+
+describe('', () => {
+
+ it('is defined', () => {
+ expect(PrintForm).not.toBeUndefined();
+ });
+
+});
diff --git a/src/components/ToolMenu/Draw/index.spec.tsx b/src/components/ToolMenu/Draw/index.spec.tsx
new file mode 100644
index 000000000..d33c48ec3
--- /dev/null
+++ b/src/components/ToolMenu/Draw/index.spec.tsx
@@ -0,0 +1,155 @@
+// import TestUtil from '../../Util/TestUtil';
+
+import Draw from './index';
+
+describe('', () => {
+
+ it('is defined', () => {
+ expect(Draw).not.toBeUndefined();
+ });
+
+});
+// it('can be rendered', () => {
+// const wrapper = TestUtil.mountComponent(Panel);
+// expect(wrapper).not.toBeUndefined();
+// });
+
+// it('passes props to Rnd', () => {
+// const wrapper = TestUtil.mountComponent(Panel, {
+// className: 'podolski',
+// fc: 'koeln'
+// });
+// const rnd = wrapper.find('Rnd').getElements()[0];
+// expect(rnd.props.className).toContain('podolski');
+// expect(rnd.props.fc).toBe('koeln');
+// });
+
+// describe('#onKeyDown', () => {
+
+// const wrapper = TestUtil.mountComponent(Panel);
+
+// // Mock a DOM to play around
+// document.body.className = 'react-geo-panel';
+
+// const element = wrapper.instance()._rnd.getSelfElement();
+
+// it('is defined', () => {
+// expect(wrapper.instance().onKeyDown).not.toBeUndefined();
+// });
+
+// it('calls onEscape method if provided in props', () => {
+// const mockEvt = {
+// key: 'invalid_key'
+// };
+
+// wrapper.setProps({
+// onEscape: jest.fn()
+// });
+
+// const onEscSpy = jest.spyOn(wrapper.props(), 'onEscape');
+// const focusSpy = jest.spyOn(element, 'focus');
+
+// wrapper.instance().onKeyDown(mockEvt);
+// expect(onEscSpy).toHaveBeenCalledTimes(0);
+// expect(focusSpy).toHaveBeenCalledTimes(0);
+
+// // call once again with valid key and onEscape function
+// mockEvt.key = 'Escape';
+
+// wrapper.instance().onKeyDown(mockEvt);
+// expect(onEscSpy).toHaveBeenCalledTimes(1);
+// expect(focusSpy).toHaveBeenCalledTimes(1);
+// expect(element.className).toContain(document.activeElement.className);
+
+// onEscSpy.mockRestore();
+// focusSpy.mockRestore();
+// });
+// });
+
+// describe('#toggleCollapse', () => {
+// const wrapper = TestUtil.mountComponent(Panel);
+
+// it('is defined', () => {
+// expect(wrapper.instance().toggleCollapse).not.toBeUndefined();
+// });
+
+// it('inverts the collapsed property on the state', () => {
+// const oldState = wrapper.state();
+// wrapper.instance().toggleCollapse();
+// const newState = wrapper.state();
+// expect(oldState.collapsed).toBe(!newState.collapsed);
+// });
+// });
+
+// describe('#onResize', () => {
+// const wrapper = TestUtil.mountComponent(Panel);
+
+// it('is defined', () => {
+// expect(wrapper.instance().onResize).not.toBeUndefined();
+// });
+
+// it('sets resizing on the state to true', () => {
+// wrapper.instance().onResize(null, null, {clientHeight: 1337});
+// expect(wrapper.state().height).toBe(1337);
+// });
+
+// it('calls corresponding function "onResize" of props if defined', () => {
+// const onResizeMock = jest.fn();
+// const wrapperWithMockedFunction = TestUtil.mountComponent(Panel, {
+// onResize: onResizeMock
+// });
+// expect(wrapperWithMockedFunction.instance().onResizeStop).not.toBeUndefined();
+// wrapperWithMockedFunction.instance().onResize(null, null, {clientHeight: 4711});
+// expect(onResizeMock.mock.calls).toHaveLength(1);
+// });
+// });
+
+// describe('#onResizeStart', () => {
+// const wrapper = TestUtil.mountComponent(Panel);
+
+// it('is defined', () => {
+// expect(wrapper.instance().onResizeStart).not.toBeUndefined();
+// });
+
+// it('sets resizing on the state to true', () => {
+// wrapper.instance().onResizeStart();
+// const state = wrapper.state();
+// expect(state.resizing).toBe(true);
+// });
+
+// it('calls corresponding function "onResizeStart" of props if defined', () => {
+// const onResizeStartMock = jest.fn();
+// const wrapperWithMockedFunction = TestUtil.mountComponent(Panel, {
+// onResizeStart: onResizeStartMock
+// });
+// expect(wrapperWithMockedFunction.instance().onResizeStart).not.toBeUndefined();
+// wrapperWithMockedFunction.instance().onResizeStart();
+// expect(onResizeStartMock.mock.calls).toHaveLength(1);
+// });
+// });
+
+// describe('#onResizeStop', () => {
+// const wrapper = TestUtil.mountComponent(Panel);
+
+// it('is defined', () => {
+// expect(wrapper.instance().onResizeStop).not.toBeUndefined();
+// });
+
+// it('sets the el size on the state', () => {
+// wrapper.instance().onResizeStop();
+// const state = wrapper.state();
+// expect(state.resizing).toBe(false);
+// });
+
+// it('calls corresponding function "onResizeStop" of props if defined', () => {
+// const onResizeStopMock = jest.fn();
+// const wrapperWithMockedFunction = TestUtil.mountComponent(Panel, {
+// onResizeStop: onResizeStopMock
+// });
+// expect(wrapperWithMockedFunction.instance().onResizeStop).not.toBeUndefined();
+// wrapperWithMockedFunction.instance().onResizeStop();
+// expect(onResizeStopMock.mock.calls).toHaveLength(1);
+// });
+// });
+
+// });
diff --git a/src/components/ToolMenu/FeatureInfo/FeaturePropertyGrid/index.spec.tsx b/src/components/ToolMenu/FeatureInfo/FeaturePropertyGrid/index.spec.tsx
new file mode 100644
index 000000000..effdc5f2b
--- /dev/null
+++ b/src/components/ToolMenu/FeatureInfo/FeaturePropertyGrid/index.spec.tsx
@@ -0,0 +1,9 @@
+import FeaturePropertyGrid from './index';
+
+describe('', () => {
+
+ it('is defined', () => {
+ expect(FeaturePropertyGrid).not.toBeUndefined();
+ });
+
+});
diff --git a/src/components/ToolMenu/FeatureInfo/index.spec.tsx b/src/components/ToolMenu/FeatureInfo/index.spec.tsx
new file mode 100644
index 000000000..098b412a2
--- /dev/null
+++ b/src/components/ToolMenu/FeatureInfo/index.spec.tsx
@@ -0,0 +1,9 @@
+import FeatureInfo from './index';
+
+describe('', () => {
+
+ it('is defined', () => {
+ expect(FeatureInfo).not.toBeUndefined();
+ });
+
+});
diff --git a/src/components/ToolMenu/LayerTree/LayerTreeContextMenu/index.spec.tsx b/src/components/ToolMenu/LayerTree/LayerTreeContextMenu/index.spec.tsx
new file mode 100644
index 000000000..fc397be2f
--- /dev/null
+++ b/src/components/ToolMenu/LayerTree/LayerTreeContextMenu/index.spec.tsx
@@ -0,0 +1,9 @@
+import LayerTreeContextMenu from './index';
+
+describe('', () => {
+
+ it('is defined', () => {
+ expect(LayerTreeContextMenu).not.toBeUndefined();
+ });
+
+});
diff --git a/src/components/ToolMenu/LayerTree/index.spec.tsx b/src/components/ToolMenu/LayerTree/index.spec.tsx
new file mode 100644
index 000000000..057b40e55
--- /dev/null
+++ b/src/components/ToolMenu/LayerTree/index.spec.tsx
@@ -0,0 +1,9 @@
+import LayerTree from './index';
+
+describe('', () => {
+
+ it('is defined', () => {
+ expect(LayerTree).not.toBeUndefined();
+ });
+
+});
diff --git a/src/components/ToolMenu/Measure/index.spec.tsx b/src/components/ToolMenu/Measure/index.spec.tsx
new file mode 100644
index 000000000..3d116beb5
--- /dev/null
+++ b/src/components/ToolMenu/Measure/index.spec.tsx
@@ -0,0 +1,9 @@
+import Measure from './index';
+
+describe('', () => {
+
+ it('is defined', () => {
+ expect(Measure).not.toBeUndefined();
+ });
+
+});
diff --git a/src/components/ToolMenu/index.spec.tsx b/src/components/ToolMenu/index.spec.tsx
new file mode 100644
index 000000000..4f38e8c5c
--- /dev/null
+++ b/src/components/ToolMenu/index.spec.tsx
@@ -0,0 +1,9 @@
+import ToolMenu from './index';
+
+describe('', () => {
+
+ it('is defined', () => {
+ expect(ToolMenu).not.toBeUndefined();
+ });
+
+});
diff --git a/src/components/UserMenu/index.spec.tsx b/src/components/UserMenu/index.spec.tsx
new file mode 100644
index 000000000..bcc5e3226
--- /dev/null
+++ b/src/components/UserMenu/index.spec.tsx
@@ -0,0 +1,9 @@
+import UserMenu from './index';
+
+describe('', () => {
+
+ it('is defined', () => {
+ expect(UserMenu).not.toBeUndefined();
+ });
+
+});