Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
[![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux-111997.svg)](#build--distribution)
[![Electron](https://img.shields.io/badge/Electron-43-47848F.svg)](https://www.electronjs.org/)
[![Downloads](https://img.shields.io/github/downloads/CaYatur/SoundVisualizer/total?label=downloads)](https://github.com/CaYatur/SoundVisualizer/releases)
[![Tests](https://img.shields.io/badge/tests-2197%20passing-2ea043.svg)](#tests)
[![Tests](https://img.shields.io/badge/tests-2202%20passing-2ea043.svg)](#tests)
[![cayadev.com](https://img.shields.io/badge/cayadev.com-e11d2a.svg)](https://cayadev.com)

</div>
Expand Down Expand Up @@ -1315,7 +1315,7 @@ npm test
npm start -- --smoke
```

**2197 unit tests, all passing.** They are written to check answers, not to exercise lines:
**2202 unit tests, all passing.** They are written to check answers, not to exercise lines:

- **Formulas** are checked against values derived by hand from their definitions — Viviani's curve
staying on its sphere, the torus tube radius, Chladni's m↔n antisymmetry, every attractor
Expand Down
4 changes: 2 additions & 2 deletions README.tr.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
[![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux-111997.svg)](#paketleme--dağıtım)
[![Electron](https://img.shields.io/badge/Electron-43-47848F.svg)](https://www.electronjs.org/)
[![İndirme](https://img.shields.io/github/downloads/CaYatur/SoundVisualizer/total?label=indirme)](https://github.com/CaYatur/SoundVisualizer/releases)
[![Test](https://img.shields.io/badge/test-2197%20geçiyor-2ea043.svg)](#testler)
[![Test](https://img.shields.io/badge/test-2202%20geçiyor-2ea043.svg)](#testler)
[![cayadev.com](https://img.shields.io/badge/cayadev.com-e11d2a.svg)](https://cayadev.com)

</div>
Expand Down Expand Up @@ -1313,7 +1313,7 @@ npm test
npm start -- --smoke
```

**2197 birim testi, hepsi geçiyor.** Satır çalıştırmak için değil, cevap denetlemek için yazıldılar:
**2202 birim testi, hepsi geçiyor.** Satır çalıştırmak için değil, cevap denetlemek için yazıldılar:

- **Formüller**, tanımlarından elle türetilmiş değerlerle sınanıyor — Viviani eğrisinin küre
üzerinde kalması, simidin boru yarıçapı, Chladni'nin m↔n antisimetrisi, her çekicinin sınırlı
Expand Down
2 changes: 1 addition & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ npm test
npm start -- --smoke
```

- **2197 unit tests, all passing** on `main`. 703 of those shipped in v3.1.0;
- **2202 unit tests, all passing** on `main`. 703 of those shipped in v3.1.0;
105 came with v3.1.1; 163 came with v3.1.2; 157 came with v3.1.3 — 1128 at
that tag — 469 more with v3.1.4, most of them from the MilkDrop work, and 600
on `main` since.
Expand Down
158 changes: 158 additions & 0 deletions tests/admin-sections-scroll.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
'use strict';
/* #615 / #616: Admin #sections scroll, render() sonrası sıfırlanmamalı.
*
* DOM/Electron olmadan iki katman doğrulanır:
* 1) Kaynak sözleşmesi — admin.js içinde gen++, çift rAF, clamp, isConnected.
* 2) Davranış — restore algoritması sahte eleman + kontrollü rAF kuyruğu ile.
*/
const test = require('node:test');
const assert = require('node:assert');
const fs = require('fs');
const path = require('path');

const adminSrc = fs.readFileSync(
path.join(__dirname, '..', 'src', 'admin', 'admin.js'),
'utf8'
);

test('kaynak: #615 sections scroll restore deseni admin.js içinde', () => {
assert.match(adminSrc, /let sectionsScrollRestoreGen\s*=\s*0/);
assert.match(adminSrc, /const gen\s*=\s*\+\+sectionsScrollRestoreGen/);
assert.match(adminSrc, /const restoreSectionsScroll\s*=\s*\(\)\s*=>/);
assert.match(
adminSrc,
/if\s*\(\s*gen\s*!==\s*sectionsScrollRestoreGen\s*\|\|\s*!root\.isConnected\s*\)\s*return/
);
assert.match(
adminSrc,
/const max\s*=\s*Math\.max\(\s*0\s*,\s*root\.scrollHeight\s*-\s*root\.clientHeight\s*\)/
);
assert.match(adminSrc, /root\.scrollTop\s*=\s*Math\.min\(\s*prevScroll\s*,\s*max\s*\)/);
assert.match(
adminSrc,
/requestAnimationFrame\(\s*\(\)\s*=>\s*\{\s*restoreSectionsScroll\(\);\s*requestAnimationFrame\(restoreSectionsScroll\);\s*\}\)/
);
});

/** admin.js ile aynı restore algoritması; rAF enjekte edilebilir. */
function makeRestore(root, prevScroll, opts) {
const state = opts.state;
const raf = opts.raf;
const gen = ++state.gen;
const restoreSectionsScroll = () => {
if (gen !== state.gen || !root.isConnected) return;
const max = Math.max(0, root.scrollHeight - root.clientHeight);
root.scrollTop = Math.min(prevScroll, max);
};
restoreSectionsScroll();
raf(() => {
restoreSectionsScroll();
raf(restoreSectionsScroll);
});
return { gen, restoreSectionsScroll };
}

function fakeRoot(init) {
return {
scrollTop: init.scrollTop ?? 0,
scrollHeight: init.scrollHeight ?? 0,
clientHeight: init.clientHeight ?? 0,
isConnected: init.isConnected !== false,
};
}

function makeRafQueue() {
const q = [];
const raf = (fn) => {
q.push(fn);
return q.length;
};
const flushOne = () => {
assert.ok(q.length > 0, 'rAF kuyruğu boş');
const fn = q.shift();
fn();
};
const flushAll = () => {
while (q.length) flushOne();
};
return { raf, flushOne, flushAll, queue: q };
}

test('davranış: içerik yeniden kurulunca scroll korunur (yükseklik yeter)', () => {
const root = fakeRoot({ scrollTop: 240, scrollHeight: 800, clientHeight: 400 });
const prevScroll = root.scrollTop;
// rebuild: içerik temizlenip yeniden dolduruluyor; scrollTop sıfırlanabilir
root.scrollTop = 0;
root.scrollHeight = 800;
root.clientHeight = 400;

const state = { gen: 0 };
const { raf, flushAll } = makeRafQueue();
makeRestore(root, prevScroll, { state, raf });
assert.strictEqual(root.scrollTop, 240, 'sync restore hemen uygulamalı');
flushAll();
assert.strictEqual(root.scrollTop, 240, 'çift rAF sonrası scroll aynı kalmalı');
});

test('davranış: max küçülünce clamp edilir', () => {
const root = fakeRoot({ scrollTop: 500, scrollHeight: 1000, clientHeight: 400 });
const prevScroll = root.scrollTop;
root.scrollTop = 0;
// yeniden kurulum sonrası daha kısa içerik
root.scrollHeight = 500;
root.clientHeight = 400;
const max = Math.max(0, root.scrollHeight - root.clientHeight); // 100

const state = { gen: 0 };
const { raf, flushAll } = makeRafQueue();
makeRestore(root, prevScroll, { state, raf });
assert.strictEqual(root.scrollTop, max);
flushAll();
assert.strictEqual(root.scrollTop, 100);
});

test('davranış: sonraki render\'ın gen\'i eski rAF restore\'unu ezer', () => {
const root = fakeRoot({ scrollTop: 300, scrollHeight: 900, clientHeight: 400 });
const state = { gen: 0 };
const { raf, flushOne, queue } = makeRafQueue();

// ilk render: prev=300
makeRestore(root, 300, { state, raf });
assert.strictEqual(root.scrollTop, 300);
assert.strictEqual(queue.length, 1, 'ilk rAF planlandı');

// kullanıcı kaydırdı / ikinci render yeni prev ile
root.scrollTop = 50;
makeRestore(root, 50, { state, raf });
assert.strictEqual(root.scrollTop, 50);
assert.strictEqual(state.gen, 2);

// eski (gen=1) dış rAF çalışır — stale, dokunmamalı
flushOne();
assert.strictEqual(root.scrollTop, 50, 'eski gen scroll\'u ezmemeli');

// yeni render\'ın dış rAF'ı: restore + iç rAF
flushOne();
assert.strictEqual(root.scrollTop, 50);
// iç rAF
flushOne();
assert.strictEqual(root.scrollTop, 50);
// eski render\'ın iç rAF'ı (stale) kuyrukta kalmış olabilir
while (queue.length) flushOne();
assert.strictEqual(root.scrollTop, 50);
});

test('davranış: isConnected false iken restore atlanır', () => {
const root = fakeRoot({
scrollTop: 0,
scrollHeight: 800,
clientHeight: 400,
isConnected: false,
});
const state = { gen: 0 };
const { raf, flushAll } = makeRafQueue();
makeRestore(root, 200, { state, raf });
assert.strictEqual(root.scrollTop, 0, 'bağlı değilken sync restore yok');
flushAll();
assert.strictEqual(root.scrollTop, 0, 'bağlı değilken rAF restore yok');
});
Loading