Skip to content
  • Unwatch

    Notifications

  • Fork

    Fork livewire

    If this dialog fails to load, you can visit the fork page directly.

Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also .

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also .
base repository: livewire/livewire
base: v2.4.0
head repository: livewire/livewire
compare: master
Commits on Mar 01, 2021
This PR updates `WithPagination` to account for  `queryString()` method introduced in 2.4.0
* fixed TestableLivewire to always return an instance of itself when calling unknown method

* added test
* allow App namespace in config - closes #2107

* added test

* fixed namespacing to also work with app default - #2107
Commits on Mar 24, 2021
…#2517)

* Add failing test for dynamic nested entangled components

* Bump Alpine to 2.8.2 for browser tests
Commits on Apr 04, 2021
* Add helpful entangle error

* Build assets
* Add isPreviewable Method to TemporaryUpload Class

* Add isPreviewable Tests
* fix $wire.emit, $wire.emitUp and $wire.emitTo

* add test for $wire.emit, $wire.emitSelf, $wire.emitTo, and $wire.emitUp

* commit build artifacts

* Build assets

Co-authored-by: Caleb Porzio <calebporzio@gmail.com>
Commits on Apr 12, 2021
* rollback & simplfy

* add tests
* Add actingAs method to docblock

* Add mixin for forwarded calls to the response
* Add tests for optional route parameters

* Add support for optional route parameters

* wip

Co-authored-by: Caleb Porzio <calebporzio@gmail.com>
Commits on Apr 16, 2021
* Allow polling to be disabled outside viewport

* feat: block 95% poll requests out of viewport

This will stop polling if the element is not in the
viewport by default. If it needs to continue even
when it is not in the viewport, add `keep-alive`:

`wire:poll.keep-alive`

* chore: rewrite comment

* feat: don't poll element outside viewport

The .visible modifier ensures that polling only happens
when the element is within the viewport.

* tests: add test for visibile polling

* feat: never poll instead of 5% of the time

* fix: first check for the modifier

* chore: add livewire js builds
* Avoid exception for unsupported HTTP methods

In case a route is defined, but the utilized HTTP method (which is normally POST) is not supported, an exception other than `NotFoundHttpException` is thrown. By catching this `MethodNotAllowedHttpException`, we avoid exceptions to bubble up to the user.

* Add browser test for method not allowed
…#2725)

* Fixes to ensure Livewire can run in a sessionless Laravel application

* wip

* Merge master and re-build

Co-authored-by: Caleb Porzio <calebporzio@gmail.com>
Commits on Apr 21, 2021
* Add dynamic Livewire component support

* Add tag compiler tests

* Add support for "is" syntax

* Add check for existing dynamic component
* Add params method to macros

* Add params tests
Commits on Apr 25, 2021
* Fix layoutData bug

