1
0

More integration steps, fix bug related to only NM install
All checks were successful
Build and test project / build-and-test (push) Successful in 1m28s

This commit is contained in:
2026-03-19 19:23:23 +01:00
parent 57f3811932
commit 48ea2c183b
3 changed files with 125 additions and 18 deletions

View File

@@ -903,9 +903,9 @@ test.describe('Custom patches', () => {
await expect(page.locator('#error-message')).toContainText('Build failed'); await expect(page.locator('#error-message')).toContainText('Build failed');
await expect(page.locator('#btn-error-back')).toBeVisible(); await expect(page.locator('#btn-error-back')).toBeVisible();
// Go Back should return to patches step // "Select different patches" should return to mode selection (auto mode)
await page.click('#btn-error-back'); await page.click('#btn-error-back');
await expect(page.locator('#step-patches')).not.toBeHidden(); await expect(page.locator('#step-mode')).not.toBeHidden();
}); });
test('with device — real patch failure with Go Back (Allow rotation)', async ({ page }) => { test('with device — real patch failure with Go Back (Allow rotation)', async ({ page }) => {
@@ -936,11 +936,11 @@ test.describe('Custom patches', () => {
]); ]);
if (doneOrError === 'error') { if (doneOrError === 'error') {
// Build failed — verify Go Back works // Build failed — "Select different patches" should return to mode selection
await expect(page.locator('#error-message')).toContainText('Build failed'); await expect(page.locator('#error-message')).toContainText('Build failed');
await expect(page.locator('#btn-error-back')).toBeVisible(); await expect(page.locator('#btn-error-back')).toBeVisible();
await page.click('#btn-error-back'); await page.click('#btn-error-back');
await expect(page.locator('#step-patches')).not.toBeHidden(); await expect(page.locator('#step-mode')).not.toBeHidden();
} else { } else {
// Build succeeded — check if the patch was skipped // Build succeeded — check if the patch was skipped
const logText = await page.locator('#build-log').textContent(); const logText = await page.locator('#build-log').textContent();
@@ -949,4 +949,105 @@ test.describe('Custom patches', () => {
expect(hasSkip, 'Expected "Allow rotation" to be skipped or fail').toBe(true); expect(hasSkip, 'Expected "Allow rotation" to be skipped or fail').toBe(true);
} }
}); });
test('with device — back navigation through auto mode flow', async ({ page }) => {
await page.goto('/');
await injectMockDevice(page);
await page.click('#btn-connect');
// Step 1: Device
await expect(page.locator('#step-device')).not.toBeHidden();
// Device → Mode
await page.click('#btn-device-next');
await expect(page.locator('#step-mode')).not.toBeHidden();
// Mode → Patches
await page.click('input[name="mode"][value="patches"]');
await page.click('#btn-mode-next');
await expect(page.locator('#step-patches')).not.toBeHidden();
// Patches → Back → Mode
await page.click('#btn-patches-back');
await expect(page.locator('#step-mode')).not.toBeHidden();
// Mode → NickelMenu config
await page.click('input[name="mode"][value="nickelmenu"]');
await page.click('#btn-mode-next');
await expect(page.locator('#step-nickelmenu')).not.toBeHidden();
// NM config → Back → Mode
await page.click('#btn-nm-back');
await expect(page.locator('#step-mode')).not.toBeHidden();
// Mode → NM config → Continue → NM review
await page.click('input[name="mode"][value="nickelmenu"]');
await page.click('#btn-mode-next');
await expect(page.locator('#step-nickelmenu')).not.toBeHidden();
await page.click('input[value="nickelmenu-only"]');
await page.click('#btn-nm-next');
await expect(page.locator('#step-nm-review')).not.toBeHidden();
// NM review → Back → NM config
await page.click('#btn-nm-review-back');
await expect(page.locator('#step-nickelmenu')).not.toBeHidden();
// NM config → Back → Mode
await page.click('#btn-nm-back');
await expect(page.locator('#step-mode')).not.toBeHidden();
// Mode → Back → Device
await page.click('#btn-mode-back');
await expect(page.locator('#step-device')).not.toBeHidden();
});
test('no device — back navigation through manual mode flow', async ({ page }) => {
await page.goto('/');
await goToManualMode(page);
// Step 1: Mode
await expect(page.locator('#step-mode')).not.toBeHidden();
// Mode → Patches → Version selection
await page.click('input[name="mode"][value="patches"]');
await page.click('#btn-mode-next');
await expect(page.locator('#step-manual-version')).not.toBeHidden();
// Version → Back → Mode
await page.click('#btn-manual-version-back');
await expect(page.locator('#step-mode')).not.toBeHidden();
// Mode → NickelMenu config
await page.click('input[name="mode"][value="nickelmenu"]');
await page.click('#btn-mode-next');
await expect(page.locator('#step-nickelmenu')).not.toBeHidden();
// NM config → Back → Mode
await page.click('#btn-nm-back');
await expect(page.locator('#step-mode')).not.toBeHidden();
// Mode → Patches → Version selection
await page.click('input[name="mode"][value="patches"]');
await page.click('#btn-mode-next');
await expect(page.locator('#step-manual-version')).not.toBeHidden();
// Select version and model, confirm
await page.selectOption('#manual-version', '4.45.23646');
await page.locator('#manual-model').waitFor({ state: 'visible' });
await page.selectOption('#manual-model', 'N428');
await page.click('#btn-manual-confirm');
await expect(page.locator('#step-patches')).not.toBeHidden();
// Patches → Back → Version
await page.click('#btn-patches-back');
await expect(page.locator('#step-manual-version')).not.toBeHidden();
// Version → Back → Mode
await page.click('#btn-manual-version-back');
await expect(page.locator('#step-mode')).not.toBeHidden();
// Mode → Back → Connect
await page.click('#btn-mode-back');
await expect(page.locator('#step-connect')).not.toBeHidden();
});
}); });

