aniview 1.0.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.
Files changed (64) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/LICENSE +21 -0
  3. package/README.md +130 -0
  4. package/dist/Aniview.d.ts +63 -0
  5. package/dist/Aniview.d.ts.map +1 -0
  6. package/dist/Aniview.js +831 -0
  7. package/dist/Aniview.js.map +1 -0
  8. package/dist/AniviewPanel.d.ts +33 -0
  9. package/dist/AniviewPanel.d.ts.map +1 -0
  10. package/dist/AniviewPanel.js +66 -0
  11. package/dist/AniviewPanel.js.map +1 -0
  12. package/dist/GestureStressTest.d.ts +3 -0
  13. package/dist/GestureStressTest.d.ts.map +1 -0
  14. package/dist/GestureStressTest.js +125 -0
  15. package/dist/GestureStressTest.js.map +1 -0
  16. package/dist/aniviewConfig.d.ts +175 -0
  17. package/dist/aniviewConfig.d.ts.map +1 -0
  18. package/dist/aniviewConfig.js +568 -0
  19. package/dist/aniviewConfig.js.map +1 -0
  20. package/dist/aniviewProvider.d.ts +93 -0
  21. package/dist/aniviewProvider.d.ts.map +1 -0
  22. package/dist/aniviewProvider.js +229 -0
  23. package/dist/aniviewProvider.js.map +1 -0
  24. package/dist/core/AniviewLock.d.ts +16 -0
  25. package/dist/core/AniviewLock.d.ts.map +1 -0
  26. package/dist/core/AniviewLock.js +18 -0
  27. package/dist/core/AniviewLock.js.map +1 -0
  28. package/dist/core/AniviewMath.d.ts +41 -0
  29. package/dist/core/AniviewMath.d.ts.map +1 -0
  30. package/dist/core/AniviewMath.js +69 -0
  31. package/dist/core/AniviewMath.js.map +1 -0
  32. package/dist/index.d.ts +7 -0
  33. package/dist/index.d.ts.map +1 -0
  34. package/dist/index.js +7 -0
  35. package/dist/index.js.map +1 -0
  36. package/dist/useAniview.d.ts +39 -0
  37. package/dist/useAniview.d.ts.map +1 -0
  38. package/dist/useAniview.js +32 -0
  39. package/dist/useAniview.js.map +1 -0
  40. package/dist/useAniviewContext.d.ts +156 -0
  41. package/dist/useAniviewContext.d.ts.map +1 -0
  42. package/dist/useAniviewContext.js +3 -0
  43. package/dist/useAniviewContext.js.map +1 -0
  44. package/dist/useAniviewLock.d.ts +20 -0
  45. package/dist/useAniviewLock.d.ts.map +1 -0
  46. package/dist/useAniviewLock.js +32 -0
  47. package/dist/useAniviewLock.js.map +1 -0
  48. package/package.json +60 -0
  49. package/src/Aniview.tsx +868 -0
  50. package/src/AniviewPanel.tsx +141 -0
  51. package/src/GestureStressTest.tsx +144 -0
  52. package/src/__tests__/AniviewLock.test.ts +58 -0
  53. package/src/__tests__/AniviewMath.test.ts +211 -0
  54. package/src/__tests__/AniviewSnapshot.test.tsx +85 -0
  55. package/src/__tests__/__snapshots__/AniviewSnapshot.test.tsx.snap +7 -0
  56. package/src/__tests__/aniviewConfig.test.ts +70 -0
  57. package/src/aniviewConfig.tsx +688 -0
  58. package/src/aniviewProvider.tsx +307 -0
  59. package/src/core/AniviewLock.ts +23 -0
  60. package/src/core/AniviewMath.ts +107 -0
  61. package/src/index.ts +6 -0
  62. package/src/useAniview.tsx +75 -0
  63. package/src/useAniviewContext.tsx +170 -0
  64. package/src/useAniviewLock.tsx +37 -0