* Update tests
Showing with 1,300 additions and 79 deletions.
  1. +2 −1 README.md
  2. +1 −1 config/livewire.php
  3. +2 −2 dist/livewire.js
  4. +1 −1 dist/livewire.js.map
  5. +1 −1 dist/manifest.json
  6. +18 −1 js/component/Polling.js
  7. +10 −1 js/component/SupportAlpine.js
  8. +4 −1 js/component/UploadManager.js
  9. +3 −2 js/component/index.js
  10. +11 −5 js/connection/index.js
  11. +7 −7 js/dom/morphdom/morphdom.js
  12. +3 −10 js/util/getCsrfToken.js
  13. +1 −2 src/Commands/ComponentParser.php
  14. +0 −2 src/Commands/FileManipulationCommand.php
  15. +80 −2 src/Commands/MakeCommand.php
  16. +5 −0 src/Commands/StubsCommand.php
  17. +2 −2 src/Commands/the-tao.php
  18. +1 −1 src/ComponentConcerns/HandlesActions.php
  19. +13 −0 src/Exceptions/ComponentAttributeMissingOnDynamicComponentException.php
  20. +15 −0 src/Exceptions/DirectlyCallingLifecycleHooksNotAllowedException.php
  21. +16 −0 src/HydrationMiddleware/PerformActionCalls.php
  22. +1 −0 src/Livewire.php
  23. +1 −1 src/LivewireBladeDirectives.php
  24. +7 −2 src/LivewireManager.php
  25. +37 −7 src/LivewireServiceProvider.php
  26. +33 −7 src/LivewireTagCompiler.php
  27. +8 −0 src/Macros/DuskBrowserMacros.php
  28. +15 −0 src/Macros/ViewMacros.php
  29. +4 −3 src/RenameMe/SupportBrowserHistory.php
  30. +2 −2 src/RenameMe/SupportRedirects.php
  31. +12 −7 src/TemporaryUploadedFile.php
  32. +19 −0 src/Testing/Concerns/MakesAssertions.php
  33. +4 −1 src/Testing/TestableLivewire.php
  34. +7 −3 src/WithPagination.php
  35. +1 −1 src/views/pagination/tailwind.blade.php
  36. +15 −0 tests/AppLayout.php
  37. +54 −0 tests/Browser/Alpine/Emit/EmitComponent.php
  38. +24 −0 tests/Browser/Alpine/Emit/EmitNestedComponent.php
  39. +36 −0 tests/Browser/Alpine/Emit/Test.php
  40. +23 −0 tests/Browser/Alpine/Entangle/EntangleNestedChildComponent.php
  41. +41 −0 tests/Browser/Alpine/Entangle/EntangleNestedParentComponent.php
  42. +13 −0 tests/Browser/Alpine/Entangle/Test.php
  43. +24 −0 tests/Browser/DynamicComponentLoading/ClickableComponent.php
  44. +24 −0 tests/Browser/DynamicComponentLoading/Test.php
  45. +7 −0 tests/Browser/DynamicComponentLoading/view-clickable-component.blade.php
  46. +7 −0 tests/Browser/DynamicComponentLoading/view-dynamic-component.blade.php
  47. +25 −0 tests/Browser/DynamicComponentLoading/view-load-dynamic-component.blade.php
  48. +1 −0 tests/Browser/Morphdom/Component.php
  49. +10 −0 tests/Browser/Morphdom/Test.php
  50. +11 −0 tests/Browser/Morphdom/view.blade.php
  51. +18 −0 tests/Browser/PollingViewport/Component.php
  52. +25 −0 tests/Browser/PollingViewport/Test.php
  53. +11 −0 tests/Browser/PollingViewport/view.blade.php
  54. +26 −0 tests/Browser/SyncHistory/ComponentWithOptionalParameter.php
  55. +13 −0 tests/Browser/SyncHistory/Test.php
  56. +16 −0 tests/Browser/TestCase.php
  57. +2 −0 tests/Browser/console/.gitignore
  58. +2 −0 tests/Browser/screenshots/.gitignore
  59. +2 −0 tests/Browser/source/.gitignore
  60. +12 −0 tests/Browser/views/layouts/app-for-normal-views.blade.php
  61. +1 −1 tests/Browser/views/layouts/app.blade.php
  62. +2 −2 tests/Unit/BladeComponentAttributeMacrosTest.php
  63. +94 −0 tests/Unit/CantCallLifecycleHooksDirectlyFromJSTest.php
  64. +64 −0 tests/Unit/ComponentLayoutTest.php
  65. +41 −0 tests/Unit/ComponentNameAndNamespaceTest.php
  66. +4 −0 tests/Unit/FileUploadsTest.php
  67. +117 −0 tests/Unit/LivewireDirectivesTest.php
  68. +5 −0 tests/Unit/MakeCommandTest.php
  69. +39 −0 tests/Unit/NestingComponentsTest.php
  70. +1 −0 tests/Unit/StubCommandTest.php
  71. +118 −0 tests/Unit/TagCompilerTest.php
  72. +17 −0 tests/Unit/TestableLivewireCanAssertStatusCodesTest.php
  73. +7 −0 tests/Unit/views/layouts/app-from-class-component.blade.php
  74. +1 −0 tests/Unit/views/layouts/data-test.blade.php
@@ -10,7 +10,7 @@ Awesome Livewire stuff here: https://github.com/imliam/awesome-livewire


All contributions are welcomed! (but please submit an issue to make sure the PR is warranted first) All contributions are welcomed! (but please submit an issue to make sure the PR is warranted first)


