diff --git a/tests/e2e/integration.spec.js b/tests/e2e/integration.spec.js index 710c46b..b86e38a 100644 --- a/tests/e2e/integration.spec.js +++ b/tests/e2e/integration.spec.js @@ -4,6 +4,7 @@ const fs = require('fs'); const path = require('path'); const crypto = require('crypto'); const zlib = require('zlib'); +const JSZip = require('jszip'); // Expected SHA1 checksums for Kobo Libra Color, firmware 4.45.23646, // with only "Remove footer (row3) on new home screen" enabled. @@ -296,6 +297,9 @@ test.describe('NickelMenu', () => { await expect(page.locator('input[name="nm-cfg-simplify-tabs"]')).not.toBeChecked(); await expect(page.locator('input[name="nm-cfg-simplify-home"]')).not.toBeChecked(); + // Enable simplifyHome for testing + await page.check('input[name="nm-cfg-simplify-home"]'); + await expect(page.locator('#btn-nm-next')).toBeEnabled(); await page.click('#btn-nm-next'); @@ -310,13 +314,36 @@ test.describe('NickelMenu', () => { await expect(page.locator('#btn-nm-download')).toBeVisible(); // Click download and wait for done step - await page.click('#btn-nm-download'); + const [download] = await Promise.all([ + page.waitForEvent('download'), + page.click('#btn-nm-download'), + ]); await expect(page.locator('#step-nm-done')).toBeVisible({ timeout: 30_000 }); await expect(page.locator('#nm-done-status')).toContainText('ready to download'); // Download instructions should be visible, and include eReader.conf step for sample config await expect(page.locator('#nm-download-instructions')).not.toBeHidden(); await expect(page.locator('#nm-download-conf-step')).not.toBeHidden(); + + // Verify ZIP contents + expect(download.suggestedFilename()).toBe('NickelMenu-install.zip'); + const zipData = fs.readFileSync(await download.path()); + const zip = await JSZip.loadAsync(zipData); + const zipFiles = Object.keys(zip.files); + + // Must contain KoboRoot.tgz + expect(zipFiles).toContainEqual('.kobo/KoboRoot.tgz'); + // Must contain NickelMenu items config + expect(zipFiles).toContainEqual('.adds/nm/items'); + // Must contain font files (fonts checkbox is checked by default) + expect(zipFiles.some(f => f.startsWith('fonts/'))).toBe(true); + // Must NOT contain screensaver (unchecked by default) + expect(zipFiles.some(f => f.startsWith('.kobo/screensaver/'))).toBe(false); + + // Verify items file has simplifyHome modifications + const itemsContent = await zip.file('.adds/nm/items').async('string'); + expect(itemsContent).toContain('experimental:hide_home_row1col2_enabled:1'); + expect(itemsContent).toContain('experimental:hide_home_row3_enabled:1'); }); test('no device — install NickelMenu only via manual download', async ({ page }) => { @@ -337,12 +364,23 @@ test.describe('NickelMenu', () => { await expect(page.locator('#nm-review-list')).toContainText('NickelMenu (KoboRoot.tgz)'); // Download - await page.click('#btn-nm-download'); + const [download] = await Promise.all([ + page.waitForEvent('download'), + page.click('#btn-nm-download'), + ]); await expect(page.locator('#step-nm-done')).toBeVisible({ timeout: 30_000 }); await expect(page.locator('#nm-done-status')).toContainText('ready to download'); // eReader.conf step should be hidden for nickelmenu-only await expect(page.locator('#nm-download-conf-step')).toBeHidden(); + + // Verify ZIP contents — should only contain KoboRoot.tgz + expect(download.suggestedFilename()).toBe('NickelMenu-install.zip'); + const zipData = fs.readFileSync(await download.path()); + const zip = await JSZip.loadAsync(zipData); + const zipFiles = Object.keys(zip.files).filter(f => !zip.files[f].dir); + + expect(zipFiles).toEqual(['.kobo/KoboRoot.tgz']); }); test('no device — remove option is disabled in manual mode', async ({ page }) => { @@ -421,6 +459,40 @@ test.describe('NickelMenu', () => { expect(items).toContain('experimental:hide_home_row3_enabled:1'); }); + test('with device — install NickelMenu only and write to Kobo', async ({ page }) => { + test.skip(!hasNickelMenuAssets(), 'NickelMenu assets not found in webroot'); + + await connectMockDevice(page, { hasNickelMenu: false }); + + // Continue to mode selection + await page.click('#btn-device-next'); + await page.click('#btn-mode-next'); + + // NickelMenu configure step + await expect(page.locator('#step-nickelmenu')).not.toBeHidden(); + + // Select "Install NickelMenu only" + await page.click('input[name="nm-option"][value="nickelmenu-only"]'); + await expect(page.locator('#nm-config-options')).toBeHidden(); + + await page.click('#btn-nm-next'); + + // Review step + await expect(page.locator('#step-nm-review')).not.toBeHidden(); + await expect(page.locator('#nm-review-list')).toContainText('NickelMenu (KoboRoot.tgz)'); + + // Write to device + await page.click('#btn-nm-write'); + await expect(page.locator('#step-nm-done')).toBeVisible({ timeout: 30_000 }); + await expect(page.locator('#nm-done-status')).toContainText('installed'); + + // Verify only KoboRoot.tgz was written (no config files) + const writtenFiles = await getWrittenFiles(page); + expect(writtenFiles).toContainEqual(expect.stringContaining('KoboRoot.tgz')); + // Should NOT have written items, fonts, etc. + expect(writtenFiles.filter(f => !f.includes('KoboRoot.tgz'))).toHaveLength(0); + }); + test('with device — remove NickelMenu', async ({ page }) => { test.skip(!hasNickelMenuAssets(), 'NickelMenu assets not found in webroot'); diff --git a/tests/e2e/package-lock.json b/tests/e2e/package-lock.json index 8120100..1cb6b9d 100644 --- a/tests/e2e/package-lock.json +++ b/tests/e2e/package-lock.json @@ -6,7 +6,8 @@ "": { "name": "kobopatch-webui-e2e", "devDependencies": { - "@playwright/test": "^1.50.0" + "@playwright/test": "^1.50.0", + "jszip": "^3.10.1" } }, "node_modules/@playwright/test": { @@ -25,6 +26,13 @@ "node": ">=18" } }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT" + }, "node_modules/fsevents": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", @@ -40,6 +48,57 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "dev": true, + "license": "(MIT OR GPL-3.0-or-later)", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true, + "license": "(MIT AND Zlib)" + }, "node_modules/playwright": { "version": "1.58.2", "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz", @@ -71,6 +130,60 @@ "engines": { "node": ">=18" } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "dev": true, + "license": "MIT" + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" } } } diff --git a/tests/e2e/package.json b/tests/e2e/package.json index 76f0f7d..c306ee1 100644 --- a/tests/e2e/package.json +++ b/tests/e2e/package.json @@ -5,6 +5,7 @@ "test": "npx playwright test" }, "devDependencies": { - "@playwright/test": "^1.50.0" + "@playwright/test": "^1.50.0", + "jszip": "^3.10.1" } }