@gjsify/unit 0.37.0 → 0.38.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.
@@ -0,0 +1 @@
1
+ import"./_virtual/_rolldown/runtime.js";function isDisplaylessBackend(e){return e===`win32`||e===`darwin`}function canRealizeSurface(e,t){return isDisplaylessBackend(e)||!!(t.DISPLAY||t.WAYLAND_DISPLAY)}function canRealizeGl(e,t){return e===`linux`&&!!(t.DISPLAY||t.WAYLAND_DISPLAY)}export{canRealizeGl,canRealizeSurface,isDisplaylessBackend};
package/lib/esm/index.js CHANGED
@@ -1,12 +1,12 @@
1
- import"./_virtual/_rolldown/runtime.js";import{spy as e}from"./spy.js";import"@girs/gjs";import t from"node:assert";import{quitMainLoop as n}from"@gjsify/utils/main-loop";const runtimeGlobals=()=>globalThis,r=typeof runtimeGlobals().process?.versions?.gjs==`string`?runtimeGlobals().imports?.mainloop:void 0;let i=0,a=0,o=0,s=0,c=0;const l=[],u=[],noteWarning=e=>{u.push({suite:h,message:e})},noteStrayFailure=e=>{++a,l.push({suite:h,message:e}),_.push({suite:h,test:`<stray assertion outside any it()>`,message:e})},d=[],noteThrownAssertion=e=>{let t=d[d.length-1];t&&e instanceof Error&&t.add(e)},forgetThrownAssertion=e=>{if(e instanceof Error)for(let t of d)t.delete(e)};let p=``,m=0,h=``,g=``,_=[],v={testTimeout:5e3,suiteTimeout:3e4,runTimeout:12e4},y=new Map;var TimeoutError=class extends Error{constructor(e,t){super(`Timeout: "${e}" exceeded ${t}ms`),this.name=`TimeoutError`}};const b=[];async function withTimeout(e,t,n){if(t<=0)return e();let r,i=new Promise((e,i)=>{r=setTimeout(()=>i(new TimeoutError(n,t)),t)});i.catch(()=>{});let a,o=new Promise((e,t)=>{a=t});o.catch(()=>{}),b.push(a);try{let t=Promise.resolve(e());return t.catch(()=>{}),await Promise.race([t,i,o])}finally{clearTimeout(r);let e=b.lastIndexOf(a);e!==-1&&b.splice(e,1)}}let x=!1;const installUncaughtHooks=()=>{if(x)return;let e=runtimeGlobals().process;if(typeof e?.on!=`function`||typeof e.versions?.gjs==`string`)return;x=!0;let handle=t=>n=>{let r=n?.__testFailureCounted===!0,i=(e.listenerCount?.(t)??1)-1;if(!r&&i>0){let e=n?.message??String(n),r=c>0?` during "${g}"`:``;noteWarning(`${t}: ${e}${r} — absorbed by a listener the spec installed itself`);return}let a=b[b.length-1];a?a(n):noteStrayFailure(n?.message??String(n))};e.on(`uncaughtException`,handle(`uncaughtException`)),e.on(`unhandledRejection`,handle(`unhandledRejection`))},configure=e=>{v={...v,...e}};function applyEnvOverrides(){try{let e=runtimeGlobals().process?.env;if(!e)return;let t=parseInt(e.GJSIFY_TEST_TIMEOUT,10);!isNaN(t)&&t>=0&&(v.testTimeout=t);let n=parseInt(e.GJSIFY_SUITE_TIMEOUT,10);!isNaN(n)&&n>=0&&(v.suiteTimeout=n);let r=parseInt(e.GJSIFY_RUN_TIMEOUT,10);!isNaN(r)&&r>=0&&(v.runTimeout=r)}catch{}}const S=`\x1B[31m`,C=`\x1B[32m`,w=`\x1B[34m`,T=`\x1B[90m`,E=`\x1B[39m`,now=()=>runtimeGlobals().performance?.now?.()??Date.now(),formatDuration=e=>e>=1e3?`${(e/1e3).toFixed(2)}s`:e>=100?`${Math.round(e)}ms`:`${e.toFixed(1)}ms`,D=typeof runtimeGlobals().process?.versions?.gjs!=`string`&&runtimeGlobals().document!==void 0?console.log:globalThis.print||console.log;function isStructurallyEqual(e,n){try{return t.deepStrictEqual(e,n),!0}catch{return!1}}function formatValue(e){switch(typeof e){case`symbol`:return e.toString();case`bigint`:return`${e}n`;case`string`:return e;case`function`:return e.name?`[Function ${e.name}]`:`[Function (anonymous)]`;case`object`:if(e===null)return`null`;try{return JSON.stringify(e)??String(e)}catch{return Object.prototype.toString.call(e)}default:return String(e)}}function matchesObject(e,t){return Object.is(e,t)?!0:typeof t!=`object`||!t?e===t:typeof e!=`object`||!e?!1:Array.isArray(t)?!Array.isArray(e)||e.length!==t.length?!1:t.every((t,n)=>matchesObject(e[n],t)):Array.isArray(e)?!1:Object.keys(t).every(n=>matchesObject(e[n],t[n]))}const O=[],k=[];function envBag(){return globalThis.process?.env}const A={fn(e){let t=[],f=function(...n){return t.push(n),e?e.apply(this,n):void 0};return f.mock={calls:t},f},stubGlobal(e,t){let n=globalThis;O.push({key:e,had:e in n,prev:n[e]}),n[e]=t},unstubAllGlobals(){let e=globalThis;for(let t=O.length-1;t>=0;t--){let n=O[t];n.had?e[n.key]=n.prev:delete e[n.key]}O.length=0},stubEnv(e,t){let n=envBag();n&&(k.push({key:e,had:e in n,prev:n[e]}),t===void 0?delete n[e]:n[e]=t)},unstubAllEnvs(){let e=envBag();if(e){for(let t=k.length-1;t>=0;t--){let n=k[t];n.had?e[n.key]=n.prev:delete e[n.key]}k.length=0}}};var j=class MatcherFactory{actualValue;positive;not;constructor(e,t,n){this.actualValue=e,this.positive=t,n?this.not=n:this.not=new MatcherFactory(e,!t,this)}triggerResult(e,t){if(e&&!this.positive||!e&&this.positive){let e=Error(t);throw e.__testFailureCounted=!0,c===0?noteStrayFailure(t):noteThrownAssertion(e),e}}to(e){this.triggerResult(e(this.actualValue),` Expected callback to validate`)}toBe(e){this.triggerResult(this.actualValue===e,` Expected values to match using ===
1
+ import"./_virtual/_rolldown/runtime.js";import{canRealizeGl as e,canRealizeSurface as t}from"./capabilities.js";import{spy as n}from"./spy.js";import"@girs/gjs";import r from"node:assert";import{runtimeName as i,runtimeVersion as a}from"@gjsify/runtime";import{hostOs as o}from"@gjsify/utils/core";import{quitMainLoop as s}from"@gjsify/utils/main-loop";const runtimeGlobals=()=>globalThis,c=typeof runtimeGlobals().process?.versions?.gjs==`string`?runtimeGlobals().imports?.mainloop:void 0;let l=0,u=0,d=0,p=0,m=0;const h=[],g=[],noteWarning=e=>{g.push({suite:b,message:e})},noteStrayFailure=e=>{++u,h.push({suite:b,message:e}),S.push({suite:b,test:`<stray assertion outside any it()>`,message:e})},_=[],noteThrownAssertion=e=>{let t=_[_.length-1];t&&e instanceof Error&&t.add(e)},forgetThrownAssertion=e=>{if(e instanceof Error)for(let t of _)t.delete(e)};let v=``,y=0,b=``,x=``,S=[],C={testTimeout:5e3,suiteTimeout:3e4,runTimeout:12e4},w=new Map;var TimeoutError=class extends Error{constructor(e,t){super(`Timeout: "${e}" exceeded ${t}ms`),this.name=`TimeoutError`}};const T=[];async function withTimeout(e,t,n){if(t<=0)return e();let r,i=new Promise((e,i)=>{r=setTimeout(()=>i(new TimeoutError(n,t)),t)});i.catch(()=>{});let a,o=new Promise((e,t)=>{a=t});o.catch(()=>{}),T.push(a);try{let t=Promise.resolve(e());return t.catch(()=>{}),await Promise.race([t,i,o])}finally{clearTimeout(r);let e=T.lastIndexOf(a);e!==-1&&T.splice(e,1)}}let E=!1;const installUncaughtHooks=()=>{if(E)return;let e=runtimeGlobals().process;if(typeof e?.on!=`function`||typeof e.versions?.gjs==`string`)return;E=!0;let handle=t=>n=>{let r=n?.__testFailureCounted===!0,i=(e.listenerCount?.(t)??1)-1;if(!r&&i>0){let e=n?.message??String(n),r=m>0?` during "${x}"`:``;noteWarning(`${t}: ${e}${r} — absorbed by a listener the spec installed itself`);return}let a=T[T.length-1];a?a(n):noteStrayFailure(n?.message??String(n))};e.on(`uncaughtException`,handle(`uncaughtException`)),e.on(`unhandledRejection`,handle(`unhandledRejection`))},configure=e=>{C={...C,...e}};function applyEnvOverrides(){try{let e=runtimeGlobals().process?.env;if(!e)return;let t=parseInt(e.GJSIFY_TEST_TIMEOUT,10);!isNaN(t)&&t>=0&&(C.testTimeout=t);let n=parseInt(e.GJSIFY_SUITE_TIMEOUT,10);!isNaN(n)&&n>=0&&(C.suiteTimeout=n);let r=parseInt(e.GJSIFY_RUN_TIMEOUT,10);!isNaN(r)&&r>=0&&(C.runTimeout=r)}catch{}}const D=`\x1B[31m`,O=`\x1B[32m`,k=`\x1B[34m`,A=`\x1B[90m`,j=`\x1B[39m`,now=()=>runtimeGlobals().performance?.now?.()??Date.now(),formatDuration=e=>e>=1e3?`${(e/1e3).toFixed(2)}s`:e>=100?`${Math.round(e)}ms`:`${e.toFixed(1)}ms`,M=typeof runtimeGlobals().process?.versions?.gjs!=`string`&&runtimeGlobals().document!==void 0?console.log:globalThis.print||console.log;function isStructurallyEqual(e,t){try{return r.deepStrictEqual(e,t),!0}catch{return!1}}function formatValue(e){switch(typeof e){case`symbol`:return e.toString();case`bigint`:return`${e}n`;case`string`:return e;case`function`:return e.name?`[Function ${e.name}]`:`[Function (anonymous)]`;case`object`:if(e===null)return`null`;try{return JSON.stringify(e)??String(e)}catch{return Object.prototype.toString.call(e)}default:return String(e)}}function matchesObject(e,t){return Object.is(e,t)?!0:typeof t!=`object`||!t?e===t:typeof e!=`object`||!e?!1:Array.isArray(t)?!Array.isArray(e)||e.length!==t.length?!1:t.every((t,n)=>matchesObject(e[n],t)):Array.isArray(e)?!1:Object.keys(t).every(n=>matchesObject(e[n],t[n]))}const N=[],P=[];function envBag(){return globalThis.process?.env}const F={fn(e){let t=[],f=function(...n){return t.push(n),e?e.apply(this,n):void 0};return f.mock={calls:t},f},stubGlobal(e,t){let n=globalThis;N.push({key:e,had:e in n,prev:n[e]}),n[e]=t},unstubAllGlobals(){let e=globalThis;for(let t=N.length-1;t>=0;t--){let n=N[t];n.had?e[n.key]=n.prev:delete e[n.key]}N.length=0},stubEnv(e,t){let n=envBag();n&&(P.push({key:e,had:e in n,prev:n[e]}),t===void 0?delete n[e]:n[e]=t)},unstubAllEnvs(){let e=envBag();if(e){for(let t=P.length-1;t>=0;t--){let n=P[t];n.had?e[n.key]=n.prev:delete e[n.key]}P.length=0}}};var I=class MatcherFactory{actualValue;positive;not;constructor(e,t,n){this.actualValue=e,this.positive=t,n?this.not=n:this.not=new MatcherFactory(e,!t,this)}triggerResult(e,t){if(e&&!this.positive||!e&&this.positive){let e=Error(t);throw e.__testFailureCounted=!0,m===0?noteStrayFailure(t):noteThrownAssertion(e),e}}to(e){this.triggerResult(e(this.actualValue),` Expected callback to validate`)}toBe(e){this.triggerResult(this.actualValue===e,` Expected values to match using ===
2
2
  Expected: ${formatValue(e)} (${typeof e})\n Actual: ${formatValue(this.actualValue)} (${typeof this.actualValue})`)}toEqual(e){let t=this.actualValue==e,n=``;!t&&isStructurallyEqual(this.actualValue,e)&&(n="\n The two values are structurally equal but are not the same reference.\n `toEqual` compares with `==` — use `toStrictEqual` for deep equality."),this.triggerResult(t,` Expected values to match using ==
3
- Expected: ${formatValue(e)} (${typeof e})\n Actual: ${formatValue(this.actualValue)} (${typeof this.actualValue})${n}`)}toStrictEqual(e){let n=!0,r=``;try{t.deepStrictEqual(this.actualValue,e)}catch(e){n=!1,r=e.message||``}this.triggerResult(n,` Expected values to be deeply strictly equal
4
- Expected: ${JSON.stringify(e)}\n Actual: ${JSON.stringify(this.actualValue)}`+(r?`\n ${r}`:``))}toMatchObject(e){this.triggerResult(matchesObject(this.actualValue,e),` Expected value to match object (partial deep match)
5
- Expected: ${formatValue(e)}\n Actual: ${formatValue(this.actualValue)}`)}mockCalls(){return this.actualValue?.mock?.calls??[]}toHaveBeenCalled(){this.triggerResult(this.mockCalls().length>0,` Expected mock function to have been called`)}toHaveBeenCalledTimes(e){let t=this.mockCalls().length;this.triggerResult(t===e,` Expected mock to have been called ${e} time(s)\n Actual: ${t} time(s)`)}toHaveBeenCalledWith(...e){let n=this.mockCalls().some(n=>{try{return t.deepStrictEqual(n,e),!0}catch{return!1}});this.triggerResult(n,` Expected mock to have been called with ${formatValue(e)}`)}toEqualArray(e){let t=this.actualValue,n=Array.isArray(t)&&Array.isArray(e)&&t.length===e.length;for(let r=0;r<t.length&&(n=t[r]==e[r],n);r++);this.triggerResult(n,` Expected array items to match using ==
3
+ Expected: ${formatValue(e)} (${typeof e})\n Actual: ${formatValue(this.actualValue)} (${typeof this.actualValue})${n}`)}toStrictEqual(e){let t=!0,n=``;try{r.deepStrictEqual(this.actualValue,e)}catch(e){t=!1,n=e.message||``}this.triggerResult(t,` Expected values to be deeply strictly equal
4
+ Expected: ${JSON.stringify(e)}\n Actual: ${JSON.stringify(this.actualValue)}`+(n?`\n ${n}`:``))}toMatchObject(e){this.triggerResult(matchesObject(this.actualValue,e),` Expected value to match object (partial deep match)
5
+ Expected: ${formatValue(e)}\n Actual: ${formatValue(this.actualValue)}`)}mockCalls(){return this.actualValue?.mock?.calls??[]}toHaveBeenCalled(){this.triggerResult(this.mockCalls().length>0,` Expected mock function to have been called`)}toHaveBeenCalledTimes(e){let t=this.mockCalls().length;this.triggerResult(t===e,` Expected mock to have been called ${e} time(s)\n Actual: ${t} time(s)`)}toHaveBeenCalledWith(...e){let t=this.mockCalls().some(t=>{try{return r.deepStrictEqual(t,e),!0}catch{return!1}});this.triggerResult(t,` Expected mock to have been called with ${formatValue(e)}`)}toEqualArray(e){let t=this.actualValue,n=Array.isArray(t)&&Array.isArray(e)&&t.length===e.length;for(let r=0;r<t.length&&(n=t[r]==e[r],n);r++);this.triggerResult(n,` Expected array items to match using ==
6
6
  Expected: ${formatValue(e)} (${typeof e})\n Actual: ${formatValue(this.actualValue)} (${typeof this.actualValue})`)}toBeInstanceOf(e){this.triggerResult(this.actualValue instanceof e,` Expected value to be instance of ${e.name||e}\n Actual: ${this.actualValue?.constructor?.name||typeof this.actualValue}`)}toHaveLength(e){let t=this.actualValue?.length;this.triggerResult(t===e,` Expected length: ${e}\n Actual length: ${t}`)}toMatch(e){let t=this.actualValue;if(typeof t.match!=`function`)throw Error(`You can not use toMatch on type ${typeof this.actualValue}`);this.triggerResult(!!t.match(e),` Expected values to match using regular expression
7
7
  Expression: `+formatValue(e)+`
8
- Actual: `+formatValue(this.actualValue))}toBeDefined(){this.triggerResult(this.actualValue!==void 0,` Expected value to be defined`)}toBeUndefined(){this.triggerResult(this.actualValue===void 0,` Expected value to be undefined`)}toBeNull(){this.triggerResult(this.actualValue===null,` Expected value to be null`)}toBeTruthy(){this.triggerResult(this.actualValue,` Expected value to be truthy`)}toBeFalsy(){this.triggerResult(!this.actualValue,` Expected value to be falsy`)}toBeNaN(){this.triggerResult(Number.isNaN(this.actualValue),` Expected value to be NaN\n Actual: ${formatValue(this.actualValue)}`)}toContain(e){let t=this.actualValue,n;n=typeof t==`string`?t.includes(String(e)):t instanceof Array?t.indexOf(e)!==-1:!1,this.triggerResult(n,` Expected `+formatValue(t)+` to contain `+formatValue(e))}toBeLessThan(e){this.triggerResult(this.actualValue<e,` Expected `+formatValue(this.actualValue)+` to be less than `+e)}toBeGreaterThan(e){this.triggerResult(this.actualValue>e,` Expected `+formatValue(this.actualValue)+` to be greater than `+e)}toBeGreaterThanOrEqual(e){this.triggerResult(this.actualValue>=e,` Expected ${formatValue(this.actualValue)} to be greater than or equal to ${e}`)}toBeLessThanOrEqual(e){this.triggerResult(this.actualValue<=e,` Expected ${formatValue(this.actualValue)} to be less than or equal to ${e}`)}toBeCloseTo(e,t){let n=10**t;this.triggerResult(Math.round(this.actualValue*n)/n===Math.round(e*n)/n,` Expected `+formatValue(this.actualValue)+` with precision `+t+` to be close to `+e)}toThrow(e){let t=``,n=!1,r=!0,i=!0,a=this.actualValue;try{a(),n=!1}catch(a){t=a?.message||``,n=!0,forgetThrownAssertion(a),typeof e==`function`?r=a instanceof e:typeof e==`string`?i=t.includes(e):e instanceof RegExp&&(i=e.test(t))}let o=this.actualValue?.name||typeof this.actualValue==`function`?`[anonymous function]`:String(this.actualValue);this.triggerResult(n,` Expected ${o} to ${this.positive?`throw`:`not throw`} an exception ${!this.positive&&t?`, but an error with the message "${t}" was thrown`:``}`),typeof e==`function`?this.triggerResult(r,` Expected Error type '${e.name}', but the error is not an instance of it`):e!==void 0&&this.triggerResult(i,` Expected error message to match ${e}\n Actual message: "${t}"`)}async toReject(e){let t=!1,n=``,r=!0,i=!0;try{await this.actualValue,t=!1}catch(a){t=!0,forgetThrownAssertion(a),n=a?.message||String(a),typeof e==`function`?r=a instanceof e:typeof e==`string`?i=n.includes(e):e instanceof RegExp&&(i=e.test(n))}this.triggerResult(t,` Expected promise to ${this.positive?`reject`:`resolve`}${!this.positive&&n?`, but it rejected with "${n}"`:``}`),t&&typeof e==`function`?this.triggerResult(r,` Expected rejection type '${e.name}', but the error is not an instance of it`):t&&e!==void 0&&this.triggerResult(i,` Expected rejection message to match ${e}\n Actual message: "${n}"`)}async toResolve(){let e=!1,t=``;try{await this.actualValue,e=!0}catch(n){e=!1,forgetThrownAssertion(n),t=n?.message||String(n)}this.triggerResult(e,` Expected promise to ${this.positive?`resolve`:`reject`}${e?``:`, but it rejected with "${t}"`}`)}get rejects(){return{toThrow:e=>this.toReject(e),toReject:e=>this.toReject(e)}}get resolves(){return{toResolve:()=>this.toResolve()}}};const describe=async function(e,t,n){let r=typeof n==`number`?n:n?.timeout??v.suiteTimeout;D(`
9
- `+e);let i=h;h=e;let o=now();try{await withTimeout(t,r,`describe: ${e}`)}catch(e){if(e instanceof TimeoutError)++a,D(` ${S}⏱ Suite timed out: ${e.message}${E}`);else throw e}h=i;let s=now()-o;D(` ${T}↳ ${formatDuration(s)}${E}`),M=null,N=null};describe.skip=async function(e,t){++o,D(`\n${w}- ${e} (skipped)${E}`)};const hasDisplay=()=>{let e=runtimeGlobals().process?.env;if(e)return!!(e.DISPLAY||e.WAYLAND_DISPLAY);let t=runtimeGlobals().imports?.gi?.GLib;return t?!!(t.getenv(`DISPLAY`)||t.getenv(`WAYLAND_DISPLAY`)):!1},runtimeMatch=async function(e,t){if(e.includes(`Display`))return{matched:hasDisplay()};let n=await getRuntime(),r=e.find(e=>n.includes(e));return!r||typeof t==`string`&&!n.includes(t)?{matched:!1}:{matched:!0,runtime:r,version:t}},on=async function(e,t,n){typeof e==`string`&&(e=[e]),typeof t==`function`&&(n=t,t=void 0);let{matched:r}=await runtimeMatch(e,t);if(!r){++o;return}D(`\nOn ${e.join(`, `)}${t?` `+t:``}`),await n()};let M,N;const beforeEach=function(e){M=e},afterEach=function(e){N=e},it=async function(e,t,n){let r=y.get(e);if(r!==void 0){++o,D(` ${w}-${E} ${T}${e} (skipped: ${r})${E}`);return}let i=typeof n==`number`?n:n?.timeout??v.testTimeout,s=now();++c,g=e;let l=new Set;d.push(l);let u,p=!1;try{typeof M==`function`&&await M(),await withTimeout(t,i,e),typeof N==`function`&&await N()}catch(e){p=!0,u=e,e instanceof Error&&l.delete(e)}finally{--c,d.pop()}let m=now()-s,b=u instanceof TimeoutError?[...l]:[];if(!p){D(` ${C}✔${E} ${T}${e} (${formatDuration(m)})${E}`);return}++a;let x=[];p&&x.push(u?.message??String(u));for(let e of b)x.push(`${e.message}\n ${T}↳ thrown from a callback OUTSIDE this test's awaited chain, so it could not reach the test boundary. Reject the promise from that callback (or await a helper that does) to surface it directly.${E}`);let O=x.join(`
10
- `),k=b[0]??u,A=p&&u instanceof TimeoutError&&b.length===0?`⏱`:`❌`;_.push({suite:h,test:e,message:O}),D(` ${S}${A}${E} ${T}${e} (${formatDuration(m)})${E}`),D(`${S}${O}${E}`),k?.stack&&D(k.stack)};it.skip=async function(e,t){++o,D(` ${w}-${E} ${T}${e} (skipped)${E}`)};const getTestCounters=()=>({overall:i,failed:a,ignored:o,xfail:s,warnings:u.length});it.failing=async function(e,t,n,r){let i=typeof r==`number`?r:r?.timeout??v.testTimeout;if(typeof r==`object`&&r?.when===!1)return it(e,t,{timeout:i});let o=now();++c,d.push(new Set);let l=!1;try{typeof M==`function`&&await M(),await withTimeout(t,i,e),typeof N==`function`&&await N()}catch{l=!0}finally{--c,d.pop()}let u=now()-o;if(l){++s,D(` ${w}✗${E} ${T}${e} (expected failure — ${n})${E}`);return}++a,_.push({suite:h,test:e,message:`it.failing("${e}") PASSED — the expected failure no longer happens, so this marker is stale and must be removed (turn it back into a plain it()). It was marked expected-failing because: ${n}`}),D(` ${S}❌${E} ${T}${e} (${formatDuration(u)})${E}`),D(`${S}it.failing passed unexpectedly — remove the marker. Reason it carried: ${n}${E}`)};const expect=function(e,t){return++i,new j(e,!0)},failAssertion=e=>{throw e.__testFailureCounted=!0,c===0?noteStrayFailure(e?.message??String(e)):noteThrownAssertion(e),e},assert=function(e,n){++i;try{t(e,n)}catch(e){failAssertion(e)}};assert.strictEqual=function(e,n,r){++i;try{t.strictEqual(e,n,r)}catch(e){failAssertion(e)}},assert.throws=function(e,...n){++i;let r;try{e()}catch(e){r=e}try{t.throws(()=>{if(r)throw r},n[0],n[1])}catch(e){failAssertion(e)}},assert.deepStrictEqual=function(e,n,r){++i;try{t.deepStrictEqual(e,n,r)}catch(e){failAssertion(e)}};const runTests=async function(e){for(let t in e){let n=e[t];typeof n==`function`?await n():typeof n==`object`&&await runTests(n)}},browserSignalDone=()=>{let e=runtimeGlobals(),t=e.document;t&&(e.__gjsify_test_results={passed:i-a,failed:a,total:i,errors:_},t.documentElement.dataset.testsDone=`true`)},printResult=()=>{let e=m>0?now()-m:0,t=e>0?` ${T}(${formatDuration(e)})`:``,n=p?`[${p}] `:``;if(o&&D(`\n${w}✔ ${o} ignored test${o>1?`s`:``}${E}`),s&&D(`\n${w}✗ ${s} expected failure${s>1?`s`:``} (it.failing — upstream defects)${E}`),u.length){D(`\n${w}⚠ ${u.length} warning${u.length>1?`s`:``} (not counted — nothing is claimed about these)${E}`);for(let e of u)D(` ${w}↳ ${e.message.trim().split(`
11
- `)[0]}${E}`)}if(l.length){D(`\n${S}⚠ ${l.length} assertion${l.length>1?`s`:``} fired outside any it() (leaked from a settled test)${E}`);for(let e of l)D(` ${S}↳ ${e.message.trim().split(`
12
- `)[0]}${E}`)}D(a?`\n${S}❌ ${n}${a} of ${i} tests failed${t}${E}`:`\n${C}✔ ${n}${i} completed${t}${E}`)},getRuntime=async()=>{if(p&&p!==`Unknown`)return p;if(globalThis.Deno?.version?.deno)return`Deno `+globalThis.Deno?.version?.deno;{let e=globalThis.process;if(!e)try{e=await import(`node:process`)}catch{}if(e?.versions?.gjs)return p=`Gjs `+e.versions.gjs,p;if(e?.versions?.node)return p=`Node.js `+e.versions.node,p}return runtimeGlobals().document===void 0?p||`Unknown`:(p=`Browser`,p)},printRuntime=async()=>{let e=await getRuntime();D(`\nRunning on ${e}`)},run=async(e,t)=>{if(applyEnvOverrides(),installUncaughtHooks(),m=now(),y=new Map,s=0,u.length=0,t&&(typeof t==`number`?v.runTimeout=t:(t.timeout!==void 0&&(v.runTimeout=t.timeout),t.testTimeout!==void 0&&(v.testTimeout=t.testTimeout),t.suiteTimeout!==void 0&&(v.suiteTimeout=t.suiteTimeout),t.skip&&(y=new Map(Object.entries(t.skip))))),printRuntime().then(async()=>{try{await withTimeout(()=>runTests(e),v.runTimeout,`entire test run`)}catch(e){if(e instanceof TimeoutError)D(`\n${S}⏱ ${e.message}${E}`),++a;else throw e}}).then(async()=>{if(printResult(),browserSignalDone(),D(),n(),r?.quit(),!r){let e=+(a>0);try{(globalThis.process||await import(`node:process`)).exit(e)}catch{}}}),r?.run(),r){let e=+(a>0);runtimeGlobals().imports?.system?.exit(e)}};var P={run,assert,expect,it,afterEach,beforeEach,on,describe,configure,print:D,vi:A};export{afterEach,assert,beforeEach,configure,P as default,describe,expect,formatValue,getTestCounters,it,on,D as print,run,e as spy,A as vi};
8
+ Actual: `+formatValue(this.actualValue))}toBeDefined(){this.triggerResult(this.actualValue!==void 0,` Expected value to be defined`)}toBeUndefined(){this.triggerResult(this.actualValue===void 0,` Expected value to be undefined`)}toBeNull(){this.triggerResult(this.actualValue===null,` Expected value to be null`)}toBeTruthy(){this.triggerResult(this.actualValue,` Expected value to be truthy`)}toBeFalsy(){this.triggerResult(!this.actualValue,` Expected value to be falsy`)}toBeNaN(){this.triggerResult(Number.isNaN(this.actualValue),` Expected value to be NaN\n Actual: ${formatValue(this.actualValue)}`)}toContain(e){let t=this.actualValue,n;n=typeof t==`string`?t.includes(String(e)):t instanceof Array?t.indexOf(e)!==-1:!1,this.triggerResult(n,` Expected `+formatValue(t)+` to contain `+formatValue(e))}toBeLessThan(e){this.triggerResult(this.actualValue<e,` Expected `+formatValue(this.actualValue)+` to be less than `+e)}toBeGreaterThan(e){this.triggerResult(this.actualValue>e,` Expected `+formatValue(this.actualValue)+` to be greater than `+e)}toBeGreaterThanOrEqual(e){this.triggerResult(this.actualValue>=e,` Expected ${formatValue(this.actualValue)} to be greater than or equal to ${e}`)}toBeLessThanOrEqual(e){this.triggerResult(this.actualValue<=e,` Expected ${formatValue(this.actualValue)} to be less than or equal to ${e}`)}toBeCloseTo(e,t){let n=10**t;this.triggerResult(Math.round(this.actualValue*n)/n===Math.round(e*n)/n,` Expected `+formatValue(this.actualValue)+` with precision `+t+` to be close to `+e)}toThrow(e){let t=``,n=!1,r=!0,i=!0,a=this.actualValue;try{a(),n=!1}catch(a){t=a?.message||``,n=!0,forgetThrownAssertion(a),typeof e==`function`?r=a instanceof e:typeof e==`string`?i=t.includes(e):e instanceof RegExp&&(i=e.test(t))}let o=this.actualValue?.name||typeof this.actualValue==`function`?`[anonymous function]`:String(this.actualValue);this.triggerResult(n,` Expected ${o} to ${this.positive?`throw`:`not throw`} an exception ${!this.positive&&t?`, but an error with the message "${t}" was thrown`:``}`),typeof e==`function`?this.triggerResult(r,` Expected Error type '${e.name}', but the error is not an instance of it`):e!==void 0&&this.triggerResult(i,` Expected error message to match ${e}\n Actual message: "${t}"`)}async toReject(e){let t=!1,n=``,r=!0,i=!0;try{await this.actualValue,t=!1}catch(a){t=!0,forgetThrownAssertion(a),n=a?.message||String(a),typeof e==`function`?r=a instanceof e:typeof e==`string`?i=n.includes(e):e instanceof RegExp&&(i=e.test(n))}this.triggerResult(t,` Expected promise to ${this.positive?`reject`:`resolve`}${!this.positive&&n?`, but it rejected with "${n}"`:``}`),t&&typeof e==`function`?this.triggerResult(r,` Expected rejection type '${e.name}', but the error is not an instance of it`):t&&e!==void 0&&this.triggerResult(i,` Expected rejection message to match ${e}\n Actual message: "${n}"`)}async toResolve(){let e=!1,t=``;try{await this.actualValue,e=!0}catch(n){e=!1,forgetThrownAssertion(n),t=n?.message||String(n)}this.triggerResult(e,` Expected promise to ${this.positive?`resolve`:`reject`}${e?``:`, but it rejected with "${t}"`}`)}get rejects(){return{toThrow:e=>this.toReject(e),toReject:e=>this.toReject(e)}}get resolves(){return{toResolve:()=>this.toResolve()}}};const describe=async function(e,t,n){let r=typeof n==`number`?n:n?.timeout??C.suiteTimeout;M(`
9
+ `+e);let i=b;b=e;let a=now();try{await withTimeout(t,r,`describe: ${e}`)}catch(e){if(e instanceof TimeoutError)++u,M(` ${D}⏱ Suite timed out: ${e.message}${j}`);else throw e}b=i;let o=now()-a;M(` ${A}↳ ${formatDuration(o)}${j}`),L=null,R=null};describe.skip=async function(e,t){++d,M(`\n${k}- ${e} (skipped)${j}`)};const envVar=e=>{let t=runtimeGlobals().process?.env;if(t)return t[e];let n=runtimeGlobals().imports?.gi?.GLib;return n?n.getenv(e)??void 0:void 0},displayEnv=()=>({DISPLAY:envVar(`DISPLAY`),WAYLAND_DISPLAY:envVar(`WAYLAND_DISPLAY`)}),hasDisplay=()=>t(o(),displayEnv()),hasGl=()=>e(o(),displayEnv()),runtimeMatch=async function(e,t){if(e.includes(`Display`))return{matched:hasDisplay()};if(e.includes(`Gl`))return{matched:hasGl()};let n=await getRuntime(),r=e.find(e=>n.includes(e));return!r||typeof t==`string`&&!n.includes(t)?{matched:!1}:{matched:!0,runtime:r,version:t}},on=async function(e,t,n){typeof e==`string`&&(e=[e]),typeof t==`function`&&(n=t,t=void 0);let{matched:r}=await runtimeMatch(e,t);if(!r){++d;return}M(`\nOn ${e.join(`, `)}${t?` `+t:``}`),await n()};let L,R;const beforeEach=function(e){L=e},afterEach=function(e){R=e},it=async function(e,t,n){let r=w.get(e);if(r!==void 0){++d,M(` ${k}-${j} ${A}${e} (skipped: ${r})${j}`);return}let i=typeof n==`number`?n:n?.timeout??C.testTimeout,a=now();++m,x=e;let o=new Set;_.push(o);let s,c=!1;try{typeof L==`function`&&await L(),await withTimeout(t,i,e),typeof R==`function`&&await R()}catch(e){c=!0,s=e,e instanceof Error&&o.delete(e)}finally{--m,_.pop()}let l=now()-a,p=s instanceof TimeoutError?[...o]:[];if(!c){M(` ${O}✔${j} ${A}${e} (${formatDuration(l)})${j}`);return}++u;let h=[];c&&h.push(s?.message??String(s));for(let e of p)h.push(`${e.message}\n ${A}↳ thrown from a callback OUTSIDE this test's awaited chain, so it could not reach the test boundary. Reject the promise from that callback (or await a helper that does) to surface it directly.${j}`);let g=h.join(`
10
+ `),v=p[0]??s,y=c&&s instanceof TimeoutError&&p.length===0?`⏱`:`❌`;S.push({suite:b,test:e,message:g}),M(` ${D}${y}${j} ${A}${e} (${formatDuration(l)})${j}`),M(`${D}${g}${j}`),v?.stack&&M(v.stack)};it.skip=async function(e,t){++d,M(` ${k}-${j} ${A}${e} (skipped)${j}`)};const getTestCounters=()=>({overall:l,failed:u,ignored:d,xfail:p,warnings:g.length});it.failing=async function(e,t,n,r){let i=typeof r==`number`?r:r?.timeout??C.testTimeout;if(typeof r==`object`&&r?.when===!1)return it(e,t,{timeout:i});let a=now();++m,_.push(new Set);let o=!1;try{typeof L==`function`&&await L(),await withTimeout(t,i,e),typeof R==`function`&&await R()}catch{o=!0}finally{--m,_.pop()}let s=now()-a;if(o){++p,M(` ${k}✗${j} ${A}${e} (expected failure — ${n})${j}`);return}++u,S.push({suite:b,test:e,message:`it.failing("${e}") PASSED — the expected failure no longer happens, so this marker is stale and must be removed (turn it back into a plain it()). It was marked expected-failing because: ${n}`}),M(` ${D}❌${j} ${A}${e} (${formatDuration(s)})${j}`),M(`${D}it.failing passed unexpectedly — remove the marker. Reason it carried: ${n}${j}`)};const expect=function(e,t){return++l,new I(e,!0)},failAssertion=e=>{throw e.__testFailureCounted=!0,m===0?noteStrayFailure(e?.message??String(e)):noteThrownAssertion(e),e},assert=function(e,t){++l;try{r(e,t)}catch(e){failAssertion(e)}};assert.strictEqual=function(e,t,n){++l;try{r.strictEqual(e,t,n)}catch(e){failAssertion(e)}},assert.throws=function(e,...t){++l;let n;try{e()}catch(e){n=e}try{r.throws(()=>{if(n)throw n},t[0],t[1])}catch(e){failAssertion(e)}},assert.deepStrictEqual=function(e,t,n){++l;try{r.deepStrictEqual(e,t,n)}catch(e){failAssertion(e)}};const runTests=async function(e){for(let t in e){let n=e[t];typeof n==`function`?await n():typeof n==`object`&&await runTests(n)}},browserSignalDone=()=>{let e=runtimeGlobals(),t=e.document;t&&(e.__gjsify_test_results={passed:l-u,failed:u,total:l,errors:S},t.documentElement.dataset.testsDone=`true`)},printResult=()=>{let e=y>0?now()-y:0,t=e>0?` ${A}(${formatDuration(e)})`:``,n=v?`[${v}] `:``;if(d&&M(`\n${k}✔ ${d} ignored test${d>1?`s`:``}${j}`),p&&M(`\n${k}✗ ${p} expected failure${p>1?`s`:``} (it.failing — upstream defects)${j}`),g.length){M(`\n${k}⚠ ${g.length} warning${g.length>1?`s`:``} (not counted — nothing is claimed about these)${j}`);for(let e of g)M(` ${k}↳ ${e.message.trim().split(`
11
+ `)[0]}${j}`)}if(h.length){M(`\n${D}⚠ ${h.length} assertion${h.length>1?`s`:``} fired outside any it() (leaked from a settled test)${j}`);for(let e of h)M(` ${D}↳ ${e.message.trim().split(`
12
+ `)[0]}${j}`)}M(u?`\n${D}❌ ${n}${u} of ${l} tests failed${t}${j}`:`\n${O}✔ ${n}${l} completed${t}${j}`)},z={GJS:`Gjs`,"Node.js":`Node.js`,Bun:`Bun`,Deno:`Deno`},getRuntime=async()=>{if(v&&v!==`Unknown`)return v;let e=z[i];return e?(v=a?`${e} ${a}`:e,v):runtimeGlobals().document===void 0?v||`Unknown`:(v=`Browser`,v)},printRuntime=async()=>{let e=await getRuntime();M(`\nRunning on ${e}`)},run=async(e,t)=>{if(applyEnvOverrides(),installUncaughtHooks(),y=now(),w=new Map,p=0,g.length=0,t&&(typeof t==`number`?C.runTimeout=t:(t.timeout!==void 0&&(C.runTimeout=t.timeout),t.testTimeout!==void 0&&(C.testTimeout=t.testTimeout),t.suiteTimeout!==void 0&&(C.suiteTimeout=t.suiteTimeout),t.skip&&(w=new Map(Object.entries(t.skip))))),printRuntime().then(async()=>{try{await withTimeout(()=>runTests(e),C.runTimeout,`entire test run`)}catch(e){if(e instanceof TimeoutError)M(`\n${D}⏱ ${e.message}${j}`),++u;else throw e}}).then(async()=>{if(printResult(),browserSignalDone(),M(),s(),c?.quit(),!c){let e=+(u>0);try{(globalThis.process||await import(`node:process`)).exit(e)}catch{}}}),c?.run(),c){let e=+(u>0);runtimeGlobals().imports?.system?.exit(e)}};var B={run,assert,expect,it,afterEach,beforeEach,on,describe,configure,print:M,vi:F};export{afterEach,assert,beforeEach,configure,B as default,describe,expect,formatValue,getTestCounters,it,on,M as print,run,n as spy,F as vi};
@@ -0,0 +1,40 @@
1
+ import type { TargetOs } from '@gjsify/utils/core';
2
+ /** The env vars the rules read. `undefined` means "unset", never "empty". */
3
+ export interface DisplayEnv {
4
+ DISPLAY?: string;
5
+ WAYLAND_DISPLAY?: string;
6
+ }
7
+ /**
8
+ * True where the platform backend supplies the display itself: GdkWin32, GdkQuartz.
9
+ *
10
+ * Neither sets `DISPLAY` nor `WAYLAND_DISPLAY` — those are X11/Wayland variables.
11
+ */
12
+ export declare function isDisplaylessBackend(os: TargetOs | undefined): boolean;
13
+ /**
14
+ * Can this host realize a window?
15
+ *
16
+ * Reading only `DISPLAY`/`WAYLAND_DISPLAY` does not mean "no display" off Linux —
17
+ * it means SKIPPED, permanently and silently, on every macOS and Windows host.
18
+ * That is not hypothetical: it is how the darwin GTK path stayed uncovered long
19
+ * enough for two independent darwin defects to ship in one release, with nothing
20
+ * on that job able to fail. `@gjsify/node-gi`'s `test/display-gate.mjs` states the
21
+ * same rule for the suites outside this workspace; the two are deliberate mirrors
22
+ * because ADR 0005 forbids the dependency edge that would let them share a module.
23
+ */
24
+ export declare function canRealizeSurface(os: TargetOs | undefined, env: DisplayEnv): boolean;
25
+ /**
26
+ * Can this host realize a GL context our WebGL implementation can serve?
27
+ *
28
+ * STRICTLY narrower than {@link canRealizeSurface}, and the reason the two carry
29
+ * different names. macOS has a window server without `DISPLAY`, so it passes the
30
+ * surface gate — but CGL caps out at GL 4.1 while WebGL2 content needs
31
+ * `#version 300 es` (ARB_ES3_compatibility, core in GL 4.3), which the driver
32
+ * refuses outright. Answering the GL question with the surface answer turns macOS
33
+ * red for a reason the WebGL suite does not own; answering the surface question
34
+ * with the GL answer is what kept darwin silent. win32 is excluded for the
35
+ * measured reason in #1097: the bundled GTK resolves epoxy with no GL
36
+ * implementation behind it.
37
+ *
38
+ * Widen this the day a leg proves the context — not the day it seems plausible.
39
+ */
40
+ export declare function canRealizeGl(os: TargetOs | undefined, env: DisplayEnv): boolean;
@@ -14,7 +14,15 @@ export interface Namespaces {
14
14
  [key: string]: () => Promise<void> | Namespaces;
15
15
  }
16
16
  export type Callback = () => void | Promise<void>;
17
- export type Runtime = 'Gjs' | 'Deno' | 'Node.js' | 'Unknown' | 'Browser' | 'Display';
17
+ /**
18
+ * What `on()` gates on: a runtime IDENTITY, or a host CAPABILITY.
19
+ *
20
+ * `'Display'` and `'Gl'` are capabilities and deliberately separate — a gate must
21
+ * state the thing it actually requires. They were one name until the WebGL suites
22
+ * (which need a realizable GL context) and any plain GTK-surface test shared
23
+ * `'Display'`, which made each wrong on a different OS. See `hasDisplay`/`hasGl`.
24
+ */
25
+ export type Runtime = 'Gjs' | 'Deno' | 'Bun' | 'Node.js' | 'Unknown' | 'Browser' | 'Display' | 'Gl';
18
26
  export declare const print: (...data: any[]) => void;
19
27
  /**
20
28
  * Render any value for an assertion failure message WITHOUT throwing.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/unit",
3
- "version": "0.37.0",
3
+ "version": "0.38.0",
4
4
  "description": "A BDD-style testing framework for Gjs",
5
5
  "module": "lib/esm/index.js",
6
6
  "types": "lib/types/index.d.ts",
@@ -49,23 +49,29 @@
49
49
  "devDependencies": {
50
50
  "@girs/gjs": "^4.1.0",
51
51
  "@girs/glib-2.0": "^4.1.0",
52
- "@gjsify/cli": "^0.37.0",
52
+ "@gjsify/cli": "^0.38.0",
53
53
  "@types/node": "^25.9.2",
54
54
  "typescript": "^6.0.3"
55
55
  },
56
56
  "dependencies": {
57
- "@gjsify/abort-controller": "^0.37.0",
58
- "@gjsify/assert": "^0.37.0",
59
- "@gjsify/buffer": "^0.37.0",
60
- "@gjsify/dom-elements": "^0.37.0",
61
- "@gjsify/dom-exception": "^0.37.0",
62
- "@gjsify/node-globals": "^0.37.0",
63
- "@gjsify/process": "^0.37.0",
64
- "@gjsify/utils": "^0.37.0",
65
- "@gjsify/web-globals": "^0.37.0",
66
- "@gjsify/web-streams": "^0.37.0"
57
+ "@gjsify/abort-controller": "^0.38.0",
58
+ "@gjsify/assert": "^0.38.0",
59
+ "@gjsify/buffer": "^0.38.0",
60
+ "@gjsify/dom-elements": "^0.38.0",
61
+ "@gjsify/dom-exception": "^0.38.0",
62
+ "@gjsify/node-globals": "^0.38.0",
63
+ "@gjsify/process": "^0.38.0",
64
+ "@gjsify/runtime": "^0.38.0",
65
+ "@gjsify/utils": "^0.38.0",
66
+ "@gjsify/web-globals": "^0.38.0",
67
+ "@gjsify/web-streams": "^0.38.0"
67
68
  },
68
69
  "gjsify": {
69
- "tier": 1
70
+ "tier": 1,
71
+ "os": {
72
+ "linux": "supported",
73
+ "darwin": "supported",
74
+ "win32": "supported"
75
+ }
70
76
  }
71
77
  }