fix(ui): herstel mobiele appacties
All checks were successful
Deploy server-up (dev) / deploy (push) Successful in 4m39s
All checks were successful
Deploy server-up (dev) / deploy (push) Successful in 4m39s
This commit is contained in:
parent
8f62df90d8
commit
da9981378b
4 changed files with 86 additions and 8 deletions
13
CHANGELOG.md
13
CHANGELOG.md
|
|
@ -1,3 +1,16 @@
|
||||||
|
# v0.8.19-beta — Mobiele appacties reageren weer
|
||||||
|
|
||||||
|
In het mobiele actiemenu kregen normale acties zonder expliciete
|
||||||
|
uitschakelstatus toch het HTML-attribuut `disabled`. Daardoor deed onder meer
|
||||||
|
de knop Verwijderen zichtbaar helemaal niets.
|
||||||
|
|
||||||
|
- **Verwijderen is weer klikbaar.** Alleen acties die expliciet als
|
||||||
|
uitgeschakeld zijn gemarkeerd krijgen nog het `disabled`-attribuut.
|
||||||
|
- **De actie loopt vóór het menu sluit.** Zo blijft de Alpine-scope beschikbaar
|
||||||
|
totdat de verwijdermodal daadwerkelijk is geopend.
|
||||||
|
- **Echte DOM-regressietest.** De test rendert de appkaart met Alpine en klikt
|
||||||
|
zowel de desktop- als mobiele verwijderroute volledig door.
|
||||||
|
|
||||||
# v0.8.18-beta — UniFi opent via HTTPS
|
# v0.8.18-beta — UniFi opent via HTTPS
|
||||||
|
|
||||||
De UniFi-container was wel correct op zijn HTTPS-poort gepubliceerd, maar
|
De UniFi-container was wel correct op zijn HTTPS-poort gepubliceerd, maar
|
||||||
|
|
|
||||||
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
||||||
0.8.18-beta
|
0.8.19-beta
|
||||||
|
|
|
||||||
|
|
@ -426,8 +426,8 @@ tailwind.config = {
|
||||||
<template x-for="a in stackMenuItems(s)" :key="a.id">
|
<template x-for="a in stackMenuItems(s)" :key="a.id">
|
||||||
<button class="w-full flex items-center gap-2.5 px-2.5 py-2 rounded-lg text-xs text-left transition"
|
<button class="w-full flex items-center gap-2.5 px-2.5 py-2 rounded-lg text-xs text-left transition"
|
||||||
:class="a.gevaar ? 'text-er hover:bg-er/10' : 'text-tx dark:text-tx-dark hover:bg-s2 dark:hover:bg-s2-dark'"
|
:class="a.gevaar ? 'text-er hover:bg-er/10' : 'text-tx dark:text-tx-dark hover:bg-s2 dark:hover:bg-s2-dark'"
|
||||||
:disabled="a.uit" :style="a.uit ? 'opacity:.4' : ''"
|
:disabled="a.uit === true" :style="a.uit ? 'opacity:.4' : ''"
|
||||||
@click="stackMenu = ''; a.doe()">
|
@click="a.doe(); stackMenu = ''">
|
||||||
<span class="mdi text-base w-5 text-center" :class="a.icon"></span>
|
<span class="mdi text-base w-5 text-center" :class="a.icon"></span>
|
||||||
<span x-text="a.label"></span>
|
<span x-text="a.label"></span>
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
// Rendert de echte install-modal met Alpine in een DOM en klikt erop.
|
// Rendert de echte installatiemodal en appkaart met Alpine in een DOM en klikt
|
||||||
// Vangt bindingsfouten die je met losse logica niet ziet: :disabled="0"
|
// de bedieningen daadwerkelijk aan. Vangt bindingsfouten die je met losse
|
||||||
// schakelt een knop juist UIT, want Alpine haalt een boolean-attribuut
|
// logica niet ziet: een ontbrekende eigenschap in een x-for-item liet hier het
|
||||||
// alleen weg bij null, undefined of false.
|
// boolean-attribuut disabled staan. Alleen de gerenderde DOM laat vervolgens
|
||||||
|
// zien dat de knop geen klikevent meer ontvangt.
|
||||||
const fs = require('fs'), path = require('path');
|
const fs = require('fs'), path = require('path');
|
||||||
|
|
||||||
// jsdom en Alpine zijn geen onderdeel van dit project; zonder die twee slaat
|
// jsdom en Alpine zijn geen onderdeel van dit project; zonder die twee slaat
|
||||||
|
|
@ -38,6 +39,11 @@ w.app = function () {
|
||||||
c.pickIp = () => {};
|
c.pickIp = () => {};
|
||||||
c.netList = [];
|
c.netList = [];
|
||||||
c.sharedNetwork = 'serverup';
|
c.sharedNetwork = 'serverup';
|
||||||
|
c.page = 'stacks';
|
||||||
|
c.stacks = [{
|
||||||
|
name: 'mijn-app', has_compose: true, running: true, containers: [],
|
||||||
|
categories: [], depends_missing: [], editable: true, connected: false
|
||||||
|
}];
|
||||||
c.modal = 'install';
|
c.modal = 'install';
|
||||||
c.install = {
|
c.install = {
|
||||||
format: 'boilerplate', loading: false, stack: 'test', repo_id: 'r', instance: 'test',
|
format: 'boilerplate', loading: false, stack: 'test', repo_id: 'r', instance: 'test',
|
||||||
|
|
@ -55,7 +61,9 @@ w.app = function () {
|
||||||
};
|
};
|
||||||
|
|
||||||
const m = html.match(/<div x-show="modal==='install'"[\s\S]*?\n <\/div>\n/);
|
const m = html.match(/<div x-show="modal==='install'"[\s\S]*?\n <\/div>\n/);
|
||||||
w.document.body.innerHTML = `<div x-data="app()">${m[0]}</div>`;
|
const rm = html.match(/<!-- Remove confirm -->[\s\S]*?<!-- Editor -->/);
|
||||||
|
const stacks = html.match(/<section x-show="page === 'stacks'"[\s\S]*?<\/section>/);
|
||||||
|
w.document.body.innerHTML = `<div x-data="app()">${stacks[0]}${m[0]}${rm[0]}</div>`;
|
||||||
w.eval(fs.readFileSync(alpinePad, 'utf8'));
|
w.eval(fs.readFileSync(alpinePad, 'utf8'));
|
||||||
|
|
||||||
const wacht = ms => new Promise(r => setTimeout(r, ms));
|
const wacht = ms => new Promise(r => setTimeout(r, ms));
|
||||||
|
|
@ -96,6 +104,63 @@ const wacht = ms => new Promise(r => setTimeout(r, ms));
|
||||||
await wacht(80);
|
await wacht(80);
|
||||||
check('knop weer bruikbaar na invullen', volgende.disabled === false);
|
check('knop weer bruikbaar na invullen', volgende.disabled === false);
|
||||||
|
|
||||||
|
// De verwijderknop testen we via de echte Alpine @click-binding. De losse
|
||||||
|
// menu-test roept doRemove() direct aan en mist fouten in de DOM.
|
||||||
|
let verwijderVerzoek = null;
|
||||||
|
let verwijderd = false;
|
||||||
|
comp.rpc = async (url, body) => {
|
||||||
|
verwijderVerzoek = {url, body};
|
||||||
|
return {job_id: 'remove-1'};
|
||||||
|
};
|
||||||
|
comp.pollJob = (id, klaar) => klaar('done');
|
||||||
|
comp.loadStacks = async () => { verwijderd = true; };
|
||||||
|
comp.toast = () => {};
|
||||||
|
const kaartknop = [...w.document.querySelectorAll('button')].find(
|
||||||
|
b => b.getAttribute('@click') === 'confirmRemove(s.name)');
|
||||||
|
check('verwijderknop op de appkaart is gerenderd', !!kaartknop);
|
||||||
|
if (!kaartknop) {
|
||||||
|
console.log('stacks:', JSON.stringify(comp.stacks));
|
||||||
|
console.log('filtered:', JSON.stringify(comp.filteredStacks));
|
||||||
|
console.log('click-bindings:', [...w.document.querySelectorAll('button')]
|
||||||
|
.map(b => b.getAttribute('@click')).filter(Boolean).join(' | '));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
kaartknop.click();
|
||||||
|
await wacht(60);
|
||||||
|
const verwijderModal = [...w.document.querySelectorAll('.modal')].find(
|
||||||
|
e => e.getAttribute('x-show') === "modal==='remove'");
|
||||||
|
check('verwijdermodal opent met de juiste app',
|
||||||
|
comp.modal === 'remove' && comp.removeTarget === 'mijn-app');
|
||||||
|
const veilige = [...verwijderModal.querySelectorAll('button')].find(
|
||||||
|
b => b.getAttribute('@click') === "doRemove('full')");
|
||||||
|
check('verwijderknop is actief', veilige && !veilige.disabled);
|
||||||
|
veilige.click();
|
||||||
|
await wacht(80);
|
||||||
|
check('klik verstuurt de verwijderaanvraag', verwijderVerzoek &&
|
||||||
|
verwijderVerzoek.url === '/api/stacks/mijn-app/remove' &&
|
||||||
|
verwijderVerzoek.body.mode === 'full');
|
||||||
|
check('voltooide verwijdering herlaadt de apps', verwijderd);
|
||||||
|
|
||||||
|
// Op een telefoon loopt verwijderen via een functie in het x-for-actiemenu.
|
||||||
|
comp.modal = null;
|
||||||
|
comp.removeTarget = '';
|
||||||
|
const menuActies = [...w.document.querySelectorAll('button')].filter(
|
||||||
|
b => b.getAttribute('@click') === "a.doe(); stackMenu = ''");
|
||||||
|
comp.stackMenu = 'mijn-app';
|
||||||
|
await wacht(40);
|
||||||
|
const mobielVerwijderen = menuActies.find(
|
||||||
|
b => (w.Alpine.$data(b).a || {}).id === 'remove');
|
||||||
|
check('mobiele verwijderactie is gerenderd', !!mobielVerwijderen);
|
||||||
|
if (!mobielVerwijderen) process.exit(1);
|
||||||
|
check('mobiele verwijderactie is actief',
|
||||||
|
mobielVerwijderen && !mobielVerwijderen.disabled);
|
||||||
|
mobielVerwijderen.click();
|
||||||
|
await wacht(60);
|
||||||
|
if (comp.modal !== 'remove')
|
||||||
|
console.log('mobiele status:', comp.modal, comp.removeTarget, comp.stackMenu);
|
||||||
|
check('mobiele verwijderactie opent de modal',
|
||||||
|
comp.modal === 'remove' && comp.removeTarget === 'mijn-app');
|
||||||
|
|
||||||
console.log(fouten === 0 ? '\nAlle DOM-controles geslaagd' : `\n${fouten} FOUTEN`);
|
console.log(fouten === 0 ? '\nAlle DOM-controles geslaagd' : `\n${fouten} FOUTEN`);
|
||||||
process.exit(fouten ? 1 : 0);
|
process.exit(fouten ? 1 : 0);
|
||||||
})();
|
})();
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue