@lumaui/angular 0.1.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,379 @@
1
+ import { ComponentFixture, TestBed } from '@angular/core/testing';
2
+
3
+ import { CardComponent } from './card.component';
4
+ import { Component } from '@angular/core';
5
+
6
+ // ============================================================
7
+ // TEST HOST COMPONENTS
8
+ // ============================================================
9
+
10
+ @Component({
11
+ template: `
12
+ <luma-card>
13
+ <div class="test-content">Projected content</div>
14
+ </luma-card>
15
+ `,
16
+ imports: [CardComponent],
17
+ })
18
+ class TestHostComponent {}
19
+
20
+ // ============================================================
21
+ // TOKEN DEFINITIONS
22
+ // ============================================================
23
+
24
+ const CARD_TOKENS = {
25
+ background: 'oklch(0.99 0 0)',
26
+ gradientFrom: 'oklch(0.92 0.005 0 / 0.6)',
27
+ gradientTo: 'oklch(0.96 0.003 0 / 0.6)',
28
+ padding: '1.5rem',
29
+ } as const;
30
+
31
+ const DARK_CARD_TOKENS = {
32
+ background: 'oklch(0.17 0 0)',
33
+ gradientFrom: 'oklch(0.25 0.01 0 / 0.6)',
34
+ gradientTo: 'oklch(0.2 0.008 0 / 0.6)',
35
+ } as const;
36
+
37
+ // ============================================================
38
+ // TOKEN SETUP/CLEANUP UTILITIES
39
+ // ============================================================
40
+
41
+ function setupCardTokens(): void {
42
+ const root = document.documentElement;
43
+ root.style.setProperty('--luma-card-background', CARD_TOKENS.background);
44
+ root.style.setProperty('--luma-card-gradient-from', CARD_TOKENS.gradientFrom);
45
+ root.style.setProperty('--luma-card-gradient-to', CARD_TOKENS.gradientTo);
46
+ root.style.setProperty('--luma-card-padding', CARD_TOKENS.padding);
47
+ }
48
+
49
+ function cleanupCardTokens(): void {
50
+ const root = document.documentElement;
51
+ root.style.removeProperty('--luma-card-background');
52
+ root.style.removeProperty('--luma-card-gradient-from');
53
+ root.style.removeProperty('--luma-card-gradient-to');
54
+ root.style.removeProperty('--luma-card-padding');
55
+ root.classList.remove('dark');
56
+ }
57
+
58
+ function applyDarkTheme(): void {
59
+ const root = document.documentElement;
60
+ root.classList.add('dark');
61
+ root.style.setProperty('--luma-card-background', DARK_CARD_TOKENS.background);
62
+ root.style.setProperty(
63
+ '--luma-card-gradient-from',
64
+ DARK_CARD_TOKENS.gradientFrom,
65
+ );
66
+ root.style.setProperty(
67
+ '--luma-card-gradient-to',
68
+ DARK_CARD_TOKENS.gradientTo,
69
+ );
70
+ }
71
+
72
+ describe('CardComponent', () => {
73
+ let component: CardComponent;
74
+ let fixture: ComponentFixture<CardComponent>;
75
+
76
+ beforeEach(async () => {
77
+ await TestBed.configureTestingModule({
78
+ imports: [CardComponent],
79
+ }).compileComponents();
80
+
81
+ fixture = TestBed.createComponent(CardComponent);
82
+ component = fixture.componentInstance;
83
+ await fixture.whenStable();
84
+ });
85
+
86
+ it('should create', () => {
87
+ expect(component).toBeTruthy();
88
+ });
89
+
90
+ it('should apply wrapper classes with gradient border technique', () => {
91
+ fixture.detectChanges();
92
+
93
+ const wrapperClasses = component.wrapperClasses();
94
+
95
+ // Gradient border technique classes
96
+ expect(wrapperClasses).toContain('relative');
97
+ expect(wrapperClasses).toContain('lm-rounded-lg');
98
+ expect(wrapperClasses).toContain('p-[1px]');
99
+ expect(wrapperClasses).toContain('bg-gradient-to-b');
100
+ expect(wrapperClasses).toContain('lm-from-card-gradient-from');
101
+ expect(wrapperClasses).toContain('lm-to-card-gradient-to');
102
+ });
103
+
104
+ it('should apply content classes with background and padding', () => {
105
+ fixture.detectChanges();
106
+
107
+ const contentClasses = component.contentClasses();
108
+
109
+ // Inner content classes
110
+ expect(contentClasses).toContain('rounded-[17px]');
111
+ expect(contentClasses).toContain('lm-bg-card-background');
112
+ expect(contentClasses).toContain('lm-p-card');
113
+ expect(contentClasses).toContain('lm-text-primary');
114
+ });
115
+
116
+ it('should render wrapper and content divs in template', () => {
117
+ fixture.detectChanges();
118
+
119
+ const compiled = fixture.nativeElement as HTMLElement;
120
+ const wrapperDiv = compiled.querySelector('div:first-child');
121
+ const contentDiv = compiled.querySelector('div:first-child > div');
122
+
123
+ expect(wrapperDiv).toBeTruthy();
124
+ expect(contentDiv).toBeTruthy();
125
+ });
126
+
127
+ it('should project content correctly via ng-content', async () => {
128
+ const hostFixture = TestBed.createComponent(TestHostComponent);
129
+ await hostFixture.whenStable();
130
+ hostFixture.detectChanges();
131
+
132
+ const compiled = hostFixture.nativeElement as HTMLElement;
133
+ const projectedContent = compiled.querySelector('.test-content');
134
+
135
+ expect(projectedContent).toBeTruthy();
136
+ expect(projectedContent?.textContent).toBe('Projected content');
137
+ });
138
+
139
+ it('should use OnPush change detection', () => {
140
+ // Verify component is configured with OnPush
141
+ // OnPush means the component only checks for changes when inputs change
142
+ // or events are triggered, improving performance
143
+ expect(component).toBeTruthy();
144
+
145
+ // The component is stateless with only computed signals,
146
+ // which work perfectly with OnPush change detection
147
+ const wrapperClasses = component.wrapperClasses();
148
+ const contentClasses = component.contentClasses();
149
+
150
+ expect(wrapperClasses).toBeTruthy();
151
+ expect(contentClasses).toBeTruthy();
152
+ });
153
+
154
+ it('should have computed signal for wrapperClasses', () => {
155
+ // Verify wrapperClasses is a computed signal
156
+ expect(typeof component.wrapperClasses).toBe('function');
157
+
158
+ // Should return string when called
159
+ const classes = component.wrapperClasses();
160
+ expect(typeof classes).toBe('string');
161
+ });
162
+
163
+ it('should have computed signal for contentClasses', () => {
164
+ // Verify contentClasses is a computed signal
165
+ expect(typeof component.contentClasses).toBe('function');
166
+
167
+ // Should return string when called
168
+ const classes = component.contentClasses();
169
+ expect(typeof classes).toBe('string');
170
+ });
171
+
172
+ it('should maintain consistent class generation', () => {
173
+ fixture.detectChanges();
174
+
175
+ // Multiple calls should return the same classes
176
+ const wrapperClasses1 = component.wrapperClasses();
177
+ const wrapperClasses2 = component.wrapperClasses();
178
+ const contentClasses1 = component.contentClasses();
179
+ const contentClasses2 = component.contentClasses();
180
+
181
+ expect(wrapperClasses1).toBe(wrapperClasses2);
182
+ expect(contentClasses1).toBe(contentClasses2);
183
+ });
184
+
185
+ // ============================================================
186
+ // DESIGN TOKEN INTEGRATION TESTS
187
+ // ============================================================
188
+
189
+ describe('Design Token Integration', () => {
190
+ beforeEach(() => {
191
+ setupCardTokens();
192
+ });
193
+
194
+ afterEach(() => {
195
+ cleanupCardTokens();
196
+ });
197
+
198
+ // ----------------------------------------------------------
199
+ // CSS Variable Definition Tests
200
+ // ----------------------------------------------------------
201
+
202
+ describe('CSS Variable Definition', () => {
203
+ it('should define --luma-card-background css variable', () => {
204
+ const value = getComputedStyle(document.documentElement)
205
+ .getPropertyValue('--luma-card-background')
206
+ .trim();
207
+ expect(value).toBe(CARD_TOKENS.background);
208
+ });
209
+
210
+ it('should define --luma-card-gradient-from css variable', () => {
211
+ const value = getComputedStyle(document.documentElement)
212
+ .getPropertyValue('--luma-card-gradient-from')
213
+ .trim();
214
+ expect(value).toBe(CARD_TOKENS.gradientFrom);
215
+ });
216
+
217
+ it('should define --luma-card-gradient-to css variable', () => {
218
+ const value = getComputedStyle(document.documentElement)
219
+ .getPropertyValue('--luma-card-gradient-to')
220
+ .trim();
221
+ expect(value).toBe(CARD_TOKENS.gradientTo);
222
+ });
223
+
224
+ it('should define --luma-card-padding css variable', () => {
225
+ const value = getComputedStyle(document.documentElement)
226
+ .getPropertyValue('--luma-card-padding')
227
+ .trim();
228
+ expect(value).toBe(CARD_TOKENS.padding);
229
+ });
230
+ });
231
+
232
+ // ----------------------------------------------------------
233
+ // Token Consumption Tests
234
+ // ----------------------------------------------------------
235
+ // CSS variables are inherited from document.documentElement.
236
+ // We verify that tokens are accessible and component uses the correct classes.
237
+
238
+ describe('Token Consumption', () => {
239
+ it('should have access to --luma-card-background token', () => {
240
+ fixture.detectChanges();
241
+
242
+ const value = getComputedStyle(document.documentElement)
243
+ .getPropertyValue('--luma-card-background')
244
+ .trim();
245
+ expect(value).toBe(CARD_TOKENS.background);
246
+ });
247
+
248
+ it('should have access to --luma-card-gradient-from token', () => {
249
+ fixture.detectChanges();
250
+
251
+ const value = getComputedStyle(document.documentElement)
252
+ .getPropertyValue('--luma-card-gradient-from')
253
+ .trim();
254
+ expect(value).toBe(CARD_TOKENS.gradientFrom);
255
+ });
256
+
257
+ it('should have access to --luma-card-gradient-to token', () => {
258
+ fixture.detectChanges();
259
+
260
+ const value = getComputedStyle(document.documentElement)
261
+ .getPropertyValue('--luma-card-gradient-to')
262
+ .trim();
263
+ expect(value).toBe(CARD_TOKENS.gradientTo);
264
+ });
265
+
266
+ it('should have access to --luma-card-padding token', () => {
267
+ fixture.detectChanges();
268
+
269
+ const value = getComputedStyle(document.documentElement)
270
+ .getPropertyValue('--luma-card-padding')
271
+ .trim();
272
+ expect(value).toBe(CARD_TOKENS.padding);
273
+ });
274
+ });
275
+
276
+ // ----------------------------------------------------------
277
+ // Token Override Tests
278
+ // ----------------------------------------------------------
279
+
280
+ describe('Token Override', () => {
281
+ it('should respect custom background token override', () => {
282
+ const customBackground = '#ffffff';
283
+ document.documentElement.style.setProperty(
284
+ '--luma-card-background',
285
+ customBackground,
286
+ );
287
+ fixture.detectChanges();
288
+
289
+ const value = getComputedStyle(document.documentElement)
290
+ .getPropertyValue('--luma-card-background')
291
+ .trim();
292
+ expect(value).toBe(customBackground);
293
+ });
294
+
295
+ it('should respect custom padding token override', () => {
296
+ const customPadding = '2rem';
297
+ document.documentElement.style.setProperty(
298
+ '--luma-card-padding',
299
+ customPadding,
300
+ );
301
+ fixture.detectChanges();
302
+
303
+ const value = getComputedStyle(document.documentElement)
304
+ .getPropertyValue('--luma-card-padding')
305
+ .trim();
306
+ expect(value).toBe(customPadding);
307
+ });
308
+
309
+ it('should respect custom gradient-from token override', () => {
310
+ const customGradient = 'oklch(0.85 0.01 0 / 0.8)';
311
+ document.documentElement.style.setProperty(
312
+ '--luma-card-gradient-from',
313
+ customGradient,
314
+ );
315
+ fixture.detectChanges();
316
+
317
+ const value = getComputedStyle(document.documentElement)
318
+ .getPropertyValue('--luma-card-gradient-from')
319
+ .trim();
320
+ expect(value).toBe(customGradient);
321
+ });
322
+
323
+ it('should respect custom gradient-to token override', () => {
324
+ const customGradient = 'oklch(0.90 0.005 0 / 0.8)';
325
+ document.documentElement.style.setProperty(
326
+ '--luma-card-gradient-to',
327
+ customGradient,
328
+ );
329
+ fixture.detectChanges();
330
+
331
+ const value = getComputedStyle(document.documentElement)
332
+ .getPropertyValue('--luma-card-gradient-to')
333
+ .trim();
334
+ expect(value).toBe(customGradient);
335
+ });
336
+ });
337
+
338
+ // ----------------------------------------------------------
339
+ // Dark Theme Tests
340
+ // ----------------------------------------------------------
341
+
342
+ describe('Dark Theme', () => {
343
+ beforeEach(() => {
344
+ applyDarkTheme();
345
+ });
346
+
347
+ it('should have access to dark theme background token', () => {
348
+ fixture.detectChanges();
349
+
350
+ const value = getComputedStyle(document.documentElement)
351
+ .getPropertyValue('--luma-card-background')
352
+ .trim();
353
+ expect(value).toBe(DARK_CARD_TOKENS.background);
354
+ });
355
+
356
+ it('should have access to dark theme gradient-from token', () => {
357
+ fixture.detectChanges();
358
+
359
+ const value = getComputedStyle(document.documentElement)
360
+ .getPropertyValue('--luma-card-gradient-from')
361
+ .trim();
362
+ expect(value).toBe(DARK_CARD_TOKENS.gradientFrom);
363
+ });
364
+
365
+ it('should have access to dark theme gradient-to token', () => {
366
+ fixture.detectChanges();
367
+
368
+ const value = getComputedStyle(document.documentElement)
369
+ .getPropertyValue('--luma-card-gradient-to')
370
+ .trim();
371
+ expect(value).toBe(DARK_CARD_TOKENS.gradientTo);
372
+ });
373
+
374
+ it('should have dark class on document element', () => {
375
+ expect(document.documentElement.classList.contains('dark')).toBe(true);
376
+ });
377
+ });
378
+ });
379
+ });
@@ -0,0 +1,33 @@
1
+ import {
2
+ ChangeDetectionStrategy,
3
+ Component,
4
+ computed,
5
+ input,
6
+ } from '@angular/core';
7
+ import {
8
+ cardVariants,
9
+ cardContentVariants,
10
+ type CardVariant,
11
+ } from '@lumaui/core';
12
+
13
+ @Component({
14
+ selector: 'luma-card',
15
+ templateUrl: './card.component.html',
16
+ changeDetection: ChangeDetectionStrategy.OnPush,
17
+ })
18
+ export class CardComponent {
19
+ /**
20
+ * Card visual style variant
21
+ * - default: Gradient border wrapper style (default)
22
+ * - shadow: Elevated card with shadow for primary content
23
+ * - nested: Subtle background for sections within cards
24
+ * - preview: For documentation examples
25
+ */
26
+ lmVariant = input<CardVariant>('default');
27
+
28
+ // Computed class strings based on variant
29
+ wrapperClasses = computed(() => cardVariants({ variant: this.lmVariant() }));
30
+ contentClasses = computed(() =>
31
+ cardContentVariants({ variant: this.lmVariant() }),
32
+ );
33
+ }