This commit is contained in:
Kacper 2026-05-04 18:02:42 +01:00 committed by GitHub
commit c2940ba065
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 557 additions and 28 deletions

22
__mocks__/obsidian.ts Normal file
View file

@ -0,0 +1,22 @@
export class Component {
private _cleanups: (() => any)[] = [];
load() { this.onload(); }
onload() {}
unload() { this._cleanups.forEach(cb => cb()); this.onunload(); }
onunload() {}
register(cb: () => any) { this._cleanups.push(cb); }
addChild<T extends Component>(c: T) { return c; }
removeChild<T extends Component>(c: T) { return c; }
}
export class MarkdownRenderChild extends Component {
containerEl: HTMLElement;
constructor(containerEl: HTMLElement) {
super();
this.containerEl = containerEl;
}
}
export class App {}
export class Plugin extends Component {}
export class TFile {}

View file

@ -4,6 +4,7 @@ export default {
extensionsToTreatAsEsm: ['.ts'],
setupFilesAfterEnv: ['<rootDir>/jest.setup.mjs'],
moduleNameMapper: {
'^obsidian$': '<rootDir>/__mocks__/obsidian.ts',
'^virtual:wa-sqlite-wasm-url$': '<rootDir>/src/modules/explorer/database/__tests__/__mocks__/wa-sqlite-wasm-url.ts',
'^(\\.{1,2}/.*)\\.js$': '$1',
},

View file

@ -41,6 +41,7 @@
"esbuild-plugin-replace": "^1.4.0",
"esbuild-sass-plugin": "^3.3.1",
"jest": "^30.0.5",
"jest-environment-jsdom": "^30.3.0",
"obsidian": "^1.8.7",
"prettier": "3.6.2",
"ts-jest": "^29.4.1",

View file

@ -138,6 +138,9 @@ importers:
jest:
specifier: ^30.0.5
version: 30.0.5(@types/node@24.2.1)
jest-environment-jsdom:
specifier: ^30.3.0
version: 30.3.0
obsidian:
specifier: ^1.8.7
version: 1.8.7(@codemirror/state@6.5.2)(@codemirror/view@6.38.1)
@ -178,6 +181,9 @@ packages:
'@antfu/utils@8.1.1':
resolution: {integrity: sha512-Mex9nXf9vR6AhcXmMrlz/HVgYYZpVGJ6YlPgwl7UnaFpnshXs6EK/oa5Gpf3CzENMjkvEx2tQtntGnb7UtSTOQ==}
'@asamuzakjp/css-color@3.2.0':
resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==}
'@babel/code-frame@7.27.1':
resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==}
engines: {node: '>=6.9.0'}
@ -432,6 +438,34 @@ packages:
'@codemirror/view@6.38.1':
resolution: {integrity: sha512-RmTOkE7hRU3OVREqFVITWHz6ocgBjv08GoePscAakgVQfciA3SGCEk7mb9IzwW61cKKmlTpHXG6DUE5Ubx+MGQ==}
'@csstools/color-helpers@5.1.0':
resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==}
engines: {node: '>=18'}
'@csstools/css-calc@2.1.4':
resolution: {integrity: sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==}
engines: {node: '>=18'}
peerDependencies:
'@csstools/css-parser-algorithms': ^3.0.5
'@csstools/css-tokenizer': ^3.0.4
'@csstools/css-color-parser@3.1.0':
resolution: {integrity: sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==}
engines: {node: '>=18'}
peerDependencies:
'@csstools/css-parser-algorithms': ^3.0.5
'@csstools/css-tokenizer': ^3.0.4
'@csstools/css-parser-algorithms@3.0.5':
resolution: {integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==}
engines: {node: '>=18'}
peerDependencies:
'@csstools/css-tokenizer': ^3.0.4
'@csstools/css-tokenizer@3.0.4':
resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==}
engines: {node: '>=18'}
'@docsearch/css@4.0.0-beta.7':
resolution: {integrity: sha512-hBIwf14yLasrUcDNS7jrneM1ibFD/JFJVDjdxd1h/LUHx7eyLrS726pKHVr3cTdToNXP/7jrTbnC1MAuDHPoow==}
@ -717,6 +751,16 @@ packages:
resolution: {integrity: sha512-cG51MVnLq1ecVUaQ3fr6YuuAOitHK1S4WUJHnsPFE/quQr33ADUx1FfrTCpMCRxvy0Yr9BThKpDjSlcTi91tMA==}
engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
'@jest/environment-jsdom-abstract@30.3.0':
resolution: {integrity: sha512-0hNFs5N6We3DMCwobzI0ydhkY10sT1tZSC0AAiy+0g2Dt/qEWgrcV5BrMxPczhe41cxW4qm6X+jqZaUdpZIajA==}
engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
peerDependencies:
canvas: ^3.0.0
jsdom: '*'
peerDependenciesMeta:
canvas:
optional: true
'@jest/environment@30.0.5':
resolution: {integrity: sha512-aRX7WoaWx1oaOkDQvCWImVQ8XNtdv5sEWgk4gxR6NXb7WBUnL5sRak4WRzIQRZ1VTWPvV4VI4mgGjNL9TeKMYA==}
engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
@ -1347,6 +1391,9 @@ packages:
'@types/jest@30.0.0':
resolution: {integrity: sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==}
'@types/jsdom@21.1.7':
resolution: {integrity: sha512-yOriVnggzrnQ3a9OKOCxaVuSug3w3/SbOj5i7VwXWZEyUNl3bLF9V3MfxGbZKuwqJOQyRfqXyROBB1CoZLFWzA==}
'@types/json-schema@7.0.15':
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
@ -1383,6 +1430,9 @@ packages:
'@types/tern@0.23.9':
resolution: {integrity: sha512-ypzHFE/wBzh+BlH6rrBgS5I/Z7RD21pGhZ2rltb/+ZrVM1awdZwjx7hE5XfuYgHWk9uvV5HLZN3SloevCAp3Bw==}
'@types/tough-cookie@4.0.5':
resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==}
'@types/trusted-types@2.0.7':
resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
@ -1738,6 +1788,10 @@ packages:
ag-grid-community@34.1.1:
resolution: {integrity: sha512-ODVvGoMTkyGvMT8b5lzvum5r93bG6CKdJdNrk6u/aYS7oqZ5rUEXJJHC8n8Zq+o76KhFiXMBQrU39xuhz8i+Tg==}
agent-base@7.1.4:
resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==}
engines: {node: '>= 14'}
ajv@6.12.6:
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
@ -1995,6 +2049,10 @@ packages:
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
engines: {node: '>= 8'}
cssstyle@4.6.0:
resolution: {integrity: sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==}
engines: {node: '>=18'}
csstype@3.1.3:
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
@ -2154,6 +2212,10 @@ packages:
dagre-d3-es@7.0.11:
resolution: {integrity: sha512-tvlJLyQf834SylNKax8Wkzco/1ias1OPw8DcUMDE7oUIoSEW25riQVuiu/0OWEFqT0cxHT3Pa9/D82Jr47IONw==}
data-urls@5.0.0:
resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==}
engines: {node: '>=18'}
dayjs@1.11.13:
resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==}
@ -2175,6 +2237,9 @@ packages:
supports-color:
optional: true
decimal.js@10.6.0:
resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==}
dedent@1.6.0:
resolution: {integrity: sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==}
peerDependencies:
@ -2252,6 +2317,10 @@ packages:
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
engines: {node: '>=0.12'}
entities@6.0.1:
resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==}
engines: {node: '>=0.12'}
error-ex@1.3.2:
resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==}
@ -2602,12 +2671,24 @@ packages:
hookable@5.5.3:
resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==}
html-encoding-sniffer@4.0.0:
resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==}
engines: {node: '>=18'}
html-escaper@2.0.2:
resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==}
html-void-elements@3.0.0:
resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==}
http-proxy-agent@7.0.2:
resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==}
engines: {node: '>= 14'}
https-proxy-agent@7.0.6:
resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
engines: {node: '>= 14'}
human-id@4.1.1:
resolution: {integrity: sha512-3gKm/gCSUipeLsRYZbbdA1BD83lBoWUkZ7G9VFrhWPAU76KwYo5KR8V28bpoPm/ygy0x5/GCbpRQdY7VLYCoIg==}
hasBin: true
@ -2704,6 +2785,9 @@ packages:
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
engines: {node: '>=0.12.0'}
is-potential-custom-element-name@1.0.1:
resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==}
is-regex@1.2.1:
resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==}
engines: {node: '>= 0.4'}
@ -2803,6 +2887,15 @@ packages:
resolution: {integrity: sha512-dKjRsx1uZ96TVyejD3/aAWcNKy6ajMaN531CwWIsrazIqIoXI9TnnpPlkrEYku/8rkS3dh2rbH+kMOyiEIv0xQ==}
engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
jest-environment-jsdom@30.3.0:
resolution: {integrity: sha512-RLEOJy6ip1lpw0yqJ8tB3i88FC7VBz7i00Zvl2qF71IdxjS98gC9/0SPWYIBVXHm5hgCYK0PAlSlnHGGy9RoMg==}
engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
peerDependencies:
canvas: ^3.0.0
peerDependenciesMeta:
canvas:
optional: true
jest-environment-node@30.0.5:
resolution: {integrity: sha512-ppYizXdLMSvciGsRsMEnv/5EFpvOdXBaXRBzFUDPWrsfmog4kYrOGWXarLllz6AXan6ZAA/kYokgDWuos1IKDA==}
engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
@ -2925,6 +3018,15 @@ packages:
resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
hasBin: true
jsdom@26.1.0:
resolution: {integrity: sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==}
engines: {node: '>=18'}
peerDependencies:
canvas: ^3.0.0
peerDependenciesMeta:
canvas:
optional: true
jsesc@3.1.0:
resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==}
engines: {node: '>=6'}
@ -3160,6 +3262,9 @@ packages:
resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==}
engines: {node: '>=8'}
nwsapi@2.2.23:
resolution: {integrity: sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==}
obliterator@2.0.5:
resolution: {integrity: sha512-42CPE9AhahZRsMNslczq0ctAEtqk8Eka26QofnqC346BZdHDySk3LWka23LI7ULIw11NmltpiLagIq8gBozxTw==}
@ -3252,6 +3357,9 @@ packages:
resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
engines: {node: '>=8'}
parse5@7.3.0:
resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==}
path-data-parser@0.1.0:
resolution: {integrity: sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==}
@ -3433,6 +3541,9 @@ packages:
roughjs@4.6.6:
resolution: {integrity: sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==}
rrweb-cssom@0.8.0:
resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==}
run-parallel@1.2.0:
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
@ -3582,6 +3693,10 @@ packages:
engines: {node: '>=14.0.0'}
hasBin: true
saxes@6.0.0:
resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==}
engines: {node: '>=v12.22.7'}
semver@6.3.1:
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
hasBin: true
@ -3719,6 +3834,9 @@ packages:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
engines: {node: '>= 0.4'}
symbol-tree@3.2.4:
resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
sync-child-process@1.0.2:
resolution: {integrity: sha512-8lD+t2KrrScJ/7KXCSyfhT3/hRq78rC0wBFqNJXv3mZyn6hW2ypM05JmlSvtqRbeq6jqA94oHbxAr2vYsJ8vDA==}
engines: {node: '>=16.0.0'}
@ -3753,6 +3871,13 @@ packages:
resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
engines: {node: '>=12.0.0'}
tldts-core@6.1.86:
resolution: {integrity: sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==}
tldts@6.1.86:
resolution: {integrity: sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==}
hasBin: true
tmp@0.0.33:
resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==}
engines: {node: '>=0.6.0'}
@ -3764,6 +3889,14 @@ packages:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'}
tough-cookie@5.1.2:
resolution: {integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==}
engines: {node: '>=16'}
tr46@5.1.1:
resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==}
engines: {node: '>=18'}
trim-lines@3.0.1:
resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==}
@ -4038,12 +4171,33 @@ packages:
w3c-keyname@2.2.8:
resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==}
w3c-xmlserializer@5.0.0:
resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==}
engines: {node: '>=18'}
wa-sqlite@1.0.0:
resolution: {integrity: sha512-Kyybo5/BaJp76z7gDWGk2J6Hthl4NIPsE+swgraEjy3IY6r5zIR02wAs1OJH4XtJp1y3puj3Onp5eMGS0z7nUA==}
walker@1.0.8:
resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==}
webidl-conversions@7.0.0:
resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==}
engines: {node: '>=12'}
whatwg-encoding@3.1.1:
resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==}
engines: {node: '>=18'}
deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation
whatwg-mimetype@4.0.0:
resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==}
engines: {node: '>=18'}
whatwg-url@14.2.0:
resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==}
engines: {node: '>=18'}
which-typed-array@1.1.19:
resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==}
engines: {node: '>= 0.4'}
@ -4075,6 +4229,25 @@ packages:
resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==}
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
ws@8.20.0:
resolution: {integrity: sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==}
engines: {node: '>=10.0.0'}
peerDependencies:
bufferutil: ^4.0.1
utf-8-validate: '>=5.0.2'
peerDependenciesMeta:
bufferutil:
optional: true
utf-8-validate:
optional: true
xml-name-validator@5.0.0:
resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==}
engines: {node: '>=18'}
xmlchars@2.2.0:
resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==}
y18n@5.0.8:
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
engines: {node: '>=10'}
@ -4121,6 +4294,14 @@ snapshots:
'@antfu/utils@8.1.1': {}
'@asamuzakjp/css-color@3.2.0':
dependencies:
'@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
'@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
'@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
'@csstools/css-tokenizer': 3.0.4
lru-cache: 10.4.3
'@babel/code-frame@7.27.1':
dependencies:
'@babel/helper-validator-identifier': 7.27.1
@ -4301,7 +4482,7 @@ snapshots:
'@babel/parser': 7.28.0
'@babel/template': 7.27.2
'@babel/types': 7.28.2
debug: 4.4.1
debug: 4.4.3
transitivePeerDependencies:
- supports-color
@ -4495,6 +4676,26 @@ snapshots:
style-mod: 4.1.2
w3c-keyname: 2.2.8
'@csstools/color-helpers@5.1.0': {}
'@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)':
dependencies:
'@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
'@csstools/css-tokenizer': 3.0.4
'@csstools/css-color-parser@3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)':
dependencies:
'@csstools/color-helpers': 5.1.0
'@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
'@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
'@csstools/css-tokenizer': 3.0.4
'@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)':
dependencies:
'@csstools/css-tokenizer': 3.0.4
'@csstools/css-tokenizer@3.0.4': {}
'@docsearch/css@4.0.0-beta.7': {}
'@docsearch/js@4.0.0-beta.7': {}
@ -4802,6 +5003,17 @@ snapshots:
'@jest/diff-sequences@30.3.0': {}
'@jest/environment-jsdom-abstract@30.3.0(jsdom@26.1.0)':
dependencies:
'@jest/environment': 30.3.0
'@jest/fake-timers': 30.3.0
'@jest/types': 30.3.0
'@types/jsdom': 21.1.7
'@types/node': 24.2.1
jest-mock: 30.3.0
jest-util: 30.3.0
jsdom: 26.1.0
'@jest/environment@30.0.5':
dependencies:
'@jest/fake-timers': 30.0.5
@ -5500,6 +5712,12 @@ snapshots:
expect: 30.0.5
pretty-format: 30.0.5
'@types/jsdom@21.1.7':
dependencies:
'@types/node': 24.2.1
'@types/tough-cookie': 4.0.5
parse5: 7.3.0
'@types/json-schema@7.0.15': {}
'@types/jsonpath@0.2.4': {}
@ -5535,6 +5753,8 @@ snapshots:
dependencies:
'@types/estree': 1.0.8
'@types/tough-cookie@4.0.5': {}
'@types/trusted-types@2.0.7':
optional: true
@ -5910,6 +6130,8 @@ snapshots:
dependencies:
ag-charts-types: 12.1.1
agent-base@7.1.4: {}
ajv@6.12.6:
dependencies:
fast-deep-equal: 3.1.3
@ -6178,6 +6400,11 @@ snapshots:
shebang-command: 2.0.0
which: 2.0.2
cssstyle@4.6.0:
dependencies:
'@asamuzakjp/css-color': 3.2.0
rrweb-cssom: 0.8.0
csstype@3.1.3: {}
cytoscape-cose-bilkent@4.1.0(cytoscape@3.33.1):
@ -6364,6 +6591,11 @@ snapshots:
d3: 7.9.0
lodash-es: 4.17.21
data-urls@5.0.0:
dependencies:
whatwg-mimetype: 4.0.0
whatwg-url: 14.2.0
dayjs@1.11.13: {}
debug@4.4.1:
@ -6374,6 +6606,8 @@ snapshots:
dependencies:
ms: 2.1.3
decimal.js@10.6.0: {}
dedent@1.6.0: {}
deep-is@0.1.4: {}
@ -6434,6 +6668,8 @@ snapshots:
entities@4.5.0: {}
entities@6.0.1: {}
error-ex@1.3.2:
dependencies:
is-arrayish: 0.2.1
@ -6853,10 +7089,28 @@ snapshots:
hookable@5.5.3: {}
html-encoding-sniffer@4.0.0:
dependencies:
whatwg-encoding: 3.1.1
html-escaper@2.0.2: {}
html-void-elements@3.0.0: {}
http-proxy-agent@7.0.2:
dependencies:
agent-base: 7.1.4
debug: 4.4.3
transitivePeerDependencies:
- supports-color
https-proxy-agent@7.0.6:
dependencies:
agent-base: 7.1.4
debug: 4.4.3
transitivePeerDependencies:
- supports-color
human-id@4.1.1: {}
human-signals@2.1.0: {}
@ -6932,6 +7186,8 @@ snapshots:
is-number@7.0.0: {}
is-potential-custom-element-name@1.0.1: {}
is-regex@1.2.1:
dependencies:
call-bound: 1.0.4
@ -7101,6 +7357,16 @@ snapshots:
jest-util: 30.0.5
pretty-format: 30.0.5
jest-environment-jsdom@30.3.0:
dependencies:
'@jest/environment': 30.3.0
'@jest/environment-jsdom-abstract': 30.3.0(jsdom@26.1.0)
jsdom: 26.1.0
transitivePeerDependencies:
- bufferutil
- supports-color
- utf-8-validate
jest-environment-node@30.0.5:
dependencies:
'@jest/environment': 30.0.5
@ -7404,6 +7670,33 @@ snapshots:
dependencies:
argparse: 2.0.1
jsdom@26.1.0:
dependencies:
cssstyle: 4.6.0
data-urls: 5.0.0
decimal.js: 10.6.0
html-encoding-sniffer: 4.0.0
http-proxy-agent: 7.0.2
https-proxy-agent: 7.0.6
is-potential-custom-element-name: 1.0.1
nwsapi: 2.2.23
parse5: 7.3.0
rrweb-cssom: 0.8.0
saxes: 6.0.0
symbol-tree: 3.2.4
tough-cookie: 5.1.2
w3c-xmlserializer: 5.0.0
webidl-conversions: 7.0.0
whatwg-encoding: 3.1.1
whatwg-mimetype: 4.0.0
whatwg-url: 14.2.0
ws: 8.20.0
xml-name-validator: 5.0.0
transitivePeerDependencies:
- bufferutil
- supports-color
- utf-8-validate
jsesc@3.1.0: {}
json-buffer@3.0.1: {}
@ -7639,6 +7932,8 @@ snapshots:
dependencies:
path-key: 3.1.1
nwsapi@2.2.23: {}
obliterator@2.0.5: {}
obsidian@1.8.7(@codemirror/state@6.5.2)(@codemirror/view@6.38.1):
@ -7737,6 +8032,10 @@ snapshots:
json-parse-even-better-errors: 2.3.1
lines-and-columns: 1.2.4
parse5@7.3.0:
dependencies:
entities: 6.0.1
path-data-parser@0.1.0: {}
path-exists@4.0.0: {}
@ -7932,6 +8231,8 @@ snapshots:
points-on-curve: 0.2.0
points-on-path: 0.2.1
rrweb-cssom@0.8.0: {}
run-parallel@1.2.0:
dependencies:
queue-microtask: 1.2.3
@ -8052,6 +8353,10 @@ snapshots:
optionalDependencies:
'@parcel/watcher': 2.5.1
saxes@6.0.0:
dependencies:
xmlchars: 2.2.0
semver@6.3.1: {}
semver@7.7.2: {}
@ -8183,6 +8488,8 @@ snapshots:
supports-preserve-symlinks-flag@1.0.0: {}
symbol-tree@3.2.4: {}
sync-child-process@1.0.2:
dependencies:
sync-message-port: 1.1.3
@ -8215,6 +8522,12 @@ snapshots:
fdir: 6.5.0(picomatch@4.0.3)
picomatch: 4.0.3
tldts-core@6.1.86: {}
tldts@6.1.86:
dependencies:
tldts-core: 6.1.86
tmp@0.0.33:
dependencies:
os-tmpdir: 1.0.2
@ -8225,6 +8538,14 @@ snapshots:
dependencies:
is-number: 7.0.0
tough-cookie@5.1.2:
dependencies:
tldts: 6.1.86
tr46@5.1.1:
dependencies:
punycode: 2.3.1
trim-lines@3.0.1: {}
ts-api-utils@1.4.3(typescript@5.4.5):
@ -8480,12 +8801,29 @@ snapshots:
w3c-keyname@2.2.8: {}
w3c-xmlserializer@5.0.0:
dependencies:
xml-name-validator: 5.0.0
wa-sqlite@1.0.0: {}
walker@1.0.8:
dependencies:
makeerror: 1.0.12
webidl-conversions@7.0.0: {}
whatwg-encoding@3.1.1:
dependencies:
iconv-lite: 0.6.3
whatwg-mimetype@4.0.0: {}
whatwg-url@14.2.0:
dependencies:
tr46: 5.1.1
webidl-conversions: 7.0.0
which-typed-array@1.1.19:
dependencies:
available-typed-arrays: 1.0.7
@ -8523,6 +8861,12 @@ snapshots:
imurmurhash: 0.1.4
signal-exit: 4.1.0
ws@8.20.0: {}
xml-name-validator@5.0.0: {}
xmlchars@2.2.0: {}
y18n@5.0.8: {}
yallist@3.1.1: {}

