diff --git a/README.md b/README.md
index 489e085..7ab059e 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,7 @@
[](#build--distribution)
[](https://www.electronjs.org/)
[](https://github.com/CaYatur/SoundVisualizer/releases)
-[](#tests)
+[](#tests)
[](https://cayadev.com)
@@ -1341,7 +1341,7 @@ npm test
npm start -- --smoke
```
-**2543 unit tests, all passing.** They are written to check answers, not to exercise lines:
+**2547 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
diff --git a/README.tr.md b/README.tr.md
index 890fb7f..2f6297d 100644
--- a/README.tr.md
+++ b/README.tr.md
@@ -12,7 +12,7 @@
[](#paketleme--dağıtım)
[](https://www.electronjs.org/)
[](https://github.com/CaYatur/SoundVisualizer/releases)
-[](#testler)
+[](#testler)
[](https://cayadev.com)
@@ -1341,7 +1341,7 @@ npm test
npm start -- --smoke
```
-**2543 birim testi, hepsi geçiyor.** Satır çalıştırmak için değil, cevap denetlemek için yazıldılar:
+**2547 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ı
diff --git a/ROADMAP.md b/ROADMAP.md
index 25ebfa0..aebef4e 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -129,7 +129,7 @@ npm test
npm start -- --smoke
```
-- **2543 unit tests, all passing** on `main`. 703 of those shipped in v3.1.0;
+- **2547 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 878
on `main` since.
diff --git a/src/admin/admin.css b/src/admin/admin.css
index b056eb7..c9a11fb 100644
--- a/src/admin/admin.css
+++ b/src/admin/admin.css
@@ -828,11 +828,6 @@ select:focus { border-color: var(--accent); }
}
.footer-end { display: flex; align-items: center; gap: 12px; margin-left: auto; min-width: 0; }
.footer .hint { font-size: 11px; line-height: 1.35; color: var(--muted2); text-align: right; }
-#updatesBtn.has-update {
- color: var(--ok);
- border-color: var(--ok);
- background: rgba(67, 217, 138, .14);
-}
kbd { background: var(--card2); border: 1px solid var(--line); border-radius: 5px; padding: 1px 6px; font-size: 10.5px; }
/* scrollbar */
@@ -842,32 +837,6 @@ kbd { background: var(--card2); border: 1px solid var(--line); border-radius: 5p
::-webkit-scrollbar-track { background: transparent; }
/* Settings is the left-rail Ayarlar category; Updates is a footer modal. */
-.upd-backdrop {
- position: fixed; inset: 0; z-index: 410; display: grid; place-items: center;
- background: rgba(3, 3, 8, .62); backdrop-filter: blur(3px);
- animation: askIn .12s ease-out;
- padding: 20px;
-}
-.upd-backdrop.hidden { display: none; }
-.upd-modal {
- width: min(520px, calc(100vw - 40px)); max-height: min(820px, calc(100vh - 40px));
- overflow: auto; padding: 18px 18px 16px;
- border: 1px solid var(--line2); border-radius: 14px;
- background: var(--card); box-shadow: 0 28px 80px rgba(0, 0, 0, .6);
-}
-.upd-modal-head {
- display: flex; align-items: center; justify-content: space-between; gap: 12px;
- margin-bottom: 14px;
-}
-.upd-modal-head h2 { font-size: 16px; font-weight: 700; }
-.upd-modal-close {
- width: 30px; height: 30px; border-radius: 8px; border: 1px solid var(--line);
- background: transparent; color: var(--muted); font-size: 18px; line-height: 1;
- cursor: pointer;
-}
-.upd-modal-close:hover { background: var(--card2); color: var(--text); }
-.upd-modal-body .ctrl { margin-bottom: 12px; }
-.upd-modal-body .upd-panel { margin-top: 4px; }
/* ================= UYGULAMA İÇİ ONAY + BİLDİRİM =================
Sistem confirm/alert pencereleri renderer'ı kilitliyor ve çok ekranlı
diff --git a/src/admin/admin.js b/src/admin/admin.js
index 02a94bd..fa5ea4c 100644
--- a/src/admin/admin.js
+++ b/src/admin/admin.js
@@ -2412,7 +2412,23 @@
},
{
id: 'templates',
- roots: ['visualizer', 'background', 'postfx'],
+ /* Badge/reset must cover every SCENE_KEYS field apply() touches.
+ Partial roots left modulation/layers/geometry dirty and — with
+ layerStack on — syncStackState re-cleared classic visualizer to
+ 'none' after a partial reset, so the badge bounced back alone. */
+ roots: (window.SVTemplates && window.SVTemplates.SCENE_KEYS
+ ? window.SVTemplates.SCENE_KEYS.slice()
+ : ['background', 'visualizer', 'geometry', 'postfx', 'layers', 'layerStack', 'logo',
+ 'modulation', 'transition', 'custom', 'milkdrop', 'images', 'feedback']),
+ /* Same preserves as templates.apply / resetScene — user setup, not scene. */
+ rootOmit: [
+ 'background.transparent', 'background.transparentKey', 'background.coverTaskbar',
+ 'logo.src', 'logo.libraryId', 'logo.kind', 'logo.source', 'logo.enabled',
+ ],
+ /* TEMP: hide card modified badge + circular reset on Ready Templates.
+ Applying a template still changes SCENE_KEYS; re-enable by setting
+ hideCardReset: false once resetScene bounce is fully validated UX-wise. */
+ hideCardReset: true,
category: 'library',
icon: 'sparkles',
wide: true,
@@ -2895,7 +2911,7 @@
},
{
/* Uygulama: Dil / Pencere / Panel. Güncellemeler ayrı tam genişlik
- kartta (settings-updates) ve footer indirme düğmesi modalında. */
+ kartta (settings-updates). Footer indirme modalı kaldırıldı. */
id: 'settings-main',
category: 'settings',
wide: true,
@@ -3077,6 +3093,11 @@
return out;
}
+ function sectionShowsResetChrome(sec) {
+ /* hideCardReset: TEMP gate for Ready Templates card chrome. */
+ return !(sec && sec.hideCardReset);
+ }
+
function countModified(paths) {
return paths.filter(isModified).length;
}
@@ -3086,6 +3107,7 @@
sectionSchema().forEach((sec) => {
if (sec.category !== catId) return;
if (sec.show && !sec.show()) return;
+ if (!sectionShowsResetChrome(sec)) return;
n += countModified(sectionPaths(sec));
});
return n;
@@ -3169,7 +3191,7 @@
root.classList.toggle('single', sections.length === 1 || sections.every((s) => s.wide));
// Sıfırlanacak bir şey yoksa (ör. Kitaplık) düğme boşuna durmasın
- const resettable = sections.some((s) => countModified(sectionPaths(s)) > 0);
+ const resettable = sections.some((s) => sectionShowsResetChrome(s) && countModified(sectionPaths(s)) > 0);
$('catResetBtn').classList.toggle('hidden', !resettable);
sections.forEach((sec) => {
@@ -3245,7 +3267,7 @@
const tail = visible.filter((d) => d.tail);
const showAdvanced = advancedOn;
- const modCount = countModified(sectionPaths(sec));
+ const modCount = sectionShowsResetChrome(sec) ? countModified(sectionPaths(sec)) : 0;
const head = el('div', { class: 'card-head' }, [
el('span', { class: 'ico', icon: sec.icon }),
el('div', { class: 'ch-main' }, [
@@ -3369,7 +3391,7 @@
sections.forEach((sec, i) => {
const card = cards[i];
if (!card) return;
- const n = countModified(sectionPaths(sec));
+ const n = sectionShowsResetChrome(sec) ? countModified(sectionPaths(sec)) : 0;
let chip = card.querySelector('.chip-mod');
const acts = card.querySelector('.ch-actions');
if (n > 0 && !chip && acts) {
@@ -3393,7 +3415,7 @@
});
renderNav();
- const resettable = sections.some((s) => countModified(sectionPaths(s)) > 0);
+ const resettable = sections.some((s) => sectionShowsResetChrome(s) && countModified(sectionPaths(s)) > 0);
const catBtn = $('catResetBtn');
if (catBtn) catBtn.classList.toggle('hidden', !resettable);
}
@@ -3407,15 +3429,29 @@
}
async function resetSection(sec) {
+ if (sec && sec.hideCardReset) return;
const paths = sectionPaths(sec);
if (!paths.length) return;
const ok = await svConfirm('Bu bölümdeki ayarlar varsayılana dönecek.', { danger: true, okText: 'Bölümü sıfırla' });
if (!ok) return;
- const defaults = window.SV.defaultConfig();
- paths.forEach((p) => {
- const dv = getPath(defaults, p);
- if (dv !== undefined) setPath(cfg, p, window.SV.clone(dv));
- });
+ /* Ready Templates: whole-object SCENE_KEYS restore (not leaf setPath).
+ Leaf reset missed array/object identity and left layerStack on, so
+ the next syncStackState re-dirtied visualizer.type back to 'none'. */
+ if (sec.id === 'templates' && window.SVTemplates && typeof window.SVTemplates.resetScene === 'function') {
+ window.SVTemplates.resetScene(cfg, {
+ defaultConfig: window.SV.defaultConfig,
+ clone: window.SV.clone,
+ });
+ if (window.SVTemplatePanel && typeof window.SVTemplatePanel.clearLastApplied === 'function') {
+ window.SVTemplatePanel.clearLastApplied();
+ }
+ } else {
+ const defaults = window.SV.defaultConfig();
+ paths.forEach((p) => {
+ const dv = getPath(defaults, p);
+ if (dv !== undefined) setPath(cfg, p, window.SV.clone(dv));
+ });
+ }
push(true);
render();
}
@@ -3593,6 +3629,7 @@
sectionSchema()
.filter((s) => s.category === catId && (!s.show || s.show()))
.forEach((sec) => {
+ if (!sectionShowsResetChrome(sec)) return;
sectionPaths(sec).forEach((p) => {
const dv = getPath(defaults, p);
if (dv !== undefined) setPath(cfg, p, window.SV.clone(dv));
diff --git a/src/admin/index.html b/src/admin/index.html
index 3bcb394..ea18483 100644
--- a/src/admin/index.html
+++ b/src/admin/index.html
@@ -205,22 +205,11 @@
Sahneler
-
-
-
-
-
Güncellemeler
-
-
-
-
-
-
+
diff --git a/src/admin/template-panel.js b/src/admin/template-panel.js
index 6689726..2901c8b 100644
--- a/src/admin/template-panel.js
+++ b/src/admin/template-panel.js
@@ -153,5 +153,8 @@
P().toast('"' + t.name + '" uygulandı.');
}
- window.SVTemplatePanel = { panel };
+ window.SVTemplatePanel = {
+ panel,
+ clearLastApplied() { lastApplied = ''; },
+ };
})();
diff --git a/src/admin/updates-panel.js b/src/admin/updates-panel.js
index dbb5c3a..291c3f4 100644
--- a/src/admin/updates-panel.js
+++ b/src/admin/updates-panel.js
@@ -1,10 +1,8 @@
'use strict';
/* Güncellemeler (#640).
- Ana süreç denetler (src/main/updater.js) ve durumu gönderir. Aynı içerik
- Settings’teki tam genişlik Güncellemeler kartında ve footer indirme
- düğmesinden açılan modalda kullanılır (panel() ortak kaynak).
- Sürüm notları yalnız METİN; adresler ana süreçte açılır.
+ Ana süreç denetler (src/main/updater.js) ve durumu gönderir. İçerik Settings’teki tam genişlik Güncellemeler kartında gösterilir (panel()).
+ Sürüm notları yalnız METİN; adresler ana süreçte açılır. Footer indirme modalı kaldırıldı.
Yeni sürüm ilk görüldüğünde (oturum başına sürüm başına bir kez) toast
çıkar; modal kapalı olsa da kullanıcı haberdar olur. */
@@ -33,7 +31,8 @@
dev: 'Geliştirme kopyası: yalnız denetim yapılır; otomatik denetim kapalı.',
};
- function fmtDate(iso) {
+
+function fmtDate(iso) {
const d = new Date(iso);
if (isNaN(d.getTime())) return '';
try {
@@ -43,7 +42,7 @@
}
}
- function statusText(s) {
+function statusText(s) {
if (!s || s.status === 'idle') return s && s.auto ? 'Henüz denetlenmedi.' : 'Otomatik denetim bu kopyada kapalı; elle denetleyebilirsiniz.';
if (s.status === 'checking') return 'Denetleniyor…';
if (s.status === 'latest') return 'Güncel: en yeni sürüm kurulu.';
@@ -55,18 +54,17 @@
return '';
}
- function onStatus(s) {
+function onStatus(s) {
state = s;
if (s && s.status === 'available' && !s.skipped && !toasted.has(s.latest) && P() && P().toast) {
toasted.add(s.latest);
P().toast(tt('Yeni sürüm:') + ' v' + s.latest + ' — ' + tt('Güncellemeler'), 'ok');
}
refresh();
- syncFooterBtn();
}
- function refresh() {
- /* Kart ve modal aynı anda .upd-panel tutabilir; hepsini yenile. */
+function refresh() {
+ /* Settings kartında birden fazla .upd-panel tutabilir; hepsini yenile. */
if (typeof document !== 'undefined') {
const nodes = Array.from(document.querySelectorAll('.upd-panel'));
if (nodes.length) {
@@ -81,70 +79,7 @@
box.replaceWith(buildPanel());
}
- function syncFooterBtn() {
- if (typeof document === 'undefined') return;
- const btn = document.getElementById('updatesBtn');
- if (!btn) return;
- const hot = !!(state && state.status === 'available' && !state.skipped);
- btn.classList.toggle('has-update', hot);
- }
-
- function modeSelect() {
- const p = P();
- if (!p || !p.el || !p.cfg) return null;
- const el = p.el;
- const cfg = p.cfg();
- if (!cfg.updates) cfg.updates = { mode: 'notify' };
- const sel = el('select', {
- onchange: (e) => {
- cfg.updates.mode = e.target.value;
- if (p.push) p.push(true);
- },
- });
- [
- ['notify', 'Açık — yeni sürümü haber ver'],
- ['auto', 'Otomatik — indir ve kapanırken kur'],
- ['off', 'Kapalı'],
- ].forEach(([v, t]) => {
- const o = el('option', { value: v, text: tt(t) });
- if (String(cfg.updates.mode || 'notify') === v) o.selected = true;
- sel.appendChild(o);
- });
- return p.row(tt('Güncellemeleri Denetle'), sel);
- }
-
- function fillModalBody() {
- if (typeof document === 'undefined') return;
- const body = document.getElementById('updatesModalBody');
- if (!body || !P() || !P().el) return;
- body.textContent = '';
- const mode = modeSelect();
- if (mode) body.appendChild(mode);
- body.appendChild(panel());
- }
-
- function openModal() {
- if (typeof document === 'undefined') return;
- const backdrop = document.getElementById('updatesBackdrop');
- if (!backdrop) return;
- fillModalBody();
- backdrop.classList.remove('hidden');
- backdrop.setAttribute('aria-hidden', 'false');
- const closeBtn = document.getElementById('updatesClose');
- if (closeBtn) closeBtn.focus();
- }
-
- function closeModal() {
- if (typeof document === 'undefined') return;
- const backdrop = document.getElementById('updatesBackdrop');
- if (!backdrop) return;
- backdrop.classList.add('hidden');
- backdrop.setAttribute('aria-hidden', 'true');
- const btn = document.getElementById('updatesBtn');
- if (btn) btn.focus();
- }
-
- function buildPanel() {
+function buildPanel() {
const p = P();
const el = p.el;
const s = state || { status: 'idle' };
@@ -214,39 +149,17 @@
return root;
}
- function panel() {
+function panel() {
return buildPanel();
}
- function wireModalChrome() {
- if (typeof document === 'undefined') return;
- const btn = document.getElementById('updatesBtn');
- const backdrop = document.getElementById('updatesBackdrop');
- const closeBtn = document.getElementById('updatesClose');
- if (btn) btn.addEventListener('click', openModal);
- if (closeBtn) closeBtn.addEventListener('click', closeModal);
- if (backdrop) {
- backdrop.addEventListener('click', (e) => {
- if (e.target === backdrop) closeModal();
- });
- }
- document.addEventListener('keydown', (e) => {
- if (e.key === 'Escape' && backdrop && !backdrop.classList.contains('hidden')) {
- e.preventDefault();
- closeModal();
- }
- });
- }
-
- function init() {
- wireModalChrome();
- syncFooterBtn();
+function init() {
if (!window.api || !window.api.onUpdatesStatus) return;
window.api.onUpdatesStatus(onStatus);
if (window.api.updatesState) window.api.updatesState().then(onStatus).catch(() => {});
}
- window.SVUpdatesPanel = { panel, init, openModal, closeModal, _state: () => state, _onStatus: onStatus };
+ window.SVUpdatesPanel = { panel, init, _state: () => state, _onStatus: onStatus };
if (typeof document !== 'undefined') {
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
else init();
diff --git a/src/shared/templates.js b/src/shared/templates.js
index f6d9e55..341d38c 100644
--- a/src/shared/templates.js
+++ b/src/shared/templates.js
@@ -608,6 +608,42 @@
return null;
}
+ /* Factory reset for Ready Templates card.
+
+ Mirrors apply()'s SCENE_KEYS wipe + user-setup preserves, without
+ merging a template patch. Keeps audio/display/stream/lighting intact
+ and restores logo identity + transparent-window flags the same way
+ apply() does, so the card badge can clear permanently. */
+ function resetScene(cfg, env) {
+ if (!cfg || !env) return cfg;
+ const { defaultConfig, clone } = env;
+ const def = defaultConfig();
+ const keepTransparent = !!(cfg.background && cfg.background.transparent);
+ const keepKey = cfg.background && cfg.background.transparentKey;
+ const keepCover = cfg.background && cfg.background.coverTaskbar;
+ const logoSrc = (cfg.logo && cfg.logo.src) || null;
+ const logoLibraryId = (cfg.logo && cfg.logo.libraryId) || null;
+ const logoKind = (cfg.logo && cfg.logo.kind) || null;
+ const logoSource = (cfg.logo && cfg.logo.source) || null;
+ const logoEnabled = cfg.logo && typeof cfg.logo.enabled === 'boolean' ? cfg.logo.enabled : null;
+ for (const k of SCENE_KEYS) {
+ if (def[k] !== undefined) cfg[k] = clone(def[k]);
+ }
+ if (cfg.logo) {
+ if (logoSrc) cfg.logo.src = logoSrc;
+ if (logoLibraryId) cfg.logo.libraryId = logoLibraryId;
+ if (logoKind) cfg.logo.kind = logoKind;
+ if (logoSource) cfg.logo.source = logoSource;
+ if (logoEnabled !== null) cfg.logo.enabled = logoEnabled;
+ }
+ if (cfg.background) {
+ cfg.background.transparent = keepTransparent;
+ if (keepKey != null) cfg.background.transparentKey = keepKey;
+ if (keepCover != null) cfg.background.coverTaskbar = !!keepCover;
+ }
+ return cfg;
+ }
+
function apply(cfg, tpl, env) {
if (!cfg || !tpl || !env) return cfg;
const { defaultConfig, deepMerge, clone } = env;
@@ -712,7 +748,7 @@
return out;
}
- const api = { TEMPLATES, apply, groups };
+ const api = { TEMPLATES, SCENE_KEYS, apply, resetScene, groups };
if (typeof module !== 'undefined' && module.exports) module.exports = api;
if (typeof window !== 'undefined') window.SVTemplates = api;
})();
diff --git a/tests/admin-templates-reset.test.js b/tests/admin-templates-reset.test.js
new file mode 100644
index 0000000..1a15202
--- /dev/null
+++ b/tests/admin-templates-reset.test.js
@@ -0,0 +1,31 @@
+'use strict';
+/* Ready Templates: card modified badge + reset are TEMP hidden (hideCardReset).
+ * SCENE_KEYS alignment + resetScene remain so chrome can be re-enabled later. */
+const test = require('node:test');
+const assert = require('node:assert');
+const fs = require('fs');
+const path = require('path');
+
+const admin = fs.readFileSync(path.join(__dirname, '..', 'src', 'admin', 'admin.js'), 'utf8');
+const templates = fs.readFileSync(path.join(__dirname, '..', 'src', 'shared', 'templates.js'), 'utf8');
+const panel = fs.readFileSync(path.join(__dirname, '..', 'src', 'admin', 'template-panel.js'), 'utf8');
+
+test('templates section hides card reset chrome via hideCardReset flag', () => {
+ const start = admin.indexOf("id: 'templates'");
+ assert.ok(start > 0);
+ const slice = admin.slice(start, start + 1400);
+ assert.match(slice, /hideCardReset:\s*true/);
+ assert.match(admin, /function sectionShowsResetChrome/);
+ assert.match(admin, /sectionShowsResetChrome\(sec\)/);
+});
+
+test('templates roots still follow SCENE_KEYS for easy re-enable', () => {
+ const start = admin.indexOf("id: 'templates'");
+ const slice = admin.slice(start, start + 1400);
+ assert.match(slice, /SVTemplates\.SCENE_KEYS/);
+ assert.match(slice, /rootOmit:/);
+ assert.match(templates, /function resetScene/);
+ assert.match(templates, /SCENE_KEYS, apply, resetScene/);
+ assert.match(admin, /SVTemplates\.resetScene/);
+ assert.match(panel, /clearLastApplied/);
+});
diff --git a/tests/settings-updates-library-scroll.test.js b/tests/settings-updates-library-scroll.test.js
index eaab6db..b9939b2 100644
--- a/tests/settings-updates-library-scroll.test.js
+++ b/tests/settings-updates-library-scroll.test.js
@@ -1,6 +1,6 @@
'use strict';
/* Settings: Uygulama card (Dil/Pencere/Panel) + separate full-width Updates card.
- * Footer download icon opens the same Updates UI as a modal. */
+ * Updates live only on the Settings full-width card (no footer modal). */
const test = require('node:test');
const assert = require('node:assert');
const fs = require('fs');
@@ -34,17 +34,18 @@ test('Settings: separate full-width Updates card', () => {
assert.doesNotMatch(admin, /id:\s*'updates',\s*\n\s*category:\s*'library'/);
});
-test('Updates modal from footer download button', () => {
- assert.match(html, /id="updatesBtn"/);
- assert.match(html, /id="updatesBackdrop"/);
- assert.match(html, /id="updatesModalBody"/);
- assert.match(updatesPanel, /function openModal/);
- assert.match(updatesPanel, /function closeModal/);
- assert.match(updatesPanel, /fillModalBody/);
+test('Updates footer modal removed; Settings card panel remains', () => {
+ assert.doesNotMatch(html, /id="updatesBtn"/);
+ assert.doesNotMatch(html, /id="updatesBackdrop"/);
+ assert.doesNotMatch(html, /id="updatesModalBody"/);
+ assert.doesNotMatch(updatesPanel, /function openModal/);
+ assert.doesNotMatch(updatesPanel, /function closeModal/);
+ assert.doesNotMatch(updatesPanel, /fillModalBody/);
+ assert.doesNotMatch(updatesPanel, /updatesBtn/);
assert.match(updatesPanel, /function buildPanel/);
- assert.match(css, /\.upd-backdrop/);
- assert.match(css, /\.upd-modal/);
- assert.doesNotMatch(updatesPanel, /Ayarlar › Güncellemeler|Kitaplık › Güncellemeler/);
+ assert.match(updatesPanel, /function panel/);
+ assert.doesNotMatch(css, /\.upd-backdrop/);
+ assert.doesNotMatch(css, /#updatesBtn/);
});
test('Settings groups have distinct block styles with theme tokens', () => {
diff --git a/tests/templates.test.js b/tests/templates.test.js
index 6910359..d48b5bc 100644
--- a/tests/templates.test.js
+++ b/tests/templates.test.js
@@ -195,3 +195,42 @@ test('her şablon uygulandığında tam ve geçerli bir yapılandırma verir', (
assert.strictEqual(outCustom.layerStack.enabled, false, 'şablondaki açık layerStack tercihi uygulanmalı');
});
+
+
+test('resetScene fabrika sahnesine döner ve kurulum/korunan alanlara dokunmaz', () => {
+ const cfg = SV.defaultConfig();
+ cfg.audio.sensitivity = 2.5;
+ cfg.background.transparent = true;
+ cfg.background.coverTaskbar = true;
+ cfg.logo.enabled = true;
+ cfg.logo.src = 'data:image/png;base64,abc';
+ cfg.layerStack.enabled = true;
+
+ const tpl = T.TEMPLATES.find((t) => t.id === 'club-strobe');
+ const out = T.apply(cfg, tpl, env);
+ assert.notStrictEqual(out.visualizer.type, SV.defaultConfig().visualizer.type);
+ assert.ok((out.postfx || []).length > 0 || (out.modulation.routes || []).length > 0);
+
+ T.resetScene(out, env);
+
+ const def = SV.defaultConfig();
+ assert.strictEqual(out.visualizer.type, def.visualizer.type);
+ assert.deepStrictEqual(out.postfx, def.postfx);
+ assert.deepStrictEqual(out.modulation.routes, def.modulation.routes);
+ assert.deepStrictEqual(out.layers, def.layers);
+ assert.strictEqual(out.layerStack.enabled, def.layerStack.enabled);
+ assert.strictEqual(out.audio.sensitivity, 2.5, 'kurulum bozulmamalı');
+ assert.strictEqual(out.background.transparent, true, 'transparent korunmalı');
+ assert.strictEqual(out.background.coverTaskbar, true, 'coverTaskbar korunmalı');
+ assert.strictEqual(out.logo.enabled, true, 'logo.enabled korunmalı');
+ assert.strictEqual(out.logo.src, 'data:image/png;base64,abc', 'logo.src korunmalı');
+});
+
+test('SCENE_KEYS dışa aktarılır ve apply ile aynıdır', () => {
+ assert.ok(Array.isArray(T.SCENE_KEYS));
+ assert.ok(T.SCENE_KEYS.includes('visualizer'));
+ assert.ok(T.SCENE_KEYS.includes('modulation'));
+ assert.ok(T.SCENE_KEYS.includes('layers'));
+ assert.ok(T.SCENE_KEYS.includes('layerStack'));
+ assert.ok(typeof T.resetScene === 'function');
+});