diff --git a/CHANGELOG.md b/CHANGELOG.md
index 67a097c7ad853d0fbd8356b58ea49bcb0e85299c..41f569a3647f8d428c5b95674a4ee9050a8e6317 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,7 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - `ext_applicable` module for building fancy features which append
   functionality to entities (EXPERIMENTAL).
 - `ext_cosmetics` module which converts http(s) uris in property values into
-  clickable links.
+  clickable links (with tests)
 - Added a menu/toc for the tour
 - Added a previous and next buttons for pages in the tour
 - Added warnings to inform about minimum width when accessing tour and
diff --git a/build.properties.d/00_default.properties b/build.properties.d/00_default.properties
index 1c505bccd77282551b4c898cac3984a5990eeb42..386eb8bd22bd3f32b94d9b9ea4714b80e355ea8e 100644
--- a/build.properties.d/00_default.properties
+++ b/build.properties.d/00_default.properties
@@ -50,6 +50,7 @@ BUILD_MODULE_EXT_BOTTOM_LINE_TABLE_PREVIEW=DISABLED
 BUILD_MODULE_EXT_BOTTOM_LINE_TIFF_PREVIEW=DISABLED
 BUILD_MODULE_EXT_BOOKMARKS=ENABLED
 BUILD_MODULE_EXT_ANNOTATION=ENABLED
+BUILD_MODULE_EXT_COSMETICS_LINKIFY=DISABLED
 
 BUILD_MODULE_USER_MANAGEMENT=ENABLED
 BUILD_MODULE_USER_MANAGEMENT_CHANGE_OWN_PASSWORD_REALM=CaosDB
@@ -150,4 +151,5 @@ MODULE_DEPENDENCIES=(
     ext_sss_markdown.js
     ext_trigger_crawler_form.js
     ext_bookmarks.js
+    ext_cosmetics.js
 )