@@ -0,0 +1,141 @@
1
+ import * as React from 'react';
2
+ import { View, ViewStyle } from 'react-native';
3
+ import Aniview from './Aniview';
4
+ import { AniviewProps, AniviewFrame } from './useAniviewContext';
5
+
6
+ const DEFAULT_LAYOUT = {
7
+ CONTENT_BG: '#FFFFFF',
8
+ FRAME_INK: '#2D2D2D',
9
+ FRAME_WOOD: '#C19A6B',
10
+ BORR: 8,
11
+ FRAME_WIDTH: 2,
12
+ OUTLINE_WIDTH: 1,
13
+ GUTTER_H: 16
14
+ };
15
+
16
+ /**
17
+ * PANEL FRAME (Visual Only)
18
+ */
19
+ interface PanelFrameProps {
20
+ width?: number | string;
21
+ height?: number | string;
22
+ backgroundColor?: string;
23
+ children?: React.ReactNode;
24
+ style?: ViewStyle;
25
+ clipContent?: boolean;
26
+ }
27
+
28
+ export const PanelFrame = ({
29
+ width,
30
+ height,
31
+ backgroundColor = DEFAULT_LAYOUT.CONTENT_BG,
32
+ children,
33
+ style,
34
+ clipContent = true
35
+ }: PanelFrameProps) => {
36
+
37
+ const frameWidth = DEFAULT_LAYOUT.FRAME_WIDTH;
38
+ const inkWidth = DEFAULT_LAYOUT.OUTLINE_WIDTH;
39
+
40
+ return (
41
+ <View
42
+ style={{
43
+ width: width as any,
44
+ height: height as any,
45
+ borderWidth: inkWidth,
46
+ borderColor: DEFAULT_LAYOUT.FRAME_INK,
47
+ borderRadius: DEFAULT_LAYOUT.BORR,
48
+ backgroundColor: 'transparent',
49
+ overflow: clipContent ? 'hidden' : 'visible',
50
+ ...style
51
+ }}
52
+ >
53
+ <View
54
+ style={{
55
+ flex: 1,
56
+ borderWidth: frameWidth,
57
+ borderColor: DEFAULT_LAYOUT.FRAME_WOOD,
58
+ backgroundColor: 'transparent',
59
+ overflow: clipContent ? 'hidden' : 'visible'
60
+ }}
61
+ >
62
+ <View
63
+ style={{
64
+ flex: 1,
65
+ backgroundColor: 'transparent',
66
+ borderWidth: inkWidth,
67
+ borderColor: DEFAULT_LAYOUT.FRAME_INK,
68
+ borderRadius: Math.max(0, DEFAULT_LAYOUT.BORR - frameWidth - inkWidth),
69
+ overflow: clipContent ? 'hidden' : 'visible'
70
+ }}
71
+ >
72
+ {children}
73
+ </View>
74
+ </View>
75
+ </View>
76
+ );
77
+ };
78
+
79
+ /**
80
+ * ANIVIEW PANEL (Animated component)
81
+ */
82
+ export interface AniviewPanelProps extends AniviewProps {
83
+ pageId: number | string;
84
+ width: number;
85
+ height: number;
86
+ marginTop?: number;
87
+ marginLeft?: number;
88
+ backgroundColor?: string;
89
+ frames?: Record<string, AniviewFrame> | AniviewFrame[];
90
+ style?: ViewStyle;
91
+ children?: React.ReactNode;
92
+ clipContent?: boolean;
93
+ }
94
+
95
+ export const AniviewPanel = (props: AniviewPanelProps) => {
96
+ const {
97
+ pageId,
98
+ width,
99
+ height,
100
+ marginTop = 0,
101
+ marginLeft = 0,
102
+ backgroundColor,
103
+ frames,
104
+ children,
105
+ style,
106
+ clipContent = true,
107
+ ...rest
108
+ } = props;
109
+
110
+ const hasExternalBg = !!(style as any)?.backgroundColor ||
111
+ (frames && (Array.isArray(frames)
112
+ ? frames.some(f => (f.style as any)?.backgroundColor)
113
+ : Object.values(frames).some(f => (f.style as any)?.backgroundColor)));
114
+
115
+ const innerBg = hasExternalBg ? 'transparent' : (backgroundColor || 'transparent');
116
+
117
+ return (
118
+ <Aniview
119
+ pageId={pageId}
120
+ frames={frames as any}
121
+ style={{
122
+ width,
123
+ height,
124
+ marginTop,
125
+ marginLeft,
126
+ backgroundColor: backgroundColor || (style as any)?.backgroundColor,
127
+ ...(style as any)
128
+ }}
129
+ {...rest}
130
+ >
131
+ <PanelFrame
132
+ width="100%"
133
+ height="100%"
134
+ backgroundColor={innerBg}
135
+ clipContent={clipContent}
136
+ >
137
+ {children as React.ReactNode}
138
+ </PanelFrame>
139
+ </Aniview>
140
+ );
141
+ };
@@ -0,0 +1,144 @@
1
+ import * as React from 'react';
2
+ import { StyleSheet, View, Text } from 'react-native';
3
+ import {
4
+ GestureDetector,
5
+ Gesture,
6
+ } from 'react-native-gesture-handler';
7
+ import Animated, {
8
+ useAnimatedStyle,
9
+ useSharedValue,
10
+ withSpring,
11
+ runOnJS
12
+ } from 'react-native-reanimated';
13
+
14
+ import { useAniview } from './useAniview';
15
+
16
+ export default function GestureStressTest() {
17
+ const { lock } = useAniview();
18
+ const translateX = useSharedValue(0);
19
+ const translateY = useSharedValue(0);
20
+ const scale = useSharedValue(1);
21
+ const opacity = useSharedValue(1);
22
+
23
+ // LOGGING UTILITY
24
+ const logGesture = (name: string, state: string, x?: number, y?: number) => {
25
+ console.log(`[GESTURE: ${name}] State: ${state} | X: ${Math.round(x || 0)} Y: ${Math.round(y || 0)}`);
26
+ };
27
+
28
+ const panGesture = Gesture.Pan()
29
+ .onStart((event: any) => {
30
+ runOnJS(lock)(1); // 1 = Horizontal Lock
31
+ runOnJS(logGesture)('PAN', 'START', event.x, event.y);
32
+ opacity.value = 0.8;
33
+ })
34
+ .onUpdate((event: any) => {
35
+ translateX.value = event.translationX;
36
+ translateY.value = event.translationY;
37
+ })
38
+ .onEnd((event: any) => {
39
+ runOnJS(lock)(0); // Unlock
40
+ runOnJS(logGesture)('PAN', 'END', event.x, event.y);
41
+ translateX.value = withSpring(0);
42
+ translateY.value = withSpring(0);
43
+ opacity.value = withSpring(1);
44
+ });
45
+
46
+ const tapGesture = Gesture.Tap()
47
+ .numberOfTaps(2)
48
+ .onEnd((event: any) => {
49
+ runOnJS(logGesture)('DOUBLE_TAP', 'END', event.x, event.y);
50
+ scale.value = withSpring(1.5, {}, () => {
51
+ scale.value = withSpring(1);
52
+ });
53
+ });
54
+
55
+ const longPressGesture = Gesture.LongPress()
56
+ .onEnd((event: any, success: boolean) => {
57
+ if (success) {
58
+ runOnJS(logGesture)('LONG_PRESS', 'SUCCESS', event.x, event.y);
59
+ opacity.value = withSpring(0.3, {}, () => {
60
+ opacity.value = withSpring(1);
61
+ });
62
+ }
63
+ });
64
+
65
+ const composed = Gesture.Simultaneous(panGesture, tapGesture, longPressGesture);
66
+
67
+ const animatedStyle = useAnimatedStyle(() => ({
68
+ transform: [
69
+ { translateX: translateX.value },
70
+ { translateY: translateY.value },
71
+ { scale: scale.value }
72
+ ],
73
+ opacity: opacity.value
74
+ }));
75
+
76
+ return (
77
+ <View style={styles.container}>
78
+ <Text style={styles.title}>GESTURE ORCHESTRATION 2.0</Text>
79
+
80
+ <GestureDetector gesture={composed}>
81
+ <Animated.View style={[styles.box, animatedStyle]}>
82
+ <Text style={styles.boxText}>PAN / DOUBLE-TAP / LONG-PRESS</Text>
83
+ <Text style={styles.hint}>Testing Parallel Dispatch (Simultaneous)</Text>
84
+ </Animated.View>
85
+ </GestureDetector>
86
+
87
+ <View style={styles.logIndicator}>
88
+ <Text style={styles.logText}>Check Console for Orchestration Traces</Text>
89
+ </View>
90
+ </View>
91
+ );
92
+ }
93
+
94
+ const styles = StyleSheet.create({
95
+ container: {
96
+ padding: 20,
97
+ alignItems: 'center',
98
+ justifyContent: 'center',
99
+ },
100
+ title: {
101
+ color: '#fff',
102
+ fontSize: 14,
103
+ fontWeight: 'bold',
104
+ marginBottom: 20,
105
+ opacity: 0.6,
106
+ },
107
+ box: {
108
+ width: 300,
109
+ height: 300,
110
+ backgroundColor: '#1c1c1e',
111
+ borderRadius: 30,
112
+ borderWidth: 1,
113
+ borderColor: '#38383a',
114
+ justifyContent: 'center',
115
+ alignItems: 'center',
116
+ padding: 20,
117
+ elevation: 10,
118
+ shadowColor: '#000',
119
+ shadowOffset: { width: 0, height: 10 },
120
+ shadowOpacity: 0.5,
121
+ shadowRadius: 15,
122
+ },
123
+ boxText: {
124
+ color: '#0a84ff',
125
+ fontSize: 18,
126
+ fontWeight: '800',
127
+ textAlign: 'center',
128
+ },
129
+ hint: {
130
+ color: '#8e8e93',
131
+ marginTop: 10,
132
+ fontSize: 12,
133
+ },
134
+ logIndicator: {
135
+ marginTop: 30,
136
+ padding: 10,
137
+ backgroundColor: 'rgba(255,255,255,0.05)',
138
+ borderRadius: 10,
139
+ },
140
+ logText: {
141
+ color: '#636366',
142
+ fontSize: 11,
143
+ }
144
+ });
@@ -0,0 +1,58 @@
1
+ /**
2
+ * AniviewLock — Bitmask generation unit tests
3
+ *
4
+ * Enable/disable: ANIVIEW_TEST_LOCK=1 npm test
5
+ * Or: npx jest --testPathPattern=AniviewLock
6
+ */
7
+
8
+ import { AniviewLock } from '../core/AniviewLock';
9
+
10
+ describe('AniviewLock.mask', () => {
11
+ test('empty object returns 0 (no locks)', () => {
12
+ expect(AniviewLock.mask({})).toBe(0);
13
+ });
14
+
15
+ test('left = 1', () => {
16
+ expect(AniviewLock.mask({ left: true })).toBe(1);
17
+ });
18
+
19
+ test('right = 2', () => {
20
+ expect(AniviewLock.mask({ right: true })).toBe(2);
21
+ });
22
+
23
+ test('up = 4', () => {
24
+ expect(AniviewLock.mask({ up: true })).toBe(4);
25
+ });
26
+
27
+ test('down = 8', () => {
28
+ expect(AniviewLock.mask({ down: true })).toBe(8);
29
+ });
30
+
31
+ test('left + right = 3', () => {
32
+ expect(AniviewLock.mask({ left: true, right: true })).toBe(3);
33
+ });
34
+
35
+ test('up + down = 12', () => {
36
+ expect(AniviewLock.mask({ up: true, down: true })).toBe(12);
37
+ });
38
+
39
+ test('all directions = 15', () => {
40
+ expect(AniviewLock.mask({ left: true, right: true, up: true, down: true })).toBe(15);
41
+ });
42
+
43
+ test('false values are ignored', () => {
44
+ expect(AniviewLock.mask({ left: false, right: true })).toBe(2);
45
+ });
46
+
47
+ test('undefined values are ignored', () => {
48
+ expect(AniviewLock.mask({ left: undefined, up: true })).toBe(4);
49
+ });
50
+
51
+ test('horizontal only (left + right) blocks horizontal axis', () => {
52
+ const mask = AniviewLock.mask({ left: true, right: true });
53
+ expect(mask & 1).toBeTruthy(); // left blocked
54
+ expect(mask & 2).toBeTruthy(); // right blocked
55
+ expect(mask & 4).toBeFalsy(); // up open
56
+ expect(mask & 8).toBeFalsy(); // down open
57
+ });
58
+ });
@@ -0,0 +1,211 @@
1
+ /**
2
+ * AniviewMath — Pure function unit tests
3
+ *
4
+ * Enable/disable: ANIVIEW_TEST_MATH=1 npm test
5
+ * Or: npx jest --testPathPattern=AniviewMath
6
+ */
7
+ import {
8
+ pageIdToMatrixPos,
9
+ calculateAxisOffset,
10
+ getPageOffset,
11
+ getWorldBounds,
12
+ } from '../core/AniviewMath';
13
+
14
+ // ──────────────────────────────────────────────
15
+ // pageIdToMatrixPos
16
+ // ──────────────────────────────────────────────
17
+ describe('pageIdToMatrixPos', () => {
18
+ const ROW_3 = [[1, 1, 1]]; // 1 row, 3 cols
19
+ const GRID_2x2 = [[1, 1], [1, 1]]; // 2 rows, 2 cols
20
+ const SINGLE = [[1]]; // 1 page
21
+
22
+ test('maps ID 0 to (0,0) in a 1×3 layout', () => {
23
+ expect(pageIdToMatrixPos(0, ROW_3)).toEqual({ r: 0, c: 0 });
24
+ });
25
+
26
+ test('maps ID 2 to (0,2) in a 1×3 layout', () => {
27
+ expect(pageIdToMatrixPos(2, ROW_3)).toEqual({ r: 0, c: 2 });
28
+ });
29
+
30
+ test('maps ID 3 to (1,0) in a 2×2 layout', () => {
31
+ expect(pageIdToMatrixPos(3, GRID_2x2)).toEqual({ r: 1, c: 1 });
32
+ });
33
+
34
+ test('maps ID 2 to (1,0) in a 2×2 layout', () => {
35
+ expect(pageIdToMatrixPos(2, GRID_2x2)).toEqual({ r: 1, c: 0 });
36
+ });
37
+
38
+ test('handles single-page layout', () => {
39
+ expect(pageIdToMatrixPos(0, SINGLE)).toEqual({ r: 0, c: 0 });
40
+ });
41
+
42
+ test('returns sentinel for negative ID', () => {
43
+ expect(pageIdToMatrixPos(-1, ROW_3)).toEqual({ r: 9999, c: 9999 });
44
+ });
45
+
46
+ test('returns sentinel for out-of-bounds ID', () => {
47
+ expect(pageIdToMatrixPos(5, ROW_3)).toEqual({ r: 9999, c: 9999 });
48
+ });
49
+
50
+ test('returns sentinel for ID exactly past max', () => {
51
+ // 1×3 layout → max valid ID is 2
52
+ expect(pageIdToMatrixPos(3, ROW_3)).toEqual({ r: 9999, c: 9999 });
53
+ });
54
+ });
55
+
56
+
57
+ // ──────────────────────────────────────────────
58
+ // calculateAxisOffset
59
+ // ──────────────────────────────────────────────
60
+ describe('calculateAxisOffset', () => {
61
+ const W = 400; // page width/height
62
+
63
+ test('returns 0 when from === to', () => {
64
+ expect(calculateAxisOffset(0, 0, W, [])).toBe(0);
65
+ });
66
+
67
+ test('returns screenWidth for 1 step with no overlap', () => {
68
+ expect(calculateAxisOffset(0, 1, W, [0])).toBe(W);
69
+ });
70
+
71
+ test('returns half screenWidth when overlap is 0.5', () => {
72
+ expect(calculateAxisOffset(0, 1, W, [0.5])).toBe(W * 0.5);
73
+ });
74
+
75
+ test('returns negative offset for reverse direction', () => {
76
+ expect(calculateAxisOffset(1, 0, W, [0])).toBe(-W);
77
+ });
78
+
79
+ test('accumulates correctly over 3 steps with no overlap', () => {
80
+ expect(calculateAxisOffset(0, 3, W, [0, 0, 0])).toBe(W * 3);
81
+ });
82
+
83
+ test('accumulates with mixed overlaps (layout)', () => {
84
+ // uses cols: [0.5, 0.4] for 3 columns
85
+ // Step 0→1: W * (1 - 0.5) = 200
86
+ // Step 1→2: W * (1 - 0.4) = 240
87
+ // Total: 440
88
+ expect(calculateAxisOffset(0, 2, W, [0.5, 0.4])).toBe(440);
89
+ });
90
+
91
+ test('handles missing overlaps (treated as 0)', () => {
92
+ // Only 1 overlap value but crossing 2 gaps
93
+ // Step 0→1: W * (1 - 0.3) = 280
94
+ // Step 1→2: W * (1 - 0) = 400 (missing overlap → 0)
95
+ expect(calculateAxisOffset(0, 2, W, [0.3])).toBe(680);
96
+ });
97
+
98
+ test('single step with full overlap (1.0) returns 0', () => {
99
+ expect(calculateAxisOffset(0, 1, W, [1.0])).toBe(0);
100
+ });
101
+ });
102
+
103
+
104
+ // ──────────────────────────────────────────────
105
+ // getPageOffset
106
+ // ──────────────────────────────────────────────
107
+ describe('getPageOffset', () => {
108
+ const DIMS = { width: 400, height: 800 };
109
+
110
+ test('origin page returns (0, 0)', () => {
111
+ const layout = [[1, 1, 1]];
112
+ expect(getPageOffset(1, layout, DIMS, 1, [], [0, 0])).toEqual({ x: 0, y: 0 });
113
+ });
114
+
115
+ test('adjacent page with no overlap returns (screenWidth, 0)', () => {
116
+ const layout = [[1, 1]];
117
+ expect(getPageOffset(1, layout, DIMS, 0, [], [0])).toEqual({ x: 400, y: 0 });
118
+ });
119
+
120
+ test('page below origin returns (0, screenHeight)', () => {
121
+ const layout = [[1], [1]];
122
+ expect(getPageOffset(1, layout, DIMS, 0, [0], [])).toEqual({ x: 0, y: 800 });
123
+ });
124
+
125
+ test('page to left of origin returns negative x', () => {
126
+ const layout = [[1, 1, 1]];
127
+ expect(getPageOffset(0, layout, DIMS, 1, [], [0, 0])).toEqual({ x: -400, y: 0 });
128
+ });
129
+
130
+ test('layout: CANVAS from ROOM (overlap 0.5)', () => {
131
+ // Layout: [[1,1,1]], origin = 1 (ROOM), cols overlap = [0.5, 0.4]
132
+ const layout = [[1, 1, 1]];
133
+ const offset = getPageOffset(0, layout, DIMS, 1, [], [0.5, 0.4]);
134
+ // CANVAS (col 0) to ROOM (col 1): -W * (1 - 0.5) = -200
135
+ expect(offset).toEqual({ x: -200, y: 0 });
136
+ });
137
+
138
+ test('layout: DRAWERS from ROOM (overlap 0.4)', () => {
139
+ const layout = [[1, 1, 1]];
140
+ const offset = getPageOffset(2, layout, DIMS, 1, [], [0.5, 0.4]);
141
+ // DRAWERS (col 2) to ROOM (col 1): W * (1 - 0.4) = 240
142
+ expect(offset).toEqual({ x: 240, y: 0 });
143
+ });
144
+
145
+ test('2×2 grid: diagonal page offset', () => {
146
+ const layout = [[1, 1], [1, 1]];
147
+ // Page 3 is (1,1), origin 0 is (0,0)
148
+ const offset = getPageOffset(3, layout, DIMS, 0, [0], [0]);
149
+ expect(offset).toEqual({ x: 400, y: 800 });
150
+ });
151
+ });
152
+
153
+
154
+ // ──────────────────────────────────────────────
155
+ // getWorldBounds
156
+ // ──────────────────────────────────────────────
157
+ describe('getWorldBounds', () => {
158
+ const DIMS = { width: 400, height: 800 };
159
+
160
+ test('single page returns all zeros', () => {
161
+ const bounds = getWorldBounds([0], [[1]], DIMS, 0, [], []);
162
+ expect(bounds).toEqual({ minX: 0, maxX: 0, minY: 0, maxY: 0 });
163
+ });
164
+
165
+ test('empty pages array returns all zeros', () => {
166
+ const bounds = getWorldBounds([], [[1]], DIMS, 0, [], []);
167
+ expect(bounds).toEqual({ minX: 0, maxX: 0, minY: 0, maxY: 0 });
168
+ });
169
+
170
+ test('3 horizontal pages, no overlap, origin at 0', () => {
171
+ const layout = [[1, 1, 1]];
172
+ const bounds = getWorldBounds([0, 1, 2], layout, DIMS, 0, [], [0, 0]);
173
+ expect(bounds.minX).toBe(0);
174
+ expect(bounds.maxX).toBe(800); // 2 × 400
175
+ expect(bounds.minY).toBe(0);
176
+ expect(bounds.maxY).toBe(0);
177
+ });
178
+
179
+ test('3 horizontal pages, origin at center', () => {
180
+ const layout = [[1, 1, 1]];
181
+ const bounds = getWorldBounds([0, 1, 2], layout, DIMS, 1, [], [0, 0]);
182
+ expect(bounds.minX).toBe(-400);
183
+ expect(bounds.maxX).toBe(400);
184
+ });
185
+
186
+ test('layout bounds (overlaps, origin at ROOM)', () => {
187
+ const layout = [[1, 1, 1]];
188
+ const bounds = getWorldBounds([0, 1, 2], layout, DIMS, 1, [], [0.5, 0.4]);
189
+ // CANVAS: -200, ROOM: 0, DRAWERS: +240
190
+ expect(bounds.minX).toBe(-200);
191
+ expect(bounds.maxX).toBe(240);
192
+ expect(bounds.minY).toBe(0);
193
+ expect(bounds.maxY).toBe(0);
194
+ });
195
+
196
+ test('2×2 grid spans both axes', () => {
197
+ const layout = [[1, 1], [1, 1]];
198
+ const bounds = getWorldBounds([0, 1, 2, 3], layout, DIMS, 0, [0], [0]);
199
+ expect(bounds.minX).toBe(0);
200
+ expect(bounds.maxX).toBe(400);
201
+ expect(bounds.minY).toBe(0);
202
+ expect(bounds.maxY).toBe(800);
203
+ });
204
+
205
+ test('overlaps reduce bounds magnitude', () => {
206
+ const layout = [[1, 1]];
207
+ const noOverlap = getWorldBounds([0, 1], layout, DIMS, 0, [], [0]);
208
+ const withOverlap = getWorldBounds([0, 1], layout, DIMS, 0, [], [0.5]);
209
+ expect(withOverlap.maxX).toBe(noOverlap.maxX * 0.5);
210
+ });
211
+ });
@@ -0,0 +1,85 @@
1
+ import * as React from 'react';
2
+ import * as renderer from 'react-test-renderer';
3
+ import { View, Text, StyleSheet } from 'react-native';
4
+
5
+ // Mock Reanimated
6
+ jest.mock('react-native-reanimated', () => {
7
+ const Reanimated = require('react-native-reanimated/mock');
8
+ return Reanimated;
9
+ });
10
+
11
+ // Mock Aniview component to verify layout logic
12
+ jest.mock('../Aniview', () => {
13
+ const { View } = require('react-native');
14
+ return jest.fn((props: any) => {
15
+ return <View testID="aniview-mock" style={props.style}>{props.children}</View>;
16
+ });
17
+ });
18
+
19
+ import Aniview from '../Aniview';
20
+
21
+ describe('Aniview Snapshot Suite', () => {
22
+
23
+ const TestLayout = ({ outerFlex, innerFlex, textPos }: any) => (
24
+ <Aniview pageId={1} style={[styles.outer, outerFlex]}>
25
+ <View style={[styles.inner, innerFlex]}>
26
+ <Text style={[styles.text, textPos]}>Main Content</Text>
27
+ <Aniview pageId={2} style={styles.nested}>
28
+ <Text>Nested Item</Text>
29
+ </Aniview>
30
+ </View>
31
+ </Aniview>
32
+ );
33
+
34
+ it('Scenario 1: Default Center Aligned', () => {
35
+ const tree = renderer.create(
36
+ <TestLayout
37
+ outerFlex={{ justifyContent: 'center', alignItems: 'center' }}
38
+ innerFlex={{ padding: 20, backgroundColor: '#eee' }}
39
+ textPos={{ fontSize: 20 }}
40
+ />
41
+ ).toJSON();
42
+ console.log('TREE 1:', JSON.stringify(tree)); // DEBUG
43
+ expect(tree).toMatchSnapshot();
44
+ });
45
+
46
+ it('Scenario 2: Space-Between Flex with Absolute Text', () => {
47
+ const tree = renderer.create(
48
+ <TestLayout
49
+ outerFlex={{ justifyContent: 'space-between', flexDirection: 'row' }}
50
+ innerFlex={{ flex: 1, alignItems: 'flex-end' }}
51
+ textPos={{ position: 'absolute', top: 10, right: 10, color: 'blue' }}
52
+ />
53
+ ).toJSON();
54
+ expect(tree).toMatchSnapshot();
55
+ });
56
+
57
+ it('Scenario 3: Column Reverse with Margin Auto', () => {
58
+ const tree = renderer.create(
59
+ <TestLayout
60
+ outerFlex={{ flexDirection: 'column-reverse' }}
61
+ innerFlex={{ marginTop: 'auto', marginBottom: 20 }}
62
+ textPos={{ textAlign: 'center', fontWeight: 'bold' }}
63
+ />
64
+ ).toJSON();
65
+ expect(tree).toMatchSnapshot();
66
+ });
67
+ });
68
+
69
+ const styles = StyleSheet.create({
70
+ outer: {
71
+ width: '100%',
72
+ height: '100%',
73
+ },
74
+ inner: {
75
+ borderRadius: 10,
76
+ },
77
+ text: {
78
+ fontFamily: 'System',
79
+ },
80
+ nested: {
81
+ width: 100,
82
+ height: 50,
83
+ backgroundColor: 'rgba(0,0,0,0.1)',
84
+ }
85
+ });
@@ -0,0 +1,7 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`Aniview Snapshot Suite Scenario 1: Default Center Aligned 1`] = `null`;
4
+
5
+ exports[`Aniview Snapshot Suite Scenario 2: Space-Between Flex with Absolute Text 1`] = `null`;
6
+
7
+ exports[`Aniview Snapshot Suite Scenario 3: Column Reverse with Margin Auto 1`] = `null`;