From 1408c8345c8fbf10736e4db8ca2029a725428822 Mon Sep 17 00:00:00 2001 From: "Victor A." <52110451+cs50victor@users.noreply.github.com> Date: Thu, 19 Feb 2026 10:54:39 -0800 Subject: [PATCH 01/10] fix: validate font metadata outputs after generator success --- __tests__/generate-office-fonts-path.test.js | 94 ++++++++++++++++++++ package.json | 2 +- scripts/generate_office_fonts.js | 7 ++ 3 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 __tests__/generate-office-fonts-path.test.js diff --git a/__tests__/generate-office-fonts-path.test.js b/__tests__/generate-office-fonts-path.test.js new file mode 100644 index 0000000..1e8bd87 --- /dev/null +++ b/__tests__/generate-office-fonts-path.test.js @@ -0,0 +1,94 @@ +import { afterEach, describe, expect, test } from 'bun:test'; +import fs from 'fs'; +import os from 'os'; +import path from 'path'; +import { spawnSync } from 'child_process'; + +const SCRIPT_PATH = path.resolve(import.meta.dir, '..', 'scripts', 'generate_office_fonts.js'); + +const tempDirs = []; + +function makeTempDir(prefix) { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), prefix)); + tempDirs.push(dir); + return dir; +} + +function writeMockPreload(dir) { + const preloadPath = path.join(dir, 'mock-spawn.js'); + fs.writeFileSync( + preloadPath, + `const childProcess = require('child_process'); +const fs = require('fs'); +const path = require('path'); + +childProcess.spawnSync = (_bin, args) => { + const allFontsArg = args.find((arg) => arg.startsWith('--allfonts=')); + const selectionArg = args.find((arg) => arg.startsWith('--selection=')); + const allFontsPath = allFontsArg ? allFontsArg.slice('--allfonts='.length) : null; + const selectionPath = selectionArg ? selectionArg.slice('--selection='.length) : null; + + if (process.env.MOCK_WRITE_OUTPUTS === '1' && allFontsPath && selectionPath) { + fs.mkdirSync(path.dirname(allFontsPath), { recursive: true }); + fs.writeFileSync(allFontsPath, 'window.AllFonts = [];'); + fs.writeFileSync(selectionPath, Buffer.alloc(0)); + } + + return { status: Number(process.env.MOCK_EXIT_CODE || 0) }; +}; +` + ); + + return preloadPath; +} + +function runGenerator({ fontDataDir, writeOutputs }) { + const mockDir = makeTempDir('oo-editors-font-mock-'); + const preloadPath = writeMockPreload(mockDir); + + const env = { + ...process.env, + FONT_DATA_DIR: fontDataDir, + MOCK_EXIT_CODE: '0', + MOCK_WRITE_OUTPUTS: writeOutputs ? '1' : '0', + NODE_OPTIONS: [process.env.NODE_OPTIONS, `--require=${preloadPath}`] + .filter(Boolean) + .join(' '), + }; + + return spawnSync('node', [SCRIPT_PATH], { + env, + encoding: 'utf8', + }); +} + +afterEach(() => { + while (tempDirs.length > 0) { + const dir = tempDirs.pop(); + fs.rmSync(dir, { recursive: true, force: true }); + } +}); + +describe('generate_office_fonts path handling', () => { + test('supports non-ASCII FONT_DATA_DIR paths when outputs are written', () => { + const root = makeTempDir('oo-editors-fontdata-'); + const fontDataDir = path.join(root, 'C', 'Users', 'دانيال', 'AppData', 'Roaming', 'interpreter', 'office-extension-fontdata'); + + const result = runGenerator({ fontDataDir, writeOutputs: true }); + + expect(result.status).toBe(0); + expect(fs.existsSync(path.join(fontDataDir, 'AllFonts.js'))).toBe(true); + expect(fs.existsSync(path.join(fontDataDir, 'font_selection.bin'))).toBe(true); + }); + + test('fails when generator exits 0 but does not write metadata files', () => { + const root = makeTempDir('oo-editors-fontdata-'); + const fontDataDir = path.join(root, 'C', 'Users', 'دانيال', 'AppData', 'Roaming', 'interpreter', 'office-extension-fontdata'); + + const result = runGenerator({ fontDataDir, writeOutputs: false }); + + expect(result.status).toBe(1); + expect(result.stderr).toContain('font metadata files were not created'); + expect(fs.existsSync(path.join(fontDataDir, 'AllFonts.js'))).toBe(false); + }); +}); diff --git a/package.json b/package.json index 9a84eaf..87f3ce2 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "server": "cross-env FONT_DATA_DIR=assets/onlyoffice-fontdata node server.js", "test": "bun run test:unit && bun run test:e2e", "test:all": "bun run test:unit && bun run test:e2e", - "test:unit": "bun test __tests__/server-utils.test.js __tests__/desktop-stub-utils.test.js && node test-url-scheme.js", + "test:unit": "bun test __tests__/server-utils.test.js __tests__/desktop-stub-utils.test.js __tests__/generate-office-fonts-path.test.js && node test-url-scheme.js", "test:e2e": "bun run test:console-batch && bun run test:save", "test:console-batch": "node test-console-batch.js", "test:save": "bun test __tests__/save.test.js", diff --git a/scripts/generate_office_fonts.js b/scripts/generate_office_fonts.js index e51c529..5ab6c6b 100755 --- a/scripts/generate_office_fonts.js +++ b/scripts/generate_office_fonts.js @@ -85,4 +85,11 @@ if (result.status !== 0) { fail(`allfontsgen exited with code ${result.status || 1}`); } +const allFontsJs = path.join(OUTPUT_DIR, 'AllFonts.js'); +const fontSelectionBin = path.join(OUTPUT_DIR, 'font_selection.bin'); + +if (!fs.existsSync(allFontsJs) || !fs.existsSync(fontSelectionBin)) { + fail('allfontsgen exited successfully but font metadata files were not created'); +} + console.log('[generate_office_fonts] Font metadata updated.'); From 695b81648e2de2f949479b9cd6f3350944ddb9e4 Mon Sep 17 00:00:00 2001 From: "Victor A." <52110451+cs50victor@users.noreply.github.com> Date: Thu, 19 Feb 2026 11:28:16 -0800 Subject: [PATCH 02/10] fix: disable compact header in editor config --- editors/desktop-stub.js | 2 +- server.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/editors/desktop-stub.js b/editors/desktop-stub.js index fd99f8f..7c62d38 100644 --- a/editors/desktop-stub.js +++ b/editors/desktop-stub.js @@ -548,7 +548,7 @@ comments: false, help: false, hideRightMenu: false, - compactHeader: true + compactHeader: false }, mode: 'edit', canCoAuthoring: false, diff --git a/server.js b/server.js index 980c0d7..9312c06 100644 --- a/server.js +++ b/server.js @@ -987,7 +987,7 @@ app.get('/edit/:filename', (req, res) => { autosave: false, chat: false, comments: false, - compactHeader: true, + compactHeader: false, help: false } } From 06e7fe613783418649257984d22cee0f12bdddc7 Mon Sep 17 00:00:00 2001 From: "Victor A." <52110451+cs50victor@users.noreply.github.com> Date: Thu, 19 Feb 2026 12:08:47 -0800 Subject: [PATCH 03/10] fix: show branding logo in desktop offline editor --- editors/desktop-stub.js | 7 ++++- editors/offline-loader-proper.html | 50 +++++++++++++++++++++++++++++- server.js | 6 +++- 3 files changed, 60 insertions(+), 3 deletions(-) diff --git a/editors/desktop-stub.js b/editors/desktop-stub.js index 7c62d38..7eaa0d8 100644 --- a/editors/desktop-stub.js +++ b/editors/desktop-stub.js @@ -541,6 +541,7 @@ // Editor configuration - CRITICAL for fixing customization errors GetEditorConfig: function() { console.log('[BROWSER] GetEditorConfig called'); + var headerLogoUrl = window.location.origin + '/web-apps/apps/common/main/resources/img/header/header-logo_s.svg'; return JSON.stringify({ customization: { autosave: true, @@ -548,7 +549,11 @@ comments: false, help: false, hideRightMenu: false, - compactHeader: false + compactHeader: false, + logo: { + visible: true, + image: headerLogoUrl + } }, mode: 'edit', canCoAuthoring: false, diff --git a/editors/offline-loader-proper.html b/editors/offline-loader-proper.html index 87434de..ac5c4e3 100644 --- a/editors/offline-loader-proper.html +++ b/editors/offline-loader-proper.html @@ -174,6 +174,43 @@ // Run dialog dismissal every 2 seconds (much less aggressive) console.log('[DISMISS] Starting dialog dismissal checker'); setInterval(dismissDialogsOnce, 2000); + + function ensureDesktopLogoVisible() { + try { + var iframe = document.querySelector('iframe[id*="placeholder"]') || + document.querySelector('iframe[id*="frameEditor"]') || + document.querySelector('iframe'); + if (!iframe || !iframe.contentDocument) return; + + var iframeDoc = iframe.contentDocument; + var logoSrc = window.location.origin + '/web-apps/apps/common/main/resources/img/header/header-logo_s.svg'; + var logo = iframeDoc.getElementById('oo-desktop-logo'); + if (!logo) { + logo = iframeDoc.createElement('img'); + logo.id = 'oo-desktop-logo'; + logo.alt = 'ONLYOFFICE'; + logo.src = logoSrc; + } + + var host = iframeDoc.querySelector('#box-document-title .extra') || + iframeDoc.querySelector('.extra'); + + if (host) { + if (logo.parentNode !== host) { + host.insertBefore(logo, host.firstChild); + } + logo.style.cssText = 'height:20px;max-width:120px;margin:0 12px 0 0;display:block;pointer-events:none;'; + } else { + if (logo.parentNode !== iframeDoc.body) { + iframeDoc.body.appendChild(logo); + } + logo.style.cssText = 'position:fixed;top:12px;left:12px;z-index:2147483647;height:20px;max-width:120px;pointer-events:none;'; + } + } catch (e) { + // best-effort only + } + } + setInterval(ensureDesktopLogoVisible, 3000); // ======================================================================== // Parse URL parameters @@ -337,6 +374,7 @@ } function getEditorConfig(urlParams) { + var headerLogoUrl = window.location.origin + '/web-apps/apps/common/main/resources/img/header/header-logo_s.svg'; return { customization : { goback: { url: "onlyoffice.com" }, @@ -347,7 +385,11 @@ showReviewChanges: false, toolbarNoTabs: false, uiTheme: 'theme-classic-light', // Prevent theme switching dialogs - autosave: false // Disable autosave completely + autosave: false, // Disable autosave completely + logo: { + visible: true, + image: headerLogoUrl + } }, mode : urlParams["mode"] || 'edit', lang : urlParams["lang"] || 'en', @@ -453,6 +495,11 @@ } } catch (e) { console.error('[FIX] Error setting iframe ID:', e); } }, 100); + // Force logo visibility in desktop mode (header is hidden by SDK). + ensureDesktopLogoVisible(); + setTimeout(ensureDesktopLogoVisible, 300); + setTimeout(ensureDesktopLogoVisible, 1000); + // NOTE(victor): Poll every 50ms instead of fixed 5s delay - SDK is usually ready immediately var SDK_POLL_INTERVAL = 50; var SDK_MAX_WAIT = 10000; @@ -628,6 +675,7 @@ console.log('=== DOCUMENT READY ==='); console.log('[TIMING] Document ready! Total time:', (PERF.documentReady - PERF.loaderStart).toFixed(0), 'ms'); logTimings(); + ensureDesktopLogoVisible(); // CRITICAL FIX: Initialize change tracking to stop infinite polling // The SDK polls LocalFileGetSaved() and LocalFileGetOpenChangesCount() diff --git a/server.js b/server.js index 9312c06..c342aeb 100644 --- a/server.js +++ b/server.js @@ -988,7 +988,11 @@ app.get('/edit/:filename', (req, res) => { chat: false, comments: false, compactHeader: false, - help: false + help: false, + logo: { + visible: true, + image: "${BASE_URL}/web-apps/apps/common/main/resources/img/header/header-logo_s.svg" + } } } }); From 8595806aa3497bc203eda679ee15e59bcadf20fc Mon Sep 17 00:00:00 2001 From: "Victor A." <52110451+cs50victor@users.noreply.github.com> Date: Thu, 19 Feb 2026 12:16:12 -0800 Subject: [PATCH 04/10] fix: ensure dummy binary in font test and use rAF for logo visibility Create a temporary allfontsgen stub in tests so CI environments without the real binary do not fail on missing-file errors. Replace ad-hoc setTimeout chains for desktop logo injection with requestAnimationFrame scheduling and MutationObserver hooks for reliable, reactive visibility. --- __tests__/generate-office-fonts-path.test.js | 17 +++++++ editors/offline-loader-proper.html | 47 ++++++++++++++++++-- 2 files changed, 60 insertions(+), 4 deletions(-) diff --git a/__tests__/generate-office-fonts-path.test.js b/__tests__/generate-office-fonts-path.test.js index 1e8bd87..b3ea618 100644 --- a/__tests__/generate-office-fonts-path.test.js +++ b/__tests__/generate-office-fonts-path.test.js @@ -5,8 +5,11 @@ import path from 'path'; import { spawnSync } from 'child_process'; const SCRIPT_PATH = path.resolve(import.meta.dir, '..', 'scripts', 'generate_office_fonts.js'); +const CONVERTER_DIR = path.resolve(import.meta.dir, '..', 'converter'); +const DUMMY_BIN = path.join(CONVERTER_DIR, process.platform === 'win32' ? 'allfontsgen.exe' : 'allfontsgen'); const tempDirs = []; +let createdDummyBin = false; function makeTempDir(prefix) { const dir = fs.mkdtempSync(path.join(os.tmpdir(), prefix)); @@ -42,7 +45,17 @@ childProcess.spawnSync = (_bin, args) => { return preloadPath; } +function ensureDummyBin() { + if (!fs.existsSync(DUMMY_BIN)) { + fs.mkdirSync(CONVERTER_DIR, { recursive: true }); + fs.writeFileSync(DUMMY_BIN, ''); + fs.chmodSync(DUMMY_BIN, 0o755); + createdDummyBin = true; + } +} + function runGenerator({ fontDataDir, writeOutputs }) { + ensureDummyBin(); const mockDir = makeTempDir('oo-editors-font-mock-'); const preloadPath = writeMockPreload(mockDir); @@ -67,6 +80,10 @@ afterEach(() => { const dir = tempDirs.pop(); fs.rmSync(dir, { recursive: true, force: true }); } + if (createdDummyBin) { + fs.rmSync(DUMMY_BIN, { force: true }); + createdDummyBin = false; + } }); describe('generate_office_fonts path handling', () => { diff --git a/editors/offline-loader-proper.html b/editors/offline-loader-proper.html index ac5c4e3..02088bc 100644 --- a/editors/offline-loader-proper.html +++ b/editors/offline-loader-proper.html @@ -210,6 +210,46 @@ // best-effort only } } + + function scheduleLogoEnsure(frames) { + var remaining = typeof frames === 'number' ? frames : 4; + function tick() { + ensureDesktopLogoVisible(); + remaining -= 1; + if (remaining > 0 && window.requestAnimationFrame) { + window.requestAnimationFrame(tick); + } + } + + if (window.requestAnimationFrame) { + window.requestAnimationFrame(tick); + } else { + ensureDesktopLogoVisible(); + } + } + + function bindLogoRefreshHooks() { + var iframe = document.querySelector('iframe[id*="placeholder"]') || + document.querySelector('iframe[id*="frameEditor"]') || + document.querySelector('iframe'); + if (!iframe || iframe.dataset.logoHooksBound === 'true') return; + + iframe.dataset.logoHooksBound = 'true'; + iframe.addEventListener('load', function() { + scheduleLogoEnsure(6); + }); + + try { + if (iframe.contentDocument && iframe.contentDocument.body && window.MutationObserver) { + var observer = new MutationObserver(function() { + scheduleLogoEnsure(2); + }); + observer.observe(iframe.contentDocument.body, { childList: true, subtree: true }); + } + } catch (e) { + // best-effort only + } + } setInterval(ensureDesktopLogoVisible, 3000); // ======================================================================== @@ -496,9 +536,8 @@ } catch (e) { console.error('[FIX] Error setting iframe ID:', e); } }, 100); // Force logo visibility in desktop mode (header is hidden by SDK). - ensureDesktopLogoVisible(); - setTimeout(ensureDesktopLogoVisible, 300); - setTimeout(ensureDesktopLogoVisible, 1000); + bindLogoRefreshHooks(); + scheduleLogoEnsure(8); // NOTE(victor): Poll every 50ms instead of fixed 5s delay - SDK is usually ready immediately var SDK_POLL_INTERVAL = 50; @@ -675,7 +714,7 @@ console.log('=== DOCUMENT READY ==='); console.log('[TIMING] Document ready! Total time:', (PERF.documentReady - PERF.loaderStart).toFixed(0), 'ms'); logTimings(); - ensureDesktopLogoVisible(); + scheduleLogoEnsure(8); // CRITICAL FIX: Initialize change tracking to stop infinite polling // The SDK polls LocalFileGetSaved() and LocalFileGetOpenChangesCount() From 01c680d4ca504c57417b8013b578917992f0632d Mon Sep 17 00:00:00 2001 From: "Victor A." <52110451+cs50victor@users.noreply.github.com> Date: Thu, 19 Feb 2026 12:31:58 -0800 Subject: [PATCH 05/10] test: add e2e logo header visibility check --- __tests__/logo-header.test.js | 122 ++++++++++++++++++++++++++++++++++ package.json | 3 +- 2 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 __tests__/logo-header.test.js diff --git a/__tests__/logo-header.test.js b/__tests__/logo-header.test.js new file mode 100644 index 0000000..5da0914 --- /dev/null +++ b/__tests__/logo-header.test.js @@ -0,0 +1,122 @@ +const { chromium } = require('playwright'); +const fs = require('fs'); +const path = require('path'); +const os = require('os'); + +const SERVER_PORT = Number.parseInt(process.env.SERVER_PORT || '8080', 10); +const SERVER_URL = process.env.SERVER_URL || `http://localhost:${SERVER_PORT}`; +const LOAD_TIMEOUT = Number.parseInt(process.env.LOAD_TIMEOUT || '30000', 10); +const LOGO_TIMEOUT = Number.parseInt(process.env.LOGO_TIMEOUT || '30000', 10); +const DEFAULT_LIST_PATH = path.join(__dirname, '..', 'test', 'batch-files.txt'); +const LIST_PATH = process.env.FILE_LIST || process.argv[2] || DEFAULT_LIST_PATH; + +function normalizeListEntry(raw) { + if (!raw) return null; + let value = raw.trim(); + if (!value || value.startsWith('#')) return null; + if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) { + value = value.slice(1, -1).trim(); + } + if (!value) return null; + if (value === '~') value = os.homedir(); + else if (value.startsWith('~/')) value = path.join(os.homedir(), value.slice(2)); + if (!path.isAbsolute(value)) value = path.resolve(process.cwd(), value); + return value; +} + +function loadFileList(listPath) { + if (!fs.existsSync(listPath)) { + throw new Error(`File list not found: ${listPath}`); + } + const lines = fs.readFileSync(listPath, 'utf8').split(/\r?\n/); + const files = lines.map(normalizeListEntry).filter(Boolean); + if (files.length === 0) { + throw new Error(`File list is empty: ${listPath}`); + } + return files; +} + +function assertFilesExist(files) { + const missing = files.filter((filePath) => !fs.existsSync(filePath) || !fs.statSync(filePath).isFile()); + if (missing.length) { + throw new Error(`Missing files:\n${missing.join('\n')}`); + } +} + +function getDocType(ext) { + if (['xlsx', 'xls', 'ods', 'csv'].includes(ext)) return 'cell'; + if (['docx', 'doc', 'odt', 'txt', 'rtf', 'html'].includes(ext)) return 'word'; + if (['pptx', 'ppt', 'odp'].includes(ext)) return 'slide'; + return 'slide'; +} + +function buildOfflineLoaderUrl(filePath) { + const ext = path.extname(filePath).slice(1).toLowerCase(); + const doctype = getDocType(ext); + const convertUrl = `${SERVER_URL}/api/convert?filepath=${encodeURIComponent(filePath)}`; + const params = new URLSearchParams({ + url: convertUrl, + title: path.basename(filePath), + filepath: filePath, + filetype: ext, + doctype, + }); + return `${SERVER_URL}/offline-loader-proper.html?${params.toString()}`; +} + +async function assertServer() { + const response = await fetch(`${SERVER_URL}/healthcheck`).catch(() => null); + if (!response || !response.ok) { + throw new Error(`Server not running at ${SERVER_URL}. Start with: bun run server`); + } +} + +async function assertLogoVisible(page, filePath) { + const url = buildOfflineLoaderUrl(filePath); + await page.goto(url, { waitUntil: 'load', timeout: LOAD_TIMEOUT }); + await page.waitForFunction(() => { + const iframe = document.querySelector('iframe[id*="placeholder"]') || + document.querySelector('iframe[id*="frameEditor"]') || + document.querySelector('iframe'); + if (!iframe || !iframe.contentDocument) return false; + + const doc = iframe.contentDocument; + const logo = doc.querySelector('#oo-desktop-logo') || + doc.querySelector('#box-document-title .extra img[src*="header-logo_s.svg"]') || + doc.querySelector('#box-document-title img[src*="header-logo_s.svg"]'); + if (!logo) return false; + + const style = doc.defaultView.getComputedStyle(logo); + const rect = logo.getBoundingClientRect(); + return style.display !== 'none' && style.visibility !== 'hidden' && rect.width > 0 && rect.height > 0; + }, { timeout: LOGO_TIMEOUT }); +} + +async function run() { + await assertServer(); + const files = loadFileList(LIST_PATH); + assertFilesExist(files); + + const browser = await chromium.launch({ headless: true }); + const page = await browser.newPage({ viewport: { width: 1400, height: 900 } }); + + try { + for (const filePath of files) { + await assertLogoVisible(page, filePath); + console.log(`PASS logo visible: ${filePath}`); + } + console.log(`Logo header check passed for ${files.length} files`); + await browser.close(); + process.exit(0); + } catch (err) { + await browser.close(); + console.error('Logo header check failed'); + console.error(err && err.message ? err.message : err); + process.exit(1); + } +} + +run().catch((err) => { + console.error(err && err.message ? err.message : err); + process.exit(1); +}); diff --git a/package.json b/package.json index 87f3ce2..62e9b15 100644 --- a/package.json +++ b/package.json @@ -8,8 +8,9 @@ "test": "bun run test:unit && bun run test:e2e", "test:all": "bun run test:unit && bun run test:e2e", "test:unit": "bun test __tests__/server-utils.test.js __tests__/desktop-stub-utils.test.js __tests__/generate-office-fonts-path.test.js && node test-url-scheme.js", - "test:e2e": "bun run test:console-batch && bun run test:save", + "test:e2e": "bun run test:console-batch && bun run test:logo && bun run test:save", "test:console-batch": "node test-console-batch.js", + "test:logo": "node __tests__/logo-header.test.js", "test:save": "bun test __tests__/save.test.js", "build": "cd sdkjs/build && npm install && npx grunt --desktop=true && cd ../.. && for e in cell word slide visio; do mkdir -p editors/sdkjs/$e && cp sdkjs/deploy/sdkjs/$e/sdk-all.js editors/sdkjs/$e/ && cp sdkjs/deploy/sdkjs/$e/sdk-all-min.js editors/sdkjs/$e/; done", "build:minify": "bunx esbuild server.js --minify --outfile=dist/server.js" From 0fd848fd7bc8bdbd6a6b5dde3c3a6a94fe5019f8 Mon Sep 17 00:00:00 2001 From: "Victor A." <52110451+cs50victor@users.noreply.github.com> Date: Thu, 19 Feb 2026 12:35:53 -0800 Subject: [PATCH 06/10] refactor: migrate logo test to ESM and improve test descriptions --- __tests__/generate-office-fonts-path.test.js | 4 ++-- __tests__/logo-header.test.js | 10 +++++----- editors/offline-loader-proper.html | 6 +++--- package.json | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/__tests__/generate-office-fonts-path.test.js b/__tests__/generate-office-fonts-path.test.js index b3ea618..2372a9d 100644 --- a/__tests__/generate-office-fonts-path.test.js +++ b/__tests__/generate-office-fonts-path.test.js @@ -87,7 +87,7 @@ afterEach(() => { }); describe('generate_office_fonts path handling', () => { - test('supports non-ASCII FONT_DATA_DIR paths when outputs are written', () => { + test('should succeed when FONT_DATA_DIR contains non-ASCII characters', () => { const root = makeTempDir('oo-editors-fontdata-'); const fontDataDir = path.join(root, 'C', 'Users', 'دانيال', 'AppData', 'Roaming', 'interpreter', 'office-extension-fontdata'); @@ -98,7 +98,7 @@ describe('generate_office_fonts path handling', () => { expect(fs.existsSync(path.join(fontDataDir, 'font_selection.bin'))).toBe(true); }); - test('fails when generator exits 0 but does not write metadata files', () => { + test('should fail when generator exits 0 but does not write metadata files', () => { const root = makeTempDir('oo-editors-fontdata-'); const fontDataDir = path.join(root, 'C', 'Users', 'دانيال', 'AppData', 'Roaming', 'interpreter', 'office-extension-fontdata'); diff --git a/__tests__/logo-header.test.js b/__tests__/logo-header.test.js index 5da0914..139bf26 100644 --- a/__tests__/logo-header.test.js +++ b/__tests__/logo-header.test.js @@ -1,13 +1,13 @@ -const { chromium } = require('playwright'); -const fs = require('fs'); -const path = require('path'); -const os = require('os'); +import { chromium } from 'playwright'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; const SERVER_PORT = Number.parseInt(process.env.SERVER_PORT || '8080', 10); const SERVER_URL = process.env.SERVER_URL || `http://localhost:${SERVER_PORT}`; const LOAD_TIMEOUT = Number.parseInt(process.env.LOAD_TIMEOUT || '30000', 10); const LOGO_TIMEOUT = Number.parseInt(process.env.LOGO_TIMEOUT || '30000', 10); -const DEFAULT_LIST_PATH = path.join(__dirname, '..', 'test', 'batch-files.txt'); +const DEFAULT_LIST_PATH = path.join(import.meta.dir, '..', 'test', 'batch-files.txt'); const LIST_PATH = process.env.FILE_LIST || process.argv[2] || DEFAULT_LIST_PATH; function normalizeListEntry(raw) { diff --git a/editors/offline-loader-proper.html b/editors/offline-loader-proper.html index 02088bc..81491a9 100644 --- a/editors/offline-loader-proper.html +++ b/editors/offline-loader-proper.html @@ -207,7 +207,7 @@ logo.style.cssText = 'position:fixed;top:12px;left:12px;z-index:2147483647;height:20px;max-width:120px;pointer-events:none;'; } } catch (e) { - // best-effort only + // NOTE(victor): best-effort -- cross-origin iframe access may throw } } @@ -247,7 +247,7 @@ observer.observe(iframe.contentDocument.body, { childList: true, subtree: true }); } } catch (e) { - // best-effort only + // NOTE(victor): best-effort -- cross-origin iframe access may throw } } setInterval(ensureDesktopLogoVisible, 3000); @@ -535,7 +535,7 @@ } } catch (e) { console.error('[FIX] Error setting iframe ID:', e); } }, 100); - // Force logo visibility in desktop mode (header is hidden by SDK). + // NOTE(victor): SDK hides the header in desktop mode, so we force-inject the logo bindLogoRefreshHooks(); scheduleLogoEnsure(8); diff --git a/package.json b/package.json index 62e9b15..e1d4888 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "test:unit": "bun test __tests__/server-utils.test.js __tests__/desktop-stub-utils.test.js __tests__/generate-office-fonts-path.test.js && node test-url-scheme.js", "test:e2e": "bun run test:console-batch && bun run test:logo && bun run test:save", "test:console-batch": "node test-console-batch.js", - "test:logo": "node __tests__/logo-header.test.js", + "test:logo": "bun __tests__/logo-header.test.js", "test:save": "bun test __tests__/save.test.js", "build": "cd sdkjs/build && npm install && npx grunt --desktop=true && cd ../.. && for e in cell word slide visio; do mkdir -p editors/sdkjs/$e && cp sdkjs/deploy/sdkjs/$e/sdk-all.js editors/sdkjs/$e/ && cp sdkjs/deploy/sdkjs/$e/sdk-all-min.js editors/sdkjs/$e/; done", "build:minify": "bunx esbuild server.js --minify --outfile=dist/server.js" From c0dfbff35b83efd0b9a601ff7fc43d6c2568b2ae Mon Sep 17 00:00:00 2001 From: "Victor A." <52110451+cs50victor@users.noreply.github.com> Date: Thu, 19 Feb 2026 12:38:13 -0800 Subject: [PATCH 07/10] fix: enable compact header in editor config --- editors/desktop-stub.js | 2 +- server.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/editors/desktop-stub.js b/editors/desktop-stub.js index 7eaa0d8..bd51927 100644 --- a/editors/desktop-stub.js +++ b/editors/desktop-stub.js @@ -549,7 +549,7 @@ comments: false, help: false, hideRightMenu: false, - compactHeader: false, + compactHeader: true, logo: { visible: true, image: headerLogoUrl diff --git a/server.js b/server.js index c342aeb..e04d4ca 100644 --- a/server.js +++ b/server.js @@ -987,7 +987,7 @@ app.get('/edit/:filename', (req, res) => { autosave: false, chat: false, comments: false, - compactHeader: false, + compactHeader: true, help: false, logo: { visible: true, From 90e092efa0d702fa309181915f423215ffd85820 Mon Sep 17 00:00:00 2001 From: "Victor A." <52110451+cs50victor@users.noreply.github.com> Date: Thu, 19 Feb 2026 12:54:26 -0800 Subject: [PATCH 08/10] fix: ci hangs --- __tests__/logo-header.test.js | 2 +- scripts/generate_office_fonts.js | 1 + test/logo-files.txt | 11 +++++++++++ 3 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 test/logo-files.txt diff --git a/__tests__/logo-header.test.js b/__tests__/logo-header.test.js index 139bf26..152283e 100644 --- a/__tests__/logo-header.test.js +++ b/__tests__/logo-header.test.js @@ -7,7 +7,7 @@ const SERVER_PORT = Number.parseInt(process.env.SERVER_PORT || '8080', 10); const SERVER_URL = process.env.SERVER_URL || `http://localhost:${SERVER_PORT}`; const LOAD_TIMEOUT = Number.parseInt(process.env.LOAD_TIMEOUT || '30000', 10); const LOGO_TIMEOUT = Number.parseInt(process.env.LOGO_TIMEOUT || '30000', 10); -const DEFAULT_LIST_PATH = path.join(import.meta.dir, '..', 'test', 'batch-files.txt'); +const DEFAULT_LIST_PATH = path.join(import.meta.dir, '..', 'test', 'logo-files.txt'); const LIST_PATH = process.env.FILE_LIST || process.argv[2] || DEFAULT_LIST_PATH; function normalizeListEntry(raw) { diff --git a/scripts/generate_office_fonts.js b/scripts/generate_office_fonts.js index 5ab6c6b..7d2fec5 100755 --- a/scripts/generate_office_fonts.js +++ b/scripts/generate_office_fonts.js @@ -93,3 +93,4 @@ if (!fs.existsSync(allFontsJs) || !fs.existsSync(fontSelectionBin)) { } console.log('[generate_office_fonts] Font metadata updated.'); +process.exit(0); diff --git a/test/logo-files.txt b/test/logo-files.txt new file mode 100644 index 0000000..ced66b2 --- /dev/null +++ b/test/logo-files.txt @@ -0,0 +1,11 @@ +# One file per line. Absolute or relative paths are accepted. +# Lines starting with # are ignored. + +.github/assets/simple.xlsx +.github/assets/medium.xlsx +.github/assets/hard.xlsx +.github/assets/simple.docx +.github/assets/medium.docx +.github/assets/medium.ppt +.github/assets/simple.ppt +.github/assets/simple.csv From 8a06b802efdda05c29a9faf34858376558aa36d3 Mon Sep 17 00:00:00 2001 From: "Victor A." <52110451+cs50victor@users.noreply.github.com> Date: Thu, 19 Feb 2026 13:03:43 -0800 Subject: [PATCH 09/10] fix: logo alignment --- editors/offline-loader-proper.html | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/editors/offline-loader-proper.html b/editors/offline-loader-proper.html index 81491a9..8ad390c 100644 --- a/editors/offline-loader-proper.html +++ b/editors/offline-loader-proper.html @@ -199,7 +199,7 @@ if (logo.parentNode !== host) { host.insertBefore(logo, host.firstChild); } - logo.style.cssText = 'height:20px;max-width:120px;margin:0 12px 0 0;display:block;pointer-events:none;'; + logo.style.cssText = 'height:20px;max-width:120px;margin:2px 12px 0 6px;display:block;pointer-events:none;'; } else { if (logo.parentNode !== iframeDoc.body) { iframeDoc.body.appendChild(logo); diff --git a/package.json b/package.json index e1d4888..62e9b15 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "test:unit": "bun test __tests__/server-utils.test.js __tests__/desktop-stub-utils.test.js __tests__/generate-office-fonts-path.test.js && node test-url-scheme.js", "test:e2e": "bun run test:console-batch && bun run test:logo && bun run test:save", "test:console-batch": "node test-console-batch.js", - "test:logo": "bun __tests__/logo-header.test.js", + "test:logo": "node __tests__/logo-header.test.js", "test:save": "bun test __tests__/save.test.js", "build": "cd sdkjs/build && npm install && npx grunt --desktop=true && cd ../.. && for e in cell word slide visio; do mkdir -p editors/sdkjs/$e && cp sdkjs/deploy/sdkjs/$e/sdk-all.js editors/sdkjs/$e/ && cp sdkjs/deploy/sdkjs/$e/sdk-all-min.js editors/sdkjs/$e/; done", "build:minify": "bunx esbuild server.js --minify --outfile=dist/server.js" From 426cbba23a74c81ce7336c769b9551874ab89006 Mon Sep 17 00:00:00 2001 From: "Victor A." <52110451+cs50victor@users.noreply.github.com> Date: Thu, 19 Feb 2026 13:09:58 -0800 Subject: [PATCH 10/10] fix: use import.meta.dirname instead of import.meta.dir in logo test --- __tests__/logo-header.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/__tests__/logo-header.test.js b/__tests__/logo-header.test.js index 152283e..862096a 100644 --- a/__tests__/logo-header.test.js +++ b/__tests__/logo-header.test.js @@ -7,7 +7,7 @@ const SERVER_PORT = Number.parseInt(process.env.SERVER_PORT || '8080', 10); const SERVER_URL = process.env.SERVER_URL || `http://localhost:${SERVER_PORT}`; const LOAD_TIMEOUT = Number.parseInt(process.env.LOAD_TIMEOUT || '30000', 10); const LOGO_TIMEOUT = Number.parseInt(process.env.LOGO_TIMEOUT || '30000', 10); -const DEFAULT_LIST_PATH = path.join(import.meta.dir, '..', 'test', 'logo-files.txt'); +const DEFAULT_LIST_PATH = path.join(import.meta.dirname, '..', 'test', 'logo-files.txt'); const LIST_PATH = process.env.FILE_LIST || process.argv[2] || DEFAULT_LIST_PATH; function normalizeListEntry(raw) {