Open GitHub issues for all bugs. Ideas and questions belong on the [forum](https://forum.laravel-livewire.com) or [Discord server](https://discord.gg/livewire). Open GitHub issues for all bugs. Ideas and questions belong in [Discussions](https://github.com/livewire/livewire/discussions) or [Discord server](https://discord.gg/livewire).


Contribute to the docs here: https://github.com/livewire/docs Contribute to the docs here: https://github.com/livewire/docs


@@ -28,6 +28,7 @@ Livewire uses semantic versioning and will use the following release schedule st
* Refine the "asset_url" config. Potentially change to "app_url" (https://github.com/livewire/livewire/pull/1693) * Refine the "asset_url" config. Potentially change to "app_url" (https://github.com/livewire/livewire/pull/1693)
* Support multiple pagination (https://github.com/livewire/livewire/pull/1997) * Support multiple pagination (https://github.com/livewire/livewire/pull/1997)
* A CSP-safe mode for Livewire (https://github.com/livewire/livewire/pull/2485#issuecomment-784355989) * A CSP-safe mode for Livewire (https://github.com/livewire/livewire/pull/2485#issuecomment-784355989)
* Add `$wire.errors()` type deal


## Contributors ✨ ## Contributors ✨


@@ -38,7 +38,7 @@
| the view returned by SomeComponent will be wrapped in "layouts.app" | the view returned by SomeComponent will be wrapped in "layouts.app"
| |
*/ */

'layout' => 'layouts.app', 'layout' => 'layouts.app',


/* /*

Large diffs are not rendered by default.

Large diffs are not rendered by default.

@@ -1 +1 @@
{"/livewire.js":"/livewire.js?id=25f025805c3c370f7e87"} {"/livewire.js":"/livewire.js?id=54d078b2ce39327a1702"}
@@ -1,5 +1,5 @@
import MethodAction from '@/action/method' import MethodAction from '@/action/method'
import { wireDirectives} from '@/util' import { wireDirectives } from '@/util'
import store from '@/Store' import store from '@/Store'


export default function () { export default function () {
@@ -56,9 +56,26 @@ function fireActionOnInterval(node, component) {
if (Math.random() < .95) return if (Math.random() < .95) return
} }


// Only poll visible elements. Visible elements are elements that
// are visible in the current viewport.
if (directive.modifiers.includes('visible') && ! inViewport(directive.el)) {
return
}

// Don't poll if livewire is offline as well. // Don't poll if livewire is offline as well.
if (store.livewireIsOffline) return if (store.livewireIsOffline) return


component.addAction(new MethodAction(method, directive.params, node)) component.addAction(new MethodAction(method, directive.params, node))
}, interval); }, interval);
} }

function inViewport(el) {
var bounding = el.getBoundingClientRect();

return (
bounding.top < (window.innerHeight || document.documentElement.clientHeight) &&
bounding.left < (window.innerWidth || document.documentElement.clientWidth) &&
bounding.bottom > 0 &&
bounding.right > 0
);
}
@@ -64,10 +64,19 @@ function supportEntangle() {
let isDeferred = value.isDeferred let isDeferred = value.isDeferred
let livewireComponent = livewireEl.__livewire let livewireComponent = livewireEl.__livewire


let livewirePropertyValue = livewireEl.__livewire.get(livewireProperty)

// Check to see if the Livewire property exists and if not log a console error
// and return so everything else keeps running.
if (typeof livewirePropertyValue === 'undefined') {
console.error(`Livewire Entangle Error: Livewire property '${livewireProperty}' cannot be found`)
return
}

// Let's set the initial value of the Alpine prop to the Livewire prop's value. // Let's set the initial value of the Alpine prop to the Livewire prop's value.
component.unobservedData[key] component.unobservedData[key]
// We need to stringify and parse it though to get a deep clone. // We need to stringify and parse it though to get a deep clone.
= JSON.parse(JSON.stringify(livewireEl.__livewire.get(livewireProperty))) = JSON.parse(JSON.stringify(livewirePropertyValue))


let blockAlpineWatcher = false let blockAlpineWatcher = false


@@ -71,10 +71,13 @@ class UploadManager {
Array.from(this.uploadBag.first(name).files).forEach(file => formData.append('files[]', file)) Array.from(this.uploadBag.first(name).files).forEach(file => formData.append('files[]', file))


let headers = { let headers = {
'X-CSRF-TOKEN': getCsrfToken(),
'Accept': 'application/json', 'Accept': 'application/json',
} }


let csrfToken = getCsrfToken()

if (csrfToken) headers['X-CSRF-TOKEN'] = csrfToken

this.makeRequest(name, formData, 'post', url, headers, response => { this.makeRequest(name, formData, 'post', url, headers, response => {
return response.paths return response.paths
}) })
@@ -641,8 +641,9 @@ export default class Component {
// Forward "emits" to base Livewire object. // Forward "emits" to base Livewire object.
if (typeof property === 'string' && property.match(/^emit.*/)) return function (...args) { if (typeof property === 'string' && property.match(/^emit.*/)) return function (...args) {
if (property === 'emitSelf') return store.emitSelf(component.id, ...args) if (property === 'emitSelf') return store.emitSelf(component.id, ...args)

if (property === 'emitUp') return store.emitUp(component.el, ...args)
return store[property].apply(component, args)
return store[property](...args)
} }


if ( if (
@@ -13,8 +13,16 @@ export default class Connection {
return componentStore.onErrorCallback(status) return componentStore.onErrorCallback(status)
} }


showExpiredMessage() {
confirm(
'This page has expired due to inactivity.\nWould you like to refresh the page?'
) && window.location.reload()
}

sendMessage(message) { sendMessage(message) {
let payload = message.payload() let payload = message.payload()
let csrfToken = getCsrfToken()
let socketId = this.getSocketId()


if (window.__testing_request_interceptor) { if (window.__testing_request_interceptor) {
return window.__testing_request_interceptor(payload, this) return window.__testing_request_interceptor(payload, this)
@@ -31,12 +39,12 @@ export default class Connection {
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Accept': 'text/html, application/xhtml+xml', 'Accept': 'text/html, application/xhtml+xml',
'X-CSRF-TOKEN': getCsrfToken(),
'X-Socket-ID': this.getSocketId(),
'X-Livewire': true, 'X-Livewire': true,


// We'll set this explicitly to mitigate potential interference from ad-blockers/etc. // We'll set this explicitly to mitigate potential interference from ad-blockers/etc.
'Referer': window.location.href, 'Referer': window.location.href,
...(csrfToken && { 'X-CSRF-TOKEN': csrfToken }),
...(socketId && { 'X-Socket-ID': socketId })
}, },
} }
) )
@@ -58,9 +66,7 @@ export default class Connection {


store.sessionHasExpired = true store.sessionHasExpired = true


confirm( this.showExpiredMessage()
'This page has expired due to inactivity.\nWould you like to refresh the page?'
) && window.location.reload()
} else { } else {
response.text().then(response => { response.text().then(response => {
this.showHtmlModal(response) this.showHtmlModal(response)
@@ -10,8 +10,8 @@


'use strict'; 'use strict';


import { compareNodeNames, toElement, moveChildren, createElementNS, doc } from './util';
import specialElHandlers from './specialElHandlers'; import specialElHandlers from './specialElHandlers';
import { compareNodeNames, createElementNS, doc, moveChildren, toElement } from './util';


var ELEMENT_NODE = 1; var ELEMENT_NODE = 1;
var DOCUMENT_FRAGMENT_NODE = 11; var DOCUMENT_FRAGMENT_NODE = 11;
@@ -153,15 +153,15 @@ export default function morphdomFactory(morphAttrs) {
if (unmatchedFromEl && compareNodeNames(curChild, unmatchedFromEl)) { if (unmatchedFromEl && compareNodeNames(curChild, unmatchedFromEl)) {
curChild.parentNode.replaceChild(unmatchedFromEl, curChild); curChild.parentNode.replaceChild(unmatchedFromEl, curChild);
morphEl(unmatchedFromEl, curChild); morphEl(unmatchedFromEl, curChild);

// @livewireModification
// Otherwise, "curChild" will be unnatached when it is passed to "handleNodeAdde"
// things like .parent and .closest will break.
curChild = unmatchedFromEl
} }
else {
handleNodeAdded(curChild);
}
}
else {
handleNodeAdded(curChild);
} }


handleNodeAdded(curChild);
curChild = nextSibling; curChild = nextSibling;
} }
} }
@@ -1,16 +1,9 @@
export function getCsrfToken() { export function getCsrfToken() {
const tokenTag = document.head.querySelector('meta[name="csrf-token"]') const tokenTag = document.head.querySelector('meta[name="csrf-token"]')
let token


if (!tokenTag) { if (tokenTag) {
if (!window.livewire_token) { return tokenTag.content
throw new Error('Whoops, looks like you haven\'t added a "csrf-token" meta tag')
}

token = window.livewire_token
} else {
token = tokenTag.content
} }


return token return window.livewire_token ?? undefined
} }
@@ -200,8 +200,7 @@ public function wisdomOfTheTao()


public static function generatePathFromNamespace($namespace) public static function generatePathFromNamespace($namespace)
{ {
$name = str($namespace)->replaceFirst(app()->getNamespace(), ''); $name = str($namespace)->finish('\\')->replaceFirst(app()->getNamespace(), '');

return app('path').'/'.str_replace('\\', '/', $name); return app('path').'/'.str_replace('\\', '/', $name);
} }


@@ -52,8 +52,6 @@ public function writeWelcomeMessage()
if(PHP_OS_FAMILY == 'Linux') exec('xdg-open https://github.com/livewire/livewire'); if(PHP_OS_FAMILY == 'Linux') exec('xdg-open https://github.com/livewire/livewire');


$this->line("Thanks! Means the world to me!"); $this->line("Thanks! Means the world to me!");
} else {
$this->line("I understand, but am not going to pretend I'm not sad about it...");
} }
} }
} }
@@ -48,7 +48,7 @@ public function handle()
} }


