Files
youle_cocos/cocoscreator_projects/framework-tests/presentation/frame-renderer.test.ts
T

367 lines
11 KiB
TypeScript

import assert from 'node:assert/strict';
import test from 'node:test';
import { FrameRenderer } from '../../YouleNexus/assets/framework/presentation/frame-renderer.ts';
test('coalesces frames and invalidates callbacks even if scheduler delivers late', () => {
const callbacks: (() => void)[] = [];
let cancelled = 0;
let rendered = 0;
const renderer = new FrameRenderer({
request(cb) { callbacks.push(cb); return () => { cancelled++; }; },
}, () => { rendered++; }, e => { throw e; });
renderer.request(); renderer.request();
assert.equal(callbacks.length, 1);
renderer.flush();
assert.equal(rendered, 1);
callbacks[0]!();
assert.equal(rendered, 1);
renderer.request();
renderer.dispose(); renderer.dispose();
callbacks[1]!();
assert.equal(rendered, 1);
assert.equal(cancelled, 2);
});
test('releases the current frame before render can request the next frame', () => {
const callbacks: (() => void)[] = [];
let rendered = 0;
let renderer!: FrameRenderer;
renderer = new FrameRenderer({
request(callback) { callbacks.push(callback); return () => {}; },
}, () => {
rendered += 1;
renderer.request();
}, error => { throw error; });
renderer.request();
callbacks[0]!();
assert.equal(rendered, 1);
assert.equal(callbacks.length, 2);
callbacks[1]!();
assert.equal(rendered, 2);
assert.equal(callbacks.length, 3);
});
test('dispose during render prevents a reentrant frame request', () => {
const callbacks: (() => void)[] = [];
let rendered = 0;
let renderer!: FrameRenderer;
renderer = new FrameRenderer({
request(callback) { callbacks.push(callback); return () => {}; },
}, () => {
rendered += 1;
renderer.dispose();
renderer.request();
}, error => { throw error; });
renderer.request();
callbacks[0]!();
assert.equal(rendered, 1);
assert.equal(callbacks.length, 1);
renderer.flush();
assert.equal(rendered, 1);
});
test('a throwing dispose cancellation still makes every callback stale', () => {
const callbacks: (() => void)[] = [];
const cancelError = new Error('cancel failed');
let rendered = 0;
const renderer = new FrameRenderer({
request(callback) {
callbacks.push(callback);
return () => { throw cancelError; };
},
}, () => { rendered += 1; }, error => { throw error; });
renderer.request();
let thrown: unknown;
try {
renderer.dispose();
} catch (error) {
thrown = error;
}
assert.equal(thrown, cancelError);
callbacks[0]!();
assert.equal(rendered, 0);
renderer.request();
assert.equal(callbacks.length, 1);
});
test('a throwing flush cancellation invalidates the old frame before rethrowing', () => {
const callbacks: (() => void)[] = [];
const cancelError = new Error('cancel failed');
let rendered = 0;
const renderer = new FrameRenderer({
request(callback) {
callbacks.push(callback);
return () => { throw cancelError; };
},
}, () => { rendered += 1; }, error => { throw error; });
renderer.request();
let thrown: unknown;
try {
renderer.flush();
} catch (error) {
thrown = error;
}
assert.equal(thrown, cancelError);
callbacks[0]!();
assert.equal(rendered, 0);
renderer.request();
assert.equal(callbacks.length, 2);
});
test('flush does not render when cancellation disposes the renderer', () => {
let rendered = 0;
let renderer!: FrameRenderer;
renderer = new FrameRenderer({
request() { return () => { renderer.dispose(); }; },
}, () => { rendered += 1; }, error => { throw error; });
renderer.request();
renderer.flush();
assert.equal(rendered, 0);
});
test('a cancellation that reenters flush supersedes the outer render', () => {
let rendered = 0;
let renderer!: FrameRenderer;
renderer = new FrameRenderer({
request() { return () => { renderer.flush(); }; },
}, () => { rendered += 1; }, error => { throw error; });
renderer.request();
renderer.flush();
assert.equal(rendered, 1);
});
test('reports an asynchronous render error once and terminates future frames', () => {
const callbacks: (() => void)[] = [];
const renderError = new Error('render failed');
const faults: unknown[] = [];
let rendered = 0;
const renderer = new FrameRenderer({
request(callback) { callbacks.push(callback); return () => {}; },
}, () => {
rendered += 1;
throw renderError;
}, error => { faults.push(error); });
renderer.request();
let thrown: unknown;
try {
callbacks[0]!();
} catch (error) {
thrown = error;
}
assert.equal(thrown, undefined);
assert.deepEqual(faults, [renderError]);
callbacks[0]!();
renderer.request();
assert.equal(rendered, 1);
assert.deepEqual(faults, [renderError]);
assert.equal(callbacks.length, 1);
});
test('aggregates an asynchronous render error with reentrant cancellation failure', () => {
const callbacks: (() => void)[] = [];
const renderError = new Error('render failed');
const cancelError = new Error('cancel failed');
const faults: unknown[] = [];
let rendered = 0;
let renderer!: FrameRenderer;
renderer = new FrameRenderer({
request(callback) {
callbacks.push(callback);
return () => { throw cancelError; };
},
}, () => {
rendered += 1;
renderer.request();
throw renderError;
}, error => { faults.push(error); });
renderer.request();
let thrown: unknown;
try {
callbacks[0]!();
} catch (error) {
thrown = error;
}
assert.equal(thrown, undefined);
assert.equal(faults.length, 1);
const fault = faults[0];
assert.ok(fault instanceof Error);
assert.ok('errors' in fault);
assert.deepEqual(fault.errors, [renderError, cancelError]);
callbacks[1]!();
assert.equal(rendered, 1);
assert.equal(faults.length, 1);
});
test('flush rethrows a synchronous render error without reporting an async fault', () => {
const callbacks: (() => void)[] = [];
const renderError = new Error('render failed');
const faults: unknown[] = [];
let rendered = 0;
const renderer = new FrameRenderer({
request(callback) { callbacks.push(callback); return () => {}; },
}, () => {
rendered += 1;
throw renderError;
}, error => { faults.push(error); });
renderer.request();
let thrown: unknown;
try {
renderer.flush();
} catch (error) {
thrown = error;
}
assert.equal(thrown, renderError);
assert.deepEqual(faults, []);
callbacks[0]!();
assert.equal(rendered, 1);
});
test('does not swallow an onFault callback error', () => {
const callbacks: (() => void)[] = [];
const renderError = new Error('render failed');
const callbackError = new Error('fault callback failed');
const reported: unknown[] = [];
const renderer = new FrameRenderer({
request(callback) { callbacks.push(callback); return () => {}; },
}, () => { throw renderError; }, error => {
reported.push(error);
throw callbackError;
});
renderer.request();
let thrown: unknown;
try {
callbacks[0]!();
} catch (error) {
thrown = error;
}
assert.equal(thrown, callbackError);
assert.deepEqual(reported, [renderError]);
renderer.request();
assert.equal(callbacks.length, 1);
});
test('registration reentering flush cancels the superseded handle without blocking the next frame', () => {
const callbacks: (() => void)[] = [];
let cancelled = 0;
let rendered = 0;
const renderer = new FrameRenderer({ request(callback) {
callbacks.push(callback);
if (callbacks.length === 1) renderer.flush();
return () => { cancelled += 1; };
} }, () => { rendered += 1; }, error => { throw error; });
renderer.request();
assert.equal(rendered, 1);
assert.equal(cancelled, 1);
callbacks[0](); assert.equal(rendered, 1);
renderer.request();
assert.equal(callbacks.length, 2);
callbacks[1](); assert.equal(rendered, 2);
});
test('registration reentering dispose immediately cancels its returned handle once', () => {
const callbacks: (() => void)[] = [];
let cancelled = 0;
let rendered = 0;
const renderer = new FrameRenderer({ request(callback) {
callbacks.push(callback); renderer.dispose();
return () => { cancelled += 1; };
} }, () => { rendered += 1; }, error => { throw error; });
renderer.request(); renderer.dispose();
assert.equal(cancelled, 1);
callbacks[0]();
assert.equal(rendered, 0);
renderer.request(); assert.equal(callbacks.length, 1);
});
test('a reentrant request during registration coalesces with the acquiring frame', () => {
const callbacks: (() => void)[] = [];
let rendered = 0;
const renderer = new FrameRenderer({ request(callback) {
callbacks.push(callback);
if (callbacks.length === 1) renderer.request();
return () => {};
} }, () => { rendered += 1; }, error => { throw error; });
renderer.request();
assert.equal(callbacks.length, 1);
callbacks[0](); assert.equal(rendered, 1);
});
test('registration failure releases the reservation and invalidates its captured callback', () => {
const failure = new Error('request failed');
const callbacks: (() => void)[] = [];
let rendered = 0;
const renderer = new FrameRenderer({ request(callback) {
callbacks.push(callback);
if (callbacks.length === 1) throw failure;
return () => {};
} }, () => { rendered += 1; }, error => { throw error; });
assert.throws(() => renderer.request(), error => error === failure);
renderer.request(); assert.equal(callbacks.length, 2);
callbacks[0](); assert.equal(rendered, 0);
callbacks[1](); assert.equal(rendered, 1);
});
test('superseded registration preserves the successor scheduled by its synchronous flush', () => {
const callbacks: (() => void)[] = [];
const cancelled: number[] = [];
let rendered = 0;
const renderer = new FrameRenderer({ request(callback) {
const index = callbacks.length;
callbacks.push(callback);
if (index === 0) renderer.flush();
return () => { cancelled.push(index); };
} }, () => {
rendered += 1;
if (rendered === 1) renderer.request();
}, error => { throw error; });
renderer.request();
assert.deepEqual(cancelled, [0]);
assert.equal(callbacks.length, 2);
callbacks[0](); assert.equal(rendered, 1);
renderer.flush();
assert.deepEqual(cancelled, [0, 1]);
assert.equal(rendered, 2);
callbacks[1](); assert.equal(rendered, 2);
});
test('superseded handle cancellation errors propagate without reviving stale callbacks', () => {
const failure = new Error('late handle cancellation');
const callbacks: (() => void)[] = [];
let rendered = 0;
const renderer = new FrameRenderer({ request(callback) {
callbacks.push(callback); renderer.dispose();
return () => { throw failure; };
} }, () => { rendered += 1; }, error => { throw error; });
assert.throws(() => renderer.request(), error => error === failure);
callbacks[0](); renderer.dispose(); renderer.request();
assert.equal(rendered, 0);
assert.equal(callbacks.length, 1);
});