1
+ /**
2
+ * Performance optimization tests
3
+ * Tests caching, batching, and performance features
4
+ */
5
+
6
+ import { describe , it , expect , beforeEach , vi } from 'vitest'
7
+ import {
8
+ processText ,
9
+ clearProcessingCaches ,
10
+ needsProcessing ,
11
+ PatternMatchers ,
12
+ PatternExtractors ,
13
+ createPatterns ,
14
+ createCombinedPattern ,
15
+ DEFAULT_CONFIG ,
16
+ } from '../src/runtime/smartscript'
17
+
18
+ describe ( 'Performance: Caching' , ( ) => {
19
+ const config = DEFAULT_CONFIG
20
+ const patterns = createPatterns ( config )
21
+ const combinedPattern = createCombinedPattern ( patterns , config )
22
+
23
+ beforeEach ( ( ) => {
24
+ // Clear caches before each test
25
+ clearProcessingCaches ( )
26
+ } )
27
+
28
+ it ( 'should cache repeated text processing' , ( ) => {
29
+ const text = 'E=mc^2 and H_2O'
30
+
31
+ // First call - should process
32
+ const result1 = processText ( text , combinedPattern )
33
+
34
+ // Second call - should use cache
35
+ const result2 = processText ( text , combinedPattern )
36
+
37
+ // Results should be identical
38
+ expect ( result1 ) . toEqual ( result2 )
39
+ // The text actually produces 4 parts after processing:
40
+ // 'E=m' (text), 'c^2' -> 'c' + superscript '2', ' and ' (text), 'H_2O' -> 'H' + subscript '2' + 'O'
41
+ expect ( result1 . length ) . toBeGreaterThan ( 0 )
42
+ } )
43
+
44
+ it ( 'should clear cache when requested' , ( ) => {
45
+ const text = 'x^2 + y^3'
46
+
47
+ // Process once
48
+ const result1 = processText ( text , combinedPattern )
49
+ expect ( result1 ) . toBeDefined ( )
50
+
51
+ // Clear cache
52
+ clearProcessingCaches ( )
53
+
54
+ // Process again - should reprocess, not use cache
55
+ const result2 = processText ( text , combinedPattern )
56
+ expect ( result2 ) . toEqual ( result1 )
57
+ } )
58
+
59
+ it ( 'should handle cache size limits' , ( ) => {
60
+ // This tests that the LRU cache doesn't grow unbounded
61
+ // Generate 1100 unique strings (more than MAX_CACHE_SIZE of 1000)
62
+ const results = [ ]
63
+ for ( let i = 0 ; i < 1100 ; i ++ ) {
64
+ const text = `test_${ i } ^2`
65
+ const result = processText ( text , combinedPattern )
66
+ results . push ( result )
67
+ }
68
+
69
+ // All should process correctly
70
+ expect ( results ) . toHaveLength ( 1100 )
71
+ expect ( results [ 0 ] ) . toBeDefined ( )
72
+ expect ( results [ 1099 ] ) . toBeDefined ( )
73
+ } )
74
+ } )
75
+
76
+ describe ( 'Performance: Pattern Optimization' , ( ) => {
77
+ it ( 'should use pre-compiled regex patterns' , ( ) => {
78
+ // Test that patterns are not recreated on each call
79
+ const text1 = '™'
80
+ const text2 = '®'
81
+ const text3 = '©'
82
+
83
+ // These should all use pre-compiled patterns
84
+ expect ( PatternMatchers . isTrademark ( text1 ) ) . toBe ( true )
85
+ expect ( PatternMatchers . isRegistered ( text2 ) ) . toBe ( true )
86
+ expect ( PatternMatchers . isCopyright ( text3 ) ) . toBe ( true )
87
+ } )
88
+
89
+ it ( 'should efficiently check if text needs processing' , ( ) => {
90
+ const pattern = / t e s t / g
91
+
92
+ // Multiple calls should handle regex state correctly
93
+ expect ( needsProcessing ( 'test' , pattern ) ) . toBe ( true )
94
+ expect ( needsProcessing ( 'test' , pattern ) ) . toBe ( true ) // Should reset lastIndex
95
+ expect ( needsProcessing ( 'no match' , pattern ) ) . toBe ( false )
96
+ expect ( needsProcessing ( 'test again' , pattern ) ) . toBe ( true )
97
+ } )
98
+
99
+ it ( 'should extract patterns efficiently' , ( ) => {
100
+ // Test optimized extractors
101
+ expect ( PatternExtractors . extractOrdinal ( '1st' ) ) . toEqual ( { number : '1' , suffix : 'st' } )
102
+ expect ( PatternExtractors . extractOrdinal ( '2nd' ) ) . toEqual ( { number : '2' , suffix : 'nd' } )
103
+ expect ( PatternExtractors . extractOrdinal ( '3rd' ) ) . toEqual ( { number : '3' , suffix : 'rd' } )
104
+ expect ( PatternExtractors . extractOrdinal ( '4th' ) ) . toEqual ( { number : '4' , suffix : 'th' } )
105
+
106
+ // Chemical extraction - pattern is [A-Z][a-z]?\d+ so CO2 doesn't match (C and O are both capitals)
107
+ expect ( PatternExtractors . extractChemicalElement ( 'H2' ) ) . toEqual ( { element : 'H' , count : '2' } )
108
+ expect ( PatternExtractors . extractChemicalElement ( 'Ca2' ) ) . toEqual ( { element : 'Ca' , count : '2' } ) // Calcium works
109
+
110
+ // Math extraction (unified pattern)
111
+ expect ( PatternExtractors . extractMathWithVariable ( 'x^2' ) ) . toEqual ( { variable : 'x' , script : '2' } )
112
+ expect ( PatternExtractors . extractMathWithVariable ( 'y_1' ) ) . toEqual ( { variable : 'y' , script : '1' } )
113
+ expect ( PatternExtractors . extractMathWithVariable ( 'z^{10}' ) ) . toEqual ( { variable : 'z' , script : '10' } )
114
+ } )
115
+ } )
116
+
117
+ describe ( 'Performance: Batch Processing' , ( ) => {
118
+ it ( 'should handle empty text efficiently' , ( ) => {
119
+ const pattern = / t e s t / g
120
+
121
+ expect ( needsProcessing ( '' , pattern ) ) . toBe ( false )
122
+ expect ( needsProcessing ( ' ' , pattern ) ) . toBe ( false )
123
+ expect ( needsProcessing ( '\n\t' , pattern ) ) . toBe ( false )
124
+ } )
125
+
126
+ it ( 'should process mixed content efficiently' , ( ) => {
127
+ const config = DEFAULT_CONFIG
128
+ const patterns = createPatterns ( config )
129
+ const combinedPattern = createCombinedPattern ( patterns , config )
130
+
131
+ const complexText = `
132
+ The formula E=mc^2 revolutionized physics.
133
+ Water (H_2O) is essential for life.
134
+ The 1st, 2nd, and 3rd laws of thermodynamics.
135
+ Copyright (C) 2024, TM and (R) symbols.
136
+ Complex math: x^{2n+1} + y_{n-1} = z^2
137
+ `
138
+
139
+ const result = processText ( complexText , combinedPattern )
140
+
141
+ // Should process all patterns
142
+ expect ( result . length ) . toBeGreaterThan ( 10 )
143
+
144
+ // Verify some specific transformations
145
+ const textContent = result . map ( p => p . content ) . join ( '' )
146
+ expect ( textContent ) . toContain ( '©' ) // (C) -> ©
147
+ expect ( result . some ( p => p . type === 'super' ) ) . toBe ( true )
148
+ expect ( result . some ( p => p . type === 'sub' ) ) . toBe ( true )
149
+ } )
150
+ } )
151
+
152
+ describe ( 'Performance: Memory Management' , ( ) => {
153
+ it ( 'should clear all caches on navigation' , ( ) => {
154
+ const config = DEFAULT_CONFIG
155
+ const patterns = createPatterns ( config )
156
+ const combinedPattern = createCombinedPattern ( patterns , config )
157
+
158
+ // Process some text
159
+ processText ( 'test^2' , combinedPattern )
160
+ processText ( 'H_2O' , combinedPattern )
161
+
162
+ // Clear caches (simulating navigation)
163
+ clearProcessingCaches ( )
164
+
165
+ // Should work normally after clear
166
+ const result = processText ( 'new^text' , combinedPattern )
167
+ expect ( result ) . toBeDefined ( )
168
+ } )
169
+ } )
170
+
171
+ describe ( 'Performance: Edge Cases' , ( ) => {
172
+ it ( 'should handle malformed patterns gracefully' , ( ) => {
173
+ const config = DEFAULT_CONFIG
174
+ const patterns = createPatterns ( config )
175
+ const combinedPattern = createCombinedPattern ( patterns , config )
176
+
177
+ // These should not crash
178
+ expect ( ( ) => processText ( 'x^' , combinedPattern ) ) . not . toThrow ( )
179
+ expect ( ( ) => processText ( 'y_' , combinedPattern ) ) . not . toThrow ( )
180
+ expect ( ( ) => processText ( '^2' , combinedPattern ) ) . not . toThrow ( )
181
+ expect ( ( ) => processText ( '_1' , combinedPattern ) ) . not . toThrow ( )
182
+ } )
183
+
184
+ it ( 'should handle very long text efficiently' , ( ) => {
185
+ const config = DEFAULT_CONFIG
186
+ const patterns = createPatterns ( config )
187
+ const combinedPattern = createCombinedPattern ( patterns , config )
188
+
189
+ // Generate a long text with patterns
190
+ const longText = Array ( 100 ) . fill ( 'test^2 and H_2O' ) . join ( ' ' )
191
+
192
+ const startTime = performance . now ( )
193
+ const result = processText ( longText , combinedPattern )
194
+ const endTime = performance . now ( )
195
+
196
+ // Should complete quickly (under 100ms for 100 repetitions)
197
+ expect ( endTime - startTime ) . toBeLessThan ( 100 )
198
+ expect ( result ) . toBeDefined ( )
199
+ } )
200
+ } )
0 commit comments