if ($test) { if ($test) {
$test && $this->line("<options=bold;fg=green>Test:</> {$this->parser->relativeTestPath()}"); $test && $this->line("<options=bold;fg=green>TEST:</> {$this->parser->relativeTestPath()}");
} }


if ($showWelcomeMessage && ! app()->environment('testing')) { if ($showWelcomeMessage && ! app()->environment('testing')) {
@@ -115,6 +115,84 @@ protected function createTest($force = false)


public function isReservedClassName($name) public function isReservedClassName($name)
{ {
return array_search($name, ['Parent', 'Component', 'Interface']) !== false; return array_search(strtolower($name), $this->getReservedName()) !== false;
} }

private function getReservedName()
{
return [
'parent',
'component',
'interface',
'__halt_compiler',
'abstract',
'and',
'array',
'as',
'break',
'callable',
'case',
'catch',
'class',
'clone',
'const',
'continue',
'declare',
'default',
'die',
'do',
'echo',
'else',
'elseif',
'empty',
'enddeclare',
'endfor',
'endforeach',
'endif',
'endswitch',
'endwhile',
'eval',
'exit',
'extends',
'final',
'finally',
'fn',
'for',
'foreach',
'function',
'global',
'goto',
'if',
'implements',
'include',
'include_once',
'instanceof',
'insteadof',
'interface',
'isset',
'list',
'namespace',
'new',
'or',
'print',
'private',
'protected',
'public',
'require',
'require_once',
'return',
'static',
'switch',
'throw',
'trait',
'try',
'unset',
'use',
'var',
'while',
'xor',
'yield',
];
}

} }
@@ -34,6 +34,11 @@ public function handle()
$stubsPath.'/livewire.view.stub', $stubsPath.'/livewire.view.stub',
file_get_contents(__DIR__.'/livewire.view.stub') file_get_contents(__DIR__.'/livewire.view.stub')
); );

