@gjsify/unit 0.52.0 → 0.53.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/README.md +9 -0
- package/lib/esm/capabilities.js +1 -1
- package/lib/esm/index.js +3 -3
- package/lib/types/capabilities.d.ts +16 -16
- package/package.json +13 -13
package/README.md
CHANGED
|
@@ -36,6 +36,15 @@ gjs -m test.gjs.js
|
|
|
36
36
|
node test-runner.mjs
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
+
### Capability gates in CI
|
|
40
|
+
|
|
41
|
+
`on('Display')` and `on('Gl')` run their block only where the host can realize a surface / a
|
|
42
|
+
GL context — `on('Gl')` asks by realizing a GDK GL context once per process. A "no" is a skip,
|
|
43
|
+
which is right on a host without GL and wrong on a CI step that provisioned it. Such a step sets
|
|
44
|
+
`GJSIFY_TEST_EXPECT_AXES=Gl` (comma-separated axes): an expected axis that a gate named but the
|
|
45
|
+
host did not match then fails the run, with the probe's reason. An entry that never names the
|
|
46
|
+
axis is not held to it.
|
|
47
|
+
|
|
39
48
|
### Available matchers
|
|
40
49
|
|
|
41
50
|
- `toBe(value)` — strict equality (`===`)
|
package/lib/esm/capabilities.js
CHANGED
|
@@ -1 +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
|
|
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)}async function canRealizeGl(e,t,n){return canRealizeSurface(e,t)&&await n()}export{canRealizeGl,canRealizeSurface,isDisplaylessBackend};
|
package/lib/esm/index.js
CHANGED
|
@@ -6,9 +6,9 @@ import"./_virtual/_rolldown/runtime.js";import{canRealizeGl as e,canRealizeSurfa
|
|
|
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
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??k.suiteTimeout;z(`
|
|
9
|
-
`+e);let i=E;E=e;let a=now();
|
|
10
|
-
`),_=u[0]??s,v=c&&s instanceof TimeoutError&&u.length===0?`⏱`:`❌`;O.push({suite:E,test:e,message:g}),z(` ${P}${v}${R} ${L}${e} (${formatDuration(l)})${R}`),z(`${P}${g}${R}`),_?.stack&&z(_.stack)};it.skip=async function(e,t){++h,z(` ${I}-${R} ${L}${e} (skipped)${R}`)};const getTestCounters=()=>({assertions:u,tests:d,failed:p,failedOutsideTests:m,ignored:h,xfail:_,warnings:x.length}),getAxisLedger=()=>{let e={};for(let[t,n]of v)e[t]={...n};return e};it.failing=async function(e,t,n,r){let i=typeof r==`number`?r:r?.timeout??k.testTimeout;if(typeof r==`object`&&r?.when===!1)return it(e,t,{timeout:i});let a=now();++d,++y,C.push(new Set);let o=!1;try{await runBeforeEachHooks(),await withTimeout(t,i,e)}catch{o=!0}finally{try{await runAfterEachHooks()}catch(t){noteWarning(`afterEach threw after the expected failure "${e}": ${errorMessage(t)}`)}--y,C.pop()}let s=now()-a;if(o){++_,z(` ${I}✗${R} ${L}${e} (expected failure — ${n})${R}`);return}++p,O.push({suite:E,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}`}),z(` ${P}❌${R} ${L}${e} (${formatDuration(s)})${R}`),z(`${P}it.failing passed unexpectedly — remove the marker. Reason it carried: ${n}${R}`)};const expect=function(e,t){return++u,new U(e,!0)},failAssertion=e=>{throw e.__testFailureCounted=!0,y===0?noteStrayFailure(e?.message??String(e)):noteThrownAssertion(e),e},assert=function(e,t){++u;try{i(e,t)}catch(e){failAssertion(e)}};assert.strictEqual=function(e,t,n){++u;try{i.strictEqual(e,t,n)}catch(e){failAssertion(e)}},assert.throws=function(e,...t){++u;let n;try{e()}catch(e){n=e}try{i.throws(()=>{if(n)throw n},t[0],t[1])}catch(e){failAssertion(e)}},assert.deepStrictEqual=function(e,t,n){++u;try{i.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:d-p,failed:p+m,total:d,assertions:u,errors:O},t.documentElement.dataset.testsDone=`true`)},failUnexercisedAxes=async e=>{for(let t of e){let{matched:e}=await runtimeMatch([t]);if(!e)continue;let n=v.get(t);if(n&&n.tests>0)continue;++m;let r=`declared axis '${t}' ran on this host but exercised nothing — ${n?`${n.matched} gate(s) matched but executed no test, ${n.ignored} stood down`:`no on() gate named it`}`;O.push({suite:`<axis declaration>`,test:t,message:r}),z(`\n${P}❌ ${r}${R}`)}},formatFailureVerdict=e=>{let t=[];if(e.failed&&t.push(`${e.failed} of ${e.tests} tests failed`),e.outside){let n=e.outside===1?``:`s`;t.push(e.failed?`${e.outside} failure${n} outside any test`:`${e.outside} failure${n} outside any test, ${e.tests} tests passed`)}return t.join(`, and `)},exitCodeFor=(e,t)=>e>0||t?1:0,printResult=()=>{let e=T>0?now()-T:0,t=e>0?` ${L}(${formatDuration(e)})`:``,n=w?`[${w}] `:``;if(h&&z(`\n${I}✔ ${h} ignored test${h>1?`s`:``}${R}`),v.size){z(`\n${I}⊞ axes exercised${R}`);for(let[e,t]of v)z(` ${I}↳ ${e}: ${t.tests} test${t.tests===1?``:`s`} from ${t.matched} gate${t.matched===1?``:`s`}${t.ignored?`, ${t.ignored} stood down`:``}${R}`)}if(_&&z(`\n${I}✗ ${_} expected failure${_>1?`s`:``} (it.failing — upstream defects)${R}`),x.length){z(`\n${I}⚠ ${x.length} warning${x.length>1?`s`:``} (not counted — nothing is claimed about these)${R}`);for(let e of x)z(` ${I}↳ ${e.message.trim().split(`
|
|
9
|
+
`+e);let i=E;E=e;let a=now();K.push({before:[],after:[]});try{await withTimeout(t,r,`describe: ${e}`)}catch(t){if(t instanceof TimeoutError)++m,O.push({suite:e,test:`<suite timed out>`,message:t.message}),z(` ${P}⏱ Suite timed out: ${t.message}${R}`);else throw t}finally{K.pop()}E=i;let o=now()-a;z(` ${L}↳ ${formatDuration(o)}${R}`)};describe.skip=async function(e,t){++h,z(`\n${I}- ${e} (skipped)${R}`)};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(s(),displayEnv());let W,G=``;const loadGlProbeGi=async()=>{let[e,t]=await Promise.all([import(`gi://Gtk?version=4.0`),import(`gi://Gdk?version=4.0`)]);if(!e.default||!t.default)throw Error(`no GTK 4 reachable from this runtime`);return{Gtk:e.default,Gdk:t.default}},realizeGlContext=async()=>{if(W!==void 0)return W;try{let{Gtk:e,Gdk:t}=await loadGlProbeGi();if(!e.init_check())throw Error(`Gtk.init_check() could not open the display`);let n=t.Display.get_default();if(!n)throw Error(`GDK has no default display`);n.create_gl_context().realize(),t.GLContext.clear_current(),W=!0}catch(e){W=!1,G=errorMessage(e),noteWarning(`on('Gl') skipped: a display exists but no GL context realizes — ${G}`)}return W},hasGl=()=>e(s(),displayEnv(),realizeGlContext),runtimeMatch=async function(e,t){if(e.includes(`Display`))return{matched:hasDisplay(),runtime:`Display`};if(e.includes(`Gl`))return{matched:await hasGl(),runtime:`Gl`};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,runtime:i}=await runtimeMatch(e,t);if(!r){++h;for(let t of e)++axisRecord(t).ignored;return}z(`\nOn ${e.join(`, `)}${t?` `+t:``}`);let a=d;await n();let o=axisRecord(i??e[0]);++o.matched,o.tests+=d-a},K=[{before:[],after:[]}],currentHookFrame=()=>K[K.length-1],beforeEach=function(e){typeof e==`function`&¤tHookFrame().before.push(e)},afterEach=function(e){typeof e==`function`&¤tHookFrame().after.push(e)},runBeforeEachHooks=async()=>{for(let e of K)for(let t of e.before)await t()},runAfterEachHooks=async()=>{for(let e=K.length-1;e>=0;--e){let t=K[e];for(let e=t.after.length-1;e>=0;--e)await t.after[e]()}},it=async function(e,t,n){let r=j.get(e);if(r!==void 0){++h,z(` ${I}-${R} ${L}${e} (skipped: ${r})${R}`);return}let i=typeof n==`number`?n:n?.timeout??k.testTimeout,a=now();++d,++y,D=e;let o=new Set;C.push(o);let s,c=!1;try{await runBeforeEachHooks(),A.noteInFlight(E?`${E} › ${e}`:e,i),await withTimeout(t,i,e)}catch(e){c=!0,s=e,e instanceof Error&&o.delete(e)}finally{try{await runAfterEachHooks()}catch(t){c?noteWarning(`afterEach also threw while "${e}" was already failing: ${errorMessage(t)}`):(c=!0,s=t,t instanceof Error&&o.delete(t))}--y,C.pop(),noteIfCwdDestroyed(e),A.noteSettled()}let l=now()-a,u=s instanceof TimeoutError?[...o]:[];if(!c){z(` ${F}✔${R} ${L}${e} (${formatDuration(l)})${R}`);return}++p;let m=[];c&&m.push(s?.message??String(s));for(let e of u)m.push(`${e.message}\n ${L}↳ 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.${R}`);let g=m.join(`
|
|
10
|
+
`),_=u[0]??s,v=c&&s instanceof TimeoutError&&u.length===0?`⏱`:`❌`;O.push({suite:E,test:e,message:g}),z(` ${P}${v}${R} ${L}${e} (${formatDuration(l)})${R}`),z(`${P}${g}${R}`),_?.stack&&z(_.stack)};it.skip=async function(e,t){++h,z(` ${I}-${R} ${L}${e} (skipped)${R}`)};const getTestCounters=()=>({assertions:u,tests:d,failed:p,failedOutsideTests:m,ignored:h,xfail:_,warnings:x.length}),getAxisLedger=()=>{let e={};for(let[t,n]of v)e[t]={...n};return e};it.failing=async function(e,t,n,r){let i=typeof r==`number`?r:r?.timeout??k.testTimeout;if(typeof r==`object`&&r?.when===!1)return it(e,t,{timeout:i});let a=now();++d,++y,C.push(new Set);let o=!1;try{await runBeforeEachHooks(),await withTimeout(t,i,e)}catch{o=!0}finally{try{await runAfterEachHooks()}catch(t){noteWarning(`afterEach threw after the expected failure "${e}": ${errorMessage(t)}`)}--y,C.pop()}let s=now()-a;if(o){++_,z(` ${I}✗${R} ${L}${e} (expected failure — ${n})${R}`);return}++p,O.push({suite:E,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}`}),z(` ${P}❌${R} ${L}${e} (${formatDuration(s)})${R}`),z(`${P}it.failing passed unexpectedly — remove the marker. Reason it carried: ${n}${R}`)};const expect=function(e,t){return++u,new U(e,!0)},failAssertion=e=>{throw e.__testFailureCounted=!0,y===0?noteStrayFailure(e?.message??String(e)):noteThrownAssertion(e),e},assert=function(e,t){++u;try{i(e,t)}catch(e){failAssertion(e)}};assert.strictEqual=function(e,t,n){++u;try{i.strictEqual(e,t,n)}catch(e){failAssertion(e)}},assert.throws=function(e,...t){++u;let n;try{e()}catch(e){n=e}try{i.throws(()=>{if(n)throw n},t[0],t[1])}catch(e){failAssertion(e)}},assert.deepStrictEqual=function(e,t,n){++u;try{i.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:d-p,failed:p+m,total:d,assertions:u,errors:O},t.documentElement.dataset.testsDone=`true`)},failUnexercisedAxes=async e=>{for(let t of e){let{matched:e}=await runtimeMatch([t]);if(!e)continue;let n=v.get(t);if(n&&n.tests>0)continue;++m;let r=`declared axis '${t}' ran on this host but exercised nothing — ${n?`${n.matched} gate(s) matched but executed no test, ${n.ignored} stood down`:`no on() gate named it`}`;O.push({suite:`<axis declaration>`,test:t,message:r}),z(`\n${P}❌ ${r}${R}`)}},failUnmetExpectedAxes=async()=>{let e=(envVar(`GJSIFY_TEST_EXPECT_AXES`)??``).split(`,`).map(e=>e.trim()).filter(Boolean);for(let t of e){if(!v.has(t))continue;let{matched:e}=await runtimeMatch([t]);if(e)continue;++m;let n=`axis '${t}' is expected on this host (GJSIFY_TEST_EXPECT_AXES) but stood down${t===`Gl`&&G?` — ${G}`:``}`;O.push({suite:`<axis expectation>`,test:t,message:n}),z(`\n${P}❌ ${n}${R}`)}},formatFailureVerdict=e=>{let t=[];if(e.failed&&t.push(`${e.failed} of ${e.tests} tests failed`),e.outside){let n=e.outside===1?``:`s`;t.push(e.failed?`${e.outside} failure${n} outside any test`:`${e.outside} failure${n} outside any test, ${e.tests} tests passed`)}return t.join(`, and `)},exitCodeFor=(e,t)=>e>0||t?1:0,printResult=()=>{let e=T>0?now()-T:0,t=e>0?` ${L}(${formatDuration(e)})`:``,n=w?`[${w}] `:``;if(h&&z(`\n${I}✔ ${h} ignored test${h>1?`s`:``}${R}`),v.size){z(`\n${I}⊞ axes exercised${R}`);for(let[e,t]of v)z(` ${I}↳ ${e}: ${t.tests} test${t.tests===1?``:`s`} from ${t.matched} gate${t.matched===1?``:`s`}${t.ignored?`, ${t.ignored} stood down`:``}${R}`)}if(_&&z(`\n${I}✗ ${_} expected failure${_>1?`s`:``} (it.failing — upstream defects)${R}`),x.length){z(`\n${I}⚠ ${x.length} warning${x.length>1?`s`:``} (not counted — nothing is claimed about these)${R}`);for(let e of x)z(` ${I}↳ ${e.message.trim().split(`
|
|
11
11
|
`)[0]}${R}`)}if(b.length){z(`\n${P}⚠ ${b.length} assertion${b.length>1?`s`:``} fired outside any it() (leaked from a settled test)${R}`);for(let e of b)z(` ${P}↳ ${e.message.trim().split(`
|
|
12
12
|
`)[0]}${R}`)}if(p||m){printFailureRecap(n);let e=formatFailureVerdict({failed:p,outside:m,tests:d});z(`\n${P}❌ ${n}${e}${countsSuffix()}${t}${R}`)}else z(g?`\n${P}❌ ${n}${d} test${d===1?``:`s`} passed, then a suite body threw — the run is INCOMPLETE${countsSuffix()}${t}${R}`:`\n${F}✔ ${n}${d} test${d===1?``:`s`} passed${countsSuffix()}${t}${R}`)},countsSuffix=()=>{let e=[`${u} assertion${u===1?``:`s`}`];return h&&e.push(`${h} ignored`),` ${L}· ${e.join(` · `)}${R}`},printFailureRecap=e=>{for(let t of formatFailureRecap(O,p+m,e))z(t);if(envVar(`GITHUB_ACTIONS`))for(let t of formatFailureAnnotations(O,e))z(t)},formatFailureAnnotations=(e,t=``,n=10)=>{let escape=e=>e.replace(/%/g,`%25`).replace(/\r/g,`%0D`).replace(/\n/g,`%0A`),r=e.slice(0,n).map(e=>`::error title=${escape(`${t}${e.suite} › ${e.test}`)}::${escape(e.message.trim().split(`
|
|
13
13
|
`)[0])}`);return e.length>n&&r.push(`::error title=${escape(`${t}more failures`)}::${e.length-n} further failure(s) — see the ✖ recap in the log`),r},formatFailureRecap=(e,t,n=``)=>{let r=[`\n${P}✖ ${n}failed test${e.length===1?``:`s`}${R}`];for(let t of e){let e=t.message.trim().split(`
|
|
14
|
-
`)[0];r.push(` ${P}✖ ${t.suite} › ${t.test}${R}${L} — ${e}${R}`)}return e.length!==t&&r.push(` ${P}✖ ${t} failure${t===1?``:`s`} counted but ${e.length} named — a failure path is raising the tally without recording itself, so this list is INCOMPLETE${R}`),r},
|
|
14
|
+
`)[0];r.push(` ${P}✖ ${t.suite} › ${t.test}${R}${L} — ${e}${R}`)}return e.length!==t&&r.push(` ${P}✖ ${t} failure${t===1?``:`s`} counted but ${e.length} named — a failure path is raising the tally without recording itself, so this list is INCOMPLETE${R}`),r},q={GJS:`Gjs`,"Node.js":`Node.js`,Bun:`Bun`,Deno:`Deno`},getRuntime=async()=>{if(w&&w!==`Unknown`)return w;let e=q[a];return e?(w=o?`${e} ${o}`:e,w):runtimeGlobals().document===void 0?w||`Unknown`:(w=`Browser`,w)},printRuntime=async()=>{let e=await getRuntime();z(`\nRunning on ${e}`)},run=async(e,t)=>{applyEnvOverrides(),installUncaughtHooks(),T=now(),j=new Map,_=0,x.length=0,v.clear(),S=probeCwd(),g=!1;let r=[];if(t&&(typeof t==`number`?k.runTimeout=t:(t.timeout!==void 0&&(k.runTimeout=t.timeout),t.testTimeout!==void 0&&(k.testTimeout=t.testTimeout),t.suiteTimeout!==void 0&&(k.suiteTimeout=t.suiteTimeout),t.skip&&(j=new Map(Object.entries(t.skip))),t.requireAxes&&(r=t.requireAxes))),A=n(envBag(),k.runTimeout),printRuntime().then(async()=>{try{await withTimeout(()=>runTests(e),k.runTimeout,`entire test run`)}catch(e){if(e instanceof TimeoutError)z(`\n${P}⏱ ${e.message}${R}`),++m,O.push({suite:`<test run>`,test:`<run timed out>`,message:e.message});else throw e}}).then(()=>failUnexercisedAxes(r)).then(()=>failUnmetExpectedAxes()).catch(e=>{g=!0;let t=e instanceof Error?e.stack??e.message:String(e);z(`\n${P}❌ a suite body threw, so the run is INCOMPLETE and every later suite was skipped${E?` (in "${E}")`:``} — ${t}${R}`)}).then(async()=>{if(printResult(),browserSignalDone(),z(),A.stop(),c(),l?.quit(),!l){let e=exitCodeFor(p+m,g);try{(globalThis.process||await import(`node:process`)).exit(e)}catch{}}}),l?.run(),l){let e=exitCodeFor(p+m,g);runtimeGlobals().imports?.system?.exit(e)}};var J={run,assert,expect,it,afterEach,beforeEach,on,describe,configure,print:z,vi:H};export{afterEach,assert,beforeEach,configure,J as default,describe,exitCodeFor,expect,formatFailureAnnotations,formatFailureRecap,formatFailureVerdict,formatValue,getAxisLedger,getTestCounters,it,on,z as print,run,r as spy,H as vi};
|
|
@@ -25,22 +25,22 @@ export declare function canRealizeSurface(os: TargetOs | undefined, env: Display
|
|
|
25
25
|
/**
|
|
26
26
|
* Can this host realize a GL context our WebGL implementation can serve?
|
|
27
27
|
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* surface gate — but no CI leg has ever realized a GL context there: every darwin
|
|
31
|
-
* GL measurement in `status/open-todos.md` was taken by hand on the test VM, with
|
|
32
|
-
* `DYLD_LIBRARY_PATH` exported by the operator, because the `Gtk-4.0` typelib's
|
|
33
|
-
* bare `libgtk-4.1.dylib` leaf does not otherwise resolve on that host (#973). So
|
|
34
|
-
* the darwin answer is unknown, not no. Answering the GL question with the surface
|
|
35
|
-
* answer turns macOS red for a reason the WebGL suite does not own; answering the
|
|
36
|
-
* surface question with the GL answer is what kept darwin silent. win32 is excluded
|
|
37
|
-
* for the measured reason in #1097: the bundled GTK resolves epoxy with no GL
|
|
38
|
-
* implementation behind it.
|
|
28
|
+
* ASKED, not inferred: `probe` realizes a context and reports whether that
|
|
29
|
+
* worked, and this rule only decides whether asking is sensible at all.
|
|
39
30
|
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
31
|
+
* It used to be answered from the OS alone — Linux with a display, nothing
|
|
32
|
+
* else — because darwin was UNKNOWN rather than no (every darwin GL measurement
|
|
33
|
+
* had been taken by hand, with a loader variable the operator exported, #973),
|
|
34
|
+
* and win32's bundled GTK resolves epoxy with no GL implementation behind it on
|
|
35
|
+
* a VM without an ICD (#1097). Both reasons describe a HOST, not an OS: the
|
|
36
|
+
* first Apple Silicon Mac realized a GL 4.1 core context through GTK 4.24 and
|
|
37
|
+
* the WebGL suites ran green there, while the OS rule had kept every one of
|
|
38
|
+
* them silently skipped. An OS rule is wrong in both directions at once — it
|
|
39
|
+
* skips a Mac that has GL and would run a Linux runner whose driver has none —
|
|
40
|
+
* and the probe is the question itself, so it is right on hosts no rule named.
|
|
43
41
|
*
|
|
44
|
-
*
|
|
42
|
+
* Still STRICTLY narrower than {@link canRealizeSurface}: no surface, no GL, and
|
|
43
|
+
* the probe is never run where there is no display to open (a headless Linux
|
|
44
|
+
* container, where GTK's own init would fail first).
|
|
45
45
|
*/
|
|
46
|
-
export declare function canRealizeGl(os: TargetOs | undefined, env: DisplayEnv): boolean
|
|
46
|
+
export declare function canRealizeGl(os: TargetOs | undefined, env: DisplayEnv, probe: () => boolean | Promise<boolean>): Promise<boolean>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/unit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.53.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",
|
|
@@ -50,22 +50,22 @@
|
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@girs/gjs": "5.4.0",
|
|
52
52
|
"@girs/glib-2.0": "5.4.0",
|
|
53
|
-
"@gjsify/cli": "^0.
|
|
53
|
+
"@gjsify/cli": "^0.53.0",
|
|
54
54
|
"@types/node": "^25.9.2",
|
|
55
55
|
"typescript": "^6.0.3"
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
|
-
"@gjsify/abort-controller": "^0.
|
|
59
|
-
"@gjsify/assert": "^0.
|
|
60
|
-
"@gjsify/buffer": "^0.
|
|
61
|
-
"@gjsify/dom-elements": "^0.
|
|
62
|
-
"@gjsify/dom-exception": "^0.
|
|
63
|
-
"@gjsify/node-globals": "^0.
|
|
64
|
-
"@gjsify/process": "^0.
|
|
65
|
-
"@gjsify/runtime": "^0.
|
|
66
|
-
"@gjsify/utils": "^0.
|
|
67
|
-
"@gjsify/web-globals": "^0.
|
|
68
|
-
"@gjsify/web-streams": "^0.
|
|
58
|
+
"@gjsify/abort-controller": "^0.53.0",
|
|
59
|
+
"@gjsify/assert": "^0.53.0",
|
|
60
|
+
"@gjsify/buffer": "^0.53.0",
|
|
61
|
+
"@gjsify/dom-elements": "^0.53.0",
|
|
62
|
+
"@gjsify/dom-exception": "^0.53.0",
|
|
63
|
+
"@gjsify/node-globals": "^0.53.0",
|
|
64
|
+
"@gjsify/process": "^0.53.0",
|
|
65
|
+
"@gjsify/runtime": "^0.53.0",
|
|
66
|
+
"@gjsify/utils": "^0.53.0",
|
|
67
|
+
"@gjsify/web-globals": "^0.53.0",
|
|
68
|
+
"@gjsify/web-streams": "^0.53.0"
|
|
69
69
|
},
|
|
70
70
|
"gjsify": {
|
|
71
71
|
"tier": 1,
|