buninu-browser 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSES/jsmdcui.txt +22 -0
- package/README.md +40 -4
- package/frontends/wayland/LICENSE-land +21 -0
- package/frontends/wayland/README.md +152 -0
- package/frontends/wayland/casty/LICENSE +21 -0
- package/frontends/wayland/casty/bookmarks.js +32 -0
- package/frontends/wayland/casty/click.js +96 -0
- package/frontends/wayland/casty/commands.js +61 -0
- package/frontends/wayland/casty/config.js +45 -0
- package/frontends/wayland/casty/download-directory.js +28 -0
- package/frontends/wayland/casty/download.js +49 -0
- package/frontends/wayland/casty/hints.js +334 -0
- package/frontends/wayland/casty/keys.js +56 -0
- package/frontends/wayland/casty/page-commands.js +80 -0
- package/frontends/wayland/casty/prompt-width.js +148 -0
- package/frontends/wayland/casty/urlbar.js +606 -0
- package/frontends/wayland/client.js +88 -0
- package/frontends/wayland/clipboard.js +80 -0
- package/frontends/wayland/index.js +130 -0
- package/frontends/wayland/input.js +75 -0
- package/frontends/wayland/protocol.js +55 -0
- package/frontends/wayland/protocols/wayland.xml +3248 -0
- package/frontends/wayland/protocols/xdg-shell.xml +1415 -0
- package/frontends/wayland/transport.js +120 -0
- package/frontends/wayland/urlbar.js +162 -0
- package/index.js +28 -1
- package/lib/audio/shared-connection.js +31 -0
- package/lib/cdp-server.js +15 -2
- package/lib/clipboard-command.js +10 -0
- package/lib/clipboard-powershell.js +22 -0
- package/lib/clipboard.js +37 -2
- package/lib/document-types.js +11 -0
- package/lib/headless-shell.js +54 -12
- package/lib/layout/text-layout.js +17 -9
- package/lib/paint/display-list.js +6 -1
- package/lib/qml/README.md +1427 -0
- package/lib/qml/builtins.js +771 -0
- package/lib/qml/canvas-dom.js +115 -0
- package/lib/qml/canvas.js +76 -0
- package/lib/qml/document.js +1199 -0
- package/lib/qml/fonts.js +69 -0
- package/lib/qml/format.js +9 -0
- package/lib/qml/host.js +352 -0
- package/lib/qml/images.js +199 -0
- package/lib/qml/launcher.js +132 -0
- package/lib/qml/loader.js +521 -0
- package/lib/qml/media-renderer.js +49 -0
- package/lib/qml/media.js +86 -0
- package/lib/qml/models.js +622 -0
- package/lib/qml/parser.js +381 -0
- package/lib/qml/qs +5 -0
- package/lib/qml/qscasty +3 -0
- package/lib/qml/qsheadless +3 -0
- package/lib/qml/qswayland +3 -0
- package/lib/qml/reactive.js +182 -0
- package/lib/qml/runtime.js +3603 -0
- package/lib/qml/shader/fragment.js +83 -0
- package/lib/qml/shader/qsb.js +27 -0
- package/lib/qml/shader/renderer.js +39 -0
- package/lib/qml/shader/runtime.js +61 -0
- package/lib/qml/shapes.js +251 -0
- package/lib/qml/shortcuts.js +65 -0
- package/lib/qml/text.js +109 -0
- package/lib/qml/textures.js +126 -0
- package/lib/qml/wayland-clipboard.js +32 -0
- package/lib/qml/window-constraints.js +16 -0
- package/lib/qml/worker-thread.js +70 -0
- package/lib/render-tree/index.js +16 -1
- package/lib/renderer/bindings.js +16 -1
- package/lib/renderer/canvas-2d.js +29 -0
- package/lib/renderer/document-selection.js +19 -0
- package/lib/renderer/host.js +45 -17
- package/lib/renderer/main.js +21 -6
- package/lib/renderer/page-realm.js +20 -10
- package/lib/renderer/page-renderer.js +497 -42
- package/lib/renderer/pointer-move.js +5 -3
- package/lib/renderer/render-interval.js +9 -0
- package/lib/renderer/text-input-paint.js +54 -0
- package/lib/renderer/text-input.js +57 -0
- package/lib/style/computed-style.js +10 -4
- package/package.json +1 -1
- package/test/audio-connection-lifetime.test.js +46 -0
- package/test/canvas-paint.test.js +8 -0
- package/test/clipboard.test.js +19 -0
- package/test/computed-style.test.js +12 -0
- package/test/document-selection.test.js +27 -0
- package/test/double-click.test.js +85 -0
- package/test/fixtures/canvas-damage.js +40 -0
- package/test/fixtures/fake-renderer.js +1 -0
- package/test/fixtures/flea-helpers/Dockerfile +36 -0
- package/test/fixtures/flea-helpers/bat.mjs +43 -0
- package/test/fixtures/flea-helpers/bin/bat +3 -0
- package/test/fixtures/flea-helpers/bin/bwrap +10 -0
- package/test/fixtures/flea-helpers/bin/bwrap-real-filter +9 -0
- package/test/fixtures/flea-helpers/bin/glow +3 -0
- package/test/fixtures/flea-helpers/bin/qs +3 -0
- package/test/fixtures/flea-helpers/bin/unsupported +3 -0
- package/test/fixtures/flea-helpers/check-highlighting.mjs +47 -0
- package/test/fixtures/flea-helpers/check-media.mjs +29 -0
- package/test/fixtures/flea-helpers/check-preview.mjs +27 -0
- package/test/fixtures/flea-helpers/check.mjs +36 -0
- package/test/fixtures/flea-helpers/highlight-cases.json +227 -0
- package/test/fixtures/flea-helpers/source-mime.xml +28 -0
- package/test/fixtures/flea-helpers/syntax-compat.mjs +23 -0
- package/test/fixtures/flea-thumbnail-stub/flea-placeholder.thumbnailer +4 -0
- package/test/fixtures/flea-thumbnail-stub/flea-thumbnail-stub +3 -0
- package/test/fixtures/omastorm-helpers/Dockerfile +35 -0
- package/test/fixtures/omastorm-helpers/check.mjs +56 -0
- package/test/fixtures/omastorm-helpers/launch.mjs +24 -0
- package/test/fixtures/omastorm-helpers/prepare.mjs +17 -0
- package/test/fixtures/omastorm-helpers/runtime.mjs +32 -0
- package/test/fixtures/pointer-paint-batching.js +23 -0
- package/test/fixtures/qml/flea/EscapeHint.qml +11 -0
- package/test/fixtures/qml/flea/LICENSE +21 -0
- package/test/fixtures/qml/flea/README.md +25 -0
- package/test/fixtures/qml/flea/Theme.qml +8 -0
- package/test/fixtures/qml/flea/demo.qml +24 -0
- package/test/fixtures/qml/flea/qmldir +3 -0
- package/test/fixtures/qml/flea-theme/LICENSE-Flea +21 -0
- package/test/fixtures/qml/flea-theme/LICENSE-Omarchy +20 -0
- package/test/fixtures/qml/flea-theme/README.md +70 -0
- package/test/fixtures/qml/flea-theme/button.qml +17 -0
- package/test/fixtures/qml/flea-theme/demo.qml +27 -0
- package/test/fixtures/qml/flea-theme/field.qml +21 -0
- package/test/fixtures/qml/flea-theme/imports/qs/Commons/BorderGeometry.js +373 -0
- package/test/fixtures/qml/flea-theme/imports/qs/Commons/Color.qml +258 -0
- package/test/fixtures/qml/flea-theme/imports/qs/Commons/I18n.qml +118 -0
- package/test/fixtures/qml/flea-theme/imports/qs/Commons/Numbers.js +19 -0
- package/test/fixtures/qml/flea-theme/imports/qs/Commons/Plural.js +91 -0
- package/test/fixtures/qml/flea-theme/imports/qs/Commons/Style.qml +561 -0
- package/test/fixtures/qml/flea-theme/imports/qs/Commons/Util.qml +188 -0
- package/test/fixtures/qml/flea-theme/imports/qs/Commons/qmldir +13 -0
- package/test/fixtures/qml/flea-theme/segment.qml +17 -0
- package/test/fixtures/qml/flea-theme/settings.qml +58 -0
- package/test/fixtures/qml/flea-theme/text-metrics.qml +10 -0
- package/test/fixtures/qml/flea-theme/ui/DialogButton.qml +150 -0
- package/test/fixtures/qml/flea-theme/ui/DialogField.qml +147 -0
- package/test/fixtures/qml/flea-theme/ui/EscapeHint.qml +11 -0
- package/test/fixtures/qml/flea-theme/ui/Glass.qml +114 -0
- package/test/fixtures/qml/flea-theme/ui/Glyph.qml +54 -0
- package/test/fixtures/qml/flea-theme/ui/GlyphSolid.qml +22 -0
- package/test/fixtures/qml/flea-theme/ui/Reveal.qml +54 -0
- package/test/fixtures/qml/flea-theme/ui/SettingsRuler.qml +85 -0
- package/test/fixtures/qml/flea-theme/ui/SettingsSegment.qml +85 -0
- package/test/fixtures/qml/flea-theme/ui/StableModel.qml +89 -0
- package/test/fixtures/qml/flea-theme/ui/Theme.qml +460 -0
- package/test/fixtures/qml/flea-theme/ui/ThemeColors.qml +43 -0
- package/test/fixtures/qml/flea-theme/ui/ViewState.qml +565 -0
- package/test/fixtures/qml/flea-theme/ui/js/Anchor.js +379 -0
- package/test/fixtures/qml/flea-theme/ui/js/AnchorHold.js +162 -0
- package/test/fixtures/qml/flea-theme/ui/js/Archive.js +157 -0
- package/test/fixtures/qml/flea-theme/ui/js/BackReturn.js +110 -0
- package/test/fixtures/qml/flea-theme/ui/js/Buttons.js +54 -0
- package/test/fixtures/qml/flea-theme/ui/js/ColumnMenu.js +111 -0
- package/test/fixtures/qml/flea-theme/ui/js/Columns.js +298 -0
- package/test/fixtures/qml/flea-theme/ui/js/Contrast.js +157 -0
- package/test/fixtures/qml/flea-theme/ui/js/Density.js +44 -0
- package/test/fixtures/qml/flea-theme/ui/js/DirSizes.js +129 -0
- package/test/fixtures/qml/flea-theme/ui/js/ExtThumbs.js +115 -0
- package/test/fixtures/qml/flea-theme/ui/js/Filter.js +284 -0
- package/test/fixtures/qml/flea-theme/ui/js/FolderSorts.js +108 -0
- package/test/fixtures/qml/flea-theme/ui/js/Format.js +305 -0
- package/test/fixtures/qml/flea-theme/ui/js/Icons.js +206 -0
- package/test/fixtures/qml/flea-theme/ui/js/Input.js +39 -0
- package/test/fixtures/qml/flea-theme/ui/js/KeyName.js +64 -0
- package/test/fixtures/qml/flea-theme/ui/js/Keymap.js +370 -0
- package/test/fixtures/qml/flea-theme/ui/js/Kinds.js +145 -0
- package/test/fixtures/qml/flea-theme/ui/js/Lists.js +11 -0
- package/test/fixtures/qml/flea-theme/ui/js/MakeDefault.js +186 -0
- package/test/fixtures/qml/flea-theme/ui/js/Match.js +27 -0
- package/test/fixtures/qml/flea-theme/ui/js/Material.js +109 -0
- package/test/fixtures/qml/flea-theme/ui/js/Menu.js +513 -0
- package/test/fixtures/qml/flea-theme/ui/js/Motion.js +16 -0
- package/test/fixtures/qml/flea-theme/ui/js/Mounts.js +320 -0
- package/test/fixtures/qml/flea-theme/ui/js/MouseNav.js +13 -0
- package/test/fixtures/qml/flea-theme/ui/js/Nav.js +328 -0
- package/test/fixtures/qml/flea-theme/ui/js/Palette.js +33 -0
- package/test/fixtures/qml/flea-theme/ui/js/Places.js +236 -0
- package/test/fixtures/qml/flea-theme/ui/js/Protocols.js +296 -0
- package/test/fixtures/qml/flea-theme/ui/js/RecentMode.js +154 -0
- package/test/fixtures/qml/flea-theme/ui/js/Search.js +396 -0
- package/test/fixtures/qml/flea-theme/ui/js/Settings.js +476 -0
- package/test/fixtures/qml/flea-theme/ui/js/SettingsAbout.js +99 -0
- package/test/fixtures/qml/flea-theme/ui/js/SettingsShelf.js +52 -0
- package/test/fixtures/qml/flea-theme/ui/js/Sort.js +127 -0
- package/test/fixtures/qml/flea-theme/ui/js/StateWords.js +42 -0
- package/test/fixtures/qml/flea-theme/ui/js/Swap.js +166 -0
- package/test/fixtures/qml/flea-theme/ui/js/TextSize.js +107 -0
- package/test/fixtures/qml/flea-theme/ui/js/Thumbs.js +158 -0
- package/test/fixtures/qml/flea-theme/ui/js/UiState.js +438 -0
- package/test/fixtures/qml/flea-theme/ui/js/Update.js +132 -0
- package/test/fixtures/qml/flea-theme/ui/js/UserDirs.js +56 -0
- package/test/fixtures/qml/flea-theme/ui/qmldir +227 -0
- package/test/fixtures/qml/hello.qml +26 -0
- package/test/fixtures/qml/imports/Demo/Toolkit/VersionLabel.qml +2 -0
- package/test/fixtures/qml/imports/Demo/Toolkit/VersionLabelNew.qml +2 -0
- package/test/fixtures/qml/imports/Demo/Toolkit/qmldir +3 -0
- package/test/fixtures/qml/imports/components/Badge.qml +15 -0
- package/test/fixtures/qml/imports/logic/base.js +2 -0
- package/test/fixtures/qml/imports/logic/counter.js +4 -0
- package/test/fixtures/qml/imports/logic/math.js +3 -0
- package/test/fixtures/qml/imports/logic/shared.js +3 -0
- package/test/fixtures/qml/imports/main.qml +21 -0
- package/test/fixtures/qml/imports/theme/Theme.qml +8 -0
- package/test/fixtures/qml/imports/theme/qmldir +2 -0
- package/test/fixtures/qml/multiply.qml +61 -0
- package/test/fixtures/qml-canvas-dom.js +90 -0
- package/test/fixtures/qml-fonts/main.qml +10 -0
- package/test/fixtures/qml-locale/check.mjs +6 -0
- package/test/fixtures/qml-locale/main.qml +8 -0
- package/test/fixtures/qml-media-audio.js +39 -0
- package/test/fixtures/qml-shader/grab.qml +14 -0
- package/test/fixtures/qml-shader/gradient.frag +5 -0
- package/test/fixtures/qml-shader/main.qml +14 -0
- package/test/fixtures/qml-shader/texture-grab.qml +25 -0
- package/test/fixtures/qml-shader/texture-size.frag +5 -0
- package/test/fixtures/qml-shader-check.mjs +24 -0
- package/test/fixtures/title-no-paint.js +20 -0
- package/test/helpers/flea-sandbox.js +48 -0
- package/test/helpers/quickshell-fixture.js +125 -0
- package/test/html-clipboard-wayland.test.js +47 -0
- package/test/index.test.js +13 -0
- package/test/manual-casty-without-qml.js +87 -0
- package/test/manual-double-click.html +21 -0
- package/test/manual-flea-application.js +49 -0
- package/test/manual-flea-binary.js +62 -0
- package/test/manual-flea-qml.js +9 -0
- package/test/manual-flea-settings.js +40 -0
- package/test/manual-flea-theme.js +40 -0
- package/test/manual-mouse-buttons.html +18 -0
- package/test/manual-omastorm-engine.mjs +19 -0
- package/test/manual-omastorm-shaders.mjs +28 -0
- package/test/manual-omastorm-software.mjs +25 -0
- package/test/manual-pointer-scroll.html +43 -0
- package/test/manual-qml-removal.js +46 -0
- package/test/manual-without-qml.js +45 -0
- package/test/pointer-drag.test.js +104 -0
- package/test/qml-canvas-dom.test.js +37 -0
- package/test/qml-canvas.test.js +21 -0
- package/test/qml-clipboard-bridge.test.js +25 -0
- package/test/qml-clipboard-wayland.test.js +40 -0
- package/test/qml-connections-timing.test.js +95 -0
- package/test/qml-controls-window.test.js +182 -0
- package/test/qml-dynamic.test.js +401 -0
- package/test/qml-flea-app.test.js +146 -0
- package/test/qml-flea-backend.test.js +78 -0
- package/test/qml-flea-columns-backend.test.js +38 -0
- package/test/qml-flea-compile.test.js +78 -0
- package/test/qml-flea-launcher.test.js +88 -0
- package/test/qml-flea-markdown-backend.test.js +31 -0
- package/test/qml-flea-media.test.js +28 -0
- package/test/qml-flea-navigation-backend.test.js +35 -0
- package/test/qml-flea-navigation-timing.test.js +147 -0
- package/test/qml-flea-preview-backend.test.js +53 -0
- package/test/qml-flea-previews.test.js +163 -0
- package/test/qml-flea-rows.test.js +101 -0
- package/test/qml-flea-textures.test.js +59 -0
- package/test/qml-flea-view-switch.test.js +33 -0
- package/test/qml-flea-wayland.test.js +68 -0
- package/test/qml-flea.test.js +34 -0
- package/test/qml-fonts.test.js +31 -0
- package/test/qml-grid-multiline.test.js +48 -0
- package/test/qml-host.test.js +238 -0
- package/test/qml-imports.test.js +102 -0
- package/test/qml-inherited-handlers.test.js +51 -0
- package/test/qml-input-selection.test.js +66 -0
- package/test/qml-interactions.test.js +309 -0
- package/test/qml-language.test.js +167 -0
- package/test/qml-launcher.test.js +54 -0
- package/test/qml-layout-text.test.js +205 -0
- package/test/qml-locale.test.js +45 -0
- package/test/qml-media.test.js +63 -0
- package/test/qml-nonvisual-visible.test.js +19 -0
- package/test/qml-persisted-delegates.test.js +45 -0
- package/test/qml-quickshell-controls.test.js +65 -0
- package/test/qml-quickshell-settings.test.js +62 -0
- package/test/qml-repeater.test.js +72 -0
- package/test/qml-shader-runtime.test.js +6 -0
- package/test/qml-shortcuts.test.js +39 -0
- package/test/qml-socket.test.js +60 -0
- package/test/qml-software-shader.test.js +55 -0
- package/test/qml-style-updates.test.js +124 -0
- package/test/qml-textures.test.js +66 -0
- package/test/qml-views.test.js +332 -0
- package/test/qml-window-update.test.js +36 -0
- package/test/qml-worker.test.js +69 -0
- package/test/qml.test.js +83 -0
- package/test/render-interval.test.js +33 -0
- package/test/renderer-host.test.js +11 -0
- package/test/text-input-pointer.test.js +31 -0
- package/test/text-input-scroll.test.js +29 -0
- package/test/text-input.test.js +47 -0
- package/test/title-no-paint.test.js +6 -0
- package/test/wayland-frontend.test.js +83 -0
- package/test/wayland-scroll.test.js +40 -0
- package/test/wayland-urlbar.test.js +61 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// Opt-in integration: unmodified packaged QML and the real installed Flea binary.
|
|
2
|
+
import { test, expect } from 'bun:test';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import {
|
|
5
|
+
mkdirSync,
|
|
6
|
+
writeFileSync,
|
|
7
|
+
readdirSync,
|
|
8
|
+
statSync,
|
|
9
|
+
unlinkSync,
|
|
10
|
+
} from 'node:fs';
|
|
11
|
+
import { createBrowser } from '../lib/headless-shell.js';
|
|
12
|
+
import { fleaSandbox } from './helpers/flea-sandbox.js';
|
|
13
|
+
const root = process.env.BUNINU_FLEA_ROOT,
|
|
14
|
+
omarchy = process.env.BUNINU_OMARCHY_ROOT,
|
|
15
|
+
binary = process.env.FLEA_BIN;
|
|
16
|
+
test.skipIf(!root || !omarchy || !binary)(
|
|
17
|
+
'measure real Flea directory navigation and raw capture',
|
|
18
|
+
async () => {
|
|
19
|
+
const sandbox = fleaSandbox(root, omarchy),
|
|
20
|
+
oldPath = process.env.FLEA_PATH;
|
|
21
|
+
const home = sandbox.values.HOME,
|
|
22
|
+
folder = join(home, 'Documents');
|
|
23
|
+
mkdirSync(folder);
|
|
24
|
+
writeFileSync(join(home, 'hello.txt'), 'hello');
|
|
25
|
+
writeFileSync(join(folder, 'inside.txt'), 'inside');
|
|
26
|
+
process.env.FLEA_PATH = home;
|
|
27
|
+
let browser;
|
|
28
|
+
try {
|
|
29
|
+
browser = await createBrowser({
|
|
30
|
+
spareRenderer: false,
|
|
31
|
+
rendererTimeout: 30000,
|
|
32
|
+
});
|
|
33
|
+
await browser.context.addInitScript(
|
|
34
|
+
`globalThis.qmlErrors=[];addEventListener('error',e=>qmlErrors.push(String(e.error?.stack||e.message)));const original=console.error;console.error=(...args)=>{qmlErrors.push(args.map(String).join(' '));original(...args);};`,
|
|
35
|
+
);
|
|
36
|
+
if (process.env.BUNINU_QML_TIMING_TRACE)
|
|
37
|
+
await browser.context.addInitScript(`
|
|
38
|
+
globalThis.qmlTiming=[];
|
|
39
|
+
const stamp=(kind,detail)=>qmlTiming.push({at:Date.now(),kind,detail});
|
|
40
|
+
stamp('init',!!globalThis.__qmlHost);
|
|
41
|
+
const microtask=globalThis.queueMicrotask;
|
|
42
|
+
globalThis.queueMicrotask=fn=>microtask(()=>{const start=Date.now();try{return fn();}finally{const ms=Date.now()-start;if(ms>10)stamp('microtask',{ms,fn:String(fn).slice(0,180)});}});
|
|
43
|
+
const timeout=globalThis.setTimeout;
|
|
44
|
+
globalThis.setTimeout=(fn,delay,...args)=>timeout(()=>{const start=Date.now();try{return fn(...args);}finally{const ms=Date.now()-start;if(ms>10)stamp('timer',{ms,delay,fn:String(fn).slice(0,180)});}},delay);
|
|
45
|
+
|
|
46
|
+
if(globalThis.__qmlHost){
|
|
47
|
+
const host=globalThis.__qmlHost,run=host.run;
|
|
48
|
+
for(const name of ['compile','read','write']){
|
|
49
|
+
const original=host[name];if(!original)continue;
|
|
50
|
+
host[name]=(...args)=>{const start=Date.now();try{return original(...args);}finally{const ms=Date.now()-start;if(ms>10)stamp('host-'+name,{ms,path:args[0]});}};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
host.run=(command,callback,options={})=>{
|
|
54
|
+
const traced={...options};
|
|
55
|
+
for(const channel of ['stdout','stderr']) if(options[channel]) traced[channel]=chunk=>{
|
|
56
|
+
stamp(channel,String(chunk).slice(0,500));
|
|
57
|
+
const start=Date.now();try{return options[channel](chunk);}finally{stamp(channel+'-handled',Date.now()-start);}
|
|
58
|
+
};
|
|
59
|
+
const child=run.call(host,command,callback,traced),write=child.write;
|
|
60
|
+
if(write)child.write=text=>{stamp('stdin',String(text).slice(0,500));return write(text);};
|
|
61
|
+
return child;
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
addEventListener('keydown',e=>stamp('keydown',e.key),true);
|
|
65
|
+
`);
|
|
66
|
+
await browser.context.navigate(
|
|
67
|
+
Bun.pathToFileURL(join(sandbox.flea, 'ui/boot/shell.qml')).href,
|
|
68
|
+
);
|
|
69
|
+
const text = () => browser.context.evaluate('document.body.innerText');
|
|
70
|
+
const until = async (predicate) => {
|
|
71
|
+
for (let n = 0; n < 150; n++) {
|
|
72
|
+
if (await predicate()) return;
|
|
73
|
+
await Bun.sleep(40);
|
|
74
|
+
}
|
|
75
|
+
throw Error('Flea did not settle: ' + (await text()));
|
|
76
|
+
};
|
|
77
|
+
await until(async () => (await text()).includes('hello.txt'));
|
|
78
|
+
const results = [];
|
|
79
|
+
for (let i = 0; i < 6; i++) {
|
|
80
|
+
const entering = i % 2 === 0,
|
|
81
|
+
key = entering ? 'ArrowRight' : 'ArrowLeft';
|
|
82
|
+
const start = performance.now();
|
|
83
|
+
await browser.context.press(key, { code: key });
|
|
84
|
+
const dispatch = performance.now();
|
|
85
|
+
for (let n = 0; n < 300; n++) {
|
|
86
|
+
const content = await text();
|
|
87
|
+
if (
|
|
88
|
+
entering
|
|
89
|
+
? content.includes('inside.txt') && !content.includes('hello.txt')
|
|
90
|
+
: content.includes('hello.txt') && !content.includes('inside.txt')
|
|
91
|
+
)
|
|
92
|
+
break;
|
|
93
|
+
if (n === 299) throw Error('Navigation timed out: ' + key);
|
|
94
|
+
await Bun.sleep(10);
|
|
95
|
+
}
|
|
96
|
+
const ready = performance.now();
|
|
97
|
+
const frame = JSON.parse(
|
|
98
|
+
await browser.context.screenshot({ format: 'bgra' }),
|
|
99
|
+
);
|
|
100
|
+
if (frame.path) unlinkSync(frame.path);
|
|
101
|
+
const painted = performance.now();
|
|
102
|
+
results.push({
|
|
103
|
+
key,
|
|
104
|
+
dispatchMs: dispatch - start,
|
|
105
|
+
contentMs: ready - start,
|
|
106
|
+
captureMs: painted - ready,
|
|
107
|
+
totalMs: painted - start,
|
|
108
|
+
});
|
|
109
|
+
await Bun.sleep(250);
|
|
110
|
+
}
|
|
111
|
+
const fsTimes = [];
|
|
112
|
+
for (let i = 0; i < 20; i++) {
|
|
113
|
+
const start = performance.now();
|
|
114
|
+
for (const dir of [home, folder])
|
|
115
|
+
for (const name of readdirSync(dir)) statSync(join(dir, name));
|
|
116
|
+
fsTimes.push(performance.now() - start);
|
|
117
|
+
}
|
|
118
|
+
const trace = process.env.BUNINU_QML_TIMING_TRACE
|
|
119
|
+
? JSON.parse(
|
|
120
|
+
await browser.context.evaluate('JSON.stringify(qmlTiming)'),
|
|
121
|
+
)
|
|
122
|
+
: [];
|
|
123
|
+
const report = {
|
|
124
|
+
viewport: '800x600',
|
|
125
|
+
results,
|
|
126
|
+
fsReadBothDirsMs: fsTimes,
|
|
127
|
+
trace,
|
|
128
|
+
};
|
|
129
|
+
await Bun.write(
|
|
130
|
+
'/tmp/flea-navigation-timing.json',
|
|
131
|
+
JSON.stringify(report, null, 2),
|
|
132
|
+
);
|
|
133
|
+
console.log(JSON.stringify(report));
|
|
134
|
+
// Wait through the upstream prebuild driver's first idle tick too.
|
|
135
|
+
await Bun.sleep(1500);
|
|
136
|
+
expect(await browser.context.evaluate('JSON.stringify(qmlErrors)')).toBe(
|
|
137
|
+
'[]',
|
|
138
|
+
);
|
|
139
|
+
} finally {
|
|
140
|
+
browser?.close();
|
|
141
|
+
if (oldPath === undefined) delete process.env.FLEA_PATH;
|
|
142
|
+
else process.env.FLEA_PATH = oldPath;
|
|
143
|
+
sandbox.close();
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
60000,
|
|
147
|
+
);
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { test, expect } from 'bun:test';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { createBrowser } from '../lib/headless-shell.js';
|
|
4
|
+
import { encodeImage } from '../lib/paint/frame.js';
|
|
5
|
+
import { fleaSandbox } from './helpers/flea-sandbox.js';
|
|
6
|
+
const root=process.env.BUNINU_FLEA_ROOT,omarchy=process.env.BUNINU_OMARCHY_ROOT,binary=process.env.FLEA_BIN;
|
|
7
|
+
test.skipIf(!root||!omarchy||!binary)('official Flea backend opens and switches real text and image previews',async()=>{
|
|
8
|
+
const sandbox=fleaSandbox(root,omarchy),oldPath=process.env.FLEA_PATH;
|
|
9
|
+
let browser;
|
|
10
|
+
try {
|
|
11
|
+
process.env.FLEA_PATH=sandbox.values.HOME;
|
|
12
|
+
await Bun.write(join(sandbox.values.HOME,'first.txt'),'First real Flea preview\nDOM textures preserve the previous frame.\n'+'A long preview line.\n'.repeat(150));
|
|
13
|
+
await Bun.write(join(sandbox.values.HOME,'second.txt'),'Second real Flea preview\nThe official backend loaded this file.\n'+'Another long preview.\n'.repeat(170));
|
|
14
|
+
const rgba=new Uint8Array(320*160*4);
|
|
15
|
+
for(let y=0;y<160;y++)for(let x=0;x<320;x++)rgba.set(x<80?[0,100,255,255]:[255,0,0,255],(y*320+x)*4);
|
|
16
|
+
await Bun.write(join(sandbox.values.HOME,'third.png'),await encodeImage(rgba,320,160));
|
|
17
|
+
browser=await createBrowser({spareRenderer:false,rendererTimeout:20000});
|
|
18
|
+
await browser.context.resize(900,600);
|
|
19
|
+
await browser.context.addInitScript(`globalThis.qmlReaders=[];globalThis.qmlRows=[];let runtime;Object.defineProperty(globalThis,'__qmlRuntime',{get:()=>runtime,set:r=>{runtime=r;const describe=r.describe;r.describe=(v,t,...a)=>{if(t.includes('PreviewText'))qmlReaders.push(v);if(t.includes('Row'))qmlRows.push(v);return describe(v,t,...a)}}});globalThis.errors=[];addEventListener('error',e=>errors.push(String(e.error?.stack||e.message)));const old=console.error;console.error=(...a)=>{errors.push(a.map(String).join(' '));old(...a)};`);
|
|
20
|
+
await browser.context.navigate(Bun.pathToFileURL(join(sandbox.flea,'ui/boot/shell.qml')).href);
|
|
21
|
+
const run=code=>browser.context.evaluate(code);
|
|
22
|
+
async function until(fn){for(let i=0;i<120;i++){if(await fn())return;await Bun.sleep(30);}throw Error(await run('document.body.innerText+JSON.stringify(errors)'));}
|
|
23
|
+
await until(async()=>(await run('document.body.innerText')).includes('first.txt'));
|
|
24
|
+
const second=await run(`(()=>{const e=[...document.querySelectorAll('[data-qml-type=Text]')].find(e=>e.textContent==='second.txt'&&e.getBoundingClientRect().width>0);const r=e.getBoundingClientRect();return[r.x+r.width/2,r.y+r.height/2]})()`);
|
|
25
|
+
await browser.context.click(...second);await run('0');
|
|
26
|
+
expect(await run(`qmlRows.some(v=>v.row?.n==='second.txt'&&v.selected)`)).toBe(true);
|
|
27
|
+
const pt=await run(`(()=>{const e=[...document.querySelectorAll('[data-qml-type=Text]')].find(e=>e.textContent==='first.txt'&&e.getBoundingClientRect().width>0);if(!e)throw Error('row missing');const r=e.getBoundingClientRect();return [r.x+r.width/2,r.y+r.height/2]})()`);
|
|
28
|
+
await browser.context.click(...pt);
|
|
29
|
+
await browser.context.press(' ',{code:'Space'});
|
|
30
|
+
await until(async()=>(await run('document.body.innerText')).includes('First real Flea preview'));
|
|
31
|
+
await until(async()=>!(await run('document.body.innerText')).includes('reading'));
|
|
32
|
+
await Bun.write('/tmp/flea-real-preview-first.png',Buffer.from(await browser.context.screenshot({format:'png'}),'base64'));
|
|
33
|
+
await run(`qmlReaders.find(r=>r.active&&r.list).list.contentY=400`);await run('0');
|
|
34
|
+
expect(await run(`qmlReaders.find(r=>r.active&&r.list).list.contentY`)).toBeGreaterThan(0);
|
|
35
|
+
await browser.context.press('ArrowDown',{code:'ArrowDown'});
|
|
36
|
+
await until(async()=>(await run('document.body.innerText')).includes('Second real Flea preview'));
|
|
37
|
+
await until(async()=>await run(`qmlReaders.some(r=>r.active&&r.path.endsWith('/second.txt')&&r.list&&r.list.contentY===0)`));
|
|
38
|
+
expect(await run(`qmlReaders.find(r=>r.active&&r.path.endsWith('/second.txt')).list.contentY`)).toBe(0);
|
|
39
|
+
await Bun.sleep(100);
|
|
40
|
+
await Bun.write('/tmp/flea-real-preview-second.png',Buffer.from(await browser.context.screenshot({format:'png'}),'base64'));
|
|
41
|
+
await browser.context.press('ArrowDown',{code:'ArrowDown'});
|
|
42
|
+
await until(async()=>await run(`[...document.querySelectorAll('img')].some(e=>e.src.endsWith('/third.png')&&e.complete&&e.naturalWidth===320)`));
|
|
43
|
+
await until(async()=>!(await run('document.body.innerText')).includes('reading'));
|
|
44
|
+
const png=Buffer.from(await browser.context.screenshot({format:'png'}),'base64');
|
|
45
|
+
await Bun.write('/tmp/flea-real-preview-image.png',png);
|
|
46
|
+
const {CanvasKit:kit}=await import('../lib/renderer/page-renderer.js').then(m=>m.loadGraphics());
|
|
47
|
+
const image=kit.MakeImageFromEncoded(png);
|
|
48
|
+
try { expect([...image.readPixels(450,300,{width:1,height:1,colorType:kit.ColorType.RGBA_8888,alphaType:kit.AlphaType.Unpremul,colorSpace:kit.ColorSpace.SRGB})]).toEqual([255,0,0,255]); }
|
|
49
|
+
finally { image.delete(); }
|
|
50
|
+
expect(await run('errors')).toEqual([]);
|
|
51
|
+
await browser.context.press('Escape',{code:'Escape'});
|
|
52
|
+
}finally{browser?.close();if(oldPath===undefined)delete process.env.FLEA_PATH;else process.env.FLEA_PATH=oldPath;sandbox.close();}
|
|
53
|
+
},45000);
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { test, expect } from 'bun:test';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { createBrowser } from '../lib/headless-shell.js';
|
|
4
|
+
import { fleaSandbox } from './helpers/flea-sandbox.js';
|
|
5
|
+
const flea = process.env.BUNINU_FLEA_ROOT,
|
|
6
|
+
omarchy = process.env.BUNINU_OMARCHY_ROOT;
|
|
7
|
+
for (const [type, extension, contents, expected] of [
|
|
8
|
+
[
|
|
9
|
+
'PreviewText',
|
|
10
|
+
'txt',
|
|
11
|
+
Array.from({ length: 80 }, (_, i) => 'example line ' + i).join('\n'),
|
|
12
|
+
'example line 0',
|
|
13
|
+
],
|
|
14
|
+
['PreviewText', 'csv', 'Name,Count\nApple,12\nOrange,9\n', 'Apple'],
|
|
15
|
+
[
|
|
16
|
+
'PreviewMarkdown',
|
|
17
|
+
'md',
|
|
18
|
+
'# Flea preview\n\nA **bold** paragraph.\n\n- First item\n- Second item\n',
|
|
19
|
+
'Flea preview',
|
|
20
|
+
],
|
|
21
|
+
])
|
|
22
|
+
test.skipIf(!flea || !omarchy)(
|
|
23
|
+
'unchanged Flea ' + type + ' renders a real .' + extension + ' file',
|
|
24
|
+
async () => {
|
|
25
|
+
const sandbox = fleaSandbox(flea, omarchy);
|
|
26
|
+
let browser;
|
|
27
|
+
try {
|
|
28
|
+
const path = join(sandbox.dir, 'sample.' + extension);
|
|
29
|
+
await Bun.write(path, contents);
|
|
30
|
+
const file = join(sandbox.dir, 'main.qml');
|
|
31
|
+
await Bun.write(
|
|
32
|
+
file,
|
|
33
|
+
`import QtQuick
|
|
34
|
+
import ${JSON.stringify(join(sandbox.flea, 'ui'))} as Flea
|
|
35
|
+
Rectangle {width:800;height:600;color:Flea.Theme.color.background
|
|
36
|
+
Flea.${type} {id:preview;anchors.fill:parent;active:true;path:${JSON.stringify(path)};size:${Buffer.byteLength(contents)}}
|
|
37
|
+
}`,
|
|
38
|
+
);
|
|
39
|
+
browser = await createBrowser({
|
|
40
|
+
spareRenderer: false,
|
|
41
|
+
rendererTimeout: 30000,
|
|
42
|
+
});
|
|
43
|
+
await browser.context.addInitScript(
|
|
44
|
+
`globalThis.qmlErrors=[];addEventListener('error',e=>qmlErrors.push(String(e.error?.stack||e.message)));const old=console.error;console.error=(...args)=>{qmlErrors.push(args.map(String).join(' '));old(...args);};`,
|
|
45
|
+
);
|
|
46
|
+
await browser.context.navigate(Bun.pathToFileURL(file).href);
|
|
47
|
+
let text = '';
|
|
48
|
+
for (let i = 0; i < 150; i++) {
|
|
49
|
+
text = await browser.context.evaluate('document.body.innerText');
|
|
50
|
+
if (text.includes(expected)) break;
|
|
51
|
+
await Bun.sleep(40);
|
|
52
|
+
}
|
|
53
|
+
expect(
|
|
54
|
+
await browser.context.evaluate('JSON.stringify(qmlErrors)'),
|
|
55
|
+
).toBe('[]');
|
|
56
|
+
expect(text).toContain(expected);
|
|
57
|
+
await Bun.write(
|
|
58
|
+
'/tmp/buninu-flea-preview-' + extension + '.png',
|
|
59
|
+
Buffer.from(
|
|
60
|
+
await browser.context.screenshot({ format: 'png' }),
|
|
61
|
+
'base64',
|
|
62
|
+
),
|
|
63
|
+
);
|
|
64
|
+
} finally {
|
|
65
|
+
browser?.close();
|
|
66
|
+
sandbox.close();
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
60000,
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
for (const extension of ['svg', 'png'])
|
|
73
|
+
test.skipIf(!flea || !omarchy)(
|
|
74
|
+
'unchanged Flea PreviewImage loads, zooms and resets a real ' +
|
|
75
|
+
extension +
|
|
76
|
+
' image',
|
|
77
|
+
async () => {
|
|
78
|
+
const sandbox = fleaSandbox(flea, omarchy);
|
|
79
|
+
let browser;
|
|
80
|
+
try {
|
|
81
|
+
const path = join(sandbox.dir, 'sample.' + extension);
|
|
82
|
+
if (extension === 'svg')
|
|
83
|
+
await Bun.write(
|
|
84
|
+
path,
|
|
85
|
+
'<svg xmlns="http://www.w3.org/2000/svg" width="320" height="160"><rect width="160" height="160" fill="#ff0000"/><rect x="160" width="160" height="160" fill="#0000ff"/></svg>',
|
|
86
|
+
);
|
|
87
|
+
else {
|
|
88
|
+
const { encodeImage } = await import('../lib/paint/frame.js');
|
|
89
|
+
const pixels = new Uint8Array(320 * 160 * 4);
|
|
90
|
+
for (let y = 0; y < 160; y++)
|
|
91
|
+
for (let x = 0; x < 320; x++)
|
|
92
|
+
pixels.set(
|
|
93
|
+
x < 160 ? [255, 0, 0, 255] : [0, 0, 255, 255],
|
|
94
|
+
(y * 320 + x) * 4,
|
|
95
|
+
);
|
|
96
|
+
await Bun.write(path, await encodeImage(pixels, 320, 160));
|
|
97
|
+
}
|
|
98
|
+
const file = join(sandbox.dir, 'image.qml');
|
|
99
|
+
await Bun.write(
|
|
100
|
+
file,
|
|
101
|
+
`import QtQuick
|
|
102
|
+
import ${JSON.stringify(join(sandbox.flea, 'ui'))} as Flea
|
|
103
|
+
Rectangle {width:800;height:600;color:Flea.Theme.color.background
|
|
104
|
+
Flea.PreviewImage {id:preview;anchors.fill:parent;path:${JSON.stringify(path)}}
|
|
105
|
+
Text {id:status;text:preview.status+":"+preview.zoom+":"+preview.fullPicture.drawn}
|
|
106
|
+
Button {id:zoom;y:550;text:"Zoom";onClicked:preview.zoomBy(1)}
|
|
107
|
+
Button {id:fit;x:100;y:550;text:"Fit";onClicked:preview.zoomFit()}
|
|
108
|
+
}`,
|
|
109
|
+
);
|
|
110
|
+
browser = await createBrowser({
|
|
111
|
+
spareRenderer: false,
|
|
112
|
+
rendererTimeout: 30000,
|
|
113
|
+
});
|
|
114
|
+
await browser.context.addInitScript(
|
|
115
|
+
`globalThis.qmlErrors=[];addEventListener('error',e=>qmlErrors.push(String(e.error?.stack||e.message)));`,
|
|
116
|
+
);
|
|
117
|
+
await browser.context.navigate(Bun.pathToFileURL(file).href);
|
|
118
|
+
const until = async (predicate) => {
|
|
119
|
+
for (let i = 0; i < 150; i++) {
|
|
120
|
+
const status = await browser.context.evaluate(
|
|
121
|
+
'document.getElementById("status")?.textContent',
|
|
122
|
+
);
|
|
123
|
+
if (predicate(status)) return status;
|
|
124
|
+
await Bun.sleep(40);
|
|
125
|
+
}
|
|
126
|
+
throw Error(
|
|
127
|
+
await browser.context.evaluate(
|
|
128
|
+
'document.body.innerText+JSON.stringify(qmlErrors)',
|
|
129
|
+
),
|
|
130
|
+
);
|
|
131
|
+
};
|
|
132
|
+
await until((s) => s?.startsWith('image:1:'));
|
|
133
|
+
await browser.context.evaluate(
|
|
134
|
+
'document.getElementById("zoom").click()',
|
|
135
|
+
);
|
|
136
|
+
const zoomed = await until(
|
|
137
|
+
(s) =>
|
|
138
|
+
s?.startsWith('image:') &&
|
|
139
|
+
!s.startsWith('image:1:') &&
|
|
140
|
+
s.endsWith(':true'),
|
|
141
|
+
);
|
|
142
|
+
expect(zoomed).toContain(':true');
|
|
143
|
+
await Bun.write(
|
|
144
|
+
'/tmp/buninu-flea-preview-' + extension + '.png',
|
|
145
|
+
Buffer.from(
|
|
146
|
+
await browser.context.screenshot({ format: 'png' }),
|
|
147
|
+
'base64',
|
|
148
|
+
),
|
|
149
|
+
);
|
|
150
|
+
await browser.context.evaluate(
|
|
151
|
+
'document.getElementById("fit").click()',
|
|
152
|
+
);
|
|
153
|
+
await until((s) => s === 'image:1:false');
|
|
154
|
+
expect(
|
|
155
|
+
await browser.context.evaluate('JSON.stringify(qmlErrors)'),
|
|
156
|
+
).toBe('[]');
|
|
157
|
+
} finally {
|
|
158
|
+
browser?.close();
|
|
159
|
+
sandbox.close();
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
60000,
|
|
163
|
+
);
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// Component tests with ordinary input data, not a replacement Flea backend.
|
|
2
|
+
import { test, expect } from 'bun:test';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { createBrowser } from '../lib/headless-shell.js';
|
|
5
|
+
import { fleaSandbox } from './helpers/flea-sandbox.js';
|
|
6
|
+
const flea = process.env.BUNINU_FLEA_ROOT,
|
|
7
|
+
omarchy = process.env.BUNINU_OMARCHY_ROOT;
|
|
8
|
+
for (const type of ['Row', 'GridTile'])
|
|
9
|
+
test.skipIf(!flea || !omarchy)(
|
|
10
|
+
'unchanged Flea ' +
|
|
11
|
+
type +
|
|
12
|
+
' displays file metadata and reacts to changed input',
|
|
13
|
+
async () => {
|
|
14
|
+
const sandbox = fleaSandbox(flea, omarchy);
|
|
15
|
+
let browser;
|
|
16
|
+
try {
|
|
17
|
+
const file = join(sandbox.dir, 'rows.qml');
|
|
18
|
+
await Bun.write(
|
|
19
|
+
file,
|
|
20
|
+
`import QtQuick
|
|
21
|
+
import ${JSON.stringify(join(sandbox.flea, 'ui'))} as Flea
|
|
22
|
+
Rectangle {id:root;width:800;height:600;color:Flea.Theme.color.background
|
|
23
|
+
property var entry:({n:"example-notes.txt",s:12345,m:1700000000,p:33188,i:"file",k:0,d:false})
|
|
24
|
+
Flea.${type} {id:row;x:20;y:30;width:${type === 'Row' ? 760 : 260};height:${type === 'Row' ? 60 : 300};row:root.entry;cursor:true}
|
|
25
|
+
${
|
|
26
|
+
type === 'GridTile'
|
|
27
|
+
? `Text {id:captionMetrics;x:400;y:400;text:row.captionItem.lineCount+":"+row.captionItem.truncated}
|
|
28
|
+
Button {id:longName;x:200;y:500;text:"Long filename";onClicked:root.entry=({n:"a very long file name with many words and notes.txt",s:456,m:1700000000,p:33188,i:"file",k:0,d:false})}`
|
|
29
|
+
: ''
|
|
30
|
+
}
|
|
31
|
+
Button {id:change;y:500;text:"Change input";onClicked:root.entry=({n:"renamed-notes.txt",s:456,m:1700000000,p:33188,i:"file",k:0,d:false})}
|
|
32
|
+
}`,
|
|
33
|
+
);
|
|
34
|
+
browser = await createBrowser({
|
|
35
|
+
spareRenderer: false,
|
|
36
|
+
rendererTimeout: 30000,
|
|
37
|
+
});
|
|
38
|
+
await browser.context.addInitScript(
|
|
39
|
+
`globalThis.qmlErrors=[];addEventListener('error',e=>qmlErrors.push(String(e.error?.stack||e.message)));const old=console.error;console.error=(...args)=>{qmlErrors.push(args.map(String).join(' '));old(...args);};`,
|
|
40
|
+
);
|
|
41
|
+
await browser.context.navigate(Bun.pathToFileURL(file).href);
|
|
42
|
+
const ready = await browser.context.evaluate(
|
|
43
|
+
'!!document.getElementById("change")',
|
|
44
|
+
);
|
|
45
|
+
if (!ready)
|
|
46
|
+
throw Error(
|
|
47
|
+
await browser.context.evaluate(
|
|
48
|
+
'document.querySelector("p")?.textContent||document.body.innerText',
|
|
49
|
+
),
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
const until = async (word) => {
|
|
53
|
+
for (let i = 0; i < 150; i++) {
|
|
54
|
+
const text = await browser.context.evaluate(
|
|
55
|
+
'document.body.innerText',
|
|
56
|
+
);
|
|
57
|
+
if (text.includes(word)) return;
|
|
58
|
+
await Bun.sleep(40);
|
|
59
|
+
}
|
|
60
|
+
throw Error(
|
|
61
|
+
await browser.context.evaluate(
|
|
62
|
+
'document.body.innerText+JSON.stringify(qmlErrors)',
|
|
63
|
+
),
|
|
64
|
+
);
|
|
65
|
+
};
|
|
66
|
+
await until('example-notes.txt');
|
|
67
|
+
await browser.context.evaluate(
|
|
68
|
+
'document.getElementById("change").click()',
|
|
69
|
+
);
|
|
70
|
+
await until('renamed-notes.txt');
|
|
71
|
+
if (type === 'GridTile') {
|
|
72
|
+
await browser.context.evaluate(
|
|
73
|
+
'document.getElementById("longName").click()',
|
|
74
|
+
);
|
|
75
|
+
let metric = '';
|
|
76
|
+
for (let i = 0; i < 100; i++) {
|
|
77
|
+
metric = await browser.context.evaluate(
|
|
78
|
+
'document.getElementById("captionMetrics").textContent',
|
|
79
|
+
);
|
|
80
|
+
if (metric === '2:false') break;
|
|
81
|
+
await Bun.sleep(20);
|
|
82
|
+
}
|
|
83
|
+
expect(metric).toBe('2:false');
|
|
84
|
+
}
|
|
85
|
+
expect(
|
|
86
|
+
await browser.context.evaluate('JSON.stringify(qmlErrors)'),
|
|
87
|
+
).toBe('[]');
|
|
88
|
+
await Bun.write(
|
|
89
|
+
'/tmp/buninu-flea-' + type + '.png',
|
|
90
|
+
Buffer.from(
|
|
91
|
+
await browser.context.screenshot({ format: 'png' }),
|
|
92
|
+
'base64',
|
|
93
|
+
),
|
|
94
|
+
);
|
|
95
|
+
} finally {
|
|
96
|
+
browser?.close();
|
|
97
|
+
sandbox.close();
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
60000,
|
|
101
|
+
);
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { test, expect } from 'bun:test';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { createBrowser } from '../lib/headless-shell.js';
|
|
4
|
+
import { loadGraphics } from '../lib/renderer/page-renderer.js';
|
|
5
|
+
import { fleaSandbox } from './helpers/flea-sandbox.js';
|
|
6
|
+
const flea=process.env.BUNINU_FLEA_ROOT, omarchy=process.env.BUNINU_OMARCHY_ROOT;
|
|
7
|
+
for(const kind of ['swap','clip']) test.skipIf(!flea||!omarchy)('unchanged Flea '+kind+' paints actual texture pixels',async()=>{
|
|
8
|
+
const sandbox=fleaSandbox(flea,omarchy);
|
|
9
|
+
let browser;
|
|
10
|
+
try {
|
|
11
|
+
const content=kind==='swap'?`
|
|
12
|
+
property bool complete:true
|
|
13
|
+
Flea.PreviewSwap {id:swap;x:20;y:20;width:180;height:100;ready:complete;folderHold:true
|
|
14
|
+
Rectangle {id:source;anchors.fill:parent;color:"red"}}
|
|
15
|
+
Button {id:move;y:150;text:"Move";onClicked:{complete=false;swap.hold(function(){source.color="blue";swap.start(false)},1)}}
|
|
16
|
+
Button {id:finish;x:100;y:150;text:"Finish";onClicked:{swap.folderHold=false;complete=true}}
|
|
17
|
+
Text {id:status;y:200;text:JSON.stringify(swap.describe())}
|
|
18
|
+
`:`
|
|
19
|
+
Rectangle {id:source;x:20;y:20;width:100;height:60;color:"red"}
|
|
20
|
+
Flea.HtmlClip {id:clip;x:150;y:20;width:100;height:60;radius:20;target:source}
|
|
21
|
+
Button {id:blue;y:150;text:"Blue";onClicked:source.color="blue"}
|
|
22
|
+
`;
|
|
23
|
+
const file=join(sandbox.dir,'main.qml');
|
|
24
|
+
await Bun.write(file,`import QtQuick\nimport ${JSON.stringify(join(sandbox.flea,'ui'))} as Flea\nRectangle {width:400;height:240;color:"white"\n${content}\n}`);
|
|
25
|
+
browser=await createBrowser({spareRenderer:false,rendererTimeout:15000});
|
|
26
|
+
await browser.context.resize(400,240);
|
|
27
|
+
await browser.context.addInitScript('globalThis.errors=[];addEventListener("error",e=>errors.push(String(e.error?.stack||e.message)))');
|
|
28
|
+
await browser.context.navigate(Bun.pathToFileURL(file).href);
|
|
29
|
+
const run=code=>browser.context.evaluate(code);
|
|
30
|
+
async function until(fn){for(let i=0;i<70;i++){if(await fn())return;await Bun.sleep(20);}throw Error(await run('document.body.innerText+JSON.stringify(errors)'));}
|
|
31
|
+
const {CanvasKit:kit}=await loadGraphics();
|
|
32
|
+
async function capture(){return Buffer.from(await browser.context.screenshot({format:'png'}),'base64');}
|
|
33
|
+
function pixel(bytes,x,y){const image=kit.MakeImageFromEncoded(bytes);try{return [...image.readPixels(x,y,{width:1,height:1,colorType:kit.ColorType.RGBA_8888,alphaType:kit.AlphaType.Unpremul,colorSpace:kit.ColorSpace.SRGB})];}finally{image.delete();}}
|
|
34
|
+
if(kind==='swap'){
|
|
35
|
+
await until(async()=>await run('!!document.getElementById("move")'));
|
|
36
|
+
expect(pixel(await capture(),30,30)).toEqual([255,0,0,255]);
|
|
37
|
+
await run('document.getElementById("move").click()');
|
|
38
|
+
await until(async()=>await run('JSON.parse(document.getElementById("status").textContent).holding'));
|
|
39
|
+
const held=await capture();await Bun.write('/tmp/flea-preview-held.png',held);
|
|
40
|
+
expect(pixel(held,30,30)).toEqual([255,0,0,255]);
|
|
41
|
+
expect(await run('document.getElementById("source").style.backgroundColor')).toBe('blue');
|
|
42
|
+
await run('document.getElementById("finish").click()');
|
|
43
|
+
await until(async()=>!(await run('JSON.parse(document.getElementById("status").textContent).holding')));
|
|
44
|
+
const done=await capture();await Bun.write('/tmp/flea-preview-released.png',done);
|
|
45
|
+
expect(pixel(done,30,30)).toEqual([0,0,255,255]);
|
|
46
|
+
expect(await run('JSON.parse(document.getElementById("status").textContent).last.end')).toBe('landed');
|
|
47
|
+
}else{
|
|
48
|
+
await until(async()=>pixel(await capture(),200,50)[0]===255&&pixel(await capture(),200,50)[1]===0);
|
|
49
|
+
const rounded=await capture();await Bun.write('/tmp/flea-preview-rounded.png',rounded);
|
|
50
|
+
expect(pixel(rounded,25,25)).toEqual([255,255,255,255]);
|
|
51
|
+
expect(pixel(rounded,151,21)).toEqual([255,255,255,255]);
|
|
52
|
+
expect(pixel(rounded,200,50)).toEqual([255,0,0,255]);
|
|
53
|
+
await run('document.getElementById("blue").click()');
|
|
54
|
+
await until(async()=>pixel(await capture(),200,50)[0]===0);
|
|
55
|
+
expect(pixel(await capture(),200,50)).toEqual([0,0,255,255]);
|
|
56
|
+
}
|
|
57
|
+
expect(await run('errors')).toEqual([]);
|
|
58
|
+
}finally{browser?.close();sandbox.close();}
|
|
59
|
+
},30000);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { test, expect } from 'bun:test';
|
|
2
|
+
import { mkdirSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { createBrowser } from '../lib/headless-shell.js';
|
|
5
|
+
import { fleaSandbox } from './helpers/flea-sandbox.js';
|
|
6
|
+
const flea=process.env.BUNINU_FLEA_ROOT, omarchy=process.env.BUNINU_OMARCHY_ROOT;
|
|
7
|
+
test.skipIf(!flea||!omarchy||!process.env.FLEA_BIN)('Flea hides retained columns when switching back to list',async()=>{
|
|
8
|
+
const sandbox=fleaSandbox(flea,omarchy), oldPath=process.env.FLEA_PATH;
|
|
9
|
+
let browser;
|
|
10
|
+
try {
|
|
11
|
+
process.env.FLEA_PATH=sandbox.values.HOME;
|
|
12
|
+
mkdirSync(join(sandbox.values.HOME,'Documents'));
|
|
13
|
+
await Bun.write(join(sandbox.values.HOME,'Documents/inside.txt'),'inside');
|
|
14
|
+
await Bun.write(join(sandbox.values.HOME,'hello.md'),'hello');
|
|
15
|
+
await Bun.write(join(sandbox.values.HOME,'second.js'),'second');
|
|
16
|
+
browser=await createBrowser({spareRenderer:false,rendererTimeout:30000});
|
|
17
|
+
await browser.context.resize(800,600);
|
|
18
|
+
await browser.context.addInitScript(`globalThis.views=[];let rt;Object.defineProperty(globalThis,"__qmlRuntime",{get:()=>rt,set:r=>{rt=r;const d=r.describe;r.describe=(v,t,...a)=>{if(t.includes("ColumnsArea")||t.includes("Pane"))views.push({v,t});return d(v,t,...a)}}});globalThis.qmlErrors=[];addEventListener('error',e=>qmlErrors.push(String(e.error?.stack||e.message)));`);
|
|
19
|
+
await browser.context.navigate(Bun.pathToFileURL(join(sandbox.flea,'ui/boot/shell.qml')).href);
|
|
20
|
+
const names=column=>browser.context.evaluate(`Array.from(document.querySelectorAll('[data-qml-type="Text"]')).map(e=>({text:e.textContent,r:e.getBoundingClientRect()})).filter(({text,r})=>/^(cache|config|data|home|imports|state|Documents|inside.txt|hello.md|second.js)$/.test(text)&&r.width>0&&r.y>20&&r.x>=${column===0?200:500}&&r.x<${column===0?500:800}).sort((a,b)=>a.r.y-b.r.y).map(e=>e.text)`);
|
|
21
|
+
async function until(column,wanted){for(let i=0;i<150;i++){const actual=await names(column);if(JSON.stringify(actual)===JSON.stringify(wanted))return;await Bun.sleep(30);}expect(await names(column)).toEqual(wanted);}
|
|
22
|
+
await until(0,['Documents','hello.md','second.js']);
|
|
23
|
+
await browser.context.press('2',{code:'Digit2',modifiers:2});
|
|
24
|
+
await until(1,['inside.txt']);
|
|
25
|
+
await browser.context.press('1',{code:'Digit1',modifiers:2});
|
|
26
|
+
await Bun.sleep(100);
|
|
27
|
+
expect(await browser.context.evaluate(`views.find(({t})=>t.includes('Pane')).v.viewMode`)).toBe('list');
|
|
28
|
+
expect(await browser.context.evaluate(`views.find(({t})=>t.includes('Pane')).v.columnsArea.visible`)).toBe(false);
|
|
29
|
+
await until(1,[]);
|
|
30
|
+
await Bun.write('/tmp/flea-list-switch.png',Buffer.from(await browser.context.screenshot({format:'png'}),'base64'));
|
|
31
|
+
expect(await browser.context.evaluate('qmlErrors')).toEqual([]);
|
|
32
|
+
}finally{browser?.close();if(oldPath===undefined)delete process.env.FLEA_PATH;else process.env.FLEA_PATH=oldPath;sandbox.close();}
|
|
33
|
+
},60000);
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// Opt-in real socket test; owns its compositor and never touches a user's Land.
|
|
2
|
+
import { test, expect } from 'bun:test';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { fleaSandbox } from './helpers/flea-sandbox.js';
|
|
5
|
+
import { launch } from '../frontends/wayland/index.js';
|
|
6
|
+
const flea = process.env.BUNINU_FLEA_ROOT,
|
|
7
|
+
omarchy = process.env.BUNINU_OMARCHY_ROOT;
|
|
8
|
+
test.skipIf(!flea || !omarchy || !process.env.BUNINU_QML_WAYLAND_TEST)(
|
|
9
|
+
'real Flea receives Wayland keys and closes through Super-W',
|
|
10
|
+
async () => {
|
|
11
|
+
const { start } = await import('../../buninu-land/compositor.js');
|
|
12
|
+
const sandbox = fleaSandbox(flea, omarchy),
|
|
13
|
+
oldRuntime = process.env.XDG_RUNTIME_DIR;
|
|
14
|
+
let land, app;
|
|
15
|
+
try {
|
|
16
|
+
process.env.XDG_RUNTIME_DIR = sandbox.dir;
|
|
17
|
+
land = await start([
|
|
18
|
+
'--remote-debugging-port=0',
|
|
19
|
+
'--window-size=1000,720',
|
|
20
|
+
]);
|
|
21
|
+
app = await launch({
|
|
22
|
+
url: Bun.pathToFileURL(join(sandbox.flea, 'ui/boot/shell.qml')).href,
|
|
23
|
+
width: 1000,
|
|
24
|
+
height: 720,
|
|
25
|
+
socket: join(sandbox.dir, 'wayland-buninu'),
|
|
26
|
+
});
|
|
27
|
+
const until = async (predicate) => {
|
|
28
|
+
for (let i = 0; i < 300; i++) {
|
|
29
|
+
if (await predicate()) return;
|
|
30
|
+
await Bun.sleep(40);
|
|
31
|
+
}
|
|
32
|
+
throw Error('Flea Wayland state did not settle');
|
|
33
|
+
};
|
|
34
|
+
const text = () =>
|
|
35
|
+
app.browser.context.evaluate('document.body.innerText');
|
|
36
|
+
await until(() =>
|
|
37
|
+
land.compositor.wm.renderSurfaces().some((s) => s.pixels),
|
|
38
|
+
);
|
|
39
|
+
expect(await text()).toContain('PLACES');
|
|
40
|
+
const key = async (key, code, modifiers = 0) => {
|
|
41
|
+
for (const type of ['keyDown', 'keyUp'])
|
|
42
|
+
land.context.dispatchKeyEvent({ type, key, code, modifiers });
|
|
43
|
+
await Bun.sleep(100);
|
|
44
|
+
};
|
|
45
|
+
await key(',', 'Comma');
|
|
46
|
+
await until(async () => (await text()).includes('Settings'));
|
|
47
|
+
await Bun.write(
|
|
48
|
+
'/tmp/buninu-flea-wayland.png',
|
|
49
|
+
Buffer.from(await land.context.screenshot({ format: 'png' }), 'base64'),
|
|
50
|
+
);
|
|
51
|
+
await key('Escape', 'Escape');
|
|
52
|
+
await until(async () => !(await text()).includes('Settings'));
|
|
53
|
+
let closed = false;
|
|
54
|
+
app.done.then(() => (closed = true));
|
|
55
|
+
await key('w', 'KeyW', 4);
|
|
56
|
+
await until(() => closed);
|
|
57
|
+
await until(() => !land.compositor.wm.visible.length);
|
|
58
|
+
expect(closed).toBe(true);
|
|
59
|
+
} finally {
|
|
60
|
+
app?.close();
|
|
61
|
+
land?.close();
|
|
62
|
+
if (oldRuntime === undefined) delete process.env.XDG_RUNTIME_DIR;
|
|
63
|
+
else process.env.XDG_RUNTIME_DIR = oldRuntime;
|
|
64
|
+
sandbox.close();
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
120000,
|
|
68
|
+
);
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { test, expect } from 'bun:test';
|
|
2
|
+
import { createBrowser } from '../lib/headless-shell.js';
|
|
3
|
+
|
|
4
|
+
test('Flea EscapeHint imports a real singleton and shares reactive theme updates', async () => {
|
|
5
|
+
const browser = await createBrowser({ spareRenderer: false });
|
|
6
|
+
try {
|
|
7
|
+
await browser.context.resize(640, 400, 1);
|
|
8
|
+
await browser.context.navigate(
|
|
9
|
+
new URL('./fixtures/qml/flea/demo.qml', import.meta.url).href,
|
|
10
|
+
);
|
|
11
|
+
expect(await browser.context.evaluate('document.title')).toBe(
|
|
12
|
+
'Browser — Flea real import test',
|
|
13
|
+
);
|
|
14
|
+
const colors = () =>
|
|
15
|
+
browser.context.evaluate(
|
|
16
|
+
"JSON.stringify([...document.querySelectorAll('[data-qml-type=Text]')].filter(e=>e.textContent==='esc').map(e=>e.style.color))",
|
|
17
|
+
);
|
|
18
|
+
expect(JSON.parse(await colors())).toEqual(['#70e1e8', '#70e1e8']);
|
|
19
|
+
await browser.context.evaluate('document.querySelector("button").click()');
|
|
20
|
+
expect(JSON.parse(await colors())).toEqual(['#ffcc66', '#ffcc66']);
|
|
21
|
+
await browser.context.evaluate('document.querySelector("button").click()');
|
|
22
|
+
expect(JSON.parse(await colors())).toEqual(['#70e1e8', '#70e1e8']);
|
|
23
|
+
expect(await browser.context.evaluate('typeof Theme')).toBe('undefined');
|
|
24
|
+
await Bun.write(
|
|
25
|
+
'/tmp/browser-flea-import.png',
|
|
26
|
+
Buffer.from(
|
|
27
|
+
await browser.context.screenshot({ format: 'png' }),
|
|
28
|
+
'base64',
|
|
29
|
+
),
|
|
30
|
+
);
|
|
31
|
+
} finally {
|
|
32
|
+
browser.close();
|
|
33
|
+
}
|
|
34
|
+
}, 30000);
|