ripple 0.2.165 → 0.2.167

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 (26) hide show
  1. package/package.json +2 -2
  2. package/src/compiler/phases/1-parse/index.js +30 -1
  3. package/src/compiler/phases/1-parse/style.js +36 -1
  4. package/src/compiler/phases/2-analyze/css-analyze.js +145 -0
  5. package/src/compiler/phases/2-analyze/index.js +7 -0
  6. package/src/compiler/phases/2-analyze/prune.js +165 -11
  7. package/src/compiler/phases/2-analyze/validation.js +156 -0
  8. package/src/compiler/phases/3-transform/client/index.js +62 -12
  9. package/src/compiler/phases/3-transform/stylesheet.js +102 -3
  10. package/src/runtime/internal/client/index.js +1 -0
  11. package/src/runtime/internal/client/operations.js +0 -6
  12. package/src/runtime/internal/client/render.js +22 -16
  13. package/tests/client/css/global-additional-cases.test.ripple +702 -0
  14. package/tests/client/css/global-advanced-selectors.test.ripple +229 -0
  15. package/tests/client/css/global-at-rules.test.ripple +126 -0
  16. package/tests/client/css/global-basic.test.ripple +165 -0
  17. package/tests/client/css/global-classes-ids.test.ripple +179 -0
  18. package/tests/client/css/global-combinators.test.ripple +124 -0
  19. package/tests/client/css/global-complex-nesting.test.ripple +221 -0
  20. package/tests/client/css/global-edge-cases.test.ripple +200 -0
  21. package/tests/client/css/global-keyframes.test.ripple +101 -0
  22. package/tests/client/css/global-nested.test.ripple +150 -0
  23. package/tests/client/css/global-pseudo.test.ripple +155 -0
  24. package/tests/client/css/global-scoping.test.ripple +229 -0
  25. package/tests/client/dynamic-elements.test.ripple +0 -1
  26. package/tests/server/streaming-ssr.test.ripple +9 -6
