File size: 4,584 Bytes
c60860a
 
 
 
 
 
 
 
35c1912
c60860a
 
 
35c1912
 
 
 
 
 
c60860a
 
 
 
35c1912
 
 
 
 
 
c60860a
 
 
 
 
35c1912
 
48fe637
c60860a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35c1912
c60860a
 
 
 
 
 
 
 
3bb263a
9fd0b69
3bb263a
 
 
 
 
 
 
 
 
 
 
 
276ecc1
 
 
 
 
 
 
 
 
 
 
 
 
4b3487a
35c1912
9af5f8b
 
 
 
 
 
 
35c1912
9af5f8b
 
 
 
35c1912
 
 
 
 
 
 
 
 
 
 
4b3487a
 
 
 
 
 
 
 
 
 
 
 
c60860a
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
import { describe, it, expect, afterEach } from 'vitest';
import {
  GLOBAL_SPATIAL_DEFAULTS,
  SPATIAL_DEFAULT_OVERRIDES,
  resolveSpatialDefaults,
} from '../src/ui/spatialSliderDefaults.js';

describe('resolveSpatialDefaults', () => {
  /** @type {Array<{ key: string, previous: object|undefined }>} */
  const savedKeys = [];

  afterEach(() => {
    for (const { key, previous } of savedKeys.splice(0)) {
      if (previous === undefined) {
        delete SPATIAL_DEFAULT_OVERRIDES[key];
      } else {
        SPATIAL_DEFAULT_OVERRIDES[key] = previous;
      }
    }
  });

  function setOverride(key, partial) {
    savedKeys.push({
      key,
      previous: SPATIAL_DEFAULT_OVERRIDES[key]
        ? { ...SPATIAL_DEFAULT_OVERRIDES[key] }
        : undefined,
    });
    SPATIAL_DEFAULT_OVERRIDES[key] = partial;
  }

  it('returns global mid defaults when no overrides exist', () => {
    const d = resolveSpatialDefaults({
      workspaceMode: 'ARITHMETIC',
      viewMode: 'NAVIGATION',
      renderMode: 'RIBBONS',
    });
    expect(d.threadSpacing).toBe(GLOBAL_SPATIAL_DEFAULTS.threadSpacing);
    expect(d.threadVectorDistance).toBe(GLOBAL_SPATIAL_DEFAULTS.threadVectorDistance);
    expect(d.threadAmplitudeY).toBe(GLOBAL_SPATIAL_DEFAULTS.threadAmplitudeY);
    expect(d.threadWidth).toBe(GLOBAL_SPATIAL_DEFAULTS.threadWidth);
    expect(d.threadThickness).toBe(GLOBAL_SPATIAL_DEFAULTS.threadThickness);
    expect(d.threadSpacingY).toBe(d.threadVectorDistance);
  });

  it('applies workspace → view → render layers (more specific wins)', () => {
    setOverride('ARITHMETIC', { threadSpacing: 0.5 });
    setOverride('ARITHMETIC|NAVIGATION', { threadAmplitudeY: 9.0 });
    setOverride('ARITHMETIC|NAVIGATION|POINTS', { threadThickness: 0.12 });

    const d = resolveSpatialDefaults({
      workspaceMode: 'ARITHMETIC',
      viewMode: 'NAVIGATION',
      renderMode: 'POINTS',
    });
    expect(d.threadSpacing).toBe(0.5);
    expect(d.threadAmplitudeY).toBe(9.0);
    expect(d.threadThickness).toBe(0.12);
    expect(d.threadWidth).toBe(GLOBAL_SPATIAL_DEFAULTS.threadWidth);
  });

  it('does not leak overrides from a different context', () => {
    setOverride('COMPARE|ANALYSIS|POINTS', { threadWidth: 0.25 });

    const d = resolveSpatialDefaults({
      workspaceMode: 'ARITHMETIC',
      viewMode: 'NAVIGATION',
      renderMode: 'POINTS',
    });
    expect(d.threadWidth).toBe(GLOBAL_SPATIAL_DEFAULTS.threadWidth);
  });

  it('applies captured ARITHMETIC|ANALYSIS|POINTS preset (Amplitude 40)', () => {
    // Production override lives in SPATIAL_DEFAULT_OVERRIDES — assert the seam.
    const d = resolveSpatialDefaults({
      workspaceMode: 'ARITHMETIC',
      viewMode: 'ANALYSIS',
      renderMode: 'POINTS',
    });
    expect(d.threadSpacing).toBe(0.4);
    expect(d.threadVectorDistance).toBe(10.0);
    expect(d.threadAmplitudeY).toBe(40.0);
    expect(d.threadWidth).toBe(0.2);
    expect(d.threadThickness).toBe(0.05);
  });

  it('applies captured COMPARE|NAVIGATION|POINTS preset', () => {
    const d = resolveSpatialDefaults({
      workspaceMode: 'COMPARE',
      viewMode: 'NAVIGATION',
      renderMode: 'POINTS',
    });
    expect(d.threadSpacing).toBe(0.7);
    expect(d.threadVectorDistance).toBe(10.0);
    expect(d.threadAmplitudeY).toBe(4.9);
    expect(d.threadWidth).toBe(0.1);
    expect(d.threadThickness).toBe(0.01);
  });

  it('applies COMPARE|ANALYSIS|POINTS preset with Amplitude 16', () => {
    const d = resolveSpatialDefaults({
      workspaceMode: 'COMPARE',
      viewMode: 'ANALYSIS',
      renderMode: 'POINTS',
    });
    expect(d.threadSpacing).toBe(1.45);
    expect(d.threadVectorDistance).toBe(1.0);
    expect(d.threadAmplitudeY).toBe(16.0);
    expect(d.threadWidth).toBe(0.2);
    expect(d.threadThickness).toBe(0.01);
  });

  it('applies COMPARE|ANALYSIS|RIBBONS twin with Amplitude 16', () => {
    const d = resolveSpatialDefaults({
      workspaceMode: 'COMPARE',
      viewMode: 'ANALYSIS',
      renderMode: 'RIBBONS',
    });
    expect(d.threadAmplitudeY).toBe(16.0);
    expect(d.threadSpacing).toBe(1.45);
    expect(d.threadVectorDistance).toBe(1.0);
  });

  it('applies captured COMPARE|NAVIGATION|RIBBONS preset', () => {
    const d = resolveSpatialDefaults({
      workspaceMode: 'COMPARE',
      viewMode: 'NAVIGATION',
      renderMode: 'RIBBONS',
    });
    expect(d.threadSpacing).toBe(1.55);
    expect(d.threadVectorDistance).toBe(10.0);
    expect(d.threadAmplitudeY).toBe(7.0);
    expect(d.threadWidth).toBe(0.057);
    expect(d.threadThickness).toBe(0.05);
  });
});