@spearwolf/twopoint5d
Version:
Create 2.5D realtime graphics and pixelart with WebGL and three.js
145 lines • 5.66 kB
JavaScript
import { describe, expect, it, vi } from 'vitest';
import { RootRenderPipeline } from './RootRenderPipeline.js';
import { StageRenderer } from './StageRenderer.js';
function makeNode(label) {
const node = {
isFakeNode: true,
label,
parts: [label],
add(other) {
const combined = makeNode(`${this.label}+${other.label}`);
combined.parts = [...this.parts, ...other.parts];
return combined;
},
};
return node;
}
function fakeStage(name, passNode) {
return {
name,
resize: vi.fn(),
updateFrame: vi.fn(),
renderTo: vi.fn(),
asPassNode: vi.fn(() => passNode),
};
}
describe('RootRenderPipeline.buildOutputNode', () => {
it('returns the single pass as-is when there is exactly one stage', () => {
const p = makeNode('a');
const out = RootRenderPipeline.buildOutputNode([p]);
expect(out).toBe(p);
});
it('combines two passes with .add()', () => {
const a = makeNode('a');
const b = makeNode('b');
const out = RootRenderPipeline.buildOutputNode([a, b]);
expect(out.label).toBe('a+b');
expect(out.parts).toEqual(['a', 'b']);
});
it('combines three passes left-to-right: (a.add(b)).add(c)', () => {
const a = makeNode('a');
const b = makeNode('b');
const c = makeNode('c');
const out = RootRenderPipeline.buildOutputNode([a, b, c]);
expect(out.label).toBe('a+b+c');
expect(out.parts).toEqual(['a', 'b', 'c']);
});
it('includes ALL passes (5 stages → 5 parts in result)', () => {
const nodes = ['s0', 's1', 's2', 's3', 's4'].map(makeNode);
const out = RootRenderPipeline.buildOutputNode(nodes);
expect(out.parts).toEqual(['s0', 's1', 's2', 's3', 's4']);
expect(out.parts.length).toBe(5);
});
it('throws when called with an empty array', () => {
expect(() => RootRenderPipeline.buildOutputNode([])).toThrowError(/no passes/);
});
});
describe('StageRenderer + RootRenderPipeline (integration)', () => {
function createRendererMock() {
return {
autoClear: true,
setClearColor: vi.fn(),
getClearColor: vi.fn(),
setClearAlpha: vi.fn(),
getClearAlpha: vi.fn(() => 1),
clear: vi.fn(),
render: vi.fn(),
setRenderTarget: vi.fn(),
getRenderTarget: vi.fn(() => null),
getPixelRatio: vi.fn(() => 1),
};
}
function fakeRootPipeline() {
const fp = {
outputNode: undefined,
needsUpdate: false,
render: vi.fn(),
dispose: vi.fn(),
};
Object.setPrototypeOf(fp, RootRenderPipeline.prototype);
return fp;
}
it('uses RootRenderPipeline.buildOutputNode as the default composer (no explicit buildOutputNode)', () => {
const renderer = createRendererMock();
const sr = new StageRenderer();
sr.resize(100, 100);
const passes = [makeNode('s0'), makeNode('s1'), makeNode('s2')];
sr.add(fakeStage('s0', passes[0]))
.add(fakeStage('s1', passes[1]))
.add(fakeStage('s2', passes[2]));
sr.pipeline = fakeRootPipeline();
expect(sr.buildOutputNode).toBeUndefined();
sr.renderTo(renderer);
const out = sr.pipeline.outputNode;
expect(out).toBeDefined();
expect(out.parts).toEqual(['s0', 's1', 's2']);
expect(sr.pipeline.render).toHaveBeenCalledTimes(1);
});
it('a user-set buildOutputNode takes precedence over the RootRenderPipeline default', () => {
const renderer = createRendererMock();
const sr = new StageRenderer();
sr.resize(100, 100);
const passes = [makeNode('a'), makeNode('b')];
sr.add(fakeStage('a', passes[0])).add(fakeStage('b', passes[1]));
sr.pipeline = fakeRootPipeline();
const userBuilder = vi.fn((nodes) => nodes[0]);
sr.buildOutputNode = userBuilder;
sr.renderTo(renderer);
expect(userBuilder).toHaveBeenCalledTimes(1);
expect(userBuilder.mock.calls[0][0]).toEqual(passes);
expect(sr.pipeline.outputNode).toBe(passes[0]);
});
it('honors renderOrder when composing passes additively', () => {
const renderer = createRendererMock();
const sr = new StageRenderer();
sr.resize(100, 100);
const passes = {
bg: makeNode('bg'),
world: makeNode('world'),
ui: makeNode('ui'),
};
sr.add(fakeStage('bg', passes.bg))
.add(fakeStage('world', passes.world))
.add(fakeStage('ui', passes.ui));
sr.renderOrder = 'ui,world,bg';
sr.pipeline = fakeRootPipeline();
sr.renderTo(renderer);
const out = sr.pipeline.outputNode;
expect(out.parts).toEqual(['ui', 'world', 'bg']);
});
it('rebuilds the outputNode when a new stage is added (still composes all passes including the new one)', () => {
const renderer = createRendererMock();
const sr = new StageRenderer();
sr.resize(100, 100);
const p1 = makeNode('s0');
const p2 = makeNode('s1');
sr.add(fakeStage('s0', p1));
sr.pipeline = fakeRootPipeline();
sr.renderTo(renderer);
expect(sr.pipeline.outputNode.parts).toEqual(['s0']);
sr.add(fakeStage('s1', p2));
sr.renderTo(renderer);
expect(sr.pipeline.outputNode.parts).toEqual(['s0', 's1']);
});
});
//# sourceMappingURL=RootRenderPipeline.spec.js.map