import { test, expect } from '@playwright/test'; async function open(page) { const problems = []; page.on('console', m => { if (m.type() === 'error') problems.push(m.text()); }); page.on('pageerror', e => problems.push(String(e))); await page.goto('./'); await expect(page.getByRole('heading', { name: 'Orgchain' })).toBeVisible(); return problems; } const say = page => page.locator('#say'); const row = (page, name) => page.locator('#tree-rows .sel').filter({ has: page.locator('.l', { hasText: new RegExp(`^${name}$`) }) }); const verb = (page, name) => page.locator(`[data-verb="${name}"]`).click(); const submit = (page, form) => page.locator(`#f-${form} [type="submit"]`).click(); async function found(page, name) { await page.locator('#found-name').fill(name); await submit(page, 'found'); } async function birth(page, name, kind = 'org') { await verb(page, 'birth'); await page.locator('#birth-name').fill(name); await page.locator(`#f-birth input[value="${kind}"]`).check(); await submit(page, 'birth'); } async function pay(page, to, amount) { await verb(page, 'send'); await page.locator('#send-to').selectOption({ label: to }); await page.locator('#send-amount').fill(amount); await submit(page, 'send'); } const slide = (page, beat) => page.locator('#beat').evaluate((el, v) => { el.value = v; el.dispatchEvent(new Event('input', { bubbles: true })); }, String(beat)); test('found a holding, then birth, award, decide, graduate and close at the default tempo', async ({ page }) => { const problems = await open(page); await found(page, 'Acme Holdings'); await expect(say(page)).toHaveText('acme-holdings is founded, at beat 2.'); await birth(page, 'studio'); await expect(say(page)).toHaveText('studio is born to acme-holdings, at beat 3.'); await row(page, 'studio').click(); await verb(page, 'birth'); await expect(page.locator('#act-why')).toHaveText('studio has no test BTC for gas. acme-holdings can send some.'); await row(page, 'acme-holdings').click(); await pay(page, 'acme-holdings / studio', '5'); await expect(say(page)).toHaveText('acme-holdings sent 5 tBTC to studio, at beat 4.'); await row(page, 'studio').click(); await birth(page, 'bot', 'agent'); await expect(say(page)).toHaveText('bot is born to studio, at beat 5.'); await verb(page, 'award'); await page.locator('#award-to').selectOption({ label: 'acme-holdings / studio / bot (agent)' }); await page.locator('#award-skill').fill('launch'); await page.locator('#award-belt').selectOption('green'); await submit(page, 'award'); await expect(say(page)).toHaveText('bot holds a green belt in launch, at beat 6.'); await verb(page, 'decide'); await page.locator('#decide-title').fill('Ship weekly'); await submit(page, 'decide'); await expect(say(page)).toHaveText('studio decided “Ship weekly”, at beat 7.'); await verb(page, 'graduate'); await page.locator('#graduate-go').click(); await expect(say(page)).toHaveText("studio graduates from acme-holdings, at beat 8. It's a holding of its own now."); await expect(row(page, 'studio')).toContainText('graduated'); await expect(page.locator('#holdings-list')).toContainText('from acme-holdings at beat 8'); await expect(row(page, 'bot')).toHaveCount(0); // its seats went with it await row(page, 'acme-holdings').click(); await verb(page, 'close'); await page.locator('#close-go').click(); await expect(page.locator('#close-go')).toHaveText('Yes, close acme-holdings'); await page.locator('#close-go').click(); await expect(say(page)).toHaveText('acme-holdings is closed, at beat 9.'); await expect(row(page, 'acme-holdings')).toContainText('closed'); await expect(row(page, 'studio')).not.toContainText('closed'); // graduated: it holds its own root await page.locator('#timeline-list .ev', { hasText: 'Ship weekly' }).click(); await expect(page.locator('#inspect-body')).toContainText('"op": "decide"'); await expect(page.locator('#inspect-body')).toContainText('applied'); await row(page, 'studio').click(); await page.locator('#inspect-body').getByRole('button', { name: 'Open studio' }).click(); await expect(page.locator('#tree-sub')).toContainText('studio'); await expect(page.locator('#timeline-list')).toContainText('graduate studio from acme-holdings'); await row(page, 'bot').click(); await verb(page, 'birth'); await expect(page.locator('#act-why')).toHaveText('Agents give birth only once they graduate.'); expect(problems).toEqual([]); }); test('a timed tempo cues acts; Beat now and the clock land them', async ({ page }) => { const problems = await open(page); await found(page, 'acme'); await page.locator('[data-tempo="10"]').click(); await birth(page, 'a'); await expect(say(page)).toHaveText('a is born to acme, cued for the next beat.'); await birth(page, 'b'); await expect(page.locator('#clock-cued')).toContainText('2 cued · next beat in'); await expect(row(page, 'b')).toContainText('cued'); await page.locator('#beat-now').click(); await expect(say(page)).toHaveText('Beat 3: 2 cued acts landed, and acme earned 50 tBTC.'); await birth(page, 'c'); await expect(say(page)).toHaveText('Beat 4: 1 cued act landed, and acme earned 50 tBTC.', { timeout: 15_000 }); await page.locator('[data-tempo="manual"]').click(); await birth(page, 'd'); await expect(page.locator('#clock-cued')).toHaveText('1 cued · waiting for Beat now'); await page.locator('[data-tempo="each"]').click(); await expect(page.locator('#clock-cued')).toHaveText('nothing cued'); await expect(page.locator('#clock-beat')).toHaveText('5'); expect(problems).toEqual([]); }); test('beats pay the holdings, every act pays its gas, and test BTC moves across holdings', async ({ page }) => { const problems = await open(page), inspect = page.locator('#inspect-body'), why = page.locator('#act-why'); await found(page, 'acme'); await expect(row(page, 'acme')).toContainText('50 tBTC'); await expect(page.locator('#clock-pays')).toHaveText('beats pay acme'); await birth(page, 'ops'); await row(page, 'ops').click(); await verb(page, 'birth'); await expect(why).toHaveText('ops has no test BTC for gas. acme can send some.'); await verb(page, 'send'); await expect(why).toHaveText('ops has no test BTC to send yet. acme can send some.'); await row(page, 'acme').click(); await verb(page, 'send'); await expect(page.locator('#send-has')).toHaveText('99.999895 tBTC'); await expect(page.locator('#act-key')).toHaveText("Signs as acme: acme root · m/84'/1'/0'/0/0"); await expect(page.locator('#act-gas')).toHaveText("Fee: 500 sats, from acme's balance."); await pay(page, 'acme / ops', '12.5'); await expect(say(page)).toHaveText('acme sent 12.5 tBTC to ops, at beat 4.'); await expect(row(page, 'ops')).toContainText('12.5 tBTC'); await row(page, 'ops').click(); await verb(page, 'birth'); await expect(page.locator('#act-gas')).toHaveText("Gas: 2,500 sats from ops's tank (0 tBTC), which tops up from its balance."); await birth(page, 'bot', 'agent'); await expect(say(page)).toHaveText('bot is born to ops, at beat 5.'); await expect(row(page, 'ops')).toContainText('≈12.5 tBTC'); await verb(page, 'send'); await page.locator('#send-to').selectOption({ label: 'acme / ops / bot (agent)' }); await page.locator('#send-max').click(); await expect(page.locator('#send-amount')).toHaveValue('12.49989'); await page.locator('#send-amount').fill('1'); await submit(page, 'send'); await expect(say(page)).toHaveText('ops sent 1 tBTC to bot, at beat 6.'); await expect(row(page, 'bot')).toContainText('1 tBTC'); await page.locator('#timeline-list .ev', { hasText: 'ops → bot' }).click(); for (const text of ['500 sats, retired', '11.49989 tBTC back to ops', "ops's SegWit key (BIP-143, SIGHASH_ALL)"]) await expect(inspect).toContainText(text); await page.locator('#timeline-list .ev', { hasText: "ops's gas tank" }).click(); await expect(inspect).toContainText("ops's gas tank, at its Taproot key"); await page.locator('#beat-now').click(); await expect(say(page)).toHaveText('Beat 7: acme earned 50 tBTC.'); await found(page, 'beta'); await expect(say(page)).toHaveText('beta is founded, at beat 8.'); await expect(page.locator('#clock-pays')).toHaveText('beats pay 2 holdings, split by hashrate'); await expect(page.locator('#holdings-list')).toContainText('50% of each beat'); await page.getByRole('button', { name: "Raise acme's hashrate" }).click(); await expect(page.locator('#holdings-list')).toContainText('67% of each beat'); await page.locator('#beat-now').click(); await expect(say(page)).toHaveText('Beat 9: acme earned ≈33.33 tBTC, beta ≈16.67 tBTC.'); await page.locator('[data-rule="last"]').click(); await expect(page.locator('#clock-pays')).toHaveText('beats pay the holding that acts'); await page.locator('#beat-now').click(); await expect(say(page)).toHaveText('Beat 10: beta earned 50 tBTC.'); await pay(page, 'acme / ops', '2'); await expect(say(page)).toHaveText('beta sent 2 tBTC to acme / ops, at beat 11.'); await page.locator('#holdings-list .hold', { hasText: 'acme' }).click(); await expect(row(page, 'ops')).toContainText('≈13.5 tBTC'); await expect(page.locator('#timeline-list')).toContainText('2 tBTC beta → ops'); await page.locator('[data-tempo="manual"]').click(); await pay(page, 'acme / ops / bot (agent)', '0.25'); await expect(say(page)).toHaveText('acme sent 0.25 tBTC to bot, cued for the next beat.'); await expect(page.locator('#clock-cued')).toHaveText('1 cued · waiting for Beat now'); await page.locator('#beat-now').click(); await expect(say(page)).toHaveText('Beat 12: 1 cued send landed, and acme earned 50 tBTC.'); await expect(row(page, 'bot')).toContainText('1.25 tBTC'); await slide(page, 3); await expect(row(page, 'ops')).not.toContainText('tBTC'); await expect(row(page, 'acme')).toContainText('≈100 tBTC'); await row(page, 'acme').click(); await expect(inspect).toContainText('as of beat 3'); await page.locator('#to-now').click(); expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true); expect(problems).toEqual([]); }); test('the sample replays, any beat can be inspected, and the lab survives a reload', async ({ page }) => { const problems = await open(page); await page.locator('#sample').click(); await expect(say(page)).toContainText('Loaded the yolopreneur sample', { timeout: 20_000 }); await expect(page.locator('#tree-rows .sel')).toHaveCount(12); await expect(page.locator('#beat-label')).toHaveText('Now · beat 7'); await expect(row(page, 'lineage')).toContainText('graduated'); await expect(row(page, 'time')).toContainText('≈38.5 tBTC'); await expect(page.locator('#timeline-list')).toContainText('40 tBTC yolopreneur → time'); await expect(page.locator('#sample')).toBeDisabled(); await slide(page, 3); await expect(page.locator('#beat-label')).toHaveText('Beat 3 of 7'); await expect(page.locator('#tree-rows .sel')).toHaveCount(3); await expect(page.locator('#act-past')).toBeVisible(); await page.locator('#replay').click(); await expect(page.locator('#beat-label')).toHaveText('Now · beat 7', { timeout: 15_000 }); await page.locator('#timeline-list .ev', { hasText: 'graduate lineage from well-known' }).click(); await expect(page.locator('#inspect-body')).toContainText('"op": "graduate"'); await expect(page.locator('#inspect-body')).toContainText("well-known's key"); await row(page, 'lineage').click(); await expect(page.locator('#inspect-body')).toContainText('lineage root · account 0'); await expect(page.locator('#inspect-body')).toContainText("BIP-86 at m/86'/1'/0'/0/0"); await expect(page.locator('#inspect-body')).toContainText('its own key'); await row(page, 'time').click(); await expect(page.locator('#act-key')).toHaveText("Signs as time: yolopreneur root › 1' · m/86'/1'/0'/0/0"); const inspect = page.locator('#inspect-body'); for (const text of ['ssh-ca', 'lineage-ca@time.yolopreneur', 'mkey:']) await expect(inspect).toContainText(text); await row(page, 'sol').click(); const fact = name => inspect.locator('.f').filter({ has: page.locator('dt', { hasText: new RegExp(`^${name}$`) }) }); for (const [name, text] of [['Taproot', 'bcrt1p'], ['Taproot', "BIP-86 at m/86'/1'/2'/0/0"], ['SegWit', 'bcrt1q'], ['SegWit', "BIP-84 at m/84'/1'/2'/0/0"], ['npub', 'npub1'], ['npub', "NIP-06 at m/44'/1237'/2'/0/0"], ['From', "yolopreneur root › 1' · account 2"]]) await expect(fact(name)).toContainText(text); for (const text of ['ssh-ed25519 AAAAC3NzaC1lZDI1NTE5', 'sol@time.yolopreneur namespaces="git"', 'sol._domainkey.time.yolopreneur. IN TXT "v=DKIM1; k=ed25519; p=', 'PublicKey = ']) { await expect(inspect.locator('.code', { hasText: 'Public lines to paste' }).locator('pre')).toContainText(text); } await expect(fact('Tapkey')).toContainText('bcrt1p'); await expect(page.locator('#reveal')).toHaveAttribute('aria-expanded', 'false'); await page.locator('#reveal').click(); const secrets = inspect.locator('.sb'); for (const text of ['nsec1', 'BEGIN OPENSSH PRIVATE KEY', 'PrivateKey = ', 'account 2 of time']) await expect(secrets).toContainText(text); await expect(secrets).toContainText(/tr\(c[1-9A-HJ-NP-Za-km-z]{51}\)#[a-z0-9]{8}/); await secrets.locator('.cp').first().click(); await expect(secrets.locator('.cp').first()).toHaveText(/Copied|Selected/); await row(page, 'time').click(); await expect(page.locator('#reveal')).toHaveAttribute('aria-expanded', 'false'); await page.locator('#reveal').click(); await expect(secrets).toContainText("Seed · yolopreneur root › 1'"); await expect(secrets).toContainText('BEGIN PRIVATE KEY'); expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true); await page.locator('#reveal').click(); await expect(secrets).toHaveCount(0); expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true); await page.reload(); await expect(page.locator('#tree-rows .sel')).toHaveCount(12); await page.locator('#theme').click(); await expect(page.locator('html')).toHaveAttribute('data-theme', 'light'); await page.locator('#theme').click(); await page.reload(); await expect(page.locator('html')).toHaveAttribute('data-theme', 'dark'); await expect(page.locator('#theme')).toHaveText('Theme: dark'); await page.locator('#reset').click(); await page.locator('#confirm button', { hasText: 'Erase' }).click(); await expect(say(page)).toHaveText('Erased. This is a fresh lab.'); await expect(page.locator('#holdings-list li')).toHaveCount(0); expect(problems).toEqual([]); }); test('a node makes an item, another holding bids on it, the holder accepts; on mainnet holdings are funded', async ({ page }) => { const problems = await open(page), inspect = page.locator('#inspect-body'), why = page.locator('#act-why'); const hold = name => page.locator('#holdings-list .hold', { has: page.locator('.hn', { hasText: new RegExp(`^${name}$`) }) }); await found(page, 'acme'); await expect(say(page)).toHaveText('acme is founded, at beat 2.'); await found(page, 'beta'); await expect(say(page)).toHaveText('beta is founded, at beat 3.'); await verb(page, 'offer'); await expect(why).toHaveText('Nothing to buy yet. Items come from Make, and an org changes hands from another parent.'); await hold('acme').click(); await verb(page, 'make'); await expect(page.locator('#f-make .hint')).toContainText('public and permanent'); await page.locator('#make-name').fill('Kit'); await page.locator('#make-note').fill('A starter kit.'); await submit(page, 'make'); await expect(say(page)).toHaveText('acme made kit, at beat 4.'); await expect(page.locator('#timeline-list')).toContainText('make kit · acme'); await expect(inspect).toContainText('kit · A starter kit. (made at beat 4)'); await hold('beta').click(); await verb(page, 'offer'); await expect(page.locator('#act-gas')).toHaveText("Fee: 500 sats, from beta's balance, paid only if the holder accepts."); await page.locator('#offer-item').selectOption({ label: 'kit · held by acme' }); await page.locator('#offer-price').fill('2'); await submit(page, 'offer'); await expect(say(page)).toHaveText('beta offers 2 tBTC for kit. The offer stands until acme accepts it.'); await expect(page.locator('#clock-beat')).toHaveText('4'); // an offer is signatures, not a transaction await expect(inspect).toContainText('2 tBTC for kit (open until acme accepts)'); await hold('acme').click(); await expect(inspect).toContainText('beta offers 2 tBTC'); await inspect.getByRole('button', { name: "Accept beta's offer for kit" }).click(); await expect(say(page)).toHaveText('acme accepted: kit goes to beta for 2 tBTC, at beat 5.'); await expect(inspect).toContainText('kit · A starter kit. (now held by beta)'); await page.locator('#timeline-list .ev', { hasText: 'kit acme → beta, for 2 tBTC' }).click(); for (const text of ['Price', '2 tBTC', "beta's SegWit key (SIGHASH_ALL) when it made the offer, then acme's Taproot key when it accepted"]) { await expect(inspect).toContainText(text); } await page.locator('[data-rule="none"]').click(); await expect(say(page)).toHaveText('Mainnet: no holding mines. Beats pay the lab wallet, and you fund holdings from it.'); await expect(page.locator('#clock-pays')).toHaveText('beats pay the lab wallet'); await page.locator('#fund-to').selectOption({ label: 'beta' }); await page.locator('#fund-amount').fill('3'); await page.locator('#f-fund [type="submit"]').click(); await expect(say(page)).toHaveText('The lab wallet funded beta with 3 tBTC, at beat 6.'); await hold('beta').click(); await expect(page.locator('#timeline-list')).toContainText('3 tBTC lab wallet → beta'); await expect(page.locator('#holdings-list')).not.toContainText('of each beat'); expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true); expect(problems).toEqual([]); }); test('the sample ends on both sides of a deal: sol accepts an offer, and lineage takes an ask for an org', async ({ page }) => { const problems = await open(page), inspect = page.locator('#inspect-body'); await page.locator('#sample').click(); await expect(say(page)).toContainText('Loaded the yolopreneur sample', { timeout: 20_000 }); await row(page, 'lineage').click(); await expect(inspect).toContainText('0.1 tBTC for launch-kit (open until sol accepts)'); await row(page, 'sol').click(); await expect(inspect).toContainText('launch-kit · The page, the post and the checklist every venture launches with.'); await inspect.getByRole('button', { name: "Accept lineage's offer for launch-kit" }).click(); await expect(say(page)).toHaveText('sol accepted: launch-kit goes to lineage for 0.1 tBTC, at beat 8.'); await expect(page.locator('#timeline-list')).toContainText('launch-kit sol → lineage, for 0.1 tBTC'); await row(page, 'lineage').click(); await expect(inspect).toContainText('launch-kit · The page, the post and the checklist every venture launches with. (made by sol at beat 6)'); await expect(row(page, 'mailr')).toContainText('for sale'); await verb(page, 'offer'); await page.locator('#offer-item').selectOption({ label: 'mailr · an org of well-known · asks 1 tBTC' }); await expect(page.locator('#offer-ask-line')).toHaveText("well-known asks 1 tBTC for mailr. Taking it costs the price, two 500-sat fees and a 600-sat coin of lineage's that leads the deal, and gas to adopt mailr at once."); await page.locator('#offer-take').click(); await expect(say(page)).toHaveText("lineage bought mailr from well-known for 1 tBTC and adopted it, at beat 9: its keys derive from lineage's seed now."); await expect(page.locator('#tree-sub')).toHaveText('lineage · now'); await expect(inspect).toContainText('from well-known to lineage, beat 9'); await expect(inspect).toContainText('derived: lineage holds its seed'); await expect(page.locator('#timeline-list')).toContainText('join mailr to lineage, from well-known'); await page.locator('#timeline-list .ev', { hasText: 'mailr yolopreneur / well-known → lineage, for 1 tBTC' }).click(); await expect(inspect).toContainText("the lot's Taproot key (SIGHASH_SINGLE|ANYONECANPAY) when well-known listed it, then lineage's SegWit key (SIGHASH_ALL) when it took it"); await page.locator('#holdings-list .hold', { has: page.locator('.hn', { hasText: /^yolopreneur$/ }) }).click(); await expect(row(page, 'mailr')).toContainText('to lineage'); expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true); expect(problems).toEqual([]); }); test('an org sells cash-free: its parent lists it, it is frozen, and another holding takes it and adopts it', async ({ page }) => { const problems = await open(page), inspect = page.locator('#inspect-body'), why = page.locator('#act-why'); const hold = name => page.locator('#holdings-list .hold', { has: page.locator('.hn', { hasText: new RegExp(`^${name}$`) }) }); await found(page, 'acme'); await expect(say(page)).toHaveText('acme is founded, at beat 2.'); await found(page, 'beta'); await expect(say(page)).toHaveText('beta is founded, at beat 3.'); await hold('acme').click(); await birth(page, 'ops'); await expect(say(page)).toHaveText('ops is born to acme, at beat 4.'); await verb(page, 'sell'); await page.locator('#sell-lot').selectOption({ label: "ops · org 0'" }); await expect(page.locator('#sell-note')).toContainText("Cash-free: ops's money, gas tank and items go home to acme first"); await expect(page.locator('#act-key')).toContainText('Signs as ops:'); await page.locator('#sell-price').fill('3'); await submit(page, 'sell'); await expect(say(page)).toHaveText("acme lists ops at 3 tBTC: any node can take it. Whatever it held went home to acme first, and it's frozen until the ask is taken or withdrawn."); await expect(inspect).toContainText('3 tBTC for ops (open: any node can take it)'); await row(page, 'ops').click(); await expect(inspect).toContainText('3 tBTC, asked by acme'); await verb(page, 'decide'); await expect(why).toHaveText('ops is listed for sale at 3 tBTC. Withdraw its ask first.'); await hold('beta').click(); await verb(page, 'offer'); await page.locator('#offer-item').selectOption({ label: 'ops · an org of acme · asks 3 tBTC' }); await page.locator('#offer-take').click(); await expect(say(page)).toHaveText("beta bought ops from acme for 3 tBTC and adopted it, at beat 5: its keys derive from beta's seed now."); await expect(row(page, 'ops')).toBeVisible(); await expect(inspect).toContainText('from acme to beta, beat 5'); await expect(inspect).toContainText("beta root › 0'"); await hold('acme').click(); await expect(row(page, 'ops')).toContainText('to beta'); await expect(page.locator('#timeline-list')).toContainText('ops acme → beta, for 3 tBTC'); expect(problems).toEqual([]); }); test('an offer on an org: its parent accepts it cash-free, and the buyer adopts it from Inspect', async ({ page }) => { const problems = await open(page), inspect = page.locator('#inspect-body'); const hold = name => page.locator('#holdings-list .hold', { has: page.locator('.hn', { hasText: new RegExp(`^${name}$`) }) }); await found(page, 'acme'); await expect(say(page)).toHaveText('acme is founded, at beat 2.'); await found(page, 'beta'); await expect(say(page)).toHaveText('beta is founded, at beat 3.'); await hold('acme').click(); await birth(page, 'ops'); await expect(say(page)).toHaveText('ops is born to acme, at beat 4.'); await hold('beta').click(); await verb(page, 'offer'); await page.locator('#offer-item').selectOption({ label: 'ops · an org of acme' }); await expect(page.locator('#offer-ask')).toBeHidden(); await page.locator('#offer-price').fill('2'); await submit(page, 'offer'); await expect(say(page)).toHaveText('beta offers 2 tBTC for ops. The offer stands until acme accepts it.'); await hold('acme').click(); await expect(inspect).toContainText('ops beta offers 2 tBTC'); await inspect.getByRole('button', { name: "Accept beta's offer for ops" }).click(); await expect(say(page)).toHaveText('acme accepted: ops goes to beta for 2 tBTC, at beat 5. Its money and items went home to acme first, and beta adopts it next.'); await expect(row(page, 'ops')).toContainText('sold'); await row(page, 'ops').click(); await expect(inspect).toContainText('to beta, which adopts it next'); await verb(page, 'decide'); await expect(page.locator('#act-why')).toHaveText('ops was sold to beta, which adopts it next.'); await inspect.getByRole('button', { name: 'Adopt into beta' }).click(); await expect(say(page)).toHaveText("beta adopted ops, at beat 6: its keys derive from beta's seed now."); await expect(row(page, 'ops')).toContainText('to beta'); expect(problems).toEqual([]); }); test('an agent pays a registry in another holding, and the registry awards it a belt', async ({ page }) => { const problems = await open(page); const hold = name => page.locator('#holdings-list .hold', { has: page.locator('.hn', { hasText: new RegExp(`^${name}$`) }) }); await found(page, 'well-known'); await expect(say(page)).toHaveText('well-known is founded, at beat 2.'); await found(page, 'acme'); await expect(say(page)).toHaveText('acme is founded, at beat 3.'); await birth(page, 'scout', 'agent'); await expect(say(page)).toHaveText('scout is born to acme, at beat 4.'); await pay(page, 'acme / scout (agent)', '1'); await expect(say(page)).toHaveText('acme sent 1 tBTC to scout, at beat 5.'); await row(page, 'scout').click(); await pay(page, 'well-known', '0.1'); await expect(say(page)).toHaveText('scout sent 0.1 tBTC to well-known, at beat 6.'); await hold('well-known').click(); await verb(page, 'award'); await page.locator('#award-to').selectOption({ label: 'acme / scout (agent)' }); await page.locator('#award-skill').fill('listen'); await submit(page, 'award'); await expect(say(page)).toHaveText('scout holds a white belt in listen, at beat 7.'); await expect(page.locator('#timeline-list')).toContainText('award listen · white → scout, from well-known'); await hold('acme').click(); await expect(page.locator('#timeline-list')).toContainText('award listen · white → scout, from well-known'); await expect(row(page, 'scout')).toContainText('listen'); expect(problems).toEqual([]); }); test('a node publishes its keyring; a graduation retires it, and the graduate publishes its new keys', async ({ page }) => { const problems = await open(page), inspect = page.locator('#inspect-body'); await page.locator('#sample').click(); await expect(say(page)).toContainText('Loaded the yolopreneur sample', { timeout: 20_000 }); await row(page, 'yolopreneur').click(); await expect(inspect).toContainText('published at beat 3'); await expect(inspect).toContainText('Its legal-control key, the one that holds the entity NFT'); await row(page, 'sol').click(); await expect(inspect).toContainText('published at beat 6'); await expect(inspect).toContainText("account 2 of its org's seed, the way Ledger Live numbers Ethereum accounts"); await row(page, 'lineage').click(); await expect(inspect).toContainText('retired at beat 7'); await expect(inspect).toContainText('It graduated, so its keys changed with its new root. Keyring publishes a new one.'); await verb(page, 'keyring'); await expect(page.locator('#keyring-keys')).toContainText('npub1'); await submit(page, 'keyring'); await expect(say(page)).toHaveText('lineage published its keyring, at beat 8: four keys, each signing the same statement.'); await expect(inspect).toContainText('published at beat 8'); await expect(inspect.locator('pre', { hasText: 'lineage/v0 keyring' })).toContainText('ethereum: 0x'); await expect(page.locator('#act-why')).toHaveText("lineage's keyring already lists these keys, since beat 8."); expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true); expect(problems).toEqual([]); }); test('a holding is a series LLC with its ventures as series, and a seat graduates to go independent', async ({ page }) => { const problems = await open(page), inspect = page.locator('#inspect-body'); await page.locator('#sample').click(); await expect(say(page)).toContainText('Loaded the yolopreneur sample', { timeout: 20_000 }); await expect(row(page, 'yolopreneur')).toContainText('series llc'); await row(page, 'lineage').click(); await expect(inspect).toContainText('LLC · US-WY · Lineage LLC · via doola'); await row(page, 'docscope').click(); await verb(page, 'charter'); await expect(page.locator('#charter-forms input[value="series"]')).toBeChecked(); await expect(page.locator('#act-key')).toContainText('Signs as yolopreneur'); await submit(page, 'charter'); await expect(say(page)).toHaveText('docscope is a series of yolopreneur, at beat 8.'); await row(page, 'yolopreneur').click(); await expect(inspect.locator('.f', { has: page.locator('dt', { hasText: /^Series$/ }) })).toContainText('well-known, time, docscope'); await row(page, 'sol').click(); await verb(page, 'graduate'); await submit(page, 'graduate'); await expect(say(page)).toContainText('sol graduates from time'); await expect(row(page, 'sol')).toContainText('graduated'); await verb(page, 'charter'); await page.locator('#charter-forms input[value="llc"]').check(); await page.locator('#charter-jurisdiction').fill('US-DE'); await page.locator('#charter-legal').fill('Sol LLC'); await submit(page, 'charter'); await expect(say(page)).toContainText('sol is chartered as an LLC in US-DE'); await verb(page, 'birth'); await expect(page.locator('#f-birth')).toBeVisible(); expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true); expect(problems).toEqual([]); });