Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion .github/workflows/php.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ jobs:
- name: Checkout
uses: actions/checkout@v2

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'

- name: Run JavaScript tests
run: npm run test:js

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
Expand All @@ -35,4 +43,4 @@ jobs:
composer update --prefer-dist --no-interaction --no-suggest

- name: Run test suite
run: composer run-script test
run: composer run-script test
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"watch-poll": "mix watch -- --watch-options-poll=1000",
"hot": "mix watch --hot",
"prod": "npm run production",
"production": "mix --production"
"production": "mix --production",
"test:js": "node --test tests/js/*.test.cjs"
},
"devDependencies": {
"autoprefixer": "^10.2.5",
Expand Down
16 changes: 13 additions & 3 deletions resources/js/modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ window.LivewireUIModal = () => {
componentHistory: [],
modalWidth: null ,
listeners: [],
resetStateTimeout: null,
getActiveComponentModalAttribute(key) {
if (this.$wire.get('components')[this.activeComponent] !== undefined) {
return this.$wire.get('components')[this.activeComponent]['modalAttributes'][key];
Expand Down Expand Up @@ -148,13 +149,22 @@ window.LivewireUIModal = () => {
this.show = show;

if (show) {
if (this.resetStateTimeout !== null) {
clearTimeout(this.resetStateTimeout);
this.resetStateTimeout = null;
}

document.body.classList.add('overflow-y-hidden');
} else {
document.body.classList.remove('overflow-y-hidden');

setTimeout(() => {
this.activeComponent = false;
this.$wire.resetState();
this.resetStateTimeout = setTimeout(() => {
this.resetStateTimeout = null;

if (this.show === false) {
this.activeComponent = false;
this.$wire.resetState();
}
}, 300);
}
},
Expand Down
66 changes: 66 additions & 0 deletions tests/js/modal.test.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const vm = require('node:vm');
const test = require('node:test');

const modalSource = fs.readFileSync(
path.resolve(__dirname, '../../resources/js/modal.js'),
'utf8'
);

function createModal() {
let nextTimerId = 0;
const timers = new Map();
const cancelledTimers = new Set();
const context = {
window: {},
document: { body: { classList: { add() {}, remove() {} } } },
setTimeout(callback, delay) {
const id = ++nextTimerId;
timers.set(id, { callback, delay });
return id;
},
clearTimeout(id) {
cancelledTimers.add(id);
},
};

vm.runInNewContext(modalSource, context, { timeout: 1000 });

return {
modal: context.window.LivewireUIModal(),
timers,
cancelledTimers,
};
}

test('closing an old modal does not reset a modal opened before cleanup finishes', () => {
const { modal, timers, cancelledTimers } = createModal();
let resetCount = 0;
modal.$wire = { resetState() { resetCount++; } };

modal.setShowPropertyTo(false);
const closeTimerId = modal.resetStateTimeout;
modal.setShowPropertyTo(true);
modal.activeComponent = 'new-modal';

assert.equal(timers.get(closeTimerId).delay, 300);
assert.equal(cancelledTimers.has(closeTimerId), true);
assert.equal(resetCount, 0);
assert.equal(modal.activeComponent, 'new-modal');
});

test('a closed modal still resets after the close cleanup delay', () => {
const { modal, timers } = createModal();
let resetCount = 0;
modal.$wire = { resetState() { resetCount++; } };

modal.setShowPropertyTo(false);
const closeTimerId = modal.resetStateTimeout;
timers.get(closeTimerId).callback();

assert.equal(resetCount, 1);
assert.equal(modal.activeComponent, false);
assert.equal(modal.resetStateTimeout, null);
});