Files
Æmotion StudioandClaude Opus 5 1bd794c353 feat(web): show the shader preview on the Direct node
The preview now attaches to ShaderNoiseKSamplerDirect as well. That node
already exposes every shader control as a real input, so instead of adding a
second, unconnected copy of each widget, the preview mirrors the node's own
inputs into the properties its WebGL uniforms read, and follows them as they
change.

Its four preview-only widgets (Show Shader, Show Tooltips, Animation Speed,
Pixel Resolution) are marked serialize: false there. Saved workflows map widget
values by position, so serializing them would shift every stored input when an
older workflow loads.

The deprecated display node keeps its own widgets and behaviour unchanged.

Also adds vitest coverage for the sampling-mode migration helpers.
Typecheck clean, 91 web tests pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 15:52:03 -07:00

62 lines
2.2 KiB
TypeScript

/**
* Workflows saved before 2.0 must keep sampling the way they were tuned.
*
* 2.0 changes what a seed produces (stages became segments of one run, denoise
* and custom sigmas take effect, blended noise keeps its distribution), so a
* node loaded without the snk_version marker is switched to "legacy".
*/
import { describe, it, expect } from 'vitest';
import { needsLegacySampling, setSamplingMode } from '../src/sampling_mode_migration.js';
interface TestWidget {
name?: string;
value?: unknown;
}
function nodeWithWidgets(widgets: TestWidget[]): any {
return { properties: {}, widgets };
}
describe('sampling mode migration', () => {
describe('needsLegacySampling', () => {
it('treats a node saved before 2.0 as legacy', () => {
expect(needsLegacySampling({ properties: { 'Node name for S&R': 'ShaderNoiseKSamplerDirect' } })).toBe(true);
});
it('treats missing or empty serialized data as legacy', () => {
expect(needsLegacySampling(undefined)).toBe(true);
expect(needsLegacySampling(null)).toBe(true);
expect(needsLegacySampling({})).toBe(true);
expect(needsLegacySampling({ properties: {} })).toBe(true);
});
it('leaves a node saved by 2.0 alone', () => {
expect(needsLegacySampling({ properties: { snk_version: 2 } })).toBe(false);
});
});
describe('setSamplingMode', () => {
it('sets the sampling_mode widget', () => {
const node = nodeWithWidgets([
{ name: 'seed', value: 8888 },
{ name: 'sampling_mode', value: 'standard' },
]);
setSamplingMode(node, 'legacy');
expect(node.widgets[1].value).toBe('legacy');
expect(node.widgets[0].value).toBe(8888);
});
it('does nothing when the widget is absent', () => {
const node = nodeWithWidgets([{ name: 'seed', value: 8888 }]);
expect(() => setSamplingMode(node, 'legacy')).not.toThrow();
});
it('does nothing when the node has no widgets', () => {
expect(() => setSamplingMode({ properties: {} } as any, 'legacy')).not.toThrow();
});
});
});