file_put_contents(
$stubsPath.'/livewire.test.stub',
file_get_contents(__DIR__.'/livewire.test.stub')
);


$this->info('Stubs published successfully.'); $this->info('Stubs published successfully.');
} }
@@ -14,8 +14,8 @@
'Knowing others is intelligence; knowing yourself is true wisdom.', 'Knowing others is intelligence; knowing yourself is true wisdom.',
'If your happiness depends on money, you will never be happy with yourself.', 'If your happiness depends on money, you will never be happy with yourself.',
'If you look to others for fulfillment, you will never truly be fulfilled.', 'If you look to others for fulfillment, you will never truly be fulfilled.',
'To attain knowledge, add things every day; To attain wisdom, subtract things every day', 'To attain knowledge, add things every day; To attain wisdom, subtract things every day.',
'Close your eyes. Count to one. That is how long forever feels.', 'Close your eyes. Count to one. That is how long forever feels.',
'The whole world belongs to you', 'The whole world belongs to you.',
'Stop trying to control.', 'Stop trying to control.',
]; ];
@@ -129,7 +129,7 @@ public function callMethod($method, $params = [])
} else { } else {
$currentValue = $this->{$prop}; $currentValue = $this->{$prop};
} }

$this->syncInput($prop, ! $currentValue, $rehash = false); $this->syncInput($prop, ! $currentValue, $rehash = false);


return; return;
@@ -0,0 +1,13 @@
<?php

namespace Livewire\Exceptions;

class ComponentAttributeMissingOnDynamicComponentException extends \Exception
{
use BypassViewHandler;

public function __construct()
{
parent::__construct('Dynamic component tag is missing component attribute.');
}
}
@@ -0,0 +1,15 @@
<?php

namespace Livewire\Exceptions;

class DirectlyCallingLifecycleHooksNotAllowedException extends \Exception
{
use BypassViewHandler;

public function __construct($method, $component)
{
parent::__construct(
"Unable to call lifecycle method [{$method}] directly on component: [{$component}]"
);
}
}

No commit comments for this range