@csszyx/dynamic 0.4.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.
@@ -0,0 +1,339 @@
1
+ /**
2
+ * Tests for css-generator module.
3
+ * Verifies CSS rule generation for Tailwind v4 class patterns.
4
+ */
5
+
6
+ import { describe, expect, it } from 'vitest';
7
+
8
+ import { generateCSSRule, generateDeclarations, parseVariants } from '../src/css-generator.js';
9
+
10
+ describe('parseVariants', () => {
11
+ it('returns base tier for non-variant class', () => {
12
+ expect(parseVariants('p-4')).toMatchObject({
13
+ tier: 'base', pseudoSuffix: '', selectorPrefix: '', utility: 'p-4',
14
+ });
15
+ });
16
+
17
+ it('parses hover variant', () => {
18
+ const r = parseVariants('hover:bg-blue-500');
19
+ expect(r.tier).toBe('base');
20
+ expect(r.pseudoSuffix).toBe(':hover');
21
+ expect(r.utility).toBe('bg-blue-500');
22
+ });
23
+
24
+ it('parses dark variant', () => {
25
+ const r = parseVariants('dark:bg-gray-900');
26
+ expect(r.tier).toBe('base');
27
+ expect(r.selectorPrefix).toBe('.dark ');
28
+ expect(r.utility).toBe('bg-gray-900');
29
+ });
30
+
31
+ it('parses sm breakpoint', () => {
32
+ const r = parseVariants('sm:p-4');
33
+ expect(r.tier).toBe('sm');
34
+ expect(r.utility).toBe('p-4');
35
+ });
36
+
37
+ it('parses lg breakpoint', () => {
38
+ const r = parseVariants('lg:p-8');
39
+ expect(r.tier).toBe('lg');
40
+ expect(r.utility).toBe('p-8');
41
+ });
42
+
43
+ it('parses stacked sm:hover variant (breakpoint first)', () => {
44
+ const r = parseVariants('sm:hover:bg-blue-600');
45
+ expect(r.tier).toBe('sm');
46
+ expect(r.pseudoSuffix).toBe(':hover');
47
+ expect(r.utility).toBe('bg-blue-600');
48
+ });
49
+
50
+ it('parses max-sm tier', () => {
51
+ const r = parseVariants('max-sm:p-4');
52
+ expect(r.tier).toBe('max-sm');
53
+ expect(r.utility).toBe('p-4');
54
+ });
55
+ });
56
+
57
+ describe('generateDeclarations', () => {
58
+ // ── Spacing ──────────────────────────────────────────────────────────────
59
+
60
+ it('generates padding from p-4', () => {
61
+ expect(generateDeclarations('p-4')).toBe('padding: calc(var(--spacing) * 4)');
62
+ });
63
+
64
+ it('generates padding-inline from px-4', () => {
65
+ expect(generateDeclarations('px-4')).toBe('padding-inline: calc(var(--spacing) * 4)');
66
+ });
67
+
68
+ it('generates padding-block from py-4', () => {
69
+ expect(generateDeclarations('py-4')).toBe('padding-block: calc(var(--spacing) * 4)');
70
+ });
71
+
72
+ it('generates margin from m-2', () => {
73
+ expect(generateDeclarations('m-2')).toBe('margin: calc(var(--spacing) * 2)');
74
+ });
75
+
76
+ it('generates width from w-4', () => {
77
+ expect(generateDeclarations('w-4')).toBe('width: calc(var(--spacing) * 4)');
78
+ });
79
+
80
+ it('generates height from h-4', () => {
81
+ expect(generateDeclarations('h-4')).toBe('height: calc(var(--spacing) * 4)');
82
+ });
83
+
84
+ it('generates gap from gap-4', () => {
85
+ expect(generateDeclarations('gap-4')).toBe('gap: calc(var(--spacing) * 4)');
86
+ });
87
+
88
+ it('handles zero spacing', () => {
89
+ expect(generateDeclarations('p-0')).toBe('padding: 0');
90
+ });
91
+
92
+ it('handles px spacing (1px)', () => {
93
+ expect(generateDeclarations('p-px')).toBe('padding: 1px');
94
+ });
95
+
96
+ it('handles auto margin', () => {
97
+ expect(generateDeclarations('m-auto')).toBe('margin: auto');
98
+ });
99
+
100
+ it('handles w-full', () => {
101
+ expect(generateDeclarations('w-full')).toBe('width: 100%');
102
+ });
103
+
104
+ it('handles h-screen', () => {
105
+ expect(generateDeclarations('h-screen')).toBe('height: 100vh');
106
+ });
107
+
108
+ it('handles fractional width w-1/2', () => {
109
+ expect(generateDeclarations('w-1/2')).toBe('width: 50%');
110
+ });
111
+
112
+ it('handles fractional width w-3/4', () => {
113
+ expect(generateDeclarations('w-3/4')).toBe('width: 75%');
114
+ });
115
+
116
+ it('handles arbitrary spacing p-[13px]', () => {
117
+ expect(generateDeclarations('p-[13px]')).toBe('padding: 13px');
118
+ });
119
+
120
+ it('handles arbitrary spacing with spaces p-[calc(100%-2rem)]', () => {
121
+ expect(generateDeclarations('p-[calc(100%_-_2rem)]')).toBe('padding: calc(100% - 2rem)');
122
+ });
123
+
124
+ it('handles size property', () => {
125
+ expect(generateDeclarations('size-4')).toBe('width: calc(var(--spacing) * 4); height: calc(var(--spacing) * 4)');
126
+ });
127
+
128
+ // ── Colors ───────────────────────────────────────────────────────────────
129
+
130
+ it('generates background-color from bg-blue-500', () => {
131
+ expect(generateDeclarations('bg-blue-500')).toBe('background-color: var(--color-blue-500)');
132
+ });
133
+
134
+ it('generates color from text-red-300', () => {
135
+ expect(generateDeclarations('text-red-300')).toBe('color: var(--color-red-300)');
136
+ });
137
+
138
+ it('generates border-color from border-gray-200', () => {
139
+ expect(generateDeclarations('border-gray-200')).toBe('border-color: var(--color-gray-200)');
140
+ });
141
+
142
+ it('handles bg-white as direct keyword', () => {
143
+ expect(generateDeclarations('bg-white')).toBe('background-color: white');
144
+ });
145
+
146
+ it('handles bg-transparent', () => {
147
+ expect(generateDeclarations('bg-transparent')).toBe('background-color: transparent');
148
+ });
149
+
150
+ it('handles bg-[#ff6b35] arbitrary color', () => {
151
+ expect(generateDeclarations('bg-[#ff6b35]')).toBe('background-color: #ff6b35');
152
+ });
153
+
154
+ it('handles color with opacity modifier bg-blue-500/50', () => {
155
+ const result = generateDeclarations('bg-blue-500/50');
156
+ expect(result).toContain('color-mix');
157
+ expect(result).toContain('var(--color-blue-500)');
158
+ expect(result).toContain('50%');
159
+ });
160
+
161
+ // ── Keywords ─────────────────────────────────────────────────────────────
162
+
163
+ it('generates display flex', () => {
164
+ expect(generateDeclarations('flex')).toBe('display: flex');
165
+ });
166
+
167
+ it('generates display block', () => {
168
+ expect(generateDeclarations('block')).toBe('display: block');
169
+ });
170
+
171
+ it('generates display none for hidden', () => {
172
+ expect(generateDeclarations('hidden')).toBe('display: none');
173
+ });
174
+
175
+ it('generates flex-direction column for flex-col', () => {
176
+ expect(generateDeclarations('flex-col')).toBe('flex-direction: column');
177
+ });
178
+
179
+ it('generates align-items center for items-center', () => {
180
+ expect(generateDeclarations('items-center')).toBe('align-items: center');
181
+ });
182
+
183
+ it('generates justify-content center', () => {
184
+ expect(generateDeclarations('justify-center')).toBe('justify-content: center');
185
+ });
186
+
187
+ it('generates font-weight for font-bold', () => {
188
+ expect(generateDeclarations('font-bold')).toBe('font-weight: 700');
189
+ });
190
+
191
+ it('generates font-weight for font-medium', () => {
192
+ expect(generateDeclarations('font-medium')).toBe('font-weight: 500');
193
+ });
194
+
195
+ it('generates text-align for text-center', () => {
196
+ expect(generateDeclarations('text-center')).toBe('text-align: center');
197
+ });
198
+
199
+ it('generates text-transform for uppercase', () => {
200
+ expect(generateDeclarations('uppercase')).toBe('text-transform: uppercase');
201
+ });
202
+
203
+ it('generates position absolute', () => {
204
+ expect(generateDeclarations('absolute')).toBe('position: absolute');
205
+ });
206
+
207
+ // ── Text size ─────────────────────────────────────────────────────────────
208
+
209
+ it('generates font-size for text-lg', () => {
210
+ const result = generateDeclarations('text-lg');
211
+ expect(result).toContain('font-size: var(--text-lg)');
212
+ expect(result).toContain('line-height');
213
+ });
214
+
215
+ it('generates font-size for text-base', () => {
216
+ const result = generateDeclarations('text-base');
217
+ expect(result).toContain('font-size: var(--text-base)');
218
+ });
219
+
220
+ // ── Border radius ─────────────────────────────────────────────────────────
221
+
222
+ it('generates border-radius for rounded', () => {
223
+ expect(generateDeclarations('rounded')).toBe('border-radius: var(--radius)');
224
+ });
225
+
226
+ it('generates border-radius for rounded-lg', () => {
227
+ expect(generateDeclarations('rounded-lg')).toBe('border-radius: var(--radius-lg)');
228
+ });
229
+
230
+ it('generates border-radius for rounded-full', () => {
231
+ expect(generateDeclarations('rounded-full')).toBe('border-radius: calc(infinity * 1px)');
232
+ });
233
+
234
+ it('generates border-radius for rounded-none', () => {
235
+ expect(generateDeclarations('rounded-none')).toBe('border-radius: 0');
236
+ });
237
+
238
+ // ── Border width ──────────────────────────────────────────────────────────
239
+
240
+ it('generates border-width: 1px for border', () => {
241
+ expect(generateDeclarations('border')).toBe('border-width: 1px');
242
+ });
243
+
244
+ it('generates border-width: 2px for border-2', () => {
245
+ expect(generateDeclarations('border-2')).toBe('border-width: 2px');
246
+ });
247
+
248
+ // ── Opacity ───────────────────────────────────────────────────────────────
249
+
250
+ it('generates opacity: 0.5 for opacity-50', () => {
251
+ expect(generateDeclarations('opacity-50')).toBe('opacity: 0.5');
252
+ });
253
+
254
+ it('generates opacity: 0 for opacity-0', () => {
255
+ expect(generateDeclarations('opacity-0')).toBe('opacity: 0');
256
+ });
257
+
258
+ it('generates opacity: 1 for opacity-100', () => {
259
+ expect(generateDeclarations('opacity-100')).toBe('opacity: 1');
260
+ });
261
+
262
+ // ── Z-index ───────────────────────────────────────────────────────────────
263
+
264
+ it('generates z-index for z-10', () => {
265
+ expect(generateDeclarations('z-10')).toBe('z-index: 10');
266
+ });
267
+
268
+ it('generates z-index auto for z-auto', () => {
269
+ expect(generateDeclarations('z-auto')).toBe('z-index: auto');
270
+ });
271
+
272
+ // ── Unknown ───────────────────────────────────────────────────────────────
273
+
274
+ it('returns empty string for unknown class', () => {
275
+ expect(generateDeclarations('completely-unknown-class')).toBe('');
276
+ });
277
+ });
278
+
279
+ describe('generateCSSRule', () => {
280
+ it('generates full rule for p-4', () => {
281
+ const rule = generateCSSRule('p-4');
282
+ expect(rule).toBe('.p-4 { padding: calc(var(--spacing) * 4) }');
283
+ });
284
+
285
+ it('generates full rule for bg-blue-500', () => {
286
+ const rule = generateCSSRule('bg-blue-500');
287
+ expect(rule).toBe('.bg-blue-500 { background-color: var(--color-blue-500) }');
288
+ });
289
+
290
+ it('generates rule with :hover pseudo for hover:bg-blue-600', () => {
291
+ const rule = generateCSSRule('hover:bg-blue-600');
292
+ expect(rule).toBe('.hover\\:bg-blue-600:hover { background-color: var(--color-blue-600) }');
293
+ });
294
+
295
+ it('generates rule with .dark prefix for dark:bg-gray-900', () => {
296
+ const rule = generateCSSRule('dark:bg-gray-900');
297
+ expect(rule).toBe('.dark .dark\\:bg-gray-900 { background-color: var(--color-gray-900) }');
298
+ });
299
+
300
+ it('generates plain rule body for sm:p-4 (no @media — tier handles wrapping)', () => {
301
+ // generateCSSRule returns the rule; the @media is added by injector.wrapForTier
302
+ const rule = generateCSSRule('sm:p-4');
303
+ expect(rule).toBe('.sm\\:p-4 { padding: calc(var(--spacing) * 4) }');
304
+ });
305
+
306
+ it('handles stacked sm:hover:bg-blue-600', () => {
307
+ const rule = generateCSSRule('sm:hover:bg-blue-600');
308
+ expect(rule).toContain(':hover');
309
+ expect(rule).toContain('background-color: var(--color-blue-600)');
310
+ });
311
+
312
+ it('returns empty string for unknown class', () => {
313
+ expect(generateCSSRule('unknown-xyz-class')).toBe('');
314
+ });
315
+
316
+ // Breakpoint cascade test — key scenario from spec
317
+ it('produces different rules for lg, md, sm (tier determines cascade, not key order)', () => {
318
+ // Keys sorted alphabetically by linter: lg, md, sm
319
+ // generateCSSRule for each is independent — cascade correctness
320
+ // is guaranteed by injector tier ordering (sheets array index), not rule content
321
+ const lgRule = generateCSSRule('lg:p-8');
322
+ const mdRule = generateCSSRule('md:p-6');
323
+ const smRule = generateCSSRule('sm:p-4');
324
+
325
+ expect(lgRule).toContain('padding: calc(var(--spacing) * 8)');
326
+ expect(mdRule).toContain('padding: calc(var(--spacing) * 6)');
327
+ expect(smRule).toContain('padding: calc(var(--spacing) * 4)');
328
+
329
+ // All use the same @media wrapper approach (applied by injector tier)
330
+ // CSS content is distinct per tier
331
+ expect(lgRule).not.toEqual(mdRule);
332
+ expect(mdRule).not.toEqual(smRule);
333
+ });
334
+
335
+ it('handles arbitrary value p-[13px]', () => {
336
+ const rule = generateCSSRule('p-[13px]');
337
+ expect(rule).toBe('.p-\\[13px\\] { padding: 13px }');
338
+ });
339
+ });
@@ -0,0 +1,222 @@
1
+ /**
2
+ * Tests for the CSSOM injector module.
3
+ * Uses a simulated CSSStyleSheet environment.
4
+ */
5
+
6
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
7
+
8
+ import { cleanup, injectRule, isInjected, resolveTier, TIER_ORDER } from '../src/injector.js';
9
+
10
+ // ── Mock constructable stylesheets ────────────────────────────────────────────
11
+
12
+ /**
13
+ *
14
+ */
15
+ class MockCSSStyleSheet {
16
+ cssRules: Array<{ cssText: string }> = [];
17
+ /**
18
+ * @param text - CSS rule text to insert
19
+ * @param index - position in cssRules at which to insert the rule
20
+ */
21
+ insertRule(text: string, index: number): void {
22
+ this.cssRules.splice(index, 0, { cssText: text });
23
+ }
24
+ }
25
+
26
+ // Track the current value of adoptedStyleSheets via the mock document object.
27
+ // The injector does `document.adoptedStyleSheets = [...]` (assignment), so we
28
+ // must use a getter/setter to capture that and update mockAdoptedSheets in place.
29
+ let _adoptedSheets: MockCSSStyleSheet[] = [];
30
+ const mockAdoptedSheets: MockCSSStyleSheet[] = [];
31
+
32
+ const mockDocument = {
33
+ get adoptedStyleSheets() { return _adoptedSheets; },
34
+ set adoptedStyleSheets(val: MockCSSStyleSheet[]) {
35
+ _adoptedSheets = val;
36
+ // Keep mockAdoptedSheets in sync so tests can reference it
37
+ mockAdoptedSheets.length = 0;
38
+ mockAdoptedSheets.push(...val);
39
+ },
40
+ head: { appendChild: vi.fn() },
41
+ createElement: vi.fn(() => ({
42
+ id: '',
43
+ remove: vi.fn(),
44
+ sheet: { insertRule: vi.fn(), cssRules: [] },
45
+ })),
46
+ };
47
+
48
+ beforeEach(() => {
49
+ _adoptedSheets = [];
50
+ mockAdoptedSheets.length = 0;
51
+
52
+ vi.stubGlobal('CSSStyleSheet', MockCSSStyleSheet);
53
+ vi.stubGlobal('document', mockDocument);
54
+ });
55
+
56
+ afterEach(() => {
57
+ cleanup();
58
+ vi.unstubAllGlobals();
59
+ });
60
+
61
+ // ── resolveTier ───────────────────────────────────────────────────────────────
62
+
63
+ describe('resolveTier', () => {
64
+ it('returns base for non-breakpoint class', () => {
65
+ expect(resolveTier('p-4')).toBe('base');
66
+ expect(resolveTier('bg-blue-500')).toBe('base');
67
+ expect(resolveTier('flex')).toBe('base');
68
+ });
69
+
70
+ it('returns base for hover variant (not a breakpoint)', () => {
71
+ expect(resolveTier('hover:bg-blue-500')).toBe('base');
72
+ });
73
+
74
+ it('returns base for dark variant', () => {
75
+ expect(resolveTier('dark:bg-gray-900')).toBe('base');
76
+ });
77
+
78
+ it('returns sm for sm:p-4', () => {
79
+ expect(resolveTier('sm:p-4')).toBe('sm');
80
+ });
81
+
82
+ it('returns lg for lg:p-8', () => {
83
+ expect(resolveTier('lg:p-8')).toBe('lg');
84
+ });
85
+
86
+ it('returns max-sm for max-sm:p-4', () => {
87
+ expect(resolveTier('max-sm:p-4')).toBe('max-sm');
88
+ });
89
+
90
+ it('returns first breakpoint segment for stacked sm:hover:', () => {
91
+ expect(resolveTier('sm:hover:bg-blue-600')).toBe('sm');
92
+ });
93
+ });
94
+
95
+ // ── TIER_ORDER ────────────────────────────────────────────────────────────────
96
+
97
+ describe('TIER_ORDER cascade ordering', () => {
98
+ it('has 21 tiers', () => {
99
+ expect(TIER_ORDER).toHaveLength(21);
100
+ });
101
+
102
+ it('base is first (index 0)', () => {
103
+ expect(TIER_ORDER[0]).toBe('base');
104
+ });
105
+
106
+ it('sm comes before md comes before lg (ascending min-width)', () => {
107
+ const smIdx = TIER_ORDER.indexOf('sm');
108
+ const mdIdx = TIER_ORDER.indexOf('md');
109
+ const lgIdx = TIER_ORDER.indexOf('lg');
110
+ expect(smIdx).toBeLessThan(mdIdx);
111
+ expect(mdIdx).toBeLessThan(lgIdx);
112
+ });
113
+
114
+ it('max-2xl comes before max-sm (descending for max-width)', () => {
115
+ // max-sm must have HIGHER index (later in array = wins at small viewport)
116
+ const max2xlIdx = TIER_ORDER.indexOf('max-2xl');
117
+ const maxSmIdx = TIER_ORDER.indexOf('max-sm');
118
+ expect(max2xlIdx).toBeLessThan(maxSmIdx);
119
+ });
120
+
121
+ it('container @sm before @md before @lg', () => {
122
+ const smIdx = TIER_ORDER.indexOf('@sm');
123
+ const mdIdx = TIER_ORDER.indexOf('@md');
124
+ const lgIdx = TIER_ORDER.indexOf('@lg');
125
+ expect(smIdx).toBeLessThan(mdIdx);
126
+ expect(mdIdx).toBeLessThan(lgIdx);
127
+ });
128
+
129
+ it('@max-2xl comes before @max-sm', () => {
130
+ const max2xlIdx = TIER_ORDER.indexOf('@max-2xl');
131
+ const maxSmIdx = TIER_ORDER.indexOf('@max-sm');
132
+ expect(max2xlIdx).toBeLessThan(maxSmIdx);
133
+ });
134
+ });
135
+
136
+ // ── injectRule ────────────────────────────────────────────────────────────────
137
+
138
+ describe('injectRule', () => {
139
+ it('initializes 21 adopted stylesheets on first inject', () => {
140
+ injectRule('p-4', '.p-4 { padding: calc(var(--spacing) * 4) }', 'base');
141
+ expect(mockAdoptedSheets).toHaveLength(21);
142
+ });
143
+
144
+ it('injects rule into correct sheet by tier', () => {
145
+ injectRule('p-4', '.p-4 { padding: 1rem }', 'base');
146
+ injectRule('sm:p-4', '.sm\\:p-4 { padding: 1rem }', 'sm');
147
+
148
+ // Base sheet is index 0, sm sheet is index 1
149
+ const baseSheet = mockAdoptedSheets[0] as unknown as MockCSSStyleSheet;
150
+ const smSheet = mockAdoptedSheets[1] as unknown as MockCSSStyleSheet;
151
+
152
+ expect(baseSheet.cssRules.some(r => r.cssText.includes('.p-4'))).toBe(true);
153
+ expect(smSheet.cssRules.length).toBeGreaterThan(0);
154
+ });
155
+
156
+ it('wraps non-base tier rules in @media', () => {
157
+ injectRule('sm:p-4', '.sm\\:p-4 { padding: 1rem }', 'sm');
158
+
159
+ const smSheet = mockAdoptedSheets[1] as unknown as MockCSSStyleSheet;
160
+ const rule = smSheet.cssRules[0];
161
+ expect(rule.cssText).toContain('@media');
162
+ expect(rule.cssText).toContain('40rem');
163
+ });
164
+
165
+ it('wraps max-sm tier in max-width media query', () => {
166
+ injectRule('max-sm:p-4', '.max-sm\\:p-4 { padding: 1rem }', 'max-sm');
167
+
168
+ const maxSmIdx = TIER_ORDER.indexOf('max-sm');
169
+ const maxSmSheet = mockAdoptedSheets[maxSmIdx] as unknown as MockCSSStyleSheet;
170
+ expect(maxSmSheet.cssRules[0].cssText).toContain('max-width');
171
+ });
172
+
173
+ it('does not double-inject same class', () => {
174
+ injectRule('p-4', '.p-4 { padding: 1rem }', 'base');
175
+ injectRule('p-4', '.p-4 { padding: 1rem }', 'base'); // duplicate
176
+
177
+ const baseSheet = mockAdoptedSheets[0] as unknown as MockCSSStyleSheet;
178
+ const p4Rules = baseSheet.cssRules.filter(r => r.cssText.includes('.p-4'));
179
+ expect(p4Rules).toHaveLength(1);
180
+ });
181
+
182
+ it('isInjected returns true after inject', () => {
183
+ expect(isInjected('p-4')).toBe(false);
184
+ injectRule('p-4', '.p-4 { padding: 1rem }', 'base');
185
+ expect(isInjected('p-4')).toBe(true);
186
+ });
187
+
188
+ it('marks class as injected even when cssRule is empty (unknown class)', () => {
189
+ injectRule('unknown-class', '', 'base');
190
+ expect(isInjected('unknown-class')).toBe(true);
191
+ });
192
+ });
193
+
194
+ // ── cleanup ───────────────────────────────────────────────────────────────────
195
+
196
+ describe('cleanup', () => {
197
+ it('removes all csszyx sheets from adoptedStyleSheets', () => {
198
+ injectRule('p-4', '.p-4 { padding: 1rem }', 'base');
199
+ expect(mockAdoptedSheets).toHaveLength(21);
200
+
201
+ cleanup();
202
+ expect(mockAdoptedSheets).toHaveLength(0);
203
+ });
204
+
205
+ it('clears injected cache', () => {
206
+ injectRule('p-4', '.p-4 { padding: 1rem }', 'base');
207
+ expect(isInjected('p-4')).toBe(true);
208
+
209
+ cleanup();
210
+ expect(isInjected('p-4')).toBe(false);
211
+ });
212
+
213
+ it('allows re-injection after cleanup', () => {
214
+ injectRule('p-4', '.p-4 { padding: 1rem }', 'base');
215
+ cleanup();
216
+
217
+ // Re-init happens on next inject
218
+ injectRule('p-4', '.p-4 { padding: 1rem }', 'base');
219
+ expect(isInjected('p-4')).toBe(true);
220
+ expect(mockAdoptedSheets).toHaveLength(21);
221
+ });
222
+ });