Start the tree: a fresh 24-word lab root and docs/lineage.md's drawing as keys. Orgs get BIP-85 seeds from their parent (m/83696968'/39'/0'/24'/i'), wallets are BIP-86 m/86'/1'/account'/0/0, npubs are NIP-06; seats are accounts 1..n of their org's seed; indices are never reused. Act as any node to mine, inscribe or sign with its key. Graduation retires a leaf org on a derived edge and adopts a fresh root in its place. The handoff is OpenOrdex's buy-order direction: the recipient builds a 2-in/2-out transaction and signs its fee input first, so its signature fixes where the inscription goes; the holder then countersigns the inscription input. Both are default signatures, so chain.js is unchanged. The keyring and open claims save with the lab (optional fields, same storage key). Tests: BIP-85, BIP-86 and NIP-06 spec vectors plus the handoff and its failure cases (36/36 unit). tests/tree.spec.js rehearses a full graduation in Chromium at desktop and phone sizes, with layout checks; tests/flow.spec.js now follows this branch's copy and theme, and waits for a mined block before reloading (28/28 e2e). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
65 lines
4.3 KiB
JavaScript
65 lines
4.3 KiB
JavaScript
import { test, expect } from '@playwright/test';
|
|
|
|
const outside = page => page.evaluate(() => {
|
|
const box = document.getElementById('tree').getBoundingClientRect();
|
|
return [...document.querySelectorAll('#tree *')].filter(e => { const r = e.getBoundingClientRect(); return r.width > 0 && (r.right > box.right + 1 || r.left < box.left - 1); })
|
|
.map(e => `${e.tagName.toLowerCase()}.${e.className}`).slice(0, 5);
|
|
});
|
|
|
|
test('tree: act as time, birth docscope, graduate it, recipient claims, holder countersigns', async ({ page }) => {
|
|
const errors = [];
|
|
page.on('pageerror', e => errors.push(e.message));
|
|
page.on('console', m => { if (m.type() === 'error') errors.push(m.text()); });
|
|
await page.goto('/');
|
|
await page.getByRole('button', { name: 'Start the tree' }).click();
|
|
await expect(page.locator('#tree-state')).toHaveText('ACTING AS YOLOPRENEUR');
|
|
await expect(page.locator('#wallet-state')).toHaveText('TREE · YOLOPRENEUR');
|
|
await expect(page.locator('#tree-list .tree-node')).toHaveCount(10);
|
|
await page.getByRole('button', { name: 'Act as time', exact: true }).click();
|
|
await expect(page.locator('#tree-state')).toHaveText('ACTING AS TIME');
|
|
await page.getByRole('button', { name: 'Mine now' }).click();
|
|
await expect(page.locator('#balance')).toHaveText('50.00000000');
|
|
const inscribe = async (text, metadata, withParent = false) => {
|
|
await page.getByLabel('What will you put on your chain?').fill(text);
|
|
await page.getByLabel('JSON metadata (optional, independent of body)').fill(metadata);
|
|
await page.getByLabel('Metaprotocol (optional, independent field)').fill('lineage/v0');
|
|
if (withParent) await page.getByLabel('Parent inscription (optional, immutable)').selectOption({ index: 1 });
|
|
await page.getByRole('button', { name: 'Review inscription' }).click();
|
|
await page.getByRole('button', { name: 'Sign & inscribe' }).click();
|
|
await page.getByRole('button', { name: 'Mine now' }).click();
|
|
await expect(page.locator('#pending')).toHaveText('0');
|
|
};
|
|
await inscribe('time', '{"lineage":"root","name":"time"}');
|
|
await expect(page.locator('#tree-list .tree-node.acting')).toContainText('1 inscription: time (root)');
|
|
await inscribe('docscope', '{"lineage":"birth","name":"docscope"}', true);
|
|
await expect(page.locator('#tree-list .tree-node.acting')).toContainText('2 inscriptions');
|
|
await page.locator('#graduate-node').selectOption({ label: 'docscope' });
|
|
await page.getByRole('button', { name: 'Mint its own root' }).click();
|
|
await expect(page.locator('#tree-list .tree-node.retired')).toContainText('docscope');
|
|
await expect(page.locator('#handoff-to option:checked')).toHaveText('docscope (own root)');
|
|
await expect(page.locator('#handoff-inscription option:checked')).toHaveText('docscope (birth) · held by time');
|
|
await page.getByRole('button', { name: '1 · Recipient claims' }).click();
|
|
await expect(page.locator('#status')).toContainText('plain coin');
|
|
await page.getByRole('button', { name: 'Mine a block to the recipient' }).click();
|
|
await page.getByRole('button', { name: '1 · Recipient claims' }).click();
|
|
await expect(page.locator('.claim')).toHaveCount(1);
|
|
await expect(page.locator('.claim .tag')).toHaveText('OPEN');
|
|
await expect(page.locator('.claim pre')).toContainText('"kind": "lineage-claim"');
|
|
expect(await outside(page)).toEqual([]);
|
|
await page.getByRole('button', { name: '2 · Holder countersigns' }).click();
|
|
await expect(page.locator('#pending')).toHaveText('1');
|
|
await expect(page.locator('.claim')).toHaveCount(0);
|
|
await page.getByRole('button', { name: 'Mine now' }).click();
|
|
const own = page.locator('#tree-list .tree-node').filter({ hasText: 'ADOPTED' });
|
|
await expect(own).toContainText('1 inscription: docscope (birth)');
|
|
await expect(own).toContainText('own root (docscope)');
|
|
await expect(page.locator('#tree-list .tree-node.acting')).toContainText('1 inscription: time (root)');
|
|
await page.reload();
|
|
await expect(own).toContainText('1 inscription: docscope (birth)');
|
|
await expect(page.locator('#tree-state')).toHaveText('ACTING AS TIME');
|
|
expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBeTruthy();
|
|
expect(await outside(page)).toEqual([]);
|
|
await page.locator('#tree').screenshot({ path: `test-results/tree-${test.info().project.name}.png` });
|
|
expect(errors).toEqual([]);
|
|
});
|