diff --git a/js/views/device.js b/js/views/device.js
index befd7a5..0e714ae 100644
--- a/js/views/device.js
+++ b/js/views/device.js
@@ -14,7 +14,7 @@ function configBasename(url) {
return (url.split('/').pop() || 'config.yaml').replace(/[^\w.\-]/g, '_');
}
function downloadText(text, filename) {
- const blob = new Blob([text], { type: 'text/yaml' });
+ const blob = new Blob([text], { type: 'application/yaml' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url; a.download = filename;
@@ -66,10 +66,10 @@ export function renderDevice(el, device) {
${segHtml('channel-seg', 'Channel', channels, channel, 'channel')}
+ ${channels.length < 2 && Object.keys(device.firmware[channel]).length < 2
+ ? 'One firmware for this device — nothing to choose here.
' : ''}
- ${channels.length < 2 && Object.keys(device.firmware[channel]).length < 2
- ? 'One firmware for this device — nothing to choose here.
' : ''}
diff --git a/tests/installer.spec.js b/tests/installer.spec.js
index f15d4b2..b182e3a 100644
--- a/tests/installer.spec.js
+++ b/tests/installer.spec.js
@@ -194,7 +194,9 @@ test('step 3 explains taking control in the ESPHome Dashboard', async ({ page })
function blobFromRaw(raw) {
const m = raw.match(/^https:\/\/raw\.githubusercontent\.com\/([^/]+)\/([^/]+)\/([^/]+)\/(.+)$/);
- return `https://github.com/${m[1]}/${m[2]}/blob/${m[3]}/${m[4]}`;
+ // Mirror the app's rawToBlob fallback: on a non-match, return the raw URL rather
+ // than throwing on m[1], so a future off-pattern URL fails as a clean assertion.
+ return m ? `https://github.com/${m[1]}/${m[2]}/blob/${m[3]}/${m[4]}` : raw;
}
function defaultSel(d) {
const channels = Object.keys(d.firmware);
@@ -265,3 +267,21 @@ test('device config: no section when the device has no config', async ({ page })
await page.goto(`/#/${d.id}`);
await expect(page.locator('.config')).toHaveCount(0);
});
+
+test('single-firmware devices show the "nothing to choose" note above the release/config sections', async ({ page }) => {
+ const d = registry.devices.find((x) => {
+ const chans = Object.keys(x.firmware);
+ return chans.length === 1 && Object.keys(x.firmware[chans[0]]).length === 1;
+ });
+ test.skip(!d, 'no single-firmware device in registry');
+ await page.goto(`/#/${d.id}`);
+ const step = page.locator('.device-page .step').first();
+ await expect(step.getByText('nothing to choose here')).toBeVisible();
+ // The note must render up under the picker, above the "What's new" and reflash sections.
+ const noteBeforeRelease = await step.evaluate((el) => {
+ const note = [...el.querySelectorAll('p')].find((p) => p.textContent.includes('nothing to choose'));
+ const rel = el.querySelector('#release-slot');
+ return !!(note && rel && (note.compareDocumentPosition(rel) & Node.DOCUMENT_POSITION_FOLLOWING));
+ });
+ expect(noteBeforeRelease).toBe(true);
+});