const test = require('node:test'), assert = require('node:assert/strict'); const header = require('../utils/page-header'); test('header reserves status/notch and native controls, preserves page lifecycle and refreshes on resize', () => { let info = { statusBarHeight: 59, titleBarHeight: 44 }, loaded = 0, resized = 0; const p = header.page({ data: { original: true }, onLoad(query) { assert.equal(this.data.brandHeader.height, 103); loaded++; return query.id; }, onResize() { resized++; } }, { getSystemInfoSync: () => info }); p.setData = patch => Object.assign(p.data, patch); assert.equal(p.onLoad({ id: 7 }), 7); assert.equal(loaded, 1); assert.equal(p.data.original, true); assert.equal(p.data.brandHeader.title, "1'me肌肤状态检测"); info = { safeArea: { top: 24 }, titleBarHeight: 48 }; p.onResize(); assert.equal(p.data.brandHeader.height, 72); assert.equal(resized, 1); assert.equal(header.layout({ getSystemInfoSync() { throw Error('unsupported'); } }).height, 64); }); test('native title is cleared on load, ready and return without interfering with login or late callbacks', () => { const calls = []; let shown = 0, ready = 0, hidden = 0; const p = header.page({ data: {}, onShow() { shown++; return 'login-flow'; }, onReady() { ready++; }, onHide() { hidden++; } }, { setNavigationBar(options) { calls.push(options); } }, { nativeTitleFallback: true }); p.setData = patch => Object.assign(p.data, patch); p.onLoad(); p.onReady(); assert.equal(p.onShow(), 'login-flow'); assert.equal(calls.length, 3); assert.equal(shown, 1); assert.equal(ready, 1); assert(calls.every(call => call.title === '\u200b' && !('backgroundColor' in call))); calls[0].fail({}); assert.equal(p.data.brandHeader.showTitle, true, 'stale native failure cannot hide the current title'); calls[2].fail({}); assert.equal(p.data.brandHeader.showTitle, false, 'entry page falls back to one platform title if clearing is rejected'); p.onShow(); calls[3].success({}); assert.equal(p.data.brandHeader.showTitle, true); p.onShow(); p.onHide(); calls[4].fail({}); assert.equal(hidden, 1); assert.equal(p.data.brandHeader.showTitle, true); }); test('native errors cannot block page loading and rejected promises are observed', async () => { let loaded = 0; for (const mode of ['throw', 'reject']) { const p = header.page({ data: {}, onLoad() { loaded++; } }, { setNavigationBar() { if (mode === 'throw') throw Error('unsupported'); return Promise.reject(Error('unsupported')); } }, { nativeTitleFallback: true }); p.setData = patch => Object.assign(p.data, patch); p.onLoad(); await new Promise(resolve => setImmediate(resolve)); assert.equal(p.data.brandHeader.showTitle, false); } assert.equal(loaded, 2); }); test('actual header template centers on the full screen and keeps clear of status and native buttons', async () => { const fs = require('node:fs'), path = require('node:path'), { chromium } = require('playwright'); const root = path.resolve(__dirname, '..'); const axml = fs.readFileSync(path.join(root, 'components/brand-header.axml'), 'utf8'); const css = fs.readFileSync(path.join(root, 'app.acss'), 'utf8'); const browser = await chromium.launch({ headless: true }); try { const tab = await browser.newPage(); for (const width of [320, 375, 414]) for (const top of [20, 59]) { const geometry = header.layout({ getSystemInfoSync: () => ({ statusBarHeight: top, titleBarHeight: 44 }) }); const html = axml.replace(/<\/?template[^>]*>/g, '').replace(/view/g, 'div').replace(/<\/?text/g, tag => tag.replace('text', 'span')) .replace(/\{\{brandHeader\.(\w+)\}\}/g, (_, key) => geometry[key]); await tab.setViewportSize({ width, height: 812 }); await tab.setContent('' + html); const bounds = await tab.evaluate(() => { const row = document.querySelector('.brand-header-line').getBoundingClientRect(); const title = document.querySelector('.brand-header-title').getBoundingClientRect(); return { rowTop: row.top, titleCenter: title.left + title.width / 2, left: title.left, right: title.right }; }); assert.equal(bounds.rowTop, top); assert(Math.abs(bounds.titleCenter - width / 2) < .01); assert(bounds.left >= 76 && bounds.right <= width - 76); } for (const route of JSON.parse(fs.readFileSync(path.join(root, 'app.json'), 'utf8')).pages) { const config = JSON.parse(fs.readFileSync(path.join(root, route + '.json'), 'utf8')); assert.equal(config.transparentTitle, 'always'); assert.equal(config.defaultTitle, '\u200b'); assert.match(fs.readFileSync(path.join(root, route + '.axml'), 'utf8'), /template is="brand-header"/); } } finally { await browser.close(); } });