@@ -0,0 +1,150 @@
1
+ import { compile } from 'ripple/compiler';
2
+
3
+ describe('CSS :global nested blocks', () => {
4
+ it('handles nested global blocks', () => {
5
+ const source = `
6
+ export component Test() {
7
+ <div>
8
+ <p>{'content'}</p>
9
+ </div>
10
+
11
+ <style>
12
+ div {
13
+ :global {
14
+ .x {
15
+ color: green;
16
+ }
17
+ }
18
+
19
+ :global(.x) {
20
+ color: green;
21
+ }
22
+
23
+ p :global {
24
+ .y {
25
+ color: green;
26
+ }
27
+ }
28
+
29
+ p :global(.y) {
30
+ color: green;
31
+ }
32
+ }
33
+ </style>
34
+ }`;
35
+ const { css } = compile(source, 'test.ripple');
36
+
37
+ expect(css).toMatch(/div\.ripple-[a-z0-9]+ {/);
38
+ expect(css).toContain('.x {');
39
+ expect(css).toContain('.y {');
40
+ expect(css).not.toMatch(/\.x\.ripple-[a-z0-9]+/);
41
+ expect(css).not.toMatch(/\.y\.ripple-[a-z0-9]+/);
42
+ expect(css).toMatch(/p\.ripple-[a-z0-9]+ \.y {/);
43
+ });
44
+
45
+ it('handles :global with nesting selector', () => {
46
+ const source = `
47
+ export component Test() {
48
+ <div class="x">{'content'}</div>
49
+
50
+ <style>
51
+ div {
52
+ :global {
53
+ &.x {
54
+ color: green;
55
+ }
56
+ }
57
+ }
58
+
59
+ div :global {
60
+ &.x {
61
+ color: green;
62
+ }
63
+ }
64
+
65
+ div :global.x {
66
+ color: green;
67
+ }
68
+ </style>
69
+ }`;
70
+ const { css } = compile(source, 'test.ripple');
71
+
72
+ expect(css).toContain('&.x {');
73
+ expect(css).toMatch(/div\.ripple-[a-z0-9]+\.x {/);
74
+ });
75
+
76
+ it('handles global block with de-nested syntax', () => {
77
+ const source = `
78
+ export component Test() {
79
+ <div><p>{'content'}</p></div>
80
+
81
+ <style>
82
+ :global div {
83
+ .y {
84
+ color: green;
85
+ }
86
+ }
87
+
88
+ div :global p {
89
+ .y {
90
+ color: green;
91
+ }
92
+ }
93
+ </style>
94
+ }`;
95
+ const { css } = compile(source, 'test.ripple');
96
+
97
+ expect(css).toContain('div {');
98
+ expect(css).toContain('.y {');
99
+ expect(css).toMatch(/div\.ripple-[a-z0-9]+ p {/);
100
+ });
101
+
102
+ it('handles global local nested combinations', () => {
103
+ const source = `
104
+ export component Test() {
105
+ <div>{'content'}</div>
106
+
107
+ <style>
108
+ div {
109
+ :global(.whatever) {
110
+ color: green;
111
+ }
112
+ }
113
+
114
+ :global(.whatever) {
115
+ div {
116
+ color: green;
117
+ }
118
+ }
119
+ </style>
120
+ }`;
121
+ const { css } = compile(source, 'test.ripple');
122
+
123
+ expect(css).toContain('.whatever {');
124
+ expect(css).not.toMatch(/\.whatever\.ripple-[a-z0-9]+ {/);
125
+ expect(css).toMatch(/div\.ripple-[a-z0-9]+ {/);
126
+ });
127
+
128
+ it('handles :global with :is and :where pseudoclasses', () => {
129
+ const source = `
130
+ export component Test() {
131
+ <div>
132
+ <span>{'content'}</span>
133
+ </div>
134
+
135
+ <style>
136
+ div :global(:is(span)) {
137
+ color: green;
138
+ }
139
+
140
+ :global(.foo) :is(div) {
141
+ color: green;
142
+ }
143
+ </style>
144
+ }`;
145
+ const { css } = compile(source, 'test.ripple');
146
+
147
+ expect(css).toMatch(/div\.ripple-[a-z0-9]+ :is\(span\) {/);
148
+ expect(css).toMatch(/\.foo :is\(div\.ripple-[a-z0-9]+\) {/);
149
+ });
150
+ });
@@ -0,0 +1,155 @@
1
+ import { compile } from 'ripple/compiler';
2
+
3
+ describe('CSS :global with pseudo-classes', () => {
4
+ it('handles :global with :has()', () => {
5
+ const source = `
6
+ export component Test() {
7
+ <div>
8
+ <span>{'content'}</span>
9
+ </div>
10
+
11
+ <style>
12
+ div:has(:global(span)) {
13
+ color: red;
14
+ }
15
+
16
+ :global(div:has(span)) {
17
+ color: blue;
18
+ }
19
+ </style>
20
+ }`;
21
+ const { css } = compile(source, 'test.ripple');
22
+
23
+ expect(css).toMatch(/div\.ripple-[a-z0-9]+:has\(span\)/);
24
+ expect(css).toContain('div:has(span)');
25
+ });
26
+
27
+ it('handles :global with :is()', () => {
28
+ const source = `
29
+ export component Test() {
30
+ <div>
31
+ <span>{'one'}</span>
32
+ <p>{'two'}</p>
33
+ </div>
34
+
35
+ <style>
36
+ div :is(:global(span), p) {
37
+ color: red;
38
+ }
39
+
40
+ :global(div:is(.foo, .bar)) {
41
+ color: blue;
42
+ }
43
+ </style>
44
+ }`;
45
+ const { css } = compile(source, 'test.ripple');
46
+
47
+ expect(css).toMatch(/div\.ripple-[a-z0-9]+ :is\(span, p:where\(\.ripple-[a-z0-9]+\)\) {/);
48
+ expect(css).not.toMatch(/span:where/);
49
+ expect(css).not.toMatch(/span\.ripple/);
50
+ expect(css).toContain('div:is(.foo, .bar)');
51
+ });
52
+
53
+ it('handles :global with :where()', () => {
54
+ const source = `
55
+ export component Test() {
56
+ <div>
57
+ <span>{'one'}</span>
58
+ <p>{'two'}</p>
59
+ </div>
60
+
61
+ <style>
62
+ div :where(:global(span), p) {
63
+ color: red;
64
+ }
65
+
66
+ :global(div:where(.foo, .bar)) {
67
+ color: blue;
68
+ }
69
+ </style>
70
+ }`;
71
+ const { css } = compile(source, 'test.ripple');
72
+
73
+ expect(css).toMatch(/div\.ripple-[a-z0-9]+ :where\(span, p:where\(\.ripple-[a-z0-9]+\)\) {/);
74
+ expect(css).not.toMatch(/span:where/);
75
+ expect(css).not.toMatch(/span\.ripple/);
76
+ expect(css).toContain('div:where(.foo, .bar)');
77
+ });
78
+
79
+ it('handles :global with :not()', () => {
80
+ const source = `
81
+ export component Test() {
82
+ <div>
83
+ <span>{'content'}</span>
84
+ </div>
85
+
86
+ <style>
87
+ div:not(:global(span)) {
88
+ color: red;
89
+ }
90
+
91
+ :global(div:not(.foo)) {
92
+ color: blue;
93
+ }
94
+ </style>
95
+ }`;
96
+ const { css } = compile(source, 'test.ripple');
97
+
98
+ expect(css).toMatch(/div\.ripple-[a-z0-9]+:not\(span\)/);
99
+ expect(css).toContain('div:not(.foo)');
100
+ });
101
+
102
+ it('handles nested pseudo-classes with :global', () => {
103
+ const source = `
104
+ export component Test() {
105
+ <div>
106
+ <span>{'content'}</span>
107
+ </div>
108
+
109
+ <style>
110
+ div:is(:has(:global(span))) {
111
+ color: red;
112
+ }
113
+
114
+ :global(div:where(:is(.foo))) {
115
+ color: blue;
116
+ }
117
+ </style>
118
+ }`;
119
+ const { css } = compile(source, 'test.ripple');
120
+
121
+ expect(css).toMatch(/div\.ripple-[a-z0-9]+:is\(:where\(\.ripple-[a-z0-9]+\):has\(span\)\) {/);
122
+ expect(css).toContain('div:where(:is(.foo))');
123
+ });
124
+
125
+ it('handles :global with :nth-child and other structural pseudo-classes', () => {
126
+ const source = `
127
+ export component Test() {
128
+ <div>
129
+ <span>{'one'}</span>
130
+ <span>{'two'}</span>
131
+ <span>{'three'}</span>
132
+ </div>
133
+
134
+ <style>
135
+ :global(span):nth-child(2) {
136
+ color: red;
137
+ }
138
+
139
+ div > :global(span:first-child) {
140
+ color: blue;
141
+ }
142
+
143
+ :global(div):last-child {
144
+ color: green;
145
+ }
146
+ </style>
147
+ }`;
148
+ const { css } = compile(source, 'test.ripple');
149
+
150
+ expect(css).toContain('span:nth-child(2) {');
151
+ expect(css).toMatch(/div\.ripple-[a-z0-9]+ > span:first-child {/);
152
+ expect(css).toContain('div:last-child {');
153
+ expect(css).not.toMatch(/span\.ripple-[a-z0-9]+:nth-child/);
154
+ });
155
+ });
@@ -0,0 +1,229 @@
1
+ import { compile } from 'ripple/compiler';
2
+
3
+ describe('CSS :global scoping verification', () => {
4
+ it('verifies scoped styles are isolated', () => {
5
+ const source = `
6
+ export component Test() {
7
+ <div class="scoped">{'content'}</div>
8
+
9
+ <style>
10
+ .scoped {
11
+ color: red;
12
+ }
13
+ </style>
14
+ }`;
15
+ const { css } = compile(source, 'test.ripple');
16
+
17
+ expect(css).toMatch(/\.scoped\.ripple-[a-z0-9]+/);
18
+ expect(css).not.toContain('.scoped {');
19
+ });
20
+
21
+ it('verifies :global styles bypass scoping', () => {
22
+ const source = `
23
+ export component Test() {
24
+ <div class="global">{'content'}</div>
25
+
26
+ <style>
27
+ :global(.global) {
28
+ color: red;
29
+ }
30
+ </style>
31
+ }`;
32
+ const { css } = compile(source, 'test.ripple');
33
+
34
+ expect(css).toContain('.global {');
35
+ expect(css).not.toMatch(/\.global\.ripple-[a-z0-9]+/);
36
+ });
37
+
38
+ it('verifies mixed local and global maintain proper scoping', () => {
39
+ const source = `
40
+ export component Test() {
41
+ <div class="outer">
42
+ <span class="inner">{'content'}</span>
43
+ </div>
44
+
45
+ <style>
46
+ .outer {
47
+ color: red;
48
+ }
49
+
50
+ :global(.outer) {
51
+ color: blue;
52
+ }
53
+
54
+ .outer .inner {
55
+ color: green;
56
+ }
57
+
58
+ :global(.outer) .inner {
59
+ color: yellow;
60
+ }
61
+
62
+ .outer :global(.inner) {
63
+ color: purple;
64
+ }
65
+ </style>
66
+ }`;
67
+ const { css } = compile(source, 'test.ripple');
68
+
69
+ expect(css).toMatch(/\.outer\.ripple-[a-z0-9]+ {/);
70
+ expect(css).toMatch(/\.outer {/);
71
+ expect(css).toMatch(/\.outer\.ripple-[a-z0-9]+ \.inner:where\(\.ripple-[a-z0-9]+\) {/);
72
+ expect(css).toMatch(/\.outer \.inner\.ripple-[a-z0-9]+/);
73
+ expect(css).toMatch(/\.outer\.ripple-[a-z0-9]+ \.inner {/);
74
+ });
75
+
76
+ it('verifies :global blocks scope all nested selectors globally', () => {
77
+ const source = `
78
+ export component Test() {
79
+ <div>
80
+ <span>
81
+ <button>{'click'}</button>
82
+ </span>
83
+ </div>
84
+
85
+ <style>
86
+ :global {
87
+ div {
88
+ color: red;
89
+ }
90
+
91
+ span {
92
+ color: blue;
93
+ }
94
+
95
+ button {
96
+ color: green;
97
+ }
98
+ }
99
+ </style>
100
+ }`;
101
+ const { css } = compile(source, 'test.ripple');
102
+
103
+ expect(css).toContain('div {');
104
+ expect(css).toContain('span {');
105
+ expect(css).toContain('button {');
106
+ expect(css).not.toMatch(/div\.ripple-[a-z0-9]+/);
107
+ expect(css).not.toMatch(/span\.ripple-[a-z0-9]+/);
108
+ expect(css).not.toMatch(/button\.ripple-[a-z0-9]+/);
109
+ });
110
+
111
+ it('verifies element selectors are scoped by default', () => {
112
+ const source = `
113
+ export component Test() {
114
+ <div>
115
+ <span>
116
+ <button>{'click'}</button>
117
+ </span>
118
+ </div>
119
+
120
+ <style>
121
+ div {
122
+ color: red;
123
+ }
124
+
125
+ span {
126
+ color: blue;
127
+ }
128
+
129
+ button {
130
+ color: green;
131
+ }
132
+ </style>
133
+ }`;
134
+ const { css } = compile(source, 'test.ripple');
135
+
136
+ expect(css).toMatch(/div\.ripple-[a-z0-9]+/);
137
+ expect(css).toMatch(/span\.ripple-[a-z0-9]+/);
138
+ expect(css).toMatch(/button\.ripple-[a-z0-9]+/);
139
+ });
140
+
141
+ it('verifies :global() escapes only the wrapped selector', () => {
142
+ const source = `
143
+ export component Test() {
144
+ <div class="local">
145
+ <span class="global">
146
+ <button class="local">{'click'}</button>
147
+ </span>
148
+ </div>
149
+
150
+ <style>
151
+ .local :global(.global .local) {
152
+ color: red;
153
+ }
154
+ </style>
155
+ }`;
156
+ const { css } = compile(source, 'test.ripple');
157
+
158
+ expect(css).toMatch(/\.local\.ripple-[a-z0-9]+ \.global \.local {/);
159
+ expect(css).not.toMatch(/\.global\.ripple-[a-z0-9]+/);
160
+ });
161
+
162
+ it('verifies multiple components have different scope hashes', () => {
163
+ const source1 = `
164
+ export component Test1() {
165
+ <div class="test">{'one'}</div>
166
+
167
+ <style>
168
+ .test {
169
+ color: red;
170
+ }
171
+ </style>
172
+ }`;
173
+ const source2 = `
174
+ export component Test2() {
175
+ <div class="test">{'two'}</div>
176
+
177
+ <style>
178
+ .test {
179
+ color: blue;
180
+ }
181
+ </style>
182
+ }`;
183
+ const { css: css1 } = compile(source1, 'test1.ripple');
184
+ const { css: css2 } = compile(source2, 'test2.ripple');
185
+
186
+ expect(css1).toMatch(/\.test\.ripple-[a-z0-9]+/);
187
+ expect(css2).toMatch(/\.test\.ripple-[a-z0-9]+/);
188
+
189
+ const hash1Match = css1.match(/\.test\.ripple-([a-z0-9]+)/);
190
+ const hash2Match = css2.match(/\.test\.ripple-([a-z0-9]+)/);
191
+
192
+ expect(hash1Match).toBeTruthy();
193
+ expect(hash2Match).toBeTruthy();
194
+
195
+ if (hash1Match && hash2Match) {
196
+ expect(hash1Match[1]).not.toBe(hash2Match[1]);
197
+ }
198
+ });
199
+
200
+ it('verifies :global styles are consistent across components', () => {
201
+ const source1 = `
202
+ export component Test1() {
203
+ <div class="global">{'one'}</div>
204
+
205
+ <style>
206
+ :global(.global) {
207
+ color: red;
208
+ }
209
+ </style>
210
+ }`;
211
+ const source2 = `
212
+ export component Test2() {
213
+ <div class="global">{'two'}</div>
214
+
215
+ <style>
216
+ :global(.global) {
217
+ color: blue;
218
+ }
219
+ </style>
220
+ }`;
221
+ const { css: css1 } = compile(source1, 'test1.ripple');
222
+ const { css: css2 } = compile(source2, 'test2.ripple');
223
+
224
+ expect(css1).toContain('.global {');
225
+ expect(css1).not.toMatch(/\.global\.ripple-[a-z0-9]+/);
226
+ expect(css2).toContain('.global {');
227
+ expect(css2).not.toMatch(/\.global\.ripple-[a-z0-9]+/);
228
+ });
229
+ });
@@ -279,7 +279,6 @@ describe('dynamic DOM elements', () => {
279
279
 
280
280
  const element = container.querySelector('div');
281
281
  expect(element).toBeTruthy();
282
- console.log(element);
283
282
  expect(element.classList.contains('test-class')).toBe(true);
284
283
 
285
284
  // Check if scoped CSS class is present - THIS MIGHT FAIL if CSS pruning issue exists
@@ -42,12 +42,15 @@ test('renderToStream handles async components', async ({ expect }) => {
42
42
  test('renderToStream handles await blocks with pending state', async ({ expect }) => {
43
43
  component AwaitComponent() {
44
44
  let data = 'initial';
45
- await new Promise((resolve) => setTimeout(() => {
46
- data = 'resolved';
47
- resolve('');
48
- }, 20));
45
+ await new Promise((resolve) => setTimeout(() => {
46
+ data = 'resolved';
47
+ resolve('');
48
+ }, 20));
49
49
  try {
50
- <div>{'Data: '}{data}</div>
50
+ <div>
51
+ {'Data: '}
52
+ {data}
53
+ </div>
51
54
  } pending {
52
55
  <div>{'Loading...'}</div>
53
56
  }
@@ -62,6 +65,6 @@ test('renderToStream handles await blocks with pending state', async ({ expect }
62
65
  });
63
66
  stream.on('end', resolve);
64
67
  });
65
- console.log(result)
68
+
66
69
  expect(result).toBe('<div>Loading...</div><div>Data: resolved</div>');
67
70
  });