diff --git a/misc/ext_cosmetics_test_data.py b/misc/ext_cosmetics_test_data.py
new file mode 100755
index 0000000000000000000000000000000000000000..786f46c2d6bcd1d55488a15e2c4f50085f331950
--- /dev/null
+++ b/misc/ext_cosmetics_test_data.py
@@ -0,0 +1,67 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+# ** header v3.0
+# This file is a part of the CaosDB Project.
+#
+# Copyright (C) 2020 IndiScale GmbH <info@indiscale.com>
+# Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+#
+# ** end header
+
+import caosdb as db
+
+# clean
+old = db.execute_query("FIND Test*")
+if len(old):
+    old.delete()
+
+# data model
+datamodel = db.Container()
+datamodel.extend([
+    db.Property("TestProp", datatype=db.TEXT),
+    db.RecordType("TestRecordType"),
+])
+
+datamodel.insert()
+
+
+# test data
+testdata = db.Container()
+
+test_cases = [
+    "no link",
+    "https://example.com",
+    "https://example.com and http://example.com",
+    "this is text https://example.com",
+    "this is text https://example.com and this as well",
+    "this is text https://example.com and another linke https://example.com",
+    "this is text https://example.com and another linke https://example.com and more text",
+    ("this is a lot of text with links in it Lorem ipsum dolor sit amet, "
+     "consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore "
+     "et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud "
+     "exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. "
+     "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum "
+     "dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non "
+     "proident, sunt in culpa qui officia deserunt mollit anim id est "
+     "laborum.https://example.com and another linke https://example.com and "
+     "more text and here comes a very long link: "
+     "https://example.com/this/has/certainly/more/than/40/characters/just/count/if/you/dont/believe/it.html"),
+]
+for test_case in test_cases:
+    testdata.append(db.Record().add_parent("TestRecordType").add_property("TestProp",
+                                                                          test_case))
+testdata.insert()
diff --git a/src/core/js/ext_cosmetics.js b/src/core/js/ext_cosmetics.js
index 4d935a2a5afecd699fee1a24416c06b30d1adc46..f4f281123b39a87b7ef6848db4e84a81b5e30d9c 100644
--- a/src/core/js/ext_cosmetics.js
+++ b/src/core/js/ext_cosmetics.js
@@ -28,22 +28,33 @@
  */
 var cosmetics = new function () {
 
+    /**
+     * Cut-off length of links. When linkify processes the links any href
+     * longer than this will be cut off at character 25 and "[...]" will be
+     * appended for the link text.
+     */
+    var _link_cut_off_length = 40;
+
     var _linkify = function () {
         $('.caosdb-f-property-text-value').each(function (index) {
-            // TODO also extract and convert links surrounded by other text
-            if (/^https?:\/\//.test(this.innerText)) {
-                var uri = this.innerText;
-                var text = uri
+            if (/https?:\/\//.test(this.innerText)) {
+                var result = this.innerText.replace(/https?:\/\/[^\s]*/g, function (href, index) {
+                    var link_text = href;
+                    if (_link_cut_off_length > 4 && link_text.length > _link_cut_off_length) {
+                        link_text = link_text.substring(0, _link_cut_off_length - 5) + "[...]";
+                    }
+
+                    return `<a title="Open ${href} in a new tab." target="_blank" class="caosdb-v-property-href-value" href="${href}">${link_text} <i class="bi bi-box-arrow-up-right"></i></a>`;
+                });
 
-                $(this).parent().css("overflow", "hidden");
-                $(this).parent().css("text-overflow", "ellipsis");
-                $(this).html(`<a class="caosdb-v-property-href-value" href="${uri}">${text} <i class="bi bi-box-arrow-up-right"></i></a>`);
+                $(this).html(result);
             }
         });
     }
 
     /**
-     * Convert any text-value beginning with 'http(s)://' into a link.
+     * Convert any substring of a text-value beginning with 'http(s)://' into a
+     * link.
      *
      * A listener detects edit-mode changes and previews
      */
@@ -57,6 +68,7 @@ var cosmetics = new function () {
     }
 
     this.init = function () {
+        this.linkify = linkify;
         if ("${BUILD_MODULE_EXT_COSMETICS_LINKIFY}" == "ENABLED") {
             linkify();
         }
diff --git a/test/core/js/modules/ext_cosmetics.js.js b/test/core/js/modules/ext_cosmetics.js.js
new file mode 100644
index 0000000000000000000000000000000000000000..d5d4df7f10a2859bcd7318680d4f6720aedc6127
--- /dev/null
+++ b/test/core/js/modules/ext_cosmetics.js.js
@@ -0,0 +1,87 @@
+/*
+ * This file is a part of the CaosDB Project.
+ *
+ * Copyright (C) 2021 IndiScale GmbH <info@indiscale.com>
+ * Copyright (C) 2021 Timm Fitschen <t.fitschen@indiscale.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+'use strict';
+
+QUnit.module("ext_cosmetics.js", {
+    before: function (assert) {
+        cosmetics.init();
+        // setup before module
+    },
+    beforeEach: function (assert) {
+        // setup before each test
+    },
+    afterEach: function (assert) {
+        // teardown after each test
+    },
+    after: function (assert) {
+        // teardown after module
+    }
+});
+
+QUnit.test("linkify - https", function (assert) {
+    assert.ok(cosmetics.linkify, "linkify available");
+    var test_cases = [
+        ["https://link", 1],
+        ["this is other text https://link", 1],
+        ["https://link this is other text", 1],
+        ["this is other text https://link and this as well", 1],
+        ["this is other text https://link", 1],
+        ["this is other text https://link and here comes another link https://link and more text", 2],
+    ];
+    for (let test_case of test_cases) {
+        var text_value = $(`<div class="caosdb-f-property-text-value">${test_case[0]}</div>`);
+        $(document.body).append(text_value);
+        assert.equal($(text_value).find("a[href='https://link']").length, 0, "no link present");
+        cosmetics.linkify();
+        assert.equal($(text_value).find("a[href='https://link']").length, test_case[1], "link is present");
+        text_value.remove();
+    }
+});
+
+QUnit.test("linkify - http", function (assert) {
+    var test_cases = [
+        ["http://link", 1],
+        ["this is other text http://link", 1],
+        ["http://link this is other text", 1],
+        ["this is other text http://link and this as well", 1],
+        ["this is other text http://link", 1],
+        ["this is other text http://link and here comes another link http://link and more text", 2],
+    ];
+    for (let test_case of test_cases) {
+        var text_value = $(`<div class="caosdb-f-property-text-value">${test_case[0]}</div>`);
+        $(document.body).append(text_value);
+        assert.equal($(text_value).find("a[href='http://link']").length, 0, "no link present");
+        cosmetics.linkify();
+        assert.equal($(text_value).find("a[href='http://link']").length, test_case[1], "link is present");
+        text_value.remove();
+    }
+});
+
+QUnit.test("linkify cut-off (40)", function (assert) {
+    var test_case = "here is some text https://this.is.a.link/with/more/than/40/characters/ this is more text";
+    var text_value = $(`<div class="caosdb-f-property-text-value">${test_case}</div>`);
+    $(document.body).append(text_value);
+    assert.equal($(text_value).find("a").length, 0, "no link present");
+    cosmetics.linkify();
+    assert.equal($(text_value).find("a").length, 1, "link is present");
+    assert.equal($(text_value).find("a").text(), "https://this.is.a.link/with/more/th[...] ", "link text has been cut off");
+    text_value.remove();
+});
\ No newline at end of file