aboutsummaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-09-28 21:27:32 -0700
committerAdam Soltys <[email protected]>2026-09-28 21:27:32 -0700
commit292d5527cf36c119d74486061db3a44a608f5138 (patch)
tree303da1796aad613a43798a6784bcb69c5c0796cb /tools
parent356920e8aa7d27dd5e7eff070246ae9ed41498c7 (diff)
downloadcoinosv3-292d5527cf36c119d74486061db3a44a608f5138.tar.gz
coinosv3-292d5527cf36c119d74486061db3a44a608f5138.zip
The bottom nav rides every page: Feed, Messages, Notifications, Wallet.
It now shows across the signed-in app and a visitor's public pages (never over sign-in, onboarding or the lock prompt), marks the current page, and carries the unread dot. Home is renamed Feed, and the Messages button leaves the header. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'tools')
-rw-r--r--tools/feed-bottom-nav-test.js62
-rw-r--r--tools/feed-wallet-back-test.js11
2 files changed, 40 insertions, 33 deletions
diff --git a/tools/feed-bottom-nav-test.js b/tools/feed-bottom-nav-test.js
index 53b7529..f16134c 100644
--- a/tools/feed-bottom-nav-test.js
+++ b/tools/feed-bottom-nav-test.js
@@ -1,5 +1,5 @@
// Real feed/navigation views with local posts and no relay traffic.
-// Run: bun tools/feed-bottom-nav-test.js
+// Run: bun tools/app-bottom-nav-test.js
import assert from 'node:assert/strict';
import puppeteer from 'puppeteer-core';
const app = await Bun.file('src/app.js').text();
@@ -13,8 +13,8 @@ const wallet = { xpub: 'test-wallet', loaded: true, nostrRelays: () => [],
loadFeatureState: (k, d) => k === 'follows' ? { tags: [['p', author]], at: Date.now()/1000 } : d,
saveFeatureState() {}, registerCacheExtension() {} };
let feature;
-function render() { if (feature) morphChildren(document.querySelector('#app'), [feature.screenView() || h('div', { class: 'wallet-screen' }, 'Wallet')]); }
-feature = messagesFeature({ h, ui, wallet, render, toast() {}, hook() {}, brandHeader: () => h('div', { style: 'height:60px' }, 'Coinos') });
+function render() { if (feature) morphChildren(document.querySelector('#app'), [feature.screenView() || h('div', { class: 'wallet-screen' }, 'Wallet'), feature.bottomNav()]); }
+feature = messagesFeature({ h, ui, wallet, render, toast() {}, hook: name => name === 'unreadMessages' ? 1 : null, brandHeader: () => h('div', { style: 'height:60px' }, 'Coinos') });
window.test = { ui, wallet, render, async feed() {
ui.chatOpen = true; ui.msgView = 'feed'; ui.noteThread = null;
await feature.testSeed(Array.from({length:20}, (_, i) => ({ id: i.toString(16).padStart(64,'0'), pubkey:author, kind:1, tags:[], created_at:Math.floor(Date.now()/1000)-i,
@@ -40,43 +40,49 @@ const page=await browser.newPage(), errors=[];
page.on('pageerror', e=>errors.push(e.message));
await page.setRequestInterception(true);
page.on('request',r=>r.url().startsWith(server.url.origin)?r.continue():r.abort());
-const scroll=async()=>{ await page.evaluate(()=>window.scrollTo(0,700)); await page.waitForFunction(()=>!document.querySelector('.feed-bottom-nav')?.hidden); };
+const scroll=async()=>{ await page.evaluate(()=>window.scrollTo(0,700)); await page.waitForFunction(()=>scrollY > 0 && document.querySelector('.app-bottom-nav')?.getBoundingClientRect().height > 0); };
try {
await page.setViewport({width:390,height:844,isMobile:true,hasTouch:true});
await page.emulateMediaFeatures([{name:'prefers-reduced-motion',value:'reduce'}]);
await page.goto(server.url.href);
await page.waitForSelector('.note-post', {timeout:5000});
- assert(await page.$eval('.feed-bottom-nav',e=>!e.hidden && e.getBoundingClientRect().height>0),'visible at top without scrolling');
+ assert(await page.$eval('.app-bottom-nav',e=>!e.hidden && e.getBoundingClientRect().height>0),'visible at top without scrolling');
await scroll();
- assert.deepEqual(await page.$$eval('.feed-nav-button',els=>els.map(e=>e.textContent)),['Home','Messages','Notifications','Wallet']);
- assert(await page.$eval('.feed-bottom-nav',e=>Math.abs(e.getBoundingClientRect().bottom-innerHeight)<1),'fixed at viewport bottom');
+ assert.deepEqual(await page.$$eval('.app-nav-button',els=>els.map(e=>e.textContent)),['Feed','Messages','Notifications','Wallet']);
+ assert(await page.$eval('.app-bottom-nav',e=>Math.abs(e.getBoundingClientRect().bottom-innerHeight)<1),'fixed at viewport bottom');
await page.evaluate(()=>test.unseen());
- assert(await page.evaluate(()=>document.querySelector('.feed-new-pill').getBoundingClientRect().bottom < document.querySelector('.feed-bottom-nav').getBoundingClientRect().top),'new posts pill stays above navigation');
- await page.screenshot({path:'/tmp/feed-bottom-nav-mobile.png'});
- await page.tap('.feed-nav-button:nth-child(1)');
- await page.waitForFunction(()=>scrollY===0 && !document.querySelector('.feed-bottom-nav').hidden);
- assert.equal(await page.evaluate(()=>test.ui.msgView),'feed','Home stays in feed');
- await scroll(); await page.tap('.feed-nav-button:nth-child(2)');
- await page.waitForFunction(()=>test.ui.msgView==='home' && !document.querySelector('.feed-bottom-nav'));
+ assert(await page.evaluate(()=>document.querySelector('.feed-new-pill').getBoundingClientRect().bottom < document.querySelector('.app-bottom-nav').getBoundingClientRect().top),'new posts pill stays above navigation');
+ await page.screenshot({path:'/tmp/app-bottom-nav-mobile.png'});
+ await page.tap('.app-nav-button:nth-child(1)');
+ await page.waitForFunction(()=>scrollY===0 && !document.querySelector('.app-bottom-nav').hidden);
+ assert.equal(await page.evaluate(()=>test.ui.msgView),'feed','Feed stays in feed');
+ await scroll(); await page.tap('.app-nav-button:nth-child(2)');
+ await page.waitForFunction(()=>test.ui.msgView==='home' && !!document.querySelector('.app-bottom-nav'));
assert((await page.evaluate(()=>document.body.innerText)).includes('Messages'),'chat list renders');
- await page.evaluate(()=>test.feed()); await scroll(); await page.tap('.feed-nav-button:nth-child(3)');
- await page.waitForFunction(()=>test.ui.msgView==='notifs' && !document.querySelector('.feed-bottom-nav'));
+ assert(await page.$eval('.app-nav-button:nth-child(2)',e=>e.getAttribute('aria-current')==='page'),'messages marked active');
+ await page.evaluate(()=>{test.ui.msgView='dm';test.ui.msgPeer='b'.repeat(64);test.render();});
+ await page.waitForSelector('.chat-card');
+ assert(await page.evaluate(()=>document.querySelector('.chat-card').getBoundingClientRect().bottom <= document.querySelector('.app-bottom-nav').getBoundingClientRect().top),'conversation and composer fit above bottom nav');
+ await page.screenshot({path:'/tmp/app-nav-chat.png'});
+ await page.tap('.app-nav-button:nth-child(3)');
+ await page.waitForFunction(()=>test.ui.msgView==='notifs' && !!document.querySelector('.app-bottom-nav'));
assert((await page.evaluate(()=>document.body.innerText)).includes('Notifications'),'notifications render');
- await page.evaluate(()=>test.feed()); await scroll(); await page.tap('.note-text');
- await page.waitForFunction(()=>!!test.ui.noteThread && !document.querySelector('.feed-bottom-nav'));
- await page.evaluate(()=>test.feed());
- assert(await page.$eval('.feed-bottom-nav',e=>!e.hidden && e.getBoundingClientRect().height>0),'returning to feed restores nav immediately without a scroll');
+ await page.tap('.app-nav-button:nth-child(1)'); await scroll(); await page.tap('.note-text');
+ await page.waitForFunction(()=>!!test.ui.noteThread && !!document.querySelector('.app-bottom-nav'));
+ await page.tap('.app-nav-button:nth-child(1)');
+ assert(await page.$eval('.app-bottom-nav',e=>!e.hidden && e.getBoundingClientRect().height>0),'returning to feed restores nav immediately without a scroll');
await page.evaluate(()=>{test.ui.tab='settings';});
- await page.tap('.feed-nav-button:nth-child(4)');
+ await page.tap('.app-nav-button:nth-child(4)');
await page.waitForSelector('.wallet-screen');
- assert(await page.evaluate(()=>!test.ui.chatOpen && test.ui.tab==='receive' && !document.querySelector('.feed-bottom-nav')),'Wallet opens main wallet instead of old settings tab');
- await page.evaluate(()=>test.feed());
+ assert(await page.evaluate(()=>!test.ui.chatOpen && test.ui.tab==='receive' && !!document.querySelector('.app-bottom-nav')),'Wallet opens main wallet instead of old settings tab');
+ await page.tap('.app-nav-button:nth-child(1)');
+ await page.waitForSelector('.feed-page');
await page.setViewport({width:320,height:844,isMobile:true,hasTouch:true});
- assert(await page.$eval('.feed-bottom-nav',e=>e.scrollWidth<=e.clientWidth),'four buttons fit a narrow phone');
- await page.screenshot({path:'/tmp/feed-bottom-nav-always-mobile.png'});
+ assert(await page.$eval('.app-bottom-nav',e=>e.scrollWidth<=e.clientWidth),'four buttons fit a narrow phone');
+ await page.screenshot({path:'/tmp/app-bottom-nav-always-mobile.png'});
await page.evaluate(()=>{test.wallet.xpub=null;test.render();}); await scroll();
- await page.tap('.feed-nav-button:nth-child(2)');
- assert(await page.evaluate(()=>test.ui.signinAsk && test.ui.msgView==='feed'),'visitor gets sign-in prompt');
+ await page.tap('.app-nav-button:nth-child(2)');
+ assert(await page.evaluate(()=>!test.ui.chatOpen && test.ui.msgView===null),'visitor reaches sign-in screen');
assert.deepEqual(errors,[]);
- console.log('✓ Feed nav stays visible at top and on return; Home, Messages, Notifications and Wallet work; four buttons fit a narrow phone; threads, visitor sign-in and new-post pill work');
+ console.log('✓ Global nav works across feed, chat list, conversations, notifications, threads and wallet; Feed restores the feed; composer remains above nav; visitor entry and narrow layout work');
} finally { await page.close(); if (remote) await browser.disconnect(); else await browser.close(); server.stop(true); }
diff --git a/tools/feed-wallet-back-test.js b/tools/feed-wallet-back-test.js
index cd61184..1b0abe7 100644
--- a/tools/feed-wallet-back-test.js
+++ b/tools/feed-wallet-back-test.js
@@ -13,12 +13,13 @@ try {
await page.setRequestInterception(true);
page.on('request', r => r.url().startsWith(server.url.origin) ? r.continue() : r.abort());
await page.goto(server.url.origin + '/feed', { waitUntil: 'domcontentloaded' });
- await page.waitForSelector('.feed-bottom-nav', { visible: true });
- await page.tap('.feed-nav-button:nth-child(4)');
- await page.waitForFunction(() => !document.querySelector('.feed-bottom-nav'));
+ await page.waitForSelector('.app-bottom-nav', { visible: true });
+ await page.tap('.app-nav-button:nth-child(4)');
+ await page.waitForFunction(() => !document.querySelector('.app-bottom-nav'));
+ assert(!(await page.$('.app-bottom-nav')), 'no nav over the welcome screen');
await page.goBack({ waitUntil: 'domcontentloaded' });
- await page.waitForSelector('.feed-bottom-nav', { visible: true, timeout: 5000 });
- assert(await page.$eval('.feed-bottom-nav', e => !e.hidden && e.getBoundingClientRect().height > 0));
+ await page.waitForSelector('.app-bottom-nav', { visible: true, timeout: 5000 });
+ assert(await page.$eval('.app-bottom-nav', e => !e.hidden && e.getBoundingClientRect().height > 0));
assert.equal(await page.evaluate(() => scrollY), 0, 'visible on return without scrolling');
console.log('✓ Real browser Back restores the public feed and visible navigation after Wallet');
} finally { await browser.close(); server.stop(true); }