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
|
||||
|
||||
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">
|
||||
<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'"
|
||||
:disabled="a.uit" :style="a.uit ? 'opacity:.4' : ''"
|
||||
@click="stackMenu = ''; a.doe()">
|
||||
:disabled="a.uit === true" :style="a.uit ? 'opacity:.4' : ''"
|
||||
@click="a.doe(); stackMenu = ''">
|
||||
<span class="mdi text-base w-5 text-center" :class="a.icon"></span>
|
||||
<span x-text="a.label"></span>
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
// Rendert de echte install-modal met Alpine in een DOM en klikt erop.
|
||||
// Vangt bindingsfouten die je met losse logica niet ziet: :disabled="0"
|
||||
// schakelt een knop juist UIT, want Alpine haalt een boolean-attribuut
|
||||
// alleen weg bij null, undefined of false.
|
||||
// Rendert de echte installatiemodal en appkaart met Alpine in een DOM en klikt
|
||||
// de bedieningen daadwerkelijk aan. Vangt bindingsfouten die je met losse
|
||||
// logica niet ziet: een ontbrekende eigenschap in een x-for-item liet hier het
|
||||
// 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');
|
||||
|
||||
// jsdom en Alpine zijn geen onderdeel van dit project; zonder die twee slaat
|
||||
|
|
@ -38,6 +39,11 @@ w.app = function () {
|
|||
c.pickIp = () => {};
|
||||
c.netList = [];
|
||||
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.install = {
|
||||
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/);
|
||||
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'));
|
||||
|
||||
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);
|
||||
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`);
|
||||
process.exit(fouten ? 1 : 0);
|
||||
})();
|
||||
|
|
|
|||
Loading…
Reference in a new issue