View File

@@ -584,7 +584,7 @@ import JSZip from 'jszip';
try { try {
if (nickelMenuOption === 'remove') { if (nickelMenuOption === 'remove') {
await nmInstaller.loadAssets((msg) => { nmProgress.textContent = msg; }); await nmInstaller.loadAssets((msg) => { nmProgress.textContent = msg; }, false);
nmProgress.textContent = 'Writing KoboRoot.tgz...'; nmProgress.textContent = 'Writing KoboRoot.tgz...';
const tgz = await nmInstaller.getKoboRootTgz(); const tgz = await nmInstaller.getKoboRootTgz();
await device.writeFile(['.kobo', 'KoboRoot.tgz'], tgz); await device.writeFile(['.kobo', 'KoboRoot.tgz'], tgz);

View File

@@ -25,20 +25,24 @@ class NickelMenuInstaller {
/** /**
* Download and cache the bundled assets. * Download and cache the bundled assets.
* @param {function} progressFn
* @param {boolean} [needConfig=true] - Whether to also load kobo-config.zip
*/ */
async loadAssets(progressFn) { async loadAssets(progressFn, needConfig = true) {
if (this.nickelMenuZip && this.koboConfigZip) return; if (!this.nickelMenuZip) {
progressFn('Downloading NickelMenu...'); progressFn('Downloading NickelMenu...');
const nmResp = await fetch('nickelmenu/NickelMenu.zip'); const nmResp = await fetch('nickelmenu/NickelMenu.zip');
if (!nmResp.ok) throw new Error('Failed to download NickelMenu.zip: HTTP ' + nmResp.status); if (!nmResp.ok) throw new Error('Failed to download NickelMenu.zip: HTTP ' + nmResp.status);
this.nickelMenuZip = await JSZip.loadAsync(await nmResp.arrayBuffer()); this.nickelMenuZip = await JSZip.loadAsync(await nmResp.arrayBuffer());
}
if (needConfig && !this.koboConfigZip) {
progressFn('Downloading configuration files...'); progressFn('Downloading configuration files...');
const cfgResp = await fetch('nickelmenu/kobo-config.zip'); const cfgResp = await fetch('nickelmenu/kobo-config.zip');
if (!cfgResp.ok) throw new Error('Failed to download kobo-config.zip: HTTP ' + cfgResp.status); if (!cfgResp.ok) throw new Error('Failed to download kobo-config.zip: HTTP ' + cfgResp.status);
this.koboConfigZip = await JSZip.loadAsync(await cfgResp.arrayBuffer()); this.koboConfigZip = await JSZip.loadAsync(await cfgResp.arrayBuffer());
} }
}
/** /**
* Get the KoboRoot.tgz from the NickelMenu zip. * Get the KoboRoot.tgz from the NickelMenu zip.
@@ -111,7 +115,8 @@ class NickelMenuInstaller {
* @param {function} progressFn * @param {function} progressFn
*/ */
async installToDevice(device, option, cfg, progressFn) { async installToDevice(device, option, cfg, progressFn) {
await this.loadAssets(progressFn); const needConfig = option !== 'nickelmenu-only';
await this.loadAssets(progressFn, needConfig);
// Always install KoboRoot.tgz // Always install KoboRoot.tgz
progressFn('Writing KoboRoot.tgz...'); progressFn('Writing KoboRoot.tgz...');
@@ -172,7 +177,8 @@ class NickelMenuInstaller {
* @returns {Uint8Array} zip contents * @returns {Uint8Array} zip contents
*/ */
async buildDownloadZip(option, cfg, progressFn) { async buildDownloadZip(option, cfg, progressFn) {
await this.loadAssets(progressFn); const needConfig = option !== 'nickelmenu-only';
await this.loadAssets(progressFn, needConfig);
progressFn('Building download package...'); progressFn('Building download package...');
const zip = new JSZip(); const zip = new JSZip();