oficina / sheets Public

Clone
git clone https://amadan.net/oficina/sheets

Plain git — no account needed to clone.

Download

Download this file

1// Browser-level component checks with the real vendored grid and mocked transport.
2// PLAYWRIGHT=/path/to/playwright/index.mjs node tools/drive/editing.mjs
3import assert from 'node:assert/strict';
4import fs from 'node:fs';
5const pw = await import(process.env.PLAYWRIGHT || 'playwright');
6const browser = await pw.chromium.launch({headless: true});
7const page = await browser.newPage();
8const root = new URL('../../internal/sheets/static/', import.meta.url);
9let cells, batches, fail;
10const errors = [];
11page.on('pageerror', e => errors.push(e.message));
12await page.route('https://sheets.test/**', async route => {
13 const req = route.request(), path = new URL(req.url()).pathname;
14 if (path === '/') return route.fulfill({contentType:'text/html', body:`
15 <link rel="stylesheet" href="/static/vendor/jspreadsheet.css">
16 <link rel="stylesheet" href="/static/sheets.css">
17 <style>td {font: 24px Arial} :root {--rst-line-strong: blue}</style>
18 <input data-formula-input><div data-sheet-surface data-sheet-id="1" data-tab-id="1"
19 data-rows="30" data-cols="30" data-may-write="1"><div data-sheet-grid tabindex="0"></div></div>
20 ${['vendor/jsuites.js','vendor/jspreadsheet.js','tsv.js','cellqueue.js','grid.js','formulabar.js','formulapick.js','clipboard.js'].map(x=>`<script src="/static/${x}"></script>`).join('')}`});
21 if (path.startsWith('/static/')) return route.fulfill({body:fs.readFileSync(new URL(path.slice(8),root)),
22 contentType:path.endsWith('.css')?'text/css':'application/javascript'});
23 if (req.method() === 'POST') {
24 const data=req.postDataJSON();
25 const jobs=data.cells || [data];
26 batches.push(jobs);
27 if(fail) return route.fulfill({status:400,body:'rejected'});
28 jobs.forEach(c=>cells.set(`${c.r},${c.c}`, {...c,display:c.raw}));
29 }
30 return route.fulfill({json:{cells:[...cells.values()],version:batches.length+1}});
31});
32async function reset(seed=[]) {
33 cells=new Map(seed.map(([r,c,raw])=>[`${r},${c}`,{r,c,raw,display:raw}]));batches=[];fail=false;
34 await page.goto('https://sheets.test/');
35 await page.waitForFunction(()=>window.SheetsGrid?.worksheet());
36}
37const td=(r,c)=>page.locator(`td[data-x="${c}"][data-y="${r}"]`);
38async function select(r,c,r2=r,c2=c) {
39 await page.evaluate(([r,c,r2,c2])=>{SheetsGrid.worksheet().updateSelectionFromCoords(c,r,c2,r2);SheetsGrid.focus();},[r,c,r2,c2]);
40}
41async function clip(type,text) {
42 return page.evaluate(([type,text])=>{
43 const data=new DataTransfer();if(text!==undefined)data.setData('text/plain',text);
44 document.activeElement.dispatchEvent(new ClipboardEvent(type,{bubbles:true,cancelable:true,clipboardData:data}));
45 return data.getData('text/plain');
46 },[type,text]);
47}
48async function settled() {await page.evaluate(()=>SheetsCellQueue([],SheetsGrid.post));}
49try {
50 await reset([[0,0,'one'],[1,0,'two']]);await select(0,0,1,0);
51 const text=await clip('cut');await settled();assert.equal(batches.length,0);
52 assert.equal(cells.get('0,0').raw,'one');
53 await select(1,0);await clip('paste',text);await settled();
54 assert.equal(batches.length,1);assert.equal(cells.get('0,0').raw,'');
55 assert.equal(cells.get('1,0').raw,'one');assert.equal(cells.get('2,0').raw,'two');
56 assert.equal(new Set(batches[0].map(c=>`${c.r},${c.c}`)).size,batches[0].length);
57 console.log('ok deferred cut and overlapping atomic move');
58 await reset([[0,0,'=B1']]);await select(0,0);const formula=await clip('cut');
59 await select(1,0);fail=true;await clip('paste',formula);await settled();
60 assert.equal(cells.get('0,0').raw,'=B1');fail=false;
61 await clip('paste',formula);await settled();assert.equal(cells.get('0,0').raw,'');
62 assert.equal(cells.get('1,0').raw,'=B1');assert.ok(!('fromR' in batches.at(-1)[0]));
63 console.log('ok rejected paste preserves cut for retry; moved formulas stay verbatim');
64 await reset([[0,0,'hello']]);await select(0,0);await clip('cut');
65 await page.keyboard.press('Escape');await select(1,0);await clip('paste','hello');await settled();
66 assert.equal(cells.get('0,0').raw,'hello');
67 await select(0,0);await clip('cut');await page.locator('[data-formula-input]').focus();
68 await clip('copy');await select(2,0);await clip('paste','hello');await settled();
69 assert.equal(cells.get('0,0').raw,'hello');console.log('ok Escape and text-field copy cancel pending cuts');
70 await reset([[0,0,'one']]);await select(0,0);await clip('cut');
71 cells.set('0,0',{r:0,c:0,raw:'changed',display:'changed'});
72 await page.evaluate(()=>SheetsGrid.refetch());await select(1,0);await clip('paste','one');await settled();
73 assert.equal(batches.length,0);console.log('ok changed source rejects stale cut');
74 await reset([[0,0,'one'],[1,0,'two']]);await select(0,0,1,0);await clip('cut');
75 await select(29,0);await clip('paste','one\ntwo');await settled();assert.equal(batches.length,0);
76 console.log('ok out-of-bounds move keeps source');
77 await reset([[0,0,'Room three']]);await td(0,0).dblclick();await page.evaluate(()=>new Promise(resolve=>requestAnimationFrame(()=>requestAnimationFrame(resolve))));
78 const input=td(0,0).locator('input,textarea');await input.waitFor();
79 const fonts=await input.evaluate(el=>[getComputedStyle(el).fontSize,getComputedStyle(el.parentElement).fontSize]);
80 assert.equal(fonts[0],fonts[1]);console.log('ok editor font matches cell');
81 await input.fill('=SUM(');
82 const a=await td(0,1).boundingBox(),b=await td(2,2).boundingBox();
83 await page.mouse.move(a.x+10,a.y+10);await page.mouse.down();
84 await page.mouse.move(b.x+10,b.y+10);await page.mouse.up();
85 assert.equal(await input.inputValue(),'=SUM(B1:C3)'.slice(0,-1));
86 assert.equal(batches.length,0);await input.press('End');await input.type(')');await input.press('Enter');await page.waitForFunction(()=>SheetsGrid.rawAt(0,0)==='=SUM(B1:C3)');
87 assert.equal(cells.get('0,0').raw,'=SUM(B1:C3)');console.log('ok in-cell formula range drag and commit');
88 await select(3,0);const bar=page.locator('[data-formula-input]');await bar.fill('=SUM(');
89 await td(0,1).click();assert.equal(await bar.inputValue(),'=SUM(B1');
90 await bar.type(')');assert.equal(await bar.inputValue(),'=SUM(B1)');await bar.press('Enter');await page.waitForFunction(()=>SheetsGrid.rawAt(3,0)==='=SUM(B1)');assert.equal(cells.get('3,0').raw,'=SUM(B1)');
91 console.log('ok formula bar inserts cell reference and commits to original cell');
92 await reset([[0,0,'=SUM(B1:B3)']]);await td(0,0).dblclick();await page.evaluate(()=>new Promise(resolve=>requestAnimationFrame(()=>requestAnimationFrame(resolve))));
93 await td(0,0).locator('input,textarea').waitFor();
94 await page.waitForFunction(()=>document.activeElement.selectionStart===0);
95 await td(0,1).click();assert.equal(cells.get('0,0').raw,'=SUM(B1:B3)');
96 console.log('ok clicking away from a selected existing formula preserves it');
97 await reset([[0,0,'original']]);await td(0,0).dblclick();await page.evaluate(()=>new Promise(resolve=>requestAnimationFrame(()=>requestAnimationFrame(resolve))));
98 const cancel=td(0,0).locator('input,textarea');
99 await page.waitForFunction(()=>document.activeElement.value==='original' && document.activeElement.selectionEnd===8);
100 await cancel.fill('=SUM(');
101 await td(0,1).click();await cancel.press('Escape');
102 assert.equal(cells.get('0,0').raw,'original');assert.equal(batches.length,0);
103 console.log('ok Escape discards formula reference edit');
104 assert.deepEqual(errors,[]);
105} finally {await browser.close();}
106