aboutsummaryrefslogtreecommitdiffstats
path: root/src/app.css
blob: e13bec19144be1c889f2f1c49e0b3056ef633e25 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
@tailwind base;
@tailwind components;
@tailwind utilities;

@font-face {
	font-family: 'PublicSans';
	src: url('/fonts/Public_Sans/static/PublicSans-Regular.ttf') format('truetype');
}

html {
	font-family: PublicSans, sans-serif;
}

::selection {
	color: black;
	background: #eaf2ff;
}

:fullscreen {
	background-color: white;
}

input {
	@apply block border rounded-2xl px-5 py-4 w-full;
}

button.primary {
	@apply rounded-full border py-2 font-bold my-4 flex px-4 bg-white mx-auto hover:opacity-80;
}

.select-styles {
	@apply border rounded-2xl py-4 px-5;
	cursor: pointer;
	-webkit-appearance: none;
	-moz-appearance: none;
	background: transparent;
	background-image: url('/icons/down.svg');
	background-repeat: no-repeat;
	background-position-x: 95%;
	background-position-y: 1em;
}
p">} ${animation} let _carDragging = false, _renderDeferred = false, _accDir = null; const ui = { account: 'spending', tab: 'history' }, ACCOUNT_KEY = 'test-account'; const accountSel = () => ui.account; const root = document.getElementById('app'); window.changes = []; function balanceCard() { const carouselMode = true; const sel = accountSel(), faceFor = (v) => h('div', { style: 'height:110px' }, v), cardExtras = () => []; ${carousel} } ${end} function tabContent() { return h('div', { style: 'height:350px' }, ui.account + ':' + ui.tab); } function render() { if (_carDragging) { _renderDeferred = true; return; } ${tab} const next = h('div', {}, balanceCard(), pane); if (root.firstChild) morph(root.firstChild, next); else root.append(next); if (changes.at(-1) !== ui.account) changes.push(ui.account); } window.state = () => ({ account: ui.account, changes: [...changes], frozen: !!document.querySelector('.bal-carousel')._skipMorph }); render();`; const css = await Bun.file('src/style.css').text(); const html = `<!doctype html><style>${css}\n#app { width:400px; margin:40px auto; }</style><div id="app"></div><script>${js}</script>`; const server = Bun.serve({ port: 0, fetch: () => new Response(html, { headers: { 'Content-Type': 'text/html' } }) }); const browser = await puppeteer.launch({ executablePath: '/usr/bin/google-chrome', headless: true, args: ['--no-sandbox'] }); try { const page = await browser.newPage(); const errors = []; page.on('pageerror', (e) => errors.push(e.message)); await page.goto(server.url.href); await page.waitForSelector('.bal-carousel'); await Bun.sleep(400); for (const injectStaleEnd of [false, true]) { for (const direction of [-1, 1]) { await page.evaluate(() => { changes.length = 0; changes.push(ui.account); }); const box = await page.$eval('.bal-carousel', (e) => { const r = e.getBoundingClientRect(); return { x: r.x + r.width/2, y: r.y + 70 }; }); await page.mouse.move(box.x, box.y); await page.mouse.down(); await page.mouse.move(box.x + direction * 30, box.y); await page.mouse.move(box.x + direction * 75, box.y); if (injectStaleEnd) { // A queued scrollend from the final direct scrollLeft write arrives // after release, before the first smooth-scroll animation frame. await page.evaluate(() => window.addEventListener('pointerup', () => { document.querySelector('.bal-carousel').dispatchEvent(new Event('scrollend')); }, { once: true })); } await page.mouse.up(); await Bun.sleep(950); const state = await page.evaluate(() => window.state()); console.log({ injectStaleEnd, direction, ...state }); assert.deepEqual(state.changes, direction < 0 ? ['spending', 'savings'] : ['savings', 'spending'], 'one content transition per flick'); assert.equal(state.frozen, false); } } // A small slow pull stays on the same card and still releases the freeze. await page.evaluate(() => { changes.length = 0; changes.push(ui.account); const el = document.querySelector('.bal-carousel'); el.dispatchEvent(new PointerEvent('pointerdown', { pointerType: 'mouse', button: 0, clientX: 300, bubbles: true })); window.dispatchEvent(new PointerEvent('pointermove', { clientX: 290 })); window.dispatchEvent(new PointerEvent('pointerup')); }); await Bun.sleep(800); assert.deepEqual((await page.evaluate(() => window.state())).changes, ['spending']); assert.equal((await page.evaluate(() => window.state())).frozen, false); // Simulate a browser dropping the requested smooth scroll entirely: the // fallback must finish the intended switch, not unfreeze at the old card. await page.evaluate(() => { const el = document.querySelector('.bal-carousel'); el.scrollTo = function (opts) { if (opts.behavior !== 'smooth') Element.prototype.scrollTo.call(this, opts); }; el.dispatchEvent(new PointerEvent('pointerdown', { pointerType: 'mouse', button: 0, clientX: 300, bubbles: true })); window.dispatchEvent(new PointerEvent('pointermove', { clientX: 220 })); window.dispatchEvent(new PointerEvent('pointerup')); }); await Bun.sleep(850); assert.deepEqual((await page.evaluate(() => window.state())).changes, ['spending', 'savings']); assert.equal((await page.evaluate(() => window.state())).frozen, false); await page.evaluate(() => { delete document.querySelector('.bal-carousel').scrollTo; }); // Grab again before a glide finishes, and hold beyond its old fallback. const box = await page.$eval('.bal-carousel', (el) => { const r = el.getBoundingClientRect(); return { x: r.x + 150, y: r.y + 70 }; }); await page.mouse.move(box.x, box.y); await page.mouse.down(); await page.mouse.move(box.x + 80, box.y); await page.mouse.up(); await page.mouse.down(); await page.mouse.move(box.x + 60, box.y); await Bun.sleep(800); assert.equal((await page.evaluate(() => window.state())).frozen, true, 'old fallback cannot unfreeze a new drag'); await page.mouse.up(); await Bun.sleep(850); assert.equal((await page.evaluate(() => window.state())).frozen, false); assert.deepEqual(errors, []); console.log('✓ short pull, missing scrollend/glide fallback and re-grab hold'); console.log('✓ fast flicks in both directions switch content once, including a stale drag scrollend'); } finally { await browser.close(); server.stop(true); }