@gjsify/unit 0.51.0 → 0.52.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";const glib=()=>globalThis.imports?.gi?.GLib,formatHeartbeat=(e,t)=>`${Math.max(0,Math.round(e))}\t${t.replace(/[\t\r\n]+/g,` `).trim()}`,e={noteInFlight:()=>{},noteSettled:()=>{},stop:()=>{}};function createHeartbeat(t,n){let r=t?.GJSIFY_UNIT_HEARTBEAT,i=glib();if(!r||r.length===0||!i)return e;let write=e=>{try{i.file_set_contents(r,e)}catch{}},a=formatHeartbeat(n>0?Date.now()+n:0,`<test run>`);return write(a),{noteInFlight:(e,t)=>write(formatHeartbeat(t>0?Date.now()+t:0,e)),noteSettled:()=>write(a),stop:()=>write(formatHeartbeat(0,`<run finished>`))}}export{createHeartbeat,formatHeartbeat};
package/lib/esm/index.js CHANGED
@@ -1,14 +1,14 @@
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,h=!1,g=0;const _=new Map,axisRecord=e=>{let t=_.get(e);return t||(t={matched:0,ignored:0,tests:0},_.set(e,t)),t};let v=0;const y=[],b=[],noteWarning=e=>{b.push({suite:T,message:e})},errorMessage=e=>e?.message??String(e);let x=!0;const probeCwd=()=>{let e=runtimeGlobals().process?.cwd;if(typeof e!=`function`)return!0;try{return e(),!0}catch{return!1}},noteIfCwdDestroyed=e=>{if(!x||probeCwd())return;x=!1,++p;let t="this test left the process in a deleted working directory — every later `process.cwd()`, including one inside a spawned child, now fails with ENOENT. A spec that `chdir`s into a temp dir must chdir BACK before removing it, and the whole run shares one process.";D.push({suite:T,test:e,message:t}),L(` ${M}❌ ${e} — ${t}${I}`)},noteStrayFailure=e=>{++p,y.push({suite:T,message:e}),D.push({suite:T,test:`<stray assertion outside any it()>`,message:e})},S=[],noteThrownAssertion=e=>{let t=S[S.length-1];t&&e instanceof Error&&t.add(e)},forgetThrownAssertion=e=>{if(e instanceof Error)for(let t of S)t.delete(e)};let C=``,w=0,T=``,E=``,D=[],O={testTimeout:5e3,suiteTimeout:3e4,runTimeout:12e4},k=new Map;var TimeoutError=class extends Error{constructor(e,t){super(`Timeout: "${e}" exceeded ${t}ms`),this.name=`TimeoutError`}};const A=[];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(()=>{}),A.push(a);try{let t=Promise.resolve(e());return t.catch(()=>{}),await Promise.race([t,i,o])}finally{clearTimeout(r);let e=A.lastIndexOf(a);e!==-1&&A.splice(e,1)}}let j=!1;const installUncaughtHooks=()=>{if(j)return;let e=runtimeGlobals().process;if(typeof e?.on!=`function`||typeof e.versions?.gjs==`string`)return;j=!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=v>0?` during "${E}"`:``;noteWarning(`${t}: ${e}${r} — absorbed by a listener the spec installed itself`);return}let a=A[A.length-1];a?a(n):noteStrayFailure(n?.message??String(n))};e.on(`uncaughtException`,handle(`uncaughtException`)),e.on(`unhandledRejection`,handle(`unhandledRejection`))},configure=e=>{O={...O,...e}};function applyEnvOverrides(){try{let e=runtimeGlobals().process?.env;if(!e)return;let t=parseInt(e.GJSIFY_TEST_TIMEOUT,10);!isNaN(t)&&t>=0&&(O.testTimeout=t);let n=parseInt(e.GJSIFY_SUITE_TIMEOUT,10);!isNaN(n)&&n>=0&&(O.suiteTimeout=n);let r=parseInt(e.GJSIFY_RUN_TIMEOUT,10);!isNaN(r)&&r>=0&&(O.runTimeout=r)}catch{}}const M=`\x1B[31m`,N=`\x1B[32m`,P=`\x1B[34m`,F=`\x1B[90m`,I=`\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`,L=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 R=[],z=[];function envBag(){return globalThis.process?.env}const B={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;R.push({key:e,had:e in n,prev:n[e]}),n[e]=t},unstubAllGlobals(){let e=globalThis;for(let t=R.length-1;t>=0;t--){let n=R[t];n.had?e[n.key]=n.prev:delete e[n.key]}R.length=0},stubEnv(e,t){let n=envBag();n&&(z.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=z.length-1;t>=0;t--){let n=z[t];n.had?e[n.key]=n.prev:delete e[n.key]}z.length=0}}};var V=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,v===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{createHeartbeat as n}from"./heartbeat.js";import{spy as r}from"./spy.js";import"@girs/gjs";import i from"node:assert";import{runtimeName as a,runtimeVersion as o}from"@gjsify/runtime";import{hostOs as s}from"@gjsify/utils/core";import{quitMainLoop as c}from"@gjsify/utils/main-loop";const runtimeGlobals=()=>globalThis,l=typeof runtimeGlobals().process?.versions?.gjs==`string`?runtimeGlobals().imports?.mainloop:void 0;let u=0,d=0,p=0,m=0,h=0,g=!1,_=0;const v=new Map,axisRecord=e=>{let t=v.get(e);return t||(t={matched:0,ignored:0,tests:0},v.set(e,t)),t};let y=0;const b=[],x=[],noteWarning=e=>{x.push({suite:E,message:e})},errorMessage=e=>e?.message??String(e);let S=!0;const probeCwd=()=>{let e=runtimeGlobals().process?.cwd;if(typeof e!=`function`)return!0;try{return e(),!0}catch{return!1}},noteIfCwdDestroyed=e=>{if(!S||probeCwd())return;S=!1,++m;let t="this test left the process in a deleted working directory — every later `process.cwd()`, including one inside a spawned child, now fails with ENOENT. A spec that `chdir`s into a temp dir must chdir BACK before removing it, and the whole run shares one process.";O.push({suite:E,test:e,message:t}),z(` ${P}❌ ${e} — ${t}${R}`)},noteStrayFailure=e=>{++m,b.push({suite:E,message:e}),O.push({suite:E,test:`<stray assertion outside any it()>`,message:e})},C=[],noteThrownAssertion=e=>{let t=C[C.length-1];t&&e instanceof Error&&t.add(e)},forgetThrownAssertion=e=>{if(e instanceof Error)for(let t of C)t.delete(e)};let w=``,T=0,E=``,D=``,O=[],k={testTimeout:5e3,suiteTimeout:3e4,runTimeout:12e4},A=n(void 0,0),j=new Map;var TimeoutError=class extends Error{constructor(e,t){super(`Timeout: "${e}" exceeded ${t}ms`),this.name=`TimeoutError`}};const M=[];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(()=>{}),M.push(a);try{let t=Promise.resolve(e());return t.catch(()=>{}),await Promise.race([t,i,o])}finally{clearTimeout(r);let e=M.lastIndexOf(a);e!==-1&&M.splice(e,1)}}let N=!1;const installUncaughtHooks=()=>{if(N)return;let e=runtimeGlobals().process;if(typeof e?.on!=`function`||typeof e.versions?.gjs==`string`)return;N=!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=y>0?` during "${D}"`:``;noteWarning(`${t}: ${e}${r} — absorbed by a listener the spec installed itself`);return}let a=M[M.length-1];a?a(n):noteStrayFailure(n?.message??String(n))};e.on(`uncaughtException`,handle(`uncaughtException`)),e.on(`unhandledRejection`,handle(`unhandledRejection`))},configure=e=>{k={...k,...e}};function applyEnvOverrides(){try{let e=runtimeGlobals().process?.env;if(!e)return;let t=parseInt(e.GJSIFY_TEST_TIMEOUT,10);!isNaN(t)&&t>=0&&(k.testTimeout=t);let n=parseInt(e.GJSIFY_SUITE_TIMEOUT,10);!isNaN(n)&&n>=0&&(k.suiteTimeout=n);let r=parseInt(e.GJSIFY_RUN_TIMEOUT,10);!isNaN(r)&&r>=0&&(k.runTimeout=r)}catch{}}const P=`\x1B[31m`,F=`\x1B[32m`,I=`\x1B[34m`,L=`\x1B[90m`,R=`\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`,z=typeof runtimeGlobals().process?.versions?.gjs!=`string`&&runtimeGlobals().document!==void 0?console.log:globalThis.print||console.log;function isStructurallyEqual(e,t){try{return i.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 B=[],V=[];function envBag(){return globalThis.process?.env}const H={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;B.push({key:e,had:e in n,prev:n[e]}),n[e]=t},unstubAllGlobals(){let e=globalThis;for(let t=B.length-1;t>=0;t--){let n=B[t];n.had?e[n.key]=n.prev:delete e[n.key]}B.length=0},stubEnv(e,t){let n=envBag();n&&(V.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=V.length-1;t>=0;t--){let n=V[t];n.had?e[n.key]=n.prev:delete e[n.key]}V.length=0}}};var U=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,y===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 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
3
+ Expected: ${formatValue(e)} (${typeof e})\n Actual: ${formatValue(this.actualValue)} (${typeof this.actualValue})${n}`)}toStrictEqual(e){let t=!0,n=``;try{i.deepStrictEqual(this.actualValue,e)}catch(e){t=!1,n=e.message||``}this.triggerResult(t,` Expected values to be deeply strictly equal
4
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 ==
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 i.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??O.suiteTimeout;L(`
9
- `+e);let i=T;T=e;let a=now();H.push({before:[],after:[]});try{await withTimeout(t,r,`describe: ${e}`)}catch(t){if(t instanceof TimeoutError)++p,D.push({suite:e,test:`<suite timed out>`,message:t.message}),L(` ${M}⏱ Suite timed out: ${t.message}${I}`);else throw t}finally{H.pop()}T=i;let o=now()-a;L(` ${F}↳ ${formatDuration(o)}${I}`)};describe.skip=async function(e,t){++m,L(`\n${P}- ${e} (skipped)${I}`)};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(),runtime:`Display`};if(e.includes(`Gl`))return{matched: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){++m;for(let t of e)++axisRecord(t).ignored;return}L(`\nOn ${e.join(`, `)}${t?` `+t:``}`);let a=u;await n();let o=axisRecord(i??e[0]);++o.matched,o.tests+=u-a},H=[{before:[],after:[]}],currentHookFrame=()=>H[H.length-1],beforeEach=function(e){typeof e==`function`&&currentHookFrame().before.push(e)},afterEach=function(e){typeof e==`function`&&currentHookFrame().after.push(e)},runBeforeEachHooks=async()=>{for(let e of H)for(let t of e.before)await t()},runAfterEachHooks=async()=>{for(let e=H.length-1;e>=0;--e){let t=H[e];for(let e=t.after.length-1;e>=0;--e)await t.after[e]()}},it=async function(e,t,n){let r=k.get(e);if(r!==void 0){++m,L(` ${P}-${I} ${F}${e} (skipped: ${r})${I}`);return}let i=typeof n==`number`?n:n?.timeout??O.testTimeout,a=now();++u,++v,E=e;let o=new Set;S.push(o);let s,c=!1;try{await runBeforeEachHooks(),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))}--v,S.pop(),noteIfCwdDestroyed(e)}let l=now()-a,p=s instanceof TimeoutError?[...o]:[];if(!c){L(` ${N}✔${I} ${F}${e} (${formatDuration(l)})${I}`);return}++d;let h=[];c&&h.push(s?.message??String(s));for(let e of p)h.push(`${e.message}\n ${F}↳ 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.${I}`);let g=h.join(`
10
- `),_=p[0]??s,y=c&&s instanceof TimeoutError&&p.length===0?`⏱`:`❌`;D.push({suite:T,test:e,message:g}),L(` ${M}${y}${I} ${F}${e} (${formatDuration(l)})${I}`),L(`${M}${g}${I}`),_?.stack&&L(_.stack)};it.skip=async function(e,t){++m,L(` ${P}-${I} ${F}${e} (skipped)${I}`)};const getTestCounters=()=>({assertions:l,tests:u,failed:d,failedOutsideTests:p,ignored:m,xfail:g,warnings:b.length}),getAxisLedger=()=>{let e={};for(let[t,n]of _)e[t]={...n};return e};it.failing=async function(e,t,n,r){let i=typeof r==`number`?r:r?.timeout??O.testTimeout;if(typeof r==`object`&&r?.when===!1)return it(e,t,{timeout:i});let a=now();++u,++v,S.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)}`)}--v,S.pop()}let s=now()-a;if(o){++g,L(` ${P}✗${I} ${F}${e} (expected failure — ${n})${I}`);return}++d,D.push({suite:T,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}`}),L(` ${M}❌${I} ${F}${e} (${formatDuration(s)})${I}`),L(`${M}it.failing passed unexpectedly — remove the marker. Reason it carried: ${n}${I}`)};const expect=function(e,t){return++l,new V(e,!0)},failAssertion=e=>{throw e.__testFailureCounted=!0,v===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:u-d,failed:d+p,total:u,assertions:l,errors:D},t.documentElement.dataset.testsDone=`true`)},failUnexercisedAxes=async e=>{for(let t of e){let{matched:e}=await runtimeMatch([t]);if(!e)continue;let n=_.get(t);if(n&&n.tests>0)continue;++p;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`}`;D.push({suite:`<axis declaration>`,test:t,message:r}),L(`\n${M}❌ ${r}${I}`)}},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=w>0?now()-w:0,t=e>0?` ${F}(${formatDuration(e)})`:``,n=C?`[${C}] `:``;if(m&&L(`\n${P}✔ ${m} ignored test${m>1?`s`:``}${I}`),_.size){L(`\n${P}⊞ axes exercised${I}`);for(let[e,t]of _)L(` ${P}↳ ${e}: ${t.tests} test${t.tests===1?``:`s`} from ${t.matched} gate${t.matched===1?``:`s`}${t.ignored?`, ${t.ignored} stood down`:``}${I}`)}if(g&&L(`\n${P}✗ ${g} expected failure${g>1?`s`:``} (it.failing — upstream defects)${I}`),b.length){L(`\n${P}⚠ ${b.length} warning${b.length>1?`s`:``} (not counted — nothing is claimed about these)${I}`);for(let e of b)L(` ${P}↳ ${e.message.trim().split(`
11
- `)[0]}${I}`)}if(y.length){L(`\n${M}⚠ ${y.length} assertion${y.length>1?`s`:``} fired outside any it() (leaked from a settled test)${I}`);for(let e of y)L(` ${M}↳ ${e.message.trim().split(`
12
- `)[0]}${I}`)}if(d||p){printFailureRecap(n);let e=formatFailureVerdict({failed:d,outside:p,tests:u});L(`\n${M}❌ ${n}${e}${countsSuffix()}${t}${I}`)}else L(h?`\n${M}❌ ${n}${u} test${u===1?``:`s`} passed, then a suite body threw — the run is INCOMPLETE${countsSuffix()}${t}${I}`:`\n${N}✔ ${n}${u} test${u===1?``:`s`} passed${countsSuffix()}${t}${I}`)},countsSuffix=()=>{let e=[`${l} assertion${l===1?``:`s`}`];return m&&e.push(`${m} ignored`),` ${F}· ${e.join(` · `)}${I}`},printFailureRecap=e=>{for(let t of formatFailureRecap(D,d+p,e))L(t);if(envVar(`GITHUB_ACTIONS`))for(let t of formatFailureAnnotations(D,e))L(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
- `)[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${M}✖ ${n}failed test${e.length===1?``:`s`}${I}`];for(let t of e){let e=t.message.trim().split(`
14
- `)[0];r.push(` ${M}✖ ${t.suite} › ${t.test}${I}${F} — ${e}${I}`)}return e.length!==t&&r.push(` ${M}✖ ${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${I}`),r},U={GJS:`Gjs`,"Node.js":`Node.js`,Bun:`Bun`,Deno:`Deno`},getRuntime=async()=>{if(C&&C!==`Unknown`)return C;let e=U[i];return e?(C=a?`${e} ${a}`:e,C):runtimeGlobals().document===void 0?C||`Unknown`:(C=`Browser`,C)},printRuntime=async()=>{let e=await getRuntime();L(`\nRunning on ${e}`)},run=async(e,t)=>{applyEnvOverrides(),installUncaughtHooks(),w=now(),k=new Map,g=0,b.length=0,_.clear(),x=probeCwd(),h=!1;let n=[];if(t&&(typeof t==`number`?O.runTimeout=t:(t.timeout!==void 0&&(O.runTimeout=t.timeout),t.testTimeout!==void 0&&(O.testTimeout=t.testTimeout),t.suiteTimeout!==void 0&&(O.suiteTimeout=t.suiteTimeout),t.skip&&(k=new Map(Object.entries(t.skip))),t.requireAxes&&(n=t.requireAxes))),printRuntime().then(async()=>{try{await withTimeout(()=>runTests(e),O.runTimeout,`entire test run`)}catch(e){if(e instanceof TimeoutError)L(`\n${M}⏱ ${e.message}${I}`),++p,D.push({suite:`<test run>`,test:`<run timed out>`,message:e.message});else throw e}}).then(()=>failUnexercisedAxes(n)).catch(e=>{h=!0;let t=e instanceof Error?e.stack??e.message:String(e);L(`\n${M}❌ a suite body threw, so the run is INCOMPLETE and every later suite was skipped${T?` (in "${T}")`:``} — ${t}${I}`)}).then(async()=>{if(printResult(),browserSignalDone(),L(),s(),c?.quit(),!c){let e=exitCodeFor(d+p,h);try{(globalThis.process||await import(`node:process`)).exit(e)}catch{}}}),c?.run(),c){let e=exitCodeFor(d+p,h);runtimeGlobals().imports?.system?.exit(e)}};var W={run,assert,expect,it,afterEach,beforeEach,on,describe,configure,print:L,vi:B};export{afterEach,assert,beforeEach,configure,W as default,describe,exitCodeFor,expect,formatFailureAnnotations,formatFailureRecap,formatFailureVerdict,formatValue,getAxisLedger,getTestCounters,it,on,L as print,run,n as spy,B 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??k.suiteTimeout;z(`
9
+ `+e);let i=E;E=e;let a=now();W.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{W.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()),hasGl=()=>e(s(),displayEnv()),runtimeMatch=async function(e,t){if(e.includes(`Display`))return{matched:hasDisplay(),runtime:`Display`};if(e.includes(`Gl`))return{matched: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},W=[{before:[],after:[]}],currentHookFrame=()=>W[W.length-1],beforeEach=function(e){typeof e==`function`&&currentHookFrame().before.push(e)},afterEach=function(e){typeof e==`function`&&currentHookFrame().after.push(e)},runBeforeEachHooks=async()=>{for(let e of W)for(let t of e.before)await t()},runAfterEachHooks=async()=>{for(let e=W.length-1;e>=0;--e){let t=W[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}`)}},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
+ `)[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
+ `)[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
+ `)[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},G={GJS:`Gjs`,"Node.js":`Node.js`,Bun:`Bun`,Deno:`Deno`},getRuntime=async()=>{if(w&&w!==`Unknown`)return w;let e=G[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)).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 K={run,assert,expect,it,afterEach,beforeEach,on,describe,configure,print:z,vi:H};export{afterEach,assert,beforeEach,configure,K as default,describe,exitCodeFor,expect,formatFailureAnnotations,formatFailureRecap,formatFailureVerdict,formatValue,getAxisLedger,getTestCounters,it,on,z as print,run,r as spy,H as vi};
@@ -0,0 +1,26 @@
1
+ /**
2
+ * One line: `<deadline-epoch-ms>\t<label>`. Tabs and newlines are stripped from the label
3
+ * because the reader splits on the first tab and reads one line — a test name containing
4
+ * either would otherwise silently truncate the parse.
5
+ *
6
+ * `deadlineMs <= 0` means "no deadline" (`{ timeout: 0 }` disables the per-test timeout, and a
7
+ * guard must not invent one the harness itself declined to set).
8
+ */
9
+ export declare const formatHeartbeat: (deadlineMs: number, label: string) => string;
10
+ export interface Heartbeat {
11
+ /** Announce the test about to run, with the deadline its own timeout gives it. */
12
+ noteInFlight(label: string, timeoutMs: number): void;
13
+ /** The test settled: fall back to the run-level deadline. */
14
+ noteSettled(): void;
15
+ /** Disarm — whatever holds the process from here on is teardown, not a test. */
16
+ stop(): void;
17
+ }
18
+ /**
19
+ * A heartbeat writing into `env.GJSIFY_UNIT_HEARTBEAT`, or an inert one when nobody named a
20
+ * file (or GLib is out of reach, i.e. not GJS).
21
+ *
22
+ * An INSTANCE rather than module state on purpose: this package's own suite tests the writer,
23
+ * and under `gjsify run` that suite is itself being watched — module state would let the test
24
+ * disarm the guard watching it.
25
+ */
26
+ export declare function createHeartbeat(env: Record<string, string | undefined> | undefined, runTimeoutMs: number): Heartbeat;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/unit",
3
- "version": "0.51.0",
3
+ "version": "0.52.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",
@@ -27,7 +27,7 @@
27
27
  "test:gjs": "gjsify run test.gjs.mjs",
28
28
  "test:node": "node test.node.mjs",
29
29
  "test:bun": "bun test.node.mjs",
30
- "test:deno": "deno run -A --no-lock --node-modules-dir=none test.node.mjs",
30
+ "test:deno": "deno run -A --no-lock --node-modules-dir=none --min-dep-age=0 test.node.mjs",
31
31
  "test:cross-runtime": "gjsify run build:test:node && gjsify run test:node && gjsify run test:bun && gjsify run test:deno"
32
32
  },
33
33
  "repository": {
@@ -48,24 +48,24 @@
48
48
  },
49
49
  "homepage": "https://github.com/gjsify/unit#readme",
50
50
  "devDependencies": {
51
- "@girs/gjs": "5.0.0",
52
- "@girs/glib-2.0": "5.0.0",
53
- "@gjsify/cli": "^0.51.0",
51
+ "@girs/gjs": "5.4.0",
52
+ "@girs/glib-2.0": "5.4.0",
53
+ "@gjsify/cli": "^0.52.0",
54
54
  "@types/node": "^25.9.2",
55
55
  "typescript": "^6.0.3"
56
56
  },
57
57
  "dependencies": {
58
- "@gjsify/abort-controller": "^0.51.0",
59
- "@gjsify/assert": "^0.51.0",
60
- "@gjsify/buffer": "^0.51.0",
61
- "@gjsify/dom-elements": "^0.51.0",
62
- "@gjsify/dom-exception": "^0.51.0",
63
- "@gjsify/node-globals": "^0.51.0",
64
- "@gjsify/process": "^0.51.0",
65
- "@gjsify/runtime": "^0.51.0",
66
- "@gjsify/utils": "^0.51.0",
67
- "@gjsify/web-globals": "^0.51.0",
68
- "@gjsify/web-streams": "^0.51.0"
58
+ "@gjsify/abort-controller": "^0.52.0",
59
+ "@gjsify/assert": "^0.52.0",
60
+ "@gjsify/buffer": "^0.52.0",
61
+ "@gjsify/dom-elements": "^0.52.0",
62
+ "@gjsify/dom-exception": "^0.52.0",
63
+ "@gjsify/node-globals": "^0.52.0",
64
+ "@gjsify/process": "^0.52.0",
65
+ "@gjsify/runtime": "^0.52.0",
66
+ "@gjsify/utils": "^0.52.0",
67
+ "@gjsify/web-globals": "^0.52.0",
68
+ "@gjsify/web-streams": "^0.52.0"
69
69
  },
70
70
  "gjsify": {
71
71
  "tier": 1,