diff --git a/django_unicorn/static/js/component.js b/django_unicorn/static/js/component.js index 73eb52c2..0cba80f2 100644 --- a/django_unicorn/static/js/component.js +++ b/django_unicorn/static/js/component.js @@ -28,6 +28,7 @@ export class Component { this.document = args.document || document; this.walker = args.walker || walk; + this.window = args.window || window; this.root = undefined; this.modelEls = []; @@ -35,6 +36,7 @@ export class Component { this.loadingEls = []; this.keyEls = []; this.errors = {}; + this.return = {}; this.poll = {}; this.actionQueue = []; diff --git a/django_unicorn/static/js/messageSender.js b/django_unicorn/static/js/messageSender.js index 7eb3d449..b006be3e 100644 --- a/django_unicorn/static/js/messageSender.js +++ b/django_unicorn/static/js/messageSender.js @@ -31,7 +31,7 @@ export function send(component, callback) { Accept: "application/json", "X-Requested-With": "XMLHttpRequest", }; - headers[component.csrfTokenHeaderName] = getCsrfToken(); + headers[component.csrfTokenHeaderName] = getCsrfToken(component); fetch(component.syncUrl, { method: "POST", @@ -57,6 +57,16 @@ export function send(component, callback) { throw Error(responseJson.error); } + // Redirect to the specified url if it is set + // TODO: For turbolinks support look at https://github.com/livewire/livewire/blob/f2ba1977d73429911f81b3f6363ee8f8fea5abff/js/component/index.js#L330-L336 + if (responseJson.redirect && responseJson.redirect.url) { + component.window.location.href = responseJson.redirect.url; + + if (isFunction(callback)) { + callback([], null, null); + } + } + // Remove any unicorn validation messages before trying to merge with morphdom component.modelEls.forEach((element) => { // Re-initialize element to make sure it is up to date @@ -67,6 +77,7 @@ export function send(component, callback) { // Get the data from the response component.data = responseJson.data || {}; component.errors = responseJson.errors || {}; + component.return = responseJson.return || {}; const rerenderedComponent = responseJson.dom; morphdom(component.root, rerenderedComponent, MORPHDOM_OPTIONS); diff --git a/django_unicorn/static/js/utils.js b/django_unicorn/static/js/utils.js index 7c78042a..a38bd446 100644 --- a/django_unicorn/static/js/utils.js +++ b/django_unicorn/static/js/utils.js @@ -60,10 +60,10 @@ export function $(selector, scope) { /** * Get the CSRF token used by Django. */ -export function getCsrfToken() { +export function getCsrfToken(component) { // Default to looking for the CSRF in the cookie const cookieKey = "csrftoken="; - const csrfTokenCookie = document.cookie + const csrfTokenCookie = component.document.cookie .split(";") .filter((item) => item.trim().startsWith(cookieKey)); @@ -72,7 +72,9 @@ export function getCsrfToken() { } // Fall back to check for the CSRF hidden input - const csrfElements = document.getElementsByName("csrfmiddlewaretoken"); + const csrfElements = component.document.getElementsByName( + "csrfmiddlewaretoken" + ); if (csrfElements && csrfElements.length > 0) { return csrfElements[0].getAttribute("value"); diff --git a/django_unicorn/views.py b/django_unicorn/views.py index 61ce468a..0d99e654 100644 --- a/django_unicorn/views.py +++ b/django_unicorn/views.py @@ -4,6 +4,7 @@ from django.db.models import Model from django.http import HttpRequest, JsonResponse +from django.http.response import HttpResponseRedirect from django.views.decorators.csrf import csrf_protect from django.views.decorators.http import require_POST @@ -12,6 +13,7 @@ from .call_method_parser import InvalidKwarg, parse_call_method_name, parse_kwarg from .components import UnicornField, UnicornView from .errors import UnicornViewError +from .serializer import dumps from .utils import generate_checksum @@ -159,10 +161,9 @@ def _get_property_value(component: UnicornView, property_name: str) -> Any: def _call_method_name( component: UnicornView, method_name: str, params: List[Any] -) -> None: +) -> Any: """ Calls the method name with parameters. - Also updates the data dictionary which gets set back as part of the payload. Args: param component: Component to call method on. @@ -174,9 +175,9 @@ def _call_method_name( func = getattr(component, method_name) if params: - func(*params) + return func(*params) else: - func() + return func() class ComponentRequest: @@ -257,6 +258,7 @@ def message(request: HttpRequest, component_name: str = None) -> JsonResponse: component.hydrate() is_reset_called = False + return_value = None for action in component_request.action_queue: action_type = action.get("type") @@ -373,7 +375,7 @@ def message(request: HttpRequest, component_name: str = None) -> JsonResponse: validate_all_fields = True else: component.calling(method_name, params) - _call_method_name(component, method_name, params) + return_value = _call_method_name(component, method_name, params) component.called(method_name, params) else: raise UnicornViewError(f"Unknown action_type '{action_type}'") @@ -381,6 +383,20 @@ def message(request: HttpRequest, component_name: str = None) -> JsonResponse: # Re-load frontend context variables to deal with non-serializable properties component_request.data = orjson.loads(component.get_frontend_context_variables()) + return_data = {} + redirect_data = {} + + if return_value is not None: + if isinstance(return_value, HttpResponseRedirect): + redirect_data = { + "url": return_value.url, + } + else: + try: + return_data = orjson.loads(dumps(return_value)) + except: + pass + if not is_reset_called: if validate_all_fields: component.validate() @@ -400,6 +416,8 @@ def message(request: HttpRequest, component_name: str = None) -> JsonResponse: "dom": rendered_component, "data": component_request.data, "errors": component.errors, + "redirect": redirect_data, + "return": return_data, } return JsonResponse(res) diff --git a/example/unicorn/components/models.py b/example/unicorn/components/models.py index 8330a237..88fa5e93 100644 --- a/example/unicorn/components/models.py +++ b/example/unicorn/components/models.py @@ -1,3 +1,4 @@ +from django.shortcuts import redirect from django.utils.functional import cached_property from coffee.models import Flavor @@ -29,7 +30,8 @@ def hydrate(self): def add_instance_flavor(self): self.instance_flavor.save() - self.reset() + + return redirect(f"/models?createdId={self.instance_flavor.id}") def add_class_flavor(self): self.class_flavor.save() diff --git a/package-lock.json b/package-lock.json index 1ffdd377..4df2a1f9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,9 @@ "eslint-config-prettier": "^6.12.0", "eslint-plugin-import": "^2.22.0", "esm": "^3.2.25", + "fetch-mock": "^9.11.0", "jsdom": "^16.4.0", + "node-fetch": "^2.6.1", "prettier": "^2.1.2", "rollup": "^2.27.1", "rollup-plugin-babel": "^4.3.3", @@ -2922,6 +2924,17 @@ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "dev": true }, + "node_modules/core-js": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.8.1.tgz", + "integrity": "sha512-9Id2xHY1W7m8hCl8NkhQn5CufmF/WuR30BTRewvCXc1aZd3kMECwNZ69ndLbekKfakw9Rf2Xyc+QR6E7Gg+obg==", + "dev": true, + "hasInstallScript": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, "node_modules/core-js-compat": { "version": "3.6.5", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.6.5.tgz", @@ -3981,6 +3994,88 @@ "pend": "~1.2.0" } }, + "node_modules/fetch-mock": { + "version": "9.11.0", + "resolved": "https://registry.npmjs.org/fetch-mock/-/fetch-mock-9.11.0.tgz", + "integrity": "sha512-PG1XUv+x7iag5p/iNHD4/jdpxL9FtVSqRMUQhPab4hVDt80T1MH5ehzVrL2IdXO9Q2iBggArFvPqjUbHFuI58Q==", + "dev": true, + "dependencies": { + "@babel/core": "^7.0.0", + "@babel/runtime": "^7.0.0", + "core-js": "^3.0.0", + "debug": "^4.1.1", + "glob-to-regexp": "^0.4.0", + "is-subset": "^0.1.1", + "lodash.isequal": "^4.5.0", + "path-to-regexp": "^2.2.1", + "querystring": "^0.2.0", + "whatwg-url": "^6.5.0" + }, + "engines": { + "node": ">=4.0.0" + }, + "funding": { + "type": "charity", + "url": "https://www.justgiving.com/refugee-support-europe" + }, + "peerDependencies": { + "node-fetch": "*" + }, + "peerDependenciesMeta": { + "node-fetch": { + "optional": true + } + } + }, + "node_modules/fetch-mock/node_modules/debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/fetch-mock/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/fetch-mock/node_modules/tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/fetch-mock/node_modules/webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "dev": true + }, + "node_modules/fetch-mock/node_modules/whatwg-url": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz", + "integrity": "sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==", + "dev": true, + "dependencies": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + }, "node_modules/figures": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", @@ -4159,6 +4254,12 @@ "is-glob": "^4.0.1" } }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true + }, "node_modules/global-dirs": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-2.0.1.tgz", @@ -4837,6 +4938,12 @@ "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", "dev": true }, + "node_modules/is-subset": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-subset/-/is-subset-0.1.1.tgz", + "integrity": "sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY=", + "dev": true + }, "node_modules/is-symbol": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", @@ -5321,6 +5428,12 @@ "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=", "dev": true }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=", + "dev": true + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -5615,6 +5728,15 @@ "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", "dev": true }, + "node_modules/node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", + "dev": true, + "engines": { + "node": "4.x || >=6.0.0" + } + }, "node_modules/node-releases": { "version": "1.1.61", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.61.tgz", @@ -5912,6 +6034,12 @@ "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", "dev": true }, + "node_modules/path-to-regexp": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.4.0.tgz", + "integrity": "sha512-G6zHoVqC6GGTQkZwF4lkuEyMbVOjoBKAEybQUypI1WTkqinCOrq2x6U2+phkJ1XsEMTy4LjtwPI7HW+NVrRR2w==", + "dev": true + }, "node_modules/path-type": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", @@ -6322,6 +6450,15 @@ "integrity": "sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ==", "dev": true }, + "node_modules/querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "dev": true, + "engines": { + "node": ">=0.4.x" + } + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -10959,6 +11096,12 @@ } } }, + "core-js": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.8.1.tgz", + "integrity": "sha512-9Id2xHY1W7m8hCl8NkhQn5CufmF/WuR30BTRewvCXc1aZd3kMECwNZ69ndLbekKfakw9Rf2Xyc+QR6E7Gg+obg==", + "dev": true + }, "core-js-compat": { "version": "3.6.5", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.6.5.tgz", @@ -12038,6 +12181,67 @@ "pend": "~1.2.0" } }, + "fetch-mock": { + "version": "9.11.0", + "resolved": "https://registry.npmjs.org/fetch-mock/-/fetch-mock-9.11.0.tgz", + "integrity": "sha512-PG1XUv+x7iag5p/iNHD4/jdpxL9FtVSqRMUQhPab4hVDt80T1MH5ehzVrL2IdXO9Q2iBggArFvPqjUbHFuI58Q==", + "dev": true, + "requires": { + "@babel/core": "^7.0.0", + "@babel/runtime": "^7.0.0", + "core-js": "^3.0.0", + "debug": "^4.1.1", + "glob-to-regexp": "^0.4.0", + "is-subset": "^0.1.1", + "lodash.isequal": "^4.5.0", + "path-to-regexp": "^2.2.1", + "querystring": "^0.2.0", + "whatwg-url": "^6.5.0" + }, + "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "dev": true + }, + "whatwg-url": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz", + "integrity": "sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==", + "dev": true, + "requires": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + } + } + }, "figures": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", @@ -12216,6 +12420,12 @@ "is-glob": "^4.0.1" } }, + "glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true + }, "global-dirs": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-2.0.1.tgz", @@ -12910,6 +13120,12 @@ "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", "dev": true }, + "is-subset": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-subset/-/is-subset-0.1.1.tgz", + "integrity": "sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY=", + "dev": true + }, "is-symbol": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", @@ -13408,6 +13624,12 @@ "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=", "dev": true }, + "lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=", + "dev": true + }, "lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -13710,6 +13932,12 @@ "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", "dev": true }, + "node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", + "dev": true + }, "node-releases": { "version": "1.1.61", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.61.tgz", @@ -14009,6 +14237,12 @@ "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", "dev": true }, + "path-to-regexp": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.4.0.tgz", + "integrity": "sha512-G6zHoVqC6GGTQkZwF4lkuEyMbVOjoBKAEybQUypI1WTkqinCOrq2x6U2+phkJ1XsEMTy4LjtwPI7HW+NVrRR2w==", + "dev": true + }, "path-type": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", @@ -14427,6 +14661,12 @@ "integrity": "sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ==", "dev": true }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "dev": true + }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", diff --git a/package.json b/package.json index 11e37d8c..27c12a2f 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,9 @@ "eslint-config-prettier": "^6.12.0", "eslint-plugin-import": "^2.22.0", "esm": "^3.2.25", + "fetch-mock": "^9.11.0", "jsdom": "^16.4.0", + "node-fetch": "^2.6.1", "prettier": "^2.1.2", "rollup": "^2.27.1", "rollup-plugin-babel": "^4.3.3", diff --git a/pytest.ini b/pytest.ini index 9d1fec13..c2a27ae9 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,2 +1,4 @@ [pytest] addopts = --quiet --failed-first --reuse-db --nomigrations -p no:warnings +testpaths = + tests diff --git a/tests/js/component/messageSender.test.js b/tests/js/component/messageSender.test.js new file mode 100644 index 00000000..24674866 --- /dev/null +++ b/tests/js/component/messageSender.test.js @@ -0,0 +1,42 @@ +import test from "ava"; +import fetchMock from "fetch-mock"; +import { getComponent } from "../utils.js"; +import { send } from "../../../django_unicorn/static/js/messageSender.js"; + +test.cb("click on internal element", (t) => { + const html = ` + +