View file

@ -13,6 +13,7 @@ import { transformQuery } from "../sql/sqlTransformer";
import { registerObservers } from "../../../utils/registerObservers";
import { Settings } from "../../settings/Settings";
import { ModernCellParser } from "../../syntaxHighlight/cellParser/ModernCellParser";
import { findCollapsedCallout } from "./calloutVisibility";
export class CodeblockProcessor extends MarkdownRenderChild {
registrator: OmnibusRegistrator;
@ -73,40 +74,57 @@ export class CodeblockProcessor extends MarkdownRenderChild {
}
}
this.flags = results.flags;
let rendererEl = this.el;
if (this.flags.explain) {
this.extrasEl = this.el.createDiv({ cls: "sqlseal-extras-container" });
if (this.flags.explain) {
this.explainEl = this.extrasEl.createEl("pre", {
cls: "sqlseal-extras-explain-container",
});
}
rendererEl = this.el.createDiv({ cls: "sqlseal-renderer-container" });
const callout = findCollapsedCallout(this.el);
if (callout) {
const observer = new MutationObserver(() => {
if (!callout.classList.contains('is-collapsed')) {
observer.disconnect();
this.doLoad(results).catch(e => displayError(this.el, e.toString()));
}
});
observer.observe(callout, { attributes: true, attributeFilter: ['class'] });
this.register(() => observer.disconnect());
return;
}
// IF WE'RE ON CANVAS, LETS ADD BACKGRUND
if (this.isOnCanvas) {
rendererEl.classList.add('sqlseal-renderer-on-canvas')
}
this.renderer = this.rendererRegistry.prepareRender(
results.renderer.type.toLowerCase(),
results.renderer.options,
)(rendererEl, {
cellParser: this.cellParser,
sourcePath: this.sourceKey,
});
// FIXME: probably should save the one before transform and perform transform every time we execute it.
this.query = results.query;
await this.render();
await this.doLoad(results);
} catch (e) {
displayError(this.el, e.toString());
}
}
private async doLoad(results: ParserResult) {
this.flags = results.flags;
let rendererEl = this.el;
if (this.flags.explain) {
this.extrasEl = this.el.createDiv({ cls: "sqlseal-extras-container" });
if (this.flags.explain) {
this.explainEl = this.extrasEl.createEl("pre", {
cls: "sqlseal-extras-explain-container",
});
}
rendererEl = this.el.createDiv({ cls: "sqlseal-renderer-container" });
}
// IF WE'RE ON CANVAS, LETS ADD BACKGRUND
if (this.isOnCanvas) {
rendererEl.classList.add('sqlseal-renderer-on-canvas')
}
this.renderer = this.rendererRegistry.prepareRender(
results.renderer.type.toLowerCase(),
results.renderer.options,
)(rendererEl, {
cellParser: this.cellParser,
sourcePath: this.sourceKey,
});
// FIXME: probably should save the one before transform and perform transform every time we execute it.
this.query = results.query;
await this.render();
}
onunload() {
this.registrator.offAll();
if (this.renderer?.cleanup) {

View file

@ -0,0 +1,134 @@
/**
* @jest-environment jsdom
*
* Tests for the callout visibility gate.
*
* We test two things:
* 1. findCollapsedCallout() pure DOM logic that detects collapsed callouts
* 2. MutationObserver integration verifies that removing .is-collapsed
* triggers the deferred callback (proves the observer wiring works)
*/
import { jest } from '@jest/globals';
import { findCollapsedCallout } from './calloutVisibility';
// ─── DOM helpers ─────────────────────────────────────────────────────────────
function makeEl(parent?: HTMLElement): HTMLElement {
const el = document.createElement('div');
(parent ?? document.body).appendChild(el);
return el;
}
function makeCallout(collapsed: boolean): { callout: HTMLElement; el: HTMLElement } {
const callout = document.createElement('div');
callout.classList.add('callout');
if (collapsed) callout.classList.add('is-collapsed');
document.body.appendChild(callout);
const content = makeEl(callout);
const el = makeEl(content);
return { callout, el };
}
afterEach(() => {
document.body.innerHTML = '';
});
// ─── findCollapsedCallout ─────────────────────────────────────────────────────
describe('findCollapsedCallout()', () => {
it('returns null for element not inside any callout', () => {
const el = makeEl();
expect(findCollapsedCallout(el)).toBeNull();
});
it('returns null when inside an expanded callout', () => {
const { el } = makeCallout(false);
expect(findCollapsedCallout(el)).toBeNull();
});
it('returns the callout element when inside a collapsed callout', () => {
const { callout, el } = makeCallout(true);
expect(findCollapsedCallout(el)).toBe(callout);
});
it('returns null after .is-collapsed is removed', () => {
const { callout, el } = makeCallout(true);
callout.classList.remove('is-collapsed');
expect(findCollapsedCallout(el)).toBeNull();
});
it('handles deeply nested elements inside collapsed callout', () => {
const { callout, el } = makeCallout(true);
const deep = makeEl(makeEl(makeEl(el)));
expect(findCollapsedCallout(deep)).toBe(callout);
});
});
// ─── MutationObserver wiring (integration) ───────────────────────────────────
describe('MutationObserver deferred-render pattern', () => {
it('fires callback when .is-collapsed is removed from callout', async () => {
const { callout, el } = makeCallout(true);
const callback = jest.fn();
// Replicate the wiring from CodeblockProcessor.onload()
const observer = new MutationObserver(() => {
if (!callout.classList.contains('is-collapsed')) {
observer.disconnect();
callback();
}
});
observer.observe(callout, { attributes: true, attributeFilter: ['class'] });
expect(callback).not.toHaveBeenCalled();
callout.classList.remove('is-collapsed');
await new Promise(r => setTimeout(r, 0));
expect(callback).toHaveBeenCalledTimes(1);
});
it('does NOT fire callback when observer is disconnected before expansion', async () => {
const { callout } = makeCallout(true);
const callback = jest.fn();
const observer = new MutationObserver(() => {
if (!callout.classList.contains('is-collapsed')) {
observer.disconnect();
callback();
}
});
observer.observe(callout, { attributes: true, attributeFilter: ['class'] });
observer.disconnect(); // simulates component unload
callout.classList.remove('is-collapsed');
await new Promise(r => setTimeout(r, 0));
expect(callback).not.toHaveBeenCalled();
});
it('fires callback only once even if class toggles multiple times', async () => {
const { callout } = makeCallout(true);
const callback = jest.fn();
const observer = new MutationObserver(() => {
if (!callout.classList.contains('is-collapsed')) {
observer.disconnect(); // disconnect immediately — one-shot
callback();
}
});
observer.observe(callout, { attributes: true, attributeFilter: ['class'] });
callout.classList.remove('is-collapsed');
await new Promise(r => setTimeout(r, 0));
callout.classList.add('is-collapsed');
callout.classList.remove('is-collapsed');
await new Promise(r => setTimeout(r, 0));
expect(callback).toHaveBeenCalledTimes(1);
});
});

View file

@ -0,0 +1,9 @@
/**
* Returns the nearest collapsed callout ancestor of `el`, or null if `el` is
* not inside a collapsed callout. Used to gate query execution until the user
* expands the callout.
*/
export function findCollapsedCallout(el: Element): Element | null {
const callout = el.closest('.callout');
return callout?.classList.contains('is-collapsed') ? callout : null;
}